@saga-bus/middleware-validation 0.1.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/LICENSE +21 -0
- package/README.md +355 -0
- package/dist/index.cjs +201 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +223 -0
- package/dist/index.d.ts +223 -0
- package/dist/index.js +169 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Dean Foran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# @saga-bus/middleware-validation
|
|
2
|
+
|
|
3
|
+
Validation middleware for saga-bus that validates message payloads before processing.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @saga-bus/middleware-validation
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @saga-bus/middleware-validation
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For Zod support:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install zod
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Schema Validation**: Validate messages against Zod schemas or custom validators
|
|
22
|
+
- **Multiple Actions**: Skip, log, throw, or DLQ invalid messages
|
|
23
|
+
- **Strict Mode**: Optionally reject messages without registered validators
|
|
24
|
+
- **Type Exclusions**: Skip validation for specific message types
|
|
25
|
+
- **Custom Validators**: Use function-based validators for complex logic
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createBus } from "@saga-bus/core";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import {
|
|
33
|
+
createValidationMiddleware,
|
|
34
|
+
createZodValidator,
|
|
35
|
+
} from "@saga-bus/middleware-validation";
|
|
36
|
+
|
|
37
|
+
const OrderCreatedSchema = z.object({
|
|
38
|
+
type: z.literal("OrderCreated"),
|
|
39
|
+
orderId: z.string().uuid(),
|
|
40
|
+
customerId: z.string(),
|
|
41
|
+
items: z.array(z.object({
|
|
42
|
+
sku: z.string(),
|
|
43
|
+
quantity: z.number().positive(),
|
|
44
|
+
})).min(1),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const validationMiddleware = createValidationMiddleware({
|
|
48
|
+
validators: {
|
|
49
|
+
OrderCreated: createZodValidator(OrderCreatedSchema),
|
|
50
|
+
},
|
|
51
|
+
onInvalid: "throw",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const bus = createBus({
|
|
55
|
+
transport,
|
|
56
|
+
store,
|
|
57
|
+
sagas: [OrderSaga],
|
|
58
|
+
middleware: [validationMiddleware],
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API Reference
|
|
63
|
+
|
|
64
|
+
### createValidationMiddleware(options)
|
|
65
|
+
|
|
66
|
+
Creates middleware that validates message payloads.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
interface ValidationMiddlewareOptions {
|
|
70
|
+
/** Map of message type to validator */
|
|
71
|
+
validators: Record<string, MessageValidator>;
|
|
72
|
+
|
|
73
|
+
/** Action on invalid: "skip" | "log" | "throw" | "dlq" (default: "throw") */
|
|
74
|
+
onInvalid?: "skip" | "log" | "throw" | "dlq";
|
|
75
|
+
|
|
76
|
+
/** Handler for DLQ (required if onInvalid is "dlq") */
|
|
77
|
+
deadLetterHandler?: (envelope: MessageEnvelope, errors: ValidationError[]) => Promise<void>;
|
|
78
|
+
|
|
79
|
+
/** Logger for validation errors */
|
|
80
|
+
logger?: { warn(...): void; error(...): void };
|
|
81
|
+
|
|
82
|
+
/** Reject messages without validators (default: false) */
|
|
83
|
+
strictMode?: boolean;
|
|
84
|
+
|
|
85
|
+
/** Message types to skip validation */
|
|
86
|
+
excludeTypes?: string[];
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### createZodValidator(schema)
|
|
91
|
+
|
|
92
|
+
Creates a validator from a Zod schema.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { z } from "zod";
|
|
96
|
+
import { createZodValidator } from "@saga-bus/middleware-validation";
|
|
97
|
+
|
|
98
|
+
const validator = createZodValidator(z.object({
|
|
99
|
+
type: z.literal("OrderCreated"),
|
|
100
|
+
orderId: z.string(),
|
|
101
|
+
}));
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### createZodValidators(schemas)
|
|
105
|
+
|
|
106
|
+
Creates multiple validators from a map of Zod schemas.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { createZodValidators } from "@saga-bus/middleware-validation";
|
|
110
|
+
|
|
111
|
+
const validators = createZodValidators({
|
|
112
|
+
OrderCreated: OrderCreatedSchema,
|
|
113
|
+
OrderShipped: OrderShippedSchema,
|
|
114
|
+
PaymentReceived: PaymentReceivedSchema,
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### createFunctionValidator(fn)
|
|
119
|
+
|
|
120
|
+
Creates a validator from a simple validation function.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { createFunctionValidator } from "@saga-bus/middleware-validation";
|
|
124
|
+
|
|
125
|
+
const validator = createFunctionValidator((message) => {
|
|
126
|
+
if (!message.orderId) return "orderId is required";
|
|
127
|
+
if (message.items.length === 0) return "Order must have items";
|
|
128
|
+
return true;
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### combineValidators(validators)
|
|
133
|
+
|
|
134
|
+
Combines multiple validators into one.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { combineValidators, createFunctionValidator } from "@saga-bus/middleware-validation";
|
|
138
|
+
|
|
139
|
+
const validator = combineValidators([
|
|
140
|
+
createFunctionValidator((msg) => !!msg.orderId || "orderId required"),
|
|
141
|
+
createFunctionValidator((msg) => msg.items.length > 0 || "items required"),
|
|
142
|
+
]);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### MessageValidationError
|
|
146
|
+
|
|
147
|
+
Error thrown when validation fails and `onInvalid: "throw"`.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { MessageValidationError } from "@saga-bus/middleware-validation";
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
await bus.publish(message);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof MessageValidationError) {
|
|
156
|
+
console.log(`Invalid message: ${error.messageId}`);
|
|
157
|
+
console.log(`Errors:`, error.validationErrors);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Examples
|
|
163
|
+
|
|
164
|
+
### Basic Zod Validation
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { z } from "zod";
|
|
168
|
+
import { createValidationMiddleware, createZodValidator } from "@saga-bus/middleware-validation";
|
|
169
|
+
|
|
170
|
+
const middleware = createValidationMiddleware({
|
|
171
|
+
validators: {
|
|
172
|
+
OrderCreated: createZodValidator(z.object({
|
|
173
|
+
type: z.literal("OrderCreated"),
|
|
174
|
+
orderId: z.string().uuid(),
|
|
175
|
+
customerId: z.string(),
|
|
176
|
+
items: z.array(z.object({
|
|
177
|
+
sku: z.string(),
|
|
178
|
+
quantity: z.number().int().positive(),
|
|
179
|
+
price: z.number().nonnegative(),
|
|
180
|
+
})).min(1),
|
|
181
|
+
total: z.number().nonnegative(),
|
|
182
|
+
})),
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Custom Function Validator
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { createValidationMiddleware, createFunctionValidator } from "@saga-bus/middleware-validation";
|
|
191
|
+
|
|
192
|
+
const middleware = createValidationMiddleware({
|
|
193
|
+
validators: {
|
|
194
|
+
PaymentReceived: createFunctionValidator(async (message) => {
|
|
195
|
+
// Async validation - check against external service
|
|
196
|
+
const isValidPaymentId = await paymentService.verify(message.paymentId);
|
|
197
|
+
if (!isValidPaymentId) {
|
|
198
|
+
return "Invalid payment ID";
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}),
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Dead Letter Queue
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { createValidationMiddleware } from "@saga-bus/middleware-validation";
|
|
210
|
+
|
|
211
|
+
const middleware = createValidationMiddleware({
|
|
212
|
+
validators,
|
|
213
|
+
onInvalid: "dlq",
|
|
214
|
+
deadLetterHandler: async (envelope, errors) => {
|
|
215
|
+
await deadLetterQueue.send({
|
|
216
|
+
originalMessage: envelope,
|
|
217
|
+
validationErrors: errors,
|
|
218
|
+
timestamp: new Date(),
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
logger: console,
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Strict Mode
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// Reject any message without a registered validator
|
|
229
|
+
const middleware = createValidationMiddleware({
|
|
230
|
+
validators: {
|
|
231
|
+
OrderCreated: orderCreatedValidator,
|
|
232
|
+
OrderShipped: orderShippedValidator,
|
|
233
|
+
},
|
|
234
|
+
strictMode: true,
|
|
235
|
+
onInvalid: "throw",
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Excluding Message Types
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// Don't validate system messages
|
|
243
|
+
const middleware = createValidationMiddleware({
|
|
244
|
+
validators,
|
|
245
|
+
excludeTypes: ["Heartbeat", "HealthCheck", "SagaTimeoutExpired"],
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Logging Invalid Messages
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { logger } from "./logger";
|
|
253
|
+
|
|
254
|
+
const middleware = createValidationMiddleware({
|
|
255
|
+
validators,
|
|
256
|
+
onInvalid: "log",
|
|
257
|
+
logger: {
|
|
258
|
+
warn: (msg, meta) => logger.warn(msg, meta),
|
|
259
|
+
error: (msg, meta) => logger.error(msg, meta),
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Combining Multiple Validation Strategies
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { combineValidators, createZodValidator, createFunctionValidator } from "@saga-bus/middleware-validation";
|
|
268
|
+
import { z } from "zod";
|
|
269
|
+
|
|
270
|
+
// Schema validation + business rule validation
|
|
271
|
+
const orderValidator = combineValidators([
|
|
272
|
+
// Schema validation
|
|
273
|
+
createZodValidator(z.object({
|
|
274
|
+
type: z.literal("OrderCreated"),
|
|
275
|
+
orderId: z.string(),
|
|
276
|
+
customerId: z.string(),
|
|
277
|
+
items: z.array(z.object({
|
|
278
|
+
sku: z.string(),
|
|
279
|
+
quantity: z.number().positive(),
|
|
280
|
+
})),
|
|
281
|
+
total: z.number(),
|
|
282
|
+
})),
|
|
283
|
+
|
|
284
|
+
// Business rules
|
|
285
|
+
createFunctionValidator(async (msg) => {
|
|
286
|
+
const errors: string[] = [];
|
|
287
|
+
|
|
288
|
+
// Check customer exists
|
|
289
|
+
const customer = await customerService.get(msg.customerId);
|
|
290
|
+
if (!customer) {
|
|
291
|
+
errors.push(`Customer ${msg.customerId} not found`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Verify total matches items
|
|
295
|
+
const calculatedTotal = msg.items.reduce(
|
|
296
|
+
(sum, item) => sum + item.quantity * (item.price || 0),
|
|
297
|
+
0
|
|
298
|
+
);
|
|
299
|
+
if (Math.abs(calculatedTotal - msg.total) > 0.01) {
|
|
300
|
+
errors.push("Total doesn't match item prices");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return errors.length === 0 ? true : errors;
|
|
304
|
+
}),
|
|
305
|
+
]);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Custom Validator Implementation
|
|
309
|
+
|
|
310
|
+
Create custom validators by implementing the `MessageValidator` interface:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import type { MessageValidator, ValidationResult } from "@saga-bus/middleware-validation";
|
|
314
|
+
|
|
315
|
+
const customValidator: MessageValidator = (message) => {
|
|
316
|
+
const errors = [];
|
|
317
|
+
|
|
318
|
+
if (!message.orderId) {
|
|
319
|
+
errors.push({ path: "orderId", message: "Required" });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (message.items?.length === 0) {
|
|
323
|
+
errors.push({ path: "items", message: "Must have at least one item" });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return errors.length === 0
|
|
327
|
+
? { valid: true }
|
|
328
|
+
: { valid: false, errors };
|
|
329
|
+
};
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## ValidationResult Interface
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
interface ValidationResult {
|
|
336
|
+
valid: boolean;
|
|
337
|
+
errors?: Array<{
|
|
338
|
+
path: string; // e.g., "items[0].quantity"
|
|
339
|
+
message: string; // e.g., "Must be positive"
|
|
340
|
+
value?: unknown; // The invalid value
|
|
341
|
+
}>;
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Best Practices
|
|
346
|
+
|
|
347
|
+
1. **Use Zod for complex schemas** - It provides excellent type inference and error messages
|
|
348
|
+
2. **Use function validators for async/business logic** - External service calls, database lookups
|
|
349
|
+
3. **Consider strict mode in production** - Catch unvalidated message types early
|
|
350
|
+
4. **Use DLQ for debugging** - Store invalid messages for analysis
|
|
351
|
+
5. **Exclude system messages** - Don't validate internal messages like timeouts
|
|
352
|
+
|
|
353
|
+
## License
|
|
354
|
+
|
|
355
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
MessageValidationError: () => MessageValidationError,
|
|
24
|
+
combineValidators: () => combineValidators,
|
|
25
|
+
createFunctionValidator: () => createFunctionValidator,
|
|
26
|
+
createValidationMiddleware: () => createValidationMiddleware,
|
|
27
|
+
createZodValidator: () => createZodValidator,
|
|
28
|
+
createZodValidators: () => createZodValidators
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/types.ts
|
|
33
|
+
var MessageValidationError = class extends Error {
|
|
34
|
+
messageId;
|
|
35
|
+
messageType;
|
|
36
|
+
validationErrors;
|
|
37
|
+
constructor(messageId, messageType, errors) {
|
|
38
|
+
const errorSummary = errors.map((e) => `${e.path}: ${e.message}`).join(", ");
|
|
39
|
+
super(`Validation failed for message ${messageId}: ${errorSummary}`);
|
|
40
|
+
this.name = "MessageValidationError";
|
|
41
|
+
this.messageId = messageId;
|
|
42
|
+
this.messageType = messageType;
|
|
43
|
+
this.validationErrors = errors;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/ValidationMiddleware.ts
|
|
48
|
+
function createValidationMiddleware(options) {
|
|
49
|
+
const {
|
|
50
|
+
validators,
|
|
51
|
+
onInvalid = "throw",
|
|
52
|
+
deadLetterHandler,
|
|
53
|
+
logger,
|
|
54
|
+
strictMode = false,
|
|
55
|
+
excludeTypes = []
|
|
56
|
+
} = options;
|
|
57
|
+
if (onInvalid === "dlq" && !deadLetterHandler) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
"deadLetterHandler is required when onInvalid is set to 'dlq'"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const excludeSet = new Set(excludeTypes);
|
|
63
|
+
return async (ctx, next) => {
|
|
64
|
+
const { envelope } = ctx;
|
|
65
|
+
const messageType = envelope.type;
|
|
66
|
+
if (excludeSet.has(messageType)) {
|
|
67
|
+
await next();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const validator = validators[messageType];
|
|
71
|
+
if (!validator) {
|
|
72
|
+
if (strictMode) {
|
|
73
|
+
const errors = [
|
|
74
|
+
{
|
|
75
|
+
path: "type",
|
|
76
|
+
message: `No validator registered for message type: ${messageType}`
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
await handleInvalidMessage(
|
|
80
|
+
ctx,
|
|
81
|
+
errors,
|
|
82
|
+
onInvalid,
|
|
83
|
+
deadLetterHandler,
|
|
84
|
+
logger
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await next();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const result = await validator(envelope.payload);
|
|
92
|
+
if (result.valid) {
|
|
93
|
+
await next();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await handleInvalidMessage(
|
|
97
|
+
ctx,
|
|
98
|
+
result.errors || [],
|
|
99
|
+
onInvalid,
|
|
100
|
+
deadLetterHandler,
|
|
101
|
+
logger
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function handleInvalidMessage(ctx, errors, onInvalid, deadLetterHandler, logger) {
|
|
106
|
+
const { envelope } = ctx;
|
|
107
|
+
switch (onInvalid) {
|
|
108
|
+
case "throw":
|
|
109
|
+
throw new MessageValidationError(envelope.id, envelope.type, errors);
|
|
110
|
+
case "dlq":
|
|
111
|
+
logger?.error("Invalid message, sending to DLQ", {
|
|
112
|
+
messageId: envelope.id,
|
|
113
|
+
messageType: envelope.type,
|
|
114
|
+
correlationId: ctx.correlationId,
|
|
115
|
+
sagaName: ctx.sagaName,
|
|
116
|
+
errors
|
|
117
|
+
});
|
|
118
|
+
await deadLetterHandler(envelope, errors);
|
|
119
|
+
break;
|
|
120
|
+
case "log":
|
|
121
|
+
logger?.warn("Invalid message detected, skipping", {
|
|
122
|
+
messageId: envelope.id,
|
|
123
|
+
messageType: envelope.type,
|
|
124
|
+
correlationId: ctx.correlationId,
|
|
125
|
+
sagaName: ctx.sagaName,
|
|
126
|
+
errors
|
|
127
|
+
});
|
|
128
|
+
// Fall through to skip
|
|
129
|
+
case "skip":
|
|
130
|
+
default:
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/validators/ZodValidator.ts
|
|
136
|
+
function createZodValidator(schema) {
|
|
137
|
+
return (message) => {
|
|
138
|
+
const result = schema.safeParse(message);
|
|
139
|
+
if (result.success) {
|
|
140
|
+
return { valid: true };
|
|
141
|
+
}
|
|
142
|
+
const errors = result.error.issues.map((issue) => ({
|
|
143
|
+
path: issue.path.join("."),
|
|
144
|
+
message: issue.message
|
|
145
|
+
}));
|
|
146
|
+
return { valid: false, errors };
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function createZodValidators(schemas) {
|
|
150
|
+
const validators = {};
|
|
151
|
+
for (const [type, schema] of Object.entries(schemas)) {
|
|
152
|
+
validators[type] = createZodValidator(schema);
|
|
153
|
+
}
|
|
154
|
+
return validators;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/validators/FunctionValidator.ts
|
|
158
|
+
function createFunctionValidator(fn) {
|
|
159
|
+
return async (message) => {
|
|
160
|
+
const result = await fn(message);
|
|
161
|
+
if (result === true) {
|
|
162
|
+
return { valid: true };
|
|
163
|
+
}
|
|
164
|
+
const errors = [];
|
|
165
|
+
if (result === false) {
|
|
166
|
+
errors.push({ path: "", message: "Validation failed" });
|
|
167
|
+
} else if (typeof result === "string") {
|
|
168
|
+
errors.push({ path: "", message: result });
|
|
169
|
+
} else if (Array.isArray(result)) {
|
|
170
|
+
for (const msg of result) {
|
|
171
|
+
errors.push({ path: "", message: msg });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { valid: false, errors };
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function combineValidators(validators) {
|
|
178
|
+
return async (message) => {
|
|
179
|
+
const allErrors = [];
|
|
180
|
+
for (const validator of validators) {
|
|
181
|
+
const result = await validator(message);
|
|
182
|
+
if (!result.valid && result.errors) {
|
|
183
|
+
allErrors.push(...result.errors);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (allErrors.length === 0) {
|
|
187
|
+
return { valid: true };
|
|
188
|
+
}
|
|
189
|
+
return { valid: false, errors: allErrors };
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
193
|
+
0 && (module.exports = {
|
|
194
|
+
MessageValidationError,
|
|
195
|
+
combineValidators,
|
|
196
|
+
createFunctionValidator,
|
|
197
|
+
createValidationMiddleware,
|
|
198
|
+
createZodValidator,
|
|
199
|
+
createZodValidators
|
|
200
|
+
});
|
|
201
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/ValidationMiddleware.ts","../src/validators/ZodValidator.ts","../src/validators/FunctionValidator.ts"],"sourcesContent":["export { createValidationMiddleware } from \"./ValidationMiddleware.js\";\nexport { createZodValidator, createZodValidators } from \"./validators/ZodValidator.js\";\nexport {\n createFunctionValidator,\n combineValidators,\n} from \"./validators/FunctionValidator.js\";\nexport {\n MessageValidationError,\n type ValidationMiddlewareOptions,\n type ValidationResult,\n type ValidationError,\n type MessageValidator,\n type SyncMessageValidator,\n type AsyncMessageValidator,\n type AnyMessageValidator,\n type InvalidMessageAction,\n type DeadLetterHandler,\n} from \"./types.js\";\n","import type { MessageEnvelope, BaseMessage } from \"@saga-bus/core\";\n\n/**\n * Result of a validation.\n */\nexport interface ValidationResult {\n /** Whether the validation passed */\n valid: boolean;\n /** Validation errors if invalid */\n errors?: ValidationError[];\n}\n\n/**\n * A single validation error.\n */\nexport interface ValidationError {\n /** Path to the invalid field (e.g., \"payload.items[0].quantity\") */\n path: string;\n /** Error message */\n message: string;\n /** The invalid value */\n value?: unknown;\n}\n\n/**\n * A synchronous validator function that validates a message.\n */\nexport type SyncMessageValidator<T extends BaseMessage = BaseMessage> = (\n message: T\n) => ValidationResult;\n\n/**\n * An asynchronous validator function that validates a message.\n */\nexport type AsyncMessageValidator<T extends BaseMessage = BaseMessage> = (\n message: T\n) => Promise<ValidationResult>;\n\n/**\n * A validator function that validates a message (sync or async).\n */\nexport type MessageValidator<T extends BaseMessage = BaseMessage> = (\n message: T\n) => ValidationResult | Promise<ValidationResult>;\n\n/**\n * Action to take when validation fails.\n */\nexport type InvalidMessageAction = \"skip\" | \"log\" | \"throw\" | \"dlq\";\n\n/**\n * Dead-letter queue handler for invalid messages.\n */\nexport type DeadLetterHandler = (\n envelope: MessageEnvelope,\n errors: ValidationError[]\n) => Promise<void>;\n\n/**\n * A validator that can be used in the middleware (accepts any message type).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyMessageValidator = (message: any) => ValidationResult | Promise<ValidationResult>;\n\n/**\n * Options for the validation middleware.\n */\nexport interface ValidationMiddlewareOptions {\n /**\n * Map of message type to validator.\n * Messages without a registered validator will pass through.\n */\n validators: Record<string, AnyMessageValidator>;\n\n /**\n * Action to take when validation fails.\n * - \"skip\": Silently skip processing\n * - \"log\": Log warning and skip\n * - \"throw\": Throw a ValidationError\n * - \"dlq\": Send to dead-letter queue handler\n * @default \"throw\"\n */\n onInvalid?: InvalidMessageAction;\n\n /**\n * Handler for dead-letter queue (required if onInvalid is \"dlq\").\n */\n deadLetterHandler?: DeadLetterHandler;\n\n /**\n * Custom logger for validation errors.\n */\n logger?: {\n warn(message: string, meta?: Record<string, unknown>): void;\n error(message: string, meta?: Record<string, unknown>): void;\n };\n\n /**\n * Whether to validate messages that don't have a registered validator.\n * If true, messages without validators will be rejected.\n * @default false\n */\n strictMode?: boolean;\n\n /**\n * Message types that should not be validated.\n */\n excludeTypes?: string[];\n}\n\n/**\n * Error thrown when validation fails and onInvalid is \"throw\".\n */\nexport class MessageValidationError extends Error {\n public readonly messageId: string;\n public readonly messageType: string;\n public readonly validationErrors: ValidationError[];\n\n constructor(\n messageId: string,\n messageType: string,\n errors: ValidationError[]\n ) {\n const errorSummary = errors\n .map((e) => `${e.path}: ${e.message}`)\n .join(\", \");\n super(`Validation failed for message ${messageId}: ${errorSummary}`);\n this.name = \"MessageValidationError\";\n this.messageId = messageId;\n this.messageType = messageType;\n this.validationErrors = errors;\n }\n}\n","import type { SagaMiddleware, SagaPipelineContext } from \"@saga-bus/core\";\nimport type { ValidationMiddlewareOptions, ValidationError } from \"./types.js\";\nimport { MessageValidationError } from \"./types.js\";\n\n/**\n * Creates validation middleware that validates message payloads before processing.\n *\n * @example\n * ```typescript\n * import { createValidationMiddleware, createZodValidator } from \"@saga-bus/middleware-validation\";\n * import { z } from \"zod\";\n *\n * const validationMiddleware = createValidationMiddleware({\n * validators: {\n * OrderCreated: createZodValidator(z.object({\n * type: z.literal(\"OrderCreated\"),\n * orderId: z.string(),\n * customerId: z.string(),\n * })),\n * },\n * onInvalid: \"throw\",\n * });\n *\n * const bus = createBus({\n * transport,\n * store,\n * sagas: [MySaga],\n * middleware: [validationMiddleware],\n * });\n * ```\n */\nexport function createValidationMiddleware(\n options: ValidationMiddlewareOptions\n): SagaMiddleware {\n const {\n validators,\n onInvalid = \"throw\",\n deadLetterHandler,\n logger,\n strictMode = false,\n excludeTypes = [],\n } = options;\n\n // Validate options\n if (onInvalid === \"dlq\" && !deadLetterHandler) {\n throw new Error(\n \"deadLetterHandler is required when onInvalid is set to 'dlq'\"\n );\n }\n\n const excludeSet = new Set(excludeTypes);\n\n return async (ctx: SagaPipelineContext, next: () => Promise<void>) => {\n const { envelope } = ctx;\n const messageType = envelope.type;\n\n // Skip excluded types\n if (excludeSet.has(messageType)) {\n await next();\n return;\n }\n\n // Get validator for this message type\n const validator = validators[messageType];\n\n // Handle missing validator\n if (!validator) {\n if (strictMode) {\n const errors: ValidationError[] = [\n {\n path: \"type\",\n message: `No validator registered for message type: ${messageType}`,\n },\n ];\n await handleInvalidMessage(\n ctx,\n errors,\n onInvalid,\n deadLetterHandler,\n logger\n );\n return;\n }\n // Not strict mode - pass through\n await next();\n return;\n }\n\n // Validate the message payload\n const result = await validator(envelope.payload);\n\n if (result.valid) {\n await next();\n return;\n }\n\n // Handle invalid message\n await handleInvalidMessage(\n ctx,\n result.errors || [],\n onInvalid,\n deadLetterHandler,\n logger\n );\n };\n}\n\nasync function handleInvalidMessage(\n ctx: SagaPipelineContext,\n errors: ValidationError[],\n onInvalid: string,\n deadLetterHandler: ((envelope: typeof ctx.envelope, errors: ValidationError[]) => Promise<void>) | undefined,\n logger: { warn(message: string, meta?: Record<string, unknown>): void; error(message: string, meta?: Record<string, unknown>): void } | undefined\n): Promise<void> {\n const { envelope } = ctx;\n\n switch (onInvalid) {\n case \"throw\":\n throw new MessageValidationError(envelope.id, envelope.type, errors);\n\n case \"dlq\":\n logger?.error(\"Invalid message, sending to DLQ\", {\n messageId: envelope.id,\n messageType: envelope.type,\n correlationId: ctx.correlationId,\n sagaName: ctx.sagaName,\n errors,\n });\n await deadLetterHandler!(envelope, errors);\n break;\n\n case \"log\":\n logger?.warn(\"Invalid message detected, skipping\", {\n messageId: envelope.id,\n messageType: envelope.type,\n correlationId: ctx.correlationId,\n sagaName: ctx.sagaName,\n errors,\n });\n // Fall through to skip\n\n case \"skip\":\n default:\n // Don't call next() - silently skip processing\n break;\n }\n}\n","import type { BaseMessage } from \"@saga-bus/core\";\nimport type { SyncMessageValidator, ValidationResult, ValidationError, AnyMessageValidator } from \"../types.js\";\n\n// Type for Zod schema - we don't import zod directly to keep it optional\ninterface ZodSchema {\n safeParse(data: unknown): {\n success: boolean;\n data?: unknown;\n error?: {\n issues: Array<{\n path: (string | number)[];\n message: string;\n }>;\n };\n };\n}\n\n/**\n * Creates a validator from a Zod schema.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\";\n * import { createZodValidator } from \"@saga-bus/middleware-validation\";\n *\n * const OrderCreatedSchema = z.object({\n * type: z.literal(\"OrderCreated\"),\n * orderId: z.string().uuid(),\n * customerId: z.string(),\n * items: z.array(z.object({\n * sku: z.string(),\n * quantity: z.number().positive(),\n * })),\n * total: z.number().nonnegative(),\n * });\n *\n * const validator = createZodValidator(OrderCreatedSchema);\n * ```\n */\nexport function createZodValidator<T extends BaseMessage = BaseMessage>(schema: ZodSchema): SyncMessageValidator<T> {\n return (message: T): ValidationResult => {\n const result = schema.safeParse(message);\n\n if (result.success) {\n return { valid: true };\n }\n\n const errors: ValidationError[] = result.error!.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n }));\n\n return { valid: false, errors };\n };\n}\n\n/**\n * Creates multiple validators from a map of Zod schemas.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\";\n * import { createZodValidators } from \"@saga-bus/middleware-validation\";\n *\n * const validators = createZodValidators({\n * OrderCreated: z.object({\n * type: z.literal(\"OrderCreated\"),\n * orderId: z.string(),\n * }),\n * OrderShipped: z.object({\n * type: z.literal(\"OrderShipped\"),\n * orderId: z.string(),\n * trackingNumber: z.string(),\n * }),\n * });\n * ```\n */\nexport function createZodValidators(\n schemas: Record<string, ZodSchema>\n): Record<string, AnyMessageValidator> {\n const validators: Record<string, AnyMessageValidator> = {};\n\n for (const [type, schema] of Object.entries(schemas)) {\n validators[type] = createZodValidator(schema);\n }\n\n return validators;\n}\n","import type { BaseMessage } from \"@saga-bus/core\";\nimport type {\n MessageValidator,\n ValidationResult,\n ValidationError,\n} from \"../types.js\";\n\n/**\n * A simple validation function that returns true/false or an error message.\n */\nexport type SimpleValidationFn<T> = (\n message: T\n) => boolean | string | string[] | Promise<boolean | string | string[]>;\n\n/**\n * Creates a validator from a simple validation function.\n *\n * @example\n * ```typescript\n * import { createFunctionValidator } from \"@saga-bus/middleware-validation\";\n *\n * const validator = createFunctionValidator((message: OrderCreated) => {\n * if (!message.orderId) {\n * return \"orderId is required\";\n * }\n * if (message.items.length === 0) {\n * return \"Order must have at least one item\";\n * }\n * return true;\n * });\n * ```\n */\nexport function createFunctionValidator<T extends BaseMessage = BaseMessage>(\n fn: SimpleValidationFn<T>\n): MessageValidator<T> {\n return async (message: T): Promise<ValidationResult> => {\n const result = await fn(message);\n\n if (result === true) {\n return { valid: true };\n }\n\n const errors: ValidationError[] = [];\n\n if (result === false) {\n errors.push({ path: \"\", message: \"Validation failed\" });\n } else if (typeof result === \"string\") {\n errors.push({ path: \"\", message: result });\n } else if (Array.isArray(result)) {\n for (const msg of result) {\n errors.push({ path: \"\", message: msg });\n }\n }\n\n return { valid: false, errors };\n };\n}\n\n/**\n * Combines multiple validators into one.\n * All validators must pass for the message to be valid.\n *\n * @example\n * ```typescript\n * import { combineValidators, createFunctionValidator } from \"@saga-bus/middleware-validation\";\n *\n * const validator = combineValidators([\n * createFunctionValidator((msg) => !!msg.orderId || \"orderId required\"),\n * createFunctionValidator((msg) => msg.items.length > 0 || \"items required\"),\n * ]);\n * ```\n */\nexport function combineValidators<T extends BaseMessage = BaseMessage>(\n validators: MessageValidator<T>[]\n): MessageValidator<T> {\n return async (message: T): Promise<ValidationResult> => {\n const allErrors: ValidationError[] = [];\n\n for (const validator of validators) {\n const result = await validator(message);\n if (!result.valid && result.errors) {\n allErrors.push(...result.errors);\n }\n }\n\n if (allErrors.length === 0) {\n return { valid: true };\n }\n\n return { valid: false, errors: allErrors };\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { MessageEnvelope, BaseMessage, SagaMiddleware } from '@saga-bus/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Result of a validation.
|
|
5
|
+
*/
|
|
6
|
+
interface ValidationResult {
|
|
7
|
+
/** Whether the validation passed */
|
|
8
|
+
valid: boolean;
|
|
9
|
+
/** Validation errors if invalid */
|
|
10
|
+
errors?: ValidationError[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A single validation error.
|
|
14
|
+
*/
|
|
15
|
+
interface ValidationError {
|
|
16
|
+
/** Path to the invalid field (e.g., "payload.items[0].quantity") */
|
|
17
|
+
path: string;
|
|
18
|
+
/** Error message */
|
|
19
|
+
message: string;
|
|
20
|
+
/** The invalid value */
|
|
21
|
+
value?: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A synchronous validator function that validates a message.
|
|
25
|
+
*/
|
|
26
|
+
type SyncMessageValidator<T extends BaseMessage = BaseMessage> = (message: T) => ValidationResult;
|
|
27
|
+
/**
|
|
28
|
+
* An asynchronous validator function that validates a message.
|
|
29
|
+
*/
|
|
30
|
+
type AsyncMessageValidator<T extends BaseMessage = BaseMessage> = (message: T) => Promise<ValidationResult>;
|
|
31
|
+
/**
|
|
32
|
+
* A validator function that validates a message (sync or async).
|
|
33
|
+
*/
|
|
34
|
+
type MessageValidator<T extends BaseMessage = BaseMessage> = (message: T) => ValidationResult | Promise<ValidationResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Action to take when validation fails.
|
|
37
|
+
*/
|
|
38
|
+
type InvalidMessageAction = "skip" | "log" | "throw" | "dlq";
|
|
39
|
+
/**
|
|
40
|
+
* Dead-letter queue handler for invalid messages.
|
|
41
|
+
*/
|
|
42
|
+
type DeadLetterHandler = (envelope: MessageEnvelope, errors: ValidationError[]) => Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* A validator that can be used in the middleware (accepts any message type).
|
|
45
|
+
*/
|
|
46
|
+
type AnyMessageValidator = (message: any) => ValidationResult | Promise<ValidationResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Options for the validation middleware.
|
|
49
|
+
*/
|
|
50
|
+
interface ValidationMiddlewareOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Map of message type to validator.
|
|
53
|
+
* Messages without a registered validator will pass through.
|
|
54
|
+
*/
|
|
55
|
+
validators: Record<string, AnyMessageValidator>;
|
|
56
|
+
/**
|
|
57
|
+
* Action to take when validation fails.
|
|
58
|
+
* - "skip": Silently skip processing
|
|
59
|
+
* - "log": Log warning and skip
|
|
60
|
+
* - "throw": Throw a ValidationError
|
|
61
|
+
* - "dlq": Send to dead-letter queue handler
|
|
62
|
+
* @default "throw"
|
|
63
|
+
*/
|
|
64
|
+
onInvalid?: InvalidMessageAction;
|
|
65
|
+
/**
|
|
66
|
+
* Handler for dead-letter queue (required if onInvalid is "dlq").
|
|
67
|
+
*/
|
|
68
|
+
deadLetterHandler?: DeadLetterHandler;
|
|
69
|
+
/**
|
|
70
|
+
* Custom logger for validation errors.
|
|
71
|
+
*/
|
|
72
|
+
logger?: {
|
|
73
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
74
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Whether to validate messages that don't have a registered validator.
|
|
78
|
+
* If true, messages without validators will be rejected.
|
|
79
|
+
* @default false
|
|
80
|
+
*/
|
|
81
|
+
strictMode?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Message types that should not be validated.
|
|
84
|
+
*/
|
|
85
|
+
excludeTypes?: string[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Error thrown when validation fails and onInvalid is "throw".
|
|
89
|
+
*/
|
|
90
|
+
declare class MessageValidationError extends Error {
|
|
91
|
+
readonly messageId: string;
|
|
92
|
+
readonly messageType: string;
|
|
93
|
+
readonly validationErrors: ValidationError[];
|
|
94
|
+
constructor(messageId: string, messageType: string, errors: ValidationError[]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Creates validation middleware that validates message payloads before processing.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* import { createValidationMiddleware, createZodValidator } from "@saga-bus/middleware-validation";
|
|
103
|
+
* import { z } from "zod";
|
|
104
|
+
*
|
|
105
|
+
* const validationMiddleware = createValidationMiddleware({
|
|
106
|
+
* validators: {
|
|
107
|
+
* OrderCreated: createZodValidator(z.object({
|
|
108
|
+
* type: z.literal("OrderCreated"),
|
|
109
|
+
* orderId: z.string(),
|
|
110
|
+
* customerId: z.string(),
|
|
111
|
+
* })),
|
|
112
|
+
* },
|
|
113
|
+
* onInvalid: "throw",
|
|
114
|
+
* });
|
|
115
|
+
*
|
|
116
|
+
* const bus = createBus({
|
|
117
|
+
* transport,
|
|
118
|
+
* store,
|
|
119
|
+
* sagas: [MySaga],
|
|
120
|
+
* middleware: [validationMiddleware],
|
|
121
|
+
* });
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
declare function createValidationMiddleware(options: ValidationMiddlewareOptions): SagaMiddleware;
|
|
125
|
+
|
|
126
|
+
interface ZodSchema {
|
|
127
|
+
safeParse(data: unknown): {
|
|
128
|
+
success: boolean;
|
|
129
|
+
data?: unknown;
|
|
130
|
+
error?: {
|
|
131
|
+
issues: Array<{
|
|
132
|
+
path: (string | number)[];
|
|
133
|
+
message: string;
|
|
134
|
+
}>;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Creates a validator from a Zod schema.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* import { z } from "zod";
|
|
144
|
+
* import { createZodValidator } from "@saga-bus/middleware-validation";
|
|
145
|
+
*
|
|
146
|
+
* const OrderCreatedSchema = z.object({
|
|
147
|
+
* type: z.literal("OrderCreated"),
|
|
148
|
+
* orderId: z.string().uuid(),
|
|
149
|
+
* customerId: z.string(),
|
|
150
|
+
* items: z.array(z.object({
|
|
151
|
+
* sku: z.string(),
|
|
152
|
+
* quantity: z.number().positive(),
|
|
153
|
+
* })),
|
|
154
|
+
* total: z.number().nonnegative(),
|
|
155
|
+
* });
|
|
156
|
+
*
|
|
157
|
+
* const validator = createZodValidator(OrderCreatedSchema);
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
declare function createZodValidator<T extends BaseMessage = BaseMessage>(schema: ZodSchema): SyncMessageValidator<T>;
|
|
161
|
+
/**
|
|
162
|
+
* Creates multiple validators from a map of Zod schemas.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* import { z } from "zod";
|
|
167
|
+
* import { createZodValidators } from "@saga-bus/middleware-validation";
|
|
168
|
+
*
|
|
169
|
+
* const validators = createZodValidators({
|
|
170
|
+
* OrderCreated: z.object({
|
|
171
|
+
* type: z.literal("OrderCreated"),
|
|
172
|
+
* orderId: z.string(),
|
|
173
|
+
* }),
|
|
174
|
+
* OrderShipped: z.object({
|
|
175
|
+
* type: z.literal("OrderShipped"),
|
|
176
|
+
* orderId: z.string(),
|
|
177
|
+
* trackingNumber: z.string(),
|
|
178
|
+
* }),
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
declare function createZodValidators(schemas: Record<string, ZodSchema>): Record<string, AnyMessageValidator>;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* A simple validation function that returns true/false or an error message.
|
|
186
|
+
*/
|
|
187
|
+
type SimpleValidationFn<T> = (message: T) => boolean | string | string[] | Promise<boolean | string | string[]>;
|
|
188
|
+
/**
|
|
189
|
+
* Creates a validator from a simple validation function.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* import { createFunctionValidator } from "@saga-bus/middleware-validation";
|
|
194
|
+
*
|
|
195
|
+
* const validator = createFunctionValidator((message: OrderCreated) => {
|
|
196
|
+
* if (!message.orderId) {
|
|
197
|
+
* return "orderId is required";
|
|
198
|
+
* }
|
|
199
|
+
* if (message.items.length === 0) {
|
|
200
|
+
* return "Order must have at least one item";
|
|
201
|
+
* }
|
|
202
|
+
* return true;
|
|
203
|
+
* });
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
declare function createFunctionValidator<T extends BaseMessage = BaseMessage>(fn: SimpleValidationFn<T>): MessageValidator<T>;
|
|
207
|
+
/**
|
|
208
|
+
* Combines multiple validators into one.
|
|
209
|
+
* All validators must pass for the message to be valid.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* import { combineValidators, createFunctionValidator } from "@saga-bus/middleware-validation";
|
|
214
|
+
*
|
|
215
|
+
* const validator = combineValidators([
|
|
216
|
+
* createFunctionValidator((msg) => !!msg.orderId || "orderId required"),
|
|
217
|
+
* createFunctionValidator((msg) => msg.items.length > 0 || "items required"),
|
|
218
|
+
* ]);
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
declare function combineValidators<T extends BaseMessage = BaseMessage>(validators: MessageValidator<T>[]): MessageValidator<T>;
|
|
222
|
+
|
|
223
|
+
export { type AnyMessageValidator, type AsyncMessageValidator, type DeadLetterHandler, type InvalidMessageAction, MessageValidationError, type MessageValidator, type SyncMessageValidator, type ValidationError, type ValidationMiddlewareOptions, type ValidationResult, combineValidators, createFunctionValidator, createValidationMiddleware, createZodValidator, createZodValidators };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { MessageEnvelope, BaseMessage, SagaMiddleware } from '@saga-bus/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Result of a validation.
|
|
5
|
+
*/
|
|
6
|
+
interface ValidationResult {
|
|
7
|
+
/** Whether the validation passed */
|
|
8
|
+
valid: boolean;
|
|
9
|
+
/** Validation errors if invalid */
|
|
10
|
+
errors?: ValidationError[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A single validation error.
|
|
14
|
+
*/
|
|
15
|
+
interface ValidationError {
|
|
16
|
+
/** Path to the invalid field (e.g., "payload.items[0].quantity") */
|
|
17
|
+
path: string;
|
|
18
|
+
/** Error message */
|
|
19
|
+
message: string;
|
|
20
|
+
/** The invalid value */
|
|
21
|
+
value?: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A synchronous validator function that validates a message.
|
|
25
|
+
*/
|
|
26
|
+
type SyncMessageValidator<T extends BaseMessage = BaseMessage> = (message: T) => ValidationResult;
|
|
27
|
+
/**
|
|
28
|
+
* An asynchronous validator function that validates a message.
|
|
29
|
+
*/
|
|
30
|
+
type AsyncMessageValidator<T extends BaseMessage = BaseMessage> = (message: T) => Promise<ValidationResult>;
|
|
31
|
+
/**
|
|
32
|
+
* A validator function that validates a message (sync or async).
|
|
33
|
+
*/
|
|
34
|
+
type MessageValidator<T extends BaseMessage = BaseMessage> = (message: T) => ValidationResult | Promise<ValidationResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Action to take when validation fails.
|
|
37
|
+
*/
|
|
38
|
+
type InvalidMessageAction = "skip" | "log" | "throw" | "dlq";
|
|
39
|
+
/**
|
|
40
|
+
* Dead-letter queue handler for invalid messages.
|
|
41
|
+
*/
|
|
42
|
+
type DeadLetterHandler = (envelope: MessageEnvelope, errors: ValidationError[]) => Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* A validator that can be used in the middleware (accepts any message type).
|
|
45
|
+
*/
|
|
46
|
+
type AnyMessageValidator = (message: any) => ValidationResult | Promise<ValidationResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Options for the validation middleware.
|
|
49
|
+
*/
|
|
50
|
+
interface ValidationMiddlewareOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Map of message type to validator.
|
|
53
|
+
* Messages without a registered validator will pass through.
|
|
54
|
+
*/
|
|
55
|
+
validators: Record<string, AnyMessageValidator>;
|
|
56
|
+
/**
|
|
57
|
+
* Action to take when validation fails.
|
|
58
|
+
* - "skip": Silently skip processing
|
|
59
|
+
* - "log": Log warning and skip
|
|
60
|
+
* - "throw": Throw a ValidationError
|
|
61
|
+
* - "dlq": Send to dead-letter queue handler
|
|
62
|
+
* @default "throw"
|
|
63
|
+
*/
|
|
64
|
+
onInvalid?: InvalidMessageAction;
|
|
65
|
+
/**
|
|
66
|
+
* Handler for dead-letter queue (required if onInvalid is "dlq").
|
|
67
|
+
*/
|
|
68
|
+
deadLetterHandler?: DeadLetterHandler;
|
|
69
|
+
/**
|
|
70
|
+
* Custom logger for validation errors.
|
|
71
|
+
*/
|
|
72
|
+
logger?: {
|
|
73
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
74
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Whether to validate messages that don't have a registered validator.
|
|
78
|
+
* If true, messages without validators will be rejected.
|
|
79
|
+
* @default false
|
|
80
|
+
*/
|
|
81
|
+
strictMode?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Message types that should not be validated.
|
|
84
|
+
*/
|
|
85
|
+
excludeTypes?: string[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Error thrown when validation fails and onInvalid is "throw".
|
|
89
|
+
*/
|
|
90
|
+
declare class MessageValidationError extends Error {
|
|
91
|
+
readonly messageId: string;
|
|
92
|
+
readonly messageType: string;
|
|
93
|
+
readonly validationErrors: ValidationError[];
|
|
94
|
+
constructor(messageId: string, messageType: string, errors: ValidationError[]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Creates validation middleware that validates message payloads before processing.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* import { createValidationMiddleware, createZodValidator } from "@saga-bus/middleware-validation";
|
|
103
|
+
* import { z } from "zod";
|
|
104
|
+
*
|
|
105
|
+
* const validationMiddleware = createValidationMiddleware({
|
|
106
|
+
* validators: {
|
|
107
|
+
* OrderCreated: createZodValidator(z.object({
|
|
108
|
+
* type: z.literal("OrderCreated"),
|
|
109
|
+
* orderId: z.string(),
|
|
110
|
+
* customerId: z.string(),
|
|
111
|
+
* })),
|
|
112
|
+
* },
|
|
113
|
+
* onInvalid: "throw",
|
|
114
|
+
* });
|
|
115
|
+
*
|
|
116
|
+
* const bus = createBus({
|
|
117
|
+
* transport,
|
|
118
|
+
* store,
|
|
119
|
+
* sagas: [MySaga],
|
|
120
|
+
* middleware: [validationMiddleware],
|
|
121
|
+
* });
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
declare function createValidationMiddleware(options: ValidationMiddlewareOptions): SagaMiddleware;
|
|
125
|
+
|
|
126
|
+
interface ZodSchema {
|
|
127
|
+
safeParse(data: unknown): {
|
|
128
|
+
success: boolean;
|
|
129
|
+
data?: unknown;
|
|
130
|
+
error?: {
|
|
131
|
+
issues: Array<{
|
|
132
|
+
path: (string | number)[];
|
|
133
|
+
message: string;
|
|
134
|
+
}>;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Creates a validator from a Zod schema.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* import { z } from "zod";
|
|
144
|
+
* import { createZodValidator } from "@saga-bus/middleware-validation";
|
|
145
|
+
*
|
|
146
|
+
* const OrderCreatedSchema = z.object({
|
|
147
|
+
* type: z.literal("OrderCreated"),
|
|
148
|
+
* orderId: z.string().uuid(),
|
|
149
|
+
* customerId: z.string(),
|
|
150
|
+
* items: z.array(z.object({
|
|
151
|
+
* sku: z.string(),
|
|
152
|
+
* quantity: z.number().positive(),
|
|
153
|
+
* })),
|
|
154
|
+
* total: z.number().nonnegative(),
|
|
155
|
+
* });
|
|
156
|
+
*
|
|
157
|
+
* const validator = createZodValidator(OrderCreatedSchema);
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
declare function createZodValidator<T extends BaseMessage = BaseMessage>(schema: ZodSchema): SyncMessageValidator<T>;
|
|
161
|
+
/**
|
|
162
|
+
* Creates multiple validators from a map of Zod schemas.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* import { z } from "zod";
|
|
167
|
+
* import { createZodValidators } from "@saga-bus/middleware-validation";
|
|
168
|
+
*
|
|
169
|
+
* const validators = createZodValidators({
|
|
170
|
+
* OrderCreated: z.object({
|
|
171
|
+
* type: z.literal("OrderCreated"),
|
|
172
|
+
* orderId: z.string(),
|
|
173
|
+
* }),
|
|
174
|
+
* OrderShipped: z.object({
|
|
175
|
+
* type: z.literal("OrderShipped"),
|
|
176
|
+
* orderId: z.string(),
|
|
177
|
+
* trackingNumber: z.string(),
|
|
178
|
+
* }),
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
declare function createZodValidators(schemas: Record<string, ZodSchema>): Record<string, AnyMessageValidator>;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* A simple validation function that returns true/false or an error message.
|
|
186
|
+
*/
|
|
187
|
+
type SimpleValidationFn<T> = (message: T) => boolean | string | string[] | Promise<boolean | string | string[]>;
|
|
188
|
+
/**
|
|
189
|
+
* Creates a validator from a simple validation function.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* import { createFunctionValidator } from "@saga-bus/middleware-validation";
|
|
194
|
+
*
|
|
195
|
+
* const validator = createFunctionValidator((message: OrderCreated) => {
|
|
196
|
+
* if (!message.orderId) {
|
|
197
|
+
* return "orderId is required";
|
|
198
|
+
* }
|
|
199
|
+
* if (message.items.length === 0) {
|
|
200
|
+
* return "Order must have at least one item";
|
|
201
|
+
* }
|
|
202
|
+
* return true;
|
|
203
|
+
* });
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
declare function createFunctionValidator<T extends BaseMessage = BaseMessage>(fn: SimpleValidationFn<T>): MessageValidator<T>;
|
|
207
|
+
/**
|
|
208
|
+
* Combines multiple validators into one.
|
|
209
|
+
* All validators must pass for the message to be valid.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* import { combineValidators, createFunctionValidator } from "@saga-bus/middleware-validation";
|
|
214
|
+
*
|
|
215
|
+
* const validator = combineValidators([
|
|
216
|
+
* createFunctionValidator((msg) => !!msg.orderId || "orderId required"),
|
|
217
|
+
* createFunctionValidator((msg) => msg.items.length > 0 || "items required"),
|
|
218
|
+
* ]);
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
declare function combineValidators<T extends BaseMessage = BaseMessage>(validators: MessageValidator<T>[]): MessageValidator<T>;
|
|
222
|
+
|
|
223
|
+
export { type AnyMessageValidator, type AsyncMessageValidator, type DeadLetterHandler, type InvalidMessageAction, MessageValidationError, type MessageValidator, type SyncMessageValidator, type ValidationError, type ValidationMiddlewareOptions, type ValidationResult, combineValidators, createFunctionValidator, createValidationMiddleware, createZodValidator, createZodValidators };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var MessageValidationError = class extends Error {
|
|
3
|
+
messageId;
|
|
4
|
+
messageType;
|
|
5
|
+
validationErrors;
|
|
6
|
+
constructor(messageId, messageType, errors) {
|
|
7
|
+
const errorSummary = errors.map((e) => `${e.path}: ${e.message}`).join(", ");
|
|
8
|
+
super(`Validation failed for message ${messageId}: ${errorSummary}`);
|
|
9
|
+
this.name = "MessageValidationError";
|
|
10
|
+
this.messageId = messageId;
|
|
11
|
+
this.messageType = messageType;
|
|
12
|
+
this.validationErrors = errors;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/ValidationMiddleware.ts
|
|
17
|
+
function createValidationMiddleware(options) {
|
|
18
|
+
const {
|
|
19
|
+
validators,
|
|
20
|
+
onInvalid = "throw",
|
|
21
|
+
deadLetterHandler,
|
|
22
|
+
logger,
|
|
23
|
+
strictMode = false,
|
|
24
|
+
excludeTypes = []
|
|
25
|
+
} = options;
|
|
26
|
+
if (onInvalid === "dlq" && !deadLetterHandler) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"deadLetterHandler is required when onInvalid is set to 'dlq'"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const excludeSet = new Set(excludeTypes);
|
|
32
|
+
return async (ctx, next) => {
|
|
33
|
+
const { envelope } = ctx;
|
|
34
|
+
const messageType = envelope.type;
|
|
35
|
+
if (excludeSet.has(messageType)) {
|
|
36
|
+
await next();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const validator = validators[messageType];
|
|
40
|
+
if (!validator) {
|
|
41
|
+
if (strictMode) {
|
|
42
|
+
const errors = [
|
|
43
|
+
{
|
|
44
|
+
path: "type",
|
|
45
|
+
message: `No validator registered for message type: ${messageType}`
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
await handleInvalidMessage(
|
|
49
|
+
ctx,
|
|
50
|
+
errors,
|
|
51
|
+
onInvalid,
|
|
52
|
+
deadLetterHandler,
|
|
53
|
+
logger
|
|
54
|
+
);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await next();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const result = await validator(envelope.payload);
|
|
61
|
+
if (result.valid) {
|
|
62
|
+
await next();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
await handleInvalidMessage(
|
|
66
|
+
ctx,
|
|
67
|
+
result.errors || [],
|
|
68
|
+
onInvalid,
|
|
69
|
+
deadLetterHandler,
|
|
70
|
+
logger
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function handleInvalidMessage(ctx, errors, onInvalid, deadLetterHandler, logger) {
|
|
75
|
+
const { envelope } = ctx;
|
|
76
|
+
switch (onInvalid) {
|
|
77
|
+
case "throw":
|
|
78
|
+
throw new MessageValidationError(envelope.id, envelope.type, errors);
|
|
79
|
+
case "dlq":
|
|
80
|
+
logger?.error("Invalid message, sending to DLQ", {
|
|
81
|
+
messageId: envelope.id,
|
|
82
|
+
messageType: envelope.type,
|
|
83
|
+
correlationId: ctx.correlationId,
|
|
84
|
+
sagaName: ctx.sagaName,
|
|
85
|
+
errors
|
|
86
|
+
});
|
|
87
|
+
await deadLetterHandler(envelope, errors);
|
|
88
|
+
break;
|
|
89
|
+
case "log":
|
|
90
|
+
logger?.warn("Invalid message detected, skipping", {
|
|
91
|
+
messageId: envelope.id,
|
|
92
|
+
messageType: envelope.type,
|
|
93
|
+
correlationId: ctx.correlationId,
|
|
94
|
+
sagaName: ctx.sagaName,
|
|
95
|
+
errors
|
|
96
|
+
});
|
|
97
|
+
// Fall through to skip
|
|
98
|
+
case "skip":
|
|
99
|
+
default:
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/validators/ZodValidator.ts
|
|
105
|
+
function createZodValidator(schema) {
|
|
106
|
+
return (message) => {
|
|
107
|
+
const result = schema.safeParse(message);
|
|
108
|
+
if (result.success) {
|
|
109
|
+
return { valid: true };
|
|
110
|
+
}
|
|
111
|
+
const errors = result.error.issues.map((issue) => ({
|
|
112
|
+
path: issue.path.join("."),
|
|
113
|
+
message: issue.message
|
|
114
|
+
}));
|
|
115
|
+
return { valid: false, errors };
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function createZodValidators(schemas) {
|
|
119
|
+
const validators = {};
|
|
120
|
+
for (const [type, schema] of Object.entries(schemas)) {
|
|
121
|
+
validators[type] = createZodValidator(schema);
|
|
122
|
+
}
|
|
123
|
+
return validators;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/validators/FunctionValidator.ts
|
|
127
|
+
function createFunctionValidator(fn) {
|
|
128
|
+
return async (message) => {
|
|
129
|
+
const result = await fn(message);
|
|
130
|
+
if (result === true) {
|
|
131
|
+
return { valid: true };
|
|
132
|
+
}
|
|
133
|
+
const errors = [];
|
|
134
|
+
if (result === false) {
|
|
135
|
+
errors.push({ path: "", message: "Validation failed" });
|
|
136
|
+
} else if (typeof result === "string") {
|
|
137
|
+
errors.push({ path: "", message: result });
|
|
138
|
+
} else if (Array.isArray(result)) {
|
|
139
|
+
for (const msg of result) {
|
|
140
|
+
errors.push({ path: "", message: msg });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { valid: false, errors };
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function combineValidators(validators) {
|
|
147
|
+
return async (message) => {
|
|
148
|
+
const allErrors = [];
|
|
149
|
+
for (const validator of validators) {
|
|
150
|
+
const result = await validator(message);
|
|
151
|
+
if (!result.valid && result.errors) {
|
|
152
|
+
allErrors.push(...result.errors);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (allErrors.length === 0) {
|
|
156
|
+
return { valid: true };
|
|
157
|
+
}
|
|
158
|
+
return { valid: false, errors: allErrors };
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export {
|
|
162
|
+
MessageValidationError,
|
|
163
|
+
combineValidators,
|
|
164
|
+
createFunctionValidator,
|
|
165
|
+
createValidationMiddleware,
|
|
166
|
+
createZodValidator,
|
|
167
|
+
createZodValidators
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/ValidationMiddleware.ts","../src/validators/ZodValidator.ts","../src/validators/FunctionValidator.ts"],"sourcesContent":["import type { MessageEnvelope, BaseMessage } from \"@saga-bus/core\";\n\n/**\n * Result of a validation.\n */\nexport interface ValidationResult {\n /** Whether the validation passed */\n valid: boolean;\n /** Validation errors if invalid */\n errors?: ValidationError[];\n}\n\n/**\n * A single validation error.\n */\nexport interface ValidationError {\n /** Path to the invalid field (e.g., \"payload.items[0].quantity\") */\n path: string;\n /** Error message */\n message: string;\n /** The invalid value */\n value?: unknown;\n}\n\n/**\n * A synchronous validator function that validates a message.\n */\nexport type SyncMessageValidator<T extends BaseMessage = BaseMessage> = (\n message: T\n) => ValidationResult;\n\n/**\n * An asynchronous validator function that validates a message.\n */\nexport type AsyncMessageValidator<T extends BaseMessage = BaseMessage> = (\n message: T\n) => Promise<ValidationResult>;\n\n/**\n * A validator function that validates a message (sync or async).\n */\nexport type MessageValidator<T extends BaseMessage = BaseMessage> = (\n message: T\n) => ValidationResult | Promise<ValidationResult>;\n\n/**\n * Action to take when validation fails.\n */\nexport type InvalidMessageAction = \"skip\" | \"log\" | \"throw\" | \"dlq\";\n\n/**\n * Dead-letter queue handler for invalid messages.\n */\nexport type DeadLetterHandler = (\n envelope: MessageEnvelope,\n errors: ValidationError[]\n) => Promise<void>;\n\n/**\n * A validator that can be used in the middleware (accepts any message type).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyMessageValidator = (message: any) => ValidationResult | Promise<ValidationResult>;\n\n/**\n * Options for the validation middleware.\n */\nexport interface ValidationMiddlewareOptions {\n /**\n * Map of message type to validator.\n * Messages without a registered validator will pass through.\n */\n validators: Record<string, AnyMessageValidator>;\n\n /**\n * Action to take when validation fails.\n * - \"skip\": Silently skip processing\n * - \"log\": Log warning and skip\n * - \"throw\": Throw a ValidationError\n * - \"dlq\": Send to dead-letter queue handler\n * @default \"throw\"\n */\n onInvalid?: InvalidMessageAction;\n\n /**\n * Handler for dead-letter queue (required if onInvalid is \"dlq\").\n */\n deadLetterHandler?: DeadLetterHandler;\n\n /**\n * Custom logger for validation errors.\n */\n logger?: {\n warn(message: string, meta?: Record<string, unknown>): void;\n error(message: string, meta?: Record<string, unknown>): void;\n };\n\n /**\n * Whether to validate messages that don't have a registered validator.\n * If true, messages without validators will be rejected.\n * @default false\n */\n strictMode?: boolean;\n\n /**\n * Message types that should not be validated.\n */\n excludeTypes?: string[];\n}\n\n/**\n * Error thrown when validation fails and onInvalid is \"throw\".\n */\nexport class MessageValidationError extends Error {\n public readonly messageId: string;\n public readonly messageType: string;\n public readonly validationErrors: ValidationError[];\n\n constructor(\n messageId: string,\n messageType: string,\n errors: ValidationError[]\n ) {\n const errorSummary = errors\n .map((e) => `${e.path}: ${e.message}`)\n .join(\", \");\n super(`Validation failed for message ${messageId}: ${errorSummary}`);\n this.name = \"MessageValidationError\";\n this.messageId = messageId;\n this.messageType = messageType;\n this.validationErrors = errors;\n }\n}\n","import type { SagaMiddleware, SagaPipelineContext } from \"@saga-bus/core\";\nimport type { ValidationMiddlewareOptions, ValidationError } from \"./types.js\";\nimport { MessageValidationError } from \"./types.js\";\n\n/**\n * Creates validation middleware that validates message payloads before processing.\n *\n * @example\n * ```typescript\n * import { createValidationMiddleware, createZodValidator } from \"@saga-bus/middleware-validation\";\n * import { z } from \"zod\";\n *\n * const validationMiddleware = createValidationMiddleware({\n * validators: {\n * OrderCreated: createZodValidator(z.object({\n * type: z.literal(\"OrderCreated\"),\n * orderId: z.string(),\n * customerId: z.string(),\n * })),\n * },\n * onInvalid: \"throw\",\n * });\n *\n * const bus = createBus({\n * transport,\n * store,\n * sagas: [MySaga],\n * middleware: [validationMiddleware],\n * });\n * ```\n */\nexport function createValidationMiddleware(\n options: ValidationMiddlewareOptions\n): SagaMiddleware {\n const {\n validators,\n onInvalid = \"throw\",\n deadLetterHandler,\n logger,\n strictMode = false,\n excludeTypes = [],\n } = options;\n\n // Validate options\n if (onInvalid === \"dlq\" && !deadLetterHandler) {\n throw new Error(\n \"deadLetterHandler is required when onInvalid is set to 'dlq'\"\n );\n }\n\n const excludeSet = new Set(excludeTypes);\n\n return async (ctx: SagaPipelineContext, next: () => Promise<void>) => {\n const { envelope } = ctx;\n const messageType = envelope.type;\n\n // Skip excluded types\n if (excludeSet.has(messageType)) {\n await next();\n return;\n }\n\n // Get validator for this message type\n const validator = validators[messageType];\n\n // Handle missing validator\n if (!validator) {\n if (strictMode) {\n const errors: ValidationError[] = [\n {\n path: \"type\",\n message: `No validator registered for message type: ${messageType}`,\n },\n ];\n await handleInvalidMessage(\n ctx,\n errors,\n onInvalid,\n deadLetterHandler,\n logger\n );\n return;\n }\n // Not strict mode - pass through\n await next();\n return;\n }\n\n // Validate the message payload\n const result = await validator(envelope.payload);\n\n if (result.valid) {\n await next();\n return;\n }\n\n // Handle invalid message\n await handleInvalidMessage(\n ctx,\n result.errors || [],\n onInvalid,\n deadLetterHandler,\n logger\n );\n };\n}\n\nasync function handleInvalidMessage(\n ctx: SagaPipelineContext,\n errors: ValidationError[],\n onInvalid: string,\n deadLetterHandler: ((envelope: typeof ctx.envelope, errors: ValidationError[]) => Promise<void>) | undefined,\n logger: { warn(message: string, meta?: Record<string, unknown>): void; error(message: string, meta?: Record<string, unknown>): void } | undefined\n): Promise<void> {\n const { envelope } = ctx;\n\n switch (onInvalid) {\n case \"throw\":\n throw new MessageValidationError(envelope.id, envelope.type, errors);\n\n case \"dlq\":\n logger?.error(\"Invalid message, sending to DLQ\", {\n messageId: envelope.id,\n messageType: envelope.type,\n correlationId: ctx.correlationId,\n sagaName: ctx.sagaName,\n errors,\n });\n await deadLetterHandler!(envelope, errors);\n break;\n\n case \"log\":\n logger?.warn(\"Invalid message detected, skipping\", {\n messageId: envelope.id,\n messageType: envelope.type,\n correlationId: ctx.correlationId,\n sagaName: ctx.sagaName,\n errors,\n });\n // Fall through to skip\n\n case \"skip\":\n default:\n // Don't call next() - silently skip processing\n break;\n }\n}\n","import type { BaseMessage } from \"@saga-bus/core\";\nimport type { SyncMessageValidator, ValidationResult, ValidationError, AnyMessageValidator } from \"../types.js\";\n\n// Type for Zod schema - we don't import zod directly to keep it optional\ninterface ZodSchema {\n safeParse(data: unknown): {\n success: boolean;\n data?: unknown;\n error?: {\n issues: Array<{\n path: (string | number)[];\n message: string;\n }>;\n };\n };\n}\n\n/**\n * Creates a validator from a Zod schema.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\";\n * import { createZodValidator } from \"@saga-bus/middleware-validation\";\n *\n * const OrderCreatedSchema = z.object({\n * type: z.literal(\"OrderCreated\"),\n * orderId: z.string().uuid(),\n * customerId: z.string(),\n * items: z.array(z.object({\n * sku: z.string(),\n * quantity: z.number().positive(),\n * })),\n * total: z.number().nonnegative(),\n * });\n *\n * const validator = createZodValidator(OrderCreatedSchema);\n * ```\n */\nexport function createZodValidator<T extends BaseMessage = BaseMessage>(schema: ZodSchema): SyncMessageValidator<T> {\n return (message: T): ValidationResult => {\n const result = schema.safeParse(message);\n\n if (result.success) {\n return { valid: true };\n }\n\n const errors: ValidationError[] = result.error!.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n }));\n\n return { valid: false, errors };\n };\n}\n\n/**\n * Creates multiple validators from a map of Zod schemas.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\";\n * import { createZodValidators } from \"@saga-bus/middleware-validation\";\n *\n * const validators = createZodValidators({\n * OrderCreated: z.object({\n * type: z.literal(\"OrderCreated\"),\n * orderId: z.string(),\n * }),\n * OrderShipped: z.object({\n * type: z.literal(\"OrderShipped\"),\n * orderId: z.string(),\n * trackingNumber: z.string(),\n * }),\n * });\n * ```\n */\nexport function createZodValidators(\n schemas: Record<string, ZodSchema>\n): Record<string, AnyMessageValidator> {\n const validators: Record<string, AnyMessageValidator> = {};\n\n for (const [type, schema] of Object.entries(schemas)) {\n validators[type] = createZodValidator(schema);\n }\n\n return validators;\n}\n","import type { BaseMessage } from \"@saga-bus/core\";\nimport type {\n MessageValidator,\n ValidationResult,\n ValidationError,\n} from \"../types.js\";\n\n/**\n * A simple validation function that returns true/false or an error message.\n */\nexport type SimpleValidationFn<T> = (\n message: T\n) => boolean | string | string[] | Promise<boolean | string | string[]>;\n\n/**\n * Creates a validator from a simple validation function.\n *\n * @example\n * ```typescript\n * import { createFunctionValidator } from \"@saga-bus/middleware-validation\";\n *\n * const validator = createFunctionValidator((message: OrderCreated) => {\n * if (!message.orderId) {\n * return \"orderId is required\";\n * }\n * if (message.items.length === 0) {\n * return \"Order must have at least one item\";\n * }\n * return true;\n * });\n * ```\n */\nexport function createFunctionValidator<T extends BaseMessage = BaseMessage>(\n fn: SimpleValidationFn<T>\n): MessageValidator<T> {\n return async (message: T): Promise<ValidationResult> => {\n const result = await fn(message);\n\n if (result === true) {\n return { valid: true };\n }\n\n const errors: ValidationError[] = [];\n\n if (result === false) {\n errors.push({ path: \"\", message: \"Validation failed\" });\n } else if (typeof result === \"string\") {\n errors.push({ path: \"\", message: result });\n } else if (Array.isArray(result)) {\n for (const msg of result) {\n errors.push({ path: \"\", message: msg });\n }\n }\n\n return { valid: false, errors };\n };\n}\n\n/**\n * Combines multiple validators into one.\n * All validators must pass for the message to be valid.\n *\n * @example\n * ```typescript\n * import { combineValidators, createFunctionValidator } from \"@saga-bus/middleware-validation\";\n *\n * const validator = combineValidators([\n * createFunctionValidator((msg) => !!msg.orderId || \"orderId required\"),\n * createFunctionValidator((msg) => msg.items.length > 0 || \"items required\"),\n * ]);\n * ```\n */\nexport function combineValidators<T extends BaseMessage = BaseMessage>(\n validators: MessageValidator<T>[]\n): MessageValidator<T> {\n return async (message: T): Promise<ValidationResult> => {\n const allErrors: ValidationError[] = [];\n\n for (const validator of validators) {\n const result = await validator(message);\n if (!result.valid && result.errors) {\n allErrors.push(...result.errors);\n }\n }\n\n if (allErrors.length === 0) {\n return { valid: true };\n }\n\n return { valid: false, errors: allErrors };\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@saga-bus/middleware-validation",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Validation middleware for saga-bus message payload validation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@saga-bus/core": "0.1.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"zod": ">=3.0.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"zod": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.15.21",
|
|
36
|
+
"tsup": "^8.0.0",
|
|
37
|
+
"typescript": "^5.9.2",
|
|
38
|
+
"vitest": "^3.0.0",
|
|
39
|
+
"zod": "^3.24.0",
|
|
40
|
+
"@repo/eslint-config": "0.0.0",
|
|
41
|
+
"@repo/typescript-config": "0.0.0"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"saga",
|
|
45
|
+
"saga-bus",
|
|
46
|
+
"middleware",
|
|
47
|
+
"validation",
|
|
48
|
+
"zod",
|
|
49
|
+
"schema",
|
|
50
|
+
"event-sourcing"
|
|
51
|
+
],
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"lint": "eslint src/",
|
|
57
|
+
"check-types": "tsc --noEmit",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest"
|
|
60
|
+
}
|
|
61
|
+
}
|