Skip to content

Conversation

@RobinTail
Copy link
Owner

@RobinTail RobinTail commented Dec 27, 2025

image

Nikki Kuhnhausen was 17 years young transgender woman. She was murdered by David Bogdanov in 2019. Her body was identified from decayed remains on December 7th, in the Larch Mountain area near Vancouver, Washington. Authorities were able to identify the body from personal effects; subsequent examination showed she died from strangulation. Nikki had previously been missing since June 5th when she left with David Bogdanov, a 25-year-old male she met in Snapchat, who has since been charged with second degree murder. Authorities believe that Bogdanov, looking for a sexual encounter, became enraged upon learning of Nikki's transgender origins.

https://katu.com/news/local/missing-vancouver-teen-nikki-kuhnhausen-found-dead-in-remote-clark-county-location-suspect-arrested

Transgender women suffer too frequently from transphobic violence and cruelty, being the less protected social group. I'd like to raise an awareness of this problem. Humans should be creators — not killers. But most importantly, I want every transgender girl to have an opportunity to create applications quickly and, in general, learn to write code easily in order to receive job offers and leave dangerously transphobic territories for more favorable and civilized ones, and live happily there. Protect transgender women.


Version 27 makes typescript an optional peer dependency with explicit injection of the manually imported module into the Integration class constructor (new Integration({ typescript, ... })), or by calling the new async Integration.create() factory method for automatic loading. The Zod Plugin now leverages Zod 4.3's inheritable metadata feature, removing the need for pack() and unpack() helpers while simplifying brand handling by storing brands in standard metadata under x-brand property.

Summary by CodeRabbit

  • New Features

    • Async Integration.create() factory and optional TypeScript injection for integration setup
    • Zod metadata is now inheritable (requires Zod ^4.3.4)
  • Breaking Changes

    • Integration accepts a TypeScript compiler reference (constructor param or via create())
    • Branding model changed: brand key renamed to "x-brand"; pack/unpack removed in favor of schema.meta()
  • Documentation

    • Examples updated to show TypeScript injection and async create() usage
  • Chores

    • Version bumps to v27 beta and CI/workflow targets updated

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 27, 2025

📝 Walkthrough

Walkthrough

Version 27 refactors TypeScript handling by injecting the TypeScript runtime into Integration/TypescriptAPI, adds an async Integration.create() factory, shifts internal code to id-based helpers, makes Zod metadata inheritable (removing pack/unpack), and updates workflows, migrations, docs, and tests.

Changes

