statewire 0.18.1 → 0.19.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.
Files changed (86) hide show
  1. package/dist/client/connection.d.ts +8 -5
  2. package/dist/client/connection.d.ts.map +1 -1
  3. package/dist/client/connection.js +24 -13
  4. package/dist/client/connection.js.map +1 -1
  5. package/dist/client/session.d.ts +13 -15
  6. package/dist/client/session.d.ts.map +1 -1
  7. package/dist/client/session.js +296 -401
  8. package/dist/client/session.js.map +1 -1
  9. package/dist/client/timing.d.ts +2 -1
  10. package/dist/client/timing.d.ts.map +1 -1
  11. package/dist/client/timing.js +2 -1
  12. package/dist/client/timing.js.map +1 -1
  13. package/dist/host/engine.d.ts +2 -0
  14. package/dist/host/engine.d.ts.map +1 -1
  15. package/dist/host/engine.js +2 -0
  16. package/dist/host/engine.js.map +1 -1
  17. package/dist/host/host.d.ts +3 -1
  18. package/dist/host/host.d.ts.map +1 -1
  19. package/dist/host/host.js +38 -7
  20. package/dist/host/host.js.map +1 -1
  21. package/dist/host/http.d.ts.map +1 -1
  22. package/dist/host/http.js +9 -19
  23. package/dist/host/http.js.map +1 -1
  24. package/dist/host/socket.d.ts +48 -25
  25. package/dist/host/socket.d.ts.map +1 -1
  26. package/dist/host/socket.js +399 -266
  27. package/dist/host/socket.js.map +1 -1
  28. package/dist/host/synced.d.ts +9 -0
  29. package/dist/host/synced.d.ts.map +1 -0
  30. package/dist/host/synced.js +45 -0
  31. package/dist/host/synced.js.map +1 -0
  32. package/dist/host-internal.d.ts +20 -12
  33. package/dist/host-internal.d.ts.map +1 -1
  34. package/dist/host.d.ts +33 -9
  35. package/dist/host.d.ts.map +1 -1
  36. package/dist/host.js +2 -1
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.js +2 -1
  39. package/dist/statewire.d.ts +8 -20
  40. package/dist/statewire.d.ts.map +1 -1
  41. package/dist/statewire.js +0 -14
  42. package/dist/statewire.js.map +1 -1
  43. package/dist/storage.d.ts +2 -5
  44. package/dist/storage.d.ts.map +1 -1
  45. package/dist/storage.js +54 -89
  46. package/dist/storage.js.map +1 -1
  47. package/dist/transports/remote.d.ts.map +1 -1
  48. package/dist/transports/remote.js +1 -6
  49. package/dist/transports/remote.js.map +1 -1
  50. package/dist/transports/sse.d.ts +2 -5
  51. package/dist/transports/sse.d.ts.map +1 -1
  52. package/dist/transports/sse.js.map +1 -1
  53. package/dist/transports/testing.d.ts +7 -5
  54. package/dist/transports/testing.d.ts.map +1 -1
  55. package/dist/transports/testing.js +19 -18
  56. package/dist/transports/testing.js.map +1 -1
  57. package/dist/types.d.ts +3 -5
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/types.js.map +1 -1
  60. package/dist/wire/limits.d.ts +2 -2
  61. package/dist/wire/limits.js +2 -2
  62. package/dist/wire/limits.js.map +1 -1
  63. package/dist/wire/protocol.d.ts +6 -1
  64. package/dist/wire/protocol.d.ts.map +1 -1
  65. package/dist/wire/protocol.js +33 -12
  66. package/dist/wire/protocol.js.map +1 -1
  67. package/package.json +1 -1
  68. package/src/client/connection.ts +46 -33
  69. package/src/client/session.ts +442 -517
  70. package/src/client/timing.ts +2 -0
  71. package/src/host/engine.ts +3 -1
  72. package/src/host/host.ts +50 -3
  73. package/src/host/http.ts +23 -31
  74. package/src/host/socket.ts +560 -339
  75. package/src/host/synced.ts +60 -0
  76. package/src/host-internal.ts +21 -7
  77. package/src/host.ts +40 -8
  78. package/src/index.ts +1 -0
  79. package/src/statewire.ts +8 -37
  80. package/src/storage.ts +90 -157
  81. package/src/transports/remote.ts +0 -7
  82. package/src/transports/sse.ts +8 -3
  83. package/src/transports/testing.ts +34 -18
  84. package/src/types.ts +3 -5
  85. package/src/wire/limits.ts +1 -1
  86. package/src/wire/protocol.ts +40 -11
package/dist/host/host.js CHANGED
@@ -7,6 +7,7 @@ import { useEffect, useState } from "@assistant-ui/tap/react-shim";
7
7
  //#region src/host/host.ts
8
8
  const isDev = () => typeof process === "undefined" || process.env["NODE_ENV"] !== "production";
9
9
  const stateHosts = /* @__PURE__ */ new WeakMap();
10
+ const documentIdentity = (protocol, key) => JSON.stringify([protocol, key ?? null]);
10
11
  /** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */
11
12
  const getStateHost = (state) => stateHosts.get(state);
12
13
  const useStatewireState = (init) => {
@@ -20,8 +21,10 @@ const useStatewireState = (init) => {
20
21
  current: sanitizeValue(init()),
21
22
  draft: null,
22
23
  changed: [[]],
24
+ documents: /* @__PURE__ */ new Map(),
23
25
  onOp: null,
24
26
  onChange: null,
27
+ onDocuments: null,
25
28
  bump: () => setVersion((v) => v + 1),
26
29
  ctl: {
27
30
  apply: (ops) => {
@@ -47,13 +50,35 @@ const useStatewireState = (init) => {
47
50
  const changed = c.changed;
48
51
  c.changed = [];
49
52
  return changed;
53
+ },
54
+ mount: (protocol, value, options) => {
55
+ const key = options?.key;
56
+ const main = options?.main ?? key === void 0;
57
+ if (main) {
58
+ for (const [id, doc] of c.documents) if (doc.protocol === protocol && doc.main && id !== documentIdentity(protocol, key)) throw new Error(`statewire: ${protocol} already has a main document`);
59
+ }
60
+ c.documents.set(documentIdentity(protocol, key), {
61
+ protocol,
62
+ key: key === void 0 ? null : [...key],
63
+ main,
64
+ value: sanitizeValue(value)
65
+ });
66
+ c.onDocuments?.();
67
+ },
68
+ unmount: (protocol_0, key_0) => {
69
+ if (!c.documents.delete(documentIdentity(protocol_0, key_0))) throw new Error("statewire: document is not mounted");
70
+ c.onDocuments?.();
71
+ },
72
+ documents: () => c.documents,
73
+ setOnDocuments: (listener_1) => {
74
+ c.onDocuments = listener_1;
50
75
  }
51
76
  }
52
77
  };
53
78
  return c;
54
79
  });
55
- let value = cell.ctl.snapshot();
56
- if (typeof value === "object" && value !== null) {
80
+ let value_0 = cell.ctl.snapshot();
81
+ if (typeof value_0 === "object" && value_0 !== null) {
57
82
  cell.draft ??= createStateDraft({
58
83
  snapshot: () => cell.current,
59
84
  record: (op, changedPath) => {
@@ -64,9 +89,9 @@ const useStatewireState = (init) => {
64
89
  }
65
90
  });
66
91
  stateHosts.set(cell.draft.proxy, cell.host);
67
- value = cell.draft.proxy;
92
+ value_0 = cell.draft.proxy;
68
93
  }
69
- return [value, cell.ctl];
94
+ return [value_0, cell.ctl];
70
95
  };
71
96
  const commandsHosts = /* @__PURE__ */ new WeakMap();
72
97
  const getCommandsHost = (commands) => commandsHosts.get(commands);
@@ -79,13 +104,13 @@ const LOCAL_CALLER = Object.freeze({
79
104
  throw new Error("statewire: in-process dispatch has no client id");
80
105
  },
81
106
  auth: void 0,
82
- context: null,
83
107
  isConnected: true
84
108
  });
