ts-fortress 9.1.0 → 9.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.
- package/dist/array/tuple.d.mts.map +1 -1
- package/dist/array/tuple.mjs +4 -0
- package/dist/array/tuple.mjs.map +1 -1
- package/dist/entry-point.mjs +1 -0
- package/dist/entry-point.mjs.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/record/at.d.mts +47 -0
- package/dist/record/at.d.mts.map +1 -0
- package/dist/record/at.mjs +48 -0
- package/dist/record/at.mjs.map +1 -0
- package/dist/record/index.d.mts +1 -0
- package/dist/record/index.d.mts.map +1 -1
- package/dist/record/index.mjs +1 -0
- package/dist/record/index.mjs.map +1 -1
- package/dist/record/merge-records.d.mts.map +1 -1
- package/dist/record/merge-records.mjs +1 -23
- package/dist/record/merge-records.mjs.map +1 -1
- package/dist/type.d.mts +21 -0
- package/dist/type.d.mts.map +1 -1
- package/dist/type.mjs +47 -1
- package/dist/type.mjs.map +1 -1
- package/package.json +4 -1
- package/src/array/tuple.mts +6 -2
- package/src/record/at.mts +120 -0
- package/src/record/at.test.mts +295 -0
- package/src/record/index.mts +1 -0
- package/src/record/merge-records.mts +1 -29
- package/src/type.mts +72 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tuple.d.mts","sourceRoot":"","sources":["../../src/array/tuple.mts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"tuple.d.mts","sourceRoot":"","sources":["../../src/array/tuple.mts"],"names":[],"mappings":"AACA,OAAO,EAA2B,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAU9E,KAAK,QAAQ,CAAC,CAAC,SAAS,SAAS,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,QAAQ,CAAC;KAC1D,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC7B,CAAC,CAAC;AAEH,eAAO,MAAM,KAAK,GAAI,KAAK,CAAC,CAAC,SAAS,SAAS,IAAI,CAAC,OAAO,CAAC,EAAE,EAC5D,OAAO,CAAC,EACR,UAAU,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CACH,KACA,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CA+ElB,CAAC"}
|
package/dist/array/tuple.mjs
CHANGED
|
@@ -52,6 +52,7 @@ const tuple = (types, options) => {
|
|
|
52
52
|
: // eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
53
53
|
types.map((t, i) => t.fill(a[i]));
|
|
54
54
|
const prune = (a) => Arr.map(types, (t, i) => t.prune(a[i]));
|
|
55
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
55
56
|
return {
|
|
56
57
|
typeName,
|
|
57
58
|
get defaultValue() {
|
|
@@ -63,6 +64,9 @@ const tuple = (types, options) => {
|
|
|
63
64
|
is: createIsFn(validate),
|
|
64
65
|
assertIs: createAssertFn(validate),
|
|
65
66
|
cast: createCastFn(validate),
|
|
67
|
+
// Retain the element types so that `at(tupleType, index)` can recover the
|
|
68
|
+
// Type at a given position.
|
|
69
|
+
elementTypes: types,
|
|
66
70
|
};
|
|
67
71
|
};
|
|
68
72
|
|
package/dist/array/tuple.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tuple.mjs","sources":["../../src/array/tuple.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;MAea,KAAK,GAAG,CACnB,KAAQ,EACR,OAIC,KACoB;AAGrB,IAAA,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,OAAO;IAE7C,MAAM,eAAe,GAAG,eAAe,CAAC,MACtC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CACtC;AAED,IAAA,MAAM,QAAQ,GAAwB,CAAC,CAAC,KAAI;QAC1C,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACnB,OAAO,MAAM,CAAC,GAAG,CAAC;AAChB,gBAAA,8BAA8B,CAAC;AAC7B,oBAAA,WAAW,EAAE,CAAC;AACd,oBAAA,YAAY,EAAE,OAAO;oBACrB,QAAQ;AACR,oBAAA,OAAO,EAAE,SAAS;iBACnB,CAAC;AACH,aAAA,CAAC;QACJ;QAEA,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE;YAC7B,OAAO,MAAM,CAAC,GAAG,CAAC;AAChB,gBAAA;AACE,oBAAA,IAAI,EAAE,EAAE;AACR,oBAAA,WAAW,EAAE,CAAC;AACd,oBAAA,YAAY,EAAE,QAAQ;oBACtB,QAAQ;AACR,oBAAA,OAAO,EAAE;AACP,wBAAA,IAAI,EAAE,cAAc;wBACpB,cAAc,EAAE,KAAK,CAAC,MAAM;wBAC5B,YAAY,EAAE,CAAC,CAAC,MAAM;AACvB,qBAAA;AACwB,iBAAA;AAC5B,aAAA,CAAC;QACJ;AAEA,QAAA,MAAM,MAAM,GAA+B,GAAG,CAAC,QAAQ,CAAC,aAAS;YAC/D,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE;gBAChE,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;AAEhC,gBAAA,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;oBACrB,OAAO,8BAA8B,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC;gBACzD;YACF;AACF,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE;AAC1B,YAAA,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QAC3B;;AAGA,QAAA,OAAO,MAAM,CAAC,EAAE,CAAC,CAAM,CAAC;AAC1B,IAAA,CAAC;AAED,IAAA,MAAM,IAAI,GAAoB,CAAC,CAAC,KAC9B,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;UACV,eAAe;AACjB;YACG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAiB;AAExD,IAAA,MAAM,KAAK,GAAG,CAAC,CAAI,KAAQ,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC
|
|
1
|
+
{"version":3,"file":"tuple.mjs","sources":["../../src/array/tuple.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;MAea,KAAK,GAAG,CACnB,KAAQ,EACR,OAIC,KACoB;AAGrB,IAAA,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,OAAO;IAE7C,MAAM,eAAe,GAAG,eAAe,CAAC,MACtC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CACtC;AAED,IAAA,MAAM,QAAQ,GAAwB,CAAC,CAAC,KAAI;QAC1C,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACnB,OAAO,MAAM,CAAC,GAAG,CAAC;AAChB,gBAAA,8BAA8B,CAAC;AAC7B,oBAAA,WAAW,EAAE,CAAC;AACd,oBAAA,YAAY,EAAE,OAAO;oBACrB,QAAQ;AACR,oBAAA,OAAO,EAAE,SAAS;iBACnB,CAAC;AACH,aAAA,CAAC;QACJ;QAEA,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE;YAC7B,OAAO,MAAM,CAAC,GAAG,CAAC;AAChB,gBAAA;AACE,oBAAA,IAAI,EAAE,EAAE;AACR,oBAAA,WAAW,EAAE,CAAC;AACd,oBAAA,YAAY,EAAE,QAAQ;oBACtB,QAAQ;AACR,oBAAA,OAAO,EAAE;AACP,wBAAA,IAAI,EAAE,cAAc;wBACpB,cAAc,EAAE,KAAK,CAAC,MAAM;wBAC5B,YAAY,EAAE,CAAC,CAAC,MAAM;AACvB,qBAAA;AACwB,iBAAA;AAC5B,aAAA,CAAC;QACJ;AAEA,QAAA,MAAM,MAAM,GAA+B,GAAG,CAAC,QAAQ,CAAC,aAAS;YAC/D,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE;gBAChE,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;AAEhC,gBAAA,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;oBACrB,OAAO,8BAA8B,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC;gBACzD;YACF;AACF,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE;AAC1B,YAAA,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QAC3B;;AAGA,QAAA,OAAO,MAAM,CAAC,EAAE,CAAC,CAAM,CAAC;AAC1B,IAAA,CAAC;AAED,IAAA,MAAM,IAAI,GAAoB,CAAC,CAAC,KAC9B,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;UACV,eAAe;AACjB;YACG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAiB;AAExD,IAAA,MAAM,KAAK,GAAG,CAAC,CAAI,KAAQ,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;IAGlE,OAAO;QACL,QAAQ;AACR,QAAA,IAAI,YAAY,GAAA;YACd,OAAO,eAAe,EAAE;QAC1B,CAAC;QACD,IAAI;QACJ,KAAK;QACL,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;;;AAG5B,QAAA,YAAY,EAAE,KAAK;KAC8B;AACrD;;;;"}
|
package/dist/entry-point.mjs
CHANGED
|
@@ -60,6 +60,7 @@ export { number } from './primitives/number.mjs';
|
|
|
60
60
|
export { string } from './primitives/string.mjs';
|
|
61
61
|
export { symbol } from './primitives/symbol.mjs';
|
|
62
62
|
export { undefinedType } from './primitives/undefined.mjs';
|
|
63
|
+
export { at } from './record/at.mjs';
|
|
63
64
|
export { keyValueRecord } from './record/key-value-record.mjs';
|
|
64
65
|
export { keyof } from './record/keyof.mjs';
|
|
65
66
|
export { mergeRecords } from './record/merge-records.mjs';
|
package/dist/entry-point.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-point.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"entry-point.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.mjs
CHANGED
|
@@ -59,6 +59,7 @@ export { number } from './primitives/number.mjs';
|
|
|
59
59
|
export { string } from './primitives/string.mjs';
|
|
60
60
|
export { symbol } from './primitives/symbol.mjs';
|
|
61
61
|
export { undefinedType } from './primitives/undefined.mjs';
|
|
62
|
+
export { at } from './record/at.mjs';
|
|
62
63
|
export { keyValueRecord } from './record/key-value-record.mjs';
|
|
63
64
|
export { keyof } from './record/keyof.mjs';
|
|
64
65
|
export { mergeRecords } from './record/merge-records.mjs';
|
|
@@ -69,7 +70,7 @@ export { pick } from './record/pick.mjs';
|
|
|
69
70
|
export { record, strictRecord } from './record/record.mjs';
|
|
70
71
|
export { required } from './record/required.mjs';
|
|
71
72
|
export { valueof } from './record/valueof.mjs';
|
|
72
|
-
export { flattenShapeStructure, getShape, hasRecordInternals } from './type.mjs';
|
|
73
|
+
export { expandShapeStructure, flattenShapeStructure, getShape, hasRecordInternals, hasTupleInternals } from './type.mjs';
|
|
73
74
|
export { createAssertFn } from './utils/create-assert-fn.mjs';
|
|
74
75
|
export { createCastFn } from './utils/create-cast-fn.mjs';
|
|
75
76
|
export { createIsFn } from './utils/create-is-fn.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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type IndexOfTuple, type UnknownRecord } from 'ts-type-forge';
|
|
2
|
+
import { type Type } from '../type.mjs';
|
|
3
|
+
/**
|
|
4
|
+
* Extracts the {@link Type} stored at a single key of a record type.
|
|
5
|
+
*
|
|
6
|
+
* The first argument must be a record type (i.e. it must extend
|
|
7
|
+
* `Type<UnknownRecord>`, like {@link pick} and {@link keyof}), and the second
|
|
8
|
+
* argument is constrained to the keys declared on that record.
|
|
9
|
+
*
|
|
10
|
+
* The record type may be a simple record, an intersection (e.g. the result of
|
|
11
|
+
* {@link mergeRecords}), a union, a recursive type, or any combination of
|
|
12
|
+
* these — the underlying shape is expanded and, when a key resolves to
|
|
13
|
+
* multiple member types (as in a union), the resulting types are combined with
|
|
14
|
+
* {@link union}.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import * as t from 'ts-fortress';
|
|
19
|
+
*
|
|
20
|
+
* const ymd = t.record({
|
|
21
|
+
* year: t.number(1900),
|
|
22
|
+
* month: t.number(1),
|
|
23
|
+
* date: t.number(1),
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* const year = t.at(ymd, 'year'); // Type<number>
|
|
27
|
+
*
|
|
28
|
+
* year.is(2000); // true
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function at<const R extends UnknownRecord, const K extends keyof R & string>(recordType: Type<R>, key: K): Type<R[K]>;
|
|
32
|
+
/**
|
|
33
|
+
* Extracts the {@link Type} stored at a single index of a tuple type.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import * as t from 'ts-fortress';
|
|
38
|
+
*
|
|
39
|
+
* const tup = t.tuple([t.number(), t.string(), t.boolean()]);
|
|
40
|
+
*
|
|
41
|
+
* const second = t.at(tup, 1); // Type<string>
|
|
42
|
+
*
|
|
43
|
+
* second.is('hello'); // true
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function at<const T extends readonly unknown[], const N extends IndexOfTuple<T>>(tupleType: Type<T>, index: N): Type<T[N]>;
|
|
47
|
+
//# sourceMappingURL=at.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"at.d.mts","sourceRoot":"","sources":["../../src/record/at.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAGtE,OAAO,EAIL,KAAK,IAAI,EACV,MAAM,aAAa,CAAC;AAGrB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,EAAE,CAChB,KAAK,CAAC,CAAC,SAAS,aAAa,EAC7B,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAChC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,EAAE,CAChB,KAAK,CAAC,CAAC,SAAS,SAAS,OAAO,EAAE,EAClC,KAAK,CAAC,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,EAC/B,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Arr } from 'ts-data-forge';
|
|
2
|
+
import { hasTupleInternals, hasRecordInternals, expandShapeStructure } from '../type.mjs';
|
|
3
|
+
import { union } from '../compose/union.mjs';
|
|
4
|
+
import '@sindresorhus/is';
|
|
5
|
+
import '../other-types/unknown.mjs';
|
|
6
|
+
import { undefinedType } from '../primitives/undefined.mjs';
|
|
7
|
+
import { isOptionalProperty } from './optional.mjs';
|
|
8
|
+
|
|
9
|
+
function at(type, keyOrIndex) {
|
|
10
|
+
// --- Tuple index access ---
|
|
11
|
+
if (typeof keyOrIndex === 'number') {
|
|
12
|
+
if (!hasTupleInternals(type)) {
|
|
13
|
+
throw new Error(`Expected a tuple type but received: ${type.typeName}`);
|
|
14
|
+
}
|
|
15
|
+
const elementType = type.elementTypes[keyOrIndex];
|
|
16
|
+
if (elementType === undefined) {
|
|
17
|
+
throw new Error(`Index ${keyOrIndex} is out of range for tuple type: ${type.typeName}`);
|
|
18
|
+
}
|
|
19
|
+
return elementType;
|
|
20
|
+
}
|
|
21
|
+
// --- Record key access ---
|
|
22
|
+
if (!hasRecordInternals(type)) {
|
|
23
|
+
throw new Error(`Expected a record type but received: ${type.typeName}`);
|
|
24
|
+
}
|
|
25
|
+
const shapes = expandShapeStructure(type.shapeStructure);
|
|
26
|
+
const memberTypes = shapes.flatMap((shape) => {
|
|
27
|
+
const memberType = shape[keyOrIndex];
|
|
28
|
+
return memberType === undefined ? [] : [memberType];
|
|
29
|
+
});
|
|
30
|
+
// If the key is optional in any member, the accessed value may be undefined,
|
|
31
|
+
// matching the `T | undefined` produced by `R[K]` for optional keys.
|
|
32
|
+
const hasOptionalMember = memberTypes.some(isOptionalProperty);
|
|
33
|
+
const collected = hasOptionalMember
|
|
34
|
+
? [...memberTypes, undefinedType]
|
|
35
|
+
: memberTypes;
|
|
36
|
+
if (Arr.isArrayAtLeastLength(collected, 2)) {
|
|
37
|
+
return union(collected, {
|
|
38
|
+
typeName: `${type.typeName}[${JSON.stringify(keyOrIndex)}]`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (Arr.isNonEmpty(collected)) {
|
|
42
|
+
return collected[0];
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Key ${JSON.stringify(keyOrIndex)} does not exist on record type: ${type.typeName}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { at };
|
|
48
|
+
//# sourceMappingURL=at.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"at.mjs","sources":["../../src/record/at.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;AAgEM,SAAU,EAAE,CAChB,IAAoD,EACpD,UAA2B,EAAA;;AAG3B,IAAA,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE;AAClC,QAAA,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE;YAC5B,MAAM,IAAI,KAAK,CAAC,CAAA,oCAAA,EAAuC,IAAI,CAAC,QAAQ,CAAA,CAAE,CAAC;QACzE;QAEA,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;AAEjD,QAAA,IAAI,WAAW,KAAK,SAAS,EAAE;YAC7B,MAAM,IAAI,KAAK,CACb,CAAA,MAAA,EAAS,UAAU,CAAA,iCAAA,EAAoC,IAAI,CAAC,QAAQ,CAAA,CAAE,CACvE;QACH;AAEA,QAAA,OAAO,WAAW;IACpB;;AAGA,IAAA,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE;QAC7B,MAAM,IAAI,KAAK,CAAC,CAAA,qCAAA,EAAwC,IAAI,CAAC,QAAQ,CAAA,CAAE,CAAC;IAC1E;IAEA,MAAM,MAAM,GAAG,oBAAoB,CAAC,IAAI,CAAC,cAAc,CAAC;IAExD,MAAM,WAAW,GAA6B,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,KAAI;AACrE,QAAA,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;AAEpC,QAAA,OAAO,UAAU,KAAK,SAAS,GAAG,EAAE,GAAG,CAAC,UAAU,CAAC;AACrD,IAAA,CAAC,CAAC;;;IAIF,MAAM,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,kBAAkB,CAAC;IAE9D,MAAM,SAAS,GAA6B;AAC1C,UAAG,CAAC,GAAG,WAAW,EAAE,aAAa;UAC/B,WAAW;IAEf,IAAI,GAAG,CAAC,oBAAoB,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE;QAC1C,OAAO,KAAK,CAAC,SAAS,EAAE;AACtB,YAAA,QAAQ,EAAE,CAAA,EAAG,IAAI,CAAC,QAAQ,CAAA,CAAA,EAAI,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA,CAAA,CAAG;AAC5D,SAAA,CAAC;IACJ;AAEA,IAAA,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE;AAC7B,QAAA,OAAO,SAAS,CAAC,CAAC,CAAC;IACrB;AAEA,IAAA,MAAM,IAAI,KAAK,CACb,CAAA,IAAA,EAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,mCAAmC,IAAI,CAAC,QAAQ,CAAA,CAAE,CACpF;AACH;;;;"}
|
package/dist/record/index.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/record/index.mts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/record/index.mts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
|
package/dist/record/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":";;;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-records.d.mts","sourceRoot":"","sources":["../../src/record/merge-records.mts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,KAAK,oBAAoB,
|
|
1
|
+
{"version":3,"file":"merge-records.d.mts","sourceRoot":"","sources":["../../src/record/merge-records.mts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,KAAK,oBAAoB,EAEzB,KAAK,IAAI,EAIV,MAAM,aAAa,CAAC;AAMrB,eAAO,MAAM,YAAY,GACvB,KAAK,CAAC,KAAK,SAAS,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,EAEtD,aAAa,KAAK,EAClB,UAAU,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,oBAAoB,CAAC;CACtC,CAAC,CACH,KACA,gBAAgB,CAAC,KAAK,CA6DxB,CAAC;AAEF,KAAK,gBAAgB,CAAC,KAAK,SAAS,SAAS,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,IAAI,CACxE,gBAAgB,CAAC,KAAK,CAAC,CACxB,CAAC;AAEF,+EAA+E;AAC/E,KAAK,gBAAgB,CAAC,KAAK,SAAS,SAAS,IAAI,CAAC,aAAa,CAAC,EAAE,IAChE,mBAAmB,CAAC,YAAY,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAE5D,oGAAoG;AACpG,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,SAAS,aAAa,GACjD,QAAQ,CAAC;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC,GAClC,CAAC,CAAC;AAEN,KAAK,eAAe,CAAC,KAAK,SAAS,SAAS,OAAO,EAAE,IACnD,KAAK,SAAS,SAAS,CAAC,MAAM,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,GAC9C,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,GACvD,SAAS,EAAE,CAAC;AAElB,KAAK,YAAY,CAAC,CAAC,IACjB,CAAC,SAAS,QAAQ,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Arr, Obj } from 'ts-data-forge';
|
|
2
|
-
import { hasRecordInternals } from '../type.mjs';
|
|
2
|
+
import { hasRecordInternals, expandShapeStructure } from '../type.mjs';
|
|
3
3
|
import { toIntersectionString } from '../utils/to-union-string.mjs';
|
|
4
4
|
import { union } from '../compose/union.mjs';
|
|
5
5
|
import '@sindresorhus/is';
|
|
@@ -44,28 +44,6 @@ const mergeRecords = (recordTypes, options) => {
|
|
|
44
44
|
typeName: typeNameFilled,
|
|
45
45
|
});
|
|
46
46
|
};
|
|
47
|
-
/**
|
|
48
|
-
* Expands a ShapeStructure into all possible simple shapes.
|
|
49
|
-
* For union structures, returns an array of all variant shapes.
|
|
50
|
-
* For intersection structures, computes cartesian product of all parts.
|
|
51
|
-
*/
|
|
52
|
-
const expandShapeStructure = (structure) => {
|
|
53
|
-
switch (structure.kind) {
|
|
54
|
-
case 'simple': {
|
|
55
|
-
return [structure.shape];
|
|
56
|
-
}
|
|
57
|
-
case 'union': {
|
|
58
|
-
// Union: flatten all variants
|
|
59
|
-
return structure.variants.flatMap(expandShapeStructure);
|
|
60
|
-
}
|
|
61
|
-
case 'intersection': {
|
|
62
|
-
// Intersection: compute cartesian product of all parts
|
|
63
|
-
const expandedParts = structure.parts.map(expandShapeStructure);
|
|
64
|
-
const combinations = Arr.cartesianProduct(expandedParts);
|
|
65
|
-
return Arr.map(combinations, (shapes) => Obj.merge(...shapes));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
47
|
const deriveStrictestExcessProperty = (types) => types.some((t) => t.excessProperty === 'reject') ? 'reject' : 'allow';
|
|
70
48
|
|
|
71
49
|
export { mergeRecords };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-records.mjs","sources":["../../src/record/merge-records.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"merge-records.mjs","sources":["../../src/record/merge-records.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;AAmBA,MAAM,0BAA0B,GAAG,MAAM;MAE5B,YAAY,GAAG,CAG1B,WAAkB,EAClB,OAKC,KAC0B;IAC3B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,kBAAkB,CAAC,EAAE;AAC1C,QAAA,MAAM,IAAI,KAAK,CACb,uEAAuE,CACxE;;AAGH,IAAA,MAAM,cAAc,GAClB,OAAO,EAAE,QAAQ;AACjB,QAAA,CAAA,CAAA,EAAI,oBAAoB,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG;;IAGjE,MAAM,qBAAqB,GAAG,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,KACnD,oBAAoB,CAAC,CAAC,CAAC,cAAc,CAAC,CACvC;;;IAID,MAAM,qBAAqB,GAAG,qBAAqB,CAAC,MAAM,CACxD,CAAC,GAAG,EAAE,MAAM,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EACpC,CAAC,CACF;AAED,IAAA,IAAI,qBAAqB,GAAG,0BAA0B,EAAE;AACtD,QAAA,MAAM,IAAI,KAAK,CACb,6BAA6B,qBAAqB,CAAA,yCAAA,EAA4C,0BAA0B,CAAA,EAAA,CAAI;AAC1H,YAAA,iIAAiI,CACpI;;;;;IAMH,MAAM,eAAe,GAAG,GAAG,CAAC,gBAAgB,CAAC,qBAAqB,CAAC;;IAGnE,MAAM,YAAY,GAAG,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,MAAM,KACnD,GAAG,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,CACrB;IAED,MAAM,cAAc,GAClB,OAAO,EAAE,cAAc,IAAI,6BAA6B,CAAC,WAAW,CAAC;;IAGvE,IAAI,GAAG,CAAC,eAAe,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE;;AAExC,QAAA,OAAO,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE;AAC7B,YAAA,QAAQ,EAAE,cAAc;YACxB,cAAc;AACf,SAAA,CAA4B;;;IAI/B,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,KAAK,KAC3C,MAAM,CAAC,KAAK,EAAE,EAAE,cAAc,EAAE,CAAC,CAClC;;IAGD,OAAO,KAAK,CAAC,QAA8C,EAAE;AAC3D,QAAA,QAAQ,EAAE,cAAc;AACzB,KAAA,CAA4B;AAC/B;AAuBA,MAAM,6BAA6B,GAAG,CACpC,KAAqC,KAErC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,cAAc,KAAK,QAAQ,CAAC,GAAG,QAAQ,GAAG,OAAO;;;;"}
|
package/dist/type.d.mts
CHANGED
|
@@ -77,10 +77,31 @@ export type RecordTypeInternals = Readonly<{
|
|
|
77
77
|
shapeStructure: ShapeStructure;
|
|
78
78
|
excessProperty: ExcessPropertyOption;
|
|
79
79
|
}>;
|
|
80
|
+
/**
|
|
81
|
+
* @internal Internal properties attached to tuple types so that the element
|
|
82
|
+
* type at a given index can be recovered (e.g. by {@link at}).
|
|
83
|
+
*/
|
|
84
|
+
export type TupleTypeInternals = Readonly<{
|
|
85
|
+
elementTypes: readonly Type<unknown>[];
|
|
86
|
+
}>;
|
|
80
87
|
/** @internal Helper to flatten ShapeStructure to a simple shape if possible */
|
|
81
88
|
export declare const flattenShapeStructure: (structure: ShapeStructure) => UnknownShape | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* @internal Expands a ShapeStructure into all possible simple shapes.
|
|
91
|
+
* For union structures, returns an array of all variant shapes.
|
|
92
|
+
* For intersection structures, computes the cartesian product of all parts.
|
|
93
|
+
*
|
|
94
|
+
* Unlike {@link flattenShapeStructure}, this never returns `undefined`: a
|
|
95
|
+
* union of records is represented as the list of its concrete member shapes.
|
|
96
|
+
*
|
|
97
|
+
* @throws If the intersection expansion would exceed
|
|
98
|
+
* {@link EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS} concrete shapes.
|
|
99
|
+
*/
|
|
100
|
+
export declare const expandShapeStructure: (structure: ShapeStructure) => readonly UnknownShape[];
|
|
82
101
|
/** @internal Backward compatibility: simple shape accessor */
|
|
83
102
|
export declare const getShape: (internals: RecordTypeInternals) => UnknownShape;
|
|
84
103
|
/** @internal Runtime check for record type internals. */
|
|
85
104
|
export declare const hasRecordInternals: <T extends Type<unknown>>(t: T) => t is T & RecordTypeInternals;
|
|
105
|
+
/** @internal Runtime check for tuple type internals. */
|
|
106
|
+
export declare const hasTupleInternals: <T extends Type<unknown>>(t: T) => t is T & TupleTypeInternals;
|
|
86
107
|
//# sourceMappingURL=type.d.mts.map
|
package/dist/type.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type.d.mts","sourceRoot":"","sources":["../src/type.mts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,QAAQ,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,CAAC,CAAC;IAChB,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,QAAQ,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;IACxB,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;IAExB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAK,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IAChC,QAAQ,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC,CAAC;IAEhE,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,IAAI,CAAC;CACjB,CAAC,CAAC;AAEH,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC;AAEhE;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEtD,gBAAgB;AAChB,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AAEjE,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG,QAAQ,CACjC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;CAAE,CAC7D,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,oBAAoB,CAAC;CACtC,CAAC,CAAC;AAEH,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,GAChC,WAAW,cAAc,KACxB,YAAY,GAAG,SAsBjB,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,QAAQ,GAAI,WAAW,mBAAmB,KAAG,YAUzD,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,EACxD,GAAG,CAAC,KACH,CAAC,IAAI,CAAC,GAAG,mBAAgD,CAAC"}
|
|
1
|
+
{"version":3,"file":"type.d.mts","sourceRoot":"","sources":["../src/type.mts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,QAAQ,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,CAAC,CAAC;IAChB,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,QAAQ,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;IACxB,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;IAExB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAK,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IAChC,QAAQ,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC,CAAC;IAEhE,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,IAAI,CAAC;CACjB,CAAC,CAAC;AAEH,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC;AAEhE;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEtD,gBAAgB;AAChB,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AAEjE,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG,QAAQ,CACjC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;CAAE,CAC7D,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,oBAAoB,CAAC;CACtC,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,YAAY,EAAE,SAAS,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;CACxC,CAAC,CAAC;AAEH,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,GAChC,WAAW,cAAc,KACxB,YAAY,GAAG,SAsBjB,CAAC;AAWF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAC/B,WAAW,cAAc,KACxB,SAAS,YAAY,EAgCvB,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,QAAQ,GAAI,WAAW,mBAAmB,KAAG,YAUzD,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,EACxD,GAAG,CAAC,KACH,CAAC,IAAI,CAAC,GAAG,mBAAgD,CAAC;AAS7D,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,GAAI,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,EACvD,GAAG,CAAC,KACH,CAAC,IAAI,CAAC,GAAG,kBAA8C,CAAC"}
|
package/dist/type.mjs
CHANGED
|
@@ -22,6 +22,49 @@ const flattenShapeStructure = (structure) => {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* @internal Upper bound on the number of concrete shapes that
|
|
27
|
+
* {@link expandShapeStructure} may produce. Expanding an intersection of unions
|
|
28
|
+
* multiplies the variant counts, so a deeply nested structure can grow
|
|
29
|
+
* exponentially; this bound prevents excessive CPU/memory usage (mirrors the
|
|
30
|
+
* guard in `mergeRecords`).
|
|
31
|
+
*/
|
|
32
|
+
const EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS = 10_000;
|
|
33
|
+
/**
|
|
34
|
+
* @internal Expands a ShapeStructure into all possible simple shapes.
|
|
35
|
+
* For union structures, returns an array of all variant shapes.
|
|
36
|
+
* For intersection structures, computes the cartesian product of all parts.
|
|
37
|
+
*
|
|
38
|
+
* Unlike {@link flattenShapeStructure}, this never returns `undefined`: a
|
|
39
|
+
* union of records is represented as the list of its concrete member shapes.
|
|
40
|
+
*
|
|
41
|
+
* @throws If the intersection expansion would exceed
|
|
42
|
+
* {@link EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS} concrete shapes.
|
|
43
|
+
*/
|
|
44
|
+
const expandShapeStructure = (structure) => {
|
|
45
|
+
switch (structure.kind) {
|
|
46
|
+
case 'simple': {
|
|
47
|
+
return [structure.shape];
|
|
48
|
+
}
|
|
49
|
+
case 'union': {
|
|
50
|
+
// Union: flatten all variants
|
|
51
|
+
return structure.variants.flatMap(expandShapeStructure);
|
|
52
|
+
}
|
|
53
|
+
case 'intersection': {
|
|
54
|
+
// Intersection: compute cartesian product of all parts
|
|
55
|
+
const expandedParts = structure.parts.map(expandShapeStructure);
|
|
56
|
+
// The cartesian product size is the product of the per-part variant
|
|
57
|
+
// counts, which grows multiplicatively. Bound it before materializing to
|
|
58
|
+
// avoid excessive CPU/memory usage.
|
|
59
|
+
const estimatedVariantCount = expandedParts.reduce((acc, shapes) => acc * shapes.length, 1);
|
|
60
|
+
if (estimatedVariantCount > EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS) {
|
|
61
|
+
throw new Error(`Expanding this record type would create ${estimatedVariantCount} variants, exceeding the limit of ${EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS}. This could lead to excessive memory or CPU usage. Consider simplifying the record types or reducing union/intersection nesting.`);
|
|
62
|
+
}
|
|
63
|
+
const combinations = Arr.cartesianProduct(expandedParts);
|
|
64
|
+
return Arr.map(combinations, (shapes) => Obj.merge(...shapes));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
25
68
|
/** @internal Backward compatibility: simple shape accessor */
|
|
26
69
|
const getShape = (internals) => {
|
|
27
70
|
const flattened = flattenShapeStructure(internals.shapeStructure);
|
|
@@ -37,6 +80,9 @@ const hasRecordInternalsImpl = (t) => isRecord(t) &&
|
|
|
37
80
|
isValidShapeStructure(t.shapeStructure) &&
|
|
38
81
|
hasKey(t, 'excessProperty') &&
|
|
39
82
|
(t.excessProperty === 'allow' || t.excessProperty === 'reject');
|
|
83
|
+
/** @internal Runtime check for tuple type internals. */
|
|
84
|
+
const hasTupleInternals = (t) => hasTupleInternalsImpl(t);
|
|
85
|
+
const hasTupleInternalsImpl = (t) => isRecord(t) && hasKey(t, 'elementTypes') && Arr.isArray(t.elementTypes);
|
|
40
86
|
const isValidShapeStructure = (s) => {
|
|
41
87
|
if (!isRecord(s) || !hasKey(s, 'kind'))
|
|
42
88
|
return false;
|
|
@@ -56,5 +102,5 @@ const isValidShapeStructure = (s) => {
|
|
|
56
102
|
return false;
|
|
57
103
|
};
|
|
58
104
|
|
|
59
|
-
export { flattenShapeStructure, getShape, hasRecordInternals };
|
|
105
|
+
export { expandShapeStructure, flattenShapeStructure, getShape, hasRecordInternals, hasTupleInternals };
|
|
60
106
|
//# sourceMappingURL=type.mjs.map
|
package/dist/type.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type.mjs","sources":["../src/type.mts"],"sourcesContent":[null],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"type.mjs","sources":["../src/type.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-fortress",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript"
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
"check:ext": "tsx ./scripts/cmd/check-ext.mts",
|
|
36
36
|
"check-all": "tsx ./scripts/cmd/check-all.mts",
|
|
37
37
|
"codemod": "run-s codemod:uncommitted:as-const fmt codemod:uncommitted:readonly fmt",
|
|
38
|
+
"codemod:diff": "run-s codemod:diff:as-const fmt codemod:diff:readonly fmt",
|
|
39
|
+
"codemod:diff:as-const": "append-as-const --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
|
|
40
|
+
"codemod:diff:readonly": "convert-to-readonly --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
|
|
38
41
|
"codemod:full": "run-s codemod:full:as-const fmt codemod:full:readonly fmt",
|
|
39
42
|
"codemod:full:as-const": "append-as-const '{src,scripts,samples,test}/**/*.{mts,tsx}'",
|
|
40
43
|
"codemod:full:readonly": "convert-to-readonly '{src,scripts,samples,test}/**/*.{mts,tsx}'",
|
package/src/array/tuple.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Arr, memoizeFunction, Result } from 'ts-data-forge';
|
|
2
|
-
import { type Type, type TypeOf } from '../type.mjs';
|
|
2
|
+
import { type TupleTypeInternals, type Type, type TypeOf } from '../type.mjs';
|
|
3
3
|
import {
|
|
4
4
|
createAssertFn,
|
|
5
5
|
createCastFn,
|
|
@@ -83,6 +83,7 @@ export const tuple = <const A extends readonly Type<unknown>[]>(
|
|
|
83
83
|
|
|
84
84
|
const prune = (a: T): T => Arr.map(types, (t, i) => t.prune(a[i]));
|
|
85
85
|
|
|
86
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
86
87
|
return {
|
|
87
88
|
typeName,
|
|
88
89
|
get defaultValue() {
|
|
@@ -94,5 +95,8 @@ export const tuple = <const A extends readonly Type<unknown>[]>(
|
|
|
94
95
|
is: createIsFn(validate),
|
|
95
96
|
assertIs: createAssertFn(validate),
|
|
96
97
|
cast: createCastFn(validate),
|
|
97
|
-
|
|
98
|
+
// Retain the element types so that `at(tupleType, index)` can recover the
|
|
99
|
+
// Type at a given position.
|
|
100
|
+
elementTypes: types,
|
|
101
|
+
} satisfies Type<T> & TupleTypeInternals as Type<T>;
|
|
98
102
|
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Arr } from 'ts-data-forge';
|
|
2
|
+
import { type IndexOfTuple, type UnknownRecord } from 'ts-type-forge';
|
|
3
|
+
import { union } from '../compose/index.mjs';
|
|
4
|
+
import { undefinedType } from '../primitives/index.mjs';
|
|
5
|
+
import {
|
|
6
|
+
expandShapeStructure,
|
|
7
|
+
hasRecordInternals,
|
|
8
|
+
hasTupleInternals,
|
|
9
|
+
type Type,
|
|
10
|
+
} from '../type.mjs';
|
|
11
|
+
import { isOptionalProperty } from './optional.mjs';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extracts the {@link Type} stored at a single key of a record type.
|
|
15
|
+
*
|
|
16
|
+
* The first argument must be a record type (i.e. it must extend
|
|
17
|
+
* `Type<UnknownRecord>`, like {@link pick} and {@link keyof}), and the second
|
|
18
|
+
* argument is constrained to the keys declared on that record.
|
|
19
|
+
*
|
|
20
|
+
* The record type may be a simple record, an intersection (e.g. the result of
|
|
21
|
+
* {@link mergeRecords}), a union, a recursive type, or any combination of
|
|
22
|
+
* these — the underlying shape is expanded and, when a key resolves to
|
|
23
|
+
* multiple member types (as in a union), the resulting types are combined with
|
|
24
|
+
* {@link union}.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* import * as t from 'ts-fortress';
|
|
29
|
+
*
|
|
30
|
+
* const ymd = t.record({
|
|
31
|
+
* year: t.number(1900),
|
|
32
|
+
* month: t.number(1),
|
|
33
|
+
* date: t.number(1),
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* const year = t.at(ymd, 'year'); // Type<number>
|
|
37
|
+
*
|
|
38
|
+
* year.is(2000); // true
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function at<
|
|
42
|
+
const R extends UnknownRecord,
|
|
43
|
+
const K extends keyof R & string,
|
|
44
|
+
>(recordType: Type<R>, key: K): Type<R[K]>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extracts the {@link Type} stored at a single index of a tuple type.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* import * as t from 'ts-fortress';
|
|
52
|
+
*
|
|
53
|
+
* const tup = t.tuple([t.number(), t.string(), t.boolean()]);
|
|
54
|
+
*
|
|
55
|
+
* const second = t.at(tup, 1); // Type<string>
|
|
56
|
+
*
|
|
57
|
+
* second.is('hello'); // true
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function at<
|
|
61
|
+
const T extends readonly unknown[],
|
|
62
|
+
const N extends IndexOfTuple<T>,
|
|
63
|
+
>(tupleType: Type<T>, index: N): Type<T[N]>;
|
|
64
|
+
|
|
65
|
+
export function at(
|
|
66
|
+
type: Type<UnknownRecord> | Type<readonly unknown[]>,
|
|
67
|
+
keyOrIndex: string | number,
|
|
68
|
+
): Type<unknown> {
|
|
69
|
+
// --- Tuple index access ---
|
|
70
|
+
if (typeof keyOrIndex === 'number') {
|
|
71
|
+
if (!hasTupleInternals(type)) {
|
|
72
|
+
throw new Error(`Expected a tuple type but received: ${type.typeName}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const elementType = type.elementTypes[keyOrIndex];
|
|
76
|
+
|
|
77
|
+
if (elementType === undefined) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Index ${keyOrIndex} is out of range for tuple type: ${type.typeName}`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return elementType;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- Record key access ---
|
|
87
|
+
if (!hasRecordInternals(type)) {
|
|
88
|
+
throw new Error(`Expected a record type but received: ${type.typeName}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const shapes = expandShapeStructure(type.shapeStructure);
|
|
92
|
+
|
|
93
|
+
const memberTypes: readonly Type<unknown>[] = shapes.flatMap((shape) => {
|
|
94
|
+
const memberType = shape[keyOrIndex];
|
|
95
|
+
|
|
96
|
+
return memberType === undefined ? [] : [memberType];
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// If the key is optional in any member, the accessed value may be undefined,
|
|
100
|
+
// matching the `T | undefined` produced by `R[K]` for optional keys.
|
|
101
|
+
const hasOptionalMember = memberTypes.some(isOptionalProperty);
|
|
102
|
+
|
|
103
|
+
const collected: readonly Type<unknown>[] = hasOptionalMember
|
|
104
|
+
? ([...memberTypes, undefinedType] as const)
|
|
105
|
+
: memberTypes;
|
|
106
|
+
|
|
107
|
+
if (Arr.isArrayAtLeastLength(collected, 2)) {
|
|
108
|
+
return union(collected, {
|
|
109
|
+
typeName: `${type.typeName}[${JSON.stringify(keyOrIndex)}]`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (Arr.isNonEmpty(collected)) {
|
|
114
|
+
return collected[0];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Key ${JSON.stringify(keyOrIndex)} does not exist on record type: ${type.typeName}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { expectType } from 'ts-data-forge';
|
|
2
|
+
import { array, tuple } from '../array/index.mjs';
|
|
3
|
+
import { union } from '../compose/index.mjs';
|
|
4
|
+
import { literal, recursion } from '../other-types/index.mjs';
|
|
5
|
+
import { boolean, nullType, number, string } from '../primitives/index.mjs';
|
|
6
|
+
import {
|
|
7
|
+
expandShapeStructure,
|
|
8
|
+
type ShapeStructure,
|
|
9
|
+
type Type,
|
|
10
|
+
type TypeOf,
|
|
11
|
+
} from '../type.mjs';
|
|
12
|
+
import { at } from './at.mjs';
|
|
13
|
+
import { mergeRecords } from './merge-records.mjs';
|
|
14
|
+
import { optional } from './optional.mjs';
|
|
15
|
+
import { record } from './record.mjs';
|
|
16
|
+
|
|
17
|
+
describe(at, () => {
|
|
18
|
+
describe('record key access', () => {
|
|
19
|
+
const ymd = record({
|
|
20
|
+
year: number(1900),
|
|
21
|
+
month: number(1),
|
|
22
|
+
date: number(1),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('extracts the Type at a key (spec example)', () => {
|
|
26
|
+
const year = at(ymd, 'year');
|
|
27
|
+
|
|
28
|
+
expectType<typeof year, Type<number>>('=');
|
|
29
|
+
|
|
30
|
+
assert.isTrue(year.is(2000));
|
|
31
|
+
|
|
32
|
+
assert.isFalse(year.is('2000'));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('returns the exact stored Type (defaultValue preserved)', () => {
|
|
36
|
+
const year = at(ymd, 'year');
|
|
37
|
+
|
|
38
|
+
expect(year.defaultValue).toBe(1900);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('key argument is constrained to declared keys', () => {
|
|
42
|
+
// @ts-expect-error "nope" is not a key of ymd
|
|
43
|
+
expect(() => at(ymd, 'nope')).toThrow('does not exist');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('single-field record', () => {
|
|
47
|
+
const r = record({ only: string() });
|
|
48
|
+
|
|
49
|
+
const only = at(r, 'only');
|
|
50
|
+
|
|
51
|
+
expectType<TypeOf<typeof only>, string>('=');
|
|
52
|
+
|
|
53
|
+
assert.isTrue(only.is('x'));
|
|
54
|
+
|
|
55
|
+
assert.isFalse(only.is(1));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('optional key', () => {
|
|
60
|
+
const r = record({ a: number(), b: optional(string()) });
|
|
61
|
+
|
|
62
|
+
test('accessed value includes undefined', () => {
|
|
63
|
+
const b = at(r, 'b');
|
|
64
|
+
|
|
65
|
+
expectType<TypeOf<typeof b>, string | undefined>('=');
|
|
66
|
+
|
|
67
|
+
assert.isTrue(b.is('x'));
|
|
68
|
+
|
|
69
|
+
assert.isTrue(b.is(undefined));
|
|
70
|
+
|
|
71
|
+
assert.isFalse(b.is(1));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('required sibling key is unaffected', () => {
|
|
75
|
+
const a = at(r, 'a');
|
|
76
|
+
|
|
77
|
+
expectType<TypeOf<typeof a>, number>('=');
|
|
78
|
+
|
|
79
|
+
assert.isTrue(a.is(1));
|
|
80
|
+
|
|
81
|
+
assert.isFalse(a.is(undefined));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('union of records', () => {
|
|
86
|
+
const shape = union([
|
|
87
|
+
record({ type: literal('a'), value: number() }),
|
|
88
|
+
record({ type: literal('b'), value: string() }),
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
test('common key resolves to the union of member types', () => {
|
|
92
|
+
const value = at(shape, 'value');
|
|
93
|
+
|
|
94
|
+
expectType<TypeOf<typeof value>, number | string>('=');
|
|
95
|
+
|
|
96
|
+
assert.isTrue(value.is(1));
|
|
97
|
+
|
|
98
|
+
assert.isTrue(value.is('x'));
|
|
99
|
+
|
|
100
|
+
assert.isFalse(value.is(true));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('discriminant key resolves to the union of literals', () => {
|
|
104
|
+
const type = at(shape, 'type');
|
|
105
|
+
|
|
106
|
+
expectType<TypeOf<typeof type>, 'a' | 'b'>('=');
|
|
107
|
+
|
|
108
|
+
assert.isTrue(type.is('a'));
|
|
109
|
+
|
|
110
|
+
assert.isTrue(type.is('b'));
|
|
111
|
+
|
|
112
|
+
assert.isFalse(type.is('c'));
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('mergeRecords (intersection)', () => {
|
|
117
|
+
const merged = mergeRecords([
|
|
118
|
+
record({ a: number() }),
|
|
119
|
+
record({ b: string() }),
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
test('extracts a key from each merged part', () => {
|
|
123
|
+
const a = at(merged, 'a');
|
|
124
|
+
|
|
125
|
+
const b = at(merged, 'b');
|
|
126
|
+
|
|
127
|
+
expectType<TypeOf<typeof a>, number>('=');
|
|
128
|
+
|
|
129
|
+
expectType<TypeOf<typeof b>, string>('=');
|
|
130
|
+
|
|
131
|
+
assert.isTrue(a.is(1));
|
|
132
|
+
|
|
133
|
+
assert.isFalse(a.is('x'));
|
|
134
|
+
|
|
135
|
+
assert.isTrue(b.is('x'));
|
|
136
|
+
|
|
137
|
+
assert.isFalse(b.is(1));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('mergeRecords of a union (intersection containing a union)', () => {
|
|
142
|
+
const combo = mergeRecords([
|
|
143
|
+
union([record({ kind: literal('x') }), record({ kind: literal('y') })]),
|
|
144
|
+
record({ shared: number() }),
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
test('key from the union part resolves across all variants', () => {
|
|
148
|
+
const kind = at(combo, 'kind');
|
|
149
|
+
|
|
150
|
+
expectType<TypeOf<typeof kind>, 'x' | 'y'>('=');
|
|
151
|
+
|
|
152
|
+
assert.isTrue(kind.is('x'));
|
|
153
|
+
|
|
154
|
+
assert.isTrue(kind.is('y'));
|
|
155
|
+
|
|
156
|
+
assert.isFalse(kind.is('z'));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('key from the plain part is extracted normally', () => {
|
|
160
|
+
const shared = at(combo, 'shared');
|
|
161
|
+
|
|
162
|
+
expectType<TypeOf<typeof shared>, number>('=');
|
|
163
|
+
|
|
164
|
+
assert.isTrue(shared.is(42));
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe(recursion, () => {
|
|
169
|
+
type Tree = Readonly<{ value: number; children: readonly Tree[] }>;
|
|
170
|
+
|
|
171
|
+
const TreeNode: Type<Tree> = recursion('Tree', () =>
|
|
172
|
+
record({ value: number(), children: array(TreeNode) }),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
test('extracts a primitive key from a recursive record', () => {
|
|
176
|
+
const value = at(TreeNode, 'value');
|
|
177
|
+
|
|
178
|
+
expectType<TypeOf<typeof value>, number>('=');
|
|
179
|
+
|
|
180
|
+
assert.isTrue(value.is(5));
|
|
181
|
+
|
|
182
|
+
assert.isFalse(value.is('5'));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('extracts a self-referential key', () => {
|
|
186
|
+
const children = at(TreeNode, 'children');
|
|
187
|
+
|
|
188
|
+
assert.isTrue(children.is([]));
|
|
189
|
+
|
|
190
|
+
assert.isTrue(children.is([{ value: 1, children: [] }]));
|
|
191
|
+
|
|
192
|
+
assert.isFalse(children.is('not-an-array'));
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('recursion combined with union', () => {
|
|
197
|
+
type LinkedList = Readonly<{ value: number; next: LinkedList | null }>;
|
|
198
|
+
|
|
199
|
+
const List: Type<LinkedList> = recursion('LinkedList', () =>
|
|
200
|
+
record({ value: number(), next: union([nullType, List]) }),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
test('extracts a key whose type is a union of null and the recursive type', () => {
|
|
204
|
+
const next = at(List, 'next');
|
|
205
|
+
|
|
206
|
+
expectType<TypeOf<typeof next>, LinkedList | null>('=');
|
|
207
|
+
|
|
208
|
+
assert.isTrue(next.is(null));
|
|
209
|
+
|
|
210
|
+
assert.isTrue(next.is({ value: 1, next: null }));
|
|
211
|
+
|
|
212
|
+
assert.isFalse(next.is(5));
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('tuple index access', () => {
|
|
217
|
+
const tup = tuple([number(), string(), boolean()]);
|
|
218
|
+
|
|
219
|
+
test('extracts the Type at each index', () => {
|
|
220
|
+
const first = at(tup, 0);
|
|
221
|
+
|
|
222
|
+
const second = at(tup, 1);
|
|
223
|
+
|
|
224
|
+
const third = at(tup, 2);
|
|
225
|
+
|
|
226
|
+
expectType<TypeOf<typeof first>, number>('=');
|
|
227
|
+
|
|
228
|
+
expectType<TypeOf<typeof second>, string>('=');
|
|
229
|
+
|
|
230
|
+
expectType<TypeOf<typeof third>, boolean>('=');
|
|
231
|
+
|
|
232
|
+
assert.isTrue(first.is(1));
|
|
233
|
+
|
|
234
|
+
assert.isFalse(first.is('x'));
|
|
235
|
+
|
|
236
|
+
assert.isTrue(second.is('x'));
|
|
237
|
+
|
|
238
|
+
assert.isTrue(third.is(true));
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('index argument is constrained to valid indices', () => {
|
|
242
|
+
// @ts-expect-error 3 is out of range for a 3-element tuple
|
|
243
|
+
expect(() => at(tup, 3)).toThrow('out of range');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('error handling', () => {
|
|
248
|
+
test('throws when given a non-record, non-tuple type', () => {
|
|
249
|
+
expect(() => {
|
|
250
|
+
// @ts-expect-error number() is not a record type
|
|
251
|
+
at(number(), 'x');
|
|
252
|
+
}).toThrow('Expected a record type');
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// `at` expands the full shape structure to resolve a key. Intersecting unions
|
|
257
|
+
// multiplies the variant counts, so the underlying `expandShapeStructure`
|
|
258
|
+
// guards against exponential blow-up.
|
|
259
|
+
describe('variant-count guard', () => {
|
|
260
|
+
const simple: ShapeStructure = {
|
|
261
|
+
kind: 'simple',
|
|
262
|
+
shape: { k: number() },
|
|
263
|
+
} as const;
|
|
264
|
+
|
|
265
|
+
test('throws when an intersection would expand to too many variants', () => {
|
|
266
|
+
const unionOf7: ShapeStructure = {
|
|
267
|
+
kind: 'union',
|
|
268
|
+
variants: [simple, simple, simple, simple, simple, simple, simple],
|
|
269
|
+
} as const;
|
|
270
|
+
|
|
271
|
+
// 7^5 = 16807 > 10_000
|
|
272
|
+
const nested: ShapeStructure = {
|
|
273
|
+
kind: 'intersection',
|
|
274
|
+
parts: [unionOf7, unionOf7, unionOf7, unionOf7, unionOf7],
|
|
275
|
+
} as const;
|
|
276
|
+
|
|
277
|
+
expect(() => expandShapeStructure(nested)).toThrow('exceeding the limit');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('does not throw for a moderate intersection', () => {
|
|
281
|
+
const unionOf3: ShapeStructure = {
|
|
282
|
+
kind: 'union',
|
|
283
|
+
variants: [simple, simple, simple],
|
|
284
|
+
} as const;
|
|
285
|
+
|
|
286
|
+
// 3^2 = 9 <= 10_000
|
|
287
|
+
const moderate: ShapeStructure = {
|
|
288
|
+
kind: 'intersection',
|
|
289
|
+
parts: [unionOf3, unionOf3],
|
|
290
|
+
} as const;
|
|
291
|
+
|
|
292
|
+
expect(expandShapeStructure(moderate)).toHaveLength(9);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
package/src/record/index.mts
CHANGED
|
@@ -9,10 +9,9 @@ import { literal } from '../other-types/index.mjs';
|
|
|
9
9
|
import {
|
|
10
10
|
type ExcessPropertyOption,
|
|
11
11
|
type RecordTypeInternals,
|
|
12
|
-
type ShapeStructure,
|
|
13
12
|
type Type,
|
|
14
13
|
type TypeOf,
|
|
15
|
-
|
|
14
|
+
expandShapeStructure,
|
|
16
15
|
hasRecordInternals,
|
|
17
16
|
} from '../type.mjs';
|
|
18
17
|
import { toIntersectionString } from '../utils/index.mjs';
|
|
@@ -93,33 +92,6 @@ export const mergeRecords = <
|
|
|
93
92
|
}) as MergeRecordsType<Types>;
|
|
94
93
|
};
|
|
95
94
|
|
|
96
|
-
/**
|
|
97
|
-
* Expands a ShapeStructure into all possible simple shapes.
|
|
98
|
-
* For union structures, returns an array of all variant shapes.
|
|
99
|
-
* For intersection structures, computes cartesian product of all parts.
|
|
100
|
-
*/
|
|
101
|
-
const expandShapeStructure = (
|
|
102
|
-
structure: ShapeStructure,
|
|
103
|
-
): readonly UnknownShape[] => {
|
|
104
|
-
switch (structure.kind) {
|
|
105
|
-
case 'simple': {
|
|
106
|
-
return [structure.shape];
|
|
107
|
-
}
|
|
108
|
-
case 'union': {
|
|
109
|
-
// Union: flatten all variants
|
|
110
|
-
return structure.variants.flatMap(expandShapeStructure);
|
|
111
|
-
}
|
|
112
|
-
case 'intersection': {
|
|
113
|
-
// Intersection: compute cartesian product of all parts
|
|
114
|
-
const expandedParts = structure.parts.map(expandShapeStructure);
|
|
115
|
-
|
|
116
|
-
const combinations = Arr.cartesianProduct(expandedParts);
|
|
117
|
-
|
|
118
|
-
return Arr.map(combinations, (shapes) => Obj.merge(...shapes));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
|
|
123
95
|
type MergeRecordsType<Types extends readonly Type<UnknownRecord>[]> = Type<
|
|
124
96
|
MergedExactValue<Types>
|
|
125
97
|
>;
|
package/src/type.mts
CHANGED
|
@@ -81,6 +81,14 @@ export type RecordTypeInternals = Readonly<{
|
|
|
81
81
|
excessProperty: ExcessPropertyOption;
|
|
82
82
|
}>;
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* @internal Internal properties attached to tuple types so that the element
|
|
86
|
+
* type at a given index can be recovered (e.g. by {@link at}).
|
|
87
|
+
*/
|
|
88
|
+
export type TupleTypeInternals = Readonly<{
|
|
89
|
+
elementTypes: readonly Type<unknown>[];
|
|
90
|
+
}>;
|
|
91
|
+
|
|
84
92
|
/** @internal Helper to flatten ShapeStructure to a simple shape if possible */
|
|
85
93
|
export const flattenShapeStructure = (
|
|
86
94
|
structure: ShapeStructure,
|
|
@@ -108,6 +116,62 @@ export const flattenShapeStructure = (
|
|
|
108
116
|
}
|
|
109
117
|
};
|
|
110
118
|
|
|
119
|
+
/**
|
|
120
|
+
* @internal Upper bound on the number of concrete shapes that
|
|
121
|
+
* {@link expandShapeStructure} may produce. Expanding an intersection of unions
|
|
122
|
+
* multiplies the variant counts, so a deeply nested structure can grow
|
|
123
|
+
* exponentially; this bound prevents excessive CPU/memory usage (mirrors the
|
|
124
|
+
* guard in `mergeRecords`).
|
|
125
|
+
*/
|
|
126
|
+
const EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS = 10_000;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @internal Expands a ShapeStructure into all possible simple shapes.
|
|
130
|
+
* For union structures, returns an array of all variant shapes.
|
|
131
|
+
* For intersection structures, computes the cartesian product of all parts.
|
|
132
|
+
*
|
|
133
|
+
* Unlike {@link flattenShapeStructure}, this never returns `undefined`: a
|
|
134
|
+
* union of records is represented as the list of its concrete member shapes.
|
|
135
|
+
*
|
|
136
|
+
* @throws If the intersection expansion would exceed
|
|
137
|
+
* {@link EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS} concrete shapes.
|
|
138
|
+
*/
|
|
139
|
+
export const expandShapeStructure = (
|
|
140
|
+
structure: ShapeStructure,
|
|
141
|
+
): readonly UnknownShape[] => {
|
|
142
|
+
switch (structure.kind) {
|
|
143
|
+
case 'simple': {
|
|
144
|
+
return [structure.shape];
|
|
145
|
+
}
|
|
146
|
+
case 'union': {
|
|
147
|
+
// Union: flatten all variants
|
|
148
|
+
return structure.variants.flatMap(expandShapeStructure);
|
|
149
|
+
}
|
|
150
|
+
case 'intersection': {
|
|
151
|
+
// Intersection: compute cartesian product of all parts
|
|
152
|
+
const expandedParts = structure.parts.map(expandShapeStructure);
|
|
153
|
+
|
|
154
|
+
// The cartesian product size is the product of the per-part variant
|
|
155
|
+
// counts, which grows multiplicatively. Bound it before materializing to
|
|
156
|
+
// avoid excessive CPU/memory usage.
|
|
157
|
+
const estimatedVariantCount = expandedParts.reduce(
|
|
158
|
+
(acc, shapes) => acc * shapes.length,
|
|
159
|
+
1,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (estimatedVariantCount > EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Expanding this record type would create ${estimatedVariantCount} variants, exceeding the limit of ${EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS}. This could lead to excessive memory or CPU usage. Consider simplifying the record types or reducing union/intersection nesting.`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const combinations = Arr.cartesianProduct(expandedParts);
|
|
169
|
+
|
|
170
|
+
return Arr.map(combinations, (shapes) => Obj.merge(...shapes));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
111
175
|
/** @internal Backward compatibility: simple shape accessor */
|
|
112
176
|
export const getShape = (internals: RecordTypeInternals): UnknownShape => {
|
|
113
177
|
const flattened = flattenShapeStructure(internals.shapeStructure);
|
|
@@ -133,6 +197,14 @@ const hasRecordInternalsImpl = (t: unknown): t is RecordTypeInternals =>
|
|
|
133
197
|
hasKey(t, 'excessProperty') &&
|
|
134
198
|
(t.excessProperty === 'allow' || t.excessProperty === 'reject');
|
|
135
199
|
|
|
200
|
+
/** @internal Runtime check for tuple type internals. */
|
|
201
|
+
export const hasTupleInternals = <T extends Type<unknown>>(
|
|
202
|
+
t: T,
|
|
203
|
+
): t is T & TupleTypeInternals => hasTupleInternalsImpl(t);
|
|
204
|
+
|
|
205
|
+
const hasTupleInternalsImpl = (t: unknown): t is TupleTypeInternals =>
|
|
206
|
+
isRecord(t) && hasKey(t, 'elementTypes') && Arr.isArray(t.elementTypes);
|
|
207
|
+
|
|
136
208
|
const isValidShapeStructure = (s: unknown): s is ShapeStructure => {
|
|
137
209
|
if (!isRecord(s) || !hasKey(s, 'kind')) return false;
|
|
138
210
|
|