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.
Files changed (148) hide show
  1. package/README.md +14 -13
  2. package/dist/client/changes.d.ts +2 -2
  3. package/dist/client/changes.d.ts.map +1 -1
  4. package/dist/client/changes.js +3 -10
  5. package/dist/client/changes.js.map +1 -1
  6. package/dist/client/connection.d.ts +18 -39
  7. package/dist/client/connection.d.ts.map +1 -1
  8. package/dist/client/connection.js +61 -54
  9. package/dist/client/connection.js.map +1 -1
  10. package/dist/client/core.d.ts +7 -11
  11. package/dist/client/core.d.ts.map +1 -1
  12. package/dist/client/core.js +58 -55
  13. package/dist/client/core.js.map +1 -1
  14. package/dist/client/session.d.ts +8 -25
  15. package/dist/client/session.d.ts.map +1 -1
  16. package/dist/client/session.js +56 -36
  17. package/dist/client/session.js.map +1 -1
  18. package/dist/client/sessionStore.d.ts +1 -1
  19. package/dist/client/sessionStore.js.map +1 -1
  20. package/dist/client/timing.d.ts +2 -1
  21. package/dist/client/timing.d.ts.map +1 -1
  22. package/dist/client/timing.js +5 -1
  23. package/dist/client/timing.js.map +1 -1
  24. package/dist/host/adapter.d.ts +8 -0
  25. package/dist/host/adapter.d.ts.map +1 -0
  26. package/dist/host/adapter.js +23 -0
  27. package/dist/host/adapter.js.map +1 -0
  28. package/dist/host/draft.d.ts +21 -7
  29. package/dist/host/draft.d.ts.map +1 -1
  30. package/dist/host/draft.js +190 -103
  31. package/dist/host/draft.js.map +1 -1
  32. package/dist/host/host.d.ts +18 -35
  33. package/dist/host/host.d.ts.map +1 -1
  34. package/dist/host/host.js +81 -53
  35. package/dist/host/host.js.map +1 -1
  36. package/dist/host/socket.d.ts +14 -0
  37. package/dist/host/socket.d.ts.map +1 -0
  38. package/dist/host/socket.js +459 -0
  39. package/dist/host/socket.js.map +1 -0
  40. package/dist/host-internal.d.ts +27 -0
  41. package/dist/host-internal.d.ts.map +1 -0
  42. package/dist/host-internal.js +3 -0
  43. package/dist/host.d.ts +44 -0
  44. package/dist/host.d.ts.map +1 -0
  45. package/dist/host.js +4 -0
  46. package/dist/index.d.ts +5 -28
  47. package/dist/index.js +2 -3
  48. package/dist/statewire.d.ts +94 -10
  49. package/dist/statewire.d.ts.map +1 -1
  50. package/dist/statewire.js +23 -35
  51. package/dist/statewire.js.map +1 -1
  52. package/dist/transports/fake-sse.d.ts +12 -0
  53. package/dist/transports/fake-sse.d.ts.map +1 -0
  54. package/dist/transports/fake-sse.js +39 -0
  55. package/dist/transports/fake-sse.js.map +1 -0
  56. package/dist/transports/fixtures.d.ts +33 -0
  57. package/dist/transports/fixtures.d.ts.map +1 -0
  58. package/dist/transports/fixtures.js +50 -0
  59. package/dist/transports/fixtures.js.map +1 -0
  60. package/dist/transports/remote.d.ts +6 -13
  61. package/dist/transports/remote.d.ts.map +1 -1
  62. package/dist/transports/remote.js +5 -24
  63. package/dist/transports/remote.js.map +1 -1
  64. package/dist/transports/sse.d.ts +10 -20
  65. package/dist/transports/sse.d.ts.map +1 -1
  66. package/dist/transports/sse.js +42 -41
  67. package/dist/transports/sse.js.map +1 -1
  68. package/dist/transports/testing.d.ts +4 -3
  69. package/dist/transports/testing.d.ts.map +1 -1
  70. package/dist/transports/testing.js +19 -46
  71. package/dist/transports/testing.js.map +1 -1
  72. package/dist/transports/ws.d.ts +4 -13
  73. package/dist/transports/ws.d.ts.map +1 -1
  74. package/dist/transports/ws.js +57 -60
  75. package/dist/transports/ws.js.map +1 -1
  76. package/dist/types.d.ts +53 -100
  77. package/dist/types.d.ts.map +1 -1
  78. package/dist/types.js.map +1 -1
  79. package/dist/util/draftTraps.d.ts +6 -0
  80. package/dist/util/draftTraps.d.ts.map +1 -0
  81. package/dist/util/draftTraps.js +25 -0
  82. package/dist/util/draftTraps.js.map +1 -0
  83. package/dist/util/macrotask.d.ts +5 -0
  84. package/dist/util/macrotask.d.ts.map +1 -0
  85. package/dist/util/macrotask.js +25 -0
  86. package/dist/util/macrotask.js.map +1 -0
  87. package/dist/util/methodProxy.d.ts +5 -0
  88. package/dist/util/methodProxy.d.ts.map +1 -0
  89. package/dist/util/methodProxy.js +17 -0
  90. package/dist/util/methodProxy.js.map +1 -0
  91. package/dist/util/randomId.d.ts +5 -0
  92. package/dist/util/randomId.d.ts.map +1 -0
  93. package/dist/util/randomId.js +6 -0
  94. package/dist/util/randomId.js.map +1 -0
  95. package/dist/{client/optimistic.d.ts → util/recordMutations.d.ts} +2 -2
  96. package/dist/util/recordMutations.d.ts.map +1 -0
  97. package/dist/{client/optimistic.js → util/recordMutations.js} +5 -24
  98. package/dist/util/recordMutations.js.map +1 -0
  99. package/dist/util/statePath.d.ts +14 -0
  100. package/dist/util/statePath.d.ts.map +1 -0
  101. package/dist/util/statePath.js +31 -0
  102. package/dist/util/statePath.js.map +1 -0
  103. package/dist/wire/meta.d.ts +6 -1
  104. package/dist/wire/meta.d.ts.map +1 -1
  105. package/dist/wire/meta.js +30 -28
  106. package/dist/wire/meta.js.map +1 -1
  107. package/dist/wire/ops.d.ts +5 -26
  108. package/dist/wire/ops.d.ts.map +1 -1
  109. package/dist/wire/ops.js +67 -80
  110. package/dist/wire/ops.js.map +1 -1
  111. package/package.json +15 -11
  112. package/src/client/changes.ts +5 -15
  113. package/src/client/connection.ts +87 -113
  114. package/src/client/core.ts +57 -91
  115. package/src/client/session.ts +87 -70
  116. package/src/client/sessionStore.ts +1 -1
  117. package/src/client/timing.ts +6 -0
  118. package/src/host/adapter.ts +33 -0
  119. package/src/host/draft.ts +293 -125
  120. package/src/host/host.ts +107 -106
  121. package/src/host/socket.ts +542 -0
  122. package/src/host-internal.ts +28 -0
  123. package/src/host.ts +61 -0
  124. package/src/index.ts +2 -62
  125. package/src/statewire.ts +154 -61
  126. package/src/transports/fake-sse.ts +51 -0
  127. package/src/transports/fixtures.ts +60 -0
  128. package/src/transports/remote.ts +12 -54
  129. package/src/transports/sse.ts +68 -81
  130. package/src/transports/testing.ts +27 -59
  131. package/src/transports/ws.ts +76 -92
  132. package/src/types.ts +50 -131
  133. package/src/util/draftTraps.ts +32 -0
  134. package/src/util/macrotask.ts +25 -0
  135. package/src/util/methodProxy.ts +16 -0
  136. package/src/util/randomId.ts +4 -0
  137. package/src/{client/optimistic.ts → util/recordMutations.ts} +0 -0
  138. package/src/util/statePath.ts +61 -0
  139. package/src/wire/meta.ts +42 -47
  140. package/src/wire/ops.ts +107 -106
  141. package/LICENSE +0 -21
  142. package/dist/client/optimistic.d.ts.map +0 -1
  143. package/dist/client/optimistic.js.map +0 -1
  144. package/dist/index.d.ts.map +0 -1
  145. package/dist/server.d.ts +0 -15
  146. package/dist/server.d.ts.map +0 -1
  147. package/dist/server.js +0 -3
  148. 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
