ts-fortress 2.2.1 → 2.3.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 (139) hide show
  1. package/README.md +135 -0
  2. package/dist/array/array-of-length.d.mts.map +1 -1
  3. package/dist/array/array-of-length.mjs +8 -6
  4. package/dist/array/array-of-length.mjs.map +1 -1
  5. package/dist/array/array.d.mts.map +1 -1
  6. package/dist/array/array.mjs +8 -6
  7. package/dist/array/array.mjs.map +1 -1
  8. package/dist/array/non-empty-array.d.mts.map +1 -1
  9. package/dist/array/non-empty-array.mjs +8 -6
  10. package/dist/array/non-empty-array.mjs.map +1 -1
  11. package/dist/array/tuple.d.mts.map +1 -1
  12. package/dist/array/tuple.mjs +8 -6
  13. package/dist/array/tuple.mjs.map +1 -1
  14. package/dist/compose/index.d.mts +1 -0
  15. package/dist/compose/index.d.mts.map +1 -1
  16. package/dist/compose/index.mjs +1 -0
  17. package/dist/compose/index.mjs.map +1 -1
  18. package/dist/compose/intersection.d.mts.map +1 -1
  19. package/dist/compose/intersection.mjs +10 -10
  20. package/dist/compose/intersection.mjs.map +1 -1
  21. package/dist/compose/merge-records.d.mts.map +1 -1
  22. package/dist/compose/merge-records.mjs +10 -10
  23. package/dist/compose/merge-records.mjs.map +1 -1
  24. package/dist/compose/recursion.d.mts +5 -0
  25. package/dist/compose/recursion.d.mts.map +1 -0
  26. package/dist/compose/recursion.mjs +38 -0
  27. package/dist/compose/recursion.mjs.map +1 -0
  28. package/dist/compose/union.d.mts.map +1 -1
  29. package/dist/compose/union.mjs.map +1 -1
  30. package/dist/enum/enum.d.mts.map +1 -1
  31. package/dist/enum/enum.mjs.map +1 -1
  32. package/dist/enum/uint-range.d.mts.map +1 -1
  33. package/dist/enum/uint-range.mjs.map +1 -1
  34. package/dist/index.d.mts +0 -1
  35. package/dist/index.d.mts.map +1 -1
  36. package/dist/index.mjs +2 -1
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/record/key-value-record.d.mts.map +1 -1
  39. package/dist/record/key-value-record.mjs +21 -24
  40. package/dist/record/key-value-record.mjs.map +1 -1
  41. package/dist/record/omit.d.mts.map +1 -1
  42. package/dist/record/omit.mjs +3 -2
  43. package/dist/record/omit.mjs.map +1 -1
  44. package/dist/record/partial.d.mts.map +1 -1
  45. package/dist/record/partial.mjs +2 -2
  46. package/dist/record/partial.mjs.map +1 -1
  47. package/dist/record/pick.d.mts.map +1 -1
  48. package/dist/record/pick.mjs +2 -2
  49. package/dist/record/pick.mjs.map +1 -1
  50. package/dist/record/record.d.mts.map +1 -1
  51. package/dist/record/record.mjs +23 -21
  52. package/dist/record/record.mjs.map +1 -1
  53. package/dist/type.d.mts +1 -1
  54. package/dist/type.d.mts.map +1 -1
  55. package/dist/utils/create-assert-fn.mjs +1 -1
  56. package/dist/utils/create-cast-fn.mjs +1 -1
  57. package/dist/utils/create-primitive-type.mjs +1 -1
  58. package/dist/utils/index.d.mts +1 -0
  59. package/dist/utils/index.d.mts.map +1 -1
  60. package/dist/utils/index.mjs +1 -0
  61. package/dist/utils/index.mjs.map +1 -1
  62. package/dist/utils/validation-error.d.mts.map +1 -0
  63. package/dist/utils/validation-error.mjs.map +1 -0
  64. package/package.json +4 -2
  65. package/src/array/array-of-length.mts +12 -7
  66. package/src/array/array-of-length.test.mts +10 -1
  67. package/src/array/array.mts +12 -7
  68. package/src/array/array.test.mts +10 -1
  69. package/src/array/non-empty-array.mts +11 -7
  70. package/src/array/non-empty-array.test.mts +10 -1
  71. package/src/array/tuple.mts +11 -9
  72. package/src/array/tuple.test.mts +14 -1
  73. package/src/branded/brand.mts +2 -2
  74. package/src/branded/number/finite-number.test.mts +10 -1
  75. package/src/branded/number/int.test.mts +10 -1
  76. package/src/branded/number/int16.test.mts +10 -1
  77. package/src/branded/number/int32.test.mts +10 -1
  78. package/src/branded/number/non-negative-finite-number.test.mts +10 -1
  79. package/src/branded/number/non-zero-finite-number.test.mts +10 -1
  80. package/src/branded/number/non-zero-int.test.mts +10 -1
  81. package/src/branded/number/non-zero-safe-int.test.mts +10 -1
  82. package/src/branded/number/positive-finite-number.test.mts +10 -1
  83. package/src/branded/number/positive-int.test.mts +10 -1
  84. package/src/branded/number/positive-safe-int.test.mts +10 -1
  85. package/src/branded/number/safe-int.test.mts +10 -1
  86. package/src/branded/number/safe-uint.test.mts +10 -1
  87. package/src/branded/number/uint.test.mts +10 -1
  88. package/src/branded/number/uint16.test.mts +10 -1
  89. package/src/branded/number/uint32.test.mts +10 -1
  90. package/src/branded/simple-branded-number.test.mts +19 -1
  91. package/src/branded/simple-branded-string.test.mts +19 -1
  92. package/src/compose/index.mts +1 -0
  93. package/src/compose/intersection.mts +23 -19
  94. package/src/compose/intersection.test.mts +28 -1
  95. package/src/compose/merge-records.mts +22 -19
  96. package/src/compose/merge-records.test.mts +10 -1
  97. package/src/compose/recursion.mts +49 -0
  98. package/src/compose/recursion.test.mts +234 -0
  99. package/src/compose/union.mts +6 -2
  100. package/src/compose/union.test.mts +10 -1
  101. package/src/enum/enum.mts +6 -2
  102. package/src/enum/enum.test.mts +10 -1
  103. package/src/enum/uint-range.mts +6 -2
  104. package/src/enum/uint-range.test.mts +10 -1
  105. package/src/index.mts +0 -1
  106. package/src/primitives/bigint.test.mts +19 -1
  107. package/src/primitives/boolean.test.mts +28 -1
  108. package/src/primitives/null.test.mts +9 -0
  109. package/src/primitives/number.test.mts +18 -0
  110. package/src/primitives/string.test.mts +18 -0
  111. package/src/primitives/symbol.test.mts +10 -1
  112. package/src/primitives/undefined.test.mts +9 -0
  113. package/src/record/key-value-record.mts +34 -36
  114. package/src/record/key-value-record.test.mts +14 -1
  115. package/src/record/keyof.test.mts +10 -1
  116. package/src/record/omit.mts +8 -5
  117. package/src/record/omit.test.mts +46 -1
  118. package/src/record/partial.mts +7 -5
  119. package/src/record/partial.test.mts +71 -1
  120. package/src/record/pick.mts +7 -5
  121. package/src/record/pick.test.mts +46 -1
  122. package/src/record/record-allow-excess-properties.test.mts +28 -1
  123. package/src/record/record-strict-composition-simple.test.mts +38 -0
  124. package/src/record/record-strict-composition.test.mts +71 -0
  125. package/src/record/record.mts +30 -29
  126. package/src/record/record.test.mts +26 -1
  127. package/src/record/strict-record.test.mts +10 -1
  128. package/src/type.mts +1 -1
  129. package/src/utils/create-assert-fn.mts +1 -1
  130. package/src/utils/create-cast-fn.mts +1 -1
  131. package/src/utils/create-primitive-type.mts +1 -1
  132. package/src/utils/index.mts +1 -0
  133. package/dist/validation-error.d.mts.map +0 -1
  134. package/dist/validation-error.mjs.map +0 -1
  135. package/src/combination.test.mts +0 -544
  136. /package/dist/{validation-error.d.mts → utils/validation-error.d.mts} +0 -0
  137. /package/dist/{validation-error.mjs → utils/validation-error.mjs} +0 -0
  138. /package/src/{validation-error.mts → utils/validation-error.mts} +0 -0
  139. /package/src/{validation-error.test.mts → utils/validation-error.test.mts} +0 -0
