@crossdelta/cloudevents 0.5.1 → 0.5.3
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 +159 -30
- package/dist/domain/contract-helper.d.ts +35 -6
- package/dist/domain/contract-helper.js +37 -5
- package/dist/domain/discovery.d.ts +3 -3
- package/dist/domain/discovery.js +3 -3
- package/dist/domain/index.d.ts +2 -2
- package/dist/domain/index.js +1 -1
- package/dist/domain/types.d.ts +20 -0
- package/dist/index.d.ts +1 -1
- package/dist/processing/idempotency.d.ts +1 -1
- package/dist/processing/idempotency.js +1 -1
- package/dist/transports/nats/jetstream-consumer.d.ts +2 -2
- package/dist/transports/nats/jetstream-consumer.js +2 -2
- package/dist/transports/nats/nats-consumer.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -29,7 +29,11 @@ bun add @crossdelta/cloudevents zod@4
|
|
|
29
29
|
|
|
30
30
|
## Quick Start
|
|
31
31
|
|
|
32
|
-
**
|
|
32
|
+
> **Note:** Services **never create streams**. Streams must exist before services consume from them:
|
|
33
|
+
> - **Development**: Use [NATS CLI](https://docs.nats.io/running-a-nats-service/nats_admin/jetstream_admin/streams) or [Platform SDK](https://www.npmjs.com/package/@crossdelta/platform-sdk) (`pf dev` auto-creates from contracts)
|
|
34
|
+
> - **Production**: Use infrastructure-as-code (Pulumi, Terraform) or the [Stream Setup](#stream-setup) function below
|
|
35
|
+
|
|
36
|
+
**1. Create an event handler** (`src/events/orders-created.handler.ts`):
|
|
33
37
|
|
|
34
38
|
```typescript
|
|
35
39
|
import { handleEvent } from '@crossdelta/cloudevents'
|
|
@@ -55,17 +59,7 @@ export default handleEvent(
|
|
|
55
59
|
)
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
**2.
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import { ensureJetStreams } from '@crossdelta/cloudevents'
|
|
62
|
-
|
|
63
|
-
await ensureJetStreams({
|
|
64
|
-
streams: [{ stream: 'ORDERS', subjects: ['orders.*'] }],
|
|
65
|
-
})
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
**3. Start consuming:**
|
|
62
|
+
**2. Start consuming:**
|
|
69
63
|
|
|
70
64
|
```typescript
|
|
71
65
|
import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
@@ -73,11 +67,11 @@ import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
|
73
67
|
consumeJetStreams({
|
|
74
68
|
streams: ['ORDERS'],
|
|
75
69
|
consumer: 'my-service',
|
|
76
|
-
discover: './src/events/**/*.
|
|
70
|
+
discover: './src/events/**/*.handler.ts',
|
|
77
71
|
})
|
|
78
72
|
```
|
|
79
73
|
|
|
80
|
-
**
|
|
74
|
+
**3. Publish from another service:**
|
|
81
75
|
|
|
82
76
|
```typescript
|
|
83
77
|
import { publish } from '@crossdelta/cloudevents'
|
|
@@ -87,6 +81,10 @@ await publish('orders.created', { orderId: 'ord_123', total: 99.99 })
|
|
|
87
81
|
|
|
88
82
|
That's it. Handlers are auto-discovered, validated with Zod, and messages persist in JetStream.
|
|
89
83
|
|
|
84
|
+
> **Stream creation** is handled by your development environment or infrastructure — not by services:
|
|
85
|
+
> - Development: [NATS CLI](https://docs.nats.io/running-a-nats-service/nats_admin/jetstream_admin/streams), [@crossdelta/platform-sdk](https://www.npmjs.com/package/@crossdelta/platform-sdk), or manual setup
|
|
86
|
+
> - Production: See [Stream Setup](#stream-setup) below for `ensureJetStreams()` usage in infrastructure code
|
|
87
|
+
|
|
90
88
|
---
|
|
91
89
|
|
|
92
90
|
## Why use this?
|
|
@@ -125,10 +123,10 @@ export default handleEvent(
|
|
|
125
123
|
|
|
126
124
|
### Handlers
|
|
127
125
|
|
|
128
|
-
Drop a `*.
|
|
126
|
+
Drop a `*.handler.ts` file anywhere — it's auto-registered:
|
|
129
127
|
|
|
130
128
|
```typescript
|
|
131
|
-
// src/events/user-signup.
|
|
129
|
+
// src/events/user-signup.handler.ts
|
|
132
130
|
import { z } from 'zod'
|
|
133
131
|
|
|
134
132
|
const UserSignupSchema = z.object({
|
|
@@ -158,7 +156,9 @@ await publish('orders.created', orderData)
|
|
|
158
156
|
|
|
159
157
|
### Stream Setup
|
|
160
158
|
|
|
161
|
-
|
|
159
|
+
> **For infrastructure use only** - Services should never call this function.
|
|
160
|
+
|
|
161
|
+
Create streams during infrastructure setup (Pulumi, deployment scripts):
|
|
162
162
|
|
|
163
163
|
```typescript
|
|
164
164
|
import { ensureJetStreams } from '@crossdelta/cloudevents'
|
|
@@ -175,6 +175,7 @@ await ensureJetStreams({
|
|
|
175
175
|
},
|
|
176
176
|
],
|
|
177
177
|
})
|
|
178
|
+
})
|
|
178
179
|
```
|
|
179
180
|
|
|
180
181
|
### Consuming
|
|
@@ -186,13 +187,13 @@ import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
|
186
187
|
consumeJetStreams({
|
|
187
188
|
streams: ['ORDERS'],
|
|
188
189
|
consumer: 'billing',
|
|
189
|
-
discover: './src/events/**/*.
|
|
190
|
+
discover: './src/events/**/*.handler.ts',
|
|
190
191
|
})
|
|
191
192
|
|
|
192
193
|
// Core NATS — fire-and-forget, simpler
|
|
193
194
|
await consumeNatsEvents({
|
|
194
195
|
subjects: ['notifications.*'],
|
|
195
|
-
discover: './src/events/**/*.
|
|
196
|
+
discover: './src/events/**/*.handler.ts',
|
|
196
197
|
})
|
|
197
198
|
```
|
|
198
199
|
|
|
@@ -239,7 +240,7 @@ consumeJetStreams({
|
|
|
239
240
|
// Required
|
|
240
241
|
streams: ['ORDERS'],
|
|
241
242
|
consumer: 'my-service',
|
|
242
|
-
discover: './src/events/**/*.
|
|
243
|
+
discover: './src/events/**/*.handler.ts',
|
|
243
244
|
|
|
244
245
|
// Optional
|
|
245
246
|
servers: 'nats://localhost:4222',
|
|
@@ -254,6 +255,129 @@ consumeJetStreams({
|
|
|
254
255
|
|
|
255
256
|
## Advanced Features
|
|
256
257
|
|
|
258
|
+
<details>
|
|
259
|
+
<parameter name="summary"><b>Channel Metadata (Infrastructure Integration)</b></summary>
|
|
260
|
+
|
|
261
|
+
<br />
|
|
262
|
+
|
|
263
|
+
Contracts can include **channel metadata** to define which NATS JetStream stream they belong to. This enables infrastructure-as-code workflows where streams are materialized from contracts.
|
|
264
|
+
|
|
265
|
+
### Basic Contract with Channel
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { createContract } from '@crossdelta/cloudevents'
|
|
269
|
+
import { z } from 'zod'
|
|
270
|
+
|
|
271
|
+
export const OrdersCreatedContract = createContract({
|
|
272
|
+
type: 'orders.created',
|
|
273
|
+
channel: {
|
|
274
|
+
stream: 'ORDERS',
|
|
275
|
+
// subject defaults to 'orders.created' if omitted
|
|
276
|
+
},
|
|
277
|
+
schema: z.object({
|
|
278
|
+
orderId: z.string(),
|
|
279
|
+
total: z.number(),
|
|
280
|
+
}),
|
|
281
|
+
})
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Channel Configuration
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
interface ChannelConfig {
|
|
288
|
+
stream: string // JetStream stream name (e.g., 'ORDERS')
|
|
289
|
+
subject?: string // NATS subject (defaults to event type)
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Subject Defaulting:**
|
|
294
|
+
- If `subject` is **not provided**, it defaults to the event `type`
|
|
295
|
+
- This follows the convention: `orders.created` → subject `orders.created`
|
|
296
|
+
- Override when needed: `subject: 'orders.v1.created'`
|
|
297
|
+
|
|
298
|
+
### Multiple Events, Same Stream
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// orders.created → ORDERS stream
|
|
302
|
+
export const OrdersCreatedContract = createContract({
|
|
303
|
+
type: 'orders.created',
|
|
304
|
+
channel: { stream: 'ORDERS' },
|
|
305
|
+
schema: OrderCreatedSchema,
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
// orders.updated → ORDERS stream (same stream!)
|
|
309
|
+
export const OrdersUpdatedContract = createContract({
|
|
310
|
+
type: 'orders.updated',
|
|
311
|
+
channel: { stream: 'ORDERS' },
|
|
312
|
+
schema: OrderUpdatedSchema,
|
|
313
|
+
})
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Result:** Both events share the `ORDERS` stream with subjects `orders.created` and `orders.updated`.
|
|
317
|
+
|
|
318
|
+
### Infrastructure Materialization
|
|
319
|
+
|
|
320
|
+
Channel metadata is **conceptual** - it defines routing, not infrastructure policy.
|
|
321
|
+
|
|
322
|
+
**Contracts define:**
|
|
323
|
+
- Event type (`orders.created`)
|
|
324
|
+
- Stream routing (`ORDERS`)
|
|
325
|
+
- Subject mapping (`orders.created`)
|
|
326
|
+
|
|
327
|
+
**Infrastructure defines:**
|
|
328
|
+
- Retention (7 days, 14 days, etc.)
|
|
329
|
+
- Storage (disk, memory)
|
|
330
|
+
- Replicas (1, 3, etc.)
|
|
331
|
+
- Limits (max message size, etc.)
|
|
332
|
+
|
|
333
|
+
**Example Infrastructure (Pulumi):**
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { collectStreamDefinitions } from './helpers/streams'
|
|
337
|
+
import { ensureJetStreams } from '@crossdelta/cloudevents'
|
|
338
|
+
|
|
339
|
+
// Collect from contracts
|
|
340
|
+
const streams = collectStreamDefinitions()
|
|
341
|
+
// [{ stream: 'ORDERS', subjects: ['orders.created', 'orders.updated'] }]
|
|
342
|
+
|
|
343
|
+
// Materialize with policies
|
|
344
|
+
await ensureJetStreams({
|
|
345
|
+
streams: streams.map(({ stream, subjects }) => ({
|
|
346
|
+
stream,
|
|
347
|
+
subjects,
|
|
348
|
+
config: {
|
|
349
|
+
maxAge: 14 * 24 * 60 * 60 * 1000, // 14 days
|
|
350
|
+
replicas: 3, // High availability
|
|
351
|
+
storage: 'file', // Persistent
|
|
352
|
+
},
|
|
353
|
+
})),
|
|
354
|
+
})
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Development vs. Production
|
|
358
|
+
|
|
359
|
+
| Environment | Streams | How | Retention |
|
|
360
|
+
|-------------|---------|-----|-----------|
|
|
361
|
+
| **Development** | Ephemeral | Auto-created by services | None (memory) |
|
|
362
|
+
| **Production** | Persistent | Materialized via infrastructure | Explicit (7d, 14d, etc.) |
|
|
363
|
+
|
|
364
|
+
**Key Principle:** Contracts define **what** and **where**, infrastructure defines **how** and **how long**.
|
|
365
|
+
|
|
366
|
+
### Backward Compatibility
|
|
367
|
+
|
|
368
|
+
Channel metadata is **optional** - contracts without `channel` work as before:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
// Legacy contract (still works)
|
|
372
|
+
export const LegacyContract = createContract({
|
|
373
|
+
type: 'legacy.event',
|
|
374
|
+
schema: LegacySchema,
|
|
375
|
+
// No channel - handler still works
|
|
376
|
+
})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
</details>
|
|
380
|
+
|
|
257
381
|
<details>
|
|
258
382
|
<summary><b>Shared Contracts</b></summary>
|
|
259
383
|
|
|
@@ -340,8 +464,10 @@ const redisStore = {
|
|
|
340
464
|
add: (id, ttl) => redis.set(`idem:${id}`, '1', 'PX', ttl),
|
|
341
465
|
}
|
|
342
466
|
|
|
343
|
-
await
|
|
344
|
-
|
|
467
|
+
await consumeJetStreams({
|
|
468
|
+
streams: ['ORDERS'],
|
|
469
|
+
consumer: 'my-service',
|
|
470
|
+
discover: './src/events/**/*.handler.ts',
|
|
345
471
|
idempotencyStore: redisStore,
|
|
346
472
|
})
|
|
347
473
|
```
|
|
@@ -354,13 +480,17 @@ await consumeJetStreamEvents({
|
|
|
354
480
|
Invalid messages are quarantined, not lost:
|
|
355
481
|
|
|
356
482
|
```typescript
|
|
357
|
-
await
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
483
|
+
await consumeJetStreams({
|
|
484
|
+
streams: ['ORDERS'],
|
|
485
|
+
consumer: 'my-service',
|
|
486
|
+
discover: './src/events/**/*.handler.ts',
|
|
487
|
+
quarantineTopic: 'events.quarantine', // For malformed messages
|
|
488
|
+
errorTopic: 'events.errors', // For handler errors
|
|
361
489
|
})
|
|
362
490
|
```
|
|
363
491
|
|
|
492
|
+
Messages that fail validation go to `quarantineTopic`, messages that crash handlers go to `errorTopic`.
|
|
493
|
+
|
|
364
494
|
</details>
|
|
365
495
|
|
|
366
496
|
<details>
|
|
@@ -371,7 +501,7 @@ import { Hono } from 'hono'
|
|
|
371
501
|
import { cloudEvents } from '@crossdelta/cloudevents'
|
|
372
502
|
|
|
373
503
|
const app = new Hono()
|
|
374
|
-
app.use('/events', cloudEvents({ discover: 'src/events/**/*.
|
|
504
|
+
app.use('/events', cloudEvents({ discover: 'src/events/**/*.handler.ts' }))
|
|
375
505
|
```
|
|
376
506
|
|
|
377
507
|
</details>
|
|
@@ -384,9 +514,8 @@ app.use('/events', cloudEvents({ discover: 'src/events/**/*.event.ts' }))
|
|
|
384
514
|
|----------|---------|
|
|
385
515
|
| `handleEvent(options, handler)` | Create a handler |
|
|
386
516
|
| `createContract(options)` | Create shared event contract |
|
|
387
|
-
| `ensureJetStreams(options)` | Create/update JetStream streams
|
|
388
|
-
| `consumeJetStreams(options)` | Consume from multiple streams
|
|
389
|
-
| `consumeJetStreamEvents(options)` | Consume single stream (legacy) |
|
|
517
|
+
| `ensureJetStreams(options)` | Create/update JetStream streams |
|
|
518
|
+
| `consumeJetStreams(options)` | Consume from multiple streams |
|
|
390
519
|
| `consumeNatsEvents(options)` | Consume fire-and-forget |
|
|
391
520
|
| `publish(type, data)` | Publish event |
|
|
392
521
|
|
|
@@ -1,26 +1,50 @@
|
|
|
1
1
|
import type { ZodTypeAny } from 'zod';
|
|
2
|
-
import type { HandleEventOptions } from './types';
|
|
2
|
+
import type { ChannelMetadata, HandleEventOptions } from './types';
|
|
3
3
|
/**
|
|
4
|
-
* Creates a type-safe event contract
|
|
4
|
+
* Creates a type-safe event contract with optional channel metadata
|
|
5
5
|
*
|
|
6
6
|
* This helper ensures proper type inference when using contracts
|
|
7
7
|
* with handleEvent across package boundaries.
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
|
+
* Basic contract without channel:
|
|
10
11
|
* ```typescript
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* export const OrderCreatedContract = createContract({
|
|
13
|
+
* type: 'orders.created',
|
|
14
|
+
* schema: z.object({
|
|
15
|
+
* orderId: z.string(),
|
|
16
|
+
* total: z.number(),
|
|
17
|
+
* }),
|
|
18
|
+
* })
|
|
19
|
+
* ```
|
|
13
20
|
*
|
|
21
|
+
* @example
|
|
22
|
+
* Contract with channel metadata:
|
|
23
|
+
* ```typescript
|
|
14
24
|
* export const OrderCreatedContract = createContract({
|
|
15
25
|
* type: 'orders.created',
|
|
26
|
+
* channel: {
|
|
27
|
+
* stream: 'ORDERS',
|
|
28
|
+
* // subject defaults to 'orders.created' if omitted
|
|
29
|
+
* },
|
|
16
30
|
* schema: z.object({
|
|
17
31
|
* orderId: z.string(),
|
|
18
32
|
* total: z.number(),
|
|
19
33
|
* }),
|
|
20
34
|
* })
|
|
35
|
+
* ```
|
|
21
36
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
37
|
+
* @example
|
|
38
|
+
* Contract with explicit subject:
|
|
39
|
+
* ```typescript
|
|
40
|
+
* export const OrderCreatedContract = createContract({
|
|
41
|
+
* type: 'orders.created',
|
|
42
|
+
* channel: {
|
|
43
|
+
* stream: 'ORDERS',
|
|
44
|
+
* subject: 'orders.v1.created', // Override default
|
|
45
|
+
* },
|
|
46
|
+
* schema: OrderSchema,
|
|
47
|
+
* })
|
|
24
48
|
* ```
|
|
25
49
|
*/
|
|
26
50
|
export declare function createContract<TSchema extends ZodTypeAny>(options: {
|
|
@@ -29,6 +53,11 @@ export declare function createContract<TSchema extends ZodTypeAny>(options: {
|
|
|
29
53
|
match?: HandleEventOptions['match'];
|
|
30
54
|
safeParse?: boolean;
|
|
31
55
|
tenantId?: string | string[];
|
|
56
|
+
channel?: {
|
|
57
|
+
stream: string;
|
|
58
|
+
subject?: string;
|
|
59
|
+
};
|
|
32
60
|
}): HandleEventOptions<TSchema> & {
|
|
33
61
|
_schema: TSchema;
|
|
62
|
+
channel?: ChannelMetadata;
|
|
34
63
|
};
|
|
@@ -1,29 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Creates a type-safe event contract
|
|
2
|
+
* Creates a type-safe event contract with optional channel metadata
|
|
3
3
|
*
|
|
4
4
|
* This helper ensures proper type inference when using contracts
|
|
5
5
|
* with handleEvent across package boundaries.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
|
+
* Basic contract without channel:
|
|
8
9
|
* ```typescript
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* export const OrderCreatedContract = createContract({
|
|
11
|
+
* type: 'orders.created',
|
|
12
|
+
* schema: z.object({
|
|
13
|
+
* orderId: z.string(),
|
|
14
|
+
* total: z.number(),
|
|
15
|
+
* }),
|
|
16
|
+
* })
|
|
17
|
+
* ```
|
|
11
18
|
*
|
|
19
|
+
* @example
|
|
20
|
+
* Contract with channel metadata:
|
|
21
|
+
* ```typescript
|
|
12
22
|
* export const OrderCreatedContract = createContract({
|
|
13
23
|
* type: 'orders.created',
|
|
24
|
+
* channel: {
|
|
25
|
+
* stream: 'ORDERS',
|
|
26
|
+
* // subject defaults to 'orders.created' if omitted
|
|
27
|
+
* },
|
|
14
28
|
* schema: z.object({
|
|
15
29
|
* orderId: z.string(),
|
|
16
30
|
* total: z.number(),
|
|
17
31
|
* }),
|
|
18
32
|
* })
|
|
33
|
+
* ```
|
|
19
34
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
35
|
+
* @example
|
|
36
|
+
* Contract with explicit subject:
|
|
37
|
+
* ```typescript
|
|
38
|
+
* export const OrderCreatedContract = createContract({
|
|
39
|
+
* type: 'orders.created',
|
|
40
|
+
* channel: {
|
|
41
|
+
* stream: 'ORDERS',
|
|
42
|
+
* subject: 'orders.v1.created', // Override default
|
|
43
|
+
* },
|
|
44
|
+
* schema: OrderSchema,
|
|
45
|
+
* })
|
|
22
46
|
* ```
|
|
23
47
|
*/
|
|
24
48
|
export function createContract(options) {
|
|
49
|
+
// Resolve channel metadata: default subject to type if not provided
|
|
50
|
+
const resolvedChannel = options.channel
|
|
51
|
+
? {
|
|
52
|
+
stream: options.channel.stream,
|
|
53
|
+
subject: options.channel.subject ?? options.type,
|
|
54
|
+
}
|
|
55
|
+
: undefined;
|
|
25
56
|
return {
|
|
26
57
|
...options,
|
|
27
58
|
_schema: options.schema,
|
|
59
|
+
channel: resolvedChannel,
|
|
28
60
|
};
|
|
29
61
|
}
|
|
@@ -2,17 +2,17 @@ import type { HandlerConstructor } from './types';
|
|
|
2
2
|
/**
|
|
3
3
|
* Discovers event handlers from files matching the given glob pattern.
|
|
4
4
|
*
|
|
5
|
-
* @param pattern - Glob pattern to match handler files (e.g., 'events/*.
|
|
5
|
+
* @param pattern - Glob pattern to match handler files (e.g., 'events/*.handler.ts')
|
|
6
6
|
* @param options - Configuration options
|
|
7
7
|
* @returns Promise resolving to array of discovered handler constructors
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```typescript
|
|
11
11
|
* // Discover all handlers in events directory
|
|
12
|
-
* const handlers = await discoverHandlers('events/*.
|
|
12
|
+
* const handlers = await discoverHandlers('events/*.handler.ts')
|
|
13
13
|
*
|
|
14
14
|
* // With filtering and logging
|
|
15
|
-
* const handlers = await discoverHandlers('events/*.
|
|
15
|
+
* const handlers = await discoverHandlers('events/*.handler.ts', {
|
|
16
16
|
* filter: (name, handler) => name.includes('Customer'),
|
|
17
17
|
* log: true
|
|
18
18
|
* })
|
package/dist/domain/discovery.js
CHANGED
|
@@ -144,17 +144,17 @@ const discoverFiles = async (pattern, basePath, preferCompiled) => {
|
|
|
144
144
|
/**
|
|
145
145
|
* Discovers event handlers from files matching the given glob pattern.
|
|
146
146
|
*
|
|
147
|
-
* @param pattern - Glob pattern to match handler files (e.g., 'events/*.
|
|
147
|
+
* @param pattern - Glob pattern to match handler files (e.g., 'events/*.handler.ts')
|
|
148
148
|
* @param options - Configuration options
|
|
149
149
|
* @returns Promise resolving to array of discovered handler constructors
|
|
150
150
|
*
|
|
151
151
|
* @example
|
|
152
152
|
* ```typescript
|
|
153
153
|
* // Discover all handlers in events directory
|
|
154
|
-
* const handlers = await discoverHandlers('events/*.
|
|
154
|
+
* const handlers = await discoverHandlers('events/*.handler.ts')
|
|
155
155
|
*
|
|
156
156
|
* // With filtering and logging
|
|
157
|
-
* const handlers = await discoverHandlers('events/*.
|
|
157
|
+
* const handlers = await discoverHandlers('events/*.handler.ts', {
|
|
158
158
|
* filter: (name, handler) => name.includes('Customer'),
|
|
159
159
|
* log: true
|
|
160
160
|
* })
|
package/dist/domain/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { discoverHandlers } from './discovery';
|
|
2
1
|
export { createContract } from './contract-helper';
|
|
2
|
+
export { discoverHandlers } from './discovery';
|
|
3
3
|
export { eventSchema, handleEvent } from './handler-factory';
|
|
4
|
-
export type { EnrichedEvent, EventHandler, HandleEventOptions, HandlerConstructor, HandlerMetadata, IdempotencyStore, InferEventData, MatchFn, RoutingConfig, } from './types';
|
|
4
|
+
export type { ChannelConfig, ChannelMetadata, EnrichedEvent, EventHandler, HandleEventOptions, HandlerConstructor, HandlerMetadata, IdempotencyStore, InferEventData, MatchFn, RoutingConfig, } from './types';
|
|
5
5
|
export type { HandlerValidationError, ValidationErrorDetail } from './validation';
|
|
6
6
|
export { extractTypeFromSchema, isValidHandler } from './validation';
|
package/dist/domain/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { discoverHandlers } from './discovery';
|
|
2
1
|
export { createContract } from './contract-helper';
|
|
2
|
+
export { discoverHandlers } from './discovery';
|
|
3
3
|
export { eventSchema, handleEvent } from './handler-factory';
|
|
4
4
|
export { extractTypeFromSchema, isValidHandler } from './validation';
|
package/dist/domain/types.d.ts
CHANGED
|
@@ -43,6 +43,24 @@ export type HandlerMetadata<S extends ZodTypeAny> = {
|
|
|
43
43
|
match?: MatchFn<unknown>;
|
|
44
44
|
safeParse: boolean;
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Channel metadata for NATS JetStream routing
|
|
48
|
+
*/
|
|
49
|
+
export interface ChannelMetadata {
|
|
50
|
+
/** JetStream stream name (e.g., 'ORDERS') */
|
|
51
|
+
stream: string;
|
|
52
|
+
/** NATS subject (defaults to event type if not specified) */
|
|
53
|
+
subject: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Channel configuration input (subject is optional, defaults to type)
|
|
57
|
+
*/
|
|
58
|
+
export interface ChannelConfig {
|
|
59
|
+
/** JetStream stream name (e.g., 'ORDERS') */
|
|
60
|
+
stream: string;
|
|
61
|
+
/** NATS subject (optional, defaults to event type) */
|
|
62
|
+
subject?: string;
|
|
63
|
+
}
|
|
46
64
|
/**
|
|
47
65
|
* Options for creating event handlers
|
|
48
66
|
*
|
|
@@ -56,6 +74,8 @@ export interface HandleEventOptions<S = ZodTypeAny> {
|
|
|
56
74
|
safeParse?: boolean;
|
|
57
75
|
/** Filter events by tenant ID(s). If set, only events matching these tenant(s) are processed. */
|
|
58
76
|
tenantId?: string | string[];
|
|
77
|
+
/** Channel metadata for NATS JetStream routing (optional) */
|
|
78
|
+
channel?: ChannelConfig;
|
|
59
79
|
}
|
|
60
80
|
/**
|
|
61
81
|
* Routing configuration for type → subject → stream mapping
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { EventContext } from './adapters/cloudevents';
|
|
2
2
|
export { parseEventFromContext } from './adapters/cloudevents';
|
|
3
|
-
export type { EnrichedEvent, HandleEventOptions, IdempotencyStore, InferEventData, RoutingConfig } from './domain';
|
|
3
|
+
export type { ChannelConfig, ChannelMetadata, EnrichedEvent, HandleEventOptions, IdempotencyStore, InferEventData, RoutingConfig, } from './domain';
|
|
4
4
|
export { createContract, eventSchema, extractTypeFromSchema, handleEvent } from './domain';
|
|
5
5
|
export { clearHandlerCache, cloudEvents } from './middlewares';
|
|
6
6
|
export { checkAndMarkProcessed, createInMemoryIdempotencyStore, getDefaultIdempotencyStore, resetDefaultIdempotencyStore, } from './processing/idempotency';
|
|
@@ -28,7 +28,7 @@ export interface InMemoryIdempotencyStoreOptions {
|
|
|
28
28
|
* stream: 'ORDERS',
|
|
29
29
|
* subjects: ['orders.>'],
|
|
30
30
|
* consumer: 'notifications',
|
|
31
|
-
* discover:
|
|
31
|
+
* discover: `./src/events/**\/*.handler.ts`,
|
|
32
32
|
* idempotencyStore: store,
|
|
33
33
|
* })
|
|
34
34
|
* ```
|
|
@@ -157,7 +157,7 @@ export declare function ensureJetStreamStreams(options: JetStreamStreamsOptions)
|
|
|
157
157
|
* stream: 'ORDERS',
|
|
158
158
|
* subjects: ['orders.>'],
|
|
159
159
|
* consumer: 'notifications',
|
|
160
|
-
* discover:
|
|
160
|
+
* discover: `./src/events/**\/*.handler.ts`,
|
|
161
161
|
* })
|
|
162
162
|
* ```
|
|
163
163
|
*/
|
|
@@ -200,7 +200,7 @@ export interface JetStreamStreamsConsumerOptions extends Pick<CloudEventsOptions
|
|
|
200
200
|
* await consumeJetStreamStreams({
|
|
201
201
|
* streams: ['ORDERS', 'CUSTOMERS'],
|
|
202
202
|
* consumer: 'notifications',
|
|
203
|
-
* discover:
|
|
203
|
+
* discover: `./src/events/**\/*.handler.ts`,
|
|
204
204
|
* })
|
|
205
205
|
* ```
|
|
206
206
|
*/
|
|
@@ -167,7 +167,7 @@ async function ensureConsumer(jsm, streamName, consumerName, options) {
|
|
|
167
167
|
* stream: 'ORDERS',
|
|
168
168
|
* subjects: ['orders.>'],
|
|
169
169
|
* consumer: 'notifications',
|
|
170
|
-
* discover:
|
|
170
|
+
* discover: `./src/events/**\/*.handler.ts`,
|
|
171
171
|
* })
|
|
172
172
|
* ```
|
|
173
173
|
*/
|
|
@@ -264,7 +264,7 @@ export async function consumeJetStreamEvents(options) {
|
|
|
264
264
|
* await consumeJetStreamStreams({
|
|
265
265
|
* streams: ['ORDERS', 'CUSTOMERS'],
|
|
266
266
|
* consumer: 'notifications',
|
|
267
|
-
* discover:
|
|
267
|
+
* discover: `./src/events/**\/*.handler.ts`,
|
|
268
268
|
* })
|
|
269
269
|
* ```
|
|
270
270
|
*/
|
|
@@ -44,7 +44,7 @@ export async function consumeNatsEvents(options) {
|
|
|
44
44
|
const pass = options.pass ?? process.env.NATS_PASSWORD;
|
|
45
45
|
// Cleanup existing consumer with same name (handles hot-reload)
|
|
46
46
|
await cleanupConsumer(name);
|
|
47
|
-
// 1) Discover handler classes from *.
|
|
47
|
+
// 1) Discover handler classes from *.handler.ts files
|
|
48
48
|
const handlerConstructors = await discoverHandlers(options.discover);
|
|
49
49
|
const processedHandlers = handlerConstructors
|
|
50
50
|
.map(processHandler)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crossdelta/cloudevents",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "CloudEvents toolkit for TypeScript - Zod validation, handler discovery, NATS JetStream",
|
|
5
5
|
"author": "crossdelta",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,15 +23,15 @@
|
|
|
23
23
|
],
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
26
27
|
"import": "./dist/index.js",
|
|
27
28
|
"require": "./dist/index.js",
|
|
28
|
-
"types": "./dist/index.d.ts",
|
|
29
29
|
"default": "./dist/index.js"
|
|
30
30
|
},
|
|
31
31
|
"./transports/nats": {
|
|
32
|
+
"types": "./dist/src/transports/nats/index.d.ts",
|
|
32
33
|
"import": "./dist/src/transports/nats/index.js",
|
|
33
34
|
"require": "./dist/src/transports/nats/index.js",
|
|
34
|
-
"types": "./dist/src/transports/nats/index.d.ts",
|
|
35
35
|
"default": "./dist/src/transports/nats/index.js"
|
|
36
36
|
}
|
|
37
37
|
},
|