-
-
Notifications
You must be signed in to change notification settings - Fork 35
Memory usage optimization #3139
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
Conversation
📝 WalkthroughWalkthroughIntroduces a new TypescriptAPI class that centralizes TypeScript AST helpers and defers the TypeScript import; internal modules (integration-base, integration, zts, zts-helpers, tests) are refactored to use instance-bound api/factory calls. Docs and package version updated; no public API removals. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Integration as IntegrationBase / Integration
participant API as TypescriptAPI
participant TS as TypeScript (ts / factory)
participant ZTS as zts producers
Integration->>API: instantiate TypescriptAPI
Note right of API `#DDEBF7`: Loads `ts` lazily\ninitializes `f`, modifiers, helpers
Integration->>API: this.api.makeType / this.api.f.createX / this.api.printNode
API->>TS: delegate to ts / ts.factory to build AST nodes
ZTS->>API: producer calls (api.makeLiteralType, ensureTypeNode, makeUnion, etc.)
API->>TS: create primitives / type nodes / comments
API-->>Integration: return AST nodes / printed code
Integration-->>ZTS: provide generated type artifacts / emitted code
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (9)📓 Common learnings📚 Learning: 2025-05-28T18:58:10.064ZApplied to files:
📚 Learning: 2025-08-01T09:48:13.742ZApplied to files:
📚 Learning: 2025-09-29T03:35:55.561ZApplied to files:
📚 Learning: 2025-06-14T16:42:52.972ZApplied to files:
📚 Learning: 2025-08-08T11:59:04.814ZApplied to files:
📚 Learning: 2025-10-02T17:42:48.840ZApplied to files:
📚 Learning: 2025-09-29T06:00:16.488ZApplied to files:
📚 Learning: 2025-09-25T14:04:50.830ZApplied to files:
🔇 Additional comments (6)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
express-zod-api/src/typescript-api.ts (1)
482-500: Tightenliterally()return type for bigint literalsThe
literally()implementation correctly createsBigIntLiteralnodes at runtime, but the conditional return type doesn’t reflect that branch and falls through tots.NullLiteralforT extends bigint. If callers ever rely on the precise literal node type, this will be misleading.You could make the signature more accurate along these lines:
public literally = < T extends string | null | boolean | number | bigint >( subj: T, ) => ( typeof subj === "number" ? this.f.createNumericLiteral(subj) : typeof subj === "bigint" ? this.f.createBigIntLiteral(subj.toString()) : typeof subj === "boolean" ? subj ? this.f.createTrue() : this.f.createFalse() : subj === null ? this.f.createNull() : this.f.createStringLiteral(subj) ) as T extends string ? ts.StringLiteral : T extends number ? ts.NumericLiteral : T extends boolean ? ts.BooleanLiteral : T extends bigint ? ts.BigIntLiteral : ts.NullLiteral;This preserves current behavior while making the type system match it more closely.
express-zod-api/src/zts-helpers.ts (1)
4-10: Newapi: TypescriptAPIon ZTSContext is consistent with the refactorExtending
ZTSContextwith anapifield and importingTypescriptAPIas a type cleanly wires the new AST helper into ZTS producers. Just ensure all ZTSContext construction sites now initializeapito avoid undefined access at runtime.express-zod-api/src/zts.ts (1)
162-165: Consider replacing empty object patterns to satisfy linter.Biome flags empty object patterns
{}as errors. While the pattern is intentional (ignoring the unused schema parameter), consider using an underscore prefix to satisfy the linter and improve readability.const onPrimitive = (syntaxKind: ts.KeywordTypeSyntaxKind): Producer => - ({}, { api }) => + (_schema, { api }) => api.ensureTypeNode(syntaxKind);The same pattern appears at lines 208, 215, and 270.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
CHANGELOG.md(1 hunks)README.md(1 hunks)express-zod-api/src/integration-base.ts(18 hunks)express-zod-api/src/integration.ts(6 hunks)express-zod-api/src/typescript-api.ts(2 hunks)express-zod-api/src/zts-helpers.ts(1 hunks)express-zod-api/src/zts.ts(7 hunks)express-zod-api/tests/zts.spec.ts(2 hunks)
🧰 Additional context used
🧠 Learnings (17)
📓 Common learnings
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2428
File: express-zod-api/src/index.ts:44-44
Timestamp: 2025-05-28T18:58:10.064Z
Learning: The type-only import `import type {} from "qs";` in express-zod-api/src/index.ts is necessary to avoid TS2742 errors for exported functions like attachRouting, makeRequestMock, testEndpoint, and testMiddleware that have types depending on types/qs. This import provides the reference TypeScript needs to infer portable type names.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-09-29T03:35:55.561Z
Learning: In the express-zod-api repository, packages are built using tsdown before publishing. Source code with .ts extensions is not published - only the built JavaScript bundles and declaration files in the dist/ directory are published to npm. This means .ts extensions in source imports don't affect consumers.
📚 Learning: 2025-05-28T18:58:10.064Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2428
File: express-zod-api/src/index.ts:44-44
Timestamp: 2025-05-28T18:58:10.064Z
Learning: The type-only import `import type {} from "qs";` in express-zod-api/src/index.ts is necessary to avoid TS2742 errors for exported functions like attachRouting, makeRequestMock, testEndpoint, and testMiddleware that have types depending on types/qs. This import provides the reference TypeScript needs to infer portable type names.
Applied to files:
express-zod-api/src/zts-helpers.tsexpress-zod-api/tests/zts.spec.tsexpress-zod-api/src/integration-base.tsexpress-zod-api/src/zts.tsexpress-zod-api/src/typescript-api.tsexpress-zod-api/src/integration.ts
📚 Learning: 2025-08-01T09:48:13.742Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-08-01T09:48:13.742Z
Learning: In express-zod-api, when migrating from Zod v3 to v4, the correct approach for internal type imports is to change from `import type { $ZodType } from "zod/v4/core"` to `import { z } from "zod"` and then use `z.core.$ZodType`. The zod/v4/core module is reexported as z.core by the main zod package, making this a valid and working approach.
Applied to files:
express-zod-api/src/zts-helpers.tsexpress-zod-api/tests/zts.spec.tsexpress-zod-api/src/integration-base.tsexpress-zod-api/src/zts.tsexpress-zod-api/src/typescript-api.tsexpress-zod-api/src/integration.ts
📚 Learning: 2025-09-29T03:35:55.561Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-09-29T03:35:55.561Z
Learning: In the express-zod-api repository, packages are built using tsdown before publishing. Source code with .ts extensions is not published - only the built JavaScript bundles and declaration files in the dist/ directory are published to npm. This means .ts extensions in source imports don't affect consumers.
Applied to files:
express-zod-api/src/zts-helpers.tsexpress-zod-api/tests/zts.spec.tsexpress-zod-api/src/integration-base.tsexpress-zod-api/src/zts.tsexpress-zod-api/src/typescript-api.tsexpress-zod-api/src/integration.ts
📚 Learning: 2025-06-14T16:42:52.972Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2736
File: express-zod-api/tsup.config.ts:12-26
Timestamp: 2025-06-14T16:42:52.972Z
Learning: In express-zod-api tsup configurations, the direct mutation of `options.supported` in the `esbuildOptions` callback is intentional behavior and should not be flagged as a side effect issue.
Applied to files:
express-zod-api/src/zts-helpers.tsexpress-zod-api/tests/zts.spec.tsexpress-zod-api/src/zts.tsexpress-zod-api/src/typescript-api.tsexpress-zod-api/src/integration.ts
📚 Learning: 2025-09-29T06:00:16.488Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2974
File: express-zod-api/src/documentation-helpers.ts:48-48
Timestamp: 2025-09-29T06:00:16.488Z
Learning: In the express-zod-api repository, the project uses pnpm workspaces with autoInstallPeers: false, meaning peerDependencies are not installed. The actual TypeScript version used for development comes from devDependencies which references "catalog:dev" in pnpm-workspace.yaml where it's set to a recent version (^5.9.2), not the peerDependency version which is just the minimum consumer requirement.
Applied to files:
express-zod-api/src/zts-helpers.tsexpress-zod-api/tests/zts.spec.tsexpress-zod-api/src/zts.tsexpress-zod-api/src/typescript-api.tsexpress-zod-api/src/integration.ts
📚 Learning: 2025-08-08T11:59:04.814Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2878
File: zod-plugin/runtime.ts:39-42
Timestamp: 2025-08-08T11:59:04.814Z
Learning: express-zod-api/zod-plugin/runtime.ts (TypeScript): Do not memoize the ZodType.prototype.brand getter. The current design returning setBrand.bind(this) on each access is intentional/preferred; avoid redefining "brand" as a data property per instance.
Applied to files:
express-zod-api/src/zts-helpers.tsexpress-zod-api/tests/zts.spec.tsexpress-zod-api/src/zts.tsexpress-zod-api/src/typescript-api.tsexpress-zod-api/src/integration.ts
📚 Learning: 2025-10-02T17:42:48.840Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-10-02T17:42:48.840Z
Learning: In express-zod-api v25 (ESM-only), the `.example()` method error occurs when user code runs as CommonJS. Express Zod API patches Zod's ESM bundle with `.example()`, but CommonJS code requires a separate CJS bundle instance that lacks this patch. Users must run their code as ESM by: (1) setting `"type": "module"` in package.json, (2) using `.mts` or `.mjs` file extensions, or (3) using tools like `tsx` or `vite-node` that provide their own ESM-compatible compilation.
Applied to files:
express-zod-api/src/zts-helpers.tsexpress-zod-api/tests/zts.spec.tsexpress-zod-api/src/zts.tsexpress-zod-api/src/typescript-api.tsexpress-zod-api/src/integration.ts
📚 Learning: 2025-05-27T20:22:30.428Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/tests/zts.spec.ts:160-162
Timestamp: 2025-05-27T20:22:30.428Z
Learning: In express-zod-api/tests/zts.spec.ts, the `Fruits` enum intentionally contains both string and numeric members (Apple = "apple", Banana = "banana", Cantaloupe = "cantaloupe", A = 5) and is used with `z.enum(Fruits)` to test how the system handles mixed enum types. This is by design for testing purposes.
Applied to files:
express-zod-api/src/zts-helpers.tsexpress-zod-api/tests/zts.spec.tsexpress-zod-api/src/zts.ts
📚 Learning: 2025-05-28T05:04:40.327Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/src/io-schema.ts:5-8
Timestamp: 2025-05-28T05:04:40.327Z
Learning: The Base type `object & { [Symbol.iterator]?: never }` in express-zod-api correctly excludes arrays and other iterables from IOSchema. Arrays are properly rejected because they have a defined Symbol.iterator method which conflicts with the never constraint, as demonstrated by TypeScript compiler errors when attempting to assign z.array() to IOSchema.
Applied to files:
express-zod-api/src/zts-helpers.tsexpress-zod-api/tests/zts.spec.tsexpress-zod-api/src/zts.ts
📚 Learning: 2025-05-27T20:40:19.548Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/src/json-schema-helpers.ts:75-87
Timestamp: 2025-05-27T20:40:19.548Z
Learning: In express-zod-api's `flattenIO` function in json-schema-helpers.ts, the `additionalProperties` field is used as a template to generate property schemas for literal property names extracted from `propertyNames.const` and `propertyNames.enum`. Converting boolean `additionalProperties` values to empty objects `{}` via `Object(entry.additionalProperties)` is intentional behavior, as the function only needs property schema templates, not the boolean semantics of `additionalProperties`.
Applied to files:
express-zod-api/src/zts-helpers.tsexpress-zod-api/src/zts.ts
📚 Learning: 2025-05-27T19:35:57.357Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/tests/buffer-schema.spec.ts:32-37
Timestamp: 2025-05-27T19:35:57.357Z
Learning: In the express-zod-api project, tests are run from the `express-zod-api` workspace directory, and the project uses an ESM-first environment without `__dirname`. Relative paths like `../logo.svg` in test files correctly resolve to the repository root due to this test execution context.
Applied to files:
express-zod-api/tests/zts.spec.ts
📚 Learning: 2025-06-02T21:08:56.475Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2697
File: CHANGELOG.md:5-5
Timestamp: 2025-06-02T21:08:56.475Z
Learning: The `cjs-test` directory in the express-zod-api repository is a test workspace and should be excluded when checking for main project version consistency with changelog entries.
Applied to files:
express-zod-api/tests/zts.spec.tsexpress-zod-api/src/zts.tsexpress-zod-api/src/integration.ts
📚 Learning: 2025-05-27T19:27:13.492Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/tests/form-schema.spec.ts:31-31
Timestamp: 2025-05-27T19:27:13.492Z
Learning: Zod version 3.25.0 and later expose the Zod v4 API through the special import paths "zod/v4" and "zod/v4/core", allowing v4 features like .loose() to be used even when the package.json dependency shows a 3.x version.
Applied to files:
express-zod-api/tests/zts.spec.tsexpress-zod-api/src/zts.ts
📚 Learning: 2025-05-27T20:03:34.213Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: example/factories.ts:35-42
Timestamp: 2025-05-27T20:03:34.213Z
Learning: The `./example` directory in the express-zod-api repository contains demonstration code for educational purposes only, not intended for production use. Example code can make simplified assumptions for brevity and clarity, and should not be flagged for missing production-level error handling, security measures, or edge case handling.
Applied to files:
express-zod-api/tests/zts.spec.ts
📚 Learning: 2025-09-25T14:04:50.830Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2965
File: express-zod-api/src/sse.ts:0-0
Timestamp: 2025-09-25T14:04:50.830Z
Learning: [monologue]
[task analyze_typescript_behavior_1/1]
The user RobinTail is correcting my understanding of TypeScript's type inference for the `return setTimeout(...) && { ... }` expression.
[observations]
- I previously stated that `return setTimeout(...) && { ... }` would have type `NodeJS.Timeout | Emitter<E>`
- RobinTail is claiming the type is actually `Emitter<E>` because the `&&` operator evaluates to its right side when the left side (setTimeout return value) is truthy
- The setTimeout function returns a NodeJS.Timeout which is indeed truthy
- In TypeScript, the `&&` operator has specific type inference rules
- I need to verify the actual TypeScript behavior for this pattern
[analysis]
Let me think through TypeScript's `&&` operator type inference:
1. `setTimeout()` returns `NodeJS.Timeout` which is truthy
2. In `A && B`, if A is always truthy, TypeScript should infer the type as B
3. However, there's also the possibility that TypeScript might still consider the union type `A | B`...
Applied to files:
express-zod-api/src/typescript-api.ts
📚 Learning: 2025-05-27T20:27:17.015Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/src/json-schema-helpers.ts:1-3
Timestamp: 2025-05-27T20:27:17.015Z
Learning: Ramda is correctly listed as a dependency in express-zod-api/package.json, so imports of ramda utilities are properly supported.
Applied to files:
express-zod-api/src/integration.ts
🧬 Code graph analysis (2)
express-zod-api/src/zts-helpers.ts (2)
express-zod-api/src/common-helpers.ts (1)
FlatObject(20-20)express-zod-api/src/typescript-api.ts (1)
TypescriptAPI(15-500)
express-zod-api/src/zts.ts (2)
express-zod-api/src/zts-helpers.ts (1)
Producer(12-12)express-zod-api/src/typescript-api.ts (1)
TypescriptAPI(15-500)
🪛 Biome (2.1.2)
express-zod-api/src/zts.ts
[error] 164-164: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
[error] 208-208: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
[error] 215-215: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
[error] 270-270: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
🔇 Additional comments (11)
README.md (1)
88-88: Contributor entry looks goodNew contributor avatar/link is correctly formatted and consistent with existing entries.
CHANGELOG.md (1)
5-11: Changelog entry accurately describes the TS memory optimizationThe v26.1.0 notes clearly explain the lazy TypeScript import behavior and its temporary nature; no changes needed.
express-zod-api/src/typescript-api.ts (1)
1-58: TypescriptAPI design and lazy TypeScript loading look solidUsing
import type tspluscreateRequire(import.meta.url)("typescript")in the constructor cleanly avoids a static TS import while keeping strongly-typed access to the compiler API. The shared modifiers, accessors, and helper methods consolidate previous free functions into a coherent instance API without obvious behavioral regressions.express-zod-api/tests/zts.spec.ts (1)
4-16: LGTM! Test setup correctly adapted to the new class-based API.The test file properly instantiates
TypescriptAPIand integrates it into theZTSContext. ThemakeAliasmock andprintNodeTesthelper are correctly updated to use instance methods (api.f.createTypeReferenceNode,api.printNode).express-zod-api/src/integration.ts (2)
1-3: Good: Type-only import correctly applied.The
import type ts from "typescript"is appropriate here sincetsis only used for type annotations (e.g.,ts.Node,ts.PrinterOptions,ts.TypeAliasDeclaration). This supports the PR's goal of lazy-loading TypeScript at runtime.
93-93: Clean API injection pattern.Passing
api: thisin thecommonsobject elegantly provides theTypescriptAPIinstance to theZTSContext, enabling all downstream producers to access the factory methods.express-zod-api/src/zts.ts (2)
12-12: Type-only import correctly applied for TypescriptAPI.Since
TypescriptAPIis only used as a type annotation (inintersectandgetFallbackfunction signatures), the type-only import is appropriate.
142-155: Good:intersectcorrectly propagates the API instance.The
R.tryCatchwrapper properly receivesapias the first argument in both the try and catch handlers, maintaining access to the TypeScript factory methods.express-zod-api/src/integration-base.ts (3)
13-13: Good architectural change: IntegrationBase extends TypescriptAPI.This inheritance pattern cleanly provides access to all TypeScript AST generation utilities through
this.*, enabling the lazy-loading optimization while maintaining a clean API surface.
91-93: Constructor correctly initializes the base class.The
super()call ensuresTypescriptAPIis properly initialized before the derived class uses its methods. TheserverUrlparameter is stored for later use in URL generation.
129-141: Correct use of method binding in callback context.The
this.literally.bind(this)at line 136 ensures proper context whenliterallyis used as a callback inR.map. This is necessary since Ramda doesn't preservethiscontext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
express-zod-api/src/zts.ts (1)
162-162: Address empty object pattern lint warnings.Biome flags
{}as an unexpected empty object pattern. When the first parameter (schema) is intentionally unused, consider using an underscore-prefixed name to satisfy the linter while signaling intent.🔎 Suggested fix
const onPrimitive = ( syntaxKind: | "AnyKeyword" | "BigIntKeyword" | "BooleanKeyword" | "NeverKeyword" | "NumberKeyword" | "StringKeyword" | "UndefinedKeyword" | "UnknownKeyword" | "VoidKeyword", ): Producer => - ({}, { api }) => + (_schema, { api }) => api.ensureTypeNode(api.ts.SyntaxKind[syntaxKind]);Apply similarly for
onNull,onBuffer, andonMissing:-const onNull: Producer = ({}, { api }) => api.makeLiteralType(null); +const onNull: Producer = (_schema, { api }) => api.makeLiteralType(null);-const onBuffer: Producer = ({}, { api }) => api.ensureTypeNode("Buffer"); +const onBuffer: Producer = (_schema, { api }) => api.ensureTypeNode("Buffer");- onMissing: ({}, { isResponse, api }) => getFallback(api, isResponse), + onMissing: (_schema, { isResponse, api }) => getFallback(api, isResponse),Also applies to: 218-218, 225-225, 280-280
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
express-zod-api/src/zts.ts
🧰 Additional context used
🧠 Learnings (15)
📓 Common learnings
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2428
File: express-zod-api/src/index.ts:44-44
Timestamp: 2025-05-28T18:58:10.064Z
Learning: The type-only import `import type {} from "qs";` in express-zod-api/src/index.ts is necessary to avoid TS2742 errors for exported functions like attachRouting, makeRequestMock, testEndpoint, and testMiddleware that have types depending on types/qs. This import provides the reference TypeScript needs to infer portable type names.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2736
File: express-zod-api/tsup.config.ts:12-26
Timestamp: 2025-06-14T16:42:52.972Z
Learning: In express-zod-api tsup configurations, the direct mutation of `options.supported` in the `esbuildOptions` callback is intentional behavior and should not be flagged as a side effect issue.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-09-29T03:35:55.561Z
Learning: In the express-zod-api repository, packages are built using tsdown before publishing. Source code with .ts extensions is not published - only the built JavaScript bundles and declaration files in the dist/ directory are published to npm. This means .ts extensions in source imports don't affect consumers.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2974
File: express-zod-api/src/documentation-helpers.ts:48-48
Timestamp: 2025-09-29T06:00:16.488Z
Learning: In the express-zod-api repository, the project uses pnpm workspaces with autoInstallPeers: false, meaning peerDependencies are not installed. The actual TypeScript version used for development comes from devDependencies which references "catalog:dev" in pnpm-workspace.yaml where it's set to a recent version (^5.9.2), not the peerDependency version which is just the minimum consumer requirement.
📚 Learning: 2025-08-01T09:48:13.742Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-08-01T09:48:13.742Z
Learning: In express-zod-api, when migrating from Zod v3 to v4, the correct approach for internal type imports is to change from `import type { $ZodType } from "zod/v4/core"` to `import { z } from "zod"` and then use `z.core.$ZodType`. The zod/v4/core module is reexported as z.core by the main zod package, making this a valid and working approach.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-09-29T03:35:55.561Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-09-29T03:35:55.561Z
Learning: In the express-zod-api repository, packages are built using tsdown before publishing. Source code with .ts extensions is not published - only the built JavaScript bundles and declaration files in the dist/ directory are published to npm. This means .ts extensions in source imports don't affect consumers.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-06-14T16:42:52.972Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2736
File: express-zod-api/tsup.config.ts:12-26
Timestamp: 2025-06-14T16:42:52.972Z
Learning: In express-zod-api tsup configurations, the direct mutation of `options.supported` in the `esbuildOptions` callback is intentional behavior and should not be flagged as a side effect issue.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-05-28T18:58:10.064Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2428
File: express-zod-api/src/index.ts:44-44
Timestamp: 2025-05-28T18:58:10.064Z
Learning: The type-only import `import type {} from "qs";` in express-zod-api/src/index.ts is necessary to avoid TS2742 errors for exported functions like attachRouting, makeRequestMock, testEndpoint, and testMiddleware that have types depending on types/qs. This import provides the reference TypeScript needs to infer portable type names.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-09-29T06:00:16.488Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2974
File: express-zod-api/src/documentation-helpers.ts:48-48
Timestamp: 2025-09-29T06:00:16.488Z
Learning: In the express-zod-api repository, the project uses pnpm workspaces with autoInstallPeers: false, meaning peerDependencies are not installed. The actual TypeScript version used for development comes from devDependencies which references "catalog:dev" in pnpm-workspace.yaml where it's set to a recent version (^5.9.2), not the peerDependency version which is just the minimum consumer requirement.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-08-08T11:59:04.814Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2878
File: zod-plugin/runtime.ts:39-42
Timestamp: 2025-08-08T11:59:04.814Z
Learning: express-zod-api/zod-plugin/runtime.ts (TypeScript): Do not memoize the ZodType.prototype.brand getter. The current design returning setBrand.bind(this) on each access is intentional/preferred; avoid redefining "brand" as a data property per instance.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-10-02T17:42:48.840Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-10-02T17:42:48.840Z
Learning: In express-zod-api v25 (ESM-only), the `.example()` method error occurs when user code runs as CommonJS. Express Zod API patches Zod's ESM bundle with `.example()`, but CommonJS code requires a separate CJS bundle instance that lacks this patch. Users must run their code as ESM by: (1) setting `"type": "module"` in package.json, (2) using `.mts` or `.mjs` file extensions, or (3) using tools like `tsx` or `vite-node` that provide their own ESM-compatible compilation.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-05-27T19:27:13.492Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/tests/form-schema.spec.ts:31-31
Timestamp: 2025-05-27T19:27:13.492Z
Learning: Zod version 3.25.0 and later expose the Zod v4 API through the special import paths "zod/v4" and "zod/v4/core", allowing v4 features like .loose() to be used even when the package.json dependency shows a 3.x version.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-12-18T13:42:33.228Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 3054
File: pnpm-workspace.yaml:68-68
Timestamp: 2025-12-18T13:42:33.228Z
Learning: In the express-zod-api repository, Vite is overridden to v8 beta in pnpm-workspace.yaml specifically to ensure vitest (which depends on Vite) uses the same rolldown bundler as tsdown and unrun. This override is intentional for toolchain consistency, as Vite is only used for testing purposes (not production).
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-06-02T21:11:20.768Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2697
File: CHANGELOG.md:5-5
Timestamp: 2025-06-02T21:11:20.768Z
Learning: In the express-zod-api repository, RobinTail follows a release workflow where package.json version is only updated on the master branch after merging all planned release changes. Changelog entries may show future version numbers while package.json remains at the previous version during feature development, and this is intentional workflow, not a version inconsistency that needs to be flagged.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-05-27T20:27:17.015Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/src/json-schema-helpers.ts:1-3
Timestamp: 2025-05-27T20:27:17.015Z
Learning: Ramda is correctly listed as a dependency in express-zod-api/package.json, so imports of ramda utilities are properly supported.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-05-27T20:27:17.015Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/src/json-schema-helpers.ts:1-3
Timestamp: 2025-05-27T20:27:17.015Z
Learning: The JSONSchema type is not exported from the main "zod" module and must be imported from "zod/v4/core" when using Zod v4. This is acceptable for type-only imports as they don't create runtime dependencies.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-05-28T05:04:40.327Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/src/io-schema.ts:5-8
Timestamp: 2025-05-28T05:04:40.327Z
Learning: The Base type `object & { [Symbol.iterator]?: never }` in express-zod-api correctly excludes arrays and other iterables from IOSchema. Arrays are properly rejected because they have a defined Symbol.iterator method which conflicts with the never constraint, as demonstrated by TypeScript compiler errors when attempting to assign z.array() to IOSchema.
Applied to files:
express-zod-api/src/zts.ts
📚 Learning: 2025-05-27T20:40:19.548Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/src/json-schema-helpers.ts:75-87
Timestamp: 2025-05-27T20:40:19.548Z
Learning: In express-zod-api's `flattenIO` function in json-schema-helpers.ts, the `additionalProperties` field is used as a template to generate property schemas for literal property names extracted from `propertyNames.const` and `propertyNames.enum`. Converting boolean `additionalProperties` values to empty objects `{}` via `Object(entry.additionalProperties)` is intentional behavior, as the function only needs property schema templates, not the boolean semantics of `additionalProperties`.
Applied to files:
express-zod-api/src/zts.ts
🪛 Biome (2.1.2)
express-zod-api/src/zts.ts
[error] 162-162: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
[error] 218-218: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
[error] 225-225: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
[error] 280-280: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
🔇 Additional comments (5)
express-zod-api/src/zts.ts (5)
2-2: Type-only imports correctly defer TypeScript loading.The
import type ts from "typescript"ensures TypeScript is not imported at runtime in this module. The actual TypeScript module is now accessed through the injectedapiparameter fromTypescriptAPI, achieving the PR's memory optimization goal.Also applies to: 12-12
27-147: Consistent migration to API-driven TypeScript AST construction.All producers correctly use the injected
apiparameter for TypeScript operations (api.f.*,api.ts.*,api.makeLiteralType, etc.) instead of direct module access. Theintersectfunction signature appropriately takesapias its first parameter, andonEnum's.bind(api)correctly preserves context.
184-216: Pipeline transformation logic correctly uses API for type resolution.The sample/resolution mappings now reference
api.ts.SyntaxKindconsistently, andgetFallback(api, isResponse)properly receives the API instance for fallback type generation.
235-245: String-based syntax kind identifiers enable deferred resolution.Using string literals like
"StringKeyword"instead of directts.SyntaxKind.StringKeywordallows the resolution to happen at runtime viaapi.ts.SyntaxKind[syntaxKind], supporting the deferred TypeScript loading pattern.
268-282: Public API signature preserved.The
zodToTsexport maintains its existing signature while internally leveraging theapifromctxfor TypeScript operations. This ensures backward compatibility for consumers.
|
❌ this run croaked The workflow encountered an error before any progress could be reported. Please check the workflow for details. |
…ntegration into the ZTSContext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
express-zod-api/src/typescript-api.ts (1)
15-53: Class structure looks good with appropriate visibility modifiers.The constructor properly initializes all dependencies from the lazily-loaded TypeScript module. The TODO comment on line 25 appropriately documents the technical debt.
One minor observation:
#safePropRegexis constant and could be astaticclass field to avoid creating a new regex per instance, though the impact is negligible for typical usage patterns.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
express-zod-api/src/typescript-api.tsexpress-zod-api/src/zts-helpers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- express-zod-api/src/zts-helpers.ts
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2428
File: express-zod-api/src/index.ts:44-44
Timestamp: 2025-05-28T18:58:10.064Z
Learning: The type-only import `import type {} from "qs";` in express-zod-api/src/index.ts is necessary to avoid TS2742 errors for exported functions like attachRouting, makeRequestMock, testEndpoint, and testMiddleware that have types depending on types/qs. This import provides the reference TypeScript needs to infer portable type names.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2736
File: express-zod-api/tsup.config.ts:12-26
Timestamp: 2025-06-14T16:42:52.972Z
Learning: In express-zod-api tsup configurations, the direct mutation of `options.supported` in the `esbuildOptions` callback is intentional behavior and should not be flagged as a side effect issue.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-09-29T03:35:55.561Z
Learning: In the express-zod-api repository, packages are built using tsdown before publishing. Source code with .ts extensions is not published - only the built JavaScript bundles and declaration files in the dist/ directory are published to npm. This means .ts extensions in source imports don't affect consumers.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2878
File: zod-plugin/runtime.ts:39-42
Timestamp: 2025-08-08T11:59:04.814Z
Learning: express-zod-api/zod-plugin/runtime.ts (TypeScript): Do not memoize the ZodType.prototype.brand getter. The current design returning setBrand.bind(this) on each access is intentional/preferred; avoid redefining "brand" as a data property per instance.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2974
File: express-zod-api/src/documentation-helpers.ts:48-48
Timestamp: 2025-09-29T06:00:16.488Z
Learning: In the express-zod-api repository, the project uses pnpm workspaces with autoInstallPeers: false, meaning peerDependencies are not installed. The actual TypeScript version used for development comes from devDependencies which references "catalog:dev" in pnpm-workspace.yaml where it's set to a recent version (^5.9.2), not the peerDependency version which is just the minimum consumer requirement.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-08-01T09:48:13.742Z
Learning: In express-zod-api, when migrating from Zod v3 to v4, the correct approach for internal type imports is to change from `import type { $ZodType } from "zod/v4/core"` to `import { z } from "zod"` and then use `z.core.$ZodType`. The zod/v4/core module is reexported as z.core by the main zod package, making this a valid and working approach.
📚 Learning: 2025-05-28T18:58:10.064Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2428
File: express-zod-api/src/index.ts:44-44
Timestamp: 2025-05-28T18:58:10.064Z
Learning: The type-only import `import type {} from "qs";` in express-zod-api/src/index.ts is necessary to avoid TS2742 errors for exported functions like attachRouting, makeRequestMock, testEndpoint, and testMiddleware that have types depending on types/qs. This import provides the reference TypeScript needs to infer portable type names.
Applied to files:
express-zod-api/src/typescript-api.ts
📚 Learning: 2025-08-01T09:48:13.742Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-08-01T09:48:13.742Z
Learning: In express-zod-api, when migrating from Zod v3 to v4, the correct approach for internal type imports is to change from `import type { $ZodType } from "zod/v4/core"` to `import { z } from "zod"` and then use `z.core.$ZodType`. The zod/v4/core module is reexported as z.core by the main zod package, making this a valid and working approach.
Applied to files:
express-zod-api/src/typescript-api.ts
📚 Learning: 2025-09-29T03:35:55.561Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-09-29T03:35:55.561Z
Learning: In the express-zod-api repository, packages are built using tsdown before publishing. Source code with .ts extensions is not published - only the built JavaScript bundles and declaration files in the dist/ directory are published to npm. This means .ts extensions in source imports don't affect consumers.
Applied to files:
express-zod-api/src/typescript-api.ts
📚 Learning: 2025-06-14T16:42:52.972Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2736
File: express-zod-api/tsup.config.ts:12-26
Timestamp: 2025-06-14T16:42:52.972Z
Learning: In express-zod-api tsup configurations, the direct mutation of `options.supported` in the `esbuildOptions` callback is intentional behavior and should not be flagged as a side effect issue.
Applied to files:
express-zod-api/src/typescript-api.ts
📚 Learning: 2025-08-08T11:59:04.814Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2878
File: zod-plugin/runtime.ts:39-42
Timestamp: 2025-08-08T11:59:04.814Z
Learning: express-zod-api/zod-plugin/runtime.ts (TypeScript): Do not memoize the ZodType.prototype.brand getter. The current design returning setBrand.bind(this) on each access is intentional/preferred; avoid redefining "brand" as a data property per instance.
Applied to files:
express-zod-api/src/typescript-api.ts
📚 Learning: 2025-10-02T17:42:48.840Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-10-02T17:42:48.840Z
Learning: In express-zod-api v25 (ESM-only), the `.example()` method error occurs when user code runs as CommonJS. Express Zod API patches Zod's ESM bundle with `.example()`, but CommonJS code requires a separate CJS bundle instance that lacks this patch. Users must run their code as ESM by: (1) setting `"type": "module"` in package.json, (2) using `.mts` or `.mjs` file extensions, or (3) using tools like `tsx` or `vite-node` that provide their own ESM-compatible compilation.
Applied to files:
express-zod-api/src/typescript-api.ts
📚 Learning: 2025-09-29T06:00:16.488Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2974
File: express-zod-api/src/documentation-helpers.ts:48-48
Timestamp: 2025-09-29T06:00:16.488Z
Learning: In the express-zod-api repository, the project uses pnpm workspaces with autoInstallPeers: false, meaning peerDependencies are not installed. The actual TypeScript version used for development comes from devDependencies which references "catalog:dev" in pnpm-workspace.yaml where it's set to a recent version (^5.9.2), not the peerDependency version which is just the minimum consumer requirement.
Applied to files:
express-zod-api/src/typescript-api.ts
📚 Learning: 2025-09-25T14:04:50.830Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2965
File: express-zod-api/src/sse.ts:0-0
Timestamp: 2025-09-25T14:04:50.830Z
Learning: [monologue]
[task analyze_typescript_behavior_1/1]
The user RobinTail is correcting my understanding of TypeScript's type inference for the `return setTimeout(...) && { ... }` expression.
[observations]
- I previously stated that `return setTimeout(...) && { ... }` would have type `NodeJS.Timeout | Emitter<E>`
- RobinTail is claiming the type is actually `Emitter<E>` because the `&&` operator evaluates to its right side when the left side (setTimeout return value) is truthy
- The setTimeout function returns a NodeJS.Timeout which is indeed truthy
- In TypeScript, the `&&` operator has specific type inference rules
- I need to verify the actual TypeScript behavior for this pattern
[analysis]
Let me think through TypeScript's `&&` operator type inference:
1. `setTimeout()` returns `NodeJS.Timeout` which is truthy
2. In `A && B`, if A is always truthy, TypeScript should infer the type as B
3. However, there's also the possibility that TypeScript might still consider the union type `A | B`...
Applied to files:
express-zod-api/src/typescript-api.ts
🧬 Code graph analysis (1)
express-zod-api/src/typescript-api.ts (1)
express-zod-api/src/integration.ts (1)
printerOptions(179-189)
🔇 Additional comments (5)
express-zod-api/src/typescript-api.ts (5)
1-13: Solid approach for deferred TypeScript loading.Using
createRequirewith a type-only import effectively defers the TypeScript package loading until the class is instantiated, achieving the memory optimization goal without introducing breaking changes.
55-94: Clean implementation of AST helper methods.The template expression handling in
makeTemplatecorrectly differentiates between middle and tail spans based on index position.
147-182: Well-designed type node utilities.The
ensureTypeNodemethod provides a clean abstraction for converting various inputs tots.TypeNode. ThemakeUniondeduplication usingMapkeyed bykindfor primitives is a nice touch to avoid duplicate entries likestring | string.
319-326: Minor note onPromise.nameusage.Line 326 uses
Promise.namewhich evaluates to the string"Promise". This is reliable in standard JavaScript environments, but worth noting it depends on the globalPromiseconstructor'snameproperty. This is fine for your use case since TypeScript targets environments where this is guaranteed.
447-464: Excellent type inference inliterallymethod.The conditional return type
T extends string ? ts.StringLiteral : T extends number ? ts.NumericLiteral : ...provides precise type narrowing, which will benefit consumers of this API. The eslint-disable comment appropriately documents the intentional formatting choice.The standalone
propOfutility withNoInfer<T>is a clean pattern for type-safe key extraction.
RobinTail
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🏁 ready
The better implementation of #3139 using the dynamic `import()` instead of `createRequire()`. The new implementation requires either: - to import and provide the `typescript` to `Integration::constructor()` - or to use the new async method `Integration::create()` This is a breaking change. `typescript` becomes an optional peer dependency. Documentation adjusted accordingly. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Updated README installation section to clarify TypeScript handling. * Enhanced frontend client generation examples with explicit TypeScript configuration instructions and usage patterns. * **New Features** * Added async factory method for integration setup, offering alternative to direct instantiation. * Made TypeScript an optional peer dependency instead of required, reducing installation footprint. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->

Trying to import
typescriptdynamically in order to avoid excessive memory consumption.Addresses #3126
Using
createRequirefor sync approach — This is kinda a workaround, because the proper implementation would need:import()andasync, which is breakingSummary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.