sibyl-ts 0.4.1 → 0.5.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/CHANGELOG.md +78 -71
- package/LICENSE +15 -15
- package/README.md +788 -749
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/validators/index.d.ts +2 -0
- package/dist/cjs/validators/index.d.ts.map +1 -1
- package/dist/cjs/validators/index.js +2 -0
- package/dist/cjs/validators/index.js.map +1 -1
- package/dist/cjs/validators/intersection.d.ts +7 -0
- package/dist/cjs/validators/intersection.d.ts.map +1 -0
- package/dist/cjs/validators/intersection.js +58 -0
- package/dist/cjs/validators/intersection.js.map +1 -0
- package/dist/cjs/validators/unknown.d.ts +3 -0
- package/dist/cjs/validators/unknown.d.ts.map +1 -0
- package/dist/cjs/validators/unknown.js +13 -0
- package/dist/cjs/validators/unknown.js.map +1 -0
- package/dist/esm/validators/index.d.ts +2 -0
- package/dist/esm/validators/index.d.ts.map +1 -1
- package/dist/esm/validators/index.js +2 -0
- package/dist/esm/validators/index.js.map +1 -1
- package/dist/esm/validators/intersection.d.ts +7 -0
- package/dist/esm/validators/intersection.d.ts.map +1 -0
- package/dist/esm/validators/intersection.js +54 -0
- package/dist/esm/validators/intersection.js.map +1 -0
- package/dist/esm/validators/unknown.d.ts +3 -0
- package/dist/esm/validators/unknown.d.ts.map +1 -0
- package/dist/esm/validators/unknown.js +9 -0
- package/dist/esm/validators/unknown.js.map +1 -0
- package/package.json +74 -74
package/README.md
CHANGED
|
@@ -1,749 +1,788 @@
|
|
|
1
|
-
# sibyl-ts
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/sibyl-ts)
|
|
4
|
-
[](https://opensource.org/licenses/ISC)
|
|
5
|
-
|
|
6
|
-
> _A TypeScript validation library inspired by the Sibyl System from Psycho-Pass - precise, unwavering judgment for your data._
|
|
7
|
-
|
|
8
|
-
## Features
|
|
9
|
-
|
|
10
|
-
- **Type-safe**: Full TypeScript support with automatic type inference
|
|
11
|
-
- **Comprehensive validators**: String, number, boolean, date, array, object, tuple, union, and more
|
|
12
|
-
- **String validators**: Email, URL, UUID, IP address validation
|
|
13
|
-
- **Safe judgment**: `tryJudge()` method returns results without throwing
|
|
14
|
-
- **Detailed errors**: Rich error messages with path information
|
|
15
|
-
- **Composable**: Build complex validators from simple ones
|
|
16
|
-
- **Dual module support**: Works with both ESM and CommonJS
|
|
17
|
-
|
|
18
|
-
## Installation
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
# npm
|
|
22
|
-
npm install sibyl-ts
|
|
23
|
-
|
|
24
|
-
# yarn
|
|
25
|
-
yarn add sibyl-ts
|
|
26
|
-
|
|
27
|
-
# pnpm
|
|
28
|
-
pnpm add sibyl-ts
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Quick Start
|
|
32
|
-
|
|
33
|
-
### ESM (ES Modules)
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
import { str, num, obj, array, email } from 'sibyl-ts';
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### CommonJS
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
const { str, num, obj, array, email } = require('sibyl-ts');
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Usage
|
|
46
|
-
|
|
47
|
-
### Basic Validation
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
import { str, num, bool } from 'sibyl-ts';
|
|
51
|
-
|
|
52
|
-
// String validation
|
|
53
|
-
const nameValidator = str({ minLen: 2, maxLen: 50 });
|
|
54
|
-
const inspector = nameValidator.judge('Akane Tsunemori'); // Returns: "Akane Tsunemori"
|
|
55
|
-
|
|
56
|
-
// Number validation (Crime Coefficient)
|
|
57
|
-
const crimeCoefficientValidator = num({ min: 0, max: 300 });
|
|
58
|
-
const coefficient = crimeCoefficientValidator.judge(45); // Returns: 45
|
|
59
|
-
|
|
60
|
-
// Boolean validation
|
|
61
|
-
const isEnforcerValidator = bool();
|
|
62
|
-
const isEnforcer = isEnforcerValidator.judge(true); // Returns: true
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Object Validation
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
import { str, num, obj, array, email } from 'sibyl-ts';
|
|
69
|
-
|
|
70
|
-
// Define a complex object validator for MWPSB personnel
|
|
71
|
-
const inspectorValidator = obj({
|
|
72
|
-
name: str({ minLen: 2, maxLen: 50 }),
|
|
73
|
-
crimeCoefficient: num({ min: 0, max: 300 }),
|
|
74
|
-
email: email(),
|
|
75
|
-
roles: array(str()),
|
|
76
|
-
profile: obj({
|
|
77
|
-
joinedAt: date(),
|
|
78
|
-
isLatentCriminal: bool(),
|
|
79
|
-
}),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Type is automatically inferred!
|
|
83
|
-
const inspector = inspectorValidator.judge({
|
|
84
|
-
name: 'Akane Tsunemori',
|
|
85
|
-
crimeCoefficient: 28,
|
|
86
|
-
email: 'akane.tsunemori@mwpsb.go.jp',
|
|
87
|
-
roles: ['inspector', 'unit-one'],
|
|
88
|
-
profile: {
|
|
89
|
-
joinedAt: new Date('2112-04-01'),
|
|
90
|
-
isLatentCriminal: false,
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
// Type: { name: string; crimeCoefficient: number; email: string; roles: string[]; profile: { joinedAt: Date; isLatentCriminal: boolean } }
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Safe Judgment (No Exceptions)
|
|
97
|
-
|
|
98
|
-
Use `tryJudge()` when you want to handle validation errors gracefully without throwing exceptions:
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
import { num } from 'sibyl-ts';
|
|
102
|
-
|
|
103
|
-
const crimeCoefficientValidator = num({ min: 0, max: 300 });
|
|
104
|
-
|
|
105
|
-
// tryJudge() returns a result object instead of throwing
|
|
106
|
-
const result = crimeCoefficientValidator.tryJudge(45);
|
|
107
|
-
if (result.type === 'success') {
|
|
108
|
-
console.log(`Crime Coefficient: ${result.data}`); // "Crime Coefficient: 45"
|
|
109
|
-
console.log('Target is not a threat. Trigger will be locked.');
|
|
110
|
-
} else {
|
|
111
|
-
console.error('Validation failed:', result.issues);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Example with invalid data
|
|
115
|
-
const enforcerCheck = crimeCoefficientValidator.tryJudge(350);
|
|
116
|
-
if (enforcerCheck.type === 'error') {
|
|
117
|
-
console.error(enforcerCheck.issues);
|
|
118
|
-
// [{ message: "Value 350 is greater than max 300", path: [] }]
|
|
119
|
-
console.log('Enforcement mode: Lethal Eliminator');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Compare with judge() which throws exceptions
|
|
123
|
-
try {
|
|
124
|
-
crimeCoefficientValidator.judge(500); // Throws!
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.error('Exception thrown!');
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
**When to use:**
|
|
131
|
-
|
|
132
|
-
- `judge()` - When you want exceptions thrown (typical validation)
|
|
133
|
-
- `tryJudge()` - When you want to handle errors gracefully (user input, APIs)
|
|
134
|
-
|
|
135
|
-
### Union Types
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
import { union, str, num, literal } from 'sibyl-ts';
|
|
139
|
-
|
|
140
|
-
// Dominator mode can be lethal or non-lethal
|
|
141
|
-
const dominatorModeValidator = union([
|
|
142
|
-
literal('paralyzer'),
|
|
143
|
-
literal('eliminator'),
|
|
144
|
-
literal('decomposer'),
|
|
145
|
-
]);
|
|
146
|
-
|
|
147
|
-
dominatorModeValidator.judge('paralyzer'); // OK
|
|
148
|
-
dominatorModeValidator.judge('eliminator'); // OK
|
|
149
|
-
dominatorModeValidator.judge('stun'); // Error!
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Optional and Nullable
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
import { str, optional, nullable, nullish } from 'sibyl-ts';
|
|
156
|
-
|
|
157
|
-
// Some inspectors may not have an enforcer assigned
|
|
158
|
-
const enforcerNameValidator = optional(str()); // string | undefined
|
|
159
|
-
enforcerNameValidator.judge('Shinya Kogami'); // OK
|
|
160
|
-
enforcerNameValidator.judge(undefined); // OK
|
|
161
|
-
|
|
162
|
-
// Crime coefficient can be null for non-citizens
|
|
163
|
-
const crimeCoefficientValidator = nullable(num()); // number | null
|
|
164
|
-
crimeCoefficientValidator.judge(120); // OK
|
|
165
|
-
crimeCoefficientValidator.judge(null); // OK
|
|
166
|
-
|
|
167
|
-
// Hue color can be nullish
|
|
168
|
-
const hueColorValidator = nullish(str()); // string | null | undefined
|
|
169
|
-
hueColorValidator.judge('clear'); // OK
|
|
170
|
-
hueColorValidator.judge(null); // OK
|
|
171
|
-
hueColorValidator.judge(undefined); // OK
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## Available Validators
|
|
175
|
-
|
|
176
|
-
### Primitive Validators
|
|
177
|
-
|
|
178
|
-
#### `str(options?)`
|
|
179
|
-
|
|
180
|
-
String validation with optional constraints.
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
import { str } from 'sibyl-ts';
|
|
184
|
-
|
|
185
|
-
// Basic string
|
|
186
|
-
const nameValidator = str();
|
|
187
|
-
nameValidator.judge('Akane Tsunemori'); // ✓
|
|
188
|
-
|
|
189
|
-
// With length constraints
|
|
190
|
-
const enforcerIdValidator = str({ minLen: 5, maxLen: 10 });
|
|
191
|
-
enforcerIdValidator.judge('ENF-001'); // ✓
|
|
192
|
-
enforcerIdValidator.judge('E1'); // ✗ Too short
|
|
193
|
-
|
|
194
|
-
// With pattern (regex)
|
|
195
|
-
const idPatternValidator = str({ pattern: /^[A-Z]{3}-\d{3}$/ });
|
|
196
|
-
idPatternValidator.judge('INS-042'); // ✓
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
**Options:**
|
|
200
|
-
|
|
201
|
-
- `minLen?: number` - Minimum string length
|
|
202
|
-
- `maxLen?: number` - Maximum string length
|
|
203
|
-
- `pattern?: RegExp` - Regex pattern to match
|
|
204
|
-
- `coerce?: boolean` - Coerce non-string values to strings
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
#### `num(options?)`
|
|
209
|
-
|
|
210
|
-
Number validation with optional min/max values.
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
import { num } from 'sibyl-ts';
|
|
214
|
-
|
|
215
|
-
// Crime Coefficient (0-300)
|
|
216
|
-
const coefficientValidator = num({ min: 0, max: 300 });
|
|
217
|
-
coefficientValidator.judge(85); // ✓
|
|
218
|
-
coefficientValidator.judge(350); // ✗ Too high
|
|
219
|
-
|
|
220
|
-
// Age with coercion
|
|
221
|
-
const ageValidator = num({ min: 18, coerce: true });
|
|
222
|
-
ageValidator.judge('25'); // ✓ Coerced to 25
|
|
223
|
-
ageValidator.judge(30); // ✓
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
**Options:**
|
|
227
|
-
|
|
228
|
-
- `min?: number` - Minimum value
|
|
229
|
-
- `max?: number` - Maximum value
|
|
230
|
-
- `coerce?: boolean` - Coerce strings to numbers
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
#### `bool(options?)`
|
|
235
|
-
|
|
236
|
-
Boolean validation.
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
import { bool } from 'sibyl-ts';
|
|
240
|
-
|
|
241
|
-
// Strict boolean
|
|
242
|
-
const isLatentValidator = bool();
|
|
243
|
-
isLatentValidator.judge(true); // ✓
|
|
244
|
-
isLatentValidator.judge(false); // ✓
|
|
245
|
-
isLatentValidator.judge(1); // ✗ Not a boolean
|
|
246
|
-
|
|
247
|
-
// With coercion
|
|
248
|
-
const activeFlagValidator = bool({ coerce: true });
|
|
249
|
-
activeFlagValidator.judge('true'); // ✓ Coerced to true
|
|
250
|
-
activeFlagValidator.judge(1); // ✓ Coerced to true
|
|
251
|
-
activeFlagValidator.judge(0); // ✓ Coerced to false
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**Options:**
|
|
255
|
-
|
|
256
|
-
- `coerce?: boolean` - Coerce truthy/falsy values to boolean
|
|
257
|
-
|
|
258
|
-
---
|
|
259
|
-
|
|
260
|
-
#### `date(options?)`
|
|
261
|
-
|
|
262
|
-
Date validation.
|
|
263
|
-
|
|
264
|
-
```typescript
|
|
265
|
-
import { date } from 'sibyl-ts';
|
|
266
|
-
|
|
267
|
-
// Basic date validation
|
|
268
|
-
const joinDateValidator = date();
|
|
269
|
-
joinDateValidator.judge(new Date('2112-04-01')); // ✓
|
|
270
|
-
joinDateValidator.judge('2112-04-01'); // ✓ String is auto-converted
|
|
271
|
-
|
|
272
|
-
// With min/max constraints
|
|
273
|
-
const recentDateValidator = date({
|
|
274
|
-
min: new Date('2110-01-01'),
|
|
275
|
-
max: new Date('2115-12-31'),
|
|
276
|
-
});
|
|
277
|
-
recentDateValidator.judge(new Date('2112-04-01')); // ✓
|
|
278
|
-
recentDateValidator.judge(new Date('2100-01-01')); // ✗ Before min
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
**Expected input:** JavaScript `Date` object or valid date string
|
|
282
|
-
|
|
283
|
-
**Options:**
|
|
284
|
-
|
|
285
|
-
- `min?: Date` - Minimum date
|
|
286
|
-
- `max?: Date` - Maximum date
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
---
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
name:
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
//
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
```
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1
|
+
# sibyl-ts
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/sibyl-ts)
|
|
4
|
+
[](https://opensource.org/licenses/ISC)
|
|
5
|
+
|
|
6
|
+
> _A TypeScript validation library inspired by the Sibyl System from Psycho-Pass - precise, unwavering judgment for your data._
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Type-safe**: Full TypeScript support with automatic type inference
|
|
11
|
+
- **Comprehensive validators**: String, number, boolean, date, array, object, tuple, union, and more
|
|
12
|
+
- **String validators**: Email, URL, UUID, IP address validation
|
|
13
|
+
- **Safe judgment**: `tryJudge()` method returns results without throwing
|
|
14
|
+
- **Detailed errors**: Rich error messages with path information
|
|
15
|
+
- **Composable**: Build complex validators from simple ones
|
|
16
|
+
- **Dual module support**: Works with both ESM and CommonJS
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# npm
|
|
22
|
+
npm install sibyl-ts
|
|
23
|
+
|
|
24
|
+
# yarn
|
|
25
|
+
yarn add sibyl-ts
|
|
26
|
+
|
|
27
|
+
# pnpm
|
|
28
|
+
pnpm add sibyl-ts
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### ESM (ES Modules)
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { str, num, obj, array, email } from 'sibyl-ts';
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### CommonJS
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const { str, num, obj, array, email } = require('sibyl-ts');
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### Basic Validation
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { str, num, bool } from 'sibyl-ts';
|
|
51
|
+
|
|
52
|
+
// String validation
|
|
53
|
+
const nameValidator = str({ minLen: 2, maxLen: 50 });
|
|
54
|
+
const inspector = nameValidator.judge('Akane Tsunemori'); // Returns: "Akane Tsunemori"
|
|
55
|
+
|
|
56
|
+
// Number validation (Crime Coefficient)
|
|
57
|
+
const crimeCoefficientValidator = num({ min: 0, max: 300 });
|
|
58
|
+
const coefficient = crimeCoefficientValidator.judge(45); // Returns: 45
|
|
59
|
+
|
|
60
|
+
// Boolean validation
|
|
61
|
+
const isEnforcerValidator = bool();
|
|
62
|
+
const isEnforcer = isEnforcerValidator.judge(true); // Returns: true
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Object Validation
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { str, num, obj, array, email } from 'sibyl-ts';
|
|
69
|
+
|
|
70
|
+
// Define a complex object validator for MWPSB personnel
|
|
71
|
+
const inspectorValidator = obj({
|
|
72
|
+
name: str({ minLen: 2, maxLen: 50 }),
|
|
73
|
+
crimeCoefficient: num({ min: 0, max: 300 }),
|
|
74
|
+
email: email(),
|
|
75
|
+
roles: array(str()),
|
|
76
|
+
profile: obj({
|
|
77
|
+
joinedAt: date(),
|
|
78
|
+
isLatentCriminal: bool(),
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Type is automatically inferred!
|
|
83
|
+
const inspector = inspectorValidator.judge({
|
|
84
|
+
name: 'Akane Tsunemori',
|
|
85
|
+
crimeCoefficient: 28,
|
|
86
|
+
email: 'akane.tsunemori@mwpsb.go.jp',
|
|
87
|
+
roles: ['inspector', 'unit-one'],
|
|
88
|
+
profile: {
|
|
89
|
+
joinedAt: new Date('2112-04-01'),
|
|
90
|
+
isLatentCriminal: false,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
// Type: { name: string; crimeCoefficient: number; email: string; roles: string[]; profile: { joinedAt: Date; isLatentCriminal: boolean } }
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Safe Judgment (No Exceptions)
|
|
97
|
+
|
|
98
|
+
Use `tryJudge()` when you want to handle validation errors gracefully without throwing exceptions:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { num } from 'sibyl-ts';
|
|
102
|
+
|
|
103
|
+
const crimeCoefficientValidator = num({ min: 0, max: 300 });
|
|
104
|
+
|
|
105
|
+
// tryJudge() returns a result object instead of throwing
|
|
106
|
+
const result = crimeCoefficientValidator.tryJudge(45);
|
|
107
|
+
if (result.type === 'success') {
|
|
108
|
+
console.log(`Crime Coefficient: ${result.data}`); // "Crime Coefficient: 45"
|
|
109
|
+
console.log('Target is not a threat. Trigger will be locked.');
|
|
110
|
+
} else {
|
|
111
|
+
console.error('Validation failed:', result.issues);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Example with invalid data
|
|
115
|
+
const enforcerCheck = crimeCoefficientValidator.tryJudge(350);
|
|
116
|
+
if (enforcerCheck.type === 'error') {
|
|
117
|
+
console.error(enforcerCheck.issues);
|
|
118
|
+
// [{ message: "Value 350 is greater than max 300", path: [] }]
|
|
119
|
+
console.log('Enforcement mode: Lethal Eliminator');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Compare with judge() which throws exceptions
|
|
123
|
+
try {
|
|
124
|
+
crimeCoefficientValidator.judge(500); // Throws!
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('Exception thrown!');
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**When to use:**
|
|
131
|
+
|
|
132
|
+
- `judge()` - When you want exceptions thrown (typical validation)
|
|
133
|
+
- `tryJudge()` - When you want to handle errors gracefully (user input, APIs)
|
|
134
|
+
|
|
135
|
+
### Union Types
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { union, str, num, literal } from 'sibyl-ts';
|
|
139
|
+
|
|
140
|
+
// Dominator mode can be lethal or non-lethal
|
|
141
|
+
const dominatorModeValidator = union([
|
|
142
|
+
literal('paralyzer'),
|
|
143
|
+
literal('eliminator'),
|
|
144
|
+
literal('decomposer'),
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
dominatorModeValidator.judge('paralyzer'); // OK
|
|
148
|
+
dominatorModeValidator.judge('eliminator'); // OK
|
|
149
|
+
dominatorModeValidator.judge('stun'); // Error!
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Optional and Nullable
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { str, optional, nullable, nullish } from 'sibyl-ts';
|
|
156
|
+
|
|
157
|
+
// Some inspectors may not have an enforcer assigned
|
|
158
|
+
const enforcerNameValidator = optional(str()); // string | undefined
|
|
159
|
+
enforcerNameValidator.judge('Shinya Kogami'); // OK
|
|
160
|
+
enforcerNameValidator.judge(undefined); // OK
|
|
161
|
+
|
|
162
|
+
// Crime coefficient can be null for non-citizens
|
|
163
|
+
const crimeCoefficientValidator = nullable(num()); // number | null
|
|
164
|
+
crimeCoefficientValidator.judge(120); // OK
|
|
165
|
+
crimeCoefficientValidator.judge(null); // OK
|
|
166
|
+
|
|
167
|
+
// Hue color can be nullish
|
|
168
|
+
const hueColorValidator = nullish(str()); // string | null | undefined
|
|
169
|
+
hueColorValidator.judge('clear'); // OK
|
|
170
|
+
hueColorValidator.judge(null); // OK
|
|
171
|
+
hueColorValidator.judge(undefined); // OK
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Available Validators
|
|
175
|
+
|
|
176
|
+
### Primitive Validators
|
|
177
|
+
|
|
178
|
+
#### `str(options?)`
|
|
179
|
+
|
|
180
|
+
String validation with optional constraints.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { str } from 'sibyl-ts';
|
|
184
|
+
|
|
185
|
+
// Basic string
|
|
186
|
+
const nameValidator = str();
|
|
187
|
+
nameValidator.judge('Akane Tsunemori'); // ✓
|
|
188
|
+
|
|
189
|
+
// With length constraints
|
|
190
|
+
const enforcerIdValidator = str({ minLen: 5, maxLen: 10 });
|
|
191
|
+
enforcerIdValidator.judge('ENF-001'); // ✓
|
|
192
|
+
enforcerIdValidator.judge('E1'); // ✗ Too short
|
|
193
|
+
|
|
194
|
+
// With pattern (regex)
|
|
195
|
+
const idPatternValidator = str({ pattern: /^[A-Z]{3}-\d{3}$/ });
|
|
196
|
+
idPatternValidator.judge('INS-042'); // ✓
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Options:**
|
|
200
|
+
|
|
201
|
+
- `minLen?: number` - Minimum string length
|
|
202
|
+
- `maxLen?: number` - Maximum string length
|
|
203
|
+
- `pattern?: RegExp` - Regex pattern to match
|
|
204
|
+
- `coerce?: boolean` - Coerce non-string values to strings
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
#### `num(options?)`
|
|
209
|
+
|
|
210
|
+
Number validation with optional min/max values.
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { num } from 'sibyl-ts';
|
|
214
|
+
|
|
215
|
+
// Crime Coefficient (0-300)
|
|
216
|
+
const coefficientValidator = num({ min: 0, max: 300 });
|
|
217
|
+
coefficientValidator.judge(85); // ✓
|
|
218
|
+
coefficientValidator.judge(350); // ✗ Too high
|
|
219
|
+
|
|
220
|
+
// Age with coercion
|
|
221
|
+
const ageValidator = num({ min: 18, coerce: true });
|
|
222
|
+
ageValidator.judge('25'); // ✓ Coerced to 25
|
|
223
|
+
ageValidator.judge(30); // ✓
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Options:**
|
|
227
|
+
|
|
228
|
+
- `min?: number` - Minimum value
|
|
229
|
+
- `max?: number` - Maximum value
|
|
230
|
+
- `coerce?: boolean` - Coerce strings to numbers
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
#### `bool(options?)`
|
|
235
|
+
|
|
236
|
+
Boolean validation.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { bool } from 'sibyl-ts';
|
|
240
|
+
|
|
241
|
+
// Strict boolean
|
|
242
|
+
const isLatentValidator = bool();
|
|
243
|
+
isLatentValidator.judge(true); // ✓
|
|
244
|
+
isLatentValidator.judge(false); // ✓
|
|
245
|
+
isLatentValidator.judge(1); // ✗ Not a boolean
|
|
246
|
+
|
|
247
|
+
// With coercion
|
|
248
|
+
const activeFlagValidator = bool({ coerce: true });
|
|
249
|
+
activeFlagValidator.judge('true'); // ✓ Coerced to true
|
|
250
|
+
activeFlagValidator.judge(1); // ✓ Coerced to true
|
|
251
|
+
activeFlagValidator.judge(0); // ✓ Coerced to false
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Options:**
|
|
255
|
+
|
|
256
|
+
- `coerce?: boolean` - Coerce truthy/falsy values to boolean
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
#### `date(options?)`
|
|
261
|
+
|
|
262
|
+
Date validation.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { date } from 'sibyl-ts';
|
|
266
|
+
|
|
267
|
+
// Basic date validation
|
|
268
|
+
const joinDateValidator = date();
|
|
269
|
+
joinDateValidator.judge(new Date('2112-04-01')); // ✓
|
|
270
|
+
joinDateValidator.judge('2112-04-01'); // ✓ String is auto-converted
|
|
271
|
+
|
|
272
|
+
// With min/max constraints
|
|
273
|
+
const recentDateValidator = date({
|
|
274
|
+
min: new Date('2110-01-01'),
|
|
275
|
+
max: new Date('2115-12-31'),
|
|
276
|
+
});
|
|
277
|
+
recentDateValidator.judge(new Date('2112-04-01')); // ✓
|
|
278
|
+
recentDateValidator.judge(new Date('2100-01-01')); // ✗ Before min
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Expected input:** JavaScript `Date` object or valid date string
|
|
282
|
+
|
|
283
|
+
**Options:**
|
|
284
|
+
|
|
285
|
+
- `min?: Date` - Minimum date
|
|
286
|
+
- `max?: Date` - Maximum date
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
#### `unknown()`
|
|
291
|
+
|
|
292
|
+
Passthrough validator that accepts any value. Useful for data that hasn't been scanned by the Sibyl System yet.
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { unknown } from 'sibyl-ts';
|
|
296
|
+
|
|
297
|
+
const unscannedEvidenceValidator = unknown();
|
|
298
|
+
unscannedEvidenceValidator.judge('mysterious device'); // ✓
|
|
299
|
+
unscannedEvidenceValidator.judge({ threatLevel: 'unknown' }); // ✓
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Type:** `unknown`
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### String Validators
|
|
307
|
+
|
|
308
|
+
#### `email()`
|
|
309
|
+
|
|
310
|
+
Email address validation (RFC 5322).
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { email } from 'sibyl-ts';
|
|
314
|
+
|
|
315
|
+
const emailValidator = email();
|
|
316
|
+
emailValidator.judge('akane@mwpsb.go.jp'); // ✓
|
|
317
|
+
emailValidator.judge('kogami@enforcer'); // ✗ Invalid format
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
#### `url()`
|
|
323
|
+
|
|
324
|
+
URL validation.
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { url } from 'sibyl-ts';
|
|
328
|
+
|
|
329
|
+
const urlValidator = url();
|
|
330
|
+
urlValidator.judge('https://sibyl-system.jp'); // ✓
|
|
331
|
+
urlValidator.judge('not-a-url'); // ✗
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
#### `uuid()`
|
|
337
|
+
|
|
338
|
+
UUID validation (v4).
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { uuid } from 'sibyl-ts';
|
|
342
|
+
|
|
343
|
+
const sessionIdValidator = uuid();
|
|
344
|
+
sessionIdValidator.judge('550e8400-e29b-41d4-a716-446655440000'); // ✓
|
|
345
|
+
sessionIdValidator.judge('not-a-uuid'); // ✗
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
#### `ip()`
|
|
351
|
+
|
|
352
|
+
IP address validation (IPv4 and IPv6).
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { ip } from 'sibyl-ts';
|
|
356
|
+
|
|
357
|
+
const ipValidator = ip();
|
|
358
|
+
ipValidator.judge('192.168.1.1'); // ✓ IPv4
|
|
359
|
+
ipValidator.judge('2001:0db8::1'); // ✓ IPv6
|
|
360
|
+
ipValidator.judge('999.999.999.999'); // ✗ Invalid
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
### Complex Validators
|
|
366
|
+
|
|
367
|
+
#### `array(elementValidator, options?)`
|
|
368
|
+
|
|
369
|
+
Array validation with typed elements.
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import { array, str } from 'sibyl-ts';
|
|
373
|
+
|
|
374
|
+
// Array of enforcer names
|
|
375
|
+
const enforcersValidator = array(str());
|
|
376
|
+
enforcersValidator.judge(['Shinya Kogami', 'Nobuchika Ginoza']); // ✓
|
|
377
|
+
enforcersValidator.judge(['Valid', 123]); // ✗ Element 1 is not a string
|
|
378
|
+
|
|
379
|
+
// With length constraints
|
|
380
|
+
const rolesValidator = array(str(), { minLen: 1, maxLen: 5 });
|
|
381
|
+
rolesValidator.judge(['inspector', 'analyst']); // ✓
|
|
382
|
+
rolesValidator.judge([]); // ✗ Too short
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Options:**
|
|
386
|
+
|
|
387
|
+
- `minLen?: number` - Minimum array length
|
|
388
|
+
- `maxLen?: number` - Maximum array length
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
#### `obj(shape)`
|
|
393
|
+
|
|
394
|
+
Object validation with typed properties.
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
import { obj, str, num } from 'sibyl-ts';
|
|
398
|
+
|
|
399
|
+
const enforcerValidator = obj({
|
|
400
|
+
name: str(),
|
|
401
|
+
coefficient: num({ min: 100, max: 300 }),
|
|
402
|
+
division: str(),
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
enforcerValidator.judge({
|
|
406
|
+
name: 'Shinya Kogami',
|
|
407
|
+
coefficient: 180,
|
|
408
|
+
division: 'Unit 1',
|
|
409
|
+
}); // ✓
|
|
410
|
+
|
|
411
|
+
enforcerValidator.judge({
|
|
412
|
+
name: 'Kogami',
|
|
413
|
+
// Missing required fields
|
|
414
|
+
}); // ✗
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Expected input:** Object matching the shape definition
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
#### `partial(objectValidator)`
|
|
422
|
+
|
|
423
|
+
Creates a validator where all properties of an object are optional.
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { obj, partial, str, num } from 'sibyl-ts';
|
|
427
|
+
|
|
428
|
+
// Define an enforcer profile
|
|
429
|
+
const enforcerProfileValidator = obj({
|
|
430
|
+
name: str(),
|
|
431
|
+
coefficient: num({ min: 100, max: 300 }),
|
|
432
|
+
division: str(),
|
|
433
|
+
yearsOfService: num(),
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Make all properties optional for partial updates
|
|
437
|
+
const partialEnforcerValidator = partial(enforcerProfileValidator);
|
|
438
|
+
|
|
439
|
+
// All fields are now optional
|
|
440
|
+
partialEnforcerValidator.judge({
|
|
441
|
+
coefficient: 195, // Update only the coefficient
|
|
442
|
+
}); // ✓
|
|
443
|
+
|
|
444
|
+
partialEnforcerValidator.judge({
|
|
445
|
+
name: 'Shinya Kogami',
|
|
446
|
+
division: 'Unit 1',
|
|
447
|
+
}); // ✓ Update name and division
|
|
448
|
+
|
|
449
|
+
partialEnforcerValidator.judge({}); // ✓ Empty object is valid
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Parameters:**
|
|
453
|
+
|
|
454
|
+
- `objectValidator` - An object validator created with `obj()`
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
#### `omit(objectValidator, keys)`
|
|
459
|
+
|
|
460
|
+
Creates a validator that omits specific properties from an object validator.
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { obj, omit, str, num, bool } from 'sibyl-ts';
|
|
464
|
+
|
|
465
|
+
// Full inspector profile
|
|
466
|
+
const inspectorValidator = obj({
|
|
467
|
+
name: str(),
|
|
468
|
+
crimeCoefficient: num({ min: 0, max: 300 }),
|
|
469
|
+
email: email(),
|
|
470
|
+
isLatentCriminal: bool(),
|
|
471
|
+
securityClearance: str(),
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Public profile - omit sensitive fields
|
|
475
|
+
const publicProfileValidator = omit(inspectorValidator, [
|
|
476
|
+
'crimeCoefficient',
|
|
477
|
+
'isLatentCriminal',
|
|
478
|
+
'securityClearance',
|
|
479
|
+
]);
|
|
480
|
+
|
|
481
|
+
// Only name and email are required
|
|
482
|
+
publicProfileValidator.judge({
|
|
483
|
+
name: 'Akane Tsunemori',
|
|
484
|
+
email: 'akane.tsunemori@mwpsb.go.jp',
|
|
485
|
+
}); // ✓
|
|
486
|
+
|
|
487
|
+
publicProfileValidator.judge({
|
|
488
|
+
name: 'Akane',
|
|
489
|
+
email: 'akane@mwpsb.go.jp',
|
|
490
|
+
crimeCoefficient: 28, // ✗ This field should not be present
|
|
491
|
+
}); // ✗
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**Parameters:**
|
|
495
|
+
|
|
496
|
+
- `objectValidator` - An object validator created with `obj()`
|
|
497
|
+
- `keys` - Array of property names to omit
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
#### `tuple([...validators])`
|
|
502
|
+
|
|
503
|
+
Fixed-length array (tuple) validation.
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
import { tuple, str, num } from 'sibyl-ts';
|
|
507
|
+
|
|
508
|
+
// [name, coefficient] pair
|
|
509
|
+
const personTupleValidator = tuple([str(), num()]);
|
|
510
|
+
personTupleValidator.judge(['Akane Tsunemori', 28]); // ✓
|
|
511
|
+
personTupleValidator.judge(['Akane', 28, 'extra']); // ✗ Wrong length
|
|
512
|
+
personTupleValidator.judge(['Akane']); // ✗ Missing element
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**Expected input:** Array with exact length matching validators array
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
#### `union([...validators])`
|
|
520
|
+
|
|
521
|
+
Union type validation (OR logic).
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import { union, str, num, literal } from 'sibyl-ts';
|
|
525
|
+
|
|
526
|
+
// Can be string OR number
|
|
527
|
+
const idValidator = union([str(), num()]);
|
|
528
|
+
idValidator.judge('INS-001'); // ✓
|
|
529
|
+
idValidator.judge(42); // ✓
|
|
530
|
+
idValidator.judge(true); // ✗ Not in union
|
|
531
|
+
|
|
532
|
+
// Dominator modes
|
|
533
|
+
const modeValidator = union([literal('paralyzer'), literal('eliminator')]);
|
|
534
|
+
modeValidator.judge('paralyzer'); // ✓
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
**Expected input:** Value matching at least one validator in the array
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
#### `intersection([...validators])`
|
|
542
|
+
|
|
543
|
+
Intersection type validation (AND logic). Combines multiple validators and deeply merges the results of object validators.
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
import { intersection, obj, str, num } from 'sibyl-ts';
|
|
547
|
+
|
|
548
|
+
// Combine multiple object schemas
|
|
549
|
+
const personValidator = obj({ name: str() });
|
|
550
|
+
const employeeValidator = obj({ role: str(), salary: num() });
|
|
551
|
+
|
|
552
|
+
// Resulting type is flattened: { name: string; role: string; salary: number }
|
|
553
|
+
const employeeProfileValidator = intersection([personValidator, employeeValidator]);
|
|
554
|
+
|
|
555
|
+
employeeProfileValidator.judge({
|
|
556
|
+
name: 'Akane Tsunemori',
|
|
557
|
+
role: 'Inspector',
|
|
558
|
+
salary: 100000,
|
|
559
|
+
}); // ✓
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**Expected input:** Value matching **ALL** validators in the array
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
#### `record(keyValidator, valueValidator)`
|
|
567
|
+
|
|
568
|
+
Record/dictionary validation.
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
import { record, str, num } from 'sibyl-ts';
|
|
572
|
+
|
|
573
|
+
// Map of enforcer names to coefficients
|
|
574
|
+
const coefficientsValidator = record(str(), num());
|
|
575
|
+
coefficientsValidator.judge({
|
|
576
|
+
Kogami: 180,
|
|
577
|
+
Ginoza: 87,
|
|
578
|
+
Masaoka: 150,
|
|
579
|
+
}); // ✓
|
|
580
|
+
|
|
581
|
+
coefficientsValidator.judge({
|
|
582
|
+
Kogami: '180', // ✗ Value should be number
|
|
583
|
+
}); // ✗
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
**Expected input:** Object with dynamic keys/values matching validators
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
### Literal & Enum Validators
|
|
591
|
+
|
|
592
|
+
#### `literal(value)`
|
|
593
|
+
|
|
594
|
+
Exact value validation.
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
import { literal } from 'sibyl-ts';
|
|
598
|
+
|
|
599
|
+
const roleValidator = literal('inspector');
|
|
600
|
+
roleValidator.judge('inspector'); // ✓
|
|
601
|
+
roleValidator.judge('enforcer'); // ✗ Not exact match
|
|
602
|
+
|
|
603
|
+
// Works with numbers, booleans too
|
|
604
|
+
const statusCodeValidator = literal(200);
|
|
605
|
+
statusCodeValidator.judge(200); // ✓
|
|
606
|
+
statusCodeValidator.judge(404); // ✗
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
**Expected input:** Exact value specified
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
#### `nativeEnum(enumObject)`
|
|
614
|
+
|
|
615
|
+
Native TypeScript enum validation.
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
import { nativeEnum } from 'sibyl-ts';
|
|
619
|
+
|
|
620
|
+
enum Division {
|
|
621
|
+
CriminalInvestigation = 'CI',
|
|
622
|
+
Enforcement = 'ENF',
|
|
623
|
+
Analysis = 'AN',
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const divisionValidator = nativeEnum(Division);
|
|
627
|
+
divisionValidator.judge(Division.Enforcement); // ✓ 'ENF'
|
|
628
|
+
divisionValidator.judge('ENF'); // ✓
|
|
629
|
+
divisionValidator.judge('INVALID'); // ✗
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**Expected input:** Enum value or string matching enum
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
### Modifier Validators
|
|
637
|
+
|
|
638
|
+
#### `optional(validator, defaultValue?)`
|
|
639
|
+
|
|
640
|
+
Makes a validator optional (allows `undefined`).
|
|
641
|
+
|
|
642
|
+
```typescript
|
|
643
|
+
import { optional, str } from 'sibyl-ts';
|
|
644
|
+
|
|
645
|
+
// Without default value
|
|
646
|
+
const nicknameValidator = optional(str());
|
|
647
|
+
nicknameValidator.judge('Ko'); // ✓
|
|
648
|
+
nicknameValidator.judge(undefined); // ✓
|
|
649
|
+
nicknameValidator.judge(null); // ✗ Use nullable() for null
|
|
650
|
+
|
|
651
|
+
// With default value
|
|
652
|
+
const rankValidator = optional(str(), 'Inspector');
|
|
653
|
+
rankValidator.judge('Enforcer'); // ✓ 'Enforcer'
|
|
654
|
+
rankValidator.judge(undefined); // ✓ 'Inspector' (default)
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
**Type:** `T | undefined` (or `T` if default value provided)
|
|
658
|
+
|
|
659
|
+
**Parameters:**
|
|
660
|
+
|
|
661
|
+
- `validator` - The validator to make optional
|
|
662
|
+
- `defaultValue?` - Optional default value when undefined
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
#### `nullable(validator)`
|
|
667
|
+
|
|
668
|
+
Makes a validator nullable (allows `null`).
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
import { nullable, num } from 'sibyl-ts';
|
|
672
|
+
|
|
673
|
+
const previousCoefficientValidator = nullable(num());
|
|
674
|
+
previousCoefficientValidator.judge(150); // ✓
|
|
675
|
+
previousCoefficientValidator.judge(null); // ✓ No previous record
|
|
676
|
+
previousCoefficientValidator.judge(undefined); // ✗
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
**Type:** `T | null`
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
#### `undef()`
|
|
684
|
+
|
|
685
|
+
Explicitly validates `undefined`.
|
|
686
|
+
|
|
687
|
+
```typescript
|
|
688
|
+
import { undef } from 'sibyl-ts';
|
|
689
|
+
|
|
690
|
+
const undefValidator = undef();
|
|
691
|
+
undefValidator.judge(undefined); // ✓
|
|
692
|
+
undefValidator.judge(null); // ✗
|
|
693
|
+
undefValidator.judge(false); // ✗
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
**Type:** `undefined`
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
#### `nil()`
|
|
701
|
+
|
|
702
|
+
Explicitly validates `null`.
|
|
703
|
+
|
|
704
|
+
```typescript
|
|
705
|
+
import { nil } from 'sibyl-ts';
|
|
706
|
+
|
|
707
|
+
const nilValidator = nil();
|
|
708
|
+
nilValidator.judge(null); // ✓
|
|
709
|
+
nilValidator.judge(undefined); // ✗
|
|
710
|
+
nilValidator.judge(false); // ✗
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
**Type:** `null`
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
#### `nullish(validator)`
|
|
718
|
+
|
|
719
|
+
Makes a validator nullish (allows `null` AND `undefined`).
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
import { nullish, str } from 'sibyl-ts';
|
|
723
|
+
|
|
724
|
+
const hueColorValidator = nullish(str());
|
|
725
|
+
hueColorValidator.judge('clear'); // ✓
|
|
726
|
+
hueColorValidator.judge(null); // ✓
|
|
727
|
+
hueColorValidator.judge(undefined); // ✓
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
**Type:** `T | null | undefined`
|
|
731
|
+
|
|
732
|
+
## TypeScript Configuration
|
|
733
|
+
|
|
734
|
+
For best results, enable strict mode in your `tsconfig.json`:
|
|
735
|
+
|
|
736
|
+
```json
|
|
737
|
+
{
|
|
738
|
+
"compilerOptions": {
|
|
739
|
+
"strict": true,
|
|
740
|
+
"moduleResolution": "node",
|
|
741
|
+
"esModuleInterop": true
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
## Error Handling
|
|
747
|
+
|
|
748
|
+
Validators throw detailed errors when validation fails:
|
|
749
|
+
|
|
750
|
+
```typescript
|
|
751
|
+
import { JudgmentError } from 'sibyl-ts';
|
|
752
|
+
|
|
753
|
+
try {
|
|
754
|
+
const validator = num({ min: 0, max: 300 });
|
|
755
|
+
validator.judge(350); // Crime coefficient too high!
|
|
756
|
+
} catch (error) {
|
|
757
|
+
if (error instanceof JudgmentError) {
|
|
758
|
+
console.log(error.issues);
|
|
759
|
+
// [{ message: "Value 350 is greater than max 300", path: [] }]
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
## Troubleshooting
|
|
765
|
+
|
|
766
|
+
### Module Resolution Issues
|
|
767
|
+
|
|
768
|
+
If you encounter module resolution errors:
|
|
769
|
+
|
|
770
|
+
1. **Ensure Node.js version >= 16.0.0**
|
|
771
|
+
2. **For ESM**: Make sure your `package.json` has `"type": "module"`
|
|
772
|
+
3. **For TypeScript**: Set `"moduleResolution": "node"` in `tsconfig.json`
|
|
773
|
+
|
|
774
|
+
### Type Inference Not Working
|
|
775
|
+
|
|
776
|
+
Make sure TypeScript strict mode is enabled and you're using TypeScript 5.0+.
|
|
777
|
+
|
|
778
|
+
## Contributing
|
|
779
|
+
|
|
780
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
781
|
+
|
|
782
|
+
## License
|
|
783
|
+
|
|
784
|
+
ISC - see [LICENSE](LICENSE) file for details.
|
|
785
|
+
|
|
786
|
+
## Changelog
|
|
787
|
+
|
|
788
|
+
See [CHANGELOG.md](CHANGELOG.md) for release history.
|