ascertain 0.14.39 → 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019-2024 Ivan Zakharchanka
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ### Ascertain what data is not suitable for your library
4
4
 
5
- 0-Deps, simple, blazing fast, for browser and node js object schema validator
5
+ 0-Deps, simple, blazing fast, for browser and Node.js object schema validator
6
6
 
7
7
  [![Coverage Status][codecov-image]][codecov-url]
8
8
  [![Build Status][github-image]][github-url]
@@ -12,19 +12,64 @@
12
12
 
13
13
  ## Features
14
14
 
15
- - value validation
16
- - class validation
17
- - structure validation
18
- - regexp validation
19
- - and/or validation
20
- - object keys validation
21
- - object values validation
15
+ - Type-safe validation: Ensures your data conforms to predefined schemas.
16
+ - Composite schemas: Supports logical AND, OR, and optional schemas.
17
+ - Type casting: Automatically parses and casts strings to other types.
18
+ - Error handling: Provides detailed error messages for invalid data.
19
+
20
+ ## Schema description
21
+
22
+ - Primitive Values: Any primitive value (e.g., string, number, bigint, boolean, undefined, symbol, null) is used as an expected constant to match against.
23
+ - Function Types: Functions are used as constructors for non-objects and instance types for object types.
24
+ - Array Values: Arrays are used to represent an expected array type, where every item in the array must match the specified type (acting as an "and" operator).
25
+ - Regular Expressions: Regular expressions are used to validate that a value matches a specified string pattern.
26
+ - Object Types: Non-null objects are used as templates for expected properties, where each property of the object must match the corresponding schema definition.
22
27
 
23
28
  ## Usage Example
24
29
 
30
+ ### Schema compilation
31
+ ```typescript
32
+ import { compile, optional, and, or, $keys, $values, Schema, as } from 'ascertain';
33
+
34
+ const validate = compile({
35
+ number: Number,
36
+ string: String,
37
+ boolean: Boolean,
38
+ function: Function,
39
+ array: Array,
40
+ object: Object,
41
+ date: and(Date, { toJSON: Function }),
42
+ regexp: /regexp/,
43
+ oneOfValue: or(1, 2, 3),
44
+ arrayOfNumbers: [Number],
45
+ objectSchema: {
46
+ number: Number,
47
+ },
48
+ optional: optional({
49
+ number: Number,
50
+ }),
51
+ keyValue: {
52
+ [$keys]: /^key[A-Z]/,
53
+ [$values]: Number
54
+ },
55
+ parsedNumber: Number,
56
+ parsedString: String,
57
+ parsedBoolean: Boolean,
58
+ parsedArray: [String],
59
+ parsedJSON: {
60
+ number: 1,
61
+ },
62
+ parsedBase64: String,
63
+ parsedTime: 2 * 60 * 1000, // two minutes
64
+ parsedDate: Date,
65
+ });
66
+
67
+ ```
68
+
69
+ ### Runtime validation
25
70
  Create data ascertain
26
71
  ```typescript
27
- import ascertain, { optional, and, or, $keys, $values, Schema, as } from 'ascertain';
72
+ import { ascertain, optional, and, or, $keys, $values, Schema, as } from 'ascertain';
28
73
 
29
74
  // create data sample
30
75
  const data = {
@@ -49,11 +94,13 @@ const data = {
49
94
  },
50
95
  // fault tolernat type casting
51
96
  parsedNumber: as.number('1'),
52
- parsedString: as.number('string'),
97
+ parsedString: as.string('string'),
53
98
  parsedBoolean: as.boolean('false'),
54
99
  parsedArray: as.array('1,2,3,4,5', ','),
55
100
  parsedJSON: as.json('{ "number": 1 }'),
56
101
  parsedBase64: as.base64('dGVzdA=='),
102
+ parsedTime: as.time('2m'),
103
+ parsedDate: as.date('31-12-2024'),
57
104
  };
58
105
 
59
106
  // create data schema
@@ -86,15 +133,54 @@ const schema: Schema<typeof data> = {
86
133
  number: 1,
87
134
  },
88
135
  parsedBase64: String,
136
+ parsedTime: 2 * 60 * 1000, // two minutes
137
+ parsedDate: Date,
89
138
  };
90
139
 
91
140
  // validate
92
141
  const validate = ascertain<typeof data>(schema, data, '[DATA]');
93
142
  ```
