Skip to content

Commit 2c61ee5

Browse files
committed
feat: zod v3, but v4
1 parent b2e33a0 commit 2c61ee5

File tree

15 files changed

+105
-152
lines changed

15 files changed

+105
-152
lines changed

packages/builders/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
"discord-api-types": "^0.38.1",
7070
"ts-mixer": "^6.0.4",
7171
"tslib": "^2.8.1",
72-
"zod": "4.0.0-beta.20250430T185432"
72+
"zod": "3.25.49"
7373
},
7474
"devDependencies": {
7575
"@discordjs/api-extractor": "workspace:^",

packages/builders/src/Assertions.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
import { Locale } from 'discord-api-types/v10';
2-
import { z } from 'zod';
2+
import { z } from 'zod/v4';
33

44
export const customIdPredicate = z.string().min(1).max(100);
55

66
export const memberPermissionsPredicate = z.coerce.bigint();
77

8-
export const localeMapPredicate = z
9-
.object(
10-
Object.fromEntries(Object.values(Locale).map((loc) => [loc, z.string().optional()])) as Record<
11-
Locale,
12-
z.ZodOptional<z.ZodString>
13-
>,
14-
)
15-
.strict();
8+
export const localeMapPredicate = z.strictObject(
9+
Object.fromEntries(Object.values(Locale).map((loc) => [loc, z.string().optional()])) as Record<
10+
Locale,
11+
z.ZodOptional<z.ZodString>
12+
>,
13+
);
1614

1715
export const refineURLPredicate = (allowedProtocols: string[]) => (value: string) => {
1816
// eslint-disable-next-line n/prefer-global/url

packages/builders/src/components/Assertions.ts

Lines changed: 33 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import { ButtonStyle, ChannelType, ComponentType, SelectMenuDefaultValueType } from 'discord-api-types/v10';
2-
import { z } from 'zod';
2+
import { z } from 'zod/v4';
33
import { customIdPredicate, refineURLPredicate } from '../Assertions.js';
44

55
const labelPredicate = z.string().min(1).max(80);
66

77
export const emojiPredicate = z
8-
.object({
8+
.strictObject({
99
id: z.string().optional(),
1010
name: z.string().min(2).max(32).optional(),
1111
animated: z.boolean().optional(),
1212
})
13-
.strict()
1413
.refine((data) => data.id !== undefined || data.name !== undefined, {
15-
message: "Either 'id' or 'name' must be provided",
14+
error: "Either 'id' or 'name' must be provided",
1615
});
1716

18-
const buttonPredicateBase = z.object({
17+
const buttonPredicateBase = z.strictObject({
1918
type: z.literal(ComponentType.Button),
2019
disabled: z.boolean().optional(),
2120
});
@@ -26,31 +25,22 @@ const buttonCustomIdPredicateBase = buttonPredicateBase.extend({
2625
label: labelPredicate,
2726
});
2827

29-
const buttonPrimaryPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Primary) }).strict();
30-
const buttonSecondaryPredicate = buttonCustomIdPredicateBase
31-
.extend({ style: z.literal(ButtonStyle.Secondary) })
32-
.strict();
33-
const buttonSuccessPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Success) }).strict();
34-
const buttonDangerPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Danger) }).strict();
28+
const buttonPrimaryPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Primary) });
29+
const buttonSecondaryPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Secondary) });
30+
const buttonSuccessPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Success) });
31+
const buttonDangerPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Danger) });
3532

36-
const buttonLinkPredicate = buttonPredicateBase
37-
.extend({
38-
style: z.literal(ButtonStyle.Link),
39-
url: z
40-
.string()
41-
.url()
42-
.refine(refineURLPredicate(['http:', 'https:', 'discord:'])),
43-
emoji: emojiPredicate.optional(),
44-
label: labelPredicate,
45-
})
46-
.strict();
33+
const buttonLinkPredicate = buttonPredicateBase.extend({
34+
style: z.literal(ButtonStyle.Link),
35+
url: z.url().refine(refineURLPredicate(['http:', 'https:', 'discord:'])),
36+
emoji: emojiPredicate.optional(),
37+
label: labelPredicate,
38+
});
4739

