@byok-sdk/core 0.11.0 → 0.13.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/errors.d.ts +1 -0
- package/dist/in-memory/mailbox.d.ts +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +49 -4
- package/dist/index.js.map +1 -1
- package/dist/mailbox.d.ts +34 -2
- package/package.json +1 -1
package/dist/errors.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export declare const CORE_ERROR_CODES: {
|
|
|
39
39
|
readonly assertion_envelope_invalid: 'assertion_envelope_invalid';
|
|
40
40
|
readonly mailbox_message_not_found: 'mailbox_message_not_found';
|
|
41
41
|
readonly mailbox_cursor_regression: 'mailbox_cursor_regression';
|
|
42
|
+
readonly mailbox_cursor_ahead_of_delivery: 'mailbox_cursor_ahead_of_delivery';
|
|
42
43
|
readonly board_item_not_found: 'board_item_not_found';
|
|
43
44
|
readonly board_item_exists: 'board_item_exists';
|
|
44
45
|
readonly board_transition_invalid: 'board_transition_invalid';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MailboxAdvanceCursorInput, MailboxAppendInput, MailboxCursorState, MailboxMessage, MailboxPage, MailboxReadQuery, MailboxRetentionInput, MailboxRetentionResult, MailboxStore } from '../mailbox';
|
|
1
|
+
import type { MailboxAdvanceCursorInput, MailboxAppendInput, MailboxCursorState, MailboxMessage, MailboxPage, MailboxReadQuery, MailboxRecordDeliveryInput, MailboxRetentionInput, MailboxRetentionResult, MailboxStore } from '../mailbox';
|
|
2
2
|
import type { Clock } from '../stores';
|
|
3
3
|
import { type TenantId } from '../tenant';
|
|
4
4
|
export declare class InMemoryMailboxStore implements MailboxStore {
|
|
@@ -7,6 +7,7 @@ export declare class InMemoryMailboxStore implements MailboxStore {
|
|
|
7
7
|
append(tenant: TenantId, input: MailboxAppendInput): Promise<MailboxMessage>;
|
|
8
8
|
readAfter(tenant: TenantId, query: MailboxReadQuery): Promise<MailboxPage>;
|
|
9
9
|
advanceCursor(tenant: TenantId, input: MailboxAdvanceCursorInput): Promise<MailboxCursorState>;
|
|
10
|
+
recordDelivery(tenant: TenantId, input: MailboxRecordDeliveryInput): Promise<MailboxCursorState>;
|
|
10
11
|
readCursor(tenant: TenantId, deviceId: string): Promise<MailboxCursorState>;
|
|
11
12
|
collectRetired(tenant: TenantId, input: MailboxRetentionInput): Promise<MailboxRetentionResult>;
|
|
12
13
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export { ByokCoreError, CoreConflictError, CORE_ERROR_CODES, isCoreError, isCore
|
|
|
18
18
|
export type { CoreErrorCode } from './errors';
|
|
19
19
|
export { CANONICAL_TIMESTAMP_PATTERN, assertCanonicalTimestamp, isCanonicalTimestamp, } from './time';
|
|
20
20
|
export { MAILBOX_MESSAGE_STATES } from './mailbox';
|
|
21
|
-
export type { MailboxAdvanceCursorInput, MailboxAppendInput, MailboxBody, MailboxCursorState, MailboxMessage, MailboxMessageState, MailboxPage, MailboxReadQuery, MailboxRetentionInput, MailboxRetentionResult, MailboxStore, } from './mailbox';
|
|
21
|
+
export type { MailboxAdvanceCursorInput, MailboxAppendInput, MailboxBody, MailboxCursorState, MailboxMessage, MailboxMessageState, MailboxPage, MailboxReadQuery, MailboxRecordDeliveryInput, MailboxRetentionInput, MailboxRetentionResult, MailboxStore, } from './mailbox';
|
|
22
22
|
export { BOARD_STATUSES, BOARD_TRANSITIONS, isLegalBoardTransition } from './board';
|
|
23
23
|
export type { BoardAssignee, BoardClaimInput, BoardItem, BoardItemInput, BoardListQuery, BoardPage, BoardStatus, BoardStatusUpdateInput, BoardStore, BoardUnclaimInput, } from './board';
|
|
24
24
|
export { TRUTH_RECORD_KINDS } from './truth';
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ var CORE_ERROR_CODES = {
|
|
|
22
22
|
// mailbox (§12.7.3)
|
|
23
23
|
mailbox_message_not_found: "mailbox_message_not_found",
|
|
24
24
|
mailbox_cursor_regression: "mailbox_cursor_regression",
|
|
25
|
+
mailbox_cursor_ahead_of_delivery: "mailbox_cursor_ahead_of_delivery",
|
|
25
26
|
// board coordination (§12.3)
|
|
26
27
|
board_item_not_found: "board_item_not_found",
|
|
27
28
|
board_item_exists: "board_item_exists",
|
|
@@ -536,7 +537,7 @@ var CORE_STORE_NAMES = [
|
|
|
536
537
|
// src/ports-contract.ts
|
|
537
538
|
var CORE_NON_COMPOSITION_PORT_NAMES = [];
|
|
538
539
|
var CORE_PORT_METHODS = {
|
|
539
|
-
mailbox: ["append", "readAfter", "advanceCursor", "readCursor", "collectRetired"],
|
|
540
|
+
mailbox: ["append", "readAfter", "recordDelivery", "advanceCursor", "readCursor", "collectRetired"],
|
|
540
541
|
board: ["create", "get", "list", "claim", "unclaim", "updateStatus"],
|
|
541
542
|
truth: ["writeTerminal", "writeSnapshot", "getRecord", "listManifest"],
|
|
542
543
|
presence: ["publish", "read", "list"],
|
|
@@ -1228,7 +1229,7 @@ var InMemoryMailboxStore = class {
|
|
|
1228
1229
|
const device = this.#devices.get(tenantKey(tenant, query.deviceId));
|
|
1229
1230
|
const limit = query.limit ?? DEFAULT_READ_LIMIT;
|
|
1230
1231
|
if (device === void 0) {
|
|
1231
|
-
return { messages: [], nextSeq: query.afterSeq, hasMore: false };
|
|
1232
|
+
return { messages: [], nextSeq: query.afterSeq, hasMore: false, recoverableFrom: 1 };
|
|
1232
1233
|
}
|
|
1233
1234
|
const pending = device.messages.filter((message) => message.state === "pending" && message.seq > query.afterSeq).sort((left, right) => left.seq - right.seq);
|
|
1234
1235
|
const page = pending.slice(0, limit);
|
|
@@ -1238,7 +1239,11 @@ var InMemoryMailboxStore = class {
|
|
|
1238
1239
|
// Reading is not acknowledging: the returned position is a *read* cursor.
|
|
1239
1240
|
// Nothing above was mutated, so an identical call replays the same page.
|
|
1240
1241
|
nextSeq: last?.seq ?? query.afterSeq,
|
|
1241
|
-
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)
|
|
1242
1247
|
};
|
|
1243
1248
|
}
|
|
1244
1249
|
async advanceCursor(tenant, input) {
|
|
@@ -1252,6 +1257,14 @@ var InMemoryMailboxStore = class {
|
|
|
1252
1257
|
this.#now()
|
|
1253
1258
|
);
|
|
1254
1259
|
}
|
|
1260
|
+
if (input.ackedSeq > device.deliveredSeq) {
|
|
1261
|
+
throw new CoreConflictError(
|
|
1262
|
+
"mailbox_cursor_ahead_of_delivery",
|
|
1263
|
+
`Cursor for device ${input.deviceId} was delivered through ${device.deliveredSeq}; refusing to acknowledge future cursor ${input.ackedSeq}.`,
|
|
1264
|
+
this.#cursorState(tenant, input.deviceId, device),
|
|
1265
|
+
this.#now()
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1255
1268
|
device.ackedSeq = input.ackedSeq;
|
|
1256
1269
|
device.cursorUpdatedAt = this.#now();
|
|
1257
1270
|
for (const [index, message] of device.messages.entries()) {
|
|
@@ -1268,10 +1281,24 @@ var InMemoryMailboxStore = class {
|
|
|
1268
1281
|
);
|
|
1269
1282
|
return advanced;
|
|
1270
1283
|
}
|
|
1284
|
+
async recordDelivery(tenant, input) {
|
|
1285
|
+
const device = this.#device(tenant, input.deviceId);
|
|
1286
|
+
const recorded = device.mutationTail.then(() => {
|
|
1287
|
+
if (input.deliveredSeq > device.deliveredSeq) {
|
|
1288
|
+
device.deliveredSeq = input.deliveredSeq;
|
|
1289
|
+
}
|
|
1290
|
+
return this.#cursorState(tenant, input.deviceId, device);
|
|
1291
|
+
});
|
|
1292
|
+
device.mutationTail = recorded.then(
|
|
1293
|
+
() => void 0,
|
|
1294
|
+
() => void 0
|
|
1295
|
+
);
|
|
1296
|
+
return recorded;
|
|
1297
|
+
}
|
|
1271
1298
|
async readCursor(tenant, deviceId) {
|
|
1272
1299
|
const device = this.#devices.get(tenantKey(tenant, deviceId));
|
|
1273
1300
|
if (device === void 0) {
|
|
1274
|
-
return { tenantId: tenant, deviceId, ackedSeq: 0, updatedAt: this.#now() };
|
|
1301
|
+
return { tenantId: tenant, deviceId, deliveredSeq: 0, ackedSeq: 0, updatedAt: this.#now() };
|
|
1275
1302
|
}
|
|
1276
1303
|
return this.#cursorState(tenant, deviceId, device);
|
|
1277
1304
|
}
|
|
@@ -1312,6 +1339,7 @@ var InMemoryMailboxStore = class {
|
|
|
1312
1339
|
if (existing !== void 0) return existing;
|
|
1313
1340
|
const created = {
|
|
1314
1341
|
nextSeq: 1,
|
|
1342
|
+
deliveredSeq: 0,
|
|
1315
1343
|
ackedSeq: 0,
|
|
1316
1344
|
cursorUpdatedAt: this.#now(),
|
|
1317
1345
|
mutationTail: Promise.resolve(),
|
|
@@ -1321,10 +1349,27 @@ var InMemoryMailboxStore = class {
|
|
|
1321
1349
|
this.#devices.set(key, created);
|
|
1322
1350
|
return created;
|
|
1323
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
|
+
}
|
|
1324
1368
|
#cursorState(tenant, deviceId, device) {
|
|
1325
1369
|
return {
|
|
1326
1370
|
tenantId: tenant,
|
|
1327
1371
|
deviceId,
|
|
1372
|
+
deliveredSeq: device.deliveredSeq,
|
|
1328
1373
|
ackedSeq: device.ackedSeq,
|
|
1329
1374
|
updatedAt: device.cursorUpdatedAt
|
|
1330
1375
|
};
|