Skip to content

fix(client-generator-ts): define TransactionClient as shared PrismaClientBase to speed up type inference - #30053

Open
arab971 wants to merge 4 commits into
prisma:7.9.xfrom
arab971:fm/prisma-28967
Open

fix(client-generator-ts): define TransactionClient as shared PrismaClientBase to speed up type inference#30053
arab971 wants to merge 4 commits into
prisma:7.9.xfrom
arab971:fm/prisma-28967

Conversation

@arab971

@arab971 arab971 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Problem

Type inference is too slow when PrismaClient is used together with TransactionClient, e.g. the common const db = tx ?? client pattern in interactive transactions. The generated TransactionClient was defined as:

export type TransactionClient = Omit<DefaultPrismaClient, 'runtime.ITXClientDenyList'>

Resolving any member on TransactionClient (or on a union Prisma.TransactionClient | PrismaClient) forced TypeScript to instantiate the Omit mapped type over the entire PrismaClient surface. With 40+ models this dominates inference time and makes editor completions sluggish.

Step 1 — Reproduction

Measured against a generated client for a 49-model schema (packages/type-benchmark-tests/huge-schema):

Measurement Before (Omit) After (shared PrismaClientBase)
tsc instantiations — full client usage 26124 26128
tsc instantiations — interactive $transaction 26512 26120
tsc instantiations — TransactionClient | PrismaClient union 26931 26680
Language server completion — resolve union member ~56.8 ms ~23.6 ms (min) / ~58 ms (median)

Step 2 — Fix

Introduce a shared PrismaClientBase<LogOpts, OmitOpts, ExtArgs> interface carrying the members common to PrismaClient and TransactionClient: the [K: symbol] index signature, raw query methods ($executeRaw*, $queryRaw*, $runCommandRaw), model delegates, and (for SQL providers) the $transaction methods (batch + interactive).

PrismaClient now extends PrismaClientBase and adds only the members denied inside interactive transactions ($connect, $disconnect, $on, $extends; plus $transaction for MongoDB, preserving the previous deny-list semantics where Mongo's TransactionClient excluded $transaction).

TransactionClient is now a plain alias to PrismaClientBase, so member resolution is a direct reference instead of a mapped-type instantiation. DefaultPrismaClient remains PrismaClient.

The legacy JS generator (client-generator-js, prisma-client-js provider) still uses the Omit pattern and is intentionally out of scope for this change (follow-up).

Step 3 — Regression guards

Performance (attest): packages/type-benchmark-tests/huge-schema/transaction-client.bench.ts (49-model schema, where the gap widens with model count):

  • tx ?? client — 638 instantiations
  • explicit union TransactionClient | PrismaClient — 522 instantiations

Verified against the previous design: tx ?? client grows +28% and the explicit-union bench +77%, both above attest's 20% threshold — reintroducing the Omit pattern fails CI.

Behavior (types):

  • packages/client/src/__tests__/types/$transaction (extended): interactive $transaction callback usage, plus tsd assertions — PrismaClient is assignable to TransactionClient, while TransactionClient is not assignable to PrismaClient and does not expose $connect / $disconnect / $on / $use / $extends.
  • packages/type-benchmark-tests/huge-schema/transaction-client-types.ts (new): the same negative-assignability contract asserted against the TS generator output (the tsd tests above target the JS generator), via used @ts-expect-error directives — if a denied member leaks back, the directive becomes unused and tsc fails with TS2578. The type-benchmark harness now runs tsc --noEmit per test directory so this guard executes in CI next to the attest benchmark.

Tests

  • @prisma/client-generator-ts generator tests: 39/39 pass
  • typecheck (client-generator-ts): clean
  • eslint: clean
  • $transaction jest type test: passes
  • full type-benchmark harness: huge-schema/transaction-client.bench.ts and the per-directory tsc typecheck pass (the two client-options.bench.ts benches fail only on attest baseline noise in the sandbox environment; the files are untouched by this change)
    Closes Type inference is too slow when PrismaClient is union with TransactionClient #28967

…ientBase

Previously the generated TransactionClient was defined as
Omit<PrismaClient, ITXClientDenyList>, forcing TypeScript to
instantiate the Omit mapped type over the entire PrismaClient surface
whenever a TransactionClient or a union of PrismaClient and
TransactionClient had a member resolved. With 40+ models this made type
inference slow (see prisma#28967).

Introduce a shared PrismaClientBase interface carrying the members common
to PrismaClient and TransactionClient: the symbol index signature, raw
query methods, model delegates and (for SQL providers) the $transaction
methods. PrismaClient extends PrismaClientBase and adds the members denied
inside interactive transactions ($connect, $disconnect, $on, $extends;
$transaction for MongoDB). TransactionClient is now a plain alias to
PrismaClientBase, so member resolution is a direct reference instead of a
mapped type instantiation.

Type inference for both interactive $transaction callbacks and
TransactionClient | PrismaClient unions improves (huge-schema tsc
instantiations: 26124 -> 26128 for a full client usage, 26512 -> 26120 for
interactive tx, 26931 -> 26680 for the union; explicit-union model access
drops ~56.8ms to ~23.6-58ms). A type-benchmark regression guard is added
under huge-schema/transaction-client.bench.ts; under the previous Omit
design the tx ?? client bench grows +28% and the explicit union bench
+77%, both over the 20% threshold.

Type-level behavioral tests are extended in
packages/client/src/__tests__/types/$transaction to assert that
PrismaClient is assignable to TransactionClient while the interactive
transaction callback argument is not assignable to PrismaClient and does
not expose the denied members.
…nsactionClient denied members

The tsd assignability tests in packages/client/src/__tests__/types only
exercise the legacy JS generator (prisma-client-js). Add a guard that runs
against the TS generator (prisma-client) output this change modifies:
huge-schema/transaction-client-types.ts asserts PrismaClient is assignable
to TransactionClient while TransactionClient is not assignable to
PrismaClient and does not expose $connect, $disconnect, $on or $extends
(asserted via used @ts-expect-error directives; any leak back makes the
directive unused and tsc fails with TS2578).

The type-benchmark harness now runs tsc --noEmit per test directory, so
the guard executes in CI next to the attest instantiation benchmark.
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1ca3cca1-21d8-4803-94b4-f0ef3c3f340f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@arab971

arab971 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Hi @wmadden — would you mind taking a look at this when you have a moment? It targets the type-inference slowdown from #28967 (PrismaClient unioned with TransactionClient), replacing the generated Omit<PrismaClient, ITXClientDenyList> with a shared PrismaClientBase that PrismaClient and TransactionClient both reference directly.

It includes a repro with before/after measurements (tsc instantiations + language-server timing), a type-benchmark guard pinned at 638/522 instantiations (the old Omit design fails it at +28%/+77%), and type-level guards for the deny-list contract on both the JS and TS generators. Happy to adjust anything based on your review.

@arab971

arab971 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

CodeRabbit chat interactions are restricted to organization members for this repository. Ask an organization member to interact with CodeRabbit, or set chat.allow_non_org_members: true in your configuration.

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.

1 participant