@prosody-events/prosody 0.5.1 → 0.6.1

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.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The double-ended-queue keyed-state handle.
3
+ * @module lib/state/deque
4
+ */
5
+
6
+ import type { JsonValue } from "../payload";
7
+ import type { StateTransaction } from "./handle";
8
+ import type { PositionQueryOptions, ScanDirection } from "./query";
9
+
10
+ /**
11
+ * Typed handle over a double-ended-queue keyed-state collection, vended by
12
+ * `Context.state()`. Valid only within the handler invocation (attempt) that
13
+ * vended it. Every method opens its own per-operation trace span.
14
+ * A failed call rejects with a {@link PermanentStateError} or a
15
+ * {@link TransientStateError}. A write of a value with no JSON form rejects
16
+ * with a {@link TransientStateError}.
17
+ */
18
+ export declare class DequeState<T = JsonValue> {
19
+ /** Vended only by {@link Context#state}; not constructible directly. */
20
+ private constructor(native: unknown);
21
+ /**
22
+ * Appends an element at the back. The item type excludes `null` and
23
+ * `undefined`, because a top-level `null` is not a storable element. At run
24
+ * time Prosody rejects JSON `null` with a {@link PermanentStateError}. A
25
+ * nested `null` is stored.
26
+ */
27
+ push(item: NonNullable<T>): Promise<void>;
28
+ /**
29
+ * Prepends an element at the front. The item type excludes `null`/`undefined`
30
+ * (via {@link !NonNullable}); see {@link DequeState#push}.
31
+ */
32
+ unshift(item: NonNullable<T>): Promise<void>;
33
+ /** Removes and returns the back element, or null when empty. */
34
+ pop(): Promise<T | null>;
35
+ /** Removes and returns the front element, or null when empty. */
36
+ shift(): Promise<T | null>;
37
+ /** Returns the number of live elements. */
38
+ length(): Promise<number>;
39
+ /** Reports whether the deque holds no live elements. */
40
+ isEmpty(): Promise<boolean>;
41
+ /** Removes every element. */
42
+ clear(): Promise<void>;
43
+ /**
44
+ * Reads the element at `index`, like `Array.prototype.at`: a non-negative
45
+ * `index` counts from the front (`0` is the front), a negative `index` counts
46
+ * back from the end (`-1` is the back). Any out-of-range position — including
47
+ * every index on an empty deque — resolves to null. `index` must be a safe
48
+ * integer; a fractional, `NaN`, or infinite value is a caller mistake,
49
+ * rejected with a {@link TransientStateError} (it retries and stays visible).
50
+ *
51
+ * A non-negative `index` and `at(-1)` make a single read. Any other negative
52
+ * `index` is resolved against the current {@link DequeState#length}, so it
53
+ * makes an extra read.
54
+ *
55
+ * Under a TTL, an expired element reads as null. This is also true for an
56
+ * expired front or back element when live elements remain inside the deque.
57
+ */
58
+ at(index: number): Promise<T | null>;
59
+ /**
60
+ * Async iterator over the live elements in index order. Pass a direction or
61
+ * a {@link PositionQueryOptions} to select elements. Valid only within the handler
62
+ * invocation (attempt) that opened it; early exit from a `for await` loop
63
+ * closes the underlying cursor.
64
+ */
65
+ values(
66
+ options?: ScanDirection | PositionQueryOptions,
67
+ ): AsyncIterableIterator<T>;
68
+ /**
69
+ * Forward iteration over the elements. Valid only within the handler
70
+ * invocation (attempt) that opened it.
71
+ */
72
+ [Symbol.asyncIterator](): AsyncIterableIterator<T>;
73
+ }
74
+
75
+ export interface DequeState<T = JsonValue> extends StateTransaction {}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The double-ended-queue keyed-state handle.
3
+ * @module lib/state/deque
4
+ * @private
5
+ */
6
+
7
+ const { stateOp } = require("./bridge");
8
+ const { StateHandle } = require("./handle");
9
+ const { stateIterator } = require("./iterator");
10
+ const { dequePosition, positionQuery } = require("./query");
11
+
12
+ /** Typed handle over a double-ended-queue keyed-state collection. */
13
+ class DequeState extends StateHandle {
14
+ push(item) {
15
+ return stateOp((carrier) =>
16
+ this.native.pushBack(this.items.encode(item), carrier),
17
+ );
18
+ }
19
+
20
+ unshift(item) {
21
+ return stateOp((carrier) =>
22
+ this.native.pushFront(this.items.encode(item), carrier),
23
+ );
24
+ }
25
+
26
+ pop() {
27
+ return stateOp((carrier) => this.native.popBack(carrier)).then(
28
+ this.items.decode,
29
+ );
30
+ }
31
+
32
+ shift() {
33
+ return stateOp((carrier) => this.native.popFront(carrier)).then(
34
+ this.items.decode,
35
+ );
36
+ }
37
+
38
+ length() {
39
+ return stateOp((carrier) => this.native.len(carrier));
40
+ }
41
+
42
+ isEmpty() {
43
+ return stateOp((carrier) => this.native.isEmpty(carrier));
44
+ }
45
+
46
+ clear() {
47
+ return stateOp((carrier) => this.native.clear(carrier));
48
+ }
49
+
50
+ async at(index) {
51
+ if (index === -1) {
52
+ return this.items.decode(
53
+ await stateOp((carrier) => this.native.peekBack(carrier)),
54
+ );
55
+ }
56
+ const position = await dequePosition(index, () => this.length());
57
+ if (position === null) return null;
58
+ return this.items.decode(
59
+ await stateOp((carrier) => this.native.get(position, carrier)),
60
+ );
61
+ }
62
+
63
+ values(options) {
64
+ const query = positionQuery(options);
65
+ return stateIterator(this.native.values(query), (item) =>
66
+ this.items.decode(item),
67
+ );
68
+ }
69
+
70
+ [Symbol.asyncIterator]() {
71
+ return this.values();
72
+ }
73
+ }
74
+
75
+ module.exports = {
76
+ DequeState,
77
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The commit and rollback methods of every state handle, and their outcome.
3
+ * @module lib/state/handle
4
+ */
5
+
6
+ /**
7
+ * The effect of `commit()` or `rollback()`. `"applied"` means the call wrote or
8
+ * discarded buffered operations. `"noOp"` means nothing was buffered.
9
+ */
10
+ export type StoreOutcome = "applied" | "noOp";
11
+
12
+ /** The commit and rollback methods of every state handle. */
13
+ export interface StateTransaction {
14
+ /**
15
+ * Durably commits the buffered operations mid-handler (at-least-once). The
16
+ * committed changes survive a later rollback or a failed event. Resolves to
17
+ * `"applied"` when it wrote buffered operations, or `"noOp"`.
18
+ */
19
+ commit(): Promise<StoreOutcome>;
20
+ /**
21
+ * Discards buffered uncommitted operations back to the committed floor.
22
+ * Resolves to `"applied"` when it discarded buffered operations, or `"noOp"`.
23
+ */
24
+ rollback(): Promise<StoreOutcome>;
25
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The base class of the keyed-state handles: commit and rollback.
3
+ * @module lib/state/handle
4
+ * @private
5
+ */
6
+
7
+ const { defineHidden, stateOp } = require("./bridge");
8
+
9
+ /** The base class of every keyed-state handle: commit and rollback. */
10
+ class StateHandle {
11
+ constructor(native, items) {
12
+ defineHidden(this, { native, items });
13
+ }
14
+
15
+ commit() {
16
+ return stateOp((carrier) => this.native.commit(carrier));
17
+ }
18
+
19
+ rollback() {
20
+ return stateOp((carrier) => this.native.rollback(carrier));
21
+ }
22
+ }
23
+
24
+ module.exports = {
25
+ StateHandle,
26
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Adapts a chunked native scan cursor to a JavaScript async iterator.
3
+ * @module lib/state/iterator
4
+ * @private
5
+ */
6
+
7
+ const { injectedCarrier, toStateError } = require("./bridge");
8
+
9
+ /**
10
+ * Adapts a chunked native scan cursor to an async generator. Each native pull
11
+ * gets a fresh trace carrier and records no span. Each `next()` call takes the
12
+ * next item of the held chunk without an N-API call. A native `null` means
13
+ * the scan is done. The generator queues concurrent `next()` and `return()`
14
+ * calls, so no item is yielded twice and no close runs during a pull.
15
+ *
16
+ * The end of the scan, an early exit from a `for await` loop, and a pull or
17
+ * decode error all close the cursor. Every failure a caller sees goes through
18
+ * `toStateError`.
19
+ *
20
+ * Owned cursors stay fenced to their attempt. Published cursors stay valid
21
+ * with their reader.
22
+ * @param {object} cursor - The native scan cursor.
23
+ * @param {(item: *) => *} transform - Maps each raw item to the yielded value.
24
+ * @returns {AsyncGenerator<*>} The async iterator.
25
+ * @private
26
+ */
27
+ async function* stateIterator(cursor, transform) {
28
+ let failed = false;
29
+ try {
30
+ for (;;) {
31
+ const chunk = await cursor.nextChunk(injectedCarrier());
32
+ if (chunk === null) return;
33
+ for (let index = 0; index < chunk.length; index += 1) {
34
+ const item = chunk[index];
35
+ // Release consumed values even while the rest of a large chunk remains.
36
+ chunk[index] = undefined;
37
+ yield transform(item);
38
+ }
39
+ }
40
+ } catch (error) {
41
+ failed = true;
42
+ throw toStateError(error);
43
+ } finally {
44
+ await close(cursor, failed);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Closes a native scan cursor. After a failure the close is best effort and
50
+ * never hides that error.
51
+ * @param {object} cursor - The native scan cursor.
52
+ * @param {boolean} quietly - Whether to ignore a close failure.
53
+ * @returns {Promise<void>} Resolves when the cursor is closed.
54
+ * @private
55
+ */
56
+ async function close(cursor, quietly) {
57
+ try {
58
+ await cursor.close();
59
+ } catch (error) {
60
+ if (!quietly) throw toStateError(error);
61
+ }
62
+ }
63
+
64
+ module.exports = {
65
+ stateIterator,
66
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The ordered-map keyed-state handle.
3
+ * @module lib/state/map
4
+ */
5
+
6
+ import type { JsonValue } from "../payload";
7
+ import type { StateTransaction } from "./handle";
8
+ import type { KeyQueryOptions, ScanDirection } from "./query";
9
+
10
+ /**
11
+ * Typed handle over an ordered-map keyed-state collection, vended by
12
+ * `Context.state()`. Map keys are always `string`. Valid only within the
13
+ * handler invocation (attempt) that vended it. Every method opens its own
14
+ * per-operation trace span.
15
+ * A failed call rejects with a {@link PermanentStateError} or a
16
+ * {@link TransientStateError}. A write of a value with no JSON form rejects
17
+ * with a {@link TransientStateError}.
18
+ */
19
+ export declare class MapState<V = JsonValue> {
20
+ /** Vended only by {@link Context#state}; not constructible directly. */
21
+ private constructor(native: unknown);
22
+ /** Reads the value for `key`, or null when the key is absent. */
23
+ get(key: string): Promise<V | null>;
24
+ /**
25
+ * Reads several keys in one call. The result has one entry per key, in the
26
+ * order given, so `result[i]` is the value for `keys[i]`. An absent key reads
27
+ * as `null`. A repeated key gets an entry at each position. No other change
28
+ * to the state of this event occurs during the read.
29
+ */
30
+ getMany(keys: readonly string[]): Promise<(V | null)[]>;
31
+ /**
32
+ * Reports whether `key` currently has a stored value. It skips the value
33
+ * decode and the resolver. A message map answers with no Kafka fetch, so it
34
+ * can report `true` for a message that Kafka no longer holds. A cache miss
35
+ * still reads the store, but the call costs less than {@link MapState#get}.
36
+ */
37
+ has(key: string): Promise<boolean>;
38
+ /**
39
+ * Tests several keys for presence in one read. `result[i]` answers
40
+ * `keys[i]`. Like {@link MapState#has}, it skips the value decode.
41
+ */
42
+ hasMany(keys: readonly string[]): Promise<boolean[]>;
43
+ /** Reports whether the map holds no live entries. */
44
+ isEmpty(): Promise<boolean>;
45
+ /**
46
+ * Inserts or overwrites `key`. The value type excludes `null` and
47
+ * `undefined`, because a top-level `null` is not a storable value. At run
48
+ * time Prosody rejects JSON `null` with a {@link PermanentStateError}; use
49
+ * {@link MapState#delete} to remove an entry. A nested `null` is stored.
50
+ */
51
+ set(key: string, value: NonNullable<V>): Promise<void>;
52
+ /**
53
+ * Removes `key`.
54
+ *
55
+ * Unlike `Map#delete`, it returns no "was present" flag, because that flag
56
+ * needs a read on every delete.
57
+ */
58
+ delete(key: string): Promise<void>;
59
+ /** Removes every entry. */
60
+ clear(): Promise<void>;
61
+ /**
62
+ * Async iterator over the live `[key, value]` entries in key order. Pass a
63
+ * direction or a {@link KeyQueryOptions} to select entries. Valid only within the
64
+ * handler invocation (attempt) that opened it; early exit from a
65
+ * `for await` loop closes the underlying cursor.
66
+ */
67
+ entries(
68
+ options?: ScanDirection | KeyQueryOptions,
69
+ ): AsyncIterableIterator<[string, V]>;
70
+ /**
71
+ * Async iterator over the live keys in key order. Takes the same options as
72
+ * {@link MapState#entries}. Skips the value decode and the resolver, so a message-backed map enumerates keys with zero Kafka
73
+ * fetches; it still reads presence, so it is not zero-I/O. Valid only within
74
+ * the handler invocation (attempt) that opened it; early exit from a
75
+ * `for await` loop closes the underlying cursor.
76
+ */
77
+ keys(
78
+ options?: ScanDirection | KeyQueryOptions,
79
+ ): AsyncIterableIterator<string>;
80
+ /**
81
+ * Async iterator over the live values in key order. Takes the same options
82
+ * as {@link MapState#entries}. Valid only within the handler invocation
83
+ * (attempt) that opened it; early exit from a `for await` loop closes the
84
+ * underlying cursor.
85
+ */
86
+ values(options?: ScanDirection | KeyQueryOptions): AsyncIterableIterator<V>;
87
+ /**
88
+ * Forward iteration over `[key, value]` entries. Valid only within the
89
+ * handler invocation (attempt) that opened it.
90
+ */
91
+ [Symbol.asyncIterator](): AsyncIterableIterator<[string, V]>;
92
+ }
93
+
94
+ export interface MapState<V = JsonValue> extends StateTransaction {}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The ordered-map keyed-state handle.
3
+ * @module lib/state/map
4
+ * @private
5
+ */
6
+
7
+ const { stateOp } = require("./bridge");
8
+ const { StateHandle } = require("./handle");
9
+ const { stateIterator } = require("./iterator");
10
+ const { keyQuery } = require("./query");
11
+
12
+ /** Typed handle over an ordered-map keyed-state collection. */
13
+ class MapState extends StateHandle {
14
+ get(key) {
15
+ return stateOp((carrier) => this.native.get(key, carrier)).then(
16
+ this.items.decode,
17
+ );
18
+ }
19
+
20
+ getMany(keys) {
21
+ return stateOp((carrier) => this.native.getMany(keys, carrier)).then(
22
+ (items) => items.map(this.items.decode),
23
+ );
24
+ }
25
+
26
+ has(key) {
27
+ return stateOp((carrier) => this.native.contains(key, carrier));
28
+ }
29
+
30
+ hasMany(keys) {
31
+ return stateOp((carrier) => this.native.containsMany(keys, carrier));
32
+ }
33
+
34
+ isEmpty() {
35
+ return stateOp((carrier) => this.native.isEmpty(carrier));
36
+ }
37
+
38
+ set(key, value) {
39
+ return stateOp((carrier) =>
40
+ this.native.set(key, this.items.encode(value), carrier),
41
+ );
42
+ }
43
+
44
+ delete(key) {
45
+ return stateOp((carrier) => this.native.remove(key, carrier));
46
+ }
47
+
48
+ clear() {
49
+ return stateOp((carrier) => this.native.clear(carrier));
50
+ }
51
+
52
+ entries(options) {
53
+ const query = keyQuery(options);
54
+ return stateIterator(this.native.entries(query), ([key, item]) => [
55
+ key,
56
+ this.items.decode(item),
57
+ ]);
58
+ }
59
+
60
+ keys(options) {
61
+ const query = keyQuery(options);
62
+ return stateIterator(this.native.keys(query), (key) => key);
63
+ }
64
+
65
+ values(options) {
66
+ const query = keyQuery(options);
67
+ return stateIterator(this.native.entries(query), ([, item]) =>
68
+ this.items.decode(item),
69
+ );
70
+ }
71
+
72
+ [Symbol.asyncIterator]() {
73
+ return this.entries();
74
+ }
75
+ }
76
+
77
+ module.exports = {
78
+ MapState,
79
+ };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The read-only readers of published value, map, set, and deque collections.
3
+ *
4
+ * `ProsodyClient.state(subsystem, definition)` opens a reader. A reader reads
5
+ * the committed state of another consumer group, so every read takes the
6
+ * partition key. It is independent of subscription and stays valid while its
7
+ * client runs. A failed read rejects with a {@link TransientStateError} or a
8
+ * {@link PermanentStateError}, like a read through an owned handle. A reader
9
+ * error at open, such as a zero `readCache` TTL, rejects `state()` the same way.
10
+ * @module lib/state/published
11
+ */
12
+
13
+ import type { JsonValue } from "../payload";
14
+ import type { PermanentStateError, TransientStateError } from "../errors";
15
+ import type {
16
+ KeyQueryOptions,
17
+ PositionQueryOptions,
18
+ ScanDirection,
19
+ } from "./query";
20
+
21
+ /** Per-collection cache override for published reads. */
22
+ export interface ReadCacheOptions {
23
+ /** Cache duration in milliseconds. */
24
+ ttlMs: number;
25
+ }
26
+
27
+ /** Read-only published value collection. */
28
+ export declare class PublishedValue<T = JsonValue> {
29
+ private constructor();
30
+
31
+ /** Reads the committed value for `key`, or null when it is absent. */
32
+ get(key: string): Promise<T | null>;
33
+ }
34
+
35
+ /** Read-only published map collection. */
36
+ export declare class PublishedMap<V = JsonValue> {
37
+ private constructor();
38
+
39
+ /** Reads the committed value of `mapKey` for `key`, or null when absent. */
40
+ get(key: string, mapKey: string): Promise<V | null>;
41
+ /**
42
+ * Reads several map keys in one call. The result has one entry per map key,
43
+ * in the order given, and an absent map key reads as null.
44
+ */
45
+ getMany(key: string, mapKeys: readonly string[]): Promise<Array<V | null>>;
46
+ /** Reports whether the map for `key` holds `mapKey`. */
47
+ has(key: string, mapKey: string): Promise<boolean>;
48
+ /** Tests several map keys in one call. `result[i]` answers `mapKeys[i]`. */
49
+ hasMany(key: string, mapKeys: readonly string[]): Promise<boolean[]>;
50
+ /** Reports whether the map for `key` holds no entries. */
51
+ isEmpty(key: string): Promise<boolean>;
52
+ /** Iterates the selected `[mapKey, value]` entries in key order. */
53
+ entries(
54
+ key: string,
55
+ options?: ScanDirection | KeyQueryOptions,
56
+ ): AsyncIterableIterator<[string, V]>;
57
+ /** Iterates the selected map keys in key order. */
58
+ keys(
59
+ key: string,
60
+ options?: ScanDirection | KeyQueryOptions,
61
+ ): AsyncIterableIterator<string>;
62
+ /** Iterates the values of the selected entries in key order. */
63
+ values(
64
+ key: string,
65
+ options?: ScanDirection | KeyQueryOptions,
66
+ ): AsyncIterableIterator<V>;
67
+ }
68
+
69
+ /** Read-only published set collection. */
70
+ export declare class PublishedSet {
71
+ private constructor();
72
+
73
+ /** Reports whether the set for `key` holds `member`. */
74
+ has(key: string, member: string): Promise<boolean>;
75
+ /** Tests several members in one call. `result[i]` answers `members[i]`. */
76
+ hasMany(key: string, members: readonly string[]): Promise<boolean[]>;
77
+ /** Reports whether the set for `key` holds no members. */
78
+ isEmpty(key: string): Promise<boolean>;
79
+ /** Iterates the selected members in order. */
80
+ keys(
81
+ key: string,
82
+ options?: ScanDirection | KeyQueryOptions,
83
+ ): AsyncIterableIterator<string>;
84
+ /** The same iterator as `keys`, as on the JavaScript `Set`. */
85
+ values(
86
+ key: string,
87
+ options?: ScanDirection | KeyQueryOptions,
88
+ ): AsyncIterableIterator<string>;
89
+ }
90
+
91
+ /** Read-only published deque collection. */
92
+ export declare class PublishedDeque<T = JsonValue> {
93
+ private constructor();
94
+
95
+ /** Reads the number of values in the deque for `key`. */
96
+ length(key: string): Promise<number>;
97
+ /** Reports whether the deque for `key` holds no values. */
98
+ isEmpty(key: string): Promise<boolean>;
99
+ /**
100
+ * Reads the value at `index`, or null when the position is out of range.
101
+ * `0` is the front and `-1` is the back. A negative index other than `-1`
102
+ * also reads the length, so it makes two reads. Under a TTL, an expired
103
+ * element reads as null. This is also true for an expired front or back
104
+ * element when live elements remain inside the deque.
105
+ * @throws TransientStateError if `index` is not a safe integer.
106
+ */
107
+ at(key: string, index: number): Promise<T | null>;
108
+ /** Iterates the selected values from the front. */
109
+ values(
110
+ key: string,
111
+ options?: ScanDirection | PositionQueryOptions,
112
+ ): AsyncIterableIterator<T>;
113
+ }