feat!: use zod v4 (#10922)

* feat: zod 4

* feat: zod v3, but v4

feat: validation error class

Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>

* chore: bump

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>
This commit is contained in:
Almeida
2025-07-03 01:02:45 +01:00
committed by GitHub
parent 4dbeed933b
commit a5bd4cfe73
20 changed files with 183 additions and 199 deletions

View File

@@ -3,8 +3,7 @@ import {
InteractionContextType,
ApplicationCommandOptionType,
} from 'discord-api-types/v10';
import type { ZodTypeAny } from 'zod';
import { z } from 'zod';
import { z } from 'zod/v4';
import { localeMapPredicate, memberPermissionsPredicate } from '../../../Assertions.js';
import { ApplicationCommandOptionAllowedChannelTypes } from './mixins/ApplicationCommandOptionChannelTypesMixin.js';
@@ -24,26 +23,17 @@ const sharedNameAndDescriptionPredicate = z.object({
});
const numericMixinNumberOptionPredicate = z.object({
max_value: z.number().safe().optional(),
min_value: z.number().safe().optional(),
max_value: z.float32().optional(),
min_value: z.float32().optional(),
});
const numericMixinIntegerOptionPredicate = z.object({
max_value: z.number().safe().int().optional(),
min_value: z.number().safe().int().optional(),
max_value: z.int().optional(),
min_value: z.int().optional(),
});
const channelMixinOptionPredicate = z.object({
channel_types: z
.union(
ApplicationCommandOptionAllowedChannelTypes.map((type) => z.literal(type)) as unknown as [
ZodTypeAny,
ZodTypeAny,
...ZodTypeAny[],
],
)
.array()
.optional(),
channel_types: z.literal(ApplicationCommandOptionAllowedChannelTypes).array().optional(),
});
const autocompleteMixinOptionPredicate = z.object({
@@ -52,7 +42,7 @@ const autocompleteMixinOptionPredicate = z.object({
});
const choiceValueStringPredicate = z.string().min(1).max(100);
const choiceValueNumberPredicate = z.number().safe();
const choiceValueNumberPredicate = z.number();
const choiceBasePredicate = z.object({
name: choiceValueStringPredicate,
name_localizations: localeMapPredicate.optional(),
@@ -74,7 +64,7 @@ const choiceNumberMixinPredicate = choiceBaseMixinPredicate.extend({
choices: choiceNumberPredicate.array().max(25).optional(),
});
const basicOptionTypes = [
const basicOptionTypesPredicate = z.literal([
ApplicationCommandOptionType.Attachment,
ApplicationCommandOptionType.Boolean,
ApplicationCommandOptionType.Channel,
@@ -84,11 +74,7 @@ const basicOptionTypes = [
ApplicationCommandOptionType.Role,
ApplicationCommandOptionType.String,
ApplicationCommandOptionType.User,
] as const;
const basicOptionTypesPredicate = z.union(
basicOptionTypes.map((type) => z.literal(type)) as unknown as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]],
);
]);
export const basicOptionPredicate = sharedNameAndDescriptionPredicate.extend({
required: z.boolean().optional(),
@@ -105,14 +91,23 @@ const autocompleteOrNumberChoicesMixinOptionPredicate = z.discriminatedUnion('au
choiceNumberMixinPredicate,
]);
export const channelOptionPredicate = basicOptionPredicate.merge(channelMixinOptionPredicate);
export const channelOptionPredicate = z.object({
...basicOptionPredicate.shape,
...channelMixinOptionPredicate.shape,
});
export const integerOptionPredicate = basicOptionPredicate
.merge(numericMixinIntegerOptionPredicate)
export const integerOptionPredicate = z
.object({
...basicOptionPredicate.shape,
...numericMixinIntegerOptionPredicate.shape,
})
.and(autocompleteOrNumberChoicesMixinOptionPredicate);
export const numberOptionPredicate = basicOptionPredicate
.merge(numericMixinNumberOptionPredicate)
export const numberOptionPredicate = z
.object({
...basicOptionPredicate.shape,
...numericMixinNumberOptionPredicate.shape,
})
.and(autocompleteOrNumberChoicesMixinOptionPredicate);
export const stringOptionPredicate = basicOptionPredicate
@@ -123,9 +118,9 @@ export const stringOptionPredicate = basicOptionPredicate
.and(autocompleteOrStringChoicesMixinOptionPredicate);
const baseChatInputCommandPredicate = sharedNameAndDescriptionPredicate.extend({
contexts: z.array(z.nativeEnum(InteractionContextType)).optional(),
contexts: z.array(z.enum(InteractionContextType)).optional(),
default_member_permissions: memberPermissionsPredicate.optional(),
integration_types: z.array(z.nativeEnum(ApplicationIntegrationType)).optional(),
integration_types: z.array(z.enum(ApplicationIntegrationType)).optional(),
nsfw: z.boolean().optional(),
});

View File

@@ -17,7 +17,7 @@ export const ApplicationCommandOptionAllowedChannelTypes = [
/**
* Allowed channel types used for a channel option.
*/
export type ApplicationCommandOptionAllowedChannelTypes = (typeof ApplicationCommandOptionAllowedChannelTypes)[number];
export type ApplicationCommandOptionAllowedChannelType = (typeof ApplicationCommandOptionAllowedChannelTypes)[number];
export interface ApplicationCommandOptionChannelTypesData
extends Pick<APIApplicationCommandChannelOption, 'channel_types'> {}
@@ -36,7 +36,7 @@ export class ApplicationCommandOptionChannelTypesMixin {
*
* @param channelTypes - The channel types
*/
public addChannelTypes(...channelTypes: RestOrArray<ApplicationCommandOptionAllowedChannelTypes>) {
public addChannelTypes(...channelTypes: RestOrArray<ApplicationCommandOptionAllowedChannelType>) {
this.data.channel_types ??= [];
this.data.channel_types.push(...normalizeArray(channelTypes));
@@ -48,7 +48,7 @@ export class ApplicationCommandOptionChannelTypesMixin {
*
* @param channelTypes - The channel types
*/
public setChannelTypes(...channelTypes: RestOrArray<ApplicationCommandOptionAllowedChannelTypes>) {
public setChannelTypes(...channelTypes: RestOrArray<ApplicationCommandOptionAllowedChannelType>) {
this.data.channel_types = normalizeArray(channelTypes);
return this;
}

View File

@@ -4,7 +4,7 @@ import type {
APIApplicationCommandOption,
ApplicationCommandOptionType,
} from 'discord-api-types/v10';
import type { z } from 'zod';
import type { z } from 'zod/v4';
import { validate } from '../../../../util/validation.js';
import type { SharedNameAndDescriptionData } from '../../SharedNameAndDescription.js';
import { SharedNameAndDescription } from '../../SharedNameAndDescription.js';
@@ -24,7 +24,7 @@ export abstract class ApplicationCommandOptionBase
/**
* @internal
*/
protected static readonly predicate: z.ZodTypeAny = basicOptionPredicate;
protected static readonly predicate: z.ZodType = basicOptionPredicate;
/**
* @internal

View File

@@ -1,5 +1,5 @@
import { ApplicationCommandType, ApplicationIntegrationType, InteractionContextType } from 'discord-api-types/v10';
import { z } from 'zod';
import { z } from 'zod/v4';
import { localeMapPredicate, memberPermissionsPredicate } from '../../../Assertions.js';
const namePredicate = z
@@ -8,8 +8,8 @@ const namePredicate = z
.max(32)
.regex(/^(?:(?: *[\p{P}\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}\p{Extended_Pictographic}\p{Emoji_Component}]) *)+$/u);
const contextsPredicate = z.array(z.nativeEnum(InteractionContextType));
const integrationTypesPredicate = z.array(z.nativeEnum(ApplicationIntegrationType));
const contextsPredicate = z.array(z.enum(InteractionContextType));
const integrationTypesPredicate = z.array(z.enum(ApplicationIntegrationType));
const baseContextMenuCommandPredicate = z.object({
contexts: contextsPredicate.optional(),

View File

@@ -1,5 +1,5 @@
import { ComponentType } from 'discord-api-types/v10';
import { z } from 'zod';
import { z } from 'zod/v4';
import { customIdPredicate } from '../../Assertions.js';
const titlePredicate = z.string().min(1).max(45);