syncable-entity-types-and-constants について
Twenty に同期可能なエンティティを追加するための開発スキルです。型、エンティティ、中央登録を定義し、後続のキャッシュや移行、統合の基礎を作ります。
主な機能
- 01メタデータ名の登録
- 02TypeORM エンティティの定義
- 03フラットエンティティとアクション型
- 04編集可能プロパティの設定
- 05リレーション定数の登録
活用シーン
- 01同期可能なエンティティ実装の開始
- 02エンティティのプロパティとリレーションの設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ twentyhq/ twenty --skill syncable-entity-types-and-constantsインストール後、Claude Code で/syncable-entity-types-and-constantsを入力して読み込まれたか確認します。
詳細
同期可能エンティティ: 型と定数 (ステップ 1/6)
目的: すべての型、エンティティを定義し、中央の定数に登録します。これは基盤であり、他のすべてはこれらの型が正しいことに依存します。
使用するタイミング: 新しい同期可能エンティティを作成する際の最初のステップです。他のステップより前に完了させる必要があります。
クイックスタート
このステップでは以下を作成します:
- メタデータ名の定数 (twenty-shared)
- TypeORM エンティティ (
SyncableEntityを継承) - フラットエンティティ型
- アクション型 (ユニバーサル + フラット)
- 中央の定数への登録 (5 つの定数)
ステップ 1: メタデータ名を追加する
ファイル: packages/twenty-shared/src/metadata/all-metadata-name.constant.ts
export const ALL_METADATA_NAME = {
// ... existing entries
myEntity: 'myEntity',
} as const;
ステップ 2: TypeORM エンティティを作成する
ファイル: 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;
}
重要なルール:
SyncableEntityを継承する必要があります (id、universalIdentifier、applicationIdなどを提供します)isCustomブール型カラムを持つ必要があります- JSON データには
@Column({ type: 'jsonb' })を使用します
ステップ 3: フラットエンティティ型を定義する
ファイル: 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>;
マップファイル (エンティティにインデックス付きルックアップがある場合):
// flat-my-entity-maps.type.ts
export type FlatMyEntityMaps = {
byId: Record<string, FlatMyEntity>;
byName: Record<string, FlatMyEntity>;
// Add other indexes as needed
};
ステップ 4: 編集可能なプロパティを定義する
ファイル: 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>;
ルール: 更新可能なプロパティのみを含めます (id、createdAt、universalIdentifier などは除外します)
ステップ 5: アクション型を定義する
ファイル: 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;
};
ステップ 6: 中央の定数に登録する
6a. AllFlatEntityTypesByMetadataName
ファイル: src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name.type.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
ファイル: 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;
ルール:
toCompare: true→ 編集可能なプロパティ (変更がチェックされます)toStringify: true→ JSONB/オブジェクトプロパティ (JSON シリアライズが必要です)universalProperty→ ユニバーサル版にマッピングされます (外部キーおよびSerializedRelationを持つ JSONB 用)
6c. ALL_ONE_TO_MANY_METADATA_RELATIONS
ファイル: src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant.ts
この定数は型チェックされます — metadataName、flatEntityForeignKeyAggregator、universalFlatEntityForeignKeyAggregator の値はエンティティ型定義から導出されます。アグリゲーター名は次のパターンに従います: リレーションプロパティ名から末尾の 's' を削除し、Ids または 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
ファイル: src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts
低レベルのプリミティブ定数です。外部キーを格納する Id で終わるカラム名である foreignKey のみを含みます。エンティティのプロパティに対して型チェックされます。
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
ファイル: src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts
ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY(foreignKey 型および universalForeignKey の導出用)と ALL_ONE_TO_MANY_METADATA_RELATIONS(inverseOneToManyProperty キー制約用)の両方から導出されます。これはユーティリティと楽観的ツール群が利用する主要な定数です。
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;
導出の依存関係グラフ:
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)
ルール:
workspace: null、application: null— 常に存在し、常に null(同期対象外のリレーション)inverseOneToManyProperty—ALL_ONE_TO_MANY_METADATA_RELATIONS[targetMetadataName]のキーである必要があります。対象エンティティが逆方向の one-to-many リレーションを公開していない場合はnulluniversalForeignKey—foreignKeyからIdサフィックスをUniversalIdentifierに置き換えて導出されます- 楽観的ユーティリティは、実行時に
ALL_ONE_TO_MANY_METADATA_RELATIONSでinverseOneToManyPropertyを参照してflatEntityForeignKeyAggregator/universalFlatEntityForeignKeyAggregatorを解決します
チェックリスト
ステップ 2 に進む前に:
-
ALL_METADATA_NAMEにメタデータ名を追加した - TypeORM エンティティを作成した(
SyncableEntityを継承) -
isCustomカラムを追加した - フラットエンティティ型を定義した
- フラットエンティティマップ型を定義した(必要な場合)
- 編集可能プロパティの定数を定義した
- ユニバーサルおよびフラットなアクション型を定義した
-
AllFlatEntityTypesByMetadataNameに登録した -
ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAMEに登録した -
ALL_ONE_TO_MANY_METADATA_RELATIONSに登録した(エンティティが one-to-many リレーションを持つ場合) -
ALL_MANY_TO_ONE_METADATA_FOREIGN_KEYに登録した -
ALL_MANY_TO_ONE_METADATA_RELATIONSに登録した - TypeScript がエラーなくコンパイルされる
次のステップ
すべての型と定数を定義したら、次に進みます: Syncable Entity: キャッシュと変換(ステップ 2/6)
完全なワークフローについては、@creating-syncable-entity ルールを参照してください。