@superdangerous/app-framework 4.9.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.
Files changed (239) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +652 -0
  3. package/dist/api/logsRouter.d.ts +20 -0
  4. package/dist/api/logsRouter.d.ts.map +1 -0
  5. package/dist/api/logsRouter.js +515 -0
  6. package/dist/api/logsRouter.js.map +1 -0
  7. package/dist/cli/dev-server.d.ts +7 -0
  8. package/dist/cli/dev-server.d.ts.map +1 -0
  9. package/dist/cli/dev-server.js +640 -0
  10. package/dist/cli/dev-server.js.map +1 -0
  11. package/dist/cli/index.d.ts +7 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +26 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/core/StandardServer.d.ts +129 -0
  16. package/dist/core/StandardServer.d.ts.map +1 -0
  17. package/dist/core/StandardServer.js +453 -0
  18. package/dist/core/StandardServer.js.map +1 -0
  19. package/dist/core/apiResponse.d.ts +69 -0
  20. package/dist/core/apiResponse.d.ts.map +1 -0
  21. package/dist/core/apiResponse.js +127 -0
  22. package/dist/core/apiResponse.js.map +1 -0
  23. package/dist/core/healthCheck.d.ts +160 -0
  24. package/dist/core/healthCheck.d.ts.map +1 -0
  25. package/dist/core/healthCheck.js +398 -0
  26. package/dist/core/healthCheck.js.map +1 -0
  27. package/dist/core/index.d.ts +40 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +40 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/core/logger.d.ts +117 -0
  32. package/dist/core/logger.d.ts.map +1 -0
  33. package/dist/core/logger.js +826 -0
  34. package/dist/core/logger.js.map +1 -0
  35. package/dist/core/portUtils.d.ts +71 -0
  36. package/dist/core/portUtils.d.ts.map +1 -0
  37. package/dist/core/portUtils.js +240 -0
  38. package/dist/core/portUtils.js.map +1 -0
  39. package/dist/core/storageService.d.ts +119 -0
  40. package/dist/core/storageService.d.ts.map +1 -0
  41. package/dist/core/storageService.js +405 -0
  42. package/dist/core/storageService.js.map +1 -0
  43. package/dist/desktop/bundler.d.ts +40 -0
  44. package/dist/desktop/bundler.d.ts.map +1 -0
  45. package/dist/desktop/bundler.js +176 -0
  46. package/dist/desktop/bundler.js.map +1 -0
  47. package/dist/desktop/index.d.ts +25 -0
  48. package/dist/desktop/index.d.ts.map +1 -0
  49. package/dist/desktop/index.js +15 -0
  50. package/dist/desktop/index.js.map +1 -0
  51. package/dist/desktop/native-modules.d.ts +66 -0
  52. package/dist/desktop/native-modules.d.ts.map +1 -0
  53. package/dist/desktop/native-modules.js +200 -0
  54. package/dist/desktop/native-modules.js.map +1 -0
  55. package/dist/index.d.ts +29 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +39 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/logging/LogCategories.d.ts +87 -0
  60. package/dist/logging/LogCategories.d.ts.map +1 -0
  61. package/dist/logging/LogCategories.js +205 -0
  62. package/dist/logging/LogCategories.js.map +1 -0
  63. package/dist/middleware/aiErrorHandler.d.ts +31 -0
  64. package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
  65. package/dist/middleware/aiErrorHandler.js +181 -0
  66. package/dist/middleware/aiErrorHandler.js.map +1 -0
  67. package/dist/middleware/auth.d.ts +101 -0
  68. package/dist/middleware/auth.d.ts.map +1 -0
  69. package/dist/middleware/auth.js +230 -0
  70. package/dist/middleware/auth.js.map +1 -0
  71. package/dist/middleware/cors.d.ts +56 -0
  72. package/dist/middleware/cors.d.ts.map +1 -0
  73. package/dist/middleware/cors.js +123 -0
  74. package/dist/middleware/cors.js.map +1 -0
  75. package/dist/middleware/errorHandler.d.ts +13 -0
  76. package/dist/middleware/errorHandler.d.ts.map +1 -0
  77. package/dist/middleware/errorHandler.js +85 -0
  78. package/dist/middleware/errorHandler.js.map +1 -0
  79. package/dist/middleware/fileUpload.d.ts +62 -0
  80. package/dist/middleware/fileUpload.d.ts.map +1 -0
  81. package/dist/middleware/fileUpload.js +175 -0
  82. package/dist/middleware/fileUpload.js.map +1 -0
  83. package/dist/middleware/health.d.ts +48 -0
  84. package/dist/middleware/health.d.ts.map +1 -0
  85. package/dist/middleware/health.js +143 -0
  86. package/dist/middleware/health.js.map +1 -0
  87. package/dist/middleware/index.d.ts +20 -0
  88. package/dist/middleware/index.d.ts.map +1 -0
  89. package/dist/middleware/index.js +18 -0
  90. package/dist/middleware/index.js.map +1 -0
  91. package/dist/middleware/openapi.d.ts +64 -0
  92. package/dist/middleware/openapi.d.ts.map +1 -0
  93. package/dist/middleware/openapi.js +258 -0
  94. package/dist/middleware/openapi.js.map +1 -0
  95. package/dist/middleware/requestLogging.d.ts +22 -0
  96. package/dist/middleware/requestLogging.d.ts.map +1 -0
  97. package/dist/middleware/requestLogging.js +61 -0
  98. package/dist/middleware/requestLogging.js.map +1 -0
  99. package/dist/middleware/session.d.ts +84 -0
  100. package/dist/middleware/session.d.ts.map +1 -0
  101. package/dist/middleware/session.js +189 -0
  102. package/dist/middleware/session.js.map +1 -0
  103. package/dist/middleware/validation.d.ts +1337 -0
  104. package/dist/middleware/validation.d.ts.map +1 -0
  105. package/dist/middleware/validation.js +483 -0
  106. package/dist/middleware/validation.js.map +1 -0
  107. package/dist/services/aiService.d.ts +180 -0
  108. package/dist/services/aiService.d.ts.map +1 -0
  109. package/dist/services/aiService.js +547 -0
  110. package/dist/services/aiService.js.map +1 -0
  111. package/dist/services/conversationStorage.d.ts +38 -0
  112. package/dist/services/conversationStorage.d.ts.map +1 -0
  113. package/dist/services/conversationStorage.js +158 -0
  114. package/dist/services/conversationStorage.js.map +1 -0
  115. package/dist/services/crossPlatformBuffer.d.ts +84 -0
  116. package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
  117. package/dist/services/crossPlatformBuffer.js +246 -0
  118. package/dist/services/crossPlatformBuffer.js.map +1 -0
  119. package/dist/services/index.d.ts +17 -0
  120. package/dist/services/index.d.ts.map +1 -0
  121. package/dist/services/index.js +18 -0
  122. package/dist/services/index.js.map +1 -0
  123. package/dist/services/networkService.d.ts +81 -0
  124. package/dist/services/networkService.d.ts.map +1 -0
  125. package/dist/services/networkService.js +268 -0
  126. package/dist/services/networkService.js.map +1 -0
  127. package/dist/services/queueService.d.ts +112 -0
  128. package/dist/services/queueService.d.ts.map +1 -0
  129. package/dist/services/queueService.js +338 -0
  130. package/dist/services/queueService.js.map +1 -0
  131. package/dist/services/settingsService.d.ts +135 -0
  132. package/dist/services/settingsService.d.ts.map +1 -0
  133. package/dist/services/settingsService.js +425 -0
  134. package/dist/services/settingsService.js.map +1 -0
  135. package/dist/services/systemMonitor.d.ts +208 -0
  136. package/dist/services/systemMonitor.d.ts.map +1 -0
  137. package/dist/services/systemMonitor.js +693 -0
  138. package/dist/services/systemMonitor.js.map +1 -0
  139. package/dist/services/updateService.d.ts +78 -0
  140. package/dist/services/updateService.d.ts.map +1 -0
  141. package/dist/services/updateService.js +252 -0
  142. package/dist/services/updateService.js.map +1 -0
  143. package/dist/services/websocketEvents.d.ts +372 -0
  144. package/dist/services/websocketEvents.d.ts.map +1 -0
  145. package/dist/services/websocketEvents.js +338 -0
  146. package/dist/services/websocketEvents.js.map +1 -0
  147. package/dist/services/websocketServer.d.ts +80 -0
  148. package/dist/services/websocketServer.d.ts.map +1 -0
  149. package/dist/services/websocketServer.js +299 -0
  150. package/dist/services/websocketServer.js.map +1 -0
  151. package/dist/settings/SettingsSchema.d.ts +151 -0
  152. package/dist/settings/SettingsSchema.d.ts.map +1 -0
  153. package/dist/settings/SettingsSchema.js +424 -0
  154. package/dist/settings/SettingsSchema.js.map +1 -0
  155. package/dist/testing/TestServer.d.ts +69 -0
  156. package/dist/testing/TestServer.d.ts.map +1 -0
  157. package/dist/testing/TestServer.js +250 -0
  158. package/dist/testing/TestServer.js.map +1 -0
  159. package/dist/types/index.d.ts +137 -0
  160. package/dist/types/index.d.ts.map +1 -0
  161. package/dist/types/index.js +5 -0
  162. package/dist/types/index.js.map +1 -0
  163. package/dist/utils/appPaths.d.ts +74 -0
  164. package/dist/utils/appPaths.d.ts.map +1 -0
  165. package/dist/utils/appPaths.js +162 -0
  166. package/dist/utils/appPaths.js.map +1 -0
  167. package/dist/utils/fs-utils.d.ts +50 -0
  168. package/dist/utils/fs-utils.d.ts.map +1 -0
  169. package/dist/utils/fs-utils.js +114 -0
  170. package/dist/utils/fs-utils.js.map +1 -0
  171. package/dist/utils/index.d.ts +12 -0
  172. package/dist/utils/index.d.ts.map +1 -0
  173. package/dist/utils/index.js +10 -0
  174. package/dist/utils/index.js.map +1 -0
  175. package/dist/utils/standardConfig.d.ts +61 -0
  176. package/dist/utils/standardConfig.d.ts.map +1 -0
  177. package/dist/utils/standardConfig.js +109 -0
  178. package/dist/utils/standardConfig.js.map +1 -0
  179. package/dist/utils/startupBanner.d.ts +34 -0
  180. package/dist/utils/startupBanner.d.ts.map +1 -0
  181. package/dist/utils/startupBanner.js +169 -0
  182. package/dist/utils/startupBanner.js.map +1 -0
  183. package/dist/utils/startupLogger.d.ts +45 -0
  184. package/dist/utils/startupLogger.d.ts.map +1 -0
  185. package/dist/utils/startupLogger.js +200 -0
  186. package/dist/utils/startupLogger.js.map +1 -0
  187. package/package.json +151 -0
  188. package/src/api/logsRouter.ts +600 -0
  189. package/src/cli/dev-server.ts +803 -0
  190. package/src/cli/index.ts +31 -0
  191. package/src/core/StandardServer.ts +587 -0
  192. package/src/core/apiResponse.ts +202 -0
  193. package/src/core/healthCheck.ts +565 -0
  194. package/src/core/index.ts +80 -0
  195. package/src/core/logger.ts +1092 -0
  196. package/src/core/portUtils.ts +319 -0
  197. package/src/core/storageService.ts +595 -0
  198. package/src/desktop/bundler.ts +271 -0
  199. package/src/desktop/index.ts +18 -0
  200. package/src/desktop/native-modules.ts +289 -0
  201. package/src/index.ts +142 -0
  202. package/src/logging/LogCategories.ts +302 -0
  203. package/src/middleware/aiErrorHandler.ts +278 -0
  204. package/src/middleware/auth.ts +329 -0
  205. package/src/middleware/cors.ts +187 -0
  206. package/src/middleware/errorHandler.ts +103 -0
  207. package/src/middleware/fileUpload.ts +252 -0
  208. package/src/middleware/health.ts +206 -0
  209. package/src/middleware/index.ts +71 -0
  210. package/src/middleware/openapi.ts +305 -0
  211. package/src/middleware/requestLogging.ts +92 -0
  212. package/src/middleware/session.ts +238 -0
  213. package/src/middleware/validation.ts +603 -0
  214. package/src/services/aiService.ts +789 -0
  215. package/src/services/conversationStorage.ts +232 -0
  216. package/src/services/crossPlatformBuffer.ts +341 -0
  217. package/src/services/index.ts +47 -0
  218. package/src/services/networkService.ts +351 -0
  219. package/src/services/queueService.ts +446 -0
  220. package/src/services/settingsService.ts +549 -0
  221. package/src/services/systemMonitor.ts +936 -0
  222. package/src/services/updateService.ts +334 -0
  223. package/src/services/websocketEvents.ts +409 -0
  224. package/src/services/websocketServer.ts +394 -0
  225. package/src/settings/SettingsSchema.ts +664 -0
  226. package/src/testing/TestServer.ts +312 -0
  227. package/src/types/index.ts +154 -0
  228. package/src/utils/appPaths.ts +196 -0
  229. package/src/utils/fs-utils.ts +130 -0
  230. package/src/utils/index.ts +15 -0
  231. package/src/utils/standardConfig.ts +178 -0
  232. package/src/utils/startupBanner.ts +287 -0
  233. package/src/utils/startupLogger.ts +268 -0
  234. package/ui/dist/index.d.mts +1221 -0
  235. package/ui/dist/index.d.ts +1221 -0
  236. package/ui/dist/index.js +73 -0
  237. package/ui/dist/index.js.map +1 -0
  238. package/ui/dist/index.mjs +73 -0
  239. package/ui/dist/index.mjs.map +1 -0