Cohort / File(s) Summary
Workflows
.github/workflows/codeql-analysis.yml, .github/workflows/node.js.yml
Updated push/pull_request branch filters: removed v22, added v26 and make-v27.
Packages & workspace
express-zod-api/package.json, migration/package.json, zod-plugin/package.json, pnpm-workspace.yaml
Bumped package versions for v27 betas; made express-fileupload and typescript optional peers; upgraded zod peer to ^4.3.4.
Integration: public surface
express-zod-api/src/integration.ts, example/generate-client.ts
Added typescript to Integration params and constructor; added static async create() factory; examples updated to import/pass typescript.
IntegrationBase refactor
express-zod-api/src/integration-base.ts
Large refactor: constructor now takes (typescript, serverUrl); internal identifiers moved from ts.Identifier to string id map; many helpers renamed to make*; code now uses id-centric helpers.
TypescriptAPI changes
express-zod-api/src/typescript-api.ts, express-zod-api/tests/zts.spec.ts
Constructor now accepts injected typescript; added makeId and changed many make* helpers to accept/convert string init ids.
Zod plugin branding overhaul
zod-plugin/src/brand.ts, zod-plugin/src/meta.ts, zod-plugin/src/runtime.ts
Removed pack/unpack and setBrand; brand stored under "x-brand" via globalRegistry; added brandSetter; runtime binds brandSetter to ZodType.brand.
Removed packer module & exports
zod-plugin/src/packer.ts, zod-plugin/src/index.ts
Deleted packer.ts and removed re-exports of pack/unpack.
Migration rules simplified
migration/index.ts, migration/index.spec.ts, compat-test/migration.spec.ts, compat-test/package.json
Consolidated queries to single integration object expression; rule now injects typescript or replaces new Integration(...) with await Integration.create(...) in async contexts; tests and samples updated.
Linting / compat configs
eslint.config.js, compat-test/eslint.config.js
Added lint rule for createIdentifier; removed performanceConcerns from migration no-restricted-syntax; updated migration target to v27.
Docs & changelogs
CHANGELOG.md, README.md, zod-plugin/CHANGELOG.md, zod-plugin/README.md, SECURITY.md, migration/README.md
Added v27/v4 changelog entries and README examples documenting TypeScript injection, create() factory, Zod 4.3 inheritable metadata; security supported versions updated.
Tests & test setup
express-zod-api/tests/integration.spec.ts, express-zod-api/tests/env.spec.ts, zod-plugin/tests/*, express-zod-api/vitest.setup.ts
Tests updated to use await Integration.create(...) or pass typescript; added metadata inheritance test; removed pack/unpack tests and migrated brand assertions to meta()/globalRegistry; vitest setup simplified.
Misc small changes
express-zod-api/src/zts.ts, express-zod-api/src/startup-logo.ts, example/*
Minor edits: enum mapping uses R.map(api.makeLiteralType, ...); startup dedication changed to "Nikki"; examples updated for TypeScript option.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Code
    participant Integration as Integration
    participant Base as IntegrationBase
    participant TSAPI as TypescriptAPI
    participant TS as TypeScript Runtime

    rect rgb(240,248,255)
    Note over Client,Integration: New v27 async create flow
    Client->>Integration: await Integration.create({ config, routing, serverUrl? })
    activate Integration
    Integration->>TS: load peer TypeScript (auto-load)
    TS-->>Integration: ts module
    Integration->>Base: super(ts, serverUrl)
    activate Base
    Base->>TSAPI: new TypescriptAPI(ts)
    activate TSAPI
    TSAPI->>TS: createIdentifier / build nodes
    TSAPI-->>Base: id helpers ready
    deactivate TSAPI
    Base-->>Integration: initialized
    deactivate Base
    Integration-->>Client: Integration instance
    deactivate Integration
    end

    rect rgb(255,240,245)
    Note over Client,Integration: Old v26 direct constructor (sync)
    Client->>Integration: new Integration({ config, routing })
    Note over Integration: Synchronous path, no create() factory
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

refactoring

Poem

🐰
I hopped through code with nimble feet,
V27 makes metadata neat.
No pack or unpack left to find,
create() and ids — all well-defined.
A carrot for TypeScript, tidy and sweet.

Pre-merge checks

✅ 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 clearly reflects a key aspect of the PR—version 27's dedication to Nikki Kuhnhausen—and appears as a meaningful part of the release, but does not summarize the main technical changes such as making TypeScript optional or updating the Zod plugin.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac0e3ea and 5c9c0c5.

📒 Files selected for processing (1)
  • CHANGELOG.md
🧰 Additional context used
🧠 Learnings (8)
📓 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-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: 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-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-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:

  • CHANGELOG.md
📚 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:

  • CHANGELOG.md
📚 Learning: 2025-08-05T14:43:24.702Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2869
File: zod-plugin/package.json:42-44
Timestamp: 2025-08-05T14:43:24.702Z
Learning: Zod version 4 has been officially released and is stable. As of August 2025, the latest published version is 4.0.14 (published July 30, 2025). Zod v4 is available on npm and can be used in peer dependencies with version constraints like "^4.0.0".

Applied to files:

  • CHANGELOG.md
📚 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:

  • CHANGELOG.md
📚 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:

  • CHANGELOG.md
📚 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:

  • CHANGELOG.md
📚 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:

  • CHANGELOG.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: report
  • GitHub Check: build (22.12.0)
🔇 Additional comments (1)
CHANGELOG.md (1)

3-35: Clear and comprehensive v27 documentation with practical examples.

The changelog entry effectively communicates the TypeScript refactor by presenting both usage patterns with concrete code samples. The examples clearly show the tradeoff between synchronous constructor assignment and asynchronous factory loading, which directly addresses the memory consumption optimization mentioned in context.

However, verify that both code examples are accurately representative of the actual API. Specifically:

  • Line 22: Ensure the typescript parameter name matches the actual Integration constructor signature.
  • Line 31: Confirm Integration.create() is the correct method signature and return type (verify it's truly async).

Additionally, consider adding a brief note about the pack/unpack removal from Zod Plugin (mentioned in AI summary) if it's a user-facing change, as this could impact existing code using those helpers. Based on learnings, the Zod migration approach changed from import type { $ZodType } from "zod/v4/core" to import { z } from "zod" with z.core.$ZodType, which may be relevant if documented elsewhere.


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 27, 2025

Coverage Status

coverage: 100.0%. remained the same
when pulling 60e71ae on make-v27
into 5b127dc on master.

@RobinTail RobinTail added the documentation Improvements or additions to documentation label Dec 27, 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 -->
@RobinTail RobinTail added enhancement New feature or request dependencies Pull requests that update a dependency file breaking Backward incompatible changes refactoring The better way to achieve the same result and removed refactoring The better way to achieve the same result labels Dec 28, 2025
@RobinTail RobinTail added this to the v27 milestone Dec 28, 2025
RobinTail and others added 8 commits December 29, 2025 17:53
Thanks to #3156 storing the runtime distinguishable brand in the `bag`
is no longer required, since metadata became inheritable between zod
schemas in `globalRegistry` starting from Zod v4.3.0.

See https://github.com/colinhacks/zod/pull/5578/changes for
implementation.

Breaking changes to the Zod Plugin:
- min Zod version 4.3.4
- `pack()` and `unpack()` removed (no longer needed)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Metadata now inherits across chained schema operations and
constraints.

* **Breaking Changes**
  * Removed pack/unpack utilities; use schema.meta() for metadata.
* Brand metadata moved to a namespaced meta key ("x-brand"); retrieve
via schema.meta() or the getBrand() helper.

* **Chores**
  * Bumped Zod requirement to ^4.3.4 and plugin version to 4.0.0-beta.1.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@RobinTail RobinTail added the coverage Additional tests label Jan 2, 2026
@RobinTail RobinTail changed the title Next: v27 v27 is for Nikki Kuhnhausen Jan 2, 2026
@RobinTail RobinTail marked this pull request as ready for review January 2, 2026 11:03
@RobinTail
Copy link
Owner Author

@pullfrog please review the PR and also make me a summary of the changes describing the next major release in a paragraph no longer than 4 sentences.

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 (2)
express-zod-api/package.json (1)

60-84: Consider updating the minimum TypeScript version constraint.

Since TypeScript became optional in peerDependenciesMeta, the minimum peer dependency constraint ^5.1.3 is less critical for consumers. However, it may be worth reviewing whether the minimum should be bumped to ^5.9.2 to match the development baseline—TypeScript has released 8 major versions since 5.1.3 (now at 5.9.2 as of January 2026). If the Integration API doesn't impose version constraints, the current minimum is acceptable; otherwise, the constraint should align with what has been tested.

migration/index.ts (1)

13-20: Migration rule is on the right track; consider refining async detection, generics, and import handling

The overall approach—finding new Integration({ ... }) calls, preferring Integration.create() in async contexts, and otherwise injecting a typescript property—is sound. A few details are worth tightening:

  1. Async context detection is incomplete

    hasAsyncCtx only considers AwaitExpression, ArrowFunctionExpression, and FunctionExpression. A new Integration({ ... }) inside an async function foo() { ... } (a FunctionDeclaration) will be treated as non‑async and migrated to the constructor+typescript branch, even though await Integration.create() would be valid there.

    A small adjustment keeps behavior consistent for async declarations:

    Possible tweak to include async function declarations
          const hasAsyncCtx = ctx.sourceCode
            .getAncestors(node)
            .some(
              (one) =>
                one.type === NT.AwaitExpression ||
  •          ((one.type === NT.ArrowFunctionExpression ||
    
  •            one.type === NT.FunctionExpression) &&
    
  •          ((one.type === NT.ArrowFunctionExpression ||
    
  •            one.type === NT.FunctionExpression ||
    
  •            one.type === NT.FunctionDeclaration) &&
               one.async),
         );
    
    </details>
    
    
  1. Explicit generics on new Integration<...>() are dropped

    The async fix replaces the entire NewExpression with (await Integration.create(<object>)). If callers wrote new Integration<MyRouting>({ ... }), those type arguments are lost. If they relied on explicit generics, this silently changes the type story.

    It would be safer to preserve typeArguments when present (e.g., by reconstructing the call using ctx.sourceCode.getText on them, or by leveraging the AST rather than raw text).

  2. Unconditional typescript import can conflict with existing imports

    The non‑async branch always inserts import typescript from "typescript"; at the very top of the file. If the file already imports from "typescript" (default, namespace, or named), this can introduce duplicate bindings or conflicting imports.

    Before inserting, consider scanning ctx.sourceCode.ast.body for existing ImportDeclarations whose source.value === "typescript", and either:

    • Reuse the existing local identifier (e.g., ts) in the added property, or
    • Skip adding a new import when a suitable one already exists.
  3. Formatting of inserted typescript property

    Prepending typescript, directly before node.properties[0] works but may clash with local formatting (indentation / trailing commas) in multi‑line objects. Not a blocker, but you might later switch to using the printer or at least respecting the existing whitespace around the first property.

These aren’t blockers for the migration but would make the rule more robust against real‑world usage.

Also applies to: 62-109

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44c76f3 and c800565.

⛔ Files ignored due to path filters (1)
  • migration/__snapshots__/index.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (37)
  • .github/workflows/codeql-analysis.yml
  • .github/workflows/node.js.yml
  • CHANGELOG.md
  • README.md
  • SECURITY.md
  • compat-test/eslint.config.js
  • compat-test/migration.spec.ts
  • compat-test/package.json
  • eslint.config.js
  • example/generate-client.ts
  • express-zod-api/package.json
  • express-zod-api/src/integration-base.ts
  • express-zod-api/src/integration.ts
  • express-zod-api/src/startup-logo.ts
  • express-zod-api/src/typescript-api.ts
  • express-zod-api/src/zts.ts
  • express-zod-api/tests/env.spec.ts
  • express-zod-api/tests/integration.spec.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/vitest.setup.ts
  • migration/README.md
  • migration/index.spec.ts
  • migration/index.ts
  • migration/package.json
  • pnpm-workspace.yaml
  • zod-plugin/CHANGELOG.md
  • zod-plugin/README.md
  • zod-plugin/package.json
  • zod-plugin/src/brand.ts
  • zod-plugin/src/index.ts
  • zod-plugin/src/meta.ts
  • zod-plugin/src/packer.ts
  • zod-plugin/src/runtime.ts
  • zod-plugin/tests/brand.spec.ts
  • zod-plugin/tests/index.spec.ts
  • zod-plugin/tests/packer.spec.ts
  • zod-plugin/tests/runtime.spec.ts
💤 Files with no reviewable changes (4)
  • zod-plugin/src/index.ts
  • zod-plugin/src/packer.ts
  • zod-plugin/tests/packer.spec.ts
  • zod-plugin/tests/index.spec.ts
🧰 Additional context used
🧠 Learnings (30)
📓 Common learnings
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.
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-27T19:30:51.885Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: compat-test/sample.ts:1-1
Timestamp: 2025-05-27T19:30:51.885Z
Learning: Files in compat-test/ directories, especially those named sample.ts or similar, are often test fixtures for migration scripts and may intentionally contain deprecated or "incorrect" code that the migration tooling is designed to fix. These should not be flagged as issues.

Applied to files:

  • compat-test/eslint.config.js
  • eslint.config.js
  • migration/README.md
  • compat-test/package.json
  • compat-test/migration.spec.ts
  • migration/index.spec.ts
📚 Learning: 2025-09-29T06:06:58.605Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2974
File: migration/tsconfig.json:2-2
Timestamp: 2025-09-29T06:06:58.605Z
Learning: tsconfig/node20 includes "moduleResolution": "node16" in its base configuration, along with other settings like "module": "nodenext", "target": "es2022", and "strict": true. When extending from tsconfig/node20, explicit moduleResolution configuration is not needed.

Applied to files:

  • compat-test/eslint.config.js
  • migration/README.md
📚 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:

  • compat-test/eslint.config.js
  • express-zod-api/vitest.setup.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/tests/env.spec.ts
  • express-zod-api/src/zts.ts
  • migration/index.ts
  • express-zod-api/src/integration-base.ts
  • express-zod-api/package.json
  • zod-plugin/src/runtime.ts
  • express-zod-api/src/integration.ts
  • express-zod-api/src/typescript-api.ts
  • express-zod-api/tests/integration.spec.ts
📚 Learning: 2025-08-05T14:43:24.702Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2869
File: zod-plugin/package.json:42-44
Timestamp: 2025-08-05T14:43:24.702Z
Learning: Zod version 4 has been officially released and is stable. As of August 2025, the latest published version is 4.0.14 (published July 30, 2025). Zod v4 is available on npm and can be used in peer dependencies with version constraints like "^4.0.0".

Applied to files:

  • zod-plugin/package.json
  • pnpm-workspace.yaml
  • CHANGELOG.md
  • zod-plugin/README.md
  • zod-plugin/CHANGELOG.md
  • express-zod-api/package.json
📚 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:

  • zod-plugin/package.json
  • pnpm-workspace.yaml
  • CHANGELOG.md
  • zod-plugin/tests/runtime.spec.ts
  • zod-plugin/src/brand.ts
  • zod-plugin/README.md
  • zod-plugin/CHANGELOG.md
  • express-zod-api/package.json
  • express-zod-api/tests/integration.spec.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:

  • zod-plugin/package.json
  • migration/package.json
  • pnpm-workspace.yaml
  • CHANGELOG.md
  • express-zod-api/package.json
📚 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:

  • zod-plugin/package.json
  • zod-plugin/src/meta.ts
  • migration/package.json
  • README.md
  • pnpm-workspace.yaml
  • express-zod-api/vitest.setup.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/tests/env.spec.ts
  • CHANGELOG.md
  • zod-plugin/tests/runtime.spec.ts
  • zod-plugin/src/brand.ts
  • zod-plugin/README.md
  • express-zod-api/src/zts.ts
  • zod-plugin/CHANGELOG.md
  • express-zod-api/src/integration-base.ts
  • zod-plugin/src/runtime.ts
  • express-zod-api/src/integration.ts
  • express-zod-api/src/typescript-api.ts
  • express-zod-api/tests/integration.spec.ts
📚 Learning: 2025-05-27T20:08:50.699Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/src/metadata.ts:5-13
Timestamp: 2025-05-27T20:08:50.699Z
Learning: The `_zod` property in Zod v4 schemas is officially documented and recommended for library authors to differentiate between Zod 3 and Zod 4 schemas at runtime, despite the underscore prefix. This is explicitly mentioned in Zod's library authors documentation and is not a private internal property.

Applied to files:

  • zod-plugin/package.json
  • zod-plugin/src/meta.ts
  • zod-plugin/src/brand.ts
  • zod-plugin/README.md
  • zod-plugin/CHANGELOG.md
📚 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:

  • zod-plugin/package.json
  • migration/package.json
  • README.md
  • example/generate-client.ts
  • express-zod-api/package.json
📚 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:

  • zod-plugin/package.json
  • migration/package.json
  • README.md
  • pnpm-workspace.yaml
  • express-zod-api/tests/zts.spec.ts
  • example/generate-client.ts
  • CHANGELOG.md
  • express-zod-api/package.json
  • zod-plugin/src/runtime.ts
  • express-zod-api/src/integration.ts
  • express-zod-api/src/typescript-api.ts
  • express-zod-api/tests/integration.spec.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:

  • zod-plugin/src/meta.ts
  • express-zod-api/vitest.setup.ts
  • express-zod-api/tests/zts.spec.ts
  • express-zod-api/tests/env.spec.ts
  • zod-plugin/tests/brand.spec.ts
  • zod-plugin/tests/runtime.spec.ts
  • zod-plugin/src/brand.ts
  • zod-plugin/README.md
  • express-zod-api/src/zts.ts
  • zod-plugin/src/runtime.ts
  • express-zod-api/src/typescript-api.ts
  • express-zod-api/tests/integration.spec.ts
📚 Learning: 2025-08-08T16:45:20.527Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-08-08T16:45:20.527Z
Learning: express-zod-api/zod-plugin: The brand setter must be immutable and always invoke pack→.check() via the getter-bound setBrand. Including brandProperty with an explicit undefined enables unbranding and should not be optimized away.

Applied to files:

  • zod-plugin/src/meta.ts
  • express-zod-api/vitest.setup.ts
  • zod-plugin/tests/brand.spec.ts
  • zod-plugin/tests/runtime.spec.ts
  • zod-plugin/src/brand.ts
  • zod-plugin/README.md
  • zod-plugin/src/runtime.ts
  • express-zod-api/tests/integration.spec.ts
📚 Learning: 2025-08-08T16:45:20.527Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-08-08T16:45:20.527Z
Learning: express-zod-api zod-plugin: The brand setter must stay immutable and always invoke pack→.check() (onattach). Including brandProperty with an explicit undefined enables “unbranding” (overwriting a previous brand) and should not be optimized away.

Applied to files:

  • zod-plugin/src/meta.ts
  • express-zod-api/vitest.setup.ts
  • zod-plugin/tests/brand.spec.ts
  • zod-plugin/tests/runtime.spec.ts
  • zod-plugin/src/brand.ts
  • zod-plugin/README.md
  • zod-plugin/src/runtime.ts
📚 Learning: 2025-08-08T16:45:20.527Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-08-08T16:45:20.527Z
Learning: express-zod-api/zod-plugin: The ZodType.prototype.brand getter returns setBrand.bind(this) each time (no memoization). setBrand must always call pack() and include { brand: undefined } when unbranding so that pack → .check() runs and prior brand is overwritten; do not suggest removing the explicit undefined entry.

Applied to files:

  • zod-plugin/src/meta.ts
  • express-zod-api/vitest.setup.ts
  • zod-plugin/tests/brand.spec.ts
  • zod-plugin/tests/runtime.spec.ts
  • zod-plugin/src/brand.ts
  • zod-plugin/README.md
  • express-zod-api/src/zts.ts
  • zod-plugin/src/runtime.ts
  • express-zod-api/tests/integration.spec.ts
📚 Learning: 2025-08-08T16:45:20.527Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-08-08T16:45:20.527Z
Learning: In express-zod-api zod-plugin, the brand setter must be immutable and always call `.check()` (packer’s onattach). Therefore, writing an explicit undefined entry in the bag for brand is intentional and should not be suggested for removal.

Applied to files:

  • zod-plugin/src/meta.ts
  • express-zod-api/vitest.setup.ts
  • zod-plugin/tests/brand.spec.ts
  • zod-plugin/tests/runtime.spec.ts
  • zod-plugin/src/brand.ts
  • zod-plugin/README.md
  • zod-plugin/src/runtime.ts
  • express-zod-api/tests/integration.spec.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:

  • zod-plugin/src/meta.ts
  • README.md
  • express-zod-api/vitest.setup.ts
  • express-zod-api/tests/zts.spec.ts
  • example/generate-client.ts
  • CHANGELOG.md
  • zod-plugin/tests/runtime.spec.ts
  • compat-test/package.json
  • express-zod-api/src/zts.ts
  • express-zod-api/package.json
  • zod-plugin/src/runtime.ts
  • express-zod-api/src/typescript-api.ts
  • express-zod-api/tests/integration.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:

  • migration/package.json
  • express-zod-api/vitest.setup.ts
  • express-zod-api/tests/env.spec.ts
  • compat-test/package.json
  • 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:

  • README.md
  • pnpm-workspace.yaml
  • express-zod-api/vitest.setup.ts
  • express-zod-api/tests/zts.spec.ts
  • example/generate-client.ts
  • CHANGELOG.md
  • zod-plugin/tests/runtime.spec.ts
  • compat-test/package.json
  • express-zod-api/src/zts.ts
  • express-zod-api/src/integration-base.ts
  • express-zod-api/package.json
  • zod-plugin/src/runtime.ts
  • express-zod-api/src/integration.ts
  • express-zod-api/src/typescript-api.ts
  • express-zod-api/tests/integration.spec.ts
📚 Learning: 2025-12-18T13:42:39.225Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 3054
File: pnpm-workspace.yaml:68-68
Timestamp: 2025-12-18T13:42:39.225Z
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:

  • README.md
  • pnpm-workspace.yaml
  • express-zod-api/tests/zts.spec.ts
  • compat-test/package.json
  • express-zod-api/package.json
📚 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:

  • README.md
  • pnpm-workspace.yaml
  • express-zod-api/src/zts.ts
  • express-zod-api/src/typescript-api.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:

  • README.md
  • express-zod-api/vitest.setup.ts
  • express-zod-api/tests/zts.spec.ts
  • example/generate-client.ts
  • zod-plugin/tests/runtime.spec.ts
  • compat-test/package.json
  • compat-test/migration.spec.ts
  • express-zod-api/src/zts.ts
  • migration/index.ts
  • express-zod-api/src/integration-base.ts
  • express-zod-api/package.json
  • zod-plugin/src/runtime.ts
  • express-zod-api/src/integration.ts
  • express-zod-api/src/typescript-api.ts
  • express-zod-api/tests/integration.spec.ts
📚 Learning: 2025-05-28T19:19:27.727Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-05-28T19:19:27.727Z
Learning: In pnpm workspaces, most configuration can now be done directly in pnpm-workspace.yaml using the `settings` section instead of requiring .npmrc files. This includes workspace linking, dependency resolution, and performance settings.

Applied to files:

  • pnpm-workspace.yaml
📚 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:

  • pnpm-workspace.yaml
  • express-zod-api/vitest.setup.ts
  • zod-plugin/src/brand.ts
  • zod-plugin/CHANGELOG.md
  • express-zod-api/tests/integration.spec.ts
📚 Learning: 2025-05-28T07:58:09.853Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/src/documentation-helpers.ts:508-512
Timestamp: 2025-05-28T07:58:09.853Z
Learning: In express-zod-api, when working with Zod's JSON schema override callbacks, using `delete` to mutate `ctx.jsonSchema` is the recommended approach per Zod's official documentation, even if it triggers performance linting warnings. This is preferable to creating copies with `undefined` values, especially for snapshot testing.

Applied to files:

  • express-zod-api/vitest.setup.ts
  • zod-plugin/README.md
  • express-zod-api/tests/integration.spec.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/vitest.setup.ts
  • express-zod-api/src/zts.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/tests/zts.spec.ts
  • express-zod-api/src/zts.ts
  • express-zod-api/tests/integration.spec.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
  • express-zod-api/tests/env.spec.ts
  • zod-plugin/tests/brand.spec.ts
  • compat-test/package.json
  • express-zod-api/tests/integration.spec.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
📚 Learning: 2025-09-08T09:24:05.821Z
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2922
File: example/endpoints/list-users.ts:4-4
Timestamp: 2025-09-08T09:24:05.821Z
Learning: In Zod v4, z.literal() accepts arrays and supports multiple values in a single schema (e.g., z.literal(["manager", "operator", "admin"]) is valid), unlike previous versions where it only accepted single literal values. The .value property was replaced with .values which is a Set.

Applied to files:

  • zod-plugin/CHANGELOG.md
🧬 Code graph analysis (8)
zod-plugin/src/meta.ts (1)
zod-plugin/src/brand.ts (1)
  • brandProperty (4-4)
express-zod-api/tests/zts.spec.ts (1)
express-zod-api/src/typescript-api.ts (1)
  • TypescriptAPI (14-474)
example/generate-client.ts (2)
express-zod-api/src/integration.ts (1)
  • Integration (66-246)
express-zod-api/src/index.ts (1)
  • Integration (28-28)
express-zod-api/tests/env.spec.ts (1)
express-zod-api/src/documentation.ts (1)
  • subject (137-149)
zod-plugin/tests/brand.spec.ts (1)
zod-plugin/src/brand.ts (2)
  • brandProperty (4-4)
  • getBrand (10-19)
zod-plugin/src/brand.ts (1)
zod-plugin/src/index.ts (1)
  • getBrand (2-2)
zod-plugin/src/runtime.ts (1)
zod-plugin/src/meta.ts (1)
  • brandSetter (21-23)
express-zod-api/src/integration.ts (1)
express-zod-api/src/peer-helpers.ts (1)
  • loadPeer (3-11)
🔇 Additional comments (72)
express-zod-api/src/startup-logo.ts (1)

15-15: Dedication message updated appropriately.

The string replacement from "for Lia" to "for Nikki" is clean and maintains proper padding alignment with .padEnd(20). This is a meaningful and fitting dedication to honor Nikki Kuhnhausen.

zod-plugin/package.json (1)

3-3: LGTM! Version bump aligns with breaking changes.

The major version bump to 4.0.0-beta.1 is appropriate given the breaking changes described in the PR (removal of pack/unpack utilities, brand handling moved to inheritable metadata). The beta designation correctly signals pre-release status.

compat-test/eslint.config.js (1)

6-6: LGTM! Migration rule updated for v27.

The ESLint configuration correctly enforces the new v27 migration rule, aligning with the broader release preparation.

migration/README.md (1)

21-21: LGTM! Documentation updated for v27 migration.

The README example correctly reflects the new migration rule version, ensuring users apply the v27 migration script.

SECURITY.md (1)

7-7: LGTM! Version 27 added to supported versions table.

The addition of version 27.x.x with code name "Nikki" and planned release date of February 2026 aligns with the PR objectives and honors the dedication to Nikki Kuhnhausen.

pnpm-workspace.yaml (1)

53-53: Verify the Zod version constraint — 4.3.4 did not introduce the metadata registry feature.

The metadata registry system (including globalRegistry) was introduced in Zod v4.0.0, not in 4.3.4. The 4.3.4 changelog includes updates for patternProperties, looseRecord, and other improvements, but no metadata-related changes. If the brand handling feature only requires Zod's metadata registry, the constraint can be lowered to "^4.0.0". If 4.3.4 is needed for a different reason, clarify the requirement in the commit message.

Likely an incorrect or invalid review comment.

.github/workflows/node.js.yml (1)

8-10: LGTM! Branch triggers updated for v27 release cycle.

The removal of v22 and addition of v26 and make-v27 branches correctly aligns with the version progression documented in the PR.

.github/workflows/codeql-analysis.yml (1)

16-19: LGTM! CodeQL workflow aligned with main CI.

The branch filter updates are consistent with .github/workflows/node.js.yml, ensuring security scanning coverage matches the main build workflow.

migration/package.json (1)

3-3: LGTM! Version bump appropriate for v27 beta release.

The migration package version correctly advances to 27.0.0-beta.0, aligning with the overall v27 release cycle documented in the PR.

zod-plugin/CHANGELOG.md (1)

5-10: Update CHANGELOG version header to match package.json

The CHANGELOG documents v4.0.0 as a stable release, but package.json declares the actual current version as 4.0.0-beta.1. The changelog entry header should be v4.0.0-beta.1 to maintain consistency with the package version during the beta development phase.

⛔ Skipped due to learnings
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.
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.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2869
File: zod-plugin/package.json:42-44
Timestamp: 2025-08-05T14:43:24.702Z
Learning: Zod version 4 has been officially released and is stable. As of August 2025, the latest published version is 4.0.14 (published July 30, 2025). Zod v4 is available on npm and can be used in peer dependencies with version constraints like "^4.0.0".
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2546
File: express-zod-api/src/metadata.ts:5-13
Timestamp: 2025-05-27T20:08:50.699Z
Learning: The `_zod` property in Zod v4 schemas is officially documented and recommended for library authors to differentiate between Zod 3 and Zod 4 schemas at runtime, despite the underscore prefix. This is explicitly mentioned in Zod's library authors documentation and is not a private internal property.
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.
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.
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-08-08T16:45:20.527Z
Learning: express-zod-api/zod-plugin: The ZodType.prototype.brand getter returns setBrand.bind(this) each time (no memoization). setBrand must always call pack() and include { brand: undefined } when unbranding so that pack → .check() runs and prior brand is overwritten; do not suggest removing the explicit undefined entry.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 2922
File: example/endpoints/list-users.ts:4-4
Timestamp: 2025-09-08T09:24:05.821Z
Learning: In Zod v4, z.literal() accepts arrays and supports multiple values in a single schema (e.g., z.literal(["manager", "operator", "admin"]) is valid), unlike previous versions where it only accepted single literal values. The .value property was replaced with .values which is a Set.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-08-08T16:45:20.527Z
Learning: express-zod-api zod-plugin: The brand setter must stay immutable and always invoke pack→.check() (onattach). Including brandProperty with an explicit undefined enables “unbranding” (overwriting a previous brand) and should not be optimized away.
Learnt from: RobinTail
Repo: RobinTail/express-zod-api PR: 0
File: :0-0
Timestamp: 2025-08-08T16:45:20.527Z
Learning: express-zod-api/zod-plugin: The brand setter must be immutable and always invoke pack→.check() via the getter-bound setBrand. Including brandProperty with an explicit undefined enables unbranding and should not be optimized away.
zod-plugin/src/meta.ts (1)

2-2: LGTM! brandSetter correctly implements Zod v4 inheritable metadata pattern.

The implementation properly uses .meta() to store brand metadata with the brandProperty key, replacing the previous pack()/unpack() approach. The optional brand parameter enables both branding and unbranding scenarios, and the return value supports method chaining. Zod ^4.3.4 fully supports inheritable metadata via .meta() as documented.

compat-test/package.json (1)

6-6: LGTM - Intentional migration test fixture.

The updated pretest script generates a sample that uses the new Integration constructor pattern. Based on learnings, files in compat-test/ directories are often test fixtures for migration scripts and may intentionally contain deprecated or incomplete code that migration tooling is designed to detect and fix.

Based on learnings, compat-test files are intentionally used as migration test fixtures.

express-zod-api/src/zts.ts (1)

103-104: LGTM - Consistent use of Ramda utilities.

The refactor from Object.values(def.entries).map(api.makeLiteralType.bind(api)) to R.map(api.makeLiteralType, Object.values(def.entries)) maintains the same functionality while aligning with the codebase's Ramda-first style.

express-zod-api/vitest.setup.ts (1)

22-28: LGTM - Simplified schema serialization aligned with metadata-based branding.

The removal of the custom override handler for injecting x-brand into JSON schemas aligns with v27's shift to inheritable metadata for brand handling. The simplified toJSONSchema call with only unrepresentable: "any" is cleaner and consistent with the new approach.

express-zod-api/package.json (1)

3-3: LGTM - Version bump for v27 major release.

The version bump to 27.0.0-beta.1 is appropriate for the breaking changes in this PR, including the Integration API changes and optional TypeScript peer dependency.

Based on learnings, the version workflow in this repository intentionally allows package.json to be updated during feature development.

eslint.config.js (2)

179-182: LGTM - Enforcing consistent use of makeId() helper.

The new lint rule discourages direct use of createIdentifier in favor of the makeId() helper, aligning with the PR's shift toward id-based helpers and maintaining consistency with other TypeScript factory helper rules.


241-246: LGTM - Appropriate relaxation of performance constraints for migration tooling.

Removing performanceConcerns from the migration config makes sense, as migration scripts don't require the same runtime performance optimizations as production code. Import concerns are appropriately retained for consistency.

CHANGELOG.md (1)

3-34: v27 changelog entry accurately describes the Integration breaking change

The bullets and diff snippets correctly capture the new requirement to either pass a typescript value into the Integration constructor or switch to await Integration.create({ ... }), and the Zod ^4.3.4 note aligns with the plugin’s new minimum version. No changes needed here.

zod-plugin/README.md (1)

16-18: Branding README updates are consistent with the new plugin behavior

Documenting .brand() as shorthand for setting "x-brand" in meta, showing it in schema.meta(), and describing getBrand() as the runtime accessor all line up with the new inheritable-metadata-based branding, and the Zod ^4.3.4 requirement matches the implementation constraints.

Also applies to: 21-21, 32-32, 37-37

express-zod-api/tests/zts.spec.ts (1)

9-16: TypescriptAPI instantiation is correctly updated

Creating api with new TypescriptAPI(ts) matches the new constructor signature and keeps the test harness in sync with the implementation of TypescriptAPI and its printNode helper.

compat-test/migration.spec.ts (1)

7-9: Compat test expectation correctly reflects the new migration output

The expected content now includes the typescript import and the typescript property in the Integration constructor argument, which matches the behavior of the updated migration rule for non‑async call sites.

README.md (2)

184-184: Documentation correctly reflects TypeScript as optional peer dependency.

The removal of typescript from the installation example aligns with the breaking change where TypeScript is now an optional peer dependency that must be explicitly provided to Integration.


1078-1093: Clear documentation of the new TypeScript integration pattern.

The updated example correctly demonstrates:

  • Installing TypeScript as a prerequisite
  • Importing and passing the TypeScript module to Integration
  • Mentioning the alternative Integration.create() factory method for async loading

This provides users with clear guidance on the breaking API change.

express-zod-api/tests/env.spec.ts (1)

132-136: Appropriate test coverage for Zod 4.3.0 metadata inheritance.

This test correctly verifies that metadata defined on a parent schema is preserved when creating a child schema through chaining. The assertion expect(subject.meta()).toHaveProperty("one", "test") confirms the inheritance behavior that is central to the v27 branding refactor.

zod-plugin/src/runtime.ts (1)

3-8: Branding refactored to use inheritable metadata via brandSetter.

The change from setBrand to brandSetter aligns with the PR's objective to leverage Zod 4.3+ inheritable metadata. The brandSetter function (from meta.ts) uses this.meta({ [brandProperty]: brand }) to attach brands via metadata, which is now preserved across schema transformations.

Based on learnings, the getter returning a new bound function on each access is intentional design.

Also applies to: 30-30

zod-plugin/tests/brand.spec.ts (1)

1-19: Tests correctly updated for metadata-based branding.

The test updates reflect the architectural shift:

  • brandProperty changed to "x-brand" (line 7)
  • getBrand now reads from globalRegistry metadata instead of internal bag
  • Test directly manipulates globalRegistry to verify the new metadata-based approach

These changes align with the Zod 4.3+ inheritable metadata feature and the removal of the pack/unpack utilities.

example/generate-client.ts (1)

5-10: Example correctly demonstrates the new TypeScript integration pattern.

The addition of import typescript from "typescript" and passing it to the Integration constructor illustrates the breaking API change where TypeScript is now an optional peer dependency that must be explicitly provided.

Based on learnings, the ./example directory contains demonstration code for educational purposes.

zod-plugin/tests/runtime.spec.ts (3)

71-74: LGTM! Brand verification via metadata.

The test correctly validates that branding is now stored in and accessed through .meta() with the "x-brand" key, aligning with Zod v4's metadata-driven approach.


76-83: Good test coverage for brand persistence through refinements.

The test correctly verifies that brand metadata is preserved when applying refinements like .regex(), which is essential for the metadata-based branding approach.


85-91: Good validation of metadata property coexistence.

This test appropriately verifies that brand metadata coexists with other metadata properties like description, confirming the metadata approach handles multiple properties correctly.

express-zod-api/tests/integration.spec.ts (4)

2-2: LGTM! Correct import for Zod v4 metadata access.

The globalRegistry import is the appropriate public API for accessing and manipulating schema metadata in Zod v4.


29-30: LGTM! Demonstrates explicit TypeScript injection.

The test correctly shows the synchronous constructor pattern where TypeScript is explicitly provided, one of the two supported initialization patterns in v27.


53-53: LGTM! Correct usage of async factory method.

The tests appropriately demonstrate the Integration.create() pattern, which asynchronously loads the TypeScript peer dependency. This is the recommended approach when TypeScript is not explicitly provided.

Also applies to: 76-76, 113-113, 139-139


136-136: No action needed. globalRegistry.remove(schema) is the correct Zod v4 API for removing schema metadata in custom brand handling scenarios. The implementation aligns with the established patterns used throughout the codebase.

migration/index.spec.ts (3)

25-25: LGTM! Valid migration target established.

The valid test case correctly demonstrates the expected form after migration, with typescript provided in the Integration constructor arguments.


28-40: Good migration test for adding typescript parameter.

The test correctly validates that the migration automatically adds the TypeScript import and injects it into the Integration constructor when missing. This covers the most common upgrade scenario.


56-69: LGTM! Correct handling of async context migration.

The test correctly validates conversion to Integration.create() in async contexts. The double await pattern is necessary: one for the factory method, one for printFormatted(). The parentheses ensure proper precedence.

express-zod-api/src/integration.ts (5)

17-18: LGTM! TypeScript dependency injection properly typed.

The addition of the typescript parameter to IntegrationParams correctly establishes TypeScript as an injectable dependency with proper typing.


82-94: LGTM! TypeScript dependency injection implemented correctly.

The constructor properly extracts the typescript parameter and passes it to the base class via super(), enabling dependency injection of the TypeScript API throughout the inheritance chain.


181-186: LGTM! Async factory pattern correctly implemented.

The static create() method properly implements the async factory pattern for loading the optional TypeScript peer dependency via dynamic import. This enables TypeScript to be optional while providing a convenient initialization path.


159-161: Factory method pattern applied consistently.

The use of this.makeMethodType() and this.makeRequestType() is consistent with the factory method refactoring seen elsewhere in the file (line 67). This architectural pattern appears to be applied uniformly.


67-67: Factory methods are properly implemented.

The factory methods makeSomeOfType(), makeMethodType(), and makeRequestType() are all correctly defined in the IntegrationBase class (lines 79–102). The change from property reference to method call is intentional and follows a consistent factory pattern for creating TypeScript AST type nodes.

zod-plugin/src/brand.ts (3)

1-1: LGTM! Correct import for Zod v4 metadata API.

The addition of globalRegistry to the imports is appropriate for accessing schema metadata in Zod v4, replacing the previous internal bag-based approach.


3-4: Good use of JSON Schema extension convention.

The rename from "brand" to "x-brand" follows the JSON Schema convention for custom extension properties (x- prefix). This is a breaking change but aligns with Zod v4's metadata approach and standard conventions.


10-11: LGTM! Correct migration to globalRegistry API.

The implementation correctly uses globalRegistry.get(subject) to retrieve metadata, properly handling undefined results with nullish coalescing and maintaining the expected type checking for valid brand values.

express-zod-api/src/typescript-api.ts (12)

23-24: LGTM! Clean dependency injection pattern.

The constructor now accepts the TypeScript compiler API as a parameter, enabling better testability and supporting async loading. This is a well-structured breaking change that aligns with the PR's goal of making typescript an optional peer dependency.


74-74: Good addition of centralized identifier factory.

The makeId helper provides a single point for identifier creation, reducing boilerplate across the codebase.


78-78: LGTM! Consistent use of makeId for string-to-identifier conversion.

The updated makePropertyIdentifier and makeTemplate methods properly leverage makeId for string-based identifier creation.

Also applies to: 83-89


97-120: LGTM! Parameter signature updated to string-based initialization.

The change from init?: ts.Expression to initId?: string simplifies the API for common use cases where initialization is a simple identifier reference.


224-229: LGTM! Simplified signature from ts.Identifier[] to string[].

The makeDeconstruction method now accepts plain strings, delegating identifier creation to internal logic. This aligns with the broader refactor pattern.


251-257: LGTM! Updated to use method reference directly.

The makePublicLiteralType now passes this.makeLiteralType directly to R.map, which works correctly since makeLiteralType is defined as an arrow function.


289-290: LGTM! Method name parameter simplified to string.

This change is consistent with the overall refactor direction and simplifies the caller's interface.


385-402: LGTM! Clean variadic signature with string-to-identifier coercion.

The makeTernary method now accepts either ts.Expression or string for all three arguments, with automatic conversion via makeId. The implementation is clean and type-safe.


404-420: LGTM! Consistent string-to-identifier conversion in makeCall.

The method now handles string arguments throughout, converting them to identifiers via makeId where needed.


422-423: LGTM! makeNew and makeAssignment updated for string inputs.

Both methods now accept string parameters and convert them appropriately, maintaining consistency with the refactored API.

Also applies to: 428-438


157-157: Arrow function binding in ensureTypeNode is correct and safe.

The ensureTypeNode method is defined as an arrow function (public ensureTypeNode = (...) => ...), which automatically binds this to the instance. Passing this.ensureTypeNode to R.map at line 157 is therefore safe and maintains proper context without requiring additional binding workarounds. This pattern is used consistently throughout the codebase.


377-379: No action needed—makeParam is already correctly defined as an arrow function.

The code is correct as-is. makeParam is defined as an arrow function at line 97 (public makeParam = (...) =>), which automatically binds this to the lexical scope. When passed to R.map at line 378, it maintains proper binding. This follows the same pattern as ensureTypeNode mentioned in the original concern.

Likely an incorrect or invalid review comment.

express-zod-api/src/integration-base.ts (15)

14-31: LGTM! Clean base class constructor with dependency injection.

The protected constructor properly initializes the TypescriptAPI with the injected TypeScript compiler. The readonly modifier on api ensures immutability after construction.


33-64: LGTM! Simplified identifier storage as plain strings.

Converting #ids from ts.Identifier objects to plain strings simplifies the internal API. Identifiers are now created on-demand via makeId where needed, reducing upfront object allocation.


67-73: LGTM! Interface names simplified to strings.

The interfaces record now stores plain string names instead of ts.Identifier objects, consistent with the #ids refactor.


79-102: LGTM! Helper methods renamed with make prefix for consistency.

The renamed methods (makeMethodType, makeSomeOfType, makeRequestType) follow a consistent naming convention and properly use string-based identifiers.


108-109: LGTM! someOf updated to use string-based type reference.

The method now correctly passes the string identifier to ensureTypeNode.


145-145: LGTM! Tag mapping uses method reference directly.

R.map(this.api.literally, tags) works correctly since literally is an arrow function on TypescriptAPI.


157-173: LGTM! Implementation type construction uses string-based parameter names.

The method properly constructs the function type using string keys for parameters.


179-198: LGTM! makeParseRequestFn updated with string-based identifiers.

The method correctly uses string identifiers and makeId for type references.


204-269: LGTM! makeSubstituteFn properly uses makeId for runtime expressions.

The method correctly distinguishes between:

  • String identifiers used as property keys (passed directly)
  • Runtime identifier references (wrapped with makeId)

This is a comprehensive and correct update.


272-317: LGTM! Provider method updated with string-based parameters.

The #makeProvider method correctly uses string identifiers throughout, including for type parameters.


323-338: LGTM! Client class construction and search params helper updated.

Both makeClientClass and #makeSearchParams correctly use the new string-based identifier pattern.

Also applies to: 341-344


362-501: LGTM! makeDefaultImplementation comprehensively updated.

All identifier references within the default implementation have been correctly updated to use makeId where needed for runtime expressions.


503-537: LGTM! Subscription class methods updated consistently.

Both #makeSubscriptionConstructor and #makeOnMethod properly use string identifiers and makeId for runtime expressions.

Also applies to: 544-601


607-637: LGTM! Subscription class type parameters use string-based identifiers.

The makeSubscriptionClass method correctly uses string identifiers for type constraints.


640-667: LGTM! Usage statements generation updated.

The makeUsageStatements method correctly uses the updated APIs throughout.

Copy link

@pullfrog pullfrog bot left a comment

Choose a reason for hiding this comment

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

Looks good overall. This is a well-structured major version release with thoughtful breaking changes. The TypeScript injection pattern is sensible—making typescript an optional peer dependency and offering both synchronous (constructor with injection) and asynchronous (Integration.create()) initialization paths gives users flexibility.

One minor observation: the migration ESLint rule handles the async context detection well, but it might produce awkward output in rare edge cases (e.g., deeply nested async contexts). The auto-fix wrapping (await Integration.create(...)) should work correctly for the common case though.


v27 Release Summary

Version 27 makes TypeScript an optional peer dependency with explicit injection into the Integration class—use new Integration({ typescript, ... }) with an import, or call the new async Integration.create() factory method for automatic loading. The Zod Plugin now leverages Zod 4.3.4's inheritable metadata feature, removing the need for pack/unpack utilities while simplifying brand handling by storing brands in standard metadata under x-brand.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow runpullfrog.com𝕏

@pullfrog
Copy link

pullfrog bot commented Jan 2, 2026

Review complete for PR #3148

Summary: No blocking issues found. The changes are well-structured with thoughtful breaking changes for TypeScript injection and Zod metadata improvements.


v27 Release Summary

Version 27 makes TypeScript an optional peer dependency with explicit injection into the Integration class—use new Integration({ typescript, ... }) with an import, or call the new async Integration.create() factory method for automatic loading. The Zod Plugin now leverages Zod 4.3.4's inheritable metadata feature, removing the need for pack/unpack utilities while simplifying brand handling by storing brands in standard metadata under x-brand.

Pullfrog  | Triggered by Pullfrog | Using Claude CodeView workflow runpullfrog.com𝕏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Backward incompatible changes CI/CD coverage Additional tests dedication dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants