statewire 0.8.1 → 0.9.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/README.md +2 -2
- package/dist/client/connection.d.ts +23 -19
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +143 -103
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts +30 -3
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +222 -260
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +34 -39
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +380 -373
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +1 -0
- package/dist/client/sessionStore.d.ts.map +1 -1
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/client/timing.d.ts +1 -2
- package/dist/client/timing.d.ts.map +1 -1
- package/dist/client/timing.js +1 -5
- package/dist/client/timing.js.map +1 -1
- package/dist/host/draft.d.ts +2 -5
- package/dist/host/draft.d.ts.map +1 -1
- package/dist/host/draft.js.map +1 -1
- package/dist/host/host.d.ts +5 -8
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +23 -32
- package/dist/host/host.js.map +1 -1
- package/dist/host/socket.d.ts +3 -2
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +484 -424
- package/dist/host/socket.js.map +1 -1
- package/dist/host-internal.d.ts +4 -0
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host.d.ts +10 -10
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +2 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/statewire.d.ts +29 -24
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +8 -2
- package/dist/statewire.js.map +1 -1
- package/dist/transports/fake-sse.d.ts +1 -1
- package/dist/transports/fake-sse.d.ts.map +1 -1
- package/dist/transports/fake-sse.js +5 -2
- package/dist/transports/fake-sse.js.map +1 -1
- package/dist/transports/fixtures.d.ts +1 -2
- package/dist/transports/fixtures.d.ts.map +1 -1
- package/dist/transports/fixtures.js +5 -7
- package/dist/transports/fixtures.js.map +1 -1
- package/dist/transports/remote.d.ts +2 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +10 -4
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +32 -46
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +56 -59
- 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 +41 -14
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.d.ts +2 -3
- package/dist/transports/ws.d.ts.map +1 -1
- package/dist/transports/ws.js +25 -80
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +8 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -4
- package/dist/types.js.map +1 -1
- package/dist/wire/ops.d.ts +1 -5
- package/dist/wire/ops.d.ts.map +1 -1
- package/dist/wire/ops.js +1 -4
- package/dist/wire/ops.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connection.ts +175 -128
- package/src/client/core.ts +287 -278
- package/src/client/session.ts +474 -480
- package/src/client/sessionStore.ts +1 -0
- package/src/client/timing.ts +0 -6
- package/src/host/draft.ts +1 -3
- package/src/host/host.ts +41 -58
- package/src/host/socket.ts +653 -448
- package/src/host-internal.ts +4 -0
- package/src/host.ts +13 -10
- package/src/index.ts +0 -1
- package/src/statewire.ts +38 -32
- package/src/transports/fake-sse.ts +2 -1
- package/src/transports/fixtures.ts +4 -9
- package/src/transports/remote.ts +12 -5
- package/src/transports/sse.ts +99 -115
- package/src/transports/testing.ts +47 -19
- package/src/transports/ws.ts +44 -115
- package/src/types.ts +15 -14
- package/src/wire/ops.ts +9 -16
package/src/client/timing.ts
CHANGED
|
@@ -6,9 +6,3 @@ export const timing = {
|
|
|
6
6
|
|
|
7
7
|
export const backoff = (attempt: number) =>
|
|
8
8
|
Math.min(timing.backoffBaseMs * 2 ** (attempt - 1), timing.backoffMaxMs);
|
|
9
|
-
|
|
10
|
-
export const retryDelay = (error: unknown, attempt: number): number => {
|
|
11
|
-
const retryAfterMs = (error as { retryAfterMs?: unknown } | undefined)
|
|
12
|
-
?.retryAfterMs;
|
|
13
|
-
return typeof retryAfterMs === "number" ? retryAfterMs : backoff(attempt);
|
|
14
|
-
};
|
package/src/host/draft.ts
CHANGED
|
@@ -4,11 +4,9 @@ import { sanitizeValue } from "../wire/ops";
|
|
|
4
4
|
import { isUnsafeKey } from "../util/statePath";
|
|
5
5
|
import { sharedDraftTraps } from "../util/draftTraps";
|
|
6
6
|
|
|
7
|
-
export type ServerOp = Exclude<Statewire.Op, { op: "event" }>;
|
|
8
|
-
|
|
9
7
|
export type DraftHost = {
|
|
10
8
|
snapshot: () => unknown;
|
|
11
|
-
record: (op:
|
|
9
|
+
record: (op: Statewire.Op, changedPath: StatewirePath) => void;
|
|
12
10
|
};
|
|
13
11
|
|
|
14
12
|
export type StateDraft<T> = {
|
package/src/host/host.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { StatewirePath } from "../wire/ops";
|
|
|
4
4
|
import { StatewireReject } from "./engine";
|
|
5
5
|
import { createStateDraft } from "./draft";
|
|
6
6
|
import { createMethodProxy } from "../util/methodProxy";
|
|
7
|
-
import type {
|
|
7
|
+
import type { StateDraft } from "./draft";
|
|
8
8
|
import { StatewireSendError } from "../types";
|
|
9
9
|
import type { Statewire } from "../types";
|
|
10
10
|
import type { StatewireHost } from "../host";
|
|
@@ -25,7 +25,7 @@ type StateCell = {
|
|
|
25
25
|
current: unknown;
|
|
26
26
|
draft: StateDraft<object> | null;
|
|
27
27
|
changed: StatewirePath[];
|
|
28
|
-
onOp: ((op:
|
|
28
|
+
onOp: ((op: Statewire.Op) => void) | null;
|
|
29
29
|
onChange: (() => void) | null;
|
|
30
30
|
ctl: StatewireHost.StateCtl;
|
|
31
31
|
host: StatewireHost.StateHost;
|
|
@@ -51,15 +51,10 @@ export const useStatewireState = <TState>(
|
|
|
51
51
|
ctl: {
|
|
52
52
|
apply: (ops) => {
|
|
53
53
|
const result = applyOps(commit(), ops);
|
|
54
|
-
if (result.events.length > 0) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
"statewire: event ops do not apply to state — route them to emit",
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
54
|
c.current = result.state;
|
|
60
55
|
c.draft?.invalidate();
|
|
61
56
|
c.changed.push(...result.changedPaths);
|
|
62
|
-
if (c.onOp) for (const op of ops) c.onOp(op
|
|
57
|
+
if (c.onOp) for (const op of ops) c.onOp(op);
|
|
63
58
|
c.bump();
|
|
64
59
|
c.onChange?.();
|
|
65
60
|
},
|
|
@@ -121,7 +116,7 @@ export type CommandsOutcome =
|
|
|
121
116
|
export type CommandsListener = {
|
|
122
117
|
onStart: (inv: CommandsInvocation) => void;
|
|
123
118
|
onPosted?: (inv: CommandsInvocation) => void;
|
|
124
|
-
|
|
119
|
+
onApplied: (inv: CommandsInvocation) => void;
|
|
125
120
|
/** Canonical state now covers the invocation's effect — drop any overlay. */
|
|
126
121
|
onCovered?: (inv: CommandsInvocation) => void;
|
|
127
122
|
onSettle: (inv: CommandsInvocation, outcome: CommandsOutcome) => void;
|
|
@@ -132,13 +127,15 @@ export type CommandsSubmission = {
|
|
|
132
127
|
params: readonly unknown[];
|
|
133
128
|
clientId?: string;
|
|
134
129
|
caller?: StatewireHost.Caller;
|
|
135
|
-
|
|
130
|
+
onApplied?: () => void;
|
|
136
131
|
onSettle?: (outcome: CommandsOutcome) => void;
|
|
137
132
|
};
|
|
138
133
|
|
|
139
134
|
export type CommandsHost = {
|
|
140
135
|
setListener: (listener: CommandsListener | null) => void;
|
|
141
136
|
submit?: (call: CommandsSubmission) => Promise<unknown>;
|
|
137
|
+
/** Registered command names, read live. */
|
|
138
|
+
methods?: () => string[];
|
|
142
139
|
};
|
|
143
140
|
|
|
144
141
|
const commandsHosts = new WeakMap<object, CommandsHost>();
|
|
@@ -160,7 +157,7 @@ type QueuedCall = {
|
|
|
160
157
|
};
|
|
161
158
|
|
|
162
159
|
type ActiveInvocation = CommandsInvocation & {
|
|
163
|
-
|
|
160
|
+
applied: boolean;
|
|
164
161
|
origin?: CommandsSubmission;
|
|
165
162
|
};
|
|
166
163
|
|
|
@@ -188,19 +185,24 @@ const LOCAL_CALLER: StatewireHost.Caller = Object.freeze({
|
|
|
188
185
|
const toSendError = (error: unknown): StatewireSendError =>
|
|
189
186
|
error instanceof StatewireSendError
|
|
190
187
|
? error
|
|
191
|
-
:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
error.payload !== undefined && { payload: error.payload }),
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
188
|
+
: error instanceof StatewireReject
|
|
189
|
+
? new StatewireSendError({
|
|
190
|
+
fate: "rejected",
|
|
191
|
+
cause: "server",
|
|
192
|
+
message: error.message,
|
|
193
|
+
...(error.payload !== undefined && { payload: error.payload }),
|
|
194
|
+
})
|
|
195
|
+
: new StatewireSendError({
|
|
196
|
+
fate: "failed",
|
|
197
|
+
cause: "server",
|
|
198
|
+
message: error instanceof Error ? error.message : String(error),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const applyInvocation = (cell: CommandsCell, inv: ActiveInvocation) => {
|
|
202
|
+
if (inv.applied) return;
|
|
203
|
+
inv.applied = true;
|
|
204
|
+
cell.listener?.onApplied(inv);
|
|
205
|
+
inv.origin?.onApplied?.();
|
|
204
206
|
};
|
|
205
207
|
|
|
206
208
|
const settleInvocation = (
|
|
@@ -209,7 +211,7 @@ const settleInvocation = (
|
|
|
209
211
|
call: QueuedCall,
|
|
210
212
|
settled: Settled,
|
|
211
213
|
) => {
|
|
212
|
-
|
|
214
|
+
applyInvocation(cell, inv);
|
|
213
215
|
if (settled.type === "resolved") {
|
|
214
216
|
const outcome: CommandsOutcome =
|
|
215
217
|
settled.value === undefined
|
|
@@ -236,8 +238,9 @@ const dispatch = (cell: CommandsCell, call: QueuedCall) => {
|
|
|
236
238
|
const handler = cell.handlers![call.method];
|
|
237
239
|
if (handler === undefined) {
|
|
238
240
|
const error = new StatewireSendError({
|
|
239
|
-
fate: "
|
|
241
|
+
fate: "rejected",
|
|
240
242
|
cause: "server",
|
|
243
|
+
code: "unknown-command",
|
|
241
244
|
});
|
|
242
245
|
call.origin?.onSettle?.({ type: "rejected", error });
|
|
243
246
|
call.reject(error);
|
|
@@ -248,7 +251,7 @@ const dispatch = (cell: CommandsCell, call: QueuedCall) => {
|
|
|
248
251
|
params: call.params,
|
|
249
252
|
ts: call.ts,
|
|
250
253
|
seq: ++cell.seq,
|
|
251
|
-
|
|
254
|
+
applied: false,
|
|
252
255
|
...(call.origin !== undefined && { origin: call.origin }),
|
|
253
256
|
...(call.origin?.clientId !== undefined && {
|
|
254
257
|
clientId: call.origin.clientId,
|
|
@@ -259,7 +262,7 @@ const dispatch = (cell: CommandsCell, call: QueuedCall) => {
|
|
|
259
262
|
cell.listener?.onCovered?.(inv);
|
|
260
263
|
const ctx: StatewireHost.Ctx = {
|
|
261
264
|
caller: call.origin?.caller ?? LOCAL_CALLER,
|
|
262
|
-
|
|
265
|
+
applied: () => applyInvocation(cell, inv),
|
|
263
266
|
};
|
|
264
267
|
try {
|
|
265
268
|
const value = (
|
|
@@ -358,6 +361,7 @@ export const useStatewireCommands = <THandlers extends Statewire.Commands>(
|
|
|
358
361
|
setListener: (listener) => {
|
|
359
362
|
cell.listener = listener;
|
|
360
363
|
},
|
|
364
|
+
methods: () => (cell.handlers ? Object.keys(cell.handlers) : []),
|
|
361
365
|
submit: (origin) =>
|
|
362
366
|
new Promise((resolve, reject) => {
|
|
363
367
|
if (cell.unmounted) {
|
|
@@ -400,39 +404,18 @@ export const useStatewireCommands = <THandlers extends Statewire.Commands>(
|
|
|
400
404
|
}, [built]);
|
|
401
405
|
|
|
402
406
|
useEffect(() => {
|
|
407
|
+
if (commands) {
|
|
408
|
+
for (const method of Object.keys(commands)) {
|
|
409
|
+
if (method.startsWith("statewire/")) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`statewire: cannot register "${method}": the "statewire/" method namespace is reserved`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
403
416
|
built.cell.handlers = commands || null;
|
|
404
417
|
pump(built.cell);
|
|
405
418
|
});
|
|
406
419
|
|
|
407
420
|
return built.proxy;
|
|
408
421
|
};
|
|
409
|
-
|
|
410
|
-
// ─── emit ───────────────────────────────────────────────
|
|
411
|
-
|
|
412
|
-
export type EmitHost = {
|
|
413
|
-
setSink: (sink: ((event: unknown) => void) | null) => void;
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
const emitHosts = new WeakMap<object, EmitHost>();
|
|
417
|
-
|
|
418
|
-
export const getEmitHost = (emit: object | undefined): EmitHost | undefined =>
|
|
419
|
-
emit && emitHosts.get(emit);
|
|
420
|
-
|
|
421
|
-
export const useStatewireEmit = <TEvent>(): ((event: TEvent) => void) => {
|
|
422
|
-
const [built] = useState(() => {
|
|
423
|
-
const cell = { sink: null as ((event: unknown) => void) | null };
|
|
424
|
-
const emit = (event: TEvent) => {
|
|
425
|
-
if (!cell.sink) {
|
|
426
|
-
throw new Error("statewire: emit before a host bound the event sink");
|
|
427
|
-
}
|
|
428
|
-
cell.sink(sanitizeValue(event));
|
|
429
|
-
};
|
|
430
|
-
emitHosts.set(emit, {
|
|
431
|
-
setSink: (sink) => {
|
|
432
|
-
cell.sink = sink;
|
|
433
|
-
},
|
|
434
|
-
});
|
|
435
|
-
return emit;
|
|
436
|
-
});
|
|
437
|
-
return built;
|
|
438
|
-
};
|