{"id":50306224,"url":"https://github.com/moinsen-dev/firepack","last_synced_at":"2026-05-28T16:30:54.097Z","repository":{"id":354698500,"uuid":"1223374724","full_name":"moinsen-dev/firepack","owner":"moinsen-dev","description":" Spec-driven Firestore + Flutter codegen. Define your collections, fields,   indexes, and rules in one YAML file. Get back typed Dart models, Riverpod   providers, repositories, security rules, indexes, TypeScript types, and a   visual data-model graph — regenerated on every save.","archived":false,"fork":false,"pushed_at":"2026-04-29T16:37:06.000Z","size":498,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"develop","last_synced_at":"2026-04-29T18:24:04.128Z","etag":null,"topics":["code-generation","dart","firebase","flutter","orm"],"latest_commit_sha":null,"homepage":"https://moinsen-dev.github.io/firepack/","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":"docs/ROADMAP.md","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-04-28T09:05:29.000Z","updated_at":"2026-04-29T16:37:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/moinsen-dev/firepack","commit_stats":null,"previous_names":["moinsen-dev/firepack"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/moinsen-dev/firepack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Ffirepack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Ffirepack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Ffirepack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Ffirepack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moinsen-dev","download_url":"https://codeload.github.com/moinsen-dev/firepack/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Ffirepack/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33617718,"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-05-28T02:00:06.440Z","response_time":99,"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":["code-generation","dart","firebase","flutter","orm"],"created_at":"2026-05-28T16:30:52.960Z","updated_at":"2026-05-28T16:30:54.091Z","avatar_url":"https://github.com/moinsen-dev.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 🔥 firepack\n\n### One YAML spec → your entire Firestore data layer.\n\n**Stop hand-writing the 9-file fan-out for every new field.**\nTyped Dart models, Riverpod providers, repositories, security rules,\nindexes, storage paths, and a Mermaid graph — generated deterministically,\non every save.\n\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![version](https://img.shields.io/badge/version-0.0.15-orange.svg)](CHANGELOG.md)\n[![Dart](https://img.shields.io/badge/Dart-%5E3.5-0175C2?logo=dart\u0026logoColor=white)](https://dart.dev)\n[![tests](https://img.shields.io/badge/tests-62_passing-brightgreen.svg)](test/)\n[![status](https://img.shields.io/badge/status-pre--pub-yellow.svg)](#-status)\n\n[**What it generates**](#-what-firepack-generates) ·\n[**Quick start**](#-quick-start) ·\n[**Why**](#-why-firepack-exists) ·\n[**Spec reference**](docs/SPEC.md) ·\n[**Roadmap**](docs/ROADMAP.md)\n\n\u003c/div\u003e\n\n---\n\n## ✨ The 30-second pitch\n\nYou write **one** YAML file:\n\n```yaml\nfirepack: 1\nproject: blog\ncollections:\n  posts:\n    tenant: organizationId\n    fields:\n      id:             { type: string, primaryKey: true }\n      title:          { type: string, required: true }\n      status:         { type: \"enum[PostStatus]\", default: draft }\n      createdAt:      { type: dateTime, required: true, serverDefault: now }\n      organizationId: { type: \"ref[organizations.id]\", required: true }\n    indexes:\n      - fields: [organizationId, status, createdAt:desc]\n    queries:\n      watchByOrg:\n        where: [tenant]\n        orderBy: createdAt:desc\n        limit: 50\n```\n\nYou get **all of this** back, every save:\n\n```\nyour-app/\n├─ lib/firepack/\n│  ├─ paths.dart                       # FirestorePaths.posts ...\n│  ├─ firestore_provider.dart          # one Riverpod DI seam — override once for tests\n│  ├─ models/post.dart                 # immutable + copyWith + ==/hashCode +\n│  │                                     toJson/fromJson + toFirestore/fromFirestore\n│  ├─ repositories/post_repository.dart # typed queries + add(merge:) / update / delete\n│  └─ storage_paths.dart               # typed Cloud Storage path helpers\n├─ firestore.rules                     # generated, with reusable rule helpers\n├─ firestore.indexes.json              # composite indexes from spec\n└─ DATA_MODEL.md                       # Mermaid ER diagram, GitHub-renderable\n```\n\nEdit the spec → `firepack watch` regenerates everything → your Flutter app\nrecompiles against the new shape. One source of truth, zero hand-typing.\n\n---\n\n## 🎁 What firepack generates\n\n| Target | Output | Highlights |\n|---|---|---|\n| 🧱 **Dart models** | `lib/firepack/models/*.dart` | Immutable, value-equal, `copyWith`, `toJson`/`fromJson` (pure JSON) **+** `toFirestore`/`fromFirestore` (DateTime ⇄ Timestamp), enums with snake_case wire-format, nested types |\n| 🔌 **Repositories** | `lib/firepack/repositories/*.dart` | Typed `Stream\u003cList\u003cX\u003e\u003e` per spec query, `add(doc, {merge:})`, `updateById`, `deleteById`, `watchById`. `serverDefault: now` injects `FieldValue.serverTimestamp()` |\n| 🎯 **Riverpod providers** | inline in each repo file | `StreamProvider.family` per query, single `firestoreProvider` DI seam — override once for fakes |\n| 🛡️ **Firestore rules** | `firestore.rules` | Per-collection read/create/update/delete; reusable helpers (`isSignedIn`, `isAdmin`, `isSupervisorOrAdmin`, tenant scope); deny-all default |\n| 🗂️ **Composite indexes** | `firestore.indexes.json` | Sorted, deterministic, dedup'd — diffs cleanly in PRs |\n| 📁 **Storage paths** | `lib/firepack/storage_paths.dart` | Typed methods per declared bucket — drift between upload/read/rules code becomes impossible |\n| 🗺️ **Path constants** | `lib/firepack/paths.dart` | `FirestorePaths.\u003ccollection\u003e` — rename a collection in the spec, the build (not the runtime) breaks |\n| 🌐 **Mermaid graph** | `DATA_MODEL.md` | ER diagram with FKs, nested types, storage-bucket cross-system edges. Renders inline on GitHub |\n| 🔍 **Spec diff** | Markdown report | PR-comment-shape diff between two specs (added/removed/changed) |\n\nEverything is **deterministic**: two runs on the same spec produce\nbyte-identical output. Snapshot tests catch any drift in a generator.\n\n---\n\n## 📐 The data model, visualised\n\nThe example spec renders to this — generated by `firepack viz`,\nre-rendered on every spec save:\n\n```mermaid\nerDiagram\n  organizations {\n    string id \"PK\"\n    string name \"required\"\n    datetime createdAt \"required\"\n  }\n  users {\n    string id \"PK\"\n    ref organizationId \"FK\"\n    string displayName \"required\"\n    string email \"required\"\n    enum role\n  }\n  posts {\n    string id \"PK\"\n    ref organizationId \"FK\"\n    ref authorId \"FK\"\n    string title \"required\"\n    string body \"required\"\n    string coverImage \"storage\"\n    list attachments\n    type linkPreview\n    enum status\n    datetime createdAt \"required\"\n    datetime publishedAt \"optional\"\n  }\n  comments {\n    string id \"PK\"\n    ref organizationId \"FK\"\n    ref postId \"FK\"\n    ref authorId \"FK\"\n    string body \"required\"\n    datetime createdAt \"required\"\n  }\n  postCovers {\n    string path \"covers/{organizationId}/{postId}.jpg\"\n  }\n  attachments_bucket {\n    string path \"attachments/{organizationId}/{postId}/{attachmentId}\"\n  }\n\n  users }o--|| organizations : \"organizationId\"\n  posts }o--|| organizations : \"organizationId\"\n  posts }o--|| users : \"authorId\"\n  posts }o--|| postCovers : \"coverImage (storage)\"\n  comments }o--|| organizations : \"organizationId\"\n  comments }o--|| posts : \"postId\"\n  comments }o--|| users : \"authorId\"\n```\n\nStorage buckets, foreign keys, nested types — all in one diagram, all\ngenerated. No hand-drawn ER stays in sync with code; this one does.\n\n---\n\n## 🚀 Quick start\n\nThe bundled [`example/`](example/) is a runnable Flutter + Riverpod\napp consuming firepack-generated code. Five commands from clone to\nrunning app:\n\n```bash\n# 1. Install firepack from this clone (until pub.dev release)\ngit clone https://github.com/moinsen-dev/firepack \u0026\u0026 cd firepack\ndart pub global activate --source path .\nexport PATH=\"$PATH:$HOME/.pub-cache/bin\"          # add to ~/.zshrc\n\n# 2. Sanity check the toolchain on the example spec\nfirepack lint --spec example/firepack.yaml\n\n# 3. Regenerate the example app's data layer\njust example-regen\n\n# 4. Boot the local Firebase Emulator (Firestore + Auth + UI)\njust example-emulator-up                          # in one terminal\n\n# 5. Run the Flutter app against the emulator\njust example-run                                  # in another\n```\n\nOpen the app → **Seed demo data** → 3 posts appear, each created via\nthe generated `PostRepository.add()`, streamed live through the\ngenerated Riverpod provider. Click a tile to edit (→ `updateById`),\ntrash icon to delete (→ `deleteById`). Watch the writes land in\nthe Emulator UI at \u003chttp://localhost:4000/firestore\u003e.\n\n\u003e **Prerequisites**: Flutter ≥ 3.24, JDK 21+ for the Firestore emulator\n\u003e (the justfile auto-picks `brew install openjdk@21` if present, no\n\u003e system-wide JDK switch needed).\n\n---\n\n## 🛠️ Why firepack exists\n\nAdding one field to a Firestore-backed Flutter app fans out across **nine files**:\n\n1. Dart model class\n2. `freezed` annotation + generated part\n3. `json_serializable` part\n4. Repository (read path)\n5. Repository (write path)\n6. Riverpod provider\n7. Firestore security rule\n8. Composite index\n9. UI form / display\n\nThe fan-out is **mechanical and error-prone**. Half the production\nincidents come from rule and index drift — fixed in one file, missed\nin another.\n\nfirepack collapses the fan-out to **one diff in `firepack.yaml`**.\nRe-run, build, ship.\n\nIt's not magic. It's a small Dart CLI that reads YAML and writes\nDart/JSON/text — deterministically, with snapshot tests proving every\noutput. The spec format is intentionally minimal (~150 LoC of parser,\nno DSL fanciness). Less surface to maintain, more leverage per change.\n\n---\n\n## 🆚 Compared to the alternatives\n\n|  | **firepack** | `freezed` + `json_serializable` | Hand-written |\n|---|---|---|---|\n| Source of truth | YAML spec | Dart class | scattered |\n| Rules + indexes generated? | ✅ from same spec | ❌ separate | ❌ separate |\n| Riverpod providers generated? | ✅ | ❌ (write yourself) | ❌ |\n| Storage paths typed? | ✅ from `storage:` block | ❌ | ❌ |\n| Watch / hot-regen? | ✅ `firepack watch` | ❌ (build_runner) | ❌ |\n| Mermaid data-model graph? | ✅ with cross-system edges | ❌ | ❌ |\n| Spec diff for PR review? | ✅ `firepack diff` | ❌ | ❌ |\n| Build-runner needed? | ❌ pure Dart, watcher | ✅ | n/a |\n| New-collection cost | edit YAML, regen | new files in 5 places | new files in 9 places |\n\nfirepack is **not** a replacement for `freezed` everywhere — if your\nclasses are pure Dart with sealed unions and complex pattern matching,\nkeep using freezed. firepack solves the specific shape of \"Firestore\ncollection ↔ Flutter UI ↔ Cloud Functions\" plumbing.\n\n---\n\n## 📋 CLI reference\n\n| Command | Effect |\n|---|---|\n| `firepack lint --spec \u003cpath\u003e` | validates spec (orphan refs, storage refs, missing tenants, name collisions) |\n| `firepack viz --spec \u003cpath\u003e [--out file.md]` | renders Mermaid `erDiagram`; `.md` wraps in code fence for GitHub render |\n| `firepack regen --target \u003cname\u003e --spec \u003cpath\u003e [--out \u003cpath\u003e]` | one-shot codegen. Targets: `models`, `repos`, `paths`, `firestore_provider`, `storage`, `rules`, `indexes` |\n| `firepack watch --spec \u003cpath\u003e [--config \u003cpath\u003e]` | first-pass regen + watch spec, re-runs on save. Targets in `firepack.config.yaml` |\n| `firepack diff --old \u003ca.yaml\u003e --new \u003cb.yaml\u003e` | semantic spec diff (PR-comment shape) |\n\nRun `firepack \u003ccommand\u003e --help` for full option lists.\n\n---\n\n## 📦 Install\n\n**Pre-pub-dev release**, install from a local clone:\n\n```bash\ngit clone https://github.com/moinsen-dev/firepack\ncd firepack\njust install                              # = dart pub global activate --source path .\n```\n\nMake sure `~/.pub-cache/bin` is in your PATH:\n\n```bash\nexport PATH=\"$PATH:$HOME/.pub-cache/bin\"   # add to ~/.zshrc or ~/.bashrc\nfirepack --help                             # verify\n```\n\nPath-source activations track the working tree — after `git pull`\nthe binary picks up changes immediately. No need to re-activate.\n\n---\n\n## 🧪 Status\n\n**Working today (v0.0.15):**\n\n- ✅ YAML spec parser + lint\n- ✅ Mermaid `firepack viz` output (`.md` auto-wraps in code fence)\n- ✅ Composite indexes generator (deterministic)\n- ✅ Firestore rules generator (with reusable helpers + deny-all default)\n- ✅ Dart model codegen — immutable class with `copyWith`, `==`/`hashCode`,\n  pure-JSON `toJson`/`fromJson` **and** Firestore-native\n  `toFirestore`/`fromFirestore` (handles `Timestamp` ⇄ `DateTime`\n  via duck-typing)\n- ✅ Repository + Riverpod provider codegen — typed queries from spec,\n  `add(doc, {merge:})`, `updateById`, `deleteById`, `watchById`\n- ✅ Per-collection `className:` override\n- ✅ Single `firestoreProvider` DI seam — override once for fakes\n- ✅ `serverDefault: now` → `FieldValue.serverTimestamp()` injection\n- ✅ Storage refs + typed `StoragePaths.\u003cbucket\u003e()` helpers\n- ✅ `firepack diff` (PR-comment-shape semantic diff)\n- ✅ `firepack watch` (auto-regen on save, multi-target via config)\n- ✅ Shared enums with `snake_case` / `dartName` wireFormat\n- ☐ TypeScript-types generator (`functions/src/firepack/types/*.ts`)\n- ☐ Pub.dev release (waiting for spec format to stabilise)\n- ☐ Transactions / batch-writes codegen\n- ☐ CI for firepack itself\n\n**Real-world consumption:** the [WorkBrief](https://workbrief.app)\nproduction app uses firepack as its data-layer generator since v0.0.2.\n13 collections, 3 nested types, 2 storage buckets — generated from\none spec, regenerated on every change.\n\nSee the [CHANGELOG](CHANGELOG.md) for milestone history.\n\n---\n\n## 🗺️ Project structure\n\n```\nfirepack/\n├─ bin/firepack.dart           # CLI entry point (CommandRunner)\n├─ lib/\n│  ├─ firepack.dart            # public exports\n│  └─ src/\n│     ├─ spec/                 # parser, model, lint\n│     ├─ codegen/              # one generator per target\n│     ├─ diff/                 # spec diff\n│     └─ viz/                  # Mermaid renderer\n├─ test/                       # generator snapshot tests + parser tests\n├─ example/                    # runnable Flutter + Riverpod demo app\n└─ docs/\n   ├─ PHILOSOPHY.md            # bootstrap principle + Non-Goals\n   ├─ ROADMAP.md               # milestone history with reflections\n   └─ SPEC.md                  # formal spec reference (v1)\n```\n\n`docs/PHILOSOPHY.md` is worth reading first if you're considering\ncontributing — it explains the bootstrap rule (every feature starts\nfrom a real consumer pain) and the deliberate Non-Goals list.\n\n---\n\n## 🤝 Contributing\n\nfirepack is **bootstrap-driven**: features land when they solve a real\nconsumer pain, not speculatively. If you have a use-case that the\ncurrent spec doesn't cover, open an issue describing the shape of code\nyou're hand-writing today. The fix is usually a new spec field\n+ a generator branch + a snapshot test.\n\nLocal development:\n\n```bash\njust check        # fmt-check + analyze + dart test (62) + flutter analyze\njust test         # just dart test\njust example-regen # regen the example app's generated tree\n```\n\n---\n\n## 📜 License\n\nMIT — see [LICENSE](./LICENSE).\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\nBuilt with care by [@moinsen-dev](https://github.com/moinsen-dev) ·\n🌟 if firepack saves you a fan-out, star the repo so others find it.\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoinsen-dev%2Ffirepack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoinsen-dev%2Ffirepack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoinsen-dev%2Ffirepack/lists"}