@prosody-events/prosody 0.3.1 → 0.4.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/bindings.d.ts CHANGED
@@ -71,7 +71,7 @@ export declare class NativeClient {
71
71
  * timers @returns A promise that resolves when the subscription is
72
72
  * established @throws Error if the subscription fails
73
73
  */
74
- subscribe(eventHandler: { onMessage: (err: null | Error, args: [Context, Message, Record<string, string>]) => Promise<void>; onTimer: (err: null | Error, args: [Context, Timer, Record<string, string>]) => Promise<void>; isPermanent: (args: [Error]) => boolean }): Promise<void>
74
+ subscribe(eventHandler: { onMessage: (err: null | Error, args: [NativeContext, Message, Record<string, string>]) => Promise<void>; onTimer: (err: null | Error, args: [NativeContext, Timer, Record<string, string>]) => Promise<void>; isPermanent: (args: [Error]) => boolean }): Promise<void>
75
75
  /**
76
76
  * Gets the number of partitions assigned to the consumer.
77
77
  *
@@ -159,6 +159,409 @@ export declare class NativeContext {
159
159
  * @throws Error if retrieval fails.
160
160
  */
161
161
  scheduled(otelContext: Record<string, string>): Promise<Array<Date>>
162
+ /**
163
+ * Vends the state handle for the named JSON value collection.
164
+ *
165
+ * Vending verifies the collection's registration (core-side); no span is
166
+ * opened here — vended handles outlive the call, and every operation opens
167
+ * its own span.
168
+ *
169
+ * @param name The registered collection name.
170
+ * @returns The value-state handle for this event's transaction.
171
+ * @throws Error (permanent) if the name is unregistered or its registered
172
+ * identity mismatches.
173
+ */
174
+ valueState(name: string): NativeValueState
175
+ /**
176
+ * Vends the state handle for the named JSON map collection.
177
+ *
178
+ * Vending verifies the collection's registration (core-side); no span is
179
+ * opened here — vended handles outlive the call, and every operation opens
180
+ * its own span.
181
+ *
182
+ * @param name The registered collection name.
183
+ * @returns The map-state handle for this event's transaction.
184
+ * @throws Error (permanent) if the name is unregistered or its registered
185
+ * identity mismatches.
186
+ */
187
+ mapState(name: string): NativeMapState
188
+ /**
189
+ * Vends the state handle for the named JSON deque collection.
190
+ *
191
+ * Vending verifies the collection's registration (core-side); no span is
192
+ * opened here — vended handles outlive the call, and every operation opens
193
+ * its own span.
194
+ *
195
+ * @param name The registered collection name.
196
+ * @returns The deque-state handle for this event's transaction.
197
+ * @throws Error (permanent) if the name is unregistered or its registered
198
+ * identity mismatches.
199
+ */
200
+ dequeState(name: string): NativeDequeState
201
+ /**
202
+ * Vends the state handle for the named Kafka-message value collection.
203
+ *
204
+ * Items are the full `Message` the handler received, loader-resolved on
205
+ * read. Vending verifies registration (core-side); no span is opened here.
206
+ *
207
+ * @param name The registered collection name.
208
+ * @returns The message value-state handle for this event's transaction.
209
+ * @throws Error (permanent) if the name is unregistered or its registered
210
+ * identity mismatches.
211
+ */
212
+ messageValueState(name: string): NativeValueState
213
+ /**
214
+ * Vends the state handle for the named Kafka-message map collection.
215
+ *
216
+ * Items are the full `Message` the handler received, loader-resolved on
217
+ * read. Vending verifies registration (core-side); no span is opened here.
218
+ *
219
+ * @param name The registered collection name.
220
+ * @returns The message map-state handle for this event's transaction.
221
+ * @throws Error (permanent) if the name is unregistered or its registered
222
+ * identity mismatches.
223
+ */
224
+ messageMapState(name: string): NativeMapState
225
+ /**
226
+ * Vends the state handle for the named Kafka-message deque collection.
227
+ *
228
+ * Items are the full `Message` the handler received, loader-resolved on
229
+ * read. Vending verifies registration (core-side); no span is opened here.
230
+ *
231
+ * @param name The registered collection name.
232
+ * @returns The message deque-state handle for this event's transaction.
233
+ * @throws Error (permanent) if the name is unregistered or its registered
234
+ * identity mismatches.
235
+ */
236
+ messageDequeState(name: string): NativeDequeState
237
+ }
238
+
239
+ /** Erased deque state handle, vended per event. */
240
+ export declare class NativeDequeState {
241
+ /**
242
+ * The number of live elements.
243
+ *
244
+ * @param otelContext The OpenTelemetry context for tracing.
245
+ * @returns The element count.
246
+ * @throws Error carrying the category on `cause` if the read fails, or if
247
+ * the count exceeds the `u32` range.
248
+ */
249
+ len(otelContext: Record<string, string>): Promise<number>
250
+ /**
251
+ * Whether the deque holds no live elements.
252
+ *
253
+ * @param otelContext The OpenTelemetry context for tracing.
254
+ * @returns True when the deque is empty.
255
+ * @throws Error carrying the category on `cause` if the read fails.
256
+ */
257
+ isEmpty(otelContext: Record<string, string>): Promise<boolean>
258
+ /**
259
+ * Reads the element at front-relative position `index`.
260
+ *
261
+ * @param index The zero-based position from the front.
262
+ * @param otelContext The OpenTelemetry context for tracing.
263
+ * @returns The element, or null past the end.
264
+ * @throws Error carrying the category on `cause` if the read fails.
265
+ */
266
+ get(index: number, otelContext: Record<string, string>): Promise<any | Message | null>
267
+ /**
268
+ * Reads the front endpoint SLOT without a length round trip — exactly
269
+ * `get(0)`.
270
+ *
271
+ * Decodes and resolves the returned element (unlike eviction). An empty
272
+ * deque, or a front endpoint slot expired under a TTL, yields null even
273
+ * when live interior elements exist — a peek never searches inward.
274
+ *
275
+ * @param otelContext The OpenTelemetry context for tracing.
276
+ * @returns The front element, or null when the endpoint slot is empty.
277
+ * @throws Error carrying the category on `cause` if the read fails.
278
+ */
279
+ peekFront(otelContext: Record<string, string>): Promise<any | Message | null>
280
+ /**
281
+ * Reads the back endpoint SLOT without a length round trip — exactly
282
+ * `get(len − 1)`.
283
+ *
284
+ * Decodes and resolves the returned element (unlike eviction). An empty
285
+ * deque, or a back endpoint slot expired under a TTL, yields null even
286
+ * when live interior elements exist — a peek never searches inward.
287
+ *
288
+ * @param otelContext The OpenTelemetry context for tracing.
289
+ * @returns The back element, or null when the endpoint slot is empty.
290
+ * @throws Error carrying the category on `cause` if the read fails.
291
+ */
292
+ peekBack(otelContext: Record<string, string>): Promise<any | Message | null>
293
+ /**
294
+ * Appends an element at the back.
295
+ *
296
+ * JSON null is not a storable element and is rejected with a transient
297
+ * error. An item-shape mismatch is a caller mistake and is likewise
298
+ * transient (it retries and stays visible, never discarded).
299
+ *
300
+ * @param item The value (JSON) or message to append.
301
+ * @param otelContext The OpenTelemetry context for tracing.
302
+ * @throws Error carrying the category on `cause` if the write fails.
303
+ */
304
+ pushBack(item: any, otelContext: Record<string, string>): Promise<void>
305
+ /**
306
+ * Prepends an element at the front.
307
+ *
308
+ * JSON null is not a storable element and is rejected with a transient
309
+ * error. An item-shape mismatch is a caller mistake and is likewise
310
+ * transient (it retries and stays visible, never discarded).
311
+ *
312
+ * @param item The value (JSON) or message to prepend.
313
+ * @param otelContext The OpenTelemetry context for tracing.
314
+ * @throws Error carrying the category on `cause` if the write fails.
315
+ */
316
+ pushFront(item: any, otelContext: Record<string, string>): Promise<void>
317
+ /**
318
+ * Removes and returns the front element.
319
+ *
320
+ * @param otelContext The OpenTelemetry context for tracing.
321
+ * @returns The removed front element, or null when empty.
322
+ * @throws Error carrying the category on `cause` if the operation fails.
323
+ */
324
+ popFront(otelContext: Record<string, string>): Promise<any | Message | null>
325
+ /**
326
+ * Removes and returns the back element.
327
+ *
328
+ * @param otelContext The OpenTelemetry context for tracing.
329
+ * @returns The removed back element, or null when empty.
330
+ * @throws Error carrying the category on `cause` if the operation fails.
331
+ */
332
+ popBack(otelContext: Record<string, string>): Promise<any | Message | null>
333
+ /**
334
+ * Removes every element.
335
+ *
336
+ * @param otelContext The OpenTelemetry context for tracing.
337
+ * @throws Error carrying the category on `cause` if the clear fails.
338
+ */
339
+ clear(otelContext: Record<string, string>): Promise<void>
340
+ /**
341
+ * Opens a demand-driven cursor over the live elements in index order.
342
+ *
343
+ * Synchronous — it performs no I/O. The extracted JavaScript context is
344
+ * active while core constructs its semantic stream span; chunk pulls do
345
+ * not create binding spans.
346
+ *
347
+ * @param direction The scan direction (`"forward"` or `"backward"`).
348
+ * @param otelContext The OpenTelemetry context for tracing.
349
+ * @returns A cursor over the deque elements.
350
+ * @throws Error if the direction token is invalid.
351
+ */
352
+ scan(direction: string, otelContext: Record<string, string>): NativeStateCursor
353
+ /**
354
+ * Durably commits the buffered operations mid-handler.
355
+ *
356
+ * @param otelContext The OpenTelemetry context for tracing.
357
+ * @throws Error carrying the category on `cause` if the commit fails.
358
+ */
359
+ commit(otelContext: Record<string, string>): Promise<void>
360
+ /**
361
+ * Discards the buffered uncommitted operations.
362
+ *
363
+ * @param otelContext The OpenTelemetry context for tracing.
364
+ */
365
+ rollback(otelContext: Record<string, string>): Promise<void>
366
+ }
367
+
368
+ /** Erased ordered-map state handle, keyed by `String`, vended per event. */
369
+ export declare class NativeMapState {
370
+ /**
371
+ * Reads the value for `key`.
372
+ *
373
+ * @param key The map key.
374
+ * @param otelContext The OpenTelemetry context for tracing.
375
+ * @returns The value, or null when the key is absent.
376
+ * @throws Error carrying the category on `cause` if the read fails.
377
+ */
378
+ get(key: string, otelContext: Record<string, string>): Promise<any | Message | null>
379
+ /**
380
+ * Reads several keys in a single call.
381
+ *
382
+ * Returns one entry per key, in the same order requested: the entry at
383
+ * index `i` is the value for `keys[i]`. A key that isn't there reads as
384
+ * null, and a key listed more than once is answered at each of its spots.
385
+ * The whole read happens as one step, so no other change to this event's
386
+ * state can slip in partway through.
387
+ *
388
+ * @param keys The keys to read, in order.
389
+ * @param otelContext The OpenTelemetry context for tracing.
390
+ * @returns One result per input key; null for a key that is absent.
391
+ * @throws Error carrying the category on `cause` if the read fails.
392
+ */
393
+ getMany(keys: Array<string>, otelContext: Record<string, string>): Promise<Array<any | Message | undefined | null>>
394
+ /**
395
+ * Reports whether a stored cell exists for `key`.
396
+ *
397
+ * Reads the event's dirty overlay (read-your-writes) and answers presence
398
+ * WITHOUT decoding the value or running the resolver — a message-backed
399
+ * map answers with zero Kafka fetches and can report `true` for a
400
+ * message that can no longer be fetched. This is NOT "no I/O": a cache
401
+ * miss can still reach Cassandra, so it is async and fallible exactly
402
+ * like `get`.
403
+ *
404
+ * @param key The map key.
405
+ * @param otelContext The OpenTelemetry context for tracing.
406
+ * @returns True when a stored cell exists for `key`.
407
+ * @throws Error carrying the category on `cause` if the read fails.
408
+ */
409
+ contains(key: string, otelContext: Record<string, string>): Promise<boolean>
410
+ /**
411
+ * Inserts or overwrites `key`.
412
+ *
413
+ * JSON null is rejected with a transient error naming `delete` as the way
414
+ * to remove an entry. An item-shape mismatch is a caller mistake and is
415
+ * likewise transient (it retries and stays visible, never discarded).
416
+ *
417
+ * @param key The map key.
418
+ * @param item The value (JSON) or message to store.
419
+ * @param otelContext The OpenTelemetry context for tracing.
420
+ * @throws Error carrying the category on `cause` if the write fails.
421
+ */
422
+ set(key: string, item: any, otelContext: Record<string, string>): Promise<void>
423
+ /**
424
+ * Removes `key`.
425
+ *
426
+ * @param key The map key.
427
+ * @param otelContext The OpenTelemetry context for tracing.
428
+ * @throws Error carrying the category on `cause` if the removal fails.
429
+ */
430
+ remove(key: string, otelContext: Record<string, string>): Promise<void>
431
+ /**
432
+ * Removes every entry.
433
+ *
434
+ * @param otelContext The OpenTelemetry context for tracing.
435
+ * @throws Error carrying the category on `cause` if the clear fails.
436
+ */
437
+ clear(otelContext: Record<string, string>): Promise<void>
438
+ /**
439
+ * Opens a demand-driven cursor over the live entries in key order.
440
+ *
441
+ * Synchronous — it performs no I/O. The extracted JavaScript context is
442
+ * active while core constructs its semantic stream span; chunk pulls do
443
+ * not create binding spans. Entries are yielded as `(key, value)` pairs.
444
+ *
445
+ * @param direction The scan direction (`"forward"` or `"backward"`).
446
+ * @param otelContext The OpenTelemetry context for tracing.
447
+ * @returns A cursor over the map entries.
448
+ * @throws Error if the direction token is invalid.
449
+ */
450
+ scan(direction: string, otelContext: Record<string, string>): NativeStateCursor
451
+ /**
452
+ * Opens a demand-driven cursor over the live KEYS in key order.
453
+ *
454
+ * Skips the value codec and the resolver (no value decode, no Kafka
455
+ * fetch), so a message-backed map enumerates keys with zero Kafka
456
+ * fetches — but it still reads presence, so it is not zero-I/O.
457
+ * Synchronous like `scan`: the extracted JavaScript context is active
458
+ * while core constructs its semantic stream span. Yields bare keys.
459
+ *
460
+ * @param direction The scan direction (`"forward"` or `"backward"`).
461
+ * @param otelContext The OpenTelemetry context for tracing.
462
+ * @returns A cursor over the map keys.
463
+ * @throws Error if the direction token is invalid.
464
+ */
465
+ keys(direction: string, otelContext: Record<string, string>): NativeStateCursor
466
+ /**
467
+ * Durably commits the buffered operations mid-handler.
468
+ *
469
+ * @param otelContext The OpenTelemetry context for tracing.
470
+ * @throws Error carrying the category on `cause` if the commit fails.
471
+ */
472
+ commit(otelContext: Record<string, string>): Promise<void>
473
+ /**
474
+ * Discards the buffered uncommitted operations.
475
+ *
476
+ * @param otelContext The OpenTelemetry context for tracing.
477
+ */
478
+ rollback(otelContext: Record<string, string>): Promise<void>
479
+ }
480
+
481
+ /**
482
+ * Demand-driven scan cursor over a map or deque collection.
483
+ *
484
+ * Pulling is lazy: each `next_chunk()` restores the JavaScript context without
485
+ * creating a binding span, awaits one stream item, and asks core to drain only
486
+ * the immediately-ready tail. Chunking,
487
+ * exhaustion, error ordering, serialization, close-idempotence, and
488
+ * use-after-close behavior are core-owned; this layer only transports.
489
+ */
490
+ export declare class NativeStateCursor {
491
+ /**
492
+ * Pulls the next immediately-ready chunk of scanned items.
493
+ *
494
+ * Awaits the first item, then drains up to 255 more items only while they
495
+ * are immediately ready. This amortizes N-API overhead without waiting to
496
+ * fill a chunk.
497
+ *
498
+ * @param otelContext The OpenTelemetry context for tracing.
499
+ * @returns The next non-empty vector of items, or null when exhausted.
500
+ * @throws Error carrying the category on `cause` if the pull fails or the
501
+ * cursor was closed.
502
+ */
503
+ nextChunk(otelContext: Record<string, string>): Promise<Array<any | [string, any] | Message | [string, Message]> | null>
504
+ /**
505
+ * Closes the cursor, releasing the underlying stream.
506
+ *
507
+ * Idempotent; a subsequent `next_chunk()` errors. No span — pure teardown.
508
+ *
509
+ * @returns A promise that resolves when the cursor is closed.
510
+ */
511
+ close(): Promise<void>
512
+ }
513
+
514
+ /**
515
+ * Erased single-value state handle, vended per event.
516
+ *
517
+ * Wraps the boxed erased value handle plus the propagator used to open each
518
+ * operation's span. Values cross as JSON; message collections cross as the
519
+ * `Message` object.
520
+ */
521
+ export declare class NativeValueState {
522
+ /**
523
+ * Reads the current value.
524
+ *
525
+ * @param otelContext The OpenTelemetry context for tracing.
526
+ * @returns The current value, or null when absent/cleared.
527
+ * @throws Error carrying the category on `cause` if the read fails.
528
+ */
529
+ get(otelContext: Record<string, string>): Promise<any | Message | null>
530
+ /**
531
+ * Buffers a write of the value.
532
+ *
533
+ * JSON null is rejected with a transient error naming `clear` as the way
534
+ * to delete. A message-shaped payload cannot be stored in a JSON
535
+ * collection (and vice versa); such a mismatch is a caller mistake and is
536
+ * likewise transient (it retries and stays visible, never discarded).
537
+ *
538
+ * @param item The value (JSON) or message to write.
539
+ * @param otelContext The OpenTelemetry context for tracing.
540
+ * @throws Error carrying the category on `cause` if the write fails.
541
+ */
542
+ set(item: any, otelContext: Record<string, string>): Promise<void>
543
+ /**
544
+ * Buffers a clear of the value.
545
+ *
546
+ * @param otelContext The OpenTelemetry context for tracing.
547
+ * @throws Error carrying the category on `cause` if the clear fails.
548
+ */
549
+ clear(otelContext: Record<string, string>): Promise<void>
550
+ /**
551
+ * Durably commits the buffered operations mid-handler.
552
+ *
553
+ * @param otelContext The OpenTelemetry context for tracing.
554
+ * @throws Error carrying the category on `cause` if the commit fails.
555
+ */
556
+ commit(otelContext: Record<string, string>): Promise<void>
557
+ /**
558
+ * Discards the buffered uncommitted operations.
559
+ *
560
+ * Infallible: rolling back a terminated session is a no-op.
561
+ *
562
+ * @param otelContext The OpenTelemetry context for tracing.
563
+ */
564
+ rollback(otelContext: Record<string, string>): Promise<void>
162
565
  }
