ascertain 2.0.88 → 3.0.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/README.md +323 -153
- package/build/index.cjs +483 -180
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +231 -186
- package/build/index.js +474 -180
- package/build/index.js.map +1 -1
- package/package.json +22 -19
- package/src/index.ts +991 -0
package/build/index.js
CHANGED
|
@@ -1,32 +1,52 @@
|
|
|
1
|
-
class Operator {
|
|
2
|
-
schemas;
|
|
3
|
-
constructor(schemas){
|
|
4
|
-
this.schemas = schemas;
|
|
5
|
-
if (schemas.length === 0) {
|
|
6
|
-
throw new TypeError(`Operation schema ${this.constructor.name} must have at least one element`);
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
1
|
export const $keys = Symbol.for('@@keys');
|
|
11
2
|
export const $values = Symbol.for('@@values');
|
|
12
3
|
export const $strict = Symbol.for('@@strict');
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
4
|
+
const $op = Symbol.for('@@op');
|
|
5
|
+
const OR = Symbol.for('@@or');
|
|
6
|
+
const AND = Symbol.for('@@and');
|
|
7
|
+
const OPTIONAL = Symbol.for('@@optional');
|
|
8
|
+
const TUPLE = Symbol.for('@@tuple');
|
|
9
|
+
const DISCRIMINATED = Symbol.for('@@discriminated');
|
|
10
|
+
const OrCtor = function(schemas) {
|
|
11
|
+
this.schemas = schemas;
|
|
12
|
+
};
|
|
13
|
+
OrCtor.prototype[$op] = OR;
|
|
14
|
+
const AndCtor = function(schemas) {
|
|
15
|
+
this.schemas = schemas;
|
|
16
|
+
};
|
|
17
|
+
AndCtor.prototype[$op] = AND;
|
|
18
|
+
const OptionalCtor = function(schema) {
|
|
19
|
+
this.schemas = [
|
|
20
|
+
schema
|
|
21
|
+
];
|
|
22
|
+
};
|
|
23
|
+
OptionalCtor.prototype[$op] = OPTIONAL;
|
|
24
|
+
const TupleCtor = function(schemas) {
|
|
25
|
+
this.schemas = schemas;
|
|
26
|
+
};
|
|
27
|
+
TupleCtor.prototype[$op] = TUPLE;
|
|
28
|
+
const DiscriminatedCtor = function(schemas, key) {
|
|
29
|
+
this.schemas = schemas;
|
|
30
|
+
this.key = key;
|
|
31
|
+
};
|
|
32
|
+
DiscriminatedCtor.prototype[$op] = DISCRIMINATED;
|
|
33
|
+
export const or = (...schemas)=>{
|
|
34
|
+
if (schemas.length === 0) throw new TypeError('Operator requires at least one schema');
|
|
35
|
+
return new OrCtor(schemas);
|
|
36
|
+
};
|
|
37
|
+
export const and = (...schemas)=>{
|
|
38
|
+
if (schemas.length === 0) throw new TypeError('Operator requires at least one schema');
|
|
39
|
+
return new AndCtor(schemas);
|
|
40
|
+
};
|
|
41
|
+
export const optional = (schema)=>new OptionalCtor(schema);
|
|
42
|
+
export const tuple = (...schemas)=>{
|
|
43
|
+
if (schemas.length === 0) throw new TypeError('Operator requires at least one schema');
|
|
44
|
+
return new TupleCtor(schemas);
|
|
45
|
+
};
|
|
46
|
+
export const discriminated = (schemas, key)=>{
|
|
47
|
+
if (schemas.length === 0) throw new TypeError('discriminated requires at least one schema');
|
|
48
|
+
return new DiscriminatedCtor(schemas, key);
|
|
49
|
+
};
|
|
30
50
|
export const fromBase64 = typeof Buffer === 'undefined' ? (value)=>atob(value) : (value)=>Buffer.from(value, 'base64').toString('utf-8');
|
|
31
51
|
const MULTIPLIERS = {
|
|
32
52
|
ms: 1,
|
|
@@ -36,13 +56,25 @@ const MULTIPLIERS = {
|
|
|
36
56
|
d: 86400000,
|
|
37
57
|
w: 604800000
|
|
38
58
|
};
|
|
59
|
+
const TIME_REGEX = /^(\d*\.?\d*)(ms|s|m|h|d|w)?$/;
|
|
39
60
|
export const asError = (message)=>new TypeError(message);
|
|
40
61
|
export const as = {
|
|
41
62
|
string: (value)=>{
|
|
42
63
|
return typeof value === 'string' ? value : asError(`Invalid value "${value}", expected a string`);
|
|
43
64
|
},
|
|
44
65
|
number: (value)=>{
|
|
45
|
-
|
|
66
|
+
if (typeof value !== 'string') {
|
|
67
|
+
return asError(`Invalid value ${value}, expected a valid number`);
|
|
68
|
+
}
|
|
69
|
+
const start = value[0] === '-' || value[0] === '+' ? 1 : 0;
|
|
70
|
+
const c0 = value.charCodeAt(start);
|
|
71
|
+
const c1 = value.charCodeAt(start + 1) | 32;
|
|
72
|
+
if (c0 === 48 && (c1 === 120 || c1 === 111 || c1 === 98)) {
|
|
73
|
+
const result = Number(start ? value.slice(1) : value);
|
|
74
|
+
if (Number.isNaN(result)) return asError(`Invalid value ${value}, expected a valid number`);
|
|
75
|
+
return value[0] === '-' ? -result : result;
|
|
76
|
+
}
|
|
77
|
+
const result = value.trim() ? Number(value) : NaN;
|
|
46
78
|
return Number.isNaN(result) ? asError(`Invalid value ${value}, expected a valid number`) : result;
|
|
47
79
|
},
|
|
48
80
|
date: (value)=>{
|
|
@@ -51,12 +83,16 @@ export const as = {
|
|
|
51
83
|
return Number.isNaN(date.valueOf()) ? asError(`Invalid value "${value}", expected a valid date format`) : date;
|
|
52
84
|
},
|
|
53
85
|
time: (value, conversionFactor = 1)=>{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
86
|
+
if (!value) return asError(`Invalid value ${value}, expected a valid time format`);
|
|
87
|
+
const matches = value.match(TIME_REGEX);
|
|
88
|
+
if (!matches) return asError(`Invalid value ${value}, expected a valid time format`);
|
|
89
|
+
const [, amount, unit = 'ms'] = matches;
|
|
90
|
+
const multiplier = MULTIPLIERS[unit];
|
|
91
|
+
const parsed = parseFloat(amount);
|
|
92
|
+
if (!multiplier || Number.isNaN(parsed)) {
|
|
93
|
+
return asError(`Invalid value ${value}, expected a valid time format`);
|
|
58
94
|
}
|
|
59
|
-
return
|
|
95
|
+
return Math.floor(parsed * multiplier / conversionFactor);
|
|
60
96
|
},
|
|
61
97
|
boolean: (value)=>/^(0|1|true|false|enabled|disabled)$/i.test(value) ? /^(1|true|enabled)$/i.test(value) : asError(`Invalid value ${value}, expected a boolean like`),
|
|
62
98
|
array: (value, delimiter)=>value?.split?.(delimiter) ?? asError(`Invalid value ${value}, expected an array`),
|
|
@@ -77,183 +113,441 @@ export const as = {
|
|
|
77
113
|
};
|
|
78
114
|
class Context {
|
|
79
115
|
registry = [];
|
|
116
|
+
lookupMap = new Map();
|
|
80
117
|
varIndex = 0;
|
|
81
118
|
register(value) {
|
|
82
|
-
|
|
83
|
-
|
|
119
|
+
const index = this.lookupMap.get(value);
|
|
120
|
+
if (index !== undefined) {
|
|
121
|
+
return index;
|
|
122
|
+
}
|
|
123
|
+
{
|
|
124
|
+
const index = this.registry.push(value) - 1;
|
|
125
|
+
this.lookupMap.set(value, index);
|
|
126
|
+
return index;
|
|
84
127
|
}
|
|
85
|
-
return this.registry.indexOf(value);
|
|
86
128
|
}
|
|
87
129
|
unique(prefix) {
|
|
88
130
|
return `${prefix}$$${this.varIndex++}`;
|
|
89
131
|
}
|
|
90
132
|
}
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
133
|
+
const isTagged = (schema)=>schema?.[$op] !== undefined;
|
|
134
|
+
const childMode = (mode, key)=>{
|
|
135
|
+
if (typeof key === 'object' && 'dynamic' in key) {
|
|
136
|
+
return {
|
|
137
|
+
fast: false,
|
|
138
|
+
firstError: mode.firstError,
|
|
139
|
+
issues: mode.issues,
|
|
140
|
+
path: mode.path,
|
|
141
|
+
pathExpr: `[${mode.path.map((k)=>JSON.stringify(k)).join(',')}${mode.path.length ? ',' : ''}${key.dynamic}]`
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const newPath = [
|
|
145
|
+
...mode.path,
|
|
146
|
+
key
|
|
147
|
+
];
|
|
148
|
+
return {
|
|
149
|
+
fast: false,
|
|
150
|
+
firstError: mode.firstError,
|
|
151
|
+
issues: mode.issues,
|
|
152
|
+
path: newPath,
|
|
153
|
+
pathExpr: JSON.stringify(newPath)
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
const toLiteral = (value)=>typeof value === 'bigint' ? `${value}n` : JSON.stringify(value);
|
|
157
|
+
const codeGen = (schema, context, valuePath, mode)=>{
|
|
158
|
+
const emit = mode.fast ? null : mode.firstError ? (msg)=>`${mode.issues} = [{ message: ${msg}, path: ${mode.pathExpr} }]; return ${mode.issues};` : (msg)=>`(${mode.issues} || (${mode.issues} = [])).push({ message: ${msg}, path: ${mode.pathExpr} });`;
|
|
159
|
+
const fail = mode.fast ? mode.onFail ?? 'return false;' : '';
|
|
160
|
+
if (isTagged(schema)) {
|
|
161
|
+
const tag = schema[$op];
|
|
162
|
+
if (tag === AND) {
|
|
163
|
+
const valueAlias = context.unique('v');
|
|
164
|
+
const code = schema.schemas.map((s)=>codeGen(s, context, valueAlias, mode)).join('\n');
|
|
165
|
+
return `const ${valueAlias} = ${valuePath};\n${code}`;
|
|
166
|
+
} else if (tag === OR) {
|
|
167
|
+
const valueAlias = context.unique('v');
|
|
168
|
+
const foundValid = context.unique('valid');
|
|
169
|
+
if (mode.fast) {
|
|
170
|
+
const branches = schema.schemas.map((s)=>{
|
|
171
|
+
const branchValid = context.unique('valid');
|
|
172
|
+
const branchCode = codeGen(s, context, valueAlias, {
|
|
173
|
+
...mode,
|
|
174
|
+
onFail: `${branchValid} = false;`
|
|
175
|
+
});
|
|
176
|
+
return `if (!${foundValid}) { let ${branchValid} = true; ${branchCode} if (${branchValid}) { ${foundValid} = true; } }`;
|
|
177
|
+
});
|
|
178
|
+
return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
|
|
179
|
+
} else if (mode.firstError) {
|
|
180
|
+
const firstBranchIssues = context.unique('iss');
|
|
181
|
+
const branches = schema.schemas.map((s, idx)=>{
|
|
182
|
+
const branchIssues = context.unique('iss');
|
|
183
|
+
const branchCode = codeGen(s, context, valueAlias, {
|
|
184
|
+
fast: false,
|
|
185
|
+
firstError: true,
|
|
186
|
+
issues: branchIssues,
|
|
187
|
+
path: mode.path,
|
|
188
|
+
pathExpr: mode.pathExpr
|
|
189
|
+
}).replace(new RegExp(`; return ${branchIssues};`, 'g'), ';');
|
|
190
|
+
if (idx === 0) {
|
|
191
|
+
return `if (!${foundValid}) { let ${branchIssues}; ${branchCode} if (!${branchIssues}) { ${foundValid} = true; } else { ${firstBranchIssues} = ${branchIssues}; } }`;
|
|
192
|
+
}
|
|
193
|
+
return `if (!${foundValid}) { let ${branchIssues}; ${branchCode} if (!${branchIssues}) { ${foundValid} = true; } }`;
|
|
194
|
+
});
|
|
195
|
+
return `const ${valueAlias} = ${valuePath};\nlet ${firstBranchIssues};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { return ${firstBranchIssues}; }`;
|
|
196
|
+
} else {
|
|
197
|
+
const localIssues = context.unique('iss');
|
|
198
|
+
const branches = schema.schemas.map((s)=>{
|
|
199
|
+
const branchIssues = context.unique('iss');
|
|
200
|
+
const branchCode = codeGen(s, context, valueAlias, {
|
|
201
|
+
fast: false,
|
|
202
|
+
firstError: false,
|
|
203
|
+
issues: branchIssues,
|
|
204
|
+
path: mode.path,
|
|
205
|
+
pathExpr: mode.pathExpr
|
|
206
|
+
});
|
|
207
|
+
return `if (!${foundValid}) { let ${branchIssues}; ${branchCode} if (!${branchIssues}) { ${foundValid} = true; } else { ${localIssues}.push(...${branchIssues}); } }`;
|
|
208
|
+
});
|
|
209
|
+
return `const ${valueAlias} = ${valuePath};\nconst ${localIssues} = [];\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { (${mode.issues} || (${mode.issues} = [])).push(...${localIssues}); }`;
|
|
210
|
+
}
|
|
211
|
+
} else if (tag === OPTIONAL) {
|
|
212
|
+
const valueAlias = context.unique('v');
|
|
213
|
+
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(schema.schemas[0], context, valueAlias, mode)} }`;
|
|
214
|
+
} else if (tag === TUPLE) {
|
|
215
|
+
const valueAlias = context.unique('v');
|
|
216
|
+
if (mode.fast) {
|
|
217
|
+
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object' || !Array.isArray(${valueAlias}) || ${valueAlias}.length !== ${schema.schemas.length}) { ${fail} }\n${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, mode)).join('\n')}`;
|
|
218
|
+
} else {
|
|
219
|
+
return [
|
|
220
|
+
`const ${valueAlias} = ${valuePath};`,
|
|
221
|
+
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
|
|
222
|
+
`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Array\``)} }`,
|
|
223
|
+
`else if (!Array.isArray(${valueAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`,
|
|
224
|
+
`else if (${valueAlias}.length !== ${schema.schemas.length}) { ${emit(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.schemas.length}\``)} }`,
|
|
225
|
+
`else { ${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`
|
|
226
|
+
].join('\n');
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
const { key, schemas } = schema;
|
|
230
|
+
const valueAlias = context.unique('v');
|
|
231
|
+
const discriminantAlias = context.unique('d');
|
|
232
|
+
const keyStr = JSON.stringify(key);
|
|
233
|
+
const variants = [];
|
|
234
|
+
for (const s of schemas){
|
|
235
|
+
if (typeof s !== 'object' || s === null || !(key in s)) {
|
|
236
|
+
throw new TypeError(`discriminated: each schema must have the discriminant key "${key}"`);
|
|
237
|
+
}
|
|
238
|
+
const discriminantValue = s[key];
|
|
239
|
+
if (typeof discriminantValue !== 'string' && typeof discriminantValue !== 'number' && typeof discriminantValue !== 'boolean') {
|
|
240
|
+
throw new TypeError(`discriminated: discriminant value must be a string, number, or boolean literal`);
|
|
241
|
+
}
|
|
242
|
+
variants.push({
|
|
243
|
+
value: discriminantValue,
|
|
244
|
+
schema: s
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (mode.fast) {
|
|
248
|
+
const branches = variants.map(({ value, schema: s })=>{
|
|
249
|
+
const branchCode = codeGen(s, context, valueAlias, mode);
|
|
250
|
+
return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${branchCode} }`;
|
|
251
|
+
});
|
|
252
|
+
return [
|
|
253
|
+
`const ${valueAlias} = ${valuePath};`,
|
|
254
|
+
`if (${valueAlias} === null || ${valueAlias} === undefined || typeof ${valueAlias} !== 'object' || ${valueAlias} instanceof Error) { ${fail} }`,
|
|
255
|
+
`const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
|
|
256
|
+
branches.join(' else ') + ` else { ${fail} }`
|
|
257
|
+
].join('\n');
|
|
258
|
+
} else {
|
|
259
|
+
const validValues = variants.map((v)=>JSON.stringify(v.value)).join(', ');
|
|
260
|
+
const branches = variants.map(({ value, schema: s })=>{
|
|
261
|
+
const branchCode = codeGen(s, context, valueAlias, mode);
|
|
262
|
+
return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${branchCode} }`;
|
|
263
|
+
});
|
|
264
|
+
return [
|
|
265
|
+
`const ${valueAlias} = ${valuePath};`,
|
|
266
|
+
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
|
|
267
|
+
`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an object\``)} }`,
|
|
268
|
+
`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`,
|
|
269
|
+
`else {`,
|
|
270
|
+
` const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
|
|
271
|
+
` ${branches.join(' else ')} else { ${emit(`\`Invalid discriminant value \${JSON.stringify(${discriminantAlias})}, expected one of: ${validValues}\``)} }`,
|
|
272
|
+
`}`
|
|
273
|
+
].join('\n');
|
|
274
|
+
}
|
|
149
275
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} else if (Array.isArray(schema)) {
|
|
276
|
+
}
|
|
277
|
+
if (typeof schema === 'function') {
|
|
153
278
|
const valueAlias = context.unique('v');
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
279
|
+
const name = schema?.name;
|
|
280
|
+
const s = schema;
|
|
281
|
+
const primitiveType = s === String ? 'string' : s === Number ? 'number' : s === Boolean ? 'boolean' : s === BigInt ? 'bigint' : s === Symbol ? 'symbol' : null;
|
|
282
|
+
if (mode.fast) {
|
|
283
|
+
if (primitiveType) {
|
|
284
|
+
const checks = [
|
|
285
|
+
`typeof ${valueAlias} !== '${primitiveType}'`
|
|
286
|
+
];
|
|
287
|
+
if (primitiveType === 'number') checks.push(`Number.isNaN(${valueAlias})`);
|
|
288
|
+
return `const ${valueAlias} = ${valuePath};\nif (${checks.join(' || ')}) { ${fail} }`;
|
|
289
|
+
} else if (name === 'Function') {
|
|
290
|
+
return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== 'function') { ${fail} }`;
|
|
291
|
+
} else {
|
|
292
|
+
const isError = schema === Error || schema?.prototype instanceof Error;
|
|
293
|
+
const index = context.register(schema);
|
|
294
|
+
const registryAlias = context.unique('r');
|
|
295
|
+
return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = ctx.registry[${index}];\nif (${valueAlias} === null || ${valueAlias} === undefined${isError ? '' : ` || ${valueAlias} instanceof Error`} || (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) || (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) || Number.isNaN(${valueAlias}?.valueOf?.())) { ${fail} }`;
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
const code = [
|
|
299
|
+
`const ${valueAlias} = ${valuePath};`
|
|
300
|
+
];
|
|
301
|
+
if (primitiveType) {
|
|
302
|
+
code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
|
|
303
|
+
code.push(`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`);
|
|
304
|
+
code.push(`else if (typeof ${valueAlias} !== '${primitiveType}') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected type ${name}\``)} }`);
|
|
305
|
+
if (primitiveType === 'number') code.push(`else if (Number.isNaN(${valueAlias})) { ${emit(`\`Invalid value \${${valueAlias}}, expected a valid ${name}\``)} }`);
|
|
306
|
+
} else if (name === 'Function') {
|
|
307
|
+
code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
|
|
308
|
+
code.push(`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`);
|
|
309
|
+
code.push(`else if (typeof ${valueAlias} !== 'function') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected type Function\``)} }`);
|
|
310
|
+
} else {
|
|
311
|
+
const isError = schema === Error || schema?.prototype instanceof Error;
|
|
312
|
+
const index = context.register(schema);
|
|
313
|
+
const registryAlias = context.unique('r');
|
|
314
|
+
code.push(`const ${registryAlias} = ctx.registry[${index}];`);
|
|
315
|
+
code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
|
|
316
|
+
if (!isError) code.push(`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`);
|
|
317
|
+
code.push(`else if (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}?.constructor?.name}, expected an instance of ${name}\``)} }`);
|
|
318
|
+
code.push(`else if (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) { ${emit(`\`Invalid type \${${valueAlias}?.constructor?.name}, expected type ${name}\``)} }`);
|
|
319
|
+
code.push(`else if (Number.isNaN(${valueAlias}?.valueOf?.())) { ${emit(`\`Invalid value \${${valueAlias}}, expected a valid ${name}\``)} }`);
|
|
320
|
+
}
|
|
321
|
+
return code.join('\n');
|
|
168
322
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
323
|
+
}
|
|
324
|
+
if (Array.isArray(schema)) {
|
|
325
|
+
const valueAlias = context.unique('v');
|
|
326
|
+
if (mode.fast) {
|
|
327
|
+
let code = `const ${valueAlias} = ${valuePath};\nif (!Array.isArray(${valueAlias})) { ${fail} }`;
|
|
328
|
+
if (schema.length === 1) {
|
|
329
|
+
const value = context.unique('val');
|
|
330
|
+
const key = context.unique('key');
|
|
331
|
+
code += `\nfor (let ${key} = 0; ${key} < ${valueAlias}.length; ${key}++) { const ${value} = ${valueAlias}[${key}]; ${codeGen(schema[0], context, value, mode)} }`;
|
|
332
|
+
} else if (schema.length > 1) {
|
|
333
|
+
code += `\nif (${valueAlias}.length > ${schema.length}) { ${fail} }`;
|
|
334
|
+
code += '\n' + schema.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, mode)).join('\n');
|
|
335
|
+
}
|
|
336
|
+
return code;
|
|
179
337
|
} else {
|
|
180
|
-
const valueAlias = context.unique('v');
|
|
181
338
|
const code = [
|
|
182
339
|
`const ${valueAlias} = ${valuePath};`,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
340
|
+
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
|
|
341
|
+
`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`,
|
|
342
|
+
`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Array\``)} }`,
|
|
343
|
+
`else if (!Array.isArray(${valueAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`
|
|
186
344
|
];
|
|
187
|
-
if (
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
${
|
|
196
|
-
`);
|
|
345
|
+
if (schema.length > 0) {
|
|
346
|
+
const value = context.unique('val');
|
|
347
|
+
const key = context.unique('key');
|
|
348
|
+
if (schema.length === 1) {
|
|
349
|
+
code.push(`else { for (let ${key} = 0; ${key} < ${valueAlias}.length; ${key}++) { const ${value} = ${valueAlias}[${key}]; ${codeGen(schema[0], context, value, childMode(mode, {
|
|
350
|
+
dynamic: key
|
|
351
|
+
}))} } }`);
|
|
352
|
+
} else {
|
|
353
|
+
code.push(`else if (${valueAlias}.length > ${schema.length}) { ${emit(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.length}\``)} }`);
|
|
354
|
+
code.push(`else { ${schema.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`);
|
|
355
|
+
}
|
|
197
356
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const ${
|
|
206
|
-
|
|
207
|
-
${
|
|
208
|
-
`);
|
|
357
|
+
return code.join('\n');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (typeof schema === 'object' && schema !== null) {
|
|
361
|
+
if (schema instanceof RegExp) {
|
|
362
|
+
const valueAlias = context.unique('v');
|
|
363
|
+
if (mode.fast) {
|
|
364
|
+
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined || ${valueAlias} instanceof Error || !${schema.toString()}.test(String(${valueAlias}))) { ${fail} }`;
|
|
365
|
+
} else {
|
|
366
|
+
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }\nelse if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }\nelse if (!${schema.toString()}.test(String(${valueAlias}))) { ${emit(`\`Invalid value \${${valueAlias}}, expected to match ${schema.toString()}\``)} }`;
|
|
209
367
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
368
|
+
} else {
|
|
369
|
+
const valueAlias = context.unique('v');
|
|
370
|
+
if (mode.fast) {
|
|
371
|
+
let code = `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object' || ${valueAlias} instanceof Error) { ${fail} }`;
|
|
372
|
+
if ($keys in schema) {
|
|
373
|
+
const keysAlias = context.unique('k');
|
|
374
|
+
const kAlias = context.unique('k');
|
|
375
|
+
code += `\nconst ${keysAlias} = Object.keys(${valueAlias});\nfor (const ${kAlias} of ${keysAlias}) { ${codeGen(schema[$keys], context, kAlias, mode)} }`;
|
|
376
|
+
}
|
|
377
|
+
if ($values in schema) {
|
|
378
|
+
const vAlias = context.unique('val');
|
|
379
|
+
const kAlias = context.unique('k');
|
|
380
|
+
const entriesAlias = context.unique('en');
|
|
381
|
+
code += `\nconst ${entriesAlias} = Object.entries(${valueAlias});\nfor (const [${kAlias}, ${vAlias}] of ${entriesAlias}) { ${codeGen(schema[$values], context, vAlias, mode)} }`;
|
|
382
|
+
}
|
|
383
|
+
if ($strict in schema && schema[$strict]) {
|
|
384
|
+
const keysAlias = context.unique('k');
|
|
385
|
+
const kAlias = context.unique('k');
|
|
386
|
+
const extraAlias = context.unique('ex');
|
|
387
|
+
code += `\nconst ${keysAlias} = new Set(${JSON.stringify(Object.keys(schema))});\nconst ${extraAlias} = Object.keys(${valueAlias}).filter(${kAlias} => !${keysAlias}.has(${kAlias}));\nif (${extraAlias}.length !== 0) { ${fail} }`;
|
|
388
|
+
}
|
|
389
|
+
code += '\n' + Object.entries(schema).map(([key, s])=>codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, mode)).join('\n');
|
|
390
|
+
return code;
|
|
391
|
+
} else {
|
|
392
|
+
const code = [
|
|
393
|
+
`const ${valueAlias} = ${valuePath};`,
|
|
394
|
+
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
|
|
395
|
+
`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`,
|
|
396
|
+
`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`,
|
|
397
|
+
'else {'
|
|
398
|
+
];
|
|
399
|
+
const innerCode = [];
|
|
400
|
+
if ($keys in schema) {
|
|
401
|
+
const keysAlias = context.unique('k');
|
|
402
|
+
const kAlias = context.unique('k');
|
|
403
|
+
innerCode.push(`const ${keysAlias} = Object.keys(${valueAlias});`);
|
|
404
|
+
innerCode.push(`for (const ${kAlias} of ${keysAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
|
|
405
|
+
dynamic: kAlias
|
|
406
|
+
}))} }`);
|
|
407
|
+
}
|
|
408
|
+
if ($values in schema) {
|
|
409
|
+
const vAlias = context.unique('val');
|
|
410
|
+
const kAlias = context.unique('k');
|
|
411
|
+
const entriesAlias = context.unique('en');
|
|
412
|
+
innerCode.push(`const ${entriesAlias} = Object.entries(${valueAlias});`);
|
|
413
|
+
innerCode.push(`for (const [${kAlias}, ${vAlias}] of ${entriesAlias}) { ${codeGen(schema[$values], context, vAlias, childMode(mode, {
|
|
414
|
+
dynamic: kAlias
|
|
415
|
+
}))} }`);
|
|
416
|
+
}
|
|
417
|
+
if ($strict in schema && schema[$strict]) {
|
|
418
|
+
const keysAlias = context.unique('k');
|
|
419
|
+
const kAlias = context.unique('k');
|
|
420
|
+
const extraAlias = context.unique('ex');
|
|
421
|
+
innerCode.push(`const ${keysAlias} = new Set(${JSON.stringify(Object.keys(schema))});`);
|
|
422
|
+
innerCode.push(`const ${extraAlias} = Object.keys(${valueAlias}).filter(${kAlias} => !${keysAlias}.has(${kAlias}));`);
|
|
423
|
+
innerCode.push(`if (${extraAlias}.length !== 0) { ${emit(`\`Extra properties: \${${extraAlias}}, are not allowed\``)} }`);
|
|
424
|
+
}
|
|
425
|
+
innerCode.push(...Object.entries(schema).map(([key, s])=>codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key))));
|
|
426
|
+
code.push(innerCode.join('\n'), '}');
|
|
427
|
+
return code.join('\n');
|
|
217
428
|
}
|
|
218
|
-
code.push(...Object.entries(schema).map(([key, s])=>codeGen(s, context, `${valueAlias}['${key}']`, `${path}.${key}`)));
|
|
219
|
-
return `${code.join('\n')}`;
|
|
220
429
|
}
|
|
221
|
-
}
|
|
430
|
+
}
|
|
431
|
+
if (typeof schema === 'symbol') {
|
|
222
432
|
const index = context.register(schema);
|
|
223
433
|
const valueAlias = context.unique('v');
|
|
224
434
|
const registryAlias = context.unique('r');
|
|
225
|
-
|
|
226
|
-
const ${valueAlias} = ${valuePath}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
435
|
+
if (mode.fast) {
|
|
436
|
+
return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = ctx.registry[${index}];\nif (typeof ${valueAlias} !== 'symbol' || ${valueAlias} !== ${registryAlias}) { ${fail} }`;
|
|
437
|
+
} else {
|
|
438
|
+
return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = ctx.registry[${index}];\nif (typeof ${valueAlias} !== 'symbol') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected symbol\``)} }\nelse if (${valueAlias} !== ${registryAlias}) { ${emit(`\`Invalid value \${${valueAlias}.toString()}, expected ${schema.toString()}\``)} }`;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (schema === null || schema === undefined) {
|
|
232
442
|
const valueAlias = context.unique('v');
|
|
233
|
-
|
|
234
|
-
const ${valueAlias} = ${valuePath}
|
|
235
|
-
|
|
236
|
-
|
|
443
|
+
if (mode.fast) {
|
|
444
|
+
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== null && ${valueAlias} !== undefined) { ${fail} }`;
|
|
445
|
+
} else {
|
|
446
|
+
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== null && ${valueAlias} !== undefined) { ${emit(`\`Invalid value \${String(${valueAlias})}, expected nullable\``)} }`;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const valueAlias = context.unique('v');
|
|
450
|
+
if (mode.fast) {
|
|
451
|
+
return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== '${typeof schema}' || ${valueAlias} !== ${toLiteral(schema)}) { ${fail} }`;
|
|
237
452
|
} else {
|
|
238
|
-
const valueAlias = context.unique('v');
|
|
239
453
|
const value = context.unique('val');
|
|
240
|
-
return `
|
|
241
|
-
const ${valueAlias} = ${valuePath};
|
|
242
|
-
const ${value} = ${JSON.stringify(schema)};
|
|
243
|
-
${codeGenExpectNonError(valueAlias, path)}
|
|
244
|
-
if (typeof ${valueAlias} !== '${typeof schema}') { throw new TypeError(\`Invalid type \${typeof ${valueAlias}} for path "${path}", expected ${typeof schema}\`); }
|
|
245
|
-
if (${valueAlias} !== ${value}) { throw new TypeError(\`Invalid value \${JSON.stringify(${valueAlias})} for path "${path}", expected ${JSON.stringify(schema)}\`); }
|
|
246
|
-
`;
|
|
454
|
+
return `const ${valueAlias} = ${valuePath};\nconst ${value} = ${toLiteral(schema)};\nif (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }\nelse if (typeof ${valueAlias} !== '${typeof schema}') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected ${typeof schema}\``)} }\nelse if (${valueAlias} !== ${value}) { ${emit(`\`Invalid value \${String(${valueAlias})}, expected ${toLiteral(schema)}\``)} }`;
|
|
247
455
|
}
|
|
248
456
|
};
|
|
249
|
-
|
|
457
|
+
const emptyIssues = [];
|
|
458
|
+
export const compile = (schema, options)=>{
|
|
459
|
+
const allErrors = options?.allErrors ?? false;
|
|
460
|
+
if (allErrors) {
|
|
461
|
+
const fastContext = new Context();
|
|
462
|
+
const fastCode = `${codeGen(schema, fastContext, 'data', {
|
|
463
|
+
fast: true
|
|
464
|
+
})}\nreturn true;`;
|
|
465
|
+
const fastValidator = new Function('ctx', `return (data) => {\n${fastCode}\n};`)(fastContext);
|
|
466
|
+
const issueContext = new Context();
|
|
467
|
+
const issueCode = `let issues;\n${codeGen(schema, issueContext, 'data', {
|
|
468
|
+
fast: false,
|
|
469
|
+
firstError: false,
|
|
470
|
+
issues: 'issues',
|
|
471
|
+
path: [],
|
|
472
|
+
pathExpr: '[]'
|
|
473
|
+
})}\nreturn issues || [];`;
|
|
474
|
+
const issueValidator = new Function('ctx', `return (data) => {\n${issueCode}\n};`)(issueContext);
|
|
475
|
+
const validator = (data)=>{
|
|
476
|
+
if (fastValidator(data)) {
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
validator.issues = issueValidator(data);
|
|
480
|
+
return false;
|
|
481
|
+
};
|
|
482
|
+
validator.issues = emptyIssues;
|
|
483
|
+
return validator;
|
|
484
|
+
}
|
|
485
|
+
const fastContext = new Context();
|
|
486
|
+
const fastCode = `${codeGen(schema, fastContext, 'data', {
|
|
487
|
+
fast: true
|
|
488
|
+
})}\nreturn true;`;
|
|
489
|
+
const fastValidator = new Function('ctx', `return (data) => {\n${fastCode}\n};`)(fastContext);
|
|
250
490
|
const context = new Context();
|
|
251
|
-
const code = codeGen(schema, context, 'data',
|
|
252
|
-
|
|
253
|
-
|
|
491
|
+
const code = codeGen(schema, context, 'data', {
|
|
492
|
+
fast: false,
|
|
493
|
+
firstError: true,
|
|
494
|
+
issues: 'issues',
|
|
495
|
+
path: [],
|
|
496
|
+
pathExpr: '[]'
|
|
497
|
+
});
|
|
498
|
+
const firstErrorValidator = new Function('ctx', `return (data) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context);
|
|
499
|
+
const validator = (data)=>{
|
|
500
|
+
if (fastValidator(data)) {
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
validator.issues = firstErrorValidator(data);
|
|
504
|
+
return false;
|
|
505
|
+
};
|
|
506
|
+
validator.issues = emptyIssues;
|
|
507
|
+
return validator;
|
|
508
|
+
};
|
|
509
|
+
export const ascertain = (schema, data)=>{
|
|
510
|
+
const validator = compile(schema);
|
|
511
|
+
if (!validator(data)) {
|
|
512
|
+
throw new TypeError(validator.issues[0].message, {
|
|
513
|
+
cause: {
|
|
514
|
+
issues: validator.issues
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
254
518
|
};
|
|
255
|
-
export const
|
|
256
|
-
|
|
519
|
+
export const createValidator = (config)=>{
|
|
520
|
+
return (schema)=>{
|
|
521
|
+
ascertain(schema, config);
|
|
522
|
+
return config;
|
|
523
|
+
};
|
|
524
|
+
};
|
|
525
|
+
export const standardSchema = (schema)=>{
|
|
526
|
+
const validator = compile(schema);
|
|
527
|
+
const fn = (data)=>{
|
|
528
|
+
if (!validator(data)) {
|
|
529
|
+
throw new TypeError(validator.issues[0].message, {
|
|
530
|
+
cause: {
|
|
531
|
+
issues: validator.issues
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
fn['~standard'] = {
|
|
537
|
+
version: 1,
|
|
538
|
+
vendor: 'ascertain',
|
|
539
|
+
validate: (value)=>{
|
|
540
|
+
if (validator(value)) {
|
|
541
|
+
return {
|
|
542
|
+
value: value
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
issues: validator.issues
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
return fn;
|
|
257
551
|
};
|
|
258
552
|
|
|
259
553
|
//# sourceMappingURL=index.js.map
|