fix(spec): keep lazySchema proxies identity-compatible with z.toJSONSchema (objectui#2561)#3021
Merged
Merged
Conversation
…chema (objectui#2561) zod's toJSONSchema keys its seen-map on the traversed node (the lazySchema Proxy at z.lazy recursion getters and conversion roots), while its wrapper-type processors (pipe/lazy/optional/default/...) resolve ctx.seen.get(inst) via the REAL instance captured at construction — the identity mismatch crashes with "Cannot set properties of undefined (setting 'ref')". Latent while lazy-referenced schemas were plain objects; ADR-0089 D3a made PageComponentSchema/FormFieldSchema .strict().transform() pipes, breaking ObjectUI Studio's spec-derived Page/View inspector JSONSchema derivation under spec 15. The proxy now serves a memoised _zod facade that prototype-delegates to the real internals and wraps only processJSONSchema to alias the proxy's seen entry onto the real instance before delegating. Parse behavior unchanged; OS_EAGER_SCHEMAS=1 remains the bypass. Regression tests: the D3a pipe shape, recursion through z.lazy(() => proxy), mixed proxy+real traversal, and the PageSchema/ViewSchema Studio derivation paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 97 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
z.toJSONSchemacrashes withTypeError: Cannot set properties of undefined (setting 'ref')on spec 15PageSchema/ViewSchema/PageComponentSchema/FormFieldSchema(default lazy mode;OS_EAGER_SCHEMAS=1converts fine). This breaks ObjectUI Studio's spec-derived Page/View inspector JSONSchema derivation (page-schema.ts/view-schema.tsin objectui app-shell) and currently blocks objectui's spec ^15 adoption (objectstack-ai/objectui#2561).Root cause
zod's
toJSONSchemakeys itsseenmap on the node object it traverses — thelazySchemaProxy, wherever a schema is referenced lazily (z.lazy(() => X)recursion getters, direct conversion roots). But zod's per-type hooks close over the real instance at construction time (inst._zod.processJSONSchema = (ctx, …) => pipeProcessor(inst, ctx, …)), and the wrapper-type processors (pipe/lazy/optional/default/…) then resolvectx.seen.get(inst)— a miss when the entry was keyed on the Proxy.Latent until now: plain-object schemas never look themselves up (
objectProcessordoesn't touchseen.get(self)), so the proxies were harmless while every lazy-referenced schema was a barez.object. ADR-0089 D3a turnedPageComponentSchema/FormFieldSchemainto.strict().transform(normalizeVisibleWhen)pipes — a wrapper type — detonating the identity mismatch. (zod 4.4.3 is current latest; no zod-side fix available.)Fix
lazySchema's proxy now serves a memoised_zodfacade: prototype-delegates every read to the real internals, wrapping onlyprocessJSONSchemato alias the proxy'sseenentry onto the real instance (alias, never clobber) before delegating. Parse hot path unchanged (facade built once per proxy);OS_EAGER_SCHEMAS=1bypass unchanged.Tests
lazy-schema.test.ts: D3a pipe shape, recursion throughz.lazy(() => proxy)(FormFieldSchema shape), mixed proxy+real traversal in both orders, facade memoisation. Verified the 3 new conversion tests fail without the fix.page.test.ts/view.test.ts: pin the exact Studio derivation calls (z.toJSONSchema(PageSchema/ViewSchema, { io: 'input', unrepresentable: 'any' })).@objectstack/specsuite: 253 files / 6863 tests green.Changeset: patch
@objectstack/spec. Once released, objectui rides it in the #2561 types-package PR (spec ^15 pin bump).Refs objectstack-ai/objectui#2561, ADR-0089 D3a.
🤖 Generated with Claude Code