# D1 Setup

The application expects a Cloudflare D1 binding named `DB`, and
Drizzle writes migrations to `drizzle/d1`. The database schema lives in
`src/db/schema`, with `src/db/schema/index.ts` as the Drizzle Kit entrypoint.

D1 is currently the only supported runtime database. Runtime code should use the local
`src/db` modules rather than importing `drizzle-orm/d1` directly from routes,
actions, middleware, or UI code.

Hyperdrive support is planned for future releases. The project has proof tests for future Hyperdrive PostgreSQL and MySQL support, but these targets are not enabled at runtime. See
[Hyperdrive Proof](/docs/setup/hyperdrive-proof) for the current adapter
proof-of-concept work on the database boundary.

## Default Setup

The committed default is a single D1 binding in `wrangler.jsonc`:

```jsonc
{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "vk",
      "database_id": "00000000-0000-0000-0000-000000000000",
      "migrations_dir": "drizzle/d1",
    },
  ],
}
```

Runtime code always reads `env.DB`; keep that binding name unless you also update
`src/env.d.ts` and every `env.DB` call site.

Local development uses the Astro Cloudflare adapter and Wrangler config. The
`npm run dev` script runs `astro dev`, and the adapter reads `wrangler.jsonc` so
the `DB` binding is available through the local Workers runtime. Wrangler's local
D1 implementation is powered by Miniflare/workerd. No separate Miniflare config
is required for the default flow.

Wrangler is already a project dev dependency. Install dependencies with
`npm install`, then use the project scripts. If you want to call Wrangler
directly, prefer `npx wrangler ...` so the repository-pinned version is used.

## Local Development

Install dependencies first:

```bash
npm install
```

Generate migrations after schema changes:

```bash
npm run db:generate
```

Apply migrations to Wrangler's local D1 state:

```bash
npm run db:migrate:local
```

Optionally create a verified local user with the `admin` role after migrations:

```bash
npm run init:admin
```

This writes directly to D1 with Wrangler and does not require `npm run dev`.

Wrangler stores local D1 state under `.wrangler/`, which is ignored by git.
The Miniflare-backed D1 SQLite files live at:

```text
.wrangler/state/v3/d1/miniflare-D1DatabaseObject/
```

The application database is the hashed `*.sqlite` file in that directory. The
`metadata.sqlite` files are Miniflare bookkeeping and are not the app database.
Wrangler persists local D1 data between dev sessions by default. If you need a
fresh local database, remove the relevant `.wrangler` state or reset it with SQL.

Run the app:

```bash
npm run dev
```

The app uses the same `DB` binding locally, but the data is local-only unless the
binding is configured as remote.

## Drizzle Studio

The project includes:

```bash
npm run db:studio
```

Drizzle Studio reads `drizzle.config.ts`. With this project's default
`driver: 'd1-http'` config, Studio connects through Cloudflare's D1 HTTP API,
not through Wrangler's local `DB` binding. Use this mode for a Cloudflare-hosted
D1 database by setting the required values in the shell that starts Studio:

```bash
CLOUDFLARE_ACCOUNT_ID=your-account-id \
CLOUDFLARE_DATABASE_ID=your-d1-database-id \
CLOUDFLARE_D1_TOKEN=your-api-token \
npm run db:studio
```

Do not put `CLOUDFLARE_D1_TOKEN` in `wrangler.jsonc` or commit it. Drizzle Kit
does not read the Wrangler login session for this path; it needs the explicit
Cloudflare account, database, and token values.

If you run `npm run db:studio` without real Cloudflare values, Drizzle Kit will
use the placeholder `local` values from `drizzle.config.ts` and eventually fail
with a Cloudflare routing error such as
`Could not route to /client/v4/accounts/local/d1/database/local/query`. That
means Studio is trying to use remote D1 mode without real remote D1 credentials.

For local D1, Wrangler stores the Miniflare-backed database as a SQLite file
under `.wrangler/state/v3/d1/miniflare-D1DatabaseObject/` after local migrations
or local app usage. Drizzle Studio can inspect that file as SQLite, but that is a
local-dev convenience rather than a Cloudflare D1 API connection. This project
does not commit a local Studio config because Studio support is optional; copy
the config below only when you want this workflow.

First apply local migrations:

```bash
npm run db:migrate:local
```

Then create an uncommitted local config:

```ts
// drizzle.studio.local.config.ts
import { existsSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { defineConfig } from 'drizzle-kit';

const localD1Directory = '.wrangler/state/v3/d1/miniflare-D1DatabaseObject';

function toFileUrl(path: string) {
  return path.startsWith('file:') ? path : `file:${path}`;
}

function getLocalD1SqliteUrl() {
  const explicitPath = process.env.LOCAL_D1_SQLITE_PATH;

  if (explicitPath) {
    return toFileUrl(explicitPath);
  }

  if (!existsSync(localD1Directory)) {
    throw new Error(
      [
        `Local D1 state was not found at ${localD1Directory}.`,
        'Run npm run db:migrate:local first, or set LOCAL_D1_SQLITE_PATH.',
      ].join('\n'),
    );
  }

  const sqliteFiles = readdirSync(localD1Directory)
    .filter((file) => file.endsWith('.sqlite') && file !== 'metadata.sqlite')
    .sort();

  if (sqliteFiles.length !== 1) {
    throw new Error(
      [
        `Expected one local D1 SQLite database in ${localD1Directory}.`,
        ...sqliteFiles.map((file) => `- ${join(localD1Directory, file)}`),
        'Set LOCAL_D1_SQLITE_PATH to the database file you want Studio to open.',
      ].join('\n'),
    );
  }

  return toFileUrl(join(localD1Directory, sqliteFiles[0]));
}

export default defineConfig({
  dialect: 'sqlite',
  schema: './src/db/schema/index.ts',
  dbCredentials: {
    url: getLocalD1SqliteUrl(),
  },
});
```

