diff --git a/apps/e2e/demo-e2e-bundle-size/e2e/browser-bundle-size.e2e.spec.ts b/apps/e2e/demo-e2e-bundle-size/e2e/browser-bundle-size.e2e.spec.ts index a0604c357..e4bc6c730 100644 --- a/apps/e2e/demo-e2e-bundle-size/e2e/browser-bundle-size.e2e.spec.ts +++ b/apps/e2e/demo-e2e-bundle-size/e2e/browser-bundle-size.e2e.spec.ts @@ -25,10 +25,10 @@ import { const BUDGETS: Array<{ entry: string; label: string; maxBytes: number }> = [ { entry: 'create-tool.ts', label: 'create() + one tool', maxBytes: 1_340_056 }, - { entry: 'create-webmcp-tool.ts', label: 'create() + WebMcpPlugin + one tool', maxBytes: 1_346_921 }, + { entry: 'create-webmcp-tool.ts', label: 'create() + WebMcpPlugin + one tool', maxBytes: 1_347_378 }, ]; -const VITE_INITIAL_CHUNK_MAX_BYTES = 964_390; +const VITE_INITIAL_CHUNK_MAX_BYTES = 964_827; // Packages create() never needs: the JavaScript parser behind @enclave-vm/ast (#885) and the MCP client (#886) const NEVER_BUNDLED: Record = { diff --git a/libs/lazy-zod/src/__tests__/internal-z.spec.ts b/libs/lazy-zod/src/__tests__/internal-z.spec.ts index e4d061e01..76930a1b7 100644 --- a/libs/lazy-zod/src/__tests__/internal-z.spec.ts +++ b/libs/lazy-zod/src/__tests__/internal-z.spec.ts @@ -472,4 +472,57 @@ describe('internalZ', () => { const typed: internalZ.ZodType = schema; expect(typed.parse(value)).toEqual({ a: 'x' }); }); + + it('makes no function per stand-in: two alike stand-ins hold the same functions', () => { + const functionsOf = (schema: unknown) => + Object.values(targetOf(schema)).filter((value) => typeof value === 'function'); + const builds: Array<() => unknown> = [ + () => internalZ.string(), + () => internalZ.object({ a: internalZ.number() }), + () => internalZ.string().min(1).optional(), + () => internalZ.union([internalZ.string(), internalZ.number()]).optional(), + ]; + + for (const build of builds) { + const first = functionsOf(build()); + expect(first.length).toBeGreaterThan(0); + expect(functionsOf(build())).toEqual(first); + } + }); + + it('lets a built stand-in go of the arguments it was made with', () => { + const shape = { name: internalZ.string() }; + const options = [internalZ.literal('a'), internalZ.literal('b')]; + const object = internalZ.object(shape); + const union = internalZ.union(options).optional(); + + object.parse({ name: 'x' }); + union.parse('a'); + + for (const target of [object, union].map(targetOf)) { + const held = Object.values(target).flatMap((value) => (Array.isArray(value) ? value : [value])); + expect(held).not.toContain(shape); + expect(held).not.toContain(options); + } + }); + + it('lets a built chained stand-in go of the stand-in it was chained on', () => { + const parent = internalZ.object({ a: internalZ.string() }); + const extended = parent.extend({ b: internalZ.number() }); + + extended.parse({ a: 'x', b: 1 }); + + expect(Object.values(targetOf(extended))).not.toContain(targetOf(parent)); + }); + + it('builds from the same arguments when a build that threw is tried again', () => { + const shape = { a: internalZ.string() }; + const conflicting = { message: 'm', error: 'e' }; + const object = internalZ.object(shape, conflicting); + const optional = internalZ.object(shape, conflicting).optional(); + + for (const schema of [object, optional, object, optional]) { + expect(() => schema.parse({ a: 1 })).toThrow('Cannot specify both `message` and `error` params'); + } + }); }); diff --git a/libs/lazy-zod/src/internal-z.ts b/libs/lazy-zod/src/internal-z.ts index fad0779ba..0ad04ff86 100644 --- a/libs/lazy-zod/src/internal-z.ts +++ b/libs/lazy-zod/src/internal-z.ts @@ -2,37 +2,76 @@ import { z as realZ, type ZodTypeAny } from 'zod'; import { HEAVY_FACTORIES, LEAF_FACTORIES } from './factories'; import { LAZY_TARGET, LazyZodSchema, wrapLazy } from './lazy-schema'; -import { createLazyZ, type StandInFactory, type z } from './lazy-z'; +import { createLazyZ, type SchemaFactory, type StandInFactory, type z } from './lazy-z'; import { withoutRecursionWalk } from './memoizer'; import { isLazy } from './utils'; const BUILT_WITH_PARENT: unique symbol = Symbol('builtWithParent'); -class InternalLazySchema extends LazyZodSchema { +const NO_ARGS: readonly unknown[] = Object.freeze([]); + +/** A stand-in keeps what it is built from in fields: a closure per stand-in would cost more than the stand-in. */ +abstract class InternalLazySchema extends LazyZodSchema { readonly [BUILT_WITH_PARENT]: boolean; + private args: readonly unknown[]; - constructor(factory: () => ZodTypeAny, builtWithParent: boolean) { - super(factory); + protected constructor(args: readonly unknown[], builtWithParent: boolean) { + super(InternalLazySchema.buildOnce); + this.args = args; this[BUILT_WITH_PARENT] = builtWithParent; } + private static buildOnce(this: InternalLazySchema): ZodTypeAny { + const built = this.buildWith(this.args); + this.args = NO_ARGS; + return built; + } + + protected abstract buildWith(args: readonly unknown[]): ZodTypeAny; + protected override build(factory: () => ZodTypeAny): ZodTypeAny { - return withoutRecursionWalk(factory); + return withoutRecursionWalk(() => factory.call(this)); } override chain(method: string, args: unknown[]): unknown { - return internalStandIn( - () => { - const real = this.materialize() as unknown as Record ZodTypeAny>; - return real[method](...args.map(withBuiltChildren)); - }, - this[BUILT_WITH_PARENT] && args.every(holdsOnlyLeaves), - ); + return wrapLazy(new ChainedStandIn(this, method, args, this[BUILT_WITH_PARENT] && args.every(holdsOnlyLeaves))); + } +} + +class FactoryStandIn extends InternalLazySchema { + constructor( + private readonly factory: SchemaFactory, + private readonly namespace: object, + args: readonly unknown[], + builtWithParent: boolean, + ) { + super(args, builtWithParent); + } + + protected buildWith(args: readonly unknown[]): ZodTypeAny { + return this.factory.apply(this.namespace, args.map(withBuiltChildren)); } } -function internalStandIn(build: () => ZodTypeAny, builtWithParent: boolean): unknown { - return wrapLazy(new InternalLazySchema(build, builtWithParent)); +class ChainedStandIn extends InternalLazySchema { + private parent: InternalLazySchema | undefined; + + constructor( + parent: InternalLazySchema, + private readonly method: string, + args: readonly unknown[], + builtWithParent: boolean, + ) { + super(args, builtWithParent); + this.parent = parent; + } + + protected buildWith(args: readonly unknown[]): ZodTypeAny { + const real = (this.parent as InternalLazySchema).materialize() as unknown as Record; + const built = real[this.method](...args.map(withBuiltChildren)); + this.parent = undefined; + return built; + } } function targetOf(value: unknown): LazyZodSchema | undefined { @@ -85,8 +124,8 @@ function withBuiltChildren(arg: unknown): unknown { return changed ? Object.create(Object.getPrototypeOf(arg), descriptors) : arg; } -const deferEveryFactory: StandInFactory = (name, args, build) => - internalStandIn(() => build(args.map(withBuiltChildren)), LEAF_FACTORIES.has(name) && args.every(holdsOnlyLeaves)); +const deferEveryFactory: StandInFactory = (name, args, factory, namespace) => + wrapLazy(new FactoryStandIn(factory, namespace, args, LEAF_FACTORIES.has(name) && args.every(holdsOnlyLeaves))); /** FrontMCP's own `z`: every factory returns a stand-in that builds its leaf children with it. Not public API. */ export const internalZ: typeof realZ = createLazyZ( diff --git a/libs/lazy-zod/src/lazy-z.ts b/libs/lazy-zod/src/lazy-z.ts index 9268737bd..7abc7c317 100644 --- a/libs/lazy-zod/src/lazy-z.ts +++ b/libs/lazy-zod/src/lazy-z.ts @@ -19,9 +19,12 @@ import { LazyZodSchema, wrapLazy } from './lazy-schema'; * target owns nothing, so no invariant applies; every trap forwards to * `namespace` instead. Exported for tests — the public entry point is `z`. */ -export type StandInFactory = (name: string, args: unknown[], build: (args: unknown[]) => ZodTypeAny) => unknown; +export type SchemaFactory = (...args: unknown[]) => ZodTypeAny; -const compoundStandIn: StandInFactory = (_name, args, build) => wrapLazy(new LazyZodSchema(() => build(args))); +export type StandInFactory = (name: string, args: unknown[], factory: SchemaFactory, namespace: object) => unknown; + +const compoundStandIn: StandInFactory = (_name, args, factory, namespace) => + wrapLazy(new LazyZodSchema(() => factory.apply(namespace, args))); export function createLazyZ( namespace: typeof realZ, @@ -31,8 +34,8 @@ export function createLazyZ( return new Proxy({} as typeof realZ, { get(_target, key, receiver) { if (typeof key === 'string' && factories.has(key)) { - const realFactory = (namespace as unknown as Record ZodTypeAny>)[key]; - return (...args: unknown[]) => standIn(key, args, (built) => realFactory.apply(namespace, built)); + const realFactory = (namespace as unknown as Record)[key]; + return (...args: unknown[]) => standIn(key, args, realFactory, namespace); } return Reflect.get(namespace, key, receiver); },