Skip to content

Conversation

@RobinTail
Copy link
Owner

@RobinTail RobinTail commented Dec 16, 2025

Trying to import typescript dynamically in order to avoid excessive memory consumption.
Addresses #3126

Using createRequire for sync approach — This is kinda a workaround, because the proper implementation would need:

  • import() and async, which is breaking
  • or moving to another entrypoint or another package, which is breaking as well

Summary by CodeRabbit

  • New Features

    • Unified internal TypeScript generation engine while keeping public interfaces compatible.
  • Chores

    • Deferred TypeScript import to reduce memory usage during normal operations.
    • Bumped package version to 26.1.0-beta.2.
    • Updated changelog and added a contributor entry.

✏️ Tip: You can customize this high-level summary in your review settings.

@RobinTail RobinTail added refactoring The better way to achieve the same result prevention labels Dec 16, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 16, 2025

📝 Walkthrough

Walkthrough

Introduces 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

Cohort / File(s) Summary
Changelog & Docs
CHANGELOG.md, README.md
Added v26.1.0 entry describing deferred TypeScript import and memory usage fix; added contributor NicolasMahe.
New Typescript API
express-zod-api/src/typescript-api.ts
Added export class TypescriptAPI that encapsulates ts, f and many previously top-level helper functions as instance methods (make*, printNode, addJsDoc, ensureTypeNode, etc.). propOf remains exported.
Integration Base & Integration
express-zod-api/src/integration-base.ts, express-zod-api/src/integration.ts
Internal refactor: replaced module-level helper calls with instance-bound this.api.*, threaded api through shared contexts and generation paths; public signatures preserved.
Zod-to-TS Producers
express-zod-api/src/zts.ts
Producers and helpers now accept/use injected api: TypescriptAPI; replaced direct ts/factory usage with api.ts/api.f and api.make* methods.
Helpers, Types & Tests
express-zod-api/src/zts-helpers.ts, express-zod-api/tests/zts.spec.ts
ZTSContext extended with api: TypescriptAPI; tests updated to instantiate new TypescriptAPI() and use api.f/api.printNode.
Packaging
express-zod-api/package.json
Bumped package version to 26.1.0-beta.2.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

"I hopped through nodes with whiskers bright,
Gathered helpers, tucked TS out of sight.
When code awakes I give a soft tap,
Memory lighter in my little lap.
— 🐇"

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Memory usage optimization' directly aligns with the PR's primary objective of reducing memory consumption by deferring TypeScript package loading.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mem-3126-require-ts

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81118c8 and 4e0c8d3.

📒 Files selected for processing (1)
  • express-zod-api/src/typescript-api.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: 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: 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.
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-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
🔇 Additional comments (6)
express-zod-api/src/typescript-api.ts (6)

1-3: Good pattern for deferred module loading with type safety.

The type-only import of typescript provides compile-time type checking without eager loading, while createRequire enables the synchronous dynamic require in the constructor. This correctly achieves the memory optimization goal.


24-53: LGTM - Dynamic require achieves memory optimization goal.

The constructor correctly defers TypeScript loading until the class is instantiated. The @todo comment appropriately documents the trade-off: createRequire is a workaround to maintain synchronous API, with the intent to migrate to import() in the next major version.

Note: If typescript is not installed, this will throw at instantiation time. This is expected behavior since TypeScript is a peer dependency for integration features.


22-22: Good refactor: static regex for identifier validation.

Making #safePropRegex static is the right choice since the pattern is constant and doesn't depend on instance state. This avoids creating duplicate regex objects when multiple TypescriptAPI instances are created.


147-158: Correct use of .bind(this) for Ramda callback.

The this.ensureTypeNode.bind(this) is necessary here since R.map doesn't preserve context, and ensureTypeNode references instance members (this.f, this.ts). The recursive type resolution logic is sound.


447-455: Justified eslint override for type-dispatch pattern.

The chained ternary expression with conditional return types is appropriate here. The exhaustive type mapping is correct, and the eslint-disable prettier/prettier is reasonable given the structured nature of this type-dispatch logic.


460-464: LGTM - Type assertion is safe; propOf correctly remains standalone.

