statewire 0.0.1 → 0.0.3

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 +12 -26
  15. package/dist/client/session.d.ts.map +1 -1
  16. package/dist/client/session.js +70 -43
  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 +19 -35
  33. package/dist/host/host.d.ts.map +1 -1
  34. package/dist/host/host.js +86 -54
  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 +511 -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 +54 -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 +97 -10
  49. package/dist/statewire.d.ts.map +1 -1
  50. package/dist/statewire.js +32 -36
  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 +7 -25
  63. package/dist/transports/remote.js.map +1 -1
  64. package/dist/transports/sse.d.ts +32 -20
  65. package/dist/transports/sse.d.ts.map +1 -1
  66. package/dist/transports/sse.js +57 -46
  67. package/dist/transports/sse.js.map +1 -1
  68. package/dist/transports/testing.d.ts +8 -6
  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 +59 -63
  75. package/dist/transports/ws.js.map +1 -1
  76. package/dist/types.d.ts +55 -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 +129 -90
  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 +122 -110
  121. package/src/host/socket.ts +618 -0
  122. package/src/host-internal.ts +28 -0
  123. package/src/host.ts +72 -0
  124. package/src/index.ts +2 -62
  125. package/src/statewire.ts +168 -61
  126. package/src/transports/fake-sse.ts +51 -0
  127. package/src/transports/fixtures.ts +60 -0
  128. package/src/transports/remote.ts +14 -51
  129. package/src/transports/sse.ts +87 -89
  130. package/src/transports/testing.ts +33 -63
  131. package/src/transports/ws.ts +78 -100
  132. package/src/types.ts +52 -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
@@ -0,0 +1,6 @@
1
+ //#region src/util/randomId.ts
2
+ const randomId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
3
+ //#endregion
4
+ export { randomId };
5
+
6
+ //# sourceMappingURL=randomId.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"randomId.js","names":["randomId","crypto","randomUUID","Date","now","toString","Math","random","slice"],"sources":["../../src/util/randomId.ts"],"sourcesContent":["export const randomId = (): string =>\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n"],"mappings":";AAAA,MAAaA,iBACX,OAAOC,WAAW,eAAe,gBAAgBA,SAC7CA,OAAOC,WAAW,IAClB,GAAGC,KAAKC,IAAI,CAAC,CAACC,SAAS,EAAE,EAAC,GAAIC,KAAKC,OAAO,CAAC,CAACF,SAAS,EAAE,CAAC,CAACG,MAAM,GAAG,EAAE"}
@@ -1,4 +1,4 @@
1
- //#region src/client/optimistic.d.ts
1
+ //#region src/util/recordMutations.d.ts
2
2
  type PathKey = string | number;
3
3
  type Path = readonly PathKey[];
