@farming-labs/orm

Firestore

Firestore support in Farming Labs ORM is the document-runtime answer for teams that want one storage layer without owning a separate Firebase-only adapter stack.

The supported path is server-side Firestore clients, such as:

This is not the browser SDK path.

What this gives you

You still write the schema and storage layer once.

Then the runtime translates that layer into Firestore document operations:

That means a framework, auth package, billing package, or internal platform can keep one storage implementation while the app decides to use Firestore.

Create the runtime directly

firestore-runtime.ts
import { createOrm } from "@farming-labs/orm";
import { createFirestoreDriver } from "@farming-labs/orm-firestore";
import { Firestore } from "@google-cloud/firestore";
import { appSchema } from "./schema";

const db = new Firestore();

const orm = createOrm({
  schema: appSchema,
  driver: createFirestoreDriver({
    db,
  }),
});

Use the runtime helper path

If a library or framework wants to accept a raw Firestore client and normalize it later, use the runtime helpers instead:

import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

const orm = await createOrmFromRuntime({
  schema: appSchema,
  client: db,
});

That is the cleanest path for package-owned integrations.

Setup helpers

Firestore does not have SQL-style table DDL, so the setup helpers are honest about that.

import { bootstrapDatabase } from "@farming-labs/orm-runtime/setup";

const orm = await bootstrapDatabase({
  schema: appSchema,
  client: db,
});

Live verification

The repo also includes an opt-in live Firestore check for emulator or cloud setups.

Run it with:

terminal
pnpm --filter @farming-labs/orm-firestore test:real

Keep those secrets out of git and CI logs. For CI, prefer encrypted repository secrets rather than checked-in files.

If you want the real Firestore suite to run as part of the normal workspace test flow, export those same env vars before pnpm test:

export GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/service-account.json
export GOOGLE_CLOUD_PROJECT=your-firestore-project

pnpm test

Joins and relations

Firestore does not have native joins.

That does not mean relation selections stop working.

The ORM resolves relation branches through consecutive document queries, so code like this still works:

const user = await orm.user.findUnique({
  where: {
    email: "ada@farminglabs.dev",
  },
  select: {
    id: true,
    email: true,
    profile: {
      select: {
        bio: true,
      },
    },
    sessions: {
      select: {
        token: true,
      },
    },
  },
});

That is the same replacement story libraries like NextAuth.js or Better Auth need: write the storage layer once, and let the ORM resolve the document-database differences underneath it.

Transactions and constraints

When the Firestore client exposes runTransaction(...), the Firestore runtime uses it for writes that need consistency:

That matters because model-level unique constraints and compound unique constraints are checked inside that write flow rather than being left as best-effort client-side checks.

What is supported well

Important limits

Why this matters

This keeps Firestore inside the same bigger ORM story: