-
-
Notifications
You must be signed in to change notification settings - Fork 310
feat: add supabase_typegen package generating typed table definitions #1635
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
b020093
feat: add supabase_typegen package generating typed table definitions
spydon 064bb5c
chore: trigger CI
spydon 2432ebb
refactor: model column type kinds as an enum in the schema description
spydon 71fbaef
chore: trigger CI
spydon 80b59d5
fix(supabase_typegen): review fixes for wire correctness and CLI robu…
spydon 226ad80
feat(supabase_typegen): generate setXToNull methods for explicit SQL …
spydon edd4f95
feat: mark typed table access as experimental in typegen output
spydon d80e2cd
feat(supabase_typegen): read the postgres-meta json metadata instead …
spydon ec866b3
fix(supabase_typegen): wrap generated doc comments at 80 characters
spydon f6f3245
feat(supabase_typegen): support writing the generated code to stdout …
spydon 115b3f9
fix(supabase_typegen): handle all-read-only tables and string-seriali…
spydon 214dc4b
docs(supabase_typegen): document the cross-schema enum name limitation
spydon 9b76b14
docs(supabase_typegen): recommend the Supabase CLI as the easiest way…
spydon 047b1ba
docs(supabase_typegen): explain when committing schema.json makes sense
spydon 45667c8
refactor(supabase_typegen): consume the postgrest-typegen GeneratorMe…
spydon 28192b9
fix(supabase_typegen): match the workspace postgrest version
spydon ead96c3
feat(supabase_typegen): gate insert and update generation on relation…
spydon dd6dca7
test(supabase_typegen): regenerate the metadata fixture from a real p…
spydon 774dc9d
feat(supabase_typegen): resolve enum types exactly via the column typ…
spydon e4070d6
refactor(supabase_typegen): emit columns in the canonical name order
spydon a31b132
docs(supabase_typegen): source the metadata document from postgrest-t…
spydon 978d2ce
docs(supabase_typegen): drop the --lang json references, the CLI hand…
spydon da595a8
refactor(supabase_typegen): read the metadata document from stdin only
spydon 52c4a42
fix: address review feedback on source encoding, parser validation, a…
spydon a738dc0
fix: address review feedback on stdin decoding, README database scopi…
spydon f17b229
style: fix DCM findings on inferrable type arguments and equal switch…
spydon 4fe916d
fix: address review feedback on identifier shadowing, parser validati…
spydon 438a14c
test(supabase_typegen): check in a hostile-schema golden so generated…
spydon dd642ea
style: fix DCM findings and keep code-shaped text out of the hostile …
spydon 384b87a
ci: refresh the apt package index before the cached package install
spydon 2907420
docs: note the git dependency source until supabase_typegen is published
spydon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,78 @@ | ||
| # supabase_typegen | ||
|
|
||
| > [!WARNING] | ||
| > This is a placeholder release that reserves the package name on pub.dev. The | ||
| > code generator is still under development and this version does nothing yet. | ||
| Generates typed Supabase table definitions from your database schema, so | ||
| query results never expose raw `Map<String, dynamic>` data. | ||
|
|
||
| A command-line code generator that turns a Supabase database schema into typed | ||
| Dart table definitions for use with the Supabase client packages. | ||
| For every table the generator emits: | ||
|
|
||
| The generator implementation will land in a future release. | ||
| - a zero-cost row extension type over the decoded JSON map with typed getters, | ||
| - `Insert` and `Update` value types that enforce required columns at the | ||
| construction site, | ||
| - a `PostgrestTable` definition and `TableColumn` tokens for compile-time | ||
| checked filters, | ||
| - Dart enums for Postgres enums, with wire-name mapping. | ||
|
|
||
| ## Usage | ||
|
|
||
| The easiest way is through the Supabase CLI, which handles the database | ||
| connection and runs this package for you. Add `supabase_typegen` as a dev | ||
| dependency of your project (until the package is published to | ||
| [pub.dev](https://pub.dev), depend on it with a `git` source pointing at | ||
| `packages/supabase_typegen` in this repository), then: | ||
|
|
||
| ```sh | ||
| supabase gen types --lang dart --local > lib/supabase_schema.g.dart | ||
| ``` | ||
|
|
||
| Any of the CLI's connection flags work (`--local`, `--linked`, `--db-url`, | ||
| `--project-id`). | ||
|
|
||
| Under the hood the CLI runs the introspection of | ||
| [`@supabase/postgrest-typegen`](https://github.com/supabase/sdk/tree/main/packages/postgrest-typegen) | ||
| in-process against the database (the same `GeneratorMetadata` intermediate | ||
| representation its TypeScript, Go, Swift, and Python generators consume, | ||
| ordered with `sortGeneratorMetadata`) and hands the document to this tool | ||
| over stdin. The types reflect the current state of the selected database: | ||
| with `--local` the SQL in your `supabase/` directory stays the single source | ||
| of truth, since the CLI applies your migrations to the local database and | ||
| generates from the result, while `--linked`, `--project-id`, and `--db-url` | ||
| generate from whatever that database currently contains. | ||
|
|
||
| Use `--schema` to generate for a schema other than `public`, and `--import` | ||
| to change which library the generated file imports `PostgrestTable` and | ||
| `TableColumn` from. | ||
|
|
||
| The metadata comes from the database catalog, so nullability, database | ||
| defaults, and identity columns are exact: a `NOT NULL` column with a default | ||
| reads as non-nullable but stays optional on insert, and `GENERATED ALWAYS` | ||
| columns appear in the row type but not in the insert and update types. | ||
|
|
||
| ## Generated code in action | ||
|
|
||
| ```dart | ||
| final books = await client.table(Books.table) | ||
| .select() | ||
| .where(Books.mood.eq(Mood.happy)) | ||
| .order(Books.createdAt, ascending: false); // List<BooksRow> | ||
|
|
||
| await client.table(Books.table).insert( | ||
| BooksInsert(title: 'A typed row', tags: ['dart']), | ||
| ); | ||
| ``` | ||
|
|
||
| ## Known limitations | ||
|
|
||
| - Passing `null` to an `Insert`/`Update` parameter omits the column. To write | ||
| SQL NULL explicitly, use the generated `set…ToNull` methods, for example | ||
| `BooksUpdate(inPrint: false).setPriceToNull()`; they only exist for | ||
| nullable columns, so nulling a `NOT NULL` column is a compile error. | ||
| - Array elements are assumed non-null (`text[]` maps to `List<String>`), | ||
| matching the supabase-js type generator; arrays containing SQL NULL | ||
| elements throw when the element is read. Enum, date, and timestamp array | ||
| elements stay in their wire representation (`List<String>`); the Dart enum | ||
| for enum array elements is still generated for manual conversion. | ||
| - `timestamptz` values are written back in UTC, naive `timestamp` values as | ||
| local wall time, and `date` values date-only, so calendar dates never | ||
| shift with the client timezone. | ||
| - Foreign key relationship getters and typed functions (rpc) are not | ||
| generated yet. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| import 'dart:convert'; | ||
| import 'dart:io'; | ||
|
|
||
| import 'package:args/args.dart'; | ||
| import 'package:supabase_typegen/supabase_typegen.dart'; | ||
|
|
||
| final _argParser = ArgParser() | ||
| ..addOption( | ||
| 'schema', | ||
| defaultsTo: 'public', | ||
| help: 'The database schema to generate types for.', | ||
| ) | ||
| ..addOption( | ||
| 'output', | ||
| abbr: 'o', | ||
| defaultsTo: 'lib/supabase_schema.g.dart', | ||
| help: 'Path of the generated Dart file, or - to write the code to stdout.', | ||
| ) | ||
| ..addOption( | ||
| 'import', | ||
| defaultsTo: 'package:postgrest/postgrest.dart', | ||
| help: | ||
| 'The import the generated file uses for PostgrestTable and ' | ||
| 'TableColumn.', | ||
| ) | ||
| ..addFlag('help', abbr: 'h', negatable: false, help: 'Show this usage.'); | ||
|
|
||
| Future<void> main(List<String> arguments) async { | ||
| // The value returned from main is ignored by the Dart VM, so the exit | ||
| // code has to be set explicitly. | ||
| exitCode = await _run(arguments); | ||
| } | ||
|
|
||
| Future<int> _run(List<String> arguments) async { | ||
| final ArgResults options; | ||
| try { | ||
| options = _argParser.parse(arguments); | ||
| } on FormatException catch (error) { | ||
| stderr | ||
| ..writeln(error.message) | ||
| ..writeln(_argParser.usage); | ||
| return 64; | ||
| } | ||
|
|
||
| if (options.flag('help')) { | ||
| stdout | ||
| ..writeln( | ||
| 'Generates typed Supabase table definitions from the ' | ||
| 'GeneratorMetadata document that postgrest-typegen emits, ' | ||
| 'read from stdin.', | ||
| ) | ||
| ..writeln() | ||
| ..writeln('Usage: dart run supabase_typegen < <metadata document>') | ||
| ..writeln(_argParser.usage); | ||
| return 0; | ||
| } | ||
|
|
||
| if (stdin.hasTerminal) { | ||
| stderr.writeln( | ||
| 'Expected a GeneratorMetadata document of ' | ||
| '@supabase/postgrest-typegen on stdin. This tool is normally ' | ||
| 'invoked through `supabase gen types --lang dart`.', | ||
| ); | ||
| return 64; | ||
| } | ||
|
|
||
| final schemaName = options.option('schema')!; | ||
| final SchemaDescription schema; | ||
| try { | ||
| final contents = await utf8.decodeStream(stdin); | ||
| schema = parseGeneratorMetadata( | ||
| jsonDecode(contents) as Map<String, dynamic>, | ||
| schemaName: schemaName, | ||
| ); | ||
| } on FormatException catch (error) { | ||
| stderr.writeln('Could not parse the document on stdin: ${error.message}'); | ||
| return 65; | ||
| } | ||
|
|
||
| final code = generateDartCode(schema, importUri: options.option('import')!); | ||
|
|
||
| final output = options.option('output')!; | ||
| final String generatedInto; | ||
| if (output == '-') { | ||
| stdout.write(code); | ||
| generatedInto = 'stdout'; | ||
| } else { | ||
| final outputFile = File(output); | ||
| outputFile.parent.createSync(recursive: true); | ||
| outputFile.writeAsStringSync(code); | ||
| generatedInto = outputFile.path; | ||
| } | ||
|
|
||
| final emittedTables = schema.tables | ||
| .where((table) => table.columns.isNotEmpty) | ||
| .length; | ||
| final skippedTables = schema.tables.length - emittedTables; | ||
| final summarySink = output == '-' ? stderr : stdout; | ||
| summarySink.writeln( | ||
| 'Generated $generatedInto with $emittedTables tables and ' | ||
| '${schema.enums.length} enums from schema "$schemaName".' | ||
| '${skippedTables == 0 ? '' : ' Skipped $skippedTables tables ' | ||
| 'without columns.'}', | ||
| ); | ||
| return 0; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.