{"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 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 (valueType.optional === true && v === undefined) {\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 (v.optional === true && value === undefined) {\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<{ [key in Exclude<keyof S, OptionalKeys>]: TypeOf<S[key]> }>\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 Exclude<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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"}