Skip to content

perf(lazy-zod): add internalZ, which also builds leaf schemas on first use - #1011

Merged
frontegg-david merged 4 commits into
perf/p02-dependency-costsfrom
perf/p03a-internal-z
Nov 2, 2026
Merged

frontegg-david merged 4 commits into
perf/p02-dependency-costsfrom
perf/p03a-internal-z

Conversation

@frontegg-david

@frontegg-david frontegg-david commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1007

Refs #949 (part 1 of 2; the sweep that uses it is #1010, which closes the issue)

What and why

#949: after startup a FrontMCP server holds 6.3 MB of zod schema instances. The public lazy z defers compound schemas, but leaf schemas (z.string(), z.enum(), …) are created when their module loads, and Zod 4.6's first-parse cycle walk builds 440 of the 947 lazy schemas during startup anyway.

This PR adds the building block only, split out of #1010 so each PR stays under CodeRabbit's 150-file limit:

  • internalZ in @frontmcp/lazy-zod: every factory is deferred, leaves included. Leaves are built together with their parent, so a built schema holds real Zod leaves and parsing doesn't go through proxies.
  • No first-parse cycle walk for FrontMCP's own schemas. Their builds use Zod's memoizer without that walk (memoizer.ts), swapped in for the build and restored in finally. A user stand-in built inside that window gets Zod's own memoizer back; the marker is a Symbol.for registry symbol so two loaded copies of lazy-zod agree.
  • Public z unchanged. Tests pin that its leaf factories are Zod's own. Two internal changes also touch it: stand-ins share one proxy handler and drop their build closure once built, and forceMaterialize also follows catchall, rest and pipe in/out (written with Reflect.set, so .merge()d schemas convert with toJSONSchema).

User-facing change

None in any public API. internalZ is internal: the lazy-zod README says so and @frontmcp/sdk doesn't re-export it.

Tests

  • libs/lazy-zod/src/__tests__/internal-z.spec.ts (167): pins the public z; table-driven parity for every factory internalZ defers (inputs, issues, messages, and JSON Schema compared with Zod's own toJSONSchema); the memoizer swap and its restore; user stand-ins built inside the swap; two lazy-zod copies; the Zod internals the swap relies on, and the fallback when Zod has none.
  • materialization.spec.ts: catchall, rest, pipes, and .merge() chains through toJSONSchema.
  • lazy-zod 365 tests (98.0% statements, 93.6% branches; internal-z.ts and memoizer.ts 100%); utils, auth and sdk suites and demo-e2e-bundle-size pass on this branch.

The measurements (zod instances 6.32 → 4.14 MB retained, heap used −3.6 MB) come with #1010, which moves the schemas onto internalZ.

Summary by CodeRabbit

  • New Features
    • Added an internal API for building schemas lazily, while keeping the public z interface backed by Zod.
    • Improved materialization of nested schemas, including additional schema references.
    • Improved compatibility between lazy and eager schemas when converting to JSON Schema.
  • Documentation
    • Clarified the internal API’s behavior and noted that it may change between releases.

Bundle size

lazy-zod's browser build now carries internalZ and its memoizer. Measured on this branch against #1007 (both on today's main): create() + one tool grows by 2,180 bytes minified (1,319,959 → 1,322,139) and create() + WebMcpPlugin + one tool by 2,181 (1,327,146 → 1,329,327). The two browser budgets in demo-e2e-bundle-size move up by exactly that (1,321,639 → 1,323,819 and 1,328,515 → 1,330,696), keeping the headroom they had.

…t use

An internal lazy z for FrontMCP's own schemas: every factory is deferred, leaves included, and leaves are built with their parent. Built FrontMCP schemas skip Zod 4.6's first-parse cycle walk (user schemas keep it). The public z stays Zod's own. The sweep that moves sdk, auth and utils onto it follows in the next pull request.
@frontegg-david
frontegg-david marked this pull request as ready for review November 2, 2026 16:41
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Next included review available in 45 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available. Your 52 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between dc0bfb4 and 29aedfd.

Files selected for processing: 1
  • apps/e2e/demo-e2e-bundle-size/e2e/browser-bundle-size.e2e.spec.ts

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between aae2933 and 99f9cc7.

Files selected for processing: 10
  • libs/lazy-zod/README.md
  • libs/lazy-zod/src/__tests__/internal-z.spec.ts
  • libs/lazy-zod/src/__tests__/materialization.spec.ts
  • libs/lazy-zod/src/factories.ts
  • libs/lazy-zod/src/index.ts
  • libs/lazy-zod/src/internal-z.ts
  • libs/lazy-zod/src/lazy-schema.ts
  • libs/lazy-zod/src/lazy-z.ts
  • libs/lazy-zod/src/memoizer.ts
  • libs/lazy-zod/src/utils.ts

Limit details: You’ve used all 2 included reviews currently available. Your 51 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

Walkthrough

The change adds and exports internalZ, an internal lazy Zod interface. It defers configured schema factories, builds eligible child schemas, and updates memoizer handling and materialization. Tests compare schema behavior and JSON Schema output with Zod.

