ts-fortress 2.0.4 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +80 -1
  2. package/dist/index.mjs +1 -1
  3. package/dist/record/index.mjs +1 -1
  4. package/dist/record/key-value-record.d.mts +1 -1
  5. package/dist/record/key-value-record.d.mts.map +1 -1
  6. package/dist/record/record.d.mts +31 -5
  7. package/dist/record/record.d.mts.map +1 -1
  8. package/dist/record/record.mjs +42 -4
  9. package/dist/record/record.mjs.map +1 -1
  10. package/package.json +1 -1
  11. package/src/array/array-of-length.test.mts +16 -1
  12. package/src/array/array.test.mts +16 -1
  13. package/src/array/non-empty-array.test.mts +16 -1
  14. package/src/array/tuple.test.mts +18 -1
  15. package/src/compose/intersection.test.mts +43 -1
  16. package/src/compose/merge-records.test.mts +16 -1
  17. package/src/compose/union.test.mts +16 -1
  18. package/src/enum/enum.test.mts +16 -1
  19. package/src/enum/uint-range.test.mts +14 -1
  20. package/src/primitives/null.test.mts +170 -0
  21. package/src/primitives/number.test.mts +284 -0
  22. package/src/primitives/string.test.mts +341 -0
  23. package/src/primitives/undefined.test.mts +179 -0
  24. package/src/record/key-value-record.mts +4 -4
  25. package/src/record/key-value-record.test.mts +28 -2
  26. package/src/record/keyof.test.mts +12 -0
  27. package/src/record/partial.test.mts +18 -0
  28. package/src/record/record-allow-excess-properties.test.mts +180 -0
  29. package/src/record/record-strict-composition-simple.test.mts +134 -0
  30. package/src/record/record-strict-composition.test.mts +403 -0
  31. package/src/record/record.mts +92 -31
  32. package/src/record/record.test.mts +71 -1
  33. package/src/record/strict-record.test.mts +192 -0
package/README.md CHANGED
@@ -541,6 +541,58 @@ const UserProfileType = t.record({
541
541
  }),
542
542
  ),
543
543
  });
544
+
545
+ // Strict validation (disallow excess properties)
546
+ const StrictUserType = t.record(
547
+ {
548
+ id: t.string(''),
549
+ name: t.string(''),
550
+ },
551
+ {
552
+ allowExcessProperties: false, // Reject any properties not defined in schema
553
+ },
554
+ );
555
+
556
+ // Alternatively, use the strictRecord alias for cleaner syntax
557
+ const StrictUserTypeAlias = t.strictRecord({
558
+ id: t.string(''),
559
+ name: t.string(''),
560
+ });
561
+
562
+ // Permissive validation (allow excess properties) - this is the default
563
+ const PermissiveUserType = t.record(
564
+ {
565
+ id: t.string(''),
566
+ name: t.string(''),
567
+ },
568
+ {
569
+ allowExcessProperties: true, // Allow additional properties (default behavior)
570
+ },
571
+ );
572
+
573
+ // Example usage - both StrictUserType and StrictUserTypeAlias behave identically
574
+ const strictData = { id: '123', name: 'John', extra: 'not allowed' };
575
+ console.log(StrictUserType.is(strictData)); // false - 'extra' property causes rejection
576
+ console.log(StrictUserTypeAlias.is(strictData)); // false - same as above
577
+
578
+ const permissiveData = { id: '123', name: 'John', extra: 'allowed' };
579
+ console.log(PermissiveUserType.is(permissiveData)); // true - 'extra' property is allowed
580
+
581
+ // strictRecord provides cleaner syntax for strict validation
582
+ const UserSchema = t.strictRecord({
583
+ name: t.string(''),
584
+ email: t.string(''),
585
+ age: t.number(0),
586
+ });
587
+
588
+ // Validation examples
589
+ UserSchema.is({ name: 'John', email: 'john@example.com', age: 30 }); // ✅ true
590
+ UserSchema.is({
591
+ name: 'John',
592
+ email: 'john@example.com',
593
+ age: 30,
594
+ role: 'admin',
595
+ }); // ❌ false - excess property
544
596
  ```
545
597
 
546
598
  ### Branded Types
@@ -649,6 +701,31 @@ try {
649
701
  } catch (error) {
650
702
  console.error('Validation failed:', error.message);
651
703
  }
704
+
705
+ // Excess property validation example
706
+ const StrictType = t.record(
707
+ {
708
+ name: t.string(''),
709
+ age: t.number(0),
710
+ },
711
+ {
712
+ allowExcessProperties: false,
713
+ },
714
+ );
715
+
716
+ const dataWithExcess = { name: 'John', age: 30, extra: 'not allowed' };
717
+ const strictResult = StrictType.validate(dataWithExcess);
718
+
719
+ if (Result.isErr(strictResult)) {
720
+ console.log(strictResult.value);
721
+ // [{
722
+ // path: ['extra'],
723
+ // actualValue: 'not allowed',
724
+ // expectedType: '{ name: string, age: number }',
725
+ // message: 'Excess property "extra" is not allowed',
726
+ // typeName: '{ name: string, age: number }'
727
+ // }]
728
+ }
652
729
  ```