94
143
 
144
+ ### Benchmark VS zod and ajv
145
+ ```
146
+ ⭐ Script ajv-vs-zod-vs-ascertain.js
147
+ ⇶ Suite ajv vs zod vs ascertain
148
+ ➤ Perform benchmark
149
+ ✓ Measure 500000 zod static schema validation
150
+ ┌──────────┬──────────┬──────────┬──────────┬────────────┬────────┐
151
+ │ (index) │ med │ p95 │ p99 │ total │ count │
152
+ ├──────────┼──────────┼──────────┼──────────┼────────────┼────────┤
153
+ │ 0.000699 │ 0.000788 │ 0.000996 │ 0.001624 │ 462.602373 │ 500000 │
154
+ └──────────┴──────────┴──────────┴──────────┴────────────┴────────┘
155
+ ✓ Measure 500000 zod dynamic schema validation
156
+ ┌──────────┬──────────┬──────────┬──────────┬─────────────┬────────┐
157
+ │ (index) │ med │ p95 │ p99 │ total │ count │
158
+ ├──────────┼──────────┼──────────┼──────────┼─────────────┼────────┤
159
+ │ 0.006248 │ 0.006918 │ 0.007524 │ 0.016948 │ 3780.465563 │ 500000 │
160
+ └──────────┴──────────┴──────────┴──────────┴─────────────┴────────┘
161
+ ✓ Measure 500000 ascertain static schema validation
162
+ ┌──────────┬──────────┬──────────┬──────────┬───────────┬────────┐
163
+ │ (index) │ med │ p95 │ p99 │ total │ count │
164
+ ├──────────┼──────────┼──────────┼──────────┼───────────┼────────┤
165
+ │ 0.000063 │ 0.000071 │ 0.000098 │ 0.000267 │ 41.673271 │ 500000 │
166
+ └──────────┴──────────┴──────────┴──────────┴───────────┴────────┘
167
+ ✓ Measure 500000 ascertain dynamic schema validation
168
+ ┌──────────┬──────────┬──────────┬──────────┬────────────┬────────┐
169
+ │ (index) │ med │ p95 │ p99 │ total │ count │
170
+ ├──────────┼──────────┼──────────┼──────────┼────────────┼────────┤
171
+ │ 0.000367 │ 0.000415 │ 0.000525 │ 0.001055 │ 239.078129 │ 500000 │
172
+ └──────────┴──────────┴──────────┴──────────┴────────────┴────────┘
173
+ ✓ Measure 500000 ajv compiled schema validation
174
+ ┌──────────┬──────────┬──────────┬──────────┬───────────┬────────┐
175
+ │ (index) │ med │ p95 │ p99 │ total │ count │
176
+ ├──────────┼──────────┼──────────┼──────────┼───────────┼────────┤
177
+ │ 0.000063 │ 0.000072 │ 0.000124 │ 0.000307 │ 44.542936 │ 500000 │
178
+ └──────────┴──────────┴──────────┴──────────┴───────────┴────────┘
179
+ ```
180
+
95
181
  ## License
