UNPKG

rotorise

Version:

Supercharge your DynamoDB with Rotorise!

1 lines 29.1 kB
{"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"]}