statewire 0.18.2 → 0.19.0
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/dist/client/connection.d.ts +8 -5
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +24 -13
- package/dist/client/connection.js.map +1 -1
- package/dist/client/session.d.ts +13 -15
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +291 -256
- package/dist/client/session.js.map +1 -1
- package/dist/client/timing.d.ts +2 -1
- package/dist/client/timing.d.ts.map +1 -1
- package/dist/client/timing.js +2 -1
- package/dist/client/timing.js.map +1 -1
- package/dist/host/engine.d.ts +2 -0
- package/dist/host/engine.d.ts.map +1 -1
- package/dist/host/engine.js +2 -0
- package/dist/host/engine.js.map +1 -1
- package/dist/host/host.d.ts +3 -1
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +38 -7
- package/dist/host/host.js.map +1 -1
- package/dist/host/http.d.ts.map +1 -1
- package/dist/host/http.js +8 -17
- package/dist/host/http.js.map +1 -1
- package/dist/host/socket.d.ts +47 -23
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +389 -245
- package/dist/host/socket.js.map +1 -1
- package/dist/host/synced.d.ts +9 -0
- package/dist/host/synced.d.ts.map +1 -0
- package/dist/host/synced.js +45 -0
- package/dist/host/synced.js.map +1 -0
- package/dist/host-internal.d.ts +20 -8
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host.d.ts +33 -9
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +2 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/statewire.d.ts +8 -10
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +0 -14
- package/dist/statewire.js.map +1 -1
- package/dist/storage.d.ts +2 -5
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +55 -41
- package/dist/storage.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +1 -6
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +2 -5
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +7 -5
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +19 -19
- package/dist/transports/testing.js.map +1 -1
- package/dist/types.d.ts +3 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wire/limits.d.ts +2 -2
- package/dist/wire/limits.js +2 -2
- package/dist/wire/limits.js.map +1 -1
- package/dist/wire/protocol.d.ts +6 -1
- package/dist/wire/protocol.d.ts.map +1 -1
- package/dist/wire/protocol.js +33 -12
- package/dist/wire/protocol.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connection.ts +46 -33
- package/src/client/session.ts +434 -365
- package/src/client/timing.ts +2 -0
- package/src/host/engine.ts +3 -1
- package/src/host/host.ts +50 -3
- package/src/host/http.ts +22 -23
- package/src/host/socket.ts +550 -321
- package/src/host/synced.ts +60 -0
- package/src/host-internal.ts +21 -3
- package/src/host.ts +40 -8
- package/src/index.ts +1 -0
- package/src/statewire.ts +8 -30
- package/src/storage.ts +96 -79
- package/src/transports/remote.ts +0 -7
- package/src/transports/sse.ts +8 -3
- package/src/transports/testing.ts +34 -19
- package/src/types.ts +3 -5
- package/src/wire/limits.ts +1 -1
- package/src/wire/protocol.ts +40 -11
package/src/client/timing.ts
CHANGED
package/src/host/engine.ts
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* message/payload travel to the client's rejected promise.
|
|
4
4
|
*/
|
|
5
5
|
export class StatewireReject extends Error {
|
|
6
|
+
readonly code?: string;
|
|
6
7
|
readonly payload?: unknown;
|
|
7
8
|
|
|
8
|
-
constructor(message: string, options?: { payload?: unknown }) {
|
|
9
|
+
constructor(message: string, options?: { code?: string; payload?: unknown }) {
|
|
9
10
|
super(message);
|
|
10
11
|
this.name = "StatewireReject";
|
|
12
|
+
if (options?.code !== undefined) this.code = options.code;
|
|
11
13
|
if (options?.payload !== undefined) this.payload = options.payload;
|
|
12
14
|
}
|
|
13
15
|
}
|
package/src/host/host.ts
CHANGED
|
@@ -16,6 +16,9 @@ const isDev = () =>
|
|
|
16
16
|
|
|
17
17
|
const stateHosts = new WeakMap<object, StatewireHost.StateHost>();
|
|
18
18
|
|
|
19
|
+
export const documentIdentity = (protocol: string, key?: readonly string[]) =>
|
|
20
|
+
JSON.stringify([protocol, key ?? null]);
|
|
21
|
+
|
|
19
22
|
/** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */
|
|
20
23
|
export const getStateHost = (
|
|
21
24
|
state: object,
|
|
@@ -25,8 +28,10 @@ type StateCell = {
|
|
|
25
28
|
current: unknown;
|
|
26
29
|
draft: StateDraft<object> | null;
|
|
27
30
|
changed: StatewirePath[];
|
|
31
|
+
documents: Map<string, StatewireHost.Document>;
|
|
28
32
|
onOp: ((op: Statewire.Op) => void) | null;
|
|
29
33
|
onChange: (() => void) | null;
|
|
34
|
+
onDocuments: (() => void) | null;
|
|
30
35
|
ctl: StatewireHost.StateCtl;
|
|
31
36
|
host: StatewireHost.StateHost;
|
|
32
37
|
bump: () => void;
|
|
@@ -45,8 +50,10 @@ export const useStatewireState = <TState>(
|
|
|
45
50
|
current: sanitizeValue(init()),
|
|
46
51
|
draft: null,
|
|
47
52
|
changed: [[]],
|
|
53
|
+
documents: new Map(),
|
|
48
54
|
onOp: null,
|
|
49
55
|
onChange: null,
|
|
56
|
+
onDocuments: null,
|
|
50
57
|
bump: () => setVersion((v) => v + 1),
|
|
51
58
|
ctl: {
|
|
52
59
|
apply: (ops) => {
|
|
@@ -73,6 +80,38 @@ export const useStatewireState = <TState>(
|
|
|
73
80
|
c.changed = [];
|
|
74
81
|
return changed;
|
|
75
82
|
},
|
|
83
|
+
mount: (protocol, value, options) => {
|
|
84
|
+
const key = options?.key;
|
|
85
|
+
const main = options?.main ?? key === undefined;
|
|
86
|
+
if (main) {
|
|
87
|
+
for (const [id, doc] of c.documents) {
|
|
88
|
+
if (
|
|
89
|
+
doc.protocol === protocol &&
|
|
90
|
+
doc.main &&
|
|
91
|
+
id !== documentIdentity(protocol, key)
|
|
92
|
+
)
|
|
93
|
+
throw new Error(
|
|
94
|
+
`statewire: ${protocol} already has a main document`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
c.documents.set(documentIdentity(protocol, key), {
|
|
99
|
+
protocol,
|
|
100
|
+
key: key === undefined ? null : [...key],
|
|
101
|
+
main,
|
|
102
|
+
value: sanitizeValue(value),
|
|
103
|
+
});
|
|
104
|
+
c.onDocuments?.();
|
|
105
|
+
},
|
|
106
|
+
unmount: (protocol, key) => {
|
|
107
|
+
if (!c.documents.delete(documentIdentity(protocol, key)))
|
|
108
|
+
throw new Error("statewire: document is not mounted");
|
|
109
|
+
c.onDocuments?.();
|
|
110
|
+
},
|
|
111
|
+
documents: () => c.documents,
|
|
112
|
+
setOnDocuments: (listener) => {
|
|
113
|
+
c.onDocuments = listener;
|
|
114
|
+
},
|
|
76
115
|
},
|
|
77
116
|
};
|
|
78
117
|
return c;
|
|
@@ -128,6 +167,7 @@ export type CommandsSubmission = {
|
|
|
128
167
|
params: readonly unknown[];
|
|
129
168
|
clientId?: string;
|
|
130
169
|
caller?: StatewireHost.Caller;
|
|
170
|
+
attach?: StatewireHost.Attach;
|
|
131
171
|
onApplied?: () => void;
|
|
132
172
|
onSettle?: (outcome: CommandsOutcome) => void;
|
|
133
173
|
};
|
|
@@ -178,13 +218,12 @@ type CommandsCell = {
|
|
|
178
218
|
|
|
179
219
|
const STALL_WARN_MS = 5000;
|
|
180
220
|
|
|
181
|
-
// In-process dispatch has no wire client:
|
|
221
|
+
// In-process dispatch has no wire client: trivially attached.
|
|
182
222
|
const LOCAL_CALLER: StatewireHost.Caller = Object.freeze({
|
|
183
223
|
get clientId(): string {
|
|
184
224
|
throw new Error("statewire: in-process dispatch has no client id");
|
|
185
225
|
},
|
|
186
226
|
auth: undefined,
|
|
187
|
-
context: null,
|
|
188
227
|
isConnected: true,
|
|
189
228
|
});
|
|
190
229
|
|
|
@@ -196,6 +235,7 @@ const toSendError = (error: unknown): StatewireSendError =>
|
|
|
196
235
|
fate: "rejected",
|
|
197
236
|
cause: "server",
|
|
198
237
|
message: error.message,
|
|
238
|
+
...(error.code !== undefined && { code: error.code }),
|
|
199
239
|
...(error.payload !== undefined && { payload: error.payload }),
|
|
200
240
|
})
|
|
201
241
|
: new StatewireSendError({
|
|
@@ -221,8 +261,8 @@ const settleInvocation = (
|
|
|
221
261
|
if (inv.settled) return;
|
|
222
262
|
inv.settled = true;
|
|
223
263
|
cell.active.delete(inv);
|
|
224
|
-
applyInvocation(cell, inv);
|
|
225
264
|
if (settled.type === "resolved") {
|
|
265
|
+
applyInvocation(cell, inv);
|
|
226
266
|
const outcome: CommandsOutcome =
|
|
227
267
|
settled.value === undefined
|
|
228
268
|
? { type: "accepted" }
|
|
@@ -231,6 +271,7 @@ const settleInvocation = (
|
|
|
231
271
|
inv.origin?.onSettle?.(outcome);
|
|
232
272
|
call.resolve(outcome.payload);
|
|
233
273
|
} else {
|
|
274
|
+
cell.listener?.onCovered?.(inv);
|
|
234
275
|
const error = toSendError(settled.error);
|
|
235
276
|
const outcome: CommandsOutcome = { type: "rejected", error };
|
|
236
277
|
cell.listener?.onSettle(inv, outcome);
|
|
@@ -299,8 +340,14 @@ const dispatch = (cell: CommandsCell, call: QueuedCall) => {
|
|
|
299
340
|
cell.active.set(inv, call);
|
|
300
341
|
cell.listener?.onStart(inv);
|
|
301
342
|
cell.listener?.onPosted?.(inv);
|
|
343
|
+
const attach = call.origin?.attach;
|
|
302
344
|
const ctx: StatewireHost.Ctx = {
|
|
303
345
|
caller: call.origin?.caller ?? LOCAL_CALLER,
|
|
346
|
+
get attach() {
|
|
347
|
+
if (attach === undefined)
|
|
348
|
+
throw new Error("statewire: in-process dispatch has no attach");
|
|
349
|
+
return attach;
|
|
350
|
+
},
|
|
304
351
|
signal: inv.controller.signal,
|
|
305
352
|
applied: () => {
|
|
306
353
|
if (!inv.settled) applyInvocation(cell, inv);
|
package/src/host/http.ts
CHANGED
|
@@ -141,6 +141,7 @@ const attachSse = (
|
|
|
141
141
|
clientId: string,
|
|
142
142
|
auth: unknown,
|
|
143
143
|
selection: StatewireProtocol.Selection,
|
|
144
|
+
frame?: ParsedFrame,
|
|
144
145
|
): SseAttachment => {
|
|
145
146
|
let controller: ReadableStreamDefaultController<Uint8Array>;
|
|
146
147
|
let connection: Registration["connection"] | null = null;
|
|
@@ -194,7 +195,15 @@ const attachSse = (
|
|
|
194
195
|
}
|
|
195
196
|
},
|
|
196
197
|
};
|
|
197
|
-
const registration = register(
|
|
198
|
+
const registration = register(
|
|
199
|
+
h,
|
|
200
|
+
clientId,
|
|
201
|
+
socket,
|
|
202
|
+
false,
|
|
203
|
+
auth,
|
|
204
|
+
selection,
|
|
205
|
+
frame,
|
|
206
|
+
);
|
|
198
207
|
connection = registration?.connection ?? null;
|
|
199
208
|
if (!open) connection?.close();
|
|
200
209
|
return {
|
|
@@ -220,18 +229,6 @@ const streamResponse = (
|
|
|
220
229
|
});
|
|
221
230
|
};
|
|
222
231
|
|
|
223
|
-
const admitAttachFrame = (
|
|
224
|
-
h: SocketHostState,
|
|
225
|
-
registration: Registration,
|
|
226
|
-
frame: ParsedFrame,
|
|
227
|
-
) => {
|
|
228
|
-
if (frame.ctx.length === 0 && frame.cmd.length === 0) return;
|
|
229
|
-
const admission = admitFrame(h, registration.conn, frame);
|
|
230
|
-
if (admission.type === "refused") {
|
|
231
|
-
sendTo(h, registration.conn, JSON.stringify(admission.syn));
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
|
|
235
232
|
const getStream = async (
|
|
236
233
|
h: SocketHostState,
|
|
237
234
|
request: Request,
|
|
@@ -277,10 +274,7 @@ const postStream = async (
|
|
|
277
274
|
}
|
|
278
275
|
let frame: ParsedFrame;
|
|
279
276
|
try {
|
|
280
|
-
frame =
|
|
281
|
-
body === null && !raw.trim()
|
|
282
|
-
? { ctx: [], cmd: [] }
|
|
283
|
-
: parseFrameValue(body);
|
|
277
|
+
frame = body === null && !raw.trim() ? { cmd: [] } : parseFrameValue(body);
|
|
284
278
|
} catch (error) {
|
|
285
279
|
if (!(error instanceof Error) || error.name !== "StatewireViolation") {
|
|
286
280
|
throw error;
|
|
@@ -301,14 +295,16 @@ const postStream = async (
|
|
|
301
295
|
]);
|
|
302
296
|
if ("fin" in selection)
|
|
303
297
|
return Response.json(selection, { status: selection.fin.status });
|
|
304
|
-
|
|
305
|
-
if (attachment.registration === null) return streamResponse(attachment, null);
|
|
306
|
-
const lease = mintLease();
|
|
307
|
-
attachment.registration.conn.lease = lease;
|
|
298
|
+
let attachment: SseAttachment;
|
|
308
299
|
try {
|
|
309
|
-
|
|
300
|
+
attachment = attachSse(
|
|
301
|
+
h,
|
|
302
|
+
client.clientId,
|
|
303
|
+
authorized.auth,
|
|
304
|
+
selection,
|
|
305
|
+
frame,
|
|
306
|
+
);
|
|
310
307
|
} catch (error) {
|
|
311
|
-
closeConn(h, attachment.registration.conn, 1008, "invalid-context");
|
|
312
308
|
if (!(error instanceof Error) || error.name !== "StatewireViolation")
|
|
313
309
|
throw error;
|
|
314
310
|
return Response.json(
|
|
@@ -316,6 +312,9 @@ const postStream = async (
|
|
|
316
312
|
{ status: 400 },
|
|
317
313
|
);
|
|
318
314
|
}
|
|
315
|
+
if (attachment.registration === null) return streamResponse(attachment, null);
|
|
316
|
+
const lease = mintLease();
|
|
317
|
+
attachment.registration.conn.lease = lease;
|
|
319
318
|
return streamResponse(attachment, lease);
|
|
320
319
|
};
|
|
321
320
|
|