163
566
 
164
567
  /** Configuration options for the Prosody client. */
@@ -173,7 +576,7 @@ export interface Configuration {
173
576
  groupId?: string
174
577
  /**
175
578
  * Global shared cache capacity across all partitions for deduplicating
176
- * messages. Set to 0 to disable deduplication entirely.
579
+ * messages. Must be greater than 0.
177
580
  */
178
581
  idempotenceCacheSize?: number
179
582
  /**
@@ -313,8 +716,8 @@ export interface Configuration {
313
716
  /** Sliding window duration (in milliseconds) for failure rate tracking. */
314
717
  deferFailureWindowMs?: number
315
718
  /**
316
- * Cache size for defer middleware.
317
- * Controls capacity for store cache and loader cache.
719
+ * Cache size for the deferred-retry message loader.
720
+ * Controls capacity for the loader cache.
318
721
  */
319
722
  deferCacheSize?: number
320
723
  /**
@@ -354,6 +757,37 @@ export interface Configuration {
354
757
  * or `"follows_from"`. Default: `"follows_from"`.
355
758
  */
356
759
  timerSpans?: string
760
+ /**
761
+ * Keyed-state collections to register before subscribe.
762
+ *
763
+ * Each entry declares one collection by name, kind, and payload. Duplicate
764
+ * names within this set are rejected.
765
+ */
766
+ stateCollections?: Array<StateCollectionConfig>
767
+ /**
768
+ * Disk workspace for the local keyed-state cache.
769
+ *
770
+ * Each live client needs its own directory because it is locked
771
+ * exclusively. Falls back to the `PROSODY_STATE_CACHE_DIR` environment
772
+ * variable, then a per-client temporary directory. Must not be an
773
+ * empty string when set.
774
+ */
775
+ stateCacheDir?: string
776
+ /**
777
+ * Capacity of the in-memory keyed-state cache, in bytes. Falls back to
778
+ * `PROSODY_STATE_CACHE_SIZE_BYTES`,
779
+ * then to the storage-engine default. Must be a positive safe integer.
780
+ */
781
+ stateCacheSizeBytes?: number
782
+ /**
783
+ * Delay in whole seconds between staging a provisional cell and the
784
+ * keyed-state recovery sweep. Every registered TTL must strictly exceed
785
+ * this. Falls back to the `PROSODY_STATE_RECOVERY_DELAY` environment
786
+ * variable (a duration string such as `30s`), then to 30 seconds. Must be
787
+ * a whole number of seconds >= 1 when set (fractional, negative, and
788
+ * non-finite values are rejected).
789
+ */
790
+ stateRecoveryDelaySeconds?: number
357
791
  }
