turbo-schema 0.1.0 → 0.2.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/index.js +26 -15
- package/package.json +9 -9
- package/readme.md +5 -426
package/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const UUID =
|
|
6
6
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7
|
+
|
|
7
8
|
class ValidationError extends Error {
|
|
8
9
|
constructor(errors) {
|
|
9
10
|
const message = errors.length
|
|
@@ -11,13 +12,10 @@ class ValidationError extends Error {
|
|
|
11
12
|
: "Schema validation failed";
|
|
12
13
|
|
|
13
14
|
super(message);
|
|
14
|
-
|
|
15
15
|
this.name = "ValidationError";
|
|
16
16
|
|
|
17
|
-
// backward compatibility
|
|
18
17
|
this.errors = errors;
|
|
19
18
|
|
|
20
|
-
// structured version
|
|
21
19
|
this.details = errors.map((msg) => {
|
|
22
20
|
const [field, ...rest] = msg.split(" ");
|
|
23
21
|
return {
|
|
@@ -36,10 +34,9 @@ class Schema {
|
|
|
36
34
|
this.def = def;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
validate(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
) {
|
|
37
|
+
validate(data = {}, opts = {}) {
|
|
38
|
+
const { partial = false, coerce = true, stripUnknown = true } = opts;
|
|
39
|
+
|
|
43
40
|
const out = {};
|
|
44
41
|
const errors = [];
|
|
45
42
|
|
|
@@ -54,7 +51,7 @@ class Schema {
|
|
|
54
51
|
v = typeof c.default === "function" ? c.default() : c.default;
|
|
55
52
|
}
|
|
56
53
|
|
|
57
|
-
// required
|
|
54
|
+
// required
|
|
58
55
|
if (c.required && v == null) {
|
|
59
56
|
errors.push(`${k} required`);
|
|
60
57
|
continue;
|
|
@@ -62,7 +59,21 @@ class Schema {
|
|
|
62
59
|
|
|
63
60
|
if (v == null) continue;
|
|
64
61
|
|
|
65
|
-
//
|
|
62
|
+
// nested schema support (object + schema)
|
|
63
|
+
if (c.type === "object" && c.schema instanceof Schema) {
|
|
64
|
+
if (typeof v !== "object" || Array.isArray(v)) {
|
|
65
|
+
errors.push(`${k} must be object`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const nested = c.schema.validate(v, opts);
|
|
70
|
+
|
|
71
|
+
Object.assign(out, { [k]: nested });
|
|
72
|
+
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// envvar special case
|
|
66
77
|
if (c.type === "envvar") {
|
|
67
78
|
const envName = c.varname;
|
|
68
79
|
|
|
@@ -80,7 +91,7 @@ class Schema {
|
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
// coercion
|
|
83
|
-
if (coerce) v = this._coerce(v, c.type
|
|
94
|
+
if (coerce) v = this._coerce(v, c.type);
|
|
84
95
|
|
|
85
96
|
// type check
|
|
86
97
|
const typeErr = this._type(k, v, c.type);
|
|
@@ -89,13 +100,13 @@ class Schema {
|
|
|
89
100
|
continue;
|
|
90
101
|
}
|
|
91
102
|
|
|
92
|
-
// enum
|
|
103
|
+
// enum
|
|
93
104
|
if (c.enum && !c.enum.includes(v)) {
|
|
94
105
|
errors.push(`${k} invalid enum`);
|
|
95
106
|
continue;
|
|
96
107
|
}
|
|
97
108
|
|
|
98
|
-
// pattern
|
|
109
|
+
// pattern
|
|
99
110
|
if (c.pattern) {
|
|
100
111
|
const ok =
|
|
101
112
|
c.pattern instanceof RegExp
|
|
@@ -108,7 +119,7 @@ class Schema {
|
|
|
108
119
|
}
|
|
109
120
|
}
|
|
110
121
|
|
|
111
|
-
// min
|
|
122
|
+
// min/max
|
|
112
123
|
if (c.min != null && v < c.min) {
|
|
113
124
|
errors.push(`${k} too small`);
|
|
114
125
|
continue;
|
|
@@ -151,9 +162,9 @@ class Schema {
|
|
|
151
162
|
return Object.keys(this.def);
|
|
152
163
|
}
|
|
153
164
|
|
|
154
|
-
// ----------------
|
|
165
|
+
// ---------------- internals ----------------
|
|
155
166
|
|
|
156
|
-
_coerce(v, t
|
|
167
|
+
_coerce(v, t) {
|
|
157
168
|
switch (t) {
|
|
158
169
|
case "string":
|
|
159
170
|
return String(v);
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "commonjs",
|
|
3
3
|
"name": "turbo-schema",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "Light weight object validation schema",
|
|
6
6
|
"main": "index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node --test",
|
|
9
|
+
"test:watch": "node --test --watch",
|
|
10
|
+
"test:coverage": "node --test --experimental-test-coverage",
|
|
11
|
+
"test:only": "node --test --test-only"
|
|
12
|
+
},
|
|
7
13
|
"keywords": [
|
|
8
14
|
"light",
|
|
9
15
|
"weight",
|
|
@@ -21,11 +27,5 @@
|
|
|
21
27
|
"url": "git+https://github.com/miketerry-org/turbo-schema.git"
|
|
22
28
|
},
|
|
23
29
|
"license": "MIT",
|
|
24
|
-
"author": "Mike Terry"
|
|
25
|
-
|
|
26
|
-
"test": "node --test",
|
|
27
|
-
"test:watch": "node --test --watch",
|
|
28
|
-
"test:coverage": "node --test --experimental-test-coverage",
|
|
29
|
-
"test:only": "node --test --test-only"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
30
|
+
"author": "Mike Terry"
|
|
31
|
+
}
|
package/readme.md
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight, dependency-free schema validation library for Node.js.
|
|
4
4
|
|
|
5
|
-
Turbo Schema validates JavaScript objects against a declarative schema definition. It supports type coercion, default values, required fields, enumerations, regular expression validation, numeric ranges, UUID validation, and configurable handling of unknown properties.
|
|
5
|
+
Turbo Schema validates JavaScript objects against a declarative schema definition. It supports type coercion, default values, required fields, enumerations, regular expression validation, numeric ranges, UUID validation, and configurable handling of unknown properties. It also supports nested schema validation for deeply structured objects.
|
|
6
|
+
|
|
7
|
+
---
|
|
6
8
|
|
|
7
9
|
## Features
|
|
8
10
|
|
|
9
11
|
- Zero dependencies
|
|
10
12
|
- Declarative object schemas
|
|
13
|
+
- Nested schema validation
|
|
11
14
|
- Type validation
|
|
12
15
|
- Optional type coercion
|
|
13
16
|
- Default values
|
|
@@ -25,428 +28,4 @@ Turbo Schema validates JavaScript objects against a declarative schema definitio
|
|
|
25
28
|
## Installation
|
|
26
29
|
|
|
27
30
|
```bash
|
|
28
|
-
npm install turbo-schema
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Basic Usage
|
|
34
|
-
|
|
35
|
-
```javascript
|
|
36
|
-
const { Schema } = require("turbo-schema");
|
|
37
|
-
|
|
38
|
-
const userSchema = new Schema({
|
|
39
|
-
id: {
|
|
40
|
-
type: "uuid",
|
|
41
|
-
required: true,
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
name: {
|
|
45
|
-
type: "string",
|
|
46
|
-
required: true,
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
age: {
|
|
50
|
-
type: "integer",
|
|
51
|
-
min: 18,
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
active: {
|
|
55
|
-
type: "boolean",
|
|
56
|
-
default: true,
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const user = userSchema.validate({
|
|
61
|
-
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
62
|
-
name: "Alice",
|
|
63
|
-
age: "25",
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
console.log(user);
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Output:
|
|
70
|
-
|
|
71
|
-
```javascript
|
|
72
|
-
{
|
|
73
|
-
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
74
|
-
name: "Alice",
|
|
75
|
-
age: 25,
|
|
76
|
-
active: true
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
# Schema Definition
|
|
83
|
-
|
|
84
|
-
A schema consists of an object whose keys represent field names and whose values define validation rules.
|
|
85
|
-
|
|
86
|
-
Example:
|
|
87
|
-
|
|
88
|
-
```javascript
|
|
89
|
-
const schema = new Schema({
|
|
90
|
-
username: {
|
|
91
|
-
type: "string",
|
|
92
|
-
required: true,
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Field Properties
|
|
100
|
-
|
|
101
|
-
| Property | Type | Description |
|
|
102
|
-
| ---------- | -------------- | ------------------------------------------------------ |
|
|
103
|
-
| `type` | String | Expected data type |
|
|
104
|
-
| `required` | Boolean | Field must be present |
|
|
105
|
-
| `default` | Any / Function | Default value if missing or `null` |
|
|
106
|
-
| `enum` | Array | List of allowed values |
|
|
107
|
-
| `pattern` | RegExp | Regular expression or any object implementing `test()` |
|
|
108
|
-
| `min` | Number | Minimum numeric value |
|
|
109
|
-
| `max` | Number | Maximum numeric value |
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## Supported Types
|
|
114
|
-
|
|
115
|
-
| Type | Description |
|
|
116
|
-
| ---------- | -------------------------- |
|
|
117
|
-
| `string` | String value |
|
|
118
|
-
| `integer` | Integer number |
|
|
119
|
-
| `number` | Floating point or integer |
|
|
120
|
-
| `boolean` | Boolean value |
|
|
121
|
-
| `uuid` | UUID string |
|
|
122
|
-
| `array` | JavaScript array |
|
|
123
|
-
| `object` | Plain JavaScript object |
|
|
124
|
-
| `date` | Date string (`YYYY-MM-DD`) |
|
|
125
|
-
| `datetime` | JavaScript `Date` object |
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
# Validation
|
|
130
|
-
|
|
131
|
-
```javascript
|
|
132
|
-
const validated = schema.validate(data);
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Returns a new validated object.
|
|
136
|
-
|
|
137
|
-
If validation fails, a `ValidationError` is thrown.
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
# Validation Options
|
|
142
|
-
|
|
143
|
-
```javascript
|
|
144
|
-
schema.validate(data, {
|
|
145
|
-
partial: false,
|
|
146
|
-
coerce: true,
|
|
147
|
-
stripUnknown: true,
|
|
148
|
-
});
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## partial
|
|
152
|
-
|
|
153
|
-
Default: `false`
|
|
154
|
-
|
|
155
|
-
When `true`, fields that are missing from the input object are ignored instead of being validated.
|
|
156
|
-
|
|
157
|
-
Useful for PATCH requests or partial updates.
|
|
158
|
-
|
|
159
|
-
```javascript
|
|
160
|
-
schema.validate(update, {
|
|
161
|
-
partial: true,
|
|
162
|
-
});
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
---
|
|
166
|
-
|
|
167
|
-
## coerce
|
|
168
|
-
|
|
169
|
-
Default: `true`
|
|
170
|
-
|
|
171
|
-
Automatically converts values into the expected type when possible.
|
|
172
|
-
|
|
173
|
-
Example:
|
|
174
|
-
|
|
175
|
-
Input:
|
|
176
|
-
|
|
177
|
-
```javascript
|
|
178
|
-
{
|
|
179
|
-
age: "42";
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Output:
|
|
184
|
-
|
|
185
|
-
```javascript
|
|
186
|
-
{
|
|
187
|
-
age: 42;
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
Disable coercion:
|
|
192
|
-
|
|
193
|
-
```javascript
|
|
194
|
-
schema.validate(data, {
|
|
195
|
-
coerce: false,
|
|
196
|
-
});
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
## stripUnknown
|
|
202
|
-
|
|
203
|
-
Default: `true`
|
|
204
|
-
|
|
205
|
-
Controls how properties not defined in the schema are handled.
|
|
206
|
-
|
|
207
|
-
Input:
|
|
208
|
-
|
|
209
|
-
```javascript
|
|
210
|
-
{
|
|
211
|
-
name: "Alice",
|
|
212
|
-
admin: true
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Schema:
|
|
217
|
-
|
|
218
|
-
```javascript
|
|
219
|
-
const schema = new Schema({
|
|
220
|
-
name: {
|
|
221
|
-
type: "string",
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
With the default setting:
|
|
227
|
-
|
|
228
|
-
```javascript
|
|
229
|
-
{
|
|
230
|
-
name: "Alice";
|
|
231
|
-
}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
To preserve unknown properties:
|
|
235
|
-
|
|
236
|
-
```javascript
|
|
237
|
-
schema.validate(data, {
|
|
238
|
-
stripUnknown: false,
|
|
239
|
-
});
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
Result:
|
|
243
|
-
|
|
244
|
-
```javascript
|
|
245
|
-
{
|
|
246
|
-
name: "Alice",
|
|
247
|
-
admin: true
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
253
|
-
# Default Values
|
|
254
|
-
|
|
255
|
-
Defaults are applied when a property is `null` or `undefined`.
|
|
256
|
-
|
|
257
|
-
Static default:
|
|
258
|
-
|
|
259
|
-
```javascript
|
|
260
|
-
count: {
|
|
261
|
-
type: "integer",
|
|
262
|
-
default: 0
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
Function default:
|
|
267
|
-
|
|
268
|
-
```javascript
|
|
269
|
-
created: {
|
|
270
|
-
type: "datetime",
|
|
271
|
-
default: () => new Date()
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
# Required Fields
|
|
278
|
-
|
|
279
|
-
```javascript
|
|
280
|
-
email: {
|
|
281
|
-
type: "string",
|
|
282
|
-
required: true
|
|
283
|
-
}
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
Missing required fields generate validation errors.
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
# Enumerations
|
|
291
|
-
|
|
292
|
-
```javascript
|
|
293
|
-
status: {
|
|
294
|
-
enum: ["active", "inactive", "pending"];
|
|
295
|
-
}
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
Only values contained in the array are accepted.
|
|
299
|
-
|
|
300
|
-
---
|
|
301
|
-
|
|
302
|
-
# Pattern Validation
|
|
303
|
-
|
|
304
|
-
```javascript
|
|
305
|
-
zip: {
|
|
306
|
-
type: "string",
|
|
307
|
-
pattern: /^\d{5}$/
|
|
308
|
-
}
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
Any object implementing a `test()` method may also be supplied.
|
|
312
|
-
|
|
313
|
-
---
|
|
314
|
-
|
|
315
|
-
# Numeric Ranges
|
|
316
|
-
|
|
317
|
-
```javascript
|
|
318
|
-
age: {
|
|
319
|
-
type: "integer",
|
|
320
|
-
min: 18,
|
|
321
|
-
max: 120
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
---
|
|
326
|
-
|
|
327
|
-
# UUID Validation
|
|
328
|
-
|
|
329
|
-
```javascript
|
|
330
|
-
id: {
|
|
331
|
-
type: "uuid";
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
UUID values are validated using a built-in regular expression.
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
# Validation Errors
|
|
340
|
-
|
|
341
|
-
Validation failures throw a `ValidationError`.
|
|
342
|
-
|
|
343
|
-
```javascript
|
|
344
|
-
try {
|
|
345
|
-
schema.validate(data);
|
|
346
|
-
} catch (err) {
|
|
347
|
-
console.error(err.message);
|
|
348
|
-
}
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
Example:
|
|
352
|
-
|
|
353
|
-
```
|
|
354
|
-
Schema validation failed: age too small, email required
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
---
|
|
358
|
-
|
|
359
|
-
## ValidationError Properties
|
|
360
|
-
|
|
361
|
-
### message
|
|
362
|
-
|
|
363
|
-
A human-readable summary of the validation failures.
|
|
364
|
-
|
|
365
|
-
Example:
|
|
366
|
-
|
|
367
|
-
```
|
|
368
|
-
Schema validation failed: age too small, email required
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
---
|
|
372
|
-
|
|
373
|
-
### errors
|
|
374
|
-
|
|
375
|
-
An array of validation messages.
|
|
376
|
-
|
|
377
|
-
```javascript
|
|
378
|
-
["age too small", "email required"];
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
---
|
|
382
|
-
|
|
383
|
-
### details
|
|
384
|
-
|
|
385
|
-
A structured version of each validation error.
|
|
386
|
-
|
|
387
|
-
```javascript
|
|
388
|
-
[
|
|
389
|
-
{
|
|
390
|
-
field: "age",
|
|
391
|
-
message: "too small",
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
field: "email",
|
|
395
|
-
message: "required",
|
|
396
|
-
},
|
|
397
|
-
];
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
---
|
|
401
|
-
|
|
402
|
-
# isValid()
|
|
403
|
-
|
|
404
|
-
Returns `true` if validation succeeds, otherwise `false`.
|
|
405
|
-
|
|
406
|
-
```javascript
|
|
407
|
-
if (schema.isValid(data)) {
|
|
408
|
-
console.log("Valid");
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
Equivalent to calling `validate()` and catching any `ValidationError`.
|
|
413
|
-
|
|
414
|
-
---
|
|
415
|
-
|
|
416
|
-
# fields()
|
|
417
|
-
|
|
418
|
-
Returns an array of field names defined by the schema.
|
|
419
|
-
|
|
420
|
-
```javascript
|
|
421
|
-
schema.fields();
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
Example:
|
|
425
|
-
|
|
426
|
-
```javascript
|
|
427
|
-
["id", "name", "email"];
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
---
|
|
431
|
-
|
|
432
|
-
# Type Coercion
|
|
433
|
-
|
|
434
|
-
When coercion is enabled (the default), Turbo Schema performs the following conversions where possible.
|
|
435
|
-
|
|
436
|
-
| Type | Example |
|
|
437
|
-
| ---------- | --------------------- |
|
|
438
|
-
| `string` | `123 → "123"` |
|
|
439
|
-
| `integer` | `"42" → 42` |
|
|
440
|
-
| `number` | `"3.14" → 3.14` |
|
|
441
|
-
| `boolean` | `"true" → true` |
|
|
442
|
-
| `boolean` | `"false" → false` |
|
|
443
|
-
| `date` | `Date → "YYYY-MM-DD"` |
|
|
444
|
-
| `datetime` | ISO string → `Date` |
|
|
445
|
-
|
|
446
|
-
If a value cannot be coerced, the original value is retained and normal validation determines whether it is valid.
|
|
447
|
-
|
|
448
|
-
---
|
|
449
|
-
|
|
450
|
-
# License
|
|
451
|
-
|
|
452
|
-
MIT
|
|
31
|
+
npm install turbo-schema
|