statewire 0.12.0 → 0.13.0

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.
@@ -5,6 +5,14 @@ import { sanitizeValue } from "../wire/ops.js";
5
5
  const NODE = Symbol("statewire.draftNode");
6
6
  const isIndex = (key) => /^(0|[1-9]\d*)$/.test(key);
7
7
  const invalid = (path, message) => /* @__PURE__ */ new Error(`statewire: ${message} at path: ${path.join(".")}`);
8
+ const codePointLength = (text) => {
9
+ let count = 0;
10
+ for (let index = 0; index < text.length; index += 1) {
11
+ if (text.codePointAt(index) > 65535) index += 1;
12
+ count += 1;
13
+ }
14
+ return count;
15
+ };
8
16
  const createStateDraft = (host) => {
9
17
  let gen = 0;
10
18
  const dirty = [];
@@ -45,7 +53,7 @@ const createStateDraft = (host) => {
45
53
  path: [
46
54
  ...node.path,
47
55
  key,
48
- existing.length
56
+ codePointLength(existing)
49
57
  ],
50
58
  value: suffix
51
59
  }, changedPathFor(node, key));
@@ -1 +1 @@
1
- {"version":3,"file":"draft.js","names":["Statewire","StatewirePath","sanitizeValue","isUnsafeKey","sharedDraftTraps","DraftHost","snapshot","record","op","Op","changedPath","StateDraft","proxy","T","flush","invalidate","NODE","Symbol","NodeHandle","append","key","suffix","isIndex","test","invalid","path","message","Error","join","Container","Record","Node","parent","gen","base","copy","children","Map","isArray","handle","createStateDraft","host","dirty","latest","node","resolve","value","prepareForWrite","Array","slice","push","changedPathFor","applyAppend","rawKey","container","Number","existing","Object","hasOwn","undefined","length","createNode","seed","childKey","current","childProxy","prop","child","get","set","assign","old","isObjectContainer","Proxy","_target","c","next","isInteger","String","i","splice","index","deleteProperty","has","Reflect","root","TKey"],"sources":["../../src/host/draft.ts"],"sourcesContent":["import type { Statewire } from \"../types\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { sanitizeValue } from \"../wire/ops\";\nimport { isUnsafeKey } from \"../util/statePath\";\nimport { sharedDraftTraps } from \"../util/draftTraps\";\n\nexport type DraftHost = {\n snapshot: () => unknown;\n record: (op: Statewire.Op, changedPath: StatewirePath) => void;\n};\n\nexport type StateDraft<T> = {\n proxy: T;\n /** Seal pending writes and return the committed state. */\n flush: () => unknown;\n /** Committed state was replaced externally; cached nodes re-resolve. */\n invalidate: () => void;\n};\n\nconst NODE = Symbol(\"statewire.draftNode\");\n\ntype NodeHandle = {\n append: (key: string | number, suffix: string) => void;\n};\n\nconst isIndex = (key: string) => /^(0|[1-9]\\d*)$/.test(key);\n\nconst invalid = (path: StatewirePath, message: string) =>\n new Error(`statewire: ${message} at path: ${path.join(\".\")}`);\n\ntype Container = Record<string | number, unknown>;\n\ntype Node = {\n parent: Node | null;\n key: string | number;\n path: StatewirePath;\n gen: number;\n base: unknown;\n copy: Container | null;\n children: Map<string, Node>;\n isArray: boolean;\n proxy: object;\n handle: NodeHandle;\n};\n\nexport const createStateDraft = <T>(host: DraftHost): StateDraft<T> => {\n let gen = 0;\n const dirty: Node[] = [];\n\n const latest = (node: Node): unknown => node.copy ?? node.base;\n\n const resolve = (node: Node): Node => {\n if (node.gen === gen) return node;\n node.gen = gen;\n if (node.parent === null) {\n if (node.copy === null) node.base = host.snapshot();\n return node;\n }\n const value = (latest(resolve(node.parent)) as Container)[node.key];\n if (value !== latest(node)) {\n node.base = value;\n node.copy = null;\n }\n return node;\n };\n\n const prepareForWrite = (node: Node): Container => {\n resolve(node);\n if (node.copy !== null) return node.copy;\n const base = node.base;\n node.copy = Array.isArray(base)\n ? (base.slice() as unknown as Container)\n : { ...(base as Container) };\n dirty.push(node);\n if (node.parent !== null) {\n prepareForWrite(node.parent)[node.key] = node.copy;\n }\n return node.copy;\n };\n\n const changedPathFor = (node: Node, key: string | number): StatewirePath =>\n Array.isArray(latest(node)) ? node.path : [...node.path, key];\n\n const applyAppend = (node: Node, rawKey: string | number, suffix: string) => {\n const container = latest(resolve(node)) as Container;\n const key =\n Array.isArray(container) && typeof rawKey === \"string\" && isIndex(rawKey)\n ? Number(rawKey)\n : rawKey;\n const existing = Object.hasOwn(container as object, key)\n ? container[key]\n : undefined;\n if (typeof existing !== \"string\") {\n throw invalid([...node.path, key], \"cannot append to a non-string\");\n }\n if (suffix === \"\") return;\n prepareForWrite(node)[key] = existing + suffix;\n host.record(\n { op: \"add\", path: [...node.path, key, existing.length], value: suffix },\n changedPathFor(node, key),\n );\n };\n\n const createNode = (parent: Node | null, key: string | number): Node => {\n const seed =\n parent === null ? host.snapshot() : (latest(parent) as Container)[key];\n const node: Node = {\n parent,\n key,\n path: parent === null ? [] : [...parent.path, key],\n gen,\n base: seed,\n copy: null,\n children: new Map(),\n isArray: Array.isArray(seed),\n proxy: null!,\n handle: null!,\n };\n node.handle = {\n append: (childKey, suffix) => applyAppend(node, childKey, suffix),\n };\n\n const current = () => latest(resolve(node));\n\n const childProxy = (\n prop: string,\n childKey: string | number,\n value: object,\n ) => {\n let child = node.children.get(prop);\n if (child === undefined || child.isArray !== Array.isArray(value)) {\n child = createNode(node, childKey);\n node.children.set(prop, child);\n }\n return child.proxy;\n };\n\n const assign = (\n container: Container,\n key: string | number,\n value: unknown,\n ) => {\n const old = Object.hasOwn(container as object, key)\n ? container[key]\n : undefined;\n if (old === value) return;\n const isObjectContainer = !Array.isArray(container);\n if (\n isObjectContainer &&\n isUnsafeKey(key, node.path[node.path.length - 1])\n ) {\n throw invalid([...node.path, key], `cannot set unsafe key \"${key}\"`);\n }\n if (value === undefined) {\n if (!isObjectContainer) {\n throw invalid([...node.path, key], \"cannot serialize undefined\");\n }\n delete prepareForWrite(node)[key];\n host.record({ op: \"remove\", path: [...node.path, key] }, [\n ...node.path,\n key,\n ]);\n gen++;\n return;\n }\n prepareForWrite(node)[key] = isObjectContainer\n ? sanitizeValue(value, key as string)\n : sanitizeValue(value);\n host.record(\n { op: \"replace\", path: [...node.path, key], value },\n changedPathFor(node, key),\n );\n if (\n (typeof old === \"object\" && old !== null) ||\n (typeof value === \"object\" && value !== null)\n ) {\n gen++;\n }\n };\n\n node.proxy = new Proxy(seed as object, {\n get: (_target, prop) => {\n if (prop === NODE) return node.handle;\n const container = current();\n if (typeof prop === \"symbol\") {\n return (container as Record<symbol, unknown>)[prop];\n }\n const c = container as Container;\n const key = Array.isArray(c) && isIndex(prop) ? Number(prop) : prop;\n const value = c[key];\n if (typeof value === \"object\" && value !== null) {\n return childProxy(prop, key, value);\n }\n return value;\n },\n set: (_target, prop, value) => {\n if (typeof prop === \"symbol\") return false;\n const container = current();\n if (Array.isArray(container)) {\n if (prop === \"length\") {\n const next = Number(value);\n if (\n !Number.isInteger(next) ||\n next < 0 ||\n next > container.length\n ) {\n throw invalid(\n node.path,\n `cannot set list length to ${String(value)}`,\n );\n }\n if (next === container.length) return true;\n const copy = prepareForWrite(node) as unknown as unknown[];\n for (let i = container.length - 1; i >= next; i--) {\n copy.splice(i, 1);\n host.record({ op: \"remove\", path: [...node.path, i] }, node.path);\n }\n gen++;\n return true;\n }\n if (!isIndex(prop)) {\n throw invalid(node.path, `cannot set key \"${prop}\" on a list`);\n }\n const index = Number(prop);\n if (index > container.length) {\n throw invalid(node.path, `list index ${index} out of range`);\n }\n if (index === container.length) {\n if (value === undefined) {\n throw invalid(\n [...node.path, index],\n \"cannot serialize undefined\",\n );\n }\n prepareForWrite(node)[index] = sanitizeValue(value);\n host.record(\n { op: \"add\", path: [...node.path, index], value },\n node.path,\n );\n gen++;\n return true;\n }\n assign(container as unknown as Container, index, value);\n return true;\n }\n assign(container as Container, prop, value);\n return true;\n },\n deleteProperty: (_target, prop) => {\n if (typeof prop === \"symbol\") return false;\n const container = current();\n if (Array.isArray(container)) {\n if (!isIndex(prop) || Number(prop) >= container.length) return true;\n const index = Number(prop);\n (prepareForWrite(node) as unknown as unknown[]).splice(index, 1);\n host.record({ op: \"remove\", path: [...node.path, index] }, node.path);\n gen++;\n return true;\n }\n if (!Object.hasOwn(container as object, prop)) return true;\n if (isUnsafeKey(prop, node.path[node.path.length - 1])) {\n throw invalid(\n [...node.path, prop],\n `cannot remove unsafe key \"${prop}\"`,\n );\n }\n delete prepareForWrite(node)[prop];\n host.record({ op: \"remove\", path: [...node.path, prop] }, [\n ...node.path,\n prop,\n ]);\n gen++;\n return true;\n },\n has: (_target, prop) => Reflect.has(current() as object, prop),\n ...sharedDraftTraps(() => current() as object, \"on a state draft\"),\n });\n return node;\n };\n\n const root = createNode(null, \"\");\n\n return {\n proxy: root.proxy as T,\n flush: () => {\n if (dirty.length === 0) return host.snapshot();\n for (const node of dirty) {\n if (node.copy !== null) {\n node.base = node.copy;\n node.copy = null;\n }\n }\n dirty.length = 0;\n return root.base;\n },\n invalidate: () => {\n gen++;\n },\n };\n};\n\n/**\n * Append `suffix` to the string at `parent[key]` on a statewire state draft,\n * recording a single end-offset text-insert `add` op instead of a full\n * `replace` — use it on token-streaming hot paths. Throws when the current\n * value is not a string.\n */\nexport const append = <TKey extends string | number>(\n parent: { readonly [P in TKey]: string },\n key: TKey,\n suffix: string,\n): void => {\n const handle = (parent as { [NODE]?: NodeHandle })[NODE];\n if (handle === undefined) {\n throw new Error(\"statewire: append expects a state draft object\");\n }\n if (typeof suffix !== \"string\") {\n throw new Error(\"statewire: append suffix must be a string\");\n }\n handle.append(key, suffix);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"draft.js","names":["Statewire","StatewirePath","sanitizeValue","isUnsafeKey","sharedDraftTraps","DraftHost","snapshot","record","op","Op","changedPath","StateDraft","proxy","T","flush","invalidate","NODE","Symbol","NodeHandle","append","key","suffix","isIndex","test","invalid","path","message","Error","join","Container","Record","codePointLength","text","count","index","length","codePointAt","Node","parent","gen","base","copy","children","Map","isArray","handle","createStateDraft","host","dirty","latest","node","resolve","value","prepareForWrite","Array","slice","push","changedPathFor","applyAppend","rawKey","container","Number","existing","Object","hasOwn","undefined","createNode","seed","childKey","current","childProxy","prop","child","get","set","assign","old","isObjectContainer","Proxy","_target","c","next","isInteger","String","i","splice","deleteProperty","has","Reflect","root","TKey"],"sources":["../../src/host/draft.ts"],"sourcesContent":["import type { Statewire } from \"../types\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { sanitizeValue } from \"../wire/ops\";\nimport { isUnsafeKey } from \"../util/statePath\";\nimport { sharedDraftTraps } from \"../util/draftTraps\";\n\nexport type DraftHost = {\n snapshot: () => unknown;\n record: (op: Statewire.Op, changedPath: StatewirePath) => void;\n};\n\nexport type StateDraft<T> = {\n proxy: T;\n /** Seal pending writes and return the committed state. */\n flush: () => unknown;\n /** Committed state was replaced externally; cached nodes re-resolve. */\n invalidate: () => void;\n};\n\nconst NODE = Symbol(\"statewire.draftNode\");\n\ntype NodeHandle = {\n append: (key: string | number, suffix: string) => void;\n};\n\nconst isIndex = (key: string) => /^(0|[1-9]\\d*)$/.test(key);\n\nconst invalid = (path: StatewirePath, message: string) =>\n new Error(`statewire: ${message} at path: ${path.join(\".\")}`);\n\ntype Container = Record<string | number, unknown>;\n\n// Text-insert offsets are Unicode code points on the wire, not UTF-16 units.\nconst codePointLength = (text: string): number => {\n let count = 0;\n for (let index = 0; index < text.length; index += 1) {\n if (text.codePointAt(index)! > 0xffff) index += 1;\n count += 1;\n }\n return count;\n};\n\ntype Node = {\n parent: Node | null;\n key: string | number;\n path: StatewirePath;\n gen: number;\n base: unknown;\n copy: Container | null;\n children: Map<string, Node>;\n isArray: boolean;\n proxy: object;\n handle: NodeHandle;\n};\n\nexport const createStateDraft = <T>(host: DraftHost): StateDraft<T> => {\n let gen = 0;\n const dirty: Node[] = [];\n\n const latest = (node: Node): unknown => node.copy ?? node.base;\n\n const resolve = (node: Node): Node => {\n if (node.gen === gen) return node;\n node.gen = gen;\n if (node.parent === null) {\n if (node.copy === null) node.base = host.snapshot();\n return node;\n }\n const value = (latest(resolve(node.parent)) as Container)[node.key];\n if (value !== latest(node)) {\n node.base = value;\n node.copy = null;\n }\n return node;\n };\n\n const prepareForWrite = (node: Node): Container => {\n resolve(node);\n if (node.copy !== null) return node.copy;\n const base = node.base;\n node.copy = Array.isArray(base)\n ? (base.slice() as unknown as Container)\n : { ...(base as Container) };\n dirty.push(node);\n if (node.parent !== null) {\n prepareForWrite(node.parent)[node.key] = node.copy;\n }\n return node.copy;\n };\n\n const changedPathFor = (node: Node, key: string | number): StatewirePath =>\n Array.isArray(latest(node)) ? node.path : [...node.path, key];\n\n const applyAppend = (node: Node, rawKey: string | number, suffix: string) => {\n const container = latest(resolve(node)) as Container;\n const key =\n Array.isArray(container) && typeof rawKey === \"string\" && isIndex(rawKey)\n ? Number(rawKey)\n : rawKey;\n const existing = Object.hasOwn(container as object, key)\n ? container[key]\n : undefined;\n if (typeof existing !== \"string\") {\n throw invalid([...node.path, key], \"cannot append to a non-string\");\n }\n if (suffix === \"\") return;\n prepareForWrite(node)[key] = existing + suffix;\n host.record(\n {\n op: \"add\",\n path: [...node.path, key, codePointLength(existing)],\n value: suffix,\n },\n changedPathFor(node, key),\n );\n };\n\n const createNode = (parent: Node | null, key: string | number): Node => {\n const seed =\n parent === null ? host.snapshot() : (latest(parent) as Container)[key];\n const node: Node = {\n parent,\n key,\n path: parent === null ? [] : [...parent.path, key],\n gen,\n base: seed,\n copy: null,\n children: new Map(),\n isArray: Array.isArray(seed),\n proxy: null!,\n handle: null!,\n };\n node.handle = {\n append: (childKey, suffix) => applyAppend(node, childKey, suffix),\n };\n\n const current = () => latest(resolve(node));\n\n const childProxy = (\n prop: string,\n childKey: string | number,\n value: object,\n ) => {\n let child = node.children.get(prop);\n if (child === undefined || child.isArray !== Array.isArray(value)) {\n child = createNode(node, childKey);\n node.children.set(prop, child);\n }\n return child.proxy;\n };\n\n const assign = (\n container: Container,\n key: string | number,\n value: unknown,\n ) => {\n const old = Object.hasOwn(container as object, key)\n ? container[key]\n : undefined;\n if (old === value) return;\n const isObjectContainer = !Array.isArray(container);\n if (\n isObjectContainer &&\n isUnsafeKey(key, node.path[node.path.length - 1])\n ) {\n throw invalid([...node.path, key], `cannot set unsafe key \"${key}\"`);\n }\n if (value === undefined) {\n if (!isObjectContainer) {\n throw invalid([...node.path, key], \"cannot serialize undefined\");\n }\n delete prepareForWrite(node)[key];\n host.record({ op: \"remove\", path: [...node.path, key] }, [\n ...node.path,\n key,\n ]);\n gen++;\n return;\n }\n prepareForWrite(node)[key] = isObjectContainer\n ? sanitizeValue(value, key as string)\n : sanitizeValue(value);\n host.record(\n { op: \"replace\", path: [...node.path, key], value },\n changedPathFor(node, key),\n );\n if (\n (typeof old === \"object\" && old !== null) ||\n (typeof value === \"object\" && value !== null)\n ) {\n gen++;\n }\n };\n\n node.proxy = new Proxy(seed as object, {\n get: (_target, prop) => {\n if (prop === NODE) return node.handle;\n const container = current();\n if (typeof prop === \"symbol\") {\n return (container as Record<symbol, unknown>)[prop];\n }\n const c = container as Container;\n const key = Array.isArray(c) && isIndex(prop) ? Number(prop) : prop;\n const value = c[key];\n if (typeof value === \"object\" && value !== null) {\n return childProxy(prop, key, value);\n }\n return value;\n },\n set: (_target, prop, value) => {\n if (typeof prop === \"symbol\") return false;\n const container = current();\n if (Array.isArray(container)) {\n if (prop === \"length\") {\n const next = Number(value);\n if (\n !Number.isInteger(next) ||\n next < 0 ||\n next > container.length\n ) {\n throw invalid(\n node.path,\n `cannot set list length to ${String(value)}`,\n );\n }\n if (next === container.length) return true;\n const copy = prepareForWrite(node) as unknown as unknown[];\n for (let i = container.length - 1; i >= next; i--) {\n copy.splice(i, 1);\n host.record({ op: \"remove\", path: [...node.path, i] }, node.path);\n }\n gen++;\n return true;\n }\n if (!isIndex(prop)) {\n throw invalid(node.path, `cannot set key \"${prop}\" on a list`);\n }\n const index = Number(prop);\n if (index > container.length) {\n throw invalid(node.path, `list index ${index} out of range`);\n }\n if (index === container.length) {\n if (value === undefined) {\n throw invalid(\n [...node.path, index],\n \"cannot serialize undefined\",\n );\n }\n prepareForWrite(node)[index] = sanitizeValue(value);\n host.record(\n { op: \"add\", path: [...node.path, index], value },\n node.path,\n );\n gen++;\n return true;\n }\n assign(container as unknown as Container, index, value);\n return true;\n }\n assign(container as Container, prop, value);\n return true;\n },\n deleteProperty: (_target, prop) => {\n if (typeof prop === \"symbol\") return false;\n const container = current();\n if (Array.isArray(container)) {\n if (!isIndex(prop) || Number(prop) >= container.length) return true;\n const index = Number(prop);\n (prepareForWrite(node) as unknown as unknown[]).splice(index, 1);\n host.record({ op: \"remove\", path: [...node.path, index] }, node.path);\n gen++;\n return true;\n }\n if (!Object.hasOwn(container as object, prop)) return true;\n if (isUnsafeKey(prop, node.path[node.path.length - 1])) {\n throw invalid(\n [...node.path, prop],\n `cannot remove unsafe key \"${prop}\"`,\n );\n }\n delete prepareForWrite(node)[prop];\n host.record({ op: \"remove\", path: [...node.path, prop] }, [\n ...node.path,\n prop,\n ]);\n gen++;\n return true;\n },\n has: (_target, prop) => Reflect.has(current() as object, prop),\n ...sharedDraftTraps(() => current() as object, \"on a state draft\"),\n });\n return node;\n };\n\n const root = createNode(null, \"\");\n\n return {\n proxy: root.proxy as T,\n flush: () => {\n if (dirty.length === 0) return host.snapshot();\n for (const node of dirty) {\n if (node.copy !== null) {\n node.base = node.copy;\n node.copy = null;\n }\n }\n dirty.length = 0;\n return root.base;\n },\n invalidate: () => {\n gen++;\n },\n };\n};\n\n/**\n * Append `suffix` to the string at `parent[key]` on a statewire state draft,\n * recording a single end-offset text-insert `add` op instead of a full\n * `replace` — use it on token-streaming hot paths. Throws when the current\n * value is not a string.\n */\nexport const append = <TKey extends string | number>(\n parent: { readonly [P in TKey]: string },\n key: TKey,\n suffix: string,\n): void => {\n const handle = (parent as { [NODE]?: NodeHandle })[NODE];\n if (handle === undefined) {\n throw new Error(\"statewire: append expects a state draft object\");\n }\n if (typeof suffix !== \"string\") {\n throw new Error(\"statewire: append suffix must be a string\");\n }\n handle.append(key, suffix);\n};\n"],"mappings":"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"}
@@ -13,6 +13,9 @@ declare class StatewireClient<TState, TCommands extends Statewire.Commands = Sta
13
13
  get commands(): Statewire.CommandsProxy<TCommands>;
