Skip to content

Commit

Permalink
feat: add support for AWS SDK v3's DynamoDB and DynamoDBClient (#13)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `index.ts` exports are removed and `TypeSafeDynamoDB` is now renamed to `TypeSafeDyanmoDBv2`
  • Loading branch information
sam authored Feb 10, 2022
1 parent e70beba commit ed48689
Show file tree
Hide file tree
Showing 15 changed files with 1,194 additions and 31 deletions.
28 changes: 20 additions & 8 deletions .projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ const project = new typescript.TypeScriptProject({
defaultReleaseBranch: "main",
name: "typesafe-dynamodb",

devDeps: ["aws-sdk", "@types/aws-lambda"],
deps: [
"aws-sdk",
"@aws-sdk/client-dynamodb",
"@aws-sdk/smithy-client",
"@aws-sdk/types",
"@types/aws-lambda",
],
eslintOptions: {
ignorePatterns: ["**"],
},
Expand Down
52 changes: 50 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ npm install --save-dev typesafe-dynamodb

## Usage

To use `typesafe-dynamodb`, there is no need to change anything about your existing runtime code. It is purely type definitions, so you only need to cast an instance of `AWS.DynamoDB` to the `TypeSafeDynamoDB<T, HashKey, RangeKey>` interface and use the client as normal, except now you can enjoy a dynamic, type-safe experience in your IDE instead.
This library contains type definitions for both AWS SDK v2 (`aws-sdk`) and AWS SDK v3 (`@aws-sdk/client-dynamodb`);

### AWS SDK v2

To use `typesafe-dynamodb` with the AWS SDK v2, there is no need to change anything about your existing runtime code. It is purely type definitions, so you only need to cast an instance of `AWS.DynamoDB` to the `TypeSafeDynamoDBv2<T, HashKey, RangeKey>` interface and use the client as normal, except now you can enjoy a dynamic, type-safe experience in your IDE instead.

```ts
import { DynamoDB } from "aws-sdk";
Expand All @@ -37,13 +41,57 @@ interface Record {
Then, cast the `DynamoDB` client instance to `TypeSafeDynamoDB`;

```ts
const typesafeClient: TypeSafeDynamoDB<Record, "key", "sort"> = client;
import { TypeSafeDynamoDBv2 } from "typesafe-dynamodb/lib/client-v2";

const typesafeClient: TypeSafeDynamoDBv2<Record, "key", "sort"> = client;
```

`"key"` is the name of the Hash Key attribute, and `"sort"` is the name of the Range Key attribute.

Finally, use the client as you normally would, except now with intelligent type hints and validations.

### AWS SDK v3

#### Option 1 - DynamoDB (similar to SDK v2)

`DynamoDB` is an almost identical implementation to the AWS SDK v2, except with minor changes such as returning a `Promise` by default. It is a convenient way of using the DynamoDB API, except it is not optimized for tree-shaking (for that, see Option 2).

To override the types, follow a similar method to v2, except by importing TypeSafeDynamoDBv3 (instead of v2):

```ts
import { DynamoDB } from "@aws-sdk/client-dynamodb";
import { TypeSafeDynamoDBv3 } from "typesafe-dynamodb/lib/client-v3";

const client = new DynamoDB({..});
const typesafeClient: TypeSafeDynamoDBv3<Record, "key", "sort"> = client;
```

#### Option 2 - DynamoDBClient (a Command-Response interface optimized for tree-shaking)

`DynamoDBClient` is a generic interface with a single method, `send`. To invoke an API, call `send` with an instance of the API's corresponding `Command`.

This option is designed for optimal tree-shaking when bundling code, ensuring that the bundle only includes code for the APIs your application uses.

For this option, type-safety is achieved by declaring your own commands and then calling the standard `DynamoDBClient`:

```ts
interface MyType {
key: string;
sort: number;
list: string[];
}

const client = new DynamoDBClient({});

const GetMyTypeCommand = TypeSafeGetItemCommand<MyType, "key", "sort">();

await client.send(
new GetMyTypeCommand({
..
})
);
```

## Features

### Type-aware Input and Output
Expand Down
9 changes: 7 additions & 2 deletions package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/client.ts → src/client-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { KeyAttribute } from "./key";
import { PutItemInput, PutItemOutput } from "./put-item";
import { QueryInput, QueryOutput } from "./query";

export interface TypeSafeDynamoDB<
export interface TypeSafeDynamoDBv2<
Item extends object,
PartitionKey extends keyof Item,
RangeKey extends keyof Item | undefined = undefined
Expand Down
130 changes: 130 additions & 0 deletions src/client-v3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import type {
DynamoDB,
ReturnValue as DynamoDBReturnValue,
} from "@aws-sdk/client-dynamodb";
import { MetadataBearer } from "@aws-sdk/types";
import { Callback } from "./callback";
import { DeleteItemInput, DeleteItemOutput } from "./delete-item";
import { GetItemInput, GetItemOutput } from "./get-item";
import { KeyAttribute } from "./key";
import { PutItemInput, PutItemOutput } from "./put-item";
import { QueryInput, QueryOutput } from "./query";

export interface TypeSafeDynamoDBv3<
Item extends object,
PartitionKey extends keyof Item,
RangeKey extends keyof Item | undefined = undefined
> extends Omit<DynamoDB, "getItem" | "deleteItem" | "putItem" | "query"> {
getItem<
Key extends KeyAttribute<Item, PartitionKey, RangeKey>,
AttributesToGet extends keyof Item | undefined = undefined,
ProjectionExpression extends string | undefined = undefined
>(
params: GetItemInput<
Item,
Key,
PartitionKey,
RangeKey,
AttributesToGet,
ProjectionExpression
>
): Promise<
GetItemOutput<Item, Key, AttributesToGet, ProjectionExpression> &
MetadataBearer
>;

getItem<
Key extends KeyAttribute<Item, PartitionKey, RangeKey>,
AttributesToGet extends keyof Item | undefined = undefined,
ProjectionExpression extends string | undefined = undefined
>(
params: GetItemInput<
Item,
Key,
PartitionKey,
RangeKey,
AttributesToGet,
ProjectionExpression
>,
callback: Callback<
GetItemOutput<Item, Key, AttributesToGet, ProjectionExpression>,
any
>
): void;

deleteItem<
Key extends KeyAttribute<Item, PartitionKey, RangeKey>,
ConditionExpression extends string | undefined,
ReturnValue extends DynamoDBReturnValue = "NONE"
>(
params: DeleteItemInput<
Item,
PartitionKey,
RangeKey,
Key,
ConditionExpression,
ReturnValue
>
): Promise<DeleteItemOutput<Item, ReturnValue> & MetadataBearer>;

deleteItem<
Key extends KeyAttribute<Item, PartitionKey, RangeKey>,
ConditionExpression extends string | undefined,
ReturnValue extends DynamoDBReturnValue = "NONE"
>(
params: DeleteItemInput<
Item,
PartitionKey,
RangeKey,
Key,
ConditionExpression,
ReturnValue
>,
callback: Callback<
DeleteItemOutput<Item, ReturnValue> & MetadataBearer,
any
>
): void;

putItem<
ConditionExpression extends string | undefined,
ReturnValue extends DynamoDBReturnValue = "NONE"
>(
params: PutItemInput<Item, ConditionExpression, ReturnValue>
): Promise<PutItemOutput<Item, ReturnValue> & MetadataBearer>;

putItem<
ConditionExpression extends string | undefined,
ReturnValue extends DynamoDBReturnValue = "NONE"
>(
params: PutItemInput<Item, ConditionExpression, ReturnValue>,
callback: Callback<PutItemOutput<Item, ReturnValue> & MetadataBearer, any>
): void;

query<
KeyConditionExpression extends string | undefined = undefined,
FilterExpression extends string | undefined = undefined,
AttributesToGet extends keyof Item | undefined = undefined
>(
params: QueryInput<
Item,
KeyConditionExpression,
FilterExpression,
AttributesToGet
>
): Promise<QueryOutput<Item, AttributesToGet> & MetadataBearer>;

query<
KeyConditionExpression extends string | undefined = undefined,
FilterExpression extends string | undefined = undefined,
AttributesToGet extends keyof Item | undefined = undefined
>(
params: QueryInput<
Item,
KeyConditionExpression,
FilterExpression,
AttributesToGet
>,
callback: Callback<QueryOutput<Item, AttributesToGet> & MetadataBearer, any>
): void;
}
59 changes: 59 additions & 0 deletions src/delete-item-command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
DynamoDBClientResolvedConfig,
DeleteItemCommand as _DeleteItemCommand,
ReturnValue as DynamoDBReturnValue,
} from "@aws-sdk/client-dynamodb";
import type { Command } from "@aws-sdk/smithy-client";
import { DeleteItemInput, DeleteItemOutput } from "./delete-item";
import { MetadataBearer } from "@aws-sdk/types";
import { KeyAttribute } from "./key";

export interface DeleteItemCommand<
Item extends object,
PartitionKey extends keyof Item,
RangeKey extends keyof Item | undefined,
Key extends KeyAttribute<Item, PartitionKey, RangeKey>,
ConditionExpression extends string | undefined,
ReturnValue extends DynamoDBReturnValue = "NONE"
> extends Command<
DeleteItemInput<
Item,
PartitionKey,
RangeKey,
Key,
ConditionExpression,
ReturnValue
>,
DeleteItemOutput<Item, ReturnValue> & MetadataBearer,
DynamoDBClientResolvedConfig
> {
_brand: "DeleteItemCommand";
}

export function TypeSafeDeleteItemCommand<
Item extends object,
PartitionKey extends keyof Item,
RangeKey extends keyof Item | undefined
>(): new <
Key extends KeyAttribute<Item, PartitionKey, RangeKey>,
ConditionExpression extends string | undefined = undefined,
ReturnValue extends DynamoDBReturnValue = "NONE"
>(
input: DeleteItemInput<
Item,
PartitionKey,
RangeKey,
Key,
ConditionExpression,
ReturnValue
>
) => DeleteItemCommand<
Item,
PartitionKey,
RangeKey,
Key,
ConditionExpression,
ReturnValue
> {
return _DeleteItemCommand as any;
}
Loading

0 comments on commit ed48689

Please sign in to comment.