Skip to content

npm run dev silently serves stale @wxyc/database when shared/database/dist is out of date #968

@jakebromberg

Description

@jakebromberg

Problem

`npm run dev` does not rebuild the `@wxyc/database` workspace before starting the backend, so any schema-changing PR that lands on `main` between developer rebuilds (or a fresh `git pull`) silently serves a backend that imports a schema older than what the running PostgreSQL database actually has.

When that happens the failure mode is not the kind that points at the cause: the runtime error is a generic `TypeError: Cannot convert undefined or null to object` deep inside `drizzle-orm/utils.js`'s `orderSelectedFields`, with no log line naming the bad column. The flowsheet's `/flowsheet/?page=...`, `/flowsheet/latest`, and the entry-page hydration all 500 with that error until `shared/database/dist/` is regenerated.

Reproduced this directly during the #940 / WXYC/dj-site#560 preview session on 2026-05-18:

  1. Pulled `main` (had E6-4: Migration — flowsheet.track_position TEXT NULL #835 / commit 3256093 on board — adds `flowsheet.track_position`).
  2. `npm run dev` succeeded; backend bound to 8080.
  3. Hit the flowsheet — 500 on every read.
  4. Stack trace surfaced through Express's error handler; logged at `apps/backend/services/flowsheet.service.ts:252` (`db.select(FSEntryFieldsRaw).from(flowsheet).leftJoin(rotation, ...)`). Spent ~20 minutes ruling out drizzle config, migration state, schema export, and table presence before checking `shared/database/dist/index.js` against `shared/database/src/schema.ts`.
  5. `ls -la shared/database/dist/index.js shared/database/src/schema.ts` showed `dist/index.js` was from 2026-05-15 09:47 — before commit 3256093 (2026-05-15 17:06). `grep "track_position" shared/database/dist/index.js` returned only the `compilation_track_artist` match; `flowsheet.track_position` was absent.
  6. `npm run build --workspace=@wxyc/database` regenerated `dist/`; restarted backend; the route immediately returned 200.

The dev script doesn't help anyone catch this. `tsup --watch` in `apps/backend` watches its own sources but not the dist files of workspace deps; the only way `@wxyc/database/dist` updates is if someone manually runs `npm run build` on that workspace. CI doesn't have the problem because the deploy build chains all workspaces.

End state

`npm run dev` produces a backend whose `@wxyc/database` import matches the current `shared/database/src/schema.ts`. Two practical paths:

  1. Pre-dev script chain: Add a `predev` script in the root `package.json` that runs `npm run build --workspace=@wxyc/database`. tsup builds are fast (~30ms per the existing build log), so the cost on every `npm run dev` is trivial. Cleanest and works for anyone.

  2. Source export: Switch `shared/database/package.json`'s exports to point at `./src/index.ts` (with TypeScript path resolution) instead of `./dist/index.js`. Eliminates the build step in dev entirely. More invasive: needs verification that the existing `source` condition in the `exports` map is what dev tooling reaches for, and that production builds still pick up `./dist/index.js` correctly. Lower-effort if the `source` condition already does the right thing; could be a one-line change.

  3. Concurrently watch: Add a `build:watch` script in `shared/database` (`tsup --watch`) and add it to the root `concurrently` invocation in `npm run dev`. Catches the case where someone edits `schema.ts` mid-dev session. More moving parts than (1) or (2).

Approach (1) is the smallest defensible change; (2) is the right long-term shape if it works out cleanly.

Where

  • Root `package.json` (`scripts.dev`, `scripts.predev`).
  • `shared/database/package.json` (the `exports` map if pursuing option 2).
  • `CLAUDE.md` under "Running locally" — note that `npm run dev` now includes (or doesn't need) a workspace build step.

Acceptance criteria

  • After `git pull` brings in a `shared/database/src/schema.ts` change, the next `npm run dev` produces a backend that sees the new schema columns without a manual `npm run build --workspace=@wxyc/database` step.
  • The cost added to `npm run dev` startup is acceptable (~< 1s for option 1, zero for option 2).
  • No regression in production builds: `npm run build` from the root still produces the existing `dist/` artifacts that ECR images consume.
  • CLAUDE.md updated so a future developer doesn't have to re-derive the answer.

Constraints

  • Not a CI problem: `npm run typecheck` and `npm run build` run from CI cleanly because CI starts from a fresh checkout and builds every workspace. This is purely a dev-loop hazard.
  • The error class (`Cannot convert undefined or null to object` inside drizzle utils with no column name) is the highest-friction part. Even if the dev script is left as-is, at minimum an error log line that says "schema.ts column X is undefined at runtime — rebuild @wxyc/database" when this specific failure mode trips would save the next debugger from the same 20-minute trace. Open to filing that as a separate, lower-priority issue if reviewer prefers.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions