Skip to content

fix(vite): watch resolved inputs and avoid self-trigger loops#362

Merged
jkomyno merged 56 commits intomainfrom
tml-2277-vite-plugin-watch-behavior
Apr 22, 2026
Merged

fix(vite): watch resolved inputs and avoid self-trigger loops#362
jkomyno merged 56 commits intomainfrom
tml-2277-vite-plugin-watch-behavior

Conversation

@jkomyno
Copy link
Copy Markdown
Contributor

@jkomyno jkomyno commented Apr 21, 2026

Summary

  • finalize contract.output in the CLI loader for plain-object configs so the Vite plugin always receives resolved absolute paths
  • make the Vite plugin consume loader-resolved inputs and emitted artifact paths, update watched files as config/module-graph inputs change, and warn once when it must fall back to config-only watching
  • refresh plugin docs and align integration coverage with the default output behavior

Testing

  • pnpm test -- test/plugin.test.ts
  • pnpm test -- test/config-loader.test.ts
  • pnpm test -- test/cli.emit-command.test.ts test/vite-plugin.hmr.e2e.test.ts
  • rg "resolve(configDir" packages/1-framework/3-tooling/vite-plugin-contract-emit/src/
  • rg "slice(0, -5)" packages/1-framework/3-tooling/vite-plugin-contract-emit/src/

Notes

Summary by CodeRabbit

  • New Features

    • Contract configs now get a consistent default output path when omitted.
  • Bug Fixes

    • More precise validation for optional contract fields; clearer errors when modules lack expected exports.
  • Improvements

    • CLI always finalizes contract output and resolves input paths; Vite plugin watch/emission behavior improved with fallback warnings and fewer redundant emits.
  • Tests

    • Expanded and reorganized unit and integration tests to cover provider shapes, path resolution, watch fallback, and emit flows.

jkomyno and others added 30 commits April 19, 2026 23:37
The SQL provider was updated to use context.configDir but the mongo
provider was missed, causing watcher/loader path divergence when cwd
differs from the config directory.
…onfigDir

- Switch config-loader from node:path to pathe for consistency with
  the rest of the tooling layer.
- Resolve .ts contract paths inside load() via context.configDir instead
  of capturing process.cwd() at config-definition time, so the watcher
  and loader always reference the same file.
The paths branch omitted the config file from the watch set, so edits
to prisma-next.config.ts (e.g. changing paths or swapping provider)
would not trigger re-emit. Also surface a partial-coverage warning when
loadConfig fails instead of silently falling back.
Close D1 from the PR #356 review by driving a real Vite dev server
through both scenarios that were previously only unit-mocked:
modifying `contract.prisma` re-emits the contract, and editing the
config to swap the declared PSL input re-emits against the new file.
Align with the Mongo PSL provider and with the parser step's sourceId.
The absolute form stays accessible via meta.absoluteSchemaPath for
downstream consumers that need it.
Collapse the ~20-line inline provider that postgres and mongo facades
each build around a dynamic import of the user's `contract.ts`. The
logic (path resolution, pathToFileURL, default-export extraction) is
family-agnostic, so each contract-ts package exposes it as a sibling
to typescriptContract.
- handleTrackedFileChange now runs ctx.file through pathe.resolve
  before comparing against watchedFiles / ignoredOutputFiles, so
  symlinked or case-folded paths line up with the resolved entries.
- resolveContractOutputFiles no longer falls back to the hardcoded
  'src/prisma/contract.json' when contract.output is missing. The
  filter simply skips; defineConfig() guarantees output is present
  in the happy path, and raw-object configs get a clean no-op instead
  of a path the CLI never actually writes.
…ceContext

Left over from the split that moved configDir out of ContractSourceContext
into ContractSourceEnvironment — the context builder no longer needs it.
jkomyno added 3 commits April 21, 2026 16:04
…rdening

Updates all `prisma-next.config.ts` fixtures under test/integration to the
new `contract.source.inputs` shape. Adds the vite-plugin PSL fixture used by
the HMR e2e test and makes that test wait until the rewritten
`contract.json` is readable before parsing. Raises timeout budgets on
DB-backed CLI and sql-builder integration tests that were flaky under full-
repo load.
Updates ADR 163 to describe provider-invoked source interpretation with the
resolvedInputs I/O boundary, and aligns the config / cli / vite-plugin
READMEs with the current `load()` signature and the one-call-site path
derivation.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 804ee188-3646-453f-9a04-80223ba9f685

📥 Commits

Reviewing files that changed from the base of the PR and between e74d02e and 015b751.

📒 Files selected for processing (5)
  • packages/1-framework/1-core/config/src/config-types.ts
  • packages/1-framework/1-core/config/src/exports/config-types.ts
  • packages/1-framework/3-tooling/cli/src/config-path-validation.ts
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/src/plugin.ts
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/1-framework/3-tooling/cli/src/config-path-validation.ts
  • packages/1-framework/1-core/config/src/config-types.ts
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/src/plugin.ts

