From 0f2014063d15dc6325d859d28c2d0628f6bfb29b Mon Sep 17 00:00:00 2001 From: jussisaurio Date: Sun, 12 May 2024 18:44:30 +0300 Subject: [PATCH] Detect promises in parsing instead of using ctx.async --- deno/lib/benchmarks/benchUtil.ts | 25 +-- deno/lib/benchmarks/string.ts | 55 ------ deno/lib/helpers/parseUtil.ts | 2 - deno/lib/types.ts | 290 ++++++++++++++++-------------- src/benchmarks/string.mts | 14 +- src/helpers/parseUtil.ts | 2 - src/types.ts | 296 +++++++++++++++++-------------- 7 files changed, 333 insertions(+), 351 deletions(-) delete mode 100644 deno/lib/benchmarks/string.ts diff --git a/deno/lib/benchmarks/benchUtil.ts b/deno/lib/benchmarks/benchUtil.ts index 0f2667440..6f30fa522 100644 --- a/deno/lib/benchmarks/benchUtil.ts +++ b/deno/lib/benchmarks/benchUtil.ts @@ -1,7 +1,6 @@ import { Bench } from "tinybench"; // @ts-ignore import "console.table"; -import { Table, printTable } from "console-table-printer"; function formatNumber(val: number) { if (val >= 1e12) { @@ -40,31 +39,25 @@ export function log(name: string, bench: Bench) { // } const sorted = bench.tasks.sort((a, b) => a.result!.mean - b.result!.mean); + const data: any[] = []; const fastest = sorted[0]; - // console.log(`benchmarking ${name}...`); - const table = new Table({ - columns: [ - { name: "name" }, - { name: "comp", alignment: "left" }, - { name: "ops/sec", color: "yellow" }, - { name: "mean", color: "yellow" }, - ], - }); + console.log(`benchmarking ${name}...`); for (const task of sorted) { - table.addRow({ + data.push({ + // winner: task === sorted[0] ? "⚡️" : "", + place: "#" + (sorted.indexOf(task) + 1), name: task.name, comp: task === sorted[0] - ? "🥇" + ? "winner" : (task.result!.mean / fastest.result!.mean).toFixed(2) + `x slower than ${fastest.name}`, "ops/sec": formatNumber(1 / (task.result!.mean / 1000)) + " ops/sec", - mean: formatNumber(task.result!.mean / 1000) + "sec", // margin: "±" + (task.result!.moe * 1000000).toFixed(2) + "μs", + + mean: formatNumber(task.result!.mean / 1000) + "sec", }); } - table.printTable(); - // printTable(table); - // console.table(data); + console.table(data); } diff --git a/deno/lib/benchmarks/string.ts b/deno/lib/benchmarks/string.ts deleted file mode 100644 index 7278eaf8f..000000000 --- a/deno/lib/benchmarks/string.ts +++ /dev/null @@ -1,55 +0,0 @@ -import Benchmark from "benchmark"; - -import { z } from "../index.ts"; - -const SUITE_NAME = "z.string"; -const suite = new Benchmark.Suite(SUITE_NAME); - -const empty = ""; -const short = "short"; -const long = "long".repeat(256); -const manual = (str: unknown) => { - if (typeof str !== "string") { - throw new Error("Not a string"); - } - - return str; -}; -const stringSchema = z.string(); -const optionalStringSchema = z.string().optional(); -const optionalNullableStringSchema = z.string().optional().nullable(); - -suite - .add("empty string", () => { - stringSchema.parse(empty); - }) - .add("short string", () => { - stringSchema.parse(short); - }) - .add("long string", () => { - stringSchema.parse(long); - }) - .add("optional string", () => { - optionalStringSchema.parse(long); - }) - .add("nullable string", () => { - optionalNullableStringSchema.parse(long); - }) - .add("nullable (null) string", () => { - optionalNullableStringSchema.parse(null); - }) - .add("invalid: null", () => { - try { - stringSchema.parse(null); - } catch (err) {} - }) - .add("manual parser: long", () => { - manual(long); - }) - .on("cycle", (e: Benchmark.Event) => { - console.log(`${SUITE_NAME}: ${e.target}`); - }); - -export default { - suites: [suite], -}; diff --git a/deno/lib/helpers/parseUtil.ts b/deno/lib/helpers/parseUtil.ts index 6d5de2b09..b59e02d1a 100644 --- a/deno/lib/helpers/parseUtil.ts +++ b/deno/lib/helpers/parseUtil.ts @@ -50,7 +50,6 @@ export const makeIssue = ( export type ParseParams = { path: (string | number)[]; errorMap: ZodErrorMap; - async: boolean; }; export type ParsePathComponent = string | number; @@ -59,7 +58,6 @@ export type ParsePath = ParsePathComponent[]; export interface ParseContext { readonly contextualErrorMap?: ZodErrorMap; readonly basePath: ParsePath; - readonly async: boolean; readonly schemaErrorMap?: ZodErrorMap; } diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 586a2c878..38b4c853d 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -157,39 +157,21 @@ export abstract class ZodType< return getParsedType(input); } - _parseSync( - input: ParseInput, - ctx?: ParseContext - ): SyncParseReturnType { - const result = this._parse(input, ctx); - if (isAsync(result)) { - throw new Error("Synchronous parse encountered promise."); - } - return result; - } - - _parseAsync( - input: ParseInput, - ctx?: ParseContext - ): AsyncParseReturnType { - const result = this._parse(input, ctx); - return isAsync(result) ? result : Promise.resolve(result); - } - parse(data: unknown, params?: Partial): Output { if (!params) { - const result = this._parseSync(data, this.defaultSyncContext); + const result = this._parse(data, this.defaultSyncContext); + if (result instanceof Promise) throw Error("Synchronous parse encountered promise."); if (isAborted(result)) throw issuesToZodError(this.defaultSyncContext, result.issues); return result as any; } const ctx: ParseContext = { contextualErrorMap: params?.errorMap, - async: params?.async ?? false, basePath: params?.path || [], schemaErrorMap: this._def.errorMap, }; - const result = this._parseSync(data, ctx); + const result = this._parse(data, ctx); + if (result instanceof Promise) throw Error("Synchronous parse encountered promise."); if (isAborted(result)) throw issuesToZodError(ctx, result.issues); return result as any; } @@ -199,16 +181,17 @@ export abstract class ZodType< params?: Partial ): SafeParseReturnType { if (!params) { - const result = this._parseSync(data, this.defaultSyncContext); + const result = this._parse(data, this.defaultSyncContext); + if (result instanceof Promise) throw Error("Synchronous parse encountered promise."); return safeResult(this.defaultSyncContext, result) as any; } const ctx: ParseContext = { contextualErrorMap: params?.errorMap, - async: params?.async ?? false, basePath: params?.path || [], schemaErrorMap: this._def.errorMap, }; - const result = this._parseSync(data, ctx); + const result = this._parse(data, ctx); + if (result instanceof Promise) throw Error("Synchronous parse encountered promise."); return safeResult(ctx, result) as any; } @@ -217,18 +200,17 @@ export abstract class ZodType< params?: Partial ): Promise { if (!params) { - const result = await this._parseAsync(data, this.defaultAsyncContext); + const result = await this._parse(data, this.defaultAsyncContext); if (isAborted(result)) throw issuesToZodError(this.defaultAsyncContext, result.issues); return result; } const ctx: ParseContext = { contextualErrorMap: params?.errorMap, - async: true, basePath: params?.path || [], schemaErrorMap: this._def.errorMap, }; - const result = await this._parseAsync(data, ctx); + const result = await this._parse(data, ctx); if (isAborted(result)) throw issuesToZodError(ctx, result.issues); return result; } @@ -238,17 +220,16 @@ export abstract class ZodType< params?: Partial ): Promise> { if (!params) { - const result = await this._parseAsync(data, this.defaultAsyncContext); + const result = await this._parse(data, this.defaultAsyncContext); return safeResult(this.defaultAsyncContext, result); } const ctx: ParseContext = { contextualErrorMap: params?.errorMap, - async: true, basePath: params?.path || [], schemaErrorMap: this._def.errorMap, }; - const result = await this._parseAsync(data, ctx); + const result = await this._parse(data, ctx); return safeResult(ctx, result); } @@ -2469,12 +2450,18 @@ export class ZodArray< } } - if (ctx.async) { - return Promise.all( - (input as any[]).map((item) => { - return def.type._parseAsync(item, ctx); - }) - ).then((result) => { + let hasPromises = false; + + const parseResults = [...(input as any[])].map((item) => { + const result = def.type._parse(item, ctx); + if (result instanceof Promise) { + hasPromises = true; + } + return result; + }); + + if (hasPromises) { + return Promise.all(parseResults).then((result) => { issues.push( ...result.flatMap((r, i) => isAborted(r) @@ -2494,18 +2481,16 @@ export class ZodArray< }); } - const result = ([...input] as any[]).map((item) => { - return def.type._parseSync(item, ctx); - }); + const results = parseResults as SyncParseReturnType[]; // we know it's sync because hasPromises is false issues.push( - ...result.flatMap((r, i) => - isAborted(r) - ? r.issues.map((issue) => ({ + ...results.flatMap((r, i) => + !isAborted(r) + ? [] + : r.issues.map((issue) => ({ ...issue, path: [i, ...(issue.path || [])], })) - : [] ) ); @@ -2513,7 +2498,7 @@ export class ZodArray< return new ZodFailure(issues); } - return OK(result.map((x) => x as any) as any); + return OK(results.map((x) => x as any) as any); } get element() { @@ -2840,7 +2825,7 @@ export class ZodObject< return new ZodFailure(issues); } - return ctx.async ? Promise.resolve(OK(final)) : OK(final); + return OK(final); } get shape() { @@ -3277,19 +3262,21 @@ export class ZodUnion extends ZodType< ]); } - if (ctx.async) { - return Promise.all( - options.map(async (option) => { - const result = await option._parseAsync(input, ctx); + let hasPromises = false; + const parseResults = options.map((option) => { + const result = option._parse(input, ctx); + if (result instanceof Promise) { + hasPromises = true; + } + return result; + }); - return result; - }) - ).then(handleResults); + if (hasPromises) { + return Promise.all(parseResults).then(handleResults); } else { const issues: ZodIssue[][] = []; - for (const option of options) { - const result = option._parseSync(input, ctx); - + for (const result of parseResults as SyncParseReturnType[]) { + // we know it's sync because hasPromises is false if (!isAborted(result)) { return result; } @@ -3425,11 +3412,7 @@ export class ZodDiscriminatedUnion< ]); } - if (ctx.async) { - return option._parseAsync(input, ctx) as any; - } else { - return option._parseSync(input, ctx) as any; - } + return option._parse(input, ctx) as any; } get discriminator() { @@ -3617,15 +3600,23 @@ export class ZodIntersection< return OK(merged.data); }; - if (ctx.async) { - return Promise.all([ - this._def.left._parseAsync(input, ctx), - this._def.right._parseAsync(input, ctx), - ]).then(([left, right]) => handleParsed(left, right)); + const parseResults = [ + this._def.left._parse(input, ctx), + this._def.right._parse(input, ctx), + ]; + + const hasPromises = parseResults.some( + (result) => result instanceof Promise + ); + + if (hasPromises) { + return Promise.all(parseResults).then(([left, right]) => + handleParsed(left, right) + ); } else { return handleParsed( - this._def.left._parseSync(input, ctx), - this._def.right._parseSync(input, ctx) + parseResults[0] as SyncParseReturnType, + parseResults[1] as SyncParseReturnType ); } } @@ -3738,15 +3729,22 @@ export class ZodTuple< }); } + let hasPromises = false; + const items = ([...input] as any[]) .map((item, itemIndex) => { const schema = this._def.items[itemIndex] || this._def.rest; if (!schema) return NOT_SET as any as SyncParseReturnType; - return schema._parse(item, ctx); + const result = schema._parse(item, ctx); + if (result instanceof Promise) { + hasPromises = true; + } + + return result; }) .filter((x) => x !== NOT_SET); // filter nulls - if (ctx.async) { + if (hasPromises) { return Promise.all(items).then((results) => { issues.push( ...results.flatMap((r, i) => @@ -3948,13 +3946,9 @@ export class ZodRecord< }); } else { if (issues.length) { - return ctx.async - ? Promise.resolve(new ZodFailure(issues)) - : new ZodFailure(issues); + return new ZodFailure(issues); } - return ctx.async - ? Promise.resolve(OK(final as this["_output"])) - : OK(final as this["_output"]); + return OK(final as this["_output"]); } } @@ -4117,12 +4111,10 @@ export class ZodMap< }); } else { if (issues.length) { - return ctx.async - ? Promise.resolve(new ZodFailure(issues)) - : new ZodFailure(issues); + return new ZodFailure(issues); } - return ctx.async ? Promise.resolve(OK(final)) : OK(final); + return OK(final); } } static create< @@ -4235,11 +4227,17 @@ export class ZodSet extends ZodType< return OK(parsedSet); } - const elements = [...(input as Set).values()].map((item) => - valueType._parse(item, ctx) - ); + let hasPromises = false; - if (ctx.async) { + const elements = [...(input as Set).values()].map((item) => { + const result = valueType._parse(item, ctx); + if (result instanceof Promise) { + hasPromises = true; + } + return result; + }); + + if (hasPromises) { return Promise.all(elements).then(finalizeSet); } else { return finalizeSet(elements as SyncParseReturnType[]); @@ -5086,11 +5084,8 @@ export class ZodPromise extends ZodType< ]); } - const promisified = - parsedType === ZodParsedType.promise ? input : Promise.resolve(input); - - return promisified.then((data: any) => { - return this._def.type._parse(data, ctx); + return input.then((inner: any) => { + return this._def.type._parse(inner, ctx); }); } @@ -5178,12 +5173,22 @@ export class ZodEffects< if (effect.type === "preprocess") { const processed = effect.transform(input, checkCtx); - if (ctx.async) { - return Promise.resolve(processed).then(async (processed) => { + if (processed instanceof Promise) { + return processed.then((processed) => { if (issues.some((i) => i.fatal)) { return new ZodFailure(issues); } - const result = await this._def.schema._parseAsync(processed, ctx); + const result = this._def.schema._parse(processed, ctx); + if (result instanceof Promise) { + return result.then((r) => { + if (isAborted(r)) { + issues.push(...r.issues); + } + if (issues.length) return new ZodFailure(issues); + return r; + }); + } + if (isAborted(result)) { issues.push(...result.issues); return new ZodFailure(issues); @@ -5195,7 +5200,18 @@ export class ZodEffects< if (issues.some((i) => i.fatal)) { return new ZodFailure(issues); } - const result = this._def.schema._parseSync(processed, ctx); + const result = this._def.schema._parse(processed, ctx); + + if (result instanceof Promise) { + return result.then((r) => { + if (isAborted(r)) { + issues.push(...r.issues); + } + if (issues.length) return new ZodFailure(issues); + return r; + }); + } + if (isAborted(result)) { issues.push(...result.issues); return new ZodFailure(issues); @@ -5207,19 +5223,15 @@ export class ZodEffects< if (effect.type === "refinement") { const executeRefinement = (acc: unknown): any => { const result = effect.refinement(acc, checkCtx); - if (ctx.async) { - return Promise.resolve(result); - } if (result instanceof Promise) { - throw new Error( - "Async refinement encountered during synchronous parse operation. Use .parseAsync instead." - ); + return Promise.resolve(result); } return acc; }; - if (ctx.async === false) { - const inner = this._def.schema._parseSync(input, ctx); + const inner = this._def.schema._parse(input, ctx); + + if (!(inner instanceof Promise)) { if (isAborted(inner)) { issues.push(...inner.issues); } @@ -5236,13 +5248,19 @@ export class ZodEffects< } // return value is ignored - executeRefinement(value); + const executed = executeRefinement(value); - if (issues.length) return new ZodFailure(issues, value); + if (executed instanceof Promise) { + return executed.then(() => { + if (issues.length) return new ZodFailure(issues); + return inner; + }) as any; + } + if (issues.length) return new ZodFailure(issues); return inner as any; } else { - return this._def.schema._parseAsync(input, ctx).then((inner) => { + return inner.then((inner) => { if (isAborted(inner)) { issues.push(...inner.issues); } @@ -5259,18 +5277,24 @@ export class ZodEffects< : input // if valid, use parsed value : inner; - return executeRefinement(value).then(() => { - if (issues.length) return new ZodFailure(issues, value); - return inner; - }); + const executed = executeRefinement(value); + + if (executed instanceof Promise) { + return executed.then(() => { + if (issues.length) return new ZodFailure(issues); + return inner; + }); + } + + if (issues.length) return new ZodFailure(issues); + return inner; }); } } if (effect.type === "transform") { - if (!ctx.async) { - const base = this._def.schema._parseSync(input, ctx); - + const base = this._def.schema._parse(input, ctx); + if (!(base instanceof Promise)) { if (isAborted(base)) { issues.push(...base.issues); } @@ -5282,18 +5306,16 @@ export class ZodEffects< const result = effect.transform(baseValid, checkCtx); if (result instanceof Promise) { - throw new Error( - `Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.` - ); - } - - if (issues.length) { - return new ZodFailure(issues, result); + return result.then((result) => { + if (issues.length) return new ZodFailure(issues); + return OK(result); + }); } + if (issues.length) return new ZodFailure(issues); return OK(result); } else { - return this._def.schema._parseAsync(input, ctx).then((base) => { + return base.then((base) => { if (isAborted(base)) { issues.push(...base.issues); } @@ -5302,13 +5324,17 @@ export class ZodEffects< const baseValid = base as OK; - return Promise.resolve(effect.transform(baseValid, checkCtx)).then( - (result) => { - if (issues.length) return new ZodFailure(issues, result); + const result = effect.transform(baseValid.value, checkCtx); + if (result instanceof Promise) { + return result.then((result) => { + if (issues.length) return new ZodFailure(issues); return OK(result); - } - ); + }); + } + + if (issues.length) return new ZodFailure(issues); + return OK(result); }); } } @@ -5656,19 +5682,17 @@ export class ZodPipeline< B extends ZodTypeAny > extends ZodType, A["_input"]> { _parse(input: ParseInput, ctx: ParseContext): ParseReturnType { - if (ctx.async) { - const handleAsync = async () => { - const inResult = await this._def.in._parseAsync(input, ctx); + const result = this._def.in._parse(input, ctx); + if (result instanceof Promise) { + return result.then((inResult) => { if (isAborted(inResult)) return inResult; - return this._def.out._parseAsync(inResult, ctx); - }; - return handleAsync(); + return this._def.out._parse(inResult.value, ctx); + }); } else { - const inResult = this._def.in._parseSync(input, ctx); - if (isAborted(inResult)) return inResult; + if (isAborted(result)) return result; - return this._def.out._parseSync(inResult, ctx); + return this._def.out._parse(result, ctx); } } diff --git a/src/benchmarks/string.mts b/src/benchmarks/string.mts index 1daf4e3a2..3ab5f08b1 100644 --- a/src/benchmarks/string.mts +++ b/src/benchmarks/string.mts @@ -7,19 +7,19 @@ import zNew from "../../src"; // import z from ".."; const SUITE_NAME = "z.string"; -const suite = new Benchmark.Suite(SUITE_NAME); +new Benchmark.Suite(SUITE_NAME); const empty = ""; const short = "short"; const long = "long".repeat(256); -const baseline = (str: unknown) => { - if (typeof str !== "string") { - throw new Error("Not a string"); - } +// const baseline = (str: unknown) => { +// if (typeof str !== "string") { +// throw new Error("Not a string"); +// } - return str; -}; +// return str; +// }; const olds = { string: zOld.string(), diff --git a/src/helpers/parseUtil.ts b/src/helpers/parseUtil.ts index dc62cab45..03980c03c 100644 --- a/src/helpers/parseUtil.ts +++ b/src/helpers/parseUtil.ts @@ -50,7 +50,6 @@ export const makeIssue = ( export type ParseParams = { path: (string | number)[]; errorMap: ZodErrorMap; - async: boolean; }; export type ParsePathComponent = string | number; @@ -59,7 +58,6 @@ export type ParsePath = ParsePathComponent[]; export interface ParseContext { readonly contextualErrorMap?: ZodErrorMap; readonly basePath: ParsePath; - readonly async: boolean; readonly schemaErrorMap?: ZodErrorMap; } diff --git a/src/types.ts b/src/types.ts index af5e87cc2..02dcfa7c3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -157,39 +157,21 @@ export abstract class ZodType< return getParsedType(input); } - _parseSync( - input: ParseInput, - ctx?: ParseContext - ): SyncParseReturnType { - const result = this._parse(input, ctx); - if (isAsync(result)) { - throw new Error("Synchronous parse encountered promise."); - } - return result; - } - - _parseAsync( - input: ParseInput, - ctx?: ParseContext - ): AsyncParseReturnType { - const result = this._parse(input, ctx); - return isAsync(result) ? result : Promise.resolve(result); - } - parse(data: unknown, params?: Partial): Output { if (!params) { - const result = this._parseSync(data, this.defaultSyncContext); + const result = this._parse(data, this.defaultSyncContext); + if (result instanceof Promise) throw Error("Synchronous parse encountered promise."); if (isAborted(result)) throw issuesToZodError(this.defaultSyncContext, result.issues); return result as any; } const ctx: ParseContext = { contextualErrorMap: params?.errorMap, - async: params?.async ?? false, basePath: params?.path || [], schemaErrorMap: this._def.errorMap, }; - const result = this._parseSync(data, ctx); + const result = this._parse(data, ctx); + if (result instanceof Promise) throw Error("Synchronous parse encountered promise."); if (isAborted(result)) throw issuesToZodError(ctx, result.issues); return result as any; } @@ -199,16 +181,17 @@ export abstract class ZodType< params?: Partial ): SafeParseReturnType { if (!params) { - const result = this._parseSync(data, this.defaultSyncContext); + const result = this._parse(data, this.defaultSyncContext); + if (result instanceof Promise) throw Error("Synchronous parse encountered promise."); return safeResult(this.defaultSyncContext, result) as any; } const ctx: ParseContext = { contextualErrorMap: params?.errorMap, - async: params?.async ?? false, basePath: params?.path || [], schemaErrorMap: this._def.errorMap, }; - const result = this._parseSync(data, ctx); + const result = this._parse(data, ctx); + if (result instanceof Promise) throw Error("Synchronous parse encountered promise."); return safeResult(ctx, result) as any; } @@ -217,18 +200,17 @@ export abstract class ZodType< params?: Partial ): Promise { if (!params) { - const result = await this._parseAsync(data, this.defaultAsyncContext); + const result = await this._parse(data, this.defaultAsyncContext); if (isAborted(result)) throw issuesToZodError(this.defaultAsyncContext, result.issues); return result; } const ctx: ParseContext = { contextualErrorMap: params?.errorMap, - async: true, basePath: params?.path || [], schemaErrorMap: this._def.errorMap, }; - const result = await this._parseAsync(data, ctx); + const result = await this._parse(data, ctx); if (isAborted(result)) throw issuesToZodError(ctx, result.issues); return result; } @@ -238,17 +220,16 @@ export abstract class ZodType< params?: Partial ): Promise> { if (!params) { - const result = await this._parseAsync(data, this.defaultAsyncContext); + const result = await this._parse(data, this.defaultAsyncContext); return safeResult(this.defaultAsyncContext, result); } const ctx: ParseContext = { contextualErrorMap: params?.errorMap, - async: true, basePath: params?.path || [], schemaErrorMap: this._def.errorMap, }; - const result = await this._parseAsync(data, ctx); + const result = await this._parse(data, ctx); return safeResult(ctx, result); } @@ -2469,12 +2450,18 @@ export class ZodArray< } } - if (ctx.async) { - return Promise.all( - (input as any[]).map((item) => { - return def.type._parseAsync(item, ctx); - }) - ).then((result) => { + let hasPromises = false; + + const parseResults = [...(input as any[])].map((item) => { + const result = def.type._parse(item, ctx); + if (result instanceof Promise) { + hasPromises = true; + } + return result; + }); + + if (hasPromises) { + return Promise.all(parseResults).then((result) => { issues.push( ...result.flatMap((r, i) => isAborted(r) @@ -2494,18 +2481,16 @@ export class ZodArray< }); } - const result = ([...input] as any[]).map((item) => { - return def.type._parseSync(item, ctx); - }); + const results = parseResults as SyncParseReturnType[]; // we know it's sync because hasPromises is false issues.push( - ...result.flatMap((r, i) => - isAborted(r) - ? r.issues.map((issue) => ({ + ...results.flatMap((r, i) => + !isAborted(r) + ? [] + : r.issues.map((issue) => ({ ...issue, path: [i, ...(issue.path || [])], })) - : [] ) ); @@ -2513,7 +2498,7 @@ export class ZodArray< return new ZodFailure(issues); } - return OK(result.map((x) => x as any) as any); + return OK(results.map((x) => x as any) as any); } get element() { @@ -2840,7 +2825,7 @@ export class ZodObject< return new ZodFailure(issues); } - return ctx.async ? Promise.resolve(OK(final)) : OK(final); + return OK(final); } get shape() { @@ -3277,19 +3262,21 @@ export class ZodUnion extends ZodType< ]); } - if (ctx.async) { - return Promise.all( - options.map(async (option) => { - const result = await option._parseAsync(input, ctx); + let hasPromises = false; + const parseResults = options.map((option) => { + const result = option._parse(input, ctx); + if (result instanceof Promise) { + hasPromises = true; + } + return result; + }); - return result; - }) - ).then(handleResults); + if (hasPromises) { + return Promise.all(parseResults).then(handleResults); } else { const issues: ZodIssue[][] = []; - for (const option of options) { - const result = option._parseSync(input, ctx); - + for (const result of parseResults as SyncParseReturnType[]) { + // we know it's sync because hasPromises is false if (!isAborted(result)) { return result; } @@ -3425,11 +3412,7 @@ export class ZodDiscriminatedUnion< ]); } - if (ctx.async) { - return option._parseAsync(input, ctx) as any; - } else { - return option._parseSync(input, ctx) as any; - } + return option._parse(input, ctx) as any; } get discriminator() { @@ -3617,15 +3600,23 @@ export class ZodIntersection< return OK(merged.data); }; - if (ctx.async) { - return Promise.all([ - this._def.left._parseAsync(input, ctx), - this._def.right._parseAsync(input, ctx), - ]).then(([left, right]) => handleParsed(left, right)); + const parseResults = [ + this._def.left._parse(input, ctx), + this._def.right._parse(input, ctx), + ]; + + const hasPromises = parseResults.some( + (result) => result instanceof Promise + ); + + if (hasPromises) { + return Promise.all(parseResults).then(([left, right]) => + handleParsed(left, right) + ); } else { return handleParsed( - this._def.left._parseSync(input, ctx), - this._def.right._parseSync(input, ctx) + parseResults[0] as SyncParseReturnType, + parseResults[1] as SyncParseReturnType ); } } @@ -3738,15 +3729,22 @@ export class ZodTuple< }); } + let hasPromises = false; + const items = ([...input] as any[]) .map((item, itemIndex) => { const schema = this._def.items[itemIndex] || this._def.rest; if (!schema) return NOT_SET as any as SyncParseReturnType; - return schema._parse(item, ctx); + const result = schema._parse(item, ctx); + if (result instanceof Promise) { + hasPromises = true; + } + + return result; }) .filter((x) => x !== NOT_SET); // filter nulls - if (ctx.async) { + if (hasPromises) { return Promise.all(items).then((results) => { issues.push( ...results.flatMap((r, i) => @@ -3948,13 +3946,9 @@ export class ZodRecord< }); } else { if (issues.length) { - return ctx.async - ? Promise.resolve(new ZodFailure(issues)) - : new ZodFailure(issues); + return new ZodFailure(issues); } - return ctx.async - ? Promise.resolve(OK(final as this["_output"])) - : OK(final as this["_output"]); + return OK(final as this["_output"]); } } @@ -4117,12 +4111,10 @@ export class ZodMap< }); } else { if (issues.length) { - return ctx.async - ? Promise.resolve(new ZodFailure(issues)) - : new ZodFailure(issues); + return new ZodFailure(issues); } - return ctx.async ? Promise.resolve(OK(final)) : OK(final); + return OK(final); } } static create< @@ -4235,11 +4227,17 @@ export class ZodSet extends ZodType< return OK(parsedSet); } - const elements = [...(input as Set).values()].map((item) => - valueType._parse(item, ctx) - ); + let hasPromises = false; - if (ctx.async) { + const elements = [...(input as Set).values()].map((item) => { + const result = valueType._parse(item, ctx); + if (result instanceof Promise) { + hasPromises = true; + } + return result; + }); + + if (hasPromises) { return Promise.all(elements).then(finalizeSet); } else { return finalizeSet(elements as SyncParseReturnType[]); @@ -5086,11 +5084,8 @@ export class ZodPromise extends ZodType< ]); } - const promisified = - parsedType === ZodParsedType.promise ? input : Promise.resolve(input); - - return promisified.then((data: any) => { - return this._def.type._parse(data, ctx); + return input.then((inner: any) => { + return this._def.type._parse(inner, ctx); }); } @@ -5178,12 +5173,22 @@ export class ZodEffects< if (effect.type === "preprocess") { const processed = effect.transform(input, checkCtx); - if (ctx.async) { - return Promise.resolve(processed).then(async (processed) => { + if (processed instanceof Promise) { + return processed.then((processed) => { if (issues.some((i) => i.fatal)) { return new ZodFailure(issues); } - const result = await this._def.schema._parseAsync(processed, ctx); + const result = this._def.schema._parse(processed, ctx); + if (result instanceof Promise) { + return result.then((r) => { + if (isAborted(r)) { + issues.push(...r.issues); + } + if (issues.length) return new ZodFailure(issues); + return r; + }); + } + if (isAborted(result)) { issues.push(...result.issues); return new ZodFailure(issues); @@ -5195,7 +5200,18 @@ export class ZodEffects< if (issues.some((i) => i.fatal)) { return new ZodFailure(issues); } - const result = this._def.schema._parseSync(processed, ctx); + const result = this._def.schema._parse(processed, ctx); + + if (result instanceof Promise) { + return result.then((r) => { + if (isAborted(r)) { + issues.push(...r.issues); + } + if (issues.length) return new ZodFailure(issues); + return r; + }); + } + if (isAborted(result)) { issues.push(...result.issues); return new ZodFailure(issues); @@ -5207,19 +5223,15 @@ export class ZodEffects< if (effect.type === "refinement") { const executeRefinement = (acc: unknown): any => { const result = effect.refinement(acc, checkCtx); - if (ctx.async) { - return Promise.resolve(result); - } if (result instanceof Promise) { - throw new Error( - "Async refinement encountered during synchronous parse operation. Use .parseAsync instead." - ); + return Promise.resolve(result); } return acc; }; - if (ctx.async === false) { - const inner = this._def.schema._parseSync(input, ctx); + const inner = this._def.schema._parse(input, ctx); + + if (!(inner instanceof Promise)) { if (isAborted(inner)) { issues.push(...inner.issues); } @@ -5236,13 +5248,19 @@ export class ZodEffects< } // return value is ignored - executeRefinement(value); + const executed = executeRefinement(value); - if (issues.length) return new ZodFailure(issues, value); + if (executed instanceof Promise) { + return executed.then(() => { + if (issues.length) return new ZodFailure(issues); + return inner; + }) as any; + } + if (issues.length) return new ZodFailure(issues); return inner as any; } else { - return this._def.schema._parseAsync(input, ctx).then((inner) => { + return inner.then((inner) => { if (isAborted(inner)) { issues.push(...inner.issues); } @@ -5259,18 +5277,24 @@ export class ZodEffects< : input // if valid, use parsed value : inner; - return executeRefinement(value).then(() => { - if (issues.length) return new ZodFailure(issues, value); - return inner; - }); + const executed = executeRefinement(value); + + if (executed instanceof Promise) { + return executed.then(() => { + if (issues.length) return new ZodFailure(issues); + return inner; + }); + } + + if (issues.length) return new ZodFailure(issues); + return inner; }); } } if (effect.type === "transform") { - if (!ctx.async) { - const base = this._def.schema._parseSync(input, ctx); - + const base = this._def.schema._parse(input, ctx); + if (!(base instanceof Promise)) { if (isAborted(base)) { issues.push(...base.issues); } @@ -5278,37 +5302,39 @@ export class ZodEffects< // do not execute transform if any issues exist if (issues.length) return new ZodFailure(issues); - const baseValid = base as OK; + const value = isAborted(base) ? base.value === NOT_SET ? input : base.value : base; - const result = effect.transform(baseValid, checkCtx); + const result = effect.transform(value, checkCtx); if (result instanceof Promise) { - throw new Error( - `Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.` - ); - } - - if (issues.length) { - return new ZodFailure(issues, result); + return result.then((result) => { + if (issues.length) return new ZodFailure(issues); + return OK(result); + }); } + if (issues.length) return new ZodFailure(issues); return OK(result); } else { - return this._def.schema._parseAsync(input, ctx).then((base) => { + return base.then((base) => { if (isAborted(base)) { issues.push(...base.issues); } if (issues.length) return new ZodFailure(issues, base); - const baseValid = base as OK; + const value = isAborted(base) ? base.value === NOT_SET ? input : base.value : base; - return Promise.resolve(effect.transform(baseValid, checkCtx)).then( - (result) => { - if (issues.length) return new ZodFailure(issues, result); + const result = effect.transform(value, checkCtx); + if (result instanceof Promise) { + return result.then((result) => { + if (issues.length) return new ZodFailure(issues); return OK(result); - } - ); + }); + } + + if (issues.length) return new ZodFailure(issues); + return OK(result); }); } } @@ -5656,19 +5682,17 @@ export class ZodPipeline< B extends ZodTypeAny > extends ZodType, A["_input"]> { _parse(input: ParseInput, ctx: ParseContext): ParseReturnType { - if (ctx.async) { - const handleAsync = async () => { - const inResult = await this._def.in._parseAsync(input, ctx); + const result = this._def.in._parse(input, ctx); + if (result instanceof Promise) { + return result.then((inResult) => { if (isAborted(inResult)) return inResult; - return this._def.out._parseAsync(inResult, ctx); - }; - return handleAsync(); + return this._def.out._parse(inResult.value, ctx); + }); } else { - const inResult = this._def.in._parseSync(input, ctx); - if (isAborted(inResult)) return inResult; + if (isAborted(result)) return result; - return this._def.out._parseSync(inResult, ctx); + return this._def.out._parse(result, ctx); } }