agentleFS
JoviDeCroockJoviDeCroock/pracht/skills/add-db/SKILL.md

add-db

Skill106 starsChanged 7 months agoMITView on GitHub

Wire Drizzle ORM into a pracht app for D1, PlanetScale, Neon, Supabase, Turso, Postgres, MySQL, or SQLite: driver, schema, migrations, and a typed client. Use for "add a database", "set up Drizzle", "wire D1", "add Postgres", "set up an ORM".

  • Reads credentials
  • Installs packages

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Ask with AskUserQuestion, then cross-check against the project's adapter (pracht inspect build --json) and flag mismatches.

    line 32
  2. Very specific:

    Read connection strings via serverEnv from @pracht/core/env/server, never process.env — it keeps the secret out of the client bundle and …

    line 73

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Pracht Add Database (Drizzle)

Drizzle suits pracht because it is small, type-safe, and runs in both Node and edge runtimes. This skill sets up the driver, schema, migration tooling, and a client factory wired to the project's adapter. Never overwrite an existing drizzle.config.ts, wrangler.toml, or package.json script — diff, merge, and ask about collisions.

MCP: if pracht dev-mcp is registered, prefer its inspect_*, generate_*, doctor, and verify tools to the CLI. pracht inspect build needs a prior pracht build.

Step 1: Pick the target

Ask with AskUserQuestion, then cross-check against the project's adapter (pracht inspect build --json) and flag mismatches. On Cloudflare Workers, TCP drivers (pg, postgres) need a Hyperdrive binding plus compatibility_flags = ["nodejs_compat"] in the wrangler config; without Hyperdrive, use an HTTP/WebSocket driver (Neon, PlanetScale, Turso) or D1.

ProviderDriver importExtra packageRuntimes
Cloudflare D1drizzle-orm/d1— (Workers binding)Edge
Cloudflare Hyperdrive (Postgres)drizzle-orm/postgres-js or node-postgrespostgres / pgWorkers (Hyperdrive + nodejs_compat)
PlanetScaledrizzle-orm/planetscale-serverless@planetscale/databaseNode + edge
Neondrizzle-orm/neon-serverless or neon-http@neondatabase/serverlessNode + edge
Supabase Postgresdrizzle-orm/postgres-jspostgresNode; Workers via Hyperdrive
Turso (libSQL)drizzle-orm/libsql@libsql/clientNode + edge
Vanilla Postgresdrizzle-orm/node-postgrespg + -D @types/pgNode only
Vanilla MySQLdrizzle-orm/mysql2mysql2Node only
SQLitedrizzle-orm/better-sqlite3better-sqlite3 + -D @types/better-sqlite3Node only
pnpm add drizzle-orm <driver-package>
pnpm add -D drizzle-kit

Step 2: Schema

src/db/schema.ts — pgTable from drizzle-orm/pg-core, sqliteTable from drizzle-orm/sqlite-core (D1 and SQLite), or mysqlTable from drizzle-orm/mysql-core:

import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  email: text("email").notNull().unique(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

Step 3: Client factory

Read connection strings via serverEnv from @pracht/core/env/server, never process.env — it keeps the secret out of the client bundle and resolves per adapter (https://pracht.resynapse.dev/docs/env). The shape depends on the runtime:

  • Node, persistent process — a module-level singleton is fine, because serverEnv works at module top level there.
  • Edge with per-request context (Cloudflare, Vercel Edge) — read serverEnv or the binding inside a factory. Workers env bindings only exist per request, so a module-level read bricks the worker at import time.
// src/db/client.ts — Postgres on Node
import { serverEnv } from "@pracht/core/env/server";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";

const pool = new Pool({ connectionString: serverEnv.DATABASE_URL });
export const db = drizzle(pool, { schema });

For Cloudflare D1, register the Cloudflare context type once via the Register augmentation. Keep the import: without it the file is a global script and the declaration replaces @pracht/core's types instead of extending them.

// src/env.d.ts
import "@pracht/core";

declare module "@pracht/core" {
  interface Register {
    context: {
      env: Env; // wrangler-generated bindings type, includes DB: D1Database
      executionContext: ExecutionContext;
    };
  }
}

The factory then needs no per-file generics:

import type { LoaderArgs } from "@pracht/core";
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./schema";

export function getDb({ context }: Pick<LoaderArgs, "context">) {
  return drizzle(context.env.DB, { schema });
}

Without that augmentation, the inline generic must describe the full context — LoaderArgs<{ env: { DB: D1Database }; executionContext: ExecutionContext }> — because the context is { env, executionContext }, not the bindings object.

Step 4: drizzle.config.ts and scripts

process.env is fine in drizzle.config.ts: it runs under the drizzle-kit CLI on Node, never inside the worker.

import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/db/schema.ts",
  out: "./drizzle/migrations",
  dialect: "postgresql", // or "sqlite" / "mysql"
  dbCredentials: { url: process.env.DATABASE_URL! },
});

D1 is the exception. It has no TCP endpoint, so drizzle-kit can only generate migrations — applying goes through wrangler. Omit dbCredentials entirely (add a driver: "d1-http" block with Cloudflare account/database/API token only if you want drizzle-kit studio), and omit db:push: the migrations-apply flow is the only supported path. Split local from remote so the miniflare D1 can be iterated without touching production.

ScriptNon-D1Cloudflare D1
db:generatedrizzle-kit generatedrizzle-kit generate
db:migratedrizzle-kit migratewrangler d1 migrations apply <db-name> --local / --remote as db:migrate:local / db:migrate:remote
db:pushdrizzle-kit push (local dev only — prefer migrations beyond that)omit
db:studiodrizzle-kit studiodrizzle-kit studio

<db-name> is the database_name from wrangler.toml/.jsonc.

Step 5: Bindings and env

  • Cloudflare D1 — merge the binding into wrangler.toml/.jsonc. migrations_dir must match out in drizzle.config.ts or wrangler will not find the SQL drizzle-kit emits:

    [[d1_databases]]
    binding = "DB"
    database_name = "my-app"
    database_id = "<id>"
    migrations_dir = "drizzle/migrations"
  • Node / Vercel — document DATABASE_URL in .env.example, and add .env* to .gitignore if it is missing.

Step 6: Use it in a loader

import type { LoaderArgs } from "@pracht/core";
import { db } from "../db/client"; // or getDb(args) on edge runtimes
import { users } from "../db/schema";

export async function loader(_args: LoaderArgs) {
  const rows = await db.select().from(users).limit(20);
  return { users: rows.map((u) => ({ id: u.id, email: u.email })) };
}

Project explicitly — never spread DB rows into loader return values, since everything returned crosses the wire (see /audit-secrets).

Step 7: Verify

pnpm db:generate
pnpm db:push            # non-D1; or db:migrate once a migration exists
pnpm db:migrate:local   # D1 — then db:migrate:remote when happy
pracht verify --json
pnpm test

On a fresh project pnpm test is a no-op and proves nothing about the DB wiring. Suggest a loader smoke test that calls the Step 6 loader against a real local DB and asserts the returned shape, or run /scaffold-tests to set that up.

$ARGUMENTS

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.