Skip to content

feat(zod): add generateReusableSchemas opt-in flag - #3449

Merged
melloware merged 19 commits into
orval-labs:masterfrom
z4o4z:feat/reusable-zod-schemas
May 26, 2026
Merged

feat(zod): add generateReusableSchemas opt-in flag#3449
melloware merged 19 commits into
orval-labs:masterfrom
z4o4z:feat/reusable-zod-schemas

Conversation

@z4o4z

@z4o4z z4o4z commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix #3068

Adds an opt-in override.zod.generateReusableSchemas flag. When enabled, the Zod generator emits one reusable schema per #/components/schemas/* $ref (with namingConvention applied to the name) and references it everywhere instead of inlining. Cycles get z.lazy(() => Name) only at the edges that close them.

Default is false; existing output is unchanged when the flag is unset.

Behavior

  • $ref to #/components/schemas/X → an exported const X = zod... (or Pet, pet_owner, etc., per namingConvention); cross-schema references use bare identifiers, cycle-closing edges become zod.lazy(() => X).
  • Operation files (verb-level) import and reuse the named exports instead of inlining the schema.
  • Per-operation wrapper files (PetCreateBody.zod.ts) are skipped when the body/response is a pure $ref — consumers import the component schema directly.
  • Sibling fields on $ref:
    • Chainable (nullable, default, description) → Pet.nullable().describe(...) chain.
    • Parent-driven optional.optional() when the parent doesn't list the property in required.
    • Non-chainable (properties, example, etc.) → falls back to inlining at that one site, with a verbose log.
  • When the schemas dir is configured as a plain string (schemas: 'src/gen/models') AND client: 'zod' AND the flag is on, the schemas dir is treated as the zod output (Zod schemas land there, not TypeScript types). Use the explicit { path, type: 'zod' } form, or rely on this auto-promotion.

Implementation

  • packages/core/src/types.ts: adds generateReusableSchemas?: boolean to ZodOptions and required field to NormalizedZodOptions.
  • packages/orval/src/utils/options.ts: defaults the flag to false in both normalization sites.
  • packages/zod/src/index.ts:
    • generateZodValidationSchemaDefinition widened to accept OpenApiReferenceObject. New top-of-function branch emits a namedRef placeholder + chained modifiers when the input is a plain component-schema $ref. Non-chainable siblings fall back to today's dereference path at that one site.
    • The flag is threaded through every recursive call (properties, items, allOf/oneOf/anyOf, additionalProperties, tuple prefixItems/rest, multi-type).
    • parseZodValidationSchemaDefinition returns usedRefs: Set<string> and renders refs as __REF_<name>__ sentinels.
    • generateZodRoute collects usedRefs from each parser call and rewrites the operation-level output's sentinels to bare names (operations are top-of-graph and never need lazy). generateZod returns the refs as imports with values: true so mode writers emit proper import { ... } from '...' lines.
  • packages/orval/src/reusable-schemas.ts (new): orchestrator that walks the spec, runs the generator per root, builds the ref dependency graph, runs Tarjan's SCC + DFS back-edge detection, rewrites sentinels into direct refs or zod.lazy(...), and emits in topological order.
  • packages/orval/src/write-zod-specs.ts: writeZodSchemas and generateZodSchemasInline route through the orchestrator when the flag is on. writeZodSchemasFromVerbs skips pure-ref wrappers.
  • packages/orval/src/write-specs.ts: when client: 'zod' + flag is on + schemas: is a plain string, treat the schemas dir as zod (auto-promotion to the zod writer).

Test plan

  • Unit tests for generateZodValidationSchemaDefinition cover plain refs, all chainable siblings, parent-driven optional, and the non-chainable fall-back.
  • Unit tests for parseZodValidationSchemaDefinition cover __REF__ sentinel emission and usedRefs reporting.
  • Unit tests for the orchestrator (reusable-schemas.test.ts): name resolution + conflict guard, ref collection, per-root generation with sentinels, Tarjan SCC + back-edge cases (DAG, simple cycle, self-loop, multi-node SCC), and sentinel rewriting + topological order.
  • Integration tests in write-zod-specs.test.ts for cross-file imports (schemas: { type: 'zod' }), wrapper-skip for pure refs, and inline orchestrator output.
  • End-to-end snapshot via samples/swr-with-zod's new petstoreZodReusable config (uses -reusable.zod.ts file extension so its output sits alongside the existing TS petstore + plain zod outputs without colliding).
  • All existing tests pass; default-off keeps existing output unchanged.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Zod generation option generateReusableSchemas (disabled by default). When enabled, component schemas are emitted as reusable named exports and referenced across generated files; cycles use lazy wrappers.
  • Tests

    • Added suites covering naming, reachability, dependency ordering, lazy-edge handling, rewriting, and end-to-end reusable-schema emission.
  • Documentation

    • Documented generateReusableSchemas and updated examples.
  • Samples

    • Added sample config and generated reusable Zod schema snapshots.
  • Chores

    • Default schema file extension normalized to .ts in outputs/tests.

Review Change Stack

Adds `override.zod.generateReusableSchemas` to emit one reusable Zod
schema per `#/components/schemas/*` $ref instead of inlining. Operations
and other schemas reference the named export (with `namingConvention`
applied), and cycles are wrapped in `z.lazy(() => Name)` only where
they close a cycle.

Highlights:

- `generateZodValidationSchemaDefinition` recognizes `$ref` input,
  emits a `namedRef` placeholder + chained modifiers for nullable,
  default, description, and parent-driven optional. Non-chainable
  siblings fall back to inlining at that one site.
- `parseZodValidationSchemaDefinition` returns the set of refs each
  schema uses and renders them as `__REF_<name>__` sentinels.
- New `packages/orval/src/reusable-schemas.ts` orchestrator: collects
  reachable component refs, runs the generator per root, computes
  Tarjan SCC + DFS back-edges, rewrites sentinels to direct refs or
  `z.lazy(...)` and emits in topological order.
