ts-fortress 2.2.0 → 2.2.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 (128) 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/intersection.d.mts.map +1 -1
  15. package/dist/compose/intersection.mjs +10 -10
  16. package/dist/compose/intersection.mjs.map +1 -1
  17. package/dist/compose/merge-records.d.mts.map +1 -1
  18. package/dist/compose/merge-records.mjs +10 -10
  19. package/dist/compose/merge-records.mjs.map +1 -1
  20. package/dist/compose/union.d.mts.map +1 -1
  21. package/dist/compose/union.mjs.map +1 -1
  22. package/dist/enum/enum.d.mts.map +1 -1
  23. package/dist/enum/enum.mjs.map +1 -1
  24. package/dist/enum/uint-range.d.mts.map +1 -1
  25. package/dist/enum/uint-range.mjs.map +1 -1
  26. package/dist/index.d.mts +0 -1
  27. package/dist/index.d.mts.map +1 -1
  28. package/dist/index.mjs +1 -1
  29. package/dist/record/key-value-record.d.mts.map +1 -1
  30. package/dist/record/key-value-record.mjs +21 -24
  31. package/dist/record/key-value-record.mjs.map +1 -1
  32. package/dist/record/omit.d.mts.map +1 -1
  33. package/dist/record/omit.mjs +3 -2
  34. package/dist/record/omit.mjs.map +1 -1
  35. package/dist/record/partial.d.mts.map +1 -1
  36. package/dist/record/partial.mjs +2 -2
  37. package/dist/record/partial.mjs.map +1 -1
  38. package/dist/record/pick.d.mts.map +1 -1
  39. package/dist/record/pick.mjs +2 -2
  40. package/dist/record/pick.mjs.map +1 -1
  41. package/dist/record/record.d.mts +6 -2
  42. package/dist/record/record.d.mts.map +1 -1
  43. package/dist/record/record.mjs +23 -21
  44. package/dist/record/record.mjs.map +1 -1
  45. package/dist/type.d.mts +1 -1
  46. package/dist/type.d.mts.map +1 -1
  47. package/dist/utils/create-assert-fn.mjs +1 -1
  48. package/dist/utils/create-cast-fn.mjs +1 -1
  49. package/dist/utils/create-primitive-type.mjs +1 -1
  50. package/dist/utils/index.d.mts +1 -0
  51. package/dist/utils/index.d.mts.map +1 -1
  52. package/dist/utils/index.mjs +1 -0
  53. package/dist/utils/index.mjs.map +1 -1
  54. package/dist/utils/validation-error.d.mts.map +1 -0
  55. package/dist/utils/validation-error.mjs.map +1 -0
  56. package/package.json +4 -2
  57. package/src/array/array-of-length.mts +12 -7
  58. package/src/array/array-of-length.test.mts +10 -1
  59. package/src/array/array.mts +12 -7
  60. package/src/array/array.test.mts +10 -1
  61. package/src/array/non-empty-array.mts +11 -7
  62. package/src/array/non-empty-array.test.mts +10 -1
  63. package/src/array/tuple.mts +11 -9
  64. package/src/array/tuple.test.mts +14 -1
  65. package/src/branded/brand.mts +2 -2
  66. package/src/branded/number/finite-number.test.mts +10 -1
  67. package/src/branded/number/int.test.mts +10 -1
  68. package/src/branded/number/int16.test.mts +10 -1
  69. package/src/branded/number/int32.test.mts +10 -1
  70. package/src/branded/number/non-negative-finite-number.test.mts +10 -1
  71. package/src/branded/number/non-zero-finite-number.test.mts +10 -1
  72. package/src/branded/number/non-zero-int.test.mts +10 -1
  73. package/src/branded/number/non-zero-safe-int.test.mts +10 -1
  74. package/src/branded/number/positive-finite-number.test.mts +10 -1
  75. package/src/branded/number/positive-int.test.mts +10 -1
  76. package/src/branded/number/positive-safe-int.test.mts +10 -1
  77. package/src/branded/number/safe-int.test.mts +10 -1
  78. package/src/branded/number/safe-uint.test.mts +10 -1
  79. package/src/branded/number/uint.test.mts +10 -1
  80. package/src/branded/number/uint16.test.mts +10 -1
  81. package/src/branded/number/uint32.test.mts +10 -1
  82. package/src/branded/simple-branded-number.test.mts +19 -1
  83. package/src/branded/simple-branded-string.test.mts +19 -1
  84. package/src/compose/intersection.mts +23 -19
  85. package/src/compose/intersection.test.mts +28 -1
  86. package/src/compose/merge-records.mts +22 -19
  87. package/src/compose/merge-records.test.mts +10 -1
  88. package/src/compose/union.mts +6 -2
  89. package/src/compose/union.test.mts +10 -1
  90. package/src/enum/enum.mts +6 -2
  91. package/src/enum/enum.test.mts +10 -1
  92. package/src/enum/uint-range.mts +6 -2
  93. package/src/enum/uint-range.test.mts +10 -1
  94. package/src/index.mts +0 -1
  95. package/src/primitives/bigint.test.mts +19 -1
  96. package/src/primitives/boolean.test.mts +28 -1
  97. package/src/primitives/null.test.mts +9 -0
  98. package/src/primitives/number.test.mts +18 -0
  99. package/src/primitives/string.test.mts +18 -0
  100. package/src/primitives/symbol.test.mts +10 -1
  101. package/src/primitives/undefined.test.mts +9 -0
  102. package/src/record/key-value-record.mts +34 -36
  103. package/src/record/key-value-record.test.mts +14 -1
  104. package/src/record/keyof.test.mts +10 -1
  105. package/src/record/omit.mts +8 -5
  106. package/src/record/omit.test.mts +46 -1
  107. package/src/record/partial.mts +7 -5
  108. package/src/record/partial.test.mts +71 -1
  109. package/src/record/pick.mts +7 -5
  110. package/src/record/pick.test.mts +46 -1
  111. package/src/record/record-allow-excess-properties.test.mts +28 -1
  112. package/src/record/record-strict-composition-simple.test.mts +38 -0
  113. package/src/record/record-strict-composition.test.mts +71 -0
  114. package/src/record/record.mts +50 -36
  115. package/src/record/record.test.mts +26 -1
  116. package/src/record/strict-record.test.mts +10 -1
  117. package/src/type.mts +1 -1
  118. package/src/utils/create-assert-fn.mts +1 -1
  119. package/src/utils/create-cast-fn.mts +1 -1
  120. package/src/utils/create-primitive-type.mts +1 -1
  121. package/src/utils/index.mts +1 -0
  122. package/dist/validation-error.d.mts.map +0 -1
  123. package/dist/validation-error.mjs.map +0 -1
  124. package/src/combination.test.mts +0 -544
  125. /package/dist/{validation-error.d.mts → utils/validation-error.d.mts} +0 -0
  126. /package/dist/{validation-error.mjs → utils/validation-error.mjs} +0 -0
  127. /package/src/{validation-error.mts → utils/validation-error.mts} +0 -0
  128. /package/src/{validation-error.test.mts → utils/validation-error.test.mts} +0 -0
