@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.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Zod Validation Middleware Factory
3
+ *
4
+ * Creates middleware to validate request body against Zod schemas
5
+ */
6
+
7
+ function validateRequest(schema) {
8
+ return (req, res, next) => {
9
+ const result = schema.safeParse(req.body);
10
+
11
+ if (!result.success) {
12
+ return res.status(400).json({
13
+ error: "Validation failed",
14
+ details: result.error.errors.map((err) => ({
15
+ path: err.path.join("."),
16
+ message: err.message,
17
+ })),
18
+ });
19
+ }
20
+
21
+ // Attach validated data to request
22
+ req.validatedData = result.data;
23
+ next();
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Validate query parameters
29
+ */
30
+ function validateQuery(schema) {
31
+ return (req, res, next) => {
32
+ const result = schema.safeParse(req.query);
33
+
34
+ if (!result.success) {
35
+ return res.status(400).json({
36
+ error: "Query validation failed",
37
+ details: result.error.errors.map((err) => ({
38
+ path: err.path.join("."),
39
+ message: err.message,
40
+ })),
41
+ });
42
+ }
43
+
44
+ req.validatedQuery = result.data;
45
+ next();
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Validate request parameters
51
+ */
52
+ function validateParams(schema) {
53
+ return (req, res, next) => {
54
+ const result = schema.safeParse(req.params);
55
+
56
+ if (!result.success) {
57
+ return res.status(400).json({
58
+ error: "Parameter validation failed",
59
+ details: result.error.errors.map((err) => ({
60
+ path: err.path.join("."),
61
+ message: err.message,
62
+ })),
63
+ });
64
+ }
65
+
66
+ req.validatedParams = result.data;
67
+ next();
68
+ };
69
+ }
70
+
71
+ module.exports = {
72
+ validateRequest,
73
+ validateQuery,
74
+ validateParams,
75
+ };
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@solutionspool/node-micro-contracts",
3
+ "version": "1.0.0",
4
+ "description": "Shared contracts and schemas for node-micro services",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [
10
+ "contracts",
11
+ "schemas",
12
+ "zod",
13
+ "microservices"
14
+ ],
15
+ "license": "ISC",
16
+ "dependencies": {
17
+ "zod": "^3.22.4"
18
+ }
19
+ }
@@ -0,0 +1,33 @@
1
+ const { z } = require("zod");
2
+
3
+ /**
4
+ * Param Validation Schemas
5
+ */
6
+
7
+ // UUID parameter validation
8
+ const UUIDParamSchema = z.object({
9
+ id: z.string().uuid("Invalid user ID"),
10
+ });
11
+
12
+ /**
13
+ * Query Validation Schemas
14
+ */
15
+
16
+ // User search query validation
17
+ const UserSearchQuerySchema = z.object({
18
+ name: z.string().trim().min(1, "Name must be a non-empty string").optional(),
19
+ email: z.string().email("Invalid email format").optional(),
20
+ mobile: z
21
+ .string()
22
+ .trim()
23
+ .min(1, "Mobile must be a non-empty string")
24
+ .optional(),
25
+ });
26
+
27
+ module.exports = {
28
+ // Param Schemas
29
+ UUIDParamSchema,
30
+
31
+ // Query Schemas
32
+ UserSearchQuerySchema,
33
+ };
@@ -0,0 +1,55 @@
1
+ const { z } = require("zod");
2
+ const { UserRole } = require("./user.schema");
3
+
4
+ /**
5
+ * User Registered Event Schema
6
+ * Published by auth-service when a user registers
7
+ * Consumed by user-service to create user profile
8
+ */
9
+ const UserRegisteredEventSchema = z.object({
10
+ userId: z.string().uuid(),
11
+ name: z.string(),
12
+ email: z.string().email(),
13
+ mobile: z.string().optional(),
14
+ role: UserRole,
15
+ createdAt: z.union([z.string(), z.date()]),
16
+ });
17
+
18
+ /**
19
+ * User Verified Event Schema
20
+ * Published when a user verifies their email
21
+ */
22
+ const UserVerifiedEventSchema = z.object({
23
+ userId: z.string().uuid(),
24
+ email: z.string().email(),
25
+ verifiedAt: z.union([z.string(), z.date()]),
26
+ });
27
+
28
+ /**
29
+ * User Updated Event Schema
30
+ * Published when a user's profile is updated
31
+ */
32
+ const UserUpdatedEventSchema = z.object({
33
+ userId: z.string().uuid(),
34
+ name: z.string().optional(),
35
+ email: z.string().email().optional(),
36
+ role: UserRole.optional(),
37
+ updatedAt: z.union([z.string(), z.date()]),
38
+ });
39
+
40
+ /**
41
+ * User Deleted Event Schema
42
+ * Published when a user account is deleted
43
+ */
44
+ const UserDeletedEventSchema = z.object({
45
+ userId: z.string().uuid(),
46
+ email: z.string().email(),
47
+ deletedAt: z.union([z.string(), z.date()]),
48
+ });
49
+
50
+ module.exports = {
51
+ UserRegisteredEventSchema,
52
+ UserVerifiedEventSchema,
53
+ UserUpdatedEventSchema,
54
+ UserDeletedEventSchema,
55
+ };
@@ -0,0 +1,91 @@
1
+ const { z } = require("zod");
2
+
3
+ /**
4
+ * User Role Enum
5
+ */
6
+ const UserRole = z.enum(["user", "provider", "admin"]);
7
+
8
+ /**
9
+ * User Base Schema - Common fields
10
+ */
11
+ const UserBaseSchema = z.object({
12
+ id: z.string().uuid(),
13
+ name: z.string().min(1, "Name is required"),
14
+ email: z.string().email("Invalid email format"),
15
+ role: UserRole,
16
+ createdAt: z.union([z.string(), z.date()]).optional(),
17
+ updatedAt: z.union([z.string(), z.date()]).optional(),
18
+ });
19
+
20
+ /**
21
+ * User Registration Request Schema
22
+ */
23
+ const UserRegistrationSchema = z.object({
24
+ name: z.string().min(1, "Name is required"),
25
+ email: z.string().email("Invalid email format"),
26
+ mobile: z.string().optional(),
27
+ password: z.string().min(6, "Password must be at least 6 characters"),
28
+ role: UserRole.optional().default("user"),
29
+ });
30
+
31
+ /**
32
+ * User Login Request Schema
33
+ */
34
+ const UserLoginSchema = z.object({
35
+ email: z.string().email("Invalid email format"),
36
+ password: z.string().min(1, "Password is required"),
37
+ });
38
+
39
+ /**
40
+ * User Profile Update Schema
41
+ */
42
+ const UserProfileUpdateSchema = z.object({
43
+ name: z.string().min(1, "Name is required").optional(),
44
+ email: z.string().email("Invalid email format").optional(),
45
+ role: UserRole.optional(),
46
+ });
47
+
48
+ /**
49
+ * User Response Schema (without password)
50
+ */
51
+ const UserResponseSchema = UserBaseSchema.extend({
52
+ mobile: z.string().optional(),
53
+ });
54
+
55
+ /**
56
+ * JWT Token Payload Schema
57
+ */
58
+ const JWTPayloadSchema = z.object({
59
+ iss: z.string(), // Issuer
60
+ sub: z.string().uuid(), // Subject (user ID)
61
+ id: z.string().uuid(),
62
+ email: z.string().email(),
63
+ role: UserRole,
64
+ iat: z.number().optional(), // Issued at
65
+ exp: z.number().optional(), // Expiration
66
+ });
67
+
68
+ /**
69
+ * Auth Response Schema
70
+ */
71
+ const AuthResponseSchema = z.object({
72
+ message: z.string(),
73
+ token: z.string(),
74
+ user: UserResponseSchema,
75
+ });
76
+
77
+ module.exports = {
78
+ // Enums
79
+ UserRole,
80
+
81
+ // User Schemas
82
+ UserBaseSchema,
83
+ UserRegistrationSchema,
84
+ UserLoginSchema,
85
+ UserProfileUpdateSchema,
86
+ UserResponseSchema,
87
+
88
+ // JWT Schemas
89
+ JWTPayloadSchema,
90
+ AuthResponseSchema,
91
+ };
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Test file to verify contracts are working correctly
3
+ * Run with: node test-contracts.js
4
+ */
5
+
6
+ const {
7
+ // Schemas
8
+ UserRegistrationSchema,
9
+ UserLoginSchema,
10
+ UserProfileUpdateSchema,
11
+ UserRegisteredEventSchema,
12
+ UserRole,
13
+
14
+ // Middleware
15
+ validateRequest,
16
+ } = require("./index");
17
+
18
+ console.log("🧪 Testing @node-micro/contracts\n");
19
+
20
+ // Test 1: Valid User Registration
21
+ console.log("Test 1: Valid User Registration");
22
+ try {
23
+ const validRegistration = {
24
+ name: "John Doe",
25
+ email: "john@example.com",
26
+ password: "password123",
27
+ role: "user",
28
+ mobile: "+1234567890",
29
+ };
30
+
31
+ const result = UserRegistrationSchema.safeParse(validRegistration);
32
+ if (result.success) {
33
+ console.log("✅ PASS - Valid registration data accepted");
34
+ } else {
35
+ console.log("❌ FAIL - Valid data rejected");
36
+ console.log(result.error.errors);
37
+ }
38
+ } catch (error) {
39
+ console.log("❌ FAIL - Unexpected error:", error.message);
40
+ }
41
+
42
+ // Test 2: Invalid Email
43
+ console.log("\nTest 2: Invalid Email");
44
+ try {
45
+ const invalidEmail = {
46
+ name: "John Doe",
47
+ email: "not-an-email",
48
+ password: "password123",
49
+ };
50
+
51
+ const result = UserRegistrationSchema.safeParse(invalidEmail);
52
+ if (!result.success) {
53
+ console.log("✅ PASS - Invalid email rejected");
54
+ console.log(" Error:", result.error.errors[0].message);
55
+ } else {
56
+ console.log("❌ FAIL - Invalid email accepted");
57
+ }
58
+ } catch (error) {
59
+ console.log("❌ FAIL - Unexpected error:", error.message);
60
+ }
61
+
62
+ // Test 3: Short Password
63
+ console.log("\nTest 3: Short Password");
64
+ try {
65
+ const shortPassword = {
66
+ name: "John Doe",
67
+ email: "john@example.com",
68
+ password: "123",
69
+ };
70
+
71
+ const result = UserRegistrationSchema.safeParse(shortPassword);
72
+ if (!result.success) {
73
+ console.log("✅ PASS - Short password rejected");
74
+ console.log(" Error:", result.error.errors[0].message);
75
+ } else {
76
+ console.log("❌ FAIL - Short password accepted");
77
+ }
78
+ } catch (error) {
79
+ console.log("❌ FAIL - Unexpected error:", error.message);
80
+ }
81
+
82
+ // Test 4: Invalid Role
83
+ console.log("\nTest 4: Invalid Role");
84
+ try {
85
+ const invalidRole = {
86
+ name: "John Doe",
87
+ email: "john@example.com",
88
+ password: "password123",
89
+ role: "superadmin", // Not a valid role
90
+ };
91
+
92
+ const result = UserRegistrationSchema.safeParse(invalidRole);
93
+ if (!result.success) {
94
+ console.log("✅ PASS - Invalid role rejected");
95
+ console.log(" Error:", result.error.errors[0].message);
96
+ } else {
97
+ console.log("❌ FAIL - Invalid role accepted");
98
+ }
99
+ } catch (error) {
100
+ console.log("❌ FAIL - Unexpected error:", error.message);
101
+ }
102
+
103
+ // Test 5: Valid Login
104
+ console.log("\nTest 5: Valid Login");
105
+ try {
106
+ const validLogin = {
107
+ email: "john@example.com",
108
+ password: "password123",
109
+ };
110
+
111
+ const result = UserLoginSchema.safeParse(validLogin);
112
+ if (result.success) {
113
+ console.log("✅ PASS - Valid login data accepted");
114
+ } else {
115
+ console.log("❌ FAIL - Valid login data rejected");
116
+ console.log(result.error.errors);
117
+ }
118
+ } catch (error) {
119
+ console.log("❌ FAIL - Unexpected error:", error.message);
120
+ }
121
+
122
+ // Test 6: Valid Event Data
123
+ console.log("\nTest 6: Valid Event Data");
124
+ try {
125
+ const validEvent = {
126
+ userId: "123e4567-e89b-12d3-a456-426614174000",
127
+ name: "John Doe",
128
+ email: "john@example.com",
129
+ mobile: "+1234567890",
130
+ role: "user",
131
+ createdAt: new Date(),
132
+ };
133
+
134
+ const result = UserRegisteredEventSchema.safeParse(validEvent);
135
+ if (result.success) {
136
+ console.log("✅ PASS - Valid event data accepted");
137
+ } else {
138
+ console.log("❌ FAIL - Valid event data rejected");
139
+ console.log(result.error.errors);
140
+ }
141
+ } catch (error) {
142
+ console.log("❌ FAIL - Unexpected error:", error.message);
143
+ }
144
+
145
+ // Test 7: Invalid UUID in Event
146
+ console.log("\nTest 7: Invalid UUID in Event");
147
+ try {
148
+ const invalidUUID = {
149
+ userId: "not-a-uuid",
150
+ name: "John Doe",
151
+ email: "john@example.com",
152
+ role: "user",
153
+ createdAt: new Date(),
154
+ };
155
+
156
+ const result = UserRegisteredEventSchema.safeParse(invalidUUID);
157
+ if (!result.success) {
158
+ console.log("✅ PASS - Invalid UUID rejected");
159
+ console.log(" Error:", result.error.errors[0].message);
160
+ } else {
161
+ console.log("❌ FAIL - Invalid UUID accepted");
162
+ }
163
+ } catch (error) {
164
+ console.log("❌ FAIL - Unexpected error:", error.message);
165
+ }
166
+
167
+ // Test 8: Profile Update (Partial Data)
168
+ console.log("\nTest 8: Profile Update (Partial Data)");
169
+ try {
170
+ const partialUpdate = {
171
+ name: "Jane Doe", // Only updating name
172
+ };
173
+
174
+ const result = UserProfileUpdateSchema.safeParse(partialUpdate);
175
+ if (result.success) {
176
+ console.log("✅ PASS - Partial update accepted");
177
+ } else {
178
+ console.log("❌ FAIL - Partial update rejected");
179
+ console.log(result.error.errors);
180
+ }
181
+ } catch (error) {
182
+ console.log("❌ FAIL - Unexpected error:", error.message);
183
+ }
184
+
185
+ // Test 9: Default Role
186
+ console.log("\nTest 9: Default Role");
187
+ try {
188
+ const noRole = {
189
+ name: "John Doe",
190
+ email: "john@example.com",
191
+ password: "password123",
192
+ // role not provided
193
+ };
194
+
195
+ const result = UserRegistrationSchema.safeParse(noRole);
196
+ if (result.success && result.data.role === "user") {
197
+ console.log("✅ PASS - Default role 'user' applied");
198
+ } else {
199
+ console.log("❌ FAIL - Default role not applied");
200
+ }
201
+ } catch (error) {
202
+ console.log("❌ FAIL - Unexpected error:", error.message);
203
+ }
204
+
205
+ // Test 10: UserRole Enum
206
+ console.log("\nTest 10: UserRole Enum");
207
+ try {
208
+ const validRoles = ["user", "provider", "admin"];
209
+ const allValid = validRoles.every((role) => {
210
+ const result = UserRole.safeParse(role);
211
+ return result.success;
212
+ });
213
+
214
+ const invalidResult = UserRole.safeParse("invalid");
215
+
216
+ if (allValid && !invalidResult.success) {
217
+ console.log("✅ PASS - UserRole enum works correctly");
218
+ } else {
219
+ console.log("❌ FAIL - UserRole enum validation failed");
220
+ }
221
+ } catch (error) {
222
+ console.log("❌ FAIL - Unexpected error:", error.message);
223
+ }
224
+
225
+ console.log("\n✨ Testing complete!");
226
+ console.log("\nTo use in your services:");
227
+ console.log(
228
+ ' const { UserRegistrationSchema, validateRequest } = require("@node-micro/contracts");',
229
+ );