statewire 0.0.0-0 → 0.0.1

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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -1
  3. package/dist/client/changes.d.ts +7 -0
  4. package/dist/client/changes.d.ts.map +1 -0
  5. package/dist/client/changes.js +32 -0
  6. package/dist/client/changes.js.map +1 -0
  7. package/dist/client/connection.d.ts +84 -0
  8. package/dist/client/connection.d.ts.map +1 -0
  9. package/dist/client/connection.js +204 -0
  10. package/dist/client/connection.js.map +1 -0
  11. package/dist/client/core.d.ts +18 -0
  12. package/dist/client/core.d.ts.map +1 -0
  13. package/dist/client/core.js +269 -0
  14. package/dist/client/core.js.map +1 -0
  15. package/dist/client/optimistic.d.ts +10 -0
  16. package/dist/client/optimistic.d.ts.map +1 -0
  17. package/dist/client/optimistic.js +128 -0
  18. package/dist/client/optimistic.js.map +1 -0
  19. package/dist/client/session.d.ts +88 -0
  20. package/dist/client/session.d.ts.map +1 -0
  21. package/dist/client/session.js +509 -0
  22. package/dist/client/session.js.map +1 -0
  23. package/dist/client/sessionStore.d.ts +16 -0
  24. package/dist/client/sessionStore.d.ts.map +1 -0
  25. package/dist/client/sessionStore.js +14 -0
  26. package/dist/client/sessionStore.js.map +1 -0
  27. package/dist/client/timing.d.ts +10 -0
  28. package/dist/client/timing.d.ts.map +1 -0
  29. package/dist/client/timing.js +11 -0
  30. package/dist/client/timing.js.map +1 -0
  31. package/dist/host/draft.d.ts +14 -0
  32. package/dist/host/draft.d.ts.map +1 -0
  33. package/dist/host/draft.js +124 -0
  34. package/dist/host/draft.js.map +1 -0
  35. package/dist/host/engine.d.ts +14 -0
  36. package/dist/host/engine.d.ts.map +1 -0
  37. package/dist/host/engine.js +17 -0
  38. package/dist/host/engine.js.map +1 -0
  39. package/dist/host/host.d.ts +79 -0
  40. package/dist/host/host.d.ts.map +1 -0
  41. package/dist/host/host.js +303 -0
  42. package/dist/host/host.js.map +1 -0
  43. package/dist/index.d.ts +28 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +6 -0
  46. package/dist/server.d.ts +15 -0
  47. package/dist/server.d.ts.map +1 -0
  48. package/dist/server.js +3 -0
  49. package/dist/statewire.d.ts +20 -0
  50. package/dist/statewire.d.ts.map +1 -0
  51. package/dist/statewire.js +65 -0
  52. package/dist/statewire.js.map +1 -0
  53. package/dist/transports/remote.d.ts +19 -0
  54. package/dist/transports/remote.d.ts.map +1 -0
  55. package/dist/transports/remote.js +40 -0
  56. package/dist/transports/remote.js.map +1 -0
  57. package/dist/transports/sse-parser.d.ts +5 -0
  58. package/dist/transports/sse-parser.d.ts.map +1 -0
  59. package/dist/transports/sse-parser.js +26 -0
  60. package/dist/transports/sse-parser.js.map +1 -0
  61. package/dist/transports/sse.d.ts +74 -0
  62. package/dist/transports/sse.d.ts.map +1 -0
  63. package/dist/transports/sse.js +136 -0
  64. package/dist/transports/sse.js.map +1 -0
  65. package/dist/transports/testing.d.ts +31 -0
  66. package/dist/transports/testing.d.ts.map +1 -0
  67. package/dist/transports/testing.js +121 -0
  68. package/dist/transports/testing.js.map +1 -0
  69. package/dist/transports/ws.d.ts +18 -0
  70. package/dist/transports/ws.d.ts.map +1 -0
  71. package/dist/transports/ws.js +172 -0
  72. package/dist/transports/ws.js.map +1 -0
  73. package/dist/types.d.ts +107 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/types.js +24 -0
  76. package/dist/types.js.map +1 -0
  77. package/dist/wire/meta.d.ts +22 -0
  78. package/dist/wire/meta.d.ts.map +1 -0
  79. package/dist/wire/meta.js +73 -0
  80. package/dist/wire/meta.js.map +1 -0
  81. package/dist/wire/ops.d.ts +40 -0
  82. package/dist/wire/ops.d.ts.map +1 -0
  83. package/dist/wire/ops.js +125 -0
  84. package/dist/wire/ops.js.map +1 -0
  85. package/package.json +49 -6
  86. package/src/client/changes.ts +48 -0
  87. package/src/client/connection.ts +327 -0
  88. package/src/client/core.ts +346 -0
  89. package/src/client/optimistic.ts +0 -0
  90. package/src/client/session.ts +679 -0
  91. package/src/client/sessionStore.ts +23 -0
  92. package/src/client/timing.ts +8 -0
  93. package/src/host/draft.ts +155 -0
  94. package/src/host/engine.ts +13 -0
  95. package/src/host/host.ts +481 -0
  96. package/src/index.ts +65 -0
  97. package/src/server.ts +32 -0
  98. package/src/statewire.ts +107 -0
  99. package/src/transports/remote.ts +72 -0
  100. package/src/transports/sse-parser.ts +24 -0
  101. package/src/transports/sse.ts +280 -0
  102. package/src/transports/testing.ts +155 -0
  103. package/src/transports/ws.ts +272 -0
  104. package/src/types.ts +169 -0
  105. package/src/wire/meta.ts +106 -0
  106. package/src/wire/ops.ts +201 -0
  107. package/index.js +0 -1