4
4
  declare const recordMutations: <T>(base: T, mutate: (draft: T) => void) => {
@@ -7,4 +7,4 @@ declare const recordMutations: <T>(base: T, mutate: (draft: T) => void) => {
7
7
  };
8
8
  //#endregion
9
9
  export { recordMutations };
10
- //# sourceMappingURL=optimistic.d.ts.map
10
+ //# sourceMappingURL=recordMutations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordMutations.d.ts","names":[],"sources":["../../src/util/recordMutations.ts"],"mappings":";KAEK;KACA,gBAAgB;cAuER,kBAAmB,GAC9B,MAAM,GACN,SAAS,OAAO;EACb,OAAO;EAAG,cAAc"}
@@ -1,4 +1,5 @@
1
- //#region src/client/optimistic.ts
1
+ import { sharedDraftTraps } from "./draftTraps.js";
2
+ //#region src/util/recordMutations.ts
2
3
  const DRAFT = Symbol("statewire.draft");
3
4
  const isDraftable = (value) => {
4
5
  if (typeof value !== "object" || value === null) return false;
@@ -34,10 +35,7 @@ const finalize = (node) => {
34
35
  return result;
35
36
  };
36
37
  const recordMutations = (base, mutate) => {
37
- if (!isDraftable(base)) return {
38
- state: base,
39
- changedPaths: []
40
- };
38
+ if (!isDraftable(base)) throw new Error("statewire: cannot record mutations on primitive state");
41
39
  const changedPaths = [];
42
40
  const seen = /* @__PURE__ */ new Set();
43
41
  const record = (path) => {
@@ -93,25 +91,8 @@ const recordMutations = (base, mutate) => {
93
91
  delete node.copy[prop];
94
92
  return true;
95
93
  },
96
- defineProperty: () => {
97
- throw new Error("statewire: defineProperty is not supported inside a mutator");
98
- },
99
- setPrototypeOf: () => {
100
- throw new Error("statewire: setPrototypeOf is not supported inside a mutator");
101
- },
102
- preventExtensions: () => {
103
- throw new Error("statewire: preventExtensions is not supported inside a mutator");
104
- },
105
94
  has: (_target, prop) => prop === DRAFT || Reflect.has(latest(node), prop),
106
- ownKeys: () => Reflect.ownKeys(latest(node)),
107
- getOwnPropertyDescriptor: (_target, prop) => {
108
- const desc = Reflect.getOwnPropertyDescriptor(latest(node), prop);
109
- if (!desc) return void 0;
110
- return {
111
- ...desc,
112
- configurable: !node.isArray || prop !== "length"
113
- };
114
- }
95
+ ...sharedDraftTraps(() => latest(node), "inside a mutator")
115
96
  });
116
97
  return node;
117
98
  };
@@ -125,4 +106,4 @@ const recordMutations = (base, mutate) => {
125
106
  //#endregion
126
107
  export { recordMutations };
127
108
 
128
- //# sourceMappingURL=optimistic.js.map
109
+ //# sourceMappingURL=recordMutations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordMutations.js","names":["sharedDraftTraps","PathKey","Path","DRAFT","Symbol","Node","base","Record","PropertyKey","copy","modified","isArray","parent","path","proxy","assigned","Set","isDraftable","value","Array","proto","Object","getPrototypeOf","prototype","nodeOf","undefined","latest","node","prepareCopy","slice","markModified","n","resolveAssigned","container","key","keys","child","finalize","result","has","recordMutations","T","mutate","draft","state","changedPaths","Error","seen","record","join","add","push","createNode","touched","prop","Proxy","get","_target","current","Number","set","next","source","hasOwn","deleteProperty","Reflect","root"],"sources":["../../src/util/recordMutations.ts"],"sourcesContent":["import { sharedDraftTraps } from \"./draftTraps\";\n\ntype PathKey = string | number;\ntype Path = readonly PathKey[];\n\nconst DRAFT = Symbol(\"statewire.draft\");\n\ntype Node = {\n base: Record<PropertyKey, unknown>;\n copy: Record<PropertyKey, unknown> | null;\n modified: boolean;\n isArray: boolean;\n parent: Node | null;\n path: Path;\n proxy: unknown;\n assigned: Set<PropertyKey> | null;\n};\n\nconst isDraftable = (value: unknown): value is object => {\n if (typeof value !== \"object\" || value === null) return false;\n if (Array.isArray(value)) return true;\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n};\n\nconst nodeOf = (value: unknown): Node | undefined =>\n typeof value === \"object\" && value !== null\n ? (value as { [DRAFT]?: Node })[DRAFT]\n : undefined;\n\nconst latest = (node: Node) => node.copy ?? node.base;\n\nconst prepareCopy = (node: Node) => {\n node.copy ??= node.isArray\n ? ((node.base as unknown as unknown[]).slice() as unknown as Record<\n PropertyKey,\n unknown\n >)\n : { ...node.base };\n};\n\nconst markModified = (node: Node) => {\n for (let n: Node | null = node; n && !n.modified; n = n.parent) {\n n.modified = true;\n }\n};\n\nconst resolveAssigned = (container: object) => {\n for (const key of Object.keys(container)) {\n const value = (container as Record<string, unknown>)[key];\n const child = nodeOf(value);\n if (child) {\n (container as Record<string, unknown>)[key] = finalize(child);\n } else if (isDraftable(value)) {\n resolveAssigned(value);\n }\n }\n};\n\nconst finalize = (node: Node): unknown => {\n if (!node.modified) return node.base;\n const result = node.copy!;\n for (const key of Object.keys(result)) {\n const value = result[key];\n const child = nodeOf(value);\n if (child) {\n result[key] = finalize(child);\n } else if (node.assigned?.has(key) && isDraftable(value)) {\n resolveAssigned(value);\n }\n }\n return result;\n};\n\nexport const recordMutations = <T>(\n base: T,\n mutate: (draft: T) => void,\n): { state: T; changedPaths: Path[] } => {\n if (!isDraftable(base)) {\n throw new Error(\"statewire: cannot record mutations on primitive state\");\n }\n\n const changedPaths: Path[] = [];\n const seen = new Set<string>();\n const record = (path: Path) => {\n const key = path.join(\"\u0000\");\n if (seen.has(key)) return;\n seen.add(key);\n changedPaths.push(path);\n };\n\n const createNode = (value: object, parent: Node | null, path: Path): Node => {\n const node: Node = {\n base: value as Record<PropertyKey, unknown>,\n copy: null,\n modified: false,\n isArray: Array.isArray(value),\n parent,\n path,\n proxy: null,\n assigned: null,\n };\n const touched = (prop: PropertyKey) => {\n if (typeof prop !== \"string\") return;\n record(node.isArray ? node.path : [...node.path, prop]);\n };\n node.proxy = new Proxy(value, {\n get: (_target, prop) => {\n if (prop === DRAFT) return node;\n const current = latest(node)[prop];\n if (typeof prop === \"symbol\") return current;\n if (nodeOf(current)) return current;\n if (isDraftable(current) && current === node.base[prop]) {\n prepareCopy(node);\n const child = createNode(current, node, [\n ...path,\n node.isArray ? Number(prop) : prop,\n ]);\n node.copy![prop] = child.proxy;\n return child.proxy;\n }\n return current;\n },\n set: (_target, prop, next) => {\n const source = latest(node);\n if (Object.hasOwn(source, prop) && source[prop] === next) return true;\n prepareCopy(node);\n markModified(node);\n touched(prop);\n node.copy![prop] = next;\n (node.assigned ??= new Set()).add(prop);\n return true;\n },\n deleteProperty: (_target, prop) => {\n if (!Object.hasOwn(latest(node), prop)) return true;\n prepareCopy(node);\n markModified(node);\n touched(prop);\n delete node.copy![prop];\n return true;\n },\n has: (_target, prop) => prop === DRAFT || Reflect.has(latest(node), prop),\n ...sharedDraftTraps(() => latest(node), \"inside a mutator\"),\n });\n return node;\n };\n\n const root = createNode(base as unknown as object, null, []);\n mutate(root.proxy as T);\n return { state: finalize(root) as T, changedPaths };\n};\n"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ //#region src/util/statePath.d.ts
2
+ type StatePath = readonly (string | number)[];
3
+ declare const isUnsafeKey: (key: string | number, parent: string | number | undefined) => boolean;
4
+ type WalkedPath = {
5
+ /** Container of the final key; undefined when the path is empty. */
6
+ parent: unknown;
7
+ key: string | number;
8
+ leaf: unknown;
9
+ };
10
+ declare const walkStatePath: (root: unknown, path: StatePath, mode: "read" | "parent" | "resolve") => WalkedPath | undefined;
11
+ declare const readAtPath: (root: unknown, path: StatePath) => unknown;
12
+ //#endregion
13
+ export { StatePath, WalkedPath, isUnsafeKey, readAtPath, walkStatePath };
14
+ //# sourceMappingURL=statePath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statePath.d.ts","names":[],"sources":["../../src/util/statePath.ts"],"mappings":";KAAY;cAEC,cACX,sBACA;KAGU;;EAEV;EACA;EACA;;cAQW,gBACX,eACA,MAAM,WACN,wCACC;cAoCU,aAAc,eAAe,MAAM"}
@@ -0,0 +1,31 @@
1
+ //#region src/util/statePath.ts
2
+ const isUnsafeKey = (key, parent) => key === "__proto__" || key === "prototype" && parent === "constructor";
3
+ const walkStatePath = (root, path, mode) => {
4
+ let parent = void 0;
5
+ let key = "";
6
+ let node = root;
7
+ for (let depth = 0; depth < path.length; depth++) {
8
+ const k = path[depth];
9
+ const mustResolve = mode === "resolve" || mode === "parent" && depth < path.length - 1;
10
+ if (mode !== "read") if (typeof k === "number") {
11
+ if (!Array.isArray(node) || !Number.isInteger(k) || k < 0) return;
12
+ if (mustResolve && k >= node.length) return void 0;
13
+ } else {
14
+ if (isUnsafeKey(k, path[depth - 1]) || typeof node !== "object" || node === null || Array.isArray(node)) return;
15
+ if (mustResolve && !Object.hasOwn(node, k)) return void 0;
16
+ }
17
+ parent = node;
18
+ key = k;
19
+ node = typeof node === "object" && node !== null && Object.hasOwn(node, k) ? node[k] : void 0;
20
+ }
21
+ return {
22
+ parent,
23
+ key,
24
+ leaf: node
25
+ };
26
+ };
27
+ const readAtPath = (root, path) => walkStatePath(root, path, "read").leaf;
28
+ //#endregion
29
+ export { isUnsafeKey, readAtPath, walkStatePath };
30
+
31
+ //# sourceMappingURL=statePath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statePath.js","names":["StatePath","isUnsafeKey","key","parent","WalkedPath","leaf","walkStatePath","root","path","mode","undefined","node","depth","length","k","mustResolve","Array","isArray","Number","isInteger","Object","hasOwn","Record","readAtPath"],"sources":["../../src/util/statePath.ts"],"sourcesContent":["export type StatePath = readonly (string | number)[];\n\nexport const isUnsafeKey = (\n key: string | number,\n parent: string | number | undefined,\n) => key === \"__proto__\" || (key === \"prototype\" && parent === \"constructor\");\n\nexport type WalkedPath = {\n /** Container of the final key; undefined when the path is empty. */\n parent: unknown;\n key: string | number;\n leaf: unknown;\n};\n\n// \"read\": lenient, never fails — missing steps yield an undefined leaf.\n// \"parent\": every step before the last must resolve; the leaf may not exist.\n// \"resolve\": every step including the last must resolve.\n// Strict modes require array+integer for number keys, plain object for string\n// keys, and reject unsafe (prototype-polluting) segments.\nexport const walkStatePath = (\n root: unknown,\n path: StatePath,\n mode: \"read\" | \"parent\" | \"resolve\",\n): WalkedPath | undefined => {\n let parent: unknown = undefined;\n let key: string | number = \"\";\n let node: unknown = root;\n for (let depth = 0; depth < path.length; depth++) {\n const k = path[depth]!;\n const mustResolve =\n mode === \"resolve\" || (mode === \"parent\" && depth < path.length - 1);\n if (mode !== \"read\") {\n if (typeof k === \"number\") {\n if (!Array.isArray(node) || !Number.isInteger(k) || k < 0) {\n return undefined;\n }\n if (mustResolve && k >= node.length) return undefined;\n } else {\n if (\n isUnsafeKey(k, path[depth - 1]) ||\n typeof node !== \"object\" ||\n node === null ||\n Array.isArray(node)\n ) {\n return undefined;\n }\n if (mustResolve && !Object.hasOwn(node, k)) return undefined;\n }\n }\n parent = node;\n key = k;\n node =\n typeof node === \"object\" && node !== null && Object.hasOwn(node, k)\n ? (node as Record<string | number, unknown>)[k]\n : undefined;\n }\n return { parent, key, leaf: node };\n};\n\nexport const readAtPath = (root: unknown, path: StatePath): unknown =>\n walkStatePath(root, path, \"read\")!.leaf;\n"],"mappings":";AAEA,MAAaC,eACXC,KACAC,WACGD,QAAQ,eAAgBA,QAAQ,eAAeC,WAAW;AAc/D,MAAaG,iBACXC,MACAC,MACAC,SAC2B;CAC3B,IAAIN,SAAkBO,KAAAA;CACtB,IAAIR,MAAuB;CAC3B,IAAIS,OAAgBJ;CACpB,KAAK,IAAIK,QAAQ,GAAGA,QAAQJ,KAAKK,QAAQD,SAAS;EAChD,MAAME,IAAIN,KAAKI;EACf,MAAMG,cACJN,SAAS,aAAcA,SAAS,YAAYG,QAAQJ,KAAKK,SAAS;EACpE,IAAIJ,SAAS,QACX,IAAI,OAAOK,MAAM,UAAU;GACzB,IAAI,CAACE,MAAMC,QAAQN,IAAI,KAAK,CAACO,OAAOC,UAAUL,CAAC,KAAKA,IAAI,GACtD;GAEF,IAAIC,eAAeD,KAAKH,KAAKE,QAAQ,OAAOH,KAAAA;EAC9C,OAAO;GACL,IACET,YAAYa,GAAGN,KAAKI,QAAQ,EAAE,KAC9B,OAAOD,SAAS,YAChBA,SAAS,QACTK,MAAMC,QAAQN,IAAI,GAElB;GAEF,IAAII,eAAe,CAACK,OAAOC,OAAOV,MAAMG,CAAC,GAAG,OAAOJ,KAAAA;EACrD;EAEFP,SAASQ;EACTT,MAAMY;EACNH,OACE,OAAOA,SAAS,YAAYA,SAAS,QAAQS,OAAOC,OAAOV,MAAMG,CAAC,IAC7DH,KAA0CG,KAC3CJ,KAAAA;CACR;CACA,OAAO;EAAEP;EAAQD;EAAKG,MAAMM;CAAK;AACnC;AAEA,MAAaY,cAAchB,MAAeC,SACxCF,cAAcC,MAAMC,MAAM,MAAM,CAAC,CAAEH"}
@@ -17,6 +17,11 @@ declare const encodeParams: (params: readonly unknown[]) => {
17
17
  params: unknown[];
18
18
  meta?: StatewireMetaEntry[];
19
19
  };
20
+ /** Encode Date instances inside a wire value, recording their paths in meta. */
21
+ declare const encodeValue: (value: unknown) => {
22
+ value: unknown;
23
+ meta?: StatewireMetaEntry[];
24
+ };
20
25
  //#endregion
21
- export { StatewireMetaEntry, encodeParams, reviveOps, reviveResponse, reviveValue };
26
+ export { StatewireMetaEntry, encodeParams, encodeValue, reviveOps, reviveResponse, reviveValue };
22
27
  //# sourceMappingURL=meta.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"meta.d.ts","names":[],"sources":["../../src/wire/meta.ts"],"mappings":";KAAY;EACV;EACA;;;;cAiBW,cAAe,gBAAgB;cA6C/B,YAAa;cAOb,iBAAkB;EAAY;EAAmB;GAC5D,UAAU,MACT;;;cAQU,eACX;EACG;EAAmB,OAAO"}
1
+ {"version":3,"file":"meta.d.ts","names":[],"sources":["../../src/wire/meta.ts"],"mappings":";KAEY;EACV;EACA;;;;cAiBW,cAAe,gBAAgB;cAwB/B,YAAa;cAOb,iBAAkB;EAAY;EAAmB;GAC5D,UAAU,MACT;;;cA+BU,eACX;EACG;EAAmB,OAAO;;;cAOlB,cACX;EACG;EAAgB,OAAO"}
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":";;;AAMA,MAAMK,gBAAgBC,KAAaC,WAA+B;CAChE,IACE,OAAOC,YAAY,eACnBA,QAAQC,IAAI,gBAAgB,cAE5BC,QAAQC,KACN,mCAAmCL,IAAG,MACnCC,WAAWK,KAAAA,IAAY,KAAK,WAAWL,OAAM,GAClD;AAEJ;AAEA,MAAMM,kBACJC,MACAC,SACU;CACV,MAAM,IAAIC,MACR,+BAA+BF,UAC5BC,KAAKE,WAAW,IAAI,KAAK,OAAOF,KAAKG,KAAK,GAAG,IAClD;AACF;AAEA,MAAMC,cACJC,OACAb,QACAQ,SACY;CACZ,IAAIK,iBAAiBC,MAAM,OAAO,IAAIA,KAAKD,MAAME,QAAQ,CAAC;CAC1D,IAAIC,MAAMC,QAAQJ,KAAK,GACrB,OAAOA,MAAMK,KAAKC,MAAMC,UAAU;EAChC,IAAID,SAASd,KAAAA,GAAWC,eAAe,aAAa,CAAC,GAAGE,MAAMY,KAAK,CAAC;EACpE,OAAOR,WAAWO,MAAMd,KAAAA,GAAW,CAAC,GAAGG,MAAMY,KAAK,CAAC;CACrD,CAAC;CAEH,IAAI,OAAOP,UAAU,YAAYA,UAAU,MAAM;EAC/C,MAAMQ,MAA+B,CAAC;EACtC,KAAK,MAAMtB,OAAOwB,OAAOC,KAAKX,KAAK,GAAG;GACpC,IAAInB,YAAYK,KAAKC,MAAM,GAAG;IAC5BF,aAAaC,KAAKC,MAAM;IACxB;GACF;GACA,MAAMmB,OAAQN,MAAkCd;GAChD,IAAIoB,SAASd,KAAAA,GAAW;GACxBgB,IAAItB,OAAOa,WAAWO,MAAMpB,KAAK,CAAC,GAAGS,MAAMT,GAAG,CAAC;EACjD;EACA,OAAOsB;CACT;CACA,IAAI,OAAOR,UAAU,YAAYP,eAAe,YAAYE,IAAI;CAChE,OAAOK;AACT;AAKA,MAAaY,iBAAiBZ,OAAgBb,WAC5CY,WAAWC,OAAOb,QAAQA,WAAWK,KAAAA,IAAY,CAAA,IAAK,CAACL,MAAM,CAAC;AAIhE,MAAM8B,WAAWD,uBACf,IAAIpB,MAAM,0BAA0BoB,GAAGA,GAAE,YAAaA,GAAGrB,KAAKG,KAAK,GAAG,GAAG;AAE3E,MAAMoB,gBACJC,SACAH,IACAI,WACW;CACX,IAAI,OAAOJ,GAAGhB,UAAU,YAAYoB,SAASD,QAAQtB,QACnD,MAAMoB,QAAQD,EAAE;CAElB,OAAOG,QAAQE,MAAM,GAAGD,MAAM,IAAIJ,GAAGhB,QAAQmB,QAAQE,MAAMD,MAAM;AACnE;AAEA,MAAME,YACJC,KACAP,OACG;CACH,MAAMI,SAASJ,GAAGrB,KAAKqB,GAAGrB,KAAKE,SAAS;CACxC,MAAM4B,SAAS3C,cAAcyC,IAAIC,OAAOR,GAAGrB,KAAK0B,MAAM,GAAG,EAAE,GAAG,SAAS;CACvE,IACEI,WAAWjC,KAAAA,KACX,OAAO4B,WAAW,YAClB,CAACM,OAAOC,UAAUP,MAAM,KACxBA,SAAS,GAET,MAAMH,QAAQD,EAAE;CAElB,MAAMY,SAASH,OAAOI;CACtB,IAAI1B,MAAMC,QAAQwB,MAAM,GAAG;EACzB,IAAIR,SAASQ,OAAO/B,QAAQ,MAAMoB,QAAQD,EAAE;EAC5CY,OAAOE,OAAOV,QAAQ,GAAGR,cAAcI,GAAGhB,KAAK,CAAC;CAClD,OAAO,IAAI,OAAO4B,WAAW,UAAU;EACrC,MAAMG,OAAOb,aAAaU,QAAQZ,IAAII,MAAM;EAC5C,IAAIJ,GAAGrB,KAAKE,WAAW,GACrB0B,IAAIC,QAAQO;OAEZ,OAAQ5C,OAA4CsC,OAAOvC,OAAO6C;CAEtE,OACE,MAAMd,QAAQD,EAAE;AAEpB;AAEA,MAAMgB,eAAeC,MAAiB1B,OAAeS,OAAkB;CACrE,IAAIT,SAAS0B,KAAKpC,QAAQ,MAAMoB,QAAQD,EAAE;CAC1C,IAAIA,GAAGA,OAAO,WACZiB,KAAK1B,SAASK,cAAcI,GAAGhB,KAAK;MAEpCiC,KAAKH,OAAOvB,OAAO,CAAC;AAExB;AAEA,MAAM2B,iBACJC,KACAjD,KACA8B,OACG;CACH,IAAIA,GAAGA,OAAO,WACZmB,IAAIjD,OAAO0B,cAAcI,GAAGhB,OAAOd,GAAG;MACjC;EACL,IAAI,CAACwB,OAAO0B,OAAOD,KAAKjD,GAAG,GAAG,MAAM+B,QAAQD,EAAE;EAC9C,OAAOmB,IAAIjD;CACb;AACF;AAEA,MAAMmD,kBAAkBd,KAAyBP,OAAwB;CACvE,IAAIA,GAAGA,OAAO,OAAO;EACnB,IAAIA,GAAGrB,KAAKE,WAAW,GAAG,MAAMoB,QAAQD,EAAE;EAC1CM,SAASC,KAAKP,EAAE;EAChB;CACF;CACA,IAAIA,GAAGrB,KAAKE,WAAW,GAAG;EACxB,IAAImB,GAAGA,OAAO,WAAW,MAAMC,QAAQD,EAAE;EACzCO,IAAIC,QAAQZ,cAAcI,GAAGhB,KAAK;EAClC;CACF;CACA,MAAMyB,SAAS3C,cAAcyC,IAAIC,OAAOR,GAAGrB,MAAM,QAAQ;CACzD,IAAI8B,WAAWjC,KAAAA,GAAW,MAAMyB,QAAQD,EAAE;CAC1C,IAAI,OAAOS,OAAOvC,QAAQ,UACxB8C,YAAYP,OAAOtC,QAAqBsC,OAAOvC,KAAK8B,EAAE;MAEtDkB,cAAcT,OAAOtC,QAAmCsC,OAAOvC,KAAK8B,EAAE;AAE1E;AAEA,MAAMsB,4BAAY,IAAIC,IAAa;AACnC,MAAMC,iBAAiBC,SAAkB;CACvC,IAAIH,UAAUI,IAAID,IAAI,GAAG;CACzBH,UAAUK,IAAIF,IAAI;CAClBnD,QAAQC,KAAK,uCAAuCqD,KAAKC,UAAUJ,IAAI,GAAG;AAC5E;AAEA,MAAMK,cAAcC,UAClBA,MAAM1C,KAAKV,SAASA,KAAK0B,MAAM,CAAC,CAAC;AAEnC,MAAa2B,WACXC,MACAjC,OACsD;CACtD,MAAMmC,WAAWvE,gBAAgB,EAAE4C,OAAOyB,KAAK,IAAIG,UACjDf,eAAee,OAAOpC,EAAE,CAC1B;CACA,OAAO;EACLQ,OAAO2B,SAAS3B,MAAMA;EACtB0B,cAAcJ,WAAWK,SAASD,YAAY;CAChD;AACF;AAEA,MAAaG,YACX7B,OACA8B,QAKG;CACH,MAAMC,SAAoB,CAAA;CAC1B,MAAMC,UAAuB,CAAA;CAC7B,KAAK,MAAMxC,MAAMsC,KACf,IAAItC,GAAGA,OAAO,SACZuC,OAAOE,KAAK7C,cAAcI,GAAGhB,KAAK,CAAC;MAC9B,IAAIgB,GAAGA,OAAO,aAAaA,GAAGA,OAAO,SAASA,GAAGA,OAAO,UAC7DwC,QAAQC,KAAKzC,EAAE;MAEfwB,cAAexB,GAAuBA,EAAE;CAG5C,MAAMmC,WAAWvE,gBAAgB,EAAE4C,MAAM,IAAI4B,UAAU;EACrD,KAAK,MAAMpC,MAAMwC,SAASnB,eAAee,OAAOpC,EAAE;CACpD,CAAC;CACD,OAAO;EACLQ,OAAO2B,SAAS3B,MAAMA;EACtB+B;EACAL,cAAcJ,WAAWK,SAASD,YAAY;CAChD;AACF"}