@beignet/provider-drizzle-turso 0.0.1
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/CHANGELOG.md +5 -0
- package/README.md +415 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +163 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -0
- package/src/index.ts +306 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# @beignet/provider-drizzle-turso
|
|
2
|
+
|
|
3
|
+
Drizzle ORM + Turso/libSQL provider for Beignet applications.
|
|
4
|
+
|
|
5
|
+
The provider installs a typed database port backed by Drizzle ORM and Turso's
|
|
6
|
+
libSQL client. Your application still owns the schema, repository interfaces,
|
|
7
|
+
and migration workflow.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Factory-based provider creation with your schema at the call site.
|
|
12
|
+
- Full TypeScript inference from your Drizzle schema.
|
|
13
|
+
- Works with Turso Cloud or local libSQL.
|
|
14
|
+
- Keeps runtime provider wiring separate from Drizzle CLI configuration.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bun add @beignet/provider-drizzle-turso drizzle-orm @libsql/client
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
### 1. Define your schema
|
|
25
|
+
|
|
26
|
+
Create your Drizzle schema files wherever makes sense for your app. Framework
|
|
27
|
+
usage keeps them under `infra/db/schema/` so larger apps can split schema by
|
|
28
|
+
feature:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// infra/db/schema/todos.ts
|
|
32
|
+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
33
|
+
|
|
34
|
+
export const todos = sqliteTable("todos", {
|
|
35
|
+
id: text("id").primaryKey(),
|
|
36
|
+
title: text("title").notNull(),
|
|
37
|
+
completed: integer("completed", { mode: "boolean" }).notNull().default(false),
|
|
38
|
+
createdAt: text("created_at").notNull(),
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// infra/db/schema/index.ts
|
|
44
|
+
export { todos } from "./todos";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Configure Drizzle CLI (build-time)
|
|
48
|
+
|
|
49
|
+
Create `drizzle.config.ts` in your app root for the Drizzle CLI:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// drizzle.config.ts
|
|
53
|
+
export default {
|
|
54
|
+
schema: "./infra/db/schema/index.ts",
|
|
55
|
+
out: "./drizzle",
|
|
56
|
+
dialect: "sqlite",
|
|
57
|
+
dbCredentials: {
|
|
58
|
+
url: process.env.TURSO_DB_URL!,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Create the provider (runtime)
|
|
64
|
+
|
|
65
|
+
Import your schema and create the provider:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// server/providers.ts
|
|
69
|
+
import { createDrizzleTursoProvider } from "@beignet/provider-drizzle-turso";
|
|
70
|
+
import * as schema from "@/infra/db/schema";
|
|
71
|
+
|
|
72
|
+
const drizzleTursoProvider = createDrizzleTursoProvider({ schema });
|
|
73
|
+
|
|
74
|
+
export const providers = [drizzleTursoProvider];
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 4. Type your ports
|
|
78
|
+
|
|
79
|
+
Define your app's ports type:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// ports/index.ts
|
|
83
|
+
import type { DbPort } from "@beignet/provider-drizzle-turso";
|
|
84
|
+
import * as schema from "@/infra/db/schema";
|
|
85
|
+
|
|
86
|
+
export type AppPorts = {
|
|
87
|
+
db: DbPort<typeof schema>;
|
|
88
|
+
// other ports...
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 5. Wire it up in your server
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// server/index.ts
|
|
96
|
+
import { createNextServer } from "@beignet/next";
|
|
97
|
+
import { appPorts } from "@/infra/app-ports";
|
|
98
|
+
import { routes } from "@/server/routes";
|
|
99
|
+
import { providers } from "./providers";
|
|
100
|
+
|
|
101
|
+
export const server = await createNextServer({
|
|
102
|
+
ports: appPorts,
|
|
103
|
+
providers,
|
|
104
|
+
createContext: ({ ports }) => ({ ports }),
|
|
105
|
+
routes,
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 6. Expose repository ports to use cases
|
|
110
|
+
|
|
111
|
+
Use cases should depend on app-owned repository ports. Keep raw Drizzle access in
|
|
112
|
+
infrastructure, then wire the repository into `ctx.ports`.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// infra/todos/drizzle-todo-repository.ts
|
|
116
|
+
import { desc } from "drizzle-orm";
|
|
117
|
+
import type { DrizzleTursoDatabase } from "@beignet/provider-drizzle-turso";
|
|
118
|
+
import type { TodoRepository } from "@/features/todos/ports";
|
|
119
|
+
import * as schema from "@/infra/db/schema";
|
|
120
|
+
|
|
121
|
+
export function createTodoRepository(
|
|
122
|
+
db: DrizzleTursoDatabase<typeof schema>,
|
|
123
|
+
): TodoRepository {
|
|
124
|
+
return {
|
|
125
|
+
async list(input) {
|
|
126
|
+
return db
|
|
127
|
+
.select()
|
|
128
|
+
.from(schema.todos)
|
|
129
|
+
.orderBy(desc(schema.todos.createdAt))
|
|
130
|
+
.limit(input.limit)
|
|
131
|
+
.offset(input.offset);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Collect repositories in one infra factory:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// infra/db/repositories.ts
|
|
141
|
+
import type { DrizzleTursoDatabase } from "@beignet/provider-drizzle-turso";
|
|
142
|
+
import { createTodoRepository } from "@/infra/todos/drizzle-todo-repository";
|
|
143
|
+
import * as schema from "./schema";
|
|
144
|
+
|
|
145
|
+
export function createRepositories(db: DrizzleTursoDatabase<typeof schema>) {
|
|
146
|
+
return {
|
|
147
|
+
todos: createTodoRepository(db),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
// features/todos/use-cases/list-todos.ts
|
|
154
|
+
export const listTodos = useCase.query("todos.list").run(async ({ ctx }) => {
|
|
155
|
+
return ctx.ports.todos.list({ limit: 20, offset: 0 });
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`ctx.ports.db.db` remains available as a provider-specific escape hatch for
|
|
160
|
+
infrastructure code and one-off advanced Drizzle features. Do not make it the
|
|
161
|
+
normal dependency for application use cases.
|
|
162
|
+
|
|
163
|
+
## Devtools
|
|
164
|
+
|
|
165
|
+
When `@beignet/devtools` is registered before this provider, Drizzle query
|
|
166
|
+
logging is recorded automatically under the `db` watcher. Events include the SQL
|
|
167
|
+
query text, parameter count, port name, and provider name. Parameter values are
|
|
168
|
+
redacted by default.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { createDevtoolsProvider } from "@beignet/devtools";
|
|
172
|
+
import { createDrizzleTursoProvider } from "@beignet/provider-drizzle-turso";
|
|
173
|
+
|
|
174
|
+
export const providers = [
|
|
175
|
+
createDevtoolsProvider(),
|
|
176
|
+
createDrizzleTursoProvider({ schema }),
|
|
177
|
+
];
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Unit of work
|
|
181
|
+
|
|
182
|
+
Use `createDrizzleTursoUnitOfWork(...)` when a use case needs a real database
|
|
183
|
+
transaction. Your app still owns the repository interfaces; the helper only
|
|
184
|
+
starts a Drizzle transaction, gives you the transaction client, and optionally
|
|
185
|
+
flushes recorded domain events after commit.
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
// ports/index.ts
|
|
189
|
+
import type {
|
|
190
|
+
DomainEventRecorderPort,
|
|
191
|
+
UnitOfWorkPort,
|
|
192
|
+
} from "@beignet/core/ports";
|
|
193
|
+
import type { DbPort } from "@beignet/provider-drizzle-turso";
|
|
194
|
+
import * as schema from "@/infra/db/schema";
|
|
195
|
+
import type { TodoRepository } from "./todo-repository";
|
|
196
|
+
|
|
197
|
+
export type AppTransactionPorts = {
|
|
198
|
+
todos: TodoRepository;
|
|
199
|
+
events: DomainEventRecorderPort;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export type AppPorts = {
|
|
203
|
+
db: DbPort<typeof schema>;
|
|
204
|
+
todos: TodoRepository;
|
|
205
|
+
uow: UnitOfWorkPort<AppTransactionPorts>;
|
|
206
|
+
};
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
// infra/todos/drizzle-todo-repository.ts
|
|
211
|
+
import type { DrizzleTursoDatabase } from "@beignet/provider-drizzle-turso";
|
|
212
|
+
import type { TodoRepository } from "@/features/todos/ports";
|
|
213
|
+
import * as schema from "@/infra/db/schema";
|
|
214
|
+
|
|
215
|
+
export function createDrizzleTodoRepository(
|
|
216
|
+
db: DrizzleTursoDatabase<typeof schema>,
|
|
217
|
+
): TodoRepository {
|
|
218
|
+
return {
|
|
219
|
+
async create(input) {
|
|
220
|
+
const [row] = await db.insert(schema.todos).values(input).returning();
|
|
221
|
+
return row;
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
// server/index.ts
|
|
229
|
+
import { createDrizzleTursoUnitOfWork } from "@beignet/provider-drizzle-turso";
|
|
230
|
+
import { createRepositories } from "@/infra/db/repositories";
|
|
231
|
+
|
|
232
|
+
createContext: async ({ ports }) => {
|
|
233
|
+
const repositories = createRepositories(ports.db.db);
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
ports: {
|
|
237
|
+
...ports,
|
|
238
|
+
...repositories,
|
|
239
|
+
uow: createDrizzleTursoUnitOfWork({
|
|
240
|
+
db: ports.db.db,
|
|
241
|
+
eventBus: ports.eventBus,
|
|
242
|
+
createTransactionPorts: (tx, events) => ({
|
|
243
|
+
...createRepositories(tx),
|
|
244
|
+
events,
|
|
245
|
+
}),
|
|
246
|
+
}),
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Inside a use case, call transaction-scoped repositories through `tx` and record
|
|
253
|
+
declared events through the use-case `events` helper:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const todo = await ctx.ports.uow.transaction(async (tx) => {
|
|
257
|
+
const created = await tx.todos.create(input);
|
|
258
|
+
await events.record(tx.events, todoCreated, { todoId: created.id });
|
|
259
|
+
return created;
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Recorded events are published only after Drizzle commits. If the transaction
|
|
264
|
+
throws, events are not flushed. If event publishing fails after commit,
|
|
265
|
+
`transaction(...)` rejects but the database transaction is already committed;
|
|
266
|
+
use an outbox when events or jobs need durable delivery guarantees.
|
|
267
|
+
|
|
268
|
+
## Configuration
|
|
269
|
+
|
|
270
|
+
The provider reads configuration from environment variables with the `TURSO_` prefix:
|
|
271
|
+
|
|
272
|
+
| Variable | Required | Description |
|
|
273
|
+
|----------|----------|-------------|
|
|
274
|
+
| `TURSO_DB_URL` | Yes | Turso/libSQL database URL (e.g., `libsql://your-db.turso.io` or `file:local.db`) |
|
|
275
|
+
| `TURSO_DB_AUTH_TOKEN` | No | Turso auth token (required for cloud databases, optional for local) |
|
|
276
|
+
|
|
277
|
+
### Example `.env`
|
|
278
|
+
|
|
279
|
+
```env
|
|
280
|
+
# For Turso cloud
|
|
281
|
+
TURSO_DB_URL=libsql://my-app-db.turso.io
|
|
282
|
+
TURSO_DB_AUTH_TOKEN=eyJhbGciOiJFZERTQSIsInR5cCI...
|
|
283
|
+
|
|
284
|
+
# For local development
|
|
285
|
+
TURSO_DB_URL=file:local.db
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Advanced usage
|
|
289
|
+
|
|
290
|
+
### Multiple databases
|
|
291
|
+
|
|
292
|
+
You can create multiple database providers with different port names:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import * as mainSchema from "@/infra/db/schema";
|
|
296
|
+
import * as analyticsSchema from "@/infra/db/analytics-schema";
|
|
297
|
+
import { createDrizzleTursoProvider } from "@beignet/provider-drizzle-turso";
|
|
298
|
+
|
|
299
|
+
export const mainDbProvider = createDrizzleTursoProvider({
|
|
300
|
+
schema: mainSchema,
|
|
301
|
+
portName: "db", // default
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
export const analyticsDbProvider = createDrizzleTursoProvider({
|
|
305
|
+
schema: analyticsSchema,
|
|
306
|
+
portName: "analyticsDb",
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Then in your ports type:
|
|
310
|
+
export type AppPorts = {
|
|
311
|
+
db: DbPort<typeof mainSchema>;
|
|
312
|
+
analyticsDb: DbPort<typeof analyticsSchema>;
|
|
313
|
+
};
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Accessing the Drizzle instance
|
|
317
|
+
|
|
318
|
+
The `DbPort` exposes the typed Drizzle database instance:
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
import { eq } from "drizzle-orm";
|
|
322
|
+
|
|
323
|
+
const db = ctx.ports.db.db; // LibSQLDatabase<TSchema>
|
|
324
|
+
|
|
325
|
+
// All Drizzle operations are available:
|
|
326
|
+
await db.select().from(schema.todos);
|
|
327
|
+
await db.insert(schema.todos).values({ id: "1", title: "Hello" });
|
|
328
|
+
await db.update(schema.todos).set({ title: "Updated" }).where(eq(schema.todos.id, "1"));
|
|
329
|
+
await db.delete(schema.todos).where(eq(schema.todos.id, "1"));
|
|
330
|
+
|
|
331
|
+
// Prefer repository ports and createDrizzleTursoUnitOfWork(...) for application
|
|
332
|
+
// workflows. Use raw db access in infra, scripts, and vendor-specific escape
|
|
333
|
+
// hatches.
|
|
334
|
+
|
|
335
|
+
// Access the underlying libSQL client for advanced operations:
|
|
336
|
+
const client = ctx.ports.db.client;
|
|
337
|
+
const result = await client.execute("SELECT * FROM todos WHERE id = ?", ["1"]);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Key design principles
|
|
341
|
+
|
|
342
|
+
### Runtime vs. build-time separation
|
|
343
|
+
|
|
344
|
+
This provider follows a clean separation of concerns:
|
|
345
|
+
|
|
346
|
+
- **Build-time (Drizzle CLI)**: Configured via `drizzle.config.ts`
|
|
347
|
+
- Used for generating migrations
|
|
348
|
+
- Used for introspecting the database
|
|
349
|
+
- Lives in your app repository
|
|
350
|
+
|
|
351
|
+
- **Runtime (Provider)**: Configured via factory function
|
|
352
|
+
- Used for connecting to the database at runtime
|
|
353
|
+
- Used for executing queries in your use cases
|
|
354
|
+
- Gets the schema from your imports
|
|
355
|
+
|
|
356
|
+
### Schema location independence
|
|
357
|
+
|
|
358
|
+
The provider **does not care** where your schema file lives. You:
|
|
359
|
+
|
|
360
|
+
1. Define your schema files wherever makes sense (`infra/db/schema/`, `db/schema.ts`, etc.)
|
|
361
|
+
2. Import them in your app: `import * as schema from "@/infra/db/schema"`
|
|
362
|
+
3. Pass it to the factory: `createDrizzleTursoProvider({ schema })`
|
|
363
|
+
|
|
364
|
+
This keeps the provider flexible and your app in control of its structure.
|
|
365
|
+
|
|
366
|
+
## API reference
|
|
367
|
+
|
|
368
|
+
### `DbPort<TSchema>`
|
|
369
|
+
|
|
370
|
+
The port interface exposed on `ctx.ports.db`:
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
interface DbPort<TSchema extends Record<string, any> = any> {
|
|
374
|
+
db: LibSQLDatabase<TSchema>;
|
|
375
|
+
client: Client;
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
- `db`: The typed Drizzle database instance for ORM operations
|
|
380
|
+
- `client`: The underlying libSQL client for advanced operations not covered by Drizzle
|
|
381
|
+
|
|
382
|
+
### `createDrizzleTursoProvider<TSchema>(options)`
|
|
383
|
+
|
|
384
|
+
Factory function to create a Drizzle Turso provider.
|
|
385
|
+
|
|
386
|
+
**Parameters:**
|
|
387
|
+
- `options.schema` (required): Your Drizzle schema object
|
|
388
|
+
- `options.portName` (optional): Port name, defaults to `"db"`
|
|
389
|
+
|
|
390
|
+
**Returns:** A provider that can be registered with `createServer`, `createNextServer`, or another Beignet server adapter.
|
|
391
|
+
|
|
392
|
+
**Example:**
|
|
393
|
+
```ts
|
|
394
|
+
const provider = createDrizzleTursoProvider({
|
|
395
|
+
schema: mySchema,
|
|
396
|
+
portName: "db", // optional
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### `createDrizzleTursoUnitOfWork<TSchema, TxPorts>(options)`
|
|
401
|
+
|
|
402
|
+
Factory function to create a transaction-backed `UnitOfWorkPort`.
|
|
403
|
+
|
|
404
|
+
**Parameters:**
|
|
405
|
+
- `options.db` (required): The root `LibSQLDatabase<TSchema>` instance
|
|
406
|
+
- `options.createTransactionPorts` (required): Factory that receives the Drizzle transaction client and event recorder
|
|
407
|
+
- `options.eventBus` (optional): Event bus used to flush recorded events after commit
|
|
408
|
+
- `options.transactionConfig` (optional): Drizzle transaction configuration
|
|
409
|
+
|
|
410
|
+
**Returns:** A `UnitOfWorkPort<TxPorts>` that runs work inside
|
|
411
|
+
`db.transaction(...)`.
|
|
412
|
+
|
|
413
|
+
## License
|
|
414
|
+
|
|
415
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beignet/provider-drizzle-turso
|
|
3
|
+
*
|
|
4
|
+
* Drizzle ORM + Turso/libSQL provider that creates a typed database port.
|
|
5
|
+
*
|
|
6
|
+
* Configuration:
|
|
7
|
+
* - TURSO_DB_URL: Turso/libSQL database URL (required)
|
|
8
|
+
* - TURSO_DB_AUTH_TOKEN: Turso auth token (optional for local dev)
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import * as schema from "@/db/schema";
|
|
13
|
+
* import { createDrizzleTursoProvider } from "@beignet/provider-drizzle-turso";
|
|
14
|
+
*
|
|
15
|
+
* export const drizzleTursoProvider = createDrizzleTursoProvider({ schema });
|
|
16
|
+
*
|
|
17
|
+
* // In createNextServer:
|
|
18
|
+
* const server = await createNextServer({
|
|
19
|
+
* ports: basePorts,
|
|
20
|
+
* providers: [drizzleTursoProvider],
|
|
21
|
+
* // ...
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // In infra:
|
|
25
|
+
* const todos = createDrizzleTodoRepository(ctx.ports.db.db);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
import { type BufferedDomainEventRecorder, type EventBusPort, type UnitOfWorkPort } from "@beignet/core/ports";
|
|
29
|
+
import { type Client } from "@libsql/client";
|
|
30
|
+
import type { ExtractTablesWithRelations } from "drizzle-orm";
|
|
31
|
+
import { type LibSQLDatabase } from "drizzle-orm/libsql";
|
|
32
|
+
import type { LibSQLTransaction } from "drizzle-orm/libsql/session";
|
|
33
|
+
import type { SQLiteTransactionConfig } from "drizzle-orm/sqlite-core";
|
|
34
|
+
import { z } from "zod";
|
|
35
|
+
/**
|
|
36
|
+
* Typed database port interface.
|
|
37
|
+
* Exposes a typed Drizzle database instance for your schema.
|
|
38
|
+
*
|
|
39
|
+
* @template TSchema - The Drizzle schema type (e.g., `typeof schema`)
|
|
40
|
+
*/
|
|
41
|
+
export interface DbPort<TSchema extends Record<string, unknown> = Record<string, unknown>> {
|
|
42
|
+
/**
|
|
43
|
+
* The typed Drizzle database instance.
|
|
44
|
+
* Use this to query your database with full type safety.
|
|
45
|
+
*/
|
|
46
|
+
db: LibSQLDatabase<TSchema>;
|
|
47
|
+
/**
|
|
48
|
+
* The underlying libSQL client instance.
|
|
49
|
+
* Use this for advanced operations not covered by Drizzle ORM.
|
|
50
|
+
*/
|
|
51
|
+
client: Client;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Drizzle database or transaction client accepted by repository factories.
|
|
55
|
+
*/
|
|
56
|
+
export type DrizzleTursoDatabase<TSchema extends Record<string, unknown> = Record<string, unknown>> = LibSQLDatabase<TSchema> | LibSQLTransaction<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
57
|
+
/**
|
|
58
|
+
* Drizzle transaction client passed to Unit of Work repository factories.
|
|
59
|
+
*/
|
|
60
|
+
export type DrizzleTursoTransaction<TSchema extends Record<string, unknown> = Record<string, unknown>> = LibSQLTransaction<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
61
|
+
export interface DrizzleTursoUnitOfWorkOptions<TSchema extends Record<string, unknown>, TxPorts> {
|
|
62
|
+
/**
|
|
63
|
+
* The root Drizzle database instance from `ctx.ports.db.db`.
|
|
64
|
+
*/
|
|
65
|
+
db: LibSQLDatabase<TSchema>;
|
|
66
|
+
/**
|
|
67
|
+
* Create transaction-scoped ports from the Drizzle transaction client.
|
|
68
|
+
*
|
|
69
|
+
* The event recorder is transaction-local. Record domain events inside the
|
|
70
|
+
* callback and pass `eventBus` to publish them after the database commit.
|
|
71
|
+
*/
|
|
72
|
+
createTransactionPorts: (db: DrizzleTursoTransaction<TSchema>, events: BufferedDomainEventRecorder) => TxPorts;
|
|
73
|
+
/**
|
|
74
|
+
* Optional event bus used to flush recorded domain events after commit.
|
|
75
|
+
*/
|
|
76
|
+
eventBus?: EventBusPort;
|
|
77
|
+
/**
|
|
78
|
+
* Optional Drizzle transaction configuration.
|
|
79
|
+
*/
|
|
80
|
+
transactionConfig?: SQLiteTransactionConfig;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create a Unit of Work port backed by Drizzle's libSQL transaction API.
|
|
84
|
+
*
|
|
85
|
+
* Beignet does not own your repository interfaces. This helper only
|
|
86
|
+
* provides the transaction boundary and post-commit event flushing; your app
|
|
87
|
+
* decides which transaction-scoped ports to create.
|
|
88
|
+
*/
|
|
89
|
+
export declare function createDrizzleTursoUnitOfWork<TSchema extends Record<string, unknown>, TxPorts>(options: DrizzleTursoUnitOfWorkOptions<TSchema, TxPorts>): UnitOfWorkPort<TxPorts>;
|
|
90
|
+
/**
|
|
91
|
+
* Configuration schema for the Drizzle Turso provider.
|
|
92
|
+
* Validates environment variables with TURSO_ prefix.
|
|
93
|
+
*/
|
|
94
|
+
declare const TursoConfigSchema: z.ZodObject<{
|
|
95
|
+
DB_URL: z.ZodString;
|
|
96
|
+
DB_AUTH_TOKEN: z.ZodOptional<z.ZodString>;
|
|
97
|
+
}, z.core.$strip>;
|
|
98
|
+
/**
|
|
99
|
+
* Inferred configuration type for Turso provider.
|
|
100
|
+
*/
|
|
101
|
+
export type TursoConfig = z.infer<typeof TursoConfigSchema>;
|
|
102
|
+
/**
|
|
103
|
+
* Options for creating a Drizzle Turso provider.
|
|
104
|
+
*
|
|
105
|
+
* @template TSchema - The Drizzle schema type
|
|
106
|
+
*/
|
|
107
|
+
export interface DrizzleTursoProviderOptions<TSchema extends Record<string, unknown>, PortName extends string = "db"> {
|
|
108
|
+
/**
|
|
109
|
+
* The Drizzle schema object (e.g. `import * as schema from '@/db/schema'`).
|
|
110
|
+
* This schema is used to create a typed Drizzle database instance.
|
|
111
|
+
*/
|
|
112
|
+
schema: TSchema;
|
|
113
|
+
/**
|
|
114
|
+
* Optional port name. Defaults to "db".
|
|
115
|
+
* If you want multiple databases, you can override this.
|
|
116
|
+
*/
|
|
117
|
+
portName?: PortName;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create a Drizzle Turso provider factory.
|
|
121
|
+
*
|
|
122
|
+
* This factory creates a service provider that:
|
|
123
|
+
* - Uses Turso / libSQL via @libsql/client
|
|
124
|
+
* - Uses Drizzle ORM via drizzle-orm/libsql
|
|
125
|
+
* - Exposes a typed DbPort<TSchema> on ports
|
|
126
|
+
* - Uses TURSO_-prefixed env vars for connection config
|
|
127
|
+
*
|
|
128
|
+
* @template TSchema - The Drizzle schema type (inferred from the schema parameter)
|
|
129
|
+
* @param options - Configuration options including the schema
|
|
130
|
+
* @returns A service provider that can be used with createNextServer
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* import * as schema from "@/db/schema";
|
|
135
|
+
* import { createDrizzleTursoProvider } from "@beignet/provider-drizzle-turso";
|
|
136
|
+
*
|
|
137
|
+
* export const drizzleTursoProvider = createDrizzleTursoProvider({ schema });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare function createDrizzleTursoProvider<TSchema extends Record<string, unknown>, PortName extends string = "db">(options: DrizzleTursoProviderOptions<TSchema, PortName>): import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
|
|
141
|
+
DB_URL: z.ZodString;
|
|
142
|
+
DB_AUTH_TOKEN: z.ZodOptional<z.ZodString>;
|
|
143
|
+
}, z.core.$strip>, Record<PortName, DbPort<TSchema>>>;
|
|
144
|
+
export {};
|
|
145
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beignet/provider-drizzle-turso
|
|
3
|
+
*
|
|
4
|
+
* Drizzle ORM + Turso/libSQL provider that creates a typed database port.
|
|
5
|
+
*
|
|
6
|
+
* Configuration:
|
|
7
|
+
* - TURSO_DB_URL: Turso/libSQL database URL (required)
|
|
8
|
+
* - TURSO_DB_AUTH_TOKEN: Turso auth token (optional for local dev)
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import * as schema from "@/db/schema";
|
|
13
|
+
* import { createDrizzleTursoProvider } from "@beignet/provider-drizzle-turso";
|
|
14
|
+
*
|
|
15
|
+
* export const drizzleTursoProvider = createDrizzleTursoProvider({ schema });
|
|
16
|
+
*
|
|
17
|
+
* // In createNextServer:
|
|
18
|
+
* const server = await createNextServer({
|
|
19
|
+
* ports: basePorts,
|
|
20
|
+
* providers: [drizzleTursoProvider],
|
|
21
|
+
* // ...
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // In infra:
|
|
25
|
+
* const todos = createDrizzleTodoRepository(ctx.ports.db.db);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
import { createDomainEventRecorder, } from "@beignet/core/ports";
|
|
29
|
+
import { createProvider, createProviderInstrumentation, } from "@beignet/core/providers";
|
|
30
|
+
import { createClient } from "@libsql/client";
|
|
31
|
+
import { drizzle } from "drizzle-orm/libsql";
|
|
32
|
+
import { z } from "zod";
|
|
33
|
+
/**
|
|
34
|
+
* Create a Unit of Work port backed by Drizzle's libSQL transaction API.
|
|
35
|
+
*
|
|
36
|
+
* Beignet does not own your repository interfaces. This helper only
|
|
37
|
+
* provides the transaction boundary and post-commit event flushing; your app
|
|
38
|
+
* decides which transaction-scoped ports to create.
|
|
39
|
+
*/
|
|
40
|
+
export function createDrizzleTursoUnitOfWork(options) {
|
|
41
|
+
return {
|
|
42
|
+
async transaction(work) {
|
|
43
|
+
const events = createDomainEventRecorder();
|
|
44
|
+
const result = await options.db.transaction(async (tx) => {
|
|
45
|
+
const txPorts = options.createTransactionPorts(tx, events);
|
|
46
|
+
return work(txPorts);
|
|
47
|
+
}, options.transactionConfig);
|
|
48
|
+
if (options.eventBus) {
|
|
49
|
+
await events.flush(options.eventBus);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Configuration schema for the Drizzle Turso provider.
|
|
57
|
+
* Validates environment variables with TURSO_ prefix.
|
|
58
|
+
*/
|
|
59
|
+
const TursoConfigSchema = z.object({
|
|
60
|
+
/**
|
|
61
|
+
* Turso / libSQL database URL.
|
|
62
|
+
* Example: "libsql://<org>-<db>.turso.io" or "file:local.db"
|
|
63
|
+
*/
|
|
64
|
+
DB_URL: z
|
|
65
|
+
.string()
|
|
66
|
+
.min(1)
|
|
67
|
+
.refine((val) => val.startsWith("libsql://") || val.startsWith("file:"), {
|
|
68
|
+
message: 'DB_URL must be a valid "libsql://" or "file:" URL (e.g., "libsql://<org>-<db>.turso.io" or "file:local.db")',
|
|
69
|
+
}),
|
|
70
|
+
/**
|
|
71
|
+
* Turso auth token (optional for local dev).
|
|
72
|
+
* Required when connecting to Turso cloud databases.
|
|
73
|
+
*/
|
|
74
|
+
DB_AUTH_TOKEN: z.string().optional(),
|
|
75
|
+
});
|
|
76
|
+
function summarizeQuery(query) {
|
|
77
|
+
const normalized = query.replace(/\s+/g, " ").trim();
|
|
78
|
+
return normalized.length > 120
|
|
79
|
+
? `${normalized.slice(0, 117)}...`
|
|
80
|
+
: normalized;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create a Drizzle Turso provider factory.
|
|
84
|
+
*
|
|
85
|
+
* This factory creates a service provider that:
|
|
86
|
+
* - Uses Turso / libSQL via @libsql/client
|
|
87
|
+
* - Uses Drizzle ORM via drizzle-orm/libsql
|
|
88
|
+
* - Exposes a typed DbPort<TSchema> on ports
|
|
89
|
+
* - Uses TURSO_-prefixed env vars for connection config
|
|
90
|
+
*
|
|
91
|
+
* @template TSchema - The Drizzle schema type (inferred from the schema parameter)
|
|
92
|
+
* @param options - Configuration options including the schema
|
|
93
|
+
* @returns A service provider that can be used with createNextServer
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* import * as schema from "@/db/schema";
|
|
98
|
+
* import { createDrizzleTursoProvider } from "@beignet/provider-drizzle-turso";
|
|
99
|
+
*
|
|
100
|
+
* export const drizzleTursoProvider = createDrizzleTursoProvider({ schema });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export function createDrizzleTursoProvider(options) {
|
|
104
|
+
const { schema, portName = "db" } = options;
|
|
105
|
+
return createProvider({
|
|
106
|
+
name: "drizzle-turso",
|
|
107
|
+
config: {
|
|
108
|
+
schema: TursoConfigSchema,
|
|
109
|
+
envPrefix: "TURSO_",
|
|
110
|
+
},
|
|
111
|
+
async setup({ ports, config }) {
|
|
112
|
+
if (!config) {
|
|
113
|
+
throw new Error("[drizzleTursoProvider] Missing config. Set TURSO_DB_URL (and optional TURSO_DB_AUTH_TOKEN).");
|
|
114
|
+
}
|
|
115
|
+
// 1. Create Turso client
|
|
116
|
+
const client = createClient({
|
|
117
|
+
url: config.DB_URL,
|
|
118
|
+
authToken: config.DB_AUTH_TOKEN,
|
|
119
|
+
});
|
|
120
|
+
const instrumentation = createProviderInstrumentation(ports, {
|
|
121
|
+
providerName: "drizzle-turso",
|
|
122
|
+
watcher: "db",
|
|
123
|
+
});
|
|
124
|
+
const logger = instrumentation.isEnabled()
|
|
125
|
+
? {
|
|
126
|
+
logQuery(query, params) {
|
|
127
|
+
instrumentation.custom({
|
|
128
|
+
name: "db.query",
|
|
129
|
+
label: "Database query",
|
|
130
|
+
summary: summarizeQuery(query),
|
|
131
|
+
details: {
|
|
132
|
+
query,
|
|
133
|
+
params: params.length > 0 ? "[redacted]" : [],
|
|
134
|
+
paramsCount: params.length,
|
|
135
|
+
portName,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
: undefined;
|
|
141
|
+
// 2. Create typed Drizzle instance
|
|
142
|
+
const db = logger
|
|
143
|
+
? drizzle(client, { schema, logger })
|
|
144
|
+
: drizzle(client, { schema });
|
|
145
|
+
// 3. Build a typed DbPort
|
|
146
|
+
const dbPort = { db, client };
|
|
147
|
+
return {
|
|
148
|
+
ports: { [portName]: dbPort },
|
|
149
|
+
async stop() {
|
|
150
|
+
// Gracefully close the database connection
|
|
151
|
+
try {
|
|
152
|
+
await dbPort.client.close();
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
// Log error but don't throw - we're shutting down anyway
|
|
156
|
+
console.error("[drizzleTursoProvider] Error closing database connection:", error);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@beignet/provider-drizzle-turso",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Drizzle ORM + Turso/libSQL provider for Beignet - adds typed DbPort using drizzle-orm",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src",
|
|
17
|
+
"!src/**/*.test.ts",
|
|
18
|
+
"!src/**/*.test.tsx",
|
|
19
|
+
"!src/**/*.test-d.ts",
|
|
20
|
+
"README.md",
|
|
21
|
+
"CHANGELOG.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"dev": "tsc --watch",
|
|
26
|
+
"clean": "rm -rf dist coverage .turbo",
|
|
27
|
+
"test": "bun test",
|
|
28
|
+
"test:coverage": "bun test --coverage",
|
|
29
|
+
"lint": "biome check ."
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"beignet",
|
|
33
|
+
"drizzle",
|
|
34
|
+
"turso",
|
|
35
|
+
"libsql",
|
|
36
|
+
"provider",
|
|
37
|
+
"database",
|
|
38
|
+
"ports",
|
|
39
|
+
"hex",
|
|
40
|
+
"hexagonal"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/taylorbryant/beignet.git",
|
|
46
|
+
"directory": "packages/provider-drizzle-turso"
|
|
47
|
+
},
|
|
48
|
+
"author": "Taylor Bryant",
|
|
49
|
+
"homepage": "https://github.com/taylorbryant/beignet#readme",
|
|
50
|
+
"bugs": "https://github.com/taylorbryant/beignet/issues",
|
|
51
|
+
"sideEffects": false,
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@libsql/client": "^0.6.0 || ^0.14.0 || ^0.15.0",
|
|
60
|
+
"drizzle-orm": "^0.36.0 || ^0.37.0 || ^0.38.0"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"zod": "^4.0.0",
|
|
64
|
+
"@beignet/core": "*"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@beignet/devtools": "*",
|
|
68
|
+
"@libsql/client": "^0.14.0",
|
|
69
|
+
"@types/bun": "^1.3.13",
|
|
70
|
+
"@types/node": "^20.10.0",
|
|
71
|
+
"drizzle-orm": "^0.38.3",
|
|
72
|
+
"typescript": "^5.3.0"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beignet/provider-drizzle-turso
|
|
3
|
+
*
|
|
4
|
+
* Drizzle ORM + Turso/libSQL provider that creates a typed database port.
|
|
5
|
+
*
|
|
6
|
+
* Configuration:
|
|
7
|
+
* - TURSO_DB_URL: Turso/libSQL database URL (required)
|
|
8
|
+
* - TURSO_DB_AUTH_TOKEN: Turso auth token (optional for local dev)
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import * as schema from "@/db/schema";
|
|
13
|
+
* import { createDrizzleTursoProvider } from "@beignet/provider-drizzle-turso";
|
|
14
|
+
*
|
|
15
|
+
* export const drizzleTursoProvider = createDrizzleTursoProvider({ schema });
|
|
16
|
+
*
|
|
17
|
+
* // In createNextServer:
|
|
18
|
+
* const server = await createNextServer({
|
|
19
|
+
* ports: basePorts,
|
|
20
|
+
* providers: [drizzleTursoProvider],
|
|
21
|
+
* // ...
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // In infra:
|
|
25
|
+
* const todos = createDrizzleTodoRepository(ctx.ports.db.db);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
type BufferedDomainEventRecorder,
|
|
31
|
+
createDomainEventRecorder,
|
|
32
|
+
type EventBusPort,
|
|
33
|
+
type UnitOfWorkCallback,
|
|
34
|
+
type UnitOfWorkPort,
|
|
35
|
+
} from "@beignet/core/ports";
|
|
36
|
+
import {
|
|
37
|
+
createProvider,
|
|
38
|
+
createProviderInstrumentation,
|
|
39
|
+
} from "@beignet/core/providers";
|
|
40
|
+
import { type Client, createClient } from "@libsql/client";
|
|
41
|
+
import type { ExtractTablesWithRelations } from "drizzle-orm";
|
|
42
|
+
import { drizzle, type LibSQLDatabase } from "drizzle-orm/libsql";
|
|
43
|
+
import type { LibSQLTransaction } from "drizzle-orm/libsql/session";
|
|
44
|
+
import type { SQLiteTransactionConfig } from "drizzle-orm/sqlite-core";
|
|
45
|
+
import { z } from "zod";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Typed database port interface.
|
|
49
|
+
* Exposes a typed Drizzle database instance for your schema.
|
|
50
|
+
*
|
|
51
|
+
* @template TSchema - The Drizzle schema type (e.g., `typeof schema`)
|
|
52
|
+
*/
|
|
53
|
+
export interface DbPort<
|
|
54
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
55
|
+
> {
|
|
56
|
+
/**
|
|
57
|
+
* The typed Drizzle database instance.
|
|
58
|
+
* Use this to query your database with full type safety.
|
|
59
|
+
*/
|
|
60
|
+
db: LibSQLDatabase<TSchema>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The underlying libSQL client instance.
|
|
64
|
+
* Use this for advanced operations not covered by Drizzle ORM.
|
|
65
|
+
*/
|
|
66
|
+
client: Client;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Drizzle database or transaction client accepted by repository factories.
|
|
71
|
+
*/
|
|
72
|
+
export type DrizzleTursoDatabase<
|
|
73
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
74
|
+
> =
|
|
75
|
+
| LibSQLDatabase<TSchema>
|
|
76
|
+
| LibSQLTransaction<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Drizzle transaction client passed to Unit of Work repository factories.
|
|
80
|
+
*/
|
|
81
|
+
export type DrizzleTursoTransaction<
|
|
82
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
83
|
+
> = LibSQLTransaction<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
84
|
+
|
|
85
|
+
export interface DrizzleTursoUnitOfWorkOptions<
|
|
86
|
+
TSchema extends Record<string, unknown>,
|
|
87
|
+
TxPorts,
|
|
88
|
+
> {
|
|
89
|
+
/**
|
|
90
|
+
* The root Drizzle database instance from `ctx.ports.db.db`.
|
|
91
|
+
*/
|
|
92
|
+
db: LibSQLDatabase<TSchema>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create transaction-scoped ports from the Drizzle transaction client.
|
|
96
|
+
*
|
|
97
|
+
* The event recorder is transaction-local. Record domain events inside the
|
|
98
|
+
* callback and pass `eventBus` to publish them after the database commit.
|
|
99
|
+
*/
|
|
100
|
+
createTransactionPorts: (
|
|
101
|
+
db: DrizzleTursoTransaction<TSchema>,
|
|
102
|
+
events: BufferedDomainEventRecorder,
|
|
103
|
+
) => TxPorts;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Optional event bus used to flush recorded domain events after commit.
|
|
107
|
+
*/
|
|
108
|
+
eventBus?: EventBusPort;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Optional Drizzle transaction configuration.
|
|
112
|
+
*/
|
|
113
|
+
transactionConfig?: SQLiteTransactionConfig;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create a Unit of Work port backed by Drizzle's libSQL transaction API.
|
|
118
|
+
*
|
|
119
|
+
* Beignet does not own your repository interfaces. This helper only
|
|
120
|
+
* provides the transaction boundary and post-commit event flushing; your app
|
|
121
|
+
* decides which transaction-scoped ports to create.
|
|
122
|
+
*/
|
|
123
|
+
export function createDrizzleTursoUnitOfWork<
|
|
124
|
+
TSchema extends Record<string, unknown>,
|
|
125
|
+
TxPorts,
|
|
126
|
+
>(
|
|
127
|
+
options: DrizzleTursoUnitOfWorkOptions<TSchema, TxPorts>,
|
|
128
|
+
): UnitOfWorkPort<TxPorts> {
|
|
129
|
+
return {
|
|
130
|
+
async transaction<Result>(
|
|
131
|
+
work: UnitOfWorkCallback<TxPorts, Result>,
|
|
132
|
+
): Promise<Result> {
|
|
133
|
+
const events = createDomainEventRecorder();
|
|
134
|
+
|
|
135
|
+
const result = await options.db.transaction(async (tx) => {
|
|
136
|
+
const txPorts = options.createTransactionPorts(
|
|
137
|
+
tx as DrizzleTursoTransaction<TSchema>,
|
|
138
|
+
events,
|
|
139
|
+
);
|
|
140
|
+
return work(txPorts);
|
|
141
|
+
}, options.transactionConfig);
|
|
142
|
+
|
|
143
|
+
if (options.eventBus) {
|
|
144
|
+
await events.flush(options.eventBus);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Configuration schema for the Drizzle Turso provider.
|
|
154
|
+
* Validates environment variables with TURSO_ prefix.
|
|
155
|
+
*/
|
|
156
|
+
const TursoConfigSchema = z.object({
|
|
157
|
+
/**
|
|
158
|
+
* Turso / libSQL database URL.
|
|
159
|
+
* Example: "libsql://<org>-<db>.turso.io" or "file:local.db"
|
|
160
|
+
*/
|
|
161
|
+
DB_URL: z
|
|
162
|
+
.string()
|
|
163
|
+
.min(1)
|
|
164
|
+
.refine((val) => val.startsWith("libsql://") || val.startsWith("file:"), {
|
|
165
|
+
message:
|
|
166
|
+
'DB_URL must be a valid "libsql://" or "file:" URL (e.g., "libsql://<org>-<db>.turso.io" or "file:local.db")',
|
|
167
|
+
}),
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Turso auth token (optional for local dev).
|
|
171
|
+
* Required when connecting to Turso cloud databases.
|
|
172
|
+
*/
|
|
173
|
+
DB_AUTH_TOKEN: z.string().optional(),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Inferred configuration type for Turso provider.
|
|
178
|
+
*/
|
|
179
|
+
export type TursoConfig = z.infer<typeof TursoConfigSchema>;
|
|
180
|
+
|
|
181
|
+
function summarizeQuery(query: string): string {
|
|
182
|
+
const normalized = query.replace(/\s+/g, " ").trim();
|
|
183
|
+
return normalized.length > 120
|
|
184
|
+
? `${normalized.slice(0, 117)}...`
|
|
185
|
+
: normalized;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Options for creating a Drizzle Turso provider.
|
|
190
|
+
*
|
|
191
|
+
* @template TSchema - The Drizzle schema type
|
|
192
|
+
*/
|
|
193
|
+
export interface DrizzleTursoProviderOptions<
|
|
194
|
+
TSchema extends Record<string, unknown>,
|
|
195
|
+
PortName extends string = "db",
|
|
196
|
+
> {
|
|
197
|
+
/**
|
|
198
|
+
* The Drizzle schema object (e.g. `import * as schema from '@/db/schema'`).
|
|
199
|
+
* This schema is used to create a typed Drizzle database instance.
|
|
200
|
+
*/
|
|
201
|
+
schema: TSchema;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Optional port name. Defaults to "db".
|
|
205
|
+
* If you want multiple databases, you can override this.
|
|
206
|
+
*/
|
|
207
|
+
portName?: PortName;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create a Drizzle Turso provider factory.
|
|
212
|
+
*
|
|
213
|
+
* This factory creates a service provider that:
|
|
214
|
+
* - Uses Turso / libSQL via @libsql/client
|
|
215
|
+
* - Uses Drizzle ORM via drizzle-orm/libsql
|
|
216
|
+
* - Exposes a typed DbPort<TSchema> on ports
|
|
217
|
+
* - Uses TURSO_-prefixed env vars for connection config
|
|
218
|
+
*
|
|
219
|
+
* @template TSchema - The Drizzle schema type (inferred from the schema parameter)
|
|
220
|
+
* @param options - Configuration options including the schema
|
|
221
|
+
* @returns A service provider that can be used with createNextServer
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```ts
|
|
225
|
+
* import * as schema from "@/db/schema";
|
|
226
|
+
* import { createDrizzleTursoProvider } from "@beignet/provider-drizzle-turso";
|
|
227
|
+
*
|
|
228
|
+
* export const drizzleTursoProvider = createDrizzleTursoProvider({ schema });
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export function createDrizzleTursoProvider<
|
|
232
|
+
TSchema extends Record<string, unknown>,
|
|
233
|
+
PortName extends string = "db",
|
|
234
|
+
>(options: DrizzleTursoProviderOptions<TSchema, PortName>) {
|
|
235
|
+
const { schema, portName = "db" } = options;
|
|
236
|
+
|
|
237
|
+
return createProvider({
|
|
238
|
+
name: "drizzle-turso",
|
|
239
|
+
|
|
240
|
+
config: {
|
|
241
|
+
schema: TursoConfigSchema,
|
|
242
|
+
envPrefix: "TURSO_",
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
async setup({ ports, config }) {
|
|
246
|
+
if (!config) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
"[drizzleTursoProvider] Missing config. Set TURSO_DB_URL (and optional TURSO_DB_AUTH_TOKEN).",
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 1. Create Turso client
|
|
253
|
+
const client: Client = createClient({
|
|
254
|
+
url: config.DB_URL,
|
|
255
|
+
authToken: config.DB_AUTH_TOKEN,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const instrumentation = createProviderInstrumentation(ports, {
|
|
259
|
+
providerName: "drizzle-turso",
|
|
260
|
+
watcher: "db",
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const logger = instrumentation.isEnabled()
|
|
264
|
+
? {
|
|
265
|
+
logQuery(query: string, params: unknown[]) {
|
|
266
|
+
instrumentation.custom({
|
|
267
|
+
name: "db.query",
|
|
268
|
+
label: "Database query",
|
|
269
|
+
summary: summarizeQuery(query),
|
|
270
|
+
details: {
|
|
271
|
+
query,
|
|
272
|
+
params: params.length > 0 ? "[redacted]" : [],
|
|
273
|
+
paramsCount: params.length,
|
|
274
|
+
portName,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
}
|
|
279
|
+
: undefined;
|
|
280
|
+
|
|
281
|
+
// 2. Create typed Drizzle instance
|
|
282
|
+
const db: LibSQLDatabase<TSchema> = logger
|
|
283
|
+
? drizzle(client, { schema, logger })
|
|
284
|
+
: drizzle(client, { schema });
|
|
285
|
+
|
|
286
|
+
// 3. Build a typed DbPort
|
|
287
|
+
const dbPort: DbPort<TSchema> = { db, client };
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
ports: { [portName]: dbPort } as Record<PortName, DbPort<TSchema>>,
|
|
291
|
+
async stop() {
|
|
292
|
+
// Gracefully close the database connection
|
|
293
|
+
try {
|
|
294
|
+
await dbPort.client.close();
|
|
295
|
+
} catch (error) {
|
|
296
|
+
// Log error but don't throw - we're shutting down anyway
|
|
297
|
+
console.error(
|
|
298
|
+
"[drizzleTursoProvider] Error closing database connection:",
|
|
299
|
+
error,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
}
|