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.
@@ -1 +1 @@
1
- {"version":3,"file":"tuple.d.mts","sourceRoot":"","sources":["../../src/array/tuple.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAUrD,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,CA2ElB,CAAC"}
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"}
@@ -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
 
@@ -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;IAElE,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;KAC7B;AACH;;;;"}
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;;;;"}
@@ -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';
@@ -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';
@@ -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;;;;"}
@@ -1,3 +1,4 @@
1
+ export * from './at.mjs';
1
2
  export * from './key-value-record.mjs';
2
3
  export * from './keyof.mjs';
3
4
  export * from './merge-records.mjs';
@@ -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"}
@@ -1,3 +1,4 @@
1
+ export { at } from './at.mjs';
1
2
  export { keyValueRecord } from './key-value-record.mjs';
2
3
  export { keyof } from './keyof.mjs';
3
4
  export { mergeRecords } from './merge-records.mjs';
@@ -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,EAGzB,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;AA6BF,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
+ {"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":";;;;;;;;AAoBA,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;AAEA;;;;AAIG;AACH,MAAM,oBAAoB,GAAG,CAC3B,SAAyB,KACE;AAC3B,IAAA,QAAQ,SAAS,CAAC,IAAI;QACpB,KAAK,QAAQ,EAAE;AACb,YAAA,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC;;QAE1B,KAAK,OAAO,EAAE;;YAEZ,OAAO,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAC;;QAEzD,KAAK,cAAc,EAAE;;YAEnB,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,oBAAoB,CAAC;YAE/D,MAAM,YAAY,GAAG,GAAG,CAAC,gBAAgB,CAAC,aAAa,CAAC;AAExD,YAAA,OAAO,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC;;;AAGpE,CAAC;AAuBD,MAAM,6BAA6B,GAAG,CACpC,KAAqC,KAErC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,cAAc,KAAK,QAAQ,CAAC,GAAG,QAAQ,GAAG,OAAO;;;;"}
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
@@ -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":";;AAmFA;AACO,MAAM,qBAAqB,GAAG,CACnC,SAAyB,KACG;AAC5B,IAAA,QAAQ,SAAS,CAAC,IAAI;QACpB,KAAK,QAAQ,EAAE;YACb,OAAO,SAAS,CAAC,KAAK;QACxB;QACA,KAAK,cAAc,EAAE;;YAEnB,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,CAAC;AAExD,YAAA,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;;AAE7B,gBAAA,OAAO,SAAS;YAClB;;AAGA,YAAA,OAAO,GAAG,CAAC,KAAK,CAAC,GAAI,KAAiC,CAAC;QACzD;QACA,KAAK,OAAO,EAAE;;AAEZ,YAAA,OAAO,SAAS;QAClB;;AAEJ;AAEA;AACO,MAAM,QAAQ,GAAG,CAAC,SAA8B,KAAkB;IACvE,MAAM,SAAS,GAAG,qBAAqB,CAAC,SAAS,CAAC,cAAc,CAAC;AAEjE,IAAA,IAAI,SAAS,KAAK,SAAS,EAAE;AAC3B,QAAA,MAAM,IAAI,KAAK,CACb,CAAA,4HAAA,CAA8H,CAC/H;IACH;AAEA,IAAA,OAAO,SAAS;AAClB;AAEA;AACO,MAAM,kBAAkB,GAAG,CAChC,CAAI,KAC6B,sBAAsB,CAAC,CAAC;AAE3D,MAAM,sBAAsB,GAAG,CAAC,CAAU,KACxC,QAAQ,CAAC,CAAC,CAAC;AACX,IAAA,MAAM,CAAC,CAAC,EAAE,gBAAgB,CAAC;AAC3B,IAAA,qBAAqB,CAAC,CAAC,CAAC,cAAc,CAAC;AACvC,IAAA,MAAM,CAAC,CAAC,EAAE,gBAAgB,CAAC;AAC3B,KAAC,CAAC,CAAC,cAAc,KAAK,OAAO,IAAI,CAAC,CAAC,cAAc,KAAK,QAAQ,CAAC;AAEjE,MAAM,qBAAqB,GAAG,CAAC,CAAU,KAAyB;AAChE,IAAA,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC;AAAE,QAAA,OAAO,KAAK;AAEpD,IAAA,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE;AACvB,QAAA,OAAO,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;IAChD;AAEA,IAAA,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE;AACtB,QAAA,QACE,MAAM,CAAC,CAAC,EAAE,UAAU,CAAC;AACrB,YAAA,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;YACvB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,qBAAqB,CAAC;IAE3C;AAEA,IAAA,IAAI,CAAC,CAAC,IAAI,KAAK,cAAc,EAAE;AAC7B,QAAA,QACE,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC;AAClB,YAAA,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;YACpB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,qBAAqB,CAAC;IAExC;AAEA,IAAA,OAAO,KAAK;AACd,CAAC;;;;"}
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.1.0",
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}'",
@@ -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
+ });
@@ -1,3 +1,4 @@
1
+ export * from './at.mjs';
1
2
  export * from './key-value-record.mjs';
2
3
  export * from './keyof.mjs';
3
4
  export * from './merge-records.mjs';
@@ -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
- type UnknownShape,
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