@@ -175,5 +175,14 @@ describe('undefinedType', () => {
175
175
  ]);
176
176
  }
177
177
  });
178
+
179
+ test('validate returns input as-is for OK cases', () => {
180
+ const input = undefined;
181
+ const result = undefinedType.validate(input);
182
+ expect(Result.isOk(result)).toBe(true);
183
+ if (Result.isOk(result)) {
184
+ expect(result.value).toBe(input); // ✅ same reference
185
+ }
186
+ });
178
187
  });
179
188
  });
@@ -1,12 +1,14 @@
1
- import { isRecord, Result } from 'ts-data-forge';
1
+ import { Arr, isRecord, Result } from 'ts-data-forge';
2
2
  import { type Type, type TypeOf } from '../type.mjs';
3
- import { createAssertFn, createCastFn, createIsFn } from '../utils/index.mjs';
4
3
  import {
4
+ createAssertFn,
5
+ createCastFn,
6
+ createIsFn,
5
7
  createPrimitiveValidationError,
6
8
  prependPathToValidationErrors,
7
9
  type ValidationError,
8
10
  type ValidationErrorWithMessage,
9
- } from '../validation-error.mjs';
11
+ } from '../utils/index.mjs';
10
12
 
11
13
  type RecordResultType<
12
14
  K extends Type<string>,
