@prosody-events/prosody 0.5.1 → 0.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 +157 -55
- package/bindings.d.ts +302 -404
- package/bindings.js +168 -85
- package/index.d.ts +30 -962
- package/index.js +42 -1898
- package/lib/client.d.ts +232 -0
- package/lib/client.js +347 -0
- package/lib/context.d.ts +161 -0
- package/lib/context.js +86 -0
- package/lib/errors.d.ts +103 -0
- package/lib/errors.js +102 -0
- package/lib/logging.d.ts +75 -0
- package/lib/logging.js +99 -0
- package/lib/payload.d.ts +59 -0
- package/lib/state/bridge.js +103 -0
- package/lib/state/codec.js +206 -0
- package/lib/state/definitions.d.ts +233 -0
- package/lib/state/definitions.js +151 -0
- package/lib/state/deque.d.ts +75 -0
- package/lib/state/deque.js +77 -0
- package/lib/state/handle.d.ts +25 -0
- package/lib/state/handle.js +26 -0
- package/lib/state/iterator.js +66 -0
- package/lib/state/map.d.ts +94 -0
- package/lib/state/map.js +79 -0
- package/lib/state/published.d.ts +113 -0
- package/lib/state/published.js +147 -0
- package/lib/state/query.d.ts +85 -0
- package/lib/state/query.js +206 -0
- package/lib/state/set.d.ts +52 -0
- package/lib/state/set.js +54 -0
- package/lib/state/value.d.ts +33 -0
- package/lib/state/value.js +31 -0
- package/package.json +21 -20
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calls into native code: trace carriers, error categories, value rendering,
|
|
3
|
+
* and the hidden native handle properties.
|
|
4
|
+
* @module lib/state/bridge
|
|
5
|
+
* @private
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { types } = require("node:util");
|
|
9
|
+
const { context: otelContext, propagation } = require("@opentelemetry/api");
|
|
10
|
+
const { PermanentStateError, TransientStateError } = require("../errors");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Injects the active OpenTelemetry context into a fresh carrier for one native
|
|
14
|
+
* operation. Native glue activates the carrier without recording a binding
|
|
15
|
+
* span, so the core semantic operation span joins the JavaScript trace directly.
|
|
16
|
+
* @returns {Record<string, string>} The populated carrier.
|
|
17
|
+
* @private
|
|
18
|
+
*/
|
|
19
|
+
function injectedCarrier() {
|
|
20
|
+
const carrier = {};
|
|
21
|
+
propagation.inject(otelContext.active(), carrier);
|
|
22
|
+
return carrier;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Converts a category-tagged native state error into the matching typed state
|
|
27
|
+
* error. The native layer sets the error's `cause` to an error whose message
|
|
28
|
+
* is exactly `"permanent"` or `"transient"`. The human message is never
|
|
29
|
+
* parsed. An untagged error, such as an argument conversion `TypeError`,
|
|
30
|
+
* passes through unchanged.
|
|
31
|
+
*
|
|
32
|
+
* The checks use `util.types.isNativeError`, which works across realms. A napi
|
|
33
|
+
* error comes from the addon's realm. Under a vm context, a worker thread, or a
|
|
34
|
+
* second module copy, `instanceof Error` is false for it.
|
|
35
|
+
* @param {unknown} error - The error thrown by the native layer.
|
|
36
|
+
* @returns {unknown} The typed state error, or the original error if untagged.
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
function toStateError(error) {
|
|
40
|
+
if (!types.isNativeError(error)) return error;
|
|
41
|
+
const cause = error.cause;
|
|
42
|
+
const category = types.isNativeError(cause) ? cause.message : undefined;
|
|
43
|
+
if (category !== "permanent" && category !== "transient") return error;
|
|
44
|
+
const StateError =
|
|
45
|
+
category === "permanent" ? PermanentStateError : TransientStateError;
|
|
46
|
+
return new StateError(error.message, { cause: error });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Renders a value for a diagnostic message without ever throwing.
|
|
51
|
+
* `JSON.stringify` throws on a BigInt or a cycle, and a template literal throws
|
|
52
|
+
* on a Symbol. A raw `TypeError` from either would skip the state error
|
|
53
|
+
* classes. The function returns the value's `typeof` when it cannot serialize
|
|
54
|
+
* the value.
|
|
55
|
+
* @param {*} value - The value to describe.
|
|
56
|
+
* @returns {string} A safe, human-readable rendering.
|
|
57
|
+
* @private
|
|
58
|
+
*/
|
|
59
|
+
function describeValue(value) {
|
|
60
|
+
try {
|
|
61
|
+
const rendered = JSON.stringify(value);
|
|
62
|
+
return rendered === undefined ? typeof value : rendered;
|
|
63
|
+
} catch {
|
|
64
|
+
return typeof value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Runs one async native state operation with a fresh carrier, translating a
|
|
70
|
+
* category-tagged failure into the matching typed state error.
|
|
71
|
+
* @param {(carrier: Record<string, string>) => Promise<*>} operation - The native call.
|
|
72
|
+
* @returns {Promise<*>} The operation's resolved value.
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
75
|
+
async function stateOp(operation) {
|
|
76
|
+
try {
|
|
77
|
+
return await operation(injectedCarrier());
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw toStateError(error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Stores internal fields on a wrapper as writable properties that do not
|
|
85
|
+
* enumerate, so `console.log` shows no internals. A data property also reads
|
|
86
|
+
* correctly when a getter or method runs with a Proxy as `this`.
|
|
87
|
+
* @param {object} object - The wrapper object.
|
|
88
|
+
* @param {Record<string, *>} fields - The internal fields and their values.
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
function defineHidden(object, fields) {
|
|
92
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
93
|
+
Object.defineProperty(object, name, { value, writable: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
defineHidden,
|
|
99
|
+
describeValue,
|
|
100
|
+
injectedCarrier,
|
|
101
|
+
stateOp,
|
|
102
|
+
toStateError,
|
|
103
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encodes and decodes the items of JSON and message collections.
|
|
3
|
+
* @module lib/state/codec
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Message: NativeMessage } = require("../../bindings");
|
|
8
|
+
const {
|
|
9
|
+
PermanentError,
|
|
10
|
+
PermanentStateError,
|
|
11
|
+
TransientStateError,
|
|
12
|
+
} = require("../errors");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The native `payload` getter, which answers the raw JSON text read from the
|
|
16
|
+
* wire. Captured before {@link withParsedPayload} shadows it per instance.
|
|
17
|
+
* @private
|
|
18
|
+
*/
|
|
19
|
+
const rawPayload = Object.getOwnPropertyDescriptor(
|
|
20
|
+
NativeMessage.prototype,
|
|
21
|
+
"payload",
|
|
22
|
+
).get;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Replaces a message's raw-text `payload` with the parsed document.
|
|
26
|
+
*
|
|
27
|
+
* Rust hands the payload across as the bytes it read from the wire, never
|
|
28
|
+
* parsing them; the parse happens here, on first read, and the result is kept.
|
|
29
|
+
* A handler that only inspects metadata therefore never pays for one.
|
|
30
|
+
*
|
|
31
|
+
* A payload that is not JSON raises a permanent error: no retry makes bytes
|
|
32
|
+
* parse. Reading `payload` is where that surfaces, since nothing before it
|
|
33
|
+
* looks at the document. That outcome is kept too, so a handler reading a bad
|
|
34
|
+
* payload twice re-raises rather than re-parsing.
|
|
35
|
+
* @param {object} message - The native message.
|
|
36
|
+
* @returns {object} The same message, with `payload` parsed on demand.
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
function withParsedPayload(message) {
|
|
40
|
+
let document;
|
|
41
|
+
let failure;
|
|
42
|
+
let parsed = false;
|
|
43
|
+
// Not enumerable, matching the five native getters: none of a message's
|
|
44
|
+
// fields are own properties, so spread, `Object.keys`, and `JSON.stringify`
|
|
45
|
+
// see none of them. An enumerable own `payload` would make `JSON.stringify`
|
|
46
|
+
// of a message serialize the whole parsed document.
|
|
47
|
+
Object.defineProperty(message, "payload", {
|
|
48
|
+
configurable: true,
|
|
49
|
+
enumerable: false,
|
|
50
|
+
get() {
|
|
51
|
+
if (!parsed) {
|
|
52
|
+
// The native read fails for bytes that are not UTF-8, and JSON.parse
|
|
53
|
+
// fails for text that is not JSON. Both are the same permanent error.
|
|
54
|
+
try {
|
|
55
|
+
document = JSON.parse(rawPayload.call(this));
|
|
56
|
+
} catch (error) {
|
|
57
|
+
failure = new PermanentError(
|
|
58
|
+
`message payload is not JSON: ${error.message}`,
|
|
59
|
+
{ cause: error },
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
parsed = true;
|
|
63
|
+
}
|
|
64
|
+
if (failure !== undefined) throw failure;
|
|
65
|
+
return document;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
return message;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Serializes a JSON value.
|
|
73
|
+
*
|
|
74
|
+
* It returns `undefined` for a function, a symbol, or `undefined` itself. It
|
|
75
|
+
* throws for a BigInt or a cycle.
|
|
76
|
+
* @param {*} value - The value to serialize.
|
|
77
|
+
* @param {Function} ErrorClass - The error to raise on failure.
|
|
78
|
+
* @returns {string} The JSON text.
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
function toJson(value, ErrorClass) {
|
|
82
|
+
let json;
|
|
83
|
+
try {
|
|
84
|
+
json = JSON.stringify(value);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new ErrorClass(
|
|
87
|
+
`value is not representable as JSON: ${error.message}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (json === undefined) {
|
|
91
|
+
throw new ErrorClass(
|
|
92
|
+
"value is not representable as JSON (functions, symbols, and " +
|
|
93
|
+
"`undefined` have no JSON form)",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return json;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Serializes a value for a JSON collection.
|
|
101
|
+
*
|
|
102
|
+
* A collection stores whatever JSON it is given, so the only rejections here
|
|
103
|
+
* are values with no JSON form at all. Both are caller mistakes and reject
|
|
104
|
+
* transient: the event retries and the mistake stays visible rather than
|
|
105
|
+
* discarding the message.
|
|
106
|
+
*
|
|
107
|
+
* Everything else follows `JSON.stringify`, which is now the serializer of
|
|
108
|
+
* record. Inside a container a function-valued property is dropped and an
|
|
109
|
+
* `undefined` element becomes `null`. `NaN` and the infinities become `null`
|
|
110
|
+
* anywhere they appear. A `toJSON` method decides what its value serializes to.
|
|
111
|
+
* The previous Rust-side conversion rejected each of those instead.
|
|
112
|
+
* @param {*} value - The value to store.
|
|
113
|
+
* @returns {string} The document's JSON text.
|
|
114
|
+
* @throws {TransientStateError} If the value has no JSON representation.
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
function encodeJson(value) {
|
|
118
|
+
if (value instanceof NativeMessage) {
|
|
119
|
+
throw new TransientStateError(
|
|
120
|
+
"a Kafka message cannot be stored in a JSON collection; declare it with " +
|
|
121
|
+
"messageValue/messageMap/messageDeque instead",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return toJson(value, TransientStateError);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Reads the event metadata a payload carries.
|
|
129
|
+
*
|
|
130
|
+
* When the payload is an object with a string `id` or `type`, prosody uses
|
|
131
|
+
* them. Anything else carries no metadata. Each field is read once, so a getter
|
|
132
|
+
* that answers differently on a second read cannot make the metadata disagree
|
|
133
|
+
* with itself.
|
|
134
|
+
* @param {*} payload - The payload about to be sent.
|
|
135
|
+
* @returns {{eventId?: string, eventType?: string}} The metadata.
|
|
136
|
+
* @private
|
|
137
|
+
*/
|
|
138
|
+
function eventMetadata(payload) {
|
|
139
|
+
const id = payload?.id;
|
|
140
|
+
const type = payload?.type;
|
|
141
|
+
return {
|
|
142
|
+
eventId: typeof id === "string" ? id : undefined,
|
|
143
|
+
eventType: typeof type === "string" ? type : undefined,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Checks that a value for a message collection is a real Kafka message.
|
|
149
|
+
*
|
|
150
|
+
* A message collection stores the Kafka message itself, so it accepts only a
|
|
151
|
+
* `Message` — an object merely shaped like one is rejected. A message read back
|
|
152
|
+
* out of a collection is a `Message` and stores fine.
|
|
153
|
+
* What it stores is the message's own wire bytes, so assigning to a message's
|
|
154
|
+
* fields, or mutating its parsed `payload`, does not change what is written.
|
|
155
|
+
* @param {*} value - The value to store.
|
|
156
|
+
* @returns {object} The message.
|
|
157
|
+
* @throws {TransientStateError} If the value is not a Kafka message.
|
|
158
|
+
* @private
|
|
159
|
+
*/
|
|
160
|
+
function requireMessage(value) {
|
|
161
|
+
if (!(value instanceof NativeMessage)) {
|
|
162
|
+
throw new TransientStateError(
|
|
163
|
+
"expected a Kafka message; a JSON value cannot be stored in a message collection",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Builds the item codec for one collection payload type.
|
|
171
|
+
*
|
|
172
|
+
* The native handle has one payload type. `encode` prepares an item for a
|
|
173
|
+
* write and rejects an item of the other type.
|
|
174
|
+
* @param {(item: *) => *} encode - Prepares an item for a write.
|
|
175
|
+
* @param {(item: *) => *} decode - Turns a read item into what the caller asked for.
|
|
176
|
+
* @returns {Readonly<object>} The frozen codec.
|
|
177
|
+
* @private
|
|
178
|
+
*/
|
|
179
|
+
function itemCodec(encode, decode) {
|
|
180
|
+
return Object.freeze({
|
|
181
|
+
encode,
|
|
182
|
+
decode: (item) => (item === null ? null : decode(item)),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** JSON documents cross as their text. @private */
|
|
187
|
+
const jsonItems = itemCodec(encodeJson, (text) => {
|
|
188
|
+
try {
|
|
189
|
+
return JSON.parse(text);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
throw new PermanentStateError(
|
|
192
|
+
`stored JSON document could not be parsed: ${error.message}`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
/** Kafka messages cross as the `Message` object itself. @private */
|
|
198
|
+
const messageItems = itemCodec(requireMessage, withParsedPayload);
|
|
199
|
+
|
|
200
|
+
module.exports = {
|
|
201
|
+
eventMetadata,
|
|
202
|
+
jsonItems,
|
|
203
|
+
messageItems,
|
|
204
|
+
toJson,
|
|
205
|
+
withParsedPayload,
|
|
206
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The keyed-state definition types, their options, and their constructors.
|
|
3
|
+
* @module lib/state/definitions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { JsonValue } from "../payload";
|
|
7
|
+
import type { ReadCacheOptions } from "./published";
|
|
8
|
+
|
|
9
|
+
/** Options accepted by every keyed-state definition constructor. */
|
|
10
|
+
export interface StateDefinitionOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Optional per-write TTL in whole seconds. Must be at least 1 and must
|
|
13
|
+
* stay within the Cassandra TTL limit.
|
|
14
|
+
*/
|
|
15
|
+
ttlSeconds?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Opt out of transactional staging (read-uncommitted, at-least-once).
|
|
18
|
+
* Defaults to transactional.
|
|
19
|
+
*/
|
|
20
|
+
readUncommitted?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Options accepted by JSON collections that may be published. */
|
|
24
|
+
export interface PublishedStateDefinitionOptions extends StateDefinitionOptions {
|
|
25
|
+
/** Allow read-only access from other consumer groups. */
|
|
26
|
+
published?: boolean;
|
|
27
|
+
/** Override caching when this descriptor opens a published reader. */
|
|
28
|
+
readCache?: ReadCacheOptions | false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The keyset option of map and set collections. */
|
|
32
|
+
interface KeysetOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Keyset bound for ordered scans (`0..=4096`; default 128 core-side; `0`
|
|
35
|
+
* disables ordered-scan tracking). Map and set collections only.
|
|
36
|
+
*/
|
|
37
|
+
keysetLimit?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The capacity option of deque collections. */
|
|
41
|
+
interface CapacityOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Optional maximum value count, at least 1. Prosody does not store it and it
|
|
44
|
+
* is not part of the collection identity, so a deploy can change it. A push
|
|
45
|
+
* past the bound removes values from the opposite end. A deque above a new,
|
|
46
|
+
* smaller bound keeps its length until the next push trims it.
|
|
47
|
+
*/
|
|
48
|
+
capacity?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Options accepted by the map definition constructors. */
|
|
52
|
+
export interface MapDefinitionOptions
|
|
53
|
+
extends PublishedStateDefinitionOptions, KeysetOptions {}
|
|
54
|
+
|
|
55
|
+
/** Options accepted by the set definition constructor. */
|
|
56
|
+
export type SetDefinitionOptions = MapDefinitionOptions;
|
|
57
|
+
|
|
58
|
+
/** Options accepted by the deque definition constructors. */
|
|
59
|
+
export interface DequeDefinitionOptions
|
|
60
|
+
extends PublishedStateDefinitionOptions, CapacityOptions {}
|
|
61
|
+
|
|
62
|
+
/** Options accepted by message-map definitions, which cannot be published. */
|
|
63
|
+
export interface MessageMapDefinitionOptions
|
|
64
|
+
extends StateDefinitionOptions, KeysetOptions {}
|
|
65
|
+
|
|
66
|
+
/** Options accepted by message-deque definitions, which cannot be published. */
|
|
67
|
+
export interface MessageDequeDefinitionOptions
|
|
68
|
+
extends StateDefinitionOptions, CapacityOptions {}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Phantom brand carrying a definition's item type. Never present at runtime —
|
|
72
|
+
* it exists only so the item type survives on the frozen definition object and
|
|
73
|
+
* flows into the vended handle.
|
|
74
|
+
*/
|
|
75
|
+
declare const StateItem: unique symbol;
|
|
76
|
+
declare const StateDescriptor: unique symbol;
|
|
77
|
+
|
|
78
|
+
interface DefinitionBrand {
|
|
79
|
+
readonly [StateDescriptor]: true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A frozen single-value JSON collection definition. */
|
|
83
|
+
export interface ValueDefinition<T = JsonValue>
|
|
84
|
+
extends DefinitionBrand, Readonly<PublishedStateDefinitionOptions> {
|
|
85
|
+
readonly name: string;
|
|
86
|
+
readonly kind: "value";
|
|
87
|
+
readonly payload: "json";
|
|
88
|
+
readonly [StateItem]?: T;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A frozen ordered-map JSON collection definition (string keys). */
|
|
92
|
+
export interface MapDefinition<V = JsonValue>
|
|
93
|
+
extends DefinitionBrand, Readonly<MapDefinitionOptions> {
|
|
94
|
+
readonly name: string;
|
|
95
|
+
readonly kind: "map";
|
|
96
|
+
readonly payload: "json";
|
|
97
|
+
readonly [StateItem]?: V;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A frozen set collection definition. A set stores string members only. */
|
|
101
|
+
export interface SetDefinition
|
|
102
|
+
extends DefinitionBrand, Readonly<SetDefinitionOptions> {
|
|
103
|
+
readonly name: string;
|
|
104
|
+
readonly kind: "set";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A frozen deque JSON collection definition. */
|
|
108
|
+
export interface DequeDefinition<T = JsonValue>
|
|
109
|
+
extends DefinitionBrand, Readonly<DequeDefinitionOptions> {
|
|
110
|
+
readonly name: string;
|
|
111
|
+
readonly kind: "deque";
|
|
112
|
+
readonly payload: "json";
|
|
113
|
+
readonly [StateItem]?: T;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** A frozen single-value message collection definition (items are `Message<P>`). */
|
|
117
|
+
export interface MessageValueDefinition<P = JsonValue>
|
|
118
|
+
extends DefinitionBrand, Readonly<StateDefinitionOptions> {
|
|
119
|
+
readonly name: string;
|
|
120
|
+
readonly kind: "value";
|
|
121
|
+
readonly payload: "message";
|
|
122
|
+
readonly [StateItem]?: P;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** A frozen ordered-map message collection definition (values are `Message<P>`). */
|
|
126
|
+
export interface MessageMapDefinition<P = JsonValue>
|
|
127
|
+
extends DefinitionBrand, Readonly<MessageMapDefinitionOptions> {
|
|
128
|
+
readonly name: string;
|
|
129
|
+
readonly kind: "map";
|
|
130
|
+
readonly payload: "message";
|
|
131
|
+
readonly [StateItem]?: P;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** A frozen deque message collection definition (elements are `Message<P>`). */
|
|
135
|
+
export interface MessageDequeDefinition<P = JsonValue>
|
|
136
|
+
extends DefinitionBrand, Readonly<MessageDequeDefinitionOptions> {
|
|
137
|
+
readonly name: string;
|
|
138
|
+
readonly kind: "deque";
|
|
139
|
+
readonly payload: "message";
|
|
140
|
+
readonly [StateItem]?: P;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Declares a single-value JSON collection. The returned frozen definition is
|
|
145
|
+
* the single source of typing: place it in `Configuration.stateCollections` to
|
|
146
|
+
* register the collection, and pass it to `Context.state()` to vend a typed
|
|
147
|
+
* handle. The type parameter annotates the stored value and is compile-time
|
|
148
|
+
* only — payloads cross as plain JSON with no runtime validation.
|
|
149
|
+
* @param name - The collection name (unique per client).
|
|
150
|
+
* @param options - Optional retention, transaction, publication, and read-cache
|
|
151
|
+
* settings.
|
|
152
|
+
*/
|
|
153
|
+
export function value<T = JsonValue>(
|
|
154
|
+
name: string,
|
|
155
|
+
options?: PublishedStateDefinitionOptions,
|
|
156
|
+
): ValueDefinition<T>;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Declares an ordered-map JSON collection. Map keys are always `string`. The
|
|
160
|
+
* returned frozen definition is used both in `Configuration.stateCollections`
|
|
161
|
+
* and with `Context.state()`. The type parameter annotates the stored value
|
|
162
|
+
* (compile-time only).
|
|
163
|
+
* @param name - The collection name (unique per client).
|
|
164
|
+
* @param options - Optional retention, transaction, publication, read-cache,
|
|
165
|
+
* and `keysetLimit` settings.
|
|
166
|
+
*/
|
|
167
|
+
export function map<V = JsonValue>(
|
|
168
|
+
name: string,
|
|
169
|
+
options?: MapDefinitionOptions,
|
|
170
|
+
): MapDefinition<V>;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Declares a presence-only ordered set of string members. The returned frozen
|
|
174
|
+
* definition is used both in `Configuration.stateCollections` and with
|
|
175
|
+
* `Context.state()`. A set has no payload.
|
|
176
|
+
* @param name - The collection name (unique per client).
|
|
177
|
+
* @param options - Optional retention, transaction, publication, read-cache,
|
|
178
|
+
* and `keysetLimit` settings.
|
|
179
|
+
*/
|
|
180
|
+
export function set(
|
|
181
|
+
name: string,
|
|
182
|
+
options?: SetDefinitionOptions,
|
|
183
|
+
): SetDefinition;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Declares a double-ended-queue JSON collection. The returned frozen definition
|
|
187
|
+
* is used both in `Configuration.stateCollections` and with `Context.state()`.
|
|
188
|
+
* The type parameter annotates the stored element (compile-time only).
|
|
189
|
+
* @param name - The collection name (unique per client).
|
|
190
|
+
* @param options - Optional retention, transaction, publication, read-cache,
|
|
191
|
+
* and `capacity` settings.
|
|
192
|
+
*/
|
|
193
|
+
export function deque<T = JsonValue>(
|
|
194
|
+
name: string,
|
|
195
|
+
options?: DequeDefinitionOptions,
|
|
196
|
+
): DequeDefinition<T>;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Declares a single-value message collection: each stored item is the full
|
|
200
|
+
* Kafka `Message<P>` the handler received. The type parameter annotates the
|
|
201
|
+
* message payload (compile-time only).
|
|
202
|
+
* @param name - The collection name (unique per client).
|
|
203
|
+
* @param options - Optional `ttlSeconds` (whole seconds) and `readUncommitted`.
|
|
204
|
+
*/
|
|
205
|
+
export function messageValue<P = JsonValue>(
|
|
206
|
+
name: string,
|
|
207
|
+
options?: StateDefinitionOptions,
|
|
208
|
+
): MessageValueDefinition<P>;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Declares an ordered-map message collection (string keys; values are the full
|
|
212
|
+
* Kafka `Message<P>`). The type parameter annotates the message payload
|
|
213
|
+
* (compile-time only).
|
|
214
|
+
* @param name - The collection name (unique per client).
|
|
215
|
+
* @param options - Optional `ttlSeconds`, `readUncommitted`, and `keysetLimit`.
|
|
216
|
+
*/
|
|
217
|
+
export function messageMap<P = JsonValue>(
|
|
218
|
+
name: string,
|
|
219
|
+
options?: MessageMapDefinitionOptions,
|
|
220
|
+
): MessageMapDefinition<P>;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Declares a double-ended-queue message collection: each stored element is the
|
|
224
|
+
* full Kafka `Message<P>`. The type parameter annotates the message payload
|
|
225
|
+
* (compile-time only).
|
|
226
|
+
* @param name - The collection name (unique per client).
|
|
227
|
+
* @param options - Optional `ttlSeconds` (whole seconds), `readUncommitted`, and
|
|
228
|
+
* `capacity` (bounded backlog; enforced lazily on push).
|
|
229
|
+
*/
|
|
230
|
+
export function messageDeque<P = JsonValue>(
|
|
231
|
+
name: string,
|
|
232
|
+
options?: MessageDequeDefinitionOptions,
|
|
233
|
+
): MessageDequeDefinition<P>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The keyed-state definition constructors and the handle and reader factories they carry.
|
|
3
|
+
* @module lib/state/definitions
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { jsonItems, messageItems } = require("./codec");
|
|
8
|
+
const { DequeState } = require("./deque");
|
|
9
|
+
const { MapState } = require("./map");
|
|
10
|
+
const {
|
|
11
|
+
PublishedDeque,
|
|
12
|
+
PublishedMap,
|
|
13
|
+
PublishedSet,
|
|
14
|
+
PublishedValue,
|
|
15
|
+
} = require("./published");
|
|
16
|
+
const { SetState } = require("./set");
|
|
17
|
+
const { ValueState } = require("./value");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Maps each frozen definition to the factories that open its owned handle and
|
|
21
|
+
* its published reader. Only the definition constructors add entries.
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
const stateDefinitionAccess = new WeakMap();
|
|
25
|
+
const VALUE_ACCESS = Object.freeze({
|
|
26
|
+
owned: (context, collection) =>
|
|
27
|
+
new ValueState(context.valueState(collection), jsonItems),
|
|
28
|
+
published: async (client, subsystem, collection, readCache) =>
|
|
29
|
+
new PublishedValue(
|
|
30
|
+
await client.publishedValue(subsystem, collection, readCache),
|
|
31
|
+
),
|
|
32
|
+
});
|
|
33
|
+
const MAP_ACCESS = Object.freeze({
|
|
34
|
+
owned: (context, collection) =>
|
|
35
|
+
new MapState(context.mapState(collection), jsonItems),
|
|
36
|
+
published: async (client, subsystem, collection, readCache) =>
|
|
37
|
+
new PublishedMap(
|
|
38
|
+
await client.publishedMap(subsystem, collection, readCache),
|
|
39
|
+
),
|
|
40
|
+
});
|
|
41
|
+
const SET_ACCESS = Object.freeze({
|
|
42
|
+
owned: (context, collection) => new SetState(context.setState(collection)),
|
|
43
|
+
published: async (client, subsystem, collection, readCache) =>
|
|
44
|
+
new PublishedSet(
|
|
45
|
+
await client.publishedSet(subsystem, collection, readCache),
|
|
46
|
+
),
|
|
47
|
+
});
|
|
48
|
+
const DEQUE_ACCESS = Object.freeze({
|
|
49
|
+
owned: (context, collection) =>
|
|
50
|
+
new DequeState(context.dequeState(collection), jsonItems),
|
|
51
|
+
published: async (client, subsystem, collection, readCache) =>
|
|
52
|
+
new PublishedDeque(
|
|
53
|
+
await client.publishedDeque(subsystem, collection, readCache),
|
|
54
|
+
),
|
|
55
|
+
});
|
|
56
|
+
const MESSAGE_VALUE_ACCESS = Object.freeze({
|
|
57
|
+
owned: (context, collection) =>
|
|
58
|
+
new ValueState(context.messageValueState(collection), messageItems),
|
|
59
|
+
});
|
|
60
|
+
const MESSAGE_MAP_ACCESS = Object.freeze({
|
|
61
|
+
owned: (context, collection) =>
|
|
62
|
+
new MapState(context.messageMapState(collection), messageItems),
|
|
63
|
+
});
|
|
64
|
+
const MESSAGE_DEQUE_ACCESS = Object.freeze({
|
|
65
|
+
owned: (context, collection) =>
|
|
66
|
+
new DequeState(context.messageDequeState(collection), messageItems),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Builds a frozen state-collection definition. The definition is the single
|
|
71
|
+
* source of typing: the same frozen object is placed in
|
|
72
|
+
* `Configuration.stateCollections` (so the collection is registered once) and
|
|
73
|
+
* passed to `Context.state()` to vend a typed handle. Validation (name/ttl/
|
|
74
|
+
* keyset rules, duplicate names) is core-owned and happens at client
|
|
75
|
+
* construction and registration — this layer only shapes and freezes.
|
|
76
|
+
* @param {string} name - The collection name.
|
|
77
|
+
* @param {string} kind - `"value"`, `"map"`, `"set"`, or `"deque"`.
|
|
78
|
+
* @param {string|undefined} payload - `"json"` or `"message"`; a set has none.
|
|
79
|
+
* @param {Readonly<object>} access - The factories that open the owned handle
|
|
80
|
+
* and, for a JSON or set collection, the published reader.
|
|
81
|
+
* @param {object} [options] - The definition options of the kind.
|
|
82
|
+
* @returns {Readonly<object>} The frozen definition.
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
function stateDefinition(name, kind, payload, access, options = {}) {
|
|
86
|
+
const definition = { name, kind };
|
|
87
|
+
if (payload !== undefined) definition.payload = payload;
|
|
88
|
+
if (options.ttlSeconds !== undefined)
|
|
89
|
+
definition.ttlSeconds = options.ttlSeconds;
|
|
90
|
+
if (options.readUncommitted !== undefined)
|
|
91
|
+
definition.readUncommitted = options.readUncommitted;
|
|
92
|
+
if (options.published !== undefined) definition.published = options.published;
|
|
93
|
+
if (options.readCache !== undefined) definition.readCache = options.readCache;
|
|
94
|
+
if (options.keysetLimit !== undefined)
|
|
95
|
+
definition.keysetLimit = options.keysetLimit;
|
|
96
|
+
if (options.capacity !== undefined) definition.capacity = options.capacity;
|
|
97
|
+
const frozen = Object.freeze(definition);
|
|
98
|
+
stateDefinitionAccess.set(frozen, access);
|
|
99
|
+
return frozen;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function value(name, options) {
|
|
103
|
+
return stateDefinition(name, "value", "json", VALUE_ACCESS, options);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function map(name, options) {
|
|
107
|
+
return stateDefinition(name, "map", "json", MAP_ACCESS, options);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function set(name, options) {
|
|
111
|
+
return stateDefinition(name, "set", undefined, SET_ACCESS, options);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function deque(name, options) {
|
|
115
|
+
return stateDefinition(name, "deque", "json", DEQUE_ACCESS, options);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function messageValue(name, options) {
|
|
119
|
+
return stateDefinition(
|
|
120
|
+
name,
|
|
121
|
+
"value",
|
|
122
|
+
"message",
|
|
123
|
+
MESSAGE_VALUE_ACCESS,
|
|
124
|
+
options,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function messageMap(name, options) {
|
|
129
|
+
return stateDefinition(name, "map", "message", MESSAGE_MAP_ACCESS, options);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function messageDeque(name, options) {
|
|
133
|
+
return stateDefinition(
|
|
134
|
+
name,
|
|
135
|
+
"deque",
|
|
136
|
+
"message",
|
|
137
|
+
MESSAGE_DEQUE_ACCESS,
|
|
138
|
+
options,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
deque,
|
|
144
|
+
map,
|
|
145
|
+
messageDeque,
|
|
146
|
+
messageMap,
|
|
147
|
+
messageValue,
|
|
148
|
+
set,
|
|
149
|
+
stateDefinitionAccess,
|
|
150
|
+
value,
|
|
151
|
+
};
|