Skip to content

chore: Improve performance for prisma-client generation #2177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

mhodgson
Copy link

@mhodgson mhodgson commented Jul 8, 2025

In our fairly large schema with delegate types we saw our zenstack generate time balloon up to almost 10 minutes when switching to the new 'prisma-client' generator. I was able to track down the performance issue to the use of ts-morph in overwriting the model/*.ts files.

This change does two things:

  1. Accumulates the model type changes into a temporary list of statements before updating the source file.
  2. Moves the file overwriting outside of the ts-morph usage, making it nearly instantaneous.

It is likely these same changes would speed up the existing "legacy" implementation as well, but probably not as noticeably since there is only one file being moved instead of one for each model.

ymc9 added 30 commits November 22, 2024 11:59
Copy link
Contributor

coderabbitai bot commented Jul 8, 2025

📝 Walkthrough

"""

Walkthrough

The change refactors the process for transforming and saving TypeScript source files generated for Prisma models. Instead of incrementally building new files and moving them, it now accumulates transformed statements, creates new files in one step, and then synchronously renames them to overwrite the originals. It also adds logic to remove delegate_aux_* fields from variable statement initializers in the Prisma client namespace file. The regex in trimEmptyLines is updated to remove leading commas on empty lines. No transformation logic or public API is altered.

Changes

File(s) Change Summary
packages/schema/src/plugins/enhancer/enhance/index.ts Refactored file transformation logic: accumulates statements, creates new files, and renames them after; adds helper methods transformVariableStatementProps and findAuxProps to remove delegate_aux_* fields from variable initializers; updates regex in trimEmptyLines.
tests/integration/tests/cli/generate.test.ts Changed test setup to install zod package version 3 explicitly instead of latest version.

Sequence Diagram(s)

sequenceDiagram
    participant Enhancer
    participant FileSystem

    Enhancer->>Enhancer: Process Prisma client namespace file
    Enhancer->>Enhancer: Remove delegate_aux_* fields from variable statements
    Enhancer->>FileSystem: Write fixed namespace file and rename over original

    Enhancer->>Enhancer: For each Prisma model file:
    Enhancer->>Enhancer: Accumulate transformed statements
    Enhancer->>FileSystem: Create new source file with all statements
    Enhancer->>FileSystem: After all models processed, rename fixed files over originals
Loading

Possibly related PRs

  • zenstackhq/zenstack#1874: Refactors saving logic in the same file, focusing on synchronous saving and unified file handling.
  • zenstackhq/zenstack#1830: Refines auxiliary field filtering in the same class and file, related to auxiliary field transformations.
  • zenstackhq/zenstack#2171: Modifies saving and naming logic for generated Prisma client files in the same method, with different transformation concerns.

Suggested reviewers

  • ymc9
    """
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b73b3de and 999fdde.

📒 Files selected for processing (1)
  • packages/schema/src/plugins/enhancer/enhance/index.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/schema/src/plugins/enhancer/enhance/index.ts (2)
packages/schema/src/plugins/zod/generator.ts (1)
  • project (165-167)
packages/plugins/tanstack-query/scripts/postbuild.js (1)
  • fs (3-3)
🔇 Additional comments (6)
packages/schema/src/plugins/enhancer/enhance/index.ts (6)

512-512: LGTM! Good optimization approach.

Accumulating statements in an array instead of incremental AST additions is a smart performance optimization that aligns with the PR objectives.


525-525: LGTM! Consistent with the accumulation pattern.

The change correctly pushes import statements to the accumulated array instead of adding them incrementally to the source file.


531-531: LGTM! Proper transformation accumulation.

The interface transformation is correctly accumulated in the statements array.


533-533: LGTM! Proper transformation accumulation.

The type alias transformation is correctly accumulated in the statements array.


535-535: LGTM! Comprehensive statement handling.

All other nodes are correctly handled by getting their text and adding to the statements array.


539-544: LGTM! Efficient file creation approach.

The optimization of getting the original file structure and replacing statements before creating the new file is a good approach that should improve performance significantly.

@mhodgson
Copy link
Author

mhodgson commented Jul 9, 2025

@ymc9 Any thoughts here? I'm still tracking down some other issues related to how Zenstack re-exports the logical-prisma-client, but wondering if these changes make sense to you.

@ymc9
Copy link
Member

ymc9 commented Jul 9, 2025

I'll finish reviewing it today @mhodgson . Thank you for making the PR!

@ymc9 ymc9 changed the base branch from main to dev July 10, 2025 12:32
Copy link
Member

@ymc9 ymc9 left a comment

Choose a reason for hiding this comment

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

Hi @mhodgson , could you take a look at my comments? Thanks!

- Unnecessary regex escape
- Type assertion
- Removed logic that updates `prismaNamespace.ts` which seems to be unnecessary
@ymc9
Copy link
Member

ymc9 commented Jul 14, 2025

Hi @mhodgson I guess you've been busy. I've made several changes based on my judgment and will merge the PR. Please take a look when you have time. About to make a new patch release soon.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c882c18 and 6def505.

📒 Files selected for processing (1)
  • tests/integration/tests/cli/generate.test.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
tests/integration/tests/cli/generate.test.ts (1)
packages/testtools/src/schema.ts (1)
  • installPackage (69-71)

@@ -45,7 +45,7 @@ model Post {
// set up project
fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' }));
createNpmrc();
installPackage('prisma @prisma/client zod');
installPackage('prisma @prisma/client zod@3');
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Test dependency version pinning looks good, but verify necessity.

The explicit pinning to zod@3 ensures test stability and compatibility with the enhanced schema processing logic mentioned in the PR. However, please verify if this version constraint is actually required or if it's possible to support newer zod versions.

Consider running the following to check if the latest zod version would work:


🏁 Script executed:

#!/bin/bash
# Check latest zod version and any breaking changes
curl -s https://api.github.com/repos/colinhacks/zod/releases/latest | jq '.tag_name'

# Check if there are any major version differences that would justify pinning
npm view zod versions --json | jq '.[-10:]'

Length of output: 436


Update test to use the latest Zod major version

Latest stable Zod release is v4.0.5, so pinning to v3 may be unnecessarily restrictive and could prevent us from catching breaking changes or benefiting from fixes in v4. Unless there’s a specific incompatibility with Zod 4, please update the test setup.

• Location: tests/integration/tests/cli/generate.test.ts:48
• Change:

- installPackage('prisma @prisma/client zod@3');
+ installPackage('prisma @prisma/client zod@4');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
installPackage('prisma @prisma/client zod@3');
installPackage('prisma @prisma/client zod@4');
🤖 Prompt for AI Agents
In tests/integration/tests/cli/generate.test.ts at line 48, the test installs
Zod version 3 which is outdated. Update the installPackage call to use the
latest stable Zod major version 4 instead of pinning to v3, unless there is a
known incompatibility. This will ensure the test uses the current Zod release
and can catch any breaking changes or benefit from fixes.

@ymc9 ymc9 merged commit cd0c415 into zenstackhq:dev Jul 14, 2025
12 checks passed
@mhodgson
Copy link
Author

@ymc9 yes, sorry, was about to get back to this in the morning. I have some broader issues with the prisma-client generator support here that I need to sync with you on. What is boils down to is that the new generator creates two "barrel" style files that are meant for exporting just the models and enums (named models.ts and enums.ts). These are safe for an application to import and use for type safety in server or client code.

In the current model.ts file that Zenstack is creating, there entire Prisma client.ts is re-exported, which contains server-specific code using node module likes path and fs. My recommendation is that Zenstack mirrors the Prisma generated code and simply exports only the models in models.ts and adds a new enums.ts that does the same (re-exporting the models.ts and enums.ts files from the logical-prisma-client). However, this leaves a glaring gap, because the full Prisma namespace is then no longer exported anywhere from Zenstack. To address this, we need to decide if we should:

  1. re-export the full Prisma object from the logical-prisma-client/client.ts file from the enhance.ts in Zenstack or...
  2. Add a new client.ts file in Zenstack that just re-exports the logical-prisma-client/client.ts.

Thoughts? I'm happy to provide another PR, but need your thoughts and guidance here as it is really up to you. As it currently stands, the Zenstack exports are incompatible with any framework trying to use Prisma model or enum types in a client side bundle.

@ymc9
Copy link
Member

ymc9 commented Jul 14, 2025

@ymc9 yes, sorry, was about to get back to this in the morning. I have some broader issues with the prisma-client generator support here that I need to sync with you on. What is boils down to is that the new generator creates two "barrel" style files that are meant for exporting just the models and enums (named models.ts and enums.ts). These are safe for an application to import and use for type safety in server or client code.

In the current model.ts file that Zenstack is creating, there entire Prisma client.ts is re-exported, which contains server-specific code using node module likes path and fs. My recommendation is that Zenstack mirrors the Prisma generated code and simply exports only the models in models.ts and adds a new enums.ts that does the same (re-exporting the models.ts and enums.ts files from the logical-prisma-client). However, this leaves a glaring gap, because the full Prisma namespace is then no longer exported anywhere from Zenstack. To address this, we need to decide if we should:

  1. re-export the full Prisma object from the logical-prisma-client/client.ts file from the enhance.ts in Zenstack or...
  2. Add a new client.ts file in Zenstack that just re-exports the logical-prisma-client/client.ts.

Thoughts? I'm happy to provide another PR, but need your thoughts and guidance here as it is really up to you. As it currently stands, the Zenstack exports are incompatible with any framework trying to use Prisma model or enum types in a client side bundle.

Hi @mhodgson , thanks for following up. I think this is not specific to the new "prisma-client" generator, right? With the old generator, the model.ts file also reexports everything from either the logical client or the original prisma client.

For using the exported model types, does it work if you do a type-only import? I assume bundlers will refrain from bundling js code if everything it seems is type import. But I haven't tried. If so, is it specifically about importing enums (which have to be value imports)?

I agree adding a new client.ts is a good idea - to keep all other files (models.ts, and a new enum.ts) completely safe for frontend import. Just wanted to have a good understanding of the real issue first.

@mhodgson
Copy link
Author

@ymc9 you are correct. This worked with the prisma-client-js generator, but I think it was because the previous Prisma Client implementation was safe to load on the client since the server specific parts were wrapped up in the rust binary. Now, the client.ts file starts immediately with using server specific code that cannot be used in the browser.

You are right that just importing types works fine, but in this case there are also javascript values (namely the enums) that must be loaded, so just importing types leaves a gap. In our Remix (react-router) codebase I didn't discover this issue until running into actual front-end errors where the code was trying to load files from disk. I'm hopeful that having a clear separation of what files are browser safe vs. meant for the server will help.

I thought about raising this issue with the Prisma folks, but I think loading anything out of Prisma called client is safe to assume should not be loaded on the client side, so they are probably correct to assume they can use node specific file access APIs there.

I like the idea of just mirroring the structure of the Prisma Client as much as possible with the Zenstack files/exports. It should make onboarding of new users clean and clear, and adheres to the principal of least surprise. Then the only new/extra part of using Zenstack is the enhance.ts file that is probably only accessed once where the database connection is established.

@ymc9
Copy link
Member

ymc9 commented Jul 14, 2025

@ymc9 you are correct. This worked with the prisma-client-js generator, but I think it was because the previous Prisma Client implementation was safe to load on the client since the server specific parts were wrapped up in the rust binary. Now, the client.ts file starts immediately with using server specific code that cannot be used in the browser.

You are right that just importing types works fine, but in this case there are also javascript values (namely the enums) that must be loaded, so just importing types leaves a gap. In our Remix (react-router) codebase I didn't discover this issue until running into actual front-end errors where the code was trying to load files from disk. I'm hopeful that having a clear separation of what files are browser safe vs. meant for the server will help.

I thought about raising this issue with the Prisma folks, but I think loading anything out of Prisma called client is safe to assume should not be loaded on the client side, so they are probably correct to assume they can use node specific file access APIs there.

I like the idea of just mirroring the structure of the Prisma Client as much as possible with the Zenstack files/exports. It should make onboarding of new users clean and clear, and adheres to the principal of least surprise. Then the only new/extra part of using Zenstack is the enhance.ts file that is probably only accessed once where the database connection is established.

Thanks for the clarification. We're on the same page then. A PR is greatly appreciated if you have time!

@mhodgson
Copy link
Author

@ymc9 PR for discussed changes: #2184

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.

2 participants