feat: postgres#741
Conversation
Add a 'Database engine' category (SQLite default + PostgreSQL) so Postgres can be chosen standalone or alongside Drizzle/Kysely. Add BatiSet.hasPostgres, include postgres in hasDatabase, and make hasD1 exclude the Postgres case (Cloudflare+Postgres talks to Postgres over Workers, not D1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the sqlite boilerplate for a raw postgres.js client: db.ts singleton, example queries + schema/migrate script, $package.json (postgres/dotenv/tsx + postgres:migrate), $TODO.md (bring-your-own server note), exports/typesVersions, and nextSteps. Active only when no ORM owns the engine. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
db.ts gains a dbPostgres() branch (drizzle-orm/postgres-js + postgres client); schema picks pgTable vs sqliteTable at scaffold time; drizzle.config dialect switches to postgresql; queries bridge dialects via stripped BATI.Any casts; $package.json swaps better-sqlite3 for postgres when the engine is Postgres. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
db.ts gains dbKyselyPostgres() using kysely-postgres-js' PostgresJSDialect; the migration creates a serial id on Postgres; migrate.ts and queries select the Postgres client; $package.json swaps better-sqlite3 for postgres + kysely-postgres-js. All engines stay typed as Kysely<Database>, so no casts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
db-middleware selects dbPostgres/dbKyselyPostgres/raw pgDb (postgres branches first so Drizzle/Kysely+Postgres win over the SQLite arms); shared-db env() emits a postgresql:// DATABASE_URL (compose/dockerfile point at the postgres service host); +data and the telefunc/trpc/ts-rest/shared-server handlers gain Postgres arms. Declare @batijs/postgres workspace dep on all importers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…igration
docker-compose.yml gains a healthchecked postgres:17-alpine service, a
postgres_data volume and app.depends_on (service_healthy), all guarded by
BATI.has("postgres"); the sqlite bind volume is now gated on
hasDatabase && !postgres. $Dockerfile.ts runs postgres:migrate at startup for
the standalone client (Drizzle/Kysely already covered by their migrate steps).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rules: postgres requires a Server; postgres conflicts with the raw SQLite tool and with Prisma; postgres added to the StackBlitz-incompat info. Messages added to the CLI and website maps. rules.local.spec asserts the new failures. E2e: a postgres matrix (standalone / drizzle / kysely on Postgres) with a postgres:migrate beforeAll and todo-persistence + TODO.md assertions; CI starts a postgres:17 service for --postgres jobs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…no unreachable return Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add Postgres arms to the remaining db-type maps missed in step 5 (telefunc Telefunc.Context + telefunc-handler cast, shared-todo PageContextServer); give the postgres boilerplate node types so process.env resolves (postgres.js doesn't pull in @types/node like better-sqlite3 does). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…late A YAML comment on the first child of a mapping attaches to the parent node, so the inline guards on the named-volume entries were never evaluated (both volumes leaked into output). Keep only the key-level-guarded sqlite_data volume in the template and append the postgres_data volume from $docker-compose.yml.ts when postgres is selected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughThis PR adds PostgreSQL as a production-ready database option alongside SQLite by introducing a new ChangesPostgreSQL Support Infrastructure
ORM PostgreSQL Adapters
Shared Infrastructure and RPC Frameworks
Docker, CI, and Test Infrastructure
Utilities and User Interface
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
bati | c8d62a8 | Commit Preview URL Branch Preview URL |
Jun 03 2026, 10:10 AM |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A comment before the first entry of a block mapping is parsed onto the map node's commentBefore rather than the first key, so transformYaml never evaluated it. Hoist it onto the first key so the Pair handler resolves it like any other entry guard. This lets docker-compose declare the sqlite_data / postgres_data volumes with plain magic comments again, so the $docker-compose.yml.ts workaround is reverted. Adds a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove the unused connectionString parameter from dbPostgres/dbKyselyPostgres
and the standalone postgres db() (no caller ever passed one; it was
speculative Cloudflare/Hyperdrive compat that was never wired).
- Drop the JSDoc wall on the standalone client so it mirrors sqlite's db().
- Collapse the duplicated cast in drizzle getAllTodos into one query + a
folding ternary.
- Remove the BatiSet.hasPostgres getter (a one-use alias for has("postgres"),
unlike hasD1/hasDatabase which encapsulate derived logic).
- Trim duplicated/over-claiming comments.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ategories Rename the 'Database' category to 'ORM / Query builder' (Drizzle/Kysely/Prisma) and 'Database engine' to 'Database' (SQLite/PostgreSQL). Move SQLite into the engine category and drop the invisible sqlite-engine default — the engine is now an explicit choice. BatiSet derives hasDatabase/hasOrm from the category sets and hasD1 = cloudflare && sqlite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add ERROR_ORM_R_DATABASE (drizzle/kysely/prisma each require a Database) and drop ERROR_POSTGRES_X_PRISMA (Prisma + Postgres is now valid). Update the CLI and website message maps, and rules.local.spec (assert ORM-without-engine and two-engines fail). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
drizzle/kysely dbSqlite/dbKysely now guard on BATI.has("sqlite") && !hasD1
instead of the implicit !postgres && !hasD1. The raw sqlite/postgres client
boilerplates activate on their engine && !hasOrm (no ORM/query builder owns it).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With SQLite now an explicit flag, drizzle/kysely + sqlite also set has("sqlite"),
so the raw-engine arms (sqlite/postgres/D1) must exclude any ORM via !hasOrm —
otherwise drizzle+sqlite would resolve to the raw better-sqlite3 client. Applied
across the db-middleware ternary + context type, the +data branches and the
telefunc/trpc/ts-rest/shared-server handlers (type maps + runtime). shared-db
opts Prisma out of the demo entirely (hasDatabase && !prisma).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prisma now requires an engine; its DATABASE_URL defaults to a postgresql:// string or file:./dev.db per the chosen engine, and 'prisma init' uses the matching --datasource-provider. Prisma stays demo-less (no Bati todo wiring). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an 'orm' axis. Rework FRAMEWORK+SERVER+DATA to express engine×tool (sqlite/postgres × drizzle/kysely/raw, D1 via cloudflare+sqlite, both engines under dokploy) instead of the old implicit-sqlite db list, and reorder beforeAll to match the tool before the raw-sqlite engine. Pair Prisma with an engine (+server). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
prisma+engine sets the engine flag but has no Bati client, so: - shared-todo PageContextServer.db augmentation gates on hasDatabase && !prisma - telefunc-handler db-context map gains a _ fallback (matching the other handlers) - d1-sqlite (raw D1 queries) gates on hasD1 && !hasOrm, not hasD1 && sqlite, so drizzle/kysely + sqlite + cloudflare no longer pull in the raw-D1 client. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Unify the three context-type augmentations (shared-db, shared-todo, telefunc) to gate on hasDatabase && !prisma — they describe Bati's managed db, which Prisma doesn't have. Drops telefunc's now-redundant _ fallback. Handlers keep their _ fallback (they're gated by server/data-lib, so the no-db case is real). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary by CodeRabbit
New Features
Bug Fixes
Chores