The Dev database under load with a concurrent db update; the e2e job's database; the loop in the docs - #1242
Conversation
… update Spec #1125's Testing Decisions call for the app under create/edit/delete load with a concurrent second-process `db update` on the single-connection binding. The staged-reconcile test ran the second process but with no load, and the load is what the binding exists for: `pglite-socket` multiplexes every connection onto one PGlite backend, so a pool corrupts the session state a single backend shares (ADR-0063, 2026-09-03 amendment). `dev-database-concurrency.test.ts` runs a real `opensaas dev` loop whose app drives transactions, `.include()` reads, edits and deletes through the generated bundle's own client, in batches that keep going while three second-process `opensaas db update` runs complete. Every cycle verifies what it reads back, so a corrupted session shows up as a wrong answer and not only as a thrown error. Raised to `max: 10`, the pool the runtime binds for the Dev database fails it: 7020 of 7488 cycles, with `portal "" does not exist`, `unnamed prepared statement does not exist`, committed rows unreadable, and rows read back carrying another worker's title — and all three `db update` runs exiting non-zero on `CONTRACT.MARKER_READ_FAILED` / `ECONNRESET`. A single sequential request stays clean either way, which is why the load is the test. The `e2e` job resolves `DATABASE_URL` from a pgvector-capable Postgres 17 service container the way the `test` job does, so CI exercises the escape and a local `pnpm test:e2e`, setting nothing, exercises the Dev database — story 25. PRs into `main` keep the SQLite string that branch still uses. The job's `base_ref == 'main'` gate is untouched: its steps stand on the SQLite examples and the Prisma 7 scaffold, which cannot run here until #1129 converts them. `installation.md` describes the shipped loop — install then generate, no `db:push`, `pnpm dev` for the database, `pnpm db:update` for a destructive change, `DATABASE_URL` as the escape. The four other how-to and reference pages that still told the reader to `pnpm db:push` after `pnpm generate` follow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 32428770 | Triggered | Generic Password | 0a03fed | .github/workflows/test.yml | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Coverage Report for Core Package Coverage (./packages/core)
File CoverageNo changed files found. |
Coverage Report for UI Package Coverage (./packages/ui)
File CoverageNo changed files found. |
Coverage Report for CLI Package Coverage (./packages/cli)
File CoverageNo changed files found. |
Coverage Report for Auth Package Coverage (./packages/auth)
File CoverageNo changed files found. |
Coverage Report for Storage Package Coverage (./packages/storage)
File CoverageNo changed files found. |
Coverage Report for RAG Package Coverage (./packages/rag)
File CoverageNo changed files found. |
Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)
File CoverageNo changed files found. |
Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)
File CoverageNo changed files found. |
| # into `main` keep the SQLite string that branch's examples still use, the | ||
| # same split the `test` job makes. | ||
| env: | ||
| DATABASE_URL: ${{ github.base_ref == 'main' && 'file:./dev.db' || 'postgres://postgres:postgres@localhost:5432/postgres' }} |
There was a problem hiding this comment.
The ternary can only ever take the SQLite branch, so this doesn't put CI on the container.
The job itself is gated if: github.base_ref == 'main' (line 149). Every run that reaches this env: therefore has base_ref == 'main' true, so DATABASE_URL is always file:./dev.db and the postgres://... branch — and the service container added below — is unreachable.
Two consequences:
- Story 25's "CI on the container" is not achieved by this change; the job stays on the escape branch with a SQLite string.
- It reinstates exactly what CI on the escape for every job, the nightly cold clone, and e2e through the lookup #1227 removed. The comment this hunk replaces said a SQLite URL "is a value the Prisma 8 toolchain cannot use at all", and the run where that bites is the one case the gate lets through with this branch's code: the eventual
prisma-8→mainintegration PR, wherebase_ref == 'main', the job runs, Prisma 8 code is checked out, andDATABASE_URLisfile:./dev.db. Before this PR that run used the in-process Dev database and worked.
If the intent is "container in CI, sidecar locally", the URL needs a condition that is not the same one that gates the job (e.g. set the Postgres URL unconditionally and let the main gate keep the SQLite-example steps out), otherwise the container and the || branch are dead until the gate moves.
| # pack-declaring contract's `CREATE EXTENSION vector` has something to | ||
| # install (ADR-0063, ADR-0065). Pinned by digest for the reason given | ||
| # there; bump both together. | ||
| services: |
There was a problem hiding this comment.
This service starts on every e2e run and can never receive a connection.
services: are not conditional — GitHub starts them whenever the job runs. Because the job is gated on base_ref == 'main' and the DATABASE_URL above resolves to file:./dev.db under exactly that condition, no step in this job will ever dial localhost:5432. Net effect today: an extra image pull plus the pg_isready health wait added to every e2e run, for a database nothing opens.
Worth either deferring the service to the same commit that lifts the job gate, or fixing the URL expression so the container is actually used.
| ```bash | ||
| pnpm generate # regenerate schema, types, and context | ||
| pnpm db:push # apply schema changes to the database | ||
| pnpm db:update |
There was a problem hiding this comment.
pnpm db:update on its own will not apply the parked destructive change.
The section above says the loop parks a data-dropping change and this is how you approve it, but the approval needs a consent token:
examples/starter/package.json:"db:update": "opensaas db update"— no--confirm.packages/cli/src/commands/db.tsforwardsoptions.confirm ?? []straight through.planDatabaseUpdate()runs the Prisma CLI with--no-interactive, so with no token Prisma refuses the destructive plan; the loop repliesThe database is unchanged and nothing was promoted.anddb updateexits 1.
The loop's own parked-change message says it: run `pnpm db:update` (`opensaas db update --confirm postgres`) (packages/cli/src/commands/dev.ts:218). A reader following this page verbatim gets a failure at the one step it exists to explain.
| pnpm db:update | |
| pnpm db:update --confirm postgres |
|
|
||
| - **`prisma/schema.prisma`** — the Prisma schema | ||
| - **`prisma.config.ts`** — Prisma CLI configuration (datasource URL for db push/migrations) | ||
| - **`prisma/contract.json`** and **`prisma/contract.d.ts`** — the emitted schema contract; commit both |
There was a problem hiding this comment.
The list leaves out prisma/contract.ts, and this bullet is what tells the reader what to commit.
opensaas generate emits the Contract module itself at prisma/contract.ts (DEFAULT_CONTRACT_MODULE in packages/cli/src/generator/output-paths.ts); contract.json / contract.d.ts are the two artifacts prisma contract emit writes beside it, and prisma.config.ts's contract field points at the module, not at the JSON.
Since the bullet says "commit both", a reader treating this list as the tracked set omits prisma/contract.ts — and then every prisma CLI command on a fresh clone fails on a missing contract module.
| pnpm dev | ||
| ``` | ||
|
|
||
| `pnpm dev` is `opensaas dev`: it starts a local Postgres the stack runs |
There was a problem hiding this comment.
Stale prerequisite one screen above this new paragraph: line 7 still says "Node.js 18+".
The loop this hunk describes depends on Node natively stripping types from the emitted .ts bundle (.opensaas/context.ts, imported with no loader — see packages/cli/tests/bundle-node-load.test.ts), and packages/cli/package.json declares engines.node: ">=22.18.0". A reader on Node 18 or 20 who follows this page fails at exactly the pnpm dev step being introduced here, with no hint from the prerequisites that their Node is the cause. Worth bumping the prerequisite in the same pass that brings the page up to date.
| // overlap whichever finishes first — a sized load that ended before the | ||
| // first `db update` connected would assert nothing about the two together. | ||
| const observed: LoadResult = { cycles: 0, failures: [] } | ||
| while (!updatesFinished) { |
There was a problem hiding this comment.
The only exit condition is updatesFinished, and nothing bounds it — a stalled db update degrades into a bare 600 s timeout with none of the diagnostics.
runDbUpdate never times out, and requestDatabaseUpdate (packages/cli/src/dev/control.ts) waits on the control socket with no deadline of its own. Two concrete ways this bites:
- The loop is alive but slow to reply. It is the process this test deliberately saturates, and
dev-database.tsdocuments that "a connection holding an open transaction holds the whole query queue". While the update is pending the driver loop keeps issuing more load, which is precisely the traffic the reconcile has to queue behind — the load stops only when the update finishes, and the update is racing the load. Any real stall here surfaces asTest timed out in 600000mswith noloop.output(), no update output, and no failure list — the diagnostics every other assertion in this test is careful to attach. - The spawn rejects (
errorwithoutclose).secondProcessrejects,updatesFinishedstaysfalse,await secondProcesson line 259 is never reached, and vitest reports an unhandled rejection instead of the failure.
Suggest a wall-clock deadline on the while loop (fail with loop.output() and the update outputs when it trips) plus a kill timer inside runDbUpdate, matching the SIGKILL timer startLoop and dev-loop.test.ts already use.
| const CONNECTION_VARIABLES = ['DATABASE_URL', 'DIRECT_DATABASE_URL'] as const | ||
|
|
||
| const ROUNDS = 4 | ||
| const CONCURRENCY = 16 |
There was a problem hiding this comment.
16-wide against a max: 1 pool puts the batch's tail right up against the pool's own 20 s checkout ceiling.
resolveRuntimeConnection binds the Dev database to new Pool({ max: 1, connectionTimeoutMillis: 20_000 }) (packages/core/src/db/client.ts), and pg-pool applies connectionTimeoutMillis to queued acquisitions, not just to dialling. With CONCURRENCY = 16 every cycle's ~12 statements serialize through the one connection, so the last worker in a round waits roughly the whole round — and the round is competing with a db update whose reconcile holds PGlite's single query queue in the middle of it.
CI is the bad case: dev-loop.test.ts and staged-reconcile.test.ts each host their own PGlite in parallel vitest workers on the same 2-core runner, and this file drives batches continuously for as long as three db update runs take. A round that crosses 20 s makes the tail workers throw timeout exceeded when trying to connect, which cycle()'s catch records as a cycle failure — a red build that reads exactly like the session corruption this test exists to detect, on a machine where nothing was corrupted.
Same total load with a bounded tail: drop CONCURRENCY (4–8) and raise ROUNDS to compensate.
| return { | ||
| output: () => output, | ||
| stop: async () => { | ||
| if (child.exitCode !== null) return |
There was a problem hiding this comment.
stop() can never resolve for a child that died from a signal, so afterAll hangs to its hook timeout.
child.exitCode is null when a process was terminated by a signal (signalCode carries it instead), so this guard does not return early for a loop that was SIGKILLed — by the OOM killer, or by the 5 s timer on line 170 in an earlier stop(). close has already fired, so child.once('close', …) never runs; kill('SIGINT') on a dead child is a no-op; and the timer only issues another no-op SIGKILL without resolving. The promise stays pending, and afterAll fails on vitest's 10 s hook timeout — layered on top of whatever actually killed the loop, which is the failure you wanted to read.
| if (child.exitCode !== null) return | |
| if (child.exitCode !== null || child.signalCode !== null) return |
Resolving from the timer as well (rather than only killing) would close the remaining case.
| observed.failures.slice(0, 10), | ||
| `${observed.failures.length} of ${observed.cycles} cycles failed\n\n${loop.output()}`, | ||
| ).toEqual([]) | ||
| expect(observed.cycles).toBeGreaterThanOrEqual(ROUNDS * CONCURRENCY) |
There was a problem hiding this comment.
This assertion cannot fail, and nothing else pins the property the test is named for.
The while loop always completes at least one full batch, and a batch is exactly ROUNDS * CONCURRENCY cycles, so observed.cycles >= ROUNDS * CONCURRENCY holds unconditionally — including in the degenerate case the batching comment above was written to rule out. If opensaas db update ever starts failing fast (a regression in the control channel, say), all three runs finish before the first batch returns, the exit-code assertions catch the failure — but had they been non-zero for a benign reason, the test would pass green having exercised no overlap at all.
Cheap fix: stamp a timestamp when the first db update completes and assert at least one batch's window spanned it, or count the batches issued while updatesFinished was false and assert > 1.
borisno2
left a comment
There was a problem hiding this comment.
Code review — high effort
Verdict: REQUEST CHANGES. (Same-account authorship blocks a formal review event, so this is a Comment review; the verdict is the one above.)
The concurrency test is a real test, not a vacuous one — the 7020/7488 failure rate at max: 10 is convincing evidence it discriminates, the fixture's ORM usage matches Prisma 8's surface, and tx.unsafe is correctly scoped so max: 1 can't self-deadlock. But it has three flakiness/vacuity holes, and the e2e workflow change does not do what its comment says it does. 9 inline comments posted; ordered by severity here.
Blocking
-
.github/workflows/test.yml:160— thepostgres://arm of the ternary is unreachable. The job is itself gatedif: github.base_ref == 'main'(line 149), sogithub.base_ref == 'main'is always true inside it andDATABASE_URLis alwaysfile:./dev.db. Story 25's "CI on the container" is not achieved. Worse, the run where this bites is the eventualprisma-8→mainPR: the gate passes, Prisma 8 code checks out, and it gets handed a SQLite URL — exactly what #1227 removed. Either drop the gate and keep the ternary, or drop the ternary and set the container URL unconditionally; the two cannot both stand.On the ternary idiom itself: the
&&/||form is correct here, because the middle operand'file:./dev.db'is a non-empty string and therefore truthy. The falsy-middle-operand pitfall does not apply. The service container is pinned by digest and itsPOSTGRES_PASSWORD/port match both the expression and thetestjob's service exactly — that part is clean. -
.github/workflows/test.yml:169— consequence of (1):services:are unconditional, so every e2e run pulls the pgvector image and waits on its health check for a database nothing will ever dial. Pure added CI minutes. -
dev-database-concurrency.test.ts:264— the headline assertion cannot fail, and nothing asserts overlap.observed.cycles >= ROUNDS * CONCURRENCYcompares a batch against its own exact size. More importantly, nothing in the test asserts that anydb updateactually overlapped the load — the degenerate case where all three updates land before or after the driver loop would still pass green. Given this spec already shipped two tests that passed without testing anything, this needs a positive assertion: record the cycle counter at eachdb updatestart/finish and assert a non-trivial number of cycles fell inside those windows. -
dev-database-concurrency.test.ts:253— no timeout on the load loop's only exit condition. The driver's sole exit isupdatesFinished, and neitherrunDbUpdatenorrequestDatabaseUpdatehas a timeout. Since the load only stops when the update finishes, and the update queues behind that load on PGlite's single queue, a stall degrades into the bare 600s vitest timeout with noloop.output()— an opaque red build. A spawn rejection also leavessecondProcessunawaited (unhandled rejection). -
dev-database-concurrency.test.ts:166—stop()can hang forever. The early return is onexitCode !== null; a signal-killed child hasexitCode === nullwithclosealready fired, so the promise never settles and the 5s timer only re-kills. That hangsafterAllinto vitest's 10s hook timeout, masking whatever actually failed — and is exactly the "leaves a sidecar behind" shape.
Flakiness risk (the timeout question)
:36—CONCURRENCY = 16against amax: 1pool with a 20sconnectionTimeoutMillis. That timeout applies to queued acquisitions, so the 16th worker of a round waits nearly the whole round, and adb updateholds the queue mid-round. On a CI runner also hosting two other PGlite test files, a round crossing 20s surfaces astimeout exceeded when trying to connect, recorded as a cycle failure — a red build that looks like the corruption the test hunts. This is precisely "a timeout that fails under CI load rather than under real corruption". Either raiseconnectionTimeoutMillisfor the fixture or classify connect-timeouts separately from data-corruption failures.
On runtime and the constants: ~12s local → ~1min CI is a defensible extrapolation and proportionate for the risk this covers. But CONCURRENCY = 16 and the cycle counts read as arbitrary — the PR body justifies 16 by "it fails at max: 10", which argues the pool, not the concurrency. A sentence tying 16 to the queue depth it's meant to produce would make the number reviewable; as written a future reader has no basis to change it.
Docs
how-to/installation.md:97— factually wrong.pnpm db:updatealone will not apply a parked destructive change. The starter script isopensaas db updatewith no--confirm,db.tsforwards an empty token list, andplanDatabaseUpdateruns Prisma--no-interactive— so the loop prints "The database is unchanged and nothing was promoted" and exits 1. The loop's own message says--confirm postgres(dev.ts:218). The additive-vs-destructive story is the one thing these five files exist to tell, and this page tells it wrong.how-to/installation.md:60— the emitted-artifact list omitsprisma/contract.ts, whichprisma.config.tspoints at. The bullet says "commit both", so a reader treating this as the tracked set breaks the Prisma CLI on a fresh clone.how-to/installation.md:33— prerequisites still say "Node.js 18+", but the loop being documented needs native type stripping for the.tsbundle;packages/clideclaresengines.node >=22.18.0. A Node 18/20 reader fails at exactly the step this page adds.
On removing the pnpm db:studio line: removing rather than verifying was the wrong call. Studio is the reader's only way to see whether an additive change actually landed, which is the claim the surrounding prose now makes. Verify it and restore it, or say explicitly why it's gone.
On scope (quick-start.md, concepts/generators.md left to #1129): defensible, and I'd keep the boundary — a half-conversion is worse. But it is not free: a reader moving from installation.md (no db:push) to quick-start.md (still db:push) gets two stories. Drop a one-line "this page still describes the Prisma 7 loop; see #1129" marker on each un-converted page so the contradiction is signposted rather than discovered.
Checked and clean
Fixture ORM usage matches Prisma 8 (where().include().first(), where().update(), where().delete() — ADR-0041, #1068 spike); tx.unsafe correctly bound in runTransactionBody; packages/cli/tests/tmp-*/ is gitignored; promotion moves files so it cannot clobber .opensaas/dev-db; no db:push remains anywhere in docs/content; both pgvector services identically pinned and credentialed.
Repo rules: no any and no type casting in the diff. Comments are load-bearing (the pglite-socket single-backend multiplexing note is a legitimate external constraint) — except the e2e job comment at :153-158, which now describes behaviour the gate makes impossible; fix it with finding (1). No file under .changeset/ was added, modified, or deleted, and the no-changeset claim holds: packages/cli/tests/ ships nothing to consumers.
…e container The completion assertion compared a batch count to its own size, so it held unconditionally — and nothing asserted the load ever met a `db update`. The app now keeps a counter of cycles that finished clean and serves it at `/completed`; the test samples it either side of each `db update` child and requires a round's worth of cycles to have completed while one was alive. Constructed without overlap (updates awaited first, then one full batch) that assertion fails at 0 while the old `cycles >= ROUNDS * CONCURRENCY` still passes. `CONCURRENCY` drops to 8 and `ROUNDS` rises to 8: the same total load, but the tail worker of a round no longer sits near pg-pool's 20 s queued-checkout ceiling on the `max: 1` binding. A checkout timeout is now recorded apart from the failures, so a loaded runner cannot report itself as the session corruption this test hunts. Lifecycles: the load loop takes a wall-clock deadline and each batch an abort signal, both failing with the loop output and every `db update`'s output; `runDbUpdate` kills a stalled child and resolves on `error` rather than rejecting; the second process cannot leave an unhandled rejection or a driver loop with no exit; and `stop()` treats a signal-killed child (`exitCode === null`, `signalCode` set) as already gone and resolves from its own timer. The `e2e` job's `DATABASE_URL` ternary was keyed on the same condition that gates the job, so it always yielded the SQLite string and the pgvector service started for a database nothing dialled. Reverted to no `DATABASE_URL` — story 25's container belongs in the commit that lifts the gate, which is #1129, and the comment now says so rather than describing a split that cannot happen. `installation.md`: `pnpm db:update` alone is refused on a parked destructive change (`CLI.CONSENT_REQUIRED`, exit 1), so the page says `--confirm postgres` and what the token is; `prisma/contract.ts` joins the emitted-artifact list; the prerequisite follows `engines.node >= 22.18.0`. `prisma studio` is not a command in Prisma 8 rc.12 (`CLI.UNKNOWN_COMMAND`), so the removed `db:studio` line is replaced by the Admin UI, which is how a reader sees an additive change land. `quick-start.md` and `concepts/generators.md` are signposted as still describing the pre-Prisma-8 flow pending #1129. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Addressed in f313b5c. Per finding: 3 (blocking) — the completion assertion cannot fail, and nothing asserts overlapThe app now keeps a counter of cycles that finished clean and serves it at Proven, not assumed:
1 + 2 (blocking) — the unreachable
|
Verification of
|
Implements #1238. Part of #1125.
Closes the three gaps the QA acceptance pass on #1125 returned FAIL on.
1. The concurrency test (blocking)
#1125's Testing Decisions require "the app under create/edit/delete load with a concurrent second-process
db updateon the single-connection binding." Nothing did this —staged-reconcile.test.tsruns the second process, but with no load, and the load is the whole point.packages/cli/tests/dev-database-concurrency.test.tsruns a realopensaas devloop over a new two-list fixture. Its app drives create/edit/delete through the generated bundle's own client — so the connection under test is the oneresolveRuntimeConnectionbinds, not a hand-rolled one — in the mix ADR-0063's verification set used:context.transactionwriting both sides of a relation, a.include()read back, an edit, a re-read, a delete, and a confirmation the row is gone. Every step verifies its own answer, so a corrupted session shows up as a wrong result and not only as a thrown error.The load runs in batches that keep going until three second-process
opensaas db updateruns against the same sidecar have completed, so the two overlap whichever finishes first rather than relying on a fixed size landing in the right window.It discriminates. With
packages/core/src/db/client.ts'smax: 1raised tomax: 10and core rebuilt, the test fails: 7020 of 7488 cycles, reporting exactly the failures ADR-0063's amendment documents —portal "" does not existunnamed prepared statement does not existthe committed author was not readable(a committed row invisible to the next statement)read back "note <another worker's tag>"(silent cross-talk between sessions)— and all three
db updateruns exiting non-zero onCONTRACT.MARKER_READ_FAILED(bind message supplies 2 parameters, but prepared statement "" requires 1) andDRIVER.CONNECTION_FAILED(read ECONNRESET). A single sequential request stays clean under the raised pool, which is why the load — not a smoke check — is the test.client.tsis reverted; nothing in this PR touches it.Runtime: ~12 s locally (~4 s of it load), alongside
dev-loop.test.ts's ~24 s locally.2. The
e2ejob's database#1227 removed
DATABASE_URLfrom thee2ejob, which put CI on the Dev-database branch — the opposite of story 25 ("local e2e runs on the Dev database and CI on the container"). The job now resolvesDATABASE_URLfrom a pgvector-capable Postgres 17 service container, using the samebase_ref == 'main'expression and the same digest-pinned image thetestjob uses, so PRs intomainkeep the SQLite string that branch's examples still need.On the
if: github.base_ref == 'main'gate: it should not change yet, and this PR does not change it. Every step in the job stands on the SQLite examples and the Prisma 7 scaffold — the scaffold guard already skips itself against #1129, the rate-limit / OAuth-cascade / credential-deny guards run against SQLite, andpnpm test:e2ebuildsexamples/starter-auth, which cannot build on this branch. Lifting the gate now would add a job that cannot pass. It should be lifted with #1129 / build spec 9, when the examples convert; the comment above the job already says so.3. Stale docs
docs/content/how-to/installation.mdnow describes the shipped loop: install then generate (nodb:push),pnpm devbringing the database up, the watcher applying additive changes without a restart,pnpm db:updatefor a destructive one, andDATABASE_URLas the escape rather than "switching to PostgreSQL". The emitted-artifact list is the contract pair rather thanschema.prisma.The sweep of its neighbours found four more
pnpm generate+pnpm db:pushblocks, all of which now say what applies the change:how-to/authentication.md,how-to/rag.md,reference/tiptap.md(two) andreference/rag.md. Nodb:pushremains indocs/contentoutside the Keystone-migration guides, which are about the reader's own existing Prisma.Deliberately left alone:
tutorials/quick-start.mdandconcepts/generators.mdstill carry a wider pre-Prisma-8 description (aprismaClientConstructor, a SQLite adapter,schema.prisma). Converting them is the examples' conversion, #1129 — half-converting them would leave an incoherent page.Verification
pnpm lint— 0 errors (2 pre-existing warnings),pnpm format,pnpm manypkg fixcleanpnpm buildgreen; CLI + core suites green both withDATABASE_URLunset (Dev database) and set to a real pgvector Postgres 17: core 78 files / 1564 passed / 1 skipped, CLI 36 files / 373 passedpackages/cli/tests/only, nosrcorpackage.json🤖 Generated with Claude Code