@byok-sdk/core 0.12.0 → 0.14.0

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
@@ -1229,7 +1229,7 @@ var InMemoryMailboxStore = class {
1229
1229
  const device = this.#devices.get(tenantKey(tenant, query.deviceId));
1230
1230
  const limit = query.limit ?? DEFAULT_READ_LIMIT;
1231
1231
  if (device === void 0) {
1232
- return { messages: [], nextSeq: query.afterSeq, hasMore: false };
1232
+ return { messages: [], nextSeq: query.afterSeq, hasMore: false, recoverableFrom: 1 };
1233
1233
  }
1234
1234
  const pending = device.messages.filter((message) => message.state === "pending" && message.seq > query.afterSeq).sort((left, right) => left.seq - right.seq);
1235
1235
  const page = pending.slice(0, limit);
@@ -1239,7 +1239,11 @@ var InMemoryMailboxStore = class {
1239
1239
  // Reading is not acknowledging: the returned position is a *read* cursor.
1240
1240
  // Nothing above was mutated, so an identical call replays the same page.
1241
1241
  nextSeq: last?.seq ?? query.afterSeq,
1242
- hasMore: pending.length > page.length
1242
+ hasMore: pending.length > page.length,
1243
+ // Computed over the WHOLE mailbox, not the queried slice: the floor is a
1244
+ // property of the device's history, and reporting it relative to
1245
+ // `afterSeq` would make every cursor look recoverable from itself.
1246
+ recoverableFrom: this.#recoverableFrom(device)
1243
1247
  };
1244
1248
  }
1245
1249
  async advanceCursor(tenant, input) {
@@ -1345,6 +1349,22 @@ var InMemoryMailboxStore = class {
1345
1349
  this.#devices.set(key, created);
1346
1350
  return created;
1347
1351
  }
1352
+ /**
1353
+ * One past the highest row this device lost, which is exactly the highest
1354
+ * `expired` seq: dead-lettered rows are kept rather than deleted (§12.7.5),
1355
+ * so the loss stays observable and needs no column of its own to remember it.
1356
+ *
1357
+ * `acked` is deliberately not counted. A consumed row is not a lost one, and
1358
+ * counting it would turn a daemon re-polling from an old cursor — which the
1359
+ * at-least-once contract explicitly allows — into a hard failure.
1360
+ */
1361
+ #recoverableFrom(device) {
1362
+ let lost = 0;
1363
+ for (const message of device.messages) {
1364
+ if (message.state === "expired" && message.seq > lost) lost = message.seq;
1365
+ }
1366
+ return lost + 1;
1367
+ }
1348
1368
  #cursorState(tenant, deviceId, device) {
1349
1369
  return {
1350
1370
  tenantId: tenant,