@@ -0,0 +1,603 @@
1
+ /**
2
+ * Validation Middleware
3
+ * Request validation using Zod schemas with TypeScript support
4
+ *
5
+ * Features:
6
+ * - Request body, params, and query validation
7
+ * - Detailed field-level error reporting
8
+ * - Custom error messages
9
+ * - Async validation support
10
+ * - Schema composition and reuse
11
+ * - Full TypeScript type inference
12
+ */
13
+
14
+ import { z, ZodSchema, ZodError } from "zod";
15
+ import { Request, Response, NextFunction } from "express";
16
+ import { ApiResponse, FieldValidationError } from "../types/index.js";
17
+
18
+ /**
19
+ * Validation options for middleware
20
+ */
21
+ export interface ValidationOptions {
22
+ abortEarly?: boolean;
23
+ stripUnknown?: boolean;
24
+ convert?: boolean;
25
+ presence?: "optional" | "required" | "forbidden";
26
+ context?: Record<string, any>;
27
+ allowUnknown?: boolean;
28
+ }
29
+
30
+ /**
31
+ * Default validation options
32
+ */
33
+ const defaultOptions: ValidationOptions = {
34
+ abortEarly: false,
35
+ stripUnknown: true,
36
+ convert: true,
37
+ allowUnknown: false,
38
+ };
39
+
40
+ /**
41
+ * Format validation errors into a consistent structure
42
+ */
43
+ function formatValidationErrors(error: ZodError): FieldValidationError[] {
44
+ return error.errors.map((err) => ({
45
+ field: err.path.join("."),
46
+ message: err.message,
47
+ }));
48
+ }
49
+
50
+ /**
51
+ * Create a user-friendly error message
52
+ */
53
+ function createErrorMessage(
54
+ errors: FieldValidationError[],
55
+ context: string,
56
+ ): string {
57
+ if (errors.length === 1) {
58
+ return `${context}: ${errors[0].message}`;
59
+ }
60
+
61
+ const fieldList = errors.map((e) => e.field).join(", ");
62
+ return `${context}: Multiple validation errors in fields: ${fieldList}`;
63
+ }
64
+
65
+ /**
66
+ * Validation schemas for different endpoints
67
+ */
68
+ export const schemas = {
69
+ // Template validation
70
+ template: z.object({
71
+ id: z
72
+ .string()
73
+ .regex(
74
+ /^[a-zA-Z0-9_-]+$/,
75
+ "ID must contain only letters, numbers, underscores, and hyphens",
76
+ ),
77
+ name: z.string().min(1, "Name is required").max(100),
78
+ description: z.string().max(500).optional(),
79
+ manufacturer: z.string().min(1, "Manufacturer is required"),
80
+ model: z.string().optional(),
81
+ protocol: z.enum(["modbus", "bacnet"]).default("modbus"),
82
+ metadata: z
83
+ .object({
84
+ manufacturer: z.string().optional(),
85
+ models: z
86
+ .array(
87
+ z.object({
88
+ name: z.string(),
89
+ variants: z.array(z.string()).optional(),
90
+ }),
91
+ )
92
+ .optional(),
93
+ category: z.string().optional(),
94
+ tags: z.array(z.string()).optional(),
95
+ source: z.string().optional(),
96
+ lastUpdated: z.string().datetime().optional(),
97
+ version: z.string().optional(),
98
+ author: z.string().optional(),
99
+ })
100
+ .optional(),
101
+ data_points: z
102
+ .array(
103
+ z.object({
104
+ name: z.string(),
105
+ address: z.number().int().min(0).max(65535),
106
+ type: z.enum(["coil", "discrete", "holding", "input"]),
107
+ dataType: z.enum([
108
+ "uint16",
109
+ "int16",
110
+ "uint32",
111
+ "int32",
112
+ "float32",
113
+ "string",
114
+ "bool",
115
+ "bits",
116
+ ]),
117
+ writable: z.boolean().default(false),
118
+ unit: z.string().optional(),
119
+ min: z.number().optional(),
120
+ max: z.number().optional(),
121
+ scaling_factor: z.number().default(1),
122
+ description: z.string().optional(),
123
+ simulation_type: z
124
+ .enum(["static", "random", "csv", "function"])
125
+ .optional(),
126
+ initial_value: z.any().optional(),
127
+ csv_file: z.string().optional(),
128
+ function_code: z.string().optional(),
129
+ update_interval: z.number().int().min(100).optional(),
130
+ }),
131
+ )
132
+ .min(1, "At least one data point is required"),
133
+ devices: z
134
+ .array(
135
+ z.object({
136
+ name: z.string(),
137
+ device_type: z.string(),
138
+ description: z.string().optional(),
139
+ unit_id: z.number().int().min(1).max(247).optional(),
140
+ data_points: z.array(z.any()).optional(),
141
+ }),
142
+ )
143
+ .optional(),
144
+ attachments: z
145
+ .array(
146
+ z.object({
147
+ id: z.string(),
148
+ name: z.string(),
149
+ size: z.number(),
150
+ mimeType: z.string(),
151
+ uploadedAt: z.string().datetime(),
152
+ }),
153
+ )
154
+ .optional(),
155
+ intelligent: z.boolean().optional(),
156
+ state_machine: z
157
+ .object({
158
+ states: z.array(
159
+ z.object({
160
+ name: z.string(),
161
+ values: z.record(z.any()),
162
+ transitions: z.array(z.any()),
163
+ }),
164
+ ),
165
+ initial_state: z.string(),
166
+ current_state: z.string().optional(),
167
+ })
168
+ .optional(),
169
+ data_links: z
170
+ .array(
171
+ z.object({
172
+ source: z.string(),
173
+ targets: z.array(z.string()),
174
+ transform: z.string().optional(),
175
+ }),
176
+ )
177
+ .optional(),
178
+ }),
179
+
180
+ // Template update validation (partial)
181
+ templateUpdate: z
182
+ .object({
183
+ name: z.string().min(1).max(100).optional(),
184
+ description: z.string().max(500).optional(),
185
+ manufacturer: z.string().min(1).optional(),
186
+ model: z.string().optional(),
187
+ metadata: z.any().optional(),
188
+ data_points: z.array(z.any()).optional(),
189
+ devices: z.array(z.any()).optional(),
190
+ attachments: z.array(z.any()).optional(),
191
+ intelligent: z.boolean().optional(),
192
+ state_machine: z.any().optional(),
193
+ data_links: z.array(z.any()).optional(),
194
+ })
195
+ .refine((obj) => Object.keys(obj).length > 0, {
196
+ message: "At least one field must be provided for update",
197
+ }),
198
+
199
+ // Simulator validation
200
+ simulator: z.object({
201
+ id: z.string().uuid(),
202
+ name: z.string().min(1).max(100),
203
+ templateId: z.string(),
204
+ config: z.object({
205
+ port: z.number().int().min(1).max(65535),
206
+ host: z.string().default("0.0.0.0"),
207
+ unitId: z.number().int().min(1).max(247).default(1),
208
+ protocol: z.enum(["modbus", "bacnet"]),
209
+ }),
210
+ autoStart: z.boolean().default(false),
211
+ }),
212
+
213
+ // Settings validation
214
+ settings: z.object({
215
+ api: z
216
+ .object({
217
+ port: z.number().int().min(1024).max(65535),
218
+ host: z.string(),
219
+ cors: z.object({
220
+ enabled: z.boolean(),
221
+ origins: z.array(z.string()),
222
+ }),
223
+ })
224
+ .optional(),
225
+ simulator: z
226
+ .object({
227
+ defaultPort: z.number().int().min(1024).max(65535),
228
+ autoRestart: z.boolean(),
229
+ maxInstances: z.number().int().min(1).max(100),
230
+ })
231
+ .optional(),
232
+ logging: z
233
+ .object({
234
+ level: z.enum(["error", "warn", "info", "debug"]),
235
+ file: z.boolean(),
236
+ console: z.boolean(),
237
+ maxSize: z.string().regex(/^\d+[kmg]b?$/i),
238
+ maxFiles: z.string(),
239
+ })
240
+ .optional(),
241
+ ui: z
242
+ .object({
243
+ theme: z.enum(["light", "dark", "auto"]),
244
+ language: z.enum(["en", "es", "fr", "de"]),
245
+ dateFormat: z.string(),
246
+ })
247
+ .optional(),
248
+ }),
249
+
250
+ // Common ID validation
251
+ id: z.object({
252
+ id: z.string().min(1, "ID is required"),
253
+ }),
254
+
255
+ // Pagination validation
256
+ pagination: z.object({
257
+ page: z.number().int().min(1).default(1),
258
+ limit: z.number().int().min(1).max(100).default(20),
259
+ sort: z.string().optional(),
260
+ order: z.enum(["asc", "desc"]).default("asc"),
261
+ }),
262
+
263
+ // Search/filter validation
264
+ filter: z.object({
265
+ search: z.string().max(100).optional(),
266
+ tags: z.array(z.string()).optional(),
267
+ category: z.string().optional(),
268
+ protocol: z.enum(["modbus", "bacnet"]).optional(),
269
+ status: z.enum(["active", "inactive", "error"]).optional(),
270
+ }),
271
+ };
272
+
273
+ /**
274
+ * Create a validation middleware for request body
275
+ */
276
+ export function validate<T extends ZodSchema>(
277
+ schema: T,
278
+ options?: ValidationOptions,
279
+ ) {
280
+ const opts = { ...defaultOptions, ...options };
281
+
282
+ return (
283
+ req: Request,
284
+ res: Response<ApiResponse<any>>,
285
+ next: NextFunction,
286
+ ): any => {
287
+ try {
288
+ const value = schema.parse(req.body);
289
+
290
+ // Replace request body with validated and sanitized value
291
+ if (opts.stripUnknown) {
292
+ req.body = value;
293
+ }
294
+ next();
295
+ } catch (_error) {
296
+ if (_error instanceof ZodError) {
297
+ const errors = formatValidationErrors(_error);
298
+ const message = createErrorMessage(errors, "Invalid request");
299
+
300
+ return res.status(400).json({
301
+ success: false,
302
+ error: "Validation failed",
303
+ message,
304
+ errors,
305
+ timestamp: new Date().toISOString(),
306
+ });
307
+ }
308
+ next(_error);
309
+ }
310
+ };
311
+ }
312
+
313
+ /**
314
+ * Validate request parameters
315
+ */
316
+ export function validateParams<T extends ZodSchema>(
317
+ schema: T,
318
+ options?: ValidationOptions,
319
+ ) {
320
+ const opts = { ...defaultOptions, ...options };
321
+
322
+ return (
323
+ req: Request,
324
+ res: Response<ApiResponse<any>>,
325
+ next: NextFunction,
326
+ ): any => {
327
+ try {
328
+ const value = schema.parse(req.params);
329
+
330
+ if (opts.stripUnknown) {
331
+ req.params = value;
332
+ }
333
+ next();
334
+ } catch (_error) {
335
+ if (_error instanceof ZodError) {
336
+ const errors = formatValidationErrors(_error);
337
+ const message = createErrorMessage(errors, "Invalid parameters");
338
+
339
+ return res.status(400).json({
340
+ success: false,
341
+ error: "Invalid parameters",
342
+ message,
343
+ errors,
344
+ timestamp: new Date().toISOString(),
345
+ });
346
+ }
347
+ next(_error);
348
+ }
349
+ };
350
+ }
351
+
352
+ /**
353
+ * Validate query parameters
354
+ */
355
+ export function validateQuery<T extends ZodSchema>(
356
+ schema: T,
357
+ options?: ValidationOptions,
358
+ ) {
359
+ const opts = { ...defaultOptions, ...options };
360
+
361
+ return (
362
+ req: Request,
363
+ res: Response<ApiResponse<any>>,
364
+ next: NextFunction,
365
+ ): any => {
366
+ try {
367
+ const value = schema.parse(req.query);
368
+
369
+ if (opts.stripUnknown) {
370
+ req.query = value;
371
+ }
372
+ next();
373
+ } catch (_error) {
374
+ if (_error instanceof ZodError) {
375
+ const errors = formatValidationErrors(_error);
376
+ const message = createErrorMessage(errors, "Invalid query");
377
+
378
+ return res.status(400).json({
379
+ success: false,
380
+ error: "Invalid query parameters",
381
+ message,
382
+ errors,
383
+ timestamp: new Date().toISOString(),
384
+ });
385
+ }
386
+ next(_error);
387
+ }
388
+ };
389
+ }
390
+
391
+ /**
392
+ * Combined validation for body, params, and query
393
+ */
394
+ export function validateRequest<
395
+ T extends {
396
+ body?: ZodSchema;
397
+ params?: ZodSchema;
398
+ query?: ZodSchema;
399
+ },
400
+ >(schemas: T, options?: ValidationOptions) {
401
+ const opts = { ...defaultOptions, ...options };
402
+
403
+ return (
404
+ req: Request,
405
+ res: Response<ApiResponse<any>>,
406
+ next: NextFunction,
407
+ ): any => {
408
+ const errors: FieldValidationError[] = [];
409
+
410
+ // Validate body
411
+ if (schemas.body) {
412
+ try {
413
+ const value = schemas.body.parse(req.body);
414
+ if (opts.stripUnknown) {
415
+ req.body = value;
416
+ }
417
+ } catch (_error) {
418
+ if (_error instanceof ZodError) {
419
+ errors.push(...formatValidationErrors(_error));
420
+ }
421
+ }
422
+ }
423
+
424
+ // Validate params
425
+ if (schemas.params) {
426
+ try {
427
+ const value = schemas.params.parse(req.params);
428
+ if (opts.stripUnknown) {
429
+ req.params = value;
430
+ }
431
+ } catch (_error) {
432
+ if (_error instanceof ZodError) {
433
+ errors.push(...formatValidationErrors(_error));
434
+ }
435
+ }
436
+ }
437
+
438
+ // Validate query
439
+ if (schemas.query) {
440
+ try {
441
+ const value = schemas.query.parse(req.query);
442
+ if (opts.stripUnknown) {
443
+ req.query = value;
444
+ }
445
+ } catch (_error) {
446
+ if (_error instanceof ZodError) {
447
+ errors.push(...formatValidationErrors(_error));
448
+ }
449
+ }
450
+ }
451
+
452
+ if (errors.length > 0) {
453
+ const message = createErrorMessage(errors, "Request validation failed");
454
+
455
+ return res.status(400).json({
456
+ success: false,
457
+ error: "Validation failed",
458
+ message,
459
+ errors,
460
+ timestamp: new Date().toISOString(),
461
+ });
462
+ }
463
+
464
+ next();
465
+ };
466
+ }
467
+
468
+ /**
469
+ * Async validation wrapper for custom validation logic
470
+ */
471
+ export function validateAsync(validationFn: (req: Request) => Promise<any>) {
472
+ return async (
473
+ req: Request,
474
+ res: Response<ApiResponse<any>>,
475
+ next: NextFunction,
476
+ ) => {
477
+ try {
478
+ await validationFn(req);
479
+ next();
480
+ } catch (_error: any) {
481
+ const validationError: FieldValidationError = {
482
+ field: _error.field || "unknown",
483
+ message: _error.message || "Validation failed",
484
+ };
485
+
486
+ res.status(400).json({
487
+ success: false,
488
+ error: "Validation failed",
489
+ message: _error.message,
490
+ errors: [validationError],
491
+ timestamp: new Date().toISOString(),
492
+ });
493
+ }
494
+ };
495
+ }
496
+
497
+ /**
498
+ * Create a conditional validation middleware
499
+ */
500
+ export function validateIf<T extends ZodSchema>(
501
+ condition: (req: Request) => boolean,
502
+ schema: T,
503
+ options?: ValidationOptions,
504
+ ) {
505
+ return (
506
+ req: Request,
507
+ res: Response<ApiResponse<any>>,
508
+ next: NextFunction,
509
+ ): any => {
510
+ if (condition(req)) {
511
+ return validate(schema, options)(req, res, next);
512
+ }
513
+ next();
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Schema composition helpers using Zod's built-in methods
519
+ */
520
+ export const compose = {
521
+ /**
522
+ * Merge multiple schemas
523
+ */
524
+ merge: <T extends ZodSchema, U extends ZodSchema>(schema1: T, schema2: U) => {
525
+ if (schema1 instanceof z.ZodObject && schema2 instanceof z.ZodObject) {
526
+ return schema1.merge(schema2);
527
+ }
528
+ return z.intersection(schema1, schema2);
529
+ },
530
+
531
+ /**
532
+ * Make all fields optional
533
+ */
534
+ partial: <T extends z.ZodObject<any>>(schema: T) => {
535
+ return schema.partial();
536
+ },
537
+
538
+ /**
539
+ * Make specific fields required
540
+ */
541
+ require: <T extends z.ZodObject<any>>(
542
+ schema: T,
543
+ fields: Array<keyof T["shape"]>,
544
+ ) => {
545
+ const partialSchema = schema.partial();
546
+ const requiredOverrides: any = {};
547
+ fields.forEach((field) => {
548
+ const fieldSchema = schema.shape[field as string];
549
+ if (fieldSchema) {
550
+ requiredOverrides[field] = fieldSchema;
551
+ }
552
+ });
553
+ return partialSchema.extend(requiredOverrides);
554
+ },
555
+
556
+ /**
557
+ * Pick specific fields from a schema
558
+ */
559
+ pick: <T extends z.ZodObject<any>>(
560
+ schema: T,
561
+ fields: Array<keyof T["shape"]>,
562
+ ) => {
563
+ const picked: any = {};
564
+ fields.forEach((field) => {
565
+ if (schema.shape[field as string]) {
566
+ picked[field] = schema.shape[field as string];
567
+ }
568
+ });
569
+ return z.object(picked);
570
+ },
571
+
572
+ /**
573
+ * Omit specific fields from a schema
574
+ */
575
+ omit: <T extends z.ZodObject<any>>(
576
+ schema: T,
577
+ fields: Array<keyof T["shape"]>,
578
+ ) => {
579
+ return schema.omit(
580
+ fields.reduce((acc, field) => {
581
+ acc[field] = true;
582
+ return acc;
583
+ }, {} as any),
584
+ );
585
+ },
586
+ };
587
+
588
+ /**
589
+ * Export Zod for direct use
590
+ */
591
+ export { z, z as zod };
592
+
593
+ export default {
594
+ validate,
595
+ validateParams,
596
+ validateQuery,
597
+ validateRequest,
598
+ validateAsync,
599
+ validateIf,
600
+ schemas,
601
+ compose,
602
+ z,
603
+ };