refactor(db): collapse @neondatabase/serverless onto @vercel/postgres + delete lib/database.js #30
No reviewers
Labels
No labels
agent-context-drift
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: rstillwell/deckhearth#30
Loading…
Reference in a new issue
No description provided.
Delete branch "convoy/single-sql-client"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Convoy:
single-sql-client(P1 quality, launch sequence step 8). Closes the dual SQL-client problem documented in AGENTS.md Gotcha #1 +.convoys/ship-readiness.mdP1 #8 by deletinglib/database.jsand migrating its sole production caller (pages/api/auth-utils.js) onto the canonical@vercel/postgrestagged-template surface. Keeps@neondatabase/serverlessas a runtime dep for the 11scripts/*helpers that useneon()directly (out of scope per the no-go-zones rule).Decisions
pages/api/auth-utils.jsimportsdb;test/api/auth-utils.test.jsmocks the module purely to satisfy the import graph (the 5 tests exercisegenerateToken/verifyToken, notisAdmin/getUserById).@vercel/postgrestagged templates with byte-equivalent SQL. Single-table SELECTs, single numeric parameter (userId), same{ rows, rowCount }result shape, sametry/catch+result.rows[0]?.role === 'admin'access pattern. No transaction or pool semantics differ.@neondatabase/serverlessas a dep. 11scripts/*files still useneon()directly (setup-neon-db.js,migrations/2026-05-24-rename-admin-email.js,reset-db.js, 8 historicaladd-*/fix-*/seed-*jobs). Migrating those is out of scope per the convoy spec + the no-go-zones rule; queued aspurge-neondatabase-serverless-fully(blocked onmigration-tool).sql.unsafeaudit: NOT a real injection vector with current callers.userIdis sourced from a verified JWT (decoded.userIdafterverifyToken(token)succeeds), is a numeric SERIAL id. Security finding: NO. Pure refactor + foot-gun removal that prevents the FUTURE caller that would have been the incident.vi.mock('../../lib/database.js')call + unusedviimport. Test count + assertions unchanged (5/5).Caller inventory
pages/api/auth-utils.jsdb, callsdb.queryinisAdmin(userId)+getUserById(userId)→ migrated tosql\…${userId}``test/api/auth-utils.test.jsvi.mock('../../lib/database.js', …)→ removedlib/database.js\$1→ string interpolation +sql.unsafe(query)— the foot-gun is gone.convoys/single-sql-client.mdPer-file changes
pages/api/auth-utils.js(+5 / -7):import { db } from '../../lib/database.js'→import { sql } from '@vercel/postgres'isAdmin:db.query(\SELECT role FROM users WHERE id = $1`, [userId])→sql`SELECT role FROM users WHERE id = ${userId}``getUserById: same shape, same swaptest/api/auth-utils.test.js(+1 / -5):vi.mock('../../lib/database.js', () => ({ db: { query: vi.fn() } }))viimportlib/database.js(0 / -47): DELETED..convoys/single-sql-client.md(+434 / 0): NEW convoy file.Verification
npm run lint→ 128 problems (81 errors, 47 warnings) — baseline preserved, no regressionnpm run test:run→ 21/21 pass (4 test files, 1.18s)Grep "lib/database" --type js→ 0 hits anywhere in JS sourcesGrep "@neondatabase/serverless" --type js→ still matchesscripts/setup-neon-db.js,scripts/migrations/2026-05-24-rename-admin-email.js,scripts/reset-db.js, and 8 otherscripts/add-*/fix-*/seed-*historical helpers (expected; out of scope per D3)node --check pages/api/auth-utils.js→ exit 0Live runtime smoke deferred. The two migrated functions (
isAdmin,getUserById) are only reachable viapages/api/admin/index.jswhich requires an admin Bearer token + a populateduserstable in prod Neon. Byte-equivalent SQL + identical result shape (D2) gives high confidence; rollback is a single-commit revert of this PR if a post-merge admin action 500s.Risks
lib/database.jshas hidden behavior. Mitigation: architect re-read all 47 lines; only behavior beyond "interpolate, run SQL, return{ rows, rowCount }" is string-quoting for string params, but both current callers pass numericuserId(quoting path not exercised). Theraw()method is just an alias forquery(); no caller invokesraw(Grep "\.raw\(" --type js→ zero hits). Residual risk: very low.Grepsweep (verification step 3) — if any file still importslib/database, the file no longer exists and the import throws at module load, failing CI lint or test.lib/database.jsor anothersql.unsafe-shaped wrapper. Mitigation: documentation (this convoy file + AGENTS.md Gotcha #1 doc-writer flip in a follow-up cleanup pass). Stronger mitigation surfaced as queued follow-uplint-against-lib-database(ESLintno-restricted-imports).Follow-ups (queued)
lint-against-lib-database(P3) — ESLintno-restricted-importsrule againstlib/databasere-introduction.purge-neondatabase-serverless-fully(P3, blocked onmigration-tool) — full dep purge once scripts adopt a single client.add-neon-return-shape-rule(P3) — codify theneon()(returns[rows]) vs@vercel/postgres({ rows: [...] }) return-shape difference; partially satisfied here because the dual shape is collapsed forpages/api/**.Conflict-with-parallel-PRs note
Parallel P1 convoys in flight (
single-auth-provider,purge-weak-creds-from-helpers, etc.) also touchpages/api/**and other files. This PR's source-side changes are confined topages/api/auth-utils.js(2 function bodies + 1 import line). Conflicts at merge time should be minimal — the auth-utils import block doesn't overlap with thegetUserFromRequestor other auth-context refactors thatsingle-auth-provideris expected to make.Test plan
npm run lint→ 128 problems (baseline)npm run test:run→ 21/21 passlib/databasereference in JS sources@neondatabase/serverlessstill matches only the 11scripts/*sites (D3 scope)/admin) to confirmisAdminworks end-to-endMade with Cursor
The latest updates on your projects. Learn more about Vercel for GitHub.
Pipeline Health
Build + CI gates
Build runs on Vercel; this CI runs lint and schema-map drift only (no duplicate build).
Role reports
See individual comments above for details. This rollup updates automatically.