{"id":50946761,"url":"https://github.com/hironow/firepact","last_synced_at":"2026-06-17T21:03:36.813Z","repository":{"id":362733889,"uuid":"1259974277","full_name":"hironow/firepact","owner":"hironow","description":"A type contract between a Pydantic backend and a TypeScript frontend for realtime Firestore (onSnapshot) documents — generates the wire types and gates backward/forward compatibility in CI (FULL_TRANSITIVE).","archived":false,"fork":false,"pushed_at":"2026-06-05T18:36:22.000Z","size":350,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-05T18:39:23.044Z","etag":null,"topics":["backward-compatibility","codegen","compatibility","firebase","firestore","json-schema","pydantic","python","rust","schema-evolution","typescript"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":false,"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/hironow.png","metadata":{"files":{"readme":"README.md","changelog":null,"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-05T03:26:46.000Z","updated_at":"2026-06-05T18:36:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hironow/firepact","commit_stats":null,"previous_names":["hironow/firepact"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/hironow/firepact","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hironow%2Ffirepact","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hironow%2Ffirepact/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hironow%2Ffirepact/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hironow%2Ffirepact/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hironow","download_url":"https://codeload.github.com/hironow/firepact/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hironow%2Ffirepact/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34465333,"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-17T02:00:05.408Z","response_time":127,"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":["backward-compatibility","codegen","compatibility","firebase","firestore","json-schema","pydantic","python","rust","schema-evolution","typescript"],"created_at":"2026-06-17T21:03:36.198Z","updated_at":"2026-06-17T21:03:36.807Z","avatar_url":"https://github.com/hironow.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# firepact\n\n[![PyPI](https://img.shields.io/pypi/v/firepact)](https://pypi.org/project/firepact/)\n[![crates.io](https://img.shields.io/crates/v/firepact-core)](https://crates.io/crates/firepact-core)\n[![CI](https://github.com/hironow/firepact/actions/workflows/ci.yaml/badge.svg)](https://github.com/hironow/firepact/actions/workflows/ci.yaml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nKeep your **Pydantic backend** and your **TypeScript frontend** agreeing on the\nwire shape of **Firestore Native-mode documents read in realtime via\n`onSnapshot`** — and fail CI on a schema change that would break a frontend still\nreading the old shape (`FULL_TRANSITIVE`).\n\nfirepact is not just a type converter. It generates the TypeScript types your\nfrontend imports *and* runs a compatibility gate over the contract as it evolves.\nBefore you rely on the green check, read\n[**what firepact is — and is not**](docs/scope.md): it gates the *evolution of the\ncontract*, not the data already sitting in Firestore.\n\n## Install\n\n```sh\npip install firepact          # Python CLI (firepact-gen / firepact-compat) + native engine\ncargo install firepact-core   # standalone Rust binary `firepact` (no Python/Node)\n```\n\n## Quick start\n\n**1. Mark the models you read in realtime.** The decorator records the collection\npath and which fields are guaranteed on every document; the backend writes with a\ncamelCase alias generator (firepact matches it).\n\n```python\nfrom datetime import datetime\nfrom typing import Annotated\n\nfrom firepact import firestore_realtime, FirestoreServerTimestamp\nfrom pydantic import BaseModel, ConfigDict\nfrom pydantic.alias_generators import to_camel\n\n\nclass CamelModel(BaseModel):                       # camelCase wire keys\n    model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)\n\n\n@firestore_realtime(collection=\"rooms/{roomId}/messages\", guaranteed=[\"body\"])\nclass Message(CamelModel):\n    id: str                                        # document id\n    body: str\n    created_at: Annotated[datetime, FirestoreServerTimestamp()]\n    tags: list[str] = []\n```\n\n**2. Generate the TypeScript your frontend imports.**\n\n```sh\nfirepact-gen --module app.models --output src/firestore.ts\n```\n\n```ts\n// @firestore-collection rooms/{roomId}/messages\nexport interface Message {        // read view, for onSnapshot()\n  body: string;                   // guaranteed -\u003e required even on old docs\n  createdAt?: Timestamp | null;   // server timestamp: null until it resolves\n  id: string;                     // the converter injects snapshot.id\n  tags?: string[];                // not guaranteed -\u003e optional (safe default)\n}\n\nexport interface MessageWrite {   // write view, for setDoc(): id is excluded\n  body: string;\n  createdAt: FieldValue;          // serverTimestamp()\n  tags: string[];\n}\n```\n\nThe full worked example (refs, open enums, discriminated unions, vectors,\nGeoPoints, bytes) is in [`examples/gen/chat/`](examples/gen/chat/).\n\n**3. Gate compatibility in CI.** Export the contract bundle per release and diff\neach change against the committed history; a breaking change fails CI.\n\n```sh\nfirepact-gen --module app.models --bundle-out schemas/v2.json\nfirepact-compat --history schemas --new schemas/v2.json\n```\n\nSee [usage](docs/usage.md) for the read/write/update views and the converter, and\n[the compatibility gate](docs/compatibility.md) for what counts as breaking.\n\n## Supported versions\n\nVerified in CI (see [`.github/workflows/ci.yaml`](.github/workflows/ci.yaml)).\n\n| Component | Supported | Notes |\n|---|---|---|\n| Python | 3.11 – 3.14 | one abi3 wheel covers 3.11+ |\n| Pydantic | 2.9 – 2.13 | drift canary; the exact schema golden is pinned to the locked version |\n| JSON Schema | Draft 2020-12 | Pydantic's default dialect |\n| TypeScript (output) | 5.x / 6.x / 7.x | type-checks under `verbatimModuleSyntax` + `isolatedModules` |\n| firebase JS SDK | v11+ | `Timestamp`, `GeoPoint`, `DocumentReference`, `Bytes`, `VectorValue`, `FieldValue`, `UpdateData`, `FirestoreDataConverter` |\n| Rust | 1.75+ | MSRV (`Cargo.toml`) |\n\nDependency bumps within these ranges are tracked by Dependabot.\n\n## Documentation\n\n- [**scope**](docs/scope.md) — what firepact is and is **not** (read this first)\n- [usage](docs/usage.md) — annotating models, the read/write/update views, the gate\n- [contract \u0026 projection](docs/contract.md) — the `x-firestore-*` vocabulary\n- [compatibility](docs/compatibility.md) — the `FULL_TRANSITIVE` gate and its taxonomy\n- [architecture](docs/architecture.md) — the two components and the single bundle\n- [`docs/adr/`](docs/adr/) — the decisions (the \"Why\")\n\n## How it works\n\n- **`firepact-core`** (Rust crate, binary `firepact`): pure, Python/Node-free.\n  `firepact emit` projects one enriched JSON Schema bundle into read/write/update\n  TypeScript; `firepact compat` is the gate.\n- **`firepact`** (Python package): imports your Pydantic models, delegates schema\n  generation to Pydantic, stamps the `x-firestore-*` vocabulary, and emits via the\n  native core. Console scripts: `firepact-gen`, `firepact-compat`, and\n  `pydantic2ts` (a drop-in alias for the prior tool).\n\n## Contributing\n\n```sh\njust build           # build the Rust core + `firepact` binary\njust test            # all tests (rust + python)\njust lint            # rust + python + markdown checks\njust example-gen     # regenerate the generation examples (examples/gen/)\njust example-compat  # gate the compat example against its committed history\n```\n\n## Prior art \u0026 license\n\nThe Firestore-specialised, from-scratch successor to\n[pydantic-to-typescript](https://github.com/hironow/pydantic-to-typescript)\n(which targeted FastAPI request/response types and depended on Node). MIT licensed\n([LICENSE](LICENSE)).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhironow%2Ffirepact","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhironow%2Ffirepact","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhironow%2Ffirepact/lists"}