ascertain 2.0.87 → 2.1.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 CHANGED
@@ -1,8 +1,6 @@
1
1
  # Ascertain
2
2
 
3
- ### Ascertain what data is not suitable for your library
4
-
5
- 0-Deps, simple, blazing fast, for browser and Node.js object schema validator
3
+ Zero-dependency, high-performance schema validator for Node.js and browsers.
6
4
 
7
5
  [![Coverage Status][codecov-image]][codecov-url]
8
6
  [![Build Status][github-image]][github-url]
@@ -10,182 +8,216 @@
10
8
  [![Downloads][downloads-image]][npm-url]
11
9
  [![Snyk][snyk-image]][snyk-url]
12
10
 
13
-
14
11
  [Documentation](https://3axap4ehko.github.io/ascertain/)
15
12
 
16
13
  ## Features
17
14
 
18
- - Type-safe validation: Ensures your data conforms to predefined schemas.
19
- - Composite schemas: Supports logical AND, OR, and optional schemas.
20
- - Type casting: Automatically parses and casts strings to other types.
21
- - Error handling: Provides detailed error messages for invalid data.
15
+ - **Zero dependencies**: Minimal footprint, no external dependencies
16
+ - **High performance**: Compiles schemas to optimized JavaScript functions
17
+ - **Type-safe**: Full TypeScript support with type inference
18
+ - **Flexible schemas**: Supports AND, OR, optional, and tuple operators
19
+ - **Type casting**: Built-in parsers for numbers, dates, JSON, base64, and more
20
+ - **Object validation**: Validate keys and values with `$keys`, `$values`, and `$strict`
21
+ - **Detailed errors**: Clear error messages with paths for debugging
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install ascertain
27
+ # or
28
+ pnpm add ascertain
29
+ ```
30
+
31
+ ## Quick Start
22
32
 
23
- ## Schema description
33
+ ```typescript
34
+ import { ascertain, compile, or, optional } from 'ascertain';
35
+
36
+ // Define a schema
37
+ const userSchema = {
38
+ name: String,
39
+ age: Number,
40
+ email: optional(String),
41
+ role: or('admin', 'user', 'guest'),
42
+ };
24
43
 
25
- - Primitive Values: Any primitive value (e.g., string, number, bigint, boolean, undefined, symbol, null) is used as an expected constant to match against.
26
- - Function Types: Functions are used as constructors for non-objects and instance types for object types.
27
- - 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).
28
- - Regular Expressions: Regular expressions are used to validate that a value matches a specified string pattern.
29
- - Object Types: Non-null objects are used as templates for expected properties, where each property of the object must match the corresponding schema definition.
44
+ // Validate data (throws on invalid)
45
+ ascertain(userSchema, userData, 'User');
46
+ ```
30
47
 
48
+ ## Performance
31
49
 
50
+ Ascertain compiles schemas into optimized JavaScript functions at runtime. This compilation step generates specialized validation code that runs significantly faster than interpreting schemas on each validation.
32
51
 
33
- ## Usage Example
52
+ **For best performance, compile schemas once and reuse the validator:**
34
53
 
35
- ### Schema compilation
36
54
  ```typescript
