Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
use microtasks for speedup
  • Loading branch information
mattzcarey committed Dec 23, 2025
commit 5039b1b5de37e0d27f6235827abcac882a6405d0
139 changes: 139 additions & 0 deletions 139 packages/zod/src/v4/classic/tests/cloudflare.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";

const state = vi.hoisted(() => {
Object.defineProperty(globalThis, "navigator", {
value: { userAgent: "Cloudflare-Workers" },
configurable: true,
writable: true,
});

const userAgent = typeof navigator === "undefined" ? undefined : navigator?.userAgent;
const isCloudflare = typeof userAgent === "string" && userAgent.includes("Cloudflare");

let _allowsEval: boolean;
try {
const F = Function;
new F("");
_allowsEval = true;
} catch (_) {
_allowsEval = false;
}

const valueBeforeMicrotask = _allowsEval;

if (isCloudflare && _allowsEval) {
Promise.resolve().then(() => {
_allowsEval = false;
});
}

return {
isCloudflare,
valueBeforeMicrotask,
getAllowsEval: () => _allowsEval,
};
});

describe("Cloudflare JIT microtask timing", () => {
test("allowsEval is true during sync, false after microtask", () => {
expect(state.isCloudflare).toBe(true);
expect(state.valueBeforeMicrotask).toBe(true);
expect(state.getAllowsEval()).toBe(false);
});

test("util.allowsEval is false after module load", async () => {
vi.resetModules();
const { allowsEval } = await import("../../core/util.js");
expect(allowsEval.value).toBe(false);
});
});

describe("Cloudflare allowsEval behavior", () => {
let navigatorDescriptor: PropertyDescriptor | undefined;

beforeEach(() => {
vi.resetModules();
navigatorDescriptor = Object.getOwnPropertyDescriptor(globalThis, "navigator");
Object.defineProperty(globalThis, "navigator", {
value: { userAgent: "Cloudflare-Workers" },
configurable: true,
writable: true,
});
});

afterEach(() => {
vi.unstubAllGlobals();
if (navigatorDescriptor) {
Object.defineProperty(globalThis, "navigator", navigatorDescriptor);
} else {
delete (globalThis as any).navigator;
}
vi.resetModules();
});

const loadAllowsEval = async () => {
const mod = await import("../../core/util.js");
return mod.allowsEval.value;
};

test("disables fast path after startup", async () => {
expect(await loadAllowsEval()).toBe(false);
});

test("caches value instead of re-evaluating", async () => {
const first = await loadAllowsEval();
expect(first).toBe(false);

vi.stubGlobal("Function", function ThrowingFunction() {
throw new Error("Function constructor disabled");
});

const second = await loadAllowsEval();
expect(second).toBe(first);
});

test("stays disabled when eval is disabled from start", async () => {
vi.stubGlobal("Function", function ThrowingFunction() {
throw new Error("Function constructor disabled");
});

vi.resetModules();
expect(await loadAllowsEval()).toBe(false);
});
});

describe("non-Cloudflare allowsEval behavior", () => {
beforeEach(() => {
vi.resetModules();
});

afterEach(() => {
vi.unstubAllGlobals();
vi.resetModules();
});

const loadAllowsEval = async () => {
const mod = await import("../../core/util.js");
return mod.allowsEval.value;
};

test("falls back when eval is disabled", async () => {
vi.stubGlobal("Function", function ThrowingFunction() {
throw new Error("Function constructor disabled");
});

vi.resetModules();
expect(await loadAllowsEval()).toBe(false);
});

test("caches value", async () => {
const first = await loadAllowsEval();

vi.stubGlobal("Function", function ThrowingFunction() {
throw new Error("Function constructor disabled");
});

const second = await loadAllowsEval();
expect(second).toBe(first);
});
});
93 changes: 1 addition & 92 deletions 93 packages/zod/src/v4/classic/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, expectTypeOf, test, vi } from "vitest";
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
import type { util } from "zod/v4/core";

Expand Down Expand Up @@ -818,97 +818,6 @@ test("isPlainObject", () => {
expect(z.core.util.isPlainObject({ constructor: [] })).toEqual(true);
});

const loadAllowsEvalValue = async () => {
const mod = await import("../../core/util.js");
return mod.allowsEval.value;
};

const loadAllowsEvalValueFresh = async () => {
vi.resetModules();
return loadAllowsEvalValue();
};

test("Cloudflare Workers can use fast path when eval is available", async () => {
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "navigator");
Object.defineProperty(globalThis, "navigator", {
value: { userAgent: "Cloudflare-Workers" },
configurable: true,
writable: true,
});

try {
const allowsEval = await loadAllowsEvalValueFresh();
expect(allowsEval).toEqual(true);
} finally {
if (descriptor) {
Object.defineProperty(globalThis, "navigator", descriptor);
} else {
delete (globalThis as any).navigator;
}
}
});