Run Studio with that config:

```bash
npx drizzle-kit studio --config drizzle.studio.local.config.ts
```

If you have more than one local D1 file, point Studio at the one you want:

```bash
LOCAL_D1_SQLITE_PATH=.wrangler/state/v3/d1/miniflare-D1DatabaseObject/your-file.sqlite \
npx drizzle-kit studio --config drizzle.studio.local.config.ts
```

Drizzle Kit needs either `better-sqlite3` or `@libsql/client` installed to open a
raw SQLite file. This project does not require either package for normal D1
runtime behavior, so install one only if you want the local-file Studio workflow.

The Chrome extension is not required for the CLI workflow above. Drizzle's
Chrome extension is an optional way to use Studio from supported browser-based
database dashboards. For this project, local D1 inspection should use the CLI
Studio server plus the local SQLite file, and Cloudflare-hosted D1 inspection
should use the CLI Studio server plus D1 HTTP credentials.

## Production Database

Create the remote D1 database once:

```bash
npx wrangler d1 create vk
```

Copy the returned `database_id` into `wrangler.jsonc` under the `DB` binding:

```jsonc
{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "vk",
      "database_id": "replace-with-cloudflare-d1-id",
      "migrations_dir": "drizzle/d1",
    },
  ],
}
```

For remote migrations, prefer Wrangler's D1 commands:

```bash
npm run db:migrate:remote
```

Wrangler reads the D1 binding from `wrangler.jsonc` and uses your Wrangler auth
session or `CLOUDFLARE_API_TOKEN` from the shell or CI environment.

If you run a Drizzle Kit command that connects directly to Cloudflare D1 over
HTTP, provide these values through your shell or CI secret store instead of a
project environment file:

```bash
CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_DATABASE_ID=
CLOUDFLARE_D1_TOKEN=
```

Apply migrations to the remote database:

```bash
npm run db:migrate:remote
```

Optionally create a verified remote user with the `admin` role after migrations:

```bash
npm run init:admin -- --remote
```

This writes directly to the remote D1 database with Wrangler and does not require
the app server to be running.

## Alternate Development Databases

You have three common options when `DB` should point somewhere other than the
default local D1 state.

### Separate Local D1 State

Keep `binding` as `DB`, keep `database_name` as the database that Wrangler
commands should target, and add a `preview_database_id` to isolate the local dev
database identity:

```jsonc
{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "vk",
      "database_id": "replace-with-production-cloudflare-d1-id",
      "preview_database_id": "vk-local-jane",
      "migrations_dir": "drizzle/d1",
    },
  ],
}
```

Then apply migrations to that local database:

```bash
npm run db:migrate:local
```

Use a stable `preview_database_id` if you want local data to persist across
sessions. Use a different `preview_database_id` when you intentionally want a
separate local D1 database.

### Cloudflare-Hosted Dev Database

Create a second Cloudflare D1 database for development:

```bash
npx wrangler d1 create vk-dev
```

Use a named Wrangler environment so production and development bindings stay
separate. Binding configuration is environment-specific, so repeat both `vars`
and `d1_databases` inside the named environment:

```jsonc
{
  "env": {
    "development": {
      "name": "vk-development",
      "vars": {
        "EMAIL_PROVIDER": "console",
      },
      "d1_databases": [
        {
          "binding": "DB",
          "database_name": "vk-dev",
          "database_id": "replace-with-dev-cloudflare-d1-id",
          "migrations_dir": "drizzle/d1",
          "remote": true,
        },
      ],
    },
  },
}
```

Run local dev against that Cloudflare-hosted database by selecting the environment
for the Cloudflare Vite runtime:

```bash
CLOUDFLARE_ENV=development npm run dev
```

Apply migrations to that remote dev database with:

```bash
npx wrangler d1 migrations apply vk-dev --remote --env development
```

`remote: true` means local requests can mutate the Cloudflare D1 database. Use a
dev database, not production, unless that is intentional.

### Production-Like Named Environment

For production, use the same environment pattern but point `DB` at the production
database and omit `remote: true`; deploy commands run on Cloudflare and use the
bound remote database there:

```jsonc
{
  "env": {
    "production": {
      "name": "vk-production",
      "vars": {
        "EMAIL_PROVIDER": "console",
      },
      "d1_databases": [
        {
          "binding": "DB",
          "database_name": "vk",
          "database_id": "replace-with-production-cloudflare-d1-id",
          "migrations_dir": "drizzle/d1",
        },
      ],
    },
  },
}
```

Apply migrations and deploy with the same environment name:

```bash
npx wrangler d1 migrations apply vk --remote --env production
npx wrangler deploy --env production
```

## Runtime Boundary

Runtime code should continue to use the local `src/db` modules. Do not import
`drizzle-orm/d1` from app routes, actions, middleware, or UI code. Hyperdrive is
a planned adapter target, but D1 is the only supported runtime database in this
milestone.

The `app_settings` table is part of this app-owned D1 schema. It stores small
key/value settings such as `site.title` through the local app settings action
surface. It is not a database administration table and should not be used for
migrations, connection management, or database target selection.