- `writeZodSchemas` routes through the orchestrator when the flag is
  on, producing per-file output with cross-file imports.
- `writeZodSchemasFromVerbs` skips per-operation wrapper files when
  the body/response is a pure $ref.
- `generateZod` (verb-level) threads the flag, rewrites sentinels,
  and emits the used refs as imports so operation files cross-import
  component schemas.
- `write-specs.ts` auto-promotes `schemas: <string>` to the zod
  writer when `client: 'zod'` + flag is on, so the schemas dir
  contains zod schemas instead of TS types.
- Sample: existing `swr-with-zod` adds a `petstoreZodReusable` config
  exercising the flag end-to-end (snapshots included).

Default is off; existing output is unchanged when the flag is unset.
Copilot AI review requested due to automatic review settings May 26, 2026 13:26
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a config flag to enable reusable Zod schema generation and implements end-to-end support: name resolution, reachable-ref collection, per-ref Zod entry generation with usedRefs, dependency/SCC analysis to select lazy edges, sentinel rewriting into imports or zod.lazy, Orval writer integration, tests, docs, and sample snapshots.

Changes

Reusable Zod Schemas Pipeline

Layer / File(s) Summary
Types, option defaults, and normalization
packages/core/src/types.ts, packages/core/src/test-utils/context.ts, packages/orval/src/utils/options.ts, packages/mock/src/faker/getters/combine.test.ts, packages/orval/src/utils/options.test.ts, packages/angular/src/http-client.test.ts, packages/solid-start/src/index.test.ts, packages/core/src/test-utils/split-modes.ts
Adds generateReusableSchemas to Zod options and normalized options, documents schemaFileExtension behavior for Zod reusable mode, defaults it to false in normalizers/test helpers/mocks, introduces schemaFileExtension default .ts in test fixtures, and adds a unit test asserting the default.
Resolve names, collect reachable refs, generate entries
packages/orval/src/reusable-schemas.ts, packages/orval/src/reusable-schemas.test.ts
Derives stable export names for component refs, validates JS-identifier and collision rules, collects reachable #/components/schemas/* refs transitively from spec.paths, and generates per-ref ReusableSchemaEntry artifacts (zod, consts, usedRefs) by invoking the Zod generator in reusable mode with tests.
Dependency graph, SCC, lazy-edge computation and rewrite
packages/orval/src/reusable-schemas.ts, packages/orval/src/reusable-schemas.test.ts
Builds dependency graph from usedRefs, runs Tarjan SCC, computes lazy edges (self-loops and back-edges), orders entries to avoid TDZ, and rewrites __REF_<name>__ sentinels into identifiers or zod.lazy wrappers; tests verify DAG and cyclic behaviors.
Zod generator: namedRef, sentinels, and plumbing
packages/zod/src/index.ts, packages/zod/src/zod.test.ts
Introduces useReusableSchemas propagation; emits namedRef placeholders for chainable component $refs and parses them into __REF_<name>__ sentinels while collecting usedRefs; threads the flag through schema generation paths (arrays, tuples, objects, unions, params, bodies, form-data) and orchestrates sentinel rewriting in route generation, emitting runtime imports for used schemas; adds tests for namedRef/sentinel behavior.
Orval writers: integrate reusable pipeline
packages/orval/src/write-zod-specs.ts, packages/orval/src/write-zod-specs.test.ts
Wires reusable-schemas into generateZodSchemasInline and writeZodSchemas, adding generateZodSchemasInlineReusable and writeZodSchemasReusable that emit per-component files or inline modules with imports and index updates; updates writeZodSchemasFromVerbs to bypass dereferencing and skip pure-$ref operation wrappers when reusable mode is enabled; tests added.
writeSpecs refactor and import fixes
packages/orval/src/write-specs.ts
Refactors writeSpecs to compute a single schemasPath and isZodSchemas early; routes to Zod writers when detected and uses schemasPath consistently for import-fixing and split-schemas re-exports.
Tests, sample config, snapshots, and docs
packages/orval/src/reusable-schemas.test.ts, packages/orval/src/utils/options.test.ts, samples/swr-with-zod/*, docs/content/docs/reference/configuration/output.mdx, packages/orval/src/write-zod-specs.test.ts
Adds comprehensive tests for reusable utilities and Zod generator behavior, introduces a petstoreZodReusable sample config, generated snapshot modules showing per-component named Zod schemas and expanded barrel exports, and documents override.zod.generateReusableSchemas.

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues:

Possibly related PRs:

  • orval-labs/orval#3293 — overlaps write-zod-specs verb/body handling and emission paths.
  • orval-labs/orval#3433 — touches parseBodyAndResponse and media-type handling overlapping with zod integration changes.
  • orval-labs/orval#3118 — overlaps generateZodValidationSchemaDefinition changes in packages/zod/src/index.ts.

Suggested reviewers:

  • melloware

"I'm a rabbit in the schema patch,
hopping refs and stitching batch by batch.
One name per model, tidy and spry —
lazy loops get a gentle zod lullaby.
Reusable treats for every build! 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(zod): add generateReusableSchemas opt-in flag' clearly and specifically describes the main change: introducing a new optional flag for Zod schema generation. It is concise, uses conventional commit format, and accurately reflects the primary feature addition.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@melloware melloware left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@z4o4z make sure to update the /docs for Zod for this new option

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a generateReusableSchemas mode for the Zod generator to emit reusable, cross-file component schemas (with cycle-safe references) instead of inlining $refd schemas everywhere.

Changes:

  • Introduces override.zod.generateReusableSchemas option and wires it through normalization/config/types.
  • Implements $ref-aware “namedRef” placeholders in @orval/zod, plus ref tracking and import emission.
  • Adds a reusable-schema orchestrator in @orval/orval (graph + SCC + zod.lazy) and updates sample outputs/snapshots/tests.

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
samples/swr-with-zod/src/gen/models/pets-reusable.zod.ts Adds generated reusable Zod schema for pets.
samples/swr-with-zod/src/gen/models/pet-reusable.zod.ts Adds generated reusable Zod schema for pet with refs to cat/dog.
samples/swr-with-zod/src/gen/models/listPetsParams-reusable.zod.ts Adds generated reusable Zod schema for ListPetsParams.
samples/swr-with-zod/src/gen/models/labradoodle-reusable.zod.ts Adds generated reusable Zod schema for labradoodle.
samples/swr-with-zod/src/gen/models/index.ts Re-exports new *-reusable.zod schema modules.
samples/swr-with-zod/src/gen/models/error-reusable.zod.ts Adds generated reusable Zod schema for error.
samples/swr-with-zod/src/gen/models/dog-reusable.zod.ts Adds generated reusable Zod schema for dog with refs.
samples/swr-with-zod/src/gen/models/dachshund-reusable.zod.ts Adds generated reusable Zod schema for dachshund.
samples/swr-with-zod/src/gen/models/createPetsBody-reusable.zod.ts Adds generated reusable Zod schema for request body.
samples/swr-with-zod/src/gen/models/cat-reusable.zod.ts Adds generated reusable Zod schema for cat.
samples/swr-with-zod/src/gen/endpoints/pets/pets-reusable.zod.ts Adds generated endpoint validators that import reusable schemas.
samples/swr-with-zod/orval.config.ts Adds a sample config target enabling reusable Zod schemas.
samples/swr-with-zod/snapshots/models/pets-reusable.zod.ts Snapshot for generated reusable pets schema.
samples/swr-with-zod/snapshots/models/pet-reusable.zod.ts Snapshot for generated reusable pet schema.
samples/swr-with-zod/snapshots/models/listPetsParams-reusable.zod.ts Snapshot for generated reusable ListPetsParams schema.
samples/swr-with-zod/snapshots/models/labradoodle-reusable.zod.ts Snapshot for generated reusable labradoodle schema.
samples/swr-with-zod/snapshots/models/index.ts Snapshot updates to include new exports.
samples/swr-with-zod/snapshots/models/error-reusable.zod.ts Snapshot for generated reusable error schema.
samples/swr-with-zod/snapshots/models/dog-reusable.zod.ts Snapshot for generated reusable dog schema.
samples/swr-with-zod/snapshots/models/dachshund-reusable.zod.ts Snapshot for generated reusable dachshund schema.
samples/swr-with-zod/snapshots/models/createPetsBody-reusable.zod.ts Snapshot for generated reusable CreatePetsBody schema.
samples/swr-with-zod/snapshots/models/cat-reusable.zod.ts Snapshot for generated reusable cat schema.
samples/swr-with-zod/snapshots/endpoints/pets/pets-reusable.zod.ts Snapshot for generated endpoint validators.
packages/zod/src/zod.test.ts Adds unit tests for namedRef emission + parsing/tracking.
packages/zod/src/index.ts Implements $ref placeholder emission, sentinel rewriting, and import generation.
packages/solid-start/src/index.test.ts Updates test context default zod options to include the new flag.
packages/orval/src/write-zod-specs.ts Adds reusable-schema writer and inline generation path, plus plumbing.
packages/orval/src/write-zod-specs.test.ts Adds tests ensuring cross-file imports + wrapper skipping behavior.
packages/orval/src/write-specs.ts Routes schema writing through Zod writer when reusable mode is enabled.
packages/orval/src/utils/options.ts Normalizes generateReusableSchemas into output override defaults.
packages/orval/src/utils/options.test.ts Adds test that the new flag defaults to false.
packages/orval/src/reusable-schemas.ts New reusable-schema orchestrator: graph building, SCC/lazy-edge computing, rewriting.
packages/orval/src/reusable-schemas.test.ts Adds coverage for name resolution, reachable refs, SCC/lazy rewriting.
packages/mock/src/faker/getters/combine.test.ts Updates mock test context defaults to include the new flag.
packages/core/src/types.ts Extends ZodOptions/NormalizedZodOptions with generateReusableSchemas.
packages/core/src/test-utils/context.ts Updates test context defaults to include the new flag.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/zod/src/index.ts Outdated
Comment thread packages/orval/src/write-zod-specs.ts
Comment thread packages/orval/src/write-zod-specs.ts
Comment thread packages/orval/src/write-zod-specs.ts
Comment thread packages/orval/src/write-zod-specs.ts
Comment thread packages/orval/src/write-specs.ts Outdated
Comment thread packages/orval/src/reusable-schemas.ts
Comment thread packages/zod/src/index.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/zod/src/index.ts (1)

553-563: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix the tuple-item recursion inputs.

Line 554 still dereferences each prefixItems entry, so reusable mode can never emit a namedRef for tuple members, and Lines 557-558 pass isZodV4/strict in the opposite order. Tuple items will be inlined unexpectedly or generated with the wrong Zod/strict mode.

Suggested fix
               prefixItems.map((item, idx) =>
                 generateZodValidationSchemaDefinition(
-                  dereference(item, context),
+                  useReusableSchemas ? item : dereference(item, context),
                   context,
                   camel(`${name}-${idx}-item`),
-                  isZodV4,
                   strict,
+                  isZodV4,
                   {
                     required: true,
                     constNameRegistry,
                     useReusableSchemas,
                   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zod/src/index.ts` around lines 553 - 563, The tuple item call to
generateZodValidationSchemaDefinition is wrong: stop dereferencing each
prefixItems entry so reusable schemas can emit namedRef (pass the raw item
instead of dereference(item, context)), and swap the isZodV4/strict boolean
arguments so their order matches the function signature (pass strict then
isZodV4); keep context, the camel name, and the options object
(required/constNameRegistry/useReusableSchemas) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/orval/src/reusable-schemas.ts`:
- Around line 78-85: The current logic in collectRefsInValue adds record.$ref to
refs and returns early, which skips sibling properties; change it so that when
isComponentSchemaRef(record.$ref) is true you still add record.$ref to refs but
do NOT return from the function — instead skip recursing into the $ref property
itself and continue iterating the other keys on record so siblings are still
processed. Update collectRefsInValue to only avoid descending into the $ref
value (record.$ref) while continuing to call collectRefsInValue on the other
properties; keep using isComponentSchemaRef, refs and record.$ref to locate and
handle the case.
- Around line 274-299: computeLazyEdges currently marks lazy edges using the DFS
order (dfs, stackSet, lazy, edgeKey) but topoSortEntries later reorders nodes
inside each SCC lexicographically, which can make a forward edge non-lazy while
the source is emitted before the target and trigger TDZ; fix by making
edge-laziness and emit order consistent: either derive the emit order from the
same DFS that sets lazy edges (use the dfs visitation order / recorded
start/visited sequence for topoSortEntries) or, when topo-sorting an SCC (scc),
compute lazy edges after you sort the SCC so lazy reflects the final emit order
(ensure computeLazyEdges and topoSortEntries agree on node order and update lazy
markings accordingly).

In `@packages/orval/src/write-specs.ts`:
- Around line 283-286: The current isZodSchemas check incorrectly allows
auto-promotion to Zod when output.client === 'zod' even if output.schemas is an
explicit object; change the condition so the client-based promotion only applies
when output.schemas is a string (i.e., a named preset) — specifically update the
isZodSchemas logic to require isString(output.schemas) for the branch that
checks output.client === 'zod' and output.override.zod.generateReusableSchemas,
leaving the original path that detects non-string output.schemas.type === 'zod'
intact; locate the isZodSchemas declaration and adjust the boolean expression
accordingly.

In `@packages/orval/src/write-zod-specs.ts`:
- Around line 569-571: The code is passing unresolved component $ref schemas
downstream when useReusableSchemas is true because you only dereference when
false; update the wrapper rendering path in writeZodSchemasFromVerbs (and the
branches handling schema: bodySchema/querySchema/headerSchema/responseSchema) so
that when useReusableSchemas is true you do NOT dereference and the wrapper
writer is invoked with the reusable rendering path (i.e., ensure the wrapper
generation uses the reusable export/import logic instead of the inline generator
path); conversely, only call dereference(bodySchema, zodContext) (and the
equivalent dereference calls at the other branches) when useReusableSchemas is
false so nested component refs resolve consistently to shared exports.

In `@packages/zod/src/index.ts`:
- Around line 1527-1534: The branch that sets effectiveSchema allows a pure $ref
request body to bypass removal of readOnly fields; change the logic so when
useReusableSchemas is true and parseType === 'body' you detect if schema is an
OpenApiReferenceObject (i.e., has a $ref) and in that case use
removeReadOnlyProperties(resolvedJsonSchema) instead of forwarding schema;
update the conditional that assigns effectiveSchema (refer to effectiveSchema,
useReusableSchemas, parseType, schema, resolvedJsonSchema, and
removeReadOnlyProperties) to prefer the processed resolvedJsonSchema for $ref
body schemas while still reusing raw schema for non-body or non-$ref cases.
- Around line 1654-1668: The current logic eagerly resolves parameter.schema via
resolveRef causing component $ref (e.g. "`#/components/schemas/`*") to be inlined
instead of preserved for the namedRef path; change the schemaForGen branch so
that when useReusableSchemas is true you first detect if parameter.schema is a
reference (i.e. has a $ref) and, if so, return that reference directly (or a
shallow copy that adds parameter.description without mutating the original)
rather than calling resolveRef; only call resolveRef for non-$ref schemas so
namedRef handling can still see and export reusable component refs (affecting
symbols: schemaForGen, useReusableSchemas, parameter.schema, resolveRef,
namedRef).

---

Outside diff comments:
In `@packages/zod/src/index.ts`:
- Around line 553-563: The tuple item call to
generateZodValidationSchemaDefinition is wrong: stop dereferencing each
prefixItems entry so reusable schemas can emit namedRef (pass the raw item
instead of dereference(item, context)), and swap the isZodV4/strict boolean
arguments so their order matches the function signature (pass strict then
isZodV4); keep context, the camel name, and the options object
(required/constNameRegistry/useReusableSchemas) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 50f9c99f-1838-4897-8b5b-e6b620e63e43

📥 Commits

Reviewing files that changed from the base of the PR and between 78e2ab4 and 00fe47b.

⛔ Files ignored due to path filters (11)
  • samples/swr-with-zod/src/gen/endpoints/pets/pets-reusable.zod.ts is excluded by !**/gen/**
  • samples/swr-with-zod/src/gen/models/cat-reusable.zod.ts is excluded by !**/gen/**
  • samples/swr-with-zod/src/gen/models/createPetsBody-reusable.zod.ts is excluded by !**/gen/**
  • samples/swr-with-zod/src/gen/models/dachshund-reusable.zod.ts is excluded by !**/gen/**
  • samples/swr-with-zod/src/gen/models/dog-reusable.zod.ts is excluded by !**/gen/**
  • samples/swr-with-zod/src/gen/models/error-reusable.zod.ts is excluded by !**/gen/**
  • samples/swr-with-zod/src/gen/models/index.ts is excluded by !**/gen/**
  • samples/swr-with-zod/src/gen/models/labradoodle-reusable.zod.ts is excluded by !**/gen/**
  • samples/swr-with-zod/src/gen/models/listPetsParams-reusable.zod.ts is excluded by !**/gen/**
  • samples/swr-with-zod/src/gen/models/pet-reusable.zod.ts is excluded by !**/gen/**
  • samples/swr-with-zod/src/gen/models/pets-reusable.zod.ts is excluded by !**/gen/**
📒 Files selected for processing (25)
  • packages/core/src/test-utils/context.ts
  • packages/core/src/types.ts
  • packages/mock/src/faker/getters/combine.test.ts
  • packages/orval/src/reusable-schemas.test.ts
  • packages/orval/src/reusable-schemas.ts
  • packages/orval/src/utils/options.test.ts
  • packages/orval/src/utils/options.ts
  • packages/orval/src/write-specs.ts
  • packages/orval/src/write-zod-specs.test.ts
  • packages/orval/src/write-zod-specs.ts
  • packages/solid-start/src/index.test.ts
  • packages/zod/src/index.ts
  • packages/zod/src/zod.test.ts
  • samples/swr-with-zod/__snapshots__/endpoints/pets/pets-reusable.zod.ts
  • samples/swr-with-zod/__snapshots__/models/cat-reusable.zod.ts
  • samples/swr-with-zod/__snapshots__/models/createPetsBody-reusable.zod.ts
  • samples/swr-with-zod/__snapshots__/models/dachshund-reusable.zod.ts
  • samples/swr-with-zod/__snapshots__/models/dog-reusable.zod.ts
  • samples/swr-with-zod/__snapshots__/models/error-reusable.zod.ts
  • samples/swr-with-zod/__snapshots__/models/index.ts
  • samples/swr-with-zod/__snapshots__/models/labradoodle-reusable.zod.ts
  • samples/swr-with-zod/__snapshots__/models/listPetsParams-reusable.zod.ts
  • samples/swr-with-zod/__snapshots__/models/pet-reusable.zod.ts
  • samples/swr-with-zod/__snapshots__/models/pets-reusable.zod.ts
  • samples/swr-with-zod/orval.config.ts

Comment thread packages/orval/src/reusable-schemas.ts
Comment thread packages/orval/src/reusable-schemas.ts Outdated
Comment thread packages/orval/src/write-specs.ts Outdated
Comment thread packages/orval/src/write-zod-specs.ts
Comment thread packages/zod/src/index.ts
Comment thread packages/zod/src/index.ts Outdated
- Preserve $refs verbatim in `parseParameters` when `useReusableSchemas`
  is on (previously called `resolveRef`, stripping the wrapper and
  bypassing the namedRef path for parameter schemas).
- Thread `useReusableSchemas` into the inner `generateZodValidationSchemaDefinition`
  / `generateFormDataZodSchema` calls in `writeZodSchemasFromVerbs` so
  nested refs inside non-pure-ref body/response wrappers emit namedRef
  placeholders.
- Restore `.zod.ts` default file extension in `write-specs.ts`: the
  normalizer always sets `output.fileExtension` (default `.ts`), so the
  previous `|| '.zod.ts'` fallback was dead. Treat the bare default as
  unset; honour any user-set extension.
- Reject `namingConvention: 'kebab-case'` (and any other convention
  that produces a non-identifier name) for reusable schema export
  names — would otherwise emit `export const pet-owner = ...` which
  is invalid TS.
- Sort `usedRefs` before mapping to imports in `generateZod` so import
  lines are stable across runs regardless of Set iteration order.
- Document `generateReusableSchemas` in `docs/content/docs/reference/configuration/output.mdx`
  (per @melloware's review).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/orval/src/reusable-schemas.ts (2)

271-315: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

One lazy edge per SCC is unsafe once schemas are emitted as separate files.

This back-edge strategy works for single-file output, but writeZodSchemasReusable writes one module per schema and imports every usedRef. In a cycle like A <-> B, leaving A -> B direct and only lazifying B -> A still produces a TDZ path when the consumer imports B first: B loads A, A reads B before B finishes initializing. For split output, every intra-SCC reference needs to be lazy, or the whole SCC needs to be emitted together.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/orval/src/reusable-schemas.ts` around lines 271 - 315,
computeLazyEdges currently only adds a single back-edge per SCC which is unsafe
for split-file output; change it so that for any non-trivial SCC (scc from
tarjan) you mark every intra-SCC directed edge as lazy (i.e., call edgeKey(u,v)
and add to the lazy set for every u in scc and every v in graph.get(u) that is
contained in sccSet), preserving the existing self-loop handling; update
references to computeLazyEdges/edgeKey/tarjan so writeZodSchemasReusable will
treat all intra-SCC references as lazy imports rather than relying on a single
back-edge.

27-53: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Reject reserved JS keywords for generated reusable schema exports

JS_IDENTIFIER_PATTERN only checks identifier shape (not whether the result is a reserved keyword), so names like default (e.g. #/components/schemas/Defaultdefault under camelCase) can pass validation and then be emitted as export const default = ..., which is invalid syntax. Add a reserved-word/keyword blacklist (or use a JS/TS identifier validator that rejects keywords) in resolveSchemaNames.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/orval/src/reusable-schemas.ts` around lines 27 - 53, The current
JS_IDENTIFIER_PATTERN only checks identifier shape and allows reserved JS/TS
keywords (e.g. "default") which would produce invalid exports; update
resolveSchemaNames to also reject reserved words by adding a blacklist of JS/TS
reserved keywords (or integrate a validator that checks keywords) and throw an
Error when resolveSchemaName(ref, namingConvention) yields a reserved word;
reference JS_IDENTIFIER_PATTERN, resolveSchemaNames, and resolveSchemaName when
locating where to add the keyword check and the error message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/orval/src/write-specs.ts`:
- Around line 289-293: The code infers an "unset" extension by checking
output.fileExtension === '.ts', which loses the distinction between a normalized
default and an explicit user value; update the normalizer to carry an explicit
flag (e.g. output.fileExtensionExplicit or similar) alongside
output.fileExtension, update the normalization code that produces output to set
that flag when the user provided an extension, and change the logic in
write-specs.ts (where fileExtension is computed) to use that explicit flag: use
'.zod.ts' only when fileExtensionExplicit is false/undefined, otherwise honor
output.fileExtension exactly; ensure any callers that construct/normalize output
objects propagate the new flag.

---

Outside diff comments:
In `@packages/orval/src/reusable-schemas.ts`:
- Around line 271-315: computeLazyEdges currently only adds a single back-edge
per SCC which is unsafe for split-file output; change it so that for any
non-trivial SCC (scc from tarjan) you mark every intra-SCC directed edge as lazy
(i.e., call edgeKey(u,v) and add to the lazy set for every u in scc and every v
in graph.get(u) that is contained in sccSet), preserving the existing self-loop
handling; update references to computeLazyEdges/edgeKey/tarjan so
writeZodSchemasReusable will treat all intra-SCC references as lazy imports
rather than relying on a single back-edge.
- Around line 27-53: The current JS_IDENTIFIER_PATTERN only checks identifier
shape and allows reserved JS/TS keywords (e.g. "default") which would produce
invalid exports; update resolveSchemaNames to also reject reserved words by
adding a blacklist of JS/TS reserved keywords (or integrate a validator that
checks keywords) and throw an Error when resolveSchemaName(ref,
namingConvention) yields a reserved word; reference JS_IDENTIFIER_PATTERN,
resolveSchemaNames, and resolveSchemaName when locating where to add the keyword
check and the error message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f7d58470-3424-46d5-9ae3-1bbb07962861

📥 Commits

Reviewing files that changed from the base of the PR and between 00fe47b and e3c68ae.

📒 Files selected for processing (6)
  • docs/content/docs/reference/configuration/output.mdx
  • packages/orval/src/reusable-schemas.test.ts
  • packages/orval/src/reusable-schemas.ts
  • packages/orval/src/write-specs.ts
  • packages/orval/src/write-zod-specs.ts
  • packages/zod/src/index.ts

Comment thread packages/orval/src/write-specs.ts Outdated
@melloware melloware added the zod Zod schema client related issue label May 26, 2026
@melloware

Copy link
Copy Markdown
Collaborator

i believe this fixes #2535

@melloware melloware linked an issue May 26, 2026 that may be closed by this pull request
z4o4z added 2 commits May 26, 2026 15:56
- Stop early-returning at `$ref` in `collectRefsInValue`: OpenAPI 3.1
  allows siblings with their own refs; only skip descending into the
  `$ref` string itself.
- Integrate back-edge detection into Tarjan and use the SCC's natural
  pop order for emit ordering. The previous code lex-sorted nodes
  within an SCC, which could leave a non-lazy forward edge pointing
  to a not-yet-emitted node and trigger a TDZ at module load.
- Restrict the auto-promote-to-zod branch in `write-specs.ts` to
  string `schemas:` configs only. Explicit `{ type: 'typescript' }`
  configs are kept on the TS path even when `client: 'zod'` + the
  flag are set.
- Document the `readOnly` trade-off: with the flag on, shared
  component schemas keep `readOnly` properties in request body
  validators (previously stripped via `dereference + removeReadOnlyProperties`).
The previous fix in write-specs.ts inferred "unset" by checking
output.fileExtension === '.ts', which couldn't distinguish the
normalizer default from a user-set '.ts'. Move the default into
normalization where we still see the raw outputOptions: when the
output is zod-schemas (explicit `{ type: 'zod' }` OR a string
`schemas:` + `client: 'zod'` + `generateReusableSchemas`), default
fileExtension to `.zod.ts`; otherwise '.ts'. The write-specs.ts
branch now reads output.fileExtension directly with no override.

A user-set fileExtension still wins via the `?? defaultFileExtension`
chain.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/orval/src/utils/options.ts`:
- Around line 221-232: The change flips the shared defaultFileExtension causing
non-schema writers to get the .zod.ts suffix; instead, keep defaultFileExtension
as the general output suffix and introduce a schema-specific extension
flag/variable used only by schema writers: retain defaultFileExtension as '.ts',
create a new schemaFileExtension (or a boolean signal like isZodSchemasOutput)
scoped to schema emitters (reference isZodSchemasOutput and
writeFakerSchemaMocks) and use that when generating schema filenames, ensuring
other writers still read output.fileExtension unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 608754e1-006b-4e1e-aaf4-f2c8bfd6b654

📥 Commits

Reviewing files that changed from the base of the PR and between e3c68ae and 5ed3dff.

📒 Files selected for processing (4)
  • docs/content/docs/reference/configuration/output.mdx
  • packages/orval/src/reusable-schemas.ts
  • packages/orval/src/utils/options.ts
  • packages/orval/src/write-specs.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/content/docs/reference/configuration/output.mdx

Comment thread packages/orval/src/utils/options.ts Outdated
@z4o4z
z4o4z requested review from Copilot and melloware May 26, 2026 14:22
@z4o4z

z4o4z commented May 26, 2026

Copy link
Copy Markdown
Contributor Author

i believe this fixes #2535

yep

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

samples/swr-with-zod/src/gen/models/index.ts:1

  • Re-exporting both the original model modules and the new *-reusable.zod modules from the same barrel can create ambiguous/unstable public exports (e.g., consumers importing pet may now get a runtime Zod schema value instead of only types) and can also trigger duplicate export-name conflicts if both sides export the same type names. Consider separating reusable Zod exports into a dedicated barrel (e.g. models/zod/index.ts) or aliasing the Zod exports (e.g. petSchema, petInput, petOutput) to avoid collisions and accidental runtime imports.

Comment thread packages/zod/src/index.ts
Comment thread packages/zod/src/index.ts Outdated
Comment thread packages/orval/src/reusable-schemas.test.ts Outdated
Comment thread packages/orval/src/reusable-schemas.ts Outdated
- Scope the `.zod.ts` default to schema writers only: introduce
  `output.schemaFileExtension` so non-schema writers (client / mock /
  workspace barrel) keep their own extensions and don't leak the zod
  suffix when no `fileExtension` is set. `output.fileExtension` keeps
  its `.ts` default. User-set `fileExtension` still flows to both.
- Unify ref naming between the generator and the orchestrator: the
  orchestrator's `lastRefSegment` now URL-decodes and unescapes JSON
  Pointer tokens (`~1`/`~0`), matching `getRefInfo(...).originalName`
  used by the namedRef emission. Avoids drift on refs with escaped
  characters.
- Use `constNameRegistry` for ref-default const names. Without it,
  multiple defaults sharing the same `name` would collide on
  `<name>Default`. Matches the suffix pattern used elsewhere in
  `generateZodValidationSchemaDefinition`.
- Move the stray `rewriteReusableSchemas` import in
  `reusable-schemas.test.ts` up to the main import block.
- Switch `collectReachableComponentRefs` BFS from `queue.shift()` to
  index-based iteration — O(n) instead of O(n²) for large specs.

Also update the various test-utils fixtures that build
`NormalizedOutputOptions` literals to include the new
`schemaFileExtension` field.
@z4o4z
z4o4z requested a review from Copilot May 26, 2026 14:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/orval/src/utils/options.ts`:
- Around line 312-313: The normalization currently sets schemaFileExtension to
outputOptions.fileExtension and ignores an explicit schema-specific override;
update the assignment so schemaFileExtension uses the schema-specific value
first (e.g., outputOptions.schemaFileExtension ?? outputOptions.fileExtension ??
defaultSchemaFileExtension) so an explicit schema override is honored; locate
the schemaFileExtension assignment in the normalization logic and replace the
current expression accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb4ffeba-e541-4806-9995-02b595f91a94

📥 Commits

Reviewing files that changed from the base of the PR and between 0199697 and cd9cbdb.

📒 Files selected for processing (12)
  • packages/angular/src/http-client.test.ts
  • packages/angular/src/http-resource.test.ts
  • packages/core/src/test-utils/context.ts
  • packages/core/src/test-utils/split-modes.ts
  • packages/core/src/types.ts
  • packages/mock/src/faker/getters/combine.test.ts
  • packages/orval/src/reusable-schemas.test.ts
  • packages/orval/src/reusable-schemas.ts
  • packages/orval/src/utils/options.ts
  • packages/orval/src/write-specs.ts
  • packages/solid-start/src/index.test.ts
  • packages/zod/src/index.ts

Comment thread packages/orval/src/utils/options.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 40 out of 40 changed files in this pull request and generated 2 comments.

Comment thread packages/zod/src/index.ts Outdated
Comment thread packages/orval/src/write-zod-specs.ts

@melloware melloware left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

looks like a lint issue

z4o4z added 2 commits May 26, 2026 17:02
- Honor an explicit `output.schemaFileExtension` user-set value during
  normalization. Added the field to `OutputOptions` and prioritised it
  over `outputOptions.fileExtension` and the default. This lets users
  set a separate extension for schema files (e.g. `.ts` for client +
  `.zod.ts` for schemas) without affecting the global one.
- Fix `.default().optional()` ordering in the reusable-ref path. In
  zod, `.optional()` after `.default()` short-circuits on undefined
  and the default never runs. Reordered the ref-branch to match the
  main path (nullable/nullish/optional → default → describe) and
  skip `.optional()` when a default is set.
- Expand the orchestrator's root set to the transitive closure of
  reachable component-schema refs. Without this, a generated schema
  could emit a sentinel for a ref not in the seed list, producing
  an unresolved identifier in the rewritten output.

Tests added:
- skip .optional() when default is set
- emit .nullish() when nullable + !required (combines nullable+optional)
- transitive-closure expansion in generateReusableSchemaSet
@z4o4z
z4o4z requested review from Copilot and melloware May 26, 2026 15:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 40 out of 40 changed files in this pull request and generated 3 comments.

Comment thread packages/orval/src/write-zod-specs.ts Outdated
Comment thread packages/orval/src/write-zod-specs.ts Outdated
Comment thread docs/content/docs/reference/configuration/output.mdx Outdated
Adds `override.zod.generateReusableSchemas` to emit one reusable Zod
schema per `#/components/schemas/*` $ref instead of inlining. Operations
and other schemas reference the named export (with `namingConvention`
applied), and cycles are wrapped in `z.lazy(() => Name)` only where
they close a cycle.

Highlights:

- `generateZodValidationSchemaDefinition` recognizes `$ref` input,
  emits a `namedRef` placeholder + chained modifiers for nullable,
  default, description, and parent-driven optional. Non-chainable
  siblings fall back to inlining at that one site.
- `parseZodValidationSchemaDefinition` returns the set of refs each
  schema uses and renders them as `__REF_<name>__` sentinels.
- New `packages/orval/src/reusable-schemas.ts` orchestrator: collects
  reachable component refs, runs the generator per root, computes
  Tarjan SCC + DFS back-edges, rewrites sentinels to direct refs or
  `z.lazy(...)` and emits in topological order.
- `writeZodSchemas` routes through the orchestrator when the flag is
  on, producing per-file output with cross-file imports.
- `writeZodSchemasFromVerbs` skips per-operation wrapper files when
  the body/response is a pure $ref.
- `generateZod` (verb-level) threads the flag, rewrites sentinels,
  and emits the used refs as imports so operation files cross-import
  component schemas.
- `write-specs.ts` auto-promotes `schemas: <string>` to the zod
  writer when `client: 'zod'` + flag is on, so the schemas dir
  contains zod schemas instead of TS types.
- Sample: existing `swr-with-zod` adds a `petstoreZodReusable` config
  exercising the flag end-to-end (snapshots included).

Default is off; existing output is unchanged when the flag is unset.
z4o4z added 7 commits May 26, 2026 11:26
- Preserve $refs verbatim in `parseParameters` when `useReusableSchemas`
  is on (previously called `resolveRef`, stripping the wrapper and
  bypassing the namedRef path for parameter schemas).
- Thread `useReusableSchemas` into the inner `generateZodValidationSchemaDefinition`
  / `generateFormDataZodSchema` calls in `writeZodSchemasFromVerbs` so
  nested refs inside non-pure-ref body/response wrappers emit namedRef
  placeholders.
- Restore `.zod.ts` default file extension in `write-specs.ts`: the
  normalizer always sets `output.fileExtension` (default `.ts`), so the
  previous `|| '.zod.ts'` fallback was dead. Treat the bare default as
  unset; honour any user-set extension.
- Reject `namingConvention: 'kebab-case'` (and any other convention
  that produces a non-identifier name) for reusable schema export
  names — would otherwise emit `export const pet-owner = ...` which
  is invalid TS.
- Sort `usedRefs` before mapping to imports in `generateZod` so import
  lines are stable across runs regardless of Set iteration order.
- Document `generateReusableSchemas` in `docs/content/docs/reference/configuration/output.mdx`
  (per @melloware's review).
- Stop early-returning at `$ref` in `collectRefsInValue`: OpenAPI 3.1
  allows siblings with their own refs; only skip descending into the
  `$ref` string itself.
- Integrate back-edge detection into Tarjan and use the SCC's natural
  pop order for emit ordering. The previous code lex-sorted nodes
  within an SCC, which could leave a non-lazy forward edge pointing
  to a not-yet-emitted node and trigger a TDZ at module load.
- Restrict the auto-promote-to-zod branch in `write-specs.ts` to
  string `schemas:` configs only. Explicit `{ type: 'typescript' }`
  configs are kept on the TS path even when `client: 'zod'` + the
  flag are set.
- Document the `readOnly` trade-off: with the flag on, shared
  component schemas keep `readOnly` properties in request body
  validators (previously stripped via `dereference + removeReadOnlyProperties`).
The previous fix in write-specs.ts inferred "unset" by checking
output.fileExtension === '.ts', which couldn't distinguish the
normalizer default from a user-set '.ts'. Move the default into
normalization where we still see the raw outputOptions: when the
output is zod-schemas (explicit `{ type: 'zod' }` OR a string
`schemas:` + `client: 'zod'` + `generateReusableSchemas`), default
fileExtension to `.zod.ts`; otherwise '.ts'. The write-specs.ts
branch now reads output.fileExtension directly with no override.

A user-set fileExtension still wins via the `?? defaultFileExtension`
chain.
- Scope the `.zod.ts` default to schema writers only: introduce
  `output.schemaFileExtension` so non-schema writers (client / mock /
  workspace barrel) keep their own extensions and don't leak the zod
  suffix when no `fileExtension` is set. `output.fileExtension` keeps
  its `.ts` default. User-set `fileExtension` still flows to both.
- Unify ref naming between the generator and the orchestrator: the
  orchestrator's `lastRefSegment` now URL-decodes and unescapes JSON
  Pointer tokens (`~1`/`~0`), matching `getRefInfo(...).originalName`
  used by the namedRef emission. Avoids drift on refs with escaped
  characters.
- Use `constNameRegistry` for ref-default const names. Without it,
  multiple defaults sharing the same `name` would collide on
  `<name>Default`. Matches the suffix pattern used elsewhere in
  `generateZodValidationSchemaDefinition`.
- Move the stray `rewriteReusableSchemas` import in
  `reusable-schemas.test.ts` up to the main import block.
- Switch `collectReachableComponentRefs` BFS from `queue.shift()` to
  index-based iteration — O(n) instead of O(n²) for large specs.

Also update the various test-utils fixtures that build
`NormalizedOutputOptions` literals to include the new
`schemaFileExtension` field.
- Honor an explicit `output.schemaFileExtension` user-set value during
  normalization. Added the field to `OutputOptions` and prioritised it
  over `outputOptions.fileExtension` and the default. This lets users
  set a separate extension for schema files (e.g. `.ts` for client +
  `.zod.ts` for schemas) without affecting the global one.
- Fix `.default().optional()` ordering in the reusable-ref path. In
  zod, `.optional()` after `.default()` short-circuits on undefined
  and the default never runs. Reordered the ref-branch to match the
  main path (nullable/nullish/optional → default → describe) and
  skip `.optional()` when a default is set.
- Expand the orchestrator's root set to the transitive closure of
  reachable component-schema refs. Without this, a generated schema
  could emit a sentinel for a ref not in the seed list, producing
  an unresolved identifier in the rewritten output.

Tests added:
- skip .optional() when default is set
- emit .nullish() when nullable + !required (combines nullable+optional)
- transitive-closure expansion in generateReusableSchemaSet
@melloware
melloware force-pushed the feat/reusable-zod-schemas branch from aff60b3 to 49ee638 Compare May 26, 2026 15:26
z4o4z added 3 commits May 26, 2026 17:36
- Emit an explicit `;` after `export const <name> = ${zod}` in both
  reusable schema writers. The output already round-tripped fine via
  prettier (which inserts the semicolon at the end of the chain), but
  emitting it in the raw string keeps the output consistent with other
  generators and avoids any ASI edge cases when no formatter is run.
- Update the docs to describe the new `schemaFileExtension` option:
  added a top-level `## schemaFileExtension` section explaining when to
  use it vs `fileExtension`, and updated the reusable-schemas bullet to
  point at it.
@melloware
melloware merged commit 63cdccb into orval-labs:master May 26, 2026
6 checks passed
@melloware

Copy link
Copy Markdown
Collaborator

This also fixed #3068

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

Labels

zod Zod schema client related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Zod: Apply schema suffix only to schema and not to the type Zod Generator Produces Large Monolithic Schemas

3 participants