| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
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 个月前 | |
| 2 个月前 | ||
TML-3163: add BigIntNumber and UnboundedInt column types (#29902) ## Linked issue Refs [TML-3163](https://linear.app/prisma-company/issue/TML-3163/opt-in-number-representation-integer-codecs-bigintnumber-unboundedint) — slice 06 of the [Codec JSON projections](https://linear.app/prisma-company/project/codec-json-projections-a10fba2e9cd5) project. This unblocks TML-3165, whose aggregate defaults consume the new codec IDs. This PR makes integer representation a per-column contract choice without changing the lossless `BigInt` default. ```prisma model Meter { id Int @id peak BigIntNumber lifetime UnboundedInt } ``` `peak` reads and writes as a JavaScript `number`, throwing outside ±(2^53 − 1) instead of rounding. `lifetime` uses PostgreSQL unconstrained `numeric` storage and round-trips integral values as exact JavaScript `bigint` values at arbitrary magnitude. ## Changes - **Integer representation codecs**: Adds `pg/int8number@1` and `sqlite/bigintnumber@1` for safe-range JavaScript numbers, plus PostgreSQL `pg/unboundedint@1` for arbitrary-precision integral values. Encode and decode paths reject non-integral or out-of-range values with structured `RUNTIME.ENCODE_FAILED` / `RUNTIME.DECODE_FAILED` errors. - **Target-scoped authored types**: PostgreSQL contributes `BigIntNumber` and `UnboundedInt`; SQLite contributes only `BigIntNumber`. These are top-level zero-argument type constructors, so PSL fields use ordinary bare type syntax and retain normal optional/default/list composition. The corresponding codecs keep `targetTypes: []`, leaving canonical introspection unchanged (`int8 → BigInt`, `numeric → Numeric`). - **TypeScript authoring**: The composed callback exposes `type.BigIntNumber()` and PostgreSQL `type.UnboundedInt()` for registered storage types used through `field.namedType(...)`. Direct authoring remains available through `field.column(pgInt8NumberColumn())`, `field.column(pgUnboundedIntColumn())`, and `field.column(sqliteBigintNumberColumn())`. - **Aggregate typing**: Adds target-probed `sum` / `avg` rows for the new codecs. `min` / `max` continue to resolve through the numeric-trait self fallback. PostgreSQL `sum` over `UnboundedInt` remains exact as `bigint`; widening results use the target's canonical numeric codec. - **End-to-end proof and migration guidance**: Adds PostgreSQL and SQLite emitted PSL fixtures, runtime and type-level ORM coverage, codec and aggregate conformance cases, reference documentation, and no-op upgrade declarations on the current `8.0.0-rc.1 → 8.0.0-rc.2` edge because existing source requires no migration. ## Why The database storage type cannot identify the intended application representation: PostgreSQL `int8` may be read as lossless `bigint` or guarded `number`, while `numeric` may represent general decimal text or integral `bigint`. Giving the alternative codecs native-type claims would make reverse resolution and introspection ambiguous. Target-contributed type constructors separate the two concerns cleanly: authors explicitly select the application representation, while introspection continues to emit the canonical type for each storage type. This also uses Prisma Next's surviving type-constructor abstraction rather than field-template machinery that would incorrectly impose preset-specific field restrictions. `BigIntNumber` deliberately projects database-produced JSON as a JSON number. The safe-range guard is sound because ECMAScript numbers are IEEE 754 binary64, 2^53 is exactly representable, and monotone rounding cannot move an out-of-range integer into the accepted safe range. Values that could lose precision always throw. ## Review notes - Registering the numeric codecs radiates additive `aggregateTypes.byCodec` rows into generated contracts even when a schema does not use the authored types. Existing entries remain unchanged. - SQLite has no `UnboundedInt` because it has no lossless unbounded integer storage. - On a flat SQLite read, `node:sqlite` may reject an out-of-range INTEGER before the codec runs; include/database-JSON reads still surface the structured codec error. - The integer-representation fixture outputs remain semantically unchanged after moving from call syntax to bare types; canonical regeneration adds only the expected globally radiated aggregate rows to one previously stale fixture. ## Validation Post-rebase validation against current `origin/main`: - `pnpm build` - `pnpm --dir test/integration typecheck` - Fresh PR Type Check job - `pnpm lint:deps` — 1,921 modules / 2,934 dependencies, no violations - `pnpm lint:skills` - `pnpm lint:docs` — passes with existing README warnings - `pnpm fixtures:check` - `pnpm check:upgrade-coverage` - PostgreSQL and SQLite target, scalar-parity, codec-conformance, aggregate-conformance, and contract-TS suites - Package-local typechecks for the changed target, extension, adapter-testkit, and contract-TS packages - Focused integer-representation integration: all 6 tests pass with no type errors - Stale authoring-call and preset-guidance `rg` gates - `git diff --check` All PR-scoped gates pass, including the fresh CI Type Check. Two local `pnpm typecheck` attempts hit a Turbo output-ordering race while concurrent builds cleaned package `dist` self-imports (`pgvector/pack`, then `supabase/runtime`); CI's isolated Type Check completes successfully. ## Checklist - [x] Commits are signed off per the DCO. - [x] Tests cover target availability, TypeScript authoring, codec boundaries, emitted contracts, runtime reads/writes, includes, and aggregate result types. - [x] Upgrade declarations classify the generated aggregate-row additions as inert for existing source on the current release edge. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added integer representation presets for PostgreSQL and SQLite, supporting safe-range JavaScript numbers and arbitrary-size PostgreSQL integers. * Added validation for unsafe, fractional, and out-of-range values. * Extended `avg`, `min`, `max`, and `sum` aggregates with nullable results and large-value support. * Added nested-read support and improved inferred types for these representations. * **Documentation** * Expanded guidance on integer presets, aggregate behavior, JSON formats, and validation errors. * **Tests** * Added comprehensive unit, integration, and aggregate conformance coverage. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net> | 1 个月前 | |
TML-3163: add BigIntNumber and UnboundedInt column types (#29902) ## Linked issue Refs [TML-3163](https://linear.app/prisma-company/issue/TML-3163/opt-in-number-representation-integer-codecs-bigintnumber-unboundedint) — slice 06 of the [Codec JSON projections](https://linear.app/prisma-company/project/codec-json-projections-a10fba2e9cd5) project. This unblocks TML-3165, whose aggregate defaults consume the new codec IDs. This PR makes integer representation a per-column contract choice without changing the lossless `BigInt` default. ```prisma model Meter { id Int @id peak BigIntNumber lifetime UnboundedInt } ``` `peak` reads and writes as a JavaScript `number`, throwing outside ±(2^53 − 1) instead of rounding. `lifetime` uses PostgreSQL unconstrained `numeric` storage and round-trips integral values as exact JavaScript `bigint` values at arbitrary magnitude. ## Changes - **Integer representation codecs**: Adds `pg/int8number@1` and `sqlite/bigintnumber@1` for safe-range JavaScript numbers, plus PostgreSQL `pg/unboundedint@1` for arbitrary-precision integral values. Encode and decode paths reject non-integral or out-of-range values with structured `RUNTIME.ENCODE_FAILED` / `RUNTIME.DECODE_FAILED` errors. - **Target-scoped authored types**: PostgreSQL contributes `BigIntNumber` and `UnboundedInt`; SQLite contributes only `BigIntNumber`. These are top-level zero-argument type constructors, so PSL fields use ordinary bare type syntax and retain normal optional/default/list composition. The corresponding codecs keep `targetTypes: []`, leaving canonical introspection unchanged (`int8 → BigInt`, `numeric → Numeric`). - **TypeScript authoring**: The composed callback exposes `type.BigIntNumber()` and PostgreSQL `type.UnboundedInt()` for registered storage types used through `field.namedType(...)`. Direct authoring remains available through `field.column(pgInt8NumberColumn())`, `field.column(pgUnboundedIntColumn())`, and `field.column(sqliteBigintNumberColumn())`. - **Aggregate typing**: Adds target-probed `sum` / `avg` rows for the new codecs. `min` / `max` continue to resolve through the numeric-trait self fallback. PostgreSQL `sum` over `UnboundedInt` remains exact as `bigint`; widening results use the target's canonical numeric codec. - **End-to-end proof and migration guidance**: Adds PostgreSQL and SQLite emitted PSL fixtures, runtime and type-level ORM coverage, codec and aggregate conformance cases, reference documentation, and no-op upgrade declarations on the current `8.0.0-rc.1 → 8.0.0-rc.2` edge because existing source requires no migration. ## Why The database storage type cannot identify the intended application representation: PostgreSQL `int8` may be read as lossless `bigint` or guarded `number`, while `numeric` may represent general decimal text or integral `bigint`. Giving the alternative codecs native-type claims would make reverse resolution and introspection ambiguous. Target-contributed type constructors separate the two concerns cleanly: authors explicitly select the application representation, while introspection continues to emit the canonical type for each storage type. This also uses Prisma Next's surviving type-constructor abstraction rather than field-template machinery that would incorrectly impose preset-specific field restrictions. `BigIntNumber` deliberately projects database-produced JSON as a JSON number. The safe-range guard is sound because ECMAScript numbers are IEEE 754 binary64, 2^53 is exactly representable, and monotone rounding cannot move an out-of-range integer into the accepted safe range. Values that could lose precision always throw. ## Review notes - Registering the numeric codecs radiates additive `aggregateTypes.byCodec` rows into generated contracts even when a schema does not use the authored types. Existing entries remain unchanged. - SQLite has no `UnboundedInt` because it has no lossless unbounded integer storage. - On a flat SQLite read, `node:sqlite` may reject an out-of-range INTEGER before the codec runs; include/database-JSON reads still surface the structured codec error. - The integer-representation fixture outputs remain semantically unchanged after moving from call syntax to bare types; canonical regeneration adds only the expected globally radiated aggregate rows to one previously stale fixture. ## Validation Post-rebase validation against current `origin/main`: - `pnpm build` - `pnpm --dir test/integration typecheck` - Fresh PR Type Check job - `pnpm lint:deps` — 1,921 modules / 2,934 dependencies, no violations - `pnpm lint:skills` - `pnpm lint:docs` — passes with existing README warnings - `pnpm fixtures:check` - `pnpm check:upgrade-coverage` - PostgreSQL and SQLite target, scalar-parity, codec-conformance, aggregate-conformance, and contract-TS suites - Package-local typechecks for the changed target, extension, adapter-testkit, and contract-TS packages - Focused integer-representation integration: all 6 tests pass with no type errors - Stale authoring-call and preset-guidance `rg` gates - `git diff --check` All PR-scoped gates pass, including the fresh CI Type Check. Two local `pnpm typecheck` attempts hit a Turbo output-ordering race while concurrent builds cleaned package `dist` self-imports (`pgvector/pack`, then `supabase/runtime`); CI's isolated Type Check completes successfully. ## Checklist - [x] Commits are signed off per the DCO. - [x] Tests cover target availability, TypeScript authoring, codec boundaries, emitted contracts, runtime reads/writes, includes, and aggregate result types. - [x] Upgrade declarations classify the generated aggregate-row additions as inert for existing source on the current release edge. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added integer representation presets for PostgreSQL and SQLite, supporting safe-range JavaScript numbers and arbitrary-size PostgreSQL integers. * Added validation for unsafe, fractional, and out-of-range values. * Extended `avg`, `min`, `max`, and `sum` aggregates with nullable results and large-value support. * Added nested-read support and improved inferred types for these representations. * **Documentation** * Expanded guidance on integer presets, aggregate behavior, JSON formats, and validation errors. * **Tests** * Added comprehensive unit, integration, and aggregate conformance coverage. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net> | 1 个月前 | |
TML-3163: add BigIntNumber and UnboundedInt column types (#29902) ## Linked issue Refs [TML-3163](https://linear.app/prisma-company/issue/TML-3163/opt-in-number-representation-integer-codecs-bigintnumber-unboundedint) — slice 06 of the [Codec JSON projections](https://linear.app/prisma-company/project/codec-json-projections-a10fba2e9cd5) project. This unblocks TML-3165, whose aggregate defaults consume the new codec IDs. This PR makes integer representation a per-column contract choice without changing the lossless `BigInt` default. ```prisma model Meter { id Int @id peak BigIntNumber lifetime UnboundedInt } ``` `peak` reads and writes as a JavaScript `number`, throwing outside ±(2^53 − 1) instead of rounding. `lifetime` uses PostgreSQL unconstrained `numeric` storage and round-trips integral values as exact JavaScript `bigint` values at arbitrary magnitude. ## Changes - **Integer representation codecs**: Adds `pg/int8number@1` and `sqlite/bigintnumber@1` for safe-range JavaScript numbers, plus PostgreSQL `pg/unboundedint@1` for arbitrary-precision integral values. Encode and decode paths reject non-integral or out-of-range values with structured `RUNTIME.ENCODE_FAILED` / `RUNTIME.DECODE_FAILED` errors. - **Target-scoped authored types**: PostgreSQL contributes `BigIntNumber` and `UnboundedInt`; SQLite contributes only `BigIntNumber`. These are top-level zero-argument type constructors, so PSL fields use ordinary bare type syntax and retain normal optional/default/list composition. The corresponding codecs keep `targetTypes: []`, leaving canonical introspection unchanged (`int8 → BigInt`, `numeric → Numeric`). - **TypeScript authoring**: The composed callback exposes `type.BigIntNumber()` and PostgreSQL `type.UnboundedInt()` for registered storage types used through `field.namedType(...)`. Direct authoring remains available through `field.column(pgInt8NumberColumn())`, `field.column(pgUnboundedIntColumn())`, and `field.column(sqliteBigintNumberColumn())`. - **Aggregate typing**: Adds target-probed `sum` / `avg` rows for the new codecs. `min` / `max` continue to resolve through the numeric-trait self fallback. PostgreSQL `sum` over `UnboundedInt` remains exact as `bigint`; widening results use the target's canonical numeric codec. - **End-to-end proof and migration guidance**: Adds PostgreSQL and SQLite emitted PSL fixtures, runtime and type-level ORM coverage, codec and aggregate conformance cases, reference documentation, and no-op upgrade declarations on the current `8.0.0-rc.1 → 8.0.0-rc.2` edge because existing source requires no migration. ## Why The database storage type cannot identify the intended application representation: PostgreSQL `int8` may be read as lossless `bigint` or guarded `number`, while `numeric` may represent general decimal text or integral `bigint`. Giving the alternative codecs native-type claims would make reverse resolution and introspection ambiguous. Target-contributed type constructors separate the two concerns cleanly: authors explicitly select the application representation, while introspection continues to emit the canonical type for each storage type. This also uses Prisma Next's surviving type-constructor abstraction rather than field-template machinery that would incorrectly impose preset-specific field restrictions. `BigIntNumber` deliberately projects database-produced JSON as a JSON number. The safe-range guard is sound because ECMAScript numbers are IEEE 754 binary64, 2^53 is exactly representable, and monotone rounding cannot move an out-of-range integer into the accepted safe range. Values that could lose precision always throw. ## Review notes - Registering the numeric codecs radiates additive `aggregateTypes.byCodec` rows into generated contracts even when a schema does not use the authored types. Existing entries remain unchanged. - SQLite has no `UnboundedInt` because it has no lossless unbounded integer storage. - On a flat SQLite read, `node:sqlite` may reject an out-of-range INTEGER before the codec runs; include/database-JSON reads still surface the structured codec error. - The integer-representation fixture outputs remain semantically unchanged after moving from call syntax to bare types; canonical regeneration adds only the expected globally radiated aggregate rows to one previously stale fixture. ## Validation Post-rebase validation against current `origin/main`: - `pnpm build` - `pnpm --dir test/integration typecheck` - Fresh PR Type Check job - `pnpm lint:deps` — 1,921 modules / 2,934 dependencies, no violations - `pnpm lint:skills` - `pnpm lint:docs` — passes with existing README warnings - `pnpm fixtures:check` - `pnpm check:upgrade-coverage` - PostgreSQL and SQLite target, scalar-parity, codec-conformance, aggregate-conformance, and contract-TS suites - Package-local typechecks for the changed target, extension, adapter-testkit, and contract-TS packages - Focused integer-representation integration: all 6 tests pass with no type errors - Stale authoring-call and preset-guidance `rg` gates - `git diff --check` All PR-scoped gates pass, including the fresh CI Type Check. Two local `pnpm typecheck` attempts hit a Turbo output-ordering race while concurrent builds cleaned package `dist` self-imports (`pgvector/pack`, then `supabase/runtime`); CI's isolated Type Check completes successfully. ## Checklist - [x] Commits are signed off per the DCO. - [x] Tests cover target availability, TypeScript authoring, codec boundaries, emitted contracts, runtime reads/writes, includes, and aggregate result types. - [x] Upgrade declarations classify the generated aggregate-row additions as inert for existing source on the current release edge. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added integer representation presets for PostgreSQL and SQLite, supporting safe-range JavaScript numbers and arbitrary-size PostgreSQL integers. * Added validation for unsafe, fractional, and out-of-range values. * Extended `avg`, `min`, `max`, and `sum` aggregates with nullable results and large-value support. * Added nested-read support and improved inferred types for these representations. * **Documentation** * Expanded guidance on integer presets, aggregate behavior, JSON formats, and validation errors. * **Tests** * Added comprehensive unit, integration, and aggregate conformance coverage. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net> | 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 个月前 |
Codec JSON projections
Transient project workspace for Codec JSON projections, tracked by TML-3060. See spec.md for the system-level contract, design-notes.md for the settled design and rejected alternatives, and plan.md for the four-PR stack.
Branch: tml-3060-codec-json-projections.
The exact pre-project PostgreSQL numeric prototype is preserved under assets/, including its original uncommitted diff and integrity hash. Its regression tests and database evidence remain inputs to later slices, but its codec-ID-hardcoded renderer and derived-table lineage inference are not the selected architecture.
Everything under
projects/is transient — migrate long-lived architecture and upgrade documentation todocs/, remove repo-wide references to this workspace, and delete it at project close-out perprojects/README.md.