48-
const buttonPremiumPredicate = buttonPredicateBase
49-
.extend({
50-
style: z.literal(ButtonStyle.Premium),
51-
sku_id: z.string(),
52-
})
53-
.strict();
40+
const buttonPremiumPredicate = buttonPredicateBase.extend({
41+
style: z.literal(ButtonStyle.Premium),
42+
sku_id: z.string(),
43+
});
5444

5545
export const buttonPredicate = z.discriminatedUnion('style', [
5646
buttonLinkPredicate,
@@ -71,7 +61,7 @@ const selectMenuBasePredicate = z.object({
7161

7262
export const selectMenuChannelPredicate = selectMenuBasePredicate.extend({
7363
type: z.literal(ComponentType.ChannelSelect),
74-
channel_types: z.nativeEnum(ChannelType).array().optional(),
64+
channel_types: z.enum(ChannelType).array().optional(),
7565
default_values: z
7666
.object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.Channel) })
7767
.array()
@@ -84,7 +74,7 @@ export const selectMenuMentionablePredicate = selectMenuBasePredicate.extend({
8474
default_values: z
8575
.object({
8676
id: z.string(),
87-
type: z.union([z.literal(SelectMenuDefaultValueType.Role), z.literal(SelectMenuDefaultValueType.User)]),
77+
type: z.literal([SelectMenuDefaultValueType.Role, SelectMenuDefaultValueType.User]),
8878
})
8979
.array()
9080
.max(25)
@@ -113,9 +103,9 @@ export const selectMenuStringPredicate = selectMenuBasePredicate
113103
type: z.literal(ComponentType.StringSelect),
114104
options: selectMenuStringOptionPredicate.array().min(1).max(25),
115105
})
116-
.superRefine((menu, ctx) => {
106+
.check((ctx) => {
117107
const addIssue = (name: string, minimum: number) =>
118-
ctx.addIssue({
108+
ctx.issues.push({
119109
code: 'too_small',
120110
message: `The number of options must be greater than or equal to ${name}`,
121111
inclusive: true,
@@ -126,12 +116,12 @@ export const selectMenuStringPredicate = selectMenuBasePredicate
126116
input: minimum,
127117
});
128118

129-
if (menu.max_values !== undefined && menu.options.length < menu.max_values) {
130-
addIssue('max_values', menu.max_values);
119+
if (ctx.value.max_values !== undefined && ctx.value.options.length < ctx.value.max_values) {
120+
addIssue('max_values', ctx.value.max_values);
131121
}
132122

133-
if (menu.min_values !== undefined && menu.options.length < menu.min_values) {
134-
addIssue('min_values', menu.min_values);
123+
if (ctx.value.min_values !== undefined && ctx.value.options.length < ctx.value.min_values) {
124+
addIssue('min_values', ctx.value.min_values);
135125
}
136126
});
137127

@@ -154,14 +144,13 @@ export const actionRowPredicate = z.object({
154144
.max(5),
155145
z
156146
.object({
157-
type: z.union([
158-
z.literal(ComponentType.ChannelSelect),
159-
z.literal(ComponentType.MentionableSelect),
160-
z.literal(ComponentType.RoleSelect),
161-
z.literal(ComponentType.StringSelect),
162-
z.literal(ComponentType.UserSelect),
163-
// And this!
164-
z.literal(ComponentType.TextInput),
147+
type: z.literal([
148+
ComponentType.ChannelSelect,
149+
ComponentType.MentionableSelect,
150+
ComponentType.StringSelect,
151+
ComponentType.RoleSelect,
152+
ComponentType.TextInput,
153+
ComponentType.UserSelect,
165154
]),
166155
})
167156
.array()

packages/builders/src/components/textInput/Assertions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { ComponentType, TextInputStyle } from 'discord-api-types/v10';
2-
import { z } from 'zod';
2+
import { z } from 'zod/v4';
33
import { customIdPredicate } from '../../Assertions.js';
44

55
export const textInputPredicate = z.object({
66
type: z.literal(ComponentType.TextInput),
77
custom_id: customIdPredicate,
88
label: z.string().min(1).max(45),
9-
style: z.nativeEnum(TextInputStyle),
9+
style: z.enum(TextInputStyle),
1010
min_length: z.number().min(0).max(4_000).optional(),
1111
max_length: z.number().min(1).max(4_000).optional(),
1212
placeholder: z.string().max(100).optional(),

packages/builders/src/components/v2/Assertions.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import { ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10';
2-
import { z } from 'zod';
2+
import { z } from 'zod/v4';
33
import { refineURLPredicate } from '../../Assertions.js';
44
import { actionRowPredicate } from '../Assertions.js';
55

66
const unfurledMediaItemPredicate = z.object({
7-
url: z
8-
.string()
9-
.url()
10-
.refine(refineURLPredicate(['http:', 'https:', 'attachment:']), {
11-
message: 'Invalid protocol for media URL. Must be http:, https:, or attachment:',
12-
}),
7+
url: z.url().refine(refineURLPredicate(['http:', 'https:', 'attachment:']), {
8+
error: 'Invalid protocol for media URL. Must be http:, https:, or attachment:',
9+
}),
1310
});
1411

1512
export const thumbnailPredicate = z.object({
@@ -19,12 +16,9 @@ export const thumbnailPredicate = z.object({
1916
});
2017

2118
const unfurledMediaItemAttachmentOnlyPredicate = z.object({
22-
url: z
23-
.string()
24-
.url()
25-
.refine(refineURLPredicate(['attachment:']), {
26-
message: 'Invalid protocol for file URL. Must be attachment:',
27-
}),
19+
url: z.url().refine(refineURLPredicate(['attachment:']), {
20+
error: 'Invalid protocol for file URL. Must be attachment:',
21+
}),
2822
});
2923

3024
export const filePredicate = z.object({
@@ -34,7 +28,7 @@ export const filePredicate = z.object({
3428

3529
export const separatorPredicate = z.object({
3630
divider: z.boolean().optional(),
37-
spacing: z.nativeEnum(SeparatorSpacingSize).optional(),
31+
spacing: z.enum(SeparatorSpacingSize).optional(),
3832
});
3933

4034
export const textDisplayPredicate = z.object({

packages/builders/src/interactions/commands/chatInput/Assertions.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import {
33
InteractionContextType,
44
ApplicationCommandOptionType,
55
} from 'discord-api-types/v10';
6-
import type { ZodTypeAny } from 'zod';
7-
import { z } from 'zod';
6+
import { z } from 'zod/v4';
87
import { localeMapPredicate, memberPermissionsPredicate } from '../../../Assertions.js';
98
import { ApplicationCommandOptionAllowedChannelTypes } from './mixins/ApplicationCommandOptionChannelTypesMixin.js';
109

@@ -35,13 +34,7 @@ const numericMixinIntegerOptionPredicate = z.object({
3534

3635
const channelMixinOptionPredicate = z.object({
3736
channel_types: z
38-
.union(
39-
ApplicationCommandOptionAllowedChannelTypes.map((type) => z.literal(type)) as unknown as [
40-
ZodTypeAny,
41-
ZodTypeAny,
42-
...ZodTypeAny[],
43-
],
44-
)
37+
.literal(ApplicationCommandOptionAllowedChannelTypes as unknown as ApplicationCommandOptionAllowedChannelTypes[])
4538
.array()
4639
.optional(),
4740
});
@@ -74,7 +67,7 @@ const choiceNumberMixinPredicate = choiceBaseMixinPredicate.extend({
7467
choices: choiceNumberPredicate.array().max(25).optional(),
7568
});
7669

77-
const basicOptionTypes = [
70+
const basicOptionTypesPredicate = z.literal([
7871
ApplicationCommandOptionType.Attachment,
7972
ApplicationCommandOptionType.Boolean,
8073
ApplicationCommandOptionType.Channel,
@@ -84,11 +77,7 @@ const basicOptionTypes = [
8477
ApplicationCommandOptionType.Role,
8578
ApplicationCommandOptionType.String,
8679
ApplicationCommandOptionType.User,
87-
] as const;
88-
89-
const basicOptionTypesPredicate = z.union(
90-
basicOptionTypes.map((type) => z.literal(type)) as unknown as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]],
91-
);
80+
]);
9281

9382
export const basicOptionPredicate = sharedNameAndDescriptionPredicate.extend({
9483
required: z.boolean().optional(),
@@ -123,9 +112,9 @@ export const stringOptionPredicate = basicOptionPredicate
123112
.and(autocompleteOrStringChoicesMixinOptionPredicate);
124113

125114
const baseChatInputCommandPredicate = sharedNameAndDescriptionPredicate.extend({
126-
contexts: z.array(z.nativeEnum(InteractionContextType)).optional(),
115+
contexts: z.array(z.enum(InteractionContextType)).optional(),
127116
default_member_permissions: memberPermissionsPredicate.optional(),
128-
integration_types: z.array(z.nativeEnum(ApplicationIntegrationType)).optional(),
117+
integration_types: z.array(z.enum(ApplicationIntegrationType)).optional(),
129118
nsfw: z.boolean().optional(),
130119
});
131120

packages/builders/src/interactions/commands/chatInput/options/ApplicationCommandOptionBase.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
APIApplicationCommandOption,
55
ApplicationCommandOptionType,
66
} from 'discord-api-types/v10';
7-
import type { z } from 'zod';
7+
import type { z } from 'zod/v4';
88
import { validate } from '../../../../util/validation.js';
99
import type { SharedNameAndDescriptionData } from '../../SharedNameAndDescription.js';
1010
import { SharedNameAndDescription } from '../../SharedNameAndDescription.js';
@@ -24,7 +24,7 @@ export abstract class ApplicationCommandOptionBase
2424
/**
2525
* @internal
2626
*/
27-
protected static readonly predicate: z.ZodTypeAny = basicOptionPredicate;
27+
protected static readonly predicate: z.ZodType = basicOptionPredicate;
2828

2929
/**
3030
* @internal

packages/builders/src/interactions/commands/contextMenu/Assertions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ApplicationCommandType, ApplicationIntegrationType, InteractionContextType } from 'discord-api-types/v10';
2-
import { z } from 'zod';
2+
import { z } from 'zod/v4';
33
import { localeMapPredicate, memberPermissionsPredicate } from '../../../Assertions.js';
44

55
const namePredicate = z
@@ -8,8 +8,8 @@ const namePredicate = z
88
.max(32)
99
.regex(/^(?:(?: *[\p{P}\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}\p{Extended_Pictographic}\p{Emoji_Component}]) *)+$/u);
1010

11-
const contextsPredicate = z.array(z.nativeEnum(InteractionContextType));
12-
const integrationTypesPredicate = z.array(z.nativeEnum(ApplicationIntegrationType));
11+
const contextsPredicate = z.array(z.enum(InteractionContextType));
12+
const integrationTypesPredicate = z.array(z.enum(ApplicationIntegrationType));
1313

1414
const baseContextMenuCommandPredicate = z.object({
1515
contexts: contextsPredicate.optional(),

packages/builders/src/interactions/modals/Assertions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ComponentType } from 'discord-api-types/v10';
2-
import { z } from 'zod';
2+
import { z } from 'zod/v4';
33
import { customIdPredicate } from '../../Assertions.js';
44

55
const titlePredicate = z.string().min(1).max(45);

0 commit comments

Comments
 (0)