| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
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> | 1 个月前 | |
TML-3165: count/sum/avg return JS numbers, with lossless variants beside them (#29930) ## Linked issue Refs [TML-3165](https://linear.app/prisma-company/issue/TML-3165/native-number-aggregate-defaults-countcountbigint-sumsumbigint) — slice 08, the last of the [Codec JSON projections](https://linear.app/prisma-company/project/codec-json-projections-a10fba2e9cd5) project. **Stacked on [#29922](https://github.com/prisma/prisma/pull/29922)** (TML-3164) and targets that branch until it merges, then advances to `main`. Prerequisites: slice 06 ([#29902](https://github.com/prisma/prisma/pull/29902), merged) supplied the codecs this names; slice 07 (#29922) made the operation set a contribution, so this PR adds three operations without touching a line of client or lane code. Follow-ups filed: [TML-3187](https://linear.app/prisma-company/issue/TML-3187/schema-written-literals-are-not-application-values-give-them-their-own). ## At a glance ```ts const stats = await db.orm.Order.aggregate((agg) => ({ orders: agg.count(), // number — was bigint cents: agg.sum('amountCents'), // number — was bigint; throws past 2^53 mean: agg.avg('amountCents'), // number — was a decimal string exactOrders: agg.countBigInt(), // bigint exactCents: agg.sumBigInt('amountCents'), // bigint, exact past 2^63 exactMean: agg.avgDecimal('amountCents'), // decimal string })); ``` Since the aggregate hard cut, `count()` returned a `bigint` — which `JSON.stringify` refuses — and integer `avg()` returned a decimal string. Correct, but not what a JS developer expects. This restores the expected types without restoring the silent corruption they used to hide: where a value cannot fit, the codec throws. ## Decision The aggregate vocabulary splits in two, by policy: 1. **Bare operations answer in JS-native types.** `count()` and `sum()` over integers return `number` through a guarded codec that raises `RUNTIME.DECODE_FAILED` past the safe-integer range rather than handing back a rounded value. `avg()` returns `number` through `float8` — a mean is a fraction already, so there is nothing to guard. 2. **Suffixed operations are lossless.** `countBigInt()`, `sumBigInt()`, `avgDecimal()`. Offered over every integer input, including those whose bare form is already lossless, so the escape hatch is uniform rather than something you learn column by column. 3. **Bare operations over Float and Decimal columns stay in the column's own family** — those users already chose their representation. `min`/`max` return the column's own type and are untouched. 4. **A non-nullable aggregate descriptor now declares `emptyResultJson`** — the empty-input answer in its result codec's canonical JSON. It belongs to the operation, not the codec: `count`'s identity is zero, but a `every()` would answer `true`. Classic Prisma is the prior art — `BigInt` columns are `bigint` there while `count` is a `number` — but where it casts down in the engine, this throws at the boundary. ## Reviewer notes - **Read the two matrices first** (`packages/3-targets/3-targets/{postgres,sqlite}/src/core/aggregates.ts`). They are the entire judgment; everything else derives. Every row was probed against a live database before it was authored. - **Three facts are load-bearing, and each has a test that fails if it is quietly substituted.** `sumBigInt` over `int8` reads PostgreSQL's `numeric` through `pg/unboundedint@1` rather than casting to `int8` — the cast is exercised *as a negative in the same test*, raising `bigint out of range` over the data the shipped row reads exactly. `avg` casts the **result**, not the input, pinned on a dataset where the two genuinely differ (`4503599627370497` vs `...496`). `emptyResultJson` cannot be omitted: the type is a discriminated union, so a `nullable: false` descriptor without it does not compile. - **Three substrate repairs the matrices exposed rather than caused**, each a stale assumption that held only while every non-nullable aggregate decoded through a bigint codec. SQLite's number-flavoured codec needed a JSON projection (its transport cast renders a JSON *string* inside an envelope, so every SQLite include aggregate was failing to decode — and no test covered that path, which is why CI stayed green over it). The integer codecs now distinguish a wrong JS type from a wrong magnitude — which uncovered that the bigint codecs had been *silently accepting* JS numbers, so `1.5` could reach an integer column as `'1.5'`. And the DDL renderers compose `encode(decodeJson(stored))` instead of feeding canonical JSON to `encode`, which also fixed a `timestamptz` default handed an ISO string where the codec declares a `Date`. - **One acknowledged stopgap.** Tightening those guards broke `BigInt @default(0)`: a schema language writes no `bigint`, so PSL literals arrive as JSON numbers, and emission of the Supabase extension's contract stopped. `encodeJson` now accepts a safe-integer `number` (guarded — integral, in-range) while the wire `encode` stays strict. The proper seam is TML-3187. Reviewed as safe: `encodeJson` is unreachable from the runtime parameter path. - **~100 regenerated `contract.d.ts` files.** All movement is inside `export type AggregateTypes` — verified mechanically: `git diff -U0` yields 247 hunks under that one header and no other. No `contract.json` and no migration fixture moved. - A local fresh-eyes review ran before this PR; its three MUST-FIX findings were all in the documentation, not the code, and are fixed here. ## How it fits together 1. **The PostgreSQL matrix** — the policy, probed and authored, with database-backed conformance evidence. 2. **The SQLite matrix** — the same policy in SQLite's terms; `avgDecimal` is not contributed (no decimal), and its absence is asserted as unavailability rather than a runtime error. 3. **The substrate repairs** — the three above, at their source. 4. **The sweep** — contracts regenerated, every moved expectation classified as *mechanical form change* or *corrected defect*; five tests re-expressed against `sumBigInt` because they asserted that a wide bare `sum` survives, which the policy now forbids. 5. **The record** — upgrade instructions in both clusters, a 13-pattern docs sweep, ADR 020 and the descriptor guide. ## Behavior changes & evidence - **`count()`/`sum()` return `number` and throw past 2^53** rather than rounding — on the wire path *and* the include/JSON path, where the value is emitted as a JSON number, rounded by `JSON.parse`, and refused by the post-parse guard. Evidence: [integer-representation.test.ts](test/integration/test/sql-orm-client/integer-representation.test.ts), both cases with whole error shapes. - **`sumBigInt()` is exact past 2^63** on PostgreSQL. Evidence: [aggregate-defaults.integration.test.ts](packages/3-targets/6-adapters/postgres-codec-testkit/test/aggregate-defaults.integration.test.ts) — `18446744073709551614n`, beside the `int8` cast raising. - **`avg()` returns a `number`, `avgDecimal()` a decimal string**, pinned on a non-terminating mean so the two visibly differ. - **SQLite include aggregates decode again**, as JSON numbers. Evidence: [sqlite-include-canonical-json.test.ts](test/integration/test/sql-orm-client/sqlite-include-canonical-json.test.ts) — the first committed coverage of that path. ## Testing performed - `pnpm build`, `pnpm typecheck:all` (92 tasks), `pnpm lint:deps` (no violations), `pnpm lint` — green - `pnpm test:packages` — 1113 files, 14,776 tests green; `pnpm test:e2e` — 113 green - Full unsharded `pnpm test:integration` — green apart from two host-environment files reproduced independently of this branch (`init-journey.e2e`, host pnpm; `issues-28192-pg-historical-dates`, host timezone) - `pnpm fixtures:check` green with movement fully attributable; `check:upgrade-coverage`, `check:error-reference` (274 codes), `lint:docs`, `lint:skills` green; cast ratchet `delta=-5` ## Skill update Both upgrade clusters carry entries for `8.0.0-rc.1-to-8.0.0-rc.2`: the app cluster covers the result-type flips and the integer columns now refusing a wrong JS type; the extension cluster adds the `emptyResultJson` obligation and the `encode`/`encodeJson` split. Entries slice 07 wrote in the same transition were corrected where this slice falsified them. The shipped query guide's aggregate result-type table is rewritten. ## Follow-ups - [TML-3187](https://linear.app/prisma-company/issue/TML-3187/schema-written-literals-are-not-application-values-give-them-their-own) — schema-written literals need their own codec seam, distinct from `encodeJson`'s application-value contract; includes the related gap that the TS authoring surface cannot express a `bigint` default at all. ## Alternatives considered - **Casting `sumBigInt` to `int8`** — simpler, and wrong: it reintroduces a 64-bit overflow this design does not have, and would resurrect the need for a `sumDecimal` the design discarded. - **Casting `avg`'s input rather than its result** — changes accumulation semantics; the result cast computes the exact mean once and rounds once. - **A codec-side "canonical zero" for the empty-input answer** — it can only serve operations whose identity is zero, and asks every codec in the stack a question most cannot answer. - **Skipping the transport lowering inside a JSON envelope** (the obvious fix for the SQLite defect) — wrong: the lossless variants' lowerings are semantic, not transport, so skipping them computes nothing. - **Withholding the lossless variant where the bare form is already lossless** — logically tidy, but it makes the escape hatch conditional on knowledge a caller shouldn't need. ## Checklist - [x] All commits are signed off (`git commit -s`) per the [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). The DCO status check will block merge if any commit is missing a `Signed-off-by:` trailer. - [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is scoped to one logical concern. - [x] Tests are updated (or `n/a` if the change is doc-only / refactor with no behavioural delta). - [x] The PR title is in `TML-NNNN: <sentence-case title>` form (Linear ticket prefix + concise title naming the concrete deliverable). See `.claude/skills/create-pr/SKILL.md` for the full convention. - [x] The **Skill update** section above is filled in (or stated `n/a — internal only`). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Aggregate `count`, integer `sum`, and integer `avg` results now use JavaScript numbers by default. * Added lossless `countBigInt`, `sumBigInt`, and PostgreSQL `avgDecimal` options for exact results. * Aggregate results now follow the selected database target and field representation. * **Bug Fixes** * Unsafe numeric results beyond JavaScript’s safe-integer range now raise a runtime error. * Non-nullable aggregates correctly return their defined empty-result values. * **Documentation** * Updated aggregate behavior, codec guidance, error references, and upgrade instructions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net> | 1 个月前 | |
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> | 1 个月前 | |
docs(drive): shape the consolidate-clis project (TML-3172) (#29909) Shaping PR for the CLI-consolidation project: one `prisma` CLI and one `prisma.config.ts` across Prisma 8, Composer, and the platform, before launch. Direction agreed Will/Luan 2026-08-05; this PR lands the artifacts for validation. ## What this adds Four documents under `projects/consolidate-clis/`: - **spec.md** — goals, scope, and settled design: host package `prisma` in prisma/prisma-cli; ORM and Composer as ordinary pinned dependencies with `@prisma/dev` as the only optional package; the CLI never builds the user's app and never does package management; config evaluation never errors (per-section diagnostics, versioned `defineConfig` marker); launch scope = port existing surfaces, stub Composer's control client until its programmatic API lands. - **plan.md** — three-phase execution across the three repos, with Composer's programmatic API as the long pole started first. - **cli-consolidation-plan.md** — the accepted consolidated command-tree design (Luan's target-experience doc with the agreed corrections applied: `db`/`postgres` split, `migrate` under `db`, retained Prisma Next verb conventions). - **current-state.md** — evidence base: inventory of the five existing CLIs, their config files, seams, and dependency graphs. ## Review focus - Will: implementation ownership — plan phases and launch scope. - Luan: the applied grammar corrections in cli-consolidation-plan.md match what we agreed. Refs: TML-3172 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- 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> | 1 个月前 | |
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> | 1 个月前 | |
feat(sql): declare every CHECK constraint in the contract, named by its content (#29892) Every SQL `CHECK` constraint is now declared in the contract as a SQL expression whose physical name is derived from its content. The migration planner no longer invents constraints the contract doesn't declare — it only reconciles what's declared against what's live. ## At a glance This schema: ```prisma enum Role { user admin } model User { id Int @id role Role // text-backed enum roles Role[] // enum list tags String[] // plain list } ``` emits three checks into the contract's `User` table: ```jsonc "checks": [ { "name": "User_role_check_9f21ab04", "prefix": "User_role_check", "expression": "\"role\" IN ('user', 'admin')" }, { "name": "User_roles_check_5e6f7a8b", "prefix": "User_roles_check", "expression": "\"roles\"::text[] <@ ARRAY['user', 'admin']::text[]" }, { "name": "User_tags_elem_not_null_c4d02e91", "prefix": "User_tags_elem_not_null", "expression": "array_position(\"tags\", NULL) IS NULL" } ] ``` The 8-hex suffix is a content hash of the expression — the same convention indexes and RLS policies already use (ADR 234). That one property drives everything else: - **Change an expression** → the hash changes → the name changes → the diff sees a missing constraint and an extra one → the plan drops the old and adds the new. - **Rename only the prefix** → same hash → a follow-up slice pairs old and new by hash and plans a single `RENAME CONSTRAINT`. - **Postgres reformats the predicate on introspection** (it always does) → irrelevant, because managed checks compare by *name*, never by parsing SQL back out of the database. ## What this replaces Checks used to travel two disconnected paths: 1. Enum membership checks were structured contract entries (`{ name, column, valueSet }`), planned by code that regex-parsed `pg_get_constraintdef` output back into value lists. Any predicate the two regexes didn't match was silently dropped from introspection — including Postgres's own reformatting of checks on `varchar` columns, which produced false drift and a repair plan that then failed on a duplicate name. 2. Scalar-list element-non-null checks were raw SQL synthesized *inside the planner* at `CREATE TABLE` only. Nothing else knew they existed: not the contract, not introspection, not verify. Adding a list column later silently skipped the check; dropping the constraint by hand was undetectable; and the planner was fabricating schema objects — a violation of the design rule that the planner materializes the contract and nothing more. Now there is one path: authoring renders the expression at contract-build time (a Postgres-pack hook supplies the SQL; the family composes the name, caps it at Postgres's 63-byte identifier limit, and hashes it), introspection captures every `contype = 'c'` row verbatim via `pg_get_expr` with no parsing at all, and the planner plans checks purely from diff issues. `CREATE TABLE` renders declared checks inline. ## Enforcement is scoped to tables you manage Derived checks are an enforcement opinion, so they are only emitted for `managed` tables. A contract that *describes* a schema it doesn't own — the Supabase extension mirroring `auth` under `@@control(external)` — gets no invented constraints, because verify would demand them forever and the policy forbids the plan that could create them. For inferred (pulled) schemas, the first plan offers to install the missing checks; a follow-up slice adds a per-column opt-out so pulled schemas verify clean by default (opting out never changes the declared types — runtime may then diverge from types, and that's the user's accepted trade). ## Bugs this fixes on the way - **Enum list columns emitted invalid DDL.** The old path rendered `CHECK ("roles" IN (...))` against `text[]`, which Postgres rejects — nothing enforced enum membership on list elements, and no test covered it. The `<@` containment form fixes it (proven live, including NULL-element rejection, with a mutation test showing the new e2e fails under the old renderer). - The silent-drop introspection parser and its false-drift/failed-repair class are gone with the parser itself. - The old check strategy planned against a single namespace and keyed bookkeeping without the schema name; multi-schema contracts now plan independently (pinned live). ## What existing users see This is a breaking contract change with no compatibility shims. Every enum check gets a new physical name; the first plan after upgrading converges a deployed database by dropping the old unsuffixed constraint and adding the wire-named one (requires a `destructive`-capable plan; under additive-only, the new check installs and strict verify reports the stale one until it's dropped). Hand-written checks that introspection previously couldn't see are now visible: lenient verify tolerates them, `--strict` reports them, and a destructive plan may drop them — review the first plan. Full migration steps for both audiences live in `upgrades/0.17-to-0.18/` (users and extension authors), including the `typescriptContract` options change. Recorded in **ADR 244** (new), which partially supersedes **ADR 156** — its check-constraint half; `storage.sets` remains in force — and amends ADR 234's naming rules (byte-based bound, derived-prefix truncation). ## Alternatives considered - **Structured check variants** (a discriminated union per check kind) would keep the contract free of raw SQL, but every new kind means a new IR variant across four layers, and introspection stays in the reverse-parsing business — which is where the shipped bugs lived. Indexes and RLS already store raw SQL in the contract; checks are the third instance, not a new direction. - **Exact names (no hash)** fail because Postgres reprints predicates: verbatim comparison of a stored expression against `pg_get_expr` output produces permanent false drift on every constraint. - **Adopting legacy constraints by rename** (instead of drop + add) would silently bless whatever predicate is actually live under the old name — name-based comparison would then trust it forever. Drop + add revalidates; that cost is the sound choice. - **Deduplicating identical generated checks** instead of rejecting them would hide generator bugs behind an arbitrary choice of survivor; rejection mirrors the index rule. Review artifacts (spec, system-design review, code review with acceptance-criteria verification, re-review rounds) live under `projects/sql-check-constraint-unification/`. Slice 2 (`RENAME CONSTRAINT` pairing) follows in #29894. 🤖 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 support for opaque SQL check constraints with stable, content-based names. * PostgreSQL now generates checks for enum membership and non-null array elements. * Added support for detecting, adding, removing, and adopting check constraints during migrations. * Improved handling of multibyte identifiers and expression-based constraint comparisons. * **Documentation** * Added architecture decisions and upgrade guidance for the new check-constraint behavior. * **Bug Fixes** * Corrected PostgreSQL enum-array validation and enabled related integration scenarios. <!-- 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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
Inferred PSL can carry top-level blocks beside a namespace, so a recovered enum has somewhere legal to live (#30012) ## Linked issue No Linear ticket — the operator waived tracker integration for this project. Slice 1 of [domain-enum-inference](projects/domain-enum-inference/spec.md) ([slice spec](projects/domain-enum-inference/slices/top-level-blocks-in-inferred-psl/spec.md)); follows the [sql-check-constraint-unification](projects/sql-check-constraint-unification/spec.md) project (#29892, #29894, #29928, #29959, #29972). ## At a glance A database constrains a column to a fixed set of values by hand: ```sql CREATE TABLE users (id int PRIMARY KEY, role text NOT NULL); ALTER TABLE users ADD CONSTRAINT chk_role CHECK (role IN ('user', 'admin')); ``` Pull it today and the value set is lost — `role` comes back a plain `String`. The goal of this project is to recover it as an enum. This slice does not do that yet; it removes the structural blocker standing in the way, which is that **the recovered enum has nowhere legal to live**: ```prisma namespace public { enum UsersRole { … } // PSL_ENUM_NAMESPACE_NOT_SUPPORTED — enums must be top-level model Users { … } } ``` `contract infer` wraps its output in `namespace <schema> { … }` whenever the database has a native enum or an RLS policy, and it builds exactly one namespace. So on precisely the databases that need the wrap, a recovered domain enum and the wrap were mutually exclusive. ## Decision An inferred document can carry two buckets: a flat one for content that must stay top-level, and the named one for models, native enums and policies. Nothing about the document shape needed to change. `PslDocumentAst.namespaces` is already an array, `UNSPECIFIED_PSL_NAMESPACE_ID` already names the flat bucket, and the printer already sorts that bucket first and prints its contents unwrapped — exactly so top-level declarations round-trip to top-level output. The only thing missing was that the emitter built one namespace and had no way to be given anything for the other. ```ts buildPslDocumentAst(schemaIR, options, fkExtras, namespaceName, rlsExtras, topLevelExtensionBlocks) ``` The new parameter defaults to an empty list, and an empty flat bucket is not emitted at all — so every existing `contract infer` output is byte-identical. Slice 2 fills it. ## Reviewer notes Three things in this diff were found by review rather than written that way, and each is worth a look because the first version of each was quietly wrong. **The feature was unreachable in its first form.** `topLevelExtensionBlocks` began as a local `const` empty array that nothing wrote to, so the branch deciding whether to split buckets could never be true. Deleting the entire branch left the suite green — which is what "ships untested" looks like from the inside. Making it a parameter is what put the branch within reach of a test. **The ordering assertion did not test the ordering.** It asserted the flat bucket prints before the named one, but the fixture already listed the flat bucket first and `Array.prototype.sort` is stable — so deleting the printer's comparator left it passing. The fixture now lists the named namespace first, so the sort has to move it. Verified by removing the comparator, **rebuilding `@internal/psl-printer`** (the test resolves it from `dist`, so a source edit alone proves nothing), and watching the assertion fail with `expected 183 to be less than 126`. **Two same-named namespaces silently printed every model twice.** Found while checking whether the emitter's merge was necessary. `modelNamespaceIndex` maps a model name to its namespace *name*, and each section filters the sorted model list by that name — so two entries called `__unspecified__` both claimed every flat model, and `model Widget` appeared twice in the output with nothing reporting a problem. The printer now builds one section per distinct name. Pinned by two tests confirmed to fail on the old behaviour. **One test fixture asserted a reprint Postgres would never produce.** `schema-verify.verdict.test.ts` stood in `((status)::text = 'a'::text)` for a column declared `nativeType: 'text'` whose expression had two members. Postgres emits the `(status)::text` relabel only for `varchar`, and a two-member `IN` never collapses to a single `=`. The corpus captured in this slice gives the right shape. ## Behaviour changes & evidence - **An inferred document can carry a flat bucket beside a named namespace.** `psl-infer/infer-psl-contract.ts` — evidence: `print-psl/print-psl.top-level-blocks.test.ts` covers both sides of the split, the round trip through parse + interpret into a value set, and the invisibility of an empty bucket. - **Two namespace entries sharing a name print once.** `psl-printer/src/ast-to-print-document.ts` — evidence: `print-psl-from-ast.test.ts`, both tests confirmed failing before the fix (`expected [ 'model Widget {', 'model Widget {' ] to have a length of 1`). - **No output change.** No production caller passes the new parameter; `fixtures:check` regenerates clean. ## The reprint corpus This slice also captures, against a real database, what Postgres actually prints back for every check-expression shape the project will meet — one-member and multi-member `IN` on both `text` and `varchar`, the enum-array `<@` form, and an escaped quote. Two hand-written fixtures elsewhere in the tree were wrong and are corrected against it. The corpus carries its own provenance now, in the test rather than in project docs that get deleted at close-out: observed on PGlite 17.5 via `@prisma/dev`, re-run identical on PostgreSQL 15.18 — the supported floor since [ADR 244 — PostgreSQL floor lowered to 15](docs/architecture%20docs/adrs/ADR%20244%20-%20PostgreSQL%20floor%20lowered%20to%2015.md). The shapes hold across the supported range, not just the newest server. ## Verification - `pnpm build` 86/86 · `pnpm typecheck` 165/165 · `pnpm lint` 100/100 · `lint:deps` clean · `fixtures:check` clean, no churn · cast ratchet delta 0 - Suites: target-postgres 1404 · psl-printer 58 · family-sql 339 · sql-schema-ir 255 - Real database: `check-introspection` 7/7 · `enum-check-constraint` 4/4 · `infer-roundtrip-fidelity` 19/19 ## Compatibility None. Additive parameter with a default; no emitted output changes. ## What is left in the project - **Slice 2** — harvest the literals and emit the enum: a wire-named check verifies by hash, everything else falls back to `@noCheck(membership)` plus a verbatim `@@check(map:)`. - **Slice 3** — round-trip and adoption proof against a real database. ## Alternatives considered - **Emit the recovered enum inside the namespace anyway.** Rejected outright: it is a hard diagnostic, so the pulled schema would not load. - **Never wrap in a namespace, so everything stays top-level.** The wrap exists because a top-level `native_enum` does not lower; dropping it trades this problem for that one. - **Give `PslDocumentAst` a dedicated top-level slot instead of a second namespace entry.** Cleaner to read, and a change to a shape that already expresses this — the flat bucket exists for exactly this purpose, and the printer already handles it. ## Checklist - [x] All commits carry DCO sign-off - [x] I have read CONTRIBUTING.md - [x] Tests added for every behaviour change, each confirmed to fail before the fix - [x] Title follows the prevailing convention 🤖 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** - PostgreSQL schema output now supports top-level extension blocks alongside named namespaces. - Extension blocks are validated for naming conflicts and placed consistently. - Duplicate namespaces are consolidated while retaining their models and extensions. - **Bug Fixes** - Namespace ordering is now deterministic across environments. - Improved handling of reprinted PostgreSQL check constraints and membership expressions. - **Tests** - Expanded coverage for namespace merging, extension blocks, enum constraints, and PostgreSQL introspection. - **Documentation** - Updated PostgreSQL support-floor references to ADR 248. <!-- 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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
feat(orm): rename take/skip to limit/offset (#30112) ## Linked issue n/a — no Linear ticket. ## At a glance ```ts const page2 = await db.orm.User .orderBy((u) => u.id.asc()) .offset(10) .limit(10) .all(); ``` The same ORM query previously used `.skip(10).take(10)`. ## Decision This PR ships a breaking rename of ORM collection pagination from `.take(n)` / `.skip(n)` to `.limit(n)` / `.offset(n)` across root SQL collections, relation refinements, grouped SQL collections, and Mongo ORM collections. The old ORM names are removed, while Mongo's lower-level query builder continues to expose `.skip(n)` for the native `$skip` pipeline stage. ## Reviewer notes - The implementation is concentrated in the SQL, grouped SQL, and Mongo collection classes; most of the broad diff migrates repository call sites and documentation. - Pagination semantics are unchanged. The renamed methods write the same `limit` and `offset` collection state, which still lowers to SQL `LIMIT` / `OFFSET` and Mongo `$limit` / `$skip`. - Grouped SQL pagination still requires a prior non-empty `orderBy`; only the method names changed. - This is intentionally breaking and includes rc.6-to-rc.7 app and extension upgrade instructions. ## How it fits together 1. [SQL collections](packages/3-extensions/sql-orm-client/src/collection.ts) expose `limit` and `offset`, with `first()` using the renamed limiter internally. 2. [Grouped SQL collections](packages/3-extensions/sql-orm-client/src/grouped-collection.ts) carry the same vocabulary through post-group pagination while preserving their ordering gate and separate pre-group/post-group windows. 3. [Mongo ORM collections](packages/2-mongo-family/5-query-builders/orm/src/collection.ts) expose the shared ORM names while retaining native `$skip` / `$limit` lowering and updated mutation-windowing diagnostics. 4. Examples, reference material, scorecards, and [upgrade instructions](skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.6-to-8.0.0-rc.7/instructions.md) move with the API so consumers get one consistent migration path. ## Behavior changes & evidence - **SQL ORM callers now paginate with `.limit(n)` and `.offset(n)` across root collections, includes, combinators, and aggregate input windows.** The implementation lives in [collection.ts](packages/3-extensions/sql-orm-client/src/collection.ts) and [query-plan-select.ts](packages/3-extensions/sql-orm-client/src/query-plan-select.ts); [pagination.test.ts](test/integration/test/sql-orm-client/pagination.test.ts) verifies database results and [aggregate-pagination.test.ts](packages/3-extensions/sql-orm-client/test/aggregate-pagination.test.ts) verifies aggregate scoping. - **Grouped SQL ORM callers use the renamed methods without weakening deterministic ordering.** [grouped-collection.ts](packages/3-extensions/sql-orm-client/src/grouped-collection.ts) preserves the gate, while [grouped-collection.test.ts](packages/3-extensions/sql-orm-client/test/grouped-collection.test.ts) and [grouped-pagination-gate.test-d.ts](packages/3-extensions/sql-orm-client/test/grouped-pagination-gate.test-d.ts) cover runtime planning and type-level availability. - **Mongo ORM callers use `.limit(n)` and `.offset(n)`, while plans still contain `$limit` and `$skip`.** [collection.ts](packages/2-mongo-family/5-query-builders/orm/src/collection.ts) implements the rename; [collection.test.ts](packages/2-mongo-family/5-query-builders/orm/test/collection.test.ts) verifies immutable stage construction and diagnostics, and [orm.test.ts](test/integration/test/mongo/orm.test.ts) verifies the resulting subset against MongoDB. ## Compatibility / migration / risk This is a source-breaking API rename with no deprecated aliases. Consumers must translate ORM `.take(n)` to `.limit(n)` and ORM `.skip(n)` to `.offset(n)`, including calls inside relation refinements, combinator branches, and grouped SQL chains. Mongo query-builder `.skip(n)` calls must remain unchanged. Runtime pagination behavior, cursor semantics, ordering requirements, and generated query-plan shapes do not otherwise change. ## Testing performed - `pnpm --filter @internal/sql-orm-client typecheck` - `pnpm --filter @internal/sql-orm-client test` — 771 tests - `pnpm --filter @internal/mongo-orm typecheck` - `pnpm --filter @internal/mongo-orm test` — 231 tests - Integration package typecheck plus targeted SQL, SQLite, and Mongo integration coverage — 33 tests - E2E package typecheck plus targeted SQLite ORM coverage — 18 tests - Typechecks for affected examples - `pnpm lint:deps` - `pnpm lint:skills` - `pnpm check:upgrade-coverage` - `pnpm lint:rules:symlinks` - `git diff --check` ## Skill update Updated the Prisma 8 query guidance for SQL and Mongo, and added app and extension upgrade instructions for `8.0.0-rc.6` → `8.0.0-rc.7`. The upgrade guidance explicitly preserves Mongo query-builder `.skip(n)`. ## Alternatives considered - **Keep deprecated `.take()` / `.skip()` aliases:** not chosen because Prisma Next is pre-1.0 and repository policy favors updating consumers over carrying compatibility shims. - **Rename Mongo query-builder `.skip()` too:** not chosen because the lower-level builder deliberately names native Mongo pipeline stages; `$skip` remains the correct vocabulary there. ## 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. - [x] No Linear ticket exists; the title uses a concrete issue-free format. - [x] The **Skill update** section is filled in. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Renamed ORM pagination methods from `take()` and `skip()` to `limit()` and `offset()` across SQL and Mongo collections. * Preserved existing pagination behavior, including relation refinement, grouped queries, aggregation, and cursor scenarios. * **Documentation** * Updated guides, examples, reference material, scorecards, and upgrade instructions with the new terminology. * Added migration guidance for upgrading to the latest release. * **Tests** * Updated coverage to validate `limit()` and `offset()` pagination across supported query scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Steven McClankerton <tatarintsev@prisma.io> Co-authored-by: Steven McClankerton <tatarintsev@prisma.io> | 26 天前 | |
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> | 1 个月前 | |
tml-3059: deduplicate migration contracts into a content-addressed migrations/snapshots/ store (#1018) **We now store each migration contract once, in a content-addressed store shared by the whole migrations tree — instead of copying it into every migration package that references it.** A migration's bookend contracts move out of its own directory into `migrations/snapshots/<hex>/`, and the emitted `migration.ts` imports them from there by hash: ```diff // migrations/app/20260513T0507_add_product_category_index/migration.ts - import endContract from './end-contract.json' with { type: 'json' }; + import endContract from '../../snapshots/93f0…c9e1/contract.json' with { type: 'json' }; ``` ``` migrations/ snapshots/ 93f0…c9e1/ ← one directory per distinct contract, named by its storage hash contract.json ← the canonical contract JSON contract.d.ts ← its emitted types app/ 20260513T0507_add_product_category_index/ migration.ts ← imports ../../snapshots/<hex>/contract.json migration.json ← unchanged ops.json ← unchanged ``` A migration package directory is now just `migration.ts` + `migration.json` + `ops.json`. ## Why A contract that sits between two migrations was stored twice: once as the earlier migration's `end-contract` (its destination) and again as the next migration's `start-contract` (its source) — the same bytes, in two files. Across a chain of N migrations that is roughly 2N copies of N+1 distinct contracts. This repo committed 78 `end-contract.json` and 60 `start-contract.json`; the store holds each distinct contract exactly once. The timing is not incidental: the `migrations/` directory layout freezes as public surface at RC, so this is the window to change it. ## How the store works **Keyed by storage hash.** `migration.json` already records a migration's `from` and `to` as storage hashes, so the hash *is* the address — no link file is needed to find a migration's bookend contracts. (This mirrors the Postgres control plane, which already keeps one row per distinct contract in a content-addressed `prisma_contract` table.) **Write-if-absent, made sound by canonicalization.** Writing a snapshot only checks whether `snapshots/<hex>/` already exists; if it does, the write is skipped. That is safe only because every writer canonicalizes the contract first, so two producers writing the entry for the same hash always agree on the bytes. Each write lands in a temp directory and is `rename`d into place, so an interrupted write can't leave a half-written entry. **The import path is threaded, never guessed.** The relative path from a package to the store differs by repo shape (`../../snapshots` for an app migration, `../snapshots` for an extension's own repo). It's computed once per package and threaded through the planners into the renderer, so no reader ever walks up the directory tree to find the store. **Clean break.** There is no fallback reader for the old sibling files. A committed tree that hasn't been converted fails to load with a structured `MIGRATION.CONTRACT_SNAPSHOT_MISSING` that names the missing hash and path — pointing you at the migrator (below) rather than failing silently. ## What does not change `migration.json`, `ops.json`, and every `migrationHash` are byte-identical. The contract snapshot was never part of migration identity (ADR 199), so moving it changes no migration's hash — the committed tree's history is untouched. The apply path is likewise unaffected: `migration apply` reads only `migration.json` + `ops.json` per package and never touches `snapshots/`. You can delete `migrations/snapshots/` entirely and still apply an app-space chain end to end — `snapshots/` is authoring and typechecking surface, not a runtime input. New postgres and sqlite regression tests delete the store and assert apply still succeeds. ## Converting the committed tree The bulk of the diff is the one-time conversion of every committed migrations tree, done by a committed migrator (`scripts/migrate-migrations-layout.mjs`, referenced by the upgrade notes so downstream projects can run it). It plans every migrations root read-only first and only applies once all roots plan cleanly; per migration it asserts the contract's inner `storage.storageHash` against the hash it's filed under before writing, and re-verifies every `migrationHash` is unchanged after — any drift aborts the whole run before deleting anything. Across 17 migrations roots that came to ~140 deleted sibling files, ~71 rewritten `migration.ts` import blocks, and the new store entries, with **zero `migration.json` changes**. The regeneration scripts produce byte-identical output, so `fixtures:check` stays green. ## One deliberate deviation: no gzip The RC plan item floated gzip tolerance (".json.gz accepted from day one"). It's dropped: TypeScript can't resolve a gzipped `.d.ts`, and the emitted `migration.ts`'s ESM JSON import can't decompress at import time — so gzip would break every committed `migration.ts` while only ever helping tooling that reads store files directly. (Recorded on #986.) ## Alternatives considered - **Per-migration link files** — keep the contract copies where they are and add a small file in each package naming a shared location by hash. Rejected: `migration.json`'s `from` / `to` already *are* that link. - **Keying by a full-content hash** rather than reusing `storage.storageHash`. Rejected: readers already hold the storage hash; a second hash would be computed for no added safety. - **gzip-compressed entries** — rejected, above. --- Full rationale and the accepted trade-offs (domain-surface drift under one hash; contract *source* deliberately left out of the store) are in **ADR 240**. Ships in **0.17.0**; a follow-up (TML-3072) folds the ADR-218 ref-paired snapshots into this same store so `migrations/` ends up with a single snapshot concept. --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> | 1 个月前 | |
Decode `$project`/`$addFields` aggregation reads (were returning raw BSON) (#897) ## At a glance ```ts // `_id` is stored as an ObjectId; the contract's codec decodes it to a string. const plan = db.query.from('products').project('_id', 'name').build() // $project reshapes the doc const rows = await db.execute(plan) rows[0]._id // declared type: string // runtime, BEFORE: ObjectId("66f2…") ← raw BSON; the type was lying // runtime, AFTER: "66f2…" ← decoded through the _id codec ``` A read that went through a `$project` — or any stage that reshapes the document — came back **un-decoded**: TypeScript said `_id: string`, but at runtime you got a raw `ObjectId`. This PR makes those reads decode their fields, so the runtime value matches the type you were promised. ## The decision Reads that go through a **reshaping** aggregation stage now decode their result fields through the contract's codecs — exactly like an ordinary `find`. This PR delivers that for **`$project` and `$addFields`**, and fixes `$vectorSearch`. `$group`, `$unwind`, and `$replaceRoot` follow in later PRs — this is the first slice of TML-2954. ## Why it happened A Mongo query plan carries a small description of its result — for each field, which codec decodes that position — and the runtime walks that description to decode each row (ADR 209). An ordinary read carries the model's description, so every field decodes. But the builder **gave up** the moment a stage reshaped the document. `$project` picks and renames fields, `$group` invents new ones, `$addFields` adds them — so the builder stamped the plan "shape unknown" and the runtime handed the raw BSON back untouched. The *type* was still computed correctly (`_id: string`), so the declared type and the runtime value silently disagreed. `$vectorSearch` got swept into this too, even though it doesn't reshape anything. ## What this PR does Instead of giving up, the builder now **replays the pipeline stage by stage**, rebuilding the result description as it goes: - **`$project`** copies each kept field's codec from its *source* field — a rename `{ label: '$name' }` gives `label` the `name` field's codec, never something guessed from the output name — and keeps `_id` unless you project it out. - **`$addFields`** carries the running shape and adds the new fields the same way. - **Identity stages** (`$match`/`$sort`/`$limit`/…) pass the shape straight through. - **`$vectorSearch`** returns the collection's documents unchanged, so it's reclassified as identity and vector reads now decode. The retail-store `findSimilarProducts` example drops its `db.raw` + unsafe cast for the typed builder and returns decoded `Product[]`. A field the builder **can't** attribute a codec to — a computed expression like `$concat` or `$cond` — is left as pass-through (raw). That's the honest answer, not a gap: a computed value has no single contract codec. ## Verification - `@prisma-next/mongo-query-builder` 429 tests + typecheck; a Mongo integration test that a projected `_id` comes back as a decoded **string** (it fails if this change is reverted → raw `ObjectId`). - retail-store typecheck + 55 tests + `next build`; upgrade-coverage, fixtures, and the cast-ratchet all clean. - Two stale test fixtures were updated: one had pinned the old buggy `unknown` behavior; one used a pre-refactor contract field shape the reifier now exercises. ## Alternatives considered - **Assume the model's shape for every aggregate.** Rejected — reshaping stages produce arbitrary documents, so applying the source model's codecs would decode the *wrong* fields: silent data corruption. Leaving unknown positions un-decoded is the safe failure mode. - **Make aggregate reads honestly untyped (`Record<string, unknown>`) and stop there.** Removes the lie, but you still can't get decoded rows out of an aggregation — the thing you actually want. This PR gives you both: the type and the decoded value agree. - **Guess a codec for computed expressions.** Out of scope by design — pass-through is correct for a value with no contract-attributable codec. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> | 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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
test(ports): account for first 500 Prisma engine query tests (#29924) ## Linked issue n/a — no ticket for this project batch. ## At a glance ```ts expect(() => query.public.a.select('string').groupBy()).toThrow( expect.objectContaining({ name: 'StructuredError', code: 'ORM.ARGUMENT_INVALID', message: 'Invalid groupBy arguments', }), ); ``` This is one of 201 executable cases derived from the first 500 prisma-engines query tests; the remaining cases receive explicit, reviewed non-port dispositions. ## Summary The engine query inventory was entirely unchecked, so Prisma Next compatibility and unsupported behavior were both implicit. This PR accounts for exactly the first 500 source cases and preserves case 501 as the boundary for subsequent work. ## Decision This PR ships the first engine-query accounting milestone as three explicit outcomes: 1. 180 passing ports across aggregation, scalar data types, distinct queries, relation filters, and field-reference filters. 2. 21 runnable compatibility gaps marked with `it.fails` and documented one-to-one in the canonical failure ledger. 3. 299 reviewed non-port dispositions recorded in 44 source-suite ledgers. All ports use public ORM, SQL-builder, Mongo AST, and contract-authoring surfaces. No production implementation changes are included. ## Reviewer notes - Generated contract artifacts make up most of the diff. The behavior-bearing additions are the 44 `*.test.ts` files under [`test/integration/test/ports/engines/queries/`](test/integration/test/ports/engines/queries/). - The source corpus is pinned to `prisma-engines@e922089b7d7502aff4249d5da3420f6fa55fc6ad`. - The cutoff is deliberate: case 500, `queries::filters::ported_filters::str_not_starts_with`, is checked; case 501, `str_ends_with`, and all later cases remain untouched. - Expected failures preserve executable evidence for current differences such as aggregate cursor/pagination handling, JSONB and enum decoding, scalar-list defaults, and cursor semantics. This PR does not fix those production gaps. - Every runnable port was reviewed and executed individually before the final combined validation pass. ## How it fits together 1. Co-located fixtures reproduce each source suite's relevant PostgreSQL or MongoDB schema and retain generated contract artifacts for deterministic execution. 2. Public Prisma Next query surfaces translate the upstream operation while preserving inputs, database-side behavior, and complete assertions. 3. Executable divergences remain in the suite as `it.fails`, paired with precise current-behavior explanations in [`failing.md`](test/integration/test/ports/engines/failing.md). 4. Cases without a faithful public translation are recorded one test per line under [`non-ported/queries`](test/integration/test/ports/engines/non-ported/queries/). 5. [`engines-queries.md`](projects/port-all-tests/checklists/engines-queries.md) links every one of the first 500 source identifiers to exactly one disposition. 6. The new [implementer](projects/port-all-tests/briefs/engine-implementer.md) and [reviewer](projects/port-all-tests/briefs/engine-reviewer.md) briefs codify source-file batch limits, individual execution, fidelity review, and single-writer finalization. ## Behavior changes & evidence - **Aggregation cases now run through Prisma Next's public query APIs.** Coverage includes count, average, min/max/sum, group-by, HAVING, and relation counts in [`queries/aggregation`](test/integration/test/ports/engines/queries/aggregation/), with representative evidence in [`group_by.test.ts`](test/integration/test/ports/engines/queries/aggregation/group_by/group_by.test.ts) and [`group_by_having.test.ts`](test/integration/test/ports/engines/queries/aggregation/group_by_having/group_by_having.test.ts). - **Scalar and native PostgreSQL behavior is exercised against faithful schemas.** The ports cover BigInt, Boolean, Bytes, DateTime, Decimal, enums, Float, Int, JSON, strings, native PostgreSQL types, and relation traversal under [`queries/data_types`](test/integration/test/ports/engines/queries/data_types/), including [`postgres.test.ts`](test/integration/test/ports/engines/queries/data_types/native/postgres/postgres.test.ts). - **Filter behavior now includes scalar, relation, list, JSON, and field-reference cases.** The public ORM and SQL-builder paths are exercised under [`queries/filters`](test/integration/test/ports/engines/queries/filters/), with field-reference evidence in [`relation_filter.test.ts`](test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/relation_filter.test.ts) and [`json_filter.test.ts`](test/integration/test/ports/engines/queries/filters/field_reference/json_filter/json_filter.test.ts). - **Known compatibility differences stay executable and visible.** The 21 expected failures are documented in [`failing.md`](test/integration/test/ports/engines/failing.md) and exercised by suites such as [`sum.test.ts`](test/integration/test/ports/engines/queries/aggregation/sum/sum.test.ts) and [`json.test.ts`](test/integration/test/ports/engines/queries/data_types/json/json.test.ts). - **Unsupported cases have precise source-level accounting.** The 299 entries across [`non-ported/queries`](test/integration/test/ports/engines/non-ported/queries/) distinguish unavailable connectors, APIs, authoring features, and engine-specific protocols instead of treating them as generic skips. ## Compatibility / migration / risk There are no production API, runtime, or migration changes. The review risk is test fidelity: each port must retain the source schema, operation, inputs, and full assertions rather than merely exercise similar behavior. The reviewer protocol and one-to-one checklist accounting are intended to make that fidelity auditable. ## Testing performed - Focused engine suite: 44 files passed; 180 tests passed; 21 expected failures; no type errors. - `pnpm build` — 86 tasks successful. - `cd test/integration && pnpm typecheck` — passed. - `pnpm lint` — 101 tasks successful; informational pre-existing Biome diagnostics only. - `pnpm fixtures:check` — passed with no fixture drift. - `git diff --check` — passed. - Accounting validation — 500 checked, 373 unchecked; 180 PASS, 21 `test.fails`, 299 non-ported; no inboxes remain. ## Skill update n/a — internal test inventory and review workflow only; no user-facing API or CLI surface changes. ## Follow-ups - Continue from case 501 in a separate milestone. - Address the production gaps captured by the 21 expected failures independently from this accounting PR. ## Alternatives considered - **Port beyond case 500 in the same PR:** not chosen because the fixed boundary keeps the review and accounting milestone auditable. - **Mark every non-running case as skipped:** not chosen because skips do not distinguish unavailable public surfaces from known executable incompatibilities. - **Use internal runtime access or test-only production changes to force ports:** not chosen because those translations would not represent behavior available to Prisma Next consumers. - **Fix compatibility gaps while porting:** not chosen because mixing production fixes into the accounting work would obscure whether each source case was translated faithfully. ## 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 — n/a, this work has no Linear ticket. - [x] The **Skill update** section above is filled in. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Tests** - Expanded PostgreSQL integration coverage for aggregations, grouping, relation counts, distinct queries, filters, field references, relations, and scalar data types. - Added validation for BigInt, Decimal, JSON, enums, bytes, dates, lists, null handling, pagination, and nested relations. - Added regression coverage for relation-filter and one-to-one behaviors. - Improved access to the connected database client in integration tests. - **Documentation** - Documented unsupported or non-portable scenarios across providers and advanced query patterns. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Steven McClankerton <tatarintsev@prisma.io> Signed-off-by: Steven McClankerton <tatarintsev+clanker@prisma.io> Co-authored-by: Steven McClankerton <tatarintsev@prisma.io> | 1 个月前 | |
Inferred PSL can carry top-level blocks beside a namespace, so a recovered enum has somewhere legal to live (#30012) ## Linked issue No Linear ticket — the operator waived tracker integration for this project. Slice 1 of [domain-enum-inference](projects/domain-enum-inference/spec.md) ([slice spec](projects/domain-enum-inference/slices/top-level-blocks-in-inferred-psl/spec.md)); follows the [sql-check-constraint-unification](projects/sql-check-constraint-unification/spec.md) project (#29892, #29894, #29928, #29959, #29972). ## At a glance A database constrains a column to a fixed set of values by hand: ```sql CREATE TABLE users (id int PRIMARY KEY, role text NOT NULL); ALTER TABLE users ADD CONSTRAINT chk_role CHECK (role IN ('user', 'admin')); ``` Pull it today and the value set is lost — `role` comes back a plain `String`. The goal of this project is to recover it as an enum. This slice does not do that yet; it removes the structural blocker standing in the way, which is that **the recovered enum has nowhere legal to live**: ```prisma namespace public { enum UsersRole { … } // PSL_ENUM_NAMESPACE_NOT_SUPPORTED — enums must be top-level model Users { … } } ``` `contract infer` wraps its output in `namespace <schema> { … }` whenever the database has a native enum or an RLS policy, and it builds exactly one namespace. So on precisely the databases that need the wrap, a recovered domain enum and the wrap were mutually exclusive. ## Decision An inferred document can carry two buckets: a flat one for content that must stay top-level, and the named one for models, native enums and policies. Nothing about the document shape needed to change. `PslDocumentAst.namespaces` is already an array, `UNSPECIFIED_PSL_NAMESPACE_ID` already names the flat bucket, and the printer already sorts that bucket first and prints its contents unwrapped — exactly so top-level declarations round-trip to top-level output. The only thing missing was that the emitter built one namespace and had no way to be given anything for the other. ```ts buildPslDocumentAst(schemaIR, options, fkExtras, namespaceName, rlsExtras, topLevelExtensionBlocks) ``` The new parameter defaults to an empty list, and an empty flat bucket is not emitted at all — so every existing `contract infer` output is byte-identical. Slice 2 fills it. ## Reviewer notes Three things in this diff were found by review rather than written that way, and each is worth a look because the first version of each was quietly wrong. **The feature was unreachable in its first form.** `topLevelExtensionBlocks` began as a local `const` empty array that nothing wrote to, so the branch deciding whether to split buckets could never be true. Deleting the entire branch left the suite green — which is what "ships untested" looks like from the inside. Making it a parameter is what put the branch within reach of a test. **The ordering assertion did not test the ordering.** It asserted the flat bucket prints before the named one, but the fixture already listed the flat bucket first and `Array.prototype.sort` is stable — so deleting the printer's comparator left it passing. The fixture now lists the named namespace first, so the sort has to move it. Verified by removing the comparator, **rebuilding `@internal/psl-printer`** (the test resolves it from `dist`, so a source edit alone proves nothing), and watching the assertion fail with `expected 183 to be less than 126`. **Two same-named namespaces silently printed every model twice.** Found while checking whether the emitter's merge was necessary. `modelNamespaceIndex` maps a model name to its namespace *name*, and each section filters the sorted model list by that name — so two entries called `__unspecified__` both claimed every flat model, and `model Widget` appeared twice in the output with nothing reporting a problem. The printer now builds one section per distinct name. Pinned by two tests confirmed to fail on the old behaviour. **One test fixture asserted a reprint Postgres would never produce.** `schema-verify.verdict.test.ts` stood in `((status)::text = 'a'::text)` for a column declared `nativeType: 'text'` whose expression had two members. Postgres emits the `(status)::text` relabel only for `varchar`, and a two-member `IN` never collapses to a single `=`. The corpus captured in this slice gives the right shape. ## Behaviour changes & evidence - **An inferred document can carry a flat bucket beside a named namespace.** `psl-infer/infer-psl-contract.ts` — evidence: `print-psl/print-psl.top-level-blocks.test.ts` covers both sides of the split, the round trip through parse + interpret into a value set, and the invisibility of an empty bucket. - **Two namespace entries sharing a name print once.** `psl-printer/src/ast-to-print-document.ts` — evidence: `print-psl-from-ast.test.ts`, both tests confirmed failing before the fix (`expected [ 'model Widget {', 'model Widget {' ] to have a length of 1`). - **No output change.** No production caller passes the new parameter; `fixtures:check` regenerates clean. ## The reprint corpus This slice also captures, against a real database, what Postgres actually prints back for every check-expression shape the project will meet — one-member and multi-member `IN` on both `text` and `varchar`, the enum-array `<@` form, and an escaped quote. Two hand-written fixtures elsewhere in the tree were wrong and are corrected against it. The corpus carries its own provenance now, in the test rather than in project docs that get deleted at close-out: observed on PGlite 17.5 via `@prisma/dev`, re-run identical on PostgreSQL 15.18 — the supported floor since [ADR 244 — PostgreSQL floor lowered to 15](docs/architecture%20docs/adrs/ADR%20244%20-%20PostgreSQL%20floor%20lowered%20to%2015.md). The shapes hold across the supported range, not just the newest server. ## Verification - `pnpm build` 86/86 · `pnpm typecheck` 165/165 · `pnpm lint` 100/100 · `lint:deps` clean · `fixtures:check` clean, no churn · cast ratchet delta 0 - Suites: target-postgres 1404 · psl-printer 58 · family-sql 339 · sql-schema-ir 255 - Real database: `check-introspection` 7/7 · `enum-check-constraint` 4/4 · `infer-roundtrip-fidelity` 19/19 ## Compatibility None. Additive parameter with a default; no emitted output changes. ## What is left in the project - **Slice 2** — harvest the literals and emit the enum: a wire-named check verifies by hash, everything else falls back to `@noCheck(membership)` plus a verbatim `@@check(map:)`. - **Slice 3** — round-trip and adoption proof against a real database. ## Alternatives considered - **Emit the recovered enum inside the namespace anyway.** Rejected outright: it is a hard diagnostic, so the pulled schema would not load. - **Never wrap in a namespace, so everything stays top-level.** The wrap exists because a top-level `native_enum` does not lower; dropping it trades this problem for that one. - **Give `PslDocumentAst` a dedicated top-level slot instead of a second namespace entry.** Cleaner to read, and a change to a shape that already expresses this — the flat bucket exists for exactly this purpose, and the printer already handles it. ## Checklist - [x] All commits carry DCO sign-off - [x] I have read CONTRIBUTING.md - [x] Tests added for every behaviour change, each confirmed to fail before the fix - [x] Title follows the prevailing convention 🤖 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** - PostgreSQL schema output now supports top-level extension blocks alongside named namespaces. - Extension blocks are validated for naming conflicts and placed consistently. - Duplicate namespaces are consolidated while retaining their models and extensions. - **Bug Fixes** - Namespace ordering is now deterministic across environments. - Improved handling of reprinted PostgreSQL check constraints and membership expressions. - **Tests** - Expanded coverage for namespace merging, extension blocks, enum constraints, and PostgreSQL introspection. - **Documentation** - Updated PostgreSQL support-floor references to ADR 248. <!-- 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> | 1 个月前 | |
feat(sql): Prisma 7 users point Prisma 8 at their existing schema.prisma instead of running contract infer and hand-fixing it (#30287) ## At a glance A Prisma 7 project has a `prisma/schema.prisma`. To run Prisma 8 beside it today, the user runs `prisma contract infer` against the database, deletes the `PrismaMigrations` model it picks up, adds `@@map` to every model, loses relation field names, ORM-side defaults and `@updatedAt`, and does it all again after every Prisma 7 migration. With this PR, `prisma.config.ts` points at the file they already have: ```ts // prisma.config.ts import { definePrismaConfig } from 'prisma/config'; import { defineConfig as ormConfig, prisma7Schema } from '@prisma/orm-postgres/config'; export default definePrismaConfig({ orm: ormConfig({ contract: prisma7Schema('prisma/schema.prisma'), db: { connection: process.env['DATABASE_URL']! }, }), }); ``` The normal commands then work unchanged: ```bash prisma contract emit # reads schema.prisma, writes contract.json + contract.d.ts prisma db sign # verifies against the database Prisma 7 built: zero findings ``` After every Prisma 7 migration the user runs `contract emit` and `db sign` again. Nothing is hand-edited. ## The decision Prisma 8 accepts the Prisma 7 schema language as a first-class contract source, interpreted straight into the contract. Prisma 7 keeps owning the database and its migrations; Prisma 8 adopts it read-only. [ADR 252](<docs/architecture docs/adrs/ADR 252 - An earlier Prisma version's schema is a contract source.md>) records the decision and the rules below. 1. **Fidelity means `db verify` reports nothing** against the database Prisma 7 built. `db verify` compares column types and nullability, defaults, primary key columns, foreign key actions, unique columns, index and check names, and native enums with member order. The interpreter reproduces exactly that, and leaves key, foreign key, and unique constraint names to Prisma 8. 2. **Every construct is either described exactly or a hard error.** No warnings, nothing dropped silently. An error names the construct, its line, and an edit that is valid in Prisma 7, with what that edit does to Prisma 7's next migration and to the Prisma 7 client. 3. **Prisma 8's own checks are not relaxed to admit a construct.** Where Prisma 8 PSL cannot spell something — an optional generated field, `@updatedAt` with a storage default — the source reports a hard error. Those refusals are a list of features to build, recorded in the project spec; nothing here loosens the parser or the interpreter to hide them. ## What changed for every Prisma 8 user Most of this PR is a new surface, but running real Prisma 7 DDL through `db verify` exposed defects on shared paths. Each fix is here with a regression test. - **The parser change is off by default.** Attributes on enum members and field lines inside a `view` block are read only under `grammar: 'prisma7'`, which only this source passes. A Prisma 8 PSL schema still rejects exactly what it rejected before, and the formatter now throws on a node kind it cannot print instead of writing a truncated block. - **`db verify` reads more default spellings.** A negative or cast numeral (`'-1'::integer`), an enum literal cast to a type in another schema, a zoneless `timestamp` literal, and `ARRAY[...]` list defaults are now read as the values they are. Columns that were reported as drift now verify clean; nothing that verified before starts failing. A schema-qualified mixed-case type name such as `audit."AuditAction"` also compares correctly now. Of 41 existing Prisma 8 contracts planned, applied and verified, 24 went from a false mismatch to clean and none went the other way. - **Introspection reads in a pinned session.** Defaults, check constraints, index predicates and policy text are read with `TimeZone = UTC`, `DateStyle = ISO, MDY`, `IntervalStyle = postgres`, and the caller's settings are restored. A contract inferred earlier from a server outside UTC that holds a `timestamptz` constant in such text shows that text once as a difference; the new text is stable. - **Migration planning renders a list literal default with its cast** (`ARRAY['1', '-2']::int8[]`), the same rendering the adapter uses for column DDL. - **`contract infer` prints each default in the form `contract emit` accepts:** quoted decimal text for `Decimal` and `Numeric`, plain digits for a large `BigInt`, quoted `NaN` and `Infinity`, and `dbgenerated(...)` for a list default holding a `NULL` element, which was previously dropped in silence. - **PSL number defaults keep every digit.** A `@default` number is read from its source text, so `Decimal @default(1.50)` stays `"1.50"` and a `BigInt` past 2^53 emits instead of failing. Contracts with such defaults get a new storage hash and need re-signing; the app upgrade instructions have the steps. - **A failing contract source reports findings, not just JSON.** `CONTRACT.SOURCE_LOAD_FAILED` now carries a `diagnostics` array — one entry per finding, with its code, its summary and, where known, its file and line — and the terminal prints them. A dotted source code travels as itself; an undotted legacy `PSL_*` code is wrapped as `CONTRACT.SOURCE_DIAGNOSTIC` with the original in `meta.code`. `meta.diagnostics` and `meta.issues` are unchanged. - **An ORM-side "now" default on a zoneless `timestamp` column no longer fails at write time.** The generator produced an instant where the codec encodes a plain date-time; Prisma 8's own `temporal.timestamp(onUpdate: now)` had the same defect. Every temporal preset now takes its generator from one codec-to-"now" lookup. ## How it works **Interpreting.** `@internal/sql-contract-prisma7` holds the dialect's rules: blocks, attributes, relation pairing, junction tables, defaults, and the diagnostics. It knows nothing about a particular database. Everything a target must answer arrives through `Prisma7TargetBinding` — the provider names it accepts, the type map, the native enum entity kind, index types, the identifier byte limit, junction relation field names, the `@updatedAt` generator per codec, and how literal defaults are read. The Postgres target exports one instance, `prisma7PostgresBinding`, and the Postgres facade wires the two together as `prisma7Schema(path)`. `defineConfig` accepts `contract: string | ContractConfig`; artifacts land beside the schema file or directory, and `output` on `defineConfig` overrides. The rules were not written from memory. `prisma@7.10.0 migrate diff` generated the SQL for every fixture that produces a contract, and that SQL is committed beside it. The ones that matter most: - Table and column names are the Prisma 7 names verbatim; `String` is `text`, `DateTime` is `timestamp(3)`, `Json` is `jsonb`, `Decimal` is `numeric(65,30)`, with `@db.*` overriding per Prisma 7's own table. - List columns are nullable, because Prisma 7 creates them without `NOT NULL`. - Native enums keep mapped values in declared order, in their `@@schema`. - `onDelete` and `onUpdate` are always written, with Prisma 7's defaults (`Restrict` or `SetNull`, and `Cascade`). - An implicit many-to-many relation becomes the junction Prisma 7 creates: table `_AToB`, primary key `(A, B)`, index `_AToB_B_index`, cascading foreign keys. Its two relation fields are named the way `contract infer` names the same table's foreign keys, so the model reads the same before and after cutover. - `@unique` becomes a unique **index** named `{table}_{cols}_key`, cut to 63 bytes as Prisma 7 cuts it, because that is what Prisma 7 creates and `db verify` tells indexes and constraints apart. - `@updatedAt`, `uuid()`, `cuid()`, `ulid()` and `nanoid()` become ORM-side generators; `cuid()` maps to cuid2, since Prisma 8 ships no cuid v1 and the column type and the opacity of the ids are the same. **Hard errors.** 22 codes, all dotted `PSL.PRISMA7_*`, each with a fixture and an entry in `docs/reference/error-reference.md`: views, `Unsupported(...)`, `@db.*` types with no Prisma 8 codec, `relationMode = "prisma"`, an enum used from another `@@schema`, index `sort`/`length`/`ops` arguments, a JSON `null` default, referential actions a required field cannot take, table and junction name collisions, an `@ignore`d field a key or relation uses, and the optional or `@default`-combined generated fields above. ## The example app `examples/prisma7-adoption` runs the public upgrade guide's story for real, in one vitest run against a `@prisma/dev` database: Prisma 7 installed as `@prisma/prisma7@7.10.0` with its own binary and config, `prisma7 migrate deploy`, Prisma 8 emitting from the same `schema.prisma`, `db sign`, `db verify` with zero findings, rows written through one client and read through the other (including tags through `_PostToTag`), then a second Prisma 7 migration, emit and sign again. Its README records two things a user following the guide meets that are not this PR's to fix: Prisma 7's peer dependency on `prisma` makes pnpm resolve the `prisma` binary to Prisma 7 unless a Prisma 8 `prisma` dev dependency is explicit, and pnpm's `no-downgrade` trust policy refuses `prisma@7.10.0` because it carries no provenance. ## How it is tested - 77 package fixtures, one per rule and per error code, run through the real Postgres pack with the expected contract or diagnostics committed; the 32 that expect a contract each carry the `migration.sql` Prisma 7.10.0 wrote, and a table-driven integration test applies each and verifies against it. - The `supported` proof schema covers every scalar, the `@db.*` overrides, number, list and temporal defaults, native enums, implicit many-to-many, `@updatedAt` and multiSchema. Prisma 7's SQL for it is applied unchanged and verified in **strict** mode, so a dropped column, index or foreign key fails the test; the expected extras are exactly what Prisma 7 creates for `@ignore` and `@@ignore`. One `timestamptz` default is checked with the session time zone outside UTC. - CLI journeys for `contract emit`, `db sign` and `db verify` from a user-shaped config, plus a journey for the hard-error exit (code 2, one diagnostic, nothing written). - The example's own test, in the examples CI job, with Prisma 7's schema engine fetched in a step before the tests. ## Known gaps Recorded, not hidden, in `projects/prisma7-contract-source/spec.md` § Deferred gaps. In short: cross-`@@schema` enum references need a feature (ADR 226 covers `@relation` only); `Bytes` and `DateTime` literal defaults are carried as the SQL literal of the default Postgres stores, which verifies exactly but prints as `dbgenerated("...")` at cutover; the Mongo PSL interpreter still ignores unknown top-level blocks, which the Mongo slice fixes. The same section lists the pre-existing defects this work found and did not fix — `contract infer` printing PascalCase tables without `@@map` and nullable lists as required, several `db init` failures, and a handful of list-default spellings that still fail verify — each with where it lives and the note that it exists on `main`. ## Alternatives considered - **A one-shot converter to a Prisma 8 file** (the original design). Rejected: the converted file drifts on every Prisma 7 migration, and every Prisma 8 spelling gap would become a lossy conversion rule. The converter survives as the cutover step on top of this source. - **Prisma 7's own parser** (`@prisma/get-dmmf`, the WebAssembly build). Rejected: its output deletes `@ignore` fields and `@@ignore` models and lists views as models, and it is a 3 MB synchronous load on the emit path. This repository depends on no Prisma 7 package. - **Porting Prisma 7's parser to TypeScript.** Unnecessary; the Prisma 8 parser needed two small grammar additions. - **`contract infer` plus hand edits.** The status quo this replaces. ## Notes for reviewers - The user-visible changes above are covered by entries in `skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.11-to-8.0.0-rc.12/`. The extension notes gain one entry: `parseRawDefault` left the `family/psl-infer` subpath for `parsePostgresDefault` on `@prisma/orm-postgres/target/default-normalizer`, same signature. - The branch merges `origin/main` at rc.11. 🤖 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 Prisma 7 schema support for generating Prisma 8 PostgreSQL contracts. - Added `prisma7Schema` configuration, contract signing and verification workflows, and a Prisma 7 adoption example. - Added support for Prisma 7 models, relations, indexes, enums, defaults, native types, and multi-schema projects. - Added improved temporal, numeric, array, and enum default handling. - **Improvements** - Contract source failures now include detailed, location-aware diagnostics. - PostgreSQL introspection is more consistent across session settings. - **Documentation** - Expanded adoption guidance, error references, upgrade notes, and troubleshooting documentation. <!-- 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 天前 | |
TML-2503: close out the Supabase integration — promote the decision log, delete the project workspaces (#990) ## What this is The Supabase integration umbrella close-out (TML-2503): the durable knowledge the project produced moves to its permanent home, and the transient project workspaces — `projects/supabase-integration/` and `projects/extension-supabase/` — are deleted. This is the deletion both dirs deferred to (extension-supabase Slice E deferred its own dir here; the umbrella README's §Close-out prescribed constituent dirs delete alongside the umbrella). ## Project DoD — verified Every constituent shipped and merged; the launch acceptance executed: | Item | Evidence | |---|---| | Slices B/C (RLS via authoring, roles first-class) | #956, #957, #950 | | Slice D (`db.supabase` secondary root) | #845 | | Slice F (complete introspected contract) | #960 | | Slice G (extension-aware `contract infer`) | #919 | | Slice E (docs, acceptance harness, close-out) | #985 | | User-facing skill (`prisma-next-supabase`) | #987 | | Launch-blocking acceptance run | executed 2026-07-15 against a real Supabase project (session pooler), 3/3 green: anon read, authenticated update-own + withCheck, service-role read, expired-JWT rejection | | Deferred items | all recorded with durable homes (package README "Known gaps"; the promoted doc's Deferred directions / Follow-on work sections) | | Final retro | lessons landed continuously in durable memory + ADRs during delivery; no unlanded lesson at close | ## Migration (1 long-lived file, rewritten at migration) `projects/supabase-integration/decisions.md` → **`docs/architecture docs/Supabase Integration.md`** (git mv, history preserved), per the umbrella README's own close-out instruction. The rewrite: a design-history header pointing at the living surfaces (package README, `examples/supabase`, the user skill, ADRs 224/226/230/234); links into the deleted workspaces stripped to plain text; the content-addressed-names draft link re-pointed at ADR 234; "Decisions still open" closed out as the design-hole resolution record; and two appended sections fold in the workspace's still-relevant remainder — **Deferred directions** (from `deferred.md` + the DX roadmap) and **Follow-on work** (FK1 discrete FK/index entities superseding ADR 161, FK2 auth/storage split, the `auth.uid()` default stretch, TML-2492, the post-launch perf/bundle deferrals). Everything else classified transient per the close-out defaults: the umbrella README (tracker), `overview.md` (its user-facing narrative is superseded by the skill + READMEs; its composition story by the ADRs and subsystem docs), the `example/` design artefact (deletion explicitly prescribed), and the extension-supabase spec/plan/slices/trace. ## Reference strip (4 files re-pointed) - `packages/3-extensions/supabase/README.md` — 4 links → the promoted doc. - `examples/supabase/README.md` — 1 link → the promoted doc. - `projects/native-postgres-enums/.../infer-native-enum-adoption/spec.md` — plan link → merged #960. - `projects/psl-ambient-declarations/spec.md` — OC1/OC4/deferred links → the promoted doc (that project actively consumes the decision log; the promoted doc keeps it grounded). Post-strip scan is clean — the only residual mentions are the scope notes in the new upgrade-instruction files, which describe the deletion itself. The README touches trip the upgrade-coverage gate, so this PR also opens the `0.15-to-0.16` transition dirs in both upgrade skills with `changes: []` + incidental scope notes. ## Verified `lint:docs`, `lint:skills`, `check:upgrade-coverage --mode pr`, `check:release-notes --mode pr` all green; markdown-only diff otherwise. With this merged, the Supabase integration is fully closed: shipped, verified against real Supabase, documented for users, and its process scaffolding retired. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added ADR 237 detailing how Supabase-internal data is accessed via a `service_role`-only secondary query surface. * Updated Supabase extension documentation references to reflect the new secondary-root architecture guidance. * Clarified documentation scope for the 0.15 → 0.16 upgrade (no application-facing changes), including upgrade metadata and related READMEs. <!-- 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> | 2 个月前 | |
docs(upgrade): complete the app and extension upgrade fragments for verbatim PSL table names (#30321) PR #30317 (verbatim PSL table names) was squash-merged before the finished upgrade fragment was pushed, so `main` carries a 24-line placeholder app fragment and no extension fragment. This PR carries the two commits that were left behind: - The finished **app** fragment at [`upgrade-instructions/pending/psl-verbatim-table-names/app/`](upgrade-instructions/pending/psl-verbatim-table-names/app/): what changed and why, the codemod command over every `.prisma` file including the migration-chain `contract.prisma` copies, that emit and storage hashes are unchanged afterwards, what `MIGRATION.TABLE_NAME_CASE_CHANGED` looks like and means, the by-hand rename path for Postgres, SQLite and Mongo, and the `contract infer` behaviour. Detection is a regex on a `model` declaration header that does not fire on a field named `model`. - A new **extension** fragment at [`upgrade-instructions/pending/psl-verbatim-table-names/extension/`](upgrade-instructions/pending/psl-verbatim-table-names/extension/) with the same entry and its own copy of the script, because extension authors write contract-space PSL and are affected in the same way. A test in `scripts/codemods/add-model-map.test.mjs` keeps both copies byte-identical to the repo codemod. - The codemod CLI now skips `node_modules` and `dist`, so the documented `'**/*.prisma'` command is safe from a project root. - The project plan and spec update under `projects/psl-verbatim-table-names/`. The app fragment was validated by execution per `record-upgrade-instructions`: with `examples/` restored to the pre-change state, running the fragment's script reproduced the example diff exactly, left example tests untouched, and `pnpm --filter prisma-8-demo test` passed. `pnpm check:upgrade-coverage --mode pr` passes against `main`. 🤖 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 upgrade guidance for Prisma 8’s verbatim table and collection names. * Added a migration tool that preserves existing lowercase-first-letter names by inserting explicit mappings in schema models. * The tool supports app and extension upgrade paths, reports each mapped model, and can be run repeatedly without additional changes. * **Bug Fixes** * Schema scanning now skips `node_modules` and `dist` directories. * Quoted URLs and other `//` text are handled correctly during schema processing. * **Documentation** * Documented migration planning behavior, database-specific rename options, and contract inference updates. <!-- 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> | 3 天前 | |
TML-2953: type Mongo enum fields from a stored value set (like SQL) (#900) ## At a glance A Mongo model with an enum field: ```prisma enum UserRole { @@type("mongo/string@1") Admin = "admin" Author = "author" Reader = "reader" } model User { role UserRole } ``` Reading `role` back gives you the union of its allowed values — not `string`: ```ts const u = await db.user.findOne({ where: { id } }) u.role // 'admin' | 'author' | 'reader' ``` That already worked. What this PR changes is **where that union comes from**. Previously Mongo read the allowed values straight off the domain `enum`. Now they live once, in the storage block, as a named **value set** — and the field's type and the collection's validator are both built from it: ```jsonc // contract.json → storage "entries": { "valueSet": { "UserRole": { "kind": "valueSet", "values": ["admin", "author", "reader"] } }, "collection": { "users": { "validator": { /* $jsonSchema "enum" built from the value set */ } } } } ``` ## The decision **A field's type is determined by its value set — the union of the codec-rendered literals of its allowed values — not the plain codec type.** That is the primitive SQL already uses (shipped in #896); this PR brings Mongo onto it. The domain `enum` stops being a typing or enforcement input and becomes runtime-only: it still powers `db.enums`, and nothing else. Why this is worth doing, beyond removing a SQL/Mongo divergence: a "value set" is a *generic* restriction — "the permitted values here are `x, y, z`". A domain enum is one thing that produces one; a check constraint, a range, or a native database enum could produce one too, and each would be typed through the identical path with no enum-specific code. This closes the last place Mongo typed a field by reaching for the enum directly. ## What it does - **Adds the value-set entity to Mongo storage**, the same shape as SQL's (`{ kind: 'valueSet', values }`, codec-encoded, at `storage.namespaces[ns].entries.valueSet[Name]`). - **Authoring emits it** — the Mongo PSL interpreter and the TypeScript builder write the value set alongside the domain enum. - **Typing and the validator read it** — the emitter types fields from the value set (through the shared codec renderer that #896 added), and the `$jsonSchema` `enum` keyword is built from it. The code that read `domain.enum` for these is deleted; `db.enums` is the only reader of `domain.enum` left. ## Why it's safe - **Nothing observable changes.** The value set's values are exactly what the domain enum produced, so the switch-over is byte-identical: the emitted types and the validator are unchanged, and `fixtures:check` is clean across it. - **No migrations.** A value set is a typing/contract entity with no database object of its own — the collection validator is the physical artifact, and it is unchanged. The planner only walks collections, so adding a value set produces zero migration operations; `db verify` and migration behaviour are unchanged (pinned by a test). - For the same reason this is **uniformity, not a bug fix**: Mongo's validator already carries the enum values inline in the storage block that `storageHash` covers, so an enum change was already tracked. ## How it's verified - A Mongo enum field types as the value union on the emitted `contract.d.ts` (both the ORM row and `FieldOutputTypes`) *and* on `typeof contract`, matching SQL's shape — a typecheck-enforced test. - A non-identity mock codec (stores `0|1|2`, reads back `'low'|'high'|'urgent'`) proves the type is produced *through the codec*, not by printing the stored value. - Build, typecheck, lints, `fixtures:check`, upgrade-coverage, and the Mongo suites are green; no new casts. ## Upgrade (0.14 → 0.15) - **Users:** no action. Contracts regenerate to carry the value set, but the emitted types and the validator are identical and `db.enums` is unchanged. - **Extension authors:** `deriveJsonSchema` / `derivePolymorphicJsonSchema` (public `@prisma-next/mongo-contract-psl` exports) now take a value-set map instead of a domain-enum map. ## Alternatives considered - **Give the field an explicit stored reference to its value set, and make the Mongo query builder type straight from the storage block** (as SQL's does). Deferred to **TML-2961**. It needs an explicit per-collection document-structure projection, which nothing here requires: Mongo's query builder types via `FieldOutputTypes`, and the feature that would drive storage-only typing — native Postgres enums — is Postgres-only and never touches Mongo. This PR links a field to its value set *by name* in the emitter instead, which is enough for every consumer that needs it today. - **Rework the `typeof contract` path to read the value set too.** Unnecessary. That path is handed the authored literals directly and is already the value union — the same posture SQL keeps. --- Follow-up: [TML-2961](https://linear.app/prisma-company/issue/TML-2961) — the explicit Mongo document-structure projection. 🤖 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 support for stored enum value sets in Mongo contracts, including schema derivation, contract serialization, and target planning behavior. * Exposed Mongo codec descriptors via a new public `codecs` entry point. * **Bug Fixes** * Improved contract hashing and migration fingerprints to account for value-set content. * Ensured enum values are encoded/typed consistently through the configured codec, with clearer errors when a required enum codec is missing. * **Tests** * Added/updated coverage for value-set hydration, JSON round-trips, schema validation, and emitted type behavior. <!-- 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> | 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> | 1 个月前 | |
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> | 1 个月前 | |
fix(sql): the TypeScript sql tag resolves \$, so a raw default can contain ${ (#30347) A raw SQL default whose text contains `${` can now be written from TypeScript: ```ts // the column default is the literal text 'Home | ${user.name}' title: field.column(textColumn).default(sql`'Home | \${user.name}'`), ``` ```prisma // the same default in PSL, where no escape is needed title String @default(sql`'Home | ${user.name}'`) ``` Both emit the same contract, `{ kind: 'function', expression: "'Home | ${user.name}'" }`. ## The decision The `sql` tagged literal, added in #30325, reads its body as raw text and resolves two escapes, a backtick and a backslash. That works for PSL, where `${` is ordinary text. It does not work for TypeScript, because `${` in a template literal starts an interpolation. Writing it unescaped makes JavaScript substitute a value, which the tag refuses; writing `\${`, the only alternative JavaScript offers, left the backslash in the body and sent the wrong text to the database. So a default containing those two characters could be written in PSL and not in TypeScript. The TypeScript tag now resolves a third escape, `\$` to `$`. PSL is unchanged. The two languages therefore differ for the sequence `\$` alone, and only there. The alternative was to resolve `\$` in PSL too, so the escaping rules stay identical. That was rejected: PSL needs no escape for `${`, so the rule would tax every PSL author who writes a backslash before a dollar sign, to serve a body that only JavaScript struggles to write. ADR 129 records both. ## Changes - **Framework (`@internal/framework-components`)**: the one escape resolver becomes two, `resolvePslBacktickEscapes` and `resolveTemplateTagEscapes`, sharing a helper. Each says at its declaration which surface uses it and why the template one differs. - **TypeScript builder**: the `sql` tag uses the template-tag resolver. A real interpolation is still a compile error and still throws `CONTRACT.DEFAULT_SQL_INTERPOLATION` at runtime. - **Tests**: both resolvers, including `\$` on each side; the tag turning `` sql`'Home | \${user}'` `` into `'Home | ${user}'`; `` sql`\\$x` `` giving a backslash then a dollar; and, on the PSL side, a parser test and an interpreter test pinning that `${` and `\$` pass through as written. - **Docs**: both authoring READMEs, the two pending upgrade instructions, and ADR 129. Refs: ADR 129, #30325. 🤖 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** * TypeScript `sql` templates now support escaped dollar signs and literal `${...}` sequences using `\${...}`. * PSL backtick strings preserve `${...}` and dollar escapes as written. * **Documentation** * Updated SQL default-value guidance and migration instructions to explain differing TypeScript and PSL escape rules, including how to preserve literal `\$` sequences. * **Tests** * Added coverage for dollar-brace sequences, escaped dollars, backslashes, and preserved PSL escapes. <!-- 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 <noreply@anthropic.com> | 2 天前 | |
feat(sql): @@check declares CHECK constraints; infer adopts existing ones (#29972) ## Linked issue No Linear ticket — the operator waived tracker integration for this project. Final slice of [sql-check-constraint-unification](projects/sql-check-constraint-unification/spec.md) ([slice spec](projects/sql-check-constraint-unification/slices/authored-check-constraints/spec.md)); follows #29892, #29894, #29928. ## At a glance Your database has a constraint somebody added by hand: ```sql ALTER TABLE orders ADD CONSTRAINT positive_total CHECK (total > 0); ``` Run `prisma-next contract infer` against it today and that rule is silently left out of the contract — there is no syntax that can express it. After this PR, it comes back: ```prisma model Order { id Int @id total Decimal @@check(expression: "(total > (0)::numeric)", map: "positive_total") } ``` That one line is the difference between the constraint being invisible to Prisma Next and being owned by it. ## Why that matters Before this project, Prisma Next could not see hand-written checks at all — introspection only recognised the ones it generated itself. Slice 1 (#29892) changed that so it reads every constraint, which is what made complete reconciliation possible. But it created an exposure. A constraint Prisma Next can see and the contract cannot mention reads as *leftover*. `db verify` tolerates it, `db verify --strict` reports it, and a migration allowed to make destructive changes **deletes it**. Somebody's data-integrity rule disappears in a routine migration, and there was no way to say "keep this". ## Decision Give authors a way to declare a check, in both authoring surfaces: ```prisma @@check(expression: "total > 0", name: "order_total_positive") // create a new one @@check(expression: "(total > (0)::numeric)", map: "positive_total") // adopt an existing one ``` ```ts check({ expression: 'total > 0', name: 'order_total_positive' }) // @prisma/orm-postgres/contract-builder ``` Three things follow from that: 1. **`name:` creates**, `map:` **adopts.** Use `name:` for a constraint you want Prisma Next to install; use `map:` to take ownership of one that already exists under its own name. 2. **`contract infer` writes the `map:` form for you** for every live check it did not generate. That is what closes the exposure above — pull a database and its hand-written constraints are declared from the first pull, no longer leftover, no longer dropped. 3. **Both surfaces produce identical contracts**, `storageHash` included, so PSL and TypeScript authors get the same artefact. ## How it works **`name:` is a prefix, not the constraint's name.** Writing `name: "order_total_positive"` produces a constraint actually called `order_total_positive_a1b2c3d4`, where the suffix is a hash of the predicate. Prisma Next then compares constraints *by name* rather than by SQL text. That indirection is the load-bearing part, and it exists because **Postgres rewrites your SQL**. Write `price > 0` and the database hands back `(price > (0)::numeric)`; write `status IN ('a','b')` on a `varchar` and you get `((status)::text = ANY ((ARRAY['a'::character varying, …])::text[]))`. Comparing your text against that never matches, so a naively-named check would report drift on every single verify — permanently, with no way for the author to fix it. Hashing the predicate into the name sidesteps the comparison entirely: same predicate, same name, no drift. Indexes ([ADR 243](docs/architecture%20docs/adrs/ADR%20243%20-%20Name-identified%20indexes%20and%20exact-name%20adoption.md)) and RLS policies ([ADR 234](docs/architecture%20docs/adrs/ADR%20234%20-%20Content-addressed%20wire%20names%20for%20Postgres-normalized%20objects.md)) already work this way; checks now join them. **`map:` is the exception, and it is for adoption only.** It uses the name verbatim and *does* compare the SQL text — which is safe precisely when the text came from the database itself, as it does when `contract infer` writes it. Hand-writing a body under `map:` gets you a warning pointing at `name:`. **One internal rule had to change.** Prisma Next generates some checks itself (enum membership, no-nulls-in-a-list) and strips them from tables it does not manage. It used to identify them by "does this have a hashed name?" — which authored checks now also have, so an author's constraint would have been silently deleted. Derivation is now decided by whether the name's prefix is one *generation* would have produced for a column of that table. An authored name that collides with that shape is rejected up front (`CONTRACT.CHECK_NAME_RESERVED`). ## Reviewer notes - **The proof is one journey test.** `test/integration/test/cli-journeys/infer-roundtrip-fidelity.e2e.test.ts` seeds a hand-written constraint by raw SQL, pulls it, and asserts a destructive-allowing plan carries no check operation. It was confirmed to fail at the true fork point — an earlier attempt against a stale local `main` gave a false signal and was diagnosed rather than accepted — and re-verified independently at review by reproducing `main` and observing the plan there carry exactly `dropCheckConstraint.users.users_tags_not_empty`. - **One existing assertion was deliberately inverted.** Slice 3 asserted a hand-written check is *not* inferred; that is the behaviour this PR changes. The `--strict` expectation flipped with it, confirmed by running it rather than reasoning about it. - **`@@check` is capability-gated; `check()` is not.** Capabilities are adapter-reported and reach the contract only after it is built, so the TypeScript path cannot be refused at authoring time. A `check()` against SQLite is refused at DDL render instead — an accurate error, where previously the constraint was silently dropped. - **84 emitted contracts regenerate**, all gaining the same one additive capability line. The check node's own shape is unchanged. - **Review found `@@check` on a single-table-inheritance variant was silently discarded** — a pre-existing class that `@@index` and `@@unique` still share. Checks now raise a diagnostic; the siblings are left alone and recorded as a follow-up. ## Behaviour changes & evidence - **A hand-written constraint is declared by a pull and survives a destructive plan.** `psl-infer/infer-model-blocks.ts`, `psl-infer/infer-index-attributes.ts` — evidence: `cli-journeys/infer-roundtrip-fidelity.e2e.test.ts`. - **`@@check` / `check()` declare a constraint.** `contract-psl/src/sql-attribute-specs.ts`, `contract-ts/src/contract-dsl.ts`, `contract-ts/src/build-contract.ts` — evidence: `interpreter.check-attribute.test.ts` (parity incl. `storageHash`), `check-constraint.authoring.test.ts`. - **Authored checks survive the strip on unmanaged tables.** `schema-ir/src/naming.ts`, `contract-ts/src/derived-checks.ts` — evidence: the strip tests in `check-constraint.authoring.test.ts`. - **Predicate edits plan drop+add; renames plan one `RENAME CONSTRAINT`.** Evidence: `check-lifecycle-e2e.integration.test.ts`, against a real database. - **SQLite refuses rather than dropping.** `sqlite/src/core/migrations/column-ddl-rendering.ts` — evidence: `column-ddl-rendering.test.ts`. ## Compatibility Additive. No existing contract changes meaning, and the check node's shape is untouched — emitted contracts gain one capability line. Upgrade entries are recorded for both audiences; the pack-author entry flags that `check.prefix !== undefined` no longer means "Prisma Next generated this". ADR 244's known cost — hand-written checks droppable under a destructive policy — is now closed. ## Verification - `pnpm -w build` 86/86 · `pnpm typecheck` 165/165 - `lint:deps`, `fixtures:check`, `check:error-reference`, `check:upgrade-coverage`, cast ratchet (delta 0) — clean - Suites: sql-contract-ts 479 · sql-contract-psl 436 · sql-schema-ir 255 · target-postgres 1397 · target-sqlite 271 · adapter-postgres 814 (+3 pre-existing expected-fail) · adapter-sqlite 268 - Real database: `check-lifecycle-e2e` 19/19 · `infer-roundtrip-fidelity` 19/19 · journeys 157/157 - Unrelated and pre-existing: `issues-28192-pg-historical-dates` fails 2/9 on a non-UTC machine. ## Follow-ups - Validate declared capability requirements after adapter enrichment, so the TypeScript surface can be refused at authoring rather than at DDL render. - `@@index` / `@@unique` on an inheritance variant are still silently discarded. - A live enum-membership check still does not infer back into `enumType()`, so a text-backed domain enum is not recovered from a pull. ## Alternatives considered - **Let the author name the constraint exactly.** The obvious design, and the reason for the prefix indirection above: byte-comparing an author's SQL against Postgres's rewrite fails verify under every policy — including `external` — and hard-errors the planner, with no remedy available to the author. - **Normalise both sides before comparing.** Needs a parser for the rewritten form; that is the approach slice 1 deleted. - **Mark authored checks with a field on the check node** instead of inferring it from the name. Rejected: it changes the contract's wire shape — regenerating every fixture — to record something the naming rule already implies. - **Make `@@check` a Postgres-contributed attribute** rather than a SQL-wide one. Cheaper to gate, but CHECK is standard SQL, the contribution seam files entities in the wrong place for a table-level object, and it would not cover the TypeScript surface. - **A column-level `@check`.** Postgres stores column-level CHECK syntax identically to table-level, so one model-level surface covers both. ## Checklist - [x] All commits carry DCO sign-off - [x] I have read CONTRIBUTING.md - [x] Tests added for every behaviour change, including a regression test proven to fail on `main` - [x] Title follows the prevailing convention 🤖 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 support for authoring PostgreSQL CHECK constraints through `@@check` and the TypeScript contract builder. * Supports generated wire names, exact database-name mapping, multiple checks, and preservation during schema inference. * Added validation, warnings, and diagnostics for invalid names and unsupported inheritance scenarios. * **Bug Fixes** * Improved handling of authored and automatically derived checks during inference and migrations. * SQLite now reports a clear error when CHECK constraints are unsupported. * **Documentation** * Updated capability references, error guidance, architecture decisions, and upgrade instructions. <!-- 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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
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> | 1 个月前 | |
chore(drive): retire drive-project-workflow Cursor rule The rule has been fully superseded by the drive-* skill cluster and the methodology docs under docs/drive/: - shaping workflow -> drive-start-workflow + drive-specify-project + drive-plan-project (the rule still referenced the renamed drive-create-spec / drive-create-plan names) - project lifecycle -> docs/drive/workflow.md - artifact layout -> drive-create-project skill body - close-out -> drive-close-project skill body - tracker conventions -> drive/project/README.md + drive/pr/README.md Keeping it added duplicate always-applied context that referenced non-existent skills. Delete both copies (.agents/rules/ + .cursor/rules/), re-point the AGENTS.md and projects/README.md pointers at the drive workflow skills + docs/drive/, and update the drive-create-plan + drive-close-project skill bodies that referenced the rule by name. Signed-off-by: Will Madden <madden@prisma.io> | 4 个月前 |
Projects
This repo keeps project-specific specs, plans, ADR drafts, reference notes, and assets under projects/.
Anything in projects/ is transient: once the project is complete, migrate long-lived docs to docs/ and delete the project folder.
Directory layout
- Project root:
projects/<project>/ - Project spec (shaping output):
projects/<project>/spec.md - Project plan:
projects/<project>/plan.md - Task/feature specs:
projects/<project>/specs/<task>.spec.md - Task/feature plans:
projects/<project>/plans/<task>.plan.md - Reference material / assets:
projects/<project>/**
Workflow
- Start with
drive-start-workflow(which routes new work into direct change / slice / project) or invokedrive-create-projectdirectly when you already know it's a project, then shape the work as spec → plan → implement. Methodology and skill map:docs/drive/.
Project lifecycle
- Shaping: Create the initial spec + plan under
projects/<project>/and open the first PR containing these artifacts.- Validate the spec with the PM/stakeholders and the plan with the team.
- Execution: Implement tasks via as many follow-on branches/PRs as needed. Keep project docs and Linear up to date.
- Stakeholder verification: Confirm objectives/acceptance criteria are met.
- Close-out: Finalize long-lived docs into
docs/, strip repo-wide references toprojects/<project>/**(replace with canonicaldocs/links or remove), then the last PR deletesprojects/<project>/.