mirror of
https://github.com/twentyhq/twenty.git
synced 2026-04-18 14:01:45 -04:00
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:
@@ -71,7 +71,7 @@
|
||||
"uuid": "^9.0.0",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"xlsx-ugnis": "^0.19.3",
|
||||
"zod": "3.23.8"
|
||||
"zod": "^4.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.5",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"ora": "^8.0.0",
|
||||
"yaml": "^2.4.0",
|
||||
"zod": "^3.22.0"
|
||||
"zod": "^4.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.0",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"@graphiql/plugin-explorer": "^1.0.2",
|
||||
"@graphiql/react": "^0.23.0",
|
||||
"@hello-pangea/dnd": "^16.2.0",
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@lingui/core": "^5.1.2",
|
||||
"@lingui/detect-locale": "^5.2.0",
|
||||
"@lingui/react": "^5.1.2",
|
||||
|
||||
@@ -17,7 +17,10 @@ const makeValidationSchema = (signInUpStep: SignInUpStep) =>
|
||||
z
|
||||
.object({
|
||||
exist: z.boolean(),
|
||||
email: z.string().trim().email('Email must be a valid email'),
|
||||
email: z
|
||||
.string()
|
||||
.trim()
|
||||
.pipe(z.email({ error: 'Email must be a valid email' })),
|
||||
password:
|
||||
signInUpStep === SignInUpStep.Password
|
||||
? z
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { metadataLabelSchema } from '@/object-metadata/validation-schemas/metadataLabelSchema';
|
||||
import { themeColorSchema } from 'twenty-ui/theme';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
import { FieldMetadataType, RelationType } from '~/generated/graphql';
|
||||
import { camelCaseStringSchema } from '~/utils/validation-schemas/camelCaseStringSchema';
|
||||
|
||||
export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
|
||||
return z.object({
|
||||
__typename: z.literal('Field').optional(),
|
||||
createdAt: z.string().datetime(),
|
||||
createdAt: z.iso.datetime(),
|
||||
defaultValue: z.any().optional(),
|
||||
description: z.string().trim().nullable().optional(),
|
||||
icon: z
|
||||
.union([z.string().startsWith('Icon').trim(), z.literal('')])
|
||||
.nullable()
|
||||
.optional(),
|
||||
id: z.string().uuid(),
|
||||
id: z.uuid(),
|
||||
isActive: z.boolean(),
|
||||
isCustom: z.boolean(),
|
||||
isNullable: z.boolean(),
|
||||
@@ -30,7 +29,7 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
|
||||
.array(
|
||||
z.object({
|
||||
color: themeColorSchema,
|
||||
id: z.string().uuid(),
|
||||
id: z.uuid(),
|
||||
label: z.string().trim().min(1),
|
||||
position: z.number(),
|
||||
value: z.string().trim().min(1),
|
||||
@@ -42,33 +41,33 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
|
||||
relation: z
|
||||
.object({
|
||||
__typename: z.literal('Relation').optional(),
|
||||
type: z.nativeEnum(RelationType),
|
||||
type: z.enum(RelationType),
|
||||
sourceFieldMetadata: z.object({
|
||||
__typename: z.literal('Field').optional(),
|
||||
id: z.string().uuid(),
|
||||
id: z.uuid(),
|
||||
name: z.string().trim().min(1),
|
||||
}),
|
||||
sourceObjectMetadata: z.object({
|
||||
__typename: z.literal('Object').optional(),
|
||||
id: z.string().uuid(),
|
||||
id: z.uuid(),
|
||||
namePlural: z.string().trim().min(1),
|
||||
nameSingular: z.string().trim().min(1),
|
||||
}),
|
||||
targetFieldMetadata: z.object({
|
||||
__typename: z.literal('Field').optional(),
|
||||
id: z.string().uuid(),
|
||||
id: z.uuid(),
|
||||
name: z.string().trim().min(1),
|
||||
}),
|
||||
targetObjectMetadata: z.object({
|
||||
__typename: z.literal('Object').optional(),
|
||||
id: z.string().uuid(),
|
||||
id: z.uuid(),
|
||||
namePlural: z.string().trim().min(1),
|
||||
nameSingular: z.string().trim().min(1),
|
||||
}),
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
type: z.nativeEnum(FieldMetadataType),
|
||||
updatedAt: z.string().datetime(),
|
||||
}) satisfies z.ZodType<FieldMetadataItem>;
|
||||
type: z.enum(FieldMetadataType),
|
||||
updatedAt: z.iso.datetime(),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { type IndexFieldMetadataItem } from '@/object-metadata/types/IndexFieldM
|
||||
|
||||
export const indexFieldMetadataItemSchema = z.object({
|
||||
__typename: z.literal('IndexField'),
|
||||
fieldMetadataId: z.string().uuid(),
|
||||
fieldMetadataId: z.uuid(),
|
||||
id: z.string(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
|
||||
@@ -2,16 +2,16 @@ import { z } from 'zod';
|
||||
|
||||
import { type IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem';
|
||||
import { indexFieldMetadataItemSchema } from '@/object-metadata/validation-schemas/indexFieldMetadataItemSchema';
|
||||
import { IndexType } from '~/generated-metadata/graphql';
|
||||
import { IndexType } from '~/generated/graphql';
|
||||
|
||||
export const indexMetadataItemSchema = z.object({
|
||||
__typename: z.literal('Index'),
|
||||
id: z.string().uuid(),
|
||||
id: z.uuid(),
|
||||
name: z.string(),
|
||||
indexFieldMetadatas: z.array(indexFieldMetadataItemSchema),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
indexType: z.nativeEnum(IndexType),
|
||||
indexType: z.enum(IndexType),
|
||||
indexWhereClause: z.string().nullable(),
|
||||
isUnique: z.boolean(),
|
||||
objectMetadata: z.any(),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema';
|
||||
import { indexMetadataItemSchema } from '@/object-metadata/validation-schemas/indexMetadataItemSchema';
|
||||
import { metadataLabelSchema } from '@/object-metadata/validation-schemas/metadataLabelSchema';
|
||||
@@ -8,28 +7,28 @@ import { camelCaseStringSchema } from '~/utils/validation-schemas/camelCaseStrin
|
||||
|
||||
export const objectMetadataItemSchema = z.object({
|
||||
__typename: z.literal('Object').optional(),
|
||||
createdAt: z.string().datetime(),
|
||||
createdAt: z.iso.datetime(),
|
||||
description: z.string().trim().nullable().optional(),
|
||||
fields: z.array(fieldMetadataItemSchema()),
|
||||
readableFields: z.array(fieldMetadataItemSchema()),
|
||||
updatableFields: z.array(fieldMetadataItemSchema()),
|
||||
indexMetadatas: z.array(indexMetadataItemSchema),
|
||||
icon: z.string().startsWith('Icon').trim(),
|
||||
id: z.string().uuid(),
|
||||
id: z.uuid(),
|
||||
duplicateCriteria: z.array(z.array(z.string())),
|
||||
imageIdentifierFieldMetadataId: z.string().uuid().nullable(),
|
||||
imageIdentifierFieldMetadataId: z.uuid().nullable(),
|
||||
isActive: z.boolean(),
|
||||
isCustom: z.boolean(),
|
||||
isRemote: z.boolean(),
|
||||
isSystem: z.boolean(),
|
||||
isUIReadOnly: z.boolean(),
|
||||
isSearchable: z.boolean(),
|
||||
labelIdentifierFieldMetadataId: z.string().uuid(),
|
||||
labelIdentifierFieldMetadataId: z.uuid(),
|
||||
labelPlural: metadataLabelSchema(),
|
||||
labelSingular: metadataLabelSchema(),
|
||||
namePlural: camelCaseStringSchema,
|
||||
nameSingular: camelCaseStringSchema,
|
||||
updatedAt: z.string().datetime(),
|
||||
updatedAt: z.iso.datetime(),
|
||||
shortcut: z.string().nullable().optional(),
|
||||
isLabelSyncedWithName: z.boolean(),
|
||||
}) satisfies z.ZodType<ObjectMetadataItem>;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { themeColorSchema } from 'twenty-ui/theme';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/computeOptionValueFromLabel';
|
||||
|
||||
@@ -22,9 +21,9 @@ const selectOptionSchema = z
|
||||
}
|
||||
},
|
||||
{
|
||||
message: 'Label is not transliterable',
|
||||
error: 'Label is not transliterable',
|
||||
},
|
||||
) satisfies z.ZodType<FieldMetadataItemOption>;
|
||||
);
|
||||
|
||||
export const selectOptionsSchema = z
|
||||
.array(selectOptionSchema)
|
||||
@@ -35,7 +34,7 @@ export const selectOptionsSchema = z
|
||||
return new Set(optionIds).size === options.length;
|
||||
},
|
||||
{
|
||||
message: 'Options must have unique ids',
|
||||
error: 'Options must have unique ids',
|
||||
},
|
||||
)
|
||||
.refine(
|
||||
@@ -44,13 +43,13 @@ export const selectOptionsSchema = z
|
||||
return new Set(optionValues).size === options.length;
|
||||
},
|
||||
{
|
||||
message: 'Options must have unique values',
|
||||
error: 'Options must have unique values',
|
||||
},
|
||||
)
|
||||
.refine(
|
||||
(options) =>
|
||||
[...options].sort().every((option, index) => option.position === index),
|
||||
{
|
||||
message: 'Options positions must be sequential',
|
||||
error: 'Options positions must be sequential',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type WorkflowRunState } from '@/workflow/types/Workflow';
|
||||
import { workflowRunStateSchema } from '@/workflow/validation-schemas/workflowSchema';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { workflowRunStateSchema } from 'twenty-shared/workflow';
|
||||
import { type JsonValue } from 'type-fest';
|
||||
|
||||
export const orderWorkflowRunState = (value: JsonValue) => {
|
||||
|
||||
@@ -290,7 +290,7 @@ export const FieldActorValueSchema = z.object({
|
||||
name: z.string(),
|
||||
context: z
|
||||
.object({
|
||||
provider: z.nativeEnum(ConnectedAccountProvider).optional(),
|
||||
provider: z.enum(ConnectedAccountProvider).optional(),
|
||||
})
|
||||
.nullable(),
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CurrencyCode } from '../CurrencyCode';
|
||||
import { type FieldCurrencyValue } from '../FieldMetadata';
|
||||
|
||||
const currencySchema = z.object({
|
||||
currencyCode: z.nativeEnum(CurrencyCode).nullable(),
|
||||
currencyCode: z.enum(CurrencyCode).nullable(),
|
||||
amountMicros: z.number().nullable(),
|
||||
});
|
||||
|
||||
|
||||
@@ -5,13 +5,17 @@ import { type FieldJsonValue, type Json } from '../FieldMetadata';
|
||||
// See https://zod.dev/?id=json-type
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
||||
const jsonSchema: z.ZodType<Json> = z.lazy(() =>
|
||||
z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]),
|
||||
z.union([
|
||||
literalSchema,
|
||||
z.array(jsonSchema),
|
||||
z.record(z.string(), jsonSchema),
|
||||
]),
|
||||
);
|
||||
|
||||
export const jsonWithoutLiteralsSchema: z.ZodType<FieldJsonValue> = z.union([
|
||||
z.null(), // Exclude literal values other than null
|
||||
z.array(jsonSchema),
|
||||
z.record(jsonSchema),
|
||||
z.record(z.string(), jsonSchema),
|
||||
]);
|
||||
|
||||
export const isFieldRawJsonValue = (
|
||||
|
||||
@@ -2,4 +2,4 @@ import { z } from 'zod';
|
||||
|
||||
import { CurrencyCode } from '@/object-record/record-field/ui/types/CurrencyCode';
|
||||
|
||||
export const currencyCodeSchema = z.nativeEnum(CurrencyCode);
|
||||
export const currencyCodeSchema = z.enum(CurrencyCode);
|
||||
|
||||
@@ -10,6 +10,8 @@ export const currencyFieldDefaultValueSchema = z.object({
|
||||
currencyCode: simpleQuotesStringSchema.refine(
|
||||
(value): value is `'${CurrencyCode}'` =>
|
||||
currencyCodeSchema.safeParse(stripSimpleQuotesFromString(value)).success,
|
||||
{ message: 'String is not a valid currencyCode' },
|
||||
{
|
||||
error: 'String is not a valid currencyCode',
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const emailSchema = z.string().email();
|
||||
export const emailSchema = z.email();
|
||||
|
||||
@@ -41,7 +41,7 @@ export const SettingsAccountsBlocklistInput = ({
|
||||
emailOrDomain: z
|
||||
.string()
|
||||
.trim()
|
||||
.email(t`Invalid email or domain`)
|
||||
.pipe(z.email({ error: t`Invalid email or domain` }))
|
||||
.or(
|
||||
z.string().refine(
|
||||
(value) =>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { type ConnectionParameters } from '~/generated/graphql';
|
||||
const connectionParameters = z
|
||||
.object({
|
||||
host: z.string().default(''),
|
||||
port: z.number().int().nullable().default(null),
|
||||
port: z.int().nullable().default(null),
|
||||
username: z.string().optional(),
|
||||
password: z.string().default(''),
|
||||
secure: z.boolean().default(true),
|
||||
@@ -18,14 +18,14 @@ const connectionParameters = z
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: 'Port must be a positive number when configuring this protocol',
|
||||
path: ['port'],
|
||||
error: 'Port must be a positive number when configuring this protocol',
|
||||
},
|
||||
);
|
||||
|
||||
export const connectionImapSmtpCalDav = z
|
||||
.object({
|
||||
handle: z.string().email('Invalid email address'),
|
||||
handle: z.email('Invalid email address'),
|
||||
IMAP: connectionParameters.optional(),
|
||||
SMTP: connectionParameters.optional(),
|
||||
CALDAV: connectionParameters.optional(),
|
||||
@@ -37,9 +37,9 @@ export const connectionImapSmtpCalDav = z
|
||||
);
|
||||
},
|
||||
{
|
||||
message:
|
||||
'At least one account type (IMAP, SMTP, or CalDAV) must be completely configured',
|
||||
path: ['handle'],
|
||||
error:
|
||||
'At least one account type (IMAP, SMTP, or CalDAV) must be completely configured',
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -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],
|
||||
}),
|
||||
|
||||
@@ -7,7 +7,7 @@ export const webhookFormSchema = z.object({
|
||||
.trim()
|
||||
.min(1, 'URL is required')
|
||||
.refine((url) => isValidUrl(url), {
|
||||
message: 'Please enter a valid URL',
|
||||
error: 'Please enter a valid URL',
|
||||
}),
|
||||
description: z.string().optional(),
|
||||
operations: z
|
||||
@@ -22,7 +22,7 @@ export const webhookFormSchema = z.object({
|
||||
(operations) =>
|
||||
operations.some((op) => op.object !== null && op.action !== null),
|
||||
{
|
||||
message: 'At least one complete operation is required',
|
||||
error: 'At least one complete operation is required',
|
||||
},
|
||||
),
|
||||
secret: z.string().optional(),
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const settingsIntegrationsDatabaseTablesSchema = z.object({
|
||||
syncedTablesByName: z.record(z.boolean()),
|
||||
syncedTablesByName: z.record(z.string(), z.boolean()),
|
||||
});
|
||||
|
||||
export type SettingsIntegrationsDatabaseTablesFormValues = z.infer<
|
||||
|
||||
@@ -19,8 +19,8 @@ import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const playgroundSetupFormSchema = z.object({
|
||||
apiKeyForPlayground: z.string(),
|
||||
schema: z.nativeEnum(PlaygroundSchemas),
|
||||
playgroundType: z.nativeEnum(PlaygroundTypes),
|
||||
schema: z.enum(PlaygroundSchemas),
|
||||
playgroundType: z.enum(PlaygroundTypes),
|
||||
});
|
||||
|
||||
type PlaygroundSetupFormValues = z.infer<typeof playgroundSetupFormSchema>;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const validator = z.object({
|
||||
entityID: z.string().url(),
|
||||
ssoUrl: z.string().url(),
|
||||
entityID: z.url(),
|
||||
ssoUrl: z.url(),
|
||||
certificate: z.string().min(1),
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export const SSOIdentitiesProvidersSAMLParamsSchema = z
|
||||
.object({
|
||||
type: z.literal('SAML'),
|
||||
id: z.string().nonempty(),
|
||||
ssoURL: z.string().url().nonempty(),
|
||||
ssoURL: z.url().nonempty(),
|
||||
certificate: z.string().nonempty(),
|
||||
})
|
||||
.required();
|
||||
@@ -28,7 +28,7 @@ export const SSOIdentitiesProvidersParamsSchema = z
|
||||
z
|
||||
.object({
|
||||
name: z.string().nonempty(),
|
||||
issuer: z.string().url().nonempty(),
|
||||
issuer: z.url().nonempty(),
|
||||
})
|
||||
.required(),
|
||||
);
|
||||
|
||||
@@ -23,8 +23,9 @@ const filterQueryParamsSchema = z.object({
|
||||
viewId: z.string().optional(),
|
||||
filter: z
|
||||
.record(
|
||||
z.record(
|
||||
z.nativeEnum(ViewFilterOperand),
|
||||
z.string(),
|
||||
z.partialRecord(
|
||||
z.enum(ViewFilterOperand),
|
||||
z.string().or(z.array(z.string())).or(relationFilterValueSchemaObject),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ export const variableDateViewFilterValuePartsSchema = z
|
||||
unit: variableDateViewFilterValueUnitSchema,
|
||||
})
|
||||
.refine((data) => !(data.amount === undefined && data.direction !== 'THIS'), {
|
||||
message: "Amount cannot be 'undefined' unless direction is 'THIS'",
|
||||
error: "Amount cannot be 'undefined' unless direction is 'THIS'",
|
||||
});
|
||||
|
||||
const variableDateViewFilterValueSchema = z.string().transform((value) => {
|
||||
|
||||
@@ -14,6 +14,6 @@ export const arrayOfStringsOrVariablesSchema = z
|
||||
(parsed) =>
|
||||
Array.isArray(parsed) && parsed.every((item) => typeof item === 'string'),
|
||||
{
|
||||
message: 'Expected an array of strings',
|
||||
error: 'Expected an array of strings',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ export const jsonRelationFilterValueSchema = z
|
||||
return JSON.parse(value);
|
||||
} catch (error) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
code: 'custom',
|
||||
message: (error as Error).message,
|
||||
});
|
||||
return z.NEVER;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { type WorkflowRun } from '@/workflow/types/Workflow';
|
||||
import { workflowRunSchema } from '@/workflow/validation-schemas/workflowSchema';
|
||||
import { useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { workflowRunSchema } from 'twenty-shared/workflow';
|
||||
|
||||
export const useWorkflowRun = ({
|
||||
workflowRunId,
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
type workflowTriggerSchema,
|
||||
type workflowUpdateRecordActionSchema,
|
||||
type workflowWebhookTriggerSchema,
|
||||
} from '@/workflow/validation-schemas/workflowSchema';
|
||||
} from 'twenty-shared/workflow';
|
||||
import { type z } from 'zod';
|
||||
|
||||
export type WorkflowCodeAction = z.infer<typeof workflowCodeActionSchema>;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const schema = z
|
||||
.record(z.any())
|
||||
.record(z.string(), z.any())
|
||||
.refine((data) => Object.keys(data).every((key) => !key.match(/\s/)), {
|
||||
message: 'JSON keys cannot contain spaces',
|
||||
error: 'JSON keys cannot contain spaces',
|
||||
});
|
||||
|
||||
export const parseAndValidateVariableFriendlyStringifiedJson = (
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { StepStatus } from 'twenty-shared/workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const objectRecordSchema = z.record(z.any());
|
||||
|
||||
export const baseWorkflowActionSettingsSchema = z.object({
|
||||
input: z.object({}).passthrough(),
|
||||
outputSchema: z.object({}).passthrough(),
|
||||
errorHandlingOptions: z.object({
|
||||
retryOnFailure: z.object({
|
||||
value: z.boolean(),
|
||||
}),
|
||||
continueOnFailure: z.object({
|
||||
value: z.boolean(),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const baseWorkflowActionSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
valid: z.boolean(),
|
||||
nextStepIds: z.array(z.string()).optional().nullable(),
|
||||
position: z.object({ x: z.number(), y: z.number() }).optional().nullable(),
|
||||
});
|
||||
|
||||
export const baseTriggerSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
type: z.string(),
|
||||
position: z.object({ x: z.number(), y: z.number() }).optional().nullable(),
|
||||
nextStepIds: z.array(z.string()).optional().nullable(),
|
||||
});
|
||||
|
||||
export const workflowCodeActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
serverlessFunctionId: z.string(),
|
||||
serverlessFunctionVersion: z.string(),
|
||||
serverlessFunctionInput: z.record(z.any()),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowSendEmailActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
connectedAccountId: z.string(),
|
||||
email: z.string(),
|
||||
subject: z.string().optional(),
|
||||
body: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowCreateRecordActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z.string(),
|
||||
objectRecord: objectRecordSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowUpdateRecordActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z.string(),
|
||||
objectRecord: objectRecordSchema,
|
||||
objectRecordId: z.string(),
|
||||
fieldsToUpdate: z.array(z.string()),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowDeleteRecordActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z.string(),
|
||||
objectRecordId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowFindRecordsActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z.string(),
|
||||
limit: z.number().optional(),
|
||||
filter: z
|
||||
.object({
|
||||
recordFilterGroups: z.array(z.object({})).optional(),
|
||||
recordFilters: z.array(z.object({})).optional(),
|
||||
gqlOperationFilter: z.object({}).optional().nullable(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowFormActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
label: z.string(),
|
||||
type: z.union([
|
||||
z.literal(FieldMetadataType.TEXT),
|
||||
z.literal(FieldMetadataType.NUMBER),
|
||||
z.literal(FieldMetadataType.DATE),
|
||||
z.literal(FieldMetadataType.SELECT),
|
||||
z.literal('RECORD'),
|
||||
]),
|
||||
placeholder: z.string().optional(),
|
||||
settings: z.record(z.any()).optional(),
|
||||
value: z.any().optional(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export const workflowHttpRequestActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
url: z.string(),
|
||||
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
|
||||
headers: z.record(z.string()).optional(),
|
||||
body: z
|
||||
.record(
|
||||
z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.null(),
|
||||
z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])),
|
||||
]),
|
||||
)
|
||||
.or(z.string())
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowAiAgentActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
agentId: z.string().optional(),
|
||||
prompt: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowFilterActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
stepFilterGroups: z.array(z.any()),
|
||||
stepFilters: z.array(z.any()),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowIteratorActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
items: z
|
||||
.union([
|
||||
z.array(
|
||||
z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.null(),
|
||||
z.record(z.any()),
|
||||
z.any(),
|
||||
]),
|
||||
),
|
||||
z.string(),
|
||||
])
|
||||
.optional(),
|
||||
initialLoopStepIds: z.array(z.string()).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowEmptyActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({}),
|
||||
});
|
||||
|
||||
export const workflowCodeActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('CODE'),
|
||||
settings: workflowCodeActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowSendEmailActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('SEND_EMAIL'),
|
||||
settings: workflowSendEmailActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowCreateRecordActionSchema = baseWorkflowActionSchema.extend(
|
||||
{
|
||||
type: z.literal('CREATE_RECORD'),
|
||||
settings: workflowCreateRecordActionSettingsSchema,
|
||||
},
|
||||
);
|
||||
|
||||
export const workflowUpdateRecordActionSchema = baseWorkflowActionSchema.extend(
|
||||
{
|
||||
type: z.literal('UPDATE_RECORD'),
|
||||
settings: workflowUpdateRecordActionSettingsSchema,
|
||||
},
|
||||
);
|
||||
|
||||
export const workflowDeleteRecordActionSchema = baseWorkflowActionSchema.extend(
|
||||
{
|
||||
type: z.literal('DELETE_RECORD'),
|
||||
settings: workflowDeleteRecordActionSettingsSchema,
|
||||
},
|
||||
);
|
||||
|
||||
export const workflowFindRecordsActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('FIND_RECORDS'),
|
||||
settings: workflowFindRecordsActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowFormActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('FORM'),
|
||||
settings: workflowFormActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowHttpRequestActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('HTTP_REQUEST'),
|
||||
settings: workflowHttpRequestActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowAiAgentActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('AI_AGENT'),
|
||||
settings: workflowAiAgentActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowFilterActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('FILTER'),
|
||||
settings: workflowFilterActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowIteratorActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('ITERATOR'),
|
||||
settings: workflowIteratorActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowEmptyActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('EMPTY'),
|
||||
settings: workflowEmptyActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowActionSchema = z.discriminatedUnion('type', [
|
||||
workflowCodeActionSchema,
|
||||
workflowSendEmailActionSchema,
|
||||
workflowCreateRecordActionSchema,
|
||||
workflowUpdateRecordActionSchema,
|
||||
workflowDeleteRecordActionSchema,
|
||||
workflowFindRecordsActionSchema,
|
||||
workflowFormActionSchema,
|
||||
workflowHttpRequestActionSchema,
|
||||
workflowAiAgentActionSchema,
|
||||
workflowFilterActionSchema,
|
||||
workflowIteratorActionSchema,
|
||||
workflowEmptyActionSchema,
|
||||
]);
|
||||
|
||||
export const workflowDatabaseEventTriggerSchema = baseTriggerSchema.extend({
|
||||
type: z.literal('DATABASE_EVENT'),
|
||||
settings: z.object({
|
||||
eventName: z.string(),
|
||||
input: z.object({}).passthrough().optional(),
|
||||
outputSchema: z.object({}).passthrough(),
|
||||
objectType: z.string().optional(),
|
||||
fields: z.array(z.string()).optional().nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowManualTriggerSchema = baseTriggerSchema
|
||||
.extend({
|
||||
type: z.literal('MANUAL'),
|
||||
settings: z.object({
|
||||
objectType: z.string().optional(),
|
||||
outputSchema: z
|
||||
.object({})
|
||||
.passthrough()
|
||||
.describe(
|
||||
'Schema defining the output data structure. When a record is selected, it is accessible via {{trigger.record.fieldName}}. When no record is selected, no data is available.',
|
||||
),
|
||||
icon: z.string().optional(),
|
||||
isPinned: z.boolean().optional(),
|
||||
}),
|
||||
})
|
||||
.describe(
|
||||
'Manual trigger that can be launched by the user. If a record is selected when launched, it is accessible via {{trigger.record.fieldName}}. If no record is selected, no data context is available.',
|
||||
);
|
||||
|
||||
export const workflowCronTriggerSchema = baseTriggerSchema.extend({
|
||||
type: z.literal('CRON'),
|
||||
settings: z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal('DAYS'),
|
||||
schedule: z.object({
|
||||
day: z.number().min(1),
|
||||
hour: z.number().min(0).max(23),
|
||||
minute: z.number().min(0).max(59),
|
||||
}),
|
||||
outputSchema: z.object({}).passthrough(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('HOURS'),
|
||||
schedule: z.object({
|
||||
hour: z.number().min(1),
|
||||
minute: z.number().min(0).max(59),
|
||||
}),
|
||||
outputSchema: z.object({}).passthrough(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('MINUTES'),
|
||||
schedule: z.object({ minute: z.number().min(1) }),
|
||||
outputSchema: z.object({}).passthrough(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('CUSTOM'),
|
||||
pattern: z.string(),
|
||||
outputSchema: z.object({}).passthrough(),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
export const workflowWebhookTriggerSchema = baseTriggerSchema.extend({
|
||||
type: z.literal('WEBHOOK'),
|
||||
settings: z.discriminatedUnion('httpMethod', [
|
||||
z.object({
|
||||
outputSchema: z.object({}).passthrough(),
|
||||
httpMethod: z.literal('GET'),
|
||||
authentication: z.literal('API_KEY').nullable(),
|
||||
}),
|
||||
z.object({
|
||||
outputSchema: z.object({}).passthrough(),
|
||||
httpMethod: z.literal('POST'),
|
||||
expectedBody: z.object({}).passthrough(),
|
||||
authentication: z.literal('API_KEY').nullable(),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
export const workflowTriggerSchema = z.discriminatedUnion('type', [
|
||||
workflowDatabaseEventTriggerSchema,
|
||||
workflowManualTriggerSchema,
|
||||
workflowCronTriggerSchema,
|
||||
workflowWebhookTriggerSchema,
|
||||
]);
|
||||
|
||||
export const workflowRunStepStatusSchema = z.nativeEnum(StepStatus);
|
||||
|
||||
export const workflowRunStateStepInfoSchema = z.object({
|
||||
result: z.any().optional(),
|
||||
error: z.any().optional(),
|
||||
status: workflowRunStepStatusSchema,
|
||||
});
|
||||
|
||||
export const workflowRunStateStepInfosSchema = z.record(
|
||||
workflowRunStateStepInfoSchema,
|
||||
);
|
||||
|
||||
export const workflowRunStateSchema = z.object({
|
||||
flow: z.object({
|
||||
trigger: workflowTriggerSchema,
|
||||
steps: z.array(workflowActionSchema),
|
||||
}),
|
||||
stepInfos: workflowRunStateStepInfosSchema,
|
||||
workflowRunError: z.any().optional(),
|
||||
});
|
||||
|
||||
export const workflowRunStatusSchema = z.enum([
|
||||
'NOT_STARTED',
|
||||
'RUNNING',
|
||||
'COMPLETED',
|
||||
'FAILED',
|
||||
'ENQUEUED',
|
||||
]);
|
||||
|
||||
export const workflowRunSchema = z
|
||||
.object({
|
||||
__typename: z.literal('WorkflowRun'),
|
||||
id: z.string(),
|
||||
workflowVersionId: z.string(),
|
||||
workflowId: z.string(),
|
||||
state: workflowRunStateSchema.nullable(),
|
||||
status: workflowRunStatusSchema,
|
||||
createdAt: z.string(),
|
||||
deletedAt: z.string().nullable(),
|
||||
endedAt: z.string().nullable(),
|
||||
name: z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
type WorkflowHttpRequestAction,
|
||||
type WorkflowSendEmailAction,
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
workflowFormActionSettingsSchema,
|
||||
workflowHttpRequestActionSettingsSchema,
|
||||
workflowSendEmailActionSettingsSchema,
|
||||
} from '@/workflow/validation-schemas/workflowSchema';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
} from 'twenty-shared/workflow';
|
||||
import { useWorkflowActionHeader } from '../useWorkflowActionHeader';
|
||||
|
||||
jest.mock('../useActionIconColorOrThrow', () => ({
|
||||
|
||||
@@ -25,7 +25,7 @@ const StyledLinkContainer = styled.div`
|
||||
`;
|
||||
|
||||
const emailValidationSchema = (email: string) =>
|
||||
z.string().email(`Invalid email '${email}'`);
|
||||
z.email(`Invalid email '${email}'`);
|
||||
|
||||
const validationSchema = () =>
|
||||
z
|
||||
@@ -37,9 +37,8 @@ const validationSchema = () =>
|
||||
const emails = sanitizeEmailList(value.split(','));
|
||||
if (emails.length === 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.invalid_string,
|
||||
code: 'custom',
|
||||
message: 'Emails should not be empty',
|
||||
validation: 'email',
|
||||
});
|
||||
}
|
||||
const invalidEmails: string[] = [];
|
||||
@@ -51,12 +50,11 @@ const validationSchema = () =>
|
||||
}
|
||||
if (invalidEmails.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.invalid_string,
|
||||
code: 'custom',
|
||||
message:
|
||||
invalidEmails.length > 1
|
||||
? 'Emails "' + invalidEmails.join('", "') + '" are invalid'
|
||||
: 'Email "' + invalidEmails.join('", "') + '" is invalid',
|
||||
validation: 'email',
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -49,8 +49,12 @@ const StyledComboInputContainer = styled.div`
|
||||
|
||||
const validationSchema = z
|
||||
.object({
|
||||
firstName: z.string().min(1, { message: 'First name can not be empty' }),
|
||||
lastName: z.string().min(1, { message: 'Last name can not be empty' }),
|
||||
firstName: z.string().min(1, {
|
||||
error: 'First name can not be empty',
|
||||
}),
|
||||
lastName: z.string().min(1, {
|
||||
error: 'Last name can not be empty',
|
||||
}),
|
||||
})
|
||||
.required();
|
||||
|
||||
|
||||
@@ -54,9 +54,7 @@ const StyledActionSkipLinkContainer = styled.div`
|
||||
`;
|
||||
|
||||
const validationSchema = z.object({
|
||||
emails: z.array(
|
||||
z.object({ email: z.union([z.literal(''), z.string().email()]) }),
|
||||
),
|
||||
emails: z.array(z.object({ email: z.union([z.literal(''), z.email()]) })),
|
||||
});
|
||||
|
||||
type FormInput = z.infer<typeof validationSchema>;
|
||||
|
||||
@@ -27,22 +27,20 @@ export const SettingsSecurityApprovedAccessDomain = () => {
|
||||
const form = useForm<{ domain: string; email: string }>({
|
||||
mode: 'onSubmit',
|
||||
resolver: zodResolver(
|
||||
z
|
||||
.object({
|
||||
domain: z
|
||||
.string()
|
||||
.regex(
|
||||
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])\.[a-zA-Z]{2,}$/,
|
||||
{
|
||||
message: t`Domains have to be smaller than 256 characters, cannot contain spaces and cannot contain any special characters.`,
|
||||
},
|
||||
)
|
||||
.max(256),
|
||||
email: z.string().min(1, {
|
||||
message: t`Email cannot be empty`,
|
||||
}),
|
||||
})
|
||||
.strict(),
|
||||
z.strictObject({
|
||||
domain: z
|
||||
.string()
|
||||
.regex(
|
||||
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])\.[a-zA-Z]{2,}$/,
|
||||
{
|
||||
message: t`Domains have to be smaller than 256 characters, cannot contain spaces and cannot contain any special characters.`,
|
||||
},
|
||||
)
|
||||
.max(256),
|
||||
email: z.string().min(1, {
|
||||
message: t`Email cannot be empty`,
|
||||
}),
|
||||
}),
|
||||
),
|
||||
defaultValues: {
|
||||
email: '',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type SafeParseError } from 'zod';
|
||||
import { type ZodSafeParseError } from 'zod';
|
||||
|
||||
import { camelCaseStringSchema } from '../camelCaseStringSchema';
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('camelCaseStringSchema', () => {
|
||||
it('fails for non-camel case strings', () => {
|
||||
const result = camelCaseStringSchema.safeParse('NotCamelCase');
|
||||
expect(result.success).toBe(false);
|
||||
expect((result as SafeParseError<string>).error.errors).toEqual([
|
||||
expect((result as ZodSafeParseError<string>).error.issues).toEqual([
|
||||
{
|
||||
code: 'custom',
|
||||
message: 'String should be camel case',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type SafeParseError } from 'zod';
|
||||
import { type ZodSafeParseError } from 'zod';
|
||||
|
||||
import { simpleQuotesStringSchema } from '../simpleQuotesStringSchema';
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('simpleQuotesStringSchema', () => {
|
||||
|
||||
// Then
|
||||
expect(result.success).toBe(false);
|
||||
expect((result as SafeParseError<string>).error.errors).toEqual([
|
||||
expect((result as ZodSafeParseError<string>).error.issues).toEqual([
|
||||
{
|
||||
code: 'custom',
|
||||
message: 'String should be wrapped in simple quotes',
|
||||
|
||||
@@ -4,5 +4,5 @@ import { z } from 'zod';
|
||||
export const camelCaseStringSchema = z
|
||||
.string()
|
||||
.refine((value) => camelCase(value) === value, {
|
||||
message: 'String should be camel case',
|
||||
error: 'String should be camel case',
|
||||
});
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const simpleQuotesStringSchema: z.ZodType<
|
||||
`'${string}'`,
|
||||
z.ZodTypeDef,
|
||||
string
|
||||
> = z
|
||||
export const simpleQuotesStringSchema = z
|
||||
.string()
|
||||
.refine(
|
||||
(value: string): value is `'${string}'` =>
|
||||
value.startsWith("'") && value.endsWith("'"),
|
||||
{
|
||||
message: 'String should be wrapped in simple quotes',
|
||||
error: 'String should be wrapped in simple quotes',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import {
|
||||
workflowActionSchema,
|
||||
workflowTriggerSchema,
|
||||
} from 'twenty-shared/workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
import { workflowActionSchema, workflowTriggerSchema } from './workflow.schema';
|
||||
|
||||
export const createWorkflowVersionStepSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
|
||||
@@ -1,492 +0,0 @@
|
||||
import {
|
||||
FieldMetadataType,
|
||||
StepLogicalOperator,
|
||||
ViewFilterOperand,
|
||||
} from 'twenty-shared/types';
|
||||
import { StepStatus } from 'twenty-shared/workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const objectRecordSchema = z
|
||||
.record(z.string(), z.any())
|
||||
.describe(
|
||||
'Record data object. Use nested objects for relationships (e.g., "company": {"id": "{{reference}}"}). Common patterns:\n' +
|
||||
'- Person: {"name": {"firstName": "John", "lastName": "Doe"}, "emails": {"primaryEmail": "john@example.com"}, "company": {"id": "{{trigger.object.id}}"}}\n' +
|
||||
'- Company: {"name": "Acme Corp", "domainName": {"primaryLinkUrl": "https://acme.com"}}\n' +
|
||||
'- Task: {"title": "Follow up", "status": "TODO", "assignee": {"id": "{{user.id}}"}}',
|
||||
);
|
||||
|
||||
export const baseWorkflowActionSettingsSchema = z.object({
|
||||
input: z
|
||||
.looseObject({})
|
||||
.describe(
|
||||
'Input data for the workflow action. Structure depends on the action type.',
|
||||
),
|
||||
outputSchema: z
|
||||
.looseObject({})
|
||||
.describe(
|
||||
'Schema defining the output data structure. This data can be referenced in subsequent steps using {{stepId.fieldName}}.',
|
||||
),
|
||||
errorHandlingOptions: z.object({
|
||||
retryOnFailure: z.object({
|
||||
value: z.boolean().describe('Whether to retry the action if it fails.'),
|
||||
}),
|
||||
continueOnFailure: z.object({
|
||||
value: z
|
||||
.boolean()
|
||||
.describe('Whether to continue to the next step if this action fails.'),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const baseWorkflowActionSchema = z.object({
|
||||
id: z
|
||||
.string()
|
||||
.describe(
|
||||
'Unique identifier for the workflow step. Must be unique within the workflow.',
|
||||
),
|
||||
name: z
|
||||
.string()
|
||||
.describe(
|
||||
'Human-readable name for the workflow step. Should clearly describe what the step does.',
|
||||
),
|
||||
valid: z
|
||||
.boolean()
|
||||
.describe(
|
||||
'Whether the step configuration is valid. Set to true when all required fields are properly configured.',
|
||||
),
|
||||
nextStepIds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.nullable()
|
||||
.describe(
|
||||
'Array of step IDs that this step connects to. Leave empty or null for the final step.',
|
||||
),
|
||||
position: z
|
||||
.object({ x: z.number(), y: z.number() })
|
||||
.optional()
|
||||
.nullable()
|
||||
.describe('Position coordinates for the step in the workflow diagram.'),
|
||||
});
|
||||
|
||||
export const baseTriggerSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Human-readable name for the trigger. Optional but recommended for clarity.',
|
||||
),
|
||||
type: z
|
||||
.enum(['DATABASE_EVENT', 'MANUAL', 'CRON', 'WEBHOOK'])
|
||||
.describe(
|
||||
'Type of trigger. DATABASE_EVENT for record changes, MANUAL for user-initiated, CRON for scheduled, WEBHOOK for external calls.',
|
||||
),
|
||||
position: z
|
||||
.object({ x: z.number(), y: z.number() })
|
||||
.optional()
|
||||
.nullable()
|
||||
.describe(
|
||||
'Position coordinates for the trigger in the workflow diagram. Use (0, 0) for the trigger step.',
|
||||
),
|
||||
nextStepIds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.nullable()
|
||||
.describe(
|
||||
'Array of step IDs that the trigger connects to. These are the first steps in the workflow.',
|
||||
),
|
||||
});
|
||||
|
||||
export const workflowCodeActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
serverlessFunctionId: z.string(),
|
||||
serverlessFunctionVersion: z.string(),
|
||||
serverlessFunctionInput: z.record(z.string(), z.any()),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowSendEmailActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
connectedAccountId: z.string(),
|
||||
email: z.string(),
|
||||
subject: z.string().optional(),
|
||||
body: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowCreateRecordActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z
|
||||
.string()
|
||||
.describe(
|
||||
'The name of the object to create a record in. Must be lowercase (e.g., "person", "company", "task").',
|
||||
),
|
||||
objectRecord: objectRecordSchema.describe('The record data to create.'),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowUpdateRecordActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z.string(),
|
||||
objectRecord: objectRecordSchema,
|
||||
objectRecordId: z.string(),
|
||||
fieldsToUpdate: z.array(z.string()),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowDeleteRecordActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z.string(),
|
||||
objectRecordId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowFindRecordsActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z.string(),
|
||||
limit: z.number().optional(),
|
||||
filter: z
|
||||
.object({
|
||||
recordFilterGroups: z.array(z.object({})).optional(),
|
||||
recordFilters: z.array(z.object({})).optional(),
|
||||
gqlOperationFilter: z.object({}).optional().nullable(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowFormActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
label: z.string(),
|
||||
type: z.union([
|
||||
z.literal(FieldMetadataType.TEXT),
|
||||
z.literal(FieldMetadataType.NUMBER),
|
||||
z.literal(FieldMetadataType.DATE),
|
||||
z.literal(FieldMetadataType.SELECT),
|
||||
z.literal('RECORD'),
|
||||
]),
|
||||
placeholder: z.string().optional(),
|
||||
settings: z.record(z.string(), z.any()).optional(),
|
||||
value: z.any().optional(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export const workflowHttpRequestActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
url: z.string(),
|
||||
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
|
||||
headers: z.record(z.string(), z.string()).optional(),
|
||||
body: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.null(),
|
||||
z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])),
|
||||
]),
|
||||
)
|
||||
.or(z.string())
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowAiAgentActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
agentId: z.string().optional(),
|
||||
prompt: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowFilterActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
stepFilterGroups: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
logicalOperator: z.enum(StepLogicalOperator),
|
||||
parentStepFilterGroupId: z.string().optional(),
|
||||
positionInStepFilterGroup: z.number().optional(),
|
||||
}),
|
||||
),
|
||||
stepFilters: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
type: z.string(),
|
||||
stepOutputKey: z.string(),
|
||||
operand: z.enum(ViewFilterOperand),
|
||||
value: z.string(),
|
||||
stepFilterGroupId: z.string(),
|
||||
positionInStepFilterGroup: z.number().optional(),
|
||||
fieldMetadataId: z.string().optional(),
|
||||
compositeFieldSubFieldName: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowIteratorActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
items: z
|
||||
.union([
|
||||
z.array(
|
||||
z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.null(),
|
||||
z.record(z.string(), z.any()),
|
||||
z.any(),
|
||||
]),
|
||||
),
|
||||
z.string(),
|
||||
])
|
||||
.optional(),
|
||||
initialLoopStepIds: z.array(z.string()).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const workflowEmptyActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({}),
|
||||
});
|
||||
|
||||
export const workflowCodeActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('CODE'),
|
||||
settings: workflowCodeActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowSendEmailActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('SEND_EMAIL'),
|
||||
settings: workflowSendEmailActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowCreateRecordActionSchema = baseWorkflowActionSchema.extend(
|
||||
{
|
||||
type: z.literal('CREATE_RECORD'),
|
||||
settings: workflowCreateRecordActionSettingsSchema,
|
||||
},
|
||||
);
|
||||
|
||||
export const workflowUpdateRecordActionSchema = baseWorkflowActionSchema.extend(
|
||||
{
|
||||
type: z.literal('UPDATE_RECORD'),
|
||||
settings: workflowUpdateRecordActionSettingsSchema,
|
||||
},
|
||||
);
|
||||
|
||||
export const workflowDeleteRecordActionSchema = baseWorkflowActionSchema.extend(
|
||||
{
|
||||
type: z.literal('DELETE_RECORD'),
|
||||
settings: workflowDeleteRecordActionSettingsSchema,
|
||||
},
|
||||
);
|
||||
|
||||
export const workflowFindRecordsActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('FIND_RECORDS'),
|
||||
settings: workflowFindRecordsActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowFormActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('FORM'),
|
||||
settings: workflowFormActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowHttpRequestActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('HTTP_REQUEST'),
|
||||
settings: workflowHttpRequestActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowAiAgentActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('AI_AGENT'),
|
||||
settings: workflowAiAgentActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowFilterActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('FILTER'),
|
||||
settings: workflowFilterActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowIteratorActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('ITERATOR'),
|
||||
settings: workflowIteratorActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowEmptyActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('EMPTY'),
|
||||
settings: workflowEmptyActionSettingsSchema,
|
||||
});
|
||||
|
||||
export const workflowActionSchema = z.discriminatedUnion('type', [
|
||||
workflowCodeActionSchema,
|
||||
workflowSendEmailActionSchema,
|
||||
workflowCreateRecordActionSchema,
|
||||
workflowUpdateRecordActionSchema,
|
||||
workflowDeleteRecordActionSchema,
|
||||
workflowFindRecordsActionSchema,
|
||||
workflowFormActionSchema,
|
||||
workflowHttpRequestActionSchema,
|
||||
workflowAiAgentActionSchema,
|
||||
workflowFilterActionSchema,
|
||||
workflowIteratorActionSchema,
|
||||
workflowEmptyActionSchema,
|
||||
]);
|
||||
|
||||
export const workflowDatabaseEventTriggerSchema = baseTriggerSchema
|
||||
.extend({
|
||||
type: z.literal('DATABASE_EVENT'),
|
||||
settings: z.object({
|
||||
eventName: z
|
||||
.string()
|
||||
.regex(
|
||||
/^[a-z][a-zA-Z0-9_]*\.(created|updated|deleted)$/,
|
||||
'Event name must follow the pattern: objectName.action (e.g., "company.created", "person.updated")',
|
||||
)
|
||||
.describe(
|
||||
'Event name in format: objectName.action (e.g., "company.created", "person.updated", "task.deleted"). Use lowercase object names.',
|
||||
),
|
||||
input: z.looseObject({}).optional(),
|
||||
outputSchema: z
|
||||
.looseObject({})
|
||||
.describe(
|
||||
'Schema defining the output data structure. For database events, this includes the record that triggered the workflow accessible via {{trigger.object.fieldName}}.',
|
||||
),
|
||||
objectType: z.string().optional(),
|
||||
fields: z.array(z.string()).optional().nullable(),
|
||||
}),
|
||||
})
|
||||
.describe(
|
||||
'Database event trigger that fires when a record is created, updated, or deleted. The triggered record is accessible in workflow steps via {{trigger.object.fieldName}}.',
|
||||
);
|
||||
|
||||
export const workflowManualTriggerSchema = baseTriggerSchema
|
||||
.extend({
|
||||
type: z.literal('MANUAL'),
|
||||
settings: z.object({
|
||||
objectType: z.string().optional(),
|
||||
outputSchema: z
|
||||
.looseObject({})
|
||||
.describe(
|
||||
'Schema defining the output data structure. When a record is selected, it is accessible via {{trigger.record.fieldName}}. When no record is selected, no data is available.',
|
||||
),
|
||||
icon: z.string().optional(),
|
||||
isPinned: z.boolean().optional(),
|
||||
}),
|
||||
})
|
||||
.describe(
|
||||
'Manual trigger that can be launched by the user. If a record is selected when launched, it is accessible via {{trigger.record.fieldName}}. If no record is selected, no data context is available.',
|
||||
);
|
||||
|
||||
export const workflowCronTriggerSchema = baseTriggerSchema.extend({
|
||||
type: z.literal('CRON'),
|
||||
settings: z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal('DAYS'),
|
||||
schedule: z.object({
|
||||
day: z.number().min(1),
|
||||
hour: z.number().min(0).max(23),
|
||||
minute: z.number().min(0).max(59),
|
||||
}),
|
||||
outputSchema: z.looseObject({}),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('HOURS'),
|
||||
schedule: z.object({
|
||||
hour: z.number().min(1),
|
||||
minute: z.number().min(0).max(59),
|
||||
}),
|
||||
outputSchema: z.looseObject({}),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('MINUTES'),
|
||||
schedule: z.object({ minute: z.number().min(1) }),
|
||||
outputSchema: z.looseObject({}),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('CUSTOM'),
|
||||
pattern: z.string(),
|
||||
outputSchema: z.looseObject({}),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
export const workflowWebhookTriggerSchema = baseTriggerSchema.extend({
|
||||
type: z.literal('WEBHOOK'),
|
||||
settings: z.discriminatedUnion('httpMethod', [
|
||||
z.object({
|
||||
outputSchema: z.looseObject({}),
|
||||
httpMethod: z.literal('GET'),
|
||||
authentication: z.literal('API_KEY').nullable(),
|
||||
}),
|
||||
z.object({
|
||||
outputSchema: z.looseObject({}),
|
||||
httpMethod: z.literal('POST'),
|
||||
expectedBody: z.looseObject({}),
|
||||
authentication: z.literal('API_KEY').nullable(),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
export const workflowTriggerSchema = z.discriminatedUnion('type', [
|
||||
workflowDatabaseEventTriggerSchema,
|
||||
workflowManualTriggerSchema,
|
||||
workflowCronTriggerSchema,
|
||||
workflowWebhookTriggerSchema,
|
||||
]);
|
||||
|
||||
export const workflowRunStepStatusSchema = z.enum(StepStatus);
|
||||
|
||||
export const workflowRunStateStepInfoSchema = z.object({
|
||||
result: z.any().optional(),
|
||||
error: z.any().optional(),
|
||||
status: workflowRunStepStatusSchema,
|
||||
});
|
||||
|
||||
export const workflowRunStateStepInfosSchema = z.record(
|
||||
z.string(),
|
||||
workflowRunStateStepInfoSchema,
|
||||
);
|
||||
|
||||
export const workflowRunStateSchema = z.object({
|
||||
flow: z.object({
|
||||
trigger: workflowTriggerSchema,
|
||||
steps: z.array(workflowActionSchema),
|
||||
}),
|
||||
stepInfos: workflowRunStateStepInfosSchema,
|
||||
workflowRunError: z.any().optional(),
|
||||
});
|
||||
|
||||
export const workflowRunStatusSchema = z.enum([
|
||||
'NOT_STARTED',
|
||||
'RUNNING',
|
||||
'COMPLETED',
|
||||
'FAILED',
|
||||
'ENQUEUED',
|
||||
]);
|
||||
|
||||
export const workflowRunSchema = z.looseObject({
|
||||
__typename: z.literal('WorkflowRun'),
|
||||
id: z.string(),
|
||||
workflowVersionId: z.string(),
|
||||
workflowId: z.string(),
|
||||
state: workflowRunStateSchema.nullable(),
|
||||
status: workflowRunStatusSchema,
|
||||
createdAt: z.string(),
|
||||
deletedAt: z.string().nullable(),
|
||||
endedAt: z.string().nullable(),
|
||||
name: z.string(),
|
||||
});
|
||||
@@ -30,7 +30,7 @@
|
||||
"libphonenumber-js": "^1.10.26",
|
||||
"qs": "^6.11.2",
|
||||
"react-router-dom": "^6.4.4",
|
||||
"zod": "3.23.8"
|
||||
"zod": "^4.1.11"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
|
||||
@@ -15,7 +15,7 @@ const RelativeDateValueSchema = z.object({
|
||||
}
|
||||
return true;
|
||||
}, {
|
||||
message: 'Amount is required for NEXT and PAST directions and must be positive',
|
||||
error: 'Amount is required for NEXT and PAST directions and must be positive'
|
||||
});
|
||||
|
||||
export const safeParseRelativeDateFilterValue = (
|
||||
|
||||
@@ -16,7 +16,7 @@ export const absoluteUrlSchema = z.string().transform((value, ctx) => {
|
||||
// if the hostname is a number, it's not a valid url
|
||||
// if we let URL() parse it, it will throw cast an IP address and we lose the information
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
code: "custom",
|
||||
message: 'domain is not a valid url',
|
||||
});
|
||||
|
||||
@@ -28,14 +28,14 @@ export const absoluteUrlSchema = z.string().transform((value, ctx) => {
|
||||
return absoluteUrl;
|
||||
}
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
code: "custom",
|
||||
message: 'domain is not a valid url',
|
||||
});
|
||||
|
||||
return z.NEVER;
|
||||
} catch {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
code: "custom",
|
||||
message: 'domain is not a valid url',
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,46 @@
|
||||
|
||||
export { CONTENT_TYPE_VALUES_HTTP_REQUEST } from './constants/contentTypeValuesHttpRequest';
|
||||
export { TRIGGER_STEP_ID } from './constants/TriggerStepId';
|
||||
export { workflowAiAgentActionSchema } from './schemas/ai-agent-action-schema';
|
||||
export { workflowAiAgentActionSettingsSchema } from './schemas/ai-agent-action-settings-schema';
|
||||
export { baseTriggerSchema } from './schemas/base-trigger-schema';
|
||||
export { baseWorkflowActionSchema } from './schemas/base-workflow-action-schema';
|
||||
export { baseWorkflowActionSettingsSchema } from './schemas/base-workflow-action-settings-schema';
|
||||
export { workflowCodeActionSchema } from './schemas/code-action-schema';
|
||||
export { workflowCodeActionSettingsSchema } from './schemas/code-action-settings-schema';
|
||||
export { workflowCreateRecordActionSchema } from './schemas/create-record-action-schema';
|
||||
export { workflowCreateRecordActionSettingsSchema } from './schemas/create-record-action-settings-schema';
|
||||
export { workflowCronTriggerSchema } from './schemas/cron-trigger-schema';
|
||||
export { workflowDatabaseEventTriggerSchema } from './schemas/database-event-trigger-schema';
|
||||
export { workflowDeleteRecordActionSchema } from './schemas/delete-record-action-schema';
|
||||
export { workflowDeleteRecordActionSettingsSchema } from './schemas/delete-record-action-settings-schema';
|
||||
export { workflowEmptyActionSchema } from './schemas/empty-action-schema';
|
||||
export { workflowEmptyActionSettingsSchema } from './schemas/empty-action-settings-schema';
|
||||
export { workflowFilterActionSchema } from './schemas/filter-action-schema';
|
||||
export { workflowFilterActionSettingsSchema } from './schemas/filter-action-settings-schema';
|
||||
export { workflowFindRecordsActionSchema } from './schemas/find-records-action-schema';
|
||||
export { workflowFindRecordsActionSettingsSchema } from './schemas/find-records-action-settings-schema';
|
||||
export { workflowFormActionSchema } from './schemas/form-action-schema';
|
||||
export { workflowFormActionSettingsSchema } from './schemas/form-action-settings-schema';
|
||||
export { workflowHttpRequestActionSchema } from './schemas/http-request-action-schema';
|
||||
export { workflowHttpRequestActionSettingsSchema } from './schemas/http-request-action-settings-schema';
|
||||
export { workflowIteratorActionSchema } from './schemas/iterator-action-schema';
|
||||
export { workflowIteratorActionSettingsSchema } from './schemas/iterator-action-settings-schema';
|
||||
export { workflowManualTriggerSchema } from './schemas/manual-trigger-schema';
|
||||
export { objectRecordSchema } from './schemas/object-record-schema';
|
||||
export { workflowSendEmailActionSchema } from './schemas/send-email-action-schema';
|
||||
export { workflowSendEmailActionSettingsSchema } from './schemas/send-email-action-settings-schema';
|
||||
export { workflowUpdateRecordActionSchema } from './schemas/update-record-action-schema';
|
||||
export { workflowUpdateRecordActionSettingsSchema } from './schemas/update-record-action-settings-schema';
|
||||
export { workflowWebhookTriggerSchema } from './schemas/webhook-trigger-schema';
|
||||
export { workflowActionSchema } from './schemas/workflow-action-schema';
|
||||
export { workflowRunSchema } from './schemas/workflow-run-schema';
|
||||
export { workflowRunStateSchema } from './schemas/workflow-run-state-schema';
|
||||
export { workflowRunStateStepInfoSchema } from './schemas/workflow-run-state-step-info-schema';
|
||||
export { workflowRunStateStepInfosSchema } from './schemas/workflow-run-state-step-infos-schema';
|
||||
export { workflowRunStatusSchema } from './schemas/workflow-run-status-schema';
|
||||
export { workflowRunStepStatusSchema } from './schemas/workflow-run-step-status-schema';
|
||||
export { workflowTriggerSchema } from './schemas/workflow-trigger-schema';
|
||||
export type { BodyType } from './types/workflowHttpRequestStep';
|
||||
export type {
|
||||
WorkflowRunStepInfo,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { workflowAiAgentActionSettingsSchema } from './ai-agent-action-settings-schema';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
|
||||
export const workflowAiAgentActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('AI_AGENT'),
|
||||
settings: workflowAiAgentActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
|
||||
export const workflowAiAgentActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
agentId: z.string().optional(),
|
||||
prompt: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const baseTriggerSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Human-readable name for the trigger. Optional but recommended for clarity.'),
|
||||
type: z
|
||||
.enum(['DATABASE_EVENT', 'MANUAL', 'CRON', 'WEBHOOK'])
|
||||
.describe(
|
||||
'Type of trigger. DATABASE_EVENT for record changes, MANUAL for user-initiated, CRON for scheduled, WEBHOOK for external calls.',
|
||||
),
|
||||
position: z
|
||||
.object({ x: z.number(), y: z.number() })
|
||||
.optional()
|
||||
.nullable()
|
||||
.describe('Position coordinates for the trigger in the workflow diagram. Use (0, 0) for the trigger step.'),
|
||||
nextStepIds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.nullable()
|
||||
.describe('Array of step IDs that the trigger connects to. These are the first steps in the workflow.'),
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const baseWorkflowActionSchema = z.object({
|
||||
id: z
|
||||
.string()
|
||||
.describe('Unique identifier for the workflow step. Must be unique within the workflow.'),
|
||||
name: z
|
||||
.string()
|
||||
.describe('Human-readable name for the workflow step. Should clearly describe what the step does.'),
|
||||
valid: z
|
||||
.boolean()
|
||||
.describe('Whether the step configuration is valid. Set to true when all required fields are properly configured.'),
|
||||
nextStepIds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.nullable()
|
||||
.describe('Array of step IDs that this step connects to. Leave empty or null for the final step.'),
|
||||
position: z
|
||||
.object({ x: z.number(), y: z.number() })
|
||||
.optional()
|
||||
.nullable()
|
||||
.describe('Position coordinates for the step in the workflow diagram.'),
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const baseWorkflowActionSettingsSchema = z.object({
|
||||
input: z
|
||||
.looseObject({})
|
||||
.describe(
|
||||
'Input data for the workflow action. Structure depends on the action type.',
|
||||
),
|
||||
outputSchema: z
|
||||
.looseObject({})
|
||||
.describe(
|
||||
'Schema defining the output data structure. This data can be referenced in subsequent steps using {{stepId.fieldName}}.',
|
||||
),
|
||||
errorHandlingOptions: z.object({
|
||||
retryOnFailure: z.object({
|
||||
value: z.boolean().describe('Whether to retry the action if it fails.'),
|
||||
}),
|
||||
continueOnFailure: z.object({
|
||||
value: z.boolean().describe('Whether to continue to the next step if this action fails.'),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowCodeActionSettingsSchema } from './code-action-settings-schema';
|
||||
|
||||
export const workflowCodeActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('CODE'),
|
||||
settings: workflowCodeActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
|
||||
export const workflowCodeActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
serverlessFunctionId: z.string(),
|
||||
serverlessFunctionVersion: z.string(),
|
||||
serverlessFunctionInput: z.record(z.string(), z.any()),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowCreateRecordActionSettingsSchema } from './create-record-action-settings-schema';
|
||||
|
||||
export const workflowCreateRecordActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('CREATE_RECORD'),
|
||||
settings: workflowCreateRecordActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
import { objectRecordSchema } from './object-record-schema';
|
||||
|
||||
export const workflowCreateRecordActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z
|
||||
.string()
|
||||
.describe(
|
||||
'The name of the object to create a record in. Must be lowercase (e.g., "person", "company", "task").',
|
||||
),
|
||||
objectRecord: objectRecordSchema
|
||||
.describe(
|
||||
'The record data to create.',
|
||||
)
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { z } from 'zod';
|
||||
import { baseTriggerSchema } from './base-trigger-schema';
|
||||
|
||||
export const workflowCronTriggerSchema = baseTriggerSchema.extend({
|
||||
type: z.literal('CRON'),
|
||||
settings: z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal('DAYS'),
|
||||
schedule: z.object({
|
||||
day: z.number().min(1),
|
||||
hour: z.number().min(0).max(23),
|
||||
minute: z.number().min(0).max(59),
|
||||
}),
|
||||
outputSchema: z.looseObject({}),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('HOURS'),
|
||||
schedule: z.object({
|
||||
hour: z.number().min(1),
|
||||
minute: z.number().min(0).max(59),
|
||||
}),
|
||||
outputSchema: z.looseObject({}),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('MINUTES'),
|
||||
schedule: z.object({ minute: z.number().min(1) }),
|
||||
outputSchema: z.looseObject({}),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('CUSTOM'),
|
||||
pattern: z.string(),
|
||||
outputSchema: z.looseObject({}),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from 'zod';
|
||||
import { baseTriggerSchema } from './base-trigger-schema';
|
||||
|
||||
export const workflowDatabaseEventTriggerSchema = baseTriggerSchema
|
||||
.extend({
|
||||
type: z.literal('DATABASE_EVENT'),
|
||||
settings: z.object({
|
||||
eventName: z
|
||||
.string()
|
||||
.regex(
|
||||
/^[a-z][a-zA-Z0-9_]*\.(created|updated|deleted)$/,
|
||||
'Event name must follow the pattern: objectName.action (e.g., "company.created", "person.updated")',
|
||||
)
|
||||
.describe(
|
||||
'Event name in format: objectName.action (e.g., "company.created", "person.updated", "task.deleted"). Use lowercase object names.',
|
||||
),
|
||||
input: z.looseObject({}).optional(),
|
||||
outputSchema: z
|
||||
.looseObject({})
|
||||
.describe(
|
||||
'Schema defining the output data structure. For database events, this includes the record that triggered the workflow accessible via {{trigger.object.fieldName}}.',
|
||||
),
|
||||
objectType: z.string().optional(),
|
||||
fields: z.array(z.string()).optional().nullable(),
|
||||
}),
|
||||
})
|
||||
.describe(
|
||||
'Database event trigger that fires when a record is created, updated, or deleted. The triggered record is accessible in workflow steps via {{trigger.object.fieldName}}.',
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowDeleteRecordActionSettingsSchema } from './delete-record-action-settings-schema';
|
||||
|
||||
export const workflowDeleteRecordActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('DELETE_RECORD'),
|
||||
settings: workflowDeleteRecordActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
|
||||
export const workflowDeleteRecordActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z.string(),
|
||||
objectRecordId: z.string(),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowEmptyActionSettingsSchema } from './empty-action-settings-schema';
|
||||
|
||||
export const workflowEmptyActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('EMPTY'),
|
||||
settings: workflowEmptyActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
|
||||
export const workflowEmptyActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({}),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowFilterActionSettingsSchema } from './filter-action-settings-schema';
|
||||
|
||||
export const workflowFilterActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('FILTER'),
|
||||
settings: workflowFilterActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { z } from 'zod';
|
||||
import { StepLogicalOperator } from '../../types/StepFilters';
|
||||
import { ViewFilterOperand } from '../../types/ViewFilterOperand';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
|
||||
export const workflowFilterActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
stepFilterGroups: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
logicalOperator: z.enum(StepLogicalOperator),
|
||||
parentStepFilterGroupId: z.string().optional(),
|
||||
positionInStepFilterGroup: z.number().optional(),
|
||||
}),
|
||||
),
|
||||
stepFilters: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
type: z.string(),
|
||||
stepOutputKey: z.string(),
|
||||
operand: z.enum(ViewFilterOperand),
|
||||
value: z.string(),
|
||||
stepFilterGroupId: z.string(),
|
||||
positionInStepFilterGroup: z.number().optional(),
|
||||
fieldMetadataId: z.string().optional(),
|
||||
compositeFieldSubFieldName: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowFindRecordsActionSettingsSchema } from './find-records-action-settings-schema';
|
||||
|
||||
export const workflowFindRecordsActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('FIND_RECORDS'),
|
||||
settings: workflowFindRecordsActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
|
||||
export const workflowFindRecordsActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z.string(),
|
||||
limit: z.number().optional(),
|
||||
filter: z
|
||||
.object({
|
||||
recordFilterGroups: z.array(z.any()).optional(),
|
||||
recordFilters: z.array(z.any()).optional(),
|
||||
gqlOperationFilter: z.any().optional().nullable(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowFormActionSettingsSchema } from './form-action-settings-schema';
|
||||
|
||||
export const workflowFormActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('FORM'),
|
||||
settings: workflowFormActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
import { FieldMetadataType } from '../../types/FieldMetadataType';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
|
||||
export const workflowFormActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
label: z.string(),
|
||||
type: z.union([
|
||||
z.literal(FieldMetadataType.TEXT),
|
||||
z.literal(FieldMetadataType.NUMBER),
|
||||
z.literal(FieldMetadataType.DATE),
|
||||
z.literal(FieldMetadataType.SELECT),
|
||||
z.literal('RECORD'),
|
||||
]),
|
||||
placeholder: z.string().optional(),
|
||||
settings: z.record(z.string(), z.any()).optional(),
|
||||
value: z.any().optional(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowHttpRequestActionSettingsSchema } from './http-request-action-settings-schema';
|
||||
|
||||
export const workflowHttpRequestActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('HTTP_REQUEST'),
|
||||
settings: workflowHttpRequestActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
|
||||
export const workflowHttpRequestActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
url: z.string(),
|
||||
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
|
||||
headers: z.record(z.string(), z.string()).optional(),
|
||||
body: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.null(),
|
||||
z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])),
|
||||
]),
|
||||
)
|
||||
.or(z.string())
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowIteratorActionSettingsSchema } from './iterator-action-settings-schema';
|
||||
|
||||
export const workflowIteratorActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('ITERATOR'),
|
||||
settings: workflowIteratorActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
|
||||
export const workflowIteratorActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
items: z
|
||||
.union([
|
||||
z.array(
|
||||
z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.null(),
|
||||
z.record(z.string(), z.any()),
|
||||
z.any(),
|
||||
]),
|
||||
),
|
||||
z.string(),
|
||||
])
|
||||
.optional(),
|
||||
initialLoopStepIds: z.array(z.string()).optional(),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
import { baseTriggerSchema } from './base-trigger-schema';
|
||||
|
||||
export const workflowManualTriggerSchema = baseTriggerSchema
|
||||
.extend({
|
||||
type: z.literal('MANUAL'),
|
||||
settings: z.object({
|
||||
objectType: z.string().optional(),
|
||||
outputSchema: z
|
||||
.looseObject({})
|
||||
.describe(
|
||||
'Schema defining the output data structure. When a record is selected, it is accessible via {{trigger.record.fieldName}}. When no record is selected, no data is available.',
|
||||
),
|
||||
icon: z.string().optional(),
|
||||
isPinned: z.boolean().optional(),
|
||||
}),
|
||||
})
|
||||
.describe(
|
||||
'Manual trigger that can be launched by the user. If a record is selected when launched, it is accessible via {{trigger.record.fieldName}}. If no record is selected, no data context is available.',
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const objectRecordSchema = z
|
||||
.record(z.string(), z.any())
|
||||
.describe(
|
||||
'Record data object. Use nested objects for relationships (e.g., "company": {"id": "{{reference}}"}). Common patterns:\n' +
|
||||
'- Person: {"name": {"firstName": "John", "lastName": "Doe"}, "emails": {"primaryEmail": "john@example.com"}, "company": {"id": "{{trigger.object.id}}"}}\n' +
|
||||
'- Company: {"name": "Acme Corp", "domainName": {"primaryLinkUrl": "https://acme.com"}}\n' +
|
||||
'- Task: {"title": "Follow up", "status": "TODO", "assignee": {"id": "{{user.id}}"}}',
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowSendEmailActionSettingsSchema } from './send-email-action-settings-schema';
|
||||
|
||||
export const workflowSendEmailActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('SEND_EMAIL'),
|
||||
settings: workflowSendEmailActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
|
||||
export const workflowSendEmailActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
connectedAccountId: z.string(),
|
||||
email: z.string(),
|
||||
subject: z.string().optional(),
|
||||
body: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSchema } from './base-workflow-action-schema';
|
||||
import { workflowUpdateRecordActionSettingsSchema } from './update-record-action-settings-schema';
|
||||
|
||||
export const workflowUpdateRecordActionSchema = baseWorkflowActionSchema.extend({
|
||||
type: z.literal('UPDATE_RECORD'),
|
||||
settings: workflowUpdateRecordActionSettingsSchema,
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
import { baseWorkflowActionSettingsSchema } from './base-workflow-action-settings-schema';
|
||||
import { objectRecordSchema } from './object-record-schema';
|
||||
|
||||
export const workflowUpdateRecordActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
objectName: z.string(),
|
||||
objectRecord: objectRecordSchema,
|
||||
objectRecordId: z.string(),
|
||||
fieldsToUpdate: z.array(z.string()),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { z } from 'zod';
|
||||
import { baseTriggerSchema } from './base-trigger-schema';
|
||||
|
||||
export const workflowWebhookTriggerSchema = baseTriggerSchema.extend({
|
||||
type: z.literal('WEBHOOK'),
|
||||
settings: z.discriminatedUnion('httpMethod', [
|
||||
z.object({
|
||||
outputSchema: z.looseObject({}),
|
||||
httpMethod: z.literal('GET'),
|
||||
authentication: z.literal('API_KEY').nullable(),
|
||||
}),
|
||||
z.object({
|
||||
outputSchema: z.looseObject({}),
|
||||
httpMethod: z.literal('POST'),
|
||||
expectedBody: z.looseObject({}),
|
||||
authentication: z.literal('API_KEY').nullable(),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { z } from 'zod';
|
||||
import { workflowAiAgentActionSchema } from './ai-agent-action-schema';
|
||||
import { workflowCodeActionSchema } from './code-action-schema';
|
||||
import { workflowCreateRecordActionSchema } from './create-record-action-schema';
|
||||
import { workflowDeleteRecordActionSchema } from './delete-record-action-schema';
|
||||
import { workflowEmptyActionSchema } from './empty-action-schema';
|
||||
import { workflowFilterActionSchema } from './filter-action-schema';
|
||||
import { workflowFindRecordsActionSchema } from './find-records-action-schema';
|
||||
import { workflowFormActionSchema } from './form-action-schema';
|
||||
import { workflowHttpRequestActionSchema } from './http-request-action-schema';
|
||||
import { workflowIteratorActionSchema } from './iterator-action-schema';
|
||||
import { workflowSendEmailActionSchema } from './send-email-action-schema';
|
||||
import { workflowUpdateRecordActionSchema } from './update-record-action-schema';
|
||||
|
||||
export const workflowActionSchema = z.discriminatedUnion('type', [
|
||||
workflowCodeActionSchema,
|
||||
workflowSendEmailActionSchema,
|
||||
workflowCreateRecordActionSchema,
|
||||
workflowUpdateRecordActionSchema,
|
||||
workflowDeleteRecordActionSchema,
|
||||
workflowFindRecordsActionSchema,
|
||||
workflowFormActionSchema,
|
||||
workflowHttpRequestActionSchema,
|
||||
workflowAiAgentActionSchema,
|
||||
workflowFilterActionSchema,
|
||||
workflowIteratorActionSchema,
|
||||
workflowEmptyActionSchema,
|
||||
]);
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
import { workflowRunStateSchema } from './workflow-run-state-schema';
|
||||
import { workflowRunStatusSchema } from './workflow-run-status-schema';
|
||||
|
||||
export const workflowRunSchema = z.looseObject({
|
||||
__typename: z.literal('WorkflowRun'),
|
||||
id: z.string(),
|
||||
workflowVersionId: z.string(),
|
||||
workflowId: z.string(),
|
||||
state: workflowRunStateSchema.nullable(),
|
||||
status: workflowRunStatusSchema,
|
||||
createdAt: z.string(),
|
||||
deletedAt: z.string().nullable(),
|
||||
endedAt: z.string().nullable(),
|
||||
name: z.string(),
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
import { workflowActionSchema } from './workflow-action-schema';
|
||||
import { workflowRunStateStepInfosSchema } from './workflow-run-state-step-infos-schema';
|
||||
import { workflowTriggerSchema } from './workflow-trigger-schema';
|
||||
|
||||
export const workflowRunStateSchema = z.object({
|
||||
flow: z.object({
|
||||
trigger: workflowTriggerSchema,
|
||||
steps: z.array(workflowActionSchema),
|
||||
}),
|
||||
stepInfos: workflowRunStateStepInfosSchema,
|
||||
workflowRunError: z.any().optional(),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { workflowRunStepStatusSchema } from './workflow-run-step-status-schema';
|
||||
|
||||
export const workflowRunStateStepInfoSchema = z.object({
|
||||
result: z.any().optional(),
|
||||
error: z.any().optional(),
|
||||
status: workflowRunStepStatusSchema,
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
import { workflowRunStateStepInfoSchema } from './workflow-run-state-step-info-schema';
|
||||
|
||||
export const workflowRunStateStepInfosSchema = z.record(
|
||||
z.string(),
|
||||
workflowRunStateStepInfoSchema,
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const workflowRunStatusSchema = z.enum([
|
||||
'NOT_STARTED',
|
||||
'RUNNING',
|
||||
'COMPLETED',
|
||||
'FAILED',
|
||||
'ENQUEUED',
|
||||
]);
|
||||
@@ -0,0 +1,4 @@
|
||||
import { z } from 'zod';
|
||||
import { StepStatus } from '../types/WorkflowRunStateStepInfos';
|
||||
|
||||
export const workflowRunStepStatusSchema = z.enum(StepStatus);
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
import { workflowCronTriggerSchema } from './cron-trigger-schema';
|
||||
import { workflowDatabaseEventTriggerSchema } from './database-event-trigger-schema';
|
||||
import { workflowManualTriggerSchema } from './manual-trigger-schema';
|
||||
import { workflowWebhookTriggerSchema } from './webhook-trigger-schema';
|
||||
|
||||
export const workflowTriggerSchema = z.discriminatedUnion('type', [
|
||||
workflowDatabaseEventTriggerSchema,
|
||||
workflowManualTriggerSchema,
|
||||
workflowCronTriggerSchema,
|
||||
workflowWebhookTriggerSchema,
|
||||
]);
|
||||
@@ -39,7 +39,7 @@
|
||||
"react-tooltip": "^5.13.1",
|
||||
"recoil": "^0.7.7",
|
||||
"twenty-shared": "workspace:*",
|
||||
"zod": "3.23.8"
|
||||
"zod": "^4.1.11"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npx vite build"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"libphonenumber-js": "^1.10.26",
|
||||
"zapier-platform-core": "15.5.1",
|
||||
"zod": "3.23.8"
|
||||
"zod": "^4.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "29.7.0",
|
||||
|
||||
40
yarn.lock
40
yarn.lock
@@ -7044,12 +7044,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@hookform/resolvers@npm:^3.1.1":
|
||||
version: 3.9.0
|
||||
resolution: "@hookform/resolvers@npm:3.9.0"
|
||||
"@hookform/resolvers@npm:^5.2.2":
|
||||
version: 5.2.2
|
||||
resolution: "@hookform/resolvers@npm:5.2.2"
|
||||
dependencies:
|
||||
"@standard-schema/utils": "npm:^0.3.0"
|
||||
peerDependencies:
|
||||
react-hook-form: ^7.0.0
|
||||
checksum: 10c0/0e0e55f63abbd212cf14abbd39afad1f9b6105d6b25ce827fc651b624ed2be467ebe9b186026e0f032062db59ce2370b14e9583b436ae2d057738bdd6f04356c
|
||||
react-hook-form: ^7.55.0
|
||||
checksum: 10c0/0692cd61dcc2a70cbb27b88a37f733c39e97f555c036ba04a81bd42b0467461cfb6bafacb46c16f173672f9c8a216bd7928a2330d4e49c700d130622bf1defaf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -18828,6 +18830,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@standard-schema/utils@npm:^0.3.0":
|
||||
version: 0.3.0
|
||||
resolution: "@standard-schema/utils@npm:0.3.0"
|
||||
checksum: 10c0/6eb74cd13e52d5fc74054df51e37d947ef53f3ab9e02c085665dcca3c38c60ece8d735cebbdf18fbb13c775fbcb9becb3f53109b0e092a63f0f7389ce0993fd0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@stitches/core@npm:^1.2.6":
|
||||
version: 1.2.8
|
||||
resolution: "@stitches/core@npm:1.2.8"
|
||||
@@ -52362,7 +52371,7 @@ __metadata:
|
||||
tsx: "npm:^4.7.0"
|
||||
typescript: "npm:^5.3.0"
|
||||
yaml: "npm:^2.4.0"
|
||||
zod: "npm:^3.22.0"
|
||||
zod: "npm:^4.1.11"
|
||||
bin:
|
||||
twenty: dist/cli.js
|
||||
languageName: unknown
|
||||
@@ -52412,7 +52421,7 @@ __metadata:
|
||||
"@graphiql/plugin-explorer": "npm:^1.0.2"
|
||||
"@graphiql/react": "npm:^0.23.0"
|
||||
"@hello-pangea/dnd": "npm:^16.2.0"
|
||||
"@hookform/resolvers": "npm:^3.1.1"
|
||||
"@hookform/resolvers": "npm:^5.2.2"
|
||||
"@lingui/cli": "npm:^5.1.2"
|
||||
"@lingui/core": "npm:^5.1.2"
|
||||
"@lingui/detect-locale": "npm:^5.2.0"
|
||||
@@ -52747,7 +52756,7 @@ __metadata:
|
||||
vite: "npm:^7.0.0"
|
||||
vite-plugin-dts: "npm:3.8.1"
|
||||
vite-tsconfig-paths: "npm:^4.2.1"
|
||||
zod: "npm:3.23.8"
|
||||
zod: "npm:^4.1.11"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -52787,7 +52796,7 @@ __metadata:
|
||||
tsx: "npm:^4.19.3"
|
||||
twenty-shared: "workspace:*"
|
||||
vite-plugin-svgr: "npm:^4.3.0"
|
||||
zod: "npm:3.23.8"
|
||||
zod: "npm:^4.1.11"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -52834,7 +52843,7 @@ __metadata:
|
||||
rimraf: "npm:^3.0.2"
|
||||
zapier-platform-cli: "npm:^15.4.1"
|
||||
zapier-platform-core: "npm:15.5.1"
|
||||
zod: "npm:3.23.8"
|
||||
zod: "npm:^4.1.11"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -53046,7 +53055,7 @@ __metadata:
|
||||
vite-tsconfig-paths: "npm:^4.2.1"
|
||||
vitest: "npm:1.4.0"
|
||||
xlsx-ugnis: "npm:^0.19.3"
|
||||
zod: "npm:3.23.8"
|
||||
zod: "npm:^4.1.11"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -56244,14 +56253,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:3.23.8":
|
||||
version: 3.23.8
|
||||
resolution: "zod@npm:3.23.8"
|
||||
checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.20.2, zod@npm:^3.22.0, zod@npm:^3.23.8":
|
||||
"zod@npm:^3.20.2, zod@npm:^3.23.8":
|
||||
version: 3.25.76
|
||||
resolution: "zod@npm:3.25.76"
|
||||
checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c
|
||||
|
||||
Reference in New Issue
Block a user