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
package/dist/validator/engine.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TexDiscriminatedUnionEngine = exports.TexTransformedEngine = exports.TexEngine = void 0;
|
|
4
|
-
const
|
|
4
|
+
const compile_1 = require("./compile");
|
|
5
5
|
const tex_types_1 = require("./tex-types");
|
|
6
6
|
const error_1 = require("./error");
|
|
7
7
|
class TexEngine {
|
|
@@ -11,7 +11,7 @@ class TexEngine {
|
|
|
11
11
|
this.rawSchema = rawSchema;
|
|
12
12
|
this.strict = strict;
|
|
13
13
|
this._isOptional = isOptional;
|
|
14
|
-
this.validator =
|
|
14
|
+
this.validator = (0, compile_1.compileSchema)(schema, strict);
|
|
15
15
|
}
|
|
16
16
|
getCompiledSchema() {
|
|
17
17
|
return this.schema;
|
|
@@ -89,250 +89,19 @@ class TexEngine {
|
|
|
89
89
|
return new TexEngine(newSchema, newRaw, this.strict, this._isOptional);
|
|
90
90
|
}
|
|
91
91
|
parse(data) {
|
|
92
|
-
|
|
92
|
+
const plan = this._plan || (this._plan = splitPlan(buildPlan(this.rawSchema)));
|
|
93
|
+
const coerceScalars = Boolean(this._isQueryOrParam);
|
|
94
|
+
// 1. Defaults, empty-string handling, coercion and sanitizers. Only
|
|
95
|
+
// fields that need any of it are visited.
|
|
93
96
|
if (data && typeof data === 'object') {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (val instanceof tex_types_1.TexType && val.defaultValue !== undefined) {
|
|
98
|
-
data[key] =
|
|
99
|
-
typeof val.defaultValue === 'function'
|
|
100
|
-
? val.defaultValue()
|
|
101
|
-
: val.defaultValue;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (typeof data[key] === 'string' && data[key].trim() === '') {
|
|
105
|
-
const raw = val instanceof tex_types_1.TexType ? val._raw : String(val);
|
|
106
|
-
if (raw.includes('optional') ||
|
|
107
|
-
raw.includes('nullable') ||
|
|
108
|
-
raw.includes('nullish') ||
|
|
109
|
-
raw.includes('?') ||
|
|
110
|
-
val?._isOptional) {
|
|
111
|
-
if (raw.includes('nullable') &&
|
|
112
|
-
!raw.includes('optional') &&
|
|
113
|
-
!raw.includes('nullish')) {
|
|
114
|
-
data[key] = null;
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
delete data[key];
|
|
118
|
-
// If deleted but default is defined, apply default
|
|
119
|
-
if (val instanceof tex_types_1.TexType && val.defaultValue !== undefined) {
|
|
120
|
-
data[key] =
|
|
121
|
-
typeof val.defaultValue === 'function'
|
|
122
|
-
? val.defaultValue()
|
|
123
|
-
: val.defaultValue;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if (data[key] !== undefined && data[key] !== null) {
|
|
129
|
-
if (val instanceof tex_types_1.TexType && val._raw.startsWith('date')) {
|
|
130
|
-
if (val._raw.includes('coerce')) {
|
|
131
|
-
if (typeof data[key] === 'string' ||
|
|
132
|
-
typeof data[key] === 'number') {
|
|
133
|
-
const d = new Date(data[key]);
|
|
134
|
-
if (isNaN(d.getTime())) {
|
|
135
|
-
throw new error_1.ValidatorError([
|
|
136
|
-
{
|
|
137
|
-
path: key,
|
|
138
|
-
message: 'Must be a valid date',
|
|
139
|
-
expected: 'valid date string or timestamp',
|
|
140
|
-
received: (0, error_1.formatReceivedValue)(data[key]),
|
|
141
|
-
code: 'INVALID_DATE',
|
|
142
|
-
},
|
|
143
|
-
]);
|
|
144
|
-
}
|
|
145
|
-
data[key] = d;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (!(data[key] instanceof Date) &&
|
|
149
|
-
typeof data[key] !== 'string' &&
|
|
150
|
-
typeof data[key] !== 'number') {
|
|
151
|
-
throw new error_1.ValidatorError([
|
|
152
|
-
{
|
|
153
|
-
path: key,
|
|
154
|
-
message: 'Must be a valid date',
|
|
155
|
-
expected: 'valid date or date string',
|
|
156
|
-
received: (0, error_1.formatReceivedValue)(data[key]),
|
|
157
|
-
code: 'INVALID_TYPE',
|
|
158
|
-
},
|
|
159
|
-
]);
|
|
160
|
-
}
|
|
161
|
-
if (data[key] instanceof Date && isNaN(data[key].getTime())) {
|
|
162
|
-
throw new error_1.ValidatorError([
|
|
163
|
-
{
|
|
164
|
-
path: key,
|
|
165
|
-
message: 'Must be a valid date',
|
|
166
|
-
expected: 'valid date string or timestamp',
|
|
167
|
-
received: (0, error_1.formatReceivedValue)(data[key]),
|
|
168
|
-
code: 'INVALID_DATE',
|
|
169
|
-
},
|
|
170
|
-
]);
|
|
171
|
-
}
|
|
172
|
-
if (val._raw.includes('minDate:')) {
|
|
173
|
-
const minStr = val._raw.match(/minDate:([^\s|]+)/)?.[1];
|
|
174
|
-
if (minStr &&
|
|
175
|
-
new Date(data[key]).getTime() < new Date(minStr).getTime()) {
|
|
176
|
-
throw new error_1.ValidatorError([
|
|
177
|
-
{
|
|
178
|
-
path: key,
|
|
179
|
-
message: `Date must be after ${minStr}`,
|
|
180
|
-
expected: `>= ${minStr}`,
|
|
181
|
-
received: (0, error_1.formatReceivedValue)(data[key]),
|
|
182
|
-
code: 'DATE_TOO_EARLY',
|
|
183
|
-
},
|
|
184
|
-
]);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (val._raw.includes('maxDate:')) {
|
|
188
|
-
const maxStr = val._raw.match(/maxDate:([^\s|]+)/)?.[1];
|
|
189
|
-
if (maxStr &&
|
|
190
|
-
new Date(data[key]).getTime() > new Date(maxStr).getTime()) {
|
|
191
|
-
throw new error_1.ValidatorError([
|
|
192
|
-
{
|
|
193
|
-
path: key,
|
|
194
|
-
message: `Date must be before ${maxStr}`,
|
|
195
|
-
expected: `<= ${maxStr}`,
|
|
196
|
-
received: (0, error_1.formatReceivedValue)(data[key]),
|
|
197
|
-
code: 'DATE_TOO_LATE',
|
|
198
|
-
},
|
|
199
|
-
]);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if (val instanceof tex_types_1.TexType && val._raw.startsWith('string')) {
|
|
204
|
-
if (val._raw.includes('coerce') && typeof data[key] !== 'string') {
|
|
205
|
-
data[key] = String(data[key]);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
if (val instanceof tex_types_1.TexType && val._raw.startsWith('number')) {
|
|
209
|
-
if (val._raw.includes('coerce') || this._isQueryOrParam) {
|
|
210
|
-
if (typeof data[key] === 'string' && data[key].trim() !== '') {
|
|
211
|
-
const n = Number(data[key]);
|
|
212
|
-
if (!isNaN(n)) {
|
|
213
|
-
data[key] = n;
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
throw new error_1.ValidatorError([
|
|
217
|
-
{
|
|
218
|
-
path: key,
|
|
219
|
-
message: 'Must be a number',
|
|
220
|
-
expected: 'number',
|
|
221
|
-
received: (0, error_1.formatReceivedValue)(data[key]),
|
|
222
|
-
code: 'INVALID_TYPE',
|
|
223
|
-
},
|
|
224
|
-
]);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
if (val instanceof tex_types_1.TexType && val._raw.startsWith('boolean')) {
|
|
230
|
-
if (val._raw.includes('coerce') || this._isQueryOrParam) {
|
|
231
|
-
if (typeof data[key] === 'string' && data[key].trim() !== '') {
|
|
232
|
-
const lower = data[key].toLowerCase().trim();
|
|
233
|
-
if (lower === 'true' || lower === '1') {
|
|
234
|
-
data[key] = true;
|
|
235
|
-
}
|
|
236
|
-
else if (lower === 'false' || lower === '0') {
|
|
237
|
-
data[key] = false;
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
throw new error_1.ValidatorError([
|
|
241
|
-
{
|
|
242
|
-
path: key,
|
|
243
|
-
message: 'Must be a boolean',
|
|
244
|
-
expected: 'boolean (true or false)',
|
|
245
|
-
received: (0, error_1.formatReceivedValue)(data[key]),
|
|
246
|
-
code: 'INVALID_TYPE',
|
|
247
|
-
},
|
|
248
|
-
]);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (val instanceof tex_types_1.TexType &&
|
|
254
|
-
val._raw.startsWith('array<') &&
|
|
255
|
-
val._raw.includes('coerce')) {
|
|
256
|
-
if (!Array.isArray(data[key])) {
|
|
257
|
-
if (typeof data[key] === 'string') {
|
|
258
|
-
const parts = data[key].includes(',')
|
|
259
|
-
? data[key].split(',').map((s) => s.trim())
|
|
260
|
-
: [data[key]];
|
|
261
|
-
if (val._raw.startsWith('array<number')) {
|
|
262
|
-
data[key] = parts.map((p) => {
|
|
263
|
-
const n = Number(p);
|
|
264
|
-
return isNaN(n) ? p : n;
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
else if (val._raw.startsWith('array<boolean')) {
|
|
268
|
-
data[key] = parts.map((p) => {
|
|
269
|
-
if (p.toLowerCase() === 'true')
|
|
270
|
-
return true;
|
|
271
|
-
if (p.toLowerCase() === 'false')
|
|
272
|
-
return false;
|
|
273
|
-
return p;
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
data[key] = parts;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
data[key] = [data[key]];
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
if (val._raw.startsWith('array<number')) {
|
|
286
|
-
data[key] = data[key].map((item) => {
|
|
287
|
-
if (typeof item === 'string') {
|
|
288
|
-
const n = Number(item);
|
|
289
|
-
return isNaN(n) ? item : n;
|
|
290
|
-
}
|
|
291
|
-
return item;
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
else if (val._raw.startsWith('array<boolean')) {
|
|
295
|
-
data[key] = data[key].map((item) => {
|
|
296
|
-
if (typeof item === 'string') {
|
|
297
|
-
if (item.toLowerCase() === 'true')
|
|
298
|
-
return true;
|
|
299
|
-
if (item.toLowerCase() === 'false')
|
|
300
|
-
return false;
|
|
301
|
-
}
|
|
302
|
-
return item;
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
if (val instanceof tex_types_1.TexType && val.sanitizers.length > 0) {
|
|
308
|
-
for (const s of val.sanitizers) {
|
|
309
|
-
if (data[key] === undefined || data[key] === null)
|
|
310
|
-
break;
|
|
311
|
-
data[key] = s(data[key]);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
const itemSchema = val?._item;
|
|
315
|
-
if (itemSchema && Array.isArray(data[key])) {
|
|
316
|
-
if (itemSchema instanceof tex_types_1.TexType &&
|
|
317
|
-
itemSchema.sanitizers.length > 0) {
|
|
318
|
-
for (let i = 0; i < data[key].length; i++) {
|
|
319
|
-
if (data[key][i] !== undefined && data[key][i] !== null) {
|
|
320
|
-
for (const s of itemSchema.sanitizers) {
|
|
321
|
-
if (data[key][i] === undefined || data[key][i] === null)
|
|
322
|
-
break;
|
|
323
|
-
data[key][i] = s(data[key][i]);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
97
|
+
const prep = coerceScalars ? plan.prepQuery : plan.prep;
|
|
98
|
+
for (const f of prep)
|
|
99
|
+
prepareField(data, f, coerceScalars);
|
|
331
100
|
}
|
|
332
|
-
// 2.
|
|
101
|
+
// 2. Type validation (compiled once per schema)
|
|
333
102
|
let parsedData;
|
|
334
103
|
try {
|
|
335
|
-
parsedData = this.validator
|
|
104
|
+
parsedData = this.validator(data);
|
|
336
105
|
}
|
|
337
106
|
catch (err) {
|
|
338
107
|
if (data === process.env &&
|
|
@@ -451,88 +220,33 @@ class TexEngine {
|
|
|
451
220
|
const received = (0, error_1.formatReceivedValue)(receivedRaw);
|
|
452
221
|
throw new error_1.ValidatorError([{ path, message, expected, received, code }]);
|
|
453
222
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
for (const
|
|
223
|
+
if (parsedData && typeof parsedData === 'object' && plan.post.length > 0) {
|
|
224
|
+
const errors = [];
|
|
225
|
+
for (const f of plan.post) {
|
|
226
|
+
const key = f.key;
|
|
227
|
+
// Preserve Date objects and ensure defaults are applied
|
|
457
228
|
if (parsedData[key] === undefined &&
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
parsedData[key] =
|
|
461
|
-
typeof val.defaultValue === 'function'
|
|
462
|
-
? val.defaultValue()
|
|
463
|
-
: val.defaultValue;
|
|
229
|
+
f.tex?.defaultValue !== undefined) {
|
|
230
|
+
parsedData[key] = resolveDefault(f.tex.defaultValue);
|
|
464
231
|
}
|
|
465
|
-
if (
|
|
466
|
-
val._raw.startsWith('date') &&
|
|
467
|
-
data &&
|
|
468
|
-
data[key] instanceof Date) {
|
|
232
|
+
if (f.isDate && data && data[key] instanceof Date) {
|
|
469
233
|
parsedData[key] = data[key];
|
|
470
234
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (parsedData && typeof parsedData === 'object') {
|
|
476
|
-
for (const [key, val] of Object.entries(this.rawSchema)) {
|
|
477
|
-
if (parsedData[key] !== undefined) {
|
|
478
|
-
const isNullableField = val instanceof tex_types_1.TexType &&
|
|
479
|
-
(val._raw.includes('nullable') || val._raw.includes('nullish'));
|
|
480
|
-
if (parsedData[key] === null && isNullableField) {
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
if (val instanceof tex_types_1.TexType && val.refinements.length > 0) {
|
|
484
|
-
for (const r of val.refinements) {
|
|
485
|
-
if (!r.async && !r.fn(parsedData[key])) {
|
|
486
|
-
const msg = typeof r.message === 'function'
|
|
487
|
-
? r.message(parsedData[key])
|
|
488
|
-
: r.message || 'Invalid value';
|
|
489
|
-
errors.push({
|
|
490
|
-
path: key,
|
|
491
|
-
message: msg,
|
|
492
|
-
expected: 'custom refinement rule',
|
|
493
|
-
received: (0, error_1.formatReceivedValue)(parsedData[key]),
|
|
494
|
-
code: 'CUSTOM_VALIDATION',
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
const itemSchema = val?._item;
|
|
500
|
-
if (itemSchema && Array.isArray(parsedData[key])) {
|
|
501
|
-
if (itemSchema instanceof tex_types_1.TexType &&
|
|
502
|
-
itemSchema.refinements.length > 0) {
|
|
503
|
-
const isItemNullable = itemSchema._raw.includes('nullable') ||
|
|
504
|
-
itemSchema._raw.includes('nullish');
|
|
505
|
-
for (let i = 0; i < parsedData[key].length; i++) {
|
|
506
|
-
if (parsedData[key][i] === null && isItemNullable)
|
|
507
|
-
continue;
|
|
508
|
-
for (const r of itemSchema.refinements) {
|
|
509
|
-
if (!r.async && !r.fn(parsedData[key][i])) {
|
|
510
|
-
const msg = typeof r.message === 'function'
|
|
511
|
-
? r.message(parsedData[key][i])
|
|
512
|
-
: r.message || 'Invalid value';
|
|
513
|
-
errors.push({
|
|
514
|
-
path: `${key}[${i}]`,
|
|
515
|
-
message: msg,
|
|
516
|
-
expected: 'custom refinement rule',
|
|
517
|
-
received: (0, error_1.formatReceivedValue)(parsedData[key][i]),
|
|
518
|
-
code: 'CUSTOM_VALIDATION',
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
235
|
+
// 3. Post-validation synchronous refinements
|
|
236
|
+
if (f.hasRefinements && parsedData[key] !== undefined) {
|
|
237
|
+
if (!(parsedData[key] === null && f.isNullable)) {
|
|
238
|
+
runSyncRefinements(f, parsedData[key], errors);
|
|
524
239
|
}
|
|
525
240
|
}
|
|
526
241
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
for (const t of val.transformations) {
|
|
242
|
+
if (errors.length > 0)
|
|
243
|
+
throw new error_1.ValidatorError(errors);
|
|
244
|
+
// 4. Post-validation field transformations
|
|
245
|
+
for (const f of plan.post) {
|
|
246
|
+
if (f.tex && f.tex.transformations.length > 0) {
|
|
247
|
+
const key = f.key;
|
|
248
|
+
if (parsedData[key] !== undefined) {
|
|
249
|
+
for (const t of f.tex.transformations) {
|
|
536
250
|
parsedData[key] = t(parsedData[key]);
|
|
537
251
|
}
|
|
538
252
|
}
|
|
@@ -807,4 +521,258 @@ class TexDiscriminatedUnionEngine {
|
|
|
807
521
|
}
|
|
808
522
|
}
|
|
809
523
|
exports.TexDiscriminatedUnionEngine = TexDiscriminatedUnionEngine;
|
|
524
|
+
function needsPrep(f, coerceScalars) {
|
|
525
|
+
return (f.tex?.defaultValue !== undefined ||
|
|
526
|
+
f.emptyString !== 'keep' ||
|
|
527
|
+
f.isDate ||
|
|
528
|
+
f.stringCoerce ||
|
|
529
|
+
f.number === 'coerce' ||
|
|
530
|
+
f.boolean === 'coerce' ||
|
|
531
|
+
(coerceScalars && (f.number === 'query' || f.boolean === 'query')) ||
|
|
532
|
+
f.array !== 'no' ||
|
|
533
|
+
(f.tex !== null && f.tex.sanitizers.length > 0) ||
|
|
534
|
+
(f.item !== null && f.item.sanitizers.length > 0));
|
|
535
|
+
}
|
|
536
|
+
function splitPlan(plan) {
|
|
537
|
+
return {
|
|
538
|
+
prep: plan.filter((f) => needsPrep(f, false)),
|
|
539
|
+
prepQuery: plan.filter((f) => needsPrep(f, true)),
|
|
540
|
+
post: plan.filter((f) => f.tex?.defaultValue !== undefined ||
|
|
541
|
+
f.isDate ||
|
|
542
|
+
f.hasRefinements ||
|
|
543
|
+
(f.tex !== null && f.tex.transformations.length > 0)),
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function scalarMode(plain, raw, type, coerce) {
|
|
547
|
+
if (!plain || !raw.startsWith(type))
|
|
548
|
+
return 'no';
|
|
549
|
+
return coerce ? 'coerce' : 'query';
|
|
550
|
+
}
|
|
551
|
+
function arrayMode(plain, raw, coerce) {
|
|
552
|
+
if (!plain || !coerce || !raw.startsWith('array<'))
|
|
553
|
+
return 'no';
|
|
554
|
+
if (raw.startsWith('array<number'))
|
|
555
|
+
return 'number';
|
|
556
|
+
if (raw.startsWith('array<boolean'))
|
|
557
|
+
return 'boolean';
|
|
558
|
+
return 'other';
|
|
559
|
+
}
|
|
560
|
+
function buildPlan(rawSchema) {
|
|
561
|
+
const plan = [];
|
|
562
|
+
for (const key of Object.keys(rawSchema)) {
|
|
563
|
+
const val = rawSchema[key];
|
|
564
|
+
const tex = val instanceof tex_types_1.TexType ? val : null;
|
|
565
|
+
const raw = tex ? tex._raw : String(val);
|
|
566
|
+
// Unions coerce per alternative inside the compiled rule
|
|
567
|
+
const plain = tex !== null && !raw.includes('||');
|
|
568
|
+
const coerce = raw.includes('coerce');
|
|
569
|
+
const nullable = raw.includes('nullable');
|
|
570
|
+
const nullish = raw.includes('nullish');
|
|
571
|
+
const optional = raw.includes('optional');
|
|
572
|
+
let emptyString = 'keep';
|
|
573
|
+
if (optional ||
|
|
574
|
+
nullable ||
|
|
575
|
+
nullish ||
|
|
576
|
+
raw.includes('?') ||
|
|
577
|
+
val?._isOptional) {
|
|
578
|
+
emptyString = nullable && !optional && !nullish ? 'null' : 'delete';
|
|
579
|
+
}
|
|
580
|
+
const item = val?._item instanceof tex_types_1.TexType ? val._item : null;
|
|
581
|
+
const isDate = plain && raw.startsWith('date');
|
|
582
|
+
plan.push({
|
|
583
|
+
key,
|
|
584
|
+
tex,
|
|
585
|
+
emptyString,
|
|
586
|
+
isDate,
|
|
587
|
+
dateCoerce: isDate && coerce,
|
|
588
|
+
minDate: isDate ? raw.match(/minDate:([^\s|]+)/)?.[1] : undefined,
|
|
589
|
+
maxDate: isDate ? raw.match(/maxDate:([^\s|]+)/)?.[1] : undefined,
|
|
590
|
+
stringCoerce: plain && coerce && raw.startsWith('string'),
|
|
591
|
+
number: scalarMode(plain, raw, 'number', coerce),
|
|
592
|
+
boolean: scalarMode(plain, raw, 'boolean', coerce),
|
|
593
|
+
array: arrayMode(plain, raw, coerce),
|
|
594
|
+
item,
|
|
595
|
+
isNullable: tex !== null && (nullable || nullish),
|
|
596
|
+
hasRefinements: (tex !== null && tex.refinements.length > 0) ||
|
|
597
|
+
(item !== null && item.refinements.length > 0),
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
return plan;
|
|
601
|
+
}
|
|
602
|
+
function resolveDefault(defaultValue) {
|
|
603
|
+
return typeof defaultValue === 'function' ? defaultValue() : defaultValue;
|
|
604
|
+
}
|
|
605
|
+
function fieldError(key, value, message, expected, code) {
|
|
606
|
+
throw new error_1.ValidatorError([
|
|
607
|
+
{
|
|
608
|
+
path: key,
|
|
609
|
+
message,
|
|
610
|
+
expected,
|
|
611
|
+
received: (0, error_1.formatReceivedValue)(value),
|
|
612
|
+
code,
|
|
613
|
+
},
|
|
614
|
+
]);
|
|
615
|
+
}
|
|
616
|
+
function prepareDate(key, value, f) {
|
|
617
|
+
if (f.dateCoerce &&
|
|
618
|
+
(typeof value === 'string' || typeof value === 'number')) {
|
|
619
|
+
const d = new Date(value);
|
|
620
|
+
if (isNaN(d.getTime())) {
|
|
621
|
+
fieldError(key, value, 'Must be a valid date', 'valid date string or timestamp', 'INVALID_DATE');
|
|
622
|
+
}
|
|
623
|
+
value = d;
|
|
624
|
+
}
|
|
625
|
+
if (!(value instanceof Date) &&
|
|
626
|
+
typeof value !== 'string' &&
|
|
627
|
+
typeof value !== 'number') {
|
|
628
|
+
fieldError(key, value, 'Must be a valid date', 'valid date or date string', 'INVALID_TYPE');
|
|
629
|
+
}
|
|
630
|
+
if (value instanceof Date && isNaN(value.getTime())) {
|
|
631
|
+
fieldError(key, value, 'Must be a valid date', 'valid date string or timestamp', 'INVALID_DATE');
|
|
632
|
+
}
|
|
633
|
+
const time = new Date(value).getTime();
|
|
634
|
+
if (f.minDate && time < new Date(f.minDate).getTime()) {
|
|
635
|
+
fieldError(key, value, `Date must be after ${f.minDate}`, `>= ${f.minDate}`, 'DATE_TOO_EARLY');
|
|
636
|
+
}
|
|
637
|
+
if (f.maxDate && time > new Date(f.maxDate).getTime()) {
|
|
638
|
+
fieldError(key, value, `Date must be before ${f.maxDate}`, `<= ${f.maxDate}`, 'DATE_TOO_LATE');
|
|
639
|
+
}
|
|
640
|
+
return value;
|
|
641
|
+
}
|
|
642
|
+
function prepareField(data, f, coerceScalars) {
|
|
643
|
+
const key = f.key;
|
|
644
|
+
const tex = f.tex;
|
|
645
|
+
// Apply default value / lazy factory function if field is omitted
|
|
646
|
+
if (data[key] === undefined && tex?.defaultValue !== undefined) {
|
|
647
|
+
data[key] = resolveDefault(tex.defaultValue);
|
|
648
|
+
}
|
|
649
|
+
if (f.emptyString !== 'keep' &&
|
|
650
|
+
typeof data[key] === 'string' &&
|
|
651
|
+
data[key].trim() === '') {
|
|
652
|
+
if (f.emptyString === 'null') {
|
|
653
|
+
data[key] = null;
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
delete data[key];
|
|
657
|
+
if (tex?.defaultValue !== undefined) {
|
|
658
|
+
data[key] = resolveDefault(tex.defaultValue);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
let value = data[key];
|
|
663
|
+
if (value === undefined || value === null)
|
|
664
|
+
return;
|
|
665
|
+
if (f.isDate)
|
|
666
|
+
data[key] = value = prepareDate(key, value, f);
|
|
667
|
+
if (f.stringCoerce && typeof value !== 'string') {
|
|
668
|
+
data[key] = value = String(value);
|
|
669
|
+
}
|
|
670
|
+
if ((f.number === 'coerce' || (f.number === 'query' && coerceScalars)) &&
|
|
671
|
+
typeof value === 'string' &&
|
|
672
|
+
value.trim() !== '') {
|
|
673
|
+
const n = Number(value);
|
|
674
|
+
if (isNaN(n))
|
|
675
|
+
fieldError(key, value, 'Must be a number', 'number', 'INVALID_TYPE');
|
|
676
|
+
data[key] = value = n;
|
|
677
|
+
}
|
|
678
|
+
if ((f.boolean === 'coerce' || (f.boolean === 'query' && coerceScalars)) &&
|
|
679
|
+
typeof value === 'string' &&
|
|
680
|
+
value.trim() !== '') {
|
|
681
|
+
const lower = value.toLowerCase().trim();
|
|
682
|
+
if (lower === 'true' || lower === '1')
|
|
683
|
+
data[key] = value = true;
|
|
684
|
+
else if (lower === 'false' || lower === '0')
|
|
685
|
+
data[key] = value = false;
|
|
686
|
+
else {
|
|
687
|
+
fieldError(key, value, 'Must be a boolean', 'boolean (true or false)', 'INVALID_TYPE');
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (f.array !== 'no')
|
|
691
|
+
data[key] = coerceArray(value, f.array);
|
|
692
|
+
if (tex !== null && tex.sanitizers.length > 0) {
|
|
693
|
+
for (const s of tex.sanitizers) {
|
|
694
|
+
if (data[key] === undefined || data[key] === null)
|
|
695
|
+
break;
|
|
696
|
+
data[key] = s(data[key]);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const item = f.item;
|
|
700
|
+
if (item !== null && item.sanitizers.length > 0 && Array.isArray(data[key])) {
|
|
701
|
+
const arr = data[key];
|
|
702
|
+
for (let i = 0; i < arr.length; i++) {
|
|
703
|
+
for (const s of item.sanitizers) {
|
|
704
|
+
if (arr[i] === undefined || arr[i] === null)
|
|
705
|
+
break;
|
|
706
|
+
arr[i] = s(arr[i]);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function coerceItem(item, kind) {
|
|
712
|
+
if (typeof item !== 'string')
|
|
713
|
+
return item;
|
|
714
|
+
if (kind === 'number') {
|
|
715
|
+
const n = Number(item);
|
|
716
|
+
return isNaN(n) ? item : n;
|
|
717
|
+
}
|
|
718
|
+
if (kind === 'boolean') {
|
|
719
|
+
const lower = item.toLowerCase();
|
|
720
|
+
if (lower === 'true')
|
|
721
|
+
return true;
|
|
722
|
+
if (lower === 'false')
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
return item;
|
|
726
|
+
}
|
|
727
|
+
function coerceArray(value, kind) {
|
|
728
|
+
if (!Array.isArray(value)) {
|
|
729
|
+
if (typeof value !== 'string')
|
|
730
|
+
return [value];
|
|
731
|
+
const parts = value.includes(',')
|
|
732
|
+
? value.split(',').map((s) => s.trim())
|
|
733
|
+
: [value];
|
|
734
|
+
return kind === 'other' ? parts : parts.map((p) => coerceItem(p, kind));
|
|
735
|
+
}
|
|
736
|
+
return kind === 'other' ? value : value.map((v) => coerceItem(v, kind));
|
|
737
|
+
}
|
|
738
|
+
function refinementMessage(r, value) {
|
|
739
|
+
return typeof r.message === 'function'
|
|
740
|
+
? r.message(value)
|
|
741
|
+
: r.message || 'Invalid value';
|
|
742
|
+
}
|
|
743
|
+
function runSyncRefinements(f, value, errors) {
|
|
744
|
+
if (f.tex !== null) {
|
|
745
|
+
for (const r of f.tex.refinements) {
|
|
746
|
+
if (!r.async && !r.fn(value)) {
|
|
747
|
+
errors.push({
|
|
748
|
+
path: f.key,
|
|
749
|
+
message: refinementMessage(r, value),
|
|
750
|
+
expected: 'custom refinement rule',
|
|
751
|
+
received: (0, error_1.formatReceivedValue)(value),
|
|
752
|
+
code: 'CUSTOM_VALIDATION',
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const item = f.item;
|
|
758
|
+
if (item === null || item.refinements.length === 0 || !Array.isArray(value)) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const itemNullable = item._raw.includes('nullable') || item._raw.includes('nullish');
|
|
762
|
+
for (let i = 0; i < value.length; i++) {
|
|
763
|
+
if (value[i] === null && itemNullable)
|
|
764
|
+
continue;
|
|
765
|
+
for (const r of item.refinements) {
|
|
766
|
+
if (!r.async && !r.fn(value[i])) {
|
|
767
|
+
errors.push({
|
|
768
|
+
path: `${f.key}[${i}]`,
|
|
769
|
+
message: refinementMessage(r, value[i]),
|
|
770
|
+
expected: 'custom refinement rule',
|
|
771
|
+
received: (0, error_1.formatReceivedValue)(value[i]),
|
|
772
|
+
code: 'CUSTOM_VALIDATION',
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
810
778
|
//# sourceMappingURL=engine.js.map
|