37
- import { compile, optional, and, or, $keys, $values, Schema, as } from 'ascertain';
38
-
39
- const validate = compile({
40
- number: Number,
41
- string: String,
42
- boolean: Boolean,
43
- function: Function,
44
- array: Array,
45
- object: Object,
46
- date: and(Date, { toJSON: Function }),
47
- regexp: /regexp/,
48
- oneOfValue: or(1, 2, 3),
49
- arrayOfNumbers: [Number],
50
- objectSchema: {
51
- number: Number,
52
- },
53
- optional: optional({
54
- number: Number,
55
- }),
56
- keyValue: {
57
- [$keys]: /^key[A-Z]/,
58
- [$values]: Number
59
- },
60
- parsedNumber: Number,
61
- parsedString: String,
62
- parsedBoolean: Boolean,
63
- parsedArray: [String],
64
- parsedJSON: {
65
- number: 1,
66
- },
67
- parsedBase64: String,
68
- parsedTime: 2 * 60 * 1000, // two minutes
69
- parsedDate: Date,
70
- });
55
+ import { compile } from 'ascertain';
56
+
57
+ // Compile once at startup
58
+ const validateUser = compile(userSchema, 'User');
59
+
60
+ // Reuse for each validation (no recompilation)
61
+ validateUser(user1);
62
+ validateUser(user2);
63
+ ```
71
64
 
65
+ Use `ascertain()` for one-off validations where convenience matters more than performance. Use `compile()` when validating the same schema repeatedly (e.g., API request handlers).
66
+
67
+ ### Benchmark
68
+
69
+ | Library | Operations/sec | Relative Speed |
70
+ |---------|----------------|----------------|
71
+ | **Ascertain** | 58,962,264 | **1.0x (fastest)** |
72
+ | AJV | 42,204,777 | 0.72x |
73
+ | Zod | 32,309,133 | 0.55x |
74
+
75
+ ```bash
76
+ pnpm bench
72
77
  ```
73
78
 
74
- ### Runtime validation
75
- Create data ascertain
79
+ ## Schema Types
80
+
81
+ | Schema | Description | Example |
82
+ |--------|-------------|---------|
83
+ | Primitives | Match exact values | `42`, `'active'`, `true`, `null` |
84
+ | Constructors | Validate by type | `String`, `Number`, `Boolean`, `Date` |
85
+ | Arrays | Validate array items | `[Number]` (array of numbers) |
86
+ | Objects | Validate properties | `{ name: String, age: Number }` |
87
+ | RegExp | Match string patterns | `/^[a-z]+$/` |
88
+ | `or()` | Match any schema | `or(String, Number)` |
89
+ | `and()` | Match all schemas | `and(Date, { toJSON: Function })` |
90
+ | `optional()` | Allow null/undefined | `optional(String)` |
91
+ | `tuple()` | Fixed-length arrays | `tuple(Number, Number)` |
92
+
93
+ ### Object Validation Symbols
94
+
76
95
  ```typescript
