You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
57 lines
4.0 KiB
Markdown
57 lines
4.0 KiB
Markdown
# HQ Console — Postgres Switch (D15 executed → D19)
|
|
|
|
> **STATUS: IN BUILD, started 2026-07-17.** The dedicated slice queued after the D18
|
|
> go-live cluster and **before** the real APEX cutover — so production data is born in
|
|
> Postgres and nothing is ever migrated.
|
|
|
|
## 1. Goal & why now
|
|
|
|
D15 locked Postgres as the production engine; the repo has run on `better-sqlite3`
|
|
throughout development, with the portable-repo pattern maintained specifically so this
|
|
switch stays a contained task. Doing it before the one-shot 300-client import means:
|
|
**empty Postgres → seed → import APEX CSVs straight in. No SQLite→Postgres data
|
|
migration, ever.**
|
|
|
|
## 2. Locked decisions
|
|
|
|
| # | Decision | Why |
|
|
|---|---|---|
|
|
| D19.1 | Driver: **`pg`** (node-postgres) with a `Pool` | Boring, ubiquitous, first-class TS types |
|
|
| D19.2 | **Async `Db` interface** (`query`/`get`/`all`/`run`/`transaction`) with TWO implementations: better-sqlite3 (dev/tests, wraps sync in async) and pg | There is no sync Postgres client for Node; the whole repo layer goes `async/await` once, mechanically. SQLite stays for fast `:memory:` tests — the dual-engine suite *proves* the portable-SQL rule |
|
|
| D19.3 | Paise columns: **`bigint`** in Postgres (`INTEGER` unchanged in SQLite) | int4 overflows at ₹2.15 crore in paise; aggregate SUMs cross that immediately. `pg` returns bigint as string → parse at the adapter edge, still integer paise in the domain |
|
|
| D19.4 | Dates stay **`TEXT` ISO** (`YYYY-MM-DD` / ISO timestamps) | Already the house convention; portable, index-friendly, zero timezone surprises |
|
|
| D19.5 | JSON payloads (`document.payload`, `client.contacts`, …): **`jsonb`** in Postgres, TEXT in SQLite; repos keep JSON.stringify/parse at the edge | jsonb gets us future indexing for free without changing repo code |
|
|
| D19.6 | Migrations: numbered SQL files + a tiny **`schema_migrations`** runner (per engine schema file) | Replaces the PRAGMA-guard `migrate()` + `rebuildTable` gymnastics; Postgres does real `ALTER TABLE` |
|
|
| D19.7 | Placeholders: repos keep `?`; the pg adapter rewrites `?` → `$n` | One SQL dialect in the codebase; the adapter owns the difference |
|
|
| D19.8 | Engine differences behind the adapter: `INSERT OR IGNORE` → `ON CONFLICT DO NOTHING`, nested `db.transaction` → savepoints, `sqlite_master` → `information_schema` | Repo code stays engine-blind |
|
|
| D19.9 | Config: **`DATABASE_URL`** env selects Postgres; absent → SQLite as today | Dev machines keep working with zero setup; prod sets one variable |
|
|
|
|
## 3. Build order (each phase lands with the full suite green)
|
|
|
|
1. **Async refactor, SQLite still underneath** — introduce the `Db` interface, convert
|
|
every `repos-*.ts`, `api.ts`, scheduler, server and test to `async/await` with the
|
|
sqlite implementation behind it. **Zero behavior change**; this is ~70% of the diff
|
|
and is deliberately not mixed with any engine change.
|
|
2. **Postgres schema + migration runner** — `schema.pg.sql` (bigint paise, boolean,
|
|
jsonb, real CHECKs) + `schema_migrations`.
|
|
3. **pg adapter** — Pool, `?`→`$n`, ON CONFLICT mapping, savepoint-nested transactions,
|
|
bigint parsing.
|
|
4. **Dual-engine test run** — full suite on `:memory:` SQLite (default, fast) and on a
|
|
real local Postgres via `TEST_PG=1` + `DATABASE_URL`. Both green before landing.
|
|
5. **Boot + smoke on local Postgres** — server on `DATABASE_URL`, real flows exercised
|
|
(login → quote → issue → payment → PDF → share).
|
|
6. **Prod later**: RDS (Mumbai) when go-live infra is provisioned; same `DATABASE_URL`
|
|
mechanism, then cutover per DEPLOY-HQ.
|
|
|
|
## 4. Environment (this machine)
|
|
|
|
- PostgreSQL 17 installed locally as a Windows service (EDB installer, unattended);
|
|
superuser `postgres`. App role `hq`; databases `hq` (dev runtime) and `hq_test`
|
|
(suite). DBeaver connects to `localhost:5432` for inspection.
|
|
- `DATABASE_URL=postgres://hq:hq_dev@localhost:5432/hq`
|
|
|
|
## 5. Non-goals
|
|
|
|
RDS provisioning (separate infra step at go-live), pgbouncer/replicas, ORM adoption
|
|
(the portable-repo pattern is the point), rewriting any business logic.
|