✨ Type-friendly DynamoDB query builder! ✨
Inspired by Kysely
Tsynamo simplifies the DynamoDB API so that you don't have to write commands with raw expressions and hassle with the attribute names and values. Moreover, Tsynamo makes sure you use correct types in your DynamoDB expressions, and the queries are nicer to write with autocompletion!
Warning
Tsynamo is still an early stage project, please post issues if you notice something missing from the API! Currently the update-item
command is still a work in progress, and support for batch commands is missing.
Available in NPM.
npm i tsynamo
pnpm install tsynamo
yarn add tsynamo
- Define the types for your DynamoDB (DDB) tables:
import { PartitionKey, SortKey } from "tsynamo";
export interface DDB {
UserEvents: {
userId: PartitionKey<string>;
eventId: SortKey<number>;
eventType: string;
userAuthenticated: boolean;
};
}
Tip
Notice that you can have multiple tables in the DDB schema. Nested attributes are supported too.
- Create a DynamoDB document client:
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const ddbClient = DynamoDBDocumentClient.from(
new DynamoDBClient({
/* Configure client... */
})
);
Important
The document client must come from @aws-sdk/lib-dynamodb!
- Create a Tsynamo client with the defined DynamoDB types and client:
const tsynamoClient = new Tsynamo<DDB>({
ddbClient: dynamoDbDocumentClient,
});
await tsynamoClient
.getItem("UserEvents")
.keys({
userId: "123",
eventId: 222,
})
.attributes(["userId"])
.execute();
await tsynamoClient
.query("UserEvents")
.keyCondition("userId", "=", "123")
.execute();
await tsynamoClient
.query("UserEvents")
.keyCondition("userId", "=", "123")
.keyCondition("eventId", "<", 1000)
.execute();
await tsynamoClient
.query("UserEvents")
.keyCondition("userId", "=", "123")
.filterExpression("eventType", "=", "LOG_IN_EVENT")
.execute();
await tsynamoClient
.query("UserEvents")
.keyCondition("userId", "=", "123")
.filterExpression("eventType", "begins_with", "LOG")
.execute();
await tsynamoClient
.query("UserEvents")
.keyCondition("userId", "=", "123")
.filterExpression("eventType", "begins_with", "LOG_IN")
.orFilterExpression("eventType", "begins_with", "SIGN_IN")
.execute();
await tsynamoClient
.query("UserEvents")
.keyCondition("userId", "=", "123")
.filterExpression("eventType", "=", "LOG_IN")
.orFilterExpression((qb) =>
qb
.filterExpression("eventType", "=", "UNAUTHORIZED_ACCESS")
.filterExpression("userAuthenticated", "=", true)
)
.orFilterExpression("eventType", "begins_with", "SIGN_IN")
.execute();
Note
This would compile as the following FilterExpression:
eventType = "LOG_IN" OR (eventType = "UNAUTHORIZED_ACCESS" AND userAuthenticated = true
)
await tsynamoClient
.query("UserEvents")
.keyCondition("userId", "=", "123")
.filterExpression("NOT", (qb) =>
qb.filterExpression("eventType", "=", "LOG_IN")
)
.execute();
This would compile as the following FilterExpression:
NOT eventType = "LOG_IN"
, i.e. return all events whose types is not "LOG_IN"
await tsynamoClient
.putItem("myTable")
.item({
userId: "123",
eventId: 313,
})
.execute();
await tsynamoClient
.putItem("myTable")
.item({
userId: "123",
eventId: 313,
})
.conditionExpression("userId", "attribute_not_exists")
.execute();
await tsynamoClient
.putItem("myTable")
.item({
userId: "123",
eventId: 313,
})
.conditionExpression("userId", "attribute_not_exists")
.orConditionExpression("eventType", "begins_with", "LOG_")
.execute();
await tsynamoClient
.deleteItem("myTable")
.keys({
userId: "123",
eventId: 313,
})
.execute();
await tsynamoClient
.deleteItem("myTable")
.keys({
userId: "123",
eventId: 313,
})
.conditionExpression("eventType", "attribute_not_exists")
.execute();
WIP