syncable-entity-cache-and-transform

安装量: 35
排名: #19855

安装

npx skills add https://github.com/twentyhq/twenty --skill syncable-entity-cache-and-transform
Syncable Entity: Cache & Transform (Step 2/6)
Purpose
Create cache layer and transformation utilities to convert between different entity representations.
When to use
After completing Step 1 (Types & Constants). Required before building validators and action handlers.
Quick Start
This step creates:
Cache service for flat entity maps
Entity-to-flat conversion utility
Input transform utils (DTO → Universal Flat Entity)
Key principle
Input transform utils must output universal flat entities (with universalIdentifier and foreign keys mapped to universal identifiers). Step 1: Create Cache Service File : src/engine/metadata-modules/flat-my-entity/services/flat-my-entity-cache.service.ts import { Injectable } from '@nestjs/common' ; import { InjectRepository } from '@nestjs/typeorm' ; import { Repository } from 'typeorm' ; import { v4 } from 'uuid' ; import { WorkspaceCache } from 'src/engine/twenty-orm/decorators/workspace-cache.decorator' ; import { MyEntityEntity } from 'src/engine/metadata-modules/my-entity/entities/my-entity.entity' ; import { type FlatMyEntityMaps } from 'src/engine/metadata-modules/flat-my-entity/types/flat-my-entity-maps.type' ; import { fromMyEntityEntityToFlatMyEntity } from 'src/engine/metadata-modules/flat-my-entity/utils/from-my-entity-entity-to-flat-my-entity.util' ; @ Injectable ( ) export class FlatMyEntityCacheService { constructor ( @ InjectRepository ( MyEntityEntity , 'metadata' ) private readonly myEntityRepository : Repository < MyEntityEntity

, ) { } @ WorkspaceCache ( { flatMapsKey : 'flatMyEntityMaps' } ) async getFlatMyEntityMaps ( ) : Promise < FlatMyEntityMaps

{ const myEntities = await this . myEntityRepository . find ( { withDeleted : true , // CRITICAL: Include soft-deleted entities } ) ; const flatMyEntities = myEntities . map ( ( entity ) => fromMyEntityEntityToFlatMyEntity ( entity ) , ) ; return { byId : Object . fromEntries ( flatMyEntities . map ( ( e ) => [ e . id , e ] ) ) , byName : Object . fromEntries ( flatMyEntities . map ( ( e ) => [ e . name , e ] ) ) , } ; } } Critical rules : Use @WorkspaceCache decorator with unique flatMapsKey Always use withDeleted: true to include soft-deleted entities Cache key pattern: flat{EntityName}Maps (camelCase) Step 2: Entity-to-Flat Conversion File : src/engine/metadata-modules/flat-my-entity/utils/from-my-entity-entity-to-flat-my-entity.util.ts import { v4 } from 'uuid' ; import { type MyEntityEntity } from 'src/engine/metadata-modules/my-entity/entities/my-entity.entity' ; import { type FlatMyEntity } from 'src/engine/metadata-modules/flat-my-entity/types/flat-my-entity.type' ; export const fromMyEntityEntityToFlatMyEntity = ( entity : MyEntityEntity , ) : FlatMyEntity => { return { id : entity . id , // Critical: generate a new UUID for universalIdentifier universalIdentifier : v4 ( ) , workspaceId : entity . workspaceId , applicationId : entity . applicationId , name : entity . name , label : entity . label , description : entity . description , isCustom : entity . isCustom , parentEntityId : entity . parentEntityId , settings : entity . settings , createdAt : entity . createdAt . toISOString ( ) , updatedAt : entity . updatedAt . toISOString ( ) , deletedAt : entity . deletedAt ?. toISOString ( ) ?? null , } ; } ; Critical : universalIdentifier must be a new UUID generated with v4() (not entity.id ) Step 3: Input Transform Utils (DTO → Universal Flat Entity) File : src/engine/metadata-modules/flat-my-entity/utils/from-create-my-entity-input-to-universal-flat-my-entity.util.ts import { v4 } from 'uuid' ; import { sanitizeString } from 'twenty-shared/string' ; import { type CreateMyEntityInput } from 'src/engine/metadata-modules/my-entity/dtos/create-my-entity.input' ; import { type UniversalFlatMyEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-my-entity.type' ; import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util' ; import { type AllFlatEntityMapsByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps-by-metadata-name.type' ; export const fromCreateMyEntityInputToUniversalFlatMyEntity = ( { input , workspaceId , flatEntityMaps , } : { input : CreateMyEntityInput ; workspaceId : string ; flatEntityMaps ? : AllFlatEntityMapsByMetadataName ; } ) : UniversalFlatMyEntity => { const id = v4 ( ) ; const universalIdentifier = v4 ( ) ; // 1. Extract foreign key IDs BEFORE sanitization const parentEntityId = input . parentEntityId ?? null ; // 2. Sanitize string properties const name = sanitizeString ( input . name ) ; const label = sanitizeString ( input . label ) ; const description = input . description ? sanitizeString ( input . description ) : null ; // 3. Build base flat entity const baseFlatEntity = { id , universalIdentifier , workspaceId , applicationId : null , name , label , description , isCustom : true , parentEntityId , settings : input . settings ?? null , createdAt : new Date ( ) . toISOString ( ) , updatedAt : new Date ( ) . toISOString ( ) , deletedAt : null , } ; // 4. Resolve foreign keys to universal identifiers (if flatEntityMaps provided) if ( flatEntityMaps ) { return resolveEntityRelationUniversalIdentifiers ( { metadataName : 'myEntity' , flatEntity : baseFlatEntity , flatEntityMaps , } ) ; } // 5. Return with null universal foreign keys if no maps return { ... baseFlatEntity , parentEntityUniversalIdentifier : null , } ; } ; Key steps : Generate IDs ( id and universalIdentifier with v4() ) Extract foreign keys before sanitization Sanitize all string properties Build base flat entity Resolve foreign keys → universal identifiers Step 4: Create Flat Entity Module File : src/engine/metadata-modules/flat-my-entity/flat-my-entity.module.ts import { Module } from '@nestjs/common' ; import { TypeOrmModule } from '@nestjs/typeorm' ; import { MyEntityEntity } from 'src/engine/metadata-modules/my-entity/entities/my-entity.entity' ; import { FlatMyEntityCacheService } from 'src/engine/metadata-modules/flat-my-entity/services/flat-my-entity-cache.service' ; @ Module ( { imports : [ TypeOrmModule . forFeature ( [ MyEntityEntity ] , 'metadata' ) ] , providers : [ FlatMyEntityCacheService ] , exports : [ FlatMyEntityCacheService ] , } ) export class FlatMyEntityModule { } Rules : Import entity with 'metadata' datasource Export cache service for use in other modules Common Patterns Pattern: Foreign Key Resolution // Extract foreign keys BEFORE sanitization const parentEntityId = input . parentEntityId ?? null ; // After building base entity, resolve to universal identifiers const universalFlatEntity = resolveEntityRelationUniversalIdentifiers ( { metadataName : 'myEntity' , flatEntity : baseFlatEntity , flatEntityMaps , } ) ; Pattern: JSONB with SerializedRelation // For JSONB properties containing foreign keys const settings = input . settings ? { ... input . settings , fieldMetadataId : input . settings . fieldMetadataId , } : null ; // After resolution, JSONB foreign keys become universal identifiers return resolveEntityRelationUniversalIdentifiers ( { metadataName : 'myEntity' , flatEntity : { ... baseFlatEntity , settings } , flatEntityMaps , } ) ; Pattern: Update Transform // from-update-my-entity-input-to-universal-flat-my-entity-updates.util.ts export const fromUpdateMyEntityInputToUniversalFlatMyEntityUpdates = ( { input , flatEntityMaps , } : { input : UpdateMyEntityInput ; flatEntityMaps ? : AllFlatEntityMapsByMetadataName ; } ) : Partial < UniversalFlatMyEntity

=> { const updates : Partial < UniversalFlatMyEntity

= { } ; if ( input . name !== undefined ) { updates . name = sanitizeString ( input . name ) ; } if ( input . parentEntityId !== undefined ) { updates . parentEntityId = input . parentEntityId ; } updates . updatedAt = new Date ( ) . toISOString ( ) ; // Resolve foreign keys if maps provided if ( flatEntityMaps ) { return resolveEntityRelationUniversalIdentifiers ( { metadataName : 'myEntity' , flatEntity : updates as any , flatEntityMaps , } ) ; } return updates ; } ; Checklist Before moving to Step 3: Cache service created with @WorkspaceCache decorator Cache uses withDeleted: true Cache key follows flat{EntityName}Maps pattern Entity-to-flat conversion implemented universalIdentifier set correctly (generated with v4() ) Create input transform implemented Update input transform implemented (if needed) Foreign keys extracted before sanitization String properties sanitized Foreign keys resolved to universal identifiers Flat entity module created and exports cache service Next Step Once cache and transform utilities are complete, proceed to: Syncable Entity: Builder & Validation (Step 3/6) For complete workflow, see @creating-syncable-entity rule.

返回排行榜