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/README.md CHANGED
@@ -1,749 +1,788 @@
1
- # sibyl-ts
2
-
3
- [![npm version](https://img.shields.io/npm/v/sibyl-ts.svg)](https://www.npmjs.com/package/sibyl-ts)
4
- [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](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
- ### String Validators
291
-
292
- #### `email()`
293
-
294
- Email address validation (RFC 5322).
295
-
296
- ```typescript
297
- import { email } from 'sibyl-ts';
298
-
299
- const emailValidator = email();
300
- emailValidator.judge('akane@mwpsb.go.jp'); // ✓
301
- emailValidator.judge('kogami@enforcer'); // ✗ Invalid format
302
- ```
303
-
304
- ---
305
-
306
- #### `url()`
307
-
308
- URL validation.
309
-
310
- ```typescript
311
- import { url } from 'sibyl-ts';
312
-
313
- const urlValidator = url();
314
- urlValidator.judge('https://sibyl-system.jp'); // ✓
315
- urlValidator.judge('not-a-url'); // ✗
316
- ```
317
-
318
- ---
319
-
320
- #### `uuid()`
321
-
322
- UUID validation (v4).
323
-
324
- ```typescript
325
- import { uuid } from 'sibyl-ts';
326
-
327
- const sessionIdValidator = uuid();
328
- sessionIdValidator.judge('550e8400-e29b-41d4-a716-446655440000'); // ✓
329
- sessionIdValidator.judge('not-a-uuid'); // ✗
330
- ```
331
-
332
- ---
333
-
334
- #### `ip()`
335
-
336
- IP address validation (IPv4 and IPv6).
337
-
338
- ```typescript
339
- import { ip } from 'sibyl-ts';
340
-
341
- const ipValidator = ip();
342
- ipValidator.judge('192.168.1.1'); // ✓ IPv4
343
- ipValidator.judge('2001:0db8::1'); // ✓ IPv6
344
- ipValidator.judge('999.999.999.999'); // ✗ Invalid
345
- ```
346
-
347
- ---
348
-
349
- ### Complex Validators
350
-
351
- #### `array(elementValidator, options?)`
352
-
353
- Array validation with typed elements.
354
-
355
- ```typescript
356
- import { array, str } from 'sibyl-ts';
357
-
358
- // Array of enforcer names
359
- const enforcersValidator = array(str());
360
- enforcersValidator.judge(['Shinya Kogami', 'Nobuchika Ginoza']); //
361
- enforcersValidator.judge(['Valid', 123]); // ✗ Element 1 is not a string
362
-
363
- // With length constraints
364
- const rolesValidator = array(str(), { minLen: 1, maxLen: 5 });
365
- rolesValidator.judge(['inspector', 'analyst']); // ✓
366
- rolesValidator.judge([]); // ✗ Too short
367
- ```
368
-
369
- **Options:**
370
-
371
- - `minLen?: number` - Minimum array length
372
- - `maxLen?: number` - Maximum array length
373
-
374
- ---
375
-
376
- #### `obj(shape)`
377
-
378
- Object validation with typed properties.
379
-
380
- ```typescript
381
- import { obj, str, num } from 'sibyl-ts';
382
-
383
- const enforcerValidator = obj({
384
- name: str(),
385
- coefficient: num({ min: 100, max: 300 }),
386
- division: str(),
387
- });
388
-
389
- enforcerValidator.judge({
390
- name: 'Shinya Kogami',
391
- coefficient: 180,
392
- division: 'Unit 1',
393
- }); // ✓
394
-
395
- enforcerValidator.judge({
396
- name: 'Kogami',
397
- // Missing required fields
398
- }); // ✗
399
- ```
400
-
401
- **Expected input:** Object matching the shape definition
402
-
403
- ---
404
-
405
- #### `partial(objectValidator)`
406
-
407
- Creates a validator where all properties of an object are optional.
408
-
409
- ```typescript
410
- import { obj, partial, str, num } from 'sibyl-ts';
411
-
412
- // Define an enforcer profile
413
- const enforcerProfileValidator = obj({
414
- name: str(),
415
- coefficient: num({ min: 100, max: 300 }),
416
- division: str(),
417
- yearsOfService: num(),
418
- });
419
-
420
- // Make all properties optional for partial updates
421
- const partialEnforcerValidator = partial(enforcerProfileValidator);
422
-
423
- // All fields are now optional
424
- partialEnforcerValidator.judge({
425
- coefficient: 195, // Update only the coefficient
426
- }); //
427
-
428
- partialEnforcerValidator.judge({
429
- name: 'Shinya Kogami',
430
- division: 'Unit 1',
431
- }); // Update name and division
432
-
433
- partialEnforcerValidator.judge({}); // ✓ Empty object is valid
434
- ```
435
-
436
- **Parameters:**
437
-
438
- - `objectValidator` - An object validator created with `obj()`
439
-
440
- ---
441
-
442
- #### `omit(objectValidator, keys)`
443
-
444
- Creates a validator that omits specific properties from an object validator.
445
-
446
- ```typescript
447
- import { obj, omit, str, num, bool } from 'sibyl-ts';
448
-
449
- // Full inspector profile
450
- const inspectorValidator = obj({
451
- name: str(),
452
- crimeCoefficient: num({ min: 0, max: 300 }),
453
- email: email(),
454
- isLatentCriminal: bool(),
455
- securityClearance: str(),
456
- });
457
-
458
- // Public profile - omit sensitive fields
459
- const publicProfileValidator = omit(inspectorValidator, [
460
- 'crimeCoefficient',
461
- 'isLatentCriminal',
462
- 'securityClearance',
463
- ]);
464
-
465
- // Only name and email are required
466
- publicProfileValidator.judge({
467
- name: 'Akane Tsunemori',
468
- email: 'akane.tsunemori@mwpsb.go.jp',
469
- }); // ✓
470
-
471
- publicProfileValidator.judge({
472
- name: 'Akane',
473
- email: 'akane@mwpsb.go.jp',
474
- crimeCoefficient: 28, // This field should not be present
475
- }); //
476
- ```
477
-
478
- **Parameters:**
479
-
480
- - `objectValidator` - An object validator created with `obj()`
481
- - `keys` - Array of property names to omit
482
-
483
- ---
484
-
485
- #### `tuple([...validators])`
486
-
487
- Fixed-length array (tuple) validation.
488
-
489
- ```typescript
490
- import { tuple, str, num } from 'sibyl-ts';
491
-
492
- // [name, coefficient] pair
493
- const personTupleValidator = tuple([str(), num()]);
494
- personTupleValidator.judge(['Akane Tsunemori', 28]); // ✓
495
- personTupleValidator.judge(['Akane', 28, 'extra']); // ✗ Wrong length
496
- personTupleValidator.judge(['Akane']); // Missing element
497
- ```
498
-
499
- **Expected input:** Array with exact length matching validators array
500
-
501
- ---
502
-
503
- #### `union([...validators])`
504
-
505
- Union type validation (OR logic).
506
-
507
- ```typescript
508
- import { union, str, num, literal } from 'sibyl-ts';
509
-
510
- // Can be string OR number
511
- const idValidator = union([str(), num()]);
512
- idValidator.judge('INS-001'); //
513
- idValidator.judge(42); // ✓
514
- idValidator.judge(true); // ✗ Not in union
515
-
516
- // Dominator modes
517
- const modeValidator = union([literal('paralyzer'), literal('eliminator')]);
518
- modeValidator.judge('paralyzer'); // ✓
519
- ```
520
-
521
- **Expected input:** Value matching at least one validator in the array
522
-
523
- ---
524
-
525
- #### `record(keyValidator, valueValidator)`
526
-
527
- Record/dictionary validation.
528
-
529
- ```typescript
530
- import { record, str, num } from 'sibyl-ts';
531
-
532
- // Map of enforcer names to coefficients
533
- const coefficientsValidator = record(str(), num());
534
- coefficientsValidator.judge({
535
- Kogami: 180,
536
- Ginoza: 87,
537
- Masaoka: 150,
538
- }); // ✓
539
-
540
- coefficientsValidator.judge({
541
- Kogami: '180', // ✗ Value should be number
542
- }); // ✗
543
- ```
544
-
545
- **Expected input:** Object with dynamic keys/values matching validators
546
-
547
- ---
548
-
549
- ### Literal & Enum Validators
550
-
551
- #### `literal(value)`
552
-
553
- Exact value validation.
554
-
555
- ```typescript
556
- import { literal } from 'sibyl-ts';
557
-
558
- const roleValidator = literal('inspector');
559
- roleValidator.judge('inspector'); // ✓
560
- roleValidator.judge('enforcer'); // ✗ Not exact match
561
-
562
- // Works with numbers, booleans too
563
- const statusCodeValidator = literal(200);
564
- statusCodeValidator.judge(200); // ✓
565
- statusCodeValidator.judge(404); // ✗
566
- ```
567
-
568
- **Expected input:** Exact value specified
569
-
570
- ---
571
-
572
- #### `nativeEnum(enumObject)`
573
-
574
- Native TypeScript enum validation.
575
-
576
- ```typescript
577
- import { nativeEnum } from 'sibyl-ts';
578
-
579
- enum Division {
580
- CriminalInvestigation = 'CI',
581
- Enforcement = 'ENF',
582
- Analysis = 'AN',
583
- }
584
-
585
- const divisionValidator = nativeEnum(Division);
586
- divisionValidator.judge(Division.Enforcement); // 'ENF'
587
- divisionValidator.judge('ENF'); // ✓
588
- divisionValidator.judge('INVALID'); // ✗
589
- ```
590
-
591
- **Expected input:** Enum value or string matching enum
592
-
593
- ---
594
-
595
- ### Modifier Validators
596
-
597
- #### `optional(validator, defaultValue?)`
598
-
599
- Makes a validator optional (allows `undefined`).
600
-
601
- ```typescript
602
- import { optional, str } from 'sibyl-ts';
603
-
604
- // Without default value
605
- const nicknameValidator = optional(str());
606
- nicknameValidator.judge('Ko'); //
607
- nicknameValidator.judge(undefined); // ✓
608
- nicknameValidator.judge(null); // ✗ Use nullable() for null
609
-
610
- // With default value
611
- const rankValidator = optional(str(), 'Inspector');
612
- rankValidator.judge('Enforcer'); // ✓ 'Enforcer'
613
- rankValidator.judge(undefined); // ✓ 'Inspector' (default)
614
- ```
615
-
616
- **Type:** `T | undefined` (or `T` if default value provided)
617
-
618
- **Parameters:**
619
-
620
- - `validator` - The validator to make optional
621
- - `defaultValue?` - Optional default value when undefined
622
-
623
- ---
624
-
625
- #### `nullable(validator)`
626
-
627
- Makes a validator nullable (allows `null`).
628
-
629
- ```typescript
630
- import { nullable, num } from 'sibyl-ts';
631
-
632
- const previousCoefficientValidator = nullable(num());
633
- previousCoefficientValidator.judge(150); // ✓
634
- previousCoefficientValidator.judge(null); // ✓ No previous record
635
- previousCoefficientValidator.judge(undefined); // ✗
636
- ```
637
-
638
-
639
- **Type:** `T | null`
640
-
641
- ---
642
-
643
- #### `undef()`
644
-
645
- Explicitly validates `undefined`.
646
-
647
- ```typescript
648
- import { undef } from 'sibyl-ts';
649
-
650
- const undefValidator = undef();
651
- undefValidator.judge(undefined); //
652
- undefValidator.judge(null); // ✗
653
- undefValidator.judge(false); //
654
- ```
655
-
656
- **Type:** `undefined`
657
-
658
- ---
659
-
660
- #### `nil()`
661
-
662
- Explicitly validates `null`.
663
-
664
- ```typescript
665
- import { nil } from 'sibyl-ts';
666
-
667
- const nilValidator = nil();
668
- nilValidator.judge(null); // ✓
669
- nilValidator.judge(undefined); // ✗
670
- nilValidator.judge(false); // ✗
671
- ```
672
-
673
- **Type:** `null`
674
-
675
-
676
- ---
677
-
678
- #### `nullish(validator)`
679
-
680
- Makes a validator nullish (allows `null` AND `undefined`).
681
-
682
- ```typescript
683
- import { nullish, str } from 'sibyl-ts';
684
-
685
- const hueColorValidator = nullish(str());
686
- hueColorValidator.judge('clear'); // ✓
687
- hueColorValidator.judge(null); // ✓
688
- hueColorValidator.judge(undefined); //
689
- ```
690
-
691
- **Type:** `T | null | undefined`
692
-
693
- ## TypeScript Configuration
694
-
695
- For best results, enable strict mode in your `tsconfig.json`:
696
-
697
- ```json
698
- {
699
- "compilerOptions": {
700
- "strict": true,
701
- "moduleResolution": "node",
702
- "esModuleInterop": true
703
- }
704
- }
705
- ```
706
-
707
- ## Error Handling
708
-
709
- Validators throw detailed errors when validation fails:
710
-
711
- ```typescript
712
- import { JudgmentError } from 'sibyl-ts';
713
-
714
- try {
715
- const validator = num({ min: 0, max: 300 });
716
- validator.judge(350); // Crime coefficient too high!
717
- } catch (error) {
718
- if (error instanceof JudgmentError) {
719
- console.log(error.issues);
720
- // [{ message: "Value 350 is greater than max 300", path: [] }]
721
- }
722
- }
723
- ```
724
-
725
- ## Troubleshooting
726
-
727
- ### Module Resolution Issues
728
-
729
- If you encounter module resolution errors:
730
-
731
- 1. **Ensure Node.js version >= 16.0.0**
732
- 2. **For ESM**: Make sure your `package.json` has `"type": "module"`
733
- 3. **For TypeScript**: Set `"moduleResolution": "node"` in `tsconfig.json`
734
-
735
- ### Type Inference Not Working
736
-
737
- Make sure TypeScript strict mode is enabled and you're using TypeScript 5.0+.
738
-
739
- ## Contributing
740
-
741
- Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
742
-
743
- ## License
744
-
745
- ISC - see [LICENSE](LICENSE) file for details.
746
-
747
- ## Changelog
748
-
749
- See [CHANGELOG.md](CHANGELOG.md) for release history.
1
+ # sibyl-ts
2
+
3
+ [![npm version](https://img.shields.io/npm/v/sibyl-ts.svg)](https://www.npmjs.com/package/sibyl-ts)
4
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](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.