From ecd274dc3cd6b19a8aff80e84382a53f117ab2ab Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Mon, 18 Mar 2024 15:05:13 +0200 Subject: [PATCH 01/12] added PutItemQueryBuilder --- src/nodes/itemNode.ts | 4 + src/nodes/putNode.ts | 10 +++ src/nodes/returnValuesNode.ts | 11 +++ .../putItemQueryBuilder.integration.test.ts | 67 +++++++++++++++ src/queryBuilders/putItemQueryBuilder.ts | 81 +++++++++++++++++++ src/queryCompiler/queryCompiler.ts | 22 ++++- src/queryCreator.ts | 26 ++++++ 7 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/nodes/itemNode.ts create mode 100644 src/nodes/putNode.ts create mode 100644 src/nodes/returnValuesNode.ts create mode 100644 src/queryBuilders/putItemQueryBuilder.integration.test.ts create mode 100644 src/queryBuilders/putItemQueryBuilder.ts diff --git a/src/nodes/itemNode.ts b/src/nodes/itemNode.ts new file mode 100644 index 0000000..6b27a92 --- /dev/null +++ b/src/nodes/itemNode.ts @@ -0,0 +1,4 @@ +export type ItemNode = { + readonly kind: "ItemNode"; + readonly item: Record; +}; diff --git a/src/nodes/putNode.ts b/src/nodes/putNode.ts new file mode 100644 index 0000000..687a75d --- /dev/null +++ b/src/nodes/putNode.ts @@ -0,0 +1,10 @@ +import { ItemNode } from "./itemNode"; +import { ReturnValuesNode } from "./returnValuesNode"; +import { TableNode } from "./tableNode"; + +export type PutNode = { + readonly kind: "PutNode"; + readonly table: TableNode; + readonly item?: ItemNode; + readonly returnValues?: ReturnValuesNode; +}; diff --git a/src/nodes/returnValuesNode.ts b/src/nodes/returnValuesNode.ts new file mode 100644 index 0000000..d413ccd --- /dev/null +++ b/src/nodes/returnValuesNode.ts @@ -0,0 +1,11 @@ +export type ReturnValuesOptions = + | "NONE" + | "ALL_OLD" + | "UPDATED_OLD" + | "ALL_NEW" + | "UPDATED_NEW"; + +export type ReturnValuesNode = { + readonly kind: "ReturnValuesNode"; + readonly option: ReturnValuesOptions; +}; diff --git a/src/queryBuilders/putItemQueryBuilder.integration.test.ts b/src/queryBuilders/putItemQueryBuilder.integration.test.ts new file mode 100644 index 0000000..1301750 --- /dev/null +++ b/src/queryBuilders/putItemQueryBuilder.integration.test.ts @@ -0,0 +1,67 @@ +import { DDB, TEST_DATA } from "../../test/testFixture"; +import { getDDBClientFor, startDDBTestContainer } from "../../test/testUtil"; +import { Tsynamo } from "./../index"; + +describe("GetItemQueryBuilder", () => { + let tsynamoClient: Tsynamo; + + const itemToPut = { + userId: "333", + dataTimestamp: 222, + someBoolean: true, + }; + + beforeAll(async () => { + const testContainer = await startDDBTestContainer(); + + tsynamoClient = new Tsynamo({ + ddbClient: await getDDBClientFor(testContainer), + }); + }); + + it("handles a simple put query", async () => { + let result = await tsynamoClient + .getItemFrom("myTable") + .keys({ + userId: itemToPut.userId, + dataTimestamp: itemToPut.dataTimestamp, + }) + .execute(); + + expect(result).toBeUndefined(); + + await tsynamoClient.putItem("myTable").item(itemToPut).execute(); + + result = await tsynamoClient + .getItemFrom("myTable") + .keys({ + userId: "333", + dataTimestamp: 222, + }) + .execute(); + + expect(result).toBeDefined(); + expect(result).toEqual(itemToPut); + }); + + it("handles ReturnValues option", async () => { + let result = await tsynamoClient + .putItem("myTable") + .item(itemToPut) + .returnValues("ALL_OLD") + .execute(); + + expect(result).toBeUndefined(); + + result = await tsynamoClient + .putItem("myTable") + .item({ + ...itemToPut, + tags: ["kissa"], + }) + .returnValues("ALL_OLD") + .execute(); + + expect(result).toEqual(itemToPut); + }); +}); diff --git a/src/queryBuilders/putItemQueryBuilder.ts b/src/queryBuilders/putItemQueryBuilder.ts new file mode 100644 index 0000000..1651207 --- /dev/null +++ b/src/queryBuilders/putItemQueryBuilder.ts @@ -0,0 +1,81 @@ +import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb"; +import { PutNode } from "../nodes/putNode"; +import { QueryCompiler } from "../queryCompiler"; +import { ExecuteOutput } from "../typeHelpers"; +import { preventAwait } from "../util/preventAwait"; +import { ReturnValuesOptions } from "../nodes/returnValuesNode"; + +export interface PutItemQueryBuilderInterface { + execute(): Promise[] | undefined>; + + returnValues( + option: Extract + ): PutItemQueryBuilderInterface; + + item>( + item: Item + ): PutItemQueryBuilderInterface; +} + +/** + * @todo support ConditionExpression + */ +export class PutItemQueryBuilder< + DDB, + Table extends keyof DDB, + O extends DDB[Table] +> implements PutItemQueryBuilderInterface +{ + readonly #props: PutItemQueryBuilderProps; + + constructor(props: PutItemQueryBuilderProps) { + this.#props = props; + } + + item>( + item: Item + ): PutItemQueryBuilderInterface { + return new PutItemQueryBuilder({ + ...this.#props, + node: { + ...this.#props.node, + item: { + kind: "ItemNode", + item, + }, + }, + }); + } + + returnValues( + option: ReturnValuesOptions + ): PutItemQueryBuilderInterface { + return new PutItemQueryBuilder({ + ...this.#props, + node: { + ...this.#props.node, + returnValues: { + kind: "ReturnValuesNode", + option, + }, + }, + }); + } + + execute = async (): Promise[] | undefined> => { + const putCommand = this.#props.queryCompiler.compile(this.#props.node); + const data = await this.#props.ddbClient.send(putCommand); + return data.Attributes as any; + }; +} + +preventAwait( + PutItemQueryBuilder, + "Don't await PutQueryBuilder instances directly. To execute the query you need to call the `execute` method" +); + +interface PutItemQueryBuilderProps { + readonly node: PutNode; + readonly ddbClient: DynamoDBDocumentClient; + readonly queryCompiler: QueryCompiler; +} diff --git a/src/queryCompiler/queryCompiler.ts b/src/queryCompiler/queryCompiler.ts index d80c671..916a0ec 100644 --- a/src/queryCompiler/queryCompiler.ts +++ b/src/queryCompiler/queryCompiler.ts @@ -1,4 +1,4 @@ -import { GetCommand, QueryCommand } from "@aws-sdk/lib-dynamodb"; +import { GetCommand, PutCommand, QueryCommand } from "@aws-sdk/lib-dynamodb"; import { FilterExpressionJoinTypeNode } from "../nodes/filterExpressionJoinTypeNode"; import { FilterExpressionNode } from "../nodes/filterExpressionNode"; import { GetNode } from "../nodes/getNode"; @@ -10,16 +10,20 @@ import { mergeObjectIntoMap, } from "./compilerUtil"; import { AttributesNode } from "../nodes/attributesNode"; +import { PutNode } from "../nodes/putNode"; export class QueryCompiler { compile(rootNode: QueryNode): QueryCommand; compile(rootNode: GetNode): GetCommand; - compile(rootNode: QueryNode | GetNode) { + compile(rootNode: PutNode): PutCommand; + compile(rootNode: QueryNode | GetNode | PutNode) { switch (rootNode.kind) { case "GetNode": return this.compileGetNode(rootNode); case "QueryNode": return this.compileQueryNode(rootNode); + case "PutNode": + return this.compilePutNode(rootNode); } } @@ -97,6 +101,20 @@ export class QueryCompiler { }); } + compilePutNode(putNode: PutNode) { + const { + table: tableNode, + item: itemNode, + returnValues: returnValuesNode, + } = putNode; + + return new PutCommand({ + TableName: tableNode.table, + Item: itemNode?.item, + ReturnValues: returnValuesNode?.option, + }); + } + compileAttributeNamesNode(node?: AttributesNode) { const ProjectionExpression = node?.attributes .map((att) => getExpressionAttributeNameFrom(att)) diff --git a/src/queryCreator.ts b/src/queryCreator.ts index 06c0664..5f5f30d 100644 --- a/src/queryCreator.ts +++ b/src/queryCreator.ts @@ -1,5 +1,9 @@ import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb"; import { GetQueryBuilder } from "./queryBuilders/getItemQueryBuilder"; +import { + PutItemQueryBuilder, + PutItemQueryBuilderInterface, +} from "./queryBuilders/putItemQueryBuilder"; import { QueryQueryBuilder, QueryQueryBuilderInterface, @@ -36,6 +40,12 @@ export class QueryCreator { }); } + /** + * + * @param table Table to perform the query command to + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/QueryCommand/ + */ query( table: Table ): QueryQueryBuilderInterface { @@ -56,6 +66,22 @@ export class QueryCreator { queryCompiler: this.#props.queryCompiler, }); } + + putItem
( + table: Table + ): PutItemQueryBuilderInterface { + return new PutItemQueryBuilder({ + node: { + kind: "PutNode", + table: { + kind: "TableNode", + table, + }, + }, + ddbClient: this.#props.ddbClient, + queryCompiler: this.#props.queryCompiler, + }); + } } export interface QueryCreatorProps { From 01e95e14f05bfbb1057af495585ddb8826650161 Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Mon, 18 Mar 2024 19:34:41 +0200 Subject: [PATCH 02/12] filterExpression -> expression --- src/nodes/expressionComparatorExpression.ts | 8 ++++ ...nTypeNode.ts => expressionJoinTypeNode.ts} | 16 ++++---- src/nodes/expressionNode.ts | 6 +++ src/nodes/expressionNotExpression.ts | 6 +++ .../filterExpressionComparatorExpression.ts | 8 ---- src/nodes/filterExpressionNode.ts | 6 --- src/nodes/filterExpressionNotExpression.ts | 6 --- src/nodes/operands.ts | 2 +- src/nodes/queryNode.ts | 4 +- src/queryBuilders/queryQueryBuilder.ts | 37 +++++++++---------- src/queryCompiler/queryCompiler.ts | 22 +++++------ src/queryCreator.ts | 2 +- 12 files changed, 61 insertions(+), 62 deletions(-) create mode 100644 src/nodes/expressionComparatorExpression.ts rename src/nodes/{filterExpressionJoinTypeNode.ts => expressionJoinTypeNode.ts} (60%) create mode 100644 src/nodes/expressionNode.ts create mode 100644 src/nodes/expressionNotExpression.ts delete mode 100644 src/nodes/filterExpressionComparatorExpression.ts delete mode 100644 src/nodes/filterExpressionNode.ts delete mode 100644 src/nodes/filterExpressionNotExpression.ts diff --git a/src/nodes/expressionComparatorExpression.ts b/src/nodes/expressionComparatorExpression.ts new file mode 100644 index 0000000..5d64a72 --- /dev/null +++ b/src/nodes/expressionComparatorExpression.ts @@ -0,0 +1,8 @@ +import { ExpressionConditionComparators } from "./operands"; + +export type ExpressionComparatorExpressions = { + readonly kind: "ExpressionComparatorExpressions"; + readonly key: string; + readonly operation: ExpressionConditionComparators; + readonly value: unknown; +}; diff --git a/src/nodes/filterExpressionJoinTypeNode.ts b/src/nodes/expressionJoinTypeNode.ts similarity index 60% rename from src/nodes/filterExpressionJoinTypeNode.ts rename to src/nodes/expressionJoinTypeNode.ts index 97bfc0c..e589490 100644 --- a/src/nodes/filterExpressionJoinTypeNode.ts +++ b/src/nodes/expressionJoinTypeNode.ts @@ -3,18 +3,18 @@ import { AttributeNotExistsFunctionExpression } from "./attributeNotExistsFuncti import { BeginsWithFunctionExpression } from "./beginsWithFunctionExpression"; import { BetweenConditionExpression } from "./betweenConditionExpression"; import { ContainsFunctionExpression } from "./containsFunctionExpression"; -import { FilterExpressionComparatorExpressions } from "./filterExpressionComparatorExpression"; -import { FilterExpressionNode } from "./filterExpressionNode"; -import { FilterExpressionNotExpression } from "./filterExpressionNotExpression"; +import { ExpressionComparatorExpressions } from "./expressionComparatorExpression"; +import { ExpressionNode } from "./expressionNode"; +import { ExpressionNotExpression } from "./expressionNotExpression"; export type JoinType = "AND" | "OR"; -export type FilterExpressionJoinTypeNode = { - readonly kind: "FilterExpressionJoinTypeNode"; +export type ExpressionJoinTypeNode = { + readonly kind: "ExpressionJoinTypeNode"; readonly expr: - | FilterExpressionNode - | FilterExpressionComparatorExpressions - | FilterExpressionNotExpression + | ExpressionNode + | ExpressionComparatorExpressions + | ExpressionNotExpression | AttributeExistsFunctionExpression | AttributeNotExistsFunctionExpression | BetweenConditionExpression diff --git a/src/nodes/expressionNode.ts b/src/nodes/expressionNode.ts new file mode 100644 index 0000000..390e6c7 --- /dev/null +++ b/src/nodes/expressionNode.ts @@ -0,0 +1,6 @@ +import { ExpressionJoinTypeNode } from "./expressionJoinTypeNode"; + +export type ExpressionNode = { + readonly kind: "ExpressionNode"; + readonly expressions: ExpressionJoinTypeNode[]; +}; diff --git a/src/nodes/expressionNotExpression.ts b/src/nodes/expressionNotExpression.ts new file mode 100644 index 0000000..8325947 --- /dev/null +++ b/src/nodes/expressionNotExpression.ts @@ -0,0 +1,6 @@ +import { ExpressionNode } from "./expressionNode"; + +export type ExpressionNotExpression = { + readonly kind: "ExpressionNotExpression"; + readonly expr: ExpressionNode; +}; diff --git a/src/nodes/filterExpressionComparatorExpression.ts b/src/nodes/filterExpressionComparatorExpression.ts deleted file mode 100644 index 76f0099..0000000 --- a/src/nodes/filterExpressionComparatorExpression.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { FilterConditionComparators } from "./operands"; - -export type FilterExpressionComparatorExpressions = { - readonly kind: "FilterExpressionComparatorExpressions"; - readonly key: string; - readonly operation: FilterConditionComparators; - readonly value: unknown; -}; diff --git a/src/nodes/filterExpressionNode.ts b/src/nodes/filterExpressionNode.ts deleted file mode 100644 index 8213903..0000000 --- a/src/nodes/filterExpressionNode.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FilterExpressionJoinTypeNode } from "./filterExpressionJoinTypeNode"; - -export type FilterExpressionNode = { - readonly kind: "FilterExpressionNode"; - readonly expressions: FilterExpressionJoinTypeNode[]; -}; diff --git a/src/nodes/filterExpressionNotExpression.ts b/src/nodes/filterExpressionNotExpression.ts deleted file mode 100644 index fd84716..0000000 --- a/src/nodes/filterExpressionNotExpression.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FilterExpressionNode } from "./filterExpressionNode"; - -export type FilterExpressionNotExpression = { - readonly kind: "FilterExpressionNotExpression"; - readonly expr: FilterExpressionNode; -}; diff --git a/src/nodes/operands.ts b/src/nodes/operands.ts index e8aec33..922a287 100644 --- a/src/nodes/operands.ts +++ b/src/nodes/operands.ts @@ -10,4 +10,4 @@ export type FunctionExpression = export type NotExpression = "NOT"; export type KeyConditionComparators = "=" | "<" | "<=" | ">" | ">="; -export type FilterConditionComparators = KeyConditionComparators | "<>"; +export type ExpressionConditionComparators = KeyConditionComparators | "<>"; diff --git a/src/nodes/queryNode.ts b/src/nodes/queryNode.ts index ece9837..959e7f5 100644 --- a/src/nodes/queryNode.ts +++ b/src/nodes/queryNode.ts @@ -1,6 +1,6 @@ import { AttributesNode } from "./attributesNode"; import { ConsistentReadNode } from "./consistentReadNode"; -import { FilterExpressionNode } from "./filterExpressionNode"; +import { ExpressionNode } from "./expressionNode"; import { KeyConditionNode } from "./keyConditionNode"; import { LimitNode } from "./limitNode"; import { ScanIndexForwardNode } from "./scanIndexForwardNode"; @@ -10,7 +10,7 @@ export type QueryNode = { readonly kind: "QueryNode"; readonly table: TableNode; readonly keyConditions: KeyConditionNode[]; - readonly filterExpression: FilterExpressionNode; + readonly filterExpression: ExpressionNode; readonly consistentRead?: ConsistentReadNode; readonly scanIndexForward?: ScanIndexForwardNode; readonly limit?: LimitNode; diff --git a/src/queryBuilders/queryQueryBuilder.ts b/src/queryBuilders/queryQueryBuilder.ts index 186cee9..6f6f698 100644 --- a/src/queryBuilders/queryQueryBuilder.ts +++ b/src/queryBuilders/queryQueryBuilder.ts @@ -2,12 +2,12 @@ import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb"; import { AttributeExistsFunctionExpression } from "../nodes/attributeExistsFunctionExpression"; import { AttributeNotExistsFunctionExpression } from "../nodes/attributeNotExistsFunctionExpression"; import { - FilterExpressionJoinTypeNode, + ExpressionJoinTypeNode, JoinType, -} from "../nodes/filterExpressionJoinTypeNode"; +} from "../nodes/expressionJoinTypeNode"; import { BetweenExpression, - FilterConditionComparators, + ExpressionConditionComparators, FunctionExpression, KeyConditionComparators, NotExpression, @@ -63,7 +63,7 @@ export interface QueryQueryBuilderInterface { // Regular operand filterExpression>>( key: Exclude, - operation: Key extends NotExpression ? never : FilterConditionComparators, + operation: Key extends NotExpression ? never : ExpressionConditionComparators, val: StripKeys> ): QueryQueryBuilderInterface; @@ -123,7 +123,7 @@ export interface QueryQueryBuilderInterface { // Regular operand orFilterExpression>>( key: Key, - operation: FilterConditionComparators, + operation: ExpressionConditionComparators, val: StripKeys> ): QueryQueryBuilderInterface; @@ -203,7 +203,7 @@ export interface QueryQueryBuilderInterfaceWithOnlyFilterOperations< */ filterExpression>>( key: Key, - operation: FilterConditionComparators, + operation: ExpressionConditionComparators, val: StripKeys> ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; @@ -255,7 +255,7 @@ export interface QueryQueryBuilderInterfaceWithOnlyFilterOperations< */ orFilterExpression>>( key: Key, - operation: FilterConditionComparators, + operation: ExpressionConditionComparators, val: StripKeys> ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; @@ -313,7 +313,7 @@ type FilterExprArgs< > = | [ key: Key, - operation: FilterConditionComparators, + operation: ExpressionConditionComparators, value: StripKeys> ] | [ @@ -452,7 +452,7 @@ export class QueryQueryBuilder< filterExpression: { ...this.#props.node.filterExpression, expressions: this.#props.node.filterExpression.expressions.concat({ - kind: "FilterExpressionJoinTypeNode", + kind: "ExpressionJoinTypeNode", expr: { kind: "BeginsWithFunctionExpression", key, @@ -473,7 +473,7 @@ export class QueryQueryBuilder< filterExpression: { ...this.#props.node.filterExpression, expressions: this.#props.node.filterExpression.expressions.concat({ - kind: "FilterExpressionJoinTypeNode", + kind: "ExpressionJoinTypeNode", expr: { kind: "ContainsFunctionExpression", key, @@ -506,7 +506,7 @@ export class QueryQueryBuilder< filterExpression: { ...this.#props.node.filterExpression, expressions: this.#props.node.filterExpression.expressions.concat({ - kind: "FilterExpressionJoinTypeNode", + kind: "ExpressionJoinTypeNode", expr: resultExpr, joinType, }), @@ -523,7 +523,7 @@ export class QueryQueryBuilder< filterExpression: { ...this.#props.node.filterExpression, expressions: this.#props.node.filterExpression.expressions.concat({ - kind: "FilterExpressionJoinTypeNode", + kind: "ExpressionJoinTypeNode", expr: { kind: "BetweenConditionExpression", key, @@ -551,10 +551,10 @@ export class QueryQueryBuilder< filterExpression: { ...this.#props.node.filterExpression, expressions: this.#props.node.filterExpression.expressions.concat({ - kind: "FilterExpressionJoinTypeNode", + kind: "ExpressionJoinTypeNode", joinType, expr: { - kind: "FilterExpressionComparatorExpressions", + kind: "ExpressionComparatorExpressions", key, operation, value, @@ -580,7 +580,7 @@ export class QueryQueryBuilder< ...this.#props.node, filterExpression: { expressions: [], - kind: "FilterExpressionNode", + kind: "ExpressionNode", }, }, }); @@ -589,8 +589,8 @@ export class QueryQueryBuilder< const { filterExpression } = result._getNode(); - let resultNode: FilterExpressionJoinTypeNode = { - kind: "FilterExpressionJoinTypeNode", + let resultNode: ExpressionJoinTypeNode = { + kind: "ExpressionJoinTypeNode", expr: filterExpression, joinType, }; @@ -599,7 +599,7 @@ export class QueryQueryBuilder< resultNode = { ...resultNode, expr: { - kind: "FilterExpressionNotExpression", + kind: "ExpressionNotExpression", expr: filterExpression, }, }; @@ -621,7 +621,6 @@ export class QueryQueryBuilder< throw new Error("Invalid arguments given to filterExpression"); } - // TODO: Add support for all operations from here: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Syntax filterExpression>>( ...args: FilterExprArgs ): QueryQueryBuilderInterface { diff --git a/src/queryCompiler/queryCompiler.ts b/src/queryCompiler/queryCompiler.ts index 916a0ec..a1e49b8 100644 --- a/src/queryCompiler/queryCompiler.ts +++ b/src/queryCompiler/queryCompiler.ts @@ -1,6 +1,6 @@ import { GetCommand, PutCommand, QueryCommand } from "@aws-sdk/lib-dynamodb"; -import { FilterExpressionJoinTypeNode } from "../nodes/filterExpressionJoinTypeNode"; -import { FilterExpressionNode } from "../nodes/filterExpressionNode"; +import { ExpressionJoinTypeNode } from "../nodes/expressionJoinTypeNode"; +import { ExpressionNode } from "../nodes/expressionNode"; import { GetNode } from "../nodes/getNode"; import { KeyConditionNode } from "../nodes/keyConditionNode"; import { QueryNode } from "../nodes/queryNode"; @@ -68,7 +68,7 @@ export class QueryCompiler { attributeNames ); - const compiledFilterExpression = this.compileFilterExpression( + const compiledFilterExpression = this.compileExpression( filterExpressionNode, filterExpressionAttributeValues, attributeNames @@ -151,8 +151,8 @@ export class QueryCompiler { }; } - compileFilterExpression = ( - expression: FilterExpressionNode, + compileExpression = ( + expression: ExpressionNode, filterExpressionAttributeValues: Map, attributeNames: Map ) => { @@ -174,7 +174,7 @@ export class QueryCompiler { }; compileFilterExpressionJoinNodes = ( - { expr }: FilterExpressionJoinTypeNode, + { expr }: ExpressionJoinTypeNode, filterExpressionAttributeValues: Map, attributeNames: Map ) => { @@ -193,9 +193,9 @@ export class QueryCompiler { } switch (expr.kind) { - case "FilterExpressionNode": { + case "ExpressionNode": { res += "("; - res += this.compileFilterExpression( + res += this.compileExpression( expr, filterExpressionAttributeValues, attributeNames @@ -204,15 +204,15 @@ export class QueryCompiler { break; } - case "FilterExpressionComparatorExpressions": { + case "ExpressionComparatorExpressions": { res += `${attributeName} ${expr.operation} ${attributeValue}`; filterExpressionAttributeValues.set(attributeValue, expr.value); break; } - case "FilterExpressionNotExpression": { + case "ExpressionNotExpression": { res += "NOT ("; - res += this.compileFilterExpression( + res += this.compileExpression( expr.expr, filterExpressionAttributeValues, attributeNames diff --git a/src/queryCreator.ts b/src/queryCreator.ts index 5f5f30d..18d3eca 100644 --- a/src/queryCreator.ts +++ b/src/queryCreator.ts @@ -58,7 +58,7 @@ export class QueryCreator { }, keyConditions: [], filterExpression: { - kind: "FilterExpressionNode", + kind: "ExpressionNode", expressions: [], }, }, From 3db874cc955ebc9e54a635a96a5ede554c9a5eab Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Mon, 18 Mar 2024 22:44:48 +0200 Subject: [PATCH 03/12] refactor expression logic into separate builder --- src/nodes/putNode.ts | 2 + src/queryBuilders/expressionBuilder.ts | 488 +++++++++++++++++ .../putItemQueryBuilder.integration.test.ts | 15 + src/queryBuilders/putItemQueryBuilder.ts | 49 +- .../queryQueryBuilder.integration.test.ts | 13 +- src/queryBuilders/queryQueryBuilder.ts | 518 ++---------------- src/queryCompiler/queryCompiler.ts | 25 + src/queryCreator.ts | 4 + src/typeHelpers.ts | 9 + 9 files changed, 633 insertions(+), 490 deletions(-) create mode 100644 src/queryBuilders/expressionBuilder.ts diff --git a/src/nodes/putNode.ts b/src/nodes/putNode.ts index 687a75d..c158089 100644 --- a/src/nodes/putNode.ts +++ b/src/nodes/putNode.ts @@ -1,3 +1,4 @@ +import { ExpressionNode } from "./expressionNode"; import { ItemNode } from "./itemNode"; import { ReturnValuesNode } from "./returnValuesNode"; import { TableNode } from "./tableNode"; @@ -5,6 +6,7 @@ import { TableNode } from "./tableNode"; export type PutNode = { readonly kind: "PutNode"; readonly table: TableNode; + readonly conditionExpression: ExpressionNode; readonly item?: ItemNode; readonly returnValues?: ReturnValuesNode; }; diff --git a/src/queryBuilders/expressionBuilder.ts b/src/queryBuilders/expressionBuilder.ts new file mode 100644 index 0000000..c9ba5b7 --- /dev/null +++ b/src/queryBuilders/expressionBuilder.ts @@ -0,0 +1,488 @@ +import { AttributeExistsFunctionExpression } from "../nodes/attributeExistsFunctionExpression"; +import { AttributeNotExistsFunctionExpression } from "../nodes/attributeNotExistsFunctionExpression"; +import { + ExpressionJoinTypeNode, + JoinType, +} from "../nodes/expressionJoinTypeNode"; +import { ExpressionNode } from "../nodes/expressionNode"; +import { + BetweenExpression, + ExpressionConditionComparators, + FunctionExpression, + NotExpression, +} from "../nodes/operands"; +import { + GetFromPath, + ObjectKeyPaths, + PickNonKeys, + StripKeys, +} from "../typeHelpers"; + +export interface ExpressionBuilderInterface { + // Regular operand + expression>>( + key: Exclude, + operation: Key extends NotExpression + ? never + : ExpressionConditionComparators, + val: StripKeys> + ): ExpressionBuilderInterface; + + // function expression for functions that only take path as param + expression>>( + key: Exclude, + func: Extract< + FunctionExpression, + "attribute_exists" | "attribute_not_exists" + > + ): ExpressionBuilderInterface; + + // CONTAINS function expression + expression< + Key extends ObjectKeyPaths>, + Property extends GetFromPath & unknown[] + >( + key: Key, + expr: Extract, + value: StripKeys[number] + ): ExpressionBuilderInterface; + + // BEGINS_WITH function expression + expression>>( + key: Key, + expr: Extract, + substr: string + ): ExpressionBuilderInterface; + + // BETWEEN expression + expression>>( + key: Key, + expr: BetweenExpression, + left: StripKeys>, + right: StripKeys> + ): ExpressionBuilderInterface; + + // NOT expression + expression( + not: NotExpression, + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + ): ExpressionBuilderInterface; + + // Nested expression + expression( + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + ): ExpressionBuilderInterface; + + // Or expressions + orExpression>>( + key: Key, + operation: ExpressionConditionComparators, + val: StripKeys> + ): ExpressionBuilderInterface; + + orExpression>>( + key: Exclude, + func: Extract< + FunctionExpression, + "attribute_exists" | "attribute_not_exists" + > + ): ExpressionBuilderInterface; + + orExpression>>( + key: Key, + func: Extract, + substr: string + ): ExpressionBuilderInterface; + + orExpression< + Key extends ObjectKeyPaths>, + Property extends GetFromPath & unknown[] + >( + key: Key, + expr: Extract, + value: StripKeys[number] + ): ExpressionBuilderInterface; + + orExpression>>( + key: Key, + expr: BetweenExpression, + left: StripKeys>, + right: StripKeys> + ): ExpressionBuilderInterface; + + orExpression( + not: NotExpression, + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + ): ExpressionBuilderInterface; + + orExpression( + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + ): ExpressionBuilderInterface; + + _getNode(): ExpressionNode; +} + +export interface ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table extends keyof DDB, + O +> { + /** + * expression methods + */ + expression>>( + key: Key, + operation: ExpressionConditionComparators, + val: StripKeys> + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + expression>>( + key: Exclude, + func: Extract< + FunctionExpression, + "attribute_exists" | "attribute_not_exists" + > + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + expression>>( + key: Key, + func: Extract, + substr: string + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + expression< + Key extends ObjectKeyPaths>, + Property extends GetFromPath & unknown[] + >( + key: Key, + expr: Extract, + value: StripKeys[number] + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + expression>>( + key: Key, + expr: BetweenExpression, + left: StripKeys>, + right: StripKeys> + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + expression( + not: NotExpression, + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + expression( + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + /** + * orExpression methods + */ + orExpression>>( + key: Key, + operation: ExpressionConditionComparators, + val: StripKeys> + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + orExpression>>( + key: Exclude, + func: Extract< + FunctionExpression, + "attribute_exists" | "attribute_not_exists" + > + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + orExpression>>( + key: Key, + func: Extract, + substr: string + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + orExpression< + Key extends ObjectKeyPaths>, + Property extends GetFromPath & unknown[] + >( + key: Key, + expr: Extract, + value: StripKeys[number] + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + orExpression>>( + key: Key, + expr: BetweenExpression, + left: StripKeys>, + right: StripKeys> + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + orExpression( + not: NotExpression, + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + orExpression( + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + + _getNode(): ExpressionNode; +} + +export type ExprArgs< + DDB, + Table extends keyof DDB, + O, + Key extends ObjectKeyPaths> +> = + | [ + key: Key, + operation: ExpressionConditionComparators, + value: StripKeys> + ] + | [ + key: Exclude, + func: Extract< + FunctionExpression, + "attribute_exists" | "attribute_not_exists" + > + ] + | [key: Key, func: Extract, substr: string] + | [ + key: Key, + expr: Extract, + value: GetFromPath extends unknown[] + ? StripKeys>[number] + : never + ] + | [ + key: Key, + expr: BetweenExpression, + left: StripKeys>, + right: StripKeys> + ] + | [ + not: NotExpression, + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O + > + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + ] + | [ + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O + > + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + ]; + +export class ExpressionBuilder< + DDB, + Table extends keyof DDB, + O extends DDB[Table] +> implements ExpressionBuilderInterface +{ + readonly #props: ExpressionBuilderProps; + + constructor(props: ExpressionBuilderProps) { + this.#props = props; + } + + _expression>>( + joinType: JoinType, + ...args: ExprArgs + ): ExpressionBuilderInterface { + if (args[1] === "begins_with") { + const [key, f, substr] = args; + + return new ExpressionBuilder({ + ...this.#props, + node: { + ...this.#props.node, + expressions: this.#props.node.expressions.concat({ + kind: "ExpressionJoinTypeNode", + expr: { + kind: "BeginsWithFunctionExpression", + key, + substr, + }, + joinType, + }), + }, + }); + } else if (args[1] === "contains") { + const [key, expr, value] = args; + + return new ExpressionBuilder({ + ...this.#props, + node: { + ...this.#props.node, + expressions: this.#props.node.expressions.concat({ + kind: "ExpressionJoinTypeNode", + expr: { + kind: "ContainsFunctionExpression", + key, + value, + }, + joinType, + }), + }, + }); + } else if ( + args[1] === "attribute_exists" || + args[1] === "attribute_not_exists" + ) { + const [key, func] = args; + let resultExpr: + | AttributeExistsFunctionExpression + | AttributeNotExistsFunctionExpression; + + if (func === "attribute_exists") { + resultExpr = { kind: "AttributeExistsFunctionExpression", key }; + } else { + resultExpr = { kind: "AttributeNotExistsFunctionExpression", key }; + } + + return new ExpressionBuilder({ + ...this.#props, + node: { + ...this.#props.node, + expressions: this.#props.node.expressions.concat({ + kind: "ExpressionJoinTypeNode", + expr: resultExpr, + joinType, + }), + }, + }); + } else if (args[1] === "BETWEEN") { + const [key, expr, left, right] = args; + + return new ExpressionBuilder({ + ...this.#props, + node: { + ...this.#props.node, + expressions: this.#props.node.expressions.concat({ + kind: "ExpressionJoinTypeNode", + expr: { + kind: "BetweenConditionExpression", + key, + left, + right, + }, + joinType, + }), + }, + }); + } else if ( + typeof args[0] !== "function" && + args[0] !== "NOT" && + typeof args[1] !== "function" && + args[1] !== undefined && + args[2] !== undefined + ) { + const [key, operation, value] = args; + + return new ExpressionBuilder({ + ...this.#props, + node: { + ...this.#props.node, + expressions: this.#props.node.expressions.concat({ + kind: "ExpressionJoinTypeNode", + joinType, + expr: { + kind: "ExpressionComparatorExpressions", + key, + operation, + value, + }, + }), + }, + }); + } else if (typeof args[0] === "function" || typeof args[1] === "function") { + let builder; + + if (typeof args[0] === "function") { + builder = args[0]; + } else if (typeof args[1] === "function") { + builder = args[1]; + } + + if (!builder) throw new Error("Could not find builder"); + + const qb = new ExpressionBuilder({ + ...this.#props, + node: { + expressions: [], + kind: "ExpressionNode", + }, + }); + + const result = builder(qb); + const expressionNode = result._getNode(); + + let resultNode: ExpressionJoinTypeNode = { + kind: "ExpressionJoinTypeNode", + expr: expressionNode, + joinType, + }; + + if (args[0] === "NOT") { + resultNode = { + ...resultNode, + expr: { + kind: "ExpressionNotExpression", + expr: expressionNode, + }, + }; + } + + return new ExpressionBuilder({ + ...this.#props, + node: { + ...this.#props.node, + expressions: this.#props.node.expressions.concat(resultNode), + }, + }); + } + + throw new Error("Invalid arguments given to expression builder"); + } + + expression>>( + ...args: ExprArgs + ): ExpressionBuilderInterface { + return this._expression("AND", ...args); + } + + orExpression>>( + ...args: ExprArgs + ): ExpressionBuilderInterface { + return this._expression("OR", ...args); + } + + _getNode() { + return this.#props.node; + } +} + +interface ExpressionBuilderProps { + readonly node: ExpressionNode; +} diff --git a/src/queryBuilders/putItemQueryBuilder.integration.test.ts b/src/queryBuilders/putItemQueryBuilder.integration.test.ts index 1301750..6fc5b4a 100644 --- a/src/queryBuilders/putItemQueryBuilder.integration.test.ts +++ b/src/queryBuilders/putItemQueryBuilder.integration.test.ts @@ -64,4 +64,19 @@ describe("GetItemQueryBuilder", () => { expect(result).toEqual(itemToPut); }); + + it("handles an 'attribute_not_exists' ConditionExpression", async () => { + await tsynamoClient.putItem("myTable").item(itemToPut).execute(); + expect( + tsynamoClient + .putItem("myTable") + .item(itemToPut) + .conditionExpression("userId", "attribute_not_exists") + .execute() + ).rejects.toMatchInlineSnapshot( + `[ConditionalCheckFailedException: The conditional request failed]` + ); + }); + + it.todo("handles 'contains' ConditionExpression"); }); diff --git a/src/queryBuilders/putItemQueryBuilder.ts b/src/queryBuilders/putItemQueryBuilder.ts index 1651207..0920bb8 100644 --- a/src/queryBuilders/putItemQueryBuilder.ts +++ b/src/queryBuilders/putItemQueryBuilder.ts @@ -1,12 +1,19 @@ import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb"; import { PutNode } from "../nodes/putNode"; import { QueryCompiler } from "../queryCompiler"; -import { ExecuteOutput } from "../typeHelpers"; +import { ExecuteOutput, ObjectKeyPaths, PickNonKeys } from "../typeHelpers"; import { preventAwait } from "../util/preventAwait"; import { ReturnValuesOptions } from "../nodes/returnValuesNode"; +import { ExprArgs, ExpressionBuilder } from "./expressionBuilder"; export interface PutItemQueryBuilderInterface { - execute(): Promise[] | undefined>; + conditionExpression>( + ...args: ExprArgs + ): PutItemQueryBuilderInterface; + + orConditionExpression>( + ...args: ExprArgs + ): PutItemQueryBuilderInterface; returnValues( option: Extract @@ -15,6 +22,8 @@ export interface PutItemQueryBuilderInterface { item>( item: Item ): PutItemQueryBuilderInterface; + + execute(): Promise[] | undefined>; } /** @@ -32,6 +41,42 @@ export class PutItemQueryBuilder< this.#props = props; } + conditionExpression>( + ...args: ExprArgs + ): PutItemQueryBuilderInterface { + const eB = new ExpressionBuilder({ + node: { ...this.#props.node.conditionExpression }, + }); + + const expressionNode = eB.expression(...args)._getNode(); + + return new PutItemQueryBuilder({ + ...this.#props, + node: { + ...this.#props.node, + conditionExpression: expressionNode, + }, + }); + } + + orConditionExpression>( + ...args: ExprArgs + ): PutItemQueryBuilderInterface { + const eB = new ExpressionBuilder({ + node: { ...this.#props.node.conditionExpression }, + }); + + const expressionNode = eB.orExpression(...args)._getNode(); + + return new PutItemQueryBuilder({ + ...this.#props, + node: { + ...this.#props.node, + conditionExpression: expressionNode, + }, + }); + } + item>( item: Item ): PutItemQueryBuilderInterface { diff --git a/src/queryBuilders/queryQueryBuilder.integration.test.ts b/src/queryBuilders/queryQueryBuilder.integration.test.ts index 212b128..df35423 100644 --- a/src/queryBuilders/queryQueryBuilder.integration.test.ts +++ b/src/queryBuilders/queryQueryBuilder.integration.test.ts @@ -48,6 +48,7 @@ describe("QueryQueryBuilder", () => { .query("myTable") .keyCondition("userId", "=", TEST_DATA[0].userId) .filterExpression("someBoolean", "=", true) + .filterExpression("someBoolean", "=", true) .execute(); expect(data?.length).toBe(2); @@ -74,8 +75,8 @@ describe("QueryQueryBuilder", () => { .filterExpression("somethingElse", "<", 2) .orFilterExpression((qb) => qb - .filterExpression("someBoolean", "=", true) - .filterExpression("somethingElse", "=", 2) + .expression("someBoolean", "=", true) + .expression("somethingElse", "=", 2) ) .execute(); @@ -86,9 +87,7 @@ describe("QueryQueryBuilder", () => { let data = await tsynamoClient .query("myTable") .keyCondition("userId", "=", "123") - .filterExpression("NOT", (qb) => - qb.filterExpression("someBoolean", "=", true) - ) + .filterExpression("NOT", (qb) => qb.expression("someBoolean", "=", true)) .execute(); expect(data).toMatchSnapshot(); @@ -97,9 +96,7 @@ describe("QueryQueryBuilder", () => { .query("myTable") .keyCondition("userId", "=", "123") .filterExpression("someBoolean", "=", true) - .orFilterExpression("NOT", (qb) => - qb.filterExpression("somethingElse", "=", 0) - ) + .orFilterExpression("NOT", (qb) => qb.expression("somethingElse", "=", 0)) .execute(); expect(data).toMatchSnapshot(); diff --git a/src/queryBuilders/queryQueryBuilder.ts b/src/queryBuilders/queryQueryBuilder.ts index 6f6f698..d7fb667 100644 --- a/src/queryBuilders/queryQueryBuilder.ts +++ b/src/queryBuilders/queryQueryBuilder.ts @@ -1,22 +1,13 @@ import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb"; -import { AttributeExistsFunctionExpression } from "../nodes/attributeExistsFunctionExpression"; -import { AttributeNotExistsFunctionExpression } from "../nodes/attributeNotExistsFunctionExpression"; -import { - ExpressionJoinTypeNode, - JoinType, -} from "../nodes/expressionJoinTypeNode"; import { BetweenExpression, - ExpressionConditionComparators, FunctionExpression, KeyConditionComparators, - NotExpression, } from "../nodes/operands"; import { QueryNode } from "../nodes/queryNode"; import { QueryCompiler } from "../queryCompiler"; import { ExecuteOutput, - GetFromPath, ObjectFullPaths, ObjectKeyPaths, PickAllKeys, @@ -26,9 +17,16 @@ import { StripKeys, } from "../typeHelpers"; import { preventAwait } from "../util/preventAwait"; +import { ExprArgs, ExpressionBuilder } from "./expressionBuilder"; export interface QueryQueryBuilderInterface { - execute(): Promise[] | undefined>; + filterExpression>>( + ...args: ExprArgs + ): QueryQueryBuilderInterface; + + orFilterExpression>>( + ...args: ExprArgs + ): QueryQueryBuilderInterface; /** * keyCondition methods @@ -54,128 +52,6 @@ export interface QueryQueryBuilderInterface { val: StripKeys ): QueryQueryBuilderInterface; - /** - * filterExpression methods - * - * @todo Currently NOT FilterExpression returns operations as suggestions as well. - */ - - // Regular operand - filterExpression>>( - key: Exclude, - operation: Key extends NotExpression ? never : ExpressionConditionComparators, - val: StripKeys> - ): QueryQueryBuilderInterface; - - // function expression for functions that only take path as param - filterExpression>>( - key: Exclude, - func: Extract< - FunctionExpression, - "attribute_exists" | "attribute_not_exists" - > - ): QueryQueryBuilderInterface; - - // BEGINS_WITH function expression - filterExpression>>( - key: Key, - expr: Extract, - substr: string - ): QueryQueryBuilderInterface; - - // CONTAINS function expression - filterExpression< - Key extends ObjectKeyPaths>, - Property extends GetFromPath & unknown[] - >( - key: Key, - expr: Extract, - value: StripKeys[number] - ): QueryQueryBuilderInterface; - - // BETWEEN expression - filterExpression>>( - key: Key, - expr: BetweenExpression, - left: StripKeys>, - right: StripKeys> - ): QueryQueryBuilderInterface; - - // NOT expression - filterExpression( - not: NotExpression, - builder: ( - qb: QueryQueryBuilderInterfaceWithOnlyFilterOperations - ) => QueryQueryBuilderInterfaceWithOnlyFilterOperations - ): QueryQueryBuilderInterface; - - // Nested expression - filterExpression( - builder: ( - qb: QueryQueryBuilderInterfaceWithOnlyFilterOperations - ) => QueryQueryBuilderInterfaceWithOnlyFilterOperations - ): QueryQueryBuilderInterface; - - /** - * orFilterExpression methods - */ - - // Regular operand - orFilterExpression>>( - key: Key, - operation: ExpressionConditionComparators, - val: StripKeys> - ): QueryQueryBuilderInterface; - - // function expression for functions that only take path as param - orFilterExpression>>( - key: Exclude, - func: Extract< - FunctionExpression, - "attribute_exists" | "attribute_not_exists" - > - ): QueryQueryBuilderInterface; - - // begins_with function expression - orFilterExpression>>( - key: Key, - expr: Extract, - substr: string - ): QueryQueryBuilderInterface; - - // CONTAINS function expression - orFilterExpression< - Key extends ObjectKeyPaths>, - Property extends GetFromPath & unknown[] - >( - key: Key, - expr: Extract, - value: StripKeys[number] - ): QueryQueryBuilderInterface; - - // BETWEEN expression - orFilterExpression>>( - key: Key, - expr: BetweenExpression, - left: StripKeys>, - right: StripKeys> - ): QueryQueryBuilderInterface; - - // NOT expression - orFilterExpression( - not: NotExpression, - builder: ( - qb: QueryQueryBuilderInterfaceWithOnlyFilterOperations - ) => QueryQueryBuilderInterfaceWithOnlyFilterOperations - ): QueryQueryBuilderInterface; - - // Nested expression - orFilterExpression( - builder: ( - qb: QueryQueryBuilderInterfaceWithOnlyFilterOperations - ) => QueryQueryBuilderInterfaceWithOnlyFilterOperations - ): QueryQueryBuilderInterface; - limit(value: number): QueryQueryBuilderInterface; scanIndexForward(enabled: boolean): QueryQueryBuilderInterface; @@ -186,167 +62,9 @@ export interface QueryQueryBuilderInterface { attributes: A ): QueryQueryBuilderInterface>; - _getNode(): QueryNode; -} - -/** - * When we use a nested builder, this type is used to remove - * all the extra functions of the builder for DX improvement. - */ -export interface QueryQueryBuilderInterfaceWithOnlyFilterOperations< - DDB, - Table extends keyof DDB, - O -> { - /** - * filterExpression methods - */ - filterExpression>>( - key: Key, - operation: ExpressionConditionComparators, - val: StripKeys> - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - filterExpression>>( - key: Exclude, - func: Extract< - FunctionExpression, - "attribute_exists" | "attribute_not_exists" - > - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - filterExpression>>( - key: Key, - func: Extract, - substr: string - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - filterExpression< - Key extends ObjectKeyPaths>, - Property extends GetFromPath & unknown[] - >( - key: Key, - expr: Extract, - value: StripKeys[number] - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - filterExpression>>( - key: Key, - expr: BetweenExpression, - left: StripKeys>, - right: StripKeys> - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - filterExpression( - not: NotExpression, - builder: ( - qb: QueryQueryBuilderInterfaceWithOnlyFilterOperations - ) => QueryQueryBuilderInterfaceWithOnlyFilterOperations - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - filterExpression( - builder: ( - qb: QueryQueryBuilderInterfaceWithOnlyFilterOperations - ) => QueryQueryBuilderInterfaceWithOnlyFilterOperations - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - /** - * orFilterExpression methods - */ - orFilterExpression>>( - key: Key, - operation: ExpressionConditionComparators, - val: StripKeys> - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - orFilterExpression>>( - key: Exclude, - func: Extract< - FunctionExpression, - "attribute_exists" | "attribute_not_exists" - > - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - orFilterExpression>>( - key: Key, - func: Extract, - substr: string - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - orFilterExpression< - Key extends ObjectKeyPaths>, - Property extends GetFromPath & unknown[] - >( - key: Key, - expr: Extract, - value: StripKeys[number] - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - orFilterExpression>>( - key: Key, - expr: BetweenExpression, - left: StripKeys>, - right: StripKeys> - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - orFilterExpression( - not: NotExpression, - builder: ( - qb: QueryQueryBuilderInterfaceWithOnlyFilterOperations - ) => QueryQueryBuilderInterfaceWithOnlyFilterOperations - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - orFilterExpression( - builder: ( - qb: QueryQueryBuilderInterfaceWithOnlyFilterOperations - ) => QueryQueryBuilderInterfaceWithOnlyFilterOperations - ): QueryQueryBuilderInterfaceWithOnlyFilterOperations; - - _getNode(): QueryNode; + execute(): Promise[] | undefined>; } -type FilterExprArgs< - DDB, - Table extends keyof DDB, - O, - Key extends ObjectKeyPaths> -> = - | [ - key: Key, - operation: ExpressionConditionComparators, - value: StripKeys> - ] - | [ - key: Exclude, - func: Extract< - FunctionExpression, - "attribute_exists" | "attribute_not_exists" - > - ] - | [key: Key, func: Extract, substr: string] - | [ - key: Key, - expr: Extract, - value: StripKeys> - ] - | [ - key: Key, - expr: BetweenExpression, - left: StripKeys>, - right: StripKeys> - ] - | [ - not: NotExpression, - builder: ( - qb: QueryQueryBuilderInterfaceWithOnlyFilterOperations - ) => QueryQueryBuilderInterfaceWithOnlyFilterOperations - ] - | [ - builder: ( - qb: QueryQueryBuilderInterfaceWithOnlyFilterOperations - ) => QueryQueryBuilderInterfaceWithOnlyFilterOperations - ]; - /** * @todo support IndexName * @todo support ExclusiveStartKey @@ -363,7 +81,6 @@ export class QueryQueryBuilder< this.#props = props; } - // TODO: Add support for all operations from here: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.KeyConditionExpressions.html keyCondition & string>( ...args: | [ @@ -438,199 +155,40 @@ export class QueryQueryBuilder< } } - _filterExpression>>( - joinType: JoinType, - ...args: FilterExprArgs + filterExpression>>( + ...args: ExprArgs ): QueryQueryBuilderInterface { - if (args[1] === "begins_with") { - const [key, f, substr] = args; - - return new QueryQueryBuilder({ - ...this.#props, - node: { - ...this.#props.node, - filterExpression: { - ...this.#props.node.filterExpression, - expressions: this.#props.node.filterExpression.expressions.concat({ - kind: "ExpressionJoinTypeNode", - expr: { - kind: "BeginsWithFunctionExpression", - key, - substr, - }, - joinType, - }), - }, - }, - }); - } else if (args[1] === "contains") { - const [key, expr, value] = args; - - return new QueryQueryBuilder({ - ...this.#props, - node: { - ...this.#props.node, - filterExpression: { - ...this.#props.node.filterExpression, - expressions: this.#props.node.filterExpression.expressions.concat({ - kind: "ExpressionJoinTypeNode", - expr: { - kind: "ContainsFunctionExpression", - key, - value, - }, - joinType, - }), - }, - }, - }); - } else if ( - args[1] === "attribute_exists" || - args[1] === "attribute_not_exists" - ) { - const [key, func] = args; - let resultExpr: - | AttributeExistsFunctionExpression - | AttributeNotExistsFunctionExpression; - - if (func === "attribute_exists") { - resultExpr = { kind: "AttributeExistsFunctionExpression", key }; - } else { - resultExpr = { kind: "AttributeNotExistsFunctionExpression", key }; - } - - return new QueryQueryBuilder({ - ...this.#props, - node: { - ...this.#props.node, - filterExpression: { - ...this.#props.node.filterExpression, - expressions: this.#props.node.filterExpression.expressions.concat({ - kind: "ExpressionJoinTypeNode", - expr: resultExpr, - joinType, - }), - }, - }, - }); - } else if (args[1] === "BETWEEN") { - const [key, expr, left, right] = args; - - return new QueryQueryBuilder({ - ...this.#props, - node: { - ...this.#props.node, - filterExpression: { - ...this.#props.node.filterExpression, - expressions: this.#props.node.filterExpression.expressions.concat({ - kind: "ExpressionJoinTypeNode", - expr: { - kind: "BetweenConditionExpression", - key, - left, - right, - }, - joinType, - }), - }, - }, - }); - } else if ( - typeof args[0] !== "function" && - args[0] !== "NOT" && - typeof args[1] !== "function" && - args[1] !== undefined && - args[2] !== undefined - ) { - const [key, operation, value] = args; - - return new QueryQueryBuilder({ - ...this.#props, - node: { - ...this.#props.node, - filterExpression: { - ...this.#props.node.filterExpression, - expressions: this.#props.node.filterExpression.expressions.concat({ - kind: "ExpressionJoinTypeNode", - joinType, - expr: { - kind: "ExpressionComparatorExpressions", - key, - operation, - value, - }, - }), - }, - }, - }); - } else if (typeof args[0] === "function" || typeof args[1] === "function") { - let builder; - - if (typeof args[0] === "function") { - builder = args[0]; - } else if (typeof args[1] === "function") { - builder = args[1]; - } - - if (!builder) throw new Error("Could not find builder"); - - const qb = new QueryQueryBuilder({ - ...this.#props, - node: { - ...this.#props.node, - filterExpression: { - expressions: [], - kind: "ExpressionNode", - }, - }, - }); - - const result = builder(qb); - - const { filterExpression } = result._getNode(); - - let resultNode: ExpressionJoinTypeNode = { - kind: "ExpressionJoinTypeNode", - expr: filterExpression, - joinType, - }; - - if (args[0] === "NOT") { - resultNode = { - ...resultNode, - expr: { - kind: "ExpressionNotExpression", - expr: filterExpression, - }, - }; - } - - return new QueryQueryBuilder({ - ...this.#props, - node: { - ...this.#props.node, - filterExpression: { - ...this.#props.node.filterExpression, - expressions: - this.#props.node.filterExpression.expressions.concat(resultNode), - }, - }, - }); - } + const eB = new ExpressionBuilder({ + node: { ...this.#props.node.filterExpression }, + }); - throw new Error("Invalid arguments given to filterExpression"); - } + const expressionNode = eB.expression(...args)._getNode(); - filterExpression>>( - ...args: FilterExprArgs - ): QueryQueryBuilderInterface { - return this._filterExpression("AND", ...args); + return new QueryQueryBuilder({ + ...this.#props, + node: { + ...this.#props.node, + filterExpression: expressionNode, + }, + }); } orFilterExpression>>( - ...args: FilterExprArgs + ...args: ExprArgs ): QueryQueryBuilderInterface { - return this._filterExpression("OR", ...args); + const eB = new ExpressionBuilder({ + node: { ...this.#props.node.filterExpression }, + }); + + const expressionNode = eB.orExpression(...args)._getNode(); + + return new QueryQueryBuilder({ + ...this.#props, + node: { + ...this.#props.node, + filterExpression: expressionNode, + }, + }); } limit(value: number): QueryQueryBuilderInterface { @@ -646,9 +204,9 @@ export class QueryQueryBuilder< }); } - _getNode() { + /* _getNode() { return this.#props.node; - } + } */ scanIndexForward( enabled: boolean diff --git a/src/queryCompiler/queryCompiler.ts b/src/queryCompiler/queryCompiler.ts index a1e49b8..7df9777 100644 --- a/src/queryCompiler/queryCompiler.ts +++ b/src/queryCompiler/queryCompiler.ts @@ -106,12 +106,37 @@ export class QueryCompiler { table: tableNode, item: itemNode, returnValues: returnValuesNode, + conditionExpression: conditionExpressionNode, } = putNode; + const attributeNames = new Map(); + const filterExpressionAttributeValues = new Map(); + + const compiledConditionExpression = this.compileExpression( + conditionExpressionNode, + filterExpressionAttributeValues, + attributeNames + ); + return new PutCommand({ TableName: tableNode.table, Item: itemNode?.item, ReturnValues: returnValuesNode?.option, + ConditionExpression: compiledConditionExpression + ? compiledConditionExpression + : undefined, + ExpressionAttributeValues: + filterExpressionAttributeValues.size > 0 + ? { + ...Object.fromEntries(filterExpressionAttributeValues), + } + : undefined, + ExpressionAttributeNames: + attributeNames.size > 0 + ? { + ...Object.fromEntries(attributeNames), + } + : undefined, }); } diff --git a/src/queryCreator.ts b/src/queryCreator.ts index 18d3eca..afc2496 100644 --- a/src/queryCreator.ts +++ b/src/queryCreator.ts @@ -77,6 +77,10 @@ export class QueryCreator { kind: "TableNode", table, }, + conditionExpression: { + kind: "ExpressionNode", + expressions: [], + }, }, ddbClient: this.#props.ddbClient, queryCompiler: this.#props.queryCompiler, diff --git a/src/typeHelpers.ts b/src/typeHelpers.ts index c07e15e..60cb45b 100644 --- a/src/typeHelpers.ts +++ b/src/typeHelpers.ts @@ -1,3 +1,4 @@ +import { DDB } from "../test/testFixture"; import type { PartitionKey, SortKey } from "./ddbTypes"; /** @@ -56,6 +57,14 @@ export type StripKeys = T extends { _PK: true } ? Omit : T; +export type DeepStripKeys = { + [P in keyof T]: T[P] extends Array + ? Array> + : T[P] extends object + ? DeepStripKeys + : StripKeys; +}; + /** * Returns a subset of a table's properties. */ From b621b639152d7cfd0b6f640f4af7d18f268ae906 Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Mon, 18 Mar 2024 23:02:46 +0200 Subject: [PATCH 04/12] fix nested expressions with keys --- src/queryBuilders/expressionBuilder.ts | 248 ++++++++++++++++++++----- src/queryBuilders/queryQueryBuilder.ts | 8 +- 2 files changed, 203 insertions(+), 53 deletions(-) diff --git a/src/queryBuilders/expressionBuilder.ts b/src/queryBuilders/expressionBuilder.ts index c9ba5b7..9cb0a3e 100644 --- a/src/queryBuilders/expressionBuilder.ts +++ b/src/queryBuilders/expressionBuilder.ts @@ -20,7 +20,7 @@ import { export interface ExpressionBuilderInterface { // Regular operand - expression>>( + expression>( key: Exclude, operation: Key extends NotExpression ? never @@ -29,7 +29,7 @@ export interface ExpressionBuilderInterface { ): ExpressionBuilderInterface; // function expression for functions that only take path as param - expression>>( + expression>( key: Exclude, func: Extract< FunctionExpression, @@ -39,7 +39,7 @@ export interface ExpressionBuilderInterface { // CONTAINS function expression expression< - Key extends ObjectKeyPaths>, + Key extends ObjectKeyPaths, Property extends GetFromPath & unknown[] >( key: Key, @@ -48,14 +48,14 @@ export interface ExpressionBuilderInterface { ): ExpressionBuilderInterface; // BEGINS_WITH function expression - expression>>( + expression>( key: Key, expr: Extract, substr: string ): ExpressionBuilderInterface; // BETWEEN expression - expression>>( + expression>( key: Key, expr: BetweenExpression, left: StripKeys>, @@ -78,13 +78,13 @@ export interface ExpressionBuilderInterface { ): ExpressionBuilderInterface; // Or expressions - orExpression>>( + orExpression>( key: Key, operation: ExpressionConditionComparators, val: StripKeys> ): ExpressionBuilderInterface; - orExpression>>( + orExpression>( key: Exclude, func: Extract< FunctionExpression, @@ -92,14 +92,14 @@ export interface ExpressionBuilderInterface { > ): ExpressionBuilderInterface; - orExpression>>( + orExpression>( key: Key, func: Extract, substr: string ): ExpressionBuilderInterface; orExpression< - Key extends ObjectKeyPaths>, + Key extends ObjectKeyPaths, Property extends GetFromPath & unknown[] >( key: Key, @@ -107,7 +107,7 @@ export interface ExpressionBuilderInterface { value: StripKeys[number] ): ExpressionBuilderInterface; - orExpression>>( + orExpression>( key: Key, expr: BetweenExpression, left: StripKeys>, @@ -133,111 +133,258 @@ export interface ExpressionBuilderInterface { export interface ExpressionBuilderInterfaceWithOnlyExpressionOperations< DDB, Table extends keyof DDB, - O + O, + AllowKeys = true > { /** * expression methods */ - expression>>( + expression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys + > + >( key: Key, operation: ExpressionConditionComparators, val: StripKeys> - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; - expression>>( + expression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys + > + >( key: Exclude, func: Extract< FunctionExpression, "attribute_exists" | "attribute_not_exists" > - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; - expression>>( + expression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys + > + >( key: Key, func: Extract, substr: string - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; expression< - Key extends ObjectKeyPaths>, + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys + >, Property extends GetFromPath & unknown[] >( key: Key, expr: Extract, value: StripKeys[number] - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; - expression>>( + expression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys + > + >( key: Key, expr: BetweenExpression, left: StripKeys>, right: StripKeys> - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; expression( not: NotExpression, builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + > + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + > + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; expression( builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + > + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + > + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; /** * orExpression methods */ - orExpression>>( + orExpression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys + > + >( key: Key, operation: ExpressionConditionComparators, val: StripKeys> - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; - orExpression>>( + orExpression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys + > + >( key: Exclude, func: Extract< FunctionExpression, "attribute_exists" | "attribute_not_exists" > - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; - orExpression>>( + orExpression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys + > + >( key: Key, func: Extract, substr: string - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; orExpression< - Key extends ObjectKeyPaths>, + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys + >, Property extends GetFromPath & unknown[] >( key: Key, expr: Extract, value: StripKeys[number] - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; - orExpression>>( + orExpression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys + > + >( key: Key, expr: BetweenExpression, left: StripKeys>, right: StripKeys> - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; orExpression( not: NotExpression, builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + > + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + > + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; orExpression( builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations; + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + > + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + > + ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeys + >; _getNode(): ExpressionNode; } @@ -246,7 +393,8 @@ export type ExprArgs< DDB, Table extends keyof DDB, O, - Key extends ObjectKeyPaths> + Key, + AllowKeysInExpression = true > = | [ key: Key, @@ -280,7 +428,8 @@ export type ExprArgs< qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< DDB, Table, - O + O, + AllowKeysInExpression > ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations ] @@ -289,7 +438,8 @@ export type ExprArgs< qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< DDB, Table, - O + O, + AllowKeysInExpression > ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations ]; @@ -306,7 +456,7 @@ export class ExpressionBuilder< this.#props = props; } - _expression>>( + _expression>( joinType: JoinType, ...args: ExprArgs ): ExpressionBuilderInterface { @@ -466,13 +616,13 @@ export class ExpressionBuilder< throw new Error("Invalid arguments given to expression builder"); } - expression>>( + expression>( ...args: ExprArgs ): ExpressionBuilderInterface { return this._expression("AND", ...args); } - orExpression>>( + orExpression>( ...args: ExprArgs ): ExpressionBuilderInterface { return this._expression("OR", ...args); diff --git a/src/queryBuilders/queryQueryBuilder.ts b/src/queryBuilders/queryQueryBuilder.ts index d7fb667..6375ff0 100644 --- a/src/queryBuilders/queryQueryBuilder.ts +++ b/src/queryBuilders/queryQueryBuilder.ts @@ -21,11 +21,11 @@ import { ExprArgs, ExpressionBuilder } from "./expressionBuilder"; export interface QueryQueryBuilderInterface { filterExpression>>( - ...args: ExprArgs + ...args: ExprArgs ): QueryQueryBuilderInterface; orFilterExpression>>( - ...args: ExprArgs + ...args: ExprArgs ): QueryQueryBuilderInterface; /** @@ -156,7 +156,7 @@ export class QueryQueryBuilder< } filterExpression>>( - ...args: ExprArgs + ...args: ExprArgs ): QueryQueryBuilderInterface { const eB = new ExpressionBuilder({ node: { ...this.#props.node.filterExpression }, @@ -174,7 +174,7 @@ export class QueryQueryBuilder< } orFilterExpression>>( - ...args: ExprArgs + ...args: ExprArgs ): QueryQueryBuilderInterface { const eB = new ExpressionBuilder({ node: { ...this.#props.node.filterExpression }, From 36b1eb32f0a1fad840470d3e04db8f7336aa0901 Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Mon, 18 Mar 2024 23:10:42 +0200 Subject: [PATCH 05/12] clean up --- src/queryBuilders/queryQueryBuilder.ts | 4 ---- src/typeHelpers.ts | 8 -------- 2 files changed, 12 deletions(-) diff --git a/src/queryBuilders/queryQueryBuilder.ts b/src/queryBuilders/queryQueryBuilder.ts index 6375ff0..de390ee 100644 --- a/src/queryBuilders/queryQueryBuilder.ts +++ b/src/queryBuilders/queryQueryBuilder.ts @@ -204,10 +204,6 @@ export class QueryQueryBuilder< }); } - /* _getNode() { - return this.#props.node; - } */ - scanIndexForward( enabled: boolean ): QueryQueryBuilderInterface { diff --git a/src/typeHelpers.ts b/src/typeHelpers.ts index 60cb45b..65f86e1 100644 --- a/src/typeHelpers.ts +++ b/src/typeHelpers.ts @@ -57,14 +57,6 @@ export type StripKeys = T extends { _PK: true } ? Omit : T; -export type DeepStripKeys = { - [P in keyof T]: T[P] extends Array - ? Array> - : T[P] extends object - ? DeepStripKeys - : StripKeys; -}; - /** * Returns a subset of a table's properties. */ From 1b1b0ec62357af1bb142fd01bab5be97a0977e21 Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Tue, 19 Mar 2024 01:45:15 +0200 Subject: [PATCH 06/12] fix typings --- src/queryBuilders/expressionBuilder.ts | 120 ++++++++++++++--------- src/queryBuilders/putItemQueryBuilder.ts | 66 ++++++++++++- src/queryBuilders/queryQueryBuilder.ts | 60 +++++++++++- 3 files changed, 190 insertions(+), 56 deletions(-) diff --git a/src/queryBuilders/expressionBuilder.ts b/src/queryBuilders/expressionBuilder.ts index 9cb0a3e..cb49fa3 100644 --- a/src/queryBuilders/expressionBuilder.ts +++ b/src/queryBuilders/expressionBuilder.ts @@ -389,6 +389,71 @@ export interface ExpressionBuilderInterfaceWithOnlyExpressionOperations< _getNode(): ExpressionNode; } +export type ComparatorExprArg = [ + key: Key, + operation: ExpressionConditionComparators, + value: StripKeys> +]; + +export type AttributeFuncExprArg = [ + key: Key, + func: Extract +]; + +export type AttributeBeginsWithExprArg = [ + key: Key, + func: Extract, + substr: string +]; + +export type AttributeContainsExprArg = [ + key: Key, + expr: Extract, + value: GetFromPath extends unknown[] + ? StripKeys>[number] + : never +]; + +export type AttributeBetweenExprArg = [ + key: Key, + expr: BetweenExpression, + left: StripKeys>, + right: StripKeys> +]; + +export type NotExprArg< + DDB, + Table extends keyof DDB, + O, + AllowKeysInExpression = true +> = [ + not: NotExpression, + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeysInExpression + > + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations +]; + +export type BuilderExprArg< + DDB, + Table extends keyof DDB, + O, + AllowKeysInExpression = true +> = [ + builder: ( + qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< + DDB, + Table, + O, + AllowKeysInExpression + > + ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations +]; + export type ExprArgs< DDB, Table extends keyof DDB, @@ -396,54 +461,13 @@ export type ExprArgs< Key, AllowKeysInExpression = true > = - | [ - key: Key, - operation: ExpressionConditionComparators, - value: StripKeys> - ] - | [ - key: Exclude, - func: Extract< - FunctionExpression, - "attribute_exists" | "attribute_not_exists" - > - ] - | [key: Key, func: Extract, substr: string] - | [ - key: Key, - expr: Extract, - value: GetFromPath extends unknown[] - ? StripKeys>[number] - : never - ] - | [ - key: Key, - expr: BetweenExpression, - left: StripKeys>, - right: StripKeys> - ] - | [ - not: NotExpression, - builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeysInExpression - > - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations - ] - | [ - builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeysInExpression - > - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations - ]; - + | ComparatorExprArg + | AttributeFuncExprArg + | AttributeBeginsWithExprArg + | AttributeContainsExprArg + | AttributeBetweenExprArg + | BuilderExprArg + | NotExprArg; export class ExpressionBuilder< DDB, Table extends keyof DDB, diff --git a/src/queryBuilders/putItemQueryBuilder.ts b/src/queryBuilders/putItemQueryBuilder.ts index 0920bb8..ce4cbd0 100644 --- a/src/queryBuilders/putItemQueryBuilder.ts +++ b/src/queryBuilders/putItemQueryBuilder.ts @@ -4,15 +4,75 @@ import { QueryCompiler } from "../queryCompiler"; import { ExecuteOutput, ObjectKeyPaths, PickNonKeys } from "../typeHelpers"; import { preventAwait } from "../util/preventAwait"; import { ReturnValuesOptions } from "../nodes/returnValuesNode"; -import { ExprArgs, ExpressionBuilder } from "./expressionBuilder"; +import { + AttributeBeginsWithExprArg, + AttributeBetweenExprArg, + AttributeContainsExprArg, + AttributeFuncExprArg, + BuilderExprArg, + ComparatorExprArg, + ExprArgs, + ExpressionBuilder, + NotExprArg, +} from "./expressionBuilder"; export interface PutItemQueryBuilderInterface { + // conditionExpression conditionExpression>( - ...args: ExprArgs + ...args: ComparatorExprArg + ): PutItemQueryBuilderInterface; + + conditionExpression>( + ...args: AttributeFuncExprArg + ): PutItemQueryBuilderInterface; + + conditionExpression>( + ...args: AttributeBeginsWithExprArg + ): PutItemQueryBuilderInterface; + + conditionExpression>( + ...args: AttributeContainsExprArg + ): PutItemQueryBuilderInterface; + + conditionExpression>( + ...args: AttributeBetweenExprArg + ): PutItemQueryBuilderInterface; + + conditionExpression>( + ...args: NotExprArg + ): PutItemQueryBuilderInterface; + + conditionExpression>( + ...args: BuilderExprArg ): PutItemQueryBuilderInterface; + // orConditionExpression orConditionExpression>( - ...args: ExprArgs + ...args: ComparatorExprArg + ): PutItemQueryBuilderInterface; + + orConditionExpression>( + ...args: AttributeFuncExprArg + ): PutItemQueryBuilderInterface; + + orConditionExpression>( + ...args: AttributeBeginsWithExprArg + ): PutItemQueryBuilderInterface; + + orConditionExpression>( + ...args: AttributeContainsExprArg + ): PutItemQueryBuilderInterface; + + orConditionExpression>( + ...args: AttributeBetweenExprArg + ): PutItemQueryBuilderInterface; + + orConditionExpression>( + ...args: NotExprArg + ): PutItemQueryBuilderInterface; + + orConditionExpression>( + ...args: BuilderExprArg ): PutItemQueryBuilderInterface; returnValues( diff --git a/src/queryBuilders/queryQueryBuilder.ts b/src/queryBuilders/queryQueryBuilder.ts index de390ee..706a846 100644 --- a/src/queryBuilders/queryQueryBuilder.ts +++ b/src/queryBuilders/queryQueryBuilder.ts @@ -17,15 +17,65 @@ import { StripKeys, } from "../typeHelpers"; import { preventAwait } from "../util/preventAwait"; -import { ExprArgs, ExpressionBuilder } from "./expressionBuilder"; +import { AttributeBeginsWithExprArg, AttributeBetweenExprArg, AttributeContainsExprArg, AttributeFuncExprArg, BuilderExprArg, ComparatorExprArg, ExprArgs, ExpressionBuilder, NotExprArg } from "./expressionBuilder"; export interface QueryQueryBuilderInterface { - filterExpression>>( - ...args: ExprArgs + // filterExpression + filterExpression>( + ...args: ComparatorExprArg ): QueryQueryBuilderInterface; - orFilterExpression>>( - ...args: ExprArgs + filterExpression>( + ...args: AttributeFuncExprArg + ): QueryQueryBuilderInterface; + + filterExpression>( + ...args: AttributeBeginsWithExprArg + ): QueryQueryBuilderInterface; + + filterExpression>( + ...args: AttributeContainsExprArg + ): QueryQueryBuilderInterface; + + filterExpression>( + ...args: AttributeBetweenExprArg + ): QueryQueryBuilderInterface; + + filterExpression>( + ...args: NotExprArg + ): QueryQueryBuilderInterface; + + filterExpression>( + ...args: BuilderExprArg + ): QueryQueryBuilderInterface; + + // orFilterExpression + orFilterExpression>( + ...args: ComparatorExprArg + ): QueryQueryBuilderInterface; + + orFilterExpression>( + ...args: AttributeFuncExprArg + ): QueryQueryBuilderInterface; + + orFilterExpression>( + ...args: AttributeBeginsWithExprArg + ): QueryQueryBuilderInterface; + + orFilterExpression>( + ...args: AttributeContainsExprArg + ): QueryQueryBuilderInterface; + + orFilterExpression>( + ...args: AttributeBetweenExprArg + ): QueryQueryBuilderInterface; + + orFilterExpression>( + ...args: NotExprArg + ): QueryQueryBuilderInterface; + + orFilterExpression>( + ...args: BuilderExprArg ): QueryQueryBuilderInterface; /** From 5635176c726de5ab819ab56441a7a95894c86f32 Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Tue, 19 Mar 2024 02:12:04 +0200 Subject: [PATCH 07/12] refactor expressionBuilder --- src/queryBuilders/expressionBuilder.ts | 376 ++++--------------------- src/queryBuilders/queryQueryBuilder.ts | 44 +-- 2 files changed, 86 insertions(+), 334 deletions(-) diff --git a/src/queryBuilders/expressionBuilder.ts b/src/queryBuilders/expressionBuilder.ts index cb49fa3..2d7d963 100644 --- a/src/queryBuilders/expressionBuilder.ts +++ b/src/queryBuilders/expressionBuilder.ts @@ -18,373 +18,125 @@ import { StripKeys, } from "../typeHelpers"; -export interface ExpressionBuilderInterface { - // Regular operand - expression>( - key: Exclude, - operation: Key extends NotExpression - ? never - : ExpressionConditionComparators, - val: StripKeys> - ): ExpressionBuilderInterface; - - // function expression for functions that only take path as param - expression>( - key: Exclude, - func: Extract< - FunctionExpression, - "attribute_exists" | "attribute_not_exists" - > - ): ExpressionBuilderInterface; - - // CONTAINS function expression - expression< - Key extends ObjectKeyPaths, - Property extends GetFromPath & unknown[] - >( - key: Key, - expr: Extract, - value: StripKeys[number] - ): ExpressionBuilderInterface; - - // BEGINS_WITH function expression - expression>( - key: Key, - expr: Extract, - substr: string - ): ExpressionBuilderInterface; - - // BETWEEN expression - expression>( - key: Key, - expr: BetweenExpression, - left: StripKeys>, - right: StripKeys> - ): ExpressionBuilderInterface; - - // NOT expression - expression( - not: NotExpression, - builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations - ): ExpressionBuilderInterface; - - // Nested expression - expression( - builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations - ): ExpressionBuilderInterface; - - // Or expressions - orExpression>( - key: Key, - operation: ExpressionConditionComparators, - val: StripKeys> - ): ExpressionBuilderInterface; - - orExpression>( - key: Exclude, - func: Extract< - FunctionExpression, - "attribute_exists" | "attribute_not_exists" - > - ): ExpressionBuilderInterface; - - orExpression>( - key: Key, - func: Extract, - substr: string - ): ExpressionBuilderInterface; - - orExpression< - Key extends ObjectKeyPaths, - Property extends GetFromPath & unknown[] - >( - key: Key, - expr: Extract, - value: StripKeys[number] - ): ExpressionBuilderInterface; - - orExpression>( - key: Key, - expr: BetweenExpression, - left: StripKeys>, - right: StripKeys> - ): ExpressionBuilderInterface; - - orExpression( - not: NotExpression, - builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations - ): ExpressionBuilderInterface; - - orExpression( - builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations - ): ExpressionBuilderInterface; - - _getNode(): ExpressionNode; -} - -export interface ExpressionBuilderInterfaceWithOnlyExpressionOperations< +export interface ExpressionBuilderInterface< DDB, Table extends keyof DDB, O, - AllowKeys = true + AllowKeys = false > { - /** - * expression methods - */ + // expression expression< Key extends ObjectKeyPaths< AllowKeys extends true ? DDB[Table] : PickNonKeys > >( - key: Key, - operation: ExpressionConditionComparators, - val: StripKeys> - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; + ...args: ComparatorExprArg + ): ExpressionBuilderInterface; expression< Key extends ObjectKeyPaths< AllowKeys extends true ? DDB[Table] : PickNonKeys > >( - key: Exclude, - func: Extract< - FunctionExpression, - "attribute_exists" | "attribute_not_exists" - > - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; + ...args: AttributeFuncExprArg + ): ExpressionBuilderInterface; expression< Key extends ObjectKeyPaths< AllowKeys extends true ? DDB[Table] : PickNonKeys > >( - key: Key, - func: Extract, - substr: string - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; + ...args: AttributeBeginsWithExprArg + ): ExpressionBuilderInterface; expression< Key extends ObjectKeyPaths< AllowKeys extends true ? DDB[Table] : PickNonKeys - >, - Property extends GetFromPath & unknown[] + > >( - key: Key, - expr: Extract, - value: StripKeys[number] - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; + ...args: AttributeContainsExprArg + ): ExpressionBuilderInterface; expression< Key extends ObjectKeyPaths< AllowKeys extends true ? DDB[Table] : PickNonKeys > >( - key: Key, - expr: BetweenExpression, - left: StripKeys>, - right: StripKeys> - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; - - expression( - not: NotExpression, - builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - > - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys + ...args: AttributeBetweenExprArg + ): ExpressionBuilderInterface; + + expression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys > - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; - - expression( - builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - > - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys + >( + ...args: NotExprArg + ): ExpressionBuilderInterface; + + expression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys > - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; - - /** - * orExpression methods - */ + >( + ...args: BuilderExprArg + ): ExpressionBuilderInterface; + + // orExpression orExpression< Key extends ObjectKeyPaths< AllowKeys extends true ? DDB[Table] : PickNonKeys > >( - key: Key, - operation: ExpressionConditionComparators, - val: StripKeys> - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; + ...args: ComparatorExprArg + ): ExpressionBuilderInterface; orExpression< Key extends ObjectKeyPaths< AllowKeys extends true ? DDB[Table] : PickNonKeys > >( - key: Exclude, - func: Extract< - FunctionExpression, - "attribute_exists" | "attribute_not_exists" - > - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; + ...args: AttributeFuncExprArg + ): ExpressionBuilderInterface; orExpression< Key extends ObjectKeyPaths< AllowKeys extends true ? DDB[Table] : PickNonKeys > >( - key: Key, - func: Extract, - substr: string - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; + ...args: AttributeBeginsWithExprArg + ): ExpressionBuilderInterface; orExpression< Key extends ObjectKeyPaths< AllowKeys extends true ? DDB[Table] : PickNonKeys - >, - Property extends GetFromPath & unknown[] + > >( - key: Key, - expr: Extract, - value: StripKeys[number] - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; + ...args: AttributeContainsExprArg + ): ExpressionBuilderInterface; orExpression< Key extends ObjectKeyPaths< AllowKeys extends true ? DDB[Table] : PickNonKeys > >( - key: Key, - expr: BetweenExpression, - left: StripKeys>, - right: StripKeys> - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; - - orExpression( - not: NotExpression, - builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - > - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys + ...args: AttributeBetweenExprArg + ): ExpressionBuilderInterface; + + orExpression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys > - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; - - orExpression( - builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - > - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys + >( + ...args: NotExprArg + ): ExpressionBuilderInterface; + + orExpression< + Key extends ObjectKeyPaths< + AllowKeys extends true ? DDB[Table] : PickNonKeys > - ): ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeys - >; + >( + ...args: BuilderExprArg + ): ExpressionBuilderInterface; _getNode(): ExpressionNode; } @@ -429,13 +181,8 @@ export type NotExprArg< > = [ not: NotExpression, builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeysInExpression - > - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + qb: ExpressionBuilderInterface + ) => ExpressionBuilderInterface ]; export type BuilderExprArg< @@ -445,13 +192,8 @@ export type BuilderExprArg< AllowKeysInExpression = true > = [ builder: ( - qb: ExpressionBuilderInterfaceWithOnlyExpressionOperations< - DDB, - Table, - O, - AllowKeysInExpression - > - ) => ExpressionBuilderInterfaceWithOnlyExpressionOperations + qb: ExpressionBuilderInterface + ) => ExpressionBuilderInterface ]; export type ExprArgs< diff --git a/src/queryBuilders/queryQueryBuilder.ts b/src/queryBuilders/queryQueryBuilder.ts index 706a846..dc29c5d 100644 --- a/src/queryBuilders/queryQueryBuilder.ts +++ b/src/queryBuilders/queryQueryBuilder.ts @@ -17,64 +17,74 @@ import { StripKeys, } from "../typeHelpers"; import { preventAwait } from "../util/preventAwait"; -import { AttributeBeginsWithExprArg, AttributeBetweenExprArg, AttributeContainsExprArg, AttributeFuncExprArg, BuilderExprArg, ComparatorExprArg, ExprArgs, ExpressionBuilder, NotExprArg } from "./expressionBuilder"; +import { + AttributeBeginsWithExprArg, + AttributeBetweenExprArg, + AttributeContainsExprArg, + AttributeFuncExprArg, + BuilderExprArg, + ComparatorExprArg, + ExprArgs, + ExpressionBuilder, + NotExprArg, +} from "./expressionBuilder"; export interface QueryQueryBuilderInterface { // filterExpression - filterExpression>( + filterExpression>>( ...args: ComparatorExprArg ): QueryQueryBuilderInterface; - filterExpression>( + filterExpression>>( ...args: AttributeFuncExprArg ): QueryQueryBuilderInterface; - filterExpression>( + filterExpression>>( ...args: AttributeBeginsWithExprArg ): QueryQueryBuilderInterface; - filterExpression>( + filterExpression>>( ...args: AttributeContainsExprArg ): QueryQueryBuilderInterface; - filterExpression>( + filterExpression>>( ...args: AttributeBetweenExprArg ): QueryQueryBuilderInterface; - filterExpression>( - ...args: NotExprArg + filterExpression>>( + ...args: NotExprArg ): QueryQueryBuilderInterface; - filterExpression>( - ...args: BuilderExprArg + filterExpression>>( + ...args: BuilderExprArg ): QueryQueryBuilderInterface; // orFilterExpression - orFilterExpression>( + orFilterExpression>>( ...args: ComparatorExprArg ): QueryQueryBuilderInterface; - orFilterExpression>( + orFilterExpression>>( ...args: AttributeFuncExprArg ): QueryQueryBuilderInterface; - orFilterExpression>( + orFilterExpression>>( ...args: AttributeBeginsWithExprArg ): QueryQueryBuilderInterface; - orFilterExpression>( + orFilterExpression>>( ...args: AttributeContainsExprArg ): QueryQueryBuilderInterface; - orFilterExpression>( + orFilterExpression>>( ...args: AttributeBetweenExprArg ): QueryQueryBuilderInterface; - orFilterExpression>( + orFilterExpression>>( ...args: NotExprArg ): QueryQueryBuilderInterface; - orFilterExpression>( + orFilterExpression>>( ...args: BuilderExprArg ): QueryQueryBuilderInterface; From b57b68a3e1418551595ff7814a3d4fdf226d2d18 Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Tue, 19 Mar 2024 02:20:23 +0200 Subject: [PATCH 08/12] more tests --- ...tItemQueryBuilder.integration.test.ts.snap | 11 +++ .../putItemQueryBuilder.integration.test.ts | 70 ++++++++++++++++++- 2 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/queryBuilders/__snapshots__/putItemQueryBuilder.integration.test.ts.snap diff --git a/src/queryBuilders/__snapshots__/putItemQueryBuilder.integration.test.ts.snap b/src/queryBuilders/__snapshots__/putItemQueryBuilder.integration.test.ts.snap new file mode 100644 index 0000000..2925154 --- /dev/null +++ b/src/queryBuilders/__snapshots__/putItemQueryBuilder.integration.test.ts.snap @@ -0,0 +1,11 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`PutItemQueryBuilder > handles 'contains' ConditionExpression 1`] = ` +{ + "dataTimestamp": 212, + "tags": [ + "cats", + ], + "userId": "333", +} +`; diff --git a/src/queryBuilders/putItemQueryBuilder.integration.test.ts b/src/queryBuilders/putItemQueryBuilder.integration.test.ts index 6fc5b4a..43a4d84 100644 --- a/src/queryBuilders/putItemQueryBuilder.integration.test.ts +++ b/src/queryBuilders/putItemQueryBuilder.integration.test.ts @@ -1,8 +1,8 @@ -import { DDB, TEST_DATA } from "../../test/testFixture"; +import { DDB } from "../../test/testFixture"; import { getDDBClientFor, startDDBTestContainer } from "../../test/testUtil"; import { Tsynamo } from "./../index"; -describe("GetItemQueryBuilder", () => { +describe("PutItemQueryBuilder", () => { let tsynamoClient: Tsynamo; const itemToPut = { @@ -78,5 +78,69 @@ describe("GetItemQueryBuilder", () => { ); }); - it.todo("handles 'contains' ConditionExpression"); + it("handles 'contains' ConditionExpression", async () => { + await tsynamoClient + .putItem("myTable") + .item({ + userId: "333", + dataTimestamp: 212, + tags: ["cats"], + }) + .execute(); + + const oldValues = await tsynamoClient + .putItem("myTable") + .item({ + userId: "333", + dataTimestamp: 212, + tags: ["cats"], + }) + .conditionExpression("tags", "contains", "cats") + .returnValues("ALL_OLD") + .execute(); + + expect(oldValues).toBeTruthy(); + expect(oldValues).toMatchSnapshot(); + + expect( + tsynamoClient + .putItem("myTable") + .item({ + userId: "333", + dataTimestamp: 212, + }) + .conditionExpression("NOT", (qb) => + qb.expression("tags", "contains", "cats") + ) + .execute() + ).rejects.toMatchInlineSnapshot( + `[ConditionalCheckFailedException: The conditional request failed]` + ); + }); + + it("Handles nested ConditionExpressions", async () => { + await tsynamoClient + .putItem("myTable") + .item({ + userId: "333", + dataTimestamp: 212, + nested: { + nestedBoolean: false, + }, + }) + .execute(); + + expect( + tsynamoClient + .putItem("myTable") + .item({ + userId: "333", + dataTimestamp: 212, + }) + .conditionExpression("nested.nestedBoolean", "=", true) + .execute() + ).rejects.toMatchInlineSnapshot( + `[ConditionalCheckFailedException: The conditional request failed]` + ); + }); }); From 56930bcc379183b01b1b38129c1dff498af48323 Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Tue, 19 Mar 2024 02:24:09 +0200 Subject: [PATCH 09/12] update readme --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f77dfea..81cfc28 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,6 @@ Type-friendly DynamoDB query builder! Inspired by [Kysely](https://github.com/ky Usable with AWS SDK v3 `DynamoDBDocumentClient`. -> [!NOTE] -> Currently this is a POC and a WIP. Currently, `get-item` and `query` operations are -> supported, but I am planning to add support for the rest of the operations too. - ![](https://github.com/woltsu/tsynamo/blob/main/assets/demo.gif) # Installation @@ -163,11 +159,47 @@ await tsynamoClient > This would compile as the following FilterExpression: > `NOT eventType = "LOG_IN"`, i.e. return all events whose types is not "LOG_IN" -## Delete item +## Put item -WIP +### Simple put item -## Put item +```ts +await tsynamoClient + .putItem("myTable") + .item({ + userId: "123", + eventId: 313, + }) + .execute(); +``` + +### Put item with ConditionExpression + +```ts +await tsynamoClient + .putItem("myTable") + .item({ + userId: "123", + eventId: 313, + }) + .conditionExpression("userId", "attribute_not_exists") + .execute(); +``` + +### Put item with multiple ConditionExpressions + +```ts +await tsynamoClient + .putItem("myTable") + .item({ + userId: "123", + eventId: 313, + }) + .conditionExpression("eventType", "begins_with", "LOG_") + .execute(); +``` + +## Delete item WIP @@ -180,8 +212,9 @@ WIP WIP # Contributors +

-

\ No newline at end of file +

From b0b5ae5aa72944b75e37280970b693142f3d3153 Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Tue, 19 Mar 2024 02:24:19 +0200 Subject: [PATCH 10/12] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9bb4a85..74d1490 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tsynamo", "author": "woltsu", - "version": "0.0.5", + "version": "0.0.6", "description": "Typed query builder for DynamoDB", "main": "dist/index.js", "types": "dist/index.d.ts", From 53ac983259398ee8af3a4e9b190ec6b3ea3ec3d9 Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Tue, 19 Mar 2024 02:25:27 +0200 Subject: [PATCH 11/12] update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 81cfc28..f631ecd 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,8 @@ await tsynamoClient userId: "123", eventId: 313, }) - .conditionExpression("eventType", "begins_with", "LOG_") + .conditionExpression("userId", "attribute_not_exists") + .orConditionExpression("eventType", "begins_with", "LOG_") .execute(); ``` From 74cf6fccf2e2443cd0900b5440eb108cd06e1e3a Mon Sep 17 00:00:00 2001 From: Olli Warro Date: Tue, 19 Mar 2024 02:30:47 +0200 Subject: [PATCH 12/12] update comment --- src/queryCreator.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/queryCreator.ts b/src/queryCreator.ts index afc2496..d2a532c 100644 --- a/src/queryCreator.ts +++ b/src/queryCreator.ts @@ -67,6 +67,12 @@ export class QueryCreator { }); } + /** + * + * @param table Table to perform the put item command to + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/PutItemCommand/ + */ putItem
( table: Table ): PutItemQueryBuilderInterface {