Migrate from Zod v3 to v4 (#14639)

Closes [#1526](https://github.com/twentyhq/core-team-issues/issues/1526)

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Abdul Rahman
2025-09-24 21:59:05 +05:30
committed by GitHub
parent 8d78032357
commit 3cada58908
98 changed files with 809 additions and 1075 deletions
@@ -39,56 +39,56 @@ const isUniqueFieldFormSchema = z.object({
const booleanFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.BOOLEAN) })
.merge(settingsDataModelFieldBooleanFormSchema);
.extend(settingsDataModelFieldBooleanFormSchema.shape);
const currencyFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.CURRENCY) })
.merge(settingsDataModelFieldCurrencyFormSchema);
.extend(settingsDataModelFieldCurrencyFormSchema.shape);
const dateFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.DATE) })
.merge(settingsDataModelFieldDateFormSchema)
.merge(isUniqueFieldFormSchema);
.extend(settingsDataModelFieldDateFormSchema.shape)
.extend(isUniqueFieldFormSchema.shape);
const dateTimeFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.DATE_TIME) })
.merge(settingsDataModelFieldDateFormSchema)
.merge(isUniqueFieldFormSchema);
.extend(settingsDataModelFieldDateFormSchema.shape)
.extend(isUniqueFieldFormSchema.shape);
const relationFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.RELATION) })
.merge(settingsDataModelFieldRelationFormSchema);
.extend(settingsDataModelFieldRelationFormSchema.shape);
const morphRelationFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.MORPH_RELATION) })
.merge(settingsDataModelFieldMorphRelationFormSchema);
.extend(settingsDataModelFieldMorphRelationFormSchema.shape);
const selectFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.SELECT) })
.merge(settingsDataModelFieldSelectFormSchema);
.extend(settingsDataModelFieldSelectFormSchema.shape);
const multiSelectFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.MULTI_SELECT) })
.merge(settingsDataModelFieldMultiSelectFormSchema);
.extend(settingsDataModelFieldMultiSelectFormSchema.shape);
const numberFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.NUMBER) })
.merge(settingsDataModelFieldNumberFormSchema)
.merge(isUniqueFieldFormSchema);
.extend(settingsDataModelFieldNumberFormSchema.shape)
.extend(isUniqueFieldFormSchema.shape);
const textFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.TEXT) })
.merge(settingsDataModelFieldTextFormSchema)
.merge(isUniqueFieldFormSchema);
.extend(settingsDataModelFieldTextFormSchema.shape)
.extend(isUniqueFieldFormSchema.shape);
const addressFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.ADDRESS) })
.merge(settingsDataModelFieldAddressFormSchema);
.extend(settingsDataModelFieldAddressFormSchema.shape);
const phonesFieldFormSchema = z
.object({ type: z.literal(FieldMetadataType.PHONES) })
.merge(settingsDataModelFieldPhonesFormSchema)
.merge(isUniqueFieldFormSchema);
.extend(settingsDataModelFieldPhonesFormSchema.shape)
.extend(isUniqueFieldFormSchema.shape);
const otherFieldsFormSchema = z
.object({
@@ -111,7 +111,7 @@ const otherFieldsFormSchema = z
) as [FieldMetadataType, ...FieldMetadataType[]],
),
})
.merge(isUniqueFieldFormSchema);
.extend(isUniqueFieldFormSchema.shape);
export const settingsDataModelFieldSettingsFormSchema = z.discriminatedUnion(
'type',
@@ -25,7 +25,7 @@ import { isDefined } from 'twenty-shared/utils';
import { RelationType } from '~/generated-metadata/graphql';
export const settingsDataModelFieldMorphRelationFormSchema = z.object({
morphRelationObjectMetadataIds: z.array(z.string().uuid()).min(2),
morphRelationObjectMetadataIds: z.array(z.uuid()).min(2),
relationType: z.enum(
Object.keys(RELATION_TYPES) as [RelationType, ...RelationType[]],
),
@@ -25,15 +25,15 @@ export const settingsDataModelFieldRelationFormSchema = z.object({
label: true,
})
// NOT SURE IF THIS IS CORRECT
.merge(
.extend(
fieldMetadataItemSchema()
.pick({
name: true,
isLabelSyncedWithName: true,
})
.partial(),
.partial().shape,
),
objectMetadataId: z.string().uuid(),
objectMetadataId: z.uuid(),
type: z.enum(
Object.keys(RELATION_TYPES) as [RelationType, ...RelationType[]],
),
@@ -7,8 +7,10 @@ import { settingsDataModelFieldTypeFormSchema } from '~/pages/settings/data-mode
export const settingsFieldFormSchema = (existingOtherLabels?: string[]) => {
return z
.object({})
.merge(settingsDataModelFieldIconLabelFormSchema(existingOtherLabels))
.merge(settingsDataModelFieldDescriptionFormSchema())
.merge(settingsDataModelFieldTypeFormSchema)
.extend(
settingsDataModelFieldIconLabelFormSchema(existingOtherLabels).shape,
)
.extend(settingsDataModelFieldDescriptionFormSchema().shape)
.extend(settingsDataModelFieldTypeFormSchema.shape)
.and(settingsDataModelFieldSettingsFormSchema);
};
@@ -27,8 +27,9 @@ export const getMultiSelectFieldPreviewValue = ({
return multiSelectFieldDefaultValueSchema(fieldMetadataItem.options)
.refine(isDefined)
.transform((value) =>
value.map(stripSimpleQuotesFromString).filter(isNonEmptyString),
.transform(
(value) =>
value?.map(stripSimpleQuotesFromString).filter(isNonEmptyString) ?? [],
)
.refine(isNonEmptyArray)
.catch(allOptionValues)
@@ -26,7 +26,7 @@ export const getSelectFieldPreviewValue = ({
return selectFieldDefaultValueSchema(fieldMetadataItem.options)
.refine(isDefined)
.transform(stripSimpleQuotesFromString)
.transform((value) => stripSimpleQuotesFromString(value ?? ''))
.refine(isNonEmptyString)
.catch(firstOptionValue)
.parse(fieldMetadataItem.defaultValue);
@@ -1,7 +1,7 @@
import styled from '@emotion/styled';
import { useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { ZodError, isDirty, type z } from 'zod';
import { ZodError, type z } from 'zod';
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
@@ -54,7 +54,7 @@ export const SettingsDataModelObjectIdentifiersForm = ({
const handleSave = async (
formValues: SettingsDataModelObjectIdentifiersFormValues,
) => {
if (!isDirty) {
if (!formConfig.formState.isDirty) {
return;
}
@@ -3,13 +3,12 @@
exports[`settingsDataModelObjectAboutFormSchema fails when isLabelSyncedWithName is not a boolean 1`] = `
[ZodError: [
{
"code": "invalid_type",
"expected": "boolean",
"received": "string",
"code": "invalid_type",
"path": [
"isLabelSyncedWithName"
],
"message": "Expected boolean, received string"
"message": "Invalid input: expected boolean, received string"
}
]]
`;
@@ -17,26 +16,24 @@ exports[`settingsDataModelObjectAboutFormSchema fails when isLabelSyncedWithName
exports[`settingsDataModelObjectAboutFormSchema fails when labels are empty strings 1`] = `
[ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"type": "string",
"inclusive": true,
"exact": false,
"message": "String must contain at least 1 character(s)",
"path": [
"labelSingular"
]
],
"message": "Too small: expected string to have >=1 characters"
},
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"type": "string",
"inclusive": true,
"exact": false,
"message": "String must contain at least 1 character(s)",
"path": [
"labelPlural"
]
],
"message": "Too small: expected string to have >=1 characters"
},
{
"code": "custom",
@@ -59,17 +56,17 @@ exports[`settingsDataModelObjectAboutFormSchema fails when names are not in came
[ZodError: [
{
"code": "custom",
"message": "String should be camel case",
"path": [
"namePlural"
]
],
"message": "String should be camel case"
},
{
"code": "custom",
"message": "String should be camel case",
"path": [
"nameSingular"
]
],
"message": "String should be camel case"
}
]]
`;
@@ -77,22 +74,20 @@ exports[`settingsDataModelObjectAboutFormSchema fails when names are not in came
exports[`settingsDataModelObjectAboutFormSchema fails when required fields are missing 1`] = `
[ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"code": "invalid_type",
"path": [
"labelSingular"
],
"message": "Required"
"message": "Invalid input: expected string, received undefined"
},
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"code": "invalid_type",
"path": [
"labelPlural"
],
"message": "Required"
"message": "Invalid input: expected string, received undefined"
}
]]
`;
@@ -138,22 +133,20 @@ exports[`settingsDataModelObjectAboutFormSchema fails when singular and plural n
exports[`settingsDataModelObjectAboutFormSchema fails with invalid types for optional fields 1`] = `
[ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "number",
"code": "invalid_type",
"path": [
"description"
],
"message": "Expected string, received number"
"message": "Invalid input: expected string, received number"
},
{
"code": "invalid_type",
"expected": "string",
"received": "boolean",
"code": "invalid_type",
"path": [
"icon"
],
"message": "Expected string, received boolean"
"message": "Invalid input: expected string, received boolean"
}
]]
`;
@@ -39,7 +39,7 @@ export const settingsDataModelObjectAboutFormSchema =
];
labelFields.forEach((field) =>
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: 'custom',
message: t`Singular and plural labels must be different`,
path: [field],
}),
@@ -55,7 +55,7 @@ export const settingsDataModelObjectAboutFormSchema =
];
nameFields.forEach((field) =>
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: 'custom',
message: t`Singular and plural names must be different`,
path: [field],
}),