📝 Walkthrough

Walkthrough

Delegates contract normalization to normalizeContractConfig with a default DEFAULT_CONTRACT_OUTPUT; tightens validation to detect optional fields via Object.hasOwn; ensures TypeScript contract modules export default or contract; normalizes contract output resolution in CLI finalization; improves Vite plugin watch/fallback handling; and expands/refactors tests accordingly.

Changes

Cohort / File(s) Summary
Core config types & exports
packages/1-framework/1-core/config/src/config-types.ts, packages/1-framework/1-core/config/src/exports/config-types.ts
Added DEFAULT_CONTRACT_OUTPUT and normalizeContractConfig; replaced inline contract normalization with normalizeContractConfig; re-exported new symbols.
Config validation
packages/1-framework/1-core/config/src/config-validation.ts, packages/1-framework/1-core/config/test/config-validation.test.ts
validateContractConfig now uses Object.hasOwn to treat optional subkeys precisely; tests extended to cover prototype-inherited provider shapes and field-path handling.
CLI path finalization & loader tests
packages/1-framework/3-tooling/cli/src/config-path-validation.ts, packages/1-framework/3-tooling/cli/test/config-loader.test.ts
finalizeConfig now applies normalizeContractConfig and always computes/resolves contract.output; tests added/updated for input resolution, emitted-artifact overlap checks, and default output behavior.
CLI tests & helpers
packages/1-framework/3-tooling/cli/test/commands/migration-e2e.test.ts, packages/1-framework/3-tooling/cli/test/control-api/client.test.ts, packages/1-framework/3-tooling/cli/test/control-api/contract-emit.test.ts
Introduced withTempDir helper for deterministic temp-dir cleanup; updated test helpers to use ifDefined for inputs inclusion and adjusted expectations/messages for invalid source provider semantics.
Vite plugin watch behavior & docs
packages/1-framework/3-tooling/vite-plugin-contract-emit/src/plugin.ts, .../README.md, .../test/plugin.test.ts
Added one-time warning flag, optional refreshWatchedFiles parameter for emits, fallback watched-file preservation on resolver failure, and server cleanup resets; README and tests updated to cover new watch/fallback and dynamic watcher behavior.
TypeScript contract loader guards
packages/2-mongo-family/2-authoring/contract-ts/src/config-types.ts, packages/2-mongo-family/.../test/config-types.test.ts, packages/2-sql/2-authoring/contract-ts/src/config-types.ts, packages/2-sql/.../test/config-types.test.ts
typescriptContractFromPath now treats module export as possibly undefined and throws a descriptive error if neither default nor contract export exists; tests added to assert the failure.
Integration & emit command tests
test/integration/test/cli.emit-command.test.ts, test/integration/test/cli.emit-command.additional.test.ts, test/integration/test/fixtures/cli/.../prisma-next.config.missing-output.ts, test/integration/test/sql-builder/extension-functions.test.ts
Removed several older integration cases; added a new comprehensive fixture-based emit test suite covering parity, diagnostics, default outputs, and Mongo emit scenarios; updated missing-output comment and suite timeouts.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

🐰 I hopped through config, neat and spry,
Defaults tucked in where outputs lie,
Guards set for modules that hide,
Watchers learn to step aside,
Tests now tidy, all hops satisfied. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: fixing Vite plugin watch behavior to track resolved inputs and prevent self-triggered loops, which is the core focus across the modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2277-vite-plugin-watch-behavior

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 21, 2026

Open in StackBlitz

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-runtime@362

@prisma-next/family-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/family-mongo@362

@prisma-next/sql-runtime

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-runtime@362

@prisma-next/family-sql

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/family-sql@362

@prisma-next/middleware-telemetry

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/middleware-telemetry@362

@prisma-next/mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo@362

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-paradedb@362

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-pgvector@362

@prisma-next/postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/postgres@362

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-orm-client@362

@prisma-next/sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sqlite@362

@prisma-next/target-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-mongo@362

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-mongo@362

@prisma-next/driver-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-mongo@362

@prisma-next/contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/contract@362

@prisma-next/utils

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/utils@362

@prisma-next/config

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/config@362

@prisma-next/errors

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/errors@362

@prisma-next/framework-components

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/framework-components@362

@prisma-next/operations

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/operations@362

@prisma-next/ts-render

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/ts-render@362

@prisma-next/contract-authoring

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/contract-authoring@362

@prisma-next/ids

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/ids@362

@prisma-next/psl-parser

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/psl-parser@362

@prisma-next/psl-printer

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/psl-printer@362