@@ -36,43 +38,39 @@ export const keyValueRecord = <K extends Type<string>, V extends Type<unknown>>(
36
38
  ]);
37
39
  }
38
40
 
39
- const errors: readonly ValidationError[] = Object.entries(a).flatMap(
40
- ([k, v]) => {
41
- const keyErrors = (() => {
41
+ const errors: readonly ValidationError[] = Arr.generate(function* () {
42
+ for (const [k, v] of Object.entries(a)) {
43
+ {
42
44
  const res = keyType.validate(k);
43
- return Result.isErr(res)
44
- ? [
45
- {
46
- path: [],
47
- actualValue: k,
48
- expectedType: typeName,
49
- message: `The key of the record is expected to be <${keyType.typeName}>`,
50
- typeName,
51
- } satisfies ValidationErrorWithMessage,
52
- ...res.value,
53
- ]
54
- : [];
55
- })();
56
45
 
57
- const valueErrors = (() => {
46
+ if (Result.isErr(res)) {
47
+ yield {
48
+ path: [],
49
+ actualValue: k,
50
+ expectedType: typeName,
51
+ message: `The key of the record is expected to be <${keyType.typeName}>`,
52
+ typeName,
53
+ } satisfies ValidationErrorWithMessage;
54
+
55
+ yield* res.value;
56
+ }
57
+ }
58
+ {
58
59
  const res = valueType.validate(v);
59
- return Result.isErr(res)
60
- ? [
61
- {
62
- path: [],
63
- actualValue: v,
64
- expectedType: typeName,
65
- message: `The value of the record is expected to be <${valueType.typeName}>`,
66
- typeName,
67
- } satisfies ValidationErrorWithMessage,
68
- ...prependPathToValidationErrors(res.value, k),
69
- ]
70
- : [];
71
- })();
60
+ if (Result.isErr(res)) {
61
+ yield {
62
+ path: [],
63
+ actualValue: v,
64
+ expectedType: typeName,
65
+ message: `The value of the record is expected to be <${valueType.typeName}>`,
66
+ typeName,
67
+ } satisfies ValidationErrorWithMessage;
72
68
 
73
- return [...keyErrors, ...valueErrors];
74
- },
75
- );
69
+ yield* prependPathToValidationErrors(res.value, k);
70
+ }
71
+ }
72
+ }
73
+ });
76
74
 
