exisjs 0.7.5 → 0.7.6

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 (164) hide show
  1. package/dist/cli/commands/dev.d.ts.map +1 -1
  2. package/dist/cli/commands/dev.js +156 -85
  3. package/dist/cli/commands/dev.js.map +1 -1
  4. package/dist/cli/commands/start.d.ts.map +1 -1
  5. package/dist/cli/commands/start.js +54 -16
  6. package/dist/cli/commands/start.js.map +1 -1
  7. package/dist/cli/index.js +10 -1
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/manifest.d.ts.map +1 -1
  10. package/dist/cli/manifest.js +106 -13
  11. package/dist/cli/manifest.js.map +1 -1
  12. package/dist/cli/resolve-aliases.d.ts +1 -1
  13. package/dist/cli/resolve-aliases.d.ts.map +1 -1
  14. package/dist/cli/resolve-aliases.js +30 -13
  15. package/dist/cli/resolve-aliases.js.map +1 -1
  16. package/dist/config/config.d.ts +3 -1
  17. package/dist/config/config.d.ts.map +1 -1
  18. package/dist/config/config.js.map +1 -1
  19. package/dist/config/types.d.ts +2 -0
  20. package/dist/config/types.d.ts.map +1 -1
  21. package/dist/decorators/middleware.d.ts +65 -2
  22. package/dist/decorators/middleware.d.ts.map +1 -1
  23. package/dist/decorators/middleware.js +148 -62
  24. package/dist/decorators/middleware.js.map +1 -1
  25. package/dist/decorators/params.d.ts +5 -0
  26. package/dist/decorators/params.d.ts.map +1 -1
  27. package/dist/decorators/params.js +11 -1
  28. package/dist/decorators/params.js.map +1 -1
  29. package/dist/decorators/response.d.ts.map +1 -1
  30. package/dist/decorators/response.js +32 -22
  31. package/dist/decorators/response.js.map +1 -1
  32. package/dist/di/container.d.ts.map +1 -1
  33. package/dist/di/container.js +6 -1
  34. package/dist/di/container.js.map +1 -1
  35. package/dist/docs/content/cli/commands.mdx +1 -1
  36. package/dist/docs/content/controllers.mdx +127 -10
  37. package/dist/docs/content/decorators.mdx +22 -4
  38. package/dist/docs/content/introduction.mdx +7 -7
  39. package/dist/docs/content/middleware.mdx +269 -6
  40. package/dist/docs/content/requests.mdx +180 -6
  41. package/dist/docs/content/responses.mdx +283 -32
  42. package/dist/docs/content/routing.mdx +46 -0
  43. package/dist/docs/content/validation.mdx +190 -11
  44. package/dist/docs/reference/context.mdx +11 -0
  45. package/dist/docs/reference/decorators.mdx +14 -4
  46. package/dist/docs/reference/error.mdx +44 -0
  47. package/dist/docs/reference/logger.mdx +175 -43
  48. package/dist/docs/reference/middleware.mdx +31 -0
  49. package/dist/docs/reference/router.mdx +22 -0
  50. package/dist/docs/reference/validator.mdx +83 -23
  51. package/dist/error/errors.d.ts.map +1 -1
  52. package/dist/error/errors.js +95 -43
  53. package/dist/error/errors.js.map +1 -1
  54. package/dist/error/index.d.ts +1 -0
  55. package/dist/error/index.d.ts.map +1 -1
  56. package/dist/error/index.js +11 -1
  57. package/dist/error/index.js.map +1 -1
  58. package/dist/error/overlay.d.ts +42 -0
  59. package/dist/error/overlay.d.ts.map +1 -0
  60. package/dist/error/overlay.js +306 -0
  61. package/dist/error/overlay.js.map +1 -0
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +22 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/lib/start-server.js +33 -7
  67. package/dist/lib/start-server.js.map +1 -1
  68. package/dist/logger/index.d.ts +1 -1
  69. package/dist/logger/index.d.ts.map +1 -1
  70. package/dist/logger/index.js +9 -1
  71. package/dist/logger/index.js.map +1 -1
  72. package/dist/middleware/guard.d.ts +2 -2
  73. package/dist/middleware/guard.d.ts.map +1 -1
  74. package/dist/middleware/guard.js.map +1 -1
  75. package/dist/middleware/ip-filter.d.ts +1 -0
  76. package/dist/middleware/ip-filter.d.ts.map +1 -1
  77. package/dist/middleware/ip-filter.js +2 -0
  78. package/dist/middleware/ip-filter.js.map +1 -1
  79. package/dist/middleware/middleware.d.ts +2 -2
  80. package/dist/middleware/middleware.d.ts.map +1 -1
  81. package/dist/middleware/middleware.js +20 -2
  82. package/dist/middleware/middleware.js.map +1 -1
  83. package/dist/middleware/security.d.ts +43 -1
  84. package/dist/middleware/security.d.ts.map +1 -1
  85. package/dist/middleware/security.js +101 -0
  86. package/dist/middleware/security.js.map +1 -1
  87. package/dist/response/index.d.ts +229 -6
  88. package/dist/response/index.d.ts.map +1 -1
  89. package/dist/response/index.js +272 -2
  90. package/dist/response/index.js.map +1 -1
  91. package/dist/router/boundary.d.ts +17 -9
  92. package/dist/router/boundary.d.ts.map +1 -1
  93. package/dist/router/boundary.js.map +1 -1
  94. package/dist/router/controller-registrar.d.ts.map +1 -1
  95. package/dist/router/controller-registrar.js +56 -1
  96. package/dist/router/controller-registrar.js.map +1 -1
  97. package/dist/router/index.d.ts +2 -2
  98. package/dist/router/index.d.ts.map +1 -1
  99. package/dist/router/index.js +2 -1
  100. package/dist/router/index.js.map +1 -1
  101. package/dist/router/route-builder.d.ts +70 -36
  102. package/dist/router/route-builder.d.ts.map +1 -1
  103. package/dist/router/route-builder.js +13 -15
  104. package/dist/router/route-builder.js.map +1 -1
  105. package/dist/router/route-scanner.d.ts.map +1 -1
  106. package/dist/router/route-scanner.js +24 -2
  107. package/dist/router/route-scanner.js.map +1 -1
  108. package/dist/router/router.d.ts.map +1 -1
  109. package/dist/router/router.js +35 -17
  110. package/dist/router/router.js.map +1 -1
  111. package/dist/router/types.d.ts +8 -0
  112. package/dist/router/types.d.ts.map +1 -1
  113. package/dist/server/app.d.ts +1 -0
  114. package/dist/server/app.d.ts.map +1 -1
  115. package/dist/server/app.js +10 -0
  116. package/dist/server/app.js.map +1 -1
  117. package/dist/server/bootstrapper.d.ts.map +1 -1
  118. package/dist/server/bootstrapper.js +91 -30
  119. package/dist/server/bootstrapper.js.map +1 -1
  120. package/dist/server/cluster.d.ts.map +1 -1
  121. package/dist/server/cluster.js +13 -2
  122. package/dist/server/cluster.js.map +1 -1
  123. package/dist/server/context.d.ts +8 -0
  124. package/dist/server/context.d.ts.map +1 -1
  125. package/dist/server/context.js +50 -6
  126. package/dist/server/context.js.map +1 -1
  127. package/dist/server/lifecycle.d.ts +22 -0
  128. package/dist/server/lifecycle.d.ts.map +1 -0
  129. package/dist/server/lifecycle.js +106 -0
  130. package/dist/server/lifecycle.js.map +1 -0
  131. package/dist/server/request-handler.d.ts +1 -0
  132. package/dist/server/request-handler.d.ts.map +1 -1
  133. package/dist/server/request-handler.js +27 -17
  134. package/dist/server/request-handler.js.map +1 -1
  135. package/dist/server/request.d.ts.map +1 -1
  136. package/dist/server/request.js +18 -0
  137. package/dist/server/request.js.map +1 -1
  138. package/dist/server/response.d.ts +10 -1
  139. package/dist/server/response.d.ts.map +1 -1
  140. package/dist/server/response.js +154 -2
  141. package/dist/server/response.js.map +1 -1
  142. package/dist/testing/index.d.ts.map +1 -1
  143. package/dist/testing/index.js +6 -1
  144. package/dist/testing/index.js.map +1 -1
  145. package/dist/testing/mocks.d.ts.map +1 -1
  146. package/dist/testing/mocks.js +15 -3
  147. package/dist/testing/mocks.js.map +1 -1
  148. package/dist/utils/logger.d.ts +8 -0
  149. package/dist/utils/logger.d.ts.map +1 -1
  150. package/dist/utils/logger.js +184 -1
  151. package/dist/utils/logger.js.map +1 -1
  152. package/dist/validator/tex-types.d.ts +2 -1
  153. package/dist/validator/tex-types.d.ts.map +1 -1
  154. package/dist/validator/tex-types.js +2 -1
  155. package/dist/validator/tex-types.js.map +1 -1
  156. package/dist/validator/tex.d.ts +31 -15
  157. package/dist/validator/tex.d.ts.map +1 -1
  158. package/dist/validator/tex.js +280 -42
  159. package/dist/validator/tex.js.map +1 -1
  160. package/package.json +4 -4
  161. package/dist/dev/error-overlay.d.ts +0 -3
  162. package/dist/dev/error-overlay.d.ts.map +0 -1
  163. package/dist/dev/error-overlay.js +0 -118
  164. package/dist/dev/error-overlay.js.map +0 -1
