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