@farming-labs/orm

Prisma

Prisma support in this repo is now both:

What Prisma support means today

Today, Prisma support means:

Runtime translation status

The unified query methods now translate into Prisma delegate calls through @farming-labs/orm-prisma.

That means code like this:

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

can execute through PrismaClient without the library author rewriting the same storage helper for Prisma separately.

Supported direct relation branches now translate into Prisma's native nested select tree instead of always bouncing back through the fallback resolver. That means reads like session.user and session.user.profile stay on the base Prisma delegate call.

Simple explicit join-table manyToMany(...) convenience traversal now also stays on the base Prisma delegate call. The driver rewrites that logical branch through the generated join model instead of issuing follow-up runtime queries.

When a query still falls back, that does not mean Prisma itself lacks relation loading support. It means the Farming ORM Prisma driver has not yet translated that logical relation shape natively. The remaining fallback cases are mostly relation branches that add their own filtering, ordering, paging, or other planner requirements beyond the currently translated subset.

Runtime setup

import { createOrm } from "@farming-labs/orm";
import { createPrismaDriver } from "@farming-labs/orm-prisma";
import { PrismaClient } from "@prisma/client";
import { authSchema } from "./schema";

const prisma = new PrismaClient();

const orm = createOrm({
  schema: authSchema,
  driver: createPrismaDriver({
    client: prisma,
  }),
});

From there, the unified runtime methods execute through Prisma:

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

Relation support

The Prisma runtime package already covers:

The important detail is that explicit join-table manyToMany(...) traversal is still modeled through the Farming ORM join table contract. The native Prisma path does not rely on Prisma implicit many-to-many shortcuts. It rewrites the logical convenience branch through the generated join model instead.

That keeps the runtime behavior aligned with the same schema contract used by the SQL and Mongoose drivers.

Optional model mapping

If the logical model name and the Prisma delegate name do not match exactly, you can pass delegate overrides:

const orm = createOrm({
  schema: authSchema,
  driver: createPrismaDriver({
    client: prisma,
    models: {
      user: "user",
      session: "session",
    },
  }),
});

Most apps that generate Prisma from the same Farming ORM schema do not need this.

Basic generator config

import { defineConfig } from "@farming-labs/orm-cli";
import { authSchema } from "./src/schema";

export default defineConfig({
  schemas: [authSchema],
  targets: {
    prisma: {
      out: "./generated/prisma/schema.prisma",
      provider: "postgresql",
      mode: "replace",
    },
  },
});

Supported Prisma providers

The runtime driver talks to PrismaClient, so it follows Prisma’s provider surface for relational apps. In this repo, the real local integration coverage is currently exercised against SQLite, PostgreSQL, and MySQL.

That live Prisma matrix now also covers:

Helpful references

What gets generated

Given this field:

email: string().unique().map("email_address");

the Prisma output looks like:

email String @unique @map("email_address")

If you want the Prisma schema as a string instead of writing a file, use renderPrismaSchema(...) from @farming-labs/orm. The CLI page has a full example: CLI generation.

For relations, the generator covers the direct cases cleanly:

Example generated model

model User {
  id        String   @id @default(cuid())
  email     String   @unique @map("email_address")
  name      String
  createdAt DateTime @default(now())

  @@map("users")
}

How it fits into a Prisma app

Typical flow:

  1. define schema in @farming-labs/orm
  2. run farm-orm generate prisma
  3. run normal Prisma commands in the app
  4. create the typed runtime with createPrismaDriver({ client: prisma })
  5. keep library storage helpers on the unified API instead of rewriting them for Prisma

In other words, Farming Labs ORM can now both feed Prisma and execute through Prisma.

Replace mode vs block mode

Replace mode

Use this when the generated schema should own the whole output file.

prisma: {
  out: "./generated/prisma/schema.prisma",
  provider: "postgresql",
  mode: "replace",
}

Block mode

Use this when the application wants generated Prisma content inserted inside a managed block of an existing file.

prisma: {
  out: "./prisma/schema.prisma",
  provider: "postgresql",
  mode: "block",
}

Local verification

The repo verifies Prisma locally against SQLite, PostgreSQL, and MySQL with a real generated PrismaClient.

Run it with:

terminal
pnpm test:local:prisma

Demo in this repo

There is also a small app-level demo that uses one shared auth flow and swaps the runtime underneath it. One of those adapters is the real generated Prisma client path, and it double-checks the created user with a direct Prisma query.

Run it with:

terminal
pnpm --filter demo demo -- all
pnpm --filter demo demo -- prisma

The same demo registry also supports:

Runtime family

The Prisma runtime now lives alongside the other shipped runtime packages:

The important constraint is the same across all of them: they plug into one shared query contract instead of redefining the API per stack.