feat(pwa): move app.moshcode.sh from Turso/libSQL to Postgres via @profullstack/libsql-pg - #551
Merged
Merged
Conversation
postgres:// URLs open @profullstack/libsql-pg (dialect 'sqlite', so the statements this app writes are rewritten per call); file: keeps @libsql/client for development and tests; libsql:// (Turso) is refused at boot. src/migrations-pg/ is the Postgres twin of src/migrations/, same file names, converted with `libsql-pg convert-schema` and reviewed (DEFAULT+CHECK order, the derived-table alias in the team_access view). migrate.mjs picks the set by dialect and sends the Postgres files verbatim on a dialect:'postgres' client, because the SQLite rewriter's converter is not idempotent on Postgres DDL. The root Dockerfile (from ops/dev2-deploy) installs apps/pwa's own dependencies; the .nixpacks image installed only the CLI's. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…q under a row lock `user_id IS NOT ?` is SQLite-only (Postgres has IS NOT NULL/TRUE/FALSE); IS DISTINCT FROM ? works on both. session_output.seq is allocated as MAX(seq)+1 inside the INSERT. That is atomic on SQLite's single writer but not under Postgres READ COMMITTED with a connection per request: a burst of five chunks came back numbered [1,1,2,3,3], and a browser resuming from ?since= skips the repeat for good. On Postgres the owning cli_sessions row is locked FOR UPDATE first; migration 025 makes (session_id, seq) unique as the backstop (the live data has no duplicates). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
scripts/pg-test-preload.mjs creates a throwaway database per test process and points config.db.url at it through a loader hook, so the unchanged suite runs on a real Postgres (PG_TEST_ADMIN_URL=... node --import ./scripts/pg-test-preload.mjs --test). Fixtures that leaned on SQLite: ORDER BY rowid (created_at is already distinct), a device-code seed that relied on INSERT OR REPLACE cascading (an upsert on Postgres), the PRAGMA foreign_keys probe and the raw SQLite migration replay are skipped there. sql-portability gains two guards: the two migration directories must mirror each other, and no `IS NOT ?`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
ThreatCrush Security Scan40 finding(s) in the 46 file(s) this pull request changes. MEDIUM: 40
64 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 8 | **MEDIUM**: 45 | **LOW**: 11Not introduced by this pull request. The full set is in the Security tab.
…and 44 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
The sample admin URL carried a placeholder password, which the ThreatCrush scanner reads as a database credential. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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 changed
app.moshcode.sh (
apps/pwa) moves from Turso/libSQL to Postgres at the code level. Nothing here touches a server or deploys anything.src/db.mjsopens@profullstack/libsql-pg@0.1.2forpostgres://URLs (createClient({ url, dialect: 'sqlite' })): the@libsql/clientsurface over apgpool, with the SQLite idioms this code writes rewritten per statement — so no caller changed. Afile:URL still opens@libsql/client(development, the whole test suite). Fail fast: alibsql://URL throws at import with a message namingDATABASE_URLandDATABASE_AUTH_TOKEN; there is no fallback to Turso.src/migrations-pg/is the Postgres twin ofsrc/migrations/— 25 files, identical names, generated withnpx libsql-pg convert-schemaand reviewed.INTEGER PRIMARY KEY AUTOINCREMENT→ identity (session_output.id,moshpit_tld_log.seq); every otherINTEGERstaysbigint(0/1 flags and millisecond timestamps, as the app writes them);REAL→double precision; partial indexes, CHECKs and the two views kept. Hand fixes: the converter emitsDEFAULT 'x' (CHECK) (…)forDEFAULT 'x' CHECK (…)(010, 017, 023), and theteam_accessview's derived table needed an alias.src/migrate.mjspicks the directory by dialect and sends the Postgres files verbatim on adialect: 'postgres'client (the SQLite rewriter's schema converter is not idempotent on Postgres DDL). The_migrationsledger is keyed by file name, so a database whose ledger is copied across is already up to date.Dockerfile(the oneops/dev2-deployalready runs with) installsapps/pwa's own dependencies withnpm ci --omit=dev; bothpackage-lock.jsonandpnpm-lock.yamlinapps/pwacarry the new dependency.@libsql/clientstays, forfile:..env.exampleupdated;DATABASE_AUTH_TOKENis no longer read.Idioms fixed by hand
src/moshpit.mjs(listTldsNotOwnedBy, countTldsNotOwnedBy, searchScope):user_id IS NOT ?→user_id IS DISTINCT FROM ?(Postgres only has IS NOT NULL/TRUE/FALSE; SQLite ≥ 3.39 accepts both).src/routes/sessions.mjs: a real Postgres bug the suite caught.session_output.seqisMAX(seq)+1inside the INSERT — atomic on SQLite's single writer, not under READ COMMITTED with a connection per request: a burst of five chunks came back[1,1,2,3,3], and a browser resuming from?since=skips the repeat for good. On Postgres the owningcli_sessionsrow is now lockedFOR UPDATEin a short transaction before the insert (SQLite path unchanged); migration 025 makes(session_id, seq)unique as the backstop. The live Turso data has 0 duplicate pairs.INSERT OR IGNORE/INSERT OR REPLACE(moshpit_alias_exempt, moshpit_tlds, moshpit_leases, mcp_oauth_grants, organization_members, session_team_shares),PRAGMA(no-op),strftime. Probed against Postgres 17:INSERT … SELECT ?,… WHERE (SELECT SUM(delta)…) >= ?(credits),COALESCE(?, col)updates,ORDER BY price_usd IS NULL … LIMIT ? OFFSET ?,db.batch,db.transaction+tx.close(),rowsAffectedon ON CONFLICT DO NOTHING. Norowid,json_each,COLLATE NOCASE,GLOB,last_insert_rowid(),?NNN,LIMIT -1, FTS,printf/typeofin this app.LIKEinputs are lowercased before they reach SQL (lib/moshpit-search.mjs), so Postgres's case-sensitive LIKE matches the same rows.Tests
node --testinapps/pwa— 777 pass, 0 fail (775 on main + 2 new guards insql-portability: the two migration directories must mirror each other; noIS NOT ?).PG_TEST_ADMIN_URL=postgres://… node --import ./scripts/pg-test-preload.mjs --testruns the same suite on a real Postgres 17 (a throwaway database per test process,config.db.urlredirected by a loader hook) — 775 pass, 0 fail, 2 skipped (the raw SQLite migration replay inmcp-migration, thePRAGMA foreign_keysprobe). Before the fixes above it was 759/18.node --test: 3512 pass, 0 fail, 4 skipped (baseline 3510/0/4).ORDER BY rowiddropped wherecreated_atis already distinct; the device-code seed clears its stale row instead of relying onINSERT OR REPLACE INTO userscascading (an upsert on Postgres, a delete+insert on SQLite).Cutover recipe (coordinator)
Identity sequences (
session_output.idmax 5,205,921;moshpit_tld_log.seqmax 19,539) are reset by the copier'ssetval. RunANALYZEafter the load.🤖 Generated with Claude Code