{"id":50741620,"url":"https://github.com/moinsen-dev/supabase_client_gen","last_synced_at":"2026-06-10T17:30:56.624Z","repository":{"id":362488896,"uuid":"1259300162","full_name":"moinsen-dev/supabase_client_gen","owner":"moinsen-dev","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-04T11:50:17.000Z","size":23,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"develop","last_synced_at":"2026-06-04T13:22:31.417Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moinsen-dev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-04T11:23:56.000Z","updated_at":"2026-06-04T11:50:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/moinsen-dev/supabase_client_gen","commit_stats":null,"previous_names":["moinsen-dev/supabase_client_gen"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/moinsen-dev/supabase_client_gen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fsupabase_client_gen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fsupabase_client_gen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fsupabase_client_gen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fsupabase_client_gen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moinsen-dev","download_url":"https://codeload.github.com/moinsen-dev/supabase_client_gen/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fsupabase_client_gen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34163253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-10T17:30:55.743Z","updated_at":"2026-06-10T17:30:56.617Z","avatar_url":"https://github.com/moinsen-dev.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# supabase_client_gen\n\nContract-driven code generator for Supabase. Define your backend in a single\nYAML file, then generate a complete typed Dart client — models, enums,\nrepositories, edge function clients, and storage bucket clients.\n\n```bash\n# Install globally:\ndart pub global activate supabase_client_gen\n\n# Generate from any project:\ndart pub global run supabase_client_gen:generate \\\n  --contract docs/contracts/supabase.yaml \\\n  --output lib/generated\n```\n\n## Why?\n\nWriting Supabase client code by hand means repeating yourself across tables,\nkeeping Dart types in sync with Postgres columns, and wiring up realtime\nsubscriptions manually. This tool reads a single contract file — your backend's\nsource of truth — and produces all the Dart code you need.\n\n**One contract. One command. Zero drift.**\n\nThe same contract always produces byte-identical output (it does not depend on\nwhether a database is reachable), so generated code is safe to commit and gate\nin CI.\n\n## Installation\n\n```bash\ndart pub global activate supabase_client_gen\n```\n\nOr add as a dev dependency:\n\n```yaml\ndev_dependencies:\n  supabase_client_gen: ^0.2.0\n```\n\n### Runtime dependencies in the consuming app\n\nGenerated code imports the following — add them to the app that consumes the\ngenerated client:\n\n```yaml\ndependencies:\n  supabase_flutter: ^2.8.0   # repositories, edge functions, storage\n  equatable: ^2.0.0          # models\n```\n\n## What Gets Generated\n\n| Output | Path | Description |\n|---|---|---|\n| Data models | `models/` | `Equatable` classes with `fromJson`/`toJson`/`copyWith`, null-safe |\n| Enums | `enums/enums.dart` | Dart enums from Postgres enum types, with `fromString` |\n| Repositories | `repositories/` | Typed `select`/`insert`/`update`/`delete`/`stream` |\n| Edge function clients | `edge_functions/edge_functions.dart` | Typed wrappers over `functions.invoke` |\n| Storage clients | `storage/storage.dart` | Per-bucket upload/download/URL with MIME + size checks |\n\nEvery generated file carries a `// GENERATED by supabase_client_gen` header and\nmust not be hand-edited.\n\n### Nullability\n\nNullability comes **only** from the contract's `nullable_fields` lists — this is\nwhat keeps generated output deterministic and machine-independent.\n\nTo keep those lists honest against the real database, let the DB write them back\ninto the contract:\n\n```bash\ndart pub global run supabase_client_gen:generate \\\n  --contract docs/contracts/supabase.yaml --output lib/generated \\\n  --with-db --sync-nullability --db-url \"$SUPABASE_DB_URL\"\n```\n\n`--sync-nullability` reads live column nullability and updates `nullable_fields`\nin the contract (preserving comments/formatting). Generation then reads from the\ncontract as usual. Use `validate --with-db` to *detect* drift without writing.\n\n## The Contract Format\n\nA `supabase.yaml` file is the single source of truth for your backend. A complete,\nrunnable example lives at [`example/supabase.yaml`](example/supabase.yaml). The\ntop-level shape:\n\n```yaml\ncontract:\n  name: my_project_supabase_contract\n  version: \"0.1.0\"\n  date: \"2026-01-01\"\n\n# Connection details live under project.remote — name and ref are required.\nproject:\n  remote:\n    name: my_project\n    ref: abcdefghijklmnop\n\nauth:\n  provider: supabase\n  planned_sign_in_methods:\n    - email\n\ndata_model:\n  public:\n    things:\n      ownership: workspace\n      primary_key: id            # honoured by update/delete/stream\n      fields:\n        id: uuid\n        workspace_id: uuid       # presence makes the repo workspace-scoped\n        name: text\n        category: thing_category\n        notes: text\n      nullable_fields:\n        - notes\n      enum_values:\n        thing_category: [device, document, other]\n      client_access:\n        select: member_of_workspace\n        insert: member_of_workspace\n        update: member_of_workspace\n        delete: member_of_workspace\n\nstorage:\n  buckets:\n    avatars:\n      public: true\n      allowed_mime_types: [image/png, image/jpeg]\n      file_size_limit_mb: 5\n\nedge_functions:\n  runtime: deno                  # non-mapping keys like this are ignored\n  resolve_thing:\n    method: POST\n    required_request_fields: [query]\n    optional_request_fields: [workspace_id]\n\nrealtime:\n  publication:\n    allowed_tables:\n      - public.things\n```\n\nMalformed contracts fail with a path-qualified message\n(e.g. `data_model.public.things.primary_key is required`) rather than a stack trace.\n\n### Field Types\n\n| Contract Type | Generated Dart Type |\n|---|---|\n| `uuid`, `text` | `String` |\n| `integer` / `int4`, `bigint` / `int8` | `int` |\n| `numeric` / `decimal` | `double` |\n| `boolean` / `bool` | `bool` |\n| `timestamptz` / `timestamp` / `date` | `DateTime` |\n| `jsonb` / `json` | `Map\u003cString, dynamic\u003e` |\n| `vector(N)` | `List\u003cdouble\u003e` |\n| Custom enum | Named Dart enum |\n\n## CLI Reference\n\n### generate\n\n```bash\ndart pub global run supabase_client_gen:generate \\\n  --contract \u003cpath\u003e \\          # Path to supabase.yaml\n  --output \u003cdir\u003e \\             # Output directory for generated code\n  [--check] \\                  # Verify generated code is up to date (exit 1 if not)\n  [--with-db] \\                # Connect to the DB (only meaningful with --sync-nullability)\n  [--sync-nullability] \\       # Write live-DB nullability back into the contract\n  [--db-url \u003curl\u003e] \\           # postgres:// connection string (or SUPABASE_DB_URL env)\n  [--version]\n```\n\n### validate\n\n```bash\ndart pub global run supabase_client_gen:validate \\\n  --contract \u003cpath\u003e \\\n  --output \u003cdir\u003e \\\n  [--ts \u003cpath\u003e] \\              # Path to supabase.types.ts\n  [--migrations \u003cdir\u003e] \\       # Supabase migrations directory\n  [--mode=\u003cmode\u003e] \\            # db | types | ts | migrations | all (default)\n  [--with-db] [--db-url \u003curl\u003e] [--json]\n```\n\nFour validation checks:\n1. **DB ↔ Contract** — tables, columns, types, enums, and nullability aligned?\n2. **Contract ↔ Generated Code** — is the Dart client up to date?\n3. **Contract ↔ TS Types** — does `supabase.types.ts` match the contract?\n4. **Migration Freshness** — any migrations newer than the contract?\n\n## Integration with Melos\n\n```yaml\n# melos.yaml\nscripts:\n  gen:client:\n    run: dart pub global run supabase_client_gen:generate\n      --contract ../../docs/contracts/supabase.yaml --output lib/generated\n\n  gen:client:check:\n    run: dart pub global run supabase_client_gen:generate\n      --contract ../../docs/contracts/supabase.yaml --output lib/generated --check\n\n  validate:all:\n    run: dart pub global run supabase_client_gen:validate\n      --contract ../../docs/contracts/supabase.yaml --output lib/generated\n      --ts ../../docs/generated/supabase.types.ts\n      --migrations ../../supabase/migrations --mode=all --with-db\n```\n\n## Generated API\n\n### Repository\n\nA workspace-scoped `things` table (it has a `workspace_id` column) yields:\n\n```dart\nfinal repo = ThingRepository(supabase.client);\n\nfinal things = await repo.select(workspaceId: wsId, limit: 50, offset: 0);\nfinal created = await repo.insert({'workspace_id': wsId, 'name': 'Drill'});\nfinal updated = await repo.update(id, {'name': 'Hammer drill'});\nawait repo.delete(id);\n\n// Realtime (table listed under realtime.publication.allowed_tables):\nrepo.stream(workspaceId: wsId).listen((things) =\u003e print(things));\n```\n\n`update`/`delete`/`stream` use the table's declared `primary_key`. Tables without\na `workspace_id` column get unscoped `select()` / `stream()`. Read-only tables\n(all writes `edge_function_only`) get no mutation methods, but still get a\n`.stream()` if realtime is enabled.\n\n### Edge function client\n\nEach client-invoked edge function becomes a typed top-level function:\n\n```dart\nfinal result = await resolveThing(query: 'cordless drill', workspaceId: wsId);\n// result is Map\u003cString, dynamic\u003e from the function response\n```\n\n### Storage bucket\n\n```dart\nfinal avatars = AvatarsBucket(supabase.client);\n\nawait avatars.upload('user/$id.png', bytes, contentType: 'image/png');\nfinal url = avatars.getPublicUrl('user/$id.png'); // signed URL for private buckets\n```\n\nUploads are validated against the bucket's `allowed_mime_types` and\n`file_size_limit_mb` before hitting the network.\n\n## Programmatic API\n\n```dart\nimport 'dart:io';\nimport 'package:supabase_client_gen/supabase_client_gen.dart';\n\nvoid main() {\n  final contract = loadContract('docs/contracts/supabase.yaml');\n  final files = ClientGenerator(contract).generate();\n  for (final entry in files.entries) {\n    File('lib/generated/${entry.key}')\n      ..createSync(recursive: true)\n      ..writeAsStringSync(entry.value);\n  }\n}\n```\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoinsen-dev%2Fsupabase_client_gen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoinsen-dev%2Fsupabase_client_gen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoinsen-dev%2Fsupabase_client_gen/lists"}