Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/zod/src/v4/classic/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ export interface ZodString extends _ZodString<core.$ZodStringInternals<string>>
): this;
/** @deprecated Use `z.iso.duration()` instead. */
duration(params?: string | core.$ZodCheckISODurationParams): this;
script(name: string, params?: string | core.$ZodCheckScriptParams): this;
}

export const ZodString: core.$constructor<ZodString> = /*@__PURE__*/ core.$constructor("ZodString", (inst, def) => {
Expand Down Expand Up @@ -418,6 +419,7 @@ export const ZodString: core.$constructor<ZodString> = /*@__PURE__*/ core.$const
inst.cidrv4 = (params) => inst.check(core._cidrv4(ZodCIDRv4, params));
inst.cidrv6 = (params) => inst.check(core._cidrv6(ZodCIDRv6, params));
inst.e164 = (params) => inst.check(core._e164(ZodE164, params));
inst.script = (name, params) => inst.check(core._script(ZodScript, name, params));

// iso
inst.datetime = (params) => inst.check(iso.datetime(params as any));
Expand Down Expand Up @@ -537,6 +539,18 @@ export function emoji(params?: string | core.$ZodEmojiParams): ZodEmoji {
return core._emoji(ZodEmoji, params);
}

// ZodScript
export interface ZodScript extends ZodStringFormat<"script"> {
_zod: core.$ZodScriptInternals;
}
export const ZodScript: core.$constructor<ZodScript> = /*@__PURE__*/ core.$constructor("ZodScript", (inst, def) => {
core.$ZodScript.init(inst, def);
ZodStringFormat.init(inst, def);
});
export function script(name: string, params?: string | core.$ZodScriptParams): ZodScript {
return core._script(ZodScript, name, params);
}

// ZodNanoID
export interface ZodNanoID extends ZodStringFormat<"nanoid"> {
_zod: core.$ZodNanoIDInternals;
Expand Down
28 changes: 28 additions & 0 deletions packages/zod/src/v4/classic/tests/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,34 @@ test("emoji validations", () => {
expect(() => emoji.parse("stuff😀")).toThrow();
});

test("script validations", () => {
const arabic = z.script("Arabic");
arabic.parse("مرحبا");
arabic.parse("عالم");
arabic.parse("أمين");
expect(() => arabic.parse("hello")).toThrow();
expect(() => arabic.parse("مرحبا123")).toThrow();

const latin = z.script("Latin");
latin.parse("hello");
expect(() => latin.parse("مرحبا")).toThrow();

const cyrillic = z.script("Cyrillic");
cyrillic.parse("привет");
expect(() => cyrillic.parse("hello")).toThrow();

// method form is equivalent to the standalone factory
expect(() => z.string().script("Cyrillic").parse("hello")).toThrow();

expect(() => z.script("NotAScript")).toThrow(SyntaxError);

const result = arabic.safeParse("hello");
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues[0].code).toBe("invalid_format");
}
});

test("nanoid", () => {
const nanoid = z.string().nanoid("custom error");
nanoid.parse("lfNZluvAxMkf7Q8C5H-QS");
Expand Down
19 changes: 19 additions & 0 deletions packages/zod/src/v4/core/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,25 @@ export function _emoji<T extends schemas.$ZodEmoji>(
});
}

// Script
export type $ZodScriptParams = StringFormatParams<schemas.$ZodScript, "name" | "when">;
export type $ZodCheckScriptParams = CheckStringFormatParams<schemas.$ZodScript, "name" | "when">;
// @__NO_SIDE_EFFECTS__
export function _script<T extends schemas.$ZodScript>(
Class: util.SchemaClass<T>,
name: string,
params?: string | Omit<$ZodScriptParams, "name"> | Omit<$ZodCheckScriptParams, "name">
): T {
return new Class({
type: "string",
format: "script",
check: "string_format",
abort: false,
name,
...util.normalizeParams(params),
});
}

// NanoID
export type $ZodNanoIDParams = StringFormatParams<schemas.$ZodNanoID, "when">;
export type $ZodCheckNanoIDParams = CheckStringFormatParams<schemas.$ZodNanoID, "when">;
Expand Down
4 changes: 4 additions & 0 deletions packages/zod/src/v4/core/regexes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,7 @@ export const sha384_base64url: RegExp = /*@__PURE__*/ fixedBase64url(64);
export const sha512_hex: RegExp = /^[0-9a-fA-F]{128}$/;
export const sha512_base64: RegExp = /*@__PURE__*/ fixedBase64(86, "==");
export const sha512_base64url: RegExp = /*@__PURE__*/ fixedBase64url(86);

export function script(name: string): RegExp {
return new RegExp(`^\\p{Script=${name}}+$`, "u");
}
22 changes: 21 additions & 1 deletion packages/zod/src/v4/core/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,25 @@ export const $ZodEmoji: core.$constructor<$ZodEmoji> = /*@__PURE__*/ core.$const
}
);

////////////////////////////// ZodScript //////////////////////////////

export interface $ZodScriptDef extends $ZodStringFormatDef<"script"> {
name: string;
}
export interface $ZodScriptInternals extends $ZodStringFormatInternals<"script"> {
def: $ZodScriptDef;
}
export interface $ZodScript extends $ZodType {
_zod: $ZodScriptInternals;
}
export const $ZodScript: core.$constructor<$ZodScript> = /*@__PURE__*/ core.$constructor(
"$ZodScript",
(inst, def): void => {
def.pattern ??= regexes.script(def.name);
$ZodStringFormat.init(inst, def);
}
);

////////////////////////////// ZodNanoID //////////////////////////////

export interface $ZodNanoIDDef extends $ZodStringFormatDef<"nanoid"> {}
Expand Down Expand Up @@ -4566,4 +4585,5 @@ export type $ZodStringFormatTypes =
| $ZodJWT
| $ZodCustomStringFormat<"hex">
| $ZodCustomStringFormat<util.HashFormat>
| $ZodCustomStringFormat<"hostname">;
| $ZodCustomStringFormat<"hostname">
| $ZodScript;
17 changes: 17 additions & 0 deletions packages/zod/src/v4/mini/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,23 @@ export function emoji(params?: string | core.$ZodEmojiParams): ZodMiniEmoji {
return core._emoji(ZodMiniEmoji, params);
}

// ZodMiniScript
export interface ZodMiniScript extends _ZodMiniString<core.$ZodScriptInternals> {
// _zod: core.$ZodScriptInternals;
}
export const ZodMiniScript: core.$constructor<ZodMiniScript> = /*@__PURE__*/ core.$constructor(
"ZodMiniScript",
(inst, def) => {
core.$ZodScript.init(inst, def);
ZodMiniStringFormat.init(inst, def);
}
);

// @__NO_SIDE_EFFECTS__
export function script(name: string, params?: string | core.$ZodScriptParams): ZodMiniScript {
return core._script(ZodMiniScript, name, params);
}

// ZodMiniNanoID
export interface ZodMiniNanoID extends _ZodMiniString<core.$ZodNanoIDInternals> {
// _zod: core.$ZodNanoIDInternals;
Expand Down
Loading