+1
-1
| { | ||
| "name": "zod", | ||
| "version": "4.5.3", | ||
| "version": "4.5.4", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -679,1 +679,43 @@ import { expect, test } from "vitest"; | ||
| }); | ||
| test("detects a cycle reachable only through a merged catchall", () => { | ||
| const Cyclic: any = z.object({ | ||
| id: z.string(), | ||
| get next() { | ||
| return z.optional(Root); | ||
| }, | ||
| }); | ||
| const Root: any = z.object({ tag: z.string() }).merge(z.object({}).catchall(Cyclic)); | ||
| const input: any = { tag: "root" }; | ||
| input.child = { id: "1", next: input }; | ||
| const out: any = Root.parse(input); | ||
| expect(out.child.next).toBe(out); | ||
| }); | ||
| // the per-type switch can only enumerate kinds Zod ships, so an unknown `def.type` falls back to scanning the def; without it a cycle through a third-party container overflows the stack | ||
| test("detects a cycle through a user-defined container type", () => { | ||
| const MyBox: any = z.core.$constructor("MyBox", (inst: any, def: any) => { | ||
| z.core.$ZodType.init(inst, def); | ||
| inst._zod.parse = (payload: any, ctx: any) => { | ||
| if (payload.value === null || typeof payload.value !== "object") return payload; | ||
| const inner = def.inner._zod.run({ value: payload.value.v, issues: [] }, ctx); | ||
| payload.value = { v: inner.value }; | ||
| return payload; | ||
| }; | ||
| }); | ||
| const Node: any = z.object({ | ||
| id: z.string(), | ||
| get boxed() { | ||
| return z.optional(new MyBox({ type: "mybox", inner: z.lazy(() => Node) })); | ||
| }, | ||
| }); | ||
| const input: any = { id: "1" }; | ||
| input.boxed = { v: input }; | ||
| const out: any = Node.parse(input); | ||
| expect(out.boxed.v).toBe(out); | ||
| }); |
@@ -410,1 +410,47 @@ import { expect, expectTypeOf, test } from "vitest"; | ||
| }); | ||
| test("default factory runs once per parse inside a container", () => { | ||
| let calls = 0; | ||
| const schema = z.object({ | ||
| a: z.string().default(() => { | ||
| calls++; | ||
| return "d"; | ||
| }), | ||
| }); | ||
| expect(schema.parse({})).toEqual({ a: "d" }); | ||
| expect(calls).toBe(1); | ||
| // the key is present, so the factory has nothing to produce | ||
| expect(schema.parse({ a: "given" })).toEqual({ a: "given" }); | ||
| expect(calls).toBe(1); | ||
| }); | ||
| test("prefault factory runs once per parse inside a container", () => { | ||
| let calls = 0; | ||
| const schema = z.object({ | ||
| a: z.string().prefault(() => { | ||
| calls++; | ||
| return "d"; | ||
| }), | ||
| }); | ||
| expect(schema.parse({})).toEqual({ a: "d" }); | ||
| expect(calls).toBe(1); | ||
| }); | ||
| test("default factory does not run at compile time", () => { | ||
| let calls = 0; | ||
| const compiled = z.compile( | ||
| z.object({ | ||
| a: z.string().default(() => { | ||
| calls++; | ||
| return "d"; | ||
| }), | ||
| }) | ||
| ); | ||
| expect(calls).toBe(0); | ||
| expect(compiled.parse({})).toEqual({ a: "d" }); | ||
| expect(calls).toBe(1); | ||
| }); |
+89
-13
| import type * as errors from "./errors.js"; | ||
| import type { $ZodMemoizer, $ZodType, ParseContextInternal, ParsePayload } from "./schemas.js"; | ||
| import type { $ZodMemoizer, $ZodType, $ZodTypeDef, ParseContextInternal, ParsePayload } from "./schemas.js"; | ||
| import type * as util from "./util.js"; | ||
@@ -51,15 +51,91 @@ | ||
| const def = inst._zod.def as any; | ||
| if (def.type === "lazy") { | ||
| check((inst as any)._zod.innerType); | ||
| } else { | ||
| // $ZodObject redefines `shape` as a non-enumerable accessor, so `for...in` misses it. | ||
| const shape = def.shape; | ||
| // `for...in` skips symbols, so a cycle through a declared symbol key would read as non-recursive | ||
| if (shape) for (const key of Reflect.ownKeys(shape)) check(shape[key]); | ||
| for (const key in def) { | ||
| const value = def[key]; | ||
| if (!value || typeof value !== "object") continue; | ||
| if (value._zod) check(value); | ||
| else if (Array.isArray(value)) for (const el of value) check(el); | ||
| const kind = def.type as $ZodTypeDef["type"]; | ||
| switch (kind) { | ||
| case "object": { | ||
| // `Reflect.ownKeys` rather than `Object.keys`, so a cycle through a declared symbol key is still seen | ||
| for (const key of Reflect.ownKeys(def.shape)) check(def.shape[key]); | ||
| check(def.catchall); | ||
| break; | ||
| } | ||
| case "array": | ||
| check(def.element); | ||
| break; | ||
| case "tuple": | ||
| for (const el of def.items) check(el); | ||
| check(def.rest); | ||
| break; | ||
| case "record": | ||
| case "map": | ||
| check(def.keyType); | ||
| check(def.valueType); | ||
| break; | ||
| case "set": | ||
| check(def.valueType); | ||
| break; | ||
| case "union": | ||
| for (const el of def.options) check(el); | ||
| break; | ||
| case "intersection": | ||
| check(def.left); | ||
| check(def.right); | ||
| break; | ||
| case "optional": | ||
| case "nullable": | ||
| case "default": | ||
| case "prefault": | ||
| case "catch": | ||
| case "readonly": | ||
| case "nonoptional": | ||
| case "promise": | ||
| case "success": | ||
| check(def.innerType); | ||
| break; | ||
| case "pipe": | ||
| check(def.in); | ||
| check(def.out); | ||
| break; | ||
| case "function": | ||
| check(def.input); | ||
| check(def.output); | ||
| break; | ||
| // reading `_zod.innerType` resolves the getter once and caches it | ||
| case "lazy": | ||
| check((inst as any)._zod.innerType); | ||
| break; | ||
| // a leaf by choice: `parts` are regex fragments, not data positions | ||
| case "template_literal": | ||
| // leaves | ||
| case "string": | ||
| case "number": | ||
| case "int": | ||
| case "boolean": | ||
| case "bigint": | ||
| case "symbol": | ||
| case "undefined": | ||
| case "null": | ||
| case "void": | ||
| case "never": | ||
| case "any": | ||
| case "unknown": | ||
| case "date": | ||
| case "nan": | ||
| case "enum": | ||
| case "literal": | ||
| case "file": | ||
| case "transform": | ||
| case "custom": | ||
| break; | ||
| default: { | ||
| // a new built-in kind becomes a compile error here | ||
| kind satisfies never; | ||
| // a user-defined kind can still hold children, and only its author knows where, so fall back to scanning the def — skipping accessors, since reading one can run user code | ||
| for (const key in def) { | ||
| const desc = Object.getOwnPropertyDescriptor(def, key); | ||
| if (!desc || desc.get) continue; | ||
| const value = desc.value; | ||
| if (!value || typeof value !== "object") continue; | ||
| if (value._zod) check(value); | ||
| else if (Array.isArray(value)) for (const el of value) check(el); | ||
| } | ||
| } | ||
| } | ||
@@ -66,0 +142,0 @@ |
| export const version = { | ||
| major: 4, | ||
| minor: 5, | ||
| patch: 3 as number, | ||
| patch: 4 as number, | ||
| } as const; |
+96
-19
@@ -37,22 +37,99 @@ "use strict"; | ||
| const def = inst._zod.def; | ||
| if (def.type === "lazy") { | ||
| check(inst._zod.innerType); | ||
| } | ||
| else { | ||
| // $ZodObject redefines `shape` as a non-enumerable accessor, so `for...in` misses it. | ||
| const shape = def.shape; | ||
| // `for...in` skips symbols, so a cycle through a declared symbol key would read as non-recursive | ||
| if (shape) | ||
| for (const key of Reflect.ownKeys(shape)) | ||
| check(shape[key]); | ||
| for (const key in def) { | ||
| const value = def[key]; | ||
| if (!value || typeof value !== "object") | ||
| continue; | ||
| if (value._zod) | ||
| check(value); | ||
| else if (Array.isArray(value)) | ||
| for (const el of value) | ||
| check(el); | ||
| const kind = def.type; | ||
| switch (kind) { | ||
| case "object": { | ||
| // `Reflect.ownKeys` rather than `Object.keys`, so a cycle through a declared symbol key is still seen | ||
| for (const key of Reflect.ownKeys(def.shape)) | ||
| check(def.shape[key]); | ||
| check(def.catchall); | ||
| break; | ||
| } | ||
| case "array": | ||
| check(def.element); | ||
| break; | ||
| case "tuple": | ||
| for (const el of def.items) | ||
| check(el); | ||
| check(def.rest); | ||
| break; | ||
| case "record": | ||
| case "map": | ||
| check(def.keyType); | ||
| check(def.valueType); | ||
| break; | ||
| case "set": | ||
| check(def.valueType); | ||
| break; | ||
| case "union": | ||
| for (const el of def.options) | ||
| check(el); | ||
| break; | ||
| case "intersection": | ||
| check(def.left); | ||
| check(def.right); | ||
| break; | ||
| case "optional": | ||
| case "nullable": | ||
| case "default": | ||
| case "prefault": | ||
| case "catch": | ||
| case "readonly": | ||
| case "nonoptional": | ||
| case "promise": | ||
| case "success": | ||
| check(def.innerType); | ||
| break; | ||
| case "pipe": | ||
| check(def.in); | ||
| check(def.out); | ||
| break; | ||
| case "function": | ||
| check(def.input); | ||
| check(def.output); | ||
| break; | ||
| // reading `_zod.innerType` resolves the getter once and caches it | ||
| case "lazy": | ||
| check(inst._zod.innerType); | ||
| break; | ||
| // a leaf by choice: `parts` are regex fragments, not data positions | ||
| case "template_literal": | ||
| // leaves | ||
| case "string": | ||
| case "number": | ||
| case "int": | ||
| case "boolean": | ||
| case "bigint": | ||
| case "symbol": | ||
| case "undefined": | ||
| case "null": | ||
| case "void": | ||
| case "never": | ||
| case "any": | ||
| case "unknown": | ||
| case "date": | ||
| case "nan": | ||
| case "enum": | ||
| case "literal": | ||
| case "file": | ||
| case "transform": | ||
| case "custom": | ||
| break; | ||
| default: { | ||
| // a new built-in kind becomes a compile error here | ||
| kind; | ||
| // a user-defined kind can still hold children, and only its author knows where, so fall back to scanning the def — skipping accessors, since reading one can run user code | ||
| for (const key in def) { | ||
| const desc = Object.getOwnPropertyDescriptor(def, key); | ||
| if (!desc || desc.get) | ||
| continue; | ||
| const value = desc.value; | ||
| if (!value || typeof value !== "object") | ||
| continue; | ||
| if (value._zod) | ||
| check(value); | ||
| else if (Array.isArray(value)) | ||
| for (const el of value) | ||
| check(el); | ||
| } | ||
| } | ||
| } | ||
@@ -59,0 +136,0 @@ stack.delete(inst); |
+96
-19
@@ -30,22 +30,99 @@ export class $ZodCyclicError extends Error { | ||
| const def = inst._zod.def; | ||
| if (def.type === "lazy") { | ||
| check(inst._zod.innerType); | ||
| } | ||
| else { | ||
| // $ZodObject redefines `shape` as a non-enumerable accessor, so `for...in` misses it. | ||
| const shape = def.shape; | ||
| // `for...in` skips symbols, so a cycle through a declared symbol key would read as non-recursive | ||
| if (shape) | ||
| for (const key of Reflect.ownKeys(shape)) | ||
| check(shape[key]); | ||
| for (const key in def) { | ||
| const value = def[key]; | ||
| if (!value || typeof value !== "object") | ||
| continue; | ||
| if (value._zod) | ||
| check(value); | ||
| else if (Array.isArray(value)) | ||
| for (const el of value) | ||
| check(el); | ||
| const kind = def.type; | ||
| switch (kind) { | ||
| case "object": { | ||
| // `Reflect.ownKeys` rather than `Object.keys`, so a cycle through a declared symbol key is still seen | ||
| for (const key of Reflect.ownKeys(def.shape)) | ||
| check(def.shape[key]); | ||
| check(def.catchall); | ||
| break; | ||
| } | ||
| case "array": | ||
| check(def.element); | ||
| break; | ||
| case "tuple": | ||
| for (const el of def.items) | ||
| check(el); | ||
| check(def.rest); | ||
| break; | ||
| case "record": | ||
| case "map": | ||
| check(def.keyType); | ||
| check(def.valueType); | ||
| break; | ||
| case "set": | ||
| check(def.valueType); | ||
| break; | ||
| case "union": | ||
| for (const el of def.options) | ||
| check(el); | ||
| break; | ||
| case "intersection": | ||
| check(def.left); | ||
| check(def.right); | ||
| break; | ||
| case "optional": | ||
| case "nullable": | ||
| case "default": | ||
| case "prefault": | ||
| case "catch": | ||
| case "readonly": | ||
| case "nonoptional": | ||
| case "promise": | ||
| case "success": | ||
| check(def.innerType); | ||
| break; | ||
| case "pipe": | ||
| check(def.in); | ||
| check(def.out); | ||
| break; | ||
| case "function": | ||
| check(def.input); | ||
| check(def.output); | ||
| break; | ||
| // reading `_zod.innerType` resolves the getter once and caches it | ||
| case "lazy": | ||
| check(inst._zod.innerType); | ||
| break; | ||
| // a leaf by choice: `parts` are regex fragments, not data positions | ||
| case "template_literal": | ||
| // leaves | ||
| case "string": | ||
| case "number": | ||
| case "int": | ||
| case "boolean": | ||
| case "bigint": | ||
| case "symbol": | ||
| case "undefined": | ||
| case "null": | ||
| case "void": | ||
| case "never": | ||
| case "any": | ||
| case "unknown": | ||
| case "date": | ||
| case "nan": | ||
| case "enum": | ||
| case "literal": | ||
| case "file": | ||
| case "transform": | ||
| case "custom": | ||
| break; | ||
| default: { | ||
| // a new built-in kind becomes a compile error here | ||
| kind; | ||
| // a user-defined kind can still hold children, and only its author knows where, so fall back to scanning the def — skipping accessors, since reading one can run user code | ||
| for (const key in def) { | ||
| const desc = Object.getOwnPropertyDescriptor(def, key); | ||
| if (!desc || desc.get) | ||
| continue; | ||
| const value = desc.value; | ||
| if (!value || typeof value !== "object") | ||
| continue; | ||
| if (value._zod) | ||
| check(value); | ||
| else if (Array.isArray(value)) | ||
| for (const el of value) | ||
| check(el); | ||
| } | ||
| } | ||
| } | ||
@@ -52,0 +129,0 @@ stack.delete(inst); |
@@ -7,3 +7,3 @@ "use strict"; | ||
| minor: 5, | ||
| patch: 3, | ||
| patch: 4, | ||
| }; |
| export const version = { | ||
| major: 4, | ||
| minor: 5, | ||
| patch: 3, | ||
| patch: 4, | ||
| }; |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 3 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 3 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
5797652
0.15%134216
0.23%