653
730
 
654
731
  ### ValidationError Structure
@@ -684,7 +761,9 @@ type ValidationError = Readonly<{
684
761
 
685
762
  ### Objects
686
763
 
687
- - `t.record(schema)` - Object validation
764
+ - `t.record(schema, options?)` - Object validation
765
+ - `options.allowExcessProperties?: boolean` - Allow properties not defined in schema (default: true)
766
+ - `t.strictRecord(schema, options?)` - Object validation with strict mode (alias for `record` with `allowExcessProperties: false`)
688
767
  - `t.keyValueRecord(keyType, valueType)` - Corresponding to the `Record<K, V>` type
689
768
  - `t.partial(recordType)` - Make all fields optional
690
769
  - `t.optional(type)` - Optional field wrapper
package/dist/index.mjs CHANGED
@@ -39,7 +39,7 @@ export { omit } from './record/omit.mjs';
39
39
  export { isOptionalProperty, optional } from './record/optional.mjs';
40
40
  export { partial } from './record/partial.mjs';
41
41
  export { pick } from './record/pick.mjs';
42
- export { record } from './record/record.mjs';
42
+ export { record, strictRecord } from './record/record.mjs';
43
43
  export { unknown } from './unknown.mjs';
44
44
  export { createAssertFn } from './utils/create-assert-fn.mjs';
45
45
  export { createCastFn } from './utils/create-cast-fn.mjs';
@@ -4,5 +4,5 @@ export { omit } from './omit.mjs';
4
4
  export { isOptionalProperty, optional } from './optional.mjs';
5
5
  export { partial } from './partial.mjs';
6
6
  export { pick } from './pick.mjs';
7
- export { record } from './record.mjs';
7
+ export { record, strictRecord } from './record.mjs';
8
8
  //# sourceMappingURL=index.mjs.map
@@ -1,5 +1,5 @@
1
1
  import { type Type, type TypeOf } from '../type.mjs';
2
- type RecordResultType<K extends Type<string>, V extends Type<unknown>> = Record<TypeOf<K>, TypeOf<V>>;
2
+ type RecordResultType<K extends Type<string>, V extends Type<unknown>> = ReadonlyRecord<TypeOf<K>, TypeOf<V>>;
3
3
  export declare const keyValueRecord: <K extends Type<string>, V extends Type<unknown>>(keyType: K, valueType: V, options?: Readonly<{
4
4
  typeName?: string;
5
5
  }>) => Type<RecordResultType<K, V>>;
@@ -1 +1 @@
1
- {"version":3,"file":"key-value-record.d.mts","sourceRoot":"","sources":["../../src/record/key-value-record.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AASrD,KAAK,gBAAgB,CAAC,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAC7E,MAAM,CAAC,CAAC,CAAC,EACT,MAAM,CAAC,CAAC,CAAC,CACV,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,EAC5E,SAAS,CAAC,EACV,WAAW,CAAC,EACZ,UAAU,QAAQ,CAAC;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KACxC,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAoF7B,CAAC"}
1
+ {"version":3,"file":"key-value-record.d.mts","sourceRoot":"","sources":["../../src/record/key-value-record.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AASrD,KAAK,gBAAgB,CACnB,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EACtB,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,IACrB,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzC,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,EAC5E,SAAS,CAAC,EACV,WAAW,CAAC,EACZ,UAAU,QAAQ,CAAC;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KACxC,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAoF7B,CAAC"}
@@ -1,21 +1,47 @@
1
1
  import { type Type, type TypeOf } from '../type.mjs';
2
- export declare const record: <const R extends Record<string, Type<unknown>>>(source: R, options?: Partial<Readonly<{
2
+ export declare const record: <const R extends ReadonlyRecord<string, Type<unknown>>>(source: R, options?: Partial<Readonly<{
3
3
  typeName: string;
4
+ /** @default true */
5
+ allowExcessProperties: boolean;
4
6
  }>>) => Type<RecordTypeValue<R>>;
5
- type RecordTypeValue<R extends Record<string, Type<unknown>>> = TsFortressInternal.RecordTypeValueImpl<R>;
7
+ type RecordTypeValue<R extends ReadonlyRecord<string, Type<unknown>>> = TsFortressInternal.RecordTypeValueImpl<R>;
6
8
  declare namespace TsFortressInternal {
7
- export type RecordTypeValueImpl<R extends Record<string, Type<unknown>>> = RecordTypeValueImplSub<R, OptionalTypeKeys<R>>;
8
- type RecordTypeValueImplSub<A extends Record<string, Type<unknown>>, OptionalKeys extends keyof A> = Readonly<{
9
+ export type RecordTypeValueImpl<R extends ReadonlyRecord<string, Type<unknown>>> = RecordTypeValueImplSub<R, OptionalTypeKeys<R>>;
10
+ type RecordTypeValueImplSub<A extends ReadonlyRecord<string, Type<unknown>>, OptionalKeys extends keyof A> = Readonly<{
9
11
  [key in OptionalKeys]?: TypeOf<A[key]>;
10
12
  } & {
11
13
  [key in Exclude<keyof A, OptionalKeys>]: TypeOf<A[key]>;
12
14
  }>;
13
- type OptionalTypeKeys<A extends Record<string, Type<unknown>>> = {
15
+ type OptionalTypeKeys<A extends ReadonlyRecord<string, Type<unknown>>> = {
14
16
  [K in keyof A]: A[K] extends {
15
17
  optional: true;
16
18
  } ? K : never;
17
19
  }[keyof A];
18
20
  export {};
19
21
  }
22
+ /**
23
+ * Creates a strict record type that does not allow excess properties.
24
+ * This is an alias for `record(source, { allowExcessProperties: false })`.
25
+ *
26
+ * @param source - The record schema definition
27
+ * @param options - Optional configuration (allowExcessProperties will be overridden to false)
28
+ * @returns A Type that validates records without allowing excess properties
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * import { strictRecord, string, number } from 'ts-fortress';
33
+ *
34
+ * const User = strictRecord({
35
+ * name: string(),
36
+ * age: number()
37
+ * });
38
+ *
39
+ * User.is({ name: "John", age: 30 }); // true
40
+ * User.is({ name: "John", age: 30, extra: "not allowed" }); // false
41
+ * ```
42
+ */
43
+ export declare const strictRecord: <const R extends ReadonlyRecord<string, Type<unknown>>>(source: R, options?: Partial<Readonly<{
44
+ typeName: string;
45
+ }>>) => Type<RecordTypeValue<R>>;
20
46
  export {};
21
47
  //# sourceMappingURL=record.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"record.d.mts","sourceRoot":"","sources":["../../src/record/record.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AASrD,eAAO,MAAM,MAAM,GAAI,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,EAClE,QAAQ,CAAC,EACT,UAAU,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CACH,KACA,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAgFzB,CAAC;AAEF,KAAK,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IAC1D,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAE5C,kBAAU,kBAAkB,CAAC;IAC3B,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IACrE,sBAAsB,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjD,KAAK,sBAAsB,CACzB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,EACvC,YAAY,SAAS,MAAM,CAAC,IAC1B,QAAQ,CACV;SACG,GAAG,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KACvC,GAAG;SACD,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KACxD,CACF,CAAC;IAEF,KAAK,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI;SAC9D,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;YAAE,QAAQ,EAAE,IAAI,CAAA;SAAE,GAAG,CAAC,GAAG,KAAK;KAC5D,CAAC,MAAM,CAAC,CAAC,CAAC;;CACZ"}
1
+ {"version":3,"file":"record.d.mts","sourceRoot":"","sources":["../../src/record/record.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AASrD,eAAO,MAAM,MAAM,GAAI,KAAK,CAAC,CAAC,SAAS,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,EAC1E,QAAQ,CAAC,EACT,UAAU,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IAEjB,oBAAoB;IACpB,qBAAqB,EAAE,OAAO,CAAC;CAChC,CAAC,CACH,KACA,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAwGzB,CAAC;AAEF,KAAK,eAAe,CAAC,CAAC,SAAS,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IAClE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAE5C,kBAAU,kBAAkB,CAAC;IAC3B,MAAM,MAAM,mBAAmB,CAC7B,CAAC,SAAS,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IAC7C,sBAAsB,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IAEnD,KAAK,sBAAsB,CACzB,CAAC,SAAS,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,EAC/C,YAAY,SAAS,MAAM,CAAC,IAC1B,QAAQ,CACV;SACG,GAAG,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KACvC,GAAG;SACD,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KACxD,CACF,CAAC;IAEF,KAAK,gBAAgB,CAAC,CAAC,SAAS,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI;SACtE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;YAAE,QAAQ,EAAE,IAAI,CAAA;SAAE,GAAG,CAAC,GAAG,KAAK;KAC5D,CAAC,MAAM,CAAC,CAAC,CAAC;;CACZ;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,CAAC,CAAC,SAAS,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,EAErD,QAAQ,CAAC,EACT,UAAU,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CACH,KACA,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CACoC,CAAC"}
@@ -5,10 +5,12 @@ import { createIsFn } from '../utils/create-is-fn.mjs';
5
5
  import { createPrimitiveValidationError, prependPathToValidationErrors } from '../validation-error.mjs';
6
6
 
7
7
  const record = (source, options) => {
8
+ const sourceKeys = new Set(Object.keys(source));
8
9
  const typeNameFilled = options?.typeName ??
9
10
  `{ ${Object.entries(source)
10
11
  .map(([k, v]) => `${k}: ${v.typeName}`)
11
12
  .join(', ')} }`;
13
+ const allowExcessProperties = options?.allowExcessProperties ?? true;
12
14
  const defaultValue =
13
15
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
14
16
  Object.fromEntries(Object.entries(source).map(([key, value]) => tp(key, value.defaultValue)));
@@ -22,7 +24,7 @@ const record = (source, options) => {
22
24
  }),
23
25
  ]);
24
26
  }
25
- const errors = Object.entries(source).flatMap(([k, valueType]) => {
27
+ const defaultErrors = Object.entries(source).flatMap(([k, valueType]) => {
26
28
  if (!Object.hasOwn(a, k)) {
27
29
  if (source[k]?.optional === true)
28
30
  return [];
@@ -43,8 +45,22 @@ const record = (source, options) => {
43
45
  }
44
46
  return [];
45
47
  });
46
- if (errors.length > 0) {
47
- return Result.err(errors);
48
+ // Check for excess properties if allowExcessProperties is false
49
+ if (!allowExcessProperties) {
50
+ const excessKeys = Object.keys(a).filter((key) => !sourceKeys.has(key));
51
+ if (excessKeys.length > 0) {
52
+ const excessErrors = excessKeys.map((key) => ({
53
+ path: [key],
54
+ actualValue: a[key],
55
+ typeName: typeNameFilled,
56
+ expectedType: typeNameFilled,
57
+ message: `Excess property "${key}" is not allowed`,
58
+ }));
59
+ return Result.err([...defaultErrors, ...excessErrors]);
60
+ }
61
+ }
62
+ if (defaultErrors.length > 0) {
63
+ return Result.err(defaultErrors);
48
64
  }
49
65
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
50
66
  return Result.ok(a);
@@ -63,6 +79,28 @@ const record = (source, options) => {
63
79
  cast: createCastFn(validate),
64
80
  };
65
81
  };
82
+ /**
83
+ * Creates a strict record type that does not allow excess properties.
84
+ * This is an alias for `record(source, { allowExcessProperties: false })`.
85
+ *
86
+ * @param source - The record schema definition
87
+ * @param options - Optional configuration (allowExcessProperties will be overridden to false)
88
+ * @returns A Type that validates records without allowing excess properties
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * import { strictRecord, string, number } from 'ts-fortress';
93
+ *
94
+ * const User = strictRecord({
95
+ * name: string(),
96
+ * age: number()
97
+ * });
98
+ *
99
+ * User.is({ name: "John", age: 30 }); // true
100
+ * User.is({ name: "John", age: 30, extra: "not allowed" }); // false
101
+ * ```
102
+ */
103
+ const strictRecord = (source, options) => record(source, { ...options, allowExcessProperties: false });
66
104
 
67
- export { record };
105
+ export { record, strictRecord };
68
106
  //# sourceMappingURL=record.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"record.mjs","sources":["../../src/record/record.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"record.mjs","sources":["../../src/record/record.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;MAUa,MAAM,GAAG,CACpB,MAAS,EACT,OAOC,KAC2B;AAG5B,IAAA,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAE/C,IAAA,MAAM,cAAc,GAClB,OAAO,EAAE,QAAQ;AACjB,QAAA,CAAA,EAAA,EAAK,MAAM,CAAC,OAAO,CAAC,MAAM;AACvB,aAAA,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAA,EAAA,EAAK,CAAC,CAAC,QAAQ,EAAE;AACrC,aAAA,IAAI,CAAC,IAAI,CAAC,CAAA,EAAA,CAAI;AAEnB,IAAA,MAAM,qBAAqB,GAAG,OAAO,EAAE,qBAAqB,IAAI,IAAI;AAEpE,IAAA,MAAM,YAAY;;AAEhB,IAAA,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,CACrE;AAER,IAAA,MAAM,QAAQ,GAAwB,CAAC,CAAC,KAAI;AAC1C,QAAA,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;YAChB,OAAO,MAAM,CAAC,GAAG,CAAC;AAChB,gBAAA,8BAA8B,CAAC;AAC7B,oBAAA,WAAW,EAAE,CAAC;AACd,oBAAA,YAAY,EAAE,QAAQ;AACtB,oBAAA,QAAQ,EAAE,cAAc;iBACzB,CAAC;AACH,aAAA,CAAC;QACJ;AAEA,QAAA,MAAM,aAAa,GAA+B,MAAM,CAAC,OAAO,CAC9D,MAAM,CACP,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,KAAI;YAC3B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;AACxB,gBAAA,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI;AAAE,oBAAA,OAAO,EAAE;gBAE3C,OAAO;AACL,oBAAA;wBACE,IAAI,EAAE,CAAC,CAAC,CAAC;AACT,wBAAA,WAAW,EAAE,CAAC;AACd,wBAAA,QAAQ,EAAE,cAAc;AACxB,wBAAA,YAAY,EAAE,cAAc;wBAC5B,OAAO,EAAE,CAAA,sBAAA,EAAyB,CAAC,CAAA,CAAA,CAAG;AACF,qBAAA;iBACvC;YACH;AAEA,YAAA,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACd,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;AAEjC,YAAA,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;gBACrB,OAAO,6BAA6B,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;YACpD;AAEA,YAAA,OAAO,EAAE;AACX,QAAA,CAAC,CAAC;;QAGF,IAAI,CAAC,qBAAqB,EAAE;YAC1B,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEvE,YAAA,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE;gBACzB,MAAM,YAAY,GAA+B,UAAU,CAAC,GAAG,CAC7D,CAAC,GAAG,MACD;oBACC,IAAI,EAAE,CAAC,GAAG,CAAC;AACX,oBAAA,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC;AACnB,oBAAA,QAAQ,EAAE,cAAc;AACxB,oBAAA,YAAY,EAAE,cAAc;oBAC5B,OAAO,EAAE,CAAA,iBAAA,EAAoB,GAAG,CAAA,gBAAA,CAAkB;AACnD,iBAAA,CAAsC,CAC1C;AAED,gBAAA,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,aAAa,EAAE,GAAG,YAAY,CAAC,CAAC;YACxD;QACF;AAEA,QAAA,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE;AAC5B,YAAA,OAAO,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC;QAClC;;AAGA,QAAA,OAAO,MAAM,CAAC,EAAE,CAAC,CAAM,CAAC;AAC1B,IAAA,CAAC;IAED,MAAM,IAAI,GAAoB,CAAC,CAAC,KAC9B,QAAQ,CAAC,CAAC;AACR;YACG,MAAM,CAAC,WAAW,CACjB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAChC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAC3D;UAEH,YAAY;IAElB,OAAO;AACL,QAAA,QAAQ,EAAE,cAAc;QACxB,YAAY;QACZ,IAAI;QACJ,QAAQ;AACR,QAAA,EAAE,EAAE,UAAU,CAAC,QAAQ,CAAC;AACxB,QAAA,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC;AAClC,QAAA,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC;KAC7B;AACH;AA0BA;;;;;;;;;;;;;;;;;;;;AAoBG;AACI,MAAM,YAAY,GAAG,CAG1B,MAAS,EACT,OAIC,KAED,MAAM,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-fortress",
3
- "version": "2.0.4",
3
+ "version": "2.2.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript"
@@ -1,7 +1,10 @@
1
1
  import { expectType, Result } from 'ts-data-forge';
2
2
  import { number } from '../primitives/index.mjs';
3
3
  import { type TypeOf } from '../type.mjs';
4
- import { validationErrorsToMessages } from '../validation-error.mjs';
4
+ import {
5
+ type ValidationError,
6
+ validationErrorsToMessages,
7
+ } from '../validation-error.mjs';
5
8
  import { arrayOfLength } from './array-of-length.mjs';
6
9
 
7
10
  describe('arrayOfLength', () => {
@@ -74,6 +77,18 @@ describe('arrayOfLength', () => {
74
77
  });
75
78
 
76
79
  describe('validate', () => {
80
+ test('truthy case', () => {
81
+ const ys: unknown = [5, 6, 7, 8];
82
+
83
+ const result = xs.validate(ys);
84
+ expectType<typeof result, Result<Xs, readonly ValidationError[]>>('=');
85
+ expect(Result.isOk(result)).toBe(true);
86
+
87
+ if (Result.isOk(result)) {
88
+ expect(result.value).toStrictEqual([5, 6, 7, 8]);
89
+ }
90
+ });
91
+
77
92
  test('falsy case 1', () => {
78
93
  const ys: unknown = [];
79
94
 
@@ -1,7 +1,10 @@
1
1
  import { expectType, Result } from 'ts-data-forge';
2
2
  import { number } from '../primitives/index.mjs';
3
3
  import { type TypeOf } from '../type.mjs';
4
- import { validationErrorsToMessages } from '../validation-error.mjs';
4
+ import {
5
+ type ValidationError,
6
+ validationErrorsToMessages,
7
+ } from '../validation-error.mjs';
5
8
  import { array } from './array.mjs';
6
9
 
7
10
  describe('array', () => {
@@ -58,6 +61,18 @@ describe('array', () => {
58
61
  });
59
62
 
60
63
  describe('validate', () => {
64
+ test('truthy case', () => {
65
+ const ys: unknown = [1, 2, 3];
66
+
67
+ const result = xs.validate(ys);
68
+ expectType<typeof result, Result<Xs, readonly ValidationError[]>>('=');
69
+ expect(Result.isOk(result)).toBe(true);
70
+
71
+ if (Result.isOk(result)) {
72
+ expect(result.value).toStrictEqual([1, 2, 3]);
73
+ }
74
+ });
75
+
61
76
  test('falsy case', () => {
62
77
  const ys: unknown = ['1', '', 3];
63
78
 
@@ -1,7 +1,10 @@
1
1
  import { expectType, Result } from 'ts-data-forge';
2
2
  import { number } from '../primitives/index.mjs';
3
3
  import { type TypeOf } from '../type.mjs';
4
- import { validationErrorsToMessages } from '../validation-error.mjs';
4
+ import {
5
+ type ValidationError,
6
+ validationErrorsToMessages,
7
+ } from '../validation-error.mjs';
5
8
  import { nonEmptyArray } from './non-empty-array.mjs';
6
9
 
7
10
  describe('nonEmptyArray', () => {
@@ -74,6 +77,18 @@ describe('nonEmptyArray', () => {
74
77
  });
75
78
 
76
79
  describe('validate', () => {
80
+ test('truthy case', () => {
81
+ const ys: unknown = [1, 2, 3];
82
+
83
+ const result = xs.validate(ys);
84
+ expectType<typeof result, Result<Xs, readonly ValidationError[]>>('=');
85
+ expect(Result.isOk(result)).toBe(true);
86
+
87
+ if (Result.isOk(result)) {
88
+ expect(result.value).toStrictEqual([1, 2, 3]);
89
+ }
90
+ });
91
+
77
92
  test('falsy case 1', () => {
78
93
  const ys: unknown = [];
79
94
 
@@ -2,7 +2,10 @@ import { expectType, Result } from 'ts-data-forge';
2
2
  import { number, numberLiteral, stringLiteral } from '../primitives/index.mjs';
3
3
  import { record } from '../record/index.mjs';
4
4
  import { type TypeOf } from '../type.mjs';
5
- import { validationErrorsToMessages } from '../validation-error.mjs';
5
+ import {
6
+ type ValidationError,
7
+ validationErrorsToMessages,
8
+ } from '../validation-error.mjs';
6
9
  import { tuple } from './tuple.mjs';
7
10
 
8
11
  describe('tuple', () => {
@@ -72,6 +75,20 @@ describe('tuple', () => {
72
75
  });
73
76
 
74
77
  describe('validate', () => {
78
+ test('truthy case', () => {
79
+ const x: unknown = [{ x: 1, y: 2 }, 3, '2'];
80
+
81
+ const result = targetType.validate(x);
82
+ expectType<typeof result, Result<TargetType, readonly ValidationError[]>>(
83
+ '=',
84
+ );
85
+ expect(Result.isOk(result)).toBe(true);
86
+
87
+ if (Result.isOk(result)) {
88
+ expect(result.value).toStrictEqual([{ x: 1, y: 2 }, 3, '2']);
89
+ }
90
+ });
91
+
75
92
  test('falsy case - not array', () => {
76
93
  const x: unknown = 'not an array';
77
94
 
@@ -3,7 +3,10 @@ import { uintRange } from '../enum/index.mjs';
3
3
  import { number } from '../primitives/index.mjs';
4
4
  import { record } from '../record/index.mjs';
5
5
  import { type TypeOf } from '../type.mjs';
6
- import { validationErrorsToMessages } from '../validation-error.mjs';
6
+ import {
7
+ type ValidationError,
8
+ validationErrorsToMessages,
9
+ } from '../validation-error.mjs';
7
10
  import { intersection } from './intersection.mjs';
8
11
 
9
12
  describe('intersection', () => {
@@ -74,6 +77,19 @@ describe('intersection', () => {
74
77
  });
75
78
 
76
79
  describe('validate', () => {
80
+ test('truthy case', () => {
81
+ const result = targetType.validate({ x: 0, y: 1, z: 2, w: 3 });
82
+ expectType<
83
+ typeof result,
84
+ Result<TargetType, readonly ValidationError[]>
85
+ >('=');
86
+ expect(Result.isOk(result)).toBe(true);
87
+
88
+ if (Result.isOk(result)) {
89
+ expect(result.value).toStrictEqual({ x: 0, y: 1, z: 2, w: 3 });
90
+ }
91
+ });
92
+
77
93
  test('falsy case', () => {
78
94
  const result = targetType.validate({ x: 0, y: 1 });
79
95
  expect(Result.isErr(result)).toBe(true);
@@ -165,6 +181,19 @@ describe('intersection', () => {
165
181
  });
166
182
 
167
183
  describe('validate', () => {
184
+ test('truthy case', () => {
185
+ const result = targetType.validate(0);
186
+ expectType<
187
+ typeof result,
188
+ Result<TargetType, readonly ValidationError[]>
189
+ >('=');
190
+ expect(Result.isOk(result)).toBe(true);
191
+
192
+ if (Result.isOk(result)) {
193
+ expect(result.value).toBe(0);
194
+ }
195
+ });
196
+
168
197
  test('falsy case', () => {
169
198
  const result = targetType.validate('aaa');
170
199
  expect(Result.isErr(result)).toBe(true);
@@ -278,6 +307,19 @@ describe('intersection', () => {
278
307
  });
279
308
 
280
309
  describe('validate', () => {
310
+ test('truthy case', () => {
311
+ const result = targetType.validate(3);
312
+ expectType<
313
+ typeof result,
314
+ Result<TargetType, readonly ValidationError[]>
315
+ >('=');
316
+ expect(Result.isOk(result)).toBe(true);
317
+
318
+ if (Result.isOk(result)) {
319
+ expect(result.value).toBe(3);
320
+ }
321
+ });
322
+
281
323
  test('falsy case', () => {
282
324
  const result = targetType.validate(7);
283
325
  expect(Result.isErr(result)).toBe(true);
@@ -2,7 +2,10 @@ import { expectType, Result } from 'ts-data-forge';
2
2
  import { number } from '../primitives/index.mjs';
3
3
  import { record } from '../record/index.mjs';
4
4
  import { type TypeOf } from '../type.mjs';
5
- import { validationErrorsToMessages } from '../validation-error.mjs';
5
+ import {
6
+ type ValidationError,
7
+ validationErrorsToMessages,
8
+ } from '../validation-error.mjs';
6
9
  import { mergeRecords } from './merge-records.mjs';
7
10
 
8
11
  describe('mergeRecords', () => {
@@ -67,6 +70,18 @@ describe('mergeRecords', () => {
67
70
  });
68
71
 
69
72
  describe('validate', () => {
73
+ test('truthy case', () => {
74
+ const result = targetType.validate({ x: 0, y: 1, z: 2, w: 3 });
75
+ expectType<typeof result, Result<TargetType, readonly ValidationError[]>>(
76
+ '=',
77
+ );
78
+ expect(Result.isOk(result)).toBe(true);
79
+
80
+ if (Result.isOk(result)) {
81
+ expect(result.value).toStrictEqual({ x: 0, y: 1, z: 2, w: 3 });
82
+ }
83
+ });
84
+
70
85
  test('falsy case', () => {
71
86
  const result = targetType.validate({ x: 0, y: 1 });
72
87
  expect(Result.isErr(result)).toBe(true);
@@ -2,7 +2,10 @@ import { expectType, Result } from 'ts-data-forge';
2
2
  import { number, numberLiteral, stringLiteral } from '../primitives/index.mjs';
3
3
  import { record } from '../record/index.mjs';
4
4
  import { type TypeOf } from '../type.mjs';
5
- import { validationErrorsToMessages } from '../validation-error.mjs';
5
+ import {
6
+ type ValidationError,
7
+ validationErrorsToMessages,
8
+ } from '../validation-error.mjs';
6
9
  import { union } from './union.mjs';
7
10
 
8
11
  describe('union', () => {
@@ -70,6 +73,18 @@ describe('union', () => {
70
73
  });
71
74
 
72
75
  describe('validate', () => {
76
+ test('truthy case', () => {
77
+ const result = targetType.validate(3);
78
+ expectType<typeof result, Result<TargetType, readonly ValidationError[]>>(
79
+ '=',
80
+ );
81
+ expect(Result.isOk(result)).toBe(true);
82
+
83
+ if (Result.isOk(result)) {
84
+ expect(result.value).toBe(3);
85
+ }
86
+ });
87
+
73
88
  test('falsy case', () => {
74
89
  const result = targetType.validate(5);
75
90
  expect(Result.isErr(result)).toBe(true);
@@ -1,6 +1,9 @@
1
1
  import { expectType, Result } from 'ts-data-forge';
2
2
  import { type TypeOf } from '../type.mjs';
3
- import { validationErrorsToMessages } from '../validation-error.mjs';
3
+ import {
4
+ type ValidationError,
5
+ validationErrorsToMessages,
6
+ } from '../validation-error.mjs';
4
7
  import { enumType } from './enum.mjs';
5
8
 
6
9
  describe('enumType', () => {
@@ -41,6 +44,18 @@ describe('enumType', () => {
41
44
  });
42
45
 
43
46
  describe('validate', () => {
47
+ test('truthy case', () => {
48
+ const result = targetType.validate(3);
49
+ expectType<typeof result, Result<TargetType, readonly ValidationError[]>>(
50
+ '=',
51
+ );
52
+ expect(Result.isOk(result)).toBe(true);
53
+
54
+ if (Result.isOk(result)) {
55
+ expect(result.value).toBe(3);
56
+ }
57
+ });
58
+
44
59
  test('falsy case', () => {
45
60
  const result = targetType.validate(5);
46
61
  expect(Result.isErr(result)).toBe(true);
@@ -1,6 +1,9 @@
1
1
  import { expectType, Result } from 'ts-data-forge';
2
2
  import { type TypeOf } from '../type.mjs';
3
- import { validationErrorsToMessages } from '../validation-error.mjs';
3
+ import {
4
+ type ValidationError,
5
+ validationErrorsToMessages,
6
+ } from '../validation-error.mjs';
4
7
  import { uintRange } from './uint-range.mjs';
5
8
 
6
9
  describe('uintRange', () => {
@@ -44,6 +47,16 @@ describe('uintRange', () => {
44
47
  });
45
48
 
46
49
  describe('validate', () => {
50
+ test('truthy case', () => {
51
+ const result = month.validate(5);
52
+ expectType<typeof result, Result<Month, readonly ValidationError[]>>('=');
53
+ expect(Result.isOk(result)).toBe(true);
54
+
55
+ if (Result.isOk(result)) {
56
+ expect(result.value).toBe(5);
57
+ }
58
+ });
59
+
47
60
  test('falsy case', () => {
48
61
  const result = month.validate(13);
49
62
  expect(Result.isErr(result)).toBe(true);