Changes: Internal lazy Zod schema construction

Layer / File(s) Summary
Lazy schema build lifecycle
libs/lazy-zod/src/lazy-z.ts, libs/lazy-zod/src/lazy-schema.ts, libs/lazy-zod/src/memoizer.ts
createLazyZ accepts configurable factory names and a stand-in callback. Lazy schema construction uses memoizer wrappers and clears its materialization factory after building.
Internal Zod factories and child schemas
libs/lazy-zod/src/internal-z.ts, libs/lazy-zod/src/factories.ts, libs/lazy-zod/src/index.ts, libs/lazy-zod/src/utils.ts, libs/lazy-zod/README.md
internalZ defers configured factories, builds eligible children, and exposes Zod type aliases. Deep materialization covers additional child-definition keys. The package exports and documents internalZ.
Schema behavior and materialization tests
libs/lazy-zod/src/__tests__/internal-z.spec.ts, libs/lazy-zod/src/__tests__/materialization.spec.ts
Tests compare internalZ and Zod across factory and chain behavior, parsing, JSON Schema, memoizer handling, and inferred types. Materialization tests compare lazy and eager merged schema JSON output.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant internalZ
  participant InternalLazySchema
  participant Zod
  Caller->>internalZ: call a schema factory
  internalZ-->>Caller: return a schema or stand-in
  Caller->>InternalLazySchema: parse or invoke a chained method
  InternalLazySchema->>Zod: build the deferred schema
  Zod-->>Caller: return the parse result
Loading

Possibly related issues

Possibly related PRs

  • agentfront/frontmcp#981: Both PRs concern lazy-schema JSON Schema materialization. This PR extends materialization to catchall, rest, in, and out.

Suggested reviewers: alexmercerpo

Merge Risk: Minimal · previous review 99f9c

This PR adds an internal lazy schema builder without changing the public z behavior. No actionable merge-blocking issue was found. Memory savings are measured in the follow-up PR that adopts internalZ, not here.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 65.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly identifies the main change: adding internalZ to defer leaf schema construction. It is concise and specific.
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.
Description check Passed The description covers the 'What and why' and 'Tests' sections with substantive content. It explains the motivation (issue #949: about 6.3 MB of zod schema instances retained after startup, with leaf …
Full details: Description check

Explanation

The description covers the 'What and why' and 'Tests' sections with substantive content. It explains the motivation (issue #949: about 6.3 MB of zod schema instances retained after startup, with leaf schemas created at module load and a first-parse cycle walk building 440 of 947 lazy schemas), the new internalZ building block, the memoizer swap, and the public z behavior. The Tests section lists the spec files, what each covers, and the coverage numbers. It also adds a Bundle size section that is not in the template; it is useful context. The 'User-facing change' section is present and states 'None in any public API', with a reason. Remaining gaps are minor: the 'User-facing change' template bullets (frontmcp.dev pages affected, Skills catalog entries) are not addressed, and the 'Tests' section does not say whether any checks were run by hand. These gaps are non-critical, so the description is mostly complete.

Full details: Docstring Coverage

Explanation

Docstring coverage is 23.08% which is insufficient. The required threshold is 65.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 13 functions across 9 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Performance Test Results

Status: ✅ All tests passed

Summary

Project Tests Passed Warnings Failed Leaks
✅ demo-e2e-agents 4 4 0 0 0
✅ demo-e2e-cache 11 11 0 0 0
✅ demo-e2e-codecall 4 4 0 0 0
✅ demo-e2e-config 4 4 0 0 0
✅ demo-e2e-direct 3 3 0 0 0
✅ demo-e2e-elicitation 1 1 0 0 0
✅ demo-e2e-errors 4 4 0 0 0
✅ demo-e2e-hooks 3 3 0 0 0
✅ demo-e2e-multiapp 4 4 0 0 0
✅ demo-e2e-notifications 3 3 0 0 0
✅ demo-e2e-openapi 2 2 0 0 0
✅ demo-e2e-providers 4 4 0 0 0
✅ demo-e2e-public 4 4 0 0 0
✅ demo-e2e-redis 15 15 0 0 0
✅ demo-e2e-remember 4 4 0 0 0
✅ demo-e2e-remote 5 5 0 0 0
✅ demo-e2e-serverless 2 2 0 0 0
✅ demo-e2e-skills 15 15 0 0 0
✅ demo-e2e-standalone 2 2 0 0 0
✅ demo-e2e-transport-recreation 3 3 0 0 0
✅ demo-e2e-ui 4 4 0 0 0

Total: 101 tests across 21 projects

📊 View full report in workflow run


Generated at: 2026-11-02T17:40:40.214Z
Commit: c173efa2

lazy-zod's browser build now carries internalZ and its memoizer, so create() + one tool grows by 2,180 bytes minified and create() + WebMcpPlugin + one tool by 2,181. The two budgets move up by exactly that, keeping the headroom they had.
@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@frontegg-david
frontegg-david merged commit 41b1ea5 into main Nov 2, 2026
45 checks passed
@frontegg-david
frontegg-david deleted the perf/p03a-internal-z branch November 2, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants