fix: add | null to composite type fields in TypeScript generator - #1063
fix: add | null to composite type fields in TypeScript generator#1063nancysangani wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes TypeScript type generation for PostgreSQL composite types by ensuring every composite type field is emitted as nullable (| null), including fields whose underlying type resolves to unknown (e.g., domain-typed fields).
Changes:
- Updated the TypeScript template to always append
| nullto composite type attributes. - Extended the test database schema with a domain + composite type that uses it.
- Updated TypeScript typegen snapshot expectations to include the new composite type and nullability.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/server/templates/typescript.ts |
Ensures composite type attributes are emitted as `${tsType} |
test/db/00-init.sql |
Adds a domain and a composite type using that domain to exercise the domain-as-attribute case. |
test/server/typegen.ts |
Updates expected generated TS output to include the new composite type and ` |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Thank you for the contribution! postgres-meta's type generation is moving to the shared |
Postgres composite type attributes cannot carry NOT NULL constraints, so every field is inherently nullable, yet the generated Python models declared them as required non-None fields and rejected valid rows. Ported from supabase/postgres-meta#1063 (the Python side of the same nullability bug; the upstream PR only implemented the TypeScript part)
…columns, composite nullability (#124) ## Summary Ports the worthwhile Python generator fixes from postgres-meta's open template PRs into this package (the templates are being deleted in favor of this package in supabase/postgres-meta#1084, so open fixes there are triaged and re-landed here). Four fixes, one commit each: 1. **Identifier escaping** (from supabase/postgres-meta#1082): enum `Literal` labels and `Field(alias=...)` values were interpolated unescaped, so a quote, backslash, or newline in a database name broke the generated module. A shared `escapePythonString` helper (JSON escaping, a strict subset of Python's) now covers all three interpolation sites. 2. **Python 3.9/3.10 support** (from supabase/postgres-meta#1094): `NotRequired` (3.11+) and `TypeAlias` (3.10+) now import from `typing_extensions`, which is always installed as a required dependency of pydantic. 3. **Deserialized json/jsonb** (from supabase/postgres-meta#1129): `json`/`jsonb` map to pydantic's `JsonValue` instead of `Json[Any]`. PostgREST returns these columns already deserialized, while `Json[Any]` validates a JSON *string* and parses it, so every generated model with a JSON column failed `model_validate` (supabase/supabase-py#1597). 4. **Composite type nullability** (the Python side of supabase/postgres-meta#1063, reimplemented): composite type attributes cannot carry NOT NULL constraints in Postgres, so their fields now emit `Optional[...]`. The origin PR's Python hunks were dead code (an unused `PythonDomain` class and a type-map entry for a name Postgres never emits), so the actual fix was implemented instead of ported. ## Triage of origin PRs | postgres-meta PR | Verdict | Reasoning | |---|---|---| | #1082 | Ported | Real invalid-syntax bug, correct approach. | | #1094 | Ported | Import failure on Python 3.9/3.10, independently verified by community comments on the PR. | | #1129 | Ported | Every JSON column failed validation at runtime; `JsonValue` is pydantic's native type for a parsed JSON value. | | #1063 (python part) | Reimplemented | Real bug, but the PR's Python changes did not actually fix it (dead code); the underlying fix is one line in `typeToClass`. | | #1072 (`frozen=True`) | Skipped | Author-labeled feature and an opinionated behavior change that breaks consumers who mutate row models; belongs behind a generator option if wanted. | | #808 | Skipped | 2023 draft fully superseded by the maintainer-authored template this package ports. | ## Validation - Unit tests per fix (pathological enum labels and aliases, import block assertions, json/jsonb mapping, composite `Optional` fields). - Parity golden regenerated (39 lines): the `typing_extensions` import split, 16 `Json[Any]` to `JsonValue` occurrences, and two composite attributes gaining `Optional[...]`; reviewed line by line and the golden gate was verified to actually trip on corruption. - The regenerated golden imports cleanly under pydantic, passes `mypy`, and runtime checks confirm deserialized JSON and `None` composite fields now validate. - `check-types`, `format-and-lint`, `knip`, `build`, `test` (97 pass, includes Docker-backed introspection and parity) all green. - Note: the nightly parity job against real postgres-meta will show this intentional drift until postgres-meta consumes a release containing it (supabase/postgres-meta#1084 replaces the templates with this package, closing the gap).
What kind of change does this PR introduce?
Bug fix
What is the current behavior?
Composite type fields were not emitting
| nullin the generated TypeScript types, making them incorrectly non-nullable.Fixes #763
What is the new behavior?
All composite type fields now emit
| null, including domain-typed fields which previously fell through tounknownwithout| null.Before:
After:
Additional context
Per the PostgreSQL docs, composite type fields cannot have NOT NULL constraints, so every field is inherently nullable.