@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.
@@ -12753,6 +12753,12 @@ var InventoryView = class {
12753
12753
  release(tokenId) {
12754
12754
  if (this.inFlight.delete(tokenId)) this.deps.emit("inventory:updated");
12755
12755
  }
12756
+ /** Release a whole set as ONE change (per-token emitted 54 events per send). */
12757
+ releaseMany(tokenIds) {
12758
+ let changed = false;
12759
+ for (const tokenId of tokenIds) if (this.inFlight.delete(tokenId)) changed = true;
12760
+ if (changed) this.deps.emit("inventory:updated");
12761
+ }
12756
12762
  pinned(tokenId) {
12757
12763
  return this.deps.isPinned?.(tokenId) ?? false;
12758
12764
  }
@@ -13005,16 +13011,28 @@ var Receive = class {
13005
13011
  this.unsubscribeWake?.();
13006
13012
  this.unsubscribeWake = null;
13007
13013
  }
13008
- /** Ask for a mirror refresh at most once per REFRESH_INTERVAL_MS. */
13009
- maybeRefresh(deps) {
13014
+ /**
13015
+ * Flush what has been accepted, then ask for a mirror refresh — at most once
13016
+ * per REFRESH_INTERVAL_MS.
13017
+ *
13018
+ * The flush is not incidental. A `claimed` ack is what materializes a token
13019
+ * into SERVER inventory, and acks otherwise sit queued until ACK_BATCH_SIZE
13020
+ * (200) — so for any ordinary drain, including the 54-token case this exists
13021
+ * for, refreshing before the flush would query an inventory holding none of
13022
+ * the tokens just accepted, and the balance would still only move at the end.
13023
+ * Ordering is preserved: store() still precedes the ack (§5.7).
13024
+ */
13025
+ async maybeRefresh(deps, pending, epochOf) {
13010
13026
  if (deps.refreshView === void 0) return;
13011
13027
  const now = deps.now?.() ?? Date.now();
13012
13028
  if (now - this.lastRefreshAt < REFRESH_INTERVAL_MS) return;
13013
13029
  this.lastRefreshAt = now;
13030
+ await flushAcks(deps, pending, epochOf);
13014
13031
  deps.refreshView();
13015
13032
  }
13016
13033
  async doDrain() {
13017
13034
  const deps = this.deps;
13035
+ this.lastRefreshAt = deps.now?.() ?? Date.now();
13018
13036
  const engine = deps.engine();
13019
13037
  const stored = [];
13020
13038
  const pending = [];
@@ -13024,9 +13042,10 @@ var Receive = class {
13024
13042
  let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
13025
13043
  for (let pass = 0; pass < 2; pass++) {
13026
13044
  for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
13045
+ const storedBefore = stored.length;
13027
13046
  await this.processEntry(deps, engine, entry, pending, stored);
13028
13047
  if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
13029
- if (stored.length > 0) this.maybeRefresh(deps);
13048
+ if (stored.length > storedBefore) await this.maybeRefresh(deps, pending, pageEpoch);
13030
13049
  }
13031
13050
  await flushAcks(deps, pending, pageEpoch);
13032
13051
  const served = deps.delivery.incomingEpoch();
@@ -13886,6 +13905,16 @@ function parseAmount(raw) {
13886
13905
  function supportsDeterministicMint(engine) {
13887
13906
  return engine.deterministicMint === true;
13888
13907
  }
13908
+ function makeSingleFlightRefresh(refresh) {
13909
+ let inFlight = false;
13910
+ return () => {
13911
+ if (inFlight) return;
13912
+ inFlight = true;
13913
+ void refresh().catch(() => void 0).finally(() => {
13914
+ inFlight = false;
13915
+ });
13916
+ };
13917
+ }
13889
13918
  function composeFacadeParts(deps, hooks) {
13890
13919
  const ownPubkeyBytes = hexToBytes4(deps.ownPubkey);
13891
13920
  const ledger = new ReservationLedger();
@@ -14007,11 +14036,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, view) {
14007
14036
  registry: deps.registry,
14008
14037
  recordReceived: (record) => recordReceived(historyStore, record),
14009
14038
  emit: (event, transfer) => deps.emit(event, transfer),
14010
- // The mirror is the balance the wallet renders; refresh it as the drain runs
14011
- // so a multi-token receive lands progressively instead of all at the end.
14012
- refreshView: () => {
14013
- void view.delta().catch(() => void 0);
14014
- },
14039
+ // Single-flight: view.delta() enqueues on a strict-FIFO SerialChain, so
14040
+ // firing per throttle tick regardless of the last one builds a queue that
14041
+ // drains in a burst at the END — a slower version of the bug being fixed.
14042
+ refreshView: makeSingleFlightRefresh(() => view.delta()),
14015
14043
  attention: (transferId, code, detail) => {
14016
14044
  deps.emit(
14017
14045
  "transfer:attention",
@@ -14456,7 +14484,7 @@ var PaymentsFacade = class {
14456
14484
  settleFailure(transferId, coinId, releaseIds) {
14457
14485
  this.ledger.cancel(transferId);
14458
14486
  this.queue.clearExpectedChange(transferId);
14459
- for (const tokenId of releaseIds) this.view.release(tokenId);
14487
+ this.view.releaseMany(releaseIds);
14460
14488
  this.queue.notifyChange(coinId);
14461
14489
  }
14462
14490
  /** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
@@ -14481,7 +14509,7 @@ var PaymentsFacade = class {
14481
14509
  async refreshThenRelease(coinId, spentIds) {
14482
14510
  try {
14483
14511
  await this.view.delta();
14484
- for (const tokenId of spentIds) this.view.release(tokenId);
14512
+ this.view.releaseMany(spentIds);
14485
14513
  } finally {
14486
14514
  this.queue.notifyChange(coinId);
14487
14515
  }