statewire 0.23.0 → 0.24.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/dist/client/core.d.ts +4 -3
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +56 -45
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +56 -12
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +632 -260
- 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 +1 -0
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/host/host.d.ts +6 -5
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js.map +1 -1
- package/dist/indexedDb.d.ts +15 -0
- package/dist/indexedDb.d.ts.map +1 -0
- package/dist/indexedDb.js +171 -0
- package/dist/indexedDb.js.map +1 -0
- package/dist/statewire.d.ts +117 -59
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +13 -25
- package/dist/statewire.js.map +1 -1
- package/dist/storage.d.ts +42 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +387 -125
- package/dist/storage.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +7 -9
- package/dist/transports/remote.js.map +1 -1
- package/dist/types.d.ts +6 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -1
- package/src/client/core.ts +81 -64
- package/src/client/session.ts +886 -424
- package/src/client/sessionStore.ts +2 -0
- package/src/host/host.ts +10 -3
- package/src/indexedDb.ts +167 -0
- package/src/statewire.ts +133 -86
- package/src/storage.ts +548 -112
- package/src/transports/remote.ts +13 -14
- package/src/types.ts +6 -7
package/src/client/core.ts
CHANGED
|
@@ -16,7 +16,6 @@ export const CONNECTED_CONNECTION: StatewireClient.Connection = {
|
|
|
16
16
|
reconnect: () => {},
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const NO_FAILED: readonly StatewireClient.FailedCommand[] = [];
|
|
20
19
|
const NO_OUTBOX: readonly StatewireClient.OutboxEntry[] = [];
|
|
21
20
|
|
|
22
21
|
const failureOf = (error: unknown): StatewireClient.SendFailure =>
|
|
@@ -28,13 +27,17 @@ const failureOf = (error: unknown): StatewireClient.SendFailure =>
|
|
|
28
27
|
message: error instanceof Error ? error.message : String(error),
|
|
29
28
|
};
|
|
30
29
|
|
|
31
|
-
type Overlay = { inv: CommandsInvocation
|
|
30
|
+
type Overlay = { inv: CommandsInvocation };
|
|
32
31
|
|
|
33
32
|
type Core = {
|
|
34
33
|
getOptions: () => StatewireClient.Options<any, any>;
|
|
35
34
|
value: Statewire<unknown> | null;
|
|
36
35
|
source: Statewire.StateSource | null;
|
|
37
36
|
overlays: Overlay[];
|
|
37
|
+
overlayOutbox: readonly StatewireClient.OutboxEntry[];
|
|
38
|
+
/** Folds whose reducer threw. */
|
|
39
|
+
dropped: WeakSet<object>;
|
|
40
|
+
droppedIds: Set<string>;
|
|
38
41
|
updates: Statewire.CommandUpdate[];
|
|
39
42
|
pendingChanged: StatewirePath[];
|
|
40
43
|
canonical: unknown;
|
|
@@ -45,7 +48,6 @@ type Core = {
|
|
|
45
48
|
notifiedConnection: StatewireClient.Connection | null;
|
|
46
49
|
notifiedError: StatewireClient.ConnectionError | undefined;
|
|
47
50
|
notifiedBusy: boolean;
|
|
48
|
-
notifiedFailed: readonly StatewireClient.FailedCommand[];
|
|
49
51
|
notifiedOutbox: readonly StatewireClient.OutboxEntry[];
|
|
50
52
|
stale: boolean;
|
|
51
53
|
everAttached: boolean;
|
|
@@ -58,7 +60,6 @@ type Core = {
|
|
|
58
60
|
commandsListener: CommandsListener;
|
|
59
61
|
getState: () => unknown;
|
|
60
62
|
getBusy: () => boolean;
|
|
61
|
-
getFailed: () => readonly StatewireClient.FailedCommand[];
|
|
62
63
|
getOutbox: () => readonly StatewireClient.OutboxEntry[];
|
|
63
64
|
rematerialize: () => void;
|
|
64
65
|
commands: Statewire.CommandsProxy<any>;
|
|
@@ -100,15 +101,6 @@ const drainCanonical = (c: Core) => {
|
|
|
100
101
|
}
|
|
101
102
|
};
|
|
102
103
|
|
|
103
|
-
const dropCovered = (c: Core) => {
|
|
104
|
-
const { failureCovered } = c.getOptions();
|
|
105
|
-
const failures = c.value?.failures;
|
|
106
|
-
if (!failureCovered || !failures || c.canonical === undefined) return;
|
|
107
|
-
for (const entry of failures.list()) {
|
|
108
|
-
if (failureCovered(c.canonical as never, entry)) failures.discard(entry);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
104
|
const recompute = (c: Core) => {
|
|
113
105
|
if (!c.value) return;
|
|
114
106
|
const { optimistic, materialize, materializeKey } = c.getOptions();
|
|
@@ -117,44 +109,82 @@ const recompute = (c: Core) => {
|
|
|
117
109
|
c.stale = false;
|
|
118
110
|
const prevCanonical = c.canonical;
|
|
119
111
|
drainCanonical(c);
|
|
120
|
-
|
|
112
|
+
const outbox = c.value.outbox?.list() ?? NO_OUTBOX;
|
|
113
|
+
if (outbox !== c.overlayOutbox) {
|
|
114
|
+
c.overlayOutbox = outbox;
|
|
115
|
+
c.overlaysDirty = true;
|
|
116
|
+
}
|
|
121
117
|
if (c.canonical === prevCanonical && !c.overlaysDirty && !rekeyed) return;
|
|
122
118
|
c.overlaysDirty = false;
|
|
123
119
|
c.materializeKey = materializeKey;
|
|
124
120
|
let next = c.canonical;
|
|
125
121
|
let paths: readonly StatewirePath[] = [];
|
|
126
122
|
if (optimistic && c.canonical !== undefined) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
123
|
+
const protocol = c.getOptions().protocol ?? "default";
|
|
124
|
+
const unsent = outbox.filter((e) => e.state !== "sending");
|
|
125
|
+
const unsentIds = new Set(unsent.map((e) => e.id));
|
|
126
|
+
const clientId = c.value.outbox?.clientId();
|
|
127
|
+
// In queue order: the commands on their way, then the outbox's queued
|
|
128
|
+
// and held entries across sharers.
|
|
129
|
+
const folds: {
|
|
130
|
+
key: object;
|
|
131
|
+
id: string | undefined;
|
|
132
|
+
method: string;
|
|
133
|
+
params: readonly unknown[];
|
|
134
|
+
ctx: StatewireClient.OptimisticContext;
|
|
135
|
+
}[] = [
|
|
136
|
+
...c.overlays
|
|
137
|
+
.filter(
|
|
138
|
+
({ inv }) => inv.protocol === undefined || inv.protocol === protocol,
|
|
139
|
+
)
|
|
140
|
+
.filter(({ inv }) => inv.id === undefined || !unsentIds.has(inv.id))
|
|
141
|
+
.map(({ inv }) => ({
|
|
142
|
+
key: inv,
|
|
143
|
+
id: inv.id,
|
|
144
|
+
method: inv.method,
|
|
145
|
+
params: inv.params,
|
|
146
|
+
ctx: {
|
|
147
|
+
ts: inv.ts,
|
|
148
|
+
...(inv.seq !== undefined && { seq: inv.seq }),
|
|
149
|
+
...(inv.clientId !== undefined && { clientId: inv.clientId }),
|
|
150
|
+
state:
|
|
151
|
+
inv.seq !== undefined ? "sending" : inv.held ? "held" : "queued",
|
|
152
|
+
localMeta: inv.localMeta,
|
|
153
|
+
} satisfies StatewireClient.OptimisticContext,
|
|
154
|
+
})),
|
|
155
|
+
...unsent
|
|
156
|
+
.filter((e) => e.protocol === protocol)
|
|
157
|
+
.map((e) => ({
|
|
158
|
+
key: e,
|
|
159
|
+
id: e.id,
|
|
160
|
+
method: e.command.method,
|
|
161
|
+
params: e.command.params,
|
|
162
|
+
ctx: {
|
|
163
|
+
ts: e.ts,
|
|
164
|
+
...(clientId !== undefined && { clientId }),
|
|
165
|
+
state: e.state,
|
|
166
|
+
localMeta: e.localMeta,
|
|
167
|
+
} satisfies StatewireClient.OptimisticContext,
|
|
168
|
+
})),
|
|
169
|
+
];
|
|
170
|
+
for (const fold of folds) {
|
|
171
|
+
if (c.dropped.has(fold.key) || (fold.id && c.droppedIds.has(fold.id)))
|
|
134
172
|
continue;
|
|
135
|
-
const ctx: StatewireClient.OptimisticContext = {
|
|
136
|
-
ts: inv.ts,
|
|
137
|
-
...(inv.seq !== undefined && { seq: inv.seq }),
|
|
138
|
-
...(inv.clientId !== undefined && { clientId: inv.clientId }),
|
|
139
|
-
held:
|
|
140
|
-
inv.seq === undefined && inv.hold !== undefined
|
|
141
|
-
? { meta: inv.hold.meta }
|
|
142
|
-
: null,
|
|
143
|
-
};
|
|
144
173
|
try {
|
|
145
174
|
const recorded = recordMutations(next, (draft) =>
|
|
146
175
|
optimistic(
|
|
147
176
|
draft as never,
|
|
148
|
-
{ method:
|
|
149
|
-
ctx,
|
|
177
|
+
{ method: fold.method, params: fold.params } as Statewire.Command,
|
|
178
|
+
fold.ctx,
|
|
150
179
|
),
|
|
151
180
|
);
|
|
152
181
|
next = recorded.state;
|
|
153
182
|
paths = [...paths, ...recorded.changedPaths];
|
|
154
183
|
} catch (error) {
|
|
155
|
-
|
|
184
|
+
c.dropped.add(fold.key);
|
|
185
|
+
if (fold.id) c.droppedIds.add(fold.id);
|
|
156
186
|
console.error(
|
|
157
|
-
`statewire: optimistic reducer threw for "${
|
|
187
|
+
`statewire: optimistic reducer threw for "${fold.method}"; dropping its optimistic update`,
|
|
158
188
|
error,
|
|
159
189
|
);
|
|
160
190
|
}
|
|
@@ -224,18 +254,8 @@ const flush = (c: Core, scheduled = false) => {
|
|
|
224
254
|
const busy = c.getBusy();
|
|
225
255
|
const busyChanged = busy !== c.notifiedBusy;
|
|
226
256
|
c.notifiedBusy = busy;
|
|
227
|
-
// The
|
|
257
|
+
// The outbox snapshot advances only on the scheduled pass: settles land
|
|
228
258
|
// mid-unmount, where a changed snapshot re-renders the unmounting tree.
|
|
229
|
-
const failed = c.value?.failures?.list() ?? NO_FAILED;
|
|
230
|
-
let failedChanged = false;
|
|
231
|
-
if (failed !== c.notifiedFailed) {
|
|
232
|
-
if (scheduled) {
|
|
233
|
-
c.notifiedFailed = failed;
|
|
234
|
-
failedChanged = true;
|
|
235
|
-
} else {
|
|
236
|
-
schedule(c);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
259
|
const outbox = c.value?.outbox?.list() ?? NO_OUTBOX;
|
|
240
260
|
let outboxChanged = false;
|
|
241
261
|
if (outbox !== c.notifiedOutbox) {
|
|
@@ -246,13 +266,7 @@ const flush = (c: Core, scheduled = false) => {
|
|
|
246
266
|
schedule(c);
|
|
247
267
|
}
|
|
248
268
|
}
|
|
249
|
-
if (
|
|
250
|
-
stateChanged ||
|
|
251
|
-
connectionChanged ||
|
|
252
|
-
busyChanged ||
|
|
253
|
-
failedChanged ||
|
|
254
|
-
outboxChanged
|
|
255
|
-
) {
|
|
269
|
+
if (stateChanged || connectionChanged || busyChanged || outboxChanged) {
|
|
256
270
|
for (const listener of [...c.listeners]) listener();
|
|
257
271
|
}
|
|
258
272
|
} finally {
|
|
@@ -274,6 +288,7 @@ const note = (
|
|
|
274
288
|
params: inv.params as never[],
|
|
275
289
|
...(inv.seq !== undefined && { seq: inv.seq }),
|
|
276
290
|
status,
|
|
291
|
+
...(inv.localMeta !== undefined && { localMeta: inv.localMeta }),
|
|
277
292
|
...extra,
|
|
278
293
|
});
|
|
279
294
|
schedule(c);
|
|
@@ -329,6 +344,9 @@ export const useStatewireCore = (
|
|
|
329
344
|
value: null,
|
|
330
345
|
source: null,
|
|
331
346
|
overlays: [],
|
|
347
|
+
overlayOutbox: NO_OUTBOX,
|
|
348
|
+
dropped: new WeakSet(),
|
|
349
|
+
droppedIds: new Set(),
|
|
332
350
|
updates: [],
|
|
333
351
|
pendingChanged: [],
|
|
334
352
|
canonical: undefined,
|
|
@@ -339,7 +357,6 @@ export const useStatewireCore = (
|
|
|
339
357
|
notifiedConnection: null,
|
|
340
358
|
notifiedError: undefined,
|
|
341
359
|
notifiedBusy: true,
|
|
342
|
-
notifiedFailed: NO_FAILED,
|
|
343
360
|
notifiedOutbox: NO_OUTBOX,
|
|
344
361
|
stale: false,
|
|
345
362
|
everAttached: false,
|
|
@@ -354,7 +371,7 @@ export const useStatewireCore = (
|
|
|
354
371
|
}),
|
|
355
372
|
commandsListener: {
|
|
356
373
|
onStart: (inv) => {
|
|
357
|
-
c.overlays.push({ inv
|
|
374
|
+
c.overlays.push({ inv });
|
|
358
375
|
c.overlaysDirty = true;
|
|
359
376
|
markStale(c);
|
|
360
377
|
note(c, inv, "queued");
|
|
@@ -373,15 +390,16 @@ export const useStatewireCore = (
|
|
|
373
390
|
c.overlaysDirty = true;
|
|
374
391
|
markStale(c);
|
|
375
392
|
},
|
|
376
|
-
onSettle: (inv, outcome) => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
393
|
+
onSettle: (inv, outcome, durable) => {
|
|
394
|
+
if (!durable)
|
|
395
|
+
note(
|
|
396
|
+
c,
|
|
397
|
+
inv,
|
|
398
|
+
"settled",
|
|
399
|
+
outcome.type === "accepted"
|
|
400
|
+
? { result: outcome.payload }
|
|
401
|
+
: { failure: failureOf(outcome.error) },
|
|
402
|
+
);
|
|
385
403
|
flush(c);
|
|
386
404
|
},
|
|
387
405
|
},
|
|
@@ -390,7 +408,6 @@ export const useStatewireCore = (
|
|
|
390
408
|
return c.published;
|
|
391
409
|
},
|
|
392
410
|
getBusy: () => c.value?.isBusy ?? true,
|
|
393
|
-
getFailed: () => c.notifiedFailed,
|
|
394
411
|
getOutbox: () => c.notifiedOutbox,
|
|
395
412
|
rematerialize: () => flush(c),
|
|
396
413
|
commands: createMethodProxy<Statewire.CommandsProxy<any>>(
|