feat: add PostgreSQL backend #1
No Reviewers
Labels
Clear labels
Agentic
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Agent
Fable
Work made by a Claude Fable agent
Agent
gpt-5.6-luna
Work made by a gpt-5.6-luna agent
Agent
gpt-5.6-sol
Work made by a gpt-5.6-sol agent
Agent
gpt-5.6-terra
Work made by a gpt-5.6-terra agent
Work made by an agent
Agent
Opus
Work made by a Claude Opus agent
Agent
Sonnet
Work made by a Claude Sonnet agent
Breaking change that won't be backward compatible
Something is not working
Documentation changes
Improve existing functionality
New functionality
This is security issue
Issue or pull request related to testing
Priority
Critical
1
The priority is critical
Priority
High
2
The priority is high
Priority
Low
4
The priority is low
Priority
Medium
3
The priority is medium
Reviewed
Confirmed
1
Issue has been confirmed
Reviewed
Duplicate
2
This issue or pull request already exists
Reviewed
Invalid
3
Invalid issue
Reviewed
Won't Fix
3
This issue won't be fixed
Status
Abandoned
3
Somebody has started to work on this but abandoned work
Status
Blocked
1
Something is blocking this issue or pull request
Status
Need More Info
2
Feedback is required to reproduce issue or to continue work
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Reference: codinget/abode#1
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Adds a PostgreSQL backend that mirrors the
node:sqlitesub-backend structure, using native pg types and full migration support.New:
src/db/postgres/sql.ts— Template tag using?placeholders internally (same pattern as the sqlite side);toPositional()converts?→$1,$2…only at execution time, socatSql/joinSqlcomposition works identically to sqlite with no renumbering.cast.ts— Row converters for pg's native return types: UUIDs as strings (no buffer conversion), JSONB columns as already-parsed objects,TIMESTAMPTZcolumns asDate→.toISOString().pool.ts—WrappedPgClientinterface with two implementations:WrappedPool(wrapspg.Pool) andWrappedPgTx(dedicatedPoolClientfor transactions).multi()runsBEGIN/COMMIT/ROLLBACKon a checked-out client.rethrowmaps pg error codes23505→ConflictAbodeError,23503→NotFoundAbodeError.query.ts— Async SELECT helpers mirroringsqlite/query.ts; nojson("flags")wrapping needed since JSONB comes back parsed.url.ts— Parsespostgres://andpostgresql://URLs; strips thereadonlyquery param before passing the connection string to pg.PostgresInterface.ts— ImplementsBackendDbInterface. All db calls are awaited;multi()receives atx: WrappedPgClientwhich is threaded through to private helpers (#getUserById(uid, tx), etc.). Timestamps useNOW()/NOW() + INTERVAL '7 days'.PostgresMigrator.ts— ImplementsMigrator. Table existence checked viainformation_schema.tables(vs catching a "no such table" error in sqlite). Each migration runs in its own transaction on a dedicated pool client.getdb.static.ts— Factory registered indbSources.static.tsforpostgres:andpostgresql:protocols.migrations/— Schema DDL using proper pg types:UUID,JSONB,TIMESTAMPTZ,NOW(),INTERVAL '7 days','{}'::jsonb.Modified
src/db/dbSources.static.ts— registersgetPgStaticpackage.json— adds@types/pg ^8.20.0devDependencyTest plan
tsc --noEmitpasses (verified clean)npm run abode-migrate -- --url postgres://localhost/abode_test availablelists 3 migrationsnpm run abode-migrate -- --url postgres://localhost/abode_test migrate 3runs all migrations cleanlynpm run abode-migrate -- --url postgres://localhost/abode_test currentshows migration 3 applied--url postgres://...and user CRUD works via APIINTERVALarithmetictest comment from review
Review: approve, with one bug worth fixing
(Ignore the stray "test comment from review" above — that was me debugging the CLI, not part of this review.)
Reviewed the postgres backend implementation (tsc clean, checked against the sqlite backend for parity). Overall this faithfully mirrors SqliteInterface's semantics — readonly checks, error-code mapping (23505→Conflict, 23503→NotFound), transaction handling, and cast/type coercion all check out. No SQL injection risk found; all values go through the
sqltagged template's parameter binding.Bug worth fixing before/soon after merge:
src/db/postgres/PostgresMigrator.ts(~L97-122): in the per-migration loop, JS-defined migration parts run viapart.apply(this.#pool)— passing the pool, not the transactionalclient. Compare toSqliteMigrator.ts, which correctly passes the single active connection (part.apply(this.#db)). No migration currently uses a JSapply()part (all are.sqlfiles), so this is latent, but the first JS-based migration part added will run outside the transaction on a separate pooled connection — breaking atomicity silently. Suggest threading the transactionalWrappedPgClientthrough instead.Non-blocking notes:
sql.ts'stoPositional()naively replaces every literal?with$n— fine today, but Postgres's JSONB?/?|/?&operators would break this if ever used, given how JSONB-heavy this schema is. Worth a comment/guard.WrappedPgTx/WrappedPoolinpool.tsduplicateall/get/run— could share a small base/helper.pool.tsrollback-on-error doesn't guard against the rollback call itself throwing (would mask the original error).This PR is fully additive (new
src/db/postgres/dir + two small touch points), so it's low-conflict and safe to merge independently of #2/#3.bc787a208atob9fa79ff1f