@crossdelta/cloudevents 0.2.1 → 0.3.2

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 CHANGED
@@ -20,10 +20,12 @@ Type-safe event-driven microservices with [NATS](https://nats.io) and [Zod](http
20
20
  ```
21
21
 
22
22
  ```bash
23
- bun add @crossdelta/cloudevents zod
23
+ bun add @crossdelta/cloudevents zod@4
24
24
  ```
25
25
 
26
26
  > **Prerequisites:** A running [NATS server](https://docs.nats.io/running-a-nats-service/introduction) with JetStream enabled.
27
+ >
28
+ > **Note:** Requires Zod v4 for full TypeScript support.
27
29
 
28
30
  ## Quick Start
29
31
 
@@ -33,15 +35,23 @@ bun add @crossdelta/cloudevents zod
33
35
  import { handleEvent } from '@crossdelta/cloudevents'
34
36
  import { z } from 'zod'
35
37
 
36
- export default handleEvent({
37
- type: 'orders.created',
38
- schema: z.object({
39
- orderId: z.string(),
40
- total: z.number(),
41
- }),
42
- }, async (data) => {
43
- console.log(`New order: ${data.orderId}, total: ${data.total}`)
38
+ const OrderCreatedSchema = z.object({
39
+ orderId: z.string(),
40
+ total: z.number(),
44
41
  })
42
+
43
+ // Export type for use in use-cases
44
+ export type OrderCreatedEvent = z.infer<typeof OrderCreatedSchema>
45
+
46
+ export default handleEvent(
47
+ {
48
+ schema: OrderCreatedSchema,
49
+ type: 'orders.created',
50
+ },
51
+ async (data) => {
52
+ console.log(`New order: ${data.orderId}, total: ${data.total}`)
53
+ },
54
+ )
45
55
  ```
46
56
 
47
57
  **2. Start consuming:**
@@ -82,18 +92,52 @@ That's it. Handlers are auto-discovered, validated with Zod, and messages persis
82
92
 
83
93
  ## Core Concepts
84
94
 
95
+ ### Event Type vs. Event Data
96
+
97
+ **Important distinction:**
98
+
99
+ - **Event Type** (`orders.created`): Lives in the CloudEvent **envelope** (`ce.type`). Used for routing and handler matching.
100
+ - **Event Data** (`{ orderId, total }`): The actual payload. Does **not** include the type.
101
+
102
+ ```typescript
103
+ const Schema = z.object({
104
+ orderId: z.string(),
105
+ })
106
+
107
+ export default handleEvent(
108
+ {
109
+ schema: Schema,
110
+ type: 'orders.created',
111
+ },
112
+ async (data) => { ... }
113
+ )
114
+ ```
115
+
85
116
  ### Handlers
86
117
 
87
118
  Drop a `*.event.ts` file anywhere — it's auto-registered:
88
119
 
89
120
  ```typescript
90
121
  // src/handlers/user-signup.event.ts
91
- export default handleEvent({
92
- type: 'users.signup',
93
- schema: z.object({ email: z.string().email() }),
94
- }, async (data) => {
95
- await sendWelcomeEmail(data.email)
122
+ import { z } from 'zod'
123
+
124
+ const UserSignupSchema = z.object({
125
+ email: z.string().email(),
126
+ name: z.string(),
96
127
  })
128
+
129
+ // Export type for use in use-cases
130
+ export type UserSignupEvent = z.infer<typeof UserSignupSchema>
131
+
132
+ export default handleEvent(
133
+ {
134
+ schema: UserSignupSchema,
135
+ type: 'users.signup',
136
+ },
137
+ async (data) => {
138
+ await sendWelcomeEmail(data.email)
139
+ },
140
+ )
97
141
  ```
98
142
 
99
143
  ### Publishing
@@ -19,6 +19,23 @@ export const quarantineMessage = async (processingContext, reason, options, erro
19
19
  return;
20
20
  }
21
21
  try {
22
+ // Serialize error properly - handle ValidationError specially
23
+ let serializedError;
24
+ if (error) {
25
+ if (typeof error === 'object' && error !== null && 'type' in error && error.type === 'ValidationError') {
26
+ serializedError = JSON.stringify(error, null, 2);
27
+ }
28
+ else if (error instanceof Error) {
29
+ serializedError = JSON.stringify({
30
+ name: error.name,
31
+ message: error.message,
32
+ stack: error.stack,
33
+ }, null, 2);
34
+ }
35
+ else {
36
+ serializedError = JSON.stringify(error, null, 2);
37
+ }
38
+ }
22
39
  const quarantineData = {
23
40
  originalMessageId: processingContext.messageId,
24
41
  originalEventType: processingContext.eventType,
@@ -27,7 +44,7 @@ export const quarantineMessage = async (processingContext, reason, options, erro
27
44
  originalCloudEvent: processingContext.originalCloudEvent,
28
45
  quarantinedAt: new Date().toISOString(),
29
46
  quarantineReason: reason,
30
- error: error ? String(error) : undefined,
47
+ error: serializedError,
31
48
  };
32
49
  await publishRawEvent(options.quarantineTopic, 'hono.cloudevents.quarantined', quarantineData, {
33
50
  projectId: options.projectId,
@@ -40,6 +40,8 @@ export function createBaseMessageProcessor(deps) {
40
40
  return { handled: true, shouldAck: true };
41
41
  };
42
42
  const handleValidationFailure = async (validationResult, handler, context) => {
43
+ // Log validation errors with full details for debugging
44
+ logger.error(`[${name}] validation failed for handler ${handler.name}`, JSON.stringify(validationResult.error, null, 2));
43
45
  if (dlqEnabled) {
44
46
  await quarantineMessage(context, 'validation_error', options, validationResult.error);
45
47
  return { handled: true, shouldAck: true };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/cloudevents",
3
- "version": "0.2.1",
3
+ "version": "0.3.2",
4
4
  "description": "CloudEvents toolkit for TypeScript - Zod validation, handler discovery, NATS JetStream",
5
5
  "author": "crossdelta",
6
6
  "license": "MIT",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "peerDependencies": {
52
52
  "hono": "^4.6.0",
53
- "zod": "^3.23.0 || ^4.0.0"
53
+ "zod": "^4.0.0"
54
54
  },
55
55
  "trustedDependencies": [],
56
56
  "devDependencies": {