@spfn/core 0.3.0-beta.31 → 0.3.0-beta.33

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.
@@ -71,6 +71,9 @@ type StateListener = () => void;
71
71
  * client retries a dropped one — `attempt` is its count, `0` before its first retry;
72
72
  * `'failed'` when the retries are exhausted (the SSE client's `maxReconnectAttempts`, the
73
73
  * client's `maxRetries` for the first snapshot), until `retry()`, a new `subscribe` or `online`.
74
+ * `'closed'` with reason `'unauthorized'` when the stream's token mint answered 401 — the
75
+ * session is gone (e.g. its device key was revoked): no retry is set, and neither `online` nor
76
+ * a new `subscribe` reopens it; only the app's own `retry()` (after signing in again) does.
74
77
  */
75
78
  type ConnectionStatus = {
76
79
  status: 'connected';
@@ -79,6 +82,9 @@ type ConnectionStatus = {
79
82
  attempt: number;
80
83
  } | {
81
84
  status: 'failed';
85
+ } | {
86
+ status: 'closed';
87
+ reason: 'unauthorized';
82
88
  };
83
89
  /** Called with the new status after the client's connection status changed. */
84
90
  type ConnectionListener = (status: ConnectionStatus) => void;
@@ -161,7 +167,8 @@ interface StateClient<TStates extends AnyStateRouter> {
161
167
  */
162
168
  subscribeConnection(listener: ConnectionListener): () => void;
163
169
  /**
164
- * Restart now when `'failed'` or `'reconnecting'`: the first snapshot is
170
+ * Restart now when `'failed'`, `'reconnecting'` or `'closed'` (unauthorized —
171
+ * call it once the app signed in again): the first snapshot is
165
172
  * read again, its retry count reset, or the stream reopened. A no-op when
166
173
  * `'connected'`, while the first snapshot is in flight, or with nothing
167
174
  * subscribed — so after `close()`, until a new `subscribe`, which `retry()`
@@ -122,6 +122,7 @@ function notify(slot, listener) {
122
122
  // src/state/client.ts
123
123
  var CONNECTED = Object.freeze({ status: "connected" });
124
124
  var FAILED = Object.freeze({ status: "failed" });
125
+ var UNAUTHORIZED = Object.freeze({ status: "closed", reason: "unauthorized" });
125
126
  var STARTING = Object.freeze({ status: "reconnecting", attempt: 0 });
126
127
  function reconnecting(attempt) {
127
128
  return attempt === 0 ? STARTING : Object.freeze({ status: "reconnecting", attempt });
@@ -166,7 +167,12 @@ var BrowserStateClient = class {
166
167
  syncQueued = false;
167
168
  status = STARTING;
168
169
  connectionListeners = /* @__PURE__ */ new Set();
169
- onOnline = () => this.retry();
170
+ // A refused session is not a network blip: coming back online does not reopen it.
171
+ onOnline = () => {
172
+ if (this.status !== UNAUTHORIZED) {
173
+ this.retry();
174
+ }
175
+ };
170
176
  /** The retries of the first snapshot since its last start, against `maxRetries`. */
171
177
  retries = 0;
172
178
  /** The lifetime whose subscriptions wait on the first snapshot — its read in flight, or its retry timer set. */
@@ -297,6 +303,10 @@ var BrowserStateClient = class {
297
303
  const fields = this.manifest[id].key;
298
304
  return fields.length ? Object.fromEntries(fields.map((field) => [field, query[field]])) : void 0;
299
305
  }
306
+ /** The slot of state `id` on the key `query` carries; `undefined` when the query lacks one of its key fields. */
307
+ carriedSlot(id, query) {
308
+ return this.manifest[id].key.every((field) => field in query) ? this.slot(id, this.keyIn(id, query)) : void 0;
309
+ }
300
310
  /** Why the manifest refuses `subscribe(id, key)` — an unknown id, or key fields not the state's. */
301
311
  refusal(id, key) {
302
312
  const state = this.manifest[id];
@@ -389,18 +399,19 @@ var BrowserStateClient = class {
389
399
  }
390
400
  }
391
401
  /**
392
- * A watched state whose key the query carried (or a keyless one) and that
393
- * the snapshot omits is not served to this subject: `'revoked'`. Not when
394
- * a newer connection exists, nor when a grant came after the read started.
402
+ * Every state the snapshot omits whose key the query carried (or a keyless
403
+ * one), watched or not, is not served to this subject: `'revoked'` on that
404
+ * key. Not when a newer connection exists, nor when a grant came after the
405
+ * read started. Every watched slot the query carries is one of these.
395
406
  */
396
407
  applyOmitted(snapshot, query, tag) {
397
408
  if (tag.generation !== this.generation) {
398
409
  return;
399
410
  }
400
- for (const [slot, watch] of this.watched) {
401
- const asked = !watch.key || carries(query, watch.key);
402
- const grantedSince = (this.grants.get(slot) ?? 0) > tag.tick;
403
- if (asked && !(watch.id in snapshot.states) && !grantedSince) {
411
+ for (const id of Object.keys(this.manifest)) {
412
+ const slot = this.carriedSlot(id, query);
413
+ const grantedSince = slot !== void 0 && (this.grants.get(slot) ?? 0) > tag.tick;
414
+ if (slot !== void 0 && !(id in snapshot.states) && !grantedSince) {
404
415
  this.store.revoke(slot, tag.generation);
405
416
  }
406
417
  }
@@ -448,6 +459,9 @@ var BrowserStateClient = class {
448
459
  this.awaitManifest();
449
460
  return;
450
461
  }
462
+ if (this.status === UNAUTHORIZED) {
463
+ return;
464
+ }
451
465
  const query = this.streamQuery();
452
466
  if (this.stream?.failed || this.stream?.text !== stableKey(query)) {
453
467
  this.openStream(query);
@@ -551,7 +565,7 @@ var BrowserStateClient = class {
551
565
  this.streamStatus(stream, CONNECTED);
552
566
  },
553
567
  onReconnect: (attempt) => this.streamStatus(stream, reconnecting(attempt)),
554
- onClose: () => this.onStreamClose(stream),
568
+ onClose: (reason) => this.onStreamClose(stream, reason),
555
569
  onConnected: (connected) => this.onConnected(connected, stream),
556
570
  onSubscriptionChange: (change) => this.onSubscriptionChange(change, stream)
557
571
  });
@@ -573,13 +587,14 @@ var BrowserStateClient = class {
573
587
  }
574
588
  /**
575
589
  * The SSE client fires `onClose` on every teardown; one the client did
576
- * not start is the reconnect attempts exhausted: `'failed'`, and the
590
+ * not start is the reconnect attempts exhausted — `'failed'` — or the
591
+ * token mint answered 401 — `'closed'` / `'unauthorized'`. Either way the
577
592
  * stream counts as absent for the next `sync`.
578
593
  */
579
- onStreamClose(stream) {
594
+ onStreamClose(stream, reason) {
580
595
  if (!stream.closedByClient && stream === this.stream) {
581
596
  stream.failed = true;
582
- this.setConnection(FAILED);
597
+ this.setConnection(reason === "unauthorized" ? UNAUTHORIZED : FAILED);
583
598
  }
584
599
  }
585
600
  /**
@@ -587,9 +602,9 @@ var BrowserStateClient = class {
587
602
  * must cover the frames sent while no stream was open. The server says in
588
603
  * `connected` which states it served their current frame right after it;
589
604
  * the snapshot is read only when one it did not serve is neither denied
590
- * nor served — or when the server predates `initial`. A denied state the
591
- * client watches is `'revoked'`, as an omission from that snapshot would
592
- * be. A connection already replaced is ignored.
605
+ * nor served — or when the server predates `initial`. A denied state whose
606
+ * key the stream carries is `'revoked'`, watched or not, as an omission
607
+ * from that snapshot would be. A connection already replaced is ignored.
593
608
  */
594
609
  onConnected(connected, stream) {
595
610
  if (stream !== this.stream || stream.generation !== this.generation) {
@@ -605,11 +620,15 @@ var BrowserStateClient = class {
605
620
  this.refresh(stream.query);
606
621
  }
607
622
  }
608
- /** Each watched state the connection denies, on the key it carries, marked `'revoked'` by it. */
623
+ /**
624
+ * Each state the connection denies, on the key its query carries, marked
625
+ * `'revoked'` by it, watched or not. A keyed state denied because the query
626
+ * lacks one of its key fields has no carried key, so nothing is marked.
627
+ */
609
628
  revokeDenied(denied, stream) {
610
629
  for (const id of Object.keys(this.manifest)) {
611
- const slot = this.slot(id, this.keyIn(id, stream.query));
612
- if (denied.has(stateEventName(id)) && this.watched.has(slot)) {
630
+ const slot = this.carriedSlot(id, stream.query);
631
+ if (slot !== void 0 && denied.has(stateEventName(id))) {
613
632
  this.store.revoke(slot, stream.generation);
614
633
  }
615
634
  }
@@ -636,14 +655,16 @@ var BrowserStateClient = class {
636
655
  this.serve(this.slot(id, key), frame, tag.generation);
637
656
  }
638
657
  /**
639
- * A frame for a key nobody here subscribed is dropped — the server's filter
640
- * is the first guard, this the second. A frame is served under its
658
+ * A frame on the key the stream's query carries is applied, watched or not,
659
+ * so a later subscribe that does not reopen the stream is served the newest
660
+ * value; a frame for any other key is dropped unless watched — the server's
661
+ * filter is the first guard, this the second. A frame is served under its
641
662
  * connection's generation: the server sends one only on a connection that
642
663
  * allows the state, so a newer connection's frame lifts an older revoke.
643
664
  */
644
665
  onFrame(id, frame, stream) {
645
666
  const slot = this.slot(id, frame.key);
646
- if (this.watched.has(slot)) {
667
+ if (slot === this.carriedSlot(id, stream.query) || this.watched.has(slot)) {
647
668
  this.serve(slot, frame, stream.generation);
648
669
  }
649
670
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/state/client-store.ts","../../src/state/client.ts"],"names":["notify"],"mappings":";;;;;AAiBO,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;AAOA,SAAS,OAAA,CAAQ,OAA2B,OAAA,EAC5C;AACI,EAAA,OAAO,KAAA,KAAU,MAAA,IAAa,OAAA,CAAQ,EAAA,GAAK,KAAA;AAC/C;AAMA,SAAS,OAAA,CAAQ,SAAiC,OAAA,EAClD;AACI,EAAA,OAAO,OAAA,EAAS,EAAA,KAAO,MAAA,IAAa,CAAC,SAAS,OAAA,CAAQ,KAAA,EAAO,OAAA,CAAQ,KAAK,KAAK,CAAC,QAAA,CAAS,OAAA,CAAQ,IAAA,EAAM,QAAQ,IAAI,CAAA;AACvH;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;AAAA;AAAA;AAAA;AAAA;AAAA,EAOtC,MAAA,uBAAa,GAAA,EAAoB;AAAA,EAEjC,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;AACjE,IAAA,MAAM,OAAA,GAAU,CAAC,OAAA,CAAQ,IAAA,CAAK,OAAO,GAAA,CAAI,IAAI,GAAG,KAAK,CAAA;AAErD,IAAA,IAAI,OAAA,EACJ;AACI,MAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,IAAA,EAAM,KAAA,CAAM,EAAE,CAAA;AAAA,IAClC;AAEA,IAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,OAAA,EAAS,KAAK,CAAA,EACrC;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;;;AC/GA,IAAM,YAA8B,MAAA,CAAO,MAAA,CAAO,EAAE,MAAA,EAAQ,aAAa,CAAA;AACzE,IAAM,SAA2B,MAAA,CAAO,MAAA,CAAO,EAAE,MAAA,EAAQ,UAAU,CAAA;AACnE,IAAM,QAAA,GAA6B,OAAO,MAAA,CAAO,EAAE,QAAQ,cAAA,EAAgB,OAAA,EAAS,GAAG,CAAA;AAEvF,SAAS,aAAa,OAAA,EACtB;AACI,EAAA,OAAO,OAAA,KAAY,IAAI,QAAA,GAAW,MAAA,CAAO,OAAO,EAAE,MAAA,EAAQ,cAAA,EAAgB,OAAA,EAAS,CAAA;AACvF;AAEA,SAAS,UAAA,CAAW,GAAqB,CAAA,EACzC;AACI,EAAA,OAAO,EAAE,MAAA,KAAW,CAAA,CAAE,MAAA,IAAW,CAAA,CAA2B,YAAa,CAAA,CAA2B,OAAA;AACxG;AAGA,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,eAAe,EAAA,EACxB;AACI,EAAA,OAAO,SAAS,EAAE,CAAA,QAAA,CAAA;AACtB;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;AAUO,SAAS,kBAAkD,MAAA,EAClE;AACI,EAAA,OAAO,IAAI,mBAAmB,MAAM,CAAA;AACxC;AAEA,IAAM,qBAAN,MACA;AAAA,EAiCI,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;AAE/F,IAAA,IAAI,OAAO,OAAA,EACX;AACI,MAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,OAAO,CAAA;AAAA,IAC/B;AAGA,IAAA,IAAI,OAAO,UAAA,CAAW,gBAAA,KAAqB,UAAA,EAC3C;AACI,MAAA,UAAA,CAAW,gBAAA,CAAiB,QAAA,EAAU,IAAA,CAAK,QAAQ,CAAA;AAAA,IACvD;AAAA,EACJ;AAAA,EA/CiB,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,EAEb,MAAA,GAAS,QAAA;AAAA,EACA,mBAAA,uBAA0B,GAAA,EAAwB;AAAA,EAClD,QAAA,GAAW,MAAY,IAAA,CAAK,KAAA,EAAM;AAAA;AAAA,EAG3C,OAAA,GAAU,CAAA;AAAA;AAAA,EAGV,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,EAmBlD,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,UAAA,GACA;AACI,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EAChB;AAAA,EAEA,oBAAoB,QAAA,EACpB;AAEI,IAAA,MAAMA,OAAAA,GAA6B,CAAA,MAAA,KAAU,QAAA,CAAS,MAAM,CAAA;AAC5D,IAAA,IAAA,CAAK,mBAAA,CAAoB,IAAIA,OAAM,CAAA;AAEnC,IAAA,OAAO,MACP;AACI,MAAA,IAAA,CAAK,mBAAA,CAAoB,OAAOA,OAAM,CAAA;AAAA,IAC1C,CAAA;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,KAAA,GACA;AACI,IAAA,IAAI,KAAK,MAAA,KAAW,SAAA,IAAa,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA,EACvD;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,IAAI,KAAK,QAAA,EACT;AACI,MAAA,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,WAAA,EAAa,CAAA;AAAA,IACtC,CAAA,MAEA;AACI,MAAA,IAAA,CAAK,cAAA,EAAe;AAAA,IACxB;AAAA,EACJ;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,OAAA,GAAU,CAAA;AACf,IAAA,IAAA,CAAK,WAAA,EAAY;AACjB,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AACnB,IAAA,IAAA,CAAK,MAAM,cAAA,EAAe;AAC1B,IAAA,IAAA,CAAK,oBAAoB,KAAA,EAAM;AAC/B,IAAA,IAAA,CAAK,MAAA,GAAS,QAAA;AAEd,IAAA,IAAI,OAAO,UAAA,CAAW,mBAAA,KAAwB,UAAA,EAC9C;AACI,MAAA,UAAA,CAAW,mBAAA,CAAoB,QAAA,EAAU,IAAA,CAAK,QAAQ,CAAA;AAAA,IAC1D;AAAA,EACJ;AAAA;AAAA,EAGQ,cAAc,IAAA,EACtB;AACI,IAAA,IAAI,UAAA,CAAW,IAAA,EAAM,IAAA,CAAK,MAAM,CAAA,EAChC;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AAEd,IAAA,KAAA,MAAW,QAAA,IAAY,CAAC,GAAG,IAAA,CAAK,mBAAmB,CAAA,EACnD;AACI,MAAA,IACA;AACI,QAAA,QAAA,CAAS,IAAI,CAAA;AAAA,MACjB,SACO,KAAA,EACP;AACI,QAAA,OAAA,CAAQ,IAAA,CAAK,wCAAwC,KAAK,CAAA;AAAA,MAC9D;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,QAAQ,OAAA,EAChB;AACI,IAAA,MAAM,WAAW,OAAA,CAAQ,QAAA;AACzB,IAAA,IAAA,CAAK,eAAA,CAAgB,SAAS,QAAQ,CAAA;AACtC,IAAA,IAAA,CAAK,YAAA,CAAa,UAAU,OAAA,CAAQ,KAAA,IAAS,EAAC,EAAG,IAAA,CAAK,KAAK,CAAA;AAAA,EAC/D;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,UAAA,EAC/C;AACI,IAAA,IAAA,CAAK,KAAK,KAAA,CAAM,SAAA,CAAU,IAAI,CAAA,IAAK,YAAY,UAAA,EAC/C;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;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,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,IAAI,IAAA,CAAK,SAAU,EAAE,CAAA,EAAG,IAAI,KAAA,CAAM,CAAA,KAAA,KAAS,KAAA,IAAS,KAAK,CAAA,EACzD;AACI,QAAA,IAAA,CAAK,MAAM,IAAA,CAAK,IAAA,CAAK,IAAI,IAAA,CAAK,KAAA,CAAM,IAAI,KAAK,CAAC,CAAA,EAAG,EAAE,IAAI,QAAA,CAAS,EAAA,EAAI,GAAG,MAAA,EAAO,EAAG,IAAI,UAAU,CAAA;AAAA,MACnG;AAAA,IACJ;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;AAGA,IAAA,IAAI,KAAA,KAAU,KAAK,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA,IAAK,IAAA,CAAK,WAAW,MAAA,EAC9D;AACI,MAAA,IAAA,CAAK,SAAA,EAAU;AAAA,IACnB;AAAA,EACJ;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;AAAA;AAAA;AAAA,EASQ,IAAA,GACR;AACI,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,IAAA,KAAS,CAAA,EAC1B;AACI,MAAA,IAAA,CAAK,OAAA,GAAU,CAAA;AACf,MAAA,IAAA,CAAK,WAAA,EAAY;AACjB,MAAA,IAAA,CAAK,cAAc,QAAQ,CAAA;AAE3B,MAAA;AAAA,IACJ;AAEA,IAAA,IAAI,CAAC,KAAK,QAAA,EACV;AACI,MAAA,IAAA,CAAK,aAAA,EAAc;AAEnB,MAAA;AAAA,IACJ;AAGA,IAAA,MAAM,KAAA,GAAQ,KAAK,WAAA,EAAY;AAE/B,IAAA,IAAI,IAAA,CAAK,QAAQ,MAAA,IAAU,IAAA,CAAK,QAAQ,IAAA,KAAS,SAAA,CAAU,KAAK,CAAA,EAChE;AACI,MAAA,IAAA,CAAK,WAAW,KAAK,CAAA;AAAA,IACzB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,aAAA,GACR;AACI,IAAA,MAAM,WAAW,IAAA,CAAK,QAAA;AAEtB,IAAA,IAAI,IAAA,CAAK,oBAAoB,QAAA,EAC7B;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,IAAI,IAAA,CAAK,WAAW,MAAA,EACpB;AACI,MAAA,IAAA,CAAK,OAAA,GAAU,CAAA;AACf,MAAA,IAAA,CAAK,cAAc,QAAQ,CAAA;AAAA,IAC/B;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;AAAA;AAAA;AAAA;AAAA,EAOQ,UAAA,GACR;AACI,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,MAAA,CAAO,UAAA,IAAc,CAAA;AAE7C,IAAA,IAAI,UAAA,GAAa,CAAA,IAAK,IAAA,CAAK,OAAA,IAAW,UAAA,EACtC;AACI,MAAA,IAAA,CAAK,eAAA,GAAkB,MAAA;AACvB,MAAA,IAAA,CAAK,cAAc,MAAM,CAAA;AAEzB,MAAA;AAAA,IACJ;AAEA,IAAA,IAAA,CAAK,OAAA,EAAA;AACL,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,cAAA,GACR;AACI,IAAA,IAAA,CAAK,OAAA,GAAU,CAAA;AAEf,IAAA,IAAI,KAAK,eAAA,KAAoB,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,eAAe,MAAA,EAClE;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,YAAA,CAAa,KAAK,UAAU,CAAA;AAC5B,IAAA,IAAA,CAAK,UAAA,GAAa,MAAA;AAClB,IAAA,IAAA,CAAK,cAAc,QAAQ,CAAA;AAC3B,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EAChB;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;AAAA;AAAA;AAAA,EAUQ,WAAW,KAAA,EACnB;AACI,IAAA,IAAA,CAAK,WAAA,EAAY;AAEjB,IAAA,IAAI,IAAA,CAAK,WAAW,SAAA,EACpB;AACI,MAAA,IAAA,CAAK,cAAc,QAAQ,CAAA;AAAA,IAC/B;AAEA,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,QAAS,CAAA;AACtC,IAAA,MAAM,MAAA,GAAiB,EAAE,KAAA,EAAO,IAAA,EAAM,UAAU,KAAK,CAAA,EAAG,UAAA,EAAY,CAAA,EAAG,gBAAgB,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,KAAA,EAAO,MAAM,MAAA,EAAU;AACpI,IAAA,MAAA,CAAO,KAAA,GAAQ,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,SAAA,CAAU;AAAA,MACrC,MAAA,EAAQ,GAAA,CAAI,GAAA,CAAI,cAAc,CAAA;AAAA,MAC9B,UAAU,MAAA,CAAO,WAAA,CAAY,IAAI,GAAA,CAAI,CAAA,EAAA,KAAM,CAAC,cAAA,CAAe,EAAE,GAAG,CAAC,KAAA,KAAqB,KAAK,OAAA,CAAQ,EAAA,EAAI,OAAO,MAAM,CAAC,CAAC,CAAC,CAAA;AAAA,MACvH,KAAA;AAAA,MACA,QAAQ,MACR;AACI,QAAA,MAAA,CAAO,UAAA,GAAa,EAAE,IAAA,CAAK,UAAA;AAC3B,QAAA,IAAA,CAAK,YAAA,CAAa,QAAQ,SAAS,CAAA;AAAA,MACvC,CAAA;AAAA,MACA,aAAa,CAAA,OAAA,KAAW,IAAA,CAAK,aAAa,MAAA,EAAQ,YAAA,CAAa,OAAO,CAAC,CAAA;AAAA,MACvE,OAAA,EAAS,MAAM,IAAA,CAAK,aAAA,CAAc,MAAM,CAAA;AAAA,MACxC,WAAA,EAAa,CAAA,SAAA,KAAa,IAAA,CAAK,WAAA,CAAY,WAAW,MAAM,CAAA;AAAA,MAC5D,oBAAA,EAAsB,CAAA,MAAA,KAAU,IAAA,CAAK,oBAAA,CAAqB,QAAQ,MAAM;AAAA,KAC3E,CAAA;AAED,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAClB;AAAA;AAAA,EAGQ,WAAA,GACR;AACI,IAAA,IAAI,KAAK,MAAA,EACT;AACI,MAAA,IAAA,CAAK,OAAO,cAAA,GAAiB,IAAA;AAC7B,MAAA,IAAA,CAAK,OAAO,KAAA,EAAM;AAClB,MAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,IAClB;AAAA,EACJ;AAAA;AAAA,EAGQ,YAAA,CAAa,QAAgB,MAAA,EACrC;AACI,IAAA,IAAI,MAAA,KAAW,KAAK,MAAA,EACpB;AACI,MAAA,IAAA,CAAK,cAAc,MAAM,CAAA;AAAA,IAC7B;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAc,MAAA,EACtB;AACI,IAAA,IAAI,CAAC,MAAA,CAAO,cAAA,IAAkB,MAAA,KAAW,KAAK,MAAA,EAC9C;AACI,MAAA,MAAA,CAAO,MAAA,GAAS,IAAA;AAChB,MAAA,IAAA,CAAK,cAAc,MAAM,CAAA;AAAA,IAC7B;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,WAAA,CAAY,WAA6B,MAAA,EACjD;AACI,IAAA,IAAI,WAAW,IAAA,CAAK,MAAA,IAAU,MAAA,CAAO,UAAA,KAAe,KAAK,UAAA,EACzD;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,MAAA,GAAS,IAAI,GAAA,CAAI,SAAA,CAAU,YAAY,CAAA;AAC7C,IAAA,MAAM,SAAS,IAAI,GAAA,CAAI,SAAA,CAAU,OAAA,IAAW,EAAE,CAAA;AAC9C,IAAA,MAAM,QAAA,GAAW,OAAO,IAAA,CAAK,IAAA,CAAK,QAAS,CAAA,CAAE,GAAA,CAAI,cAAc,CAAA,CAAE,MAAA,CAAO,UAAQ,CAAC,MAAA,CAAO,IAAI,IAAI,CAAA,IAAK,CAAC,MAAA,CAAO,GAAA,CAAI,IAAI,CAAC,CAAA;AAEtH,IAAA,IAAA,CAAK,YAAA,CAAa,QAAQ,MAAM,CAAA;AAEhC,IAAA,IAAI,SAAA,CAAU,OAAA,IAAW,QAAA,CAAS,MAAA,KAAW,CAAA,EAC7C;AACI,MAAA,IAAA,CAAK,gBAAA,CAAiB,OAAO,KAAK,CAAA;AAAA,IACtC,CAAA,MAEA;AACI,MAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,KAAK,CAAA;AAAA,IAC7B;AAAA,EACJ;AAAA;AAAA,EAGQ,YAAA,CAAa,QAAqB,MAAA,EAC1C;AACI,IAAA,KAAA,MAAW,EAAA,IAAM,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,QAAS,CAAA,EAC3C;AACI,MAAA,MAAM,IAAA,GAAO,KAAK,IAAA,CAAK,EAAA,EAAI,KAAK,KAAA,CAAM,EAAA,EAAI,MAAA,CAAO,KAAK,CAAC,CAAA;AAEvD,MAAA,IAAI,MAAA,CAAO,GAAA,CAAI,cAAA,CAAe,EAAE,CAAC,KAAK,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA,EAC3D;AACI,QAAA,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAA,EAAM,MAAA,CAAO,UAAU,CAAA;AAAA,MAC7C;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA,EAGQ,QAAQ,KAAA,EAChB;AACI,IAAA,IAAA,CAAK,YAAA,CAAa,OAAO,IAAA,CAAK,GAAA,EAAK,CAAA,CAAE,KAAA,CAAM,KAAK,WAAW,CAAA;AAC3D,IAAA,IAAA,CAAK,iBAAiB,KAAK,CAAA;AAAA,EAC/B;AAAA;AAAA,EAGQ,iBAAiB,KAAA,EACzB;AACI,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,KAAA,CAAM,KAAK,IAAA,CAAK,EAAA,EAAI,GAAG,CAAA,EAAG,KAAA,EAAO,IAAI,UAAU,CAAA;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,OAAA,CAAQ,EAAA,EAAY,KAAA,EAAkB,MAAA,EAC9C;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,IAAA,EAAM,KAAA,EAAO,MAAA,CAAO,UAAU,CAAA;AAAA,IAC7C;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 when its status, value or data changes, so\n * `current()` returns the same object until then: `useSyncExternalStore`\n * compares by reference and would re-render forever on a fresh object per\n * call. A newer `at` alone advances the slot's ordering stamp, not the entry.\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: an arrival older than the slot's stamp —\n * the newest `at` seen — is dropped. An equal `at` is by design the same\n * value (one read stamps one `at`), so it is not older and `changes` decides.\n */\nfunction isOlder(stamp: string | undefined, arrival: StateFrame): boolean\n{\n return stamp !== undefined && arrival.at < stamp;\n}\n\n/**\n * Whether an arrival says something the entry does not. An entry marked\n * revoked before any value arrived has no `at`: anything changes it.\n */\nfunction changes(current: StateEntry | undefined, arrival: StateFrame): boolean\n{\n return current?.at === undefined || !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\n /**\n * The newest `at` seen per slot, kept apart from the entry: an arrival\n * that changes nothing still orders the ones after it, while the entry's\n * `at` stays the `at` of the read that last changed the value.\n */\n private readonly stamps = new Map<string, string>();\n\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 a changed status, value or data.\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 const ordered = !isOlder(this.stamps.get(slot), frame);\n\n if (ordered)\n {\n this.stamps.set(slot, frame.at);\n }\n\n if (ordered && changes(current, frame))\n {\n this.replace(slot, entryOf(frame, status));\n }\n else if (current!.status !== status)\n {\n // Not applied, 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 { ConnectedPayload, 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 ConnectionListener,\n ConnectionStatus,\n StateClient,\n StateClientConfig,\n StateClientInitial,\n StateEntry,\n StateFrame,\n StateListener,\n} from './client-types';\n\nexport type {\n AnyStateRouter,\n ConnectionListener,\n ConnectionStatus,\n InferStateData,\n StateCatalog,\n StateFrameOf,\n InferStateInput,\n InferStateKey,\n InferStateValue,\n StateClient,\n StateClientConfig,\n StateClientInitial,\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/**\n * The stream as one `sse.subscribe` opened it; `generation` names its current\n * connection. `closedByClient`: the client closed it itself, so its `onClose`\n * is no failure; `failed`: the SSE client gave up on it.\n */\ninterface Stream\n{\n query: StateKeyFields;\n text: string;\n generation: number;\n closedByClient: boolean;\n failed: boolean;\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\nconst CONNECTED: ConnectionStatus = Object.freeze({ status: 'connected' });\nconst FAILED: ConnectionStatus = Object.freeze({ status: 'failed' });\nconst STARTING: ConnectionStatus = Object.freeze({ status: 'reconnecting', attempt: 0 });\n\nfunction reconnecting(attempt: number): ConnectionStatus\n{\n return attempt === 0 ? STARTING : Object.freeze({ status: 'reconnecting', attempt });\n}\n\nfunction sameStatus(a: ConnectionStatus, b: ConnectionStatus): boolean\n{\n return a.status === b.status && (a as { attempt?: number }).attempt === (b as { attempt?: number }).attempt;\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/** The wire name of a state's SSE event, as the server derives it. */\nfunction stateEventName(id: string): string\n{\n return `state.${id}.changed`;\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 — or takes it from\n * `initial`, the snapshot the server rendered with; the stream then\n * requests every state event — the server's `grant` can only activate an\n * event the connection requested. The router type parameter is what types\n * `initial.snapshot`; without it the client is untyped, as before.\n */\nexport function createStateClient<TStates extends AnyStateRouter>(config: StateClientConfig<TStates>): 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 private status = STARTING;\n private readonly connectionListeners = new Set<ConnectionListener>();\n private readonly onOnline = (): void => this.retry();\n\n /** The retries of the first snapshot since its last start, against `maxRetries`. */\n private retries = 0;\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 if (config.initial)\n {\n this.hydrate(config.initial);\n }\n\n // Absent in Node and in a server render.\n if (typeof globalThis.addEventListener === 'function')\n {\n globalThis.addEventListener('online', this.onOnline);\n }\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 connection(): ConnectionStatus\n {\n return this.status;\n }\n\n subscribeConnection(listener: ConnectionListener): () => void\n {\n // One wrapper per subscription: the same listener subscribed twice is two subscriptions.\n const notify: ConnectionListener = status => listener(status);\n this.connectionListeners.add(notify);\n\n return () =>\n {\n this.connectionListeners.delete(notify);\n };\n }\n\n /**\n * Restart what gave up or is still retrying, now: the first snapshot\n * while the manifest is unknown, else the stream — a new `sse.subscribe`,\n * whose attempt count starts at 0. Nothing to do when connected or with\n * nothing watched — so after `close()`, until a new `subscribe`.\n */\n retry(): void\n {\n if (this.status === CONNECTED || this.watched.size === 0)\n {\n return;\n }\n\n if (this.manifest)\n {\n this.openStream(this.streamQuery());\n }\n else\n {\n this.rereadManifest();\n }\n }\n\n close(): void\n {\n this.lifetime++;\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.retries = 0;\n this.closeStream();\n this.watched.clear();\n this.store.clearListeners();\n this.connectionListeners.clear();\n this.status = STARTING;\n\n if (typeof globalThis.removeEventListener === 'function')\n {\n globalThis.removeEventListener('online', this.onOnline);\n }\n }\n\n /** The status is `next` from now; each connection listener hears it, a throwing one logged. */\n private setConnection(next: ConnectionStatus): void\n {\n if (sameStatus(next, this.status))\n {\n return;\n }\n\n this.status = next;\n\n for (const listener of [...this.connectionListeners])\n {\n try\n {\n listener(next);\n }\n catch (error)\n {\n console.warn('[state] A connection listener threw:', error);\n }\n }\n }\n\n /**\n * Start from the snapshot the server rendered with: its manifest, then\n * each state present, as a read snapshot applies them, under generation 0\n * — no connection yet. Nothing is marked `'revoked'`: nothing is watched,\n * and an omission in the render's snapshot is not a denial by the stream.\n */\n private hydrate(initial: StateClientInitial): void\n {\n const snapshot = initial.snapshot as Snapshot;\n this.installManifest(snapshot.manifest);\n this.applyPresent(snapshot, initial.query ?? {}, this.tag());\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, generation: number): void\n {\n if ((this.store.revokedBy(slot) ?? Infinity) < generation)\n {\n this.store.allow(slot);\n }\n\n this.store.apply(slot, frame);\n }\n\n /**\n * A state present is served, under the generation rule of `serve`. A state\n * the manifest does not describe, or a keyed one whose key fields the query\n * lacks, is skipped: the server never serves either, but a hand-built\n * `initial` might.\n */\n private applyPresent(snapshot: Snapshot, query: StateKeyFields, tag: ReadTag): void\n {\n for (const [id, result] of Object.entries(snapshot.states))\n {\n if (this.manifest![id]?.key.every(field => field in query))\n {\n this.serve(this.slot(id, this.keyIn(id, query)), { at: snapshot.at, ...result }, tag.generation);\n }\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 // A removal that leaves subscribers keeps a failed client failed; the last removal closes and resets.\n if (delta === 1 || this.watched.size === 0 || this.status !== FAILED)\n {\n this.queueSync();\n }\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 * A client built with `initial` knows it from the start. With nothing\n * watched the stream closes, the retry count starts over for the next\n * wait, and the status is the start value: no stream, nothing answered.\n */\n private sync(): void\n {\n if (this.watched.size === 0)\n {\n this.retries = 0;\n this.closeStream();\n this.setConnection(STARTING);\n\n return;\n }\n\n if (!this.manifest)\n {\n this.awaitManifest();\n\n return;\n }\n\n // A stream the SSE client gave up on counts as absent.\n const query = this.streamQuery();\n\n if (this.stream?.failed || 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()`, when nothing is watched (`sync` closes instead), or after\n * `maxRetries`; a wait started after that last one restarts the count.\n */\n private awaitManifest(): void\n {\n const lifetime = this.lifetime;\n\n if (this.waitingLifetime === lifetime)\n {\n return;\n }\n\n if (this.status === FAILED)\n {\n this.retries = 0;\n this.setConnection(STARTING);\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 /**\n * Read the first snapshot again after `retryDelay`, as the SSE client\n * reconnects a dropped stream — or, with `maxRetries` spent, give up:\n * `'failed'`, no timer, no wait.\n */\n private retryLater(): void\n {\n const maxRetries = this.config.maxRetries ?? 0;\n\n if (maxRetries > 0 && this.retries >= maxRetries)\n {\n this.waitingLifetime = undefined;\n this.setConnection(FAILED);\n\n return;\n }\n\n this.retries++;\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 * `retry()` before the manifest: the count starts over and the first\n * snapshot is read now, its pending timer cleared. A read in flight is\n * already the retry; it is not doubled.\n */\n private rereadManifest(): void\n {\n this.retries = 0;\n\n if (this.waitingLifetime === this.lifetime && this.retryTimer === undefined)\n {\n return;\n }\n\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.setConnection(STARTING);\n this.resync();\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 * Unless connected, the status is `'reconnecting'` 0 until it opens: a\n * new subscription counts its attempts from 0. A reopen on a key change\n * while connected stays `'connected'`.\n */\n private openStream(query: StateKeyFields): void\n {\n this.closeStream();\n\n if (this.status !== CONNECTED)\n {\n this.setConnection(STARTING);\n }\n\n const ids = Object.keys(this.manifest!);\n const stream: Stream = { query, text: stableKey(query), generation: 0, closedByClient: false, failed: false, close: () => undefined };\n stream.close = this.config.sse.subscribe({\n events: ids.map(stateEventName),\n handlers: Object.fromEntries(ids.map(id => [stateEventName(id), (frame: WireFrame) => this.onFrame(id, frame, stream)])),\n query,\n onOpen: () =>\n {\n stream.generation = ++this.generation;\n this.streamStatus(stream, CONNECTED);\n },\n onReconnect: attempt => this.streamStatus(stream, reconnecting(attempt)),\n onClose: () => this.onStreamClose(stream),\n onConnected: connected => this.onConnected(connected, stream),\n onSubscriptionChange: change => this.onSubscriptionChange(change, stream),\n });\n\n this.stream = stream;\n }\n\n /** Flagged first: the SSE client fires `onClose` on this teardown too, and it is no failure. */\n private closeStream(): void\n {\n if (this.stream)\n {\n this.stream.closedByClient = true;\n this.stream.close();\n this.stream = undefined;\n }\n }\n\n /** The status of a callback of `stream`, when no other stream replaced it. */\n private streamStatus(stream: Stream, status: ConnectionStatus): void\n {\n if (stream === this.stream)\n {\n this.setConnection(status);\n }\n }\n\n /**\n * The SSE client fires `onClose` on every teardown; one the client did\n * not start is the reconnect attempts exhausted: `'failed'`, and the\n * stream counts as absent for the next `sync`.\n */\n private onStreamClose(stream: Stream): void\n {\n if (!stream.closedByClient && stream === this.stream)\n {\n stream.failed = true;\n this.setConnection(FAILED);\n }\n }\n\n /**\n * Every connection — the first, a reconnect, a reopen on a key change —\n * must cover the frames sent while no stream was open. The server says in\n * `connected` which states it served their current frame right after it;\n * the snapshot is read only when one it did not serve is neither denied\n * nor served — or when the server predates `initial`. A denied state the\n * client watches is `'revoked'`, as an omission from that snapshot would\n * be. A connection already replaced is ignored.\n */\n private onConnected(connected: ConnectedPayload, stream: Stream): void\n {\n if (stream !== this.stream || stream.generation !== this.generation)\n {\n return;\n }\n\n const denied = new Set(connected.deniedEvents);\n const served = new Set(connected.initial ?? []);\n const unserved = Object.keys(this.manifest!).map(stateEventName).filter(name => !denied.has(name) && !served.has(name));\n\n this.revokeDenied(denied, stream);\n\n if (connected.initial && unserved.length === 0)\n {\n this.readShadowedKeys(stream.query);\n }\n else\n {\n this.refresh(stream.query);\n }\n }\n\n /** Each watched state the connection denies, on the key it carries, marked `'revoked'` by it. */\n private revokeDenied(denied: Set<string>, stream: Stream): void\n {\n for (const id of Object.keys(this.manifest!))\n {\n const slot = this.slot(id, this.keyIn(id, stream.query));\n\n if (denied.has(stateEventName(id)) && this.watched.has(slot))\n {\n this.store.revoke(slot, stream.generation);\n }\n }\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 this.readShadowedKeys(query);\n }\n\n /** One read for each subscribed key the query shadows: the stream serves one key per field. */\n private readShadowedKeys(query: StateKeyFields): void\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.generation);\n }\n\n /**\n * A frame for a key nobody here subscribed is dropped — the server's filter\n * is the first guard, this the second. A frame is served under its\n * connection's generation: the server sends one only on a connection that\n * allows the state, so a newer connection's frame lifts an older revoke.\n */\n private onFrame(id: string, frame: WireFrame, stream: Stream): void\n {\n const slot = this.slot(id, frame.key);\n\n if (this.watched.has(slot))\n {\n this.serve(slot, frame, stream.generation);\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"]}
1
+ {"version":3,"sources":["../../src/state/client-store.ts","../../src/state/client.ts"],"names":["notify"],"mappings":"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","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 when its status, value or data changes, so\n * `current()` returns the same object until then: `useSyncExternalStore`\n * compares by reference and would re-render forever on a fresh object per\n * call. A newer `at` alone advances the slot's ordering stamp, not the entry.\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: an arrival older than the slot's stamp —\n * the newest `at` seen — is dropped. An equal `at` is by design the same\n * value (one read stamps one `at`), so it is not older and `changes` decides.\n */\nfunction isOlder(stamp: string | undefined, arrival: StateFrame): boolean\n{\n return stamp !== undefined && arrival.at < stamp;\n}\n\n/**\n * Whether an arrival says something the entry does not. An entry marked\n * revoked before any value arrived has no `at`: anything changes it.\n */\nfunction changes(current: StateEntry | undefined, arrival: StateFrame): boolean\n{\n return current?.at === undefined || !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\n /**\n * The newest `at` seen per slot, kept apart from the entry: an arrival\n * that changes nothing still orders the ones after it, while the entry's\n * `at` stays the `at` of the read that last changed the value.\n */\n private readonly stamps = new Map<string, string>();\n\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 a changed status, value or data.\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 const ordered = !isOlder(this.stamps.get(slot), frame);\n\n if (ordered)\n {\n this.stamps.set(slot, frame.at);\n }\n\n if (ordered && changes(current, frame))\n {\n this.replace(slot, entryOf(frame, status));\n }\n else if (current!.status !== status)\n {\n // Not applied, 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 { ConnectedPayload, 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 ConnectionListener,\n ConnectionStatus,\n StateClient,\n StateClientConfig,\n StateClientInitial,\n StateEntry,\n StateFrame,\n StateListener,\n} from './client-types';\n\nexport type {\n AnyStateRouter,\n ConnectionListener,\n ConnectionStatus,\n InferStateData,\n StateCatalog,\n StateFrameOf,\n InferStateInput,\n InferStateKey,\n InferStateValue,\n StateClient,\n StateClientConfig,\n StateClientInitial,\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/**\n * The stream as one `sse.subscribe` opened it; `generation` names its current\n * connection. `closedByClient`: the client closed it itself, so its `onClose`\n * is no failure; `failed`: the SSE client gave up on it.\n */\ninterface Stream\n{\n query: StateKeyFields;\n text: string;\n generation: number;\n closedByClient: boolean;\n failed: boolean;\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\nconst CONNECTED: ConnectionStatus = Object.freeze({ status: 'connected' });\nconst FAILED: ConnectionStatus = Object.freeze({ status: 'failed' });\nconst UNAUTHORIZED: ConnectionStatus = Object.freeze({ status: 'closed', reason: 'unauthorized' });\nconst STARTING: ConnectionStatus = Object.freeze({ status: 'reconnecting', attempt: 0 });\n\nfunction reconnecting(attempt: number): ConnectionStatus\n{\n return attempt === 0 ? STARTING : Object.freeze({ status: 'reconnecting', attempt });\n}\n\nfunction sameStatus(a: ConnectionStatus, b: ConnectionStatus): boolean\n{\n return a.status === b.status && (a as { attempt?: number }).attempt === (b as { attempt?: number }).attempt;\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/** The wire name of a state's SSE event, as the server derives it. */\nfunction stateEventName(id: string): string\n{\n return `state.${id}.changed`;\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 — or takes it from\n * `initial`, the snapshot the server rendered with; the stream then\n * requests every state event — the server's `grant` can only activate an\n * event the connection requested. The router type parameter is what types\n * `initial.snapshot`; without it the client is untyped, as before.\n */\nexport function createStateClient<TStates extends AnyStateRouter>(config: StateClientConfig<TStates>): 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 private status = STARTING;\n private readonly connectionListeners = new Set<ConnectionListener>();\n // A refused session is not a network blip: coming back online does not reopen it.\n private readonly onOnline = (): void =>\n {\n if (this.status !== UNAUTHORIZED)\n {\n this.retry();\n }\n };\n\n /** The retries of the first snapshot since its last start, against `maxRetries`. */\n private retries = 0;\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 if (config.initial)\n {\n this.hydrate(config.initial);\n }\n\n // Absent in Node and in a server render.\n if (typeof globalThis.addEventListener === 'function')\n {\n globalThis.addEventListener('online', this.onOnline);\n }\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 connection(): ConnectionStatus\n {\n return this.status;\n }\n\n subscribeConnection(listener: ConnectionListener): () => void\n {\n // One wrapper per subscription: the same listener subscribed twice is two subscriptions.\n const notify: ConnectionListener = status => listener(status);\n this.connectionListeners.add(notify);\n\n return () =>\n {\n this.connectionListeners.delete(notify);\n };\n }\n\n /**\n * Restart what gave up or is still retrying, now: the first snapshot\n * while the manifest is unknown, else the stream — a new `sse.subscribe`,\n * whose attempt count starts at 0. Nothing to do when connected or with\n * nothing watched — so after `close()`, until a new `subscribe`.\n */\n retry(): void\n {\n if (this.status === CONNECTED || this.watched.size === 0)\n {\n return;\n }\n\n if (this.manifest)\n {\n this.openStream(this.streamQuery());\n }\n else\n {\n this.rereadManifest();\n }\n }\n\n close(): void\n {\n this.lifetime++;\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.retries = 0;\n this.closeStream();\n this.watched.clear();\n this.store.clearListeners();\n this.connectionListeners.clear();\n this.status = STARTING;\n\n if (typeof globalThis.removeEventListener === 'function')\n {\n globalThis.removeEventListener('online', this.onOnline);\n }\n }\n\n /** The status is `next` from now; each connection listener hears it, a throwing one logged. */\n private setConnection(next: ConnectionStatus): void\n {\n if (sameStatus(next, this.status))\n {\n return;\n }\n\n this.status = next;\n\n for (const listener of [...this.connectionListeners])\n {\n try\n {\n listener(next);\n }\n catch (error)\n {\n console.warn('[state] A connection listener threw:', error);\n }\n }\n }\n\n /**\n * Start from the snapshot the server rendered with: its manifest, then\n * each state present, as a read snapshot applies them, under generation 0\n * — no connection yet. Nothing is marked `'revoked'`: nothing is watched,\n * and an omission in the render's snapshot is not a denial by the stream.\n */\n private hydrate(initial: StateClientInitial): void\n {\n const snapshot = initial.snapshot as Snapshot;\n this.installManifest(snapshot.manifest);\n this.applyPresent(snapshot, initial.query ?? {}, this.tag());\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 /** The slot of state `id` on the key `query` carries; `undefined` when the query lacks one of its key fields. */\n private carriedSlot(id: string, query: StateKeyFields): string | undefined\n {\n return this.manifest![id].key.every(field => field in query) ? this.slot(id, this.keyIn(id, query)) : 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, generation: number): void\n {\n if ((this.store.revokedBy(slot) ?? Infinity) < generation)\n {\n this.store.allow(slot);\n }\n\n this.store.apply(slot, frame);\n }\n\n /**\n * A state present is served, under the generation rule of `serve`. A state\n * the manifest does not describe, or a keyed one whose key fields the query\n * lacks, is skipped: the server never serves either, but a hand-built\n * `initial` might.\n */\n private applyPresent(snapshot: Snapshot, query: StateKeyFields, tag: ReadTag): void\n {\n for (const [id, result] of Object.entries(snapshot.states))\n {\n if (this.manifest![id]?.key.every(field => field in query))\n {\n this.serve(this.slot(id, this.keyIn(id, query)), { at: snapshot.at, ...result }, tag.generation);\n }\n }\n }\n\n /**\n * Every state the snapshot omits whose key the query carried (or a keyless\n * one), watched or not, is not served to this subject: `'revoked'` on that\n * key. Not when a newer connection exists, nor when a grant came after the\n * read started. Every watched slot the query carries is one of these.\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 id of Object.keys(this.manifest!))\n {\n const slot = this.carriedSlot(id, query);\n const grantedSince = slot !== undefined && (this.grants.get(slot) ?? 0) > tag.tick;\n\n if (slot !== undefined && !(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 // A removal that leaves subscribers keeps a failed client failed; the last removal closes and resets.\n if (delta === 1 || this.watched.size === 0 || this.status !== FAILED)\n {\n this.queueSync();\n }\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 * A client built with `initial` knows it from the start. With nothing\n * watched the stream closes, the retry count starts over for the next\n * wait, and the status is the start value: no stream, nothing answered.\n */\n private sync(): void\n {\n if (this.watched.size === 0)\n {\n this.retries = 0;\n this.closeStream();\n this.setConnection(STARTING);\n\n return;\n }\n\n if (!this.manifest)\n {\n this.awaitManifest();\n\n return;\n }\n\n // A refused session stays closed until the app's own `retry()`.\n if (this.status === UNAUTHORIZED)\n {\n return;\n }\n\n // A stream the SSE client gave up on counts as absent.\n const query = this.streamQuery();\n\n if (this.stream?.failed || 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()`, when nothing is watched (`sync` closes instead), or after\n * `maxRetries`; a wait started after that last one restarts the count.\n */\n private awaitManifest(): void\n {\n const lifetime = this.lifetime;\n\n if (this.waitingLifetime === lifetime)\n {\n return;\n }\n\n if (this.status === FAILED)\n {\n this.retries = 0;\n this.setConnection(STARTING);\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 /**\n * Read the first snapshot again after `retryDelay`, as the SSE client\n * reconnects a dropped stream — or, with `maxRetries` spent, give up:\n * `'failed'`, no timer, no wait.\n */\n private retryLater(): void\n {\n const maxRetries = this.config.maxRetries ?? 0;\n\n if (maxRetries > 0 && this.retries >= maxRetries)\n {\n this.waitingLifetime = undefined;\n this.setConnection(FAILED);\n\n return;\n }\n\n this.retries++;\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 * `retry()` before the manifest: the count starts over and the first\n * snapshot is read now, its pending timer cleared. A read in flight is\n * already the retry; it is not doubled.\n */\n private rereadManifest(): void\n {\n this.retries = 0;\n\n if (this.waitingLifetime === this.lifetime && this.retryTimer === undefined)\n {\n return;\n }\n\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.setConnection(STARTING);\n this.resync();\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 * Unless connected, the status is `'reconnecting'` 0 until it opens: a\n * new subscription counts its attempts from 0. A reopen on a key change\n * while connected stays `'connected'`.\n */\n private openStream(query: StateKeyFields): void\n {\n this.closeStream();\n\n if (this.status !== CONNECTED)\n {\n this.setConnection(STARTING);\n }\n\n const ids = Object.keys(this.manifest!);\n const stream: Stream = { query, text: stableKey(query), generation: 0, closedByClient: false, failed: false, close: () => undefined };\n stream.close = this.config.sse.subscribe({\n events: ids.map(stateEventName),\n handlers: Object.fromEntries(ids.map(id => [stateEventName(id), (frame: WireFrame) => this.onFrame(id, frame, stream)])),\n query,\n onOpen: () =>\n {\n stream.generation = ++this.generation;\n this.streamStatus(stream, CONNECTED);\n },\n onReconnect: attempt => this.streamStatus(stream, reconnecting(attempt)),\n onClose: reason => this.onStreamClose(stream, reason),\n onConnected: connected => this.onConnected(connected, stream),\n onSubscriptionChange: change => this.onSubscriptionChange(change, stream),\n });\n\n this.stream = stream;\n }\n\n /** Flagged first: the SSE client fires `onClose` on this teardown too, and it is no failure. */\n private closeStream(): void\n {\n if (this.stream)\n {\n this.stream.closedByClient = true;\n this.stream.close();\n this.stream = undefined;\n }\n }\n\n /** The status of a callback of `stream`, when no other stream replaced it. */\n private streamStatus(stream: Stream, status: ConnectionStatus): void\n {\n if (stream === this.stream)\n {\n this.setConnection(status);\n }\n }\n\n /**\n * The SSE client fires `onClose` on every teardown; one the client did\n * not start is the reconnect attempts exhausted — `'failed'` — or the\n * token mint answered 401 — `'closed'` / `'unauthorized'`. Either way the\n * stream counts as absent for the next `sync`.\n */\n private onStreamClose(stream: Stream, reason?: 'unauthorized'): void\n {\n if (!stream.closedByClient && stream === this.stream)\n {\n stream.failed = true;\n this.setConnection(reason === 'unauthorized' ? UNAUTHORIZED : FAILED);\n }\n }\n\n /**\n * Every connection — the first, a reconnect, a reopen on a key change —\n * must cover the frames sent while no stream was open. The server says in\n * `connected` which states it served their current frame right after it;\n * the snapshot is read only when one it did not serve is neither denied\n * nor served — or when the server predates `initial`. A denied state whose\n * key the stream carries is `'revoked'`, watched or not, as an omission\n * from that snapshot would be. A connection already replaced is ignored.\n */\n private onConnected(connected: ConnectedPayload, stream: Stream): void\n {\n if (stream !== this.stream || stream.generation !== this.generation)\n {\n return;\n }\n\n const denied = new Set(connected.deniedEvents);\n const served = new Set(connected.initial ?? []);\n const unserved = Object.keys(this.manifest!).map(stateEventName).filter(name => !denied.has(name) && !served.has(name));\n\n this.revokeDenied(denied, stream);\n\n if (connected.initial && unserved.length === 0)\n {\n this.readShadowedKeys(stream.query);\n }\n else\n {\n this.refresh(stream.query);\n }\n }\n\n /**\n * Each state the connection denies, on the key its query carries, marked\n * `'revoked'` by it, watched or not. A keyed state denied because the query\n * lacks one of its key fields has no carried key, so nothing is marked.\n */\n private revokeDenied(denied: Set<string>, stream: Stream): void\n {\n for (const id of Object.keys(this.manifest!))\n {\n const slot = this.carriedSlot(id, stream.query);\n\n if (slot !== undefined && denied.has(stateEventName(id)))\n {\n this.store.revoke(slot, stream.generation);\n }\n }\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 this.readShadowedKeys(query);\n }\n\n /** One read for each subscribed key the query shadows: the stream serves one key per field. */\n private readShadowedKeys(query: StateKeyFields): void\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.generation);\n }\n\n /**\n * A frame on the key the stream's query carries is applied, watched or not,\n * so a later subscribe that does not reopen the stream is served the newest\n * value; a frame for any other key is dropped unless watched — the server's\n * filter is the first guard, this the second. A frame is served under its\n * connection's generation: the server sends one only on a connection that\n * allows the state, so a newer connection's frame lifts an older revoke.\n */\n private onFrame(id: string, frame: WireFrame, stream: Stream): void\n {\n const slot = this.slot(id, frame.key);\n\n if (slot === this.carriedSlot(id, stream.query) || this.watched.has(slot))\n {\n this.serve(slot, frame, stream.generation);\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"]}
@@ -32,7 +32,9 @@ type ServerState<TStates extends AnyStateRouter, Id extends StateId<TStates>> =
32
32
  declare function useServerState<TStates extends AnyStateRouter, Id extends StateId<TStates>>(client: StateClient<TStates>, id: Id, ...key: StateKeyArgs<TStates, Id>): ServerState<TStates, Id>;
33
33
  /**
34
34
  * The client's connection status, re-rendered on every change: `'connected'`,
35
- * `'reconnecting'` with the attempt, or `'failed'` until `client.retry()`.
35
+ * `'reconnecting'` with the attempt, `'failed'` until `client.retry()`, or
36
+ * `'closed'` with reason `'unauthorized'` once the token mint answered 401 —
37
+ * the status reaches the hook unchanged, and only `client.retry()` reopens it.
36
38
  *
37
39
  * @example
38
40
  * ```tsx
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/state/client-store.ts","../../src/state/react.tsx"],"names":[],"mappings":"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","file":"react.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 when its status, value or data changes, so\n * `current()` returns the same object until then: `useSyncExternalStore`\n * compares by reference and would re-render forever on a fresh object per\n * call. A newer `at` alone advances the slot's ordering stamp, not the entry.\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: an arrival older than the slot's stamp —\n * the newest `at` seen — is dropped. An equal `at` is by design the same\n * value (one read stamps one `at`), so it is not older and `changes` decides.\n */\nfunction isOlder(stamp: string | undefined, arrival: StateFrame): boolean\n{\n return stamp !== undefined && arrival.at < stamp;\n}\n\n/**\n * Whether an arrival says something the entry does not. An entry marked\n * revoked before any value arrived has no `at`: anything changes it.\n */\nfunction changes(current: StateEntry | undefined, arrival: StateFrame): boolean\n{\n return current?.at === undefined || !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\n /**\n * The newest `at` seen per slot, kept apart from the entry: an arrival\n * that changes nothing still orders the ones after it, while the entry's\n * `at` stays the `at` of the read that last changed the value.\n */\n private readonly stamps = new Map<string, string>();\n\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 a changed status, value or data.\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 const ordered = !isOlder(this.stamps.get(slot), frame);\n\n if (ordered)\n {\n this.stamps.set(slot, frame.at);\n }\n\n if (ordered && changes(current, frame))\n {\n this.replace(slot, entryOf(frame, status));\n }\n else if (current!.status !== status)\n {\n // Not applied, 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","import { useCallback, useMemo, useSyncExternalStore } from 'react';\n// Types come from the client entry, so react.d.ts refers to the one declaration\n// of StateClient there instead of carrying a copy of its own.\nimport type {\n AnyStateRouter,\n ConnectionStatus,\n StateCatalog,\n StateClient,\n StateEntry,\n StateFrame,\n StateFrameOf,\n StateId,\n StateKeyArgs,\n} from '@spfn/core/state/client';\nimport { parseKey, stableKey, type StateKeyFields } from './client-store';\n\ntype StateWrite<TStates extends AnyStateRouter, Id extends StateId<TStates>> =\n (input: StateCatalog<TStates>[Id]['input']) => Promise<StateFrameOf<TStates, Id>>;\n\n/**\n * What `useServerState` returns. `'loading'` until the client holds an entry —\n * during a server render too, unless the client was built with `initial`. `write` is typed only on a state that\n * declares one, bound to the key the hook was given.\n */\nexport type ServerState<TStates extends AnyStateRouter, Id extends StateId<TStates>> = {\n status: 'loading' | 'ready' | 'revoked';\n at?: string;\n value?: StateCatalog<TStates>[Id]['value'];\n data?: StateCatalog<TStates>[Id]['data'];\n} & ([StateCatalog<TStates>[Id]['input']] extends [never] ? {} : { write: StateWrite<TStates, Id> });\n\n/** The client as the hook drives it: the hook's own signature already checked id, key and input. */\ninterface LooseClient\n{\n subscribe(id: string, ...args: unknown[]): () => void;\n current(id: string, ...key: unknown[]): StateEntry | undefined;\n write(id: string, input: unknown, ...key: unknown[]): Promise<StateFrame>;\n}\n\nfunction keyArgs(keyText: string): [] | [StateKeyFields]\n{\n const key = parseKey(keyText);\n\n return key ? [key] : [];\n}\n\n/**\n * The entry as the hook returns it. `write` is always attached at runtime — the\n * type hides it on a state without one, whose client `write` rejects — so the\n * result never waits on the manifest.\n */\nfunction toServerState(entry: StateEntry | undefined, write: (input: unknown) => Promise<StateFrame>): object\n{\n if (!entry)\n {\n return { status: 'loading', write };\n }\n\n const { status, at, value, data } = entry;\n\n return { status, ...(at === undefined ? {} : { at, value }), ...(data === undefined ? {} : { data }), write };\n}\n\n/**\n * Subscribe a component to one state of a state client. The key is compared\n * by its fields, so a new object literal on every render does not resubscribe.\n *\n * @example\n * ```tsx\n * const consent = useServerState(state, 'consent');\n * const runtime = useServerState(state, 'runtime', { workspaceId });\n *\n * if (consent.status === 'ready' && consent.value === 'pending')\n * {\n * await consent.write({ termsVersion, marketing });\n * }\n * ```\n */\nexport function useServerState<TStates extends AnyStateRouter, Id extends StateId<TStates>>(\n client: StateClient<TStates>,\n id: Id,\n ...key: StateKeyArgs<TStates, Id>\n): ServerState<TStates, Id>\n{\n const loose = client as unknown as LooseClient;\n const keyText = stableKey(key[0] as StateKeyFields | undefined);\n const args = useMemo(() => keyArgs(keyText), [keyText]);\n\n const subscribe = useCallback((onChange: () => void) => loose.subscribe(id, ...args, onChange), [loose, id, args]);\n const getSnapshot = useCallback(() => loose.current(id, ...args), [loose, id, args]);\n // The server snapshot is the client's entry too: a client built with `initial`\n // renders its value on the server, and hydration on the browser matches it.\n const entry = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n const write = useCallback((input: unknown) => loose.write(id, input, ...args), [loose, id, args]);\n\n return useMemo(() => toServerState(entry, write), [entry, write]) as ServerState<TStates, Id>;\n}\n\n/** A server render opens nothing: its status is the one a client starts with. */\nconst SERVER_CONNECTION: ConnectionStatus = Object.freeze({ status: 'reconnecting', attempt: 0 });\n\nfunction serverConnection(): ConnectionStatus\n{\n return SERVER_CONNECTION;\n}\n\n/**\n * The client's connection status, re-rendered on every change: `'connected'`,\n * `'reconnecting'` with the attempt, or `'failed'` until `client.retry()`.\n *\n * @example\n * ```tsx\n * const connection = useConnectionStatus(state);\n *\n * if (connection.status === 'failed')\n * {\n * return <Banner onRetry={() => state.retry()}>The server is not answering.</Banner>;\n * }\n * ```\n */\nexport function useConnectionStatus(client: StateClient<any>): ConnectionStatus\n{\n const subscribe = useCallback((onChange: () => void) => client.subscribeConnection(onChange), [client]);\n const getSnapshot = useCallback(() => client.connection(), [client]);\n\n return useSyncExternalStore(subscribe, getSnapshot, serverConnection);\n}\n"]}
1
+ {"version":3,"sources":["../../src/state/client-store.ts","../../src/state/react.tsx"],"names":[],"mappings":"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","file":"react.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 when its status, value or data changes, so\n * `current()` returns the same object until then: `useSyncExternalStore`\n * compares by reference and would re-render forever on a fresh object per\n * call. A newer `at` alone advances the slot's ordering stamp, not the entry.\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: an arrival older than the slot's stamp —\n * the newest `at` seen — is dropped. An equal `at` is by design the same\n * value (one read stamps one `at`), so it is not older and `changes` decides.\n */\nfunction isOlder(stamp: string | undefined, arrival: StateFrame): boolean\n{\n return stamp !== undefined && arrival.at < stamp;\n}\n\n/**\n * Whether an arrival says something the entry does not. An entry marked\n * revoked before any value arrived has no `at`: anything changes it.\n */\nfunction changes(current: StateEntry | undefined, arrival: StateFrame): boolean\n{\n return current?.at === undefined || !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\n /**\n * The newest `at` seen per slot, kept apart from the entry: an arrival\n * that changes nothing still orders the ones after it, while the entry's\n * `at` stays the `at` of the read that last changed the value.\n */\n private readonly stamps = new Map<string, string>();\n\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 a changed status, value or data.\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 const ordered = !isOlder(this.stamps.get(slot), frame);\n\n if (ordered)\n {\n this.stamps.set(slot, frame.at);\n }\n\n if (ordered && changes(current, frame))\n {\n this.replace(slot, entryOf(frame, status));\n }\n else if (current!.status !== status)\n {\n // Not applied, 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","import { useCallback, useMemo, useSyncExternalStore } from 'react';\n// Types come from the client entry, so react.d.ts refers to the one declaration\n// of StateClient there instead of carrying a copy of its own.\nimport type {\n AnyStateRouter,\n ConnectionStatus,\n StateCatalog,\n StateClient,\n StateEntry,\n StateFrame,\n StateFrameOf,\n StateId,\n StateKeyArgs,\n} from '@spfn/core/state/client';\nimport { parseKey, stableKey, type StateKeyFields } from './client-store';\n\ntype StateWrite<TStates extends AnyStateRouter, Id extends StateId<TStates>> =\n (input: StateCatalog<TStates>[Id]['input']) => Promise<StateFrameOf<TStates, Id>>;\n\n/**\n * What `useServerState` returns. `'loading'` until the client holds an entry —\n * during a server render too, unless the client was built with `initial`. `write` is typed only on a state that\n * declares one, bound to the key the hook was given.\n */\nexport type ServerState<TStates extends AnyStateRouter, Id extends StateId<TStates>> = {\n status: 'loading' | 'ready' | 'revoked';\n at?: string;\n value?: StateCatalog<TStates>[Id]['value'];\n data?: StateCatalog<TStates>[Id]['data'];\n} & ([StateCatalog<TStates>[Id]['input']] extends [never] ? {} : { write: StateWrite<TStates, Id> });\n\n/** The client as the hook drives it: the hook's own signature already checked id, key and input. */\ninterface LooseClient\n{\n subscribe(id: string, ...args: unknown[]): () => void;\n current(id: string, ...key: unknown[]): StateEntry | undefined;\n write(id: string, input: unknown, ...key: unknown[]): Promise<StateFrame>;\n}\n\nfunction keyArgs(keyText: string): [] | [StateKeyFields]\n{\n const key = parseKey(keyText);\n\n return key ? [key] : [];\n}\n\n/**\n * The entry as the hook returns it. `write` is always attached at runtime — the\n * type hides it on a state without one, whose client `write` rejects — so the\n * result never waits on the manifest.\n */\nfunction toServerState(entry: StateEntry | undefined, write: (input: unknown) => Promise<StateFrame>): object\n{\n if (!entry)\n {\n return { status: 'loading', write };\n }\n\n const { status, at, value, data } = entry;\n\n return { status, ...(at === undefined ? {} : { at, value }), ...(data === undefined ? {} : { data }), write };\n}\n\n/**\n * Subscribe a component to one state of a state client. The key is compared\n * by its fields, so a new object literal on every render does not resubscribe.\n *\n * @example\n * ```tsx\n * const consent = useServerState(state, 'consent');\n * const runtime = useServerState(state, 'runtime', { workspaceId });\n *\n * if (consent.status === 'ready' && consent.value === 'pending')\n * {\n * await consent.write({ termsVersion, marketing });\n * }\n * ```\n */\nexport function useServerState<TStates extends AnyStateRouter, Id extends StateId<TStates>>(\n client: StateClient<TStates>,\n id: Id,\n ...key: StateKeyArgs<TStates, Id>\n): ServerState<TStates, Id>\n{\n const loose = client as unknown as LooseClient;\n const keyText = stableKey(key[0] as StateKeyFields | undefined);\n const args = useMemo(() => keyArgs(keyText), [keyText]);\n\n const subscribe = useCallback((onChange: () => void) => loose.subscribe(id, ...args, onChange), [loose, id, args]);\n const getSnapshot = useCallback(() => loose.current(id, ...args), [loose, id, args]);\n // The server snapshot is the client's entry too: a client built with `initial`\n // renders its value on the server, and hydration on the browser matches it.\n const entry = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n const write = useCallback((input: unknown) => loose.write(id, input, ...args), [loose, id, args]);\n\n return useMemo(() => toServerState(entry, write), [entry, write]) as ServerState<TStates, Id>;\n}\n\n/** A server render opens nothing: its status is the one a client starts with. */\nconst SERVER_CONNECTION: ConnectionStatus = Object.freeze({ status: 'reconnecting', attempt: 0 });\n\nfunction serverConnection(): ConnectionStatus\n{\n return SERVER_CONNECTION;\n}\n\n/**\n * The client's connection status, re-rendered on every change: `'connected'`,\n * `'reconnecting'` with the attempt, `'failed'` until `client.retry()`, or\n * `'closed'` with reason `'unauthorized'` once the token mint answered 401 —\n * the status reaches the hook unchanged, and only `client.retry()` reopens it.\n *\n * @example\n * ```tsx\n * const connection = useConnectionStatus(state);\n *\n * if (connection.status === 'failed')\n * {\n * return <Banner onRetry={() => state.retry()}>The server is not answering.</Banner>;\n * }\n * ```\n */\nexport function useConnectionStatus(client: StateClient<any>): ConnectionStatus\n{\n const subscribe = useCallback((onChange: () => void) => client.subscribeConnection(onChange), [client]);\n const getSnapshot = useCallback(() => client.connection(), [client]);\n\n return useSyncExternalStore(subscribe, getSnapshot, serverConnection);\n}\n"]}