@prisma-next/cli

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/cli@362

@prisma-next/emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/emitter@362

@prisma-next/migration-tools

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/migration-tools@362

prisma-next

npm i https://pkg.pr.new/prisma/prisma-next@362

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/vite-plugin-contract-emit@362

@prisma-next/runtime-executor

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/runtime-executor@362

@prisma-next/mongo-codec

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-codec@362

@prisma-next/mongo-contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract@362

@prisma-next/mongo-value

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-value@362

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract-psl@362

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract-ts@362

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-emitter@362

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-schema-ir@362

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-query-ast@362

@prisma-next/mongo-orm

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-orm@362

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-query-builder@362

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-lowering@362

@prisma-next/mongo-wire

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-wire@362

@prisma-next/sql-contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract@362

@prisma-next/sql-errors

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-errors@362

@prisma-next/sql-operations

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-operations@362

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-schema-ir@362

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-psl@362

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-ts@362

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-emitter@362

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-lane-query-builder@362

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-relational-core@362

@prisma-next/sql-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-builder@362

@prisma-next/target-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-postgres@362

@prisma-next/target-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-sqlite@362

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-postgres@362

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-sqlite@362

@prisma-next/driver-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-postgres@362

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-sqlite@362

commit: 46d57c6

@jkomyno jkomyno changed the title fix(vite): watch authoritative inputs and avoid self-trigger loops fix(vite): watch resolved inputs and avoid self-trigger loops Apr 21, 2026
Base automatically changed from tml-2276-authoritative-watch-inputs to main April 21, 2026 15:57
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (6)
test/integration/test/sql-builder/extension-functions.test.ts (1)

4-29: Optional: apply the suite timeout to the ilike suite too for consistency.

Only the extension functions suite got { timeout: timeouts.databaseOperation }. The ilike suite above still relies on Vitest's default. If the intent is uniform suite-level timeouts across integration tests (as done in sibling files), consider applying it here as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/integration/test/sql-builder/extension-functions.test.ts` around lines 4
- 29, The "integration: ilike (adapter operation)" test suite currently lacks
the extended timeout used elsewhere; update the describe call that defines this
suite (describe('integration: ilike (adapter operation)')) to include the same
suite option object used in the other suite, e.g. add { timeout:
timeouts.databaseOperation } so the ilike tests use the longer databaseOperation
timeout during execution.
packages/1-framework/3-tooling/cli/test/control-api/contract-emit.test.ts (1)

72-87: Rename the stale negative-test description.

The assertion now verifies a valid source provider object, but the test still says “not callable,” which describes the old function API.

