From b8dab3d2af1e54e67aa526df312a122a8c953572 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 09:16:46 +0000 Subject: [PATCH 01/11] Add plan for Zod and JSON Schema type generators https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- plan.md | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 plan.md diff --git a/plan.md b/plan.md new file mode 100644 index 00000000..11b63d2c --- /dev/null +++ b/plan.md @@ -0,0 +1,311 @@ +# Plan: Add Zod Type Generator & JSON Schema Generator + +## Overview + +The `postgres-meta` project has an established pattern for type generators. Each language has a route handler, a template, and tests, plus registration in shared files. We'll follow this exact pattern to add two new generators: + +1. **Zod** — Generates TypeScript code using the `zod` library for runtime validation (unlike the existing TypeScript generator which only produces static types). +2. **JSON Schema** — Generates a language-agnostic [JSON Schema](https://json-schema.org/) (Draft 2020-12) document describing the database schema. + +Both generators consume the existing `GeneratorMetadata` type from `src/lib/generators.ts` — no changes to the core introspection layer are needed. + +--- + +## 1. Zod Type Generator + +### What it produces + +TypeScript source code that imports from `zod` and exports schema objects for every table, view, enum, composite type, and function in the database. + +Example output: + +```ts +import { z } from "zod"; + +export const userStatusSchema = z.enum(["active", "inactive"]); + +export const usersRowSchema = z.object({ + id: z.number().int(), + name: z.string(), + email: z.string(), + status: userStatusSchema, + created_at: z.string().datetime(), + metadata: z.unknown().nullable(), +}); + +export const usersInsertSchema = z.object({ + id: z.number().int().optional(), // has default (identity) + name: z.string(), + email: z.string(), + status: userStatusSchema.optional(), // has default + created_at: z.string().datetime().optional(), + metadata: z.unknown().nullable().optional(), +}); + +export const usersUpdateSchema = usersRowSchema.partial(); +``` + +### Type mapping (`PG_TYPE_TO_ZOD_MAP`) + +| PostgreSQL type | Zod validator | +|---|---| +| `bool` | `z.boolean()` | +| `int2`, `int4` | `z.number().int()` | +| `int8` | `z.number().int()` | +| `float4`, `float8`, `numeric` | `z.number()` | +| `text`, `varchar`, `char`, `name`, `bpchar` | `z.string()` | +| `uuid` | `z.string().uuid()` | +| `json`, `jsonb` | `z.unknown()` | +| `timestamptz`, `timestamp` | `z.string().datetime()` | +| `date` | `z.string().date()` | +| `time`, `timetz` | `z.string()` | +| `bytea` | `z.string()` | +| `inet`, `cidr` | `z.string()` | +| `macaddr`, `macaddr8` | `z.string()` | +| `int4range`, `int8range`, `numrange`, `tsrange`, `tstzrange`, `daterange` | `z.string()` | +| `void` | `z.void()` | +| `record` | `z.record(z.unknown())` | +| Array types (prefix `_`) | `z.array(innerSchema)` | +| Enum types | Reference to generated enum schema | +| Composite types | Reference to generated composite schema | +| Unknown/unmapped | `z.unknown()` | + +**Nullable handling:** Append `.nullable()` when the column is nullable. + +**Insert schemas:** Columns with defaults or identity columns get `.optional()`. + +**Update schemas:** All fields are `.partial()`. + +### Generation structure + +Per schema, generate: + +1. Enum schemas (`z.enum([...])`) +2. Composite type schemas (`z.object({...})`) +3. Table Row / Insert / Update schemas +4. View Row schemas (Insert/Update only if the view is updatable) +5. Materialized view Row schemas +6. Function input/output schemas + +Use `prettier` for formatting (same as the existing TypeScript generator). + +### Query parameters + +| Parameter | Description | +|---|---| +| `included_schemas` | Comma-separated schema whitelist | +| `excluded_schemas` | Comma-separated schema blacklist | + +### Files + +| Action | File | +|---|---| +| **Create** | `src/server/templates/zod.ts` | +| **Create** | `src/server/routes/generators/zod.ts` | +| Modify | `src/server/routes/index.ts` — register route | +| Modify | `src/server/server.ts` — add CLI case | +| Modify | `package.json` — add `gen:types:zod` script | +| Modify | `test/server/typegen.ts` — add snapshot tests | + +--- + +## 2. JSON Schema Generator + +### What it produces + +A JSON document conforming to JSON Schema Draft 2020-12, describing every table, view, enum, composite type, and function in the database. + +Example output: + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "public": { + "type": "object", + "properties": { + "Tables": { + "type": "object", + "properties": { + "users": { + "type": "object", + "properties": { + "Row": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "status": { "$ref": "#/$defs/public.user_status" } + }, + "required": ["id", "name", "status"] + }, + "Insert": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "status": { "$ref": "#/$defs/public.user_status" } + }, + "required": ["name"] + }, + "Update": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "status": { "$ref": "#/$defs/public.user_status" } + } + } + } + } + } + }, + "Views": { "..." : "..." }, + "Enums": { + "type": "object", + "properties": { + "user_status": { "$ref": "#/$defs/public.user_status" } + } + }, + "CompositeTypes": { "..." : "..." }, + "Functions": { "..." : "..." } + } + } + }, + "$defs": { + "public.user_status": { + "type": "string", + "enum": ["active", "inactive"] + } + } +} +``` + +### Type mapping (`PG_TYPE_TO_JSON_SCHEMA_MAP`) + +| PostgreSQL type | JSON Schema | +|---|---| +| `bool` | `{ "type": "boolean" }` | +| `int2`, `int4`, `int8` | `{ "type": "integer" }` | +| `float4`, `float8`, `numeric` | `{ "type": "number" }` | +| `text`, `varchar`, `char`, `name`, `bpchar` | `{ "type": "string" }` | +| `uuid` | `{ "type": "string", "format": "uuid" }` | +| `json`, `jsonb` | `{}` (any value) | +| `timestamptz`, `timestamp` | `{ "type": "string", "format": "date-time" }` | +| `date` | `{ "type": "string", "format": "date" }` | +| `time`, `timetz` | `{ "type": "string", "format": "time" }` | +| `bytea` | `{ "type": "string" }` | +| `inet`, `cidr` | `{ "type": "string" }` | +| Range types | `{ "type": "string" }` | +| `void` | `{ "type": "null" }` | +| `record` | `{ "type": "object" }` | +| Array types (prefix `_`) | `{ "type": "array", "items": innerSchema }` | +| Enum types | `{ "$ref": "#/$defs/schema.enum_name" }` | +| Composite types | `{ "$ref": "#/$defs/schema.composite_name" }` | +| Unknown/unmapped | `{}` | + +**Nullable handling:** Wrap with `{ "oneOf": [typeSchema, { "type": "null" }] }` + +**Insert schemas:** Only columns without defaults go in `required`. + +**Update schemas:** Empty `required` array (all fields optional). + +### Generation structure + +The top-level JSON object has: +- `$schema` — the JSON Schema draft URI +- `type: "object"` with a property per database schema +- Each schema property contains `Tables`, `Views`, `Enums`, `CompositeTypes`, `Functions` +- `$defs` — shared definitions for enums and composite types (referenced via `$ref`) + +Output is `JSON.stringify(schema, null, 2)` — no external formatter needed. + +### Query parameters + +| Parameter | Description | +|---|---| +| `included_schemas` | Comma-separated schema whitelist | +| `excluded_schemas` | Comma-separated schema blacklist | + +### Files + +| Action | File | +|---|---| +| **Create** | `src/server/templates/jsonschema.ts` | +| **Create** | `src/server/routes/generators/jsonschema.ts` | +| Modify | `src/server/routes/index.ts` — register route | +| Modify | `src/server/server.ts` — add CLI case | +| Modify | `package.json` — add `gen:types:jsonschema` script | +| Modify | `test/server/typegen.ts` — add snapshot tests | + +--- + +## 3. Shared modifications summary + +### `src/server/routes/index.ts` + +```ts +import ZodTypeGenRoute from './generators/zod.js' +import JsonSchemaTypeGenRoute from './generators/jsonschema.js' + +// inside registration: +fastify.register(ZodTypeGenRoute, { prefix: '/generators/zod' }) +fastify.register(JsonSchemaTypeGenRoute, { prefix: '/generators/jsonschema' }) +``` + +### `src/server/server.ts` + +Add two new template imports and two new cases in the `getTypeOutput()` switch: + +```ts +case 'zod': + return applyZodTemplate({ ...generatorMeta }) +case 'jsonschema': + return applyJsonSchemaTemplate({ ...generatorMeta }) +``` + +### `package.json` + +```json +"gen:types:zod": "PG_META_GENERATE_TYPES=zod ...", +"gen:types:jsonschema": "PG_META_GENERATE_TYPES=jsonschema ..." +``` + +--- + +## 4. Testing + +Add to `test/server/typegen.ts`: + +- **`test('typegen: zod')`** — Inline snapshot test validating Zod output for the test database (enums, tables, views, composite types, functions, nullable fields, arrays, identity columns, defaults). +- **`test('typegen: jsonschema')`** — Inline snapshot test validating JSON Schema output structure, `$defs`, `$ref` usage, `required` arrays, and nullable handling. + +Both tests use the same `app.inject()` pattern as existing tests. + +--- + +## 5. Implementation order + +| Step | Task | Depends on | +|---|---|---| +| 1 | Create `src/server/templates/zod.ts` | — | +| 2 | Create `src/server/routes/generators/zod.ts` | Step 1 | +| 3 | Create `src/server/templates/jsonschema.ts` | — | +| 4 | Create `src/server/routes/generators/jsonschema.ts` | Step 3 | +| 5 | Modify `src/server/routes/index.ts` (register both routes) | Steps 2, 4 | +| 6 | Modify `src/server/server.ts` (add CLI support) | Steps 1, 3 | +| 7 | Modify `package.json` (add npm scripts) | — | +| 8 | Add tests to `test/server/typegen.ts` | Steps 1–6 | +| 9 | Run `tsc` type check, fix any issues | Steps 1–7 | +| 10 | Run tests, update snapshots | Step 8 | + +--- + +## 6. Design decisions + +- **No new dependencies.** Zod output is plain TypeScript source code — users install `zod` themselves. JSON Schema is a plain JSON object built with standard library. +- **Reuse `GeneratorMetadata` as-is.** Both generators consume the same metadata interface, requiring zero changes to `src/lib/generators.ts`. +- **Follow existing patterns exactly.** Route handler structure, template `apply()` signature, error handling, and test approach all mirror existing generators. +- **PostgREST-aware types.** Since data comes through PostgREST (which serializes timestamps, dates, ranges, etc. as JSON strings), both generators map these types to string representations rather than language-native Date/Range objects. +- **JSON Schema uses `$ref` for reusability.** Enums and composite types are placed in `$defs` and referenced via `$ref`, keeping the output DRY and composable. From 7669401e19e108557c49d73e4bef5b89c2986f3f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 09:19:36 +0000 Subject: [PATCH 02/11] Update plan: add db_driver_type parameter for postgrest vs direct mode Users may connect directly to PostgreSQL (not through PostgREST), so types like timestamp and int8 should map differently depending on the driver. Both generators now support a db_driver_type query parameter with postgrest (default) and direct modes. https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- plan.md | 44 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/plan.md b/plan.md index 11b63d2c..5a600e03 100644 --- a/plan.md +++ b/plan.md @@ -17,7 +17,7 @@ Both generators consume the existing `GeneratorMetadata` type from `src/lib/gene TypeScript source code that imports from `zod` and exports schema objects for every table, view, enum, composite type, and function in the database. -Example output: +Example output (`db_driver_type=postgrest`, the default): ```ts import { z } from "zod"; @@ -29,7 +29,7 @@ export const usersRowSchema = z.object({ name: z.string(), email: z.string(), status: userStatusSchema, - created_at: z.string().datetime(), + created_at: z.string().datetime(), // string in postgrest mode metadata: z.unknown().nullable(), }); @@ -45,19 +45,27 @@ export const usersInsertSchema = z.object({ export const usersUpdateSchema = usersRowSchema.partial(); ``` +With `db_driver_type=direct`, `created_at` would instead be `z.coerce.date()` (since `node-postgres` returns `Date` objects for timestamp columns). + ### Type mapping (`PG_TYPE_TO_ZOD_MAP`) +These generators support two output modes, controlled by a `db_driver_type` query parameter: + +- **`postgrest`** (default) — Types reflect PostgREST serialization behavior (e.g., timestamps and dates are returned as strings, `int8` as string). +- **`direct`** — Types reflect what a direct PostgreSQL driver like `node-postgres` returns (e.g., timestamps as `Date`, `int8` as `string` by default in pg). + +The base type map is shared; only the types that differ between modes are listed below. + +#### Base types (same in both modes) + | PostgreSQL type | Zod validator | |---|---| | `bool` | `z.boolean()` | | `int2`, `int4` | `z.number().int()` | -| `int8` | `z.number().int()` | | `float4`, `float8`, `numeric` | `z.number()` | | `text`, `varchar`, `char`, `name`, `bpchar` | `z.string()` | | `uuid` | `z.string().uuid()` | | `json`, `jsonb` | `z.unknown()` | -| `timestamptz`, `timestamp` | `z.string().datetime()` | -| `date` | `z.string().date()` | | `time`, `timetz` | `z.string()` | | `bytea` | `z.string()` | | `inet`, `cidr` | `z.string()` | @@ -70,6 +78,14 @@ export const usersUpdateSchema = usersRowSchema.partial(); | Composite types | Reference to generated composite schema | | Unknown/unmapped | `z.unknown()` | +#### Types that differ by mode + +| PostgreSQL type | `postgrest` mode | `direct` mode | +|---|---|---| +| `int8` | `z.string()` (PostgREST returns bigints as strings) | `z.string()` (node-postgres also returns bigint as string by default) | +| `timestamptz`, `timestamp` | `z.string().datetime()` | `z.coerce.date()` | +| `date` | `z.string().date()` | `z.coerce.date()` | + **Nullable handling:** Append `.nullable()` when the column is nullable. **Insert schemas:** Columns with defaults or identity columns get `.optional()`. @@ -95,6 +111,7 @@ Use `prettier` for formatting (same as the existing TypeScript generator). |---|---| | `included_schemas` | Comma-separated schema whitelist | | `excluded_schemas` | Comma-separated schema blacklist | +| `db_driver_type` | `postgrest` (default) or `direct` — controls type mappings for driver-sensitive types | ### Files @@ -184,10 +201,14 @@ Example output: ### Type mapping (`PG_TYPE_TO_JSON_SCHEMA_MAP`) +Like the Zod generator, the JSON Schema generator supports a `db_driver_type` query parameter (`postgrest` or `direct`). JSON Schema is language-agnostic, so the differences are smaller — mainly around whether `int8` is represented as a string or integer. + +#### Base types (same in both modes) + | PostgreSQL type | JSON Schema | |---|---| | `bool` | `{ "type": "boolean" }` | -| `int2`, `int4`, `int8` | `{ "type": "integer" }` | +| `int2`, `int4` | `{ "type": "integer" }` | | `float4`, `float8`, `numeric` | `{ "type": "number" }` | | `text`, `varchar`, `char`, `name`, `bpchar` | `{ "type": "string" }` | | `uuid` | `{ "type": "string", "format": "uuid" }` | @@ -205,6 +226,14 @@ Example output: | Composite types | `{ "$ref": "#/$defs/schema.composite_name" }` | | Unknown/unmapped | `{}` | +#### Types that differ by mode + +| PostgreSQL type | `postgrest` mode | `direct` mode | +|---|---|---| +| `int8` | `{ "type": "string" }` (PostgREST serializes bigints as strings) | `{ "type": "integer" }` | + +Note: `timestamp`/`date` use `"format": "date-time"` / `"format": "date"` in both modes. JSON Schema `format` is a hint, not a type constraint — the consumer decides whether to interpret the value as a string or a Date object. + **Nullable handling:** Wrap with `{ "oneOf": [typeSchema, { "type": "null" }] }` **Insert schemas:** Only columns without defaults go in `required`. @@ -227,6 +256,7 @@ Output is `JSON.stringify(schema, null, 2)` — no external formatter needed. |---|---| | `included_schemas` | Comma-separated schema whitelist | | `excluded_schemas` | Comma-separated schema blacklist | +| `db_driver_type` | `postgrest` (default) or `direct` — controls type mappings for driver-sensitive types | ### Files @@ -307,5 +337,5 @@ Both tests use the same `app.inject()` pattern as existing tests. - **No new dependencies.** Zod output is plain TypeScript source code — users install `zod` themselves. JSON Schema is a plain JSON object built with standard library. - **Reuse `GeneratorMetadata` as-is.** Both generators consume the same metadata interface, requiring zero changes to `src/lib/generators.ts`. - **Follow existing patterns exactly.** Route handler structure, template `apply()` signature, error handling, and test approach all mirror existing generators. -- **PostgREST-aware types.** Since data comes through PostgREST (which serializes timestamps, dates, ranges, etc. as JSON strings), both generators map these types to string representations rather than language-native Date/Range objects. +- **Driver-aware type modes.** Both generators accept a `db_driver_type` query parameter (`postgrest` or `direct`). The default is `postgrest` (matching Supabase's primary use case), but users connecting directly to PostgreSQL via `node-postgres` or similar drivers can use `direct` mode to get native type mappings (e.g., `Date` instead of datetime strings for timestamps). This avoids forcing a single serialization assumption on all users. - **JSON Schema uses `$ref` for reusability.** Enums and composite types are placed in `$defs` and referenced via `$ref`, keeping the output DRY and composable. From ef16b1c105a20c323443229b1ecde20e25f9d838 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 09:20:48 +0000 Subject: [PATCH 03/11] Update plan: make direct the default db_driver_type These are general-purpose generators not tied to PostgREST, so direct driver types should be the default. Users going through PostgREST can opt in with db_driver_type=postgrest. https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- plan.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plan.md b/plan.md index 5a600e03..766cf391 100644 --- a/plan.md +++ b/plan.md @@ -17,7 +17,7 @@ Both generators consume the existing `GeneratorMetadata` type from `src/lib/gene TypeScript source code that imports from `zod` and exports schema objects for every table, view, enum, composite type, and function in the database. -Example output (`db_driver_type=postgrest`, the default): +Example output (`db_driver_type=direct`, the default): ```ts import { z } from "zod"; @@ -29,7 +29,7 @@ export const usersRowSchema = z.object({ name: z.string(), email: z.string(), status: userStatusSchema, - created_at: z.string().datetime(), // string in postgrest mode + created_at: z.coerce.date(), // Date object in direct mode metadata: z.unknown().nullable(), }); @@ -38,21 +38,21 @@ export const usersInsertSchema = z.object({ name: z.string(), email: z.string(), status: userStatusSchema.optional(), // has default - created_at: z.string().datetime().optional(), + created_at: z.coerce.date().optional(), metadata: z.unknown().nullable().optional(), }); export const usersUpdateSchema = usersRowSchema.partial(); ``` -With `db_driver_type=direct`, `created_at` would instead be `z.coerce.date()` (since `node-postgres` returns `Date` objects for timestamp columns). +With `db_driver_type=postgrest`, `created_at` would instead be `z.string().datetime()` (since PostgREST serializes timestamps as ISO strings). ### Type mapping (`PG_TYPE_TO_ZOD_MAP`) These generators support two output modes, controlled by a `db_driver_type` query parameter: -- **`postgrest`** (default) — Types reflect PostgREST serialization behavior (e.g., timestamps and dates are returned as strings, `int8` as string). -- **`direct`** — Types reflect what a direct PostgreSQL driver like `node-postgres` returns (e.g., timestamps as `Date`, `int8` as `string` by default in pg). +- **`direct`** (default) — Types reflect what a direct PostgreSQL driver like `node-postgres` returns (e.g., timestamps as `Date`, `int8` as `string` by default in pg). +- **`postgrest`** — Types reflect PostgREST serialization behavior (e.g., timestamps and dates are returned as strings, `int8` as string). The base type map is shared; only the types that differ between modes are listed below. @@ -111,7 +111,7 @@ Use `prettier` for formatting (same as the existing TypeScript generator). |---|---| | `included_schemas` | Comma-separated schema whitelist | | `excluded_schemas` | Comma-separated schema blacklist | -| `db_driver_type` | `postgrest` (default) or `direct` — controls type mappings for driver-sensitive types | +| `db_driver_type` | `direct` (default) or `postgrest` — controls type mappings for driver-sensitive types | ### Files @@ -201,7 +201,7 @@ Example output: ### Type mapping (`PG_TYPE_TO_JSON_SCHEMA_MAP`) -Like the Zod generator, the JSON Schema generator supports a `db_driver_type` query parameter (`postgrest` or `direct`). JSON Schema is language-agnostic, so the differences are smaller — mainly around whether `int8` is represented as a string or integer. +Like the Zod generator, the JSON Schema generator supports a `db_driver_type` query parameter (`direct` or `postgrest`). JSON Schema is language-agnostic, so the differences are smaller — mainly around whether `int8` is represented as a string or integer. #### Base types (same in both modes) @@ -256,7 +256,7 @@ Output is `JSON.stringify(schema, null, 2)` — no external formatter needed. |---|---| | `included_schemas` | Comma-separated schema whitelist | | `excluded_schemas` | Comma-separated schema blacklist | -| `db_driver_type` | `postgrest` (default) or `direct` — controls type mappings for driver-sensitive types | +| `db_driver_type` | `direct` (default) or `postgrest` — controls type mappings for driver-sensitive types | ### Files @@ -337,5 +337,5 @@ Both tests use the same `app.inject()` pattern as existing tests. - **No new dependencies.** Zod output is plain TypeScript source code — users install `zod` themselves. JSON Schema is a plain JSON object built with standard library. - **Reuse `GeneratorMetadata` as-is.** Both generators consume the same metadata interface, requiring zero changes to `src/lib/generators.ts`. - **Follow existing patterns exactly.** Route handler structure, template `apply()` signature, error handling, and test approach all mirror existing generators. -- **Driver-aware type modes.** Both generators accept a `db_driver_type` query parameter (`postgrest` or `direct`). The default is `postgrest` (matching Supabase's primary use case), but users connecting directly to PostgreSQL via `node-postgres` or similar drivers can use `direct` mode to get native type mappings (e.g., `Date` instead of datetime strings for timestamps). This avoids forcing a single serialization assumption on all users. +- **Driver-aware type modes.** Both generators accept a `db_driver_type` query parameter (`direct` or `postgrest`). The default is `direct`, producing types that match what a standard PostgreSQL driver returns (e.g., `Date` for timestamps). Users going through PostgREST can pass `db_driver_type=postgrest` to get string-based types matching PostgREST's JSON serialization. This avoids forcing a single serialization assumption on all users. - **JSON Schema uses `$ref` for reusability.** Enums and composite types are placed in `$defs` and referenced via `$ref`, keeping the output DRY and composable. From 3c709e97056f43e7afbbe842c2193b60850f4c6a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 16:16:23 +0000 Subject: [PATCH 04/11] Update plan: namespace Zod schemas to handle cross-schema name collisions Nest all Zod schemas under per-schema namespace objects (matching the TypeScript generator's Database type structure) so that tables with the same name in different schemas (e.g. public.users and auth.users) don't produce conflicting exports. https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- plan.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/plan.md b/plan.md index 766cf391..7575b0af 100644 --- a/plan.md +++ b/plan.md @@ -17,14 +17,45 @@ Both generators consume the existing `GeneratorMetadata` type from `src/lib/gene TypeScript source code that imports from `zod` and exports schema objects for every table, view, enum, composite type, and function in the database. -Example output (`db_driver_type=direct`, the default): +#### Schema namespacing + +PostgreSQL allows the same table name in different schemas (e.g., `public.users` and `auth.users`). To avoid export name collisions, the Zod generator nests all schemas under a per-schema namespace object — mirroring the structure of the existing TypeScript generator: + +```ts +import { z } from "zod"; + +const publicSchema = { + Tables: { + users: { Row: z.object({ ... }), Insert: z.object({ ... }), Update: z.object({ ... }) }, + }, + Views: { ... }, + Enums: { + user_status: z.enum(["active", "inactive"]), + }, + CompositeTypes: { ... }, + Functions: { ... }, +}; + +const authSchema = { + Tables: { + users: { Row: z.object({ ... }), Insert: z.object({ ... }), Update: z.object({ ... }) }, + }, + // ... +}; + +export const schemas = { public: publicSchema, auth: authSchema }; +``` + +This way, consumers access schemas via `schemas.public.Tables.users.Row` — no ambiguity, and the structure is consistent with how the TypeScript generator organizes its `Database` type. + +#### Full example output (`db_driver_type=direct`, the default): ```ts import { z } from "zod"; -export const userStatusSchema = z.enum(["active", "inactive"]); +const userStatusSchema = z.enum(["active", "inactive"]); -export const usersRowSchema = z.object({ +const usersRowSchema = z.object({ id: z.number().int(), name: z.string(), email: z.string(), @@ -33,7 +64,7 @@ export const usersRowSchema = z.object({ metadata: z.unknown().nullable(), }); -export const usersInsertSchema = z.object({ +const usersInsertSchema = z.object({ id: z.number().int().optional(), // has default (identity) name: z.string(), email: z.string(), @@ -42,7 +73,25 @@ export const usersInsertSchema = z.object({ metadata: z.unknown().nullable().optional(), }); -export const usersUpdateSchema = usersRowSchema.partial(); +const usersUpdateSchema = usersRowSchema.partial(); + +export const schemas = { + public: { + Tables: { + users: { + Row: usersRowSchema, + Insert: usersInsertSchema, + Update: usersUpdateSchema, + }, + }, + Views: {}, + Enums: { + user_status: userStatusSchema, + }, + CompositeTypes: {}, + Functions: {}, + }, +}; ``` With `db_driver_type=postgrest`, `created_at` would instead be `z.string().datetime()` (since PostgREST serializes timestamps as ISO strings). From 4bca416f1a72f7e539f57af48178b7a7be9c8a09 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 16:22:33 +0000 Subject: [PATCH 05/11] Add docs for all type generators Documentation for all 6 generators (TypeScript, Go, Swift, Python, Zod, JSON Schema) covering endpoints, query parameters, CLI usage, output structure, type mappings, and features. https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- docs/go.md | 93 +++++++++++++++++++++++++++ docs/jsonschema.md | 157 +++++++++++++++++++++++++++++++++++++++++++++ docs/python.md | 117 +++++++++++++++++++++++++++++++++ docs/swift.md | 144 +++++++++++++++++++++++++++++++++++++++++ docs/typescript.md | 117 +++++++++++++++++++++++++++++++++ docs/zod.md | 144 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 772 insertions(+) create mode 100644 docs/go.md create mode 100644 docs/jsonschema.md create mode 100644 docs/python.md create mode 100644 docs/swift.md create mode 100644 docs/typescript.md create mode 100644 docs/zod.md diff --git a/docs/go.md b/docs/go.md new file mode 100644 index 00000000..6e84738b --- /dev/null +++ b/docs/go.md @@ -0,0 +1,93 @@ +# Go Type Generator + +Generates Go struct definitions from your PostgreSQL database schema. Produces `Select`, `Insert`, and `Update` structs for each table, with JSON struct tags for serialization. + +## Endpoint + +``` +GET /generators/go +``` + +## Query parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `included_schemas` | string | — | Comma-separated list of schemas to include | +| `excluded_schemas` | string | — | Comma-separated list of schemas to exclude | + +## CLI usage + +```bash +# Using the dev server (npm run dev must be running) +npm run gen:types:go + +# With a custom database +PG_META_DB_URL=postgresql://user:pass@host:5432/db npm run gen:types:go +``` + +## Output structure + +All structs are generated in a single `database` package. Struct names are derived from the schema name and table name in PascalCase, suffixed with the operation type. + +```go +package database + +type PublicUsersSelect struct { + Id int32 `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + CreatedAt string `json:"created_at"` + Metadata interface{} `json:"metadata"` +} + +type PublicUsersInsert struct { + Id int32 `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + CreatedAt string `json:"created_at"` + Metadata interface{} `json:"metadata"` +} + +type PublicUsersUpdate struct { + Id *int32 `json:"id"` + Name *string `json:"name"` + Email *string `json:"email"` + CreatedAt *string `json:"created_at"` + Metadata interface{} `json:"metadata"` +} +``` + +Each table produces three structs: + +- **Select** — Fields returned from a query. +- **Insert** — Fields for inserting a row. +- **Update** — All fields are pointers (nullable) to allow partial updates. + +Views and materialized views produce a `Select` struct only. + +## Type mapping + +| PostgreSQL type | Go type | Nullable Go type | +|---|---|---| +| `bool` | `bool` | `*bool` | +| `int2` | `int16` | `*int16` | +| `int4` | `int32` | `*int32` | +| `int8` | `int64` | `*int64` | +| `float4` | `float32` | `*float32` | +| `float8`, `numeric` | `float64` | `*float64` | +| `text`, `varchar`, `bpchar`, `citext` | `string` | `*string` | +| `uuid` | `string` | `*string` | +| `date`, `time`, `timetz`, `timestamp`, `timestamptz` | `string` | `*string` | +| `bytea` | `[]byte` | `[]byte` | +| `json`, `jsonb` | `interface{}` | `interface{}` | +| `vector` | `string` | `*string` | +| Range types | `string` | `*string` | +| Array types | `[]T` (where T is the element type) | `[]T` | +| Enum types | `string` | `*string` | +| Composite types | `map[string]interface{}` | `map[string]interface{}` | + +## Features + +- Struct fields are aligned for readability +- JSON struct tags match the database column names +- Composite types are supported (mapped to `map[string]interface{}`) diff --git a/docs/jsonschema.md b/docs/jsonschema.md new file mode 100644 index 00000000..dd3f18d1 --- /dev/null +++ b/docs/jsonschema.md @@ -0,0 +1,157 @@ +# JSON Schema Generator + +Generates a [JSON Schema](https://json-schema.org/) (Draft 2020-12) document from your PostgreSQL database schema. The output is a language-agnostic JSON document that can be used for validation, documentation, or code generation in any language. + +**Status:** Planned (not yet implemented) + +## Endpoint + +``` +GET /generators/jsonschema +``` + +## Query parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `included_schemas` | string | — | Comma-separated list of schemas to include | +| `excluded_schemas` | string | — | Comma-separated list of schemas to exclude | +| `db_driver_type` | string | `direct` | `direct` or `postgrest` — controls type mappings for driver-sensitive types (see [Driver modes](#driver-modes)) | + +## CLI usage + +```bash +# Using the dev server (npm run dev must be running) +npm run gen:types:jsonschema + +# With a custom database +PG_META_DB_URL=postgresql://user:pass@host:5432/db npm run gen:types:jsonschema +``` + +## Output structure + +The output is a JSON object with a top-level property per database schema. Each schema contains `Tables`, `Views`, `Enums`, `CompositeTypes`, and `Functions`. Enums and composite types are defined in `$defs` and referenced via `$ref`. + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "public": { + "type": "object", + "properties": { + "Tables": { + "type": "object", + "properties": { + "users": { + "type": "object", + "properties": { + "Row": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "status": { "$ref": "#/$defs/public.user_status" } + }, + "required": ["id", "name", "status"] + }, + "Insert": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "status": { "$ref": "#/$defs/public.user_status" } + }, + "required": ["name"] + }, + "Update": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "status": { "$ref": "#/$defs/public.user_status" } + } + } + } + } + } + }, + "Views": { ... }, + "Enums": { + "type": "object", + "properties": { + "user_status": { "$ref": "#/$defs/public.user_status" } + } + }, + "CompositeTypes": { ... }, + "Functions": { ... } + } + } + }, + "$defs": { + "public.user_status": { + "type": "string", + "enum": ["active", "inactive"] + } + } +} +``` + +Each table produces three sub-schemas: + +- **Row** — All columns in `required`. +- **Insert** — Only columns without defaults in `required`. +- **Update** — No `required` array (all columns optional). + +Views and materialized views produce a Row schema only. + +## Driver modes + +The `db_driver_type` parameter controls how certain types are represented. JSON Schema is language-agnostic, so the differences are minimal. + +### `direct` (default) + +| PostgreSQL type | JSON Schema | +|---|---| +| `int8` | `{ "type": "integer" }` | + +### `postgrest` + +| PostgreSQL type | JSON Schema | +|---|---| +| `int8` | `{ "type": "string" }` (PostgREST serializes bigints as strings) | + +Timestamps and dates use `"format": "date-time"` / `"format": "date"` in both modes. JSON Schema `format` is a hint — the consumer decides how to interpret the value. + +## Type mapping + +| PostgreSQL type | JSON Schema | +|---|---| +| `bool` | `{ "type": "boolean" }` | +| `int2`, `int4` | `{ "type": "integer" }` | +| `float4`, `float8`, `numeric` | `{ "type": "number" }` | +| `text`, `varchar`, `char`, `name`, `bpchar` | `{ "type": "string" }` | +| `uuid` | `{ "type": "string", "format": "uuid" }` | +| `json`, `jsonb` | `{}` (any value) | +| `timestamp`, `timestamptz` | `{ "type": "string", "format": "date-time" }` | +| `date` | `{ "type": "string", "format": "date" }` | +| `time`, `timetz` | `{ "type": "string", "format": "time" }` | +| `bytea` | `{ "type": "string" }` | +| `inet`, `cidr` | `{ "type": "string" }` | +| Range types | `{ "type": "string" }` | +| `void` | `{ "type": "null" }` | +| `record` | `{ "type": "object" }` | +| Array types | `{ "type": "array", "items": innerSchema }` | +| Enum types | `{ "$ref": "#/$defs/schema.enum_name" }` | +| Composite types | `{ "$ref": "#/$defs/schema.composite_name" }` | +| Unknown/unmapped | `{}` | + +Nullable columns are wrapped with `{ "oneOf": [typeSchema, { "type": "null" }] }`. + +## Features + +- Standard JSON Schema Draft 2020-12 output +- `$defs` and `$ref` for reusable enum and composite type definitions +- `required` arrays reflect column defaults and identity columns +- Driver-aware type mappings (`direct` vs `postgrest`) +- No external dependencies — output is plain JSON diff --git a/docs/python.md b/docs/python.md new file mode 100644 index 00000000..9f9fac18 --- /dev/null +++ b/docs/python.md @@ -0,0 +1,117 @@ +# Python Type Generator + +Generates Python type definitions from your PostgreSQL database schema using [Pydantic](https://docs.pydantic.dev/) `BaseModel` classes for row types and `TypedDict` classes for insert and update types. + +## Endpoint + +``` +GET /generators/python +``` + +## Query parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `included_schemas` | string | — | Comma-separated list of schemas to include | +| `excluded_schemas` | string | — | Comma-separated list of schemas to exclude | + +## CLI usage + +```bash +# Using the dev server (npm run dev must be running) +npm run gen:types:python + +# With a custom database +PG_META_DB_URL=postgresql://user:pass@host:5432/db npm run gen:types:python +``` + +## Output structure + +The generator produces Pydantic `BaseModel` classes for row types and `TypedDict` classes for insert/update operations. + +```python +from __future__ import annotations + +import datetime +import uuid +from typing import ( + Annotated, + Any, + List, + Literal, + NotRequired, + Optional, + TypeAlias, + TypedDict, +) + +from pydantic import BaseModel, Field, Json + + +# Enums +UserStatus: TypeAlias = Literal["active", "inactive"] + + +# Tables +class Users(BaseModel): + id: int + name: str + email: str + status: UserStatus + created_at: datetime.datetime + metadata: Any | None + + +class UsersInsert(TypedDict): + id: NotRequired[int] + name: str + email: str + status: NotRequired[UserStatus] + created_at: NotRequired[datetime.datetime] + metadata: NotRequired[Any | None] + + +class UsersUpdate(TypedDict): + id: NotRequired[int] + name: NotRequired[str] + email: NotRequired[str] + status: NotRequired[UserStatus] + created_at: NotRequired[datetime.datetime] + metadata: NotRequired[Any | None] +``` + +Each table produces three types: + +- **BaseModel class** (Row) — A Pydantic model representing a row returned from a query. +- **Insert TypedDict** — Fields with defaults use `NotRequired`. +- **Update TypedDict** — All fields use `NotRequired`. + +Views and materialized views produce a BaseModel class only. + +## Type mapping + +| PostgreSQL type | Python type | +|---|---| +| `bool` | `bool` | +| `int2`, `int4`, `int8` | `int` | +| `float4`, `float8`, `numeric` | `float` | +| `text`, `varchar`, `char`, `bpchar`, `citext`, `name` | `str` | +| `uuid` | `uuid.UUID` | +| `date` | `datetime.date` | +| `time`, `timetz` | `datetime.time` | +| `timestamp`, `timestamptz` | `datetime.datetime` | +| `json`, `jsonb` | `Any` | +| `bytea` | `str` | +| Array types | `List[T]` (where T is the element type) | +| Enum types | `TypeAlias = Literal["val1", "val2"]` | +| Composite types | Generated Pydantic BaseModel class | + +Nullable columns use `Optional[T]` (or `T \| None`). + +## Features + +- Pydantic `BaseModel` for row types with full validation support +- `TypedDict` for insert/update types for compatibility with dict-based APIs +- Field aliases via `Field(alias=...)` when column names conflict with Python reserved words +- Enum types as `Literal` type aliases +- Composite type support as nested BaseModel classes diff --git a/docs/swift.md b/docs/swift.md new file mode 100644 index 00000000..57216167 --- /dev/null +++ b/docs/swift.md @@ -0,0 +1,144 @@ +# Swift Type Generator + +Generates Swift struct and enum definitions from your PostgreSQL database schema. Structs conform to `Codable`, `Hashable`, and `Sendable` protocols, and include `CodingKeys` enums for JSON serialization. + +## Endpoint + +``` +GET /generators/swift +``` + +## Query parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `included_schemas` | string | — | Comma-separated list of schemas to include | +| `excluded_schemas` | string | — | Comma-separated list of schemas to exclude | +| `access_control` | string | `internal` | Swift access control level: `internal`, `public`, `private`, or `package` | + +## CLI usage + +```bash +# Using the dev server (npm run dev must be running) +npm run gen:types:swift + +# With a custom database +PG_META_DB_URL=postgresql://user:pass@host:5432/db npm run gen:types:swift + +# With options +PG_META_GENERATE_TYPES=swift \ +PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL=public \ +node --loader ts-node/esm src/server/server.ts +``` + +## Environment variables + +| Variable | Description | +|---|---| +| `PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL` | Access control level: `internal` (default), `public`, `private`, or `package` | + +## Output structure + +The generator produces structs organized by schema, with each table generating `Select`, `Insert`, and `Update` variants. + +```swift +enum PublicSchema { + // Enums + enum UserStatus: String, Codable, Hashable, Sendable { + case active = "active" + case inactive = "inactive" + } + + // Tables + struct UsersSelect: Codable, Hashable, Sendable { + let id: Int32 + let name: String + let email: String + let status: UserStatus + let createdAt: String + + enum CodingKeys: String, CodingKey { + case id + case name + case email + case status + case createdAt = "created_at" + } + } + + struct UsersInsert: Codable, Hashable, Sendable { + let id: Int32? + let name: String + let email: String + let status: UserStatus? + let createdAt: String? + + enum CodingKeys: String, CodingKey { + case id + case name + case email + case status + case createdAt = "created_at" + } + } + + struct UsersUpdate: Codable, Hashable, Sendable { + let id: Int32? + let name: String? + let email: String? + let status: UserStatus? + let createdAt: String? + + enum CodingKeys: String, CodingKey { + case id + case name + case email + case status + case createdAt = "created_at" + } + } +} +``` + +Each table produces three structs: + +- **Select** — Fields returned from a query. +- **Insert** — Columns with defaults are optional (`?`). +- **Update** — All columns are optional. + +Views and materialized views produce a `Select` struct only. + +Types with identity columns also conform to `Identifiable`. + +## Type mapping + +| PostgreSQL type | Swift type | +|---|---| +| `bool` | `Bool` | +| `int2` | `Int16` | +| `int4` | `Int32` | +| `int8` | `Int64` | +| `float4` | `Float` | +| `float8` | `Double` | +| `numeric`, `decimal` | `Decimal` | +| `uuid` | `UUID` | +| `text`, `varchar`, `bpchar`, `citext` | `String` | +| `date`, `time`, `timetz`, `timestamp`, `timestamptz` | `String` | +| `bytea` | `String` | +| `vector` | `String` | +| `json`, `jsonb` | `AnyJSON` | +| `void` | `Void` | +| `record` | `JSONObject` | +| Array types | `[T]` (where T is the element type) | +| Enum types | Generated Swift enum with `String` raw value | +| Composite types | Reference to generated struct (with `Select` suffix) | + +Nullable columns use Swift optionals (`T?`). + +## Features + +- Protocol conformance: `Codable`, `Hashable`, `Sendable` +- `Identifiable` conformance for tables with identity columns +- `CodingKeys` enum for mapping between Swift property names (camelCase) and database column names (snake_case) +- Configurable access control level +- Formatted with Prettier diff --git a/docs/typescript.md b/docs/typescript.md new file mode 100644 index 00000000..dc407aea --- /dev/null +++ b/docs/typescript.md @@ -0,0 +1,117 @@ +# TypeScript Type Generator + +Generates TypeScript type definitions from your PostgreSQL database schema. The output is designed for use with the [Supabase client libraries](https://github.com/supabase/supabase-js) and produces a single `Database` type containing all schemas, tables, views, functions, enums, and composite types. + +## Endpoint + +``` +GET /generators/typescript +``` + +## Query parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `included_schemas` | string | — | Comma-separated list of schemas to include | +| `excluded_schemas` | string | — | Comma-separated list of schemas to exclude | +| `detect_one_to_one_relationships` | string | `false` | Set to `true` to detect and annotate one-to-one relationships | +| `postgrest_version` | string | — | PostgREST version string for version-specific type output | + +## CLI usage + +```bash +# Using the dev server (npm run dev must be running) +npm run gen:types:typescript + +# With a custom database +PG_META_DB_URL=postgresql://user:pass@host:5432/db npm run gen:types:typescript + +# With options +PG_META_GENERATE_TYPES=typescript \ +PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=public,auth \ +PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS=true \ +node --loader ts-node/esm src/server/server.ts +``` + +## Environment variables + +| Variable | Description | +|---|---| +| `PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS` | Comma-separated list of schemas to include | +| `PG_META_GENERATE_TYPES_DEFAULT_SCHEMA` | Default schema (defaults to `public`) | +| `PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS` | Set to `true` to detect one-to-one relationships | +| `PG_META_POSTGREST_VERSION` | PostgREST version string | + +## Output structure + +The generator produces a single `Database` type with nested schemas: + +```ts +export type Database = { + public: { + Tables: { + users: { + Row: { + id: number + name: string + email: string + created_at: string + } + Insert: { + id?: number // optional (has default) + name: string + email: string + created_at?: string + } + Update: { + id?: number + name?: string + email?: string + created_at?: string + } + Relationships: [...] + } + } + Views: { ... } + Functions: { ... } + Enums: { ... } + CompositeTypes: { ... } + } +} +``` + +Each table produces three sub-types: + +- **Row** — The shape of a row returned from a `SELECT`. +- **Insert** — The shape accepted by `INSERT`. Columns with defaults or identity columns are optional. +- **Update** — The shape accepted by `UPDATE`. All columns are optional. + +Views and materialized views produce a `Row` type only (unless the view is updatable). + +## Type mapping + +| PostgreSQL type | TypeScript type | +|---|---| +| `bool` | `boolean` | +| `int2`, `int4`, `float4`, `float8`, `numeric` | `number` | +| `int8` | `number` | +| `text`, `varchar`, `char`, `bpchar`, `citext`, `name` | `string` | +| `uuid` | `string` | +| `date`, `time`, `timetz`, `timestamp`, `timestamptz` | `string` | +| `json`, `jsonb` | `Json` | +| `bytea` | `string` | +| `void` | `undefined` | +| `record` | `Record` | +| Array types | `T[]` (where T is the element type) | +| Enum types | Union of string literals | +| Composite types | Object type with typed fields | + +Nullable columns produce `T \| null`. + +## Features + +- Helper utility types: `Tables`, `TablesInsert`, `TablesUpdate`, `Enums`, `CompositeTypes` +- Constants object with enum values for runtime use +- One-to-one relationship detection (opt-in) +- PostgREST version-aware output +- Formatted with Prettier diff --git a/docs/zod.md b/docs/zod.md new file mode 100644 index 00000000..a61ed593 --- /dev/null +++ b/docs/zod.md @@ -0,0 +1,144 @@ +# Zod Type Generator + +Generates [Zod](https://zod.dev/) schema definitions from your PostgreSQL database schema. Unlike the [TypeScript generator](typescript.md) which produces static types only, the Zod generator produces runtime validation schemas that can parse and validate data at runtime. + +**Status:** Planned (not yet implemented) + +## Endpoint + +``` +GET /generators/zod +``` + +## Query parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `included_schemas` | string | — | Comma-separated list of schemas to include | +| `excluded_schemas` | string | — | Comma-separated list of schemas to exclude | +| `db_driver_type` | string | `direct` | `direct` or `postgrest` — controls type mappings for driver-sensitive types (see [Driver modes](#driver-modes)) | + +## CLI usage + +```bash +# Using the dev server (npm run dev must be running) +npm run gen:types:zod + +# With a custom database +PG_META_DB_URL=postgresql://user:pass@host:5432/db npm run gen:types:zod +``` + +## Output structure + +Schemas are nested under per-schema namespace objects to avoid name collisions when the same table name exists in multiple schemas (e.g., `public.users` and `auth.users`). This mirrors the structure of the TypeScript generator's `Database` type. + +```ts +import { z } from "zod"; + +const userStatusSchema = z.enum(["active", "inactive"]); + +const usersRowSchema = z.object({ + id: z.number().int(), + name: z.string(), + email: z.string(), + status: userStatusSchema, + created_at: z.coerce.date(), + metadata: z.unknown().nullable(), +}); + +const usersInsertSchema = z.object({ + id: z.number().int().optional(), + name: z.string(), + email: z.string(), + status: userStatusSchema.optional(), + created_at: z.coerce.date().optional(), + metadata: z.unknown().nullable().optional(), +}); + +const usersUpdateSchema = usersRowSchema.partial(); + +export const schemas = { + public: { + Tables: { + users: { + Row: usersRowSchema, + Insert: usersInsertSchema, + Update: usersUpdateSchema, + }, + }, + Views: {}, + Enums: { + user_status: userStatusSchema, + }, + CompositeTypes: {}, + Functions: {}, + }, +}; +``` + +Access schemas via `schemas.public.Tables.users.Row`. + +Each table produces three schemas: + +- **Row** — Validates data returned from a `SELECT`. +- **Insert** — Columns with defaults or identity columns are `.optional()`. +- **Update** — All fields are `.partial()` (all optional). + +Views and materialized views produce a Row schema only (unless the view is updatable). + +## Driver modes + +The `db_driver_type` parameter controls how driver-sensitive types are mapped. This matters because PostgREST and direct PostgreSQL drivers return different runtime types for certain columns. + +### `direct` (default) + +Types reflect what a direct PostgreSQL driver like `node-postgres` returns: + +| PostgreSQL type | Zod validator | +|---|---| +| `timestamp`, `timestamptz` | `z.coerce.date()` | +| `date` | `z.coerce.date()` | +| `int8` | `z.string()` (node-postgres returns bigint as string by default) | + +### `postgrest` + +Types reflect PostgREST JSON serialization: + +| PostgreSQL type | Zod validator | +|---|---| +| `timestamp`, `timestamptz` | `z.string().datetime()` | +| `date` | `z.string().date()` | +| `int8` | `z.string()` | + +## Type mapping + +| PostgreSQL type | Zod validator | +|---|---| +| `bool` | `z.boolean()` | +| `int2`, `int4` | `z.number().int()` | +| `float4`, `float8`, `numeric` | `z.number()` | +| `text`, `varchar`, `char`, `name`, `bpchar` | `z.string()` | +| `uuid` | `z.string().uuid()` | +| `json`, `jsonb` | `z.unknown()` | +| `time`, `timetz` | `z.string()` | +| `bytea` | `z.string()` | +| `inet`, `cidr` | `z.string()` | +| `macaddr`, `macaddr8` | `z.string()` | +| Range types | `z.string()` | +| `void` | `z.void()` | +| `record` | `z.record(z.unknown())` | +| Array types | `z.array(innerSchema)` | +| Enum types | `z.enum(["val1", "val2"])` | +| Composite types | `z.object({ ... })` | +| Unknown/unmapped | `z.unknown()` | + +Nullable columns append `.nullable()`. Insert schemas append `.optional()` for columns with defaults. + +## Features + +- Runtime validation schemas (not just static types) +- Schema namespacing to handle cross-schema table name collisions +- Driver-aware type mappings (`direct` vs `postgrest`) +- Enum, composite type, and array support +- Formatted with Prettier +- No runtime dependencies in this package — consumers install `zod` themselves From 070f8ebba2da697d6c1e8773252b52d486d2772f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 16:25:38 +0000 Subject: [PATCH 06/11] Rename zod generator to typescript-zod for language-grouped naming Use {language}-{format} naming convention (e.g. typescript-zod) so generators are grouped by language. This makes it extensible for future variants like python-pydantic, typescript-io-ts, etc. Renames: docs, route prefix, template file, CLI command, npm script. https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- docs/{zod.md => typescript-zod.md} | 10 +++++----- plan.md | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) rename docs/{zod.md => typescript-zod.md} (94%) diff --git a/docs/zod.md b/docs/typescript-zod.md similarity index 94% rename from docs/zod.md rename to docs/typescript-zod.md index a61ed593..29c3fb90 100644 --- a/docs/zod.md +++ b/docs/typescript-zod.md @@ -1,13 +1,13 @@ -# Zod Type Generator +# TypeScript Zod Type Generator -Generates [Zod](https://zod.dev/) schema definitions from your PostgreSQL database schema. Unlike the [TypeScript generator](typescript.md) which produces static types only, the Zod generator produces runtime validation schemas that can parse and validate data at runtime. +Generates [Zod](https://zod.dev/) schema definitions from your PostgreSQL database schema. Unlike the [TypeScript generator](typescript.md) which produces static types only, this generator produces runtime validation schemas that can parse and validate data at runtime. **Status:** Planned (not yet implemented) ## Endpoint ``` -GET /generators/zod +GET /generators/typescript-zod ``` ## Query parameters @@ -22,10 +22,10 @@ GET /generators/zod ```bash # Using the dev server (npm run dev must be running) -npm run gen:types:zod +npm run gen:types:typescript-zod # With a custom database -PG_META_DB_URL=postgresql://user:pass@host:5432/db npm run gen:types:zod +PG_META_DB_URL=postgresql://user:pass@host:5432/db npm run gen:types:typescript-zod ``` ## Output structure diff --git a/plan.md b/plan.md index 7575b0af..5d587ff4 100644 --- a/plan.md +++ b/plan.md @@ -166,8 +166,8 @@ Use `prettier` for formatting (same as the existing TypeScript generator). | Action | File | |---|---| -| **Create** | `src/server/templates/zod.ts` | -| **Create** | `src/server/routes/generators/zod.ts` | +| **Create** | `src/server/templates/typescript-zod.ts` | +| **Create** | `src/server/routes/generators/typescript-zod.ts` | | Modify | `src/server/routes/index.ts` — register route | | Modify | `src/server/server.ts` — add CLI case | | Modify | `package.json` — add `gen:types:zod` script | @@ -325,11 +325,11 @@ Output is `JSON.stringify(schema, null, 2)` — no external formatter needed. ### `src/server/routes/index.ts` ```ts -import ZodTypeGenRoute from './generators/zod.js' +import TypeScriptZodTypeGenRoute from './generators/typescript-zod.js' import JsonSchemaTypeGenRoute from './generators/jsonschema.js' // inside registration: -fastify.register(ZodTypeGenRoute, { prefix: '/generators/zod' }) +fastify.register(TypeScriptZodTypeGenRoute, { prefix: '/generators/typescript-zod' }) fastify.register(JsonSchemaTypeGenRoute, { prefix: '/generators/jsonschema' }) ``` @@ -338,8 +338,8 @@ fastify.register(JsonSchemaTypeGenRoute, { prefix: '/generators/jsonschema' }) Add two new template imports and two new cases in the `getTypeOutput()` switch: ```ts -case 'zod': - return applyZodTemplate({ ...generatorMeta }) +case 'typescript-zod': + return applyTypeScriptZodTemplate({ ...generatorMeta }) case 'jsonschema': return applyJsonSchemaTemplate({ ...generatorMeta }) ``` @@ -347,7 +347,7 @@ case 'jsonschema': ### `package.json` ```json -"gen:types:zod": "PG_META_GENERATE_TYPES=zod ...", +"gen:types:typescript-zod": "PG_META_GENERATE_TYPES=typescript-zod ...", "gen:types:jsonschema": "PG_META_GENERATE_TYPES=jsonschema ..." ``` @@ -357,7 +357,7 @@ case 'jsonschema': Add to `test/server/typegen.ts`: -- **`test('typegen: zod')`** — Inline snapshot test validating Zod output for the test database (enums, tables, views, composite types, functions, nullable fields, arrays, identity columns, defaults). +- **`test('typegen: typescript-zod')`** — Inline snapshot test validating Zod output for the test database (enums, tables, views, composite types, functions, nullable fields, arrays, identity columns, defaults). - **`test('typegen: jsonschema')`** — Inline snapshot test validating JSON Schema output structure, `$defs`, `$ref` usage, `required` arrays, and nullable handling. Both tests use the same `app.inject()` pattern as existing tests. @@ -368,8 +368,8 @@ Both tests use the same `app.inject()` pattern as existing tests. | Step | Task | Depends on | |---|---|---| -| 1 | Create `src/server/templates/zod.ts` | — | -| 2 | Create `src/server/routes/generators/zod.ts` | Step 1 | +| 1 | Create `src/server/templates/typescript-zod.ts` | — | +| 2 | Create `src/server/routes/generators/typescript-zod.ts` | Step 1 | | 3 | Create `src/server/templates/jsonschema.ts` | — | | 4 | Create `src/server/routes/generators/jsonschema.ts` | Step 3 | | 5 | Modify `src/server/routes/index.ts` (register both routes) | Steps 2, 4 | From 7eea5c71dba42873eb50dab760c8b9c89ff345c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 16:30:06 +0000 Subject: [PATCH 07/11] Clarify TypeScript generator docs are framework-agnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The types work with Supabase client libraries but are not tied to them — they can be used with any TypeScript project. https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- docs/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/typescript.md b/docs/typescript.md index dc407aea..b10c69d3 100644 --- a/docs/typescript.md +++ b/docs/typescript.md @@ -1,6 +1,6 @@ # TypeScript Type Generator -Generates TypeScript type definitions from your PostgreSQL database schema. The output is designed for use with the [Supabase client libraries](https://github.com/supabase/supabase-js) and produces a single `Database` type containing all schemas, tables, views, functions, enums, and composite types. +Generates TypeScript type definitions from your PostgreSQL database schema. The output produces a single `Database` type containing all schemas, tables, views, functions, enums, and composite types. While designed to work with the [Supabase client libraries](https://github.com/supabase/supabase-js), the generated types are framework-agnostic and can be used with any TypeScript project that interacts with PostgreSQL. ## Endpoint From 279ad49ec3760ec151964eb6869343dd21f4bc26 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 16:36:11 +0000 Subject: [PATCH 08/11] Add usage examples to TypeScript Zod generator docs Show practical examples: validating query results, insert data, API request bodies, and deriving static types with z.infer. https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- docs/typescript-zod.md | 68 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/docs/typescript-zod.md b/docs/typescript-zod.md index 29c3fb90..b5450f67 100644 --- a/docs/typescript-zod.md +++ b/docs/typescript-zod.md @@ -86,6 +86,74 @@ Each table produces three schemas: Views and materialized views produce a Row schema only (unless the view is updatable). +## Usage + +Save the generated output to a file (e.g., `db-schemas.ts`) in your project, then import schemas to validate data at runtime. + +### Validating query results + +```ts +import { schemas } from "./db-schemas"; + +const { Row: UserRow } = schemas.public.Tables.users; + +// Parse validates and returns typed data — throws on invalid input +const user = UserRow.parse(row); +// ^ typed as { id: number; name: string; email: string; ... } + +// safeParse returns a result object instead of throwing +const result = UserRow.safeParse(row); +if (result.success) { + console.log(result.data.name); +} else { + console.error(result.error.issues); +} +``` + +### Validating data before inserting + +```ts +import { schemas } from "./db-schemas"; + +const { Insert: UserInsert } = schemas.public.Tables.users; + +// Validate user input before sending to the database +const newUser = UserInsert.parse({ + name: "Alice", + email: "alice@example.com", + // id, status, created_at are optional (have defaults) +}); +``` + +### Validating API request bodies + +```ts +import { schemas } from "./db-schemas"; + +const { Update: UserUpdate } = schemas.public.Tables.users; + +app.patch("/users/:id", (req, res) => { + // All fields are optional for updates — rejects unknown fields + const body = UserUpdate.parse(req.body); + // ...update the database with validated data +}); +``` + +### Deriving static types from schemas + +You don't need both the TypeScript generator and the Zod generator. Zod schemas can produce static types directly: + +```ts +import { z } from "zod"; +import { schemas } from "./db-schemas"; + +type UserRow = z.infer; +// ^ { id: number; name: string; email: string; status: ...; ... } + +type UserInsert = z.infer; +// ^ { name: string; email: string; id?: number; status?: ...; ... } +``` + ## Driver modes The `db_driver_type` parameter controls how driver-sensitive types are mapped. This matters because PostgREST and direct PostgreSQL drivers return different runtime types for certain columns. From 7bae18a1d9feec2299fe757d6a989c12b04d8d3d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 16:41:18 +0000 Subject: [PATCH 09/11] Add usage sections to all generator docs Each doc now has a Usage section right after the intro showing practical code examples: reading/validating rows, inserts, updates, and language-specific patterns (Supabase client, FastAPI, Ajv, etc). https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- docs/go.md | 47 ++++++++++++++ docs/jsonschema.md | 60 ++++++++++++++++++ docs/python.md | 69 +++++++++++++++++++++ docs/swift.md | 59 ++++++++++++++++++ docs/typescript-zod.md | 136 ++++++++++++++++++++--------------------- docs/typescript.md | 61 ++++++++++++++++++ 6 files changed, 364 insertions(+), 68 deletions(-) diff --git a/docs/go.md b/docs/go.md index 6e84738b..a30a8656 100644 --- a/docs/go.md +++ b/docs/go.md @@ -2,6 +2,53 @@ Generates Go struct definitions from your PostgreSQL database schema. Produces `Select`, `Insert`, and `Update` structs for each table, with JSON struct tags for serialization. +## Usage + +Save the generated output to a file (e.g., `database.go`) in your project, then use the structs in your code. + +### Reading rows + +```go +package main + +import ( + "database/sql" + "encoding/json" + + db "myproject/database" +) + +func GetUser(row *sql.Row) (db.PublicUsersSelect, error) { + var user db.PublicUsersSelect + err := row.Scan(&user.Id, &user.Name, &user.Email, &user.CreatedAt, &user.Metadata) + return user, err +} +``` + +### Inserting rows + +```go +newUser := db.PublicUsersInsert{ + Name: "Alice", + Email: "alice@example.com", +} + +// Marshal to JSON for an API request +body, _ := json.Marshal(newUser) +``` + +### Partial updates + +```go +// Update structs use pointers so you can distinguish +// between "not set" (nil) and "set to zero value" +name := "Bob" +update := db.PublicUsersUpdate{ + Name: &name, + // other fields are nil — won't be included +} +``` + ## Endpoint ``` diff --git a/docs/jsonschema.md b/docs/jsonschema.md index dd3f18d1..5eb1ee56 100644 --- a/docs/jsonschema.md +++ b/docs/jsonschema.md @@ -4,6 +4,66 @@ Generates a [JSON Schema](https://json-schema.org/) (Draft 2020-12) document fro **Status:** Planned (not yet implemented) +## Usage + +Save the generated output to a file (e.g., `database-schema.json`) in your project. JSON Schema is language-agnostic, so it can be used with any JSON Schema validator in any language. + +### Validating data in JavaScript/TypeScript + +```ts +import Ajv from "ajv"; +import schema from "./database-schema.json"; + +const ajv = new Ajv(); +const validate = ajv.compile( + schema.properties.public.properties.Tables.properties.users.properties.Row +); + +const valid = validate(row); +if (!valid) { + console.error(validate.errors); +} +``` + +### Validating data in Python + +```python +import json +from jsonschema import validate + +with open("database-schema.json") as f: + schema = json.load(f) + +row_schema = schema["properties"]["public"]["properties"]["Tables"] \ + ["properties"]["users"]["properties"]["Row"] + +validate(instance=row_data, schema=row_schema) +``` + +### Generating types from JSON Schema + +JSON Schema can be used as an input to code generators for languages that don't have a dedicated generator: + +```bash +# Generate TypeScript types from JSON Schema +npx json-schema-to-typescript database-schema.json > types.ts + +# Generate Rust types +typify database-schema.json > types.rs +``` + +### API documentation + +The schema can be embedded in OpenAPI specs or used to document your database structure: + +```yaml +# openapi.yaml +components: + schemas: + User: + $ref: "database-schema.json#/properties/public/properties/Tables/properties/users/properties/Row" +``` + ## Endpoint ``` diff --git a/docs/python.md b/docs/python.md index 9f9fac18..cb9469e4 100644 --- a/docs/python.md +++ b/docs/python.md @@ -2,6 +2,75 @@ Generates Python type definitions from your PostgreSQL database schema using [Pydantic](https://docs.pydantic.dev/) `BaseModel` classes for row types and `TypedDict` classes for insert and update types. +## Usage + +Save the generated output to a file (e.g., `database_types.py`) in your project, then import the types. + +### Validating query results + +```python +from database_types import Users + +# Pydantic BaseModel validates and parses data +user = Users.model_validate(row_dict) +print(user.name) # str +print(user.created_at) # datetime.datetime +``` + +### Typing inserts + +```python +from database_types import UsersInsert + +# TypedDict gives you type checking without runtime validation +new_user: UsersInsert = { + "name": "Alice", + "email": "alice@example.com", + # id, status, created_at are NotRequired — they have defaults +} +``` + +### Typing updates + +```python +from database_types import UsersUpdate + +# All fields are NotRequired for partial updates +update: UsersUpdate = { + "name": "Bob", +} +``` + +### Using with FastAPI + +```python +from fastapi import FastAPI +from database_types import Users, UsersInsert + +app = FastAPI() + +@app.get("/users/{user_id}", response_model=Users) +async def get_user(user_id: int): + row = await db.fetch_one("SELECT * FROM users WHERE id = $1", user_id) + return Users.model_validate(dict(row)) + +@app.post("/users", response_model=Users) +async def create_user(user: UsersInsert): + # Pydantic validates the request body automatically + ... +``` + +### Using enums + +```python +from database_types import UserStatus + +# UserStatus is a Literal type alias +def check_status(status: UserStatus): + if status == "active": + ... +``` + ## Endpoint ``` diff --git a/docs/swift.md b/docs/swift.md index 57216167..18f89fdd 100644 --- a/docs/swift.md +++ b/docs/swift.md @@ -2,6 +2,65 @@ Generates Swift struct and enum definitions from your PostgreSQL database schema. Structs conform to `Codable`, `Hashable`, and `Sendable` protocols, and include `CodingKeys` enums for JSON serialization. +## Usage + +Save the generated output to a file (e.g., `Database.swift`) in your project, then use the structs in your code. + +### Decoding query results + +```swift +import Foundation + +let data = // ... JSON data from your API +let decoder = JSONDecoder() +let users = try decoder.decode([PublicSchema.UsersSelect].self, from: data) + +for user in users { + print(user.name) // String + print(user.createdAt) // String (mapped from created_at via CodingKeys) +} +``` + +### Encoding data for inserts + +```swift +let newUser = PublicSchema.UsersInsert( + id: nil, // optional — has default + name: "Alice", + email: "alice@example.com", + status: nil, // optional — has default + createdAt: nil // optional — has default +) + +let encoder = JSONEncoder() +let body = try encoder.encode(newUser) +``` + +### Partial updates + +```swift +let update = PublicSchema.UsersUpdate( + id: nil, + name: "Bob", // only update the name + email: nil, + status: nil, + createdAt: nil +) +``` + +### Using enums + +```swift +let status: PublicSchema.UserStatus = .active + +switch status { +case .active: + print("User is active") +case .inactive: + print("User is inactive") +} +``` + ## Endpoint ``` diff --git a/docs/typescript-zod.md b/docs/typescript-zod.md index b5450f67..068b5165 100644 --- a/docs/typescript-zod.md +++ b/docs/typescript-zod.md @@ -4,6 +4,74 @@ Generates [Zod](https://zod.dev/) schema definitions from your PostgreSQL databa **Status:** Planned (not yet implemented) +## Usage + +Save the generated output to a file (e.g., `db-schemas.ts`) in your project, then import schemas to validate data at runtime. + +### Validating query results + +```ts +import { schemas } from "./db-schemas"; + +const { Row: UserRow } = schemas.public.Tables.users; + +// Parse validates and returns typed data — throws on invalid input +const user = UserRow.parse(row); +// ^ typed as { id: number; name: string; email: string; ... } + +// safeParse returns a result object instead of throwing +const result = UserRow.safeParse(row); +if (result.success) { + console.log(result.data.name); +} else { + console.error(result.error.issues); +} +``` + +### Validating data before inserting + +```ts +import { schemas } from "./db-schemas"; + +const { Insert: UserInsert } = schemas.public.Tables.users; + +// Validate user input before sending to the database +const newUser = UserInsert.parse({ + name: "Alice", + email: "alice@example.com", + // id, status, created_at are optional (have defaults) +}); +``` + +### Validating API request bodies + +```ts +import { schemas } from "./db-schemas"; + +const { Update: UserUpdate } = schemas.public.Tables.users; + +app.patch("/users/:id", (req, res) => { + // All fields are optional for updates — rejects unknown fields + const body = UserUpdate.parse(req.body); + // ...update the database with validated data +}); +``` + +### Deriving static types from schemas + +You don't need both the TypeScript generator and the Zod generator. Zod schemas can produce static types directly: + +```ts +import { z } from "zod"; +import { schemas } from "./db-schemas"; + +type UserRow = z.infer; +// ^ { id: number; name: string; email: string; status: ...; ... } + +type UserInsert = z.infer; +// ^ { name: string; email: string; id?: number; status?: ...; ... } +``` + ## Endpoint ``` @@ -86,74 +154,6 @@ Each table produces three schemas: Views and materialized views produce a Row schema only (unless the view is updatable). -## Usage - -Save the generated output to a file (e.g., `db-schemas.ts`) in your project, then import schemas to validate data at runtime. - -### Validating query results - -```ts -import { schemas } from "./db-schemas"; - -const { Row: UserRow } = schemas.public.Tables.users; - -// Parse validates and returns typed data — throws on invalid input -const user = UserRow.parse(row); -// ^ typed as { id: number; name: string; email: string; ... } - -// safeParse returns a result object instead of throwing -const result = UserRow.safeParse(row); -if (result.success) { - console.log(result.data.name); -} else { - console.error(result.error.issues); -} -``` - -### Validating data before inserting - -```ts -import { schemas } from "./db-schemas"; - -const { Insert: UserInsert } = schemas.public.Tables.users; - -// Validate user input before sending to the database -const newUser = UserInsert.parse({ - name: "Alice", - email: "alice@example.com", - // id, status, created_at are optional (have defaults) -}); -``` - -### Validating API request bodies - -```ts -import { schemas } from "./db-schemas"; - -const { Update: UserUpdate } = schemas.public.Tables.users; - -app.patch("/users/:id", (req, res) => { - // All fields are optional for updates — rejects unknown fields - const body = UserUpdate.parse(req.body); - // ...update the database with validated data -}); -``` - -### Deriving static types from schemas - -You don't need both the TypeScript generator and the Zod generator. Zod schemas can produce static types directly: - -```ts -import { z } from "zod"; -import { schemas } from "./db-schemas"; - -type UserRow = z.infer; -// ^ { id: number; name: string; email: string; status: ...; ... } - -type UserInsert = z.infer; -// ^ { name: string; email: string; id?: number; status?: ...; ... } -``` - ## Driver modes The `db_driver_type` parameter controls how driver-sensitive types are mapped. This matters because PostgREST and direct PostgreSQL drivers return different runtime types for certain columns. diff --git a/docs/typescript.md b/docs/typescript.md index b10c69d3..42981d11 100644 --- a/docs/typescript.md +++ b/docs/typescript.md @@ -2,6 +2,67 @@ Generates TypeScript type definitions from your PostgreSQL database schema. The output produces a single `Database` type containing all schemas, tables, views, functions, enums, and composite types. While designed to work with the [Supabase client libraries](https://github.com/supabase/supabase-js), the generated types are framework-agnostic and can be used with any TypeScript project that interacts with PostgreSQL. +## Usage + +Save the generated output to a file (e.g., `database.types.ts`) in your project, then import the types. + +### Typing database queries + +```ts +import { Database } from "./database.types"; + +type User = Database["public"]["Tables"]["users"]["Row"]; + +// Use with any PostgreSQL client +function getUser(row: unknown): User { + return row as User; +} +``` + +### Typing inserts and updates + +```ts +import { Database } from "./database.types"; + +type NewUser = Database["public"]["Tables"]["users"]["Insert"]; +type UserUpdate = Database["public"]["Tables"]["users"]["Update"]; + +function createUser(user: NewUser) { + // id, created_at are optional — they have defaults + // name, email are required +} + +function updateUser(id: number, changes: UserUpdate) { + // All fields are optional +} +``` + +### Using helper utility types + +The generated output includes shorthand helper types: + +```ts +import { Tables, TablesInsert, TablesUpdate, Enums } from "./database.types"; + +type User = Tables<"users">; +type NewUser = TablesInsert<"users">; +type UserUpdate = TablesUpdate<"users">; +type UserStatus = Enums<"user_status">; +``` + +### With Supabase client + +```ts +import { createClient } from "@supabase/supabase-js"; +import { Database } from "./database.types"; + +const supabase = createClient(url, key); + +// Queries are now fully typed +const { data } = await supabase.from("users").select("*"); +// ^ typed as Database["public"]["Tables"]["users"]["Row"][] +``` + ## Endpoint ``` From 917d6b136adde78b5f065f5edf0df35c9ae44a23 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 17:06:18 +0000 Subject: [PATCH 10/11] Use accurate file paths in generator doc examples Change import paths to use conventional project-relative paths (e.g. database/types.ts, database/zod.ts, database/schema.json) instead of vague names like db-schemas or database.types. https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- docs/go.md | 2 +- docs/jsonschema.md | 12 ++++++------ docs/python.md | 12 ++++++------ docs/swift.md | 2 +- docs/typescript-zod.md | 10 +++++----- docs/typescript.md | 10 +++++----- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/go.md b/docs/go.md index a30a8656..934b6e5b 100644 --- a/docs/go.md +++ b/docs/go.md @@ -4,7 +4,7 @@ Generates Go struct definitions from your PostgreSQL database schema. Produces ` ## Usage -Save the generated output to a file (e.g., `database.go`) in your project, then use the structs in your code. +Save the generated output to a file (e.g., `database/types.go`) in your project, then import the package and use the structs in your code. ### Reading rows diff --git a/docs/jsonschema.md b/docs/jsonschema.md index 5eb1ee56..a881c9dc 100644 --- a/docs/jsonschema.md +++ b/docs/jsonschema.md @@ -6,13 +6,13 @@ Generates a [JSON Schema](https://json-schema.org/) (Draft 2020-12) document fro ## Usage -Save the generated output to a file (e.g., `database-schema.json`) in your project. JSON Schema is language-agnostic, so it can be used with any JSON Schema validator in any language. +Save the generated output to a file (e.g., `database/schema.json`) in your project. JSON Schema is language-agnostic, so it can be used with any JSON Schema validator in any language. ### Validating data in JavaScript/TypeScript ```ts import Ajv from "ajv"; -import schema from "./database-schema.json"; +import schema from "./database/schema.json"; const ajv = new Ajv(); const validate = ajv.compile( @@ -31,7 +31,7 @@ if (!valid) { import json from jsonschema import validate -with open("database-schema.json") as f: +with open("database/schema.json") as f: schema = json.load(f) row_schema = schema["properties"]["public"]["properties"]["Tables"] \ @@ -46,10 +46,10 @@ JSON Schema can be used as an input to code generators for languages that don't ```bash # Generate TypeScript types from JSON Schema -npx json-schema-to-typescript database-schema.json > types.ts +npx json-schema-to-typescript database/schema.json > types.ts # Generate Rust types -typify database-schema.json > types.rs +typify database/schema.json > types.rs ``` ### API documentation @@ -61,7 +61,7 @@ The schema can be embedded in OpenAPI specs or used to document your database st components: schemas: User: - $ref: "database-schema.json#/properties/public/properties/Tables/properties/users/properties/Row" + $ref: "database/schema.json#/properties/public/properties/Tables/properties/users/properties/Row" ``` ## Endpoint diff --git a/docs/python.md b/docs/python.md index cb9469e4..0b3f366f 100644 --- a/docs/python.md +++ b/docs/python.md @@ -4,12 +4,12 @@ Generates Python type definitions from your PostgreSQL database schema using [Py ## Usage -Save the generated output to a file (e.g., `database_types.py`) in your project, then import the types. +Save the generated output to a file (e.g., `database/types.py`) in your project, then import the types. ### Validating query results ```python -from database_types import Users +from database.types import Users # Pydantic BaseModel validates and parses data user = Users.model_validate(row_dict) @@ -20,7 +20,7 @@ print(user.created_at) # datetime.datetime ### Typing inserts ```python -from database_types import UsersInsert +from database.types import UsersInsert # TypedDict gives you type checking without runtime validation new_user: UsersInsert = { @@ -33,7 +33,7 @@ new_user: UsersInsert = { ### Typing updates ```python -from database_types import UsersUpdate +from database.types import UsersUpdate # All fields are NotRequired for partial updates update: UsersUpdate = { @@ -45,7 +45,7 @@ update: UsersUpdate = { ```python from fastapi import FastAPI -from database_types import Users, UsersInsert +from database.types import Users, UsersInsert app = FastAPI() @@ -63,7 +63,7 @@ async def create_user(user: UsersInsert): ### Using enums ```python -from database_types import UserStatus +from database.types import UserStatus # UserStatus is a Literal type alias def check_status(status: UserStatus): diff --git a/docs/swift.md b/docs/swift.md index 18f89fdd..645958d4 100644 --- a/docs/swift.md +++ b/docs/swift.md @@ -4,7 +4,7 @@ Generates Swift struct and enum definitions from your PostgreSQL database schema ## Usage -Save the generated output to a file (e.g., `Database.swift`) in your project, then use the structs in your code. +Save the generated output to a file (e.g., `Database/Types.swift`) in your project, then use the structs in your code. ### Decoding query results diff --git a/docs/typescript-zod.md b/docs/typescript-zod.md index 068b5165..d3fdfb41 100644 --- a/docs/typescript-zod.md +++ b/docs/typescript-zod.md @@ -6,12 +6,12 @@ Generates [Zod](https://zod.dev/) schema definitions from your PostgreSQL databa ## Usage -Save the generated output to a file (e.g., `db-schemas.ts`) in your project, then import schemas to validate data at runtime. +Save the generated output to a file (e.g., `database/zod.ts`) in your project, then import schemas to validate data at runtime. ### Validating query results ```ts -import { schemas } from "./db-schemas"; +import { schemas } from "@/database/zod"; const { Row: UserRow } = schemas.public.Tables.users; @@ -31,7 +31,7 @@ if (result.success) { ### Validating data before inserting ```ts -import { schemas } from "./db-schemas"; +import { schemas } from "@/database/zod"; const { Insert: UserInsert } = schemas.public.Tables.users; @@ -46,7 +46,7 @@ const newUser = UserInsert.parse({ ### Validating API request bodies ```ts -import { schemas } from "./db-schemas"; +import { schemas } from "@/database/zod"; const { Update: UserUpdate } = schemas.public.Tables.users; @@ -63,7 +63,7 @@ You don't need both the TypeScript generator and the Zod generator. Zod schemas ```ts import { z } from "zod"; -import { schemas } from "./db-schemas"; +import { schemas } from "@/database/zod"; type UserRow = z.infer; // ^ { id: number; name: string; email: string; status: ...; ... } diff --git a/docs/typescript.md b/docs/typescript.md index 42981d11..695a2405 100644 --- a/docs/typescript.md +++ b/docs/typescript.md @@ -4,12 +4,12 @@ Generates TypeScript type definitions from your PostgreSQL database schema. The ## Usage -Save the generated output to a file (e.g., `database.types.ts`) in your project, then import the types. +Save the generated output to a file (e.g., `database/types.ts`) in your project, then import the types. ### Typing database queries ```ts -import { Database } from "./database.types"; +import { Database } from "@/database/types"; type User = Database["public"]["Tables"]["users"]["Row"]; @@ -22,7 +22,7 @@ function getUser(row: unknown): User { ### Typing inserts and updates ```ts -import { Database } from "./database.types"; +import { Database } from "@/database/types"; type NewUser = Database["public"]["Tables"]["users"]["Insert"]; type UserUpdate = Database["public"]["Tables"]["users"]["Update"]; @@ -42,7 +42,7 @@ function updateUser(id: number, changes: UserUpdate) { The generated output includes shorthand helper types: ```ts -import { Tables, TablesInsert, TablesUpdate, Enums } from "./database.types"; +import { Tables, TablesInsert, TablesUpdate, Enums } from "@/database/types"; type User = Tables<"users">; type NewUser = TablesInsert<"users">; @@ -54,7 +54,7 @@ type UserStatus = Enums<"user_status">; ```ts import { createClient } from "@supabase/supabase-js"; -import { Database } from "./database.types"; +import { Database } from "@/database/types"; const supabase = createClient(url, key); From abff46b0e9627aee7d8b778da9e569b09f559bd9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Feb 2026 07:33:14 +0000 Subject: [PATCH 11/11] Pin generator versions: Zod v4, JSON Schema Draft 2020-12 Specify target versions explicitly in docs and plan so it's clear what the generators produce. Zod v4 is the current major version (released July 2025). JSON Schema Draft 2020-12 is the current stable specification. https://claude.ai/code/session_01RqAeSB7evS4BdJVtKhHMdx --- docs/jsonschema.md | 2 +- docs/typescript-zod.md | 2 +- plan.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/jsonschema.md b/docs/jsonschema.md index a881c9dc..483964e5 100644 --- a/docs/jsonschema.md +++ b/docs/jsonschema.md @@ -210,7 +210,7 @@ Nullable columns are wrapped with `{ "oneOf": [typeSchema, { "type": "null" }] } ## Features -- Standard JSON Schema Draft 2020-12 output +- Targets JSON Schema Draft 2020-12 (the current stable specification) - `$defs` and `$ref` for reusable enum and composite type definitions - `required` arrays reflect column defaults and identity columns - Driver-aware type mappings (`direct` vs `postgrest`) diff --git a/docs/typescript-zod.md b/docs/typescript-zod.md index d3fdfb41..b51430fe 100644 --- a/docs/typescript-zod.md +++ b/docs/typescript-zod.md @@ -1,6 +1,6 @@ # TypeScript Zod Type Generator -Generates [Zod](https://zod.dev/) schema definitions from your PostgreSQL database schema. Unlike the [TypeScript generator](typescript.md) which produces static types only, this generator produces runtime validation schemas that can parse and validate data at runtime. +Generates [Zod v4](https://zod.dev/v4) schema definitions from your PostgreSQL database schema. Unlike the [TypeScript generator](typescript.md) which produces static types only, this generator produces runtime validation schemas that can parse and validate data at runtime. **Status:** Planned (not yet implemented) diff --git a/plan.md b/plan.md index 5d587ff4..b13452fa 100644 --- a/plan.md +++ b/plan.md @@ -4,8 +4,8 @@ The `postgres-meta` project has an established pattern for type generators. Each language has a route handler, a template, and tests, plus registration in shared files. We'll follow this exact pattern to add two new generators: -1. **Zod** — Generates TypeScript code using the `zod` library for runtime validation (unlike the existing TypeScript generator which only produces static types). -2. **JSON Schema** — Generates a language-agnostic [JSON Schema](https://json-schema.org/) (Draft 2020-12) document describing the database schema. +1. **Zod** — Generates TypeScript code targeting [Zod v4](https://zod.dev/v4) for runtime validation (unlike the existing TypeScript generator which only produces static types). +2. **JSON Schema** — Generates a language-agnostic [JSON Schema Draft 2020-12](https://json-schema.org/draft/2020-12) document describing the database schema (the current stable specification). Both generators consume the existing `GeneratorMetadata` type from `src/lib/generators.ts` — no changes to the core introspection layer are needed.