{"id":40049002,"url":"https://github.com/multipliedtwice/prisma-to-sql","last_synced_at":"2026-04-02T11:56:38.037Z","repository":{"id":333397566,"uuid":"1137144749","full_name":"multipliedtwice/prisma-to-sql","owner":"multipliedtwice","description":"Prisma read operations at raw sql speed","archived":false,"fork":false,"pushed_at":"2026-03-24T07:25:12.000Z","size":37229,"stargazers_count":20,"open_issues_count":9,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-25T09:05:59.008Z","etag":null,"topics":["boost","nodejs","performance","postgresql","prisma","sql","sqlite","typescript"],"latest_commit_sha":null,"homepage":"https://multipliedtwice.github.io/prisma-to-sql/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/multipliedtwice.png","metadata":{"files":{"readme":"readme.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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-01-19T01:19:27.000Z","updated_at":"2026-03-24T07:25:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/multipliedtwice/prisma-to-sql","commit_stats":null,"previous_names":["multipliedtwice/prisma-to-sql"],"tags_count":104,"template":false,"template_full_name":null,"purl":"pkg:github/multipliedtwice/prisma-to-sql","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multipliedtwice%2Fprisma-to-sql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multipliedtwice%2Fprisma-to-sql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multipliedtwice%2Fprisma-to-sql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multipliedtwice%2Fprisma-to-sql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/multipliedtwice","download_url":"https://codeload.github.com/multipliedtwice/prisma-to-sql/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multipliedtwice%2Fprisma-to-sql/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31305955,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T09:48:21.550Z","status":"ssl_error","status_checked_at":"2026-04-02T09:48:19.196Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["boost","nodejs","performance","postgresql","prisma","sql","sqlite","typescript"],"created_at":"2026-01-19T06:00:40.482Z","updated_at":"2026-04-02T11:56:38.020Z","avatar_url":"https://github.com/multipliedtwice.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# prisma-sql\n\n\u003cimg width=\"250\" height=\"170\" alt=\"image\" src=\"https://github.com/user-attachments/assets/3f9233f2-5d5c-41e3-b1cd-ced7ce0b54c2\" /\u003e\n\nPrerender Prisma queries to SQL and execute them directly via `postgres.js` or `better-sqlite3`.\n\n**Same Prisma API. Same Prisma types. Lower read overhead.**\n\n```ts\nimport { PrismaClient } from '@prisma/client'\nimport { speedExtension, type SpeedClient } from './generated/sql'\nimport postgres from 'postgres'\n\nconst sql = postgres(process.env.DATABASE_URL!)\nconst basePrisma = new PrismaClient()\n\nexport const prisma = basePrisma.$extends(\n  speedExtension({ postgres: sql }),\n) as SpeedClient\u003ctypeof basePrisma\u003e\n\nconst users = await prisma.user.findMany({\n  where: { status: 'ACTIVE' },\n  include: { posts: true },\n})\n\nconst dashboard = await prisma.$batch((batch) =\u003e ({\n  activeUsers: batch.user.count({ where: { status: 'ACTIVE' } }),\n  recentPosts: batch.post.findMany({\n    take: 10,\n    orderBy: { createdAt: 'desc' },\n  }),\n  taskStats: batch.task.aggregate({\n    _count: true,\n    _avg: { estimatedHours: true },\n  }),\n}))\n```\n\n## What it does\n\n`prisma-sql` accelerates Prisma **read** queries by skipping Prisma's read execution path and running generated SQL directly through a database-native client.\n\nIt keeps the Prisma client for:\n\n- schema and migrations\n- generated types\n- writes\n- fallback for unsupported cases\n\nIt accelerates:\n\n- `findMany`\n- `findFirst`\n- `findUnique`\n- `count`\n- `aggregate`\n- `groupBy`\n- PostgreSQL `$batch`\n\n## Why use it\n\nPrisma's DX is excellent, but read queries still pay runtime overhead for query-engine planning, validation, transformation, and result mapping.\n\n`prisma-sql` moves that work out of the hot path:\n\n- builds SQL from Prisma-style query args\n- can prebake hot queries at generate time\n- executes via `postgres.js` or `better-sqlite3`\n- maps results back to Prisma-like shapes\n\nThe goal is simple:\n\n- keep Prisma's developer experience\n- cut read-path overhead\n- stay compatible with existing Prisma code\n\n## Installation\n\n### PostgreSQL\n\n```bash\nnpm install prisma-sql postgres\n```\n\n### SQLite\n\n```bash\nnpm install prisma-sql better-sqlite3\n```\n\n## Quick start\n\n### 1) Add the generator\n\n```prisma\ngenerator client {\n  provider = \"prisma-client\"\n}\n\ngenerator sql {\n  provider = \"prisma-sql-generator\"\n}\n\nmodel User {\n  id     Int    @id @default(autoincrement())\n  email  String @unique\n  status String\n  posts  Post[]\n}\n\nmodel Post {\n  id        Int    @id @default(autoincrement())\n  title     String\n  authorId  Int\n  author    User   @relation(fields: [authorId], references: [id])\n}\n```\n\n### 2) Generate\n\n```bash\nnpx prisma generate\n```\n\nThis generates `./generated/sql/index.ts`.\n\n### 3) Extend Prisma\n\n### PostgreSQL\n\n```ts\nimport { PrismaClient } from '@prisma/client'\nimport { speedExtension, type SpeedClient } from './generated/sql'\nimport postgres from 'postgres'\n\nconst sql = postgres(process.env.DATABASE_URL!)\nconst basePrisma = new PrismaClient()\n\nexport const prisma = basePrisma.$extends(\n  speedExtension({ postgres: sql }),\n) as SpeedClient\u003ctypeof basePrisma\u003e\n```\n\n### SQLite\n\n```ts\nimport { PrismaClient } from '@prisma/client'\nimport { speedExtension, type SpeedClient } from './generated/sql'\nimport Database from 'better-sqlite3'\n\nconst db = new Database('./data.db')\nconst basePrisma = new PrismaClient()\n\nexport const prisma = basePrisma.$extends(\n  speedExtension({ sqlite: db }),\n) as SpeedClient\u003ctypeof basePrisma\u003e\n```\n\n### With existing Prisma extensions\n\nApply `speedExtension` last so it sees the final query surface.\n\n```ts\nimport { PrismaClient } from '@prisma/client'\nimport { speedExtension, type SpeedClient } from './generated/sql'\nimport postgres from 'postgres'\n\nconst sql = postgres(process.env.DATABASE_URL!)\nconst basePrisma = new PrismaClient()\n\nconst extendedPrisma = basePrisma\n  .$extends(myCustomExtension)\n  .$extends(anotherExtension)\n\nexport const prisma = extendedPrisma.$extends(\n  speedExtension({ postgres: sql }),\n) as SpeedClient\u003ctypeof extendedPrisma\u003e\n```\n\n## Supported queries\n\n### Accelerated\n\n- `findMany`\n- `findFirst`\n- `findUnique`\n- `count`\n- `aggregate`\n- `groupBy`\n- `$batch` for PostgreSQL\n\n### Not accelerated\n\nThese continue to run through Prisma:\n\n- `create`\n- `update`\n- `delete`\n- `upsert`\n- `createMany`\n- `updateMany`\n- `deleteMany`\n\n### Fallback behavior\n\nIf a query shape is unsupported or cannot be accelerated safely, the extension falls back to Prisma instead of returning incorrect results.\n\nEnable `debug: true` to see generated SQL and fallback behavior.\n\n## Features\n\n### 1) Runtime SQL generation\n\nAny supported read query can be converted from Prisma args into SQL at runtime.\n\n```ts\nconst users = await prisma.user.findMany({\n  where: {\n    status: 'ACTIVE',\n    email: { contains: '@example.com' },\n  },\n  orderBy: { createdAt: 'desc' },\n  take: 20,\n})\n```\n\n### 2) Prebaked hot queries with `@optimize`\n\nFor the hottest query shapes, you can prebake SQL at generate time.\n\n```prisma\n/// @optimize {\n///   \"method\": \"findMany\",\n///   \"query\": {\n///     \"where\": { \"status\": \"ACTIVE\" },\n///     \"orderBy\": { \"createdAt\": \"desc\" },\n///     \"skip\": \"$skip\",\n///     \"take\": \"$take\"\n///   }\n/// }\nmodel User {\n  id        Int      @id @default(autoincrement())\n  email     String   @unique\n  status    String\n  createdAt DateTime @default(now())\n}\n```\n\nAt runtime:\n\n- matching query shape → prebaked SQL\n- non-matching query shape → runtime SQL generation\n\n### 3) PostgreSQL batch queries\n\n`$batch` combines multiple independent read queries into one round trip.\n\n```ts\nconst results = await prisma.$batch((batch) =\u003e ({\n  users: batch.user.findMany({ where: { status: 'ACTIVE' } }),\n  posts: batch.post.count(),\n  stats: batch.task.aggregate({ _count: true }),\n}))\n```\n\n### 4) Include and relation reduction\n\nFor supported include trees, `prisma-sql` can execute flat SQL and reduce rows back into Prisma-like nested results.\n\n### 5) Aggregate result type handling\n\nAggregates are mapped back to Prisma-style value types instead of flattening everything into strings or plain numbers.\n\nThat includes preserving types like:\n\n- `Decimal`\n- `BigInt`\n- `DateTime`\n- `_count`\n\n## Query examples\n\n### Filters\n\n```ts\n{ age: { gt: 18, lte: 65 } }\n{ status: { in: ['ACTIVE', 'PENDING'] } }\n{ status: { notIn: ['DELETED'] } }\n\n{ email: { contains: '@example.com' } }\n{ email: { startsWith: 'user' } }\n{ email: { endsWith: '.com' } }\n{ email: { contains: 'EXAMPLE', mode: 'insensitive' } }\n\n{ AND: [{ status: 'ACTIVE' }, { verified: true }] }\n{ OR: [{ role: 'ADMIN' }, { role: 'MODERATOR' }] }\n{ NOT: { status: 'DELETED' } }\n\n{ deletedAt: null }\n{ deletedAt: { not: null } }\n```\n\n### Relations\n\n```ts\n{\n  include: {\n    posts: true,\n    profile: true,\n  }\n}\n```\n\n```ts\n{\n  include: {\n    posts: {\n      where: { published: true },\n      orderBy: { createdAt: 'desc' },\n      take: 5,\n      include: {\n        comments: true,\n      },\n    },\n  },\n}\n```\n\n```ts\n{\n  where: {\n    posts: { some: { published: true } },\n  },\n}\n```\n\n```ts\n{\n  where: {\n    posts: { every: { published: true } },\n  },\n}\n```\n\n```ts\n{\n  where: {\n    posts: { none: { published: false } },\n  },\n}\n```\n\n### Pagination and ordering\n\n```ts\n{\n  take: 10,\n  skip: 20,\n  orderBy: { createdAt: 'desc' },\n}\n```\n\n```ts\n{\n  cursor: { id: 100 },\n  skip: 1,\n  take: 10,\n  orderBy: { id: 'asc' },\n}\n```\n\n```ts\n{\n  orderBy: [\n    { status: 'asc' },\n    { priority: 'desc' },\n    { createdAt: 'desc' },\n  ],\n}\n```\n\n### Composite cursor pagination\n\nFor composite cursors, use an `orderBy` that starts with the cursor fields in the same order.\n\n```ts\n{\n  cursor: { tenantId: 10, id: 500 },\n  skip: 1,\n  take: 20,\n  orderBy: [\n    { tenantId: 'asc' },\n    { id: 'asc' },\n  ],\n}\n```\n\nThis matches keyset pagination expectations and avoids unstable page boundaries.\n\n### Aggregates\n\n```ts\nawait prisma.user.count({\n  where: { status: 'ACTIVE' },\n})\n```\n\n```ts\nawait prisma.task.aggregate({\n  where: { status: 'DONE' },\n  _count: { _all: true },\n  _sum: { estimatedHours: true },\n  _avg: { estimatedHours: true },\n  _min: { startedAt: true },\n  _max: { completedAt: true },\n})\n```\n\n```ts\nawait prisma.task.groupBy({\n  by: ['status', 'priority'],\n  _count: { _all: true },\n  _avg: { estimatedHours: true },\n  having: {\n    status: {\n      _count: { gte: 5 },\n    },\n  },\n})\n```\n\n## Cardinality planner\n\nThe cardinality planner is the piece that decides how relation-heavy reads should be executed for best performance.\n\nIn practice, it helps choose between strategies such as:\n\n- direct joins\n- lateral/subquery-style fetches\n- flat row expansion + reducer\n- segmented follow-up loading for high fan-out relations\n\nThis matters because the fastest strategy depends on **cardinality**, not just query shape.\n\nA `profile` include behaves very differently from a `posts.comments.likes` include.\n\n### Why it matters\n\nA naive join strategy can explode row counts:\n\n- `User -\u003e Profile` is usually low fan-out\n- `User -\u003e Posts -\u003e Comments` can multiply rows aggressively\n- `Organization -\u003e Users -\u003e Sessions -\u003e Events` can become huge very quickly\n\nThe planner tries to keep read amplification under control.\n\n### Best setup for the planner\n\nTo get the best results, prepare your schema and indexes so the planner can make good choices.\n\n#### 1) Model real cardinality accurately\n\nUse correct relation fields and uniqueness constraints.\n\nGood examples:\n\n```prisma\nmodel User {\n  id      Int      @id @default(autoincrement())\n  profile Profile?\n  posts   Post[]\n}\n\nmodel Profile {\n  id      Int  @id @default(autoincrement())\n  userId  Int  @unique\n  user    User @relation(fields: [userId], references: [id])\n}\n\nmodel Post {\n  id       Int  @id @default(autoincrement())\n  authorId Int\n  author   User @relation(fields: [authorId], references: [id])\n\n  @@index([authorId])\n}\n```\n\nWhy this helps:\n\n- `@unique` on one-to-one foreign keys tells the planner the relation is bounded\n- indexes on one-to-many foreign keys make follow-up or segmented loading cheap\n\n#### 2) Index every foreign key used in includes and relation filters\n\nAt minimum, index:\n\n- all `@relation(fields: [...])` foreign keys on the child side\n- fields used in nested `where`\n- fields used in nested `orderBy`\n- fields used in cursor pagination\n\nExample:\n\n```prisma\nmodel Comment {\n  id        Int      @id @default(autoincrement())\n  postId    Int\n  createdAt DateTime @default(now())\n  published Boolean  @default(false)\n\n  post Post @relation(fields: [postId], references: [id])\n\n  @@index([postId])\n  @@index([postId, createdAt])\n  @@index([postId, published])\n}\n```\n\n#### 3) Prefer deterministic nested ordering\n\nWhen including collections, always provide a stable order when practical.\n\n```ts\nconst users = await prisma.user.findMany({\n  include: {\n    posts: {\n      orderBy: { createdAt: 'desc' },\n      take: 5,\n    },\n  },\n})\n```\n\nThat helps both the planner and the reducer keep result shapes predictable.\n\n### What to configure\n\nUse the cardinality planner wherever your generator/runtime exposes it.\n\nBecause config names can differ between versions, the safe rule is:\n\n- enable the planner in generator/runtime config if your build exposes that switch\n- keep it on for relation-heavy workloads\n- tune any thresholds only after measuring with real production-shaped queries\n\nIf your project has planner thresholds, start conservatively:\n\n- prefer bounded strategies for one-to-one and unique includes\n- prefer segmented or reduced strategies for one-to-many and many-to-many\n- lower thresholds for deep includes with large child tables\n- raise thresholds only after verifying lower fan-out in production data\n\n### How to verify the planner is helping\n\nUse `debug` and `onQuery`.\n\nLook for:\n\n- large latency spikes on include-heavy queries\n- unusually large result sets for a small parent page\n- repeated slow nested includes on high-fanout relations\n\n```ts\nconst prisma = basePrisma.$extends(\n  speedExtension({\n    postgres: sql,\n    debug: true,\n    onQuery: (info) =\u003e {\n      console.log(`${info.model}.${info.method} ${info.duration}ms`)\n      console.log(info.sql)\n    },\n  }),\n) as SpeedClient\u003ctypeof basePrisma\u003e\n```\n\nWhat good results look like:\n\n- small parent page stays small in latency\n- bounded child includes remain predictable\n- high-fanout includes stop exploding row counts\n- moving a heavy include into `$batch` or splitting it improves latency materially\n\n## Deployment without database access at build time\n\nThe cardinality planner collects relation statistics and roundtrip cost measurements directly from the database during `prisma generate`. In CI/CD pipelines or containerized builds, the database is often unreachable.\n\n### Skip planner during generation\n\nSet `PRISMA_SQL_SKIP_PLANNER=true` to skip stats collection at generate time. The generator will emit default planner values instead.\n\n```bash\nPRISMA_SQL_SKIP_PLANNER=true npx prisma generate\n```\n\n### Collect stats before server start\n\nRun `prisma-sql-collect-stats` as a pre-start step, after deployment, when the database is reachable.\n\n```bash\nprisma-sql-collect-stats \\\n  --output dist/prisma/generated/sql/planner.generated.js \\\n  --prisma-client dist/prisma/generated/client/index.js\n```\n\n| Flag              | Default                                            | Description                                                    |\n| ----------------- | -------------------------------------------------- | -------------------------------------------------------------- |\n| `--output`        | `./dist/prisma/generated/sql/planner.generated.js` | Path to the generated planner module                           |\n| `--prisma-client` | `@prisma/client`                                   | Path to the compiled Prisma client (must expose `Prisma.dmmf`) |\n\nThe script reads `DATABASE_URL` from the environment (supports `.env` via `dotenv`). If the connection fails or times out, it exits silently without blocking startup.\n\n### Example scripts\n\n```json\n{\n  \"prisma:generate\": \"PRISMA_SQL_SKIP_PLANNER=true prisma generate\",\n  \"collect-planner-stats\": \"prisma-sql-collect-stats --output dist/prisma/generated/sql/planner.generated.js --prisma-client dist/prisma/generated/client/index.js\",\n  \"start:production\": \"yarn collect-planner-stats; node dist/src/index.js\"\n}\n```\n\nThe semicolon (`;`) after `collect-planner-stats` ensures the server starts even if stats collection fails. Use `\u0026\u0026` instead if you want startup to abort on failure.\n\n### What happens with default planner values\n\nWhen stats are not collected, the planner uses conservative defaults:\n\n- `roundtripRowEquivalent`: 73\n- `jsonRowFactor`: 1.5\n- `relationStats`: empty (all relations treated as unknown cardinality)\n\nThis means the planner cannot make informed decisions about join strategies. Queries still work correctly — the planner falls back to safe general-purpose strategies — but relation-heavy reads may not use the optimal execution plan.\n\n### Timeout control\n\nStats collection has a default timeout of 15 seconds. Override with:\n\n```bash\nPRISMA_SQL_PLANNER_TIMEOUT_MS=5000 yarn collect-planner-stats\n```\n\n### Practical recommendations\n\nFor best results with the planner:\n\n1. index all relation keys\n2. encode one-to-one relations with `@unique`\n3. use stable `orderBy`\n4. cap nested collections with `take`\n5. page parents before including deep trees\n6. split unrelated heavy branches into `$batch`\n7. benchmark with real data distributions, not toy fixtures\n\n## Batch queries\n\n`$batch` runs multiple independent read queries in one PostgreSQL round trip.\n\n```ts\nconst dashboard = await prisma.$batch((batch) =\u003e ({\n  totalUsers: batch.user.count(),\n  activeUsers: batch.user.count({\n    where: { status: 'ACTIVE' },\n  }),\n  recentProjects: batch.project.findMany({\n    take: 5,\n    orderBy: { createdAt: 'desc' },\n    include: { organization: true },\n  }),\n  taskStats: batch.task.aggregate({\n    _count: true,\n    _avg: { estimatedHours: true },\n    where: { status: 'IN_PROGRESS' },\n  }),\n}))\n```\n\n### Rules\n\nDo not `await` inside the callback.\n\nIncorrect:\n\n```ts\nawait prisma.$batch(async (batch) =\u003e ({\n  users: await batch.user.findMany(),\n}))\n```\n\nCorrect:\n\n```ts\nawait prisma.$batch((batch) =\u003e ({\n  users: batch.user.findMany(),\n}))\n```\n\n### Best use cases\n\n- dashboards\n- analytics summaries\n- counts + page data\n- multiple independent aggregates\n- splitting unrelated heavy reads instead of building one massive include tree\n\n### Limitations\n\n- PostgreSQL only\n- queries are independent\n- not transactional\n- use `$transaction` when you need transactional guarantees\n\n## Configuration\n\n### Debug logging\n\n```ts\nconst prisma = basePrisma.$extends(\n  speedExtension({\n    postgres: sql,\n    debug: true,\n  }),\n) as SpeedClient\u003ctypeof basePrisma\u003e\n```\n\n### Performance hook\n\n```ts\nconst prisma = basePrisma.$extends(\n  speedExtension({\n    postgres: sql,\n    onQuery: (info) =\u003e {\n      console.log(`${info.model}.${info.method}: ${info.duration}ms`)\n      console.log(`prebaked=${info.prebaked}`)\n    },\n  }),\n) as SpeedClient\u003ctypeof basePrisma\u003e\n```\n\nThe callback receives:\n\n```ts\ninterface QueryInfo {\n  model: string\n  method: string\n  sql: string\n  params: unknown[]\n  duration: number\n  prebaked: boolean\n}\n```\n\n## Generator configuration\n\n```prisma\ngenerator sql {\n  provider = \"prisma-sql-generator\"\n\n  // optional\n  // dialect = \"postgres\"\n\n  // optional\n  // output = \"./generated/sql\"\n\n  // optional\n  // skipInvalid = \"true\"\n}\n```\n\n## `@optimize` examples\n\n### Basic prebaked query\n\n```prisma\n/// @optimize {\n///   \"method\": \"findMany\",\n///   \"query\": {\n///     \"where\": { \"status\": \"ACTIVE\" }\n///   }\n/// }\nmodel User {\n  id     Int    @id\n  status String\n}\n```\n\n### Dynamic parameters\n\n```prisma\n/// @optimize {\n///   \"method\": \"findMany\",\n///   \"query\": {\n///     \"where\": { \"status\": \"$status\" },\n///     \"skip\": \"$skip\",\n///     \"take\": \"$take\"\n///   }\n/// }\nmodel User {\n  id     Int    @id\n  status String\n}\n```\n\n### Nested include\n\n```prisma\n/// @optimize {\n///   \"method\": \"findMany\",\n///   \"query\": {\n///     \"include\": {\n///       \"posts\": {\n///         \"where\": { \"published\": true },\n///         \"orderBy\": { \"createdAt\": \"desc\" },\n///         \"take\": 5\n///       }\n///     }\n///   }\n/// }\nmodel User {\n  id    Int    @id\n  posts Post[]\n}\n```\n\n## Edge usage\n\n### Vercel Edge\n\n```ts\nimport { PrismaClient } from '@prisma/client'\nimport { speedExtension, type SpeedClient } from './generated/sql'\nimport postgres from 'postgres'\n\nconst sql = postgres(process.env.DATABASE_URL!)\nconst prisma = new PrismaClient().$extends(\n  speedExtension({ postgres: sql }),\n) as SpeedClient\u003ctypeof PrismaClient\u003e\n\nexport const config = { runtime: 'edge' }\n\nexport default async function handler() {\n  const users = await prisma.user.findMany()\n  return Response.json(users)\n}\n```\n\n### Cloudflare Workers\n\nUse the standalone SQL generation API.\n\n```ts\nimport { createToSQL } from 'prisma-sql'\nimport { MODELS } from './generated/sql'\n\nconst toSQL = createToSQL(MODELS, 'sqlite')\n\nexport default {\n  async fetch(request: Request, env: Env) {\n    const { sql, params } = toSQL('User', 'findMany', {\n      where: { status: 'ACTIVE' },\n    })\n\n    const result = await env.DB.prepare(sql)\n      .bind(...params)\n      .all()\n\n    return Response.json(result.results)\n  },\n}\n```\n\n## Performance\n\nPerformance depends on:\n\n- database type\n- query shape\n- indexing\n- relation fan-out\n- whether the query is prebaked\n- whether the cardinality planner can choose a bounded strategy\n\nTypical gains are strongest when:\n\n- Prisma overhead dominates total time\n- includes are moderate but structured well\n- query shapes repeat\n- indexes exist on relation and filter columns\n\nRun your own benchmarks on production-shaped data.\n\n## Troubleshooting\n\n### `speedExtension requires postgres or sqlite client`\n\nPass a database-native client to the generated extension.\n\n```ts\nconst prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))\n```\n\n### Generated dialect mismatch\n\nIf generated code targets PostgreSQL, do not pass SQLite, and vice versa.\n\nOverride dialect in the generator if needed.\n\n```prisma\ngenerator sql {\n  provider = \"prisma-sql-generator\"\n  dialect  = \"postgres\"\n}\n```\n\n### Results differ from Prisma\n\nTurn on debug logging and compare generated SQL with Prisma query logs.\n\n```ts\nconst prisma = new PrismaClient().$extends(\n  speedExtension({\n    postgres: sql,\n    debug: true,\n  }),\n)\n```\n\nIf behavior differs, open an issue with:\n\n- schema excerpt\n- Prisma query\n- generated SQL\n- expected result\n- actual result\n\n### Performance is worse on a relation-heavy query\n\nCheck these first:\n\n- missing foreign-key indexes\n- deep unbounded includes\n- no nested `take`\n- unstable or missing `orderBy`\n- high-fanout relation trees that should be split into `$batch`\n\n### Connection pool exhaustion\n\nIncrease `postgres.js` pool size if needed.\n\n```ts\nconst sql = postgres(process.env.DATABASE_URL!, {\n  max: 50,\n})\n```\n\n## Limitations\n\n### Partially supported\n\n- basic array operators\n- basic JSON path filtering\n\n### Not yet supported\n\nThese should fall back to Prisma:\n\n- full-text `search`\n- composite/document-style embedded types\n- vendor-specific extensions not yet modeled by the SQL builder\n- some advanced `groupBy` edge cases\n\n## FAQ\n\n**Do I still need Prisma?**  \nYes. Prisma remains the source of truth for schema, migrations, types, writes, and fallback behavior.\n\n**Does this replace Prisma Client?**  \nNo. It extends Prisma Client.\n\n**What gets accelerated?**  \nSupported read queries only.\n\n**What about writes?**  \nWrites continue through Prisma.\n\n**Do I need `@optimize`?**  \nNo. It is optional. It only reduces the overhead of repeated hot query shapes.\n\n**Does `$batch` work with SQLite?**  \nNot currently.\n\n**Is it safe to use in production?**  \nUse it the same way you would adopt any query-path optimization layer: benchmark it on real data, compare against Prisma for parity, and keep Prisma fallback enabled for unsupported cases.\n\n## Migration\n\n### Before\n\n```ts\nconst prisma = new PrismaClient()\nconst users = await prisma.user.findMany()\n```\n\n### After\n\n```ts\nimport { PrismaClient } from '@prisma/client'\nimport { speedExtension, type SpeedClient } from './generated/sql'\nimport postgres from 'postgres'\n\nconst sql = postgres(process.env.DATABASE_URL!)\nconst basePrisma = new PrismaClient()\n\nexport const prisma = basePrisma.$extends(\n  speedExtension({ postgres: sql }),\n) as SpeedClient\u003ctypeof basePrisma\u003e\n\nconst users = await prisma.user.findMany()\n```\n\n## Examples\n\n- `examples/generator-mode`\n- `tests/e2e/postgres.test.ts`\n- `tests/e2e/sqlite.e2e.test.ts`\n- `tests/sql-injection/batch-transaction.test.ts`\n\n## Development\n\n```bash\ngit clone https://github.com/multipliedtwice/prisma-to-sql\ncd prisma-sql\nnpm install\nnpm run build\nnpm test\n```\n\n## License\n\nMIT\n\n## Links\n\n- [NPM Package](https://www.npmjs.com/package/prisma-sql)\n- [GitHub Repository](https://github.com/multipliedtwice/prisma-to-sql)\n- [Issue Tracker](https://github.com/multipliedtwice/prisma-to-sql/issues)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmultipliedtwice%2Fprisma-to-sql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmultipliedtwice%2Fprisma-to-sql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmultipliedtwice%2Fprisma-to-sql/lists"}