@band-ai/band-sdk-core 2.5.0 → 2.6.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/README.md CHANGED
@@ -107,6 +107,31 @@ switch (tracker.recordRoomParticipantsJoinFailed("room-1", ticket, false)) {
107
107
  }
108
108
  ```
109
109
 
110
+ ### `DecisionRegistry` lifecycle
111
+
112
+ `DecisionRegistry` tracks chat-mediated asks: whoever claims one first owns
113
+ its outcome. Every registration returns `[token, ticket]`, with `ticket` a
114
+ `bigint`, and later calls name that exact registration, so a stale holder
115
+ gets `"stale"` instead of acting on a newer ask that reused the token.
116
+ `maxPending` must be an integer of at least `1`, or omitted/`null` for
117
+ unbounded; `seed` is an optional `bigint`. `cancelAll`/`cancelRoom` return a
118
+ plain `{ unclaimed, claimed }` object. `isAuthorizedSender` takes an array,
119
+ a `Set`, or `null`.
120
+
121
+ ```ts
122
+ import { DecisionRegistry, isAuthorizedSender } from "@band-ai/band-sdk-core";
123
+
124
+ const registry = new DecisionRegistry(10);
125
+ const registration = registry.registerMinted("room-1");
126
+ if (registration === undefined) throw new Error("tickets exhausted");
127
+ const [token, ticket] = registration;
128
+
129
+ if (registry.tryClaim(token, ticket) !== "claimed") throw new Error("not claimed");
130
+ if (registry.tryClaim(token, ticket) !== "already_claimed") throw new Error("claimed twice");
131
+ if (registry.cancelRoom("room-1").claimed[0] !== token) throw new Error("claim not reported");
132
+ if (!isAuthorizedSender(new Set(["alice"]), "alice")) throw new Error("alice refused");
133
+ ```
134
+
110
135
  ### `Session` — WebSocket reconnect state machine
111
136
 
112
137
  `Session`/`SessionPolicy` are a sans-io session state machine plus
package/band_sdk_core.js CHANGED
@@ -270,6 +270,182 @@ class ClaimRegistry {
270
270
  if (Symbol.dispose) ClaimRegistry.prototype[Symbol.dispose] = ClaimRegistry.prototype.free;
271
271
  exports.ClaimRegistry = ClaimRegistry;
272
272
 
273
+ /**
274
+ * Pending chat-mediated asks by token, oldest first. Every registration
275
+ * returns `[token, ticket]`; later calls name that exact registration, so a
276
+ * stale holder never acts on a newer ask that reused the token.
277
+ */
278
+ class DecisionRegistry {
279
+ __destroy_into_raw() {
280
+ const ptr = this.__wbg_ptr;
281
+ this.__wbg_ptr = 0;
282
+ DecisionRegistryFinalization.unregister(this);
283
+ return ptr;
284
+ }
285
+ free() {
286
+ const ptr = this.__destroy_into_raw();
287
+ wasm.__wbg_decisionregistry_free(ptr, 0);
288
+ }
289
+ /**
290
+ * Returns a plain `{ unclaimed, claimed }` object of token arrays.
291
+ * @returns {any}
292
+ */
293
+ cancelAll() {
294
+ const ret = wasm.decisionregistry_cancelAll(this.__wbg_ptr);
295
+ if (ret[2]) {
296
+ throw takeFromExternrefTable0(ret[1]);
297
+ }
298
+ return takeFromExternrefTable0(ret[0]);
299
+ }
300
+ /**
301
+ * Returns a plain `{ unclaimed, claimed }` object of token arrays.
302
+ * @param {any} roomId
303
+ * @returns {any}
304
+ */
305
+ cancelRoom(roomId) {
306
+ const ret = wasm.decisionregistry_cancelRoom(this.__wbg_ptr, roomId);
307
+ if (ret[2]) {
308
+ throw takeFromExternrefTable0(ret[1]);
309
+ }
310
+ return takeFromExternrefTable0(ret[0]);
311
+ }
312
+ /**
313
+ * @returns {string | undefined}
314
+ */
315
+ evictOldest() {
316
+ const ret = wasm.decisionregistry_evictOldest(this.__wbg_ptr);
317
+ let v1;
318
+ if (ret[0] !== 0) {
319
+ v1 = getStringFromWasm0(ret[0], ret[1]);
320
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
321
+ }
322
+ return v1;
323
+ }
324
+ /**
325
+ * @param {any} token
326
+ * @param {bigint} ticket
327
+ * @returns {boolean}
328
+ */
329
+ forget(token, ticket) {
330
+ const ret = wasm.decisionregistry_forget(this.__wbg_ptr, token, ticket);
331
+ if (ret[2]) {
332
+ throw takeFromExternrefTable0(ret[1]);
333
+ }
334
+ return ret[0] !== 0;
335
+ }
336
+ /**
337
+ * @returns {number | undefined}
338
+ */
339
+ get maxPending() {
340
+ const ret = wasm.decisionregistry_maxPending(this.__wbg_ptr);
341
+ return ret === Number.MAX_SAFE_INTEGER ? undefined : ret;
342
+ }
343
+ /**
344
+ * `maxPending` omitted or `null` never evicts. `seed` fixes the
345
+ * minted-token sequence; omitted, each registry draws its own.
346
+ * @param {number | null} [maxPending]
347
+ * @param {bigint | null} [seed]
348
+ */
349
+ constructor(maxPending, seed) {
350
+ const ret = wasm.decisionregistry_new(!isLikeNone(maxPending), isLikeNone(maxPending) ? 0 : maxPending, !isLikeNone(seed), isLikeNone(seed) ? BigInt(0) : seed);
351
+ if (ret[2]) {
352
+ throw takeFromExternrefTable0(ret[1]);
353
+ }
354
+ this.__wbg_ptr = ret[0];
355
+ DecisionRegistryFinalization.register(this, this.__wbg_ptr, this);
356
+ return this;
357
+ }
358
+ /**
359
+ * @returns {string | undefined}
360
+ */
361
+ oldestUnclaimed() {
362
+ const ret = wasm.decisionregistry_oldestUnclaimed(this.__wbg_ptr);
363
+ let v1;
364
+ if (ret[0] !== 0) {
365
+ v1 = getStringFromWasm0(ret[0], ret[1]);
366
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
367
+ }
368
+ return v1;
369
+ }
370
+ /**
371
+ * @param {any} key
372
+ * @returns {Array<any> | undefined}
373
+ */
374
+ registerKeyed(key) {
375
+ const ret = wasm.decisionregistry_registerKeyed(this.__wbg_ptr, key);
376
+ if (ret[2]) {
377
+ throw takeFromExternrefTable0(ret[1]);
378
+ }
379
+ return takeFromExternrefTable0(ret[0]);
380
+ }
381
+ /**
382
+ * @param {any} roomId
383
+ * @returns {Array<any> | undefined}
384
+ */
385
+ registerMinted(roomId) {
386
+ const ret = wasm.decisionregistry_registerMinted(this.__wbg_ptr, roomId);
387
+ if (ret[2]) {
388
+ throw takeFromExternrefTable0(ret[1]);
389
+ }
390
+ return takeFromExternrefTable0(ret[0]);
391
+ }
392
+ /**
393
+ * @param {any} token
394
+ * @param {bigint} ticket
395
+ * @returns {string}
396
+ */
397
+ tryClaim(token, ticket) {
398
+ const ret = wasm.decisionregistry_tryClaim(this.__wbg_ptr, token, ticket);
399
+ if (ret[3]) {
400
+ throw takeFromExternrefTable0(ret[2]);
401
+ }
402
+ return getStringFromWasm0(ret[0], ret[1]);
403
+ }
404
+ /**
405
+ * @returns {number}
406
+ */
407
+ unclaimedCount() {
408
+ const ret = wasm.decisionregistry_unclaimedCount(this.__wbg_ptr);
409
+ return ret >>> 0;
410
+ }
411
+ /**
412
+ * @param {any} roomId
413
+ * @returns {string[]}
414
+ */
415
+ unclaimedInRoom(roomId) {
416
+ const ret = wasm.decisionregistry_unclaimedInRoom(this.__wbg_ptr, roomId);
417
+ if (ret[3]) {
418
+ throw takeFromExternrefTable0(ret[2]);
419
+ }
420
+ var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]);
421
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
422
+ return v1;
423
+ }
424
+ /**
425
+ * @returns {string[]}
426
+ */
427
+ unclaimed() {
428
+ const ret = wasm.decisionregistry_unclaimed(this.__wbg_ptr);
429
+ var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]);
430
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
431
+ return v1;
432
+ }
433
+ /**
434
+ * @param {any} token
435
+ * @param {bigint} ticket
436
+ * @returns {boolean}
437
+ */
438
+ withdraw(token, ticket) {
439
+ const ret = wasm.decisionregistry_withdraw(this.__wbg_ptr, token, ticket);
440
+ if (ret[2]) {
441
+ throw takeFromExternrefTable0(ret[1]);
442
+ }
443
+ return ret[0] !== 0;
444
+ }
445
+ }
446
+ if (Symbol.dispose) DecisionRegistry.prototype[Symbol.dispose] = DecisionRegistry.prototype.free;
447
+ exports.DecisionRegistry = DecisionRegistry;
448
+
273
449
  /**
274
450
  * The always-injected passive participant roster. A participant is any
275
451
  * plain object; `add`/`setAll` read `id`, `name`, `type`, `handle`,
@@ -1387,6 +1563,24 @@ function evaluateNextMessage(triggeringMessageId, nextMessageId) {
1387
1563
  }
1388
1564
  exports.evaluateNextMessage = evaluateNextMessage;
1389
1565
 
1566
+ /**
1567
+ * Whether `senderId` may answer an ask: `null` admits anyone; any array or
1568
+ * `Set` -- empty included -- admits only its members. Throws a plain
1569
+ * `Error` if `authorizedSenders` is not an array or `Set` of strings (or
1570
+ * `null`), or `senderId` is not a string (or `null`).
1571
+ * @param {any} authorizedSenders
1572
+ * @param {any} senderId
1573
+ * @returns {boolean}
1574
+ */
1575
+ function isAuthorizedSender(authorizedSenders, senderId) {
1576
+ const ret = wasm.isAuthorizedSender(authorizedSenders, senderId);
1577
+ if (ret[2]) {
1578
+ throw takeFromExternrefTable0(ret[1]);
1579
+ }
1580
+ return ret[0] !== 0;
1581
+ }
1582
+ exports.isAuthorizedSender = isAuthorizedSender;
1583
+
1390
1584
  /**
1391
1585
  * The one definition of "self echo" (`senderType === "Agent" &&
