statewire 0.0.0-0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -1
- package/dist/client/changes.d.ts +7 -0
- package/dist/client/changes.d.ts.map +1 -0
- package/dist/client/changes.js +25 -0
- package/dist/client/changes.js.map +1 -0
- package/dist/client/connection.d.ts +63 -0
- package/dist/client/connection.d.ts.map +1 -0
- package/dist/client/connection.js +211 -0
- package/dist/client/connection.js.map +1 -0
- package/dist/client/core.d.ts +14 -0
- package/dist/client/core.d.ts.map +1 -0
- package/dist/client/core.js +272 -0
- package/dist/client/core.js.map +1 -0
- package/dist/client/session.d.ts +71 -0
- package/dist/client/session.d.ts.map +1 -0
- package/dist/client/session.js +529 -0
- package/dist/client/session.js.map +1 -0
- package/dist/client/sessionStore.d.ts +16 -0
- package/dist/client/sessionStore.d.ts.map +1 -0
- package/dist/client/sessionStore.js +14 -0
- package/dist/client/sessionStore.js.map +1 -0
- package/dist/client/timing.d.ts +11 -0
- package/dist/client/timing.d.ts.map +1 -0
- package/dist/client/timing.js +15 -0
- package/dist/client/timing.js.map +1 -0
- package/dist/host/adapter.d.ts +8 -0
- package/dist/host/adapter.d.ts.map +1 -0
- package/dist/host/adapter.js +23 -0
- package/dist/host/adapter.js.map +1 -0
- package/dist/host/draft.d.ts +28 -0
- package/dist/host/draft.d.ts.map +1 -0
- package/dist/host/draft.js +211 -0
- package/dist/host/draft.js.map +1 -0
- package/dist/host/engine.d.ts +14 -0
- package/dist/host/engine.d.ts.map +1 -0
- package/dist/host/engine.js +17 -0
- package/dist/host/engine.js.map +1 -0
- package/dist/host/host.d.ts +62 -0
- package/dist/host/host.d.ts.map +1 -0
- package/dist/host/host.js +331 -0
- package/dist/host/host.js.map +1 -0
- package/dist/host/socket.d.ts +14 -0
- package/dist/host/socket.d.ts.map +1 -0
- package/dist/host/socket.js +459 -0
- package/dist/host/socket.js.map +1 -0
- package/dist/host-internal.d.ts +27 -0
- package/dist/host-internal.d.ts.map +1 -0
- package/dist/host-internal.js +3 -0
- package/dist/host.d.ts +44 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/statewire.d.ts +104 -0
- package/dist/statewire.d.ts.map +1 -0
- package/dist/statewire.js +53 -0
- package/dist/statewire.js.map +1 -0
- package/dist/transports/fake-sse.d.ts +12 -0
- package/dist/transports/fake-sse.d.ts.map +1 -0
- package/dist/transports/fake-sse.js +39 -0
- package/dist/transports/fake-sse.js.map +1 -0
- package/dist/transports/fixtures.d.ts +33 -0
- package/dist/transports/fixtures.d.ts.map +1 -0
- package/dist/transports/fixtures.js +50 -0
- package/dist/transports/fixtures.js.map +1 -0
- package/dist/transports/remote.d.ts +12 -0
- package/dist/transports/remote.d.ts.map +1 -0
- package/dist/transports/remote.js +21 -0
- package/dist/transports/remote.js.map +1 -0
- package/dist/transports/sse-parser.d.ts +5 -0
- package/dist/transports/sse-parser.d.ts.map +1 -0
- package/dist/transports/sse-parser.js +26 -0
- package/dist/transports/sse-parser.js.map +1 -0
- package/dist/transports/sse.d.ts +64 -0
- package/dist/transports/sse.d.ts.map +1 -0
- package/dist/transports/sse.js +137 -0
- package/dist/transports/sse.js.map +1 -0
- package/dist/transports/testing.d.ts +32 -0
- package/dist/transports/testing.d.ts.map +1 -0
- package/dist/transports/testing.js +94 -0
- package/dist/transports/testing.js.map +1 -0
- package/dist/transports/ws.d.ts +9 -0
- package/dist/transports/ws.d.ts.map +1 -0
- package/dist/transports/ws.js +169 -0
- package/dist/transports/ws.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/dist/util/draftTraps.d.ts +6 -0
- package/dist/util/draftTraps.d.ts.map +1 -0
- package/dist/util/draftTraps.js +25 -0
- package/dist/util/draftTraps.js.map +1 -0
- package/dist/util/macrotask.d.ts +5 -0
- package/dist/util/macrotask.d.ts.map +1 -0
- package/dist/util/macrotask.js +25 -0
- package/dist/util/macrotask.js.map +1 -0
- package/dist/util/methodProxy.d.ts +5 -0
- package/dist/util/methodProxy.d.ts.map +1 -0
- package/dist/util/methodProxy.js +17 -0
- package/dist/util/methodProxy.js.map +1 -0
- package/dist/util/randomId.d.ts +5 -0
- package/dist/util/randomId.d.ts.map +1 -0
- package/dist/util/randomId.js +6 -0
- package/dist/util/randomId.js.map +1 -0
- package/dist/util/recordMutations.d.ts +10 -0
- package/dist/util/recordMutations.d.ts.map +1 -0
- package/dist/util/recordMutations.js +109 -0
- package/dist/util/recordMutations.js.map +1 -0
- package/dist/util/statePath.d.ts +14 -0
- package/dist/util/statePath.d.ts.map +1 -0
- package/dist/util/statePath.js +31 -0
- package/dist/util/statePath.js.map +1 -0
- package/dist/wire/meta.d.ts +27 -0
- package/dist/wire/meta.d.ts.map +1 -0
- package/dist/wire/meta.js +75 -0
- package/dist/wire/meta.js.map +1 -0
- package/dist/wire/ops.d.ts +19 -0
- package/dist/wire/ops.d.ts.map +1 -0
- package/dist/wire/ops.js +112 -0
- package/dist/wire/ops.js.map +1 -0
- package/package.json +52 -5
- package/src/client/changes.ts +38 -0
- package/src/client/connection.ts +301 -0
- package/src/client/core.ts +312 -0
- package/src/client/session.ts +696 -0
- package/src/client/sessionStore.ts +23 -0
- package/src/client/timing.ts +14 -0
- package/src/host/adapter.ts +33 -0
- package/src/host/draft.ts +323 -0
- package/src/host/engine.ts +13 -0
- package/src/host/host.ts +482 -0
- package/src/host/socket.ts +542 -0
- package/src/host-internal.ts +28 -0
- package/src/host.ts +61 -0
- package/src/index.ts +5 -0
- package/src/statewire.ts +200 -0
- package/src/transports/fake-sse.ts +51 -0
- package/src/transports/fixtures.ts +60 -0
- package/src/transports/remote.ts +30 -0
- package/src/transports/sse-parser.ts +24 -0
- package/src/transports/sse.ts +267 -0
- package/src/transports/testing.ts +123 -0
- package/src/transports/ws.ts +256 -0
- package/src/types.ts +88 -0
- package/src/util/draftTraps.ts +32 -0
- package/src/util/macrotask.ts +25 -0
- package/src/util/methodProxy.ts +16 -0
- package/src/util/randomId.ts +4 -0
- package/src/util/recordMutations.ts +0 -0
- package/src/util/statePath.ts +61 -0
- package/src/wire/meta.ts +101 -0
- package/src/wire/ops.ts +202 -0
- package/index.js +0 -1
|
@@ -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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/util/recordMutations.d.ts
|
|
2
|
+
type PathKey = string | number;
|
|
3
|
+
type Path = readonly PathKey[];
|
|
4
|
+
declare const recordMutations: <T>(base: T, mutate: (draft: T) => void) => {
|
|
5
|
+
state: T;
|
|
6
|
+
changedPaths: Path[];
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { recordMutations };
|
|
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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { sharedDraftTraps } from "./draftTraps.js";
|
|
2
|
+
//#region src/util/recordMutations.ts
|
|
3
|
+
const DRAFT = Symbol("statewire.draft");
|
|
4
|
+
const isDraftable = (value) => {
|
|
5
|
+
if (typeof value !== "object" || value === null) return false;
|
|
6
|
+
if (Array.isArray(value)) return true;
|
|
7
|
+
const proto = Object.getPrototypeOf(value);
|
|
8
|
+
return proto === Object.prototype || proto === null;
|
|
9
|
+
};
|
|
10
|
+
const nodeOf = (value) => typeof value === "object" && value !== null ? value[DRAFT] : void 0;
|
|
11
|
+
const latest = (node) => node.copy ?? node.base;
|
|
12
|
+
const prepareCopy = (node) => {
|
|
13
|
+
node.copy ??= node.isArray ? node.base.slice() : { ...node.base };
|
|
14
|
+
};
|
|
15
|
+
const markModified = (node) => {
|
|
16
|
+
for (let n = node; n && !n.modified; n = n.parent) n.modified = true;
|
|
17
|
+
};
|
|
18
|
+
const resolveAssigned = (container) => {
|
|
19
|
+
for (const key of Object.keys(container)) {
|
|
20
|
+
const value = container[key];
|
|
21
|
+
const child = nodeOf(value);
|
|
22
|
+
if (child) container[key] = finalize(child);
|
|
23
|
+
else if (isDraftable(value)) resolveAssigned(value);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const finalize = (node) => {
|
|
27
|
+
if (!node.modified) return node.base;
|
|
28
|
+
const result = node.copy;
|
|
29
|
+
for (const key of Object.keys(result)) {
|
|
30
|
+
const value = result[key];
|
|
31
|
+
const child = nodeOf(value);
|
|
32
|
+
if (child) result[key] = finalize(child);
|
|
33
|
+
else if (node.assigned?.has(key) && isDraftable(value)) resolveAssigned(value);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
const recordMutations = (base, mutate) => {
|
|
38
|
+
if (!isDraftable(base)) throw new Error("statewire: cannot record mutations on primitive state");
|
|
39
|
+
const changedPaths = [];
|
|
40
|
+
const seen = /* @__PURE__ */ new Set();
|
|
41
|
+
const record = (path) => {
|
|
42
|
+
const key = path.join("\0");
|
|
43
|
+
if (seen.has(key)) return;
|
|
44
|
+
seen.add(key);
|
|
45
|
+
changedPaths.push(path);
|
|
46
|
+
};
|
|
47
|
+
const createNode = (value, parent, path) => {
|
|
48
|
+
const node = {
|
|
49
|
+
base: value,
|
|
50
|
+
copy: null,
|
|
51
|
+
modified: false,
|
|
52
|
+
isArray: Array.isArray(value),
|
|
53
|
+
parent,
|
|
54
|
+
path,
|
|
55
|
+
proxy: null,
|
|
56
|
+
assigned: null
|
|
57
|
+
};
|
|
58
|
+
const touched = (prop) => {
|
|
59
|
+
if (typeof prop !== "string") return;
|
|
60
|
+
record(node.isArray ? node.path : [...node.path, prop]);
|
|
61
|
+
};
|
|
62
|
+
node.proxy = new Proxy(value, {
|
|
63
|
+
get: (_target, prop) => {
|
|
64
|
+
if (prop === DRAFT) return node;
|
|
65
|
+
const current = latest(node)[prop];
|
|
66
|
+
if (typeof prop === "symbol") return current;
|
|
67
|
+
if (nodeOf(current)) return current;
|
|
68
|
+
if (isDraftable(current) && current === node.base[prop]) {
|
|
69
|
+
prepareCopy(node);
|
|
70
|
+
const child = createNode(current, node, [...path, node.isArray ? Number(prop) : prop]);
|
|
71
|
+
node.copy[prop] = child.proxy;
|
|
72
|
+
return child.proxy;
|
|
73
|
+
}
|
|
74
|
+
return current;
|
|
75
|
+
},
|
|
76
|
+
set: (_target, prop, next) => {
|
|
77
|
+
const source = latest(node);
|
|
78
|
+
if (Object.hasOwn(source, prop) && source[prop] === next) return true;
|
|
79
|
+
prepareCopy(node);
|
|
80
|
+
markModified(node);
|
|
81
|
+
touched(prop);
|
|
82
|
+
node.copy[prop] = next;
|
|
83
|
+
(node.assigned ??= /* @__PURE__ */ new Set()).add(prop);
|
|
84
|
+
return true;
|
|
85
|
+
},
|
|
86
|
+
deleteProperty: (_target, prop) => {
|
|
87
|
+
if (!Object.hasOwn(latest(node), prop)) return true;
|
|
88
|
+
prepareCopy(node);
|
|
89
|
+
markModified(node);
|
|
90
|
+
touched(prop);
|
|
91
|
+
delete node.copy[prop];
|
|
92
|
+
return true;
|
|
93
|
+
},
|
|
94
|
+
has: (_target, prop) => prop === DRAFT || Reflect.has(latest(node), prop),
|
|
95
|
+
...sharedDraftTraps(() => latest(node), "inside a mutator")
|
|
96
|
+
});
|
|
97
|
+
return node;
|
|
98
|
+
};
|
|
99
|
+
const root = createNode(base, null, []);
|
|
100
|
+
mutate(root.proxy);
|
|
101
|
+
return {
|
|
102
|
+
state: finalize(root),
|
|
103
|
+
changedPaths
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
//#endregion
|
|
107
|
+
export { recordMutations };
|
|
108
|
+
|
|
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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/wire/meta.d.ts
|
|
2
|
+
type StatewireMetaEntry = {
|
|
3
|
+
path: (string | number)[];
|
|
4
|
+
type: string;
|
|
5
|
+
};
|
|
6
|
+
/** Revive meta-typed locations inside a wire value (in place). Unknown types
|
|
7
|
+
* are skipped; a path that does not resolve to a valid target faults. */
|
|
8
|
+
declare const reviveValue: (value: unknown, meta: unknown) => unknown;
|
|
9
|
+
declare const reviveOps: (ops: unknown[]) => unknown[];
|
|
10
|
+
declare const reviveResponse: <T extends {
|
|
11
|
+
payload?: unknown;
|
|
12
|
+
meta?: unknown;
|
|
13
|
+
}>(response: T) => T;
|
|
14
|
+
/** Encode Date instances inside command params as canonical ISO strings,
|
|
15
|
+
* recording their paths (starting at the param index) in meta. */
|
|
16
|
+
declare const encodeParams: (params: readonly unknown[]) => {
|
|
17
|
+
params: unknown[];
|
|
18
|
+
meta?: StatewireMetaEntry[];
|
|
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
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { StatewireMetaEntry, encodeParams, encodeValue, reviveOps, reviveResponse, reviveValue };
|
|
27
|
+
//# sourceMappingURL=meta.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { walkStatePath } from "../util/statePath.js";
|
|
2
|
+
//#region src/wire/meta.ts
|
|
3
|
+
const fault = (message) => {
|
|
4
|
+
throw new Error(`statewire: protocol error: ${message}`);
|
|
5
|
+
};
|
|
6
|
+
const isPath = (path) => Array.isArray(path) && path.every((seg) => typeof seg === "string" || typeof seg === "number" && Number.isInteger(seg));
|
|
7
|
+
/** Revive meta-typed locations inside a wire value (in place). Unknown types
|
|
8
|
+
* are skipped; a path that does not resolve to a valid target faults. */
|
|
9
|
+
const reviveValue = (value, meta) => {
|
|
10
|
+
if (!Array.isArray(meta)) return fault("meta must be an array");
|
|
11
|
+
for (const entry of meta) {
|
|
12
|
+
const path = typeof entry === "object" && entry !== null ? entry.path : null;
|
|
13
|
+
if (!isPath(path)) return fault("meta entry must carry a path array");
|
|
14
|
+
if (entry.type !== "date") continue;
|
|
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);
|
|
19
|
+
if (Number.isNaN(date.getTime())) return fault(`invalid date at meta path ${path.join(".")}`);
|
|
20
|
+
if (walked.parent === void 0) return date;
|
|
21
|
+
walked.parent[walked.key] = date;
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
};
|
|
25
|
+
const reviveOps = (ops) => ops.map((op) => {
|
|
26
|
+
if (typeof op !== "object" || op === null || !("meta" in op)) return op;
|
|
27
|
+
const { meta, ...rest } = op;
|
|
28
|
+
return {
|
|
29
|
+
...rest,
|
|
30
|
+
value: reviveValue(rest.value, meta)
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
const reviveResponse = (response) => {
|
|
34
|
+
if (response.meta === void 0) return response;
|
|
35
|
+
const { meta, ...rest } = response;
|
|
36
|
+
return {
|
|
37
|
+
...rest,
|
|
38
|
+
payload: reviveValue(rest.payload, meta)
|
|
39
|
+
};
|
|
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
|
+
};
|
|
53
|
+
/** Encode Date instances inside command params as canonical ISO strings,
|
|
54
|
+
* recording their paths (starting at the param index) in meta. */
|
|
55
|
+
const encodeParams = (params) => {
|
|
56
|
+
const meta = [];
|
|
57
|
+
const encoded = params.map((param, i) => walkDates(param, [i], meta));
|
|
58
|
+
return meta.length > 0 ? {
|
|
59
|
+
params: encoded,
|
|
60
|
+
meta
|
|
61
|
+
} : { params: encoded };
|
|
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
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
export { encodeParams, encodeValue, reviveOps, reviveResponse, reviveValue };
|
|
74
|
+
|
|
75
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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":";;AAOA,MAAMI,SAASC,YAA2B;CACxC,MAAM,IAAIC,MAAM,8BAA8BD,SAAS;AACzD;AAEA,MAAME,UAAUL,SACdM,MAAMC,QAAQP,IAAI,KAClBA,KAAKQ,OACFC,QACC,OAAOA,QAAQ,YACd,OAAOA,QAAQ,YAAYC,OAAOC,UAAUF,GAAG,CACpD;;;AAIF,MAAaG,eAAeC,OAAgBC,SAA2B;CACrE,IAAI,CAACR,MAAMC,QAAQO,IAAI,GAAG,OAAOZ,MAAM,uBAAuB;CAC9D,KAAK,MAAMa,SAASD,MAA8C;EAChE,MAAMd,OACJ,OAAOe,UAAU,YAAYA,UAAU,OAAOA,MAAMf,OAAO;EAC7D,IAAI,CAACK,OAAOL,IAAI,GAAG,OAAOE,MAAM,oCAAoC;EACpE,IAAIa,MAAMd,SAAS,QAAQ;EAC3B,MAAMe,SAASlB,cAAce,OAAOb,MAAM,SAAS;EACnD,IAAIgB,WAAWC,KAAAA,GACb,OAAOf,MAAM,aAAaF,KAAKkB,KAAK,GAAG,EAAC,kBAAmB;EAE7D,IAAI,OAAOF,OAAOG,SAAS,UACzB,OAAOjB,MAAM,aAAaF,KAAKkB,KAAK,GAAG,EAAC,oBAAqB;EAE/D,MAAME,OAAO,IAAIC,KAAKL,OAAOG,IAAI;EACjC,IAAIT,OAAOY,MAAMF,KAAKG,QAAQ,CAAC,GAC7B,OAAOrB,MAAM,6BAA6BF,KAAKkB,KAAK,GAAG,GAAG;EAE5D,IAAIF,OAAOQ,WAAWP,KAAAA,GAAW,OAAOG;EACxC,OAAQI,OAA4CR,OAAOU,OAAON;CACpE;CACA,OAAOP;AACT;AAEA,MAAac,aAAaC,QACxBA,IAAIC,KAAKC,OAAO;CACd,IAAI,OAAOA,OAAO,YAAYA,OAAO,QAAQ,EAAE,UAAUA,KAAK,OAAOA;CACrE,MAAM,EAAEhB,MAAM,GAAGiB,SAASD;CAC1B,OAAO;EAAE,GAAGC;EAAMlB,OAAOD,YAAYmB,KAAKlB,OAAOC,IAAI;CAAE;AACzD,CAAC;AAEH,MAAakB,kBACXE,aACM;CACN,IAAIA,SAASpB,SAASG,KAAAA,GAAW,OAAOiB;CACxC,MAAM,EAAEpB,MAAM,GAAGiB,SAASG;CAC1B,OAAO;EAAE,GAAGH;EAAME,SAASrB,YAAYmB,KAAKE,SAASnB,IAAI;CAAE;AAC7D;AAEA,MAAMsB,aACJC,MACArC,MACAc,SACY;CACZ,IAAIuB,gBAAgBhB,MAAM;EACxBP,KAAKwB,KAAK;GAAEtC;GAAMC,MAAM;EAAO,CAAC;EAChC,OAAOoC,KAAKE,YAAY;CAC1B;CACA,IAAIjC,MAAMC,QAAQ8B,IAAI,GACpB,OAAOA,KAAKR,KAAKW,MAAMC,MAAML,UAAUI,MAAM,CAAC,GAAGxC,MAAMyC,CAAC,GAAG3B,IAAI,CAAC;CAElE,IAAI,OAAOuB,SAAS,YAAYA,SAAS,MACvC,OAAOK,OAAOC,YACZD,OAAOE,KAAKP,IAAI,CAAC,CAACR,KAAKH,QAAQ,CAC7BA,KACAU,UAAWC,KAAiCX,MAAM,CAAC,GAAG1B,MAAM0B,GAAG,GAAGZ,IAAI,CAAC,CACxE,CACH;CAEF,OAAOuB;AACT;;;AAIA,MAAaQ,gBACXC,WACuD;CACvD,MAAMhC,OAA6B,CAAA;CACnC,MAAMiC,UAAUD,OAAOjB,KAAKmB,OAAOP,MAAML,UAAUY,OAAO,CAACP,CAAC,GAAG3B,IAAI,CAAC;CACpE,OAAOA,KAAKmC,SAAS,IAAI;EAAEH,QAAQC;EAASjC;CAAK,IAAI,EAAEgC,QAAQC,QAAQ;AACzE;;AAGA,MAAaG,eACXrC,UACoD;CACpD,MAAMC,OAA6B,CAAA;CACnC,MAAMiC,UAAUX,UAAUvB,OAAO,CAAA,GAAIC,IAAI;CACzC,OAAOA,KAAKmC,SAAS,IAAI;EAAEpC,OAAOkC;EAASjC;CAAK,IAAI,EAAED,OAAOkC,QAAQ;AACvE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Statewire } from "../types.js";
|
|
2
|
+
//#region src/wire/ops.d.ts
|
|
3
|
+
type StatewirePath = readonly (string | number)[];
|
|
4
|
+
declare const sanitizeValue: (value: unknown, parent?: string) => unknown;
|
|
5
|
+
type MutableOp = Extract<Statewire.Op, {
|
|
6
|
+
op: "replace" | "add" | "remove";
|
|
7
|
+
}>;
|
|
8
|
+
declare const applyOp: (root: unknown, op: MutableOp) => {
|
|
9
|
+
state: unknown;
|
|
10
|
+
changedPaths: StatewirePath[];
|
|
11
|
+
};
|
|
12
|
+
declare const applyOps: (state: unknown, ops: readonly unknown[]) => {
|
|
13
|
+
state: unknown;
|
|
14
|
+
events: unknown[];
|
|
15
|
+
changedPaths: StatewirePath[];
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { StatewirePath, applyOp, applyOps, sanitizeValue };
|
|
19
|
+
//# sourceMappingURL=ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { recordMutations } from "../util/recordMutations.js";
|
|
2
|
+
import { isUnsafeKey, walkStatePath } from "../util/statePath.js";
|
|
3
|
+
//#region src/wire/ops.ts
|
|
4
|
+
const warnStripped = (key, parent) => {
|
|
5
|
+
if (typeof process === "undefined" || process.env["NODE_ENV"] !== "production") console.warn(`statewire: stripped unsafe key "${key}"` + (parent === void 0 ? "" : ` under "${parent}"`));
|
|
6
|
+
};
|
|
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) => {
|
|
11
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
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;
|
|
31
|
+
};
|
|
32
|
+
const sanitizeValue = (value, parent) => sanitizeAt(value, parent, parent === void 0 ? [] : [parent]);
|
|
33
|
+
const invalid = (op) => /* @__PURE__ */ new Error(`Statewire cannot apply ${op.op} at path: ${op.path.join(".")}`);
|
|
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);
|
|
37
|
+
};
|
|
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);
|
|
51
|
+
};
|
|
52
|
+
const applyInList = (list, index, op) => {
|
|
53
|
+
if (index >= list.length) throw invalid(op);
|
|
54
|
+
if (op.op === "replace") list[index] = sanitizeValue(op.value);
|
|
55
|
+
else list.splice(index, 1);
|
|
56
|
+
};
|
|
57
|
+
const applyInObject = (obj, key, op) => {
|
|
58
|
+
if (op.op === "replace") obj[key] = sanitizeValue(op.value, key);
|
|
59
|
+
else {
|
|
60
|
+
if (!Object.hasOwn(obj, key)) throw invalid(op);
|
|
61
|
+
delete obj[key];
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const applyOpToDraft = (box, op) => {
|
|
65
|
+
if (op.op === "add") {
|
|
66
|
+
if (op.path.length === 0) throw invalid(op);
|
|
67
|
+
applyAdd(box, op);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (op.path.length === 0) {
|
|
71
|
+
if (op.op !== "replace") throw invalid(op);
|
|
72
|
+
box.state = sanitizeValue(op.value);
|
|
73
|
+
return;
|
|
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);
|
|
79
|
+
};
|
|
80
|
+
const warnedOps = /* @__PURE__ */ new Set();
|
|
81
|
+
const warnUnknownOp = (type) => {
|
|
82
|
+
if (warnedOps.has(type)) return;
|
|
83
|
+
warnedOps.add(type);
|
|
84
|
+
console.warn(`statewire: ignoring unknown op type ${JSON.stringify(type)}`);
|
|
85
|
+
};
|
|
86
|
+
const unboxPaths = (paths) => paths.map((path) => path.slice(1));
|
|
87
|
+
const applyOp = (root, op) => {
|
|
88
|
+
const recorded = recordMutations({ state: root }, (draft) => applyOpToDraft(draft, op));
|
|
89
|
+
return {
|
|
90
|
+
state: recorded.state.state,
|
|
91
|
+
changedPaths: unboxPaths(recorded.changedPaths)
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
const applyOps = (state, ops) => {
|
|
95
|
+
const events = [];
|
|
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);
|
|
99
|
+
else warnUnknownOp(op.op);
|
|
100
|
+
const recorded = recordMutations({ state }, (draft) => {
|
|
101
|
+
for (const op of mutable) applyOpToDraft(draft, op);
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
state: recorded.state.state,
|
|
105
|
+
events,
|
|
106
|
+
changedPaths: unboxPaths(recorded.changedPaths)
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
//#endregion
|
|
110
|
+
export { applyOp, applyOps, sanitizeValue };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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,11 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "statewire",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "State over the wire: snapshot-first delta streaming and command submission for any state and any command",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/assistant-ui/harness-sdk"
|
|
8
|
+
"url": "git+https://github.com/assistant-ui/harness-sdk.git",
|
|
9
|
+
"directory": "packages/statewire/core"
|
|
8
10
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
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"
|
|
27
|
+
},
|
|
28
|
+
"./testing": {
|
|
29
|
+
"types": "./dist/transports/testing.d.ts",
|
|
30
|
+
"default": "./dist/transports/testing.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "aui-build",
|
|
42
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
43
|
+
"test": "vitest run"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@assistant-ui/tap": "^0.9.6"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": "^18 || ^19"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@assistant-ui/x-buildutils": "^0.0.20",
|
|
53
|
+
"@types/react": "^19.2.17",
|
|
54
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
55
|
+
"react": "^19.2.8",
|
|
56
|
+
"vitest": "^4.1.10"
|
|
57
|
+
}
|
|
11
58
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { StatewirePath } from "../wire/ops";
|
|
2
|
+
import type { Statewire } from "../types";
|
|
3
|
+
import { readAtPath } from "../util/statePath";
|
|
4
|
+
|
|
5
|
+
const keysAt = (node: unknown): string[] =>
|
|
6
|
+
typeof node === "object" && node !== null ? Object.keys(node) : [];
|
|
7
|
+
|
|
8
|
+
const isPrefix = (prefix: StatewirePath, path: StatewirePath): boolean =>
|
|
9
|
+
prefix.length <= path.length &&
|
|
10
|
+
prefix.every((key, i) => String(key) === String(path[i]));
|
|
11
|
+
|
|
12
|
+
export const createChanges = (
|
|
13
|
+
prev: unknown,
|
|
14
|
+
next: unknown,
|
|
15
|
+
changedPaths: readonly StatewirePath[],
|
|
16
|
+
): Statewire.Changes => ({
|
|
17
|
+
isChangedAt: (path) =>
|
|
18
|
+
changedPaths.some((p) => isPrefix(p, path) || isPrefix(path, p)),
|
|
19
|
+
getChangedKeys: (path) => {
|
|
20
|
+
const wiped = changedPaths.some(
|
|
21
|
+
(p) => p.length <= path.length && isPrefix(p, path),
|
|
22
|
+
);
|
|
23
|
+
if (wiped) {
|
|
24
|
+
const keys = keysAt(readAtPath(next, path));
|
|
25
|
+
for (const key of keysAt(readAtPath(prev, path))) {
|
|
26
|
+
if (!keys.includes(key)) keys.push(key);
|
|
27
|
+
}
|
|
28
|
+
return keys;
|
|
29
|
+
}
|
|
30
|
+
const keys: string[] = [];
|
|
31
|
+
for (const p of changedPaths) {
|
|
32
|
+
if (p.length <= path.length || !isPrefix(path, p)) continue;
|
|
33
|
+
const key = String(p[path.length]);
|
|
34
|
+
if (!keys.includes(key)) keys.push(key);
|
|
35
|
+
}
|
|
36
|
+
return keys;
|
|
37
|
+
},
|
|
38
|
+
});
|