From 0b036d17a31587b34944103a1fb46980be5cbd1d Mon Sep 17 00:00:00 2001 From: John Harlow Date: Tue, 15 Oct 2024 16:41:50 -0700 Subject: [PATCH 1/2] feat(docs): deep dive section and back to zero deps! --- README.md | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb53d45..d1742f6 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 +
Expand for an example that explicitly sets the indexes +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"; @@ -99,7 +106,7 @@ const accountModel = table.getModel("Account"); const newAccount: z.infer = { pk: "Account#1", sk: "Account", - id: 1, + id: "1", email: "hello@example.com", status: "unverified", }; @@ -111,6 +118,115 @@ expect(newAccount).toMatchObject(storedAccount);
+### Mixing OneTable schema syntax with `zod` schemas + +
+Expand for an example that nests zod model in existing schema + +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. + +
+ +### Decoupling the `schema` from `Table` + +
+Expand for an example demonstrating dependency injection using your schema + +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` 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; + +interface AccountStore { + getAccount: (accountId: string) => Promise; +} + +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) {} + + async getAccount(accountId: string): Promise { + 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"); +``` + +
+ ## Contributing I appreciate any contributions, issues or discussions. My aim is to make contributing quick and easy. From ad86d6dc2e92581e51d7d68c3b1719484d5609ff Mon Sep 17 00:00:00 2001 From: John Harlow Date: Tue, 15 Oct 2024 17:07:23 -0700 Subject: [PATCH 2/2] feat(docs): push new docs to npm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e92b083..47ef145 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zod-to-dynamodb-onetable-schema", - "version": "0.0.6", + "version": "0.0.8", "description": "Auto-generate `dynamodb-onetable` model schemas using `zod`, with best-in-class autocomplete", "keywords": [ "dynamo",