77
- import { ascertain, optional, and, or, $keys, $values, Schema, as } from 'ascertain';
78
-
79
- // create data sample
80
- const data = {
81
- number: 1,
82
- string: 'string',
83
- boolean: true,
84
- function: () => {},
85
- array: [],
86
- object: {},
87
- date: new Date,
88
- regexp: 'regexp',
89
- oneOfValue: 1,
90
- arrayOfNumbers: [1,2,3,4,5],
91
- objectSchema: {
92
- number: 1,
93
- },
94
- optional: null,
95
- keyValue: {
96
- keyOne: 1,
97
- keyTwo: 2,
98
- keyThree: 3,
99
- },
100
- // fault tolernat type casting
101
- parsedNumber: as.number('1'),
102
- parsedString: as.string('string'),
103
- parsedBoolean: as.boolean('false'),
104
- parsedArray: as.array('1,2,3,4,5', ','),
105
- parsedJSON: as.json('{ "number": 1 }'),
106
- parsedBase64: as.base64('dGVzdA=='),
107
- parsedTime: as.time('2m'),
108
- parsedDate: as.date('31-12-2024'),
96
+ import { $keys, $values, $strict } from 'ascertain';
97
+
98
+ const schema = {
99
+ [$keys]: /^[a-z]+$/, // All keys must match pattern
100
+ [$values]: Number, // All values must be numbers
101
+ [$strict]: true, // No extra properties allowed
109
102
  };
103
+ ```
110
104
 
111
- // create data schema
112
- const schema: Schema<typeof data> = {
113
- number: Number,
114
- string: String,
115
- boolean: Boolean,
116
- function: Function,
117
- array: Array,
118
- object: Object,
119
- date: and(Date, { toJSON: Function }),
120
- regexp: /regexp/,
121
- oneOfValue: or(1, 2, 3),
122
- arrayOfNumbers: [Number],
123
- objectSchema: {
124
- number: Number,
125
- },
126
- optional: optional({
127
- number: Number,
128
- }),
129
- keyValue: {
130
- [$keys]: /^key[A-Z]/,
131
- [$values]: Number
132
- },
133
- parsedNumber: Number,
134
- parsedString: String,
135
- parsedBoolean: Boolean,
136
- parsedArray: [String],
137
- parsedJSON: {
138
- number: 1,
139
- },
140
- parsedBase64: String,
141
- parsedTime: 2 * 60 * 1000, // two minutes
142
- parsedDate: Date,
105
+ ## Type Casting with `as`
106
+
107
+ Parse and validate values from strings (useful for environment variables, query params, etc.):
108
+
109
+ ```typescript
110
+ import { as } from 'ascertain';
111
+
112
+ as.string('hello') // 'hello'
113
+ as.number('42') // 42
114
+ as.number('3.14') // 3.14
115
+ as.number('1e10') // 10000000000
116
+ as.number('0xFF') // 255 (hex)
117
+ as.number('0o77') // 63 (octal)
118
+ as.number('0b1010') // 10 (binary)
119
+ as.boolean('true') // true
120
+ as.boolean('1') // true
121
+ as.date('2024-12-31') // Date object
122
+ as.time('2m') // 120000 (milliseconds)
123
+ as.time('1h') // 3600000
124
+ as.array('a,b,c', ',') // ['a', 'b', 'c']
125
+ as.json('{"x":1}') // { x: 1 }
126
+ as.base64('dGVzdA==') // 'test'
127
+ ```
128
+
129
+ Invalid values return a `TypeError` instead of throwing, enabling deferred validation:
130
+
131
+ ```typescript
132
+ const config = {
133
+ port: as.number(process.env.PORT), // TypeError if invalid
134
+ host: as.string(process.env.HOST),
143
135
  };
144
136
 
145
- // validate
146
- const validate = ascertain<typeof data>(schema, data, '[DATA]');
137
+ // Errors surface during schema validation with clear paths
138
+ ascertain({ port: Number, host: String }, config, 'Config');
147
139
  ```
148
140
 
149
- ### Benchmark VS zod and ajv
141
+ ## Config Validation with `createValidator`
142
+
143
+ For configuration objects, `createValidator` provides type-safe partial validation:
144
+
145
+ ```typescript
146
+ import { createValidator, as } from 'ascertain';
147
+
148
+ const config = {
149
+ app: { name: as.string(process.env.APP_NAME), port: as.number(process.env.PORT) },
150
+ db: { host: as.string(process.env.DB_HOST), pool: as.number(process.env.DB_POOL) },
151
+ redis: { url: as.string(process.env.REDIS_URL) },
152
+ };
153
+
154
+ const validate = createValidator(config, 'Config');
155
+
156
+ // Each module validates only what it needs
157
+ const { app, db } = validate({
158
+ app: { name: String, port: Number },
159
+ db: { host: String, pool: Number },
160
+ });
161
+ // app.name is typed as string
162
+ // app.port is typed as number
163
+ // redis is not accessible (TypeScript error)
150
164
  ```
151
- ⭐ Script ajv-vs-zod-vs-ascertain.js
152
- Suite ajv vs zod vs ascertain
153
- ➤ Perform benchmark
154
- ✓ Measure 500000 zod static schema validation
155
- ┌──────────┬──────────┬──────────┬──────────┬────────────┬────────┐
156
- │ (index) │ med │ p95 │ p99 │ total │ count │
157
- ├──────────┼──────────┼──────────┼──────────┼────────────┼────────┤
158
- 0.000699 │ 0.000788 │ 0.000996 │ 0.001624 │ 462.602373 │ 500000 │
159
- └──────────┴──────────┴──────────┴──────────┴────────────┴────────┘
160
- Measure 500000 zod dynamic schema validation
161
- ┌──────────┬──────────┬──────────┬──────────┬─────────────┬────────┐
162
- │ (index) │ med │ p95 │ p99 │ total │ count │
163
- ├──────────┼──────────┼──────────┼──────────┼─────────────┼────────┤
164
- 0.006248 │ 0.006918 │ 0.007524 │ 0.016948 │ 3780.465563 │ 500000 │
165
- └──────────┴──────────┴──────────┴──────────┴─────────────┴────────┘
166
- Measure 500000 ascertain static schema validation
167
- ┌──────────┬──────────┬──────────┬──────────┬───────────┬────────┐
168
- │ (index) │ med │ p95 │ p99 │ total │ count │
169
- ├──────────┼──────────┼──────────┼──────────┼───────────┼────────┤
170
- 0.000063 │ 0.000071 │ 0.000098 │ 0.000267 │ 41.673271 │ 500000 │
171
- └──────────┴──────────┴──────────┴──────────┴───────────┴────────┘
172
- Measure 500000 ascertain dynamic schema validation
173
- ┌──────────┬──────────┬──────────┬──────────┬────────────┬────────┐
174
- (index) │ med │ p95 │ p99 │ total │ count │
175
- ├──────────┼──────────┼──────────┼──────────┼────────────┼────────┤
176
- │ 0.000367 │ 0.000415 │ 0.000525 │ 0.001055 │ 239.078129 │ 500000 │
177
- └──────────┴──────────┴──────────┴──────────┴────────────┴────────┘
178
- Measure 500000 ajv compiled schema validation
179
- ┌──────────┬──────────┬──────────┬──────────┬───────────┬────────┐
180
- │ (index) │ med │ p95 │ p99 │ total │ count │
181
- ├──────────┼──────────┼──────────┼──────────┼───────────┼────────┤
182
- 0.000063 │ 0.000072 │ 0.000124 │ 0.000307 │ 44.542936 │ 500000 │
183
- └──────────┴──────────┴──────────┴──────────┴───────────┴────────┘
165
+
166
+ ## Complete Example
167
+
168
+ ```typescript
169
+ import { compile, or, optional, and, tuple, $keys, $values, $strict, as } from 'ascertain';
170
+
171
+ const schema = {
172
+ // Type validation
173
+ id: Number,
174
+ name: String,
175
+ active: Boolean,
176
+
177
+ // Pattern matching
178
+ email: /^[^@]+@[^@]+$/,
179
+
180
+ // Union types
181
+ status: or('pending', 'active', 'disabled'),
182
+
183
+ // Optional fields
184
+ nickname: optional(String),
185
+
186
+ // Nested objects
187
+ profile: {
188
+ bio: String,
189
+ links: [String],
190
+ },
191
+
192
+ // Combined constraints
193
+ createdAt: and(Date, { toISOString: Function }),
194
+
195
+ // Tuples
196
+ coordinates: tuple(Number, Number),
197
+
198
+ // Dynamic objects
199
+ metadata: {
200
+ [$keys]: /^[a-z_]+$/,
201
+ [$values]: or(String, Number),
202
+ [$strict]: true,
203
+ },
204
+
205
+ // Parsed values
206
+ retryCount: as.number(process.env.RETRY_COUNT),
207
+ timeout: as.time(process.env.TIMEOUT),
208
+ };
209
+
210
+ const validate = compile(schema, 'AppConfig');
211
+
212
+ // Throws TypeError with detailed path on validation failure
213
+ validate(data);
184
214
  ```
185
215
 
186
216
  ## License
187
- License [The MIT License](http://opensource.org/licenses/MIT)
188
- Copyright (c) 2019-2025 Ivan Zakharchanka
217
+
218
+ [The MIT License](http://opensource.org/licenses/MIT)
219
+
220
+ Copyright (c) 2019-2026 Ivan Zakharchanka
189
221
 
190
222
  [npm-url]: https://www.npmjs.com/package/ascertain
191
223
  [downloads-image]: https://img.shields.io/npm/dw/ascertain.svg?maxAge=43200
package/build/index.cjs CHANGED
@@ -33,6 +33,9 @@ _export(exports, {
33
33
  get compile () {
34
34
  return compile;
35
35
  },
36
+ get createValidator () {
37
+ return createValidator;
38
+ },
36
39
  get fromBase64 () {
37
40
  return fromBase64;
38
41
  },
@@ -84,13 +87,25 @@ const MULTIPLIERS = {
84
87
  d: 86400000,
85
88
  w: 604800000
86
89
  };
90
+ const TIME_REGEX = /^(\d*\.?\d*)(ms|s|m|h|d|w)?$/;
87
91
  const asError = (message)=>new TypeError(message);
88
92
  const as = {
89
93
  string: (value)=>{
90
94
  return typeof value === 'string' ? value : asError(`Invalid value "${value}", expected a string`);
91
95
  },
92
96
  number: (value)=>{
93
- const result = parseFloat(value);
97
+ if (typeof value !== 'string') {
98
+ return asError(`Invalid value ${value}, expected a valid number`);
99
+ }
100
+ const start = value[0] === '-' || value[0] === '+' ? 1 : 0;
101
+ const c0 = value.charCodeAt(start);
102
+ const c1 = value.charCodeAt(start + 1) | 32;
103
+ if (c0 === 48 && (c1 === 120 || c1 === 111 || c1 === 98)) {
104
+ const result = Number(start ? value.slice(1) : value);
105
+ if (Number.isNaN(result)) return asError(`Invalid value ${value}, expected a valid number`);
106
+ return value[0] === '-' ? -result : result;
107
+ }
108
+ const result = value.includes('.') || value.includes('e') || value.includes('E') ? parseFloat(value) : parseInt(value, 10);
94
109
  return Number.isNaN(result) ? asError(`Invalid value ${value}, expected a valid number`) : result;
95
110
  },
96
111
  date: (value)=>{
@@ -99,12 +114,16 @@ const as = {
99
114
  return Number.isNaN(date.valueOf()) ? asError(`Invalid value "${value}", expected a valid date format`) : date;
100
115
  },
101
116
  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}`);
