Files
miti99bot/plans/260415-1010-d1-cron-infra/phase-01-d1-setup.md

106 lines
4.7 KiB
Markdown

# Phase 01 — D1 Setup
**Priority:** P0 (blocker for 02/03/04)
**Status:** Complete
## Overview
Wire Cloudflare D1 into the framework: binding, per-module migrations, `SqlStore` factory mirroring the `KVStore` shape, Miniflare-backed tests.
## Key Insights
- D1 is SQLite at the edge; prepared statements + `db.prepare().bind().all()/first()/run()`.
- `vitest-pool-workers` (or plain Miniflare) exposes D1 in tests without real Cloudflare calls.
- Per-module table prefixing mirrors the existing KV prefixing — module authors never touch raw `env.DB`.
## Requirements
**Functional**
- Module init receives `sql` alongside `db` in `init({ db, sql, env })`.
- `sql.prepare(query, ...binds)` / `sql.run(query, ...binds)` / `sql.all(query, ...binds)` / `sql.first(query, ...binds)`.
- Table names referenced in queries are left literal — authors write `trading_trades` directly (prefix is convention, not rewriting).
- `sql.tablePrefix` exposed for authors who want to interpolate.
- Migrations auto-discovered from `src/modules/*/migrations/*.sql`, applied via `wrangler d1 migrations apply` in deploy script.
**Non-functional**
- Zero overhead when a module does not use SQL.
- Tests run fully offline against Miniflare.
## Architecture
```
src/db/
├── kv-store-interface.js # existing
├── cf-kv-store.js # existing
├── create-store.js # existing (KV)
├── sql-store-interface.js # NEW — JSDoc typedef
├── cf-sql-store.js # NEW — wraps env.DB
└── create-sql-store.js # NEW — factory, sets tablePrefix = `${moduleName}_`
```
`createSqlStore(moduleName, env)` returns an object exposing `prepare`, `run`, `all`, `first`, `batch`, `tablePrefix`.
## Related Code Files
**Create**
- `src/db/sql-store-interface.js`
- `src/db/cf-sql-store.js`
- `src/db/create-sql-store.js`
- `tests/db/create-sql-store.test.js`
- `tests/fakes/fake-d1.js` (Miniflare D1 helper for tests)
**Modify**
- `src/modules/registry.js` — pass `sql` into `init({ db, sql, env })`
- `src/modules/dispatcher.js` — same
- `wrangler.toml` — add `[[d1_databases]]` block, `migrations_dir` optional
- `package.json` — add `db:migrate` script: `wrangler d1 migrations apply miti99bot-db --remote`; chain into `deploy`
- `scripts/register.js` — no change needed, but verify no breakage
## Implementation Steps
1. Create D1 database: `npx wrangler d1 create miti99bot-db`. Record UUID in `wrangler.toml`.
2. Author `sql-store-interface.js` with JSDoc `@typedef` for `SqlStore`.
3. Implement `cf-sql-store.js` — thin wrapper around `env.DB.prepare()`.
4. Implement `create-sql-store.js` — returns wrapper + `tablePrefix`.
5. Update `registry.js` + `dispatcher.js` to pass `sql` into module `init` + command handler contexts (via `ctx.sql`? **decision below**).
6. Add migration discovery: walk `src/modules/*/migrations/` at deploy time, consolidate into a central `migrations/` or use wrangler's default per-dir.
7. Wire `db:migrate` into `npm run deploy`: `wrangler deploy && npm run db:migrate && npm run register`.
8. Add `fake-d1.js` using `@miniflare/d1` or `better-sqlite3`-backed fake.
9. Tests: `create-sql-store.test.js` verifying prefix exposure + basic CRUD.
## Open Decisions
- **Command handler access to `sql`:** expose via `ctx.sql` (grammY context extension in dispatcher) or require modules to close over `sql` captured in `init`? Lean **close over in init** — matches how `db` is currently used.
- **Migration runner:** wrangler's native `d1 migrations apply` requires a single `migrations_dir`. Options:
- (a) consolidate all per-module SQL into root `migrations/` at build time via a prebuild script.
- (b) custom runner script that applies each `src/modules/*/migrations/*.sql` in order.
- **Lean (b)** — keeps per-module locality.
## Todo List
- [x] Create D1 database + update `wrangler.toml`
- [x] `sql-store-interface.js` with typedefs
- [x] `cf-sql-store.js` implementation
- [x] `create-sql-store.js` factory
- [x] Update `registry.js` init signature
- [x] Update `dispatcher.js` to pass `sql` (no change needed — delegates to buildRegistry)
- [x] Write custom migration runner at `scripts/migrate.js`
- [x] Wire into `npm run deploy`
- [x] `fake-d1.js` test helper
- [x] Unit tests for `create-sql-store`
## Success Criteria
- A module can define `init({ sql }) => sql.run("INSERT INTO mymod_foo VALUES (?)", "x")` and it works in dev + test + prod.
- `npm test` green.
- No regression in existing KV-only modules.
## Risks
- Wrangler migration tooling may not support per-module layout → fallback to custom runner.
- D1 read-after-write consistency in eventually-consistent replicas — document for module authors.
## Next Steps
- Phase 02 can start once `sql` threads through `init`.