Skip to content

fix(publish): declarations must name only what a consumer will have (TML-3125) - #29862

Merged
wmadden merged 7 commits into
mainfrom
tml-3125-declaration-fixes
Aug 3, 2026
Merged

fix(publish): declarations must name only what a consumer will have (TML-3125)#29862
wmadden merged 7 commits into
mainfrom
tml-3125-declaration-fixes

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Overview

Two defects that are invisible in-repo but hit any consumer compiling against the published declarations, plus a sweep of the defect class each belongs to and a publish-time guard so neither can recur.

Based on main, independent of the ADR 242 stack. Refs: TML-3125.

Defect 1 — an operation's self could not target list-typed fields

@prisma-next/sql-operations passes QueryOperationTypeEntry into the framework generics constrained by OperationEntry. The SQL self spec has three variants — codec identity, codec traits, and list-typed (many) fields — while the framework SelfSpec had only the first two, so the type argument never satisfied its constraint (TS2344).

What hides it in-repo is exactOptionalPropertyTypes: true, not skipLibCheck. With the flag on, traits?: never stays never, which is assignable to readonly string[]. With it off — the default, so what every consumer gets — it widens to undefined and the union member no longer matches. Compiling the package with --exactOptionalPropertyTypes false reproduces all three errors in the source file, with skipLibCheck untouched.

It was also a runtime bug. createOperationRegistry().register() threw CONTRACT.PACK_CONTRIBUTION_INVALID for any self without codecId or traits, so registering a list-targeted operation failed outright. That is what the new test pins — an in-repo type test cannot fail here, because the flag masks the relation either way.

The fix adds the list variant to the framework SelfSpec and accepts it in register(). This widens an exported type in @prisma-next/operations: additive, a new accepted input shape nothing currently produces, with two readers in the repo (both handled). The alternatives were worse — making the framework's self opaque would delete its early-error validation, and dropping the SQL variant would leave the framework unable to express list dispatch at all.

A workspace-wide compile with exactOptionalPropertyTypes: false confirms this has no siblings: 2 errors total, both stale-dist TS2307, zero TS2344.

Defect 2 — declarations naming modules the consumer will not have

pg ships no types of its own. Three publishable packages re-export pg types from .d.mts while declaring @types/pg only in devDependencies, which consumers never install.

The class sweep

The class: a publishable package whose shipped declarations name a module the consumer will not have — either the package is absent from consumer-installed dependency fields, or it is present but ships no types and its @types/* companion is missing.

Swept all 65 publishable packages, extracting specifiers with TypeScript's preProcessFile rather than a regex — the regex lied in both directions, reading a 'release' event mentioned in a doc comment as an import of pg, and an earlier dist/index.d.mts-only pass missed a real finding under dist/test/.

Package Was missing Fix
driver-postgres @types/pg (devDep) dependencies
postgres @types/pg (devDep) dependencies
extension-supabase @types/pg (devDep) dependencies
sql-lane-query-builder no dependencies block at all; declarations named arktype and @standard-schema/spec declared its real deps

The query-builder root cause is worth knowing: tsdown bundles devDependency types instead of importing them. With @prisma-next/contract and sql-contract as devDeps, their declarations were inlined — 1211 lines of foreign types — dragging in bare side-effect imports the package never declared. Moving them to dependencies made them external: the declaration dropped to 231 lines and the stray arktype import disappeared on its own. It also restores the exact-pin rule, so a consumer gets one identity for Contract rather than a structurally-similar inlined copy.

The guard

scripts/check-publish-deps.mjs gains a third rule beside its leak and pin checks: every module specifier named by a declaration in the tarball's published entry-point tree must resolve for a consumer. This is the right home — publish.yml runs pnpm build immediately before it, and it already packs the exact tarball that ships.

Scoped to the entry-point tree rather than the whole tarball, since tarballs also ship src/ for declaration maps; roots are derived from exports/types/main/module rather than hardcoding dist/. Proven non-vacuous by reintroducing both defects and confirming the gate names each one.

23 tests in check-publish-deps-declarations.test.mjs, wired into test:scripts, including the doc-comment case that broke the first scanner.

Known exemption, recorded not guessed

@prisma-next/sql-runtime publishes a ./test/utils subpath whose module graph reaches @prisma-next/test-utils, which is private: true and never published. That subpath is already broken for external consumers at runtime, not merely at type level, and no manifest edit fixes it — declaring a private package would put a nonexistent version into the shipped manifest and break installs for everyone. Every real fix either removes a published export or moves code across a package boundary. Recorded as a named exemption and filed separately with the options costed.

Verification

pnpm build, typecheck:packages (130), test:scripts (251), lint, lint:deps, lint:manifests, check:publish-deps, check:clean-tree, lint:casts and lint:throws (delta 0) all green. test:packages 13945 passed with one known 100ms-timeout flake that passes in isolation.

Consumer compile from the final tree: 12 tarballs repacked, installed outside the workspace, nodenext + skipLibCheck: false — 5 errors before, exit 0 after. The pg fix was confirmed to resolve through the isolated dependency path rather than by hoisting accident.

Incidental finding

scripts/check-publish-deps.test.mjs is dead code — vitest-style, but the root vitest config only globs packages/**/vitest.config.ts and test:scripts does not list it, so nothing runs it. Pre-existing; left alone, worth a separate cleanup.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for list-valued self-references using many: true.
    • Family self-references can now include element traits alongside list targets.
  • Bug Fixes

    • Improved validation messages for incomplete or invalid self-reference configurations.
    • Updated package dependency declarations for reliable published type usage.
  • Quality Improvements

    • Added publish-time checks for missing type declarations.
    • Expanded automated coverage for validation and package publishing scenarios.
  • Documentation

    • Added upgrade guidance for PostgreSQL type dependencies to help prevent type conflicts.

