@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 ADDED
@@ -0,0 +1,5 @@
1
+ # @beignet/provider-drizzle-turso
2
+
3
+ ## 0.0.1
4
+
5
+ - Initial Beignet release under the `@beignet` npm scope.
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
@@ -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
+ }