@solutionspool/node-micro-contracts 1.0.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/ARCHITECTURE.md +302 -0
- package/QUICKSTART.md +204 -0
- package/README.md +95 -0
- package/USAGE_GUIDE.md +434 -0
- package/examples/auth-routes.example.js +38 -0
- package/examples/event-consumption.example.js +109 -0
- package/examples/event-publishing.example.js +82 -0
- package/index.js +25 -0
- package/middleware/validate.js +75 -0
- package/package.json +19 -0
- package/schemas/common.schema.js +33 -0
- package/schemas/events.schema.js +55 -0
- package/schemas/user.schema.js +91 -0
- package/test-contracts.js +229 -0
package/USAGE_GUIDE.md
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# Contracts Package Usage Guide
|
|
2
|
+
|
|
3
|
+
## Complete Guide to Using @node-micro/contracts
|
|
4
|
+
|
|
5
|
+
This guide shows you how to integrate the contracts package into your microservices.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Installation](#installation)
|
|
10
|
+
2. [Request Validation](#request-validation)
|
|
11
|
+
3. [Event Publishing](#event-publishing)
|
|
12
|
+
4. [Event Consumption](#event-consumption)
|
|
13
|
+
5. [Error Handling](#error-handling)
|
|
14
|
+
6. [Best Practices](#best-practices)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
The package is already installed in both services. If you need to reinstall:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# In auth-service or user-service
|
|
22
|
+
npm install ../../packages/contracts
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Request Validation
|
|
26
|
+
|
|
27
|
+
### Using Middleware (Recommended)
|
|
28
|
+
|
|
29
|
+
The easiest way to validate requests is using the built-in middleware:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
const express = require("express");
|
|
33
|
+
const router = express.Router();
|
|
34
|
+
const {
|
|
35
|
+
UserRegistrationSchema,
|
|
36
|
+
UserLoginSchema,
|
|
37
|
+
UserProfileUpdateSchema,
|
|
38
|
+
validateRequest,
|
|
39
|
+
} = require("@node-micro/contracts");
|
|
40
|
+
|
|
41
|
+
// Registration endpoint with validation
|
|
42
|
+
router.post(
|
|
43
|
+
"/register",
|
|
44
|
+
validateRequest(UserRegistrationSchema),
|
|
45
|
+
async (req, res) => {
|
|
46
|
+
// req.validatedData contains the validated and sanitized data
|
|
47
|
+
const { name, email, password, role, mobile } = req.validatedData;
|
|
48
|
+
|
|
49
|
+
// Your logic here...
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Login endpoint with validation
|
|
54
|
+
router.post("/login", validateRequest(UserLoginSchema), async (req, res) => {
|
|
55
|
+
const { email, password } = req.validatedData;
|
|
56
|
+
// Your logic here...
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Profile update with validation
|
|
60
|
+
router.put(
|
|
61
|
+
"/profile",
|
|
62
|
+
verifyToken, // Your auth middleware
|
|
63
|
+
validateRequest(UserProfileUpdateSchema),
|
|
64
|
+
async (req, res) => {
|
|
65
|
+
const updates = req.validatedData;
|
|
66
|
+
// Your logic here...
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Manual Validation
|
|
72
|
+
|
|
73
|
+
For more control, validate manually in your controllers:
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
const { UserRegistrationSchema } = require("@node-micro/contracts");
|
|
77
|
+
|
|
78
|
+
async function register(req, res) {
|
|
79
|
+
// Safe parse returns { success, data, error }
|
|
80
|
+
const result = UserRegistrationSchema.safeParse(req.body);
|
|
81
|
+
|
|
82
|
+
if (!result.success) {
|
|
83
|
+
return res.status(400).json({
|
|
84
|
+
error: "Validation failed",
|
|
85
|
+
details: result.error.errors.map((err) => ({
|
|
86
|
+
path: err.path.join("."),
|
|
87
|
+
message: err.message,
|
|
88
|
+
})),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const validData = result.data;
|
|
93
|
+
// Continue with valid data...
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Event Publishing
|
|
98
|
+
|
|
99
|
+
Always validate event data before publishing to ensure contract compliance:
|
|
100
|
+
|
|
101
|
+
### In auth-service Controller
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
const {
|
|
105
|
+
UserRegisteredEventSchema,
|
|
106
|
+
UserUpdatedEventSchema,
|
|
107
|
+
} = require("@node-micro/contracts");
|
|
108
|
+
|
|
109
|
+
class AuthController {
|
|
110
|
+
constructor(messageQueue) {
|
|
111
|
+
this.messageQueue = messageQueue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async register(req, res) {
|
|
115
|
+
try {
|
|
116
|
+
// ... create user logic ...
|
|
117
|
+
|
|
118
|
+
// Validate and publish event
|
|
119
|
+
if (this.messageQueue) {
|
|
120
|
+
try {
|
|
121
|
+
const eventData = UserRegisteredEventSchema.parse({
|
|
122
|
+
userId: user.id,
|
|
123
|
+
name: user.name,
|
|
124
|
+
email: user.email,
|
|
125
|
+
mobile: req.body.mobile,
|
|
126
|
+
role: user.role,
|
|
127
|
+
createdAt: user.createdAt,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await this.messageQueue.publishEvent(
|
|
131
|
+
"users",
|
|
132
|
+
"user.registered",
|
|
133
|
+
eventData,
|
|
134
|
+
);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error("Event validation failed:", error.message);
|
|
137
|
+
// Log but don't fail the request
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
res.status(201).json({ message: "User registered", token, user });
|
|
142
|
+
} catch (error) {
|
|
143
|
+
res.status(500).json({ error: error.message });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Event Consumption
|
|
150
|
+
|
|
151
|
+
Validate incoming events to ensure data integrity:
|
|
152
|
+
|
|
153
|
+
### In user-service Event Listeners
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
const {
|
|
157
|
+
UserRegisteredEventSchema,
|
|
158
|
+
UserVerifiedEventSchema,
|
|
159
|
+
UserUpdatedEventSchema,
|
|
160
|
+
} = require("@node-micro/contracts");
|
|
161
|
+
const { User } = require("../models");
|
|
162
|
+
|
|
163
|
+
async function subscribeToEvents(messageQueue) {
|
|
164
|
+
// User registered event
|
|
165
|
+
await messageQueue.subscribeToEvent(
|
|
166
|
+
"users",
|
|
167
|
+
"user-service-queue",
|
|
168
|
+
"user.registered",
|
|
169
|
+
async (rawData) => {
|
|
170
|
+
try {
|
|
171
|
+
// Validate incoming event data
|
|
172
|
+
const userData = UserRegisteredEventSchema.parse(rawData);
|
|
173
|
+
|
|
174
|
+
// Create user with validated data
|
|
175
|
+
await User.create({
|
|
176
|
+
id: userData.userId,
|
|
177
|
+
name: userData.name,
|
|
178
|
+
email: userData.email,
|
|
179
|
+
mobile: userData.mobile,
|
|
180
|
+
role: userData.role,
|
|
181
|
+
createdAt: userData.createdAt,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
console.log(`✓ User profile created for ${userData.email}`);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error.name === "ZodError") {
|
|
187
|
+
console.error("Invalid event data:", error.errors);
|
|
188
|
+
// Send to dead letter queue or alert
|
|
189
|
+
} else if (error.name === "SequelizeUniqueConstraintError") {
|
|
190
|
+
console.log(`User ${rawData.email} already exists`);
|
|
191
|
+
} else {
|
|
192
|
+
console.error("Failed to create user:", error.message);
|
|
193
|
+
throw error; // Trigger retry
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// User verified event
|
|
200
|
+
await messageQueue.subscribeToEvent(
|
|
201
|
+
"users",
|
|
202
|
+
"user-service-queue",
|
|
203
|
+
"user.verified",
|
|
204
|
+
async (rawData) => {
|
|
205
|
+
try {
|
|
206
|
+
const verificationData = UserVerifiedEventSchema.parse(rawData);
|
|
207
|
+
|
|
208
|
+
await User.update(
|
|
209
|
+
{
|
|
210
|
+
emailVerified: true,
|
|
211
|
+
verifiedAt: verificationData.verifiedAt,
|
|
212
|
+
},
|
|
213
|
+
{ where: { id: verificationData.userId } },
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
console.log(`✓ User verified: ${verificationData.email}`);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (error.name === "ZodError") {
|
|
219
|
+
console.error("Invalid verification event:", error.errors);
|
|
220
|
+
} else {
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = { subscribeToEvents };
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Error Handling
|
|
232
|
+
|
|
233
|
+
### Validation Error Format
|
|
234
|
+
|
|
235
|
+
When validation fails, errors are returned in a consistent format:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"error": "Validation failed",
|
|
240
|
+
"details": [
|
|
241
|
+
{
|
|
242
|
+
"path": "email",
|
|
243
|
+
"message": "Invalid email format"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"path": "password",
|
|
247
|
+
"message": "Password must be at least 6 characters"
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Handling Zod Errors
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
try {
|
|
257
|
+
const validData = UserRegistrationSchema.parse(data);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error.name === "ZodError") {
|
|
260
|
+
// Access specific error details
|
|
261
|
+
error.errors.forEach((err) => {
|
|
262
|
+
console.log(`Field: ${err.path.join(".")}`);
|
|
263
|
+
console.log(`Message: ${err.message}`);
|
|
264
|
+
console.log(`Code: ${err.code}`);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Best Practices
|
|
271
|
+
|
|
272
|
+
### 1. Always Validate at Boundaries
|
|
273
|
+
|
|
274
|
+
- ✅ Validate HTTP requests at route level
|
|
275
|
+
- ✅ Validate events before publishing
|
|
276
|
+
- ✅ Validate events when consuming
|
|
277
|
+
|
|
278
|
+
### 2. Use Type Inference
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
const { z } = require("zod");
|
|
282
|
+
const { UserRegistrationSchema } = require("@node-micro/contracts");
|
|
283
|
+
|
|
284
|
+
// Get the inferred type (useful for TypeScript or JSDoc)
|
|
285
|
+
/** @typedef {z.infer<typeof UserRegistrationSchema>} UserRegistration */
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 3. Handle Validation Errors Gracefully
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
// For requests - return 400 Bad Request
|
|
292
|
+
if (!result.success) {
|
|
293
|
+
return res.status(400).json({
|
|
294
|
+
error: "Validation failed",
|
|
295
|
+
details: result.error.errors,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// For events - log and send to DLQ
|
|
300
|
+
if (error.name === "ZodError") {
|
|
301
|
+
console.error("Invalid event:", error.errors);
|
|
302
|
+
await sendToDeadLetterQueue(rawData);
|
|
303
|
+
return; // Don't throw - prevent infinite retries
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 4. Partial Validation
|
|
308
|
+
|
|
309
|
+
For optional fields or partial updates:
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
const { UserProfileUpdateSchema } = require("@node-micro/contracts");
|
|
313
|
+
|
|
314
|
+
// All fields are optional in UserProfileUpdateSchema
|
|
315
|
+
const result = UserProfileUpdateSchema.parse({
|
|
316
|
+
name: "New Name", // email not provided - that's ok
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### 5. Custom Schemas
|
|
321
|
+
|
|
322
|
+
Extend existing schemas for specific use cases:
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
const { z } = require("zod");
|
|
326
|
+
const { UserBaseSchema } = require("@node-micro/contracts");
|
|
327
|
+
|
|
328
|
+
// Create a custom schema with additional fields
|
|
329
|
+
const UserWithStatsSchema = UserBaseSchema.extend({
|
|
330
|
+
loginCount: z.number().int().min(0),
|
|
331
|
+
lastLoginAt: z.date().optional(),
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### 6. Environment-Specific Validation
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
const { UserRegistrationSchema } = require("@node-micro/contracts");
|
|
339
|
+
|
|
340
|
+
// In development, be strict
|
|
341
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
342
|
+
|
|
343
|
+
function validateRegistration(data) {
|
|
344
|
+
if (isDevelopment) {
|
|
345
|
+
// Throw on validation error in dev
|
|
346
|
+
return UserRegistrationSchema.parse(data);
|
|
347
|
+
} else {
|
|
348
|
+
// Return result in production for better error handling
|
|
349
|
+
return UserRegistrationSchema.safeParse(data);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Migration Strategy
|
|
355
|
+
|
|
356
|
+
### Step 1: Add Validation Middleware to Routes
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
// Before
|
|
360
|
+
router.post("/register", authController.register);
|
|
361
|
+
|
|
362
|
+
// After
|
|
363
|
+
router.post(
|
|
364
|
+
"/register",
|
|
365
|
+
validateRequest(UserRegistrationSchema),
|
|
366
|
+
authController.register,
|
|
367
|
+
);
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Step 2: Update Controllers to Use Validated Data
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
// Before
|
|
374
|
+
async register(req, res) {
|
|
375
|
+
const { name, email, password } = req.body;
|
|
376
|
+
// ...
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// After
|
|
380
|
+
async register(req, res) {
|
|
381
|
+
const { name, email, password } = req.validatedData; // Use validatedData
|
|
382
|
+
// ...
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Step 3: Add Event Validation
|
|
387
|
+
|
|
388
|
+
```javascript
|
|
389
|
+
// Before
|
|
390
|
+
await messageQueue.publishEvent("users", "user.registered", {
|
|
391
|
+
userId: user.id,
|
|
392
|
+
name: user.name,
|
|
393
|
+
// ...
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// After
|
|
397
|
+
const eventData = UserRegisteredEventSchema.parse({
|
|
398
|
+
userId: user.id,
|
|
399
|
+
name: user.name,
|
|
400
|
+
// ...
|
|
401
|
+
});
|
|
402
|
+
await messageQueue.publishEvent("users", "user.registered", eventData);
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Available Schemas Reference
|
|
406
|
+
|
|
407
|
+
### Request Schemas
|
|
408
|
+
|
|
409
|
+
- `UserRegistrationSchema` - name, email, mobile?, password, role?
|
|
410
|
+
- `UserLoginSchema` - email, password
|
|
411
|
+
- `UserProfileUpdateSchema` - name?, email?, role?
|
|
412
|
+
|
|
413
|
+
### Event Schemas
|
|
414
|
+
|
|
415
|
+
- `UserRegisteredEventSchema` - userId, name, email, mobile?, role, createdAt
|
|
416
|
+
- `UserVerifiedEventSchema` - userId, email, verifiedAt
|
|
417
|
+
- `UserUpdatedEventSchema` - userId, name?, email?, role?, updatedAt
|
|
418
|
+
- `UserDeletedEventSchema` - userId, email, deletedAt
|
|
419
|
+
|
|
420
|
+
### Response Schemas
|
|
421
|
+
|
|
422
|
+
- `UserResponseSchema` - User data without password
|
|
423
|
+
- `AuthResponseSchema` - message, token, user
|
|
424
|
+
- `JWTPayloadSchema` - iss, sub, id, email, role, iat?, exp?
|
|
425
|
+
|
|
426
|
+
### Middleware
|
|
427
|
+
|
|
428
|
+
- `validateRequest(schema)` - Validates req.body
|
|
429
|
+
- `validateQuery(schema)` - Validates req.query
|
|
430
|
+
- `validateParams(schema)` - Validates req.params
|
|
431
|
+
|
|
432
|
+
## Questions?
|
|
433
|
+
|
|
434
|
+
Check the `/examples` directory in the contracts package for more examples, or review the schemas directly in `/schemas`.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Using Contracts in Auth Service Routes
|
|
3
|
+
*
|
|
4
|
+
* This file demonstrates how to use the @node-micro/contracts package
|
|
5
|
+
* for request validation in auth-service routes
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const express = require("express");
|
|
9
|
+
const router = express.Router();
|
|
10
|
+
const {
|
|
11
|
+
UserRegistrationSchema,
|
|
12
|
+
UserLoginSchema,
|
|
13
|
+
validateRequest,
|
|
14
|
+
} = require("@node-micro/contracts");
|
|
15
|
+
|
|
16
|
+
// Example: Register route with validation
|
|
17
|
+
router.post(
|
|
18
|
+
"/register",
|
|
19
|
+
validateRequest(UserRegistrationSchema),
|
|
20
|
+
async (req, res) => {
|
|
21
|
+
// req.validatedData contains the validated data
|
|
22
|
+
const { name, email, password, role, mobile } = req.validatedData;
|
|
23
|
+
|
|
24
|
+
// Your controller logic here
|
|
25
|
+
// authController.register(req, res);
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Example: Login route with validation
|
|
30
|
+
router.post("/login", validateRequest(UserLoginSchema), async (req, res) => {
|
|
31
|
+
// req.validatedData contains the validated data
|
|
32
|
+
const { email, password } = req.validatedData;
|
|
33
|
+
|
|
34
|
+
// Your controller logic here
|
|
35
|
+
// authController.login(req, res);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
module.exports = router;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Using Contracts for Event Consumption
|
|
3
|
+
*
|
|
4
|
+
* This file demonstrates how to use the @node-micro/contracts package
|
|
5
|
+
* for validating incoming event data in event listeners
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
UserRegisteredEventSchema,
|
|
10
|
+
UserVerifiedEventSchema,
|
|
11
|
+
UserUpdatedEventSchema,
|
|
12
|
+
} = require("@node-micro/contracts");
|
|
13
|
+
const { User } = require("../models");
|
|
14
|
+
|
|
15
|
+
async function subscribeToEventsExample(messageQueue) {
|
|
16
|
+
// Subscribe to user.registered event with validation
|
|
17
|
+
await messageQueue.subscribeToEvent(
|
|
18
|
+
"users",
|
|
19
|
+
"user-service-queue",
|
|
20
|
+
"user.registered",
|
|
21
|
+
async (rawData) => {
|
|
22
|
+
try {
|
|
23
|
+
// Validate incoming event data
|
|
24
|
+
const userData = UserRegisteredEventSchema.parse(rawData);
|
|
25
|
+
|
|
26
|
+
// Now userData is validated and type-safe
|
|
27
|
+
await User.create({
|
|
28
|
+
id: userData.userId,
|
|
29
|
+
name: userData.name,
|
|
30
|
+
email: userData.email,
|
|
31
|
+
mobile: userData.mobile,
|
|
32
|
+
role: userData.role,
|
|
33
|
+
createdAt: userData.createdAt,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log(`✓ User profile created for ${userData.email}`);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error.name === "ZodError") {
|
|
39
|
+
console.error("Event validation failed:", error.errors);
|
|
40
|
+
// Handle validation error - maybe send to DLQ
|
|
41
|
+
} else if (error.name === "SequelizeUniqueConstraintError") {
|
|
42
|
+
console.log(`User already exists`);
|
|
43
|
+
} else {
|
|
44
|
+
console.error("Failed to create user profile:", error.message);
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Subscribe to user.verified event with validation
|
|
52
|
+
await messageQueue.subscribeToEvent(
|
|
53
|
+
"users",
|
|
54
|
+
"user-service-queue",
|
|
55
|
+
"user.verified",
|
|
56
|
+
async (rawData) => {
|
|
57
|
+
try {
|
|
58
|
+
const verificationData = UserVerifiedEventSchema.parse(rawData);
|
|
59
|
+
|
|
60
|
+
// Update user with validated data
|
|
61
|
+
await User.update(
|
|
62
|
+
{ emailVerified: true, verifiedAt: verificationData.verifiedAt },
|
|
63
|
+
{ where: { id: verificationData.userId } },
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
console.log(`✓ User verified: ${verificationData.email}`);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error.name === "ZodError") {
|
|
69
|
+
console.error("Event validation failed:", error.errors);
|
|
70
|
+
} else {
|
|
71
|
+
console.error("Failed to verify user:", error.message);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Subscribe to user.updated event with validation
|
|
79
|
+
await messageQueue.subscribeToEvent(
|
|
80
|
+
"users",
|
|
81
|
+
"user-service-queue",
|
|
82
|
+
"user.updated",
|
|
83
|
+
async (rawData) => {
|
|
84
|
+
try {
|
|
85
|
+
const updateData = UserUpdatedEventSchema.parse(rawData);
|
|
86
|
+
|
|
87
|
+
// Build update object from validated data
|
|
88
|
+
const updates = {};
|
|
89
|
+
if (updateData.name) updates.name = updateData.name;
|
|
90
|
+
if (updateData.email) updates.email = updateData.email;
|
|
91
|
+
if (updateData.role) updates.role = updateData.role;
|
|
92
|
+
updates.updatedAt = updateData.updatedAt;
|
|
93
|
+
|
|
94
|
+
await User.update(updates, { where: { id: updateData.userId } });
|
|
95
|
+
|
|
96
|
+
console.log(`✓ User updated: ${updateData.userId}`);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error.name === "ZodError") {
|
|
99
|
+
console.error("Event validation failed:", error.errors);
|
|
100
|
+
} else {
|
|
101
|
+
console.error("Failed to update user:", error.message);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { subscribeToEventsExample };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Using Contracts for Event Publishing
|
|
3
|
+
*
|
|
4
|
+
* This file demonstrates how to use the @node-micro/contracts package
|
|
5
|
+
* for validating event data before publishing
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
UserRegisteredEventSchema,
|
|
10
|
+
UserUpdatedEventSchema,
|
|
11
|
+
UserDeletedEventSchema,
|
|
12
|
+
} = require("@node-micro/contracts");
|
|
13
|
+
|
|
14
|
+
class AuthControllerExample {
|
|
15
|
+
constructor(messageQueue) {
|
|
16
|
+
this.messageQueue = messageQueue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async register(req, res) {
|
|
20
|
+
try {
|
|
21
|
+
// ... user creation logic ...
|
|
22
|
+
const user = {
|
|
23
|
+
id: "uuid",
|
|
24
|
+
name: "John",
|
|
25
|
+
email: "john@example.com",
|
|
26
|
+
role: "user",
|
|
27
|
+
createdAt: new Date(),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Validate event data before publishing
|
|
31
|
+
const eventData = UserRegisteredEventSchema.parse({
|
|
32
|
+
userId: user.id,
|
|
33
|
+
name: user.name,
|
|
34
|
+
email: user.email,
|
|
35
|
+
mobile: req.body.mobile,
|
|
36
|
+
role: user.role,
|
|
37
|
+
createdAt: user.createdAt,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Publish validated event
|
|
41
|
+
await this.messageQueue.publishEvent(
|
|
42
|
+
"users",
|
|
43
|
+
"user.registered",
|
|
44
|
+
eventData,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
res.status(201).json({ message: "User registered" });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error.name === "ZodError") {
|
|
50
|
+
return res.status(500).json({
|
|
51
|
+
error: "Event validation failed",
|
|
52
|
+
details: error.errors,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
res.status(500).json({ error: error.message });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async updateUser(req, res) {
|
|
60
|
+
try {
|
|
61
|
+
// ... update logic ...
|
|
62
|
+
const user = { id: "uuid", name: "Jane", email: "jane@example.com" };
|
|
63
|
+
|
|
64
|
+
// Validate event data
|
|
65
|
+
const eventData = UserUpdatedEventSchema.parse({
|
|
66
|
+
userId: user.id,
|
|
67
|
+
name: user.name,
|
|
68
|
+
email: user.email,
|
|
69
|
+
role: user.role,
|
|
70
|
+
updatedAt: new Date(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await this.messageQueue.publishEvent("users", "user.updated", eventData);
|
|
74
|
+
|
|
75
|
+
res.json({ message: "User updated" });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
res.status(500).json({ error: error.message });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = AuthControllerExample;
|
package/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @node-micro/contracts
|
|
3
|
+
*
|
|
4
|
+
* Centralized contracts and schemas for microservices communication
|
|
5
|
+
* Uses Zod for runtime validation and type inference
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const userSchemas = require("./schemas/user.schema");
|
|
9
|
+
const eventSchemas = require("./schemas/events.schema");
|
|
10
|
+
const commonSchemas = require("./schemas/common.schema");
|
|
11
|
+
const validators = require("./middleware/validate");
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
// User Schemas
|
|
15
|
+
...userSchemas,
|
|
16
|
+
|
|
17
|
+
// Event Schemas
|
|
18
|
+
...eventSchemas,
|
|
19
|
+
|
|
20
|
+
// Common Schemas (params, query)
|
|
21
|
+
...commonSchemas,
|
|
22
|
+
|
|
23
|
+
// Validators/Middleware
|
|
24
|
+
...validators,
|
|
25
|
+
};
|