@@ -0,0 +1,269 @@
1
+ import { createChanges } from "./changes.js";
2
+ import { recordMutations } from "./optimistic.js";
3
+ import { StatewireSendError } from "../types.js";
4
+ import { getCommandsHost, getEmitHost, getStateHost } from "../host/host.js";
5
+ //#region src/client/core.ts
6
+ const LIVE_CONNECTION = {
7
+ status: "live",
8
+ reconnect: () => {}
9
+ };
10
+ const failureOf = (error) => error instanceof StatewireSendError ? error.failure : {
11
+ fate: "rejected",
12
+ cause: "server",
13
+ message: error instanceof Error ? error.message : String(error)
14
+ };
15
+ const createMacrotaskPoster = (callback) => {
16
+ if (typeof MessageChannel !== "undefined") {
17
+ let port1 = null;
18
+ let port2;
19
+ return () => {
20
+ if (!port1) {
21
+ const channel = new MessageChannel();
22
+ channel.port1.onmessage = () => {
23
+ port1?.unref?.();
24
+ callback();
25
+ };
26
+ port1 = channel.port1;
27
+ port2 = channel.port2;
28
+ }
29
+ port1.ref?.();
30
+ port2.postMessage(null);
31
+ };
32
+ }
33
+ return () => setTimeout(callback, 0);
34
+ };
35
+ const createStatewireCore = (getOptions) => {
36
+ let value = null;
37
+ let source = null;
38
+ let overlays = [];
39
+ let updates = [];
40
+ let events = [];
41
+ let pendingChanged = [];
42
+ let canonical;
43
+ let published;
44
+ let overlayPaths = [];
45
+ let notifiedPublished;
46
+ let notifiedOverlayPaths = [];
47
+ let notifiedConnection = null;
48
+ let stale = false;
49
+ let overlaysDirty = false;
50
+ let flushing = false;
51
+ let disposed = false;
52
+ const listeners = /* @__PURE__ */ new Set();
53
+ const post = createMacrotaskPoster(() => {
54
+ scheduled = false;
55
+ flush();
56
+ });
57
+ let scheduled = false;
58
+ const schedule = () => {
59
+ if (scheduled || disposed) return;
60
+ scheduled = true;
61
+ post();
62
+ };
63
+ const markStale = () => {
64
+ stale = true;
65
+ schedule();
66
+ };
67
+ const drainCanonical = () => {
68
+ if (source) {
69
+ pendingChanged.push(...source.takeChanged());
70
+ canonical = source.root();
71
+ } else if (value && value.state !== canonical) {
72
+ pendingChanged.push([]);
73
+ canonical = value.state;
74
+ }
75
+ };
76
+ const recompute = () => {
77
+ if (!stale || !value) return;
78
+ stale = false;
79
+ const prevCanonical = canonical;
80
+ drainCanonical();
81
+ if (canonical === prevCanonical && !overlaysDirty) return;
82
+ overlaysDirty = false;
83
+ const { optimistic } = getOptions();
84
+ let next = canonical;
85
+ let paths = [];
86
+ if (optimistic && canonical !== void 0) for (const overlay of overlays) {
87
+ if (overlay.dropped) continue;
88
+ const { inv } = overlay;
89
+ const ctx = {
90
+ ts: inv.ts,
91
+ seq: inv.seq,
92
+ ...inv.clientId !== void 0 && { clientId: inv.clientId }
93
+ };
94
+ try {
95
+ const recorded = recordMutations(next, (draft) => optimistic(draft, {
96
+ method: inv.method,
97
+ params: inv.params
98
+ }, ctx));
99
+ next = recorded.state;
100
+ paths = [...paths, ...recorded.changedPaths];
101
+ } catch (error) {
102
+ overlay.dropped = true;
103
+ console.error(`statewire: optimistic reducer threw for "${inv.method}"; dropping its optimistic update`, error);
104
+ }
105
+ }
106
+ published = next;
107
+ overlayPaths = paths;
108
+ };
109
+ const getConnection = () => value?.connection ?? LIVE_CONNECTION;
110
+ const flush = () => {
111
+ if (disposed || !value) return;
112
+ if (flushing) {
113
+ schedule();
114
+ return;
115
+ }
116
+ flushing = true;
117
+ try {
118
+ recompute();
119
+ const options = getOptions();
120
+ const stateChanged = published !== notifiedPublished;
121
+ if (stateChanged) {
122
+ const prev = notifiedPublished;
123
+ const changed = [
124
+ ...pendingChanged,
125
+ ...notifiedOverlayPaths,
126
+ ...overlayPaths
127
+ ];
128
+ pendingChanged = [];
129
+ notifiedPublished = published;
130
+ notifiedOverlayPaths = overlayPaths;
131
+ options.onStateChange?.(published, { changes: createChanges(prev, published, changed) });
132
+ }
133
+ if (updates.length > 0 && published !== void 0) {
134
+ const batch = updates;
135
+ updates = [];
136
+ for (const update of batch) options.onCommandChange?.(update, { state: published });
137
+ }
138
+ if (events.length > 0) {
139
+ const batch = events;
140
+ events = [];
141
+ for (const event of batch) options.onEvent?.(event, { state: published });
142
+ }
143
+ const connection = getConnection();
144
+ const connectionChanged = connection !== notifiedConnection;
145
+ notifiedConnection = connection;
146
+ if (stateChanged || connectionChanged) for (const listener of [...listeners]) listener();
147
+ } finally {
148
+ flushing = false;
149
+ }
150
+ };
151
+ const note = (inv, status, extra) => {
152
+ if (!getOptions().onCommandChange) return;
153
+ updates.push({
154
+ method: inv.method,
155
+ params: inv.params,
156
+ seq: inv.seq,
157
+ status,
158
+ ...extra
159
+ });
160
+ schedule();
161
+ };
162
+ const commandsListener = {
163
+ onStart: (inv) => {
164
+ overlays.push({
165
+ inv,
166
+ dropped: false
167
+ });
168
+ overlaysDirty = true;
169
+ markStale();
170
+ note(inv, "queued");
171
+ },
172
+ onPosted: (inv) => note(inv, "posted"),
173
+ onAck: (inv) => note(inv, "acked"),
174
+ onCovered: (inv) => {
175
+ if (!overlays.some((o) => o.inv === inv)) return;
176
+ overlays = overlays.filter((o) => o.inv !== inv);
177
+ overlaysDirty = true;
178
+ markStale();
179
+ },
180
+ onSettle: (inv, outcome) => {
181
+ note(inv, "settled", outcome.type === "accepted" ? { result: outcome.payload } : { failure: failureOf(outcome.error) });
182
+ flush();
183
+ }
184
+ };
185
+ const eventSink = (event) => {
186
+ if (!getOptions().onEvent) return;
187
+ events.push(event);
188
+ schedule();
189
+ };
190
+ const attach = (next) => {
191
+ if (disposed || next === value) return;
192
+ const prev = value;
193
+ value = next;
194
+ const nextSource = next.source ?? (typeof next.state === "object" && next.state !== null ? getStateHost(next.state) : void 0) ?? null;
195
+ if (nextSource !== source) {
196
+ source?.setOnChange(null);
197
+ source = nextSource;
198
+ source?.setOnChange(markStale);
199
+ markStale();
200
+ }
201
+ if (next.commands !== prev?.commands) {
202
+ if (prev) getCommandsHost(prev.commands)?.setListener(null);
203
+ getCommandsHost(next.commands)?.setListener(commandsListener);
204
+ if (overlays.length > 0) {
205
+ overlays = [];
206
+ overlaysDirty = true;
207
+ }
208
+ markStale();
209
+ }
210
+ if (next.emit !== prev?.emit) {
211
+ if (prev) getEmitHost(prev.emit)?.setSink(null);
212
+ getEmitHost(next.emit)?.setSink(eventSink);
213
+ }
214
+ if (!source) markStale();
215
+ schedule();
216
+ };
217
+ const detach = () => {
218
+ source?.setOnChange(null);
219
+ source = null;
220
+ if (value) {
221
+ getCommandsHost(value.commands)?.setListener(null);
222
+ getEmitHost(value.emit)?.setSink(null);
223
+ value = null;
224
+ }
225
+ };
226
+ const memo = /* @__PURE__ */ new Map();
227
+ return {
228
+ attach,
229
+ detach,
230
+ dispose: () => {
231
+ detach();
232
+ disposed = true;
233
+ },
234
+ getState: () => {
235
+ recompute();
236
+ return published;
237
+ },
238
+ getConnection,
239
+ commands: new Proxy({}, { get: (_target, method) => {
240
+ if (typeof method !== "string") return void 0;
241
+ let fn = memo.get(method);
242
+ if (fn === void 0) {
243
+ fn = (...params) => {
244
+ if (!value) return Promise.reject(new StatewireSendError({
245
+ fate: "aborted",
246
+ cause: "unmount"
247
+ }));
248
+ const send = value.commands[method];
249
+ const promise = send(...params);
250
+ flush();
251
+ return promise;
252
+ };
253
+ memo.set(method, fn);
254
+ }
255
+ return fn;
256
+ } }),
257
+ flush,
258
+ subscribe: (listener) => {
259
+ listeners.add(listener);
260
+ return () => {
261
+ listeners.delete(listener);
262
+ };
263
+ }
264
+ };
265
+ };
266
+ //#endregion
267
+ export { LIVE_CONNECTION, createStatewireCore };
268
+
269
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","names":["createChanges","getCommandsHost","getEmitHost","getStateHost","CommandsInvocation","CommandsListener","recordMutations","StatewirePath","StatewireSendError","SendFailure","StatewireSendFailure","StatewireStateSource","StatewireTransport","StatewireCommand","StatewireCommandsProxy","StatewireCommandUpdate","StatewireOptimisticContext","StatewireOptions","StatewireConnection","LIVE_CONNECTION","status","reconnect","failureOf","error","failure","fate","cause","message","Error","String","createMacrotaskPoster","callback","MessageChannel","port1","MessagePort","ref","unref","port2","channel","onmessage","postMessage","setTimeout","Overlay","inv","dropped","StatewireCore","ReturnType","createStatewireCore","getOptions","value","source","overlays","updates","events","pendingChanged","canonical","published","overlayPaths","notifiedPublished","notifiedOverlayPaths","notifiedConnection","stale","overlaysDirty","flushing","disposed","listeners","Set","post","scheduled","flush","schedule","markStale","drainCanonical","push","takeChanged","root","state","recompute","prevCanonical","optimistic","next","paths","undefined","overlay","ctx","ts","seq","clientId","recorded","draft","method","params","changedPaths","console","getConnection","connection","options","stateChanged","prev","changed","onStateChange","changes","length","batch","update","onCommandChange","event","onEvent","connectionChanged","listener","note","extra","result","commandsListener","onStart","onPosted","onAck","onCovered","some","o","filter","onSettle","outcome","type","payload","eventSink","attach","nextSource","setOnChange","commands","setListener","emit","setSink","detach","memo","Map","Promise","Proxy","get","_target","fn","reject","send","Record","args","promise","set","dispose","getState","subscribe","add","delete"],"sources":["../../src/client/core.ts"],"sourcesContent":["import { createChanges } from \"./changes\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport { recordMutations } from \"./optimistic\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireSendError } from \"../types\";\nimport type {\n SendFailure as StatewireSendFailure,\n StatewireStateSource,\n StatewireTransport,\n StatewireCommand,\n StatewireCommandsProxy,\n StatewireCommandUpdate,\n StatewireOptimisticContext,\n StatewireOptions,\n} from \"../types\";\nimport type { StatewireConnection } from \"../transports/sse\";\n\nexport const LIVE_CONNECTION: StatewireConnection = {\n status: \"live\",\n reconnect: () => {},\n};\n\nconst failureOf = (error: unknown): StatewireSendFailure =>\n error instanceof StatewireSendError\n ? error.failure\n : {\n fate: \"rejected\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n };\n\n// MessageChannel schedules the flush as a macrotask (tap's pattern): it runs\n// after the whole microtask drain, so every producer in the current turn\n// coalesces. The port is ref'd only while a flush is pending — an active\n// MessagePort holds the Node event loop open. ref/unref are Node-only.\nconst createMacrotaskPoster = (callback: () => void): (() => void) => {\n if (typeof MessageChannel !== \"undefined\") {\n let port1: (MessagePort & { ref?: () => void; unref?: () => void }) | null =\n null;\n let port2: MessagePort;\n return () => {\n if (!port1) {\n const channel = new MessageChannel();\n channel.port1.onmessage = () => {\n port1?.unref?.();\n callback();\n };\n port1 = channel.port1;\n port2 = channel.port2;\n }\n port1.ref?.();\n port2!.postMessage(null);\n };\n }\n return () => setTimeout(callback, 0);\n};\n\ntype Overlay = { inv: CommandsInvocation; dropped: boolean };\n\nexport type StatewireCore = ReturnType<typeof createStatewireCore>;\n\nexport const createStatewireCore = (\n getOptions: () => StatewireOptions<any, any, any>,\n) => {\n let value: StatewireTransport<unknown> | null = null;\n let source: StatewireStateSource | null = null;\n let overlays: Overlay[] = [];\n let updates: StatewireCommandUpdate[] = [];\n let events: unknown[] = [];\n let pendingChanged: StatewirePath[] = [];\n let canonical: unknown;\n let published: unknown;\n let overlayPaths: readonly StatewirePath[] = [];\n let notifiedPublished: unknown;\n let notifiedOverlayPaths: readonly StatewirePath[] = [];\n let notifiedConnection: StatewireConnection | null = null;\n let stale = false;\n let overlaysDirty = false;\n let flushing = false;\n let disposed = false;\n const listeners = new Set<() => void>();\n\n const post = createMacrotaskPoster(() => {\n scheduled = false;\n flush();\n });\n let scheduled = false;\n const schedule = () => {\n if (scheduled || disposed) return;\n scheduled = true;\n post();\n };\n\n const markStale = () => {\n stale = true;\n schedule();\n };\n\n const drainCanonical = () => {\n if (source) {\n pendingChanged.push(...source.takeChanged());\n canonical = source.root();\n } else if (value && value.state !== canonical) {\n pendingChanged.push([]);\n canonical = value.state;\n }\n };\n\n const recompute = () => {\n if (!stale || !value) return;\n stale = false;\n const prevCanonical = canonical;\n drainCanonical();\n if (canonical === prevCanonical && !overlaysDirty) return;\n overlaysDirty = false;\n const { optimistic } = getOptions();\n let next = canonical;\n let paths: readonly StatewirePath[] = [];\n if (optimistic && canonical !== undefined) {\n for (const overlay of overlays) {\n if (overlay.dropped) continue;\n const { inv } = overlay;\n const ctx: StatewireOptimisticContext = {\n ts: inv.ts,\n seq: inv.seq,\n ...(inv.clientId !== undefined && { clientId: inv.clientId }),\n };\n try {\n const recorded = recordMutations(next, (draft) =>\n optimistic(\n draft as never,\n { method: inv.method, params: inv.params } as StatewireCommand,\n ctx,\n ),\n );\n next = recorded.state;\n paths = [...paths, ...recorded.changedPaths];\n } catch (error) {\n overlay.dropped = true;\n console.error(\n `statewire: optimistic reducer threw for \"${inv.method}\"; dropping its optimistic update`,\n error,\n );\n }\n }\n }\n published = next;\n overlayPaths = paths;\n };\n\n const getConnection = (): StatewireConnection =>\n value?.connection ?? LIVE_CONNECTION;\n\n const flush = () => {\n if (disposed || !value) return;\n if (flushing) {\n schedule();\n return;\n }\n flushing = true;\n try {\n recompute();\n const options = getOptions();\n const stateChanged = published !== notifiedPublished;\n if (stateChanged) {\n const prev = notifiedPublished;\n const changed = [\n ...pendingChanged,\n ...notifiedOverlayPaths,\n ...overlayPaths,\n ];\n pendingChanged = [];\n notifiedPublished = published;\n notifiedOverlayPaths = overlayPaths;\n options.onStateChange?.(published as never, {\n changes: createChanges(prev, published, changed),\n });\n }\n if (updates.length > 0 && published !== undefined) {\n const batch = updates;\n updates = [];\n for (const update of batch) {\n options.onCommandChange?.(update, { state: published as never });\n }\n }\n if (events.length > 0) {\n const batch = events;\n events = [];\n for (const event of batch) {\n options.onEvent?.(event, { state: published as never });\n }\n }\n const connection = getConnection();\n const connectionChanged = connection !== notifiedConnection;\n notifiedConnection = connection;\n if (stateChanged || connectionChanged) {\n for (const listener of [...listeners]) listener();\n }\n } finally {\n flushing = false;\n }\n };\n\n const note = (\n inv: CommandsInvocation,\n status: StatewireCommandUpdate[\"status\"],\n extra?: { result?: unknown; failure?: StatewireSendFailure },\n ) => {\n if (!getOptions().onCommandChange) return;\n updates.push({\n method: inv.method,\n params: inv.params as never[],\n seq: inv.seq,\n status,\n ...extra,\n });\n schedule();\n };\n\n const commandsListener: CommandsListener = {\n onStart: (inv) => {\n overlays.push({ inv, dropped: false });\n overlaysDirty = true;\n markStale();\n note(inv, \"queued\");\n },\n onPosted: (inv) => note(inv, \"posted\"),\n onAck: (inv) => note(inv, \"acked\"),\n onCovered: (inv) => {\n if (!overlays.some((o) => o.inv === inv)) return;\n overlays = overlays.filter((o) => o.inv !== inv);\n overlaysDirty = true;\n markStale();\n },\n onSettle: (inv, outcome) => {\n note(\n inv,\n \"settled\",\n outcome.type === \"accepted\"\n ? { result: outcome.payload }\n : { failure: failureOf(outcome.error) },\n );\n flush();\n },\n };\n\n const eventSink = (event: unknown) => {\n if (!getOptions().onEvent) return;\n events.push(event);\n schedule();\n };\n\n const attach = (next: StatewireTransport<unknown>) => {\n if (disposed || next === value) return;\n const prev = value;\n value = next;\n const nextSource =\n next.source ??\n (typeof next.state === \"object\" && next.state !== null\n ? getStateHost(next.state)\n : undefined) ??\n null;\n if (nextSource !== source) {\n source?.setOnChange(null);\n source = nextSource;\n source?.setOnChange(markStale);\n markStale();\n }\n if (next.commands !== prev?.commands) {\n if (prev) getCommandsHost(prev.commands)?.setListener(null);\n getCommandsHost(next.commands)?.setListener(commandsListener);\n if (overlays.length > 0) {\n overlays = [];\n overlaysDirty = true;\n }\n markStale();\n }\n if (next.emit !== prev?.emit) {\n if (prev) getEmitHost(prev.emit)?.setSink(null);\n getEmitHost(next.emit)?.setSink(eventSink);\n }\n if (!source) markStale();\n schedule();\n };\n\n const detach = () => {\n source?.setOnChange(null);\n source = null;\n if (value) {\n getCommandsHost(value.commands)?.setListener(null);\n getEmitHost(value.emit)?.setSink(null);\n value = null;\n }\n };\n\n const memo = new Map<string, (...params: unknown[]) => Promise<unknown>>();\n const commands = new Proxy({} as StatewireCommandsProxy<any>, {\n get: (_target, method) => {\n if (typeof method !== \"string\") return undefined;\n let fn = memo.get(method);\n if (fn === undefined) {\n fn = (...params: unknown[]) => {\n if (!value) {\n return Promise.reject(\n new StatewireSendError({ fate: \"aborted\", cause: \"unmount\" }),\n );\n }\n const send = (\n value.commands as Record<\n string,\n (...args: unknown[]) => Promise<unknown>\n >\n )[method]!;\n const promise = send(...params);\n flush();\n return promise;\n };\n memo.set(method, fn);\n }\n return fn;\n },\n });\n\n return {\n attach,\n detach,\n dispose: () => {\n detach();\n disposed = true;\n },\n getState: () => {\n recompute();\n return published;\n },\n getConnection,\n commands,\n flush,\n subscribe: (listener: () => void) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n};\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ //#region src/client/optimistic.d.ts
2
+ type PathKey = string | number;
3
+ type Path = readonly PathKey[];
4
+ declare const recordMutations: <T>(base: T, mutate: (draft: T) => void) => {
5
+ state: T;
6
+ changedPaths: Path[];
7
+ };
8
+ //#endregion
9
+ export { recordMutations };
10
+ //# sourceMappingURL=optimistic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimistic.d.ts","names":[],"sources":["../../src/client/optimistic.ts"],"mappings":";KAAK;KACA,gBAAgB;cAuER,kBAAmB,GAC9B,MAAM,GACN,SAAS,OAAO;EACb,OAAO;EAAG,cAAc"}
@@ -0,0 +1,128 @@
1
+ //#region src/client/optimistic.ts
2
+ const DRAFT = Symbol("statewire.draft");
3
+ const isDraftable = (value) => {
4
+ if (typeof value !== "object" || value === null) return false;
5
+ if (Array.isArray(value)) return true;
6
+ const proto = Object.getPrototypeOf(value);
7
+ return proto === Object.prototype || proto === null;
8
+ };
9
+ const nodeOf = (value) => typeof value === "object" && value !== null ? value[DRAFT] : void 0;
10
+ const latest = (node) => node.copy ?? node.base;
11
+ const prepareCopy = (node) => {
12
+ node.copy ??= node.isArray ? node.base.slice() : { ...node.base };
13
+ };
14
+ const markModified = (node) => {
15
+ for (let n = node; n && !n.modified; n = n.parent) n.modified = true;
16
+ };
17
+ const resolveAssigned = (container) => {
18
+ for (const key of Object.keys(container)) {
19
+ const value = container[key];
20
+ const child = nodeOf(value);
21
+ if (child) container[key] = finalize(child);
22
+ else if (isDraftable(value)) resolveAssigned(value);
23
+ }
24
+ };
25
+ const finalize = (node) => {
26
+ if (!node.modified) return node.base;
27
+ const result = node.copy;
28
+ for (const key of Object.keys(result)) {
29
+ const value = result[key];
30
+ const child = nodeOf(value);
31
+ if (child) result[key] = finalize(child);
32
+ else if (node.assigned?.has(key) && isDraftable(value)) resolveAssigned(value);
33
+ }
34
+ return result;
35
+ };
36
+ const recordMutations = (base, mutate) => {
37
+ if (!isDraftable(base)) return {
38
+ state: base,
39
+ changedPaths: []
40
+ };
41
+ const changedPaths = [];
42
+ const seen = /* @__PURE__ */ new Set();
43
+ const record = (path) => {
44
+ const key = path.join("\0");
45
+ if (seen.has(key)) return;
46
+ seen.add(key);
47
+ changedPaths.push(path);
48
+ };
49
+ const createNode = (value, parent, path) => {
50
+ const node = {
51
+ base: value,
52
+ copy: null,
53
+ modified: false,
54
+ isArray: Array.isArray(value),
55
+ parent,
56
+ path,
57
+ proxy: null,
58
+ assigned: null
59
+ };
60
+ const touched = (prop) => {
61
+ if (typeof prop !== "string") return;
62
+ record(node.isArray ? node.path : [...node.path, prop]);
63
+ };
64
+ node.proxy = new Proxy(value, {
65
+ get: (_target, prop) => {
66
+ if (prop === DRAFT) return node;
67
+ const current = latest(node)[prop];
68
+ if (typeof prop === "symbol") return current;
69
+ if (nodeOf(current)) return current;
70
+ if (isDraftable(current) && current === node.base[prop]) {
71
+ prepareCopy(node);
72
+ const child = createNode(current, node, [...path, node.isArray ? Number(prop) : prop]);
73
+ node.copy[prop] = child.proxy;
74
+ return child.proxy;
75
+ }
76
+ return current;
77
+ },
78
+ set: (_target, prop, next) => {
79
+ const source = latest(node);
80
+ if (Object.hasOwn(source, prop) && source[prop] === next) return true;
81
+ prepareCopy(node);
82
+ markModified(node);
83
+ touched(prop);
84
+ node.copy[prop] = next;
85
+ (node.assigned ??= /* @__PURE__ */ new Set()).add(prop);
86
+ return true;
87
+ },
88
+ deleteProperty: (_target, prop) => {
89
+ if (!Object.hasOwn(latest(node), prop)) return true;
90
+ prepareCopy(node);
91
+ markModified(node);
92
+ touched(prop);
93
+ delete node.copy[prop];
94
+ return true;
95
+ },
96
+ defineProperty: () => {
97
+ throw new Error("statewire: defineProperty is not supported inside a mutator");
98
+ },
99
+ setPrototypeOf: () => {
100
+ throw new Error("statewire: setPrototypeOf is not supported inside a mutator");
101
+ },
102
+ preventExtensions: () => {
103
+ throw new Error("statewire: preventExtensions is not supported inside a mutator");
104
+ },
105
+ has: (_target, prop) => prop === DRAFT || Reflect.has(latest(node), prop),
106
+ ownKeys: () => Reflect.ownKeys(latest(node)),
107
+ getOwnPropertyDescriptor: (_target, prop) => {
108
+ const desc = Reflect.getOwnPropertyDescriptor(latest(node), prop);
109
+ if (!desc) return void 0;
110
+ return {
111
+ ...desc,
112
+ configurable: !node.isArray || prop !== "length"
113
+ };
114
+ }
115
+ });
116
+ return node;
117
+ };
118
+ const root = createNode(base, null, []);
119
+ mutate(root.proxy);
120
+ return {
121
+ state: finalize(root),
122
+ changedPaths
123
+ };
124
+ };
125
+ //#endregion
126
+ export { recordMutations };
127
+
128
+ //# sourceMappingURL=optimistic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimistic.js","names":["PathKey","Path","DRAFT","Symbol","Node","base","Record","PropertyKey","copy","modified","isArray","parent","path","proxy","assigned","Set","isDraftable","value","Array","proto","Object","getPrototypeOf","prototype","nodeOf","undefined","latest","node","prepareCopy","slice","markModified","n","resolveAssigned","container","key","keys","child","finalize","result","has","recordMutations","T","mutate","draft","state","changedPaths","seen","record","join","add","push","createNode","touched","prop","Proxy","get","_target","current","Number","set","next","source","hasOwn","deleteProperty","defineProperty","Error","setPrototypeOf","preventExtensions","Reflect","ownKeys","getOwnPropertyDescriptor","desc","configurable","root"],"sources":["../../src/client/optimistic.ts"],"sourcesContent":["type PathKey = string | number;\ntype Path = readonly PathKey[];\n\nconst DRAFT = Symbol(\"statewire.draft\");\n\ntype Node = {\n base: Record<PropertyKey, unknown>;\n copy: Record<PropertyKey, unknown> | null;\n modified: boolean;\n isArray: boolean;\n parent: Node | null;\n path: Path;\n proxy: unknown;\n assigned: Set<PropertyKey> | null;\n};\n\nconst isDraftable = (value: unknown): value is object => {\n if (typeof value !== \"object\" || value === null) return false;\n if (Array.isArray(value)) return true;\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n};\n\nconst nodeOf = (value: unknown): Node | undefined =>\n typeof value === \"object\" && value !== null\n ? (value as { [DRAFT]?: Node })[DRAFT]\n : undefined;\n\nconst latest = (node: Node) => node.copy ?? node.base;\n\nconst prepareCopy = (node: Node) => {\n node.copy ??= node.isArray\n ? ((node.base as unknown as unknown[]).slice() as unknown as Record<\n PropertyKey,\n unknown\n >)\n : { ...node.base };\n};\n\nconst markModified = (node: Node) => {\n for (let n: Node | null = node; n && !n.modified; n = n.parent) {\n n.modified = true;\n }\n};\n\nconst resolveAssigned = (container: object) => {\n for (const key of Object.keys(container)) {\n const value = (container as Record<string, unknown>)[key];\n const child = nodeOf(value);\n if (child) {\n (container as Record<string, unknown>)[key] = finalize(child);\n } else if (isDraftable(value)) {\n resolveAssigned(value);\n }\n }\n};\n\nconst finalize = (node: Node): unknown => {\n if (!node.modified) return node.base;\n const result = node.copy!;\n for (const key of Object.keys(result)) {\n const value = result[key];\n const child = nodeOf(value);\n if (child) {\n result[key] = finalize(child);\n } else if (node.assigned?.has(key) && isDraftable(value)) {\n resolveAssigned(value);\n }\n }\n return result;\n};\n\nexport const recordMutations = <T>(\n base: T,\n mutate: (draft: T) => void,\n): { state: T; changedPaths: Path[] } => {\n if (!isDraftable(base)) return { state: base, changedPaths: [] };\n\n const changedPaths: Path[] = [];\n const seen = new Set<string>();\n const record = (path: Path) => {\n const key = path.join(\"\u0000\");\n if (seen.has(key)) return;\n seen.add(key);\n changedPaths.push(path);\n };\n\n const createNode = (value: object, parent: Node | null, path: Path): Node => {\n const node: Node = {\n base: value as Record<PropertyKey, unknown>,\n copy: null,\n modified: false,\n isArray: Array.isArray(value),\n parent,\n path,\n proxy: null,\n assigned: null,\n };\n const touched = (prop: PropertyKey) => {\n if (typeof prop !== \"string\") return;\n record(node.isArray ? node.path : [...node.path, prop]);\n };\n node.proxy = new Proxy(value, {\n get: (_target, prop) => {\n if (prop === DRAFT) return node;\n const current = latest(node)[prop];\n if (typeof prop === \"symbol\") return current;\n if (nodeOf(current)) return current;\n if (isDraftable(current) && current === node.base[prop]) {\n prepareCopy(node);\n const child = createNode(current, node, [\n ...path,\n node.isArray ? Number(prop) : prop,\n ]);\n node.copy![prop] = child.proxy;\n return child.proxy;\n }\n return current;\n },\n set: (_target, prop, next) => {\n const source = latest(node);\n if (Object.hasOwn(source, prop) && source[prop] === next) return true;\n prepareCopy(node);\n markModified(node);\n touched(prop);\n node.copy![prop] = next;\n (node.assigned ??= new Set()).add(prop);\n return true;\n },\n deleteProperty: (_target, prop) => {\n if (!Object.hasOwn(latest(node), prop)) return true;\n prepareCopy(node);\n markModified(node);\n touched(prop);\n delete node.copy![prop];\n return true;\n },\n defineProperty: () => {\n throw new Error(\n \"statewire: defineProperty is not supported inside a mutator\",\n );\n },\n setPrototypeOf: () => {\n throw new Error(\n \"statewire: setPrototypeOf is not supported inside a mutator\",\n );\n },\n preventExtensions: () => {\n throw new Error(\n \"statewire: preventExtensions is not supported inside a mutator\",\n );\n },\n has: (_target, prop) => prop === DRAFT || Reflect.has(latest(node), prop),\n ownKeys: () => Reflect.ownKeys(latest(node)),\n getOwnPropertyDescriptor: (_target, prop) => {\n const desc = Reflect.getOwnPropertyDescriptor(latest(node), prop);\n if (!desc) return undefined;\n return {\n ...desc,\n configurable: !node.isArray || prop !== \"length\",\n };\n },\n });\n return node;\n };\n\n const root = createNode(base as unknown as object, null, []);\n mutate(root.proxy as T);\n return { state: finalize(root) as T, changedPaths };\n};\n"],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { StatewirePath } from "../wire/ops.js";
2
+ import { StatewireResponse } from "./connection.js";
3
+ import { StatewireSessionRecord, StatewireSessionStore } from "./sessionStore.js";
4
+ import { RemoteLink, StatewireWireCommand } from "../transports/sse.js";
5
+ import { StatewireSendError, StatewireStateSource } from "../types.js";
6
+ import { CommandsInvocation, CommandsListener } from "../host/host.js";
7
+ //#region src/client/session.d.ts
8
+ type SessionOptions = {
9
+ sessionStore?: StatewireSessionStore | undefined;
10
+ };
11
+ /**
12
+ * queued/trying/posted: overlay live, result pending. awaiting-result: acked
13
+ * (overlay gone) but the result rides a late res; `posted: false` re-sends.
14
+ * settled-live: result settled but the overlay stays until the covering ack.
15
+ * done: removed from the queue.
16
+ */
17
+ type PendingState = {
18
+ at: "queued";
19
+ pendingMarked: boolean;
20
+ } | {
21
+ at: "trying";
22
+ pendingMarked: boolean;
23
+ failedAttempts: number;
24
+ } | {
25
+ at: "posted";
26
+ pendingMarked: boolean;
27
+ failedAttempts: number;
28
+ } | {
29
+ at: "awaiting-result";
30
+ posted: boolean;
31
+ failedAttempts: number;
32
+ } | {
33
+ at: "settled-live";
34
+ posted: boolean;
35
+ failedAttempts: number;
36
+ } | {
37
+ at: "done";
38
+ };
39
+ type Pending = {
40
+ readonly seq: number;
41
+ readonly command: StatewireWireCommand;
42
+ readonly inv: CommandsInvocation;
43
+ state: PendingState;
44
+ resolve: (payload: unknown) => void;
45
+ reject: (error: StatewireSendError) => void;
46
+ };
47
+ type AttachState = "detached" | "attached" | "finished";
48
+ type Session = {
49
+ record: StatewireSessionRecord;
50
+ recordPersisted: boolean;
51
+ store: StatewireSessionStore;
52
+ lastAck: number;
53
+ pending: Pending[];
54
+ canonical: unknown;
55
+ lastPublished: unknown;
56
+ changed: StatewirePath[];
57
+ source: StatewireStateSource;
58
+ attach: AttachState;
59
+ attachEpoch: number;
60
+ running: boolean;
61
+ pumping: boolean;
62
+ transport: RemoteLink | null;
63
+ waiters: (() => void)[];
64
+ lifecycle: CommandsListener | null;
65
+ notify: () => void;
66
+ emitEvent: (event: unknown) => void;
67
+ send: (command: StatewireWireCommand) => Promise<unknown>;
68
+ commands: Record<string, (...params: unknown[]) => Promise<unknown>>;
69
+ };
70
+ declare const createSession: (options: SessionOptions) => Session;
71
+ declare const handleSnapshot: (s: Session, { ops, lastSeq }: {
72
+ ops: unknown[];
73
+ lastSeq: number;
74
+ }) => void;
75
+ declare const handleOps: (s: Session, { ops, res, ack }: {
76
+ ops: unknown[];
77
+ res?: StatewireResponse[];
78
+ ack?: number;
79
+ }) => void;
80
+ declare const handleFinish: (s: Session, { reason }: {
81
+ reason: string;
82
+ }) => void;
83
+ declare const start: (s: Session) => void;
84
+ declare const stop: (s: Session) => void;
85
+ declare const setTransport: (s: Session, transport: RemoteLink) => void;
86
+ //#endregion
87
+ export { SessionOptions, createSession, handleFinish, handleOps, handleSnapshot, setTransport, start, stop };
88
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;;KAoBY;EACV,eAAe;;;;;;;;KASZ;EACC;EAAc;;EACd;EAAc;EAAwB;;EACtC;EAAc;EAAwB;;EACtC;EAAuB;EAAiB;;EACxC;EAAoB;EAAiB;;EACrC;;KAED;WACM;WACA,SAAS;WACT,KAAK;EACd,OAAO;EACP,UAAU;EACV,SAAS,OAAO;;KAyFb;KAEA;EACH,QAAQ;EACR;EACA,OAAO;EACP;EACA,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ;EACR,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX;EACA,YAAY;EACZ,OAAO,SAAS,yBAAyB;EACzC,UAAU,mBAAmB,sBAAsB;;cAoB/C,gBAAiB,SAAS,mBAAiB;cA0N3C,iBACJ,GAAG,WACH,KAAA;EAAoB;EAAgB;;cAuChC,YACJ,GAAG,WACH,KAAA,KAAA;EAIK;EAAgB,MAAM;EAAqB;;cAoB5C,eAAgB,GAAG,WAAS;EAAc;;cAuJ1C,QAAS,GAAG;cASZ,OAAQ,GAAG;cAMX,eAAgB,GAAG,SAAS,WAAW"}