ts-fortress 11.0.2 → 11.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/README.md +11 -8
- package/dist/brand/brand.mjs +1 -0
- package/dist/brand/brand.mjs.map +1 -1
- package/dist/compose/intersection.mjs +1 -0
- package/dist/compose/intersection.mjs.map +1 -1
- package/dist/compose/union.d.mts.map +1 -1
- package/dist/compose/union.mjs +94 -17
- package/dist/compose/union.mjs.map +1 -1
- package/dist/entry-point.mjs +3 -1
- package/dist/entry-point.mjs.map +1 -1
- package/dist/enum/index.d.mts +2 -0
- package/dist/enum/index.d.mts.map +1 -1
- package/dist/enum/index.mjs +2 -0
- package/dist/enum/index.mjs.map +1 -1
- package/dist/enum/int-range-inclusive.d.mts +13 -0
- package/dist/enum/int-range-inclusive.d.mts.map +1 -0
- package/dist/enum/int-range-inclusive.mjs +61 -0
- package/dist/enum/int-range-inclusive.mjs.map +1 -0
- package/dist/enum/int-range.d.mts +7 -8
- package/dist/enum/int-range.d.mts.map +1 -1
- package/dist/enum/int-range.mjs +17 -7
- package/dist/enum/int-range.mjs.map +1 -1
- package/dist/enum/uint-range-inclusive.d.mts +13 -0
- package/dist/enum/uint-range-inclusive.d.mts.map +1 -0
- package/dist/enum/uint-range-inclusive.mjs +61 -0
- package/dist/enum/uint-range-inclusive.mjs.map +1 -0
- package/dist/enum/uint-range.d.mts +6 -3
- package/dist/enum/uint-range.d.mts.map +1 -1
- package/dist/enum/uint-range.mjs +17 -7
- package/dist/enum/uint-range.mjs.map +1 -1
- package/dist/index.mjs +4 -2
- package/dist/index.mjs.map +1 -1
- package/dist/other-types/literal.mjs +1 -0
- package/dist/other-types/literal.mjs.map +1 -1
- package/dist/other-types/recursion.mjs +1 -0
- package/dist/other-types/recursion.mjs.map +1 -1
- package/dist/other-types/unknown.mjs +1 -0
- package/dist/other-types/unknown.mjs.map +1 -1
- package/dist/predefined/int8.d.mts.map +1 -1
- package/dist/predefined/int8.mjs +1 -5
- package/dist/predefined/int8.mjs.map +1 -1
- package/dist/predefined/json.mjs +1 -1
- package/dist/predefined/nullable.mjs +1 -1
- package/dist/predefined/uint8.d.mts.map +1 -1
- package/dist/predefined/uint8.mjs +1 -5
- package/dist/predefined/uint8.mjs.map +1 -1
- package/dist/primitives/bigint.mjs +1 -1
- package/dist/primitives/boolean.mjs +1 -0
- package/dist/primitives/boolean.mjs.map +1 -1
- package/dist/primitives/null.mjs +1 -0
- package/dist/primitives/null.mjs.map +1 -1
- package/dist/primitives/number.mjs +1 -1
- package/dist/primitives/string.mjs +1 -1
- package/dist/primitives/symbol.mjs +1 -0
- package/dist/primitives/symbol.mjs.map +1 -1
- package/dist/primitives/undefined.mjs +1 -0
- package/dist/primitives/undefined.mjs.map +1 -1
- package/dist/record/at.mjs +1 -1
- package/dist/record/merge-records.mjs +1 -1
- package/dist/record/omit.mjs +1 -0
- package/dist/record/omit.mjs.map +1 -1
- package/dist/record/partial.mjs +1 -0
- package/dist/record/partial.mjs.map +1 -1
- package/dist/record/pick.mjs +1 -0
- package/dist/record/pick.mjs.map +1 -1
- package/dist/record/required.mjs +1 -0
- package/dist/record/required.mjs.map +1 -1
- package/dist/record/valueof.mjs +1 -1
- package/dist/type.d.mts +17 -0
- package/dist/type.d.mts.map +1 -1
- package/dist/type.mjs +3 -1
- package/dist/type.mjs.map +1 -1
- package/dist/utils/index.mjs +1 -1
- package/dist/utils/validation-error.d.mts +35 -0
- package/dist/utils/validation-error.d.mts.map +1 -1
- package/dist/utils/validation-error.mjs +80 -51
- package/dist/utils/validation-error.mjs.map +1 -1
- package/package.json +35 -92
- package/src/brand/branded-string.test.mts +1 -1
- package/src/compose/intersection.test.mts +2 -2
- package/src/compose/union-closest-member.test.mts +328 -0
- package/src/compose/union-error-message.test.mts +173 -0
- package/src/compose/union-mixed.test.mts +1 -1
- package/src/compose/union-nested-record.test.mts +7 -4
- package/src/compose/union-nested-union.test.mts +131 -0
- package/src/compose/union-primitive-literal.test.mts +1 -1
- package/src/compose/union-records.test.mts +7 -6
- package/src/compose/union.mts +192 -21
- package/src/enum/index.mts +2 -0
- package/src/enum/int-range-inclusive.mts +106 -0
- package/src/enum/int-range-inclusive.test.mts +112 -0
- package/src/enum/int-range.mts +48 -71
- package/src/enum/int-range.test.mts +1 -3
- package/src/enum/uint-range-inclusive.mts +106 -0
- package/src/enum/uint-range-inclusive.test.mts +159 -0
- package/src/enum/uint-range.mts +50 -15
- package/src/enum/uint-range.test.mts +1 -3
- package/src/other-types/set.test.mts +2 -2
- package/src/predefined/int8.mts +1 -5
- package/src/predefined/uint8.mts +1 -5
- package/src/primitives/undefined.test.mts +2 -2
- package/src/type.mts +24 -0
- package/src/utils/validation-error.formatting.test.mts +395 -85
- package/src/utils/validation-error.mts +223 -61
- package/src/utils/validation-error.test.mts +1 -1
package/README.md
CHANGED
|
@@ -909,14 +909,15 @@ const ColorEnum = t.enumType(['red', 'green', 'blue']);
|
|
|
909
909
|
|
|
910
910
|
type Color = t.TypeOf<typeof ColorEnum>; // 'red' | 'green' | 'blue'
|
|
911
911
|
|
|
912
|
-
// Numeric ranges
|
|
913
|
-
const DiceRoll = t.uintRange({
|
|
914
|
-
start: 1,
|
|
915
|
-
end: 7,
|
|
916
|
-
defaultValue: 1,
|
|
917
|
-
}); // integers from 1 to 6
|
|
912
|
+
// Numeric ranges (end is exclusive)
|
|
913
|
+
const DiceRoll = t.uintRange(1, 7, { defaultValue: 1 }); // integers from 1 to 6
|
|
918
914
|
|
|
919
915
|
type DiceRoll = t.TypeOf<typeof DiceRoll>; // 1 | 2 | 3 | 4 | 5 | 6
|
|
916
|
+
|
|
917
|
+
// Numeric ranges (end is inclusive)
|
|
918
|
+
const DiceRoll2 = t.uintRangeInclusive(1, 6, { defaultValue: 1 }); // integers from 1 to 6
|
|
919
|
+
|
|
920
|
+
type DiceRoll2 = t.TypeOf<typeof DiceRoll2>; // 1 | 2 | 3 | 4 | 5 | 6
|
|
920
921
|
```
|
|
921
922
|
|
|
922
923
|
Tips: It is often better to use `uintRange` instead of `enumType` when possible, because `enumType` stores a Set of the sizes of its members as data, while `uintRange` only stores the range, resulting in smaller memory usage.
|
|
@@ -1077,8 +1078,10 @@ type ValidationError = Readonly<{
|
|
|
1077
1078
|
|
|
1078
1079
|
- `t.TypeOf<T>` - Extract TypeScript type from validator
|
|
1079
1080
|
- `t.enumType(values)` - Enum validation
|
|
1080
|
-
- `t.uintRange(
|
|
1081
|
-
- `t.intRange(
|
|
1081
|
+
- `t.uintRange(start, end, { defaultValue? })` - Non-negative integer range validation (`end` is exclusive)
|
|
1082
|
+
- `t.intRange(start, end, { defaultValue? })` - Integer range validation (`end` is exclusive)
|
|
1083
|
+
- `t.uintRangeInclusive(start, end, { defaultValue? })` - Non-negative integer range validation (`end` is inclusive)
|
|
1084
|
+
- `t.intRangeInclusive(start, end, { defaultValue? })` - Integer range validation (`end` is inclusive)
|
|
1082
1085
|
- `t.unknown` - Unknown Type
|
|
1083
1086
|
- `t.recursion(typeName, definition)` - Define recursive type
|
|
1084
1087
|
|
package/dist/brand/brand.mjs
CHANGED
package/dist/brand/brand.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brand.mjs","sources":["../../src/brand/brand.mts"],"sourcesContent":["import { pipe, Result } from 'ts-data-forge';\nimport { type Brand, type Primitive, type StrictExtract } from 'ts-type-forge';\nimport { type Type } from '../type.mjs';\nimport { createType, type ValidationError } from '../utils/index.mjs';\n\nexport type { Brand } from 'ts-type-forge';\n\ntype ArrayToUnion<A extends readonly unknown[]> = A extends readonly []\n ? never\n : A[number];\n\nexport const brand = <\n const A extends StrictExtract<Primitive, string | number | bigint | boolean>,\n const BrandTrueKeys extends readonly string[],\n const BrandFalseKeys extends readonly string[] = readonly [],\n>({\n baseType,\n brandFalseKeys,\n brandKeys,\n defaultValue: defaultValue_,\n is: is_,\n typeName,\n}: Readonly<{\n baseType: Type<A>;\n is?: (\n a: A,\n ) => a is Brand<A, ArrayToUnion<BrandTrueKeys>, ArrayToUnion<BrandFalseKeys>>;\n defaultValue?: A;\n typeName?: string;\n brandKeys: BrandTrueKeys;\n brandFalseKeys?: BrandFalseKeys;\n}>): Type<\n Brand<A, ArrayToUnion<BrandTrueKeys>, ArrayToUnion<BrandFalseKeys>>\n> => {\n type T = Brand<A, ArrayToUnion<BrandTrueKeys>, ArrayToUnion<BrandFalseKeys>>;\n\n const is: (a: A) => a is T = is_ ?? ((_a): _a is T => true);\n\n const defaultValue: A = defaultValue_ ?? baseType.defaultValue;\n\n if (!is(defaultValue)) {\n throw new Error(\n `defaultValue ${defaultValue} doesn't pass \\`is\\` function`,\n );\n }\n\n const brandKeysStr = [\n ...brandKeys.map((s) => `\"${s}\"`),\n ...(brandFalseKeys?.map((s) => `not(\"${s}\")`) ?? []),\n ].join(' & ');\n\n const typeNameFilled = typeName ?? brandKeysStr;\n\n const validate: Type<T>['validate'] = (a) =>\n pipe(a)\n .map(baseType.validate)\n .map((res): Result<T, readonly ValidationError[]> =>\n Result.isErr(res)\n ? res\n : is(res.value)\n ? Result.ok(res.value satisfies T)\n : Result.err([\n {\n path: [],\n actualValue: res.value,\n expectedType: typeNameFilled,\n typeName: typeNameFilled,\n details:\n // If typeName is specified, it will be used in the error message, so no further information is required.\n typeName === undefined\n ? {\n kind: 'brand' as const,\n description: brandKeysStr,\n }\n : undefined,\n } satisfies ValidationError,\n ]),\n ).value;\n\n return createType({\n typeName: typeNameFilled,\n defaultValue,\n validate,\n });\n};\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"brand.mjs","sources":["../../src/brand/brand.mts"],"sourcesContent":["import { pipe, Result } from 'ts-data-forge';\nimport { type Brand, type Primitive, type StrictExtract } from 'ts-type-forge';\nimport { type Type } from '../type.mjs';\nimport { createType, type ValidationError } from '../utils/index.mjs';\n\nexport type { Brand } from 'ts-type-forge';\n\ntype ArrayToUnion<A extends readonly unknown[]> = A extends readonly []\n ? never\n : A[number];\n\nexport const brand = <\n const A extends StrictExtract<Primitive, string | number | bigint | boolean>,\n const BrandTrueKeys extends readonly string[],\n const BrandFalseKeys extends readonly string[] = readonly [],\n>({\n baseType,\n brandFalseKeys,\n brandKeys,\n defaultValue: defaultValue_,\n is: is_,\n typeName,\n}: Readonly<{\n baseType: Type<A>;\n is?: (\n a: A,\n ) => a is Brand<A, ArrayToUnion<BrandTrueKeys>, ArrayToUnion<BrandFalseKeys>>;\n defaultValue?: A;\n typeName?: string;\n brandKeys: BrandTrueKeys;\n brandFalseKeys?: BrandFalseKeys;\n}>): Type<\n Brand<A, ArrayToUnion<BrandTrueKeys>, ArrayToUnion<BrandFalseKeys>>\n> => {\n type T = Brand<A, ArrayToUnion<BrandTrueKeys>, ArrayToUnion<BrandFalseKeys>>;\n\n const is: (a: A) => a is T = is_ ?? ((_a): _a is T => true);\n\n const defaultValue: A = defaultValue_ ?? baseType.defaultValue;\n\n if (!is(defaultValue)) {\n throw new Error(\n `defaultValue ${defaultValue} doesn't pass \\`is\\` function`,\n );\n }\n\n const brandKeysStr = [\n ...brandKeys.map((s) => `\"${s}\"`),\n ...(brandFalseKeys?.map((s) => `not(\"${s}\")`) ?? []),\n ].join(' & ');\n\n const typeNameFilled = typeName ?? brandKeysStr;\n\n const validate: Type<T>['validate'] = (a) =>\n pipe(a)\n .map(baseType.validate)\n .map((res): Result<T, readonly ValidationError[]> =>\n Result.isErr(res)\n ? res\n : is(res.value)\n ? Result.ok(res.value satisfies T)\n : Result.err([\n {\n path: [],\n actualValue: res.value,\n expectedType: typeNameFilled,\n typeName: typeNameFilled,\n details:\n // If typeName is specified, it will be used in the error message, so no further information is required.\n typeName === undefined\n ? {\n kind: 'brand' as const,\n description: brandKeysStr,\n }\n : undefined,\n } satisfies ValidationError,\n ]),\n ).value;\n\n return createType({\n typeName: typeNameFilled,\n defaultValue,\n validate,\n });\n};\n"],"names":[],"mappings":";;;;AAWO,MAAM,QAAQ,CAInB;AAAA,EACA,QAAA;AAAA,EACA,cAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA,EAAc,aAAA;AAAA,EACd,EAAA,EAAI,GAAA;AAAA,EACJ;AACF,CAAA,KAWK;AAGH,EAAA,MAAM,EAAA,GAAuB,GAAA,KAAQ,CAAC,EAAA,KAAgB,IAAA,CAAA;AAEtD,EAAA,MAAM,YAAA,GAAkB,iBAAiB,QAAA,CAAS,YAAA;AAElD,EAAA,IAAI,CAAC,EAAA,CAAG,YAAY,CAAA,EAAG;AACrB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,gBAAgB,YAAY,CAAA,6BAAA;AAAA,KAC9B;AAAA,EACF;AAEA,EAAA,MAAM,YAAA,GAAe;AAAA,IACnB,GAAG,SAAA,CAAU,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,IAChC,GAAI,gBAAgB,GAAA,CAAI,CAAC,MAAM,CAAA,KAAA,EAAQ,CAAC,CAAA,EAAA,CAAI,CAAA,IAAK;AAAC,GACpD,CAAE,KAAK,KAAK,CAAA;AAEZ,EAAA,MAAM,iBAAiB,QAAA,IAAY,YAAA;AAEnC,EAAA,MAAM,QAAA,GAAgC,CAAC,CAAA,KACrC,IAAA,CAAK,CAAC,CAAA,CACH,GAAA,CAAI,QAAA,CAAS,QAAQ,CAAA,CACrB,GAAA;AAAA,IAAI,CAAC,GAAA,KACJ,MAAA,CAAO,KAAA,CAAM,GAAG,IACZ,GAAA,GACA,EAAA,CAAG,GAAA,CAAI,KAAK,IACV,MAAA,CAAO,EAAA,CAAG,IAAI,KAAiB,CAAA,GAC/B,OAAO,GAAA,CAAI;AAAA,MACT;AAAA,QACE,MAAM,EAAC;AAAA,QACP,aAAa,GAAA,CAAI,KAAA;AAAA,QACjB,YAAA,EAAc,cAAA;AAAA,QACd,QAAA,EAAU,cAAA;AAAA,QACV,OAAA;AAAA;AAAA,UAEE,aAAa,MAAA,GACT;AAAA,YACE,IAAA,EAAM,OAAA;AAAA,YACN,WAAA,EAAa;AAAA,WACf,GACA;AAAA;AAAA;AACR,KACD;AAAA,GACT,CAAE,KAAA;AAEN,EAAA,OAAO,UAAA,CAAW;AAAA,IAChB,QAAA,EAAU,cAAA;AAAA,IACV,YAAA;AAAA,IACA;AAAA,GACD,CAAA;AACH;;;;"}
|
|
@@ -3,6 +3,7 @@ import { hasRecordInternals } from '../type.mjs';
|
|
|
3
3
|
import { createAssertFn } from '../utils/create-assert-fn.mjs';
|
|
4
4
|
import { createCastFn } from '../utils/create-cast-fn.mjs';
|
|
5
5
|
import { createIsFn } from '../utils/create-is-fn.mjs';
|
|
6
|
+
import '@sindresorhus/is';
|
|
6
7
|
import { toIntersectionString } from '../utils/to-union-string.mjs';
|
|
7
8
|
|
|
8
9
|
const intersection = (types, defaultType, options) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intersection.mjs","sources":["../../src/compose/intersection.mts"],"sourcesContent":["import { Arr, expectType, hasKey, isRecord, Result, tp } from 'ts-data-forge';\nimport { type Intersection, type NonEmptyTuple } from 'ts-type-forge';\nimport {\n hasRecordInternals,\n type AnyType,\n type ExcessPropertyOption,\n type Type,\n type TypeOf,\n} from '../type.mjs';\nimport {\n createAssertFn,\n createCastFn,\n createIsFn,\n toIntersectionString,\n type ValidationError,\n} from '../utils/index.mjs';\n\nexport const intersection = <const Types extends NonEmptyTuple<AnyType>>(\n types: Types,\n defaultType: IntersectionType<Types>,\n options?: Partial<\n Readonly<{\n typeName: string;\n }>\n >,\n): IntersectionType<Types> => {\n type T = IntersectionTypeValue<Types>;\n\n const typeNameFilled: string =\n options?.typeName ??\n `(${toIntersectionString(types.map((a) => a.typeName))})`;\n\n const validate: Type<T>['validate'] = (a) => {\n const errors: readonly ValidationError[] = Arr.generate(function* () {\n for (const type of types) {\n const res = type.validate(a);\n\n if (Result.isErr(res)) {\n yield {\n path: [],\n actualValue: a,\n expectedType: typeNameFilled,\n typeName: typeNameFilled,\n details: {\n kind: 'intersection',\n typeNames: types.map((t) => t.typeName),\n },\n } satisfies ValidationError;\n\n yield* res.value;\n }\n }\n });\n\n if (Arr.isNonEmpty(errors)) {\n return Result.err(errors);\n }\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return Result.ok(a as T);\n };\n\n const is = createIsFn<T>(validate);\n\n const fill: Type<T>['fill'] = (a) => (is(a) ? a : defaultType.fill(a));\n\n const baseType: Type<T> = {\n typeName: typeNameFilled,\n defaultValue: defaultType.defaultValue,\n fill,\n // Non-record intersections (e.g. branded primitives) have no excess paths\n // to remove. Record intersections override this below.\n prune: (a) => a,\n validate,\n is,\n assertIs: createAssertFn(validate),\n cast: createCastFn(validate),\n } as const;\n\n // If all types are records, add RecordTypeInternals\n if (types.every(hasRecordInternals)) {\n const shapeStructures = Arr.map(types, (t) => t.shapeStructure);\n\n const excessProperty: ExcessPropertyOption = Arr.some(\n types,\n (t) => t.excessProperty === 'reject',\n )\n ? 'reject'\n : 'allow';\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return {\n ...baseType,\n // A value path is represented by a record intersection if it is\n // represented by any member, so the pruned members are merged (deeply;\n // see `mergePruned`). All members are record types here\n // (`hasRecordInternals`), so each pruned result is a record.\n prune: (a: T): T =>\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-return\n types.map((t) => t.prune(a)).reduce(mergePruned, {}) as T,\n shapeStructure: Arr.isFixedLengthTuple(shapeStructures, 1)\n ? shapeStructures[0]\n : ({ kind: 'intersection', parts: shapeStructures } as const),\n excessProperty,\n } as IntersectionType<Types>;\n }\n\n return baseType;\n};\n\n/**\n * Deep-merges the pruned results of intersection members. Each pruned result\n * contains only the value paths represented by its own member type, so the\n * results have to be merged recursively to keep the union of all represented\n * paths — a shallow spread would drop nested paths kept by an earlier member\n * whenever a later member shares the same key (e.g. two members both having a\n * `pos` sub-record with different fields).\n */\nconst mergePruned = (x: unknown, y: unknown): unknown => {\n if (isRecord(x) && isRecord(y)) {\n const xMergedWithY = Object.fromEntries(\n Object.entries(x).map(([k, xv]) =>\n tp(k, hasKey(y, k) ? mergePruned(xv, y[k]) : xv),\n ),\n );\n\n const yOnly = Object.fromEntries(\n Object.entries(y).filter(([k]) => !hasKey(x, k)),\n );\n\n return { ...xMergedWithY, ...yOnly };\n }\n\n // Both members pruned the same input array, so the lengths always match;\n // merge element-wise to keep every represented element path.\n if (Arr.isArray(x) && Arr.isArray(y) && x.length === y.length) {\n return x.map((xi, i) => mergePruned(xi, y[i]));\n }\n\n // Not structurally mergeable — the later member wins.\n return y;\n};\n\ntype IntersectionType<Types extends NonEmptyTuple<AnyType>> = Type<\n IntersectionTypeValue<Types>\n>;\n\ntype IntersectionTypeValue<Types extends NonEmptyTuple<AnyType>> =\n TsFortressInternal.IntersectionTypeValueImpl<Types>;\n\nnamespace TsFortressInternal {\n export type IntersectionTypeValueImpl<Types extends NonEmptyTuple<AnyType>> =\n Intersection<Cast0<UnwrapTypeList<Types>>>;\n\n type Cast0<T> = readonly [T] extends readonly [NonEmptyTuple<unknown>]\n ? T\n : never;\n}\n\nexpectType<\n IntersectionType<\n readonly [\n Type<\n Readonly<{\n a: 0;\n b: 0;\n }>\n >,\n Type<\n Readonly<{\n b: 0;\n c: 0;\n }>\n >,\n ]\n >,\n Type<\n Readonly<{\n a: 0;\n b: 0;\n c: 0;\n }>\n >\n>('=');\n\ntype UnwrapTypeList<Types extends readonly AnyType[]> =\n TsFortressInternal.UnwrapTypeImpl<Types>;\n\nnamespace TsFortressInternal {\n export type UnwrapTypeImpl<Types extends readonly unknown[]> =\n Types extends readonly []\n ? readonly []\n : Types extends readonly [infer Head, ...infer Tail]\n ? readonly [TypeOf<Cast1<Head>>, ...UnwrapTypeImpl<Tail>]\n : never;\n\n type Cast1<T> = [T] extends [AnyType] ? T : never;\n}\n\nexpectType<\n TypeOf<\n Type<\n Readonly<{\n a: 0;\n b: 0;\n }>\n >\n >,\n Readonly<{\n a: 0;\n b: 0;\n }>\n>('=');\n\nexpectType<\n UnwrapTypeList<\n readonly [\n Type<\n Readonly<{\n a: 0;\n b: 0;\n }>\n >,\n Type<\n Readonly<{\n b: 0;\n c: 0;\n }>\n >,\n ]\n >,\n readonly [\n Readonly<{\n a: 0;\n b: 0;\n }>,\n Readonly<{\n b: 0;\n c: 0;\n }>,\n ]\n>('=');\n\nexpectType<\n Intersection<\n readonly [\n Readonly<{\n a: 0;\n b: 0;\n }>,\n Readonly<{\n b: 0;\n c: 0;\n }>,\n ]\n >,\n Readonly<{\n a: 0;\n b: 0;\n c: 0;\n }>\n>('=');\n\nexpectType<\n Intersection<\n readonly [\n Readonly<{\n a: 0;\n b: 0;\n }>,\n Readonly<{\n b: 0;\n c: 0;\n }>,\n Readonly<{\n c: 0;\n d: 0;\n }>,\n ]\n >,\n Readonly<{\n a: 0;\n b: 0;\n c: 0;\n d: 0;\n }>\n>('=');\n\nexpectType<\n Intersection<\n readonly [\n Readonly<{\n a: 0;\n b: 0;\n }>,\n Readonly<{\n b: 1;\n c: 0;\n }>,\n ]\n >,\n never\n>('=');\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"intersection.mjs","sources":["../../src/compose/intersection.mts"],"sourcesContent":["import { Arr, expectType, hasKey, isRecord, Result, tp } from 'ts-data-forge';\nimport { type Intersection, type NonEmptyTuple } from 'ts-type-forge';\nimport {\n hasRecordInternals,\n type AnyType,\n type ExcessPropertyOption,\n type Type,\n type TypeOf,\n} from '../type.mjs';\nimport {\n createAssertFn,\n createCastFn,\n createIsFn,\n toIntersectionString,\n type ValidationError,\n} from '../utils/index.mjs';\n\nexport const intersection = <const Types extends NonEmptyTuple<AnyType>>(\n types: Types,\n defaultType: IntersectionType<Types>,\n options?: Partial<\n Readonly<{\n typeName: string;\n }>\n >,\n): IntersectionType<Types> => {\n type T = IntersectionTypeValue<Types>;\n\n const typeNameFilled: string =\n options?.typeName ??\n `(${toIntersectionString(types.map((a) => a.typeName))})`;\n\n const validate: Type<T>['validate'] = (a) => {\n const errors: readonly ValidationError[] = Arr.generate(function* () {\n for (const type of types) {\n const res = type.validate(a);\n\n if (Result.isErr(res)) {\n yield {\n path: [],\n actualValue: a,\n expectedType: typeNameFilled,\n typeName: typeNameFilled,\n details: {\n kind: 'intersection',\n typeNames: types.map((t) => t.typeName),\n },\n } satisfies ValidationError;\n\n yield* res.value;\n }\n }\n });\n\n if (Arr.isNonEmpty(errors)) {\n return Result.err(errors);\n }\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return Result.ok(a as T);\n };\n\n const is = createIsFn<T>(validate);\n\n const fill: Type<T>['fill'] = (a) => (is(a) ? a : defaultType.fill(a));\n\n const baseType: Type<T> = {\n typeName: typeNameFilled,\n defaultValue: defaultType.defaultValue,\n fill,\n // Non-record intersections (e.g. branded primitives) have no excess paths\n // to remove. Record intersections override this below.\n prune: (a) => a,\n validate,\n is,\n assertIs: createAssertFn(validate),\n cast: createCastFn(validate),\n } as const;\n\n // If all types are records, add RecordTypeInternals\n if (types.every(hasRecordInternals)) {\n const shapeStructures = Arr.map(types, (t) => t.shapeStructure);\n\n const excessProperty: ExcessPropertyOption = Arr.some(\n types,\n (t) => t.excessProperty === 'reject',\n )\n ? 'reject'\n : 'allow';\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return {\n ...baseType,\n // A value path is represented by a record intersection if it is\n // represented by any member, so the pruned members are merged (deeply;\n // see `mergePruned`). All members are record types here\n // (`hasRecordInternals`), so each pruned result is a record.\n prune: (a: T): T =>\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-return\n types.map((t) => t.prune(a)).reduce(mergePruned, {}) as T,\n shapeStructure: Arr.isFixedLengthTuple(shapeStructures, 1)\n ? shapeStructures[0]\n : ({ kind: 'intersection', parts: shapeStructures } as const),\n excessProperty,\n } as IntersectionType<Types>;\n }\n\n return baseType;\n};\n\n/**\n * Deep-merges the pruned results of intersection members. Each pruned result\n * contains only the value paths represented by its own member type, so the\n * results have to be merged recursively to keep the union of all represented\n * paths — a shallow spread would drop nested paths kept by an earlier member\n * whenever a later member shares the same key (e.g. two members both having a\n * `pos` sub-record with different fields).\n */\nconst mergePruned = (x: unknown, y: unknown): unknown => {\n if (isRecord(x) && isRecord(y)) {\n const xMergedWithY = Object.fromEntries(\n Object.entries(x).map(([k, xv]) =>\n tp(k, hasKey(y, k) ? mergePruned(xv, y[k]) : xv),\n ),\n );\n\n const yOnly = Object.fromEntries(\n Object.entries(y).filter(([k]) => !hasKey(x, k)),\n );\n\n return { ...xMergedWithY, ...yOnly };\n }\n\n // Both members pruned the same input array, so the lengths always match;\n // merge element-wise to keep every represented element path.\n if (Arr.isArray(x) && Arr.isArray(y) && x.length === y.length) {\n return x.map((xi, i) => mergePruned(xi, y[i]));\n }\n\n // Not structurally mergeable — the later member wins.\n return y;\n};\n\ntype IntersectionType<Types extends NonEmptyTuple<AnyType>> = Type<\n IntersectionTypeValue<Types>\n>;\n\ntype IntersectionTypeValue<Types extends NonEmptyTuple<AnyType>> =\n TsFortressInternal.IntersectionTypeValueImpl<Types>;\n\nnamespace TsFortressInternal {\n export type IntersectionTypeValueImpl<Types extends NonEmptyTuple<AnyType>> =\n Intersection<Cast0<UnwrapTypeList<Types>>>;\n\n type Cast0<T> = readonly [T] extends readonly [NonEmptyTuple<unknown>]\n ? T\n : never;\n}\n\nexpectType<\n IntersectionType<\n readonly [\n Type<\n Readonly<{\n a: 0;\n b: 0;\n }>\n >,\n Type<\n Readonly<{\n b: 0;\n c: 0;\n }>\n >,\n ]\n >,\n Type<\n Readonly<{\n a: 0;\n b: 0;\n c: 0;\n }>\n >\n>('=');\n\ntype UnwrapTypeList<Types extends readonly AnyType[]> =\n TsFortressInternal.UnwrapTypeImpl<Types>;\n\nnamespace TsFortressInternal {\n export type UnwrapTypeImpl<Types extends readonly unknown[]> =\n Types extends readonly []\n ? readonly []\n : Types extends readonly [infer Head, ...infer Tail]\n ? readonly [TypeOf<Cast1<Head>>, ...UnwrapTypeImpl<Tail>]\n : never;\n\n type Cast1<T> = [T] extends [AnyType] ? T : never;\n}\n\nexpectType<\n TypeOf<\n Type<\n Readonly<{\n a: 0;\n b: 0;\n }>\n >\n >,\n Readonly<{\n a: 0;\n b: 0;\n }>\n>('=');\n\nexpectType<\n UnwrapTypeList<\n readonly [\n Type<\n Readonly<{\n a: 0;\n b: 0;\n }>\n >,\n Type<\n Readonly<{\n b: 0;\n c: 0;\n }>\n >,\n ]\n >,\n readonly [\n Readonly<{\n a: 0;\n b: 0;\n }>,\n Readonly<{\n b: 0;\n c: 0;\n }>,\n ]\n>('=');\n\nexpectType<\n Intersection<\n readonly [\n Readonly<{\n a: 0;\n b: 0;\n }>,\n Readonly<{\n b: 0;\n c: 0;\n }>,\n ]\n >,\n Readonly<{\n a: 0;\n b: 0;\n c: 0;\n }>\n>('=');\n\nexpectType<\n Intersection<\n readonly [\n Readonly<{\n a: 0;\n b: 0;\n }>,\n Readonly<{\n b: 0;\n c: 0;\n }>,\n Readonly<{\n c: 0;\n d: 0;\n }>,\n ]\n >,\n Readonly<{\n a: 0;\n b: 0;\n c: 0;\n d: 0;\n }>\n>('=');\n\nexpectType<\n Intersection<\n readonly [\n Readonly<{\n a: 0;\n b: 0;\n }>,\n Readonly<{\n b: 1;\n c: 0;\n }>,\n ]\n >,\n never\n>('=');\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"union.d.mts","sourceRoot":"","sources":["../../src/compose/union.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,
|
|
1
|
+
{"version":3,"file":"union.d.mts","sourceRoot":"","sources":["../../src/compose/union.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAKL,KAAK,OAAO,EAEZ,KAAK,IAAI,EACT,KAAK,MAAM,EAEZ,MAAM,aAAa,CAAC;AAWrB,eAAO,MAAM,KAAK,GAAI,KAAK,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,SACvD,KAAK,YACF,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;CAC/B,CAAC,CACH,KACA,SAAS,CAAC,KAAK,CA0KjB,CAAC;AA0EF,KAAK,SAAS,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,IAAI,IAAI,CACzD,cAAc,CAAC,KAAK,CAAC,CACtB,CAAC;AAEF,KAAK,cAAc,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,IACtD,kBAAkB,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;AAE/C,kBAAU,kBAAkB,CAAC;IAC3B,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,IACjE,WAAW,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAEnC,KAAK,WAAW,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;;CACvE"}
|
package/dist/compose/union.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { memoizeFunction, Arr, Result } from 'ts-data-forge';
|
|
2
|
-
import { hasRecordInternals } from '../type.mjs';
|
|
2
|
+
import { hasUnionInternals, hasRecordInternals, flattenShapeStructure, hasTupleInternals } from '../type.mjs';
|
|
3
3
|
import { createAssertFn } from '../utils/create-assert-fn.mjs';
|
|
4
4
|
import { createCastFn } from '../utils/create-cast-fn.mjs';
|
|
5
5
|
import { createIsFn } from '../utils/create-is-fn.mjs';
|
|
6
|
+
import { UNION_MEMBER_LISTING_MAX_LENGTH, runtimeTypeNameOf } from '../utils/validation-error.mjs';
|
|
6
7
|
import { toUnionString } from '../utils/to-union-string.mjs';
|
|
7
8
|
|
|
8
9
|
const union = (types, options) => {
|
|
@@ -10,25 +11,74 @@ const union = (types, options) => {
|
|
|
10
11
|
() => options?.defaultType ?? types[0]
|
|
11
12
|
);
|
|
12
13
|
const typeNameFilled = options?.typeName ?? `(${toUnionString(types.map((a) => a.typeName))})`;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
)
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
const memberTypes = types.flatMap(
|
|
15
|
+
(t) => hasUnionInternals(t) ? t.memberTypes : [t]
|
|
16
|
+
);
|
|
17
|
+
const validate = (a) => {
|
|
18
|
+
if (memberTypes.some((t) => t.is(a))) {
|
|
19
|
+
return Result.ok(a);
|
|
20
|
+
}
|
|
21
|
+
const memberTypeNames = types.map((t) => t.typeName);
|
|
22
|
+
if (toUnionString(memberTypeNames).length <= UNION_MEMBER_LISTING_MAX_LENGTH) {
|
|
23
|
+
return Result.err([
|
|
24
|
+
{
|
|
25
|
+
path: [],
|
|
26
|
+
actualValue: a,
|
|
27
|
+
expectedType: typeNameFilled,
|
|
28
|
+
typeName: typeNameFilled,
|
|
29
|
+
details: {
|
|
30
|
+
kind: "union",
|
|
31
|
+
typeNames: memberTypeNames
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
const actualCategory = runtimeTypeNameOf(a);
|
|
37
|
+
const candidates = memberTypes.filter((t) => runtimeTypeNameOf(t.defaultValue) === actualCategory).map((t) => evaluateMember(t, a));
|
|
38
|
+
if (!Arr.isNonEmpty(candidates)) {
|
|
39
|
+
return Result.err([
|
|
40
|
+
{
|
|
41
|
+
path: [],
|
|
42
|
+
actualValue: a,
|
|
43
|
+
expectedType: typeNameFilled,
|
|
44
|
+
typeName: typeNameFilled,
|
|
45
|
+
details: {
|
|
46
|
+
kind: "union-category-mismatch",
|
|
47
|
+
memberCount: memberTypes.length,
|
|
48
|
+
memberCategories: Arr.uniq(
|
|
49
|
+
memberTypes.map((t) => runtimeTypeNameOf(t.defaultValue))
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]);
|
|
26
54
|
}
|
|
27
|
-
|
|
55
|
+
const closest = Arr.minBy(
|
|
56
|
+
candidates,
|
|
57
|
+
(candidate) => candidate,
|
|
58
|
+
compareByCloseness
|
|
59
|
+
).value;
|
|
60
|
+
const equallyClose = candidates.filter(
|
|
61
|
+
(c) => c !== closest && compareByCloseness(c, closest) === 0
|
|
62
|
+
);
|
|
63
|
+
return Result.err(
|
|
64
|
+
Arr.toUnshifted({
|
|
65
|
+
path: [],
|
|
66
|
+
actualValue: a,
|
|
67
|
+
expectedType: typeNameFilled,
|
|
68
|
+
typeName: typeNameFilled,
|
|
69
|
+
details: {
|
|
70
|
+
kind: "union-closest-member",
|
|
71
|
+
memberCount: memberTypes.length,
|
|
72
|
+
closestMemberTypeName: closest.typeName,
|
|
73
|
+
equallyCloseMemberTypeNames: equallyClose.map((c) => c.typeName)
|
|
74
|
+
}
|
|
75
|
+
})(closest.errors)
|
|
76
|
+
);
|
|
77
|
+
};
|
|
28
78
|
const is = createIsFn(validate);
|
|
29
79
|
const fill = (a) => is(a) ? a : getDefaultType().fill(a);
|
|
30
80
|
const prune = (a) => {
|
|
31
|
-
const matched =
|
|
81
|
+
const matched = memberTypes.find((t) => t.is(a));
|
|
32
82
|
return matched === void 0 ? a : (
|
|
33
83
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
34
84
|
matched.prune(a)
|
|
@@ -44,7 +94,9 @@ const union = (types, options) => {
|
|
|
44
94
|
validate,
|
|
45
95
|
is,
|
|
46
96
|
assertIs: createAssertFn(validate),
|
|
47
|
-
cast: createCastFn(validate)
|
|
97
|
+
cast: createCastFn(validate),
|
|
98
|
+
// Lets a union that takes this one as a member flatten it in.
|
|
99
|
+
memberTypes
|
|
48
100
|
};
|
|
49
101
|
if (types.every(hasRecordInternals)) {
|
|
50
102
|
const shapeStructures = Arr.map(types, (t) => t.shapeStructure);
|
|
@@ -60,6 +112,31 @@ const union = (types, options) => {
|
|
|
60
112
|
}
|
|
61
113
|
return baseType;
|
|
62
114
|
};
|
|
115
|
+
const evaluateMember = (memberType, a) => {
|
|
116
|
+
const errors = validationErrorsOf(memberType.validate(a));
|
|
117
|
+
const satisfiedCheckCount = Math.max(
|
|
118
|
+
0,
|
|
119
|
+
checkCountOf(memberType) - errors.length
|
|
120
|
+
);
|
|
121
|
+
return {
|
|
122
|
+
typeName: memberType.typeName,
|
|
123
|
+
errors,
|
|
124
|
+
satisfiedCheckCount,
|
|
125
|
+
score: errors.length - satisfiedCheckCount
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
const compareByCloseness = (x, y) => x.score !== y.score ? x.score - y.score : y.satisfiedCheckCount - x.satisfiedCheckCount;
|
|
129
|
+
const checkCountOf = (memberType) => {
|
|
130
|
+
if (hasRecordInternals(memberType)) {
|
|
131
|
+
const shape = flattenShapeStructure(memberType.shapeStructure);
|
|
132
|
+
return shape === void 0 ? 1 : Object.keys(shape).length;
|
|
133
|
+
}
|
|
134
|
+
if (hasTupleInternals(memberType)) {
|
|
135
|
+
return memberType.elementTypes.length;
|
|
136
|
+
}
|
|
137
|
+
return 1;
|
|
138
|
+
};
|
|
139
|
+
const validationErrorsOf = (result) => Result.isErr(result) ? result.value : [];
|
|
63
140
|
|
|
64
141
|
export { union };
|
|
65
142
|
//# sourceMappingURL=union.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"union.mjs","sources":["../../src/compose/union.mts"],"sourcesContent":["import { Arr, Result, expectType, memoizeFunction } from 'ts-data-forge';\nimport { type ArrayElement, type NonEmptyTuple } from 'ts-type-forge';\nimport {\n hasRecordInternals,\n type AnyType,\n type ExcessPropertyOption,\n type Type,\n type TypeOf,\n} from '../type.mjs';\nimport {\n createAssertFn,\n createCastFn,\n createIsFn,\n toUnionString,\n type ValidationError,\n} from '../utils/index.mjs';\n\nexport const union = <const Types extends NonEmptyTuple<AnyType>>(\n types: Types,\n options?: Partial<\n Readonly<{\n typeName: string;\n defaultType: UnionType<Types>;\n }>\n >,\n): UnionType<Types> => {\n type T = UnionTypeValue<Types>;\n\n const getDefaultType = memoizeFunction(\n (): UnionType<Types> =>\n options?.defaultType ?? (types[0] as UnionType<Types>),\n );\n\n const typeNameFilled: string =\n options?.typeName ?? `(${toUnionString(types.map((a) => a.typeName))})`;\n\n const validate: Type<T>['validate'] = (a) =>\n types.some((t) => t.is(a))\n ? // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n Result.ok(a as T)\n : Result.err([\n {\n path: [],\n actualValue: a,\n expectedType: typeNameFilled,\n typeName: typeNameFilled,\n details: {\n kind: 'union',\n typeNames: types.map((t) => t.typeName),\n },\n } satisfies ValidationError,\n ]);\n\n const is = createIsFn<T>(validate);\n\n const fill: Type<T>['fill'] = (a) => (is(a) ? a : getDefaultType().fill(a));\n\n // Prunes with the first member type that matches the value.\n const prune = (a: T): T => {\n const matched = types.find((t) => t.is(a));\n\n return matched === undefined\n ? a\n : // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n (matched.prune(a) as T);\n };\n\n const baseType: Type<T> = {\n typeName: typeNameFilled,\n get defaultValue() {\n return getDefaultType().defaultValue;\n },\n fill,\n prune,\n validate,\n is,\n assertIs: createAssertFn(validate),\n cast: createCastFn(validate),\n } as const;\n\n // If all types are records, add RecordTypeInternals with union structure\n if (types.every(hasRecordInternals)) {\n const shapeStructures = Arr.map(types, (t) => t.shapeStructure);\n\n const excessProperty: ExcessPropertyOption = Arr.some(\n types,\n (t) => t.excessProperty === 'reject',\n )\n ? 'reject'\n : 'allow';\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return {\n ...baseType,\n shapeStructure: Arr.isFixedLengthTuple(shapeStructures, 1)\n ? shapeStructures[0]\n : ({ kind: 'union', variants: shapeStructures } as const),\n excessProperty,\n } as UnionType<Types>;\n }\n\n return baseType;\n};\n\ntype UnionType<Types extends NonEmptyTuple<AnyType>> = Type<\n UnionTypeValue<Types>\n>;\n\ntype UnionTypeValue<Types extends NonEmptyTuple<AnyType>> =\n TsFortressInternal.UnionTypeValueImpl<Types>;\n\nnamespace TsFortressInternal {\n export type UnionTypeValueImpl<Types extends NonEmptyTuple<AnyType>> =\n UnwrapUnion<ArrayElement<Types>>;\n\n type UnwrapUnion<T extends AnyType> = T extends T ? TypeOf<T> : never;\n}\n\nexpectType<\n UnionType<\n readonly [Type<Readonly<{ a: 0; b: 0 }>>, Type<Readonly<{ b: 0; c: 0 }>>]\n >,\n Type<Readonly<{ a: 0; b: 0 } | { b: 0; c: 0 }>>\n>('=');\n\nexpectType<\n UnionType<\n readonly [\n Type<Readonly<{ a: 0; b: 0 }>>,\n Type<Readonly<{ b: 0; c: 0 }>>,\n Type<Readonly<{ e: 0; f: 0 }>>,\n ]\n >,\n Type<Readonly<{ a: 0; b: 0 } | { b: 0; c: 0 } | { e: 0; f: 0 }>>\n>('=');\n"],"names":[],"mappings":";;;;;;;MAiBa,KAAA,GAAQ,CACnB,KAAA,EACA,OAAA,KAMqB;AAGrB,EAAA,MAAM,cAAA,GAAiB,eAAA;AAAA,IACrB,MACE,OAAA,EAAS,WAAA,IAAgB,KAAA,CAAM,CAAC;AAAA,GACpC;AAEA,EAAA,MAAM,cAAA,GACJ,OAAA,EAAS,QAAA,IAAY,CAAA,CAAA,EAAI,aAAA,CAAc,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,QAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AAEtE,EAAA,MAAM,QAAA,GAAgC,CAAC,CAAA,KACrC,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,CAAG,CAAC,CAAC,CAAA;AAAA;AAAA,IAErB,MAAA,CAAO,GAAG,CAAM;AAAA,MAChB,OAAO,GAAA,CAAI;AAAA,IACT;AAAA,MACE,MAAM,EAAC;AAAA,MACP,WAAA,EAAa,CAAA;AAAA,MACb,YAAA,EAAc,cAAA;AAAA,MACd,QAAA,EAAU,cAAA;AAAA,MACV,OAAA,EAAS;AAAA,QACP,IAAA,EAAM,OAAA;AAAA,QACN,WAAW,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,QAAQ;AAAA;AACxC;AACF,GACD,CAAA;AAEP,EAAA,MAAM,EAAA,GAAK,WAAc,QAAQ,CAAA;AAEjC,EAAA,MAAM,IAAA,GAAwB,CAAC,CAAA,KAAO,EAAA,CAAG,CAAC,IAAI,CAAA,GAAI,cAAA,EAAe,CAAE,IAAA,CAAK,CAAC,CAAA;AAGzE,EAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,KAAY;AACzB,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,CAAG,CAAC,CAAC,CAAA;AAEzC,IAAA,OAAO,YAAY,MAAA,GACf,CAAA;AAAA;AAAA,MAEC,OAAA,CAAQ,MAAM,CAAC;AAAA,KAAA;AAAA,EAAA,CACtB;AAEA,EAAA,MAAM,QAAA,GAAoB;AAAA,IACxB,QAAA,EAAU,cAAA;AAAA,IACV,IAAI,YAAA,GAAe;AACjB,MAAA,OAAO,gBAAe,CAAE,YAAA;AAAA,IAAA,CAC1B;AAAA,IACA,IAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,EAAA;AAAA,IACA,QAAA,EAAU,eAAe,QAAQ,CAAA;AAAA,IACjC,IAAA,EAAM,aAAa,QAAQ;AAAA,GAC7B;AAGA,EAAA,IAAI,KAAA,CAAM,KAAA,CAAM,kBAAkB,CAAA,EAAG;AACnC,IAAA,MAAM,kBAAkB,GAAA,CAAI,GAAA,CAAI,OAAO,CAAC,CAAA,KAAM,EAAE,cAAc,CAAA;AAE9D,IAAA,MAAM,iBAAuC,GAAA,CAAI,IAAA;AAAA,MAC/C,KAAA;AAAA,MACA,CAAC,CAAA,KAAM,CAAA,CAAE,cAAA,KAAmB;AAAA,QAE1B,QAAA,GACA,OAAA;AAGJ,IAAA,OAAO;AAAA,MACL,GAAG,QAAA;AAAA,MACH,cAAA,EAAgB,GAAA,CAAI,kBAAA,CAAmB,eAAA,EAAiB,CAAC,CAAA,GACrD,eAAA,CAAgB,CAAC,CAAA,GAChB,EAAE,IAAA,EAAM,OAAA,EAAS,UAAU,eAAA,EAAgB;AAAA,MAChD;AAAA,KACF;AAAA,EAAA;AAGF,EAAA,OAAO,QAAA;AACT;;;;"}
|
|
1
|
+
{"version":3,"file":"union.mjs","sources":["../../src/compose/union.mts"],"sourcesContent":["import { Arr, expectType, memoizeFunction, Result } from 'ts-data-forge';\nimport { type ArrayElement, type NonEmptyTuple } from 'ts-type-forge';\nimport {\n flattenShapeStructure,\n hasRecordInternals,\n hasTupleInternals,\n hasUnionInternals,\n type AnyType,\n type ExcessPropertyOption,\n type Type,\n type TypeOf,\n type UnionTypeInternals,\n} from '../type.mjs';\nimport {\n createAssertFn,\n createCastFn,\n createIsFn,\n runtimeTypeNameOf,\n toUnionString,\n UNION_MEMBER_LISTING_MAX_LENGTH,\n type ValidationError,\n} from '../utils/index.mjs';\n\nexport const union = <const Types extends NonEmptyTuple<AnyType>>(\n types: Types,\n options?: Partial<\n Readonly<{\n typeName: string;\n defaultType: UnionType<Types>;\n }>\n >,\n): UnionType<Types> => {\n type T = UnionTypeValue<Types>;\n\n const getDefaultType = memoizeFunction(\n (): UnionType<Types> =>\n options?.defaultType ?? (types[0] as UnionType<Types>),\n );\n\n const typeNameFilled: string =\n options?.typeName ?? `(${toUnionString(types.map((a) => a.typeName))})`;\n\n /**\n * A union nested directly inside this one contributes its own members\n * instead of itself. `A | (B | C)` and `A | B | C` describe the same set of\n * values, so this changes nothing about what the union accepts.\n *\n * It was introduced for the error path. Reporting the member closest to a\n * rejected value starts by filing each member under a single runtime type\n * category, taken from its default value — and a nested union has only one\n * default value. Unflattened, `union([union([literal('alpha'), literal(42)]),\n * ...])` is filed under `string`, so validating `41` finds no candidate at\n * all and the union falls back to listing the categories it accepts.\n * Flattened, the `42` member competes on its own and is reported.\n *\n * Two things deliberately stay unflattened: `typeName` and the member\n * listing use the members as they were written, so a nested union that\n * carries a name of its own keeps it; and a `recursion` member answers\n * {@link hasUnionInternals} without running its definition, so a type\n * defined in terms of itself stays a single opaque member.\n *\n * See `documents/union-closest-member-heuristic.md`.\n */\n const memberTypes: readonly AnyType[] = types.flatMap((t) =>\n hasUnionInternals(t) ? t.memberTypes : [t],\n );\n\n const validate: Type<T>['validate'] = (a) => {\n if (memberTypes.some((t) => t.is(a))) {\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return Result.ok(a as T);\n }\n\n const memberTypeNames = types.map((t) => t.typeName);\n\n if (\n toUnionString(memberTypeNames).length <= UNION_MEMBER_LISTING_MAX_LENGTH\n ) {\n return Result.err([\n {\n path: [],\n actualValue: a,\n expectedType: typeNameFilled,\n typeName: typeNameFilled,\n details: {\n kind: 'union',\n typeNames: memberTypeNames,\n },\n } satisfies ValidationError,\n ]);\n }\n\n // The member listing is too long to be a readable error message, so\n // narrow the report down to the members whose runtime type category\n // (derived from each member's default value) matches the value.\n const actualCategory = runtimeTypeNameOf(a);\n\n const candidates = memberTypes\n .filter((t) => runtimeTypeNameOf(t.defaultValue) === actualCategory)\n .map((t) => evaluateMember(t, a));\n\n if (!Arr.isNonEmpty(candidates)) {\n return Result.err([\n {\n path: [],\n actualValue: a,\n expectedType: typeNameFilled,\n typeName: typeNameFilled,\n details: {\n kind: 'union-category-mismatch',\n memberCount: memberTypes.length,\n memberCategories: Arr.uniq(\n memberTypes.map((t) => runtimeTypeNameOf(t.defaultValue)),\n ),\n },\n } satisfies ValidationError,\n ]);\n }\n\n // The closest member is the one with the lowest similarity score (see\n // `evaluateMember`); its errors explain the mismatch far better than the\n // full member listing would. Members that tie with it are named alongside\n // it, and the first of them (in declaration order, as in `prune` and\n // `fill`) is the one whose errors are reported.\n const closest = Arr.minBy(\n candidates,\n (candidate) => candidate,\n compareByCloseness,\n ).value;\n\n const equallyClose = candidates.filter(\n (c) => c !== closest && compareByCloseness(c, closest) === 0,\n );\n\n return Result.err(\n Arr.toUnshifted({\n path: [],\n actualValue: a,\n expectedType: typeNameFilled,\n typeName: typeNameFilled,\n details: {\n kind: 'union-closest-member',\n memberCount: memberTypes.length,\n closestMemberTypeName: closest.typeName,\n equallyCloseMemberTypeNames: equallyClose.map((c) => c.typeName),\n },\n } satisfies ValidationError)(closest.errors),\n );\n };\n\n const is = createIsFn<T>(validate);\n\n const fill: Type<T>['fill'] = (a) => (is(a) ? a : getDefaultType().fill(a));\n\n // Prunes with the first member type that matches the value.\n const prune = (a: T): T => {\n const matched = memberTypes.find((t) => t.is(a));\n\n return matched === undefined\n ? a\n : // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n (matched.prune(a) as T);\n };\n\n const baseType = {\n typeName: typeNameFilled,\n get defaultValue() {\n return getDefaultType().defaultValue;\n },\n fill,\n prune,\n validate,\n is,\n assertIs: createAssertFn(validate),\n cast: createCastFn(validate),\n // Lets a union that takes this one as a member flatten it in.\n memberTypes,\n } as const satisfies Type<T> & UnionTypeInternals;\n\n // If all types are records, add RecordTypeInternals with union structure\n if (types.every(hasRecordInternals)) {\n const shapeStructures = Arr.map(types, (t) => t.shapeStructure);\n\n const excessProperty: ExcessPropertyOption = Arr.some(\n types,\n (t) => t.excessProperty === 'reject',\n )\n ? 'reject'\n : 'allow';\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return {\n ...baseType,\n shapeStructure: Arr.isFixedLengthTuple(shapeStructures, 1)\n ? shapeStructures[0]\n : ({ kind: 'union', variants: shapeStructures } as const),\n excessProperty,\n } as UnionType<Types>;\n }\n\n return baseType as UnionType<Types>;\n};\n\n/**\n * How close a member is to a value it rejected.\n *\n * `score` is the number of checks the member failed minus the number it\n * passed, so a member is credited for what the value does satisfy. Plain error\n * counting would let a small unrelated member (one missing key) outrank a large\n * member the value nearly satisfies (two wrong fields out of ten), while a\n * ratio would do the opposite and let a large member win with a long list of\n * errors just because it also has many keys. Subtracting keeps both the failed\n * and the passed checks in absolute terms.\n *\n * Members with the same score are ordered by how much they satisfied, so the\n * one that recognized more of the value comes first.\n *\n * See `documents/union-closest-member-heuristic.md` for the scenarios these\n * rules were chosen against.\n */\ntype MemberCloseness = Readonly<{\n typeName: string;\n errors: readonly ValidationError[];\n satisfiedCheckCount: number;\n score: number;\n}>;\n\nconst evaluateMember = (memberType: AnyType, a: unknown): MemberCloseness => {\n const errors = validationErrorsOf(memberType.validate(a));\n\n // A member cannot pass fewer than zero checks: a record that rejects excess\n // properties can report more errors than it has keys.\n const satisfiedCheckCount = Math.max(\n 0,\n checkCountOf(memberType) - errors.length,\n );\n\n return {\n typeName: memberType.typeName,\n errors,\n satisfiedCheckCount,\n score: errors.length - satisfiedCheckCount,\n };\n};\n\nconst compareByCloseness = (x: MemberCloseness, y: MemberCloseness): number =>\n x.score !== y.score\n ? x.score - y.score\n : y.satisfiedCheckCount - x.satisfiedCheckCount;\n\n/**\n * How many checks a member performs on a value: one per key for a record, one\n * per element for a tuple, and a single check for everything else, which either\n * matches or does not. A record whose shape cannot be flattened to a single set\n * of keys (a union of shapes) also counts as one check.\n */\nconst checkCountOf = (memberType: AnyType): number => {\n if (hasRecordInternals(memberType)) {\n const shape = flattenShapeStructure(memberType.shapeStructure);\n\n return shape === undefined ? 1 : Object.keys(shape).length;\n }\n\n if (hasTupleInternals(memberType)) {\n return memberType.elementTypes.length;\n }\n\n return 1;\n};\n\nconst validationErrorsOf = (\n result: Result<unknown, readonly ValidationError[]>,\n): readonly ValidationError[] =>\n Result.isErr(result) ? result.value : ([] as const);\n\ntype UnionType<Types extends NonEmptyTuple<AnyType>> = Type<\n UnionTypeValue<Types>\n>;\n\ntype UnionTypeValue<Types extends NonEmptyTuple<AnyType>> =\n TsFortressInternal.UnionTypeValueImpl<Types>;\n\nnamespace TsFortressInternal {\n export type UnionTypeValueImpl<Types extends NonEmptyTuple<AnyType>> =\n UnwrapUnion<ArrayElement<Types>>;\n\n type UnwrapUnion<T extends AnyType> = T extends T ? TypeOf<T> : never;\n}\n\nexpectType<\n UnionType<\n readonly [Type<Readonly<{ a: 0; b: 0 }>>, Type<Readonly<{ b: 0; c: 0 }>>]\n >,\n Type<Readonly<{ a: 0; b: 0 } | { b: 0; c: 0 }>>\n>('=');\n\nexpectType<\n UnionType<\n readonly [\n Type<Readonly<{ a: 0; b: 0 }>>,\n Type<Readonly<{ b: 0; c: 0 }>>,\n Type<Readonly<{ e: 0; f: 0 }>>,\n ]\n >,\n Type<Readonly<{ a: 0; b: 0 } | { b: 0; c: 0 } | { e: 0; f: 0 }>>\n>('=');\n"],"names":[],"mappings":"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"}
|
package/dist/entry-point.mjs
CHANGED
|
@@ -16,7 +16,9 @@ export { brandedString, simpleBrandedString } from './brand/branded-string.mjs';
|
|
|
16
16
|
export { intersection } from './compose/intersection.mjs';
|
|
17
17
|
export { union } from './compose/union.mjs';
|
|
18
18
|
export { enumType } from './enum/enum.mjs';
|
|
19
|
+
export { intRangeInclusive } from './enum/int-range-inclusive.mjs';
|
|
19
20
|
export { intRange } from './enum/int-range.mjs';
|
|
21
|
+
export { uintRangeInclusive } from './enum/uint-range-inclusive.mjs';
|
|
20
22
|
export { uintRange } from './enum/uint-range.mjs';
|
|
21
23
|
export { literal } from './other-types/literal.mjs';
|
|
22
24
|
export { MapType } from './other-types/map.mjs';
|
|
@@ -81,5 +83,5 @@ export { createIsFn } from './utils/create-is-fn.mjs';
|
|
|
81
83
|
export { createPrimitiveType } from './utils/create-primitive-type.mjs';
|
|
82
84
|
export { createType } from './utils/create-type.mjs';
|
|
83
85
|
export { toIntersectionString, toUnionKeyString, toUnionString } from './utils/to-union-string.mjs';
|
|
84
|
-
export { createPrimitiveValidationError, prependIndexToValidationErrors, prependPathToValidationErrors, validationErrorToMessage, validationErrorsToMessages } from './utils/validation-error.mjs';
|
|
86
|
+
export { UNION_MEMBER_LISTING_MAX_LENGTH, createPrimitiveValidationError, prependIndexToValidationErrors, prependPathToValidationErrors, runtimeTypeNameOf, validationErrorToMessage, validationErrorsToMessages } from './utils/validation-error.mjs';
|
|
85
87
|
//# sourceMappingURL=entry-point.mjs.map
|
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/enum/index.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/enum/index.mts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/enum/index.mts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kBAAkB,CAAC"}
|
package/dist/enum/index.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { enumType } from './enum.mjs';
|
|
2
|
+
export { intRangeInclusive } from './int-range-inclusive.mjs';
|
|
2
3
|
export { intRange } from './int-range.mjs';
|
|
4
|
+
export { uintRangeInclusive } from './uint-range-inclusive.mjs';
|
|
3
5
|
export { uintRange } from './uint-range.mjs';
|
|
4
6
|
//# sourceMappingURL=index.mjs.map
|
package/dist/enum/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,13 @@
|
|
|
1
|
+
import { type Int11, type IntRangeInclusive } from 'ts-type-forge';
|
|
2
|
+
import { type Type } from '../type.mjs';
|
|
3
|
+
export type { IntRangeInclusive } from 'ts-type-forge';
|
|
4
|
+
export declare const intRangeInclusive: <Start extends Int11, End extends Int11>(...args: readonly [start: Start, end: End, options?: Readonly<{
|
|
5
|
+
typeName?: string;
|
|
6
|
+
defaultValue?: IntRangeInclusive<Start, End>;
|
|
7
|
+
}>] | readonly [options: Readonly<{
|
|
8
|
+
start: Start;
|
|
9
|
+
end: End;
|
|
10
|
+
typeName?: string;
|
|
11
|
+
defaultValue?: IntRangeInclusive<Start, End>;
|
|
12
|
+
}>]) => Type<IntRangeInclusive<Start, End>>;
|
|
13
|
+
//# sourceMappingURL=int-range-inclusive.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"int-range-inclusive.d.mts","sourceRoot":"","sources":["../../src/enum/int-range-inclusive.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAQxC,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,eAAO,MAAM,iBAAiB,GAAI,KAAK,SAAS,KAAK,EAAE,GAAG,SAAS,KAAK,WAElE,SAAS,CACP,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;CAC9C,CAAC,CACH,GACD,SAAS,CACP,OAAO,EAAE,QAAQ,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;CAC9C,CAAC,CACH,KACJ,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAcpC,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { memoizeFunction, isNumber, Num, Result } from 'ts-data-forge';
|
|
2
|
+
import { createAssertFn } from '../utils/create-assert-fn.mjs';
|
|
3
|
+
import { createCastFn } from '../utils/create-cast-fn.mjs';
|
|
4
|
+
import { createIsFn } from '../utils/create-is-fn.mjs';
|
|
5
|
+
import '@sindresorhus/is';
|
|
6
|
+
|
|
7
|
+
const intRangeInclusive = (...args) => {
|
|
8
|
+
switch (args.length) {
|
|
9
|
+
case 1:
|
|
10
|
+
return intRangeInclusiveImpl(args[0].start, args[0].end, {
|
|
11
|
+
defaultValue: args[0].defaultValue,
|
|
12
|
+
typeName: args[0].typeName
|
|
13
|
+
});
|
|
14
|
+
case 2:
|
|
15
|
+
return intRangeInclusiveImpl(args[0], args[1]);
|
|
16
|
+
case 3:
|
|
17
|
+
return intRangeInclusiveImpl(args[0], args[1], args[2]);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const intRangeInclusiveImpl = (start, end, options) => {
|
|
21
|
+
const typeNameFilled = options?.typeName ?? `intRangeInclusive(${start}, ${end})`;
|
|
22
|
+
const getDefaultValue = memoizeFunction(
|
|
23
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
24
|
+
() => options?.defaultValue ?? start
|
|
25
|
+
);
|
|
26
|
+
const validate = (a) => {
|
|
27
|
+
if (!(isNumber(a) && Number.isSafeInteger(a) && Num.isInRangeInclusive(start, end)(a))) {
|
|
28
|
+
return Result.err([
|
|
29
|
+
{
|
|
30
|
+
path: [],
|
|
31
|
+
actualValue: a,
|
|
32
|
+
expectedType: typeNameFilled,
|
|
33
|
+
typeName: typeNameFilled,
|
|
34
|
+
details: {
|
|
35
|
+
kind: "integer-range-inclusive",
|
|
36
|
+
start,
|
|
37
|
+
endInclusive: end
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
return Result.ok(a);
|
|
43
|
+
};
|
|
44
|
+
const is = createIsFn(validate);
|
|
45
|
+
const fill = (a) => is(a) ? a : getDefaultValue();
|
|
46
|
+
return {
|
|
47
|
+
typeName: typeNameFilled,
|
|
48
|
+
get defaultValue() {
|
|
49
|
+
return getDefaultValue();
|
|
50
|
+
},
|
|
51
|
+
fill,
|
|
52
|
+
prune: (a) => a,
|
|
53
|
+
validate,
|
|
54
|
+
is,
|
|
55
|
+
assertIs: createAssertFn(validate),
|
|
56
|
+
cast: createCastFn(validate)
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export { intRangeInclusive };
|
|
61
|
+
//# sourceMappingURL=int-range-inclusive.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"int-range-inclusive.mjs","sources":["../../src/enum/int-range-inclusive.mts"],"sourcesContent":["import { isNumber, memoizeFunction, Num, Result } from 'ts-data-forge';\nimport { type Int11, type IntRangeInclusive } from 'ts-type-forge';\nimport { type Type } from '../type.mjs';\nimport {\n createAssertFn,\n createCastFn,\n createIsFn,\n type ValidationError,\n} from '../utils/index.mjs';\n\nexport type { IntRangeInclusive } from 'ts-type-forge';\n\nexport const intRangeInclusive = <Start extends Int11, End extends Int11>(\n ...args:\n | readonly [\n start: Start,\n end: End,\n options?: Readonly<{\n typeName?: string;\n defaultValue?: IntRangeInclusive<Start, End>;\n }>,\n ]\n | readonly [\n options: Readonly<{\n start: Start;\n end: End;\n typeName?: string;\n defaultValue?: IntRangeInclusive<Start, End>;\n }>,\n ]\n): Type<IntRangeInclusive<Start, End>> => {\n switch (args.length) {\n case 1:\n return intRangeInclusiveImpl(args[0].start, args[0].end, {\n defaultValue: args[0].defaultValue,\n typeName: args[0].typeName,\n });\n\n case 2:\n return intRangeInclusiveImpl(args[0], args[1]);\n\n case 3:\n return intRangeInclusiveImpl(args[0], args[1], args[2]);\n }\n};\n\nconst intRangeInclusiveImpl = <Start extends Int11, End extends Int11>(\n start: Start,\n end: End,\n options?: Readonly<{\n typeName?: string;\n defaultValue?: IntRangeInclusive<Start, End>;\n }>,\n): Type<IntRangeInclusive<Start, End>> => {\n type T = IntRangeInclusive<Start, End>;\n\n const typeNameFilled =\n options?.typeName ?? `intRangeInclusive(${start}, ${end})`;\n\n const getDefaultValue = memoizeFunction(\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n (): T => options?.defaultValue ?? (start as T),\n );\n\n const validate: Type<T>['validate'] = (a) => {\n if (!(\n isNumber(a) &&\n Number.isSafeInteger(a) &&\n Num.isInRangeInclusive(start, end)(a)\n )) {\n return Result.err([\n {\n path: [],\n actualValue: a,\n expectedType: typeNameFilled,\n typeName: typeNameFilled,\n details: {\n kind: 'integer-range-inclusive',\n start,\n endInclusive: end,\n },\n } satisfies ValidationError,\n ]);\n }\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return Result.ok(a as T);\n };\n\n const is = createIsFn<T>(validate);\n\n const fill: Type<T>['fill'] = (a) => (is(a) ? a : getDefaultValue());\n\n return {\n typeName: typeNameFilled,\n get defaultValue() {\n return getDefaultValue();\n },\n fill,\n prune: (a) => a,\n validate,\n is,\n assertIs: createAssertFn(validate),\n cast: createCastFn(validate),\n };\n};\n"],"names":[],"mappings":";;;;;;AAYO,MAAM,iBAAA,GAAoB,IAC5B,IAAA,KAiBqC;AACxC,EAAA,QAAQ,KAAK,MAAA;AAAQ,IACnB,KAAK,CAAA;AACH,MAAA,OAAO,qBAAA,CAAsB,KAAK,CAAC,CAAA,CAAE,OAAO,IAAA,CAAK,CAAC,EAAE,GAAA,EAAK;AAAA,QACvD,YAAA,EAAc,IAAA,CAAK,CAAC,CAAA,CAAE,YAAA;AAAA,QACtB,QAAA,EAAU,IAAA,CAAK,CAAC,CAAA,CAAE;AAAA,OACnB,CAAA;AAAA,IAEH,KAAK,CAAA;AACH,MAAA,OAAO,sBAAsB,IAAA,CAAK,CAAC,CAAA,EAAG,IAAA,CAAK,CAAC,CAAC,CAAA;AAAA,IAE/C,KAAK,CAAA;AACH,MAAA,OAAO,qBAAA,CAAsB,KAAK,CAAC,CAAA,EAAG,KAAK,CAAC,CAAA,EAAG,IAAA,CAAK,CAAC,CAAC,CAAA;AAAA;AAE5D;AAEA,MAAM,qBAAA,GAAwB,CAC5B,KAAA,EACA,GAAA,EACA,OAAA,KAIwC;AAGxC,EAAA,MAAM,iBACJ,OAAA,EAAS,QAAA,IAAY,CAAA,kBAAA,EAAqB,KAAK,KAAK,GAAG,CAAA,CAAA,CAAA;AAEzD,EAAA,MAAM,eAAA,GAAkB,eAAA;AAAA;AAAA,IAEtB,MAAS,SAAS,YAAA,IAAiB;AAAA,GACrC;AAEA,EAAA,MAAM,QAAA,GAAgC,CAAC,CAAA,KAAM;AAC3C,IAAA,IAAI,EACF,QAAA,CAAS,CAAC,CAAA,IACV,OAAO,aAAA,CAAc,CAAC,CAAA,IACtB,GAAA,CAAI,kBAAA,CAAmB,KAAA,EAAO,GAAG,CAAA,CAAE,CAAC,CAAA,CAAA,EACnC;AACD,MAAA,OAAO,OAAO,GAAA,CAAI;AAAA,QAChB;AAAA,UACE,MAAM,EAAC;AAAA,UACP,WAAA,EAAa,CAAA;AAAA,UACb,YAAA,EAAc,cAAA;AAAA,UACd,QAAA,EAAU,cAAA;AAAA,UACV,OAAA,EAAS;AAAA,YACP,IAAA,EAAM,yBAAA;AAAA,YACN,KAAA;AAAA,YACA,YAAA,EAAc;AAAA;AAChB;AACF,OACD,CAAA;AAAA,IACH;AAGA,IAAA,OAAO,MAAA,CAAO,GAAG,CAAM,CAAA;AAAA,EACzB,CAAA;AAEA,EAAA,MAAM,EAAA,GAAK,WAAc,QAAQ,CAAA;AAEjC,EAAA,MAAM,OAAwB,CAAC,CAAA,KAAO,GAAG,CAAC,CAAA,GAAI,IAAI,eAAA,EAAgB;AAElE,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,cAAA;AAAA,IACV,IAAI,YAAA,GAAe;AACjB,MAAA,OAAO,eAAA,EAAgB;AAAA,IACzB,CAAA;AAAA,IACA,IAAA;AAAA,IACA,KAAA,EAAO,CAAC,CAAA,KAAM,CAAA;AAAA,IACd,QAAA;AAAA,IACA,EAAA;AAAA,IACA,QAAA,EAAU,eAAe,QAAQ,CAAA;AAAA,IACjC,IAAA,EAAM,aAAa,QAAQ;AAAA,GAC7B;AACF,CAAA;;;;"}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Int11, type IntRange } from 'ts-type-forge';
|
|
2
2
|
import { type Type } from '../type.mjs';
|
|
3
|
-
export
|
|
3
|
+
export type { IntRange } from 'ts-type-forge';
|
|
4
|
+
export declare const intRange: <Start extends Int11, End extends Int11 | 1024>(...args: readonly [start: Start, end: End, options?: Readonly<{
|
|
5
|
+
typeName?: string;
|
|
6
|
+
defaultValue?: IntRange<Start, End>;
|
|
7
|
+
}>] | readonly [options: Readonly<{
|
|
4
8
|
start: Start;
|
|
5
9
|
end: End;
|
|
6
10
|
typeName?: string;
|
|
7
11
|
defaultValue?: IntRange<Start, End>;
|
|
8
|
-
}>) => Type<IntRange<Start, End>>;
|
|
9
|
-
type NegativeRange = NegativeIndex<128>;
|
|
10
|
-
type PositiveRange = IndexInclusive<128>;
|
|
11
|
-
export type IntRange<Start extends Int8, End extends Int8 | 128> = BoolAnd<TypeExtends<Start, PositiveRange>, TypeExtends<End, PositiveRange>> extends true ? UintRange<Start, End> : BoolAnd<TypeExtends<Start, NegativeRange>, TypeExtends<End, PositiveRange>> extends true ? NegativeIndex<NegativeToPositive<Start>> | Index<End> : BoolAnd<TypeExtends<Start, PositiveRange>, TypeExtends<End, NegativeRange>> extends true ? never : BoolAnd<TypeExtends<Start, NegativeRange>, TypeExtends<End, NegativeRange>> extends true ? Exclude<NegativeIndex<NegativeToPositive<Start>>, NegativeIndex<NegativeToPositive<End>>> : never;
|
|
12
|
-
type NegativeToPositive<N extends number> = `${N}` extends `-${infer D extends number}` ? D : never;
|
|
13
|
-
export {};
|
|
12
|
+
}>]) => Type<IntRange<Start, End>>;
|
|
14
13
|
//# sourceMappingURL=int-range.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"int-range.d.mts","sourceRoot":"","sources":["../../src/enum/int-range.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"int-range.d.mts","sourceRoot":"","sources":["../../src/enum/int-range.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAQxC,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,eAAO,MAAM,QAAQ,GAAI,KAAK,SAAS,KAAK,EAAE,GAAG,SAAS,KAAK,GAAG,IAAI,WAEhE,SAAS,CACP,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;CACrC,CAAC,CACH,GACD,SAAS,CACP,OAAO,EAAE,QAAQ,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;CACrC,CAAC,CACH,KACJ,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAc3B,CAAC"}
|
package/dist/enum/int-range.mjs
CHANGED
|
@@ -2,16 +2,26 @@ import { memoizeFunction, isNumber, Num, Result } from 'ts-data-forge';
|
|
|
2
2
|
import { createAssertFn } from '../utils/create-assert-fn.mjs';
|
|
3
3
|
import { createCastFn } from '../utils/create-cast-fn.mjs';
|
|
4
4
|
import { createIsFn } from '../utils/create-is-fn.mjs';
|
|
5
|
+
import '@sindresorhus/is';
|
|
5
6
|
|
|
6
|
-
const intRange = ({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
const intRange = (...args) => {
|
|
8
|
+
switch (args.length) {
|
|
9
|
+
case 1:
|
|
10
|
+
return intRangeImpl(args[0].start, args[0].end, {
|
|
11
|
+
defaultValue: args[0].defaultValue,
|
|
12
|
+
typeName: args[0].typeName
|
|
13
|
+
});
|
|
14
|
+
case 2:
|
|
15
|
+
return intRangeImpl(args[0], args[1]);
|
|
16
|
+
case 3:
|
|
17
|
+
return intRangeImpl(args[0], args[1], args[2]);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const intRangeImpl = (start, end, options) => {
|
|
21
|
+
const typeNameFilled = options?.typeName ?? `intRange(${start}, ${end})`;
|
|
12
22
|
const getDefaultValue = memoizeFunction(
|
|
13
23
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
14
|
-
() => options
|
|
24
|
+
() => options?.defaultValue ?? start
|
|
15
25
|
);
|
|
16
26
|
const validate = (a) => {
|
|
17
27
|
if (!(isNumber(a) && Number.isSafeInteger(a) && Num.isInRange(start, end)(a))) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"int-range.mjs","sources":["../../src/enum/int-range.mts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"int-range.mjs","sources":["../../src/enum/int-range.mts"],"sourcesContent":["import { isNumber, memoizeFunction, Num, Result } from 'ts-data-forge';\nimport { type Int11, type IntRange } from 'ts-type-forge';\nimport { type Type } from '../type.mjs';\nimport {\n createAssertFn,\n createCastFn,\n createIsFn,\n type ValidationError,\n} from '../utils/index.mjs';\n\nexport type { IntRange } from 'ts-type-forge';\n\nexport const intRange = <Start extends Int11, End extends Int11 | 1024>(\n ...args:\n | readonly [\n start: Start,\n end: End,\n options?: Readonly<{\n typeName?: string;\n defaultValue?: IntRange<Start, End>;\n }>,\n ]\n | readonly [\n options: Readonly<{\n start: Start;\n end: End;\n typeName?: string;\n defaultValue?: IntRange<Start, End>;\n }>,\n ]\n): Type<IntRange<Start, End>> => {\n switch (args.length) {\n case 1:\n return intRangeImpl(args[0].start, args[0].end, {\n defaultValue: args[0].defaultValue,\n typeName: args[0].typeName,\n });\n\n case 2:\n return intRangeImpl(args[0], args[1]);\n\n case 3:\n return intRangeImpl(args[0], args[1], args[2]);\n }\n};\n\nconst intRangeImpl = <Start extends Int11, End extends Int11 | 1024>(\n start: Start,\n end: End,\n options?: Readonly<{\n typeName?: string;\n defaultValue?: IntRange<Start, End>;\n }>,\n): Type<IntRange<Start, End>> => {\n type T = IntRange<Start, End>;\n\n const typeNameFilled = options?.typeName ?? `intRange(${start}, ${end})`;\n\n const getDefaultValue = memoizeFunction(\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n (): T => options?.defaultValue ?? (start as T),\n );\n\n const validate: Type<T>['validate'] = (a) => {\n if (!(\n isNumber(a) &&\n Number.isSafeInteger(a) &&\n Num.isInRange(start, end)(a)\n )) {\n return Result.err([\n {\n path: [],\n actualValue: a,\n expectedType: typeNameFilled,\n typeName: typeNameFilled,\n details: {\n kind: 'integer-range',\n start,\n endExclusive: end,\n },\n } satisfies ValidationError,\n ]);\n }\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return Result.ok(a as T);\n };\n\n const is = createIsFn<T>(validate);\n\n const fill: Type<T>['fill'] = (a) => (is(a) ? a : getDefaultValue());\n\n return {\n typeName: typeNameFilled,\n get defaultValue() {\n return getDefaultValue();\n },\n fill,\n prune: (a) => a,\n validate,\n is,\n assertIs: createAssertFn(validate),\n cast: createCastFn(validate),\n };\n};\n"],"names":[],"mappings":";;;;;;AAYO,MAAM,QAAA,GAAW,IACnB,IAAA,KAiB4B;AAC/B,EAAA,QAAQ,KAAK,MAAA;AAAQ,IACnB,KAAK,CAAA;AACH,MAAA,OAAO,YAAA,CAAa,KAAK,CAAC,CAAA,CAAE,OAAO,IAAA,CAAK,CAAC,EAAE,GAAA,EAAK;AAAA,QAC9C,YAAA,EAAc,IAAA,CAAK,CAAC,CAAA,CAAE,YAAA;AAAA,QACtB,QAAA,EAAU,IAAA,CAAK,CAAC,CAAA,CAAE;AAAA,OACnB,CAAA;AAAA,IAEH,KAAK,CAAA;AACH,MAAA,OAAO,aAAa,IAAA,CAAK,CAAC,CAAA,EAAG,IAAA,CAAK,CAAC,CAAC,CAAA;AAAA,IAEtC,KAAK,CAAA;AACH,MAAA,OAAO,YAAA,CAAa,KAAK,CAAC,CAAA,EAAG,KAAK,CAAC,CAAA,EAAG,IAAA,CAAK,CAAC,CAAC,CAAA;AAAA;AAEnD;AAEA,MAAM,YAAA,GAAe,CACnB,KAAA,EACA,GAAA,EACA,OAAA,KAI+B;AAG/B,EAAA,MAAM,iBAAiB,OAAA,EAAS,QAAA,IAAY,CAAA,SAAA,EAAY,KAAK,KAAK,GAAG,CAAA,CAAA,CAAA;AAErE,EAAA,MAAM,eAAA,GAAkB,eAAA;AAAA;AAAA,IAEtB,MAAS,SAAS,YAAA,IAAiB;AAAA,GACrC;AAEA,EAAA,MAAM,QAAA,GAAgC,CAAC,CAAA,KAAM;AAC3C,IAAA,IAAI,EACF,QAAA,CAAS,CAAC,CAAA,IACV,OAAO,aAAA,CAAc,CAAC,CAAA,IACtB,GAAA,CAAI,SAAA,CAAU,KAAA,EAAO,GAAG,CAAA,CAAE,CAAC,CAAA,CAAA,EAC1B;AACD,MAAA,OAAO,OAAO,GAAA,CAAI;AAAA,QAChB;AAAA,UACE,MAAM,EAAC;AAAA,UACP,WAAA,EAAa,CAAA;AAAA,UACb,YAAA,EAAc,cAAA;AAAA,UACd,QAAA,EAAU,cAAA;AAAA,UACV,OAAA,EAAS;AAAA,YACP,IAAA,EAAM,eAAA;AAAA,YACN,KAAA;AAAA,YACA,YAAA,EAAc;AAAA;AAChB;AACF,OACD,CAAA;AAAA,IACH;AAGA,IAAA,OAAO,MAAA,CAAO,GAAG,CAAM,CAAA;AAAA,EACzB,CAAA;AAEA,EAAA,MAAM,EAAA,GAAK,WAAc,QAAQ,CAAA;AAEjC,EAAA,MAAM,OAAwB,CAAC,CAAA,KAAO,GAAG,CAAC,CAAA,GAAI,IAAI,eAAA,EAAgB;AAElE,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,cAAA;AAAA,IACV,IAAI,YAAA,GAAe;AACjB,MAAA,OAAO,eAAA,EAAgB;AAAA,IACzB,CAAA;AAAA,IACA,IAAA;AAAA,IACA,KAAA,EAAO,CAAC,CAAA,KAAM,CAAA;AAAA,IACd,QAAA;AAAA,IACA,EAAA;AAAA,IACA,QAAA,EAAU,eAAe,QAAQ,CAAA;AAAA,IACjC,IAAA,EAAM,aAAa,QAAQ;AAAA,GAC7B;AACF,CAAA;;;;"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Uint11, type UintRangeInclusive } from 'ts-type-forge';
|
|
2
|
+
import { type Type } from '../type.mjs';
|
|
3
|
+
export type { UintRangeInclusive } from 'ts-type-forge';
|
|
4
|
+
export declare const uintRangeInclusive: <Start extends Uint11, End extends Uint11>(...args: readonly [start: Start, end: End, options?: Readonly<{
|
|
5
|
+
typeName?: string;
|
|
6
|
+
defaultValue?: UintRangeInclusive<Start, End>;
|
|
7
|
+
}>] | readonly [options: Readonly<{
|
|
8
|
+
start: Start;
|
|
9
|
+
end: End;
|
|
10
|
+
typeName?: string;
|
|
11
|
+
defaultValue?: UintRangeInclusive<Start, End>;
|
|
12
|
+
}>]) => Type<UintRangeInclusive<Start, End>>;
|
|
13
|
+
//# sourceMappingURL=uint-range-inclusive.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uint-range-inclusive.d.mts","sourceRoot":"","sources":["../../src/enum/uint-range-inclusive.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAQxC,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,eAAO,MAAM,kBAAkB,GAAI,KAAK,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,WAErE,SAAS,CACP,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;CAC/C,CAAC,CACH,GACD,SAAS,CACP,OAAO,EAAE,QAAQ,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;CAC/C,CAAC,CACH,KACJ,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,CAcrC,CAAC"}
|