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,272 @@
|
|
|
1
|
+
import { recordMutations } from "../util/recordMutations.js";
|
|
2
|
+
import { createMethodProxy } from "../util/methodProxy.js";
|
|
3
|
+
import { StatewireSendError } from "../types.js";
|
|
4
|
+
import { getCommandsHost, getEmitHost, getStateHost } from "../host/host.js";
|
|
5
|
+
import { createMacrotaskPoster } from "../util/macrotask.js";
|
|
6
|
+
import { createChanges } from "./changes.js";
|
|
7
|
+
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
8
|
+
import { useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
9
|
+
//#region src/client/core.ts
|
|
10
|
+
const LIVE_CONNECTION = {
|
|
11
|
+
status: "live",
|
|
12
|
+
reconnect: () => {}
|
|
13
|
+
};
|
|
14
|
+
const failureOf = (error) => error instanceof StatewireSendError ? error.failure : {
|
|
15
|
+
fate: "rejected",
|
|
16
|
+
cause: "server",
|
|
17
|
+
message: error instanceof Error ? error.message : String(error)
|
|
18
|
+
};
|
|
19
|
+
const createCore = (getOptions) => {
|
|
20
|
+
let value = null;
|
|
21
|
+
let source = null;
|
|
22
|
+
let overlays = [];
|
|
23
|
+
let updates = [];
|
|
24
|
+
let events = [];
|
|
25
|
+
let pendingChanged = [];
|
|
26
|
+
let canonical;
|
|
27
|
+
let published;
|
|
28
|
+
let overlayPaths = [];
|
|
29
|
+
let notifiedPublished;
|
|
30
|
+
let notifiedOverlayPaths = [];
|
|
31
|
+
let notifiedConnection = null;
|
|
32
|
+
let stale = false;
|
|
33
|
+
let overlaysDirty = false;
|
|
34
|
+
let flushing = false;
|
|
35
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
36
|
+
const post = createMacrotaskPoster(() => {
|
|
37
|
+
scheduled = false;
|
|
38
|
+
flush();
|
|
39
|
+
});
|
|
40
|
+
let scheduled = false;
|
|
41
|
+
const schedule = () => {
|
|
42
|
+
if (scheduled) return;
|
|
43
|
+
scheduled = true;
|
|
44
|
+
post();
|
|
45
|
+
};
|
|
46
|
+
const markStale = () => {
|
|
47
|
+
stale = true;
|
|
48
|
+
schedule();
|
|
49
|
+
};
|
|
50
|
+
const drainCanonical = () => {
|
|
51
|
+
if (source) {
|
|
52
|
+
pendingChanged.push(...source.takeChanged());
|
|
53
|
+
canonical = source.snapshot();
|
|
54
|
+
} else if (value && value.state !== canonical) {
|
|
55
|
+
pendingChanged.push([]);
|
|
56
|
+
canonical = value.state;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const recompute = () => {
|
|
60
|
+
if (!stale || !value) return;
|
|
61
|
+
stale = false;
|
|
62
|
+
const prevCanonical = canonical;
|
|
63
|
+
drainCanonical();
|
|
64
|
+
if (canonical === prevCanonical && !overlaysDirty) return;
|
|
65
|
+
overlaysDirty = false;
|
|
66
|
+
const { optimistic } = getOptions();
|
|
67
|
+
let next = canonical;
|
|
68
|
+
let paths = [];
|
|
69
|
+
if (optimistic && canonical !== void 0) for (const overlay of overlays) {
|
|
70
|
+
if (overlay.dropped) continue;
|
|
71
|
+
const { inv } = overlay;
|
|
72
|
+
const ctx = {
|
|
73
|
+
ts: inv.ts,
|
|
74
|
+
seq: inv.seq,
|
|
75
|
+
...inv.clientId !== void 0 && { clientId: inv.clientId }
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
const recorded = recordMutations(next, (draft) => optimistic(draft, {
|
|
79
|
+
method: inv.method,
|
|
80
|
+
params: inv.params
|
|
81
|
+
}, ctx));
|
|
82
|
+
next = recorded.state;
|
|
83
|
+
paths = [...paths, ...recorded.changedPaths];
|
|
84
|
+
} catch (error) {
|
|
85
|
+
overlay.dropped = true;
|
|
86
|
+
console.error(`statewire: optimistic reducer threw for "${inv.method}"; dropping its optimistic update`, error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
published = next;
|
|
90
|
+
overlayPaths = paths;
|
|
91
|
+
};
|
|
92
|
+
const getConnection = () => value?.connection ?? LIVE_CONNECTION;
|
|
93
|
+
const flush = () => {
|
|
94
|
+
if (!value) return;
|
|
95
|
+
if (flushing) {
|
|
96
|
+
schedule();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
flushing = true;
|
|
100
|
+
try {
|
|
101
|
+
recompute();
|
|
102
|
+
const options = getOptions();
|
|
103
|
+
const stateChanged = published !== notifiedPublished;
|
|
104
|
+
if (stateChanged) {
|
|
105
|
+
const prev = notifiedPublished;
|
|
106
|
+
const changed = [
|
|
107
|
+
...pendingChanged,
|
|
108
|
+
...notifiedOverlayPaths,
|
|
109
|
+
...overlayPaths
|
|
110
|
+
];
|
|
111
|
+
pendingChanged = [];
|
|
112
|
+
notifiedPublished = published;
|
|
113
|
+
notifiedOverlayPaths = overlayPaths;
|
|
114
|
+
options.onStateChange?.(published, { changes: createChanges(prev, published, changed) });
|
|
115
|
+
}
|
|
116
|
+
if (updates.length > 0 && published !== void 0) {
|
|
117
|
+
const batch = updates;
|
|
118
|
+
updates = [];
|
|
119
|
+
for (const update of batch) options.onCommandChange?.(update, { state: published });
|
|
120
|
+
}
|
|
121
|
+
if (events.length > 0 && published !== void 0) {
|
|
122
|
+
const batch = events;
|
|
123
|
+
events = [];
|
|
124
|
+
for (const event of batch) options.onEvent?.(event, { state: published });
|
|
125
|
+
}
|
|
126
|
+
const connection = getConnection();
|
|
127
|
+
const connectionChanged = connection !== notifiedConnection;
|
|
128
|
+
notifiedConnection = connection;
|
|
129
|
+
if (stateChanged || connectionChanged) for (const listener of [...listeners]) listener();
|
|
130
|
+
} finally {
|
|
131
|
+
flushing = false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const note = (inv, status, extra) => {
|
|
135
|
+
if (!getOptions().onCommandChange) return;
|
|
136
|
+
updates.push({
|
|
137
|
+
method: inv.method,
|
|
138
|
+
params: inv.params,
|
|
139
|
+
seq: inv.seq,
|
|
140
|
+
status,
|
|
141
|
+
...extra
|
|
142
|
+
});
|
|
143
|
+
schedule();
|
|
144
|
+
};
|
|
145
|
+
const commandsListener = {
|
|
146
|
+
onStart: (inv) => {
|
|
147
|
+
overlays.push({
|
|
148
|
+
inv,
|
|
149
|
+
dropped: false
|
|
150
|
+
});
|
|
151
|
+
overlaysDirty = true;
|
|
152
|
+
markStale();
|
|
153
|
+
note(inv, "queued");
|
|
154
|
+
},
|
|
155
|
+
onPosted: (inv) => note(inv, "posted"),
|
|
156
|
+
onAck: (inv) => note(inv, "acked"),
|
|
157
|
+
onCovered: (inv) => {
|
|
158
|
+
if (!overlays.some((o) => o.inv === inv)) return;
|
|
159
|
+
overlays = overlays.filter((o) => o.inv !== inv);
|
|
160
|
+
overlaysDirty = true;
|
|
161
|
+
markStale();
|
|
162
|
+
},
|
|
163
|
+
onSettle: (inv, outcome) => {
|
|
164
|
+
note(inv, "settled", outcome.type === "accepted" ? { result: outcome.payload } : { failure: failureOf(outcome.error) });
|
|
165
|
+
flush();
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const eventSink = (event) => {
|
|
169
|
+
if (!getOptions().onEvent) return;
|
|
170
|
+
events.push(event);
|
|
171
|
+
schedule();
|
|
172
|
+
};
|
|
173
|
+
const attach = (next) => {
|
|
174
|
+
if (next === value) return;
|
|
175
|
+
const prev = value;
|
|
176
|
+
value = next;
|
|
177
|
+
const nextSource = next.source ?? (typeof next.state === "object" && next.state !== null ? getStateHost(next.state) : void 0) ?? null;
|
|
178
|
+
if (nextSource !== source) {
|
|
179
|
+
source?.setOnChange(null);
|
|
180
|
+
source = nextSource;
|
|
181
|
+
source?.setOnChange(markStale);
|
|
182
|
+
markStale();
|
|
183
|
+
}
|
|
184
|
+
if (next.commands !== prev?.commands) {
|
|
185
|
+
if (prev) getCommandsHost(prev.commands)?.setListener(null);
|
|
186
|
+
getCommandsHost(next.commands)?.setListener(commandsListener);
|
|
187
|
+
if (overlays.length > 0) {
|
|
188
|
+
overlays = [];
|
|
189
|
+
overlaysDirty = true;
|
|
190
|
+
}
|
|
191
|
+
markStale();
|
|
192
|
+
}
|
|
193
|
+
if (next.emit !== prev?.emit) {
|
|
194
|
+
if (prev) getEmitHost(prev.emit)?.setSink(null);
|
|
195
|
+
getEmitHost(next.emit)?.setSink(eventSink);
|
|
196
|
+
}
|
|
197
|
+
if (!source) markStale();
|
|
198
|
+
schedule();
|
|
199
|
+
};
|
|
200
|
+
const detach = () => {
|
|
201
|
+
source?.setOnChange(null);
|
|
202
|
+
source = null;
|
|
203
|
+
if (value) {
|
|
204
|
+
getCommandsHost(value.commands)?.setListener(null);
|
|
205
|
+
getEmitHost(value.emit)?.setSink(null);
|
|
206
|
+
value = null;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
return {
|
|
210
|
+
attach,
|
|
211
|
+
detach,
|
|
212
|
+
getState: () => {
|
|
213
|
+
recompute();
|
|
214
|
+
return published;
|
|
215
|
+
},
|
|
216
|
+
commands: createMethodProxy((method, params) => {
|
|
217
|
+
if (!value) return Promise.reject(new StatewireSendError({
|
|
218
|
+
fate: "aborted",
|
|
219
|
+
cause: "unmount"
|
|
220
|
+
}));
|
|
221
|
+
const send = value.commands[method];
|
|
222
|
+
const promise = send(...params);
|
|
223
|
+
flush();
|
|
224
|
+
return promise;
|
|
225
|
+
}),
|
|
226
|
+
subscribe: (listener) => {
|
|
227
|
+
listeners.add(listener);
|
|
228
|
+
return () => {
|
|
229
|
+
listeners.delete(listener);
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
const useStatewireCore = (getOptions, value) => {
|
|
235
|
+
const $ = c(8);
|
|
236
|
+
let t0;
|
|
237
|
+
if ($[0] !== getOptions) {
|
|
238
|
+
t0 = () => createCore(getOptions);
|
|
239
|
+
$[0] = getOptions;
|
|
240
|
+
$[1] = t0;
|
|
241
|
+
} else t0 = $[1];
|
|
242
|
+
const [core] = useState(t0);
|
|
243
|
+
core.attach(value);
|
|
244
|
+
let t1;
|
|
245
|
+
if ($[2] !== core || $[3] !== value) {
|
|
246
|
+
t1 = () => {
|
|
247
|
+
core.attach(value);
|
|
248
|
+
};
|
|
249
|
+
$[2] = core;
|
|
250
|
+
$[3] = value;
|
|
251
|
+
$[4] = t1;
|
|
252
|
+
} else t1 = $[4];
|
|
253
|
+
useEffect(t1);
|
|
254
|
+
let t2;
|
|
255
|
+
let t3;
|
|
256
|
+
if ($[5] !== core) {
|
|
257
|
+
t2 = () => () => core.detach();
|
|
258
|
+
t3 = [core];
|
|
259
|
+
$[5] = core;
|
|
260
|
+
$[6] = t2;
|
|
261
|
+
$[7] = t3;
|
|
262
|
+
} else {
|
|
263
|
+
t2 = $[6];
|
|
264
|
+
t3 = $[7];
|
|
265
|
+
}
|
|
266
|
+
useEffect(t2, t3);
|
|
267
|
+
return core;
|
|
268
|
+
};
|
|
269
|
+
//#endregion
|
|
270
|
+
export { LIVE_CONNECTION, useStatewireCore };
|
|
271
|
+
|
|
272
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","names":["useEffect","useState","createChanges","getCommandsHost","getEmitHost","getStateHost","CommandsInvocation","CommandsListener","recordMutations","createMacrotaskPoster","createMethodProxy","StatewirePath","StatewireSendError","Statewire","StatewireClient","LIVE_CONNECTION","Connection","status","reconnect","failureOf","error","SendFailure","failure","fate","cause","message","Error","String","Overlay","inv","dropped","createCore","getOptions","Options","value","source","StateSource","overlays","updates","CommandUpdate","events","pendingChanged","canonical","published","overlayPaths","notifiedPublished","notifiedOverlayPaths","notifiedConnection","stale","overlaysDirty","flushing","listeners","Set","post","scheduled","flush","schedule","markStale","drainCanonical","push","takeChanged","snapshot","state","recompute","prevCanonical","optimistic","next","paths","undefined","overlay","ctx","OptimisticContext","ts","seq","clientId","recorded","draft","method","params","Command","changedPaths","console","getConnection","connection","options","stateChanged","prev","changed","onStateChange","changes","length","batch","update","onCommandChange","event","onEvent","connectionChanged","listener","note","extra","result","commandsListener","onStart","onPosted","onAck","onCovered","some","o","filter","onSettle","outcome","type","payload","eventSink","attach","nextSource","setOnChange","commands","setListener","emit","setSink","detach","CommandsProxy","Promise","reject","send","Record","args","promise","getState","subscribe","add","delete","useStatewireCore","$","_c","t0","core","t1","t2","t3"],"sources":["../../src/client/core.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { createChanges } from \"./changes\";\nimport { getCommandsHost, getEmitHost, getStateHost } from \"../host/host\";\nimport type { CommandsInvocation, CommandsListener } from \"../host/host\";\nimport { recordMutations } from \"../util/recordMutations\";\nimport { createMacrotaskPoster } from \"../util/macrotask\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\n\nexport const LIVE_CONNECTION: StatewireClient.Connection = {\n status: \"live\",\n reconnect: () => {},\n};\n\nconst failureOf = (error: unknown): StatewireClient.SendFailure =>\n error instanceof StatewireSendError\n ? error.failure\n : {\n fate: \"rejected\",\n cause: \"server\",\n message: error instanceof Error ? error.message : String(error),\n };\n\ntype Overlay = { inv: CommandsInvocation; dropped: boolean };\n\nconst createCore = (\n getOptions: () => StatewireClient.Options<any, any, any>,\n) => {\n let value: Statewire<unknown> | null = null;\n let source: Statewire.StateSource | null = null;\n let overlays: Overlay[] = [];\n let updates: Statewire.CommandUpdate[] = [];\n let events: unknown[] = [];\n let pendingChanged: StatewirePath[] = [];\n let canonical: unknown;\n let published: unknown;\n let overlayPaths: readonly StatewirePath[] = [];\n let notifiedPublished: unknown;\n let notifiedOverlayPaths: readonly StatewirePath[] = [];\n let notifiedConnection: StatewireClient.Connection | null = null;\n let stale = false;\n let overlaysDirty = false;\n let flushing = false;\n const listeners = new Set<() => void>();\n\n const post = createMacrotaskPoster(() => {\n scheduled = false;\n flush();\n });\n let scheduled = false;\n const schedule = () => {\n if (scheduled) return;\n scheduled = true;\n post();\n };\n\n const markStale = () => {\n stale = true;\n schedule();\n };\n\n const drainCanonical = () => {\n if (source) {\n pendingChanged.push(...source.takeChanged());\n canonical = source.snapshot();\n } else if (value && value.state !== canonical) {\n pendingChanged.push([]);\n canonical = value.state;\n }\n };\n\n const recompute = () => {\n if (!stale || !value) return;\n stale = false;\n const prevCanonical = canonical;\n drainCanonical();\n if (canonical === prevCanonical && !overlaysDirty) return;\n overlaysDirty = false;\n const { optimistic } = getOptions();\n let next = canonical;\n let paths: readonly StatewirePath[] = [];\n if (optimistic && canonical !== undefined) {\n for (const overlay of overlays) {\n if (overlay.dropped) continue;\n const { inv } = overlay;\n const ctx: StatewireClient.OptimisticContext = {\n ts: inv.ts,\n seq: inv.seq,\n ...(inv.clientId !== undefined && { clientId: inv.clientId }),\n };\n try {\n const recorded = recordMutations(next, (draft) =>\n optimistic(\n draft as never,\n { method: inv.method, params: inv.params } as Statewire.Command,\n ctx,\n ),\n );\n next = recorded.state;\n paths = [...paths, ...recorded.changedPaths];\n } catch (error) {\n overlay.dropped = true;\n console.error(\n `statewire: optimistic reducer threw for \"${inv.method}\"; dropping its optimistic update`,\n error,\n );\n }\n }\n }\n published = next;\n overlayPaths = paths;\n };\n\n const getConnection = (): StatewireClient.Connection =>\n value?.connection ?? LIVE_CONNECTION;\n\n const flush = () => {\n if (!value) return;\n if (flushing) {\n schedule();\n return;\n }\n flushing = true;\n try {\n recompute();\n const options = getOptions();\n const stateChanged = published !== notifiedPublished;\n if (stateChanged) {\n const prev = notifiedPublished;\n const changed = [\n ...pendingChanged,\n ...notifiedOverlayPaths,\n ...overlayPaths,\n ];\n pendingChanged = [];\n notifiedPublished = published;\n notifiedOverlayPaths = overlayPaths;\n options.onStateChange?.(published as never, {\n changes: createChanges(prev, published, changed),\n });\n }\n if (updates.length > 0 && published !== undefined) {\n const batch = updates;\n updates = [];\n for (const update of batch) {\n options.onCommandChange?.(update, { state: published as never });\n }\n }\n if (events.length > 0 && published !== undefined) {\n const batch = events;\n events = [];\n for (const event of batch) {\n options.onEvent?.(event, { state: published as never });\n }\n }\n const connection = getConnection();\n const connectionChanged = connection !== notifiedConnection;\n notifiedConnection = connection;\n if (stateChanged || connectionChanged) {\n for (const listener of [...listeners]) listener();\n }\n } finally {\n flushing = false;\n }\n };\n\n const note = (\n inv: CommandsInvocation,\n status: Statewire.CommandUpdate[\"status\"],\n extra?: { result?: unknown; failure?: StatewireClient.SendFailure },\n ) => {\n if (!getOptions().onCommandChange) return;\n updates.push({\n method: inv.method,\n params: inv.params as never[],\n seq: inv.seq,\n status,\n ...extra,\n });\n schedule();\n };\n\n const commandsListener: CommandsListener = {\n onStart: (inv) => {\n overlays.push({ inv, dropped: false });\n overlaysDirty = true;\n markStale();\n note(inv, \"queued\");\n },\n onPosted: (inv) => note(inv, \"posted\"),\n onAck: (inv) => note(inv, \"acked\"),\n onCovered: (inv) => {\n if (!overlays.some((o) => o.inv === inv)) return;\n overlays = overlays.filter((o) => o.inv !== inv);\n overlaysDirty = true;\n markStale();\n },\n onSettle: (inv, outcome) => {\n note(\n inv,\n \"settled\",\n outcome.type === \"accepted\"\n ? { result: outcome.payload }\n : { failure: failureOf(outcome.error) },\n );\n flush();\n },\n };\n\n const eventSink = (event: unknown) => {\n if (!getOptions().onEvent) return;\n events.push(event);\n schedule();\n };\n\n const attach = (next: Statewire<unknown>) => {\n if (next === value) return;\n const prev = value;\n value = next;\n const nextSource =\n next.source ??\n (typeof next.state === \"object\" && next.state !== null\n ? getStateHost(next.state)\n : undefined) ??\n null;\n if (nextSource !== source) {\n source?.setOnChange(null);\n source = nextSource;\n source?.setOnChange(markStale);\n markStale();\n }\n if (next.commands !== prev?.commands) {\n if (prev) getCommandsHost(prev.commands)?.setListener(null);\n getCommandsHost(next.commands)?.setListener(commandsListener);\n if (overlays.length > 0) {\n overlays = [];\n overlaysDirty = true;\n }\n markStale();\n }\n if (next.emit !== prev?.emit) {\n if (prev) getEmitHost(prev.emit)?.setSink(null);\n getEmitHost(next.emit)?.setSink(eventSink);\n }\n if (!source) markStale();\n schedule();\n };\n\n const detach = () => {\n source?.setOnChange(null);\n source = null;\n if (value) {\n getCommandsHost(value.commands)?.setListener(null);\n getEmitHost(value.emit)?.setSink(null);\n value = null;\n }\n };\n\n const commands = createMethodProxy<Statewire.CommandsProxy<any>>(\n (method, params) => {\n if (!value) {\n return Promise.reject(\n new StatewireSendError({ fate: \"aborted\", cause: \"unmount\" }),\n );\n }\n const send = (\n value.commands as Record<\n string,\n (...args: unknown[]) => Promise<unknown>\n >\n )[method]!;\n const promise = send(...params);\n flush();\n return promise;\n },\n );\n\n return {\n attach,\n detach,\n getState: () => {\n recompute();\n return published;\n },\n commands,\n subscribe: (listener: () => void) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n};\n\nexport const useStatewireCore = (\n getOptions: () => StatewireClient.Options<any, any, any>,\n value: Statewire<unknown>,\n) => {\n const [core] = useState(() => createCore(getOptions));\n // Attach during render so the first snapshot reads the transport's state;\n // re-attaching the same transport is a no-op and the single-slot host\n // listeners make a swap land on the last writer.\n core.attach(value);\n useEffect(() => {\n core.attach(value);\n });\n useEffect(() => () => core.detach(), [core]);\n return core;\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CommandsInvocation, CommandsListener } from "../host/host.js";
|
|
2
|
+
import { RemoteLink, StatewireRemoteProps, StatewireWireCommand } from "../transports/sse.js";
|
|
3
|
+
import { StatewireSessionRecord, StatewireSessionStore, StatewireSessionStoreOptions } from "./sessionStore.js";
|
|
4
|
+
import { Statewire, StatewireSendError } from "../types.js";
|
|
5
|
+
import { StatewirePath } from "../wire/ops.js";
|
|
6
|
+
//#region src/client/session.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* queued/trying/posted: overlay live, result pending. awaiting-result: acked
|
|
9
|
+
* (overlay gone) but the result rides a late res; `posted: false` re-sends.
|
|
10
|
+
* settled-live: result settled but the overlay stays until the covering ack.
|
|
11
|
+
* done: removed from the queue.
|
|
12
|
+
*/
|
|
13
|
+
type PendingState = {
|
|
14
|
+
at: "queued";
|
|
15
|
+
pendingMarked: boolean;
|
|
16
|
+
} | {
|
|
17
|
+
at: "trying";
|
|
18
|
+
pendingMarked: boolean;
|
|
19
|
+
failedAttempts: number;
|
|
20
|
+
} | {
|
|
21
|
+
at: "posted";
|
|
22
|
+
pendingMarked: boolean;
|
|
23
|
+
failedAttempts: number;
|
|
24
|
+
} | {
|
|
25
|
+
at: "awaiting-result";
|
|
26
|
+
posted: boolean;
|
|
27
|
+
failedAttempts: number;
|
|
28
|
+
} | {
|
|
29
|
+
at: "settled-live";
|
|
30
|
+
posted: boolean;
|
|
31
|
+
failedAttempts: number;
|
|
32
|
+
} | {
|
|
33
|
+
at: "done";
|
|
34
|
+
};
|
|
35
|
+
type Pending = {
|
|
36
|
+
readonly seq: number;
|
|
37
|
+
readonly command: StatewireWireCommand;
|
|
38
|
+
readonly inv: CommandsInvocation;
|
|
39
|
+
state: PendingState;
|
|
40
|
+
resolve: (payload: unknown) => void;
|
|
41
|
+
reject: (error: StatewireSendError) => void;
|
|
42
|
+
};
|
|
43
|
+
type AttachState = "detached" | "attached" | "finished";
|
|
44
|
+
type Session = {
|
|
45
|
+
record: StatewireSessionRecord;
|
|
46
|
+
ctx: StatewireRemoteProps;
|
|
47
|
+
recordPersisted: boolean;
|
|
48
|
+
store: StatewireSessionStore;
|
|
49
|
+
lastAck: number;
|
|
50
|
+
pending: Pending[];
|
|
51
|
+
canonical: unknown;
|
|
52
|
+
lastPublished: unknown;
|
|
53
|
+
changed: StatewirePath[];
|
|
54
|
+
source: Statewire.StateSource;
|
|
55
|
+
attach: AttachState;
|
|
56
|
+
attachEpoch: number;
|
|
57
|
+
running: boolean;
|
|
58
|
+
pumping: boolean;
|
|
59
|
+
transport: RemoteLink | null;
|
|
60
|
+
waiters: (() => void)[];
|
|
61
|
+
lifecycle: CommandsListener | null;
|
|
62
|
+
notify: () => void;
|
|
63
|
+
emitEvent: (event: unknown) => void;
|
|
64
|
+
send: (command: StatewireWireCommand) => Promise<unknown>;
|
|
65
|
+
commands: Record<string, (...params: unknown[]) => Promise<unknown>>;
|
|
66
|
+
};
|
|
67
|
+
declare const setTransport: (s: Session, transport: RemoteLink) => void;
|
|
68
|
+
declare const useStatewireSession: (options: StatewireSessionStoreOptions, emit: (event: unknown) => void) => Session;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { setTransport, useStatewireSession };
|
|
71
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","names":[],"sources":["../../src/client/session.ts"],"mappings":";;;;;;;;;;;;KA+BK;EACC;EAAc;;EACd;EAAc;EAAwB;;EACtC;EAAc;EAAwB;;EACtC;EAAuB;EAAiB;;EACxC;EAAoB;EAAiB;;EACrC;;KAED;WACM;WACA,SAAS;WACT,KAAK;EACd,OAAO;EACP,UAAU;EACV,SAAS,OAAO;;KAmGb;KAEA;EACH,QAAQ;EACR,KAAK;EACL;EACA,OAAO;EACP;EACA,SAAS;EACT;EACA;EACA,SAAS;EACT,QAAQ,UAAU;EAClB,QAAQ;EACR;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX;EACA,YAAY;EACZ,OAAO,SAAS,yBAAyB;EACzC,UAAU,mBAAmB,sBAAsB;;cAkd/C,eAAgB,GAAG,SAAS,WAAW;cA4ChC,sBACX,SAAS,8BACT,OAAO,4BACN"}
|