statewire 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -13
- package/dist/client/changes.d.ts +2 -2
- package/dist/client/changes.d.ts.map +1 -1
- package/dist/client/changes.js +3 -10
- package/dist/client/changes.js.map +1 -1
- package/dist/client/connection.d.ts +18 -39
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +61 -54
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts +7 -11
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +58 -55
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +12 -26
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +70 -43
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +1 -1
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/client/timing.d.ts +2 -1
- package/dist/client/timing.d.ts.map +1 -1
- package/dist/client/timing.js +5 -1
- package/dist/client/timing.js.map +1 -1
- package/dist/host/adapter.d.ts +8 -0
- package/dist/host/adapter.d.ts.map +1 -0
- package/dist/host/adapter.js +23 -0
- package/dist/host/adapter.js.map +1 -0
- package/dist/host/draft.d.ts +21 -7
- package/dist/host/draft.d.ts.map +1 -1
- package/dist/host/draft.js +190 -103
- package/dist/host/draft.js.map +1 -1
- package/dist/host/host.d.ts +19 -35
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +86 -54
- package/dist/host/host.js.map +1 -1
- package/dist/host/socket.d.ts +14 -0
- package/dist/host/socket.d.ts.map +1 -0
- package/dist/host/socket.js +511 -0
- package/dist/host/socket.js.map +1 -0
- package/dist/host-internal.d.ts +27 -0
- package/dist/host-internal.d.ts.map +1 -0
- package/dist/host-internal.js +3 -0
- package/dist/host.d.ts +54 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +5 -28
- package/dist/index.js +2 -3
- package/dist/statewire.d.ts +97 -10
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +32 -36
- package/dist/statewire.js.map +1 -1
- package/dist/transports/fake-sse.d.ts +12 -0
- package/dist/transports/fake-sse.d.ts.map +1 -0
- package/dist/transports/fake-sse.js +39 -0
- package/dist/transports/fake-sse.js.map +1 -0
- package/dist/transports/fixtures.d.ts +33 -0
- package/dist/transports/fixtures.d.ts.map +1 -0
- package/dist/transports/fixtures.js +50 -0
- package/dist/transports/fixtures.js.map +1 -0
- package/dist/transports/remote.d.ts +6 -13
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +7 -25
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +32 -20
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +57 -46
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +8 -6
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +19 -46
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.d.ts +4 -13
- package/dist/transports/ws.d.ts.map +1 -1
- package/dist/transports/ws.js +59 -63
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +55 -100
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util/draftTraps.d.ts +6 -0
- package/dist/util/draftTraps.d.ts.map +1 -0
- package/dist/util/draftTraps.js +25 -0
- package/dist/util/draftTraps.js.map +1 -0
- package/dist/util/macrotask.d.ts +5 -0
- package/dist/util/macrotask.d.ts.map +1 -0
- package/dist/util/macrotask.js +25 -0
- package/dist/util/macrotask.js.map +1 -0
- package/dist/util/methodProxy.d.ts +5 -0
- package/dist/util/methodProxy.d.ts.map +1 -0
- package/dist/util/methodProxy.js +17 -0
- package/dist/util/methodProxy.js.map +1 -0
- package/dist/util/randomId.d.ts +5 -0
- package/dist/util/randomId.d.ts.map +1 -0
- package/dist/util/randomId.js +6 -0
- package/dist/util/randomId.js.map +1 -0
- package/dist/{client/optimistic.d.ts → util/recordMutations.d.ts} +2 -2
- package/dist/util/recordMutations.d.ts.map +1 -0
- package/dist/{client/optimistic.js → util/recordMutations.js} +5 -24
- package/dist/util/recordMutations.js.map +1 -0
- package/dist/util/statePath.d.ts +14 -0
- package/dist/util/statePath.d.ts.map +1 -0
- package/dist/util/statePath.js +31 -0
- package/dist/util/statePath.js.map +1 -0
- package/dist/wire/meta.d.ts +6 -1
- package/dist/wire/meta.d.ts.map +1 -1
- package/dist/wire/meta.js +30 -28
- package/dist/wire/meta.js.map +1 -1
- package/dist/wire/ops.d.ts +5 -26
- package/dist/wire/ops.d.ts.map +1 -1
- package/dist/wire/ops.js +67 -80
- package/dist/wire/ops.js.map +1 -1
- package/package.json +15 -11
- package/src/client/changes.ts +5 -15
- package/src/client/connection.ts +87 -113
- package/src/client/core.ts +57 -91
- package/src/client/session.ts +129 -90
- package/src/client/sessionStore.ts +1 -1
- package/src/client/timing.ts +6 -0
- package/src/host/adapter.ts +33 -0
- package/src/host/draft.ts +293 -125
- package/src/host/host.ts +122 -110
- package/src/host/socket.ts +618 -0
- package/src/host-internal.ts +28 -0
- package/src/host.ts +72 -0
- package/src/index.ts +2 -62
- package/src/statewire.ts +168 -61
- package/src/transports/fake-sse.ts +51 -0
- package/src/transports/fixtures.ts +60 -0
- package/src/transports/remote.ts +14 -51
- package/src/transports/sse.ts +87 -89
- package/src/transports/testing.ts +33 -63
- package/src/transports/ws.ts +78 -100
- package/src/types.ts +52 -131
- package/src/util/draftTraps.ts +32 -0
- package/src/util/macrotask.ts +25 -0
- package/src/util/methodProxy.ts +16 -0
- package/src/util/randomId.ts +4 -0
- package/src/{client/optimistic.ts → util/recordMutations.ts} +0 -0
- package/src/util/statePath.ts +61 -0
- package/src/wire/meta.ts +42 -47
- package/src/wire/ops.ts +107 -106
- package/LICENSE +0 -21
- package/dist/client/optimistic.d.ts.map +0 -1
- package/dist/client/optimistic.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/server.d.ts +0 -15
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -3
- package/src/server.ts +0 -32
package/dist/client/core.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { recordMutations } from "../util/recordMutations.js";
|
|
2
|
+
import { createMethodProxy } from "../util/methodProxy.js";
|
|
3
3
|
import { StatewireSendError } from "../types.js";
|
|
4
4
|
import { getCommandsHost, getEmitHost, getStateHost } from "../host/host.js";
|
|
5
|
+
import { createMacrotaskPoster } from "../util/macrotask.js";
|
|
6
|
+
import { createChanges } from "./changes.js";
|
|
7
|
+
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
8
|
+
import { useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
5
9
|
//#region src/client/core.ts
|
|
6
10
|
const LIVE_CONNECTION = {
|
|
7
11
|
status: "live",
|
|
@@ -12,27 +16,7 @@ const failureOf = (error) => error instanceof StatewireSendError ? error.failure
|
|
|
12
16
|
cause: "server",
|
|
13
17
|
message: error instanceof Error ? error.message : String(error)
|
|
14
18
|
};
|
|
15
|
-
const
|
|
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) => {
|
|
19
|
+
const createCore = (getOptions) => {
|
|
36
20
|
let value = null;
|
|
37
21
|
let source = null;
|
|
38
22
|
let overlays = [];
|
|
@@ -48,7 +32,6 @@ const createStatewireCore = (getOptions) => {
|
|
|
48
32
|
let stale = false;
|
|
49
33
|
let overlaysDirty = false;
|
|
50
34
|
let flushing = false;
|
|
51
|
-
let disposed = false;
|
|
52
35
|
const listeners = /* @__PURE__ */ new Set();
|
|
53
36
|
const post = createMacrotaskPoster(() => {
|
|
54
37
|
scheduled = false;
|
|
@@ -56,7 +39,7 @@ const createStatewireCore = (getOptions) => {
|
|
|
56
39
|
});
|
|
57
40
|
let scheduled = false;
|
|
58
41
|
const schedule = () => {
|
|
59
|
-
if (scheduled
|
|
42
|
+
if (scheduled) return;
|
|
60
43
|
scheduled = true;
|
|
61
44
|
post();
|
|
62
45
|
};
|
|
@@ -67,7 +50,7 @@ const createStatewireCore = (getOptions) => {
|
|
|
67
50
|
const drainCanonical = () => {
|
|
68
51
|
if (source) {
|
|
69
52
|
pendingChanged.push(...source.takeChanged());
|
|
70
|
-
canonical = source.
|
|
53
|
+
canonical = source.snapshot();
|
|
71
54
|
} else if (value && value.state !== canonical) {
|
|
72
55
|
pendingChanged.push([]);
|
|
73
56
|
canonical = value.state;
|
|
@@ -108,7 +91,7 @@ const createStatewireCore = (getOptions) => {
|
|
|
108
91
|
};
|
|
109
92
|
const getConnection = () => value?.connection ?? LIVE_CONNECTION;
|
|
110
93
|
const flush = () => {
|
|
111
|
-
if (
|
|
94
|
+
if (!value) return;
|
|
112
95
|
if (flushing) {
|
|
113
96
|
schedule();
|
|
114
97
|
return;
|
|
@@ -135,7 +118,7 @@ const createStatewireCore = (getOptions) => {
|
|
|
135
118
|
updates = [];
|
|
136
119
|
for (const update of batch) options.onCommandChange?.(update, { state: published });
|
|
137
120
|
}
|
|
138
|
-
if (events.length > 0) {
|
|
121
|
+
if (events.length > 0 && published !== void 0) {
|
|
139
122
|
const batch = events;
|
|
140
123
|
events = [];
|
|
141
124
|
for (const event of batch) options.onEvent?.(event, { state: published });
|
|
@@ -188,7 +171,7 @@ const createStatewireCore = (getOptions) => {
|
|
|
188
171
|
schedule();
|
|
189
172
|
};
|
|
190
173
|
const attach = (next) => {
|
|
191
|
-
if (
|
|
174
|
+
if (next === value) return;
|
|
192
175
|
const prev = value;
|
|
193
176
|
value = next;
|
|
194
177
|
const nextSource = next.source ?? (typeof next.state === "object" && next.state !== null ? getStateHost(next.state) : void 0) ?? null;
|
|
@@ -223,38 +206,23 @@ const createStatewireCore = (getOptions) => {
|
|
|
223
206
|
value = null;
|
|
224
207
|
}
|
|
225
208
|
};
|
|
226
|
-
const memo = /* @__PURE__ */ new Map();
|
|
227
209
|
return {
|
|
228
210
|
attach,
|
|
229
211
|
detach,
|
|
230
|
-
dispose: () => {
|
|
231
|
-
detach();
|
|
232
|
-
disposed = true;
|
|
233
|
-
},
|
|
234
212
|
getState: () => {
|
|
235
213
|
recompute();
|
|
236
214
|
return published;
|
|
237
215
|
},
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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,
|
|
216
|
+
commands: createMethodProxy((method, params) => {
|
|
217
|
+
if (!value) return Promise.reject(new StatewireSendError({
|
|
218
|
+
fate: "aborted",
|
|
219
|
+
cause: "unmount"
|
|
220
|
+
}));
|
|
221
|
+
const send = value.commands[method];
|
|
222
|
+
const promise = send(...params);
|
|
223
|
+
flush();
|
|
224
|
+
return promise;
|
|
225
|
+
}),
|
|
258
226
|
subscribe: (listener) => {
|
|
259
227
|
listeners.add(listener);
|
|
260
228
|
return () => {
|
|
@@ -263,7 +231,42 @@ const createStatewireCore = (getOptions) => {
|
|
|
263
231
|
}
|
|
264
232
|
};
|
|
265
233
|
};
|
|
234
|
+
const useStatewireCore = (getOptions, value) => {
|
|
235
|
+
const $ = c(8);
|
|
236
|
+
let t0;
|
|
237
|
+
if ($[0] !== getOptions) {
|
|
238
|
+
t0 = () => createCore(getOptions);
|
|
239
|
+
$[0] = getOptions;
|
|
240
|
+
$[1] = t0;
|
|
241
|
+
} else t0 = $[1];
|
|
242
|
+
const [core] = useState(t0);
|
|
243
|
+
core.attach(value);
|
|
244
|
+
let t1;
|
|
245
|
+
if ($[2] !== core || $[3] !== value) {
|
|
246
|
+
t1 = () => {
|
|
247
|
+
core.attach(value);
|
|
248
|
+
};
|
|
249
|
+
$[2] = core;
|
|
250
|
+
$[3] = value;
|
|
251
|
+
$[4] = t1;
|
|
252
|
+
} else t1 = $[4];
|
|
253
|
+
useEffect(t1);
|
|
254
|
+
let t2;
|
|
255
|
+
let t3;
|
|
256
|
+
if ($[5] !== core) {
|
|
257
|
+
t2 = () => () => core.detach();
|
|
258
|
+
t3 = [core];
|
|
259
|
+
$[5] = core;
|
|
260
|
+
$[6] = t2;
|
|
261
|
+
$[7] = t3;
|
|
262
|
+
} else {
|
|
263
|
+
t2 = $[6];
|
|
264
|
+
t3 = $[7];
|
|
265
|
+
}
|
|
266
|
+
useEffect(t2, t3);
|
|
267
|
+
return core;
|
|
268
|
+
};
|
|
266
269
|
//#endregion
|
|
267
|
-
export { LIVE_CONNECTION,
|
|
270
|
+
export { LIVE_CONNECTION, useStatewireCore };
|
|
268
271
|
|
|
269
272
|
//# sourceMappingURL=core.js.map
|
package/dist/client/core.js.map
CHANGED
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"core.js","names":["useEffect","useState","createChanges","getCommandsHost","getEmitHost","getStateHost","CommandsInvocation","CommandsListener","recordMutations","createMacrotaskPoster","createMethodProxy","StatewirePath","StatewireSendError","Statewire","StatewireClient","LIVE_CONNECTION","Connection","status","reconnect","failureOf","error","SendFailure","failure","fate","cause","message","Error","String","Overlay","inv","dropped","createCore","getOptions","Options","value","source","StateSource","overlays","updates","CommandUpdate","events","pendingChanged","canonical","published","overlayPaths","notifiedPublished","notifiedOverlayPaths","notifiedConnection","stale","overlaysDirty","flushing","listeners","Set","post","scheduled","flush","schedule","markStale","drainCanonical","push","takeChanged","snapshot","state","recompute","prevCanonical","optimistic","next","paths","undefined","overlay","ctx","OptimisticContext","ts","seq","clientId","recorded","draft","method","params","Command","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","CommandsProxy","Promise","reject","send","Record","args","promise","getState","subscribe","add","delete","useStatewireCore","$","_c","t0","core","t1","t2","t3"],"sources":["../../src/client/core.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { createChanges } from \"./changes\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport { recordMutations } from \"../util/recordMutations\";\nimport { createMacrotaskPoster } from \"../util/macrotask\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\n\nexport const LIVE_CONNECTION: StatewireClient.Connection = {\n status: \"live\",\n reconnect: () => {},\n};\n\nconst failureOf = (error: unknown): StatewireClient.SendFailure =>\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\ntype Overlay = { inv: CommandsInvocation; dropped: boolean };\n\nconst createCore = (\n getOptions: () => StatewireClient.Options<any, any, any>,\n) => {\n let value: Statewire<unknown> | null = null;\n let source: Statewire.StateSource | null = null;\n let overlays: Overlay[] = [];\n let updates: Statewire.CommandUpdate[] = [];\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: StatewireClient.Connection | null = null;\n let stale = false;\n let overlaysDirty = false;\n let flushing = 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) 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.snapshot();\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: StatewireClient.OptimisticContext = {\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 Statewire.Command,\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 = (): StatewireClient.Connection =>\n value?.connection ?? LIVE_CONNECTION;\n\n const flush = () => {\n if (!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 && published !== undefined) {\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: Statewire.CommandUpdate[\"status\"],\n extra?: { result?: unknown; failure?: StatewireClient.SendFailure },\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: Statewire<unknown>) => {\n if (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 commands = createMethodProxy<Statewire.CommandsProxy<any>>(\n (method, params) => {\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 );\n\n return {\n attach,\n detach,\n getState: () => {\n recompute();\n return published;\n },\n commands,\n subscribe: (listener: () => void) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n};\n\nexport const useStatewireCore = (\n getOptions: () => StatewireClient.Options<any, any, any>,\n value: Statewire<unknown>,\n) => {\n const [core] = useState(() => createCore(getOptions));\n // Attach during render so the first snapshot reads the transport's state;\n // re-attaching the same transport is a no-op and the single-slot host\n // listeners make a swap land on the last writer.\n core.attach(value);\n useEffect(() => {\n core.attach(value);\n });\n useEffect(() => () => core.detach(), [core]);\n return core;\n};\n"],"mappings":"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"}
|
package/dist/client/session.d.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
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
1
|
import { CommandsInvocation, CommandsListener } from "../host/host.js";
|
|
2
|
+
import { RemoteLink, StatewireRemoteProps, StatewireWireCommand } from "../transports/sse.js";
|
|
3
|
+
import { StatewireSessionRecord, StatewireSessionStore, StatewireSessionStoreOptions } from "./sessionStore.js";
|
|
4
|
+
import { Statewire, StatewireSendError } from "../types.js";
|
|
5
|
+
import { StatewirePath } from "../wire/ops.js";
|
|
7
6
|
//#region src/client/session.d.ts
|
|
8
|
-
type SessionOptions = {
|
|
9
|
-
sessionStore?: StatewireSessionStore | undefined;
|
|
10
|
-
};
|
|
11
7
|
/**
|
|
12
8
|
* queued/trying/posted: overlay live, result pending. awaiting-result: acked
|
|
13
9
|
* (overlay gone) but the result rides a late res; `posted: false` re-sends.
|
|
@@ -39,7 +35,8 @@ type PendingState = {
|
|
|
39
35
|
type Pending = {
|
|
40
36
|
readonly seq: number;
|
|
41
37
|
readonly command: StatewireWireCommand;
|
|
42
|
-
|
|
38
|
+
/** null for context submissions — they carry no command lifecycle. */
|
|
39
|
+
readonly inv: CommandsInvocation | null;
|
|
43
40
|
state: PendingState;
|
|
44
41
|
resolve: (payload: unknown) => void;
|
|
45
42
|
reject: (error: StatewireSendError) => void;
|
|
@@ -47,14 +44,16 @@ type Pending = {
|
|
|
47
44
|
type AttachState = "detached" | "attached" | "finished";
|
|
48
45
|
type Session = {
|
|
49
46
|
record: StatewireSessionRecord;
|
|
47
|
+
ctx: StatewireRemoteProps;
|
|
50
48
|
recordPersisted: boolean;
|
|
51
49
|
store: StatewireSessionStore;
|
|
52
50
|
lastAck: number;
|
|
51
|
+
context: Record<string, unknown> | undefined;
|
|
53
52
|
pending: Pending[];
|
|
54
53
|
canonical: unknown;
|
|
55
54
|
lastPublished: unknown;
|
|
56
55
|
changed: StatewirePath[];
|
|
57
|
-
source:
|
|
56
|
+
source: Statewire.StateSource;
|
|
58
57
|
attach: AttachState;
|
|
59
58
|
attachEpoch: number;
|
|
60
59
|
running: boolean;
|
|
@@ -67,22 +66,9 @@ type Session = {
|
|
|
67
66
|
send: (command: StatewireWireCommand) => Promise<unknown>;
|
|
68
67
|
commands: Record<string, (...params: unknown[]) => Promise<unknown>>;
|
|
69
68
|
};
|
|
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
69
|
declare const setTransport: (s: Session, transport: RemoteLink) => void;
|
|
70
|
+
declare const setContext: (s: Session, value: Record<string, unknown>) => Promise<unknown>;
|
|
71
|
+
declare const useStatewireSession: (options: StatewireSessionStoreOptions, emit: (event: unknown) => void) => Session;
|
|
86
72
|
//#endregion
|
|
87
|
-
export {
|
|
73
|
+
export { setContext, setTransport, useStatewireSession };
|
|
88
74
|
//# sourceMappingURL=session.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;;;;;;;KA+BK;EACC;EAAc;;EACd;EAAc;EAAwB;;EACtC;EAAc;EAAwB;;EACtC;EAAuB;EAAiB;;EACxC;EAAoB;EAAiB;;EACrC;;KAED;WACM;WACA,SAAS;;WAET,KAAK;EACd,OAAO;EACP,UAAU;EACV,SAAS,OAAO;;KAqGb;KAEA;EACH,QAAQ;EACR,KAAK;EACL;EACA,OAAO;EACP;EACA,SAAS;EACT,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ,UAAU;EAClB,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX;EACA,YAAY;EACZ,OAAO,SAAS,yBAAyB;EACzC,UAAU,mBAAmB,sBAAsB;;cAsd/C,eAAgB,GAAG,SAAS,WAAW;cA+CvC,aACJ,GAAG,SACH,OAAO,4BACN;cAQU,sBACX,SAAS,8BACT,OAAO,4BACN"}
|
package/dist/client/session.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { applyOps } from "../wire/ops.js";
|
|
2
|
+
import { createMethodProxy } from "../util/methodProxy.js";
|
|
2
3
|
import { StatewireSendError } from "../types.js";
|
|
3
4
|
import { bindCommandsHost } from "../host/host.js";
|
|
4
|
-
import {
|
|
5
|
+
import { randomId } from "../util/randomId.js";
|
|
6
|
+
import { retryDelay } from "./timing.js";
|
|
5
7
|
import { createMemorySessionStore } from "./sessionStore.js";
|
|
8
|
+
import { useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
6
9
|
//#region src/client/session.ts
|
|
7
10
|
const isResolved = (state) => state.at === "settled-live" || state.at === "done";
|
|
8
11
|
const isStateSettled = (state) => state.at === "awaiting-result" || state.at === "done";
|
|
12
|
+
const inFlight = (state) => {
|
|
13
|
+
if (state.at === "queued" || state.at === "done") throw new Error(`statewire: ${state.at} entry observed after a send`);
|
|
14
|
+
return state;
|
|
15
|
+
};
|
|
9
16
|
const legalTransitions = {
|
|
10
17
|
queued: [
|
|
11
18
|
"queued",
|
|
@@ -40,16 +47,16 @@ const transition = (s, entry, next, outcome) => {
|
|
|
40
47
|
if (settles && outcome === void 0) throw new Error(`statewire: settling transition ${prev.at} -> ${next.at} without an outcome`);
|
|
41
48
|
const covers = !isStateSettled(prev) && isStateSettled(next);
|
|
42
49
|
entry.state = next;
|
|
43
|
-
if (covers) s.lifecycle?.onCovered?.(entry.inv);
|
|
50
|
+
if (covers && entry.inv) s.lifecycle?.onCovered?.(entry.inv);
|
|
44
51
|
if (settles && outcome !== void 0) if (outcome.ok) {
|
|
45
52
|
entry.resolve(outcome.payload);
|
|
46
|
-
s.lifecycle?.onSettle(entry.inv, {
|
|
53
|
+
if (entry.inv) s.lifecycle?.onSettle(entry.inv, {
|
|
47
54
|
type: "accepted",
|
|
48
55
|
payload: outcome.payload
|
|
49
56
|
});
|
|
50
57
|
} else {
|
|
51
58
|
entry.reject(new StatewireSendError(outcome.failure));
|
|
52
|
-
s.lifecycle?.onSettle(entry.inv, {
|
|
59
|
+
if (entry.inv) s.lifecycle?.onSettle(entry.inv, {
|
|
53
60
|
type: "rejected",
|
|
54
61
|
error: new StatewireSendError(outcome.failure)
|
|
55
62
|
});
|
|
@@ -66,27 +73,12 @@ const carried = (state) => state.at === "queued" ? {
|
|
|
66
73
|
posted: state.posted,
|
|
67
74
|
failedAttempts: state.failedAttempts
|
|
68
75
|
};
|
|
69
|
-
const newClientId = () => `c-${
|
|
76
|
+
const newClientId = () => `c-${randomId()}`;
|
|
70
77
|
const rejectedSend = (failure) => {
|
|
71
78
|
const result = Promise.reject(new StatewireSendError(failure));
|
|
72
79
|
result.catch(() => {});
|
|
73
80
|
return result;
|
|
74
81
|
};
|
|
75
|
-
const createCommandsProxy = (send) => {
|
|
76
|
-
const memo = /* @__PURE__ */ new Map();
|
|
77
|
-
return new Proxy({}, { get: (_target, method) => {
|
|
78
|
-
if (typeof method !== "string") return void 0;
|
|
79
|
-
let fn = memo.get(method);
|
|
80
|
-
if (fn === void 0) {
|
|
81
|
-
fn = (...params) => send({
|
|
82
|
-
method,
|
|
83
|
-
params
|
|
84
|
-
});
|
|
85
|
-
memo.set(method, fn);
|
|
86
|
-
}
|
|
87
|
-
return fn;
|
|
88
|
-
} });
|
|
89
|
-
};
|
|
90
82
|
const createSession = (options) => {
|
|
91
83
|
const store = options.sessionStore ?? createMemorySessionStore();
|
|
92
84
|
const stored = store.load();
|
|
@@ -95,15 +87,24 @@ const createSession = (options) => {
|
|
|
95
87
|
clientId: newClientId(),
|
|
96
88
|
nextSeq: 1
|
|
97
89
|
},
|
|
90
|
+
ctx: {
|
|
91
|
+
get clientId() {
|
|
92
|
+
return s.record.clientId;
|
|
93
|
+
},
|
|
94
|
+
onSnapshot: (args) => handleSnapshot(s, args),
|
|
95
|
+
onOps: (args) => handleOps(s, args),
|
|
96
|
+
onFinish: (args) => handleFinish(s, args)
|
|
97
|
+
},
|
|
98
98
|
recordPersisted: stored !== null,
|
|
99
99
|
store,
|
|
100
100
|
lastAck: -1,
|
|
101
|
+
context: void 0,
|
|
101
102
|
pending: [],
|
|
102
103
|
canonical: void 0,
|
|
103
104
|
lastPublished: void 0,
|
|
104
105
|
changed: [],
|
|
105
106
|
source: {
|
|
106
|
-
|
|
107
|
+
snapshot: () => s.canonical,
|
|
107
108
|
takeChanged: () => {
|
|
108
109
|
const changed = s.changed;
|
|
109
110
|
s.changed = [];
|
|
@@ -123,7 +124,10 @@ const createSession = (options) => {
|
|
|
123
124
|
notify: () => {},
|
|
124
125
|
emitEvent: () => {},
|
|
125
126
|
send: (command) => sendCommand(s, command),
|
|
126
|
-
commands:
|
|
127
|
+
commands: createMethodProxy((method, params) => s.send({
|
|
128
|
+
method,
|
|
129
|
+
params
|
|
130
|
+
}))
|
|
127
131
|
};
|
|
128
132
|
bindCommandsHost(s.commands, { setListener: (listener) => {
|
|
129
133
|
s.lifecycle = listener;
|
|
@@ -141,6 +145,17 @@ const wake = (s) => {
|
|
|
141
145
|
for (const waiter of waiters) waiter();
|
|
142
146
|
};
|
|
143
147
|
const waitForWake = (s) => new Promise((r) => s.waiters.push(r));
|
|
148
|
+
const sleepUntilWake = (s, delayMs) => new Promise((resolve) => {
|
|
149
|
+
const waiter = () => {
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
resolve();
|
|
152
|
+
};
|
|
153
|
+
const timer = setTimeout(() => {
|
|
154
|
+
s.waiters = s.waiters.filter((w) => w !== waiter);
|
|
155
|
+
resolve();
|
|
156
|
+
}, delayMs);
|
|
157
|
+
s.waiters.push(waiter);
|
|
158
|
+
});
|
|
144
159
|
const settleAll = (s, detach, verdict) => {
|
|
145
160
|
const cause = {
|
|
146
161
|
evicted: "session-lost",
|
|
@@ -180,13 +195,14 @@ const evict = (s, verdict) => {
|
|
|
180
195
|
};
|
|
181
196
|
s.store.save(s.record);
|
|
182
197
|
s.attach = "detached";
|
|
198
|
+
if (s.context !== void 0) s.send({ context: s.context });
|
|
183
199
|
publish(s);
|
|
184
200
|
wake(s);
|
|
185
201
|
};
|
|
186
202
|
const noteAcked = (s, seq) => {
|
|
187
203
|
for (const entry of s.pending) {
|
|
188
204
|
if (entry.seq > seq || isStateSettled(entry.state)) continue;
|
|
189
|
-
s.lifecycle?.onAck(entry.inv);
|
|
205
|
+
if (entry.inv) s.lifecycle?.onAck(entry.inv);
|
|
190
206
|
}
|
|
191
207
|
};
|
|
192
208
|
const spliceAcked = (s, seq, settleVoid) => {
|
|
@@ -238,11 +254,11 @@ const applyResponse = (s, response) => {
|
|
|
238
254
|
} : response.code === "invalid-params" ? {
|
|
239
255
|
fate: "invalid-params",
|
|
240
256
|
cause: "server",
|
|
241
|
-
message: response.message ?? ""
|
|
257
|
+
message: response.message ?? protocolError("rejected response missing message")
|
|
242
258
|
} : {
|
|
243
259
|
fate: "rejected",
|
|
244
260
|
cause: "server",
|
|
245
|
-
message: response.message ?? "",
|
|
261
|
+
message: response.message ?? protocolError("rejected response missing message"),
|
|
246
262
|
...response.payload !== void 0 && { payload: response.payload }
|
|
247
263
|
}
|
|
248
264
|
});
|
|
@@ -271,7 +287,7 @@ const ingestOps = (s, ops) => {
|
|
|
271
287
|
};
|
|
272
288
|
const publishIngested = (s, events) => {
|
|
273
289
|
publish(s);
|
|
274
|
-
for (const
|
|
290
|
+
for (const event of events) s.emitEvent(event);
|
|
275
291
|
wake(s);
|
|
276
292
|
};
|
|
277
293
|
const handleSnapshot = (s, { ops, lastSeq }) => {
|
|
@@ -327,7 +343,7 @@ const handleOps = (s, { ops, res, ack }) => {
|
|
|
327
343
|
publishIngested(s, events);
|
|
328
344
|
};
|
|
329
345
|
const handleFinish = (s, { reason }) => {
|
|
330
|
-
if (reason === "
|
|
346
|
+
if (reason === "evicted" || reason === "error") return;
|
|
331
347
|
s.attach = "finished";
|
|
332
348
|
settleAll(s, reason === "superseded" ? "superseded" : "gone");
|
|
333
349
|
publish(s);
|
|
@@ -363,15 +379,12 @@ const pump = async (s) => {
|
|
|
363
379
|
} catch (error) {
|
|
364
380
|
if (!s.running || isFinished(s)) return;
|
|
365
381
|
if (!s.pending.includes(entry)) continue;
|
|
366
|
-
const state = entry.state;
|
|
367
|
-
if (state.at === "queued" || state.at === "done") continue;
|
|
382
|
+
const state = inFlight(entry.state);
|
|
368
383
|
transition(s, entry, {
|
|
369
384
|
...state,
|
|
370
385
|
failedAttempts: state.failedAttempts + 1
|
|
371
386
|
});
|
|
372
|
-
|
|
373
|
-
const delay = typeof retryAfterMs === "number" ? retryAfterMs : backoff(state.failedAttempts + 1);
|
|
374
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
387
|
+
await sleepUntilWake(s, retryDelay(error, state.failedAttempts + 1));
|
|
375
388
|
continue;
|
|
376
389
|
}
|
|
377
390
|
if (!s.running) return;
|
|
@@ -388,20 +401,18 @@ const pump = async (s) => {
|
|
|
388
401
|
...state,
|
|
389
402
|
posted: true
|
|
390
403
|
});
|
|
391
|
-
s.lifecycle?.onPosted?.(entry.inv);
|
|
404
|
+
if (entry.inv) s.lifecycle?.onPosted?.(entry.inv);
|
|
392
405
|
if (response !== void 0) {
|
|
393
406
|
applyResponse(s, response);
|
|
394
407
|
publish(s);
|
|
395
408
|
}
|
|
396
409
|
} else if (result.type === "rejected") {
|
|
397
|
-
const { message, payload } = result;
|
|
398
410
|
transition(s, entry, { at: "done" }, {
|
|
399
411
|
ok: false,
|
|
400
412
|
failure: {
|
|
401
413
|
fate: "rejected",
|
|
402
414
|
cause: "server",
|
|
403
|
-
message
|
|
404
|
-
...payload !== void 0 && { payload }
|
|
415
|
+
message: result.message
|
|
405
416
|
}
|
|
406
417
|
});
|
|
407
418
|
publish(s);
|
|
@@ -420,13 +431,10 @@ const pump = async (s) => {
|
|
|
420
431
|
} else if (result.type === "seq-gap") {
|
|
421
432
|
if (clientId === s.record.clientId) {
|
|
422
433
|
console.error(`statewire: server reports a seq gap for seq ${entry.seq}; treating the session as corrupt and rotating the client id`);
|
|
423
|
-
const state = entry.state;
|
|
434
|
+
const state = inFlight(entry.state);
|
|
424
435
|
const failure = state.at === "awaiting-result" ? {
|
|
425
436
|
fate: "result-unavailable",
|
|
426
437
|
cause: "session-lost"
|
|
427
|
-
} : state.at === "queued" || state.at === "done" ? {
|
|
428
|
-
fate: "aborted",
|
|
429
|
-
cause: "session-lost"
|
|
430
438
|
} : {
|
|
431
439
|
fate: state.failedAttempts > 0 ? "lost" : "aborted",
|
|
432
440
|
cause: "session-lost"
|
|
@@ -479,7 +487,7 @@ const sendCommand = (s, command) => {
|
|
|
479
487
|
const entry = {
|
|
480
488
|
seq,
|
|
481
489
|
command,
|
|
482
|
-
inv: {
|
|
490
|
+
inv: command.method === void 0 ? null : {
|
|
483
491
|
method: command.method,
|
|
484
492
|
params: command.params,
|
|
485
493
|
ts: Date.now(),
|
|
@@ -499,11 +507,30 @@ const sendCommand = (s, command) => {
|
|
|
499
507
|
});
|
|
500
508
|
result.catch(() => {});
|
|
501
509
|
s.pending.push(entry);
|
|
502
|
-
s.lifecycle?.onStart(entry.inv);
|
|
510
|
+
if (entry.inv) s.lifecycle?.onStart(entry.inv);
|
|
503
511
|
pump(s);
|
|
504
512
|
return result;
|
|
505
513
|
};
|
|
514
|
+
const setContext = (s, value) => {
|
|
515
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("statewire: context must be a plain object");
|
|
516
|
+
s.context = value;
|
|
517
|
+
return s.send({ context: value });
|
|
518
|
+
};
|
|
519
|
+
const useStatewireSession = (options, emit) => {
|
|
520
|
+
const [s] = useState(() => createSession(options));
|
|
521
|
+
useEffect(() => {
|
|
522
|
+
s.emitEvent = emit;
|
|
523
|
+
return () => {
|
|
524
|
+
s.emitEvent = () => {};
|
|
525
|
+
};
|
|
526
|
+
}, [s, emit]);
|
|
527
|
+
useEffect(() => {
|
|
528
|
+
start(s);
|
|
529
|
+
return () => stop(s);
|
|
530
|
+
}, [s]);
|
|
531
|
+
return s;
|
|
532
|
+
};
|
|
506
533
|
//#endregion
|
|
507
|
-
export {
|
|
534
|
+
export { setContext, setTransport, useStatewireSession };
|
|
508
535
|
|
|
509
536
|
//# sourceMappingURL=session.js.map
|