96
182
  License [The MIT License](http://opensource.org/licenses/MIT)
97
- Copyright (c) 2024 Ivan Zakharchanka
183
+ Copyright (c) 2019-2024 Ivan Zakharchanka
98
184
 
99
185
  [npm-url]: https://www.npmjs.com/package/ascertain
100
186
  [downloads-image]: https://img.shields.io/npm/dw/ascertain.svg?maxAge=43200
package/build/index.cjs CHANGED
@@ -15,6 +15,9 @@ _export(exports, {
15
15
  $values: function() {
16
16
  return $values;
17
17
  },
18
+ AssertError: function() {
19
+ return AssertError;
20
+ },
18
21
  and: function() {
19
22
  return and;
20
23
  },
@@ -24,8 +27,8 @@ _export(exports, {
24
27
  ascertain: function() {
25
28
  return ascertain;
26
29
  },
27
- default: function() {
28
- return _default;
30
+ compile: function() {
31
+ return compile;
29
32
  },
30
33
  fromBase64: function() {
31
34
  return fromBase64;
@@ -35,63 +38,50 @@ _export(exports, {
35
38
  },
36
39
  or: function() {
37
40
  return or;
41
+ },
42
+ tuple: function() {
43
+ return tuple;
38
44
  }
39
45
  });
40
- let AssertError = class AssertError {
46
+ class AssertError extends TypeError {
47
+ value;
48
+ expected;
49
+ path;
50
+ subject;
41
51
  constructor(value, expected, path, subject = 'value'){
52
+ super(`Invalid ${subject} ${JSON.stringify(value)} for path ${path}, expected ${expected}.`);
42
53
  this.value = value;
43
54
  this.expected = expected;
44
55
  this.path = path;
45
56
  this.subject = subject;
46
57
  }
47
- toString() {
48
- return `Invalid ${this.subject} ${JSON.stringify(this.value)} specified by path ${this.path} expected ${this.expected}`;
49
- }
50
- };
51
- function findFirstError(array, map) {
52
- for(let i = 0; i < array.length; i++){
53
- const error = map(array[i], i);
54
- if (error) {
55
- return error;
56
- }
57
- }
58
- return false;
59
58
  }
60
- function findNotError(array, map) {
61
- const errors = [];
62
- for(let i = 0; i < array.length; i++){
63
- const error = map(array[i], i);
64
- if (!error) {
65
- return false;
66
- } else {
67
- errors.push(error);
59
+ class Operation {
60
+ schemas;
61
+ constructor(schemas){
62
+ this.schemas = schemas;
63
+ if (schemas.length === 0) {
64
+ throw new TypeError(`Operation schema ${this.constructor.name} must have at least one element`);
68
65
  }
69
66
  }
70
- return errors.reduce((result, error)=>{
71
- return new AssertError(result.value, [
72
- result.expected,
73
- error.expected
74
- ].join(' or '), result.path, result.string);
75
- });
76
67
  }
77
- function Optional(schema) {
78
- this.schema = schema;
68
+ class Or extends Operation {
79
69
  }
80
- function And(schema) {
81
- this.schema = schema;
70
+ const or = (...schemas)=>new Or(schemas);
71
+ class And extends Operation {
82
72
  }
83
- function Or(schema) {
84
- this.schema = schema;
73
+ const and = (...schemas)=>new And(schemas);
74
+ class Optional extends Operation {
75
+ constructor(schema){
76
+ super([
77
+ schema
78
+ ]);
79
+ }
85
80
  }
86
- const optional = (schema)=>{
87
- return new Optional(schema);
88
- };
89
- const and = (...schema)=>{
90
- return new And(schema);
91
- };
92
- const or = (...schema)=>{
93
- return new Or(schema);
94
- };
81
+ const optional = (schema)=>new Optional(schema);
82
+ class Tuple extends Operation {
83
+ }
84
+ const tuple = (...schemas)=>new Tuple(schemas);
95
85
  const $keys = Symbol.for('@@keys');
96
86
  const $values = Symbol.for('@@values');
97
87
  const fromBase64 = typeof Buffer === 'undefined' ? (value)=>atob(value) : (value)=>Buffer.from(value, 'base64').toString('utf-8');
@@ -118,13 +108,13 @@ const as = {
118
108
  time: (value)=>{
119
109
  const matches = value?.match(/^(\d+)(ms|s|m|h|d|w)?$/);
120
110
  if (matches) {
121
- const [_, amount, unit = 'ms'] = matches;
111
+ const [, amount, unit = 'ms'] = matches;
122
112
  return parseInt(amount, 10) * MULTIPLIERS[unit];
123
113
  }
124
114
  return undefined;
125
115
  },
126
116
  boolean: (value)=>/^(0|1|true|false|enabled|disabled)$/i.test(value) ? /^(1|true|enabled)$/i.test(value) : undefined,
127
- array: (value, delimiter)=>value?.split(delimiter) ?? undefined,
117
+ array: (value, delimiter)=>value?.split?.(delimiter) ?? undefined,
128
118
  json: (value)=>{
129
119
  try {
130
120
  return JSON.parse(value);
@@ -140,84 +130,266 @@ const as = {
140
130
  }
141
131
  }
142
132
  };
143
- function certain(target, schema, path, optional) {
144
- if (schema === null || typeof schema === 'undefined') {
145
- return new AssertError(schema, 'any value', path, 'schema value');
133
+ class Context {
134
+ options;
135
+ Error;
136
+ registry;
137
+ varIndex;
138
+ constructor(options = {}){
139
+ this.options = options;
140
+ this.registry = [];
141
+ this.varIndex = 0;
142
+ this.Error = options.error ?? AssertError;
146
143
  }
147
- if (schema instanceof Optional) {
148
- return certain(target, schema.schema, path, true);
144
+ register(value) {
145
+ if (!this.registry.includes(value)) {
146
+ this.registry.push(value);
147
+ }
148
+ return this.registry.indexOf(value);
149
149
  }
150
- const isValue = target !== null && typeof target !== 'undefined';
151
- if (!isValue && optional) {
152
- return;
150
+ unique(prefix) {
151
+ return `${prefix}$$${this.varIndex++}`;
153
152
  }
154
- if (schema instanceof Or) {
155
- if (!schema.schema.length) {
156
- return new AssertError(target, 'values', path, 'OR schema');
153
+ }
154
+ const codeGen = (schema, context, valuePath, path)=>{
155
+ if (schema instanceof And) {
156
+ const valueAlias = context.unique('v');
157
+ const errorsAlias = context.unique('err');
158
+ const code = schema.schemas.map((s)=>`try { ${codeGen(s, context, valueAlias, path)} } catch (e) { ${errorsAlias}.push(e); }`).join('\n');
159
+ return `// And
160
+ const ${errorsAlias} = [];
161
+ const ${valueAlias} = ${valuePath};
162
+ ${code}
163
+ if (${errorsAlias}.length !== 0) { throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"'); }
164
+ `;
165
+ } else if (schema instanceof Or) {
166
+ const valueAlias = context.unique('v');
167
+ const errorsAlias = context.unique('err');
168
+ const code = schema.schemas.map((s)=>codeGen(s, context, valueAlias, path)).reduceRight((result, code)=>`try {${code}} catch (e) {${errorsAlias}.push(e);${result}}`, `throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"');`);
169
+ return `// Or
170
+ const ${errorsAlias} = [];
171
+ const ${valueAlias} = ${valuePath};
172
+ ${code}
173
+ `;
174
+ } else if (schema instanceof Optional) {
175
+ const valueAlias = context.unique('v');
176
+ return `// Optional
177
+ const ${valueAlias} = ${valuePath};
178
+ if (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(schema.schemas[0], context, valueAlias, path)} }
179
+ `;
180
+ } else if (schema instanceof Tuple) {
181
+ const valueAlias = context.unique('v');
182
+ const errorsAlias = context.unique('err');
183
+ const code = [
184
+ '// Tuple',
185
+ `const ${valueAlias} = ${valuePath};`,
186
+ `const ${errorsAlias} = [];`,
187
+ `if (!Array.isArray(${valueAlias})) { throw new ctx.Error(${valueAlias}, 'array', \`${path}\`); }`,
188
+ ...schema.schemas.map((s, idx)=>`try { ${codeGen(s, context, `${valueAlias}[${idx}]`, `${path}[${idx}]`)} } catch (e) { ${errorsAlias}.push(e); }`),
189
+ `if (${errorsAlias}.length !== 0) { throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"'); }`
190
+ ];
191
+ return code.join('\n');
192
+ } else if (typeof schema === 'function') {
193
+ const index = context.register(schema);
194
+ const valueAlias = context.unique('v');
195
+ const registryAlias = context.unique('r');
196
+ return `
197
+ const ${valueAlias} = ${valuePath};
198
+ const ${registryAlias} = ctx.registry[${index}];
199
+ if (${valueAlias} === null || ${valueAlias} === undefined) { throw new ctx.Error(${valueAlias}, 'a non-nullable', \`${path}\`); }
200
+ if (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) { throw new ctx.Error(${valueAlias}?.constructor?.name, \`instance of \${${registryAlias}.name}\`, \`${path}\`, 'instance of'); }
201
+ if (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) { throw new ctx.Error(${valueAlias}?.constructor?.name, ${registryAlias}.name, \`${path}\`, 'type'); }
202
+ `;
203
+ } else if (Array.isArray(schema)) {
204
+ const valueAlias = context.unique('v');
205
+ const code = [
206
+ `const ${valueAlias} = ${valuePath};`,
207
+ `if (!Array.isArray(${valueAlias})) { throw new ctx.Error(${valueAlias}, 'array', \`${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}) => { try { ${codeGen(s, context, value, `${path}[\${${key}}]`)} } catch(e){ ${errorsAlias}.push(e); } });`));
215
+ code.push(`if (${errorsAlias}.length !== 0) { throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"'); }`);
157
216
  }
158
- return findNotError(schema.schema, (schema)=>certain(target, schema, path));
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
+ if (!${schema.toString()}.test('' + ${valueAlias})) { throw new ctx.Error(${valueAlias}, 'matching ${schema.toString()}', \`${path}\`); }
224
+ `;
225
+ } else {
226
+ const valueAlias = context.unique('v');
227
+ const code = [
228
+ `const ${valueAlias} = ${valuePath};`,
229
+ `if (${valueAlias} === null || ${valueAlias} === undefined) { throw new ctx.Error(${valueAlias}, 'object', \`${path}\`); }`,
230
+ `if (typeof ${valueAlias} !== 'object') { throw new ctx.Error(${valueAlias}, '${schema.constructor.name}', \`${path}\`); }`
231
+ ];
232
+ if ($keys in schema) {
233
+ const keysAlias = context.unique('key');
234
+ const errorsAlias = context.unique('err');
235
+ const value = context.unique('v');
236
+ code.push(`
237
+ const ${keysAlias} = Object.keys(${valueAlias});
238
+ const ${errorsAlias} = ${keysAlias}.flatMap((${value}) => { ${codeGen(schema[$keys], context, value, path)} }).filter(Boolean);
239
+ if (${errorsAlias}.length !== 0) { throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"'); }
240
+ `);
241
+ }
242
+ if ($values in schema) {
243
+ const vAlias = context.unique('val');
244
+ const valuesAlias = context.unique('vals');
245
+ const errorsAlias = context.unique('err');
246
+ code.push(`{
247
+ const ${valuesAlias} = Object.values(${valuePath});
248
+ const ${errorsAlias} = ${valuesAlias}.flatMap((${vAlias}) => { ${codeGen(schema[$values], context, vAlias, path)} }).filter(Boolean);
249
+ if (${errorsAlias}.length !== 0) { throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"'); }
250
+ }`);
251
+ }
252
+ const keys = Object.keys(schema);
253
+ code.push(...keys.map((key)=>codeGen(schema[key], context, `${valueAlias}['${key}']`, `${path}.${key}`)));
254
+ return `{${code.join('\n')}}`;
255
+ }
256
+ } else if (typeof schema === 'symbol') {
257
+ const index = context.register(schema);
258
+ const valueAlias = context.unique('v');
259
+ const registryAlias = context.unique('r');
260
+ return `
261
+ const ${valueAlias} = ${valuePath};
262
+ const ${registryAlias} = ctx.registry[${index}];
263
+ if (typeof ${valueAlias} !== 'symbol') { throw new ctx.Error(typeof ${valueAlias}, 'symbol', '${path}', 'type of'); }
264
+ if (${valueAlias} !== ${registryAlias}) { throw new ctx.Error(${valueAlias}.toString(), ${registryAlias}.toString(), '${path}', 'symbol'); }
265
+ `;
266
+ } else if (schema === null || schema === undefined) {
267
+ const valueAlias = context.unique('v');
268
+ return `
269
+ const ${valueAlias} = ${valuePath};
270
+ if (${valueAlias} !== null && ${valueAlias} !== undefined ) { throw new ctx.Error(${valueAlias}, 'nullable', '${path}'); }
271
+ `;
272
+ } else {
273
+ const valueAlias = context.unique('v');
274
+ const typeAlias = context.unique('t');
275
+ const value = context.unique('val');
276
+ return `
277
+ const ${valueAlias} = ${valuePath};
278
+ const ${typeAlias} = '${typeof schema}';
279
+ const ${value} = ${JSON.stringify(schema)};
280
+ if (typeof ${valueAlias} !== ${typeAlias}) { throw new ctx.Error(typeof ${valueAlias}, ${typeAlias}, '${path}', 'type of'); }
281
+ if (${valueAlias} !== ${value}) { throw new ctx.Error(${valueAlias}, ${value}, '${path}'); }
282
+ `;
159
283
  }
284
+ };
285
+ const flatAggregateError = (error)=>{
286
+ return error.errors.flatMap((e)=>e instanceof AggregateError ? flatAggregateError(e) : e);
287
+ };
288
+ const compile = (schema, rootName, options = {})=>{
289
+ const context = new Context(options);
290
+ const code = codeGen(schema, context, 'data', rootName);
291
+ const validator = new Function('ctx', 'data', code);
292
+ return (data)=>{
293
+ try {
294
+ validator(context, data);
295
+ } catch (e) {
296
+ const errors = e instanceof AggregateError ? flatAggregateError(e) : [
297
+ e
298
+ ];
299
+ throw new AggregateError(errors, 'Validation failure');
300
+ }
301
+ };
302
+ };
303
+ const assert = (target, schema, path)=>{
160
304
  if (schema instanceof And) {
161
- if (!schema.schema.length) {
162
- return new AssertError(target, 'values', path, 'AND schema');
305
+ return schema.schemas.flatMap((schema)=>assert(target, schema, path)).filter((error)=>!!error);
306
+ } else if (schema instanceof Or) {
307
+ const errors = schema.schemas.flatMap((schema)=>assert(target, schema, path));
308
+ const filteredErrors = errors.filter((error)=>!!error);
309
+ if (filteredErrors.length === schema.schemas.length) {
310
+ return filteredErrors;
163
311
  }
164
- return findFirstError(schema.schema, (schema)=>certain(target, schema, path));
165
- }
166
- if (typeof schema === 'function') {
167
- if (!isValue) {
168
- return new AssertError(target, schema.name, path);
312
+ } else if (schema instanceof Optional) {
313
+ if (target !== undefined && target !== null) {
314
+ return assert(target, schema.schemas[0], path);
315
+ }
316
+ } else if (schema instanceof Tuple) {
317
+ if (!Array.isArray(target)) {
318
+ return [
319
+ new AssertError(target, 'array', path)
320
+ ];
321
+ }
322
+ return schema.schemas.flatMap((s, idx)=>assert(target[idx], s, `${path}[${idx}]`)).filter((error)=>!!error);
323
+ } else if (typeof schema === 'function') {
324
+ if (target === null || target === undefined) {
325
+ return [
326
+ new AssertError(target, 'a non-nullable', path)
327
+ ];
169
328
  }
170
329
  if (typeof target === 'object' && !(target instanceof schema)) {
171
- return new AssertError(target, schema.name, path);
330
+ return [
331
+ new AssertError(target?.constructor?.name, `instance of ${schema.name}`, path, 'instance of')
332
+ ];
172
333
  }
173
- if (typeof target !== 'object' && target.constructor !== schema) {
174
- return new AssertError(target, schema.name, path);
334
+ if (typeof target !== 'object' && target?.constructor !== schema) {
335
+ return [
336
+ new AssertError(target?.constructor?.name, schema.name, path, 'type')
337
+ ];
175
338
  }
176
339
  } else if (Array.isArray(schema)) {
177
340
  if (!Array.isArray(target)) {
178
- return new AssertError(target, schema.constructor.name, path);
341
+ return [
342
+ new AssertError(target, 'array', path)
343
+ ];
179
344
  }
180
- return findFirstError(target, (value, idx)=>{
181
- return findNotError(schema, (itemSchemaType)=>certain(value, itemSchemaType, `${path}.${idx}`));
182
- });
183
- } else if (typeof schema === 'object') {
345
+ return schema.flatMap((s)=>target.flatMap((value, idx)=>assert(value, s, `${path}[${idx}]`))).filter((error)=>!!error);
346
+ } else if (typeof schema === 'object' && schema !== null) {
184
347
  if (schema instanceof RegExp) {
185
348
  if (!schema.test('' + target)) {
186
- return new AssertError(target, `matching /${schema.source}/`, path);
349
+ return [
350
+ new AssertError(target, `matching ${schema.toString()}`, path)
351
+ ];
187
352
  }
353
+ return [];
188
354
  } else {
189
- if (typeof target !== 'object') {
190
- return new AssertError(target, schema.constructor.name, path);
355
+ if (target === null || target === undefined) {
356
+ return [
357
+ new AssertError(target, 'object', path)
358
+ ];
191
359
  }
192
- if (target === null) {
193
- return new AssertError(target, 'an object', path);
360
+ if (typeof target !== 'object') {
361
+ return [
362
+ new AssertError(target, schema.constructor.name, path)
363
+ ];
194
364
  }
195
365
  if ($keys in schema) {
196
366
  const targetKeys = Object.keys(target);
197
- const assertError = findFirstError(targetKeys, (targetKey)=>certain(targetKey, schema[$keys], `${path}.${targetKey}`));
198
- if (assertError) {
199
- return assertError;
200
- }
367
+ return targetKeys.flatMap((key)=>assert(key, schema[$keys], path)).filter((error)=>!!error);
201
368
  }
202
369
  if ($values in schema) {
203
370
  const targetKeys = Object.keys(target);
204
- const assertError = findFirstError(targetKeys, (targetKey)=>certain(target[targetKey], schema[$values], `${path}.${targetKey}`));
205
- if (assertError) {
206
- return assertError;
207
- }
371
+ return targetKeys.flatMap((key)=>assert(target[key], schema[$values], path)).filter((error)=>!!error);
208
372
  }
209
- return findFirstError(Object.keys(schema), (key)=>certain(target[key], schema[key], `${path}.${key}`));
373
+ return Object.keys(schema).flatMap((key)=>assert(target[key], schema[key], path)).filter((error)=>!!error);
374
+ }
375
+ } else if (schema === null || schema === undefined) {
376
+ if (target !== null && target !== undefined) {
377
+ return [
378
+ new AssertError(target, 'nullable', path)
379
+ ];
210
380
  }
211
381
  } else if (target !== schema) {
212
- return new AssertError(target, schema, path);
382
+ return [
383
+ new AssertError(target, schema, path)
384
+ ];
213
385
  }
214
- }
386
+ return [];
387
+ };
215
388
  const ascertain = (schema, data, rootName = '[root]')=>{
216
- const result = certain(data, schema, rootName);
217
- if (result instanceof AssertError) {
218
- throw new TypeError(result.toString());
389
+ const result = assert(data, schema, rootName).filter((error)=>!!error);
390
+ if (result.length > 0) {
391
+ throw new AggregateError(result, 'Validation failure');
219
392
  }
220
393
  };
221
- const _default = ascertain;
222
394
 
223
395
  //# sourceMappingURL=index.cjs.map