fix merge

This commit is contained in:
Weiko
2026-02-24 10:59:46 +01:00
parent 16e2204d4d
commit 19aeec66d2
17 changed files with 269 additions and 4537 deletions
@@ -16,7 +16,7 @@
"auth": "twenty auth login"
},
"dependencies": {
"twenty-sdk": "portal:../../twenty-sdk"
"twenty-sdk": "0.2.4"
},
"devDependencies": {
"@types/node": "^24.7.2"
@@ -1,61 +1,67 @@
import type {
CronPayload,
FunctionConfig,
DatabaseEventPayload,
ObjectRecordCreateEvent,
CronPayload,
} from 'twenty-sdk';
import { defineLogicFunction } from 'twenty-sdk';
import { CoreApiClient, type Person } from 'twenty-sdk/core-api';
import Twenty, { type Person } from '../../generated';
type CreateNewPostCardParams =
| { name?: string }
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
| CronPayload;
const handler = async (params: CreateNewPostCardParams) => {
export const main = async (params: CreateNewPostCardParams) => {
try {
const client = new CoreApiClient();
const client = new Twenty();
const name =
'name' in params
? params.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
: 'Hello world';
// TODO: restore after first sync creates the PostCard object on the server
// const createPostCard = await client.mutation({
// createPostCard: {
// __args: {
// data: {
// name,
// },
// },
// name: true,
// id: true,
// },
// });
const createPostCard = await client.mutation({
createPostCard: {
__args: {
data: {
name,
},
},
name: true,
id: true,
},
});
console.log('createPostCard handler called with name:', name);
console.log('createPostCard result', createPostCard);
return { name };
return createPostCard;
} catch (error) {
console.error(error);
throw error;
}
};
export default defineLogicFunction({
export const config: FunctionConfig = {
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'create-new-post-card',
timeoutSeconds: 2,
handler,
httpRouteTriggerSettings: {
path: '/post-card/create',
httpMethod: 'GET',
isAuthRequired: false,
},
cronTriggerSettings: {
pattern: '0 0 1 1 *', // Every year 1st of January
},
databaseEventTriggerSettings: {
eventName: 'person.created',
},
});
triggers: [
{
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
type: 'route',
path: '/post-card/create',
httpMethod: 'GET',
isAuthRequired: false,
},
{
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
type: 'cron',
pattern: '0 0 1 1 *', // Every year 1st of January
},
{
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
type: 'databaseEvent',
eventName: 'person.created',
},
],
};
@@ -1,6 +1,6 @@
import { defineApplication } from 'twenty-sdk';
import { type ApplicationConfig } from 'twenty-sdk';
export default defineApplication({
const config: ApplicationConfig = {
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
displayName: 'Hello World',
description: 'A simple hello world app',
@@ -14,4 +14,6 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061',
});
};
export default config;
@@ -1,4 +1,16 @@
import { defineObject, FieldType } from 'twenty-sdk';
import { type Note } from '../../generated';
import {
type AddressField,
Field,
FieldType,
type FullNameField,
Object,
OnDeleteAction,
Relation,
RelationType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';
enum PostCardStatus {
DRAFT = 'DRAFT',
@@ -7,86 +19,94 @@ enum PostCardStatus {
RETURNED = 'RETURNED',
}
export const POST_CARD_UNIVERSAL_IDENTIFIER =
'54b589ca-eeed-4950-a176-358418b85c05';
export default defineObject({
universalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
@Object({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post card',
labelPlural: 'Post cards',
description: 'A post card object',
description: ' A post card object',
icon: 'IconMail',
fields: [
{
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
name: 'content',
},
{
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
name: 'recipientName',
},
{
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
name: 'recipientAddress',
},
{
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{
id: 'a1b2c3d4-0001-4000-8000-000000000001',
value: PostCardStatus.DRAFT,
label: 'Draft',
position: 0,
color: 'gray',
},
{
id: 'a1b2c3d4-0002-4000-8000-000000000002',
value: PostCardStatus.SENT,
label: 'Sent',
position: 1,
color: 'orange',
},
{
id: 'a1b2c3d4-0003-4000-8000-000000000003',
value: PostCardStatus.DELIVERED,
label: 'Delivered',
position: 2,
color: 'green',
},
{
id: 'a1b2c3d4-0004-4000-8000-000000000004',
value: PostCardStatus.RETURNED,
label: 'Returned',
position: 3,
color: 'orange',
},
],
name: 'status',
},
{
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
name: 'deliveredAt',
},
],
});
})
export class PostCard {
@Field({
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
})
content: string;
@Field({
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
})
recipientName: FullNameField;
@Field({
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
})
recipientAddress: AddressField;
@Field({
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{
value: PostCardStatus.DRAFT,
label: 'Draft',
position: 0,
color: 'gray',
},
{
value: PostCardStatus.SENT,
label: 'Sent',
position: 1,
color: 'orange',
},
{
value: PostCardStatus.DELIVERED,
label: 'Delivered',
position: 2,
color: 'green',
},
{
value: PostCardStatus.RETURNED,
label: 'Returned',
position: 3,
color: 'orange',
},
],
})
status: PostCardStatus;
@Relation({
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
type: RelationType.ONE_TO_MANY,
label: 'Notes',
icon: 'IconComment',
inverseSideTargetUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note.universalIdentifier,
onDelete: OnDeleteAction.CASCADE,
})
notes: Note[];
@Field({
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
})
deliveredAt?: Date;
}
@@ -1,7 +1,6 @@
import { defineRole, PermissionFlag } from 'twenty-sdk';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/postCard';
import { PermissionFlag, type RoleConfig } from 'twenty-sdk';
export default defineRole({
export const functionRole: RoleConfig = {
universalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061',
label: 'Default function role',
description: 'Default role for function Twenty client',
@@ -15,7 +14,7 @@ export default defineRole({
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
@@ -24,11 +23,11 @@ export default defineRole({
],
fieldPermissions: [
{
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
canReadFieldValue: false,
canUpdateFieldValue: false,
},
],
permissionFlags: [PermissionFlag.APPLICATIONS],
});
};
File diff suppressed because it is too large Load Diff
@@ -795,13 +795,13 @@ Notes:
#### Uploading files
The generated `Twenty` client includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Because standard GraphQL clients do not support multipart file uploads natively, the client provides this dedicated method that implements the [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec) under the hood.
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Because standard GraphQL clients do not support multipart file uploads natively, the client provides this dedicated method that implements the [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec) under the hood.
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/metadata-api';
import * as fs from 'fs';
const client = new Twenty();
const client = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
@@ -809,13 +809,13 @@ const { me } = await client.query({ me: { id: true, displayName: true } });
#### رفع الملفات
يتضمن العميل `Twenty` المُولَّد طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع ملف ضمن كائنات مساحة العمل الخاصة بك. نظرًا لأن عملاء GraphQL القياسيون لا يدعمون تحميل الملفات متعددة الأجزاء افتراضيًا، يوفر العميل هذه الطريقة المخصصة التي تطبق [مواصفة طلب GraphQL متعدد الأجزاء](https://github.com/jaydenseric/graphql-multipart-request-spec) في الخلفية.
يتضمن العميل `MetadataApiClient` المُولَّد طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع ملف ضمن كائنات مساحة العمل الخاصة بك. نظرًا لأن عملاء GraphQL القياسيون لا يدعمون تحميل الملفات متعددة الأجزاء افتراضيًا، يوفر العميل هذه الطريقة المخصصة التي تطبق [مواصفة طلب GraphQL متعدد الأجزاء](https://github.com/jaydenseric/graphql-multipart-request-spec) في الخلفية.
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/metadata-api';
import * as fs from 'fs';
const client = new Twenty();
const client = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
@@ -809,13 +809,13 @@ Poznámky:
#### Nahrávání souborů
Vygenerovaný klient `Twenty` obsahuje metodu `uploadFile` pro připojování souborů k polím typu souboru u objektů ve vašem pracovním prostoru. Protože standardní klienti GraphQL nativně nepodporují nahrávání souborů pomocí multipart, klient poskytuje tuto speciální metodu, která interně implementuje [specifikaci multipart požadavků GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
Vygenerovaný klient `MetadataApiClient` obsahuje metodu `uploadFile` pro připojování souborů k polím typu souboru u objektů ve vašem pracovním prostoru. Protože standardní klienti GraphQL nativně nepodporují nahrávání souborů pomocí multipart, klient poskytuje tuto speciální metodu, která interně implementuje [specifikaci multipart požadavků GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/metadata-api';
import * as fs from 'fs';
const client = new Twenty();
const client = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
@@ -809,13 +809,13 @@ Notizen:
#### Dateien hochladen
Der generierte `Twenty`-Client enthält eine Methode `uploadFile`, um Dateien an Felder des Typs Datei in Ihren Arbeitsbereichsobjekten anzuhängen. Da Standard-GraphQL-Clients Multipart-Datei-Uploads nicht nativ unterstützen, stellt der Client diese dedizierte Methode bereit, die unter der Haube die [GraphQL-Multipart-Anfragespezifikation](https://github.com/jaydenseric/graphql-multipart-request-spec) implementiert.
Der generierte `MetadataApiClient`-Client enthält eine Methode `uploadFile`, um Dateien an Felder des Typs Datei in Ihren Arbeitsbereichsobjekten anzuhängen. Da Standard-GraphQL-Clients Multipart-Datei-Uploads nicht nativ unterstützen, stellt der Client diese dedizierte Methode bereit, die unter der Haube die [GraphQL-Multipart-Anfragespezifikation](https://github.com/jaydenseric/graphql-multipart-request-spec) implementiert.
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/metadata-api';
import * as fs from 'fs';
const client = new Twenty();
const client = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
@@ -809,13 +809,13 @@ Note:
#### Caricamento dei file
Il client `Twenty` generato include un metodo `uploadFile` per allegare file ai campi di tipo file sugli oggetti del tuo spazio di lavoro. Poiché i client GraphQL standard non supportano nativamente il caricamento di file multipart, il client fornisce questo metodo dedicato che implementa la [specifica della richiesta GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec) dietro le quinte.
Il client `MetadataApiClient` generato include un metodo `uploadFile` per allegare file ai campi di tipo file sugli oggetti del tuo spazio di lavoro. Poiché i client GraphQL standard non supportano nativamente il caricamento di file multipart, il client fornisce questo metodo dedicato che implementa la [specifica della richiesta GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec) dietro le quinte.
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/metadata-api';
import * as fs from 'fs';
const client = new Twenty();
const client = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
@@ -809,13 +809,13 @@ Notas:
#### Carregamento de ficheiros
O cliente `Twenty` gerado inclui um método `uploadFile` para anexar ficheiros a campos do tipo ficheiro nos objetos do seu espaço de trabalho. Como os clientes GraphQL padrão não suportam nativamente o carregamento de ficheiros multipart, o cliente fornece este método dedicado que implementa, nos bastidores, a [especificação de pedidos multipart do GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
O cliente `MetadataApiClient` gerado inclui um método `uploadFile` para anexar ficheiros a campos do tipo ficheiro nos objetos do seu espaço de trabalho. Como os clientes GraphQL padrão não suportam nativamente o carregamento de ficheiros multipart, o cliente fornece este método dedicado que implementa, nos bastidores, a [especificação de pedidos multipart do GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/metadata-api';
import * as fs from 'fs';
const client = new Twenty();
const client = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
@@ -809,13 +809,13 @@ Notițe:
#### Încărcarea fișierelor
Clientul `Twenty` generat include o metodă `uploadFile` pentru atașarea fișierelor la câmpuri de tip fișier ale obiectelor din spațiul de lucru. Deoarece clienții GraphQL standard nu acceptă în mod nativ încărcarea fișierelor multipart, clientul oferă această metodă dedicată care implementează, sub capotă, [specificația cererilor GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec).
Clientul `MetadataApiClient` generat include o metodă `uploadFile` pentru atașarea fișierelor la câmpuri de tip fișier ale obiectelor din spațiul de lucru. Deoarece clienții GraphQL standard nu acceptă în mod nativ încărcarea fișierelor multipart, clientul oferă această metodă dedicată care implementează, sub capotă, [specificația cererilor GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec).
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/metadata-api';
import * as fs from 'fs';
const client = new Twenty();
const client = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
@@ -809,13 +809,13 @@ const { me } = await client.query({ me: { id: true, displayName: true } });
#### Загрузка файлов
Сгенерированный клиент `Twenty` включает метод `uploadFile` для прикрепления файлов к полям типа «файл» в объектах вашего рабочего пространства. Поскольку стандартные клиенты GraphQL изначально не поддерживают многочастовую загрузку файлов, клиент предоставляет специальный метод, который под капотом реализует [спецификацию многочастных запросов GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
Сгенерированный клиент `MetadataApiClient` включает метод `uploadFile` для прикрепления файлов к полям типа «файл» в объектах вашего рабочего пространства. Поскольку стандартные клиенты GraphQL изначально не поддерживают многочастовую загрузку файлов, клиент предоставляет специальный метод, который под капотом реализует [спецификацию многочастных запросов GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/metadata-api';
import * as fs from 'fs';
const client = new Twenty();
const client = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
@@ -809,13 +809,13 @@ Notlar:
#### Dosya yükleme
Oluşturulan `Twenty` istemcisi, çalışma alanı nesnelerinizdeki dosya türündeki alanlara dosya eklemek için bir `uploadFile` yöntemi içerir. Standart GraphQL istemcileri çok parçalı dosya yüklemelerini yerel olarak desteklemediğinden, istemci arka planda [GraphQL çok parçalı istek belirtimi](https://github.com/jaydenseric/graphql-multipart-request-spec) uygulayan bu özel yöntemi sağlar.
Oluşturulan `MetadataApiClient` istemcisi, çalışma alanı nesnelerinizdeki dosya türündeki alanlara dosya eklemek için bir `uploadFile` yöntemi içerir. Standart GraphQL istemcileri çok parçalı dosya yüklemelerini yerel olarak desteklemediğinden, istemci arka planda [GraphQL çok parçalı istek belirtimi](https://github.com/jaydenseric/graphql-multipart-request-spec) uygulayan bu özel yöntemi sağlar.
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/metadata-api';
import * as fs from 'fs';
const client = new Twenty();
const client = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
@@ -809,13 +809,13 @@ const { me } = await client.query({ me: { id: true, displayName: true } });
#### 上传文件
生成的 `Twenty` 客户端包含一个 `uploadFile` 方法,用于将文件附加到工作区对象的文件类型字段。 由于标准 GraphQL 客户端不原生支持多部分文件上传,该客户端提供了一个专用方法,在底层实现了 [GraphQL 多部分请求规范](https://github.com/jaydenseric/graphql-multipart-request-spec)。
生成的 `MetadataApiClient` 包含一个 `uploadFile` 方法,用于将文件附加到工作区对象的文件类型字段。 由于标准 GraphQL 客户端不原生支持多部分文件上传,该客户端提供了一个专用方法,在底层实现了 [GraphQL 多部分请求规范](https://github.com/jaydenseric/graphql-multipart-request-spec)。
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/metadata-api';
import * as fs from 'fs';
const client = new Twenty();
const client = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
@@ -104,18 +104,36 @@ export class ClientService {
className: string,
urlSuffix: string,
) {
const clientContent = `
const clientContent = this.buildApiClientCode({
className,
urlSuffix,
includeUploadFile: urlSuffix === '/metadata',
});
await fs.appendFile(join(output, 'index.ts'), clientContent);
}
private buildApiClientCode({
className,
urlSuffix,
includeUploadFile,
}: {
className: string;
urlSuffix: string;
includeUploadFile: boolean;
}): string {
const uploadFileMethod = includeUploadFile
? this.buildUploadFileMethod()
: '';
return `
// ----------------------------------------------------
// ${className} (auto-injected)
// ----------------------------------------------------
const defaultOptions: ClientOptions = {
// TODO: this current branch
url: \`\${process.env.${DEFAULT_API_URL_NAME}}${urlSuffix}\`,
// TODO: this PR https://github.com/twentyhq/twenty/pull/18129
url: \`\${process.env.${DEFAULT_API_URL_NAME}}/graphql\`,
metadataUrl: \`\${process.env.${DEFAULT_API_URL_NAME}}/metadata\`,
headers: {
'Content-Type': 'application/json',
Authorization: \`Bearer \${process.env.${DEFAULT_API_KEY_NAME}}\`,
@@ -125,11 +143,10 @@ const defaultOptions: ClientOptions = {
export class ${className} {
private client: Client;
private url: string;
private metadataUrl: string;
private authorizationToken: string;
constructor(options?: ClientOptions) {
const merged: ClientOptions = {
const merged: ClientOptions = {
...defaultOptions,
...options,
headers: {
@@ -139,7 +156,6 @@ export class ${className} {
};
this.client = createClient(merged);
this.url = merged.url;
this.metadataUrl = merged.metadataUrl;
this.authorizationToken = merged.headers.Authorization;
}
@@ -150,7 +166,14 @@ export class ${className} {
mutation<R extends MutationGenqlSelection>(request: R & { __name?: string }) {
return this.client.mutation(request);
}
${uploadFileMethod}
}
`;
}
private buildUploadFileMethod(): string {
return `
async uploadFile(
fileBuffer: Buffer,
filename: string,
@@ -177,7 +200,7 @@ export class ${className} {
form.append('map', JSON.stringify({ '0': ['variables.file'] }));
form.append('0', new Blob([fileBuffer], { type: contentType }), filename);
const response = await fetch(this.metadataUrl, {
const response = await fetch(this.url, {
method: 'POST',
headers: {
Authorization: this.authorizationToken,
@@ -192,11 +215,6 @@ export class ${className} {
}
return result.data.uploadFilesFieldFileByUniversalIdentifier;
}
}
`;
await fs.appendFile(join(output, 'index.ts'), clientContent);
}`;
}
}