The type assertion (this.#primitives as ts.SyntaxKind[]) is safe since KeywordTypeSyntaxKind is a subset of SyntaxKind. The propOf utility correctly remains a standalone export since it's a pure type-level helper that doesn't require the TypeScript AST.


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 and usage tips.

@coveralls-official
Copy link

coveralls-official bot commented Dec 16, 2025

Coverage Status

coverage: 100.0%. remained the same
when pulling 029e5cb on mem-3126-require-ts
into 8dc8b43 on master.

@RobinTail RobinTail marked this pull request as ready for review December 17, 2025 08:03
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: 0

🧹 Nitpick comments (3)
express-zod-api/src/typescript-api.ts (1)

482-500: Tighten literally() return type for bigint literals

The literally() implementation correctly creates BigIntLiteral nodes at runtime, but the conditional return type doesn’t reflect that branch and falls through to ts.NullLiteral for T 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: New api: TypescriptAPI on ZTSContext is consistent with the refactor

Extending ZTSContext with an api field and importing TypescriptAPI as a type cleanly wires the new AST helper into ZTS producers. Just ensure all ZTSContext construction sites now initialize api to 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

📥 Commits

Reviewing files that changed from the base of the PR and between f32c2ae and b9b925f.

📒 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.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/src/integration-base.ts
  • express-zod-api/src/zts.ts
  • express-zod-api/src/typescript-api.ts
  • express-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.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/src/integration-base.ts
  • express-zod-api/src/zts.ts
  • express-zod-api/src/typescript-api.ts
  • express-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.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/src/integration-base.ts
  • express-zod-api/src/zts.ts
  • express-zod-api/src/typescript-api.ts
  • express-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.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/src/zts.ts
  • express-zod-api/src/typescript-api.ts
  • express-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.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/src/zts.ts
  • express-zod-api/src/typescript-api.ts
  • express-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.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/src/zts.ts
  • express-zod-api/src/typescript-api.ts
  • express-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.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/src/zts.ts
  • express-zod-api/src/typescript-api.ts
  • express-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.ts
  • express-zod-api/tests/zts.spec.ts
  • 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-helpers.ts
  • express-zod-api/tests/zts.spec.ts
  • 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-helpers.ts
  • express-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.ts
  • express-zod-api/src/zts.ts
  • express-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.ts
  • express-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 good

New contributor avatar/link is correctly formatted and consistent with existing entries.

CHANGELOG.md (1)

5-11: Changelog entry accurately describes the TS memory optimization

The 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 solid

Using import type ts plus createRequire(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 TypescriptAPI and integrates it into the ZTSContext. The makeAlias mock and printNodeTest helper 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 since ts is 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: this in the commons object elegantly provides the TypescriptAPI instance to the ZTSContext, 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 TypescriptAPI is only used as a type annotation (in intersect and getFallback function signatures), the type-only import is appropriate.


142-155: Good: intersect correctly propagates the API instance.

The R.tryCatch wrapper properly receives api as 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 ensures TypescriptAPI is properly initialized before the derived class uses its methods. The serverUrl parameter 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 when literally is used as a callback in R.map. This is necessary since Ramda doesn't preserve this context.

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: 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, and onMissing:

-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

📥 Commits

Reviewing files that changed from the base of the PR and between 25c92ad and a371e1b.

📒 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 injected api parameter from TypescriptAPI, 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 api parameter for TypeScript operations (api.f.*, api.ts.*, api.makeLiteralType, etc.) instead of direct module access. The intersect function signature appropriately takes api as its first parameter, and onEnum'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.SyntaxKind consistently, and getFallback(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 direct ts.SyntaxKind.StringKeyword allows the resolution to happen at runtime via api.ts.SyntaxKind[syntaxKind], supporting the deferred TypeScript loading pattern.


268-282: Public API signature preserved.

The zodToTs export maintains its existing signature while internally leveraging the api from ctx for TypeScript operations. This ensures backward compatibility for consumers.

@pullfrog
Copy link
Contributor

pullfrog bot commented Dec 25, 2025

❌ this run croaked

The workflow encountered an error before any progress could be reported. Please check the workflow for details.

Pullfrog  | Triggered by Pullfrog | Using Gemini CLIView workflow runpullfrog.com𝕏

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: 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: #safePropRegex is constant and could be a static class 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

📥 Commits

Reviewing files that changed from the base of the PR and between f94dd53 and 81118c8.

📒 Files selected for processing (2)
  • express-zod-api/src/typescript-api.ts
  • express-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 createRequire with 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 makeTemplate correctly differentiates between middle and tail spans based on index position.


147-182: Well-designed type node utilities.

The ensureTypeNode method provides a clean abstraction for converting various inputs to ts.TypeNode. The makeUnion deduplication using Map keyed by kind for primitives is a nice touch to avoid duplicate entries like string | string.


319-326: Minor note on Promise.name usage.

Line 326 uses Promise.name which evaluates to the string "Promise". This is reliable in standard JavaScript environments, but worth noting it depends on the global Promise constructor's name property. This is fine for your use case since TypeScript targets environments where this is guaranteed.


447-464: Excellent type inference in literally method.

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 propOf utility with NoInfer<T> is a clean pattern for type-safe key extraction.

Copy link
Owner Author

@RobinTail RobinTail left a comment

Choose a reason for hiding this comment

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

🏁 ready

@RobinTail RobinTail merged commit 047e1ef into master Dec 26, 2025
13 checks passed
@RobinTail RobinTail deleted the mem-3126-require-ts branch December 26, 2025 09:03
RobinTail added a commit that referenced this pull request Dec 28, 2025
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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prevention refactoring The better way to achieve the same result

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants