Compare commits
343
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5717752be | ||
|
|
24796e9355 | ||
|
|
15fc850212 | ||
|
|
e6d5df751b | ||
|
|
5b4fed1afe | ||
|
|
e757a1418d | ||
|
|
2f298307f9 | ||
|
|
c15536f9f8 | ||
|
|
b2f7c745f8 | ||
|
|
0ee63a0525 | ||
|
|
18880f0385 | ||
|
|
0902579fbe | ||
|
|
52e57e70fd | ||
|
|
1e01f15182 | ||
|
|
d0c1841f0f | ||
|
|
5be64bf4be | ||
|
|
d9dab75052 | ||
|
|
2237273869 | ||
|
|
9e21e55db4 | ||
|
|
859241d237 | ||
|
|
41e413d7b1 | ||
|
|
b0cb29c11c | ||
|
|
b4d957306e | ||
|
|
9c984b137f | ||
|
|
05a6a96b13 | ||
|
|
16d414590b | ||
|
|
bc72879c70 | ||
|
|
bb037e13dd | ||
|
|
17786a3298 | ||
|
|
8f91529153 | ||
|
|
e995e84621 | ||
|
|
148584c730 | ||
|
|
b7ff587b5e | ||
|
|
2e77a68daf | ||
|
|
382746dd52 | ||
|
|
202a130ac8 | ||
|
|
24c6d3fef3 | ||
|
|
52fe21c04b | ||
|
|
ee0474e287 | ||
|
|
3f202c5c6a | ||
|
|
a63b31931f | ||
|
|
3c2aec1894 | ||
|
|
b250de216e | ||
|
|
2d42b0a726 | ||
|
|
7e5e800f63 | ||
|
|
8c70a875f8 | ||
|
|
a40612ef9e | ||
|
|
1979e013e9 | ||
|
|
3747005fd5 | ||
|
|
268cc40d80 | ||
|
|
11fd1a41f3 | ||
|
|
05eca08ad2 | ||
|
|
ef1464db73 | ||
|
|
5ccf18fdf5 | ||
|
|
9e6f19d16e | ||
|
|
987ed845ac | ||
|
|
f51291704d | ||
|
|
37b9a55382 | ||
|
|
aa7973e5b8 | ||
|
|
8c951d3623 | ||
|
|
2a76e1791e | ||
|
|
4ae375308f | ||
|
|
c01853c349 | ||
|
|
21b2b65dbe | ||
|
|
c6d04ccced | ||
|
|
c18726d712 | ||
|
|
96bc3594a3 | ||
|
|
617f634b6d | ||
|
|
8e977912e2 | ||
|
|
4cdb7d61b2 | ||
|
|
7fbd50e5a0 | ||
|
|
0b4cebfe0e | ||
|
|
d7c28d6455 | ||
|
|
2b29918bf8 | ||
|
|
2106f46e9e | ||
|
|
1edce5088c | ||
|
|
3216b634a3 | ||
|
|
6c7c389785 | ||
|
|
9162685b2e | ||
|
|
bf4c348c8b | ||
|
|
ece265c6e4 | ||
|
|
15f09736b2 | ||
|
|
bade5289b6 | ||
|
|
497230a052 | ||
|
|
9aa63f7ddc | ||
|
|
40d7e740ef | ||
|
|
7402edb887 | ||
|
|
d7132b35d3 | ||
|
|
bc6268bb29 | ||
|
|
c44d61f324 | ||
|
|
cc86b9acae | ||
|
|
30cbf0e40e | ||
|
|
4acca5b10d | ||
|
|
3dc5b162c7 | ||
|
|
a494a7a902 | ||
|
|
f6beb06364 | ||
|
|
7074d9ce1b | ||
|
|
b3c95744ef | ||
|
|
10de51fcbd | ||
|
|
d537144ab1 | ||
|
|
955c1b4916 | ||
|
|
e505eba978 | ||
|
|
77d15356e1 | ||
|
|
e382641ac3 | ||
|
|
04b8f2aaf5 | ||
|
|
23df33172a | ||
|
|
5b701f5ba4 | ||
|
|
df516a904b | ||
|
|
c0fd4c7fed | ||
|
|
c521406bf8 | ||
|
|
27da9d60eb | ||
|
|
0473efcef0 | ||
|
|
de5764ede0 | ||
|
|
f899804db3 | ||
|
|
66b87c641c | ||
|
|
309785f7ff | ||
|
|
60f2a74bf0 | ||
|
|
6851085143 | ||
|
|
9604dbe78a | ||
|
|
f733f30b62 | ||
|
|
1d6dc04cda | ||
|
|
67a98f77e3 | ||
|
|
752359335e | ||
|
|
64700a00b0 | ||
|
|
8674cf973e | ||
|
|
96c37b30c5 | ||
|
|
bef643970b | ||
|
|
6a5974e06c | ||
|
|
d4303469ff | ||
|
|
4629fb90be | ||
|
|
48c8fa6809 | ||
|
|
b03e3772e6 | ||
|
|
660a15b721 | ||
|
|
59c36736d4 | ||
|
|
92359603c1 | ||
|
|
177cae8c53 | ||
|
|
6407474461 | ||
|
|
bcfacdead9 | ||
|
|
4fcc424e24 | ||
|
|
9b8eacb7f7 | ||
|
|
f6b210dd0d | ||
|
|
50ef231704 | ||
|
|
aa06dab920 | ||
|
|
414f68fb63 | ||
|
|
0edc3a385c | ||
|
|
29093fdbf1 | ||
|
|
402d149ee1 | ||
|
|
e10e0b337e | ||
|
|
7867617385 | ||
|
|
9aba2f62e1 | ||
|
|
4ae5732483 | ||
|
|
867b03393b | ||
|
|
60b48339b9 | ||
|
|
c18f574aee | ||
|
|
fdd3e6d245 | ||
|
|
29a2635164 | ||
|
|
b53dfa0533 | ||
|
|
ccd40e7633 | ||
|
|
3db961c038 | ||
|
|
476bdf764c | ||
|
|
43042d55b2 | ||
|
|
66d7182d02 | ||
|
|
5c91a96e84 | ||
|
|
f009913cbe | ||
|
|
97867c11e6 | ||
|
|
1a39cd6019 | ||
|
|
c31ffef510 | ||
|
|
19b56c5d3a | ||
|
|
a1f26cda0d | ||
|
|
f2284579f0 | ||
|
|
10ba8b9a97 | ||
|
|
9b063af7ab | ||
|
|
99aab9f40d | ||
|
|
65678ed99f | ||
|
|
cdaa8f1d9d | ||
|
|
963066c7e3 | ||
|
|
2826540bb1 | ||
|
|
d35d5c0463 | ||
|
|
cfdcc0065c | ||
|
|
fd47d5a1a9 | ||
|
|
7dd8d573ed | ||
|
|
5f0ec8ed9e | ||
|
|
7b48efb5d6 | ||
|
|
d9995157bf | ||
|
|
6faef19f64 | ||
|
|
75921e79bf | ||
|
|
48b4127afa | ||
|
|
28763428f6 | ||
|
|
86c15551ce | ||
|
|
b643fbe425 | ||
|
|
7f33286274 | ||
|
|
492847a693 | ||
|
|
ce7b4838e1 | ||
|
|
bd9688421f | ||
|
|
f0bc9fcb43 | ||
|
|
b2218cbbf2 | ||
|
|
84deb8067d | ||
|
|
749bda92e3 | ||
|
|
f6e182ac23 | ||
|
|
82619d9e66 | ||
|
|
54cf857a1a | ||
|
|
c9d5f48ecc | ||
|
|
63c5c54a39 | ||
|
|
7dc53fee8c | ||
|
|
4bfa777893 | ||
|
|
fb97c40cad | ||
|
|
1976ad58c4 | ||
|
|
be8629d8f6 | ||
|
|
46d28509b9 | ||
|
|
db31b83a86 | ||
|
|
394b6f5717 | ||
|
|
a97ca14289 | ||
|
|
4a770eafa1 | ||
|
|
cb5e5e4622 | ||
|
|
0001c2e7d0 | ||
|
|
36cf388c81 | ||
|
|
b8d85e0b26 | ||
|
|
1fea3f8228 | ||
|
|
15b053de46 | ||
|
|
ebc7125b20 | ||
|
|
51a2a90b0a | ||
|
|
6462ff6c5f | ||
|
|
febab2760a | ||
|
|
9eabfc628a | ||
|
|
9767c418ee | ||
|
|
2bcae523ac | ||
|
|
cc1ca630fd | ||
|
|
5791bd2943 | ||
|
|
f46da3eefd | ||
|
|
bc022f82cb | ||
|
|
5996d0fc03 | ||
|
|
d624652e36 | ||
|
|
ffdbb57eaa | ||
|
|
9c55313590 | ||
|
|
dffb61e437 | ||
|
|
4b66ae5320 | ||
|
|
9439afde22 | ||
|
|
4090837de5 | ||
|
|
945bedb7c7 | ||
|
|
fc833a4afe | ||
|
|
6eebf6f23a | ||
|
|
077cfeffca | ||
|
|
4ee9e23ad9 | ||
|
|
446afcbc30 | ||
|
|
f7adf1698e | ||
|
|
69c53c7dff | ||
|
|
fbbd8fe967 | ||
|
|
e91457a47e | ||
|
|
7006c53bd9 | ||
|
|
b99582c665 | ||
|
|
efbbfe5570 | ||
|
|
7d69000ab7 | ||
|
|
09de762285 | ||
|
|
3dfb5ffc02 | ||
|
|
9b7bd1a6aa | ||
|
|
b15d092abd | ||
|
|
97a37604bd | ||
|
|
245111dea9 | ||
|
|
c9a826aba1 | ||
|
|
06e803d18b | ||
|
|
3ad7a9ac94 | ||
|
|
22c7e207e3 | ||
|
|
3188fb5295 | ||
|
|
63bf46703d | ||
|
|
3412992e99 | ||
|
|
1cc08b84c4 | ||
|
|
9ee6917c86 | ||
|
|
07c48c076b | ||
|
|
fc908e9d87 | ||
|
|
59f7582463 | ||
|
|
9672ca09a2 | ||
|
|
3e9dda6761 | ||
|
|
5c1c998b97 | ||
|
|
fe9d6f34ff | ||
|
|
81eaee81a8 | ||
|
|
15de09caeb | ||
|
|
f7908de4c1 | ||
|
|
f33b8e12f4 | ||
|
|
a1ff13ee6a | ||
|
|
2ac8c9e38b | ||
|
|
9fbd05ead7 | ||
|
|
7d64ee85ac | ||
|
|
da6f1bbef3 | ||
|
|
59d123d2b1 | ||
|
|
2ffe2d8aa6 | ||
|
|
6b38686d87 | ||
|
|
b9586769b9 | ||
|
|
8fe02c5c19 | ||
|
|
8fb8b5d2f1 | ||
|
|
51c1fa0137 | ||
|
|
a4499d21bb | ||
|
|
e1f92bd951 | ||
|
|
9162e62664 | ||
|
|
2daebc6d0f | ||
|
|
dd98146c99 | ||
|
|
ddf1c43bb1 | ||
|
|
7e3d9cd85a | ||
|
|
a913ac7452 | ||
|
|
f4ca69a474 | ||
|
|
bc7791871f | ||
|
|
27e5b9797b | ||
|
|
3a43e714bb | ||
|
|
f532a51467 | ||
|
|
856dc8be56 | ||
|
|
326585157c | ||
|
|
c5953c9d50 | ||
|
|
f06caae098 | ||
|
|
a98418c6d8 | ||
|
|
43c9a75402 | ||
|
|
c36d112ab4 | ||
|
|
d6a7dbb12b | ||
|
|
16826224cd | ||
|
|
2a8f834377 | ||
|
|
c79f633f17 | ||
|
|
784b05cf45 | ||
|
|
41d470e687 | ||
|
|
570f83ea76 | ||
|
|
ae1151cf5d | ||
|
|
8931f4681a | ||
|
|
9f811d3f8e | ||
|
|
9c9c0a7595 | ||
|
|
44202668fd | ||
|
|
d0bc9a94c0 | ||
|
|
4a5ffcc9d2 | ||
|
|
406e269cf1 | ||
|
|
90a496bbe8 | ||
|
|
7e7a535af0 | ||
|
|
e0d4492013 | ||
|
|
2353bc62cc | ||
|
|
c8c821a692 | ||
|
|
3be05641fa | ||
|
|
23b1b7914a | ||
|
|
161689be18 | ||
|
|
3b512164d1 | ||
|
|
62845cac87 | ||
|
|
fa0615d41e | ||
|
|
21ce7ea420 | ||
|
|
84d140025a | ||
|
|
389b6ce7b2 | ||
|
|
cd7c2864d2 | ||
|
|
a07590eba5 | ||
|
|
9ecab8fb82 | ||
|
|
2346efd71f |
@@ -22,24 +22,51 @@ Examples of existing syncable entities: `skill`, `agent`, `view`, `viewField`, `
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [File Structure](#file-structure)
|
||||
3. [Step-by-Step Implementation](#step-by-step-implementation)
|
||||
- [Step 1: Add Metadata Name Constant](#step-1-add-metadata-name-constant-twenty-shared)
|
||||
- [Step 2: Create TypeORM Entity](#step-2-create-typeorm-entity)
|
||||
- [Step 3: Define Flat Entity Type](#step-3-define-flat-entity-type)
|
||||
- [Step 4: Define Editable Properties](#step-4-define-editable-properties)
|
||||
- [Step 5: Register in Central Constants](#step-5-register-in-central-constants)
|
||||
- [Step 6: Create Cache Service](#step-6-create-cache-service)
|
||||
- [Step 7: Create Flat Entity Module](#step-7-create-flat-entity-module)
|
||||
- [Step 8: Define Action Types](#step-8-define-action-types)
|
||||
- [Step 9: Create Validator Service](#step-9-create-validator-service)
|
||||
- [Step 10: Create Builder Service](#step-10-create-builder-service)
|
||||
- [Step 11: Create Action Handlers](#step-11-create-action-handlers-runner)
|
||||
- [Step 12: Wire in Orchestrator Service](#step-12-wire-in-orchestrator-service-critical)
|
||||
- [Step 13: Register in Modules](#step-13-register-in-modules)
|
||||
4. [Using the Entity in Services](#using-the-entity-in-services)
|
||||
5. [Integration Tests](#integration-tests)
|
||||
- [Creating a New Syncable Entity](#creating-a-new-syncable-entity)
|
||||
- [What is a Syncable Entity?](#what-is-a-syncable-entity)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Overview](#overview)
|
||||
- [Key Design Principle: Separation of Concerns](#key-design-principle-separation-of-concerns)
|
||||
- [File Structure](#file-structure)
|
||||
- [Step-by-Step Implementation](#step-by-step-implementation)
|
||||
- [Step 1: Add Metadata Name Constant (twenty-shared)](#step-1-add-metadata-name-constant-twenty-shared)
|
||||
- [Step 2: Create TypeORM Entity](#step-2-create-typeorm-entity)
|
||||
- [Step 2b: Using JsonbProperty and SerializedRelation Types](#step-2b-using-jsonbproperty-and-serializedrelation-types)
|
||||
- [JsonbProperty Wrapper](#jsonbproperty-wrapper)
|
||||
- [SerializedRelation Type](#serializedrelation-type)
|
||||
- [Complete Example](#complete-example)
|
||||
- [Step 3: Define Flat Entity Type](#step-3-define-flat-entity-type)
|
||||
- [Step 4: Define Editable Properties](#step-4-define-editable-properties)
|
||||
- [Step 5: Register in Central Constants](#step-5-register-in-central-constants)
|
||||
- [5a. All Flat Entity Types Registry](#5a-all-flat-entity-types-registry)
|
||||
- [5b. Properties to Compare and Stringify](#5b-properties-to-compare-and-stringify)
|
||||
- [5c. Metadata Relations](#5c-metadata-relations)
|
||||
- [5d. Required Metadata for Validation](#5d-required-metadata-for-validation)
|
||||
- [Step 6: Create Cache Service](#step-6-create-cache-service)
|
||||
- [Step 7: Create Flat Entity Module](#step-7-create-flat-entity-module)
|
||||
- [Step 8: Define Action Types](#step-8-define-action-types)
|
||||
- [Step 9: Create Validator Service](#step-9-create-validator-service)
|
||||
- [Step 10: Create Builder Service](#step-10-create-builder-service)
|
||||
- [Step 11: Create Action Handlers (Runner)](#step-11-create-action-handlers-runner)
|
||||
- [Step 12: Wire in Orchestrator Service (CRITICAL)](#step-12-wire-in-orchestrator-service-critical)
|
||||
- [Step 13: Register in Modules](#step-13-register-in-modules)
|
||||
- [13a. Builder Module](#13a-builder-module)
|
||||
- [13b. Validators Module](#13b-validators-module)
|
||||
- [13c. Action Handlers Module](#13c-action-handlers-module)
|
||||
- [Using the Entity in Services](#using-the-entity-in-services)
|
||||
- [Integration Tests](#integration-tests)
|
||||
- [Checklist](#checklist)
|
||||
- [Syncable Entity Requirements](#syncable-entity-requirements)
|
||||
- [JSONB Properties and Serialized Relations](#jsonb-properties-and-serialized-relations)
|
||||
- [Registration (twenty-shared)](#registration-twenty-shared)
|
||||
- [Flat Entity Definition](#flat-entity-definition)
|
||||
- [Central Constants Registration](#central-constants-registration)
|
||||
- [Cache Layer](#cache-layer)
|
||||
- [Migration Builder](#migration-builder)
|
||||
- [Migration Runner](#migration-runner)
|
||||
- [Orchestrator Wiring (⚠️ COMMONLY FORGOTTEN)](#orchestrator-wiring-️-commonly-forgotten)
|
||||
- [Module Registration](#module-registration)
|
||||
- [Testing](#testing)
|
||||
|
||||
---
|
||||
|
||||
@@ -176,9 +203,6 @@ export class MyEntityEntity
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
standardId: string | null;
|
||||
|
||||
@Column({ nullable: false })
|
||||
name: string;
|
||||
|
||||
@@ -237,6 +261,157 @@ export abstract class WorkspaceRelatedEntity {
|
||||
|
||||
---
|
||||
|
||||
### Step 2b: Using JsonbProperty and SerializedRelation Types
|
||||
|
||||
When your entity has JSONB columns or stores foreign key references inside JSONB structures, you must use the branded type wrappers to enable automatic universal identifier mapping.
|
||||
|
||||
#### JsonbProperty Wrapper
|
||||
|
||||
Wrap all JSONB column types with `JsonbProperty<T>` to mark them for the universal entity transformation system:
|
||||
|
||||
```typescript
|
||||
import { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
|
||||
|
||||
@Entity('myEntity')
|
||||
export class MyEntityEntity extends SyncableEntity {
|
||||
// Simple JSONB column - wrap the type
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
settings: JsonbProperty<MyEntitySettings> | null;
|
||||
|
||||
// JSONB column with complex type
|
||||
@Column({ type: 'jsonb', nullable: false })
|
||||
configuration: JsonbProperty<MyEntityConfiguration>;
|
||||
|
||||
// Array stored as JSONB
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
tags: JsonbProperty<string[]> | null;
|
||||
}
|
||||
```
|
||||
|
||||
**When to use `JsonbProperty<T>`:**
|
||||
- Any column with `type: 'jsonb'` that stores an object or array
|
||||
- Configuration objects, settings, metadata blobs
|
||||
- Any structured data stored as JSON in the database
|
||||
|
||||
**What it enables:**
|
||||
- The type system can identify which properties are JSONB columns
|
||||
- Automatic transformation of serialized relations within JSONB structures
|
||||
- Type-safe universal entity mapping
|
||||
|
||||
#### SerializedRelation Type
|
||||
|
||||
Use `SerializedRelation` for properties **inside JSONB structures** that store foreign key references (entity IDs):
|
||||
|
||||
```typescript
|
||||
import { SerializedRelation } from 'twenty-shared/types';
|
||||
import { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
|
||||
|
||||
// Define the JSONB structure type
|
||||
type MyEntityConfiguration = {
|
||||
name: string;
|
||||
// This stores a reference to another field's ID - use SerializedRelation
|
||||
targetFieldMetadataId: SerializedRelation;
|
||||
// This stores a reference to an object's ID
|
||||
sourceObjectMetadataId: SerializedRelation;
|
||||
// Regular string - NOT a foreign key reference
|
||||
displayFormat: string;
|
||||
};
|
||||
|
||||
@Entity('myEntity')
|
||||
export class MyEntityEntity extends SyncableEntity {
|
||||
@Column({ type: 'jsonb', nullable: false })
|
||||
configuration: JsonbProperty<MyEntityConfiguration>;
|
||||
}
|
||||
```
|
||||
|
||||
**When to use `SerializedRelation`:**
|
||||
- Properties inside JSONB that store UUIDs referencing other entities
|
||||
- Foreign key relationships that can't use TypeORM relations (because they're in JSONB)
|
||||
- Any `*Id` property inside a JSONB structure that references another metadata entity
|
||||
|
||||
**What it enables:**
|
||||
- Automatic renaming from `*Id` to `*UniversalIdentifier` in universal entities
|
||||
- Type-safe extraction of serialized relation properties
|
||||
- Proper handling during workspace sync/migration
|
||||
|
||||
#### Complete Example
|
||||
|
||||
```typescript
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { SerializedRelation } from 'twenty-shared/types';
|
||||
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
import { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
|
||||
|
||||
// JSONB structure with serialized relations
|
||||
type WidgetConfiguration = {
|
||||
title: string;
|
||||
// Foreign keys stored in JSONB - use SerializedRelation
|
||||
fieldMetadataId: SerializedRelation;
|
||||
objectMetadataId: SerializedRelation;
|
||||
// Optional foreign key
|
||||
viewId?: SerializedRelation;
|
||||
// Regular properties (not foreign keys)
|
||||
displayMode: 'compact' | 'expanded';
|
||||
maxItems: number;
|
||||
};
|
||||
|
||||
type GridPosition = {
|
||||
row: number;
|
||||
column: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
@Entity('widget')
|
||||
export class WidgetEntity extends SyncableEntity implements Required<WidgetEntity> {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
name: string;
|
||||
|
||||
// JSONB column with serialized relations - wrap with JsonbProperty
|
||||
@Column({ type: 'jsonb', nullable: false })
|
||||
configuration: JsonbProperty<WidgetConfiguration>;
|
||||
|
||||
// JSONB column without serialized relations - still wrap with JsonbProperty
|
||||
@Column({ type: 'jsonb', nullable: false })
|
||||
gridPosition: JsonbProperty<GridPosition>;
|
||||
|
||||
@Column({ default: false })
|
||||
isCustom: boolean;
|
||||
|
||||
// ... other columns
|
||||
}
|
||||
```
|
||||
|
||||
**Result in Universal Entity:**
|
||||
|
||||
When transformed to a universal entity, the `configuration` property will have its `SerializedRelation` fields automatically renamed:
|
||||
|
||||
```typescript
|
||||
// Original (in database/flat entity)
|
||||
{
|
||||
fieldMetadataId: "abc-123",
|
||||
objectMetadataId: "def-456",
|
||||
viewId: "ghi-789",
|
||||
displayMode: "compact",
|
||||
maxItems: 10,
|
||||
}
|
||||
|
||||
// Transformed (in universal entity)
|
||||
{
|
||||
fieldMetadataUniversalIdentifier: "abc-123",
|
||||
objectMetadataUniversalIdentifier: "def-456",
|
||||
viewUniversalIdentifier: "ghi-789",
|
||||
displayMode: "compact",
|
||||
maxItems: 10,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Define Flat Entity Type
|
||||
|
||||
**File:** `src/engine/metadata-modules/flat-my-entity/types/flat-my-entity.type.ts`
|
||||
@@ -517,7 +692,7 @@ import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
|
||||
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
|
||||
import { MyEntityExceptionCode } from 'src/engine/metadata-modules/my-entity/my-entity.exception';
|
||||
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
|
||||
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
|
||||
@@ -585,7 +760,7 @@ export class FlatMyEntityValidatorService {
|
||||
}
|
||||
|
||||
// Prevent deletion of standard entities unless it's a system build
|
||||
if (!buildOptions.isSystemBuild && isStandardMetadata(existingEntity)) {
|
||||
if (!buildOptions.isSystemBuild && belongsToTwentyStandardApp(existingEntity)) {
|
||||
validationResult.errors.push({
|
||||
code: MyEntityExceptionCode.MY_ENTITY_IS_STANDARD,
|
||||
message: t`Cannot delete standard entity`,
|
||||
@@ -738,7 +913,7 @@ export class WorkspaceMigrationMyEntityActionsBuilderService extends WorkspaceEn
|
||||
type: 'update',
|
||||
metadataName: 'myEntity',
|
||||
entityId: flatEntityId,
|
||||
updates: flatEntityUpdates,
|
||||
update: flatEntityUpdates,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -1143,6 +1318,11 @@ Before considering your syncable entity complete, verify:
|
||||
- [ ] Entity has `isCustom` boolean column
|
||||
- [ ] Entity-to-flat transform sets `universalIdentifier` correctly (`standardId || id`)
|
||||
|
||||
### JSONB Properties and Serialized Relations
|
||||
- [ ] All JSONB columns are wrapped with `JsonbProperty<T>`
|
||||
- [ ] Foreign key references inside JSONB structures use `SerializedRelation` type
|
||||
- [ ] JSONB structure types are properly defined with `SerializedRelation` for `*Id` properties
|
||||
|
||||
### Registration (twenty-shared)
|
||||
- [ ] Metadata name added to `ALL_METADATA_NAME`
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 10
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -14,8 +14,8 @@ concurrency:
|
||||
|
||||
env:
|
||||
# restore-cache action adds 'v4-' prefix and '-<branch>-<sha>' suffix to the key
|
||||
STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION: storybook-build-depot-ubuntu-24.04-8-runner
|
||||
STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION: v4-storybook-build-depot-ubuntu-24.04-8-runner-${{ github.ref_name }}-${{ github.sha }}
|
||||
STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION: storybook-build-ubuntu-latest-8-cores-runner
|
||||
STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION: v4-storybook-build-ubuntu-latest-8-cores-runner-${{ github.ref_name }}-${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
@@ -27,6 +27,7 @@ jobs:
|
||||
packages/twenty-front/**
|
||||
packages/twenty-ui/**
|
||||
packages/twenty-shared/**
|
||||
packages/twenty-sdk/**
|
||||
changed-files-check-e2e:
|
||||
uses: ./.github/workflows/changed-files.yaml
|
||||
with:
|
||||
@@ -38,7 +39,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
env:
|
||||
REACT_APP_SERVER_BASE_URL: http://localhost:3000
|
||||
steps:
|
||||
@@ -64,7 +65,7 @@ jobs:
|
||||
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION }}
|
||||
front-sb-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: front-sb-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -85,6 +86,7 @@ jobs:
|
||||
run: |
|
||||
npx nx build twenty-shared
|
||||
npx nx build twenty-ui
|
||||
npx nx build twenty-sdk
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
cd packages/twenty-front
|
||||
@@ -138,7 +140,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
if: false
|
||||
needs: front-sb-build
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
env:
|
||||
REACT_APP_SERVER_BASE_URL: http://127.0.0.1:3000
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
@@ -204,7 +206,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=10240"
|
||||
steps:
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test:unit, test:integration]
|
||||
task: [lint, typecheck, test:unit, storybook:build, storybook:test, test:integration]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/[email protected]
|
||||
@@ -39,64 +39,62 @@ jobs:
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build
|
||||
run: npx nx build twenty-sdk
|
||||
- name: Install Playwright
|
||||
if: contains(matrix.task, 'storybook')
|
||||
run: npx playwright install chromium
|
||||
- name: Run ${{ matrix.task }} task
|
||||
uses: ./.github/actions/nx-affected
|
||||
with:
|
||||
tag: scope:sdk
|
||||
tasks: ${{ matrix.task }}
|
||||
# TODO: Re-enable sdk-e2e-test once application sync is stable
|
||||
# sdk-e2e-test:
|
||||
# timeout-minutes: 30
|
||||
# runs-on: depot-ubuntu-24.04-8
|
||||
# needs: [changed-files-check, sdk-test]
|
||||
# if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
# services:
|
||||
# postgres:
|
||||
# image: twentycrm/twenty-postgres-spilo
|
||||
# env:
|
||||
# PGUSER_SUPERUSER: postgres
|
||||
# PGPASSWORD_SUPERUSER: postgres
|
||||
# ALLOW_NOSSL: 'true'
|
||||
# SPILO_PROVIDER: 'local'
|
||||
# ports:
|
||||
# - 5432:5432
|
||||
# options: >-
|
||||
# --health-cmd pg_isready
|
||||
# --health-interval 10s
|
||||
# --health-timeout 5s
|
||||
# --health-retries 5
|
||||
# redis:
|
||||
# image: redis
|
||||
# ports:
|
||||
# - 6379:6379
|
||||
# env:
|
||||
# NODE_ENV: test
|
||||
# steps:
|
||||
# - name: Fetch custom Github Actions and base branch history
|
||||
# uses: actions/checkout@v4
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - name: Install dependencies
|
||||
# uses: ./.github/actions/yarn-install
|
||||
# - name: Server / Append billing config to .env.test
|
||||
# working-directory: packages/twenty-server
|
||||
# run: |
|
||||
# echo "" >> .env.test
|
||||
# echo "IS_BILLING_ENABLED=true" >> .env.test
|
||||
# echo "BILLING_STRIPE_API_KEY=test-api-key" >> .env.test
|
||||
# echo "BILLING_STRIPE_BASE_PLAN_PRODUCT_ID=test-base-plan-product-id" >> .env.test
|
||||
# echo "BILLING_STRIPE_WEBHOOK_SECRET=test-webhook-secret" >> .env.test
|
||||
# echo "BILLING_PLAN_REQUIRED_LINK=http://localhost:3001/stripe-redirection" >> .env.test
|
||||
# - name: Server / Create Test DB
|
||||
# run: |
|
||||
# PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
|
||||
# - name: SDK / Run E2E Tests
|
||||
# run: npx nx test:e2e twenty-sdk
|
||||
sdk-e2e-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: [changed-files-check, sdk-test]
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
env:
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
env:
|
||||
NODE_ENV: test
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build
|
||||
run: npx nx build twenty-sdk
|
||||
- name: Server / Create Test DB
|
||||
run: |
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
|
||||
- name: SDK / Run e2e Tests
|
||||
uses: ./.github/actions/nx-affected
|
||||
with:
|
||||
tag: scope:sdk
|
||||
tasks: test:e2e
|
||||
ci-sdk-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changed-files-check, sdk-test]
|
||||
needs: [changed-files-check, sdk-test, sdk-e2e-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
key: ${{ steps.restore-server-setup-cache.outputs.cache-primary-key }}
|
||||
server-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: server-setup
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
@@ -164,7 +164,7 @@ jobs:
|
||||
|
||||
server-integration-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: server-setup
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
repository_dispatch:
|
||||
types: [claude-core-team-issues]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.event.client_payload.issue_number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot') ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot') ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && github.event.review.user.type != 'Bot') ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
id-token: write
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Run Claude Code
|
||||
id: claude-code
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
claude_args: '--max-turns 200 --model opus --allowedTools "Edit,Write,WebFetch,Bash(bash packages/twenty-utils/setup-dev-env.sh),Bash(npx nx *),Bash(npx jest *),Bash(yarn *),Bash(git *),Bash(gh *),Bash(sed *),Bash(python3 *),Bash(rm *),Bash(find *),Bash(grep *),Bash(cat *),Bash(ls *),Bash(head *),Bash(tail *),Bash(wc *),Bash(sort *),Bash(uniq *),Bash(mkdir *),Bash(cp *),Bash(mv *),Bash(touch *),Bash(chmod *),Bash(echo *),Bash(curl *),Bash(cd *),Bash(pwd *),Bash(diff *),Bash(xargs *),Bash(awk *),Bash(cut *),Bash(tee *),Bash(tr *)"'
|
||||
settings: |
|
||||
{
|
||||
"env": {
|
||||
"PG_DATABASE_URL": "postgres://postgres:postgres@localhost:5432/default"
|
||||
}
|
||||
}
|
||||
- name: Post Create-PR link if Claude ran out of turns
|
||||
if: failure()
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
BRANCH=$(git branch --show-current)
|
||||
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "" ]; then
|
||||
exit 0
|
||||
fi
|
||||
AHEAD=$(git rev-list --count main.."$BRANCH" 2>/dev/null || echo "0")
|
||||
if [ "$AHEAD" = "0" ]; then
|
||||
exit 0
|
||||
fi
|
||||
EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || echo "")
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
exit 0
|
||||
fi
|
||||
ISSUE_NUMBER="${{ github.event.issue.number || github.event.pull_request.number }}"
|
||||
ENCODED_BRANCH=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$BRANCH', safe=''))")
|
||||
PR_URL="https://github.com/${{ github.repository }}/compare/main...${ENCODED_BRANCH}?quick_pull=1"
|
||||
BODY="⚠️ Claude ran out of turns before creating a PR. Work has been pushed to [\`$BRANCH\`](https://github.com/${{ github.repository }}/tree/$ENCODED_BRANCH).\n\n[**Create PR →**]($PR_URL)"
|
||||
if [ -n "$ISSUE_NUMBER" ]; then
|
||||
gh issue comment "$ISSUE_NUMBER" --body "$(echo -e "$BODY")"
|
||||
fi
|
||||
|
||||
claude-cross-repo:
|
||||
if: github.event_name == 'repository_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
id-token: write
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build prompt from dispatch payload
|
||||
id: prompt
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const p = context.payload.client_payload;
|
||||
let prompt;
|
||||
if (p.comment_body) {
|
||||
prompt = `You are responding to a comment on issue #${p.issue_number} ("${p.issue_title}") in the ${p.repo_full_name} repository.\n\nThe comment by @${p.sender} says:\n\n${p.comment_body}\n\nIssue body:\n\n${p.issue_body}\n\nPlease help with this request. The code you are working with is the twenty codebase (this repository).`;
|
||||
} else {
|
||||
prompt = `You are responding to issue #${p.issue_number} ("${p.issue_title}") in the ${p.repo_full_name} repository, opened by @${p.sender}.\n\nIssue body:\n\n${p.issue_body}\n\nPlease help with this request. The code you are working with is the twenty codebase (this repository).`;
|
||||
}
|
||||
core.setOutput('prompt', prompt);
|
||||
core.setOutput('repo', p.repo_full_name);
|
||||
core.setOutput('issue_number', p.issue_number);
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
prompt: ${{ steps.prompt.outputs.prompt }}
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
claude_args: '--max-turns 200 --model opus --allowedTools "Edit,Write,WebFetch,Bash(bash packages/twenty-utils/setup-dev-env.sh),Bash(npx nx *),Bash(npx jest *),Bash(yarn *),Bash(git *),Bash(gh *),Bash(sed *),Bash(python3 *),Bash(rm *),Bash(find *),Bash(grep *),Bash(cat *),Bash(ls *),Bash(head *),Bash(tail *),Bash(wc *),Bash(sort *),Bash(uniq *),Bash(mkdir *),Bash(cp *),Bash(mv *),Bash(touch *),Bash(chmod *),Bash(echo *),Bash(curl *),Bash(cd *),Bash(pwd *),Bash(diff *),Bash(xargs *),Bash(awk *),Bash(cut *),Bash(tee *),Bash(tr *)"'
|
||||
settings: |
|
||||
{
|
||||
"env": {
|
||||
"PG_DATABASE_URL": "postgres://postgres:postgres@localhost:5432/default"
|
||||
}
|
||||
}
|
||||
- name: Post response to source issue
|
||||
if: always()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.TWENTY_DISPATCH_TOKEN }}
|
||||
script: |
|
||||
const [owner, repo] = '${{ steps.prompt.outputs.repo }}'.split('/');
|
||||
const issueNumber = parseInt('${{ steps.prompt.outputs.issue_number }}', 10);
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
body: `Claude finished processing this request. [See workflow run](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`
|
||||
});
|
||||
Vendored
+22
@@ -22,6 +22,28 @@
|
||||
"close": false
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "twenty-server - run unit test file",
|
||||
"type": "shell",
|
||||
"command": "npx nx run twenty-server:jest -- --config ./jest.config.mjs ${relativeFile} --silent=false ${input:watchMode} ${input:updateSnapshot}",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/packages/twenty-server",
|
||||
"env": {
|
||||
"NODE_ENV": "test",
|
||||
"NODE_OPTIONS": "--max-old-space-size=12288 --import tsx/esm"
|
||||
},
|
||||
"shell": {
|
||||
"executable": "/bin/zsh",
|
||||
"args": ["-l", "-c"]
|
||||
}
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new",
|
||||
"close": false
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
@@ -21,30 +21,31 @@ npx nx run twenty-server:worker # Start background worker
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
# Run tests
|
||||
# Preferred: run a single test file (fast)
|
||||
npx jest path/to/test.test.ts --config=packages/PROJECT/jest.config.mjs
|
||||
|
||||
# Run all tests for a package
|
||||
npx nx test twenty-front # Frontend unit tests
|
||||
npx nx test twenty-server # Backend unit tests
|
||||
npx nx run twenty-server:test:integration:with-db-reset # Integration tests with DB reset
|
||||
|
||||
# Storybook
|
||||
npx nx storybook:build twenty-front # Build Storybook
|
||||
npx nx storybook:test twenty-front # Run Storybook tests
|
||||
npx nx storybook:build twenty-front
|
||||
npx nx storybook:test twenty-front
|
||||
|
||||
|
||||
When testing the UI end to end, click on "Continue with Email" and use the prefilled credentials.
|
||||
# When testing the UI end to end, click on "Continue with Email" and use the prefilled credentials.
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
```bash
|
||||
# Linting (diff with main - fastest)
|
||||
npx nx lint:diff-with-main twenty-front # Lint only files changed vs main
|
||||
npx nx lint:diff-with-main twenty-server # Lint only files changed vs main
|
||||
npx nx lint:diff-with-main twenty-front --configuration=fix # Auto-fix files changed vs main
|
||||
# Linting (diff with main - fastest, always prefer this)
|
||||
npx nx lint:diff-with-main twenty-front
|
||||
npx nx lint:diff-with-main twenty-server
|
||||
npx nx lint:diff-with-main twenty-front --configuration=fix # Auto-fix
|
||||
|
||||
# Linting (full project)
|
||||
npx nx lint twenty-front # Lint all files in frontend
|
||||
npx nx lint twenty-server # Lint all files in backend
|
||||
npx nx lint twenty-front --fix # Auto-fix all linting issues
|
||||
# Linting (full project - slower, use only when needed)
|
||||
npx nx lint twenty-front
|
||||
npx nx lint twenty-server
|
||||
|
||||
# Type checking
|
||||
npx nx typecheck twenty-front
|
||||
@@ -57,7 +58,8 @@ npx nx fmt twenty-server
|
||||
|
||||
### Build
|
||||
```bash
|
||||
# Build packages
|
||||
# Build packages (twenty-shared must be built first)
|
||||
npx nx build twenty-shared
|
||||
npx nx build twenty-front
|
||||
npx nx build twenty-server
|
||||
```
|
||||
@@ -69,7 +71,7 @@ npx nx database:reset twenty-server # Reset database
|
||||
npx nx run twenty-server:database:init:prod # Initialize database
|
||||
npx nx run twenty-server:database:migrate:prod # Run migrations
|
||||
|
||||
# Generate migration
|
||||
# Generate migration (replace [name] with kebab-case descriptive name)
|
||||
npx nx run twenty-server:typeorm migration:generate src/database/typeorm/core/migrations/common/[name] -d src/database/typeorm/core/core.datasource.ts
|
||||
|
||||
# Sync metadata
|
||||
@@ -78,8 +80,9 @@ npx nx run twenty-server:command workspace:sync-metadata
|
||||
|
||||
### GraphQL
|
||||
```bash
|
||||
# Generate GraphQL types
|
||||
# Generate GraphQL types (run after schema changes)
|
||||
npx nx run twenty-front:graphql:generate
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
@@ -107,13 +110,36 @@ packages/
|
||||
- **Named exports only** (no default exports)
|
||||
- **Types over interfaces** (except when extending third-party interfaces)
|
||||
- **String literals over enums** (except for GraphQL enums)
|
||||
- **No 'any' type allowed**
|
||||
- **No 'any' type allowed** — strict TypeScript enforced
|
||||
- **Event handlers preferred over useEffect** for state updates
|
||||
- **Props down, events up** — unidirectional data flow
|
||||
- **Composition over inheritance**
|
||||
- **No abbreviations** in variable names (`user` not `u`, `fieldMetadata` not `fm`)
|
||||
|
||||
### Naming Conventions
|
||||
- **Variables/functions**: camelCase
|
||||
- **Constants**: SCREAMING_SNAKE_CASE
|
||||
- **Types/Classes**: PascalCase (suffix component props with `Props`, e.g. `ButtonProps`)
|
||||
- **Files/directories**: kebab-case with descriptive suffixes (`.component.tsx`, `.service.ts`, `.entity.ts`, `.dto.ts`, `.module.ts`)
|
||||
- **TypeScript generics**: descriptive names (`TData` not `T`)
|
||||
|
||||
### File Structure
|
||||
- Components under 300 lines, services under 500 lines
|
||||
- Components in their own directories with tests and stories
|
||||
- Use `index.ts` barrel exports for clean imports
|
||||
- Import order: external libraries first, then internal (`@/`), then relative
|
||||
|
||||
### Comments
|
||||
- Use short-form comments (`//`), not JSDoc blocks
|
||||
- Explain WHY (business logic), not WHAT
|
||||
- Do not comment obvious code
|
||||
- Multi-line comments use multiple `//` lines, not `/** */`
|
||||
|
||||
### State Management
|
||||
- **Recoil** for global state management
|
||||
- Component-specific state with React hooks
|
||||
- **Recoil** for global state: atoms for primitive state, selectors for derived state, atom families for dynamic collections
|
||||
- Component-specific state with React hooks (`useState`, `useReducer` for complex logic)
|
||||
- GraphQL cache managed by Apollo Client
|
||||
- Use functional state updates: `setState(prev => prev + 1)`
|
||||
|
||||
### Backend Architecture
|
||||
- **NestJS modules** for feature organization
|
||||
@@ -122,36 +148,54 @@ packages/
|
||||
- **Redis** for caching and session management
|
||||
- **BullMQ** for background job processing
|
||||
|
||||
### Database
|
||||
### Database & Migrations
|
||||
- **PostgreSQL** as primary database
|
||||
- **Redis** for caching and sessions
|
||||
- **TypeORM migrations** for schema management
|
||||
- **ClickHouse** for analytics (when enabled)
|
||||
- Always generate migrations when changing entity files
|
||||
- Migration names must be kebab-case (e.g. `add-agent-turn-evaluation`)
|
||||
- Include both `up` and `down` logic in migrations
|
||||
- Never delete or rewrite committed migrations
|
||||
|
||||
### Utility Helpers
|
||||
Use existing helpers from `twenty-shared` instead of manual type guards:
|
||||
- `isDefined()`, `isNonEmptyString()`, `isNonEmptyArray()`
|
||||
|
||||
## Development Workflow
|
||||
|
||||
IMPORTANT: Use Context7 for code generation, setup or configuration steps, or library/API documentation. Automatically use the Context7 MCP tools to resolve library IDs and get library docs without waiting for explicit requests.
|
||||
|
||||
### Before Making Changes
|
||||
1. Always run linting and type checking after code changes
|
||||
2. Test changes with relevant test suites
|
||||
3. Ensure database migrations are properly structured
|
||||
1. Always run linting (`lint:diff-with-main`) and type checking after code changes
|
||||
2. Test changes with relevant test suites (prefer single-file test runs)
|
||||
3. Ensure database migrations are generated for entity changes
|
||||
4. Check that GraphQL schema changes are backward compatible
|
||||
5. Run `graphql:generate` after any GraphQL schema changes
|
||||
|
||||
### Code Style Notes
|
||||
- Use **Emotion** for styling with styled-components pattern
|
||||
- Follow **Nx** workspace conventions for imports
|
||||
- Use **Lingui** for internationalization
|
||||
- Components should be in their own directories with tests and stories
|
||||
- Apply security first, then formatting (sanitize before format)
|
||||
|
||||
### Testing Strategy
|
||||
- **Unit tests** with Jest for both frontend and backend
|
||||
- **Integration tests** for critical backend workflows
|
||||
- **Storybook** for component development and testing
|
||||
- **E2E tests** with Playwright for critical user flows
|
||||
- **Test behavior, not implementation** — focus on user perspective
|
||||
- **Test pyramid**: 70% unit, 20% integration, 10% E2E
|
||||
- Query by user-visible elements (text, roles, labels) over test IDs
|
||||
- Use `@testing-library/user-event` for realistic interactions
|
||||
- Descriptive test names: "should [behavior] when [condition]"
|
||||
- Clear mocks between tests with `jest.clearAllMocks()`
|
||||
|
||||
## CI Environment (GitHub Actions)
|
||||
|
||||
When running in CI, the dev environment is **not** pre-configured. Dependencies are installed but builds, env files, and databases are not set up.
|
||||
|
||||
- **Before running tests, builds, lint, type checks, or DB operations**, run: `bash packages/twenty-utils/setup-dev-env.sh`
|
||||
- **Skip the setup script** for tasks that only read code — architecture questions, code review, documentation, etc.
|
||||
- The script is idempotent and safe to run multiple times.
|
||||
|
||||
## Important Files
|
||||
- `nx.json` - Nx workspace configuration with task definitions
|
||||
- `tsconfig.base.json` - Base TypeScript configuration
|
||||
- `package.json` - Root package with workspace definitions
|
||||
- `.cursor/rules/` - Development guidelines and best practices
|
||||
- `.cursor/rules/` - Detailed development guidelines and best practices
|
||||
|
||||
+8
-2
@@ -11,7 +11,9 @@ import unicornPlugin from 'eslint-plugin-unicorn';
|
||||
import unusedImportsPlugin from 'eslint-plugin-unused-imports';
|
||||
import jsoncParser from 'jsonc-eslint-parser';
|
||||
|
||||
const twentyRules = await nxPlugin.loadWorkspaceRules('packages/twenty-eslint-rules');
|
||||
const twentyRules = await nxPlugin.loadWorkspaceRules(
|
||||
'packages/twenty-eslint-rules',
|
||||
);
|
||||
|
||||
export default [
|
||||
// Base JavaScript configuration
|
||||
@@ -65,6 +67,10 @@ export default [
|
||||
sourceTag: 'scope:sdk',
|
||||
onlyDependOnLibsWithTags: ['scope:sdk', 'scope:shared'],
|
||||
},
|
||||
{
|
||||
sourceTag: 'scope:create-app',
|
||||
onlyDependOnLibsWithTags: ['scope:create-app', 'scope:shared'],
|
||||
},
|
||||
{
|
||||
sourceTag: 'scope:shared',
|
||||
onlyDependOnLibsWithTags: ['scope:shared'],
|
||||
@@ -197,7 +203,7 @@ export default [
|
||||
plugins: {
|
||||
...mdxPlugin.flat.plugins,
|
||||
'@nx': nxPlugin,
|
||||
'twenty': { rules: twentyRules },
|
||||
twenty: { rules: twentyRules },
|
||||
},
|
||||
},
|
||||
mdxPlugin.flatCodeBlocks,
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
Create Twenty App is the official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). It sets up a ready‑to‑run project that works seamlessly with the [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
|
||||
|
||||
- Zero‑config project bootstrap
|
||||
- Preconfigured scripts for auth, generate, dev sync, one‑off sync, uninstall
|
||||
- Preconfigured scripts for auth, dev mode (watch & sync), generate, uninstall, and function management
|
||||
- Strong TypeScript support and typed client generation
|
||||
|
||||
## Documentation
|
||||
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
|
||||
## Prerequisites
|
||||
- Node.js 24+ (recommended) and Yarn 4
|
||||
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
|
||||
@@ -32,7 +35,7 @@ cd my-twenty-app
|
||||
corepack enable
|
||||
yarn install
|
||||
|
||||
# Get Help
|
||||
# Get help
|
||||
yarn run help
|
||||
|
||||
# Authenticate using your API key (you'll be prompted)
|
||||
@@ -44,29 +47,33 @@ yarn entity:add
|
||||
# Generate a typed Twenty client and workspace entity types
|
||||
yarn app:generate
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
# Start dev mode: watches, builds, and syncs local changes to your workspace
|
||||
yarn app:dev
|
||||
|
||||
# Or run a one‑time sync
|
||||
yarn app:sync
|
||||
|
||||
# Watch your application's functions logs
|
||||
# Watch your application's function logs
|
||||
yarn function:logs
|
||||
|
||||
# Execute a function with a JSON payload
|
||||
yarn function:execute -n my-function -p '{"key": "value"}'
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn app:uninstall
|
||||
```
|
||||
|
||||
## What gets scaffolded
|
||||
- A minimal app structure ready for Twenty
|
||||
- A minimal app structure ready for Twenty with example files:
|
||||
- `application-config.ts` - Application metadata configuration
|
||||
- `roles/default-role.ts` - Default role for logic functions
|
||||
- `logic-functions/hello-world.ts` - Example logic function with HTTP trigger
|
||||
- `front-components/hello-world.tsx` - Example front component
|
||||
- TypeScript configuration
|
||||
- Prewired scripts that wrap the `twenty` CLI from twenty-sdk
|
||||
- Example placeholders to help you add entities, actions, and sync logic
|
||||
|
||||
## Next steps
|
||||
- Explore the generated project and add your first entity with `yarn entity:add`.
|
||||
- Use `yarn auth:login` to authenticate with your Twenty workspace.
|
||||
- Explore the generated project and add your first entity with `yarn entity:add` (logic functions, front components, objects, roles).
|
||||
- Use `yarn app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
|
||||
- Keep your types up‑to‑date using `yarn app:generate`.
|
||||
- Use `yarn app:dev` while you iterate to see changes instantly in your workspace.
|
||||
|
||||
|
||||
## Publish your application
|
||||
|
||||
@@ -1,111 +1,20 @@
|
||||
import js from '@eslint/js';
|
||||
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
||||
import typescriptParser from '@typescript-eslint/parser';
|
||||
import prettierPlugin from 'eslint-plugin-prettier';
|
||||
import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
parser: typescriptParser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
},
|
||||
globals: {
|
||||
// Node.js globals
|
||||
process: 'readonly',
|
||||
console: 'readonly',
|
||||
Buffer: 'readonly',
|
||||
__dirname: 'readonly',
|
||||
__filename: 'readonly',
|
||||
global: 'readonly',
|
||||
setTimeout: 'readonly',
|
||||
clearTimeout: 'readonly',
|
||||
setInterval: 'readonly',
|
||||
clearInterval: 'readonly',
|
||||
// Browser globals that Node.js also has
|
||||
URL: 'readonly',
|
||||
URLSearchParams: 'readonly',
|
||||
// Node.js types
|
||||
NodeJS: 'readonly',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': typescriptEslint,
|
||||
prettier: prettierPlugin,
|
||||
},
|
||||
ignores: ['**/dist/**'],
|
||||
},
|
||||
{
|
||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||
rules: {
|
||||
...typescriptEslint.configs.recommended.rules,
|
||||
'prettier/prettier': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
process: 'readonly',
|
||||
console: 'readonly',
|
||||
Buffer: 'readonly',
|
||||
__dirname: 'readonly',
|
||||
__filename: 'readonly',
|
||||
global: 'readonly',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**/*.ts'],
|
||||
languageOptions: {
|
||||
parser: typescriptParser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
},
|
||||
globals: {
|
||||
// Node.js globals
|
||||
process: 'readonly',
|
||||
console: 'readonly',
|
||||
Buffer: 'readonly',
|
||||
__dirname: 'readonly',
|
||||
__filename: 'readonly',
|
||||
global: 'readonly',
|
||||
// Jest globals
|
||||
describe: 'readonly',
|
||||
it: 'readonly',
|
||||
test: 'readonly',
|
||||
expect: 'readonly',
|
||||
jest: 'readonly',
|
||||
beforeEach: 'readonly',
|
||||
afterEach: 'readonly',
|
||||
beforeAll: 'readonly',
|
||||
afterAll: 'readonly',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': typescriptEslint,
|
||||
prettier: prettierPlugin,
|
||||
},
|
||||
rules: {
|
||||
...typescriptEslint.configs.recommended.rules,
|
||||
'prettier/prettier': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ['dist/**', 'node_modules/**'],
|
||||
ignores: ['src/**/*.ts', '!src/cli/**/*.ts'],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"name": "create-twenty-app",
|
||||
"version": "0.3.1",
|
||||
"version": "0.5.0",
|
||||
"description": "Command-line interface to create Twenty application",
|
||||
"main": "dist/cli.cjs",
|
||||
"bin": "dist/cli.cjs",
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
"dist",
|
||||
"README.md",
|
||||
"package.json"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npx rimraf dist && npx vite build"
|
||||
"build": "npx rimraf dist && npx vite build",
|
||||
"prepublishOnly": "tsx ../twenty-utils/pack-scripts/pre-publish-only.ts",
|
||||
"postpublish": "tsx ../twenty-utils/pack-scripts/post-publish.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"twenty",
|
||||
@@ -34,6 +38,7 @@
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.kebabcase": "^4.1.1",
|
||||
"lodash.startcase": "^4.4.0",
|
||||
"twenty-shared": "workspace:*",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -43,6 +48,7 @@
|
||||
"@types/lodash.kebabcase": "^4.1.7",
|
||||
"@types/lodash.startcase": "^4",
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-dts": "^4.5.4",
|
||||
"vite-tsconfig-paths": "^4.2.1"
|
||||
|
||||
@@ -14,12 +14,6 @@ Then, start development mode to sync your app and watch for changes:
|
||||
yarn app:dev
|
||||
```
|
||||
|
||||
Or run a one-time sync:
|
||||
|
||||
```bash
|
||||
yarn app:sync
|
||||
```
|
||||
|
||||
Open your Twenty instance and go to `/settings/applications` section to see the result.
|
||||
|
||||
## Available Commands
|
||||
@@ -29,11 +23,12 @@ Open your Twenty instance and go to `/settings/applications` section to see the
|
||||
yarn auth:login # Authenticate with Twenty
|
||||
yarn auth:logout # Remove credentials
|
||||
yarn auth:status # Check auth status
|
||||
yarn auth:switch # Switch default workspace
|
||||
yarn auth:list # List all configured workspaces
|
||||
|
||||
# Application
|
||||
yarn app:dev # Start dev mode (sync + watch)
|
||||
yarn app:sync # One-time sync
|
||||
yarn entity:add # Add a new entity (function, object, role)
|
||||
yarn app:dev # Start dev mode (watch, build, and sync)
|
||||
yarn entity:add # Add a new entity (function, front-component, object, role)
|
||||
yarn app:generate # Generate typed Twenty client
|
||||
yarn function:logs # Stream function logs
|
||||
yarn function:execute # Execute a function with JSON payload
|
||||
|
||||
@@ -12,6 +12,9 @@ jest.mock('fs-extra', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const APPLICATION_FILE_NAME = 'application-config.ts';
|
||||
const DEFAULT_ROLE_FILE_NAME = 'default-role.ts';
|
||||
|
||||
describe('copyBaseApplicationProject', () => {
|
||||
let testAppDirectory: string;
|
||||
|
||||
@@ -32,7 +35,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should create the correct folder structure with src/app/', async () => {
|
||||
it('should create the correct folder structure with src/', async () => {
|
||||
await copyBaseApplicationProject({
|
||||
appName: 'my-test-app',
|
||||
appDisplayName: 'My Test App',
|
||||
@@ -40,16 +43,16 @@ describe('copyBaseApplicationProject', () => {
|
||||
appDirectory: testAppDirectory,
|
||||
});
|
||||
|
||||
// Verify src/app/ folder exists
|
||||
// Verify src/ folder exists
|
||||
const srcAppPath = join(testAppDirectory, 'src');
|
||||
expect(await fs.pathExists(srcAppPath)).toBe(true);
|
||||
|
||||
// Verify application.config.ts exists in src/app/
|
||||
const appConfigPath = join(srcAppPath, 'application.config.ts');
|
||||
// Verify application-config.ts exists in src/
|
||||
const appConfigPath = join(srcAppPath, APPLICATION_FILE_NAME);
|
||||
expect(await fs.pathExists(appConfigPath)).toBe(true);
|
||||
|
||||
// Verify default-function.role.ts exists in src/app/
|
||||
const roleConfigPath = join(srcAppPath, 'default-function.role.ts');
|
||||
// Verify default-role.ts exists in src/
|
||||
const roleConfigPath = join(srcAppPath, 'roles', DEFAULT_ROLE_FILE_NAME);
|
||||
expect(await fs.pathExists(roleConfigPath)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -67,8 +70,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
expect(packageJson.name).toBe('my-test-app');
|
||||
expect(packageJson.version).toBe('0.1.0');
|
||||
expect(packageJson.dependencies['twenty-sdk']).toBe('0.3.1');
|
||||
expect(packageJson.scripts['app:sync']).toBe('twenty app:sync');
|
||||
expect(packageJson.dependencies['twenty-sdk']).toBe('0.5.0');
|
||||
expect(packageJson.scripts['app:dev']).toBe('twenty app:dev');
|
||||
});
|
||||
|
||||
@@ -103,7 +105,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
expect(yarnLockContent).toContain('yarn lockfile v1');
|
||||
});
|
||||
|
||||
it('should create application.config.ts with defineApp and correct values', async () => {
|
||||
it('should create application-config.ts with defineApplication and correct values', async () => {
|
||||
await copyBaseApplicationProject({
|
||||
appName: 'my-test-app',
|
||||
appDisplayName: 'My Test App',
|
||||
@@ -111,22 +113,18 @@ describe('copyBaseApplicationProject', () => {
|
||||
appDirectory: testAppDirectory,
|
||||
});
|
||||
|
||||
const appConfigPath = join(
|
||||
testAppDirectory,
|
||||
'src',
|
||||
'application.config.ts',
|
||||
);
|
||||
const appConfigPath = join(testAppDirectory, 'src', APPLICATION_FILE_NAME);
|
||||
const appConfigContent = await fs.readFile(appConfigPath, 'utf8');
|
||||
|
||||
// Verify it uses defineApp
|
||||
// Verify it uses defineApplication
|
||||
expect(appConfigContent).toContain(
|
||||
"import { defineApp } from 'twenty-sdk'",
|
||||
"import { defineApplication } from 'twenty-sdk'",
|
||||
);
|
||||
expect(appConfigContent).toContain('export default defineApp({');
|
||||
expect(appConfigContent).toContain('export default defineApplication({');
|
||||
|
||||
// Verify it imports the role identifier
|
||||
expect(appConfigContent).toContain(
|
||||
"import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from 'src/default-function.role'",
|
||||
"import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'",
|
||||
);
|
||||
|
||||
// Verify display name and description
|
||||
@@ -140,11 +138,11 @@ describe('copyBaseApplicationProject', () => {
|
||||
|
||||
// Verify it references the role
|
||||
expect(appConfigContent).toContain(
|
||||
'functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER',
|
||||
'defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER',
|
||||
);
|
||||
});
|
||||
|
||||
it('should create default-function.role.ts with defineRole and correct values', async () => {
|
||||
it('should create default-role.ts with defineRole and correct values', async () => {
|
||||
await copyBaseApplicationProject({
|
||||
appName: 'my-test-app',
|
||||
appDisplayName: 'My Test App',
|
||||
@@ -155,7 +153,8 @@ describe('copyBaseApplicationProject', () => {
|
||||
const roleConfigPath = join(
|
||||
testAppDirectory,
|
||||
'src',
|
||||
'default-function.role.ts',
|
||||
'roles',
|
||||
DEFAULT_ROLE_FILE_NAME,
|
||||
);
|
||||
const roleConfigContent = await fs.readFile(roleConfigPath, 'utf8');
|
||||
|
||||
@@ -167,7 +166,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
|
||||
// Verify it exports the universal identifier constant
|
||||
expect(roleConfigContent).toContain(
|
||||
'export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER',
|
||||
'export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER',
|
||||
);
|
||||
|
||||
// Verify role label includes app name
|
||||
@@ -183,7 +182,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
|
||||
// Verify it has a universalIdentifier (UUID format)
|
||||
expect(roleConfigContent).toMatch(
|
||||
/universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER/,
|
||||
/universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -211,11 +210,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
appDirectory: testAppDirectory,
|
||||
});
|
||||
|
||||
const appConfigPath = join(
|
||||
testAppDirectory,
|
||||
'src',
|
||||
'application.config.ts',
|
||||
);
|
||||
const appConfigPath = join(testAppDirectory, 'src', APPLICATION_FILE_NAME);
|
||||
const appConfigContent = await fs.readFile(appConfigPath, 'utf8');
|
||||
|
||||
expect(appConfigContent).toContain("description: ''");
|
||||
@@ -244,11 +239,11 @@ describe('copyBaseApplicationProject', () => {
|
||||
|
||||
// Read both app configs
|
||||
const firstAppConfig = await fs.readFile(
|
||||
join(firstAppDir, 'src', 'application.config.ts'),
|
||||
join(firstAppDir, 'src', APPLICATION_FILE_NAME),
|
||||
'utf8',
|
||||
);
|
||||
const secondAppConfig = await fs.readFile(
|
||||
join(secondAppDir, 'src', 'application.config.ts'),
|
||||
join(secondAppDir, 'src', APPLICATION_FILE_NAME),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
@@ -284,19 +279,19 @@ describe('copyBaseApplicationProject', () => {
|
||||
appDirectory: secondAppDir,
|
||||
});
|
||||
|
||||
// Read both role configs
|
||||
const firstRoleConfig = await fs.readFile(
|
||||
join(firstAppDir, 'src', 'default-function.role.ts'),
|
||||
join(firstAppDir, 'src', 'roles', DEFAULT_ROLE_FILE_NAME),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const secondRoleConfig = await fs.readFile(
|
||||
join(secondAppDir, 'src', 'default-function.role.ts'),
|
||||
join(secondAppDir, 'src', 'roles', DEFAULT_ROLE_FILE_NAME),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
// Extract UUIDs using regex
|
||||
const uuidRegex =
|
||||
/DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =\s*'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'/;
|
||||
/DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =\s*'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'/;
|
||||
const firstUuid = firstRoleConfig.match(uuidRegex)?.[1];
|
||||
const secondUuid = secondRoleConfig.match(uuidRegex)?.[1];
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { v4 } from 'uuid';
|
||||
import { ASSETS_DIR } from 'twenty-shared/application';
|
||||
|
||||
const APP_FOLDER = 'src';
|
||||
const SRC_FOLDER = 'src';
|
||||
|
||||
export const copyBaseApplicationProject = async ({
|
||||
appName,
|
||||
@@ -21,32 +22,45 @@ export const copyBaseApplicationProject = async ({
|
||||
|
||||
await createGitignore(appDirectory);
|
||||
|
||||
await createPublicAssetDirectory(appDirectory);
|
||||
|
||||
await createYarnLock(appDirectory);
|
||||
|
||||
const appFolderPath = join(appDirectory, APP_FOLDER);
|
||||
const sourceFolderPath = join(appDirectory, SRC_FOLDER);
|
||||
|
||||
await fs.ensureDir(appFolderPath);
|
||||
await fs.ensureDir(sourceFolderPath);
|
||||
|
||||
await createDefaultServerlessFunctionRoleConfig({
|
||||
await createDefaultRoleConfig({
|
||||
displayName: appDisplayName,
|
||||
appDirectory: appFolderPath,
|
||||
appDirectory: sourceFolderPath,
|
||||
fileFolder: 'roles',
|
||||
fileName: 'default-role.ts',
|
||||
});
|
||||
|
||||
await createDefaultFrontComponent({
|
||||
appDirectory: appFolderPath,
|
||||
appDirectory: sourceFolderPath,
|
||||
fileFolder: 'front-components',
|
||||
fileName: 'hello-world.tsx',
|
||||
});
|
||||
|
||||
await createDefaultFunction({
|
||||
appDirectory: appFolderPath,
|
||||
appDirectory: sourceFolderPath,
|
||||
fileFolder: 'logic-functions',
|
||||
fileName: 'hello-world.ts',
|
||||
});
|
||||
|
||||
await createApplicationConfig({
|
||||
displayName: appDisplayName,
|
||||
description: appDescription,
|
||||
appDirectory: appFolderPath,
|
||||
appDirectory: sourceFolderPath,
|
||||
fileName: 'application-config.ts',
|
||||
});
|
||||
};
|
||||
|
||||
const createPublicAssetDirectory = async (appDirectory: string) => {
|
||||
await fs.ensureDir(join(appDirectory, ASSETS_DIR));
|
||||
};
|
||||
|
||||
const createYarnLock = async (appDirectory: string) => {
|
||||
const yarnLockContent = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
@@ -71,7 +85,9 @@ generated
|
||||
|
||||
# dev
|
||||
/dist/
|
||||
.twenty
|
||||
|
||||
.twenty/*
|
||||
!.twenty/output/
|
||||
|
||||
# production
|
||||
/build
|
||||
@@ -96,22 +112,26 @@ yarn-error.log*
|
||||
await fs.writeFile(join(appDirectory, '.gitignore'), gitignoreContent);
|
||||
};
|
||||
|
||||
const createDefaultServerlessFunctionRoleConfig = async ({
|
||||
const createDefaultRoleConfig = async ({
|
||||
displayName,
|
||||
appDirectory,
|
||||
fileFolder,
|
||||
fileName,
|
||||
}: {
|
||||
displayName: string;
|
||||
appDirectory: string;
|
||||
fileFolder?: string;
|
||||
fileName: string;
|
||||
}) => {
|
||||
const universalIdentifier = v4();
|
||||
|
||||
const content = `import { defineRole } from 'twenty-sdk';
|
||||
|
||||
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'${universalIdentifier}';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: '${displayName} default function role',
|
||||
description: '${displayName} default function role',
|
||||
canReadAllObjectRecords: true,
|
||||
@@ -121,13 +141,18 @@ export default defineRole({
|
||||
});
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, 'default-function.role.ts'), content);
|
||||
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createDefaultFrontComponent = async ({
|
||||
appDirectory,
|
||||
fileFolder,
|
||||
fileName,
|
||||
}: {
|
||||
appDirectory: string;
|
||||
fileFolder?: string;
|
||||
fileName: string;
|
||||
}) => {
|
||||
const universalIdentifier = v4();
|
||||
|
||||
@@ -150,68 +175,72 @@ export default defineFrontComponent({
|
||||
});
|
||||
`;
|
||||
|
||||
await fs.writeFile(
|
||||
join(appDirectory, 'hello-world.front-component.tsx'),
|
||||
content,
|
||||
);
|
||||
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createDefaultFunction = async ({
|
||||
appDirectory,
|
||||
fileFolder,
|
||||
fileName,
|
||||
}: {
|
||||
appDirectory: string;
|
||||
fileFolder?: string;
|
||||
fileName: string;
|
||||
}) => {
|
||||
const universalIdentifier = v4();
|
||||
const triggerUniversalIdentifier = v4();
|
||||
|
||||
const content = `import { defineFunction } from 'twenty-sdk';
|
||||
const content = `import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (): Promise<{ message: string }> => {
|
||||
return { message: 'Hello, World!' };
|
||||
};
|
||||
|
||||
export default defineFunction({
|
||||
// Logic function handler - rename and implement your logic
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: '${universalIdentifier}',
|
||||
name: 'hello-world-function',
|
||||
description: 'A sample serverless function',
|
||||
name: 'hello-world-logic-function',
|
||||
description: 'A simple logic function',
|
||||
timeoutSeconds: 5,
|
||||
handler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: '${triggerUniversalIdentifier}',
|
||||
type: 'route',
|
||||
path: '/hello-world-function',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: false,
|
||||
},
|
||||
],
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/hello-world-logic-function',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: false,
|
||||
},
|
||||
});
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, 'hello-world.function.ts'), content);
|
||||
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createApplicationConfig = async ({
|
||||
displayName,
|
||||
description,
|
||||
appDirectory,
|
||||
fileFolder,
|
||||
fileName,
|
||||
}: {
|
||||
displayName: string;
|
||||
description?: string;
|
||||
appDirectory: string;
|
||||
fileFolder?: string;
|
||||
fileName: string;
|
||||
}) => {
|
||||
const content = `import { defineApp } from 'twenty-sdk';
|
||||
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from 'src/default-function.role';
|
||||
const content = `import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
|
||||
export default defineApp({
|
||||
export default defineApplication({
|
||||
universalIdentifier: '${v4()}',
|
||||
displayName: '${displayName}',
|
||||
description: '${description ?? ''}',
|
||||
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, 'application.config.ts'), content);
|
||||
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createPackageJson = async ({
|
||||
@@ -238,8 +267,6 @@ const createPackageJson = async ({
|
||||
'auth:switch': 'twenty auth:switch',
|
||||
'auth:list': 'twenty auth:list',
|
||||
'app:dev': 'twenty app:dev',
|
||||
'app:build': 'twenty app:build',
|
||||
'app:sync': 'twenty app:sync',
|
||||
'entity:add': 'twenty entity:add',
|
||||
'app:generate': 'twenty app:generate',
|
||||
'function:logs': 'twenty function:logs',
|
||||
@@ -250,13 +277,13 @@ const createPackageJson = async ({
|
||||
'lint:fix': 'eslint --fix',
|
||||
},
|
||||
dependencies: {
|
||||
'twenty-sdk': '0.3.1',
|
||||
'twenty-sdk': '0.5.0',
|
||||
},
|
||||
devDependencies: {
|
||||
typescript: '^5.9.3',
|
||||
'@types/node': '^24.7.2',
|
||||
'@types/react': '^19.0.2',
|
||||
react: '^19.0.2',
|
||||
'@types/react': '^18.2.0',
|
||||
react: '^18.2.0',
|
||||
eslint: '^9.32.0',
|
||||
'typescript-eslint': '^8.50.0',
|
||||
},
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"types": ["jest", "node"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
|
||||
@@ -13,7 +13,7 @@ const config: ApplicationConfig = {
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
functionRoleUniversalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061',
|
||||
defaultRoleUniversalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061',
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -95,7 +95,7 @@ export class PostCard {
|
||||
label: 'Notes',
|
||||
icon: 'IconComment',
|
||||
inverseSideTargetUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note.universalIdentifier,
|
||||
onDelete: OnDeleteAction.CASCADE,
|
||||
})
|
||||
notes: Note[];
|
||||
|
||||
@@ -14,7 +14,7 @@ export const functionRole: RoleConfig = {
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
@@ -23,8 +23,8 @@ export const functionRole: RoleConfig = {
|
||||
],
|
||||
fieldPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
fieldName: 'content',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
|
||||
canReadFieldValue: false,
|
||||
canUpdateFieldValue: false,
|
||||
},
|
||||
|
||||
@@ -8,8 +8,25 @@
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"scripts": {
|
||||
"auth:login": "twenty auth login",
|
||||
"auth:logout": "twenty auth logout",
|
||||
"auth:status": "twenty auth status",
|
||||
"auth:switch": "twenty auth switch",
|
||||
"auth:list": "twenty auth list",
|
||||
"app:dev": "twenty app dev",
|
||||
"app:sync": "twenty app sync",
|
||||
"entity:add": "twenty entity add",
|
||||
"app:generate": "twenty app generate",
|
||||
"function:logs": "twenty function logs",
|
||||
"function:execute": "twenty function execute",
|
||||
"app:uninstall": "twenty app uninstall",
|
||||
"help": "twenty help",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "0.0.4"
|
||||
"twenty-sdk": "0.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2"
|
||||
|
||||
+3
-5
@@ -1,6 +1,6 @@
|
||||
import { type ApplicationConfig } from 'twenty-sdk/application';
|
||||
import { defineApp } from 'twenty-sdk';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
export default defineApp({
|
||||
universalIdentifier: '94f7db30-59e5-4b09-a5fe-64cd3d4a65b0',
|
||||
displayName: 'Self Hosting',
|
||||
description: 'Used to manage billing and telemetry of self-hosted instances',
|
||||
@@ -16,6 +16,4 @@ const config: ApplicationConfig = {
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { FieldType, defineObject } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '06f3fb53-599e-4c6b-9df6-8f731973afd7',
|
||||
nameSingular: 'selfHostingUser',
|
||||
namePlural: 'selfHostingUsers',
|
||||
labelSingular: 'Self Hosting User',
|
||||
labelPlural: 'Self Hosting Users',
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.EMAILS,
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
description: 'The email of the self hosting user',
|
||||
universalIdentifier: 'a4b7892c-431a-4d44-973e-a5481652704f',
|
||||
},
|
||||
],
|
||||
});
|
||||
+26
-8
@@ -1,5 +1,20 @@
|
||||
import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
|
||||
import { createClient } from '../generated';
|
||||
import { defineFunction } from 'twenty-sdk';
|
||||
import { createClient } from '../../generated';
|
||||
|
||||
// TODO: import from twenty-sdk when 0.4.0 is deployed
|
||||
type ServerlessFunctionEvent<TBody = object> = {
|
||||
headers: Record<string, string | undefined>;
|
||||
queryStringParameters: Record<string, string | undefined>;
|
||||
pathParameters: Record<string, string | undefined>;
|
||||
body: TBody | null;
|
||||
isBase64Encoded: boolean;
|
||||
requestContext: {
|
||||
http: {
|
||||
method: string;
|
||||
path: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type TelemetryEventPayload = {
|
||||
action: string;
|
||||
@@ -22,10 +37,10 @@ type TelemetryEventPayload = {
|
||||
};
|
||||
|
||||
export const main = async (
|
||||
params: TelemetryEventPayload,
|
||||
params: ServerlessFunctionEvent<TelemetryEventPayload>,
|
||||
): Promise<{ success: boolean; message: string; error?: string }> => {
|
||||
try {
|
||||
const { action, payload } = params;
|
||||
const { action, payload } = params.body || {};
|
||||
|
||||
if (action !== 'user_signup') {
|
||||
return {
|
||||
@@ -69,7 +84,10 @@ export const main = async (
|
||||
createSelfHostingUser: {
|
||||
__args: {
|
||||
data: {
|
||||
name: payload?.payload?.events?.[0]?.userFirstName + ' ' + payload?.payload?.events?.[0]?.userLastName,
|
||||
name:
|
||||
payload?.payload?.events?.[0]?.userFirstName +
|
||||
' ' +
|
||||
payload?.payload?.events?.[0]?.userLastName,
|
||||
email: {
|
||||
primaryEmail: userEmail,
|
||||
additionalEmails: null,
|
||||
@@ -97,10 +115,11 @@ export const main = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const config: ServerlessFunctionConfig = {
|
||||
export default defineFunction({
|
||||
universalIdentifier: '10104201-622b-4a5e-9f27-8f2af19b2a3c',
|
||||
name: 'telemetry-webhook',
|
||||
timeoutSeconds: 5,
|
||||
handler: main,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: '7c8e3f5a-9b4c-4d1e-8f2a-1b3c4d5e6f7a',
|
||||
@@ -110,5 +129,4 @@ export const config: ServerlessFunctionConfig = {
|
||||
isAuthRequired: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { FieldMetadata, FieldMetadataType, ObjectMetadata } from 'twenty-sdk/application';
|
||||
|
||||
@ObjectMetadata({
|
||||
universalIdentifier: '06f3fb53-599e-4c6b-9df6-8f731973afd7',
|
||||
nameSingular: 'selfHostingUser',
|
||||
namePlural: 'selfHostingUsers',
|
||||
labelSingular: 'Self Hosting User',
|
||||
labelPlural: 'Self Hosting Users',
|
||||
})
|
||||
export class SelfHostingUser {
|
||||
@FieldMetadata({
|
||||
type: FieldMetadataType.EMAILS,
|
||||
label: 'Email',
|
||||
description: 'The email of the self hosting user',
|
||||
universalIdentifier: '06f3fb53-599e-4c6b-9df6-8f731973afd7',
|
||||
})
|
||||
email: object;
|
||||
}
|
||||
@@ -20,7 +20,11 @@
|
||||
"lib": ["es2020", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"src/*": ["./src/*"],
|
||||
"~/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,25 +42,50 @@
|
||||
{{- regexFind "\\|(.+)$" . | trimPrefix "|" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Compose DB connection URL */}}
|
||||
{{- define "twenty.dbUrl" -}}
|
||||
{{- if .Values.server.env.PG_DATABASE_URL -}}
|
||||
{{- .Values.server.env.PG_DATABASE_URL -}}
|
||||
{{- else if .Values.db.enabled -}}
|
||||
{{- $host := printf "%s-db" (include "twenty.fullname" .) -}}
|
||||
{{- $user := .Values.db.internal.appUser | default "twenty_app_user" -}}
|
||||
{{- $pass := .Values.db.internal.appPassword | default (randAlphaNum 32) -}}
|
||||
{{- $db := .Values.db.internal.database | default "twenty" -}}
|
||||
{{- printf "postgres://%s:%s@%s.%s.svc.cluster.local/%s" $user $pass $host (include "twenty.namespace" .) $db -}}
|
||||
{{/* Check if using external secret for database password */}}
|
||||
{{- define "twenty.db.useExternalSecret" -}}
|
||||
{{- if and (not .Values.db.enabled) .Values.db.external.secretName .Values.db.external.passwordKey -}}
|
||||
true
|
||||
{{- else -}}
|
||||
false
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Database URL secret name */}}
|
||||
{{- define "twenty.dbUrl.secretName" -}}
|
||||
{{- printf "%s-db-url" (include "twenty.fullname" .) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Database password secret name */}}
|
||||
{{- define "twenty.dbPassword.secretName" -}}
|
||||
{{- if eq (include "twenty.db.useExternalSecret" .) "true" -}}
|
||||
{{- .Values.db.external.secretName -}}
|
||||
{{- else -}}
|
||||
{{- include "twenty.dbUrl.secretName" . -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Database password secret key */}}
|
||||
{{- define "twenty.dbPassword.secretKey" -}}
|
||||
{{- if eq (include "twenty.db.useExternalSecret" .) "true" -}}
|
||||
{{- .Values.db.external.passwordKey -}}
|
||||
{{- else if .Values.db.enabled -}}
|
||||
appPassword
|
||||
{{- else -}}
|
||||
password
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Database URL template for external secret (will be evaluated at runtime) */}}
|
||||
{{- define "twenty.dbUrl.template" -}}
|
||||
{{- if eq (include "twenty.db.useExternalSecret" .) "true" -}}
|
||||
{{- $scheme := "postgres" -}}
|
||||
{{- $host := .Values.db.external.host -}}
|
||||
{{- $port := .Values.db.external.port | default 5432 -}}
|
||||
{{- $user := .Values.db.external.user | default "postgres" -}}
|
||||
{{- $pass := .Values.db.external.password | default "postgres" -}}
|
||||
{{- $db := .Values.db.external.database | default "twenty" -}}
|
||||
{{- $qs := ternary "?sslmode=require" "" (eq .Values.db.external.ssl true) -}}
|
||||
{{- printf "%s://%s:%s@%s:%v/%s%s" $scheme $user $pass $host $port $db $qs -}}
|
||||
{{- printf "%s://%s:$(DB_PASSWORD)@%s:%v/%s%s" $scheme $user $host $port $db $qs -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -78,9 +103,11 @@
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Compose Server URL from ingress, else service */}}
|
||||
{{/* Compose Server URL from override, ingress, or service */}}
|
||||
{{- define "twenty.serverUrl" -}}
|
||||
{{- if and .Values.server.ingress.enabled (gt (len .Values.server.ingress.hosts) 0) -}}
|
||||
{{- if .Values.server.env.SERVER_URL -}}
|
||||
{{- .Values.server.env.SERVER_URL -}}
|
||||
{{- else if and .Values.server.ingress.enabled (gt (len .Values.server.ingress.hosts) 0) -}}
|
||||
{{- $host := (index .Values.server.ingress.hosts 0).host -}}
|
||||
{{- $tls := gt (len .Values.server.ingress.tls) 0 -}}
|
||||
{{- $scheme := ternary "https" "http" $tls -}}
|
||||
|
||||
@@ -116,11 +116,21 @@ spec:
|
||||
- >-
|
||||
npx -y typeorm migration:run -d dist/database/typeorm/core/core.datasource
|
||||
env:
|
||||
{{- if eq (include "twenty.db.useExternalSecret" .) "true" }}
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "twenty.dbPassword.secretName" . }}
|
||||
key: {{ include "twenty.dbPassword.secretKey" . }}
|
||||
- name: PG_DATABASE_URL
|
||||
value: {{ include "twenty.dbUrl.template" . | quote }}
|
||||
{{- else }}
|
||||
- name: PG_DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "twenty.fullname" . }}-db-url
|
||||
name: {{ include "twenty.dbUrl.secretName" . }}
|
||||
key: url
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: server
|
||||
{{- $img := include "twenty.server.image" . }}
|
||||
@@ -129,11 +139,21 @@ spec:
|
||||
env:
|
||||
- name: SERVER_URL
|
||||
value: {{ include "twenty.serverUrl" . | quote }}
|
||||
{{- if eq (include "twenty.db.useExternalSecret" .) "true" }}
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "twenty.dbPassword.secretName" . }}
|
||||
key: {{ include "twenty.dbPassword.secretKey" . }}
|
||||
- name: PG_DATABASE_URL
|
||||
value: {{ include "twenty.dbUrl.template" . | quote }}
|
||||
{{- else }}
|
||||
- name: PG_DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "twenty.fullname" . }}-db-url
|
||||
name: {{ include "twenty.dbUrl.secretName" . }}
|
||||
key: url
|
||||
{{- end }}
|
||||
- name: REDIS_URL
|
||||
value: {{ include "twenty.redisUrl" . | quote }}
|
||||
- name: SIGN_IN_PREFILLED
|
||||
|
||||
@@ -52,11 +52,21 @@ spec:
|
||||
env:
|
||||
- name: SERVER_URL
|
||||
value: {{ include "twenty.serverUrl" . | quote }}
|
||||
{{- if eq (include "twenty.db.useExternalSecret" .) "true" }}
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "twenty.dbPassword.secretName" . }}
|
||||
key: {{ include "twenty.dbPassword.secretKey" . }}
|
||||
- name: PG_DATABASE_URL
|
||||
value: {{ include "twenty.dbUrl.template" . | quote }}
|
||||
{{- else }}
|
||||
- name: PG_DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "twenty.fullname" . }}-db-url
|
||||
name: {{ include "twenty.dbUrl.secretName" . }}
|
||||
key: url
|
||||
{{- end }}
|
||||
- name: REDIS_URL
|
||||
value: {{ include "twenty.redisUrl" . | quote }}
|
||||
- name: STORAGE_TYPE
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{{- $secretName := printf "%s-db-url" (include "twenty.fullname" .) -}}
|
||||
{{- if .Values.db.enabled -}}
|
||||
{{- $existingSecret := lookup "v1" "Secret" (include "twenty.namespace" .) $secretName -}}
|
||||
{{- $appPassword := "" -}}
|
||||
{{- if $existingSecret -}}
|
||||
@@ -20,3 +21,25 @@ type: Opaque
|
||||
stringData:
|
||||
url: {{ printf "postgres://%s:%s@%s-db.%s.svc.cluster.local/%s" (urlquery $appUser) (urlquery $appPassword) (include "twenty.fullname" .) (include "twenty.namespace" .) (.Values.db.internal.database | default "twenty") | quote }}
|
||||
appPassword: {{ $appPassword | quote }}
|
||||
{{- else if not .Values.db.external.secretName -}}
|
||||
{{- $scheme := "postgres" -}}
|
||||
{{- $host := .Values.db.external.host -}}
|
||||
{{- $port := .Values.db.external.port | default 5432 -}}
|
||||
{{- $user := .Values.db.external.user | default "postgres" -}}
|
||||
{{- $pass := .Values.db.external.password | default "" -}}
|
||||
{{- $db := .Values.db.external.database | default "twenty" -}}
|
||||
{{- $qs := ternary "?sslmode=require" "" (eq .Values.db.external.ssl true) -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $secretName }}
|
||||
namespace: {{ include "twenty.namespace" . }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "twenty.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/component: server
|
||||
type: Opaque
|
||||
stringData:
|
||||
url: {{ printf "%s://%s:%s@%s:%v/%s%s" $scheme (urlquery $user) (urlquery $pass) $host $port $db $qs | quote }}
|
||||
password: {{ $pass | quote }}
|
||||
{{- end -}}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"env": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"SERVER_URL": { "type": "string", "description": "Override for the server URL (e.g., https://crm.example.com). If not set, derived from ingress or service." },
|
||||
"NODE_PORT": { "type": "integer", "minimum": 1 },
|
||||
"PG_DATABASE_URL": { "type": "string" },
|
||||
"REDIS_URL": { "type": "string" },
|
||||
|
||||
@@ -19,8 +19,13 @@ storage:
|
||||
bucket: ""
|
||||
region: ""
|
||||
endpoint: ""
|
||||
# Option A: direct values
|
||||
accessKeyId: ""
|
||||
secretAccessKey: ""
|
||||
# Option B: reference a Secret
|
||||
# secretName: my-s3-creds
|
||||
# accessKeyIdKey: accessKeyId
|
||||
# secretAccessKeyKey: secretAccessKey
|
||||
|
||||
# Auth tokens (random if not provided)
|
||||
secrets:
|
||||
@@ -137,6 +142,8 @@ db:
|
||||
password: ""
|
||||
database: twenty
|
||||
ssl: false
|
||||
secretName: ""
|
||||
passwordKey: ""
|
||||
|
||||
# Redis
|
||||
redisInternal:
|
||||
|
||||
@@ -14,6 +14,7 @@ COPY ./packages/twenty-server/patches /app/packages/twenty-server/patches
|
||||
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
|
||||
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/
|
||||
COPY ./packages/twenty-front/package.json /app/packages/twenty-front/
|
||||
COPY ./packages/twenty-sdk/package.json /app/packages/twenty-sdk/
|
||||
|
||||
# Install all dependencies
|
||||
RUN yarn && yarn cache clean && npx nx reset
|
||||
@@ -39,6 +40,7 @@ ARG REACT_APP_SERVER_BASE_URL
|
||||
COPY ./packages/twenty-front /app/packages/twenty-front
|
||||
COPY ./packages/twenty-ui /app/packages/twenty-ui
|
||||
COPY ./packages/twenty-shared /app/packages/twenty-shared
|
||||
COPY ./packages/twenty-sdk /app/packages/twenty-sdk
|
||||
RUN npx nx build twenty-front
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@ setup_and_migrate_db() {
|
||||
yarn database:migrate:prod
|
||||
fi
|
||||
|
||||
yarn command:prod cache:flush
|
||||
yarn command:prod upgrade
|
||||
yarn command:prod cache:flush
|
||||
|
||||
echo "Successfully migrated DB!"
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,12 @@ You should run all commands in the following steps from the root of the project.
|
||||
CREATE ROLE postgres WITH SUPERUSER LOGIN;
|
||||
```
|
||||
This creates a superuser role named `postgres` with login access.
|
||||
```bash
|
||||
Role name | Attributes | Member of
|
||||
-----------+-------------+-----------
|
||||
postgres | Superuser | {}
|
||||
john | Superuser | {}
|
||||
```
|
||||
|
||||
**Option 2:** If you have docker installed:
|
||||
```bash
|
||||
|
||||
@@ -9,16 +9,13 @@ Apps are currently in alpha testing. The feature is functional but still evolvin
|
||||
|
||||
## What Are Apps?
|
||||
|
||||
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and serverless functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
|
||||
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and logic functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
|
||||
|
||||
**What you can do today:**
|
||||
- Define custom objects and fields as code (managed data model)
|
||||
- Build serverless functions with custom triggers
|
||||
- Build logic functions with custom triggers
|
||||
- Deploy the same app across multiple workspaces
|
||||
|
||||
**Coming soon:**
|
||||
- Custom UI layouts and components
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 24+ and Yarn 4
|
||||
@@ -48,15 +45,12 @@ From here you can:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn app:create-entity
|
||||
yarn entity:add
|
||||
|
||||
# Generate a typed Twenty client and workspace entity types
|
||||
yarn app:generate
|
||||
|
||||
# Run a one‑time sync (instead of watch mode)
|
||||
yarn app:sync
|
||||
|
||||
# Watch your application's functions logs
|
||||
# Watch your application's function logs
|
||||
yarn function:logs
|
||||
|
||||
# Execute a function by name
|
||||
@@ -66,7 +60,7 @@ yarn function:execute -n my-function -p '{"name": "test"}'
|
||||
yarn app:uninstall
|
||||
|
||||
# Display commands' help
|
||||
yarn app:help
|
||||
yarn help
|
||||
```
|
||||
|
||||
See also: the CLI reference pages for [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) and [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
@@ -94,79 +88,60 @@ my-twenty-app/
|
||||
eslint.config.mjs
|
||||
tsconfig.json
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
src/
|
||||
app/
|
||||
application.config.ts # Required - main application configuration
|
||||
default-function.role.ts # Default role for serverless functions
|
||||
// your entities (*.object.ts, *.function.ts, *.role.ts)
|
||||
utils/ # Optional - handler implementations & utilities
|
||||
```
|
||||
|
||||
### Convention-over-configuration
|
||||
|
||||
Applications use a **convention-over-configuration** approach where entities are detected by their file suffix. This allows flexible organization within the `src/app/` folder:
|
||||
|
||||
| File suffix | Entity type |
|
||||
|-------------|-------------|
|
||||
| `*.object.ts` | Custom object definitions |
|
||||
| `*.function.ts` | Serverless function definitions |
|
||||
| `*.role.ts` | Role definitions |
|
||||
|
||||
### Supported folder organizations
|
||||
|
||||
You can organize your entities in any of these patterns:
|
||||
|
||||
**Traditional (by type):**
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
├── objects/
|
||||
│ └── postCard.object.ts
|
||||
├── functions/
|
||||
│ └── createPostCard.function.ts
|
||||
└── roles/
|
||||
└── admin.role.ts
|
||||
```
|
||||
|
||||
**Feature-based:**
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
└── post-card/
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
└── postCardAdmin.role.ts
|
||||
```
|
||||
|
||||
**Flat:**
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
└── admin.role.ts
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── logic-functions/
|
||||
│ └── hello-world.ts # Example logic function
|
||||
└── front-components/
|
||||
└── hello-world.tsx # Example front component
|
||||
```
|
||||
|
||||
At a high level:
|
||||
|
||||
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall`, and `auth` that delegate to the local `twenty` CLI.
|
||||
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall`, and authentication commands that delegate to the local `twenty` CLI.
|
||||
- **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
|
||||
- **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
|
||||
- **.nvmrc**: Pins the Node.js version expected by the project.
|
||||
- **eslint.config.mjs** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
|
||||
- **README.md**: A short README in the app root with basic instructions.
|
||||
- **src/app/**: The main place where you define your application-as-code:
|
||||
- `application.config.ts`: Global configuration for your app (metadata and runtime wiring). See "Application config" below.
|
||||
- `*.role.ts`: Role definitions used by your serverless functions. See "Default function role" below.
|
||||
- `*.object.ts`: Custom object definitions.
|
||||
- `*.function.ts`: Serverless function definitions.
|
||||
- **src/utils/**: Optional folder for handler implementations and utilities.
|
||||
- **public/**: A folder for storing public assets (images, fonts, static files) that will be served with your application. Files placed here are uploaded during sync and accessible at runtime.
|
||||
- **src/**: The main place where you define your application-as-code
|
||||
|
||||
### Entity detection
|
||||
|
||||
The SDK detects entities by parsing your TypeScript files for **`export default define<Entity>({...})`** calls. Each entity type has a corresponding helper function exported from `twenty-sdk`:
|
||||
|
||||
| Helper function | Entity type |
|
||||
|-----------------|-------------|
|
||||
| `defineObject()` | Custom object definitions |
|
||||
| `defineLogicFunction()` | Logic function definitions |
|
||||
| `defineFrontComponent()` | Front component definitions |
|
||||
| `defineRole()` | Role definitions |
|
||||
| `defineField()` | Field extensions for existing objects |
|
||||
|
||||
<Note>
|
||||
**File naming is flexible.** Entity detection is AST-based — the SDK scans your source files for the `export default define<Entity>({...})` pattern. You can organize your files and folders however you like. Grouping by entity type (e.g., `logic-functions/`, `roles/`) is just a convention for code organization, not a requirement.
|
||||
</Note>
|
||||
|
||||
Example of a detected entity:
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
Later commands will add more files and folders:
|
||||
|
||||
- `yarn app:generate` will create a `generated/` folder (typed Twenty client + workspace types).
|
||||
- `yarn app:create-entity` will add entity definition files under `src/app/` for your custom objects, functions, or roles.
|
||||
l
|
||||
- `yarn entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, or roles.
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -207,16 +182,18 @@ The twenty-sdk provides typed building blocks and helper functions you use insid
|
||||
|
||||
### Helper functions
|
||||
|
||||
The SDK provides four helper functions with built-in validation for defining your app entities:
|
||||
The SDK provides helper functions for defining your app entities. As described in [Entity detection](#entity-detection), you must use `export default define<Entity>({...})` for your entities to be detected:
|
||||
|
||||
| Function | Purpose |
|
||||
|----------|---------|
|
||||
| `defineApp()` | Configure application metadata |
|
||||
| `defineApplication()` | Configure application metadata (required, one per app) |
|
||||
| `defineObject()` | Define custom objects with fields |
|
||||
| `defineFunction()` | Define serverless functions with handlers |
|
||||
| `defineLogicFunction()` | Define logic functions with handlers |
|
||||
| `defineFrontComponent()` | Define front components for custom UI |
|
||||
| `defineRole()` | Configure role permissions and object access |
|
||||
| `defineField()` | Extend existing objects with additional fields |
|
||||
|
||||
These functions validate your configuration at runtime and provide better IDE autocompletion and type safety.
|
||||
These functions validate your configuration at build time and provide IDE autocompletion and type safety.
|
||||
|
||||
### Defining objects
|
||||
|
||||
@@ -297,80 +274,29 @@ Key points:
|
||||
- The `universalIdentifier` must be unique and stable across deployments.
|
||||
- Each field requires a `name`, `type`, `label`, and its own stable `universalIdentifier`.
|
||||
- The `fields` array is optional — you can define objects without custom fields.
|
||||
- You can scaffold new objects using `yarn app:create-entity`, which guides you through naming, fields, and relationships.
|
||||
- You can scaffold new objects using `yarn entity:add`, which guides you through naming, fields, and relationships.
|
||||
|
||||
<Note>
|
||||
**Base fields are created automatically.** When you define a custom object, Twenty automatically adds standard fields such as `name`, `createdAt`, `updatedAt`, `createdBy`, `position`, and `deletedAt`. You don't need to define these in your `fields` array — only add your custom fields.
|
||||
</Note>
|
||||
|
||||
<Accordion title="Alternative: Decorator-based syntax">
|
||||
You can also define objects using TypeScript decorators. This approach uses class-based syntax with `@Object`, `@Field`, and `@Relation` decorators:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
type AddressField,
|
||||
Field,
|
||||
FieldType,
|
||||
type FullNameField,
|
||||
Object,
|
||||
OnDeleteAction,
|
||||
Relation,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
import { type Note } from '../../generated';
|
||||
### Application config (application-config.ts)
|
||||
|
||||
@Object({
|
||||
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
|
||||
nameSingular: 'postCard',
|
||||
namePlural: 'postCards',
|
||||
labelSingular: 'Post card',
|
||||
labelPlural: 'Post cards',
|
||||
description: 'A post card object',
|
||||
icon: 'IconMail',
|
||||
})
|
||||
export class PostCard {
|
||||
@Field({
|
||||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||||
type: FieldType.TEXT,
|
||||
label: 'Content',
|
||||
description: "Postcard's content",
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
content: string;
|
||||
|
||||
@Relation({
|
||||
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
label: 'Notes',
|
||||
icon: 'IconComment',
|
||||
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
|
||||
onDelete: OnDeleteAction.CASCADE,
|
||||
})
|
||||
notes: Note[];
|
||||
}
|
||||
```
|
||||
|
||||
Note: The decorator approach requires `experimentalDecorators` in your TypeScript config.
|
||||
</Accordion>
|
||||
|
||||
|
||||
### Application config (application.config.ts)
|
||||
|
||||
Every app has a single `application.config.ts` file that describes:
|
||||
Every app has a single `application-config.ts` file that describes:
|
||||
|
||||
- **Who the app is**: identifiers, display name, and description.
|
||||
- **How its functions run**: which role they use for permissions.
|
||||
- **(Optional) variables**: key–value pairs exposed to your functions as environment variables.
|
||||
|
||||
Use `defineApp()` to define your application configuration:
|
||||
Use `defineApplication()` to define your application configuration:
|
||||
|
||||
```typescript
|
||||
// src/app/application.config.ts
|
||||
import { defineApp } from 'twenty-sdk';
|
||||
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
|
||||
export default defineApp({
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
@@ -383,18 +309,18 @@ export default defineApp({
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `universalIdentifier` fields are deterministic IDs you own; generate them once and keep them stable across syncs.
|
||||
- `applicationVariables` become environment variables for your functions (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
- `functionRoleUniversalIdentifier` must match the role you define in your `*.role.ts` file (see below).
|
||||
- `defaultRoleUniversalIdentifier` must match the role file (see below).
|
||||
|
||||
#### Roles and permissions
|
||||
|
||||
Applications can define roles that encapsulate permissions on your workspace's objects and actions. The field `functionRoleUniversalIdentifier` in `application.config.ts` designates the default role used by your app's serverless functions.
|
||||
Applications can define roles that encapsulate permissions on your workspace's objects and actions. The field `defaultRoleUniversalIdentifier` in `application-config.ts` designates the default role used by your app's logic functions.
|
||||
|
||||
- The runtime API key injected as `TWENTY_API_KEY` is derived from this default function role.
|
||||
- The typed client will be restricted to the permissions granted to that role.
|
||||
@@ -405,14 +331,14 @@ Applications can define roles that encapsulate permissions on your workspace's o
|
||||
When you scaffold a new app, the CLI also creates a default role file. Use `defineRole()` to define roles with built-in validation:
|
||||
|
||||
```typescript
|
||||
// src/app/default-function.role.ts
|
||||
// src/roles/default-role.ts
|
||||
import { defineRole, PermissionFlag } from 'twenty-sdk';
|
||||
|
||||
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Default function role',
|
||||
description: 'Default role for function Twenty client',
|
||||
canReadAllObjectRecords: false,
|
||||
@@ -425,7 +351,7 @@ export default defineRole({
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
@@ -434,8 +360,8 @@ export default defineRole({
|
||||
],
|
||||
fieldPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
fieldName: 'content',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
|
||||
canReadFieldValue: false,
|
||||
canUpdateFieldValue: false,
|
||||
},
|
||||
@@ -444,10 +370,10 @@ export default defineRole({
|
||||
});
|
||||
```
|
||||
|
||||
The `universalIdentifier` of this role is then referenced in `application.config.ts` as `functionRoleUniversalIdentifier`. In other words:
|
||||
The `universalIdentifier` of this role is then referenced in `application-config.ts` as `defaultRoleUniversalIdentifier`. In other words:
|
||||
|
||||
- **\*.role.ts** defines what the default function role can do.
|
||||
- **application.config.ts** points to that role so your functions inherit its permissions.
|
||||
- **application-config.ts** points to that role so your functions inherit its permissions.
|
||||
|
||||
Notes:
|
||||
- Start from the scaffolded role, then progressively restrict it following least‑privilege.
|
||||
@@ -455,22 +381,17 @@ Notes:
|
||||
- `permissionFlags` control access to platform-level capabilities. Keep them minimal; add only what you need.
|
||||
- See a working example in the Hello World app: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
|
||||
### Serverless function config and entrypoint
|
||||
### Logic function config and entrypoint
|
||||
|
||||
Each function file uses `defineFunction()` to export a configuration with a handler and optional triggers. Use the `*.function.ts` file suffix for automatic detection.
|
||||
Each function file uses `defineLogicFunction()` to export a configuration with a handler and optional triggers.
|
||||
|
||||
```typescript
|
||||
// src/app/createPostCard.function.ts
|
||||
import { defineFunction } from 'twenty-sdk';
|
||||
// src/app/createPostCard.logic-function.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
|
||||
import Twenty, { type Person } from '../../generated';
|
||||
import Twenty, { type Person } from '~/generated';
|
||||
|
||||
const handler = async (
|
||||
params:
|
||||
| RoutePayload
|
||||
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
|
||||
| CronPayload,
|
||||
) => {
|
||||
const handler = async (params: RoutePayload) => {
|
||||
const client = new Twenty(); // generated typed client
|
||||
const name = 'name' in params.queryStringParameters
|
||||
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
@@ -486,7 +407,7 @@ const handler = async (
|
||||
return result;
|
||||
};
|
||||
|
||||
export default defineFunction({
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'create-new-post-card',
|
||||
timeoutSeconds: 2,
|
||||
@@ -501,18 +422,18 @@ export default defineFunction({
|
||||
isAuthRequired: false,
|
||||
},
|
||||
// Cron trigger (CRON pattern)
|
||||
{
|
||||
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
type: 'cron',
|
||||
pattern: '0 0 1 1 *',
|
||||
},
|
||||
// {
|
||||
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
// type: 'cron',
|
||||
// pattern: '0 0 1 1 *',
|
||||
// },
|
||||
// Database event trigger
|
||||
{
|
||||
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
type: 'databaseEvent',
|
||||
eventName: 'person.updated',
|
||||
updatedFields: ['name'],
|
||||
},
|
||||
// {
|
||||
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
// type: 'databaseEvent',
|
||||
// eventName: 'person.updated',
|
||||
// updatedFields: ['name'],
|
||||
// },
|
||||
],
|
||||
});
|
||||
```
|
||||
@@ -552,10 +473,10 @@ const handler = async (event: RoutePayload) => {
|
||||
**To migrate existing functions:** Update your handler to destructure from `event.body`, `event.queryStringParameters`, or `event.pathParameters` instead of directly from the params object.
|
||||
</Warning>
|
||||
|
||||
When a route trigger invokes your function, it receives a `RoutePayload` object that follows the AWS HTTP API v2 format. Import the type from `twenty-sdk`:
|
||||
When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the AWS HTTP API v2 format. Import the type from `twenty-sdk`:
|
||||
|
||||
```typescript
|
||||
import { defineFunction, type RoutePayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
// Access request data
|
||||
@@ -582,10 +503,10 @@ The `RoutePayload` type has the following structure:
|
||||
|
||||
### Forwarding HTTP headers
|
||||
|
||||
By default, HTTP headers from incoming requests are **not** passed to your serverless function for security reasons. To access specific headers, explicitly list them in the `forwardedRequestHeaders` array:
|
||||
By default, HTTP headers from incoming requests are **not** passed to your logic function for security reasons. To access specific headers, explicitly list them in the `forwardedRequestHeaders` array:
|
||||
|
||||
```typescript
|
||||
export default defineFunction({
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'webhook-handler',
|
||||
handler,
|
||||
@@ -620,23 +541,59 @@ const handler = async (event: RoutePayload) => {
|
||||
|
||||
You can create new functions in two ways:
|
||||
|
||||
- **Scaffolded**: Run `yarn app:create-entity` and choose the option to add a new function. This generates a starter file with a handler and config.
|
||||
- **Manual**: Create a new `*.function.ts` file and use `defineFunction()`, following the same pattern.
|
||||
- **Scaffolded**: Run `yarn entity:add` and choose the option to add a new logic function. This generates a starter file with a handler and config.
|
||||
- **Manual**: Create a new `*.logic-function.ts` file and use `defineLogicFunction()`, following the same pattern.
|
||||
|
||||
### Front components
|
||||
|
||||
Front components let you build custom React components that render within Twenty's UI. Use `defineFrontComponent()` to define components with built-in validation:
|
||||
|
||||
```typescript
|
||||
// src/my-widget.front-component.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
const MyWidget = () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>My Custom Widget</h1>
|
||||
<p>This is a custom front component for Twenty.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
name: 'my-widget',
|
||||
description: 'A custom widget component',
|
||||
component: MyWidget,
|
||||
});
|
||||
```
|
||||
|
||||
Key points:
|
||||
- Front components are React components that render in isolated contexts within Twenty.
|
||||
- Use the `*.front-component.tsx` file suffix for automatic detection.
|
||||
- The `component` field references your React component.
|
||||
- Components are built and synced automatically during `yarn app:dev`.
|
||||
|
||||
You can create new front components in two ways:
|
||||
|
||||
- **Scaffolded**: Run `yarn entity:add` and choose the option to add a new front component.
|
||||
- **Manual**: Create a new `*.front-component.tsx` file and use `defineFrontComponent()`.
|
||||
|
||||
### Generated typed client
|
||||
|
||||
Run yarn app:generate to create a local typed client in generated/ based on your workspace schema. Use it in your functions:
|
||||
|
||||
```typescript
|
||||
import Twenty from './generated';
|
||||
import Twenty from '~/generated';
|
||||
|
||||
const client = new Twenty();
|
||||
const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
```
|
||||
|
||||
The client is re-generated by `yarn app:generate`. Re-run after changing your objects and `yarn app:sync` or when onboarding to a new workspace.
|
||||
The client is re-generated by `yarn app:generate`. Re-run after changing your objects or when onboarding to a new workspace.
|
||||
|
||||
#### Runtime credentials in serverless functions
|
||||
#### Runtime credentials in logic functions
|
||||
|
||||
When your function runs on Twenty, the platform injects credentials as environment variables before your code executes:
|
||||
|
||||
@@ -645,13 +602,13 @@ When your function runs on Twenty, the platform injects credentials as environme
|
||||
|
||||
Notes:
|
||||
- You do not need to pass URL or API key to the generated client. It reads `TWENTY_API_URL` and `TWENTY_API_KEY` from process.env at runtime.
|
||||
- The API key's permissions are determined by the role referenced in your `application.config.ts` via `functionRoleUniversalIdentifier`. This is the default role used by serverless functions of your application.
|
||||
- Applications can define roles to follow least‑privilege. Grant only the permissions your functions need, then point `functionRoleUniversalIdentifier` to that role's universal identifier.
|
||||
- The API key's permissions are determined by the role referenced in your `application-config.ts` via `defaultRoleUniversalIdentifier`. This is the default role used by logic functions of your application.
|
||||
- Applications can define roles to follow least‑privilege. Grant only the permissions your functions need, then point `defaultRoleUniversalIdentifier` to that role's universal identifier.
|
||||
|
||||
|
||||
### Hello World example
|
||||
|
||||
Explore a minimal, end-to-end example that demonstrates objects, functions, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
Explore a minimal, end-to-end example that demonstrates objects, logic functions, front components, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
|
||||
## Manual setup (without the scaffolder)
|
||||
|
||||
@@ -666,25 +623,29 @@ Then add scripts like these:
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"auth": "twenty auth login",
|
||||
"generate": "twenty app generate",
|
||||
"dev": "twenty app dev",
|
||||
"sync": "twenty app sync",
|
||||
"uninstall": "twenty app uninstall",
|
||||
"logs": "twenty app logs",
|
||||
"create-entity": "twenty app add",
|
||||
"help": "twenty --help"
|
||||
"auth:login": "twenty auth:login",
|
||||
"auth:logout": "twenty auth:logout",
|
||||
"auth:status": "twenty auth:status",
|
||||
"auth:switch": "twenty auth:switch",
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"app:generate": "twenty app:generate",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
"entity:add": "twenty entity:add",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"help": "twenty help"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now you can run the same commands via Yarn, e.g. `yarn app:dev`, `yarn app:sync`, etc.
|
||||
Now you can run the same commands via Yarn, e.g. `yarn app:dev`, `yarn app:generate`, etc.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Authentication errors: run `yarn auth:login` and ensure your API key has the required permissions.
|
||||
- Cannot connect to server: verify the API URL and that the Twenty server is reachable.
|
||||
- Types or client missing/outdated: run `yarn app:generate` and then `yarn app:dev`.
|
||||
- Types or client missing/outdated: run `yarn app:generate`.
|
||||
- Dev mode not syncing: ensure `yarn app:dev` is running and that changes are not ignored by your environment.
|
||||
|
||||
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
|
||||
@@ -289,19 +289,19 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
|
||||
</Warning>
|
||||
|
||||
## Serverless Functions
|
||||
## Logic Functions
|
||||
|
||||
Twenty supports serverless functions for workflows and custom logic. The execution environment is configured via the `SERVERLESS_TYPE` environment variable.
|
||||
Twenty supports logic functions for workflows and custom logic. The execution environment is configured via the `SERVERLESS_TYPE` environment variable.
|
||||
|
||||
<Warning>
|
||||
**Security Notice:** The local serverless driver (`SERVERLESS_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, we highly recommend using `SERVERLESS_TYPE=LAMBDA` or `SERVERLESS_TYPE=DISABLED`.
|
||||
**Security Notice:** The local driver (`SERVERLESS_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, we highly recommend using `SERVERLESS_TYPE=LAMBDA` or `SERVERLESS_TYPE=DISABLED`.
|
||||
</Warning>
|
||||
|
||||
### Available Drivers
|
||||
|
||||
| Driver | Environment Variable | Use Case | Security Level |
|
||||
|--------|---------------------|----------|----------------|
|
||||
| Disabled | `SERVERLESS_TYPE=DISABLED` | Disable serverless functions entirely | N/A |
|
||||
| Disabled | `SERVERLESS_TYPE=DISABLED` | Disable logic functions entirely | N/A |
|
||||
| Local | `SERVERLESS_TYPE=LOCAL` | Development and trusted environments | Low (no sandboxing) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Production with untrusted code | High (hardware-level isolation) |
|
||||
|
||||
@@ -321,11 +321,11 @@ SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**To disable serverless functions:**
|
||||
**To disable logic functions:**
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
```
|
||||
|
||||
<Note>
|
||||
When using `SERVERLESS_TYPE=DISABLED`, any attempt to execute a serverless function will return an error. This is useful if you want to run Twenty without serverless function capabilities.
|
||||
When using `SERVERLESS_TYPE=DISABLED`, any attempt to execute a logic function will return an error. This is useful if you want to run Twenty without logic function capabilities.
|
||||
</Note>
|
||||
|
||||
+307
-307
File diff suppressed because it is too large
Load Diff
@@ -170,6 +170,13 @@ cd twenty
|
||||
|
||||
يقوم هذا بإنشاء دور مشرف نظام باسم `postgres` مع إمكانية تسجيل الدخول.
|
||||
|
||||
```bash
|
||||
اسم الدور | الخصائص | عضو في
|
||||
-----------+-------------+-----------
|
||||
postgres | مشرف نظام | {}
|
||||
john | مشرف نظام | {}
|
||||
```
|
||||
|
||||
**الخيار 2:** إذا كنت قد قمت بتثبيت docker:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -9,18 +9,14 @@ description: أنشئ وأدِر تخصيصات Twenty على هيئة كود.
|
||||
|
||||
## ما هي التطبيقات؟
|
||||
|
||||
تتيح لك التطبيقات إنشاء وإدارة تخصيصات Twenty **ككود**. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تُعرِّف نموذج بياناتك ووظائف بلا خادم في الكود — مما يجعل الإنشاء والصيانة والنشر إلى مساحات عمل متعددة أسرع.
|
||||
تتيح لك التطبيقات إنشاء وإدارة تخصيصات Twenty **ككود**. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تُعرِّف نموذج بياناتك ووظائف المنطق في الكود — مما يجعل البناء والصيانة والنشر إلى مساحات عمل متعددة أسرع.
|
||||
|
||||
**ما الذي يمكنك فعله اليوم:**
|
||||
|
||||
* عرِّف كائنات وحقولًا مخصصة على شكل كود (نموذج بيانات مُدار)
|
||||
* أنشئ وظائف بلا خادم مع مشغلات مخصصة
|
||||
* أنشئ وظائف منطقية مع مشغلات مخصصة
|
||||
* انشر التطبيق نفسه عبر مساحات عمل متعددة
|
||||
|
||||
**قريبًا:**
|
||||
|
||||
* تخطيطات ومكونات واجهة مستخدم مخصصة
|
||||
|
||||
## المتطلبات الأساسية
|
||||
|
||||
* Node.js 24+ وYarn 4
|
||||
@@ -50,15 +46,12 @@ yarn app:dev
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn app:create-entity
|
||||
yarn entity:add
|
||||
|
||||
# Generate a typed Twenty client and workspace entity types
|
||||
yarn app:generate
|
||||
|
||||
# Run a one‑time sync (instead of watch mode)
|
||||
yarn app:sync
|
||||
|
||||
# Watch your application's functions logs
|
||||
# Watch your application's function logs
|
||||
yarn function:logs
|
||||
|
||||
# Execute a function by name
|
||||
@@ -68,7 +61,7 @@ yarn function:execute -n my-function -p '{"name": "test"}'
|
||||
yarn app:uninstall
|
||||
|
||||
# Display commands' help
|
||||
yarn app:help
|
||||
yarn help
|
||||
```
|
||||
|
||||
راجع أيضًا: صفحات مرجع CLI لـ [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) و[twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
@@ -96,82 +89,61 @@ my-twenty-app/
|
||||
eslint.config.mjs
|
||||
tsconfig.json
|
||||
README.md
|
||||
public/ # مجلد الأصول العامة (صور، خطوط، إلخ)
|
||||
src/
|
||||
app/
|
||||
application.config.ts # مطلوب - التكوين الرئيسي للتطبيق
|
||||
default-function.role.ts # الدور الافتراضي للوظائف بدون خادم
|
||||
// الكيانات الخاصة بك (*.object.ts, *.function.ts, *.role.ts)
|
||||
utils/ # اختياري - تنفيذات المُعالِجات والأدوات المساعدة
|
||||
```
|
||||
|
||||
### الاتفاقية فوق التهيئة
|
||||
|
||||
تستخدم التطبيقات نهج **الاتفاقية فوق التهيئة** حيث تُكتشف الكيانات عبر لاحقة اسم الملف. يتيح ذلك تنظيمًا مرنًا داخل مجلد `src/app/`:
|
||||
|
||||
| لاحقة الملف | نوع الكيان |
|
||||
| --------------- | ---------------------- |
|
||||
| `*.object.ts` | تعريفات كائنات مخصصة |
|
||||
| `*.function.ts` | تعريفات وظائف بلا خادم |
|
||||
| `*.role.ts` | تعريفات الأدوار |
|
||||
|
||||
### طرق تنظيم المجلدات المدعومة
|
||||
|
||||
يمكنك تنظيم الكيانات بأي من الأنماط التالية:
|
||||
|
||||
**تقليدي (حسب النوع):**
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
├── objects/
|
||||
│ └── postCard.object.ts
|
||||
├── functions/
|
||||
│ └── createPostCard.function.ts
|
||||
└── roles/
|
||||
└── admin.role.ts
|
||||
```
|
||||
|
||||
**حسب الميزة:**
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
└── post-card/
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
└── postCardAdmin.role.ts
|
||||
```
|
||||
|
||||
**مسطح:**
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
└── admin.role.ts
|
||||
├── application-config.ts # مطلوب - التكوين الرئيسي للتطبيق
|
||||
├── roles/
|
||||
│ └── default-role.ts # الدور الافتراضي لوظائف المنطق
|
||||
├── logic-functions/
|
||||
│ └── hello-world.ts # مثال لوظيفة منطقية
|
||||
└── front-components/
|
||||
└── hello-world.tsx # مثال لمكوّن الواجهة الأمامية
|
||||
```
|
||||
|
||||
بشكل عام:
|
||||
|
||||
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` فضلًا عن نصوص مثل `dev` و`sync` و`generate` و`create-entity` و`logs` و`uninstall` و`auth` التي تفوِّض إلى `twenty` CLI المحلي.
|
||||
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` فضلًا عن نصوص مثل `app:dev` و`app:generate` و`entity:add` و`function:logs` و`function:execute` و`app:uninstall` وأوامر المصادقة التي تُفوِّض إلى `twenty` CLI المحلي.
|
||||
* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`generated/` (عميل مضبوط الأنواع) و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`.
|
||||
* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع.
|
||||
* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع.
|
||||
* **eslint.config.mjs** و**tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
|
||||
* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية.
|
||||
* **src/app/**: المكان الرئيسي حيث تعرّف تطبيقك ككود:
|
||||
* `application.config.ts`: التكوين العام لتطبيقك (بيانات وصفية وربط وقت التشغيل). انظر "تكوين التطبيق" أدناه.
|
||||
* `*.role.ts`: تعريفات الأدوار المستخدمة بواسطة وظائفك بلا خادم. انظر "الدور الافتراضي للوظيفة" أدناه.
|
||||
* `*.object.ts`: تعريفات كائنات مخصصة.
|
||||
* `*.function.ts`: تعريفات وظائف بلا خادم.
|
||||
* **src/utils/**: مجلد اختياري لتنفيذات المعالجات والأدوات المساعدة.
|
||||
* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل.
|
||||
* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود
|
||||
|
||||
### اكتشاف الكيانات
|
||||
|
||||
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
|
||||
|
||||
| دالة مساعدة | نوع الكيان |
|
||||
| ------------------------ | --------------------------------- |
|
||||
| `defineObject()` | تعريفات كائنات مخصصة |
|
||||
| `defineLogicFunction()` | تعريفات الوظائف المنطقية |
|
||||
| `defineFrontComponent()` | Front component definitions |
|
||||
| `defineRole()` | تعريفات الأدوار |
|
||||
| `defineField()` | امتدادات الحقول للكائنات الموجودة |
|
||||
|
||||
<Note>
|
||||
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
|
||||
</Note>
|
||||
|
||||
مثال على كيان تم اكتشافه:
|
||||
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
|
||||
|
||||
* `yarn app:generate` سيُنشئ مجلدًا `generated/` (عميل Twenty مضبوط الأنواع + أنواع مساحة العمل).
|
||||
* `yarn app:create-entity` سيضيف ملفات تعريف الكيانات تحت `src/app/` لكائناتك المخصصة أو الوظائف أو الأدوار.
|
||||
l
|
||||
* `yarn entity:add` سيضيف ملفات تعريف الكيانات تحت `src/` لكائناتك المخصصة أو الوظائف أو المكونات الواجهية أو الأدوار.
|
||||
|
||||
## المصادقة
|
||||
|
||||
@@ -212,16 +184,18 @@ Once you've switched workspaces with `auth:switch`, all subsequent commands will
|
||||
|
||||
### دوال مساعدة
|
||||
|
||||
يوفّر SDK أربع دوال مساعدة مع تحقق مدمج لتعريف كيانات تطبيقك:
|
||||
يوفّر SDK دوالًا مساعدة لتعريف كيانات تطبيقك. كما هو موضح في [اكتشاف الكيانات](#entity-detection)، يجب استخدام `export default define<Entity>({...})` كي يتم اكتشاف كياناتك:
|
||||
|
||||
| دالة | الغرض |
|
||||
| ------------------ | ---------------------------------------- |
|
||||
| `defineApp()` | تهيئة بيانات التطبيق الوصفية |
|
||||
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
|
||||
| `defineFunction()` | تعريف وظائف بلا خادم مع معالجات |
|
||||
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
|
||||
| دالة | الغرض |
|
||||
| ------------------------ | ---------------------------------------------------- |
|
||||
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
|
||||
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
|
||||
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
|
||||
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
|
||||
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
|
||||
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
|
||||
|
||||
تتحقق هذه الدوال من تكوينك في وقت التشغيل وتوفر إكمالًا تلقائيًا أفضل في بيئة التطوير وأمان أنواع أعلى.
|
||||
تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع.
|
||||
|
||||
### تعريف الكائنات
|
||||
|
||||
@@ -302,79 +276,28 @@ export default defineObject({
|
||||
* `universalIdentifier` يجب أن يكون فريدًا وثابتًا عبر عمليات النشر.
|
||||
* يتطلب كل حقل `name` و`type` و`label` ومعرّف `universalIdentifier` ثابتًا خاصًا به.
|
||||
* المصفوفة `fields` اختيارية — يمكنك تعريف كائنات بدون حقول مخصصة.
|
||||
* يمكنك إنشاء كائنات جديدة باستخدام `yarn app:create-entity`، والذي يرشدك خلال التسمية والحقول والعلاقات.
|
||||
* يمكنك إنشاء كائنات جديدة باستخدام `yarn entity:add`، والذي يرشدك خلال التسمية والحقول والعلاقات.
|
||||
|
||||
<Note>
|
||||
**يتم إنشاء الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، يضيف Twenty تلقائيًا حقولًا قياسية مثل `name` و`createdAt` و`updatedAt` و`createdBy` و`position` و`deletedAt`. لا تحتاج إلى تعريف هذه في مصفوفة `fields` — أضف فقط حقولك المخصصة.
|
||||
</Note>
|
||||
|
||||
<Accordion title="بديل: صياغة قائمة على المزيّنات">
|
||||
يمكنك أيضًا تعريف كائنات باستخدام مزيّنات TypeScript. يستخدم هذا النهج صياغة معتمدة على الأصناف مع مزيّنات `@Object` و`@Field` و`@Relation`:
|
||||
### تكوين التطبيق (application-config.ts)
|
||||
|
||||
```typescript
|
||||
import {
|
||||
type AddressField,
|
||||
Field,
|
||||
FieldType,
|
||||
type FullNameField,
|
||||
Object,
|
||||
OnDeleteAction,
|
||||
Relation,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
import { type Note } from '../../generated';
|
||||
|
||||
@Object({
|
||||
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
|
||||
nameSingular: 'postCard',
|
||||
namePlural: 'postCards',
|
||||
labelSingular: 'Post card',
|
||||
labelPlural: 'Post cards',
|
||||
description: 'A post card object',
|
||||
icon: 'IconMail',
|
||||
})
|
||||
export class PostCard {
|
||||
@Field({
|
||||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||||
type: FieldType.TEXT,
|
||||
label: 'Content',
|
||||
description: "Postcard's content",
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
content: string;
|
||||
|
||||
@Relation({
|
||||
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
label: 'Notes',
|
||||
icon: 'IconComment',
|
||||
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
|
||||
onDelete: OnDeleteAction.CASCADE,
|
||||
})
|
||||
notes: Note[];
|
||||
}
|
||||
```
|
||||
|
||||
ملاحظة: يتطلب نهج المزيّنات `experimentalDecorators` في تهيئة TypeScript لديك.
|
||||
</Accordion>
|
||||
|
||||
### تكوين التطبيق (application.config.ts)
|
||||
|
||||
كل تطبيق لديه ملف واحد `application.config.ts` يصف:
|
||||
كل تطبيق لديه ملف واحد `application-config.ts` يصف:
|
||||
|
||||
* **هوية التطبيق**: المعرفات، اسم العرض، والوصف.
|
||||
* **كيفية تشغيل وظائفه**: الدور الذي تستخدمه للأذونات.
|
||||
* **متغيرات (اختياري)**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة.
|
||||
|
||||
استخدم `defineApp()` لتعريف تهيئة تطبيقك:
|
||||
Use `defineApplication()` to define your application configuration:
|
||||
|
||||
```typescript
|
||||
// src/app/application.config.ts
|
||||
import { defineApp } from 'twenty-sdk';
|
||||
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
|
||||
export default defineApp({
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
@@ -387,7 +310,7 @@ export default defineApp({
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -395,11 +318,11 @@ export default defineApp({
|
||||
|
||||
* حقول `universalIdentifier` هي معرّفات حتمية تخصك؛ أنشئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة.
|
||||
* `applicationVariables` تصبح متغيرات بيئة لوظائفك (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `functionRoleUniversalIdentifier` يجب أن يطابق الدور الذي تعرّفه في ملف `*.role.ts` (انظر أدناه).
|
||||
* `defaultRoleUniversalIdentifier` يجب أن يطابق ملف الدور (انظر أدناه).
|
||||
|
||||
#### الأدوار والصلاحيات
|
||||
|
||||
يمكن للتطبيقات تعريف أدوار تُغلّف الصلاحيات على كائنات وإجراءات مساحة العمل لديك. يعين الحقل `functionRoleUniversalIdentifier` في `application.config.ts` الدور الافتراضي الذي تستخدمه الوظائف بلا خادم في تطبيقك.
|
||||
يمكن للتطبيقات تعريف أدوار تُغلّف الصلاحيات على كائنات وإجراءات مساحة العمل لديك. يعين الحقل `defaultRoleUniversalIdentifier` في `application-config.ts` الدور الافتراضي الذي تستخدمه وظائف المنطق في تطبيقك.
|
||||
|
||||
* مفتاح واجهة البرمجة في وقت التشغيل المحقون باسم `TWENTY_API_KEY` مستمد من دور الوظيفة الافتراضي هذا.
|
||||
* سيُقيَّد العميل مضبوط الأنواع بالأذونات الممنوحة لذلك الدور.
|
||||
@@ -410,14 +333,14 @@ export default defineApp({
|
||||
عند توليد تطبيق جديد بالقالب، ينشئ CLI أيضًا ملف دور افتراضي. استخدم `defineRole()` لتعريف أدوار مع تحقق مدمج:
|
||||
|
||||
```typescript
|
||||
// src/app/default-function.role.ts
|
||||
// src/roles/default-role.ts
|
||||
import { defineRole, PermissionFlag } from 'twenty-sdk';
|
||||
|
||||
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Default function role',
|
||||
description: 'Default role for function Twenty client',
|
||||
canReadAllObjectRecords: false,
|
||||
@@ -430,7 +353,7 @@ export default defineRole({
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
@@ -439,8 +362,8 @@ export default defineRole({
|
||||
],
|
||||
fieldPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
fieldName: 'content',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
|
||||
canReadFieldValue: false,
|
||||
canUpdateFieldValue: false,
|
||||
},
|
||||
@@ -449,10 +372,10 @@ export default defineRole({
|
||||
});
|
||||
```
|
||||
|
||||
يُشار بعد ذلك إلى `universalIdentifier` لهذا الدور في `application.config.ts` باسم `functionRoleUniversalIdentifier`. بعبارة أخرى:
|
||||
يُشار بعد ذلك إلى `universalIdentifier` لهذا الدور في `application-config.ts` باسم `defaultRoleUniversalIdentifier`. بعبارة أخرى:
|
||||
|
||||
* **\\*.role.ts** يحدد ما يمكن أن يفعله الدور الافتراضي للوظيفة.
|
||||
* **application.config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك صلاحياته.
|
||||
* **application-config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك أذوناته.
|
||||
|
||||
الملاحظات:
|
||||
|
||||
@@ -461,22 +384,17 @@ export default defineRole({
|
||||
* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في الحد الأدنى؛ أضف فقط ما تحتاجه.
|
||||
* اطّلع على مثال عملي في تطبيق Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
|
||||
### تكوين وظيفة بلا خادم ونقطة الدخول
|
||||
### تكوين الوظيفة المنطقية ونقطة الدخول
|
||||
|
||||
كل ملف وظيفة يستخدم `defineFunction()` لتصدير تكوين مع معالج ومشغلات اختيارية. استخدم لاحقة الملف `*.function.ts` للاكتشاف التلقائي.
|
||||
كل ملف وظيفة يستخدم `defineLogicFunction()` لتصدير تكوين مع معالج ومشغّلات اختيارية.
|
||||
|
||||
```typescript
|
||||
// src/app/createPostCard.function.ts
|
||||
import { defineFunction } from 'twenty-sdk';
|
||||
// src/app/createPostCard.logic-function.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
|
||||
import Twenty, { type Person } from '../../generated';
|
||||
import Twenty, { type Person } from '~/generated';
|
||||
|
||||
const handler = async (
|
||||
params:
|
||||
| RoutePayload
|
||||
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
|
||||
| CronPayload,
|
||||
) => {
|
||||
const handler = async (params: RoutePayload) => {
|
||||
const client = new Twenty(); // generated typed client
|
||||
const name = 'name' in params.queryStringParameters
|
||||
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
@@ -492,7 +410,7 @@ const handler = async (
|
||||
return result;
|
||||
};
|
||||
|
||||
export default defineFunction({
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'create-new-post-card',
|
||||
timeoutSeconds: 2,
|
||||
@@ -507,18 +425,18 @@ export default defineFunction({
|
||||
isAuthRequired: false,
|
||||
},
|
||||
// Cron trigger (CRON pattern)
|
||||
{
|
||||
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
type: 'cron',
|
||||
pattern: '0 0 1 1 *',
|
||||
},
|
||||
// {
|
||||
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
// type: 'cron',
|
||||
// pattern: '0 0 1 1 *',
|
||||
// },
|
||||
// Database event trigger
|
||||
{
|
||||
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
type: 'databaseEvent',
|
||||
eventName: 'person.updated',
|
||||
updatedFields: ['name'],
|
||||
},
|
||||
// {
|
||||
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
// type: 'databaseEvent',
|
||||
// eventName: 'person.updated',
|
||||
// updatedFields: ['name'],
|
||||
// },
|
||||
],
|
||||
});
|
||||
```
|
||||
@@ -565,10 +483,10 @@ export default defineFunction({
|
||||
**لترحيل الدوال الحالية:** حدّث المعالج لديك لفكّ البنية من `event.body` أو `event.queryStringParameters` أو `event.pathParameters` بدلاً من القراءة مباشرةً من كائن params.
|
||||
</Warning>
|
||||
|
||||
عندما يستدعي مشغّل المسار دالتك، فإنه يتلقى كائنًا من النوع `RoutePayload` يتبع تنسيق AWS HTTP API v2. استورد النوع من `twenty-sdk`:
|
||||
عندما يستدعي مشغّل المسار وظيفتك المنطقية، يتلقى كائنًا من النوع `RoutePayload` يتبع تنسيق AWS HTTP API v2. استورد النوع من `twenty-sdk`:
|
||||
|
||||
```typescript
|
||||
import { defineFunction, type RoutePayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
// Access request data
|
||||
@@ -595,10 +513,10 @@ const handler = async (event: RoutePayload) => {
|
||||
|
||||
### تمرير رؤوس HTTP
|
||||
|
||||
افتراضيًا، لا يتم تمرير رؤوس HTTP من الطلبات الواردة إلى دالتك بدون خادم لأسباب أمنية. للوصول إلى رؤوس محددة، قم بإدراجها صراحةً في مصفوفة `forwardedRequestHeaders`:
|
||||
افتراضيًا، **لا** تُمرَّر رؤوس HTTP من الطلبات الواردة إلى وظيفتك المنطقية لأسباب أمنية. للوصول إلى رؤوس محددة، قم بإدراجها صراحةً في مصفوفة `forwardedRequestHeaders`:
|
||||
|
||||
```typescript
|
||||
export default defineFunction({
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'webhook-handler',
|
||||
handler,
|
||||
@@ -633,23 +551,60 @@ const handler = async (event: RoutePayload) => {
|
||||
|
||||
يمكنك إنشاء وظائف جديدة بطريقتين:
|
||||
|
||||
* **مُنشأ بالقالب**: شغّل `yarn app:create-entity` واختر خيار إضافة وظيفة جديدة. يُولّد هذا ملفًا مبدئيًا مع معالج وتكوين.
|
||||
* **يدوي**: أنشئ ملفًا جديدًا `*.function.ts` واستخدم `defineFunction()` مع اتباع النمط نفسه.
|
||||
* **مُنشأ بالقالب**: شغّل `yarn entity:add` واختر خيار إضافة وظيفة منطقية جديدة. يُولّد هذا ملفًا مبدئيًا مع معالج وتكوين.
|
||||
* **يدوي**: أنشئ ملفًا جديدًا `*.logic-function.ts` واستخدم `defineLogicFunction()` مع اتباع النمط نفسه.
|
||||
|
||||
### المكوّنات الأمامية
|
||||
|
||||
تتيح لك المكوّنات الأمامية إنشاء مكوّنات React مخصّصة تُعرَض داخل واجهة مستخدم Twenty. استخدم `defineFrontComponent()` لتعريف مكوّنات مع تحقّق مدمج:
|
||||
|
||||
```typescript
|
||||
// src/my-widget.front-component.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
const MyWidget = () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>My Custom Widget</h1>
|
||||
<p>This is a custom front component for Twenty.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
name: 'my-widget',
|
||||
description: 'A custom widget component',
|
||||
component: MyWidget,
|
||||
});
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
|
||||
* المكوّنات الأمامية هي مكوّنات React تُعرَض ضمن سياقات معزولة داخل Twenty.
|
||||
* استخدم لاحقة الملف `*.front-component.tsx` للاكتشاف التلقائي.
|
||||
* يشير الحقل `component` إلى مكوّن React الخاص بك.
|
||||
* يتم بناء المكوّنات ومزامنتها تلقائيًا أثناء `yarn app:dev`.
|
||||
|
||||
يمكنك إنشاء مكوّنات أمامية جديدة بطريقتين:
|
||||
|
||||
* **مُنشأ بالقالب**: شغّل `yarn entity:add` واختر خيار إضافة مكوّن أمامي جديد.
|
||||
* **يدوي**: أنشئ ملفًا جديدًا `*.front-component.tsx` واستخدم `defineFrontComponent()`.
|
||||
|
||||
### عميل مُولَّد مضبوط الأنواع
|
||||
|
||||
شغّل yarn app:generate لإنشاء عميل محلي مضبوط الأنواع في generated/ استنادًا إلى مخطط مساحة العمل لديك. استخدمه في وظائفك:
|
||||
|
||||
```typescript
|
||||
import Twenty from './generated';
|
||||
import Twenty from '~/generated';
|
||||
|
||||
const client = new Twenty();
|
||||
const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
```
|
||||
|
||||
يُعاد توليد العميل بواسطة `yarn app:generate`. أعِد تشغيله بعد تغيير كائناتك وتشغيل `yarn app:sync` أو عند الانضمام إلى مساحة عمل جديدة.
|
||||
يُعاد توليد العميل بواسطة `yarn app:generate`. أعِد التشغيل بعد تغيير كائناتك أو عند الانضمام إلى مساحة عمل جديدة.
|
||||
|
||||
#### بيانات الاعتماد في وقت التشغيل في الوظائف بلا خادم
|
||||
#### بيانات الاعتماد وقت التشغيل في الوظائف المنطقية
|
||||
|
||||
عندما تعمل وظيفتك على Twenty، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئة قبل تنفيذ كودك:
|
||||
|
||||
@@ -659,12 +614,12 @@ const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
الملاحظات:
|
||||
|
||||
* لا تحتاج إلى تمرير عنوان URL أو مفتاح واجهة برمجة التطبيقات إلى العميل المُولَّد. يقوم بقراءة `TWENTY_API_URL` و`TWENTY_API_KEY` من process.env وقت التشغيل.
|
||||
* تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `application.config.ts` عبر `functionRoleUniversalIdentifier`. هذا هو الدور الافتراضي الذي تستخدمه الوظائف بلا خادم في تطبيقك.
|
||||
* يمكن للتطبيقات تعريف أدوار لاتباع مبدأ أقل الامتياز. امنح فقط الأذونات التي تحتاجها وظائفك، ثم وجّه `functionRoleUniversalIdentifier` إلى المعرّف الشامل لذلك الدور.
|
||||
* تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `application-config.ts` عبر `defaultRoleUniversalIdentifier`. هذا هو الدور الافتراضي الذي تستخدمه الوظائف المنطقية في تطبيقك.
|
||||
* يمكن للتطبيقات تعريف أدوار لاتباع مبدأ أقل الامتياز. امنح فقط الأذونات التي تحتاجها وظائفك، ثم وجّه `defaultRoleUniversalIdentifier` إلى المعرّف الشامل لذلك الدور.
|
||||
|
||||
### مثال Hello World
|
||||
|
||||
استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف ومشغلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف المنطقية والمكوّنات الأمامية ومشغّلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
|
||||
## إعداد يدوي (بدون المهيئ)
|
||||
|
||||
@@ -679,25 +634,29 @@ yarn add -D twenty-sdk
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"auth": "twenty auth login",
|
||||
"generate": "twenty app generate",
|
||||
"dev": "twenty app dev",
|
||||
"sync": "twenty app sync",
|
||||
"uninstall": "twenty app uninstall",
|
||||
"logs": "twenty app logs",
|
||||
"create-entity": "twenty app add",
|
||||
"help": "twenty --help"
|
||||
"auth:login": "twenty auth:login",
|
||||
"auth:logout": "twenty auth:logout",
|
||||
"auth:status": "twenty auth:status",
|
||||
"auth:switch": "twenty auth:switch",
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"app:generate": "twenty app:generate",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
"entity:add": "twenty entity:add",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"help": "twenty help"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
يمكنك الآن تشغيل الأوامر نفسها عبر Yarn، مثل `yarn app:dev` و`yarn app:sync`، إلخ.
|
||||
يمكنك الآن تشغيل الأوامر نفسها عبر Yarn، مثل `yarn app:dev` و`yarn app:generate`، إلخ.
|
||||
|
||||
## استكشاف الأخطاء وإصلاحها
|
||||
|
||||
* أخطاء المصادقة: شغّل `yarn auth:login` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة.
|
||||
* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة البرمجة وأن خادم Twenty قابل للوصول.
|
||||
* الأنواع أو العميل مفقود/قديم: شغّل `yarn app:generate` ثم `yarn app:dev`.
|
||||
* الأنواع أو العميل مفقود/قديم: شغّل `yarn app:generate`.
|
||||
* وضع التطوير لا يزامن: تأكد من أن `yarn app:dev` قيد التشغيل وأن التغييرات ليست متجاهلة من بيئتك.
|
||||
|
||||
قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
|
||||
@@ -292,19 +292,19 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**وضع بيئي فقط:** إذا كنت قد ضبطت `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`، فأضف هذه المتغيرات إلى ملف `.env` الخاص بك بدلاً من ذلك.
|
||||
</Warning>
|
||||
|
||||
## وظائف بلا خادم
|
||||
## الوظائف المنطقية
|
||||
|
||||
تدعم Twenty وظائف بلا خادم لعمليات سير العمل والمنطق المخصص. يتم تكوين بيئة التنفيذ عبر متغير البيئة `SERVERLESS_TYPE`.
|
||||
تدعم Twenty الوظائف المنطقية لعمليات سير العمل والمنطق المخصص. يتم تكوين بيئة التنفيذ عبر متغير البيئة `SERVERLESS_TYPE`.
|
||||
|
||||
<Warning>
|
||||
**ملاحظة أمنية:** يقوم برنامج التشغيل المحلي لوظائف بلا خادم (`SERVERLESS_TYPE=LOCAL`) بتشغيل الشيفرة مباشرةً على المضيف ضمن عملية Node.js من دون عزل. يجب استخدامه فقط للشيفرة الموثوقة أثناء التطوير. بالنسبة لعمليات النشر الإنتاجية التي تتعامل مع شيفرة غير موثوق بها، نوصي بشدة باستخدام `SERVERLESS_TYPE=LAMBDA` أو `SERVERLESS_TYPE=DISABLED`.
|
||||
**ملاحظة أمنية:** يقوم برنامج التشغيل المحلي (`SERVERLESS_TYPE=LOCAL`) بتشغيل الشيفرة مباشرةً على المضيف ضمن عملية Node.js من دون عزل. يجب استخدامه فقط للشيفرة الموثوقة أثناء التطوير. بالنسبة لعمليات النشر الإنتاجية التي تتعامل مع شيفرة غير موثوق بها، نوصي بشدة باستخدام `SERVERLESS_TYPE=LAMBDA` أو `SERVERLESS_TYPE=DISABLED`.
|
||||
</Warning>
|
||||
|
||||
### برامج التشغيل المتاحة
|
||||
|
||||
| برنامج التشغيل | متغير البيئة | حالة الاستخدام | مستوى الأمان |
|
||||
| -------------- | -------------------------- | ------------------------------- | ----------------------------- |
|
||||
| معطل | `SERVERLESS_TYPE=DISABLED` | تعطيل وظائف بلا خادم بالكامل | غير متاح |
|
||||
| معطل | `SERVERLESS_TYPE=DISABLED` | تعطيل الوظائف المنطقية بالكامل | غير متاح |
|
||||
| محلي | `SERVERLESS_TYPE=LOCAL` | بيئات التطوير والبيئات الموثوقة | منخفض (من دون عزل) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | الإنتاج مع شيفرة غير موثوق بها | مرتفع (عزل على مستوى الأجهزة) |
|
||||
|
||||
@@ -326,12 +326,12 @@ SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**لتعطيل وظائف بلا خادم:**
|
||||
**لتعطيل الوظائف المنطقية:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
```
|
||||
|
||||
<Note>
|
||||
عند استخدام `SERVERLESS_TYPE=DISABLED`، ستؤدي أي محاولة لتنفيذ وظيفة بلا خادم إلى إرجاع خطأ. يكون هذا مفيدًا إذا كنت ترغب في تشغيل Twenty من دون قدرات وظائف بلا خادم.
|
||||
عند استخدام `SERVERLESS_TYPE=DISABLED`، ستؤدي أي محاولة لتنفيذ وظيفة منطقية إلى إرجاع خطأ. يكون هذا مفيدًا إذا كنت ترغب في تشغيل Twenty من دون إمكانات الوظائف المنطقية.
|
||||
</Note>
|
||||
|
||||
@@ -59,4 +59,4 @@ description: ميزات مدعومة بالذكاء الاصطناعي قادم
|
||||
سنحدّث هذا القسم عندما تصبح ميزات الذكاء الاصطناعي متاحة. وفي هذه الأثناء:
|
||||
|
||||
* تابع [GitHub](https://github.com/twentyhq/twenty) للاطّلاع على تحديثات التطوير
|
||||
* انضم إلى [Discord](https://discord.gg/twenty) لمشاركة الملاحظات وطلبات الميزات
|
||||
* انضم إلى [Discord](https://discord.gg/UfGNZJfAG6) لمشاركة الملاحظات وطلبات الميزات
|
||||
|
||||
@@ -39,12 +39,16 @@ description: اربط السجلات عبر كائنات مختلفة باستخ
|
||||
|
||||
**مثال:** يمكن ربط العديد من الأشخاص بالعديد من المشاريع، والعكس صحيح.
|
||||
|
||||
<Warning>
|
||||
**متعدد-إلى-متعدد غير مدعوم بعد.**
|
||||
تستخدم العلاقات من نوع متعدد إلى متعدد نمط **كائن ربط**: كائن وسيط يربط بين الجانبين. باستخدام ميزة علاقة الربط، تعرض Twenty السجلات المرتبطة النهائية مباشرةً، مع إخفاء الكائن الوسيط من واجهة المستخدم.
|
||||
|
||||
هذا النوع من العلاقات مُخطّط للنصف الأول من عام 2026. كحل بديل، أنشئ كائنًا وسيطًا "junction" (مثال: "Project Assignments") لديه علاقات من نوع متعدد-إلى-واحد مع كلا الكائنين.
|
||||
<img src="/images/user-guide/fields/junction-relation-diagram.png" style={{width:'100%'}} />
|
||||
|
||||
<Warning>
|
||||
**ميزة المختبر**: يجب تمكين علاقات الربط في **الإعدادات → التحديثات → المختبر** قبل الاستخدام.
|
||||
</Warning>
|
||||
|
||||
راجع [كيفية إنشاء علاقات متعدد-إلى-متعدد](/l/ar/user-guide/data-model/how-tos/create-many-to-many-relations) للحصول على دليل كامل خطوة بخطوة.
|
||||
|
||||
## إنشاء حقل علاقة
|
||||
|
||||
1. اذهب إلى **الإعدادات → نموذج البيانات**
|
||||
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
---
|
||||
title: إنشاء علاقات متعدد-إلى-متعدد
|
||||
description: اربط السجلات حيث يمكن ربط عناصر كثيرة على كلا الجانبين معًا باستخدام كائنات الربط.
|
||||
---
|
||||
|
||||
تتيح علاقات متعدد-إلى-متعدد ربط سجلات متعددة على كلا الجانبين. على سبيل المثال: يمكن للعديد من الأشخاص العمل على العديد من المشاريع، ويمكن لكل مشروع أن يضم العديد من الأشخاص.
|
||||
|
||||
<Warning>
|
||||
**ميزة المختبر**: علاقات الربط متاحة حاليًا في المختبر. فعِّلها في **الإعدادات → التحديثات → المختبر** قبل اتباع هذا الدليل.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
تتطلب هذه الميزة أيضًا تفعيل **الوضع المتقدم** (زر التبديل في أسفل يمين صفحة الإعدادات).
|
||||
</Note>
|
||||
|
||||
## متى نستخدم علاقات متعدد-إلى-متعدد
|
||||
|
||||
استخدم علاقات متعدد-إلى-متعدد عندما يمكن لكل جانب من العلاقة أن يحتوي على عدة ارتباطات:
|
||||
|
||||
| العلاقة | مثال |
|
||||
| ------------------ | --------------------------------------------------------------------- |
|
||||
| الأشخاص ↔ المشاريع | قد يعمل الشخص على عدة مشاريع؛ ويضم المشروع عدة أعضاء فريق |
|
||||
| الشركات ↔ الوسوم | يمكن أن تحتوي الشركة على عدة وسوم؛ ويمكن أن ينطبق الوسم على عدة شركات |
|
||||
| المنتجات ↔ الطلبات | يمكن أن يوجد المنتج في عدة طلبات؛ ويحتوي الطلب على عدة منتجات |
|
||||
|
||||
## كيف يعمل
|
||||
|
||||
تستخدم Twenty نمط **كائن الربط** لعلاقات متعدد-إلى-متعدد. يوضع كائن الربط بين كائنين ويحتفظ بالارتباطات:
|
||||
|
||||
```
|
||||
People ←→ Project Assignments ←→ Projects
|
||||
```
|
||||
|
||||
يحتوي كائن **تعيينات المشروع** (كائن ربط) على:
|
||||
|
||||
* علاقة مع الأشخاص (متعدد-إلى-واحد)
|
||||
* علاقة مع المشاريع (متعدد-إلى-واحد)
|
||||
|
||||
عند تفعيل مفتاح علاقة الربط، تعرض Twenty السجلات المرتبطة مباشرةً بدلًا من إظهار سجلات كائن الربط الوسيطة.
|
||||
|
||||
## المتطلبات الأساسية
|
||||
|
||||
1. **تفعيل علاقات الربط في المختبر**: انتقل إلى **الإعدادات → التحديثات → المختبر** وفعِّل **علاقات الربط**
|
||||
2. **فعِّل الوضع المتقدم**: شغِّل **الوضع المتقدم** من أسفل يمين الشريط الجانبي لصفحة الإعدادات
|
||||
3. خطِّط نموذج البيانات الخاص بك:
|
||||
* ما الكائنان اللذان ستربطهما؟
|
||||
* ما الاسم الذي ينبغي أن يُطلق على كائن الربط؟
|
||||
|
||||
## الخطوة 1: إنشاء كائن الربط
|
||||
|
||||
أولًا، أنشئ الكائن الوسيط الذي سيحتفظ بالارتباطات.
|
||||
|
||||
1. اذهب إلى **الإعدادات → نموذج البيانات**
|
||||
2. انقر **+ كائن جديد**
|
||||
3. سمِّه تسمية وصفية (مثلًا: "تعيين مشروع"، "عضو فريق"، "طلب منتج")
|
||||
4. انقر على **حفظ**
|
||||
|
||||
<Tip>
|
||||
**اتفاقية التسمية**: استخدم اسمًا يصف العلاقة، مثل "تعيين مشروع" أو "عضوية الفريق". هذا يجعل نموذج البيانات أسهل في الفهم.
|
||||
</Tip>
|
||||
|
||||
## الخطوة 2: إنشاء علاقات من كائن الربط
|
||||
|
||||
أضِف حقول علاقة من كائن الربط إلى كلا الكائنين اللذين تريد ربطهما.
|
||||
|
||||
### العلاقة الأولى (كائن الربط → الكائن A)
|
||||
|
||||
1. حدِّد كائن الربط في **الإعدادات → نموذج البيانات**
|
||||
2. انقر **+ إضافة حقل**
|
||||
3. اختر **العلاقة** كنوع الحقل
|
||||
4. اختر الكائن الأول (مثلًا، "الأشخاص")
|
||||
5. عيِّن نوع العلاقة إلى **متعدد-إلى-واحد** (يمكن لعديد من التعيينات الارتباط بشخص واحد)
|
||||
6. قم بتسمية الحقول:
|
||||
* الحقل على كائن الربط: مثلًا، "شخص"
|
||||
* الحقل على الأشخاص: مثلًا، "تعيينات المشروع"
|
||||
7. انقر على **حفظ**
|
||||
|
||||
### العلاقة الثانية (كائن الربط → الكائن B)
|
||||
|
||||
1. وأنت ما زلت في كائن الربط، انقر **+ إضافة حقل**
|
||||
2. اختر **العلاقة** كنوع الحقل
|
||||
3. اختر الكائن الثاني (مثلًا، "المشاريع")
|
||||
4. عيِّن نوع العلاقة إلى **متعدد-إلى-واحد**
|
||||
5. قم بتسمية الحقول:
|
||||
* الحقل على كائن الربط: مثلًا، "مشروع"
|
||||
* الحقل على المشاريع: مثلًا، "أعضاء الفريق"
|
||||
6. انقر على **حفظ**
|
||||
|
||||
## الخطوة 3: ضبط عرض علاقة الربط
|
||||
|
||||
قم الآن بضبط كائنات المصدر لعرض السجلات المرتبطة مباشرةً، مع تجاوز كائن الربط الوسيط.
|
||||
|
||||
1. اذهب إلى **الإعدادات → نموذج البيانات**
|
||||
2. اختر الكائن الأول (مثلًا، "الأشخاص")
|
||||
3. اعثر على حقل العلاقة الذي يشير إلى كائن الربط (مثلًا، "تعيينات المشروع")
|
||||
4. انقر لتحرير الحقل
|
||||
5. فعّل **"هذه علاقة بكائن ربط"**
|
||||
6. حدِّد **العلاقة الهدف** (مثلًا، "مشروع" — الحقل على كائن الربط الذي يشير إلى الجانب الآخر)
|
||||
7. انقر على **حفظ**
|
||||
|
||||
{/* TODO: Add image
|
||||
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
|
||||
*/}
|
||||
|
||||
كرِّر على الكائن الآخر:
|
||||
|
||||
1. اختر "المشاريع" في نموذج البيانات
|
||||
2. حرِّر حقل العلاقة "أعضاء الفريق"
|
||||
3. فعّل مفتاح الربط
|
||||
4. حدِّد "شخص" كالعلاقة الهدف
|
||||
5. حفظ
|
||||
|
||||
## النتيجة
|
||||
|
||||
بعد التكوين:
|
||||
|
||||
* في سجل **شخص**، يعرض حقل "تعيينات المشروع" **المشاريع** مباشرةً (وليس سجلات التعيين)
|
||||
* في سجل **مشروع**، يعرض حقل "أعضاء الفريق" **الأشخاص** مباشرةً
|
||||
|
||||
لا يزال كائن الربط موجودًا ويخزّن الارتباطات، لكن واجهة المستخدم تقدّم عرضًا أوضح لعلاقات متعدد-إلى-متعدد.
|
||||
|
||||
## مثال: الأشخاص ↔ المشاريع
|
||||
|
||||
إليك شرحًا كاملًا خطوة بخطوة:
|
||||
|
||||
### إنشاء كائن الربط
|
||||
|
||||
* الاسم: **تعيين مشروع**
|
||||
* الوصف: "يربط الأشخاص بالمشاريع التي يعملون عليها"
|
||||
|
||||
### إضافة علاقات
|
||||
|
||||
1. **تعيين مشروع → الأشخاص**
|
||||
* النوع: متعدد-إلى-واحد
|
||||
* الحقل على التعيين: "شخص"
|
||||
* الحقل على الأشخاص: "تعيينات المشروع"
|
||||
|
||||
2. **تعيين مشروع → المشاريع**
|
||||
* النوع: متعدد-إلى-واحد
|
||||
* الحقل على التعيين: "مشروع"
|
||||
* الحقل على المشاريع: "أعضاء الفريق"
|
||||
|
||||
### ضبط عرض علاقة الربط
|
||||
|
||||
1. على كائن **الأشخاص**:
|
||||
* حرِّر حقل "تعيينات المشروع"
|
||||
* فعّل مفتاح الربط
|
||||
* الهدف: "مشروع"
|
||||
|
||||
2. على كائن **المشاريع**:
|
||||
* حرِّر حقل "أعضاء الفريق"
|
||||
* فعّل مفتاح الربط
|
||||
* الهدف: "شخص"
|
||||
|
||||
### استخدمه
|
||||
|
||||
* افتح سجل شخص → سترى مشاريعه مباشرةً
|
||||
* افتح سجل مشروع → سترى أعضاء الفريق مباشرةً
|
||||
* أنشئ ارتباطات جديدة من أي جانب
|
||||
|
||||
## إضافة بيانات إضافية إلى الارتباطات
|
||||
|
||||
نظرًا لأن كائن الربط كائن حقيقي، يمكنك إضافة حقول مخصصة لتخزين معلومات حول العلاقة:
|
||||
|
||||
* **الدور**: "مطوّر"، "مصمّم"، "مدير"
|
||||
* **تاريخ البدء**: متى انضمّوا إلى المشروع
|
||||
* **الساعات المخصّصة**: عدد الساعات الأسبوعية على هذا المشروع
|
||||
|
||||
للوصول إلى هذه البيانات، انتقل إلى كائن الربط مباشرةً أو استعلم عنها عبر واجهة API.
|
||||
|
||||
## القيود
|
||||
|
||||
* **استيراد/تصدير CSV**: لا يُدعم استيراد علاقات متعدد-إلى-متعدد مباشرةً. بدلًا من ذلك، استورد السجلات إلى كائن الربط.
|
||||
* **عوامل التصفية**: قد تكون خيارات التصفية حسب علاقات متعدد-إلى-متعدد محدودة.
|
||||
|
||||
## ذات صلة
|
||||
|
||||
* [حقول العلاقات](/l/ar/user-guide/data-model/capabilities/relation-fields) — شرح أنواع العلاقات
|
||||
* [إنشاء كائنات مخصصة](/l/ar/user-guide/data-model/how-tos/create-custom-objects) — كيفية إنشاء الكائنات
|
||||
* [إنشاء حقول العلاقات](/l/ar/user-guide/data-model/how-tos/create-relation-fields) — إعداد العلاقات الأساسي
|
||||
@@ -9,7 +9,7 @@ description: تعرّف على المصطلحات الأساسية المستخ
|
||||
|
||||
## التطبيقات
|
||||
|
||||
التطبيقات هي امتدادات مخصّصة مُنشأة على شكل شيفرة يمكنها تعريف نماذج البيانات ووظائف بدون خوادم. تمكّن المطورين من إنشاء تخصيصات قابلة لإعادة الاستخدام يمكن نشرها عبر مساحات عمل متعددة.
|
||||
التطبيقات هي امتدادات مخصّصة مُنشأة على شكل شيفرة يمكنها تعريف نماذج البيانات والوظائف المنطقية. تمكّن المطورين من إنشاء تخصيصات قابلة لإعادة الاستخدام يمكن نشرها عبر مساحات عمل متعددة.
|
||||
|
||||
## إجراءات الكود
|
||||
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ image: /images/user-guide/workflows/workflow.png
|
||||
| **تأكيدات الفعاليات** | تفاصيل الفعالية أو جدول الأعمال |
|
||||
|
||||
<Note>
|
||||
المرفقات ثابتة—يتم إرسال الملف نفسه إلى جميع المستلمين. بالنسبة للمستندات الديناميكية (مثل عروض الأسعار المخصصة)، أنشئ الملفات وأرفقها باستخدام [وظيفة بدون خادم](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
|
||||
المرفقات ثابتة—يتم إرسال الملف نفسه إلى جميع المستلمين. بالنسبة للمستندات الديناميكية (مثل عروض الأسعار المخصصة)، أنشئ الملفات وأرفقها باستخدام [وظيفة منطقية](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
|
||||
</Note>
|
||||
|
||||
## أفضل الممارسات
|
||||
|
||||
@@ -256,7 +256,7 @@ import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
* اختبر الكود مباشرة في الخطوة
|
||||
|
||||
<Note>
|
||||
إذا كنت بحاجة إلى استخدام مفاتيح API خارجية في كودك، فيجب إدخالها مباشرةً في جسم الدالة. لا يمكنك تكوين مفاتيح API في مكان آخر والإشارة إليها في الدالة عديمة الخادم.
|
||||
إذا كنت بحاجة إلى استخدام مفاتيح API خارجية في كودك، فيجب إدخالها مباشرةً في جسم الدالة. لا يمكنك تكوين مفاتيح API في مكان آخر والإشارة إليها في الدالة المنطقية.
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
|
||||
+9
-9
@@ -7,7 +7,7 @@ description: أنشئ سير عمل لإنشاء ملف PDF (مثل عرض سع
|
||||
|
||||
## نظرة عامة
|
||||
|
||||
يستخدم سير العمل هذا **المحفز اليدوي** بحيث يتمكن المستخدمون من إنشاء ملف PDF عند الطلب لأي سجل محدد. **وظيفة بلا خادم** تتولى ما يلي:
|
||||
يستخدم سير العمل هذا **المحفز اليدوي** بحيث يتمكن المستخدمون من إنشاء ملف PDF عند الطلب لأي سجل محدد. تتولى **الوظيفة المنطقية** ما يلي:
|
||||
|
||||
1. تنزيل ملف PDF من عنوان URL (من خدمة إنشاء PDF)
|
||||
2. رفع الملف إلى Twenty
|
||||
@@ -17,7 +17,7 @@ description: أنشئ سير عمل لإنشاء ملف PDF (مثل عرض سع
|
||||
|
||||
قبل إعداد سير العمل:
|
||||
|
||||
1. **أنشئ مفتاح API**: انتقل إلى **الإعدادات → واجهات برمجة التطبيقات** ثم أنشئ مفتاح API جديدًا. ستحتاج إلى هذا الرمز المميز للوظيفة بلا خادم.
|
||||
1. **أنشئ مفتاح API**: انتقل إلى **الإعدادات → واجهات برمجة التطبيقات** ثم أنشئ مفتاح API جديدًا. ستحتاج إلى هذا الرمز المميز للوظيفة المنطقية.
|
||||
2. **قم بإعداد خدمة إنشاء PDF** (اختياري): إذا كنت تريد إنشاء ملفات PDF ديناميكيًا (مثل عروض الأسعار)، فاستخدم خدمة مثل Carbone أو PDFMonkey أو DocuSeal لإنشاء ملف PDF والحصول على رابط تنزيل.
|
||||
|
||||
## إعداد خطوة بخطوة
|
||||
@@ -32,9 +32,9 @@ description: أنشئ سير عمل لإنشاء ملف PDF (مثل عرض سع
|
||||
باستخدام المحفز اليدوي، يمكن للمستخدمين تشغيل سير العمل هذا عبر زر يظهر في أعلى اليمين عند تحديد سجل، وذلك لإنشاء ملف PDF وإرفاقه.
|
||||
</Tip>
|
||||
|
||||
### الخطوة 2: إضافة وظيفة بلا خادم
|
||||
### الخطوة 2: إضافة وظيفة منطقية
|
||||
|
||||
1. أضف إجراء **وظيفة بلا خادم**
|
||||
1. أضف إجراء **Code** (وظيفة منطقية)
|
||||
2. أنشئ وظيفة جديدة باستخدام الكود أدناه
|
||||
3. قم بتهيئة معلمات الإدخال
|
||||
|
||||
@@ -45,10 +45,10 @@ description: أنشئ سير عمل لإنشاء ملف PDF (مثل عرض سع
|
||||
| `companyId` | `{{trigger.object.id}}` |
|
||||
|
||||
<Note>
|
||||
إذا كنت تُرفق إلى كائن مختلف (شخص، فرصة، إلخ)، فأعد تسمية المعلمة وفقًا لذلك (مثلًا، `personId`، `opportunityId`) وحدث الوظيفة بلا خادم.
|
||||
إذا كنت تُرفِقه بكائن مختلف (شخص، فرصة، إلخ)، فأعد تسمية المعلمة وفقًا لذلك (مثلًا، `personId`، `opportunityId`) وحدّث الوظيفة المنطقية.
|
||||
</Note>
|
||||
|
||||
#### كود الوظيفة بلا خادم
|
||||
#### كود الوظيفة المنطقية
|
||||
|
||||
```typescript
|
||||
export const main = async (
|
||||
@@ -172,7 +172,7 @@ export const main = async (
|
||||
إذا كنت تستخدم خدمة إنشاء PDF، يمكنك:
|
||||
|
||||
1. أولًا، أنشئ إجراء طلب HTTP لإنشاء ملف PDF
|
||||
2. مرّر رابط ملف PDF المُعاد إلى الوظيفة بلا خادم كمعلمة
|
||||
2. مرّر رابط ملف PDF المُعاد إلى الوظيفة المنطقية كمعلمة
|
||||
|
||||
```typescript
|
||||
export const main = async (
|
||||
@@ -211,7 +211,7 @@ export const main = async (
|
||||
* **DocuSeal** - منصة أتمتة المستندات
|
||||
* **Documint** - إنشاء مستندات يعتمد على واجهة برمجة التطبيقات أولًا
|
||||
|
||||
توفر كل خدمة واجهة برمجة تطبيقات تُرجع رابط ملف PDF، ويمكنك بعدها تمريره إلى الوظيفة بلا خادم.
|
||||
توفر كل خدمة واجهة برمجة تطبيقات تُرجع رابط ملف PDF، ويمكنك بعدها تمريره إلى الوظيفة المنطقية.
|
||||
|
||||
## استكشاف الأخطاء وإصلاحها
|
||||
|
||||
@@ -224,5 +224,5 @@ export const main = async (
|
||||
## ذات صلة
|
||||
|
||||
* [مشغلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers)
|
||||
* [وظائف بلا خادم](/l/ar/user-guide/workflows/capabilities/workflow-actions#serverless-function)
|
||||
* [الوظائف المنطقية](/l/ar/user-guide/workflows/capabilities/workflow-actions#code)
|
||||
* [إنشاء عرض سعر أو فاتورة من Twenty](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ description: الأسئلة الشائعة حول سير العمل في Twenty.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="ما أقصى وقت تنفيذ لإجراءات Code؟">
|
||||
إجراءات Code (دوال بلا خادم) لديها **مهلة افتراضية قدرها 5 دقائق** (300 ثانية).
|
||||
إجراءات Code (دوال منطقية) لديها **مهلة افتراضية قدرها 5 دقائق** (300 ثانية).
|
||||
|
||||
أقصى مهلة يمكن ضبطها هي **15 دقيقة** (900 ثانية).
|
||||
|
||||
|
||||
@@ -169,6 +169,13 @@ Všechny příkazy v následujících krocích byste měli provádět z kořene
|
||||
|
||||
Tím vytvoříte superuživatelskou roli pojmenovanou `postgres` s přístupovými právy.
|
||||
|
||||
```bash
|
||||
Jméno role | Vlastnosti | Členem
|
||||
-----------+-------------+-----------
|
||||
postgres | Superuživatel | {}
|
||||
john | Superuživatel | {}
|
||||
```
|
||||
|
||||
**Možnost 2:** Pokud máte nainstalován docker:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -9,18 +9,14 @@ description: Vytvářejte a spravujte přizpůsobení Twenty jako kód.
|
||||
|
||||
## Co jsou aplikace?
|
||||
|
||||
Aplikace vám umožňují vytvářet a spravovat přizpůsobení Twenty **jako kód**. Místo konfigurace všeho přes uživatelské rozhraní definujete v kódu svůj datový model a serverless funkce — což zrychluje vývoj, údržbu i nasazování do více pracovních prostorů.
|
||||
Aplikace vám umožňují vytvářet a spravovat přizpůsobení Twenty **jako kód**. Místo konfigurace všeho přes uživatelské rozhraní definujete v kódu svůj datový model a logické funkce — což zrychluje vývoj, údržbu i nasazování do více pracovních prostorů.
|
||||
|
||||
**Co můžete dělat už dnes:**
|
||||
|
||||
* Definujte vlastní objekty a pole jako kód (spravovaný datový model)
|
||||
* Vytvářejte serverless funkce s vlastními spouštěči
|
||||
* Vytvářejte logické funkce s vlastními spouštěči
|
||||
* Nasazujte stejnou aplikaci do více pracovních prostorů
|
||||
|
||||
**Již brzy:**
|
||||
|
||||
* Vlastní rozvržení a komponenty uživatelského rozhraní
|
||||
|
||||
## Předpoklady
|
||||
|
||||
* Node.js 24+ a Yarn 4
|
||||
@@ -49,26 +45,23 @@ yarn app:dev
|
||||
Odtud můžete:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn app:create-entity
|
||||
# Přidejte do vaší aplikace novou entitu (s průvodcem)
|
||||
yarn entity:add
|
||||
|
||||
# Generate a typed Twenty client and workspace entity types
|
||||
# Vygenerujte typovaného klienta Twenty a typy entit pracovního prostoru
|
||||
yarn app:generate
|
||||
|
||||
# Run a one‑time sync (instead of watch mode)
|
||||
yarn app:sync
|
||||
|
||||
# Watch your application's functions logs
|
||||
# Sledujte logy funkcí vaší aplikace
|
||||
yarn function:logs
|
||||
|
||||
# Execute a function by name
|
||||
# Spusťte funkci podle názvu
|
||||
yarn function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
# Odinstalujte aplikaci z aktuálního pracovního prostoru
|
||||
yarn app:uninstall
|
||||
|
||||
# Display commands' help
|
||||
yarn app:help
|
||||
# Zobrazte nápovědu k příkazům
|
||||
yarn help
|
||||
```
|
||||
|
||||
Viz také: referenční stránky CLI pro [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) a [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
@@ -96,82 +89,61 @@ my-twenty-app/
|
||||
eslint.config.mjs
|
||||
tsconfig.json
|
||||
README.md
|
||||
public/ # Složka s veřejnými prostředky (obrázky, písma apod.)
|
||||
src/
|
||||
app/
|
||||
application.config.ts # Povinné - hlavní konfigurace aplikace
|
||||
default-function.role.ts # Výchozí role pro serverless funkce
|
||||
// vaše entity (*.object.ts, *.function.ts, *.role.ts)
|
||||
utils/ # Volitelné - implementace handlerů a nástroje
|
||||
```
|
||||
|
||||
### Konvence před konfigurací
|
||||
|
||||
Aplikace používají přístup **konvence před konfigurací**, kde jsou entity detekovány podle přípony souboru. To umožňuje flexibilní organizaci ve složce `src/app/`:
|
||||
|
||||
| Přípona souboru | Typ entity |
|
||||
| --------------- | -------------------------- |
|
||||
| `*.object.ts` | Definice vlastních objektů |
|
||||
| `*.function.ts` | Definice serverless funkcí |
|
||||
| `*.role.ts` | Definice rolí |
|
||||
|
||||
### Podporované uspořádání složek
|
||||
|
||||
Entity můžete uspořádat podle některého z těchto vzorů:
|
||||
|
||||
**Tradiční (podle typu):**
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
├── objects/
|
||||
│ └── postCard.object.ts
|
||||
├── functions/
|
||||
│ └── createPostCard.function.ts
|
||||
└── roles/
|
||||
└── admin.role.ts
|
||||
```
|
||||
|
||||
**Podle funkcí:**
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
└── post-card/
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
└── postCardAdmin.role.ts
|
||||
```
|
||||
|
||||
**Plochá:**
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
└── admin.role.ts
|
||||
├── application-config.ts # Povinné – hlavní konfigurace aplikace
|
||||
├── roles/
|
||||
│ └── default-role.ts # Výchozí role pro logické funkce
|
||||
├── logic-functions/
|
||||
│ └── hello-world.ts # Ukázková logická funkce
|
||||
└── front-components/
|
||||
└── hello-world.tsx # Ukázková front-endová komponenta
|
||||
```
|
||||
|
||||
V kostce:
|
||||
|
||||
* **package.json**: Deklaruje název aplikace, verzi, engines (Node 24+, Yarn 4) a přidává `twenty-sdk` plus skripty jako `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall` a `auth`, které delegují na lokální `twenty` CLI.
|
||||
* **package.json**: Deklaruje název aplikace, verzi, engines (Node 24+, Yarn 4) a přidává `twenty-sdk` plus skripty jako `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall` a autentizační příkazy, které delegují na lokální `twenty` CLI.
|
||||
* **.gitignore**: Ignoruje běžné artefakty jako `node_modules`, `.yarn`, `generated/` (typovaný klient), `dist/`, `build/`, složky s coverage, logy a soubory `.env*`.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Zamykají a konfigurují nástrojový řetězec Yarn 4 používaný projektem.
|
||||
* **.nvmrc**: Fixuje verzi Node.js požadovanou projektem.
|
||||
* **eslint.config.mjs** a **tsconfig.json**: Poskytují lintování a konfiguraci TypeScriptu pro zdrojové soubory vaší aplikace v TypeScriptu.
|
||||
* **README.md**: Krátké README v kořeni aplikace se základními pokyny.
|
||||
* **src/app/**: Hlavní místo, kde definujete svou aplikaci jako kód:
|
||||
* `application.config.ts`: Globální konfigurace vaší aplikace (metadata a napojení za běhu). Viz „Konfigurace aplikace“ níže.
|
||||
* `*.role.ts`: Definice rolí používané vašimi serverless funkcemi. Viz „Výchozí role funkce“ níže.
|
||||
* `*.object.ts`: Definice vlastních objektů.
|
||||
* `*.function.ts`: Definice serverless funkcí.
|
||||
* **src/utils/**: Volitelná složka pro implementace obslužných funkcí a pomocné nástroje.
|
||||
* **public/**: Složka pro ukládání veřejných prostředků (obrázky, písma, statické soubory), které bude vaše aplikace poskytovat. Soubory umístěné zde se během synchronizace nahrají a jsou za běhu dostupné.
|
||||
* **src/**: Hlavní místo, kde definujete svou aplikaci jako kód
|
||||
|
||||
### Detekce entit
|
||||
|
||||
SDK detekuje entity analýzou vašich souborů TypeScript a hledá volání **`export default define<Entity>({...})`**. Každý typ entity má odpovídající pomocnou funkci exportovanou z `twenty-sdk`:
|
||||
|
||||
| Pomocná funkce | Typ entity |
|
||||
| ------------------------ | ------------------------------------- |
|
||||
| `defineObject()` | Definice vlastních objektů |
|
||||
| `defineLogicFunction()` | Definice logických funkcí |
|
||||
| `defineFrontComponent()` | Definice frontendových komponent |
|
||||
| `defineRole()` | Definice rolí |
|
||||
| `defineField()` | Rozšíření polí u existujících objektů |
|
||||
|
||||
<Note>
|
||||
**Pojmenování souborů je flexibilní.** Detekce entit je založená na AST — SDK prochází vaše zdrojové soubory a hledá vzor `export default define<Entity>({...})`. Soubory a složky můžete organizovat, jak chcete. Seskupování podle typu entity (např. `logic-functions/`, `roles/`) je pouze konvence pro organizaci kódu, nikoli požadavek.
|
||||
</Note>
|
||||
|
||||
Příklad detekované entity:
|
||||
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
Pozdější příkazy přidají další soubory a složky:
|
||||
|
||||
* `yarn app:generate` vytvoří složku `generated/` (typovaný klient Twenty + typy pracovního prostoru).
|
||||
* `yarn app:create-entity` přidá soubory s definicemi entit do `src/app/` pro vaše vlastní objekty, funkce nebo role.
|
||||
l
|
||||
* `yarn entity:add` přidá soubory s definicemi entit do `src/` pro vaše vlastní objekty, funkce, frontové komponenty nebo role.
|
||||
|
||||
## Ověření
|
||||
|
||||
@@ -212,16 +184,18 @@ twenty-sdk poskytuje typované stavební bloky a pomocné funkce, které použí
|
||||
|
||||
### Pomocné funkce
|
||||
|
||||
SDK poskytuje čtyři pomocné funkce s vestavěnou validací pro definování entit vaší aplikace:
|
||||
SDK poskytuje pomocné funkce pro definování entit vaší aplikace. Jak je popsáno v [Detekce entit](#entity-detection), musíte použít `export default define<Entity>({...})`, aby byly vaše entity detekovány:
|
||||
|
||||
| Funkce | Účel |
|
||||
| ------------------ | ------------------------------------------------ |
|
||||
| `defineApp()` | Konfigurace metadat aplikace |
|
||||
| `defineObject()` | Definice vlastních objektů s poli |
|
||||
| `defineFunction()` | Definice serverless funkcí s obslužnými funkcemi |
|
||||
| `defineRole()` | Konfigurace oprávnění rolí a přístupu k objektům |
|
||||
| Funkce | Účel |
|
||||
| ------------------------ | ----------------------------------------------------------------- |
|
||||
| `defineApplication()` | Nakonfigurujte metadata aplikace (povinné, jedno na aplikaci) |
|
||||
| `defineObject()` | Definice vlastních objektů s poli |
|
||||
| `defineLogicFunction()` | Definice logických funkcí s obslužnými funkcemi |
|
||||
| `defineFrontComponent()` | Definujte frontendové komponenty pro vlastní uživatelské rozhraní |
|
||||
| `defineRole()` | Konfigurace oprávnění rolí a přístupu k objektům |
|
||||
| `defineField()` | Rozšiřte existující objekty o další pole |
|
||||
|
||||
Tyto funkce validují vaši konfiguraci za běhu a poskytují lepší automatické doplňování v IDE a lepší typovou bezpečnost.
|
||||
Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost.
|
||||
|
||||
### Definování objektů
|
||||
|
||||
@@ -302,79 +276,28 @@ Hlavní body:
|
||||
* Hodnota `universalIdentifier` musí být jedinečná a stabilní napříč nasazeními.
|
||||
* Každé pole vyžaduje `name`, `type`, `label` a svůj vlastní stabilní `universalIdentifier`.
|
||||
* Pole `fields` je volitelné — objekty můžete definovat i bez vlastních polí.
|
||||
* Nové objekty můžete vygenerovat pomocí `yarn app:create-entity`, který vás provede pojmenováním, poli a vztahy.
|
||||
* Nové objekty můžete vygenerovat pomocí `yarn entity:add`, který vás provede pojmenováním, poli a vztahy.
|
||||
|
||||
<Note>
|
||||
**Základní pole jsou vytvořena automaticky.** Když definujete vlastní objekt, Twenty automaticky přidá standardní pole jako `name`, `createdAt`, `updatedAt`, `createdBy`, `position` a `deletedAt`. Nemusíte je definovat v poli `fields` — přidejte pouze svá vlastní pole.
|
||||
</Note>
|
||||
|
||||
<Accordion title="Alternativa: Syntaxe založená na dekorátorech">
|
||||
Objekty můžete definovat také pomocí dekorátorů TypeScriptu. Tento přístup používá třídovou syntaxi s dekorátory `@Object`, `@Field` a `@Relation`:
|
||||
### Konfigurace aplikace (application-config.ts)
|
||||
|
||||
```typescript
|
||||
import {
|
||||
type AddressField,
|
||||
Field,
|
||||
FieldType,
|
||||
type FullNameField,
|
||||
Object,
|
||||
OnDeleteAction,
|
||||
Relation,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
import { type Note } from '../../generated';
|
||||
|
||||
@Object({
|
||||
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
|
||||
nameSingular: 'postCard',
|
||||
namePlural: 'postCards',
|
||||
labelSingular: 'Post card',
|
||||
labelPlural: 'Post cards',
|
||||
description: 'A post card object',
|
||||
icon: 'IconMail',
|
||||
})
|
||||
export class PostCard {
|
||||
@Field({
|
||||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||||
type: FieldType.TEXT,
|
||||
label: 'Content',
|
||||
description: "Postcard's content",
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
content: string;
|
||||
|
||||
@Relation({
|
||||
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
label: 'Notes',
|
||||
icon: 'IconComment',
|
||||
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
|
||||
onDelete: OnDeleteAction.CASCADE,
|
||||
})
|
||||
notes: Note[];
|
||||
}
|
||||
```
|
||||
|
||||
Poznámka: Přístup s dekorátory vyžaduje `experimentalDecorators` v konfiguraci TypeScriptu.
|
||||
</Accordion>
|
||||
|
||||
### Konfigurace aplikace (application.config.ts)
|
||||
|
||||
Každá aplikace má jeden soubor `application.config.ts`, který popisuje:
|
||||
Každá aplikace má jeden soubor `application-config.ts`, který popisuje:
|
||||
|
||||
* **Identitu aplikace**: identifikátory, zobrazovaný název a popis.
|
||||
* **Jak běží její funkce**: kterou roli používají pro oprávnění.
|
||||
* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí.
|
||||
|
||||
K definování konfigurace aplikace použijte `defineApp()`:
|
||||
Use `defineApplication()` to define your application configuration:
|
||||
|
||||
```typescript
|
||||
// src/app/application.config.ts
|
||||
import { defineApp } from 'twenty-sdk';
|
||||
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
|
||||
export default defineApp({
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
@@ -387,7 +310,7 @@ export default defineApp({
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -395,11 +318,11 @@ Poznámky:
|
||||
|
||||
* Pole `universalIdentifier` jsou deterministická ID, která vlastníte; vygenerujte je jednou a udržujte je stabilní napříč synchronizacemi.
|
||||
* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `functionRoleUniversalIdentifier` se musí shodovat s rolí, kterou definujete ve svém souboru `*.role.ts` (viz níže).
|
||||
* `defaultRoleUniversalIdentifier` se musí shodovat se souborem role (viz níže).
|
||||
|
||||
#### Role a oprávnění
|
||||
|
||||
Aplikace mohou definovat role, které zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru. Pole `functionRoleUniversalIdentifier` v `application.config.ts` určuje výchozí roli používanou serverless funkcemi vaší aplikace.
|
||||
Aplikace mohou definovat role, které zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru. Pole `defaultRoleUniversalIdentifier` v `application-config.ts` určuje výchozí roli používanou logickými funkcemi vaší aplikace.
|
||||
|
||||
* Běhový klíč API vložený jako `TWENTY_API_KEY` je odvozen z této výchozí role funkcí.
|
||||
* Typovaný klient bude omezen oprávněními udělenými této roli.
|
||||
@@ -410,14 +333,14 @@ Aplikace mohou definovat role, které zapouzdřují oprávnění k objektům a a
|
||||
Když vygenerujete novou aplikaci, CLI také vytvoří výchozí soubor role. K definování rolí s vestavěnou validací použijte `defineRole()`:
|
||||
|
||||
```typescript
|
||||
// src/app/default-function.role.ts
|
||||
// src/roles/default-role.ts
|
||||
import { defineRole, PermissionFlag } from 'twenty-sdk';
|
||||
|
||||
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Default function role',
|
||||
description: 'Default role for function Twenty client',
|
||||
canReadAllObjectRecords: false,
|
||||
@@ -430,7 +353,7 @@ export default defineRole({
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
@@ -439,8 +362,8 @@ export default defineRole({
|
||||
],
|
||||
fieldPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
fieldName: 'content',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
|
||||
canReadFieldValue: false,
|
||||
canUpdateFieldValue: false,
|
||||
},
|
||||
@@ -449,10 +372,10 @@ export default defineRole({
|
||||
});
|
||||
```
|
||||
|
||||
Na `universalIdentifier` této role se poté odkazuje v `application.config.ts` jako na `functionRoleUniversalIdentifier`. Jinými slovy:
|
||||
Na `universalIdentifier` této role se poté odkazuje v `application-config.ts` jako na `defaultRoleUniversalIdentifier`. Jinými slovy:
|
||||
|
||||
* **\*.role.ts** definuje, co může výchozí role funkce dělat.
|
||||
* **application.config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění.
|
||||
* **application-config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění.
|
||||
|
||||
Poznámky:
|
||||
|
||||
@@ -461,22 +384,17 @@ Poznámky:
|
||||
* `permissionFlags` řídí přístup k schopnostem na úrovni platformy. Držte je na minimu; přidávejte pouze to, co potřebujete.
|
||||
* Podívejte se na funkční příklad v aplikaci Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
|
||||
### Konfigurace serverless funkcí a vstupní bod
|
||||
### Konfigurace logických funkcí a vstupní bod
|
||||
|
||||
Každý soubor funkce používá `defineFunction()` k exportu konfigurace s obslužnou funkcí (handlerem) a volitelnými spouštěči. Pro automatickou detekci použijte příponu souboru `*.function.ts`.
|
||||
Každý soubor funkce používá `defineLogicFunction()` k exportu konfigurace s obslužnou funkcí (handlerem) a volitelnými spouštěči.
|
||||
|
||||
```typescript
|
||||
// src/app/createPostCard.function.ts
|
||||
import { defineFunction } from 'twenty-sdk';
|
||||
// src/app/createPostCard.logic-function.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
|
||||
import Twenty, { type Person } from '../../generated';
|
||||
import Twenty, { type Person } from '~/generated';
|
||||
|
||||
const handler = async (
|
||||
params:
|
||||
| RoutePayload
|
||||
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
|
||||
| CronPayload,
|
||||
) => {
|
||||
const handler = async (params: RoutePayload) => {
|
||||
const client = new Twenty(); // generated typed client
|
||||
const name = 'name' in params.queryStringParameters
|
||||
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
@@ -492,7 +410,7 @@ const handler = async (
|
||||
return result;
|
||||
};
|
||||
|
||||
export default defineFunction({
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'create-new-post-card',
|
||||
timeoutSeconds: 2,
|
||||
@@ -507,18 +425,18 @@ export default defineFunction({
|
||||
isAuthRequired: false,
|
||||
},
|
||||
// Cron trigger (CRON pattern)
|
||||
{
|
||||
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
type: 'cron',
|
||||
pattern: '0 0 1 1 *',
|
||||
},
|
||||
// {
|
||||
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
// type: 'cron',
|
||||
// pattern: '0 0 1 1 *',
|
||||
// },
|
||||
// Database event trigger
|
||||
{
|
||||
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
type: 'databaseEvent',
|
||||
eventName: 'person.updated',
|
||||
updatedFields: ['name'],
|
||||
},
|
||||
// {
|
||||
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
// type: 'databaseEvent',
|
||||
// eventName: 'person.updated',
|
||||
// updatedFields: ['name'],
|
||||
// },
|
||||
],
|
||||
});
|
||||
```
|
||||
@@ -565,10 +483,10 @@ Poznámky:
|
||||
**Jak migrovat existující funkce:** Aktualizujte svůj handler tak, aby destrukturoval z `event.body`, `event.queryStringParameters` nebo `event.pathParameters` místo přímo z objektu params.
|
||||
</Warning>
|
||||
|
||||
Když spouštěč trasy vyvolá vaši funkci, ta obdrží objekt `RoutePayload`, který odpovídá formátu AWS HTTP API v2. Importujte typ z `twenty-sdk`:
|
||||
Když spouštěč trasy vyvolá vaši logickou funkci, ta obdrží objekt `RoutePayload`, který odpovídá formátu AWS HTTP API v2. Importujte typ z `twenty-sdk`:
|
||||
|
||||
```typescript
|
||||
import { defineFunction, type RoutePayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
// Access request data
|
||||
@@ -595,10 +513,10 @@ Typ `RoutePayload` má následující strukturu:
|
||||
|
||||
### Přeposílání záhlaví HTTP
|
||||
|
||||
Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší serverless funkce **ne** předávají. Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`:
|
||||
Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší logické funkce **ne** předávají. Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`:
|
||||
|
||||
```typescript
|
||||
export default defineFunction({
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'webhook-handler',
|
||||
handler,
|
||||
@@ -633,23 +551,60 @@ const handler = async (event: RoutePayload) => {
|
||||
|
||||
Nové funkce můžete vytvářet dvěma způsoby:
|
||||
|
||||
* **Vygenerované**: Spusťte `yarn app:create-entity` a zvolte možnost přidat novou funkci. Tím se vygeneruje startovací soubor s obslužnou funkcí a konfigurací.
|
||||
* **Ruční**: Vytvořte nový soubor `*.function.ts` a použijte `defineFunction()` podle stejného vzoru.
|
||||
* **Vygenerované**: Spusťte `yarn entity:add` a zvolte možnost přidat novou logickou funkci. Tím se vygeneruje startovací soubor s obslužnou funkcí a konfigurací.
|
||||
* **Ruční**: Vytvořte nový soubor `*.logic-function.ts` a použijte `defineLogicFunction()` podle stejného vzoru.
|
||||
|
||||
### Frontendové komponenty
|
||||
|
||||
Frontendové komponenty vám umožňují vytvářet vlastní React komponenty, které se vykreslují v rozhraní Twenty. K definování komponent s vestavěnou validací použijte `defineFrontComponent()`:
|
||||
|
||||
```typescript
|
||||
// src/my-widget.front-component.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
const MyWidget = () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>My Custom Widget</h1>
|
||||
<p>This is a custom front component for Twenty.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
name: 'my-widget',
|
||||
description: 'A custom widget component',
|
||||
component: MyWidget,
|
||||
});
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
|
||||
* Frontendové komponenty jsou React komponenty, které se vykreslují v izolovaných kontextech v rámci Twenty.
|
||||
* Pro automatickou detekci použijte příponu souboru `*.front-component.tsx`.
|
||||
* Pole `component` odkazuje na vaši React komponentu.
|
||||
* Komponenty se během `yarn app:dev` automaticky sestaví a synchronizují.
|
||||
|
||||
Nové frontendové komponenty můžete vytvořit dvěma způsoby:
|
||||
|
||||
* **Vygenerované**: Spusťte `yarn entity:add` a zvolte možnost přidat novou frontendovou komponentu.
|
||||
* **Ruční**: Vytvořte nový soubor `*.front-component.tsx` a použijte `defineFrontComponent()`.
|
||||
|
||||
### Generovaný typovaný klient
|
||||
|
||||
Spusťte yarn app:generate a vytvořte lokálního typovaného klienta v generated/ na základě schématu vašeho pracovního prostoru. Použijte jej ve svých funkcích:
|
||||
|
||||
```typescript
|
||||
import Twenty from './generated';
|
||||
import Twenty from '~/generated';
|
||||
|
||||
const client = new Twenty();
|
||||
const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
```
|
||||
|
||||
Klient je znovu generován příkazem `yarn app:generate`. Spusťte jej znovu po změně objektů a po `yarn app:sync`, případně při připojení k novému pracovnímu prostoru.
|
||||
Klient je znovu generován příkazem `yarn app:generate`. Spusťte znovu po změně vašich objektů nebo při připojování k novému pracovnímu prostoru.
|
||||
|
||||
#### Běhové přihlašovací údaje v serverless funkcích
|
||||
#### Běhové přihlašovací údaje v logických funkcích
|
||||
|
||||
Když vaše funkce běží na Twenty, platforma před spuštěním kódu vloží přihlašovací údaje jako proměnné prostředí:
|
||||
|
||||
@@ -659,12 +614,12 @@ Když vaše funkce běží na Twenty, platforma před spuštěním kódu vloží
|
||||
Poznámky:
|
||||
|
||||
* Není nutné předávat URL ani klíč API vygenerovanému klientovi. Za běhu čte `TWENTY_API_URL` a `TWENTY_API_KEY` z process.env.
|
||||
* Oprávnění API klíče jsou určena rolí odkazovanou v `application.config.ts` prostřednictvím `functionRoleUniversalIdentifier`. Toto je výchozí role používaná serverless funkcemi vaší aplikace.
|
||||
* Aplikace mohou definovat role podle principu nejmenších oprávnění. Udělte pouze oprávnění, která vaše funkce potřebují, a poté nastavte `functionRoleUniversalIdentifier` na univerzální identifikátor této role.
|
||||
* Oprávnění klíče API jsou určena rolí odkazovanou ve vašem `application-config.ts` prostřednictvím `defaultRoleUniversalIdentifier`. Toto je výchozí role používaná logickými funkcemi vaší aplikace.
|
||||
* Aplikace mohou definovat role podle principu nejmenších oprávnění. Udělte pouze oprávnění, která vaše funkce potřebují, a poté nastavte `defaultRoleUniversalIdentifier` na univerzální identifikátor této role.
|
||||
|
||||
### Příklad Hello World
|
||||
|
||||
Prozkoumejte minimalistický end-to-end příklad, který demonstruje objekty, funkce a více spouštěčů [zde](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
Prozkoumejte minimalistický end-to-end příklad, který demonstruje objekty, logické funkce, frontendové komponenty a více spouštěčů [zde](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
|
||||
## Ruční nastavení (bez scaffolderu)
|
||||
|
||||
@@ -679,25 +634,29 @@ Poté přidejte skripty jako tyto:
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"auth": "twenty auth login",
|
||||
"generate": "twenty app generate",
|
||||
"dev": "twenty app dev",
|
||||
"sync": "twenty app sync",
|
||||
"uninstall": "twenty app uninstall",
|
||||
"logs": "twenty app logs",
|
||||
"create-entity": "twenty app add",
|
||||
"help": "twenty --help"
|
||||
"auth:login": "twenty auth:login",
|
||||
"auth:logout": "twenty auth:logout",
|
||||
"auth:status": "twenty auth:status",
|
||||
"auth:switch": "twenty auth:switch",
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"app:generate": "twenty app:generate",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
"entity:add": "twenty entity:add",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"help": "twenty help"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Nyní můžete spouštět stejné příkazy přes Yarn, např. `yarn app:dev`, `yarn app:sync` atd.
|
||||
Nyní můžete spouštět stejné příkazy přes Yarn, např. `yarn app:dev`, `yarn app:generate` atd.
|
||||
|
||||
## Řešení potíží
|
||||
|
||||
* Chyby ověření: spusťte `yarn auth:login` a ujistěte se, že váš klíč API má požadovaná oprávnění.
|
||||
* Nelze se připojit k serveru: ověřte URL API a že je server Twenty dosažitelný.
|
||||
* Typy nebo klient chybí/jsou zastaralé: spusťte `yarn app:generate` a poté `yarn app:dev`.
|
||||
* Typy nebo klient chybí nebo jsou zastaralé: spusťte `yarn app:generate`.
|
||||
* Režim vývoje nesynchronizuje: ujistěte se, že běží `yarn app:dev` a že vaše prostředí změny neignoruje.
|
||||
|
||||
Kanál podpory na Discordu: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
|
||||
@@ -292,19 +292,19 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**Režim pouze s prostředím:** Pokud nastavíte `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, přidejte tyto proměnné do souboru `.env`
|
||||
</Warning>
|
||||
|
||||
## Serverless funkce
|
||||
## Logické funkce
|
||||
|
||||
Twenty podporuje serverless funkce pro pracovní postupy a vlastní logiku. Běhové prostředí se konfiguruje pomocí proměnné prostředí `SERVERLESS_TYPE`.
|
||||
Twenty podporuje logické funkce pro pracovní postupy a vlastní logiku. Běhové prostředí se konfiguruje pomocí proměnné prostředí `SERVERLESS_TYPE`.
|
||||
|
||||
<Warning>
|
||||
**Upozornění na zabezpečení:** Místní serverless ovladač (`SERVERLESS_TYPE=LOCAL`) spouští kód přímo na hostiteli v procesu Node.js bez sandboxu. Měl by být používán pouze pro důvěryhodný kód při vývoji. Pro produkční nasazení, která pracují s nedůvěryhodným kódem, důrazně doporučujeme použít `SERVERLESS_TYPE=LAMBDA` nebo `SERVERLESS_TYPE=DISABLED`.
|
||||
**Upozornění na zabezpečení:** Místní ovladač (`SERVERLESS_TYPE=LOCAL`) spouští kód přímo na hostiteli v procesu Node.js bez sandboxu. Měl by být používán pouze pro důvěryhodný kód při vývoji. Pro produkční nasazení, která pracují s nedůvěryhodným kódem, důrazně doporučujeme použít `SERVERLESS_TYPE=LAMBDA` nebo `SERVERLESS_TYPE=DISABLED`.
|
||||
</Warning>
|
||||
|
||||
### Dostupné ovladače
|
||||
|
||||
| Ovladač | Proměnná prostředí | Případ použití | Úroveň zabezpečení |
|
||||
| --------- | -------------------------- | ------------------------------------------ | ----------------------------------- |
|
||||
| Neaktivní | `SERVERLESS_TYPE=DISABLED` | Úplně zakázat serverless funkce | N/A |
|
||||
| Neaktivní | `SERVERLESS_TYPE=DISABLED` | Úplně zakázat logické funkce | N/A |
|
||||
| Lokální | `SERVERLESS_TYPE=LOCAL` | Vývojová a důvěryhodná prostředí | Nízká (bez sandboxu) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Produkční prostředí s nedůvěryhodným kódem | Vysoká (izolace na úrovni hardwaru) |
|
||||
|
||||
@@ -326,12 +326,12 @@ SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**Pro zakázání serverless funkcí:**
|
||||
**Pro zakázání logických funkcí:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
```
|
||||
|
||||
<Note>
|
||||
Při použití `SERVERLESS_TYPE=DISABLED` skončí každý pokus o spuštění serverless funkce chybou. To je užitečné, pokud chcete provozovat Twenty bez podpory serverless funkcí.
|
||||
Při použití `SERVERLESS_TYPE=DISABLED` skončí každý pokus o spuštění logické funkce chybou. To je užitečné, pokud chcete provozovat Twenty bez podpory logických funkcí.
|
||||
</Note>
|
||||
|
||||
@@ -59,4 +59,4 @@ To zajistí, že AI agenti budou respektovat vaše zásady správy dat a budou p
|
||||
Tuto sekci budeme aktualizovat, jakmile budou funkce AI k dispozici. Mezitím:
|
||||
|
||||
* Sledujte náš [GitHub](https://github.com/twentyhq/twenty) pro novinky o vývoji
|
||||
* Přidejte se na náš [Discord](https://discord.gg/twenty) a podělte se o zpětnou vazbu a návrhy na nové funkce
|
||||
* Přidejte se na náš [Discord](https://discord.gg/UfGNZJfAG6) a podělte se o zpětnou vazbu a návrhy na nové funkce
|
||||
|
||||
@@ -39,12 +39,16 @@ Mnoho záznamů v Objektu A může být propojeno s mnoha záznamy v Objektu B.
|
||||
|
||||
**Příklad:** Mnoho lidí může být propojeno s mnoha projekty a naopak.
|
||||
|
||||
<Warning>
|
||||
**Mnoho k mnoha zatím není podporováno.**
|
||||
Vztahy typu mnoho-na-mnoho používají vzor zvaný **spojovací objekt**: zprostředkující objekt, který propojuje obě strany. S funkcí spojovacího vztahu zobrazuje Twenty přímo výsledné propojené záznamy a zprostředkující objekt v UI skrývá.
|
||||
|
||||
Tento typ relace je plánován na 1. pololetí 2026. Jako dočasné řešení vytvořte prostřední "spojovací" objekt (např. "Project Assignments"), který má relace typu mnoho k jednomu k oběma objektům.
|
||||
<img src="/images/user-guide/fields/junction-relation-diagram.png" style={{width:'100%'}} />
|
||||
|
||||
<Warning>
|
||||
**Experimentální funkce**: Vztahy přes spojovací objekt je před použitím nutné povolit v **Settings → Updates → Lab**.
|
||||
</Warning>
|
||||
|
||||
Podívejte se na [Jak vytvořit vztahy typu mnoho k mnoha](/l/cs/user-guide/data-model/how-tos/create-many-to-many-relations) pro úplný návod krok za krokem.
|
||||
|
||||
## Vytvoření relačního pole
|
||||
|
||||
1. Přejděte do **Nastavení → Datový model**
|
||||
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
---
|
||||
title: Vytváření vztahů mnoho-na-mnoho
|
||||
description: Propojte záznamy, kde lze mnoho položek na obou stranách vzájemně propojit pomocí spojovacích objektů.
|
||||
---
|
||||
|
||||
Vztahy mnoho-na-mnoho vám umožňují propojit více záznamů na obou stranách. Například: mnoho lidí může pracovat na mnoha projektech a každý projekt může mít mnoho lidí.
|
||||
|
||||
<Warning>
|
||||
**Lab Feature**: Vztahy přes spojovací objekt jsou momentálně v Lab. Povolte je v **Settings → Updates → Lab** předtím, než budete pokračovat podle tohoto návodu.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
Tato funkce také vyžaduje, aby byl povolen **Advanced mode** (přepínač je vpravo dole v Settings).
|
||||
</Note>
|
||||
|
||||
## Kdy použít mnoho-na-mnoho
|
||||
|
||||
Použijte mnoho-na-mnoho, když obě strany vztahu mohou mít více propojení:
|
||||
|
||||
| Vztah | Příklad |
|
||||
| --------------------- | --------------------------------------------------------------------------- |
|
||||
| Lidé ↔ Projekty | Osoba pracuje na více projektech; projekt má více členů týmu |
|
||||
| Společnosti ↔ Štítky | Společnost může mít více štítků; štítek může platit pro více společností |
|
||||
| Produkty ↔ Objednávky | Produkt může být v několika objednávkách; objednávka obsahuje více produktů |
|
||||
|
||||
## Jak to funguje
|
||||
|
||||
Twenty používá u vztahů mnoho-na-mnoho vzor **spojovacího objektu**. Spojovací objekt leží mezi dvěma objekty a uchovává propojení:
|
||||
|
||||
```
|
||||
People ←→ Project Assignments ←→ Projects
|
||||
```
|
||||
|
||||
Objekt **Project Assignments** (spojovací) má:
|
||||
|
||||
* Vztah na People (mnoho k jednomu)
|
||||
* Vztah na Projects (mnoho k jednomu)
|
||||
|
||||
Když povolíte přepínač pro vztah přes spojovací objekt, Twenty zobrazí propojené záznamy přímo místo zobrazení prostředních záznamů spojovacího objektu.
|
||||
|
||||
## Předpoklady
|
||||
|
||||
1. **Povolte Junction Relations v Lab**: Přejděte do **Settings → Updates → Lab** a povolte **Junction Relations**
|
||||
2. **Povolte Advanced mode**: Zapněte **Advanced mode** vpravo dole na postranním panelu Settings
|
||||
3. Naplánujte svůj datový model:
|
||||
* Které dva objekty propojujete?
|
||||
* Jak by se měl jmenovat spojovací objekt?
|
||||
|
||||
## Krok 1: Vytvořte spojovací objekt
|
||||
|
||||
Nejprve vytvořte mezilehlý objekt, který bude uchovávat propojení.
|
||||
|
||||
1. Přejděte do **Nastavení → Datový model**
|
||||
2. Klikněte na **+ New object**
|
||||
3. Pojmenujte jej výstižně (např. "Project Assignment", "Team Member", "Product Order")
|
||||
4. Klikněte na **Uložit**
|
||||
|
||||
<Tip>
|
||||
**Konvence pojmenování**: Použijte název, který popisuje vztah, například "Project Assignment" nebo "Team Membership". Tím bude datový model srozumitelnější.
|
||||
</Tip>
|
||||
|
||||
## Krok 2: Vytvořte vztahy ze spojovacího objektu
|
||||
|
||||
Přidejte relační pole ze spojovacího objektu do obou objektů, které chcete propojit.
|
||||
|
||||
### První vztah (Junction → Objekt A)
|
||||
|
||||
1. Vyberte svůj spojovací objekt v **Settings → Data Model**
|
||||
2. Klikněte na **+ Add Field**
|
||||
3. Zvolte **Relation** jako typ pole
|
||||
4. Vyberte první objekt (např. "People")
|
||||
5. Nastavte typ vztahu na **Many-to-One** (mnoho přiřazení může odkazovat na jednu osobu)
|
||||
6. Pojmenujte pole:
|
||||
* Pole na spojovacím objektu: např. "Person"
|
||||
* Pole na People: např. "Project Assignments"
|
||||
7. Klikněte na **Uložit**
|
||||
|
||||
### Druhý vztah (Junction → Objekt B)
|
||||
|
||||
1. Stále na spojovacím objektu klikněte na **+ Add Field**
|
||||
2. Zvolte **Relation** jako typ pole
|
||||
3. Vyberte druhý objekt (např. "Projects")
|
||||
4. Nastavte typ vztahu na **Many-to-One**
|
||||
5. Pojmenujte pole:
|
||||
* Pole na spojovacím objektu: např. "Project"
|
||||
* Pole na Projects: např. "Team Members"
|
||||
6. Klikněte na **Uložit**
|
||||
|
||||
## Krok 3: Nakonfigurujte zobrazení vztahu přes spojovací objekt
|
||||
|
||||
Nyní nakonfigurujte zdrojové objekty tak, aby zobrazovaly propojené záznamy přímo a přeskočily mezilehlý spojovací objekt.
|
||||
|
||||
1. Přejděte do **Nastavení → Datový model**
|
||||
2. Vyberte první objekt (např. "People")
|
||||
3. Najděte relační pole směřující na spojovací objekt (např. "Project Assignments")
|
||||
4. Klikněte pro úpravu pole
|
||||
5. Povolte **"This is a relation to a Junction Object"**
|
||||
6. Vyberte **Target relation** (např. "Project" — pole na spojovacím objektu, které ukazuje na druhou stranu)
|
||||
7. Klikněte na **Uložit**
|
||||
|
||||
{/* TODO: Add image
|
||||
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
|
||||
*/}
|
||||
|
||||
Opakujte pro druhý objekt:
|
||||
|
||||
1. Vyberte "Projects" v Data Model
|
||||
2. Upravte relační pole "Team Members"
|
||||
3. Povolte přepínač pro vztah přes spojovací objekt
|
||||
4. Vyberte "Person" jako cílový vztah
|
||||
5. Uložit
|
||||
|
||||
## Výsledek
|
||||
|
||||
Po konfiguraci:
|
||||
|
||||
* Na záznamu **Person** pole "Project Assignments" zobrazuje přímo **Projects** (nikoli záznamy přiřazení)
|
||||
* Na záznamu **Project** pole "Team Members" zobrazuje přímo **People**
|
||||
|
||||
Spojovací objekt stále existuje a ukládá propojení, ale rozhraní zobrazuje čistší pohled mnoho-na-mnoho.
|
||||
|
||||
## Příklad: Lidé ↔ Projekty
|
||||
|
||||
Zde je kompletní postup:
|
||||
|
||||
### Vytvořte spojovací objekt
|
||||
|
||||
* Název: **Project Assignment**
|
||||
* Popis: "Propojuje lidi s projekty, na kterých pracují"
|
||||
|
||||
### Přidejte vztahy
|
||||
|
||||
1. **Project Assignment → People**
|
||||
* Typ: Many-to-One
|
||||
* Pole na Assignment: "Person"
|
||||
* Pole na People: "Project Assignments"
|
||||
|
||||
2. **Project Assignment → Projects**
|
||||
* Typ: Many-to-One
|
||||
* Pole na Assignment: "Project"
|
||||
* Pole na Projects: "Team Members"
|
||||
|
||||
### Nakonfigurujte zobrazení spojovacího objektu
|
||||
|
||||
1. Na objektu **People**:
|
||||
* Upravte pole "Project Assignments"
|
||||
* Povolte přepínač pro vztah přes spojovací objekt
|
||||
* Cíl: "Project"
|
||||
|
||||
2. Na objektu **Projects**:
|
||||
* Upravte pole "Team Members"
|
||||
* Povolte přepínač pro vztah přes spojovací objekt
|
||||
* Cíl: "Person"
|
||||
|
||||
### Použití
|
||||
|
||||
* Otevřete záznam osoby → Uvidíte její projekty přímo
|
||||
* Otevřete záznam projektu → Uvidíte členy týmu přímo
|
||||
* Vytvářejte nová propojení z obou stran
|
||||
|
||||
## Přidávání doplňkových dat do propojení
|
||||
|
||||
Protože spojovací objekt je skutečný objekt, můžete přidat vlastní pole k ukládání informací o vztahu:
|
||||
|
||||
* **Role**: "Developer", "Designer", "Manager"
|
||||
* **Start Date**: Kdy se k projektu připojili
|
||||
* **Hours Allocated**: Týdenní počet hodin na tomto projektu
|
||||
|
||||
Pro přístup k těmto datům přejděte přímo na spojovací objekt nebo jej dotazujte přes API.
|
||||
|
||||
## Omezení
|
||||
|
||||
* **CSV Import/Export**: Přímý import vztahů mnoho-na-mnoho není podporován. Místo toho importujte záznamy do spojovacího objektu.
|
||||
* **Filters**: Filtrování podle vztahů mnoho-na-mnoho může mít omezené možnosti.
|
||||
|
||||
## Související
|
||||
|
||||
* [Relační pole](/l/cs/user-guide/data-model/capabilities/relation-fields) — vysvětlení typů vztahů
|
||||
* [Vytváření vlastních objektů](/l/cs/user-guide/data-model/how-tos/create-custom-objects) — jak vytvářet objekty
|
||||
* [Vytváření relačních polí](/l/cs/user-guide/data-model/how-tos/create-relation-fields) — základní nastavení vztahů
|
||||
@@ -9,7 +9,7 @@ API (rozhraní pro programování aplikací) umožňuje propojení Twenty s dal
|
||||
|
||||
## Aplikace
|
||||
|
||||
Aplikace jsou vlastní rozšíření vytvořená jako kód, která mohou definovat datové modely a bezserverové funkce. Umožňují vývojářům vytvářet znovupoužitelné přizpůsobení, které lze nasadit do více pracovních prostorů.
|
||||
Aplikace jsou vlastní rozšíření vytvořená jako kód, která mohou definovat datové modely a logické funkce. Umožňují vývojářům vytvářet znovupoužitelné přizpůsobení, které lze nasadit do více pracovních prostorů.
|
||||
|
||||
## Akce kódu
|
||||
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ K e-mailům odesílaným z pracovních postupů můžete přikládat soubory. P
|
||||
| **Potvrzení událostí** | Podrobnosti o události nebo program |
|
||||
|
||||
<Note>
|
||||
Přílohy jsou statické—všem příjemcům se posílá stejný soubor. U dynamických dokumentů (například personalizovaných nabídek) soubory vygenerujte a přiložte pomocí [Serverless funkce](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
|
||||
Přílohy jsou statické—všem příjemcům se posílá stejný soubor. U dynamických dokumentů (například personalizovaných nabídek) soubory vygenerujte a přiložte pomocí [Logic funkce](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
|
||||
</Note>
|
||||
|
||||
## Osvědčené postupy
|
||||
|
||||
@@ -256,7 +256,7 @@ Spouští vlastní JavaScript ve vašem pracovním postupu.
|
||||
* Testovat kód přímo ve kroku
|
||||
|
||||
<Note>
|
||||
Pokud potřebujete ve svém kódu použít externí klíče API, musíte je zadat přímo do těla funkce. Klíče API nelze nakonfigurovat jinde a odkazovat na ně v bezserverové funkci.
|
||||
Pokud potřebujete ve svém kódu použít externí klíče API, musíte je zadat přímo do těla funkce. You cannot configure API keys elsewhere and reference them in the logic function.
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
|
||||
+9
-9
@@ -7,7 +7,7 @@ Automaticky vygenerujte nebo získejte PDF a připojte ho k záznamu v Twenty. T
|
||||
|
||||
## Přehled
|
||||
|
||||
Tento pracovní postup používá **Ruční spouštěč**, takže uživatelé mohou na požádání vygenerovat PDF pro libovolný vybraný záznam. O zpracování se stará **Serverless funkce**:
|
||||
Tento pracovní postup používá **Ruční spouštěč**, takže uživatelé mohou na požádání vygenerovat PDF pro libovolný vybraný záznam. O zpracování se stará **logická funkce**:
|
||||
|
||||
1. Stažení PDF z adresy URL (ze služby pro generování PDF)
|
||||
2. Nahrání souboru do Twenty
|
||||
@@ -17,7 +17,7 @@ Tento pracovní postup používá **Ruční spouštěč**, takže uživatelé mo
|
||||
|
||||
Než nastavíte pracovní postup:
|
||||
|
||||
1. **Vytvořte klíč API**: Přejděte do **Nastavení → API** a vytvořte nový klíč API. Tento token budete potřebovat pro serverless funkci.
|
||||
1. **Vytvořte klíč API**: Přejděte do **Nastavení → API** a vytvořte nový klíč API. Tento token budete potřebovat pro logickou funkci.
|
||||
2. **Nastavte službu pro generování PDF** (volitelné): Pokud chcete dynamicky generovat PDF (např. nabídky), použijte službu jako Carbone, PDFMonkey nebo DocuSeal k vytvoření PDF a získání adresy URL pro stažení.
|
||||
|
||||
## Nastavení krok za krokem
|
||||
@@ -32,9 +32,9 @@ Než nastavíte pracovní postup:
|
||||
S **Ručním spouštěčem** mohou uživatelé spustit tento pracovní postup pomocí tlačítka, které se zobrazí vpravo nahoře po výběru záznamu, aby vygenerovali a připojili PDF.
|
||||
</Tip>
|
||||
|
||||
### Krok 2: Přidejte serverless funkci
|
||||
### Krok 2: Přidejte logickou funkci
|
||||
|
||||
1. Přidejte akci **Serverless funkce**
|
||||
1. Přidejte akci **Code** (logická funkce)
|
||||
2. Vytvořte novou funkci pomocí kódu níže
|
||||
3. Nakonfigurujte vstupní parametry
|
||||
|
||||
@@ -45,10 +45,10 @@ Než nastavíte pracovní postup:
|
||||
| `companyId` | `{{trigger.object.id}}` |
|
||||
|
||||
<Note>
|
||||
Pokud připojujete k jinému objektu (Osoba, Příležitost apod.), přejmenujte parametr podle toho (např. `personId`, `opportunityId`) a aktualizujte serverless funkci.
|
||||
Pokud připojujete k jinému objektu (Osoba, Příležitost apod.), přejmenujte parametr podle toho (např. `personId`, `opportunityId`) a aktualizujte logickou funkci.
|
||||
</Note>
|
||||
|
||||
#### Kód serverless funkce
|
||||
#### Kód logické funkce
|
||||
|
||||
```typescript
|
||||
export const main = async (
|
||||
@@ -172,7 +172,7 @@ Aktualizujte jak parametr funkce, tak objekt `variables.data` v mutaci přílohy
|
||||
Pokud používáte službu pro generování PDF, můžete:
|
||||
|
||||
1. Nejprve proveďte akci HTTP Request pro vygenerování PDF
|
||||
2. Předejte vrácenou adresu URL PDF serverless funkci jako parametr
|
||||
2. Předejte vrácenou adresu URL PDF logické funkci jako parametr
|
||||
|
||||
```typescript
|
||||
export const main = async (
|
||||
@@ -211,7 +211,7 @@ Pro vytváření dynamických nabídek nebo faktur:
|
||||
* **DocuSeal** - Platforma pro automatizaci dokumentů
|
||||
* **Documint** - Generování dokumentů primárně přes API
|
||||
|
||||
Každá služba poskytuje API, které vrací adresu URL PDF, kterou pak můžete předat serverless funkci.
|
||||
Každá služba poskytuje API, které vrací adresu URL PDF, kterou pak můžete předat logické funkci.
|
||||
|
||||
## Řešení potíží
|
||||
|
||||
@@ -224,5 +224,5 @@ Každá služba poskytuje API, které vrací adresu URL PDF, kterou pak můžete
|
||||
## Související
|
||||
|
||||
* [Spouštěče pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-triggers)
|
||||
* [Serverless funkce](/l/cs/user-guide/workflows/capabilities/workflow-actions#serverless-function)
|
||||
* [Logické funkce](/l/cs/user-guide/workflows/capabilities/workflow-actions#code)
|
||||
* [Vygenerujte nabídku nebo fakturu z Twenty](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ description: Často kladené otázky k pracovním postupům v Twenty.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Jaký je maximální čas spuštění pro akce Code?">
|
||||
Akce Code (serverless funkce) mají **výchozí časový limit 5 minut** (300 sekund).
|
||||
Akce Code (logické funkce) mají **výchozí časový limit 5 minut** (300 sekund).
|
||||
|
||||
Maximální nastavitelný časový limit je **15 minut** (900 sekund).
|
||||
|
||||
|
||||
@@ -170,6 +170,13 @@ Alle folgenden Befehle innerhalb des Projekts sind vom Stammverzeichnis aus ausz
|
||||
|
||||
Dadurch wird eine Superuser-Rolle namens `postgres` mit Anmeldezugriff erstellt.
|
||||
|
||||
```bash
|
||||
Rollenname | Attribute | Mitglied von
|
||||
-----------+-------------+-----------
|
||||
postgres | Superuser | {}
|
||||
john | Superuser | {}
|
||||
```
|
||||
|
||||
**Option 2:** Wenn Sie Docker installiert haben:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -9,18 +9,14 @@ description: Twenty-Anpassungen als Code erstellen und verwalten.
|
||||
|
||||
## Was sind Apps?
|
||||
|
||||
Mit Apps können Sie Twenty-Anpassungen **als Code** erstellen und verwalten. Anstatt alles über die UI zu konfigurieren, definieren Sie Ihr Datenmodell und serverlose Funktionen im Code — das beschleunigt Entwicklung, Wartung und Rollout auf mehrere Workspaces.
|
||||
Mit Apps können Sie Twenty-Anpassungen **als Code** erstellen und verwalten. Anstatt alles über die UI zu konfigurieren, definieren Sie Ihr Datenmodell und Logikfunktionen im Code — das beschleunigt Entwicklung, Wartung und den Rollout auf mehrere Workspaces.
|
||||
|
||||
**Was Sie heute tun können:**
|
||||
|
||||
* Benutzerdefinierte Objekte und Felder als Code definieren (verwaltetes Datenmodell)
|
||||
* Serverlose Funktionen mit benutzerdefinierten Triggern erstellen
|
||||
* Logikfunktionen mit benutzerdefinierten Triggern erstellen
|
||||
* Dieselbe App in mehreren Workspaces bereitstellen
|
||||
|
||||
**Bald verfügbar:**
|
||||
|
||||
* Benutzerdefinierte UI-Layouts und Komponenten
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
* Node.js 24+ und Yarn 4
|
||||
@@ -49,26 +45,23 @@ yarn app:dev
|
||||
Von hier aus können Sie:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn app:create-entity
|
||||
# Eine neue Entität zu deiner Anwendung hinzufügen (geführt)
|
||||
yarn entity:add
|
||||
|
||||
# Generate a typed Twenty client and workspace entity types
|
||||
# Einen typisierten Twenty-Client und Entitätstypen für den Arbeitsbereich generieren
|
||||
yarn app:generate
|
||||
|
||||
# Run a one‑time sync (instead of watch mode)
|
||||
yarn app:sync
|
||||
|
||||
# Watch your application's functions logs
|
||||
# Die Funktionsprotokolle deiner Anwendung überwachen
|
||||
yarn function:logs
|
||||
|
||||
# Execute a function by name
|
||||
# Eine Funktion anhand ihres Namens ausführen
|
||||
yarn function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
# Die Anwendung aus dem aktuellen Arbeitsbereich deinstallieren
|
||||
yarn app:uninstall
|
||||
|
||||
# Display commands' help
|
||||
yarn app:help
|
||||
# Hilfe zu Befehlen anzeigen
|
||||
yarn help
|
||||
```
|
||||
|
||||
Siehe auch: die CLI-Referenzseiten für [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) und [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
@@ -96,82 +89,61 @@ my-twenty-app/
|
||||
eslint.config.mjs
|
||||
tsconfig.json
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
src/
|
||||
app/
|
||||
application.config.ts # Required - main application configuration
|
||||
default-function.role.ts # Default role for serverless functions
|
||||
// your entities (*.object.ts, *.function.ts, *.role.ts)
|
||||
utils/ # Optional - handler implementations & utilities
|
||||
```
|
||||
|
||||
### Konvention vor Konfiguration
|
||||
|
||||
Anwendungen verwenden einen Ansatz **Konvention vor Konfiguration**, bei dem Entitäten anhand ihrer Dateiendung erkannt werden. Dies ermöglicht eine flexible Organisation im Ordner `src/app/`:
|
||||
|
||||
| Dateiendung | Entitätstyp |
|
||||
| --------------- | ------------------------------------- |
|
||||
| `*.object.ts` | Benutzerdefinierte Objektdefinitionen |
|
||||
| `*.function.ts` | Definitionen serverloser Funktionen |
|
||||
| `*.role.ts` | Rollendefinitionen |
|
||||
|
||||
### Unterstützte Ordnerorganisationen
|
||||
|
||||
Sie können Ihre Entitäten nach einem der folgenden Muster organisieren:
|
||||
|
||||
**Traditionell (nach Typ):**
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
├── objects/
|
||||
│ └── postCard.object.ts
|
||||
├── functions/
|
||||
│ └── createPostCard.function.ts
|
||||
└── roles/
|
||||
└── admin.role.ts
|
||||
```
|
||||
|
||||
**Feature-basiert:**
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
└── post-card/
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
└── postCardAdmin.role.ts
|
||||
```
|
||||
|
||||
**Flach:**
|
||||
|
||||
```text
|
||||
src/app/
|
||||
├── application.config.ts
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
└── admin.role.ts
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── logic-functions/
|
||||
│ └── hello-world.ts # Example logic function
|
||||
└── front-components/
|
||||
└── hello-world.tsx # Example front component
|
||||
```
|
||||
|
||||
Auf hoher Ebene:
|
||||
|
||||
* **package.json**: Deklariert App-Name, Version, Engines (Node 24+, Yarn 4) und fügt `twenty-sdk` sowie Skripte wie `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall` und `auth` hinzu, die an die lokale `twenty`-CLI delegieren.
|
||||
* **package.json**: Deklariert den App-Namen, die Version, Engines (Node 24+, Yarn 4) und fügt `twenty-sdk` sowie Skripte wie `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall` sowie Authentifizierungsbefehle hinzu, die an die lokale `twenty`-CLI delegieren.
|
||||
* **.gitignore**: Ignoriert übliche Artefakte wie `node_modules`, `.yarn`, `generated/` (typisierter Client), `dist/`, `build/`, Coverage-Ordner, Logdateien und `.env*`-Dateien.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Fixieren und konfigurieren die vom Projekt verwendete Yarn-4-Toolchain.
|
||||
* **.nvmrc**: Legt die vom Projekt erwartete Node.js-Version fest.
|
||||
* **eslint.config.mjs** und **tsconfig.json**: Stellen Linting und TypeScript-Konfiguration für die TypeScript-Quellen Ihrer App bereit.
|
||||
* **README.md**: Ein kurzes README im App-Root mit grundlegenden Anweisungen.
|
||||
* **src/app/**: Der Hauptort, an dem Sie Ihre Anwendung als Code definieren:
|
||||
* `application.config.ts`: Globale Konfiguration für Ihre App (Metadaten und Laufzeit-Anbindung). Siehe unten „Anwendungskonfiguration“.
|
||||
* `*.role.ts`: Rollendefinitionen, die von Ihren serverlosen Funktionen verwendet werden. Siehe unten „Standard-Funktionsrolle“.
|
||||
* `*.object.ts`: Benutzerdefinierte Objektdefinitionen.
|
||||
* `*.function.ts`: Definitionen serverloser Funktionen.
|
||||
* **src/utils/**: Optionaler Ordner für Handler-Implementierungen und Utilities.
|
||||
* **public/**: Ein Ordner zum Speichern öffentlicher Assets (Bilder, Schriftarten, statische Dateien), die zusammen mit Ihrer Anwendung bereitgestellt werden. Hier abgelegte Dateien werden während der Synchronisierung hochgeladen und sind zur Laufzeit zugänglich.
|
||||
* **src/**: Der Hauptort, an dem Sie Ihre Anwendung als Code definieren
|
||||
|
||||
### Entitätserkennung
|
||||
|
||||
Das SDK erkennt Entitäten, indem es Ihre TypeScript-Dateien nach Aufrufen von **`export default define<Entity>({...})`** parst. Für jeden Entitätstyp gibt es eine entsprechende Hilfsfunktion, die aus `twenty-sdk` exportiert wird:
|
||||
|
||||
| Hilfsfunktion | Entitätstyp |
|
||||
| ------------------------ | ---------------------------------------- |
|
||||
| `defineObject()` | Benutzerdefinierte Objektdefinitionen |
|
||||
| `defineLogicFunction()` | Definitionen von Logikfunktionen |
|
||||
| `defineFrontComponent()` | Definitionen von Frontend-Komponenten |
|
||||
| `defineRole()` | Rollendefinitionen |
|
||||
| `defineField()` | Felderweiterungen für bestehende Objekte |
|
||||
|
||||
<Note>
|
||||
**Dateibenennung ist flexibel.** Die Entitätserkennung ist AST-basiert — das SDK durchsucht Ihre Quelldateien nach dem Muster `export default define<Entity>({...})`. Sie können Ihre Dateien und Ordner nach Belieben organisieren. Die Gruppierung nach Entitätstyp (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention zur Codeorganisation, keine Voraussetzung.
|
||||
</Note>
|
||||
|
||||
Beispiel für eine erkannte Entität:
|
||||
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
Spätere Befehle fügen weitere Dateien und Ordner hinzu:
|
||||
|
||||
* `yarn app:generate` erstellt einen `generated/`-Ordner (typisierter Twenty-Client + Workspace-Typen).
|
||||
* `yarn app:create-entity` fügt unter `src/app/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen oder Rollen hinzu.
|
||||
l
|
||||
* `yarn entity:add` fügt unter `src/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen, Front-Komponenten oder Rollen hinzu.
|
||||
|
||||
## Authentifizierung
|
||||
|
||||
@@ -212,16 +184,18 @@ Das twenty-sdk stellt typisierte Bausteine und Hilfsfunktionen bereit, die Sie i
|
||||
|
||||
### Hilfsfunktionen
|
||||
|
||||
Das SDK stellt vier Hilfsfunktionen mit eingebauter Validierung bereit, um Ihre App-Entitäten zu definieren:
|
||||
Das SDK stellt Hilfsfunktionen bereit, um die Entitäten Ihrer App zu definieren. Wie in [Entitätserkennung](#entity-detection) beschrieben, müssen Sie `export default define<Entity>({...})` verwenden, damit Ihre Entitäten erkannt werden:
|
||||
|
||||
| Funktion | Zweck |
|
||||
| ------------------ | ---------------------------------------------------- |
|
||||
| `defineApp()` | Anwendungsmetadaten konfigurieren |
|
||||
| `defineObject()` | Benutzerdefinierte Objekte mit Feldern definieren |
|
||||
| `defineFunction()` | Serverlose Funktionen mit Handlern definieren |
|
||||
| `defineRole()` | Rollenberechtigungen und Objektzugriff konfigurieren |
|
||||
| Funktion | Zweck |
|
||||
| ------------------------ | -------------------------------------------------------------- |
|
||||
| `defineApplication()` | Anwendungsmetadaten konfigurieren (erforderlich, eine pro App) |
|
||||
| `defineObject()` | Benutzerdefinierte Objekte mit Feldern definieren |
|
||||
| `defineLogicFunction()` | Logikfunktionen mit Handlern definieren |
|
||||
| `defineFrontComponent()` | Frontend-Komponenten für benutzerdefinierte UI definieren |
|
||||
| `defineRole()` | Rollenberechtigungen und Objektzugriff konfigurieren |
|
||||
| `defineField()` | Bestehende Objekte mit zusätzlichen Feldern erweitern |
|
||||
|
||||
Diese Funktionen validieren Ihre Konfiguration zur Laufzeit und bieten bessere IDE-Autovervollständigung sowie Typsicherheit.
|
||||
Diese Funktionen validieren Ihre Konfiguration zur Build-Zeit und bieten IDE-Autovervollständigung sowie Typsicherheit.
|
||||
|
||||
### Objekte definieren
|
||||
|
||||
@@ -302,79 +276,28 @@ Hauptpunkte:
|
||||
* Der `universalIdentifier` muss eindeutig und über Deployments hinweg stabil sein.
|
||||
* Jedes Feld benötigt `name`, `type`, `label` und einen eigenen stabilen `universalIdentifier`.
|
||||
* Das Array `fields` ist optional — Sie können Objekte ohne benutzerdefinierte Felder definieren.
|
||||
* Sie können mit `yarn app:create-entity` neue Objekte erzeugen; der Assistent führt Sie durch Benennung, Felder und Beziehungen.
|
||||
* Sie können mit `yarn entity:add` neue Objekte erzeugen; der Assistent führt Sie durch Benennung, Felder und Beziehungen.
|
||||
|
||||
<Note>
|
||||
**Basisfelder werden automatisch erstellt.** Wenn Sie ein benutzerdefiniertes Objekt definieren, fügt Twenty automatisch Standardfelder wie `name`, `createdAt`, `updatedAt`, `createdBy`, `position` und `deletedAt` hinzu. Sie müssen diese nicht in Ihrem `fields`-Array definieren — fügen Sie nur Ihre benutzerdefinierten Felder hinzu.
|
||||
</Note>
|
||||
|
||||
<Accordion title="Alternative: Dekoratorbasierte Syntax">
|
||||
Sie können Objekte auch mit TypeScript-Dekoratoren definieren. Dieser Ansatz verwendet klassenbasierte Syntax mit den Dekoratoren `@Object`, `@Field` und `@Relation`:
|
||||
### Anwendungskonfiguration (application-config.ts)
|
||||
|
||||
```typescript
|
||||
import {
|
||||
type AddressField,
|
||||
Field,
|
||||
FieldType,
|
||||
type FullNameField,
|
||||
Object,
|
||||
OnDeleteAction,
|
||||
Relation,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
import { type Note } from '../../generated';
|
||||
|
||||
@Object({
|
||||
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
|
||||
nameSingular: 'postCard',
|
||||
namePlural: 'postCards',
|
||||
labelSingular: 'Post card',
|
||||
labelPlural: 'Post cards',
|
||||
description: 'A post card object',
|
||||
icon: 'IconMail',
|
||||
})
|
||||
export class PostCard {
|
||||
@Field({
|
||||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||||
type: FieldType.TEXT,
|
||||
label: 'Content',
|
||||
description: "Postcard's content",
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
content: string;
|
||||
|
||||
@Relation({
|
||||
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
label: 'Notes',
|
||||
icon: 'IconComment',
|
||||
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
|
||||
onDelete: OnDeleteAction.CASCADE,
|
||||
})
|
||||
notes: Note[];
|
||||
}
|
||||
```
|
||||
|
||||
Hinweis: Der Dekorator-Ansatz erfordert `experimentalDecorators` in Ihrer TypeScript-Konfiguration.
|
||||
</Accordion>
|
||||
|
||||
### Anwendungskonfiguration (application.config.ts)
|
||||
|
||||
Jede App hat eine einzelne Datei `application.config.ts`, die Folgendes beschreibt:
|
||||
Jede App hat eine einzelne Datei `application-config.ts`, die Folgendes beschreibt:
|
||||
|
||||
* **Was die App ist**: Bezeichner, Anzeigename und Beschreibung.
|
||||
* **Wie ihre Funktionen ausgeführt werden**: welche Rolle sie für Berechtigungen verwenden.
|
||||
* **(Optional) Variablen**: Schlüssel–Wert-Paare, die Ihren Funktionen als Umgebungsvariablen zur Verfügung gestellt werden.
|
||||
|
||||
Verwenden Sie `defineApp()`, um Ihre Anwendungskonfiguration zu definieren:
|
||||
Verwenden Sie `defineApplication()`, um Ihre Anwendungskonfiguration zu definieren:
|
||||
|
||||
```typescript
|
||||
// src/app/application.config.ts
|
||||
import { defineApp } from 'twenty-sdk';
|
||||
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
|
||||
export default defineApp({
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
@@ -387,7 +310,7 @@ export default defineApp({
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -395,11 +318,11 @@ Notizen:
|
||||
|
||||
* `universalIdentifier`-Felder sind deterministische IDs, die Sie besitzen; generieren Sie sie einmal und halten Sie sie über Synchronisierungen hinweg stabil.
|
||||
* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen (zum Beispiel ist `DEFAULT_RECIPIENT_NAME` als `process.env.DEFAULT_RECIPIENT_NAME` verfügbar).
|
||||
* `functionRoleUniversalIdentifier` muss mit der Rolle übereinstimmen, die Sie in Ihrer `*.role.ts`-Datei definieren (siehe unten).
|
||||
* `defaultRoleUniversalIdentifier` muss mit der Rollendatei übereinstimmen (siehe unten).
|
||||
|
||||
#### Rollen und Berechtigungen
|
||||
|
||||
Anwendungen können Rollen definieren, die Berechtigungen für die Objekte und Aktionen Ihres Workspaces kapseln. Das Feld `functionRoleUniversalIdentifier` in `application.config.ts` legt die Standardrolle fest, die von den serverlosen Funktionen Ihrer App verwendet wird.
|
||||
Anwendungen können Rollen definieren, die Berechtigungen für die Objekte und Aktionen Ihres Workspaces kapseln. Das Feld `defaultRoleUniversalIdentifier` in `application-config.ts` legt die Standardrolle fest, die von den Logikfunktionen Ihrer App verwendet wird.
|
||||
|
||||
* Der zur Laufzeit als `TWENTY_API_KEY` injizierte API-Schlüssel wird von dieser Standard-Funktionsrolle abgeleitet.
|
||||
* Der typisierte Client ist auf die dieser Rolle gewährten Berechtigungen beschränkt.
|
||||
@@ -410,14 +333,14 @@ Anwendungen können Rollen definieren, die Berechtigungen für die Objekte und A
|
||||
Wenn Sie eine neue App erzeugen, erstellt die CLI auch eine Standard-Rolldatei. Verwenden Sie `defineRole()`, um Rollen mit eingebauter Validierung zu definieren:
|
||||
|
||||
```typescript
|
||||
// src/app/default-function.role.ts
|
||||
// src/roles/default-role.ts
|
||||
import { defineRole, PermissionFlag } from 'twenty-sdk';
|
||||
|
||||
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Default function role',
|
||||
description: 'Default role for function Twenty client',
|
||||
canReadAllObjectRecords: false,
|
||||
@@ -430,7 +353,7 @@ export default defineRole({
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
@@ -439,8 +362,8 @@ export default defineRole({
|
||||
],
|
||||
fieldPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
fieldName: 'content',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
|
||||
canReadFieldValue: false,
|
||||
canUpdateFieldValue: false,
|
||||
},
|
||||
@@ -449,10 +372,10 @@ export default defineRole({
|
||||
});
|
||||
```
|
||||
|
||||
Der `universalIdentifier` dieser Rolle wird anschließend in `application.config.ts` als `functionRoleUniversalIdentifier` referenziert. Anders ausgedrückt:
|
||||
Der `universalIdentifier` dieser Rolle wird anschließend in `application-config.ts` als `defaultRoleUniversalIdentifier` referenziert. Anders ausgedrückt:
|
||||
|
||||
* **\*.role.ts** definiert, was die Standard-Funktionsrolle darf.
|
||||
* **application.config.ts** verweist auf diese Rolle, sodass Ihre Funktionen deren Berechtigungen erben.
|
||||
* **application-config.ts** verweist auf diese Rolle, sodass Ihre Funktionen deren Berechtigungen erben.
|
||||
|
||||
Notizen:
|
||||
|
||||
@@ -461,22 +384,17 @@ Notizen:
|
||||
* `permissionFlags` steuern den Zugriff auf Funktionen auf Plattformebene. Halten Sie sie minimal; fügen Sie nur hinzu, was Sie benötigen.
|
||||
* Ein funktionierendes Beispiel finden Sie in der Hello-World-App: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
|
||||
### Konfiguration serverloser Funktionen und Einstiegspunkt
|
||||
### Konfiguration von Logikfunktionen und Einstiegspunkt
|
||||
|
||||
Jede Funktionsdatei verwendet `defineFunction()`, um eine Konfiguration mit einem Handler und optionalen Triggern zu exportieren. Verwenden Sie die Dateiendung `*.function.ts` für die automatische Erkennung.
|
||||
Jede Funktionsdatei verwendet `defineLogicFunction()`, um eine Konfiguration mit einem Handler und optionalen Triggern zu exportieren.
|
||||
|
||||
```typescript
|
||||
// src/app/createPostCard.function.ts
|
||||
import { defineFunction } from 'twenty-sdk';
|
||||
// src/app/createPostCard.logic-function.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
|
||||
import Twenty, { type Person } from '../../generated';
|
||||
import Twenty, { type Person } from '~/generated';
|
||||
|
||||
const handler = async (
|
||||
params:
|
||||
| RoutePayload
|
||||
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
|
||||
| CronPayload,
|
||||
) => {
|
||||
const handler = async (params: RoutePayload) => {
|
||||
const client = new Twenty(); // generated typed client
|
||||
const name = 'name' in params.queryStringParameters
|
||||
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
@@ -492,7 +410,7 @@ const handler = async (
|
||||
return result;
|
||||
};
|
||||
|
||||
export default defineFunction({
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'create-new-post-card',
|
||||
timeoutSeconds: 2,
|
||||
@@ -507,18 +425,18 @@ export default defineFunction({
|
||||
isAuthRequired: false,
|
||||
},
|
||||
// Cron trigger (CRON pattern)
|
||||
{
|
||||
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
type: 'cron',
|
||||
pattern: '0 0 1 1 *',
|
||||
},
|
||||
// {
|
||||
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
// type: 'cron',
|
||||
// pattern: '0 0 1 1 *',
|
||||
// },
|
||||
// Database event trigger
|
||||
{
|
||||
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
type: 'databaseEvent',
|
||||
eventName: 'person.updated',
|
||||
updatedFields: ['name'],
|
||||
},
|
||||
// {
|
||||
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
// type: 'databaseEvent',
|
||||
// eventName: 'person.updated',
|
||||
// updatedFields: ['name'],
|
||||
// },
|
||||
],
|
||||
});
|
||||
```
|
||||
@@ -565,10 +483,10 @@ Notizen:
|
||||
**So migrieren Sie bestehende Funktionen:** Aktualisieren Sie Ihren Handler, sodass er nicht mehr direkt aus dem params-Objekt destrukturiert, sondern aus `event.body`, `event.queryStringParameters` oder `event.pathParameters`.
|
||||
</Warning>
|
||||
|
||||
Wenn ein Routen-Trigger Ihre Funktion aufruft, erhält sie ein `RoutePayload`-Objekt, das dem AWS-HTTP-API-v2-Format entspricht. Importieren Sie den Typ aus `twenty-sdk`:
|
||||
Wenn ein Routen-Trigger Ihre Logikfunktion aufruft, erhält sie ein `RoutePayload`-Objekt, das dem AWS HTTP API v2-Format entspricht. Importieren Sie den Typ aus `twenty-sdk`:
|
||||
|
||||
```typescript
|
||||
import { defineFunction, type RoutePayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
// Access request data
|
||||
@@ -595,10 +513,10 @@ Der Typ `RoutePayload` hat die folgende Struktur:
|
||||
|
||||
### Weiterleiten von HTTP-Headern
|
||||
|
||||
Standardmäßig werden HTTP-Header von eingehenden Anfragen aus Sicherheitsgründen nicht an Ihre serverlose Funktion weitergegeben. Um auf bestimmte Header zuzugreifen, listen Sie diese explizit im Array `forwardedRequestHeaders` auf:
|
||||
Standardmäßig werden HTTP-Header von eingehenden Anfragen aus Sicherheitsgründen nicht an Ihre Logikfunktion weitergegeben. Um auf bestimmte Header zuzugreifen, listen Sie diese explizit im Array `forwardedRequestHeaders` auf:
|
||||
|
||||
```typescript
|
||||
export default defineFunction({
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'webhook-handler',
|
||||
handler,
|
||||
@@ -633,23 +551,60 @@ const handler = async (event: RoutePayload) => {
|
||||
|
||||
Sie können neue Funktionen auf zwei Arten erstellen:
|
||||
|
||||
* **Generiert**: Führen Sie `yarn app:create-entity` aus und wählen Sie die Option zum Hinzufügen einer neuen Funktion. Dadurch wird eine Starterdatei mit Handler und Konfiguration erzeugt.
|
||||
* **Manuell**: Erstellen Sie eine neue `*.function.ts`-Datei und verwenden Sie `defineFunction()` nach demselben Muster.
|
||||
* **Generiert**: Führen Sie `yarn entity:add` aus und wählen Sie die Option zum Hinzufügen einer neuen Logikfunktion. Dadurch wird eine Starterdatei mit Handler und Konfiguration erzeugt.
|
||||
* **Manuell**: Erstellen Sie eine neue `*.logic-function.ts`-Datei und verwenden Sie `defineLogicFunction()` nach demselben Muster.
|
||||
|
||||
### Frontend-Komponenten
|
||||
|
||||
Frontend-Komponenten ermöglichen es Ihnen, benutzerdefinierte React-Komponenten zu erstellen, die innerhalb der Twenty-UI gerendert werden. Verwenden Sie `defineFrontComponent()`, um Komponenten mit eingebauter Validierung zu definieren:
|
||||
|
||||
```typescript
|
||||
// src/my-widget.front-component.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
const MyWidget = () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>My Custom Widget</h1>
|
||||
<p>This is a custom front component for Twenty.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
name: 'my-widget',
|
||||
description: 'A custom widget component',
|
||||
component: MyWidget,
|
||||
});
|
||||
```
|
||||
|
||||
Hauptpunkte:
|
||||
|
||||
* Frontend-Komponenten sind React-Komponenten, die in isolierten Kontexten innerhalb von Twenty gerendert werden.
|
||||
* Verwenden Sie die Dateiendung `*.front-component.tsx` für die automatische Erkennung.
|
||||
* Das Feld `component` verweist auf Ihre React-Komponente.
|
||||
* Komponenten werden während `yarn app:dev` automatisch gebaut und synchronisiert.
|
||||
|
||||
Sie können neue Frontend-Komponenten auf zwei Arten erstellen:
|
||||
|
||||
* **Generiert**: Führen Sie `yarn entity:add` aus und wählen Sie die Option zum Hinzufügen einer neuen Frontend-Komponente.
|
||||
* **Manuell**: Erstellen Sie eine neue `*.front-component.tsx`-Datei und verwenden Sie `defineFrontComponent()`.
|
||||
|
||||
### Generierter typisierter Client
|
||||
|
||||
Führen Sie yarn app:generate aus, um einen lokalen typisierten Client in generated/ basierend auf Ihrem Workspace-Schema zu erstellen. Verwenden Sie ihn in Ihren Funktionen:
|
||||
|
||||
```typescript
|
||||
import Twenty from './generated';
|
||||
import Twenty from '~/generated';
|
||||
|
||||
const client = new Twenty();
|
||||
const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
```
|
||||
|
||||
Der Client wird durch `yarn app:generate` erneut generiert. Führen Sie ihn nach Änderungen an Ihren Objekten und nach `yarn app:sync` bzw. beim Onboarding in einen neuen Workspace erneut aus.
|
||||
Der Client wird durch `yarn app:generate` erneut generiert. Führen Sie ihn nach Änderungen an Ihren Objekten oder beim Onboarding in einen neuen Workspace erneut aus.
|
||||
|
||||
#### Laufzeit-Anmeldedaten in serverlosen Funktionen
|
||||
#### Laufzeit-Anmeldedaten in Logikfunktionen
|
||||
|
||||
Wenn Ihre Funktion auf Twenty läuft, injiziert die Plattform vor der Ausführung Ihres Codes Anmeldedaten als Umgebungsvariablen:
|
||||
|
||||
@@ -659,12 +614,12 @@ Wenn Ihre Funktion auf Twenty läuft, injiziert die Plattform vor der Ausführun
|
||||
Notizen:
|
||||
|
||||
* Sie müssen dem generierten Client weder URL noch API-Schlüssel übergeben. Er liest `TWENTY_API_URL` und `TWENTY_API_KEY` zur Laufzeit aus process.env.
|
||||
* Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, auf die in Ihrer `application.config.ts` über `functionRoleUniversalIdentifier` verwiesen wird. Dies ist die Standardrolle, die von den serverlosen Funktionen Ihrer Anwendung verwendet wird.
|
||||
* Anwendungen können Rollen definieren, um das Least-Privilege-Prinzip einzuhalten. Gewähren Sie nur die Berechtigungen, die Ihre Funktionen benötigen, und verweisen Sie dann mit `functionRoleUniversalIdentifier` auf den universellen Bezeichner dieser Rolle.
|
||||
* Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, auf die in Ihrer `application-config.ts` über `defaultRoleUniversalIdentifier` verwiesen wird. Dies ist die Standardrolle, die von den Logikfunktionen Ihrer Anwendung verwendet wird.
|
||||
* Anwendungen können Rollen definieren, um das Least-Privilege-Prinzip einzuhalten. Gewähren Sie nur die Berechtigungen, die Ihre Funktionen benötigen, und verweisen Sie dann mit `defaultRoleUniversalIdentifier` auf den universellen Bezeichner dieser Rolle.
|
||||
|
||||
### Hello-World-Beispiel
|
||||
|
||||
Ein minimales End-to-End-Beispiel, das Objekte, Funktionen und mehrere Trigger demonstriert, finden Sie [hier](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
Ein minimales End-to-End-Beispiel, das Objekte, Logikfunktionen, Frontend-Komponenten und mehrere Trigger demonstriert, finden Sie [hier](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
|
||||
## Manuelle Einrichtung (ohne Scaffolder)
|
||||
|
||||
@@ -679,25 +634,29 @@ Fügen Sie dann Skripte wie diese hinzu:
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"auth": "twenty auth login",
|
||||
"generate": "twenty app generate",
|
||||
"dev": "twenty app dev",
|
||||
"sync": "twenty app sync",
|
||||
"uninstall": "twenty app uninstall",
|
||||
"logs": "twenty app logs",
|
||||
"create-entity": "twenty app add",
|
||||
"help": "twenty --help"
|
||||
"auth:login": "twenty auth:login",
|
||||
"auth:logout": "twenty auth:logout",
|
||||
"auth:status": "twenty auth:status",
|
||||
"auth:switch": "twenty auth:switch",
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"app:generate": "twenty app:generate",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
"entity:add": "twenty entity:add",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"help": "twenty help"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Jetzt können Sie dieselben Befehle über Yarn ausführen, z. B. `yarn app:dev`, `yarn app:sync` usw.
|
||||
Jetzt können Sie dieselben Befehle über Yarn ausführen, z. B. `yarn app:dev`, `yarn app:generate` usw.
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
* Authentifizierungsfehler: Führen Sie `yarn auth:login` aus und stellen Sie sicher, dass Ihr API-Schlüssel die erforderlichen Berechtigungen hat.
|
||||
* Verbindung zum Server nicht möglich: Überprüfen Sie die API-URL und dass der Twenty-Server erreichbar ist.
|
||||
* Typen oder Client fehlen/veraltet: Führen Sie `yarn app:generate` und anschließend `yarn app:dev` aus.
|
||||
* Typen oder Client fehlen/veraltet: Führen Sie `yarn app:generate` aus.
|
||||
* Dev-Modus synchronisiert nicht: Stellen Sie sicher, dass `yarn app:dev` läuft und dass Änderungen von Ihrer Umgebung nicht ignoriert werden.
|
||||
|
||||
Discord-Hilfekanal: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
|
||||
@@ -292,19 +292,19 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**Nur-Umgebungsmodus:** Wenn Sie `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` setzen, fügen Sie diese Variablen stattdessen Ihrer `.env`-Datei hinzu.
|
||||
</Warning>
|
||||
|
||||
## Serverlose Funktionen
|
||||
## Logikfunktionen
|
||||
|
||||
Twenty unterstützt serverlose Funktionen für Workflows und benutzerdefinierte Logik. Die Ausführungsumgebung wird über die Umgebungsvariable `SERVERLESS_TYPE` konfiguriert.
|
||||
Twenty unterstützt Logikfunktionen für Workflows und benutzerdefinierte Logik. Die Ausführungsumgebung wird über die Umgebungsvariable `SERVERLESS_TYPE` konfiguriert.
|
||||
|
||||
<Warning>
|
||||
**Sicherheitshinweis:** Der lokale serverlose Treiber (`SERVERLESS_TYPE=LOCAL`) führt Code ohne Sandbox direkt auf dem Host in einem Node.js-Prozess aus. Er sollte nur für vertrauenswürdigen Code in der Entwicklung verwendet werden. Für Produktivbereitstellungen, die nicht vertrauenswürdigen Code verarbeiten, empfehlen wir nachdrücklich, `SERVERLESS_TYPE=LAMBDA` oder `SERVERLESS_TYPE=DISABLED` zu verwenden.
|
||||
**Sicherheitshinweis:** Der lokale Treiber (`SERVERLESS_TYPE=LOCAL`) führt Code ohne Sandbox direkt auf dem Host in einem Node.js-Prozess aus. Er sollte nur für vertrauenswürdigen Code in der Entwicklung verwendet werden. Für Produktivbereitstellungen, die nicht vertrauenswürdigen Code verarbeiten, empfehlen wir nachdrücklich, `SERVERLESS_TYPE=LAMBDA` oder `SERVERLESS_TYPE=DISABLED` zu verwenden.
|
||||
</Warning>
|
||||
|
||||
### Verfügbare Treiber
|
||||
|
||||
| Treiber | Umgebungsvariable | Anwendungsfall | Sicherheitsstufe |
|
||||
| ----------- | -------------------------- | -------------------------------------------------- | ---------------------------------- |
|
||||
| Deaktiviert | `SERVERLESS_TYPE=DISABLED` | Serverlose Funktionen vollständig deaktivieren | N/A |
|
||||
| Deaktiviert | `SERVERLESS_TYPE=DISABLED` | Logikfunktionen vollständig deaktivieren | N/A |
|
||||
| Lokal | `SERVERLESS_TYPE=LOCAL` | Entwicklung und vertrauenswürdige Umgebungen | Niedrig (keine Sandbox) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Produktivbetrieb mit nicht vertrauenswürdigem Code | Hoch (Isolation auf Hardwareebene) |
|
||||
|
||||
@@ -326,12 +326,12 @@ SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**Zum Deaktivieren serverloser Funktionen:**
|
||||
**Zum Deaktivieren von Logikfunktionen:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
```
|
||||
|
||||
<Note>
|
||||
Bei Verwendung von `SERVERLESS_TYPE=DISABLED` führt jeder Versuch, eine serverlose Funktion auszuführen, zu einem Fehler. Dies ist nützlich, wenn Sie Twenty ohne Unterstützung für serverlose Funktionen betreiben möchten.
|
||||
Bei Verwendung von `SERVERLESS_TYPE=DISABLED` führt jeder Versuch, eine Logikfunktion auszuführen, zu einem Fehler. Dies ist nützlich, wenn Sie Twenty ohne Unterstützung für Logikfunktionen betreiben möchten.
|
||||
</Note>
|
||||
|
||||
@@ -59,4 +59,4 @@ So wird sichergestellt, dass KI-Agenten Ihre Richtlinien zur Daten-Governance re
|
||||
Wir werden diesen Abschnitt aktualisieren, sobald KI-Funktionen verfügbar werden. In der Zwischenzeit:
|
||||
|
||||
* Folgen Sie unserem [GitHub](https://github.com/twentyhq/twenty) für Entwicklungsupdates
|
||||
* Treten Sie unserem [Discord](https://discord.gg/twenty) bei, um Feedback und Funktionswünsche zu teilen
|
||||
* Treten Sie unserem [Discord](https://discord.gg/UfGNZJfAG6) bei, um Feedback und Funktionswünsche zu teilen
|
||||
|
||||
@@ -39,12 +39,16 @@ Viele Datensätze in Objekt A können mit vielen Datensätzen in Objekt B verkn
|
||||
|
||||
**Beispiel:** Viele Personen können mit vielen Projekten verknüpft werden, und umgekehrt.
|
||||
|
||||
<Warning>
|
||||
**Viele-zu-Viele-Beziehungen werden noch nicht unterstützt.**
|
||||
Viele-zu-viele-Beziehungen verwenden ein **Verknüpfungsobjekt**-Muster: ein Zwischenobjekt, das beide Seiten verbindet. Mit der Funktion für Verknüpfungsbeziehungen zeigt Twenty die endgültig verknüpften Datensätze direkt an und blendet das Zwischenobjekt in der Benutzeroberfläche aus.
|
||||
|
||||
Dieser Beziehungstyp ist für H1 2026 geplant. Als Workaround erstellen Sie ein zwischengeschaltetes "Junction"-Objekt (z. B. "Projektzuweisungen"), das Viele-zu-Eins-Beziehungen zu beiden Objekten hat.
|
||||
<img src="/images/user-guide/fields/junction-relation-diagram.png" style={{width:'100%'}} />
|
||||
|
||||
<Warning>
|
||||
**Lab-Funktion**: Verknüpfungsbeziehungen müssen vor der Verwendung unter **Einstellungen → Updates → Lab** aktiviert werden.
|
||||
</Warning>
|
||||
|
||||
Siehe [Viele-zu-Viele-Beziehungen erstellen](/l/de/user-guide/data-model/how-tos/create-many-to-many-relations) für eine vollständige Schritt-für-Schritt-Anleitung.
|
||||
|
||||
## Beziehungsfeld erstellen
|
||||
|
||||
1. Gehen Sie zu **Einstellungen → Datenmodell**
|
||||
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
---
|
||||
title: Viele-zu-Viele-Beziehungen erstellen
|
||||
description: Verbinden Sie Datensätze, bei denen auf beiden Seiten viele Elemente mithilfe von Verknüpfungsobjekten miteinander verknüpft werden können.
|
||||
---
|
||||
|
||||
Viele-zu-Viele-Beziehungen ermöglichen es Ihnen, auf beiden Seiten mehrere Datensätze zu verknüpfen. Beispiel: Viele Personen können an vielen Projekten arbeiten, und jedes Projekt kann viele Personen haben.
|
||||
|
||||
<Warning>
|
||||
**Lab-Funktion**: Verknüpfungsbeziehungen befinden sich derzeit im Lab. Aktivieren Sie sie unter **Einstellungen → Updates → Lab**, bevor Sie dieser Anleitung folgen.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
Für diese Funktion muss außerdem der **Erweiterte Modus** aktiviert sein (Schalter unten rechts in den Einstellungen).
|
||||
</Note>
|
||||
|
||||
## Wann Viele-zu-Viele-Beziehungen verwenden
|
||||
|
||||
Verwenden Sie Viele-zu-Viele-Beziehungen, wenn beide Seiten einer Beziehung mehrere Verknüpfungen haben können:
|
||||
|
||||
| Beziehung | Beispiel |
|
||||
| ----------------------- | -------------------------------------------------------------------------------------------------- |
|
||||
| Personen ↔ Projekte | Eine Person arbeitet an mehreren Projekten; ein Projekt hat mehrere Teammitglieder |
|
||||
| Unternehmen ↔ Tags | Ein Unternehmen kann mehrere Tags haben; ein Tag kann für mehrere Unternehmen gelten |
|
||||
| Produkte ↔ Bestellungen | Ein Produkt kann in mehreren Bestellungen enthalten sein; eine Bestellung enthält mehrere Produkte |
|
||||
|
||||
## Wie es funktioniert
|
||||
|
||||
Twenty verwendet für Viele-zu-Viele-Beziehungen ein Muster mit **Verknüpfungsobjekt**. Ein Verknüpfungsobjekt sitzt zwischen zwei Objekten und speichert die Verknüpfungen:
|
||||
|
||||
```
|
||||
People ←→ Project Assignments ←→ Projects
|
||||
```
|
||||
|
||||
Das Objekt **Projektzuweisungen** (Verknüpfung) hat:
|
||||
|
||||
* Eine Beziehung zu Personen (Viele-zu-Eins)
|
||||
* Eine Beziehung zu Projekten (Viele-zu-Eins)
|
||||
|
||||
Wenn Sie den Schalter für die Verknüpfungsbeziehung aktivieren, zeigt Twenty verknüpfte Datensätze direkt an, anstatt die zwischengeschalteten Verknüpfungsdatensätze anzuzeigen.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
1. **Verknüpfungsbeziehungen im Lab aktivieren**: Gehen Sie zu **Einstellungen → Updates → Lab** und aktivieren Sie **Verknüpfungsbeziehungen**
|
||||
2. **Erweiterten Modus aktivieren**: Aktivieren Sie den **Erweiterten Modus** unten rechts in der Seitenleiste der Einstellungen
|
||||
3. Planen Sie Ihr Datenmodell:
|
||||
* Welche zwei Objekte verbinden Sie?
|
||||
* Wie soll das Verknüpfungsobjekt heißen?
|
||||
|
||||
## Schritt 1: Verknüpfungsobjekt erstellen
|
||||
|
||||
Erstellen Sie zunächst das Zwischenobjekt, das die Verknüpfungen speichert.
|
||||
|
||||
1. Gehen Sie zu **Einstellungen → Datenmodell**
|
||||
2. Klicken Sie auf **+ Neues Objekt**
|
||||
3. Benennen Sie es aussagekräftig (z. B. "Projektzuweisung", "Teammitglied", "Produktbestellung")
|
||||
4. Klicken Sie auf **Speichern**
|
||||
|
||||
<Tip>
|
||||
**Namenskonvention**: Verwenden Sie einen Namen, der die Beziehung beschreibt, z. B. "Projektzuweisung" oder "Teammitgliedschaft". Das macht das Datenmodell leichter verständlich.
|
||||
</Tip>
|
||||
|
||||
## Schritt 2: Beziehungen vom Verknüpfungsobjekt erstellen
|
||||
|
||||
Fügen Sie vom Verknüpfungsobjekt aus Beziehungsfelder zu beiden Objekten hinzu, die Sie verbinden möchten.
|
||||
|
||||
### Erste Beziehung (Verknüpfung → Objekt A)
|
||||
|
||||
1. Wählen Sie Ihr Verknüpfungsobjekt unter **Einstellungen → Datenmodell** aus
|
||||
2. Klicken Sie auf **+ Feld hinzufügen**
|
||||
3. Wählen Sie **Relation** als Feldtyp
|
||||
4. Wählen Sie das erste Objekt aus (z. B. "Personen")
|
||||
5. Legen Sie den Beziehungstyp auf **Viele-zu-Eins** fest (viele Zuweisungen können mit einer Person verknüpft werden)
|
||||
6. Benennen Sie die Felder:
|
||||
* Feld auf der Verknüpfung: z. B. "Person"
|
||||
* Feld bei Personen: z. B. "Projektzuweisungen"
|
||||
7. Klicken Sie auf **Speichern**
|
||||
|
||||
### Zweite Beziehung (Verknüpfung → Objekt B)
|
||||
|
||||
1. Bleiben Sie im Verknüpfungsobjekt und klicken Sie auf **+ Feld hinzufügen**
|
||||
2. Wählen Sie **Relation** als Feldtyp
|
||||
3. Wählen Sie das zweite Objekt aus (z. B. "Projekte")
|
||||
4. Legen Sie den Beziehungstyp auf **Viele-zu-Eins** fest
|
||||
5. Benennen Sie die Felder:
|
||||
* Feld auf der Verknüpfung: z. B. "Projekt"
|
||||
* Feld bei Projekten: z. B. "Teammitglieder"
|
||||
6. Klicken Sie auf **Speichern**
|
||||
|
||||
## Schritt 3: Anzeige der Verknüpfungsbeziehung konfigurieren
|
||||
|
||||
Konfigurieren Sie nun die Quellobjekte so, dass verknüpfte Datensätze direkt angezeigt werden, wobei das zwischengeschaltete Verknüpfungsobjekt übersprungen wird.
|
||||
|
||||
1. Gehen Sie zu **Einstellungen → Datenmodell**
|
||||
2. Wählen Sie das erste Objekt aus (z. B. "Personen")
|
||||
3. Suchen Sie das Beziehungsfeld, das auf das Verknüpfungsobjekt zeigt (z. B. "Projektzuweisungen")
|
||||
4. Klicken Sie, um das Feld zu bearbeiten
|
||||
5. Aktivieren Sie **"Dies ist eine Beziehung zu einem Verknüpfungsobjekt"**
|
||||
6. Wählen Sie die **Zielbeziehung** aus (z. B. "Projekt" — das Feld an der Verknüpfung, das auf die andere Seite zeigt)
|
||||
7. Klicken Sie auf **Speichern**
|
||||
|
||||
{/* TODO: Add image
|
||||
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
|
||||
*/}
|
||||
|
||||
Wiederholen Sie dies für das andere Objekt:
|
||||
|
||||
1. Wählen Sie "Projekte" im Datenmodell aus
|
||||
2. Bearbeiten Sie das Beziehungsfeld "Teammitglieder"
|
||||
3. Aktivieren Sie den Verknüpfungsschalter
|
||||
4. Wählen Sie "Person" als Zielbeziehung aus
|
||||
5. Speichern
|
||||
|
||||
## Ergebnis
|
||||
|
||||
Nach der Konfiguration:
|
||||
|
||||
* In einem **Person**-Datensatz zeigt das Feld "Projektzuweisungen" **Projekte** direkt an (keine Zuweisungsdatensätze)
|
||||
* In einem **Projekt**-Datensatz zeigt das Feld "Teammitglieder" **Personen** direkt an
|
||||
|
||||
Das Verknüpfungsobjekt existiert weiterhin und speichert die Verknüpfungen, aber die UI präsentiert eine übersichtlichere Viele-zu-Viele-Ansicht.
|
||||
|
||||
## Beispiel: Personen ↔ Projekte
|
||||
|
||||
Hier ist eine vollständige Schritt-für-Schritt-Anleitung:
|
||||
|
||||
### Verknüpfungsobjekt erstellen
|
||||
|
||||
* Name: **Projektzuweisung**
|
||||
* Beschreibung: "Verknüpft Personen mit den Projekten, an denen sie arbeiten"
|
||||
|
||||
### Beziehungen hinzufügen
|
||||
|
||||
1. **Projektzuweisung → Personen**
|
||||
* Typ: Viele-zu-Eins
|
||||
* Feld bei Zuweisung: "Person"
|
||||
* Feld bei Personen: "Projektzuweisungen"
|
||||
|
||||
2. **Projektzuweisung → Projekte**
|
||||
* Typ: Viele-zu-Eins
|
||||
* Feld bei Zuweisung: "Projekt"
|
||||
* Feld bei Projekten: "Teammitglieder"
|
||||
|
||||
### Verknüpfungsanzeige konfigurieren
|
||||
|
||||
1. Am Objekt **Personen**:
|
||||
* Feld "Projektzuweisungen" bearbeiten
|
||||
* Verknüpfungsschalter aktivieren
|
||||
* Ziel: "Projekt"
|
||||
|
||||
2. Am Objekt **Projekte**:
|
||||
* Feld "Teammitglieder" bearbeiten
|
||||
* Verknüpfungsschalter aktivieren
|
||||
* Ziel: "Person"
|
||||
|
||||
### Verwendung
|
||||
|
||||
* Öffnen Sie einen Person-Datensatz → Sehen Sie deren Projekte direkt
|
||||
* Öffnen Sie einen Projekt-Datensatz → Sehen Sie Teammitglieder direkt
|
||||
* Erstellen Sie neue Verknüpfungen von beiden Seiten
|
||||
|
||||
## Zusätzliche Daten zu Verknüpfungen hinzufügen
|
||||
|
||||
Da das Verknüpfungsobjekt ein echtes Objekt ist, können Sie benutzerdefinierte Felder hinzufügen, um Informationen über die Beziehung zu speichern:
|
||||
|
||||
* **Rolle**: "Entwickler", "Designer", "Manager"
|
||||
* **Startdatum**: Wann sie dem Projekt beigetreten sind
|
||||
* **Zugeordnete Stunden**: Wöchentliche Stunden für dieses Projekt
|
||||
|
||||
Um auf diese Daten zuzugreifen, navigieren Sie direkt zum Verknüpfungsobjekt oder greifen Sie per API-Abfrage darauf zu.
|
||||
|
||||
## Einschränkungen
|
||||
|
||||
* **CSV-Import/-Export**: Das direkte Importieren von Viele-zu-Viele-Beziehungen wird nicht unterstützt. Importieren Sie stattdessen Datensätze in das Verknüpfungsobjekt.
|
||||
* **Filter**: Das Filtern nach Viele-zu-Viele-Beziehungen bietet möglicherweise nur begrenzte Optionen.
|
||||
|
||||
## Verwandt
|
||||
|
||||
* [Beziehungsfelder](/l/de/user-guide/data-model/capabilities/relation-fields) — Beziehungstypen erklärt
|
||||
* [Benutzerdefinierte Objekte erstellen](/l/de/user-guide/data-model/how-tos/create-custom-objects) — so erstellen Sie Objekte
|
||||
* [Beziehungsfelder erstellen](/l/de/user-guide/data-model/how-tos/create-relation-fields) — grundlegende Einrichtung von Beziehungen
|
||||
@@ -9,7 +9,7 @@ API (Application Programming Interface) ermöglicht es Ihnen, Twenty mit anderen
|
||||
|
||||
## Apps
|
||||
|
||||
Apps sind benutzerdefinierte Erweiterungen, die als Code erstellt werden und Datenmodelle sowie serverlose Funktionen definieren können. Damit können Entwickler wiederverwendbare Anpassungen erstellen, die in mehreren Workspaces bereitgestellt werden können.
|
||||
Apps sind benutzerdefinierte Erweiterungen, die als Code erstellt werden und Datenmodelle sowie Logikfunktionen definieren können. Damit können Entwickler wiederverwendbare Anpassungen erstellen, die in mehreren Workspaces bereitgestellt werden können.
|
||||
|
||||
## Code-Aktionen
|
||||
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ Sie können Dateien an E-Mails anhängen, die von Workflows gesendet werden. Der
|
||||
| **Veranstaltungsbestätigungen** | Veranstaltungsdetails oder Agenda |
|
||||
|
||||
<Note>
|
||||
Anhänge sind statisch—dieselbe Datei wird an alle Empfänger gesendet. Für dynamische Dokumente (z. B. personalisierte Angebote) erstellen und hängen Sie Dateien mithilfe einer [Serverless-Funktion](/l/de/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) an.
|
||||
Anhänge sind statisch—dieselbe Datei wird an alle Empfänger gesendet. For dynamic documents (like personalized quotes), generate and attach files using a [Logic Function](/l/de/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
|
||||
</Note>
|
||||
|
||||
## Beste Praktiken
|
||||
|
||||
@@ -256,7 +256,7 @@ Führt benutzerdefiniertes JavaScript in Ihrem Workflow aus.
|
||||
* Code direkt im Schritt testen
|
||||
|
||||
<Note>
|
||||
If you need to use external API keys in your code, you must input them directly in the function body. You cannot configure API keys elsewhere and reference them in the serverless function.
|
||||
If you need to use external API keys in your code, you must input them directly in the function body. Sie können API-Schlüssel nicht an anderer Stelle konfigurieren und sie dann in der Logikfunktion referenzieren.
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
|
||||
+9
-9
@@ -7,7 +7,7 @@ Automatisch ein PDF generieren oder abrufen und an einen Datensatz in Twenty anh
|
||||
|
||||
## Übersicht
|
||||
|
||||
Dieser Workflow verwendet einen **Manuellen Auslöser**, damit Benutzer bei Bedarf für jeden ausgewählten Datensatz ein PDF generieren können. Eine **Serverlose Funktion** übernimmt:
|
||||
Dieser Workflow verwendet einen **Manuellen Auslöser**, damit Benutzer bei Bedarf für jeden ausgewählten Datensatz ein PDF generieren können. Eine **Logikfunktion** übernimmt:
|
||||
|
||||
1. Das Herunterladen des PDFs von einer URL (von einem PDF-Generierungsdienst)
|
||||
2. Das Hochladen der Datei in Twenty
|
||||
@@ -17,7 +17,7 @@ Dieser Workflow verwendet einen **Manuellen Auslöser**, damit Benutzer bei Beda
|
||||
|
||||
Bevor Sie den Workflow einrichten:
|
||||
|
||||
1. **API-Schlüssel erstellen**: Gehen Sie zu **Einstellungen → APIs** und erstellen Sie einen neuen API-Schlüssel. Sie benötigen dieses Token für die serverlose Funktion.
|
||||
1. **API-Schlüssel erstellen**: Gehen Sie zu **Einstellungen → APIs** und erstellen Sie einen neuen API-Schlüssel. Sie benötigen dieses Token für die Logikfunktion.
|
||||
2. **Richten Sie einen PDF-Generierungsdienst ein** (optional): Wenn Sie PDFs dynamisch generieren möchten (z. B. Angebote), verwenden Sie einen Dienst wie Carbone, PDFMonkey oder DocuSeal, um das PDF zu erstellen und eine Download-URL zu erhalten.
|
||||
|
||||
## Schritt-für-Schritt-Einrichtung
|
||||
@@ -32,9 +32,9 @@ Bevor Sie den Workflow einrichten:
|
||||
Mit einem manuellen Auslöser können Benutzer diesen Workflow über eine Schaltfläche ausführen, die oben rechts erscheint, sobald ein Datensatz ausgewählt ist, um ein PDF zu generieren und anzuhängen.
|
||||
</Tip>
|
||||
|
||||
### Schritt 2: Serverlose Funktion hinzufügen
|
||||
### Schritt 2: Logikfunktion hinzufügen
|
||||
|
||||
1. Fügen Sie eine **Serverlose Funktion**-Aktion hinzu
|
||||
1. Fügen Sie eine **Code**-Aktion (Logikfunktion) hinzu
|
||||
2. Erstellen Sie eine neue Funktion mit dem folgenden Code
|
||||
3. Konfigurieren Sie die Eingabeparameter
|
||||
|
||||
@@ -45,10 +45,10 @@ Bevor Sie den Workflow einrichten:
|
||||
| `companyId` | `{{trigger.object.id}}` |
|
||||
|
||||
<Note>
|
||||
Wenn Sie an ein anderes Objekt anhängen (Person, Opportunity usw.), benennen Sie den Parameter entsprechend um (z. B. `personId`, `opportunityId`) und aktualisieren Sie die serverlose Funktion.
|
||||
Wenn Sie an ein anderes Objekt anhängen (Person, Opportunity usw.), benennen Sie den Parameter entsprechend um (z. B. `personId`, `opportunityId`) und aktualisieren Sie die Logikfunktion.
|
||||
</Note>
|
||||
|
||||
#### Code der serverlosen Funktion
|
||||
#### Code der Logikfunktion
|
||||
|
||||
```typescript
|
||||
export const main = async (
|
||||
@@ -172,7 +172,7 @@ Aktualisieren Sie sowohl den Funktionsparameter als auch das Objekt `variables.d
|
||||
Wenn Sie einen PDF-Generierungsdienst verwenden, können Sie:
|
||||
|
||||
1. Führen Sie zuerst eine HTTP-Request-Aktion aus, um das PDF zu generieren
|
||||
2. Übergeben Sie die zurückgegebene PDF-URL als Parameter an die serverlose Funktion
|
||||
2. Übergeben Sie die zurückgegebene PDF-URL als Parameter an die Logikfunktion
|
||||
|
||||
```typescript
|
||||
export const main = async (
|
||||
@@ -211,7 +211,7 @@ Zum Erstellen dynamischer Angebote oder Rechnungen:
|
||||
* **DocuSeal** – Plattform für Dokumentenautomatisierung
|
||||
* **Documint** – API-first-Dokumentenerstellung
|
||||
|
||||
Jeder Dienst stellt eine API bereit, die eine PDF-URL zurückgibt, die Sie anschließend an die serverlose Funktion übergeben können.
|
||||
Jeder Dienst stellt eine API bereit, die eine PDF-URL zurückgibt, die Sie anschließend an die Logikfunktion übergeben können.
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
@@ -224,5 +224,5 @@ Jeder Dienst stellt eine API bereit, die eine PDF-URL zurückgibt, die Sie ansch
|
||||
## Verwandt
|
||||
|
||||
* [Workflow-Trigger](/l/de/user-guide/workflows/capabilities/workflow-triggers)
|
||||
* [Serverlose Funktionen](/l/de/user-guide/workflows/capabilities/workflow-actions#serverless-function)
|
||||
* [Logikfunktionen](/l/de/user-guide/workflows/capabilities/workflow-actions#code)
|
||||
* [Ein Angebot oder eine Rechnung aus Twenty generieren](/l/de/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ description: Häufig gestellte Fragen zu Workflows in Twenty.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Wie lang ist die maximale Ausführungszeit für Code-Aktionen?">
|
||||
Code-Aktionen (serverlose Funktionen) haben ein **Standard-Timeout von 5 Minuten** (300 Sekunden).
|
||||
Code-Aktionen (Logikfunktionen) haben ein **Standard-Timeout von 5 Minuten** (300 Sekunden).
|
||||
|
||||
Das maximal konfigurierbare Timeout beträgt **15 Minuten** (900 Sekunden).
|
||||
|
||||
|
||||
+11
-11
@@ -1,22 +1,22 @@
|
||||
---
|
||||
title: Best Practices
|
||||
title: Mejores prácticas
|
||||
---
|
||||
|
||||
This document outlines the best practices you should follow when working on the backend.
|
||||
Este documento describe las mejores prácticas que debe seguir al trabajar en el backend.
|
||||
|
||||
## Follow a modular approach
|
||||
## Siga un enfoque modular
|
||||
|
||||
The backend follows a modular approach, which is a fundamental principle when working with NestJS. Make sure you break down your code into reusable modules to maintain a clean and organized codebase.
|
||||
Each module should encapsulate a particular feature or functionality and have a well-defined scope. This modular approach enables clear separation of concerns and removes unnecessary complexities.
|
||||
El backend sigue un enfoque modular, que es un principio fundamental al trabajar con NestJS. Asegúrese de descomponer su código en módulos reutilizables para mantener una base de código limpia y organizada.
|
||||
Cada módulo debe encapsular una característica o funcionalidad particular y tener un alcance bien definido. Este enfoque modular permite una clara separación de responsabilidades y elimina complejidades innecesarias.
|
||||
|
||||
## Expose services to use in modules
|
||||
## Exponer servicios para usar en módulos
|
||||
|
||||
Always create services that have a clear and single responsibility, which enhances code readability and maintainability. Name the services descriptively and consistently.
|
||||
Siempre cree servicios que tengan una responsabilidad clara y única, lo que mejora la legibilidad y mantenibilidad del código. Nombre los servicios de manera descriptiva y consistente.
|
||||
|
||||
You should also expose services that you want to use in other modules. Exposing services to other modules is possible through NestJS's powerful dependency injection system, and promotes loose coupling between components.
|
||||
También debe exponer servicios que desee usar en otros módulos. Exponer servicios a otros módulos es posible a través del poderoso sistema de inyección de dependencias de NestJS, y promueve un acoplamiento débil entre los componentes.
|
||||
|
||||
## Avoid using `any` type
|
||||
## Evitar usar el tipo `any`
|
||||
|
||||
When you declare a variable as `any`, TypeScript's type checker doesn't perform any type checking, making it possible to assign any type of values to the variable. TypeScript uses type inference to determine the type of variable based on the value. By declaring it as `any`, TypeScript can no longer infer the type. This makes it hard to catch type-related errors during development, leading to runtime errors and makes the code less maintainable, less reliable, and harder to understand for others.
|
||||
Cuando declara una variable como `any`, el verificador de tipos de TypeScript no realiza ninguna comprobación de tipos, lo que hace posible asignar cualquier tipo de valores a la variable. TypeScript utiliza la inferencia de tipos para determinar el tipo de la variable a partir del valor. Al declararlo como `any`, TypeScript ya no puede inferir el tipo. Esto dificulta la captura de errores relacionados con el tipo durante el desarrollo, lo que lleva a errores en tiempo de ejecución y hace que el código sea menos mantenible, menos fiable y más difícil de entender para otros.
|
||||
|
||||
This is why everything should have a type. So if you create a new object with a first name and last name, you should create an interface or type that contains a first name and last name that defines the shape of the object you are manipulating.
|
||||
Por eso todo debe tener un tipo. Entonces, si crea un nuevo objeto con un nombre y apellido, debe crear una interfaz o tipo que contenga un nombre y apellido y defina la forma del objeto que está manipulando.
|
||||
|
||||
+15
-15
@@ -1,39 +1,39 @@
|
||||
---
|
||||
title: Custom Objects
|
||||
title: Objetos personalizados
|
||||
---
|
||||
|
||||
Objects are structures that allow you to store data (records, attributes, and values) specific to an organization. Twenty provides both standard and custom objects.
|
||||
Los objetos son estructuras que te permiten almacenar datos (registros, atributos y valores) específicos de una organización. Twenty proporciona tanto objetos estándar como personalizados.
|
||||
|
||||
Standard objects are in-built objects with a set of attributes available for all users. Examples of standard objects in Twenty include Company and Person. Standard objects have standard fields that are also available for all Twenty users, like Company.displayName.
|
||||
Los objetos estándar son objetos incorporados con un conjunto de atributos disponibles para todos los usuarios. Ejemplos de objetos estándar en Twenty incluyen Empresa y Persona. Los objetos estándar tienen campos estándar que también están disponibles para todos los usuarios de Twenty, como Company.displayName.
|
||||
|
||||
Custom objects are objects that you can create to store information that is unique to your organization. They are not built-in; members of your workspace can create and customize custom objects to hold information that standard objects aren't suitable for.
|
||||
Los objetos personalizados son objetos que puedes crear para almacenar información que es única para tu organización. No están incorporados; los miembros de tu espacio de trabajo pueden crear y personalizar objetos personalizados para albergar información para la cual los objetos estándar no son aptos.
|
||||
|
||||
## High-level schema
|
||||
## Esquema de alto nivel
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/custom-object-schema.png" alt="High level schema" />
|
||||
<img src="/images/docs/server/custom-object-schema.png" alt="Esquema de alto nivel" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
## How it works
|
||||
## Cómo funciona
|
||||
|
||||
Custom objects come from metadata tables that determine the shape, name, and type of the objects. All this information is present in the metadata schema database, consisting of tables:
|
||||
Los objetos personalizados provienen de tablas de metadatos que determinan la forma, el nombre y el tipo de los objetos. Toda esta información está presente en la base de datos del esquema de metadatos, que consta de tablas:
|
||||
|
||||
* **DataSource**: Details where the data is present.
|
||||
* **Object**: Describes the object and links to a DataSource.
|
||||
* **Field**: Outlines an Object's fields and connects to the Object.
|
||||
* **DataSource**: Detalles de dónde se encuentra la información.
|
||||
* **Object**: Describe el objeto y lo vincula a un DataSource.
|
||||
* **Field**: Describe los campos de un objeto y lo conecta al objeto.
|
||||
|
||||
To add a custom object, the workspaceMember will query the /metadata API. This updates the metadata accordingly and computes a GraphQL schema based on the metadata, storing it in a GQL cache for later use.
|
||||
Para añadir un objeto personalizado, el workspaceMember consultará la API de /metadata. Esto actualiza los metadatos de acuerdo y calcula un esquema GraphQL basado en los metadatos, almacenándolo en un caché de GQL para su uso posterior.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/add-custom-objects.jpeg" alt="Query the /metadata API to add custom objects" />
|
||||
<img src="/images/docs/server/add-custom-objects.jpeg" alt="Consultando la API de /metadata para añadir objetos personalizados" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
To fetch data, the process involves making queries through the /graphql endpoint and passing them through the Query Resolver.
|
||||
Para obtener datos, el proceso implica hacer consultas a través del endpoint /graphql y pasarlos a través del Query Resolver.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/custom-object-schema.png" alt="Query the /graphql endpoint to fetch data" />
|
||||
<img src="/images/docs/server/custom-object-schema.png" alt="Consulta el endpoint /graphql para obtener datos" />
|
||||
</div>
|
||||
|
||||
+12
-12
@@ -1,12 +1,12 @@
|
||||
---
|
||||
title: Feature Flags
|
||||
title: Indicadores de característica
|
||||
---
|
||||
|
||||
Feature flags are used to hide experimental features. For Twenty, they are set on workspace level and not on a user level.
|
||||
Los indicadores de característica se usan para ocultar características experimentales. Para Twenty, se configuran a nivel de espacio de trabajo y no a nivel de usuario.
|
||||
|
||||
## Adding a new feature flag
|
||||
## Agregar un nuevo indicador de característica
|
||||
|
||||
In `FeatureFlagKey.ts` add the feature flag:
|
||||
En `FeatureFlagKey.ts` agrega el indicador de característica:
|
||||
|
||||
```ts
|
||||
type FeatureFlagKey =
|
||||
@@ -14,7 +14,7 @@ type FeatureFlagKey =
|
||||
| ...;
|
||||
```
|
||||
|
||||
Also add it to the enum in `feature-flag.entity.ts`:
|
||||
También agrégalo al enum en `feature-flag.entity.ts`:
|
||||
|
||||
```ts
|
||||
enum FeatureFlagKeys {
|
||||
@@ -23,7 +23,7 @@ enum FeatureFlagKeys {
|
||||
}
|
||||
```
|
||||
|
||||
To apply a feature flag on a **backend** feature use:
|
||||
Para aplicar un indicador de característica en una característica de **backend** usa:
|
||||
|
||||
```ts
|
||||
@Gate({
|
||||
@@ -31,16 +31,16 @@ To apply a feature flag on a **backend** feature use:
|
||||
})
|
||||
```
|
||||
|
||||
To apply a feature flag on a **frontend** feature use:
|
||||
Para aplicar un indicador de característica en una característica de **frontend** usa:
|
||||
|
||||
```ts
|
||||
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FEATURENAME_ENABLED');
|
||||
```
|
||||
|
||||
## Configure feature flags for the deployment
|
||||
## Configurar indicadores de característica para el despliegue
|
||||
|
||||
Change the corresponding record in the Table `core.featureFlag`:
|
||||
Cambie el registro correspondiente en la Tabla `core.featureFlag`:
|
||||
|
||||
| id | key | workspaceId | value |
|
||||
| ------ | ------------------------ | ----------- | ------ |
|
||||
| Random | `IS_FEATURENAME_ENABLED` | WorkspaceID | `true` |
|
||||
| iD | clave | workspaceId | valor |
|
||||
| --------- | ------------------------ | ------------------------- | ----------- |
|
||||
| Aleatorio | `IS_FEATURENAME_ENABLED` | ID del espacio de trabajo | `verdadero` |
|
||||
|
||||
+62
-62
@@ -1,12 +1,12 @@
|
||||
---
|
||||
title: Folder Architecture
|
||||
info: A detailed look into our server folder architecture
|
||||
title: Arquitectura de Carpetas
|
||||
info: Una mirada detallada a la arquitectura de carpetas de nuestro servidor
|
||||
---
|
||||
|
||||
The backend directory structure is as follows:
|
||||
La estructura del directorio backend es la siguiente:
|
||||
|
||||
```
|
||||
server
|
||||
servidor
|
||||
└───ability
|
||||
└───constants
|
||||
└───core
|
||||
@@ -21,105 +21,105 @@ server
|
||||
└───utils
|
||||
```
|
||||
|
||||
## Ability
|
||||
## Habilidad
|
||||
|
||||
Defines permissions and includes handlers for each entity.
|
||||
Define permisos e incluye gestores para cada entidad.
|
||||
|
||||
## Decorators
|
||||
## Decoradores
|
||||
|
||||
Defines custom decorators in NestJS for added functionality.
|
||||
Define decoradores personalizados en NestJS para funcionalidad adicional.
|
||||
|
||||
See [custom decorators](https://docs.nestjs.com/custom-decorators) for more details.
|
||||
Ver [decoradores personalizados](https://docs.nestjs.com/custom-decorators) para más detalles.
|
||||
|
||||
## Filters
|
||||
## Filtros
|
||||
|
||||
Includes exception filters to handle exceptions that might occur in GraphQL endpoints.
|
||||
Incluye filtros de excepciones para manejar excepciones que puedan ocurrir en endpoints de GraphQL.
|
||||
|
||||
## Guards
|
||||
## Guardias
|
||||
|
||||
See [guards](https://docs.nestjs.com/guards) for more details.
|
||||
Ver [guardias](https://docs.nestjs.com/guards) para más detalles.
|
||||
|
||||
## Health
|
||||
## Salud
|
||||
|
||||
Includes a publicly available REST API (healthz) that returns a JSON to confirm whether the database is working as expected.
|
||||
Incluye una API REST públicamente disponible (healthz) que devuelve un JSON para confirmar si la base de datos está funcionando como se esperaba.
|
||||
|
||||
## Metadata
|
||||
## Metadatos
|
||||
|
||||
Defines custom objects and makes available a GraphQL API (graphql/metadata).
|
||||
Define objetos personalizados y hace disponible una API de GraphQL (graphql/metadata).
|
||||
|
||||
## Workspace
|
||||
## Espacio de trabajo
|
||||
|
||||
Generates and serves custom GraphQL schema based on the metadata.
|
||||
Genera y sirve un esquema GraphQL personalizado basado en los metadatos.
|
||||
|
||||
### Workspace Directory Structure
|
||||
### Estructura del Directorio de Espacio de Trabajo
|
||||
|
||||
```
|
||||
workspace
|
||||
|
||||
└───workspace-schema-builder
|
||||
└───factories
|
||||
└───graphql-types
|
||||
└───database
|
||||
└───construcción-esquema-espacio-de-trabajo
|
||||
└───fábricas
|
||||
└───tipos-graphql
|
||||
└───bases-datos
|
||||
└───interfaces
|
||||
└───object-definitions
|
||||
└───services
|
||||
└───storage
|
||||
└───utils
|
||||
└───workspace-resolver-builder
|
||||
└───factories
|
||||
└───definiciones-objetos
|
||||
└───servicios
|
||||
└───almacenamiento
|
||||
└───utilidades
|
||||
└───constructor-resolver-espacio-de-trabajo
|
||||
└───fábricas
|
||||
└───interfaces
|
||||
└───workspace-query-builder
|
||||
└───factories
|
||||
└───constructor-consultas-espacio-de-trabajo
|
||||
└───fábricas
|
||||
└───interfaces
|
||||
└───workspace-query-runner
|
||||
└───ejecutor-consultas-espacio-de-trabajo
|
||||
└───interfaces
|
||||
└───utils
|
||||
└───workspace-datasource
|
||||
└───workspace-manager
|
||||
└───workspace-migration-runner
|
||||
└───utils
|
||||
└───workspace.module.ts
|
||||
└───workspace.factory.spec.ts
|
||||
└───workspace.factory.ts
|
||||
└───utilidades
|
||||
└───fuente-datos-espacio-de-trabajo
|
||||
└───gestor-espacio-de-trabajo
|
||||
└───ejecutor-migraciones-espacio-de-trabajo
|
||||
└───utilidades
|
||||
└───espacio.trabajo.module.ts
|
||||
└───espacio.trabajo.factory.spec.ts
|
||||
└───espacio.trabajo.factory.ts
|
||||
```
|
||||
|
||||
The root of the workspace directory includes the `workspace.factory.ts`, a file containing the `createGraphQLSchema` function. This function generates workspace-specific schema by using the metadata to tailor a schema for individual workspaces. By separating the schema and resolver construction, we use the `makeExecutableSchema` function, which combines these discrete elements.
|
||||
La raíz del directorio de espacio de trabajo incluye el `espacio.trabajo.factory.ts`, un archivo que contiene la función `createGraphQLSchema`. Esta función genera un esquema específico para el espacio de trabajo utilizando los metadatos para adaptar un esquema para espacios de trabajo individuales. Al separar la construcción del esquema y del resolver, usamos la función `makeExecutableSchema`, que combina estos elementos discretos.
|
||||
|
||||
This strategy is not just about organization, but also helps with optimization, such as caching generated type definitions to enhance performance and scalability.
|
||||
Esta estrategia no solo se trata de organización, sino que también ayuda con la optimización, como el almacenamiento en caché de definiciones de tipos generados para mejorar el rendimiento y la escalabilidad.
|
||||
|
||||
### Workspace Schema builder
|
||||
### Constructor de Esquema de Espacio de Trabajo
|
||||
|
||||
Generates the GraphQL schema, and includes:
|
||||
Genera el esquema GraphQL, e incluye:
|
||||
|
||||
#### Factories:
|
||||
#### Fábricas:
|
||||
|
||||
Specialised constructors to generate GraphQL-related constructs.
|
||||
Constructores especializados para generar constructos relacionados con GraphQL.
|
||||
|
||||
* The type.factory translates field metadata into GraphQL types using `TypeMapperService`.
|
||||
* The type-definition.factory creates GraphQL input or output objects derived from `objectMetadata`.
|
||||
* La fábrica de tipos traduce los metadatos de los campos en tipos GraphQL utilizando `TypeMapperService`.
|
||||
* La fábrica de definiciones de tipos crea objetos de entrada o salida de GraphQL derivados de `objectMetadata`.
|
||||
|
||||
#### GraphQL Types
|
||||
#### Tipos GraphQL
|
||||
|
||||
Includes enumerations, inputs, objects, and scalars, and serves as the building blocks for the schema construction.
|
||||
Incluye enumeraciones, entradas, objetos y escalares, y sirve como bloques de construcción para la construcción del esquema.
|
||||
|
||||
#### Interfaces and Object Definitions
|
||||
#### Interfaces y Definiciones de Objetos
|
||||
|
||||
Contains the blueprints for GraphQL entities, and includes both predefined and custom types like `MONEY` or `URL`.
|
||||
Contiene los planos para entidades GraphQL, e incluye tanto tipos predefinidos como personalizados como `MONEY` o `URL`.
|
||||
|
||||
#### Services
|
||||
#### Servicios
|
||||
|
||||
Contains the service responsible for associating FieldMetadataType with its appropriate GraphQL scalar or query modifiers.
|
||||
Contiene el servicio responsable de asociar FieldMetadataType con su escalar de GraphQL apropiado o modificadores de consulta.
|
||||
|
||||
#### Storage
|
||||
#### Almacenamiento
|
||||
|
||||
Includes the `TypeDefinitionsStorage` class that contains reusable type definitions, preventing duplication of GraphQL types.
|
||||
Incluye la clase `TypeDefinitionsStorage` que contiene definiciones de tipos reutilizables, previniendo duplicación de tipos GraphQL.
|
||||
|
||||
### Workspace Resolver Builder
|
||||
### Constructor de Resolver de Espacio de Trabajo
|
||||
|
||||
Creates resolver functions for querying and mutating the GraphQL schema.
|
||||
Crea funciones de resolutor para consultar y modificar el esquema GraphQL.
|
||||
|
||||
Each factory in this directory is responsible for producing a distinct resolver type, such as the `FindManyResolverFactory`, designed for adaptable application across various tables.
|
||||
Cada fábrica en este directorio es responsable de producir un tipo de resolutor distinto, como el `FindManyResolverFactory`, diseñado para aplicación adaptable a través de varias tablas.
|
||||
|
||||
### Workspace Query Runner
|
||||
### Ejecutor de Consultas de Espacio de Trabajo
|
||||
|
||||
Runs the generated queries on the database and parses the result.
|
||||
Ejecuta las consultas generadas en la base de datos y analiza el resultado.
|
||||
|
||||
+10
-10
@@ -1,20 +1,20 @@
|
||||
---
|
||||
title: Message Queue
|
||||
title: Cola de Mensajes
|
||||
---
|
||||
|
||||
Queues facilitate async operations to be performed. They can be used for performing background tasks such as sending a welcome email on register.
|
||||
Each use case will have its own queue class extended from `MessageQueueServiceBase`.
|
||||
Las colas facilitan la realización de operaciones asíncronas. Se pueden utilizar para realizar tareas en segundo plano, como enviar un correo de bienvenida al registrarse.
|
||||
Cada caso de uso tendrá su propia clase de cola extendida de `MessageQueueServiceBase`.
|
||||
|
||||
Currently, we only support `bull-mq`[bull-mq](https://bullmq.io/) as the queue driver.
|
||||
Actualmente, solo soportamos `bull-mq`[bull-mq](https://bullmq.io/) como el controlador de cola.
|
||||
|
||||
## Steps to create and use a new queue
|
||||
## Pasos para crear y usar una nueva cola
|
||||
|
||||
1. Add a queue name for your new queue under enum `MESSAGE_QUEUES`.
|
||||
2. Provide the factory implementation of the queue with the queue name as the dependency token.
|
||||
3. Inject the queue that you created in the required module/service with the queue name as the dependency token.
|
||||
4. Add worker class with token based injection just like producer.
|
||||
1. Agregue un nombre de cola para su nueva cola en la enumeración `MESSAGE_QUEUES`.
|
||||
2. Proporcione la implementación de fábrica de la cola con el nombre de la cola como el token de dependencia.
|
||||
3. Inyecte la cola que creó en el módulo/servicio requerido con el nombre de la cola como el token de dependencia.
|
||||
4. Agregue una clase de trabajador con inyección basada en token, al igual que el productor.
|
||||
|
||||
### Example usage
|
||||
### Ejemplo de uso
|
||||
|
||||
```ts
|
||||
class Resolver {
|
||||
|
||||
+26
-27
@@ -1,19 +1,19 @@
|
||||
---
|
||||
title: Backend Commands
|
||||
title: Comandos de Backend
|
||||
---
|
||||
|
||||
## Useful commands
|
||||
## Comandos útiles
|
||||
|
||||
These commands should be executed from packages/twenty-server folder.
|
||||
From any other folder you can run `npx nx {command} twenty-server` (or `npx nx run twenty-server:{command}`).
|
||||
Estos comandos deben ejecutarse desde la carpeta packages/twenty-server.
|
||||
Desde cualquier otra carpeta, puedes ejecutar `npx nx {command} twenty-server` (o `npx nx run twenty-server:{command}`).
|
||||
|
||||
### First time setup
|
||||
### Configuración inicial
|
||||
|
||||
```
|
||||
npx nx database:reset twenty-server # setup the database with dev seeds
|
||||
```
|
||||
|
||||
### Starting the server
|
||||
### Iniciando el servidor
|
||||
|
||||
```
|
||||
npx nx run twenty-server:start
|
||||
@@ -25,53 +25,52 @@ npx nx run twenty-server:start
|
||||
npx nx run twenty-server:lint # pass --fix to fix lint errors
|
||||
```
|
||||
|
||||
### Test
|
||||
### Prueba
|
||||
|
||||
```
|
||||
npx nx run twenty-server:test:unit # run unit tests
|
||||
npx nx run twenty-server:test:integration # run integration tests
|
||||
```
|
||||
|
||||
Note: you can run `npx nx run twenty-server:test:integration:with-db-reset` in case you need to reset the database before running the integration tests.
|
||||
Nota: puedes ejecutar `npx nx run twenty-server:test:integration:with-db-reset` en caso de que necesites restablecer la base de datos antes de ejecutar las pruebas de integración.
|
||||
|
||||
### Resetting the database
|
||||
### Restablecer la base de datos
|
||||
|
||||
If you want to reset and seed the database, you can run the following command:
|
||||
Si deseas restablecer y sembrar la base de datos, puedes ejecutar el siguiente comando:
|
||||
|
||||
```bash
|
||||
npx nx run twenty-server:database:reset
|
||||
```
|
||||
|
||||
### Migrations
|
||||
### Migraciones
|
||||
|
||||
#### For objects in Core/Metadata schemas (TypeORM)
|
||||
#### Para objetos en esquemas Core/Metadata (TypeORM)
|
||||
|
||||
```bash
|
||||
npx nx run twenty-server:typeorm migration:generate src/database/typeorm/core/migrations/nameOfYourMigration -d src/database/typeorm/core/core.datasource.ts
|
||||
```
|
||||
|
||||
#### For Workspace objects
|
||||
#### Para objetos de Workspace
|
||||
|
||||
There are no migrations files, migration are generated automatically for each workspace,
|
||||
stored in the database, and applied with this command
|
||||
No hay archivos de migraciones, las migraciones se generan automáticamente para cada espacio de trabajo, se almacenan en la base de datos y se aplican con este comando
|
||||
|
||||
```bash
|
||||
npx nx run twenty-server:command workspace:sync-metadata -f
|
||||
```
|
||||
|
||||
<Warning>
|
||||
This will drop the database and re-run the migrations and seed.
|
||||
Esto eliminará la base de datos y volverá a ejecutar las migraciones y semillas.
|
||||
|
||||
Make sure to back up any data you want to keep before running this command.
|
||||
Asegúrate de respaldar cualquier dato que desees conservar antes de ejecutar este comando.
|
||||
</Warning>
|
||||
|
||||
## Tech Stack
|
||||
## Stack Tecnológico
|
||||
|
||||
Twenty primarily uses NestJS for the backend.
|
||||
Twenty utiliza principalmente NestJS para el backend.
|
||||
|
||||
Prisma was the first ORM we used. But in order to allow users to create custom fields and custom objects, a lower-level made more sense as we need to have fine-grained control. The project now uses TypeORM.
|
||||
Prisma fue el primer ORM que usamos. Pero para permitir a los usuarios crear campos y objetos personalizados, un nivel más bajo tenía más sentido ya que necesitamos tener un control detallado. El proyecto ahora usa TypeORM.
|
||||
|
||||
Here's what the tech stack now looks like.
|
||||
Así es como se ve la pila tecnológica ahora.
|
||||
|
||||
**Core**
|
||||
|
||||
@@ -79,23 +78,23 @@ Here's what the tech stack now looks like.
|
||||
* [TypeORM](https://typeorm.io/)
|
||||
* [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
|
||||
|
||||
**Database**
|
||||
**Base de datos**
|
||||
|
||||
* [Postgres](https://www.postgresql.org/)
|
||||
|
||||
**Third-party integrations**
|
||||
**Integraciones de terceros**
|
||||
|
||||
* [Sentry](https://sentry.io/welcome/) for tracking bugs
|
||||
* [Sentry](https://sentry.io/welcome/) para rastrear errores
|
||||
|
||||
**Testing**
|
||||
**Pruebas**
|
||||
|
||||
* [Jest](https://jestjs.io/)
|
||||
|
||||
**Tooling**
|
||||
**Herramientas**
|
||||
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
**Development**
|
||||
**Desarrollo**
|
||||
|
||||
* [AWS EKS](https://aws.amazon.com/eks/)
|
||||
|
||||
+20
-20
@@ -1,18 +1,18 @@
|
||||
---
|
||||
title: Zapier App
|
||||
title: Aplicación Zapier
|
||||
---
|
||||
|
||||
Effortlessly sync Twenty with 3000+ apps using [Zapier](https://zapier.com/). Automate tasks, boost productivity, and supercharge your customer relationships!
|
||||
Sincroniza sin esfuerzo Twenty con más de 3000 aplicaciones usando [Zapier](https://zapier.com/). ¡Automatiza tareas, mejora la productividad y potencia tus relaciones con los clientes!
|
||||
|
||||
## About Zapier
|
||||
## Acerca de Zapier
|
||||
|
||||
Zapier is a tool that allows you to automate workflows by connecting the apps that your team uses every day. The fundamental concept of Zapier is automation workflows, called Zaps, and include triggers and actions.
|
||||
Zapier es una herramienta que permite automatizar flujos de trabajo al conectar las aplicaciones que tu equipo utiliza a diario. El concepto fundamental de Zapier son los flujos de trabajo automatizados, llamados Zaps, que incluyen desencadenantes y acciones.
|
||||
|
||||
You can learn more about how Zapier works [here](https://zapier.com/how-it-works).
|
||||
Puedes aprender más sobre cómo funciona Zapier [aquí](https://zapier.com/how-it-works).
|
||||
|
||||
## Setup
|
||||
## Configuración
|
||||
|
||||
### Step 1: Install Zapier packages
|
||||
### Paso 1: Instalar paquetes de Zapier
|
||||
|
||||
```bash
|
||||
cd packages/twenty-zapier
|
||||
@@ -20,33 +20,33 @@ cd packages/twenty-zapier
|
||||
yarn
|
||||
```
|
||||
|
||||
### Step 2: Login with the CLI
|
||||
### Paso 2: Iniciar sesión con la CLI
|
||||
|
||||
Use your Zapier credentials to log in using the CLI:
|
||||
Utiliza tus credenciales de Zapier para iniciar sesión usando la CLI:
|
||||
|
||||
```bash
|
||||
zapier login
|
||||
```
|
||||
|
||||
### Step 3: Set environment variables
|
||||
### Paso 3: Configurar variables de entorno
|
||||
|
||||
From the `packages/twenty-zapier` folder, run:
|
||||
Desde la carpeta `packages/twenty-zapier`, ejecuta:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Run the application locally, go to [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks), and generate an API key.
|
||||
Ejecuta la aplicación localmente, ve a [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks) y genera una clave API.
|
||||
|
||||
Replace the **YOUR_API_KEY** value in the `.env` file with the API key you just generated.
|
||||
Reemplaza el valor de **YOUR_API_KEY** en el archivo `.env` con la clave API que acabas de generar.
|
||||
|
||||
## Development
|
||||
## Desarrollo
|
||||
|
||||
<Warning>
|
||||
Make sure to run `yarn build` before any `zapier` command.
|
||||
Asegúrate de ejecutar `yarn build` antes de cualquier comando `zapier`.
|
||||
</Warning>
|
||||
|
||||
### Test
|
||||
### Prueba
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
@@ -58,25 +58,25 @@ yarn test
|
||||
yarn format
|
||||
```
|
||||
|
||||
### Watch and compile as you edit code
|
||||
### Observa y compila mientras editas el código
|
||||
|
||||
```bash
|
||||
yarn watch
|
||||
```
|
||||
|
||||
### Validate your Zapier app
|
||||
### Valida tu aplicación Zapier
|
||||
|
||||
```bash
|
||||
yarn validate
|
||||
```
|
||||
|
||||
### Deploy your Zapier app
|
||||
### Despliega tu aplicación Zapier
|
||||
|
||||
```bash
|
||||
yarn deploy
|
||||
```
|
||||
|
||||
### List all Zapier CLI commands
|
||||
### Lista todos los comandos de Zapier CLI
|
||||
|
||||
```bash
|
||||
zapier
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
---
|
||||
title: Bugs, Requests & Pull Requests
|
||||
info: Report issues, request features, and contribute code
|
||||
title: Errores, solicitudes y pull requests
|
||||
info: Informa de problemas, solicita funcionalidades y contribuye con código
|
||||
---
|
||||
|
||||
## Reporting Bugs
|
||||
## Reportar errores
|
||||
|
||||
To report a bug, please [create an issue on GitHub](https://github.com/twentyhq/twenty/issues/new).
|
||||
Para reportar un error, por favor [crea un problema en GitHub](https://github.com/twentyhq/twenty/issues/new).
|
||||
|
||||
You can also ask for help on [Discord](https://discord.gg/cx5n4Jzs57).
|
||||
También puedes pedir ayuda en [Discord](https://discord.gg/cx5n4Jzs57).
|
||||
|
||||
## Feature Requests
|
||||
## Solicitudes de funciones
|
||||
|
||||
If you're not sure if it's a bug, and you feel it's closer to a feature request, then you should probably [open a discussion instead](https://github.com/twentyhq/twenty/discussions/new).
|
||||
Si no estás seguro de si es un error y sientes que está más cerca de una solicitud de función, entonces probablemente deberías [abrir una discusión en su lugar](https://github.com/twentyhq/twenty/discussions/new).
|
||||
|
||||
## Submit a Pull Request
|
||||
## Envía un pull request
|
||||
|
||||
Contributing code to Twenty starts with a pull request (PR).
|
||||
Contribuir código a Twenty comienza con un pull request (PR).
|
||||
|
||||
### Before You Start
|
||||
### Antes de empezar
|
||||
|
||||
1. Check [existing issues](https://github.com/twentyhq/twenty/issues) for related work
|
||||
2. For new features, open an issue first to discuss
|
||||
3. Review our [Code of Conduct](https://github.com/twentyhq/twenty/blob/main/CODE_OF_CONDUCT.md)
|
||||
1. Consulta [los issues existentes](https://github.com/twentyhq/twenty/issues) para ver trabajos relacionados
|
||||
2. Para nuevas funcionalidades, abre primero un issue para discutir
|
||||
3. Revisa nuestro [Código de conducta](https://github.com/twentyhq/twenty/blob/main/CODE_OF_CONDUCT.md)
|
||||
|
||||
### Fork and Clone
|
||||
### Haz un fork y clona
|
||||
|
||||
1. Fork the repository on GitHub
|
||||
2. Clone your fork:
|
||||
1. Haz un fork del repositorio en GitHub
|
||||
2. Clona tu fork:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/twenty.git
|
||||
cd twenty
|
||||
```
|
||||
|
||||
3. Add upstream remote:
|
||||
3. Añade el remoto upstream:
|
||||
|
||||
```bash
|
||||
git remote add upstream https://github.com/twentyhq/twenty.git
|
||||
```
|
||||
|
||||
### Create a Branch
|
||||
### Crea una rama
|
||||
|
||||
```bash
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
|
||||
Use descriptive branch names:
|
||||
Usa nombres de rama descriptivos:
|
||||
|
||||
* `feature/add-export-button`
|
||||
* `fix/login-redirect-issue`
|
||||
* `docs/update-api-guide`
|
||||
|
||||
### Make Your Changes
|
||||
### Realiza tus cambios
|
||||
|
||||
1. Write clean, well-documented code
|
||||
2. Follow existing code style
|
||||
3. Add tests for new functionality
|
||||
4. Update documentation if needed
|
||||
1. Escribe código limpio y bien documentado
|
||||
2. Sigue el estilo de código existente
|
||||
3. Añade pruebas para la nueva funcionalidad
|
||||
4. Actualiza la documentación si es necesario
|
||||
|
||||
### Submit Your PR
|
||||
### Envía tu PR
|
||||
|
||||
1. Push your branch:
|
||||
1. Haz push de tu rama:
|
||||
|
||||
```bash
|
||||
git push origin feature/your-feature-name
|
||||
```
|
||||
|
||||
2. Open a PR on GitHub
|
||||
3. Fill in the PR template
|
||||
4. Link related issues
|
||||
2. Abre un PR en GitHub
|
||||
3. Rellena la plantilla del PR
|
||||
4. Vincula los issues relacionados
|
||||
|
||||
### PR Checklist
|
||||
### Lista de verificación de PR
|
||||
|
||||
* [ ] Code follows project style guidelines
|
||||
* [ ] Tests pass locally
|
||||
* [ ] Documentation is updated
|
||||
* [ ] PR description explains the changes
|
||||
* [ ] El código sigue las pautas de estilo del proyecto
|
||||
* [ ] Las pruebas pasan localmente
|
||||
* [ ] La documentación está actualizada
|
||||
* [ ] La descripción del PR explica los cambios
|
||||
|
||||
+80
-80
@@ -1,19 +1,19 @@
|
||||
---
|
||||
title: Best Practices
|
||||
title: Mejores prácticas
|
||||
---
|
||||
|
||||
This document outlines the best practices you should follow when working on the frontend.
|
||||
Este documento describe las mejores prácticas que debes seguir al trabajar en el frontend.
|
||||
|
||||
## State management
|
||||
## Gestión de estado
|
||||
|
||||
React and Recoil handle state management in the codebase.
|
||||
React y Recoil manejan la gestión de estado en la base de código.
|
||||
|
||||
### Use `useRecoilState` to store state
|
||||
### Usa `useRecoilState` para almacenar el estado
|
||||
|
||||
It's good practice to create as many atoms as you need to store your state.
|
||||
Es buena práctica crear tantos átomos como necesites para almacenar tu estado.
|
||||
|
||||
<Warning>
|
||||
It's better to use extra atoms than trying to be too concise with props drilling.
|
||||
Es mejor usar átomos adicionales que intentar ser demasiado concisos con la perforación de props.
|
||||
</Warning>
|
||||
|
||||
```tsx
|
||||
@@ -36,29 +36,29 @@ export const MyComponent = () => {
|
||||
}
|
||||
```
|
||||
|
||||
### Do not use `useRef` to store state
|
||||
### No uses `useRef` para almacenar el estado
|
||||
|
||||
Avoid using `useRef` to store state.
|
||||
Evita usar `useRef` para almacenar el estado.
|
||||
|
||||
If you want to store state, you should use `useState` or `useRecoilState`.
|
||||
|
||||
See [how to manage re-renders](#managing-re-renders) if you feel like you need `useRef` to prevent some re-renders from happening.
|
||||
Consulta [cómo gestionar las re-renderizaciones](#managing-re-renders) si sientes que necesitas `useRef` para evitar algunas re-renderizaciones.
|
||||
|
||||
## Managing re-renders
|
||||
## Gestión de las re-renderizaciones
|
||||
|
||||
Re-renders can be hard to manage in React.
|
||||
Las re-renderizaciones pueden ser difíciles de gestionar en React.
|
||||
|
||||
Here are some rules to follow to avoid unnecessary re-renders.
|
||||
Aquí hay algunas reglas a seguir para evitar re-renderizaciones innecesarias.
|
||||
|
||||
Keep in mind that you can **always** avoid re-renders by understanding their cause.
|
||||
Ten en cuenta que siempre puedes evitar re-renderizaciones comprendiendo su causa.
|
||||
|
||||
### Work at the root level
|
||||
### Trabaja a nivel de raíz
|
||||
|
||||
Avoiding re-renders in new features is now made easy by eliminating them at the root level.
|
||||
Ahora es fácil evitar re-renderizaciones en nuevas funciones eliminándolas a nivel de raíz.
|
||||
|
||||
The `PageChangeEffect` sidecar component contains just one `useEffect` that holds all the logic to execute on a page change.
|
||||
|
||||
That way you know that there's just one place that can trigger a re-render.
|
||||
De esa manera, sabes que solo hay un lugar que puede desencadenar una re-renderización.
|
||||
|
||||
### Always think twice before adding `useEffect` in your codebase
|
||||
|
||||
@@ -68,17 +68,17 @@ You should think whether you need `useEffect`, or if you can move the logic in a
|
||||
|
||||
You'll find it generally easy to move the logic in a `handleClick` or `handleChange` function.
|
||||
|
||||
You can also find them in libraries like Apollo: `onCompleted`, `onError`, etc.
|
||||
También puedes encontrarlas en bibliotecas como Apollo: `onCompleted`, `onError`, etc.
|
||||
|
||||
### Use a sibling component to extract `useEffect` or data fetching logic
|
||||
|
||||
If you feel like you need to add a `useEffect` in your root component, you should consider extracting it in a sidecar component.
|
||||
|
||||
You can apply the same for data fetching logic, with Apollo hooks.
|
||||
Puedes aplicar lo mismo para la lógica de obtención de datos, con hooks de Apollo.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, will cause re-renders even if data is not changing,
|
||||
// because useEffect needs to be re-evaluated
|
||||
// ❌ Malo, provocará re-renderizados incluso si los datos no cambian,
|
||||
// porque useEffect necesita volver a evaluarse
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useRecoilState(dataState);
|
||||
const [someDependency] = useRecoilState(someDependencyState);
|
||||
@@ -100,8 +100,8 @@ export const App = () => (
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, will not cause re-renders if data is not changing,
|
||||
// because useEffect is re-evaluated in another sibling component
|
||||
// ✅ Bueno, no provocará re-renderizados si los datos no cambian,
|
||||
// porque useEffect se vuelve a evaluar en otro componente hermano
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useRecoilState(dataState);
|
||||
|
||||
@@ -129,84 +129,84 @@ export const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Use recoil family states and recoil family selectors
|
||||
### Usa estados de familia de recoil y selectores de familia de recoil
|
||||
|
||||
Recoil family states and selectors are a great way to avoid re-renders.
|
||||
Los estados y selectores de familia de recoil son una gran manera de evitar re-renderizaciones.
|
||||
|
||||
They are useful when you need to store a list of items.
|
||||
Son útiles cuando necesitas almacenar una lista de elementos.
|
||||
|
||||
### You shouldn't use `React.memo(MyComponent)`
|
||||
### No deberías usar `React.memo(MyComponent)`
|
||||
|
||||
Avoid using `React.memo()` because it does not solve the cause of the re-render, but instead breaks the re-render chain, which can lead to unexpected behavior and make the code very hard to refactor.
|
||||
Evita usar `React.memo()` porque no resuelve la causa de la re-renderización, sino que rompe la cadena de re-renderización, lo que puede llevar a un comportamiento inesperado y hacer que el código sea muy difícil de refactorizar.
|
||||
|
||||
### Limit `useCallback` or `useMemo` usage
|
||||
### Limita el uso de `useCallback` o `useMemo`
|
||||
|
||||
They are often not necessary and will make the code harder to read and maintain for a gain of performance that is unnoticeable.
|
||||
A menudo no son necesarios y harán que el código sea más difícil de leer y mantener para una ganancia de rendimiento que es imperceptible.
|
||||
|
||||
## Console.logs
|
||||
|
||||
`console.log` statements are valuable during development, offering real-time insights into variable values and code flow. But, leaving them in production code can lead to several issues:
|
||||
Las declaraciones `console.log` son valiosas durante el desarrollo, ofreciendo información en tiempo real sobre los valores de las variables y el flujo de código. Pero, dejarlas en el código de producción puede llevar a varios problemas:
|
||||
|
||||
1. **Performance**: Excessive logging can affect the runtime performance, especially on client-side applications.
|
||||
1. **Rendimiento**: El registro excesivo puede afectar el rendimiento en tiempo de ejecución, especialmente en aplicaciones del lado del cliente.
|
||||
|
||||
2. **Security**: Logging sensitive data can expose critical information to anyone who inspects the browser's console.
|
||||
2. **Seguridad**: Registrar datos sensibles puede exponer información crítica a cualquier persona que inspeccione la consola del navegador.
|
||||
|
||||
3. **Cleanliness**: Filling up the console with logs can obscure important warnings or errors that developers or tools need to see.
|
||||
3. **Limpieza**: Llenar la consola con registros puede oscurecer advertencias o errores importantes que los desarrolladores o herramientas necesitan ver.
|
||||
|
||||
4. **Professionalism**: End users or clients checking the console and seeing a myriad of log statements might question the code's quality and polish.
|
||||
4. **Profesionalismo**: Los usuarios finales o clientes que revisen la consola y vean una miríada de declaraciones de registros podrían cuestionar la calidad y el acabado del código.
|
||||
|
||||
Make sure you remove all `console.logs` before pushing the code to production.
|
||||
Asegúrate de eliminar todos los `console.logs` antes de enviar el código a producción.
|
||||
|
||||
## Naming
|
||||
## Nomenclatura
|
||||
|
||||
### Variable Naming
|
||||
### Nombres de variables
|
||||
|
||||
Variable names ought to precisely depict the purpose or function of the variable.
|
||||
Los nombres de variables deben describir con precisión el propósito o función de la variable.
|
||||
|
||||
#### The issue with generic names
|
||||
#### El problema con los nombres genéricos
|
||||
|
||||
Generic names in programming are not ideal because they lack specificity, leading to ambiguity and reduced code readability. Such names fail to convey the variable or function's purpose, making it challenging for developers to understand the code's intent without deeper investigation. This can result in increased debugging time, higher susceptibility to errors, and difficulties in maintenance and collaboration. Meanwhile, descriptive naming makes the code self-explanatory and easier to navigate, enhancing code quality and developer productivity.
|
||||
Los nombres genéricos en programación no son ideales porque carecen de especificidad, lo que lleva a la ambigüedad y reduce la legibilidad del código. Tales nombres no transmiten el propósito de la variable o función, lo que dificulta a los desarrolladores entender la intención del código sin una investigación más profunda. Esto puede resultar en un aumento del tiempo de depuración, una mayor susceptibilidad a errores y dificultades en el mantenimiento y colaboración. Mientras tanto, la nomenclatura descriptiva hace que el código sea autoexplicativo y más fácil de navegar, mejorando la calidad del código y la productividad del desarrollador.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, uses a generic name that doesn't communicate its
|
||||
// purpose or content clearly
|
||||
// ❌ Malo, usa un nombre genérico que no comunica claramente su
|
||||
// propósito o contenido
|
||||
const [value, setValue] = useState('');
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, uses a descriptive name
|
||||
// ✅ Bueno, usa un nombre descriptivo
|
||||
const [email, setEmail] = useState('');
|
||||
```
|
||||
|
||||
#### Some words to avoid in variable names
|
||||
#### Algunas palabras a evitar en los nombres de variables
|
||||
|
||||
* dummy
|
||||
* ficticio
|
||||
|
||||
### Event handlers
|
||||
### Manejadores de eventos
|
||||
|
||||
Event handler names should start with `handle`, while `on` is a prefix used to name events in components props.
|
||||
Los nombres de manejadores de eventos deben comenzar con `handle`, mientras que `on` es un prefijo usado para nombrar eventos en las props de los componentes.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
// ❌ Malo
|
||||
const onEmailChange = (val: string) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good
|
||||
// ✅ Bueno
|
||||
const handleEmailChange = (val: string) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
## Optional Props
|
||||
## Props opcionales
|
||||
|
||||
Avoid passing the default value for an optional prop.
|
||||
Evita pasar el valor predeterminado para una prop opcional.
|
||||
|
||||
**EXAMPLE**
|
||||
**EJEMPLO**
|
||||
|
||||
Take the`EmailField` component defined below:
|
||||
Toma el componente `EmailField` definido a continuación:
|
||||
|
||||
```tsx
|
||||
type EmailFieldProps = {
|
||||
@@ -219,28 +219,28 @@ const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
|
||||
);
|
||||
```
|
||||
|
||||
**Usage**
|
||||
**Uso**
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, passing in the same value as the default value adds no value
|
||||
// ❌ Malo, pasar el mismo valor que el valor predeterminado no aporta nada
|
||||
const Form = () => <EmailField value="[email protected]" disabled={false} />;
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, assumes the default value
|
||||
// ✅ Bueno, asume el valor predeterminado
|
||||
const Form = () => <EmailField value="[email protected]" />;
|
||||
```
|
||||
|
||||
## Component as props
|
||||
## Componente como props
|
||||
|
||||
Try as much as possible to pass uninstantiated components as props, so children can decide on their own of what props they need to pass.
|
||||
Intenta tanto como sea posible pasar componentes no instanciados como propiedades, para que los hijos puedan decidir por sí mismos qué propiedades necesitan pasar.
|
||||
|
||||
The most common example for that is icon components:
|
||||
El ejemplo más común de esto son los componentes de icono:
|
||||
|
||||
```tsx
|
||||
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
|
||||
|
||||
// In MyComponent
|
||||
// En MyComponent
|
||||
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -252,25 +252,25 @@ const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
|
||||
};
|
||||
```
|
||||
|
||||
For React to understand that the component is a component, you need to use PascalCase, to later instantiate it with `<MyIcon>`
|
||||
Para que React entienda que el componente es un componente, necesitas usar PascalCase, para luego instanciarlo con `<MyIcon>`
|
||||
|
||||
## Prop Drilling: Keep It Minimal
|
||||
## Prop Drilling: Mantenlo Minimalista
|
||||
|
||||
Prop drilling, in the React context, refers to the practice of passing state variables and their setters through many component layers, even if intermediary components don't use them. While sometimes necessary, excessive prop drilling can lead to:
|
||||
El prop drilling, en el contexto de React, se refiere a la práctica de pasar variables de estado y sus setters a través de muchas capas de componentes, incluso si los componentes intermedios no los usan. Aunque a veces es necesario, el exceso de prop drilling puede llevar a:
|
||||
|
||||
1. **Decreased Readability**: Tracing where a prop originates or where it's utilized can become convoluted in a deeply nested component structure.
|
||||
1. **Readabilidad Reducida**: Rastrear de dónde proviene una propiedad o dónde se utiliza puede volverse complicado en una estructura de componentes muy anidada.
|
||||
|
||||
2. **Maintenance Challenges**: Changes in one component's prop structure might require adjustments in several components, even if they don't directly use the prop.
|
||||
2. **Desafíos de Mantenimiento**: Los cambios en la estructura de propiedades de un componente podrían requerir ajustes en varios componentes, incluso si no utilizan directamente la propiedad.
|
||||
|
||||
3. **Reduced Component Reusability**: A component receiving a lot of props solely for passing them down becomes less general-purpose and harder to reuse in different contexts.
|
||||
3. **Reducción de la Reusabilidad del Componente**: Un componente que recibe muchas propiedades solo para pasarlas se vuelve menos general y más difícil de reutilizar en diferentes contextos.
|
||||
|
||||
If you feel that you are using excessive prop drilling, see [state management best practices](#state-management).
|
||||
Si sientes que estás usando en exceso el prop drilling, consulta [mejores prácticas de gestión de estado](#state-management).
|
||||
|
||||
## Imports
|
||||
## Importar
|
||||
|
||||
When importing, opt for the designated aliases rather than specifying complete or relative paths.
|
||||
Al importar, opta por los alias designados en lugar de especificar rutas completas o relativas.
|
||||
|
||||
**The Aliases**
|
||||
**Alias del identificador**
|
||||
|
||||
```js
|
||||
{
|
||||
@@ -282,10 +282,10 @@ When importing, opt for the designated aliases rather than specifying complete o
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**
|
||||
**Uso**
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, specifies the entire relative path
|
||||
// ❌ Malo, especifica toda la ruta relativa
|
||||
import {
|
||||
CatalogDecorator
|
||||
} from '../../../../../testing/decorators/CatalogDecorator';
|
||||
@@ -295,14 +295,14 @@ import {
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, utilises the designated aliases
|
||||
// ✅ Bueno, utiliza los alias designados
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
```
|
||||
|
||||
## Schema Validation
|
||||
## Validación de Esquema
|
||||
|
||||
[Zod](https://github.com/colinhacks/zod) is the schema validator for untyped objects:
|
||||
[Zod](https://github.com/colinhacks/zod) es el validador de esquemas para objetos no tipados:
|
||||
|
||||
```js
|
||||
const validationSchema = z
|
||||
@@ -310,16 +310,16 @@ const validationSchema = z
|
||||
exist: z.boolean(),
|
||||
email: z
|
||||
.string()
|
||||
.email('Email must be a valid email'),
|
||||
.email('El correo electrónico debe ser válido'),
|
||||
password: z
|
||||
.string()
|
||||
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
|
||||
.regex(PASSWORD_REGEX, 'La contraseña debe contener al menos 8 caracteres'),
|
||||
})
|
||||
.required();
|
||||
|
||||
type Form = z.infer<typeof validationSchema>;
|
||||
```
|
||||
|
||||
## Breaking Changes
|
||||
## Cambios Cruciales
|
||||
|
||||
Always perform thorough manual testing before proceeding to guarantee that modifications haven’t caused disruptions elsewhere, given that tests have not yet been extensively integrated.
|
||||
Siempre ejecuta pruebas manuales exhaustivas antes de proceder para garantizar que las modificaciones no hayan causado interrupciones en otras partes, dado que las pruebas aún no se han integrado extensivamente.
|
||||
|
||||
+36
-36
@@ -1,11 +1,11 @@
|
||||
---
|
||||
title: Folder Architecture
|
||||
info: A detailed look into our folder architecture
|
||||
title: Arquitectura de Carpetas
|
||||
info: Una mirada detallada a nuestra arquitectura de carpetas
|
||||
---
|
||||
|
||||
In this guide, you will explore the details of the project directory structure and how it contributes to the organization and maintainability of Twenty.
|
||||
En esta guía, explorarás los detalles de la estructura del directorio del proyecto y cómo contribuye a la organización y mantenibilidad de Twenty.
|
||||
|
||||
By following this folder architecture convention, it's easier to find the files related to specific features and ensure that the application is scalable and maintainable.
|
||||
Siguiendo esta convención de arquitectura de carpetas, es más fácil encontrar los archivos relacionados con funciones específicas y asegurar que la aplicación sea escalable y mantenible.
|
||||
|
||||
```
|
||||
front
|
||||
@@ -22,14 +22,14 @@ front
|
||||
└───...
|
||||
```
|
||||
|
||||
## Pages
|
||||
## Páginas
|
||||
|
||||
Includes the top-level components defined by the application routes. They import more low-level components from the modules folder (more details below).
|
||||
Incluye los componentes de alto nivel definidos por las rutas de la aplicación. Importan más componentes de bajo nivel de la carpeta de módulos (más detalles abajo).
|
||||
|
||||
## Modules
|
||||
## Módulos
|
||||
|
||||
Each module represents a feature or a group of feature, comprising its specific components, states, and operational logic.
|
||||
They should all follow the structure below. You can nest modules within modules (referred to as submodules) and the same rules will apply.
|
||||
Cada módulo representa una función o un grupo de funciones, comprendiendo sus componentes específicos, estados y lógica operativa.
|
||||
Todos deberían seguir la estructura siguiente. Puedes anidar módulos dentro de módulos (denominados submódulos) y se aplicarán las mismas reglas.
|
||||
|
||||
```
|
||||
module1
|
||||
@@ -50,60 +50,60 @@ module1
|
||||
└───utils
|
||||
```
|
||||
|
||||
### Contexts
|
||||
### Contextos
|
||||
|
||||
A context is a way to pass data through the component tree without having to pass props down manually at every level.
|
||||
Un contexto es una manera de pasar datos a través del árbol de componentes sin tener que pasar propiedades manualmente en cada nivel.
|
||||
|
||||
See [React Context](https://react.dev/reference/react#context-hooks) for more details.
|
||||
Ver [React Context](https://react.dev/reference/react#context-hooks) para más detalles.
|
||||
|
||||
### GraphQL
|
||||
|
||||
Includes fragments, queries, and mutations.
|
||||
Incluye fragmentos, consultas y mutaciones.
|
||||
|
||||
See [GraphQL](https://graphql.org/learn/) for more details.
|
||||
Ver [GraphQL](https://graphql.org/learn/) para más detalles.
|
||||
|
||||
* Fragments
|
||||
* Fragmentos
|
||||
|
||||
A fragment is a reusable piece of a query, which you can use in different places. By using fragments, it's easier to avoid duplicating code.
|
||||
Un fragmento es una parte reutilizable de una consulta, que puedes usar en diferentes lugares. Usando fragmentos, es más fácil evitar la duplicación de código.
|
||||
|
||||
See [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) for more details.
|
||||
Ver [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) para más detalles.
|
||||
|
||||
* Queries
|
||||
* Consultas
|
||||
|
||||
See [GraphQL Queries](https://graphql.org/learn/queries/) for more details.
|
||||
Ver [GraphQL Queries](https://graphql.org/learn/queries/) para más detalles.
|
||||
|
||||
* Mutations
|
||||
* Mutaciones
|
||||
|
||||
See [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) for more details.
|
||||
Ver [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) para más detalles.
|
||||
|
||||
### Hooks
|
||||
|
||||
See [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) for more details.
|
||||
Ver [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) para más detalles.
|
||||
|
||||
### States
|
||||
### Estados
|
||||
|
||||
Contains the state management logic. [RecoilJS](https://recoiljs.org) handles this.
|
||||
Contiene la lógica de gestión de estado. [RecoilJS](https://recoiljs.org) maneja esto.
|
||||
|
||||
* Selectors: See [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) for more details.
|
||||
* Selectores: Ver [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) para más detalles.
|
||||
|
||||
React's built-in state management still handles state within a component.
|
||||
La gestión de estado incorporada de React todavía maneja el estado dentro de un componente.
|
||||
|
||||
### Utils
|
||||
### Utilidades
|
||||
|
||||
Should just contain reusable pure functions. Otherwise, create custom hooks in the `hooks` folder.
|
||||
Debería contener solo funciones puras reutilizables. De lo contrario, crea hooks personalizados en la carpeta `hooks`.
|
||||
|
||||
## UI
|
||||
## Interfaz de usuario
|
||||
|
||||
Contains all the reusable UI components used in the application.
|
||||
Contiene todos los componentes de interfaz de usuario reutilizables utilizados en la aplicación.
|
||||
|
||||
This folder can contain sub-folders, like `data`, `display`, `feedback`, and `input` for specific types of components. Each component should be self-contained and reusable, so that you can use it in different parts of the application.
|
||||
Esta carpeta puede contener subcarpetas, como `datos`, `visualización`, `retroalimentación` y `entrada` para tipos específicos de componentes. Cada componente debe ser autónomo y reutilizable, para que puedas usarlo en diferentes partes de la aplicación.
|
||||
|
||||
By separating the UI components from the other components in the `modules` folder, it's easier to maintain a consistent design and to make changes to the UI without affecting other parts (business logic) of the codebase.
|
||||
Al separar los componentes de la interfaz de usuario de otros componentes en la carpeta `modules`, es más fácil mantener un diseño consistente y realizar cambios en la interfaz de usuario sin afectar otras partes (lógica de negocio) del código base.
|
||||
|
||||
## Interface and dependencies
|
||||
## Interfaz y dependencias
|
||||
|
||||
You can import other module code from any module except for the `ui` folder. This will keep its code easy to test.
|
||||
Puedes importar otro código de módulo desde cualquier módulo excepto desde la carpeta `ui`. Esto mantendrá su código fácil de probar.
|
||||
|
||||
### Internal
|
||||
### Interno
|
||||
|
||||
Each part (hooks, states, ...) of a module can have an `internal` folder, which contains parts that are just used within the module.
|
||||
Cada parte (hooks, estados, ...) de un módulo puede tener una carpeta `internal`, que contiene partes que se usan solo dentro del módulo.
|
||||
|
||||
+27
-27
@@ -1,22 +1,22 @@
|
||||
---
|
||||
title: Frontend Commands
|
||||
title: Comandos del Frontend
|
||||
---
|
||||
|
||||
## Useful commands
|
||||
## Comandos útiles
|
||||
|
||||
### Starting the app
|
||||
### Iniciando la aplicación
|
||||
|
||||
```bash
|
||||
npx nx start twenty-front
|
||||
```
|
||||
|
||||
### Regenerate graphql schema based on API graphql schema
|
||||
### Regenerar el esquema de graphql basado en el esquema API graphql
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata
|
||||
```
|
||||
|
||||
OR
|
||||
O
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate
|
||||
@@ -25,30 +25,30 @@ npx nx run twenty-front:graphql:generate
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lint # pass --fix to fix lint errors
|
||||
npx nx run twenty-front:lint # pasar --fix para corregir errores de lint
|
||||
```
|
||||
|
||||
## Translations
|
||||
## Traducciones
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lingui:extract
|
||||
npx nx run twenty-front:lingui:compile
|
||||
```
|
||||
|
||||
### Test
|
||||
### Prueba
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:test # run jest tests
|
||||
npx nx run twenty-front:storybook:serve:dev # run storybook
|
||||
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
|
||||
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
|
||||
npx nx run twenty-front:test # ejecutar pruebas con Jest
|
||||
npx nx run twenty-front:storybook:serve:dev # ejecutar Storybook
|
||||
npx nx run twenty-front:storybook:test # ejecutar pruebas # (requiere que yarn storybook:serve:dev esté en ejecución)
|
||||
npx nx run twenty-front:storybook:coverage # (requiere que yarn storybook:serve:dev esté en ejecución)
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
## Stack Tecnológico
|
||||
|
||||
The project has a clean and simple stack, with minimal boilerplate code.
|
||||
El proyecto tiene un stack limpio y sencillo, con un código boilerplate mínimo.
|
||||
|
||||
**App**
|
||||
**Aplicación**
|
||||
|
||||
* [React](https://react.dev/)
|
||||
* [Apollo](https://www.apollographql.com/docs/)
|
||||
@@ -56,35 +56,35 @@ The project has a clean and simple stack, with minimal boilerplate code.
|
||||
* [Recoil](https://recoiljs.org/docs/introduction/core-concepts)
|
||||
* [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
**Testing**
|
||||
**Pruebas**
|
||||
|
||||
* [Jest](https://jestjs.io/)
|
||||
* [Storybook](https://storybook.js.org/)
|
||||
|
||||
**Tooling**
|
||||
**Herramientas**
|
||||
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
* [Craco](https://craco.js.org/docs/)
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
## Architecture
|
||||
## Arquitectura
|
||||
|
||||
### Routing
|
||||
### Enrutamiento
|
||||
|
||||
[React Router](https://reactrouter.com/) handles the routing.
|
||||
[React Router](https://reactrouter.com/) maneja el enrutamiento.
|
||||
|
||||
To avoid unnecessary [re-renders](/l/es/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) all the routing logic is in a `useEffect` in `PageChangeEffect`.
|
||||
|
||||
### State Management
|
||||
### Gestión del Estado
|
||||
|
||||
[Recoil](https://recoiljs.org/docs/introduction/core-concepts) handles state management.
|
||||
[Recoil](https://recoiljs.org/docs/introduction/core-concepts) maneja la gestión del estado.
|
||||
|
||||
See [best practices](/l/es/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) for more information on state management.
|
||||
Ver [mejores prácticas](/l/es/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) para más información sobre la gestión del estado.
|
||||
|
||||
## Testing
|
||||
## Pruebas
|
||||
|
||||
[Jest](https://jestjs.io/) serves as the tool for unit testing while [Storybook](https://storybook.js.org/) is for component testing.
|
||||
[Jest](https://jestjs.io/) sirve como la herramienta para pruebas unitarias mientras [Storybook](https://storybook.js.org/) es para pruebas de componentes.
|
||||
|
||||
Jest is mainly for testing utility functions, and not components themselves.
|
||||
Jest es principalmente para probar funciones utilitarias, y no los componentes en sí mismos.
|
||||
|
||||
Storybook is for testing the behavior of isolated components, as well as displaying the design system.
|
||||
Storybook es para probar el comportamiento de componentes aislados, así como mostrar el sistema de diseño.
|
||||
|
||||
+34
-34
@@ -1,42 +1,42 @@
|
||||
---
|
||||
title: Hotkeys
|
||||
title: Atajos de teclado
|
||||
---
|
||||
|
||||
## Introduction
|
||||
## Introducción
|
||||
|
||||
When you need to listen to a hotkey, you would normally use the `onKeyDown` event listener.
|
||||
Cuando necesitas escuchar una tecla de acceso rápido, normalmente usarías el evento `onKeyDown`.
|
||||
|
||||
In `twenty-front` however, you might have conflicts between same hotkeys that are used in different components, mounted at the same time.
|
||||
En `twenty-front`, sin embargo, podrías tener conflictos entre los mismos atajos de teclado utilizados en diferentes componentes, montados al mismo tiempo.
|
||||
|
||||
For example, if you have a page that listens for the Enter key, and a modal that listens for the Enter key, with a Select component inside that modal that listens for the Enter key, you might have a conflict when all are mounted at the same time.
|
||||
Por ejemplo, si tienes una página que escucha la tecla Enter y un modal que escucha la tecla Enter, con un componente Select dentro de ese modal que también escucha la tecla Enter, podrías tener un conflicto cuando todos están montados al mismo tiempo.
|
||||
|
||||
## The `useScopedHotkeys` hook
|
||||
## El gancho `useScopedHotkeys`
|
||||
|
||||
To handle this problem, we have a custom hook that makes it possible to listen to hotkeys without any conflict.
|
||||
Para manejar este problema, tenemos un gancho personalizado que hace posible escuchar atajos de teclado sin ningún conflicto.
|
||||
|
||||
You place it in a component, and it will listen to the hotkeys only when the component is mounted AND when the specified **hotkey scope** is active.
|
||||
Lo colocas en un componente, y escuchará los atajos de teclado solo cuando el componente está montado Y cuando el **ámbito del atajo de teclado** especificado está activo.
|
||||
|
||||
## How to listen for hotkeys in practice?
|
||||
## ¿Cómo escuchar atajos de teclado en la práctica?
|
||||
|
||||
There are two steps involved in setting up hotkey listening :
|
||||
Hay dos pasos involucrados en configurar la escucha de atajos de teclado:
|
||||
|
||||
1. Set the [hotkey scope](#what-is-a-hotkey-scope-) that will listen to hotkeys
|
||||
2. Use the `useScopedHotkeys` hook to listen to hotkeys
|
||||
1. Establece el [ámbito del atajo de teclado](#qué-es-un-ambito-de-atajo-de-teclado-) que escuchará los atajos de teclado
|
||||
2. Usa el gancho `useScopedHotkeys` para escuchar atajos de teclado
|
||||
|
||||
Setting up hotkey scopes is required even in simple pages, because other UI elements like left menu or command menu might also listen to hotkeys.
|
||||
Configurar los ámbitos de atajos de teclado es necesario incluso en páginas simples, porque otros elementos de la interfaz de usuario como el menú lateral o el menú de comandos también podrían escuchar atajos de teclado.
|
||||
|
||||
## Use cases for hotkeys
|
||||
## Casos de uso para atajos de teclado
|
||||
|
||||
In general, you'll have two use cases that require hotkeys :
|
||||
En general, tendrás dos casos de uso que requieren atajos de teclado:
|
||||
|
||||
1. In a page or a component mounted in a page
|
||||
2. In a modal-type component that takes the focus due to a user action
|
||||
1. En una página o un componente montado en una página
|
||||
2. En un componente tipo modal que toma el enfoque debido a la acción del usuario
|
||||
|
||||
The second use case can happen recursively : a dropdown in a modal for example.
|
||||
El segundo caso de uso puede ocurrir recursivamente: un desplegable en un modal, por ejemplo.
|
||||
|
||||
### Listening to hotkeys in a page
|
||||
### Escuchando atajos de teclado en una página
|
||||
|
||||
Example :
|
||||
Ejemplo:
|
||||
|
||||
```tsx
|
||||
const PageListeningEnter = () => {
|
||||
@@ -71,11 +71,11 @@ const PageListeningEnter = () => {
|
||||
};
|
||||
```
|
||||
|
||||
### Listening to hotkeys in a modal-type component
|
||||
### Escuchando atajos de teclado en un componente tipo modal
|
||||
|
||||
For this example we'll use a modal component that listens for the Escape key to tell its parent to close it.
|
||||
Para este ejemplo utilizaremos un componente modal que escucha la tecla Escape para indicar a su padre que lo cierre.
|
||||
|
||||
Here the user interaction is changing the scope.
|
||||
Aquí la interacción del usuario está cambiando el ámbito.
|
||||
|
||||
```tsx
|
||||
const ExamplePageWithModal = () => {
|
||||
@@ -108,7 +108,7 @@ const ExamplePageWithModal = () => {
|
||||
};
|
||||
```
|
||||
|
||||
Then in the modal component :
|
||||
Luego, en el componente modal:
|
||||
|
||||
```tsx
|
||||
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
|
||||
@@ -131,15 +131,15 @@ It's important to use this pattern when you're not sure that just using a useEff
|
||||
|
||||
Those conflicts can be hard to debug, and it might happen more often than not with useEffects.
|
||||
|
||||
## What is a hotkey scope?
|
||||
## ¿Qué es un ámbito de atajo de teclado?
|
||||
|
||||
A hotkey scope is a string that represents a context in which the hotkeys are active. It is generally encoded as an enum.
|
||||
Un ámbito de atajo de teclado es una cadena que representa un contexto en el que los atajos de teclado están activos. Por lo general, se codifica como un enum.
|
||||
|
||||
When you change the hotkey scope, the hotkeys that are listening to this scope will be enabled and the hotkeys that are listening to other scopes will be disabled.
|
||||
Cuando cambias el ámbito del atajo de teclado, se habilitarán los atajos que están escuchando este ámbito y se deshabilitarán los atajos que escuchan otros ámbitos.
|
||||
|
||||
You can set only one scope at a time.
|
||||
Solo puedes establecer un ámbito a la vez.
|
||||
|
||||
As an example, the hotkey scopes for each page are defined in the `PageHotkeyScope` enum:
|
||||
Como ejemplo, los ámbitos de atajos de teclado para cada página se definen en el enum `PageHotkeyScope`:
|
||||
|
||||
```tsx
|
||||
export enum PageHotkeyScope {
|
||||
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
|
||||
}
|
||||
```
|
||||
|
||||
Internally, the currently selected scope is stored in a Recoil state that is shared across the application :
|
||||
Internamente, el ámbito seleccionado se almacena en un estado de Recoil que se comparte en toda la aplicación:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
});
|
||||
```
|
||||
|
||||
But this Recoil state should never be handled manually ! We'll see how to use it in the next section.
|
||||
¡Pero este estado de Recoil nunca debe manejarse manualmente! Veremos cómo usarlo en la siguiente sección.
|
||||
|
||||
## How is it working internally?
|
||||
## ¿Cómo funciona internamente?
|
||||
|
||||
We made a thin wrapper on top of [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) that makes it more performant and avoids unnecessary re-renders.
|
||||
Hicimos un contenedor delgado sobre [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) que lo hace más eficiente y evita renders innecesarios.
|
||||
|
||||
We also create a Recoil state to handle the hotkey scope state and make it available everywhere in the application.
|
||||
También creamos un estado de Recoil para manejar el estado del ámbito de atajos de teclado y hacerlo disponible en toda la aplicación.
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
---
|
||||
title: Storybook
|
||||
description: Browse Twenty's UI component library
|
||||
description: Navegar por la biblioteca de componentes de interfaz de Twenty
|
||||
---
|
||||
|
||||
View our complete component library and documentation in Storybook.
|
||||
Ver nuestra biblioteca completa de componentes y documentación en Storybook.
|
||||
|
||||
[Open Storybook →](https://storybook.twenty.com)
|
||||
[Abrir Storybook →](https://storybook.twenty.com)
|
||||
|
||||
+58
-58
@@ -1,24 +1,24 @@
|
||||
---
|
||||
title: Style Guide
|
||||
title: Guía de Estilo
|
||||
---
|
||||
|
||||
This document includes the rules to follow when writing code.
|
||||
Este documento incluye las reglas a seguir al escribir código.
|
||||
|
||||
The goal here is to have a consistent codebase, which is easy to read and easy to maintain.
|
||||
El objetivo aquí es tener una base de código coherente, que sea fácil de leer y de mantener.
|
||||
|
||||
For this, it's better to be a bit more verbose than to be too concise.
|
||||
Para esto, es mejor ser un poco más detallado que ser demasiado conciso.
|
||||
|
||||
Always keep in mind that people read code more often than they write it, specially on an open source project, where anyone can contribute.
|
||||
Ten siempre en cuenta que la gente lee código más a menudo de lo que lo escribe, especialmente en un proyecto de código abierto, donde cualquiera puede contribuir.
|
||||
|
||||
There are a lot of rules that are not defined here, but that are automatically checked by linters.
|
||||
Hay muchas reglas que no están definidas aquí, pero que son verificadas automáticamente por linters.
|
||||
|
||||
## React
|
||||
|
||||
### Use functional components
|
||||
### Usar componentes funcionales
|
||||
|
||||
Always use TSX functional components.
|
||||
Siempre usa componentes funcionales TSX.
|
||||
|
||||
Do not use default `import` with `const`, because it's harder to read and harder to import with code completion.
|
||||
No uses `import` por defecto con `const`, porque es más difícil de leer y más difícil de importar con el autocompletado de código.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, harder to read, harder to import with code completion
|
||||
@@ -34,11 +34,11 @@ export function MyComponent() {
|
||||
};
|
||||
```
|
||||
|
||||
### Props
|
||||
### "Props"
|
||||
|
||||
Create the type of the props and call it `(ComponentName)Props` if there's no need to export it.
|
||||
Crea el tipo de las props y llámalo `(NombreDelComponente)Props` si no hay necesidad de exportarlo.
|
||||
|
||||
Use props destructuring.
|
||||
Usa la desestructuración de props.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, no type
|
||||
@@ -52,7 +52,7 @@ type MyComponentProps = {
|
||||
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
|
||||
```
|
||||
|
||||
#### Refrain from using `React.FC` or `React.FunctionComponent` to define prop types
|
||||
#### Evita usar `React.FC` o `React.FunctionComponent` para definir tipos de props
|
||||
|
||||
```tsx
|
||||
/* ❌ - Bad, defines the component type annotations with `FC`
|
||||
@@ -67,10 +67,10 @@ const EmailField: React.FC<{
|
||||
```
|
||||
|
||||
```tsx
|
||||
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
|
||||
* component's props
|
||||
* - This method doesn't automatically include the children prop. If
|
||||
* you want to include it, you have to specify it in OwnProps.
|
||||
/* ✅ - Bueno, se define explícitamente un tipo (OwnProps) separado para las
|
||||
* props del componente
|
||||
* - Este método no incluye automáticamente la prop children. Si
|
||||
* quieres incluirla, debes especificarla en OwnProps.
|
||||
*/
|
||||
type EmailFieldProps = {
|
||||
value: string;
|
||||
@@ -81,9 +81,9 @@ const EmailField = ({ value }: EmailFieldProps) => (
|
||||
);
|
||||
```
|
||||
|
||||
#### No Single Variable Prop Spreading in JSX Elements
|
||||
#### Sin Propagación de una sola variable de Props en Elementos JSX
|
||||
|
||||
Avoid using single variable prop spreading in JSX elements, like `{...props}`. This practice often results in code that is less readable and harder to maintain because it's unclear which props the component is receiving.
|
||||
Evita usar la propagación de una sola variable de props en elementos JSX, como `{...props}`. Esta práctica a menudo resulta en un código que es menos legible y más difícil de mantener porque no está claro qué props está recibiendo el componente.
|
||||
|
||||
```tsx
|
||||
/* ❌ - Bad, spreads a single variable prop into the underlying component
|
||||
@@ -94,23 +94,23 @@ const MyComponent = (props: OwnProps) => {
|
||||
```
|
||||
|
||||
```tsx
|
||||
/* ✅ - Good, Explicitly lists all props
|
||||
* - Enhances readability and maintainability
|
||||
/* ✅ - Bueno, enumera explícitamente todas las props
|
||||
* - Mejora la legibilidad y la mantenibilidad
|
||||
*/
|
||||
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
|
||||
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
|
||||
};
|
||||
```
|
||||
|
||||
Rationale:
|
||||
Razonamiento:
|
||||
|
||||
* At a glance, it's clearer which props the code passes down, making it easier to understand and maintain.
|
||||
* It helps to prevent tight coupling between components via their props.
|
||||
* Linting tools make it easier to identify misspelled or unused props when you list props explicitly.
|
||||
* A simple vista, es más claro qué props se está pasando, lo que hace que sea más fácil de entender y mantener.
|
||||
* Ayuda a prevenir el acoplamiento estricto entre componentes mediante sus props.
|
||||
* Las herramientas de linting facilitan la identificación de props mal escritas o sin uso al listar props explícitamente.
|
||||
|
||||
## JavaScript
|
||||
|
||||
### Use nullish-coalescing operator `??`
|
||||
### Usar el operador de fusión nula `??`
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, can return 'default' even if value is 0 or ''
|
||||
@@ -120,21 +120,21 @@ const value = process.env.MY_VALUE || 'default';
|
||||
const value = process.env.MY_VALUE ?? 'default';
|
||||
```
|
||||
|
||||
### Use optional chaining `?.`
|
||||
### Usar encadenamiento opcional `?.`
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
// ❌ Malo
|
||||
onClick && onClick();
|
||||
|
||||
// ✅ Good
|
||||
// ✅ Bueno
|
||||
onClick?.();
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
### Use `type` instead of `interface`
|
||||
### Usar `type` en lugar de `interface`
|
||||
|
||||
Always use `type` instead of `interface`, because they almost always overlap, and `type` is more flexible.
|
||||
Siempre usa `type` en lugar de `interface`, porque casi siempre se superponen y `type` es más flexible.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
@@ -148,11 +148,11 @@ type MyType = {
|
||||
};
|
||||
```
|
||||
|
||||
### Use string literals instead of enums
|
||||
### Usar literales de cadena en lugar de enums
|
||||
|
||||
[String literals](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) are the go-to way to handle enum-like values in TypeScript. They are easier to extend with Pick and Omit, and offer a better developer experience, specially with code completion.
|
||||
[Los literales de cadena](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) son la manera preferida para manejar valores tipo enum en TypeScript. Son más fáciles de extender con Pick y Omit, y ofrecen una mejor experiencia de desarrollo, especialmente con la autocompletación de código.
|
||||
|
||||
You can see why TypeScript recommends avoiding enums [here](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
|
||||
Puedes ver por qué TypeScript recomienda evitar enums [aquí](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, utilizes an enum
|
||||
@@ -171,13 +171,13 @@ let color = Color.Red;
|
||||
let color: "red" | "green" | "blue" = "red";
|
||||
```
|
||||
|
||||
#### GraphQL and internal libraries
|
||||
#### GraphQL y bibliotecas internas
|
||||
|
||||
You should use enums that GraphQL codegen generates.
|
||||
Deberías usar enums que genera el codegen de GraphQL.
|
||||
|
||||
It's also better to use an enum when using an internal library, so the internal library doesn't have to expose a string literal type that is not related to the internal API.
|
||||
También es mejor usar un enum al usar una biblioteca interna, para que la biblioteca interna no tenga que exponer un tipo de literal de cadena que no está relacionado con la API interna.
|
||||
|
||||
Example:
|
||||
Ejemplo:
|
||||
|
||||
```TSX
|
||||
const {
|
||||
@@ -190,11 +190,11 @@ setHotkeyScopeAndMemorizePreviousScope(
|
||||
);
|
||||
```
|
||||
|
||||
## Styling
|
||||
## Estilo
|
||||
|
||||
### Use StyledComponents
|
||||
### Usar StyledComponents
|
||||
|
||||
Style the components with [styled-components](https://emotion.sh/docs/styled).
|
||||
Estiliza los componentes con [styled-components](https://emotion.sh/docs/styled).
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
@@ -208,7 +208,7 @@ const StyledTitle = styled.div`
|
||||
`;
|
||||
```
|
||||
|
||||
Prefix styled components with "Styled" to differentiate them from "real" components.
|
||||
Prefija los componentes estilizados con "Styled" para diferenciarlos de los componentes "reales".
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
@@ -224,17 +224,17 @@ const StyledTitle = styled.div`
|
||||
`;
|
||||
```
|
||||
|
||||
### Theming
|
||||
### Tematización
|
||||
|
||||
Utilizing the theme for the majority of component styling is the preferred approach.
|
||||
Utilizar el tema para la mayor parte de la estilización de los componentes es el enfoque preferido.
|
||||
|
||||
#### Units of measurement
|
||||
#### Unidades de medida
|
||||
|
||||
Avoid using `px` or `rem` values directly within the styled components. The necessary values are generally already defined in the theme, so it’s recommended to make use of the theme for these purposes.
|
||||
Evita usar valores `px` o `rem` directamente dentro de los componentes estilizados. Los valores necesarios suelen estar ya definidos en el tema, por lo que se recomienda usar el tema para estos fines.
|
||||
|
||||
#### Colors
|
||||
#### Colores
|
||||
|
||||
Refrain from introducing new colors; instead, use the existing palette from the theme. Should there be a situation where the palette does not align, please leave a comment so that the team can rectify it.
|
||||
Abstente de introducir nuevos colores; en su lugar, utiliza la paleta existente del tema. Si hay una situación en la que la paleta no se ajusta, deja un comentario para que el equipo pueda corregirlo.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, directly specifies style values without utilizing the theme
|
||||
@@ -258,9 +258,9 @@ const StyledButton = styled.button`
|
||||
`;
|
||||
```
|
||||
|
||||
## Enforcing No-Type Imports
|
||||
## Aplicando No-Type Imports
|
||||
|
||||
Avoid type imports. To enforce this standard, an ESLint rule checks for and reports any type imports. This helps maintain consistency and readability in the TypeScript code.
|
||||
Evita las importaciones de tipo. Para reforzar este estándar, una regla de ESLint verifica y reporta cualquier importación de tipo. Esto ayuda a mantener la consistencia y la legibilidad en el código TypeScript.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
@@ -273,18 +273,18 @@ import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
```
|
||||
|
||||
### Why No-Type Imports
|
||||
### Por qué No-Type Imports
|
||||
|
||||
* **Consistency**: By avoiding type imports and using a single approach for both type and value imports, the codebase remains consistent in its module import style.
|
||||
* **Consistencia**: Al evitar las importaciones de tipo y usar un solo enfoque tanto para las importaciones de tipo como de valor, la base de código se mantiene consistente en su estilo de importación de módulos.
|
||||
|
||||
* **Readability**: No-type imports improve code readability by making it clear when you're importing values or types. This reduces ambiguity and makes it easier to understand the purpose of imported symbols.
|
||||
* **Legibilidad**: Las no-importaciones de tipo mejoran la legibilidad del código al dejar claro cuándo se están importando valores o tipos. Esto reduce la ambigüedad y hace más fácil entender el propósito de los símbolos importados.
|
||||
|
||||
* **Maintainability**: It enhances codebase maintainability because developers can identify and locate type-only imports when reviewing or modifying code.
|
||||
* **Mantenibilidad**: Mejora la mantenibilidad de la base de código porque los desarrolladores pueden identificar y localizar importaciones solo de tipo al revisar o modificar el código.
|
||||
|
||||
### ESLint Rule
|
||||
### Regla de ESLint
|
||||
|
||||
An ESLint rule, `@typescript-eslint/consistent-type-imports`, enforces the no-type import standard. This rule will generate errors or warnings for any type import violations.
|
||||
Una regla de ESLint, `@typescript-eslint/consistent-type-imports`, impone el estándar de no usar "type" en las importaciones. Esta regla generará errores o advertencias sobre cualquier violación de importación de tipo.
|
||||
|
||||
Please note that this rule specifically addresses rare edge cases where unintentional type imports occur. TypeScript itself discourages this practice, as mentioned in the [TypeScript 3.8 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). In most situations, you should not need to use type-only imports.
|
||||
Por favor, ten en cuenta que esta regla específicamente aborda extraños casos límite donde ocurren importaciones de tipo no intencionadas. TypeScript en sí mismo desaconseja esta práctica, como se menciona en las [notas de lanzamiento de TypeScript 3.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). En la mayoría de situaciones, no deberías necesitar usar importaciones solo de tipo.
|
||||
|
||||
To ensure your code complies with this rule, make sure to run ESLint as part of your development workflow.
|
||||
Para asegurarte de que tu código cumpla con esta regla, asegúrate de ejecutar ESLint como parte de tu flujo de trabajo de desarrollo.
|
||||
|
||||
+33
-33
@@ -1,59 +1,59 @@
|
||||
---
|
||||
title: Work with Figma
|
||||
info: Learn how you can collaborate with Twenty's Figma
|
||||
title: Trabajar con Figma
|
||||
info: Aprende cómo puedes colaborar con Twenty en Figma
|
||||
---
|
||||
|
||||
Figma is a collaborative interface design tool that aids in bridging the communication barrier between designers and developers.
|
||||
This guide explains how you can collaborate with Figma.
|
||||
Figma es una herramienta de diseño de interfaces colaborativa que ayuda a superar la barrera de comunicación entre diseñadores y desarrolladores.
|
||||
Esta guía explica cómo puedes colaborar con Figma.
|
||||
|
||||
## Access
|
||||
## Acceso
|
||||
|
||||
1. **Access the shared link:** You can access the project's Figma file [here](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
|
||||
2. **Sign in:** If you're not already signed in, Figma will prompt you to do so.
|
||||
Key features are only available to logged-in users, such as the developer mode and the ability to select a dedicated frame.
|
||||
1. **Accede al enlace compartido:** Puedes acceder al archivo de Figma del proyecto [aquí](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
|
||||
2. **Inicia sesión:** Si aún no has iniciado sesión, Figma te pedirá que lo hagas.
|
||||
Las características clave solo están disponibles para usuarios registrados, como el modo desarrollador y la capacidad de seleccionar un marco dedicado.
|
||||
|
||||
<Warning>
|
||||
You will not be able to collaborate effectively without an account.
|
||||
No podrás colaborar efectivamente sin una cuenta.
|
||||
</Warning>
|
||||
|
||||
## Figma structure
|
||||
## Estructura de Figma
|
||||
|
||||
On the left sidebar, you can access the different pages of Twenty's Figma. This is how they're organized:
|
||||
En la barra lateral izquierda, puedes acceder a las diferentes páginas del Figma de Twenty. Así es como están organizadas:
|
||||
|
||||
* **Components page:** This is the first page. The designer uses it to create and organize the reusable design elements used throughout the design file. For example, buttons, icons, symbols, or any other reusable components. It serves to maintain consistency across the design.
|
||||
* **Main page:** The second page is the main page, which shows the complete user interface of the project. You can press ***Play*** to use the full app prototype.
|
||||
* **Features pages:** The other pages are typically dedicated to features in progress. They contain the design of specific features or modules of the application or website. They are typically still in progress.
|
||||
* **Página de componentes:** Esta es la primera página. El diseñador la utiliza para crear y organizar los elementos de diseño reutilizables que se usan en todo el archivo de diseño. Por ejemplo, botones, íconos, símbolos u otros componentes reutilizables. Sirve para mantener la consistencia a lo largo del diseño.
|
||||
* **Página principal:** La segunda página es la página principal, que muestra la interfaz de usuario completa del proyecto. Puedes presionar ***Play*** para usar el prototipo completo de la aplicación.
|
||||
* **Páginas de características:** Las otras páginas están típicamente dedicadas a características en progreso. Contienen el diseño de características específicas o módulos de la aplicación o sitio web. Normalmente, aún están en desarrollo.
|
||||
|
||||
## Useful Tips
|
||||
## Consejos útiles
|
||||
|
||||
With read-only access, you can't edit the design, but you can access all features that will be useful to convert the designs into code.
|
||||
Con acceso de solo lectura, no puedes editar el diseño, pero puedes acceder a todas las características que serán útiles para convertir los diseños en código.
|
||||
|
||||
### Use the Dev mode
|
||||
### Usa el modo Dev
|
||||
|
||||
Figma's Dev Mode enhances developers' productivity by providing easy design navigation, effective asset management, efficient communication tools, toolbox integrations, quick code snippets, and key layer information, bridging the gap between design and development. You can learn more about Dev Mode [here](https://www.figma.com/dev-mode/).
|
||||
El Modo Dev de Figma mejora la productividad de los desarrolladores al proporcionar una navegación fácil por el diseño, una gestión eficaz de assets, herramientas de comunicación eficientes, integraciones de caja de herramientas, fragmentos de código rápidos e información clave de capas, superando la brecha entre diseño y desarrollo. Puedes aprender más sobre el Modo Dev [aquí](https://www.figma.com/dev-mode/).
|
||||
|
||||
Switch to the "Developer" mode in the right part of the toolbar to see design specs, copy CSS, and access assets.
|
||||
Cambia al modo "Desarrollador" en la parte derecha de la barra de herramientas para ver las especificaciones de diseño, copiar CSS y acceder a los assets.
|
||||
|
||||
### Use the Prototype
|
||||
### Usar el prototipo
|
||||
|
||||
Click on any element on the canvas and press the “Play” button at the top right edge of the interface to access the prototype view. Prototype mode allows you to interact with the design as if it were the final product. It demonstrates the flow between screens and how interface elements like buttons, links, or menus behave when interacted with.
|
||||
Haz clic en cualquier elemento del lienzo y presiona el botón "Play" en el extremo superior derecho de la interfaz para acceder a la vista del prototipo. El modo de prototipo te permite interactuar con el diseño como si fuera el producto final. Demuestra el flujo entre pantallas y cómo los elementos de la interfaz, como botones, enlaces o menús, se comportan al interactuar con ellos.
|
||||
|
||||
1. **Understanding transitions and animations:** In the Prototype mode, you can view any transitions or animations added by a designer between screens or UI elements, providing clear visual instructions to developers on the intended behavior and style.
|
||||
2. **Implementation clarification:** A prototype can also help reduce ambiguities. Developers can interact with it to gain a better understanding of the functionality or appearance of particular elements.
|
||||
1. **Entendiendo las transiciones y animaciones:** En el modo Prototipo, puedes ver cualquier transición o animación añadida por un diseñador entre pantallas o elementos de UI, proporcionando instrucciones visuales claras a los desarrolladores sobre la conducta y el estilo intencionados.
|
||||
2. **Aclaración de implementación:** Un prototipo también puede ayudar a reducir las ambigüedades. Los desarrolladores pueden interactuar con él para obtener una mejor comprensión de la funcionalidad o apariencia de elementos particulares.
|
||||
|
||||
For more comprehensive details and guidance on learning the Figma platform, you can visit the official [Figma Documentation](https://help.figma.com/hc/en-us).
|
||||
Para más detalles y orientación sobre el aprendizaje de la plataforma Figma, puedes visitar la [Documentación Oficial de Figma](https://help.figma.com/hc/en-us).
|
||||
|
||||
### Measure distances
|
||||
### Medir distancias
|
||||
|
||||
Select an element, hold `Option` key (Mac) or `Alt` key (Windows), then hover over another element to see the distance between them.
|
||||
Selecciona un elemento, mantén presionada la tecla `Option` (Mac) o `Alt` (Windows), luego pasa el cursor sobre otro elemento para ver la distancia entre ellos.
|
||||
|
||||
### Figma extension for VSCode (Recommended)
|
||||
### Extensión Figma para VSCode (Recomendado)
|
||||
|
||||
[Figma for VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
|
||||
lets you navigate and inspect design files, collaborate with designers, track changes, and speed up implementation - all without leaving your text editor.
|
||||
It's part of our recommended extensions.
|
||||
[Figma para VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
|
||||
te permite navegar e inspeccionar archivos de diseño, colaborar con diseñadores, rastrear cambios y acelerar la implementación, todo sin salir de tu editor de texto.
|
||||
Forma parte de nuestras extensiones recomendadas.
|
||||
|
||||
## Collaboration
|
||||
## Colaboración
|
||||
|
||||
1. **Using Comments:** You are welcome to use the comment feature by clicking on the bubble icon in the left part of the toolbar.
|
||||
2. **Cursor chat:** A nice feature of Figma is the Cursor chat. Just press `;` on Mac and `/` on Windows to send a message if you see someone else using Figma as the same time as you.
|
||||
1. **Uso de Comentarios:** Puedes utilizar la función de comentarios haciendo clic en el ícono de burbuja en la parte izquierda de la barra de herramientas.
|
||||
2. **Chat de Cursor:** Una característica agradable de Figma es el Chat de Cursor. Simplemente presiona `;` en Mac y `/` en Windows para enviar un mensaje si ves a alguien más usando Figma al mismo tiempo que tú.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
title: Local Setup
|
||||
description: The guide for contributors (or curious developers) who want to run Twenty locally.
|
||||
title: Configuración Local
|
||||
description: La guía para los colaboradores (o desarrolladores curiosos) que quieren ejecutar Twenty localmente.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
## Prerrequisitos
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Linux and MacOS">
|
||||
Before you can install and use Twenty, make sure you install the following on your computer:
|
||||
<Tab title="Linux y MacOS">
|
||||
Antes de que puedas instalar y usar Twenty, asegúrate de instalar lo siguiente en tu computadora:
|
||||
|
||||
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||
* [Node v24.5.0](https://nodejs.org/en/download)
|
||||
@@ -15,25 +15,25 @@ description: The guide for contributors (or curious developers) who want to run
|
||||
* [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
|
||||
|
||||
<Warning>
|
||||
`npm` won't work, you should use `yarn` instead. Yarn is now shipped with Node.js, so you don't need to install it separately.
|
||||
You only have to run `corepack enable` to enable Yarn if you haven't done it yet.
|
||||
`npm` no funcionará, deberías usar `yarn` en su lugar. Yarn ahora se envía con Node.js, por lo que no necesitas instalarlo por separado.
|
||||
Solo tienes que ejecutar `corepack enable` para habilitar Yarn si aún no lo has hecho.
|
||||
</Warning>
|
||||
</Tab>
|
||||
|
||||
<Tab title="Windows (WSL)">
|
||||
1. Install WSL
|
||||
Open PowerShell as Administrator and run:
|
||||
1. Instalar WSL
|
||||
Abre PowerShell como Administrador y ejecuta:
|
||||
|
||||
```powershell
|
||||
wsl --install
|
||||
```
|
||||
|
||||
You should now see a prompt to restart your computer. If not, restart it manually.
|
||||
Ahora deberías ver un aviso para reiniciar tu computadora. Si no, reiníciala manualmente.
|
||||
|
||||
Upon restart, a powershell window will open and install Ubuntu. This may take up some time.
|
||||
You'll see a prompt to create a username and password for your Ubuntu installation.
|
||||
Al reiniciar, se abrirá una ventana de PowerShell e instalará Ubuntu. Esto puede tomar algo de tiempo.
|
||||
Verás un aviso para crear un nombre de usuario y contraseña para tu instalación de Ubuntu.
|
||||
|
||||
2. Install and configure git
|
||||
2. Instalar y configurar git
|
||||
|
||||
```bash
|
||||
sudo apt-get install git
|
||||
@@ -43,10 +43,10 @@ description: The guide for contributors (or curious developers) who want to run
|
||||
git config --global user.email "[email protected]"
|
||||
```
|
||||
|
||||
3. Install nvm, node.js and yarn
|
||||
3. Instalar nvm, node.js y yarn
|
||||
|
||||
<Warning>
|
||||
Use `nvm` to install the correct `node` version. The `.nvmrc` ensures all contributors use the same version.
|
||||
Usa `nvm` para instalar la versión correcta de `node`. El `.nvmrc` asegura que todos los colaboradores usen la misma versión.
|
||||
</Warning>
|
||||
|
||||
```bash
|
||||
@@ -55,7 +55,7 @@ description: The guide for contributors (or curious developers) who want to run
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
|
||||
```
|
||||
|
||||
Close and reopen your terminal to use nvm. Then run the following commands.
|
||||
Cierra y vuelve a abrir tu terminal para usar nvm. Luego ejecuta los siguientes comandos.
|
||||
|
||||
```bash
|
||||
|
||||
@@ -70,13 +70,13 @@ description: The guide for contributors (or curious developers) who want to run
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Git Clone
|
||||
## Paso 1: Clonar con Git
|
||||
|
||||
In your terminal, run the following command.
|
||||
En tu terminal, ejecuta el siguiente comando.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="SSH (Recommended)">
|
||||
If you haven't already set up SSH keys, you can learn how to do so [here](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh).
|
||||
<Tab title="SSH (Recomendado)">
|
||||
Si aún no has configurado claves SSH, puedes aprender cómo hacerlo [aquí](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh).
|
||||
|
||||
```bash
|
||||
git clone [email protected]:twentyhq/twenty.git
|
||||
@@ -90,36 +90,36 @@ In your terminal, run the following command.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Step 2: Position yourself at the root
|
||||
## Paso 2: Ubícate en la raíz
|
||||
|
||||
```bash
|
||||
cd twenty
|
||||
```
|
||||
|
||||
You should run all commands in the following steps from the root of the project.
|
||||
Debes ejecutar todos los comandos de los siguientes pasos desde la raíz del proyecto.
|
||||
|
||||
## Step 3: Set up a PostgreSQL Database
|
||||
## Paso 3: Configurar una Base de Datos PostgreSQL
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Linux">
|
||||
**Option 1 (preferred):** To provision your database locally:
|
||||
Use the following link to install Postgresql on your Linux machine: [Postgresql Installation](https://www.postgresql.org/download/linux/)
|
||||
**Opción 1 (preferido):** Para aprovisionar tu base de datos localmente:
|
||||
Usa el siguiente enlace para instalar PostgreSQL en tu máquina Linux: [Instalación de PostgreSQL](https://www.postgresql.org/download/linux/)
|
||||
|
||||
```bash
|
||||
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
|
||||
```
|
||||
|
||||
Note: You might need to add `sudo -u postgres` to the command before `psql` to avoid permission errors.
|
||||
Nota: Puede que necesites agregar `sudo -u postgres` antes del comando `psql` para evitar errores de permisos.
|
||||
|
||||
**Option 2:** If you have docker installed:
|
||||
**Opción 2:** Si tienes Docker instalado:
|
||||
|
||||
```bash
|
||||
make postgres-on-docker
|
||||
make -C packages/twenty-docker postgres-on-docker
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Mac OS">
|
||||
**Option 1 (preferred):** To provision your database locally with `brew`:
|
||||
**Opción 1 (preferido):** Para aprovisionar tu base de datos localmente con `brew`:
|
||||
|
||||
```bash
|
||||
brew install postgresql@16
|
||||
@@ -128,16 +128,16 @@ You should run all commands in the following steps from the root of the project.
|
||||
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
|
||||
```
|
||||
|
||||
You can verify if the PostgreSQL server is running by executing:
|
||||
Puedes verificar si el servidor PostgreSQL está corriendo ejecutando:
|
||||
|
||||
```bash
|
||||
brew services list
|
||||
```
|
||||
|
||||
The installer might not create the `postgres` user by default when installing
|
||||
via Homebrew on MacOS. Instead, it creates a PostgreSQL role that matches your macOS
|
||||
username (e.g., "john").
|
||||
To check and create the `postgres` user if necessary, follow these steps:
|
||||
El instalador puede que no cree el usuario `postgres` por defecto al instalar
|
||||
vía Homebrew en macOS. En cambio, crea un rol de PostgreSQL que coincide con tu
|
||||
nombre de usuario de macOS (por ejemplo, "john").
|
||||
Para comprobar y crear el usuario `postgres` si es necesario, sigue estos pasos:
|
||||
|
||||
```bash
|
||||
# Connect to PostgreSQL
|
||||
@@ -146,14 +146,14 @@ You should run all commands in the following steps from the root of the project.
|
||||
psql -U $(whoami) -d postgres
|
||||
```
|
||||
|
||||
Once at the psql prompt (postgres=#), run:
|
||||
Una vez en el comando psql (postgres=#), ejecuta:
|
||||
|
||||
```bash
|
||||
# List existing PostgreSQL roles
|
||||
\du
|
||||
```
|
||||
|
||||
You'll see output similar to:
|
||||
Verás una salida similar a:
|
||||
|
||||
```bash
|
||||
Role name | Attributes | Member of
|
||||
@@ -161,98 +161,105 @@ You should run all commands in the following steps from the root of the project.
|
||||
john | Superuser | {}
|
||||
```
|
||||
|
||||
If you do not see a `postgres` role listed, proceed to the next step.
|
||||
Create the `postgres` role manually:
|
||||
Si no ves un rol `postgres` listado, procede al siguiente paso.
|
||||
Crea el rol `postgres` manualmente:
|
||||
|
||||
```bash
|
||||
CREATE ROLE postgres WITH SUPERUSER LOGIN;
|
||||
```
|
||||
|
||||
This creates a superuser role named `postgres` with login access.
|
||||
|
||||
**Option 2:** If you have docker installed:
|
||||
Esto crea un rol de superusuario llamado `postgres` con acceso de inicio de sesión.
|
||||
|
||||
```bash
|
||||
make postgres-on-docker
|
||||
Nombre del rol | Atributos | Miembro de
|
||||
-----------+-------------+-----------
|
||||
postgres | Superuser | {}
|
||||
john | Superuser | {}
|
||||
```
|
||||
|
||||
**Opción 2:** Si tienes Docker instalado:
|
||||
|
||||
```bash
|
||||
make -C packages/twenty-docker postgres-on-docker
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Windows (WSL)">
|
||||
All the following steps are to be run in the WSL terminal (within your virtual machine)
|
||||
Todos los siguientes pasos deben ejecutarse en la terminal de WSL (dentro de tu máquina virtual)
|
||||
|
||||
**Option 1:** To provision your Postgresql locally:
|
||||
Use the following link to install Postgresql on your Linux virtual machine: [Postgresql Installation](https://www.postgresql.org/download/linux/)
|
||||
**Opción 1:** Para aprovisionar tu PostgreSQL localmente:
|
||||
Usa el siguiente enlace para instalar PostgreSQL en tu máquina virtual Linux: [Instalación de PostgreSQL](https://www.postgresql.org/download/linux/)
|
||||
|
||||
```bash
|
||||
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
|
||||
```
|
||||
|
||||
Note: You might need to add `sudo -u postgres` to the command before `psql` to avoid permission errors.
|
||||
Nota: Puede que necesites agregar `sudo -u postgres` antes del comando `psql` para evitar errores de permisos.
|
||||
|
||||
**Option 2:** If you have docker installed:
|
||||
Running Docker on WSL adds an extra layer of complexity.
|
||||
Only use this option if you are comfortable with the extra steps involved, including turning on [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl).
|
||||
**Opción 2:** Si tienes Docker instalado:
|
||||
Ejecutar Docker en WSL agrega una capa extra de complejidad.
|
||||
Solo usa esta opción si estás cómodo con los pasos extras involucrados, incluyendo activar [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl).
|
||||
|
||||
```bash
|
||||
make postgres-on-docker
|
||||
make -C packages/twenty-docker postgres-on-docker
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
You can now access the database at [localhost:5432](localhost:5432), with user `postgres` and password `postgres` .
|
||||
Ahora puedes acceder a la base de datos en [localhost:5432](localhost:5432), con usuario `postgres` y contraseña `postgres`.
|
||||
|
||||
## Step 4: Set up a Redis Database (cache)
|
||||
## Paso 4: Configurar una Base de Datos Redis (cache)
|
||||
|
||||
Twenty requires a redis cache to provide the best performance
|
||||
Twenty requiere un caché de redis para proporcionar el mejor rendimiento
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Linux">
|
||||
**Option 1:** To provision your Redis locally:
|
||||
Use the following link to install Redis on your Linux machine: [Redis Installation](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
|
||||
**Opción 1:** Para aprovisionar tu Redis localmente:
|
||||
Usa el siguiente enlace para instalar Redis en tu máquina Linux: [Instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
|
||||
|
||||
**Option 2:** If you have docker installed:
|
||||
**Opción 2:** Si tienes Docker instalado:
|
||||
|
||||
```bash
|
||||
make redis-on-docker
|
||||
make -C packages/twenty-docker redis-on-docker
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Mac OS">
|
||||
**Option 1 (preferred):** To provision your Redis locally with `brew`:
|
||||
**Opción 1 (preferido):** Para aprovisionar tu Redis localmente con `brew`:
|
||||
|
||||
```bash
|
||||
brew install redis
|
||||
```
|
||||
|
||||
Start your redis server:
|
||||
Inicia tu servidor redis:
|
||||
`brew services start redis`
|
||||
|
||||
**Option 2:** If you have docker installed:
|
||||
**Opción 2:** Si tienes Docker instalado:
|
||||
|
||||
```bash
|
||||
make redis-on-docker
|
||||
make -C packages/twenty-docker redis-on-docker
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Windows (WSL)">
|
||||
**Option 1:** To provision your Redis locally:
|
||||
Use the following link to install Redis on your Linux virtual machine: [Redis Installation](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
|
||||
**Opción 1:** Para aprovisionar tu Redis localmente:
|
||||
Usa el siguiente enlace para instalar Redis en tu máquina virtual Linux: [Instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
|
||||
|
||||
**Option 2:** If you have docker installed:
|
||||
**Opción 2:** Si tienes Docker instalado:
|
||||
|
||||
```bash
|
||||
make redis-on-docker
|
||||
make -C packages/twenty-docker redis-on-docker
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
If you need a Client GUI, we recommend [redis insight](https://redis.io/insight/) (free version available)
|
||||
Si necesitas una interfaz gráfica de cliente, recomendamos [Redis Insight](https://redis.io/insight/) (versión gratuita disponible)
|
||||
|
||||
## Step 5: Setup environment variables
|
||||
## Paso 5: Configurar las variables de entorno
|
||||
|
||||
Use environment variables or `.env` files to configure your project. More info [here](/l/es/developers/self-host/capabilities/setup)
|
||||
Usa variables de entorno o archivos `.env` para configurar tu proyecto. Más información [aquí](/l/es/developers/self-host/capabilities/setup)
|
||||
|
||||
Copy the `.env.example` files in `/front` and `/server`:
|
||||
Copia los archivos `.env.example` en `/front` y `/server`:
|
||||
|
||||
```bash
|
||||
cp ./packages/twenty-front/.env.example ./packages/twenty-front/.env
|
||||
@@ -260,29 +267,29 @@ cp ./packages/twenty-server/.env.example ./packages/twenty-server/.env
|
||||
```
|
||||
|
||||
<Info>
|
||||
**Multi-Workspace Mode:** By default, Twenty runs in single-workspace mode where only one workspace can be created. To enable multi-workspace support (useful for testing subdomain-based features), set `IS_MULTIWORKSPACE_ENABLED=true` in your server `.env` file. See [Multi-Workspace Mode](/l/es/developers/self-host/capabilities/setup#multi-workspace-mode) for details.
|
||||
**Modo de múltiples espacios de trabajo:** De forma predeterminada, Twenty se ejecuta en modo de un solo espacio de trabajo, donde solo se puede crear un espacio de trabajo. Para habilitar la compatibilidad con múltiples espacios de trabajo (útil para probar funciones basadas en subdominios), establece `IS_MULTIWORKSPACE_ENABLED=true` en el archivo `.env` de tu servidor. Consulta [Modo de múltiples espacios de trabajo](/l/es/developers/self-host/capabilities/setup#multi-workspace-mode) para más detalles.
|
||||
</Info>
|
||||
|
||||
## Step 6: Installing dependencies
|
||||
## Paso 6: Instalación de dependencias
|
||||
|
||||
To build Twenty server and seed some data into your database, run the following command:
|
||||
Para compilar el servidor de Twenty e ingresar algunos datos en tu base de datos, ejecuta el siguiente comando:
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
Note that `npm` or `pnpm` won't work
|
||||
Ten en cuenta que `npm` o `pnpm` no funcionarán
|
||||
|
||||
## Step 7: Running the project
|
||||
## Paso 7: Ejecutar el proyecto
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Linux">
|
||||
Depending on your Linux distribution, Redis server might be started automatically.
|
||||
If not, check the [Redis installation guide](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) for your distro.
|
||||
Dependiendo de tu distribución de Linux, el servidor Redis podría iniciarse automáticamente.
|
||||
Si no, revisa la [guía de instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) para tu distribución.
|
||||
</Tab>
|
||||
|
||||
<Tab title="Mac OS">
|
||||
Redis should already be running. If not, run:
|
||||
Redis ya debería estar funcionando. Si no, ejecuta:
|
||||
|
||||
```bash
|
||||
brew services start redis
|
||||
@@ -290,18 +297,18 @@ Note that `npm` or `pnpm` won't work
|
||||
</Tab>
|
||||
|
||||
<Tab title="Windows (WSL)">
|
||||
Depending on your Linux distribution, Redis server might be started automatically.
|
||||
If not, check the [Redis installation guide](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) for your distro.
|
||||
Dependiendo de tu distribución de Linux, el servidor Redis podría iniciarse automáticamente.
|
||||
Si no es así, consulte la [guía de instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) para su distribución.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Set up your database with the following command:
|
||||
Configure su base de datos con el siguiente comando:
|
||||
|
||||
```bash
|
||||
npx nx database:reset twenty-server
|
||||
```
|
||||
|
||||
Start the server, the worker and the frontend services:
|
||||
Inicie el servidor, el trabajador y los servicios frontend:
|
||||
|
||||
```bash
|
||||
npx nx start twenty-server
|
||||
@@ -309,25 +316,25 @@ npx nx worker twenty-server
|
||||
npx nx start twenty-front
|
||||
```
|
||||
|
||||
Alternatively, you can start all services at once:
|
||||
Alternativamente, puede iniciar todos los servicios a la vez:
|
||||
|
||||
```bash
|
||||
npx nx start
|
||||
```
|
||||
|
||||
## Step 8: Use Twenty
|
||||
## Paso 8: Use Twenty
|
||||
|
||||
**Frontend**
|
||||
|
||||
Twenty's frontend will be running at [http://localhost:3001](http://localhost:3001).
|
||||
You can log in using the default demo account: `[email protected]` (password: `[email protected]`)
|
||||
El frontend de Twenty estará ejecutándose en [http://localhost:3001](http://localhost:3001).
|
||||
Puede iniciar sesión usando la cuenta demo por defecto: `[email protected]` (contraseña: `[email protected]`)
|
||||
|
||||
**Backend**
|
||||
|
||||
* Twenty's server will be up and running at [http://localhost:3000](http://localhost:3000)
|
||||
* The GraphQL API can be accessed at [http://localhost:3000/graphql](http://localhost:3000/graphql)
|
||||
* The REST API can be reached at [http://localhost:3000/rest](http://localhost:3000/rest)
|
||||
* El servidor de Twenty estará operativo en [http://localhost:3000](http://localhost:3000)
|
||||
* La API GraphQL puede ser accedida en [http://localhost:3000/graphql](http://localhost:3000/graphql)
|
||||
* La API REST puede ser alcanzada en [http://localhost:3000/rest](http://localhost:3000/rest)
|
||||
|
||||
## Troubleshooting
|
||||
## Solución de Problemas
|
||||
|
||||
If you encounter any problem, check [Troubleshooting](/l/es/developers/self-host/capabilities/troubleshooting) for solutions.
|
||||
Si encuentras algún problema, consulta [Solución de Problemas](/l/es/developers/self-host/capabilities/troubleshooting) para ver soluciones.
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
---
|
||||
title: Contribute
|
||||
description: Contribute to Twenty's open-source development.
|
||||
title: Contribuir
|
||||
description: Contribuye al desarrollo de código abierto de Twenty.
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/github/github-header.png" alt="AI" />
|
||||
<img src="/images/user-guide/github/github-header.png" alt="IA" />
|
||||
</Frame>
|
||||
|
||||
## Overview
|
||||
## Resumen
|
||||
|
||||
Twenty is open-source and welcomes contributions from the community. Whether you're fixing bugs, adding features, or improving documentation, your contributions help make Twenty better for everyone.
|
||||
Twenty es de código abierto y da la bienvenida a las contribuciones de la comunidad. Ya sea que estés solucionando errores, agregando funciones o mejorando la documentación, tus contribuciones ayudan a que Twenty sea mejor para todos.
|
||||
|
||||
## Ways to Contribute
|
||||
## Formas de contribuir
|
||||
|
||||
* **Report bugs**: Help identify and document issues
|
||||
* **Submit features**: Propose and implement new functionality
|
||||
* **Improve documentation**: Make our docs clearer and more helpful
|
||||
* **Frontend development**: Work on the React-based UI
|
||||
* **Backend development**: Contribute to the NestJS server
|
||||
* **Reporta errores**: Ayuda a identificar y documentar problemas
|
||||
* **Propón funcionalidades**: Propón e implementa nuevas funcionalidades
|
||||
* **Mejora la documentación**: Haz que nuestra documentación sea más clara y útil
|
||||
* **Desarrollo de frontend**: Trabaja en la interfaz de usuario basada en React
|
||||
* **Desarrollo de backend**: Contribuye al servidor NestJS
|
||||
|
||||
## Getting Started
|
||||
## Primeros pasos
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Bug Reports & Requests" icon="bug" href="/l/es/developers/contribute/capabilities/bug-and-requests">
|
||||
Report issues or request features
|
||||
<Card title="Informes de errores y solicitudes" icon="bug" href="/l/es/developers/contribute/capabilities/bug-and-requests">
|
||||
Reporta problemas o solicita funcionalidades
|
||||
</Card>
|
||||
|
||||
<Card title="Frontend Development" icon="browser" href="/l/es/developers/contribute/capabilities/frontend-development">
|
||||
Contribute to the UI
|
||||
<Card title="Desarrollo Frontend" icon="browser" href="/l/es/developers/contribute/capabilities/frontend-development">
|
||||
Contribuye a la interfaz de usuario
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,147 +1,147 @@
|
||||
---
|
||||
title: APIs
|
||||
description: Query and modify your CRM data programmatically using REST or GraphQL.
|
||||
description: Consulta y modifica tus datos de CRM de forma programática usando REST o GraphQL.
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
Twenty was built to be developer-friendly, offering powerful APIs that adapt to your custom data model. We provide four distinct API types to meet different integration needs.
|
||||
Twenty fue creado para ser amigable con los desarrolladores, ofreciendo APIs potentes que se adaptan a tu modelo de datos personalizado. Proveemos cuatro tipos de API distintos para satisfacer diferentes necesidades de integración.
|
||||
|
||||
## Developer-First Approach
|
||||
## Enfoque centrado en el desarrollador
|
||||
|
||||
Twenty generates APIs specifically for your data model:
|
||||
Twenty genera APIs específicamente para tu modelo de datos:
|
||||
|
||||
* **No long IDs required**: Use your object and field names directly in endpoints
|
||||
* **Standard and custom objects treated equally**: Your custom objects get the same API treatment as built-in ones
|
||||
* **Dedicated endpoints**: Each object and field gets its own API endpoint
|
||||
* **Custom documentation**: Generated specifically for your workspace's data model
|
||||
* **No se requieren IDs largos**: Usa los nombres de tus objetos y campos directamente en los endpoints.
|
||||
* **Objetos estándar y personalizados tratados por igual**: Tus objetos personalizados reciben el mismo tratamiento de API que los incorporados.
|
||||
* **Endpoints dedicados**: Cada objeto y campo recibe su propio endpoint de API.
|
||||
* **Documentación personalizada**: Generada específicamente para el modelo de datos de tu espacio de trabajo.
|
||||
|
||||
<Note>
|
||||
Your personalized API documentation is available under **Settings → API & Webhooks** after creating an API key. Since Twenty generates APIs that match your custom data model, the documentation is unique to your workspace.
|
||||
Tu documentación personalizada de la API está disponible en **Configuración → API & Webhooks** después de crear una clave de API. Como Twenty genera APIs que coinciden con tu modelo de datos personalizado, la documentación es única para tu espacio de trabajo.
|
||||
</Note>
|
||||
|
||||
## The Two API Types
|
||||
## Los dos tipos de API
|
||||
|
||||
### Core API
|
||||
### API Principal
|
||||
|
||||
Accessed on `/rest/` or `/graphql/`
|
||||
Accesible en `/rest/` o `/graphql/`
|
||||
|
||||
Work with your actual **records** (the data):
|
||||
Trabaja con tus **registros** reales (los datos):
|
||||
|
||||
* Create, read, update, delete People, Companies, Opportunities, etc.
|
||||
* Query and filter data
|
||||
* Manage record relationships
|
||||
* Crear, leer, actualizar y eliminar Personas, Empresas, Oportunidades, etc.
|
||||
* Consultar y filtrar datos
|
||||
* Gestionar relaciones de registros
|
||||
|
||||
### Metadata API
|
||||
### API de Metadatos
|
||||
|
||||
Accessed on `/rest/metadata/` or `/metadata/`
|
||||
Accesible en `/rest/metadata/` o `/metadata/`
|
||||
|
||||
Manage your **workspace and data model**:
|
||||
Administra tu **espacio de trabajo y modelo de datos**:
|
||||
|
||||
* Create, modify, or delete objects and fields
|
||||
* Configure workspace settings
|
||||
* Define relationships between objects
|
||||
* Crear, modificar o eliminar objetos y campos
|
||||
* Configurar ajustes del espacio de trabajo
|
||||
* Define relaciones entre objetos
|
||||
|
||||
## REST vs GraphQL
|
||||
|
||||
Both Core and Metadata APIs are available in REST and GraphQL formats:
|
||||
Tanto las API Core como las de Metadatos están disponibles en formatos REST y GraphQL:
|
||||
|
||||
| Format | Available Operations |
|
||||
| ----------- | ---------------------------------------------------------- |
|
||||
| **REST** | CRUD, batch operations, upserts |
|
||||
| **GraphQL** | Same + **batch upserts**, relationship queries in one call |
|
||||
| Formato | Operaciones disponibles |
|
||||
| ----------- | ----------------------------------------------------------------------------- |
|
||||
| **REST** | CRUD, operaciones por lotes, upserts |
|
||||
| **GraphQL** | Lo mismo + **upserts por lotes**, consultas de relaciones en una sola llamada |
|
||||
|
||||
Choose based on your needs — both formats access the same data.
|
||||
Elige según tus necesidades — ambos formatos acceden a los mismos datos.
|
||||
|
||||
## API Endpoints
|
||||
## Puntos de Acceso de API
|
||||
|
||||
| Environment | Base URL |
|
||||
| --------------- | ------------------------- |
|
||||
| **Cloud** | `https://api.twenty.com/` |
|
||||
| **Self-Hosted** | `https://{your-domain}/` |
|
||||
| Entorno | URL base |
|
||||
| ------------------- | ------------------------- |
|
||||
| **Nube** | `https://api.twenty.com/` |
|
||||
| **Autoalojamiento** | `https://{your-domain}/` |
|
||||
|
||||
## Authentication
|
||||
## Autenticación
|
||||
|
||||
Every API request requires an API key in the header:
|
||||
Cada solicitud a la API requiere una clave de API en el encabezado:
|
||||
|
||||
```
|
||||
Authorization: Bearer YOUR_API_KEY
|
||||
```
|
||||
|
||||
### Create an API Key
|
||||
### Crear una Clave de API
|
||||
|
||||
1. Go to **Settings → APIs & Webhooks**
|
||||
2. Click **+ Create key**
|
||||
3. Configure:
|
||||
* **Name**: Descriptive name for the key
|
||||
* **Expiration Date**: When the key expires
|
||||
4. Click **Save**
|
||||
5. **Copy immediately** — the key is only shown once
|
||||
1. Ve a **Configuración → APIs y Webhooks**
|
||||
2. Haz clic en **+ Crear clave**
|
||||
3. Configurar:
|
||||
* **Nombre**: Nombre descriptivo para la clave
|
||||
* **Fecha de vencimiento**: Cuándo expira la clave
|
||||
4. Haga clic en **Guardar**
|
||||
5. **Copia de inmediato** — la clave solo se muestra una vez
|
||||
|
||||
<VimeoEmbed videoId="928786722" title="Creating API key" />
|
||||
<VimeoEmbed videoId="928786722" title="Creación de clave de API" />
|
||||
|
||||
<Warning>
|
||||
Your API key grants access to sensitive data. Don't share it with untrusted services. If compromised, disable it immediately and generate a new one.
|
||||
Tu clave de API concede acceso a datos sensibles. No la compartas con servicios no confiables. Si se ve comprometida, desactívala de inmediato y genera una nueva.
|
||||
</Warning>
|
||||
|
||||
### Assign a Role to an API Key
|
||||
### Asignar un rol a una clave de API
|
||||
|
||||
For better security, assign a specific role to limit access:
|
||||
Para mayor seguridad, asigna un rol específico para limitar el acceso:
|
||||
|
||||
1. Go to **Settings → Roles**
|
||||
2. Click on the role to assign
|
||||
3. Open the **Assignment** tab
|
||||
4. Under **API Keys**, click **+ Assign to API key**
|
||||
5. Select the API key
|
||||
1. Ve a **Configuración → Roles**
|
||||
2. Haz clic en el rol que deseas asignar
|
||||
3. Abre la pestaña **Asignación**
|
||||
4. En **Claves de API**, haz clic en **+ Asignar a clave de API**
|
||||
5. Selecciona la clave de API
|
||||
|
||||
The key will inherit that role's permissions. See [Permissions](/l/es/user-guide/permissions-access/capabilities/permissions) for details.
|
||||
La clave heredará los permisos de ese rol. Consulta [Permisos](/l/es/user-guide/permissions-access/capabilities/permissions) para más detalles.
|
||||
|
||||
### Manage API Keys
|
||||
### Gestionar Claves de API
|
||||
|
||||
**Regenerate**: Settings → APIs & Webhooks → Click key → **Regenerate**
|
||||
**Regenerar**: Configuración → APIs & Webhooks → Haz clic en la clave → **Regenerar**
|
||||
|
||||
**Delete**: Settings → APIs & Webhooks → Click key → **Delete**
|
||||
**Eliminar**: Configuración → APIs & Webhooks → Haz clic en la clave → **Eliminar**
|
||||
|
||||
## API Playground
|
||||
## Playground de la API
|
||||
|
||||
Test your APIs directly in the browser with our built-in playground — available for both **REST** and **GraphQL**.
|
||||
Prueba tus API directamente en el navegador con nuestro playground integrado — disponible tanto para **REST** como para **GraphQL**.
|
||||
|
||||
### Access the Playground
|
||||
### Accede al Playground
|
||||
|
||||
1. Go to **Settings → APIs & Webhooks**
|
||||
2. Create an API key (required)
|
||||
3. Click on **REST API** or **GraphQL API** to open the playground
|
||||
1. Ve a **Configuración → APIs y Webhooks**
|
||||
2. Crea una clave de API (obligatorio)
|
||||
3. Haz clic en **REST API** o **GraphQL API** para abrir el playground
|
||||
|
||||
### What You Get
|
||||
### Lo que obtienes
|
||||
|
||||
* **Interactive documentation**: Generated for your specific data model
|
||||
* **Live testing**: Execute real API calls against your workspace
|
||||
* **Schema explorer**: Browse available objects, fields, and relationships
|
||||
* **Request builder**: Construct queries with autocomplete
|
||||
* **Documentación interactiva**: Generada para tu modelo de datos específico
|
||||
* **Pruebas en vivo**: Ejecuta llamadas reales a la API en tu espacio de trabajo
|
||||
* **Explorador de esquemas**: Navega por los objetos, campos y relaciones disponibles
|
||||
* **Constructor de solicitudes**: Crea consultas con autocompletado
|
||||
|
||||
The playground reflects your custom objects and fields, so documentation is always accurate for your workspace.
|
||||
El playground refleja tus objetos y campos personalizados, por lo que la documentación siempre es precisa para tu espacio de trabajo.
|
||||
|
||||
## Batch Operations
|
||||
## Operaciones por Lotes
|
||||
|
||||
Both REST and GraphQL support batch operations:
|
||||
Tanto REST como GraphQL admiten operaciones por lotes:
|
||||
|
||||
* **Batch size**: Up to 60 records per request
|
||||
* **Operations**: Create, update, delete multiple records
|
||||
* **Tamaño del lote**: Hasta 60 registros por solicitud
|
||||
* **Operaciones**: Crear, actualizar y eliminar múltiples registros
|
||||
|
||||
**GraphQL-only features:**
|
||||
**Características exclusivas de GraphQL:**
|
||||
|
||||
* **Batch Upsert**: Create or update in one call
|
||||
* Use plural object names (e.g., `CreateCompanies` instead of `CreateCompany`)
|
||||
* **Upsert por lotes**: Crear o actualizar en una llamada
|
||||
* Usa nombres de objetos en plural (por ejemplo, `CreateCompanies` en lugar de `CreateCompany`)
|
||||
|
||||
## Rate Limits
|
||||
## Límites de tasa
|
||||
|
||||
API requests are throttled to ensure platform stability:
|
||||
Las solicitudes a la API se limitan para garantizar la estabilidad de la plataforma:
|
||||
|
||||
| Limit | Value |
|
||||
| -------------- | -------------------- |
|
||||
| **Requests** | 100 calls per minute |
|
||||
| **Batch size** | 60 records per call |
|
||||
| Límite | Valor |
|
||||
| ------------------- | -------------------------- |
|
||||
| **Solicitudes** | 100 solicitudes por minuto |
|
||||
| **Tamaño del lote** | 60 registros por llamada |
|
||||
|
||||
<Tip>
|
||||
Use batch operations to maximize throughput — process up to 60 records in a single API call instead of making individual requests.
|
||||
Usa operaciones por lotes para maximizar el rendimiento — procesa hasta 60 registros en una sola llamada a la API en lugar de hacer solicitudes individuales.
|
||||
</Tip>
|
||||
|
||||
@@ -1,81 +1,85 @@
|
||||
---
|
||||
title: Twenty Apps
|
||||
description: Build and manage Twenty customizations as code.
|
||||
title: Aplicaciones de Twenty
|
||||
description: Crea y gestiona personalizaciones de Twenty como código.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Apps are currently in alpha testing. The feature is functional but still evolving.
|
||||
Las aplicaciones están actualmente en pruebas alfa. La funcionalidad es operativa, pero sigue evolucionando.
|
||||
</Warning>
|
||||
|
||||
## What Are Apps?
|
||||
## ¿Qué son las aplicaciones?
|
||||
|
||||
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and serverless functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
|
||||
Las aplicaciones te permiten crear y administrar personalizaciones de Twenty **como código**. En lugar de configurar todo a través de la interfaz de usuario, defines tu modelo de datos y funciones de lógica en código, lo que hace más rápido crear, mantener y desplegar en múltiples espacios de trabajo.
|
||||
|
||||
**What you can do today:**
|
||||
**Lo que puedes hacer hoy:**
|
||||
|
||||
* Define custom objects and fields as code (managed data model)
|
||||
* Build serverless functions with custom triggers
|
||||
* Deploy the same app across multiple workspaces
|
||||
* Define objetos y campos personalizados como código (modelo de datos gestionado)
|
||||
* Crea funciones de lógica con desencadenadores personalizados
|
||||
* Despliega la misma aplicación en múltiples espacios de trabajo
|
||||
|
||||
**Coming soon:**
|
||||
**Próximamente:**
|
||||
|
||||
* Custom UI layouts and components
|
||||
* Diseños y componentes de la interfaz de usuario personalizados
|
||||
|
||||
## Prerequisites
|
||||
## Prerrequisitos
|
||||
|
||||
* Node.js 24+ and Yarn 4
|
||||
* A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
|
||||
* Node.js 24+ y Yarn 4
|
||||
* Un espacio de trabajo de Twenty y una clave de API (créala en https://app.twenty.com/settings/api-webhooks)
|
||||
|
||||
## Getting Started
|
||||
## Primeros pasos
|
||||
|
||||
Create a new app using the official scaffolder, then authenticate and start developing:
|
||||
Crea una aplicación nueva usando el generador oficial, luego autentícate y comienza a desarrollar:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app
|
||||
# Crear la estructura de una nueva aplicación
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
|
||||
# Authenticate using your API key (you'll be prompted)
|
||||
yarn auth
|
||||
# Si no usas yarn@4
|
||||
corepack enable
|
||||
yarn install
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
yarn dev
|
||||
# Autentícate con tu clave de API (se te pedirá)
|
||||
yarn auth:login
|
||||
|
||||
# Inicia el modo de desarrollo: sincroniza automáticamente los cambios locales con tu espacio de trabajo
|
||||
yarn app:dev
|
||||
```
|
||||
|
||||
From here you can:
|
||||
Desde aquí usted puede:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn create-entity
|
||||
# Añade una nueva entidad a tu aplicación (guiado)
|
||||
yarn entity:add
|
||||
|
||||
# Generate a typed Twenty client and workspace entity types
|
||||
yarn generate
|
||||
# Genera un cliente tipado de Twenty y tipos de entidad del espacio de trabajo
|
||||
yarn app:generate
|
||||
|
||||
# Run a one‑time sync (instead of watch mode)
|
||||
yarn sync
|
||||
# Supervisa los registros de funciones de tu aplicación
|
||||
yarn function:logs
|
||||
|
||||
# Watch your application's functions logs
|
||||
yarn logs
|
||||
# Ejecuta una función por nombre
|
||||
yarn function:execute -n my-function -p '{\"name\": \"test\"}'
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn uninstall
|
||||
# Desinstala la aplicación del espacio de trabajo actual
|
||||
yarn app:uninstall
|
||||
|
||||
# Display commands' help
|
||||
# Muestra la ayuda de los comandos
|
||||
yarn help
|
||||
```
|
||||
|
||||
See also: the CLI reference pages for [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) and [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
Consulta también: las páginas de referencia de la CLI para [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) y [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
|
||||
## Project structure (scaffolded)
|
||||
## Estructura del proyecto (generada)
|
||||
|
||||
When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
|
||||
Cuando ejecutas `npx create-twenty-app@latest my-twenty-app`, el generador:
|
||||
|
||||
* Copies a minimal base application into `my-twenty-app/`
|
||||
* Adds a local `twenty-sdk` dependency and Yarn 4 configuration
|
||||
* Creates config files and scripts wired to the `twenty` CLI
|
||||
* Generates a default application config and a default function role
|
||||
* Copia una aplicación base mínima en `my-twenty-app/`
|
||||
* Añade una dependencia local de `twenty-sdk` y la configuración de Yarn 4
|
||||
* Crea archivos de configuración y scripts vinculados a la CLI `twenty`
|
||||
* Genera una configuración de aplicación predeterminada y un rol de función predeterminado
|
||||
|
||||
A freshly scaffolded app looks like this:
|
||||
Una aplicación recién generada se ve así:
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -85,79 +89,150 @@ my-twenty-app/
|
||||
.nvmrc
|
||||
.yarnrc.yml
|
||||
.yarn/
|
||||
releases/
|
||||
yarn-4.9.2.cjs
|
||||
install-state.gz
|
||||
eslint.config.mjs
|
||||
tsconfig.json
|
||||
README.md
|
||||
public/ # Carpeta de recursos públicos (imágenes, fuentes, etc.)
|
||||
src/
|
||||
application.config.ts
|
||||
role.config.ts
|
||||
// your entities, actions, and other app files
|
||||
application.config.ts # Requerido - configuración principal de la aplicación
|
||||
default-function.role.ts # Rol predeterminado para funciones sin servidor
|
||||
hello-world.function.ts # Ejemplo de función sin servidor
|
||||
hello-world.front-component.tsx # Ejemplo de componente frontal
|
||||
// tus entidades (*.object.ts, *.function.ts, *.front-component.tsx, *.role.ts)
|
||||
```
|
||||
|
||||
At a high level:
|
||||
### Convención sobre configuración
|
||||
|
||||
* **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall`, and `auth` that delegate to the local `twenty` CLI.
|
||||
* **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
|
||||
* **.nvmrc**: Pins the Node.js version expected by the project.
|
||||
* **eslint.config.mjs** and **tsconfig.json**: Provide linting and TypeScript configuration for your app’s TypeScript sources.
|
||||
* **README.md**: A short README in the app root with basic instructions.
|
||||
* **src/**: The main place where you define your application-as-code:
|
||||
* `application.config.ts`: Global configuration for your app (metadata and runtime wiring). See “Application config” below.
|
||||
* `role.config.ts`: Default function role used by your serverless functions. See “Default function role” below.
|
||||
* Future entities, actions/functions, and any supporting code you add.
|
||||
Las aplicaciones usan un enfoque de **convención sobre configuración** en el que las entidades se detectan por su sufijo de archivo. Esto permite una organización flexible dentro de la carpeta `src/app/`:
|
||||
|
||||
Later commands will add more files and folders:
|
||||
| Sufijo de archivo | Tipo de entidad |
|
||||
| ----------------------- | --------------------------------------- |
|
||||
| `*.object.ts` | Definiciones de objetos personalizados |
|
||||
| `*.function.ts` | Definiciones de funciones sin servidor |
|
||||
| `*.front-component.tsx` | Definiciones de componentes de interfaz |
|
||||
| `*.role.ts` | Definiciones de roles |
|
||||
|
||||
* `yarn generate` will create a `generated/` folder (typed Twenty client + workspace types).
|
||||
* `yarn create-entity` will add entity definition files under `src/` for your custom objects.
|
||||
### Organizaciones de carpetas compatibles
|
||||
|
||||
## Authentication
|
||||
Puedes organizar tus entidades con cualquiera de estos patrones:
|
||||
|
||||
The first time you run `yarn auth`, you'll be prompted for:
|
||||
**Tradicional (por tipo):**
|
||||
|
||||
* API URL (defaults to http://localhost:3000 or your current workspace profile)
|
||||
* API key
|
||||
```text
|
||||
src/
|
||||
├── application.config.ts
|
||||
├── objects/
|
||||
│ └── postCard.object.ts
|
||||
├── functions/
|
||||
│ └── createPostCard.function.ts
|
||||
├── components/
|
||||
│ └── card.front-component.tsx
|
||||
└── roles/
|
||||
└── admin.role.ts
|
||||
```
|
||||
|
||||
Your credentials are stored per-user in `~/.twenty/config.json`. You can maintain multiple profiles and switch using `--workspace <name>`.
|
||||
**Basado en funcionalidades:**
|
||||
|
||||
Examples:
|
||||
```text
|
||||
src/
|
||||
├── application.config.ts
|
||||
└── post-card/
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
├── card.front-component.tsx
|
||||
└── postCardAdmin.role.ts
|
||||
```
|
||||
|
||||
**Plano:**
|
||||
|
||||
```text
|
||||
src/
|
||||
├── application.config.ts
|
||||
├── postCard.object.ts
|
||||
├── createPostCard.function.ts
|
||||
├── card.front-component.tsx
|
||||
└── admin.role.ts
|
||||
```
|
||||
|
||||
A grandes rasgos:
|
||||
|
||||
* **package.json**: Declara el nombre de la aplicación, la versión, los entornos (Node 24+, Yarn 4) y agrega `twenty-sdk` además de scripts como `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall` y `auth:login` que delegan en la CLI local `twenty`.
|
||||
* **.gitignore**: Ignora artefactos comunes como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, carpetas de cobertura, archivos de registro y archivos `.env*`.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloquean y configuran la cadena de herramientas Yarn 4 utilizada por el proyecto.
|
||||
* **.nvmrc**: Fija la versión de Node.js esperada por el proyecto.
|
||||
* **eslint.config.mjs** y **tsconfig.json**: Proporcionan linting y configuración de TypeScript para las fuentes de TypeScript de tu aplicación.
|
||||
* **README.md**: Un README breve en la raíz de la aplicación con instrucciones básicas.
|
||||
* **public/**: Una carpeta para almacenar recursos públicos (imágenes, fuentes, archivos estáticos) que se servirán con tu aplicación. Los archivos colocados aquí se cargan durante la sincronización y son accesibles en tiempo de ejecución.
|
||||
* **src/**: El lugar principal donde defines tu aplicación como código:
|
||||
* `application.config.ts`: Configuración global de tu aplicación (metadatos y vinculación en tiempo de ejecución). Consulta "Configuración de la aplicación" más abajo.
|
||||
* `*.role.ts`: Definiciones de roles usadas por tus funciones de lógica. Consulta "Rol de función predeterminado" más abajo.
|
||||
* `*.object.ts`: Definiciones de objetos personalizados.
|
||||
* `*.function.ts`: Definiciones de funciones de lógica.
|
||||
* `*.front-component.tsx`: Definiciones de componentes de interfaz.
|
||||
|
||||
Comandos posteriores añadirán más archivos y carpetas:
|
||||
|
||||
* `yarn app:generate` creará una carpeta `generated/` (cliente tipado de Twenty + tipos del espacio de trabajo).
|
||||
* `yarn entity:add` añadirá archivos de definición de entidades en `src/` para tus objetos, funciones, componentes de interfaz o roles personalizados.
|
||||
|
||||
## Autenticación
|
||||
|
||||
La primera vez que ejecutes `yarn auth:login`, se te solicitará:
|
||||
|
||||
* URL de la API (por defecto http://localhost:3000 o el perfil de tu espacio de trabajo actual)
|
||||
* Clave de API
|
||||
|
||||
Tus credenciales se almacenan por usuario en `~/.twenty/config.json`. Puedes mantener varios perfiles y cambiar entre ellos.
|
||||
|
||||
### Gestión de espacios de trabajo
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn auth
|
||||
yarn auth:login
|
||||
|
||||
# Use a specific workspace profile
|
||||
yarn auth --workspace my-custom-workspace
|
||||
# Login to a specific workspace profile
|
||||
yarn auth:login --workspace my-custom-workspace
|
||||
|
||||
# List all configured workspaces
|
||||
yarn auth:list
|
||||
|
||||
# Switch the default workspace (interactive)
|
||||
yarn auth:switch
|
||||
|
||||
# Switch to a specific workspace
|
||||
yarn auth:switch production
|
||||
|
||||
# Check current authentication status
|
||||
yarn auth:status
|
||||
```
|
||||
|
||||
## Use the SDK resources (types & config)
|
||||
Una vez que hayas cambiado de espacio de trabajo con `auth:switch`, todos los comandos posteriores usarán ese espacio de trabajo de forma predeterminada. Aún puedes anularlo temporalmente con `--workspace <name>`.
|
||||
|
||||
The twenty-sdk provides typed building blocks you use inside your app. Below are the key pieces you'll touch most often.
|
||||
## Usa los recursos del SDK (tipos y configuración)
|
||||
|
||||
### Defining objects
|
||||
El twenty-sdk proporciona bloques de construcción tipados y funciones auxiliares que utilizas dentro de tu aplicación. A continuación, las partes clave que usarás con más frecuencia.
|
||||
|
||||
Custom objects are regular TypeScript classes annotated with decorators from `twenty-sdk`. They live under `src/objects/` in your app and describe both schema and behavior for records in your workspace.
|
||||
### Funciones auxiliares
|
||||
|
||||
Here is an example `postCard` object from the Hello World app:
|
||||
El SDK proporciona cuatro funciones auxiliares con validación incorporada para definir las entidades de tu aplicación:
|
||||
|
||||
| Función | Propósito |
|
||||
| --------------------- | ---------------------------------------------- |
|
||||
| `defineApplication()` | Configura los metadatos de la aplicación |
|
||||
| `defineObject()` | Define objetos personalizados con campos |
|
||||
| `defineFunction()` | Define funciones de lógica con controladores |
|
||||
| `defineRole()` | Configura permisos de roles y acceso a objetos |
|
||||
|
||||
Estas funciones validan tu configuración en tiempo de ejecución y proporcionan un mejor autocompletado en el IDE y seguridad de tipos.
|
||||
|
||||
### Definir objetos
|
||||
|
||||
Los objetos personalizados describen tanto el esquema como el comportamiento de los registros en tu espacio de trabajo. Usa `defineObject()` para definir objetos con validación incorporada:
|
||||
|
||||
```typescript
|
||||
import { type Note } from '../../generated';
|
||||
|
||||
import {
|
||||
type AddressField,
|
||||
Field,
|
||||
FieldType,
|
||||
type FullNameField,
|
||||
Object,
|
||||
OnDeleteAction,
|
||||
Relation,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
// src/app/postCard.object.ts
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
enum PostCardStatus {
|
||||
DRAFT = 'DRAFT',
|
||||
@@ -166,176 +241,135 @@ enum PostCardStatus {
|
||||
RETURNED = 'RETURNED',
|
||||
}
|
||||
|
||||
@Object({
|
||||
export default defineObject({
|
||||
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
|
||||
nameSingular: 'postCard',
|
||||
namePlural: 'postCards',
|
||||
labelSingular: 'Post card',
|
||||
labelPlural: 'Post cards',
|
||||
description: ' A post card object',
|
||||
labelSingular: 'Post Card',
|
||||
labelPlural: 'Post Cards',
|
||||
description: 'A post card object',
|
||||
icon: 'IconMail',
|
||||
})
|
||||
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,
|
||||
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;
|
||||
}
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||||
name: 'content',
|
||||
type: FieldType.TEXT,
|
||||
label: 'Content',
|
||||
description: "Postcard's content",
|
||||
icon: 'IconAbc',
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
|
||||
name: 'recipientName',
|
||||
type: FieldType.FULL_NAME,
|
||||
label: 'Recipient name',
|
||||
icon: 'IconUser',
|
||||
},
|
||||
{
|
||||
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
|
||||
name: 'recipientAddress',
|
||||
type: FieldType.ADDRESS,
|
||||
label: 'Recipient address',
|
||||
icon: 'IconHome',
|
||||
},
|
||||
{
|
||||
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
|
||||
name: 'status',
|
||||
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' },
|
||||
],
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
|
||||
name: 'deliveredAt',
|
||||
type: FieldType.DATE_TIME,
|
||||
label: 'Delivered at',
|
||||
icon: 'IconCheck',
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Key points:
|
||||
Puntos clave:
|
||||
|
||||
* The `@Object` decorator defines the object identity and labels used across the workspace; its `universalIdentifier` must be unique and stable across deployments.
|
||||
* Each `@Field` decorator defines a field on the object with a type, label, and its own stable `universalIdentifier`.
|
||||
* `@Relation` wires this object to other objects (standard or custom) and controls cascade behavior with `onDelete`.
|
||||
* You can scaffold new objects using `yarn create-entity`, which guides you through naming, fields, and relationships, then generates object files similar to the `postCard` example.
|
||||
* Usa `defineObject()` para validación incorporada y mejor soporte del IDE.
|
||||
* El `universalIdentifier` debe ser único y estable entre implementaciones.
|
||||
* Cada campo requiere `name`, `type`, `label` y su propio `universalIdentifier` estable.
|
||||
* La matriz `fields` es opcional: puedes definir objetos sin campos personalizados.
|
||||
* Puedes generar nuevos objetos usando `yarn entity:add`, que te guía por el nombrado, los campos y las relaciones.
|
||||
|
||||
### Application config (application.config.ts)
|
||||
<Note>
|
||||
**Los campos base se crean automáticamente.** Cuando defines un objeto personalizado, Twenty añade automáticamente campos estándar como `name`, `createdAt`, `updatedAt`, `createdBy`, `position` y `deletedAt`. No necesitas definir estos en tu matriz `fields` — solo agrega tus campos personalizados.
|
||||
</Note>
|
||||
|
||||
Every app has a single `application.config.ts` file that describes:
|
||||
### Configuración de la aplicación (application.config.ts)
|
||||
|
||||
* **Who the app is**: identifiers, display name, and description.
|
||||
* **How its functions run**: which role they use for permissions.
|
||||
* **(Optional) variables**: key–value pairs exposed to your functions as environment variables.
|
||||
Cada aplicación tiene un único archivo `application.config.ts` que describe:
|
||||
|
||||
When you scaffold a new app, you start with a minimal config:
|
||||
* **Qué es la aplicación**: identificadores, nombre para mostrar y descripción.
|
||||
* **Cómo se ejecutan sus funciones**: qué rol usan para permisos.
|
||||
* **Variables (opcionales)**: pares clave–valor expuestos a tus funciones como variables de entorno.
|
||||
|
||||
Use `defineApplication()` to define your application configuration:
|
||||
|
||||
```typescript
|
||||
import { type ApplicationConfig } from 'twenty-sdk';
|
||||
// src/app/application.config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: '<generated-app-uuid>',
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
functionRoleUniversalIdentifier: '<generated-role-uuid>',
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
You can gradually extend this file as your app grows. For example, you can add an icon and application-scoped variables:
|
||||
|
||||
```typescript
|
||||
import { type ApplicationConfig } from 'twenty-sdk';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: '<your-app-uuid>',
|
||||
displayName: 'My App',
|
||||
description: 'What your app does',
|
||||
icon: 'IconWorld', // Choose an icon by name
|
||||
icon: 'IconWorld',
|
||||
applicationVariables: {
|
||||
DEFAULT_RECIPIENT_NAME: {
|
||||
universalIdentifier: '<uuid>',
|
||||
description: 'Default recipient used by functions',
|
||||
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
|
||||
description: 'Default recipient name for postcards',
|
||||
value: 'Jane Doe',
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
functionRoleUniversalIdentifier: '<your-role-uuid>',
|
||||
};
|
||||
|
||||
export default config;
|
||||
roleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Notes:
|
||||
Notas:
|
||||
|
||||
* `universalIdentifier` fields are deterministic IDs you own; generate them once and keep them stable across syncs.
|
||||
* `applicationVariables` become environment variables for your functions (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `functionRoleUniversalIdentifier` must match the role you define in `role.config.ts` (see below).
|
||||
* Los campos `universalIdentifier` son ID deterministas bajo tu control; genéralos una vez y mantenlos estables entre sincronizaciones.
|
||||
* Las `applicationVariables` se convierten en variables de entorno para tus funciones (por ejemplo, `DEFAULT_RECIPIENT_NAME` está disponible como `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `roleUniversalIdentifier` must match the role you define in your `*.role.ts` file (see below).
|
||||
|
||||
#### Roles and permissions
|
||||
#### Roles y permisos
|
||||
|
||||
Applications can define roles that encapsulate permissions on your workspace’s objects and actions. The field `functionRoleUniversalIdentifier` in `application.config.ts` designates the default role used by your app’s serverless functions.
|
||||
Las aplicaciones pueden definir roles que encapsulan permisos sobre los objetos y acciones de tu espacio de trabajo. The field `roleUniversalIdentifier` in `application.config.ts` designates the default role used by your app's logic functions.
|
||||
|
||||
* The runtime API key injected as `TWENTY_API_KEY` is derived from this default function role.
|
||||
* The typed client will be restricted to the permissions granted to that role.
|
||||
* Follow least‑privilege: create a dedicated role with only the permissions your functions need, then reference its universal identifier.
|
||||
* La clave de API en tiempo de ejecución inyectada como `TWENTY_API_KEY` se deriva de este rol de función predeterminado.
|
||||
* El cliente tipado estará restringido a los permisos otorgados a ese rol.
|
||||
* Sigue el principio de mínimo privilegio: crea un rol dedicado con solo los permisos que necesitan tus funciones y luego referencia su identificador universal.
|
||||
|
||||
##### Default function role (role.config.ts)
|
||||
##### Rol de función predeterminado (\*.role.ts)
|
||||
|
||||
When you scaffold a new app, the CLI also creates `src/role.config.ts`. This file exports the default role your serverless functions will use at runtime:
|
||||
Cuando generas una nueva aplicación, la CLI también crea un archivo de rol predeterminado. Usa `defineRole()` para definir roles con validación incorporada:
|
||||
|
||||
```typescript
|
||||
import { PermissionFlag, type RoleConfig } from 'twenty-sdk';
|
||||
// src/app/default-function.role.ts
|
||||
import { defineRole, PermissionFlag } from 'twenty-sdk';
|
||||
|
||||
export const functionRole: RoleConfig = {
|
||||
universalIdentifier: '<generated-role-uuid>',
|
||||
label: 'My Twenty App default function role',
|
||||
description: 'My Twenty App default function role',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: false,
|
||||
};
|
||||
```
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||||
|
||||
The `universalIdentifier` of this role is automatically wired into `application.config.ts` as `functionRoleUniversalIdentifier`. In other words:
|
||||
|
||||
* **role.config.ts** defines what the default function role can do.
|
||||
* **application.config.ts** points to that role so your functions inherit its permissions.
|
||||
|
||||
As you move beyond the initial scaffold, you should tighten this role and make it explicit about what it can access. A more production-ready role might look closer to:
|
||||
|
||||
```typescript
|
||||
import { PermissionFlag, type RoleConfig } from 'twenty-sdk';
|
||||
|
||||
export const functionRole: RoleConfig = {
|
||||
universalIdentifier: '<your-role-uuid>',
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Default function role',
|
||||
description: 'Default role for function Twenty client',
|
||||
canReadAllObjectRecords: false,
|
||||
@@ -348,7 +382,7 @@ export const functionRole: RoleConfig = {
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
@@ -357,47 +391,42 @@ export const functionRole: RoleConfig = {
|
||||
],
|
||||
fieldPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
fieldName: 'content',
|
||||
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
||||
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
|
||||
canReadFieldValue: false,
|
||||
canUpdateFieldValue: false,
|
||||
},
|
||||
],
|
||||
permissionFlags: ['APPLICATIONS'],
|
||||
};
|
||||
permissionFlags: [PermissionFlag.APPLICATIONS],
|
||||
});
|
||||
```
|
||||
|
||||
Notes:
|
||||
The `universalIdentifier` of this role is then referenced in `application.config.ts` as `roleUniversalIdentifier`. En otras palabras:
|
||||
|
||||
* Start from the scaffolded role, then progressively restrict it following least‑privilege.
|
||||
* Replace the `objectPermissions` and `fieldPermissions` with the objects/fields your functions need.
|
||||
* `permissionFlags` control access to platform-level capabilities. Keep them minimal; add only what you need.
|
||||
* See a working example in the Hello World app: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
* **\*.role.ts** define lo que puede hacer el rol de función predeterminado.
|
||||
* **application.config.ts** apunta a ese rol para que tus funciones hereden sus permisos.
|
||||
|
||||
### Serverless function config and entrypoint
|
||||
Notas:
|
||||
|
||||
Each function exports a main handler and a config describing its triggers. You can mix multiple trigger types.
|
||||
* Parte del rol generado y luego restríngele progresivamente siguiendo el principio de mínimo privilegio.
|
||||
* Reemplaza `objectPermissions` y `fieldPermissions` con los objetos/campos que necesitan tus funciones.
|
||||
* `permissionFlags` controla el acceso a capacidades a nivel de plataforma. Mantenlos al mínimo; agrega solo lo que necesites.
|
||||
* Consulta un ejemplo funcional en la aplicación Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
|
||||
### Configuración y punto de entrada de funciones de lógica
|
||||
|
||||
Cada archivo de función usa `defineFunction()` para exportar una configuración con un controlador y desencadenadores opcionales. Usa el sufijo de archivo `*.function.ts` para la detección automática.
|
||||
|
||||
```typescript
|
||||
// src/actions/create-new-post-card.ts
|
||||
import type {
|
||||
FunctionConfig,
|
||||
DatabaseEventPayload,
|
||||
ObjectRecordCreateEvent,
|
||||
CronPayload,
|
||||
} from 'twenty-sdk';
|
||||
import Twenty, { type Person } from '../generated';
|
||||
// src/app/createPostCard.function.ts
|
||||
import { defineFunction } from 'twenty-sdk';
|
||||
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
|
||||
import Twenty, { type Person } from '~/generated';
|
||||
|
||||
// main handler can accept parameters from route, cron, or database events
|
||||
export const main = async (
|
||||
params:
|
||||
| { name?: string }
|
||||
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
|
||||
| CronPayload,
|
||||
) => {
|
||||
const handler = async (params: RoutePayload) => {
|
||||
const client = new Twenty(); // generated typed client
|
||||
const name = 'name' in params
|
||||
? params.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
const name = 'name' in params.queryStringParameters
|
||||
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
: 'Hello world';
|
||||
|
||||
const result = await client.mutation({
|
||||
@@ -410,113 +439,216 @@ export const main = async (
|
||||
return result;
|
||||
};
|
||||
|
||||
export const config: FunctionConfig = {
|
||||
universalIdentifier: '<function-uuid>',
|
||||
export default defineFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'create-new-post-card',
|
||||
timeoutSeconds: 2,
|
||||
handler,
|
||||
triggers: [
|
||||
// Public HTTP route trigger '/s/post-card/create'
|
||||
{
|
||||
universalIdentifier: '<route-trigger-uuid>',
|
||||
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
||||
type: 'route',
|
||||
path: '/post-card/create',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: false,
|
||||
},
|
||||
// Cron trigger (CRON pattern)
|
||||
{
|
||||
universalIdentifier: '<cron-trigger-uuid>',
|
||||
type: 'cron',
|
||||
pattern: '0 0 1 1 *',
|
||||
},
|
||||
// {
|
||||
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
||||
// type: 'cron',
|
||||
// pattern: '0 0 1 1 *',
|
||||
// },
|
||||
// Database event trigger
|
||||
{
|
||||
universalIdentifier: '<db-trigger-uuid>',
|
||||
type: 'databaseEvent',
|
||||
eventName: 'person.created',
|
||||
},
|
||||
// {
|
||||
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
||||
// type: 'databaseEvent',
|
||||
// eventName: 'person.updated',
|
||||
// updatedFields: ['name'],
|
||||
// },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Tipos de desencadenadores comunes:
|
||||
|
||||
* **route**: Expone tu función en una ruta y método HTTP **bajo el endpoint `/s/`**:
|
||||
|
||||
> p. ej. `path: '/post-card/create',` -> llamar en `<APP_URL>/s/post-card/create`
|
||||
|
||||
* **cron**: Ejecuta tu función en un horario usando una expresión CRON.
|
||||
* **databaseEvent**: Se ejecuta en eventos del ciclo de vida de objetos del espacio de trabajo. Cuando la operación del evento es `updated`, se pueden especificar campos específicos que se deben escuchar en el arreglo `updatedFields`. Si se deja sin definir o vacío, cualquier actualización activará la función.
|
||||
|
||||
> p. ej., `person.updated`
|
||||
|
||||
Notas:
|
||||
|
||||
* La matriz `triggers` es opcional. Las funciones sin desencadenadores pueden usarse como funciones utilitarias llamadas por otras funciones.
|
||||
* Puedes combinar múltiples tipos de desencadenadores en una sola función.
|
||||
|
||||
### Carga útil del disparador de ruta
|
||||
|
||||
<Warning>
|
||||
**Cambio no retrocompatible (v1.16, enero de 2026):** El formato de la carga útil del disparador de ruta ha cambiado. Antes de la v1.16, los parámetros de consulta, los parámetros de ruta y el cuerpo se enviaban directamente como la carga útil. A partir de la v1.16, están anidados dentro de un objeto `RoutePayload` estructurado.
|
||||
|
||||
**Antes de la v1.16:**
|
||||
|
||||
```typescript
|
||||
const handler = async (params) => {
|
||||
const { param1, param2 } = params; // Direct access
|
||||
};
|
||||
```
|
||||
|
||||
**Después de la v1.16:**
|
||||
|
||||
```typescript
|
||||
const handler = async (event: RoutePayload) => {
|
||||
const { param1, param2 } = event.body; // Access via .body
|
||||
const { queryParam } = event.queryStringParameters;
|
||||
const { id } = event.pathParameters;
|
||||
};
|
||||
```
|
||||
|
||||
**Para migrar las funciones existentes:** Actualiza tu controlador para desestructurar desde `event.body`, `event.queryStringParameters` o `event.pathParameters` en lugar de hacerlo directamente desde el objeto params.
|
||||
</Warning>
|
||||
|
||||
Cuando un disparador de ruta invoca tu función de lógica, esta recibe un objeto `RoutePayload` que sigue el formato de AWS HTTP API v2. Importa el tipo desde `twenty-sdk`:
|
||||
|
||||
```typescript
|
||||
import { defineFunction, type RoutePayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
// Access request data
|
||||
const { headers, queryStringParameters, pathParameters, body } = event;
|
||||
|
||||
// HTTP method and path are available in requestContext
|
||||
const { method, path } = event.requestContext.http;
|
||||
|
||||
return { message: 'Success' };
|
||||
};
|
||||
```
|
||||
|
||||
Common trigger types:
|
||||
El tipo `RoutePayload` tiene la siguiente estructura:
|
||||
|
||||
* route: Exposes your function on an HTTP path and method **under the `/s/` endpoint**:
|
||||
| Propiedad | Tipo | Descripción |
|
||||
| ---------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `headers` | `Record<string, string \| undefined>` | Encabezados HTTP (solo aquellos listados en `forwardedRequestHeaders`) |
|
||||
| `queryStringParameters` | `Record<string, string \| undefined>` | Parámetros de consulta (valores múltiples unidos con comas) |
|
||||
| `pathParameters` | `Record<string, string \| undefined>` | Parámetros de ruta extraídos del patrón de ruta (p. ej., `/users/:id` → `{ id: '123' }`) |
|
||||
| `cuerpo` | `object \| null` | Cuerpo de la solicitud analizado (JSON) |
|
||||
| `isBase64Encoded` | `booleano` | Indica si el cuerpo está codificado en base64 |
|
||||
| `requestContext.http.method` | `string` | Método HTTP (GET, POST, PUT, PATCH, DELETE) |
|
||||
| `requestContext.http.path` | `string` | Ruta de la solicitud sin procesar |
|
||||
|
||||
> e.g. `path: '/post-card/create',` -> call on `<APP_URL>/s/post-card/create`
|
||||
### Reenvío de encabezados HTTP
|
||||
|
||||
* cron: Runs your function on a schedule using a CRON expression.
|
||||
* databaseEvent: Runs on workspace object lifecycle events
|
||||
|
||||
> e.g. `person.created`
|
||||
|
||||
You can create new functions in two ways:
|
||||
|
||||
* **Scaffolded**: Run `yarn create-entity --path <custom-path>` and choose the option to add a new function. This generates a starter file under `<custom-path>` with a `main` handler and a `config` block similar to the example above.
|
||||
* **Manual**: Create a new file and export `main` and `config` yourself, following the same pattern.
|
||||
|
||||
### Generated typed client
|
||||
|
||||
Run yarn generate to create a local typed client in generated/ based on your workspace schema. Use it in your functions:
|
||||
De forma predeterminada, los encabezados HTTP de las solicitudes entrantes **no** se pasan a tu función de lógica por razones de seguridad. Para acceder a encabezados específicos, enuméralos explícitamente en el arreglo `forwardedRequestHeaders`:
|
||||
|
||||
```typescript
|
||||
import Twenty from './generated';
|
||||
export default defineFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'webhook-handler',
|
||||
handler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
||||
type: 'route',
|
||||
path: '/webhook',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: false,
|
||||
forwardedRequestHeaders: ['x-webhook-signature', 'content-type'],
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
En tu controlador, luego puedes acceder a estos encabezados:
|
||||
|
||||
```typescript
|
||||
const handler = async (event: RoutePayload) => {
|
||||
const signature = event.headers['x-webhook-signature'];
|
||||
const contentType = event.headers['content-type'];
|
||||
|
||||
// Validate webhook signature...
|
||||
return { received: true };
|
||||
};
|
||||
```
|
||||
|
||||
<Note>
|
||||
Los nombres de los encabezados se normalizan a minúsculas. Accede a ellos usando claves en minúsculas (por ejemplo, `event.headers['content-type']`).
|
||||
</Note>
|
||||
|
||||
Puedes crear funciones nuevas de dos maneras:
|
||||
|
||||
* **Generado**: Ejecuta `yarn entity:add` y elige la opción para añadir una nueva función. Esto genera un archivo inicial con un controlador y configuración.
|
||||
* **Manual**: Crea un nuevo archivo `*.function.ts` y usa `defineFunction()`, siguiendo el mismo patrón.
|
||||
|
||||
### Cliente tipado generado
|
||||
|
||||
Ejecuta yarn app:generate para crear un cliente tipado local en generated/ basado en el esquema de tu espacio de trabajo. Úsalo en tus funciones:
|
||||
|
||||
```typescript
|
||||
import Twenty from '~/generated';
|
||||
|
||||
const client = new Twenty();
|
||||
const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
```
|
||||
|
||||
The client is re-generated by `yarn generate`. Re-run after changing your objects and `yarn sync` or when onboarding to a new workspace.
|
||||
El cliente se vuelve a generar con `yarn app:generate`. Vuelve a ejecutarlo después de cambiar tus objetos o al incorporarte a un nuevo espacio de trabajo.
|
||||
|
||||
#### Runtime credentials in serverless functions
|
||||
#### Credenciales en tiempo de ejecución en funciones de lógica
|
||||
|
||||
When your function runs on Twenty, the platform injects credentials as environment variables before your code executes:
|
||||
Cuando tu función se ejecuta en Twenty, la plataforma inyecta credenciales como variables de entorno antes de que tu código se ejecute:
|
||||
|
||||
* `TWENTY_API_URL`: Base URL of the Twenty API your app targets.
|
||||
* `TWENTY_API_KEY`: Short‑lived key scoped to your application’s default function role.
|
||||
* `TWENTY_API_URL`: URL base de la API de Twenty a la que apunta tu aplicación.
|
||||
* `TWENTY_API_KEY`: Clave de corta duración con alcance al rol de función predeterminado de tu aplicación.
|
||||
|
||||
Notes:
|
||||
Notas:
|
||||
|
||||
* You do not need to pass URL or API key to the generated client. It reads `TWENTY_API_URL` and `TWENTY_API_KEY` from process.env at runtime.
|
||||
* The API key’s permissions are determined by the role referenced in your `application.config.ts` via `functionRoleUniversalIdentifier`. This is the default role used by serverless functions of your application.
|
||||
* Applications can define roles to follow least‑privilege. Grant only the permissions your functions need, then point `functionRoleUniversalIdentifier` to that role’s universal identifier.
|
||||
* No necesitas pasar la URL ni la clave de API al cliente generado. Lee `TWENTY_API_URL` y `TWENTY_API_KEY` de process.env en tiempo de ejecución.
|
||||
* The API key's permissions are determined by the role referenced in your `application.config.ts` via `roleUniversalIdentifier`. Este es el rol predeterminado que usan las funciones de lógica de tu aplicación.
|
||||
* Las aplicaciones pueden definir roles para seguir el principio de mínimo privilegio. Grant only the permissions your functions need, then point `roleUniversalIdentifier` to that role's universal identifier.
|
||||
|
||||
### Hello World example
|
||||
### Ejemplo Hello World
|
||||
|
||||
Explore a minimal, end-to-end example that demonstrates objects, functions, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
Explora un ejemplo mínimo de extremo a extremo que demuestra objetos, funciones y múltiples desencadenadores [aquí](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
|
||||
## Manual setup (without the scaffolder)
|
||||
## Configuración manual (sin el generador)
|
||||
|
||||
While we recommend using `create-twenty-app` for the best getting-started experience, you can also set up a project manually. Do not install the CLI globally. Instead, add `twenty-sdk` as a local dependency and wire scripts in your package.json:
|
||||
Aunque recomendamos usar `create-twenty-app` para la mejor experiencia de inicio, también puedes configurar un proyecto manualmente. No instales la CLI globalmente. En su lugar, agrega `twenty-sdk` como dependencia local y conecta scripts en tu package.json:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
```
|
||||
|
||||
Then add scripts like these:
|
||||
Luego agrega scripts como estos:
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"auth": "twenty auth login",
|
||||
"generate": "twenty app generate",
|
||||
"dev": "twenty app dev",
|
||||
"sync": "twenty app sync",
|
||||
"uninstall": "twenty app uninstall",
|
||||
"logs": "twenty app logs",
|
||||
"create-entity": "twenty app add",
|
||||
"help": "twenty --help"
|
||||
"auth:login": "twenty auth:login",
|
||||
"auth:logout": "twenty auth:logout",
|
||||
"auth:status": "twenty auth:status",
|
||||
"auth:switch": "twenty auth:switch",
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"app:generate": "twenty app:generate",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
"entity:add": "twenty entity:add",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"help": "twenty help"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now you can run the same commands via Yarn, e.g. `yarn dev`, `yarn sync`, etc.
|
||||
Ahora puedes ejecutar los mismos comandos mediante Yarn, p. ej., `yarn app:dev`, `yarn app:generate`, etc.
|
||||
|
||||
## Troubleshooting
|
||||
## Solución de problemas
|
||||
|
||||
* Authentication errors: run `yarn auth` and ensure your API key has the required permissions.
|
||||
* Cannot connect to server: verify the API URL and that the Twenty server is reachable.
|
||||
* Types or client missing/outdated: run `yarn generate` and then `yarn dev`.
|
||||
* Dev mode not syncing: ensure `yarn dev` is running and that changes are not ignored by your environment.
|
||||
* Errores de autenticación: ejecuta `yarn auth:login` y asegúrate de que tu clave de API tenga los permisos necesarios.
|
||||
* No se puede conectar al servidor: verifica la URL de la API y que el servidor de Twenty sea accesible.
|
||||
* Tipos o cliente faltantes/obsoletos: ejecuta `yarn app:generate`.
|
||||
* El modo de desarrollo no sincroniza: asegúrate de que `yarn app:dev` esté ejecutándose y de que los cambios no sean ignorados por tu entorno.
|
||||
|
||||
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
Canal de ayuda en Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
---
|
||||
title: Webhooks
|
||||
description: Receive real-time notifications when events occur in your CRM.
|
||||
description: Recibe notificaciones en tiempo real cuando ocurran eventos en tu CRM.
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
Webhooks push data to your systems in real-time when events occur in Twenty — no polling required. Use them to keep external systems in sync, trigger automations, or send alerts.
|
||||
Los webhooks envían datos a tus sistemas en tiempo real cuando ocurren eventos en Twenty — no se requiere sondeo. Úsalos para mantener sincronizados los sistemas externos, activar automatizaciones o enviar alertas.
|
||||
|
||||
## Create a Webhook
|
||||
## Crear un Webhook
|
||||
|
||||
1. Go to **Settings → APIs & Webhooks → Webhooks**
|
||||
2. Click **+ Create webhook**
|
||||
3. Enter your webhook URL (must be publicly accessible)
|
||||
4. Click **Save**
|
||||
1. Ve a **Configuración → APIs y Webhooks → Webhooks**
|
||||
2. Haga clic en **+ Crear webhook**
|
||||
3. Introduce la URL de tu webhook (debe ser públicamente accesible)
|
||||
4. Haga clic en **Guardar**
|
||||
|
||||
The webhook activates immediately and starts sending notifications.
|
||||
El webhook se activa de inmediato y comienza a enviar notificaciones.
|
||||
|
||||
<VimeoEmbed videoId="928786708" title="Creating a webhook" />
|
||||
<VimeoEmbed videoId="928786708" title="Crear un webhook" />
|
||||
|
||||
### Manage Webhooks
|
||||
### Gestionar Webhooks
|
||||
|
||||
**Edit**: Click the webhook → Update URL → **Save**
|
||||
**Editar**: Haz clic en el webhook → Actualizar la URL → **Guardar**
|
||||
|
||||
**Delete**: Click the webhook → **Delete** → Confirm
|
||||
**Eliminar**: Haz clic en el webhook → **Eliminar** → Confirmar
|
||||
|
||||
## Events
|
||||
## Eventos
|
||||
|
||||
Twenty sends webhooks for these event types:
|
||||
Twenty envía webhooks para estos tipos de eventos:
|
||||
|
||||
| Event | Example |
|
||||
| ------------------ | ---------------------------------------------------------- |
|
||||
| **Record Created** | `person.created`, `company.created`, `note.created` |
|
||||
| **Record Updated** | `person.updated`, `company.updated`, `opportunity.updated` |
|
||||
| **Record Deleted** | `person.deleted`, `company.deleted` |
|
||||
| Evento | Ejemplo |
|
||||
| ---------------------------- | ---------------------------------------------------------- |
|
||||
| **Se crea un registro** | `person.created`, `company.created`, `note.created` |
|
||||
| **Se actualiza un registro** | `person.updated`, `company.updated`, `opportunity.updated` |
|
||||
| **Se elimina un registro** | `person.deleted`, `company.deleted` |
|
||||
|
||||
All event types are sent to your webhook URL. Event filtering may be added in future releases.
|
||||
Todos los tipos de eventos se envían a la URL de tu webhook. Es posible que se agregue el filtrado de eventos en versiones futuras.
|
||||
|
||||
## Payload Format
|
||||
## Formato de la carga útil
|
||||
|
||||
Each webhook sends an HTTP POST with a JSON body:
|
||||
Cada webhook envía una solicitud HTTP POST con un cuerpo JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -55,35 +55,35 @@ Each webhook sends an HTTP POST with a JSON body:
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ----------- | ------------------------------------------------ |
|
||||
| `event` | What happened (e.g., `person.created`) |
|
||||
| `data` | The full record that was created/updated/deleted |
|
||||
| `timestamp` | When the event occurred (UTC) |
|
||||
| Campo | Descripción |
|
||||
| ----------------- | -------------------------------------------------- |
|
||||
| `evento` | Qué ocurrió (p. ej., `person.created`) |
|
||||
| `datos` | El registro completo que se creó/actualizó/eliminó |
|
||||
| `marca de tiempo` | Cuándo ocurrió el evento (UTC) |
|
||||
|
||||
<Note>
|
||||
Respond with a **2xx HTTP status** (200-299) to acknowledge receipt. Non-2xx responses are logged as delivery failures.
|
||||
Responde con un **estado HTTP 2xx** (200-299) para confirmar la recepción. Las respuestas que no sean 2xx se registran como errores de entrega.
|
||||
</Note>
|
||||
|
||||
## Webhook Validation
|
||||
## Validación de Webhook
|
||||
|
||||
Twenty signs each webhook request for security. Validate signatures to ensure requests are authentic.
|
||||
Twenty firma cada solicitud de webhook por seguridad. Valida las firmas para garantizar que las solicitudes sean auténticas.
|
||||
|
||||
### Headers
|
||||
### Encabezados
|
||||
|
||||
| Header | Description |
|
||||
| ---------------------------- | --------------------- |
|
||||
| `X-Twenty-Webhook-Signature` | HMAC SHA256 signature |
|
||||
| `X-Twenty-Webhook-Timestamp` | Request timestamp |
|
||||
| Encabezado | Descripción |
|
||||
| ---------------------------- | ------------------------------- |
|
||||
| `X-Twenty-Webhook-Signature` | Firma HMAC SHA256 |
|
||||
| `X-Twenty-Webhook-Timestamp` | Marca de tiempo de la solicitud |
|
||||
|
||||
### Validation Steps
|
||||
### Pasos de validación
|
||||
|
||||
1. Get the timestamp from `X-Twenty-Webhook-Timestamp`
|
||||
2. Create the string: `{timestamp}:{JSON payload}`
|
||||
3. Compute HMAC SHA256 using your webhook secret
|
||||
4. Compare with `X-Twenty-Webhook-Signature`
|
||||
1. Obtén la marca de tiempo de `X-Twenty-Webhook-Timestamp`
|
||||
2. Crea la cadena: `{timestamp}:{JSON payload}`
|
||||
3. Calcula HMAC SHA256 usando tu secreto de webhook
|
||||
4. Compara con `X-Twenty-Webhook-Signature`
|
||||
|
||||
### Example (Node.js)
|
||||
### Ejemplo (Node.js)
|
||||
|
||||
```javascript
|
||||
const crypto = require("crypto");
|
||||
@@ -101,12 +101,12 @@ const expectedSignature = crypto
|
||||
const isValid = expectedSignature === req.headers["x-twenty-webhook-signature"];
|
||||
```
|
||||
|
||||
## Webhooks vs Workflows
|
||||
## Webhooks vs flujos de trabajo
|
||||
|
||||
| Method | Direction | Use Case |
|
||||
| ---------------------------- | --------- | ---------------------------------------------------------- |
|
||||
| **Webhooks** | OUT | Automatically notify external systems of any record change |
|
||||
| **Workflow + HTTP Request** | OUT | Send data out with custom logic (filters, transformations) |
|
||||
| **Workflow Webhook Trigger** | IN | Receive data into Twenty from external systems |
|
||||
| Método | Dirección | Caso de uso |
|
||||
| --------------------------------------------- | --------- | --------------------------------------------------------------------------------- |
|
||||
| **Webhooks** | SALIDA | Notificar automáticamente a los sistemas externos cualquier cambio en un registro |
|
||||
| **Flujo de trabajo + solicitud HTTP** | SALIDA | Enviar datos con lógica personalizada (filtros, transformaciones) |
|
||||
| **Disparador de webhook de flujo de trabajo** | ENTRADA | Recibir datos en Twenty desde sistemas externos |
|
||||
|
||||
For receiving external data, see [Set Up a Webhook Trigger](/l/es/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger).
|
||||
Para recibir datos externos, consulta [Configurar un disparador de webhook](/l/es/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger).
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
---
|
||||
title: Extend
|
||||
description: Extend Twenty's functionality with APIs, webhooks, and custom apps.
|
||||
title: Ampliar
|
||||
description: Amplía la funcionalidad de Twenty con APIs, webhooks y aplicaciones personalizadas.
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/integrations/plug.png" alt="AI" />
|
||||
<img src="/images/user-guide/integrations/plug.png" alt="IA" />
|
||||
</Frame>
|
||||
|
||||
## Overview
|
||||
## Resumen
|
||||
|
||||
Twenty is designed to be extensible. Use our APIs, webhooks, and app framework to integrate with your existing tools and build custom functionality.
|
||||
Twenty está diseñado para ser extensible. Usa nuestras APIs, webhooks y el framework de aplicaciones para integrarte con tus herramientas existentes y crear funcionalidades personalizadas.
|
||||
|
||||
## What You Can Do
|
||||
## Lo que puedes hacer
|
||||
|
||||
* **APIs**: Query and modify your CRM data programmatically using REST or GraphQL
|
||||
* **Webhooks**: Receive real-time notifications when events occur in Twenty
|
||||
* **Apps**: Build custom applications that extend Twenty's capabilities - Coming soon!
|
||||
* **APIs**: Consulta y modifica tus datos de CRM de forma programática usando REST o GraphQL
|
||||
* **Webhooks**: Recibe notificaciones en tiempo real cuando ocurran eventos en Twenty
|
||||
* **Aplicaciones**: Crea aplicaciones personalizadas que amplíen las capacidades de Twenty - Próximamente.
|
||||
|
||||
## Getting Started
|
||||
## Primeros pasos
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="APIs" icon="code" href="/l/es/developers/extend/capabilities/apis">
|
||||
Connect to Twenty programmatically
|
||||
<Card title="APIs" icon="código" href="/l/es/developers/extend/capabilities/apis">
|
||||
Conéctate a Twenty de forma programática
|
||||
</Card>
|
||||
|
||||
<Card title="Webhooks" icon="bell" href="/l/es/developers/extend/capabilities/webhooks">
|
||||
Get notified of events in real-time
|
||||
Recibe notificaciones de eventos en tiempo real
|
||||
</Card>
|
||||
|
||||
<Card title="Apps" icon="puzzle-piece" href="/l/es/developers/extend/capabilities/apps">
|
||||
Build customizations as code (Alpha)
|
||||
<Card title="Aplicaciones" icon="puzzle-piece" href="/l/es/developers/extend/capabilities/apps">
|
||||
Crea personalizaciones como código (Alpha)
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Welcome to Twenty Developer Documentation, your resources for extending, self-hosting, and contributing to Twenty.
|
||||
title: Primeros pasos
|
||||
description: Bienvenido a la documentación para desarrolladores de Twenty, tus recursos para ampliar, autoalojar y contribuir a Twenty.
|
||||
---
|
||||
|
||||
import { CardTitle } from "/snippets/card-title.mdx"
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card href="/l/es/developers/extend/extend" img="/images/user-guide/integrations/plug.png">
|
||||
<CardTitle>Extend</CardTitle>
|
||||
Build integrations with APIs, webhooks, and custom apps.
|
||||
<CardTitle>Ampliar</CardTitle>
|
||||
Crea integraciones con APIs, webhooks y aplicaciones personalizadas.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/es/developers/self-host/self-host" img="/images/user-guide/what-is-twenty/20.png">
|
||||
<CardTitle>Self-Host</CardTitle>
|
||||
Deploy and manage Twenty on your own infrastructure.
|
||||
<CardTitle>Autoalojar</CardTitle>
|
||||
Despliega y administra Twenty en tu propia infraestructura.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/es/developers/contribute/contribute" img="/images/user-guide/github/github-header.png">
|
||||
<CardTitle>Contribute</CardTitle>
|
||||
Join our open-source community and contribute to Twenty.
|
||||
<CardTitle>Contribuir</CardTitle>
|
||||
Únete a nuestra comunidad de código abierto y contribuye a Twenty.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
---
|
||||
title: Other methods
|
||||
title: Otros métodos
|
||||
---
|
||||
|
||||
<Warning>
|
||||
This document is maintained by the community. It might contain issues.
|
||||
Este documento es mantenido por la comunidad. Podría contener problemas.
|
||||
</Warning>
|
||||
|
||||
## Kubernetes via Terraform and Manifests
|
||||
## Kubernetes vía Terraform y Manifests
|
||||
|
||||
Community-led documentation for Kubernetes deployment is available [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-docker/k8s)
|
||||
Documentación comunitaria para la implementación de Kubernetes está disponible [aquí](https://github.com/twentyhq/twenty/tree/main/packages/twenty-docker/k8s)
|
||||
|
||||
### Coolify
|
||||
|
||||
Deploy Twenty on servers using Coolify. (official image on Coolify will be available soon)
|
||||
Despliega Twenty en servidores usando Coolify. (la imagen oficial en Coolify estará disponible pronto)
|
||||
|
||||
[Coolify documentation](https://coolify.io/docs/get-started/introduction)
|
||||
[Documentación de Coolify](https://coolify.io/docs/get-started/introduction)
|
||||
|
||||
### EasyPanel
|
||||
|
||||
Deploy Twenty on EasyPanel with the community maintained template below.
|
||||
Despliega Twenty en EasyPanel con la plantilla mantenida por la comunidad a continuación.
|
||||
|
||||
[Deploy on EasyPanel](https://easypanel.io/docs/templates/twenty)
|
||||
[Desplegar en EasyPanel](https://easypanel.io/docs/templates/twenty)
|
||||
|
||||
### Elest.io
|
||||
|
||||
Deploy Twenty on servers with Elest.io using link below.
|
||||
Despliega Twenty en servidores con Elest.io utilizando el enlace a continuación.
|
||||
|
||||
[Deploy on Elest.io](https://elest.io/open-source/twenty)
|
||||
[Desplegar en Elest.io](https://elest.io/open-source/twenty)
|
||||
|
||||
### Twenty on Railway
|
||||
### Twenty en Railway
|
||||
|
||||
Deploy Twenty on Railway with the community maintained template below.
|
||||
Despliega Twenty en Railway con la plantilla mantenida por la comunidad a continuación.
|
||||
|
||||
[](https://railway.com/deploy/nAL3hA)
|
||||
[](https://railway.com/deploy/nAL3hA)
|
||||
|
||||
### Twenty on Sealos
|
||||
### Twenty en Sealos
|
||||
|
||||
Deploy Twenty on Sealos with the community maintained template below.
|
||||
Despliega Twenty en Sealos con la plantilla mantenida por la comunidad a continuación.
|
||||
|
||||
[](https://sealos.io/products/app-store/twenty)
|
||||
[](https://sealos.io/products/app-store/twenty)
|
||||
|
||||
## Others
|
||||
## Otros
|
||||
|
||||
Please feel free to Open a PR to add more Cloud Provider options.
|
||||
No dudes en abrir un PR para añadir más opciones de proveedores de la nube.
|
||||
|
||||
@@ -1,253 +1,253 @@
|
||||
---
|
||||
title: 1-Click w/ Docker Compose
|
||||
title: 1-Clic con Docker Compose
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Docker containers are for production hosting or self-hosting, for the contribution please check the [Local Setup](/l/es/developers/contribute/capabilities/local-setup).
|
||||
Los contenedores de Docker son para alojamiento en producción o autoalojamiento, para la contribución por favor revise la [Configuración Local](/l/es/developers/contribute/capabilities/local-setup).
|
||||
</Warning>
|
||||
|
||||
## Overview
|
||||
## Resumen
|
||||
|
||||
This guide provides step-by-step instructions to install and configure the Twenty application using Docker Compose. The aim is to make the process straightforward and prevent common pitfalls that could break your setup.
|
||||
Esta guía proporciona instrucciones paso a paso para instalar y configurar la aplicación Twenty utilizando Docker Compose. El objetivo es simplificar el proceso y prevenir errores comunes que podrían arruinar tu configuración.
|
||||
|
||||
**Important:** Only modify settings explicitly mentioned in this guide. Altering other configurations may lead to issues.
|
||||
**Importante:** Solo modifica configuraciones explícitamente mencionadas en esta guía. Alterar otras configuraciones puede causar problemas.
|
||||
|
||||
See docs [Setup Environment Variables](/l/es/developers/self-host/capabilities/setup) for advanced configuration. All environment variables must be declared in the docker-compose.yml file at the server and / or worker level depending on the variable.
|
||||
Consulta los documentos [Configurar Variables de Entorno](/l/es/developers/self-host/capabilities/setup) para configuraciones avanzadas. Todas las variables de entorno deben ser declaradas en el archivo docker-compose.yml en el nivel del servidor y/o trabajador dependiendo de la variable.
|
||||
|
||||
## System Requirements
|
||||
## Requisitos del sistema
|
||||
|
||||
* RAM: Ensure your environment has at least 2GB of RAM. Insufficient memory can cause processes to crash.
|
||||
* Docker & Docker Compose: Make sure both are installed and up-to-date.
|
||||
* RAM: Asegúrate de que tu entorno tenga al menos 2GB de RAM. La memoria insuficiente puede causar que los procesos se bloqueen.
|
||||
* Docker & Docker Compose: Asegúrate de que ambos estén instalados y actualizados.
|
||||
|
||||
## Option 1: One-line script
|
||||
## Opción 1: Script de una línea
|
||||
|
||||
Install the latest stable version of Twenty with a single command:
|
||||
Instala la última versión estable de Twenty con un solo comando:
|
||||
|
||||
```bash
|
||||
bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/scripts/install.sh)
|
||||
```
|
||||
|
||||
To install a specific version or branch:
|
||||
Para instalar una versión o rama específica:
|
||||
|
||||
```bash
|
||||
VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/scripts/install.sh)
|
||||
```
|
||||
|
||||
* Replace x.y.z with the desired version number.
|
||||
* Replace branch-name with the name of the branch you want to install.
|
||||
* Reemplace x.y.z con el número de versión deseado.
|
||||
* Reemplace branch-name con el nombre de la rama que desea instalar.
|
||||
|
||||
## Option 2: Manual steps
|
||||
## Opción 2: Pasos manuales
|
||||
|
||||
Follow these steps for a manual setup.
|
||||
Sigue estos pasos para una configuración manual.
|
||||
|
||||
### Step 1: Set Up the Environment File
|
||||
### Paso 1: Configurar el archivo de entorno
|
||||
|
||||
1. **Create the .env File**
|
||||
1. **Crea el archivo .env**
|
||||
|
||||
Copy the example environment file to a new .env file in your working directory:
|
||||
Copia el archivo de entorno de ejemplo a tu directorio de trabajo a un nuevo archivo .env:
|
||||
|
||||
```bash
|
||||
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
|
||||
```
|
||||
|
||||
2. **Generate Secret Tokens**
|
||||
2. **Generar tokens secretos**
|
||||
|
||||
Run the following command to generate a unique random string:
|
||||
Ejecuta el siguiente comando para generar una cadena única aleatoria:
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
**Important:** Keep this value secret / do not share it.
|
||||
**Importante:** Mantén este valor en secreto / no lo compartas.
|
||||
|
||||
3. **Update the `.env`**
|
||||
3. **Actualiza el `.env`**
|
||||
|
||||
Replace the placeholder value in your .env file with the generated token:
|
||||
Reemplaza el valor de marcador de posición en tu archivo .env con el token generado:
|
||||
|
||||
```ini
|
||||
APP_SECRET=first_random_string
|
||||
```
|
||||
|
||||
4. **Set the Postgres Password**
|
||||
4. **Establecer la contraseña de Postgres**
|
||||
|
||||
Update the `PG_DATABASE_PASSWORD` value in the .env file with a strong password without special characters.
|
||||
Actualiza el valor de `PG_DATABASE_PASSWORD` en el archivo .env con una contraseña fuerte sin caracteres especiales.
|
||||
|
||||
```ini
|
||||
PG_DATABASE_PASSWORD=my_strong_password
|
||||
```
|
||||
|
||||
### Step 2: Obtain the Docker Compose File
|
||||
### Paso 2: Obtener el archivo Docker Compose
|
||||
|
||||
Download the `docker-compose.yml` file to your working directory:
|
||||
Descarga el archivo `docker-compose.yml` en tu directorio de trabajo:
|
||||
|
||||
```bash
|
||||
curl -o docker-compose.yml https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/docker-compose.yml
|
||||
```
|
||||
|
||||
### Step 3: Launch the Application
|
||||
### Paso 3: Lanza la aplicación
|
||||
|
||||
Start the Docker containers:
|
||||
Inicia los contenedores Docker:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Step 4: Access the Application
|
||||
### Paso 4: Acceder a la aplicación
|
||||
|
||||
If you host twentyCRM on your own computer, open your browser and navigate to [http://localhost:3000](http://localhost:3000).
|
||||
Si alojas twentyCRM en tu propia computadora, abre tu navegador y navega a [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
If you host it on a server, check that the server is running and that everything is ok with
|
||||
Si lo alojas en un servidor, verifica que el servidor esté en funcionamiento y que todo esté bien con
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000
|
||||
```
|
||||
|
||||
## Configuration
|
||||
## Configuración
|
||||
|
||||
### Expose Twenty to External Access
|
||||
### Exponer Twenty para acceso externo
|
||||
|
||||
By default, Twenty runs on `localhost` at port `3000`. To access it via an external domain or IP address, you need to configure the `SERVER_URL` in your `.env` file.
|
||||
Por defecto, Twenty se ejecuta en `localhost` en el puerto `3000`. Para acceder a él mediante un dominio externo o dirección IP, necesitas configurar `SERVER_URL` en tu archivo `.env`.
|
||||
|
||||
#### Understanding `SERVER_URL`
|
||||
#### Entendiendo `SERVER_URL`
|
||||
|
||||
* **Protocol:** Use `http` or `https` depending on your setup.
|
||||
* Use `http` if you haven't set up SSL.
|
||||
* Use `https` if you have SSL configured.
|
||||
* **Domain/IP:** This is the domain name or IP address where your application is accessible.
|
||||
* **Port:** Include the port number if you're not using the default ports (`80` for `http`, `443` for `https`).
|
||||
* **Protocolo:** Usa `http` o `https` dependiendo de tu configuración.
|
||||
* Usa `http` si no has configurado SSL.
|
||||
* Usa `https` si tienes SSL configurado.
|
||||
* **Dominio/IP:** Este es el nombre de dominio o dirección IP donde tu aplicación es accesible.
|
||||
* **Puerto:** Incluye el número de puerto si no estás usando los puertos predeterminados (`80` para `http`, `443` para `https`).
|
||||
|
||||
### SSL Requirements
|
||||
### Requisitos de SSL
|
||||
|
||||
SSL (HTTPS) is required for certain browser features to work properly. While these features might work during local development (as browsers treat localhost differently), a proper SSL setup is needed when hosting Twenty on a regular domain.
|
||||
SSL (HTTPS) es requerido para que ciertas características del navegador funcionen correctamente. Aunque estas características podrían funcionar durante el desarrollo local (ya que los navegadores tratan localhost de manera diferente), se requiere una configuración SSL adecuada al alojar Twenty en un dominio regular.
|
||||
|
||||
For example, the clipboard API might require a secure context - some features like copy buttons throughout the application might not work without HTTPS enabled.
|
||||
Por ejemplo, es posible que la API del portapapeles requiera un contexto seguro: algunas características como los botones de copia en toda la aplicación pueden no funcionar sin HTTPS habilitado.
|
||||
|
||||
We strongly recommend setting up Twenty behind a reverse proxy with SSL termination for optimal security and functionality.
|
||||
Recomendamos encarecidamente configurar Twenty detrás de un proxy inverso con terminación SSL para una seguridad y funcionalidad óptimas.
|
||||
|
||||
#### Configuring `SERVER_URL`
|
||||
#### Configurando `SERVER_URL`
|
||||
|
||||
1. **Determine Your Access URL**
|
||||
* **Without Reverse Proxy (Direct Access):**
|
||||
1. **Determine su URL de acceso**
|
||||
* **Sin proxy inverso (Acceso directo):**
|
||||
|
||||
If you're accessing the application directly without a reverse proxy:
|
||||
Si estás accediendo a la aplicación directamente sin un proxy inverso:
|
||||
|
||||
```ini
|
||||
SERVER_URL=http://your-domain-or-ip:3000
|
||||
```
|
||||
|
||||
* **With Reverse Proxy (Standard Ports):**
|
||||
* **Con proxy inverso (Puertos estándar):**
|
||||
|
||||
If you're using a reverse proxy like Nginx or Traefik and have SSL configured:
|
||||
Si estás usando un proxy inverso como Nginx o Traefik y tienes SSL configurado:
|
||||
|
||||
```ini
|
||||
SERVER_URL=https://your-domain-or-ip
|
||||
```
|
||||
|
||||
* **With Reverse Proxy (Custom Ports):**
|
||||
* **Con proxy inverso (Puertos personalizados):**
|
||||
|
||||
If you're using non-standard ports:
|
||||
Si estás usando puertos no estándar:
|
||||
|
||||
```ini
|
||||
SERVER_URL=https://your-domain-or-ip:custom-port
|
||||
```
|
||||
|
||||
2. **Update the `.env` File**
|
||||
2. **Actualiza el archivo `.env`**
|
||||
|
||||
Open your `.env` file and update the `SERVER_URL`:
|
||||
Abre tu archivo `.env` y actualiza el `SERVER_URL`:
|
||||
|
||||
```ini
|
||||
SERVER_URL=http(s)://your-domain-or-ip:your-port
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
**Ejemplos:**
|
||||
|
||||
* Direct access without SSL:
|
||||
* Acceso directo sin SSL:
|
||||
```ini
|
||||
SERVER_URL=http://123.45.67.89:3000
|
||||
```
|
||||
* Access via domain with SSL:
|
||||
* Acceso vía dominio con SSL:
|
||||
```ini
|
||||
SERVER_URL=https://mytwentyapp.com
|
||||
```
|
||||
|
||||
3. **Restart the Application**
|
||||
3. **Reiniciar la aplicación**
|
||||
|
||||
For changes to take effect, restart the Docker containers:
|
||||
Para que los cambios surtan efecto, reinicia los contenedores Docker:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
#### Considerations
|
||||
#### Consideraciones
|
||||
|
||||
* **Reverse Proxy Configuration:**
|
||||
* **Configuración del Proxy Inverso:**
|
||||
|
||||
Ensure your reverse proxy forwards requests to the correct internal port (`3000` by default). Configure SSL termination and any necessary headers.
|
||||
Asegúrese de que su proxy inverso envíe las solicitudes al puerto interno correcto (`3000` por defecto). Configure la terminación SSL y cualquier cabecera necesaria.
|
||||
|
||||
* **Firewall Settings:**
|
||||
* **Configuración del Cortafuegos:**
|
||||
|
||||
Open necessary ports in your firewall to allow external access.
|
||||
Abra los puertos necesarios en su cortafuegos para permitir el acceso externo.
|
||||
|
||||
* **Consistency:**
|
||||
* **Consistencia:**
|
||||
|
||||
The `SERVER_URL` must match how users access your application in their browsers.
|
||||
La `SERVER_URL` debe coincidir con cómo los usuarios acceden a su aplicación en sus navegadores.
|
||||
|
||||
#### Persistence
|
||||
#### Persistencia
|
||||
|
||||
* **Data Volumes:**
|
||||
* **Volúmenes de Datos:**
|
||||
|
||||
The Docker Compose configuration uses volumes to persist data for the database and server storage.
|
||||
La configuración de Docker Compose utiliza volúmenes para persistir datos para la base de datos y el almacenamiento del servidor.
|
||||
|
||||
* **Stateless Environments:**
|
||||
* **Entornos Sin Estado:**
|
||||
|
||||
If deploying to a stateless environment (e.g., certain cloud services), configure external storage to persist data.
|
||||
Si se despliega en un entorno sin estado (por ejemplo, ciertos servicios en la nube), configure un almacenamiento externo para persistir los datos.
|
||||
|
||||
## Backup and Restore
|
||||
## Copia de seguridad y restauración
|
||||
|
||||
Regular backups protect your CRM data from loss.
|
||||
Las copias de seguridad periódicas protegen los datos de tu CRM contra la pérdida.
|
||||
|
||||
### Create a Database Backup
|
||||
### Crea una copia de seguridad de la base de datos
|
||||
|
||||
```bash
|
||||
docker exec twenty-postgres pg_dump -U postgres twenty > backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
### Automate Daily Backups
|
||||
### Automatiza las copias de seguridad diarias
|
||||
|
||||
Add to your crontab (`crontab -e`):
|
||||
Añade a tu crontab (`crontab -e`):
|
||||
|
||||
```bash
|
||||
0 2 * * * docker exec twenty-postgres pg_dump -U postgres twenty > /backups/twenty_$(date +\%Y\%m\%d).sql
|
||||
```
|
||||
|
||||
### Restore from Backup
|
||||
### Restaura desde una copia de seguridad
|
||||
|
||||
1. Stop the application:
|
||||
1. Detén la aplicación:
|
||||
|
||||
```bash
|
||||
docker compose stop twenty-server twenty-front
|
||||
```
|
||||
|
||||
2. Restore the database:
|
||||
2. Restaura la base de datos:
|
||||
|
||||
```bash
|
||||
docker exec -i twenty-postgres psql -U postgres twenty < backup_20240115.sql
|
||||
```
|
||||
|
||||
3. Restart services:
|
||||
3. Reinicia los servicios:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Backup Best Practices
|
||||
### Mejores prácticas de copias de seguridad
|
||||
|
||||
* **Test restores regularly** — verify backups actually work
|
||||
* **Store backups off-site** — use cloud storage (S3, GCS, etc.)
|
||||
* **Encrypt sensitive data** — protect backups with encryption
|
||||
* **Retain multiple copies** — keep daily, weekly, and monthly backups
|
||||
* **Prueba las restauraciones con regularidad** — verifica que las copias de seguridad realmente funcionen
|
||||
* **Almacena las copias de seguridad fuera del sitio** — usa almacenamiento en la nube (S3, GCS, etc.)
|
||||
* **Cifra los datos sensibles** — protege las copias de seguridad con cifrado
|
||||
* **Conserva múltiples copias** — mantén copias de seguridad diarias, semanales y mensuales
|
||||
|
||||
## Troubleshooting
|
||||
## Solución de Problemas
|
||||
|
||||
If you encounter any problem, check [Troubleshooting](/l/es/developers/self-host/capabilities/troubleshooting) for solutions.
|
||||
Si encuentras algún problema, consulta [Solución de Problemas](/l/es/developers/self-host/capabilities/troubleshooting) para ver soluciones.
|
||||
|
||||
@@ -1,146 +1,146 @@
|
||||
---
|
||||
title: Setup
|
||||
title: Configuración
|
||||
---
|
||||
|
||||
# Configuration Management
|
||||
# Gestión de Configuración
|
||||
|
||||
<Warning>
|
||||
**First time installing?** Follow the [Docker Compose installation guide](/l/es/developers/self-host/capabilities/docker-compose) to get Twenty running, then return here for configuration.
|
||||
**¿Instalando por primera vez?** Siga la [guía de instalación de Docker Compose](/l/es/developers/self-host/capabilities/docker-compose) para ejecutar Twenty, luego regrese aquí para la configuración.
|
||||
</Warning>
|
||||
|
||||
Twenty offers **two configuration modes** to suit different deployment needs:
|
||||
Twenty ofrece **dos modos de configuración** para adaptarse a diferentes necesidades de implementación:
|
||||
|
||||
**Admin panel access:** Only users with admin privileges (`canAccessFullAdminPanel: true`) can access the configuration interface.
|
||||
**Acceso al panel de administración:** Solo los usuarios con privilegios de administrador (`canAccessFullAdminPanel: true`) pueden acceder a la interfaz de configuración.
|
||||
|
||||
## 1. Admin Panel Configuration (Default)
|
||||
## 1. Configuración del Panel de Administración (Predeterminado)
|
||||
|
||||
```bash
|
||||
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
|
||||
```
|
||||
|
||||
**Most configuration happens through the UI** after installation:
|
||||
**La mayoría de las configuraciones se realizan a través de la interfaz** después de la instalación:
|
||||
|
||||
1. Access your Twenty instance (usually `http://localhost:3000`)
|
||||
2. Go to **Settings / Admin Panel / Configuration Variables**
|
||||
3. Configure integrations, email, storage, and more
|
||||
4. Changes take effect immediately (within 15 seconds for multi-container deployments)
|
||||
1. Acceda a su instancia de Twenty (normalmente `http://localhost:3000`)
|
||||
2. Vaya a **Configuración / Panel de Administración / Variables de Configuración**
|
||||
3. Configure integraciones, correo electrónico, almacenamiento y más
|
||||
4. Los cambios se aplican inmediatamente (dentro de 15 segundos para implementaciones multicontenedor)
|
||||
|
||||
<Warning>
|
||||
**Multi-Container Deployments:** When using database configuration (`IS_CONFIG_VARIABLES_IN_DB_ENABLED=true`), both server and worker containers read from the same database. Admin panel changes affect both automatically, eliminating the need to duplicate environment variables between containers (except for infrastructure variables).
|
||||
**Implementaciones Multicontenedor:** Al usar la configuración de base de datos (`IS_CONFIG_VARIABLES_IN_DB_ENABLED=true`), tanto los contenedores del servidor como los de trabajo leen de la misma base de datos. Los cambios en el panel de administración afectan a ambos automáticamente, eliminando la necesidad de duplicar las variables de entorno entre contenedores (excepto para las variables de infraestructura).
|
||||
</Warning>
|
||||
|
||||
**What you can configure through the admin panel:**
|
||||
**Qué se puede configurar a través del panel de administración:**
|
||||
|
||||
* **Authentication** - Google/Microsoft OAuth, password settings
|
||||
* **Email** - SMTP settings, templates, verification
|
||||
* **Storage** - S3 configuration, local storage paths
|
||||
* **Integrations** - Gmail, Google Calendar, Microsoft services
|
||||
* **Workflow & Rate Limiting** - Execution limits, API throttling
|
||||
* **And much more...**
|
||||
* **Autenticación** - OAuth de Google/Microsoft, configuración de contraseñas
|
||||
* **Correo Electrónico** - Configuración de SMTP, plantillas, verificación
|
||||
* **Almacenamiento** - Configuración S3, rutas de almacenamiento local
|
||||
* **Integraciones** - Gmail, Google Calendar, servicios de Microsoft
|
||||
* **Flujo de Trabajo y Limitación de Tasas** - Límites de ejecución, restricción de API
|
||||
* **Y mucho más...**
|
||||
|
||||

|
||||

|
||||
|
||||
<Warning>
|
||||
Each variable is documented with descriptions in your admin panel at **Settings → Admin Panel → Configuration Variables**.
|
||||
Some infrastructure settings like database connections (`PG_DATABASE_URL`), server URLs (`SERVER_URL`), and app secrets (`APP_SECRET`) can only be configured via `.env` file.
|
||||
Cada variable está documentada con descripciones en su panel de administración en **Configuración → Panel de Administración → Variables de Configuración**.
|
||||
Algunas configuraciones de infraestructura como las conexiones de base de datos (`PG_DATABASE_URL`), URLs del servidor (`SERVER_URL`), y secretos de la aplicación (`APP_SECRET`) solo se pueden configurar a través del archivo `.env`.
|
||||
|
||||
[Complete technical reference →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
|
||||
[Referencia técnica completa →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
|
||||
</Warning>
|
||||
|
||||
## 2. Environment-Only Configuration
|
||||
## 2. Configuración Solo de Entorno
|
||||
|
||||
```bash
|
||||
IS_CONFIG_VARIABLES_IN_DB_ENABLED=false
|
||||
```
|
||||
|
||||
**All configuration managed through `.env` files:**
|
||||
**Toda la configuración se gestiona a través de archivos `.env`:**
|
||||
|
||||
1. Set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` in your `.env` file
|
||||
2. Add all configuration variables to your `.env` file
|
||||
3. Restart containers for changes to take effect
|
||||
4. Admin panel will show current values but cannot modify them
|
||||
1. Establezca `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` en su archivo `.env`
|
||||
2. Agregue todas las variables de configuración a su archivo `.env`
|
||||
3. Reinicie los contenedores para que los cambios tengan efecto
|
||||
4. El panel de administración mostrará los valores actuales pero no podrá modificarlos
|
||||
|
||||
## Multi-Workspace Mode
|
||||
## Modo de múltiples espacios de trabajo
|
||||
|
||||
By default, Twenty runs in **single-workspace mode** — ideal for most self-hosted deployments where you need one CRM instance for your organization.
|
||||
De forma predeterminada, Twenty se ejecuta en **modo de un solo espacio de trabajo** — ideal para la mayoría de las implementaciones autoalojadas en las que necesitas una instancia de CRM para tu organización.
|
||||
|
||||
### Single-Workspace Mode (Default)
|
||||
### Modo de un solo espacio de trabajo (predeterminado)
|
||||
|
||||
```bash
|
||||
IS_MULTIWORKSPACE_ENABLED=false # default
|
||||
```
|
||||
|
||||
* One workspace per Twenty instance
|
||||
* First user automatically becomes admin with full privileges (`canImpersonate` and `canAccessFullAdminPanel`)
|
||||
* New signups are disabled after the first workspace is created
|
||||
* Simple URL structure: `https://your-domain.com`
|
||||
* Un espacio de trabajo por instancia de Twenty
|
||||
* El primer usuario se convierte automáticamente en administrador con privilegios completos (`canImpersonate` y `canAccessFullAdminPanel`)
|
||||
* Los nuevos registros se deshabilitan después de crear el primer espacio de trabajo
|
||||
* Estructura de URL simple: `https://your-domain.com`
|
||||
|
||||
### Enabling Multi-Workspace Mode
|
||||
### Habilitar el modo de múltiples espacios de trabajo
|
||||
|
||||
```bash
|
||||
IS_MULTIWORKSPACE_ENABLED=true
|
||||
DEFAULT_SUBDOMAIN=app # default value
|
||||
```
|
||||
|
||||
Enable multi-workspace mode for SaaS-like deployments where multiple independent teams need their own workspaces on the same Twenty instance.
|
||||
Habilita el modo de múltiples espacios de trabajo para implementaciones tipo SaaS en las que varios equipos independientes necesitan sus propios espacios de trabajo en la misma instancia de Twenty.
|
||||
|
||||
**Key differences from single-workspace mode:**
|
||||
**Diferencias clave respecto al modo de un solo espacio de trabajo:**
|
||||
|
||||
* Multiple workspaces can be created on the same instance
|
||||
* Each workspace gets its own subdomain (e.g., `sales.your-domain.com`, `marketing.your-domain.com`)
|
||||
* Users sign up and log in at `{DEFAULT_SUBDOMAIN}.your-domain.com` (e.g., `app.your-domain.com`)
|
||||
* No automatic admin privileges — first user in each workspace is a regular user
|
||||
* Workspace-specific settings like subdomain and custom domain become available in workspace settings
|
||||
* Se pueden crear varios espacios de trabajo en la misma instancia
|
||||
* Cada espacio de trabajo obtiene su propio subdominio (p. ej., `sales.your-domain.com`, `marketing.your-domain.com`)
|
||||
* Los usuarios se registran e inician sesión en `{DEFAULT_SUBDOMAIN}.your-domain.com` (p. ej., `app.your-domain.com`)
|
||||
* Sin privilegios de administrador automáticos — el primer usuario de cada espacio de trabajo es un usuario normal
|
||||
* Configuraciones específicas del espacio de trabajo, como subdominio y dominio personalizado, están disponibles en la configuración del espacio de trabajo
|
||||
|
||||
<Warning>
|
||||
**Environment-only setting:** `IS_MULTIWORKSPACE_ENABLED` can only be configured via `.env` file and requires a restart. It cannot be changed through the admin panel.
|
||||
**Configuración solo por entorno:** `IS_MULTIWORKSPACE_ENABLED` solo se puede configurar mediante el archivo `.env` y requiere un reinicio. No se puede cambiar a través del panel de administración.
|
||||
</Warning>
|
||||
|
||||
### DNS Configuration for Multi-Workspace
|
||||
### Configuración de DNS para múltiples espacios de trabajo
|
||||
|
||||
When using multi-workspace mode, configure your DNS with a wildcard record to allow dynamic subdomain creation:
|
||||
Al usar el modo de múltiples espacios de trabajo, configura tu DNS con un registro comodín para permitir la creación dinámica de subdominios:
|
||||
|
||||
```
|
||||
*.your-domain.com -> your-server-ip
|
||||
```
|
||||
|
||||
This enables automatic subdomain routing for new workspaces without manual DNS configuration.
|
||||
Esto habilita el enrutamiento automático de subdominios para nuevos espacios de trabajo sin configuración manual de DNS.
|
||||
|
||||
### Restricting Workspace Creation
|
||||
### Restricción de la creación de espacios de trabajo
|
||||
|
||||
In multi-workspace mode, you may want to limit who can create new workspaces:
|
||||
En el modo de múltiples espacios de trabajo, es posible que quieras limitar quién puede crear nuevos espacios de trabajo:
|
||||
|
||||
```bash
|
||||
IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS=true
|
||||
```
|
||||
|
||||
When enabled, only users with `canAccessFullAdminPanel` can create additional workspaces. Users can still create their first workspace during initial signup.
|
||||
Cuando está habilitado, solo los usuarios con `canAccessFullAdminPanel` pueden crear espacios de trabajo adicionales. Los usuarios aún pueden crear su primer espacio de trabajo durante el registro inicial.
|
||||
|
||||
## Gmail & Google Calendar Integration
|
||||
## Integración con Gmail y Google Calendar
|
||||
|
||||
### Create Google Cloud Project
|
||||
### Crear Proyecto en Google Cloud
|
||||
|
||||
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
||||
2. Create a new project or select existing one
|
||||
3. Enable these APIs:
|
||||
1. Vaya a [Google Cloud Console](https://console.cloud.google.com/)
|
||||
2. Cree un nuevo proyecto o seleccione uno existente
|
||||
3. Habilite estas APIs:
|
||||
|
||||
* [Gmail API](https://console.cloud.google.com/apis/library/gmail.googleapis.com)
|
||||
* [Google Calendar API](https://console.cloud.google.com/apis/library/calendar-json.googleapis.com)
|
||||
* [People API](https://console.cloud.google.com/apis/library/people.googleapis.com)
|
||||
|
||||
### Configure OAuth
|
||||
### Configurar OAuth
|
||||
|
||||
1. Go to [Credentials](https://console.cloud.google.com/apis/credentials)
|
||||
2. Create OAuth 2.0 Client ID
|
||||
3. Add these redirect URIs:
|
||||
* `https://{your-domain}/auth/google/redirect` (for SSO)
|
||||
* `https://{your-domain}/auth/google-apis/get-access-token` (for integrations)
|
||||
1. Vaya a [Credenciales](https://console.cloud.google.com/apis/credentials)
|
||||
2. Cree un ID de Cliente OAuth 2.0
|
||||
3. Agregue estas URIs de redirección:
|
||||
* `https://{your-domain}/auth/google/redirect` (para SSO)
|
||||
* `https://{your-domain}/auth/google-apis/get-access-token` (para integraciones)
|
||||
|
||||
### Configure in Twenty
|
||||
### Configurar en Twenty
|
||||
|
||||
1. Go to **Settings → Admin Panel → Configuration Variables**
|
||||
2. Find the **Google Auth** section
|
||||
3. Set these variables:
|
||||
1. Vaya a **Configuración → Panel de Administración → Variables de Configuración**
|
||||
2. Encuentre la sección **Google Auth**
|
||||
3. Establezca estas variables:
|
||||
* `MESSAGING_PROVIDER_GMAIL_ENABLED=true`
|
||||
* `CALENDAR_PROVIDER_GOOGLE_ENABLED=true`
|
||||
* `AUTH_GOOGLE_CLIENT_ID={client-id}`
|
||||
@@ -149,35 +149,35 @@ When enabled, only users with `canAccessFullAdminPanel` can create additional wo
|
||||
* `AUTH_GOOGLE_APIS_CALLBACK_URL=https://{your-domain}/auth/google-apis/get-access-token`
|
||||
|
||||
<Warning>
|
||||
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
|
||||
**Modo solo de entorno:** Si establece `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, agregue estas variables a su archivo `.env` en su lugar.
|
||||
</Warning>
|
||||
|
||||
**Required scopes** (automatically configured):
|
||||
[See relevant source code](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes.ts#L4-L10)
|
||||
**Ámbitos requeridos** (configurados automáticamente):
|
||||
[Ver código fuente relevante](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes.ts#L4-L10)
|
||||
|
||||
* `https://www.googleapis.com/auth/calendar.events`
|
||||
* `https://www.googleapis.com/auth/gmail.readonly`
|
||||
* `https://www.googleapis.com/auth/profile.emails.read`
|
||||
|
||||
### If your app is in test mode
|
||||
### Si su aplicación está en modo de prueba
|
||||
|
||||
If your app is in test mode, you will need to add test users to your project.
|
||||
Si su aplicación está en modo de prueba, deberá agregar usuarios de prueba a su proyecto.
|
||||
|
||||
Under [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent), add your test users to the "Test users" section.
|
||||
En [Pantalla de consentimiento OAuth](https://console.cloud.google.com/apis/credentials/consent), agregue sus usuarios de prueba en la sección "Usuarios de prueba".
|
||||
|
||||
## Microsoft 365 Integration
|
||||
## Integración con Microsoft 365
|
||||
|
||||
<Warning>
|
||||
Users must have a [Microsoft 365 Licence](https://admin.microsoft.com/Adminportal/Home) to be able to use the Calendar and Messaging API. They will not be able to sync their account on Twenty without one.
|
||||
Los usuarios deben tener una [licencia de Microsoft 365](https://admin.microsoft.com/Adminportal/Home) para poder usar la API de Calendar y Messaging. No podrán sincronizar su cuenta en Twenty sin una.
|
||||
</Warning>
|
||||
|
||||
### Create a project in Microsoft Azure
|
||||
### Cree un proyecto en Microsoft Azure
|
||||
|
||||
You will need to create a project in [Microsoft Azure](https://portal.azure.com/#view/Microsoft_AAD_IAM/AppGalleryBladeV2) and get the credentials.
|
||||
Necesitará crear un proyecto en [Microsoft Azure](https://portal.azure.com/#view/Microsoft_AAD_IAM/AppGalleryBladeV2) y obtener las credenciales.
|
||||
|
||||
### Enable APIs
|
||||
### Habilitar APIs
|
||||
|
||||
On Microsoft Azure Console enable the following APIs in "Permissions":
|
||||
En la Consola de Microsoft Azure habilite las siguientes APIs en "Permisos":
|
||||
|
||||
* Microsoft Graph: Mail.ReadWrite
|
||||
* Microsoft Graph: Mail.Send
|
||||
@@ -188,20 +188,20 @@ On Microsoft Azure Console enable the following APIs in "Permissions":
|
||||
* Microsoft Graph: profile
|
||||
* Microsoft Graph: offline_access
|
||||
|
||||
Note: "Mail.ReadWrite" and "Mail.Send" are only mandatory if you want to send emails using our workflow actions. You can use "Mail.Read" instead if you only want to receive emails.
|
||||
Nota: "Mail.ReadWrite" y "Mail.Send" solo son obligatorios si desea enviar correos electrónicos usando nuestras acciones de flujo de trabajo. Puede usar "Mail.Read" en su lugar si solo desea recibir correos electrónicos.
|
||||
|
||||
### Authorized redirect URIs
|
||||
### URIs de redirección autorizadas
|
||||
|
||||
You need to add the following redirect URIs to your project:
|
||||
Necesita agregar las siguientes URIs de redirección a su proyecto:
|
||||
|
||||
* `https://{your-domain}/auth/microsoft/redirect` if you want to use Microsoft SSO
|
||||
* `https://{your-domain}/auth/microsoft/redirect` si quieres usar el inicio de sesión único de Microsoft
|
||||
* `https://{your-domain}/auth/microsoft-apis/get-access-token`
|
||||
|
||||
### Configure in Twenty
|
||||
### Configurar en Twenty
|
||||
|
||||
1. Go to **Settings → Admin Panel → Configuration Variables**
|
||||
2. Find the **Microsoft Auth** section
|
||||
3. Set these variables:
|
||||
1. Vaya a **Configuración → Panel de Administración → Variables de Configuración**
|
||||
2. Encuentre la sección **Microsoft Auth**
|
||||
3. Establezca estas variables:
|
||||
* `MESSAGING_PROVIDER_MICROSOFT_ENABLED=true`
|
||||
* `CALENDAR_PROVIDER_MICROSOFT_ENABLED=true`
|
||||
* `AUTH_MICROSOFT_ENABLED=true`
|
||||
@@ -211,32 +211,32 @@ You need to add the following redirect URIs to your project:
|
||||
* `AUTH_MICROSOFT_APIS_CALLBACK_URL=https://{your-domain}/auth/microsoft-apis/get-access-token`
|
||||
|
||||
<Warning>
|
||||
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
|
||||
**Modo solo de entorno:** Si establece `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, agregue estas variables a su archivo `.env` en su lugar.
|
||||
</Warning>
|
||||
|
||||
### Configure scopes
|
||||
### Configurar ámbitos
|
||||
|
||||
[See relevant source code](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts#L2-L9)
|
||||
[Ver código fuente relevante](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts#L2-L9)
|
||||
|
||||
* 'openid'
|
||||
* 'email'
|
||||
* 'profile'
|
||||
* 'correo Electrónico'
|
||||
* 'perfil'
|
||||
* 'offline_access'
|
||||
* 'Mail.ReadWrite'
|
||||
* 'Mail.Send'
|
||||
* 'Calendars.Read'
|
||||
|
||||
### If your app is in test mode
|
||||
### Si su aplicación está en modo de prueba
|
||||
|
||||
If your app is in test mode, you will need to add test users to your project.
|
||||
Si su aplicación está en modo de prueba, deberá agregar usuarios de prueba a su proyecto.
|
||||
|
||||
Add your test users to the "Users and groups" section.
|
||||
Agregue sus usuarios de prueba a la sección "Usuarios y grupos".
|
||||
|
||||
## Background Jobs for Calendar & Messaging
|
||||
## Trabajos en segundo plano para Calendarios y Mensajes
|
||||
|
||||
After configuring Gmail, Google Calendar, or Microsoft 365 integrations, you need to start the background jobs that sync data.
|
||||
Después de configurar las integraciones de Gmail, Google Calendar, o Microsoft 365, necesita iniciar los trabajos en segundo plano que sincronizan los datos.
|
||||
|
||||
Register the following recurring jobs in your worker container:
|
||||
Registre los siguientes trabajos recurrentes en su contenedor de trabajo:
|
||||
|
||||
```bash
|
||||
# from your worker container
|
||||
@@ -249,15 +249,15 @@ yarn command:prod cron:calendar:ongoing-stale
|
||||
yarn command:prod cron:workflow:automated-cron-trigger
|
||||
```
|
||||
|
||||
## Email Configuration
|
||||
## Configuración de Correo Electrónico
|
||||
|
||||
1. Go to **Settings → Admin Panel → Configuration Variables**
|
||||
2. Find the **Email** section
|
||||
3. Configure your SMTP settings:
|
||||
1. Vaya a **Configuración → Panel de Administración → Variables de Configuración**
|
||||
2. Encuentre la sección **Correo Electrónico**
|
||||
3. Configure su configuración SMTP:
|
||||
|
||||
<ArticleTabs label1="Gmail" label2="Office365" label3="Smtp4dev">
|
||||
<ArticleTab>
|
||||
You will need to provision an [App Password](https://support.google.com/accounts/answer/185833).
|
||||
Necesitará proporcionar una [Contraseña de Aplicación](https://support.google.com/accounts/answer/185833).
|
||||
|
||||
* EMAIL_DRIVER=smtp
|
||||
* EMAIL_SMTP_HOST=smtp.gmail.com
|
||||
@@ -267,7 +267,7 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
</ArticleTab>
|
||||
|
||||
<ArticleTab>
|
||||
Keep in mind that if you have 2FA enabled, you will need to provision an [App Password](https://support.microsoft.com/en-us/account-billing/manage-app-passwords-for-two-step-verification-d6dc8c6d-4bf7-4851-ad95-6d07799387e9).
|
||||
Tenga en cuenta que si tiene la autenticación de dos factores habilitada, necesitará proporcionar una [Contraseña de Aplicación](https://support.microsoft.com/en-us/account-billing/manage-app-passwords-for-two-step-verification-d6dc8c6d-4bf7-4851-ad95-6d07799387e9).
|
||||
|
||||
* EMAIL_DRIVER=smtp
|
||||
* EMAIL_SMTP_HOST=smtp.office365.com
|
||||
@@ -277,11 +277,11 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
</ArticleTab>
|
||||
|
||||
<ArticleTab>
|
||||
**smtp4dev** is a fake SMTP email server for development and testing.
|
||||
**smtp4dev** es un servidor de correo SMTP falso para desarrollo y pruebas.
|
||||
|
||||
* Run the smtp4dev image: `docker run --rm -it -p 8090:80 -p 2525:25 rnwood/smtp4dev`
|
||||
* Access the smtp4dev ui here: [http://localhost:8090](http://localhost:8090)
|
||||
* Set the following variables:
|
||||
* Ejecute la imagen de smtp4dev: `docker run --rm -it -p 8090:80 -p 2525:25 rnwood/smtp4dev`
|
||||
* Acceda a la interfaz de smtp4dev aquí: [http://localhost:8090](http://localhost:8090)
|
||||
* Establezca las siguientes variables:
|
||||
* EMAIL_DRIVER=smtp
|
||||
* EMAIL_SMTP_HOST=localhost
|
||||
* EMAIL_SMTP_PORT=2525
|
||||
@@ -289,5 +289,49 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
</ArticleTabs>
|
||||
|
||||
<Warning>
|
||||
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
|
||||
**Modo solo de entorno:** Si establece `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, agregue estas variables a su archivo `.env` en su lugar.
|
||||
</Warning>
|
||||
|
||||
## Funciones de lógica
|
||||
|
||||
Twenty admite funciones de lógica para flujos de trabajo y lógica personalizada. El entorno de ejecución se configura mediante la variable de entorno `SERVERLESS_TYPE`.
|
||||
|
||||
<Warning>
|
||||
**Aviso de seguridad:** El controlador local (`SERVERLESS_TYPE=LOCAL`) ejecuta código directamente en el host en un proceso de Node.js sin aislamiento. Solo debe utilizarse para código de confianza en desarrollo. Para implementaciones de producción que manejen código no confiable, recomendamos encarecidamente usar `SERVERLESS_TYPE=LAMBDA` o `SERVERLESS_TYPE=DISABLED`.
|
||||
</Warning>
|
||||
|
||||
### Controladores disponibles
|
||||
|
||||
| Controlador | Variable de entorno | Caso de uso | Nivel de seguridad |
|
||||
| ----------- | -------------------------- | ------------------------------------------------ | -------------------------------------- |
|
||||
| Desactivado | `SERVERLESS_TYPE=DISABLED` | Desactivar completamente las funciones de lógica | N/A |
|
||||
| Local | `SERVERLESS_TYPE=LOCAL` | Entornos de desarrollo y de confianza | Bajo (sin aislamiento) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Producción con código no confiable | Alto (aislamiento a nivel de hardware) |
|
||||
|
||||
### Configuración recomendada
|
||||
|
||||
**Para desarrollo:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LOCAL # default
|
||||
```
|
||||
|
||||
**Para producción (AWS):**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LAMBDA
|
||||
SERVERLESS_LAMBDA_REGION=us-east-1
|
||||
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**Para desactivar las funciones de lógica:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
```
|
||||
|
||||
<Note>
|
||||
Al usar `SERVERLESS_TYPE=DISABLED`, cualquier intento de ejecutar una función de lógica devolverá un error. Esto es útil si desea ejecutar Twenty sin capacidades de funciones de lógica.
|
||||
</Note>
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
title: Solución de problemas
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
## Solución de Problemas
|
||||
|
||||
If you encounter any problem while setting up environment for development, upgrading your instance or self-hosting,
|
||||
here are some solutions for common problems.
|
||||
Si encuentra algún problema al configurar el entorno para el desarrollo, al actualizar su instancia o al autoalojar, aquí hay algunas soluciones para problemas comunes.
|
||||
|
||||
### Self-hosting
|
||||
### Autoalojamiento
|
||||
|
||||
#### First install results in `password authentication failed for user "postgres"`
|
||||
#### La primera instalación resulta en `fallo de autenticación de contraseña para el usuario "postgres"`
|
||||
|
||||
🚨 **IMPORTANT: This solution is ONLY for fresh installations** 🚨
|
||||
If you have an existing Twenty instance with production data, **DO NOT** follow these steps as they will permanently delete your database!
|
||||
🚨 **IMPORTANTE: Esta solución es SOLO para instalaciones nuevas** 🚨
|
||||
Si tiene una instancia de Twenty existente con datos de producción, **NO** siga estos pasos ya que borrarán permanentemente su base de datos.
|
||||
|
||||
While installing Twenty for the first time, you might want to change the default database password.
|
||||
The password you set during the first installation becomes permanently stored in the database volume. If you later try to change this password in your configuration without removing the old volume, you'll get authentication errors because the database is still using the original password.
|
||||
Al instalar Twenty por primera vez, es posible que desee cambiar la contraseña predeterminada de la base de datos.
|
||||
La contraseña que establezca durante la primera instalación se almacena permanentemente en el volumen de base de datos. Si más tarde intenta cambiar esta contraseña en su configuración sin eliminar el volumen anterior, obtendrá errores de autenticación porque la base de datos todavía está usando la contraseña original.
|
||||
|
||||
⚠️ WARNING: Following steps will PERMANENTLY DELETE all database data! ⚠️
|
||||
Only proceed if this is a fresh installation with no important data.
|
||||
⚠️ ADVERTENCIA: ¡Seguir los pasos borrará PERMANENTEMENTE todos los datos de la base de datos! ⚠️
|
||||
Prosiga solo si se trata de una instalación nueva sin datos importantes.
|
||||
|
||||
In order to update the `PG_DATABASE_PASSWORD` you need to:
|
||||
Para actualizar el `PG_DATABASE_PASSWORD` necesita:
|
||||
|
||||
```sh
|
||||
# Update the PG_DATABASE_PASSWORD in .env
|
||||
@@ -28,33 +27,33 @@ docker compose down --volumes
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
#### CR line breaks found [Windows]
|
||||
#### Rupturas de línea de CR encontradas [Windows]
|
||||
|
||||
This is due to the line break characters of Windows and the git configuration. Try running:
|
||||
Esto se debe a los caracteres de salto de línea de Windows y a la configuración de Git. Intente ejecutar:
|
||||
|
||||
```
|
||||
git config --global core.autocrlf false
|
||||
```
|
||||
|
||||
Then delete the repository and clone it again.
|
||||
Luego elimine el repositorio y clónelo de nuevo.
|
||||
|
||||
#### Missing metadata schema
|
||||
#### Esquema de metadatos faltante
|
||||
|
||||
During Twenty installation, you need to provision your postgres database with the right schemas, extensions, and users.
|
||||
If you're successful in running this provisioning, you should have `default` and `metadata` schemas in your database.
|
||||
If you don't, make sure you don't have more than one postgres instance running on your computer.
|
||||
Durante la instalación de Twenty, debe aprovisionar su base de datos postgres con los esquemas, extensiones y usuarios correctos.
|
||||
Si ha ejecutado con éxito este aprovisionamiento, debería tener esquemas `default` y `metadata` en su base de datos.
|
||||
Si no los tiene, asegúrese de no tener más de una instancia de postgres ejecutándose en su computadora.
|
||||
|
||||
#### Cannot find module 'twenty-emails' or its corresponding type declarations.
|
||||
#### No se puede encontrar el módulo 'twenty-emails' o sus declaraciones de tipo correspondientes.
|
||||
|
||||
You have to build the package `twenty-emails` before running the initialization of the database with `npx nx run twenty-emails:build`
|
||||
Tienes que construir el paquete `twenty-emails` antes de ejecutar la inicialización de la base de datos con `npx nx run twenty-emails:build`
|
||||
|
||||
#### Missing twenty-x package
|
||||
#### Falta el paquete twenty-x
|
||||
|
||||
Make sure to run yarn in the root directory and then run `npx nx server:dev twenty-server`. If this still doesn't work try building the missing package manually.
|
||||
Asegúrese de ejecutar yarn en el directorio raíz y luego ejecute `npx nx server:dev twenty-server`. Si esto aún no funciona, intente construir el paquete faltante manualmente.
|
||||
|
||||
#### Lint on Save not working
|
||||
#### Lint on Save no funciona
|
||||
|
||||
This should work out of the box with the eslint extension installed. If this doesn't work try adding this to your vscode setting (on the dev container scope):
|
||||
Esto debería funcionar sin configuración adicional con la extensión de ESLint instalada. Si esto no funciona, intente agregar esto a su configuración de vscode (en el ámbito del contenedor de desarrollo):
|
||||
|
||||
```
|
||||
"editor.codeActionsOnSave": {
|
||||
@@ -64,85 +63,85 @@ This should work out of the box with the eslint extension installed. If this doe
|
||||
}
|
||||
```
|
||||
|
||||
#### While running `npx nx start` or `npx nx start twenty-front`, Out of memory error is thrown
|
||||
#### Al ejecutar `npx nx start` o `npx nx start twenty-front`, se produce un error de falta de memoria
|
||||
|
||||
In `packages/twenty-front/.env` uncomment `VITE_DISABLE_TYPESCRIPT_CHECKER=true` to disable background checks thus reducing amount of needed RAM.
|
||||
En `packages/twenty-front/.env` descomente `VITE_DISABLE_TYPESCRIPT_CHECKER=true` y `VITE_DISABLE_ESLINT_CHECKER=true` para deshabilitar las comprobaciones en segundo plano y así reducir la cantidad de RAM necesaria.
|
||||
|
||||
**If it does not work:**
|
||||
Run only the services you need, instead of `npx nx start`. For instance, if you work on the server, run only `npx nx worker twenty-server`
|
||||
**Si no funciona:**
|
||||
Ejecute solo los servicios que necesite, en lugar de `npx nx start`. Por ejemplo, si trabaja en el servidor, ejecute solo `npx nx worker twenty-server`
|
||||
|
||||
**If it does not work:**
|
||||
If you tried to run only `npx nx run twenty-server:start` on WSL and it's failing with the below memory error:
|
||||
**Si no funciona:**
|
||||
Si intentó ejecutar solo `npx nx run twenty-server:start` en WSL y falla con el siguiente error de memoria:
|
||||
|
||||
`FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory`
|
||||
`ERROR FATAL: Las marcas compactas ineficaces cercanas al límite del montón Falló la asignación - JavaScript heap out of memory`
|
||||
|
||||
Workaround is to execute below command in terminal or add it in .bashrc profile to get setup automatically:
|
||||
La solución es ejecutar el siguiente comando en el terminal o agregarlo en el perfil .bashrc para configurarlo automáticamente:
|
||||
|
||||
`export NODE_OPTIONS="--max-old-space-size=8192"`
|
||||
|
||||
The --max-old-space-size=8192 flag sets an upper limit of 8GB for the Node.js heap; usage scales with application demand.
|
||||
Reference: https://stackoverflow.com/questions/56982005/where-do-i-set-node-options-max-old-space-size-2048
|
||||
La bandera --max-old-space-size=8192 establece un límite superior de 8GB para el montón de Node.js; su uso escala con la demanda de la aplicación.
|
||||
Referencia: https://stackoverflow.com/questions/56982005/where-do-i-set-node-options-max-old-space-size-2048
|
||||
|
||||
**If it does not work:**
|
||||
Investigate which processes are taking you most of your machine RAM. At Twenty, we noticed that some VScode extensions were taking a lot of RAM so we temporarily disable them.
|
||||
**Si no funciona:**
|
||||
Investigue qué procesos están ocupando la mayor parte de la RAM de su máquina. En Twenty, notamos que algunas extensiones de VScode estaban ocupando mucha RAM, por lo que las desactivamos temporalmente.
|
||||
|
||||
**If it does not work:**
|
||||
Restart your machine helps to clean up ghost processes.
|
||||
**Si no funciona:**
|
||||
Reiniciar su máquina ayuda a limpiar procesos fantasma.
|
||||
|
||||
#### While running `npx nx start` there are weird [0] and [1] in logs
|
||||
#### Mientras ejecuta `npx nx start` hay [0] y [1] extraños en los registros
|
||||
|
||||
That's expected as command `npx nx start` is running more commands under the hood
|
||||
Es esperado, ya que el comando `npx nx start` está ejecutando más comandos detrás de escena.
|
||||
|
||||
#### No emails are sent
|
||||
#### No se envían correos electrónicos
|
||||
|
||||
Most of the time, it's because the `worker` is not running in the background. Try to run
|
||||
La mayoría de las veces, se debe a que el `worker` no se está ejecutando en segundo plano. Intente ejecutar
|
||||
|
||||
```
|
||||
npx nx worker twenty-server
|
||||
```
|
||||
|
||||
#### Cannot connect my Microsoft 365 account
|
||||
#### No se puede conectar mi cuenta de Microsoft 365
|
||||
|
||||
Most of the time, it's because your admin has not enabled the Microsoft 365 Licence for your account. Check [https://admin.microsoft.com/](https://admin.microsoft.com/Adminportal/Home).
|
||||
La mayoría de las veces, se debe a que su administrador no ha habilitado la licencia de Microsoft 365 para su cuenta. Verifique [https://admin.microsoft.com/](https://admin.microsoft.com/Adminportal/Home).
|
||||
|
||||
If you have an error code `AADSTS50020`, it probably means that you are using a personal Microsoft account. This is not supported yet. More info [here](https://learn.microsoft.com/fr-fr/troubleshoot/entra/entra-id/app-integration/error-code-aadsts50020-user-account-identity-provider-does-not-exist)
|
||||
Si tiene un código de error `AADSTS50020`, probablemente significa que está usando una cuenta de Microsoft personal. Esto aún no es compatible. Más información [aquí](https://learn.microsoft.com/fr-fr/troubleshoot/entra/entra-id/app-integration/error-code-aadsts50020-user-account-identity-provider-does-not-exist)
|
||||
|
||||
#### While running `yarn` warnings appear in console
|
||||
#### Mientras ejecuta `yarn` aparecen advertencias en la consola
|
||||
|
||||
Warnings are informing about pulling additional dependencies which aren't explicitly stated in `package.json`, so as long as no breaking error appears, everything should work as expected.
|
||||
Las advertencias informan sobre la carga de dependencias adicionales que no están explicitadas en `package.json`, así que mientras no aparezca un error crítico, todo debería funcionar como se espera.
|
||||
|
||||
#### When user accesses login page, error about unauthorized user trying to access workspace appears in logs
|
||||
#### Cuando el usuario accede a la página de inicio de sesión, aparece un error sobre un usuario no autorizado que intenta acceder al espacio de trabajo en los registros
|
||||
|
||||
That's expected as user is unauthorized when logged out since its identity is not verified.
|
||||
Es esperado ya que el usuario no está autorizado cuando cierra sesión porque su identidad no está verificada.
|
||||
|
||||
#### How to check if your worker is running?
|
||||
#### ¿Cómo comprobar si su worker está funcionando?
|
||||
|
||||
* Go to [webhook-test.com](https://webhook-test.com/) and copy **Your Unique Webhook URL**.
|
||||
* Vaya a [webhook-test.com](https://webhook-test.com/) y copie **Su URL de Webhook Única**.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/self-hosting/webhook-test.jpg" alt="Webhook test" />
|
||||
<img src="/images/docs/developers/self-hosting/webhook-test.jpg" alt="Prueba de webhook" />
|
||||
</div>
|
||||
|
||||
* Open your Twenty app, navigate to `/settings`, and enable the **Advanced** toggle at the bottom left of the screen.
|
||||
* Create a new webhook.
|
||||
* Paste **Your Unique Webhook URL** in the **Endpoint Url** field in Twenty. Set the **Filters** to `Companies` and `Created`.
|
||||
* Abra la aplicación Twenty, navegue a `/settings` y active el interruptor **Avanzado** en la parte inferior izquierda de la pantalla.
|
||||
* Cree un nuevo webhook.
|
||||
* Pegue **Su URL de Webhook Única** en el campo **Url de EndPoint** en Twenty. Establezca los **Filtros** en `Companies` y `Created`.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/self-hosting/webhook-settings.jpg" alt="Webhook settings" />
|
||||
<img src="/images/docs/developers/self-hosting/webhook-settings.jpg" alt="Configuraciones del webhook" />
|
||||
</div>
|
||||
|
||||
* Go to `/objects/companies` and create a new company record.
|
||||
* Return to [webhook-test.com](https://webhook-test.com/) and check if a new **POST request** has been received.
|
||||
* Vaya a `/objects/companies` y cree un nuevo registro de empresa.
|
||||
* Regrese a [webhook-test.com](https://webhook-test.com/) y verifique si se ha recibido una nueva **solicitud POST**.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/self-hosting/webhook-test-result.jpg" alt="Webhook test result" />
|
||||
<img src="/images/docs/developers/self-hosting/webhook-test-result.jpg" alt="Resultado de la prueba del webhook" />
|
||||
</div>
|
||||
|
||||
* If a **POST request** is received, your worker is running successfully. Otherwise, you need to troubleshoot your worker.
|
||||
* Si se recibe una **solicitud POST**, su worker está funcionando con éxito. De lo contrario, debe solucionar problemas de su worker.
|
||||
|
||||
#### Front-end fails to start and returns error TS5042: Option 'project' cannot be mixed with source files on a command line
|
||||
#### El front-end no comienza y devuelve el error TS5042: La opción 'project' no se puede mezclar con archivos fuente en una línea de comando
|
||||
|
||||
Comment out checker plugin in `packages/twenty-ui/vite-config.ts` like in example below
|
||||
Comente el plugin checker en `packages/twenty-ui/vite-config.ts` como en el ejemplo a continuación
|
||||
|
||||
```
|
||||
plugins: [
|
||||
@@ -166,62 +165,62 @@ plugins: [
|
||||
],
|
||||
```
|
||||
|
||||
#### Admin panel not accessible
|
||||
#### Panel de administración no accesible
|
||||
|
||||
Run `UPDATE core."user" SET "canAccessFullAdminPanel" = TRUE WHERE email = '[email protected]';` in database container to get access to admin panel.
|
||||
Ejecute `UPDATE core."user" SET "canAccessFullAdminPanel" = TRUE WHERE email = '[email protected]';` en el contenedor de la base de datos para obtener acceso al panel de administración.
|
||||
|
||||
### 1-click Docker compose
|
||||
### 1-clic con Docker Compose
|
||||
|
||||
#### Unable to Log In
|
||||
#### No se puede iniciar sesión
|
||||
|
||||
If you can't log in after setup:
|
||||
Si no puedes iniciar sesión después de la configuración:
|
||||
|
||||
1. Run the following commands:
|
||||
1. Ejecución de los siguientes comandos:
|
||||
```bash
|
||||
docker exec -it twenty-server-1 yarn
|
||||
docker exec -it twenty-server-1 npx nx database:reset --configuration=no-seed
|
||||
```
|
||||
2. Restart the Docker containers:
|
||||
2. Reinicie los contenedores de Docker:
|
||||
```bash
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Note the database:reset command will completely erase your database and recreate it from scratch.
|
||||
Tenga en cuenta que el comando database:reset borrará toda su base de datos y la recreará desde cero.
|
||||
|
||||
#### Connection Issues Behind a Reverse Proxy
|
||||
#### Problemas de conexión detrás de un Proxy Reverso
|
||||
|
||||
If you're running Twenty behind a reverse proxy and experiencing connection issues:
|
||||
Si está ejecutando Twenty detrás de un proxy inverso y experimenta problemas de conexión:
|
||||
|
||||
1. **Verify SERVER_URL:**
|
||||
1. **Verifique SERVER_URL:**
|
||||
|
||||
Ensure `SERVER_URL` in your `.env` file matches your external access URL, including `https` if SSL is enabled.
|
||||
Asegúrese de que `SERVER_URL` en su archivo `.env` coincida con su URL de acceso externa, incluyendo `https` si SSL está habilitado.
|
||||
|
||||
2. **Check Reverse Proxy Settings:**
|
||||
2. **Verifique la configuración del Proxy Reverso:**
|
||||
|
||||
* Confirm that your reverse proxy is correctly forwarding requests to the Twenty server.
|
||||
* Ensure headers like `X-Forwarded-For` and `X-Forwarded-Proto` are properly set.
|
||||
* Confirme que su proxy reverso está reenviando correctamente las solicitudes al servidor de Twenty.
|
||||
* Asegúrese de que los encabezados como `X-Forwarded-For` y `X-Forwarded-Proto` estén configurados correctamente.
|
||||
|
||||
3. **Restart Services:**
|
||||
3. **Reinicie los Servicios:**
|
||||
|
||||
After making changes, restart both the reverse proxy and Twenty containers.
|
||||
Después de hacer cambios, reinicie tanto el proxy inverso como los contenedores de Twenty.
|
||||
|
||||
#### Error when uploading an image - permission denied
|
||||
#### Error al cargar una imagen - permiso denegado
|
||||
|
||||
Switching the data folder ownership on the host from root to another user and group resolves this problem.
|
||||
Cambiar la propiedad de la carpeta de datos en el host de raíz a otro usuario y grupo resuelve este problema.
|
||||
|
||||
## Getting Help
|
||||
## Obtención de Ayuda
|
||||
|
||||
If you encounter issues not covered in this guide:
|
||||
Si enfrenta problemas no cubiertos en esta guía:
|
||||
|
||||
* Check Logs:
|
||||
* Verifique los Registros:
|
||||
|
||||
View container logs for error messages:
|
||||
Vea los registros del contenedor por mensajes de error:
|
||||
|
||||
```bash
|
||||
docker compose logs
|
||||
```
|
||||
|
||||
* Community Support:
|
||||
* Soporte Comunitario:
|
||||
|
||||
Reach out to the [Twenty community](https://github.com/twentyhq/twenty/issues) or [support channels](https://discord.gg/cx5n4Jzs57) for assistance.
|
||||
Póngase en contacto con la [comunidad de Twenty](https://github.com/twentyhq/twenty/issues) o [los canales de soporte](https://discord.gg/cx5n4Jzs57) para obtener asistencia.
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
---
|
||||
title: Upgrade guide
|
||||
title: Guía de actualización
|
||||
---
|
||||
|
||||
## General guidelines
|
||||
## Guías generales
|
||||
|
||||
**Always make sure to back up your database before starting the upgrade process** by running `docker exec -it {db_container_name_or_id} pg_dumpall -U {postgres_user} > databases_backup.sql`.
|
||||
**Asegúrese siempre de respaldar su base de datos antes de iniciar el proceso de actualización** ejecutando `docker exec -it {db_container_name_or_id} pg_dumpall -U {postgres_user} > databases_backup.sql`.
|
||||
|
||||
To restore backup, run `cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {postgres_user}`.
|
||||
Para restaurar el respaldo, ejecute `cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {postgres_user}`.
|
||||
|
||||
If you used Docker Compose, follow these steps:
|
||||
Si usó Docker Compose, siga estos pasos:
|
||||
|
||||
1. In a terminal, on the host where Twenty is running, turn off Twenty: `docker compose down`
|
||||
1. En una terminal, en el host donde Twenty está funcionando, apague Twenty: `docker compose down`
|
||||
|
||||
2. Upgrade the version by changing the `TAG` value in the .env file near your docker-compose. ( We recommend consuming `major.minor` version such as `v0.53` )
|
||||
2. Actualice la versión cambiando el valor de `TAG` en el archivo .env cerca de su docker-compose. ( Recomendamos consumir la versión `major.minor` como `v0.53` )
|
||||
|
||||
3. Bring Twenty back online with `docker compose up -d`
|
||||
3. Vuelva a conectar Twenty con `docker compose up -d`
|
||||
|
||||
If you want to upgrade your instance by few versions, e.g. from v0.33.0 to v0.35.0, you have to upgrade your instance sequentially, in this example from v0.33.0 to v0.34.0, then from v0.34.0 to v0.35.0.
|
||||
Si desea actualizar su instancia por algunas versiones, por ejemplo de v0.33.0 a v0.35.0, debe actualizar su instancia secuencialmente, en este ejemplo de v0.33.0 a v0.34.0, luego de v0.34.0 a v0.35.0.
|
||||
|
||||
**Make sure that after each upgraded version you have non-corrupted backup.**
|
||||
**Asegúrese de que después de cada versión actualizada tenga una copia de respaldo no corrupta.**
|
||||
|
||||
## Version-specific upgrade steps
|
||||
## Pasos de actualización específicos por versión
|
||||
|
||||
## v1.0
|
||||
|
||||
Hello Twenty v1.0! 🎉
|
||||
¡Hola Twenty v1.0! 🎉
|
||||
|
||||
## v0.60
|
||||
|
||||
### Performance Enhancements
|
||||
### Mejoras de rendimiento
|
||||
|
||||
All interactions with the metadata API have been optimized for better performance, particularly for object metadata manipulation and workspace creation operations.
|
||||
Todas las interacciones con la API de metadatos han sido optimizadas para un mejor rendimiento, particularmente para la manipulación de metadatos de objetos y operaciones de creación de espacios de trabajo.
|
||||
|
||||
We've refactored our caching strategy to prioritize cache hits over database queries when possible, significantly improving the performance of metadata API operations.
|
||||
Hemos reestructurado nuestra estrategia de almacenamiento en caché para priorizar los aciertos de caché sobre las consultas de base de datos cuando sea posible, mejorando significativamente el rendimiento de las operaciones de la API de metadatos.
|
||||
|
||||
If you encounter any runtime issues after upgrading, you may need to flush your cache to ensure it's synchronized with the latest changes. Run this command in your twenty-server container:
|
||||
Si encuentra problemas de ejecución después de actualizar, es posible que deba vaciar su caché para asegurar que esté sincronizado con los cambios más recientes. Ejecute este comando en su contenedor del servidor de twenty:
|
||||
|
||||
```bash
|
||||
yarn command:prod cache:flush
|
||||
@@ -42,113 +42,113 @@ yarn command:prod cache:flush
|
||||
|
||||
### v0.55
|
||||
|
||||
Upgrade your Twenty instance to use v0.55 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.55
|
||||
|
||||
You don't need to run any command anymore, the new image will automatically care about running all required migrations.
|
||||
Ya no necesita ejecutar ningún comando, la nueva imagen se encargará automáticamente de ejecutar todas las migraciones necesarias.
|
||||
|
||||
### `User does not have permission` error
|
||||
### Error: `El usuario no tiene permiso`
|
||||
|
||||
If you encounter authorization errors on most requests after upgrading, you may need to flush your cache to recompute the latest permissions.
|
||||
Si encuentra errores de autorización en la mayoría de solicitudes después de actualizar, es posible que deba vaciar su caché para recalcular los permisos más recientes.
|
||||
|
||||
In your `twenty-server` container, run:
|
||||
En su contenedor `twenty-server`, ejecute:
|
||||
|
||||
```bash
|
||||
yarn command:prod cache:flush
|
||||
```
|
||||
|
||||
This issue is specific to this Twenty version and should not be required for future upgrades.
|
||||
Este problema es específico de esta versión de Twenty y no debería ser necesario para futuras actualizaciones.
|
||||
|
||||
### v0.54
|
||||
|
||||
Since version `0.53`, no manual actions needed.
|
||||
Desde la versión `0.53`, no se necesitan acciones manuales.
|
||||
|
||||
#### Metadata schema deprecation
|
||||
#### Desaparición del esquema de metadatos
|
||||
|
||||
We've merged the `metadata` schema into the `core` one to simplify data retrieval from `TypeORM`.
|
||||
We have merged the `migrate` command step within the `upgrade` command. We do not recommend running `migrate` manually within any of your server/worker containers.
|
||||
Hemos fusionado el esquema `metadata` en el esquema `core` para simplificar la recuperación de datos desde `TypeORM`.
|
||||
Hemos fusionado el paso del comando `migrate` dentro del comando `upgrade`. No recomendamos ejecutar `migrate` manualmente dentro de ninguno de sus contenedores de servidor/trabajador.
|
||||
|
||||
### Since v0.53
|
||||
### Desde v0.53
|
||||
|
||||
Starting from `0.53`, upgrade is programmatically done within the `DockerFile`, this means from now on, you shouldn't have to run any command manually anymore.
|
||||
A partir de `0.53`, la actualización se realiza de forma programática dentro del `DockerFile`, esto significa que de ahora en adelante, no debería necesitar ejecutar ningún comando manualmente.
|
||||
|
||||
Make sure to keep upgrading your instance sequentially, without skipping any major version (e.g. `0.43.3` to `0.44.0` is allowed, but `0.43.1` to `0.45.0` isn't), else could lead to workspace version desynchronization that could result in runtime error and missing functionality.
|
||||
Asegúrese de seguir actualizando su instancia secuencialmente, sin omitir ninguna versión principal (por ejemplo, de `0.43.3` a `0.44.0` está permitido, pero de `0.43.1` a `0.45.0` no lo está), de lo contrario, podría provocar un desincronización de la versión del espacio de trabajo que podría resultar en errores de ejecución y funciones faltantes.
|
||||
|
||||
To check if a workspace has been correctly migrated you can review its version in database in `core.workspace` table.
|
||||
Para verificar si un espacio de trabajo se ha migrado correctamente, puede revisar su versión en la base de datos en la tabla `core.workspace`.
|
||||
|
||||
It should always be in the range of your current Twenty's instance `major.minor` version, you can view your instance version in the admin panel (at `/settings/admin-panel`, accessible if your user has `canAccessFullAdminPanel` property set to true in the database) or by running `echo $APP_VERSION` in your `twenty-server` container.
|
||||
Siempre debería estar dentro del rango de la versión `major.minor` actual de su instancia de Twenty; puede ver la versión de su instancia en el panel de administración (en `/settings/admin-panel`, accesible si su usuario tiene la propiedad `canAccessFullAdminPanel` establecida en verdadero en la base de datos) o ejecutando `echo $APP_VERSION` en su contenedor `twenty-server`.
|
||||
|
||||
To fix a desynchronized workspace version, you will have to upgrade from the corresponding twenty's version following related upgrade guide sequentially and so on until it reaches desired version.
|
||||
Para corregir una versión de espacio de trabajo desincronizada, tendrá que actualizar desde la correspondiente versión de twenty siguiendo la guía de actualización relacionada secuencialmente, y así sucesivamente hasta alcanzar la versión deseada.
|
||||
|
||||
#### `auditLog` removal
|
||||
#### Eliminación de `auditLog`
|
||||
|
||||
We've removed the auditLog standard object, which means your backup size might be significantly reduced after this migration.
|
||||
Hemos eliminado el objeto estándar auditLog, lo que significa que el tamaño de su copia de seguridad podría reducirse significativamente después de esta migración.
|
||||
|
||||
### v0.51 to v0.52
|
||||
### v0.51 a v0.52
|
||||
|
||||
Upgrade your Twenty instance to use v0.52 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.52
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
#### I have a workspace blocked in version between `0.52.0` and `0.52.6`
|
||||
#### Tengo un espacio de trabajo bloqueado en la versión entre `0.52.0` y `0.52.6`
|
||||
|
||||
Unfortunately `0.52.0` and `0.52.6` have been completely removed from dockerHub.
|
||||
You will have to manually update your workspace version to `0.51.0` in database and upgrade using twenty version `0.52.11` following its just above upgrade guide.
|
||||
Desafortunadamente, `0.52.0` y `0.52.6` se han eliminado completamente de dockerHub.
|
||||
Tendrá que actualizar manualmente la versión de su espacio de trabajo a `0.51.0` en la base de datos y actualizar usando la versión twenty `0.52.11` siguiendo su guía de actualización justo arriba.
|
||||
|
||||
### v0.50 to v0.51
|
||||
### v0.50 a v0.51
|
||||
|
||||
Upgrade your Twenty instance to use v0.51 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.51
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
### v0.44.0 to v0.50.0
|
||||
### v0.44.0 a v0.50.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.50.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.50.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
#### Docker-compose.yml mutation
|
||||
#### Mutación del docker-compose.yml
|
||||
|
||||
This version includes a `docker-compose.yml` mutation to give `worker` service access to the `server-local-data` volume.
|
||||
Please update your local `docker-compose.yml` with [v0.50.0 docker-compose.yml](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml)
|
||||
Esta versión incluye una mutación del `docker-compose.yml` para dar acceso al servicio `worker` al volumen `server-local-data`.
|
||||
Actualice su `docker-compose.yml` local con el [docker-compose.yml de v0.50.0](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml)
|
||||
|
||||
### v0.43.0 to v0.44.0
|
||||
### v0.43.0 a v0.44.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.44.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.44.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
### v0.42.0 to v0.43.0
|
||||
### v0.42.0 a v0.43.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.43.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.43.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
In this version, we have also switched to postgres:16 image in docker-compose.yml.
|
||||
En esta versión, también hemos cambiado a la imagen de postgres:16 en docker-compose.yml.
|
||||
|
||||
#### (Option 1) Database migration
|
||||
#### (Opción 1) Migración de base de datos
|
||||
|
||||
Keeping the existing postgres-spilo image is fine, but you will have to freeze the version in your docker-compose.yml to be 0.43.0.
|
||||
Mantener la imagen postgres-spilo existente está bien, pero tendrá que congelar la versión en su docker-compose.yml a 0.43.0.
|
||||
|
||||
#### (Option 2) Database migration
|
||||
#### (Opción 2) Migración de base de datos
|
||||
|
||||
If you want to migrate your database to the new postgres:16 image, please follow these steps:
|
||||
Si desea migrar su base de datos a la nueva imagen de postgres:16, siga estos pasos:
|
||||
|
||||
1. Dump your database from the old postgres-spilo container
|
||||
1. Descargue su base de datos del contenedor antiguo de postgres-spilo
|
||||
|
||||
```
|
||||
docker exec -it twenty-db-1 sh
|
||||
@@ -157,11 +157,11 @@ exit
|
||||
docker cp twenty-db-1:/home/postgres/databases_backup.sql .
|
||||
```
|
||||
|
||||
Make sure your dump file is not empty.
|
||||
Asegúrese de que su archivo de respaldo no esté vacío.
|
||||
|
||||
2. Upgrade your docker-compose.yml to use postgres:16 image as in the [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) file.
|
||||
2. Actualice su docker-compose.yml para usar la imagen de postgres:16 como en el archivo [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml).
|
||||
|
||||
3. Restore the database to the new postgres:16 container
|
||||
3. Restaure la base de datos al nuevo contenedor postgres:16
|
||||
|
||||
```
|
||||
docker cp databases_backup.sql twenty-db-1:/databases_backup.sql
|
||||
@@ -170,86 +170,86 @@ psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql
|
||||
exit
|
||||
```
|
||||
|
||||
### v0.41.0 to v0.42.0
|
||||
### v0.41.0 a v0.42.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.42.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.42.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.42
|
||||
```
|
||||
|
||||
**Environment Variables**
|
||||
**Variables del entorno**
|
||||
|
||||
* Removed: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT`
|
||||
* Added: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED`
|
||||
* Removidos: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT`
|
||||
* Agregados: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED`
|
||||
|
||||
### v0.40.0 to v0.41.0
|
||||
### v0.40.0 a v0.41.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.41.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.41.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.41
|
||||
```
|
||||
|
||||
**Environment Variables**
|
||||
**Variables del entorno**
|
||||
|
||||
* Removed: `AUTH_MICROSOFT_TENANT_ID`
|
||||
* Removido: `AUTH_MICROSOFT_TENANT_ID`
|
||||
|
||||
### v0.35.0 to v0.40.0
|
||||
### v0.35.0 a v0.40.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.40.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.40.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.40
|
||||
```
|
||||
|
||||
**Environment Variables**
|
||||
**Variables del entorno**
|
||||
|
||||
* Added: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL`
|
||||
* Agregados: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL`
|
||||
|
||||
### v0.34.0 to v0.35.0
|
||||
### v0.34.0 a v0.35.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.35.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.35.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.35
|
||||
```
|
||||
|
||||
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
|
||||
The `yarn command:prod upgrade-0.35` takes care of the data migration of all workspaces.
|
||||
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
|
||||
El `yarn command:prod upgrade-0.35` se encarga de la migración de datos de todos los espacios de trabajo.
|
||||
|
||||
**Environment Variables**
|
||||
**Variables del entorno**
|
||||
|
||||
* We replaced `ENABLE_DB_MIGRATIONS` with `DISABLE_DB_MIGRATIONS` (default value is now `false`, you probably don't have to set anything)
|
||||
* Reemplazamos `ENABLE_DB_MIGRATIONS` por `DISABLE_DB_MIGRATIONS` (valor predeterminado ahora es `false`, probablemente no tenga que establecer nada)
|
||||
|
||||
### v0.33.0 to v0.34.0
|
||||
### v0.33.0 a v0.34.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.34.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.34.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.34
|
||||
```
|
||||
|
||||
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
|
||||
The `yarn command:prod upgrade-0.34` takes care of the data migration of all workspaces.
|
||||
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
|
||||
El `yarn command:prod upgrade-0.34` se encarga de la migración de datos de todos los espacios de trabajo.
|
||||
|
||||
**Environment Variables**
|
||||
**Variables del entorno**
|
||||
|
||||
* Removed: `FRONT_BASE_URL`
|
||||
* Added: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT`
|
||||
* Removido: `FRONT_BASE_URL`
|
||||
* Agregados: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT`
|
||||
|
||||
We have updated the way we handle the frontend URL.
|
||||
You can now set the frontend URL using the `FRONT_DOMAIN`, `FRONT_PROTOCOL` and `FRONT_PORT` variables.
|
||||
If FRONT_DOMAIN is not set, the frontend URL will fall back to `SERVER_URL`.
|
||||
Hemos actualizado la forma en que manejamos la URL del frontend.
|
||||
Ahora puede configurar la URL del frontend usando las variables `FRONT_DOMAIN`, `FRONT_PROTOCOL` y `FRONT_PORT`.
|
||||
Si FRONT_DOMAIN no está configurado, la URL del frontend volverá a `SERVER_URL`.
|
||||
|
||||
### v0.32.0 to v0.33.0
|
||||
### v0.32.0 a v0.33.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.33.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.33.0
|
||||
|
||||
```
|
||||
yarn command:prod cache:flush
|
||||
@@ -257,68 +257,68 @@ yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.33
|
||||
```
|
||||
|
||||
The `yarn command:prod cache:flush` command will flush the Redis cache.
|
||||
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
|
||||
The `yarn command:prod upgrade-0.33` takes care of the data migration of all workspaces.
|
||||
El comando `yarn command:prod cache:flush` eliminará la caché de Redis.
|
||||
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
|
||||
El `yarn command:prod upgrade-0.33` se encarga de la migración de datos de todos los espacios de trabajo.
|
||||
|
||||
Starting from this version, twenty-postgres image for DB became deprecated and twenty-postgres-spilo is used instead.
|
||||
If you want to keep using twenty-postgres image, simply replace `twentycrm/twenty-postgres:${TAG}` with `twentycrm/twenty-postgres` in docker-compose.yml.
|
||||
A partir de esta versión, la imagen twenty-postgres para DB quedó obsoleta y ahora se usa twenty-postgres-spilo.
|
||||
Si desea seguir usando la imagen twenty-postgres, simplemente reemplace `twentycrm/twenty-postgres:${TAG}` con `twentycrm/twenty-postgres` en docker-compose.yml.
|
||||
|
||||
### v0.31.0 to v0.32.0
|
||||
### v0.31.0 a v0.32.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.32.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.32.0
|
||||
|
||||
**Schema and data migration**
|
||||
**Migración de esquemas y datos**
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.32
|
||||
```
|
||||
|
||||
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
|
||||
The `yarn command:prod upgrade-0.32` takes care of the data migration of all workspaces.
|
||||
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
|
||||
El `yarn command:prod upgrade-0.32` se encarga de la migración de datos de todos los espacios de trabajo.
|
||||
|
||||
**Environment Variables**
|
||||
**Variables del entorno**
|
||||
|
||||
We have updated the way we handle the Redis connection.
|
||||
Hemos actualizado la forma en que manejamos la conexión Redis.
|
||||
|
||||
* Removed: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`
|
||||
* Added: `REDIS_URL`
|
||||
* Removidos: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`
|
||||
* Agregado: `REDIS_URL`
|
||||
|
||||
Update your `.env` file to use the new `REDIS_URL` variable instead of the individual Redis connection parameters.
|
||||
Actualice su archivo `.env` para usar la nueva variable `REDIS_URL` en lugar de los parámetros de conexión individuales de Redis.
|
||||
|
||||
We have also simplified the way we handle the JWT tokens.
|
||||
También hemos simplificado la forma en que manejamos los tokens JWT.
|
||||
|
||||
* Removed: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET`
|
||||
* Added: `APP_SECRET`
|
||||
* Removidos: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET`
|
||||
* Agregado: `APP_SECRET`
|
||||
|
||||
Update your `.env` file to use the new `APP_SECRET` variable instead of the individual tokens secrets (you can use the same secret as before or generate a new random string)
|
||||
Actualice su archivo `.env` para usar la nueva variable `APP_SECRET` en lugar de los secretos de tokens individuales (puede usar el mismo secreto que antes o generar una nueva cadena aleatoria)
|
||||
|
||||
**Connected Account**
|
||||
**Cuenta conectada**
|
||||
|
||||
If you are using connected account to synchronize your Google emails and calendars, you will need to activate the [People API](https://developers.google.com/people) on your Google Admin console.
|
||||
Si está utilizando cuentas conectadas para sincronizar sus correos electrónicos y calendarios de Google, deberá activar la [API de People](https://developers.google.com/people) en su consola de administración de Google.
|
||||
|
||||
### v0.30.0 to v0.31.0
|
||||
### v0.30.0 a v0.31.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.31.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.31.0
|
||||
|
||||
**Schema and data migration**:
|
||||
**Migración de esquemas y datos:**
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.31
|
||||
```
|
||||
|
||||
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
|
||||
The `yarn command:prod upgrade-0.31` takes care of the data migration of all workspaces.
|
||||
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
|
||||
El `yarn command:prod upgrade-0.31` se encarga de la migración de datos de todos los espacios de trabajo.
|
||||
|
||||
### v0.24.0 to v0.30.0
|
||||
### v0.24.0 a v0.30.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.30.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.30.0
|
||||
|
||||
**Breaking change**:
|
||||
To enhance performances, Twenty now requires redis cache to be configured. We have updated our [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) to reflect this.
|
||||
Make sure to update your configuration and to update your environment variables accordingly:
|
||||
**Cambio importante**:
|
||||
Para mejorar el rendimiento, Twenty ahora requiere que la caché redis esté configurada. Hemos actualizado nuestro [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) para reflejar esto.
|
||||
Asegúrese de actualizar su configuración y sus variables de entorno en consecuencia:
|
||||
|
||||
```
|
||||
REDIS_HOST={your-redis-host}
|
||||
@@ -326,49 +326,49 @@ REDIS_PORT={your-redis-port}
|
||||
CACHE_STORAGE_TYPE=redis
|
||||
```
|
||||
|
||||
**Schema and data migration**:
|
||||
**Migración de esquemas y datos:**
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.30
|
||||
```
|
||||
|
||||
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
|
||||
The `yarn command:prod upgrade-0.30` takes care of the data migration of all workspaces.
|
||||
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
|
||||
El `yarn command:prod upgrade-0.30` se encarga de la migración de datos de todos los espacios de trabajo.
|
||||
|
||||
### v0.23.0 to v0.24.0
|
||||
### v0.23.0 a v0.24.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.24.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.24.0
|
||||
|
||||
Run the following commands:
|
||||
Ejecución de los siguientes comandos:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.24
|
||||
```
|
||||
|
||||
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
|
||||
The `yarn command:prod upgrade-0.24` takes care of the data migration of all workspaces.
|
||||
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
|
||||
El `yarn command:prod upgrade-0.24` se encarga de la migración de datos de todos los espacios de trabajo.
|
||||
|
||||
### v0.22.0 to v0.23.0
|
||||
### v0.22.0 a v0.23.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.23.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.23.0
|
||||
|
||||
Run the following commands:
|
||||
Ejecución de los siguientes comandos:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.23
|
||||
```
|
||||
|
||||
The `yarn database:migrate:prod` command will apply the migrations to the Database.
|
||||
The `yarn command:prod upgrade-0.23` takes care of the data migration, including transferring activities to tasks/notes.
|
||||
El comando `yarn database:migrate:prod` aplicará las migraciones a la base de datos.
|
||||
El `yarn command:prod upgrade-0.23` se encarga de la migración de datos, incluyendo la transferencia de actividades a tareas/notas.
|
||||
|
||||
### v0.21.0 to v0.22.0
|
||||
### v0.21.0 a v0.22.0
|
||||
|
||||
Upgrade your Twenty instance to use v0.22.0 image
|
||||
Actualice su instancia de Twenty para usar la imagen v0.22.0
|
||||
|
||||
Run the following commands:
|
||||
Ejecución de los siguientes comandos:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
@@ -376,6 +376,6 @@ yarn command:prod workspace:sync-metadata -f
|
||||
yarn command:prod upgrade-0.22
|
||||
```
|
||||
|
||||
The `yarn database:migrate:prod` command will apply the migrations to the Database.
|
||||
The `yarn command:prod workspace:sync-metadata -f` command will sync the definition of standard objects to the metadata tables and apply to required migrations to existing workspaces.
|
||||
The `yarn command:prod upgrade-0.22` command will apply specific data transformations to adapt to the new object defaultRequestInstrumentationOptions.
|
||||
El comando `yarn database:migrate:prod` aplicará las migraciones a la base de datos.
|
||||
El comando `yarn command:prod workspace:sync-metadata -f` sincronizará la definición de objetos estándar a las tablas de metadatos y aplicará las migraciones requeridas a los espacios de trabajo existentes.
|
||||
El comando `yarn command:prod upgrade-0.22` aplicará transformaciones de datos específicas para adaptarse a las nuevas opciones predeterminadas de instrumentación de solicitud de objetos.
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
---
|
||||
title: Self-Host
|
||||
description: Deploy and manage Twenty on your own infrastructure.
|
||||
title: Autoalojamiento
|
||||
description: Despliega y administra Twenty en tu propia infraestructura.
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/what-is-twenty/20.png" alt="AI" />
|
||||
<img src="/images/user-guide/what-is-twenty/20.png" alt="IA" />
|
||||
</Frame>
|
||||
|
||||
## Overview
|
||||
## Resumen
|
||||
|
||||
Twenty can be self-hosted on your own infrastructure, giving you full control over your data and deployment.
|
||||
Puedes autoalojar Twenty en tu propia infraestructura, lo que te brinda control total sobre tus datos y el despliegue.
|
||||
|
||||
## Why Self-Host?
|
||||
## ¿Por qué autoalojar?
|
||||
|
||||
* **Data ownership**: Keep all CRM data on your own servers
|
||||
* **Compliance**: Meet regulatory requirements for data residency
|
||||
* **Customization**: Full access to modify and extend the platform
|
||||
* **Propiedad de los datos**: Mantén todos los datos de CRM en tus propios servidores
|
||||
* **Cumplimiento**: Cumple los requisitos normativos de residencia de datos
|
||||
* **Personalización**: Acceso completo para modificar y ampliar la plataforma
|
||||
|
||||
## Getting Started
|
||||
## Primeros pasos
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Docker Compose" icon="docker" href="/l/es/developers/self-host/capabilities/docker-compose">
|
||||
Quick setup with Docker
|
||||
Configuración rápida con Docker
|
||||
</Card>
|
||||
|
||||
<Card title="Cloud Providers" icon="cloud" href="/l/es/developers/self-host/capabilities/cloud-providers">
|
||||
Deploy on AWS, GCP, or Azure
|
||||
<Card title="Proveedores de la nube" icon="cloud" href="/l/es/developers/self-host/capabilities/cloud-providers">
|
||||
Despliega en AWS, GCP o Azure
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,197 +1,197 @@
|
||||
{
|
||||
"tabs": {
|
||||
"userGuide": {
|
||||
"label": "User Guide",
|
||||
"label": "Guía de usuario",
|
||||
"groups": {
|
||||
"discoverTwenty": {
|
||||
"label": "Discover Twenty",
|
||||
"label": "Descubre Twenty",
|
||||
"groups": {
|
||||
"gettingStartedCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"gettingStartedHowTos": {
|
||||
"label": "How-Tos"
|
||||
"label": "Guías prácticas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataModel": {
|
||||
"label": "Data Model",
|
||||
"label": "Modelo de datos",
|
||||
"groups": {
|
||||
"dataModelCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"dataModelHowTos": {
|
||||
"label": "How-Tos"
|
||||
"label": "Guías prácticas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataMigration": {
|
||||
"label": "Data Migration",
|
||||
"label": "Migración de datos",
|
||||
"groups": {
|
||||
"dataMigrationCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"dataMigrationHowTos": {
|
||||
"label": "How-Tos"
|
||||
"label": "Guías prácticas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"calendarEmails": {
|
||||
"label": "Calendar & Emails",
|
||||
"label": "Calendario y correos electrónicos",
|
||||
"groups": {
|
||||
"calendarEmailsCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"calendarEmailsHowTos": {
|
||||
"label": "How-Tos"
|
||||
"label": "Guías prácticas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workflows": {
|
||||
"label": "Workflows",
|
||||
"label": "Flujos de trabajo",
|
||||
"groups": {
|
||||
"workflowsCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"workflowsHowTos": {
|
||||
"label": "How-Tos",
|
||||
"label": "Guías prácticas",
|
||||
"groups": {
|
||||
"crmAutomations": {
|
||||
"label": "CRM Automations"
|
||||
"label": "Automatizaciones de CRM"
|
||||
},
|
||||
"connectToOtherTools": {
|
||||
"label": "Connect to Other Tools"
|
||||
"label": "Conectar con otras herramientas"
|
||||
},
|
||||
"advancedConfigurations": {
|
||||
"label": "Advanced Configurations"
|
||||
"label": "Configuraciones avanzadas"
|
||||
},
|
||||
"needMoreHelp": {
|
||||
"label": "Need More Help"
|
||||
"label": "¿Necesitas más ayuda?"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ai": {
|
||||
"label": "AI",
|
||||
"label": "IA",
|
||||
"groups": {
|
||||
"aiCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"aiHowTos": {
|
||||
"label": "How-Tos"
|
||||
"label": "Guías prácticas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewsPipelines": {
|
||||
"label": "Views & Pipelines",
|
||||
"label": "Vistas y canalizaciones",
|
||||
"groups": {
|
||||
"viewsPipelinesCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"viewsPipelinesHowTos": {
|
||||
"label": "How-Tos"
|
||||
"label": "Guías prácticas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashboards": {
|
||||
"label": "Dashboards",
|
||||
"label": "Tableros",
|
||||
"groups": {
|
||||
"dashboardsCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"dashboardsHowTos": {
|
||||
"label": "How-Tos"
|
||||
"label": "Guías prácticas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permissionsAccess": {
|
||||
"label": "Permissions & Access",
|
||||
"label": "Permisos y acceso",
|
||||
"groups": {
|
||||
"permissionsAccessCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"permissionsAccessHowTos": {
|
||||
"label": "How-Tos"
|
||||
"label": "Guías prácticas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"billing": {
|
||||
"label": "Billing",
|
||||
"label": "Facturación",
|
||||
"groups": {
|
||||
"billingCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"billingHowTos": {
|
||||
"label": "How-Tos"
|
||||
"label": "Guías prácticas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"label": "Settings",
|
||||
"label": "Configuración",
|
||||
"groups": {
|
||||
"settingsCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
},
|
||||
"settingsHowTos": {
|
||||
"label": "How-Tos"
|
||||
"label": "Guías prácticas"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"developers": {
|
||||
"label": "Developers",
|
||||
"label": "Desarrolladores",
|
||||
"groups": {
|
||||
"developersGroup": {
|
||||
"label": "Developers"
|
||||
"label": "Desarrolladores"
|
||||
},
|
||||
"extend": {
|
||||
"label": "Extend",
|
||||
"label": "Ampliar",
|
||||
"groups": {
|
||||
"extendCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selfHost": {
|
||||
"label": "Self-Host",
|
||||
"label": "Autoalojamiento",
|
||||
"groups": {
|
||||
"selfHostCapabilities": {
|
||||
"label": "Capabilities"
|
||||
"label": "Capacidades"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contribute": {
|
||||
"label": "Contribute",
|
||||
"label": "Contribuir",
|
||||
"groups": {
|
||||
"contributeCapabilities": {
|
||||
"label": "Capabilities",
|
||||
"label": "Capacidades",
|
||||
"groups": {
|
||||
"frontendDevelopment": {
|
||||
"label": "Frontend Development",
|
||||
"label": "Desarrollo Frontend",
|
||||
"groups": {
|
||||
"twentyUi": {
|
||||
"label": "Twenty UI",
|
||||
"groups": {
|
||||
"display": {
|
||||
"label": "Display"
|
||||
"label": "Mostrar"
|
||||
},
|
||||
"feedback": {
|
||||
"label": "Feedback"
|
||||
"label": "Retroalimentación"
|
||||
},
|
||||
"input": {
|
||||
"label": "Input"
|
||||
"label": "Entrada"
|
||||
},
|
||||
"navigation": {
|
||||
"label": "Navigation"
|
||||
"label": "Navegación"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"backendDevelopment": {
|
||||
"label": "Backend Development"
|
||||
"label": "Desarrollo Backend"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user