358
792
 
359
793
  /**
@@ -510,6 +944,48 @@ export declare function setLogger(logger: Logger): void
510
944
  */
511
945
  export declare function setLoggerIfUnset(logger: Logger): boolean
512
946
 
947
+ /** Declares one keyed-state collection to register before subscribe. */
948
+ export interface StateCollectionConfig {
949
+ /**
950
+ * The collection name. Must be non-empty and unique within the client's
951
+ * definition set.
952
+ */
953
+ name: string
954
+ /** The collection kind: `"value"`, `"map"`, or `"deque"`. */
955
+ kind: string
956
+ /**
957
+ * The item payload: `"json"` (JSON values) or `"message"` (the full Kafka
958
+ * message the handler received).
959
+ */
960
+ payload: string
961
+ /**
962
+ * Optional per-write TTL in whole seconds. Must be a whole number >= 1
963
+ * (fractional, negative, and non-finite values are rejected) and must
964
+ * exceed the recovery delay (the latter checked at consumer build).
965
+ */
966
+ ttlSeconds?: number
967
+ /**
968
+ * Optional opt-out of transactional staging (read-uncommitted, at-least
969
+ * once). Defaults to transactional.
970
+ */
971
+ readUncommitted?: boolean
972
+ /**
973
+ * Optional map-only keyset bound (`0..=4096`; default 128 core-side; `0`
974
+ * disables ordered-scan tracking). Must be a whole number in that range
975
+ * (fractional, negative, and non-finite values are rejected). Invalid on
976
+ * value or deque collections.
977
+ */
978
+ keysetLimit?: number
979
+ /**
980
+ * Optional deque-only capacity (bounded backlog). Must be a whole number
981
+ * >= 1 (fractional, zero, negative, and non-finite values are rejected).
982
+ * Runtime configuration only — not persisted, not part of collection
983
+ * identity, and freely changeable across deploys; enforced lazily on push.
984
+ * Invalid on value or map collections.
985
+ */
986
+ capacity?: number
987
+ }
988
+
513
989
  /**
514
990
  * Represents a timer event.
515
991
  *