@crossdelta/cloudevents 0.4.22 → 0.4.24
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/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export type { EventContext } from './adapters/cloudevents';
|
|
2
2
|
export { parseEventFromContext } from './adapters/cloudevents';
|
|
3
|
-
export { createContract } from './domain';
|
|
4
3
|
export type { EnrichedEvent, HandleEventOptions, IdempotencyStore, InferEventData, RoutingConfig } from './domain';
|
|
5
|
-
export { eventSchema, extractTypeFromSchema, handleEvent } from './domain';
|
|
4
|
+
export { createContract, eventSchema, extractTypeFromSchema, handleEvent } from './domain';
|
|
6
5
|
export { clearHandlerCache, cloudEvents } from './middlewares';
|
|
7
6
|
export { checkAndMarkProcessed, createInMemoryIdempotencyStore, getDefaultIdempotencyStore, resetDefaultIdempotencyStore, } from './processing/idempotency';
|
|
8
7
|
export * from './publishing';
|
|
9
|
-
export {
|
|
8
|
+
export type { JetStreamConsumerOptions, JetStreamStreamOptions, JetStreamStreamsConsumerOptions, JetStreamStreamsOptions, StreamConfig, StreamDefinition, } from './transports/nats';
|
|
9
|
+
export { consumeJetStreamEvents, consumeJetStreamStreams, consumeNatsEvents, ensureJetStreamStream, ensureJetStreamStreams, } from './transports/nats';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export { parseEventFromContext } from './adapters/cloudevents';
|
|
2
|
-
export { createContract } from './domain';
|
|
3
|
-
export { eventSchema, extractTypeFromSchema, handleEvent } from './domain';
|
|
2
|
+
export { createContract, eventSchema, extractTypeFromSchema, handleEvent } from './domain';
|
|
4
3
|
export { clearHandlerCache, cloudEvents } from './middlewares';
|
|
5
4
|
export { checkAndMarkProcessed, createInMemoryIdempotencyStore, getDefaultIdempotencyStore, resetDefaultIdempotencyStore, } from './processing/idempotency';
|
|
6
5
|
export * from './publishing';
|
|
7
|
-
export { consumeJetStreamEvents, consumeNatsEvents, ensureJetStreamStream } from './transports/nats';
|
|
6
|
+
export { consumeJetStreamEvents, consumeJetStreamStreams, consumeNatsEvents, ensureJetStreamStream, ensureJetStreamStreams, } from './transports/nats';
|
|
@@ -29,6 +29,30 @@ export interface JetStreamStreamOptions {
|
|
|
29
29
|
/** Stream configuration */
|
|
30
30
|
config?: StreamConfig;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Stream definition for batch operations
|
|
34
|
+
*/
|
|
35
|
+
export interface StreamDefinition {
|
|
36
|
+
/** JetStream stream name (e.g., 'ORDERS') */
|
|
37
|
+
stream: string;
|
|
38
|
+
/** Subjects to bind (e.g., ['orders.*']) */
|
|
39
|
+
subjects: string[];
|
|
40
|
+
/** Optional stream-specific config */
|
|
41
|
+
config?: StreamConfig;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Options for ensuring multiple JetStream streams exist
|
|
45
|
+
*/
|
|
46
|
+
export interface JetStreamStreamsOptions {
|
|
47
|
+
/** NATS server URL. Defaults to NATS_URL env or nats://localhost:4222 */
|
|
48
|
+
servers?: string;
|
|
49
|
+
/** NATS username for authentication (defaults to NATS_USER env var) */
|
|
50
|
+
user?: string;
|
|
51
|
+
/** NATS password for authentication (defaults to NATS_PASSWORD env var) */
|
|
52
|
+
pass?: string;
|
|
53
|
+
/** Array of stream definitions */
|
|
54
|
+
streams: StreamDefinition[];
|
|
55
|
+
}
|
|
32
56
|
/**
|
|
33
57
|
* JetStream consumer configuration
|
|
34
58
|
*/
|
|
@@ -102,6 +126,21 @@ export interface JetStreamConsumerOptions extends Pick<CloudEventsOptions, 'quar
|
|
|
102
126
|
* ```
|
|
103
127
|
*/
|
|
104
128
|
export declare function ensureJetStreamStream(options: JetStreamStreamOptions): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Ensures multiple JetStream streams exist with a single NATS connection.
|
|
131
|
+
* More efficient than calling ensureJetStreamStream multiple times.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* await ensureJetStreamStreams({
|
|
136
|
+
* streams: [
|
|
137
|
+
* { stream: 'ORDERS', subjects: ['orders.*'] },
|
|
138
|
+
* { stream: 'CUSTOMERS', subjects: ['customers.*'] },
|
|
139
|
+
* ]
|
|
140
|
+
* })
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export declare function ensureJetStreamStreams(options: JetStreamStreamsOptions): Promise<void>;
|
|
105
144
|
/**
|
|
106
145
|
* Consume CloudEvents from NATS JetStream with persistence and guaranteed delivery.
|
|
107
146
|
*
|
|
@@ -123,3 +162,46 @@ export declare function ensureJetStreamStream(options: JetStreamStreamOptions):
|
|
|
123
162
|
* ```
|
|
124
163
|
*/
|
|
125
164
|
export declare function consumeJetStreamEvents(options: JetStreamConsumerOptions): Promise<ConsumerMessages>;
|
|
165
|
+
/**
|
|
166
|
+
* Options for consuming from multiple JetStream streams
|
|
167
|
+
*/
|
|
168
|
+
export interface JetStreamStreamsConsumerOptions extends Pick<CloudEventsOptions, 'quarantineTopic' | 'errorTopic' | 'projectId' | 'source'> {
|
|
169
|
+
/** NATS server URL. Defaults to NATS_URL env or nats://localhost:4222 */
|
|
170
|
+
servers?: string;
|
|
171
|
+
/** NATS username for authentication (defaults to NATS_USER env var) */
|
|
172
|
+
user?: string;
|
|
173
|
+
/** NATS password for authentication (defaults to NATS_PASSWORD env var) */
|
|
174
|
+
pass?: string;
|
|
175
|
+
/** Durable consumer name. Required for persistence across restarts */
|
|
176
|
+
consumer: string;
|
|
177
|
+
/** Array of stream names to consume from */
|
|
178
|
+
streams: string[];
|
|
179
|
+
/** Glob pattern to discover event handlers */
|
|
180
|
+
discover: string;
|
|
181
|
+
/** Where to start consuming from on first subscription. @default 'new' */
|
|
182
|
+
startFrom?: 'all' | 'new' | 'last' | Date;
|
|
183
|
+
/** Max number of messages to buffer for processing @default 100 */
|
|
184
|
+
maxMessages?: number;
|
|
185
|
+
/** Ack wait timeout in milliseconds @default 30000 */
|
|
186
|
+
ackWait?: number;
|
|
187
|
+
/** Max redelivery attempts @default 3 */
|
|
188
|
+
maxDeliver?: number;
|
|
189
|
+
/** Idempotency store for deduplication */
|
|
190
|
+
idempotencyStore?: IdempotencyStore | false;
|
|
191
|
+
/** TTL for idempotency records in milliseconds @default 86400000 */
|
|
192
|
+
idempotencyTtl?: number;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Consume CloudEvents from multiple JetStream streams with a single connection.
|
|
196
|
+
* More efficient than calling consumeJetStreamEvents multiple times.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* await consumeJetStreamStreams({
|
|
201
|
+
* streams: ['ORDERS', 'CUSTOMERS'],
|
|
202
|
+
* consumer: 'notifications',
|
|
203
|
+
* discover: './src/events/**\/*.event.ts',
|
|
204
|
+
* })
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
export declare function consumeJetStreamStreams(options: JetStreamStreamsConsumerOptions): Promise<ConsumerMessages[]>;
|
|
@@ -84,6 +84,38 @@ async function ensureStream(jsm, name, subjects, config = {}) {
|
|
|
84
84
|
logger.info(`[jetstream] created stream ${name} with subjects: ${subjects.join(', ')}`);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Ensures multiple JetStream streams exist with a single NATS connection.
|
|
89
|
+
* More efficient than calling ensureJetStreamStream multiple times.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* await ensureJetStreamStreams({
|
|
94
|
+
* streams: [
|
|
95
|
+
* { stream: 'ORDERS', subjects: ['orders.*'] },
|
|
96
|
+
* { stream: 'CUSTOMERS', subjects: ['customers.*'] },
|
|
97
|
+
* ]
|
|
98
|
+
* })
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export async function ensureJetStreamStreams(options) {
|
|
102
|
+
const servers = options.servers ?? process.env.NATS_URL ?? 'nats://localhost:4222';
|
|
103
|
+
const user = options.user ?? process.env.NATS_USER;
|
|
104
|
+
const pass = options.pass ?? process.env.NATS_PASSWORD;
|
|
105
|
+
const nc = await connect({
|
|
106
|
+
servers,
|
|
107
|
+
...(user && pass ? { user, pass } : {}),
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
const jsm = await nc.jetstreamManager();
|
|
111
|
+
for (const { stream, subjects, config } of options.streams) {
|
|
112
|
+
await ensureStream(jsm, stream, subjects, config);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await nc.close();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
87
119
|
/**
|
|
88
120
|
* Ensures durable consumer exists
|
|
89
121
|
*/
|
|
@@ -223,3 +255,103 @@ export async function consumeJetStreamEvents(options) {
|
|
|
223
255
|
})();
|
|
224
256
|
return messages;
|
|
225
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Consume CloudEvents from multiple JetStream streams with a single connection.
|
|
260
|
+
* More efficient than calling consumeJetStreamEvents multiple times.
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```typescript
|
|
264
|
+
* await consumeJetStreamStreams({
|
|
265
|
+
* streams: ['ORDERS', 'CUSTOMERS'],
|
|
266
|
+
* consumer: 'notifications',
|
|
267
|
+
* discover: './src/events/**\/*.event.ts',
|
|
268
|
+
* })
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
export async function consumeJetStreamStreams(options) {
|
|
272
|
+
const name = options.consumer;
|
|
273
|
+
const servers = options.servers ?? process.env.NATS_URL ?? 'nats://localhost:4222';
|
|
274
|
+
const user = options.user ?? process.env.NATS_USER;
|
|
275
|
+
const pass = options.pass ?? process.env.NATS_PASSWORD;
|
|
276
|
+
// 1) Discover handlers once (shared across all streams)
|
|
277
|
+
const handlerConstructors = await discoverHandlers(options.discover);
|
|
278
|
+
const processedHandlers = handlerConstructors
|
|
279
|
+
.map(processHandler)
|
|
280
|
+
.filter((h) => h !== null);
|
|
281
|
+
const handlerNames = processedHandlers.map((h) => h.name).join(', ');
|
|
282
|
+
logger.info(`[${name}] discovered ${processedHandlers.length} handler(s): ${handlerNames}`);
|
|
283
|
+
// 2) Connect to NATS once
|
|
284
|
+
const nc = await connect({
|
|
285
|
+
servers,
|
|
286
|
+
...(user && pass ? { user, pass } : {}),
|
|
287
|
+
});
|
|
288
|
+
logger.info(`[${name}] connected to NATS: ${servers}${user ? ' (authenticated)' : ''}`);
|
|
289
|
+
// 3) Setup JetStream
|
|
290
|
+
const jsm = await nc.jetstreamManager();
|
|
291
|
+
const js = nc.jetstream();
|
|
292
|
+
const dlqEnabled = Boolean(options.quarantineTopic || options.errorTopic);
|
|
293
|
+
const idempotencyStore = options.idempotencyStore === false ? null : (options.idempotencyStore ?? getDefaultIdempotencyStore());
|
|
294
|
+
const idempotencyTtl = options.idempotencyTtl;
|
|
295
|
+
const { handleMessage, handleUnhandledProcessingError } = createJetStreamMessageProcessor({
|
|
296
|
+
name,
|
|
297
|
+
dlqEnabled,
|
|
298
|
+
options,
|
|
299
|
+
processedHandlers,
|
|
300
|
+
decode: (data) => sc.decode(data),
|
|
301
|
+
logger,
|
|
302
|
+
});
|
|
303
|
+
const checkIdempotency = async (msg) => {
|
|
304
|
+
if (!idempotencyStore)
|
|
305
|
+
return true;
|
|
306
|
+
const messageId = `${msg.info.stream}:${msg.seq}`;
|
|
307
|
+
const shouldProcess = await checkAndMarkProcessed(idempotencyStore, messageId, idempotencyTtl);
|
|
308
|
+
if (!shouldProcess) {
|
|
309
|
+
logger.debug(`[${name}] skipping duplicate message: ${messageId}`);
|
|
310
|
+
msg.ack();
|
|
311
|
+
}
|
|
312
|
+
return shouldProcess;
|
|
313
|
+
};
|
|
314
|
+
const processSingleMessage = async (msg) => {
|
|
315
|
+
const shouldProcess = await checkIdempotency(msg);
|
|
316
|
+
if (!shouldProcess)
|
|
317
|
+
return;
|
|
318
|
+
try {
|
|
319
|
+
const success = await handleMessage(msg);
|
|
320
|
+
if (success) {
|
|
321
|
+
msg.ack();
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
msg.nak();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
await handleUnhandledProcessingError(msg, error);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
// 4) Setup consumer for each stream
|
|
332
|
+
const allMessages = [];
|
|
333
|
+
for (const stream of options.streams) {
|
|
334
|
+
const consumerOpts = {
|
|
335
|
+
...options,
|
|
336
|
+
stream,
|
|
337
|
+
};
|
|
338
|
+
await ensureConsumer(jsm, stream, name, consumerOpts);
|
|
339
|
+
const consumer = await js.consumers.get(stream, name);
|
|
340
|
+
const messages = await consumer.consume({
|
|
341
|
+
max_messages: options.maxMessages ?? 100,
|
|
342
|
+
});
|
|
343
|
+
logger.info(`[${name}] consuming from stream ${stream}`);
|
|
344
|
+
(async () => {
|
|
345
|
+
try {
|
|
346
|
+
for await (const msg of messages) {
|
|
347
|
+
await processSingleMessage(msg);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
logger.error(`[${name}] message processing loop crashed for stream ${stream}`, err);
|
|
352
|
+
}
|
|
353
|
+
})();
|
|
354
|
+
allMessages.push(messages);
|
|
355
|
+
}
|
|
356
|
+
return allMessages;
|
|
357
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crossdelta/cloudevents",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.24",
|
|
4
4
|
"description": "CloudEvents toolkit for TypeScript - Zod validation, handler discovery, NATS JetStream",
|
|
5
5
|
"author": "crossdelta",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"pubsub",
|
|
12
12
|
"event-driven",
|
|
13
13
|
"typescript",
|
|
14
|
-
"hono"
|
|
14
|
+
"hono",
|
|
15
|
+
"nestjs",
|
|
16
|
+
"nest"
|
|
15
17
|
],
|
|
16
18
|
"type": "module",
|
|
17
19
|
"main": "dist/index.js",
|
|
@@ -22,11 +24,15 @@
|
|
|
22
24
|
"exports": {
|
|
23
25
|
".": {
|
|
24
26
|
"import": "./dist/index.js",
|
|
25
|
-
"
|
|
27
|
+
"require": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
26
30
|
},
|
|
27
31
|
"./transports/nats": {
|
|
28
32
|
"import": "./dist/src/transports/nats/index.js",
|
|
29
|
-
"
|
|
33
|
+
"require": "./dist/src/transports/nats/index.js",
|
|
34
|
+
"types": "./dist/src/transports/nats/index.d.ts",
|
|
35
|
+
"default": "./dist/src/transports/nats/index.js"
|
|
30
36
|
}
|
|
31
37
|
},
|
|
32
38
|
"publishConfig": {
|
|
@@ -51,7 +57,8 @@
|
|
|
51
57
|
},
|
|
52
58
|
"peerDependencies": {
|
|
53
59
|
"hono": "^4.6.0",
|
|
54
|
-
"zod": "^4.0.0"
|
|
60
|
+
"zod": "^4.0.0",
|
|
61
|
+
"@nestjs/common": "^10.0.0"
|
|
55
62
|
},
|
|
56
63
|
"trustedDependencies": [],
|
|
57
64
|
"devDependencies": {
|