Suggested rename
-  it('throws when contract source is not callable', async () => {
+  it('throws when contract source is not a valid provider object', async () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/3-tooling/cli/test/control-api/contract-emit.test.ts`
around lines 72 - 87, The test description "throws when contract source is not
callable" is outdated; update the it(...) title to reflect the current assertion
that the source must be a valid source provider object. Change the test name in
the it call (surrounding the assertion that uses mockConfigWithContract and
executeContractEmit) to something like "throws when contract source is not a
valid source provider object" so it matches the expectation checking error.why
includes 'valid source provider object'.
test/integration/test/cli.emit-command.test.ts (1)

628-658: Consider moving this new coverage into a split emit-command test file.

This file is already 865 lines, so adding the default-output case here makes the oversized test harder to navigate. A focused split such as cli.emit-command.defaults.test.ts would keep this behavior covered while following the test-size rule.

As per coding guidelines, **/*.test.ts: “Keep test files under 500 lines to maintain readability and navigability. If a test file exceeds this limit, it should be split into multiple files.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/integration/test/cli.emit-command.test.ts` around lines 628 - 658, The
new "uses default output path for plain-object configs without defineConfig"
test should be moved out of the large cli.emit-command.test.ts into a dedicated
smaller test file (suggested name: cli.emit-command.defaults.test.ts) to keep
files under 500 lines; locate the spec by its it(...) block and the helper usage
(createContractEmitCommand, setupIntegrationTestDirectoryFromFixtures,
executeCommand) and copy that entire test case (including its timeout,
process.chdir handling, assertions on src/prisma/contract.json and
contract.d.ts) into the new file, update any imports/fixture references to match
the new file context, and remove the original block from
cli.emit-command.test.ts so both suites run but the large file stays below the
size guideline.
packages/1-framework/3-tooling/cli/test/control-api/client.test.ts (1)

1-18: Use ifDefined() for the optional inputs property.

This keeps the new helper aligned with the repo’s conditional-spread convention.

♻️ Proposed refactor
 import type { ContractSourceProvider } from '@prisma-next/config/config-types';
 import { createContract } from '@prisma-next/contract/testing';
 import type { Contract } from '@prisma-next/contract/types';
 import type { EmitResult } from '@prisma-next/emitter';
@@
 import type { EmissionSpi } from '@prisma-next/framework-components/emission';
+import { ifDefined } from '@prisma-next/utils/defined';
 import { notOk, ok } from '@prisma-next/utils/result';
 import { describe, expect, it, vi } from 'vitest';
@@
 ): ContractSourceProvider {
   return {
-    ...(inputs ? { inputs } : {}),
+    ...ifDefined('inputs', inputs),
     load,
   };
 }

As per coding guidelines, use ifDefined() from @prisma-next/utils/defined for conditional object spreads instead of inline conditional spread patterns.

Also applies to: 37-44

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/3-tooling/cli/test/control-api/client.test.ts` around
lines 1 - 18, Replace inline conditional object-spread for the optional inputs
property with the repository helper ifDefined from '@prisma-next/utils/defined':
import ifDefined and, where the test builds the object containing inputs (the
object passed to createContract / the helper that currently does ... (inputs ? {
inputs } : {})), change that spread to ...ifDefined(inputs) so the
conditional-spread follows the repo convention; update all occurrences mentioned
(including lines around the createContract usage where inputs is optional).
packages/1-framework/1-core/config/src/config-types.ts (1)

89-92: Use Schema.array() for this Arktype array field.

The provider schema currently uses the string DSL array form for inputs; switch to a string schema plus .array() to match the repo’s Arktype conventions.

Proposed refactor
+const ContractSourceInputSchema = type('string');
+
 export const ContractSourceProviderSchema = type({
-  'inputs?': 'string[]',
+  'inputs?': ContractSourceInputSchema.array(),
   load: 'Function',
 });

As per coding guidelines, Use 'Schema.array()' for array definitions in Arktype schemas (avoid tuple mistakes).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/1-core/config/src/config-types.ts` around lines 89 - 92,
ContractSourceProviderSchema currently uses the DSL string array form
('inputs?': 'string[]') which can be a tuple pitfall; update the schema to use
the Arktype Schema API by replacing that field with a string schema plus
.array() and mark it optional (e.g., use Schema.string().array().optional()).
Modify the ContractSourceProviderSchema definition (where 'inputs?' and load are
declared) to use Schema.string().array().optional() for inputs and keep load as
the existing Function type, and ensure the Schema symbol is imported if not
already.
packages/1-framework/3-tooling/vite-plugin-contract-emit/src/plugin.ts (1)

378-414: Redundant config load on server startup.

configureServer calls resolveWatchedFiles(viteServer) directly at Line 378 and then await emitContract() at Line 414, which internally calls updateWatchedFiles(viteServer)resolveWatchedFiles(viteServer) again. That means loadConfig(absoluteConfigPath) and ssrLoadModule(...) for every module-graph root run twice on cold start. Functionally correct (the diff between watchedFiles and newWatchedFiles is empty on the second pass, so no duplicate watcher.add/unwatch), but wasteful.

Consider either passing a flag to emitContract to skip the inner refresh on the initial call, or removing the explicit call at Line 378 (moving the “watchedFiles is empty → overlay error” check to run after the first emitContract).

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/1-framework/1-core/config/src/config-validation.ts`:
- Around line 21-48: The current validation reads prototype-inherited properties
(e.g., source.load, source.inputs, contract.output); change the checks to
require own properties by using Object.prototype.hasOwnProperty.call(...) before
validating each nested field: for inputs use hasOwnProperty on sourceConfig with
key 'inputs' before checking Array.isArray and element types, for load use
hasOwnProperty on sourceConfig with key 'load' before verifying typeof ===
'function', and for output use hasOwnProperty on contract with key 'output'
before validating it is a string; keep using throwValidation (e.g.,
throwValidation('contract.source.inputs', ...),
throwValidation('contract.source.load', ...), throwValidation('contract.output',
...)) for failures.

In `@packages/1-framework/3-tooling/cli/test/commands/migration-e2e.test.ts`:
- Around line 98-190: The test creates a temp dir via createTempDir() and never
removes it; wrap the main test body (the code that creates root, migrationsDir,
writes migrations, attests and verifies) inside a try/finally so that in the
finally block you call rm(root, { recursive: true, force: true }) to clean up;
locate usage of createTempDir, migrationsDir, writeMigrationPackage,
attestMigration, readMigrationsDir, verifyMigration and ensure the finally
always executes even if assertions fail.

In
`@packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts`:
- Around line 415-417: The test contains a tautological negative assertion using
consoleErrorSpy.not.toHaveBeenCalledWith(expect.stringContaining('Failed to
resolve watched files:')) which will always pass because that string never
appears in plugin.ts; replace this with a meaningful check—either remove the
assertion entirely or change it to assert that consoleErrorSpy was not called
with a real fallback/emit error string such as 'Contract emit failed'
(referencing consoleErrorSpy in plugin.test.ts and the actual error messages in
plugin.ts like 'Failed to load config module graph root:' / 'Failed to collect
watched files:' when making the replacement).

In `@packages/2-mongo-family/2-authoring/contract-ts/src/config-types.ts`:
- Around line 22-32: The loader currently imports the module in the load
function and sets const contract: Contract = mod.default ?? mod.contract, which
can silently become undefined; change this to explicitly validate that either
mod.default or mod.contract exists and throw a descriptive Error if neither is
present (e.g., "typescriptContractFromPath: module must export default or named
'contract'"). Update the block around the import in load to check the presence
of mod.default and mod.contract before returning ok(contract) and only call
ok(contract) with a properly-typed Contract.

In `@packages/2-sql/2-authoring/contract-ts/src/config-types.ts`:
- Around line 20-29: The loader in load: async (context) currently accepts a
module that may export neither default nor contract, assigning undefined to
const contract and returning ok(contract); change this to validate that
mod.default or mod.contract is present: after const mod = await import(...),
compute const contract = mod.default ?? mod.contract and if contract is
undefined throw a clear Error (including the absolutePath and a message saying
the module must export either default or named export "contract") instead of
returning ok(undefined); only return ok(contract) once the contract value is
confirmed.

In `@test/integration/test/authoring/parity/callback-mode-scalars/contract.ts`:
- Around line 15-22: The contract uses defaultSql('autoincrement()') and
defaultSql('now()') in fields (see uses of defaultSql on id and createdAt), but
the defineContract call is missing matching capabilities; update the
defineContract options to include 'defaults.autoincrement': true and
'defaults.now': true under capabilities so the emitter knows to include full
default payloads, then re-run the emitter and verify the emitted JSON contains
the complete default payloads for those fields.

---

Nitpick comments:
In `@packages/1-framework/1-core/config/src/config-types.ts`:
- Around line 89-92: ContractSourceProviderSchema currently uses the DSL string
array form ('inputs?': 'string[]') which can be a tuple pitfall; update the
schema to use the Arktype Schema API by replacing that field with a string
schema plus .array() and mark it optional (e.g., use
Schema.string().array().optional()). Modify the ContractSourceProviderSchema
definition (where 'inputs?' and load are declared) to use
Schema.string().array().optional() for inputs and keep load as the existing
Function type, and ensure the Schema symbol is imported if not already.

In `@packages/1-framework/3-tooling/cli/test/control-api/client.test.ts`:
- Around line 1-18: Replace inline conditional object-spread for the optional
inputs property with the repository helper ifDefined from
'@prisma-next/utils/defined': import ifDefined and, where the test builds the
object containing inputs (the object passed to createContract / the helper that
currently does ... (inputs ? { inputs } : {})), change that spread to
...ifDefined(inputs) so the conditional-spread follows the repo convention;
update all occurrences mentioned (including lines around the createContract
usage where inputs is optional).

In `@packages/1-framework/3-tooling/cli/test/control-api/contract-emit.test.ts`:
- Around line 72-87: The test description "throws when contract source is not
callable" is outdated; update the it(...) title to reflect the current assertion
that the source must be a valid source provider object. Change the test name in
the it call (surrounding the assertion that uses mockConfigWithContract and
executeContractEmit) to something like "throws when contract source is not a
valid source provider object" so it matches the expectation checking error.why
includes 'valid source provider object'.

In `@test/integration/test/cli.emit-command.test.ts`:
- Around line 628-658: The new "uses default output path for plain-object
configs without defineConfig" test should be moved out of the large
cli.emit-command.test.ts into a dedicated smaller test file (suggested name:
cli.emit-command.defaults.test.ts) to keep files under 500 lines; locate the
spec by its it(...) block and the helper usage (createContractEmitCommand,
setupIntegrationTestDirectoryFromFixtures, executeCommand) and copy that entire
test case (including its timeout, process.chdir handling, assertions on
src/prisma/contract.json and contract.d.ts) into the new file, update any
imports/fixture references to match the new file context, and remove the
original block from cli.emit-command.test.ts so both suites run but the large
file stays below the size guideline.

In `@test/integration/test/sql-builder/extension-functions.test.ts`:
- Around line 4-29: The "integration: ilike (adapter operation)" test suite
currently lacks the extended timeout used elsewhere; update the describe call
that defines this suite (describe('integration: ilike (adapter operation)')) to
include the same suite option object used in the other suite, e.g. add {
timeout: timeouts.databaseOperation } so the ilike tests use the longer
databaseOperation timeout during execution.
🪄 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: 2887f2b6-4ca7-4522-bf8a-0b091c565bc5

📥 Commits

Reviewing files that changed from the base of the PR and between b1ffdba and d33fb4c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (97)
  • docs/architecture docs/adrs/ADR 163 - Provider-invoked source interpretation packages.md
  • packages/1-framework/1-core/config/README.md
  • packages/1-framework/1-core/config/src/config-types.ts
  • packages/1-framework/1-core/config/src/config-validation.ts
  • packages/1-framework/1-core/config/src/contract-source-types.ts
  • packages/1-framework/1-core/config/test/config-types.test-d.ts
  • packages/1-framework/1-core/config/test/config-validation.test.ts
  • packages/1-framework/3-tooling/cli/README.md
  • packages/1-framework/3-tooling/cli/recordings/fixtures/prisma-next.config.ts
  • packages/1-framework/3-tooling/cli/src/commands/contract-emit.ts
  • packages/1-framework/3-tooling/cli/src/config-loader.ts
  • packages/1-framework/3-tooling/cli/src/config-path-validation.ts
  • packages/1-framework/3-tooling/cli/src/control-api/client.ts
  • packages/1-framework/3-tooling/cli/src/control-api/operations/contract-emit.ts
  • packages/1-framework/3-tooling/cli/src/control-api/types.ts
  • packages/1-framework/3-tooling/cli/test/commands/migration-e2e.test.ts
  • packages/1-framework/3-tooling/cli/test/commands/migration-plan.test.ts
  • packages/1-framework/3-tooling/cli/test/config-loader.test.ts
  • packages/1-framework/3-tooling/cli/test/config-types.test.ts
  • packages/1-framework/3-tooling/cli/test/control-api/client.test.ts
  • packages/1-framework/3-tooling/cli/test/control-api/contract-emit.test.ts
  • packages/1-framework/3-tooling/emitter/src/artifact-paths.ts
  • packages/1-framework/3-tooling/emitter/src/emit-types.ts
  • packages/1-framework/3-tooling/emitter/src/emit.ts
  • packages/1-framework/3-tooling/emitter/src/exports/index.ts
  • packages/1-framework/3-tooling/emitter/test/emitter.test.ts
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/README.md
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/package.json
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/src/plugin.ts
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts
  • packages/2-mongo-family/2-authoring/contract-psl/src/provider.ts
  • packages/2-mongo-family/2-authoring/contract-psl/test/provider.test.ts
  • packages/2-mongo-family/2-authoring/contract-ts/package.json
  • packages/2-mongo-family/2-authoring/contract-ts/src/config-types.ts
  • packages/2-mongo-family/2-authoring/contract-ts/src/exports/config-types.ts
  • packages/2-mongo-family/2-authoring/contract-ts/test/config-types.test.ts
  • packages/2-sql/2-authoring/contract-psl/src/provider.ts
  • packages/2-sql/2-authoring/contract-psl/test/fixtures.ts
  • packages/2-sql/2-authoring/contract-psl/test/provider.test.ts
  • packages/2-sql/2-authoring/contract-ts/package.json
  • packages/2-sql/2-authoring/contract-ts/src/config-types.ts
  • packages/2-sql/2-authoring/contract-ts/src/exports/config-types.ts
  • packages/2-sql/2-authoring/contract-ts/test/config-types.test.ts
  • packages/3-extensions/mongo/src/config/define-config.ts
  • packages/3-extensions/mongo/test/config/define-config.test.ts
  • packages/3-extensions/postgres/package.json
  • packages/3-extensions/postgres/src/config/define-config.ts
  • packages/3-extensions/postgres/test/config/define-config.test.ts
  • packages/3-extensions/sql-orm-client/test/fixtures/prisma-next.config.ts
  • packages/3-targets/6-adapters/postgres/test/codec-render-output-type.test.ts
  • test/integration/test/authoring/cli.emit-parity-fixtures.test.ts
  • test/integration/test/authoring/parity/callback-mode-scalars/contract.ts
  • test/integration/test/authoring/psl.pgvector-dbinit.test.ts
  • test/integration/test/authoring/side-by-side-contracts.test.ts
  • test/integration/test/cli.emit-command.test.ts
  • test/integration/test/cli.emit-contract.test.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/cli-journeys/prisma-next.config.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/cli-journeys/prisma-next.config.with-db.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/db-init/prisma-next.config.with-db.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/db-introspect/prisma-next.config.no-db.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/db-introspect/prisma-next.config.no-driver.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/db-introspect/prisma-next.config.with-db.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/db-sign/prisma-next.config.with-db.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/db-update-scenarios/prisma-next.config.with-db.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/db-verify/prisma-next.config.no-driver.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/db-verify/prisma-next.config.no-verify.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/db-verify/prisma-next.config.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/db-verify/prisma-next.config.with-db.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/emit/prisma-next.config.emit.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/migration-apply/prisma-next.config.with-db.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/migration-plan/prisma-next.config.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/mongo-cli-journeys/prisma-next.config.with-db.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/mongo-db-commands/prisma-next.config.with-db.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/vite-plugin-psl/contract-alt.prisma
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/vite-plugin-psl/contract.prisma
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/vite-plugin-psl/prisma-next.config.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/vite-plugin-psl/vite.config.ts
  • test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/vite-plugin/prisma-next.config.ts
  • test/integration/test/fixtures/cli/cli-integration-test-app/fixtures/emit-command/prisma-next.config.async-source.ts
  • test/integration/test/fixtures/cli/cli-integration-test-app/fixtures/emit-command/prisma-next.config.missing-output.ts
  • test/integration/test/fixtures/cli/cli-integration-test-app/fixtures/emit-command/prisma-next.config.mongo-contract-ts.ts
  • test/integration/test/fixtures/prisma-next.config.ts
  • test/integration/test/mongo/fixtures/prisma-next.config.ts
  • test/integration/test/sql-builder/distinct.test.ts
  • test/integration/test/sql-builder/execution.test.ts
  • test/integration/test/sql-builder/extension-functions.test.ts
  • test/integration/test/sql-builder/fixtures/prisma-next.config.ts
  • test/integration/test/sql-builder/group-by.test.ts
  • test/integration/test/sql-builder/join.test.ts
  • test/integration/test/sql-builder/mutation-defaults.test.ts
  • test/integration/test/sql-builder/mutation.test.ts
  • test/integration/test/sql-builder/order-by.test.ts
  • test/integration/test/sql-builder/pagination.test.ts
  • test/integration/test/sql-builder/select.test.ts
  • test/integration/test/sql-builder/subquery.test.ts
  • test/integration/test/sql-builder/where.test.ts
  • test/integration/test/vite-plugin.hmr.e2e.test.ts

Comment thread packages/1-framework/1-core/config/src/config-validation.ts
Comment thread packages/1-framework/3-tooling/cli/test/commands/migration-e2e.test.ts Outdated
Comment thread packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts Outdated
Comment thread packages/2-mongo-family/2-authoring/contract-ts/src/config-types.ts
Comment thread packages/2-sql/2-authoring/contract-ts/src/config-types.ts
jkomyno added 4 commits April 21, 2026 18:14
…watch-behavior

# Conflicts:
#	packages/1-framework/3-tooling/cli/src/config-path-validation.ts
#	packages/1-framework/3-tooling/cli/test/config-loader.test.ts
#	packages/1-framework/3-tooling/vite-plugin-contract-emit/README.md
#	packages/1-framework/3-tooling/vite-plugin-contract-emit/src/plugin.ts
#	packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts (1)

354-367: Assert the startup ordering, not just the load count.

This test name says loadConfig happens before the initial emit, but the assertion only checks it was called once. Add an invocation-order check so the test covers the contract it documents.

Suggested assertion tightening
       await configureServer(mockServer);
 
       expect(mockedLoadConfig).toHaveBeenCalledTimes(1);
+      expect(mockedExecuteContractEmit).toHaveBeenCalledTimes(1);
+      expect(mockedLoadConfig.mock.invocationCallOrder[0]!).toBeLessThan(
+        mockedExecuteContractEmit.mock.invocationCallOrder[0]!,
+      );
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts`
around lines 354 - 367, The test currently only asserts mockedLoadConfig was
called once but must verify it was called before the plugin's initial emit;
after invoking configResolved and awaiting plugin.configureServer(mockServer)
add an invocation-order assertion comparing mockedLoadConfig's call order to the
initial emit on the mock server (e.g., compare
mockedLoadConfig.mock.invocationCallOrder[0] <
mockServer.ws.send.mock.invocationCallOrder[0] or use a Jest call-order matcher)
so that mockedLoadConfig is confirmed to run before the initial emit triggered
by configureServer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/1-framework/3-tooling/vite-plugin-contract-emit/src/plugin.ts`:
- Around line 287-296: The warning text is misleading when
previousWatchedFiles.size > 0 because the code continues watching the prior
dependency set but the message says "Watching only {absoluteConfigPath}"; update
the log emitted in the block that checks didWarnConfigWatchFallback (and that
may follow the branch where previousWatchedFiles is used) to conditionally
include the preserved watch state: use didWarnConfigWatchFallback and
previousWatchedFiles to choose a message that either says "Watching only
{absoluteConfigPath}" (when previousWatchedFiles is empty) or "Watching previous
dependency set plus {absoluteConfigPath}; contract watch coverage is partial"
(when previousWatchedFiles.size > 0), and keep using logWarning and the existing
reason variable to retain error.message details.

In
`@packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts`:
- Around line 407-432: The test currently rejects mockedLoadConfig but leaves
executeContractEmit using the default resolved mock from beforeEach, so the
assertion that console.error was not called for "Contract emit failed" doesn't
exercise the emit fallback; update the test to explicitly set
executeContractEmit to the intended behavior for the fallback path (e.g.,
mockedExecuteContractEmit.mockRejectedValue(new Error('emit failed')) or
.mockResolvedValue(...) depending on desired outcome) before invoking
plugin.configureServer, or remove the console.error assertion; reference
mockedLoadConfig and mockedExecuteContractEmit (or executeContractEmit mock set
up in beforeEach) and adjust the test case "falls back to watching the config
file and warns when loadConfig fails" accordingly.

---

Nitpick comments:
In
`@packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts`:
- Around line 354-367: The test currently only asserts mockedLoadConfig was
called once but must verify it was called before the plugin's initial emit;
after invoking configResolved and awaiting plugin.configureServer(mockServer)
add an invocation-order assertion comparing mockedLoadConfig's call order to the
initial emit on the mock server (e.g., compare
mockedLoadConfig.mock.invocationCallOrder[0] <
mockServer.ws.send.mock.invocationCallOrder[0] or use a Jest call-order matcher)
so that mockedLoadConfig is confirmed to run before the initial emit triggered
by configureServer.
🪄 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: ed8ca4bf-826f-4642-8bc8-b5d3e3402e2a

📥 Commits

Reviewing files that changed from the base of the PR and between d33fb4c and e74d02e.

📒 Files selected for processing (15)
  • packages/1-framework/1-core/config/src/config-types.ts
  • packages/1-framework/1-core/config/src/config-validation.ts
  • packages/1-framework/1-core/config/test/config-validation.test.ts
  • packages/1-framework/3-tooling/cli/test/commands/migration-e2e.test.ts
  • packages/1-framework/3-tooling/cli/test/control-api/client.test.ts
  • packages/1-framework/3-tooling/cli/test/control-api/contract-emit.test.ts
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/src/plugin.ts
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/test/plugin.test.ts
  • packages/2-mongo-family/2-authoring/contract-ts/src/config-types.ts
  • packages/2-mongo-family/2-authoring/contract-ts/test/config-types.test.ts
  • packages/2-sql/2-authoring/contract-ts/src/config-types.ts
  • packages/2-sql/2-authoring/contract-ts/test/config-types.test.ts
  • test/integration/test/cli.emit-command.additional.test.ts
  • test/integration/test/cli.emit-command.test.ts
  • test/integration/test/sql-builder/extension-functions.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/1-framework/1-core/config/test/config-validation.test.ts
  • packages/1-framework/1-core/config/src/config-types.ts
  • test/integration/test/cli.emit-command.additional.test.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • test/integration/test/sql-builder/extension-functions.test.ts
  • packages/2-mongo-family/2-authoring/contract-ts/test/config-types.test.ts
  • packages/1-framework/3-tooling/cli/test/control-api/contract-emit.test.ts
  • packages/1-framework/3-tooling/cli/test/control-api/client.test.ts
  • packages/2-sql/2-authoring/contract-ts/src/config-types.ts
  • packages/1-framework/1-core/config/src/config-validation.ts
  • test/integration/test/cli.emit-command.test.ts
  • packages/1-framework/3-tooling/cli/test/commands/migration-e2e.test.ts

Comment thread packages/1-framework/3-tooling/vite-plugin-contract-emit/src/plugin.ts Outdated
Comment thread packages/1-framework/3-tooling/cli/src/config-path-validation.ts Outdated
Copy link
Copy Markdown
Contributor

@SevInf SevInf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tentatively approving, with one minor issue:
Default output handling is now duplicated between defineConfig and finalizeConfig, i think we should consolidate that.

@jkomyno jkomyno enabled auto-merge (squash) April 22, 2026 13:25
@jkomyno jkomyno merged commit 73125a3 into main Apr 22, 2026
16 checks passed
@jkomyno jkomyno deleted the tml-2277-vite-plugin-watch-behavior branch April 22, 2026 13:43
jkomyno added a commit that referenced this pull request Apr 22, 2026
## Summary

- add a PSL-first Vite plugin HMR integration test that exercises
startup emit, bad edit, and recovery
- assert invalid schema edits leave the last good contract.json and
contract.d.ts on disk
- assert the next valid edit re-emits both artifacts

## Testing

- pnpm --filter @prisma-next/integration-tests exec vitest run
test/vite-plugin.hmr.e2e.test.ts
- pnpm --filter @prisma-next/integration-tests typecheck
- pnpm exec biome check
test/integration/test/vite-plugin.hmr.e2e.test.ts

## Notes

- Stacked on #362
- Refs TML-2293

---------

Co-authored-by: jkomyno <12381818+jkomyno@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants