@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/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
+ };