syncable-entity-runner-and-actions

Syncable Entity: Runner & Actions (Step 4/6)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "syncable-entity-runner-and-actions" with this command: npx skills add twentyhq/twenty/twentyhq-twenty-syncable-entity-runner-and-actions

Syncable Entity: Runner & Actions (Step 4/6)

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.

Quick Start

This step creates:

  • Create action handler

  • Update action handler

  • Delete action handler

  • Universal-to-flat conversion utilities

Key pattern: Each handler has two phases:

  • Transpilation: Universal action → Flat action

  • Execution: Flat action → Database operation

Step 1: Create Action Handler

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 → flat

  • insertFlatEntitiesInRepository : Base class helper for inserts

  • executeForMetadata : Metadata database operations

  • executeForWorkspaceSchema : Workspace schema changes (if needed)

Step 2: Update Action Handler

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 object

Step 3: Delete Action Handler

File: 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 transpilation

Step 4: Universal-to-Flat Conversion

File: 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 )

Action Handler Patterns

Pattern: Create Handler

// 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), }); }

Pattern: Update Handler

// 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 }; }

Pattern: Delete Handler

// 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); }

Checklist

Before moving to Step 5:

  • Create action handler implemented

  • Update action handler implemented

  • Delete action handler implemented

  • All handlers extend appropriate base class

  • transpileUniversalActionToFlatAction implemented in all handlers

  • executeForMetadata implemented in all handlers

  • executeForWorkspaceSchema implemented (or returns empty)

  • Universal-to-flat conversion utility created

  • Create handler uses insertFlatEntitiesInRepository

  • Update handler uses resolveUniversalUpdateRelationIdentifiersToIds

  • Delete handler uses transpileUniversalDeleteActionToFlatDeleteAction

  • Delete handler uses hard delete (delete() )

Next Step

Once action handlers are complete, proceed to: Syncable Entity: Integration (Step 5/6)

For complete workflow, see @creating-syncable-entity rule.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

syncable-entity-cache-and-transform

No summary provided by upstream source.

Repository SourceNeeds Review
General

syncable-entity-types-and-constants

No summary provided by upstream source.

Repository SourceNeeds Review
General

syncable-entity-testing

No summary provided by upstream source.

Repository SourceNeeds Review