@ereo/db-drizzle 0.1.6
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 +1401 -0
- package/dist/adapter.d.ts +23 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/config.d.ts +108 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +653 -0
- package/dist/types.d.ts +188 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,1401 @@
|
|
|
1
|
+
# @ereo/db-drizzle
|
|
2
|
+
|
|
3
|
+
Drizzle ORM adapter for the EreoJS database abstraction layer. This package provides seamless integration between Drizzle ORM and EreoJS applications with support for multiple database drivers, including edge-compatible options.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Quick Start](#quick-start)
|
|
10
|
+
- [API Reference](#api-reference)
|
|
11
|
+
- [createDrizzleAdapter](#createdrizzleadapter)
|
|
12
|
+
- [Configuration Helpers](#configuration-helpers)
|
|
13
|
+
- [Environment Detection](#environment-detection)
|
|
14
|
+
- [Configuration Options](#configuration-options)
|
|
15
|
+
- [PostgreSQL (postgres-js)](#postgresql-postgres-js)
|
|
16
|
+
- [Neon HTTP](#neon-http)
|
|
17
|
+
- [Neon WebSocket](#neon-websocket)
|
|
18
|
+
- [PlanetScale](#planetscale)
|
|
19
|
+
- [LibSQL/Turso](#libsqlturso)
|
|
20
|
+
- [Bun SQLite](#bun-sqlite)
|
|
21
|
+
- [better-sqlite3](#better-sqlite3)
|
|
22
|
+
- [Cloudflare D1](#cloudflare-d1)
|
|
23
|
+
- [Drizzle ORM Integration](#drizzle-orm-integration)
|
|
24
|
+
- [Use Cases](#use-cases)
|
|
25
|
+
- [Basic Query Execution](#basic-query-execution)
|
|
26
|
+
- [Transactions](#transactions)
|
|
27
|
+
- [Request-Scoped Queries with Deduplication](#request-scoped-queries-with-deduplication)
|
|
28
|
+
- [Edge Deployment](#edge-deployment)
|
|
29
|
+
- [Health Checks](#health-checks)
|
|
30
|
+
- [Migrations](#migrations)
|
|
31
|
+
- [Error Handling](#error-handling)
|
|
32
|
+
- [TypeScript Types Reference](#typescript-types-reference)
|
|
33
|
+
- [Troubleshooting / FAQ](#troubleshooting--faq)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Overview
|
|
38
|
+
|
|
39
|
+
`@ereo/db-drizzle` bridges Drizzle ORM with the EreoJS database abstraction layer (`@ereo/db`). It implements the `DatabaseAdapter` interface, providing:
|
|
40
|
+
|
|
41
|
+
- **Multi-driver support**: PostgreSQL, MySQL (PlanetScale), SQLite (multiple variants), and Cloudflare D1
|
|
42
|
+
- **Edge compatibility**: First-class support for serverless and edge runtimes
|
|
43
|
+
- **Request-scoped query deduplication**: Automatic caching of identical queries within a request
|
|
44
|
+
- **Type safety**: Full TypeScript support with Drizzle schema inference
|
|
45
|
+
- **Connection management**: Automatic connection pooling and lifecycle management
|
|
46
|
+
|
|
47
|
+
### Supported Drivers
|
|
48
|
+
|
|
49
|
+
| Driver | Database | Edge Compatible | Use Case |
|
|
50
|
+
|--------|----------|-----------------|----------|
|
|
51
|
+
| `postgres-js` | PostgreSQL | No | Traditional server deployments |
|
|
52
|
+
| `neon-http` | PostgreSQL (Neon) | Yes | Edge/serverless with Neon |
|
|
53
|
+
| `neon-websocket` | PostgreSQL (Neon) | Yes | Edge with connection pooling |
|
|
54
|
+
| `planetscale` | MySQL | Yes | Edge/serverless with PlanetScale |
|
|
55
|
+
| `libsql` | SQLite (Turso) | Yes | Edge/serverless with Turso |
|
|
56
|
+
| `bun-sqlite` | SQLite | No | Bun runtime local development |
|
|
57
|
+
| `better-sqlite3` | SQLite | No | Node.js local development |
|
|
58
|
+
| `d1` | SQLite (Cloudflare) | Yes | Cloudflare Workers |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Install the package
|
|
66
|
+
bun add @ereo/db-drizzle
|
|
67
|
+
|
|
68
|
+
# Install Drizzle ORM (required peer dependency)
|
|
69
|
+
bun add drizzle-orm
|
|
70
|
+
|
|
71
|
+
# Install driver-specific dependencies (pick one based on your database)
|
|
72
|
+
# PostgreSQL
|
|
73
|
+
bun add postgres
|
|
74
|
+
|
|
75
|
+
# Neon (serverless PostgreSQL)
|
|
76
|
+
bun add @neondatabase/serverless
|
|
77
|
+
|
|
78
|
+
# PlanetScale (serverless MySQL)
|
|
79
|
+
bun add @planetscale/database
|
|
80
|
+
|
|
81
|
+
# LibSQL/Turso
|
|
82
|
+
bun add @libsql/client
|
|
83
|
+
|
|
84
|
+
# better-sqlite3 (Node.js)
|
|
85
|
+
bun add better-sqlite3
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Quick Start
|
|
91
|
+
|
|
92
|
+
### 1. Define Your Drizzle Schema
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// db/schema.ts
|
|
96
|
+
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
|
|
97
|
+
|
|
98
|
+
export const users = pgTable('users', {
|
|
99
|
+
id: serial('id').primaryKey(),
|
|
100
|
+
name: text('name').notNull(),
|
|
101
|
+
email: text('email').notNull().unique(),
|
|
102
|
+
createdAt: timestamp('created_at').defaultNow(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const posts = pgTable('posts', {
|
|
106
|
+
id: serial('id').primaryKey(),
|
|
107
|
+
title: text('title').notNull(),
|
|
108
|
+
content: text('content'),
|
|
109
|
+
authorId: serial('author_id').references(() => users.id),
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 2. Configure the Adapter
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// ereo.config.ts
|
|
117
|
+
import { defineConfig } from '@ereo/core';
|
|
118
|
+
import {
|
|
119
|
+
createDrizzleAdapter,
|
|
120
|
+
definePostgresConfig,
|
|
121
|
+
createDatabasePlugin,
|
|
122
|
+
} from '@ereo/db-drizzle';
|
|
123
|
+
import * as schema from './db/schema';
|
|
124
|
+
|
|
125
|
+
const config = definePostgresConfig({
|
|
126
|
+
url: process.env.DATABASE_URL!,
|
|
127
|
+
schema,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const adapter = createDrizzleAdapter(config);
|
|
131
|
+
|
|
132
|
+
export default defineConfig({
|
|
133
|
+
plugins: [createDatabasePlugin(adapter)],
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 3. Use in Routes
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// routes/users.ts
|
|
141
|
+
import { createLoader } from '@ereo/core';
|
|
142
|
+
import { useDb } from '@ereo/db-drizzle';
|
|
143
|
+
import { users } from '../db/schema';
|
|
144
|
+
|
|
145
|
+
export const loader = createLoader({
|
|
146
|
+
load: async ({ context }) => {
|
|
147
|
+
const db = useDb(context);
|
|
148
|
+
const allUsers = await db.client.select().from(users);
|
|
149
|
+
return { users: allUsers };
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## API Reference
|
|
157
|
+
|
|
158
|
+
### createDrizzleAdapter
|
|
159
|
+
|
|
160
|
+
Creates a Drizzle database adapter implementing the `DatabaseAdapter` interface.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
function createDrizzleAdapter<TSchema = unknown>(
|
|
164
|
+
config: DrizzleConfig
|
|
165
|
+
): DatabaseAdapter<TSchema>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Parameters:**
|
|
169
|
+
- `config` - A `DrizzleConfig` object specifying the driver and connection options
|
|
170
|
+
|
|
171
|
+
**Returns:**
|
|
172
|
+
- A `DatabaseAdapter<TSchema>` instance
|
|
173
|
+
|
|
174
|
+
**Example:**
|
|
175
|
+
```typescript
|
|
176
|
+
import { createDrizzleAdapter } from '@ereo/db-drizzle';
|
|
177
|
+
|
|
178
|
+
const adapter = createDrizzleAdapter({
|
|
179
|
+
driver: 'postgres-js',
|
|
180
|
+
url: process.env.DATABASE_URL!,
|
|
181
|
+
schema,
|
|
182
|
+
logger: true,
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Configuration Helpers
|
|
187
|
+
|
|
188
|
+
#### defineDrizzleConfig
|
|
189
|
+
|
|
190
|
+
Generic type-safe configuration builder that provides autocomplete based on the selected driver.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
function defineDrizzleConfig<T extends DrizzleConfig>(config: T): T
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Example:**
|
|
197
|
+
```typescript
|
|
198
|
+
const config = defineDrizzleConfig({
|
|
199
|
+
driver: 'postgres-js',
|
|
200
|
+
url: process.env.DATABASE_URL!,
|
|
201
|
+
schema,
|
|
202
|
+
connection: {
|
|
203
|
+
ssl: 'require',
|
|
204
|
+
max: 10,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### definePostgresConfig
|
|
210
|
+
|
|
211
|
+
Creates a PostgreSQL configuration with sensible defaults.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
function definePostgresConfig(
|
|
215
|
+
config: Omit<PostgresConfig, 'driver'>
|
|
216
|
+
): PostgresConfig
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Default values:**
|
|
220
|
+
- `ssl`: `'require'`
|
|
221
|
+
- `max`: `10` connections
|
|
222
|
+
- `idle_timeout`: `20` seconds
|
|
223
|
+
- `connect_timeout`: `10` seconds
|
|
224
|
+
- `prepare`: `true`
|
|
225
|
+
|
|
226
|
+
**Example:**
|
|
227
|
+
```typescript
|
|
228
|
+
const config = definePostgresConfig({
|
|
229
|
+
url: process.env.DATABASE_URL!,
|
|
230
|
+
schema,
|
|
231
|
+
connection: {
|
|
232
|
+
max: 20, // Override default
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### defineNeonHttpConfig
|
|
238
|
+
|
|
239
|
+
Creates a Neon HTTP configuration (edge-compatible).
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
function defineNeonHttpConfig(
|
|
243
|
+
config: Omit<NeonHttpConfig, 'driver'>
|
|
244
|
+
): NeonHttpConfig
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Example:**
|
|
248
|
+
```typescript
|
|
249
|
+
const config = defineNeonHttpConfig({
|
|
250
|
+
url: process.env.DATABASE_URL!,
|
|
251
|
+
schema,
|
|
252
|
+
});
|
|
253
|
+
// config.edgeCompatible === true
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### defineNeonWebSocketConfig
|
|
257
|
+
|
|
258
|
+
Creates a Neon WebSocket configuration with connection pooling.
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
function defineNeonWebSocketConfig(
|
|
262
|
+
config: Omit<NeonWebSocketConfig, 'driver'>
|
|
263
|
+
): NeonWebSocketConfig
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Default values:**
|
|
267
|
+
- `pool.max`: `5` connections
|
|
268
|
+
- `pool.idleTimeoutMs`: `10000` ms
|
|
269
|
+
|
|
270
|
+
**Example:**
|
|
271
|
+
```typescript
|
|
272
|
+
const config = defineNeonWebSocketConfig({
|
|
273
|
+
url: process.env.DATABASE_URL!,
|
|
274
|
+
schema,
|
|
275
|
+
pool: {
|
|
276
|
+
max: 10,
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### definePlanetScaleConfig
|
|
282
|
+
|
|
283
|
+
Creates a PlanetScale configuration (edge-compatible).
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
function definePlanetScaleConfig(
|
|
287
|
+
config: Omit<PlanetScaleConfig, 'driver'>
|
|
288
|
+
): PlanetScaleConfig
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Example:**
|
|
292
|
+
```typescript
|
|
293
|
+
const config = definePlanetScaleConfig({
|
|
294
|
+
url: process.env.DATABASE_URL!,
|
|
295
|
+
schema,
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
#### defineLibSQLConfig
|
|
300
|
+
|
|
301
|
+
Creates a LibSQL/Turso configuration (edge-compatible).
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
function defineLibSQLConfig(
|
|
305
|
+
config: Omit<LibSQLConfig, 'driver'>
|
|
306
|
+
): LibSQLConfig
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Example:**
|
|
310
|
+
```typescript
|
|
311
|
+
const config = defineLibSQLConfig({
|
|
312
|
+
url: 'libsql://your-db.turso.io',
|
|
313
|
+
authToken: process.env.TURSO_AUTH_TOKEN,
|
|
314
|
+
schema,
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### defineBunSQLiteConfig
|
|
319
|
+
|
|
320
|
+
Creates a Bun SQLite configuration with optimized PRAGMA settings.
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
function defineBunSQLiteConfig(
|
|
324
|
+
config: Omit<BunSQLiteConfig, 'driver'>
|
|
325
|
+
): BunSQLiteConfig
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Default PRAGMA values:**
|
|
329
|
+
- `journal_mode`: `'WAL'`
|
|
330
|
+
- `synchronous`: `'NORMAL'`
|
|
331
|
+
- `foreign_keys`: `true`
|
|
332
|
+
- `cache_size`: `10000`
|
|
333
|
+
|
|
334
|
+
**Example:**
|
|
335
|
+
```typescript
|
|
336
|
+
const config = defineBunSQLiteConfig({
|
|
337
|
+
url: './data/app.db',
|
|
338
|
+
schema,
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
#### defineBetterSQLite3Config
|
|
343
|
+
|
|
344
|
+
Creates a better-sqlite3 configuration for Node.js.
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
function defineBetterSQLite3Config(
|
|
348
|
+
config: Omit<BetterSQLite3Config, 'driver'>
|
|
349
|
+
): BetterSQLite3Config
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Example:**
|
|
353
|
+
```typescript
|
|
354
|
+
const config = defineBetterSQLite3Config({
|
|
355
|
+
url: './data/app.db',
|
|
356
|
+
schema,
|
|
357
|
+
options: {
|
|
358
|
+
readonly: false,
|
|
359
|
+
fileMustExist: false,
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### defineD1Config
|
|
365
|
+
|
|
366
|
+
Creates a Cloudflare D1 configuration (edge-compatible).
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
function defineD1Config(
|
|
370
|
+
config: Omit<D1Config, 'driver'>
|
|
371
|
+
): D1Config
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Example:**
|
|
375
|
+
```typescript
|
|
376
|
+
// In a Cloudflare Worker
|
|
377
|
+
export default {
|
|
378
|
+
async fetch(request, env) {
|
|
379
|
+
const config = defineD1Config({
|
|
380
|
+
url: '', // Not used for D1
|
|
381
|
+
d1: env.DB, // D1 binding from wrangler.toml
|
|
382
|
+
schema,
|
|
383
|
+
});
|
|
384
|
+
// ...
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
#### defineEdgeConfig
|
|
390
|
+
|
|
391
|
+
Creates an edge-optimized configuration with a simplified API.
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
function defineEdgeConfig(options: EdgeConfigOptions): DrizzleConfig
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Parameters:**
|
|
398
|
+
- `driver`: `'neon-http' | 'neon-websocket' | 'planetscale' | 'libsql' | 'd1'`
|
|
399
|
+
- `url`: Database connection URL
|
|
400
|
+
- `schema`: Optional Drizzle schema object
|
|
401
|
+
- `authToken`: Optional auth token (for LibSQL/Turso)
|
|
402
|
+
- `debug`: Optional debug logging flag
|
|
403
|
+
|
|
404
|
+
**Example:**
|
|
405
|
+
```typescript
|
|
406
|
+
const config = defineEdgeConfig({
|
|
407
|
+
driver: 'neon-http',
|
|
408
|
+
url: process.env.DATABASE_URL!,
|
|
409
|
+
schema,
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Environment Detection
|
|
414
|
+
|
|
415
|
+
#### detectRuntime
|
|
416
|
+
|
|
417
|
+
Detects the current JavaScript runtime environment.
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
function detectRuntime(): RuntimeEnvironment
|
|
421
|
+
|
|
422
|
+
type RuntimeEnvironment =
|
|
423
|
+
| 'bun'
|
|
424
|
+
| 'node'
|
|
425
|
+
| 'cloudflare-workers'
|
|
426
|
+
| 'vercel-edge'
|
|
427
|
+
| 'deno'
|
|
428
|
+
| 'unknown';
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Example:**
|
|
432
|
+
```typescript
|
|
433
|
+
const runtime = detectRuntime();
|
|
434
|
+
if (runtime === 'cloudflare-workers') {
|
|
435
|
+
// Use D1 or edge-compatible driver
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### isEdgeRuntime
|
|
440
|
+
|
|
441
|
+
Checks if the current environment is an edge runtime.
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
function isEdgeRuntime(): boolean
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Returns:** `true` for Cloudflare Workers and Vercel Edge, `false` otherwise.
|
|
448
|
+
|
|
449
|
+
**Example:**
|
|
450
|
+
```typescript
|
|
451
|
+
if (isEdgeRuntime()) {
|
|
452
|
+
console.log('Running on the edge');
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### suggestDrivers
|
|
457
|
+
|
|
458
|
+
Suggests appropriate drivers for the current runtime environment.
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
function suggestDrivers(): DrizzleDriver[]
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Returns by runtime:**
|
|
465
|
+
- **Bun**: `['bun-sqlite', 'postgres-js', 'libsql']`
|
|
466
|
+
- **Node.js**: `['better-sqlite3', 'postgres-js', 'libsql']`
|
|
467
|
+
- **Cloudflare Workers**: `['d1', 'neon-http', 'planetscale']`
|
|
468
|
+
- **Vercel Edge**: `['neon-http', 'planetscale', 'libsql']`
|
|
469
|
+
- **Deno**: `['postgres-js', 'libsql']`
|
|
470
|
+
- **Unknown**: `['neon-http', 'postgres-js']`
|
|
471
|
+
|
|
472
|
+
**Example:**
|
|
473
|
+
```typescript
|
|
474
|
+
const recommended = suggestDrivers();
|
|
475
|
+
console.log(`Recommended drivers: ${recommended.join(', ')}`);
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Configuration Options
|
|
481
|
+
|
|
482
|
+
### PostgreSQL (postgres-js)
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
interface PostgresConfig {
|
|
486
|
+
driver: 'postgres-js';
|
|
487
|
+
url: string;
|
|
488
|
+
schema?: Record<string, unknown>;
|
|
489
|
+
logger?: boolean;
|
|
490
|
+
debug?: boolean;
|
|
491
|
+
edgeCompatible?: boolean; // Defaults to false
|
|
492
|
+
connection?: {
|
|
493
|
+
ssl?: boolean | 'require' | 'prefer' | 'allow';
|
|
494
|
+
max?: number; // Maximum connections (default: 10)
|
|
495
|
+
idle_timeout?: number; // Seconds (default: 20)
|
|
496
|
+
connect_timeout?: number; // Seconds (default: 10)
|
|
497
|
+
prepare?: boolean; // Use prepared statements (default: true)
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Neon HTTP
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
interface NeonHttpConfig {
|
|
506
|
+
driver: 'neon-http';
|
|
507
|
+
url: string;
|
|
508
|
+
schema?: Record<string, unknown>;
|
|
509
|
+
logger?: boolean;
|
|
510
|
+
debug?: boolean;
|
|
511
|
+
edgeCompatible?: boolean; // Defaults to true
|
|
512
|
+
neon?: {
|
|
513
|
+
fetchOptions?: RequestInit; // Custom fetch options
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Neon WebSocket
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
interface NeonWebSocketConfig {
|
|
522
|
+
driver: 'neon-websocket';
|
|
523
|
+
url: string;
|
|
524
|
+
schema?: Record<string, unknown>;
|
|
525
|
+
logger?: boolean;
|
|
526
|
+
debug?: boolean;
|
|
527
|
+
edgeCompatible?: boolean; // Defaults to true
|
|
528
|
+
pool?: {
|
|
529
|
+
min?: number;
|
|
530
|
+
max?: number; // Default: 5
|
|
531
|
+
idleTimeoutMs?: number; // Default: 10000
|
|
532
|
+
acquireTimeoutMs?: number;
|
|
533
|
+
acquireRetries?: number;
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### PlanetScale
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
interface PlanetScaleConfig {
|
|
542
|
+
driver: 'planetscale';
|
|
543
|
+
url: string;
|
|
544
|
+
schema?: Record<string, unknown>;
|
|
545
|
+
logger?: boolean;
|
|
546
|
+
debug?: boolean;
|
|
547
|
+
edgeCompatible?: boolean; // Defaults to true
|
|
548
|
+
planetscale?: {
|
|
549
|
+
fetch?: typeof fetch; // Custom fetch function
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### LibSQL/Turso
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
interface LibSQLConfig {
|
|
558
|
+
driver: 'libsql';
|
|
559
|
+
url: string;
|
|
560
|
+
schema?: Record<string, unknown>;
|
|
561
|
+
logger?: boolean;
|
|
562
|
+
debug?: boolean;
|
|
563
|
+
edgeCompatible?: boolean; // Defaults to true
|
|
564
|
+
authToken?: string; // Turso authentication token
|
|
565
|
+
syncUrl?: string; // Sync URL for embedded replicas
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Bun SQLite
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
interface BunSQLiteConfig {
|
|
573
|
+
driver: 'bun-sqlite';
|
|
574
|
+
url: string; // File path to SQLite database
|
|
575
|
+
schema?: Record<string, unknown>;
|
|
576
|
+
logger?: boolean;
|
|
577
|
+
debug?: boolean;
|
|
578
|
+
edgeCompatible?: boolean; // Defaults to false
|
|
579
|
+
pragma?: {
|
|
580
|
+
journal_mode?: 'DELETE' | 'TRUNCATE' | 'PERSIST' | 'MEMORY' | 'WAL' | 'OFF';
|
|
581
|
+
synchronous?: 'OFF' | 'NORMAL' | 'FULL' | 'EXTRA';
|
|
582
|
+
foreign_keys?: boolean;
|
|
583
|
+
cache_size?: number;
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### better-sqlite3
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
interface BetterSQLite3Config {
|
|
592
|
+
driver: 'better-sqlite3';
|
|
593
|
+
url: string; // File path to SQLite database
|
|
594
|
+
schema?: Record<string, unknown>;
|
|
595
|
+
logger?: boolean;
|
|
596
|
+
debug?: boolean;
|
|
597
|
+
edgeCompatible?: boolean; // Defaults to false
|
|
598
|
+
options?: {
|
|
599
|
+
readonly?: boolean;
|
|
600
|
+
fileMustExist?: boolean;
|
|
601
|
+
timeout?: number;
|
|
602
|
+
verbose?: (message: string) => void;
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Cloudflare D1
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
interface D1Config {
|
|
611
|
+
driver: 'd1';
|
|
612
|
+
url: string; // Not used, but required for interface
|
|
613
|
+
schema?: Record<string, unknown>;
|
|
614
|
+
logger?: boolean;
|
|
615
|
+
debug?: boolean;
|
|
616
|
+
edgeCompatible?: boolean; // Defaults to true
|
|
617
|
+
d1?: D1Database; // D1 binding from Cloudflare Workers
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## Drizzle ORM Integration
|
|
624
|
+
|
|
625
|
+
The adapter provides the full Drizzle client through the `client` property. All Drizzle query methods are available.
|
|
626
|
+
|
|
627
|
+
### Schema Passing
|
|
628
|
+
|
|
629
|
+
Pass your Drizzle schema to enable relational queries and type inference:
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
import * as schema from './db/schema';
|
|
633
|
+
|
|
634
|
+
const config = definePostgresConfig({
|
|
635
|
+
url: process.env.DATABASE_URL!,
|
|
636
|
+
schema, // Enables relational queries
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
const adapter = createDrizzleAdapter(config);
|
|
640
|
+
const db = adapter.getClient();
|
|
641
|
+
|
|
642
|
+
// Relational queries work
|
|
643
|
+
const usersWithPosts = await db.query.users.findMany({
|
|
644
|
+
with: {
|
|
645
|
+
posts: true,
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Logger Integration
|
|
651
|
+
|
|
652
|
+
Enable Drizzle's built-in query logger:
|
|
653
|
+
|
|
654
|
+
```typescript
|
|
655
|
+
const config = definePostgresConfig({
|
|
656
|
+
url: process.env.DATABASE_URL!,
|
|
657
|
+
schema,
|
|
658
|
+
logger: true, // Logs all queries to console
|
|
659
|
+
});
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Type Inference
|
|
663
|
+
|
|
664
|
+
Use Drizzle's type inference utilities:
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
import { InferSelect, InferInsert } from '@ereo/db-drizzle';
|
|
668
|
+
import { users } from './db/schema';
|
|
669
|
+
|
|
670
|
+
type User = InferSelect<typeof users>;
|
|
671
|
+
type NewUser = InferInsert<typeof users>;
|
|
672
|
+
|
|
673
|
+
// Or use Drizzle's built-in types
|
|
674
|
+
type User = typeof users.$inferSelect;
|
|
675
|
+
type NewUser = typeof users.$inferInsert;
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## Use Cases
|
|
681
|
+
|
|
682
|
+
### Basic Query Execution
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
import { createDrizzleAdapter, definePostgresConfig } from '@ereo/db-drizzle';
|
|
686
|
+
import { eq } from 'drizzle-orm';
|
|
687
|
+
import * as schema from './db/schema';
|
|
688
|
+
|
|
689
|
+
const adapter = createDrizzleAdapter(
|
|
690
|
+
definePostgresConfig({
|
|
691
|
+
url: process.env.DATABASE_URL!,
|
|
692
|
+
schema,
|
|
693
|
+
})
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
const db = adapter.getClient();
|
|
697
|
+
|
|
698
|
+
// Select queries
|
|
699
|
+
const allUsers = await db.select().from(schema.users);
|
|
700
|
+
|
|
701
|
+
// Filtered queries
|
|
702
|
+
const user = await db
|
|
703
|
+
.select()
|
|
704
|
+
.from(schema.users)
|
|
705
|
+
.where(eq(schema.users.id, 1));
|
|
706
|
+
|
|
707
|
+
// Insert
|
|
708
|
+
const [newUser] = await db
|
|
709
|
+
.insert(schema.users)
|
|
710
|
+
.values({ name: 'Alice', email: 'alice@example.com' })
|
|
711
|
+
.returning();
|
|
712
|
+
|
|
713
|
+
// Update
|
|
714
|
+
await db
|
|
715
|
+
.update(schema.users)
|
|
716
|
+
.set({ name: 'Bob' })
|
|
717
|
+
.where(eq(schema.users.id, 1));
|
|
718
|
+
|
|
719
|
+
// Delete
|
|
720
|
+
await db
|
|
721
|
+
.delete(schema.users)
|
|
722
|
+
.where(eq(schema.users.id, 1));
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### Transactions
|
|
726
|
+
|
|
727
|
+
#### Callback-Based Transactions (Recommended)
|
|
728
|
+
|
|
729
|
+
```typescript
|
|
730
|
+
import { withTransaction } from '@ereo/db-drizzle';
|
|
731
|
+
|
|
732
|
+
// Automatic commit on success, rollback on error
|
|
733
|
+
const result = await adapter.transaction(async (tx) => {
|
|
734
|
+
const [user] = await tx
|
|
735
|
+
.insert(schema.users)
|
|
736
|
+
.values({ name: 'Alice', email: 'alice@example.com' })
|
|
737
|
+
.returning();
|
|
738
|
+
|
|
739
|
+
await tx
|
|
740
|
+
.insert(schema.posts)
|
|
741
|
+
.values({ title: 'First Post', authorId: user.id });
|
|
742
|
+
|
|
743
|
+
return user;
|
|
744
|
+
});
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
#### Using withTransaction Helper
|
|
748
|
+
|
|
749
|
+
```typescript
|
|
750
|
+
import { createLoader } from '@ereo/core';
|
|
751
|
+
import { useDb, withTransaction } from '@ereo/db-drizzle';
|
|
752
|
+
|
|
753
|
+
export const action = createAction({
|
|
754
|
+
action: async ({ context, request }) => {
|
|
755
|
+
const db = useDb(context);
|
|
756
|
+
|
|
757
|
+
const result = await withTransaction(context, async (tx) => {
|
|
758
|
+
// All operations use the same transaction
|
|
759
|
+
const [user] = await tx
|
|
760
|
+
.insert(schema.users)
|
|
761
|
+
.values({ name: 'Alice', email: 'alice@example.com' })
|
|
762
|
+
.returning();
|
|
763
|
+
|
|
764
|
+
return user;
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
return { user: result };
|
|
768
|
+
},
|
|
769
|
+
});
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
#### Manual Transactions
|
|
773
|
+
|
|
774
|
+
```typescript
|
|
775
|
+
const tx = await adapter.beginTransaction();
|
|
776
|
+
|
|
777
|
+
try {
|
|
778
|
+
const [user] = await tx.client
|
|
779
|
+
.insert(schema.users)
|
|
780
|
+
.values({ name: 'Alice', email: 'alice@example.com' })
|
|
781
|
+
.returning();
|
|
782
|
+
|
|
783
|
+
await tx.commit();
|
|
784
|
+
return user;
|
|
785
|
+
} catch (error) {
|
|
786
|
+
await tx.rollback();
|
|
787
|
+
throw error;
|
|
788
|
+
}
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Request-Scoped Queries with Deduplication
|
|
792
|
+
|
|
793
|
+
The adapter automatically deduplicates identical queries within a single request:
|
|
794
|
+
|
|
795
|
+
```typescript
|
|
796
|
+
import { createLoader } from '@ereo/core';
|
|
797
|
+
import { useDb } from '@ereo/db-drizzle';
|
|
798
|
+
|
|
799
|
+
export const loader = createLoader({
|
|
800
|
+
load: async ({ context }) => {
|
|
801
|
+
const db = useDb(context);
|
|
802
|
+
|
|
803
|
+
// First call executes the query
|
|
804
|
+
const { result: users1, fromCache: cached1 } = await db.query(
|
|
805
|
+
'SELECT * FROM users WHERE active = $1',
|
|
806
|
+
[true]
|
|
807
|
+
);
|
|
808
|
+
// cached1 === false
|
|
809
|
+
|
|
810
|
+
// Identical query returns cached result
|
|
811
|
+
const { result: users2, fromCache: cached2 } = await db.query(
|
|
812
|
+
'SELECT * FROM users WHERE active = $1',
|
|
813
|
+
[true]
|
|
814
|
+
);
|
|
815
|
+
// cached2 === true
|
|
816
|
+
|
|
817
|
+
// Get deduplication statistics
|
|
818
|
+
const stats = db.getDedupStats();
|
|
819
|
+
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
|
|
820
|
+
|
|
821
|
+
// Clear cache after mutations
|
|
822
|
+
await db.client.insert(schema.users).values({ ... });
|
|
823
|
+
db.clearDedup(); // or db.invalidate(['users']);
|
|
824
|
+
|
|
825
|
+
return { users: users1.rows };
|
|
826
|
+
},
|
|
827
|
+
});
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
### Edge Deployment
|
|
831
|
+
|
|
832
|
+
#### Vercel Edge Functions
|
|
833
|
+
|
|
834
|
+
```typescript
|
|
835
|
+
// ereo.config.ts
|
|
836
|
+
import { defineConfig } from '@ereo/core';
|
|
837
|
+
import {
|
|
838
|
+
createDrizzleAdapter,
|
|
839
|
+
defineEdgeConfig,
|
|
840
|
+
createDatabasePlugin,
|
|
841
|
+
} from '@ereo/db-drizzle';
|
|
842
|
+
import * as schema from './db/schema';
|
|
843
|
+
|
|
844
|
+
const adapter = createDrizzleAdapter(
|
|
845
|
+
defineEdgeConfig({
|
|
846
|
+
driver: 'neon-http',
|
|
847
|
+
url: process.env.DATABASE_URL!,
|
|
848
|
+
schema,
|
|
849
|
+
})
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
export default defineConfig({
|
|
853
|
+
plugins: [createDatabasePlugin(adapter)],
|
|
854
|
+
});
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
#### Cloudflare Workers with D1
|
|
858
|
+
|
|
859
|
+
```typescript
|
|
860
|
+
// src/index.ts
|
|
861
|
+
import {
|
|
862
|
+
createDrizzleAdapter,
|
|
863
|
+
defineD1Config,
|
|
864
|
+
createDatabasePlugin,
|
|
865
|
+
} from '@ereo/db-drizzle';
|
|
866
|
+
import * as schema from './db/schema';
|
|
867
|
+
|
|
868
|
+
export default {
|
|
869
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
870
|
+
const adapter = createDrizzleAdapter(
|
|
871
|
+
defineD1Config({
|
|
872
|
+
url: '',
|
|
873
|
+
d1: env.DB,
|
|
874
|
+
schema,
|
|
875
|
+
})
|
|
876
|
+
);
|
|
877
|
+
|
|
878
|
+
const db = adapter.getClient();
|
|
879
|
+
const users = await db.select().from(schema.users);
|
|
880
|
+
|
|
881
|
+
return Response.json({ users });
|
|
882
|
+
},
|
|
883
|
+
};
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
### Health Checks
|
|
887
|
+
|
|
888
|
+
```typescript
|
|
889
|
+
import { createDrizzleAdapter, definePostgresConfig } from '@ereo/db-drizzle';
|
|
890
|
+
|
|
891
|
+
const adapter = createDrizzleAdapter(
|
|
892
|
+
definePostgresConfig({
|
|
893
|
+
url: process.env.DATABASE_URL!,
|
|
894
|
+
})
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
// Perform health check
|
|
898
|
+
const health = await adapter.healthCheck();
|
|
899
|
+
|
|
900
|
+
if (health.healthy) {
|
|
901
|
+
console.log(`Database healthy, latency: ${health.latencyMs}ms`);
|
|
902
|
+
console.log(`Driver: ${health.metadata?.driver}`);
|
|
903
|
+
} else {
|
|
904
|
+
console.error(`Database unhealthy: ${health.error}`);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Graceful shutdown
|
|
908
|
+
process.on('SIGTERM', async () => {
|
|
909
|
+
await adapter.disconnect();
|
|
910
|
+
process.exit(0);
|
|
911
|
+
});
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
---
|
|
915
|
+
|
|
916
|
+
## Migrations
|
|
917
|
+
|
|
918
|
+
This package does not handle migrations directly. Use Drizzle Kit for schema migrations.
|
|
919
|
+
|
|
920
|
+
### Setup Drizzle Kit
|
|
921
|
+
|
|
922
|
+
```typescript
|
|
923
|
+
// drizzle.config.ts
|
|
924
|
+
import type { Config } from 'drizzle-kit';
|
|
925
|
+
|
|
926
|
+
export default {
|
|
927
|
+
schema: './db/schema.ts',
|
|
928
|
+
out: './drizzle',
|
|
929
|
+
driver: 'pg', // or 'mysql2', 'better-sqlite', 'libsql', 'd1'
|
|
930
|
+
dbCredentials: {
|
|
931
|
+
connectionString: process.env.DATABASE_URL!,
|
|
932
|
+
},
|
|
933
|
+
} satisfies Config;
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
### Common Commands
|
|
937
|
+
|
|
938
|
+
```bash
|
|
939
|
+
# Generate migrations
|
|
940
|
+
bunx drizzle-kit generate:pg
|
|
941
|
+
|
|
942
|
+
# Apply migrations
|
|
943
|
+
bunx drizzle-kit push:pg
|
|
944
|
+
|
|
945
|
+
# Open Drizzle Studio
|
|
946
|
+
bunx drizzle-kit studio
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
### Migration with Different Drivers
|
|
950
|
+
|
|
951
|
+
```typescript
|
|
952
|
+
// PostgreSQL
|
|
953
|
+
export default {
|
|
954
|
+
driver: 'pg',
|
|
955
|
+
dbCredentials: {
|
|
956
|
+
connectionString: process.env.DATABASE_URL!,
|
|
957
|
+
},
|
|
958
|
+
} satisfies Config;
|
|
959
|
+
|
|
960
|
+
// PlanetScale
|
|
961
|
+
export default {
|
|
962
|
+
driver: 'mysql2',
|
|
963
|
+
dbCredentials: {
|
|
964
|
+
uri: process.env.DATABASE_URL!,
|
|
965
|
+
},
|
|
966
|
+
} satisfies Config;
|
|
967
|
+
|
|
968
|
+
// LibSQL/Turso
|
|
969
|
+
export default {
|
|
970
|
+
driver: 'libsql',
|
|
971
|
+
dbCredentials: {
|
|
972
|
+
url: process.env.DATABASE_URL!,
|
|
973
|
+
authToken: process.env.DATABASE_AUTH_TOKEN,
|
|
974
|
+
},
|
|
975
|
+
} satisfies Config;
|
|
976
|
+
|
|
977
|
+
// Cloudflare D1
|
|
978
|
+
export default {
|
|
979
|
+
driver: 'd1',
|
|
980
|
+
dbCredentials: {
|
|
981
|
+
wranglerConfigPath: './wrangler.toml',
|
|
982
|
+
dbName: 'my-database',
|
|
983
|
+
},
|
|
984
|
+
} satisfies Config;
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
---
|
|
988
|
+
|
|
989
|
+
## Error Handling
|
|
990
|
+
|
|
991
|
+
The adapter throws typed errors from `@ereo/db`:
|
|
992
|
+
|
|
993
|
+
### ConnectionError
|
|
994
|
+
|
|
995
|
+
Thrown when the database connection fails.
|
|
996
|
+
|
|
997
|
+
```typescript
|
|
998
|
+
import { ConnectionError } from '@ereo/db-drizzle';
|
|
999
|
+
|
|
1000
|
+
try {
|
|
1001
|
+
const db = adapter.getClient();
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
if (error instanceof ConnectionError) {
|
|
1004
|
+
console.error('Failed to connect:', error.message);
|
|
1005
|
+
// error.code === 'CONNECTION_ERROR'
|
|
1006
|
+
// error.cause contains the original error
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
### QueryError
|
|
1012
|
+
|
|
1013
|
+
Thrown when a query fails to execute.
|
|
1014
|
+
|
|
1015
|
+
```typescript
|
|
1016
|
+
import { QueryError } from '@ereo/db-drizzle';
|
|
1017
|
+
|
|
1018
|
+
try {
|
|
1019
|
+
await adapter.query('SELECT * FROM nonexistent');
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
if (error instanceof QueryError) {
|
|
1022
|
+
console.error('Query failed:', error.message);
|
|
1023
|
+
console.error('SQL:', error.query);
|
|
1024
|
+
console.error('Params:', error.params);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
### TransactionError
|
|
1030
|
+
|
|
1031
|
+
Thrown when a transaction operation fails.
|
|
1032
|
+
|
|
1033
|
+
```typescript
|
|
1034
|
+
import { TransactionError } from '@ereo/db-drizzle';
|
|
1035
|
+
|
|
1036
|
+
try {
|
|
1037
|
+
await adapter.transaction(async (tx) => {
|
|
1038
|
+
// ...
|
|
1039
|
+
throw new Error('Something went wrong');
|
|
1040
|
+
});
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
if (error instanceof TransactionError) {
|
|
1043
|
+
console.error('Transaction failed:', error.message);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
### Error Handling Pattern
|
|
1049
|
+
|
|
1050
|
+
```typescript
|
|
1051
|
+
import {
|
|
1052
|
+
ConnectionError,
|
|
1053
|
+
QueryError,
|
|
1054
|
+
TransactionError,
|
|
1055
|
+
DatabaseError,
|
|
1056
|
+
} from '@ereo/db-drizzle';
|
|
1057
|
+
|
|
1058
|
+
try {
|
|
1059
|
+
await performDatabaseOperation();
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
if (error instanceof ConnectionError) {
|
|
1062
|
+
// Handle connection issues (retry, alert, etc.)
|
|
1063
|
+
} else if (error instanceof QueryError) {
|
|
1064
|
+
// Handle query issues (log, validate input, etc.)
|
|
1065
|
+
} else if (error instanceof TransactionError) {
|
|
1066
|
+
// Handle transaction issues (retry, partial rollback, etc.)
|
|
1067
|
+
} else if (error instanceof DatabaseError) {
|
|
1068
|
+
// Generic database error
|
|
1069
|
+
} else {
|
|
1070
|
+
// Non-database error
|
|
1071
|
+
throw error;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
---
|
|
1077
|
+
|
|
1078
|
+
## TypeScript Types Reference
|
|
1079
|
+
|
|
1080
|
+
### Driver Types
|
|
1081
|
+
|
|
1082
|
+
```typescript
|
|
1083
|
+
// Supported database drivers
|
|
1084
|
+
type DrizzleDriver =
|
|
1085
|
+
| 'postgres-js'
|
|
1086
|
+
| 'neon-http'
|
|
1087
|
+
| 'neon-websocket'
|
|
1088
|
+
| 'planetscale'
|
|
1089
|
+
| 'libsql'
|
|
1090
|
+
| 'bun-sqlite'
|
|
1091
|
+
| 'better-sqlite3'
|
|
1092
|
+
| 'd1';
|
|
1093
|
+
|
|
1094
|
+
// Edge compatibility map
|
|
1095
|
+
const EDGE_COMPATIBLE_DRIVERS: Record<DrizzleDriver, boolean>;
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
### Configuration Types
|
|
1099
|
+
|
|
1100
|
+
```typescript
|
|
1101
|
+
// Union of all configuration types
|
|
1102
|
+
type DrizzleConfig =
|
|
1103
|
+
| PostgresConfig
|
|
1104
|
+
| NeonHttpConfig
|
|
1105
|
+
| NeonWebSocketConfig
|
|
1106
|
+
| PlanetScaleConfig
|
|
1107
|
+
| LibSQLConfig
|
|
1108
|
+
| BunSQLiteConfig
|
|
1109
|
+
| BetterSQLite3Config
|
|
1110
|
+
| D1Config;
|
|
1111
|
+
|
|
1112
|
+
// Edge configuration options
|
|
1113
|
+
interface EdgeConfigOptions {
|
|
1114
|
+
driver: 'neon-http' | 'neon-websocket' | 'planetscale' | 'libsql' | 'd1';
|
|
1115
|
+
url: string;
|
|
1116
|
+
schema?: Record<string, unknown>;
|
|
1117
|
+
authToken?: string;
|
|
1118
|
+
debug?: boolean;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Runtime environment
|
|
1122
|
+
type RuntimeEnvironment =
|
|
1123
|
+
| 'bun'
|
|
1124
|
+
| 'node'
|
|
1125
|
+
| 'cloudflare-workers'
|
|
1126
|
+
| 'vercel-edge'
|
|
1127
|
+
| 'deno'
|
|
1128
|
+
| 'unknown';
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
### Re-exported Types from @ereo/db
|
|
1132
|
+
|
|
1133
|
+
```typescript
|
|
1134
|
+
// Core adapter interface
|
|
1135
|
+
interface DatabaseAdapter<TSchema = unknown> {
|
|
1136
|
+
readonly name: string;
|
|
1137
|
+
readonly edgeCompatible: boolean;
|
|
1138
|
+
getClient(): TSchema;
|
|
1139
|
+
getRequestClient(context: AppContext): RequestScopedClient<TSchema>;
|
|
1140
|
+
query<T = unknown>(sql: string, params?: unknown[]): Promise<QueryResult<T>>;
|
|
1141
|
+
execute(sql: string, params?: unknown[]): Promise<MutationResult>;
|
|
1142
|
+
transaction<T>(fn: (tx: TSchema) => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
1143
|
+
beginTransaction(options?: TransactionOptions): Promise<Transaction<TSchema>>;
|
|
1144
|
+
healthCheck(): Promise<HealthCheckResult>;
|
|
1145
|
+
disconnect(): Promise<void>;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// Request-scoped client with deduplication
|
|
1149
|
+
interface RequestScopedClient<TSchema> {
|
|
1150
|
+
readonly client: TSchema;
|
|
1151
|
+
query<T = unknown>(sql: string, params?: unknown[]): Promise<DedupResult<QueryResult<T>>>;
|
|
1152
|
+
getDedupStats(): DedupStats;
|
|
1153
|
+
clearDedup(): void;
|
|
1154
|
+
invalidate(tables?: string[]): void;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Query results
|
|
1158
|
+
interface QueryResult<T = unknown> {
|
|
1159
|
+
rows: T[];
|
|
1160
|
+
rowCount: number;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
interface MutationResult {
|
|
1164
|
+
rowsAffected: number;
|
|
1165
|
+
lastInsertId?: number | bigint;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
interface DedupResult<T> {
|
|
1169
|
+
result: T;
|
|
1170
|
+
fromCache: boolean;
|
|
1171
|
+
cacheKey: string;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
interface DedupStats {
|
|
1175
|
+
total: number;
|
|
1176
|
+
deduplicated: number;
|
|
1177
|
+
unique: number;
|
|
1178
|
+
hitRate: number;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Transaction types
|
|
1182
|
+
interface TransactionOptions {
|
|
1183
|
+
isolationLevel?: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable';
|
|
1184
|
+
readOnly?: boolean;
|
|
1185
|
+
timeout?: number;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
interface Transaction<TSchema> {
|
|
1189
|
+
readonly client: TSchema;
|
|
1190
|
+
commit(): Promise<void>;
|
|
1191
|
+
rollback(): Promise<void>;
|
|
1192
|
+
readonly isActive: boolean;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// Health check
|
|
1196
|
+
interface HealthCheckResult {
|
|
1197
|
+
healthy: boolean;
|
|
1198
|
+
latencyMs: number;
|
|
1199
|
+
error?: string;
|
|
1200
|
+
metadata?: Record<string, unknown>;
|
|
1201
|
+
}
|
|
1202
|
+
```
|
|
1203
|
+
|
|
1204
|
+
### D1 Types (Cloudflare Workers)
|
|
1205
|
+
|
|
1206
|
+
```typescript
|
|
1207
|
+
interface D1Database {
|
|
1208
|
+
prepare(query: string): D1PreparedStatement;
|
|
1209
|
+
dump(): Promise<ArrayBuffer>;
|
|
1210
|
+
batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
|
|
1211
|
+
exec(query: string): Promise<D1ExecResult>;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
interface D1PreparedStatement {
|
|
1215
|
+
bind(...values: unknown[]): D1PreparedStatement;
|
|
1216
|
+
first<T = unknown>(colName?: string): Promise<T>;
|
|
1217
|
+
run(): Promise<D1Result>;
|
|
1218
|
+
all<T = unknown>(): Promise<D1Result<T>>;
|
|
1219
|
+
raw<T = unknown>(): Promise<T[]>;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
interface D1Result<T = unknown> {
|
|
1223
|
+
results?: T[];
|
|
1224
|
+
success: boolean;
|
|
1225
|
+
error?: string;
|
|
1226
|
+
meta: {
|
|
1227
|
+
changed_db?: boolean;
|
|
1228
|
+
changes?: number;
|
|
1229
|
+
last_row_id?: number;
|
|
1230
|
+
duration?: number;
|
|
1231
|
+
rows_read?: number;
|
|
1232
|
+
rows_written?: number;
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
interface D1ExecResult {
|
|
1237
|
+
count: number;
|
|
1238
|
+
duration: number;
|
|
1239
|
+
}
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
---
|
|
1243
|
+
|
|
1244
|
+
## Troubleshooting / FAQ
|
|
1245
|
+
|
|
1246
|
+
### Connection Issues
|
|
1247
|
+
|
|
1248
|
+
**Q: I'm getting "Failed to connect to database" errors.**
|
|
1249
|
+
|
|
1250
|
+
A: Check the following:
|
|
1251
|
+
1. Verify your `DATABASE_URL` is correct and accessible
|
|
1252
|
+
2. For PostgreSQL, ensure SSL settings match your database requirements
|
|
1253
|
+
3. For serverless databases (Neon, PlanetScale), verify your API keys/tokens
|
|
1254
|
+
4. Check firewall rules if connecting to a remote database
|
|
1255
|
+
|
|
1256
|
+
```typescript
|
|
1257
|
+
// Enable debug logging to see connection details
|
|
1258
|
+
const config = definePostgresConfig({
|
|
1259
|
+
url: process.env.DATABASE_URL!,
|
|
1260
|
+
debug: true,
|
|
1261
|
+
});
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
### Edge Runtime Errors
|
|
1265
|
+
|
|
1266
|
+
**Q: My database queries fail on Vercel Edge or Cloudflare Workers.**
|
|
1267
|
+
|
|
1268
|
+
A: Ensure you're using an edge-compatible driver:
|
|
1269
|
+
|
|
1270
|
+
```typescript
|
|
1271
|
+
// Check edge compatibility
|
|
1272
|
+
import { EDGE_COMPATIBLE_DRIVERS } from '@ereo/db-drizzle';
|
|
1273
|
+
|
|
1274
|
+
console.log(EDGE_COMPATIBLE_DRIVERS);
|
|
1275
|
+
// {
|
|
1276
|
+
// 'postgres-js': false, // NOT edge compatible
|
|
1277
|
+
// 'neon-http': true, // Edge compatible
|
|
1278
|
+
// 'neon-websocket': true, // Edge compatible
|
|
1279
|
+
// 'planetscale': true, // Edge compatible
|
|
1280
|
+
// 'libsql': true, // Edge compatible
|
|
1281
|
+
// 'bun-sqlite': false, // NOT edge compatible
|
|
1282
|
+
// 'better-sqlite3': false, // NOT edge compatible
|
|
1283
|
+
// 'd1': true, // Edge compatible
|
|
1284
|
+
// }
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
### Transaction Support
|
|
1288
|
+
|
|
1289
|
+
**Q: Transactions don't work with SQLite drivers.**
|
|
1290
|
+
|
|
1291
|
+
A: For `bun-sqlite` and `better-sqlite3`, use the Drizzle client's transaction method directly:
|
|
1292
|
+
|
|
1293
|
+
```typescript
|
|
1294
|
+
const db = adapter.getClient();
|
|
1295
|
+
|
|
1296
|
+
// Use Drizzle's built-in transaction support
|
|
1297
|
+
await db.transaction(async (tx) => {
|
|
1298
|
+
// Your transaction code
|
|
1299
|
+
});
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
### Query Deduplication
|
|
1303
|
+
|
|
1304
|
+
**Q: How do I disable query deduplication?**
|
|
1305
|
+
|
|
1306
|
+
A: Query deduplication only applies to request-scoped clients. Use `adapter.getClient()` for non-deduplicated queries:
|
|
1307
|
+
|
|
1308
|
+
```typescript
|
|
1309
|
+
// Deduplicated (via useDb/getRequestClient)
|
|
1310
|
+
const db = useDb(context);
|
|
1311
|
+
await db.query('SELECT * FROM users', []);
|
|
1312
|
+
|
|
1313
|
+
// Not deduplicated (direct client)
|
|
1314
|
+
const client = adapter.getClient();
|
|
1315
|
+
await client.select().from(users);
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
**Q: Why isn't my cache invalidating after mutations?**
|
|
1319
|
+
|
|
1320
|
+
A: Call `invalidate()` or `clearDedup()` after mutations:
|
|
1321
|
+
|
|
1322
|
+
```typescript
|
|
1323
|
+
const db = useDb(context);
|
|
1324
|
+
|
|
1325
|
+
// Perform mutation
|
|
1326
|
+
await db.client.insert(users).values({ name: 'Alice' });
|
|
1327
|
+
|
|
1328
|
+
// Invalidate cache for specific tables
|
|
1329
|
+
db.invalidate(['users']);
|
|
1330
|
+
|
|
1331
|
+
// Or clear entire cache
|
|
1332
|
+
db.clearDedup();
|
|
1333
|
+
```
|
|
1334
|
+
|
|
1335
|
+
### Driver Selection
|
|
1336
|
+
|
|
1337
|
+
**Q: Which driver should I use?**
|
|
1338
|
+
|
|
1339
|
+
A: Use `suggestDrivers()` to get recommendations based on your runtime:
|
|
1340
|
+
|
|
1341
|
+
```typescript
|
|
1342
|
+
import { suggestDrivers, detectRuntime } from '@ereo/db-drizzle';
|
|
1343
|
+
|
|
1344
|
+
const runtime = detectRuntime();
|
|
1345
|
+
const recommended = suggestDrivers();
|
|
1346
|
+
|
|
1347
|
+
console.log(`Runtime: ${runtime}`);
|
|
1348
|
+
console.log(`Recommended drivers: ${recommended.join(', ')}`);
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
General guidelines:
|
|
1352
|
+
- **Local development**: `bun-sqlite` (Bun) or `better-sqlite3` (Node.js)
|
|
1353
|
+
- **Traditional servers**: `postgres-js` for PostgreSQL
|
|
1354
|
+
- **Serverless/Edge**: `neon-http`, `planetscale`, or `libsql`
|
|
1355
|
+
- **Cloudflare Workers**: `d1` or `neon-http`
|
|
1356
|
+
|
|
1357
|
+
### Schema Not Working
|
|
1358
|
+
|
|
1359
|
+
**Q: Relational queries return undefined.**
|
|
1360
|
+
|
|
1361
|
+
A: Ensure you pass the schema to your configuration:
|
|
1362
|
+
|
|
1363
|
+
```typescript
|
|
1364
|
+
import * as schema from './db/schema';
|
|
1365
|
+
|
|
1366
|
+
const config = definePostgresConfig({
|
|
1367
|
+
url: process.env.DATABASE_URL!,
|
|
1368
|
+
schema, // Required for relational queries
|
|
1369
|
+
});
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
### Performance
|
|
1373
|
+
|
|
1374
|
+
**Q: How can I improve query performance?**
|
|
1375
|
+
|
|
1376
|
+
A: Consider these optimizations:
|
|
1377
|
+
|
|
1378
|
+
1. **Enable prepared statements** (default for postgres-js):
|
|
1379
|
+
```typescript
|
|
1380
|
+
connection: { prepare: true }
|
|
1381
|
+
```
|
|
1382
|
+
|
|
1383
|
+
2. **Tune connection pool** for your workload:
|
|
1384
|
+
```typescript
|
|
1385
|
+
connection: { max: 20, idle_timeout: 30 }
|
|
1386
|
+
```
|
|
1387
|
+
|
|
1388
|
+
3. **Use WAL mode for SQLite**:
|
|
1389
|
+
```typescript
|
|
1390
|
+
pragma: { journal_mode: 'WAL' }
|
|
1391
|
+
```
|
|
1392
|
+
|
|
1393
|
+
4. **Leverage query deduplication** for read-heavy routes
|
|
1394
|
+
|
|
1395
|
+
5. **Use appropriate indexes** in your schema
|
|
1396
|
+
|
|
1397
|
+
---
|
|
1398
|
+
|
|
1399
|
+
## License
|
|
1400
|
+
|
|
1401
|
+
MIT
|