@contract-kit/provider-drizzle-turso
v0.1.4
Published
Drizzle ORM + Turso/libSQL provider for contract-kit - adds typed DbPort using drizzle-orm
Maintainers
Readme
@contract-kit/provider-drizzle-turso
Drizzle ORM + Turso/libSQL provider for Contract Kit. Adds a typed database port to your hexagonal application using Drizzle ORM with Turso's libSQL.
Features
- 🎯 Factory-based: Create providers with your schema at the call site
- 🔒 Type-safe: Full TypeScript inference from your Drizzle schema
- 🌐 Turso-ready: Works with Turso cloud or local libSQL
- 🏗️ Schema-agnostic: You control where your schema files live
- 🔌 Clean separation: Runtime provider is separate from build-time CLI config
Installation
bun add @contract-kit/provider-drizzle-turso drizzle-orm @libsql/clientTypeScript Requirements
This package requires TypeScript 5.0 or higher for proper type inference.
Setup
1. Define your schema
Create your Drizzle schema file wherever makes sense for your app:
// src/db/schema.ts
import { sqliteTable, text } from "drizzle-orm/sqlite-core";
export const todos = sqliteTable("todos", {
id: text("id").primaryKey(),
title: text("title").notNull(),
completed: text("completed").notNull().default("false"),
});2. Configure Drizzle CLI (build-time)
Create drizzle.config.ts in your app root for the Drizzle CLI:
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./drizzle",
dialect: "sqlite",
dbCredentials: {
url: process.env.TURSO_DB_URL!,
},
});3. Create the provider (runtime)
Import your schema and create the provider:
// src/lib/providers/db.ts
import * as schema from "@/db/schema";
import { createDrizzleTursoProvider } from "@contract-kit/provider-drizzle-turso";
export const drizzleTursoProvider = createDrizzleTursoProvider({ schema });4. Type your ports
Define your app's ports type:
// src/lib/ports.ts
import type { DbPort } from "@contract-kit/provider-drizzle-turso";
import * as schema from "@/db/schema";
export type AppPorts = {
db: DbPort<typeof schema>;
// other ports...
};5. Wire it up in your server
// src/lib/app.ts
import { createServer } from "@contract-kit/server";
import { drizzleTursoProvider } from "./providers/db";
export const app = createServer({
ports: {},
providers: [drizzleTursoProvider],
// ... other config
});6. Use in your use cases
// src/application/todos/listTodos.ts
import { useCase } from "@/lib/useCase";
import * as schema from "@/db/schema";
export const listTodos = useCase
.query("todos.list")
.input(z.object({}))
.output(z.array(z.object({ id: z.string(), title: z.string() })))
.run(async ({ ctx }) => {
const db = ctx.ports.db.db; // LibSQLDatabase<typeof schema> - fully typed!
const rows = await db.select().from(schema.todos);
return rows;
});Configuration
The provider reads configuration from environment variables with the TURSO_ prefix:
| Variable | Required | Description |
|----------|----------|-------------|
| TURSO_DB_URL | Yes | Turso/libSQL database URL (e.g., libsql://your-db.turso.io or file:local.db) |
| TURSO_DB_AUTH_TOKEN | No | Turso auth token (required for cloud databases, optional for local) |
Example .env
# For Turso cloud
TURSO_DB_URL=libsql://my-app-db.turso.io
TURSO_DB_AUTH_TOKEN=eyJhbGciOiJFZERTQSIsInR5cCI...
# For local development
TURSO_DB_URL=file:local.dbAdvanced Usage
Multiple databases
You can create multiple database providers with different port names:
import * as mainSchema from "@/db/schema";
import * as analyticsSchema from "@/db/analytics-schema";
import { createDrizzleTursoProvider } from "@contract-kit/provider-drizzle-turso";
export const mainDbProvider = createDrizzleTursoProvider({
schema: mainSchema,
portName: "db", // default
});
export const analyticsDbProvider = createDrizzleTursoProvider({
schema: analyticsSchema,
portName: "analyticsDb",
});
// Then in your ports type:
export type AppPorts = {
db: DbPort<typeof mainSchema>;
analyticsDb: DbPort<typeof analyticsSchema>;
};Accessing the Drizzle instance
The DbPort exposes the typed Drizzle database instance:
import { eq } from "drizzle-orm";
const db = ctx.ports.db.db; // LibSQLDatabase<TSchema>
// All Drizzle operations are available:
await db.select().from(schema.todos);
await db.insert(schema.todos).values({ id: "1", title: "Hello" });
await db.update(schema.todos).set({ title: "Updated" }).where(eq(schema.todos.id, "1"));
await db.delete(schema.todos).where(eq(schema.todos.id, "1"));
// Transactions:
await db.transaction(async (tx) => {
await tx.insert(schema.todos).values({ id: "1", title: "Todo 1" });
await tx.insert(schema.todos).values({ id: "2", title: "Todo 2" });
});
// Access the underlying libSQL client for advanced operations:
const client = ctx.ports.db.client;
const result = await client.execute("SELECT * FROM todos WHERE id = ?", ["1"]);Key Design Principles
Runtime vs. Build-time Separation
This provider follows a clean separation of concerns:
Build-time (Drizzle CLI): Configured via
drizzle.config.ts- Used for generating migrations
- Used for introspecting the database
- Lives in your app repository
Runtime (Provider): Configured via factory function
- Used for connecting to the database at runtime
- Used for executing queries in your use cases
- Gets the schema from your imports
Schema Location Independence
The provider does not care where your schema file lives. You:
- Define your schema file wherever makes sense (
src/db/schema.ts,db/schema.ts, etc.) - Import it in your app:
import * as schema from "@/db/schema" - Pass it to the factory:
createDrizzleTursoProvider({ schema })
This keeps the provider flexible and your app in control of its structure.
API Reference
DbPort<TSchema>
The port interface exposed on ctx.ports.db:
interface DbPort<TSchema extends Record<string, any> = any> {
db: LibSQLDatabase<TSchema>;
client: Client;
}db: The typed Drizzle database instance for ORM operationsclient: The underlying libSQL client for advanced operations not covered by Drizzle
createDrizzleTursoProvider<TSchema>(options)
Factory function to create a Drizzle Turso provider.
Parameters:
options.schema(required): Your Drizzle schema objectoptions.portName(optional): Port name, defaults to"db"
Returns: A ServiceProvider that can be used with createServer
Example:
const provider = createDrizzleTursoProvider({
schema: mySchema,
portName: "db", // optional
});License
MIT
