@spfn/core 0.3.0-beta.25 → 0.3.0-beta.27

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.
@@ -0,0 +1,478 @@
1
+ import { createApi } from '@spfn/core/nextjs';
2
+
3
+ // src/state/client.ts
4
+
5
+ // src/state/client-store.ts
6
+ function stableKey(key) {
7
+ if (!key) {
8
+ return "";
9
+ }
10
+ return JSON.stringify(Object.keys(key).sort().map((field) => [field, key[field]]));
11
+ }
12
+ function sameJson(a, b) {
13
+ if (a === b) {
14
+ return true;
15
+ }
16
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null || Array.isArray(a) !== Array.isArray(b)) {
17
+ return false;
18
+ }
19
+ const aKeys = Object.keys(a);
20
+ const bRecord = b;
21
+ return aKeys.length === Object.keys(b).length && aKeys.every((field) => field in bRecord && sameJson(a[field], bRecord[field]));
22
+ }
23
+ function supersedes(current, arrival) {
24
+ if (current?.at === void 0 || arrival.at > current.at) {
25
+ return true;
26
+ }
27
+ if (arrival.at < current.at) {
28
+ return false;
29
+ }
30
+ return !sameJson(current.value, arrival.value) || !sameJson(current.data, arrival.data);
31
+ }
32
+ function entryOf(frame, status) {
33
+ const { at, value, data } = frame;
34
+ return Object.freeze(data === void 0 ? { at, value, status } : { at, value, data, status });
35
+ }
36
+ var StateStore = class {
37
+ entries = /* @__PURE__ */ new Map();
38
+ listeners = /* @__PURE__ */ new Map();
39
+ /**
40
+ * The slots the server stopped serving, with the generation of the
41
+ * connection that said so. Kept apart from the entries: a revoke can
42
+ * arrive before the slot has a value, and an arrival afterwards supplies
43
+ * the value without clearing the mark.
44
+ */
45
+ revoked = /* @__PURE__ */ new Map();
46
+ /** The store slot of `(id, key)`. */
47
+ static slot(id, keyText) {
48
+ return `${id}\0${keyText}`;
49
+ }
50
+ current(slot) {
51
+ return this.entries.get(slot);
52
+ }
53
+ /**
54
+ * Apply an arrival: the value under the ordering rule, the status from the
55
+ * revoked mark. Listeners hear only an applied change.
56
+ */
57
+ apply(slot, frame) {
58
+ const current = this.entries.get(slot);
59
+ const status = this.revoked.has(slot) ? "revoked" : "ready";
60
+ if (supersedes(current, frame)) {
61
+ this.replace(slot, entryOf(frame, status));
62
+ } else if (current.status !== status) {
63
+ this.replace(slot, entryOf(current, status));
64
+ }
65
+ }
66
+ /** Mark `slot` revoked by the connection of `generation`; its value, if any, is kept. */
67
+ revoke(slot, generation) {
68
+ this.revoked.set(slot, generation);
69
+ const entry = this.entries.get(slot);
70
+ if (entry?.status !== "revoked") {
71
+ this.replace(slot, Object.freeze(entry ? { ...entry, status: "revoked" } : { status: "revoked" }));
72
+ }
73
+ }
74
+ /** The generation of the connection that revoked `slot`, or `undefined` while it is served. */
75
+ revokedBy(slot) {
76
+ return this.revoked.get(slot);
77
+ }
78
+ /** Clear the revoked mark; the next arrival for `slot` is `'ready'`. */
79
+ allow(slot) {
80
+ this.revoked.delete(slot);
81
+ }
82
+ listen(slot, listener) {
83
+ const set = this.listeners.get(slot) ?? /* @__PURE__ */ new Set();
84
+ set.add(listener);
85
+ this.listeners.set(slot, set);
86
+ }
87
+ unlisten(slot, listener) {
88
+ const set = this.listeners.get(slot);
89
+ if (listener) {
90
+ set?.delete(listener);
91
+ }
92
+ if (!listener || set?.size === 0) {
93
+ this.listeners.delete(slot);
94
+ }
95
+ }
96
+ clearListeners() {
97
+ this.listeners.clear();
98
+ }
99
+ /** Replace the entry and notify; a throwing listener is logged and does not stop the others. */
100
+ replace(slot, entry) {
101
+ this.entries.set(slot, entry);
102
+ for (const listener of [...this.listeners.get(slot) ?? []]) {
103
+ notify(slot, listener);
104
+ }
105
+ }
106
+ };
107
+ function notify(slot, listener) {
108
+ try {
109
+ listener();
110
+ } catch (error) {
111
+ console.warn(`[state] A listener of state "${slot.split("\0")[0]}" threw:`, error);
112
+ }
113
+ }
114
+
115
+ // src/state/client.ts
116
+ function routeName(method, id) {
117
+ return `${method}${id[0].toUpperCase()}${id.slice(1)}State`;
118
+ }
119
+ function carries(query, key) {
120
+ return Object.entries(key).every(([field, value]) => query[field] === value);
121
+ }
122
+ function sortedFields(fields) {
123
+ return [...fields].sort().join(", ");
124
+ }
125
+ function createStateClient(config) {
126
+ return new BrowserStateClient(config);
127
+ }
128
+ var BrowserStateClient = class {
129
+ constructor(config) {
130
+ this.config = config;
131
+ this.api = createApi(config.api);
132
+ this.reportError = config.onError ?? ((error) => console.error("[state] A refresh failed:", error));
133
+ }
134
+ store = new StateStore();
135
+ api;
136
+ watched = /* @__PURE__ */ new Map();
137
+ reportError;
138
+ manifest;
139
+ manifestRead;
140
+ stream;
141
+ syncQueued = false;
142
+ /** The lifetime whose subscriptions wait on the first snapshot — its read in flight, or its retry timer set. */
143
+ waitingLifetime;
144
+ retryTimer;
145
+ /** Bumped by `close()`: a snapshot started before it is discarded. */
146
+ lifetime = 0;
147
+ /** Bumped on every connection open; tags the snapshot read for it. */
148
+ generation = 0;
149
+ /** Bumped on every automatic read and every grant, to tell which came first. */
150
+ clock = 0;
151
+ /** The clock at the latest grant of each slot. */
152
+ grants = /* @__PURE__ */ new Map();
153
+ async get(id, key) {
154
+ await this.requireManifest();
155
+ const frame = await this.api[routeName("get", id)].call(key ? { query: key } : {});
156
+ this.store.apply(this.slot(id, key), frame);
157
+ return frame;
158
+ }
159
+ async write(id, input, key) {
160
+ if (!(await this.requireManifest())[id]?.write) {
161
+ throw new Error(`State client: state "${id}" declares no .write(), so it has no write route.`);
162
+ }
163
+ const frame = await this.api[routeName("put", id)].call({ body: input, ...key ? { query: key } : {} });
164
+ this.store.apply(this.slot(id, key), frame);
165
+ return frame;
166
+ }
167
+ snapshot(query = this.streamQuery()) {
168
+ return this.readSnapshot(query, this.tag());
169
+ }
170
+ subscribe(id, ...args) {
171
+ const listener = args[args.length - 1];
172
+ const key = args.length === 2 ? args[0] : void 0;
173
+ const refusal = this.manifest && this.refusal(id, key);
174
+ if (refusal) {
175
+ this.reportError(refusal);
176
+ return () => void 0;
177
+ }
178
+ const notify2 = () => listener();
179
+ const slot = this.slot(id, key);
180
+ this.store.listen(slot, notify2);
181
+ this.watch(slot, id, key, 1);
182
+ let live = true;
183
+ return () => {
184
+ if (live) {
185
+ live = false;
186
+ this.store.unlisten(slot, notify2);
187
+ this.watch(slot, id, key, -1);
188
+ }
189
+ };
190
+ }
191
+ current(id, key) {
192
+ return this.store.current(this.slot(id, key));
193
+ }
194
+ close() {
195
+ this.lifetime++;
196
+ clearTimeout(this.retryTimer);
197
+ this.retryTimer = void 0;
198
+ this.closeStream();
199
+ this.watched.clear();
200
+ this.store.clearListeners();
201
+ }
202
+ slot(id, key) {
203
+ return StateStore.slot(id, stableKey(key));
204
+ }
205
+ tag() {
206
+ return { generation: this.generation, tick: ++this.clock };
207
+ }
208
+ /** The key of state `id` the query names; `undefined` for a keyless state. */
209
+ keyIn(id, query) {
210
+ const fields = this.manifest[id].key;
211
+ return fields.length ? Object.fromEntries(fields.map((field) => [field, query[field]])) : void 0;
212
+ }
213
+ /** Why the manifest refuses `subscribe(id, key)` — an unknown id, or key fields not the state's. */
214
+ refusal(id, key) {
215
+ const state = this.manifest[id];
216
+ if (!state) {
217
+ return new Error(`State client: the server's manifest has no state "${id}".`);
218
+ }
219
+ const expected = sortedFields(state.key);
220
+ const given = sortedFields(Object.keys(key ?? {}));
221
+ return expected === given ? void 0 : new Error(`State client: state "${id}" takes the key fields [${expected}], given [${given}].`);
222
+ }
223
+ /** The first snapshot, read once however many callers wait for it. */
224
+ ensureManifest() {
225
+ if (this.manifest) {
226
+ return Promise.resolve();
227
+ }
228
+ this.manifestRead ??= this.snapshot().then(() => void 0).finally(() => {
229
+ this.manifestRead = void 0;
230
+ });
231
+ return this.manifestRead;
232
+ }
233
+ /** The manifest for a call the app made; one whose wait outlived `close()` is refused. */
234
+ async requireManifest() {
235
+ await this.ensureManifest();
236
+ if (!this.manifest) {
237
+ throw new Error("State client: the client was closed while the first snapshot was in flight.");
238
+ }
239
+ return this.manifest;
240
+ }
241
+ /**
242
+ * Read the snapshot and apply it: the manifest first — `keyIn` and the
243
+ * key checks need it — then each state present, then each watched state
244
+ * it omits. A snapshot that resolves after `close()` is discarded.
245
+ */
246
+ async readSnapshot(query, tag) {
247
+ const lifetime = this.lifetime;
248
+ const snapshot = await this.api.getStates.call({ query });
249
+ if (lifetime === this.lifetime) {
250
+ this.installManifest(snapshot.manifest);
251
+ this.applyPresent(snapshot, query, tag);
252
+ this.applyOmitted(snapshot, query, tag);
253
+ }
254
+ return snapshot;
255
+ }
256
+ /**
257
+ * The manifest is static; the first one stays. Subscriptions made before
258
+ * it are checked now, and the stream opens for those it accepts —
259
+ * whichever call (`subscribe`, `get`, `write`, `snapshot`) brought it.
260
+ */
261
+ installManifest(manifest) {
262
+ if (this.manifest) {
263
+ return;
264
+ }
265
+ this.manifest = manifest;
266
+ for (const [slot, watch] of this.watched) {
267
+ const refusal = this.refusal(watch.id, watch.key);
268
+ if (refusal) {
269
+ this.watched.delete(slot);
270
+ this.store.unlisten(slot);
271
+ this.reportError(refusal);
272
+ }
273
+ }
274
+ if (this.watched.size > 0) {
275
+ this.queueSync();
276
+ }
277
+ }
278
+ /**
279
+ * An arrival from a read started for the connection of `tag`: a
280
+ * connection newer than the one that revoked the slot lifts the mark, an
281
+ * older or the same connection leaves `'revoked'`. The lift is by
282
+ * generation alone — a frame or a write answer of the new connection may
283
+ * land first with a newer `at` — and the ordering rule decides only the value.
284
+ */
285
+ serve(slot, frame, tag) {
286
+ if ((this.store.revokedBy(slot) ?? Infinity) < tag.generation) {
287
+ this.store.allow(slot);
288
+ }
289
+ this.store.apply(slot, frame);
290
+ }
291
+ /** A state present is served, under the generation rule of `serve`. */
292
+ applyPresent(snapshot, query, tag) {
293
+ for (const [id, result] of Object.entries(snapshot.states)) {
294
+ this.serve(this.slot(id, this.keyIn(id, query)), { at: snapshot.at, ...result }, tag);
295
+ }
296
+ }
297
+ /**
298
+ * A watched state whose key the query carried (or a keyless one) and that
299
+ * the snapshot omits is not served to this subject: `'revoked'`. Not when
300
+ * a newer connection exists, nor when a grant came after the read started.
301
+ */
302
+ applyOmitted(snapshot, query, tag) {
303
+ if (tag.generation !== this.generation) {
304
+ return;
305
+ }
306
+ for (const [slot, watch] of this.watched) {
307
+ const asked = !watch.key || carries(query, watch.key);
308
+ const grantedSince = (this.grants.get(slot) ?? 0) > tag.tick;
309
+ if (asked && !(watch.id in snapshot.states) && !grantedSince) {
310
+ this.store.revoke(slot, tag.generation);
311
+ }
312
+ }
313
+ }
314
+ watch(slot, id, key, delta) {
315
+ const count = (this.watched.get(slot)?.count ?? 0) + delta;
316
+ if (count > 0) {
317
+ this.watched.set(slot, { id, key, count });
318
+ } else {
319
+ this.watched.delete(slot);
320
+ }
321
+ this.queueSync();
322
+ }
323
+ /**
324
+ * One stream for the client, its query the union of the subscribed keys.
325
+ * Batched to the end of the task, so components mounting together (or a
326
+ * StrictMode unmount-remount) open the stream once.
327
+ */
328
+ queueSync() {
329
+ if (!this.syncQueued) {
330
+ this.syncQueued = true;
331
+ queueMicrotask(() => {
332
+ this.syncQueued = false;
333
+ this.sync();
334
+ });
335
+ }
336
+ }
337
+ /**
338
+ * Until the manifest is known, subscriptions wait for the first snapshot;
339
+ * the stream opens after it, never before — its `events=` comes from it.
340
+ */
341
+ sync() {
342
+ if (this.watched.size === 0) {
343
+ this.closeStream();
344
+ return;
345
+ }
346
+ if (!this.manifest) {
347
+ this.awaitManifest();
348
+ return;
349
+ }
350
+ const query = this.streamQuery();
351
+ if (this.stream?.text !== stableKey(query)) {
352
+ this.openStream(query);
353
+ }
354
+ }
355
+ /**
356
+ * One waiter per lifetime on the first snapshot. A failure goes to
357
+ * `onError`, and the read is retried after `retryDelay` — the stream that
358
+ * would reconnect on its own does not exist yet. A retry stops on
359
+ * `close()` or when nothing is watched (`sync` closes instead).
360
+ */
361
+ awaitManifest() {
362
+ const lifetime = this.lifetime;
363
+ if (this.waitingLifetime === lifetime) {
364
+ return;
365
+ }
366
+ this.waitingLifetime = lifetime;
367
+ this.ensureManifest().then(
368
+ () => lifetime === this.lifetime && this.resync(),
369
+ (error) => {
370
+ this.reportError(error);
371
+ if (lifetime === this.lifetime) {
372
+ this.retryLater();
373
+ }
374
+ }
375
+ );
376
+ }
377
+ /** The wait is over; `sync` opens the stream, or waits again when a closed lifetime's read was discarded. */
378
+ resync() {
379
+ this.waitingLifetime = void 0;
380
+ this.sync();
381
+ }
382
+ /** Read the first snapshot again after `retryDelay`, as the SSE client reconnects a dropped stream. */
383
+ retryLater() {
384
+ this.retryTimer = setTimeout(() => {
385
+ this.retryTimer = void 0;
386
+ this.resync();
387
+ }, this.config.retryDelay ?? 3e3);
388
+ this.retryTimer.unref?.();
389
+ }
390
+ /**
391
+ * Every subscribed key's fields; one connection carries one value per
392
+ * field, so on a conflict the latest subscribed key wins. The keys it
393
+ * shadows are read once when the stream opens and get no frames.
394
+ */
395
+ streamQuery() {
396
+ return Object.assign({}, ...[...this.watched.values()].map((watch) => watch.key ?? {}));
397
+ }
398
+ /**
399
+ * Open (or reopen) the stream. `events` is every state of the manifest, in
400
+ * its order, never only the subscribed ones: a state subscribed later would
401
+ * otherwise be unrequested, and the server's `grant` cannot activate it.
402
+ */
403
+ openStream(query) {
404
+ this.closeStream();
405
+ const ids = Object.keys(this.manifest);
406
+ const stream = { query, text: stableKey(query), generation: 0, close: () => void 0 };
407
+ stream.close = this.config.sse.subscribe({
408
+ events: ids.map((id) => `state.${id}.changed`),
409
+ handlers: Object.fromEntries(ids.map((id) => [`state.${id}.changed`, (frame) => this.onFrame(id, frame)])),
410
+ query,
411
+ // Every open — the first, a reconnect, a reopen on a key change — re-reads the
412
+ // snapshot. A frame sent while no stream was open is lost, whether its key
413
+ // changed or not; the snapshot read after the open covers that gap.
414
+ onOpen: () => {
415
+ stream.generation = ++this.generation;
416
+ this.refresh(query);
417
+ },
418
+ onSubscriptionChange: (change) => this.onSubscriptionChange(change, stream)
419
+ });
420
+ this.stream = stream;
421
+ }
422
+ closeStream() {
423
+ this.stream?.close();
424
+ this.stream = void 0;
425
+ }
426
+ /** The snapshot for the stream's query, and one read for each subscribed key the query shadows. */
427
+ refresh(query) {
428
+ this.readSnapshot(query, this.tag()).catch(this.reportError);
429
+ for (const watch of this.watched.values()) {
430
+ if (watch.key && !carries(query, watch.key)) {
431
+ this.readShadowed(watch.id, watch.key, this.tag()).catch(this.reportError);
432
+ }
433
+ }
434
+ }
435
+ /**
436
+ * A shadowed key gets no frames, no grant and no snapshot entry; the
437
+ * server answering its own read is the evidence of access instead.
438
+ */
439
+ async readShadowed(id, key, tag) {
440
+ const frame = await this.api[routeName("get", id)].call({ query: key });
441
+ this.serve(this.slot(id, key), frame, tag);
442
+ }
443
+ /** A frame for a key nobody here subscribed is dropped — the server's filter is the first guard, this the second. */
444
+ onFrame(id, frame) {
445
+ const slot = this.slot(id, frame.key);
446
+ if (this.watched.has(slot)) {
447
+ this.store.apply(slot, frame);
448
+ }
449
+ }
450
+ /**
451
+ * A revoke or a grant concerns the key the connection carries — the
452
+ * server scopes both by the connect query — and so does the read after a
453
+ * grant. Revoked: the entry keeps its value, marked `'revoked'`, until a
454
+ * grant. Granted: the mark is cleared and the key read once; the server
455
+ * sends a frame too, in either order, and both arrive `'ready'`.
456
+ */
457
+ onSubscriptionChange(change, stream) {
458
+ const id = /^state\.(.+)\.changed$/.exec(change.event)?.[1];
459
+ if (id === void 0 || !this.manifest[id]) {
460
+ return;
461
+ }
462
+ const key = this.keyIn(id, stream.query);
463
+ const slot = this.slot(id, key);
464
+ if (change.status === "revoked") {
465
+ this.store.revoke(slot, stream.generation);
466
+ return;
467
+ }
468
+ this.grants.set(slot, ++this.clock);
469
+ this.store.allow(slot);
470
+ if (this.watched.has(slot)) {
471
+ this.get(id, key).catch(this.reportError);
472
+ }
473
+ }
474
+ };
475
+
476
+ export { createStateClient };
477
+ //# sourceMappingURL=client.js.map
478
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/state/client-store.ts","../../src/state/client.ts"],"names":["notify"],"mappings":";;;;;AAgBO,SAAS,UAAU,GAAA,EAC1B;AACI,EAAA,IAAI,CAAC,GAAA,EACL;AACI,IAAA,OAAO,EAAA;AAAA,EACX;AAEA,EAAA,OAAO,KAAK,SAAA,CAAU,MAAA,CAAO,IAAA,CAAK,GAAG,EAAE,IAAA,EAAK,CAAE,GAAA,CAAI,CAAA,KAAA,KAAS,CAAC,KAAA,EAAO,GAAA,CAAI,KAAK,CAAC,CAAC,CAAC,CAAA;AACnF;AASA,SAAS,QAAA,CAAS,GAAY,CAAA,EAC9B;AACI,EAAA,IAAI,MAAM,CAAA,EACV;AACI,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,CAAA,KAAM,YAAY,CAAA,KAAM,IAAA,IAAQ,CAAA,KAAM,IAAA,IAAQ,MAAM,OAAA,CAAQ,CAAC,MAAM,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EACtH;AACI,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA;AAC3B,EAAA,MAAM,OAAA,GAAU,CAAA;AAEhB,EAAA,OAAO,MAAM,MAAA,KAAW,MAAA,CAAO,KAAK,CAAC,CAAA,CAAE,UAChC,KAAA,CAAM,KAAA,CAAM,WAAS,KAAA,IAAS,OAAA,IAAW,SAAU,CAAA,CAA8B,KAAK,GAAG,OAAA,CAAQ,KAAK,CAAC,CAAC,CAAA;AACnH;AAQA,SAAS,UAAA,CAAW,SAAiC,OAAA,EACrD;AACI,EAAA,IAAI,SAAS,EAAA,KAAO,MAAA,IAAa,OAAA,CAAQ,EAAA,GAAK,QAAQ,EAAA,EACtD;AACI,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,IAAI,OAAA,CAAQ,EAAA,GAAK,OAAA,CAAQ,EAAA,EACzB;AACI,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,OAAO,CAAC,QAAA,CAAS,OAAA,CAAQ,KAAA,EAAO,OAAA,CAAQ,KAAK,CAAA,IAAK,CAAC,QAAA,CAAS,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,IAAI,CAAA;AAC1F;AAEA,SAAS,OAAA,CAAQ,OAAmB,MAAA,EACpC;AACI,EAAA,MAAM,EAAE,EAAA,EAAI,KAAA,EAAO,IAAA,EAAK,GAAI,KAAA;AAE5B,EAAA,OAAO,MAAA,CAAO,MAAA,CAAO,IAAA,KAAS,MAAA,GAAY,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAO,GAAI,EAAE,EAAA,EAAI,KAAA,EAAO,IAAA,EAAM,QAAQ,CAAA;AACjG;AAIO,IAAM,aAAN,MACP;AAAA,EACqB,OAAA,uBAAc,GAAA,EAAwB;AAAA,EACtC,SAAA,uBAAgB,GAAA,EAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQhD,OAAA,uBAAc,GAAA,EAAoB;AAAA;AAAA,EAGnD,OAAO,IAAA,CAAK,EAAA,EAAY,OAAA,EACxB;AACI,IAAA,OAAO,CAAA,EAAG,EAAE,CAAA,EAAA,EAAS,OAAO,CAAA,CAAA;AAAA,EAChC;AAAA,EAEA,QAAQ,IAAA,EACR;AACI,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,KAAA,CAAM,MAAc,KAAA,EACpB;AACI,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AACrC,IAAA,MAAM,SAAsB,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,IAAI,SAAA,GAAY,OAAA;AAEjE,IAAA,IAAI,UAAA,CAAW,OAAA,EAAS,KAAK,CAAA,EAC7B;AACI,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,KAAA,EAAO,MAAM,CAAC,CAAA;AAAA,IAC7C,CAAA,MAAA,IACS,OAAA,CAAS,MAAA,KAAW,MAAA,EAC7B;AAEI,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,OAAA,EAAuB,MAAM,CAAC,CAAA;AAAA,IAC7D;AAAA,EACJ;AAAA;AAAA,EAGA,MAAA,CAAO,MAAc,UAAA,EACrB;AACI,IAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAA,EAAM,UAAU,CAAA;AACjC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AAEnC,IAAA,IAAI,KAAA,EAAO,WAAW,SAAA,EACtB;AACI,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,QAAQ,EAAE,GAAG,KAAA,EAAO,MAAA,EAAQ,WAAmB,GAAI,EAAE,MAAA,EAAQ,SAAA,EAAoB,CAAC,CAAA;AAAA,IACvH;AAAA,EACJ;AAAA;AAAA,EAGA,UAAU,IAAA,EACV;AACI,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AAAA,EAChC;AAAA;AAAA,EAGA,MAAM,IAAA,EACN;AACI,IAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,IAAI,CAAA;AAAA,EAC5B;AAAA,EAEA,MAAA,CAAO,MAAc,QAAA,EACrB;AACI,IAAA,MAAM,MAAM,IAAA,CAAK,SAAA,CAAU,IAAI,IAAI,CAAA,wBAAS,GAAA,EAAI;AAChD,IAAA,GAAA,CAAI,IAAI,QAAQ,CAAA;AAChB,IAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,IAAA,EAAM,GAAG,CAAA;AAAA,EAChC;AAAA,EAEA,QAAA,CAAS,MAAc,QAAA,EACvB;AACI,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAEnC,IAAA,IAAI,QAAA,EACJ;AACI,MAAA,GAAA,EAAK,OAAO,QAAQ,CAAA;AAAA,IACxB;AAEA,IAAA,IAAI,CAAC,QAAA,IAAY,GAAA,EAAK,IAAA,KAAS,CAAA,EAC/B;AACI,MAAA,IAAA,CAAK,SAAA,CAAU,OAAO,IAAI,CAAA;AAAA,IAC9B;AAAA,EACJ;AAAA,EAEA,cAAA,GACA;AACI,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AAAA,EACzB;AAAA;AAAA,EAGQ,OAAA,CAAQ,MAAc,KAAA,EAC9B;AACI,IAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAA,EAAM,KAAK,CAAA;AAE5B,IAAA,KAAA,MAAW,QAAA,IAAY,CAAC,GAAG,IAAA,CAAK,SAAA,CAAU,IAAI,IAAI,CAAA,IAAK,EAAE,CAAA,EACzD;AACI,MAAA,MAAA,CAAO,MAAM,QAAQ,CAAA;AAAA,IACzB;AAAA,EACJ;AACJ,CAAA;AAEA,SAAS,MAAA,CAAO,MAAc,QAAA,EAC9B;AACI,EAAA,IACA;AACI,IAAA,QAAA,EAAS;AAAA,EACb,SACO,KAAA,EACP;AAEI,IAAA,OAAA,CAAQ,IAAA,CAAK,gCAAgC,IAAA,CAAK,KAAA,CAAM,IAAQ,CAAA,CAAE,CAAC,CAAC,CAAA,QAAA,CAAA,EAAY,KAAK,CAAA;AAAA,EACzF;AACJ;;;AC7GA,SAAS,SAAA,CAAU,QAAuB,EAAA,EAC1C;AACI,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,EAAG,EAAA,CAAG,CAAC,CAAA,CAAE,WAAA,EAAa,CAAA,EAAG,EAAA,CAAG,KAAA,CAAM,CAAC,CAAC,CAAA,KAAA,CAAA;AACxD;AAGA,SAAS,OAAA,CAAQ,OAAuB,GAAA,EACxC;AACI,EAAA,OAAO,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,KAAA,EAAO,KAAK,CAAA,KAAM,KAAA,CAAM,KAAK,MAAM,KAAK,CAAA;AAC/E;AAEA,SAAS,aAAa,MAAA,EACtB;AACI,EAAA,OAAO,CAAC,GAAG,MAAM,EAAE,IAAA,EAAK,CAAE,KAAK,IAAI,CAAA;AACvC;AAQO,SAAS,kBAAkD,MAAA,EAClE;AACI,EAAA,OAAO,IAAI,mBAAmB,MAAM,CAAA;AACxC;AAEA,IAAM,qBAAN,MACA;AAAA,EA0BI,YAA6B,MAAA,EAC7B;AAD6B,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAEzB,IAAA,IAAA,CAAK,GAAA,GAAM,SAAA,CAAe,MAAA,CAAO,GAAG,CAAA;AACpC,IAAA,IAAA,CAAK,cAAc,MAAA,CAAO,OAAA,KAAY,WAAS,OAAA,CAAQ,KAAA,CAAM,6BAA6B,KAAK,CAAA,CAAA;AAAA,EACnG;AAAA,EA7BiB,KAAA,GAAQ,IAAI,UAAA,EAAW;AAAA,EACvB,GAAA;AAAA,EACA,OAAA,uBAAc,GAAA,EAAmB;AAAA,EACjC,WAAA;AAAA,EACT,QAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA,UAAA,GAAa,KAAA;AAAA;AAAA,EAGb,eAAA;AAAA,EACA,UAAA;AAAA;AAAA,EAGA,QAAA,GAAW,CAAA;AAAA;AAAA,EAGX,UAAA,GAAa,CAAA;AAAA;AAAA,EAGb,KAAA,GAAQ,CAAA;AAAA;AAAA,EAGC,MAAA,uBAAa,GAAA,EAAoB;AAAA,EAQlD,MAAM,GAAA,CAAI,EAAA,EAAY,GAAA,EACtB;AACI,IAAA,MAAM,KAAK,eAAA,EAAgB;AAC3B,IAAA,MAAM,QAAQ,MAAM,IAAA,CAAK,GAAA,CAAI,SAAA,CAAU,OAAO,EAAE,CAAC,CAAA,CAAE,IAAA,CAAK,MAAM,EAAE,KAAA,EAAO,GAAA,EAAI,GAAI,EAAE,CAAA;AACjF,IAAA,IAAA,CAAK,MAAM,KAAA,CAAM,IAAA,CAAK,KAAK,EAAA,EAAI,GAAG,GAAG,KAAK,CAAA;AAE1C,IAAA,OAAO,KAAA;AAAA,EACX;AAAA,EAEA,MAAM,KAAA,CAAM,EAAA,EAAY,KAAA,EAAgB,GAAA,EACxC;AACI,IAAA,IAAI,EAAE,MAAM,IAAA,CAAK,iBAAgB,EAAG,EAAE,GAAG,KAAA,EACzC;AACI,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,qBAAA,EAAwB,EAAE,CAAA,iDAAA,CAAmD,CAAA;AAAA,IACjG;AAEA,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,GAAA,CAAI,UAAU,KAAA,EAAO,EAAE,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,IAAA,EAAM,KAAA,EAAO,GAAI,GAAA,GAAM,EAAE,OAAO,GAAA,EAAI,GAAI,EAAC,EAAI,CAAA;AACvG,IAAA,IAAA,CAAK,MAAM,KAAA,CAAM,IAAA,CAAK,KAAK,EAAA,EAAI,GAAG,GAAG,KAAK,CAAA;AAE1C,IAAA,OAAO,KAAA;AAAA,EACX;AAAA,EAEA,QAAA,CAAS,KAAA,GAAwB,IAAA,CAAK,WAAA,EAAY,EAClD;AACI,IAAA,OAAO,IAAA,CAAK,YAAA,CAAa,KAAA,EAAO,IAAA,CAAK,KAAK,CAAA;AAAA,EAC9C;AAAA,EAEA,SAAA,CAAU,OAAe,IAAA,EACzB;AACI,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA;AACrC,IAAA,MAAM,MAAM,IAAA,CAAK,MAAA,KAAW,CAAA,GAAI,IAAA,CAAK,CAAC,CAAA,GAAsB,MAAA;AAC5D,IAAA,MAAM,UAAU,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,OAAA,CAAQ,IAAI,GAAG,CAAA;AAErD,IAAA,IAAI,OAAA,EACJ;AACI,MAAA,IAAA,CAAK,YAAY,OAAO,CAAA;AAExB,MAAA,OAAO,MAAM,MAAA;AAAA,IACjB;AAGA,IAAA,MAAMA,OAAAA,GAAS,MAAM,QAAA,EAAS;AAC9B,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,EAAA,EAAI,GAAG,CAAA;AAC9B,IAAA,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAA,EAAMA,OAAM,CAAA;AAC9B,IAAA,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,EAAA,EAAI,GAAA,EAAK,CAAC,CAAA;AAC3B,IAAA,IAAI,IAAA,GAAO,IAAA;AAEX,IAAA,OAAO,MACP;AACI,MAAA,IAAI,IAAA,EACJ;AACI,QAAA,IAAA,GAAO,KAAA;AACP,QAAA,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS,IAAA,EAAMA,OAAM,CAAA;AAChC,QAAA,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,EAAA,EAAI,GAAA,EAAK,EAAE,CAAA;AAAA,MAChC;AAAA,IACJ,CAAA;AAAA,EACJ;AAAA,EAEA,OAAA,CAAQ,IAAY,GAAA,EACpB;AACI,IAAA,OAAO,KAAK,KAAA,CAAM,OAAA,CAAQ,KAAK,IAAA,CAAK,EAAA,EAAI,GAAG,CAAC,CAAA;AAAA,EAChD;AAAA,EAEA,KAAA,GACA;AACI,IAAA,IAAA,CAAK,QAAA,EAAA;AACL,IAAA,YAAA,CAAa,KAAK,UAAU,CAAA;AAC5B,IAAA,IAAA,CAAK,UAAA,GAAa,MAAA;AAClB,IAAA,IAAA,CAAK,WAAA,EAAY;AACjB,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AACnB,IAAA,IAAA,CAAK,MAAM,cAAA,EAAe;AAAA,EAC9B;AAAA,EAEQ,IAAA,CAAK,IAAY,GAAA,EACzB;AACI,IAAA,OAAO,UAAA,CAAW,IAAA,CAAK,EAAA,EAAI,SAAA,CAAU,GAAG,CAAC,CAAA;AAAA,EAC7C;AAAA,EAEQ,GAAA,GACR;AACI,IAAA,OAAO,EAAE,UAAA,EAAY,IAAA,CAAK,YAAY,IAAA,EAAM,EAAE,KAAK,KAAA,EAAM;AAAA,EAC7D;AAAA;AAAA,EAGQ,KAAA,CAAM,IAAY,KAAA,EAC1B;AACI,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,QAAA,CAAU,EAAE,CAAA,CAAE,GAAA;AAElC,IAAA,OAAO,MAAA,CAAO,MAAA,GAAS,MAAA,CAAO,WAAA,CAAY,OAAO,GAAA,CAAI,CAAA,KAAA,KAAS,CAAC,KAAA,EAAO,KAAA,CAAM,KAAK,CAAC,CAAC,CAAC,CAAA,GAAI,MAAA;AAAA,EAC5F;AAAA;AAAA,EAGQ,OAAA,CAAQ,IAAY,GAAA,EAC5B;AACI,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,QAAA,CAAU,EAAE,CAAA;AAE/B,IAAA,IAAI,CAAC,KAAA,EACL;AACI,MAAA,OAAO,IAAI,KAAA,CAAM,CAAA,kDAAA,EAAqD,EAAE,CAAA,EAAA,CAAI,CAAA;AAAA,IAChF;AAEA,IAAA,MAAM,QAAA,GAAW,YAAA,CAAa,KAAA,CAAM,GAAG,CAAA;AACvC,IAAA,MAAM,QAAQ,YAAA,CAAa,MAAA,CAAO,KAAK,GAAA,IAAO,EAAE,CAAC,CAAA;AAEjD,IAAA,OAAO,QAAA,KAAa,KAAA,GACd,MAAA,GACA,IAAI,KAAA,CAAM,CAAA,qBAAA,EAAwB,EAAE,CAAA,wBAAA,EAA2B,QAAQ,CAAA,UAAA,EAAa,KAAK,CAAA,EAAA,CAAI,CAAA;AAAA,EACvG;AAAA;AAAA,EAGQ,cAAA,GACR;AACI,IAAA,IAAI,KAAK,QAAA,EACT;AACI,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IAC3B;AAEA,IAAA,IAAA,CAAK,YAAA,KAAiB,KAAK,QAAA,EAAS,CAC/B,KAAK,MAAM,MAAS,CAAA,CACpB,OAAA,CAAQ,MACT;AACI,MAAA,IAAA,CAAK,YAAA,GAAe,MAAA;AAAA,IACxB,CAAC,CAAA;AAEL,IAAA,OAAO,IAAA,CAAK,YAAA;AAAA,EAChB;AAAA;AAAA,EAGA,MAAc,eAAA,GACd;AACI,IAAA,MAAM,KAAK,cAAA,EAAe;AAE1B,IAAA,IAAI,CAAC,KAAK,QAAA,EACV;AACI,MAAA,MAAM,IAAI,MAAM,6EAA6E,CAAA;AAAA,IACjG;AAEA,IAAA,OAAO,IAAA,CAAK,QAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,YAAA,CAAa,KAAA,EAAuB,GAAA,EAClD;AACI,IAAA,MAAM,WAAW,IAAA,CAAK,QAAA;AACtB,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,GAAA,CAAI,UAAU,IAAA,CAAK,EAAE,OAAO,CAAA;AAExD,IAAA,IAAI,QAAA,KAAa,KAAK,QAAA,EACtB;AACI,MAAA,IAAA,CAAK,eAAA,CAAgB,SAAS,QAAQ,CAAA;AACtC,MAAA,IAAA,CAAK,YAAA,CAAa,QAAA,EAAU,KAAA,EAAO,GAAG,CAAA;AACtC,MAAA,IAAA,CAAK,YAAA,CAAa,QAAA,EAAU,KAAA,EAAO,GAAG,CAAA;AAAA,IAC1C;AAEA,IAAA,OAAO,QAAA;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,gBAAgB,QAAA,EACxB;AACI,IAAA,IAAI,KAAK,QAAA,EACT;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAEhB,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,KAAK,CAAA,IAAK,KAAK,OAAA,EACjC;AACI,MAAA,MAAM,UAAU,IAAA,CAAK,OAAA,CAAQ,KAAA,CAAM,EAAA,EAAI,MAAM,GAAG,CAAA;AAEhD,MAAA,IAAI,OAAA,EACJ;AACI,QAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,IAAI,CAAA;AACxB,QAAA,IAAA,CAAK,KAAA,CAAM,SAAS,IAAI,CAAA;AACxB,QAAA,IAAA,CAAK,YAAY,OAAO,CAAA;AAAA,MAC5B;AAAA,IACJ;AAEA,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,IAAA,GAAO,CAAA,EACxB;AACI,MAAA,IAAA,CAAK,SAAA,EAAU;AAAA,IACnB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,KAAA,CAAM,IAAA,EAAc,KAAA,EAAmB,GAAA,EAC/C;AACI,IAAA,IAAA,CAAK,KAAK,KAAA,CAAM,SAAA,CAAU,IAAI,CAAA,IAAK,QAAA,IAAY,IAAI,UAAA,EACnD;AACI,MAAA,IAAA,CAAK,KAAA,CAAM,MAAM,IAAI,CAAA;AAAA,IACzB;AAEA,IAAA,IAAA,CAAK,KAAA,CAAM,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA;AAAA,EAChC;AAAA;AAAA,EAGQ,YAAA,CAAa,QAAA,EAAoB,KAAA,EAAuB,GAAA,EAChE;AACI,IAAA,KAAA,MAAW,CAAC,IAAI,MAAM,CAAA,IAAK,OAAO,OAAA,CAAQ,QAAA,CAAS,MAAM,CAAA,EACzD;AACI,MAAA,IAAA,CAAK,MAAM,IAAA,CAAK,IAAA,CAAK,EAAA,EAAI,IAAA,CAAK,MAAM,EAAA,EAAI,KAAK,CAAC,CAAA,EAAG,EAAE,EAAA,EAAI,QAAA,CAAS,IAAI,GAAG,MAAA,IAAU,GAAG,CAAA;AAAA,IACxF;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,YAAA,CAAa,QAAA,EAAoB,KAAA,EAAuB,GAAA,EAChE;AACI,IAAA,IAAI,GAAA,CAAI,UAAA,KAAe,IAAA,CAAK,UAAA,EAC5B;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,KAAK,CAAA,IAAK,KAAK,OAAA,EACjC;AACI,MAAA,MAAM,QAAQ,CAAC,KAAA,CAAM,OAAO,OAAA,CAAQ,KAAA,EAAO,MAAM,GAAG,CAAA;AACpD,MAAA,MAAM,gBAAgB,IAAA,CAAK,MAAA,CAAO,IAAI,IAAI,CAAA,IAAK,KAAK,GAAA,CAAI,IAAA;AAExD,MAAA,IAAI,SAAS,EAAE,KAAA,CAAM,MAAM,QAAA,CAAS,MAAA,CAAA,IAAW,CAAC,YAAA,EAChD;AACI,QAAA,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAA,EAAM,GAAA,CAAI,UAAU,CAAA;AAAA,MAC1C;AAAA,IACJ;AAAA,EACJ;AAAA,EAEQ,KAAA,CAAM,IAAA,EAAc,EAAA,EAAY,GAAA,EAAiC,KAAA,EACzE;AACI,IAAA,MAAM,SAAS,IAAA,CAAK,OAAA,CAAQ,IAAI,IAAI,CAAA,EAAG,SAAS,CAAA,IAAK,KAAA;AAErD,IAAA,IAAI,QAAQ,CAAA,EACZ;AACI,MAAA,IAAA,CAAK,QAAQ,GAAA,CAAI,IAAA,EAAM,EAAE,EAAA,EAAI,GAAA,EAAK,OAAO,CAAA;AAAA,IAC7C,CAAA,MAEA;AACI,MAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,IAAI,CAAA;AAAA,IAC5B;AAEA,IAAA,IAAA,CAAK,SAAA,EAAU;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,SAAA,GACR;AACI,IAAA,IAAI,CAAC,KAAK,UAAA,EACV;AACI,MAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAClB,MAAA,cAAA,CAAe,MACf;AACI,QAAA,IAAA,CAAK,UAAA,GAAa,KAAA;AAClB,QAAA,IAAA,CAAK,IAAA,EAAK;AAAA,MACd,CAAC,CAAA;AAAA,IACL;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,IAAA,GACR;AACI,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,IAAA,KAAS,CAAA,EAC1B;AACI,MAAA,IAAA,CAAK,WAAA,EAAY;AAEjB,MAAA;AAAA,IACJ;AAEA,IAAA,IAAI,CAAC,KAAK,QAAA,EACV;AACI,MAAA,IAAA,CAAK,aAAA,EAAc;AAEnB,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,KAAA,GAAQ,KAAK,WAAA,EAAY;AAE/B,IAAA,IAAI,IAAA,CAAK,MAAA,EAAQ,IAAA,KAAS,SAAA,CAAU,KAAK,CAAA,EACzC;AACI,MAAA,IAAA,CAAK,WAAW,KAAK,CAAA;AAAA,IACzB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,aAAA,GACR;AACI,IAAA,MAAM,WAAW,IAAA,CAAK,QAAA;AAEtB,IAAA,IAAI,IAAA,CAAK,oBAAoB,QAAA,EAC7B;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,IAAA,CAAK,eAAA,GAAkB,QAAA;AACvB,IAAA,IAAA,CAAK,gBAAe,CAAE,IAAA;AAAA,MAClB,MAAM,QAAA,KAAa,IAAA,CAAK,QAAA,IAAY,KAAK,MAAA,EAAO;AAAA,MAChD,CAAA,KAAA,KACA;AACI,QAAA,IAAA,CAAK,YAAY,KAAK,CAAA;AAEtB,QAAA,IAAI,QAAA,KAAa,KAAK,QAAA,EACtB;AACI,UAAA,IAAA,CAAK,UAAA,EAAW;AAAA,QACpB;AAAA,MACJ;AAAA,KACJ;AAAA,EACJ;AAAA;AAAA,EAGQ,MAAA,GACR;AACI,IAAA,IAAA,CAAK,eAAA,GAAkB,MAAA;AACvB,IAAA,IAAA,CAAK,IAAA,EAAK;AAAA,EACd;AAAA;AAAA,EAGQ,UAAA,GACR;AACI,IAAA,IAAA,CAAK,UAAA,GAAa,WAAW,MAC7B;AACI,MAAA,IAAA,CAAK,UAAA,GAAa,MAAA;AAClB,MAAA,IAAA,CAAK,MAAA,EAAO;AAAA,IAChB,CAAA,EAAG,IAAA,CAAK,MAAA,CAAO,UAAA,IAAc,GAAI,CAAA;AAEjC,IAAC,IAAA,CAAK,WAAsC,KAAA,IAAQ;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,WAAA,GACR;AACI,IAAA,OAAO,OAAO,MAAA,CAAO,IAAI,GAAG,CAAC,GAAG,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,EAAE,GAAA,CAAI,CAAA,KAAA,KAAS,MAAM,GAAA,IAAO,EAAE,CAAC,CAAA;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,WAAW,KAAA,EACnB;AACI,IAAA,IAAA,CAAK,WAAA,EAAY;AAEjB,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,QAAS,CAAA;AACtC,IAAA,MAAM,MAAA,GAAiB,EAAE,KAAA,EAAO,IAAA,EAAM,SAAA,CAAU,KAAK,CAAA,EAAG,UAAA,EAAY,CAAA,EAAG,KAAA,EAAO,MAAM,MAAA,EAAU;AAC9F,IAAA,MAAA,CAAO,KAAA,GAAQ,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,SAAA,CAAU;AAAA,MACrC,QAAQ,GAAA,CAAI,GAAA,CAAI,CAAA,EAAA,KAAM,CAAA,MAAA,EAAS,EAAE,CAAA,QAAA,CAAU,CAAA;AAAA,MAC3C,UAAU,MAAA,CAAO,WAAA,CAAY,IAAI,GAAA,CAAI,CAAA,EAAA,KAAM,CAAC,CAAA,MAAA,EAAS,EAAE,CAAA,QAAA,CAAA,EAAY,CAAC,UAAqB,IAAA,CAAK,OAAA,CAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,CAAA;AAAA,MAClH,KAAA;AAAA;AAAA;AAAA;AAAA,MAIA,QAAQ,MACR;AACI,QAAA,MAAA,CAAO,UAAA,GAAa,EAAE,IAAA,CAAK,UAAA;AAC3B,QAAA,IAAA,CAAK,QAAQ,KAAK,CAAA;AAAA,MACtB,CAAA;AAAA,MACA,oBAAA,EAAsB,CAAA,MAAA,KAAU,IAAA,CAAK,oBAAA,CAAqB,QAAQ,MAAM;AAAA,KAC3E,CAAA;AAED,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAClB;AAAA,EAEQ,WAAA,GACR;AACI,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AACnB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAClB;AAAA;AAAA,EAGQ,QAAQ,KAAA,EAChB;AACI,IAAA,IAAA,CAAK,YAAA,CAAa,OAAO,IAAA,CAAK,GAAA,EAAK,CAAA,CAAE,KAAA,CAAM,KAAK,WAAW,CAAA;AAE3D,IAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAO,EACxC;AACI,MAAA,IAAI,MAAM,GAAA,IAAO,CAAC,QAAQ,KAAA,EAAO,KAAA,CAAM,GAAG,CAAA,EAC1C;AACI,QAAA,IAAA,CAAK,YAAA,CAAa,KAAA,CAAM,EAAA,EAAI,KAAA,CAAM,GAAA,EAAK,IAAA,CAAK,GAAA,EAAK,CAAA,CAAE,KAAA,CAAM,IAAA,CAAK,WAAW,CAAA;AAAA,MAC7E;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,YAAA,CAAa,EAAA,EAAY,GAAA,EAAqB,GAAA,EAC5D;AACI,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,GAAA,CAAI,SAAA,CAAU,KAAA,EAAO,EAAE,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,KAAA,EAAO,KAAK,CAAA;AACtE,IAAA,IAAA,CAAK,MAAM,IAAA,CAAK,IAAA,CAAK,IAAI,GAAG,CAAA,EAAG,OAAO,GAAG,CAAA;AAAA,EAC7C;AAAA;AAAA,EAGQ,OAAA,CAAQ,IAAY,KAAA,EAC5B;AACI,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,EAAA,EAAI,MAAM,GAAG,CAAA;AAEpC,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA,EACzB;AACI,MAAA,IAAA,CAAK,KAAA,CAAM,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA;AAAA,IAChC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,oBAAA,CAAqB,QAAoC,MAAA,EACjE;AACI,IAAA,MAAM,KAAK,wBAAA,CAAyB,IAAA,CAAK,MAAA,CAAO,KAAe,IAAI,CAAC,CAAA;AAEpE,IAAA,IAAI,OAAO,MAAA,IAAa,CAAC,IAAA,CAAK,QAAA,CAAU,EAAE,CAAA,EAC1C;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,EAAA,EAAI,OAAO,KAAK,CAAA;AACvC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,EAAA,EAAI,GAAG,CAAA;AAE9B,IAAA,IAAI,MAAA,CAAO,WAAW,SAAA,EACtB;AACI,MAAA,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAA,EAAM,MAAA,CAAO,UAAU,CAAA;AAEzC,MAAA;AAAA,IACJ;AAEA,IAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,IAAA,EAAM,EAAE,KAAK,KAAK,CAAA;AAClC,IAAA,IAAA,CAAK,KAAA,CAAM,MAAM,IAAI,CAAA;AAErB,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA,EACzB;AACI,MAAA,IAAA,CAAK,IAAI,EAAA,EAAI,GAAG,CAAA,CAAE,KAAA,CAAM,KAAK,WAAW,CAAA;AAAA,IAC5C;AAAA,EACJ;AACJ,CAAA","file":"client.js","sourcesContent":["/**\n * State Client Store\n *\n * One frozen entry per `(id, key)` and the ordering rule that decides whether\n * an arrival — a snapshot entry, a frame, a read or a write answer — replaces\n * it. An entry is replaced only on an applied change, so `current()` returns\n * the same object until then: `useSyncExternalStore` compares by reference\n * and would re-render forever on a fresh object per call.\n */\n\nimport type { StateEntry, StateFrame, StateListener } from './client-types';\n\n/** Key fields, flat strings by construction (`.key()` refuses other fields). */\nexport type StateKeyFields = Record<string, string>;\n\n/** A key as text with its fields sorted, so equal keys written in any order name one entry. */\nexport function stableKey(key: StateKeyFields | undefined): string\n{\n if (!key)\n {\n return '';\n }\n\n return JSON.stringify(Object.keys(key).sort().map(field => [field, key[field]]));\n}\n\n/** The inverse of `stableKey`. */\nexport function parseKey(text: string): StateKeyFields | undefined\n{\n return text ? Object.fromEntries(JSON.parse(text) as [string, string][]) : undefined;\n}\n\n/** Deep equality of JSON values, which is all a frame carries. */\nfunction sameJson(a: unknown, b: unknown): boolean\n{\n if (a === b)\n {\n return true;\n }\n\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null || Array.isArray(a) !== Array.isArray(b))\n {\n return false;\n }\n\n const aKeys = Object.keys(a);\n const bRecord = b as Record<string, unknown>;\n\n return aKeys.length === Object.keys(b).length\n && aKeys.every(field => field in bRecord && sameJson((a as Record<string, unknown>)[field], bRecord[field]));\n}\n\n/**\n * The ordering rule for the value. A newer `at` replaces; an older one is\n * dropped; an equal one is by design the same value (one read stamps one\n * `at`), so it replaces only when it says something different. An entry\n * marked revoked before any value arrived has no `at`: anything replaces it.\n */\nfunction supersedes(current: StateEntry | undefined, arrival: StateFrame): boolean\n{\n if (current?.at === undefined || arrival.at > current.at)\n {\n return true;\n }\n\n if (arrival.at < current.at)\n {\n return false;\n }\n\n return !sameJson(current.value, arrival.value) || !sameJson(current.data, arrival.data);\n}\n\nfunction entryOf(frame: StateFrame, status: StateStatus): StateEntry\n{\n const { at, value, data } = frame;\n\n return Object.freeze(data === undefined ? { at, value, status } : { at, value, data, status });\n}\n\ntype StateStatus = StateEntry['status'];\n\nexport class StateStore\n{\n private readonly entries = new Map<string, StateEntry>();\n private readonly listeners = new Map<string, Set<StateListener>>();\n\n /**\n * The slots the server stopped serving, with the generation of the\n * connection that said so. Kept apart from the entries: a revoke can\n * arrive before the slot has a value, and an arrival afterwards supplies\n * the value without clearing the mark.\n */\n private readonly revoked = new Map<string, number>();\n\n /** The store slot of `(id, key)`. */\n static slot(id: string, keyText: string): string\n {\n return `${id}\\u0000${keyText}`;\n }\n\n current(slot: string): StateEntry | undefined\n {\n return this.entries.get(slot);\n }\n\n /**\n * Apply an arrival: the value under the ordering rule, the status from the\n * revoked mark. Listeners hear only an applied change.\n */\n apply(slot: string, frame: StateFrame): void\n {\n const current = this.entries.get(slot);\n const status: StateStatus = this.revoked.has(slot) ? 'revoked' : 'ready';\n\n if (supersedes(current, frame))\n {\n this.replace(slot, entryOf(frame, status));\n }\n else if (current!.status !== status)\n {\n // Not superseded, so the current entry has a value: only its status changes.\n this.replace(slot, entryOf(current as StateFrame, status));\n }\n }\n\n /** Mark `slot` revoked by the connection of `generation`; its value, if any, is kept. */\n revoke(slot: string, generation: number): void\n {\n this.revoked.set(slot, generation);\n const entry = this.entries.get(slot);\n\n if (entry?.status !== 'revoked')\n {\n this.replace(slot, Object.freeze(entry ? { ...entry, status: 'revoked' as const } : { status: 'revoked' as const }));\n }\n }\n\n /** The generation of the connection that revoked `slot`, or `undefined` while it is served. */\n revokedBy(slot: string): number | undefined\n {\n return this.revoked.get(slot);\n }\n\n /** Clear the revoked mark; the next arrival for `slot` is `'ready'`. */\n allow(slot: string): void\n {\n this.revoked.delete(slot);\n }\n\n listen(slot: string, listener: StateListener): void\n {\n const set = this.listeners.get(slot) ?? new Set();\n set.add(listener);\n this.listeners.set(slot, set);\n }\n\n unlisten(slot: string, listener?: StateListener): void\n {\n const set = this.listeners.get(slot);\n\n if (listener)\n {\n set?.delete(listener);\n }\n\n if (!listener || set?.size === 0)\n {\n this.listeners.delete(slot);\n }\n }\n\n clearListeners(): void\n {\n this.listeners.clear();\n }\n\n /** Replace the entry and notify; a throwing listener is logged and does not stop the others. */\n private replace(slot: string, entry: StateEntry): void\n {\n this.entries.set(slot, entry);\n\n for (const listener of [...this.listeners.get(slot) ?? []])\n {\n notify(slot, listener);\n }\n }\n}\n\nfunction notify(slot: string, listener: StateListener): void\n{\n try\n {\n listener();\n }\n catch (error)\n {\n // The state id only: the key is the app's data.\n console.warn(`[state] A listener of state \"${slot.split('\\u0000')[0]}\" threw:`, error);\n }\n}\n","/**\n * State Client\n *\n * The browser side of a state router (`@spfn/core/state`): it calls the derived\n * read, write and snapshot routes, keeps one SSE stream open for every state of\n * the router, and orders snapshots against frames per `(id, key)`. It knows the\n * router by type — `typeof states` — and at runtime by the manifest every\n * snapshot carries; it imports no server code.\n *\n * @example\n * ```typescript\n * import { createStateClient } from '@spfn/core/state/client';\n * import { createAuthSSEClient } from '@spfn/core/event/sse/client';\n * import type { states } from '@/server/states';\n *\n * export const state = createStateClient<typeof states>({ sse: createAuthSSEClient() });\n *\n * const stop = state.subscribe('runtime', { workspaceId }, () => render(state.current('runtime', { workspaceId })));\n * ```\n */\n\nimport type { SSESubscriptionChange } from '@spfn/core/event/sse';\nimport { createApi } from '@spfn/core/nextjs';\nimport { stableKey, StateStore, type StateKeyFields } from './client-store';\nimport type {\n AnyStateRouter,\n StateClient,\n StateClientConfig,\n StateEntry,\n StateFrame,\n StateListener,\n} from './client-types';\n\nexport type {\n AnyStateRouter,\n InferStateData,\n StateCatalog,\n StateFrameOf,\n InferStateInput,\n InferStateKey,\n InferStateValue,\n StateClient,\n StateClientConfig,\n StateEntry,\n StateFrame,\n StateId,\n StateKeyArgs,\n StateListener,\n WritableStateId,\n} from './client-types';\n\n/** A frame as the SSE event carries it. */\ntype WireFrame = StateFrame & { key?: StateKeyFields; subject?: string };\n\n/** Every state of the router, in declaration order: its key fields and whether it has a write. */\ntype Manifest = Record<string, { key: string[]; write: boolean }>;\n\ninterface Snapshot\n{\n at: string;\n states: Record<string, { value: unknown; data?: unknown }>;\n manifest: Manifest;\n}\n\ntype RouteCalls = Record<string, { call(input: object): Promise<unknown> }>;\n\n/** One `(id, key)` someone watches, and how many subscriptions hold it. */\ninterface Watch\n{\n id: string;\n key: StateKeyFields | undefined;\n count: number;\n}\n\n/** The stream as one `sse.subscribe` opened it; `generation` names its current connection. */\ninterface Stream\n{\n query: StateKeyFields;\n text: string;\n generation: number;\n close: () => void;\n}\n\n/** When an automatic read was started: the connection generation and the client's clock. */\ninterface ReadTag\n{\n generation: number;\n tick: number;\n}\n\n/** `get<Id>State` / `put<Id>State`, the names the server router derives. */\nfunction routeName(method: 'get' | 'put', id: string): string\n{\n return `${method}${id[0].toUpperCase()}${id.slice(1)}State`;\n}\n\n/** Whether `query` carries every field of `key` with its value. */\nfunction carries(query: StateKeyFields, key: StateKeyFields): boolean\n{\n return Object.entries(key).every(([field, value]) => query[field] === value);\n}\n\nfunction sortedFields(fields: string[]): string\n{\n return [...fields].sort().join(', ');\n}\n\n/**\n * Build the client for `typeof states`. The first subscription (or call)\n * reads the snapshot, whose manifest names every state; the stream then\n * requests every state event — the server's `grant` can only activate an\n * event the connection requested.\n */\nexport function createStateClient<TStates extends AnyStateRouter>(config: StateClientConfig): StateClient<TStates>\n{\n return new BrowserStateClient(config) as unknown as StateClient<TStates>;\n}\n\nclass BrowserStateClient\n{\n private readonly store = new StateStore();\n private readonly api: RouteCalls;\n private readonly watched = new Map<string, Watch>();\n private readonly reportError: (error: unknown) => void;\n private manifest: Manifest | undefined;\n private manifestRead: Promise<void> | undefined;\n private stream: Stream | undefined;\n private syncQueued = false;\n\n /** The lifetime whose subscriptions wait on the first snapshot — its read in flight, or its retry timer set. */\n private waitingLifetime: number | undefined;\n private retryTimer: ReturnType<typeof setTimeout> | undefined;\n\n /** Bumped by `close()`: a snapshot started before it is discarded. */\n private lifetime = 0;\n\n /** Bumped on every connection open; tags the snapshot read for it. */\n private generation = 0;\n\n /** Bumped on every automatic read and every grant, to tell which came first. */\n private clock = 0;\n\n /** The clock at the latest grant of each slot. */\n private readonly grants = new Map<string, number>();\n\n constructor(private readonly config: StateClientConfig)\n {\n this.api = createApi<any>(config.api) as unknown as RouteCalls;\n this.reportError = config.onError ?? (error => console.error('[state] A refresh failed:', error));\n }\n\n async get(id: string, key?: StateKeyFields): Promise<StateFrame>\n {\n await this.requireManifest();\n const frame = await this.api[routeName('get', id)].call(key ? { query: key } : {}) as StateFrame;\n this.store.apply(this.slot(id, key), frame);\n\n return frame;\n }\n\n async write(id: string, input: unknown, key?: StateKeyFields): Promise<StateFrame>\n {\n if (!(await this.requireManifest())[id]?.write)\n {\n throw new Error(`State client: state \"${id}\" declares no .write(), so it has no write route.`);\n }\n\n const frame = await this.api[routeName('put', id)].call({ body: input, ...(key ? { query: key } : {}) }) as StateFrame;\n this.store.apply(this.slot(id, key), frame);\n\n return frame;\n }\n\n snapshot(query: StateKeyFields = this.streamQuery()): Promise<Snapshot>\n {\n return this.readSnapshot(query, this.tag());\n }\n\n subscribe(id: string, ...args: [StateListener] | [StateKeyFields, StateListener]): () => void\n {\n const listener = args[args.length - 1] as StateListener;\n const key = args.length === 2 ? args[0] as StateKeyFields : undefined;\n const refusal = this.manifest && this.refusal(id, key);\n\n if (refusal)\n {\n this.reportError(refusal);\n\n return () => undefined;\n }\n\n // One wrapper per subscription: the same listener subscribed twice is two subscriptions.\n const notify = () => listener();\n const slot = this.slot(id, key);\n this.store.listen(slot, notify);\n this.watch(slot, id, key, 1);\n let live = true;\n\n return () =>\n {\n if (live)\n {\n live = false;\n this.store.unlisten(slot, notify);\n this.watch(slot, id, key, -1);\n }\n };\n }\n\n current(id: string, key?: StateKeyFields): StateEntry | undefined\n {\n return this.store.current(this.slot(id, key));\n }\n\n close(): void\n {\n this.lifetime++;\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.closeStream();\n this.watched.clear();\n this.store.clearListeners();\n }\n\n private slot(id: string, key: StateKeyFields | undefined): string\n {\n return StateStore.slot(id, stableKey(key));\n }\n\n private tag(): ReadTag\n {\n return { generation: this.generation, tick: ++this.clock };\n }\n\n /** The key of state `id` the query names; `undefined` for a keyless state. */\n private keyIn(id: string, query: StateKeyFields): StateKeyFields | undefined\n {\n const fields = this.manifest![id].key;\n\n return fields.length ? Object.fromEntries(fields.map(field => [field, query[field]])) : undefined;\n }\n\n /** Why the manifest refuses `subscribe(id, key)` — an unknown id, or key fields not the state's. */\n private refusal(id: string, key: StateKeyFields | undefined): Error | undefined\n {\n const state = this.manifest![id];\n\n if (!state)\n {\n return new Error(`State client: the server's manifest has no state \"${id}\".`);\n }\n\n const expected = sortedFields(state.key);\n const given = sortedFields(Object.keys(key ?? {}));\n\n return expected === given\n ? undefined\n : new Error(`State client: state \"${id}\" takes the key fields [${expected}], given [${given}].`);\n }\n\n /** The first snapshot, read once however many callers wait for it. */\n private ensureManifest(): Promise<void>\n {\n if (this.manifest)\n {\n return Promise.resolve();\n }\n\n this.manifestRead ??= this.snapshot()\n .then(() => undefined)\n .finally(() =>\n {\n this.manifestRead = undefined;\n });\n\n return this.manifestRead;\n }\n\n /** The manifest for a call the app made; one whose wait outlived `close()` is refused. */\n private async requireManifest(): Promise<Manifest>\n {\n await this.ensureManifest();\n\n if (!this.manifest)\n {\n throw new Error('State client: the client was closed while the first snapshot was in flight.');\n }\n\n return this.manifest;\n }\n\n /**\n * Read the snapshot and apply it: the manifest first — `keyIn` and the\n * key checks need it — then each state present, then each watched state\n * it omits. A snapshot that resolves after `close()` is discarded.\n */\n private async readSnapshot(query: StateKeyFields, tag: ReadTag): Promise<Snapshot>\n {\n const lifetime = this.lifetime;\n const snapshot = await this.api.getStates.call({ query }) as Snapshot;\n\n if (lifetime === this.lifetime)\n {\n this.installManifest(snapshot.manifest);\n this.applyPresent(snapshot, query, tag);\n this.applyOmitted(snapshot, query, tag);\n }\n\n return snapshot;\n }\n\n /**\n * The manifest is static; the first one stays. Subscriptions made before\n * it are checked now, and the stream opens for those it accepts —\n * whichever call (`subscribe`, `get`, `write`, `snapshot`) brought it.\n */\n private installManifest(manifest: Manifest): void\n {\n if (this.manifest)\n {\n return;\n }\n\n this.manifest = manifest;\n\n for (const [slot, watch] of this.watched)\n {\n const refusal = this.refusal(watch.id, watch.key);\n\n if (refusal)\n {\n this.watched.delete(slot);\n this.store.unlisten(slot);\n this.reportError(refusal);\n }\n }\n\n if (this.watched.size > 0)\n {\n this.queueSync();\n }\n }\n\n /**\n * An arrival from a read started for the connection of `tag`: a\n * connection newer than the one that revoked the slot lifts the mark, an\n * older or the same connection leaves `'revoked'`. The lift is by\n * generation alone — a frame or a write answer of the new connection may\n * land first with a newer `at` — and the ordering rule decides only the value.\n */\n private serve(slot: string, frame: StateFrame, tag: ReadTag): void\n {\n if ((this.store.revokedBy(slot) ?? Infinity) < tag.generation)\n {\n this.store.allow(slot);\n }\n\n this.store.apply(slot, frame);\n }\n\n /** A state present is served, under the generation rule of `serve`. */\n private applyPresent(snapshot: Snapshot, query: StateKeyFields, tag: ReadTag): void\n {\n for (const [id, result] of Object.entries(snapshot.states))\n {\n this.serve(this.slot(id, this.keyIn(id, query)), { at: snapshot.at, ...result }, tag);\n }\n }\n\n /**\n * A watched state whose key the query carried (or a keyless one) and that\n * the snapshot omits is not served to this subject: `'revoked'`. Not when\n * a newer connection exists, nor when a grant came after the read started.\n */\n private applyOmitted(snapshot: Snapshot, query: StateKeyFields, tag: ReadTag): void\n {\n if (tag.generation !== this.generation)\n {\n return;\n }\n\n for (const [slot, watch] of this.watched)\n {\n const asked = !watch.key || carries(query, watch.key);\n const grantedSince = (this.grants.get(slot) ?? 0) > tag.tick;\n\n if (asked && !(watch.id in snapshot.states) && !grantedSince)\n {\n this.store.revoke(slot, tag.generation);\n }\n }\n }\n\n private watch(slot: string, id: string, key: StateKeyFields | undefined, delta: 1 | -1): void\n {\n const count = (this.watched.get(slot)?.count ?? 0) + delta;\n\n if (count > 0)\n {\n this.watched.set(slot, { id, key, count });\n }\n else\n {\n this.watched.delete(slot);\n }\n\n this.queueSync();\n }\n\n /**\n * One stream for the client, its query the union of the subscribed keys.\n * Batched to the end of the task, so components mounting together (or a\n * StrictMode unmount-remount) open the stream once.\n */\n private queueSync(): void\n {\n if (!this.syncQueued)\n {\n this.syncQueued = true;\n queueMicrotask(() =>\n {\n this.syncQueued = false;\n this.sync();\n });\n }\n }\n\n /**\n * Until the manifest is known, subscriptions wait for the first snapshot;\n * the stream opens after it, never before — its `events=` comes from it.\n */\n private sync(): void\n {\n if (this.watched.size === 0)\n {\n this.closeStream();\n\n return;\n }\n\n if (!this.manifest)\n {\n this.awaitManifest();\n\n return;\n }\n\n const query = this.streamQuery();\n\n if (this.stream?.text !== stableKey(query))\n {\n this.openStream(query);\n }\n }\n\n /**\n * One waiter per lifetime on the first snapshot. A failure goes to\n * `onError`, and the read is retried after `retryDelay` — the stream that\n * would reconnect on its own does not exist yet. A retry stops on\n * `close()` or when nothing is watched (`sync` closes instead).\n */\n private awaitManifest(): void\n {\n const lifetime = this.lifetime;\n\n if (this.waitingLifetime === lifetime)\n {\n return;\n }\n\n this.waitingLifetime = lifetime;\n this.ensureManifest().then(\n () => lifetime === this.lifetime && this.resync(),\n error =>\n {\n this.reportError(error);\n\n if (lifetime === this.lifetime)\n {\n this.retryLater();\n }\n },\n );\n }\n\n /** The wait is over; `sync` opens the stream, or waits again when a closed lifetime's read was discarded. */\n private resync(): void\n {\n this.waitingLifetime = undefined;\n this.sync();\n }\n\n /** Read the first snapshot again after `retryDelay`, as the SSE client reconnects a dropped stream. */\n private retryLater(): void\n {\n this.retryTimer = setTimeout(() =>\n {\n this.retryTimer = undefined;\n this.resync();\n }, this.config.retryDelay ?? 3000);\n // A timer in Node has `unref`: a pending retry does not keep the process alive.\n (this.retryTimer as { unref?: () => void }).unref?.();\n }\n\n /**\n * Every subscribed key's fields; one connection carries one value per\n * field, so on a conflict the latest subscribed key wins. The keys it\n * shadows are read once when the stream opens and get no frames.\n */\n private streamQuery(): StateKeyFields\n {\n return Object.assign({}, ...[...this.watched.values()].map(watch => watch.key ?? {}));\n }\n\n /**\n * Open (or reopen) the stream. `events` is every state of the manifest, in\n * its order, never only the subscribed ones: a state subscribed later would\n * otherwise be unrequested, and the server's `grant` cannot activate it.\n */\n private openStream(query: StateKeyFields): void\n {\n this.closeStream();\n\n const ids = Object.keys(this.manifest!);\n const stream: Stream = { query, text: stableKey(query), generation: 0, close: () => undefined };\n stream.close = this.config.sse.subscribe({\n events: ids.map(id => `state.${id}.changed`),\n handlers: Object.fromEntries(ids.map(id => [`state.${id}.changed`, (frame: WireFrame) => this.onFrame(id, frame)])),\n query,\n // Every open — the first, a reconnect, a reopen on a key change — re-reads the\n // snapshot. A frame sent while no stream was open is lost, whether its key\n // changed or not; the snapshot read after the open covers that gap.\n onOpen: () =>\n {\n stream.generation = ++this.generation;\n this.refresh(query);\n },\n onSubscriptionChange: change => this.onSubscriptionChange(change, stream),\n });\n\n this.stream = stream;\n }\n\n private closeStream(): void\n {\n this.stream?.close();\n this.stream = undefined;\n }\n\n /** The snapshot for the stream's query, and one read for each subscribed key the query shadows. */\n private refresh(query: StateKeyFields): void\n {\n this.readSnapshot(query, this.tag()).catch(this.reportError);\n\n for (const watch of this.watched.values())\n {\n if (watch.key && !carries(query, watch.key))\n {\n this.readShadowed(watch.id, watch.key, this.tag()).catch(this.reportError);\n }\n }\n }\n\n /**\n * A shadowed key gets no frames, no grant and no snapshot entry; the\n * server answering its own read is the evidence of access instead.\n */\n private async readShadowed(id: string, key: StateKeyFields, tag: ReadTag): Promise<void>\n {\n const frame = await this.api[routeName('get', id)].call({ query: key }) as StateFrame;\n this.serve(this.slot(id, key), frame, tag);\n }\n\n /** A frame for a key nobody here subscribed is dropped — the server's filter is the first guard, this the second. */\n private onFrame(id: string, frame: WireFrame): void\n {\n const slot = this.slot(id, frame.key);\n\n if (this.watched.has(slot))\n {\n this.store.apply(slot, frame);\n }\n }\n\n /**\n * A revoke or a grant concerns the key the connection carries — the\n * server scopes both by the connect query — and so does the read after a\n * grant. Revoked: the entry keeps its value, marked `'revoked'`, until a\n * grant. Granted: the mark is cleared and the key read once; the server\n * sends a frame too, in either order, and both arrive `'ready'`.\n */\n private onSubscriptionChange(change: SSESubscriptionChange<any>, stream: Stream): void\n {\n const id = /^state\\.(.+)\\.changed$/.exec(change.event as string)?.[1];\n\n if (id === undefined || !this.manifest![id])\n {\n return;\n }\n\n const key = this.keyIn(id, stream.query);\n const slot = this.slot(id, key);\n\n if (change.status === 'revoked')\n {\n this.store.revoke(slot, stream.generation);\n\n return;\n }\n\n this.grants.set(slot, ++this.clock);\n this.store.allow(slot);\n\n if (this.watched.has(slot))\n {\n this.get(id, key).catch(this.reportError);\n }\n }\n}\n"]}