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
@@ -0,0 +1,180 @@
1
+ import { Result } from 'ts-data-forge';
2
+ import { number } from '../primitives/index.mjs';
3
+ import { validationErrorsToMessages } from '../validation-error.mjs';
4
+ import { record } from './record.mjs';
5
+
6
+ describe('record allowExcessProperties option', () => {
7
+ const strictRecord = record(
8
+ {
9
+ name: number(0),
10
+ age: number(0),
11
+ },
12
+ { allowExcessProperties: false },
13
+ );
14
+
15
+ const permissiveRecord = record(
16
+ {
17
+ name: number(0),
18
+ age: number(0),
19
+ },
20
+ { allowExcessProperties: true },
21
+ );
22
+
23
+ const defaultRecord = record({
24
+ name: number(0),
25
+ age: number(0),
26
+ });
27
+
28
+ test('allowExcessProperties: false - rejects excess properties', () => {
29
+ const dataWithExcess = {
30
+ name: 42,
31
+ age: 25,
32
+ extra: 'not allowed',
33
+ };
34
+
35
+ expect(strictRecord.is(dataWithExcess)).toBe(false);
36
+
37
+ const result = strictRecord.validate(dataWithExcess);
38
+ expect(Result.isErr(result)).toBe(true);
39
+
40
+ if (Result.isErr(result)) {
41
+ expect(result.value).toStrictEqual([
42
+ {
43
+ path: ['extra'],
44
+ actualValue: 'not allowed',
45
+ typeName: '{ name: number, age: number }',
46
+ expectedType: '{ name: number, age: number }',
47
+ message: 'Excess property "extra" is not allowed',
48
+ },
49
+ ]);
50
+
51
+ expect(validationErrorsToMessages(result.value)).toStrictEqual([
52
+ 'Excess property "extra" is not allowed at extra',
53
+ ]);
54
+ }
55
+ });
56
+
57
+ test('allowExcessProperties: false - accepts valid data without excess properties', () => {
58
+ const validData = {
59
+ name: 42,
60
+ age: 25,
61
+ };
62
+
63
+ expect(strictRecord.is(validData)).toBe(true);
64
+
65
+ const result = strictRecord.validate(validData);
66
+ expect(Result.isOk(result)).toBe(true);
67
+
68
+ if (Result.isOk(result)) {
69
+ expect(result.value).toStrictEqual({
70
+ name: 42,
71
+ age: 25,
72
+ });
73
+ }
74
+ });
75
+
76
+ test('allowExcessProperties: true - accepts excess properties', () => {
77
+ const dataWithExcess = {
78
+ name: 42,
79
+ age: 25,
80
+ extra: 'allowed',
81
+ };
82
+
83
+ expect(permissiveRecord.is(dataWithExcess)).toBe(true);
84
+
85
+ const result = permissiveRecord.validate(dataWithExcess);
86
+ expect(Result.isOk(result)).toBe(true);
87
+
88
+ if (Result.isOk(result)) {
89
+ expect(result.value).toStrictEqual({
90
+ name: 42,
91
+ age: 25,
92
+ extra: 'allowed',
93
+ });
94
+ }
95
+ });
96
+
97
+ test('default behavior - allows excess properties (allowExcessProperties defaults to true)', () => {
98
+ const dataWithExcess = {
99
+ name: 42,
100
+ age: 25,
101
+ extra: 'allowed by default',
102
+ };
103
+
104
+ expect(defaultRecord.is(dataWithExcess)).toBe(true);
105
+
106
+ const result = defaultRecord.validate(dataWithExcess);
107
+ expect(Result.isOk(result)).toBe(true);
108
+
109
+ if (Result.isOk(result)) {
110
+ expect(result.value).toStrictEqual({
111
+ name: 42,
112
+ age: 25,
113
+ extra: 'allowed by default',
114
+ });
115
+ }
116
+ });
117
+
118
+ test('allowExcessProperties: false - multiple excess properties', () => {
119
+ const dataWithMultipleExcess = {
120
+ name: 42,
121
+ age: 25,
122
+ extra1: 'not allowed 1',
123
+ extra2: 'not allowed 2',
124
+ };
125
+
126
+ const result = strictRecord.validate(dataWithMultipleExcess);
127
+ expect(Result.isErr(result)).toBe(true);
128
+
129
+ if (Result.isErr(result)) {
130
+ expect(result.value).toHaveLength(2);
131
+ expect(result.value).toStrictEqual([
132
+ {
133
+ path: ['extra1'],
134
+ actualValue: 'not allowed 1',
135
+ typeName: '{ name: number, age: number }',
136
+ expectedType: '{ name: number, age: number }',
137
+ message: 'Excess property "extra1" is not allowed',
138
+ },
139
+ {
140
+ path: ['extra2'],
141
+ actualValue: 'not allowed 2',
142
+ typeName: '{ name: number, age: number }',
143
+ expectedType: '{ name: number, age: number }',
144
+ message: 'Excess property "extra2" is not allowed',
145
+ },
146
+ ]);
147
+ }
148
+ });
149
+
150
+ test('allowExcessProperties: false - combines with other validation errors', () => {
151
+ const invalidData = {
152
+ name: 'invalid', // should be number
153
+ age: 25,
154
+ extra: 'not allowed',
155
+ };
156
+
157
+ const result = strictRecord.validate(invalidData);
158
+ expect(Result.isErr(result)).toBe(true);
159
+
160
+ if (Result.isErr(result)) {
161
+ expect(result.value).toHaveLength(2);
162
+ // First error: invalid type for 'name'
163
+ expect(result.value[0]).toStrictEqual({
164
+ path: ['name'],
165
+ actualValue: 'invalid',
166
+ expectedType: 'number',
167
+ typeName: 'number',
168
+ message: undefined,
169
+ });
170
+ // Second error: excess property
171
+ expect(result.value[1]).toStrictEqual({
172
+ path: ['extra'],
173
+ actualValue: 'not allowed',
174
+ typeName: '{ name: number, age: number }',
175
+ expectedType: '{ name: number, age: number }',
176
+ message: 'Excess property "extra" is not allowed',
177
+ });
178
+ }
179
+ });
180
+ });
@@ -0,0 +1,134 @@
1
+ import { Result } from 'ts-data-forge';
2
+ import { number, string } from '../primitives/index.mjs';
3
+ import { omit } from './omit.mjs';
4
+ import { partial } from './partial.mjs';
5
+ import { pick } from './pick.mjs';
6
+ import { record } from './record.mjs';
7
+
8
+ describe('record strict composition - simple tests', () => {
9
+ // Base strict record type
10
+ const strictRecord = record(
11
+ {
12
+ id: string(''),
13
+ name: string(''),
14
+ age: number(0),
15
+ },
16
+ { allowExcessProperties: false },
17
+ );
18
+
19
+ describe('pick behavior with strict record', () => {
20
+ test('pick inherits strict validation from base record', () => {
21
+ const pickedType = pick(strictRecord, ['id', 'name']);
22
+
23
+ // Test with excess property
24
+ const dataWithExcess = { id: '123', name: 'John', extra: 'not allowed' };
25
+
26
+ const result = pickedType.validate(dataWithExcess);
27
+ expect(Result.isErr(result)).toBe(true);
28
+
29
+ if (Result.isErr(result)) {
30
+ const excessError = result.value.find(
31
+ (error) =>
32
+ error.message?.includes(
33
+ 'Excess property "extra" is not allowed',
34
+ ) === true,
35
+ );
36
+ expect(excessError).toBeDefined();
37
+ }
38
+ });
39
+
40
+ test('pick accepts valid data and fills missing fields', () => {
41
+ const pickedType = pick(strictRecord, ['id', 'name']);
42
+ const validData = { id: '123', name: 'John' };
43
+
44
+ const result = pickedType.validate(validData);
45
+ expect(Result.isOk(result)).toBe(true);
46
+
47
+ if (Result.isOk(result)) {
48
+ expect(result.value).toStrictEqual({ id: '123', name: 'John' });
49
+ }
50
+ });
51
+ });
52
+
53
+ describe('omit behavior with strict record', () => {
54
+ test('omit inherits strict validation from base record', () => {
55
+ const omittedType = omit(strictRecord, ['age']);
56
+
57
+ // Test with excess property
58
+ const dataWithExcess = { id: '123', name: 'John', extra: 'not allowed' };
59
+
60
+ const result = omittedType.validate(dataWithExcess);
61
+ expect(Result.isErr(result)).toBe(true);
62
+
63
+ if (Result.isErr(result)) {
64
+ const excessError = result.value.find(
65
+ (error) =>
66
+ error.message?.includes(
67
+ 'Excess property "extra" is not allowed',
68
+ ) === true,
69
+ );
70
+ expect(excessError).toBeDefined();
71
+ }
72
+ });
73
+ });
74
+
75
+ describe('partial behavior with strict record', () => {
76
+ test('partial inherits strict validation from base record', () => {
77
+ const partialType = partial(strictRecord);
78
+
79
+ // Test with excess property
80
+ const dataWithExcess = { id: '123', extra: 'not allowed' };
81
+
82
+ const result = partialType.validate(dataWithExcess);
83
+ expect(Result.isErr(result)).toBe(true);
84
+
85
+ if (Result.isErr(result)) {
86
+ const excessError = result.value.find(
87
+ (error) =>
88
+ error.message?.includes(
89
+ 'Excess property "extra" is not allowed',
90
+ ) === true,
91
+ );
92
+ expect(excessError).toBeDefined();
93
+ }
94
+ });
95
+ });
96
+
97
+ describe('behavior comparison: strict vs permissive', () => {
98
+ const permissiveRecord = record({
99
+ id: string(''),
100
+ name: string(''),
101
+ age: number(0),
102
+ }); // allowExcessProperties defaults to true
103
+
104
+ test('strict record rejects excess properties', () => {
105
+ const data = { id: '123', name: 'John', age: 25, extra: 'not allowed' };
106
+
107
+ const result = strictRecord.validate(data);
108
+ expect(Result.isErr(result)).toBe(true);
109
+ });
110
+
111
+ test('permissive record accepts excess properties', () => {
112
+ const data = { id: '123', name: 'John', age: 25, extra: 'allowed' };
113
+
114
+ const result = permissiveRecord.validate(data);
115
+ expect(Result.isOk(result)).toBe(true);
116
+ });
117
+
118
+ test('pick from strict record rejects excess properties', () => {
119
+ const strictPicked = pick(strictRecord, ['id', 'name']);
120
+ const data = { id: '123', name: 'John', extra: 'not allowed' };
121
+
122
+ const result = strictPicked.validate(data);
123
+ expect(Result.isErr(result)).toBe(true);
124
+ });
125
+
126
+ test('pick from permissive record accepts excess properties', () => {
127
+ const permissivePicked = pick(permissiveRecord, ['id', 'name']);
128
+ const data = { id: '123', name: 'John', extra: 'allowed' };
129
+
130
+ const result = permissivePicked.validate(data);
131
+ expect(Result.isOk(result)).toBe(true);
132
+ });
133
+ });
134
+ });