Skip to content

Commit

Permalink
Merge pull request #14 from mindler-olli/main
Browse files Browse the repository at this point in the history
add support for delete item command
  • Loading branch information
woltsu authored Mar 19, 2024
2 parents d50633b + 10b4662 commit b92aba5
Show file tree
Hide file tree
Showing 12 changed files with 448 additions and 20 deletions.
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const tsynamoClient = new Tsynamo<DDB>({

```ts
await tsynamoClient
.getItemFrom("UserEvents")
.getItem("UserEvents")
.keys({
userId: "123",
eventId: 222,
Expand Down Expand Up @@ -202,7 +202,30 @@ await tsynamoClient

## Delete item

WIP
### Simple delete item

```ts
await tsynamoClient
.deleteItem("myTable")
.keys({
userId: "123",
eventId: 313,
})
.execute();
```

### Simple delete item with ConditionExpression

```ts
await tsynamoClient
.deleteItem("myTable")
.keys({
userId: "123",
eventId: 313,
})
.conditionExpression("eventType", "attribute_not_exists")
.execute();
```

## Update item

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "tsynamo",
"author": "woltsu",
"version": "0.0.6",
"version": "0.0.7",
"description": "Typed query builder for DynamoDB",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
13 changes: 13 additions & 0 deletions src/nodes/deleteNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ExpressionNode } from "./expressionNode";
import { KeysNode } from "./keysNode";
import { ReturnOldValuesNode, ReturnValuesNode } from "./returnValuesNode";
import { TableNode } from "./tableNode";

export type DeleteNode = {
readonly kind: "DeleteNode";
readonly table: TableNode;
readonly conditionExpression: ExpressionNode;
readonly returnValues?: ReturnValuesNode;
readonly returnValuesOnConditionCheckFailure?: ReturnOldValuesNode;
readonly keys?: KeysNode;
};
5 changes: 5 additions & 0 deletions src/nodes/returnValuesNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ export type ReturnValuesNode = {
readonly kind: "ReturnValuesNode";
readonly option: ReturnValuesOptions;
};

export type ReturnOldValuesNode = {
readonly kind: "ReturnValuesNode";
readonly option: Extract<ReturnValuesOptions, "NONE" | "ALL_OLD">;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`DeleteItemQueryBuilder > handles a simple delete query 1`] = `
{
"dataTimestamp": 2,
"userId": "1",
}
`;
89 changes: 89 additions & 0 deletions src/queryBuilders/deleteItemQueryBuilder.integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { DDB } from "../../test/testFixture";
import { getDDBClientFor, startDDBTestContainer } from "../../test/testUtil";
import { Tsynamo } from "./../index";

describe("DeleteItemQueryBuilder", () => {
let tsynamoClient: Tsynamo<DDB>;

beforeAll(async () => {
const testContainer = await startDDBTestContainer();

tsynamoClient = new Tsynamo<DDB>({
ddbClient: await getDDBClientFor(testContainer),
});
});

it("handles a simple delete query", async () => {
await tsynamoClient
.putItem("myTable")
.item({
userId: "1",
dataTimestamp: 2,
})
.execute();

const itemBeforeDeletion = await tsynamoClient
.getItem("myTable")
.keys({ userId: "1", dataTimestamp: 2 })
.execute();

expect(itemBeforeDeletion).toBeDefined();

const deleteResponse = await tsynamoClient
.deleteItem("myTable")
.keys({
userId: "1",
dataTimestamp: 2,
})
.returnValues("ALL_OLD")
.execute();

expect(deleteResponse).toMatchSnapshot();

const itemAfterDeletion = await tsynamoClient
.getItem("myTable")
.keys({ userId: "1", dataTimestamp: 2 })
.execute();

expect(itemAfterDeletion).toBeUndefined();
});

it("handles a delete query with a ConditionExpression", async () => {
await tsynamoClient
.putItem("myTable")
.item({
userId: "1",
dataTimestamp: 2,
tags: ["meow"],
someBoolean: true,
})
.execute();

expect(
tsynamoClient
.deleteItem("myTable")
.keys({
userId: "1",
dataTimestamp: 2,
})
.conditionExpression("NOT", (qb) => {
return qb.expression("tags", "contains", "meow");
})
.execute()
).rejects.toMatchInlineSnapshot(
`[ConditionalCheckFailedException: The conditional request failed]`
);

const res = await tsynamoClient
.deleteItem("myTable")
.keys({ userId: "1", dataTimestamp: 2 })
.conditionExpression("NOT", (qb) => {
return qb.expression("tags", "contains", "meow");
})
.orConditionExpression("someBoolean", "attribute_exists")
.returnValues("ALL_OLD")
.execute();

expect(res).toBeDefined();
});
});
214 changes: 214 additions & 0 deletions src/queryBuilders/deleteItemQueryBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
import { DeleteNode } from "../nodes/deleteNode";
import { ReturnValuesOptions } from "../nodes/returnValuesNode";
import { QueryCompiler } from "../queryCompiler";
import {
ExecuteOutput,
ObjectKeyPaths,
PickPk,
PickSkRequired,
} from "../typeHelpers";
import { preventAwait } from "../util/preventAwait";
import {
AttributeBeginsWithExprArg,
AttributeBetweenExprArg,
AttributeContainsExprArg,
AttributeFuncExprArg,
BuilderExprArg,
ComparatorExprArg,
ExprArgs,
ExpressionBuilder,
NotExprArg,
} from "./expressionBuilder";

export interface DeleteItemQueryBuilderInterface<
DDB,
Table extends keyof DDB,
O
> {
// conditionExpression
conditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: ComparatorExprArg<DDB, Table, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

conditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: AttributeFuncExprArg<Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

conditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: AttributeBeginsWithExprArg<Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

conditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: AttributeContainsExprArg<DDB, Table, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

conditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: AttributeBetweenExprArg<DDB, Table, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

conditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: NotExprArg<DDB, Table, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

conditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: BuilderExprArg<DDB, Table, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

// orConditionExpression
orConditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: ComparatorExprArg<DDB, Table, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

orConditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: AttributeFuncExprArg<Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

orConditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: AttributeBeginsWithExprArg<Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

orConditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: AttributeContainsExprArg<DDB, Table, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

orConditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: AttributeBetweenExprArg<DDB, Table, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

orConditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: NotExprArg<DDB, Table, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

orConditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: BuilderExprArg<DDB, Table, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

returnValues(
option: Extract<ReturnValuesOptions, "NONE" | "ALL_OLD">
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

returnValuesOnConditionCheckFailure(
option: Extract<ReturnValuesOptions, "NONE" | "ALL_OLD">
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

keys<Keys extends PickPk<DDB[Table]> & PickSkRequired<DDB[Table]>>(
pk: Keys
): DeleteItemQueryBuilderInterface<DDB, Table, O>;

execute(): Promise<ExecuteOutput<O>[] | undefined>;
}

/**
* @todo support ReturnValuesOnConditionCheckFailure
*/
export class DeleteItemQueryBuilder<
DDB,
Table extends keyof DDB,
O extends DDB[Table]
> implements DeleteItemQueryBuilderInterface<DDB, Table, O>
{
readonly #props: DeleteItemQueryBuilderProps;

constructor(props: DeleteItemQueryBuilderProps) {
this.#props = props;
}

conditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: ExprArgs<DDB, Table, O, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O> {
const eB = new ExpressionBuilder<DDB, Table, O>({
node: { ...this.#props.node.conditionExpression },
});

const expressionNode = eB.expression(...args)._getNode();

return new DeleteItemQueryBuilder<DDB, Table, O>({
...this.#props,
node: {
...this.#props.node,
conditionExpression: expressionNode,
},
});
}

orConditionExpression<Key extends ObjectKeyPaths<DDB[Table]>>(
...args: ExprArgs<DDB, Table, O, Key>
): DeleteItemQueryBuilderInterface<DDB, Table, O> {
const eB = new ExpressionBuilder<DDB, Table, O>({
node: { ...this.#props.node.conditionExpression },
});

const expressionNode = eB.orExpression(...args)._getNode();

return new DeleteItemQueryBuilder<DDB, Table, O>({
...this.#props,
node: {
...this.#props.node,
conditionExpression: expressionNode,
},
});
}

returnValues(
option: Extract<ReturnValuesOptions, "NONE" | "ALL_OLD">
): DeleteItemQueryBuilderInterface<DDB, Table, O> {
return new DeleteItemQueryBuilder<DDB, Table, O>({
...this.#props,
node: {
...this.#props.node,
returnValues: {
kind: "ReturnValuesNode",
option,
},
},
});
}

returnValuesOnConditionCheckFailure(
option: Extract<ReturnValuesOptions, "NONE" | "ALL_OLD">
): DeleteItemQueryBuilderInterface<DDB, Table, O> {
return new DeleteItemQueryBuilder<DDB, Table, O>({
...this.#props,
node: {
...this.#props.node,
returnValuesOnConditionCheckFailure: {
kind: "ReturnValuesNode",
option,
},
},
});
}

keys<Keys extends PickPk<DDB[Table]> & PickSkRequired<DDB[Table]>>(
keys: Keys
) {
return new DeleteItemQueryBuilder<DDB, Table, O>({
...this.#props,
node: {
...this.#props.node,
keys: {
kind: "KeysNode",
keys,
},
},
});
}

execute = async (): Promise<ExecuteOutput<O>[] | undefined> => {
const deleteCommand = this.#props.queryCompiler.compile(this.#props.node);
const data = await this.#props.ddbClient.send(deleteCommand);
return data.Attributes as any;
};
}

preventAwait(
DeleteItemQueryBuilder,
"Don't await DeleteItemQueryBuilder instances directly. To execute the query you need to call the `execute` method"
);

interface DeleteItemQueryBuilderProps {
readonly node: DeleteNode;
readonly ddbClient: DynamoDBDocumentClient;
readonly queryCompiler: QueryCompiler;
}
Loading

0 comments on commit b92aba5

Please sign in to comment.