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:
martmull
2026-01-30 16:26:45 +01:00
committed by GitHub
parent bc022f82cb
commit f46da3eefd
162 changed files with 2555 additions and 3778 deletions
@@ -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';