statewire 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -13
- package/dist/client/changes.d.ts +2 -2
- package/dist/client/changes.d.ts.map +1 -1
- package/dist/client/changes.js +3 -10
- package/dist/client/changes.js.map +1 -1
- package/dist/client/connection.d.ts +18 -39
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +61 -54
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts +7 -11
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +58 -55
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +8 -25
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +56 -36
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +1 -1
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/client/timing.d.ts +2 -1
- package/dist/client/timing.d.ts.map +1 -1
- package/dist/client/timing.js +5 -1
- package/dist/client/timing.js.map +1 -1
- package/dist/host/adapter.d.ts +8 -0
- package/dist/host/adapter.d.ts.map +1 -0
- package/dist/host/adapter.js +23 -0
- package/dist/host/adapter.js.map +1 -0
- package/dist/host/draft.d.ts +21 -7
- package/dist/host/draft.d.ts.map +1 -1
- package/dist/host/draft.js +190 -103
- package/dist/host/draft.js.map +1 -1
- package/dist/host/host.d.ts +18 -35
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +81 -53
- package/dist/host/host.js.map +1 -1
- package/dist/host/socket.d.ts +14 -0
- package/dist/host/socket.d.ts.map +1 -0
- package/dist/host/socket.js +459 -0
- package/dist/host/socket.js.map +1 -0
- package/dist/host-internal.d.ts +27 -0
- package/dist/host-internal.d.ts.map +1 -0
- package/dist/host-internal.js +3 -0
- package/dist/host.d.ts +44 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +5 -28
- package/dist/index.js +2 -3
- package/dist/statewire.d.ts +94 -10
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +23 -35
- package/dist/statewire.js.map +1 -1
- package/dist/transports/fake-sse.d.ts +12 -0
- package/dist/transports/fake-sse.d.ts.map +1 -0
- package/dist/transports/fake-sse.js +39 -0
- package/dist/transports/fake-sse.js.map +1 -0
- package/dist/transports/fixtures.d.ts +33 -0
- package/dist/transports/fixtures.d.ts.map +1 -0
- package/dist/transports/fixtures.js +50 -0
- package/dist/transports/fixtures.js.map +1 -0
- package/dist/transports/remote.d.ts +6 -13
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +5 -24
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +10 -20
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +42 -41
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +4 -3
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +19 -46
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.d.ts +4 -13
- package/dist/transports/ws.d.ts.map +1 -1
- package/dist/transports/ws.js +57 -60
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +53 -100
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util/draftTraps.d.ts +6 -0
- package/dist/util/draftTraps.d.ts.map +1 -0
- package/dist/util/draftTraps.js +25 -0
- package/dist/util/draftTraps.js.map +1 -0
- package/dist/util/macrotask.d.ts +5 -0
- package/dist/util/macrotask.d.ts.map +1 -0
- package/dist/util/macrotask.js +25 -0
- package/dist/util/macrotask.js.map +1 -0
- package/dist/util/methodProxy.d.ts +5 -0
- package/dist/util/methodProxy.d.ts.map +1 -0
- package/dist/util/methodProxy.js +17 -0
- package/dist/util/methodProxy.js.map +1 -0
- package/dist/util/randomId.d.ts +5 -0
- package/dist/util/randomId.d.ts.map +1 -0
- package/dist/util/randomId.js +6 -0
- package/dist/util/randomId.js.map +1 -0
- package/dist/{client/optimistic.d.ts → util/recordMutations.d.ts} +2 -2
- package/dist/util/recordMutations.d.ts.map +1 -0
- package/dist/{client/optimistic.js → util/recordMutations.js} +5 -24
- package/dist/util/recordMutations.js.map +1 -0
- package/dist/util/statePath.d.ts +14 -0
- package/dist/util/statePath.d.ts.map +1 -0
- package/dist/util/statePath.js +31 -0
- package/dist/util/statePath.js.map +1 -0
- package/dist/wire/meta.d.ts +6 -1
- package/dist/wire/meta.d.ts.map +1 -1
- package/dist/wire/meta.js +30 -28
- package/dist/wire/meta.js.map +1 -1
- package/dist/wire/ops.d.ts +5 -26
- package/dist/wire/ops.d.ts.map +1 -1
- package/dist/wire/ops.js +67 -80
- package/dist/wire/ops.js.map +1 -1
- package/package.json +15 -11
- package/src/client/changes.ts +5 -15
- package/src/client/connection.ts +87 -113
- package/src/client/core.ts +57 -91
- package/src/client/session.ts +87 -70
- package/src/client/sessionStore.ts +1 -1
- package/src/client/timing.ts +6 -0
- package/src/host/adapter.ts +33 -0
- package/src/host/draft.ts +293 -125
- package/src/host/host.ts +107 -106
- package/src/host/socket.ts +542 -0
- package/src/host-internal.ts +28 -0
- package/src/host.ts +61 -0
- package/src/index.ts +2 -62
- package/src/statewire.ts +154 -61
- package/src/transports/fake-sse.ts +51 -0
- package/src/transports/fixtures.ts +60 -0
- package/src/transports/remote.ts +12 -54
- package/src/transports/sse.ts +68 -81
- package/src/transports/testing.ts +27 -59
- package/src/transports/ws.ts +76 -92
- package/src/types.ts +50 -131
- package/src/util/draftTraps.ts +32 -0
- package/src/util/macrotask.ts +25 -0
- package/src/util/methodProxy.ts +16 -0
- package/src/util/randomId.ts +4 -0
- package/src/{client/optimistic.ts → util/recordMutations.ts} +0 -0
- package/src/util/statePath.ts +61 -0
- package/src/wire/meta.ts +42 -47
- package/src/wire/ops.ts +107 -106
- package/LICENSE +0 -21
- package/dist/client/optimistic.d.ts.map +0 -1
- package/dist/client/optimistic.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/server.d.ts +0 -15
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -3
- package/src/server.ts +0 -32
package/dist/wire/meta.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { walkStatePath } from "../util/statePath.js";
|
|
1
2
|
//#region src/wire/meta.ts
|
|
2
3
|
const fault = (message) => {
|
|
3
4
|
throw new Error(`statewire: protocol error: ${message}`);
|
|
@@ -11,21 +12,13 @@ const reviveValue = (value, meta) => {
|
|
|
11
12
|
const path = typeof entry === "object" && entry !== null ? entry.path : null;
|
|
12
13
|
if (!isPath(path)) return fault("meta entry must carry a path array");
|
|
13
14
|
if (entry.type !== "date") continue;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
parent = node;
|
|
19
|
-
key = seg;
|
|
20
|
-
if (typeof seg === "string" && typeof node === "object" && node !== null && !Array.isArray(node) && Object.hasOwn(node, seg)) node = node[seg];
|
|
21
|
-
else if (typeof seg === "number" && Array.isArray(node) && seg >= 0 && seg < node.length) node = node[seg];
|
|
22
|
-
else return fault(`meta path ${path.join(".")} does not resolve`);
|
|
23
|
-
}
|
|
24
|
-
if (typeof node !== "string") return fault(`meta path ${path.join(".")} must name a string`);
|
|
25
|
-
const date = new Date(node);
|
|
15
|
+
const walked = walkStatePath(value, path, "resolve");
|
|
16
|
+
if (walked === void 0) return fault(`meta path ${path.join(".")} does not resolve`);
|
|
17
|
+
if (typeof walked.leaf !== "string") return fault(`meta path ${path.join(".")} must name a string`);
|
|
18
|
+
const date = new Date(walked.leaf);
|
|
26
19
|
if (Number.isNaN(date.getTime())) return fault(`invalid date at meta path ${path.join(".")}`);
|
|
27
|
-
if (parent ===
|
|
28
|
-
parent[key] = date;
|
|
20
|
+
if (walked.parent === void 0) return date;
|
|
21
|
+
walked.parent[walked.key] = date;
|
|
29
22
|
}
|
|
30
23
|
return value;
|
|
31
24
|
};
|
|
@@ -45,29 +38,38 @@ const reviveResponse = (response) => {
|
|
|
45
38
|
payload: reviveValue(rest.payload, meta)
|
|
46
39
|
};
|
|
47
40
|
};
|
|
41
|
+
const walkDates = (node, path, meta) => {
|
|
42
|
+
if (node instanceof Date) {
|
|
43
|
+
meta.push({
|
|
44
|
+
path,
|
|
45
|
+
type: "date"
|
|
46
|
+
});
|
|
47
|
+
return node.toISOString();
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(node)) return node.map((item, i) => walkDates(item, [...path, i], meta));
|
|
50
|
+
if (typeof node === "object" && node !== null) return Object.fromEntries(Object.keys(node).map((key) => [key, walkDates(node[key], [...path, key], meta)]));
|
|
51
|
+
return node;
|
|
52
|
+
};
|
|
48
53
|
/** Encode Date instances inside command params as canonical ISO strings,
|
|
49
54
|
* recording their paths (starting at the param index) in meta. */
|
|
50
55
|
const encodeParams = (params) => {
|
|
51
56
|
const meta = [];
|
|
52
|
-
const
|
|
53
|
-
if (node instanceof Date) {
|
|
54
|
-
meta.push({
|
|
55
|
-
path,
|
|
56
|
-
type: "date"
|
|
57
|
-
});
|
|
58
|
-
return node.toISOString();
|
|
59
|
-
}
|
|
60
|
-
if (Array.isArray(node)) return node.map((item, i) => walk(item, [...path, i]));
|
|
61
|
-
if (typeof node === "object" && node !== null) return Object.fromEntries(Object.keys(node).map((key) => [key, walk(node[key], [...path, key])]));
|
|
62
|
-
return node;
|
|
63
|
-
};
|
|
64
|
-
const encoded = params.map((param, i) => walk(param, [i]));
|
|
57
|
+
const encoded = params.map((param, i) => walkDates(param, [i], meta));
|
|
65
58
|
return meta.length > 0 ? {
|
|
66
59
|
params: encoded,
|
|
67
60
|
meta
|
|
68
61
|
} : { params: encoded };
|
|
69
62
|
};
|
|
63
|
+
/** Encode Date instances inside a wire value, recording their paths in meta. */
|
|
64
|
+
const encodeValue = (value) => {
|
|
65
|
+
const meta = [];
|
|
66
|
+
const encoded = walkDates(value, [], meta);
|
|
67
|
+
return meta.length > 0 ? {
|
|
68
|
+
value: encoded,
|
|
69
|
+
meta
|
|
70
|
+
} : { value: encoded };
|
|
71
|
+
};
|
|
70
72
|
//#endregion
|
|
71
|
-
export { encodeParams, reviveOps, reviveResponse, reviveValue };
|
|
73
|
+
export { encodeParams, encodeValue, reviveOps, reviveResponse, reviveValue };
|
|
72
74
|
|
|
73
75
|
//# sourceMappingURL=meta.js.map
|
package/dist/wire/meta.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta.js","names":["StatewireMetaEntry","path","type","fault","message","Error","isPath","Array","isArray","every","seg","Number","isInteger","reviveValue","value","meta","entry","
|
|
1
|
+
{"version":3,"file":"meta.js","names":["walkStatePath","StatewireMetaEntry","path","type","fault","message","Error","isPath","Array","isArray","every","seg","Number","isInteger","reviveValue","value","meta","entry","walked","undefined","join","leaf","date","Date","isNaN","getTime","parent","Record","key","reviveOps","ops","map","op","rest","reviveResponse","payload","response","T","walkDates","node","push","toISOString","item","i","Object","fromEntries","keys","encodeParams","params","encoded","param","length","encodeValue"],"sources":["../../src/wire/meta.ts"],"sourcesContent":["import { walkStatePath } from \"../util/statePath\";\n\nexport type StatewireMetaEntry = {\n path: (string | number)[];\n type: string;\n};\n\nconst fault = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nconst isPath = (path: unknown): path is (string | number)[] =>\n Array.isArray(path) &&\n path.every(\n (seg) =>\n typeof seg === \"string\" ||\n (typeof seg === \"number\" && Number.isInteger(seg)),\n );\n\n/** Revive meta-typed locations inside a wire value (in place). Unknown types\n * are skipped; a path that does not resolve to a valid target faults. */\nexport const reviveValue = (value: unknown, meta: unknown): unknown => {\n if (!Array.isArray(meta)) return fault(\"meta must be an array\");\n for (const entry of meta as { path?: unknown; type?: unknown }[]) {\n const path =\n typeof entry === \"object\" && entry !== null ? entry.path : null;\n if (!isPath(path)) return fault(\"meta entry must carry a path array\");\n if (entry.type !== \"date\") continue;\n const walked = walkStatePath(value, path, \"resolve\");\n if (walked === undefined) {\n return fault(`meta path ${path.join(\".\")} does not resolve`);\n }\n if (typeof walked.leaf !== \"string\") {\n return fault(`meta path ${path.join(\".\")} must name a string`);\n }\n const date = new Date(walked.leaf);\n if (Number.isNaN(date.getTime())) {\n return fault(`invalid date at meta path ${path.join(\".\")}`);\n }\n if (walked.parent === undefined) return date;\n (walked.parent as Record<string | number, unknown>)[walked.key] = date;\n }\n return value;\n};\n\nexport const reviveOps = (ops: unknown[]): unknown[] =>\n ops.map((op) => {\n if (typeof op !== \"object\" || op === null || !(\"meta\" in op)) return op;\n const { meta, ...rest } = op as { meta: unknown; value?: unknown };\n return { ...rest, value: reviveValue(rest.value, meta) };\n });\n\nexport const reviveResponse = <T extends { payload?: unknown; meta?: unknown }>(\n response: T,\n): T => {\n if (response.meta === undefined) return response;\n const { meta, ...rest } = response;\n return { ...rest, payload: reviveValue(rest.payload, meta) } as T;\n};\n\nconst walkDates = (\n node: unknown,\n path: (string | number)[],\n meta: StatewireMetaEntry[],\n): unknown => {\n if (node instanceof Date) {\n meta.push({ path, type: \"date\" });\n return node.toISOString();\n }\n if (Array.isArray(node)) {\n return node.map((item, i) => walkDates(item, [...path, i], meta));\n }\n if (typeof node === \"object\" && node !== null) {\n return Object.fromEntries(\n Object.keys(node).map((key) => [\n key,\n walkDates((node as Record<string, unknown>)[key], [...path, key], meta),\n ]),\n );\n }\n return node;\n};\n\n/** Encode Date instances inside command params as canonical ISO strings,\n * recording their paths (starting at the param index) in meta. */\nexport const encodeParams = (\n params: readonly unknown[],\n): { params: unknown[]; meta?: StatewireMetaEntry[] } => {\n const meta: StatewireMetaEntry[] = [];\n const encoded = params.map((param, i) => walkDates(param, [i], meta));\n return meta.length > 0 ? { params: encoded, meta } : { params: encoded };\n};\n\n/** Encode Date instances inside a wire value, recording their paths in meta. */\nexport const encodeValue = (\n value: unknown,\n): { value: unknown; meta?: StatewireMetaEntry[] } => {\n const meta: StatewireMetaEntry[] = [];\n const encoded = walkDates(value, [], meta);\n return meta.length > 0 ? { value: encoded, meta } : { value: encoded };\n};\n"],"mappings":"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"}
|
package/dist/wire/ops.d.ts
CHANGED
|
@@ -1,27 +1,9 @@
|
|
|
1
|
+
import { Statewire } from "../types.js";
|
|
1
2
|
//#region src/wire/ops.d.ts
|
|
2
3
|
type StatewirePath = readonly (string | number)[];
|
|
3
|
-
type StatewireOp = {
|
|
4
|
-
op: "replace";
|
|
5
|
-
path: StatewirePath;
|
|
6
|
-
value: unknown;
|
|
7
|
-
} | {
|
|
8
|
-
op: "add";
|
|
9
|
-
path: StatewirePath;
|
|
10
|
-
value: unknown;
|
|
11
|
-
} | {
|
|
12
|
-
op: "append";
|
|
13
|
-
path: StatewirePath;
|
|
14
|
-
value: unknown;
|
|
15
|
-
} | {
|
|
16
|
-
op: "remove";
|
|
17
|
-
path: StatewirePath;
|
|
18
|
-
} | {
|
|
19
|
-
op: "event";
|
|
20
|
-
value: object;
|
|
21
|
-
};
|
|
22
4
|
declare const sanitizeValue: (value: unknown, parent?: string) => unknown;
|
|
23
|
-
type MutableOp = Extract<
|
|
24
|
-
op: "replace" | "add" | "
|
|
5
|
+
type MutableOp = Extract<Statewire.Op, {
|
|
6
|
+
op: "replace" | "add" | "remove";
|
|
25
7
|
}>;
|
|
26
8
|
declare const applyOp: (root: unknown, op: MutableOp) => {
|
|
27
9
|
state: unknown;
|
|
@@ -29,12 +11,9 @@ declare const applyOp: (root: unknown, op: MutableOp) => {
|
|
|
29
11
|
};
|
|
30
12
|
declare const applyOps: (state: unknown, ops: readonly unknown[]) => {
|
|
31
13
|
state: unknown;
|
|
32
|
-
events:
|
|
33
|
-
event: unknown;
|
|
34
|
-
state: unknown;
|
|
35
|
-
}[];
|
|
14
|
+
events: unknown[];
|
|
36
15
|
changedPaths: StatewirePath[];
|
|
37
16
|
};
|
|
38
17
|
//#endregion
|
|
39
|
-
export {
|
|
18
|
+
export { StatewirePath, applyOp, applyOps, sanitizeValue };
|
|
40
19
|
//# sourceMappingURL=ops.d.ts.map
|
package/dist/wire/ops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.d.ts","names":[],"sources":["../../src/wire/ops.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"ops.d.ts","names":[],"sources":["../../src/wire/ops.ts"],"mappings":";;KAIY;cAwDC,gBAAiB,gBAAgB;KAGzC,YAAY,QAAQ,UAAU;EAAM;;cAkG5B,UACX,eACA,IAAI;EACD;EAAgB,cAAc;;cAUtB,WACX,gBACA;EAEA;EACA;EACA,cAAc"}
|
package/dist/wire/ops.js
CHANGED
|
@@ -1,80 +1,81 @@
|
|
|
1
|
-
import { recordMutations } from "../
|
|
1
|
+
import { recordMutations } from "../util/recordMutations.js";
|
|
2
|
+
import { isUnsafeKey, walkStatePath } from "../util/statePath.js";
|
|
2
3
|
//#region src/wire/ops.ts
|
|
3
|
-
const isUnsafe = (key, parent) => key === "__proto__" || key === "prototype" && parent === "constructor";
|
|
4
4
|
const warnStripped = (key, parent) => {
|
|
5
5
|
if (typeof process === "undefined" || process.env["NODE_ENV"] !== "production") console.warn(`statewire: stripped unsafe key "${key}"` + (parent === void 0 ? "" : ` under "${parent}"`));
|
|
6
6
|
};
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const unserializable = (kind, path) => {
|
|
8
|
+
throw new Error(`statewire: cannot serialize ${kind}` + (path.length === 0 ? "" : ` at ${path.join(".")}`));
|
|
9
|
+
};
|
|
10
|
+
const sanitizeAt = (value, parent, path) => {
|
|
9
11
|
if (value instanceof Date) return new Date(value.getTime());
|
|
10
|
-
if (Array.isArray(value)) return value.map((item) =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
if (Array.isArray(value)) return value.map((item, index) => {
|
|
13
|
+
if (item === void 0) unserializable("undefined", [...path, index]);
|
|
14
|
+
return sanitizeAt(item, void 0, [...path, index]);
|
|
15
|
+
});
|
|
16
|
+
if (typeof value === "object" && value !== null) {
|
|
17
|
+
const out = {};
|
|
18
|
+
for (const key of Object.keys(value)) {
|
|
19
|
+
if (isUnsafeKey(key, parent)) {
|
|
20
|
+
warnStripped(key, parent);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const item = value[key];
|
|
24
|
+
if (item === void 0) continue;
|
|
25
|
+
out[key] = sanitizeAt(item, key, [...path, key]);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === "function") unserializable("function", path);
|
|
30
|
+
return value;
|
|
15
31
|
};
|
|
32
|
+
const sanitizeValue = (value, parent) => sanitizeAt(value, parent, parent === void 0 ? [] : [parent]);
|
|
16
33
|
const invalid = (op) => /* @__PURE__ */ new Error(`Statewire cannot apply ${op.op} at path: ${op.path.join(".")}`);
|
|
17
|
-
const
|
|
18
|
-
if (typeof
|
|
19
|
-
return current + op.value;
|
|
34
|
+
const insertedText = (current, op, offset) => {
|
|
35
|
+
if (typeof op.value !== "string" || offset > current.length) throw invalid(op);
|
|
36
|
+
return current.slice(0, offset) + op.value + current.slice(offset);
|
|
20
37
|
};
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
const applyAdd = (box, op) => {
|
|
39
|
+
const offset = op.path[op.path.length - 1];
|
|
40
|
+
const walked = walkStatePath(box.state, op.path.slice(0, -1), "resolve");
|
|
41
|
+
if (walked === void 0 || typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) throw invalid(op);
|
|
42
|
+
const target = walked.leaf;
|
|
43
|
+
if (Array.isArray(target)) {
|
|
44
|
+
if (offset > target.length) throw invalid(op);
|
|
45
|
+
target.splice(offset, 0, sanitizeValue(op.value));
|
|
46
|
+
} else if (typeof target === "string") {
|
|
47
|
+
const next = insertedText(target, op, offset);
|
|
48
|
+
if (op.path.length === 1) box.state = next;
|
|
49
|
+
else walked.parent[walked.key] = next;
|
|
50
|
+
} else throw invalid(op);
|
|
25
51
|
};
|
|
26
52
|
const applyInList = (list, index, op) => {
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} else if (op.op === "add") {
|
|
31
|
-
if (index > list.length) throw invalid(op);
|
|
32
|
-
list.splice(index, 0, sanitizeValue(op.value));
|
|
33
|
-
} else if (op.op === "append") {
|
|
34
|
-
if (index >= list.length) throw invalid(op);
|
|
35
|
-
list[index] = appended(list[index], op);
|
|
36
|
-
} else {
|
|
37
|
-
if (index >= list.length) throw invalid(op);
|
|
38
|
-
list.splice(index, 1);
|
|
39
|
-
}
|
|
53
|
+
if (index >= list.length) throw invalid(op);
|
|
54
|
+
if (op.op === "replace") list[index] = sanitizeValue(op.value);
|
|
55
|
+
else list.splice(index, 1);
|
|
40
56
|
};
|
|
41
57
|
const applyInObject = (obj, key, op) => {
|
|
42
|
-
const exists = Object.hasOwn(obj, key);
|
|
43
58
|
if (op.op === "replace") obj[key] = sanitizeValue(op.value, key);
|
|
44
|
-
else
|
|
45
|
-
|
|
46
|
-
if (!exists) throw invalid(op);
|
|
59
|
+
else {
|
|
60
|
+
if (!Object.hasOwn(obj, key)) throw invalid(op);
|
|
47
61
|
delete obj[key];
|
|
48
|
-
}
|
|
62
|
+
}
|
|
49
63
|
};
|
|
50
64
|
const applyOpToDraft = (box, op) => {
|
|
51
|
-
if (op.
|
|
52
|
-
|
|
65
|
+
if (op.op === "add") {
|
|
66
|
+
if (op.path.length === 0) throw invalid(op);
|
|
67
|
+
applyAdd(box, op);
|
|
53
68
|
return;
|
|
54
69
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (typeof key === "number") {
|
|
60
|
-
if (!Array.isArray(node) || !Number.isInteger(key) || key < 0) throw invalid(op);
|
|
61
|
-
if (last) {
|
|
62
|
-
applyInList(node, key, op);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (key >= node.length) throw invalid(op);
|
|
66
|
-
node = node[key];
|
|
67
|
-
} else {
|
|
68
|
-
if (isUnsafe(key, op.path[depth - 1]) || typeof node !== "object" || node === null || Array.isArray(node)) throw invalid(op);
|
|
69
|
-
const obj = node;
|
|
70
|
-
if (last) {
|
|
71
|
-
applyInObject(obj, key, op);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (!Object.hasOwn(obj, key)) throw invalid(op);
|
|
75
|
-
node = obj[key];
|
|
76
|
-
}
|
|
70
|
+
if (op.path.length === 0) {
|
|
71
|
+
if (op.op !== "replace") throw invalid(op);
|
|
72
|
+
box.state = sanitizeValue(op.value);
|
|
73
|
+
return;
|
|
77
74
|
}
|
|
75
|
+
const walked = walkStatePath(box.state, op.path, "parent");
|
|
76
|
+
if (walked === void 0) throw invalid(op);
|
|
77
|
+
if (typeof walked.key === "number") applyInList(walked.parent, walked.key, op);
|
|
78
|
+
else applyInObject(walked.parent, walked.key, op);
|
|
78
79
|
};
|
|
79
80
|
const warnedOps = /* @__PURE__ */ new Set();
|
|
80
81
|
const warnUnknownOp = (type) => {
|
|
@@ -92,31 +93,17 @@ const applyOp = (root, op) => {
|
|
|
92
93
|
};
|
|
93
94
|
const applyOps = (state, ops) => {
|
|
94
95
|
const events = [];
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (segment.length === 0) return;
|
|
99
|
-
const ops = segment;
|
|
100
|
-
segment = [];
|
|
101
|
-
const recorded = recordMutations({ state }, (draft) => {
|
|
102
|
-
for (const op of ops) applyOpToDraft(draft, op);
|
|
103
|
-
});
|
|
104
|
-
state = recorded.state.state;
|
|
105
|
-
changedPaths.push(...unboxPaths(recorded.changedPaths));
|
|
106
|
-
};
|
|
107
|
-
for (const op of ops) if (op.op === "event") {
|
|
108
|
-
flush();
|
|
109
|
-
events.push({
|
|
110
|
-
event: sanitizeValue(op.value),
|
|
111
|
-
state
|
|
112
|
-
});
|
|
113
|
-
} else if (op.op === "replace" || op.op === "add" || op.op === "append" || op.op === "remove") segment.push(op);
|
|
96
|
+
const mutable = [];
|
|
97
|
+
for (const op of ops) if (op.op === "event") events.push(sanitizeValue(op.value));
|
|
98
|
+
else if (op.op === "replace" || op.op === "add" || op.op === "remove") mutable.push(op);
|
|
114
99
|
else warnUnknownOp(op.op);
|
|
115
|
-
|
|
100
|
+
const recorded = recordMutations({ state }, (draft) => {
|
|
101
|
+
for (const op of mutable) applyOpToDraft(draft, op);
|
|
102
|
+
});
|
|
116
103
|
return {
|
|
117
|
-
state,
|
|
104
|
+
state: recorded.state.state,
|
|
118
105
|
events,
|
|
119
|
-
changedPaths
|
|
106
|
+
changedPaths: unboxPaths(recorded.changedPaths)
|
|
120
107
|
};
|
|
121
108
|
};
|
|
122
109
|
//#endregion
|
package/dist/wire/ops.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.js","names":["recordMutations","StatewirePath","StatewireOp","op","path","value","isUnsafe","key","parent","warnStripped","process","env","console","warn","undefined","sanitizeValue","Date","getTime","Array","isArray","map","item","out","Record","Object","keys","MutableOp","Extract","invalid","Error","join","appended","current","applyAtRoot","box","state","applyInList","list","index","length","splice","applyInObject","obj","exists","hasOwn","applyOpToDraft","node","depth","last","Number","isInteger","warnedOps","Set","warnUnknownOp","type","has","add","JSON","stringify","unboxPaths","paths","slice","applyOp","root","changedPaths","recorded","draft","applyOps","ops","events","event","segment","flush","push"],"sources":["../../src/wire/ops.ts"],"sourcesContent":["import { recordMutations } from \"../client/optimistic\";\n\nexport type StatewirePath = readonly (string | number)[];\n\nexport type StatewireOp =\n | { op: \"replace\"; path: StatewirePath; value: unknown }\n | { op: \"add\"; path: StatewirePath; value: unknown }\n | { op: \"append\"; path: StatewirePath; value: unknown }\n | { op: \"remove\"; path: StatewirePath }\n | { op: \"event\"; value: object };\n\nconst isUnsafe = (key: string, parent: string | number | undefined) =>\n key === \"__proto__\" || (key === \"prototype\" && parent === \"constructor\");\n\nconst warnStripped = (key: string, parent: string | undefined) => {\n if (\n typeof process === \"undefined\" ||\n process.env[\"NODE_ENV\"] !== \"production\"\n ) {\n console.warn(\n `statewire: stripped unsafe key \"${key}\"` +\n (parent === undefined ? \"\" : ` under \"${parent}\"`),\n );\n }\n};\n\nexport const sanitizeValue = (value: unknown, parent?: string): unknown => {\n if (typeof value !== \"object\" || value === null) return value;\n if (value instanceof Date) return new Date(value.getTime());\n if (Array.isArray(value)) return value.map((item) => sanitizeValue(item));\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n if (isUnsafe(key, parent)) warnStripped(key, parent);\n else out[key] = sanitizeValue((value as Record<string, unknown>)[key], key);\n }\n return out;\n};\n\ntype MutableOp = Extract<\n StatewireOp,\n { op: \"replace\" | \"add\" | \"append\" | \"remove\" }\n>;\n\nconst invalid = (op: MutableOp) =>\n new Error(`Statewire cannot apply ${op.op} at path: ${op.path.join(\".\")}`);\n\nconst appended = (\n current: unknown,\n op: Extract<MutableOp, { op: \"append\" }>,\n): string => {\n if (typeof current !== \"string\" || typeof op.value !== \"string\") {\n throw invalid(op);\n }\n return current + op.value;\n};\n\nconst applyAtRoot = (box: { state: unknown }, op: MutableOp) => {\n if (op.op === \"replace\") {\n box.state = sanitizeValue(op.value);\n } else if (op.op === \"append\") {\n box.state = appended(box.state, op);\n } else {\n throw invalid(op);\n }\n};\n\nconst applyInList = (list: unknown[], index: number, op: MutableOp) => {\n if (op.op === \"replace\") {\n if (index >= list.length) throw invalid(op);\n list[index] = sanitizeValue(op.value);\n } else if (op.op === \"add\") {\n if (index > list.length) throw invalid(op);\n list.splice(index, 0, sanitizeValue(op.value));\n } else if (op.op === \"append\") {\n if (index >= list.length) throw invalid(op);\n list[index] = appended(list[index], op);\n } else {\n if (index >= list.length) throw invalid(op);\n list.splice(index, 1);\n }\n};\n\nconst applyInObject = (\n obj: Record<string, unknown>,\n key: string,\n op: MutableOp,\n) => {\n const exists = Object.hasOwn(obj, key);\n if (op.op === \"replace\") {\n obj[key] = sanitizeValue(op.value, key);\n } else if (op.op === \"append\") {\n obj[key] = appended(exists ? obj[key] : undefined, op);\n } else if (op.op === \"remove\") {\n if (!exists) throw invalid(op);\n delete obj[key];\n } else {\n throw invalid(op);\n }\n};\n\nconst applyOpToDraft = (box: { state: unknown }, op: MutableOp): void => {\n if (op.path.length === 0) {\n applyAtRoot(box, op);\n return;\n }\n let node: unknown = box.state;\n for (let depth = 0; depth < op.path.length; depth++) {\n const key = op.path[depth]!;\n const last = depth === op.path.length - 1;\n if (typeof key === \"number\") {\n if (!Array.isArray(node) || !Number.isInteger(key) || key < 0) {\n throw invalid(op);\n }\n if (last) {\n applyInList(node, key, op);\n return;\n }\n if (key >= node.length) throw invalid(op);\n node = node[key];\n } else {\n if (\n isUnsafe(key, op.path[depth - 1]) ||\n typeof node !== \"object\" ||\n node === null ||\n Array.isArray(node)\n ) {\n throw invalid(op);\n }\n const obj = node as Record<string, unknown>;\n if (last) {\n applyInObject(obj, key, op);\n return;\n }\n if (!Object.hasOwn(obj, key)) throw invalid(op);\n node = obj[key];\n }\n }\n};\n\nconst warnedOps = new Set<unknown>();\nconst warnUnknownOp = (type: unknown) => {\n if (warnedOps.has(type)) return;\n warnedOps.add(type);\n console.warn(`statewire: ignoring unknown op type ${JSON.stringify(type)}`);\n};\n\nconst unboxPaths = (paths: readonly StatewirePath[]): StatewirePath[] =>\n paths.map((path) => path.slice(1));\n\nexport const applyOp = (\n root: unknown,\n op: MutableOp,\n): { state: unknown; changedPaths: StatewirePath[] } => {\n const recorded = recordMutations({ state: root }, (draft) =>\n applyOpToDraft(draft, op),\n );\n return {\n state: recorded.state.state,\n changedPaths: unboxPaths(recorded.changedPaths),\n };\n};\n\nexport const applyOps = (\n state: unknown,\n ops: readonly unknown[],\n): {\n state: unknown;\n events: { event: unknown; state: unknown }[];\n changedPaths: StatewirePath[];\n} => {\n const events: { event: unknown; state: unknown }[] = [];\n const changedPaths: StatewirePath[] = [];\n let segment: MutableOp[] = [];\n const flush = () => {\n if (segment.length === 0) return;\n const ops = segment;\n segment = [];\n const recorded = recordMutations({ state }, (draft) => {\n for (const op of ops) applyOpToDraft(draft, op);\n });\n state = recorded.state.state;\n changedPaths.push(...unboxPaths(recorded.changedPaths));\n };\n for (const op of ops as StatewireOp[]) {\n if (op.op === \"event\") {\n flush();\n events.push({ event: sanitizeValue(op.value), state });\n } else if (\n op.op === \"replace\" ||\n op.op === \"add\" ||\n op.op === \"append\" ||\n op.op === \"remove\"\n ) {\n segment.push(op);\n } else {\n warnUnknownOp((op as { op: unknown }).op);\n }\n }\n flush();\n return { state, events, changedPaths };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ops.js","names":["recordMutations","isUnsafeKey","walkStatePath","Statewire","StatewirePath","warnStripped","key","parent","process","env","console","warn","undefined","unserializable","kind","path","Error","length","join","sanitizeAt","value","Date","getTime","Array","isArray","map","item","index","out","Record","Object","keys","sanitizeValue","MutableOp","Extract","Op","op","invalid","insertedText","current","offset","slice","applyAdd","box","state","walked","Number","isInteger","target","leaf","splice","next","applyInList","list","applyInObject","obj","hasOwn","applyOpToDraft","warnedOps","Set","warnUnknownOp","type","has","add","JSON","stringify","unboxPaths","paths","applyOp","root","changedPaths","recorded","draft","applyOps","ops","events","mutable","push"],"sources":["../../src/wire/ops.ts"],"sourcesContent":["import { recordMutations } from \"../util/recordMutations\";\nimport { isUnsafeKey, walkStatePath } from \"../util/statePath\";\nimport type { Statewire } from \"../types\";\n\nexport type StatewirePath = readonly (string | number)[];\n\nconst warnStripped = (key: string, parent: string | undefined) => {\n if (\n typeof process === \"undefined\" ||\n process.env[\"NODE_ENV\"] !== \"production\"\n ) {\n console.warn(\n `statewire: stripped unsafe key \"${key}\"` +\n (parent === undefined ? \"\" : ` under \"${parent}\"`),\n );\n }\n};\n\nconst unserializable = (\n kind: \"function\" | \"undefined\",\n path: readonly (string | number)[],\n): never => {\n throw new Error(\n `statewire: cannot serialize ${kind}` +\n (path.length === 0 ? \"\" : ` at ${path.join(\".\")}`),\n );\n};\n\nconst sanitizeAt = (\n value: unknown,\n parent: string | undefined,\n path: readonly (string | number)[],\n): unknown => {\n if (value instanceof Date) return new Date(value.getTime());\n if (Array.isArray(value)) {\n return value.map((item, index) => {\n if (item === undefined) unserializable(\"undefined\", [...path, index]);\n return sanitizeAt(item, undefined, [...path, index]);\n });\n }\n if (typeof value === \"object\" && value !== null) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n if (isUnsafeKey(key, parent)) {\n warnStripped(key, parent);\n continue;\n }\n const item = (value as Record<string, unknown>)[key];\n if (item === undefined) continue;\n out[key] = sanitizeAt(item, key, [...path, key]);\n }\n return out;\n }\n if (typeof value === \"function\") unserializable(\"function\", path);\n return value;\n};\n\n// One clone pass: strips unsafe keys (with a warning); explicit-undefined\n// object entries are omitted (delete semantics), functions and undefined\n// list items throw.\nexport const sanitizeValue = (value: unknown, parent?: string): unknown =>\n sanitizeAt(value, parent, parent === undefined ? [] : [parent]);\n\ntype MutableOp = Extract<Statewire.Op, { op: \"replace\" | \"add\" | \"remove\" }>;\n\nconst invalid = (op: MutableOp) =>\n new Error(`Statewire cannot apply ${op.op} at path: ${op.path.join(\".\")}`);\n\nconst insertedText = (\n current: string,\n op: Extract<MutableOp, { op: \"add\" }>,\n offset: number,\n): string => {\n if (typeof op.value !== \"string\" || offset > current.length) {\n throw invalid(op);\n }\n return current.slice(0, offset) + op.value + current.slice(offset);\n};\n\nconst applyAdd = (\n box: { state: unknown },\n op: Extract<MutableOp, { op: \"add\" }>,\n) => {\n const offset = op.path[op.path.length - 1];\n const walked = walkStatePath(box.state, op.path.slice(0, -1), \"resolve\");\n if (\n walked === undefined ||\n typeof offset !== \"number\" ||\n !Number.isInteger(offset) ||\n offset < 0\n ) {\n throw invalid(op);\n }\n const target = walked.leaf;\n if (Array.isArray(target)) {\n if (offset > target.length) throw invalid(op);\n target.splice(offset, 0, sanitizeValue(op.value));\n } else if (typeof target === \"string\") {\n const next = insertedText(target, op, offset);\n if (op.path.length === 1) {\n box.state = next;\n } else {\n (walked.parent as Record<string | number, unknown>)[walked.key] = next;\n }\n } else {\n throw invalid(op);\n }\n};\n\nconst applyInList = (list: unknown[], index: number, op: MutableOp) => {\n if (index >= list.length) throw invalid(op);\n if (op.op === \"replace\") {\n list[index] = sanitizeValue(op.value);\n } else {\n list.splice(index, 1);\n }\n};\n\nconst applyInObject = (\n obj: Record<string, unknown>,\n key: string,\n op: MutableOp,\n) => {\n if (op.op === \"replace\") {\n obj[key] = sanitizeValue(op.value, key);\n } else {\n if (!Object.hasOwn(obj, key)) throw invalid(op);\n delete obj[key];\n }\n};\n\nconst applyOpToDraft = (box: { state: unknown }, op: MutableOp): void => {\n if (op.op === \"add\") {\n if (op.path.length === 0) throw invalid(op);\n applyAdd(box, op);\n return;\n }\n if (op.path.length === 0) {\n if (op.op !== \"replace\") throw invalid(op);\n box.state = sanitizeValue(op.value);\n return;\n }\n const walked = walkStatePath(box.state, op.path, \"parent\");\n if (walked === undefined) throw invalid(op);\n if (typeof walked.key === \"number\") {\n applyInList(walked.parent as unknown[], walked.key, op);\n } else {\n applyInObject(walked.parent as Record<string, unknown>, walked.key, op);\n }\n};\n\nconst warnedOps = new Set<unknown>();\nconst warnUnknownOp = (type: unknown) => {\n if (warnedOps.has(type)) return;\n warnedOps.add(type);\n console.warn(`statewire: ignoring unknown op type ${JSON.stringify(type)}`);\n};\n\nconst unboxPaths = (paths: readonly StatewirePath[]): StatewirePath[] =>\n paths.map((path) => path.slice(1));\n\nexport const applyOp = (\n root: unknown,\n op: MutableOp,\n): { state: unknown; changedPaths: StatewirePath[] } => {\n const recorded = recordMutations({ state: root }, (draft) =>\n applyOpToDraft(draft, op),\n );\n return {\n state: recorded.state.state,\n changedPaths: unboxPaths(recorded.changedPaths),\n };\n};\n\nexport const applyOps = (\n state: unknown,\n ops: readonly unknown[],\n): {\n state: unknown;\n events: unknown[];\n changedPaths: StatewirePath[];\n} => {\n const events: unknown[] = [];\n const mutable: MutableOp[] = [];\n for (const op of ops as Statewire.Op[]) {\n if (op.op === \"event\") {\n events.push(sanitizeValue(op.value));\n } else if (op.op === \"replace\" || op.op === \"add\" || op.op === \"remove\") {\n mutable.push(op);\n } else {\n warnUnknownOp((op as { op: unknown }).op);\n }\n }\n const recorded = recordMutations({ state }, (draft) => {\n for (const op of mutable) applyOpToDraft(draft, op);\n });\n return {\n state: recorded.state.state,\n events,\n changedPaths: unboxPaths(recorded.changedPaths),\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "statewire",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "State over the wire: snapshot-first delta streaming and command submission for any state and any command",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/assistant-ui/harness-sdk.git",
|
|
9
|
-
"directory": "packages/statewire"
|
|
9
|
+
"directory": "packages/statewire/core"
|
|
10
10
|
},
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
@@ -17,9 +17,13 @@
|
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"default": "./dist/index.js"
|
|
19
19
|
},
|
|
20
|
-
"./
|
|
21
|
-
"types": "./dist/
|
|
22
|
-
"default": "./dist/
|
|
20
|
+
"./host": {
|
|
21
|
+
"types": "./dist/host.d.ts",
|
|
22
|
+
"default": "./dist/host.js"
|
|
23
|
+
},
|
|
24
|
+
"./host-internal": {
|
|
25
|
+
"types": "./dist/host-internal.d.ts",
|
|
26
|
+
"default": "./dist/host-internal.js"
|
|
23
27
|
},
|
|
24
28
|
"./testing": {
|
|
25
29
|
"types": "./dist/transports/testing.d.ts",
|
|
@@ -33,6 +37,11 @@
|
|
|
33
37
|
"src",
|
|
34
38
|
"README.md"
|
|
35
39
|
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "aui-build",
|
|
42
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
43
|
+
"test": "vitest run"
|
|
44
|
+
},
|
|
36
45
|
"dependencies": {
|
|
37
46
|
"@assistant-ui/tap": "^0.9.6"
|
|
38
47
|
},
|
|
@@ -45,10 +54,5 @@
|
|
|
45
54
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
46
55
|
"react": "^19.2.8",
|
|
47
56
|
"vitest": "^4.1.10"
|
|
48
|
-
},
|
|
49
|
-
"scripts": {
|
|
50
|
-
"build": "aui-build",
|
|
51
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
52
|
-
"test": "vitest run"
|
|
53
57
|
}
|
|
54
|
-
}
|
|
58
|
+
}
|
package/src/client/changes.ts
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import type { StatewirePath } from "../wire/ops";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
const lookupValue = (state: unknown, path: StatewirePath): unknown => {
|
|
5
|
-
let node = state;
|
|
6
|
-
for (const key of path) {
|
|
7
|
-
if (typeof node !== "object" || node === null) return undefined;
|
|
8
|
-
node = Object.hasOwn(node, key)
|
|
9
|
-
? (node as Record<string, unknown>)[key]
|
|
10
|
-
: undefined;
|
|
11
|
-
}
|
|
12
|
-
return node;
|
|
13
|
-
};
|
|
2
|
+
import type { Statewire } from "../types";
|
|
3
|
+
import { readAtPath } from "../util/statePath";
|
|
14
4
|
|
|
15
5
|
const keysAt = (node: unknown): string[] =>
|
|
16
6
|
typeof node === "object" && node !== null ? Object.keys(node) : [];
|
|
@@ -23,7 +13,7 @@ export const createChanges = (
|
|
|
23
13
|
prev: unknown,
|
|
24
14
|
next: unknown,
|
|
25
15
|
changedPaths: readonly StatewirePath[],
|
|
26
|
-
):
|
|
16
|
+
): Statewire.Changes => ({
|
|
27
17
|
isChangedAt: (path) =>
|
|
28
18
|
changedPaths.some((p) => isPrefix(p, path) || isPrefix(path, p)),
|
|
29
19
|
getChangedKeys: (path) => {
|
|
@@ -31,8 +21,8 @@ export const createChanges = (
|
|
|
31
21
|
(p) => p.length <= path.length && isPrefix(p, path),
|
|
32
22
|
);
|
|
33
23
|
if (wiped) {
|
|
34
|
-
const keys = keysAt(
|
|
35
|
-
for (const key of keysAt(
|
|
24
|
+
const keys = keysAt(readAtPath(next, path));
|
|
25
|
+
for (const key of keysAt(readAtPath(prev, path))) {
|
|
36
26
|
if (!keys.includes(key)) keys.push(key);
|
|
37
27
|
}
|
|
38
28
|
return keys;
|