77
75
  if (errors.length > 0) {
78
76
  return Result.err(errors);
@@ -4,7 +4,7 @@ import { type TypeOf } from '../type.mjs';
4
4
  import {
5
5
  type ValidationError,
6
6
  validationErrorsToMessages,
7
- } from '../validation-error.mjs';
7
+ } from '../utils/index.mjs';
8
8
  import { keyValueRecord } from './key-value-record.mjs';
9
9
 
10
10
  describe('keyValueRecord', () => {
@@ -74,6 +74,19 @@ describe('keyValueRecord', () => {
74
74
  }
75
75
  });
76
76
 
77
+ test('validate returns input as-is for OK cases', () => {
78
+ const input: UnknownRecord = {
79
+ year: 2000,
80
+ month: 12,
81
+ date: 25,
82
+ };
83
+ const result = strNumRecord.validate(input);
84
+ expect(Result.isOk(result)).toBe(true);
85
+ if (Result.isOk(result)) {
86
+ expect(result.value).toBe(input); // ✅ same reference
87
+ }
88
+ });
89
+
77
90
  test('falsy case', () => {
78
91
  const x: UnknownRecord = {
79
92
  year: 2000,
@@ -1,7 +1,7 @@
1
1
  import { expectType, Result } from 'ts-data-forge';
2
2
  import { number } from '../primitives/index.mjs';
3
3
  import { type Type, type TypeOf } from '../type.mjs';
4
- import { validationErrorsToMessages } from '../validation-error.mjs';
4
+ import { validationErrorsToMessages } from '../utils/index.mjs';
5
5
  import { keyof } from './keyof.mjs';
6
6
  import { record } from './record.mjs';
7
7
 
@@ -61,6 +61,15 @@ describe('keyof', () => {
61
61
  }
62
62
  });
63
63
 
64
+ test('validate returns input as-is for OK cases', () => {
65
+ const input = 'year';
66
+ const result = ymdKey.validate(input);
67
+ expect(Result.isOk(result)).toBe(true);
68
+ if (Result.isOk(result)) {
69
+ expect(result.value).toBe(input); // ✅ same reference
70
+ }
71
+ });
72
+
64
73
  test('falsy case', () => {
65
74
  const x: unknown = 'minutes';
66
75
 
@@ -1,7 +1,11 @@
1
1
  import { expectType, isRecord, Obj, Result } from 'ts-data-forge';
2
2
  import { type Type, type TypeOf } from '../type.mjs';
3
- import { createAssertFn, createCastFn, createIsFn } from '../utils/index.mjs';
4
- import { createPrimitiveValidationError } from '../validation-error.mjs';
3
+ import {
4
+ createAssertFn,
5
+ createCastFn,
6
+ createIsFn,
7
+ createPrimitiveValidationError,
8
+ } from '../utils/index.mjs';
5
9
 
6
10
  /** Creates a record type with keys omitted. */
7
11
  export const omit = <
@@ -43,9 +47,8 @@ export const omit = <
43
47
 
44
48
  const baseValidationResult = recordType.validate(objectFilled);
45
49
 
46
- return Result.map(baseValidationResult, Obj.omit(keysToOmit)) as ReturnType<
47
- Type<V>['validate']
48
- >;
50
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
51
+ return Result.map(baseValidationResult, () => a as V);
49
52
  };
50
53
 
51
54
  const fill: Type<V>['fill'] = (a) => Obj.omit(recordType.fill(a), keysToOmit);
@@ -1,7 +1,7 @@
1
1
  import { expectType, Result } from 'ts-data-forge';
2
2
  import { number } from '../primitives/index.mjs';
3
3
  import { type Type, type TypeOf } from '../type.mjs';
4
- import { validationErrorsToMessages } from '../validation-error.mjs';
4
+ import { validationErrorsToMessages } from '../utils/index.mjs';
5
5
  import { omit } from './omit.mjs';
6
6
  import { record } from './record.mjs';
7
7
 
@@ -73,6 +73,51 @@ describe('omit', () => {
73
73
  }
74
74
  });
75
75
 
76
+ test('validate returns input as-is for OK cases', () => {
77
+ const input: UnknownRecord = {
78
+ year: 2000,
79
+ month: 12,
80
+ };
81
+ const result = ym.validate(input);
82
+ expect(Result.isOk(result)).toBe(true);
83
+ if (Result.isOk(result)) {
84
+ expect(result.value).toBe(input); // ✅ same reference
85
+ }
86
+ });
87
+
88
+ test('truthy case with additional keys', () => {
89
+ const x: UnknownRecord = {
90
+ year: 2000,
91
+ month: 12,
92
+ aaa: 999,
93
+ };
94
+
95
+ const result = ym.validate(x);
96
+ expect(Result.isOk(result)).toBe(true);
97
+
98
+ if (Result.isOk(result)) {
99
+ expectType<typeof result.value, Ym>('=');
100
+ expect(result.value).toStrictEqual({
101
+ year: 2000,
102
+ month: 12,
103
+ aaa: 999,
104
+ });
105
+ }
106
+ });
107
+
108
+ test('validate returns input as-is for OK cases with additional keys', () => {
109
+ const input: UnknownRecord = {
110
+ year: 2000,
111
+ month: 12,
112
+ aaa: 999,
113
+ };
114
+ const result = ym.validate(input);
115
+ expect(Result.isOk(result)).toBe(true);
116
+ if (Result.isOk(result)) {
117
+ expect(result.value).toBe(input); // ✅ same reference
118
+ }
119
+ });
120
+
76
121
  test('falsy case', () => {
77
122
  const x: UnknownRecord = {
78
123
  year: 2000,
@@ -1,7 +1,11 @@
1
1
  import { expectType, ISet, isRecord, Obj, Result } from 'ts-data-forge';
2
2
  import { type Type, type TypeOf } from '../type.mjs';
3
- import { createAssertFn, createCastFn, createIsFn } from '../utils/index.mjs';
4
- import { createPrimitiveValidationError } from '../validation-error.mjs';
3
+ import {
4
+ createAssertFn,
5
+ createCastFn,
6
+ createIsFn,
7
+ createPrimitiveValidationError,
8
+ } from '../utils/index.mjs';
5
9
 
6
10
  /**
7
11
  * Creates a Partial type. If keysToBeOptional is set, only those keys are
@@ -58,9 +62,7 @@ export const partial = <
58
62
  const validationResult = recordType.validate(objectFilled);
59
63
 
60
64
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
61
- return Result.map(validationResult, Obj.pick(Object.keys(a))) as ReturnType<
62
- Type<V>['validate']
63
- >;
65
+ return Result.map(validationResult, () => a as V);
64
66
  };
65
67
 
66
68
  const fill: Type<V>['fill'] = (a) => {
@@ -4,7 +4,7 @@ import { type Type, type TypeOf } from '../type.mjs';
4
4
  import {
5
5
  type ValidationError,
6
6
  validationErrorsToMessages,
7
- } from '../validation-error.mjs';
7
+ } from '../utils/index.mjs';
8
8
  import { optional } from './optional.mjs';
9
9
  import { partial } from './partial.mjs';
10
10
  import { record } from './record.mjs';
@@ -88,6 +88,19 @@ describe('partial', () => {
88
88
  }
89
89
  });
90
90
 
91
+ test('validate returns input as-is for OK cases', () => {
92
+ const input: UnknownRecord = {
93
+ year: 2000,
94
+ month: 12,
95
+ date: 25,
96
+ };
97
+ const result = ymd.validate(input);
98
+ expect(Result.isOk(result)).toBe(true);
99
+ if (Result.isOk(result)) {
100
+ expect(result.value).toBe(input); // ✅ same reference
101
+ }
102
+ });
103
+
91
104
  test('truthy case optional keys', () => {
92
105
  const x: UnknownRecord = {};
93
106
 
@@ -100,6 +113,51 @@ describe('partial', () => {
100
113
  }
101
114
  });
102
115
 
116
+ test('validate returns input as-is for empty object', () => {
117
+ const input: UnknownRecord = {};
118
+ const result = ymd.validate(input);
119
+ expect(Result.isOk(result)).toBe(true);
120
+ if (Result.isOk(result)) {
121
+ expect(result.value).toBe(input); // ✅ same reference
122
+ }
123
+ });
124
+
125
+ test('truthy case with additional keys', () => {
126
+ const x: UnknownRecord = {
127
+ year: 2000,
128
+ month: 12,
129
+ date: 25,
130
+ aaa: 999,
131
+ };
132
+
133
+ const result = ymd.validate(x);
134
+ expectType<typeof result, Result<Ymd, readonly ValidationError[]>>('=');
135
+ expect(Result.isOk(result)).toBe(true);
136
+
137
+ if (Result.isOk(result)) {
138
+ expect(result.value).toStrictEqual({
139
+ year: 2000,
140
+ month: 12,
141
+ date: 25,
142
+ aaa: 999,
143
+ });
144
+ }
145
+ });
146
+
147
+ test('validate returns input as-is for OK cases with additional keys', () => {
148
+ const input: UnknownRecord = {
149
+ year: 2000,
150
+ month: 12,
151
+ date: 25,
152
+ aaa: 999,
153
+ };
154
+ const result = ymd.validate(input);
155
+ expect(Result.isOk(result)).toBe(true);
156
+ if (Result.isOk(result)) {
157
+ expect(result.value).toBe(input); // ✅ same reference
158
+ }
159
+ });
160
+
103
161
  test('falsy case 1', () => {
104
162
  const x: UnknownRecord = {
105
163
  year: 2000,
@@ -287,6 +345,18 @@ describe('partial', () => {
287
345
  }
288
346
  });
289
347
 
348
+ test('partiallyPartialType validate returns input as-is for OK cases', () => {
349
+ const input: UnknownRecord = {
350
+ year: 2000,
351
+ month: 12,
352
+ };
353
+ const result = ymd.validate(input);
354
+ expect(Result.isOk(result)).toBe(true);
355
+ if (Result.isOk(result)) {
356
+ expect(result.value).toBe(input); // ✅ same reference
357
+ }
358
+ });
359
+
290
360
  test('falsy case', () => {
291
361
  const x: UnknownRecord = {
292
362
  year: 2000,
@@ -1,7 +1,11 @@
1
1
  import { expectType, isRecord, Obj, Result } from 'ts-data-forge';
2
2
  import { type Type, type TypeOf } from '../type.mjs';
3
- import { createAssertFn, createCastFn, createIsFn } from '../utils/index.mjs';
4
- import { createPrimitiveValidationError } from '../validation-error.mjs';
3
+ import {
4
+ createAssertFn,
5
+ createCastFn,
6
+ createIsFn,
7
+ createPrimitiveValidationError,
8
+ } from '../utils/index.mjs';
5
9
 
6
10
  /** Creates a record type with keys picked. */
7
11
  export const pick = <
@@ -44,9 +48,7 @@ export const pick = <
44
48
  const baseValidationResult = recordType.validate(objectFilled);
45
49
 
46
50
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
47
- return Result.map(baseValidationResult, Obj.pick(keysToPick)) as ReturnType<
48
- Type<V>['validate']
49
- >;
51
+ return Result.map(baseValidationResult, () => a as V);
50
52
  };
51
53
 
52
54
  const fill: Type<V>['fill'] = (a) =>
@@ -1,7 +1,7 @@
1
1
  import { expectType, Result } from 'ts-data-forge';
2
2
  import { number } from '../primitives/index.mjs';
3
3
  import { type Type, type TypeOf } from '../type.mjs';
4
- import { validationErrorsToMessages } from '../validation-error.mjs';
4
+ import { validationErrorsToMessages } from '../utils/index.mjs';
5
5
  import { pick } from './pick.mjs';
6
6
  import { record } from './record.mjs';
7
7
 
@@ -73,6 +73,51 @@ describe('pick', () => {
73
73
  }
74
74
  });
75
75
 
76
+ test('validate returns input as-is for OK cases', () => {
77
+ const input: UnknownRecord = {
78
+ year: 2000,
79
+ month: 12,
80
+ };
81
+ const result = ym.validate(input);
82
+ expect(Result.isOk(result)).toBe(true);
83
+ if (Result.isOk(result)) {
84
+ expect(result.value).toBe(input); // ✅ same reference
85
+ }
86
+ });
87
+
88
+ test('truthy case with additional keys', () => {
89
+ const x: UnknownRecord = {
90
+ year: 2000,
91
+ month: 12,
92
+ aaa: 999,
93
+ };
94
+
95
+ const result = ym.validate(x);
96
+ expect(Result.isOk(result)).toBe(true);
97
+
98
+ if (Result.isOk(result)) {
99
+ expectType<typeof result.value, Ym>('=');
100
+ expect(result.value).toStrictEqual({
101
+ year: 2000,
102
+ month: 12,
103
+ aaa: 999,
104
+ });
105
+ }
106
+ });
107
+
108
+ test('validate returns input as-is for OK cases with additional keys', () => {
109
+ const input: UnknownRecord = {
110
+ year: 2000,
111
+ month: 12,
112
+ aaa: 999,
113
+ };
114
+ const result = ym.validate(input);
115
+ expect(Result.isOk(result)).toBe(true);
116
+ if (Result.isOk(result)) {
117
+ expect(result.value).toBe(input); // ✅ same reference
118
+ }
119
+ });
120
+
76
121
  test('falsy case', () => {
77
122
  const x: UnknownRecord = {
78
123
  year: 2000,
@@ -1,6 +1,6 @@
1
1
  import { Result } from 'ts-data-forge';
2
2
  import { number } from '../primitives/index.mjs';
3
- import { validationErrorsToMessages } from '../validation-error.mjs';
3
+ import { validationErrorsToMessages } from '../utils/index.mjs';
4
4
  import { record } from './record.mjs';
5
5
 
6
6
  describe('record allowExcessProperties option', () => {
@@ -73,6 +73,15 @@ describe('record allowExcessProperties option', () => {
73
73
  }
74
74
  });
75
75
 
76
+ test('strictRecord validate returns input as-is for OK cases', () => {
77
+ const input = { name: 42, age: 25 };
78
+ const result = strictRecord.validate(input);
79
+ expect(Result.isOk(result)).toBe(true);
80
+ if (Result.isOk(result)) {
81
+ expect(result.value).toBe(input); // ✅ same reference
82
+ }
83
+ });
84
+
76
85
  test('allowExcessProperties: true - accepts excess properties', () => {
77
86
  const dataWithExcess = {
78
87
  name: 42,
@@ -94,6 +103,15 @@ describe('record allowExcessProperties option', () => {
94
103
  }
95
104
  });
96
105
 
106
+ test('permissiveRecord validate returns input as-is for OK cases', () => {
107
+ const input = { name: 42, age: 25, extra: 'allowed' };
108
+ const result = permissiveRecord.validate(input);
109
+ expect(Result.isOk(result)).toBe(true);
110
+ if (Result.isOk(result)) {
111
+ expect(result.value).toBe(input); // ✅ same reference
112
+ }
113
+ });
114
+
97
115
  test('default behavior - allows excess properties (allowExcessProperties defaults to true)', () => {
98
116
  const dataWithExcess = {
99
117
  name: 42,
@@ -115,6 +133,15 @@ describe('record allowExcessProperties option', () => {
115
133
  }
116
134
  });
117
135
 
136
+ test('defaultRecord validate returns input as-is for OK cases', () => {
137
+ const input = { name: 42, age: 25, extra: 'allowed by default' };
138
+ const result = defaultRecord.validate(input);
139
+ expect(Result.isOk(result)).toBe(true);
140
+ if (Result.isOk(result)) {
141
+ expect(result.value).toBe(input); // ✅ same reference
142
+ }
143
+ });
144
+
118
145
  test('allowExcessProperties: false - multiple excess properties', () => {
119
146
  const dataWithMultipleExcess = {
120
147
  name: 42,
@@ -48,6 +48,16 @@ describe('record strict composition - simple tests', () => {
48
48
  expect(result.value).toStrictEqual({ id: '123', name: 'John' });
49
49
  }
50
50
  });
51
+
52
+ test('pickedType validate returns input as-is for OK cases', () => {
53
+ const pickedType = pick(strictRecord, ['id', 'name']);
54
+ const input = { id: '123', name: 'John' };
55
+ const result = pickedType.validate(input);
56
+ expect(Result.isOk(result)).toBe(true);
57
+ if (Result.isOk(result)) {
58
+ expect(result.value).toBe(input); // ✅ same reference
59
+ }
60
+ });
51
61
  });
52
62
 
53
63
  describe('omit behavior with strict record', () => {
@@ -108,6 +118,15 @@ describe('record strict composition - simple tests', () => {
108
118
  expect(Result.isErr(result)).toBe(true);
109
119
  });
110
120
 
121
+ test('strictRecord validate returns input as-is for OK cases', () => {
122
+ const input = { id: '123', name: 'John', age: 25 };
123
+ const result = strictRecord.validate(input);
124
+ expect(Result.isOk(result)).toBe(true);
125
+ if (Result.isOk(result)) {
126
+ expect(result.value).toBe(input); // ✅ same reference
127
+ }
128
+ });
129
+
111
130
  test('permissive record accepts excess properties', () => {
112
131
  const data = { id: '123', name: 'John', age: 25, extra: 'allowed' };
113
132
 
@@ -115,6 +134,15 @@ describe('record strict composition - simple tests', () => {
115
134
  expect(Result.isOk(result)).toBe(true);
116
135
  });
117
136
 
137
+ test('permissiveRecord validate returns input as-is for OK cases', () => {
138
+ const input = { id: '123', name: 'John', age: 25, extra: 'allowed' };
139
+ const result = permissiveRecord.validate(input);
140
+ expect(Result.isOk(result)).toBe(true);
141
+ if (Result.isOk(result)) {
142
+ expect(result.value).toBe(input); // ✅ same reference
143
+ }
144
+ });
145
+
118
146
  test('pick from strict record rejects excess properties', () => {
119
147
  const strictPicked = pick(strictRecord, ['id', 'name']);
120
148
  const data = { id: '123', name: 'John', extra: 'not allowed' };
@@ -130,5 +158,15 @@ describe('record strict composition - simple tests', () => {
130
158
  const result = permissivePicked.validate(data);
131
159
  expect(Result.isOk(result)).toBe(true);
132
160
  });
161
+
162
+ test('permissivePicked validate returns input as-is for OK cases', () => {
163
+ const permissivePicked = pick(permissiveRecord, ['id', 'name']);
164
+ const input = { id: '123', name: 'John', extra: 'allowed' };
165
+ const result = permissivePicked.validate(input);
166
+ expect(Result.isOk(result)).toBe(true);
167
+ if (Result.isOk(result)) {
168
+ expect(result.value).toBe(input); // ✅ same reference
169
+ }
170
+ });
133
171
  });
134
172
  });