Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions lib/condition/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,87 @@ import { transformValue } from '@lib/entity/helpers/transformValues';
import { EntityKey, EntityValue } from '@lib/entity/types';
import { BASE_OPERATOR, OPERATORS, Operators, ValidationError } from '@lib/utils';

/**
* Condition builder for DynamoDB condition expressions.
*
* This class provides a fluent interface for building complex condition expressions
* that can be used in DynamoDB operations like PutItem, UpdateItem, and DeleteItem.
* It supports all DynamoDB condition operators and functions with type safety.
*
* @template E - The entity class type
*
* @example
* ```typescript
* // Basic conditions
* const condition = new Condition(User)
* .attribute('status').eq('active')
* .attribute('age').gt(18);
*
* // Complex conditions with grouping
* const condition = new Condition(User)
* .attribute('status').eq('active')
* .parenthesis(
* new Condition(User)
* .attribute('age').gt(18)
* .or
* .attribute('role').eq('admin')
* );
*
* // Using with entity manager
* await UserManager.update(
* { id: 'user-123' },
* { status: 'inactive' },
* { condition }
* );
* ```
*
* @see {@link https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html} for DynamoDB condition expressions
* @see {@link https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html} for DynamoDB operators and functions
*/
export default class Condition<E extends typeof Entity> {
/** The entity class for type-safe attribute access */
protected entity: E;
/** The current logical operator (AND or OR) for chaining conditions */
protected logicalOperator: typeof BASE_OPERATOR.and | typeof BASE_OPERATOR.or = BASE_OPERATOR.and;
/** Array of operators that make up the condition expression */
protected operators: Operators;

/**
* Creates a new Condition instance for the specified entity.
*
* @param entity - The entity class to build conditions for
*
* @example
* ```typescript
* const condition = new Condition(User);
* ```
*/
constructor(entity: E) {
this.entity = entity;
this.operators = [];
}

/**
* Starts building a condition for a specific attribute.
*
* This method returns an object with all available comparison operators
* and functions for the specified attribute, providing a fluent interface
* for building conditions.
*
* @template C - The condition instance type
* @template K - The attribute key type
* @param key - The attribute name to build a condition for
* @returns An object with comparison operators and functions
*
* @example
* ```typescript
* const condition = new Condition(User)
* .attribute('status').eq('active')
* .attribute('age').gt(18)
* .attribute('email').beginsWith('admin@')
* .attribute('tags').contains('premium');
* ```
*/
public attribute<C extends this, K extends EntityKey<E>>(this: C, key: K) {
this.maybePushLogicalOperator();

Expand Down Expand Up @@ -137,6 +208,25 @@ export default class Condition<E extends typeof Entity> {
};
}

/**
* Wraps a condition in parentheses for grouping.
*
* @param condition - Optional condition to wrap in parentheses
* @returns The condition instance for chaining
*
* @example
* ```typescript
* const condition = new Condition(User)
* .attribute('status').eq('active')
* .and
* .parenthesis(
* new Condition(User)
* .attribute('age').gt(18)
* .or
* .attribute('role').eq('admin')
* );
* ```
*/
public parenthesis(condition?: Condition<E>): this {
if (condition) {
this.maybePushLogicalOperator();
Expand All @@ -145,10 +235,34 @@ export default class Condition<E extends typeof Entity> {
return this;
}

/**
* Alias for parenthesis() method for grouping conditions.
*
* @param condition - Optional condition to wrap in parentheses
* @returns The condition instance for chaining
*/
public group(condition?: Condition<E>): this {
return this.parenthesis(condition);
}

/**
* Adds another condition to this condition.
*
* @param condition - The condition to add
* @returns The condition instance for chaining
*
* @example
* ```typescript
* const baseCondition = new Condition(User)
* .attribute('status').eq('active');
*
* const finalCondition = baseCondition
* .condition(
* new Condition(User)
* .attribute('age').gt(18)
* );
* ```
*/
public condition(condition?: Condition<E>): this {
if (condition) {
this.maybePushLogicalOperator();
Expand All @@ -157,51 +271,108 @@ export default class Condition<E extends typeof Entity> {
return this;
}

/**
* Sets the logical operator to AND for the next condition.
*
* @returns The condition instance for chaining
*
* @example
* ```typescript
* const condition = new Condition(User)
* .attribute('status').eq('active')
* .attribute('age').gt(18);
* ```
*/
public get and(): this {
this.logicalOperator = BASE_OPERATOR.and;
return this;
}

/**
* Sets the logical operator to OR for the next condition.
*
* @returns The condition instance for chaining
*
* @example
* ```typescript
* const condition = new Condition(User)
* .attribute('status').eq('active')
* .or
* .attribute('role').eq('admin');
* ```
*/
public get or(): this {
this.logicalOperator = BASE_OPERATOR.or;
return this;
}

/**
* Adds an equality comparison operator.
* @protected
*/
protected eq<K extends EntityKey<E>>(operators: Operators, key: K, value: EntityValue<E, K>): this {
operators.push(...OPERATORS.eq(key, transformValue(this.entity, key, value)));
return this;
}

/**
* Adds a not equal comparison operator.
* @protected
*/
protected ne<K extends EntityKey<E>>(operators: Operators, key: K, value: EntityValue<E, K>): this {
operators.push(...OPERATORS.ne(key, transformValue(this.entity, key, value)));
return this;
}

/**
* Adds a less than comparison operator.
* @protected
*/
protected lt<K extends EntityKey<E>>(operators: Operators, key: K, value: EntityValue<E, K>): this {
operators.push(...OPERATORS.lt(key, transformValue(this.entity, key, value)));
return this;
}

/**
* Adds a less than or equal comparison operator.
* @protected
*/
protected le<K extends EntityKey<E>>(operators: Operators, key: K, value: EntityValue<E, K>): this {
operators.push(...OPERATORS.le(key, transformValue(this.entity, key, value)));
return this;
}

/**
* Adds a greater than comparison operator.
* @protected
*/
protected gt<K extends EntityKey<E>>(operators: Operators, key: K, value: EntityValue<E, K>): this {
operators.push(...OPERATORS.gt(key, transformValue(this.entity, key, value)));
return this;
}

/**
* Adds a greater than or equal comparison operator.
* @protected
*/
protected ge<K extends EntityKey<E>>(operators: Operators, key: K, value: EntityValue<E, K>): this {
operators.push(...OPERATORS.ge(key, transformValue(this.entity, key, value)));
return this;
}

/**
* Adds a begins_with function operator.
* @protected
*/
protected beginsWith<K extends EntityKey<E>>(operators: Operators, key: K, value: EntityValue<E, K>): this {
operators.push(...OPERATORS.beginsWith(key, transformValue(this.entity, key, value)));
return this;
}

/**
* Adds a BETWEEN operator.
* @protected
*/
protected between<K extends EntityKey<E>>(
operators: Operators,
key: K,
Expand All @@ -214,6 +385,10 @@ export default class Condition<E extends typeof Entity> {
return this;
}

/**
* Conditionally adds a logical operator between conditions.
* @protected
*/
protected maybePushLogicalOperator(): void {
if (this.operators.length > 0) {
this.operators.push(BASE_OPERATOR.space, this.logicalOperator, BASE_OPERATOR.space);
Expand All @@ -222,4 +397,9 @@ export default class Condition<E extends typeof Entity> {
}
}

/**
* DynamoDB attribute types enumeration.
*
* @see {@link AttributeType} for the full enumeration
*/
export { AttributeType };
16 changes: 16 additions & 0 deletions lib/condition/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
/**
* DynamoDB attribute types enumeration.
*
* These correspond to the attribute types used in DynamoDB operations
* and condition expressions.
*/
export enum AttributeType {
/** String attribute type */
String = 'S',
/** String set attribute type */
StringSet = 'SS',
/** Number attribute type */
Number = 'N',
/** Number set attribute type */
NumberSet = 'NS',
/** Binary attribute type */
Binary = 'B',
/** Binary set attribute type */
BinarySet = 'BS',
/** Boolean attribute type */
Boolean = 'BOOL',
/** Null attribute type */
Null = 'NULL',
/** List attribute type */
List = 'L',
/** Map attribute type */
Map = 'M',
}
Loading
Loading