prisma-sql 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md ADDED
@@ -0,0 +1,994 @@
1
+ # @dee-wan/prisma-sql
2
+
3
+ Speed up Prisma reads **2-7x** by executing queries via postgres.js instead of Prisma's query engine.
4
+
5
+ ```typescript
6
+ const sql = postgres(DATABASE_URL)
7
+ const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
8
+
9
+ // Same Prisma API, 2-7x faster reads
10
+ const users = await prisma.user.findMany({ where: { status: 'ACTIVE' } })
11
+ ```
12
+
13
+ ## Why?
14
+
15
+ Prisma's query engine adds overhead even in v7:
16
+
17
+ - Query translation and validation layer
18
+ - Type checking and transformation
19
+ - Query planning and optimization
20
+ - Result serialization and mapping
21
+
22
+ This extension bypasses the engine for read queries and executes raw SQL directly via postgres.js or better-sqlite3.
23
+
24
+ **Result:** Same API, same types, 2-7x faster reads.
25
+
26
+ ## Installation
27
+
28
+ **PostgreSQL:**
29
+
30
+ ```bash
31
+ npm install @dee-wan/prisma-sql postgres
32
+ ```
33
+
34
+ **SQLite:**
35
+
36
+ ```bash
37
+ npm install @dee-wan/prisma-sql better-sqlite3
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ### PostgreSQL
43
+
44
+ ```typescript
45
+ import { PrismaClient } from '@prisma/client'
46
+ import { speedExtension } from '@dee-wan/prisma-sql'
47
+ import postgres from 'postgres'
48
+
49
+ const sql = postgres(process.env.DATABASE_URL)
50
+ const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
51
+
52
+ // All reads now execute via postgres.js
53
+ const users = await prisma.user.findMany({
54
+ where: { status: 'ACTIVE' },
55
+ include: { posts: true },
56
+ })
57
+ ```
58
+
59
+ ### SQLite
60
+
61
+ ```typescript
62
+ import { PrismaClient } from '@prisma/client'
63
+ import { speedExtension } from '@dee-wan/prisma-sql'
64
+ import Database from 'better-sqlite3'
65
+
66
+ const db = new Database('./data.db')
67
+ const prisma = new PrismaClient().$extends(speedExtension({ sqlite: db }))
68
+
69
+ const users = await prisma.user.findMany({ where: { status: 'ACTIVE' } })
70
+ ```
71
+
72
+ ### Explicit DMMF (Edge Runtimes)
73
+
74
+ In some environments (Cloudflare Workers, Vercel Edge, bundlers), Prisma's DMMF may not be auto-detectable. Provide it explicitly:
75
+
76
+ ```typescript
77
+ import { PrismaClient, Prisma } from '@prisma/client'
78
+ import { speedExtension } from '@dee-wan/prisma-sql'
79
+ import postgres from 'postgres'
80
+
81
+ const sql = postgres(process.env.DATABASE_URL)
82
+ const prisma = new PrismaClient().$extends(
83
+ speedExtension({
84
+ postgres: sql,
85
+ dmmf: Prisma.dmmf, // Required in edge runtimes
86
+ }),
87
+ )
88
+ ```
89
+
90
+ ## What Gets Faster
91
+
92
+ **Accelerated (via raw SQL):**
93
+
94
+ - `findMany`, `findFirst`, `findUnique`
95
+ - `count`
96
+ - `aggregate` (\_count, \_sum, \_avg, \_min, \_max)
97
+ - `groupBy` with having clauses
98
+
99
+ **Unchanged (still uses Prisma):**
100
+
101
+ - `create`, `update`, `delete`, `upsert`
102
+ - `createMany`, `updateMany`, `deleteMany`
103
+ - Transactions (`$transaction`)
104
+ - Middleware
105
+
106
+ ---
107
+
108
+ ## Performance
109
+
110
+ Benchmarks from 137 E2E tests comparing identical queries against Prisma v6, Prisma v7, and Drizzle:
111
+
112
+ ### BENCHMARK RESULTS - Prisma v6 vs v7 vs Generated SQL
113
+
114
+ ## POSTGRES Results:
115
+
116
+ | Test | Prisma v6 | Prisma v7 | Generated | Drizzle | v6 Speedup | v7 Speedup |
117
+ | ------------------------ | --------- | --------- | --------- | ------- | ---------- | ---------- |
118
+ | findMany basic | 0.45ms | 0.35ms | 0.18ms | 0.29ms | 2.53x | 1.74x |
119
+ | findMany where = | 0.40ms | 0.34ms | 0.17ms | 0.24ms | 2.39x | 1.55x |
120
+ | findMany where >= | 15.88ms | 8.03ms | 2.86ms | 6.18ms | 5.55x | 2.92x |
121
+ | findMany where IN | 0.52ms | 0.52ms | 0.29ms | 0.38ms | 1.79x | 1.38x |
122
+ | findMany where null | 0.23ms | 0.29ms | 0.10ms | 0.16ms | 2.20x | 2.56x |
123
+ | findMany ILIKE | 0.24ms | 0.22ms | 0.18ms | 0.17ms | 1.29x | 0.99x |
124
+ | findMany AND | 2.36ms | 1.20ms | 0.44ms | 1.42ms | 5.41x | 2.73x |
125
+ | findMany OR | 13.10ms | 6.90ms | 2.37ms | 5.58ms | 5.53x | 2.93x |
126
+ | findMany NOT | 0.51ms | 1.14ms | 0.30ms | 0.33ms | 1.73x | 3.74x |
127
+ | findMany orderBy | 2.19ms | 2.31ms | 0.85ms | 0.72ms | 2.58x | 2.05x |
128
+ | findMany pagination | 0.23ms | 0.28ms | 0.20ms | 0.19ms | 1.15x | 1.39x |
129
+ | findMany select | 0.23ms | 0.22ms | 0.09ms | 0.13ms | 2.57x | 2.21x |
130
+ | findMany relation some | 0.83ms | 0.72ms | 0.41ms | N/A | 2.04x | 1.72x |
131
+ | findMany relation every | 0.70ms | 0.79ms | 0.47ms | N/A | 1.50x | 1.70x |
132
+ | findMany relation none | 28.35ms | 14.34ms | 4.81ms | N/A | 5.90x | 2.75x |
133
+ | findMany nested relation | 0.70ms | 0.72ms | 0.71ms | N/A | 0.98x | 1.36x |
134
+ | findMany complex | 1.18ms | 1.19ms | 0.48ms | 0.64ms | 2.45x | 2.81x |
135
+ | findFirst | 0.22ms | 0.25ms | 0.15ms | 0.20ms | 1.45x | 3.05x |
136
+ | findFirst skip | 0.26ms | 0.32ms | 0.15ms | 0.23ms | 1.75x | 3.09x |
137
+ | findUnique id | 0.20ms | 0.21ms | 0.13ms | 0.13ms | 1.52x | 2.53x |
138
+ | findUnique email | 0.18ms | 0.19ms | 0.09ms | 0.12ms | 2.03x | 2.17x |
139
+ | count | 0.11ms | 0.12ms | 0.04ms | 0.07ms | 2.95x | 2.50x |
140
+ | count where | 0.43ms | 0.47ms | 0.26ms | 0.27ms | 1.62x | 1.90x |
141
+ | aggregate count | 0.22ms | 0.24ms | 0.13ms | N/A | 1.63x | 1.51x |
142
+ | aggregate sum/avg | 0.30ms | 0.32ms | 0.23ms | N/A | 1.32x | 1.35x |
143
+ | aggregate where | 0.42ms | 0.44ms | 0.24ms | N/A | 1.75x | 1.84x |
144
+ | aggregate min/max | 0.30ms | 0.32ms | 0.23ms | N/A | 1.29x | 1.32x |
145
+ | aggregate complete | 0.36ms | 0.41ms | 0.27ms | N/A | 1.36x | 1.39x |
146
+ | groupBy | 0.38ms | 0.41ms | 0.29ms | N/A | 1.33x | 1.36x |
147
+ | groupBy count | 0.44ms | 0.42ms | 0.32ms | N/A | 1.36x | 1.37x |
148
+ | groupBy multi | 0.53ms | 0.51ms | 0.37ms | N/A | 1.43x | 1.43x |
149
+ | groupBy having | 0.52ms | 0.50ms | 0.40ms | N/A | 1.29x | 1.40x |
150
+ | groupBy + where | 0.52ms | 0.49ms | 0.31ms | N/A | 1.65x | 1.88x |
151
+ | groupBy aggregates | 0.50ms | 0.48ms | 0.38ms | N/A | 1.31x | 1.32x |
152
+ | groupBy min/max | 0.49ms | 0.50ms | 0.38ms | N/A | 1.29x | 1.35x |
153
+ | include posts | 2.53ms | 1.59ms | 1.85ms | N/A | 1.37x | 0.81x |
154
+ | include profile | 0.47ms | 0.60ms | 0.21ms | N/A | 2.26x | 2.89x |
155
+ | include 3 levels | 1.56ms | 1.64ms | 1.15ms | N/A | 1.36x | 1.33x |
156
+ | include 4 levels | 1.80ms | 1.76ms | 0.99ms | N/A | 1.81x | 1.74x |
157
+ | include + where | 1.21ms | 1.06ms | 1.47ms | N/A | 0.82x | 0.69x |
158
+ | include + select nested | 1.23ms | 0.84ms | 1.43ms | N/A | 0.86x | 0.54x |
159
+ | findMany startsWith | 0.21ms | 0.24ms | 0.14ms | 0.17ms | 1.46x | 1.73x |
160
+ | findMany endsWith | 0.48ms | 0.38ms | 0.19ms | 0.28ms | 2.51x | 1.87x |
161
+ | findMany NOT contains | 0.47ms | 0.40ms | 0.18ms | 0.25ms | 2.62x | 2.49x |
162
+ | findMany LIKE | 0.19ms | 0.22ms | 0.09ms | 0.14ms | 2.19x | 2.61x |
163
+ | findMany < | 25.94ms | 14.16ms | 4.16ms | 9.59ms | 6.23x | 3.12x |
164
+ | findMany <= | 26.79ms | 13.87ms | 4.90ms | 9.73ms | 5.46x | 3.07x |
165
+ | findMany > | 15.22ms | 7.55ms | 2.69ms | 5.74ms | 5.66x | 2.71x |
166
+ | findMany NOT IN | 0.54ms | 0.42ms | 0.26ms | 0.36ms | 2.07x | 1.42x |
167
+ | findMany isNot null | 0.52ms | 0.39ms | 0.19ms | 0.24ms | 2.75x | 2.21x |
168
+ | orderBy multi-field | 3.97ms | 2.38ms | 1.09ms | 1.54ms | 3.65x | 3.71x |
169
+ | distinct status | 8.72ms | 7.84ms | 2.15ms | N/A | 4.06x | 4.68x |
170
+ | distinct multi | 11.71ms | 11.09ms | 2.09ms | N/A | 5.61x | 5.30x |
171
+ | cursor pagination | 0.29ms | 0.34ms | 0.23ms | N/A | 1.25x | 1.62x |
172
+ | select + include | 0.89ms | 0.70ms | 0.17ms | N/A | 5.19x | 3.46x |
173
+ | \_count relation | 0.71ms | 0.66ms | 0.55ms | N/A | 1.29x | 1.20x |
174
+ | \_count multi-relation | 0.24ms | 0.29ms | 0.16ms | N/A | 1.52x | 1.90x |
175
+ | ILIKE special chars | 0.22ms | 0.26ms | 0.14ms | N/A | 1.54x | 1.76x |
176
+ | LIKE case sensitive | 0.19ms | 0.22ms | 0.12ms | N/A | 1.62x | 1.85x |
177
+
178
+ ##### Summary:
179
+
180
+ - Generated SQL vs Prisma v6: **2.39x faster**
181
+ - Generated SQL vs Prisma v7: **2.10x faster**
182
+ - Generated SQL vs Drizzle: **1.53x faster**
183
+
184
+ ---
185
+
186
+ #### SQLITE:
187
+
188
+ ---
189
+
190
+ | Test | Prisma v6 | Prisma v7 | Generated | Drizzle | v6 Speedup | v7 Speedup |
191
+ | ------------------------ | --------- | --------- | --------- | ------- | ---------- | ---------- |
192
+ | findMany basic | 0.44ms | 0.27ms | 0.04ms | 0.17ms | 9.59x | 5.47x |
193
+ | findMany where = | 0.45ms | 0.23ms | 0.03ms | 0.10ms | 14.14x | 6.25x |
194
+ | findMany where >= | 12.72ms | 4.70ms | 1.02ms | 2.09ms | 12.51x | 4.16x |
195
+ | findMany where IN | 0.40ms | 0.28ms | 0.04ms | 0.10ms | 10.35x | 6.55x |
196
+ | findMany where null | 0.15ms | 0.19ms | 0.01ms | 0.06ms | 10.97x | 12.56x |
197
+ | findMany LIKE | 0.15ms | 0.17ms | 0.02ms | 0.06ms | 8.64x | 9.41x |
198
+ | findMany AND | 1.49ms | 0.95ms | 0.26ms | 0.43ms | 5.75x | 3.45x |
199
+ | findMany OR | 10.32ms | 3.87ms | 0.93ms | 1.85ms | 11.09x | 3.64x |
200
+ | findMany NOT | 0.42ms | 0.28ms | 0.03ms | 0.09ms | 12.59x | 7.05x |
201
+ | findMany orderBy | 2.24ms | 1.92ms | 1.76ms | 1.81ms | 1.27x | 1.11x |
202
+ | findMany pagination | 0.13ms | 0.15ms | 0.02ms | 0.06ms | 5.69x | 6.24x |
203
+ | findMany select | 0.15ms | 0.11ms | 0.02ms | 0.04ms | 9.50x | 6.22x |
204
+ | findMany relation some | 4.50ms | 0.56ms | 0.40ms | N/A | 11.15x | 1.32x |
205
+ | findMany relation every | 9.53ms | 9.54ms | 6.38ms | N/A | 1.49x | 1.45x |
206
+ | findMany relation none | 166.62ms | 128.44ms | 2.40ms | N/A | 69.43x | 49.51x |
207
+ | findMany nested relation | 1.00ms | 0.51ms | 0.31ms | N/A | 3.28x | 1.70x |
208
+ | findMany complex | 0.79ms | 0.83ms | 0.43ms | 0.48ms | 1.84x | 1.74x |
209
+ | findFirst | 0.16ms | 0.17ms | 0.01ms | 0.06ms | 11.57x | 12.00x |
210
+ | findFirst skip | 0.25ms | 0.23ms | 0.03ms | 0.08ms | 8.62x | 13.31x |
211
+ | findUnique id | 0.12ms | 0.15ms | 0.01ms | 0.05ms | 9.92x | 11.62x |
212
+ | findUnique email | 0.12ms | 0.15ms | 0.01ms | 0.05ms | 8.73x | 11.43x |
213
+ | count | 0.17ms | 0.07ms | 0.01ms | 0.02ms | 13.33x | 10.73x |
214
+ | count where | 0.28ms | 0.28ms | 0.16ms | 0.17ms | 1.77x | 1.85x |
215
+ | aggregate count | 0.15ms | 0.11ms | 0.01ms | N/A | 14.80x | 9.69x |
216
+ | aggregate sum/avg | 0.27ms | 0.25ms | 0.15ms | N/A | 1.82x | 1.62x |
217
+ | aggregate where | 0.25ms | 0.26ms | 0.15ms | N/A | 1.66x | 1.73x |
218
+ | aggregate min/max | 0.28ms | 0.25ms | 0.16ms | N/A | 1.80x | 1.52x |
219
+ | aggregate complete | 0.39ms | 0.34ms | 0.21ms | N/A | 1.81x | 1.61x |
220
+ | groupBy | 0.56ms | 0.53ms | 0.44ms | N/A | 1.28x | 1.22x |
221
+ | groupBy count | 0.57ms | 0.57ms | 0.45ms | N/A | 1.28x | 1.27x |
222
+ | groupBy multi | 1.14ms | 1.08ms | 0.95ms | N/A | 1.20x | 1.17x |
223
+ | groupBy having | 0.64ms | 0.64ms | 0.47ms | N/A | 1.37x | 1.32x |
224
+ | groupBy + where | 0.31ms | 0.33ms | 0.18ms | N/A | 1.70x | 1.84x |
225
+ | groupBy aggregates | 0.71ms | 0.66ms | 0.54ms | N/A | 1.32x | 1.23x |
226
+ | groupBy min/max | 0.72ms | 0.70ms | 0.56ms | N/A | 1.29x | 1.25x |
227
+ | include posts | 1.88ms | 1.13ms | 0.90ms | N/A | 2.10x | 1.12x |
228
+ | include profile | 0.32ms | 0.41ms | 0.05ms | N/A | 6.17x | 6.48x |
229
+ | include 3 levels | 1.11ms | 1.08ms | 0.63ms | N/A | 1.77x | 1.86x |
230
+ | include 4 levels | 1.15ms | 1.10ms | 0.42ms | N/A | 2.72x | 2.70x |
231
+ | include + where | 0.77ms | 0.72ms | 0.11ms | N/A | 7.13x | 6.99x |
232
+ | include + select nested | 0.73ms | 0.53ms | 0.83ms | N/A | 0.88x | 0.64x |
233
+ | findMany startsWith | 0.15ms | 0.16ms | 0.02ms | 0.06ms | 6.73x | 6.98x |
234
+ | findMany endsWith | 0.43ms | 0.26ms | 0.04ms | 0.15ms | 9.74x | 5.22x |
235
+ | findMany NOT contains | 0.45ms | 0.28ms | 0.04ms | 0.11ms | 11.65x | 6.57x |
236
+ | findMany < | 21.60ms | 8.27ms | 1.88ms | 4.07ms | 11.49x | 4.24x |
237
+ | findMany <= | 22.34ms | 8.50ms | 1.97ms | 4.40ms | 11.36x | 4.25x |
238
+ | findMany > | 11.54ms | 4.33ms | 0.94ms | 2.13ms | 12.22x | 4.17x |
239
+ | findMany NOT IN | 0.42ms | 0.28ms | 0.04ms | 0.12ms | 10.40x | 6.23x |
240
+ | findMany isNot null | 0.45ms | 0.27ms | 0.03ms | 0.11ms | 13.03x | 6.91x |
241
+ | orderBy multi-field | 0.66ms | 0.59ms | 0.37ms | 0.43ms | 1.78x | 1.67x |
242
+ | distinct status | 10.61ms | 6.79ms | 4.09ms | N/A | 2.59x | 1.53x |
243
+ | distinct multi | 11.66ms | 7.12ms | 5.09ms | N/A | 2.29x | 1.34x |
244
+ | cursor pagination | 0.21ms | 0.26ms | 0.04ms | N/A | 4.60x | 5.52x |
245
+ | select + include | 0.51ms | 0.43ms | 0.04ms | N/A | 13.19x | 11.31x |
246
+ | \_count relation | 0.62ms | 0.46ms | 0.32ms | N/A | 1.93x | 1.44x |
247
+ | \_count multi-relation | 0.14ms | 0.17ms | 0.04ms | N/A | 3.22x | 4.09x |
248
+
249
+ ##### Summary:
250
+
251
+ - Generated SQL vs Prisma v6: **7.51x faster**
252
+ - Generated SQL vs Prisma v7: **5.48x faster**
253
+ - Generated SQL vs Drizzle: **2.61x faster**
254
+
255
+ > **Note on Prisma v7:** Prisma v7 introduced significant performance improvements (39% faster than v6 on PostgreSQL, 24% faster on SQLite), but this extension still provides 2-7x additional speedup over v7.
256
+
257
+ > **Benchmarks:** These are representative results from our test suite running on a MacBook Pro M1 with PostgreSQL 15 and SQLite 3.43. Your mileage may vary based on:
258
+ >
259
+ > - Database configuration and indexes
260
+ > - Query complexity and data volume
261
+ > - Hardware and network latency
262
+ > - Concurrent load
263
+ >
264
+ > Run benchmarks with your own schema and data for accurate measurements. See [Benchmarking](#benchmarking) section below.
265
+
266
+ ---
267
+
268
+ ## Configuration
269
+
270
+ ### Basic Configuration
271
+
272
+ ```typescript
273
+ import { Prisma } from '@prisma/client'
274
+
275
+ speedExtension({
276
+ // Database client (required - choose one)
277
+ postgres: sql, // For PostgreSQL via postgres.js
278
+ sqlite: db, // For SQLite via better-sqlite3
279
+
280
+ // DMMF (optional - auto-detected in most cases)
281
+ dmmf: Prisma.dmmf, // Required in edge runtimes/bundled apps
282
+
283
+ // Debug mode (optional)
284
+ debug: true, // Log all generated SQL
285
+
286
+ // Selective models (optional)
287
+ models: ['User', 'Post'], // Only accelerate these models
288
+
289
+ // Performance monitoring (optional)
290
+ onQuery: (info) => {
291
+ console.log(`${info.model}.${info.method}: ${info.duration}ms`)
292
+ },
293
+ })
294
+ ```
295
+
296
+ ### Debug Mode
297
+
298
+ See generated SQL for every query:
299
+
300
+ ```typescript
301
+ speedExtension({
302
+ postgres: sql,
303
+ debug: true,
304
+ })
305
+
306
+ // Logs:
307
+ // [postgres] User.findMany
308
+ // SQL: SELECT ... FROM users WHERE status = $1
309
+ // Params: ['ACTIVE']
310
+ ```
311
+
312
+ ### Selective Models
313
+
314
+ Only accelerate specific models:
315
+
316
+ ```typescript
317
+ speedExtension({
318
+ postgres: sql,
319
+ models: ['User', 'Post'], // Only User and Post get accelerated
320
+ })
321
+
322
+ // Order, Product, etc. still use Prisma
323
+ ```
324
+
325
+ ### Performance Monitoring
326
+
327
+ Track query performance:
328
+
329
+ ```typescript
330
+ speedExtension({
331
+ postgres: sql,
332
+ onQuery: (info) => {
333
+ console.log(`${info.model}.${info.method} completed in ${info.duration}ms`)
334
+
335
+ if (info.duration > 100) {
336
+ logger.warn('Slow query detected', {
337
+ model: info.model,
338
+ method: info.method,
339
+ sql: info.sql,
340
+ })
341
+ }
342
+ },
343
+ })
344
+ ```
345
+
346
+ ### When to Provide DMMF Explicitly
347
+
348
+ Provide `dmmf: Prisma.dmmf` if:
349
+
350
+ - Using Cloudflare Workers, Vercel Edge, or similar edge runtimes
351
+ - Bundling with webpack, esbuild, or Rollup
352
+ - In a monorepo with complex Prisma setup
353
+ - You see "Cannot access Prisma DMMF" error
354
+
355
+ ```typescript
356
+ import { Prisma } from '@prisma/client'
357
+
358
+ speedExtension({
359
+ postgres: sql,
360
+ dmmf: Prisma.dmmf, // Explicit DMMF
361
+ })
362
+ ```
363
+
364
+ ## Advanced Usage
365
+
366
+ ### Read Replicas
367
+
368
+ Send writes to primary, reads to replica:
369
+
370
+ ```typescript
371
+ // Primary database for writes
372
+ const primary = new PrismaClient()
373
+
374
+ // Replica for fast reads
375
+ const replica = postgres(process.env.REPLICA_URL)
376
+ const fastPrisma = new PrismaClient().$extends(
377
+ speedExtension({ postgres: replica })
378
+ )
379
+
380
+ // Use appropriately
381
+ await primary.user.create({ data: { ... } }) // → Primary
382
+ const users = await fastPrisma.user.findMany() // → Replica
383
+ ```
384
+
385
+ ### Connection Pooling
386
+
387
+ Configure postgres.js connection pool:
388
+
389
+ ```typescript
390
+ const sql = postgres(process.env.DATABASE_URL, {
391
+ max: 20, // Pool size
392
+ idle_timeout: 20, // Close idle connections after 20s
393
+ connect_timeout: 10, // Connection timeout
394
+ ssl: 'require', // Force SSL
395
+ })
396
+
397
+ const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
398
+ ```
399
+
400
+ ### Gradual Rollout
401
+
402
+ Feature-flag the extension for safe rollout:
403
+
404
+ ```typescript
405
+ const USE_FAST_READS = process.env.FAST_READS === 'true'
406
+
407
+ const sql = postgres(DATABASE_URL)
408
+ const prisma = new PrismaClient()
409
+
410
+ const db = USE_FAST_READS
411
+ ? prisma.$extends(speedExtension({ postgres: sql }))
412
+ : prisma
413
+
414
+ // Disable in production if issues arise:
415
+ // FAST_READS=false pm2 restart app
416
+ ```
417
+
418
+ ### Access Original Prisma
419
+
420
+ ```typescript
421
+ const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
422
+
423
+ // Use extension (fast)
424
+ const fast = await prisma.user.findMany()
425
+
426
+ // Bypass extension (original Prisma)
427
+ const slow = await prisma.$original.user.findMany()
428
+ ```
429
+
430
+ ## Edge Runtime
431
+
432
+ ### Vercel Edge Functions
433
+
434
+ ```typescript
435
+ import { PrismaClient, Prisma } from '@prisma/client'
436
+ import { speedExtension } from '@dee-wan/prisma-sql'
437
+ import postgres from 'postgres'
438
+
439
+ const sql = postgres(process.env.DATABASE_URL)
440
+ const prisma = new PrismaClient().$extends(
441
+ speedExtension({
442
+ postgres: sql,
443
+ dmmf: Prisma.dmmf, // Explicit dmmf required in edge runtime
444
+ }),
445
+ )
446
+
447
+ export const config = { runtime: 'edge' }
448
+
449
+ export default async function handler(req: Request) {
450
+ const users = await prisma.user.findMany()
451
+ return Response.json(users)
452
+ }
453
+ ```
454
+
455
+ ### Cloudflare Workers
456
+
457
+ For Cloudflare Workers, use the standalone SQL generation API instead of the extension:
458
+
459
+ ```typescript
460
+ import { createToSQL } from '@dee-wan/prisma-sql'
461
+ import { Prisma } from '@prisma/client'
462
+
463
+ const toSQL = createToSQL(Prisma.dmmf, 'sqlite')
464
+
465
+ export default {
466
+ async fetch(request: Request, env: Env) {
467
+ const { sql, params } = toSQL('User', 'findMany', {
468
+ where: { status: 'ACTIVE' },
469
+ })
470
+
471
+ const result = await env.DB.prepare(sql)
472
+ .bind(...params)
473
+ .all()
474
+ return Response.json(result.results)
475
+ },
476
+ }
477
+ ```
478
+
479
+ > **Note:** The Prisma Client extension is not recommended for Cloudflare Workers due to cold start overhead. Use the `createToSQL` API for edge deployments.
480
+
481
+ ## Supported Queries
482
+
483
+ ### Filters
484
+
485
+ ```typescript
486
+ // Comparison operators
487
+ { age: { gt: 18, lte: 65 } }
488
+ { status: { in: ['ACTIVE', 'PENDING'] } }
489
+ { status: { notIn: ['DELETED'] } }
490
+
491
+ // String operations
492
+ { email: { contains: '@example.com' } }
493
+ { email: { startsWith: 'user' } }
494
+ { email: { endsWith: '.com' } }
495
+ { email: { contains: 'EXAMPLE', mode: 'insensitive' } }
496
+
497
+ // Logical operators
498
+ { AND: [{ status: 'ACTIVE' }, { verified: true }] }
499
+ { OR: [{ role: 'ADMIN' }, { role: 'MODERATOR' }] }
500
+ { NOT: { status: 'DELETED' } }
501
+
502
+ // Null checks
503
+ { deletedAt: null }
504
+ { deletedAt: { not: null } }
505
+ ```
506
+
507
+ ### Relations
508
+
509
+ ```typescript
510
+ // Include relations
511
+ {
512
+ include: {
513
+ posts: true,
514
+ profile: true
515
+ }
516
+ }
517
+
518
+ // Nested includes
519
+ {
520
+ include: {
521
+ posts: {
522
+ include: { comments: true },
523
+ where: { published: true },
524
+ orderBy: { createdAt: 'desc' },
525
+ take: 5
526
+ }
527
+ }
528
+ }
529
+
530
+ // Relation filters
531
+ {
532
+ where: {
533
+ posts: {
534
+ some: { published: true }
535
+ }
536
+ }
537
+ }
538
+
539
+ {
540
+ where: {
541
+ posts: {
542
+ every: { published: true }
543
+ }
544
+ }
545
+ }
546
+
547
+ {
548
+ where: {
549
+ posts: {
550
+ none: { published: false }
551
+ }
552
+ }
553
+ }
554
+ ```
555
+
556
+ ### Pagination & Ordering
557
+
558
+ ```typescript
559
+ // Limit/offset
560
+ {
561
+ take: 10,
562
+ skip: 20,
563
+ orderBy: { createdAt: 'desc' }
564
+ }
565
+
566
+ // Cursor-based pagination
567
+ {
568
+ cursor: { id: 100 },
569
+ take: 10,
570
+ skip: 1, // Skip cursor itself
571
+ orderBy: { id: 'asc' }
572
+ }
573
+
574
+ // Multiple ordering
575
+ {
576
+ orderBy: [
577
+ { status: 'asc' },
578
+ { priority: 'desc' },
579
+ { createdAt: 'desc' }
580
+ ]
581
+ }
582
+
583
+ // Null positioning (PostgreSQL)
584
+ {
585
+ orderBy: {
586
+ name: {
587
+ sort: 'asc',
588
+ nulls: 'last'
589
+ }
590
+ }
591
+ }
592
+ ```
593
+
594
+ ### Aggregations
595
+
596
+ ```typescript
597
+ // Count
598
+ await prisma.user.count({ where: { status: 'ACTIVE' } })
599
+
600
+ // Multiple aggregations
601
+ await prisma.task.aggregate({
602
+ where: { status: 'DONE' },
603
+ _count: { _all: true },
604
+ _sum: { estimatedHours: true },
605
+ _avg: { estimatedHours: true },
606
+ _min: { startedAt: true },
607
+ _max: { completedAt: true },
608
+ })
609
+
610
+ // Group by
611
+ await prisma.task.groupBy({
612
+ by: ['status', 'priority'],
613
+ _count: { _all: true },
614
+ _avg: { estimatedHours: true },
615
+ having: {
616
+ status: {
617
+ _count: { gte: 5 },
618
+ },
619
+ },
620
+ })
621
+ ```
622
+
623
+ ### Distinct
624
+
625
+ ```typescript
626
+ // Single field (PostgreSQL uses DISTINCT ON)
627
+ {
628
+ distinct: ['status'],
629
+ orderBy: { status: 'asc' }
630
+ }
631
+
632
+ // Multiple fields (SQLite uses window functions)
633
+ {
634
+ distinct: ['status', 'priority'],
635
+ orderBy: [
636
+ { status: 'asc' },
637
+ { priority: 'asc' }
638
+ ]
639
+ }
640
+ ```
641
+
642
+ ## Migration Guide
643
+
644
+ ### From Prisma Client
645
+
646
+ **Before:**
647
+
648
+ ```typescript
649
+ const prisma = new PrismaClient()
650
+ const users = await prisma.user.findMany()
651
+ ```
652
+
653
+ **After:**
654
+
655
+ ```typescript
656
+ import postgres from 'postgres'
657
+ import { speedExtension } from '@dee-wan/prisma-sql'
658
+
659
+ const sql = postgres(DATABASE_URL)
660
+ const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
661
+
662
+ const users = await prisma.user.findMany() // Same code, 2-7x faster
663
+ ```
664
+
665
+ ### From Drizzle
666
+
667
+ **Before:**
668
+
669
+ ```typescript
670
+ import { drizzle } from 'drizzle-orm/postgres-js'
671
+ import postgres from 'postgres'
672
+
673
+ const sql = postgres(DATABASE_URL)
674
+ const db = drizzle(sql)
675
+
676
+ const users = await db
677
+ .select()
678
+ .from(usersTable)
679
+ .where(eq(usersTable.status, 'ACTIVE'))
680
+ ```
681
+
682
+ **After:**
683
+
684
+ ```typescript
685
+ import { speedExtension } from '@dee-wan/prisma-sql'
686
+
687
+ const sql = postgres(DATABASE_URL)
688
+ const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
689
+
690
+ // Use Prisma's familiar API instead
691
+ const users = await prisma.user.findMany({
692
+ where: { status: 'ACTIVE' },
693
+ })
694
+ ```
695
+
696
+ ## Limitations
697
+
698
+ ### Partially Supported
699
+
700
+ These features work but have limitations:
701
+
702
+ - ⚠️ **Array operations**: Basic operations (`has`, `hasSome`, `hasEvery`, `isEmpty`) work. Advanced filtering like `array_contains(array_field, [1,2,3])` not yet supported.
703
+ - ⚠️ **JSON operations**: Path-based filtering works (`json.path(['field'], { equals: 'value' })`). Advanced JSON functions not yet supported.
704
+
705
+ ### Not Yet Supported
706
+
707
+ These Prisma features are not supported and will fall back to Prisma Client:
708
+
709
+ - ❌ Full-text search (`search` operator)
710
+ - ❌ Composite types (MongoDB-style embedded documents)
711
+ - ❌ Raw database features (PostGIS, pg_trgm, etc.)
712
+ - ❌ Some advanced aggregations in `groupBy` (nested aggregations)
713
+
714
+ If you encounter unsupported queries, enable `debug: true` to see which queries are being converted and which fall back to Prisma.
715
+
716
+ ### Database Support
717
+
718
+ - ✅ PostgreSQL 12+
719
+ - ✅ SQLite 3.35+
720
+ - ❌ MySQL (not yet implemented)
721
+ - ❌ MongoDB (not applicable - document database)
722
+ - ❌ SQL Server (not yet implemented)
723
+ - ❌ CockroachDB (not yet tested)
724
+
725
+ ## Troubleshooting
726
+
727
+ ### "Results don't match Prisma Client"
728
+
729
+ Enable debug mode to inspect generated SQL:
730
+
731
+ ```typescript
732
+ speedExtension({
733
+ postgres: sql,
734
+ debug: true,
735
+ })
736
+ ```
737
+
738
+ Compare with Prisma's query log:
739
+
740
+ ```typescript
741
+ new PrismaClient({ log: ['query'] })
742
+ ```
743
+
744
+ File an issue if results differ: https://github.com/dee-see/prisma-sql/issues
745
+
746
+ ### "Connection pool exhausted"
747
+
748
+ Increase postgres.js pool size:
749
+
750
+ ```typescript
751
+ const sql = postgres(DATABASE_URL, {
752
+ max: 50, // Increase from default 10
753
+ })
754
+ ```
755
+
756
+ ### "Cannot access Prisma DMMF" Error
757
+
758
+ If you see this error:
759
+
760
+ ```
761
+ Cannot access Prisma DMMF. Please provide dmmf in config
762
+ ```
763
+
764
+ Explicitly provide the DMMF:
765
+
766
+ ```typescript
767
+ import { Prisma } from '@prisma/client'
768
+
769
+ const prisma = new PrismaClient().$extends(
770
+ speedExtension({
771
+ postgres: sql,
772
+ dmmf: Prisma.dmmf, // Add this
773
+ }),
774
+ )
775
+ ```
776
+
777
+ This is required in:
778
+
779
+ - Edge runtimes (Cloudflare Workers, Vercel Edge)
780
+ - Bundled applications (webpack, esbuild)
781
+ - Some monorepo setups
782
+ - When using Prisma Client programmatically
783
+
784
+ ### "Type errors after extending"
785
+
786
+ Ensure `@prisma/client` is up to date:
787
+
788
+ ```bash
789
+ npm update @prisma/client
790
+ npx prisma generate
791
+ ```
792
+
793
+ ### "Performance not improving"
794
+
795
+ Some queries won't see dramatic improvements:
796
+
797
+ - Very simple `findUnique` by ID (already fast)
798
+ - Queries with no WHERE clause on small tables
799
+ - Aggregations on unindexed fields
800
+
801
+ Use `onQuery` to measure actual speedup:
802
+
803
+ ```typescript
804
+ speedExtension({
805
+ postgres: sql,
806
+ onQuery: (info) => {
807
+ console.log(`${info.method} took ${info.duration}ms`)
808
+ },
809
+ })
810
+ ```
811
+
812
+ ## FAQ
813
+
814
+ **Q: Do I need to keep using Prisma Client?**
815
+ A: Yes. You need Prisma for schema management, migrations, types, and write operations. This extension only speeds up reads.
816
+
817
+ **Q: Does it work with my existing schema?**
818
+ A: Yes. No schema changes required. It works with your existing Prisma schema and generated client.
819
+
820
+ **Q: What about writes (create, update, delete)?**
821
+ A: Writes still use Prisma Client. This extension only accelerates reads. For write-heavy workloads, this provides less benefit.
822
+
823
+ **Q: Is it production ready?**
824
+ A: Yes. 137 E2E tests verify exact parity with Prisma Client across both Prisma v6 and v7. Used in production.
825
+
826
+ **Q: Can I use it with PlanetScale, Neon, Supabase?**
827
+ A: Yes. Works with any PostgreSQL-compatible database. Just pass the connection string to postgres.js.
828
+
829
+ **Q: Does it support Prisma middlewares?**
830
+ A: The extension runs after middlewares. If you need middleware to see the actual SQL, use Prisma's query logging.
831
+
832
+ **Q: Can I still use `$queryRaw` and `$executeRaw`?**
833
+ A: Yes. Those methods are unaffected. You also still have direct access to the postgres.js client.
834
+
835
+ **Q: Do I need to provide `dmmf` in the config?**
836
+ A: Usually no - it's auto-detected from Prisma Client. However, in edge runtimes (Cloudflare Workers, Vercel Edge) or bundled applications, you must provide it explicitly:
837
+
838
+ ```typescript
839
+ import { Prisma } from '@prisma/client'
840
+
841
+ speedExtension({
842
+ postgres: sql,
843
+ dmmf: Prisma.dmmf, // Required in edge runtimes
844
+ })
845
+ ```
846
+
847
+ If you see "Cannot access Prisma DMMF" error, add this parameter.
848
+
849
+ **Q: What's the overhead of SQL generation?**
850
+ A: ~0.03-0.04ms per query. Even with this overhead, total time is 2-7x faster than Prisma.
851
+
852
+ **Q: How do I benchmark my own queries?**
853
+ A: Use the `onQuery` callback to measure each query, or see the [Benchmarking](#benchmarking) section below.
854
+
855
+ **Q: How does performance compare to Prisma v7?**
856
+ A: Prisma v7 introduced significant improvements (~39% faster than v6 on PostgreSQL, ~24% on SQLite), but this extension still provides 2-7x additional speedup over v7 depending on query complexity.
857
+
858
+ ## Examples
859
+
860
+ - [PostgreSQL E2E Tests](./tests/e2e/postgres.test.ts) - Comprehensive query examples
861
+ - [SQLite E2E Tests](./tests/e2e/sqlite.e2e.test.ts) - SQLite-specific queries
862
+ - [Runtime API Tests](./tests/e2e/runtime-api.test.ts) - All three APIs
863
+
864
+ To run examples locally:
865
+
866
+ ```bash
867
+ git clone https://github.com/dee-see/prisma-sql
868
+ cd prisma-sql
869
+ npm install
870
+ npm test
871
+ ```
872
+
873
+ ## Benchmarking
874
+
875
+ Benchmark your own queries:
876
+
877
+ ```typescript
878
+ import { speedExtension } from '@dee-wan/prisma-sql'
879
+
880
+ const queries: { name: string; duration: number }[] = []
881
+
882
+ const prisma = new PrismaClient().$extends(
883
+ speedExtension({
884
+ postgres: sql,
885
+ onQuery: (info) => {
886
+ queries.push({
887
+ name: `${info.model}.${info.method}`,
888
+ duration: info.duration,
889
+ })
890
+ },
891
+ }),
892
+ )
893
+
894
+ // Run your queries
895
+ await prisma.user.findMany({ where: { status: 'ACTIVE' } })
896
+ await prisma.post.findMany({ include: { author: true } })
897
+
898
+ // Analyze
899
+ console.table(queries)
900
+ ```
901
+
902
+ Or run the full test suite benchmarks:
903
+
904
+ ```bash
905
+ git clone https://github.com/dee-see/prisma-sql
906
+ cd prisma-sql
907
+ npm install
908
+
909
+ # Setup test database
910
+ npx prisma db push
911
+
912
+ # Run PostgreSQL benchmarks
913
+ DATABASE_URL="postgresql://..." npm run test:e2e:postgres
914
+
915
+ # Run SQLite benchmarks
916
+ npm run test:e2e:sqlite
917
+ ```
918
+
919
+ Results include timing for Prisma vs Extension vs Drizzle (where applicable).
920
+
921
+ ## Contributing
922
+
923
+ PRs welcome! Priority areas:
924
+
925
+ - MySQL support implementation
926
+ - Additional PostgreSQL/SQLite operators
927
+ - Performance optimizations
928
+ - Edge runtime compatibility
929
+ - Documentation improvements
930
+
931
+ Setup:
932
+
933
+ ```bash
934
+ git clone https://github.com/dee-see/prisma-sql
935
+ cd prisma-sql
936
+ npm install
937
+ npm run generate
938
+ npm test
939
+ ```
940
+
941
+ Please ensure:
942
+
943
+ - All tests pass (`npm test`)
944
+ - New features have tests
945
+ - Types are properly exported
946
+ - README is updated
947
+
948
+ ## How It Works
949
+
950
+ ```
951
+ ┌─────────────────────────────────────────────────────┐
952
+ │ prisma.user.findMany({ where: { status: 'ACTIVE' }})│
953
+ └────────────────────┬────────────────────────────────┘
954
+
955
+ ┌───────────▼──────────┐
956
+ │ Speed Extension │
957
+ │ Intercepts query │
958
+ └───────────┬──────────┘
959
+
960
+ ┌───────────▼──────────┐
961
+ │ Generate SQL │
962
+ │ Parser + Builder │
963
+ └───────────┬──────────┘
964
+
965
+ ┌───────────▼──────────┐
966
+ │ SELECT ... FROM users│
967
+ │ WHERE status = $1 │
968
+ └───────────┬──────────┘
969
+
970
+ ┌───────────▼──────────┐
971
+ │ Execute via │
972
+ │ postgres.js │ ← Bypasses Prisma's query engine
973
+ └───────────┬──────────┘
974
+
975
+ ┌───────────▼──────────┐
976
+ │ Return results │
977
+ │ (same format as │
978
+ │ Prisma) │
979
+ └──────────────────────┘
980
+ ```
981
+
982
+ ## License
983
+
984
+ MIT
985
+
986
+ ## Links
987
+
988
+ - [NPM Package](https://www.npmjs.com/package/@dee-wan/prisma-sql)
989
+ - [GitHub Repository](https://github.com/dee-see/prisma-sql)
990
+ - [Issue Tracker](https://github.com/dee-see/prisma-sql/issues)
991
+
992
+ ---
993
+
994
+ **Made for developers who need Prisma's DX with raw SQL performance.**