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.
Files changed (3) hide show
  1. package/index.js +26 -15
  2. package/package.json +9 -9
  3. 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
- data = {},
41
- { partial = false, coerce = true, stripUnknown = true } = {},
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 check
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
- // envvar special case (ignore input value)
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, c);
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 check
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 check
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 / max
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
- // ---------------- internal ----------------
165
+ // ---------------- internals ----------------
155
166
 
156
- _coerce(v, t, c) {
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.1.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
- "scripts": {
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