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.
- package/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +4 -37
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +3 -1
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +10 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +9 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/database/lifecycle.d.ts +7 -0
- package/dist/database/lifecycle.d.ts.map +1 -1
- package/dist/database/lifecycle.js +30 -7
- package/dist/database/lifecycle.js.map +1 -1
- package/dist/decorators/caching.d.ts +2 -5
- package/dist/decorators/caching.d.ts.map +1 -1
- package/dist/decorators/caching.js +7 -61
- package/dist/decorators/caching.js.map +1 -1
- package/dist/decorators/core.d.ts.map +1 -1
- package/dist/decorators/core.js +5 -0
- package/dist/decorators/core.js.map +1 -1
- package/dist/decorators/cron.d.ts +1 -0
- package/dist/decorators/cron.d.ts.map +1 -1
- package/dist/decorators/cron.js +4 -1
- package/dist/decorators/cron.js.map +1 -1
- package/dist/di/container.d.ts +1 -0
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +19 -3
- package/dist/di/container.js.map +1 -1
- package/dist/docs/ARCHITECTURE.md +117 -78
- package/dist/docs/BENCHMARKS.md +59 -26
- package/dist/docs/README.md +1 -1
- package/dist/docs/content/boundaries.mdx +2 -0
- package/dist/docs/content/cli/commands.mdx +1 -2
- package/dist/docs/content/cli/overview.mdx +1 -1
- package/dist/docs/content/config.mdx +22 -9
- package/dist/docs/content/cron.mdx +2 -2
- package/dist/docs/content/decorators.mdx +2 -2
- package/dist/docs/content/errors.mdx +22 -6
- package/dist/docs/content/installation.mdx +13 -8
- package/dist/docs/content/introduction.mdx +12 -12
- package/dist/docs/content/middleware.mdx +1 -1
- package/dist/docs/content/providers.mdx +4 -4
- package/dist/docs/content/requests.mdx +12 -1
- package/dist/docs/content/responses.mdx +44 -3
- package/dist/docs/content/routing.mdx +24 -0
- package/dist/docs/content/structure.mdx +4 -1
- package/dist/docs/content/testing.mdx +2 -0
- package/dist/docs/content/validation.mdx +16 -2
- package/dist/docs/reference/README.md +1 -1
- package/dist/docs/reference/config.mdx +1 -1
- package/dist/docs/reference/decorators.mdx +2 -2
- package/dist/docs/reference/di.mdx +1 -1
- package/dist/docs/reference/middleware.mdx +7 -5
- package/dist/docs/reference/sanitize.mdx +4 -4
- package/dist/docs/reference/validator.mdx +1 -1
- package/dist/error/errors.d.ts.map +1 -1
- package/dist/error/errors.js +31 -8
- package/dist/error/errors.js.map +1 -1
- package/dist/lib/start-server.js +24 -1
- package/dist/lib/start-server.js.map +1 -1
- package/dist/middleware/compression.d.ts +0 -12
- package/dist/middleware/compression.d.ts.map +1 -1
- package/dist/middleware/compression.js +52 -41
- package/dist/middleware/compression.js.map +1 -1
- package/dist/middleware/idempotency.d.ts +11 -6
- package/dist/middleware/idempotency.d.ts.map +1 -1
- package/dist/middleware/idempotency.js +5 -35
- package/dist/middleware/idempotency.js.map +1 -1
- package/dist/middleware/ip-filter.d.ts.map +1 -1
- package/dist/middleware/ip-filter.js +0 -16
- package/dist/middleware/ip-filter.js.map +1 -1
- package/dist/middleware/middleware.d.ts +2 -1
- package/dist/middleware/middleware.d.ts.map +1 -1
- package/dist/middleware/middleware.js +92 -56
- package/dist/middleware/middleware.js.map +1 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/rate-limit.js +19 -38
- package/dist/middleware/rate-limit.js.map +1 -1
- package/dist/middleware/security.d.ts.map +1 -1
- package/dist/middleware/security.js +3 -1
- package/dist/middleware/security.js.map +1 -1
- package/dist/middleware/upload.d.ts.map +1 -1
- package/dist/middleware/upload.js +15 -7
- package/dist/middleware/upload.js.map +1 -1
- package/dist/response/index.d.ts +23 -0
- package/dist/response/index.d.ts.map +1 -1
- package/dist/response/index.js +28 -1
- package/dist/response/index.js.map +1 -1
- package/dist/router/index.d.ts +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/router/radix.d.ts +3 -16
- package/dist/router/radix.d.ts.map +1 -1
- package/dist/router/radix.js +135 -211
- package/dist/router/radix.js.map +1 -1
- package/dist/router/route-builder.d.ts +28 -0
- package/dist/router/route-builder.d.ts.map +1 -1
- package/dist/router/route-builder.js.map +1 -1
- package/dist/router/route-scanner.d.ts +2 -1
- package/dist/router/route-scanner.d.ts.map +1 -1
- package/dist/router/route-scanner.js +37 -44
- package/dist/router/route-scanner.js.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +153 -126
- package/dist/router/router.js.map +1 -1
- package/dist/router/scanner/boundary-loader.d.ts.map +1 -1
- package/dist/router/scanner/boundary-loader.js +7 -2
- package/dist/router/scanner/boundary-loader.js.map +1 -1
- package/dist/router/scanner/fs-scanner.d.ts.map +1 -1
- package/dist/router/scanner/fs-scanner.js +22 -0
- package/dist/router/scanner/fs-scanner.js.map +1 -1
- package/dist/router/scanner/functional-compiler.d.ts.map +1 -1
- package/dist/router/scanner/functional-compiler.js +9 -1
- package/dist/router/scanner/functional-compiler.js.map +1 -1
- package/dist/router/types.d.ts +6 -4
- package/dist/router/types.d.ts.map +1 -1
- package/dist/sanitize/index.d.ts +2 -3
- package/dist/sanitize/index.d.ts.map +1 -1
- package/dist/sanitize/index.js +15 -20
- package/dist/sanitize/index.js.map +1 -1
- package/dist/server/adapters/bun-adapter.d.ts +2 -1
- package/dist/server/adapters/bun-adapter.d.ts.map +1 -1
- package/dist/server/adapters/bun-adapter.js +22 -2
- package/dist/server/adapters/bun-adapter.js.map +1 -1
- package/dist/server/app.d.ts +2 -2
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +14 -7
- package/dist/server/app.js.map +1 -1
- package/dist/server/bootstrapper.d.ts +2 -2
- package/dist/server/bootstrapper.d.ts.map +1 -1
- package/dist/server/bootstrapper.js +21 -2
- package/dist/server/bootstrapper.js.map +1 -1
- package/dist/server/download.d.ts +22 -0
- package/dist/server/download.d.ts.map +1 -0
- package/dist/server/download.js +18 -0
- package/dist/server/download.js.map +1 -0
- package/dist/server/helpers/body-parser.d.ts +1 -1
- package/dist/server/helpers/body-parser.d.ts.map +1 -1
- package/dist/server/helpers/body-parser.js +126 -48
- package/dist/server/helpers/body-parser.js.map +1 -1
- package/dist/server/helpers/cookie.d.ts +6 -0
- package/dist/server/helpers/cookie.d.ts.map +1 -1
- package/dist/server/helpers/cookie.js +35 -0
- package/dist/server/helpers/cookie.js.map +1 -1
- package/dist/server/helpers/etag.d.ts.map +1 -1
- package/dist/server/helpers/etag.js +7 -2
- package/dist/server/helpers/etag.js.map +1 -1
- package/dist/server/helpers/index.d.ts +2 -1
- package/dist/server/helpers/index.d.ts.map +1 -1
- package/dist/server/helpers/index.js +5 -1
- package/dist/server/helpers/index.js.map +1 -1
- package/dist/server/helpers/ip.d.ts +13 -0
- package/dist/server/helpers/ip.d.ts.map +1 -1
- package/dist/server/helpers/ip.js +65 -54
- package/dist/server/helpers/ip.js.map +1 -1
- package/dist/server/helpers/json.d.ts +3 -0
- package/dist/server/helpers/json.d.ts.map +1 -0
- package/dist/server/helpers/json.js +43 -0
- package/dist/server/helpers/json.js.map +1 -0
- package/dist/server/helpers/upload-name.d.ts +9 -0
- package/dist/server/helpers/upload-name.d.ts.map +1 -0
- package/dist/server/helpers/upload-name.js +39 -0
- package/dist/server/helpers/upload-name.js.map +1 -0
- package/dist/server/request-handler.d.ts +0 -7
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server/request-handler.js +19 -46
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts +3 -2
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +22 -16
- package/dist/server/request.js.map +1 -1
- package/dist/server/response.d.ts +20 -6
- package/dist/server/response.d.ts.map +1 -1
- package/dist/server/response.js +107 -34
- package/dist/server/response.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +8 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/reporter.d.mts.map +1 -1
- package/dist/testing/reporter.mjs +18 -0
- package/dist/testing/reporter.mjs.map +1 -1
- package/dist/utils/ttl-cache.d.ts +11 -0
- package/dist/utils/ttl-cache.d.ts.map +1 -0
- package/dist/utils/ttl-cache.js +42 -0
- package/dist/utils/ttl-cache.js.map +1 -0
- package/dist/validator/builder.d.ts +2 -2
- package/dist/validator/builder.js +2 -2
- package/dist/validator/compile.d.ts +3 -0
- package/dist/validator/compile.d.ts.map +1 -0
- package/dist/validator/compile.js +254 -0
- package/dist/validator/compile.js.map +1 -0
- package/dist/validator/engine.d.ts +1 -0
- package/dist/validator/engine.d.ts.map +1 -1
- package/dist/validator/engine.js +285 -317
- package/dist/validator/engine.js.map +1 -1
- package/dist/validator/error.d.ts +1 -0
- package/dist/validator/error.d.ts.map +1 -1
- package/dist/validator/error.js +11 -1
- package/dist/validator/error.js.map +1 -1
- package/dist/validator/rules.d.ts +64 -0
- package/dist/validator/rules.d.ts.map +1 -0
- package/dist/validator/rules.js +655 -0
- package/dist/validator/rules.js.map +1 -0
- package/dist/validator/tex-types.d.ts +4 -4
- package/dist/validator/tex-types.d.ts.map +1 -1
- package/dist/validator/text.d.ts +11 -0
- package/dist/validator/text.d.ts.map +1 -0
- package/dist/validator/text.js +69 -0
- package/dist/validator/text.js.map +1 -0
- 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
|