From 981d4b5e272e7e35ff44a31fbb5e8e90594b1933 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Mon, 14 Aug 2023 12:21:40 -0700 Subject: [PATCH 01/29] Add ZodReadonly (#2634) * Add ZodReadonly * Use Bun in CI * Fix link * Update * Fix prettier * Update readme --- .github/workflows/test.yml | 1 - README.md | 36 ++++- deno/lib/README.md | 36 ++++- deno/lib/__tests__/readonly.test.ts | 205 ++++++++++++++++++++++++++++ deno/lib/types.ts | 71 ++++++++++ playground.ts | 7 +- src/__tests__/readonly.test.ts | 204 +++++++++++++++++++++++++++ src/types.ts | 71 ++++++++++ tsconfig.json | 5 +- 9 files changed, 624 insertions(+), 12 deletions(-) create mode 100644 deno/lib/__tests__/readonly.test.ts create mode 100644 src/__tests__/readonly.test.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 23121c90e..0e6e5c217 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,6 @@ jobs: - run: yarn build - run: yarn test - test-deno: runs-on: ubuntu-latest strategy: diff --git a/README.md b/README.md index 73efc76f7..d913ed9b4 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,8 @@ - [`.or`](#or) - [`.and`](#and) - [`.brand`](#brand) - - [`.pipe()`](#pipe) + - [`.readonly`](#readonly) + - [`.pipe`](#pipe) - [You can use `.pipe()` to fix common issues with `z.coerce`.](#you-can-use-pipe-to-fix-common-issues-with-zcoerce) - [Guides and concepts](#guides-and-concepts) - [Type inference](#type-inference) @@ -2453,7 +2454,38 @@ type Cat = z.infer; Note that branded types do not affect the runtime result of `.parse`. It is a static-only construct. -### `.pipe()` +### `.readonly` + +`.readonly() => ZodReadonly` + +This method returns a `ZodReadonly` schema instance that parses the input using the base schema, then calls `Object.freeze()` on the result. The inferred type is also marked as `readonly`. + +```ts +const schema = z.object({ name: string }).readonly(); +type schema = z.infer; +// Readonly<{name: string}> + +const result = schema.parse({ name: "fido" }); +result.name = "simba"; // error +``` + +The inferred type uses TypeScript's built-in readonly types when relevant. + +```ts +z.array(z.string()).readonly(); +// readonly string[] + +z.tuple([z.string(), z.number()]).readonly(); +// readonly [string, number] + +z.map(z.string(), z.date()).readonly(); +// ReadonlyMap + +z.set(z.string()).readonly(); +// ReadonlySet> +``` + +### `.pipe` Schemas can be chained into validation "pipelines". It's useful for easily validating the result after a `.transform()`: diff --git a/deno/lib/README.md b/deno/lib/README.md index 73efc76f7..d913ed9b4 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -143,7 +143,8 @@ - [`.or`](#or) - [`.and`](#and) - [`.brand`](#brand) - - [`.pipe()`](#pipe) + - [`.readonly`](#readonly) + - [`.pipe`](#pipe) - [You can use `.pipe()` to fix common issues with `z.coerce`.](#you-can-use-pipe-to-fix-common-issues-with-zcoerce) - [Guides and concepts](#guides-and-concepts) - [Type inference](#type-inference) @@ -2453,7 +2454,38 @@ type Cat = z.infer; Note that branded types do not affect the runtime result of `.parse`. It is a static-only construct. -### `.pipe()` +### `.readonly` + +`.readonly() => ZodReadonly` + +This method returns a `ZodReadonly` schema instance that parses the input using the base schema, then calls `Object.freeze()` on the result. The inferred type is also marked as `readonly`. + +```ts +const schema = z.object({ name: string }).readonly(); +type schema = z.infer; +// Readonly<{name: string}> + +const result = schema.parse({ name: "fido" }); +result.name = "simba"; // error +``` + +The inferred type uses TypeScript's built-in readonly types when relevant. + +```ts +z.array(z.string()).readonly(); +// readonly string[] + +z.tuple([z.string(), z.number()]).readonly(); +// readonly [string, number] + +z.map(z.string(), z.date()).readonly(); +// ReadonlyMap + +z.set(z.string()).readonly(); +// ReadonlySet> +``` + +### `.pipe` Schemas can be chained into validation "pipelines". It's useful for easily validating the result after a `.transform()`: diff --git a/deno/lib/__tests__/readonly.test.ts b/deno/lib/__tests__/readonly.test.ts new file mode 100644 index 000000000..27d2e0c9c --- /dev/null +++ b/deno/lib/__tests__/readonly.test.ts @@ -0,0 +1,205 @@ +// @ts-ignore TS6133 +import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; +const test = Deno.test; + +import { util } from "../helpers/util.ts"; +import * as z from "../index.ts"; + +enum testEnum { + A, + B, +} + +const schemas = [ + z.string().readonly(), + z.number().readonly(), + z.nan().readonly(), + z.bigint().readonly(), + z.boolean().readonly(), + z.date().readonly(), + z.undefined().readonly(), + z.null().readonly(), + z.any().readonly(), + z.unknown().readonly(), + z.void().readonly(), + z.function().args(z.string(), z.number()).readonly(), + + z.array(z.string()).readonly(), + z.tuple([z.string(), z.number()]).readonly(), + z.map(z.string(), z.date()).readonly(), + z.set(z.promise(z.string())).readonly(), + z.record(z.string()).readonly(), + z.record(z.string(), z.number()).readonly(), + z.object({ a: z.string(), 1: z.number() }).readonly(), + z.nativeEnum(testEnum).readonly(), + z.promise(z.string()).readonly(), +] as const; + +test("flat inference", () => { + util.assertEqual, string>(true); + util.assertEqual, number>(true); + util.assertEqual, number>(true); + util.assertEqual, bigint>(true); + util.assertEqual, boolean>(true); + util.assertEqual, Date>(true); + util.assertEqual, undefined>(true); + util.assertEqual, null>(true); + util.assertEqual, any>(true); + util.assertEqual, Readonly>(true); + util.assertEqual, void>(true); + util.assertEqual< + z.infer<(typeof schemas)[11]>, + (args_0: string, args_1: number, ...args_2: unknown[]) => unknown + >(true); + util.assertEqual, readonly string[]>(true); + + util.assertEqual, readonly [string, number]>( + true + ); + util.assertEqual, ReadonlyMap>( + true + ); + util.assertEqual, ReadonlySet>>( + true + ); + util.assertEqual< + z.infer<(typeof schemas)[16]>, + Readonly> + >(true); + util.assertEqual< + z.infer<(typeof schemas)[17]>, + Readonly> + >(true); + util.assertEqual< + z.infer<(typeof schemas)[18]>, + { readonly a: string; readonly 1: number } + >(true); + util.assertEqual, Readonly>(true); + util.assertEqual, Promise>(true); +}); + +// test("deep inference", () => { +// util.assertEqual, string>(true); +// util.assertEqual, number>(true); +// util.assertEqual, number>(true); +// util.assertEqual, bigint>(true); +// util.assertEqual, boolean>(true); +// util.assertEqual, Date>(true); +// util.assertEqual, undefined>(true); +// util.assertEqual, null>(true); +// util.assertEqual, any>(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[9]>, +// Readonly +// >(true); +// util.assertEqual, void>(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[11]>, +// (args_0: string, args_1: number, ...args_2: unknown[]) => unknown +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[12]>, +// readonly string[] +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[13]>, +// readonly [string, number] +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[14]>, +// ReadonlyMap +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[15]>, +// ReadonlySet> +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[16]>, +// Readonly> +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[17]>, +// Readonly> +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[18]>, +// { readonly a: string; readonly 1: number } +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[19]>, +// Readonly +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[20]>, +// Promise +// >(true); + +// util.assertEqual< +// z.infer, +// ReadonlyMap< +// ReadonlySet, +// { +// readonly a: { +// readonly [x: string]: readonly any[]; +// }; +// readonly b: { +// readonly c: { +// readonly d: { +// readonly e: { +// readonly f: { +// readonly g?: {}; +// }; +// }; +// }; +// }; +// }; +// } +// > +// >(true); +// }); + +test("object freezing", () => { + expect(Object.isFrozen(z.array(z.string()).readonly().parse(["a"]))).toBe( + true + ); + expect( + Object.isFrozen( + z.tuple([z.string(), z.number()]).readonly().parse(["a", 1]) + ) + ).toBe(true); + expect( + Object.isFrozen( + z + .map(z.string(), z.date()) + .readonly() + .parse(new Map([["a", new Date()]])) + ) + ).toBe(true); + expect( + Object.isFrozen( + z + .set(z.promise(z.string())) + .readonly() + .parse(new Set([Promise.resolve("a")])) + ) + ).toBe(true); + expect( + Object.isFrozen(z.record(z.string()).readonly().parse({ a: "b" })) + ).toBe(true); + expect( + Object.isFrozen(z.record(z.string(), z.number()).readonly().parse({ a: 1 })) + ).toBe(true); + expect( + Object.isFrozen( + z + .object({ a: z.string(), 1: z.number() }) + .readonly() + .parse({ a: "b", 1: 2 }) + ) + ).toBe(true); + expect( + Object.isFrozen( + z.promise(z.string()).readonly().parse(Promise.resolve("a")) + ) + ).toBe(true); +}); diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 13d42a738..0740927fa 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -402,6 +402,7 @@ export abstract class ZodType< this.catch = this.catch.bind(this); this.describe = this.describe.bind(this); this.pipe = this.pipe.bind(this); + this.readonly = this.readonly.bind(this); this.isNullable = this.isNullable.bind(this); this.isOptional = this.isOptional.bind(this); } @@ -489,6 +490,9 @@ export abstract class ZodType< pipe(target: T): ZodPipeline { return ZodPipeline.create(this, target); } + readonly(): ZodReadonly { + return ZodReadonly.create(this); + } isOptional(): boolean { return this.safeParse(undefined).success; @@ -4768,6 +4772,72 @@ export class ZodPipeline< } } +/////////////////////////////////////////// +/////////////////////////////////////////// +////////// ////////// +////////// ZodReadonly ////////// +////////// ////////// +/////////////////////////////////////////// +/////////////////////////////////////////// +type BuiltIn = + | (((...args: any[]) => any) | (new (...args: any[]) => any)) + | { readonly [Symbol.toStringTag]: string } + | Date + | Error + | Generator + | Promise + | RegExp; + +type MakeReadonly = T extends Map + ? ReadonlyMap + : T extends Set + ? ReadonlySet + : T extends [infer Head, ...infer Tail] + ? readonly [Head, ...Tail] + : T extends Array + ? ReadonlyArray + : T extends BuiltIn + ? T + : Readonly; + +export interface ZodReadonlyDef + extends ZodTypeDef { + innerType: T; + typeName: ZodFirstPartyTypeKind.ZodReadonly; +} + +export class ZodReadonly extends ZodType< + MakeReadonly, + ZodReadonlyDef, + T["_input"] +> { + _parse(input: ParseInput): ParseReturnType { + const result = this._def.innerType._parse(input); + if (isValid(result)) { + result.value = Object.freeze(result.value); + } + return result; + } + + static create = ( + type: T, + params?: RawCreateParams + ): ZodReadonly => { + return new ZodReadonly({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodReadonly, + ...processCreateParams(params), + }) as any; + }; +} + +//////////////////////////////////////// +//////////////////////////////////////// +////////// ////////// +////////// z.custom ////////// +////////// ////////// +//////////////////////////////////////// +//////////////////////////////////////// type CustomParams = CustomErrorParams & { fatal?: boolean }; export const custom = ( check?: (data: unknown) => any, @@ -4843,6 +4913,7 @@ export enum ZodFirstPartyTypeKind { ZodPromise = "ZodPromise", ZodBranded = "ZodBranded", ZodPipeline = "ZodPipeline", + ZodReadonly = "ZodReadonly", } export type ZodFirstPartySchemaTypes = | ZodString diff --git a/playground.ts b/playground.ts index b12a86fd6..4e01473b6 100644 --- a/playground.ts +++ b/playground.ts @@ -1,8 +1,3 @@ import { z } from "./src"; -z; -const schema = z.coerce.date(); -// console.log(schema.parse("3.14")); -test("asdf", () => { - expect(schema.parse("3.14")).toBeInstanceOf(Date); -}); +z; diff --git a/src/__tests__/readonly.test.ts b/src/__tests__/readonly.test.ts new file mode 100644 index 000000000..3007a6fac --- /dev/null +++ b/src/__tests__/readonly.test.ts @@ -0,0 +1,204 @@ +// @ts-ignore TS6133 +import { test, expect } from "@jest/globals"; + +import { util } from "../helpers/util"; +import * as z from "../index"; + +enum testEnum { + A, + B, +} + +const schemas = [ + z.string().readonly(), + z.number().readonly(), + z.nan().readonly(), + z.bigint().readonly(), + z.boolean().readonly(), + z.date().readonly(), + z.undefined().readonly(), + z.null().readonly(), + z.any().readonly(), + z.unknown().readonly(), + z.void().readonly(), + z.function().args(z.string(), z.number()).readonly(), + + z.array(z.string()).readonly(), + z.tuple([z.string(), z.number()]).readonly(), + z.map(z.string(), z.date()).readonly(), + z.set(z.promise(z.string())).readonly(), + z.record(z.string()).readonly(), + z.record(z.string(), z.number()).readonly(), + z.object({ a: z.string(), 1: z.number() }).readonly(), + z.nativeEnum(testEnum).readonly(), + z.promise(z.string()).readonly(), +] as const; + +test("flat inference", () => { + util.assertEqual, string>(true); + util.assertEqual, number>(true); + util.assertEqual, number>(true); + util.assertEqual, bigint>(true); + util.assertEqual, boolean>(true); + util.assertEqual, Date>(true); + util.assertEqual, undefined>(true); + util.assertEqual, null>(true); + util.assertEqual, any>(true); + util.assertEqual, Readonly>(true); + util.assertEqual, void>(true); + util.assertEqual< + z.infer<(typeof schemas)[11]>, + (args_0: string, args_1: number, ...args_2: unknown[]) => unknown + >(true); + util.assertEqual, readonly string[]>(true); + + util.assertEqual, readonly [string, number]>( + true + ); + util.assertEqual, ReadonlyMap>( + true + ); + util.assertEqual, ReadonlySet>>( + true + ); + util.assertEqual< + z.infer<(typeof schemas)[16]>, + Readonly> + >(true); + util.assertEqual< + z.infer<(typeof schemas)[17]>, + Readonly> + >(true); + util.assertEqual< + z.infer<(typeof schemas)[18]>, + { readonly a: string; readonly 1: number } + >(true); + util.assertEqual, Readonly>(true); + util.assertEqual, Promise>(true); +}); + +// test("deep inference", () => { +// util.assertEqual, string>(true); +// util.assertEqual, number>(true); +// util.assertEqual, number>(true); +// util.assertEqual, bigint>(true); +// util.assertEqual, boolean>(true); +// util.assertEqual, Date>(true); +// util.assertEqual, undefined>(true); +// util.assertEqual, null>(true); +// util.assertEqual, any>(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[9]>, +// Readonly +// >(true); +// util.assertEqual, void>(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[11]>, +// (args_0: string, args_1: number, ...args_2: unknown[]) => unknown +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[12]>, +// readonly string[] +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[13]>, +// readonly [string, number] +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[14]>, +// ReadonlyMap +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[15]>, +// ReadonlySet> +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[16]>, +// Readonly> +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[17]>, +// Readonly> +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[18]>, +// { readonly a: string; readonly 1: number } +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[19]>, +// Readonly +// >(true); +// util.assertEqual< +// z.infer<(typeof deepReadonlySchemas_0)[20]>, +// Promise +// >(true); + +// util.assertEqual< +// z.infer, +// ReadonlyMap< +// ReadonlySet, +// { +// readonly a: { +// readonly [x: string]: readonly any[]; +// }; +// readonly b: { +// readonly c: { +// readonly d: { +// readonly e: { +// readonly f: { +// readonly g?: {}; +// }; +// }; +// }; +// }; +// }; +// } +// > +// >(true); +// }); + +test("object freezing", () => { + expect(Object.isFrozen(z.array(z.string()).readonly().parse(["a"]))).toBe( + true + ); + expect( + Object.isFrozen( + z.tuple([z.string(), z.number()]).readonly().parse(["a", 1]) + ) + ).toBe(true); + expect( + Object.isFrozen( + z + .map(z.string(), z.date()) + .readonly() + .parse(new Map([["a", new Date()]])) + ) + ).toBe(true); + expect( + Object.isFrozen( + z + .set(z.promise(z.string())) + .readonly() + .parse(new Set([Promise.resolve("a")])) + ) + ).toBe(true); + expect( + Object.isFrozen(z.record(z.string()).readonly().parse({ a: "b" })) + ).toBe(true); + expect( + Object.isFrozen(z.record(z.string(), z.number()).readonly().parse({ a: 1 })) + ).toBe(true); + expect( + Object.isFrozen( + z + .object({ a: z.string(), 1: z.number() }) + .readonly() + .parse({ a: "b", 1: 2 }) + ) + ).toBe(true); + expect( + Object.isFrozen( + z.promise(z.string()).readonly().parse(Promise.resolve("a")) + ) + ).toBe(true); +}); diff --git a/src/types.ts b/src/types.ts index eb4349c25..81a5e9f8a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -402,6 +402,7 @@ export abstract class ZodType< this.catch = this.catch.bind(this); this.describe = this.describe.bind(this); this.pipe = this.pipe.bind(this); + this.readonly = this.readonly.bind(this); this.isNullable = this.isNullable.bind(this); this.isOptional = this.isOptional.bind(this); } @@ -489,6 +490,9 @@ export abstract class ZodType< pipe(target: T): ZodPipeline { return ZodPipeline.create(this, target); } + readonly(): ZodReadonly { + return ZodReadonly.create(this); + } isOptional(): boolean { return this.safeParse(undefined).success; @@ -4768,6 +4772,72 @@ export class ZodPipeline< } } +/////////////////////////////////////////// +/////////////////////////////////////////// +////////// ////////// +////////// ZodReadonly ////////// +////////// ////////// +/////////////////////////////////////////// +/////////////////////////////////////////// +type BuiltIn = + | (((...args: any[]) => any) | (new (...args: any[]) => any)) + | { readonly [Symbol.toStringTag]: string } + | Date + | Error + | Generator + | Promise + | RegExp; + +type MakeReadonly = T extends Map + ? ReadonlyMap + : T extends Set + ? ReadonlySet + : T extends [infer Head, ...infer Tail] + ? readonly [Head, ...Tail] + : T extends Array + ? ReadonlyArray + : T extends BuiltIn + ? T + : Readonly; + +export interface ZodReadonlyDef + extends ZodTypeDef { + innerType: T; + typeName: ZodFirstPartyTypeKind.ZodReadonly; +} + +export class ZodReadonly extends ZodType< + MakeReadonly, + ZodReadonlyDef, + T["_input"] +> { + _parse(input: ParseInput): ParseReturnType { + const result = this._def.innerType._parse(input); + if (isValid(result)) { + result.value = Object.freeze(result.value); + } + return result; + } + + static create = ( + type: T, + params?: RawCreateParams + ): ZodReadonly => { + return new ZodReadonly({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodReadonly, + ...processCreateParams(params), + }) as any; + }; +} + +//////////////////////////////////////// +//////////////////////////////////////// +////////// ////////// +////////// z.custom ////////// +////////// ////////// +//////////////////////////////////////// +//////////////////////////////////////// type CustomParams = CustomErrorParams & { fatal?: boolean }; export const custom = ( check?: (data: unknown) => any, @@ -4843,6 +4913,7 @@ export enum ZodFirstPartyTypeKind { ZodPromise = "ZodPromise", ZodBranded = "ZodBranded", ZodPipeline = "ZodPipeline", + ZodReadonly = "ZodReadonly", } export type ZodFirstPartySchemaTypes = | ZodString diff --git a/tsconfig.json b/tsconfig.json index a1b3dc7e4..aead72af7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "./configs/tsconfig.base.json" + "extends": "./configs/tsconfig.base.json", + "compilerOptions": { + "outDir": "../lib" + } } From fba438cddea800b081a15aefc8b1efea2eccf7af Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Mon, 14 Aug 2023 12:22:35 -0700 Subject: [PATCH 02/29] 3.22.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19f114de9..040f511f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zod", - "version": "3.21.4", + "version": "3.22.0", "author": "Colin McDonnell ", "repository": { "type": "git", From 932cc472d2e66430d368a409b8d251909d7d8d21 Mon Sep 17 00:00:00 2001 From: ddurschlag Date: Tue, 15 Aug 2023 15:38:55 -0400 Subject: [PATCH 03/29] Initial prototype fix for issue #2651 (#2652) * Initial prototype fix * Add implementation/test for async * Fix formatting --- deno/lib/__tests__/function.test.ts | 26 ++++++++++++++++++++++++++ deno/lib/types.ts | 24 ++++++++++++++++-------- src/__tests__/function.test.ts | 26 ++++++++++++++++++++++++++ src/types.ts | 24 ++++++++++++++++-------- 4 files changed, 84 insertions(+), 16 deletions(-) diff --git a/deno/lib/__tests__/function.test.ts b/deno/lib/__tests__/function.test.ts index 4737afb72..45031fb09 100644 --- a/deno/lib/__tests__/function.test.ts +++ b/deno/lib/__tests__/function.test.ts @@ -29,6 +29,32 @@ test("function inference 1", () => { util.assertEqual number>(true); }); +test("method parsing", () => { + const methodObject = z.object({ + property: z.number(), + method: z.function().args(z.string()).returns(z.number()) + }); + const methodInstance = { + property: 3, + method: function(s: string) { return s.length + this.property; } + }; + const parsed = methodObject.parse(methodInstance); + expect(parsed.method('length=8')).toBe(11); // 8 length + 3 property +}); + +test("async method parsing", async () => { + const methodObject = z.object({ + property: z.number(), + method: z.function().args(z.string()).returns(z.promise(z.number())) + }); + const methodInstance = { + property: 3, + method: async function(s: string) { return s.length + this.property; } + }; + const parsed = methodObject.parse(methodInstance); + expect(await parsed.method('length=8')).toBe(11); // 8 length + 3 property +}); + test("args method", () => { const t1 = z.function(); type t1 = z.infer; diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 0740927fa..8743f4154 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -3736,17 +3736,21 @@ export class ZodFunction< const fn = ctx.data; if (this._def.returns instanceof ZodPromise) { - return OK(async (...args: any[]) => { + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(async function (this: any, ...args: any[]) { const error = new ZodError([]); - const parsedArgs = await this._def.args + const parsedArgs = await me._def.args .parseAsync(args, params) .catch((e) => { error.addIssue(makeArgsIssue(args, e)); throw error; }); - const result = await fn(...(parsedArgs as any)); + const result = await Reflect.apply(fn, this, parsedArgs as any); const parsedReturns = await ( - this._def.returns as unknown as ZodPromise + me._def.returns as unknown as ZodPromise )._def.type .parseAsync(result, params) .catch((e) => { @@ -3756,13 +3760,17 @@ export class ZodFunction< return parsedReturns; }); } else { - return OK((...args: any[]) => { - const parsedArgs = this._def.args.safeParse(args, params); + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(function (this: any, ...args: any[]) { + const parsedArgs = me._def.args.safeParse(args, params); if (!parsedArgs.success) { throw new ZodError([makeArgsIssue(args, parsedArgs.error)]); } - const result = fn(...(parsedArgs.data as any)); - const parsedReturns = this._def.returns.safeParse(result, params); + const result = Reflect.apply(fn, this, parsedArgs.data); + const parsedReturns = me._def.returns.safeParse(result, params); if (!parsedReturns.success) { throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]); } diff --git a/src/__tests__/function.test.ts b/src/__tests__/function.test.ts index 3767e867c..b6a572700 100644 --- a/src/__tests__/function.test.ts +++ b/src/__tests__/function.test.ts @@ -28,6 +28,32 @@ test("function inference 1", () => { util.assertEqual number>(true); }); +test("method parsing", () => { + const methodObject = z.object({ + property: z.number(), + method: z.function().args(z.string()).returns(z.number()) + }); + const methodInstance = { + property: 3, + method: function(s: string) { return s.length + this.property; } + }; + const parsed = methodObject.parse(methodInstance); + expect(parsed.method('length=8')).toBe(11); // 8 length + 3 property +}); + +test("async method parsing", async () => { + const methodObject = z.object({ + property: z.number(), + method: z.function().args(z.string()).returns(z.promise(z.number())) + }); + const methodInstance = { + property: 3, + method: async function(s: string) { return s.length + this.property; } + }; + const parsed = methodObject.parse(methodInstance); + expect(await parsed.method('length=8')).toBe(11); // 8 length + 3 property +}); + test("args method", () => { const t1 = z.function(); type t1 = z.infer; diff --git a/src/types.ts b/src/types.ts index 81a5e9f8a..bdcf16662 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3736,17 +3736,21 @@ export class ZodFunction< const fn = ctx.data; if (this._def.returns instanceof ZodPromise) { - return OK(async (...args: any[]) => { + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(async function (this: any, ...args: any[]) { const error = new ZodError([]); - const parsedArgs = await this._def.args + const parsedArgs = await me._def.args .parseAsync(args, params) .catch((e) => { error.addIssue(makeArgsIssue(args, e)); throw error; }); - const result = await fn(...(parsedArgs as any)); + const result = await Reflect.apply(fn, this, parsedArgs as any); const parsedReturns = await ( - this._def.returns as unknown as ZodPromise + me._def.returns as unknown as ZodPromise )._def.type .parseAsync(result, params) .catch((e) => { @@ -3756,13 +3760,17 @@ export class ZodFunction< return parsedReturns; }); } else { - return OK((...args: any[]) => { - const parsedArgs = this._def.args.safeParse(args, params); + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(function (this: any, ...args: any[]) { + const parsedArgs = me._def.args.safeParse(args, params); if (!parsedArgs.success) { throw new ZodError([makeArgsIssue(args, parsedArgs.error)]); } - const result = fn(...(parsedArgs.data as any)); - const parsedReturns = this._def.returns.safeParse(result, params); + const result = Reflect.apply(fn, this, parsedArgs.data); + const parsedReturns = me._def.returns.safeParse(result, params); if (!parsedReturns.success) { throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]); } From 0a055e726ac210ef6efc69aa70cd2491767f6060 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 15 Aug 2023 12:41:17 -0700 Subject: [PATCH 04/29] 3.22.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 040f511f7..1cafe97ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zod", - "version": "3.22.0", + "version": "3.22.1", "author": "Colin McDonnell ", "repository": { "type": "git", From 13d9e6bda286cbd4c1b177171273695d8309e5de Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 15 Aug 2023 12:45:00 -0700 Subject: [PATCH 05/29] Fix lint --- deno/lib/__tests__/function.test.ts | 16 ++++++++++------ src/__tests__/function.test.ts | 16 ++++++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/deno/lib/__tests__/function.test.ts b/deno/lib/__tests__/function.test.ts index 45031fb09..675891e6d 100644 --- a/deno/lib/__tests__/function.test.ts +++ b/deno/lib/__tests__/function.test.ts @@ -32,27 +32,31 @@ test("function inference 1", () => { test("method parsing", () => { const methodObject = z.object({ property: z.number(), - method: z.function().args(z.string()).returns(z.number()) + method: z.function().args(z.string()).returns(z.number()), }); const methodInstance = { property: 3, - method: function(s: string) { return s.length + this.property; } + method: function (s: string) { + return s.length + this.property; + }, }; const parsed = methodObject.parse(methodInstance); - expect(parsed.method('length=8')).toBe(11); // 8 length + 3 property + expect(parsed.method("length=8")).toBe(11); // 8 length + 3 property }); test("async method parsing", async () => { const methodObject = z.object({ property: z.number(), - method: z.function().args(z.string()).returns(z.promise(z.number())) + method: z.function().args(z.string()).returns(z.promise(z.number())), }); const methodInstance = { property: 3, - method: async function(s: string) { return s.length + this.property; } + method: async function (s: string) { + return s.length + this.property; + }, }; const parsed = methodObject.parse(methodInstance); - expect(await parsed.method('length=8')).toBe(11); // 8 length + 3 property + expect(await parsed.method("length=8")).toBe(11); // 8 length + 3 property }); test("args method", () => { diff --git a/src/__tests__/function.test.ts b/src/__tests__/function.test.ts index b6a572700..1c23cb9c5 100644 --- a/src/__tests__/function.test.ts +++ b/src/__tests__/function.test.ts @@ -31,27 +31,31 @@ test("function inference 1", () => { test("method parsing", () => { const methodObject = z.object({ property: z.number(), - method: z.function().args(z.string()).returns(z.number()) + method: z.function().args(z.string()).returns(z.number()), }); const methodInstance = { property: 3, - method: function(s: string) { return s.length + this.property; } + method: function (s: string) { + return s.length + this.property; + }, }; const parsed = methodObject.parse(methodInstance); - expect(parsed.method('length=8')).toBe(11); // 8 length + 3 property + expect(parsed.method("length=8")).toBe(11); // 8 length + 3 property }); test("async method parsing", async () => { const methodObject = z.object({ property: z.number(), - method: z.function().args(z.string()).returns(z.promise(z.number())) + method: z.function().args(z.string()).returns(z.promise(z.number())), }); const methodInstance = { property: 3, - method: async function(s: string) { return s.length + this.property; } + method: async function (s: string) { + return s.length + this.property; + }, }; const parsed = methodObject.parse(methodInstance); - expect(await parsed.method('length=8')).toBe(11); // 8 length + 3 property + expect(await parsed.method("length=8")).toBe(11); // 8 length + 3 property }); test("args method", () => { From 0d49f10b3c25a8e4cbb6534cc0773b195c56d06d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Costa?= Date: Thu, 17 Aug 2023 12:41:58 -0700 Subject: [PATCH 06/29] docs: add typeschema to ecosystem (#2626) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d913ed9b4..efea26451 100644 --- a/README.md +++ b/README.md @@ -488,6 +488,7 @@ There are a growing number of tools that are built atop or support Zod natively! - [`nestjs-graphql-zod`](https://github.com/incetarik/nestjs-graphql-zod): Generates NestJS GraphQL model classes from Zod schemas. Provides GraphQL method decorators working with Zod schemas. - [`zod-openapi`](https://github.com/samchungy/zod-openapi): Create full OpenAPI v3.x documentation from Zod schemas. - [`fastify-zod-openapi`](https://github.com/samchungy/fastify-zod-openapi): Fastify type provider, validation, serialization and @fastify/swagger support for Zod schemas. +- [`typeschema`](https://typeschema.com/): Universal adapter for schema validation. #### X to Zod From 8e4af7b56df6f2e3daf0dd825b986f1d963025ce Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sat, 19 Aug 2023 01:23:11 +0200 Subject: [PATCH 07/29] X to Zod: add app.quicktype.io (#2668) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index efea26451..5649249ec 100644 --- a/README.md +++ b/README.md @@ -502,6 +502,7 @@ There are a growing number of tools that are built atop or support Zod natively! - [`prisma-zod-generator`](https://github.com/omar-dulaimi/prisma-zod-generator): Emit Zod schemas from your Prisma schema. - [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod. - [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models. +- [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas. #### Mocking From 792b3ef0d41c144cd10641c6966b98dae1222d82 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Fri, 18 Aug 2023 17:13:16 -0700 Subject: [PATCH 08/29] Fix superrefine types --- deno/lib/types.ts | 5 ++++- package.json | 2 +- playground.ts | 9 +++++++++ src/types.ts | 5 ++++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 8743f4154..39325a9a9 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -371,7 +371,10 @@ export abstract class ZodType< refinement: (arg: Output, ctx: RefinementCtx) => arg is RefinedOutput ): ZodEffects; superRefine( - refinement: (arg: Output, ctx: RefinementCtx) => void | Promise + refinement: (arg: Output, ctx: RefinementCtx) => void + ): ZodEffects; + superRefine( + refinement: (arg: Output, ctx: RefinementCtx) => Promise ): ZodEffects; superRefine( refinement: (arg: Output, ctx: RefinementCtx) => unknown | Promise diff --git a/package.json b/package.json index 1cafe97ba..e51c4076e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zod", - "version": "3.22.1", + "version": "3.22.2", "author": "Colin McDonnell ", "repository": { "type": "git", diff --git a/playground.ts b/playground.ts index 4e01473b6..2a67b4e95 100644 --- a/playground.ts +++ b/playground.ts @@ -1,3 +1,12 @@ import { z } from "./src"; z; + +const schema = z.object({ + name: z.string(), + value: z.string(), +}); + +const schemaRefine = schema.superRefine(async (val, _ctx) => { + return val.value !== "INVALID"; +}); diff --git a/src/types.ts b/src/types.ts index bdcf16662..51e188948 100644 --- a/src/types.ts +++ b/src/types.ts @@ -371,7 +371,10 @@ export abstract class ZodType< refinement: (arg: Output, ctx: RefinementCtx) => arg is RefinedOutput ): ZodEffects; superRefine( - refinement: (arg: Output, ctx: RefinementCtx) => void | Promise + refinement: (arg: Output, ctx: RefinementCtx) => void + ): ZodEffects; + superRefine( + refinement: (arg: Output, ctx: RefinementCtx) => Promise ): ZodEffects; superRefine( refinement: (arg: Output, ctx: RefinementCtx) => unknown | Promise From 1e23990bcdd33d1e81b31e40e77a031fcfd87ce1 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Mon, 21 Aug 2023 11:37:59 -0700 Subject: [PATCH 09/29] Commit --- deno/lib/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deno/lib/README.md b/deno/lib/README.md index d913ed9b4..5649249ec 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -488,6 +488,7 @@ There are a growing number of tools that are built atop or support Zod natively! - [`nestjs-graphql-zod`](https://github.com/incetarik/nestjs-graphql-zod): Generates NestJS GraphQL model classes from Zod schemas. Provides GraphQL method decorators working with Zod schemas. - [`zod-openapi`](https://github.com/samchungy/zod-openapi): Create full OpenAPI v3.x documentation from Zod schemas. - [`fastify-zod-openapi`](https://github.com/samchungy/fastify-zod-openapi): Fastify type provider, validation, serialization and @fastify/swagger support for Zod schemas. +- [`typeschema`](https://typeschema.com/): Universal adapter for schema validation. #### X to Zod @@ -501,6 +502,7 @@ There are a growing number of tools that are built atop or support Zod natively! - [`prisma-zod-generator`](https://github.com/omar-dulaimi/prisma-zod-generator): Emit Zod schemas from your Prisma schema. - [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod. - [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models. +- [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas. #### Mocking From 9bd3879b482f139fd03d5025813ee66a04195cdd Mon Sep 17 00:00:00 2001 From: Matias Kinnunen Date: Thu, 24 Aug 2023 01:41:24 +0300 Subject: [PATCH 10/29] docs: remove obsolete text about readonly types (#2676) Readonly types were introduced in Zod v3.22.0: https://github.com/colinhacks/zod/releases/tag/v3.22.0 --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 5649249ec..938df6ba6 100644 --- a/README.md +++ b/README.md @@ -2825,10 +2825,9 @@ This more declarative API makes schema definitions vastly more concise. [https://github.com/pelotom/runtypes](https://github.com/pelotom/runtypes) -Good type inference support. They DO support readonly types, which Zod does not. +Good type inference support. - Supports "pattern matching": computed properties that distribute over unions -- Supports readonly types - Missing object methods: (deepPartial, merge) - Missing nonempty arrays with proper typing (`[T, ...T[]]`) - Missing promise schemas From f59be093ec21430d9f32bbcb628d7e39116adf34 Mon Sep 17 00:00:00 2001 From: Jonathan Stanley Date: Wed, 23 Aug 2023 18:44:08 -0400 Subject: [PATCH 11/29] clarify datetime ISO 8601 (#2673) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 938df6ba6..1996dfada 100644 --- a/README.md +++ b/README.md @@ -721,7 +721,7 @@ z.string().regex(regex); z.string().includes(string); z.string().startsWith(string); z.string().endsWith(string); -z.string().datetime(); // defaults to UTC, see below for options +z.string().datetime(); // ISO 8601; default is without UTC offset, see below for options z.string().ip(); // defaults to IPv4 and IPv6, see below for options // transformations @@ -760,7 +760,7 @@ z.string().ip({ message: "Invalid IP address" }); ### ISO datetimes -The `z.string().datetime()` method defaults to UTC validation: no timezone offsets with arbitrary sub-second decimal precision. +The `z.string().datetime()` method enforces ISO 8601; default is no timezone offsets and arbitrary sub-second decimal precision. ```ts const datetime = z.string().datetime(); From 64dcc8e2b16febe48fa8e3c82c47c92643e6c9e3 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 12 Sep 2023 13:09:20 -0700 Subject: [PATCH 12/29] Update sponsors --- README.md | 62 +++++++++++++++++++--------------------------- deno/lib/README.md | 62 +++++++++++++++++++--------------------------- 2 files changed, 50 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 1996dfada..6874a0a8b 100644 --- a/README.md +++ b/README.md @@ -185,23 +185,19 @@ Sponsorship at any level is appreciated and encouraged. For individual developer - - - + + - - + +
- - Astro + + Speakeasy API
- Astro + Speakeasy
- astro.build + speakeasyapi.dev
-

- Astro is a new kind of static
- site builder for the modern web.
- Powerful developer experience meets
- lightweight output.

+

SDKs, Terraform, Docs. Your API made enterprise-ready.

- + Glow Wallet
Glow Wallet @@ -223,17 +219,6 @@ Sponsorship at any level is appreciated and encouraged. For individual developer
deletype.com
- - Proxy logo - -
- Proxy -
- proxy.com -
Trigger.dev logo @@ -245,6 +230,8 @@ Sponsorship at any level is appreciated and encouraged. For individual developer

Effortless automation for developers.

Transloadit logo @@ -256,8 +243,6 @@ Sponsorship at any level is appreciated and encouraged. For individual developer

Simple file processing for developers.

Infisical logo @@ -270,13 +255,25 @@ Sponsorship at any level is appreciated and encouraged. For individual developer

Open-source platform for secret
management: sync secrets across your
team/infrastructure and prevent secret leaks.

+ + Whop logo + +
+ Whop +
+ whop.com +
+

A marketplace for really cool internet products.

+
#### Silver - - - - - + + - +
+ Numeric logo @@ -286,17 +283,6 @@ Sponsorship at any level is appreciated and encouraged. For individual developer numeric.io - - Snaplet logo - -
- Snaplet -
- snaplet.dev -
Marcato Partners @@ -305,7 +291,9 @@ Sponsorship at any level is appreciated and encouraged. For individual developer
marcatopartners.com
+
@@ -324,7 +312,7 @@ Sponsorship at any level is appreciated and encouraged. For individual developer seasoned.cc
Bamboo Creative logo diff --git a/deno/lib/README.md b/deno/lib/README.md index 5649249ec..fc114a3cb 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -185,23 +185,19 @@ Sponsorship at any level is appreciated and encouraged. For individual developer - - - + + - - + +
- - Astro + + Speakeasy API
- Astro + Speakeasy
- astro.build + speakeasyapi.dev
-

- Astro is a new kind of static
- site builder for the modern web.
- Powerful developer experience meets
- lightweight output.

+

SDKs, Terraform, Docs. Your API made enterprise-ready.

- + Glow Wallet
Glow Wallet @@ -223,17 +219,6 @@ Sponsorship at any level is appreciated and encouraged. For individual developer
deletype.com
- - Proxy logo - -
- Proxy -
- proxy.com -
Trigger.dev logo @@ -245,6 +230,8 @@ Sponsorship at any level is appreciated and encouraged. For individual developer

Effortless automation for developers.

Transloadit logo @@ -256,8 +243,6 @@ Sponsorship at any level is appreciated and encouraged. For individual developer

Simple file processing for developers.

Infisical logo @@ -270,13 +255,25 @@ Sponsorship at any level is appreciated and encouraged. For individual developer

Open-source platform for secret
management: sync secrets across your
team/infrastructure and prevent secret leaks.

+ + Whop logo + +
+ Whop +
+ whop.com +
+

A marketplace for really cool internet products.

+
#### Silver - - - - - + + - + - - - - diff --git a/deno/lib/README.md b/deno/lib/README.md index b6b1a8102..78cf45e7b 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -233,13 +233,13 @@ Sponsorship at any level is appreciated and encouraged. For individual developer From ad2ee9ccf723c4388158ff6b8669c2a6cdc85643 Mon Sep 17 00:00:00 2001 From: mitchgollub <37623776+mitchgollub@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:55:02 -0400 Subject: [PATCH 15/29] 2718 Updated Custom Schemas documentation example to use type narrowing (#2778) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78cf45e7b..e693bd099 100644 --- a/README.md +++ b/README.md @@ -1879,7 +1879,7 @@ You can create a Zod schema for any TypeScript type by using `z.custom()`. This ```ts const px = z.custom<`${number}px`>((val) => { - return /^\d+px$/.test(val as string); + return typeof val === "string" ? /^\d+px$/.test(val) : false; }); type px = z.infer; // `${number}px` From ae0f7a2c15e7741ee1b23c03a3bfb9acebd86551 Mon Sep 17 00:00:00 2001 From: Ryan Vermooten Date: Fri, 29 Sep 2023 15:00:34 +0200 Subject: [PATCH 16/29] docs: update ref to discriminated-unions docs (#2485) Co-authored-by: Ryan Vermooten --- ERROR_HANDLING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERROR_HANDLING.md b/ERROR_HANDLING.md index 027176d45..f2e0b8e62 100644 --- a/ERROR_HANDLING.md +++ b/ERROR_HANDLING.md @@ -24,7 +24,7 @@ Each ZodError has an `issues` property that is an array of `ZodIssues`. Each iss ## ZodIssue -`ZodIssue` is _not_ a class. It is a [discriminated union](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions). +`ZodIssue` is _not_ a class. It is a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions). The link above is the best way to learn about the concept. Discriminated unions are an ideal way to represent a data structures that may be one of many possible variants. You can see all the possible variants defined [here](./src/ZodError.ts). They are also described in the table below if you prefer. From 2ba00fe2377f4d53947a84b8cdb314a63bbd6dd4 Mon Sep 17 00:00:00 2001 From: Macs Dickinson Date: Tue, 3 Oct 2023 19:32:15 +0100 Subject: [PATCH 17/29] [2609] fix ReDoS vulnerability in email regex (#2824) --- deno/lib/README.md | 2 +- deno/lib/__tests__/string.test.ts | 1 + deno/lib/types.ts | 2 +- src/__tests__/string.test.ts | 1 + src/types.ts | 2 +- 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/deno/lib/README.md b/deno/lib/README.md index 78cf45e7b..e693bd099 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -1879,7 +1879,7 @@ You can create a Zod schema for any TypeScript type by using `z.custom()`. This ```ts const px = z.custom<`${number}px`>((val) => { - return /^\d+px$/.test(val as string); + return typeof val === "string" ? /^\d+px$/.test(val) : false; }); type px = z.infer; // `${number}px` diff --git a/deno/lib/__tests__/string.test.ts b/deno/lib/__tests__/string.test.ts index e067a1c81..6b74cf9f5 100644 --- a/deno/lib/__tests__/string.test.ts +++ b/deno/lib/__tests__/string.test.ts @@ -146,6 +146,7 @@ test("email validations", () => { `gbacher0@[IPv6:bc37:4d3f:5048:2e26:37cc:248e:df8e:2f7f:af]`, `invalid@[IPv6:5348:4ed3:5d38:67fb:e9b:acd2:c13:192.168.256.1]`, `test@.com`, + `aaaaaaaaaaaaaaalongemailthatcausesregexDoSvulnerability@test.c` ]; const emailSchema = z.string().email(); diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 39325a9a9..f99cab48a 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -565,7 +565,7 @@ const uuidRegex = // const emailRegex = // /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i; const emailRegex = - /^([A-Z0-9_+-]+\.?)*[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; + /^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; // const emailRegex = // /^[a-z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9\-]+)*$/i; diff --git a/src/__tests__/string.test.ts b/src/__tests__/string.test.ts index b84f080fc..e546bc82a 100644 --- a/src/__tests__/string.test.ts +++ b/src/__tests__/string.test.ts @@ -145,6 +145,7 @@ test("email validations", () => { `gbacher0@[IPv6:bc37:4d3f:5048:2e26:37cc:248e:df8e:2f7f:af]`, `invalid@[IPv6:5348:4ed3:5d38:67fb:e9b:acd2:c13:192.168.256.1]`, `test@.com`, + `aaaaaaaaaaaaaaalongemailthatcausesregexDoSvulnerability@test.c` ]; const emailSchema = z.string().email(); diff --git a/src/types.ts b/src/types.ts index 51e188948..723f6f824 100644 --- a/src/types.ts +++ b/src/types.ts @@ -565,7 +565,7 @@ const uuidRegex = // const emailRegex = // /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i; const emailRegex = - /^([A-Z0-9_+-]+\.?)*[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; + /^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; // const emailRegex = // /^[a-z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9\-]+)*$/i; From 1e61d76cdec05de9271fc0df58798ddf9ce94923 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 3 Oct 2023 11:32:55 -0700 Subject: [PATCH 18/29] 3.22.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e51c4076e..f5cda78c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zod", - "version": "3.22.2", + "version": "3.22.3", "author": "Colin McDonnell ", "repository": { "type": "git", From d931ea3f0f15a6ae64f5f68e3c03912dffb2269d Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 3 Oct 2023 11:35:34 -0700 Subject: [PATCH 19/29] Lint --- playground.ts | 9 --------- src/__tests__/readonly.test.ts | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/playground.ts b/playground.ts index 2a67b4e95..4e01473b6 100644 --- a/playground.ts +++ b/playground.ts @@ -1,12 +1,3 @@ import { z } from "./src"; z; - -const schema = z.object({ - name: z.string(), - value: z.string(), -}); - -const schemaRefine = schema.superRefine(async (val, _ctx) => { - return val.value !== "INVALID"; -}); diff --git a/src/__tests__/readonly.test.ts b/src/__tests__/readonly.test.ts index 3007a6fac..9d775c991 100644 --- a/src/__tests__/readonly.test.ts +++ b/src/__tests__/readonly.test.ts @@ -1,5 +1,5 @@ // @ts-ignore TS6133 -import { test, expect } from "@jest/globals"; +import { expect,test } from "@jest/globals"; import { util } from "../helpers/util"; import * as z from "../index"; From 8e634bd600093b7161487bed705279c892395118 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 3 Oct 2023 13:04:00 -0700 Subject: [PATCH 20/29] Fix prettier --- deno/lib/__tests__/string.test.ts | 2 +- src/__tests__/readonly.test.ts | 2 +- src/__tests__/string.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deno/lib/__tests__/string.test.ts b/deno/lib/__tests__/string.test.ts index 6b74cf9f5..2a25f650e 100644 --- a/deno/lib/__tests__/string.test.ts +++ b/deno/lib/__tests__/string.test.ts @@ -146,7 +146,7 @@ test("email validations", () => { `gbacher0@[IPv6:bc37:4d3f:5048:2e26:37cc:248e:df8e:2f7f:af]`, `invalid@[IPv6:5348:4ed3:5d38:67fb:e9b:acd2:c13:192.168.256.1]`, `test@.com`, - `aaaaaaaaaaaaaaalongemailthatcausesregexDoSvulnerability@test.c` + `aaaaaaaaaaaaaaalongemailthatcausesregexDoSvulnerability@test.c`, ]; const emailSchema = z.string().email(); diff --git a/src/__tests__/readonly.test.ts b/src/__tests__/readonly.test.ts index 9d775c991..267ff190c 100644 --- a/src/__tests__/readonly.test.ts +++ b/src/__tests__/readonly.test.ts @@ -1,5 +1,5 @@ // @ts-ignore TS6133 -import { expect,test } from "@jest/globals"; +import { expect, test } from "@jest/globals"; import { util } from "../helpers/util"; import * as z from "../index"; diff --git a/src/__tests__/string.test.ts b/src/__tests__/string.test.ts index e546bc82a..b870ad1b6 100644 --- a/src/__tests__/string.test.ts +++ b/src/__tests__/string.test.ts @@ -145,7 +145,7 @@ test("email validations", () => { `gbacher0@[IPv6:bc37:4d3f:5048:2e26:37cc:248e:df8e:2f7f:af]`, `invalid@[IPv6:5348:4ed3:5d38:67fb:e9b:acd2:c13:192.168.256.1]`, `test@.com`, - `aaaaaaaaaaaaaaalongemailthatcausesregexDoSvulnerability@test.c` + `aaaaaaaaaaaaaaalongemailthatcausesregexDoSvulnerability@test.c`, ]; const emailSchema = z.string().email(); From 4018d88f0e94992b2987428c4fda387b99ae2a53 Mon Sep 17 00:00:00 2001 From: Saiichi Shayan Hashimoto Date: Wed, 4 Oct 2023 23:15:45 +0300 Subject: [PATCH 21/29] docs: add @sanity-typed/zod to ecosystem (#2731) --- README.md | 1 + deno/lib/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index e693bd099..de8b7e163 100644 --- a/README.md +++ b/README.md @@ -489,6 +489,7 @@ There are a growing number of tools that are built atop or support Zod natively! - [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod. - [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models. - [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas. +- [`@sanity-typed/zod`]([https://app.quicktype.io/](https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/zod)): Generate Zod Schemas from [Sanity Schemas](https://www.sanity.io/docs/schema-types). #### Mocking diff --git a/deno/lib/README.md b/deno/lib/README.md index e693bd099..de8b7e163 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -489,6 +489,7 @@ There are a growing number of tools that are built atop or support Zod natively! - [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod. - [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models. - [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas. +- [`@sanity-typed/zod`]([https://app.quicktype.io/](https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/zod)): Generate Zod Schemas from [Sanity Schemas](https://www.sanity.io/docs/schema-types). #### Mocking From 15ba5a4d4cb5be5af23771de0ba1346b4ba20a0e Mon Sep 17 00:00:00 2001 From: Nereu Melo <52723155+nereumelo@users.noreply.github.com> Date: Wed, 4 Oct 2023 17:29:26 -0300 Subject: [PATCH 22/29] docs: add `zod-sandbox` to README ecosystem links (#2707) --- README.md | 1 + deno/lib/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index de8b7e163..845734471 100644 --- a/README.md +++ b/README.md @@ -510,6 +510,7 @@ There are a growing number of tools that are built atop or support Zod natively! #### Utilities for Zod - [`zod_utilz`](https://github.com/JacobWeisenburger/zod_utilz): Framework agnostic utilities for Zod. +- [`zod-sandbox`](https://github.com/nereumelo/zod-sandbox): Controlled environment for testing zod schemas. [Live demo](https://zod-sandbox.vercel.app/). ## Installation diff --git a/deno/lib/README.md b/deno/lib/README.md index de8b7e163..845734471 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -510,6 +510,7 @@ There are a growing number of tools that are built atop or support Zod natively! #### Utilities for Zod - [`zod_utilz`](https://github.com/JacobWeisenburger/zod_utilz): Framework agnostic utilities for Zod. +- [`zod-sandbox`](https://github.com/nereumelo/zod-sandbox): Controlled environment for testing zod schemas. [Live demo](https://zod-sandbox.vercel.app/). ## Installation From 699ccae13b875d4fcadac268fd789c93b6ce8aef Mon Sep 17 00:00:00 2001 From: amon Date: Thu, 5 Oct 2023 00:01:49 +0300 Subject: [PATCH 23/29] Export jsdoc with `@deprecated` when building (#2717) * export with comments * Avoid using arrow function in class * to jsdoc --- configs/tsconfig.cjs.json | 3 +-- deno/lib/types.ts | 29 +++++++++++++++++------------ src/types.ts | 29 +++++++++++++++++------------ 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/configs/tsconfig.cjs.json b/configs/tsconfig.cjs.json index 32729f179..04e1efe40 100644 --- a/configs/tsconfig.cjs.json +++ b/configs/tsconfig.cjs.json @@ -5,8 +5,7 @@ "outDir": "../lib", "declaration": true, "declarationMap": false, - "sourceMap": false, - "removeComments": true + "sourceMap": false }, "exclude": ["../src/**/__tests__", "../playground.ts"] } diff --git a/deno/lib/types.ts b/deno/lib/types.ts index f99cab48a..0ddeb15da 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -849,16 +849,17 @@ export class ZodString extends ZodType { return { status: status.value, value: input.data }; } - protected _regex = ( + protected _regex( regex: RegExp, validation: StringValidation, message?: errorUtil.ErrMessage - ) => - this.refinement((data) => regex.test(data), { + ) { + return this.refinement((data) => regex.test(data), { validation, code: ZodIssueCode.invalid_string, ...errorUtil.errToObj(message), }); + } _addCheck(check: ZodStringCheck) { return new ZodString({ @@ -980,26 +981,30 @@ export class ZodString extends ZodType { * @deprecated Use z.string().min(1) instead. * @see {@link ZodString.min} */ - nonempty = (message?: errorUtil.ErrMessage) => - this.min(1, errorUtil.errToObj(message)); + nonempty(message?: errorUtil.ErrMessage) { + return this.min(1, errorUtil.errToObj(message)); + } - trim = () => - new ZodString({ + trim() { + return new ZodString({ ...this._def, checks: [...this._def.checks, { kind: "trim" }], }); + } - toLowerCase = () => - new ZodString({ + toLowerCase() { + return new ZodString({ ...this._def, checks: [...this._def.checks, { kind: "toLowerCase" }], }); + } - toUpperCase = () => - new ZodString({ + toUpperCase() { + return new ZodString({ ...this._def, checks: [...this._def.checks, { kind: "toUpperCase" }], }); + } get isDatetime() { return !!this._def.checks.find((ch) => ch.kind === "datetime"); @@ -4853,7 +4858,7 @@ type CustomParams = CustomErrorParams & { fatal?: boolean }; export const custom = ( check?: (data: unknown) => any, params: string | CustomParams | ((input: any) => CustomParams) = {}, - /* + /** * @deprecated * * Pass `fatal` into the params object instead: diff --git a/src/types.ts b/src/types.ts index 723f6f824..924fa3fba 100644 --- a/src/types.ts +++ b/src/types.ts @@ -849,16 +849,17 @@ export class ZodString extends ZodType { return { status: status.value, value: input.data }; } - protected _regex = ( + protected _regex( regex: RegExp, validation: StringValidation, message?: errorUtil.ErrMessage - ) => - this.refinement((data) => regex.test(data), { + ) { + return this.refinement((data) => regex.test(data), { validation, code: ZodIssueCode.invalid_string, ...errorUtil.errToObj(message), }); + } _addCheck(check: ZodStringCheck) { return new ZodString({ @@ -980,26 +981,30 @@ export class ZodString extends ZodType { * @deprecated Use z.string().min(1) instead. * @see {@link ZodString.min} */ - nonempty = (message?: errorUtil.ErrMessage) => - this.min(1, errorUtil.errToObj(message)); + nonempty(message?: errorUtil.ErrMessage) { + return this.min(1, errorUtil.errToObj(message)); + } - trim = () => - new ZodString({ + trim() { + return new ZodString({ ...this._def, checks: [...this._def.checks, { kind: "trim" }], }); + } - toLowerCase = () => - new ZodString({ + toLowerCase() { + return new ZodString({ ...this._def, checks: [...this._def.checks, { kind: "toLowerCase" }], }); + } - toUpperCase = () => - new ZodString({ + toUpperCase() { + return new ZodString({ ...this._def, checks: [...this._def.checks, { kind: "toUpperCase" }], }); + } get isDatetime() { return !!this._def.checks.find((ch) => ch.kind === "datetime"); @@ -4853,7 +4858,7 @@ type CustomParams = CustomErrorParams & { fatal?: boolean }; export const custom = ( check?: (data: unknown) => any, params: string | CustomParams | ((input: any) => CustomParams) = {}, - /* + /** * @deprecated * * Pass `fatal` into the params object instead: From dfe3719eae250ab3eca2d276da6c292867899cc6 Mon Sep 17 00:00:00 2001 From: Saiichi Shayan Hashimoto Date: Thu, 5 Oct 2023 00:02:17 +0300 Subject: [PATCH 24/29] Fix sanity-typed links (#2840) * docs: fix @sanity-typed/zod link in README * docs: fix @sanity-typed/zod link in deno/lib/README --- README.md | 2 +- deno/lib/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 845734471..490265fc8 100644 --- a/README.md +++ b/README.md @@ -489,7 +489,7 @@ There are a growing number of tools that are built atop or support Zod natively! - [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod. - [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models. - [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas. -- [`@sanity-typed/zod`]([https://app.quicktype.io/](https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/zod)): Generate Zod Schemas from [Sanity Schemas](https://www.sanity.io/docs/schema-types). +- [`@sanity-typed/zod`](https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/zod): Generate Zod Schemas from [Sanity Schemas](https://www.sanity.io/docs/schema-types). #### Mocking diff --git a/deno/lib/README.md b/deno/lib/README.md index 845734471..490265fc8 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -489,7 +489,7 @@ There are a growing number of tools that are built atop or support Zod natively! - [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod. - [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models. - [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas. -- [`@sanity-typed/zod`]([https://app.quicktype.io/](https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/zod)): Generate Zod Schemas from [Sanity Schemas](https://www.sanity.io/docs/schema-types). +- [`@sanity-typed/zod`](https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/zod): Generate Zod Schemas from [Sanity Schemas](https://www.sanity.io/docs/schema-types). #### Mocking From cd7991e04a550868bfcb5b5d46e5eb5bc7edf5f3 Mon Sep 17 00:00:00 2001 From: Romain Charbit Date: Wed, 4 Oct 2023 23:31:42 +0200 Subject: [PATCH 25/29] fix ulid regex (#2225) --- deno/lib/__tests__/string.test.ts | 2 ++ deno/lib/types.ts | 2 +- src/__tests__/string.test.ts | 2 ++ src/types.ts | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/deno/lib/__tests__/string.test.ts b/deno/lib/__tests__/string.test.ts index 2a25f650e..7798db244 100644 --- a/deno/lib/__tests__/string.test.ts +++ b/deno/lib/__tests__/string.test.ts @@ -266,6 +266,8 @@ test("ulid", () => { ulid.parse("01ARZ3NDEKTSV4RRFFQ69G5FAV"); const result = ulid.safeParse("invalidulid"); expect(result.success).toEqual(false); + const tooLong = "01ARZ3NDEKTSV4RRFFQ69G5FAVA" + expect(ulid.safeParse(tooLong).success).toEqual(false); if (!result.success) { expect(result.error.issues[0].message).toEqual("Invalid ulid"); } diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 0ddeb15da..80939da3b 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -547,7 +547,7 @@ export interface ZodStringDef extends ZodTypeDef { const cuidRegex = /^c[^\s-]{8,}$/i; const cuid2Regex = /^[a-z][a-z0-9]*$/; -const ulidRegex = /[0-9A-HJKMNP-TV-Z]{26}/; +const ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/; // const uuidRegex = // /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; const uuidRegex = diff --git a/src/__tests__/string.test.ts b/src/__tests__/string.test.ts index b870ad1b6..c1393c0fd 100644 --- a/src/__tests__/string.test.ts +++ b/src/__tests__/string.test.ts @@ -265,6 +265,8 @@ test("ulid", () => { ulid.parse("01ARZ3NDEKTSV4RRFFQ69G5FAV"); const result = ulid.safeParse("invalidulid"); expect(result.success).toEqual(false); + const tooLong = "01ARZ3NDEKTSV4RRFFQ69G5FAVA" + expect(ulid.safeParse(tooLong).success).toEqual(false); if (!result.success) { expect(result.error.issues[0].message).toEqual("Invalid ulid"); } diff --git a/src/types.ts b/src/types.ts index 924fa3fba..20bb60dae 100644 --- a/src/types.ts +++ b/src/types.ts @@ -547,7 +547,7 @@ export interface ZodStringDef extends ZodTypeDef { const cuidRegex = /^c[^\s-]{8,}$/i; const cuid2Regex = /^[a-z][a-z0-9]*$/; -const ulidRegex = /[0-9A-HJKMNP-TV-Z]{26}/; +const ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/; // const uuidRegex = // /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; const uuidRegex = From 7cb4ba2f85dd6b28290dda5de80ed54dfd2a793c Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Wed, 4 Oct 2023 14:35:48 -0700 Subject: [PATCH 26/29] Remove stalebot --- .github/stale.yml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 46c8b7cef..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 90 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 30 -# Issues with these labels will never be considered stale -exemptLabels: - - enhancement -# - security -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false From 9340fd51e48576a75adc919bff65dbc4a5d4c99b Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Wed, 4 Oct 2023 14:51:14 -0700 Subject: [PATCH 27/29] Lazy emojiRegex --- deno/lib/types.ts | 6 +++++- src/types.ts | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 80939da3b..673ab1ec9 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -570,7 +570,8 @@ const emailRegex = // /^[a-z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9\-]+)*$/i; // from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression -const emojiRegex = /^(\p{Extended_Pictographic}|\p{Emoji_Component})+$/u; +const _emojiRegex = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`; +let emojiRegex: RegExp; const ipv4Regex = /^(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))$/; @@ -710,6 +711,9 @@ export class ZodString extends ZodType { status.dirty(); } } else if (check.kind === "emoji") { + if (!emojiRegex) { + emojiRegex = new RegExp(_emojiRegex, "u"); + } if (!emojiRegex.test(input.data)) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { diff --git a/src/types.ts b/src/types.ts index 20bb60dae..567936b76 100644 --- a/src/types.ts +++ b/src/types.ts @@ -570,7 +570,8 @@ const emailRegex = // /^[a-z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9\-]+)*$/i; // from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression -const emojiRegex = /^(\p{Extended_Pictographic}|\p{Emoji_Component})+$/u; +const _emojiRegex = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`; +let emojiRegex: RegExp; const ipv4Regex = /^(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))$/; @@ -710,6 +711,9 @@ export class ZodString extends ZodType { status.dirty(); } } else if (check.kind === "emoji") { + if (!emojiRegex) { + emojiRegex = new RegExp(_emojiRegex, "u"); + } if (!emojiRegex.test(input.data)) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { From e7a9b9b3033991be6b4225f1be21da39c250bbb0 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Wed, 4 Oct 2023 14:51:33 -0700 Subject: [PATCH 28/29] 3.22.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f5cda78c4..c900c6ec6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zod", - "version": "3.22.3", + "version": "3.22.4", "author": "Colin McDonnell ", "repository": { "type": "git", From 481c9ba1932203777f6fe9497bb2a8a1d33c620e Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Wed, 4 Oct 2023 15:24:18 -0700 Subject: [PATCH 29/29] Fix lint --- deno/lib/__tests__/string.test.ts | 2 +- src/__tests__/string.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deno/lib/__tests__/string.test.ts b/deno/lib/__tests__/string.test.ts index 7798db244..2dc5b79a6 100644 --- a/deno/lib/__tests__/string.test.ts +++ b/deno/lib/__tests__/string.test.ts @@ -266,7 +266,7 @@ test("ulid", () => { ulid.parse("01ARZ3NDEKTSV4RRFFQ69G5FAV"); const result = ulid.safeParse("invalidulid"); expect(result.success).toEqual(false); - const tooLong = "01ARZ3NDEKTSV4RRFFQ69G5FAVA" + const tooLong = "01ARZ3NDEKTSV4RRFFQ69G5FAVA"; expect(ulid.safeParse(tooLong).success).toEqual(false); if (!result.success) { expect(result.error.issues[0].message).toEqual("Invalid ulid"); diff --git a/src/__tests__/string.test.ts b/src/__tests__/string.test.ts index c1393c0fd..43e348b7b 100644 --- a/src/__tests__/string.test.ts +++ b/src/__tests__/string.test.ts @@ -265,7 +265,7 @@ test("ulid", () => { ulid.parse("01ARZ3NDEKTSV4RRFFQ69G5FAV"); const result = ulid.safeParse("invalidulid"); expect(result.success).toEqual(false); - const tooLong = "01ARZ3NDEKTSV4RRFFQ69G5FAVA" + const tooLong = "01ARZ3NDEKTSV4RRFFQ69G5FAVA"; expect(ulid.safeParse(tooLong).success).toEqual(false); if (!result.success) { expect(result.error.issues[0].message).toEqual("Invalid ulid");
+ Numeric logo @@ -286,17 +283,6 @@ Sponsorship at any level is appreciated and encouraged. For individual developer numeric.io - - Snaplet logo - -
- Snaplet -
- snaplet.dev -
Marcato Partners @@ -305,7 +291,9 @@ Sponsorship at any level is appreciated and encouraged. For individual developer
marcatopartners.com
+
@@ -324,7 +312,7 @@ Sponsorship at any level is appreciated and encouraged. For individual developer seasoned.cc
Bamboo Creative logo From 18115a8f128680b4526df58ce96deab7dce93b93 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Tue, 12 Sep 2023 13:11:21 -0700 Subject: [PATCH 13/29] Formatting --- README.md | 4 +--- deno/lib/README.md | 11 ++++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6874a0a8b..b6b1a8102 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ Sponsorship at any level is appreciated and encouraged. For individual developer
speakeasyapi.dev
-

SDKs, Terraform, Docs. Your API made enterprise-ready.

+

SDKs, Terraform, Docs.
Your API made enterprise-ready.

@@ -311,8 +311,6 @@ Sponsorship at any level is appreciated and encouraged. For individual developer
seasoned.cc
Bamboo Creative logo diff --git a/deno/lib/README.md b/deno/lib/README.md index fc114a3cb..b6b1a8102 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -193,7 +193,7 @@ Sponsorship at any level is appreciated and encouraged. For individual developer
speakeasyapi.dev
-

SDKs, Terraform, Docs. Your API made enterprise-ready.

+

SDKs, Terraform, Docs.
Your API made enterprise-ready.

@@ -311,8 +311,6 @@ Sponsorship at any level is appreciated and encouraged. For individual developer
seasoned.cc
Bamboo Creative logo @@ -709,7 +707,7 @@ z.string().regex(regex); z.string().includes(string); z.string().startsWith(string); z.string().endsWith(string); -z.string().datetime(); // defaults to UTC, see below for options +z.string().datetime(); // ISO 8601; default is without UTC offset, see below for options z.string().ip(); // defaults to IPv4 and IPv6, see below for options // transformations @@ -748,7 +746,7 @@ z.string().ip({ message: "Invalid IP address" }); ### ISO datetimes -The `z.string().datetime()` method defaults to UTC validation: no timezone offsets with arbitrary sub-second decimal precision. +The `z.string().datetime()` method enforces ISO 8601; default is no timezone offsets and arbitrary sub-second decimal precision. ```ts const datetime = z.string().datetime(); @@ -2813,10 +2811,9 @@ This more declarative API makes schema definitions vastly more concise. [https://github.com/pelotom/runtypes](https://github.com/pelotom/runtypes) -Good type inference support. They DO support readonly types, which Zod does not. +Good type inference support. - Supports "pattern matching": computed properties that distribute over unions -- Supports readonly types - Missing object methods: (deepPartial, merge) - Missing nonempty arrays with proper typing (`[T, ...T[]]`) - Missing promise schemas From 28c19273658b164c53c149785fa7a8187c428ad4 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Wed, 13 Sep 2023 23:36:23 -0700 Subject: [PATCH 14/29] Update sponsors --- README.md | 4 ++-- deno/lib/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b6b1a8102..78cf45e7b 100644 --- a/README.md +++ b/README.md @@ -233,13 +233,13 @@ Sponsorship at any level is appreciated and encouraged. For individual developer
- + Transloadit logo
Transloadit
- transloadit.com + transloadit.com

Simple file processing for developers.

- + Transloadit logo
Transloadit
- transloadit.com + transloadit.com

Simple file processing for developers.