Stack verified live (/health green, 4 tables pushed). Record Docker deviation in SUMMARY, mark 01-01 complete in ROADMAP, clean up paused-setup handoff files.
226 lines
12 KiB
Markdown
226 lines
12 KiB
Markdown
---
|
|
phase: 01-foundation-broker-spike
|
|
plan: "01"
|
|
subsystem: infra
|
|
tags: [hono, drizzle, mariadb, mysql2, vitest, docker, pnpm, react, vite, typescript]
|
|
|
|
# Dependency graph
|
|
requires: []
|
|
provides:
|
|
- pnpm monorepo workspace (apps/api + apps/pwa)
|
|
- Hono API scaffold with /health route (unauthenticated, real DB round-trip)
|
|
- Drizzle ORM schema: users, memberCredentials, calendars, calendarEvents
|
|
- drizzle(mysql2 pool) db singleton
|
|
- Docker Compose stack: api + mariadb:11 (healthcheck) + redis
|
|
- Vitest harness with Wave 0 test stubs
|
|
- React PWA shell fetching /health
|
|
- drizzle.config.ts for drizzle-kit push/migrate
|
|
affects:
|
|
- 01-02 (OIDC auth — imports db, users schema)
|
|
- 01-03 (broker — imports db, all schemas, crypto pattern)
|
|
- 01-04 (spike — imports broker module)
|
|
|
|
# Tech tracking
|
|
tech-stack:
|
|
added:
|
|
- hono@4.12.23
|
|
- "@hono/node-server@2.0.4"
|
|
- "@hono/oidc-auth@1.8.3"
|
|
- "@hono/zod-validator@0.8.0"
|
|
- drizzle-orm@0.45.2
|
|
- drizzle-kit@0.31.10
|
|
- mysql2@3.22.4
|
|
- tsdav@2.2.2
|
|
- ical.js@2.2.1
|
|
- zod@^3.25.0
|
|
- node-cron@^4.2.1
|
|
- vitest@^4.1.8
|
|
- react@^19.0.0
|
|
- "@tanstack/react-query@5.101.0"
|
|
- zustand@5.0.14
|
|
- vite@8.0.16
|
|
patterns:
|
|
- Hono app exported from src/index.ts for testability (no server start on import)
|
|
- db singleton pattern (drizzle mysql2 pool, connectionLimit 10)
|
|
- vi.mock at module top level for test isolation (Vitest hoisting)
|
|
- Wave 0 test stubs using it.todo to document future tests before implementation
|
|
|
|
key-files:
|
|
created:
|
|
- package.json (root workspace, pnpm@11.5.1)
|
|
- pnpm-workspace.yaml (apps/*, allowBuilds.esbuild: true)
|
|
- .gitignore (.env excluded — secrets never committed)
|
|
- .env.example (all env var names documented)
|
|
- docker-compose.yml (api + mariadb:11 + redis)
|
|
- docker-compose.dev.yml (dev overrides)
|
|
- apps/api/package.json (pinned deps)
|
|
- apps/api/tsconfig.json (strict, NodeNext, ES2023)
|
|
- apps/api/Dockerfile (node:22-alpine, multi-stage)
|
|
- apps/api/vitest.config.ts (environment: node, globals: true)
|
|
- apps/api/drizzle.config.ts (dialect: mysql)
|
|
- apps/api/src/db/schema.ts (users/memberCredentials/calendars/calendarEvents)
|
|
- apps/api/src/db/client.ts (db export)
|
|
- apps/api/src/routes/health.ts (GET / with SELECT 1 round-trip)
|
|
- apps/api/src/index.ts (Hono app, /health mounted before auth)
|
|
- apps/api/tests/health.test.ts (2 tests pass)
|
|
- apps/api/tests/helpers/db.ts (mock helpers + sample VEVENTs)
|
|
- apps/api/tests/auth/user.test.ts (5 todos — Plan 02)
|
|
- apps/api/tests/broker/crypto.test.ts (5 todos — Plan 03)
|
|
- apps/api/tests/broker/sync.test.ts (6 todos — Plan 03)
|
|
- apps/api/tests/broker/poller.test.ts (5 todos — Plan 03)
|
|
- apps/pwa/package.json
|
|
- apps/pwa/tsconfig.json
|
|
- apps/pwa/vite.config.ts (proxy /health + /api to :3000)
|
|
- apps/pwa/index.html
|
|
- apps/pwa/src/main.tsx (QueryClientProvider)
|
|
- apps/pwa/src/App.tsx (fetches /health, renders stack: up/down)
|
|
modified: []
|
|
|
|
key-decisions:
|
|
- "Export app from src/index.ts without auto-starting server: enables direct import in Vitest tests without binding a port"
|
|
- "Use vi.mock at module top level (not inside test): Vitest hoists vi.mock — placing inside describe/it causes warnings"
|
|
- "pnpm-workspace.yaml allowBuilds.esbuild: true: pnpm 11 uses allowBuilds syntax, not onlyBuiltDependencies"
|
|
- "zod pinned at ^3.25.0 (not ^4): conservative per RESEARCH — @hono/zod-validator@0.8.0 accepts both but v3 avoids unknown v4 API differences"
|
|
|
|
patterns-established:
|
|
- "Pattern: Hono testability — export app from index.ts, use import.meta.url guard to start server only when run directly"
|
|
- "Pattern: db mock — vi.mock('../src/db/client.js') at module level; override per-test with vi.mocked().mockRejectedValueOnce"
|
|
- "Pattern: Wave 0 stubs — it.todo with plan reference so future agents know which plan fills each test"
|
|
|
|
requirements-completed: [CAL-01]
|
|
|
|
# Metrics
|
|
duration: 6min
|
|
completed: "2026-06-04"
|
|
---
|
|
|
|
# Phase 01 Plan 01: Walking Skeleton — Summary
|
|
|
|
**pnpm monorepo with Hono API, Drizzle/MariaDB schema (4 tables), Docker Compose stack, and /health route with real DB round-trip — ALL 3 tasks complete. Task 3 checkpoint cleared by orchestrator: stack brought up, `drizzle-kit push` applied the 4 tables to live MariaDB, and `/health` returned `{"ok":true,"db":"up"}` end-to-end. Required fixing 3 Docker build defects (see Deviations).**
|
|
|
|
## Performance
|
|
|
|
- **Duration:** ~6 min
|
|
- **Started:** 2026-06-04T13:46:55Z
|
|
- **Completed:** 2026-06-04T13:53:00Z (Tasks 1-2; Task 3 is a human-action checkpoint)
|
|
- **Tasks:** 2 of 3 complete (Task 3 is a blocking checkpoint)
|
|
- **Files modified:** 28
|
|
|
|
## Accomplishments
|
|
|
|
- Full pnpm monorepo scaffold: apps/api (Hono + Drizzle + all pinned deps) and apps/pwa (Vite/React 19 + TanStack Query)
|
|
- Drizzle schema with all 4 tables (users, memberCredentials, calendars, calendarEvents) following D-10 (oidc_iss+oidc_sub composite key) and D-13 (separate dtstart_utc/dtstart_date for all-day events)
|
|
- /health route with real DB round-trip (SELECT 1) — GREEN: 2 tests pass, 503 on DB error
|
|
- Docker Compose stack with mariadb:11 healthcheck, api depends_on service_healthy, redis stub
|
|
- Wave 0 test harness: 5 test files, 21 todos (auth/user, broker/crypto, broker/sync, broker/poller) + 2 passing health tests
|
|
- React PWA shell fetching /health and rendering stack: up/down
|
|
|
|
## Task Commits
|
|
|
|
Each task committed atomically:
|
|
|
|
1. **Task 1: Scaffold monorepo, Docker Compose stack, and Vitest harness** — `3f59156` (chore)
|
|
2. **Task 2: RED gate (failing health test)** — `f31711a` (test)
|
|
3. **Task 2: Drizzle schema + DB client + /health slice (GREEN)** — `96cda58` (feat)
|
|
|
|
## Files Created/Modified
|
|
|
|
Key files (full list in frontmatter key-files):
|
|
|
|
- `apps/api/src/db/schema.ts` — 4 Drizzle mysqlTable definitions with all constraints
|
|
- `apps/api/src/db/client.ts` — `db` singleton export (drizzle mysql2 pool)
|
|
- `apps/api/src/routes/health.ts` — GET /health with SELECT 1 round-trip
|
|
- `apps/api/src/index.ts` — Hono app, /health before auth, serveStatic
|
|
- `apps/api/drizzle.config.ts` — drizzle-kit push/migrate config
|
|
- `apps/pwa/src/App.tsx` — React shell fetching /health
|
|
- `docker-compose.yml` — mariadb:11 + healthcheck + api depends_on service_healthy
|
|
|
|
## Decisions Made
|
|
|
|
- Exported `app` from `src/index.ts` without auto-starting server (import.meta.url guard) so Vitest tests can import it directly without a real HTTP port
|
|
- pnpm 11 uses `allowBuilds.esbuild: true` in pnpm-workspace.yaml (not `onlyBuiltDependencies`) — pnpm rewrote this during install
|
|
- zod pinned `^3.25.0` per RESEARCH recommendation (not v4)
|
|
|
|
## Deviations from Plan
|
|
|
|
None — plan executed exactly as specified. One minor pnpm API difference (allowBuilds syntax) was auto-handled.
|
|
|
|
### Auto-fixed Issues
|
|
|
|
**1. [Rule 3 - Blocking] pnpm 11 allowBuilds syntax**
|
|
- **Found during:** Task 1 (pnpm install)
|
|
- **Issue:** `pnpm install` failed with `ERR_PNPM_IGNORED_BUILDS: esbuild@*`. pnpm 11 uses `allowBuilds` map (not `onlyBuiltDependencies` list used in older versions)
|
|
- **Fix:** Set `allowBuilds.esbuild: true` in pnpm-workspace.yaml
|
|
- **Files modified:** pnpm-workspace.yaml
|
|
- **Verification:** `pnpm install` succeeded; all deps installed
|
|
- **Committed in:** `3f59156` (Task 1 commit)
|
|
|
|
**2. [Rule 1 - Bug] vi.mock hoisting in health test**
|
|
- **Found during:** Task 2 (writing TDD RED test)
|
|
- **Issue:** Placing `vi.mock()` inside `describe()` blocks caused Vitest hoisting warnings; tests used `resetModules` approach which conflicted with hoisting behavior
|
|
- **Fix:** Moved `vi.mock` to module top level; used `vi.mocked().mockRejectedValueOnce()` for per-test override
|
|
- **Files modified:** apps/api/tests/health.test.ts
|
|
- **Verification:** Both health tests pass; no hoisting warnings
|
|
- **Committed in:** `96cda58` (Task 2 feat commit)
|
|
|
|
**3. [Checkpoint clearing - Blocking] Docker image build broken for pnpm workspace**
|
|
- **Found during:** Task 3 (orchestrator bringing up the stack to clear the checkpoint)
|
|
- **Issue:** The original `apps/api/Dockerfile` built from a `./apps/api` context and could not work in a pnpm workspace:
|
|
1. `COPY package.json pnpm-lock.yaml* ./` + `pnpm install --frozen-lockfile` failed (`ERR_PNPM_NO_LOCKFILE`) — the lockfile lives at the repo root, not in `apps/api/`.
|
|
2. pnpm 11 refused to run `esbuild`'s build script (`ERR_PNPM_IGNORED_BUILDS`) because the root `pnpm-workspace.yaml` (which carries `allowBuilds.esbuild`) was outside the build context. Neither package.json `pnpm.onlyBuiltDependencies` nor `.npmrc dangerously-allow-all-builds` resolved it in the isolated context.
|
|
3. `dev` stage ran `node --watch dist/index.js` but never compiled `src`→`dist`; production stage had invalid Dockerfile syntax (`COPY apps/pwa/dist/ ./public/ 2>/dev/null || true`) referencing a path outside its context.
|
|
- **Fix:** Switched to the correct monorepo pattern — build from the **repo-root context** (`docker-compose.yml` `build.context: .`, `dockerfile: apps/api/Dockerfile`), copy the root `pnpm-workspace.yaml` + `pnpm-lock.yaml` + both workspace `package.json`s, and `pnpm install --frozen-lockfile --filter @familysync/api...`. Reordered stages so `production` is the default; `dev` now reuses the builder output; dropped the invalid PWA COPY. Updated `docker-compose.dev.yml` volume mount to `./apps/api/src:/app/apps/api/src`.
|
|
- **Files modified:** apps/api/Dockerfile, docker-compose.yml, docker-compose.dev.yml
|
|
- **Verification:** `docker compose up -d --build` succeeds; `drizzle-kit push` applied 4 tables; `curl /health` → `{"ok":true,"db":"up"}`; `pnpm test` → 2 passed / 21 todo.
|
|
- **Committed in:** `fix(01-01): build Docker image from repo-root pnpm workspace context`
|
|
|
|
---
|
|
|
|
**Total deviations:** 3 (2 auto-fixed during execution, 1 Docker-build fix while clearing the Task 3 checkpoint)
|
|
**Impact on plan:** All fixes necessary for install/tests/stack to work. No scope creep — same walking-skeleton capability, corrected build topology.
|
|
|
|
## Issues Encountered
|
|
|
|
- pnpm 11 changed the `onlyBuiltDependencies` API to `allowBuilds` map syntax — resolved automatically
|
|
|
|
## User Setup Required
|
|
|
|
Task 3 requires manual steps. See checkpoint details returned to orchestrator:
|
|
|
|
1. Copy `.env.example` to `.env` and set `DB_PASSWORD` + `DB_ROOT_PASSWORD`
|
|
2. `docker compose up -d mariadb` and wait for healthy
|
|
3. `DB_HOST=127.0.0.1 ... pnpm exec drizzle-kit push` from apps/api
|
|
4. Confirm `SHOW TABLES` lists 4 tables
|
|
5. `docker compose up -d` and `curl http://localhost:3000/health` → `{"ok":true,"db":"up"}`
|
|
|
|
## Known Stubs
|
|
|
|
None. The `serveStatic` warning for `./public` in tests is expected (no built PWA in test env) and is not a stub — the route exists and will serve correctly when the PWA is built.
|
|
|
|
## Threat Surface Scan
|
|
|
|
No new threat surface beyond what was planned in the threat model:
|
|
- T-01-01: `.env` excluded from git via `.gitignore` ✓
|
|
- T-01-02: `familysync` user (not root) in docker-compose.yml ✓
|
|
- T-01-03: `/health` unauthenticated, returns only `{ok, db}` ✓
|
|
|
|
## Next Phase Readiness
|
|
|
|
- Task 3 checkpoint CLEARED — schema pushed, stack verified, `/health` green
|
|
- Plan 02 (OIDC auth) can proceed — imports `db`, `users` schema
|
|
- Plan 03 (broker) depends on this schema + crypto pattern
|
|
|
|
## Self-Check: PASSED
|
|
|
|
- `apps/api/src/db/schema.ts` exists: FOUND
|
|
- `apps/api/src/db/client.ts` exists: FOUND
|
|
- `apps/api/src/routes/health.ts` exists: FOUND
|
|
- `apps/api/src/index.ts` exists: FOUND
|
|
- `docker-compose.yml` exists with healthcheck: FOUND
|
|
- `apps/pwa/src/App.tsx` fetches /health: FOUND
|
|
- Commits 3f59156, f31711a, 96cda58: FOUND
|
|
|
|
---
|
|
*Phase: 01-foundation-broker-spike*
|
|
*Completed: 2026-06-04 (Tasks 1-2; Task 3 at checkpoint)*
|