- Syncable Entity: Types & Constants (Step 1/6)
- Purpose
-
- Define all types, entities, and register in central constants. This is the foundation - everything else depends on these types being correct.
- When to use
-
- First step when creating any new syncable entity. Must be completed before other steps.
- Quick Start
- This step creates:
- Metadata name constant (twenty-shared)
- TypeORM entity (extends
- SyncableEntity
- )
- Flat entity types
- Action types (universal + flat)
- Central constant registrations (5 constants)
- Step 1: Add Metadata Name
- File
- :
- packages/twenty-shared/src/metadata/all-metadata-name.constant.ts
- export
- const
- ALL_METADATA_NAME
- =
- {
- // ... existing entries
- myEntity
- :
- 'myEntity'
- ,
- }
- as
- const
- ;
- Step 2: Create TypeORM Entity
- File
- :
- src/engine/metadata-modules/my-entity/entities/my-entity.entity.ts
- import
- {
- Entity
- ,
- Column
- ,
- ManyToOne
- ,
- JoinColumn
- }
- from
- 'typeorm'
- ;
- import
- {
- SyncableEntity
- }
- from
- 'src/engine/workspace-manager/types/syncable-entity.interface'
- ;
- @
- Entity
- (
- {
- name
- :
- 'myEntity'
- }
- )
- export
- class
- MyEntityEntity
- extends
- SyncableEntity
- {
- @
- Column
- (
- {
- type
- :
- 'varchar'
- }
- )
- name
- :
- string
- ;
- @
- Column
- (
- {
- type
- :
- 'varchar'
- }
- )
- label
- :
- string
- ;
- @
- Column
- (
- {
- type
- :
- 'boolean'
- ,
- default
- :
- true
- }
- )
- isCustom
- :
- boolean
- ;
- // Foreign key example (optional)
- @
- Column
- (
- {
- type
- :
- 'uuid'
- ,
- nullable
- :
- true
- }
- )
- parentEntityId
- :
- string
- |
- null
- ;
- @
- ManyToOne
- (
- (
- )
- =>
- ParentEntityEntity
- ,
- {
- nullable
- :
- true
- }
- )
- @
- JoinColumn
- (
- {
- name
- :
- 'parentEntityId'
- }
- )
- parentEntity
- :
- ParentEntityEntity
- |
- null
- ;
- // JSONB column example (optional)
- @
- Column
- (
- {
- type
- :
- 'jsonb'
- ,
- nullable
- :
- true
- }
- )
- settings
- :
- Record
- <
- string
- ,
- any
- >
- |
- null
- ;
- }
- Key rules
- :
- Must extend
- SyncableEntity
- (provides
- id
- ,
- universalIdentifier
- ,
- applicationId
- , etc.)
- Must have
- isCustom
- boolean column
- Use
- @Column({ type: 'jsonb' })
- for JSON data
- Step 3: Define Flat Entity Types
- File
- :
- src/engine/metadata-modules/flat-my-entity/types/flat-my-entity.type.ts
- import
- {
- type
- FlatEntityFrom
- }
- from
- 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'
- ;
- import
- {
- type
- MyEntityEntity
- }
- from
- 'src/engine/metadata-modules/my-entity/entities/my-entity.entity'
- ;
- export
- type
- FlatMyEntity
- =
- FlatEntityFrom
- <
- MyEntityEntity
- >
- ;
- Maps file
- (if entity has indexed lookups):
- // flat-my-entity-maps.type.ts
- export
- type
- FlatMyEntityMaps
- =
- {
- byId
- :
- Record
- <
- string
- ,
- FlatMyEntity
- >
- ;
- byName
- :
- Record
- <
- string
- ,
- FlatMyEntity
- >
- ;
- // Add other indexes as needed
- }
- ;
- Step 4: Define Editable Properties
- File
- :
- src/engine/metadata-modules/flat-my-entity/constants/editable-flat-my-entity-properties.constant.ts
- export
- const
- EDITABLE_FLAT_MY_ENTITY_PROPERTIES
- =
- [
- 'name'
- ,
- 'label'
- ,
- 'description'
- ,
- 'parentEntityId'
- ,
- 'settings'
- ,
- ]
- as
- const
- satisfies ReadonlyArray
- <
- keyof
- FlatMyEntity
- >
- ;
- Rule
- Only include properties that can be updated (exclude
id
,
createdAt
,
universalIdentifier
, etc.)
Step 5: Define Action Types
File
:
src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/my-entity/types/workspace-migration-my-entity-action.type.ts
import
{
type
FlatMyEntity
}
from
'src/engine/metadata-modules/flat-my-entity/types/flat-my-entity.type'
;
import
{
type
UniversalFlatMyEntity
}
from
'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-my-entity.type'
;
// Universal actions (used by builder/runner)
export
type
UniversalCreateMyEntityAction
=
{
type
:
'create'
;
metadataName
:
'myEntity'
;
universalFlatEntity
:
UniversalFlatMyEntity
;
}
;
export
type
UniversalUpdateMyEntityAction
=
{
type
:
'update'
;
metadataName
:
'myEntity'
;
universalFlatEntity
:
UniversalFlatMyEntity
;
universalUpdates
:
Partial
<
UniversalFlatMyEntity
; } ; export type UniversalDeleteMyEntityAction = { type : 'delete' ; metadataName : 'myEntity' ; universalFlatEntity : UniversalFlatMyEntity ; } ; // Flat actions (internal to runner) export type FlatCreateMyEntityAction = { type : 'create' ; metadataName : 'myEntity' ; flatEntity : FlatMyEntity ; } ; export type FlatUpdateMyEntityAction = { type : 'update' ; metadataName : 'myEntity' ; flatEntity : FlatMyEntity ; updates : Partial < FlatMyEntity
; } ; export type FlatDeleteMyEntityAction = { type : 'delete' ; metadataName : 'myEntity' ; flatEntity : FlatMyEntity ; } ; Step 6: Register in Central Constants 6a. AllFlatEntityTypesByMetadataName File : src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name.ts export type AllFlatEntityTypesByMetadataName = { // ... existing entries myEntity : { flatEntityMaps : FlatMyEntityMaps ; universalActions : { create : UniversalCreateMyEntityAction ; update : UniversalUpdateMyEntityAction ; delete : UniversalDeleteMyEntityAction ; } ; flatActions : { create : FlatCreateMyEntityAction ; update : FlatUpdateMyEntityAction ; delete : FlatDeleteMyEntityAction ; } ; flatEntity : FlatMyEntity ; universalFlatEntity : UniversalFlatMyEntity ; entity : MyEntityEntity ; } ; } ; 6b. ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME File : src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = { // ... existing entries myEntity : { name : { toCompare : true } , label : { toCompare : true } , description : { toCompare : true } , parentEntityId : { toCompare : true , universalProperty : 'parentEntityUniversalIdentifier' , } , settings : { toCompare : true , toStringify : true , universalProperty : 'universalSettings' , } , } , } as const ; Rules : toCompare: true → Editable property (checked for changes) toStringify: true → JSONB/object property (needs JSON serialization) universalProperty → Maps to universal version (for foreign keys & JSONB with SerializedRelation ) 6c. ALL_ONE_TO_MANY_METADATA_RELATIONS File : src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant.ts This constant is type-checked — values for metadataName , flatEntityForeignKeyAggregator , and universalFlatEntityForeignKeyAggregator are derived from entity type definitions. The aggregator names follow the pattern: remove trailing 's' from the relation property name, then append Ids or UniversalIdentifiers . export const ALL_ONE_TO_MANY_METADATA_RELATIONS = { // ... existing entries myEntity : { // If myEntity has a
childEntities: ChildEntityEntity[]property: childEntities : { metadataName : 'childEntity' , flatEntityForeignKeyAggregator : 'childEntityIds' , universalFlatEntityForeignKeyAggregator : 'childEntityUniversalIdentifiers' , } , // null for relations to non-syncable entities someNonSyncableRelation : null , } , } as const ; 6d. ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY File : src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts Low-level primitive constant. Only contains foreignKey — the column name ending in Id that stores the foreign key. Type-checked against entity properties. export const ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY = { // ... existing entries myEntity : { workspace : null , application : null , parentEntity : { foreignKey : 'parentEntityId' , } , } , } as const ; 6e. ALL_MANY_TO_ONE_METADATA_RELATIONS File : src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts Derived from both ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY (for foreignKey type and universalForeignKey derivation) and ALL_ONE_TO_MANY_METADATA_RELATIONS (for inverseOneToManyProperty key constraint). This is the main constant consumed by utils and optimistic tooling. export const ALL_MANY_TO_ONE_METADATA_RELATIONS = { // ... existing entries myEntity : { workspace : null , application : null , parentEntity : { metadataName : 'parentEntity' , foreignKey : 'parentEntityId' , inverseOneToManyProperty : 'myEntities' , // key in ALL_ONE_TO_MANY_METADATA_RELATIONS['parentEntity'], or null if no inverse isNullable : false , universalForeignKey : 'parentEntityUniversalIdentifier' , } , } , } as const ; Derivation dependency graph : ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY ALL_ONE_TO_MANY_METADATA_RELATIONS (foreignKey only) (metadataName, aggregators) │ │ │ FK type + universalFK derivation │ inverseOneToManyProperty keys │ │ └────────────────┬───────────────────────┘ ▼ ALL_MANY_TO_ONE_METADATA_RELATIONS (metadataName, foreignKey, inverseOneToManyProperty, isNullable, universalForeignKey) Rules : workspace: null , application: null — always present, always null (non-syncable relations) inverseOneToManyProperty — must be a key in ALL_ONE_TO_MANY_METADATA_RELATIONS[targetMetadataName] , or null if the target entity doesn't expose an inverse one-to-many relation universalForeignKey — derived from foreignKey by replacing the Id suffix with UniversalIdentifier Optimistic utils resolve flatEntityForeignKeyAggregator / universalFlatEntityForeignKeyAggregator at runtime by looking up inverseOneToManyProperty in ALL_ONE_TO_MANY_METADATA_RELATIONS Checklist Before moving to Step 2: Metadata name added to ALL_METADATA_NAME TypeORM entity created (extends SyncableEntity ) isCustom column added Flat entity type defined Flat entity maps type defined (if needed) Editable properties constant defined Universal and flat action types defined Registered in AllFlatEntityTypesByMetadataName Registered in ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME Registered in ALL_ONE_TO_MANY_METADATA_RELATIONS (if entity has one-to-many relations) Registered in ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY Registered in ALL_MANY_TO_ONE_METADATA_RELATIONS TypeScript compiles without errors Next Step Once all types and constants are defined, proceed to: Syncable Entity: Cache & Transform (Step 2/6) For complete workflow, see @creating-syncable-entity rule.
syncable-entity-types-and-constants
安装
npx skills add https://github.com/twentyhq/twenty --skill syncable-entity-types-and-constants