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. Create cache services and transformation utilities for syncable entities in Twenty. Use when implementing entity-to-flat conversions, input DTO transpilation to universal flat entities, or cache recomputation for syncable entities.
インストール方法を見るインストールする前に、エージェントに与えられる指示の中身を確認できます。
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.
This step creates:
Key principle: Input transform utils must output universal flat entities (with universalIdentifier and foreign keys mapped to universal identifiers).
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:
@WorkspaceCache decorator with unique flatMapsKeywithDeleted: true to include soft-deleted entitiesflat{EntityName}Maps (camelCase)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)
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:
id and universalIdentifier with v4())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:
'metadata' datasource// 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,
});
// 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,
});
// 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;
};
Before moving to Step 3:
@WorkspaceCache decoratorwithDeleted: trueflat{EntityName}Maps patternuniversalIdentifier set correctly (generated with v4())Once cache and transform utilities are complete, proceed to: Syncable Entity: Builder & Validation (Step 3/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.
日本語の概要は準備中です。原文の説明を表示しています。