@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.js CHANGED
@@ -12683,6 +12683,12 @@ var InventoryView = class {
12683
12683
  release(tokenId) {
12684
12684
  if (this.inFlight.delete(tokenId)) this.deps.emit("inventory:updated");
12685
12685
  }
12686
+ /** Release a whole set as ONE change (per-token emitted 54 events per send). */
12687
+ releaseMany(tokenIds) {
12688
+ let changed = false;
12689
+ for (const tokenId of tokenIds) if (this.inFlight.delete(tokenId)) changed = true;
12690
+ if (changed) this.deps.emit("inventory:updated");
12691
+ }
12686
12692
  pinned(tokenId) {
12687
12693
  return this.deps.isPinned?.(tokenId) ?? false;
12688
12694
  }
@@ -12935,16 +12941,28 @@ var Receive = class {
12935
12941
  this.unsubscribeWake?.();
12936
12942
  this.unsubscribeWake = null;
12937
12943
  }
12938
- /** Ask for a mirror refresh at most once per REFRESH_INTERVAL_MS. */
12939
- maybeRefresh(deps) {
12944
+ /**
12945
+ * Flush what has been accepted, then ask for a mirror refresh — at most once
12946
+ * per REFRESH_INTERVAL_MS.
12947
+ *
12948
+ * The flush is not incidental. A `claimed` ack is what materializes a token
12949
+ * into SERVER inventory, and acks otherwise sit queued until ACK_BATCH_SIZE
12950
+ * (200) — so for any ordinary drain, including the 54-token case this exists
12951
+ * for, refreshing before the flush would query an inventory holding none of
12952
+ * the tokens just accepted, and the balance would still only move at the end.
12953
+ * Ordering is preserved: store() still precedes the ack (§5.7).
12954
+ */
12955
+ async maybeRefresh(deps, pending, epochOf) {
12940
12956
  if (deps.refreshView === void 0) return;
12941
12957
  const now = deps.now?.() ?? Date.now();
12942
12958
  if (now - this.lastRefreshAt < REFRESH_INTERVAL_MS) return;
12943
12959
  this.lastRefreshAt = now;
12960
+ await flushAcks(deps, pending, epochOf);
12944
12961
  deps.refreshView();
12945
12962
  }
12946
12963
  async doDrain() {
12947
12964
  const deps = this.deps;
12965
+ this.lastRefreshAt = deps.now?.() ?? Date.now();
12948
12966
  const engine = deps.engine();
12949
12967
  const stored = [];
12950
12968
  const pending = [];
@@ -12954,9 +12972,10 @@ var Receive = class {
12954
12972
  let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
12955
12973
  for (let pass = 0; pass < 2; pass++) {
12956
12974
  for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
12975
+ const storedBefore = stored.length;
12957
12976
  await this.processEntry(deps, engine, entry, pending, stored);
12958
12977
  if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
12959
- if (stored.length > 0) this.maybeRefresh(deps);
12978
+ if (stored.length > storedBefore) await this.maybeRefresh(deps, pending, pageEpoch);
12960
12979
  }
12961
12980
  await flushAcks(deps, pending, pageEpoch);
12962
12981
  const served = deps.delivery.incomingEpoch();
@@ -13816,6 +13835,16 @@ function parseAmount(raw) {
13816
13835
  function supportsDeterministicMint(engine) {
13817
13836
  return engine.deterministicMint === true;
13818
13837
  }
13838
+ function makeSingleFlightRefresh(refresh) {
13839
+ let inFlight = false;
13840
+ return () => {
13841
+ if (inFlight) return;
13842
+ inFlight = true;
13843
+ void refresh().catch(() => void 0).finally(() => {
13844
+ inFlight = false;
13845
+ });
13846
+ };
13847
+ }
13819
13848
  function composeFacadeParts(deps, hooks) {
13820
13849
  const ownPubkeyBytes = hexToBytes4(deps.ownPubkey);
13821
13850
  const ledger = new ReservationLedger();
@@ -13937,11 +13966,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, view) {
13937
13966
  registry: deps.registry,
13938
13967
  recordReceived: (record) => recordReceived(historyStore, record),
13939
13968
  emit: (event, transfer) => deps.emit(event, transfer),
13940
- // The mirror is the balance the wallet renders; refresh it as the drain runs
13941
- // so a multi-token receive lands progressively instead of all at the end.
13942
- refreshView: () => {
13943
- void view.delta().catch(() => void 0);
13944
- },
13969
+ // Single-flight: view.delta() enqueues on a strict-FIFO SerialChain, so
13970
+ // firing per throttle tick regardless of the last one builds a queue that
13971
+ // drains in a burst at the END — a slower version of the bug being fixed.
13972
+ refreshView: makeSingleFlightRefresh(() => view.delta()),
13945
13973
  attention: (transferId, code, detail) => {
13946
13974
  deps.emit(
13947
13975
  "transfer:attention",
@@ -14386,7 +14414,7 @@ var PaymentsFacade = class {
14386
14414
  settleFailure(transferId, coinId, releaseIds) {
14387
14415
  this.ledger.cancel(transferId);
14388
14416
  this.queue.clearExpectedChange(transferId);
14389
- for (const tokenId of releaseIds) this.view.release(tokenId);
14417
+ this.view.releaseMany(releaseIds);
14390
14418
  this.queue.notifyChange(coinId);
14391
14419
  }
14392
14420
  /** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
@@ -14411,7 +14439,7 @@ var PaymentsFacade = class {
14411
14439
  async refreshThenRelease(coinId, spentIds) {
14412
14440
  try {
14413
14441
  await this.view.delta();
14414
- for (const tokenId of spentIds) this.view.release(tokenId);
14442
+ this.view.releaseMany(spentIds);
14415
14443
  } finally {
14416
14444
  this.queue.notifyChange(coinId);
14417
14445
  }