@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.
package/dist/index.cjs CHANGED
@@ -12811,6 +12811,12 @@ var InventoryView = class {
12811
12811
  release(tokenId) {
12812
12812
  if (this.inFlight.delete(tokenId)) this.deps.emit("inventory:updated");
12813
12813
  }
12814
+ /** Release a whole set as ONE change (per-token emitted 54 events per send). */
12815
+ releaseMany(tokenIds) {
12816
+ let changed = false;
12817
+ for (const tokenId of tokenIds) if (this.inFlight.delete(tokenId)) changed = true;
12818
+ if (changed) this.deps.emit("inventory:updated");
12819
+ }
12814
12820
  pinned(tokenId) {
12815
12821
  return this.deps.isPinned?.(tokenId) ?? false;
12816
12822
  }
@@ -13063,16 +13069,28 @@ var Receive = class {
13063
13069
  this.unsubscribeWake?.();
13064
13070
  this.unsubscribeWake = null;
13065
13071
  }
13066
- /** Ask for a mirror refresh at most once per REFRESH_INTERVAL_MS. */
13067
- maybeRefresh(deps) {
13072
+ /**
13073
+ * Flush what has been accepted, then ask for a mirror refresh — at most once
13074
+ * per REFRESH_INTERVAL_MS.
13075
+ *
13076
+ * The flush is not incidental. A `claimed` ack is what materializes a token
13077
+ * into SERVER inventory, and acks otherwise sit queued until ACK_BATCH_SIZE
13078
+ * (200) — so for any ordinary drain, including the 54-token case this exists
13079
+ * for, refreshing before the flush would query an inventory holding none of
13080
+ * the tokens just accepted, and the balance would still only move at the end.
13081
+ * Ordering is preserved: store() still precedes the ack (§5.7).
13082
+ */
13083
+ async maybeRefresh(deps, pending, epochOf) {
13068
13084
  if (deps.refreshView === void 0) return;
13069
13085
  const now = deps.now?.() ?? Date.now();
13070
13086
  if (now - this.lastRefreshAt < REFRESH_INTERVAL_MS) return;
13071
13087
  this.lastRefreshAt = now;
13088
+ await flushAcks(deps, pending, epochOf);
13072
13089
  deps.refreshView();
13073
13090
  }
13074
13091
  async doDrain() {
13075
13092
  const deps = this.deps;
13093
+ this.lastRefreshAt = deps.now?.() ?? Date.now();
13076
13094
  const engine = deps.engine();
13077
13095
  const stored = [];
13078
13096
  const pending = [];
@@ -13082,9 +13100,10 @@ var Receive = class {
13082
13100
  let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
13083
13101
  for (let pass = 0; pass < 2; pass++) {
13084
13102
  for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
13103
+ const storedBefore = stored.length;
13085
13104
  await this.processEntry(deps, engine, entry, pending, stored);
13086
13105
  if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
13087
- if (stored.length > 0) this.maybeRefresh(deps);
13106
+ if (stored.length > storedBefore) await this.maybeRefresh(deps, pending, pageEpoch);
13088
13107
  }
13089
13108
  await flushAcks(deps, pending, pageEpoch);
13090
13109
  const served = deps.delivery.incomingEpoch();
@@ -13944,6 +13963,16 @@ function parseAmount(raw) {
13944
13963
  function supportsDeterministicMint(engine) {
13945
13964
  return engine.deterministicMint === true;
13946
13965
  }
13966
+ function makeSingleFlightRefresh(refresh) {
13967
+ let inFlight = false;
13968
+ return () => {
13969
+ if (inFlight) return;
13970
+ inFlight = true;
13971
+ void refresh().catch(() => void 0).finally(() => {
13972
+ inFlight = false;
13973
+ });
13974
+ };
13975
+ }
13947
13976
  function composeFacadeParts(deps, hooks) {
13948
13977
  const ownPubkeyBytes = hexToBytes4(deps.ownPubkey);
13949
13978
  const ledger = new ReservationLedger();
@@ -14065,11 +14094,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, view) {
14065
14094
  registry: deps.registry,
14066
14095
  recordReceived: (record) => recordReceived(historyStore, record),
14067
14096
  emit: (event, transfer) => deps.emit(event, transfer),
14068
- // The mirror is the balance the wallet renders; refresh it as the drain runs
14069
- // so a multi-token receive lands progressively instead of all at the end.
14070
- refreshView: () => {
14071
- void view.delta().catch(() => void 0);
14072
- },
14097
+ // Single-flight: view.delta() enqueues on a strict-FIFO SerialChain, so
14098
+ // firing per throttle tick regardless of the last one builds a queue that
14099
+ // drains in a burst at the END — a slower version of the bug being fixed.
14100
+ refreshView: makeSingleFlightRefresh(() => view.delta()),
14073
14101
  attention: (transferId, code, detail) => {
14074
14102
  deps.emit(
14075
14103
  "transfer:attention",
@@ -14514,7 +14542,7 @@ var PaymentsFacade = class {
14514
14542
  settleFailure(transferId, coinId, releaseIds) {
14515
14543
  this.ledger.cancel(transferId);
14516
14544
  this.queue.clearExpectedChange(transferId);
14517
- for (const tokenId of releaseIds) this.view.release(tokenId);
14545
+ this.view.releaseMany(releaseIds);
14518
14546
  this.queue.notifyChange(coinId);
14519
14547
  }
14520
14548
  /** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
@@ -14539,7 +14567,7 @@ var PaymentsFacade = class {
14539
14567
  async refreshThenRelease(coinId, spentIds) {
14540
14568
  try {
14541
14569
  await this.view.delta();
14542
- for (const tokenId of spentIds) this.view.release(tokenId);
14570
+ this.view.releaseMany(spentIds);
14543
14571
  } finally {
14544
14572
  this.queue.notifyChange(coinId);
14545
14573
  }