@wmadden-electric
wmadden-electric requested a review from a team as a code owner July 31, 2026 13:43
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: f8c31591-4d43-4229-b3c0-5e485ac05da8

📥 Commits

Reviewing files that changed from the base of the PR and between c022d8a and 9e36363.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • package.json
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/1-framework/1-core/operations/test/operations-registry.test.ts
  • packages/2-sql/4-lanes/query-builder/package.json
  • packages/2-sql/5-runtime/test/utils.ts
  • packages/2-sql/5-runtime/tsdown.config.ts
  • packages/3-extensions/postgres/package.json
  • packages/3-extensions/supabase/package.json
  • packages/3-targets/7-drivers/postgres/package.json
  • scripts/check-publish-deps-declarations.test.mjs
  • scripts/check-publish-deps.mjs
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/instructions.md

📝 Walkthrough

Walkthrough

The publish check validates dependencies referenced by published declaration files. Operation registries support self: { many: true } for list-typed fields. Package metadata, upgrade guidance, SQL test utilities, and tests were updated.

Changes

Declaration dependency validation

Layer / File(s) Summary
Declaration analysis and dependency classification
scripts/check-publish-deps.mjs
The checker extracts declarations, finds published entry roots, parses module specifiers, and classifies undeclared or untyped dependencies.
Publish-check integration and diagnostics
scripts/check-publish-deps.mjs
runCheck includes declaration violations in offender records, JSON output, success messages, and failure diagnostics.
Validation and package declarations
scripts/check-publish-deps-declarations.test.mjs, package.json, packages/2-sql/4-lanes/query-builder/package.json, packages/3-extensions/*/package.json, packages/3-targets/7-drivers/postgres/package.json, skills/extension-author/.../instructions.md
Tests cover declaration analysis and runCheck. Package manifests, the test:scripts command, and PostgreSQL upgrade instructions were updated.

List-targeted operation self specifications

Layer / File(s) Summary
SelfSpec many target and registry validation
packages/1-framework/1-core/operations/src/index.ts, packages/1-framework/1-core/operations/test/operations-registry.test.ts
SelfSpec includes the mutually exclusive many: true variant. Registry validation and tests accept list-targeted self specifications and report the expanded missing-target error.

Runtime test client decoupling

Layer / File(s) Summary
Structural test client and local utilities
packages/2-sql/5-runtime/test/utils.ts
Test utilities define TestSqlClient, local async helpers, and application-domain construction without generic test-utils or pg.Client imports.
Generic database setup and exports
packages/2-sql/5-runtime/test/utils.ts, packages/2-sql/5-runtime/tsdown.config.ts
Database setup and marker helpers accept structural clients. Generic test utility re-exports and related external configuration were removed.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PackageTarball
  participant runCheck
  participant DeclarationParser
  participant DependencyResolver
  participant CheckOutput
  PackageTarball->>runCheck: provide packed declaration files
  runCheck->>DeclarationParser: extract module specifiers
  DeclarationParser->>DependencyResolver: resolve package and type dependencies
  DependencyResolver-->>runCheck: return declaration violations
  runCheck->>CheckOutput: include violations in diagnostics and JSON output
Loading

Possibly related PRs

Suggested labels: lgtm

Suggested reviewers: aqrln, wmadden

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: fixing published declaration dependencies for consumer environments.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-3125-declaration-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
postgres / no-emit 172.63 KB (0%)
postgres / emit 153.47 KB (0%)
mongo / no-emit 100.63 KB (0%)
mongo / emit 90.4 KB (0%)
cf-worker / no-emit 198.52 KB (0%)
cf-worker / emit 177.52 KB (0%)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/1-framework/1-core/operations/src/index.ts (1)

57-75: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate mutual exclusivity between many and codecId/traits at runtime.

The SelfSpec type enforces that many is mutually exclusive with codecId and traits through never fields. The runtime check only rejects hasCodecId && hasTraits. It does not reject hasCodecId && targetsMany or hasTraits && targetsMany.

A descriptor built from untyped JS, or from a generic T that widens self, can pass { codecId: 'x', many: true } without triggering a contract error. Add the missing pairwise checks so the runtime validation matches the type contract.

🛠️ Proposed fix
         if (hasCodecId && hasTraits) {
           throw contractError(
             'CONTRACT.PACK_CONTRIBUTION_INVALID',
             `Operation "${name}" self has both codecId and traits`,
             { meta: { operation: name } },
           );
         }
+        if ((hasCodecId || hasTraits) && targetsMany) {
+          throw contractError(
+            'CONTRACT.PACK_CONTRIBUTION_INVALID',
+            `Operation "${name}" self has many together with codecId or traits`,
+            { meta: { operation: name } },
+          );
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/1-framework/1-core/operations/src/index.ts` around lines 57 - 75,
Update the runtime validation in the descriptor.self block to reject every
pairwise conflict among hasCodecId, hasTraits, and targetsMany: codecId with
traits, codecId with many, and traits with many. Preserve the existing
CONTRACT.PACK_CONTRIBUTION_INVALID error behavior and operation metadata for
each invalid combination.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check-publish-deps.mjs`:
- Around line 240-245: Update publishedEntryRoots() to retain tarball-root entry
points such as ./index.d.ts instead of filtering them out, using an explicit
root representation. Adjust findDeclarationDepViolations() so its
entryRoots.has(file.split('/')[0]) lookup recognizes declaration files at the
package root, while preserving existing subdirectory-root handling.

---

Outside diff comments:
In `@packages/1-framework/1-core/operations/src/index.ts`:
- Around line 57-75: Update the runtime validation in the descriptor.self block
to reject every pairwise conflict among hasCodecId, hasTraits, and targetsMany:
codecId with traits, codecId with many, and traits with many. Preserve the
existing CONTRACT.PACK_CONTRIBUTION_INVALID error behavior and operation
metadata for each invalid combination.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5a3691c1-f950-4c96-8869-1c520d322bc9

📥 Commits

Reviewing files that changed from the base of the PR and between 202819d and e892054.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • package.json
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/1-framework/1-core/operations/test/operations-registry.test.ts
  • packages/2-sql/4-lanes/query-builder/package.json
  • packages/3-extensions/postgres/package.json
  • packages/3-extensions/supabase/package.json
  • packages/3-targets/7-drivers/postgres/package.json
  • scripts/check-publish-deps-declarations.test.mjs
  • scripts/check-publish-deps.mjs

Comment thread scripts/check-publish-deps.mjs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/instructions.md`:
- Line 524: Update the fenced diagnostic block at the documented location to
specify the text language by adding the text fence tag, while preserving the
block’s existing contents.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 846e187e-a197-48aa-a68f-c9b012f386e4

📥 Commits

Reviewing files that changed from the base of the PR and between e892054 and b9c2f5c.

📒 Files selected for processing (1)
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.16-to-0.17/instructions.md

Comment thread scripts/check-publish-deps.mjs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/2-sql/5-runtime/test/utils.ts (1)

537-538: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the remaining re-export from this module.

packages/2-sql/5-runtime/test/utils.ts retains a decode-helper re-export outside an exports/ folder. Import the helper at its use sites or move the barrel to an exports/ folder.

As per coding guidelines: “Do not re-export from one file in another, except in exports/ folders.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/2-sql/5-runtime/test/utils.ts` around lines 537 - 538, Remove the
decode-helper re-export from the test utilities module and update its use sites
to import the helper from its defining module, or relocate the barrel into an
exports/ folder. Ensure no re-export remains outside an exports/ directory.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/2-sql/5-runtime/test/utils.ts`:
- Around line 537-538: Remove the decode-helper re-export from the test
utilities module and update its use sites to import the helper from its defining
module, or relocate the barrel into an exports/ folder. Ensure no re-export
remains outside an exports/ directory.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 901dd6b5-7ac8-478d-b1ad-ea1fd27227be

📥 Commits

Reviewing files that changed from the base of the PR and between b9c2f5c and 866121e.

📒 Files selected for processing (4)
  • packages/2-sql/5-runtime/test/utils.ts
  • packages/2-sql/5-runtime/tsdown.config.ts
  • scripts/check-publish-deps-declarations.test.mjs
  • scripts/check-publish-deps.mjs
💤 Files with no reviewable changes (1)
  • packages/2-sql/5-runtime/tsdown.config.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/check-publish-deps-declarations.test.mjs

`@prisma-next/sql-operations` declares `SqlOperationEntry = QueryOperationTypeEntry`
and passes it to the framework generics `OperationDescriptor<T extends
OperationEntry>`, `OperationDescriptors<T>` and `OperationRegistry<T>`. The SQL
self spec has three variants — codec identity, codec traits, and list-typed
(`many`) fields — while the framework `SelfSpec` had only the first two, so the
type argument did not satisfy its constraint and the emitted
`dist/index.d.mts` failed to compile for anyone building with
`exactOptionalPropertyTypes` off (the default) and `skipLibCheck: false`.

In the repo the mismatch is invisible because `packages/0-config/tsconfig/base.json`
sets `exactOptionalPropertyTypes: true`, which keeps `traits?: never` as `never`
(assignable to `readonly string[]`) instead of widening it to `undefined`.

The type mismatch mirrored a real runtime one: `createOperationRegistry().register()`
rejected any `self` without `codecId` or `traits`, so registering a list-targeted
operation threw `CONTRACT.PACK_CONTRIBUTION_INVALID`.

Adds the list variant to the framework `SelfSpec` and accepts it in `register()`.
`many` is already family-blind framework vocabulary (`domain-types.ts`, the
emitter); families refine the variant structurally, which is how SQL carries
`elementTraits`.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
`pg` carries no types of its own — they come from `@types/pg`. Three
publishable packages re-export `pg` types from their `.d.mts` files:

  @prisma-next/driver-postgres    dist/control.d.mts, dist/runtime.d.mts
  @prisma-next/postgres           dist/postgres-*.d.mts
  @prisma-next/extension-supabase dist/runtime.d.mts

All three had `pg` in `dependencies` but `@types/pg` only in
`devDependencies`, which consumers never install. Anyone compiling against
the published declarations with `skipLibCheck: false` got TS7016
("Could not find a declaration file for module 'pg'").

Moves `@types/pg` into `dependencies` for those three. `@types/pg-cursor`
stays a devDependency: `pg-cursor` is only referenced from `dist/*.mjs`,
never from a declaration, so consumers never need its types.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Sweep of the class the `@types/pg` fix belongs to: a publishable package
whose shipped declarations name a module the consumer will not have.
Checked all 65 publishable packages by extracting the module specifiers
from every declaration file in their published entry-point tree with
TypeScript's own file preprocessor.

`@prisma-next/sql-lane-query-builder` declared no `dependencies` at all.
Its public API is written in `Contract` / `SqlStorage` types, but the
packages that own those were devDependencies, and tsdown bundles
devDependency types instead of importing them — so `dist/index.d.mts`
inlined 1211 lines of foreign declarations and carried bare side-effect
imports of `arktype` and `@standard-schema/spec`, neither of which a
consumer installs.

Moves `@prisma-next/contract` and `@prisma-next/sql-contract` into
`dependencies` (the declaration now imports them, 1211 lines → 231, and
the stray `arktype` import is gone) and declares `@standard-schema/spec`,
which the emitter still names.

Depending on the packages whose types it re-exposes also puts the package
back under the `@prisma-next/*` exact-pin rule, so a consumer gets one
identity for `Contract` rather than a structurally-similar inlined copy.

One instance is not fixed here. `@prisma-next/sql-runtime` publishes a
`./test/utils` subpath whose graph reaches `@prisma-next/test-utils`, a
`private: true` package that is never published — so the subpath is
already broken for external consumers at runtime, and no manifest edit
fixes it. Removing it from the published surface is a public-surface
decision; the next commit records it as a named exemption so it stays
visible.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Both defects in this branch had the same shape: a package compiled in the
workspace, where pnpm has every devDependency linked, and failed for a
consumer who builds with `skipLibCheck: false`. Nothing caught either one.

Adds a third rule to `check-publish-deps`. For every publishable package it
already packs the exact tarball that ships; this reads the declaration files
in that tarball's published entry-point tree and asserts every module
specifier they name resolves for a consumer — the package is in
`dependencies` / `peerDependencies` / `optionalDependencies`, and when it
carries no types of its own, its `@types/*` companion is declared too.

`check-publish-deps` is the right home: `publish.yml` runs `pnpm build`
immediately before it, so `dist/` exists, and the tarball is what consumers
actually get rather than a reconstruction of it.

Specifiers come from TypeScript's own file preprocessor, not a regex — an
earlier regex pass read `pg-pool` prose in a doc comment as an import of
`pg`. Scope is the entry-point tree (`exports` / `types` targets), because
tarballs also ship `src/` for declaration maps and nothing in a consumer's
module graph reaches those files.

Verified against both defects: reintroducing them makes the gate report
`dist/control.d.mts names "pg" — declare "@types/pg"` and `dist/index.d.mts
names "arktype" — declare "arktype"`.

Carries one exemption, printed on every run so it cannot go quiet:
`@prisma-next/sql-runtime`'s `./test/utils` subpath reaches the private,
never-published `@prisma-next/test-utils`.

Tests land in a `node --test` file wired into `test:scripts`. The existing
`scripts/check-publish-deps.test.mjs` is vitest-style but no runner picks it
up — the root vitest config only globs `packages/**/vitest.config.ts` — so
tests added there would never execute.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The `@types/pg` fix touches `packages/3-extensions/`, so `check-upgrade-coverage`
requires this PR to record its intent in the 0.16 → 0.17 extension-author
instructions.

The change is not a no-op for extension authors, and the reason is the
workaround it removes. `@prisma-next/postgres` and `@prisma-next/extension-supabase`
re-export `pg` types from their declarations but only devDepended on `@types/pg`,
so authors had to add `@types/pg` themselves to compile. Now that those packages
ship it, an author who keeps their own entry at a different version has two
`@types/pg` copies in the tree — and `pg` has no types of its own, so `pg.Client`
and `pg.Pool` get two identities.

Verified against a scratch consumer built from the packed tarballs: with
`@types/pg@8.11.0` alongside the shipped `8.20.0`, passing a `pg.Client` into
`new PostgresControlDriver(client)` fails with

  Argument of type 'Client' is not assignable to parameter of type 'Client'.
    Type 'Client' is missing the following properties from type 'Client':
    connection, setTypeParser, getTypeParser

and aligning the version compiles clean. So the entry states an action —
drop the entry and take it transitively, or pin it to what
`@prisma-next/postgres` depends on — rather than a courtesy note, which the
authoring skill treats as a defect.

Appends one `changes[]` entry and one body section; the existing entries are
untouched.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The exemption was the wrong outcome — it made the guard document a defect
instead of catching it. Fixes the defect and deletes the carve-out.

`@prisma-next/sql-runtime/test/utils` is published, and its output named
`@prisma-next/test-utils`, which is `private: true` and never reaches the
registry. Two separate reasons, both removed:

Re-export block. It forwarded `collectAsync`, `createDevDatabase`,
`DevDatabase`, `teardownTestDatabase`, and `withClient`. Dead — all twelve
in-repo importers of this subpath already take those symbols straight from
`@prisma-next/test-utils` and pull only sql-runtime-specific ones
(`createTestRuntime`, `createStubAdapter`, `createTestContext`,
`seedTestMarker`, `decodeRow`) from here. Dropped; no call site changed.

Three internal helpers. Inlined `collectAsync` and `drainAsyncIterable`
(eleven lines, no dependencies) and a trimmed `applicationDomainOf` (the
`valueObjects` branch was unused here). I tried bundling first — dropping
`@prisma-next/test-utils` from tsdown `external` — and rejected it on
evidence: tree-shaking removed the values but left `import "@prisma/dev"`
in the emitted `.mjs`, trading one undeclared specifier for another. The
inline is deterministic and does not depend on tree-shaking behaviour.

That left `pg`, which the subpath had all along: `setupTestDatabase`,
`seedTestMarker`, and `writeTestContractMarker` were typed against
`pg.Client`, and `pg` is a devDependency here. Declaring it was not an
option — this is the target-agnostic SQL runtime, which must not depend on
a Postgres driver. All three only ever call `client.query(...)`, so they
now take a structural `TestSqlClient`; `setupTestDatabase` is generic over
it so callers passing `pg.Client`-typed callbacks still infer correctly.
The `external` list is now empty and gone.

Every specifier left in the packed `dist/test/utils.{mjs,d.mts}` is a
declared dependency: @prisma-next/{contract,framework-components,ids,
sql-contract,sql-relational-core,utils}, arktype, node:crypto.

Exemption plumbing fully removed, not emptied: the constant, the check-site
lookup, `reportExemptions` and both call sites, and the test that asserted
the carve-out — replaced by one asserting a private package is flagged like
any other undeclared module.

Also fixes a literal NUL byte I had left in a template string in
check-publish-deps.mjs. It made the file read as binary, so plain `grep`
silently returned no matches against it — which is how the leftover
`reportExemptions` call sites nearly survived this cleanup.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
`publishedEntryRoots()` derived each root by taking the leading path segment
of an `exports` / `types` / `main` / `module` target and keeping it only when
it looked like a directory. An entry with no directory component — a package
whose `types` is `./index.d.ts` — produced no root at all, and the lookup
`entryRoots.has(file.split("/")[0])` then matched nothing, so every
declaration in that tarball went unchecked and the gate reported OK having
inspected no files.

No package in the repo publishes that way today, which is precisely the
danger: the failure mode is silence, so the first package with root-level
entries would get a green check that read nothing. Same shape as the NUL
byte earlier in this branch — a verification that passes because it looked
at nothing.

A root-level entry is now represented explicitly as `PACKAGE_ROOT` ("."),
and `declarationRoot()` maps a declaration with no directory component to
the same value, so root and subdirectory entries compare the same way and
mixed packages keep both. The `package.json` self-reference is skipped: it
is a manifest, not a code entry, so it does not put the root in scope on
its own.

Three tests, each failing before the fix: `publishedEntryRoots` yields `.`
for a root-level entry; a fixture package with `types: "./index.d.ts"` and
an `index.d.ts` naming an undeclared `arktype` is flagged; and a package
publishing from both root and `dist/` reports violations in both while
still ignoring `src/`. The last one pins that the fix widens scope without
losing the subdirectory scoping.

Also tags the fenced error-message block in the 0.16-to-0.17 extension
instructions as `text` (markdownlint MD040).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden
wmadden force-pushed the tml-3125-declaration-fixes branch from c022d8a to 9e36363 Compare August 3, 2026 08:45
@wmadden
wmadden added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit f43beb0 Aug 3, 2026
5 of 6 checks passed
@wmadden
wmadden deleted the tml-3125-declaration-fixes branch August 3, 2026 08:45
wmadden-electric added a commit that referenced this pull request Aug 3, 2026
Fifteen conflicts. Five are files main deleted — the `functional-indexes`
project write-ups, whose content became ADR 243 — and this branch had only
renamed strings inside them, so the deletion stands. Seven took main's content
and then the rename. Three needed more.

`scripts/check-publish-deps.mjs` — main's declaration-dependency rule is taken
whole. Its scope literals are renamed, and that matters more than it reads:
the exact-pin check keys on the scope by name, so left as `@prisma-next/*` it
would have matched nothing and passed vacuously on this tree — the same defect
found in the dist scan a merge ago. The new rule 3 is scope-agnostic (it reads
package names out of specifiers), so it needed nothing.

`package.json` — taking main's wholesale dropped this branch's two checks and
three of its script-test entries, and left main's `--filter @internal/e2e-tests`
against packages this branch renamed to bare names. Reconciled key by key
rather than by side.

The extension-author upgrade notes — main appended `postgres-packages-now-ship-
types-pg` under the old directory name while this branch renamed the directory
and carries an entry of its own. Both survive in the renamed location, 27
entries total, and both keep their `@prisma-next` literals: each tells a reader
what their pack depends on today, which is the name they still have.

The `@types/pg` move from #29862 pulls three shells with it —
`orm-target-postgres`, `orm-postgres`, `orm-extension-supabase` each carry a
package that now has it as a runtime dependency, and the shell manifests
validate against exactly that.

ADR 243 names the old scope and is a dated decision record like ADR 234, so the
legacy-name check's existing allowance covers it with no change.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric added a commit that referenced this pull request Aug 3, 2026
Fifteen conflicts. Five are files main deleted — the `functional-indexes`
project write-ups, whose content became ADR 243 — and this branch had only
renamed strings inside them, so the deletion stands. Seven took main's content
and then the rename. Three needed more.

`scripts/check-publish-deps.mjs` — main's declaration-dependency rule is taken
whole. Its scope literals are renamed, and that matters more than it reads:
the exact-pin check keys on the scope by name, so left as `@prisma-next/*` it
would have matched nothing and passed vacuously on this tree — the same defect
found in the dist scan a merge ago. The new rule 3 is scope-agnostic (it reads
package names out of specifiers), so it needed nothing.

`package.json` — taking main's wholesale dropped this branch's two checks and
three of its script-test entries, and left main's `--filter @internal/e2e-tests`
against packages this branch renamed to bare names. Reconciled key by key
rather than by side.

The extension-author upgrade notes — main appended `postgres-packages-now-ship-
types-pg` under the old directory name while this branch renamed the directory
and carries an entry of its own. Both survive in the renamed location, 27
entries total, and both keep their `@prisma-next` literals: each tells a reader
what their pack depends on today, which is the name they still have.

The `@types/pg` move from #29862 pulls three shells with it —
`orm-target-postgres`, `orm-postgres`, `orm-extension-supabase` each carry a
package that now has it as a runtime dependency, and the shell manifests
validate against exactly that.

ADR 243 names the old scope and is a dated decision record like ADR 234, so the
legacy-name check's existing allowance covers it with no change.

Alongside the merge, the language server stops being published surface. Editors
reach it by spawning `prisma-next lsp` and speaking the protocol over stdio,
never by importing a module — the split from the CLI was for code organization,
not API — and the published entrypoint's only consumer in the tree was a README
mention. `@prisma/orm-toolchain` goes from 54 export subpaths to 53.

The package is unchanged and still bundled: the CLI depends on it, so the shell
carries the code. Expressing that took one addition to the shell map — a
package can now be mapped to a shell without publishing entrypoints, which is
what "bundled here, named nowhere" needs to say. ADR 242 listed language-server
distribution as a deferred decision; that entry now records the resolution.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants