statewire 0.0.0-0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +83 -1
- package/dist/client/changes.d.ts +7 -0
- package/dist/client/changes.d.ts.map +1 -0
- package/dist/client/changes.js +32 -0
- package/dist/client/changes.js.map +1 -0
- package/dist/client/connection.d.ts +84 -0
- package/dist/client/connection.d.ts.map +1 -0
- package/dist/client/connection.js +204 -0
- package/dist/client/connection.js.map +1 -0
- package/dist/client/core.d.ts +18 -0
- package/dist/client/core.d.ts.map +1 -0
- package/dist/client/core.js +269 -0
- package/dist/client/core.js.map +1 -0
- package/dist/client/optimistic.d.ts +10 -0
- package/dist/client/optimistic.d.ts.map +1 -0
- package/dist/client/optimistic.js +128 -0
- package/dist/client/optimistic.js.map +1 -0
- package/dist/client/session.d.ts +88 -0
- package/dist/client/session.d.ts.map +1 -0
- package/dist/client/session.js +509 -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 +10 -0
- package/dist/client/timing.d.ts.map +1 -0
- package/dist/client/timing.js +11 -0
- package/dist/client/timing.js.map +1 -0
- package/dist/host/draft.d.ts +14 -0
- package/dist/host/draft.d.ts.map +1 -0
- package/dist/host/draft.js +124 -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 +79 -0
- package/dist/host/host.d.ts.map +1 -0
- package/dist/host/host.js +303 -0
- package/dist/host/host.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +3 -0
- package/dist/statewire.d.ts +20 -0
- package/dist/statewire.d.ts.map +1 -0
- package/dist/statewire.js +65 -0
- package/dist/statewire.js.map +1 -0
- package/dist/transports/remote.d.ts +19 -0
- package/dist/transports/remote.d.ts.map +1 -0
- package/dist/transports/remote.js +40 -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 +74 -0
- package/dist/transports/sse.d.ts.map +1 -0
- package/dist/transports/sse.js +136 -0
- package/dist/transports/sse.js.map +1 -0
- package/dist/transports/testing.d.ts +31 -0
- package/dist/transports/testing.d.ts.map +1 -0
- package/dist/transports/testing.js +121 -0
- package/dist/transports/testing.js.map +1 -0
- package/dist/transports/ws.d.ts +18 -0
- package/dist/transports/ws.d.ts.map +1 -0
- package/dist/transports/ws.js +172 -0
- package/dist/transports/ws.js.map +1 -0
- package/dist/types.d.ts +107 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/dist/wire/meta.d.ts +22 -0
- package/dist/wire/meta.d.ts.map +1 -0
- package/dist/wire/meta.js +73 -0
- package/dist/wire/meta.js.map +1 -0
- package/dist/wire/ops.d.ts +40 -0
- package/dist/wire/ops.d.ts.map +1 -0
- package/dist/wire/ops.js +125 -0
- package/dist/wire/ops.js.map +1 -0
- package/package.json +49 -6
- package/src/client/changes.ts +48 -0
- package/src/client/connection.ts +327 -0
- package/src/client/core.ts +346 -0
- package/src/client/optimistic.ts +0 -0
- package/src/client/session.ts +679 -0
- package/src/client/sessionStore.ts +23 -0
- package/src/client/timing.ts +8 -0
- package/src/host/draft.ts +155 -0
- package/src/host/engine.ts +13 -0
- package/src/host/host.ts +481 -0
- package/src/index.ts +65 -0
- package/src/server.ts +32 -0
- package/src/statewire.ts +107 -0
- package/src/transports/remote.ts +72 -0
- package/src/transports/sse-parser.ts +24 -0
- package/src/transports/sse.ts +280 -0
- package/src/transports/testing.ts +155 -0
- package/src/transports/ws.ts +272 -0
- package/src/types.ts +169 -0
- package/src/wire/meta.ts +106 -0
- package/src/wire/ops.ts +201 -0
- package/index.js +0 -1
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import { applyOps } from "../wire/ops.js";
|
|
2
|
+
import { StatewireSendError } from "../types.js";
|
|
3
|
+
import { bindCommandsHost } from "../host/host.js";
|
|
4
|
+
import { backoff } from "./timing.js";
|
|
5
|
+
import { createMemorySessionStore } from "./sessionStore.js";
|
|
6
|
+
//#region src/client/session.ts
|
|
7
|
+
const isResolved = (state) => state.at === "settled-live" || state.at === "done";
|
|
8
|
+
const isStateSettled = (state) => state.at === "awaiting-result" || state.at === "done";
|
|
9
|
+
const legalTransitions = {
|
|
10
|
+
queued: [
|
|
11
|
+
"queued",
|
|
12
|
+
"trying",
|
|
13
|
+
"awaiting-result",
|
|
14
|
+
"settled-live",
|
|
15
|
+
"done"
|
|
16
|
+
],
|
|
17
|
+
trying: [
|
|
18
|
+
"queued",
|
|
19
|
+
"trying",
|
|
20
|
+
"posted",
|
|
21
|
+
"awaiting-result",
|
|
22
|
+
"settled-live",
|
|
23
|
+
"done"
|
|
24
|
+
],
|
|
25
|
+
posted: [
|
|
26
|
+
"posted",
|
|
27
|
+
"trying",
|
|
28
|
+
"awaiting-result",
|
|
29
|
+
"settled-live",
|
|
30
|
+
"done"
|
|
31
|
+
],
|
|
32
|
+
"awaiting-result": ["awaiting-result", "done"],
|
|
33
|
+
"settled-live": ["settled-live", "done"],
|
|
34
|
+
done: []
|
|
35
|
+
};
|
|
36
|
+
const transition = (s, entry, next, outcome) => {
|
|
37
|
+
const prev = entry.state;
|
|
38
|
+
if (!legalTransitions[prev.at].includes(next.at)) throw new Error(`statewire: illegal pending transition ${prev.at} -> ${next.at}`);
|
|
39
|
+
const settles = !isResolved(prev) && isResolved(next);
|
|
40
|
+
if (settles && outcome === void 0) throw new Error(`statewire: settling transition ${prev.at} -> ${next.at} without an outcome`);
|
|
41
|
+
const covers = !isStateSettled(prev) && isStateSettled(next);
|
|
42
|
+
entry.state = next;
|
|
43
|
+
if (covers) s.lifecycle?.onCovered?.(entry.inv);
|
|
44
|
+
if (settles && outcome !== void 0) if (outcome.ok) {
|
|
45
|
+
entry.resolve(outcome.payload);
|
|
46
|
+
s.lifecycle?.onSettle(entry.inv, {
|
|
47
|
+
type: "accepted",
|
|
48
|
+
payload: outcome.payload
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
entry.reject(new StatewireSendError(outcome.failure));
|
|
52
|
+
s.lifecycle?.onSettle(entry.inv, {
|
|
53
|
+
type: "rejected",
|
|
54
|
+
error: new StatewireSendError(outcome.failure)
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (next.at === "done") s.pending = s.pending.filter((p) => p !== entry);
|
|
58
|
+
};
|
|
59
|
+
const carried = (state) => state.at === "queued" ? {
|
|
60
|
+
posted: false,
|
|
61
|
+
failedAttempts: 0
|
|
62
|
+
} : state.at === "trying" || state.at === "posted" ? {
|
|
63
|
+
posted: state.at === "posted",
|
|
64
|
+
failedAttempts: state.failedAttempts
|
|
65
|
+
} : {
|
|
66
|
+
posted: state.posted,
|
|
67
|
+
failedAttempts: state.failedAttempts
|
|
68
|
+
};
|
|
69
|
+
const newClientId = () => `c-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
70
|
+
const rejectedSend = (failure) => {
|
|
71
|
+
const result = Promise.reject(new StatewireSendError(failure));
|
|
72
|
+
result.catch(() => {});
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
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
|
+
const createSession = (options) => {
|
|
91
|
+
const store = options.sessionStore ?? createMemorySessionStore();
|
|
92
|
+
const stored = store.load();
|
|
93
|
+
const s = {
|
|
94
|
+
record: stored ?? {
|
|
95
|
+
clientId: newClientId(),
|
|
96
|
+
nextSeq: 1
|
|
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
|
+
root: () => 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: createCommandsProxy((command) => s.send(command))
|
|
127
|
+
};
|
|
128
|
+
bindCommandsHost(s.commands, { setListener: (listener) => {
|
|
129
|
+
s.lifecycle = listener;
|
|
130
|
+
} });
|
|
131
|
+
return s;
|
|
132
|
+
};
|
|
133
|
+
const publish = (s) => {
|
|
134
|
+
if (s.canonical === s.lastPublished) return;
|
|
135
|
+
s.lastPublished = s.canonical;
|
|
136
|
+
s.notify();
|
|
137
|
+
};
|
|
138
|
+
const wake = (s) => {
|
|
139
|
+
const waiters = s.waiters;
|
|
140
|
+
s.waiters = [];
|
|
141
|
+
for (const waiter of waiters) waiter();
|
|
142
|
+
};
|
|
143
|
+
const waitForWake = (s) => new Promise((r) => s.waiters.push(r));
|
|
144
|
+
const settleAll = (s, detach, verdict) => {
|
|
145
|
+
const cause = {
|
|
146
|
+
evicted: "session-lost",
|
|
147
|
+
unmount: "unmount",
|
|
148
|
+
gone: "stream-gone",
|
|
149
|
+
superseded: "superseded"
|
|
150
|
+
}[detach];
|
|
151
|
+
const batch = s.pending;
|
|
152
|
+
const detachFailure = (entry) => {
|
|
153
|
+
const state = entry.state;
|
|
154
|
+
if (state.at === "awaiting-result") return {
|
|
155
|
+
fate: "result-unavailable",
|
|
156
|
+
cause
|
|
157
|
+
};
|
|
158
|
+
if (state.at === "queued") return {
|
|
159
|
+
fate: "aborted",
|
|
160
|
+
cause: batch.some((p) => p.seq < entry.seq && p.state.at !== "queued") ? "cascade" : cause
|
|
161
|
+
};
|
|
162
|
+
return {
|
|
163
|
+
fate: "lost",
|
|
164
|
+
cause
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
for (const entry of [...batch]) {
|
|
168
|
+
const failure = entry === verdict?.entry ? verdict.failure : detachFailure(entry);
|
|
169
|
+
transition(s, entry, { at: "done" }, {
|
|
170
|
+
ok: false,
|
|
171
|
+
failure
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const evict = (s, verdict) => {
|
|
176
|
+
settleAll(s, "evicted", verdict);
|
|
177
|
+
s.record = {
|
|
178
|
+
clientId: newClientId(),
|
|
179
|
+
nextSeq: 1
|
|
180
|
+
};
|
|
181
|
+
s.store.save(s.record);
|
|
182
|
+
s.attach = "detached";
|
|
183
|
+
publish(s);
|
|
184
|
+
wake(s);
|
|
185
|
+
};
|
|
186
|
+
const noteAcked = (s, seq) => {
|
|
187
|
+
for (const entry of s.pending) {
|
|
188
|
+
if (entry.seq > seq || isStateSettled(entry.state)) continue;
|
|
189
|
+
s.lifecycle?.onAck(entry.inv);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const spliceAcked = (s, seq, settleVoid) => {
|
|
193
|
+
for (const entry of [...s.pending]) {
|
|
194
|
+
const state = entry.state;
|
|
195
|
+
if (entry.seq > seq || isStateSettled(state)) continue;
|
|
196
|
+
if (state.at === "settled-live") transition(s, entry, { at: "done" });
|
|
197
|
+
else if (settleVoid && !state.pendingMarked) transition(s, entry, { at: "done" }, {
|
|
198
|
+
ok: true,
|
|
199
|
+
payload: void 0
|
|
200
|
+
});
|
|
201
|
+
else transition(s, entry, {
|
|
202
|
+
at: "awaiting-result",
|
|
203
|
+
...carried(state)
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const protocolError = (message) => {
|
|
208
|
+
throw new Error(`statewire: protocol error: ${message}`);
|
|
209
|
+
};
|
|
210
|
+
const applyResponse = (s, response) => {
|
|
211
|
+
const entry = s.pending.find((p) => p.seq === response.seq);
|
|
212
|
+
if (!entry) return;
|
|
213
|
+
const state = entry.state;
|
|
214
|
+
if (state.at === "done") return;
|
|
215
|
+
switch (response.type) {
|
|
216
|
+
case "pending":
|
|
217
|
+
if (state.at === "queued" || state.at === "trying" || state.at === "posted") transition(s, entry, {
|
|
218
|
+
...state,
|
|
219
|
+
pendingMarked: true
|
|
220
|
+
});
|
|
221
|
+
break;
|
|
222
|
+
case "accepted":
|
|
223
|
+
if (state.at === "settled-live") break;
|
|
224
|
+
transition(s, entry, state.at === "awaiting-result" ? { at: "done" } : {
|
|
225
|
+
at: "settled-live",
|
|
226
|
+
...carried(state)
|
|
227
|
+
}, {
|
|
228
|
+
ok: true,
|
|
229
|
+
payload: response.payload
|
|
230
|
+
});
|
|
231
|
+
break;
|
|
232
|
+
case "rejected":
|
|
233
|
+
transition(s, entry, { at: "done" }, {
|
|
234
|
+
ok: false,
|
|
235
|
+
failure: response.code === "unknown-command" ? {
|
|
236
|
+
fate: "unknown-command",
|
|
237
|
+
cause: "server"
|
|
238
|
+
} : response.code === "invalid-params" ? {
|
|
239
|
+
fate: "invalid-params",
|
|
240
|
+
cause: "server",
|
|
241
|
+
message: response.message ?? ""
|
|
242
|
+
} : {
|
|
243
|
+
fate: "rejected",
|
|
244
|
+
cause: "server",
|
|
245
|
+
message: response.message ?? "",
|
|
246
|
+
...response.payload !== void 0 && { payload: response.payload }
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
break;
|
|
250
|
+
case "crashed":
|
|
251
|
+
case "result-unavailable":
|
|
252
|
+
if (state.at === "settled-live") break;
|
|
253
|
+
transition(s, entry, state.at === "awaiting-result" ? { at: "done" } : {
|
|
254
|
+
at: "settled-live",
|
|
255
|
+
...carried(state)
|
|
256
|
+
}, {
|
|
257
|
+
ok: false,
|
|
258
|
+
failure: {
|
|
259
|
+
fate: response.type,
|
|
260
|
+
cause: "server"
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const ingestOps = (s, ops) => {
|
|
267
|
+
const applied = applyOps(s.canonical, ops);
|
|
268
|
+
s.canonical = applied.state;
|
|
269
|
+
s.changed.push(...applied.changedPaths);
|
|
270
|
+
return applied.events;
|
|
271
|
+
};
|
|
272
|
+
const publishIngested = (s, events) => {
|
|
273
|
+
publish(s);
|
|
274
|
+
for (const { event } of events) s.emitEvent(event);
|
|
275
|
+
wake(s);
|
|
276
|
+
};
|
|
277
|
+
const handleSnapshot = (s, { ops, lastSeq }) => {
|
|
278
|
+
if (lastSeq === -1 && s.attach !== "detached") {
|
|
279
|
+
evict(s);
|
|
280
|
+
s.transport?.connection.reconnect();
|
|
281
|
+
publishIngested(s, ingestOps(s, ops));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const events = ingestOps(s, ops);
|
|
285
|
+
s.attach = "attached";
|
|
286
|
+
s.attachEpoch += 1;
|
|
287
|
+
noteAcked(s, lastSeq);
|
|
288
|
+
spliceAcked(s, lastSeq, false);
|
|
289
|
+
s.lastAck = lastSeq;
|
|
290
|
+
for (const entry of s.pending) {
|
|
291
|
+
const state = entry.state;
|
|
292
|
+
if (entry.seq <= lastSeq) {
|
|
293
|
+
if (state.at === "awaiting-result" && state.posted) transition(s, entry, {
|
|
294
|
+
...state,
|
|
295
|
+
posted: false
|
|
296
|
+
});
|
|
297
|
+
if (state.at === "awaiting-result") pump(s);
|
|
298
|
+
} else if (state.at === "posted") {
|
|
299
|
+
transition(s, entry, {
|
|
300
|
+
at: "trying",
|
|
301
|
+
pendingMarked: state.pendingMarked,
|
|
302
|
+
failedAttempts: state.failedAttempts
|
|
303
|
+
});
|
|
304
|
+
pump(s);
|
|
305
|
+
} else if (state.at === "settled-live" && state.posted) {
|
|
306
|
+
transition(s, entry, {
|
|
307
|
+
...state,
|
|
308
|
+
posted: false
|
|
309
|
+
});
|
|
310
|
+
pump(s);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
publishIngested(s, events);
|
|
314
|
+
};
|
|
315
|
+
const handleOps = (s, { ops, res, ack }) => {
|
|
316
|
+
const horizon = ack === void 0 ? s.lastAck : Math.max(s.lastAck, ack);
|
|
317
|
+
if (res !== void 0) {
|
|
318
|
+
for (const response of res) if (response.seq > horizon) protocolError("response seq beyond the ack");
|
|
319
|
+
}
|
|
320
|
+
const events = ingestOps(s, ops);
|
|
321
|
+
if (ack !== void 0) noteAcked(s, ack);
|
|
322
|
+
if (res !== void 0) for (const response of res) applyResponse(s, response);
|
|
323
|
+
if (ack !== void 0) {
|
|
324
|
+
spliceAcked(s, ack, true);
|
|
325
|
+
s.lastAck = Math.max(s.lastAck, ack);
|
|
326
|
+
}
|
|
327
|
+
publishIngested(s, events);
|
|
328
|
+
};
|
|
329
|
+
const handleFinish = (s, { reason }) => {
|
|
330
|
+
if (reason === "ended" || reason === "error") return;
|
|
331
|
+
s.attach = "finished";
|
|
332
|
+
settleAll(s, reason === "superseded" ? "superseded" : "gone");
|
|
333
|
+
publish(s);
|
|
334
|
+
wake(s);
|
|
335
|
+
};
|
|
336
|
+
const isFinished = (s) => s.attach === "finished";
|
|
337
|
+
const sendable = (p) => p.state.at === "queued" || p.state.at === "trying" || (p.state.at === "awaiting-result" || p.state.at === "settled-live") && !p.state.posted;
|
|
338
|
+
const pump = async (s) => {
|
|
339
|
+
if (s.pumping) return;
|
|
340
|
+
s.pumping = true;
|
|
341
|
+
try {
|
|
342
|
+
while (s.running && !isFinished(s)) {
|
|
343
|
+
const entry = s.pending.find(sendable);
|
|
344
|
+
if (!entry) return;
|
|
345
|
+
const transport = s.transport;
|
|
346
|
+
if (s.attach !== "attached" || !transport) {
|
|
347
|
+
await waitForWake(s);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const clientId = s.record.clientId;
|
|
351
|
+
const epoch = s.attachEpoch;
|
|
352
|
+
if (entry.state.at === "queued") transition(s, entry, {
|
|
353
|
+
at: "trying",
|
|
354
|
+
pendingMarked: entry.state.pendingMarked,
|
|
355
|
+
failedAttempts: 0
|
|
356
|
+
});
|
|
357
|
+
let result;
|
|
358
|
+
try {
|
|
359
|
+
result = await transport.send({
|
|
360
|
+
seq: entry.seq,
|
|
361
|
+
command: entry.command
|
|
362
|
+
});
|
|
363
|
+
} catch (error) {
|
|
364
|
+
if (!s.running || isFinished(s)) return;
|
|
365
|
+
if (!s.pending.includes(entry)) continue;
|
|
366
|
+
const state = entry.state;
|
|
367
|
+
if (state.at === "queued" || state.at === "done") continue;
|
|
368
|
+
transition(s, entry, {
|
|
369
|
+
...state,
|
|
370
|
+
failedAttempts: state.failedAttempts + 1
|
|
371
|
+
});
|
|
372
|
+
const { retryAfterMs } = error;
|
|
373
|
+
const delay = typeof retryAfterMs === "number" ? retryAfterMs : backoff(state.failedAttempts + 1);
|
|
374
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (!s.running) return;
|
|
378
|
+
if (!s.pending.includes(entry)) continue;
|
|
379
|
+
if (result.type === "receipt") {
|
|
380
|
+
const response = result.response;
|
|
381
|
+
const state = entry.state;
|
|
382
|
+
if (state.at === "trying" || state.at === "posted") transition(s, entry, {
|
|
383
|
+
at: "posted",
|
|
384
|
+
pendingMarked: state.pendingMarked,
|
|
385
|
+
failedAttempts: state.failedAttempts
|
|
386
|
+
});
|
|
387
|
+
else if (state.at === "awaiting-result" || state.at === "settled-live") transition(s, entry, {
|
|
388
|
+
...state,
|
|
389
|
+
posted: true
|
|
390
|
+
});
|
|
391
|
+
s.lifecycle?.onPosted?.(entry.inv);
|
|
392
|
+
if (response !== void 0) {
|
|
393
|
+
applyResponse(s, response);
|
|
394
|
+
publish(s);
|
|
395
|
+
}
|
|
396
|
+
} else if (result.type === "rejected") {
|
|
397
|
+
const { message, payload } = result;
|
|
398
|
+
transition(s, entry, { at: "done" }, {
|
|
399
|
+
ok: false,
|
|
400
|
+
failure: {
|
|
401
|
+
fate: "rejected",
|
|
402
|
+
cause: "server",
|
|
403
|
+
message,
|
|
404
|
+
...payload !== void 0 && { payload }
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
publish(s);
|
|
408
|
+
} else if (result.type === "stale-lease") {
|
|
409
|
+
if (epoch === s.attachEpoch) await waitForWake(s);
|
|
410
|
+
} else if (result.type === "unknown-client") {
|
|
411
|
+
if (epoch === s.attachEpoch) {
|
|
412
|
+
const state = entry.state;
|
|
413
|
+
if (state.at === "trying" && state.failedAttempts === 0) transition(s, entry, {
|
|
414
|
+
at: "queued",
|
|
415
|
+
pendingMarked: state.pendingMarked
|
|
416
|
+
});
|
|
417
|
+
s.transport?.connection.reconnect();
|
|
418
|
+
while (s.running && !isFinished(s) && epoch === s.attachEpoch && clientId === s.record.clientId) await waitForWake(s);
|
|
419
|
+
}
|
|
420
|
+
} else if (result.type === "seq-gap") {
|
|
421
|
+
if (clientId === s.record.clientId) {
|
|
422
|
+
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;
|
|
424
|
+
const failure = state.at === "awaiting-result" ? {
|
|
425
|
+
fate: "result-unavailable",
|
|
426
|
+
cause: "session-lost"
|
|
427
|
+
} : state.at === "queued" || state.at === "done" ? {
|
|
428
|
+
fate: "aborted",
|
|
429
|
+
cause: "session-lost"
|
|
430
|
+
} : {
|
|
431
|
+
fate: state.failedAttempts > 0 ? "lost" : "aborted",
|
|
432
|
+
cause: "session-lost"
|
|
433
|
+
};
|
|
434
|
+
evict(s, {
|
|
435
|
+
entry,
|
|
436
|
+
failure
|
|
437
|
+
});
|
|
438
|
+
s.transport?.connection.reconnect();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
} finally {
|
|
443
|
+
s.pumping = false;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
const start = (s) => {
|
|
447
|
+
if (!s.recordPersisted) {
|
|
448
|
+
s.recordPersisted = true;
|
|
449
|
+
s.store.save(s.record);
|
|
450
|
+
}
|
|
451
|
+
s.running = true;
|
|
452
|
+
pump(s);
|
|
453
|
+
};
|
|
454
|
+
const stop = (s) => {
|
|
455
|
+
s.running = false;
|
|
456
|
+
wake(s);
|
|
457
|
+
settleAll(s, "unmount");
|
|
458
|
+
};
|
|
459
|
+
const setTransport = (s, transport) => {
|
|
460
|
+
if (s.transport === transport) return;
|
|
461
|
+
s.transport = transport;
|
|
462
|
+
wake(s);
|
|
463
|
+
};
|
|
464
|
+
const sendCommand = (s, command) => {
|
|
465
|
+
if (!s.running) return rejectedSend({
|
|
466
|
+
fate: "aborted",
|
|
467
|
+
cause: "unmount"
|
|
468
|
+
});
|
|
469
|
+
if (isFinished(s)) return rejectedSend({
|
|
470
|
+
fate: "aborted",
|
|
471
|
+
cause: "stream-gone"
|
|
472
|
+
});
|
|
473
|
+
const seq = s.record.nextSeq;
|
|
474
|
+
s.record = {
|
|
475
|
+
...s.record,
|
|
476
|
+
nextSeq: seq + 1
|
|
477
|
+
};
|
|
478
|
+
s.store.save(s.record);
|
|
479
|
+
const entry = {
|
|
480
|
+
seq,
|
|
481
|
+
command,
|
|
482
|
+
inv: {
|
|
483
|
+
method: command.method,
|
|
484
|
+
params: command.params,
|
|
485
|
+
ts: Date.now(),
|
|
486
|
+
seq,
|
|
487
|
+
clientId: s.record.clientId
|
|
488
|
+
},
|
|
489
|
+
state: {
|
|
490
|
+
at: "queued",
|
|
491
|
+
pendingMarked: false
|
|
492
|
+
},
|
|
493
|
+
resolve: () => {},
|
|
494
|
+
reject: () => {}
|
|
495
|
+
};
|
|
496
|
+
const result = new Promise((resolve, reject) => {
|
|
497
|
+
entry.resolve = resolve;
|
|
498
|
+
entry.reject = reject;
|
|
499
|
+
});
|
|
500
|
+
result.catch(() => {});
|
|
501
|
+
s.pending.push(entry);
|
|
502
|
+
s.lifecycle?.onStart(entry.inv);
|
|
503
|
+
pump(s);
|
|
504
|
+
return result;
|
|
505
|
+
};
|
|
506
|
+
//#endregion
|
|
507
|
+
export { createSession, handleFinish, handleOps, handleSnapshot, setTransport, start, stop };
|
|
508
|
+
|
|
509
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","names":["applyOps","StatewirePath","StatewireResponse","bindCommandsHost","CommandsInvocation","CommandsListener","StatewireStateSource","createMemorySessionStore","StatewireSessionRecord","StatewireSessionStore","backoff","StatewireSendError","SendFailure","StatewireSendFailure","RemoteLink","StatewireSubmitResult","StatewireWireCommand","SessionOptions","sessionStore","PendingState","at","pendingMarked","failedAttempts","posted","Pending","seq","command","inv","state","resolve","payload","reject","error","Outcome","ok","failure","isResolved","isStateSettled","legalTransitions","Record","queued","trying","done","transition","s","Session","entry","next","outcome","prev","includes","Error","settles","undefined","covers","lifecycle","onCovered","onSettle","type","pending","filter","p","carried","Exclude","newClientId","Date","now","toString","Math","random","slice","rejectedSend","Promise","result","catch","AttachState","record","recordPersisted","store","lastAck","canonical","lastPublished","changed","source","attach","attachEpoch","running","pumping","transport","waiters","notify","emitEvent","event","send","commands","params","createCommandsProxy","memo","Map","Proxy","get","_target","method","fn","set","createSession","options","stored","load","clientId","nextSeq","root","takeChanged","setOnChange","listener","sendCommand","setListener","publish","wake","waiter","waitForWake","r","push","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","handleSnapshot","lastSeq","connection","reconnect","pump","handleOps","res","ack","horizon","max","handleFinish","reason","isFinished","sendable","epoch","retryAfterMs","delay","setTimeout","onPosted","console","start","stop","setTransport","ts","onStart"],"sources":["../../src/client/session.ts"],"sourcesContent":["import { 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 { StatewireStateSource } from \"../types\";\nimport { createMemorySessionStore } from \"./sessionStore\";\nimport type {\n StatewireSessionRecord,\n StatewireSessionStore,\n} from \"./sessionStore\";\nimport { backoff } from \"./timing\";\nimport { StatewireSendError } from \"../types\";\nimport type { SendFailure as StatewireSendFailure } from \"../types\";\nimport type {\n RemoteLink,\n StatewireSubmitResult,\n StatewireWireCommand,\n} from \"../transports/sse\";\n\nexport type SessionOptions = {\n sessionStore?: StatewireSessionStore | undefined;\n};\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: StatewireSendFailure };\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\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 = () =>\n `c-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n\nconst rejectedSend = (failure: StatewireSendFailure): 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 recordPersisted: boolean;\n store: StatewireSessionStore;\n lastAck: number;\n pending: Pending[];\n canonical: unknown;\n lastPublished: unknown;\n changed: StatewirePath[];\n source: StatewireStateSource;\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 createCommandsProxy = (\n send: (command: StatewireWireCommand) => Promise<unknown>,\n): Session[\"commands\"] => {\n const memo = new Map<string, (...params: unknown[]) => Promise<unknown>>();\n return new Proxy({} as Session[\"commands\"], {\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[]) => send({ method, params });\n memo.set(method, fn);\n }\n return fn;\n },\n });\n};\n\nconst createSession = (options: SessionOptions): 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 recordPersisted: stored !== null,\n store,\n lastAck: -1,\n pending: [],\n canonical: undefined,\n lastPublished: undefined,\n changed: [],\n source: {\n root: () => 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: createCommandsProxy((command) => s.send(command)),\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 settleAll = (\n s: Session,\n detach: \"evicted\" | \"unmount\" | \"gone\" | \"superseded\",\n verdict?: {\n entry: Pending;\n failure: StatewireSendFailure;\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): StatewireSendFailure => {\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: StatewireSendFailure;\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: response.message ?? \"\",\n }\n : {\n fate: \"rejected\",\n cause: \"server\",\n message: response.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 = (\n s: Session,\n events: { event: unknown; state: unknown }[],\n) => {\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 === \"ended\" || 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 = entry.state;\n if (state.at === \"queued\" || state.at === \"done\") continue;\n transition(s, entry, {\n ...state,\n failedAttempts: state.failedAttempts + 1,\n });\n const { retryAfterMs } = error as { retryAfterMs?: unknown };\n const delay =\n typeof retryAfterMs === \"number\"\n ? retryAfterMs\n : backoff(state.failedAttempts + 1);\n await new Promise((r) => setTimeout(r, delay));\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 const { message, payload } = result;\n transition(\n s,\n entry,\n { at: \"done\" },\n {\n ok: false,\n failure: {\n fate: \"rejected\",\n cause: \"server\",\n message,\n ...(payload !== undefined && { payload }),\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 = entry.state;\n const failure: StatewireSendFailure =\n state.at === \"awaiting-result\"\n ? { fate: \"result-unavailable\", cause: \"session-lost\" }\n : state.at === \"queued\" || state.at === \"done\"\n ? { fate: \"aborted\", 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 {\n createSession,\n handleSnapshot,\n handleFinish,\n handleOps,\n setTransport,\n start,\n stop,\n};\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;
|
|
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;\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,10 @@
|
|
|
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
|
+
//#endregion
|
|
9
|
+
export { backoff, timing };
|
|
10
|
+
//# 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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/client/timing.ts
|
|
2
|
+
const timing = {
|
|
3
|
+
backoffBaseMs: 250,
|
|
4
|
+
backoffMaxMs: 3e4,
|
|
5
|
+
heartbeatTimeoutMs: 3e4
|
|
6
|
+
};
|
|
7
|
+
const backoff = (attempt) => Math.min(timing.backoffBaseMs * 2 ** (attempt - 1), timing.backoffMaxMs);
|
|
8
|
+
//#endregion
|
|
9
|
+
export { backoff, timing };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=timing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.js","names":["timing","backoffBaseMs","backoffMaxMs","heartbeatTimeoutMs","backoff","attempt","Math","min"],"sources":["../../src/client/timing.ts"],"sourcesContent":["export const timing = {\n backoffBaseMs: 250,\n backoffMaxMs: 30_000,\n heartbeatTimeoutMs: 30_000,\n};\n\nexport const backoff = (attempt: number) =>\n Math.min(timing.backoffBaseMs * 2 ** (attempt - 1), timing.backoffMaxMs);\n"],"mappings":";AAAA,MAAaA,SAAS;CACpBC,eAAe;CACfC,cAAc;CACdC,oBAAoB;AACtB;AAEA,MAAaC,WAAWC,YACtBC,KAAKC,IAAIP,OAAOC,gBAAgB,MAAMI,UAAU,IAAIL,OAAOE,YAAY"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StatewireOp, StatewirePath } from "../wire/ops.js";
|
|
2
|
+
//#region src/host/draft.d.ts
|
|
3
|
+
type ServerOp = Exclude<StatewireOp, {
|
|
4
|
+
op: "event";
|
|
5
|
+
}>;
|
|
6
|
+
type DraftHost = {
|
|
7
|
+
root: () => unknown;
|
|
8
|
+
record: (op: ServerOp) => void;
|
|
9
|
+
};
|
|
10
|
+
declare const frozen: (value: unknown) => unknown;
|
|
11
|
+
declare const createStateDraft: <T>(host: DraftHost, path?: StatewirePath) => T;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { DraftHost, ServerOp, createStateDraft, frozen };
|
|
14
|
+
//# sourceMappingURL=draft.d.ts.map
|