| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(config): the orm section is declared once as a schema, and the engine resolves its paths (#30372) ## At a glance The `orm` section of `prisma.config.ts` is declared once, with the fields that are paths marked as such: ```ts // packages/1-framework/3-tooling/config-loader/src/orm-section.ts export const ormConfigSchema = configSchema({ family: { kind: "'family'", ...descriptorFields, emission: 'object' }, target: { kind: "'target'", ...targetLikeFields }, adapter: { kind: "'adapter'", ...targetLikeFields }, 'driver?': { kind: "'driver'", ...targetLikeFields }, 'extensions?': [{ kind: "'extension'", ...targetLikeFields }, '[]'], 'db?': { 'connection?': 'unknown' }, 'contract?': { source: { load: 'Function', 'inputs?': 'path[]', 'format?': 'string' }, output: ['path', '=', () => 'src/prisma/contract.json'], }, migrations: [{ dir: ['path', '=', () => './migrations'] }, '=', () => ({})], 'formatter?': { 'indent?': "number.integer >= 1 | 'tab'", 'newline?': "'LF' | 'CRLF'" }, }).narrow(/* familyId and targetId agreement across descriptors */); ``` Given this project and this invocation: ``` exp/ sub/ prisma.config.ts # orm: ormConfig({ contract: './contract.prisma', migrations: { dir: './migrations' } }) contract.prisma ``` | Run from | Command | Before | After | | --------- | ------------------------------------------------------ | -------------------------------------------------------------- | ------------------------------ | | `exp/sub` | `prisma contract emit --config ./prisma.config.ts` | writes `exp/sub/contract.json` | same | | `exp` | `prisma contract emit --config ./sub/prisma.config.ts` | `CONTRACT.SOURCE_LOAD_FAILED`: looks for `exp/contract.prisma` | writes `exp/sub/contract.json` | Config files are unchanged. ## The decision A relative path in `prisma.config.ts` is relative to the file that wrote it. Only the ORM knows which of its fields are paths; only the CLI engine knows which file wrote each value, because its chain merge records that per key. The schema declaration puts the first where the second can use it. The engine derives validation, a `CLI.CONFIG_FIELD_INVALID` diagnostic per bad field naming the file to fix, and the resolution of every `path` field, from the one declaration; the ORM writes no validation, resolution, or path-anchoring code. This is [prisma/prisma-cli ADR 0005](https://github.com/prisma/prisma-cli/blob/main/docs/architecture/adrs/0005-config-sections-declare-their-shape.md), shipped in engine 0.6.0 by prisma/prisma-cli#279; every product that mounts commands declares its section this way. ## Why it broke The ORM's own `prisma` bin resolved config paths in its loader against the config file. The unified `prisma` CLI loads the config through the engine's loader, which handed the `orm` section over as written; the ORM's command wrapper then resolved paths itself, and the only directory it could see was the working directory. The same defect existed a second time in `projectConfigPathFor`, which rebuilt `<cwd>/prisma.config.ts` to find the project's `package.json` and, from a parent directory, read the wrong manifest. ## What changes - **`@internal/config-loader`** declares `ormConfigSchema` and `ormConfigSection` (`defineConfigSection({ name: 'orm', schema })`). It is the lowest package that can depend on the engine; `@internal/cli` and `@internal/cli-telemetry` consume the section from it. Path defaults are thunks, which arktype evaluates when the default is applied, so they resolve against the config file like authored values. - **Descriptors are declared references.** A control descriptor is a runtime object the config file constructs: `create` closes over module state, and its codec tables, contract serializer and migration hooks rely on their prototypes and on `this`. The schema declares each descriptor, and `db.connection`, with the engine's `reference(schema)` (prisma/prisma-cli#284), so the command receives the object the config file built. The schema checks only a descriptor's identifying fields: `kind`, `id`, `familyId`, `version`, `create`, and `targetId` or `emission`. Every value not declared a reference is copied before paths are resolved and defaults applied. Cross-descriptor rules (`familyId` and `targetId` agreement, the removed `extensionPacks` key) are one function, which the schema's `narrow` and the loader both call. - **A codec without params has no `paramsSchema`.** A codec that took no params used to declare `paramsSchema = voidParamsSchema`, a shared schema accepting only `undefined`, and `isParameterized` asked whether a descriptor's schema was that exact object. A copied descriptor carries a copy of that schema, so every codec on it reported itself parameterized, which is how `db init` failed with `Invalid typeParams for codec 'pg/text@1'`. `paramsSchema` is now `StandardSchemaV1<P> | undefined`, a codec without params sets it to `undefined`, and `isParameterized` is `paramsSchema !== undefined`, which no copy can change. Type-param validation still rejects `typeParams` for such a codec with `RUNTIME.TYPE_PARAMS_INVALID`. `voidParamsSchema` is removed; [`upgrade-instructions/pending/codec-without-params-schema/extension/`](https://github.com/prisma/orm/blob/feat/orm-config-schema/upgrade-instructions/pending/codec-without-params-schema/extension/instructions.md) tells extension authors how to follow. - **The ORM's bin** hands the engine each evaluated file with its sections as written (`loadConfigFiles`); the engine validates the merged section with that provenance before a command runs. `loadConfig` runs the same schema for the language server and the vite plugin, wrapping each field diagnostic as `CONFIG.VALIDATION_FAILED` with the subsection it concerns, so `requireConfigSections` keeps working. - **Commands** read absolute paths and `baseDir`. The command wrapper's cwd finalisation, `finalize-config.ts`, `collectConfigIssues` and its hand-written descriptor checks, and `projectConfigPathFor` are deleted. The migration path helpers take only the config. Control API operations that located the project through `configPath` take `projectDir`; `resolveMigrationPaths` takes the config. - **`@prisma/cli-engine` moves to 0.6.1** (0.6.0 plus prisma/prisma-cli#280 and #284: a schema declares the values it keeps as references) in `@internal/cli`, `@internal/config-loader`, `@prisma/orm-toolchain`'s peer, the four extension packages, and the integration test package. The `defineConfig` → `definePrismaConfig` rename the bump requires landed separately in #30129. Examples and fixture apps consume published packages and keep their pins. - **Diagnostics under the unified CLI change code.** A malformed `orm` field is now reported by the engine as `CLI.CONFIG_FIELD_INVALID` (one per field, `meta.section: 'orm'`, `meta.field` the dotted path, `where.path` the config file that declared it) under `CLI.CONFIG_SECTION_INVALID`. `CONFIG.VALIDATION_FAILED` remains what the ORM's own loader raises for the language server and the vite plugin. The error reference records the split; the two integration files that asserted the old code are updated. - **Docs**: `config-validation-and-normalization.mdc` now describes the schema as the single home of structural rules, `loadConfigFiles`/`loadConfig` as evaluation plus diagnostics, and path resolution as the schema's job; the CLI Style Guide says relative paths in the config file resolve against the file that wrote them, with `--output-path` the one path relative to cwd; the loader README follows. The arktype rule now says to read the arktype docs before building validation machinery and never to read arktype's compiled node tree, and `docs/reference/arktype-usage.md` records what a transformation does to its input: any pipe or default makes arktype clone the whole input, and the default clone rebuilds plain objects and class instances. The codec authoring guide, the two codec ADRs and the package READMEs declare codecs without params with `paramsSchema = undefined`. ## Tests - `framework-components/test/materialize-codec.test.ts`: a descriptor copied the way arktype's default clone copies it keeps `isParameterized` for codecs with and without params (this test fails before the change), and a codec without params rejects `typeParams`. - `config-loader/test/orm-section.test.ts`: the schema accepts a valid config, supplies the migrations dir and default contract output, records `baseDir`, resolves inputs, output and migrations dir against the config file, leaves absolute paths alone, keeps a descriptor's class instances and functions, and the source's `load`, as the file built them (closures, prototypes and `this` survive), reports missing descriptors and descriptor field problems, family and target mismatches on target, adapter, driver and extensions, the removed `extensionPacks` key, contract, migrations and formatter problems, keeps fields the schema does not name on descriptors and on the contract source, and never throws on hostile input. `load.test.ts` still passes unchanged apart from the finalise module going away. - `@internal/cli`: `contract emit` and `migration plan` reached with `--config sub/prisma.config.ts` from the parent read and write under `sub/`, the plan test exercising the manifest walk from `baseDir`; the bin loader hands the engine the requested file with paths as written; ORM command tests seed the engine with a `prisma.config.ts` in the run directory through one shared helper so the engine validates the seed as it would a real file. Verified locally against a build of prisma/prisma-cli#280 overlaid on the installed engine: | Package | Result | | --- | --- | | `@internal/config` | 5 tests | | `@internal/config-loader` | 53 tests | | `@internal/cli` | 116 files, 1484 tests | | `@internal/cli-telemetry` (incl. the real-Postgres e2e) | 113 tests | | `@internal/vite-plugin-contract-emit` | 31 tests | | extensions (paradedb, pgvector, supabase, postgis) | 390 tests | | integration | 788 files, 4224 tests | | all package suites after the codec change | 83 tasks; the first run's 7 failures (tests that expected every codec to have a schema, and a bundled comment naming an internal package) fixed and rerun green | | repo | build, typecheck, lint, `lint:deps`, `lint:casts`, rules lints, `fixtures:check`, upgrade coverage pass | CI on this PR is red until `@prisma/cli-engine@0.6.1` is published and the pin here moves to it; the earlier red run (270 integration failures) was arktype's default clone rebuilding descriptors, which 0.6.1 fixes. Verified locally with a 0.6.1 build: `config-loader` (53), the CLI package (1484), and the config-related integration files (200) pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Configuration diagnostics now identify the affected field, section, and config file. * Relative paths in config files—including extended configs—resolve from the file that declares them. Command-line output paths remain relative to the working directory. * Config loading retains details about each file in an extended configuration chain. * **Bug Fixes** * Malformed configuration sections receive more specific diagnostics, while commands can proceed when errors affect sections they do not read. * **Breaking Changes** * Legacy configuration-validation exports and config-path operation options are no longer available. * `voidParamsSchema` is no longer exported. Codecs without parameters should set `paramsSchema` to `undefined`; non-empty type parameters are rejected, while empty parameters are accepted. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 4 小时前 | |
A native enum column is not textual: text operations stop accepting it, and a conformance test enforces the trait (#30390) ## Linked issue n/a — no Linear ticket. Follow-up recorded in [#30386](https://github.com/prisma/orm/pull/30386). ## Skill update n/a — the skill reference never described native enums as text-searchable. The pending upgrade fragment `native-enum-not-textual` tells app and extension authors what changed. ## At a glance ```ts db.orm.public.Ticket.where((t) => t.status.ilike('open%')); // before: compiles, then fails in Postgres: operator does not exist: ticket_status ~~* unknown // after: type error — a native enum column has no text operations ``` ## Decision The native Postgres enum codec `pg/enum@1` no longer declares the `textual` trait. A codec's traits are its claim about what the database type supports, and Postgres has no `LIKE`, `ILIKE`, `to_tsvector`, or text-search parser for an enum type. So `like`, `ilike`, the three full-text operations, the tsquery parsers, and `@@fullTextIndex` stop accepting native enum columns, turning a runtime failure into a compile error. No operation special-cases enums; each one follows the corrected trait. A new conformance test in the Postgres codec testkit enforces what `textual` means: for every built-in codec that declares it, Postgres must accept the column in `ILIKE`, `to_tsvector`, `websearch_to_tsquery`, and a `to_tsvector` GIN index. A future codec that misdeclares the trait fails that test. ## Reviewer notes - **Only native enums change.** A PSL `enum` block with `@@type("pg/text@1")` is stored as text and keeps every text operation. - **`min`/`max` over native enums still work.** They used to come through the `textual` rule in the aggregate table; the enum codec now joins the existing per-codec `min`/`max` list beside the temporal types. Deriving `min`/`max` from `order` would claim codecs Postgres has no `min`/`max` for, as the list's own comment explains. - **The conformance test was committed first and failed on `pg/enum@1`** in all four checks with SQLSTATE `42883`, then passes with the fix. A negative control keeps it able to fail: it asserts an enum column is refused in all four places. - **A cast would not have rescued full-text search on enums.** `to_tsvector(col::text)` runs, but Postgres refuses to index it because the enum-to-text conversion is not immutable. - **Extension-visible.** An extension operation declared on `textual` no longer attaches to native enum columns; the extension fragment says so. ## Behavior changes & evidence - Native enum columns lose `like`, `ilike`, `fullTextMatches`, `fullTextRank`, `fullTextHeadline`, and parser input; they keep `eq`, `in`, ordering, and `min`/`max`. Implementation: [codecs.ts](packages/3-targets/3-targets/postgres/src/core/codecs.ts), [aggregates.ts](packages/3-targets/3-targets/postgres/src/core/aggregates.ts). Evidence: [native-enum-operations.test-d.ts](test/integration/test/enum-order-by/native-enum-operations.test-d.ts), [full-text.test-d.ts](packages/3-targets/3-targets/postgres/test/full-text.test-d.ts). - `@@fullTextIndex` / `fullTextIndex` on a native enum column is refused when the contract is built (`PSL_FULL_TEXT_INDEX_TEXT_FIELD`, `CONTRACT.INDEX_INVALID`). - Every `textual` codec is checked against a live Postgres. Evidence: [textual-trait-conformance.integration.test.ts](packages/3-targets/6-adapters/postgres-codec-testkit/test/textual-trait-conformance.integration.test.ts). ## Testing performed - Target package typecheck, lint, test; codec testkit (186 tests, including the new conformance test against a dev database). - `test/integration` `sql-builder`, `sql-orm-client`, `enum-order-by`: 1015 tests. - `pnpm build`, `pnpm typecheck:packages --concurrency=1`, examples typecheck, `pnpm lint`; `lint:casts` and `lint:throws` unchanged. - `pnpm fixtures:check`, `pnpm check:error-reference`, `pnpm check:upgrade-coverage --mode pr --prev main --head HEAD`. ## Alternatives considered - **A codec capability for rendering a column as text**, with enums declaring a `::text` cast that text operations lower through. Rejected: it needs a second capability for "indexable as text", since the cast cannot be indexed, and its only benefit is pattern or full-text search over a small fixed set of labels, where equality is the right tool. - **Special-casing enums in the full-text operations.** Rejected: the trait was wrong, so every `textual` operation was broken on enums, not just full-text search. ## Checklist - [x] All commits are signed off (`git commit -s`) per the [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). - [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is scoped to one logical concern. - [x] Tests are updated. - [ ] The PR title is in `TML-NNNN: <sentence-case title>` form — no ticket. - [x] The **Skill update** section above is filled in. ## Notes for the reviewer See **Reviewer notes** above. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Native PostgreSQL enum columns are no longer treated as text. Pattern matching, full-text search, and full-text indexes now reject them before a database error occurs. * `min` and `max` continue to preserve native enum values and their type. * **Documentation** * Added upgrade guidance for changes to enum text operations, with alternatives for pattern matching and full-text search. * Clarified which database capabilities the `textual` trait represents. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 3 小时前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 4 小时前 | ||
| 3 小时前 |