feat(examples): add React Router framework example#368
Conversation
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including ⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds a new React Router v7 example (examples/react-router-demo) demonstrating Prisma Next + Postgres integration with contract emit, HMR-safe runtime, build/test configs, and end-to-end smoke/offline tests. Adds env, gitignore, prisma contract sources (TS + .prisma + generated JSON/.d.ts), runtime DB helper, routes, Vite/Vitest/Turbo/tsconfig, and docs. Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer (pnpm dev)
participant Vite as Vite Dev Server
participant Plugin as `@prisma-next/vite-plugin-contract-emit`
participant DB as Postgres (prisma dev db / pg.Pool)
participant App as SSR App (loader/action)
participant Browser as Browser
Dev->>Vite: start dev server
Vite->>Plugin: init + watch contract sources
Plugin->>Vite: emit contract.json / contract.d.ts
Plugin->>DB: (optional) uses DATABASE_URL to validate/emit
Browser->>Vite: request page (SSR)
Vite->>App: load SSR module (imports app/lib/db.server.ts)
App->>DB: getDb() -> pg.Pool -> Prisma runtime queries
App->>Browser: HTML response with UI
Browser->>App: submit form -> action
App->>DB: action inserts user
Dev->>Plugin: edit contract.prisma / contract.ts
Plugin->>Vite: re-emit contract.json
Vite->>App: HMR dispose/load updated modules (import.meta.hot.dispose)
App->>DB: reopen pool / reinitialize runtime with new contract
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 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. ✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/middleware-telemetry
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/emitter
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
08e5bc1 to
6ff5625
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
examples/react-router-demo/prisma-next.config.ts (1)
11-29: ValidatePRISMA_NEXT_CONTRACT_SOURCEinstead of silently defaulting on typos.Right now, any unexpected value (e.g.
TS,typescript) falls through to PSL mode without signal, which can make CI/debugging noisy.Suggested hardening
-const useTs = process.env['PRISMA_NEXT_CONTRACT_SOURCE'] === 'ts'; +const contractSource = process.env['PRISMA_NEXT_CONTRACT_SOURCE'] ?? 'psl'; +if (contractSource !== 'psl' && contractSource !== 'ts') { + throw new Error("PRISMA_NEXT_CONTRACT_SOURCE must be 'ts' or 'psl'."); +} +const useTs = contractSource === 'ts';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/react-router-demo/prisma-next.config.ts` around lines 11 - 29, Validate the PRISMA_NEXT_CONTRACT_SOURCE value instead of silently treating anything other than the exact 'ts' as PSL: add explicit validation of process.env['PRISMA_NEXT_CONTRACT_SOURCE'] before computing useTs and if the value is non-empty and not exactly 'ts' or 'psl' throw a clear Error (or process.exit) so CI fails fast; update the logic around useTs/contract (symbols: PRISMA_NEXT_CONTRACT_SOURCE, useTs, typescriptContract, prismaContract, defineConfig) to only allow 'ts' to select typescriptContract, 'psl' to select prismaContract, and surface an error for any other value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@examples/react-router-demo/.env.example`:
- Line 4: Remove the surrounding double quotes from the DATABASE_URL value so
the environment variable is unquoted (change
DATABASE_URL="postgres://postgres:postgres@127.0.0.1:5432/postgres" to
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres) to resolve
the dotenv-linter QuoteCharacter warning; update the .env example entry for
DATABASE_URL accordingly.
In `@examples/react-router-demo/app/routes/users.tsx`:
- Around line 7-8: The query building for `plan` is missing an explicit ORDER BY
which makes the `limit(20)` nondeterministic; update the call chain on
`db.sql.user.select(...)` (the `plan` construction) to include an ordering
(e.g., order by `createdAt` DESC) before `.limit(20).build()` so that the
subsequent `rows = await db.runtime().execute(plan)` returns a deterministic set
(most recent users).
In `@examples/react-router-demo/README.md`:
- Line 3: The README currently includes a transient planning reference ("Closes
[April VP3](../../docs/planning/april-milestone.md)") which should be removed;
edit the README to delete that clause and any links to milestone/planning docs
and replace it with a stable, timeless sentence such as a short statement about
the example’s purpose (e.g., "Minimal React Router v7 Framework Mode example
demonstrating Prisma Next's Vite plugin re-emits contract artifacts on save").
Ensure you remove only the planning/milestone reference and leave the rest of
the descriptive text intact.
In `@examples/react-router-demo/test/react-router.smoke.e2e.test.ts`:
- Line 88: Replace the hardcoded 3_000ms cleanup timeout passed to
waitForFileMtimeChange(contractJsonPath, preRevertMtime, 3_000) with the shared
timeout helper from `@prisma-next/test-utils` (e.g., CLEANUP_TIMEOUT or
timeouts.CLEANUP_TIMEOUT). Update the import at the top to pull the helper
(import { CLEANUP_TIMEOUT } from '@prisma-next/test-utils') and use that symbol
in the call to waitForFileMtimeChange so the test uses the centralized CI
scaling timeout instead of a literal.
In `@packages/1-framework/3-tooling/vite-plugin-contract-emit/README.md`:
- Line 132: The README currently links to the transient planning artifact
`projects/vite-vp3-auto-emit/`; replace that `projects/...` link with a durable
reference (for example point to the actual example directory
`examples/react-router-demo` or to a stable docs page/archived doc URL) so
package docs don't reference transient project artifacts—update the line that
mentions `examples/react-router-demo` and remove/replace the
`projects/vite-vp3-auto-emit/` fragment accordingly.
---
Nitpick comments:
In `@examples/react-router-demo/prisma-next.config.ts`:
- Around line 11-29: Validate the PRISMA_NEXT_CONTRACT_SOURCE value instead of
silently treating anything other than the exact 'ts' as PSL: add explicit
validation of process.env['PRISMA_NEXT_CONTRACT_SOURCE'] before computing useTs
and if the value is non-empty and not exactly 'ts' or 'psl' throw a clear Error
(or process.exit) so CI fails fast; update the logic around useTs/contract
(symbols: PRISMA_NEXT_CONTRACT_SOURCE, useTs, typescriptContract,
prismaContract, defineConfig) to only allow 'ts' to select typescriptContract,
'psl' to select prismaContract, and surface an error for any other value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: b02f4050-cfed-4154-9a75-cc44f047655f
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (21)
examples/react-router-demo/.env.exampleexamples/react-router-demo/.gitignoreexamples/react-router-demo/README.mdexamples/react-router-demo/app/lib/db.server.tsexamples/react-router-demo/app/root.tsxexamples/react-router-demo/app/routes.tsexamples/react-router-demo/app/routes/users.tsxexamples/react-router-demo/biome.jsoncexamples/react-router-demo/package.jsonexamples/react-router-demo/prisma-next.config.tsexamples/react-router-demo/prisma/contract.tsexamples/react-router-demo/prisma/schema.prismaexamples/react-router-demo/react-router.config.tsexamples/react-router-demo/src/prisma/contract.d.tsexamples/react-router-demo/src/prisma/contract.jsonexamples/react-router-demo/test/react-router.smoke.e2e.test.tsexamples/react-router-demo/tsconfig.jsonexamples/react-router-demo/turbo.jsonexamples/react-router-demo/vite.config.tsexamples/react-router-demo/vitest.config.tspackages/1-framework/3-tooling/vite-plugin-contract-emit/README.md
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
examples/react-router-demo/test/react-router.smoke.e2e.test.ts (1)
136-141: ⚡ Quick winThis startup "emit" check is satisfied by a stale committed artifact.
Because
originalMtimeisnull, this returnstrueas soon assrc/prisma/contract.jsonalready exists. In this example that file is committed, so Lines 136-141 do not prove Vite emitted anything for the current server instance. Capture the pre-boot mtime first, or remove the artifact before boot, and wait for an actual change.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/react-router-demo/test/react-router.smoke.e2e.test.ts` around lines 136 - 141, The startup emit check uses waitForFileMtimeChange(contractJsonPath, null, ...) so it returns true if the committed src/prisma/contract.json already exists; instead capture the pre-boot mtime (call waitForFileMtimeChange(contractJsonPath) or read mtime) before starting the server and pass that as originalMtime to waitForFileMtimeChange, or delete the committed artifact before boot and then call waitForFileMtimeChange(contractJsonPath, null, ...); update the code around the initialEmit/contractJsonPath usage to use the captured pre-boot mtime (or ensure the file is removed) so the test waits for an actual emit rather than a stale file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@examples/react-router-demo/prisma-next.config.ts`:
- Around line 13-30: The config is missing an explicit extensions property;
update the defineConfig call to include extensions alongside family, target,
adapter, driver so the config satisfies the typed contract (use the same
familyId/targetId values used for family and target), e.g. add an extensions: []
entry (or appropriate array) to the exported object so that defineConfig({
family: sql, target: postgres, driver: postgresDriver, adapter: postgresAdapter,
extensions: ..., contract: ..., db: ... }) includes a concrete extensions field
referenced by the types.
- Around line 9-20: The file currently statically imports `contract` which
forces the TS module to be resolved even when PRISMA_NEXT_CONTRACT_SOURCE !==
'ts'; move the import behind the `useTs` branch (lazy/dynamic import) or split
into two separate config paths so `contract` is only imported when `useTs` is
true (refer to the `useTs` variable and the
`typescriptContract`/`prismaContract` branches in `defineConfig`); also add the
required `extensions` property to the exported config object with entries whose
`familyId` and `targetId` match the `family` and `target` values used in this
config.
In `@examples/react-router-demo/test/react-router.smoke.e2e.test.ts`:
- Around line 45-60: The mtime poller waitForFileMtimeChange uses existsSync +
stat which is TOCTOU-prone; remove the two-step check and wrap the stat call in
a try/catch, treating ENOENT as a transient condition (continue polling) instead
of failing, and only return true when stat succeeds and mtimeMs is greater than
originalMtime (or originalMtime is null); ensure other errors are rethrown or
logged as appropriate so the test still fails on unexpected I/O errors.
---
Nitpick comments:
In `@examples/react-router-demo/test/react-router.smoke.e2e.test.ts`:
- Around line 136-141: The startup emit check uses
waitForFileMtimeChange(contractJsonPath, null, ...) so it returns true if the
committed src/prisma/contract.json already exists; instead capture the pre-boot
mtime (call waitForFileMtimeChange(contractJsonPath) or read mtime) before
starting the server and pass that as originalMtime to waitForFileMtimeChange, or
delete the committed artifact before boot and then call
waitForFileMtimeChange(contractJsonPath, null, ...); update the code around the
initialEmit/contractJsonPath usage to use the captured pre-boot mtime (or ensure
the file is removed) so the test waits for an actual emit rather than a stale
file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 98c06de2-dd0f-4325-8080-1b2f238051fe
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (10)
examples/react-router-demo/.env.exampleexamples/react-router-demo/README.mdexamples/react-router-demo/app/lib/db.server.tsexamples/react-router-demo/app/routes/users.tsxexamples/react-router-demo/package.jsonexamples/react-router-demo/prisma-next.config.tsexamples/react-router-demo/prisma/contract.prismaexamples/react-router-demo/test/react-router.smoke.e2e.test.tsexamples/react-router-demo/tsconfig.jsonpackages/1-framework/3-tooling/vite-plugin-contract-emit/README.md
✅ Files skipped from review due to trivial changes (5)
- examples/react-router-demo/.env.example
- examples/react-router-demo/tsconfig.json
- examples/react-router-demo/README.md
- examples/react-router-demo/package.json
- examples/react-router-demo/prisma/contract.prisma
🚧 Files skipped from review as they are similar to previous changes (2)
- examples/react-router-demo/app/lib/db.server.ts
- packages/1-framework/3-tooling/vite-plugin-contract-emit/README.md
| import { contract } from './prisma/contract'; | ||
|
|
||
| const useTs = process.env['PRISMA_NEXT_CONTRACT_SOURCE'] === 'ts'; | ||
|
|
||
| export default defineConfig({ | ||
| family: sql, | ||
| target: postgres, | ||
| driver: postgresDriver, | ||
| adapter: postgresAdapter, | ||
| contract: useTs | ||
| ? typescriptContract(contract, 'src/prisma/contract.json') | ||
| : prismaContract('./prisma/contract.prisma', { |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In ECMAScript modules, are top-level static imports resolved and evaluated even when the imported binding is only used inside a conditional branch that never runs?
💡 Result:
Yes, in ECMAScript modules, top-level static imports are resolved, linked, and evaluated regardless of whether the imported binding is used inside a conditional branch that never runs. The module evaluation process is independent of runtime usage in the importing module.
Citations:
- 1: https://tc39.es/ecma262/pr/3770/multipage/ecmascript-language-scripts-and-modules.html
- 2: https://esmodules.com/resolution/
- 3: https://stackoverflow.com/questions/29329662/are-es6-module-imports-hoisted
- 4: https://stackoverflow.com/questions/37325667/does-es6-module-importing-execute-the-code-inside-the-imported-file
Move the TS contract import behind the TS branch or split configs by source mode.
Line 9 statically imports ./prisma/contract, which is resolved and evaluated regardless of the PRISMA_NEXT_CONTRACT_SOURCE condition. This creates a hard PSL dependency on the TS authoring module and undermines the source toggle. Either move the TS import into the useTs branch or maintain separate configs.
Additionally, add the missing extensions property required by the config schema (must specify extensions with matching familyId and targetId).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/react-router-demo/prisma-next.config.ts` around lines 9 - 20, The
file currently statically imports `contract` which forces the TS module to be
resolved even when PRISMA_NEXT_CONTRACT_SOURCE !== 'ts'; move the import behind
the `useTs` branch (lazy/dynamic import) or split into two separate config paths
so `contract` is only imported when `useTs` is true (refer to the `useTs`
variable and the `typescriptContract`/`prismaContract` branches in
`defineConfig`); also add the required `extensions` property to the exported
config object with entries whose `familyId` and `targetId` match the `family`
and `target` values used in this config.
| export default defineConfig({ | ||
| family: sql, | ||
| target: postgres, | ||
| driver: postgresDriver, | ||
| adapter: postgresAdapter, | ||
| contract: useTs | ||
| ? typescriptContract(contract, 'src/prisma/contract.json') | ||
| : prismaContract('./prisma/contract.prisma', { | ||
| output: 'src/prisma/contract.json', | ||
| target: postgres, | ||
| }), | ||
| db: { | ||
| // Left undefined when DATABASE_URL is not set so emit-only flows | ||
| // (`prisma-next contract emit`, CI typegen) work in fresh checkouts. | ||
| // Commands that need a connection surface their own error pointing at | ||
| // `db.connection` or `--db <url>`. | ||
| connection: process.env['DATABASE_URL'], | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Add extensions explicitly to the config.
This repo expects prisma-next.config.ts to wire family, target, adapter, driver, and extensions together as one typed unit. Leaving extensions implicit here skips that part of the contract; set it explicitly, even if the example has no extensions yet.
♻️ Proposed fix
export default defineConfig({
family: sql,
target: postgres,
driver: postgresDriver,
adapter: postgresAdapter,
+ extensions: [],
contract: useTs
? typescriptContract(contract, 'src/prisma/contract.json')
: prismaContract('./prisma/contract.prisma', {
output: 'src/prisma/contract.json',
target: postgres,As per coding guidelines: "**/prisma-next.config.ts: Config files must use defineConfig from '@prisma-next/cli/config-types' and specify family, target, adapter, driver, and extensions with matching familyId and targetId".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export default defineConfig({ | |
| family: sql, | |
| target: postgres, | |
| driver: postgresDriver, | |
| adapter: postgresAdapter, | |
| contract: useTs | |
| ? typescriptContract(contract, 'src/prisma/contract.json') | |
| : prismaContract('./prisma/contract.prisma', { | |
| output: 'src/prisma/contract.json', | |
| target: postgres, | |
| }), | |
| db: { | |
| // Left undefined when DATABASE_URL is not set so emit-only flows | |
| // (`prisma-next contract emit`, CI typegen) work in fresh checkouts. | |
| // Commands that need a connection surface their own error pointing at | |
| // `db.connection` or `--db <url>`. | |
| connection: process.env['DATABASE_URL'], | |
| }, | |
| export default defineConfig({ | |
| family: sql, | |
| target: postgres, | |
| driver: postgresDriver, | |
| adapter: postgresAdapter, | |
| extensions: [], | |
| contract: useTs | |
| ? typescriptContract(contract, 'src/prisma/contract.json') | |
| : prismaContract('./prisma/contract.prisma', { | |
| output: 'src/prisma/contract.json', | |
| target: postgres, | |
| }), | |
| db: { | |
| // Left undefined when DATABASE_URL is not set so emit-only flows | |
| // (`prisma-next contract emit`, CI typegen) work in fresh checkouts. | |
| // Commands that need a connection surface their own error pointing at | |
| // `db.connection` or `--db <url>`. | |
| connection: process.env['DATABASE_URL'], | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/react-router-demo/prisma-next.config.ts` around lines 13 - 30, The
config is missing an explicit extensions property; update the defineConfig call
to include extensions alongside family, target, adapter, driver so the config
satisfies the typed contract (use the same familyId/targetId values used for
family and target), e.g. add an extensions: [] entry (or appropriate array) to
the exported object so that defineConfig({ family: sql, target: postgres,
driver: postgresDriver, adapter: postgresAdapter, extensions: ..., contract:
..., db: ... }) includes a concrete extensions field referenced by the types.
32828b0 to
2c2f7eb
Compare
Closes April milestone VP3 by demonstrating invisible contract emission inside a Vite-based framework. Part of TML-2298 / the vite-vp3-auto-emit project. The example is a minimal React Router v7.14 Framework Mode app with one index route exposing both a loader (SELECT users) and an action (INSERT user + redirect). The Prisma Next runtime lives in `app/lib/db.server.ts` behind a `getDb()` function that stashes the client on globalThis, mirroring the prior art in `examples/retail-store/src/db-singleton.ts`. Pool size is capped at 1 so the smoke test stays compatible with @prisma/dev's single-connection PGlite harness. A single `prisma-next.config.ts` supports both PSL and TS authoring, gated on `PRISMA_NEXT_CONTRACT_SOURCE`. No sibling `.ts-contract.ts` file, no `--config` flag — one env flip at dev-server startup. The smoke test (`test/react-router.smoke.e2e.test.ts`) stands up a PGlite database via `@prisma-next/test-utils`, boots Vite programmatically via `createServer`, POSTs to the action, GETs the loader, edits `prisma/schema.prisma` mid-flight, and asserts the contract re-emits with the new column. TS re-emit is already covered at the plugin level (`test/integration/test/vite-plugin.hmr.e2e.test.ts`) and is documented as a manual README repro to avoid doubling flake surface. Vite 7/8 compat is covered by APR-VP3-05's plugin matrix, not by this example's CI job, for the same reason. Updates the Vite plugin README to reference the new example alongside `prisma-next-demo`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- db.server.ts: register an import.meta.hot.dispose handler so HMR after a contract re-emit rebuilds the runtime from the fresh contractJson instead of reusing the stale cached client. APR-VP3-07 will replace this with hash-keyed caching; this is minimum hygiene until then. - smoke test teardown: revert prisma/schema.prisma *before* closing the Vite server and wait for the plugin to re-emit the clean contract. Previously the schema was reverted but the server was torn down before the watcher drained, leaving src/prisma/contract.json reflecting the mid-test edit and dirtying the working tree. - smoke test: type JSON.parse result as `unknown` and narrow via toMatchObject (matches CLAUDE.md "never use any"); assert that the PSL replace() actually mutated the file so a future schema reformat doesn't silently no-op the test; drop the unused `_` param on the loader. - vitest.config.ts: align with examples/prisma-next-demo — environment, pool, maxWorkers, isolate; use the @prisma-next/test-utils timeouts catalog helper instead of hard-coded 60_000ms.
…nd prisma/ The root turbo.json declares `test`/`typecheck`/`lint` inputs as `src/**` + `test/**`, which matches the shape used by every other workspace in the repo. React Router Framework Mode keeps route modules under `app/**` and contract authoring under `prisma/**`, so without a local override turbo would cache this example's test result across changes to the loader, action, or db.server.ts. CI fresh checkouts are unaffected, but local `pnpm test` at repo root could replay stale success.
Two small follow-ups from local PR review. README: - The previous link to projects/vite-vp3-auto-emit/tickets/apr-vp3-07-hmr-safe-runtime-helper.md pointed at a file that doesn't exist anywhere in the repo (it was a local working-tree-only path on the author's machine). Replace the link with a prose reference to the Linear ticket so the README doesn't ship a 404. Smoke test: - Strengthen the comment above TEST_SCHEMA_SQL. Explain why this smoke test uses raw DDL instead of the control client's `dbInit`: the test's job is to validate VP3 (auto-emit + serve through framework runtime), and the dbInit path is exercised by `test/integration/test/cli.db-init.e2e.test.ts`. Inlining the DDL keeps the test readable top-to-bottom and bounds the flake surface to VP3 itself. No code-behavior changes. No new tests. No new dependencies.
…reate The action discards the result and redirects, so requesting `id`/`email` back is dead code. Drop the `.returning(...)` chain to keep the example honest about which DSL surfaces are actually exercised.
The previous `max: 1` was hard-coded into `db.server.ts` for a constraint that only exists in the smoke test (PGlite rejects concurrent connections). Anyone copying this example as a production reference would inherit a 1-connection pool by accident. Move the cap behind the `PRISMA_NEXT_DEMO_PG_POOL_MAX` env var. The smoke test sets it to `'1'`; the production path leaves it unset so the pg default applies.
Throwing on missing DATABASE_URL at config-load time made \`prisma-next contract emit\` (and CI typegen) fail in fresh checkouts even though emit doesn't touch the database. The CLI already produces a command-aware error for connection-requiring commands, pointing at \`db.connection\` or \`--db <url>\` — duplicating it here just blocked the legitimate offline path. Pass \`process.env['DATABASE_URL']\` through as-is and let the CLI surface its own error when it actually needs a connection.
…re-emit Use Vite ssrLoadModule to pull a fresh getDb() after the PSL edit, then build a SELECT plan that references the newly emitted nickname column. Without HMR cache invalidation the SQL builder synchronously throws "Column nickname not found in scope", so this closes the AC11 gap where the prior follow-up GET only exercised pre-existing fields.
Rename PRISMA_NEXT_DEMO_PG_POOL_MAX to REACT_ROUTER_DEMO_PG_POOL_MAX so the variable name matches this examples package and cannot be confused with the sibling examples/prisma-next-demo. The smoke test is the only caller; no other readers depend on the old name.
…rd getDb on missing DATABASE_URL - Promote the dispose handler to async and await cached.pool.end() so the next getDb() never opens a new pool while the old one is still draining (Vite supports Promise-returning dispose handlers). Capture the pool in a local before clearing the cache so a concurrent caller cannot observe the half-disposed cached entry. - Throw a clear error from getDb() when DATABASE_URL is unset rather than silently falling back to libpq env-var defaults. The check is inside getDb(), not at module load, so emit-only flows (CI typegen, fresh checkouts without DATABASE_URL) keep working. - Add a TODO(APR-VP3-07) marker near the dispose so the trail to the follow-up hash-keyed dev helper is discoverable from the call site.
Add react-router.config.ts, vite.config.ts, and vitest.config.ts to the tsconfig include list so pnpm typecheck catches type errors in them. The repo already includes prisma-next.config.ts; this brings the three sibling tooling configs in line.
…oke test Switch the smoke test to import path helpers from pathe (per the repo rule .cursor/rules/use-pathe-for-paths.mdc) and replace the synchronous readFileSync/writeFileSync calls with the asynchronous readFile/writeFile from node:fs/promises. The test already used stat from fs/promises, so this also removes the asymmetry inside an async hook. Adds pathe to devDependencies; refreshes pnpm-lock.yaml.
…m, document deliberate validation omission - Reword the README "Switching authoring surfaces" section so the TS line describes the actual mechanism: the plugin walks the prisma-next.config module graph and watches every imported file (including prisma/contract.ts), rather than declaring an explicit watch path. - Add a comment above the users action explaining that input validation is deliberately omitted because this example is a validation harness for the Vite plugin auto-emit flow, not a production starter.
…ke teardown Capture byte-equal baselines for prisma/contract.prisma and src/prisma/contract.json in beforeEach, then assert in afterEach (after the existing revert + wait-for-re-emit + server.close + dev.close sequence) that both files match the baseline byte-for-byte. Closes the AC10 gap: prior coverage relied on teardown sequencing alone, so a regression in any of those steps would leave the working tree dirty without test failure. The assertion now turns "tree clean" into the explicit invariant.
…ABASE_URL) Add an e2e test that spawns the example workspace binary at node_modules/.bin/prisma-next contract emit with DATABASE_URL stripped from the child env (along with the libpq fallback PG* vars), asserts the process exits cleanly, and asserts the expected artifacts exist on disk. Locks in AC9: prisma-next.config.ts was specifically modified in 6d11148 to keep db.connection undefined when DATABASE_URL is missing rather than throwing at config-load time. Without this test, a future regression of that path would only surface as a CI failure outside this example. Captures byte-equal baselines for contract.json and contract.d.ts and restores them in afterEach so the working tree stays clean even if a future emitter change makes the rewrite non-idempotent.
2c2f7eb to
7b995de
Compare
…ract types with main
After rebasing onto main:
- The `prisma_contract.marker` DDL now requires an `invariants text[] not null
default '{}'` column (added in the invariant-aware-routing M1 work on main).
The smoke test bootstraps the marker via inlined raw DDL rather than going
through the control client's `dbInit`, so the new column had to be added
here too — otherwise the runtime's marker upsert fails with `column
"invariants" does not exist` on the action's INSERT path.
- `PgAdapterQueryOps` now takes the `CodecTypes` argument as a type parameter
(`PgAdapterQueryOps<CodecTypes>`). The committed `contract.d.ts` regenerated
to match the new shape on first emit; committing the regenerated file so it
stays byte-equal with the emitter's output.
The previous TODO referenced `APR-VP3-07`, an internal milestone ID that was
not tracked anywhere durable from inside the repo. Linear ticket TML-2368
("Replace example-local HMR runtime cache with a hash-keyed dev helper shared
across frameworks", project [PN] Rough Edges) now owns the follow-up; pointing
the call-site comment at it so the trail is discoverable from `db.server.ts`
without prose archaeology.
https://linear.app/prisma-company/issue/TML-2368
…oller The plugin publishes contract artifacts atomically via temp-write + rename (publishContractArtifactPair in @prisma-next/cli/control-api). Between the unlink/rename window the target path briefly does not exist, so an existsSync + stat pair is TOCTOU-prone — stat() can throw ENOENT after existsSync returned true. Today this is unlikely to bite (atomic rename in practice), but the smoke test runs in CI and the cost of defending against it is small. Replace the existsSync guard with a single stat call wrapped in try/catch that swallows ENOENT and rethrows everything else: - in waitForFileMtimeChange, ENOENT means "not yet" — keep polling. - in afterEach's pre-revert mtime read, ENOENT means "no prior artifact to compare against" — same semantics as the previous existsSync short-circuit (preRevertMtime stays null). Drops the existsSync import. No production-code changes; smoke and offline-emit tests still pass. Addresses CodeRabbit thread on test/react-router.smoke.e2e.test.ts:61.
…n decisions Two CodeRabbit threads on prisma-next.config.ts surfaced concerns the PR is deliberately not addressing. The reasoning was scattered across the local review and the PR walkthrough; lifting it to call-site comments so future readers (human or bot) don't re-litigate: 1. Static import of ./prisma/contract is intentional. ESM evaluates it even when PRISMA_NEXT_CONTRACT_SOURCE != 'ts', but contract.ts is a pure defineContract call (no I/O). The two structural fixes were considered and rejected: typescriptContractFromPath (m1 review D05, conservative scope) and splitting into two configs (the older pattern this example replaces). 2. extensionPacks is optional and the example has no extensions to register. The schema property is `extensionPacks` — `validateConfig` actively throws if it sees `extensions`, so suggestions to add `extensions: []` would break config load. No behaviour change.
… env var Throw a clear error when PRISMA_NEXT_CONTRACT_SOURCE is set to a value other than `ts` or `psl` (e.g. `TS`, `typescript`, typos), instead of silently falling through to the PSL default. Unset/empty still defaults to PSL so emit-only flows in fresh checkouts continue to work.
…e in smoke test Pass the pre-boot mtime of `contract.json` to `waitForFileMtimeChange` instead of `null` so the startup re-emit assertion actually waits for a fresh emit on this server instance. Previously, with `originalMtime: null`, the helper returned true the moment the committed artifact existed on disk, which always succeeded regardless of whether the Vite plugin ran. Capture mtime before `createServer()` (the call that runs the plugin's `configureServer` post-hook and its initial emit) so the poll waits for a *post-boot* mtime even when the committed artifact is already on disk. Treat ENOENT as a null baseline so future changes that stop committing the artifact still work.
Summary
examples/react-router-demo— a React Router v7.14 Framework Mode example with one index route exposing both a loader (SELECT) and an action (INSERT + redirect) that run through@prisma-next/postgreson the server viaapp/lib/db.server.ts(getDb()pattern, mirroringexamples/retail-store/src/db-singleton.ts)@prisma-next/test-utils.createDevDatabase), POSTs the action, GETs the loader, editsprisma/schema.prismamid-flight, and asserts the contract re-emits with the new column without a manual commandprisma-next.config.tsgated onPRISMA_NEXT_CONTRACT_SOURCE=ts|psl— no sibling.ts-contract.tsfile, no--configflag@prisma-next/vite-plugin-contract-emit's README to point at bothprisma-next-demoandreact-router-demoPlan-vs-reality deltas
@prisma-next/postgresexposespoolOptionsbut only for timeouts. Cap via an externally-constructednew Pool({ connectionString, max: 1 })passed aspg:, which is public API.max: 1is required so the app cohabits with@prisma/dev's single-connection PGlite.db.sql.user.insert({ email }).returning(...)(the same pattern asexamples/prisma-next-demo/src/queries/dml-operations.ts) rather than the ORMcreate()path, which requires explicit id/createdAt with branded-type casts./?indexper React Router's convention for disambiguating index routes from their parent layout.test/integration/test/vite-plugin.hmr.e2e.test.ts. Running it a second time through React Router would double flake surface without adding signal.Testing
All four green locally.
Tracking
projects/vite-vp3-auto-emit/— ticketapr-vp3-06-react-router-example.md, milestone 3getDb()cache with a hash-keyed dev helper so HMR doesn't leave a stale runtime after re-emitSummary by CodeRabbit
New Features
Documentation
Tests
Chores