85
109
  const toSendError = (error) => error instanceof StatewireSendError ? error : error instanceof StatewireReject ? new StatewireSendError({
86
110
  fate: "rejected",
87
111
  cause: "server",
88
112
  message: error.message,
113
+ ...error.code !== void 0 && { code: error.code },
89
114
  ...error.payload !== void 0 && { payload: error.payload }
90
115
  }) : new StatewireSendError({
91
116
  fate: "failed",
@@ -103,8 +128,8 @@ const settleInvocation = (cell, inv, call, settled) => {
103
128
  if (inv.settled) return;
104
129
  inv.settled = true;
105
130
  cell.active.delete(inv);
106
- applyInvocation(cell, inv);
107
131
  if (settled.type === "resolved") {
132
+ applyInvocation(cell, inv);
108
133
  const outcome = settled.value === void 0 ? { type: "accepted" } : {
109
134
  type: "accepted",
110
135
  payload: sanitizeValue(settled.value)
@@ -113,6 +138,7 @@ const settleInvocation = (cell, inv, call, settled) => {
113
138
  inv.origin?.onSettle?.(outcome);
114
139
  call.resolve(outcome.payload);
115
140
  } else {
141
+ cell.listener?.onCovered?.(inv);
116
142
  const error = toSendError(settled.error);
117
143
  const outcome = {
118
144
  type: "rejected",
@@ -176,8 +202,13 @@ const dispatch = (cell, call) => {
176
202
  cell.active.set(inv, call);
177
203
  cell.listener?.onStart(inv);
178
204
  cell.listener?.onPosted?.(inv);
205
+ const attach = call.origin?.attach;
179
206
  const ctx = {
180
207
  caller: call.origin?.caller ?? LOCAL_CALLER,
208
+ get attach() {
209
+ if (attach === void 0) throw new Error("statewire: in-process dispatch has no attach");
210
+ return attach;
211
+ },
181
212
  signal: inv.controller.signal,
182
213
  applied: () => {
183
214
  if (!inv.settled) applyInvocation(cell, inv);
@@ -313,6 +344,6 @@ const useStatewireCommands = (commands) => {
313
344
  return built.proxy;
314
345
  };
315
346
  //#endregion
316
- export { bindCommandsHost, getCommandsHost, getStateHost, useStatewireCommands, useStatewireState };
347
+ export { bindCommandsHost, documentIdentity, getCommandsHost, getStateHost, useStatewireCommands, useStatewireState };
317
348
 
318
349
  //# sourceMappingURL=host.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","names":["useEffect","useState","applyOps","sanitizeValue","StatewirePath","StatewireReject","createStateDraft","createMethodProxy","StateDraft","StatewireSendError","Statewire","StatewireHost","isDev","process","env","stateHosts","WeakMap","StateHost","getStateHost","state","get","StateCell","current","draft","changed","onOp","op","Op","onChange","ctl","StateCtl","host","bump","useStatewireState","init","TState","setVersion","cell","commit","c","flush","v","apply","ops","result","invalidate","push","changedPaths","snapshot","setOnOp","listener","setOnChange","takeChanged","value","record","changedPath","set","proxy","const","Settled","type","error","CommandsInvocation","protocol","method","params","ts","seq","clientId","CommandsOutcome","payload","CommandsListener","onStart","inv","onPosted","onApplied","onCovered","onSettle","outcome","CommandsSubmission","caller","Caller","CommandsHost","setListener","submit","call","Promise","methods","abort","commandsHosts","getCommandsHost","commands","bindCommandsHost","QueuedCall","resolve","reject","origin","ActiveInvocation","applied","controller","AbortController","settled","CommandsCell","handlers","Record","args","queue","active","Map","running","unmounted","stallTimer","ReturnType","setTimeout","STALL_WARN_MS","LOCAL_CALLER","Object","freeze","Error","auth","undefined","context","isConnected","toSendError","fate","cause","message","String","applyInvocation","settleInvocation","delete","abortInvocations","clear","isThenable","PromiseLike","then","handlerOf","prototype","propertyIsEnumerable","handler","dispatch","code","ctx","Ctx","signal","this","resolved","pump","watchStall","shift","length","console","warn","useStatewireCommands","Commands","THandlers","ThisType","CommandsProxy","HandledCommands","built","Date","now","keys","filter","clearTimeout","queueMicrotask","queued","startsWith"],"sources":["../../src/host/host.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { applyOps, sanitizeValue } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireReject } from \"./engine\";\nimport { createStateDraft } from \"./draft\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StateDraft } from \"./draft\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\n\nconst isDev = () =>\n typeof process === \"undefined\" || process.env[\"NODE_ENV\"] !== \"production\";\n\n// ─── state ──────────────────────────────────────────────\n\nconst stateHosts = new WeakMap<object, StatewireHost.StateHost>();\n\n/** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */\nexport const getStateHost = (\n state: object,\n): StatewireHost.StateHost | undefined => stateHosts.get(state);\n\ntype StateCell = {\n current: unknown;\n draft: StateDraft<object> | null;\n changed: StatewirePath[];\n onOp: ((op: Statewire.Op) => void) | null;\n onChange: (() => void) | null;\n ctl: StatewireHost.StateCtl;\n host: StatewireHost.StateHost;\n bump: () => void;\n};\n\nexport const useStatewireState = <TState>(\n init: () => TState,\n): readonly [TState, StatewireHost.StateCtl<TState>] => {\n const [, setVersion] = useState(0);\n const [cell] = useState((): StateCell => {\n const commit = () => {\n if (c.draft) c.current = c.draft.flush();\n return c.current;\n };\n const c: StateCell = {\n current: sanitizeValue(init()),\n draft: null,\n changed: [[]],\n onOp: null,\n onChange: null,\n bump: () => setVersion((v) => v + 1),\n ctl: {\n apply: (ops) => {\n const result = applyOps(commit(), ops);\n c.current = result.state;\n c.draft?.invalidate();\n c.changed.push(...result.changedPaths);\n if (c.onOp) for (const op of ops) c.onOp(op);\n c.bump();\n c.onChange?.();\n },\n snapshot: commit,\n },\n host: {\n snapshot: commit,\n setOnOp: (listener) => {\n c.onOp = listener;\n },\n setOnChange: (listener) => {\n c.onChange = listener;\n },\n takeChanged: () => {\n const changed = c.changed;\n c.changed = [];\n return changed;\n },\n },\n };\n return c;\n });\n\n let value = cell.ctl.snapshot();\n if (typeof value === \"object\" && value !== null) {\n cell.draft ??= createStateDraft<object>({\n snapshot: () => cell.current,\n record: (op, changedPath) => {\n cell.changed.push(changedPath);\n cell.onOp?.(op);\n cell.bump();\n cell.onChange?.();\n },\n });\n stateHosts.set(cell.draft.proxy, cell.host);\n value = cell.draft.proxy;\n }\n return [value as TState, cell.ctl as StatewireHost.StateCtl<TState>] as const;\n};\n\n// ─── commands ───────────────────────────────────────────\n\ntype Settled =\n | { type: \"resolved\"; value: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsInvocation = {\n readonly protocol?: string;\n readonly method: string;\n readonly params: readonly unknown[];\n readonly ts: number;\n readonly seq: number;\n readonly clientId?: string;\n};\n\nexport type CommandsOutcome =\n | { type: \"accepted\"; payload?: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsListener = {\n onStart: (inv: CommandsInvocation) => void;\n onPosted?: (inv: CommandsInvocation) => void;\n onApplied: (inv: CommandsInvocation) => void;\n /** Canonical state now covers the invocation's effect — drop any overlay. */\n onCovered?: (inv: CommandsInvocation) => void;\n onSettle: (inv: CommandsInvocation, outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsSubmission = {\n method: string;\n params: readonly unknown[];\n clientId?: string;\n caller?: StatewireHost.Caller;\n onApplied?: () => void;\n onSettle?: (outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsHost = {\n setListener: (listener: CommandsListener | null) => void;\n submit?: (call: CommandsSubmission) => Promise<unknown>;\n /** Registered command names, read live. */\n methods?: () => string[];\n abort?: () => void;\n};\n\nconst commandsHosts = new WeakMap<object, CommandsHost>();\n\nexport const getCommandsHost = (commands: object): CommandsHost | undefined =>\n commandsHosts.get(commands);\n\nexport const bindCommandsHost = (commands: object, host: CommandsHost) => {\n commandsHosts.set(commands, host);\n};\n\ntype QueuedCall = {\n method: string;\n params: readonly unknown[];\n ts: number;\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n origin?: CommandsSubmission;\n};\n\ntype ActiveInvocation = CommandsInvocation & {\n applied: boolean;\n controller: AbortController;\n settled: boolean;\n origin?: CommandsSubmission;\n};\n\ntype CommandsCell = {\n handlers: Record<string, (...args: never[]) => unknown> | null;\n queue: QueuedCall[];\n active: Map<ActiveInvocation, QueuedCall>;\n running: boolean;\n listener: CommandsListener | null;\n seq: number;\n unmounted: boolean;\n stallTimer: ReturnType<typeof setTimeout> | null;\n};\n\nconst STALL_WARN_MS = 5000;\n\n// In-process dispatch has no wire client: never any ambient context, trivially attached.\nconst LOCAL_CALLER: StatewireHost.Caller = Object.freeze({\n get clientId(): string {\n throw new Error(\"statewire: in-process dispatch has no client id\");\n },\n auth: undefined,\n context: null,\n isConnected: true,\n});\n\nconst toSendError = (error: unknown): StatewireSendError =>\n error instanceof StatewireSendError\n ? error\n : error instanceof StatewireReject\n ? new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n message: error.message,\n ...(error.payload !== undefined && { payload: error.payload }),\n })\n : new StatewireSendError({\n fate: \"failed\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n });\n\nconst applyInvocation = (cell: CommandsCell, inv: ActiveInvocation) => {\n if (inv.applied) return;\n inv.applied = true;\n cell.listener?.onApplied(inv);\n cell.listener?.onCovered?.(inv);\n inv.origin?.onApplied?.();\n};\n\nconst settleInvocation = (\n cell: CommandsCell,\n inv: ActiveInvocation,\n call: QueuedCall,\n settled: Settled,\n) => {\n if (inv.settled) return;\n inv.settled = true;\n cell.active.delete(inv);\n applyInvocation(cell, inv);\n if (settled.type === \"resolved\") {\n const outcome: CommandsOutcome =\n settled.value === undefined\n ? { type: \"accepted\" }\n : { type: \"accepted\", payload: sanitizeValue(settled.value) };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.resolve(outcome.payload);\n } else {\n const error = toSendError(settled.error);\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n};\n\nconst abortInvocations = (cell: CommandsCell) => {\n for (const [inv, call] of cell.active) {\n inv.settled = true;\n inv.controller.abort();\n const error = new StatewireSendError({ fate: \"crashed\", cause: \"server\" });\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n cell.active.clear();\n};\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\";\n\n// A command is an own, enumerable, function-valued property — exactly what\n// `Object.keys` shows, so dispatch, `statewire/commands`, and the reserved-\n// namespace guard share one view; prototype-chain names are not commands.\nconst handlerOf = (\n handlers: Record<string, (...args: never[]) => unknown>,\n method: string,\n): ((...args: never[]) => unknown) | undefined => {\n if (!Object.prototype.propertyIsEnumerable.call(handlers, method)) {\n return undefined;\n }\n const handler = handlers[method];\n return typeof handler === \"function\" ? handler : undefined;\n};\n\nconst dispatch = (cell: CommandsCell, call: QueuedCall) => {\n const handler = handlerOf(cell.handlers!, call.method);\n if (handler === undefined) {\n const error = new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n code: \"unknown-command\",\n });\n call.origin?.onSettle?.({ type: \"rejected\", error });\n call.reject(error);\n return;\n }\n const inv: ActiveInvocation = {\n method: call.method,\n params: call.params,\n ts: call.ts,\n seq: ++cell.seq,\n applied: false,\n controller: new AbortController(),\n settled: false,\n ...(call.origin !== undefined && { origin: call.origin }),\n ...(call.origin?.clientId !== undefined && {\n clientId: call.origin.clientId,\n }),\n };\n cell.active.set(inv, call);\n cell.listener?.onStart(inv);\n cell.listener?.onPosted?.(inv);\n const ctx: StatewireHost.Ctx = {\n caller: call.origin?.caller ?? LOCAL_CALLER,\n signal: inv.controller.signal,\n applied: () => {\n if (!inv.settled) applyInvocation(cell, inv);\n },\n };\n try {\n const value = (\n handler as (\n this: StatewireHost.Ctx,\n ...args: readonly unknown[]\n ) => unknown\n ).call(ctx, ...call.params);\n if (isThenable(value)) {\n value.then(\n (resolved) =>\n settleInvocation(cell, inv, call, {\n type: \"resolved\",\n value: resolved,\n }),\n (error: unknown) =>\n settleInvocation(cell, inv, call, { type: \"rejected\", error }),\n );\n } else {\n settleInvocation(cell, inv, call, { type: \"resolved\", value });\n }\n } catch (error) {\n settleInvocation(cell, inv, call, { type: \"rejected\", error });\n }\n};\n\n// Prefix-only advancement: each handler runs synchronously to its first\n// await, then the pump moves on; settlement rides the returned promise.\nconst pump = (cell: CommandsCell) => {\n if (cell.running || !cell.handlers) {\n watchStall(cell);\n return;\n }\n cell.running = true;\n try {\n while (cell.handlers) {\n const call = cell.queue.shift();\n if (!call) break;\n dispatch(cell, call);\n }\n } finally {\n cell.running = false;\n }\n};\n\nconst watchStall = (cell: CommandsCell) => {\n if (!isDev() || cell.stallTimer !== null || cell.queue.length === 0) return;\n if (cell.handlers) return;\n cell.stallTimer = setTimeout(() => {\n cell.stallTimer = null;\n if (cell.handlers === null && cell.queue.length > 0 && !cell.unmounted) {\n console.warn(\n `statewire: ${cell.queue.length} buffered command(s) waiting over ${STALL_WARN_MS}ms — did the host forget to arm useStatewireCommands?`,\n );\n }\n }, STALL_WARN_MS);\n};\n\nexport const useStatewireCommands = <THandlers extends Statewire.Commands>(\n commands:\n | (THandlers & ThisType<StatewireHost.Ctx>)\n | undefined\n | null\n | false,\n): Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>> => {\n const [built] = useState(() => {\n const cell: CommandsCell = {\n handlers: null,\n queue: [],\n active: new Map(),\n running: false,\n listener: null,\n seq: 0,\n unmounted: false,\n stallTimer: null,\n };\n const proxy = createMethodProxy<\n Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>>\n >(\n (method, params) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method,\n params,\n ts: Date.now(),\n resolve,\n reject,\n });\n pump(cell);\n }),\n );\n commandsHosts.set(proxy, {\n abort: () => abortInvocations(cell),\n setListener: (listener) => {\n cell.listener = listener;\n },\n methods: () => {\n const handlers = cell.handlers;\n if (!handlers) return [];\n return Object.keys(handlers).filter(\n (method) => handlerOf(handlers, method) !== undefined,\n );\n },\n submit: (origin) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method: origin.method,\n params: origin.params,\n ts: Date.now(),\n resolve,\n reject,\n origin,\n });\n pump(cell);\n }),\n });\n return { cell, proxy };\n });\n\n useEffect(() => {\n const { cell } = built;\n cell.unmounted = false;\n return () => {\n cell.unmounted = true;\n cell.handlers = null;\n if (cell.stallTimer !== null) {\n clearTimeout(cell.stallTimer);\n cell.stallTimer = null;\n }\n queueMicrotask(() => {\n if (!cell.unmounted) return;\n abortInvocations(cell);\n const queued = cell.queue;\n cell.queue = [];\n for (const call of queued) {\n call.reject(new Error(\"statewire: unmounted\"));\n }\n });\n };\n }, [built]);\n\n useEffect(() => {\n if (commands) {\n for (const method of Object.keys(commands)) {\n if (method.startsWith(\"statewire/\")) {\n throw new Error(\n `statewire: cannot register \"${method}\": the \"statewire/\" method namespace is reserved`,\n );\n }\n }\n }\n built.cell.handlers = commands || null;\n pump(built.cell);\n });\n\n return built.proxy;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"host.js","names":["useEffect","useState","applyOps","sanitizeValue","StatewirePath","StatewireReject","createStateDraft","createMethodProxy","StateDraft","StatewireSendError","Statewire","StatewireHost","isDev","process","env","stateHosts","WeakMap","StateHost","documentIdentity","protocol","key","JSON","stringify","getStateHost","state","get","StateCell","current","draft","changed","documents","Map","Document","onOp","op","Op","onChange","onDocuments","ctl","StateCtl","host","bump","useStatewireState","init","TState","setVersion","cell","commit","c","flush","v","apply","ops","result","invalidate","push","changedPaths","snapshot","setOnOp","listener","setOnChange","takeChanged","mount","value","options","main","undefined","id","doc","Error","set","unmount","delete","setOnDocuments","record","changedPath","proxy","const","Settled","type","error","CommandsInvocation","method","params","ts","seq","clientId","CommandsOutcome","payload","CommandsListener","onStart","inv","onPosted","onApplied","onCovered","onSettle","outcome","CommandsSubmission","caller","Caller","attach","Attach","CommandsHost","setListener","submit","call","Promise","methods","abort","commandsHosts","getCommandsHost","commands","bindCommandsHost","QueuedCall","resolve","reject","origin","ActiveInvocation","applied","controller","AbortController","settled","CommandsCell","handlers","Record","args","queue","active","running","unmounted","stallTimer","ReturnType","setTimeout","STALL_WARN_MS","LOCAL_CALLER","Object","freeze","auth","isConnected","toSendError","fate","cause","message","code","String","applyInvocation","settleInvocation","abortInvocations","clear","isThenable","PromiseLike","then","handlerOf","prototype","propertyIsEnumerable","handler","dispatch","ctx","Ctx","signal","this","resolved","pump","watchStall","shift","length","console","warn","useStatewireCommands","Commands","THandlers","ThisType","CommandsProxy","HandledCommands","built","Date","now","keys","filter","clearTimeout","queueMicrotask","queued","startsWith"],"sources":["../../src/host/host.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { applyOps, sanitizeValue } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireReject } from \"./engine\";\nimport { createStateDraft } from \"./draft\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StateDraft } from \"./draft\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\n\nconst isDev = () =>\n typeof process === \"undefined\" || process.env[\"NODE_ENV\"] !== \"production\";\n\n// ─── state ──────────────────────────────────────────────\n\nconst stateHosts = new WeakMap<object, StatewireHost.StateHost>();\n\nexport const documentIdentity = (protocol: string, key?: readonly string[]) =>\n JSON.stringify([protocol, key ?? null]);\n\n/** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */\nexport const getStateHost = (\n state: object,\n): StatewireHost.StateHost | undefined => stateHosts.get(state);\n\ntype StateCell = {\n current: unknown;\n draft: StateDraft<object> | null;\n changed: StatewirePath[];\n documents: Map<string, StatewireHost.Document>;\n onOp: ((op: Statewire.Op) => void) | null;\n onChange: (() => void) | null;\n onDocuments: (() => void) | null;\n ctl: StatewireHost.StateCtl;\n host: StatewireHost.StateHost;\n bump: () => void;\n};\n\nexport const useStatewireState = <TState>(\n init: () => TState,\n): readonly [TState, StatewireHost.StateCtl<TState>] => {\n const [, setVersion] = useState(0);\n const [cell] = useState((): StateCell => {\n const commit = () => {\n if (c.draft) c.current = c.draft.flush();\n return c.current;\n };\n const c: StateCell = {\n current: sanitizeValue(init()),\n draft: null,\n changed: [[]],\n documents: new Map(),\n onOp: null,\n onChange: null,\n onDocuments: null,\n bump: () => setVersion((v) => v + 1),\n ctl: {\n apply: (ops) => {\n const result = applyOps(commit(), ops);\n c.current = result.state;\n c.draft?.invalidate();\n c.changed.push(...result.changedPaths);\n if (c.onOp) for (const op of ops) c.onOp(op);\n c.bump();\n c.onChange?.();\n },\n snapshot: commit,\n },\n host: {\n snapshot: commit,\n setOnOp: (listener) => {\n c.onOp = listener;\n },\n setOnChange: (listener) => {\n c.onChange = listener;\n },\n takeChanged: () => {\n const changed = c.changed;\n c.changed = [];\n return changed;\n },\n mount: (protocol, value, options) => {\n const key = options?.key;\n const main = options?.main ?? key === undefined;\n if (main) {\n for (const [id, doc] of c.documents) {\n if (\n doc.protocol === protocol &&\n doc.main &&\n id !== documentIdentity(protocol, key)\n )\n throw new Error(\n `statewire: ${protocol} already has a main document`,\n );\n }\n }\n c.documents.set(documentIdentity(protocol, key), {\n protocol,\n key: key === undefined ? null : [...key],\n main,\n value: sanitizeValue(value),\n });\n c.onDocuments?.();\n },\n unmount: (protocol, key) => {\n if (!c.documents.delete(documentIdentity(protocol, key)))\n throw new Error(\"statewire: document is not mounted\");\n c.onDocuments?.();\n },\n documents: () => c.documents,\n setOnDocuments: (listener) => {\n c.onDocuments = listener;\n },\n },\n };\n return c;\n });\n\n let value = cell.ctl.snapshot();\n if (typeof value === \"object\" && value !== null) {\n cell.draft ??= createStateDraft<object>({\n snapshot: () => cell.current,\n record: (op, changedPath) => {\n cell.changed.push(changedPath);\n cell.onOp?.(op);\n cell.bump();\n cell.onChange?.();\n },\n });\n stateHosts.set(cell.draft.proxy, cell.host);\n value = cell.draft.proxy;\n }\n return [value as TState, cell.ctl as StatewireHost.StateCtl<TState>] as const;\n};\n\n// ─── commands ───────────────────────────────────────────\n\ntype Settled =\n | { type: \"resolved\"; value: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsInvocation = {\n readonly protocol?: string;\n readonly method: string;\n readonly params: readonly unknown[];\n readonly ts: number;\n readonly seq: number;\n readonly clientId?: string;\n};\n\nexport type CommandsOutcome =\n | { type: \"accepted\"; payload?: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsListener = {\n onStart: (inv: CommandsInvocation) => void;\n onPosted?: (inv: CommandsInvocation) => void;\n onApplied: (inv: CommandsInvocation) => void;\n /** Canonical state now covers the invocation's effect — drop any overlay. */\n onCovered?: (inv: CommandsInvocation) => void;\n onSettle: (inv: CommandsInvocation, outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsSubmission = {\n method: string;\n params: readonly unknown[];\n clientId?: string;\n caller?: StatewireHost.Caller;\n attach?: StatewireHost.Attach;\n onApplied?: () => void;\n onSettle?: (outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsHost = {\n setListener: (listener: CommandsListener | null) => void;\n submit?: (call: CommandsSubmission) => Promise<unknown>;\n /** Registered command names, read live. */\n methods?: () => string[];\n abort?: () => void;\n};\n\nconst commandsHosts = new WeakMap<object, CommandsHost>();\n\nexport const getCommandsHost = (commands: object): CommandsHost | undefined =>\n commandsHosts.get(commands);\n\nexport const bindCommandsHost = (commands: object, host: CommandsHost) => {\n commandsHosts.set(commands, host);\n};\n\ntype QueuedCall = {\n method: string;\n params: readonly unknown[];\n ts: number;\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n origin?: CommandsSubmission;\n};\n\ntype ActiveInvocation = CommandsInvocation & {\n applied: boolean;\n controller: AbortController;\n settled: boolean;\n origin?: CommandsSubmission;\n};\n\ntype CommandsCell = {\n handlers: Record<string, (...args: never[]) => unknown> | null;\n queue: QueuedCall[];\n active: Map<ActiveInvocation, QueuedCall>;\n running: boolean;\n listener: CommandsListener | null;\n seq: number;\n unmounted: boolean;\n stallTimer: ReturnType<typeof setTimeout> | null;\n};\n\nconst STALL_WARN_MS = 5000;\n\n// In-process dispatch has no wire client: trivially attached.\nconst LOCAL_CALLER: StatewireHost.Caller = Object.freeze({\n get clientId(): string {\n throw new Error(\"statewire: in-process dispatch has no client id\");\n },\n auth: undefined,\n isConnected: true,\n});\n\nconst toSendError = (error: unknown): StatewireSendError =>\n error instanceof StatewireSendError\n ? error\n : error instanceof StatewireReject\n ? new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n message: error.message,\n ...(error.code !== undefined && { code: error.code }),\n ...(error.payload !== undefined && { payload: error.payload }),\n })\n : new StatewireSendError({\n fate: \"failed\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n });\n\nconst applyInvocation = (cell: CommandsCell, inv: ActiveInvocation) => {\n if (inv.applied) return;\n inv.applied = true;\n cell.listener?.onApplied(inv);\n cell.listener?.onCovered?.(inv);\n inv.origin?.onApplied?.();\n};\n\nconst settleInvocation = (\n cell: CommandsCell,\n inv: ActiveInvocation,\n call: QueuedCall,\n settled: Settled,\n) => {\n if (inv.settled) return;\n inv.settled = true;\n cell.active.delete(inv);\n if (settled.type === \"resolved\") {\n applyInvocation(cell, inv);\n const outcome: CommandsOutcome =\n settled.value === undefined\n ? { type: \"accepted\" }\n : { type: \"accepted\", payload: sanitizeValue(settled.value) };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.resolve(outcome.payload);\n } else {\n cell.listener?.onCovered?.(inv);\n const error = toSendError(settled.error);\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n};\n\nconst abortInvocations = (cell: CommandsCell) => {\n for (const [inv, call] of cell.active) {\n inv.settled = true;\n inv.controller.abort();\n const error = new StatewireSendError({ fate: \"crashed\", cause: \"server\" });\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n cell.active.clear();\n};\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\";\n\n// A command is an own, enumerable, function-valued property — exactly what\n// `Object.keys` shows, so dispatch, `statewire/commands`, and the reserved-\n// namespace guard share one view; prototype-chain names are not commands.\nconst handlerOf = (\n handlers: Record<string, (...args: never[]) => unknown>,\n method: string,\n): ((...args: never[]) => unknown) | undefined => {\n if (!Object.prototype.propertyIsEnumerable.call(handlers, method)) {\n return undefined;\n }\n const handler = handlers[method];\n return typeof handler === \"function\" ? handler : undefined;\n};\n\nconst dispatch = (cell: CommandsCell, call: QueuedCall) => {\n const handler = handlerOf(cell.handlers!, call.method);\n if (handler === undefined) {\n const error = new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n code: \"unknown-command\",\n });\n call.origin?.onSettle?.({ type: \"rejected\", error });\n call.reject(error);\n return;\n }\n const inv: ActiveInvocation = {\n method: call.method,\n params: call.params,\n ts: call.ts,\n seq: ++cell.seq,\n applied: false,\n controller: new AbortController(),\n settled: false,\n ...(call.origin !== undefined && { origin: call.origin }),\n ...(call.origin?.clientId !== undefined && {\n clientId: call.origin.clientId,\n }),\n };\n cell.active.set(inv, call);\n cell.listener?.onStart(inv);\n cell.listener?.onPosted?.(inv);\n const attach = call.origin?.attach;\n const ctx: StatewireHost.Ctx = {\n caller: call.origin?.caller ?? LOCAL_CALLER,\n get attach() {\n if (attach === undefined)\n throw new Error(\"statewire: in-process dispatch has no attach\");\n return attach;\n },\n signal: inv.controller.signal,\n applied: () => {\n if (!inv.settled) applyInvocation(cell, inv);\n },\n };\n try {\n const value = (\n handler as (\n this: StatewireHost.Ctx,\n ...args: readonly unknown[]\n ) => unknown\n ).call(ctx, ...call.params);\n if (isThenable(value)) {\n value.then(\n (resolved) =>\n settleInvocation(cell, inv, call, {\n type: \"resolved\",\n value: resolved,\n }),\n (error: unknown) =>\n settleInvocation(cell, inv, call, { type: \"rejected\", error }),\n );\n } else {\n settleInvocation(cell, inv, call, { type: \"resolved\", value });\n }\n } catch (error) {\n settleInvocation(cell, inv, call, { type: \"rejected\", error });\n }\n};\n\n// Prefix-only advancement: each handler runs synchronously to its first\n// await, then the pump moves on; settlement rides the returned promise.\nconst pump = (cell: CommandsCell) => {\n if (cell.running || !cell.handlers) {\n watchStall(cell);\n return;\n }\n cell.running = true;\n try {\n while (cell.handlers) {\n const call = cell.queue.shift();\n if (!call) break;\n dispatch(cell, call);\n }\n } finally {\n cell.running = false;\n }\n};\n\nconst watchStall = (cell: CommandsCell) => {\n if (!isDev() || cell.stallTimer !== null || cell.queue.length === 0) return;\n if (cell.handlers) return;\n cell.stallTimer = setTimeout(() => {\n cell.stallTimer = null;\n if (cell.handlers === null && cell.queue.length > 0 && !cell.unmounted) {\n console.warn(\n `statewire: ${cell.queue.length} buffered command(s) waiting over ${STALL_WARN_MS}ms — did the host forget to arm useStatewireCommands?`,\n );\n }\n }, STALL_WARN_MS);\n};\n\nexport const useStatewireCommands = <THandlers extends Statewire.Commands>(\n commands:\n | (THandlers & ThisType<StatewireHost.Ctx>)\n | undefined\n | null\n | false,\n): Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>> => {\n const [built] = useState(() => {\n const cell: CommandsCell = {\n handlers: null,\n queue: [],\n active: new Map(),\n running: false,\n listener: null,\n seq: 0,\n unmounted: false,\n stallTimer: null,\n };\n const proxy = createMethodProxy<\n Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>>\n >(\n (method, params) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method,\n params,\n ts: Date.now(),\n resolve,\n reject,\n });\n pump(cell);\n }),\n );\n commandsHosts.set(proxy, {\n abort: () => abortInvocations(cell),\n setListener: (listener) => {\n cell.listener = listener;\n },\n methods: () => {\n const handlers = cell.handlers;\n if (!handlers) return [];\n return Object.keys(handlers).filter(\n (method) => handlerOf(handlers, method) !== undefined,\n );\n },\n submit: (origin) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method: origin.method,\n params: origin.params,\n ts: Date.now(),\n resolve,\n reject,\n origin,\n });\n pump(cell);\n }),\n });\n return { cell, proxy };\n });\n\n useEffect(() => {\n const { cell } = built;\n cell.unmounted = false;\n return () => {\n cell.unmounted = true;\n cell.handlers = null;\n if (cell.stallTimer !== null) {\n clearTimeout(cell.stallTimer);\n cell.stallTimer = null;\n }\n queueMicrotask(() => {\n if (!cell.unmounted) return;\n abortInvocations(cell);\n const queued = cell.queue;\n cell.queue = [];\n for (const call of queued) {\n call.reject(new Error(\"statewire: unmounted\"));\n }\n });\n };\n }, [built]);\n\n useEffect(() => {\n if (commands) {\n for (const method of Object.keys(commands)) {\n if (method.startsWith(\"statewire/\")) {\n throw new Error(\n `statewire: cannot register \"${method}\": the \"statewire/\" method namespace is reserved`,\n );\n }\n }\n }\n built.cell.handlers = commands || null;\n pump(built.cell);\n });\n\n return built.proxy;\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","names":[],"sources":["../../src/host/http.ts"],"mappings":";;;cAuUa,SACX,GAAG,iBACH,SAAS,YACR,QAAQ;cAkCE,SACX,GAAG,iBACH,SAAS,YACR,QAAQ"}
1
+ {"version":3,"file":"http.d.ts","names":[],"sources":["../../src/host/http.ts"],"mappings":";;;cAgUa,SACX,GAAG,iBACH,SAAS,YACR,QAAQ;cAkCE,SACX,GAAG,iBACH,SAAS,YACR,QAAQ"}
package/dist/host/http.js CHANGED
@@ -66,7 +66,7 @@ const mintLease = () => {
66
66
  const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
67
67
  return btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
68
68
  };
69
- const attachSse = (h, clientId, auth, selection) => {
69
+ const attachSse = (h, clientId, auth, selection, frame) => {
70
70
  let controller;
71
71
  let connection = null;
72
72
  let open = true;
@@ -109,7 +109,7 @@ const attachSse = (h, clientId, auth, selection) => {
109
109
  return;
110
110
  }
111
111
  }
112
- }, false, auth, selection);
112
+ }, false, auth, selection, frame);
113
113
  connection = registration?.connection ?? null;
114
114
  if (!open) connection?.close();
115
115
  return {
@@ -130,11 +130,6 @@ const streamResponse = (attachment, lease) => {
130
130
  }
131
131
  });
132
132
  };
133
- const admitAttachFrame = (h, registration, frame) => {
134
- if (frame.ctx.length === 0 && frame.cmd.length === 0) return;
135
- const admission = admitFrame(h, registration.rec, frame, registration.conn.auth, registration.conn.selection);
136
- if (admission.type === "refused") sendTo(h, registration.conn, JSON.stringify(admission.syn));
137
- };
138
133
  const getStream = async (h, request) => {
139
134
  const authorized = await authorize(h, headersOf(request));
140
135
  if ("refused" in authorized) return authorized.refused;
@@ -163,10 +158,7 @@ const postStream = async (h, request) => {
163
158
  }
164
159
  let frame;
165
160
  try {
166
- frame = body === null && !raw.trim() ? {
167
- ctx: [],
168
- cmd: []
169
- } : parseFrameValue(body);
161
+ frame = body === null && !raw.trim() ? { cmd: [] } : parseFrameValue(body);
170
162
  } catch (error) {
171
163
  if (!(error instanceof Error) || error.name !== "StatewireViolation") throw error;
172
164
  return Response.json({ detail: error.message }, { status: 400 });
@@ -178,20 +170,19 @@ const postStream = async (h, request) => {
178
170
  if (atSubscriberLimit(h)) return subscriberLimitResponse();
179
171
  const selection = negotiateProtocols(request.headers, h.wireVersion, [h.protocol]);
180
172
  if ("fin" in selection) return Response.json(selection, { status: selection.fin.status });
181
- const attachment = attachSse(h, client.clientId, authorized.auth, selection);
182
- if (attachment.registration === null) return streamResponse(attachment, null);
183
- const lease = mintLease();
184
- attachment.registration.conn.lease = lease;
173
+ let attachment;
185
174
  try {
186
- admitAttachFrame(h, attachment.registration, frame);
175
+ attachment = attachSse(h, client.clientId, authorized.auth, selection, frame);
187
176
  } catch (error) {
188
- closeConn(h, attachment.registration.conn, 1008, "invalid-context");
189
177
  if (!(error instanceof Error) || error.name !== "StatewireViolation") throw error;
190
178
  return Response.json({ fin: {
191
179
  reason: "protocol-error",
192
180
  message: error.message
193
181
  } }, { status: 400 });
194
182
  }
183
+ if (attachment.registration === null) return streamResponse(attachment, null);
184
+ const lease = mintLease();
185
+ attachment.registration.conn.lease = lease;
195
186
  return streamResponse(attachment, lease);
196
187
  };
197
188
  const stream = async (h, request) => {
@@ -222,7 +213,6 @@ const frames = async (h, request) => {
222
213
  const leased = lease === null ? void 0 : [...h.conns].find((conn) => conn.open && conn.lease === lease);
223
214
  if (h.disposed || h.finished !== null || leased === void 0) return Response.json({ error: "stale-lease" }, { status: 423 });
224
215
  const clientId = leased.clientId;
225
- const rec = h.clients.get(clientId);
226
216
  const raw = await readFrameBody(request);
227
217
  if (raw === null) return Response.json({ error: "frame body too large" }, { status: 413 });
228
218
  let frame;
@@ -235,7 +225,7 @@ const frames = async (h, request) => {
235
225
  if (!leased.open || h.disposed || h.finished !== null) return Response.json({ error: "stale-lease" }, { status: 423 });
236
226
  let admission;
237
227
  try {
238
- admission = admitFrame(h, rec, frame, leased.auth, leased.selection);
228
+ admission = admitFrame(h, leased, frame);
239
229
  } catch (error) {
240
230
  if (!(error instanceof Error) || error.name !== "StatewireViolation") throw error;
241
231
  return violationResponse(h, clientId, error.message);
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","names":["StatewireHost","CLIENT_ID_RE","PRINCIPAL_MISMATCH_MESSAGE","SUBSCRIBER_LIMIT_MESSAGE","admitFrame","atSubscriberLimit","authEquals","closeConn","flush","parseFrame","parseFrameValue","register","sendTo","ParsedFrame","Registration","SocketHostState","MAX_FRAME_BODY_BYTES","negotiateProtocols","StatewireProtocol","CLIENT_ID_HEADER","LEASE_HEADER","encoder","TextEncoder","laggardFin","encode","readFrameBody","request","Request","Promise","declared","headers","get","Number","body","cancel","catch","reader","getReader","chunks","Uint8Array","size","done","value","read","length","push","offset","chunk","set","TextDecoder","decode","invalidClientIdReason","clientId","test","clientIdResponse","response","Response","json","detail","status","reason","headersOf","Record","Object","fromEntries","authorize","h","auth","refused","error","subscriberLimitResponse","principalMismatchResponse","rec","clients","undefined","principal","mintLease","bytes","crypto","getRandomValues","btoa","String","fromCharCode","replaceAll","replace","SseAttachment","ReadableStream","registration","activateBackpressure","attachSse","selection","Selection","controller","ReadableStreamDefaultController","connection","open","backpressureActive","start","next","close","highWaterMark","streamQueueSize","socket","send","data","desiredSize","enqueue","frame","streamResponse","attachment","lease","admitAttachFrame","ctx","cmd","admission","conn","type","JSON","stringify","syn","getStream","authorized","client","mismatch","wireVersion","protocol","fin","postStream","raw","trim","parse","Error","name","message","stream","method","Allow","finClient","Fin","conns","violationResponse","frames","leased","find","disposed","finished","ReturnType","retry","Math","ceil"],"sources":["../../src/host/http.ts"],"sourcesContent":["import type { StatewireHost } from \"../host\";\nimport {\n CLIENT_ID_RE,\n PRINCIPAL_MISMATCH_MESSAGE,\n SUBSCRIBER_LIMIT_MESSAGE,\n admitFrame,\n atSubscriberLimit,\n authEquals,\n closeConn,\n flush,\n parseFrame,\n parseFrameValue,\n register,\n sendTo,\n} from \"./socket\";\nimport type { ParsedFrame, Registration, SocketHostState } from \"./socket\";\nimport { MAX_FRAME_BODY_BYTES } from \"../wire/limits\";\nimport { negotiateProtocols } from \"../wire/protocol\";\nimport type { StatewireProtocol } from \"../wire/protocol\";\n\nexport { MAX_FRAME_BODY_BYTES } from \"../wire/limits\";\n\nconst CLIENT_ID_HEADER = \"Statewire-Client-Id\";\nconst LEASE_HEADER = \"Statewire-Lease\";\nconst encoder = new TextEncoder();\nconst laggardFin = encoder.encode(\n 'data: {\"fin\":{\"reason\":\"reconnect\",\"code\":\"client-fell-behind\"}}\\n\\n',\n);\nconst readFrameBody = async (request: Request): Promise<string | null> => {\n const declared = request.headers.get(\"content-length\");\n if (declared !== null && Number(declared) > MAX_FRAME_BODY_BYTES) {\n await request.body?.cancel(\"frame body too large\").catch(() => {});\n return null;\n }\n if (request.body === null) return \"\";\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.length;\n if (size > MAX_FRAME_BODY_BYTES) {\n await reader.cancel(\"frame body too large\").catch(() => {});\n return null;\n }\n chunks.push(value);\n }\n const body = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.length;\n }\n return new TextDecoder().decode(body);\n};\n\nconst invalidClientIdReason = (clientId: string): string | null => {\n if (clientId === \"\") return \"id must be non-empty\";\n if (clientId.length > 256) return \"id must be at most 256 characters\";\n if (!CLIENT_ID_RE.test(clientId)) {\n return \"id must contain only [A-Za-z0-9._~-]\";\n }\n return null;\n};\n\nconst clientIdResponse = (\n request: Request,\n): { clientId: string } | { response: Response } => {\n const clientId = request.headers.get(CLIENT_ID_HEADER);\n if (clientId === null) {\n return {\n response: Response.json(\n { detail: `missing ${CLIENT_ID_HEADER} header` },\n { status: 400 },\n ),\n };\n }\n const reason = invalidClientIdReason(clientId);\n if (reason !== null) {\n return {\n response: Response.json(\n { detail: `invalid ${CLIENT_ID_HEADER}: ${reason}` },\n { status: 400 },\n ),\n };\n }\n return { clientId };\n};\n\nconst headersOf = (request: Request): Record<string, string> =>\n Object.fromEntries(request.headers);\n\nconst authorize = async (\n h: SocketHostState,\n headers: Record<string, string>,\n): Promise<{ auth: unknown } | { refused: Response }> => {\n try {\n return { auth: await h.authorize?.({ headers }) };\n } catch (error) {\n if (error instanceof Response) return { refused: error };\n throw error;\n }\n};\n\nconst subscriberLimitResponse = (): Response =>\n Response.json({ detail: SUBSCRIBER_LIMIT_MESSAGE }, { status: 503 });\n\n// No awaits between this check and register: the binding cannot race.\nconst principalMismatchResponse = (\n h: SocketHostState,\n clientId: string,\n auth: unknown,\n): Response | null => {\n const rec = h.clients.get(clientId);\n if (rec !== undefined && !authEquals(rec.principal, auth)) {\n return Response.json(\n { detail: PRINCIPAL_MISMATCH_MESSAGE },\n { status: 403 },\n );\n }\n return null;\n};\n\nconst mintLease = (): string => {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n return btoa(String.fromCharCode(...bytes))\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/=+$/, \"\");\n};\n\ntype SseAttachment = {\n body: ReadableStream<Uint8Array>;\n registration: Registration | null;\n activateBackpressure: () => void;\n};\n\nconst attachSse = (\n h: SocketHostState,\n clientId: string,\n auth: unknown,\n selection: StatewireProtocol.Selection,\n): SseAttachment => {\n let controller: ReadableStreamDefaultController<Uint8Array>;\n let connection: Registration[\"connection\"] | null = null;\n let open = true;\n let backpressureActive = false;\n const body = new ReadableStream(\n {\n start: (next) => {\n controller = next;\n },\n cancel: () => {\n open = false;\n connection?.close();\n },\n },\n { highWaterMark: h.streamQueueSize },\n );\n const socket = {\n send: (data: string) => {\n if (!open) return;\n if (\n backpressureActive &&\n controller.desiredSize !== null &&\n controller.desiredSize <= 0\n ) {\n open = false;\n try {\n controller.enqueue(laggardFin);\n controller.close();\n } catch {\n /* stream already gone */\n }\n connection?.close();\n return;\n }\n const frame = data === \"{}\" ? \": ping\\n\\n\" : `data: ${data}\\n\\n`;\n try {\n controller.enqueue(encoder.encode(frame));\n } catch {\n open = false;\n connection?.close();\n }\n },\n close: () => {\n if (!open) return;\n open = false;\n try {\n controller.close();\n } catch {\n return;\n }\n },\n };\n const registration = register(h, clientId, socket, false, auth, selection);\n connection = registration?.connection ?? null;\n if (!open) connection?.close();\n return {\n body,\n registration,\n activateBackpressure: () => {\n backpressureActive = true;\n },\n };\n};\n\nconst streamResponse = (\n attachment: SseAttachment,\n lease: string | null,\n): Response => {\n attachment.activateBackpressure();\n return new Response(attachment.body, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/event-stream\",\n ...(lease !== null && { [LEASE_HEADER]: lease }),\n },\n });\n};\n\nconst admitAttachFrame = (\n h: SocketHostState,\n registration: Registration,\n frame: ParsedFrame,\n) => {\n if (frame.ctx.length === 0 && frame.cmd.length === 0) return;\n const admission = admitFrame(\n h,\n registration.rec,\n frame,\n registration.conn.auth,\n registration.conn.selection,\n );\n if (admission.type === \"refused\") {\n sendTo(h, registration.conn, JSON.stringify(admission.syn));\n }\n};\n\nconst getStream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const authorized = await authorize(h, headersOf(request));\n if (\"refused\" in authorized) return authorized.refused;\n const client = clientIdResponse(request);\n if (\"response\" in client) return client.response;\n const mismatch = principalMismatchResponse(\n h,\n client.clientId,\n authorized.auth,\n );\n if (mismatch !== null) return mismatch;\n if (atSubscriberLimit(h)) return subscriberLimitResponse();\n const selection = negotiateProtocols(request.headers, h.wireVersion, [\n h.protocol,\n ]);\n if (\"fin\" in selection)\n return Response.json(selection, { status: selection.fin.status });\n const attachment = attachSse(h, client.clientId, authorized.auth, selection);\n const lease = attachment.registration === null ? null : mintLease();\n if (attachment.registration !== null)\n attachment.registration.conn.lease = lease;\n return streamResponse(attachment, lease);\n};\n\nconst postStream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const client = clientIdResponse(request);\n if (\"response\" in client) return client.response;\n const raw = await readFrameBody(request);\n if (raw === null) {\n return Response.json({ detail: \"frame body too large\" }, { status: 413 });\n }\n let body: unknown;\n try {\n body = raw.trim() ? JSON.parse(raw) : null;\n } catch {\n return Response.json({ detail: \"body must be JSON\" }, { status: 400 });\n }\n let frame: ParsedFrame;\n try {\n frame =\n body === null && !raw.trim()\n ? { ctx: [], cmd: [] }\n : parseFrameValue(body);\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\") {\n throw error;\n }\n return Response.json({ detail: error.message }, { status: 400 });\n }\n const authorized = await authorize(h, headersOf(request));\n if (\"refused\" in authorized) return authorized.refused;\n const mismatch = principalMismatchResponse(\n h,\n client.clientId,\n authorized.auth,\n );\n if (mismatch !== null) return mismatch;\n if (atSubscriberLimit(h)) return subscriberLimitResponse();\n const selection = negotiateProtocols(request.headers, h.wireVersion, [\n h.protocol,\n ]);\n if (\"fin\" in selection)\n return Response.json(selection, { status: selection.fin.status });\n const attachment = attachSse(h, client.clientId, authorized.auth, selection);\n if (attachment.registration === null) return streamResponse(attachment, null);\n const lease = mintLease();\n attachment.registration.conn.lease = lease;\n try {\n admitAttachFrame(h, attachment.registration, frame);\n } catch (error) {\n closeConn(h, attachment.registration.conn, 1008, \"invalid-context\");\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\")\n throw error;\n return Response.json(\n { fin: { reason: \"protocol-error\", message: error.message } },\n { status: 400 },\n );\n }\n return streamResponse(attachment, lease);\n};\n\nexport const stream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n if (request.method === \"GET\") return getStream(h, request);\n if (request.method === \"POST\") return postStream(h, request);\n return new Response(null, {\n status: 405,\n headers: { Allow: \"GET, POST\" },\n });\n};\n\nconst finClient = (\n h: SocketHostState,\n clientId: string,\n fin: StatewireHost.Fin,\n) => {\n flush(h);\n for (const conn of [...h.conns]) {\n if (conn.clientId !== clientId) continue;\n sendTo(h, conn, JSON.stringify({ fin }));\n closeConn(h, conn);\n }\n};\n\nconst violationResponse = (\n h: SocketHostState,\n clientId: string,\n reason: string,\n) => {\n finClient(h, clientId, {\n reason: \"protocol-error\",\n message: reason,\n });\n return Response.json({ error: reason }, { status: 400 });\n};\n\nexport const frames = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const lease = request.headers.get(LEASE_HEADER);\n // A lease lives and dies with its attach: any live attach of the id accepts.\n const leased =\n lease === null\n ? undefined\n : [...h.conns].find((conn) => conn.open && conn.lease === lease);\n if (h.disposed || h.finished !== null || leased === undefined) {\n return Response.json({ error: \"stale-lease\" }, { status: 423 });\n }\n const clientId = leased.clientId;\n const rec = h.clients.get(clientId)!;\n const raw = await readFrameBody(request);\n if (raw === null) {\n return Response.json({ error: \"frame body too large\" }, { status: 413 });\n }\n let frame: ParsedFrame;\n try {\n frame = parseFrame(raw);\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\") {\n throw error;\n }\n return violationResponse(h, clientId, error.message);\n }\n if (!leased.open || h.disposed || h.finished !== null) {\n return Response.json({ error: \"stale-lease\" }, { status: 423 });\n }\n let admission: ReturnType<typeof admitFrame>;\n try {\n admission = admitFrame(h, rec, frame, leased.auth, leased.selection);\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\")\n throw error;\n return violationResponse(h, clientId, error.message);\n }\n if (admission.type === \"refused\") {\n const retry = admission.syn.syn.retry;\n return Response.json(admission.syn, {\n status: retry === undefined ? 409 : 429,\n ...(retry !== undefined && {\n headers: { \"Retry-After\": String(Math.ceil(retry / 1000)) },\n }),\n });\n }\n return Response.json({});\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"http.js","names":["StatewireHost","CLIENT_ID_RE","PRINCIPAL_MISMATCH_MESSAGE","SUBSCRIBER_LIMIT_MESSAGE","admitFrame","atSubscriberLimit","authEquals","closeConn","flush","parseFrame","parseFrameValue","register","sendTo","ParsedFrame","Registration","SocketHostState","MAX_FRAME_BODY_BYTES","negotiateProtocols","StatewireProtocol","CLIENT_ID_HEADER","LEASE_HEADER","encoder","TextEncoder","laggardFin","encode","readFrameBody","request","Request","Promise","declared","headers","get","Number","body","cancel","catch","reader","getReader","chunks","Uint8Array","size","done","value","read","length","push","offset","chunk","set","TextDecoder","decode","invalidClientIdReason","clientId","test","clientIdResponse","response","Response","json","detail","status","reason","headersOf","Record","Object","fromEntries","authorize","h","auth","refused","error","subscriberLimitResponse","principalMismatchResponse","rec","clients","undefined","principal","mintLease","bytes","crypto","getRandomValues","btoa","String","fromCharCode","replaceAll","replace","SseAttachment","ReadableStream","registration","activateBackpressure","attachSse","selection","Selection","frame","controller","ReadableStreamDefaultController","connection","open","backpressureActive","start","next","close","highWaterMark","streamQueueSize","socket","send","data","desiredSize","enqueue","streamResponse","attachment","lease","getStream","authorized","client","mismatch","wireVersion","protocol","fin","conn","postStream","raw","trim","JSON","parse","cmd","Error","name","message","stream","method","Allow","finClient","Fin","conns","stringify","violationResponse","frames","leased","find","disposed","finished","admission","ReturnType","type","retry","syn","Math","ceil"],"sources":["../../src/host/http.ts"],"sourcesContent":["import type { StatewireHost } from \"../host\";\nimport {\n CLIENT_ID_RE,\n PRINCIPAL_MISMATCH_MESSAGE,\n SUBSCRIBER_LIMIT_MESSAGE,\n admitFrame,\n atSubscriberLimit,\n authEquals,\n closeConn,\n flush,\n parseFrame,\n parseFrameValue,\n register,\n sendTo,\n} from \"./socket\";\nimport type { ParsedFrame, Registration, SocketHostState } from \"./socket\";\nimport { MAX_FRAME_BODY_BYTES } from \"../wire/limits\";\nimport { negotiateProtocols } from \"../wire/protocol\";\nimport type { StatewireProtocol } from \"../wire/protocol\";\n\nexport { MAX_FRAME_BODY_BYTES } from \"../wire/limits\";\n\nconst CLIENT_ID_HEADER = \"Statewire-Client-Id\";\nconst LEASE_HEADER = \"Statewire-Lease\";\nconst encoder = new TextEncoder();\nconst laggardFin = encoder.encode(\n 'data: {\"fin\":{\"reason\":\"reconnect\",\"code\":\"client-fell-behind\"}}\\n\\n',\n);\nconst readFrameBody = async (request: Request): Promise<string | null> => {\n const declared = request.headers.get(\"content-length\");\n if (declared !== null && Number(declared) > MAX_FRAME_BODY_BYTES) {\n await request.body?.cancel(\"frame body too large\").catch(() => {});\n return null;\n }\n if (request.body === null) return \"\";\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.length;\n if (size > MAX_FRAME_BODY_BYTES) {\n await reader.cancel(\"frame body too large\").catch(() => {});\n return null;\n }\n chunks.push(value);\n }\n const body = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.length;\n }\n return new TextDecoder().decode(body);\n};\n\nconst invalidClientIdReason = (clientId: string): string | null => {\n if (clientId === \"\") return \"id must be non-empty\";\n if (clientId.length > 256) return \"id must be at most 256 characters\";\n if (!CLIENT_ID_RE.test(clientId)) {\n return \"id must contain only [A-Za-z0-9._~-]\";\n }\n return null;\n};\n\nconst clientIdResponse = (\n request: Request,\n): { clientId: string } | { response: Response } => {\n const clientId = request.headers.get(CLIENT_ID_HEADER);\n if (clientId === null) {\n return {\n response: Response.json(\n { detail: `missing ${CLIENT_ID_HEADER} header` },\n { status: 400 },\n ),\n };\n }\n const reason = invalidClientIdReason(clientId);\n if (reason !== null) {\n return {\n response: Response.json(\n { detail: `invalid ${CLIENT_ID_HEADER}: ${reason}` },\n { status: 400 },\n ),\n };\n }\n return { clientId };\n};\n\nconst headersOf = (request: Request): Record<string, string> =>\n Object.fromEntries(request.headers);\n\nconst authorize = async (\n h: SocketHostState,\n headers: Record<string, string>,\n): Promise<{ auth: unknown } | { refused: Response }> => {\n try {\n return { auth: await h.authorize?.({ headers }) };\n } catch (error) {\n if (error instanceof Response) return { refused: error };\n throw error;\n }\n};\n\nconst subscriberLimitResponse = (): Response =>\n Response.json({ detail: SUBSCRIBER_LIMIT_MESSAGE }, { status: 503 });\n\n// No awaits between this check and register: the binding cannot race.\nconst principalMismatchResponse = (\n h: SocketHostState,\n clientId: string,\n auth: unknown,\n): Response | null => {\n const rec = h.clients.get(clientId);\n if (rec !== undefined && !authEquals(rec.principal, auth)) {\n return Response.json(\n { detail: PRINCIPAL_MISMATCH_MESSAGE },\n { status: 403 },\n );\n }\n return null;\n};\n\nconst mintLease = (): string => {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n return btoa(String.fromCharCode(...bytes))\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/=+$/, \"\");\n};\n\ntype SseAttachment = {\n body: ReadableStream<Uint8Array>;\n registration: Registration | null;\n activateBackpressure: () => void;\n};\n\nconst attachSse = (\n h: SocketHostState,\n clientId: string,\n auth: unknown,\n selection: StatewireProtocol.Selection,\n frame?: ParsedFrame,\n): SseAttachment => {\n let controller: ReadableStreamDefaultController<Uint8Array>;\n let connection: Registration[\"connection\"] | null = null;\n let open = true;\n let backpressureActive = false;\n const body = new ReadableStream(\n {\n start: (next) => {\n controller = next;\n },\n cancel: () => {\n open = false;\n connection?.close();\n },\n },\n { highWaterMark: h.streamQueueSize },\n );\n const socket = {\n send: (data: string) => {\n if (!open) return;\n if (\n backpressureActive &&\n controller.desiredSize !== null &&\n controller.desiredSize <= 0\n ) {\n open = false;\n try {\n controller.enqueue(laggardFin);\n controller.close();\n } catch {\n /* stream already gone */\n }\n connection?.close();\n return;\n }\n const frame = data === \"{}\" ? \": ping\\n\\n\" : `data: ${data}\\n\\n`;\n try {\n controller.enqueue(encoder.encode(frame));\n } catch {\n open = false;\n connection?.close();\n }\n },\n close: () => {\n if (!open) return;\n open = false;\n try {\n controller.close();\n } catch {\n return;\n }\n },\n };\n const registration = register(\n h,\n clientId,\n socket,\n false,\n auth,\n selection,\n frame,\n );\n connection = registration?.connection ?? null;\n if (!open) connection?.close();\n return {\n body,\n registration,\n activateBackpressure: () => {\n backpressureActive = true;\n },\n };\n};\n\nconst streamResponse = (\n attachment: SseAttachment,\n lease: string | null,\n): Response => {\n attachment.activateBackpressure();\n return new Response(attachment.body, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/event-stream\",\n ...(lease !== null && { [LEASE_HEADER]: lease }),\n },\n });\n};\n\nconst getStream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const authorized = await authorize(h, headersOf(request));\n if (\"refused\" in authorized) return authorized.refused;\n const client = clientIdResponse(request);\n if (\"response\" in client) return client.response;\n const mismatch = principalMismatchResponse(\n h,\n client.clientId,\n authorized.auth,\n );\n if (mismatch !== null) return mismatch;\n if (atSubscriberLimit(h)) return subscriberLimitResponse();\n const selection = negotiateProtocols(request.headers, h.wireVersion, [\n h.protocol,\n ]);\n if (\"fin\" in selection)\n return Response.json(selection, { status: selection.fin.status });\n const attachment = attachSse(h, client.clientId, authorized.auth, selection);\n const lease = attachment.registration === null ? null : mintLease();\n if (attachment.registration !== null)\n attachment.registration.conn.lease = lease;\n return streamResponse(attachment, lease);\n};\n\nconst postStream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const client = clientIdResponse(request);\n if (\"response\" in client) return client.response;\n const raw = await readFrameBody(request);\n if (raw === null) {\n return Response.json({ detail: \"frame body too large\" }, { status: 413 });\n }\n let body: unknown;\n try {\n body = raw.trim() ? JSON.parse(raw) : null;\n } catch {\n return Response.json({ detail: \"body must be JSON\" }, { status: 400 });\n }\n let frame: ParsedFrame;\n try {\n frame = body === null && !raw.trim() ? { cmd: [] } : parseFrameValue(body);\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\") {\n throw error;\n }\n return Response.json({ detail: error.message }, { status: 400 });\n }\n const authorized = await authorize(h, headersOf(request));\n if (\"refused\" in authorized) return authorized.refused;\n const mismatch = principalMismatchResponse(\n h,\n client.clientId,\n authorized.auth,\n );\n if (mismatch !== null) return mismatch;\n if (atSubscriberLimit(h)) return subscriberLimitResponse();\n const selection = negotiateProtocols(request.headers, h.wireVersion, [\n h.protocol,\n ]);\n if (\"fin\" in selection)\n return Response.json(selection, { status: selection.fin.status });\n let attachment: SseAttachment;\n try {\n attachment = attachSse(\n h,\n client.clientId,\n authorized.auth,\n selection,\n frame,\n );\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\")\n throw error;\n return Response.json(\n { fin: { reason: \"protocol-error\", message: error.message } },\n { status: 400 },\n );\n }\n if (attachment.registration === null) return streamResponse(attachment, null);\n const lease = mintLease();\n attachment.registration.conn.lease = lease;\n return streamResponse(attachment, lease);\n};\n\nexport const stream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n if (request.method === \"GET\") return getStream(h, request);\n if (request.method === \"POST\") return postStream(h, request);\n return new Response(null, {\n status: 405,\n headers: { Allow: \"GET, POST\" },\n });\n};\n\nconst finClient = (\n h: SocketHostState,\n clientId: string,\n fin: StatewireHost.Fin,\n) => {\n flush(h);\n for (const conn of [...h.conns]) {\n if (conn.clientId !== clientId) continue;\n sendTo(h, conn, JSON.stringify({ fin }));\n closeConn(h, conn);\n }\n};\n\nconst violationResponse = (\n h: SocketHostState,\n clientId: string,\n reason: string,\n) => {\n finClient(h, clientId, {\n reason: \"protocol-error\",\n message: reason,\n });\n return Response.json({ error: reason }, { status: 400 });\n};\n\nexport const frames = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const lease = request.headers.get(LEASE_HEADER);\n // A lease lives and dies with its attach: any live attach of the id accepts.\n const leased =\n lease === null\n ? undefined\n : [...h.conns].find((conn) => conn.open && conn.lease === lease);\n if (h.disposed || h.finished !== null || leased === undefined) {\n return Response.json({ error: \"stale-lease\" }, { status: 423 });\n }\n const clientId = leased.clientId;\n const raw = await readFrameBody(request);\n if (raw === null) {\n return Response.json({ error: \"frame body too large\" }, { status: 413 });\n }\n let frame: ParsedFrame;\n try {\n frame = parseFrame(raw);\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\") {\n throw error;\n }\n return violationResponse(h, clientId, error.message);\n }\n if (!leased.open || h.disposed || h.finished !== null) {\n return Response.json({ error: \"stale-lease\" }, { status: 423 });\n }\n let admission: ReturnType<typeof admitFrame>;\n try {\n admission = admitFrame(h, leased, frame);\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\")\n throw error;\n return violationResponse(h, clientId, error.message);\n }\n if (admission.type === \"refused\") {\n const retry = admission.syn.syn.retry;\n return Response.json(admission.syn, {\n status: retry === undefined ? 409 : 429,\n ...(retry !== undefined && {\n headers: { \"Retry-After\": String(Math.ceil(retry / 1000)) },\n }),\n });\n }\n return Response.json({});\n};\n"],"mappings":"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"}
@@ -1,8 +1,6 @@
1
- import { StatewireDocuments } from "../wire/documents.js";
2
- import { StatewireProtocol } from "../wire/protocol.js";
3
1
  import { CommandsHost } from "./host.js";
4
2
  import { StatewireHost } from "../host.js";
5
- import { StatewireCmd } from "../client/connection.js";
3
+ import { StatewireProtocol } from "../wire/protocol.js";
6
4
  import { Statewire } from "../types.js";
7
5
  import { StatewirePath } from "../wire/ops.js";
8
6
  import { StatewireDocument } from "../document.js";
@@ -15,31 +13,41 @@ declare const PRINCIPAL_MISMATCH_MESSAGE = "client id is bound to another princi
15
13
  * objects compare by value; anything else by identity. */
16
14
  declare const authEquals: (a: unknown, b: unknown) => boolean;
17
15
  declare const SUBSCRIBER_LIMIT_MESSAGE = "stream subscriber limit reached";
16
+ type Cmd = StatewireHostInternal.Cmd;
18
17
  type WireOp = {
19
18
  op: string;
19
+ doc?: number;
20
20
  path?: StatewirePath;
21
21
  value?: unknown;
22
22
  meta?: StatewireDocument.MetaEntry[];
23
+ protocol?: string;
24
+ key?: readonly string[];
25
+ main?: boolean;
23
26
  };
24
27
  type Admission = {
28
+ protocol: string;
25
29
  seq: number;
26
30
  method: string;
27
31
  applied: boolean;
28
32
  appliedSent: boolean;
29
33
  settled: boolean;
30
34
  };
31
- type ClientRec = {
32
- id: string;
35
+ /** One statement lane: durable lanes live on the client record, ephemeral lanes on the attach. */
36
+ type Lane = {
37
+ protocol: string;
38
+ clientId: string;
39
+ conn: Conn | null;
33
40
  lastSeq: number;
34
- ctxSeq: number;
35
- touched: number;
36
- context: Record<string, unknown> | null;
37
- contextDocuments: StatewireDocuments.State;
38
41
  results: Map<number, {
39
42
  ts: number;
40
- cmd: StatewireCmd;
43
+ cmd: Cmd;
41
44
  }>;
42
45
  inflight: Map<number, Admission>;
46
+ };
47
+ type ClientRec = {
48
+ id: string;
49
+ touched: number;
50
+ lanes: Map<string, Lane>;
43
51
  /** The auth captured at the record's first attach; later attaches must match. */
44
52
  principal: unknown;
45
53
  };
@@ -48,11 +56,23 @@ type Conn = {
48
56
  clientId: string;
49
57
  socket: StatewireHostInternal.Socket;
50
58
  open: boolean;
59
+ ready: boolean;
51
60
  sawFrame: boolean;
52
61
  lastSentAt: number;
53
62
  lease: string | null;
54
63
  /** The awaited `authorize` return captured at this attach's authorization. */
55
64
  auth: unknown;
65
+ lanes: Map<string, Lane>;
66
+ documents: Map<string, StatewireHost.Document>;
67
+ included: Set<string>;
68
+ view: Map<string, {
69
+ doc: number;
70
+ value: unknown;
71
+ }>;
72
+ nextDoc: number;
73
+ pendingApplied: Cmd[];
74
+ pendingCmds: Cmd[];
75
+ attach: StatewireHost.Attach;
56
76
  };
57
77
  type PendingConn = {
58
78
  socket: StatewireHostInternal.Socket;
@@ -62,10 +82,6 @@ type PendingConn = {
62
82
  timer: ReturnType<typeof setTimeout> | null;
63
83
  connection: StatewireHostInternal.SocketConnection | null;
64
84
  };
65
- type CtxStatement = {
66
- seq: number;
67
- ops: unknown[];
68
- };
69
85
  type CmdStatement = {
70
86
  protocol: string;
71
87
  seq: number;
@@ -74,7 +90,6 @@ type CmdStatement = {
74
90
  meta?: unknown;
75
91
  };
76
92
  type ParsedFrame = {
77
- ctx: CtxStatement[];
78
93
  cmd: CmdStatement[];
79
94
  };
80
95
  declare const parseFrameValue: (frame: unknown) => ParsedFrame;
@@ -95,8 +110,9 @@ type SocketHostState = {
95
110
  conns: Set<Conn>;
96
111
  pendingConns: Set<PendingConn>;
97
112
  pendingOps: WireOp[];
98
- pendingApplied: Map<string, number[]>;
99
- pendingCmds: Map<string, StatewireCmd[]>;
113
+ pendingApplied: Map<string, Cmd[]>;
114
+ pendingCmds: Map<string, Cmd[]>;
115
+ viewsDirty: boolean;
100
116
  idleWire: boolean;
101
117
  finished: StatewireHost.Fin | null;
102
118
  disposed: boolean;
@@ -122,26 +138,33 @@ type FrameAdmission = {
122
138
  type: "refused";
123
139
  syn: {
124
140
  syn: {
125
- seq: number;
126
- ctxSeq: number;
141
+ protocols: {
142
+ name: string;
143
+ seq: number;
144
+ }[];
127
145
  retry?: number;
128
146
  };
129
147
  };
130
148
  };
131
- declare const admitFrame: (h: SocketHostState, rec: ClientRec, frame: ParsedFrame, auth: unknown, selection?: StatewireProtocol.Selection) => FrameAdmission;
149
+ declare const admitFrame: (h: SocketHostState, conn: Conn, frame: ParsedFrame) => FrameAdmission;
132
150
  type Registration = {
133
- rec: ClientRec;
134
151
  conn: Conn;
135
152
  connection: StatewireHostInternal.SocketConnection;
136
153
  };
137
- declare const register: (h: SocketHostState, clientId: string, socket: StatewireHostInternal.Socket, sawFrame?: boolean, auth?: unknown, selection?: StatewireProtocol.Selection) => Registration | null;
154
+ /**
155
+ * Attach a socket: ephemeral-lane statements of the attach frame are admitted
156
+ * before the snapshot (their documents and answers ride the hello), durable
157
+ * ones after it. A violation in the attach frame closes the attach and throws.
158
+ */
159
+ declare const register: (h: SocketHostState, clientId: string, socket: StatewireHostInternal.Socket, sawFrame?: boolean, auth?: unknown, selection?: StatewireProtocol.Selection, frame?: ParsedFrame) => Registration | null;
138
160
  /**
139
161
  * Transport-agnostic WS binding over a statewire/host transport:
140
162
  * feed it sockets (anything with `send`/`close`) and raw text frames, and it
141
163
  * speaks the Statewire packet protocol — snapshot-first hello with `syn`,
142
- * statement-lane client frames under one admission law, `{cmd}` answer
143
- * arrays fanned out to every attach of a client id, idle edges, and `fin` on
144
- * finish.
164
+ * per-protocol statement lanes under one admission law, `{cmd}` answer
165
+ * arrays fanned out to every attach of a client id (durable lanes) or the
166
+ * admitting attach (ephemeral lanes), per-attach document views, idle edges,
167
+ * and `fin` on finish.
145
168
  */
146
169
  declare const StatewireSocketHost: (transport: ResourceElement<Statewire>, options?: StatewireHostInternal.SocketHostOptions, persistence?: StatewireHostInternal.SocketHostPersistence) => StatewireHostInternal.SocketHost;
147
170
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"socket.d.ts","names":[],"sources":["../../src/host/socket.ts"],"mappings":";;;;;;;;;;;cAiCa,cAAY;cACZ;;;cAKA,aAAc,YAAY;cAoC1B;KAGR;EACH;EACA,OAAO;EACP;EACA,OAAO,kBAAkB;;KAGtB;EACH;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA,SAAS;EACT,kBAAkB,mBAAmB;EACrC,SAAS;IAAc;IAAY,KAAK;;EACxC,UAAU,YAAY;;EAEtB;;KAGU;EACV,WAAW,kBAAkB;EAC7B;EACA,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA;;EAEA;;KAGG;EACH,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA,OAAO,kBAAkB;EACzB,YAAY,sBAAsB;;KAc/B;EAAiB;EAAa;;KAC9B;EACH;EACA;EACA;EACA;EACA;;KAGU;EACV,KAAK;EACL,KAAK;;cA0BM,kBAAmB,mBAAiB;cAoDpC,aAAc,gBAAc;KAiG7B;EACV,UAAU,kBAAkB;EAC5B,aAAa,kBAAkB;EAC/B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,WAAW,sBAAsB;EACjC,SAAS,YAAY;EACrB,OAAO,IAAI;EACX,cAAc,IAAI;EAClB,YAAY;EACZ,gBAAgB;EAChB,aAAa,YAAY;EACzB;EACA,UAAU,cAAc;EACxB;EACA;EACA,gBAAgB,kBAAkB;EAClC,WAAW,cAAc;EACzB,cAAc;EACd,SAAS;EACT,aAAa,sBAAsB;EACnC;EACA,UACE,kBACA,QAAQ,sBAAsB,QAC9B,UAAU,sBAAsB,yBAC7B,sBAAsB;EAC3B,SAAS,KAAK,cAAc;EAC5B,SAAS,SAAS,YAAY,QAAQ;EACtC,SAAS,SAAS,YAAY,QAAQ;;cA+C3B,YACX,GAAG,iBACH,MAAM,MACN,eACA;cAgBW,SAAU,GAAG,iBAAiB,MAAM,MAAM;cAU1C,oBAAqB,GAAG;cAkCxB,QAAS,GAAG,iBAAiB,MAAM,cAAc;KAsRlD;EACN;;EAEA;EACA;IACE;MAAO;MAAa;MAAgB;;;;cAI/B,aACX,GAAG,iBACH,KAAK,WACL,OAAO,aACP,eACA,YAAW,kBAAkB,cAI5B;KAsbS;EACV,KAAK;EACL,MAAM;EACN,YAAY,sBAAsB;;cAGvB,WACX,GAAG,iBACH,kBACA,QAAQ,sBAAsB,QAC9B,oBACA,gBACA,YAAW,kBAAkB,cAI5B;;;;;;;;;cA0QU,sBACX,WAAW,gBAAgB,YAC3B,UAAU,sBAAsB,mBAChC,cAAc,sBAAsB,0BACnC,sBAAsB"}
1
+ {"version":3,"file":"socket.d.ts","names":[],"sources":["../../src/host/socket.ts"],"mappings":";;;;;;;;;cA+Ba,cAAY;cACZ;;;cAKA,aAAc,YAAY;cAoC1B;KAGR,MAAM,sBAAsB;KAE5B;EACH;EACA;EACA,OAAO;EACP;EACA,OAAO,kBAAkB;EACzB;EACA;EACA;;KAGG;EACH;EACA;EACA;EACA;EACA;EACA;;;KAIG;EACH;EACA;EACA,MAAM;EACN;EACA,SAAS;IAAc;IAAY,KAAK;;EACxC,UAAU,YAAY;;KAGZ;EACV;EACA;EACA,OAAO,YAAY;;EAEnB;;KAIU;EACV,WAAW,kBAAkB;EAC7B;EACA,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA;EACA;;EAEA;EACA,OAAO,YAAY;EACnB,WAAW,YAAY,cAAc;EACrC,UAAU;EACV,MAAM;IAAc;IAAa;;EACjC;EACA,gBAAgB;EAChB,aAAa;EACb,QAAQ,cAAc;;KAGnB;EACH,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA,OAAO,kBAAkB;EACzB,YAAY,sBAAsB;;KAc/B;EACH;EACA;EACA;EACA;EACA;;KAGU;EAAgB,KAAK;;cAOpB,kBAAmB,mBAAiB;cA4CpC,aAAc,gBAAc;KAgG7B;EACV,UAAU,kBAAkB;EAC5B,aAAa,kBAAkB;EAC/B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,WAAW,sBAAsB;EACjC,SAAS,YAAY;EACrB,OAAO,IAAI;EACX,cAAc,IAAI;EAClB,YAAY;EACZ,gBAAgB,YAAY;EAC5B,aAAa,YAAY;EACzB;EACA;EACA,UAAU,cAAc;EACxB;EACA;EACA,gBAAgB,kBAAkB;EAClC,WAAW,cAAc;EACzB,cAAc;EACd,SAAS;EACT,aAAa,sBAAsB;EACnC;EACA,UACE,kBACA,QAAQ,sBAAsB,QAC9B,UAAU,sBAAsB,yBAC7B,sBAAsB;EAC3B,SAAS,KAAK,cAAc;EAC5B,SAAS,SAAS,YAAY,QAAQ;EACtC,SAAS,SAAS,YAAY,QAAQ;;cAsD3B,YACX,GAAG,iBACH,MAAM,MACN,eACA;cAiBW,SAAU,GAAG,iBAAiB,MAAM,MAAM;cAmB1C,oBAAqB,GAAG;cA+KxB,QAAS,GAAG,iBAAiB,MAAM,cAAc;KA0UlD;EACN;;EAEA;EACA;IACE;MAAO;QAAa;QAAc;;MAAiB;;;;cAqB9C,aACX,GAAG,iBACH,MAAM,MACN,OAAO,gBACN;KAwYS;EACV,MAAM;EACN,YAAY,sBAAsB;;;;;;;cAQvB,WACX,GAAG,iBACH,kBACA,QAAQ,sBAAsB,QAC9B,oBACA,gBACA,YAAW,kBAAkB,WAI7B,QAAO,gBACN;;;;;;;;;;cA4TU,sBACX,WAAW,gBAAgB,YAC3B,UAAU,sBAAsB,mBAChC,cAAc,sBAAsB,0BACnC,sBAAsB"}