@unicitylabs/sphere-sdk 0.14.11-dev.5 → 0.14.11-dev.7

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.
@@ -581,6 +581,26 @@ var SerialChain = class {
581
581
  return run;
582
582
  }
583
583
  };
584
+ function coalesced(run) {
585
+ let inFlight = false;
586
+ let queued = false;
587
+ const start = () => {
588
+ inFlight = true;
589
+ void run().catch(() => void 0).finally(() => {
590
+ inFlight = false;
591
+ if (!queued) return;
592
+ queued = false;
593
+ start();
594
+ });
595
+ };
596
+ return () => {
597
+ if (inFlight) {
598
+ queued = true;
599
+ return;
600
+ }
601
+ start();
602
+ };
603
+ }
584
604
 
585
605
  // modules/payments-v2/machine/journal.ts
586
606
  var MAX_DELIVERY_ATTEMPTS = 6;
@@ -2886,6 +2906,16 @@ var InventoryView = class {
2886
2906
  this.inFlight.add(tokenId);
2887
2907
  this.deps.emit("inventory:updated");
2888
2908
  }
2909
+ /** Reserve a whole plan as ONE change — per token emitted 54 events per send. */
2910
+ markInFlightMany(tokenIds) {
2911
+ let changed = false;
2912
+ for (const tokenId of tokenIds) {
2913
+ if (this.inFlight.has(tokenId)) continue;
2914
+ this.inFlight.add(tokenId);
2915
+ changed = true;
2916
+ }
2917
+ if (changed) this.deps.emit("inventory:updated");
2918
+ }
2889
2919
  release(tokenId) {
2890
2920
  if (this.inFlight.delete(tokenId)) this.deps.emit("inventory:updated");
2891
2921
  }
@@ -3202,6 +3232,11 @@ var logger = {
3202
3232
  }
3203
3233
  };
3204
3234
 
3235
+ // modules/payments-v2/ports.ts
3236
+ function isRetryableAckError(err) {
3237
+ return err !== null && typeof err === "object" && err.retryable === true;
3238
+ }
3239
+
3205
3240
  // modules/payments-v2/receive/Receive.ts
3206
3241
  var ACK_BATCH_SIZE = 200;
3207
3242
  var REFRESH_INTERVAL_MS = 2500;
@@ -3283,6 +3318,7 @@ var Receive = class {
3283
3318
  }
3284
3319
  } catch (err) {
3285
3320
  logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
3321
+ if (isRetryableAckError(err)) return stored;
3286
3322
  await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
3287
3323
  logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
3288
3324
  });
@@ -3357,14 +3393,18 @@ async function announce(deps, entry, record) {
3357
3393
  };
3358
3394
  }