14
14
  get isBusy(): boolean;
15
15
  setContext(value: Record<string, unknown>): void;
16
+ get failed(): readonly StatewireClient.FailedCommand[];
17
+ retryFailed(entry: StatewireClient.FailedCommand): Promise<unknown>;
18
+ discardFailed(entry: StatewireClient.FailedCommand): void;
16
19
  subscribe(listener: () => void): () => void;
17
20
  dispose(): void;
18
21
  }
@@ -34,6 +37,8 @@ declare namespace StatewireClient {
34
37
  state: NonNullable<TState>;
35
38
  }) => void;
36
39
  onConnectionChange?: (connection: Connection) => void;
40
+ /** Drops a retained failed entry once a canonical snapshot shows its effect landed; checked against each new snapshot. */
41
+ failureCovered?: (state: NonNullable<TState>, entry: FailedCommand) => boolean;
37
42
  };
38
43
  type Helpers<TState, TCommands extends Statewire.Commands = Statewire.Commands> = {
39
44
  /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */
@@ -46,6 +51,12 @@ declare namespace StatewireClient {
46
51
  isBusy: boolean;
47
52
  /** Replace this client's ambient context on the server; throws on transports without a client context channel. */
48
53
  setContext: (value: Record<string, unknown>) => void;
54
+ /** Non-accepted sends retained in the session record; reload with it. */
55
+ failed: readonly FailedCommand[];
56
+ /** Re-enqueues the entry's command on fresh clocks and removes the entry. */
57
+ retryFailed: (entry: FailedCommand) => Promise<unknown>;
58
+ /** Removes the entry. */
59
+ discardFailed: (entry: FailedCommand) => void;
49
60
  };
50
61
  type ConnectionState = {
51
62
  status: "connecting";
@@ -129,6 +140,15 @@ declare namespace StatewireClient {
129
140
  seq: number;
130
141
  clientId?: string;
131
142
  }>) => "requeue" | "drop";
143
+ /** A non-accepted send retained for the user: the wire statement, its failure, and its original clocks. */
144
+ type FailedCommand = {
145
+ readonly command: string;
146
+ readonly params: readonly unknown[];
147
+ readonly meta?: unknown;
148
+ readonly failure: SendFailure;
149
+ readonly ts: number;
150
+ readonly seq: number;
151
+ };
132
152
  /** A command as persisted while un-durable: its lane seq plus the wire statement. */
133
153
  type SessionCommand = {
134
154
  seq: number;
@@ -144,6 +164,8 @@ declare namespace StatewireClient {
144
164
  commands: readonly SessionCommand[];
145
165
  /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */
146
166
  verdicts?: readonly SessionVerdict[];
167
+ /** Non-accepted sends retained for the user; survive record rebases and resets. */
168
+ failed?: readonly FailedCommand[];
147
169
  };
148
170
  /** A terminal verdict one sharer's connection carried, parked for the sharer awaiting the seq. */
149
171
  type SessionVerdict = {
@@ -1 +1 @@
1
- {"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;cAgCa,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,QAAQ,QAAQ,eACxC,gBAAgB,QAAQ,QAAQ;cA8CtB,oBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,QAAQ,QAAQ,eACxC,gBAAgB,gBAAgB,QAAQ,QAAQ;cAGtC,gBACX,QACA,kBAAkB,UAAU,WAAW,UAAU;mBAEhC;UACT;EAEI,YAAA,SAAS,gBAAgB,QAAQ,QAAQ;MAKjD,SAAS;MAIT,cAAc,gBAAgB;MAI9B,YAAY,UAAU,cAAc;MAIpC;EAIJ,WAAW,OAAO;EAIlB,UAAU;EAIV;;kBAOe;OACH,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;IAEjD,WAAW,gBAAgB,UAAU,QAAQ;;IAE7C;MACM,UAAU,gBAAgB;;;IAGhC,eAAe;IACf,aAAa,WAAW,YAAY,SAAS;IAC7C,cAAc,YAAY,YAAY;IACtC,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;IAE5B,sBAAsB,YAAY;;OAGxB,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;;IAGjD,OAAO;;IAEP,gBAAgB;IAChB,YAAY;IACZ,UAAU,UAAU,cAAc;;IAElC;;IAEA,aAAa,OAAO;;OAGV;IACN;;IACA;;IACA;;IAEA;IACA;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAkB;;OAC5B,aAAa;IAAoB;;OACjC,mBAAmB;;;;;OAMnB,WACV,QACA,kBAAkB,UAAU,WAAW,UAAU,aAEjD,OAAO,QACP,SAAS,UAAU,QAAQ,YAC3B,KAAK;OAGK;IACV;IACA;IACA;;;;;;;OAQU,YAAY,WAAW,OAAO;;;;;;;;;;;;;OAc9B;IAEN;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAiB;;IACjC;IAAiB;;IACjB;IAAiB;;IAEjB;IACA;IACA;;IAGA;IACA;;;OAIM,iBACV,SAAS;IACP;IACA;IACA;IACA;IACA;;;OAKQ;IACV;IACA;IACA;IACA;;;OAIU;IACV;IACA;IACA;IACA,mBAAmB;;IAEnB,oBAAoB;;;OAIV;IACV;IACA,cAAc;IACd;IACA;IACA;IACA;;;OAIU;IACV,YAAY;IACZ,OAAO,QAAQ;;IAEf,QAAQ,GAAG,UAAU,IAAI,QAAQ,OAAO,QAAQ;;IAEhD,aAAa;;;OAIH,gBACR,gBACE;IAAO;QAAyB,cAAc,QAAQ;OAEhD;IACV,eAAe;IACf;IACA,UAAU;IACV,eAAe,WAAW;IAC1B,gBAAgB;IAChB,YAAY,YAAY;;IAExB;;OAGU;IACV,eAAe;IACf;IACA,UAAU;IACV;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,gBAAgB;IAChB,YAAY,YAAY;;IAExB"}
1
+ {"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;cAgCa,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,QAAQ,QAAQ,eACxC,gBAAgB,QAAQ,QAAQ;cA6DtB,oBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,QAAQ,QAAQ,eACxC,gBAAgB,gBAAgB,QAAQ,QAAQ;cAGtC,gBACX,QACA,kBAAkB,UAAU,WAAW,UAAU;mBAEhC;UACT;EAEI,YAAA,SAAS,gBAAgB,QAAQ,QAAQ;MAKjD,SAAS;MAIT,cAAc,gBAAgB;MAI9B,YAAY,UAAU,cAAc;MAIpC;EAIJ,WAAW,OAAO;MAId,mBAAmB,gBAAgB;EAIvC,YAAY,OAAO,gBAAgB,gBAAgB;EAInD,cAAc,OAAO,gBAAgB;EAIrC,UAAU;EAIV;;kBAOe;OACH,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;IAEjD,WAAW,gBAAgB,UAAU,QAAQ;;IAE7C;MACM,UAAU,gBAAgB;;;IAGhC,eAAe;IACf,aAAa,WAAW,YAAY,SAAS;IAC7C,cAAc,YAAY,YAAY;IACtC,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;IAE5B,sBAAsB,YAAY;;IAElC,kBACE,OAAO,YAAY,SACnB,OAAO;;OAIC,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;;IAGjD,OAAO;;IAEP,gBAAgB;IAChB,YAAY;IACZ,UAAU,UAAU,cAAc;;IAElC;;IAEA,aAAa,OAAO;;IAEpB,iBAAiB;;IAEjB,cAAc,OAAO,kBAAkB;;IAEvC,gBAAgB,OAAO;;OAGb;IACN;;IACA;;IACA;;IAEA;IACA;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAkB;;OAC5B,aAAa;IAAoB;;OACjC,mBAAmB;;;;;OAMnB,WACV,QACA,kBAAkB,UAAU,WAAW,UAAU,aAEjD,OAAO,QACP,SAAS,UAAU,QAAQ,YAC3B,KAAK;OAGK;IACV;IACA;IACA;;;;;;;OAQU,YAAY,WAAW,OAAO;;;;;;;;;;;;;OAc9B;IAEN;IACA;IACA;IACA;IACA;;IAEA;IAAgB;IAAiB;;IACjC;IAAiB;;IACjB;IAAiB;;IAEjB;IACA;IACA;;IAGA;IACA;;;OAIM,iBACV,SAAS;IACP;IACA;IACA;IACA;IACA;;;OAKQ;aACD;aACA;aACA;aACA,SAAS;aACT;aACA;;;OAIC;IACV;IACA;IACA;IACA;;;OAIU;IACV;IACA;IACA;IACA,mBAAmB;;IAEnB,oBAAoB;;IAEpB,kBAAkB;;;OAIR;IACV;IACA,cAAc;IACd;IACA;IACA;IACA;;;OAIU;IACV,YAAY;IACZ,OAAO,QAAQ;;IAEf,QAAQ,GAAG,UAAU,IAAI,QAAQ,OAAO,QAAQ;;IAEhD,aAAa;;;OAIH,gBACR,gBACE;IAAO;QAAyB,cAAc,QAAQ;OAEhD;IACV,eAAe;IACf;IACA,UAAU;IACV,eAAe,WAAW;IAC1B,gBAAgB;IAChB,YAAY,YAAY;;IAExB;;OAGU;IACV,eAAe;IACf;IACA,UAAU;IACV;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,gBAAgB;IAChB,YAAY,YAAY;;IAExB"}
package/dist/statewire.js CHANGED
@@ -17,20 +17,31 @@ const useStatewire = (options) => {
17
17
  const [subscribe] = useState(() => (listener) => core.subscribe(() => flushTapSync(listener)));
18
18
  const state = useSyncExternalStore(subscribe, core.getState, core.getState);
19
19
  const isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);
20
+ const failed = useSyncExternalStore(subscribe, core.getFailed, core.getFailed);
20
21
  const connection = value.connection ?? LIVE_CONNECTION;
21
- return useMemo(() => ({
22
- state,
23
- getState: core.getState,
24
- connection,
25
- commands: core.commands,
26
- isBusy,
27
- setContext: (contextValue) => {
28
- if (!value.setContext) throw new Error("statewire: this transport has no client context channel");
29
- value.setContext(contextValue);
30
- }
31
- }), [
22
+ return useMemo(() => {
23
+ const failures = () => {
24
+ if (!value.failures) throw new Error("statewire: this transport has no failed-send lane");
25
+ return value.failures;
26
+ };
27
+ return {
28
+ state,
29
+ getState: core.getState,
30
+ connection,
31
+ commands: core.commands,
32
+ isBusy,
33
+ setContext: (contextValue) => {
34
+ if (!value.setContext) throw new Error("statewire: this transport has no client context channel");
35
+ value.setContext(contextValue);
36
+ },
37
+ failed,
38
+ retryFailed: (entry) => failures().retry(entry),
39
+ discardFailed: (entry_0) => failures().discard(entry_0)
40
+ };
41
+ }, [
32
42
  state,
33
43
  isBusy,
44
+ failed,
34
45
  connection,
35
46
  core,
36
47
  value
@@ -59,6 +70,15 @@ var StatewireClient = class {
59
70
  setContext(value) {
60
71
  this.root.getValue().setContext(value);
61
72
  }
73
+ get failed() {
74
+ return this.root.getValue().failed;
75
+ }
76
+ retryFailed(entry) {
77
+ return this.root.getValue().retryFailed(entry);
78
+ }
79
+ discardFailed(entry) {
80
+ this.root.getValue().discardFailed(entry);
81
+ }
62
82
  subscribe(listener) {
63
83
  return this.root.subscribe(listener);
64
84
  }
@@ -1 +1 @@
1
- {"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useContextProvider","useResource","useResources","withKey","ResourceElement","LIVE_CONNECTION","useStatewireCore","Statewire","TERMINAL","SessionStorageContext","useSessionTransport","transport","session","StatewireClient","SessionStorage","SessionTransport","useStatewire","Commands","options","Options","TState","TCommands","Helpers","latest","current","storage","undefined","value","core","subscribe","listener","state","getState","isBusy","getBusy","connection","commands","CommandsProxy","setContext","contextValue","Record","Error","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","dispose","unmount","initialState","optimistic","Optimistic","NonNullable","materialize","Materialize","onStateChange","ctx","changes","Changes","onCommandChange","update","CommandUpdate","onConnectionChange","ConnectionState","status","cause","attempt","nextRetryAt","lastError","message","payload","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","code","predecessorLost","OnSessionLost","pending","ReadonlyArray","method","params","SessionCommand","meta","SessionRecord","nextSeq","ctxNextSeq","verdicts","SessionVerdict","type","load","save","record","lock","fn","T","Promise","HeadersOption","HeadersInit","staleAuth","SSEOptions","sessionStore","url","headers","fetch","globalThis","onSessionLost","onChange","onDone","WSOptions","protocols","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useContextProvider,\n useResource,\n useResources,\n withKey,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { LIVE_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { TERMINAL } from \"./client/connection\";\nimport { SessionStorageContext } from \"./client/sessionStore\";\n\nconst useSessionTransport = ({\n transport,\n session,\n}: {\n transport: ResourceElement<Statewire<any, any>>;\n session: StatewireClient.SessionStorage | undefined;\n}): Statewire<unknown> =>\n useContextProvider(SessionStorageContext, session, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useResource(transport),\n );\n\n// The transport owns the session; it reads the storage off the context this\n// wrapper's own fiber provides, so plain-React callers get a provider too.\nconst SessionTransport = resource(useSessionTransport);\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const session = useResources(\n options.storage?.session !== undefined\n ? [withKey(\"session\", options.storage.session)]\n : [],\n )[0];\n const value = useResource(\n SessionTransport({ transport: options.transport, session }),\n );\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);\n const connection = value.connection ?? LIVE_CONNECTION;\n\n return useMemo(\n () => ({\n state,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n isBusy,\n setContext: (contextValue: Record<string, unknown>) => {\n if (!value.setContext) {\n throw new Error(\n \"statewire: this transport has no client context channel\",\n );\n }\n value.setContext(contextValue);\n },\n }),\n [state, isBusy, connection, core, value],\n );\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands>) {\n const ClientRoot = () => useStatewire<TState, TCommands>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n get isBusy(): boolean {\n return this.root.getValue().isBusy;\n }\n\n setContext(value: Record<string, unknown>): void {\n this.root.getValue().setContext(value);\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n transport: ResourceElement<Statewire<TState, TCommands>>;\n /** Durable session: the mounted storage holds this client's lane identity and its un-durable commands, which resend on the next attach. */\n storage?:\n | { session?: ResourceElement<SessionStorage> | undefined }\n | undefined;\n /** Served as the canonical state until the attach snapshot; own commands fold over it. */\n initialState?: TState;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n materialize?: Materialize<NonNullable<TState>>;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n onConnectionChange?: (connection: Connection) => void;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n /** Negation of the server's broadcast idle level (a transport without idle is always busy). */\n isBusy: boolean;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => void;\n };\n\n export type ConnectionState =\n | { status: \"connecting\" }\n | { status: \"live\" }\n | { status: \"idle\" }\n | {\n status: \"reconnecting\";\n cause: \"dropped\" | \"evicted\" | \"error\" | \"unauthorized\";\n attempt: number;\n nextRetryAt: number;\n lastError?: string;\n message?: string;\n }\n | { status: \"gone\"; message?: string; payload?: unknown };\n export type Connection = ConnectionState & { reconnect: () => void };\n export type ConnectionStatus = Connection[\"status\"];\n\n /**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n };\n\n /**\n * Mutate the draft to make server-accepted, still-uncommitted intents in\n * the state concrete (e.g. dispatch records into messages). Runs after the\n * optimistic folds on every published state.\n */\n export type Materialize<TState> = (draft: TState) => void;\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the server refused it\n * (`code` \"unknown-command\" | \"invalid-params\" for the static rejects,\n * code-less for handler rejects), \"lost\" = sent but no verdict will ever\n * arrive — the record is gone (a proven seq gap) or the channel died: it\n * may or may not have run, \"failed\" = the handler ran and threw,\n * \"crashed\" = the handler died involuntarily, \"expired\" = settled —\n * result, rejected, failed, or crashed — but the verdict outlived the\n * server's retention.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\n export type SendFailure =\n | {\n fate: \"rejected\";\n cause: \"server\";\n code?: string;\n message?: string;\n payload?: unknown;\n }\n | { fate: \"failed\"; cause: \"server\"; message?: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | { fate: \"expired\"; cause: \"server\" }\n | {\n fate: \"aborted\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n predecessorLost?: true;\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n };\n\n /** Held commands a proven record gap settled `{fate: \"lost\", cause: \"session-lost\"}`; return \"requeue\" to re-enqueue them on the reset clocks. */\n export type OnSessionLost = (\n pending: ReadonlyArray<{\n method: string;\n params: readonly unknown[];\n ts: number;\n seq: number;\n clientId?: string;\n }>,\n ) => \"requeue\" | \"drop\";\n\n /** A command as persisted while un-durable: its lane seq plus the wire statement. */\n export type SessionCommand = {\n seq: number;\n method: string;\n params: unknown[];\n meta?: unknown;\n };\n\n /** The persisted session: the lane identity, the lane clocks, and the commands the server has not marked durable. */\n export type SessionRecord = {\n clientId: string;\n nextSeq: number;\n ctxNextSeq: number;\n commands: readonly SessionCommand[];\n /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */\n verdicts?: readonly SessionVerdict[];\n };\n\n /** A terminal verdict one sharer's connection carried, parked for the sharer awaiting the seq. */\n export type SessionVerdict = {\n seq: number;\n type: (typeof TERMINAL)[number];\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n };\n\n /** Session storage seam; `save` must persist synchronously in the caller's flow. */\n export type SessionStorage = {\n load: () => SessionRecord | null;\n save: (record: SessionRecord) => void;\n /** Serializes record mutations across sharers; presence marks the storage shared. */\n lock?: <T>(fn: () => T | Promise<T>) => Promise<T>;\n /** Notifies of record saves by other sharers; returns unsubscribe. Required alongside `lock`. */\n subscribe?: (listener: () => void) => () => void;\n };\n\n /** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */\n export type HeadersOption =\n | HeadersInit\n | ((ctx: { staleAuth: boolean }) => HeadersInit | Promise<HeadersInit>);\n\n export type SSEOptions = {\n sessionStore?: SessionStorage | undefined;\n url: string;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n\n export type WSOptions = {\n sessionStore?: SessionStorage | undefined;\n url: string;\n headers?: HeadersOption;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n}\n"],"mappings":";;;;;AAgBA,MAAMiB,uBAAuB,EAC3BC,WACAC,cAKAZ,mBAAmBS,uBAAuBG,eAExCX,YAAYU,SAAS,CACvB;AAIF,MAAMI,mBAAmBhB,SAASW,mBAAmB;AAErD,MAAaM,gBAIXE,YAC+C;CAC/C,MAAMK,SAAS7B,OAAOwB,OAAO;CAC7BK,OAAOC,UAAUN;CAEjB,MAAMN,UAAUV,aACdgB,QAAQO,SAASb,YAAYc,KAAAA,IACzB,CAACvB,QAAQ,WAAWe,QAAQO,QAAQb,OAAO,CAAC,IAC5C,CAAA,CACN,CAAC,CAAC;CACF,MAAMe,QAAQ1B,YACZc,iBAAiB;EAAEJ,WAAWO,QAAQP;EAAWC;CAAQ,CAAC,CAC5D;CACA,MAAMgB,OAAOtB,uBAAuBiB,OAAOC,SAASG,KAAK;CACzD,MAAM,CAACE,aAAalC,gBACXmC,aACLF,KAAKC,gBAAgB/B,aAAagC,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQnC,qBACZiC,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,SAASrC,qBAAqBiC,WAAWD,KAAKM,SAASN,KAAKM,OAAO;CACzE,MAAMC,aAAaR,MAAMQ,cAAc9B;CAEvC,OAAOZ,eACE;EACLsC;EACAC,UAAUJ,KAAKI;EACfG;EACAC,UAAUR,KAAKQ;EACfH;EACAK,aAAaC,iBAA0C;GACrD,IAAI,CAACZ,MAAMW,YACT,MAAM,IAAIG,MACR,yDACF;GAEFd,MAAMW,WAAWC,YAAY;EAC/B;CACF,IACA;EAACR;EAAOE;EAAQE;EAAYP;EAAMD;CAAK,CACzC;AACF;AAEA,MAAae,qBAIXxB,YAEAnB,SAASiB,YAA+B,CAAC,CAACE,OAAO;AAEnD,IAAaL,kBAAb,MAGE;CACA;CACA,WAAmB;CAEnBgC,YAAY3B,SAAqD;EAC/D,MAAM4B,mBAAmB9B,aAAgCE,OAAO;EAChE,KAAKyB,OAAO9C,cAAciD,UAAU;CACtC;CAEA,IAAIf,QAAgB;EAClB,OAAO,KAAKY,KAAKI,SAAS,CAAC,CAACf,SAAS;CACvC;CAEA,IAAIG,aAAyC;EAC3C,OAAO,KAAKQ,KAAKI,SAAS,CAAC,CAACZ;CAC9B;CAEA,IAAIC,WAA+C;EACjD,OAAO,KAAKO,KAAKI,SAAS,CAAC,CAACX;CAC9B;CAEA,IAAIH,SAAkB;EACpB,OAAO,KAAKU,KAAKI,SAAS,CAAC,CAACd;CAC9B;CAEAK,WAAWX,OAAsC;EAC/C,KAAKgB,KAAKI,SAAS,CAAC,CAACT,WAAWX,KAAK;CACvC;CAEAE,UAAUC,UAAkC;EAC1C,OAAO,KAAKa,KAAKd,UAAUC,QAAQ;CACrC;CAEAmB,UAAgB;EACd,IAAI,KAAKL,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKO,QAAQ;CACpB;AACF"}
1
+ {"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useContextProvider","useResource","useResources","withKey","ResourceElement","LIVE_CONNECTION","useStatewireCore","Statewire","TERMINAL","SessionStorageContext","useSessionTransport","transport","session","StatewireClient","SessionStorage","SessionTransport","useStatewire","Commands","options","Options","TState","TCommands","Helpers","latest","current","storage","undefined","value","core","subscribe","listener","state","getState","isBusy","getBusy","failed","getFailed","connection","failures","NonNullable","Error","commands","CommandsProxy","setContext","contextValue","Record","retryFailed","entry","FailedCommand","retry","discardFailed","discard","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Promise","dispose","unmount","initialState","optimistic","Optimistic","materialize","Materialize","onStateChange","ctx","changes","Changes","onCommandChange","update","CommandUpdate","onConnectionChange","failureCovered","ConnectionState","status","cause","attempt","nextRetryAt","lastError","message","payload","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","code","predecessorLost","OnSessionLost","pending","ReadonlyArray","method","params","meta","failure","SessionCommand","SessionRecord","nextSeq","ctxNextSeq","verdicts","SessionVerdict","type","load","save","record","lock","fn","T","HeadersOption","HeadersInit","staleAuth","SSEOptions","sessionStore","url","headers","fetch","globalThis","onSessionLost","onChange","onDone","WSOptions","protocols","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useContextProvider,\n useResource,\n useResources,\n withKey,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { LIVE_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { TERMINAL } from \"./client/connection\";\nimport { SessionStorageContext } from \"./client/sessionStore\";\n\nconst useSessionTransport = ({\n transport,\n session,\n}: {\n transport: ResourceElement<Statewire<any, any>>;\n session: StatewireClient.SessionStorage | undefined;\n}): Statewire<unknown> =>\n useContextProvider(SessionStorageContext, session, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useResource(transport),\n );\n\n// The transport owns the session; it reads the storage off the context this\n// wrapper's own fiber provides, so plain-React callers get a provider too.\nconst SessionTransport = resource(useSessionTransport);\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const session = useResources(\n options.storage?.session !== undefined\n ? [withKey(\"session\", options.storage.session)]\n : [],\n )[0];\n const value = useResource(\n SessionTransport({ transport: options.transport, session }),\n );\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);\n const failed = useSyncExternalStore(\n subscribe,\n core.getFailed,\n core.getFailed,\n );\n const connection = value.connection ?? LIVE_CONNECTION;\n\n return useMemo(() => {\n const failures = (): NonNullable<typeof value.failures> => {\n if (!value.failures) {\n throw new Error(\"statewire: this transport has no failed-send lane\");\n }\n return value.failures;\n };\n return {\n state,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n isBusy,\n setContext: (contextValue: Record<string, unknown>) => {\n if (!value.setContext) {\n throw new Error(\n \"statewire: this transport has no client context channel\",\n );\n }\n value.setContext(contextValue);\n },\n failed,\n retryFailed: (entry: StatewireClient.FailedCommand) =>\n failures().retry(entry),\n discardFailed: (entry: StatewireClient.FailedCommand) =>\n failures().discard(entry),\n };\n }, [state, isBusy, failed, connection, core, value]);\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands>) {\n const ClientRoot = () => useStatewire<TState, TCommands>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n get isBusy(): boolean {\n return this.root.getValue().isBusy;\n }\n\n setContext(value: Record<string, unknown>): void {\n this.root.getValue().setContext(value);\n }\n\n get failed(): readonly StatewireClient.FailedCommand[] {\n return this.root.getValue().failed;\n }\n\n retryFailed(entry: StatewireClient.FailedCommand): Promise<unknown> {\n return this.root.getValue().retryFailed(entry);\n }\n\n discardFailed(entry: StatewireClient.FailedCommand): void {\n this.root.getValue().discardFailed(entry);\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n transport: ResourceElement<Statewire<TState, TCommands>>;\n /** Durable session: the mounted storage holds this client's lane identity and its un-durable commands, which resend on the next attach. */\n storage?:\n | { session?: ResourceElement<SessionStorage> | undefined }\n | undefined;\n /** Served as the canonical state until the attach snapshot; own commands fold over it. */\n initialState?: TState;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n materialize?: Materialize<NonNullable<TState>>;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n onConnectionChange?: (connection: Connection) => void;\n /** Drops a retained failed entry once a canonical snapshot shows its effect landed; checked against each new snapshot. */\n failureCovered?: (\n state: NonNullable<TState>,\n entry: FailedCommand,\n ) => boolean;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n /** Negation of the server's broadcast idle level (a transport without idle is always busy). */\n isBusy: boolean;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => void;\n /** Non-accepted sends retained in the session record; reload with it. */\n failed: readonly FailedCommand[];\n /** Re-enqueues the entry's command on fresh clocks and removes the entry. */\n retryFailed: (entry: FailedCommand) => Promise<unknown>;\n /** Removes the entry. */\n discardFailed: (entry: FailedCommand) => void;\n };\n\n export type ConnectionState =\n | { status: \"connecting\" }\n | { status: \"live\" }\n | { status: \"idle\" }\n | {\n status: \"reconnecting\";\n cause: \"dropped\" | \"evicted\" | \"error\" | \"unauthorized\";\n attempt: number;\n nextRetryAt: number;\n lastError?: string;\n message?: string;\n }\n | { status: \"gone\"; message?: string; payload?: unknown };\n export type Connection = ConnectionState & { reconnect: () => void };\n export type ConnectionStatus = Connection[\"status\"];\n\n /**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n };\n\n /**\n * Mutate the draft to make server-accepted, still-uncommitted intents in\n * the state concrete (e.g. dispatch records into messages). Runs after the\n * optimistic folds on every published state.\n */\n export type Materialize<TState> = (draft: TState) => void;\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the server refused it\n * (`code` \"unknown-command\" | \"invalid-params\" for the static rejects,\n * code-less for handler rejects), \"lost\" = sent but no verdict will ever\n * arrive — the record is gone (a proven seq gap) or the channel died: it\n * may or may not have run, \"failed\" = the handler ran and threw,\n * \"crashed\" = the handler died involuntarily, \"expired\" = settled —\n * result, rejected, failed, or crashed — but the verdict outlived the\n * server's retention.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\n export type SendFailure =\n | {\n fate: \"rejected\";\n cause: \"server\";\n code?: string;\n message?: string;\n payload?: unknown;\n }\n | { fate: \"failed\"; cause: \"server\"; message?: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | { fate: \"expired\"; cause: \"server\" }\n | {\n fate: \"aborted\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n predecessorLost?: true;\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n };\n\n /** Held commands a proven record gap settled `{fate: \"lost\", cause: \"session-lost\"}`; return \"requeue\" to re-enqueue them on the reset clocks. */\n export type OnSessionLost = (\n pending: ReadonlyArray<{\n method: string;\n params: readonly unknown[];\n ts: number;\n seq: number;\n clientId?: string;\n }>,\n ) => \"requeue\" | \"drop\";\n\n /** A non-accepted send retained for the user: the wire statement, its failure, and its original clocks. */\n export type FailedCommand = {\n readonly command: string;\n readonly params: readonly unknown[];\n readonly meta?: unknown;\n readonly failure: SendFailure;\n readonly ts: number;\n readonly seq: number;\n };\n\n /** A command as persisted while un-durable: its lane seq plus the wire statement. */\n export type SessionCommand = {\n seq: number;\n method: string;\n params: unknown[];\n meta?: unknown;\n };\n\n /** The persisted session: the lane identity, the lane clocks, and the commands the server has not marked durable. */\n export type SessionRecord = {\n clientId: string;\n nextSeq: number;\n ctxNextSeq: number;\n commands: readonly SessionCommand[];\n /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */\n verdicts?: readonly SessionVerdict[];\n /** Non-accepted sends retained for the user; survive record rebases and resets. */\n failed?: readonly FailedCommand[];\n };\n\n /** A terminal verdict one sharer's connection carried, parked for the sharer awaiting the seq. */\n export type SessionVerdict = {\n seq: number;\n type: (typeof TERMINAL)[number];\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n };\n\n /** Session storage seam; `save` must persist synchronously in the caller's flow. */\n export type SessionStorage = {\n load: () => SessionRecord | null;\n save: (record: SessionRecord) => void;\n /** Serializes record mutations across sharers; presence marks the storage shared. */\n lock?: <T>(fn: () => T | Promise<T>) => Promise<T>;\n /** Notifies of record saves by other sharers; returns unsubscribe. Required alongside `lock`. */\n subscribe?: (listener: () => void) => () => void;\n };\n\n /** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */\n export type HeadersOption =\n | HeadersInit\n | ((ctx: { staleAuth: boolean }) => HeadersInit | Promise<HeadersInit>);\n\n export type SSEOptions = {\n sessionStore?: SessionStorage | undefined;\n url: string;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n\n export type WSOptions = {\n sessionStore?: SessionStorage | undefined;\n url: string;\n headers?: HeadersOption;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n}\n"],"mappings":";;;;;AAgBA,MAAMiB,uBAAuB,EAC3BC,WACAC,cAKAZ,mBAAmBS,uBAAuBG,eAExCX,YAAYU,SAAS,CACvB;AAIF,MAAMI,mBAAmBhB,SAASW,mBAAmB;AAErD,MAAaM,gBAIXE,YAC+C;CAC/C,MAAMK,SAAS7B,OAAOwB,OAAO;CAC7BK,OAAOC,UAAUN;CAEjB,MAAMN,UAAUV,aACdgB,QAAQO,SAASb,YAAYc,KAAAA,IACzB,CAACvB,QAAQ,WAAWe,QAAQO,QAAQb,OAAO,CAAC,IAC5C,CAAA,CACN,CAAC,CAAC;CACF,MAAMe,QAAQ1B,YACZc,iBAAiB;EAAEJ,WAAWO,QAAQP;EAAWC;CAAQ,CAAC,CAC5D;CACA,MAAMgB,OAAOtB,uBAAuBiB,OAAOC,SAASG,KAAK;CACzD,MAAM,CAACE,aAAalC,gBACXmC,aACLF,KAAKC,gBAAgB/B,aAAagC,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQnC,qBACZiC,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,SAASrC,qBAAqBiC,WAAWD,KAAKM,SAASN,KAAKM,OAAO;CACzE,MAAMC,SAASvC,qBACbiC,WACAD,KAAKQ,WACLR,KAAKQ,SACP;CACA,MAAMC,aAAaV,MAAMU,cAAchC;CAEvC,OAAOZ,cAAc;EACnB,MAAM6C,iBAAqD;GACzD,IAAI,CAACX,MAAMW,UACT,MAAM,IAAIE,MAAM,mDAAmD;GAErE,OAAOb,MAAMW;EACf;EACA,OAAO;GACLP;GACAC,UAAUJ,KAAKI;GACfK;GACAI,UAAUb,KAAKa;GACfR;GACAU,aAAaC,iBAA0C;IACrD,IAAI,CAACjB,MAAMgB,YACT,MAAM,IAAIH,MACR,yDACF;IAEFb,MAAMgB,WAAWC,YAAY;GAC/B;GACAT;GACAW,cAAcC,UACZT,SAAS,CAAC,CAACW,MAAMF,KAAK;GACxBG,gBAAgBH,YACdT,SAAS,CAAC,CAACa,QAAQJ,OAAK;EAC5B;CACF,GAAG;EAAChB;EAAOE;EAAQE;EAAQE;EAAYT;EAAMD;CAAK,CAAC;AACrD;AAEA,MAAayB,qBAIXlC,YAEAnB,SAASiB,YAA+B,CAAC,CAACE,OAAO;AAEnD,IAAaL,kBAAb,MAGE;CACA;CACA,WAAmB;CAEnB0C,YAAYrC,SAAqD;EAC/D,MAAMsC,mBAAmBxC,aAAgCE,OAAO;EAChE,KAAKmC,OAAOxD,cAAc2D,UAAU;CACtC;CAEA,IAAIzB,QAAgB;EAClB,OAAO,KAAKsB,KAAKI,SAAS,CAAC,CAACzB,SAAS;CACvC;CAEA,IAAIK,aAAyC;EAC3C,OAAO,KAAKgB,KAAKI,SAAS,CAAC,CAACpB;CAC9B;CAEA,IAAII,WAA+C;EACjD,OAAO,KAAKY,KAAKI,SAAS,CAAC,CAAChB;CAC9B;CAEA,IAAIR,SAAkB;EACpB,OAAO,KAAKoB,KAAKI,SAAS,CAAC,CAACxB;CAC9B;CAEAU,WAAWhB,OAAsC;EAC/C,KAAK0B,KAAKI,SAAS,CAAC,CAACd,WAAWhB,KAAK;CACvC;CAEA,IAAIQ,SAAmD;EACrD,OAAO,KAAKkB,KAAKI,SAAS,CAAC,CAACtB;CAC9B;CAEAW,YAAYC,OAAwD;EAClE,OAAO,KAAKM,KAAKI,SAAS,CAAC,CAACX,YAAYC,KAAK;CAC/C;CAEAG,cAAcH,OAA4C;EACxD,KAAKM,KAAKI,SAAS,CAAC,CAACP,cAAcH,KAAK;CAC1C;CAEAlB,UAAUC,UAAkC;EAC1C,OAAO,KAAKuB,KAAKxB,UAAUC,QAAQ;CACrC;CAEA8B,UAAgB;EACd,IAAI,KAAKN,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKQ,QAAQ;CACpB;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAWa,qBACX;EACE;EACA,YAAY,YAAY,gBAAgB;EACxC;IACE,yBAEJ,SAAS,UACT,UAAU,SAAS,UAAU,KAAK,yBAAyB,eAC1D"}
1
+ {"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAca,qBACX;EACE;EACA,YAAY,YAAY,gBAAgB;EACxC;IACE,yBAEJ,SAAS,UACT,UAAU,SAAS,UAAU,KAAK,yBAAyB,eAC1D"}
@@ -1,4 +1,4 @@
1
- import { setContext, setTransport, useStatewireSession } from "../client/session.js";
1
+ import { discardFailed, failedCommands, retryFailed, setContext, setTransport, useStatewireSession } from "../client/session.js";
2
2
  import { useEffect } from "@assistant-ui/tap/react-shim";
3
3
  //#region src/transports/remote.ts
4
4
  const useStatewireRemote = (options, useLink) => {
@@ -20,7 +20,12 @@ const useStatewireRemote = (options, useLink) => {
20
20
  get isBusy() {
21
21
  return !s.idle;
22
22
  },
23
- setContext: (value) => setContext(s, value)
23
+ setContext: (value) => setContext(s, value),
24
+ failures: {
25
+ list: () => failedCommands(s),
26
+ retry: (entry) => retryFailed(s, entry),
27
+ discard: (entry_0) => discardFailed(s, entry_0)
28
+ }
24
29
  };
25
30
  };
26
31
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","names":["useEffect","Statewire","setContext","setTransport","useStatewireSession","StatewireSessionOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","onDone","options","TOptions","useLink","ctx","s","sessionStore","onSessionLost","conn","state","canonical","commands","source","durableSeq","isBusy","idle","value"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport type { Statewire } from \"../types\";\nimport {\n setContext,\n setTransport,\n useStatewireSession,\n} from \"../client/session\";\nimport type { StatewireSessionOptions } from \"../client/session\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { RemoteLink, StatewireRemoteProps } from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireClient.Connection) => void;\n onDone?: () => void;\n } & StatewireSessionOptions,\n>(\n options: TOptions,\n useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,\n): Statewire<any, any> => {\n const s = useStatewireSession({\n sessionStore: options.sessionStore,\n onSessionLost: options.onSessionLost,\n onDone: options.onDone,\n });\n const conn = useLink(options, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n\n return {\n state: s.canonical,\n commands: s.commands,\n connection: conn.connection,\n source: s.source,\n get durableSeq() {\n return s.durableSeq;\n },\n get isBusy() {\n return !s.idle;\n },\n setContext: (value) => setContext(s, value),\n };\n};\n"],"mappings":";;;AAWA,MAAaS,sBAOXM,SACAE,YACwB;CACxB,MAAME,IAAIf,oBAAoB;EAC5BgB,cAAcL,QAAQK;EACtBC,eAAeN,QAAQM;EACvBP,QAAQC,QAAQD;CAClB,CAAC;CACD,MAAMQ,OAAOL,QAAQF,SAASI,EAAED,GAAG;CACnClB,gBAAgBG,aAAagB,GAAGG,IAAI,GAAG,CAACH,GAAGG,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOJ,EAAEK;EACTC,UAAUN,EAAEM;EACZb,YAAYU,KAAKV;EACjBc,QAAQP,EAAEO;EACV,IAAIC,aAAa;GACf,OAAOR,EAAEQ;EACX;EACA,IAAIC,SAAS;GACX,OAAO,CAACT,EAAEU;EACZ;EACA3B,aAAa4B,UAAU5B,WAAWiB,GAAGW,KAAK;CAC5C;AACF"}
1
+ {"version":3,"file":"remote.js","names":["useEffect","Statewire","discardFailed","failedCommands","retryFailed","setContext","setTransport","useStatewireSession","StatewireSessionOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","onDone","options","TOptions","useLink","ctx","s","sessionStore","onSessionLost","conn","state","canonical","commands","source","durableSeq","isBusy","idle","value","failures","list","retry","entry","discard"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport type { Statewire } from \"../types\";\nimport {\n discardFailed,\n failedCommands,\n retryFailed,\n setContext,\n setTransport,\n useStatewireSession,\n} from \"../client/session\";\nimport type { StatewireSessionOptions } from \"../client/session\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { RemoteLink, StatewireRemoteProps } from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireClient.Connection) => void;\n onDone?: () => void;\n } & StatewireSessionOptions,\n>(\n options: TOptions,\n useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,\n): Statewire<any, any> => {\n const s = useStatewireSession({\n sessionStore: options.sessionStore,\n onSessionLost: options.onSessionLost,\n onDone: options.onDone,\n });\n const conn = useLink(options, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n\n return {\n state: s.canonical,\n commands: s.commands,\n connection: conn.connection,\n source: s.source,\n get durableSeq() {\n return s.durableSeq;\n },\n get isBusy() {\n return !s.idle;\n },\n setContext: (value) => setContext(s, value),\n failures: {\n list: () => failedCommands(s),\n retry: (entry) => retryFailed(s, entry),\n discard: (entry) => discardFailed(s, entry),\n },\n };\n};\n"],"mappings":";;;AAcA,MAAaY,sBAOXM,SACAE,YACwB;CACxB,MAAME,IAAIf,oBAAoB;EAC5BgB,cAAcL,QAAQK;EACtBC,eAAeN,QAAQM;EACvBP,QAAQC,QAAQD;CAClB,CAAC;CACD,MAAMQ,OAAOL,QAAQF,SAASI,EAAED,GAAG;CACnCrB,gBAAgBM,aAAagB,GAAGG,IAAI,GAAG,CAACH,GAAGG,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOJ,EAAEK;EACTC,UAAUN,EAAEM;EACZb,YAAYU,KAAKV;EACjBc,QAAQP,EAAEO;EACV,IAAIC,aAAa;GACf,OAAOR,EAAEQ;EACX;EACA,IAAIC,SAAS;GACX,OAAO,CAACT,EAAEU;EACZ;EACA3B,aAAa4B,UAAU5B,WAAWiB,GAAGW,KAAK;EAC1CC,UAAU;GACRC,YAAYhC,eAAemB,CAAC;GAC5Bc,QAAQC,UAAUjC,YAAYkB,GAAGe,KAAK;GACtCC,UAAUD,YAAUnC,cAAcoB,GAAGe,OAAK;EAC5C;CACF;AACF"}
package/dist/types.d.ts CHANGED
@@ -52,6 +52,12 @@ interface Statewire<TState = any, TCommands extends Statewire.Commands = Statewi
52
52
  isBusy?: boolean;
53
53
  /** Replace this client's ambient context on the server; it rides the ctx lane (no verdict). */
54
54
  setContext?: (value: Record<string, unknown>) => void;
55
+ /** The retained failed-send lane; present on transports with a durable session. */
56
+ failures?: {
57
+ list: () => readonly StatewireClient.FailedCommand[];
58
+ retry: (entry: StatewireClient.FailedCommand) => Promise<unknown>;
59
+ discard: (entry: StatewireClient.FailedCommand) => void;
60
+ };
55
61
  }
56
62
  declare class StatewireSendError extends Error {
57
63
  readonly failure: StatewireClient.SendFailure;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;kBAGiB;OACH,WAAW,mBAAmB;OAE9B,QAAQ,kBAAkB,WAAW,eAC9C,WAAW;IAAc,QAAQ;IAAG,QAAQ,WAAW,UAAU;aAC5D;OAEI,cAAc,kBAAkB,eACzC,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,QAAQ,WAAW,UAAU;OAGhC,cAAc,kBAAkB,WAAW,eACpD,WAAW;IACV,QAAQ;IACR,QAAQ,WAAW,UAAU;IAC7B;IACA;IACA;IACA,UAAU,gBAAgB;aAEtB;;OAGI;IACV;;IAEA,mBAAmB;;IAEnB,cAAc;;OAGJ;IACN;IAAe,MAAM;IAAe;;IACpC;IAAW,MAAM;IAAe;;IAChC;IAAc,MAAM;;OAEd;IACV,YAAY;IACZ,eAAe;;;UAIF,UACf,cACA,kBAAkB,UAAU,WAAW,UAAU;EAEjD,OAAO;EACP,UAAU,UAAU,cAAc;EAClC,aAAa,gBAAgB;EAC7B,SAAS,UAAU;;EAEnB;;EAEA;;EAEA,cAAc,OAAO;;cAsBV,2BAA2B;WAC7B,SAAS,gBAAgB;EAEtB,YAAA,SAAS,gBAAgB"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;kBAGiB;OACH,WAAW,mBAAmB;OAE9B,QAAQ,kBAAkB,WAAW,eAC9C,WAAW;IAAc,QAAQ;IAAG,QAAQ,WAAW,UAAU;aAC5D;OAEI,cAAc,kBAAkB,eACzC,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,QAAQ,WAAW,UAAU;OAGhC,cAAc,kBAAkB,WAAW,eACpD,WAAW;IACV,QAAQ;IACR,QAAQ,WAAW,UAAU;IAC7B;IACA;IACA;IACA,UAAU,gBAAgB;aAEtB;;OAGI;IACV;;IAEA,mBAAmB;;IAEnB,cAAc;;OAGJ;IACN;IAAe,MAAM;IAAe;;IACpC;IAAW,MAAM;IAAe;;IAChC;IAAc,MAAM;;OAEd;IACV,YAAY;IACZ,eAAe;;;UAIF,UACf,cACA,kBAAkB,UAAU,WAAW,UAAU;EAEjD,OAAO;EACP,UAAU,UAAU,cAAc;EAClC,aAAa,gBAAgB;EAC7B,SAAS,UAAU;;EAEnB;;EAEA;;EAEA,cAAc,OAAO;;EAErB;IACE,qBAAqB,gBAAgB;IACrC,QAAQ,OAAO,gBAAgB,kBAAkB;IACjD,UAAU,OAAO,gBAAgB;;;cAuBxB,2BAA2B;WAC7B,SAAS,gBAAgB;EAEtB,YAAA,SAAS,gBAAgB"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["StatewireClient","StatewirePath","Statewire","Commands","Record","args","Command","TCommands","method","K","params","Parameters","CommandsProxy","Promise","Awaited","ReturnType","CommandUpdate","seq","status","result","failure","SendFailure","StateSource","snapshot","takeChanged","setOnChange","listener","Op","op","path","value","Changes","isChangedAt","getChangedKeys","state","TState","commands","connection","Connection","source","durableSeq","isBusy","setContext","describe","fate","code","message","cause","StatewireSendError","Error","constructor","name"],"sources":["../src/types.ts"],"sourcesContent":["import type { StatewireClient } from \"./statewire\";\nimport type { StatewirePath } from \"./wire/ops\";\n\nexport namespace Statewire {\n export type Commands = Record<string, (...args: never[]) => unknown>;\n\n export type Command<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: { method: K; params: Parameters<TCommands[K]> };\n }[keyof TCommands];\n\n export type CommandsProxy<TCommands extends Commands> = {\n [K in keyof TCommands]: (\n ...args: Parameters<TCommands[K]>\n ) => Promise<Awaited<ReturnType<TCommands[K]>>>;\n };\n\n export type CommandUpdate<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: {\n method: K;\n params: Parameters<TCommands[K]>;\n seq: number;\n status: \"queued\" | \"posted\" | \"applied\" | \"settled\";\n result?: unknown;\n failure?: StatewireClient.SendFailure;\n };\n }[keyof TCommands];\n\n /** Push-based canonical state feed a statewire hands to the client core. */\n export type StateSource = {\n snapshot: () => unknown;\n /** Drain the paths changed since the last take (`[]` entry = whole state). */\n takeChanged: () => StatewirePath[];\n /** Synchronous, per change; the consumer owns coalescing. */\n setOnChange: (listener: (() => void) | null) => void;\n };\n\n export type Op =\n | { op: \"replace\"; path: StatewirePath; value: unknown }\n | { op: \"add\"; path: StatewirePath; value: unknown }\n | { op: \"remove\"; path: StatewirePath };\n\n export type Changes = {\n isChangedAt(path: readonly (string | number)[]): boolean;\n getChangedKeys(path: readonly (string | number)[]): string[];\n };\n}\n\nexport interface Statewire<\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n state: TState;\n commands: Statewire.CommandsProxy<TCommands>;\n connection?: StatewireClient.Connection;\n source?: Statewire.StateSource;\n /** Highest command seq the server's dur flag states durably admitted — never lost, never run again — and released from the resend queue. */\n durableSeq?: number;\n /** Negation of the server's broadcast idle level; absent means always busy. */\n isBusy?: boolean;\n /** Replace this client's ambient context on the server; it rides the ctx lane (no verdict). */\n setContext?: (value: Record<string, unknown>) => void;\n}\n\nconst describe = (failure: StatewireClient.SendFailure): string => {\n switch (failure.fate) {\n case \"rejected\":\n return `statewire: command rejected${failure.code ? ` (${failure.code})` : \"\"}${failure.message ? `: ${failure.message}` : \"\"}`;\n case \"aborted\":\n return `statewire: command aborted (${failure.cause})`;\n case \"lost\":\n return `statewire: command lost (${failure.cause})`;\n case \"failed\":\n return failure.message\n ? `statewire: command failed: ${failure.message}`\n : \"statewire: command failed\";\n case \"crashed\":\n return \"statewire: command crashed\";\n case \"expired\":\n return `statewire: command result expired (${failure.cause})`;\n }\n};\n\nexport class StatewireSendError extends Error {\n readonly failure: StatewireClient.SendFailure;\n\n constructor(failure: StatewireClient.SendFailure) {\n super(describe(failure));\n this.name = \"StatewireSendError\";\n this.failure = failure;\n }\n}\n"],"mappings":";AA+DA,MAAM2C,YAAYvB,YAAiD;CACjE,QAAQA,QAAQwB,MAAhB;EACE,KAAK,YACH,OAAO,8BAA8BxB,QAAQyB,OAAO,KAAKzB,QAAQyB,KAAI,KAAM,KAAKzB,QAAQ0B,UAAU,KAAK1B,QAAQ0B,YAAY;EAC7H,KAAK,WACH,OAAO,+BAA+B1B,QAAQ2B,MAAK;EACrD,KAAK,QACH,OAAO,4BAA4B3B,QAAQ2B,MAAK;EAClD,KAAK,UACH,OAAO3B,QAAQ0B,UACX,8BAA8B1B,QAAQ0B,YACtC;EACN,KAAK,WACH,OAAO;EACT,KAAK,WACH,OAAO,sCAAsC1B,QAAQ2B,MAAK;CAC9D;AACF;AAEA,IAAaC,qBAAb,cAAwCC,MAAM;CAC5C;CAEAC,YAAY9B,SAAsC;EAChD,MAAMuB,SAASvB,OAAO,CAAC;EACvB,KAAK+B,OAAO;EACZ,KAAK/B,UAAUA;CACjB;AACF"}
1
+ {"version":3,"file":"types.js","names":["StatewireClient","StatewirePath","Statewire","Commands","Record","args","Command","TCommands","method","K","params","Parameters","CommandsProxy","Promise","Awaited","ReturnType","CommandUpdate","seq","status","result","failure","SendFailure","StateSource","snapshot","takeChanged","setOnChange","listener","Op","op","path","value","Changes","isChangedAt","getChangedKeys","state","TState","commands","connection","Connection","source","durableSeq","isBusy","setContext","failures","list","FailedCommand","retry","entry","discard","describe","fate","code","message","cause","StatewireSendError","Error","constructor","name"],"sources":["../src/types.ts"],"sourcesContent":["import type { StatewireClient } from \"./statewire\";\nimport type { StatewirePath } from \"./wire/ops\";\n\nexport namespace Statewire {\n export type Commands = Record<string, (...args: never[]) => unknown>;\n\n export type Command<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: { method: K; params: Parameters<TCommands[K]> };\n }[keyof TCommands];\n\n export type CommandsProxy<TCommands extends Commands> = {\n [K in keyof TCommands]: (\n ...args: Parameters<TCommands[K]>\n ) => Promise<Awaited<ReturnType<TCommands[K]>>>;\n };\n\n export type CommandUpdate<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: {\n method: K;\n params: Parameters<TCommands[K]>;\n seq: number;\n status: \"queued\" | \"posted\" | \"applied\" | \"settled\";\n result?: unknown;\n failure?: StatewireClient.SendFailure;\n };\n }[keyof TCommands];\n\n /** Push-based canonical state feed a statewire hands to the client core. */\n export type StateSource = {\n snapshot: () => unknown;\n /** Drain the paths changed since the last take (`[]` entry = whole state). */\n takeChanged: () => StatewirePath[];\n /** Synchronous, per change; the consumer owns coalescing. */\n setOnChange: (listener: (() => void) | null) => void;\n };\n\n export type Op =\n | { op: \"replace\"; path: StatewirePath; value: unknown }\n | { op: \"add\"; path: StatewirePath; value: unknown }\n | { op: \"remove\"; path: StatewirePath };\n\n export type Changes = {\n isChangedAt(path: readonly (string | number)[]): boolean;\n getChangedKeys(path: readonly (string | number)[]): string[];\n };\n}\n\nexport interface Statewire<\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n state: TState;\n commands: Statewire.CommandsProxy<TCommands>;\n connection?: StatewireClient.Connection;\n source?: Statewire.StateSource;\n /** Highest command seq the server's dur flag states durably admitted — never lost, never run again — and released from the resend queue. */\n durableSeq?: number;\n /** Negation of the server's broadcast idle level; absent means always busy. */\n isBusy?: boolean;\n /** Replace this client's ambient context on the server; it rides the ctx lane (no verdict). */\n setContext?: (value: Record<string, unknown>) => void;\n /** The retained failed-send lane; present on transports with a durable session. */\n failures?: {\n list: () => readonly StatewireClient.FailedCommand[];\n retry: (entry: StatewireClient.FailedCommand) => Promise<unknown>;\n discard: (entry: StatewireClient.FailedCommand) => void;\n };\n}\n\nconst describe = (failure: StatewireClient.SendFailure): string => {\n switch (failure.fate) {\n case \"rejected\":\n return `statewire: command rejected${failure.code ? ` (${failure.code})` : \"\"}${failure.message ? `: ${failure.message}` : \"\"}`;\n case \"aborted\":\n return `statewire: command aborted (${failure.cause})`;\n case \"lost\":\n return `statewire: command lost (${failure.cause})`;\n case \"failed\":\n return failure.message\n ? `statewire: command failed: ${failure.message}`\n : \"statewire: command failed\";\n case \"crashed\":\n return \"statewire: command crashed\";\n case \"expired\":\n return `statewire: command result expired (${failure.cause})`;\n }\n};\n\nexport class StatewireSendError extends Error {\n readonly failure: StatewireClient.SendFailure;\n\n constructor(failure: StatewireClient.SendFailure) {\n super(describe(failure));\n this.name = \"StatewireSendError\";\n this.failure = failure;\n }\n}\n"],"mappings":";AAqEA,MAAMiD,YAAY7B,YAAiD;CACjE,QAAQA,QAAQ8B,MAAhB;EACE,KAAK,YACH,OAAO,8BAA8B9B,QAAQ+B,OAAO,KAAK/B,QAAQ+B,KAAI,KAAM,KAAK/B,QAAQgC,UAAU,KAAKhC,QAAQgC,YAAY;EAC7H,KAAK,WACH,OAAO,+BAA+BhC,QAAQiC,MAAK;EACrD,KAAK,QACH,OAAO,4BAA4BjC,QAAQiC,MAAK;EAClD,KAAK,UACH,OAAOjC,QAAQgC,UACX,8BAA8BhC,QAAQgC,YACtC;EACN,KAAK,WACH,OAAO;EACT,KAAK,WACH,OAAO,sCAAsChC,QAAQiC,MAAK;CAC9D;AACF;AAEA,IAAaC,qBAAb,cAAwCC,MAAM;CAC5C;CAEAC,YAAYpC,SAAsC;EAChD,MAAM6B,SAAS7B,OAAO,CAAC;EACvB,KAAKqC,OAAO;EACZ,KAAKrC,UAAUA;CACjB;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"ops.d.ts","names":[],"sources":["../../src/wire/ops.ts"],"mappings":";;KAIY;cAwDC,gBAAiB,gBAAgB;cAsGjC,UACX,eACA,IAAI,UAAU;EACX;EAAgB,cAAc;;cAUtB,WACX,gBACA;EAEA;EACA,cAAc"}
1
+ {"version":3,"file":"ops.d.ts","names":[],"sources":["../../src/wire/ops.ts"],"mappings":";;KAIY;cAwDC,gBAAiB,gBAAgB;cAiIjC,UACX,eACA,IAAI,UAAU;EACX;EAAgB,cAAc;;cAUtB,WACX,gBACA;EAEA;EACA,cAAc"}
package/dist/wire/ops.js CHANGED
@@ -31,9 +31,33 @@ const sanitizeAt = (value, parent, path) => {
31
31
  };
32
32
  const sanitizeValue = (value, parent) => sanitizeAt(value, parent, parent === void 0 ? [] : [parent]);
33
33
  const invalid = (op) => /* @__PURE__ */ new Error(`Statewire cannot apply ${op.op} at path: ${op.path.join(".")}`);
34
+ const utf16Index = (text, offset) => {
35
+ let index = 0;
36
+ for (let remaining = offset; remaining > 0; remaining -= 1) {
37
+ if (index >= text.length) return -1;
38
+ index += text.codePointAt(index) > 65535 ? 2 : 1;
39
+ }
40
+ return index;
41
+ };
42
+ const countCodePoints = (text) => {
43
+ let count = 0;
44
+ for (let index = 0; index < text.length; index += 1) {
45
+ if (text.codePointAt(index) > 65535) index += 1;
46
+ count += 1;
47
+ }
48
+ return count;
49
+ };
50
+ let last = null;
34
51
  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);
52
+ if (typeof op.value !== "string") throw invalid(op);
53
+ const index = last !== null && last.str === current && last.count === offset ? current.length : utf16Index(current, offset);
54
+ if (index < 0) throw invalid(op);
55
+ const next = current.slice(0, index) + op.value + current.slice(index);
56
+ if (index === current.length) last = {
57
+ str: next,
58
+ count: offset + countCodePoints(op.value)
59
+ };
60
+ return next;
37
61
  };
38
62
  const applyAdd = (box, op) => {
39
63
  const offset = op.path[op.path.length - 1];
@@ -1 +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","invalid","op","Op","insertedText","current","Extract","offset","slice","applyAdd","box","state","walked","Number","isInteger","target","leaf","splice","next","applyInList","list","applyInObject","obj","hasOwn","applyOpToDraft","isMountPlaneOp","doc","warnedOps","Set","warnUnknownOp","type","has","add","JSON","stringify","unboxPaths","paths","applyOp","root","changedPaths","recorded","draft","applyOps","ops","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\nconst invalid = (op: Statewire.Op) =>\n new Error(`Statewire cannot apply ${op.op} at path: ${op.path.join(\".\")}`);\n\nconst insertedText = (\n current: string,\n op: Extract<Statewire.Op, { 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<Statewire.Op, { 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: Statewire.Op) => {\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: Statewire.Op,\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: Statewire.Op): 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 isMountPlaneOp = (op: { op?: unknown; doc?: unknown }) =>\n \"doc\" in op || op.op === \"mount\" || op.op === \"unmount\";\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: Statewire.Op,\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 changedPaths: StatewirePath[];\n} => {\n const mutable: Statewire.Op[] = [];\n for (const op of ops as Statewire.Op[]) {\n if (isMountPlaneOp(op)) continue;\n 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 changedPaths: unboxPaths(recorded.changedPaths),\n };\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","invalid","op","Op","utf16Index","text","offset","remaining","codePointAt","countCodePoints","count","last","str","insertedText","current","Extract","memoized","next","slice","applyAdd","box","state","walked","Number","isInteger","target","leaf","splice","applyInList","list","applyInObject","obj","hasOwn","applyOpToDraft","isMountPlaneOp","doc","warnedOps","Set","warnUnknownOp","type","has","add","JSON","stringify","unboxPaths","paths","applyOp","root","changedPaths","recorded","draft","applyOps","ops","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\nconst invalid = (op: Statewire.Op) =>\n new Error(`Statewire cannot apply ${op.op} at path: ${op.path.join(\".\")}`);\n\n// Text-insert offsets are Unicode code points on the wire, not UTF-16 units.\nconst utf16Index = (text: string, offset: number): number => {\n let index = 0;\n for (let remaining = offset; remaining > 0; remaining -= 1) {\n if (index >= text.length) return -1;\n index += text.codePointAt(index)! > 0xffff ? 2 : 1;\n }\n return index;\n};\n\nconst countCodePoints = (text: string): number => {\n let count = 0;\n for (let index = 0; index < text.length; index += 1) {\n if (text.codePointAt(index)! > 0xffff) index += 1;\n count += 1;\n }\n return count;\n};\n\nlet last: { str: string; count: number } | null = null;\n\nconst insertedText = (\n current: string,\n op: Extract<Statewire.Op, { op: \"add\" }>,\n offset: number,\n): string => {\n if (typeof op.value !== \"string\") throw invalid(op);\n const memoized =\n last !== null && last.str === current && last.count === offset;\n const index = memoized ? current.length : utf16Index(current, offset);\n if (index < 0) throw invalid(op);\n const next = current.slice(0, index) + op.value + current.slice(index);\n if (index === current.length) {\n last = { str: next, count: offset + countCodePoints(op.value) };\n }\n return next;\n};\n\nconst applyAdd = (\n box: { state: unknown },\n op: Extract<Statewire.Op, { 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: Statewire.Op) => {\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: Statewire.Op,\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: Statewire.Op): 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 isMountPlaneOp = (op: { op?: unknown; doc?: unknown }) =>\n \"doc\" in op || op.op === \"mount\" || op.op === \"unmount\";\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: Statewire.Op,\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 changedPaths: StatewirePath[];\n} => {\n const mutable: Statewire.Op[] = [];\n for (const op of ops as Statewire.Op[]) {\n if (isMountPlaneOp(op)) continue;\n 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 changedPaths: unboxPaths(recorded.changedPaths),\n };\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "statewire",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "State over the wire: snapshot-first delta streaming and command submission for any state and any command",
5
5
  "license": "MIT",
6
6
  "repository": {