🔥 Pre-pub release · v0.0.15 · 62 tests passing

One YAML spec.
Your entire Firestore data layer.

Stop hand-writing the 9-file fan-out for every new field. firepack generates typed Dart models, Riverpod providers, repositories, security rules, indexes, storage paths, and a Mermaid graph — deterministically, on every save.

🚀 Pure Dart CLI · no build_runner · 🎯 Deterministic output · 👀 Watch mode · 🧪 Snapshot-tested generators

Write the spec. Get the data layer.

One firepack.yaml declares your collections, fields, indexes, queries, and rules. Click any tab to see what gets generated.

firepack.yaml
input
firepack: 1
project: blog
collections:
  posts:
    tenant: organizationId
    fields:
      id:             { type: string, primaryKey: true }
      title:          { type: string, required: true }
      status:         { type: "enum[PostStatus]", default: draft }
      createdAt:      { type: dateTime, required: true,
                        serverDefault: now }
      organizationId: { type: "ref[organizations.id]",
                        required: true }
    indexes:
      - fields: [organizationId, status, createdAt:desc]
    queries:
      watchByOrg:
        where: [tenant]
        orderBy: createdAt:desc
        limit: 50
generated
output
// GENERATED by firepack — do not edit.

import 'enums.dart';

class Post {
  final String id;
  final String organizationId;
  final String title;
  final PostStatus status;
  final DateTime createdAt;

  const Post({
    required this.id,
    required this.organizationId,
    required this.title,
    this.status = PostStatus.draft,
    required this.createdAt,
  });

  Post copyWith({String? id, String? title, PostStatus? status, ...}) =>
      Post(id: id ?? this.id, ...);

  // Pure JSON — for REST APIs, hashing, snapshot tests.
  Map<String, dynamic> toJson() => {
    'id': id, 'title': title, 'status': status.toJson(),
    'createdAt': createdAt.toIso8601String(), ...
  };
  factory Post.fromJson(Map<String, dynamic> json) => ...;

  // Firestore-native — DateTime stays DateTime,
  // cloud_firestore writes Timestamp.
  Map<String, dynamic> toFirestore() => {
    'id': id, 'title': title, 'status': status.toJson(),
    'createdAt': createdAt, ...
  };
  factory Post.fromFirestore(Map<String, dynamic> data) => Post(
    createdAt: _toDateTime(data['createdAt']),  // duck-typed
    ...
  );

  @override bool operator ==(Object other) => ... // value equality
  @override int get hashCode => Object.hash(...);
}

And also: paths.dart · firestore_provider.dart · storage_paths.dart · DATA_MODEL.md — see the full feature grid.

Nine targets, one source of truth.

Every output is deterministic — two runs on the same spec produce byte-identical files. Snapshot tests catch any drift.

🧱

Dart models

Immutable, value-equal, copyWith, both pure-JSON and Firestore-native (de)serialisation. Handles Timestamp ⇄ DateTime via duck-typing — models stay framework-agnostic.

🔌

Repositories

Typed Stream<List<X>> per spec query. add(merge:), updateById, deleteById, watchById.

🎯

Riverpod providers

StreamProvider.family per query. Single firestoreProvider DI seam — override once, every repo follows.

🛡️

Security rules

Per-collection read/create/update/delete with reusable helpers (isSignedIn, tenantSelf, …). Deny-all default for unmatched paths.

🗂️

Composite indexes

firestore.indexes.json sorted, deterministic, dedup'd. Diffs cleanly in PRs — no more "but it worked locally" because the deploy missed an index.

📁

Storage paths

Typed methods per declared bucket. Drift between upload code, read code, and rules becomes a compile error, not a 3am page.

🌐

Mermaid graph

ER diagram with FKs, nested types, storage cross-system edges. Renders inline on GitHub — your data architecture stays documented automatically.

🔍

Spec diff

firepack diff --old --new renders a Markdown report — added / removed / changed collections, fields, indexes. Drop-in for PR comments.

👀

Watch mode

firepack watch regenerates every configured target on every spec save. Pure Dart watcher — no build_runner, no codegen DAG, no part-files.

The loop is the feature.

firepack stays out of your way. No build_runner, no part-files, no codegen DAG. One spec, one watcher, one regenerated tree.

1

Edit spec

Add a field to firepack.yaml.

2

Save

firepack watch notices instantly.

3

Regenerate

Models, repos, rules, indexes — all rewritten.

4

App reloads

Flutter hot-reloads against the new shape. Done.

Install in 30 seconds.

Pre-pub-dev — install from a local clone.

terminal
# Clone + install (path-source, picks up git pull automatically)
git clone https://github.com/moinsen-dev/firepack && cd firepack
dart pub global activate --source path .
export PATH="$PATH:$HOME/.pub-cache/bin"   # add to ~/.zshrc

# Try the bundled example app
just example-regen          # regenerate the whole data layer
just example-emulator-up    # boot Firebase Emulator (other terminal)
just example-run            # run Flutter app against the emulator

Need details? The full quick-start in the README walks through the example app's CRUD demo end-to-end.

🌟

Saved you a fan-out?

firepack is built bootstrap-style — every feature lands when a real consumer hits a real pain. If it saved you boilerplate, star the repo so others can find it.

Star firepack on GitHub