ts-fortress 2.0.0 → 2.0.2

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 (2) hide show
  1. package/README.md +51 -51
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -13,10 +13,9 @@ TypeScript-first schema validation library with static type inference.
13
13
 
14
14
  - 🔒 **Type-safe validation** - Full TypeScript support with static type inference
15
15
  - 🏗️ **Composable schemas** - Build complex validation schemas from simple building blocks
16
- - 🎯 **Zero-runtime overhead** - Type information is inferred at compile time
17
16
  - 🔄 **Result-based error handling** - Structured error reporting with `Result<T, readonly ValidationError[]>`
18
17
  - 🏷️ **Branded types** - Rich collection of branded number types (Int, SafeInt, PositiveInt, etc.)
19
- - ⚡ **Performance focused** - Minimal runtime overhead and optimized validation
18
+ - ⚡ **Performance focused** - Optimized validation with minimal runtime overhead (negligible impact on application performance)
20
19
  - 🧩 **Functional composition** - Union, intersection, and merge operations for schema composition
21
20
  - 🛠️ **Required default values** - All schemas require explicit default values, enabling automatic data filling via `fill()` function
22
21
 
@@ -50,7 +49,13 @@ const User = t.record({
50
49
 
51
50
  // Infer TypeScript type
52
51
  type User = t.TypeOf<typeof User>;
53
- // ↑ { id: string; name: string; age: number; email: string; isActive: boolean }
52
+ // ↑ Readonly<{
53
+ // id: string;
54
+ // name: string;
55
+ // age: number;
56
+ // email: string;
57
+ // isActive: boolean
58
+ // }>
54
59
 
55
60
  // Validate data
56
61
  const userData = {
@@ -66,12 +71,14 @@ if (User.is(userData)) {
66
71
  console.log(`User: ${userData.name}, Age: ${userData.age}`);
67
72
  }
68
73
 
74
+ import { Result } from 'ts-data-forge';
75
+
69
76
  // Get validation result with error details
70
77
  const result = User.validate(userData);
71
- if (result.isOk()) {
78
+ if (Result.isOk(result)) {
72
79
  const user = result.value; // typed as User
73
80
  } else {
74
- console.error('Validation errors:', result.value); // string[] of error messages
81
+ console.error('Validation errors:', result.value); // readonly ValidationError[] showing error information
75
82
  }
76
83
  ```
77
84
 
@@ -120,6 +127,14 @@ console.log(filledData);
120
127
  // fill() is type-safe and always returns a complete object
121
128
  type UserProfile = t.TypeOf<typeof UserProfile>;
122
129
  const completeUser: UserProfile = UserProfile.fill(anyPartialData);
130
+
131
+ // Important: Default value filling only occurs when fill() is called
132
+ // The is() and validate() functions can still detect missing keys
133
+ console.log(UserProfile.is(partialData)); // false - missing required keys
134
+ const result = UserProfile.validate(partialData);
135
+ if (Result.isErr(result)) {
136
+ console.log(result.value); // ValidationError[] showing missing keys
137
+ }
123
138
  ```
124
139
 
125
140
  ### Benefits of Required Default Values
@@ -169,7 +184,7 @@ export const SomeObject = z.object({
169
184
  key2: 'string', // Should be z.string()
170
185
  });
171
186
 
172
- export type SomeObject = z.infer<typeof SomeObject>;
187
+ export type SomeObject = z.infer<typeof SomeObject>; // inferred as { key1: unknown, key2: unknown }
173
188
  ```
174
189
 
175
190
  The above Zod schema will fail at runtime because raw values (`1`, `'string'`) are not valid Zod validators.
@@ -282,7 +297,6 @@ user.tags[0] = 'modified'; // No error
282
297
  - **Immutability by default**: Prevents accidental mutations that can lead to bugs
283
298
  - **Functional programming support**: Encourages functional programming patterns
284
299
  - **Predictable data flow**: Ensures data integrity throughout your application
285
- - **Better optimization**: Enables better compiler optimizations and reasoning about code
286
300
  - **Thread safety**: Immutable data is inherently safe to share between contexts
287
301
 
288
302
  ### Runtime-Type Consistency Issues in io-ts
@@ -399,7 +413,7 @@ const C = t.partial(
399
413
  const UnionBA = t.union([B, A]);
400
414
  const result = UnionBA.validate({ A: 1 });
401
415
 
402
- if (result.isOk()) {
416
+ if (Result.isOk(result)) {
403
417
  console.log(result.value); // { A: 1 } <- ✅ Correct! No unexpected fields
404
418
  console.log(A.is(result.value)); // true <- ✅ Correct
405
419
  console.log(B.is(result.value)); // false <- ✅ Correct! B requires field B
@@ -411,7 +425,7 @@ const C = t.partial(
411
425
  const UnionCA = t.union([C, A]);
412
426
  const result = UnionCA.validate({ A: 1 });
413
427
 
414
- if (result.isOk()) {
428
+ if (Result.isOk(result)) {
415
429
  console.log(result.value); // { A: 1 } <- ✅ Correct and consistent
416
430
  console.log(A.is(result.value)); // true <- ✅ Correct
417
431
  console.log(C.is(result.value)); // true <- ✅ Consistent! ts-fortress partial types allow extra fields
@@ -421,7 +435,7 @@ const C = t.partial(
421
435
 
422
436
  This makes ts-fortress especially valuable in large codebases where schema correctness is critical and runtime failures need to be minimized.
423
437
 
424
- ## Migration from io-ts
438
+ ### Migration from io-ts
425
439
 
426
440
  If you're coming from io-ts, here's how common patterns translate:
427
441
 
@@ -453,7 +467,7 @@ Key differences:
453
467
 
454
468
  - **Default values**: ts-fortress requires explicit default values for better type safety
455
469
  - **Naming**: `record` instead of `type`, more explicit function names
456
- - **Error handling**: Built-in `Result` type instead of `Either`
470
+ - **Error handling**: `Result` type instead of `Either`
457
471
  - **Branded types**: Rich collection of pre-built branded number types
458
472
 
459
473
  ## Core Concepts
@@ -548,7 +562,7 @@ const UInt16 = t.uint16(0);
548
562
 
549
563
  // Usage
550
564
  const userIdResult = UserId.validate('user_123');
551
- if (userIdResult.isOk()) {
565
+ if (Result.isOk(userIdResult)) {
552
566
  const id: UserId = userIdResult.value;
553
567
  }
554
568
  ```
@@ -589,11 +603,15 @@ const ExtendedUserType = t.mergeRecords([
589
603
 
590
604
  ```typescript
591
605
  // String enums
592
- const ColorEnum = t.enum(['red', 'green', 'blue'] as const);
606
+ const ColorEnum = t.enumType(['red', 'green', 'blue']);
593
607
  type Color = t.TypeOf<typeof ColorEnum>; // 'red' | 'green' | 'blue'
594
608
 
595
609
  // Numeric ranges
596
- const DiceRoll = t.uintRange(1, 6, 1); // integers from 1 to 6
610
+ const DiceRoll = t.uintRange({
611
+ start: 1,
612
+ end: 7,
613
+ defaultValue: 1,
614
+ }); // integers from 1 to 6
597
615
  type DiceRoll = t.TypeOf<typeof DiceRoll>; // 1 | 2 | 3 | 4 | 5 | 6
598
616
  ```
599
617
 
@@ -612,14 +630,14 @@ const invalidData = { name: 123, age: 'not a number' };
612
630
  const result = UserType.validate(invalidData);
613
631
  if (Result.isErr(result)) {
614
632
  // result.value is an array of ValidationError objects
615
- result.value.forEach((error) => {
633
+ for (const error of result.value) {
616
634
  console.log('Path:', error.path); // ['name'] or ['age']
617
635
  console.log('Expected:', error.expectedType); // 'string' or 'number'
618
636
  console.log('Actual:', error.actualValue); // 123 or 'not a number'
619
637
  console.log('Type:', error.typeName); // type being validated
620
- });
638
+ }
621
639
 
622
- // Convert to legacy string messages for backward compatibility
640
+ // Convert to string messages
623
641
  const messages = t.validationErrorsToMessages(result.value);
624
642
  console.log(messages);
625
643
  // ['Expected string at name, got number', 'Expected number at age, got string']
@@ -638,34 +656,13 @@ try {
638
656
  Each validation error provides detailed information:
639
657
 
640
658
  ```typescript
641
- interface ValidationError {
642
- path: readonly string[]; // Path to the field that failed
659
+ type ValidationError = Readonly<{
660
+ path: readonly string[];
643
661
  actualValue: unknown; // The actual value that failed validation
644
662
  expectedType: string; // The expected type or constraint
645
- message?: string; // Optional custom error message
663
+ message: string | undefined; // Optional custom error message
646
664
  typeName: string; // Name of the type being validated
647
- }
648
- ```
649
-
650
- ### Error Utilities
651
-
652
- ```typescript
653
- import * as t from 'ts-fortress';
654
-
655
- // Convert ValidationError objects to string messages
656
- const messages = t.validationErrorsToMessages(errors);
657
-
658
- // Convert single ValidationError to message
659
- const message = t.validationErrorToMessage(error);
660
-
661
- // Create custom validation errors
662
- const customError = t.createValidationError({
663
- path: ['user', 'email'],
664
- actualValue: 'invalid-email',
665
- expectedType: 'valid email address',
666
- typeName: 'EmailAddress',
667
- message: 'Please provide a valid email address',
668
- });
665
+ }>;
669
666
  ```
670
667
 
671
668
  ## API Reference
@@ -680,23 +677,25 @@ const customError = t.createValidationError({
680
677
 
681
678
  ### Collections
682
679
 
683
- - `t.array(elementType, options?)` - Array validation
684
- - `t.nonEmptyArray(elementType, options?)` - Non-empty array validation
685
- - `t.tuple([...types])` - Fixed-length tuple validation
686
- - `t.arrayOfLength(elementType, length, options?)` - Fixed-length array validation
680
+ - `t.array(elementType)` - Array validation
681
+ - `t.nonEmptyArray(elementType)` - Non-empty array validation
682
+ - `t.tuple([t1, t2, ..., tN])` - Fixed-length tuple validation
683
+ - `t.arrayOfLength(size, elementType)` - Fixed-length array validation
687
684
 
688
685
  ### Objects
689
686
 
690
- - `t.record(schema, options?)` - Object validation
687
+ - `t.record(schema)` - Object validation
688
+ - `t.keyValueRecord(keyType, valueType)` - Corresponding to the `Record<K, V>` type
691
689
  - `t.partial(recordType)` - Make all fields optional
692
690
  - `t.optional(type)` - Optional field wrapper
693
691
  - `t.pick(recordType, keys)` - Pick specific fields
694
692
  - `t.omit(recordType, keys)` - Omit specific fields
693
+ - `t.keyof(recordType)` - Key of the record type.
695
694
 
696
695
  ### Composition
697
696
 
698
- - `t.union(types, options?)` - Union type validation
699
- - `t.intersection(types)` - Intersection type validation
697
+ - `t.union(types)` - Union type validation
698
+ - `t.intersection(types, defaultType)` - Intersection type validation
700
699
  - `t.mergeRecords(recordTypes)` - Merge multiple record types
701
700
 
702
701
  ### Branded Types
@@ -708,8 +707,9 @@ const customError = t.createValidationError({
708
707
  ### Utilities
709
708
 
710
709
  - `t.TypeOf<T>` - Extract TypeScript type from validator
711
- - `t.enum(values)` - Enum validation
712
- - `t.uintRange(min, max, defaultValue)` - Integer range validation
710
+ - `t.enumType(values)` - Enum validation
711
+ - `t.uintRange({ start, end, defaultValue? })` - Integer range validation
712
+ - `t.unknown` - Unknown Type
713
713
 
714
714
  ## Contributing
715
715
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-fortress",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript"
@@ -16,13 +16,13 @@
16
16
  "exports": {
17
17
  ".": {
18
18
  "import": {
19
- "types": "./dist/type.d.mts",
19
+ "types": "./dist/types.d.mts",
20
20
  "default": "./dist/index.mjs"
21
21
  }
22
22
  }
23
23
  },
24
24
  "module": "./dist/index.mjs",
25
- "types": "./dist/type.d.mts",
25
+ "types": "./dist/types.d.mts",
26
26
  "files": [
27
27
  "src",
28
28
  "dist",