ts-fortress 12.0.2 → 12.0.3

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":"intersection.d.mts","sourceRoot":"","sources":["../../src/compose/intersection.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAEL,KAAK,OAAO,EAEZ,KAAK,IAAI,EACT,KAAK,MAAM,EACZ,MAAM,aAAa,CAAC;AASrB,eAAO,MAAM,YAAY,GAAI,KAAK,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,SAC9D,KAAK,eACC,gBAAgB,CAAC,KAAK,CAAC,YAC1B,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CACH,KACA,gBAAgB,CAAC,KAAK,CAmFxB,CAAC;AAmCF,KAAK,gBAAgB,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,IAAI,IAAI,CAChE,qBAAqB,CAAC,KAAK,CAAC,CAC7B,CAAC;AAEF,KAAK,qBAAqB,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,IAC7D,kBAAkB,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC;AAEtD,kBAAU,kBAAkB,CAAC;IAC3B,MAAM,MAAM,yBAAyB,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,IACxE,YAAY,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE7C,KAAK,KAAK,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,GAClE,CAAC,GACD,KAAK,CAAC;;CACX;AA4BD,KAAK,cAAc,CAAC,KAAK,SAAS,SAAS,OAAO,EAAE,IAClD,kBAAkB,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;AAE3C,kBAAU,kBAAkB,CAAC;IAC3B,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,SAAS,OAAO,EAAE,IACzD,KAAK,SAAS,SAAS,EAAE,GACrB,SAAS,EAAE,GACX,KAAK,SAAS,SAAS,CAAC,MAAM,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,GAChD,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,GACvD,KAAK,CAAC;IAEd,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;;CACnD"}
1
+ {"version":3,"file":"intersection.d.mts","sourceRoot":"","sources":["../../src/compose/intersection.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAEL,KAAK,OAAO,EAEZ,KAAK,IAAI,EACT,KAAK,MAAM,EACZ,MAAM,aAAa,CAAC;AASrB,eAAO,MAAM,YAAY,GAAI,KAAK,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,SAC9D,KAAK,eACC,gBAAgB,CAAC,KAAK,CAAC,YAC1B,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CACH,KACA,gBAAgB,CAAC,KAAK,CAmFxB,CAAC;AA+BF,KAAK,gBAAgB,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,IAAI,IAAI,CAChE,qBAAqB,CAAC,KAAK,CAAC,CAC7B,CAAC;AAEF,KAAK,qBAAqB,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,IAC7D,kBAAkB,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC;AAEtD,kBAAU,kBAAkB,CAAC;IAC3B,MAAM,MAAM,yBAAyB,CAAC,KAAK,SAAS,aAAa,CAAC,OAAO,CAAC,IACxE,YAAY,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE7C,KAAK,KAAK,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,GAClE,CAAC,GACD,KAAK,CAAC;;CACX;AA4BD,KAAK,cAAc,CAAC,KAAK,SAAS,SAAS,OAAO,EAAE,IAClD,kBAAkB,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;AAE3C,kBAAU,kBAAkB,CAAC;IAC3B,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,SAAS,OAAO,EAAE,IACzD,KAAK,SAAS,SAAS,EAAE,GACrB,SAAS,EAAE,GACX,KAAK,SAAS,SAAS,CAAC,MAAM,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,GAChD,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,GACvD,KAAK,CAAC;IAEd,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;;CACnD"}
@@ -1,4 +1,4 @@
1
- import { Arr, Result, isRecord, tp, hasKey } from 'ts-data-forge';
1
+ import { Arr, Result, isRecord, Obj, hasKey } from 'ts-data-forge';
2
2
  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';
@@ -70,14 +70,11 @@ const intersection = (types, defaultType, options) => {
70
70
  };
71
71
  const mergePruned = (x, y) => {
72
72
  if (isRecord(x) && isRecord(y)) {
73
- const xMergedWithY = Object.fromEntries(
74
- Object.entries(x).map(
75
- ([k, xv]) => tp(k, hasKey(y, k) ? mergePruned(xv, y[k]) : xv)
76
- )
77
- );
78
- const yOnly = Object.fromEntries(
79
- Object.entries(y).filter(([k]) => !hasKey(x, k))
73
+ const xMergedWithY = Obj.map(
74
+ x,
75
+ (xv, k) => hasKey(y, k) ? mergePruned(xv, y[k]) : xv
80
76
  );
77
+ const yOnly = Obj.filter(y, (_yv, k) => !hasKey(x, k));
81
78
  return { ...xMergedWithY, ...yOnly };
82
79
  }
83
80
  if (Arr.isArray(x) && Arr.isArray(y) && x.length === y.length) {
@@ -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(1, shapeStructures)\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, Obj, Result } 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(1, shapeStructures)\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 = Obj.map(x, (xv, k) =>\n hasKey(y, k) ? mergePruned(xv, y[k]) : xv,\n );\n\n const yOnly = Obj.filter(y, (_yv, k) => !hasKey(x, k));\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":"key-value-record.d.mts","sourceRoot":"","sources":["../../src/record/key-value-record.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAUnE,KAAK,gBAAgB,CACnB,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EACtB,CAAC,SAAS,OAAO,IACf,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzC,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,OAAO,WAC7D,CAAC,aACC,CAAC,YACF,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CACH,KACA,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAqG7B,CAAC"}
1
+ {"version":3,"file":"key-value-record.d.mts","sourceRoot":"","sources":["../../src/record/key-value-record.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAUnE,KAAK,gBAAgB,CACnB,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EACtB,CAAC,SAAS,OAAO,IACf,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzC,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,OAAO,WAC7D,CAAC,aACC,CAAC,YACF,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CACH,KACA,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CA+F7B,CAAC"}
@@ -1,4 +1,4 @@
1
- import { isRecord, Result, Arr, tp } from 'ts-data-forge';
1
+ import { isRecord, Result, Arr, Obj, Optional } 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';
@@ -63,16 +63,13 @@ const keyValueRecord = (keyType, valueType, options) => {
63
63
  };
64
64
  const fill = (a) => isRecord(a) ? (
65
65
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
66
- Object.fromEntries(
67
- Object.entries(a).filter(
68
- ([k, v]) => keyType.is(k) && valueType.is(v)
69
- )
70
- )
66
+ Obj.filter(a, (v, k) => keyType.is(k) && valueType.is(v))
71
67
  ) : defaultValue;
72
68
  const prune = (a) => (
73
69
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
74
- Object.fromEntries(
75
- Object.entries(a).filter(([k]) => keyType.is(k)).map(([k, v]) => tp(k, valueType.prune(v)))
70
+ Obj.filterMap(
71
+ a,
72
+ (v, k) => keyType.is(k) ? Optional.some(valueType.prune(v)) : Optional.none
76
73
  )
77
74
  );
78
75
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"key-value-record.mjs","sources":["../../src/record/key-value-record.mts"],"sourcesContent":["import { Arr, isRecord, Result, tp } from 'ts-data-forge';\nimport { type ReadonlyRecord } from 'ts-type-forge';\nimport { type AnyType, type Type, type TypeOf } from '../type.mjs';\nimport {\n createAssertFn,\n createCastFn,\n createIsFn,\n createPrimitiveValidationError,\n prependPathToValidationErrors,\n type ValidationError,\n} from '../utils/index.mjs';\n\ntype RecordResultType<\n K extends Type<string>,\n V extends AnyType,\n> = ReadonlyRecord<TypeOf<K>, TypeOf<V>>;\n\nexport const keyValueRecord = <K extends Type<string>, V extends AnyType>(\n keyType: K,\n valueType: V,\n options?: Partial<\n Readonly<{\n typeName: string;\n }>\n >,\n): Type<RecordResultType<K, V>> => {\n type T = RecordResultType<K, V>;\n\n const typeName = options?.typeName ?? 'key-value-record';\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n const defaultValue = {} as T;\n\n const validate: Type<T>['validate'] = (a) => {\n if (!isRecord(a)) {\n return Result.err([\n createPrimitiveValidationError({\n actualValue: a,\n expectedType: 'record',\n typeName,\n details: undefined,\n }),\n ]);\n }\n\n const errors: readonly ValidationError[] = Arr.generate(function* () {\n for (const [k, v] of Object.entries(a)) {\n {\n const res = keyType.validate(k);\n\n if (Result.isErr(res)) {\n yield {\n path: [],\n actualValue: k,\n expectedType: typeName,\n typeName,\n details: {\n kind: 'record-entry',\n entry: 'key',\n expectedType: keyType.typeName,\n },\n } satisfies ValidationError;\n\n yield* res.value;\n }\n }\n\n {\n const res = valueType.validate(v);\n\n if (Result.isErr(res)) {\n yield {\n path: [],\n actualValue: v,\n expectedType: typeName,\n typeName,\n details: {\n kind: 'record-entry',\n entry: 'value',\n expectedType: valueType.typeName,\n },\n } satisfies ValidationError;\n\n yield* prependPathToValidationErrors(res.value, k);\n }\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 fill: Type<T>['fill'] = (a) =>\n isRecord(a)\n ? // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n (Object.fromEntries(\n Object.entries(a).filter(\n ([k, v]) => keyType.is(k) && valueType.is(v),\n ),\n ) as T)\n : defaultValue;\n\n // Keys not matching keyType are excess paths; values are pruned recursively.\n const prune = (a: T): T =>\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n Object.fromEntries(\n Object.entries(a)\n .filter(([k]) => keyType.is(k))\n .map(([k, v]) => tp(k, valueType.prune(v))),\n ) as T;\n\n return {\n typeName,\n defaultValue,\n fill,\n prune,\n validate,\n is: createIsFn(validate),\n assertIs: createAssertFn(validate),\n cast: createCastFn(validate),\n };\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"key-value-record.mjs","sources":["../../src/record/key-value-record.mts"],"sourcesContent":["import { Arr, isRecord, Obj, Optional, Result } from 'ts-data-forge';\nimport { type ReadonlyRecord } from 'ts-type-forge';\nimport { type AnyType, type Type, type TypeOf } from '../type.mjs';\nimport {\n createAssertFn,\n createCastFn,\n createIsFn,\n createPrimitiveValidationError,\n prependPathToValidationErrors,\n type ValidationError,\n} from '../utils/index.mjs';\n\ntype RecordResultType<\n K extends Type<string>,\n V extends AnyType,\n> = ReadonlyRecord<TypeOf<K>, TypeOf<V>>;\n\nexport const keyValueRecord = <K extends Type<string>, V extends AnyType>(\n keyType: K,\n valueType: V,\n options?: Partial<\n Readonly<{\n typeName: string;\n }>\n >,\n): Type<RecordResultType<K, V>> => {\n type T = RecordResultType<K, V>;\n\n const typeName = options?.typeName ?? 'key-value-record';\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n const defaultValue = {} as T;\n\n const validate: Type<T>['validate'] = (a) => {\n if (!isRecord(a)) {\n return Result.err([\n createPrimitiveValidationError({\n actualValue: a,\n expectedType: 'record',\n typeName,\n details: undefined,\n }),\n ]);\n }\n\n const errors: readonly ValidationError[] = Arr.generate(function* () {\n for (const [k, v] of Object.entries(a)) {\n {\n const res = keyType.validate(k);\n\n if (Result.isErr(res)) {\n yield {\n path: [],\n actualValue: k,\n expectedType: typeName,\n typeName,\n details: {\n kind: 'record-entry',\n entry: 'key',\n expectedType: keyType.typeName,\n },\n } satisfies ValidationError;\n\n yield* res.value;\n }\n }\n\n {\n const res = valueType.validate(v);\n\n if (Result.isErr(res)) {\n yield {\n path: [],\n actualValue: v,\n expectedType: typeName,\n typeName,\n details: {\n kind: 'record-entry',\n entry: 'value',\n expectedType: valueType.typeName,\n },\n } satisfies ValidationError;\n\n yield* prependPathToValidationErrors(res.value, k);\n }\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 fill: Type<T>['fill'] = (a) =>\n isRecord(a)\n ? // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n (Obj.filter(a, (v, k) => keyType.is(k) && valueType.is(v)) as T)\n : defaultValue;\n\n // Keys not matching keyType are excess paths; values are pruned recursively.\n const prune = (a: T): T =>\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n Obj.filterMap(a, (v, k) =>\n keyType.is(k) ? Optional.some(valueType.prune(v)) : Optional.none,\n ) as T;\n\n return {\n typeName,\n defaultValue,\n fill,\n prune,\n validate,\n is: createIsFn(validate),\n assertIs: createAssertFn(validate),\n cast: createCastFn(validate),\n };\n};\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"record.d.mts","sourceRoot":"","sources":["../../src/record/record.mts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,MAAM,EAEZ,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,KAAK,oBAAoB,EAEzB,KAAK,IAAI,EACT,KAAK,MAAM,EACX,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AAUrB,eAAO,MAAM,MAAM,GACjB,KAAK,CAAC,CAAC,SAAS,YAAY,EAC5B,KAAK,CAAC,EAAE,SAAS,oBAAoB,GAAG,OAAO,SAExC,CAAC,YACE,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,cAAc,EAAE,EAAE,CAAC;CACpB,CAAC,CACH,KACA,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CA0K7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,CAAC,CAAC,SAAS,YAAY,SAChD,CAAC,YACE,OAAO,CAAC,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,KAChD,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAI1B,CAAC;AAML,gBAAgB;AAChB,KAAK,mBAAmB,CAAC,CAAC,SAAS,YAAY,IAC7C,kBAAkB,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC;AAEhD,kBAAU,kBAAkB,CAAC;IAC3B,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,YAAY,IACxD,0BAA0B,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IAErD,KAAK,0BAA0B,CAC7B,CAAC,SAAS,YAAY,EACtB,YAAY,SAAS,MAAM,CAAC,IAE5B,MAAM,CAAC,YAAY,EAAE,KAAK,CAAC,SAAS,IAAI,GACpC,QAAQ,CAAC;SACN,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KAC9D,CAAC,GACF,MAAM,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,SAAS,IAAI,GACxC,iBAAiB,CACf,QAAQ,CAAC;SAAG,GAAG,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KAAE,CAAC,CACrD,GACD,iBAAiB,CACf,QAAQ,CACN;SAAG,GAAG,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KAAE,GAAG;SAC1C,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KAC9D,CACF,CACF,CAAC;IAEV,KAAK,gBAAgB,CAAC,CAAC,SAAS,YAAY,IAAI;SAC7C,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,QAAQ,CAAC;YAAE,QAAQ,EAAE,IAAI,CAAA;SAAE,CAAC,GAAG,CAAC,GAAG,KAAK;KACtE,CAAC,MAAM,CAAC,CAAC,CAAC;;CACZ"}
1
+ {"version":3,"file":"record.d.mts","sourceRoot":"","sources":["../../src/record/record.mts"],"names":[],"mappings":"AAWA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,MAAM,EAEZ,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,KAAK,oBAAoB,EAEzB,KAAK,IAAI,EACT,KAAK,MAAM,EACX,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AAUrB,eAAO,MAAM,MAAM,GACjB,KAAK,CAAC,CAAC,SAAS,YAAY,EAC5B,KAAK,CAAC,EAAE,SAAS,oBAAoB,GAAG,OAAO,SAExC,CAAC,YACE,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,cAAc,EAAE,EAAE,CAAC;CACpB,CAAC,CACH,KACA,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAwK7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,CAAC,CAAC,SAAS,YAAY,SAChD,CAAC,YACE,OAAO,CAAC,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,KAChD,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAI1B,CAAC;AAML,gBAAgB;AAChB,KAAK,mBAAmB,CAAC,CAAC,SAAS,YAAY,IAC7C,kBAAkB,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC;AAEhD,kBAAU,kBAAkB,CAAC;IAC3B,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,YAAY,IACxD,0BAA0B,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IAErD,KAAK,0BAA0B,CAC7B,CAAC,SAAS,YAAY,EACtB,YAAY,SAAS,MAAM,CAAC,IAE5B,MAAM,CAAC,YAAY,EAAE,KAAK,CAAC,SAAS,IAAI,GACpC,QAAQ,CAAC;SACN,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KAC9D,CAAC,GACF,MAAM,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,SAAS,IAAI,GACxC,iBAAiB,CACf,QAAQ,CAAC;SAAG,GAAG,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KAAE,CAAC,CACrD,GACD,iBAAiB,CACf,QAAQ,CACN;SAAG,GAAG,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KAAE,GAAG;SAC1C,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KAC9D,CACF,CACF,CAAC;IAEV,KAAK,gBAAgB,CAAC,CAAC,SAAS,YAAY,IAAI;SAC7C,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,QAAQ,CAAC;YAAE,QAAQ,EAAE,IAAI,CAAA;SAAE,CAAC,GAAG,CAAC,GAAG,KAAK;KACtE,CAAC,MAAM,CAAC,CAAC,CAAC;;CACZ"}
@@ -1,4 +1,4 @@
1
- import { memoizeFunction, tp, isRecord, Result, Arr, hasKey } from 'ts-data-forge';
1
+ import { memoizeFunction, tp, isRecord, Result, Arr, hasKey, Obj, Optional } 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';
@@ -102,19 +102,17 @@ const record = (shape, options) => {
102
102
  };
103
103
  const prune = (a) => {
104
104
  const rec = a;
105
- return Object.fromEntries(
106
- Object.entries(shape).flatMap(([k, v]) => {
107
- if (v.optional === true) {
108
- if (!hasKey(rec, k)) {
109
- return [];
110
- }
111
- if (rec[k] === void 0) {
112
- return [tp(k, void 0)];
113
- }
105
+ return Obj.filterMap(shape, (v, k) => {
106
+ if (v.optional === true) {
107
+ if (!hasKey(rec, k)) {
108
+ return Optional.none;
114
109
  }
115
- return [tp(k, v.prune(rec[k]))];
116
- })
117
- );
110
+ if (rec[k] === void 0) {
111
+ return Optional.some(void 0);
112
+ }
113
+ }
114
+ return Optional.some(v.prune(rec[k]));
115
+ });
118
116
  };
119
117
  return {
120
118
  typeName: typeNameFilled,
@@ -1 +1 @@
1
- {"version":3,"file":"record.mjs","sources":["../../src/record/record.mts"],"sourcesContent":["import {\n Arr,\n expectType,\n hasKey,\n isRecord,\n memoizeFunction,\n Result,\n tp,\n} from 'ts-data-forge';\nimport {\n type MergeIntersection,\n type StrictExclude,\n type TypeEq,\n type UnknownRecord,\n} from 'ts-type-forge';\nimport {\n type ExcessPropertyOption,\n type RecordTypeInternals,\n type Type,\n type TypeOf,\n type UnknownShape,\n} from '../type.mjs';\nimport {\n createAssertFn,\n createCastFn,\n createIsFn,\n createPrimitiveValidationError,\n prependPathToValidationErrors,\n type ValidationError,\n} from '../utils/index.mjs';\n\nexport const record = <\n const S extends UnknownShape,\n const EP extends ExcessPropertyOption = 'allow',\n>(\n shape: S,\n options?: Partial<\n Readonly<{\n typeName: string;\n\n /**\n * Controls how excess properties (keys not in shape) are handled.\n *\n * - `'allow'` (default) — accept excess properties\n * - `'reject'` — reject objects with excess properties\n */\n excessProperty: EP;\n }>\n >,\n): Type<RecordTypeFromShape<S>> => {\n type V = RecordTypeFromShape<S>;\n\n const sourceKeys = new Set(Object.keys(shape));\n\n const typeNameFilled: string =\n options?.typeName ??\n `{ ${Object.entries(shape)\n .map(([k, v]) => `${k}: ${v.typeName}`)\n .join(', ')} }`;\n\n const ep: ExcessPropertyOption = options?.excessProperty ?? 'allow';\n\n const getDefaultValue = memoizeFunction(\n (): V =>\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n Object.fromEntries(\n Object.entries(shape).map(([key, value]) =>\n tp(key, value.defaultValue),\n ),\n ) as V,\n );\n\n const validate: Type<V>['validate'] = (a) => {\n if (!isRecord(a)) {\n return Result.err([\n createPrimitiveValidationError({\n actualValue: a,\n expectedType: 'record',\n typeName: typeNameFilled,\n details: undefined,\n }),\n ]);\n }\n\n const defaultErrors: readonly ValidationError[] = Arr.generate(\n function* () {\n for (const [k, valueType] of Object.entries(shape)) {\n if (!hasKey(a, k)) {\n if (shape[k]?.optional !== true) {\n yield {\n path: [k],\n actualValue: a,\n typeName: typeNameFilled,\n expectedType: typeNameFilled,\n details: {\n kind: 'missing-key',\n key: k,\n },\n } satisfies ValidationError;\n }\n } else {\n // The case where the key exists in the object\n const v = a[k];\n\n // Skip validation for optional fields with undefined values\n if (v === undefined && valueType.optional === true) {\n continue;\n }\n\n const res = valueType.validate(v);\n\n if (Result.isErr(res)) {\n yield* prependPathToValidationErrors(res.value, k);\n }\n }\n }\n },\n );\n\n if (ep === 'reject') {\n const excessKeys = Object.keys(a).filter((key) => !sourceKeys.has(key));\n\n const excessErrors: readonly ValidationError[] = excessKeys.map(\n (key) =>\n ({\n path: [key],\n actualValue: a[key],\n typeName: typeNameFilled,\n expectedType: typeNameFilled,\n details: {\n kind: 'excess-key',\n key,\n },\n }) satisfies ValidationError,\n );\n\n // Combine all errors\n const allErrors = [...defaultErrors, ...excessErrors] as const;\n\n return Arr.isNonEmpty(allErrors)\n ? Result.err(allErrors)\n : // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n Result.ok(a as V);\n }\n\n // ep === 'allow'\n return Arr.isNonEmpty(defaultErrors)\n ? Result.err(defaultErrors)\n : // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n Result.ok(a as V);\n };\n\n const fill = (a: unknown): V => {\n if (!isRecord(a)) {\n return getDefaultValue();\n }\n\n // fill always produces shape-only values (strips excess)\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return Object.fromEntries(\n Object.entries(shape).map(([k, v]) => {\n if (hasKey(a, k)) {\n const value = a[k];\n\n // For optional fields, if the value is undefined, keep it as undefined\n if (value === undefined && v.optional === true) {\n return tp(k, undefined);\n }\n\n return tp(k, v.fill(value));\n }\n\n return tp(k, v.defaultValue);\n }),\n ) as V;\n };\n\n const prune = (a: V): V => {\n // The input is statically guaranteed to be a record.\n const rec: UnknownRecord = a;\n\n // prune keeps only the value paths represented by the shape;\n // unlike fill, it never fills in missing keys with default values\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return Object.fromEntries(\n Object.entries(shape).flatMap(([k, v]) => {\n // Only optional keys can be absent in the input; required keys are\n // statically guaranteed to be present.\n if (v.optional === true) {\n if (!hasKey(rec, k)) {\n return [];\n }\n\n // For optional fields, if the value is undefined, keep it as undefined\n if (rec[k] === undefined) {\n return [tp(k, undefined)];\n }\n }\n\n return [tp(k, v.prune(rec[k]))];\n }),\n ) as V;\n };\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return {\n typeName: typeNameFilled,\n get defaultValue() {\n return getDefaultValue();\n },\n fill,\n prune,\n validate,\n is: createIsFn(validate),\n assertIs: createAssertFn(validate),\n cast: createCastFn(validate),\n shapeStructure: { kind: 'simple', shape } as const,\n excessProperty: ep,\n } satisfies Type<V> & RecordTypeInternals as Type<V>;\n};\n\n/**\n * Creates a strict record type that rejects excess properties.\n * This is an alias for `record(shape, { excessProperty: 'reject' })`.\n */\nexport const strictRecord = <const S extends UnknownShape>(\n shape: S,\n options?: Partial<Readonly<{ typeName: string }>>,\n): Type<RecordTypeFromShape<S>> =>\n record(shape, {\n typeName: options?.typeName,\n excessProperty: 'reject',\n });\n\n// ---------------------------------------------------------------------------\n// RecordTypeValue (internal — computes value type from shape)\n// ---------------------------------------------------------------------------\n\n/** @internal */\ntype RecordTypeFromShape<S extends UnknownShape> =\n TsFortressInternal.RecordTypeFromShapeImpl<S>;\n\nnamespace TsFortressInternal {\n export type RecordTypeFromShapeImpl<S extends UnknownShape> =\n RecordTypeFromShapeImplSub<S, OptionalTypeKeys<S>>;\n\n type RecordTypeFromShapeImplSub<\n S extends UnknownShape,\n OptionalKeys extends keyof S,\n > =\n TypeEq<OptionalKeys, never> extends true\n ? Readonly<{\n [key in StrictExclude<keyof S, OptionalKeys>]: TypeOf<S[key]>;\n }>\n : TypeEq<keyof S, OptionalKeys> extends true\n ? MergeIntersection<\n Readonly<{ [key in OptionalKeys]?: TypeOf<S[key]> }>\n >\n : MergeIntersection<\n Readonly<\n { [key in OptionalKeys]?: TypeOf<S[key]> } & {\n [key in StrictExclude<keyof S, OptionalKeys>]: TypeOf<S[key]>;\n }\n >\n >;\n\n type OptionalTypeKeys<S extends UnknownShape> = {\n [K in keyof S]: S[K] extends Readonly<{ optional: true }> ? K : never;\n }[keyof S];\n}\n\n// --- expectType assertions ---\n\nexpectType<\n RecordTypeFromShape<Readonly<{ a: Type<0>; b: Type<1>; c: Type<2> }>>,\n Readonly<{ a: 0; b: 1; c: 2 }>\n>('=');\n\nexpectType<\n Type<RecordTypeFromShape<Readonly<{ a: Type<0> }>>>,\n Type<Readonly<{ a: 0 }>>\n>('=');\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"record.mjs","sources":["../../src/record/record.mts"],"sourcesContent":["import {\n Arr,\n expectType,\n hasKey,\n isRecord,\n memoizeFunction,\n Obj,\n Optional,\n Result,\n tp,\n} from 'ts-data-forge';\nimport {\n type MergeIntersection,\n type StrictExclude,\n type TypeEq,\n type UnknownRecord,\n} from 'ts-type-forge';\nimport {\n type ExcessPropertyOption,\n type RecordTypeInternals,\n type Type,\n type TypeOf,\n type UnknownShape,\n} from '../type.mjs';\nimport {\n createAssertFn,\n createCastFn,\n createIsFn,\n createPrimitiveValidationError,\n prependPathToValidationErrors,\n type ValidationError,\n} from '../utils/index.mjs';\n\nexport const record = <\n const S extends UnknownShape,\n const EP extends ExcessPropertyOption = 'allow',\n>(\n shape: S,\n options?: Partial<\n Readonly<{\n typeName: string;\n\n /**\n * Controls how excess properties (keys not in shape) are handled.\n *\n * - `'allow'` (default) — accept excess properties\n * - `'reject'` — reject objects with excess properties\n */\n excessProperty: EP;\n }>\n >,\n): Type<RecordTypeFromShape<S>> => {\n type V = RecordTypeFromShape<S>;\n\n const sourceKeys = new Set(Object.keys(shape));\n\n const typeNameFilled: string =\n options?.typeName ??\n `{ ${Object.entries(shape)\n .map(([k, v]) => `${k}: ${v.typeName}`)\n .join(', ')} }`;\n\n const ep: ExcessPropertyOption = options?.excessProperty ?? 'allow';\n\n const getDefaultValue = memoizeFunction(\n (): V =>\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n Object.fromEntries(\n Object.entries(shape).map(([key, value]) =>\n tp(key, value.defaultValue),\n ),\n ) as V,\n );\n\n const validate: Type<V>['validate'] = (a) => {\n if (!isRecord(a)) {\n return Result.err([\n createPrimitiveValidationError({\n actualValue: a,\n expectedType: 'record',\n typeName: typeNameFilled,\n details: undefined,\n }),\n ]);\n }\n\n const defaultErrors: readonly ValidationError[] = Arr.generate(\n function* () {\n for (const [k, valueType] of Object.entries(shape)) {\n if (!hasKey(a, k)) {\n if (shape[k]?.optional !== true) {\n yield {\n path: [k],\n actualValue: a,\n typeName: typeNameFilled,\n expectedType: typeNameFilled,\n details: {\n kind: 'missing-key',\n key: k,\n },\n } satisfies ValidationError;\n }\n } else {\n // The case where the key exists in the object\n const v = a[k];\n\n // Skip validation for optional fields with undefined values\n if (v === undefined && valueType.optional === true) {\n continue;\n }\n\n const res = valueType.validate(v);\n\n if (Result.isErr(res)) {\n yield* prependPathToValidationErrors(res.value, k);\n }\n }\n }\n },\n );\n\n if (ep === 'reject') {\n const excessKeys = Object.keys(a).filter((key) => !sourceKeys.has(key));\n\n const excessErrors: readonly ValidationError[] = excessKeys.map(\n (key) =>\n ({\n path: [key],\n actualValue: a[key],\n typeName: typeNameFilled,\n expectedType: typeNameFilled,\n details: {\n kind: 'excess-key',\n key,\n },\n }) satisfies ValidationError,\n );\n\n // Combine all errors\n const allErrors = [...defaultErrors, ...excessErrors] as const;\n\n return Arr.isNonEmpty(allErrors)\n ? Result.err(allErrors)\n : // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n Result.ok(a as V);\n }\n\n // ep === 'allow'\n return Arr.isNonEmpty(defaultErrors)\n ? Result.err(defaultErrors)\n : // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n Result.ok(a as V);\n };\n\n const fill = (a: unknown): V => {\n if (!isRecord(a)) {\n return getDefaultValue();\n }\n\n // fill always produces shape-only values (strips excess)\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return Object.fromEntries(\n Object.entries(shape).map(([k, v]) => {\n if (hasKey(a, k)) {\n const value = a[k];\n\n // For optional fields, if the value is undefined, keep it as undefined\n if (value === undefined && v.optional === true) {\n return tp(k, undefined);\n }\n\n return tp(k, v.fill(value));\n }\n\n return tp(k, v.defaultValue);\n }),\n ) as V;\n };\n\n const prune = (a: V): V => {\n // The input is statically guaranteed to be a record.\n const rec: UnknownRecord = a;\n\n // prune keeps only the value paths represented by the shape;\n // unlike fill, it never fills in missing keys with default values\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return Obj.filterMap(shape, (v, k) => {\n // Only optional keys can be absent in the input; required keys are\n // statically guaranteed to be present.\n if (v.optional === true) {\n if (!hasKey(rec, k)) {\n return Optional.none;\n }\n\n // For optional fields, if the value is undefined, keep it as undefined\n if (rec[k] === undefined) {\n return Optional.some(undefined);\n }\n }\n\n return Optional.some(v.prune(rec[k]));\n }) as V;\n };\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return {\n typeName: typeNameFilled,\n get defaultValue() {\n return getDefaultValue();\n },\n fill,\n prune,\n validate,\n is: createIsFn(validate),\n assertIs: createAssertFn(validate),\n cast: createCastFn(validate),\n shapeStructure: { kind: 'simple', shape } as const,\n excessProperty: ep,\n } satisfies Type<V> & RecordTypeInternals as Type<V>;\n};\n\n/**\n * Creates a strict record type that rejects excess properties.\n * This is an alias for `record(shape, { excessProperty: 'reject' })`.\n */\nexport const strictRecord = <const S extends UnknownShape>(\n shape: S,\n options?: Partial<Readonly<{ typeName: string }>>,\n): Type<RecordTypeFromShape<S>> =>\n record(shape, {\n typeName: options?.typeName,\n excessProperty: 'reject',\n });\n\n// ---------------------------------------------------------------------------\n// RecordTypeValue (internal — computes value type from shape)\n// ---------------------------------------------------------------------------\n\n/** @internal */\ntype RecordTypeFromShape<S extends UnknownShape> =\n TsFortressInternal.RecordTypeFromShapeImpl<S>;\n\nnamespace TsFortressInternal {\n export type RecordTypeFromShapeImpl<S extends UnknownShape> =\n RecordTypeFromShapeImplSub<S, OptionalTypeKeys<S>>;\n\n type RecordTypeFromShapeImplSub<\n S extends UnknownShape,\n OptionalKeys extends keyof S,\n > =\n TypeEq<OptionalKeys, never> extends true\n ? Readonly<{\n [key in StrictExclude<keyof S, OptionalKeys>]: TypeOf<S[key]>;\n }>\n : TypeEq<keyof S, OptionalKeys> extends true\n ? MergeIntersection<\n Readonly<{ [key in OptionalKeys]?: TypeOf<S[key]> }>\n >\n : MergeIntersection<\n Readonly<\n { [key in OptionalKeys]?: TypeOf<S[key]> } & {\n [key in StrictExclude<keyof S, OptionalKeys>]: TypeOf<S[key]>;\n }\n >\n >;\n\n type OptionalTypeKeys<S extends UnknownShape> = {\n [K in keyof S]: S[K] extends Readonly<{ optional: true }> ? K : never;\n }[keyof S];\n}\n\n// --- expectType assertions ---\n\nexpectType<\n RecordTypeFromShape<Readonly<{ a: Type<0>; b: Type<1>; c: Type<2> }>>,\n Readonly<{ a: 0; b: 1; c: 2 }>\n>('=');\n\nexpectType<\n Type<RecordTypeFromShape<Readonly<{ a: Type<0> }>>>,\n Type<Readonly<{ a: 0 }>>\n>('=');\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-fortress",
3
- "version": "12.0.2",
3
+ "version": "12.0.3",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript"
@@ -36,7 +36,7 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "@sindresorhus/is": "8.1.0",
39
- "ts-data-forge": "~14.3.0",
39
+ "ts-data-forge": "~14.4.0",
40
40
  "ts-type-forge": "~9.2.1"
41
41
  },
42
42
  "devDependencies": {
@@ -53,9 +53,9 @@
53
53
  "typedoc-github-theme": "0.4.0",
54
54
  "vite": "8.2.1",
55
55
  "zod": "4.4.3",
56
- "eslint-config-typed": "5.9.2",
57
- "eslint-plugin-ts-data-forge": "0.4.7",
58
- "eslint-plugin-ts-type-forge": "0.6.5",
56
+ "eslint-config-typed": "5.9.3",
57
+ "eslint-plugin-ts-data-forge": "0.4.8",
58
+ "eslint-plugin-ts-type-forge": "0.6.6",
59
59
  "ts-repo-utils": "10.2.0"
60
60
  },
61
61
  "engines": {
@@ -1,4 +1,4 @@
1
- import { Arr, expectType, hasKey, isRecord, Result, tp } from 'ts-data-forge';
1
+ import { Arr, expectType, hasKey, isRecord, Obj, Result } from 'ts-data-forge';
2
2
  import { type Intersection, type NonEmptyTuple } from 'ts-type-forge';
3
3
  import {
4
4
  hasRecordInternals,
@@ -118,15 +118,11 @@ export const intersection = <const Types extends NonEmptyTuple<AnyType>>(
118
118
  */
119
119
  const mergePruned = (x: unknown, y: unknown): unknown => {
120
120
  if (isRecord(x) && isRecord(y)) {
121
- const xMergedWithY = Object.fromEntries(
122
- Object.entries(x).map(([k, xv]) =>
123
- tp(k, hasKey(y, k) ? mergePruned(xv, y[k]) : xv),
124
- ),
121
+ const xMergedWithY = Obj.map(x, (xv, k) =>
122
+ hasKey(y, k) ? mergePruned(xv, y[k]) : xv,
125
123
  );
126
124
 
127
- const yOnly = Object.fromEntries(
128
- Object.entries(y).filter(([k]) => !hasKey(x, k)),
129
- );
125
+ const yOnly = Obj.filter(y, (_yv, k) => !hasKey(x, k));
130
126
 
131
127
  return { ...xMergedWithY, ...yOnly };
132
128
  }
@@ -1,4 +1,4 @@
1
- import { Arr, isRecord, Result, tp } from 'ts-data-forge';
1
+ import { Arr, isRecord, Obj, Optional, Result } from 'ts-data-forge';
2
2
  import { type ReadonlyRecord } from 'ts-type-forge';
3
3
  import { type AnyType, type Type, type TypeOf } from '../type.mjs';
4
4
  import {
@@ -98,20 +98,14 @@ export const keyValueRecord = <K extends Type<string>, V extends AnyType>(
98
98
  const fill: Type<T>['fill'] = (a) =>
99
99
  isRecord(a)
100
100
  ? // eslint-disable-next-line total-functions/no-unsafe-type-assertion
101
- (Object.fromEntries(
102
- Object.entries(a).filter(
103
- ([k, v]) => keyType.is(k) && valueType.is(v),
104
- ),
105
- ) as T)
101
+ (Obj.filter(a, (v, k) => keyType.is(k) && valueType.is(v)) as T)
106
102
  : defaultValue;
107
103
 
108
104
  // Keys not matching keyType are excess paths; values are pruned recursively.
109
105
  const prune = (a: T): T =>
110
106
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
111
- Object.fromEntries(
112
- Object.entries(a)
113
- .filter(([k]) => keyType.is(k))
114
- .map(([k, v]) => tp(k, valueType.prune(v))),
107
+ Obj.filterMap(a, (v, k) =>
108
+ keyType.is(k) ? Optional.some(valueType.prune(v)) : Optional.none,
115
109
  ) as T;
116
110
 
117
111
  return {
@@ -1,5 +1,6 @@
1
1
  import { expectType, Result } from 'ts-data-forge';
2
2
  import { type ReadonlyRecord, type UnknownRecord } from 'ts-type-forge';
3
+ import { literal } from '../other-types/index.mjs';
3
4
  import { number, string } from '../primitives/index.mjs';
4
5
  import { type TypeOf } from '../type.mjs';
5
6
  import {
@@ -226,4 +227,19 @@ describe(keyValueRecord, () => {
226
227
  assert.isFalse(strNumRecord.is(x));
227
228
  });
228
229
  });
230
+
231
+ describe('prune', () => {
232
+ test('keeps every entry when all keys match the key type', () => {
233
+ assert.deepStrictEqual(strNumRecord.prune({ year: 2000, month: 12 }), {
234
+ year: 2000,
235
+ month: 12,
236
+ });
237
+ });
238
+
239
+ test('drops the entries whose key does not match the key type', () => {
240
+ const onlyA = keyValueRecord(literal('a'), number());
241
+
242
+ assert.deepStrictEqual(onlyA.prune({ a: 1, b: 2 }), { a: 1 });
243
+ });
244
+ });
229
245
  });
@@ -4,6 +4,8 @@ import {
4
4
  hasKey,
5
5
  isRecord,
6
6
  memoizeFunction,
7
+ Obj,
8
+ Optional,
7
9
  Result,
8
10
  tp,
9
11
  } from 'ts-data-forge';
@@ -182,24 +184,22 @@ export const record = <
182
184
  // prune keeps only the value paths represented by the shape;
183
185
  // unlike fill, it never fills in missing keys with default values
184
186
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
185
- return Object.fromEntries(
186
- Object.entries(shape).flatMap(([k, v]) => {
187
- // Only optional keys can be absent in the input; required keys are
188
- // statically guaranteed to be present.
189
- if (v.optional === true) {
190
- if (!hasKey(rec, k)) {
191
- return [];
192
- }
187
+ return Obj.filterMap(shape, (v, k) => {
188
+ // Only optional keys can be absent in the input; required keys are
189
+ // statically guaranteed to be present.
190
+ if (v.optional === true) {
191
+ if (!hasKey(rec, k)) {
192
+ return Optional.none;
193
+ }
193
194
 
194
- // For optional fields, if the value is undefined, keep it as undefined
195
- if (rec[k] === undefined) {
196
- return [tp(k, undefined)];
197
- }
195
+ // For optional fields, if the value is undefined, keep it as undefined
196
+ if (rec[k] === undefined) {
197
+ return Optional.some(undefined);
198
198
  }
199
+ }
199
200
 
200
- return [tp(k, v.prune(rec[k]))];
201
- }),
202
- ) as V;
201
+ return Optional.some(v.prune(rec[k]));
202
+ }) as V;
203
203
  };
204
204
 
205
205
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion