docs: Postgres portability — Kysely spike (compile-only) - #1070
Draft
lane711 wants to merge 2 commits into
Draft
Conversation
Re-measures the SonicJS -> Postgres effort. Ports the two chokepoint paths
(read DocumentRepository.list, write documents.saveDraft) to Kysely and
proves one TypeScript builder compiles to correct SQLite (D1) AND Postgres
SQL. Compile-only via DummyDriver + per-dialect QueryCompiler — no DB, no
production code touched.
Findings:
- `?` -> `$N` placeholder rewrite is free (dialect compiler).
- 0/1-int vs real boolean collapses to one per-dialect mapper.
- The R5 27-bind INSERT bind-counting class of bug disappears with named
values({col}); COALESCE(MAX)+1 version subquery (R6) stays in SQL.
- json_extract is absent from both chokepoints; the 38 dialect-divergent
sites are a bounded ~20-file set.
- Kysely compiles to D1 / better-sqlite3 / libSQL / Postgres from one layer.
Revised estimate: ~5-9 weeks to full Postgres parity + dual-dialect test
matrix, at roughly half the defect risk of a raw-SQL port.
Adds docs/ai/plans/postgres-portability-poc/ (runnable spike + README) and
links it from portability-analysis.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ordered go/no-go plan to add Postgres as a runtime DB backend alongside D1/SQLite. Core strategy: keep D1Database as the universal seam (a D1-compatible sqlite driver already exists) and add one postgres-driver + the bounded residual dialect SQL. Three simplifying decisions shrink the surface: - PG keeps 0/1 integer boolean columns -> the hundreds of `= 1` comparisons need no change. - q_* stay real STORED generated columns -> DocumentRepository.list() (read chokepoint) needs no change. - postgres-driver rewrites ?->$N internally -> the 743 .prepare sites run unchanged; only the ~20 json_extract files + 1 DDL generator + 21 INSERT OR IGNORE sites are touched. 6 phases + optional Kysely quality track, ~7-10 eng-weeks to GA with a dual-dialect (pglite) test matrix. Risk register + per-file targets enumerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
📋 Added Core strategy: keep Three decisions shrink the surface to near-nothing:
6 phases (driver → schema/migrations → residual SQL → auth → dual-dialect |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Re-measures the SonicJS → Postgres port effort with a compile-only Kysely spike. Ports the two data-access chokepoints to Kysely and proves one TypeScript builder compiles to correct SQLite (D1) and Postgres SQL:
DocumentRepository.list()(the R4 read chokepoint)documents.saveDraft()(the hardest write:.batch(),COALESCE(MAX)+1version subquery, the R5 27-bind INSERT, pruneDELETE … NOT IN (… LIMIT ?))Compile-only via Kysely
DummyDriver+ per-dialectQueryCompiler. No DB, no production code touched. Docs + a runnable spike only.Findings
?→$Nplaceholder rewrite is free (dialect compiler)0/1-int vs realbooleancollapses to one per-dialect mapper, not 300 edits'd.'alias string-prefix hack inlist()is deleted (Kysely resolves aliases)values({col});COALESCE(MAX)+1(R6) stays in SQL.batch()→.transaction();kysely-d1maps it back to D1 batch on Workersjson_extractis absent from both chokepoints; the 38 dialect-divergent sites are a bounded ~20-file setRevised estimate
~5–9 weeks to full Postgres parity + a dual-dialect test matrix, at ~half the defect risk of a raw hand-written SQL port. Kysely doesn't cut the week-count much — it changes the risk profile (typed, compiler-checked, dual-dialect).
Files
docs/ai/plans/postgres-portability-poc/— runnable spike (npm i && node list-read.mjs && node saveDraft-write.mjs) + README with full measurementsdocs/ai/plans/portability-analysis.md— status header + link to the spikeNot in scope
No production code, no
packages/core/migrations/*changes, no dependency added to the app (spike has its own isolatedpackage.json). This is an RFC/measurement to decide whether to pursue Tier 2 (Postgres).🤖 Generated with Claude Code