exisjs 0.7.13 → 0.8.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 (214) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -1
  3. package/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/dist/cli/commands/doctor.js +4 -37
  5. package/dist/cli/commands/doctor.js.map +1 -1
  6. package/dist/cli/commands/run.d.ts.map +1 -1
  7. package/dist/cli/commands/run.js +3 -1
  8. package/dist/cli/commands/run.js.map +1 -1
  9. package/dist/config/config.d.ts.map +1 -1
  10. package/dist/config/config.js +10 -0
  11. package/dist/config/config.js.map +1 -1
  12. package/dist/config/types.d.ts +9 -1
  13. package/dist/config/types.d.ts.map +1 -1
  14. package/dist/database/lifecycle.d.ts +7 -0
  15. package/dist/database/lifecycle.d.ts.map +1 -1
  16. package/dist/database/lifecycle.js +30 -7
  17. package/dist/database/lifecycle.js.map +1 -1
  18. package/dist/decorators/caching.d.ts +2 -5
  19. package/dist/decorators/caching.d.ts.map +1 -1
  20. package/dist/decorators/caching.js +7 -61
  21. package/dist/decorators/caching.js.map +1 -1
  22. package/dist/decorators/core.d.ts.map +1 -1
  23. package/dist/decorators/core.js +5 -0
  24. package/dist/decorators/core.js.map +1 -1
  25. package/dist/decorators/cron.d.ts +1 -0
  26. package/dist/decorators/cron.d.ts.map +1 -1
  27. package/dist/decorators/cron.js +4 -1
  28. package/dist/decorators/cron.js.map +1 -1
  29. package/dist/di/container.d.ts +1 -0
  30. package/dist/di/container.d.ts.map +1 -1
  31. package/dist/di/container.js +19 -3
  32. package/dist/di/container.js.map +1 -1
  33. package/dist/docs/ARCHITECTURE.md +117 -78
  34. package/dist/docs/BENCHMARKS.md +59 -26
  35. package/dist/docs/README.md +1 -1
  36. package/dist/docs/content/boundaries.mdx +2 -0
  37. package/dist/docs/content/cli/commands.mdx +1 -2
  38. package/dist/docs/content/cli/overview.mdx +1 -1
  39. package/dist/docs/content/config.mdx +22 -9
  40. package/dist/docs/content/cron.mdx +2 -2
  41. package/dist/docs/content/decorators.mdx +2 -2
  42. package/dist/docs/content/errors.mdx +22 -6
  43. package/dist/docs/content/installation.mdx +13 -8
  44. package/dist/docs/content/introduction.mdx +12 -12
  45. package/dist/docs/content/middleware.mdx +1 -1
  46. package/dist/docs/content/providers.mdx +4 -4
  47. package/dist/docs/content/requests.mdx +12 -1
  48. package/dist/docs/content/responses.mdx +44 -3
  49. package/dist/docs/content/routing.mdx +24 -0
  50. package/dist/docs/content/structure.mdx +4 -1
  51. package/dist/docs/content/testing.mdx +2 -0
  52. package/dist/docs/content/validation.mdx +16 -2
  53. package/dist/docs/reference/README.md +1 -1
  54. package/dist/docs/reference/config.mdx +1 -1
  55. package/dist/docs/reference/decorators.mdx +2 -2
  56. package/dist/docs/reference/di.mdx +1 -1
  57. package/dist/docs/reference/middleware.mdx +7 -5
  58. package/dist/docs/reference/sanitize.mdx +4 -4
  59. package/dist/docs/reference/validator.mdx +1 -1
  60. package/dist/error/errors.d.ts.map +1 -1
  61. package/dist/error/errors.js +31 -8
  62. package/dist/error/errors.js.map +1 -1
  63. package/dist/lib/start-server.js +24 -1
  64. package/dist/lib/start-server.js.map +1 -1
  65. package/dist/middleware/compression.d.ts +0 -12
  66. package/dist/middleware/compression.d.ts.map +1 -1
  67. package/dist/middleware/compression.js +52 -41
  68. package/dist/middleware/compression.js.map +1 -1
  69. package/dist/middleware/idempotency.d.ts +11 -6
  70. package/dist/middleware/idempotency.d.ts.map +1 -1
  71. package/dist/middleware/idempotency.js +5 -35
  72. package/dist/middleware/idempotency.js.map +1 -1
  73. package/dist/middleware/ip-filter.d.ts.map +1 -1
  74. package/dist/middleware/ip-filter.js +0 -16
  75. package/dist/middleware/ip-filter.js.map +1 -1
  76. package/dist/middleware/middleware.d.ts +2 -1
  77. package/dist/middleware/middleware.d.ts.map +1 -1
  78. package/dist/middleware/middleware.js +92 -56
  79. package/dist/middleware/middleware.js.map +1 -1
  80. package/dist/middleware/rate-limit.d.ts.map +1 -1
  81. package/dist/middleware/rate-limit.js +19 -38
  82. package/dist/middleware/rate-limit.js.map +1 -1
  83. package/dist/middleware/security.d.ts.map +1 -1
  84. package/dist/middleware/security.js +3 -1
  85. package/dist/middleware/security.js.map +1 -1
  86. package/dist/middleware/upload.d.ts.map +1 -1
  87. package/dist/middleware/upload.js +15 -7
  88. package/dist/middleware/upload.js.map +1 -1
  89. package/dist/response/index.d.ts +23 -0
  90. package/dist/response/index.d.ts.map +1 -1
  91. package/dist/response/index.js +28 -1
  92. package/dist/response/index.js.map +1 -1
  93. package/dist/router/index.d.ts +1 -1
  94. package/dist/router/index.d.ts.map +1 -1
  95. package/dist/router/index.js.map +1 -1
  96. package/dist/router/radix.d.ts +3 -16
  97. package/dist/router/radix.d.ts.map +1 -1
  98. package/dist/router/radix.js +135 -211
  99. package/dist/router/radix.js.map +1 -1
  100. package/dist/router/route-builder.d.ts +28 -0
  101. package/dist/router/route-builder.d.ts.map +1 -1
  102. package/dist/router/route-builder.js.map +1 -1
  103. package/dist/router/route-scanner.d.ts +2 -1
  104. package/dist/router/route-scanner.d.ts.map +1 -1
  105. package/dist/router/route-scanner.js +37 -44
  106. package/dist/router/route-scanner.js.map +1 -1
  107. package/dist/router/router.d.ts.map +1 -1
  108. package/dist/router/router.js +153 -126
  109. package/dist/router/router.js.map +1 -1
  110. package/dist/router/scanner/boundary-loader.d.ts.map +1 -1
  111. package/dist/router/scanner/boundary-loader.js +7 -2
  112. package/dist/router/scanner/boundary-loader.js.map +1 -1
  113. package/dist/router/scanner/fs-scanner.d.ts.map +1 -1
  114. package/dist/router/scanner/fs-scanner.js +22 -0
  115. package/dist/router/scanner/fs-scanner.js.map +1 -1
  116. package/dist/router/scanner/functional-compiler.d.ts.map +1 -1
  117. package/dist/router/scanner/functional-compiler.js +9 -1
  118. package/dist/router/scanner/functional-compiler.js.map +1 -1
  119. package/dist/router/types.d.ts +6 -4
  120. package/dist/router/types.d.ts.map +1 -1
  121. package/dist/sanitize/index.d.ts +2 -3
  122. package/dist/sanitize/index.d.ts.map +1 -1
  123. package/dist/sanitize/index.js +15 -20
  124. package/dist/sanitize/index.js.map +1 -1
  125. package/dist/server/adapters/bun-adapter.d.ts +2 -1
  126. package/dist/server/adapters/bun-adapter.d.ts.map +1 -1
  127. package/dist/server/adapters/bun-adapter.js +22 -2
  128. package/dist/server/adapters/bun-adapter.js.map +1 -1
  129. package/dist/server/app.d.ts +2 -2
  130. package/dist/server/app.d.ts.map +1 -1
  131. package/dist/server/app.js +14 -7
  132. package/dist/server/app.js.map +1 -1
  133. package/dist/server/bootstrapper.d.ts +2 -2
  134. package/dist/server/bootstrapper.d.ts.map +1 -1
  135. package/dist/server/bootstrapper.js +21 -2
  136. package/dist/server/bootstrapper.js.map +1 -1
  137. package/dist/server/download.d.ts +22 -0
  138. package/dist/server/download.d.ts.map +1 -0
  139. package/dist/server/download.js +18 -0
  140. package/dist/server/download.js.map +1 -0
  141. package/dist/server/helpers/body-parser.d.ts +1 -1
  142. package/dist/server/helpers/body-parser.d.ts.map +1 -1
  143. package/dist/server/helpers/body-parser.js +126 -48
  144. package/dist/server/helpers/body-parser.js.map +1 -1
  145. package/dist/server/helpers/cookie.d.ts +6 -0
  146. package/dist/server/helpers/cookie.d.ts.map +1 -1
  147. package/dist/server/helpers/cookie.js +35 -0
  148. package/dist/server/helpers/cookie.js.map +1 -1
  149. package/dist/server/helpers/etag.d.ts.map +1 -1
  150. package/dist/server/helpers/etag.js +7 -2
  151. package/dist/server/helpers/etag.js.map +1 -1
  152. package/dist/server/helpers/index.d.ts +2 -1
  153. package/dist/server/helpers/index.d.ts.map +1 -1
  154. package/dist/server/helpers/index.js +5 -1
  155. package/dist/server/helpers/index.js.map +1 -1
  156. package/dist/server/helpers/ip.d.ts +13 -0
  157. package/dist/server/helpers/ip.d.ts.map +1 -1
  158. package/dist/server/helpers/ip.js +65 -54
  159. package/dist/server/helpers/ip.js.map +1 -1
  160. package/dist/server/helpers/json.d.ts +3 -0
  161. package/dist/server/helpers/json.d.ts.map +1 -0
  162. package/dist/server/helpers/json.js +43 -0
  163. package/dist/server/helpers/json.js.map +1 -0
  164. package/dist/server/helpers/upload-name.d.ts +9 -0
  165. package/dist/server/helpers/upload-name.d.ts.map +1 -0
  166. package/dist/server/helpers/upload-name.js +39 -0
  167. package/dist/server/helpers/upload-name.js.map +1 -0
  168. package/dist/server/request-handler.d.ts +0 -7
  169. package/dist/server/request-handler.d.ts.map +1 -1
  170. package/dist/server/request-handler.js +19 -46
  171. package/dist/server/request-handler.js.map +1 -1
  172. package/dist/server/request.d.ts +3 -2
  173. package/dist/server/request.d.ts.map +1 -1
  174. package/dist/server/request.js +22 -16
  175. package/dist/server/request.js.map +1 -1
  176. package/dist/server/response.d.ts +20 -6
  177. package/dist/server/response.d.ts.map +1 -1
  178. package/dist/server/response.js +107 -34
  179. package/dist/server/response.js.map +1 -1
  180. package/dist/testing/index.d.ts.map +1 -1
  181. package/dist/testing/index.js +8 -1
  182. package/dist/testing/index.js.map +1 -1
  183. package/dist/testing/reporter.d.mts.map +1 -1
  184. package/dist/testing/reporter.mjs +18 -0
  185. package/dist/testing/reporter.mjs.map +1 -1
  186. package/dist/utils/ttl-cache.d.ts +11 -0
  187. package/dist/utils/ttl-cache.d.ts.map +1 -0
  188. package/dist/utils/ttl-cache.js +42 -0
  189. package/dist/utils/ttl-cache.js.map +1 -0
  190. package/dist/validator/builder.d.ts +2 -2
  191. package/dist/validator/builder.js +2 -2
  192. package/dist/validator/compile.d.ts +3 -0
  193. package/dist/validator/compile.d.ts.map +1 -0
  194. package/dist/validator/compile.js +254 -0
  195. package/dist/validator/compile.js.map +1 -0
  196. package/dist/validator/engine.d.ts +1 -0
  197. package/dist/validator/engine.d.ts.map +1 -1
  198. package/dist/validator/engine.js +285 -317
  199. package/dist/validator/engine.js.map +1 -1
  200. package/dist/validator/error.d.ts +1 -0
  201. package/dist/validator/error.d.ts.map +1 -1
  202. package/dist/validator/error.js +11 -1
  203. package/dist/validator/error.js.map +1 -1
  204. package/dist/validator/rules.d.ts +64 -0
  205. package/dist/validator/rules.d.ts.map +1 -0
  206. package/dist/validator/rules.js +655 -0
  207. package/dist/validator/rules.js.map +1 -0
  208. package/dist/validator/tex-types.d.ts +4 -4
  209. package/dist/validator/tex-types.d.ts.map +1 -1
  210. package/dist/validator/text.d.ts +11 -0
  211. package/dist/validator/text.d.ts.map +1 -0
  212. package/dist/validator/text.js +69 -0
  213. package/dist/validator/text.js.map +1 -0
  214. package/package.json +56 -39
