feat(sync): add flatten() function (#43)

This commit is contained in:
richard 2025-01-25 16:11:34 -08:00 • committed by GitHub
parent 63388761f8
commit 8063ea3142
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 89 additions and 0 deletions

View file

@ -0,0 +1,28 @@
local types = require(script.Parent.types)
type AtomMap = types.AtomMap
type NestedAtomMap = {
[string]: NestedAtomMap | () -> (),
}
local function flatten(atoms: NestedAtomMap): AtomMap
local result: AtomMap = {}
local function visit(node: NestedAtomMap, path: string)
for key, value in node do
local location = if path == "" then key else path .. "/" .. key
if type(value) == "table" then
visit(value, location)
else
result[location] = value
end
end
end
visit(atoms, "")
return result
end
return flatten

View file

@ -5,6 +5,8 @@ export as namespace CharmSync;
type Cleanup = () => void; type Cleanup = () => void;
type Key = string | number | symbol;
declare namespace CharmSync { declare namespace CharmSync {
type AtomMap = Record<string, Atom<any>>; type AtomMap = Record<string, Atom<any>>;
@ -60,6 +62,39 @@ declare namespace CharmSync {
*/ */
function isNone(value: unknown): value is None; function isNone(value: unknown): value is None;
interface NestedAtomMap {
readonly [key: string]: NestedAtomMap | Atom<any>;
}
type AppendPath<Path extends Key | undefined, Name extends Key> = undefined extends Path
? Name
: Name extends string
? Path extends string
? `${Path}/${Name}`
: never
: never;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
type IntersectValues<T> = UnionToIntersection<{ readonly [K in keyof T]: T[K] }[keyof T]>;
type FlattenNestedAtoms<Atoms extends NestedAtomMap, Path extends Key | undefined = undefined> = IntersectValues<{
readonly [Name in keyof Atoms as AppendPath<Path, Name>]: Atoms[Name] extends Atom<any>
? { readonly [K in AppendPath<Path, Name>]: Atoms[Name] }
: Atoms[Name] extends NestedAtomMap
? FlattenNestedAtoms<Atoms[Name], AppendPath<Path, Name>>
: never;
}>;
/**
* Flattens a nested atom map into a single object with slash-separated
* keys. Useful for recursively collecting atoms returned by modules.
*
* @param atoms The nested atom map to flatten.
* @return A flattened atom map.
*/
function flatten<Atoms extends NestedAtomMap>(atoms: Atoms): FlattenNestedAtoms<Atoms>;
type DataTypes = { type DataTypes = {
[P in keyof CheckableTypes as P extends keyof CheckablePrimitives ? never : P]: CheckableTypes[P]; [P in keyof CheckableTypes as P extends keyof CheckablePrimitives ? never : P]: CheckableTypes[P];
}; };

View file

@ -1,4 +1,5 @@
local client = require(script.client) local client = require(script.client)
local flatten = require(script.flatten)
local patch = require(script.patch) local patch = require(script.patch)
local server = require(script.server) local server = require(script.server)
local types = require(script.types) local types = require(script.types)
@ -12,5 +13,6 @@ export type SyncPayload = types.SyncPayload
return { return {
client = client, client = client,
server = server, server = server,
flatten = flatten,
isNone = patch.isNone, isNone = patch.isNone,
} }

View file

@ -0,0 +1,24 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Charm = require(ReplicatedStorage.DevPackages.Charm)
local atom = Charm.atom
local CharmSync = require(ReplicatedStorage.DevPackages.CharmSync)
local flatten = CharmSync.flatten
return function()
it("flattens atoms", function()
local input = {
a = atom(0),
b = { c = atom(0) },
d = { e = { f = atom(0) } },
}
local result = flatten(input)
expect(result.a).to.equal(input.a)
expect(result["b/c"]).to.equal(input.b.c)
expect(result["d/e/f"]).to.equal(input.d.e.f)
expect(result.b).to.never.be.ok()
expect(result.d).to.never.be.ok()
end)
end