test("falls back gracefully when eval is disabled", async () => {
try {
vi.stubGlobal("Function", function ThrowingFunction() {
throw new Error("Function constructor disabled");
});

const allowsEval = await loadAllowsEvalValue();
expect(allowsEval).toEqual(false);
} finally {
vi.unstubAllGlobals();
}
});

test("allowsEval caches for non-Cloudflare environments", async () => {
vi.resetModules();
const first = await loadAllowsEvalValue();

try {
vi.stubGlobal("Function", function ThrowingFunction() {
throw new Error("Function constructor disabled");
});

const second = await loadAllowsEvalValue();
expect(second).toEqual(first);
} finally {
vi.unstubAllGlobals();
vi.resetModules();
}
});

test("Cloudflare user agent always re-evaluates Function availability", async () => {
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "navigator");
Object.defineProperty(globalThis, "navigator", {
value: { userAgent: "Cloudflare-Workers" },
configurable: true,
writable: true,
});

try {
const first = await loadAllowsEvalValue();

vi.stubGlobal("Function", function ThrowingFunction() {
throw new Error("Function constructor disabled");
});

const second = await loadAllowsEvalValue();
expect(second).not.toEqual(first);
expect(second).toEqual(false);
} finally {
vi.unstubAllGlobals();

if (descriptor) {
Object.defineProperty(globalThis, "navigator", descriptor);
} else {
delete (globalThis as any).navigator;
}

vi.resetModules();
}
});

test("shallowClone with constructor field", () => {
const objWithConstructor = { constructor: "string", key: "value" };
const cloned = z.core.util.shallowClone(objWithConstructor);
Expand Down
35 changes: 16 additions & 19 deletions 35 packages/zod/src/v4/core/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,30 +368,27 @@ export function isObject(data: any): data is Record<PropertyKey, unknown> {
return typeof data === "object" && data !== null && !Array.isArray(data);
}

const canUseFunction = () => {
try {
const F = Function;
new F("");
return true;
} catch (_) {
return false;
}
};

const cachedCanUseFunction = cached(canUseFunction);

const userAgent = typeof navigator === "undefined" ? undefined : navigator?.userAgent;
const isCloudflare = typeof userAgent === "string" && userAgent.includes("Cloudflare");

let _allowsEval: boolean;
try {
const F = Function;
new F("");
Copy link
Owner

Choose a reason for hiding this comment

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

It's problematic that this is eagerly attempted. It'll throw a warning/error in various environments. Zod currently avoids ever using the function constructor if z.config({ jitless: true }) has been set to avoid spamming users with warnings in these environments. You can still attempt this during module initialization but only when you have high confidence (based on navigator that the code is executing in Cloudflare.

Though more broadly—as I understand it new Function() is only allowed during module initialization, so this doesn't actually help. Zod uses new Function() inside getFastPass when an object schema is parsing for the first tiem. At that point we're out of module initialization so Zod won't be able to properly JIT] this anyway. Your benchmark should include an object parse operation - it would reveal this.

Thinking about this more—there's no guarantee that a Zod schema is defined top-level either so getting Zod JIT to work is just not really possible in the general case until new Function can be enabled throughout execution.

Let me know if I'm missing something. Maybe anonrig has thoughts.

Copy link
Author

@mattzcarey mattzcarey Dec 23, 2025

Choose a reason for hiding this comment

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

thanks, will take that on and have a think :)

Copy link
Author

Choose a reason for hiding this comment

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

Though more broadly—as I understand it new Function() is only allowed during module initialization, so this doesn't actually help. Zod uses new Function() inside getFastPass when an object schema is parsing for the first tiem. At that point we're out of module initialization so Zod won't be able to properly JIT] this anyway. Your benchmark should include an object parse operation - it would reveal this.

ahhh this is rough. thanks, explains Sunil's comment.

Copy link
Author

Choose a reason for hiding this comment

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

closing this :)

_allowsEval = true;
} catch (_) {
_allowsEval = false;
}

if (isCloudflare && _allowsEval) {
Promise.resolve().then(() => {
_allowsEval = false;
});
}

export const allowsEval: { readonly value: boolean } = {
get value() {
// Cloudflare allows using new Function in the module scope but not in the request scope
// There is no way to tell when the zod schema is being parsed so we have to check every time to be sure
if (isCloudflare) {
return canUseFunction();
}

return cachedCanUseFunction.value;
return _allowsEval;
},
};

Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.