statewire 0.0.0-0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -1
- package/dist/client/changes.d.ts +7 -0
- package/dist/client/changes.d.ts.map +1 -0
- package/dist/client/changes.js +25 -0
- package/dist/client/changes.js.map +1 -0
- package/dist/client/connection.d.ts +63 -0
- package/dist/client/connection.d.ts.map +1 -0
- package/dist/client/connection.js +211 -0
- package/dist/client/connection.js.map +1 -0
- package/dist/client/core.d.ts +14 -0
- package/dist/client/core.d.ts.map +1 -0
- package/dist/client/core.js +272 -0
- package/dist/client/core.js.map +1 -0
- package/dist/client/session.d.ts +71 -0
- package/dist/client/session.d.ts.map +1 -0
- package/dist/client/session.js +529 -0
- package/dist/client/session.js.map +1 -0
- package/dist/client/sessionStore.d.ts +16 -0
- package/dist/client/sessionStore.d.ts.map +1 -0
- package/dist/client/sessionStore.js +14 -0
- package/dist/client/sessionStore.js.map +1 -0
- package/dist/client/timing.d.ts +11 -0
- package/dist/client/timing.d.ts.map +1 -0
- package/dist/client/timing.js +15 -0
- package/dist/client/timing.js.map +1 -0
- package/dist/host/adapter.d.ts +8 -0
- package/dist/host/adapter.d.ts.map +1 -0
- package/dist/host/adapter.js +23 -0
- package/dist/host/adapter.js.map +1 -0
- package/dist/host/draft.d.ts +28 -0
- package/dist/host/draft.d.ts.map +1 -0
- package/dist/host/draft.js +211 -0
- package/dist/host/draft.js.map +1 -0
- package/dist/host/engine.d.ts +14 -0
- package/dist/host/engine.d.ts.map +1 -0
- package/dist/host/engine.js +17 -0
- package/dist/host/engine.js.map +1 -0
- package/dist/host/host.d.ts +62 -0
- package/dist/host/host.d.ts.map +1 -0
- package/dist/host/host.js +331 -0
- package/dist/host/host.js.map +1 -0
- package/dist/host/socket.d.ts +14 -0
- package/dist/host/socket.d.ts.map +1 -0
- package/dist/host/socket.js +459 -0
- package/dist/host/socket.js.map +1 -0
- package/dist/host-internal.d.ts +27 -0
- package/dist/host-internal.d.ts.map +1 -0
- package/dist/host-internal.js +3 -0
- package/dist/host.d.ts +44 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/statewire.d.ts +104 -0
- package/dist/statewire.d.ts.map +1 -0
- package/dist/statewire.js +53 -0
- package/dist/statewire.js.map +1 -0
- package/dist/transports/fake-sse.d.ts +12 -0
- package/dist/transports/fake-sse.d.ts.map +1 -0
- package/dist/transports/fake-sse.js +39 -0
- package/dist/transports/fake-sse.js.map +1 -0
- package/dist/transports/fixtures.d.ts +33 -0
- package/dist/transports/fixtures.d.ts.map +1 -0
- package/dist/transports/fixtures.js +50 -0
- package/dist/transports/fixtures.js.map +1 -0
- package/dist/transports/remote.d.ts +12 -0
- package/dist/transports/remote.d.ts.map +1 -0
- package/dist/transports/remote.js +21 -0
- package/dist/transports/remote.js.map +1 -0
- package/dist/transports/sse-parser.d.ts +5 -0
- package/dist/transports/sse-parser.d.ts.map +1 -0
- package/dist/transports/sse-parser.js +26 -0
- package/dist/transports/sse-parser.js.map +1 -0
- package/dist/transports/sse.d.ts +64 -0
- package/dist/transports/sse.d.ts.map +1 -0
- package/dist/transports/sse.js +137 -0
- package/dist/transports/sse.js.map +1 -0
- package/dist/transports/testing.d.ts +32 -0
- package/dist/transports/testing.d.ts.map +1 -0
- package/dist/transports/testing.js +94 -0
- package/dist/transports/testing.js.map +1 -0
- package/dist/transports/ws.d.ts +9 -0
- package/dist/transports/ws.d.ts.map +1 -0
- package/dist/transports/ws.js +169 -0
- package/dist/transports/ws.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/dist/util/draftTraps.d.ts +6 -0
- package/dist/util/draftTraps.d.ts.map +1 -0
- package/dist/util/draftTraps.js +25 -0
- package/dist/util/draftTraps.js.map +1 -0
- package/dist/util/macrotask.d.ts +5 -0
- package/dist/util/macrotask.d.ts.map +1 -0
- package/dist/util/macrotask.js +25 -0
- package/dist/util/macrotask.js.map +1 -0
- package/dist/util/methodProxy.d.ts +5 -0
- package/dist/util/methodProxy.d.ts.map +1 -0
- package/dist/util/methodProxy.js +17 -0
- package/dist/util/methodProxy.js.map +1 -0
- package/dist/util/randomId.d.ts +5 -0
- package/dist/util/randomId.d.ts.map +1 -0
- package/dist/util/randomId.js +6 -0
- package/dist/util/randomId.js.map +1 -0
- package/dist/util/recordMutations.d.ts +10 -0
- package/dist/util/recordMutations.d.ts.map +1 -0
- package/dist/util/recordMutations.js +109 -0
- package/dist/util/recordMutations.js.map +1 -0
- package/dist/util/statePath.d.ts +14 -0
- package/dist/util/statePath.d.ts.map +1 -0
- package/dist/util/statePath.js +31 -0
- package/dist/util/statePath.js.map +1 -0
- package/dist/wire/meta.d.ts +27 -0
- package/dist/wire/meta.d.ts.map +1 -0
- package/dist/wire/meta.js +75 -0
- package/dist/wire/meta.js.map +1 -0
- package/dist/wire/ops.d.ts +19 -0
- package/dist/wire/ops.d.ts.map +1 -0
- package/dist/wire/ops.js +112 -0
- package/dist/wire/ops.js.map +1 -0
- package/package.json +52 -5
- package/src/client/changes.ts +38 -0
- package/src/client/connection.ts +301 -0
- package/src/client/core.ts +312 -0
- package/src/client/session.ts +696 -0
- package/src/client/sessionStore.ts +23 -0
- package/src/client/timing.ts +14 -0
- package/src/host/adapter.ts +33 -0
- package/src/host/draft.ts +323 -0
- package/src/host/engine.ts +13 -0
- package/src/host/host.ts +482 -0
- package/src/host/socket.ts +542 -0
- package/src/host-internal.ts +28 -0
- package/src/host.ts +61 -0
- package/src/index.ts +5 -0
- package/src/statewire.ts +200 -0
- package/src/transports/fake-sse.ts +51 -0
- package/src/transports/fixtures.ts +60 -0
- package/src/transports/remote.ts +30 -0
- package/src/transports/sse-parser.ts +24 -0
- package/src/transports/sse.ts +267 -0
- package/src/transports/testing.ts +123 -0
- package/src/transports/ws.ts +256 -0
- package/src/types.ts +88 -0
- package/src/util/draftTraps.ts +32 -0
- package/src/util/macrotask.ts +25 -0
- package/src/util/methodProxy.ts +16 -0
- package/src/util/randomId.ts +4 -0
- package/src/util/recordMutations.ts +0 -0
- package/src/util/statePath.ts +61 -0
- package/src/wire/meta.ts +101 -0
- package/src/wire/ops.ts +202 -0
- package/index.js +0 -1
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { applyOps } from "../wire/ops.js";
|
|
2
|
+
import { createMethodProxy } from "../util/methodProxy.js";
|
|
3
|
+
import { StatewireSendError } from "../types.js";
|
|
4
|
+
import { bindCommandsHost } from "../host/host.js";
|
|
5
|
+
import { randomId } from "../util/randomId.js";
|
|
6
|
+
import { retryDelay } from "./timing.js";
|
|
7
|
+
import { createMemorySessionStore } from "./sessionStore.js";
|
|
8
|
+
import { useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
9
|
+
//#region src/client/session.ts
|
|
10
|
+
const isResolved = (state) => state.at === "settled-live" || state.at === "done";
|
|
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
|
+
};
|
|
16
|
+
const legalTransitions = {
|
|
17
|
+
queued: [
|
|
18
|
+
"queued",
|
|
19
|
+
"trying",
|
|
20
|
+
"awaiting-result",
|
|
21
|
+
"settled-live",
|
|
22
|
+
"done"
|
|
23
|
+
],
|
|
24
|
+
trying: [
|
|
25
|
+
"queued",
|
|
26
|
+
"trying",
|
|
27
|
+
"posted",
|
|
28
|
+
"awaiting-result",
|
|
29
|
+
"settled-live",
|
|
30
|
+
"done"
|
|
31
|
+
],
|
|
32
|
+
posted: [
|
|
33
|
+
"posted",
|
|
34
|
+
"trying",
|
|
35
|
+
"awaiting-result",
|
|
36
|
+
"settled-live",
|
|
37
|
+
"done"
|
|
38
|
+
],
|
|
39
|
+
"awaiting-result": ["awaiting-result", "done"],
|
|
40
|
+
"settled-live": ["settled-live", "done"],
|
|
41
|
+
done: []
|
|
42
|
+
};
|
|
43
|
+
const transition = (s, entry, next, outcome) => {
|
|
44
|
+
const prev = entry.state;
|
|
45
|
+
if (!legalTransitions[prev.at].includes(next.at)) throw new Error(`statewire: illegal pending transition ${prev.at} -> ${next.at}`);
|
|
46
|
+
const settles = !isResolved(prev) && isResolved(next);
|
|
47
|
+
if (settles && outcome === void 0) throw new Error(`statewire: settling transition ${prev.at} -> ${next.at} without an outcome`);
|
|
48
|
+
const covers = !isStateSettled(prev) && isStateSettled(next);
|
|
49
|
+
entry.state = next;
|
|
50
|
+
if (covers) s.lifecycle?.onCovered?.(entry.inv);
|
|
51
|
+
if (settles && outcome !== void 0) if (outcome.ok) {
|
|
52
|
+
entry.resolve(outcome.payload);
|
|
53
|
+
s.lifecycle?.onSettle(entry.inv, {
|
|
54
|
+
type: "accepted",
|
|
55
|
+
payload: outcome.payload
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
entry.reject(new StatewireSendError(outcome.failure));
|
|
59
|
+
s.lifecycle?.onSettle(entry.inv, {
|
|
60
|
+
type: "rejected",
|
|
61
|
+
error: new StatewireSendError(outcome.failure)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (next.at === "done") s.pending = s.pending.filter((p) => p !== entry);
|
|
65
|
+
};
|
|
66
|
+
const carried = (state) => state.at === "queued" ? {
|
|
67
|
+
posted: false,
|
|
68
|
+
failedAttempts: 0
|
|
69
|
+
} : state.at === "trying" || state.at === "posted" ? {
|
|
70
|
+
posted: state.at === "posted",
|
|
71
|
+
failedAttempts: state.failedAttempts
|
|
72
|
+
} : {
|
|
73
|
+
posted: state.posted,
|
|
74
|
+
failedAttempts: state.failedAttempts
|
|
75
|
+
};
|
|
76
|
+
const newClientId = () => `c-${randomId()}`;
|
|
77
|
+
const rejectedSend = (failure) => {
|
|
78
|
+
const result = Promise.reject(new StatewireSendError(failure));
|
|
79
|
+
result.catch(() => {});
|
|
80
|
+
return result;
|
|
81
|
+
};
|
|
82
|
+
const createSession = (options) => {
|
|
83
|
+
const store = options.sessionStore ?? createMemorySessionStore();
|
|
84
|
+
const stored = store.load();
|
|
85
|
+
const s = {
|
|
86
|
+
record: stored ?? {
|
|
87
|
+
clientId: newClientId(),
|
|
88
|
+
nextSeq: 1
|
|
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
|
+
recordPersisted: stored !== null,
|
|
99
|
+
store,
|
|
100
|
+
lastAck: -1,
|
|
101
|
+
pending: [],
|
|
102
|
+
canonical: void 0,
|
|
103
|
+
lastPublished: void 0,
|
|
104
|
+
changed: [],
|
|
105
|
+
source: {
|
|
106
|
+
snapshot: () => s.canonical,
|
|
107
|
+
takeChanged: () => {
|
|
108
|
+
const changed = s.changed;
|
|
109
|
+
s.changed = [];
|
|
110
|
+
return changed;
|
|
111
|
+
},
|
|
112
|
+
setOnChange: (listener) => {
|
|
113
|
+
s.notify = listener ?? (() => {});
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
attach: "detached",
|
|
117
|
+
attachEpoch: 0,
|
|
118
|
+
running: false,
|
|
119
|
+
pumping: false,
|
|
120
|
+
transport: null,
|
|
121
|
+
waiters: [],
|
|
122
|
+
lifecycle: null,
|
|
123
|
+
notify: () => {},
|
|
124
|
+
emitEvent: () => {},
|
|
125
|
+
send: (command) => sendCommand(s, command),
|
|
126
|
+
commands: createMethodProxy((method, params) => s.send({
|
|
127
|
+
method,
|
|
128
|
+
params
|
|
129
|
+
}))
|
|
130
|
+
};
|
|
131
|
+
bindCommandsHost(s.commands, { setListener: (listener) => {
|
|
132
|
+
s.lifecycle = listener;
|
|
133
|
+
} });
|
|
134
|
+
return s;
|
|
135
|
+
};
|
|
136
|
+
const publish = (s) => {
|
|
137
|
+
if (s.canonical === s.lastPublished) return;
|
|
138
|
+
s.lastPublished = s.canonical;
|
|
139
|
+
s.notify();
|
|
140
|
+
};
|
|
141
|
+
const wake = (s) => {
|
|
142
|
+
const waiters = s.waiters;
|
|
143
|
+
s.waiters = [];
|
|
144
|
+
for (const waiter of waiters) waiter();
|
|
145
|
+
};
|
|
146
|
+
const waitForWake = (s) => new Promise((r) => s.waiters.push(r));
|
|
147
|
+
const sleepUntilWake = (s, delayMs) => new Promise((resolve) => {
|
|
148
|
+
const waiter = () => {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
resolve();
|
|
151
|
+
};
|
|
152
|
+
const timer = setTimeout(() => {
|
|
153
|
+
s.waiters = s.waiters.filter((w) => w !== waiter);
|
|
154
|
+
resolve();
|
|
155
|
+
}, delayMs);
|
|
156
|
+
s.waiters.push(waiter);
|
|
157
|
+
});
|
|
158
|
+
const settleAll = (s, detach, verdict) => {
|
|
159
|
+
const cause = {
|
|
160
|
+
evicted: "session-lost",
|
|
161
|
+
unmount: "unmount",
|
|
162
|
+
gone: "stream-gone",
|
|
163
|
+
superseded: "superseded"
|
|
164
|
+
}[detach];
|
|
165
|
+
const batch = s.pending;
|
|
166
|
+
const detachFailure = (entry) => {
|
|
167
|
+
const state = entry.state;
|
|
168
|
+
if (state.at === "awaiting-result") return {
|
|
169
|
+
fate: "result-unavailable",
|
|
170
|
+
cause
|
|
171
|
+
};
|
|
172
|
+
if (state.at === "queued") return {
|
|
173
|
+
fate: "aborted",
|
|
174
|
+
cause: batch.some((p) => p.seq < entry.seq && p.state.at !== "queued") ? "cascade" : cause
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
fate: "lost",
|
|
178
|
+
cause
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
for (const entry of [...batch]) {
|
|
182
|
+
const failure = entry === verdict?.entry ? verdict.failure : detachFailure(entry);
|
|
183
|
+
transition(s, entry, { at: "done" }, {
|
|
184
|
+
ok: false,
|
|
185
|
+
failure
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const evict = (s, verdict) => {
|
|
190
|
+
settleAll(s, "evicted", verdict);
|
|
191
|
+
s.record = {
|
|
192
|
+
clientId: newClientId(),
|
|
193
|
+
nextSeq: 1
|
|
194
|
+
};
|
|
195
|
+
s.store.save(s.record);
|
|
196
|
+
s.attach = "detached";
|
|
197
|
+
publish(s);
|
|
198
|
+
wake(s);
|
|
199
|
+
};
|
|
200
|
+
const noteAcked = (s, seq) => {
|
|
201
|
+
for (const entry of s.pending) {
|
|
202
|
+
if (entry.seq > seq || isStateSettled(entry.state)) continue;
|
|
203
|
+
s.lifecycle?.onAck(entry.inv);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const spliceAcked = (s, seq, settleVoid) => {
|
|
207
|
+
for (const entry of [...s.pending]) {
|
|
208
|
+
const state = entry.state;
|
|
209
|
+
if (entry.seq > seq || isStateSettled(state)) continue;
|
|
210
|
+
if (state.at === "settled-live") transition(s, entry, { at: "done" });
|
|
211
|
+
else if (settleVoid && !state.pendingMarked) transition(s, entry, { at: "done" }, {
|
|
212
|
+
ok: true,
|
|
213
|
+
payload: void 0
|
|
214
|
+
});
|
|
215
|
+
else transition(s, entry, {
|
|
216
|
+
at: "awaiting-result",
|
|
217
|
+
...carried(state)
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
const protocolError = (message) => {
|
|
222
|
+
throw new Error(`statewire: protocol error: ${message}`);
|
|
223
|
+
};
|
|
224
|
+
const applyResponse = (s, response) => {
|
|
225
|
+
const entry = s.pending.find((p) => p.seq === response.seq);
|
|
226
|
+
if (!entry) return;
|
|
227
|
+
const state = entry.state;
|
|
228
|
+
if (state.at === "done") return;
|
|
229
|
+
switch (response.type) {
|
|
230
|
+
case "pending":
|
|
231
|
+
if (state.at === "queued" || state.at === "trying" || state.at === "posted") transition(s, entry, {
|
|
232
|
+
...state,
|
|
233
|
+
pendingMarked: true
|
|
234
|
+
});
|
|
235
|
+
break;
|
|
236
|
+
case "accepted":
|
|
237
|
+
if (state.at === "settled-live") break;
|
|
238
|
+
transition(s, entry, state.at === "awaiting-result" ? { at: "done" } : {
|
|
239
|
+
at: "settled-live",
|
|
240
|
+
...carried(state)
|
|
241
|
+
}, {
|
|
242
|
+
ok: true,
|
|
243
|
+
payload: response.payload
|
|
244
|
+
});
|
|
245
|
+
break;
|
|
246
|
+
case "rejected":
|
|
247
|
+
transition(s, entry, { at: "done" }, {
|
|
248
|
+
ok: false,
|
|
249
|
+
failure: response.code === "unknown-command" ? {
|
|
250
|
+
fate: "unknown-command",
|
|
251
|
+
cause: "server"
|
|
252
|
+
} : response.code === "invalid-params" ? {
|
|
253
|
+
fate: "invalid-params",
|
|
254
|
+
cause: "server",
|
|
255
|
+
message: response.message ?? protocolError("rejected response missing message")
|
|
256
|
+
} : {
|
|
257
|
+
fate: "rejected",
|
|
258
|
+
cause: "server",
|
|
259
|
+
message: response.message ?? protocolError("rejected response missing message"),
|
|
260
|
+
...response.payload !== void 0 && { payload: response.payload }
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
break;
|
|
264
|
+
case "crashed":
|
|
265
|
+
case "result-unavailable":
|
|
266
|
+
if (state.at === "settled-live") break;
|
|
267
|
+
transition(s, entry, state.at === "awaiting-result" ? { at: "done" } : {
|
|
268
|
+
at: "settled-live",
|
|
269
|
+
...carried(state)
|
|
270
|
+
}, {
|
|
271
|
+
ok: false,
|
|
272
|
+
failure: {
|
|
273
|
+
fate: response.type,
|
|
274
|
+
cause: "server"
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const ingestOps = (s, ops) => {
|
|
281
|
+
const applied = applyOps(s.canonical, ops);
|
|
282
|
+
s.canonical = applied.state;
|
|
283
|
+
s.changed.push(...applied.changedPaths);
|
|
284
|
+
return applied.events;
|
|
285
|
+
};
|
|
286
|
+
const publishIngested = (s, events) => {
|
|
287
|
+
publish(s);
|
|
288
|
+
for (const event of events) s.emitEvent(event);
|
|
289
|
+
wake(s);
|
|
290
|
+
};
|
|
291
|
+
const handleSnapshot = (s, { ops, lastSeq }) => {
|
|
292
|
+
if (lastSeq === -1 && s.attach !== "detached") {
|
|
293
|
+
evict(s);
|
|
294
|
+
s.transport?.connection.reconnect();
|
|
295
|
+
publishIngested(s, ingestOps(s, ops));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const events = ingestOps(s, ops);
|
|
299
|
+
s.attach = "attached";
|
|
300
|
+
s.attachEpoch += 1;
|
|
301
|
+
noteAcked(s, lastSeq);
|
|
302
|
+
spliceAcked(s, lastSeq, false);
|
|
303
|
+
s.lastAck = lastSeq;
|
|
304
|
+
for (const entry of s.pending) {
|
|
305
|
+
const state = entry.state;
|
|
306
|
+
if (entry.seq <= lastSeq) {
|
|
307
|
+
if (state.at === "awaiting-result" && state.posted) transition(s, entry, {
|
|
308
|
+
...state,
|
|
309
|
+
posted: false
|
|
310
|
+
});
|
|
311
|
+
if (state.at === "awaiting-result") pump(s);
|
|
312
|
+
} else if (state.at === "posted") {
|
|
313
|
+
transition(s, entry, {
|
|
314
|
+
at: "trying",
|
|
315
|
+
pendingMarked: state.pendingMarked,
|
|
316
|
+
failedAttempts: state.failedAttempts
|
|
317
|
+
});
|
|
318
|
+
pump(s);
|
|
319
|
+
} else if (state.at === "settled-live" && state.posted) {
|
|
320
|
+
transition(s, entry, {
|
|
321
|
+
...state,
|
|
322
|
+
posted: false
|
|
323
|
+
});
|
|
324
|
+
pump(s);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
publishIngested(s, events);
|
|
328
|
+
};
|
|
329
|
+
const handleOps = (s, { ops, res, ack }) => {
|
|
330
|
+
const horizon = ack === void 0 ? s.lastAck : Math.max(s.lastAck, ack);
|
|
331
|
+
if (res !== void 0) {
|
|
332
|
+
for (const response of res) if (response.seq > horizon) protocolError("response seq beyond the ack");
|
|
333
|
+
}
|
|
334
|
+
const events = ingestOps(s, ops);
|
|
335
|
+
if (ack !== void 0) noteAcked(s, ack);
|
|
336
|
+
if (res !== void 0) for (const response of res) applyResponse(s, response);
|
|
337
|
+
if (ack !== void 0) {
|
|
338
|
+
spliceAcked(s, ack, true);
|
|
339
|
+
s.lastAck = Math.max(s.lastAck, ack);
|
|
340
|
+
}
|
|
341
|
+
publishIngested(s, events);
|
|
342
|
+
};
|
|
343
|
+
const handleFinish = (s, { reason }) => {
|
|
344
|
+
if (reason === "evicted" || reason === "error") return;
|
|
345
|
+
s.attach = "finished";
|
|
346
|
+
settleAll(s, reason === "superseded" ? "superseded" : "gone");
|
|
347
|
+
publish(s);
|
|
348
|
+
wake(s);
|
|
349
|
+
};
|
|
350
|
+
const isFinished = (s) => s.attach === "finished";
|
|
351
|
+
const sendable = (p) => p.state.at === "queued" || p.state.at === "trying" || (p.state.at === "awaiting-result" || p.state.at === "settled-live") && !p.state.posted;
|
|
352
|
+
const pump = async (s) => {
|
|
353
|
+
if (s.pumping) return;
|
|
354
|
+
s.pumping = true;
|
|
355
|
+
try {
|
|
356
|
+
while (s.running && !isFinished(s)) {
|
|
357
|
+
const entry = s.pending.find(sendable);
|
|
358
|
+
if (!entry) return;
|
|
359
|
+
const transport = s.transport;
|
|
360
|
+
if (s.attach !== "attached" || !transport) {
|
|
361
|
+
await waitForWake(s);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const clientId = s.record.clientId;
|
|
365
|
+
const epoch = s.attachEpoch;
|
|
366
|
+
if (entry.state.at === "queued") transition(s, entry, {
|
|
367
|
+
at: "trying",
|
|
368
|
+
pendingMarked: entry.state.pendingMarked,
|
|
369
|
+
failedAttempts: 0
|
|
370
|
+
});
|
|
371
|
+
let result;
|
|
372
|
+
try {
|
|
373
|
+
result = await transport.send({
|
|
374
|
+
seq: entry.seq,
|
|
375
|
+
command: entry.command
|
|
376
|
+
});
|
|
377
|
+
} catch (error) {
|
|
378
|
+
if (!s.running || isFinished(s)) return;
|
|
379
|
+
if (!s.pending.includes(entry)) continue;
|
|
380
|
+
const state = inFlight(entry.state);
|
|
381
|
+
transition(s, entry, {
|
|
382
|
+
...state,
|
|
383
|
+
failedAttempts: state.failedAttempts + 1
|
|
384
|
+
});
|
|
385
|
+
await sleepUntilWake(s, retryDelay(error, state.failedAttempts + 1));
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (!s.running) return;
|
|
389
|
+
if (!s.pending.includes(entry)) continue;
|
|
390
|
+
if (result.type === "receipt") {
|
|
391
|
+
const response = result.response;
|
|
392
|
+
const state = entry.state;
|
|
393
|
+
if (state.at === "trying" || state.at === "posted") transition(s, entry, {
|
|
394
|
+
at: "posted",
|
|
395
|
+
pendingMarked: state.pendingMarked,
|
|
396
|
+
failedAttempts: state.failedAttempts
|
|
397
|
+
});
|
|
398
|
+
else if (state.at === "awaiting-result" || state.at === "settled-live") transition(s, entry, {
|
|
399
|
+
...state,
|
|
400
|
+
posted: true
|
|
401
|
+
});
|
|
402
|
+
s.lifecycle?.onPosted?.(entry.inv);
|
|
403
|
+
if (response !== void 0) {
|
|
404
|
+
applyResponse(s, response);
|
|
405
|
+
publish(s);
|
|
406
|
+
}
|
|
407
|
+
} else if (result.type === "rejected") {
|
|
408
|
+
transition(s, entry, { at: "done" }, {
|
|
409
|
+
ok: false,
|
|
410
|
+
failure: {
|
|
411
|
+
fate: "rejected",
|
|
412
|
+
cause: "server",
|
|
413
|
+
message: result.message
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
publish(s);
|
|
417
|
+
} else if (result.type === "stale-lease") {
|
|
418
|
+
if (epoch === s.attachEpoch) await waitForWake(s);
|
|
419
|
+
} else if (result.type === "unknown-client") {
|
|
420
|
+
if (epoch === s.attachEpoch) {
|
|
421
|
+
const state = entry.state;
|
|
422
|
+
if (state.at === "trying" && state.failedAttempts === 0) transition(s, entry, {
|
|
423
|
+
at: "queued",
|
|
424
|
+
pendingMarked: state.pendingMarked
|
|
425
|
+
});
|
|
426
|
+
s.transport?.connection.reconnect();
|
|
427
|
+
while (s.running && !isFinished(s) && epoch === s.attachEpoch && clientId === s.record.clientId) await waitForWake(s);
|
|
428
|
+
}
|
|
429
|
+
} else if (result.type === "seq-gap") {
|
|
430
|
+
if (clientId === s.record.clientId) {
|
|
431
|
+
console.error(`statewire: server reports a seq gap for seq ${entry.seq}; treating the session as corrupt and rotating the client id`);
|
|
432
|
+
const state = inFlight(entry.state);
|
|
433
|
+
const failure = state.at === "awaiting-result" ? {
|
|
434
|
+
fate: "result-unavailable",
|
|
435
|
+
cause: "session-lost"
|
|
436
|
+
} : {
|
|
437
|
+
fate: state.failedAttempts > 0 ? "lost" : "aborted",
|
|
438
|
+
cause: "session-lost"
|
|
439
|
+
};
|
|
440
|
+
evict(s, {
|
|
441
|
+
entry,
|
|
442
|
+
failure
|
|
443
|
+
});
|
|
444
|
+
s.transport?.connection.reconnect();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} finally {
|
|
449
|
+
s.pumping = false;
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
const start = (s) => {
|
|
453
|
+
if (!s.recordPersisted) {
|
|
454
|
+
s.recordPersisted = true;
|
|
455
|
+
s.store.save(s.record);
|
|
456
|
+
}
|
|
457
|
+
s.running = true;
|
|
458
|
+
pump(s);
|
|
459
|
+
};
|
|
460
|
+
const stop = (s) => {
|
|
461
|
+
s.running = false;
|
|
462
|
+
wake(s);
|
|
463
|
+
settleAll(s, "unmount");
|
|
464
|
+
};
|
|
465
|
+
const setTransport = (s, transport) => {
|
|
466
|
+
if (s.transport === transport) return;
|
|
467
|
+
s.transport = transport;
|
|
468
|
+
wake(s);
|
|
469
|
+
};
|
|
470
|
+
const sendCommand = (s, command) => {
|
|
471
|
+
if (!s.running) return rejectedSend({
|
|
472
|
+
fate: "aborted",
|
|
473
|
+
cause: "unmount"
|
|
474
|
+
});
|
|
475
|
+
if (isFinished(s)) return rejectedSend({
|
|
476
|
+
fate: "aborted",
|
|
477
|
+
cause: "stream-gone"
|
|
478
|
+
});
|
|
479
|
+
const seq = s.record.nextSeq;
|
|
480
|
+
s.record = {
|
|
481
|
+
...s.record,
|
|
482
|
+
nextSeq: seq + 1
|
|
483
|
+
};
|
|
484
|
+
s.store.save(s.record);
|
|
485
|
+
const entry = {
|
|
486
|
+
seq,
|
|
487
|
+
command,
|
|
488
|
+
inv: {
|
|
489
|
+
method: command.method,
|
|
490
|
+
params: command.params,
|
|
491
|
+
ts: Date.now(),
|
|
492
|
+
seq,
|
|
493
|
+
clientId: s.record.clientId
|
|
494
|
+
},
|
|
495
|
+
state: {
|
|
496
|
+
at: "queued",
|
|
497
|
+
pendingMarked: false
|
|
498
|
+
},
|
|
499
|
+
resolve: () => {},
|
|
500
|
+
reject: () => {}
|
|
501
|
+
};
|
|
502
|
+
const result = new Promise((resolve, reject) => {
|
|
503
|
+
entry.resolve = resolve;
|
|
504
|
+
entry.reject = reject;
|
|
505
|
+
});
|
|
506
|
+
result.catch(() => {});
|
|
507
|
+
s.pending.push(entry);
|
|
508
|
+
s.lifecycle?.onStart(entry.inv);
|
|
509
|
+
pump(s);
|
|
510
|
+
return result;
|
|
511
|
+
};
|
|
512
|
+
const useStatewireSession = (options, emit) => {
|
|
513
|
+
const [s] = useState(() => createSession(options));
|
|
514
|
+
useEffect(() => {
|
|
515
|
+
s.emitEvent = emit;
|
|
516
|
+
return () => {
|
|
517
|
+
s.emitEvent = () => {};
|
|
518
|
+
};
|
|
519
|
+
}, [s, emit]);
|
|
520
|
+
useEffect(() => {
|
|
521
|
+
start(s);
|
|
522
|
+
return () => stop(s);
|
|
523
|
+
}, [s]);
|
|
524
|
+
return s;
|
|
525
|
+
};
|
|
526
|
+
//#endregion
|
|
527
|
+
export { setTransport, useStatewireSession };
|
|
528
|
+
|
|
529
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","names":["useEffect","useState","applyOps","StatewirePath","StatewireResponse","bindCommandsHost","CommandsInvocation","CommandsListener","Statewire","StatewireClient","createMemorySessionStore","StatewireSessionRecord","StatewireSessionStore","StatewireSessionStoreOptions","retryDelay","createMethodProxy","randomId","StatewireSendError","RemoteLink","StatewireRemoteProps","StatewireSubmitResult","StatewireWireCommand","PendingState","at","pendingMarked","failedAttempts","posted","Pending","seq","command","inv","state","resolve","payload","reject","error","Outcome","ok","failure","SendFailure","isResolved","isStateSettled","inFlight","Error","legalTransitions","Record","queued","trying","done","transition","s","Session","entry","next","outcome","prev","includes","settles","undefined","covers","lifecycle","onCovered","onSettle","type","pending","filter","p","carried","Exclude","newClientId","rejectedSend","Promise","result","catch","AttachState","record","ctx","recordPersisted","store","lastAck","canonical","lastPublished","changed","source","StateSource","attach","attachEpoch","running","pumping","transport","waiters","notify","emitEvent","event","send","commands","params","createSession","options","sessionStore","stored","load","clientId","nextSeq","onSnapshot","args","handleSnapshot","onOps","handleOps","onFinish","handleFinish","snapshot","takeChanged","setOnChange","listener","sendCommand","method","setListener","publish","wake","waiter","waitForWake","r","push","sleepUntilWake","delayMs","clearTimeout","timer","setTimeout","w","settleAll","detach","verdict","cause","evicted","unmount","gone","superseded","const","batch","detachFailure","fate","cascades","some","evict","save","noteAcked","onAck","spliceAcked","settleVoid","protocolError","message","applyResponse","response","find","code","ingestOps","ops","applied","changedPaths","events","publishIngested","lastSeq","connection","reconnect","pump","res","ack","horizon","Math","max","reason","isFinished","sendable","epoch","onPosted","console","start","stop","setTransport","ts","Date","now","onStart","useStatewireSession","emit"],"sources":["../../src/client/session.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { applyOps } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport type { StatewireResponse } from \"./connection\";\nimport { bindCommandsHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\nimport { createMemorySessionStore } from \"./sessionStore\";\nimport type {\n StatewireSessionRecord,\n StatewireSessionStore,\n StatewireSessionStoreOptions,\n} from \"./sessionStore\";\nimport { retryDelay } from \"./timing\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport { randomId } from \"../util/randomId\";\nimport { StatewireSendError } from \"../types\";\nimport type {\n RemoteLink,\n StatewireRemoteProps,\n StatewireSubmitResult,\n StatewireWireCommand,\n} from \"../transports/sse\";\n\n/**\n * queued/trying/posted: overlay live, result pending. awaiting-result: acked\n * (overlay gone) but the result rides a late res; `posted: false` re-sends.\n * settled-live: result settled but the overlay stays until the covering ack.\n * done: removed from the queue.\n */\ntype PendingState =\n | { at: \"queued\"; pendingMarked: boolean }\n | { at: \"trying\"; pendingMarked: boolean; failedAttempts: number }\n | { at: \"posted\"; pendingMarked: boolean; failedAttempts: number }\n | { at: \"awaiting-result\"; posted: boolean; failedAttempts: number }\n | { at: \"settled-live\"; posted: boolean; failedAttempts: number }\n | { at: \"done\" };\n\ntype Pending = {\n readonly seq: number;\n readonly command: StatewireWireCommand;\n readonly inv: CommandsInvocation;\n state: PendingState;\n resolve: (payload: unknown) => void;\n reject: (error: StatewireSendError) => void;\n};\n\ntype Outcome =\n | { ok: true; payload: unknown }\n | { ok: false; failure: StatewireClient.SendFailure };\n\nconst isResolved = (state: PendingState) =>\n state.at === \"settled-live\" || state.at === \"done\";\n\nconst isStateSettled = (state: PendingState) =>\n state.at === \"awaiting-result\" || state.at === \"done\";\n\n// The pump admits an entry as trying before sending; only the pump itself\n// requeues, and a done entry is never still pending.\nconst inFlight = (state: PendingState) => {\n if (state.at === \"queued\" || state.at === \"done\") {\n throw new Error(`statewire: ${state.at} entry observed after a send`);\n }\n return state;\n};\n\nconst legalTransitions: Record<PendingState[\"at\"], PendingState[\"at\"][]> = {\n queued: [\"queued\", \"trying\", \"awaiting-result\", \"settled-live\", \"done\"],\n // trying -> queued: an answered unknown-client proves the seq was never admitted.\n trying: [\n \"queued\",\n \"trying\",\n \"posted\",\n \"awaiting-result\",\n \"settled-live\",\n \"done\",\n ],\n posted: [\"posted\", \"trying\", \"awaiting-result\", \"settled-live\", \"done\"],\n \"awaiting-result\": [\"awaiting-result\", \"done\"],\n \"settled-live\": [\"settled-live\", \"done\"],\n done: [],\n};\n\nconst transition = (\n s: Session,\n entry: Pending,\n next: PendingState,\n outcome?: Outcome,\n) => {\n const prev = entry.state;\n if (!legalTransitions[prev.at].includes(next.at)) {\n throw new Error(\n `statewire: illegal pending transition ${prev.at} -> ${next.at}`,\n );\n }\n const settles = !isResolved(prev) && isResolved(next);\n if (settles && outcome === undefined) {\n throw new Error(\n `statewire: settling transition ${prev.at} -> ${next.at} without an outcome`,\n );\n }\n const covers = !isStateSettled(prev) && isStateSettled(next);\n entry.state = next;\n if (covers) s.lifecycle?.onCovered?.(entry.inv);\n if (settles && outcome !== undefined) {\n if (outcome.ok) {\n entry.resolve(outcome.payload);\n s.lifecycle?.onSettle(entry.inv, {\n type: \"accepted\",\n payload: outcome.payload,\n });\n } else {\n entry.reject(new StatewireSendError(outcome.failure));\n s.lifecycle?.onSettle(entry.inv, {\n type: \"rejected\",\n error: new StatewireSendError(outcome.failure),\n });\n }\n }\n if (next.at === \"done\") {\n s.pending = s.pending.filter((p) => p !== entry);\n }\n};\n\nconst carried = (\n state: Exclude<PendingState, { at: \"done\" }>,\n): { posted: boolean; failedAttempts: number } =>\n state.at === \"queued\"\n ? { posted: false, failedAttempts: 0 }\n : state.at === \"trying\" || state.at === \"posted\"\n ? { posted: state.at === \"posted\", failedAttempts: state.failedAttempts }\n : { posted: state.posted, failedAttempts: state.failedAttempts };\n\nconst newClientId = () => `c-${randomId()}`;\n\nconst rejectedSend = (\n failure: StatewireClient.SendFailure,\n): Promise<unknown> => {\n const result = Promise.reject<unknown>(new StatewireSendError(failure));\n result.catch(() => {});\n return result;\n};\n\ntype AttachState = \"detached\" | \"attached\" | \"finished\";\n\ntype Session = {\n record: StatewireSessionRecord;\n ctx: StatewireRemoteProps;\n recordPersisted: boolean;\n store: StatewireSessionStore;\n lastAck: number;\n pending: Pending[];\n canonical: unknown;\n lastPublished: unknown;\n changed: StatewirePath[];\n source: Statewire.StateSource;\n attach: AttachState;\n attachEpoch: number;\n running: boolean;\n pumping: boolean;\n transport: RemoteLink | null;\n waiters: (() => void)[];\n lifecycle: CommandsListener | null;\n notify: () => void;\n emitEvent: (event: unknown) => void;\n send: (command: StatewireWireCommand) => Promise<unknown>;\n commands: Record<string, (...params: unknown[]) => Promise<unknown>>;\n};\n\nconst createSession = (options: StatewireSessionStoreOptions): Session => {\n const store = options.sessionStore ?? createMemorySessionStore();\n const stored = store.load();\n const record = stored ?? { clientId: newClientId(), nextSeq: 1 };\n const s: Session = {\n record,\n ctx: {\n get clientId() {\n return s.record.clientId;\n },\n onSnapshot: (args) => handleSnapshot(s, args),\n onOps: (args) => handleOps(s, args),\n onFinish: (args) => handleFinish(s, args),\n },\n recordPersisted: stored !== null,\n store,\n lastAck: -1,\n pending: [],\n canonical: undefined,\n lastPublished: undefined,\n changed: [],\n source: {\n snapshot: () => s.canonical,\n takeChanged: () => {\n const changed = s.changed;\n s.changed = [];\n return changed;\n },\n setOnChange: (listener) => {\n s.notify = listener ?? (() => {});\n },\n },\n attach: \"detached\",\n attachEpoch: 0,\n running: false,\n pumping: false,\n transport: null,\n waiters: [],\n lifecycle: null,\n notify: () => {},\n emitEvent: () => {},\n send: (command) => sendCommand(s, command),\n commands: createMethodProxy((method, params) => s.send({ method, params })),\n };\n bindCommandsHost(s.commands, {\n setListener: (listener) => {\n s.lifecycle = listener;\n },\n });\n return s;\n};\n\nconst publish = (s: Session) => {\n if (s.canonical === s.lastPublished) return;\n s.lastPublished = s.canonical;\n s.notify();\n};\n\nconst wake = (s: Session) => {\n const waiters = s.waiters;\n s.waiters = [];\n for (const waiter of waiters) waiter();\n};\n\nconst waitForWake = (s: Session) => new Promise<void>((r) => s.waiters.push(r));\n\nconst sleepUntilWake = (s: Session, delayMs: number) =>\n new Promise<void>((resolve) => {\n const waiter = () => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n s.waiters = s.waiters.filter((w) => w !== waiter);\n resolve();\n }, delayMs);\n s.waiters.push(waiter);\n });\n\nconst settleAll = (\n s: Session,\n detach: \"evicted\" | \"unmount\" | \"gone\" | \"superseded\",\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\n const cause = (\n {\n evicted: \"session-lost\",\n unmount: \"unmount\",\n gone: \"stream-gone\",\n superseded: \"superseded\",\n } as const\n )[detach];\n const batch = s.pending;\n const detachFailure = (entry: Pending): StatewireClient.SendFailure => {\n const state = entry.state;\n if (state.at === \"awaiting-result\")\n return { fate: \"result-unavailable\", cause };\n if (state.at === \"queued\") {\n const cascades = batch.some(\n (p) => p.seq < entry.seq && p.state.at !== \"queued\",\n );\n return { fate: \"aborted\", cause: cascades ? \"cascade\" : cause };\n }\n return { fate: \"lost\", cause };\n };\n for (const entry of [...batch]) {\n const failure =\n entry === verdict?.entry ? verdict.failure : detachFailure(entry);\n transition(s, entry, { at: \"done\" }, { ok: false, failure });\n }\n};\n\nconst evict = (\n s: Session,\n verdict?: {\n entry: Pending;\n failure: StatewireClient.SendFailure;\n },\n) => {\n settleAll(s, \"evicted\", verdict);\n s.record = { clientId: newClientId(), nextSeq: 1 };\n s.store.save(s.record);\n s.attach = \"detached\";\n publish(s);\n wake(s);\n};\n\nconst noteAcked = (s: Session, seq: number) => {\n for (const entry of s.pending) {\n if (entry.seq > seq || isStateSettled(entry.state)) continue;\n s.lifecycle?.onAck(entry.inv);\n }\n};\n\nconst spliceAcked = (s: Session, seq: number, settleVoid: boolean) => {\n for (const entry of [...s.pending]) {\n const state = entry.state;\n if (entry.seq > seq || isStateSettled(state)) continue;\n if (state.at === \"settled-live\") {\n transition(s, entry, { at: \"done\" });\n } else if (settleVoid && !state.pendingMarked) {\n transition(s, entry, { at: \"done\" }, { ok: true, payload: undefined });\n } else {\n transition(s, entry, { at: \"awaiting-result\", ...carried(state) });\n }\n }\n};\n\nconst protocolError = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nconst applyResponse = (s: Session, response: StatewireResponse) => {\n const entry = s.pending.find((p) => p.seq === response.seq);\n if (!entry) return;\n const state = entry.state;\n if (state.at === \"done\") return;\n switch (response.type) {\n case \"pending\":\n if (\n state.at === \"queued\" ||\n state.at === \"trying\" ||\n state.at === \"posted\"\n ) {\n transition(s, entry, { ...state, pendingMarked: true });\n }\n break;\n case \"accepted\":\n if (state.at === \"settled-live\") break;\n transition(\n s,\n entry,\n state.at === \"awaiting-result\"\n ? { at: \"done\" }\n : { at: \"settled-live\", ...carried(state) },\n { ok: true, payload: response.payload },\n );\n break;\n case \"rejected\":\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure:\n response.code === \"unknown-command\"\n ? { fate: \"unknown-command\", cause: \"server\" }\n : response.code === \"invalid-params\"\n ? {\n fate: \"invalid-params\",\n cause: \"server\",\n message:\n response.message ??\n protocolError(\"rejected response missing message\"),\n }\n : {\n fate: \"rejected\",\n cause: \"server\",\n message:\n response.message ??\n protocolError(\"rejected response missing message\"),\n ...(response.payload !== undefined && {\n payload: response.payload,\n }),\n },\n },\n );\n break;\n case \"crashed\":\n case \"result-unavailable\":\n if (state.at === \"settled-live\") break;\n transition(\n s,\n entry,\n state.at === \"awaiting-result\"\n ? { at: \"done\" }\n : { at: \"settled-live\", ...carried(state) },\n { ok: false, failure: { fate: response.type, cause: \"server\" } },\n );\n break;\n }\n};\n\nconst ingestOps = (s: Session, ops: unknown[]) => {\n const applied = applyOps(s.canonical, ops);\n s.canonical = applied.state;\n s.changed.push(...applied.changedPaths);\n return applied.events;\n};\n\nconst publishIngested = (s: Session, events: unknown[]) => {\n publish(s);\n for (const event of events) s.emitEvent(event);\n wake(s);\n};\n\nconst handleSnapshot = (\n s: Session,\n { ops, lastSeq }: { ops: unknown[]; lastSeq: number },\n) => {\n if (lastSeq === -1 && s.attach !== \"detached\") {\n evict(s);\n s.transport?.connection.reconnect();\n publishIngested(s, ingestOps(s, ops));\n return;\n }\n const events = ingestOps(s, ops);\n s.attach = \"attached\";\n s.attachEpoch += 1;\n // The attach snapshot is not the true covering envelope: acked entries\n // stay awaiting their result and re-send under seq dedup.\n noteAcked(s, lastSeq);\n spliceAcked(s, lastSeq, false);\n s.lastAck = lastSeq;\n for (const entry of s.pending) {\n const state = entry.state;\n if (entry.seq <= lastSeq) {\n if (state.at === \"awaiting-result\" && state.posted) {\n transition(s, entry, { ...state, posted: false });\n }\n if (state.at === \"awaiting-result\") void pump(s);\n } else if (state.at === \"posted\") {\n // Above the ack watermark a prior receipt no longer counts.\n transition(s, entry, {\n at: \"trying\",\n pendingMarked: state.pendingMarked,\n failedAttempts: state.failedAttempts,\n });\n void pump(s);\n } else if (state.at === \"settled-live\" && state.posted) {\n transition(s, entry, { ...state, posted: false });\n void pump(s);\n }\n }\n publishIngested(s, events);\n};\n\nconst handleOps = (\n s: Session,\n {\n ops,\n res,\n ack,\n }: { ops: unknown[]; res?: StatewireResponse[]; ack?: number },\n) => {\n const horizon = ack === undefined ? s.lastAck : Math.max(s.lastAck, ack);\n if (res !== undefined) {\n for (const response of res) {\n if (response.seq > horizon) {\n protocolError(\"response seq beyond the ack\");\n }\n }\n }\n const events = ingestOps(s, ops);\n if (ack !== undefined) noteAcked(s, ack);\n if (res !== undefined) for (const response of res) applyResponse(s, response);\n if (ack !== undefined) {\n spliceAcked(s, ack, true);\n s.lastAck = Math.max(s.lastAck, ack);\n }\n publishIngested(s, events);\n};\n\nconst handleFinish = (s: Session, { reason }: { reason: string }) => {\n if (reason === \"evicted\" || reason === \"error\") return;\n s.attach = \"finished\";\n settleAll(s, reason === \"superseded\" ? \"superseded\" : \"gone\");\n publish(s);\n wake(s);\n};\n\nconst isFinished = (s: Session) => s.attach === \"finished\";\n\nconst sendable = (p: Pending) =>\n p.state.at === \"queued\" ||\n p.state.at === \"trying\" ||\n ((p.state.at === \"awaiting-result\" || p.state.at === \"settled-live\") &&\n !p.state.posted);\n\nconst pump = async (s: Session) => {\n if (s.pumping) return;\n s.pumping = true;\n try {\n while (s.running && !isFinished(s)) {\n const entry = s.pending.find(sendable);\n if (!entry) return;\n const transport = s.transport;\n if (s.attach !== \"attached\" || !transport) {\n await waitForWake(s);\n continue;\n }\n const clientId = s.record.clientId;\n const epoch = s.attachEpoch;\n if (entry.state.at === \"queued\") {\n transition(s, entry, {\n at: \"trying\",\n pendingMarked: entry.state.pendingMarked,\n failedAttempts: 0,\n });\n }\n let result: StatewireSubmitResult;\n try {\n result = await transport.send({\n seq: entry.seq,\n command: entry.command,\n });\n } catch (error) {\n if (!s.running || isFinished(s)) return;\n if (!s.pending.includes(entry)) continue;\n const state = inFlight(entry.state);\n transition(s, entry, {\n ...state,\n failedAttempts: state.failedAttempts + 1,\n });\n await sleepUntilWake(s, retryDelay(error, state.failedAttempts + 1));\n continue;\n }\n if (!s.running) return;\n if (!s.pending.includes(entry)) continue;\n if (result.type === \"receipt\") {\n const response = result.response;\n const state = entry.state;\n if (state.at === \"trying\" || state.at === \"posted\") {\n transition(s, entry, {\n at: \"posted\",\n pendingMarked: state.pendingMarked,\n failedAttempts: state.failedAttempts,\n });\n } else if (\n state.at === \"awaiting-result\" ||\n state.at === \"settled-live\"\n ) {\n transition(s, entry, { ...state, posted: true });\n }\n s.lifecycle?.onPosted?.(entry.inv);\n if (response !== undefined) {\n applyResponse(s, response);\n publish(s);\n }\n } else if (result.type === \"rejected\") {\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure: {\n fate: \"rejected\",\n cause: \"server\",\n message: result.message,\n },\n },\n );\n publish(s);\n } else if (result.type === \"stale-lease\") {\n if (epoch === s.attachEpoch) await waitForWake(s);\n } else if (result.type === \"unknown-client\") {\n // Eviction is never decided on the POST leg: re-attach first and let\n // the snapshot's lastSeq deliver the verdict. An answered attempt\n // proves the server never admitted this seq, so a clean entry requeues.\n if (epoch === s.attachEpoch) {\n const state = entry.state;\n if (state.at === \"trying\" && state.failedAttempts === 0) {\n transition(s, entry, {\n at: \"queued\",\n pendingMarked: state.pendingMarked,\n });\n }\n s.transport?.connection.reconnect();\n while (\n s.running &&\n !isFinished(s) &&\n epoch === s.attachEpoch &&\n clientId === s.record.clientId\n ) {\n await waitForWake(s);\n }\n }\n } else if (result.type === \"seq-gap\") {\n if (clientId === s.record.clientId) {\n console.error(\n `statewire: server reports a seq gap for seq ${entry.seq}; ` +\n \"treating the session as corrupt and rotating the client id\",\n );\n // Every attempt answered means the server never saw it; a thrown\n // attempt may have left the machine unanswered, so the verdict is \"lost\".\n const state = inFlight(entry.state);\n const failure: StatewireClient.SendFailure =\n state.at === \"awaiting-result\"\n ? { fate: \"result-unavailable\", cause: \"session-lost\" }\n : {\n fate: state.failedAttempts > 0 ? \"lost\" : \"aborted\",\n cause: \"session-lost\",\n };\n evict(s, { entry, failure });\n s.transport?.connection.reconnect();\n }\n }\n }\n } finally {\n s.pumping = false;\n }\n};\n\nconst start = (s: Session) => {\n if (!s.recordPersisted) {\n s.recordPersisted = true;\n s.store.save(s.record);\n }\n s.running = true;\n void pump(s);\n};\n\nconst stop = (s: Session) => {\n s.running = false;\n wake(s);\n settleAll(s, \"unmount\");\n};\n\nconst setTransport = (s: Session, transport: RemoteLink) => {\n if (s.transport === transport) return;\n s.transport = transport;\n wake(s);\n};\n\nconst sendCommand = (\n s: Session,\n command: StatewireWireCommand,\n): Promise<unknown> => {\n if (!s.running) {\n return rejectedSend({ fate: \"aborted\", cause: \"unmount\" });\n }\n if (isFinished(s)) {\n return rejectedSend({ fate: \"aborted\", cause: \"stream-gone\" });\n }\n const seq = s.record.nextSeq;\n s.record = { ...s.record, nextSeq: seq + 1 };\n s.store.save(s.record);\n const entry: Pending = {\n seq,\n command,\n inv: {\n method: command.method,\n params: command.params,\n ts: Date.now(),\n seq,\n clientId: s.record.clientId,\n },\n state: { at: \"queued\", pendingMarked: false },\n resolve: () => {},\n reject: () => {},\n };\n const result = new Promise<unknown>((resolve, reject) => {\n entry.resolve = resolve;\n entry.reject = reject;\n });\n result.catch(() => {});\n s.pending.push(entry);\n s.lifecycle?.onStart(entry.inv);\n void pump(s);\n return result;\n};\n\nexport const useStatewireSession = (\n options: StatewireSessionStoreOptions,\n emit: (event: unknown) => void,\n): Session => {\n const [s] = useState(() => createSession(options));\n useEffect(() => {\n s.emitEvent = emit;\n return () => {\n s.emitEvent = () => {};\n };\n }, [s, emit]);\n useEffect(() => {\n start(s);\n return () => stop(s);\n }, [s]);\n return s;\n};\n\nexport { setTransport };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/client/sessionStore.d.ts
|
|
2
|
+
type StatewireSessionRecord = {
|
|
3
|
+
clientId: string;
|
|
4
|
+
nextSeq: number;
|
|
5
|
+
};
|
|
6
|
+
type StatewireSessionStore = {
|
|
7
|
+
load: () => StatewireSessionRecord | null;
|
|
8
|
+
save: (record: StatewireSessionRecord) => void;
|
|
9
|
+
};
|
|
10
|
+
type StatewireSessionStoreOptions = {
|
|
11
|
+
sessionStore?: StatewireSessionStore | undefined;
|
|
12
|
+
};
|
|
13
|
+
declare const createMemorySessionStore: () => StatewireSessionStore;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { StatewireSessionRecord, StatewireSessionStore, StatewireSessionStoreOptions, createMemorySessionStore };
|
|
16
|
+
//# sourceMappingURL=sessionStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionStore.d.ts","names":[],"sources":["../../src/client/sessionStore.ts"],"mappings":";KAAY;EACV;EACA;;KAGU;EACV,YAAY;EACZ,OAAO,QAAQ;;KAGL;EACV,eAAe;;cAGJ,gCAA+B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/client/sessionStore.ts
|
|
2
|
+
const createMemorySessionStore = () => {
|
|
3
|
+
let record = null;
|
|
4
|
+
return {
|
|
5
|
+
load: () => record,
|
|
6
|
+
save: (next) => {
|
|
7
|
+
record = next;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { createMemorySessionStore };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=sessionStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionStore.js","names":["StatewireSessionRecord","clientId","nextSeq","StatewireSessionStore","load","save","record","StatewireSessionStoreOptions","sessionStore","createMemorySessionStore","next"],"sources":["../../src/client/sessionStore.ts"],"sourcesContent":["export type StatewireSessionRecord = {\n clientId: string;\n nextSeq: number;\n};\n\nexport type StatewireSessionStore = {\n load: () => StatewireSessionRecord | null;\n save: (record: StatewireSessionRecord) => void;\n};\n\nexport type StatewireSessionStoreOptions = {\n sessionStore?: StatewireSessionStore | undefined;\n};\n\nexport const createMemorySessionStore = (): StatewireSessionStore => {\n let record: StatewireSessionRecord | null = null;\n return {\n load: () => record,\n save: (next) => {\n record = next;\n },\n };\n};\n"],"mappings":";AAcA,MAAaS,iCAAwD;CACnE,IAAIH,SAAwC;CAC5C,OAAO;EACLF,YAAYE;EACZD,OAAOK,SAAS;GACdJ,SAASI;EACX;CACF;AACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/client/timing.d.ts
|
|
2
|
+
declare const timing: {
|
|
3
|
+
backoffBaseMs: number;
|
|
4
|
+
backoffMaxMs: number;
|
|
5
|
+
heartbeatTimeoutMs: number;
|
|
6
|
+
};
|
|
7
|
+
declare const backoff: (attempt: number) => number;
|
|
8
|
+
declare const retryDelay: (error: unknown, attempt: number) => number;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { backoff, retryDelay, timing };
|
|
11
|
+
//# sourceMappingURL=timing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.d.ts","names":[],"sources":["../../src/client/timing.ts"],"mappings":";cAAa;;;;;cAMA,UAAW;cAGX,aAAc,gBAAgB"}
|