Update manifest structure (#17547)
Move all sync entities in an `entities` key. Rename functions to
logicFunctions
```json
{
application: {
...
},
entities: {
objects: [],
logicFunctions: [],
...
}
}
```
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
import { defineLogicFunction } from '@/sdk';
|
||||
|
||||
const mockHandler = async () => ({ success: true });
|
||||
|
||||
describe('defineLogicFunction', () => {
|
||||
const validRouteConfig = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Send Postcard',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
||||
type: 'route' as const,
|
||||
path: '/postcards/send',
|
||||
httpMethod: 'POST' as const,
|
||||
isAuthRequired: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
it('should return the config when valid with route trigger', () => {
|
||||
const result = defineLogicFunction(validRouteConfig);
|
||||
|
||||
expect(result.config).toEqual(validRouteConfig);
|
||||
});
|
||||
|
||||
it('should accept cron trigger', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Daily Report',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
type: 'cron' as const,
|
||||
pattern: '0 9 * * *',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config);
|
||||
|
||||
expect(result.config.triggers[0].type).toBe('cron');
|
||||
});
|
||||
|
||||
it('should accept databaseEvent trigger', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'On Contact Created',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
type: 'databaseEvent' as const,
|
||||
eventName: 'contact.created',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config);
|
||||
|
||||
expect(result.config.triggers[0].type).toBe('databaseEvent');
|
||||
});
|
||||
|
||||
it('should accept multiple triggers', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Multi-trigger Function',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
||||
type: 'route' as const,
|
||||
path: '/sync',
|
||||
httpMethod: 'POST' as const,
|
||||
isAuthRequired: true,
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
type: 'cron' as const,
|
||||
pattern: '0 * * * *',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config);
|
||||
|
||||
expect(result.config.triggers).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should pass through optional fields', () => {
|
||||
const config = {
|
||||
...validRouteConfig,
|
||||
description: 'Send a postcard to a contact',
|
||||
timeoutSeconds: 30,
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config);
|
||||
|
||||
expect(result.config.description).toBe('Send a postcard to a contact');
|
||||
expect(result.config.timeoutSeconds).toBe(30);
|
||||
});
|
||||
|
||||
it('should return error when universalIdentifier is missing', () => {
|
||||
const config = {
|
||||
name: 'Send Postcard',
|
||||
handler: mockHandler,
|
||||
triggers: validRouteConfig.triggers,
|
||||
};
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toContain(
|
||||
'Logic function must have a universalIdentifier',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when handler is missing', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Send Postcard',
|
||||
triggers: validRouteConfig.triggers,
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toContain('Logic function must have a handler');
|
||||
});
|
||||
|
||||
it('should return error when handler is not a function', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Send Postcard',
|
||||
handler: 'not-a-function',
|
||||
triggers: validRouteConfig.triggers,
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toContain(
|
||||
'Logic function handler must be a function',
|
||||
);
|
||||
});
|
||||
|
||||
it('should accept empty triggers array', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Send Postcard',
|
||||
handler: mockHandler,
|
||||
triggers: [],
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.config.triggers).toEqual([]);
|
||||
});
|
||||
|
||||
it('should accept missing triggers', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Send Postcard',
|
||||
handler: mockHandler,
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.config.triggers).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return error when trigger is missing universalIdentifier', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Send Postcard',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
type: 'route' as const,
|
||||
path: '/postcards/send',
|
||||
httpMethod: 'POST' as const,
|
||||
isAuthRequired: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toContain(
|
||||
'Each trigger must have a universalIdentifier',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when trigger is missing type', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Send Postcard',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
||||
path: '/postcards/send',
|
||||
httpMethod: 'POST' as const,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toContain('Each trigger must have a type');
|
||||
});
|
||||
|
||||
it('should return error when route trigger is missing path', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Send Postcard',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
||||
type: 'route' as const,
|
||||
httpMethod: 'POST' as const,
|
||||
isAuthRequired: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toContain('Route trigger must have a path');
|
||||
});
|
||||
|
||||
it('should return error when route trigger is missing httpMethod', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Send Postcard',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
||||
type: 'route' as const,
|
||||
path: '/postcards/send',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toContain('Route trigger must have an httpMethod');
|
||||
});
|
||||
|
||||
it('should return error when cron trigger is missing pattern', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Daily Report',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
type: 'cron' as const,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toContain('Cron trigger must have a pattern');
|
||||
});
|
||||
|
||||
it('should return error when databaseEvent trigger is missing eventName', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'On Contact Created',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
type: 'databaseEvent' as const,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toContain(
|
||||
'Database event trigger must have an eventName',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error for unknown trigger type', () => {
|
||||
const config = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'Unknown Trigger',
|
||||
handler: mockHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
||||
type: 'unknown' as any,
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = defineLogicFunction(config as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toContain('Unknown trigger type: unknown');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { type LogicFunctionConfig } from '@/sdk/logic-functions/logic-function-config';
|
||||
import { createValidationResult } from '@/sdk/common/utils/create-validation-result';
|
||||
import type { DefineEntity } from '@/sdk/common/types/define-entity.type';
|
||||
|
||||
export const defineLogicFunction: DefineEntity<LogicFunctionConfig> = (
|
||||
config,
|
||||
) => {
|
||||
const errors = [];
|
||||
|
||||
if (!config.universalIdentifier) {
|
||||
errors.push('Logic function must have a universalIdentifier');
|
||||
}
|
||||
|
||||
if (!config.handler) {
|
||||
errors.push('Logic function must have a handler');
|
||||
}
|
||||
|
||||
if (typeof config.handler !== 'function') {
|
||||
errors.push('Logic function handler must be a function');
|
||||
}
|
||||
|
||||
for (const trigger of config.triggers ?? []) {
|
||||
if (!trigger.universalIdentifier) {
|
||||
errors.push('Each trigger must have a universalIdentifier');
|
||||
}
|
||||
|
||||
if (!trigger.type) {
|
||||
errors.push('Each trigger must have a type');
|
||||
}
|
||||
|
||||
switch (trigger.type) {
|
||||
case 'route':
|
||||
if (!trigger.path) {
|
||||
errors.push('Route trigger must have a path');
|
||||
}
|
||||
if (!trigger.httpMethod) {
|
||||
errors.push('Route trigger must have an httpMethod');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'cron':
|
||||
if (!trigger.pattern) {
|
||||
errors.push('Cron trigger must have a pattern');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'databaseEvent':
|
||||
if (!trigger.eventName) {
|
||||
errors.push('Database event trigger must have an eventName');
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
errors.push(
|
||||
`Unknown trigger type: ${(trigger as { type: string }).type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return createValidationResult({
|
||||
config,
|
||||
errors,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
type LogicFunctionManifest,
|
||||
type LogicFunctionTriggerManifest,
|
||||
} from 'twenty-shared/application';
|
||||
|
||||
export type LogicFunctionHandler = (...args: any[]) => any | Promise<any>;
|
||||
|
||||
export type LogicFunctionConfig = Omit<
|
||||
LogicFunctionManifest,
|
||||
| 'sourceHandlerPath'
|
||||
| 'builtHandlerPath'
|
||||
| 'builtHandlerChecksum'
|
||||
| 'handlerName'
|
||||
> & {
|
||||
handler: LogicFunctionHandler;
|
||||
triggers?: LogicFunctionTriggerManifest[];
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export type CronPayload = Record<string, never>;
|
||||
@@ -0,0 +1,11 @@
|
||||
export type {
|
||||
DatabaseEventPayload,
|
||||
ObjectRecordCreateEvent,
|
||||
ObjectRecordUpdateEvent,
|
||||
ObjectRecordEvent,
|
||||
ObjectRecordDeleteEvent,
|
||||
ObjectRecordDestroyEvent,
|
||||
ObjectRecordBaseEvent,
|
||||
ObjectRecordRestoreEvent,
|
||||
ObjectRecordUpsertEvent,
|
||||
} from 'twenty-shared/database-events';
|
||||
@@ -0,0 +1 @@
|
||||
export type { LogicFunctionEvent as RoutePayload } from 'twenty-shared/types';
|
||||
Reference in New Issue
Block a user