- let parent = null;
15
- let key = "";
16
- let node = value;
17
- for (const seg of path) {
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 === null) return date;
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 walk = (node, path) => {
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
@@ -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","parent","key","node","Object","hasOwn","Record","length","join","date","Date","isNaN","getTime","reviveOps","ops","map","op","rest","reviveResponse","payload","response","T","undefined","encodeParams","params","walk","push","toISOString","item","i","fromEntries","keys","encoded","param"],"sources":["../../src/wire/meta.ts"],"sourcesContent":["export 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 let parent: unknown = null;\n let key: string | number = \"\";\n let node = value;\n for (const seg of path) {\n parent = node;\n key = seg;\n if (\n typeof seg === \"string\" &&\n typeof node === \"object\" &&\n node !== null &&\n !Array.isArray(node) &&\n Object.hasOwn(node, seg)\n ) {\n node = (node as Record<string, unknown>)[seg];\n } else if (\n typeof seg === \"number\" &&\n Array.isArray(node) &&\n seg >= 0 &&\n seg < node.length\n ) {\n node = node[seg];\n } else {\n return fault(`meta path ${path.join(\".\")} does not resolve`);\n }\n }\n if (typeof node !== \"string\") {\n return fault(`meta path ${path.join(\".\")} must name a string`);\n }\n const date = new Date(node);\n if (Number.isNaN(date.getTime())) {\n return fault(`invalid date at meta path ${path.join(\".\")}`);\n }\n if (parent === null) return date;\n (parent as Record<string | number, unknown>)[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\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 walk = (node: unknown, path: (string | number)[]): 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) => walk(item, [...path, i]));\n }\n if (typeof node === \"object\" && node !== null) {\n return Object.fromEntries(\n Object.keys(node).map((key) => [\n key,\n walk((node as Record<string, unknown>)[key], [...path, key]),\n ]),\n );\n }\n return node;\n };\n const encoded = params.map((param, i) => walk(param, [i]));\n return meta.length > 0 ? { params: encoded, meta } : { params: encoded };\n};\n"],"mappings":"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"}
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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"}
@@ -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<StatewireOp, {
24
- op: "replace" | "add" | "append" | "remove";
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 { StatewireOp, StatewirePath, applyOp, applyOps, sanitizeValue };
18
+ export { StatewirePath, applyOp, applyOps, sanitizeValue };
40
19
  //# sourceMappingURL=ops.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ops.d.ts","names":[],"sources":["../../src/wire/ops.ts"],"mappings":";KAEY;KAEA;EACN;EAAe,MAAM;EAAe;;EACpC;EAAW,MAAM;EAAe;;EAChC;EAAc,MAAM;EAAe;;EACnC;EAAc,MAAM;;EACpB;EAAa;;cAiBN,gBAAiB,gBAAgB;KAYzC,YAAY,QACf;EACE;;cA6GS,UACX,eACA,IAAI;EACD;EAAgB,cAAc;;cAUtB,WACX,gBACA;EAEA;EACA;IAAU;IAAgB;;EAC1B,cAAc"}
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 "../client/optimistic.js";
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 sanitizeValue = (value, parent) => {
8
- if (typeof value !== "object" || value === null) return value;
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) => sanitizeValue(item));
11
- const out = {};
12
- for (const key of Object.keys(value)) if (isUnsafe(key, parent)) warnStripped(key, parent);
13
- else out[key] = sanitizeValue(value[key], key);
14
- return out;
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 appended = (current, op) => {
18
- if (typeof current !== "string" || typeof op.value !== "string") throw invalid(op);
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 applyAtRoot = (box, op) => {
22
- if (op.op === "replace") box.state = sanitizeValue(op.value);
23
- else if (op.op === "append") box.state = appended(box.state, op);
24
- else throw invalid(op);
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 (op.op === "replace") {
28
- if (index >= list.length) throw invalid(op);
29
- list[index] = sanitizeValue(op.value);
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 if (op.op === "append") obj[key] = appended(exists ? obj[key] : void 0, op);
45
- else if (op.op === "remove") {
46
- if (!exists) throw invalid(op);
59
+ else {
60
+ if (!Object.hasOwn(obj, key)) throw invalid(op);
47
61
  delete obj[key];
48
- } else throw invalid(op);
62
+ }
49
63
  };
50
64
  const applyOpToDraft = (box, op) => {
51
- if (op.path.length === 0) {
52
- applyAtRoot(box, op);
65
+ if (op.op === "add") {
66
+ if (op.path.length === 0) throw invalid(op);
67
+ applyAdd(box, op);
53
68
  return;
54
69
  }
55
- let node = box.state;
56
- for (let depth = 0; depth < op.path.length; depth++) {
57
- const key = op.path[depth];
58
- const last = depth === op.path.length - 1;
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 changedPaths = [];
96
- let segment = [];
97
- const flush = () => {
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
- flush();
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
@@ -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.1",
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
- "./server": {
21
- "types": "./dist/server.d.ts",
22
- "default": "./dist/server.js"
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
+ }
@@ -1,16 +1,6 @@
1
1
  import type { StatewirePath } from "../wire/ops";
2
- import type { StatewireChanges } from "../types";
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
- ): StatewireChanges => ({
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(lookupValue(next, path));
35
- for (const key of keysAt(lookupValue(prev, path))) {
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;