| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(sql)!: AST-bound codec resolution via CodecRef (#484) closes [TML-2456](https://linear.app/prisma-company/issue/TML-2456/ast-bound-codec-resolution-replace-paramrefrefscodecid) ## Intent Codec dispatch read codec identity off the contract by triangulating a `ParamRef`'s `(table, column)` reference through `contract.storage.tables[]`. That indirection produced eight runtime workarounds whose existence had no structural justification once codec identity becomes a single fact on the AST node itself. This change moves `(codecId, typeParams)` onto the AST as `CodecRef`, replacing runtime triangulation with one content-keyed lookup. Eight legacy heuristics dissolve as a consequence. A separate, mid-branch attempt to embed pre-lowering AST in `ops.json` was retracted in the same PR when it surfaced a violation of [ADR 192](docs/architecture%20docs/adrs/ADR%20192%20-%20ops.json%20is%20the%20migration%20contract.md)'s post-lowering invariant; the deletion catalysed an explicit ADR codification of "no compilation at apply time." ## Change map - **AST shape** — `ParamRef` and `ProjectionItem` carry `codec: CodecRef | undefined` (was `codecId? + refs?`). `ParamRefBindingRefs` deleted. - **Runtime dispatch** — `forCodecRef` is the sole runtime surface. `AstCodecResolver` memoises by content key `(codecId, canonicalizeJson(typeParams))`. `forColumn(t, c)` is now a one-line delegate over `forCodecRef(codecRefForColumn(t, c))` — one cache, one materialisation, one logical codec instance per `(codecId, typeParams)`. - **Build-time stamping** — every column-bound builder site stamps `codec` from `codecRefForColumn` / `codecRefForStorageColumn`. - **Build-time integrity check** — `assertColumnCodecIntegrity` runs at `createExecutionContext` and surfaces malformed codec wiring as stable `RUNTIME.CODEC_DESCRIPTOR_MISSING` / `RUNTIME.CODEC_PARAMETERIZATION_MISMATCH` / `RUNTIME.TYPE_PARAMS_INVALID` envelopes naming the failing `(table, column)`. Replaces previously-silent defensive `if (...) continue` skips in `buildContractCodecRegistry`. - **Heuristics deleted** — `forCodecId`, `byCodecId`, `byColumn`, `parameterizedRepresentatives`, `ambiguousCodecIds`, `validateParamRefRefs`, `alias-resolver`, `factory.bind(descriptor)` casts, the `factory(undefined as unknown as ...)` representative materialisation. - **Honest descriptors** — `PgVectorCodec.length` narrows from `number | undefined` to `number`. `vectorColumn` (undimensioned) retires; `vector(N)` is the canonical authoring surface. - **Refs-less raw SQL guard** — `toExpr(rawValue)` without `codec` throws `RUNTIME.PARAM_REF_CODEC_REQUIRED` at build time, naming the JS type and the remediation. - **`canonicalizeJson` hardening** — null-prototype accumulator in `sortKeys` closes a `__proto__` pollution surface for cache keys derived from caller-controlled `typeParams`. - **`ops.json` invariant codified** — [ADR 192](docs/architecture%20docs/adrs/ADR%20192%20-%20ops.json%20is%20the%20migration%20contract.md) and [ADR 028](docs/architecture%20docs/adrs/ADR%20028%20-%20Migration%20Structure%20&%20Operations.md) gain explicit "No compilation at apply time" sections; the migration tooling README adds "What `ops.json` does NOT contain." `CodecRef` is a build-time concept that does not survive lowering — every committed `ops.json` in the repo verifies clean (`grep '"codec"\|"codecId"\|"typeParams"'` returns 0). - **Documentation** — [ADR 212 (new)](docs/architecture%20docs/adrs/ADR%20212%20-%20AST-bound%20codec%20resolution.md), ADR 208 amended (its `forColumn`-based dispatch description superseded with strikethrough + retrospective callout), `relational-core/DEVELOPING.md` added with the CodecRef-on-AST invariant for AST authors. ## The story The implementation walked seven milestones plus an architect-lens follow-up and one mid-branch course-correction. **M1** added foundations (`CodecRef`, `AstCodecResolver`, relocated `canonicalizeJson` into a runtime-reachable layer). **M2** introduced the parallel surface (`codecRefForColumn`, `forCodecRef` warm pre-population) without touching legacy paths. **M3** is the pivot, split into three sub-commits: M3a made `PgVectorDescriptor` honest about its signature and retired `vectorColumn` (breaking change); M3b atomically swapped the AST shape (71 files) and migrated every builder site and test; M3c deleted the eight heuristics. **M4** added the build-time guard for refs-less raw SQL. **M5 was attempted, then retired in the same PR.** The original framing introduced a `dataTransformAst` op + `parseAnyQueryAst` deserializer + apply-time runner integration so that pre-lowering AST could be embedded in `ops.json` and re-lowered at apply time. Architect / security review surfaced that this **violated ADR 192's "no compilation at apply time" invariant** — `ops.json` must carry the post-lowering execution form (Mongo: typed driver-AST commands; SQL: `(sql_template, params[])`), and the runner is a dispatcher, not a compiler. Commit `aba6ed942` deleted the op, the parser, the apply-time `lowerAst` branch in `runner.ts`, the `AST_BOUND_SENTINEL`, the `destinationContract` threading, and the demo migration. Commit `9b547a9ca` codified the invariant explicitly in ADR 192 / ADR 028 and the migration tooling README — the deletion catalysed the documentation that was previously implicit. Commit `9d289e140` rescoped AC-7 in `spec.md` and recorded M5 retirement in `plan.md`. The follow-up to bring SQL `ops.json` serialisation into structural symmetry with Mongo's typed driver-AST is tracked as [TML-2491](https://linear.app/prisma-company/issue/TML-2491). `CodecRef` remains a build-time-only concept; every committed `ops.json` is verified clean. **M6** documented the design ([ADR 212](docs/architecture%20docs/adrs/ADR%20212%20-%20AST-bound%20codec%20resolution.md)). **M7** swept stale references and ran the full validation gate. A **post-close architect review** then surfaced one structural follow-up: the legacy `byColumn` map was still materialising codecs through a parallel path, leaving two distinct `Codec` instances per logical column. Commit `1cc757743` collapsed `forColumn` onto the resolver so the spec's "one fact, one location" promise holds structurally rather than rhetorically. The architectural-identity test in `sql-context.codec-context.test.ts` (`forColumn(t, c) === forCodecRef(codecRefForColumn(t, c))` with `toBe`) makes the dual-cache regression unrecoverable. A **fresh-eyes principal-engineer review** ([code-review.md](projects/ast-codec-binding/reviews/pr-484/code-review.md)) then identified that the load-bearing "synchronous `paramsSchema`" invariant was enforced at three boundaries inconsistently — the build-time integrity check silently passed Promise-returning validators, deferring the diagnostic to a later dispatch site. Commits `341b2e87d` and `88991f5b6` tightened the integrity-check probe to fail fast at `createExecutionContext` and added regression tests across all three enforcement seams, promoting the invariant from "convention" to "fail-fast at contract load." ## Behavior changes & evidence - **AST nodes carry `codec: CodecRef | undefined`.** - **Why**: unifies codec identity into a single, content-keyed fact the resolver can match directly. - **Implementation**: [`packages/2-sql/4-lanes/relational-core/src/ast/types.ts`](packages/2-sql/4-lanes/relational-core/src/ast/types.ts), [`packages/1-framework/1-core/framework-components/src/shared/codec-types.ts`](packages/1-framework/1-core/framework-components/src/shared/codec-types.ts) - **Evidence**: [`packages/2-sql/4-lanes/relational-core/test/ast/common.test.ts`](packages/2-sql/4-lanes/relational-core/test/ast/common.test.ts), [`packages/2-sql/5-runtime/test/encoding-dispatch.test.ts`](packages/2-sql/5-runtime/test/encoding-dispatch.test.ts) - **`forCodecRef` is the sole runtime dispatch surface; `forColumn` delegates through it.** - **Why**: content-keyed memoisation collapses encode/decode dispatch; one logical `(codecId, typeParams)` resolves to exactly one `Codec` instance regardless of the surface that asked for it. Stateful codecs reading `SqlCodecInstanceContext.usedAt` see a consistent aggregation. - **Implementation**: [`packages/2-sql/5-runtime/src/codecs/ast-codec-resolver.ts`](packages/2-sql/5-runtime/src/codecs/ast-codec-resolver.ts), [`packages/2-sql/5-runtime/src/sql-context.ts`](packages/2-sql/5-runtime/src/sql-context.ts), [`packages/2-sql/5-runtime/src/codecs/encoding.ts`](packages/2-sql/5-runtime/src/codecs/encoding.ts), [`packages/2-sql/5-runtime/src/codecs/decoding.ts`](packages/2-sql/5-runtime/src/codecs/decoding.ts) - **Evidence**: [`packages/2-sql/5-runtime/test/ast-codec-resolver.test.ts`](packages/2-sql/5-runtime/test/ast-codec-resolver.test.ts), [`packages/2-sql/5-runtime/test/sql-context.codec-context.test.ts`](packages/2-sql/5-runtime/test/sql-context.codec-context.test.ts) (architectural identity test asserts `forColumn(t, c) === forCodecRef(codecRefForColumn(t, c))`) - **Build-time integrity check fails fast on malformed contracts.** - **Why**: previously, malformed wiring (missing descriptor, parameterized-codec-without-typeParams, non-parameterized-codec-with-typeParams, async `paramsSchema`) was silently filtered in `buildContractCodecRegistry`. Failing fast at the construction seam surfaces stable diagnostics naming the failing `(table, column)` and codecId. - **Implementation**: [`packages/2-sql/5-runtime/src/sql-context.ts`](packages/2-sql/5-runtime/src/sql-context.ts) (`assertColumnCodecIntegrity`) - **Evidence**: [`packages/2-sql/5-runtime/test/codec-integrity.test.ts`](packages/2-sql/5-runtime/test/codec-integrity.test.ts) - **`vectorColumn` (undimensioned) retires; `vector(N)` is the only authoring surface.** - **Why**: `PgVectorDescriptor.paramsSchema` rejects `undefined`; the undimensioned form had no honest descriptor signature once the resolver took over dispatch. - **Implementation**: [`packages/3-extensions/pgvector/src/core/codecs.ts`](packages/3-extensions/pgvector/src/core/codecs.ts), [`packages/3-extensions/pgvector/src/exports/column-types.ts`](packages/3-extensions/pgvector/src/exports/column-types.ts) - **Evidence**: [`packages/3-extensions/pgvector/test/codecs.test.ts`](packages/3-extensions/pgvector/test/codecs.test.ts), [`packages/3-extensions/pgvector/README.md`](packages/3-extensions/pgvector/README.md) - **`toExpr(rawValue)` throws at build time without an explicit codec.** - **Why**: a `ParamRef` without a `CodecRef` cannot be dispatched; silent fallback was a correctness risk. - **Implementation**: [`packages/2-sql/4-lanes/relational-core/src/expression.ts`](packages/2-sql/4-lanes/relational-core/src/expression.ts) - **Evidence**: [`packages/2-sql/4-lanes/sql-builder/test/raw-sql-codec-required.test.ts`](packages/2-sql/4-lanes/sql-builder/test/raw-sql-codec-required.test.ts) - **`CodecRef` does not survive lowering; `ops.json` carries no codec metadata.** - **Why**: codifies the post-lowering invariant ([ADR 192](docs/architecture%20docs/adrs/ADR%20192%20-%20ops.json%20is%20the%20migration%20contract.md)). The lowerer consumes `paramRef.codec` and produces JSON-safe wire values; `ops.json` carries the post-lowering execution form (today: `(sql_template, params[])`); the runner is a dispatcher, not a compiler. - **Implementation**: removed in commit `aba6ed942` (`dataTransformAst`, `parseAnyQueryAst`, apply-time `lowerAst` branch, `AST_BOUND_SENTINEL`, demo migration); codified in commit `9b547a9ca` (ADR 192 / ADR 028 / migration README amendments). - **Evidence**: workspace-wide `grep '"codec"\|"codecId"\|"typeParams"'` across every `ops.json` returns 0 lines; workspace-wide `rg 'dataTransformAst|parseAnyQueryAst|AST_BOUND_SENTINEL|lowerAst|destinationContract'` returns 0 hits outside review artefacts. ## Compatibility / migration / risk **Breaking changes (rollup):** - `vectorColumn` removed from `@prisma-next/extension-pgvector/column-types`. **Migration:** `field.column(vectorColumn)` → `field.column(vector(N))` with `N` matching the column's vector dimension. - `ContractCodecRegistry.forCodecId` removed. **Migration:** call `forCodecRef({ codecId, typeParams })` instead. - `ParamRef` and `ProjectionItem` constructor signatures change (`codec: CodecRef | undefined` replaces `codecId + refs`). `ParamRefBindingRefs` interface deleted. Internal AST consumers must migrate. - `toExpr(rawValue)` without an explicit `codec` throws `RUNTIME.PARAM_REF_CODEC_REQUIRED`. **Migration:** pass an explicit `CodecRef` or use a column-bound builder path. - Two non-parameterized columns sharing a `codecId` now resolve to the same `Codec` instance (was: distinct per-column instances). This matches the spec's "one fact, one location" promise; stateful codecs reading `SqlCodecInstanceContext.usedAt` now see one aggregated entry per logical codec. - `CodecDescriptor.paramsSchema` must be a **synchronous** Standard Schema validator. Promise-returning validators throw `RUNTIME.TYPE_PARAMS_INVALID` at `createExecutionContext` (previously deferred to first dispatch). Extension authors registering parameterized codecs must ensure their validator is sync. - **Mid-branch retraction:** an intermediate iteration on this branch briefly shipped a `dataTransformAst` op factory, a `parseAnyQueryAst` AST deserialiser, an `AST_BOUND_SENTINEL` marker, and a demo migration that exercised them. **All four were deleted in commit `aba6ed942`** when the post-lowering invariant was codified. If you tracked this branch between M5 and the retraction and consumed any of those public surfaces, migrate to the standard `dataTransform` op (which lowers at build time and serialises `(sql_template, params[])`); the symmetric typed driver-AST follow-up is tracked as [TML-2491](https://linear.app/prisma-company/issue/TML-2491). **Validation gates:** `pnpm typecheck` (127/127), `pnpm build` (63/63), `pnpm lint:deps` (clean, 786 modules / 1597 deps), `pnpm test:packages` (7660/7665 — 5 pre-existing PGlite/CLI workspace-load flakes confirmed by isolated re-run on `origin/main`), `pnpm test:e2e` (91/91), `pnpm test:integration` (104/115 — pre-existing CLI-on-PATH e2e flakes). ## Review artefacts Three side-by-side fresh-eyes review artefacts were produced for this PR (architect lens, principal-engineer lens, tech-lead walkthrough) under [`projects/ast-codec-binding/reviews/pr-484/`](projects/ast-codec-binding/reviews/pr-484/): - [`system-design-review.md`](projects/ast-codec-binding/reviews/pr-484/system-design-review.md) — architect lens; verdict: conceptually clean, two non-blocking observations (canonicalizer duplication, instance-name string-union typology). - [`code-review.md`](projects/ast-codec-binding/reviews/pr-484/code-review.md) — principal-engineer lens; **AC verification: 8 PASS / 0 FAIL / 0 NOT VERIFIED**. The two in-PR findings (F01 async-paramsSchema integrity-check hole; F03 missing regression coverage) are addressed by commits `341b2e87d` and `88991f5b6`. - [`walkthrough.md`](projects/ast-codec-binding/reviews/pr-484/walkthrough.md) — tech-lead narrative; reading guide for tour-the-PR-in-15-minutes. ## Follow-ups Out of scope for this PR (each tracked separately): - [TML-2491](https://linear.app/prisma-company/issue/TML-2491) — symmetric SQL post-lowering driver-AST + serializer for `ops.json` (the architectural symmetry-with-Mongo work that the misframed M5 was groping toward; design docs included in the ticket). - [TML-2442](https://linear.app/prisma-company/issue/TML-2442) — Mongo parameterised codecs can adopt the same `CodecRef` shape now that the type lives family-agnostically in `framework-components/codec`. - Architect-lens naming / modelling clean-ups (`AstCodecResolver` reads as consumer-named; `codecRefForColumn` vs `codecRefForStorageColumn` symmetry; `SqlMigrationPlanOperationStep` discriminated union after `AST_BOUND_SENTINEL` retreat; retire `canonicalizeJson` in favour of foundation's `canonicalStringify`; instance-name string-union typology). Worth a single small ticket. - Pattern-catalogue promotion of "Integrity assertion at the construction seam" — recurs at ≥4 sites in the codebase ([`system-design-review.md` § 4.5](projects/ast-codec-binding/reviews/pr-484/system-design-review.md)). - Pre-existing PGlite stability flakes (`planner.reconciliation`, `collection-dispatch`, `runner.basic.integration`) deserve a separate stabilisation ticket. ## Non-goals - Mongo-side adoption (out of scope; foundations are in place — `CodecRef` is family-agnostic). - Async codec lifecycle changes (ADR 204 unchanged). - SQL post-lowering driver-AST / typed serialiser (deferred to TML-2491). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added `CodecRef` type and `canonicalizeJson` utility to public exports for codec identity and JSON canonicalization use cases. * Introduced AST-bound codec resolution system with content-keyed memoization. * **Documentation** * Updated architectural documentation (ADRs 208, 212) to detail new codec identity resolution and semantics. * **Breaking Changes** * pgvector: Removed `vectorColumn`; use `vector(N)` factory for dimensioned vectors only. * AST codec node structure now uses `CodecRef` instead of separate `codecId` and `refs`. [](https://app.coderabbit.ai/change-stack/prisma/prisma-next/pull/484) <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Will Madden <madden@prisma.io> | 4 个月前 | |
feat: an application depends on one Prisma package (ADR 242) (#29864) ## What changes for someone using Prisma Today, an application that talks to Postgres installs a long list of our packages: ```jsonc { "dependencies": { "@prisma-next/postgres": "...", "@prisma-next/sql-runtime": "...", "@prisma-next/sql-orm-client": "...", "@prisma-next/target-postgres": "...", "@prisma-next/adapter-postgres": "...", "@prisma-next/sql-contract": "..." // ...a dozen more } } ``` After this PR, it installs one: ```jsonc { "dependencies": { "@prisma/orm-postgres": "0.16.0" } } ``` Everything else arrives as that package's own dependencies. Three of our example apps are converted in this PR to prove it — one per database — and each has exactly one Prisma package in its `dependencies`. This implements [ADR 242](https://github.com/prisma/prisma/pull/29852), which is already merged. ## What gets published 17 packages, all under the `@prisma` scope: - **3 database packages** — `@prisma/orm-postgres`, `orm-sqlite`, `orm-mongo`. An application installs exactly one. We call these *facades*: each is a small package that wires its database together and re-exports everything an application needs. - **6 extension packs** — PostGIS, pgvector, ParadeDB, Supabase, arktype-json, middleware-cache. Optional, installed alongside a database package. - **7 platform packages** — the framework, the toolchain, one per database family, one per database target. Applications never install these directly; they arrive as dependencies. Extension authors do install them. - **the `prisma` command**, as a bin-only package. Every other workspace package — around 50 of them — stops being published. They still exist in the repo as the unit we organise code in; they just stop having a life on the registry. **This PR does not make that switch yet.** It builds and proves the new surface while leaving today's publish list exactly as it is. Flipping it is a separate change. ## The problem this design has to avoid A published package can't depend on packages that won't exist on the registry. So each published package *contains a compiled copy* of the internal packages it covers. That creates a trap. If one application ends up with the same code twice — once inside a published package, once as its own package — then classes, registries, and anything compared by reference exist twice too. An `instanceof` check quietly returns false. Nothing crashes, nothing fails to compile, and both copies behave identically in isolation. You find out much later, somewhere unrelated. So the rule the whole design follows is: **every piece of internal code is published from exactly one package.** Concretely, that means: - Each published package is built in one pass, so code shared between its own entry points exists once. Verified from the build's source maps: no module appears in more than one chunk, in any published package. - When one published package needs code from another, it imports it as a real dependency rather than compiling in a second copy. - A facade re-exports from the platform packages; it never carries its own copy. `@prisma/orm-postgres/orm-client` and `@prisma/orm-family-sql/orm-client` are two names for the same object, and there's a test that asserts exactly that from installed tarballs. - One table in `packages/0-shared/publish-surface` maps every internal package to where it's published. The build, the code generator, and the lint checks all read it, so there's one answer to "where does this live" rather than three that can drift. ## Generated code follows the application Prisma writes imports into your project — contract types and migration files. Those imports have to name packages your project actually depends on, or they won't resolve. So the generator now reads the `package.json` next to the config it's generating for. A project that depends on `@prisma/orm-postgres` gets imports from that package. A project on today's names keeps today's names. Nothing to configure, because the manifest already says which it is. Contract hashes are unaffected, and that isn't an assumption — hashes are computed from a structure that import text never enters, and there's a test asserting the hash is identical across naming schemes *while* the emitted imports demonstrably differ. ## What stops the trap coming back Two checks, because the failure is silent and won't show up in a test suite: - Every example app and test project must use one naming scheme, not a mix. `lint-single-import-root` scans them and fails the build if any project imports from both, since that's the situation that loads code twice. - `lint-consumer-internal-imports` counts how many internal-package imports remain in those projects and compares against a committed number. It fails if the number goes up (someone added one) and also if it goes down without the number being updated (so improvements get locked in). Target is zero. The build itself also refuses to proceed if the published-package map would put one module in two places, or if a published package's `package.json` no longer matches what its code actually needs. ## Reading this PR It's large — 257 files — because it's a migration. The commits are grouped and meant to be read in order: 1. **Platform packages** — the build mechanism, and the seven platform packages it produces. 2. **Database packages, extension packs, the `prisma` command** — completes the set of 17. 3. **Generated imports become configurable** — one place decides which names get written, with today's names still the default. 4. **Database-family symmetry, publishing the map, the identity checks.** 5. **One package per application** — the three converted examples, the re-exports they proved necessary, and the counting check. 6. **Migration files follow the project too.** One thing worth knowing while reading: re-exporting a package republishes all of its sub-paths, not just the one that was needed. This PR adds 115 published sub-paths across the three database packages. Two candidates were dropped for exactly that reason — see below. ## Alternatives considered **Let an application install platform packages alongside its facade.** Nothing would need re-exporting and the facades would stay thinner. Rejected: an application would again juggle several Prisma dependencies whose correct combination it maintains by hand, and getting it wrong — upgrading one and not the other — produces the silent two-copies failure above. Re-exporting costs a generated line and nothing at runtime. **Re-export everything an application might plausibly want.** Rejected in review: because re-exporting brings a package's entire sub-path surface, generosity is expensive and hard to undo. Migration tooling (54 sub-paths) was dropped because its only users are extension packs, which install platform packages anyway; the SQL driver re-export was dropped because nothing imported it at all. What remains is what a converted example actually needed. **Flip the publish list in this same PR.** Rejected: it would mix "does the new surface work" with "is it safe to stop publishing 50 packages" in one review. The switch is mechanical once this lands, and gets its own change. ## Verification `build`, `typecheck` (156 tasks), `test:packages` (1077 files / 14087 tests), `test:e2e`, `lint`, `lint:deps`, `lint:docs`, `lint:manifests`, `check:publish-deps`, `check:clean-tree`, `lint:casts` and `lint:throws` (no new instances), `test:scripts`, coverage, the tarball-install suites, and regenerating every committed artifact leaves the tree unchanged. Known-unstable and unrelated to this change: the `relation-mode-gh-*` port suites (TML-3140), and several test timeouts that are too tight under load. ## Follow-ups TML-3124 switch the publish list · TML-3127 build cache can validate a stale published package on CI · TML-3140 unstable port suites · TML-3141 a test-helper sub-path reaches a package that is never published. 🤖 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** - Added consolidated public ORM packages for PostgreSQL, MongoDB, SQLite, framework tooling, database targets, and extensions. - Generated contracts, migrations, and scaffolds now adapt imports to the consuming project’s package surface. - Added facade-provided `prisma-next` CLI access and consolidated migration entrypoints. - **Documentation** - Updated installation, package naming, public entrypoint, and migration scaffolding guidance. - **Tests** - Added coverage for package installation, exports, CLI behavior, module identity, and import compatibility. - **Chores** - Added checks preventing incompatible internal and public package imports. <!-- 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 <noreply@anthropic.com> | 2 个月前 | |
feat: an application depends on one Prisma package (ADR 242) (#29864) ## What changes for someone using Prisma Today, an application that talks to Postgres installs a long list of our packages: ```jsonc { "dependencies": { "@prisma-next/postgres": "...", "@prisma-next/sql-runtime": "...", "@prisma-next/sql-orm-client": "...", "@prisma-next/target-postgres": "...", "@prisma-next/adapter-postgres": "...", "@prisma-next/sql-contract": "..." // ...a dozen more } } ``` After this PR, it installs one: ```jsonc { "dependencies": { "@prisma/orm-postgres": "0.16.0" } } ``` Everything else arrives as that package's own dependencies. Three of our example apps are converted in this PR to prove it — one per database — and each has exactly one Prisma package in its `dependencies`. This implements [ADR 242](https://github.com/prisma/prisma/pull/29852), which is already merged. ## What gets published 17 packages, all under the `@prisma` scope: - **3 database packages** — `@prisma/orm-postgres`, `orm-sqlite`, `orm-mongo`. An application installs exactly one. We call these *facades*: each is a small package that wires its database together and re-exports everything an application needs. - **6 extension packs** — PostGIS, pgvector, ParadeDB, Supabase, arktype-json, middleware-cache. Optional, installed alongside a database package. - **7 platform packages** — the framework, the toolchain, one per database family, one per database target. Applications never install these directly; they arrive as dependencies. Extension authors do install them. - **the `prisma` command**, as a bin-only package. Every other workspace package — around 50 of them — stops being published. They still exist in the repo as the unit we organise code in; they just stop having a life on the registry. **This PR does not make that switch yet.** It builds and proves the new surface while leaving today's publish list exactly as it is. Flipping it is a separate change. ## The problem this design has to avoid A published package can't depend on packages that won't exist on the registry. So each published package *contains a compiled copy* of the internal packages it covers. That creates a trap. If one application ends up with the same code twice — once inside a published package, once as its own package — then classes, registries, and anything compared by reference exist twice too. An `instanceof` check quietly returns false. Nothing crashes, nothing fails to compile, and both copies behave identically in isolation. You find out much later, somewhere unrelated. So the rule the whole design follows is: **every piece of internal code is published from exactly one package.** Concretely, that means: - Each published package is built in one pass, so code shared between its own entry points exists once. Verified from the build's source maps: no module appears in more than one chunk, in any published package. - When one published package needs code from another, it imports it as a real dependency rather than compiling in a second copy. - A facade re-exports from the platform packages; it never carries its own copy. `@prisma/orm-postgres/orm-client` and `@prisma/orm-family-sql/orm-client` are two names for the same object, and there's a test that asserts exactly that from installed tarballs. - One table in `packages/0-shared/publish-surface` maps every internal package to where it's published. The build, the code generator, and the lint checks all read it, so there's one answer to "where does this live" rather than three that can drift. ## Generated code follows the application Prisma writes imports into your project — contract types and migration files. Those imports have to name packages your project actually depends on, or they won't resolve. So the generator now reads the `package.json` next to the config it's generating for. A project that depends on `@prisma/orm-postgres` gets imports from that package. A project on today's names keeps today's names. Nothing to configure, because the manifest already says which it is. Contract hashes are unaffected, and that isn't an assumption — hashes are computed from a structure that import text never enters, and there's a test asserting the hash is identical across naming schemes *while* the emitted imports demonstrably differ. ## What stops the trap coming back Two checks, because the failure is silent and won't show up in a test suite: - Every example app and test project must use one naming scheme, not a mix. `lint-single-import-root` scans them and fails the build if any project imports from both, since that's the situation that loads code twice. - `lint-consumer-internal-imports` counts how many internal-package imports remain in those projects and compares against a committed number. It fails if the number goes up (someone added one) and also if it goes down without the number being updated (so improvements get locked in). Target is zero. The build itself also refuses to proceed if the published-package map would put one module in two places, or if a published package's `package.json` no longer matches what its code actually needs. ## Reading this PR It's large — 257 files — because it's a migration. The commits are grouped and meant to be read in order: 1. **Platform packages** — the build mechanism, and the seven platform packages it produces. 2. **Database packages, extension packs, the `prisma` command** — completes the set of 17. 3. **Generated imports become configurable** — one place decides which names get written, with today's names still the default. 4. **Database-family symmetry, publishing the map, the identity checks.** 5. **One package per application** — the three converted examples, the re-exports they proved necessary, and the counting check. 6. **Migration files follow the project too.** One thing worth knowing while reading: re-exporting a package republishes all of its sub-paths, not just the one that was needed. This PR adds 115 published sub-paths across the three database packages. Two candidates were dropped for exactly that reason — see below. ## Alternatives considered **Let an application install platform packages alongside its facade.** Nothing would need re-exporting and the facades would stay thinner. Rejected: an application would again juggle several Prisma dependencies whose correct combination it maintains by hand, and getting it wrong — upgrading one and not the other — produces the silent two-copies failure above. Re-exporting costs a generated line and nothing at runtime. **Re-export everything an application might plausibly want.** Rejected in review: because re-exporting brings a package's entire sub-path surface, generosity is expensive and hard to undo. Migration tooling (54 sub-paths) was dropped because its only users are extension packs, which install platform packages anyway; the SQL driver re-export was dropped because nothing imported it at all. What remains is what a converted example actually needed. **Flip the publish list in this same PR.** Rejected: it would mix "does the new surface work" with "is it safe to stop publishing 50 packages" in one review. The switch is mechanical once this lands, and gets its own change. ## Verification `build`, `typecheck` (156 tasks), `test:packages` (1077 files / 14087 tests), `test:e2e`, `lint`, `lint:deps`, `lint:docs`, `lint:manifests`, `check:publish-deps`, `check:clean-tree`, `lint:casts` and `lint:throws` (no new instances), `test:scripts`, coverage, the tarball-install suites, and regenerating every committed artifact leaves the tree unchanged. Known-unstable and unrelated to this change: the `relation-mode-gh-*` port suites (TML-3140), and several test timeouts that are too tight under load. ## Follow-ups TML-3124 switch the publish list · TML-3127 build cache can validate a stale published package on CI · TML-3140 unstable port suites · TML-3141 a test-helper sub-path reaches a package that is never published. 🤖 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** - Added consolidated public ORM packages for PostgreSQL, MongoDB, SQLite, framework tooling, database targets, and extensions. - Generated contracts, migrations, and scaffolds now adapt imports to the consuming project’s package surface. - Added facade-provided `prisma-next` CLI access and consolidated migration entrypoints. - **Documentation** - Updated installation, package naming, public entrypoint, and migration scaffolding guidance. - **Tests** - Added coverage for package installation, exports, CLI behavior, module identity, and import compatibility. - **Chores** - Added checks preventing incompatible internal and public package imports. <!-- 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 <noreply@anthropic.com> | 2 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 4 个月前 | ||
| 2 个月前 | ||
| 2 个月前 |