Skip to content

Commit

Permalink
initial work for readTransactions
Browse files Browse the repository at this point in the history
  • Loading branch information
mindler-olli committed Apr 19, 2024
1 parent 5a48f58 commit e1a616b
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 18 deletions.
6 changes: 6 additions & 0 deletions src/nodes/readTransactionNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { TransactGetItemNode } from "./transactGetItemNode";

export type ReadTransactionNode = {
readonly kind: "ReadTransactionNode";
readonly transactGetItems: TransactGetItemNode[];
};
6 changes: 6 additions & 0 deletions src/nodes/transactGetItemNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { GetNode } from "./getNode";

export type TransactGetItemNode = {
readonly kind: "TransactGetItemNode";
readonly Get: GetNode;
};
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TransactWriteItemNode } from "./TransactItemNode";
import { TransactWriteItemNode } from "./transactWriteItemNode";

export type WriteTransactionNode = {
readonly kind: "WriteTransactionNode";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`TransactionBuilder > handles transaction with puts 1`] = `
exports[`WriteTransactionBuilder > handles transaction with puts 1`] = `
[
{
"dataTimestamp": 1,
Expand All @@ -13,7 +13,7 @@ exports[`TransactionBuilder > handles transaction with puts 1`] = `
]
`;

exports[`TransactionBuilder > handles transaction with updates 1`] = `
exports[`WriteTransactionBuilder > handles transaction with updates 1`] = `
[
{
"dataTimestamp": 1,
Expand Down
19 changes: 12 additions & 7 deletions src/queryBuilders/getItemQueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ import { preventAwait } from "../util/preventAwait";
export interface GetQueryBuilderInterface<DDB, Table extends keyof DDB, O> {
keys<Keys extends PickPk<DDB[Table]> & PickSkRequired<DDB[Table]>>(
pk: Keys
): GetQueryBuilderInterface<DDB, Table, O>;
): GetQueryBuilder<DDB, Table, O>;

consistentRead(enabled: boolean): GetQueryBuilderInterface<DDB, Table, O>;
consistentRead(enabled: boolean): GetQueryBuilder<DDB, Table, O>;

attributes<A extends readonly ObjectFullPaths<DDB[Table]>[] & string[]>(
attributes: A
): GetQueryBuilderInterface<DDB, Table, SelectAttributes<DDB[Table], A>>;
): GetQueryBuilder<DDB, Table, SelectAttributes<DDB[Table], A>>;

compile(): GetCommand;
execute(): Promise<ExecuteOutput<O> | undefined>;
}

export class GetQueryBuilder<DDB, Table extends keyof DDB, O extends DDB[Table]>
export class GetQueryBuilder<DDB, Table extends keyof DDB, O>
implements GetQueryBuilderInterface<DDB, Table, O>
{
readonly #props: GetQueryBuilderProps;
Expand All @@ -36,7 +36,7 @@ export class GetQueryBuilder<DDB, Table extends keyof DDB, O extends DDB[Table]>

keys<Keys extends PickPk<DDB[Table]> & PickSkRequired<DDB[Table]>>(
keys: Keys
) {
): GetQueryBuilder<DDB, Table, O> {
return new GetQueryBuilder<DDB, Table, O>({
...this.#props,
node: {
Expand All @@ -49,7 +49,7 @@ export class GetQueryBuilder<DDB, Table extends keyof DDB, O extends DDB[Table]>
});
}

consistentRead(enabled: boolean): GetQueryBuilderInterface<DDB, Table, O> {
consistentRead(enabled: boolean): GetQueryBuilder<DDB, Table, O> {
return new GetQueryBuilder<DDB, Table, O>({
...this.#props,
node: {
Expand All @@ -64,7 +64,7 @@ export class GetQueryBuilder<DDB, Table extends keyof DDB, O extends DDB[Table]>

attributes<A extends readonly ObjectFullPaths<DDB[Table]>[] & string[]>(
attributes: A
): GetQueryBuilderInterface<DDB, Table, SelectAttributes<DDB[Table], A>> {
): GetQueryBuilder<DDB, Table, SelectAttributes<DDB[Table], A>> {
return new GetQueryBuilder({
...this.#props,
node: {
Expand All @@ -80,11 +80,16 @@ export class GetQueryBuilder<DDB, Table extends keyof DDB, O extends DDB[Table]>
compile(): GetCommand {
return this.#props.queryCompiler.compile(this.#props.node);
}

execute = async (): Promise<ExecuteOutput<O> | undefined> => {
const command = this.compile();
const item = await this.#props.ddbClient.send(command);
return (item.Item as ExecuteOutput<O>) ?? undefined;
};

public get node() {
return this.#props.node;
}
}

preventAwait(
Expand Down
32 changes: 32 additions & 0 deletions src/queryBuilders/readTransactionBuilder.integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
import { DDB } from "../../test/testFixture";
import { getDDBClientFor, startDDBTestContainer } from "../../test/testUtil";
import { Tsynamo } from "../index";

describe("ReadTransactionBuilder", () => {
let tsynamoClient: Tsynamo<DDB>;
let ddbClient: DynamoDBDocumentClient;

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

tsynamoClient = new Tsynamo<DDB>({
ddbClient,
});
});

it("handles transaction with gets", async () => {
const trx = tsynamoClient.createReadTransaction();

trx.addItem({
Get: tsynamoClient.getItem("myTable").keys({
userId: "123",
dataTimestamp: 222,
}),
});

const result = await trx.execute();
console.log("result", result);
});
});
48 changes: 48 additions & 0 deletions src/queryBuilders/readTransactionBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
import { ReadTransactionNode } from "../nodes/readTransactionNode";
import { QueryCompiler } from "../queryCompiler";
import { GetQueryBuilder } from "./getItemQueryBuilder";

/**
* @todo What to show as return type?
*/
export interface ReadTransactionBuilderInterface<DDB> {
addItem<Table extends keyof DDB, O extends DDB[Table]>(item: {
Get: GetQueryBuilder<DDB, Table, O>;
}): void;

execute(): Promise<unknown[]>;
}

export class ReadTransactionBuilder<DDB, X>
implements ReadTransactionBuilderInterface<DDB>
{
readonly #props: ReadTransactionBuilderProps;

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

addItem(item: { Get: GetQueryBuilder<DDB, any, any> }) {
this.#props.node.transactGetItems.push({
kind: "TransactGetItemNode",
Get: item.Get.node,
});
}

async execute() {
const transactionCommand = this.#props.queryCompiler.compile(
this.#props.node
);

return (
await this.#props.ddbClient.send(transactionCommand)
).Responses?.map((o) => o.Item) as unknown[];
}
}

interface ReadTransactionBuilderProps {
readonly node: ReadTransactionNode;
readonly ddbClient: DynamoDBDocumentClient;
readonly queryCompiler: QueryCompiler;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DDB } from "../../test/testFixture";
import { getDDBClientFor, startDDBTestContainer } from "../../test/testUtil";
import { Tsynamo } from "../index";

describe("TransactionBuilder", () => {
describe("WriteTransactionBuilder", () => {
let tsynamoClient: Tsynamo<DDB>;
let ddbClient: DynamoDBDocumentClient;

Expand Down
2 changes: 1 addition & 1 deletion src/queryBuilders/writeTransactionBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
import { WriteTransactionNode } from "../nodes/transactionNode";
import { WriteTransactionNode } from "../nodes/writeTransactionNode";
import { QueryCompiler } from "../queryCompiler";
import { DeleteItemQueryBuilder } from "./deleteItemQueryBuilder";
import { PutItemQueryBuilder } from "./putItemQueryBuilder";
Expand Down
43 changes: 37 additions & 6 deletions src/queryCompiler/queryCompiler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { TransactWriteItem, Update } from "@aws-sdk/client-dynamodb";
import {
TransactGetItem,
TransactWriteItem,
Update,
} from "@aws-sdk/client-dynamodb";
import {
DeleteCommand,
DeleteCommandInput,
GetCommand,
GetCommandInput,
PutCommand,
PutCommandInput,
QueryCommand,
TransactGetCommand,
TransactWriteCommand,
UpdateCommand,
} from "@aws-sdk/lib-dynamodb";
Expand All @@ -19,12 +25,13 @@ import { GetNode } from "../nodes/getNode";
import { KeyConditionNode } from "../nodes/keyConditionNode";
import { PutNode } from "../nodes/putNode";
import { QueryNode } from "../nodes/queryNode";
import { ReadTransactionNode } from "../nodes/readTransactionNode";
import { RemoveUpdateExpression } from "../nodes/removeUpdateExpression";
import { SetUpdateExpression } from "../nodes/setUpdateExpression";
import { SetUpdateExpressionFunction } from "../nodes/setUpdateExpressionFunction";
import { WriteTransactionNode } from "../nodes/transactionNode";
import { UpdateExpression } from "../nodes/updateExpression";
import { UpdateNode } from "../nodes/updateNode";
import { WriteTransactionNode } from "../nodes/writeTransactionNode";
import {
getAttributeNameFrom,
getExpressionAttributeNameFrom,
Expand All @@ -38,6 +45,7 @@ export class QueryCompiler {
compile(rootNode: DeleteNode): DeleteCommand;
compile(rootNode: UpdateNode): UpdateCommand;
compile(rootNode: WriteTransactionNode): TransactWriteCommand;
compile(rootNode: ReadTransactionNode): TransactGetCommand;
compile(
rootNode:
| QueryNode
Expand All @@ -46,6 +54,7 @@ export class QueryCompiler {
| DeleteNode
| UpdateNode
| WriteTransactionNode
| ReadTransactionNode
) {
switch (rootNode.kind) {
case "GetNode":
Expand All @@ -59,11 +68,17 @@ export class QueryCompiler {
case "UpdateNode":
return this.compileUpdateNode(rootNode);
case "WriteTransactionNode":
return this.compileTransactionNode(rootNode);
return this.compileWriteTransactionNode(rootNode);
case "ReadTransactionNode":
return this.compileReadTransactionNode(rootNode);
}
}

compileGetNode(getNode: GetNode): GetCommand {
return new GetCommand(this.compileGetCmdInput(getNode));
}

compileGetCmdInput(getNode: GetNode): GetCommandInput {
const {
table: tableNode,
keys: keysNode,
Expand All @@ -74,13 +89,13 @@ export class QueryCompiler {
const { ProjectionExpression, ExpressionAttributeNames } =
this.compileAttributeNamesNode(attributesNode);

return new GetCommand({
return {
TableName: tableNode.table,
Key: keysNode?.keys,
ConsistentRead: consistentReadNode?.enabled,
ProjectionExpression,
ExpressionAttributeNames,
});
};
}

compileQueryNode(queryNode: QueryNode): QueryCommand {
Expand Down Expand Up @@ -280,7 +295,7 @@ export class QueryCompiler {
};
}

compileTransactionNode(transactionNode: WriteTransactionNode) {
compileWriteTransactionNode(transactionNode: WriteTransactionNode) {
const TransactItems = transactionNode.transactWriteItems.map((item) => {
const compiledTransactItem: TransactWriteItem = {};

Expand All @@ -306,6 +321,22 @@ export class QueryCompiler {
});
}

compileReadTransactionNode(transactionNode: ReadTransactionNode) {
const TransactItems = transactionNode.transactGetItems.map((item) => {
const compiledGet = this.compileGetCmdInput(item.Get);

const compiledTransactItem: TransactGetItem = {
Get: compiledGet,
};

return compiledTransactItem;
});

return new TransactGetCommand({
TransactItems: TransactItems,
});
}

compileAttributeNamesNode(node?: AttributesNode) {
const ProjectionExpression = node?.attributes
.map((att) => getExpressionAttributeNameFrom(att))
Expand Down
18 changes: 18 additions & 0 deletions src/queryCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DeleteItemQueryBuilder } from "./queryBuilders/deleteItemQueryBuilder";
import { GetQueryBuilder } from "./queryBuilders/getItemQueryBuilder";
import { PutItemQueryBuilder } from "./queryBuilders/putItemQueryBuilder";
import { QueryQueryBuilder } from "./queryBuilders/queryQueryBuilder";
import { ReadTransactionBuilder } from "./queryBuilders/readTransactionBuilder";
import { UpdateItemQueryBuilder } from "./queryBuilders/updateItemQueryBuilder";
import { WriteTransactionBuilder } from "./queryBuilders/writeTransactionBuilder";
import { QueryCompiler } from "./queryCompiler";
Expand Down Expand Up @@ -165,6 +166,23 @@ export class QueryCreator<DDB> {
queryCompiler: this.#props.queryCompiler,
});
}

/**
* Returns a builder that can be used to group many different get
* operations together and execute them in a transaction.
*
* @see https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/TransactGetItemsCommand/
*/
createReadTransaction() {
return new ReadTransactionBuilder<DDB>({

Check failure on line 177 in src/queryCreator.ts

View workflow job for this annotation

GitHub Actions / test

Unhandled error

TypeCheckError: Expected 2 type arguments, but got 1. ❯ src/queryCreator.ts:177:39
node: {
kind: "ReadTransactionNode",
transactGetItems: [],
},
ddbClient: this.#props.ddbClient,
queryCompiler: this.#props.queryCompiler,
});
}
}

export interface QueryCreatorProps {
Expand Down

0 comments on commit e1a616b

Please sign in to comment.