Skip to content

Commit

Permalink
Merge pull request #21 from jharlow/feat-docs-1
Browse files Browse the repository at this point in the history
feat(docs): deep dive section and back to zero deps!
  • Loading branch information
jharlow authored Oct 16, 2024
2 parents f13c40f + cf5bdc9 commit 85ac1a3
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 2 deletions.
118 changes: 117 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Auto-generate `dynamodb-onetable` model schemas using `zod`, with best-in-class
- Convert `zod` _schemas_ into `dynamo-onetable` model field schemas
- Get dynamic autocomplete as you expect from `dynamo-onetable` via type-fu 🥋
- Un-representable data-types cause errors, un-representable checks will notify you via `logger.debug` if you provide a Winston instance
- Zero dependencies - compatible with `zod@^3.23.8` and `dynamo-onetable@^2.7.5`

## Rationale

Expand Down Expand Up @@ -70,9 +71,15 @@ expect(newAccount).toEqual(storedAccount);

Notice we didn't need to specify the `pk` or `pk`? That's because `Table` handles it for us when we use `z.literal()` with OneTable's [value template syntax](https://doc.onetable.io/api/table/schemas/attributes/#value-templates). The typing is smart enough to identify that these values can be automatically extracted from your entity data and aren't needed.

## A deeper dive

### Explicitly setting indexes

<details>
<summary><b>Expand for an example that explicitly sets the indexes</b></summary>

If you don't want to use `z.literal()` and OneTable's value template syntax, you can set your indexes using `z.string()` and `z.number()` as you would expect.

```ts
import { Table } from "dynamodb-onetable";
import { zodOneModelSchema } from "zod-to-dynamodb-onetable-schema";
Expand All @@ -99,7 +106,7 @@ const accountModel = table.getModel("Account");
const newAccount: z.infer<typeof accountRecordSchema> = {
pk: "Account#1",
sk: "Account",
id: 1,
id: "1",
email: "[email protected]",
status: "unverified",
};
Expand All @@ -111,6 +118,115 @@ expect(newAccount).toMatchObject(storedAccount);

</details>

### Mixing OneTable schema syntax with `zod` schemas

<details>
<summary><b>Expand for an example that nests zod model in existing schema</b></summary>

This library also supports partial `zod` schema definition via the `zodOneFieldSchema` export. In this example, we add a complex schema using the `zod` API to a nested attribute.

```ts
import { Table } from "dynamodb-onetable";
import { zodOneFieldSchema } from "zod-to-dynamodb-onetable-schema";

const table = new Table({
// other fields collapsed,
schema: {
indexes: { primary: { hash: "pk", sort: "sk" } },
models: {
Account: {
pk: { type: String, required: true },
sk: { type: String, required: true },
account: {
type: "object",
required: true,
schema: {
id: { type: String, required: true },
// other attributes collapsed
emails: zodOneFieldSchema(
// 👈 utilize our zod converter
z.array(
z.object({
email: z.string().email(),
isVerified: z.boolean(),
}),
),
),
},
},
},
},
},
});
```

Thanks to the type-fu 🥋 of `ZodToOneField`, even nesting our converter like this will still leave you with best-in-class autocomplete in the `Table` instance.

</details>

### Decoupling the `schema` from `Table`

<details>
<summary><b>Expand for an example demonstrating dependency injection using your schema</b></summary>

You might get to a point where you want to have multiple `Table` instances, at which point you'll want to have one source of truth for your schema. Likewise, you might want to inject your `Table` while still getting full autocomplete.

In short, the answer is to use `Table<typeof oneTableSchema>` as your injectable table where `oneTableSchema satisfies OneSchema`!

```ts
import { OneSchema, Table } from "dynamodb-onetable";
import { z } from "zod";
import { zodOneModelSchema } from "../src";

const accountSchema = z.object({
id: z.string().uuid(),
email: z.string(),
status: z.enum(["verified", "unverified"]),
});

const accountRecordSchema = accountSchema.extend({
pk: z.literal("${_type}#${id}"),
sk: z.literal("${_type}#"),
});

type Account = z.infer<typeof accountSchema>;

interface AccountStore {
getAccount: (accountId: string) => Promise<Account | null>;
}

const oneTableSchema = {
// other attributes collapsed
indexes: { primary: { hash: "pk", sort: "sk" } },
models: { Account: zodOneModelSchema(accountRecordSchema) },
} satisfies OneSchema;

class AccountOneTableStore implements AccountStore {
constructor(private readonly table: Table<typeof oneTableSchema>) {}

async getAccount(accountId: string): Promise<Account | null> {
try {
const data = await this.table.getModel("Account").get({ id: accountId });
return accountSchema.parse(data);
} catch (err) {
console.info("Account could not be found in OneTable", { err });
return null;
}
}
}

const table = new Table({
// other attributes collapsed
schema: oneTableSchema,
});

const accountStore = new AccountOneTableStore(table);

const account = accountStore.get("test-id");
```

</details>

## Contributing

I appreciate any contributions, issues or discussions. My aim is to make contributing quick and easy.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zod-to-dynamodb-onetable-schema",
"version": "0.0.7",
"version": "0.0.8",
"description": "Auto-generate `dynamodb-onetable` model schemas using `zod`, with best-in-class autocomplete",
"keywords": [
"dynamo",
Expand Down

0 comments on commit 85ac1a3

Please sign in to comment.