import type { ExprResult, ValidPaths } from './types.js'; import type { Get, Paths } from 'type-fest'; import { AttributeMapBuilder } from './attribute-map.js'; /** * A compiled condition operation ready for use in DynamoDB requests. * * @typeParam T - The entity type this condition operates on */ export type CompiledConditionOperation = { type: 'condition_operation'; expr: ExprResult; value?: T; }; /** * A reference to another field, used for field-to-field comparisons. * * @typeParam T - The entity type this reference operates on */ export type FieldRef = { readonly type: 'field_ref'; readonly key: ValidPaths; }; /** * A comparison condition operation (=, <>, <, <=, >, >=). * * @typeParam T - The entity type this condition operates on */ export type CondOperation = { type: 'condition_condition'; key: ValidPaths; op: '=' | '<>' | '<' | '<=' | '>' | '>='; value: unknown | FieldRef; }; /** * Logical AND operation combining multiple conditions. * * @typeParam T - The entity type this condition operates on */ export type AndOperation = { type: 'condition_and'; conditions: AnyCondition[]; }; /** * Logical OR operation combining multiple conditions. * * @typeParam T - The entity type this condition operates on */ export type OrOperation = { type: 'condition_or'; conditions: AnyCondition[]; }; /** * Condition that checks if an attribute does not exist. * * @typeParam T - The entity type this condition operates on */ export type AttributeNotExistsOperation = { type: 'condition_attribute_not_exists'; key: ValidPaths; }; /** * Condition that checks if an attribute exists. * * @typeParam T - The entity type this condition operates on */ export type AttributeExistsOperation = { type: 'condition_attribute_exists'; key: ValidPaths; }; /** * Union of all possible condition operation types. */ type AnyCondition = | CondOperation | AndOperation | OrOperation | AttributeNotExistsOperation | AttributeExistsOperation; /** * Any condition operation that can be used in a DynamoDB expression. * * @typeParam T - The entity type this condition operates on */ export type ConditionOperation = AnyCondition; /** * Accepts either a pre-built condition or a builder callback. * Enables `condition: $ => $.cond(...)` shorthand. * * @typeParam T - The entity type this condition operates on */ export type ConditionInput = | ConditionOperation | ((ops: ConditionOps) => AnyCondition); /** * Valid keys for condition operations, including generic inputs. */ type ValidConditionKeys = T extends unknown ? unknown extends T ? string : Paths : never; /** * Operations available in the condition builder callback. * * @typeParam T - The entity type the conditions operate on */ export type ConditionOps = { /** Creates a reference to another field for field-to-field comparisons */ ref: >(key: Key) => FieldRef; /** Creates a comparison condition */ cond: >( key: Key, op: '=' | '<>' | '<' | '<=' | '>' | '>=', value: Get | FieldRef, ) => CondOperation; /** Combines conditions with logical AND */ and: (...ops: AnyCondition[]) => AndOperation; /** Combines conditions with logical OR */ or: (...ops: AnyCondition[]) => OrOperation; /** Checks that an attribute does not exist */ attributeNotExists: >( key: Key, ) => AttributeNotExistsOperation; /** Checks that an attribute exists */ attributeExists: >( key: Key, ) => AttributeExistsOperation; }; function ref = ValidPaths>( key: Key, ): FieldRef { return { type: 'field_ref', key }; } function and(...ops: AnyCondition[]): AndOperation { return { type: 'condition_and', conditions: ops }; } function or(...ops: AnyCondition[]): OrOperation { return { type: 'condition_or', conditions: ops }; } function attributeNotExists = ValidPaths>( key: Key, ): AttributeNotExistsOperation { return { type: 'condition_attribute_not_exists', key }; } function attributeExists = ValidPaths>( key: Key, ): AttributeExistsOperation { return { type: 'condition_attribute_exists', key }; } /** * Creates a condition expression using a type-safe builder pattern. * * @typeParam T - The entity type the condition operates on * @param builder - Callback that receives condition operations and returns a condition * @returns A condition operation ready for compilation * * @example * ```ts * const condition = exprCondition(({ cond, and }) => * and( * cond("status", "=", "active"), * cond("age", ">=", 18) * ) * ); * ``` */ export function exprCondition( builder: (ops: ConditionOps) => AnyCondition, ): ConditionOperation { return builder({ ref: (key) => ref(key as ValidPaths), cond: (key, op, value) => ({ type: 'condition_condition', key: key as ValidPaths, op, value, }), and: (...ops: AnyCondition[]) => and(...ops), or: (...ops: AnyCondition[]) => or(...ops), attributeNotExists: (key) => attributeNotExists(key as ValidPaths), attributeExists: (key) => attributeExists(key as ValidPaths), }); } /** * Creates a filter expression using the same builder pattern as exprCondition. * Alias for exprCondition, used semantically for query filter expressions. * * @typeParam T - The entity type the filter operates on * @param builder - Callback that receives condition operations and returns a condition * @returns A condition operation ready for compilation */ export function exprFilter( builder: (ops: ConditionOps) => AnyCondition, ): ConditionOperation { return exprCondition(builder); } /** * Resolves a ConditionInput to a ConditionOperation. * If the input is a function, it is called via exprCondition. */ export function resolveCondition( input: ConditionInput, ): ConditionOperation { return typeof input === 'function' ? exprCondition(input) : input; } /** * Compiles a condition operation into a DynamoDB-ready expression. * * @typeParam T - The entity type the condition operates on * @param operation - The condition operation to compile * @returns A compiled condition with expression string and attribute maps */ export function compileConditionExpr( operation: ConditionOperation, ): CompiledConditionOperation { const attrBuilder = new AttributeMapBuilder('cf_'); function compile(op: AnyCondition): string { switch (op.type) { case 'condition_condition': { const left = attrBuilder.attr(op.key); const candidate = op.value; const right = typeof candidate === 'object' && candidate !== null && 'type' in candidate && candidate.type === 'field_ref' && 'key' in candidate && typeof candidate.key === 'string' ? attrBuilder.attr(candidate.key) : attrBuilder.value(candidate); return `${left} ${op.op} ${right}`; } case 'condition_and': { const exprs = op.conditions.map(compile); return `${exprs.join(' AND ')}`; } case 'condition_or': { const exprs = op.conditions.map(compile); return `${exprs.join(' OR ')}`; } case 'condition_attribute_not_exists': return `attribute_not_exists(${attrBuilder.attr(op.key)})`; case 'condition_attribute_exists': return `attribute_exists(${attrBuilder.attr(op.key)})`; } } return { type: 'condition_operation', expr: { expr: compile(operation), attrResult: attrBuilder.build(), }, }; }