1392
1586
  * senderId === agentId`), shared by `evaluateDeliveryEvent` and
@@ -1643,6 +1837,10 @@ function __wbg_get_imports() {
1643
1837
  const ret = Object.entries(arg0);
1644
1838
  return ret;
1645
1839
  },
1840
+ __wbg_from_a39669ce566077da: function(arg0) {
1841
+ const ret = Array.from(arg0);
1842
+ return ret;
1843
+ },
1646
1844
  __wbg_get_6cf5a4d4d8ad3c5a: function() { return handleError(function (arg0, arg1) {
1647
1845
  const ret = Reflect.get(arg0, arg1);
1648
1846
  return ret;
@@ -1683,6 +1881,16 @@ function __wbg_get_imports() {
1683
1881
  const ret = result;
1684
1882
  return ret;
1685
1883
  },
1884
+ __wbg_instanceof_Set_daa5e1ad3c6acaeb: function(arg0) {
1885
+ let result;
1886
+ try {
1887
+ result = arg0 instanceof Set;
1888
+ } catch (_) {
1889
+ result = false;
1890
+ }
1891
+ const ret = result;
1892
+ return ret;
1893
+ },
1686
1894
  __wbg_instanceof_Uint8Array_598adc0fef426aa8: function(arg0) {
1687
1895
  let result;
1688
1896
  try {
@@ -1752,6 +1960,10 @@ function __wbg_get_imports() {
1752
1960
  const ret = arg0.push(arg1);
1753
1961
  return ret;
1754
1962
  },
1963
+ __wbg_random_7bce4e9e78e2e0d0: function() {
1964
+ const ret = Math.random();
1965
+ return ret;
1966
+ },
1755
1967
  __wbg_set_13d25b81ab403f5e: function(arg0, arg1, arg2) {
1756
1968
  arg0[arg1 >>> 0] = arg2;
1757
1969
  },
@@ -1812,6 +2024,9 @@ const AgentFailureFinalization = (typeof FinalizationRegistry === 'undefined')
1812
2024
  const ClaimRegistryFinalization = (typeof FinalizationRegistry === 'undefined')
1813
2025
  ? { register: () => {}, unregister: () => {} }
1814
2026
  : new FinalizationRegistry(ptr => wasm.__wbg_claimregistry_free(ptr, 1));
2027
+ const DecisionRegistryFinalization = (typeof FinalizationRegistry === 'undefined')
2028
+ ? { register: () => {}, unregister: () => {} }
2029
+ : new FinalizationRegistry(ptr => wasm.__wbg_decisionregistry_free(ptr, 1));
1815
2030
  const ParticipantRosterFinalization = (typeof FinalizationRegistry === 'undefined')
1816
2031
  ? { register: () => {}, unregister: () => {} }
1817
2032
  : new FinalizationRegistry(ptr => wasm.__wbg_participantroster_free(ptr, 1));
Binary file
package/index.d.ts CHANGED
@@ -419,6 +419,56 @@ export class RoomRoster {
419
419
  clear(): string[];
420
420
  }
421
421
 
422
+ export type ClaimOutcome = "claimed" | "already_claimed" | "stale";
423
+
424
+ /** What a cancellation removed, oldest first. */
425
+ export interface CancelledDecisions {
426
+ /** Asks nobody claimed -- the caller resolves these. */
427
+ unclaimed: string[];
428
+ /** Asks whose claimant still resolves them. */
429
+ claimed: string[];
430
+ }
431
+
432
+ /**
433
+ * Pending chat-mediated asks by token, oldest first. Every registration
434
+ * returns `[token, ticket]`; later calls name that exact registration, so a
435
+ * stale holder never acts on a newer ask that reused the token.
436
+ *
437
+ * Throws if `maxPending` is not an integer in `1..=4294967295` -- a zero
438
+ * capacity would evict every ask the moment it arrives -- or if `seed` is
439
+ * not a `bigint`. Omitted or `null`, `maxPending` never evicts; omitted,
440
+ * `seed` is drawn fresh. Every `token`/`key`/`roomId` argument below throws
441
+ * a plain `Error` if it is not a string, and every `ticket` if it is not a
442
+ * `bigint`.
443
+ */
444
+ export class DecisionRegistry {
445
+ constructor(maxPending?: number | null, seed?: bigint);
446
+ readonly maxPending: number | undefined;
447
+ registerMinted(roomId?: string | null): [token: string, ticket: bigint] | undefined;
448
+ registerKeyed(key: string): [token: string, ticket: bigint] | undefined;
449
+ tryClaim(token: string, ticket: bigint): ClaimOutcome;
450
+ withdraw(token: string, ticket: bigint): boolean;
451
+ forget(token: string, ticket: bigint): boolean;
452
+ unclaimed(): string[];
453
+ unclaimedInRoom(roomId: string): string[];
454
+ unclaimedCount(): number;
455
+ oldestUnclaimed(): string | undefined;
456
+ evictOldest(): string | undefined;
457
+ cancelAll(): CancelledDecisions;
458
+ cancelRoom(roomId: string): CancelledDecisions;
459
+ }
460
+
461
+ /**
462
+ * Whether `senderId` may answer an ask: `null` admits anyone; any array or
463
+ * `Set` -- empty included -- admits only its members. Throws a plain `Error`
464
+ * if `authorizedSenders` is not an array or `Set` of strings (a bare string
465
+ * included) or `null`, or if `senderId` is not a string or `null`.
466
+ */
467
+ export function isAuthorizedSender(
468
+ authorizedSenders: readonly string[] | ReadonlySet<string> | null,
469
+ senderId: string | null,
470
+ ): boolean;
471
+
422
472
  // The one-shot delivery lifecycle decisions: evaluateDeliveryEvent,
423
473
  // evaluateNextMessage, evaluateDrainCandidate, evaluateAdapterResult.
424
474
  // Each returns a plain tagged object -- these functions carry no
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@band-ai/band-sdk-core",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Shared Band event-payload validation",
5
5
  "license": "MIT",
6
6
  "repository": {