@@ -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
  });
@@ -39,6 +39,15 @@ describe('record strict composition tests', () => {
39
39
  }
40
40
  });
41
41
 
42
+ test('pickedType validate returns input as-is for OK cases', () => {
43
+ const input = { id: '123', name: 'John' };
44
+ const result = pickedType.validate(input);
45
+ expect(Result.isOk(result)).toBe(true);
46
+ if (Result.isOk(result)) {
47
+ expect(result.value).toBe(input); // ✅ same reference
48
+ }
49
+ });
50
+
42
51
  test('rejects data with excess properties (inherits strict behavior)', () => {
43
52
  const dataWithExcess = { id: '123', name: 'John', extra: 'not allowed' };
44
53
 
@@ -94,6 +103,15 @@ describe('record strict composition tests', () => {
94
103
  }
95
104
  });
96
105
 
106
+ test('omittedType validate returns input as-is for OK cases', () => {
107
+ const input = { id: '123', name: 'John' };
108
+ const result = omittedType.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('rejects data with excess properties (inherits strict behavior)', () => {
98
116
  const dataWithExcess = { id: '123', name: 'John', extra: 'not allowed' };
99
117
 
@@ -123,6 +141,7 @@ describe('record strict composition tests', () => {
123
141
 
124
142
  if (Result.isOk(result)) {
125
143
  expect(result.value).toStrictEqual({
144
+ age: 25,
126
145
  id: '123',
127
146
  name: 'John',
128
147
  });
@@ -150,6 +169,15 @@ describe('record strict composition tests', () => {
150
169
  }
151
170
  });
152
171
 
172
+ test('partialType validate returns input as-is for OK cases', () => {
173
+ const input = { id: '123', name: 'John' };
174
+ const result = partialType.validate(input);
175
+ expect(Result.isOk(result)).toBe(true);
176
+ if (Result.isOk(result)) {
177
+ expect(result.value).toBe(input); // ✅ same reference
178
+ }
179
+ });
180
+
153
181
  test('accepts empty object (all fields optional)', () => {
154
182
  const emptyData = {};
155
183
 
@@ -164,6 +192,15 @@ describe('record strict composition tests', () => {
164
192
  }
165
193
  });
166
194
 
195
+ test('partialType validate returns input as-is for empty object', () => {
196
+ const input = {};
197
+ const result = partialType.validate(input);
198
+ expect(Result.isOk(result)).toBe(true);
199
+ if (Result.isOk(result)) {
200
+ expect(result.value).toBe(input); // ✅ same reference
201
+ }
202
+ });
203
+
167
204
  test('rejects data with excess properties (inherits strict behavior)', () => {
168
205
  const dataWithExcess = { id: '123', name: 'John', extra: 'not allowed' };
169
206
 
@@ -206,6 +243,18 @@ describe('record strict composition tests', () => {
206
243
  }
207
244
  });
208
245
 
246
+ test('partiallyPartialType validate returns input as-is for OK cases', () => {
247
+ const partiallyPartialType = partial(strictBaseRecord, {
248
+ keysToBeOptional: ['age', 'email'],
249
+ });
250
+ const input = { id: '123', name: 'John' };
251
+ const result = partiallyPartialType.validate(input);
252
+ expect(Result.isOk(result)).toBe(true);
253
+ if (Result.isOk(result)) {
254
+ expect(result.value).toBe(input); // ✅ same reference
255
+ }
256
+ });
257
+
209
258
  test('partially partial rejects missing required fields', () => {
210
259
  const partiallyPartialType = partial(strictBaseRecord, {
211
260
  keysToBeOptional: ['age', 'email'],
@@ -362,6 +411,17 @@ describe('record strict composition tests', () => {
362
411
  }
363
412
  });
364
413
 
414
+ test('pickedPartialType validate returns input as-is for OK cases', () => {
415
+ const partialType = partial(strictBaseRecord);
416
+ const pickedPartialType = pick(partialType, ['id', 'name']);
417
+ const input = { id: '123', name: 'John' };
418
+ const result = pickedPartialType.validate(input);
419
+ expect(Result.isOk(result)).toBe(true);
420
+ if (Result.isOk(result)) {
421
+ expect(result.value).toBe(input); // ✅ same reference
422
+ }
423
+ });
424
+
365
425
  test('partial of pick of strict record', () => {
366
426
  const pickedType = pick(strictBaseRecord, ['id', 'name']);
367
427
  const partialPickedType = partial(pickedType);
@@ -381,6 +441,17 @@ describe('record strict composition tests', () => {
381
441
  }
382
442
  });
383
443
 
444
+ test('partialPickedType validate returns input as-is for OK cases', () => {
445
+ const pickedType = pick(strictBaseRecord, ['id', 'name']);
446
+ const partialPickedType = partial(pickedType);
447
+ const input = { id: '123' };
448
+ const result = partialPickedType.validate(input);
449
+ expect(Result.isOk(result)).toBe(true);
450
+ if (Result.isOk(result)) {
451
+ expect(result.value).toBe(input); // ✅ same reference
452
+ }
453
+ });
454
+
384
455
  test('nested compositions reject excess properties', () => {
385
456
  const partialType = partial(strictBaseRecord);
386
457
  const pickedPartialType = pick(partialType, ['id', 'name']);
@@ -1,12 +1,14 @@
1
- import { isRecord, Result, tp } from 'ts-data-forge';
1
+ import { Arr, isRecord, Result, tp } 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
  export const record = <const R extends ReadonlyRecord<string, Type<unknown>>>(
12
14
  source: R,
@@ -48,32 +50,31 @@ export const record = <const R extends ReadonlyRecord<string, Type<unknown>>>(
48
50
  ]);
49
51
  }
50
52
 
51
- const defaultErrors: readonly ValidationError[] = Object.entries(
52
- source,
53
- ).flatMap(([k, valueType]) => {
54
- if (!Object.hasOwn(a, k)) {
55
- if (source[k]?.optional === true) return [];
56
-
57
- return [
58
- {
59
- path: [k],
60
- actualValue: a,
61
- typeName: typeNameFilled,
62
- expectedType: typeNameFilled,
63
- message: `Missing required key "${k}"`,
64
- } satisfies ValidationErrorWithMessage,
65
- ];
66
- }
67
-
68
- const v = a[k];
69
- const res = valueType.validate(v);
70
-
71
- if (Result.isErr(res)) {
72
- return prependPathToValidationErrors(res.value, k);
73
- }
74
-
75
- return [];
76
- });
53
+ const defaultErrors: readonly ValidationError[] = Arr.generate(
54
+ function* () {
55
+ for (const [k, valueType] of Object.entries(source)) {
56
+ if (!Object.hasOwn(a, k)) {
57
+ if (source[k]?.optional !== true) {
58
+ yield {
59
+ path: [k],
60
+ actualValue: a,
61
+ typeName: typeNameFilled,
62
+ expectedType: typeNameFilled,
63
+ message: `Missing required key "${k}"`,
64
+ } satisfies ValidationErrorWithMessage;
65
+ }
66
+ } else {
67
+ // The case where the key exists in the object
68
+ const v = a[k];
69
+ const res = valueType.validate(v);
70
+
71
+ if (Result.isErr(res)) {
72
+ yield* prependPathToValidationErrors(res.value, k);
73
+ }
74
+ }
75
+ }
76
+ },
77
+ );
77
78
 
78
79
  // Check for excess properties if allowExcessProperties is false
79
80
  if (!allowExcessProperties) {
@@ -135,13 +136,26 @@ namespace TsFortressInternal {
135
136
  type RecordTypeValueImplSub<
136
137
  A extends ReadonlyRecord<string, Type<unknown>>,
137
138
  OptionalKeys extends keyof A,
138
- > = Readonly<
139
- {
140
- [key in OptionalKeys]?: TypeOf<A[key]>;
141
- } & {
142
- [key in Exclude<keyof A, OptionalKeys>]: TypeOf<A[key]>;
143
- }
144
- >;
139
+ > =
140
+ TypeEq<OptionalKeys, never> extends true
141
+ ? Readonly<{
142
+ [key in Exclude<keyof A, OptionalKeys>]: TypeOf<A[key]>;
143
+ }>
144
+ : TypeEq<keyof A, OptionalKeys> extends true
145
+ ? Readonly<
146
+ MergeIntersection<{
147
+ [key in OptionalKeys]?: TypeOf<A[key]>;
148
+ }>
149
+ >
150
+ : Readonly<
151
+ MergeIntersection<
152
+ {
153
+ [key in OptionalKeys]?: TypeOf<A[key]>;
154
+ } & {
155
+ [key in Exclude<keyof A, OptionalKeys>]: TypeOf<A[key]>;
156
+ }
157
+ >
158
+ >;
145
159
 
146
160
  type OptionalTypeKeys<A extends ReadonlyRecord<string, Type<unknown>>> = {
147
161
  [K in keyof A]: A[K] extends { optional: true } ? K : never;
@@ -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 { optional } from './optional.mjs';
6
6
  import { record } from './record.mjs';
7
7
 
@@ -143,6 +143,19 @@ describe('record', () => {
143
143
  ]);
144
144
  }
145
145
  });
146
+
147
+ test('validate returns input as-is for OK cases', () => {
148
+ const input = {
149
+ year: 2023,
150
+ month: 6,
151
+ date: 15,
152
+ };
153
+ const result = ymd.validate(input);
154
+ expect(Result.isOk(result)).toBe(true);
155
+ if (Result.isOk(result)) {
156
+ expect(result.value).toBe(input); // ✅ same reference
157
+ }
158
+ });
146
159
  });
147
160
 
148
161
  describe('fill', () => {
@@ -311,6 +324,18 @@ describe('partial record', () => {
311
324
  ]);
312
325
  }
313
326
  });
327
+
328
+ test('validate returns input as-is for OK cases', () => {
329
+ const input = {
330
+ year: 2024,
331
+ month: 8,
332
+ };
333
+ const result = ymd.validate(input);
334
+ expect(Result.isOk(result)).toBe(true);
335
+ if (Result.isOk(result)) {
336
+ expect(result.value).toBe(input); // ✅ same reference
337
+ }
338
+ });
314
339
  });
315
340
 
316
341
  describe('fill', () => {
@@ -1,7 +1,7 @@
1
1
  import { expectType, Result } from 'ts-data-forge';
2
2
  import { number, string } from '../primitives/index.mjs';
3
3
  import { type TypeOf } from '../type.mjs';
4
- import { validationErrorsToMessages } from '../validation-error.mjs';
4
+ import { validationErrorsToMessages } from '../utils/index.mjs';
5
5
  import { record, strictRecord } from './record.mjs';
6
6
 
7
7
  describe('strictRecord', () => {
@@ -49,6 +49,15 @@ describe('strictRecord', () => {
49
49
  }
50
50
  });
51
51
 
52
+ test('validate returns input as-is for OK cases', () => {
53
+ const input = { name: 'John', age: 30 };
54
+ const result = userType.validate(input);
55
+ expect(Result.isOk(result)).toBe(true);
56
+ if (Result.isOk(result)) {
57
+ expect(result.value).toBe(input); // ✅ same reference
58
+ }
59
+ });
60
+
52
61
  test('falsy case - excess properties', () => {
53
62
  const userWithExtra = { name: 'John', age: 30, extra: 'not allowed' };
54
63
 
package/src/type.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type Result } from 'ts-data-forge';
2
- import { type ValidationError } from './validation-error.mjs';
2
+ import { type ValidationError } from './utils/index.mjs';
3
3
 
4
4
  /**
5
5
  * - `typeName` : Name for this type
@@ -3,7 +3,7 @@ import { type Type } from '../type.mjs';
3
3
  import {
4
4
  validationErrorsToMessages,
5
5
  type ValidationError,
6
- } from '../validation-error.mjs';
6
+ } from './validation-error.mjs';
7
7
 
8
8
  const validationResultToString = (result: readonly ValidationError[]): string =>
9
9
  `\n${validationErrorsToMessages(result).join(',\n')}`;
@@ -3,7 +3,7 @@ import { type Type } from '../type.mjs';
3
3
  import {
4
4
  validationErrorsToMessages,
5
5
  type ValidationError,
6
- } from '../validation-error.mjs';
6
+ } from './validation-error.mjs';
7
7
 
8
8
  const validationResultToString = (result: readonly ValidationError[]): string =>
9
9
  `\n${validationErrorsToMessages(result).join(',\n')}`;
@@ -1,8 +1,8 @@
1
1
  import { Result } from 'ts-data-forge';
2
2
  import { type Type } from '../type.mjs';
3
- import { createPrimitiveValidationError } from '../validation-error.mjs';
4
3
  import { createAssertFn } from './create-assert-fn.mjs';
5
4
  import { createCastFn } from './create-cast-fn.mjs';
5
+ import { createPrimitiveValidationError } from './validation-error.mjs';
6
6
 
7
7
  export const createPrimitiveType = <A extends Primitive>({
8
8
  typeName,
@@ -3,3 +3,4 @@ export * from './create-cast-fn.mjs';
3
3
  export * from './create-is-fn.mjs';
4
4
  export * from './create-primitive-type.mjs';
5
5
  export * from './create-type.mjs';
6
+ export * from './validation-error.mjs';
@@ -1 +0,0 @@
1
- {"version":3,"file":"validation-error.d.mts","sourceRoot":"","sources":["../src/validation-error.mts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,iFAAiF;IACjF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,8CAA8C;IAC9C,WAAW,EAAE,OAAO,CAAC;IACrB,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH,MAAM,MAAM,0BAA0B,GAAG,iBAAiB,CACxD,UAAU,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CACvE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,eAAe,KAAG,MASjE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GACrC,QAAQ,SAAS,eAAe,EAAE,KACjC,SAAS,MAAM,EAA0C,CAAC;AAE7D;;GAEG;AACH,eAAO,MAAM,6BAA6B,GACxC,QAAQ,SAAS,eAAe,EAAE,EAClC,aAAa,MAAM,KAClB,SAAS,eAAe,EAItB,CAAC;AAEN;;GAEG;AACH,eAAO,MAAM,8BAA8B,GACzC,QAAQ,SAAS,eAAe,EAAE,EAClC,OAAO,MAAM,KACZ,SAAS,eAAe,EAC8B,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAAI,0CAI5C,QAAQ,CAAC;IACV,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,KAAG,eAMH,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"validation-error.mjs","sources":["../src/validation-error.mts"],"sourcesContent":[null],"names":[],"mappings":"AAoBA;;AAEG;AACI,MAAM,wBAAwB,GAAG,CAAC,KAAsB,KAAY;IACzE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,CAAA,IAAA,EAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA,CAAE,GAAG,EAAE;AAC1E,IAAA,MAAM,aAAa,GAAG,OAAO,KAAK,CAAC,WAAW;AAE9C,IAAA,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE;AAC/B,QAAA,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA,EAAG,OAAO,EAAE;IACrC;IAEA,OAAO,CAAA,SAAA,EAAY,KAAK,CAAC,YAAY,GAAG,OAAO,CAAA,MAAA,EAAS,aAAa,CAAA,CAAE;AACzE;AAEA;;;AAGG;AACI,MAAM,0BAA0B,GAAG,CACxC,MAAkC,KACZ,MAAM,CAAC,GAAG,CAAC,wBAAwB;AAE3D;;AAEG;MACU,6BAA6B,GAAG,CAC3C,MAAkC,EAClC,WAAmB,KAEnB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM;AACrB,IAAA,GAAG,KAAK;IACR,IAAI,EAAE,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC;AACnC,CAAA,CAAC;AAEJ;;AAEG;MACU,8BAA8B,GAAG,CAC5C,MAAkC,EAClC,KAAa,KAEb,6BAA6B,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE;AAExD;;AAEG;AACI,MAAM,8BAA8B,GAAG,CAAC,EAC7C,WAAW,EACX,YAAY,EACZ,QAAQ,GAKR,MAAuB;AACvB,IAAA,IAAI,EAAE,EAAE;IACR,WAAW;IACX,YAAY;IACZ,QAAQ;AACR,IAAA,OAAO,EAAE,SAAS;AACnB,CAAA;;;;"}