Use when the user wants to create or scaffold a new Twenty app
日本語の概要は準備中です。原文の説明を表示しています。
Contributing to the Twenty codebase itself (twentyhq/twenty server internals), not for building apps on top of Twenty. Implement action handlers for executing workspace migrations in Twenty. Use when creating database operations for syncable entities, implementing universal-to-flat entity transpilation, or handling create/update/delete actions in the runner layer.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Purpose: Execute migration actions against the database with proper transpilation from universal to flat entities.
When to use: After completing Steps 1-3 (Types, Cache, Builder). Required before integration.
This step creates:
Key pattern: Each handler has two phases:
File: src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/my-entity/services/create-my-entity-action-handler.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WorkspaceCreateActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-create-action-handler.service';
import { MyEntityEntity } from 'src/engine/metadata-modules/my-entity/entities/my-entity.entity';
import { fromUniversalFlatMyEntityToFlatMyEntity } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/my-entity/utils/from-universal-flat-my-entity-to-flat-my-entity.util';
import {
type UniversalCreateMyEntityAction,
type FlatCreateMyEntityAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/my-entity/types/workspace-migration-my-entity-action.type';
@Injectable()
export class CreateMyEntityActionHandlerService extends WorkspaceCreateActionHandlerService<
'myEntity',
UniversalCreateMyEntityAction,
FlatCreateMyEntityAction
> {
constructor(
@InjectRepository(MyEntityEntity, 'metadata')
private readonly myEntityRepository: Repository<MyEntityEntity>,
) {
super();
}
// Phase 1: Transpile universal action to flat action
protected transpileUniversalActionToFlatAction(
universalAction: UniversalCreateMyEntityAction,
flatEntityMaps: AllFlatEntityMapsByMetadataName,
): FlatCreateMyEntityAction {
return {
type: 'create',
metadataName: 'myEntity',
flatEntity: fromUniversalFlatMyEntityToFlatMyEntity(
universalAction.universalFlatEntity,
flatEntityMaps,
),
};
}
// Phase 2: Execute flat action against database
protected async executeForMetadata(
flatActions: FlatCreateMyEntityAction[],
): Promise<void> {
const flatEntities = flatActions.map((action) => action.flatEntity);
await this.insertFlatEntitiesInRepository({
repository: this.myEntityRepository,
flatEntities,
});
}
protected async executeForWorkspaceSchema(): Promise<void> {
// No workspace schema changes needed for metadata-only entity
return;
}
}
Key helper methods:
transpileUniversalActionToFlatAction: Converts universal → flatinsertFlatEntitiesInRepository: Base class helper for insertsexecuteForMetadata: Metadata database operationsexecuteForWorkspaceSchema: Workspace schema changes (if needed)File: src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/my-entity/services/update-my-entity-action-handler.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WorkspaceUpdateActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-update-action-handler.service';
import { MyEntityEntity } from 'src/engine/metadata-modules/my-entity/entities/my-entity.entity';
import { fromUniversalFlatMyEntityToFlatMyEntity } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/my-entity/utils/from-universal-flat-my-entity-to-flat-my-entity.util';
import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util';
@Injectable()
export class UpdateMyEntityActionHandlerService extends WorkspaceUpdateActionHandlerService<
'myEntity',
UniversalUpdateMyEntityAction,
FlatUpdateMyEntityAction
> {
constructor(
@InjectRepository(MyEntityEntity, 'metadata')
private readonly myEntityRepository: Repository<MyEntityEntity>,
) {
super();
}
protected transpileUniversalActionToFlatAction(
universalAction: UniversalUpdateMyEntityAction,
flatEntityMaps: AllFlatEntityMapsByMetadataName,
): FlatUpdateMyEntityAction {
const flatEntity = fromUniversalFlatMyEntityToFlatMyEntity(
universalAction.universalFlatEntity,
flatEntityMaps,
);
// Resolve universal foreign keys in updates to regular IDs
const flatUpdates = resolveUniversalUpdateRelationIdentifiersToIds({
metadataName: 'myEntity',
universalUpdates: universalAction.universalUpdates,
flatEntityMaps,
});
return {
type: 'update',
metadataName: 'myEntity',
flatEntity,
updates: flatUpdates,
};
}
protected async executeForMetadata(
flatActions: FlatUpdateMyEntityAction[],
): Promise<void> {
for (const action of flatActions) {
await this.myEntityRepository.update(
{ id: action.flatEntity.id },
action.updates,
);
}
}
protected async executeForWorkspaceSchema(): Promise<void> {
return;
}
}
Update-specific helper:
resolveUniversalUpdateRelationIdentifiersToIds: Maps universal identifiers back to regular IDs in the updates objectFile: src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/my-entity/services/delete-my-entity-action-handler.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WorkspaceDeleteActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-delete-action-handler.service';
import { MyEntityEntity } from 'src/engine/metadata-modules/my-entity/entities/my-entity.entity';
import { fromUniversalFlatMyEntityToFlatMyEntity } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/my-entity/utils/from-universal-flat-my-entity-to-flat-my-entity.util';
@Injectable()
export class DeleteMyEntityActionHandlerService extends WorkspaceDeleteActionHandlerService<
'myEntity',
UniversalDeleteMyEntityAction,
FlatDeleteMyEntityAction
> {
constructor(
@InjectRepository(MyEntityEntity, 'metadata')
private readonly myEntityRepository: Repository<MyEntityEntity>,
) {
super();
}
protected transpileUniversalActionToFlatAction(
universalAction: UniversalDeleteMyEntityAction,
flatEntityMaps: AllFlatEntityMapsByMetadataName,
): FlatDeleteMyEntityAction {
// Use base class helper for delete transpilation
return this.transpileUniversalDeleteActionToFlatDeleteAction({
universalAction,
flatEntityMaps,
fromUniversalFlatEntityToFlatEntity: fromUniversalFlatMyEntityToFlatMyEntity,
});
}
protected async executeForMetadata(
flatActions: FlatDeleteMyEntityAction[],
): Promise<void> {
const ids = flatActions.map((action) => action.flatEntity.id);
await this.myEntityRepository.delete(ids);
}
protected async executeForWorkspaceSchema(): Promise<void> {
return;
}
}
Delete-specific helper:
transpileUniversalDeleteActionToFlatDeleteAction: Base class helper that handles standard delete transpilationFile: src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/my-entity/utils/from-universal-flat-my-entity-to-flat-my-entity.util.ts
import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util';
import { type UniversalFlatMyEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-my-entity.type';
import { type FlatMyEntity } from 'src/engine/metadata-modules/flat-my-entity/types/flat-my-entity.type';
import { type AllFlatEntityMapsByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps-by-metadata-name.type';
export const fromUniversalFlatMyEntityToFlatMyEntity = (
universalFlatMyEntity: UniversalFlatMyEntity,
flatEntityMaps: AllFlatEntityMapsByMetadataName,
): FlatMyEntity => {
// Resolve universal foreign keys back to regular IDs
return resolveUniversalRelationIdentifiersToIds({
metadataName: 'myEntity',
universalFlatEntity: universalFlatMyEntity,
flatEntityMaps,
}) as FlatMyEntity;
};
Key utility:
resolveUniversalRelationIdentifiersToIds: Maps universal identifiers → regular IDs (reverse of resolveEntityRelationUniversalIdentifiers)// 1. Transpile: Universal → Flat
protected transpileUniversalActionToFlatAction(
universalAction,
flatEntityMaps,
) {
return {
type: 'create',
metadataName: 'myEntity',
flatEntity: fromUniversalFlatMyEntityToFlatMyEntity(
universalAction.universalFlatEntity,
flatEntityMaps,
),
};
}
// 2. Execute: Flat → Database
protected async executeForMetadata(flatActions) {
await this.insertFlatEntitiesInRepository({
repository: this.myEntityRepository,
flatEntities: flatActions.map(a => a.flatEntity),
});
}
// Transpile with update-specific resolution
protected transpileUniversalActionToFlatAction(
universalAction,
flatEntityMaps,
) {
const flatEntity = fromUniversalFlatMyEntityToFlatMyEntity(
universalAction.universalFlatEntity,
flatEntityMaps,
);
const flatUpdates = resolveUniversalUpdateRelationIdentifiersToIds({
metadataName: 'myEntity',
universalUpdates: universalAction.universalUpdates,
flatEntityMaps,
});
return { type: 'update', metadataName: 'myEntity', flatEntity, updates: flatUpdates };
}
// Use base class helper
protected transpileUniversalActionToFlatAction(
universalAction,
flatEntityMaps,
) {
return this.transpileUniversalDeleteActionToFlatDeleteAction({
universalAction,
flatEntityMaps,
fromUniversalFlatEntityToFlatEntity: fromUniversalFlatMyEntityToFlatMyEntity,
});
}
// Delete
protected async executeForMetadata(flatActions) {
const ids = flatActions.map(a => a.flatEntity.id);
await this.myEntityRepository.delete(ids);
}
Before moving to Step 5:
transpileUniversalActionToFlatAction implemented in all handlersexecuteForMetadata implemented in all handlersexecuteForWorkspaceSchema implemented (or returns empty)insertFlatEntitiesInRepositoryresolveUniversalUpdateRelationIdentifiersToIdstranspileUniversalDeleteActionToFlatDeleteActiondelete())Once action handlers are complete, proceed to: Syncable Entity: Integration (Step 5/6)
For complete workflow, see @creating-syncable-entity rule.
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Use when the user wants to create or scaffold a new Twenty app
日本語の概要は準備中です。原文の説明を表示しています。
Use when the user wants to add or modify Twenty app entities, including objects, layouts, logic functions, and front components inside an existing Twenty app.
日本語の概要は準備中です。原文の説明を表示しています。
Use when the user wants to manage or troubleshoot tooling, remotes, sync, build, deploy, logs, CI/CD, or operational workflows for an existing Twenty app.
日本語の概要は準備中です。原文の説明を表示しています。
Use when the user wants to prepare or verify a Twenty app for npm or marketplace publication, including README/About copy, package metadata, defineApplication marketplace metadata, logos, screenshots, and public assets.
日本語の概要は準備中です。原文の説明を表示しています。
Browser QA of a PR against a running Twenty app, post-merge on main or pre-merge via the qa-scout label. Derives user-visible scenarios from the PR diff, executes them with the Playwright MCP browser, attests database effects over SQL, watches server and worker logs for swallowed errors, and writes a structured verdict plus a report. Invoked by ci-e2e-main.yaml after the deterministic e2e suite; also runnable locally against a dev stack.
日本語の概要は準備中です。原文の説明を表示しています。
Contributing to the Twenty codebase itself (twentyhq/twenty server internals), not for building apps on top of Twenty. Create validation logic and migration action builders for syncable entities in Twenty. Use when implementing business rule validation, uniqueness checks, foreign key validation, or building workspace migration actions for syncable entities. Validators never throw and never mutate.
日本語の概要は準備中です。原文の説明を表示しています。