{"version":3,"sources":["../src/Rotorise.ts"],"names":["key"],"mappings":"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","file":"Rotorise.cjs","sourcesContent":["import type {\n    DistributiveOmit,\n    DistributivePick,\n    ErrorMessage,\n    Exact,\n    MergeIntersectionObject,\n    NonEmptyArray,\n    Replace,\n    SliceFromStart,\n    show,\n    ValueOf,\n} from './utils'\n\n// When a spec item has a transform function, use the transform's parameter type\n// instead of the entity's property type. This allows callers to pass only the\n// fields the transform actually needs (e.g. Pick<Obj, 'id'> instead of Obj).\ntype TransformOverride<\n    Spec extends InputSpecShape,\n    K,\n    Fallback,\n    Matched = Extract<Spec[number], [K, (...args: any[]) => any, ...any[]]>,\n> = [Matched] extends [never]\n    ? Fallback\n    : // Contravariant inference: when the same key appears multiple times with\n      // different transforms (e.g. Pick<Obj,'id'> and Pick<Obj,'name'>),\n      // this intersects the parameter types rather than unioning them.\n      (\n            Matched extends [any, (x: infer P) => any, ...any[]] // biome-ignore lint/suspicious/noExplicitAny: inference\n                ? (x: P) => void\n                : never\n        ) extends (x: infer I) => void\n      ? I\n      : Fallback\n\nexport type CompositeKeyParamsImpl<\n    Entity,\n    InputSpec extends InputSpecShape,\n    skip extends number = 1,\n> = Entity extends unknown\n    ? show<\n          {\n              [K in extractHeadOrPass<\n                  SliceFromStart<\n                      InputSpec,\n                      number extends skip ? 1 : skip\n                  >[number]\n              > &\n                  keyof Entity]: TransformOverride<InputSpec, K, Entity[K]>\n          } & {\n              [K in extractHeadOrPass<InputSpec[number]> &\n                  keyof Entity]?: TransformOverride<InputSpec, K, Entity[K]>\n          }\n      >\n    : never\n\nexport type CompositeKeyParams<\n    Entity extends Record<string, unknown>,\n    FullSpec extends InputSpec<MergeIntersectionObject<Entity>>[],\n    skip extends number = 1,\n> = CompositeKeyParamsImpl<Entity, FullSpec, skip>\n\ntype CompositeKeyBuilderImpl<\n    Entity,\n    Spec,\n    Separator extends string = '#',\n    Deep extends number = number,\n    isPartial extends boolean = false,\n> = Entity extends unknown\n    ? CompositeKeyStringBuilder<\n          Entity,\n          [Deep] extends [never]\n              ? Spec\n              : number extends Deep\n                ? Spec\n                : SliceFromStart<Spec, Deep>,\n          Separator,\n          boolean extends isPartial ? false : isPartial\n      >\n    : never\n\nexport type CompositeKeyBuilder<\n    Entity extends Record<string, unknown>,\n    Spec extends InputSpec<MergeIntersectionObject<Entity>>[],\n    Separator extends string = '#',\n    Deep extends number = number,\n    isPartial extends boolean = false,\n> = CompositeKeyBuilderImpl<Entity, Spec, Separator, Deep, isPartial>\n\ntype joinable = string | number | bigint | boolean | null | undefined\n\ntype ExtractHelper<Key, Value> = Value extends object\n    ? Value extends {\n          tag: infer Tag extends string\n          value: infer Value extends joinable\n      }\n        ? [Tag, Value]\n        : Value extends {\n                value: infer Value extends joinable\n            }\n          ? [never, Value]\n          : never\n    : [Key, Value]\n\ntype ExtractPair<Entity, Spec> = Spec extends [\n    infer Key extends string,\n    // biome-ignore lint/suspicious/noExplicitAny: required for generic transform inference\n    (...key: any[]) => infer Value,\n    ...unknown[],\n]\n    ? ExtractHelper<Uppercase<Key>, Value>\n    : Spec extends keyof Entity & string\n      ? [Uppercase<Spec>, Entity[Spec] & joinable]\n      : never\n\ntype CompositeKeyStringBuilder<\n    Entity,\n    Spec,\n    Separator extends string,\n    KeepIntermediate extends boolean,\n    Acc extends string = '',\n    AllAcc extends string = never,\n> = Spec extends [infer Head, ...infer Tail]\n    ? ExtractPair<Entity, Head> extends [\n          infer Key extends joinable,\n          infer Value extends joinable,\n      ]\n        ? CompositeKeyStringBuilder<\n              Entity,\n              Tail,\n              Separator,\n              KeepIntermediate,\n              Acc extends ''\n                  ? [Key] extends [never]\n                      ? `${Value}`\n                      : `${Key}${Separator}${Value}`\n                  : [Key] extends [never]\n                    ? `${Acc}${Separator}${Value}`\n                    : `${Acc}${Separator}${Key}${Separator}${Value}`,\n              KeepIntermediate extends true\n                  ? AllAcc | (Acc extends '' ? never : Acc)\n                  : never\n          >\n        : never\n    : AllAcc | Acc\n\ntype DiscriminatedSchemaShape = {\n    discriminator: PropertyKey\n    spec: {\n        [k in PropertyKey]: unknown\n    }\n}\n\ntype InputSpecShape =\n    // biome-ignore lint/suspicious/noExplicitAny: key type is erased at runtime, any is needed for structural matching\n    ([PropertyKey, (key: any) => unknown, ...unknown[]] | PropertyKey)[]\n\nexport type TransformShape =\n    | {\n          tag?: string\n          value: joinable\n      }\n    | joinable\n\ntype ComputeTableKeyType<\n    Entity,\n    Spec,\n    Separator extends string,\n    NullAs extends never | undefined = never,\n> = Spec extends InputSpecShape\n    ? CompositeKeyBuilderImpl<Entity, Spec, Separator, number, false>\n    : Spec extends keyof Entity\n      ? Replace<Entity[Spec], null, undefined>\n      : Spec extends null\n        ? NullAs\n        : never\n\ntype TableEntryImpl<\n    Entity,\n    Schema,\n    Separator extends string = '#',\n> = Entity extends unknown\n    ? show<\n          {\n              readonly [Key in keyof Schema]: Schema[Key] extends DiscriminatedSchemaShape\n                  ? ComputeTableKeyType<\n                        Entity,\n                        ValueOf<\n                            Schema[Key]['spec'],\n                            ValueOf<Entity, Schema[Key]['discriminator']>\n                        >,\n                        Separator\n                    >\n                  : Schema[Key] extends keyof Entity | InputSpecShape | null\n                    ? ComputeTableKeyType<Entity, Schema[Key], Separator>\n                    : ErrorMessage<'Invalid schema definition'>\n          } & Entity\n      >\n    : never\n\n/**\n * Represents a complete DynamoDB table entry, combining the original entity\n * with its computed internal and global keys.\n *\n * @template Entity The base entity type.\n * @template Schema The schema defining the table keys.\n * @template Separator The string used to join composite key components (default: '#').\n */\nexport type TableEntry<\n    Entity extends Record<string, unknown>,\n    Schema extends Record<string, FullKeySpec<Entity>>,\n    Separator extends string = '#',\n> = TableEntryImpl<Entity, Schema, Separator>\n\n// Partial<T> for objects so narrowed transform defaults pass the InputSpec constraint.\ntype DefaultOf<T> = T extends Record<string, unknown> ? Partial<T> : T\n\ntype InputSpec<E> = {\n    [key in keyof E]:\n        | (undefined extends E[key]\n              ? [\n                    key,\n                    (key: Exclude<E[key], undefined>) => TransformShape,\n                    DefaultOf<Exclude<E[key], undefined>>,\n                ]\n              : [key, (key: E[key]) => TransformShape])\n        | (undefined extends E[key] ? never : null extends E[key] ? never : key)\n}[keyof E]\n\n// --- 3-tuple default validation ---\n// InputSpec uses DefaultOf so narrowed defaults pass the constraint.\n// ValidateSchema mirrors the schema replacing each 3-tuple default slot with\n// the transform's param type. The `Schema & ValidateSchema<Schema>` intersection\n// then rejects defaults that don't match the transform param (e.g. `{}`).\n\n// biome-ignore lint/suspicious/noExplicitAny: structural matching\ntype ValidateInputSpec<T> = {\n    [I in keyof T]: T[I] extends readonly [\n        unknown,\n        (arg: infer P) => any,\n        unknown,\n    ]\n        ? [T[I][0], T[I][1], P]\n        : T[I]\n}\n\ntype Tuple3 = { length: 3 }\n\n// True if any spec entry in V is a 3-tuple. Recurses into discriminated specs.\ntype NeedsValidation<V> = V extends readonly unknown[]\n    ? Extract<V[number], Tuple3>\n    : V extends { spec: infer S }\n      ? NeedsValidation<S[keyof S]>\n      : never\n\n// Short-circuits to `unknown` when schema has no 3-tuples (zero overhead).\ntype ValidateSchema<Schema> = [NeedsValidation<Schema[keyof Schema]>] extends [\n    never,\n]\n    ? unknown\n    : {\n          [K in keyof Schema]: Schema[K] extends {\n              discriminator: unknown\n              spec: infer Spec\n          }\n              ? {\n                    discriminator: Schema[K]['discriminator']\n                    spec: {\n                        [SV in keyof Spec]: ValidateInputSpec<Spec[SV]>\n                    }\n                }\n              : ValidateInputSpec<Schema[K]>\n      }\n\ntype extractHeadOrPass<T> = T extends readonly unknown[] ? T[0] : T\n\ntype FullKeySpecSimple<Entity> =\n    | NonEmptyArray<InputSpec<MergeIntersectionObject<Entity>>>\n    | (keyof Entity & string)\n    | null\n\ntype FullKeySpecSimpleShape = InputSpecShape | string | null\n\ntype DiscriminatedSchema<Entity, E> = {\n    [key in keyof E]: E[key] extends PropertyKey\n        ? {\n              discriminator: key\n              spec: {\n                  [val in E[key]]: FullKeySpecSimple<\n                      Extract<\n                          Entity,\n                          {\n                              [k in key]: val\n                          }\n                      >\n                  >\n              }\n          }\n        : never\n}[keyof E]\n\ntype FullKeySpec<Entity> =\n    | FullKeySpecSimple<Entity>\n    | DiscriminatedSchema<Entity, MergeIntersectionObject<Entity>>\n\ntype FullKeySpecShape = FullKeySpecSimpleShape | DiscriminatedSchemaShape\n\nexport class RotoriseError extends Error {\n    constructor(message: string) {\n        super(message)\n        this.name = 'RotoriseError'\n    }\n}\n\n// Runtime implementation uses `as never` casts because the generic types are\n// too complex for TS to verify at the value level. Type correctness is enforced\n// by the type-level types (CompositeKeyStringBuilder, TableEntryImpl, etc.) and\n// validated by the attest-based test suite.\n\nconst chainableNoOpProxy: unknown = new Proxy(() => chainableNoOpProxy, {\n    get: () => chainableNoOpProxy,\n})\n\nconst createPathProxy = <T>(path = ''): T => {\n    return new Proxy(() => {}, {\n        get: (_target, prop) => {\n            if (typeof prop === 'string') {\n                if (prop === 'toString') {\n                    return () => path\n                }\n\n                return createPathProxy(\n                    path === ''\n                        ? prop\n                        : !Number.isNaN(Number.parseInt(prop))\n                          ? `${path}[${prop}]`\n                          : `${path}.${prop}`,\n                )\n            }\n        },\n    }) as T\n}\n\nconst key =\n    <const Entity>() =>\n    <\n        const Schema extends Record<\n            string,\n            | InputSpec<MergeIntersectionObject<Entity>>[]\n            | keyof Entity\n            | {\n                  discriminator: keyof Entity\n                  spec: {\n                      [val in string]:\n                          | InputSpec<MergeIntersectionObject<Entity>>[]\n                          | keyof Entity\n                  }\n              }\n        >,\n        Separator extends string = '#',\n    >(\n        schema: Schema,\n        separator: Separator = '#' as Separator,\n    ) =>\n    <\n        const Key extends keyof Schema,\n        const Config extends {\n            depth?: number\n            allowPartial?: boolean\n            enforceBoundary?: boolean\n        },\n        const Attributes extends Partial<Entity>,\n    >(\n        key: Key,\n        attributes: Attributes,\n        config?: Config,\n    ): string | undefined => {\n        const case_ = schema[key]\n\n        if (case_ === undefined) {\n            throw new RotoriseError(`Key ${key.toString()} not found in schema`)\n        }\n        let structure: InputSpec<MergeIntersectionObject<Entity>>[]\n\n        if (Array.isArray(case_)) {\n            structure = case_\n        } else if (typeof case_ === 'object') {\n            const discriminator =\n                attributes[case_.discriminator as keyof Attributes]\n            if (discriminator === undefined) {\n                throw new RotoriseError(\n                    `Discriminator ${case_.discriminator.toString()} not found in ${JSON.stringify(attributes)}`,\n                )\n            }\n            const val = case_.spec[discriminator as keyof typeof case_.spec]\n            if (val === undefined) {\n                throw new RotoriseError(\n                    `Discriminator value ${discriminator?.toString()} not found in ${JSON.stringify(attributes)}`,\n                )\n            }\n            if (val === null) {\n                return undefined\n            }\n\n            if (!Array.isArray(val)) {\n                return attributes[val as keyof Attributes] as never\n            }\n\n            structure = val\n        } else {\n            const value = attributes[case_ as keyof Attributes]\n            if (value == null) return undefined as never\n\n            return value as never\n        }\n\n        const fullLength = structure.length\n\n        if (config?.depth !== undefined) {\n            structure = structure.slice(0, config.depth) as typeof structure\n        }\n        const composite: joinable[] = []\n\n        for (const keySpec of structure) {\n            const [key, transform, Default] = Array.isArray(keySpec)\n                ? keySpec\n                : [keySpec]\n\n            const value = attributes[key as keyof Attributes] ?? Default\n\n            if (transform && value !== undefined) {\n                const transformed = transform(value as never)\n                if (typeof transformed === 'object' && transformed !== null) {\n                    if (transformed.tag !== undefined)\n                        composite.push(transformed.tag)\n                    composite.push(transformed.value)\n                } else {\n                    composite.push(key.toString().toUpperCase())\n                    composite.push(transformed)\n                }\n            } else if (value !== undefined && value !== null && value !== '') {\n                composite.push(key.toString().toUpperCase())\n                composite.push(value as joinable)\n            } else if (config?.allowPartial) {\n                break\n            } else {\n                throw new RotoriseError(\n                    `buildCompositeKey: Attribute ${key.toString()} not found in ${JSON.stringify(attributes)}`,\n                )\n            }\n        }\n\n        // Each spec element produces 2 segments (KEY, value). If fewer segments\n        // were emitted than expected (partial key), append a trailing separator.\n        if (config?.enforceBoundary && fullLength * 2 > composite.length) {\n            composite.push('')\n        }\n\n        return composite.join(separator) as never\n    }\n\nconst toEntry =\n    <const Entity extends Record<string, unknown>>() =>\n    <\n        const Schema extends Record<\n            string,\n            | InputSpec<MergeIntersectionObject<Entity>>[]\n            | keyof Entity\n            | {\n                  discriminator: keyof Entity\n                  spec: {\n                      [val in string]:\n                          | InputSpec<MergeIntersectionObject<Entity>>[]\n                          | keyof Entity\n                  }\n              }\n        >,\n        Separator extends string = '#',\n    >(\n        schema: Schema,\n        separator: Separator = '#' as Separator,\n    ) =>\n    <const ExactEntity extends Entity>(\n        item: ExactEntity,\n    ): ExactEntity extends infer E extends Entity\n        ? TableEntryImpl<E, Schema, Separator>\n        : never => {\n        const entry = { ...item }\n        const buildKey = key<Entity>()(schema, separator)\n\n        for (const key_ in schema) {\n            const val = buildKey(key_, item)\n            if (val !== undefined) {\n                entry[key_] = val satisfies string as never\n            }\n        }\n        return entry as never\n    }\n\nconst fromEntry =\n    <const Entity extends Record<string, unknown>>() =>\n    <\n        const Schema extends Record<string, FullKeySpecShape>,\n        Separator extends string = '#',\n    >(\n        schema: Schema,\n    ) =>\n    <const Entry extends TableEntryImpl<Entity, Schema, Separator>>(\n        entry: Entry,\n    ): DistributiveOmit<Entry, keyof Schema> => {\n        const item = { ...entry }\n\n        for (const key_ in schema) {\n            delete item[key_]\n        }\n        return item as never\n    }\n\ntype ProcessSpecType<\n    Entity,\n    Spec,\n    Config extends SpecConfigShape,\n> = Spec extends string\n    ? DistributivePick<Entity, Spec>\n    : Spec extends InputSpecShape\n      ? CompositeKeyParamsImpl<\n            Entity,\n            Spec,\n            Config['allowPartial'] extends true\n                ? 1\n                : Extract<Config['depth'], number>\n        >\n      : Spec extends null | undefined\n        ? unknown\n        : ErrorMessage<'Invalid Spec: Expected string, InputSpecShape, null or undefined'>\n\n// Cache commonly used conditional types\ntype SpecConfig<Spec> = Spec extends string ? never : SpecConfigShape\n\ntype SpecConfigShape = {\n    depth?: number\n    allowPartial?: boolean\n    enforceBoundary?: boolean\n}\n\n// Pre-compute discriminated variant types\ntype ExtractVariant<Entity, K extends PropertyKey, V extends PropertyKey> = [\n    Entity,\n] extends [never]\n    ? never\n    : Extract<Entity, { [k in K]: V }>\n\ntype TagVariant<Entity, K extends PropertyKey, V extends PropertyKey> = [\n    Entity,\n] extends [never]\n    ? { [k in K]: V }\n    : Entity & { [k in K]: V }\n\n// Flatten nested type computation\ntype ProcessVariant<\n    Entity,\n    K extends PropertyKey,\n    V extends PropertyKey,\n    Spec extends DiscriminatedSchemaShape,\n    Config extends SpecConfigShape,\n    VariantSpec = Spec['spec'][V & keyof Spec['spec']],\n> = TagVariant<\n    VariantSpec extends null | undefined\n        ? unknown\n        : ProcessSpecType<ExtractVariant<Entity, K, V>, VariantSpec, Config>,\n    K,\n    V\n>\n\n// Optimized attribute processing\ntype OptimizedAttributes<Entity, Spec, Config extends SpecConfigShape> = show<\n    Spec extends DiscriminatedSchemaShape\n        ? {\n              [K in Spec['discriminator']]: {\n                  [V in keyof Spec['spec']]: ProcessVariant<\n                      Entity,\n                      K,\n                      V,\n                      Spec,\n                      Config\n                  >\n              }[keyof Spec['spec']]\n          }[Spec['discriminator']]\n        : ProcessSpecType<Entity, Spec, Config>\n>\n\ntype ProcessKey<\n    Entity,\n    Spec,\n    Separator extends string,\n    NullAs extends never | undefined = never,\n    Config extends SpecConfigShape = SpecConfigShape,\n    Attributes = Pick<Entity, Spec & keyof Entity>,\n> = [Entity] extends [never]\n    ? never\n    : Spec extends keyof Entity\n      ? Replace<ValueOf<Attributes>, null, undefined>\n      : Spec extends InputSpecShape\n        ? CompositeKeyBuilderImpl<\n              Entity,\n              Spec,\n              Separator,\n              Exclude<Config['depth'], undefined>,\n              Exclude<Config['allowPartial'], undefined>\n          >\n        : Spec extends null | undefined\n          ? NullAs\n          : ErrorMessage<'Invalid Spec'>\n\ntype OptimizedBuiltKey<\n    Entity,\n    Spec,\n    Separator extends string,\n    Config extends SpecConfigShape,\n    Attributes,\n> = Entity extends unknown\n    ? show<\n          Spec extends DiscriminatedSchemaShape\n              ? ProcessKey<\n                    Entity,\n                    ValueOf<\n                        Spec['spec'],\n                        ValueOf<Entity, Spec['discriminator']>\n                    >,\n                    Separator,\n                    undefined,\n                    Config,\n                    Attributes\n                >\n              : ProcessKey<\n                    Entity,\n                    Spec,\n                    Separator,\n                    undefined,\n                    Config,\n                    Attributes\n                >\n      >\n    : never\n\ntype TableEntryDefinition<Entity, Schema, Separator extends string> = {\n    /**\n     * Converts a raw entity into a complete table entry with all keys computed.\n     * Use this when preparing items for insertion into DynamoDB.\n     */\n    toEntry: <const ExactEntity>(\n        item: Exact<Entity, ExactEntity>,\n    ) => TableEntryImpl<ExactEntity, Schema, Separator>\n\n    /**\n     * Extracts the raw entity from a table entry by removing all computed keys.\n     * Use this when processing items retrieved from DynamoDB.\n     */\n    fromEntry: <const Entry extends TableEntryImpl<Entity, Schema, Separator>>(\n        entry: Entry,\n    ) => DistributiveOmit<Entry, keyof Schema>\n\n    /**\n     * Generates a specific key for the given entity attributes.\n     * Supports partial keys and depth limiting for query operations.\n     *\n     * @param key The name of the key to generate (e.g., 'PK', 'GSIPK').\n     * @param attributes the object containing the values needed to build the key.\n     * @param config Optional configuration for partial keys or depth limiting.\n     */\n    key: <\n        const Key extends keyof Schema,\n        const Config extends SpecConfig<Spec>,\n        const Attributes extends OptimizedAttributes<Entity, Spec, Config_>,\n        Spec = Schema[Key],\n        Config_ extends SpecConfigShape = [SpecConfigShape] extends [Config]\n            ? {\n                  depth?: undefined\n                  allowPartial?: undefined\n                  enforceBoundary?: boolean\n              }\n            : Config,\n    >(\n        key: Key,\n        attributes: Attributes,\n        config?: Config,\n    ) => OptimizedBuiltKey<Attributes, Spec, Separator, Config_, Attributes>\n\n    /**\n     * A zero-runtime inference helper. Use this with `typeof` to get the\n     * total type of a table entry.\n     */\n    infer: TableEntryImpl<Entity, Schema, Separator>\n\n    /**\n     * Creates a proxy to generate property paths as strings.\n     * Useful for building UpdateExpressions or ProjectionExpressions.\n     *\n     * @example\n     * table.path().data.nested.property.toString() // returns \"data.nested.property\"\n     */\n    path: () => TableEntryImpl<Entity, Schema, Separator>\n}\n\n/**\n * Entry point for defining a DynamoDB table schema with Rotorise.\n *\n * @template Entity The base entity type that this table represents.\n * @returns A builder function that accepts the schema and an optional separator.\n *\n * Note: the double-call `<Entity>()(schema)` is required for partial type parameter inference.\n *\n * @example\n * const userTable = tableEntry<User>()({\n *   PK: [\"orgId\", \"id\"],\n *   SK: \"role\"\n * })\n */\nexport const tableEntry =\n    <const Entity extends Record<string, unknown>>() =>\n    <\n        const Schema extends Record<string, FullKeySpec<Entity>>,\n        Separator extends string = '#',\n    >(\n        schema: Schema & ValidateSchema<Schema>,\n        ...[separator]: [Separator] extends ['']\n            ? [ErrorMessage<'Separator must not be an empty string'>]\n            : [separator?: Separator]\n    ): TableEntryDefinition<Entity, Schema, Separator> => {\n        const sep = separator ?? ('#' as Separator)\n        if (sep === '' || typeof sep !== 'string') {\n            throw new RotoriseError('Separator must not be an empty string')\n        }\n        return {\n            toEntry: toEntry()(schema as never, sep) as never,\n            fromEntry: fromEntry()(schema as never) as never,\n            key: key()(schema as never, sep) as never,\n            infer: chainableNoOpProxy as never,\n            path: () => createPathProxy() as never,\n        }\n    }\n"]}