@unicitylabs/sphere-sdk 0.14.11-dev.2 → 0.14.11-dev.4

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.
@@ -12667,6 +12667,12 @@ var InventoryView = class {
12667
12667
  release(tokenId) {
12668
12668
  if (this.inFlight.delete(tokenId)) this.deps.emit("inventory:updated");
12669
12669
  }
12670
+ /** Release a whole set as ONE change (per-token emitted 54 events per send). */
12671
+ releaseMany(tokenIds) {
12672
+ let changed = false;
12673
+ for (const tokenId of tokenIds) if (this.inFlight.delete(tokenId)) changed = true;
12674
+ if (changed) this.deps.emit("inventory:updated");
12675
+ }
12670
12676
  pinned(tokenId) {
12671
12677
  return this.deps.isPinned?.(tokenId) ?? false;
12672
12678
  }
@@ -12919,16 +12925,28 @@ var Receive = class {
12919
12925
  this.unsubscribeWake?.();
12920
12926
  this.unsubscribeWake = null;
12921
12927
  }
12922
- /** Ask for a mirror refresh at most once per REFRESH_INTERVAL_MS. */
12923
- maybeRefresh(deps) {
12928
+ /**
12929
+ * Flush what has been accepted, then ask for a mirror refresh — at most once
12930
+ * per REFRESH_INTERVAL_MS.
12931
+ *
12932
+ * The flush is not incidental. A `claimed` ack is what materializes a token
12933
+ * into SERVER inventory, and acks otherwise sit queued until ACK_BATCH_SIZE
12934
+ * (200) — so for any ordinary drain, including the 54-token case this exists
12935
+ * for, refreshing before the flush would query an inventory holding none of
12936
+ * the tokens just accepted, and the balance would still only move at the end.
12937
+ * Ordering is preserved: store() still precedes the ack (§5.7).
12938
+ */
12939
+ async maybeRefresh(deps, pending, epochOf) {
12924
12940
  if (deps.refreshView === void 0) return;
12925
12941
  const now = deps.now?.() ?? Date.now();
12926
12942
  if (now - this.lastRefreshAt < REFRESH_INTERVAL_MS) return;
12927
12943
  this.lastRefreshAt = now;
12944
+ await flushAcks(deps, pending, epochOf);
12928
12945
  deps.refreshView();
12929
12946
  }
12930
12947
  async doDrain() {
12931
12948
  const deps = this.deps;
12949
+ this.lastRefreshAt = deps.now?.() ?? Date.now();
12932
12950
  const engine = deps.engine();
12933
12951
  const stored = [];
12934
12952
  const pending = [];
@@ -12938,9 +12956,10 @@ var Receive = class {
12938
12956
  let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
12939
12957
  for (let pass = 0; pass < 2; pass++) {
12940
12958
  for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
12959
+ const storedBefore = stored.length;
12941
12960
  await this.processEntry(deps, engine, entry, pending, stored);
12942
12961
  if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
12943
- if (stored.length > 0) this.maybeRefresh(deps);
12962
+ if (stored.length > storedBefore) await this.maybeRefresh(deps, pending, pageEpoch);
12944
12963
  }
12945
12964
  await flushAcks(deps, pending, pageEpoch);
12946
12965
  const served = deps.delivery.incomingEpoch();
@@ -13800,6 +13819,16 @@ function parseAmount(raw) {
13800
13819
  function supportsDeterministicMint(engine) {
13801
13820
  return engine.deterministicMint === true;
13802
13821
  }
13822
+ function makeSingleFlightRefresh(refresh) {
13823
+ let inFlight = false;
13824
+ return () => {
13825
+ if (inFlight) return;
13826
+ inFlight = true;
13827
+ void refresh().catch(() => void 0).finally(() => {
13828
+ inFlight = false;
13829
+ });
13830
+ };
13831
+ }
13803
13832
  function composeFacadeParts(deps, hooks) {
13804
13833
  const ownPubkeyBytes = hexToBytes4(deps.ownPubkey);
13805
13834
  const ledger = new ReservationLedger();
@@ -13921,11 +13950,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, view) {
13921
13950
  registry: deps.registry,
13922
13951
  recordReceived: (record) => recordReceived(historyStore, record),
13923
13952
  emit: (event, transfer) => deps.emit(event, transfer),
13924
- // The mirror is the balance the wallet renders; refresh it as the drain runs
13925
- // so a multi-token receive lands progressively instead of all at the end.
13926
- refreshView: () => {
13927
- void view.delta().catch(() => void 0);
13928
- },
13953
+ // Single-flight: view.delta() enqueues on a strict-FIFO SerialChain, so
13954
+ // firing per throttle tick regardless of the last one builds a queue that
13955
+ // drains in a burst at the END — a slower version of the bug being fixed.
13956
+ refreshView: makeSingleFlightRefresh(() => view.delta()),
13929
13957
  attention: (transferId, code, detail) => {
13930
13958
  deps.emit(
13931
13959
  "transfer:attention",
@@ -14370,7 +14398,7 @@ var PaymentsFacade = class {
14370
14398
  settleFailure(transferId, coinId, releaseIds) {
14371
14399
  this.ledger.cancel(transferId);
14372
14400
  this.queue.clearExpectedChange(transferId);
14373
- for (const tokenId of releaseIds) this.view.release(tokenId);
14401
+ this.view.releaseMany(releaseIds);
14374
14402
  this.queue.notifyChange(coinId);
14375
14403
  }
14376
14404
  /** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
@@ -14395,7 +14423,7 @@ var PaymentsFacade = class {
14395
14423
  async refreshThenRelease(coinId, spentIds) {
14396
14424
  try {
14397
14425
  await this.view.delta();
14398
- for (const tokenId of spentIds) this.view.release(tokenId);
14426
+ this.view.releaseMany(spentIds);
14399
14427
  } finally {
14400
14428
  this.queue.notifyChange(coinId);
14401
14429
  }