117
+ if (!value) return asError(`Invalid value ${value}, expected a valid time format`);
118
+ const matches = value.match(TIME_REGEX);
119
+ if (!matches) return asError(`Invalid value ${value}, expected a valid time format`);
120
+ const [, amount, unit = 'ms'] = matches;
121
+ const multiplier = MULTIPLIERS[unit];
122
+ const parsed = parseFloat(amount);
123
+ if (!multiplier || Number.isNaN(parsed)) {
124
+ return asError(`Invalid value ${value}, expected a valid time format`);
106
125
  }
107
- return asError(`Invalid value ${value}, expected a valid time format`);
126
+ return Math.floor(parsed * multiplier / conversionFactor);
108
127
  },
109
128
  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
129
  array: (value, delimiter)=>value?.split?.(delimiter) ?? asError(`Invalid value ${value}, expected an array`),
@@ -125,12 +144,18 @@ const as = {
125
144
  };
126
145
  class Context {
127
146
  registry = [];
147
+ lookupMap = new Map();
128
148
  varIndex = 0;
129
149
  register(value) {
130
- if (!this.registry.includes(value)) {
131
- this.registry.push(value);
150
+ const index = this.lookupMap.get(value);
151
+ if (index !== undefined) {
152
+ return index;
153
+ }
154
+ {
155
+ const index = this.registry.push(value) - 1;
156
+ this.lookupMap.set(value, index);
157
+ return index;
132
158
  }
133
- return this.registry.indexOf(value);
134
159
  }
135
160
  unique(prefix) {
136
161
  return `${prefix}$$${this.varIndex++}`;
@@ -178,24 +203,34 @@ if (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(schema.sc
178
203
  codeGenExpectNonNullable(valueAlias, path),
179
204
  codeGenExpectObject(valueAlias, path, 'Array'),
180
205
  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}.\`); }`,
206
+ `if (${valueAlias}.length !== ${schema.schemas.length}) { throw new TypeError(\`Invalid tuple length \${${valueAlias}.length} for path "${path}", expected ${schema.schemas.length}.\`); }`,
182
207
  ...schema.schemas.map((s, idx)=>codeGenCollectErrors(errorsAlias, codeGen(s, context, `${valueAlias}[${idx}]`, `${path}[${idx}]`))),
183
208
  codeGenExpectNoErrors(errorsAlias)
184
209
  ];
185
210
  return code.join('\n');
186
211
  } else if (typeof schema === 'function') {
187
- const index = context.register(schema);
188
212
  const valueAlias = context.unique('v');
189
- const registryAlias = context.unique('r');
190
213
  const code = [
191
214
  `const ${valueAlias} = ${valuePath};`,
192
- `const ${registryAlias} = ctx.registry[${index}];`,
193
215
  codeGenExpectNonNullable(valueAlias, path)
194
216
  ];
195
217
  if (schema !== Error && !(schema?.prototype instanceof Error)) {
196
218
  code.push(codeGenExpectNonError(valueAlias, path));
197
219
  }
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}\`); }`);
220
+ const name = schema?.name;
221
+ const primitiveType = name === 'String' ? 'string' : name === 'Number' ? 'number' : name === 'Boolean' ? 'boolean' : name === 'BigInt' ? 'bigint' : name === 'Symbol' ? 'symbol' : null;
222
+ if (primitiveType) {
223
+ code.push(`if (typeof ${valueAlias} !== '${primitiveType}') { throw new TypeError(\`Invalid type \${typeof ${valueAlias}} for path "${path}", expected type ${schema?.name}\`); }`);
224
+ if (primitiveType === 'number') {
225
+ code.push(`if (Number.isNaN(${valueAlias})) { throw new TypeError(\`Invalid value \${${valueAlias}} for path "${path}", expected a valid ${schema?.name}\`); }`);
226
+ }
227
+ } else if (name === 'Function') {
228
+ code.push(`if (typeof ${valueAlias} !== 'function') { throw new TypeError(\`Invalid type \${typeof ${valueAlias}} for path "${path}", expected type Function\`); }`);
229
+ } else {
230
+ const index = context.register(schema);
231
+ const registryAlias = context.unique('r');
232
+ code.push(`const ${registryAlias} = ctx.registry[${index}];`, `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}\`); }`);
233
+ }
199
234
  return code.join('\n');
200
235
  } else if (Array.isArray(schema)) {
201
236
  const valueAlias = context.unique('v');
@@ -211,7 +246,12 @@ if (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(schema.sc
211
246
  const key = context.unique('key');
212
247
  const errorsAlias = context.unique('err');
213
248
  code.push(`const ${errorsAlias} = [];`);
214
- code.push(...schema.map((s)=>`${valueAlias}.forEach((${value},${key}) => { ${codeGenCollectErrors(errorsAlias, codeGen(s, context, value, `${path}[\${${key}}]`))} });`));
249
+ if (schema.length === 1) {
250
+ code.push(...schema.map((s)=>`for (let ${key} = 0; ${key} < ${valueAlias}.length; ${key}++) { const ${value} = ${valueAlias}[${key}]; ${codeGenCollectErrors(errorsAlias, codeGen(s, context, value, `${path}[\${${key}}]`))} }`));
251
+ } else {
252
+ code.push(`if (${valueAlias}.length > ${schema.length}) { throw new TypeError(\`Invalid tuple length \${${valueAlias}.length} for path "${path}", expected ${schema.length}.\`); }`);
253
+ code.push(...schema.map((s, idx)=>codeGenCollectErrors(errorsAlias, codeGen(s, context, `${valueAlias}[${idx}]`, `${path}[${idx}]`))));
254
+ }
215
255
  code.push(codeGenExpectNoErrors(errorsAlias));
216
256
  }
217
257
  return code.join('\n');
@@ -222,7 +262,7 @@ if (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(schema.sc
222
262
  const ${valueAlias} = ${valuePath};
223
263
  ${codeGenExpectNonNullable(valueAlias, path)}
224
264
  ${codeGenExpectNonError(valueAlias, path)}
225
- if (!${schema.toString()}.test('' + ${valueAlias})) { throw new TypeError(\`Invalid value \${${valueAlias}} for path "${path}", expected to match ${schema.toString()}\`); }
265
+ if (!${schema.toString()}.test(String(${valueAlias}))) { throw new TypeError(\`Invalid value \${${valueAlias}} for path "${path}", expected to match ${schema.toString()}\`); }
226
266
  `;
227
267
  } else {
228
268
  const valueAlias = context.unique('v');
@@ -239,7 +279,7 @@ if (!${schema.toString()}.test('' + ${valueAlias})) { throw new TypeError(\`Inva
239
279
  code.push(`
240
280
  const ${keysAlias} = Object.keys(${valueAlias});
241
281
  const ${errorsAlias} = [];
242
- ${keysAlias}.forEach(${kAlias} => { ${codeGenCollectErrors(errorsAlias, codeGen(schema[$keys], context, kAlias, `${path}[\${${kAlias}}]`))} });
282
+ for (const ${kAlias} of ${keysAlias}) { ${codeGenCollectErrors(errorsAlias, codeGen(schema[$keys], context, kAlias, `${path}[\${${kAlias}}]`))} }
243
283
  ${codeGenExpectNoErrors(errorsAlias)}
244
284
  `);
245
285
  }
@@ -251,7 +291,7 @@ ${codeGenExpectNoErrors(errorsAlias)}
251
291
  code.push(`
252
292
  const ${entriesAlias} = Object.entries(${valueAlias});
253
293
  const ${errorsAlias} = [];
254
- ${entriesAlias}.forEach(([${kAlias},${vAlias}]) => { ${codeGenCollectErrors(errorsAlias, codeGen(schema[$values], context, vAlias, `${path}[\${${kAlias}}]`))} });
294
+ for (const [${kAlias}, ${vAlias}] of ${entriesAlias}) { ${codeGenCollectErrors(errorsAlias, codeGen(schema[$values], context, vAlias, `${path}[\${${kAlias}}]`))} }
255
295
  ${codeGenExpectNoErrors(errorsAlias)}
256
296
  `);
257
297
  }
@@ -263,7 +303,7 @@ ${codeGenExpectNoErrors(errorsAlias)}
263
303
  code.push(`const ${extraAlias} = Object.keys(${valueAlias}).filter(${kAlias} => !${keysAlias}.has(${kAlias}));`);
264
304
  code.push(`if (${extraAlias}.length !== 0) { throw new TypeError(\`Extra properties: \${${extraAlias}}, are not allowed for path "${path}"\`); }`);
265
305
  }
266
- code.push(...Object.entries(schema).map(([key, s])=>codeGen(s, context, `${valueAlias}['${key}']`, `${path}.${key}`)));
306
+ code.push(...Object.entries(schema).map(([key, s])=>codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, `${path}.${key}`)));
267
307
  return `${code.join('\n')}`;
268
308
  }
269
309
  } else if (typeof schema === 'symbol') {
@@ -303,5 +343,11 @@ const compile = (schema, rootName)=>{
303
343
  const ascertain = (schema, data, rootName = '[root]')=>{
304
344
  compile(schema, rootName)(data);
305
345
  };
346
+ const createValidator = (config, rootName = '[root]')=>{
347
+ return (schema)=>{
348
+ ascertain(schema, config, rootName);
349
+ return config;
350
+ };
351
+ };
306
352
 
307
353
  //# sourceMappingURL=index.cjs.map