3359
3395
  async function flushAcks(deps, pending, epochOf) {
3396
+ if (pending.length === 0) return;
3360
3397
  let lastAcked = null;
3361
3398
  try {
3362
- while (pending.length > 0) {
3363
- const next = pending[0];
3364
- await ackOne(deps, next);
3365
- lastAcked = next.cursor;
3366
- pending.shift();
3399
+ const settled = await settleAcks(deps, pending);
3400
+ let i = 0;
3401
+ while (i < pending.length && settled.has(pending[i].deliveryId)) {
3402
+ lastAcked = pending[i].cursor;
3403
+ i += 1;
3367
3404
  }
3405
+ const stuck = pending.filter((p) => !settled.has(p.deliveryId));
3406
+ pending.length = 0;
3407
+ pending.push(...stuck);
3368
3408
  } finally {
3369
3409
  if (lastAcked !== null) {
3370
3410
  const record = { cursor: lastAcked, syncEpoch: epochOf() };
@@ -3372,14 +3412,65 @@ async function flushAcks(deps, pending, epochOf) {
3372
3412
  }
3373
3413
  }
3374
3414
  }
3415
+ async function settleAcks(deps, pending) {
3416
+ const batch = deps.delivery.ackBatch?.bind(deps.delivery);
3417
+ if (batch === void 0) return settleOneByOne(deps, pending);
3418
+ let outcomes;
3419
+ try {
3420
+ outcomes = await batch(pending.map(toAckRequest));
3421
+ } catch (err) {
3422
+ if (isRetryableAckError(err)) throw err;
3423
+ logger.warn("PaymentsV2", "batched mailbox ack failed \u2014 settling one at a time:", err);
3424
+ return settleOneByOne(deps, pending);
3425
+ }
3426
+ const settled = new Set(outcomes.filter((o) => o.status === "settled").map((o) => o.deliveryId));
3427
+ const conflicts = outcomes.filter((o) => o.status === "conflict").map((o) => o.deliveryId);
3428
+ if (conflicts.length > 0) await resolveConflicts(deps, pending, conflicts, settled);
3429
+ return settled;
3430
+ }
3431
+ async function resolveConflicts(deps, pending, conflicts, settled) {
3432
+ for (const deliveryId of conflicts) {
3433
+ const entry = pending.find((p) => p.deliveryId === deliveryId);
3434
+ try {
3435
+ await rejectStaleClaim(deps, deliveryId, entry?.transferId);
3436
+ } catch (err) {
3437
+ logger.warn("PaymentsV2", `stale-reject failed for ${deliveryId} \u2014 cursor holds here:`, err);
3438
+ continue;
3439
+ }
3440
+ settled.add(deliveryId);
3441
+ }
3442
+ }
3443
+ async function rejectStaleClaim(deps, deliveryId, transferId) {
3444
+ logger.warn("PaymentsV2", `mailbox claim CONFLICT for ${deliveryId} \u2014 rejected('other') as stale`);
3445
+ await deps.delivery.ack(deliveryId, "rejected", "other");
3446
+ deps.attention(transferId ?? "", ATTENTION_CLAIM_CONFLICT, deliveryId);
3447
+ }
3448
+ async function settleOneByOne(deps, pending) {
3449
+ const settled = /* @__PURE__ */ new Set();
3450
+ for (const ack of pending) {
3451
+ try {
3452
+ await ackOne(deps, ack);
3453
+ } catch (err) {
3454
+ logger.warn("PaymentsV2", `mailbox ack failed for ${ack.deliveryId} \u2014 cursor holds here:`, err);
3455
+ break;
3456
+ }
3457
+ settled.add(ack.deliveryId);
3458
+ }
3459
+ return settled;
3460
+ }
3461
+ function toAckRequest(ack) {
3462
+ return {
3463
+ deliveryId: ack.deliveryId,
3464
+ disposition: ack.disposition,
3465
+ ...ack.reason !== void 0 ? { reason: ack.reason } : {}
3466
+ };
3467
+ }
3375
3468
  async function ackOne(deps, ack) {
3376
3469
  try {
3377
3470
  await deps.delivery.ack(ack.deliveryId, ack.disposition, ack.reason);
3378
3471
  } catch (err) {
3379
3472
  if (ack.disposition !== "claimed" || !isClaimConflict(err)) throw err;
3380
- logger.warn("PaymentsV2", `mailbox claim CONFLICT for ${ack.deliveryId} \u2014 rejected('other') as stale`);
3381
- await deps.delivery.ack(ack.deliveryId, "rejected", "other");
3382
- deps.attention(ack.transferId ?? "", ATTENTION_CLAIM_CONFLICT, ack.deliveryId);
3473
+ await rejectStaleClaim(deps, ack.deliveryId, ack.transferId);
3383
3474
  }
3384
3475
  }
3385
3476
  function claimAck(entry) {
@@ -4133,16 +4224,6 @@ function parseAmount(raw) {
4133
4224
  function supportsDeterministicMint(engine) {
4134
4225
  return engine.deterministicMint === true;
4135
4226
  }
4136
- function makeSingleFlightRefresh(refresh) {
4137
- let inFlight = false;
4138
- return () => {
4139
- if (inFlight) return;
4140
- inFlight = true;
4141
- void refresh().catch(() => void 0).finally(() => {
4142
- inFlight = false;
4143
- });
4144
- };
4145
- }
4146
4227
  function composeFacadeParts(deps, hooks) {
4147
4228
  const ownPubkeyBytes = hexToBytes(deps.ownPubkey);
4148
4229
  const ledger = new ReservationLedger();
@@ -4172,9 +4253,11 @@ function composeFacadeParts(deps, hooks) {
4172
4253
  const machineDeps = buildMachineDeps(deps, hooks, historyStore);
4173
4254
  const machineStores = createMachineStores(deps.kv);
4174
4255
  const heldStates = /* @__PURE__ */ new Map();
4256
+ const refreshView = coalesced(() => view.delta());
4175
4257
  return {
4176
4258
  ownPubkeyBytes,
4177
4259
  view,
4260
+ refreshView,
4178
4261
  ledger,
4179
4262
  pins: buildPins(deps, hooks, { ledger, view, machineStores, machineDeps }),
4180
4263
  queue,
@@ -4183,7 +4266,7 @@ function composeFacadeParts(deps, hooks) {
4183
4266
  machineDeps,
4184
4267
  machine: new TransferMachine(machineDeps),
4185
4268
  heldStates,
4186
- receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, view),
4269
+ receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, refreshView),
4187
4270
  requests: buildRequests(deps, hooks),
4188
4271
  restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
4189
4272
  };
@@ -4250,7 +4333,7 @@ function buildMachineDeps(deps, hooks, historyStore) {
4250
4333
  }
4251
4334
  };
4252
4335
  }
4253
- function buildReceive(deps, hooks, historyStore, heldStates, view) {
4336
+ function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
4254
4337
  return new Receive({
4255
4338
  delivery: deps.deliveryPort,
4256
4339
  engine: () => hooks.engine(),
@@ -4264,10 +4347,7 @@ function buildReceive(deps, hooks, historyStore, heldStates, view) {
4264
4347
  registry: deps.registry,
4265
4348
  recordReceived: (record) => recordReceived(historyStore, record),
4266
4349
  emit: (event, transfer) => deps.emit(event, transfer),
4267
- // Single-flight: view.delta() enqueues on a strict-FIFO SerialChain, so
4268
- // firing per throttle tick regardless of the last one builds a queue that
4269
- // drains in a burst at the END — a slower version of the bug being fixed.
4270
- refreshView: makeSingleFlightRefresh(() => view.delta()),
4350
+ refreshView,
4271
4351
  attention: (transferId, code, detail) => {
4272
4352
  deps.emit(
4273
4353
  "transfer:attention",
@@ -4319,6 +4399,7 @@ var PaymentsFacade = class {
4319
4399
  isActiveOp: (id) => this.activeMoneyOps.has(id)
4320
4400
  });
4321
4401
  this.view = parts.view;
4402
+ this.wakeRefresh = parts.refreshView;
4322
4403
  this.ledger = parts.ledger;
4323
4404
  this.queue = parts.queue;
4324
4405
  this.machine = parts.machine;
@@ -4348,6 +4429,7 @@ var PaymentsFacade = class {
4348
4429
  now: () => this.nowMs()
4349
4430
  });
4350
4431
  }
4432
+ wakeRefresh;
4351
4433
  prewarmed = new PrewarmCache();
4352
4434
  view;
4353
4435
  ledger;
@@ -4386,9 +4468,7 @@ var PaymentsFacade = class {
4386
4468
  this.deps.session.subscribeStream("mailbox", () => {
4387
4469
  this.trackTail(this.receiveLoop.drainOnce());
4388
4470
  }),
4389
- this.deps.session.subscribeStream("inventory", () => {
4390
- this.trackTail(this.view.delta());
4391
- }),
4471
+ this.deps.session.subscribeStream("inventory", this.wakeRefresh),
4392
4472
  this.deps.session.subscribeStream("payment_requests", () => {
4393
4473
  this.trackTail(this.requests.drainIncoming());
4394
4474
  })
@@ -4652,7 +4732,7 @@ var PaymentsFacade = class {
4652
4732
  ...spend.plan.direct,
4653
4733
  ...spend.plan.split !== void 0 ? [spend.plan.split.tokenId] : []
4654
4734
  ];
4655
- for (const tokenId of sourceIds) this.view.markInFlight(tokenId);
4735
+ this.view.markInFlightMany(sourceIds);
4656
4736
  if (spend.plan.split !== void 0) {
4657
4737
  this.queue.declareExpectedChange(transferId, coinId, spend.plan.split.remainderAmount);
4658
4738
  }