ts-fortress 2.2.2 → 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.
- package/dist/compose/index.d.mts +1 -0
- package/dist/compose/index.d.mts.map +1 -1
- package/dist/compose/index.mjs +1 -0
- package/dist/compose/index.mjs.map +1 -1
- package/dist/compose/recursion.d.mts +5 -0
- package/dist/compose/recursion.d.mts.map +1 -0
- package/dist/compose/recursion.mjs +38 -0
- package/dist/compose/recursion.mjs.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/compose/index.mts +1 -0
- package/src/compose/recursion.mts +49 -0
- package/src/compose/recursion.test.mts +234 -0
package/dist/compose/index.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/compose/index.mts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/compose/index.mts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC"}
|
package/dist/compose/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recursion.d.mts","sourceRoot":"","sources":["../../src/compose/recursion.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC,eAAO,MAAM,SAAS,GAAI,CAAC,EACzB,UAAU,MAAM,EAChB,YAAY,MAAM,IAAI,CAAC,CAAC,CAAC,EACzB,UAAU,OAAO,CACf,QAAQ,CAAC;IACP,YAAY,EAAE,CAAC,CAAC;CACjB,CAAC,CACH,KACA,IAAI,CAAC,CAAC,CAqCR,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createAssertFn } from '../utils/create-assert-fn.mjs';
|
|
2
|
+
import { createCastFn } from '../utils/create-cast-fn.mjs';
|
|
3
|
+
import { createIsFn } from '../utils/create-is-fn.mjs';
|
|
4
|
+
import 'ts-data-forge';
|
|
5
|
+
|
|
6
|
+
const recursion = (typeName, definition, options) => {
|
|
7
|
+
const cache = {
|
|
8
|
+
innerType: undefined,
|
|
9
|
+
computedDefaultValue: undefined,
|
|
10
|
+
};
|
|
11
|
+
const getInnerType = () => {
|
|
12
|
+
// eslint-disable-next-line functional/immutable-data
|
|
13
|
+
cache.innerType ??= definition();
|
|
14
|
+
return cache.innerType;
|
|
15
|
+
};
|
|
16
|
+
const validate = (a) => getInnerType().validate(a);
|
|
17
|
+
const is = createIsFn(validate);
|
|
18
|
+
const fill = (a) => getInnerType().fill(a);
|
|
19
|
+
return {
|
|
20
|
+
typeName,
|
|
21
|
+
get defaultValue() {
|
|
22
|
+
if (options?.defaultValue !== undefined) {
|
|
23
|
+
return options.defaultValue;
|
|
24
|
+
}
|
|
25
|
+
// eslint-disable-next-line functional/immutable-data
|
|
26
|
+
cache.computedDefaultValue ??= getInnerType().defaultValue;
|
|
27
|
+
return cache.computedDefaultValue;
|
|
28
|
+
},
|
|
29
|
+
fill,
|
|
30
|
+
validate,
|
|
31
|
+
is,
|
|
32
|
+
assertIs: createAssertFn(validate),
|
|
33
|
+
cast: createCastFn(validate),
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { recursion };
|
|
38
|
+
//# sourceMappingURL=recursion.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recursion.mjs","sources":["../../src/compose/recursion.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;AAGO,MAAM,SAAS,GAAG,CACvB,QAAgB,EAChB,UAAyB,EACzB,OAIC,KACU;AACX,IAAA,MAAM,KAAK,GAGP;AACF,QAAA,SAAS,EAAE,SAAgC;AAC3C,QAAA,oBAAoB,EAAE,SAA0B;KACjD;IAED,MAAM,YAAY,GAAG,MAAc;;AAEjC,QAAA,KAAK,CAAC,SAAS,KAAK,UAAU,EAAE;QAChC,OAAO,KAAK,CAAC,SAAS;AACxB,IAAA,CAAC;AAED,IAAA,MAAM,QAAQ,GAAwB,CAAC,CAAC,KAAK,YAAY,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AAEvE,IAAA,MAAM,EAAE,GAAG,UAAU,CAAI,QAAQ,CAAC;AAElC,IAAA,MAAM,IAAI,GAAoB,CAAC,CAAC,KAAK,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAE3D,OAAO;QACL,QAAQ;AACR,QAAA,IAAI,YAAY,GAAA;AACd,YAAA,IAAI,OAAO,EAAE,YAAY,KAAK,SAAS,EAAE;gBACvC,OAAO,OAAO,CAAC,YAAY;YAC7B;;AAEA,YAAA,KAAK,CAAC,oBAAoB,KAAK,YAAY,EAAE,CAAC,YAAY;YAC1D,OAAO,KAAK,CAAC,oBAAoB;QACnC,CAAC;QACD,IAAI;QACJ,QAAQ;QACR,EAAE;AACF,QAAA,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC;AAClC,QAAA,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC;KAC7B;AACH;;;;"}
|
package/dist/index.mjs
CHANGED
|
@@ -23,6 +23,7 @@ export { simpleBrandedNumber } from './branded/simple-branded-number.mjs';
|
|
|
23
23
|
export { simpleBrandedString } from './branded/simple-branded-string.mjs';
|
|
24
24
|
export { intersection } from './compose/intersection.mjs';
|
|
25
25
|
export { mergeRecords } from './compose/merge-records.mjs';
|
|
26
|
+
export { recursion } from './compose/recursion.mjs';
|
|
26
27
|
export { union } from './compose/union.mjs';
|
|
27
28
|
export { enumType } from './enum/enum.mjs';
|
|
28
29
|
export { uintRange } from './enum/uint-range.mjs';
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
package/src/compose/index.mts
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type Type } from '../type.mjs';
|
|
2
|
+
import { createAssertFn, createCastFn, createIsFn } from '../utils/index.mjs';
|
|
3
|
+
|
|
4
|
+
export const recursion = <A,>(
|
|
5
|
+
typeName: string,
|
|
6
|
+
definition: () => Type<A>,
|
|
7
|
+
options?: Partial<
|
|
8
|
+
Readonly<{
|
|
9
|
+
defaultValue: A;
|
|
10
|
+
}>
|
|
11
|
+
>,
|
|
12
|
+
): Type<A> => {
|
|
13
|
+
const cache: {
|
|
14
|
+
innerType: Type<A> | undefined;
|
|
15
|
+
computedDefaultValue: A | undefined;
|
|
16
|
+
} = {
|
|
17
|
+
innerType: undefined as Type<A> | undefined,
|
|
18
|
+
computedDefaultValue: undefined as A | undefined,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getInnerType = (): Type<A> => {
|
|
22
|
+
// eslint-disable-next-line functional/immutable-data
|
|
23
|
+
cache.innerType ??= definition();
|
|
24
|
+
return cache.innerType;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const validate: Type<A>['validate'] = (a) => getInnerType().validate(a);
|
|
28
|
+
|
|
29
|
+
const is = createIsFn<A>(validate);
|
|
30
|
+
|
|
31
|
+
const fill: Type<A>['fill'] = (a) => getInnerType().fill(a);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
typeName,
|
|
35
|
+
get defaultValue(): A {
|
|
36
|
+
if (options?.defaultValue !== undefined) {
|
|
37
|
+
return options.defaultValue;
|
|
38
|
+
}
|
|
39
|
+
// eslint-disable-next-line functional/immutable-data
|
|
40
|
+
cache.computedDefaultValue ??= getInnerType().defaultValue;
|
|
41
|
+
return cache.computedDefaultValue;
|
|
42
|
+
},
|
|
43
|
+
fill,
|
|
44
|
+
validate,
|
|
45
|
+
is,
|
|
46
|
+
assertIs: createAssertFn(validate),
|
|
47
|
+
cast: createCastFn(validate),
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { expectType } from 'ts-data-forge';
|
|
2
|
+
import { array } from '../array/index.mjs';
|
|
3
|
+
import { boolean, nullType, number, string } from '../primitives/index.mjs';
|
|
4
|
+
import { keyValueRecord, record } from '../record/index.mjs';
|
|
5
|
+
import { type Type, type TypeOf } from '../type.mjs';
|
|
6
|
+
import { recursion } from './recursion.mjs';
|
|
7
|
+
import { union } from './union.mjs';
|
|
8
|
+
|
|
9
|
+
describe('recursive', () => {
|
|
10
|
+
// Define JsonPrimitive type
|
|
11
|
+
const JsonPrimitive = union([nullType, number(), string(), boolean()]);
|
|
12
|
+
|
|
13
|
+
type JsonPrimitive = TypeOf<typeof JsonPrimitive>;
|
|
14
|
+
|
|
15
|
+
test('JsonPrimitive.defaultValue', () => {
|
|
16
|
+
expectType<JsonPrimitive, null | boolean | number | string>('=');
|
|
17
|
+
|
|
18
|
+
expect(JsonPrimitive.defaultValue).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Define recursive JsonValue type
|
|
22
|
+
type JsonValue =
|
|
23
|
+
| JsonPrimitive
|
|
24
|
+
| Readonly<{
|
|
25
|
+
[k: string]: JsonValue;
|
|
26
|
+
}>
|
|
27
|
+
| readonly JsonValue[];
|
|
28
|
+
|
|
29
|
+
const JsonValue: Type<JsonValue> = recursion('JsonValue', () =>
|
|
30
|
+
union([
|
|
31
|
+
JsonPrimitive,
|
|
32
|
+
keyValueRecord(string(), JsonValue),
|
|
33
|
+
array(JsonValue),
|
|
34
|
+
]),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
test('JsonValue.defaultValue', () => {
|
|
38
|
+
expectType<TypeOf<typeof JsonValue>, JsonValue>('=');
|
|
39
|
+
|
|
40
|
+
expect(JsonValue.defaultValue).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('JsonValue - primitive values', () => {
|
|
44
|
+
expect(JsonValue.is(true)).toBe(true);
|
|
45
|
+
expect(JsonValue.is(false)).toBe(true);
|
|
46
|
+
expect(JsonValue.is(42)).toBe(true);
|
|
47
|
+
expect(JsonValue.is(3.14)).toBe(true);
|
|
48
|
+
expect(JsonValue.is('hello')).toBe(true);
|
|
49
|
+
expect(JsonValue.is(null)).toBe(true);
|
|
50
|
+
expect(JsonValue.is(undefined)).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('JsonValue - arrays', () => {
|
|
54
|
+
expect(JsonValue.is([])).toBe(true);
|
|
55
|
+
expect(JsonValue.is([1, 2, 3])).toBe(true);
|
|
56
|
+
expect(JsonValue.is(['a', 'b', 'c'])).toBe(true);
|
|
57
|
+
expect(JsonValue.is([true, false, null])).toBe(true);
|
|
58
|
+
expect(JsonValue.is([1, 'a', true, null])).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('JsonValue - objects', () => {
|
|
62
|
+
expect(JsonValue.is({})).toBe(true);
|
|
63
|
+
expect(JsonValue.is({ a: 1 })).toBe(true);
|
|
64
|
+
expect(JsonValue.is({ a: 1, b: 'hello' })).toBe(true);
|
|
65
|
+
expect(JsonValue.is({ a: 1, b: [1, 2, 3] })).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('JsonValue - nested structures', () => {
|
|
69
|
+
const nestedData = {
|
|
70
|
+
name: 'John',
|
|
71
|
+
age: 30,
|
|
72
|
+
active: true,
|
|
73
|
+
address: {
|
|
74
|
+
street: '123 Main St',
|
|
75
|
+
city: 'New York',
|
|
76
|
+
coordinates: {
|
|
77
|
+
lat: 40.7128,
|
|
78
|
+
lon: -74.006,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
hobbies: ['reading', 'coding', 'gaming'],
|
|
82
|
+
scores: [95, 87, 92],
|
|
83
|
+
metadata: null,
|
|
84
|
+
tags: [],
|
|
85
|
+
settings: {
|
|
86
|
+
theme: 'dark',
|
|
87
|
+
notifications: {
|
|
88
|
+
email: true,
|
|
89
|
+
push: false,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
expect(JsonValue.is(nestedData)).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('JsonValue - deeply nested arrays', () => {
|
|
98
|
+
const deepArray = [
|
|
99
|
+
[
|
|
100
|
+
[1, 2, 3],
|
|
101
|
+
[4, 5, 6],
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
[7, 8, 9],
|
|
105
|
+
[10, 11, 12],
|
|
106
|
+
],
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
expect(JsonValue.is(deepArray)).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('JsonValue - mixed nested structure', () => {
|
|
113
|
+
const mixedData = {
|
|
114
|
+
users: [
|
|
115
|
+
{
|
|
116
|
+
id: 1,
|
|
117
|
+
name: 'Alice',
|
|
118
|
+
tasks: [
|
|
119
|
+
{ id: 'task1', completed: true },
|
|
120
|
+
{ id: 'task2', completed: false },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 2,
|
|
125
|
+
name: 'Bob',
|
|
126
|
+
tasks: [],
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
config: {
|
|
130
|
+
version: '1.0.0',
|
|
131
|
+
features: ['feature1', 'feature2'],
|
|
132
|
+
settings: {
|
|
133
|
+
debug: false,
|
|
134
|
+
timeout: 5000,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
expect(JsonValue.is(mixedData)).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('JsonValue - invalid values', () => {
|
|
143
|
+
expect(JsonValue.is(undefined)).toBe(false);
|
|
144
|
+
expect(JsonValue.is(Symbol('test'))).toBe(false);
|
|
145
|
+
expect(JsonValue.is(() => 'function')).toBe(false);
|
|
146
|
+
|
|
147
|
+
// Note: keyValueRecord accepts any object with string keys, including Date/Set/Map
|
|
148
|
+
// This is expected behavior for keyValueRecord, which doesn't distinguish
|
|
149
|
+
// between plain objects and other object instances
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('JsonValue - cast function', () => {
|
|
153
|
+
const result = JsonValue.cast({ a: 1, b: [2, 3], c: { d: 'hello' } });
|
|
154
|
+
expect(result).toStrictEqual({ a: 1, b: [2, 3], c: { d: 'hello' } });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('JsonValue - fill function', () => {
|
|
158
|
+
const result = JsonValue.fill({ a: 1, b: [2, 3] });
|
|
159
|
+
expect(result).toStrictEqual({ a: 1, b: [2, 3] });
|
|
160
|
+
|
|
161
|
+
// Fill with default value for invalid input
|
|
162
|
+
const defaultResult = JsonValue.fill(undefined);
|
|
163
|
+
expect(JsonValue.is(defaultResult)).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Test other recursive structures
|
|
167
|
+
test('Linked list structure', () => {
|
|
168
|
+
type LinkedList<T> = { value: T; next: LinkedList<T> | null };
|
|
169
|
+
|
|
170
|
+
const LinkedListNumber: Type<LinkedList<number>> = recursion(
|
|
171
|
+
'LinkedList<number>',
|
|
172
|
+
() =>
|
|
173
|
+
record({
|
|
174
|
+
value: number(0),
|
|
175
|
+
next: union([nullType, LinkedListNumber]),
|
|
176
|
+
}),
|
|
177
|
+
{ defaultValue: { value: 0, next: null } },
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const list: LinkedList<number> = {
|
|
181
|
+
value: 1,
|
|
182
|
+
next: {
|
|
183
|
+
value: 2,
|
|
184
|
+
next: {
|
|
185
|
+
value: 3,
|
|
186
|
+
next: null,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
expect(LinkedListNumber.is(list)).toBe(true);
|
|
192
|
+
expect(LinkedListNumber.is({ value: 1, next: null })).toBe(true);
|
|
193
|
+
expect(LinkedListNumber.is({ value: 'not a number', next: null })).toBe(
|
|
194
|
+
false,
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('Tree structure', () => {
|
|
199
|
+
type TreeNode<T> = Readonly<{
|
|
200
|
+
value: T;
|
|
201
|
+
children: readonly TreeNode<T>[];
|
|
202
|
+
}>;
|
|
203
|
+
|
|
204
|
+
const TreeNodeString: Type<TreeNode<string>> = recursion(
|
|
205
|
+
'TreeNode<string>',
|
|
206
|
+
() =>
|
|
207
|
+
record({
|
|
208
|
+
value: string(),
|
|
209
|
+
children: array(TreeNodeString),
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const tree: TreeNode<string> = {
|
|
214
|
+
value: 'root',
|
|
215
|
+
children: [
|
|
216
|
+
{
|
|
217
|
+
value: 'branch1',
|
|
218
|
+
children: [
|
|
219
|
+
{ value: 'leaf1', children: [] },
|
|
220
|
+
{ value: 'leaf2', children: [] },
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
value: 'branch2',
|
|
225
|
+
children: [{ value: 'leaf3', children: [] }],
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
expect(TreeNodeString.is(tree)).toBe(true);
|
|
231
|
+
expect(TreeNodeString.is({ value: 'node', children: [] })).toBe(true);
|
|
232
|
+
expect(TreeNodeString.is({ value: 123, children: [] })).toBe(false);
|
|
233
|
+
});
|
|
234
|
+
});
|