ascertain 2.0.88 → 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 +186 -154
- package/build/index.cjs +65 -19
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +67 -4
- package/build/index.js +62 -19
- package/build/index.js.map +1 -1
- package/package.json +21 -19
- package/src/index.ts +766 -0
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# Ascertain
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- Type
|
|
21
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
52
|
+
**For best performance, compile schemas once and reuse the validator:**
|
|
34
53
|
|
|
35
|
-
### Schema compilation
|
|
36
54
|
```typescript
|
|
37
|
-
import { compile
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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 {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
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
|
-
//
|
|
146
|
-
|
|
137
|
+
// Errors surface during schema validation with clear paths
|
|
138
|
+
ascertain({ port: Number, host: String }, config, 'Config');
|
|
147
139
|
```
|
|
148
140
|
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
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
|
-
|
|
131
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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}[
|
|
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
|