@@ -8,7 +8,7 @@ const tex_types_1 = require("./tex-types");
8
8
  class TexBuilder {
9
9
  string(opts) {
10
10
  let base = 'string';
11
- if (opts?.optional)
11
+ if (opts?.optional || opts?.default !== undefined)
12
12
  base += '?';
13
13
  if (opts?.nullable)
14
14
  base += ' | nullable';
@@ -38,11 +38,11 @@ class TexBuilder {
38
38
  base += ' | preventSql';
39
39
  if (opts?.preventTraversal)
40
40
  base += ' | preventTraversal';
41
- return new tex_types_1.TexType(base);
41
+ return new tex_types_1.TexType(base, opts?.default);
42
42
  }
43
43
  number(opts) {
44
44
  let base = 'number';
45
- if (opts?.optional)
45
+ if (opts?.optional || opts?.default !== undefined)
46
46
  base += '?';
47
47
  if (opts?.nullable)
48
48
  base += ' | nullable';
@@ -54,13 +54,11 @@ class TexBuilder {
54
54
  base += ` | min:${opts.min}`;
55
55
  if (opts?.max !== undefined)
56
56
  base += ` | max:${opts.max}`;
57
- if (opts?.default !== undefined)
58
- base += ` | default:${opts.default}`;
59
- return new tex_types_1.TexType(base);
57
+ return new tex_types_1.TexType(base, opts?.default);
60
58
  }
61
59
  boolean(opts) {
62
60
  let base = 'boolean';
63
- if (opts?.optional)
61
+ if (opts?.optional || opts?.default !== undefined)
64
62
  base += '?';
65
63
  if (opts?.nullable)
66
64
  base += ' | nullable';
@@ -68,11 +66,11 @@ class TexBuilder {
68
66
  base += ' | nullish';
69
67
  if (opts?.coerce)
70
68
  base += ' | coerce';
71
- return new tex_types_1.TexType(base);
69
+ return new tex_types_1.TexType(base, opts?.default);
72
70
  }
73
71
  email(opts) {
74
72
  let base = 'email';
75
- if (opts?.optional)
73
+ if (opts?.optional || opts?.default !== undefined)
76
74
  base += '?';
77
75
  if (opts?.nullable)
78
76
  base += ' | nullable';
@@ -84,11 +82,11 @@ class TexBuilder {
84
82
  base += ' | lowercase';
85
83
  if (opts?.mask)
86
84
  base += ' | mask';
87
- return new tex_types_1.TexType(base);
85
+ return new tex_types_1.TexType(base, opts?.default);
88
86
  }
89
87
  uuid(opts) {
90
88
  let base = 'uuid';
91
- if (opts?.optional)
89
+ if (opts?.optional || opts?.default !== undefined)
92
90
  base += '?';
93
91
  if (opts?.nullable)
94
92
  base += ' | nullable';
@@ -96,21 +94,21 @@ class TexBuilder {
96
94
  base += ' | nullish';
97
95
  if (opts?.version)
98
96
  base += ` | version:${opts.version}`;
99
- return new tex_types_1.TexType(base);
97
+ return new tex_types_1.TexType(base, opts?.default);
100
98
  }
101
99
  cuid(opts) {
102
100
  let base = 'cuid';
103
- if (opts?.optional)
101
+ if (opts?.optional || opts?.default !== undefined)
104
102
  base += '?';
105
103
  if (opts?.nullable)
106
104
  base += ' | nullable';
107
105
  if (opts?.nullish)
108
106
  base += ' | nullish';
109
- return new tex_types_1.TexType(base);
107
+ return new tex_types_1.TexType(base, opts?.default);
110
108
  }
111
109
  creditCard(opts) {
112
110
  let base = 'creditcard';
113
- if (opts?.optional)
111
+ if (opts?.optional || opts?.default !== undefined)
114
112
  base += '?';
115
113
  if (opts?.nullable)
116
114
  base += ' | nullable';
@@ -118,11 +116,11 @@ class TexBuilder {
118
116
  base += ' | nullish';
119
117
  if (opts?.mask)
120
118
  base += ' | mask';
121
- return new tex_types_1.TexType(base);
119
+ return new tex_types_1.TexType(base, opts?.default);
122
120
  }
123
121
  password(opts) {
124
122
  let base = 'password';
125
- if (opts?.optional)
123
+ if (opts?.optional || opts?.default !== undefined)
126
124
  base += '?';
127
125
  if (opts?.nullable)
128
126
  base += ' | nullable';
@@ -140,7 +138,7 @@ class TexBuilder {
140
138
  base += ' | requireUppercase';
141
139
  if (opts?.requireLowercase)
142
140
  base += ' | requireLowercase';
143
- return new tex_types_1.TexType(base);
141
+ return new tex_types_1.TexType(base, opts?.default);
144
142
  }
145
143
  array(schema, opts) {
146
144
  let resolvedItem = schema;
@@ -156,7 +154,7 @@ class TexBuilder {
156
154
  ? resolvedItem._raw
157
155
  : resolvedItem;
158
156
  let base = `array<${itemSchema}>`;
159
- if (opts?.optional)
157
+ if (opts?.optional || opts?.default !== undefined)
160
158
  base += '?';
161
159
  if (opts?.nullable)
162
160
  base += ' | nullable';
@@ -170,21 +168,19 @@ class TexBuilder {
170
168
  base += ' | dedupe';
171
169
  if (opts?.coerce)
172
170
  base += ' | coerce';
173
- const res = new tex_types_1.TexType(base);
171
+ const res = new tex_types_1.TexType(base, opts?.default);
174
172
  res._item = resolvedItem;
175
173
  return res;
176
174
  }
177
175
  enum(values, opts) {
178
176
  let base = `enum:${values.join(',')}`;
179
- if (opts?.optional)
177
+ if (opts?.optional || opts?.default !== undefined)
180
178
  base += '?';
181
179
  if (opts?.nullable)
182
180
  base += ' | nullable';
183
181
  if (opts?.nullish)
184
182
  base += ' | nullish';
185
- if (opts?.default)
186
- base += ` | default:${opts.default}`;
187
- return new tex_types_1.TexType(base);
183
+ return new tex_types_1.TexType(base, opts?.default);
188
184
  }
189
185
  literal(value, opts) {
190
186
  let base = `literal:${value}`;
@@ -209,7 +205,7 @@ class TexBuilder {
209
205
  }
210
206
  date(opts) {
211
207
  let base = 'date';
212
- if (opts?.optional)
208
+ if (opts?.optional || opts?.default !== undefined)
213
209
  base += '?';
214
210
  if (opts?.nullable)
215
211
  base += ' | nullable';
@@ -221,7 +217,7 @@ class TexBuilder {
221
217
  base += ` | minDate:${opts.minDate}`;
222
218
  if (opts?.maxDate)
223
219
  base += ` | maxDate:${opts.maxDate}`;
224
- return new tex_types_1.TexType(base);
220
+ return new tex_types_1.TexType(base, opts?.default);
225
221
  }
226
222
  record(schema, opts) {
227
223
  const itemSchema = schema instanceof tex_types_1.TexType ? schema._raw : schema;
@@ -274,6 +270,53 @@ class TexBuilder {
274
270
  }
275
271
  return new TexEngine(compiledSchema, schema, opts?.strict);
276
272
  }
273
+ /**
274
+ * Dedicated Environment Schema parser (tex.env).
275
+ * Automatically coerces numbers, booleans, dates, and arrays from process.env string values,
276
+ * while preserving strict type validation and defaults.
277
+ */
278
+ env(schema, opts) {
279
+ const coercedSchema = {};
280
+ for (const [key, value] of Object.entries(schema)) {
281
+ if (value instanceof tex_types_1.TexType) {
282
+ let raw = value._raw;
283
+ // Auto-inject coercion if not already present
284
+ if ((raw.startsWith('number') ||
285
+ raw.startsWith('boolean') ||
286
+ raw.startsWith('date') ||
287
+ raw.startsWith('array<')) &&
288
+ !raw.includes('coerce')) {
289
+ raw = `${raw} | coerce`;
290
+ }
291
+ const cloned = new tex_types_1.TexType(raw, value.defaultValue);
292
+ cloned.sanitizers = [...value.sanitizers];
293
+ cloned.refinements = [...value.refinements];
294
+ if (value._item) {
295
+ ;
296
+ cloned._item = value._item;
297
+ }
298
+ coercedSchema[key] = cloned;
299
+ }
300
+ else if (value instanceof TexEngine) {
301
+ coercedSchema[key] = value;
302
+ }
303
+ else if (typeof value === 'string') {
304
+ let raw = value;
305
+ if ((raw.startsWith('number') ||
306
+ raw.startsWith('boolean') ||
307
+ raw.startsWith('date') ||
308
+ raw.startsWith('array<')) &&
309
+ !raw.includes('coerce')) {
310
+ raw = `${raw} | coerce`;
311
+ }
312
+ coercedSchema[key] = new tex_types_1.TexType(raw);
313
+ }
314
+ else {
315
+ coercedSchema[key] = value;
316
+ }
317
+ }
318
+ return this.object(coercedSchema, opts);
319
+ }
277
320
  pagination(options) {
278
321
  const defaultLimit = options?.defaultLimit ?? 10;
279
322
  const maxLimit = options?.maxLimit ?? 100;
@@ -292,12 +335,60 @@ class TexBuilder {
292
335
  }
293
336
  }
294
337
  exports.TexBuilder = TexBuilder;
338
+ function getNestedValue(obj, path) {
339
+ if (!obj || typeof obj !== 'object' || !path)
340
+ return undefined;
341
+ if (path in obj)
342
+ return obj[path];
343
+ try {
344
+ const keys = path.replace(/\[(\w+)\]/g, '.$1').split('.');
345
+ let curr = obj;
346
+ for (const k of keys) {
347
+ if (curr === undefined || curr === null)
348
+ return undefined;
349
+ curr = curr[k];
350
+ }
351
+ return curr;
352
+ }
353
+ catch {
354
+ return undefined;
355
+ }
356
+ }
357
+ function formatReceivedValue(val) {
358
+ if (val === undefined)
359
+ return 'undefined';
360
+ if (val === null)
361
+ return 'null';
362
+ if (typeof val === 'string')
363
+ return JSON.stringify(val);
364
+ if (typeof val === 'number' || typeof val === 'boolean')
365
+ return String(val);
366
+ if (Array.isArray(val)) {
367
+ try {
368
+ const str = JSON.stringify(val);
369
+ return str.length > 30 ? `Array(${val.length})` : str;
370
+ }
371
+ catch {
372
+ return `Array(${val.length})`;
373
+ }
374
+ }
375
+ if (typeof val === 'object') {
376
+ try {
377
+ const str = JSON.stringify(val);
378
+ return str.length > 30 ? '[object Object]' : str;
379
+ }
380
+ catch {
381
+ return '[object Object]';
382
+ }
383
+ }
384
+ return String(val);
385
+ }
295
386
  class ValidatorError extends Error {
296
387
  constructor(errors) {
297
388
  super('Validation Error: ' +
298
389
  errors.map((e) => `${e.path}: ${e.message}`).join(', '));
299
- this.errors = errors;
300
390
  this.name = 'ValidatorError';
391
+ this.errors = errors;
301
392
  }
302
393
  }
303
394
  exports.ValidatorError = ValidatorError;
@@ -359,9 +450,18 @@ class TexEngine {
359
450
  return new TexEngine(newSchema, newRaw, this.strict, this._isOptional);
360
451
  }
361
452
  parse(data) {
362
- // 1. Run pre-validation sanitizers on the raw payload (if it's an object)
453
+ // 1. Apply defaults and run pre-validation sanitizers on the raw payload (if it's an object)
363
454
  if (data && typeof data === 'object') {
364
455
  for (const [key, val] of Object.entries(this.rawSchema)) {
456
+ // Apply default value / lazy factory function if field is omitted or undefined
457
+ if (data[key] === undefined) {
458
+ if (val instanceof tex_types_1.TexType && val.defaultValue !== undefined) {
459
+ data[key] =
460
+ typeof val.defaultValue === 'function'
461
+ ? val.defaultValue()
462
+ : val.defaultValue;
463
+ }
464
+ }
365
465
  if (typeof data[key] === 'string' && data[key].trim() === '') {
366
466
  const raw = val instanceof tex_types_1.TexType ? val._raw : String(val);
367
467
  if (raw.includes('optional') ||
@@ -376,6 +476,13 @@ class TexEngine {
376
476
  }
377
477
  else {
378
478
  delete data[key];
479
+ // If deleted but default is defined, apply default
480
+ if (val instanceof tex_types_1.TexType && val.defaultValue !== undefined) {
481
+ data[key] =
482
+ typeof val.defaultValue === 'function'
483
+ ? val.defaultValue()
484
+ : val.defaultValue;
485
+ }
379
486
  }
380
487
  }
381
488
  }
@@ -387,7 +494,13 @@ class TexEngine {
387
494
  const d = new Date(data[key]);
388
495
  if (isNaN(d.getTime())) {
389
496
  throw new ValidatorError([
390
- { path: key, message: 'Must be a valid date' },
497
+ {
498
+ path: key,
499
+ message: 'Must be a valid date',
500
+ expected: 'valid date string or timestamp',
501
+ received: formatReceivedValue(data[key]),
502
+ code: 'INVALID_DATE',
503
+ },
391
504
  ]);
392
505
  }
393
506
  data[key] = d;
@@ -397,12 +510,24 @@ class TexEngine {
397
510
  typeof data[key] !== 'string' &&
398
511
  typeof data[key] !== 'number') {
399
512
  throw new ValidatorError([
400
- { path: key, message: 'Must be a valid date' },
513
+ {
514
+ path: key,
515
+ message: 'Must be a valid date',
516
+ expected: 'valid date or date string',
517
+ received: formatReceivedValue(data[key]),
518
+ code: 'INVALID_TYPE',
519
+ },
401
520
  ]);
402
521
  }
403
522
  if (data[key] instanceof Date && isNaN(data[key].getTime())) {
404
523
  throw new ValidatorError([
405
- { path: key, message: 'Must be a valid date' },
524
+ {
525
+ path: key,
526
+ message: 'Must be a valid date',
527
+ expected: 'valid date string or timestamp',
528
+ received: formatReceivedValue(data[key]),
529
+ code: 'INVALID_DATE',
530
+ },
406
531
  ]);
407
532
  }
408
533
  if (val._raw.includes('minDate:')) {
@@ -410,7 +535,13 @@ class TexEngine {
410
535
  if (minStr &&
411
536
  new Date(data[key]).getTime() < new Date(minStr).getTime()) {
412
537
  throw new ValidatorError([
413
- { path: key, message: `Date must be after ${minStr}` },
538
+ {
539
+ path: key,
540
+ message: `Date must be after ${minStr}`,
541
+ expected: `>= ${minStr}`,
542
+ received: formatReceivedValue(data[key]),
543
+ code: 'DATE_TOO_EARLY',
544
+ },
414
545
  ]);
415
546
  }
416
547
  }
@@ -419,7 +550,13 @@ class TexEngine {
419
550
  if (maxStr &&
420
551
  new Date(data[key]).getTime() > new Date(maxStr).getTime()) {
421
552
  throw new ValidatorError([
422
- { path: key, message: `Date must be before ${maxStr}` },
553
+ {
554
+ path: key,
555
+ message: `Date must be before ${maxStr}`,
556
+ expected: `<= ${maxStr}`,
557
+ received: formatReceivedValue(data[key]),
558
+ code: 'DATE_TOO_LATE',
559
+ },
423
560
  ]);
424
561
  }
425
562
  }
@@ -530,32 +667,115 @@ class TexEngine {
530
667
  }
531
668
  let path = 'root';
532
669
  let message = err.message || 'Validation failed';
670
+ let expected;
671
+ let code = 'VALIDATION_ERROR';
533
672
  if (message.startsWith('Missing required field: ')) {
534
673
  path = message.replace('Missing required field: ', '').trim();
535
674
  message = 'Expected value, received undefined';
675
+ expected = 'defined';
676
+ code = 'MISSING_REQUIRED_FIELD';
536
677
  }
537
678
  else if (message.startsWith("Field '")) {
538
679
  const match = message.match(/Field '([^']+)' (.+)/);
539
680
  if (match) {
540
681
  path = match[1];
541
- message = match[2].charAt(0).toUpperCase() + match[2].slice(1);
682
+ const constraint = match[2];
683
+ message = constraint.charAt(0).toUpperCase() + constraint.slice(1);
684
+ if (constraint.startsWith('must be a string')) {
685
+ expected = 'string';
686
+ code = 'INVALID_TYPE';
687
+ }
688
+ else if (constraint.startsWith('must be a number')) {
689
+ expected = 'number';
690
+ code = 'INVALID_TYPE';
691
+ }
692
+ else if (constraint.startsWith('must be a boolean')) {
693
+ expected = 'boolean';
694
+ code = 'INVALID_TYPE';
695
+ }
696
+ else if (constraint.startsWith('must be an array') ||
697
+ constraint.includes('array must have') ||
698
+ constraint.includes('array exceeds')) {
699
+ expected = 'array';
700
+ code = constraint.includes('array must have')
701
+ ? 'ARRAY_TOO_SHORT'
702
+ : constraint.includes('array exceeds')
703
+ ? 'ARRAY_TOO_LONG'
704
+ : 'INVALID_TYPE';
705
+ }
706
+ else if (constraint.startsWith('must be an object')) {
707
+ expected = 'object';
708
+ code = 'INVALID_TYPE';
709
+ }
710
+ else if (constraint.startsWith('must be a valid email')) {
711
+ expected = 'valid email address';
712
+ code = 'INVALID_EMAIL';
713
+ }
714
+ else if (constraint.startsWith('must be one of')) {
715
+ expected = constraint.replace('must be one of', '').trim();
716
+ code = 'INVALID_ENUM_VALUE';
717
+ }
718
+ else if (constraint.startsWith('must be at least') ||
719
+ constraint.startsWith('must be >=')) {
720
+ expected = constraint;
721
+ code = 'VALUE_TOO_SMALL';
722
+ }
723
+ else if (constraint.startsWith('must be at most') ||
724
+ constraint.startsWith('must be <=')) {
725
+ expected = constraint;
726
+ code = 'VALUE_TOO_LARGE';
727
+ }
728
+ else if (constraint.startsWith('must contain a number')) {
729
+ expected = 'must contain at least one digit';
730
+ code = 'PASSWORD_REQUIRES_NUMBER';
731
+ }
732
+ else if (constraint.startsWith('must contain an uppercase')) {
733
+ expected = 'must contain at least one uppercase letter';
734
+ code = 'PASSWORD_REQUIRES_UPPERCASE';
735
+ }
736
+ else if (constraint.startsWith('must contain a lowercase')) {
737
+ expected = 'must contain at least one lowercase letter';
738
+ code = 'PASSWORD_REQUIRES_LOWERCASE';
739
+ }
740
+ else if (constraint.startsWith('must contain a symbol')) {
741
+ expected = 'must contain at least one symbol';
742
+ code = 'PASSWORD_REQUIRES_SYMBOL';
743
+ }
744
+ else {
745
+ expected = constraint;
746
+ }
542
747
  }
543
748
  }
544
749
  else if (message.startsWith("Strict mode error: Unknown field '")) {
545
- const match = message.match(/Strict mode error: Unknown field '([^']+)'/);
750
+ const match = message.match(/Strict mode error: Unknown field '([^']+)'(?: at path '([^']*)')?/);
546
751
  if (match) {
547
- path = match[1];
752
+ const key = match[1];
753
+ const parentPath = match[2];
754
+ path =
755
+ parentPath && parentPath.length > 0 ? `${parentPath}.${key}` : key;
548
756
  message = 'Unknown field not allowed in strict mode';
757
+ expected = 'undefined (field omitted)';
758
+ code = 'UNRECOGNIZED_KEYS';
549
759
  }
550
760
  }
551
- throw new ValidatorError([{ path, message }]);
761
+ const receivedRaw = getNestedValue(data, path);
762
+ const received = formatReceivedValue(receivedRaw);
763
+ throw new ValidatorError([{ path, message, expected, received, code }]);
552
764
  }
553
- // Preserve coerced Date objects
765
+ // Preserve Date objects and ensure defaults are applied
554
766
  if (parsedData && typeof parsedData === 'object') {
555
767
  for (const [key, val] of Object.entries(this.rawSchema)) {
768
+ if (parsedData[key] === undefined &&
769
+ val instanceof tex_types_1.TexType &&
770
+ val.defaultValue !== undefined) {
771
+ parsedData[key] =
772
+ typeof val.defaultValue === 'function'
773
+ ? val.defaultValue()
774
+ : val.defaultValue;
775
+ }
556
776
  if (val instanceof tex_types_1.TexType &&
557
777
  val._raw.startsWith('date') &&
558
- val._raw.includes('coerce') &&
778
+ data &&
559
779
  data[key] instanceof Date) {
560
780
  parsedData[key] = data[key];
561
781
  }
@@ -577,7 +797,13 @@ class TexEngine {
577
797
  const msg = typeof r.message === 'function'
578
798
  ? r.message(parsedData[key])
579
799
  : r.message || 'Invalid value';
580
- errors.push({ path: key, message: msg });
800
+ errors.push({
801
+ path: key,
802
+ message: msg,
803
+ expected: 'custom refinement rule',
804
+ received: formatReceivedValue(parsedData[key]),
805
+ code: 'CUSTOM_VALIDATION',
806
+ });
581
807
  }
582
808
  }
583
809
  }
@@ -595,7 +821,13 @@ class TexEngine {
595
821
  const msg = typeof r.message === 'function'
596
822
  ? r.message(parsedData[key][i])
597
823
  : r.message || 'Invalid value';
598
- errors.push({ path: `${key}[${i}]`, message: msg });
824
+ errors.push({
825
+ path: `${key}[${i}]`,
826
+ message: msg,
827
+ expected: 'custom refinement rule',
828
+ received: formatReceivedValue(parsedData[key][i]),
829
+ code: 'CUSTOM_VALIDATION',
830
+ });
599
831
  }
600
832
  }
601
833
  }
@@ -626,7 +858,13 @@ class TexEngine {
626
858
  const msg = typeof r.message === 'function'
627
859
  ? r.message(valData)
628
860
  : r.message || 'Invalid value';
629
- errors.push({ path: key, message: msg });
861
+ errors.push({
862
+ path: key,
863
+ message: msg,
864
+ expected: 'custom async refinement rule',
865
+ received: formatReceivedValue(valData),
866
+ code: 'CUSTOM_VALIDATION',
867
+ });
630
868
  }
631
869
  }
632
870
  }