@@ -0,0 +1,655 @@
1
+ "use strict";
2
+ // Tex rule compiler.
3
+ //
4
+ // A rule string such as "string? | min:3 | trim" is parsed once, when the
5
+ // schema is built, into a tree of small check functions. Validating a request
6
+ // is then a plain walk over the data with no parsing and no allocation beyond
7
+ // the result object. Error messages keep the established wording
8
+ // ("Missing required field: x", "Field 'x' must be ...") which TexEngine maps
9
+ // to structured error codes.
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.EMAIL = exports.RuleError = void 0;
12
+ exports.toLegacyError = toLegacyError;
13
+ exports.compileRule = compileRule;
14
+ exports.compileObject = compileObject;
15
+ exports.compileSchema = compileSchema;
16
+ exports.parseJsonString = parseJsonString;
17
+ exports.normalizeArrayInput = normalizeArrayInput;
18
+ exports.dedupeItems = dedupeItems;
19
+ const text_1 = require("./text");
20
+ // Thrown by checks. The failing field's path is collected while the error
21
+ // unwinds through containers, so valid input never builds path strings.
22
+ class RuleError extends Error {
23
+ constructor(kind, detail) {
24
+ super(detail);
25
+ this.kind = kind;
26
+ this.detail = detail;
27
+ this.path = [];
28
+ }
29
+ }
30
+ exports.RuleError = RuleError;
31
+ function formatPath(segments) {
32
+ let out = '';
33
+ for (const seg of segments) {
34
+ if (typeof seg === 'number')
35
+ out += `[${seg}]`;
36
+ else
37
+ out += out === '' ? seg : `.${seg}`;
38
+ }
39
+ return out;
40
+ }
41
+ // Keeps the established message formats that TexEngine maps to error codes
42
+ function toLegacyError(err) {
43
+ const path = formatPath(err.path);
44
+ switch (err.kind) {
45
+ case 'missing':
46
+ return new Error(`Missing required field: ${path}`);
47
+ case 'strict':
48
+ return new Error(`Strict mode error: Unknown field '${err.detail}' at path '${path}'`);
49
+ case 'input':
50
+ return new Error(err.detail);
51
+ default:
52
+ return new Error(`Field '${path}' ${err.detail}`);
53
+ }
54
+ }
55
+ function prefixPath(err, segment) {
56
+ if (err instanceof RuleError)
57
+ err.path.unshift(segment);
58
+ throw err;
59
+ }
60
+ // ─── Rule string parsing ─────────────────────────────────────────────────────
61
+ // Splits on `sep` only at nesting depth 0 and outside JSON string quotes, so
62
+ // "object<{\"a\":\"string | min:2\"}> | optional" splits into two parts.
63
+ function splitTopLevel(s, sep) {
64
+ const parts = [];
65
+ let start = 0;
66
+ let depth = 0;
67
+ let inQuotes = false;
68
+ for (let i = 0; i < s.length; i++) {
69
+ const ch = s[i];
70
+ if (inQuotes) {
71
+ if (ch === '\\')
72
+ i++;
73
+ else if (ch === '"')
74
+ inQuotes = false;
75
+ continue;
76
+ }
77
+ if (ch === '"')
78
+ inQuotes = true;
79
+ else if (ch === '<' || ch === '{' || ch === '(' || ch === '[')
80
+ depth++;
81
+ else if (ch === '>' || ch === '}' || ch === ')' || ch === ']') {
82
+ if (depth > 0)
83
+ depth--;
84
+ }
85
+ else if (ch === '|' && depth === 0) {
86
+ const isDouble = s[i + 1] === '|';
87
+ if (sep === '||' && isDouble) {
88
+ parts.push(s.slice(start, i).trim());
89
+ start = i + 2;
90
+ i++;
91
+ }
92
+ else if (sep === '|' && !isDouble) {
93
+ parts.push(s.slice(start, i).trim());
94
+ start = i + 1;
95
+ }
96
+ else if (isDouble) {
97
+ i++; // skip the other separator kind as a unit
98
+ }
99
+ }
100
+ }
101
+ const tail = s.slice(start).trim();
102
+ if (tail !== '' || parts.length === 0)
103
+ parts.push(tail);
104
+ return parts;
105
+ }
106
+ function readModifiers(parts, field) {
107
+ const m = {
108
+ coerce: false,
109
+ dedupe: false,
110
+ trim: false,
111
+ collapseWhitespace: false,
112
+ lowercase: false,
113
+ uppercase: false,
114
+ escapeHtml: false,
115
+ stripHtml: false,
116
+ slugify: false,
117
+ mask: false,
118
+ preventSql: false,
119
+ preventTraversal: false,
120
+ requireNumbers: false,
121
+ requireSymbols: false,
122
+ requireUppercase: false,
123
+ requireLowercase: false,
124
+ };
125
+ for (let i = 1; i < parts.length; i++) {
126
+ const p = parts[i];
127
+ const colon = p.indexOf(':');
128
+ if (colon !== -1) {
129
+ const name = p.slice(0, colon);
130
+ const arg = p.slice(colon + 1);
131
+ if (name === 'min' || name === 'max') {
132
+ const n = Number(arg);
133
+ if (arg.trim() !== '' && Number.isFinite(n))
134
+ m[name] = n;
135
+ }
136
+ else if (name === 'default')
137
+ m.defaultValue = arg;
138
+ else if (name === 'version') {
139
+ const n = Number(arg);
140
+ if (Number.isInteger(n))
141
+ m.version = n;
142
+ }
143
+ // minDate/maxDate/maxSize/mimeTypes are enforced by TexEngine and the
144
+ // upload middleware
145
+ continue;
146
+ }
147
+ switch (p) {
148
+ case 'nullable':
149
+ field.nullable = true;
150
+ break;
151
+ case 'optional':
152
+ field.optional = true;
153
+ break;
154
+ case 'nullish':
155
+ field.optional = true;
156
+ field.nullable = true;
157
+ break;
158
+ case 'coerce':
159
+ case 'dedupe':
160
+ case 'trim':
161
+ case 'collapseWhitespace':
162
+ case 'lowercase':
163
+ case 'uppercase':
164
+ case 'escapeHtml':
165
+ case 'stripHtml':
166
+ case 'slugify':
167
+ case 'mask':
168
+ case 'preventSql':
169
+ case 'preventTraversal':
170
+ case 'requireNumbers':
171
+ case 'requireSymbols':
172
+ case 'requireUppercase':
173
+ case 'requireLowercase':
174
+ m[p] = true;
175
+ break;
176
+ }
177
+ }
178
+ return m;
179
+ }
180
+ function compileRule(rule) {
181
+ const field = {
182
+ optional: false,
183
+ nullable: false,
184
+ check: passThrough,
185
+ gen: { kind: 'pass' },
186
+ };
187
+ // Unions first: the builder joins whole alternatives (with their own
188
+ // modifiers) using "||", so "number | coerce || email" is two rules
189
+ const alternatives = splitTopLevel(rule, '||');
190
+ if (alternatives.length > 1) {
191
+ const rules = alternatives.map(compileRule);
192
+ field.optional = rules.some((r) => r.optional);
193
+ field.nullable = rules.some((r) => r.nullable);
194
+ field.check = unionCheck(rules);
195
+ field.gen = undefined;
196
+ return field;
197
+ }
198
+ const parts = splitTopLevel(rule, '|');
199
+ let base = parts[0];
200
+ if (base.endsWith('?')) {
201
+ field.optional = true;
202
+ base = base.slice(0, -1).trim();
203
+ }
204
+ const m = readModifiers(parts, field);
205
+ field.defaultValue = m.defaultValue;
206
+ // "(a || b)?" groups a whole rule; compile the inside and keep its flags
207
+ if (base.startsWith('(') && base.endsWith(')')) {
208
+ const inner = compileRule(base.slice(1, -1));
209
+ field.check = inner.check;
210
+ field.gen = inner.gen;
211
+ field.optional = field.optional || inner.optional;
212
+ field.nullable = field.nullable || inner.nullable;
213
+ return field;
214
+ }
215
+ if (base.startsWith('object<') && base.endsWith('>')) {
216
+ const inner = parseObjectSchema(base.slice(7, -1));
217
+ field.check = objectCheck(inner);
218
+ if (inner)
219
+ field.gen = { kind: 'object', schema: inner };
220
+ }
221
+ else if (base.startsWith('array<') && base.endsWith('>')) {
222
+ const item = compileRule(base.slice(6, -1));
223
+ field.check = arrayCheck(item, m);
224
+ field.gen = { kind: 'array', item, m };
225
+ }
226
+ else if (base.startsWith('record<') && base.endsWith('>')) {
227
+ field.check = recordCheck(compileRule(base.slice(7, -1)));
228
+ field.gen = undefined;
229
+ }
230
+ else if (base.startsWith('enum:')) {
231
+ const values = base
232
+ .slice(5)
233
+ .split(',')
234
+ .map((v) => v.trim());
235
+ field.check = enumCheck(values);
236
+ field.gen = { kind: 'enum', values };
237
+ }
238
+ else if (base.startsWith('literal:')) {
239
+ field.check = literalCheck(base.slice(8));
240
+ field.gen = undefined;
241
+ }
242
+ else {
243
+ switch (base) {
244
+ case 'string':
245
+ field.check = stringCheck(m);
246
+ field.gen = { kind: 'string', m };
247
+ break;
248
+ case 'number':
249
+ field.check = numberCheck(m);
250
+ field.gen = { kind: 'number', m };
251
+ break;
252
+ case 'boolean':
253
+ field.check = booleanCheck(m);
254
+ field.gen = { kind: 'boolean', m };
255
+ break;
256
+ case 'email':
257
+ field.check = emailCheck(m);
258
+ field.gen = { kind: 'email', m };
259
+ break;
260
+ case 'password':
261
+ field.check = passwordCheck(m);
262
+ field.gen = undefined;
263
+ break;
264
+ case 'uuid':
265
+ field.check = uuidCheck(m.version);
266
+ field.gen = undefined;
267
+ break;
268
+ case 'cuid':
269
+ field.check = patternCheck(CUID, 'must be a valid CUID');
270
+ field.gen = undefined;
271
+ break;
272
+ case 'creditcard':
273
+ field.check = creditCardCheck(m);
274
+ field.gen = undefined;
275
+ break;
276
+ // date is validated by TexEngine; file by the upload middleware
277
+ default:
278
+ field.check = passThrough;
279
+ }
280
+ }
281
+ return field;
282
+ }
283
+ function compileObject(schema, strict) {
284
+ const keys = Object.keys(schema);
285
+ const fields = keys.map((k) => compileRule(schema[k]));
286
+ const known = new Set(keys);
287
+ return (obj) => {
288
+ if (strict) {
289
+ for (const key in obj) {
290
+ if (!known.has(key))
291
+ throw new RuleError('strict', key);
292
+ }
293
+ }
294
+ const out = {};
295
+ let i = 0;
296
+ try {
297
+ for (; i < keys.length; i++) {
298
+ const key = keys[i];
299
+ const field = fields[i];
300
+ let value = obj[key];
301
+ if (value === undefined || value === null) {
302
+ if (value === null && field.nullable) {
303
+ out[key] = null;
304
+ continue;
305
+ }
306
+ if (field.defaultValue !== undefined) {
307
+ value = field.defaultValue;
308
+ }
309
+ else if (!field.optional) {
310
+ throw new RuleError('missing', '');
311
+ }
312
+ else {
313
+ continue;
314
+ }
315
+ }
316
+ out[key] = field.check(value);
317
+ }
318
+ }
319
+ catch (err) {
320
+ prefixPath(err, keys[i]);
321
+ }
322
+ return out;
323
+ };
324
+ }
325
+ /** Builds the top-level validator TexEngine calls for every parse */
326
+ function compileSchema(schema, strict) {
327
+ const validate = compileObject(schema, strict);
328
+ return (data) => {
329
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) {
330
+ throw new RuleError('input', 'Input must be a JSON object');
331
+ }
332
+ try {
333
+ return validate(data);
334
+ }
335
+ catch (err) {
336
+ throw err instanceof RuleError ? toLegacyError(err) : err;
337
+ }
338
+ };
339
+ }
340
+ // ─── Field checks ────────────────────────────────────────────────────────────
341
+ const passThrough = (value) => value;
342
+ function fail(constraint) {
343
+ throw new RuleError('field', constraint);
344
+ }
345
+ function nullableCheck(rule) {
346
+ return rule.nullable ? (v) => (v === null ? null : rule.check(v)) : rule.check;
347
+ }
348
+ function applyTextModifiers(s, m) {
349
+ if (m.trim)
350
+ s = s.trim();
351
+ if (m.collapseWhitespace)
352
+ s = (0, text_1.collapseWhitespace)(s);
353
+ if (m.lowercase)
354
+ s = s.toLowerCase();
355
+ if (m.uppercase)
356
+ s = s.toUpperCase();
357
+ if (m.stripHtml)
358
+ s = (0, text_1.stripHtml)(s);
359
+ if (m.escapeHtml)
360
+ s = (0, text_1.escapeHtml)(s);
361
+ if (m.slugify)
362
+ s = (0, text_1.slugify)(s);
363
+ if (m.preventSql && (0, text_1.hasSqlInjection)(s))
364
+ fail(text_1.SQL_INJECTION_MESSAGE);
365
+ if (m.preventTraversal && (0, text_1.hasPathTraversal)(s)) {
366
+ fail(text_1.PATH_TRAVERSAL_MESSAGE);
367
+ }
368
+ return s;
369
+ }
370
+ function stringCheck(m) {
371
+ return (v) => {
372
+ if (m.coerce && (typeof v === 'number' || typeof v === 'boolean')) {
373
+ v = String(v);
374
+ }
375
+ if (typeof v !== 'string')
376
+ fail('must be a string');
377
+ let s = applyTextModifiers(v, m);
378
+ if (m.mask)
379
+ s = (0, text_1.maskString)(s);
380
+ if (m.min !== undefined && s.length < m.min) {
381
+ fail(`must be at least ${m.min} characters`);
382
+ }
383
+ if (m.max !== undefined && s.length > m.max) {
384
+ fail(`must be at most ${m.max} characters`);
385
+ }
386
+ return s;
387
+ };
388
+ }
389
+ function numberCheck(m) {
390
+ return (v) => {
391
+ if (m.coerce && typeof v === 'string' && v.trim() !== '') {
392
+ const n = Number(v);
393
+ if (Number.isFinite(n))
394
+ v = n;
395
+ }
396
+ if (typeof v !== 'number' || !Number.isFinite(v)) {
397
+ fail('must be a number');
398
+ }
399
+ if (m.min !== undefined && v < m.min)
400
+ fail(`must be >= ${m.min}`);
401
+ if (m.max !== undefined && v > m.max)
402
+ fail(`must be <= ${m.max}`);
403
+ return v;
404
+ };
405
+ }
406
+ function booleanCheck(m) {
407
+ return (v) => {
408
+ if (m.coerce) {
409
+ if (v === 'true' || v === '1' || v === 1)
410
+ v = true;
411
+ else if (v === 'false' || v === '0' || v === 0)
412
+ v = false;
413
+ }
414
+ if (typeof v !== 'boolean')
415
+ fail('must be a boolean');
416
+ return v;
417
+ };
418
+ }
419
+ exports.EMAIL = /^[^\s@]+@[^\s@]+$/;
420
+ function emailCheck(m) {
421
+ return (v) => {
422
+ if (typeof v !== 'string')
423
+ fail('must be a string');
424
+ let s = v;
425
+ if (m.trim)
426
+ s = s.trim();
427
+ if (m.lowercase)
428
+ s = s.toLowerCase();
429
+ if (!exports.EMAIL.test(s))
430
+ fail('must be a valid email');
431
+ if (m.mask)
432
+ s = (0, text_1.maskEmail)(s);
433
+ return s;
434
+ };
435
+ }
436
+ function passwordCheck(m) {
437
+ return (v) => {
438
+ if (typeof v !== 'string')
439
+ fail('must be a string');
440
+ if (m.min !== undefined && v.length < m.min) {
441
+ fail(`must be at least ${m.min} characters`);
442
+ }
443
+ if (m.max !== undefined && v.length > m.max) {
444
+ fail(`must be at most ${m.max} characters`);
445
+ }
446
+ if (m.requireNumbers && !/[0-9]/.test(v)) {
447
+ fail('must contain a number');
448
+ }
449
+ if (m.requireUppercase && !/[A-Z]/.test(v)) {
450
+ fail('must contain an uppercase letter');
451
+ }
452
+ if (m.requireLowercase && !/[a-z]/.test(v)) {
453
+ fail('must contain a lowercase letter');
454
+ }
455
+ if (m.requireSymbols && !/[^\p{L}\p{N}]/u.test(v)) {
456
+ fail('must contain a symbol');
457
+ }
458
+ return v;
459
+ };
460
+ }
461
+ function enumCheck(values) {
462
+ const allowed = new Set(values);
463
+ const label = `[${values.map((v) => JSON.stringify(v)).join(', ')}]`;
464
+ return (v) => {
465
+ if (typeof v !== 'string')
466
+ fail('must be a string');
467
+ if (!allowed.has(v))
468
+ fail(`must be one of ${label}`);
469
+ return v;
470
+ };
471
+ }
472
+ // A literal rule is text ("literal:42"), so compare the value's string form;
473
+ // the value itself keeps its original type
474
+ function literalCheck(literal) {
475
+ return (v) => {
476
+ if ((typeof v !== 'string' &&
477
+ typeof v !== 'number' &&
478
+ typeof v !== 'boolean') ||
479
+ String(v) !== literal) {
480
+ fail(`must be exactly ${JSON.stringify(literal)}`);
481
+ }
482
+ return v;
483
+ };
484
+ }
485
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-([1-8])[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
486
+ function uuidCheck(version) {
487
+ return (v) => {
488
+ const match = typeof v === 'string' ? UUID.exec(v) : null;
489
+ if (!match)
490
+ fail('must be a valid UUID');
491
+ if (version !== undefined && Number(match[1]) !== version) {
492
+ fail(`must be a valid UUID v${version}`);
493
+ }
494
+ return v;
495
+ };
496
+ }
497
+ const CUID = /^c[^\s-]{8,}$/i;
498
+ function patternCheck(pattern, constraint) {
499
+ return (v) => {
500
+ if (typeof v !== 'string' || !pattern.test(v))
501
+ fail(constraint);
502
+ return v;
503
+ };
504
+ }
505
+ function luhn(digits) {
506
+ let sum = 0;
507
+ let double = false;
508
+ for (let i = digits.length - 1; i >= 0; i--) {
509
+ let d = digits.charCodeAt(i) - 48;
510
+ if (double) {
511
+ d *= 2;
512
+ if (d > 9)
513
+ d -= 9;
514
+ }
515
+ sum += d;
516
+ double = !double;
517
+ }
518
+ return sum % 10 === 0;
519
+ }
520
+ function creditCardCheck(m) {
521
+ return (v) => {
522
+ const digits = typeof v === 'string' ? v.replace(/[\s-]/g, '') : '';
523
+ if (!/^[0-9]{12,19}$/.test(digits) || !luhn(digits)) {
524
+ fail('must be a valid credit card number');
525
+ }
526
+ return m.mask ? `${'*'.repeat(digits.length - 4)}${digits.slice(-4)}` : v;
527
+ };
528
+ }
529
+ function parseJsonString(v, open, close) {
530
+ if (typeof v !== 'string')
531
+ return v;
532
+ const s = v.trim();
533
+ if (s.startsWith(open) && s.endsWith(close)) {
534
+ try {
535
+ return JSON.parse(s);
536
+ }
537
+ catch {
538
+ return v;
539
+ }
540
+ }
541
+ return v;
542
+ }
543
+ // Query strings and form fields often carry arrays as JSON text; a lone
544
+ // object is treated as a one-item array
545
+ function normalizeArrayInput(v) {
546
+ if (typeof v === 'string') {
547
+ const parsed = parseJsonString(v, '[', ']');
548
+ if (Array.isArray(parsed))
549
+ return parsed;
550
+ const obj = parseJsonString(v, '{', '}');
551
+ if (obj !== null && typeof obj === 'object')
552
+ return [obj];
553
+ return v;
554
+ }
555
+ if (v !== null && typeof v === 'object')
556
+ return [v];
557
+ return v;
558
+ }
559
+ function dedupeItems(items) {
560
+ const seen = new Set();
561
+ return items.filter((x) => {
562
+ const k = typeof x === 'object' ? JSON.stringify(x) : `${typeof x}:${x}`;
563
+ if (seen.has(k))
564
+ return false;
565
+ seen.add(k);
566
+ return true;
567
+ });
568
+ }
569
+ function arrayCheck(item, m) {
570
+ const checkItem = nullableCheck(item);
571
+ return (v) => {
572
+ if (!Array.isArray(v))
573
+ v = normalizeArrayInput(v);
574
+ if (!Array.isArray(v))
575
+ fail('must be an array');
576
+ if (m.min !== undefined && v.length < m.min) {
577
+ fail(`array must have at least ${m.min} items`);
578
+ }
579
+ if (m.max !== undefined && v.length > m.max) {
580
+ fail(`array exceeds maximum length of ${m.max}`);
581
+ }
582
+ let out = new Array(v.length);
583
+ for (let i = 0; i < v.length; i++) {
584
+ try {
585
+ out[i] = checkItem(v[i]);
586
+ }
587
+ catch (err) {
588
+ prefixPath(err, i);
589
+ }
590
+ }
591
+ if (m.dedupe)
592
+ out = dedupeItems(out);
593
+ return out;
594
+ };
595
+ }
596
+ function parseObjectSchema(innerJson) {
597
+ try {
598
+ const parsed = JSON.parse(innerJson);
599
+ if (parsed && typeof parsed === 'object')
600
+ return parsed;
601
+ }
602
+ catch {
603
+ // fall through
604
+ }
605
+ return null;
606
+ }
607
+ function objectCheck(schema) {
608
+ if (!schema)
609
+ return passThrough;
610
+ const validate = compileObject(schema, false);
611
+ return (v) => {
612
+ v = parseJsonString(v, '{', '}');
613
+ if (v === null || typeof v !== 'object' || Array.isArray(v)) {
614
+ fail('must be an object');
615
+ }
616
+ return validate(v);
617
+ };
618
+ }
619
+ function recordCheck(value) {
620
+ const checkValue = nullableCheck(value);
621
+ return (v) => {
622
+ v = parseJsonString(v, '{', '}');
623
+ if (v === null || typeof v !== 'object' || Array.isArray(v)) {
624
+ fail('must be an object');
625
+ }
626
+ const out = {};
627
+ for (const key of Object.keys(v)) {
628
+ if (key === '__proto__' || key === 'constructor')
629
+ continue;
630
+ try {
631
+ out[key] = checkValue(v[key]);
632
+ }
633
+ catch (err) {
634
+ prefixPath(err, key);
635
+ }
636
+ }
637
+ return out;
638
+ };
639
+ }
640
+ function unionCheck(options) {
641
+ const checks = options.map(nullableCheck);
642
+ return (v) => {
643
+ for (const check of checks) {
644
+ try {
645
+ return check(v);
646
+ }
647
+ catch (err) {
648
+ if (!(err instanceof RuleError))
649
+ throw err;
650
+ }
651
+ }
652
+ return fail('must match one of the allowed types');
653
+ };
654
+ }
655
+ //# sourceMappingURL=rules.js.map