Commit Graph

22 Commits

Author SHA1 Message Date
Paul Rastoin 8c951d3623 Migrate Views-xxx Index Field Object Skill to be fully universal ( all actions and metadata runner and builder ) + all metadata update actions runner (#17687)
# What this PR does
Overall naming `universal` versus `flat` is not always the most updated
and so on
Will make a big cleaning tour after I've finished the whole migration
Migrating all `view` and ( filter fields etc ) `field` `object` `index`
to the universal pattern on all `services`, `builder` and `runner`
levels

## Universal and flat optimistic tooling

`addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
and its delete counterpart maintain the consistency of
`UniversalFlatEntityMaps` when an entity is created or removed. Beyond
inserting/removing the entity from its own maps, they walk through
`ALL_UNIVERSAL_METADATA_RELATIONS` to update the **aggregator arrays**
on related parent entities — the add appends the new entity's
`universalIdentifier` to the parent's aggregator (e.g. a new viewField's
identifier gets appended to its parent view's
`viewFieldUniversalIdentifiers`), and the delete filters it out. This
keeps the maps in sync so that diff computations and relation lookups
remain accurate throughout the migration building process.

## ALL_UNIVERSAL_METADATA_RELATIONS
`ALL_UNIVERSAL_METADATA_RELATIONS` is the universal counterpart of
`ALL_METADATA_RELATIONS`. It maps each metadata entity to its
many-to-one and one-to-many relations using universal foreign keys
(`*UniversalIdentifier`) instead of database IDs (`*Id`). This allows
migration actions to reference related entities in a workspace-agnostic
way. Relations that are workspace-specific (e.g. `workspace`,
`dataSource`, `userWorkspace`) are set to `null` and skipped during
resolution.

## `workspaceMigrationCreateIdEnrichment`
Reserved to API metadata ( will be able to validate at app installation
lvl )
- Workspace migration `create` actions now carry an optional `id` (and
`fieldIdByUniversalIdentifier` for object/field actions) so that
caller-provided IDs flow through the entire build-validate-run pipeline.
- New `enrichCreateWorkspaceMigrationActionsWithIds` utility resolves
`universalIdentifier → id` mappings after the builder runs and injects
them into the migration actions before the runner persists entities.
- Runner action handlers use the provided IDs instead of generating new
UUIDs, enabling deterministic entity creation for synchronization
workflows.


## `resolveUniversalUpdateRelationIdentifiersToIds`
`resolveUniversalUpdateRelationIdentifiersToIds` converts universal
identifiers (workspace-agnostic, stable keys) in a migration update
payload into concrete database UUIDs, so the update can be applied to a
specific workspace.

It iterates over the many-to-one relations defined in
`ALL_UNIVERSAL_METADATA_RELATIONS` for the given entity type, replaces
each `*UniversalIdentifier` property with its corresponding `*Id` by
looking up the target entity in `allFlatEntityMaps`, and throws if a
non-null identifier can't be resolved.

Used by all `update` action handlers in
`transpileUniversalActionToFlatAction`, avoiding duplicated resolution
logic across handlers.

## What this PR does not
- Migrating twenty-standard declaration to universal
- Migrating all the inputs transpilers to universal
- Migrating all metadata to be fully universal ( we still need to
de-scope the type of all of them and refactor their validator very close
)

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-02-09 19:02:38 +01:00
Paul Rastoin 3dc5b162c7 Spread in parent and requires FlatEntity.__universal (#17753)
# Introduction
Requiring the spreaded `__universal` record that aggregates all the
universal identifier ( relations fk and aggregators ) of an entity to
its root

It's blockin for https://github.com/twentyhq/twenty/pull/17687 to be
finalized because if we don't we would have to migrated all related
entities at once in order for them to always have the universal
properties

## `resolveEntityRelationUniversalIdentifiers`
Introduced `resolveEntityRelationUniversalIdentifiers` a centralized
utility that resolves foreign key IDs to universal identifiers using
ALL_METADATA_RELATIONS metadata. It provides strict typing for both
input (foreign keys) and output (universal identifiers), with
nullability dynamically inferred from entity relation types.
Strictly and dynamically typed for both output and input

To do so added a new type and const/runtime grain to
ALL_METADATA_RELATIONS `isNullable`to many-to-one entries, derived from
the entity relation property types. And fixed incorrectly typed typeorm
entities

### Usage
```ts
  const {
    availabilityObjectMetadataUniversalIdentifier,
    frontComponentUniversalIdentifier,
  } = resolveEntityRelationUniversalIdentifiers({
    metadataName: 'commandMenuItem',
    foreignKeyValues: {
      availabilityObjectMetadataId:
        createCommandMenuItemInput.availabilityObjectMetadataId,
      frontComponentId: createCommandMenuItemInput.frontComponentId,
    },
    flatEntityMaps: { flatObjectMetadataMaps, flatFrontComponentMaps },
  });
```
2026-02-06 17:02:02 +00:00
Paul Rastoin fe9d6f34ff [REQUIRES_FULL_CACHE_FLUSH_WHEN_RELEASED] Refactor FlatEntity to be UniversalFlatEntity superset (#17452)
# Introduction
In this PR we're refactoring the `FlatEntity` type to become a superset
of the `UniversalFlatEntity`.
Right now we're storing all the extra properties in `__universal`
property, at some point it might just be sibling to other entity and we
might rely on the `propertiesToCompare` constants and TypeScript
allowing passing a superset type into a smaller subset type

## FromTo utils
The entity to flat entity method now computes the universal information,
standardized a typing and pattern to do

## Example
Also strictly type
```ts
    "bbb019ea-6205-498c-aea5-67bc53bce8a9": {
      "workspaceId": "20202020-1c25-4d02-bf25-6aeccf7ea419",
      "universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
      "applicationId": "d01b010d-b984-465b-b40b-370e954e5188",
      "id": "bbb019ea-6205-498c-aea5-67bc53bce8a9",
      "pageLayoutTabId": "791a512f-169f-4209-b731-aa86716668c6",
      "title": "Deals by Company",
      "type": "GRAPH",
      "objectMetadataId": "9e14efea-df5b-4c0e-aba9-cfe455f32397",
      "gridPosition": { "row": 0, "column": 6, "rowSpan": 6, "columnSpan": 6 },
      "configuration": {
        "color": "orange",
        "orderBy": "FIELD_ASC",
        "timezone": "UTC",
        "displayLegend": true,
        "displayDataLabel": false,
        "showCenterMetric": true,
        "configurationType": "PIE_CHART",
        "firstDayOfTheWeek": 0,
        "aggregateOperation": "COUNT",
        "groupBySubFieldName": "name",
        "groupByFieldMetadataId": "6673ff18-63d2-47a1-8f85-2b9b09ca27a5",
        "aggregateFieldMetadataId": "8d64ee41-5dd4-4de6-945a-7c0c18399715"
      },
      "createdAt": "2026-01-28T14:08:52.140Z",
      "updatedAt": "2026-01-28T14:08:52.140Z",
      "deletedAt": null,
      "__universal": {
        "universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
        "applicationUniversalIdentifier": "20202020-64aa-4b6f-b003-9c74b97cee20",
        "pageLayoutTabUniversalIdentifier": "20202020-d011-4d11-8d11-da5ab0a01001",
        "objectMetadataUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
        "gridPosition": {
          "row": 0,
          "column": 6,
          "rowSpan": 6,
          "columnSpan": 6
        },
        "configuration": {
          "color": "orange",
          "orderBy": "FIELD_ASC",
          "timezone": "UTC",
          "displayLegend": true,
          "displayDataLabel": false,
          "showCenterMetric": true,
          "configurationType": "PIE_CHART",
          "firstDayOfTheWeek": 0,
          "aggregateOperation": "COUNT",
          "groupBySubFieldName": "name",
          "aggregateFieldMetadataUniversalIdentifier": "20202020-d01a-4131-8a31-f123456789ab",
          "groupByFieldMetadataUniversalIdentifier": "20202020-cbac-457e-b565-adece5fc815f"
        }
      }
    },
```
2026-01-28 16:46:34 +00:00
Paul Rastoin 4c93ab5259 Introduce UniversalFlatEntityFrom (#17367)
# Introduction

Creating a `UniversalFlatEntityFrom` that strips out all the relation
and foreignKey properties in order to replace them with
`UniversalIdentifier` suffix

This data type will be major for the workspace migration workspace
agnostic refactor

## Chore
- renamed `flat-entity.type` to `flat-entity-from.type.ts` ( more
accurate to exported module )
- create static test type over the field metadata entity on quite
complex utils as both coverage and documentation

## Example
Here's an example of a `UniversalFlatEntityFrom<FieldMetadataEntity>`

```ts
const universalFlatFieldMetadata: UniversalFlatFieldMetadata<FieldMetadataType.RELATION> = {
  // Base properties (from FieldMetadataEntity, excluding relations and applicationId)
  universalIdentifier: '550e8400-e29b-41d4-a716-446655440001',
  applicationUniversalIdentifier: '5800681c-088e-4e2b-9fc3-bcf6e8ec2051',
  type: FieldMetadataType.RELATION,
  name: 'firstName',
  label: 'First Name',
  defaultValue: null,
  description: 'The first name of the person',
  icon: 'IconUser',
  standardOverrides: null,
  options: null,
  settings: {
    relationType: RelationType.ONE_TO_MANY,
  },
  isCustom: false,
  isActive: true,
  isSystem: false,
  isUIReadOnly: false,
  isNullable: true,
  isUnique: false,
  isLabelSyncedWithName: true,
  morphId: null,

  // Date properties cast to string
  createdAt: '2024-01-15T10:30:00.000Z',
  updatedAt: '2024-01-15T10:30:00.000Z',

  // ManyToOne relation universal identifiers (from FieldMetadataEntity relations)
  relationTargetFieldMetadataUniversalIdentifier:
    '550e8400-e29b-41d4-a716-446655440012',
  relationTargetObjectMetadataUniversalIdentifier:
    '550e8400-e29b-41d4-a716-446655440013',

  // Join column universal identifiers (foreignKey -> universalIdentifier)
  objectMetadataUniversalIdentifier: '550e8400-e29b-41d4-a716-446655440010',

  // OneToMany relation universal identifiers (array of related entity identifiers)
  viewFieldUniversalIdentifiers: [
    '550e8400-e29b-41d4-a716-446655440020',
    '550e8400-e29b-41d4-a716-446655440021',
  ],
  viewFilterUniversalIdentifiers: ['550e8400-e29b-41d4-a716-446655440030'],
  kanbanAggregateOperationViewUniversalIdentifiers: [],
  calendarViewUniversalIdentifiers: [],
  mainGroupByFieldMetadataViewUniversalIdentifiers: [],
};
```

## Settings
Will hop on the settings typing next. Might not be dynamic but
declarative though
2026-01-22 18:30:19 +00:00
Paul Rastoin 6aa43b68f7 Identification cleanup (#17301)
# Introduction

following https://github.com/twentyhq/twenty/pull/17279
As we've finally identified all the syncable metadata entities, which
means they're expected to have non nullable applicationId and
universalIdentifier at pg_level we can remove previous retro comp
universalIdentifier fallbacking and update the dto too

~~This needs IdentifyRemainingEntitiesMetadataCommand and
MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand
to be run~~
```ts
[Nest] 197  - 01/21/2026, 3:08:35 PM     LOG [MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand] Successfully run MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand
```
2026-01-21 16:00:23 +00:00
Paul Rastoin 942d2fef83 Remove sync-metadata and IS_WORKSPACE_CREATION_V2_ENABLED feature flag (#16997)
# Introduction
Followup of
https://github.com/twentyhq/twenty/pull/17001#pullrequestreview-3638508738
close https://github.com/twentyhq/core-team-issues/issues/1910

We've completely decom the `sync-metadata` in production. We're now then
removing its implementation in favor of the v2.

## TODO:
- [x] Remove sync-metadata implem and commands
- [x] Remove workspace decorators 
- [x] Type each deprecated field to deprecated on their workspaceEntity
- [x] Remove the `workspace-sync-metadata` folder entirely
- [x] remove workspace migration
- [x] workspace migration removal migration
- [x] remove the `v2` references from workspace manager file names
- [x] remove the `v2` references from workspace manager modules
- [ ] Double check impact on translation file path updates


## Note
- Removed the gate logic
- Remains some service v2 naming, serverless needs to be migrated on v2
fully
- Removed workspaceMigration service app health consumption, making it
always returning up ( no more down ) cc @FelixMalfait ( quite obsolete
health check now, will require complete refactor once we introduce inter
app dependency etc )
2026-01-08 15:45:12 +00:00
Paul Rastoin 42c9ae1ebc Centralize metadata relations constant + simplification (#16901)
# Introduction
As we introduced a new grain on relation extraction thanks to low level
`SyncableEntity` and `WorkspaceRelatedEntity` we're able to strictly
typesafe extract metadata entity

The new constant centralizes both many to one and one to many constants
metadata entity constants in a more strictly typesafe way. Remains only
the flatEntityForeignKey aggregator which has to be chosen manually
across all available targeted flat entity ids properties
2026-01-02 16:49:06 +01:00
Paul Rastoin e3ffdb0c2b [BREAKING_CHANGE_NESTED_WORKSPACE]Refactor FlatEntity typing in aim of introducing UniversalFlatEntity (#16701)
# Introduction
Added a `WorkspaceRelated` and `AllNonWorkspaceRelatedEntity` to
simplify the `FlatEntityFrom` that now do not expect a string literal to
omit and itself builds the related many to one entities foreign key
aggregators

We now have the type grain over relation to syncable or just workspace
related entities

Added a migrations that sets the fk on missing entities

## Next
In upcoming PR we will be able to introduce such below type
```ts
import { type CastRecordTypeOrmDatePropertiesToString } from 'src/engine/metadata-modules/flat-entity/types/cast-record-typeorm-date-properties-to-string.type';
import { type ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.type';
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-related-entity-properties.type';
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/remove-suffix.type';
import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/types/syncable-entity.interface';

export type UniversalFlatEntityFrom<TEntity extends SyncableEntity> = Omit<
  TEntity,
  | `${ExtractEntityManyToOneEntityRelationProperties<TEntity> & string}Id`
  | ExtractEntityRelatedEntityProperties<TEntity>
  | 'application'
  | 'workspaceId'
  | 'applicationId'
  | keyof CastRecordTypeOrmDatePropertiesToString<TEntity>
> &
  CastRecordTypeOrmDatePropertiesToString<TEntity> & {
    [P in ExtractEntityManyToOneEntityRelationProperties<TEntity> &
      string as `${RemoveSuffix<P, 's'>}UniversalIdentifier`]: string;
  } & {
    [P in ExtractEntityOneToManyEntityRelationProperties<
      TEntity,
      SyncableEntity
    > &
      string as `${RemoveSuffix<P, 's'>}UniversalIdentifiers`]: string[];
  };

```
2025-12-30 13:47:02 +00:00
Charles Bochet a18203934c Fix flat entity maps date serialization (#16420)
Changes:
- as we store date in redis as serialized, let's make all flatEntity
dates as string. This requires changing FlatEntity types and making sure
that entity are converted to flatEntity and flatEntity to dtos
2025-12-09 16:41:50 +01:00
Weiko 1eb2e44058 Refactor workspace cache service (#16208)
## Context
We've recently introduced a new workspace cache service which now acts
as a cache access and local storage for all workspace related data,
deprecating the individual specific services.
- Better performance through multiple caching/fetching strategies
- Consistent data access patterns across the codebase
- Reduced redis queries through MGET/MSET/PIPELINE with multiple cache
keys
2025-12-01 17:08:21 +01:00
Paul Rastoin d640b93096 Improve v2 and cache invalidation perfs (#15467)
# Introduction
Log are debug logs of
`packages/twenty-server/test/integration/metadata/suites/object-metadata/create-delete-and-create-object-metadata-v2.integration-spec.ts`run
ten times in a row on clean db reset

## Next
Will improve cache computation to lighter invalidation.
RelationLoad `query` does not seem to work with typeorm so I'll continue
the custom integration i've started in
https://github.com/twentyhq/twenty/tree/optimize-cache-read-v2

## Integration tests duration
Significant test duration improvement too
### Before
<img width="2632" height="1402" alt="image"
src="https://github.com/user-attachments/assets/2f1f0ccf-44de-4856-bfe1-4f45a351763a"
/>

### After
<img width="2632" height="1402" alt="image"
src="https://github.com/user-attachments/assets/4bc9e6db-3046-48b9-b903-1464053936c4"
/>


## What's next
- The legacy cache invalidation removal
- Factorizing redis calls in only one operation

## Autogenerated performance comparison ( including mutation refactor
too )

[Before](https://gist.github.com/prastoin/3c1e21fa9e3b3ce4b0716902ff4a2dd6)

[After](https://gist.github.com/prastoin/7bfddd14bfded2e4991a9378970a026d)

The optimized implementation shows **dramatic performance improvements**
across all metrics:

- 🚀 **Cache Invalidation**: 156.3ms → 76.8ms (**50.9% faster**)
- 🚀 **Builder Operations**: 21.3ms → 14.2ms (**33.3% faster**)
-  **Consistency**: 16.4% more predictable performance

---

## 1. Overall Performance Summary

| Component | Before (avg) | After (avg) | Best (After) | Worst (After)
| Improvement |

|-----------|-------------|-------------|--------------|---------------|-------------|
| **Total Execution Time** | 180.2ms | 110.5ms | 52.3ms | 585.9ms |
**38.7% faster**  |
| **Cache Invalidation** | 156.3ms | 76.8ms | 47.8ms | 285.1ms | **50.9%
faster**  |
| **Transaction Execution** | 22.4ms | 22.1ms | 0.99ms | 314.2ms |
Similar |
| **Initial Cache Retrieval** | 0.81ms | 2.08ms | 0.21ms | 8.24ms |
Similar |
| **Entity Builder (total)** | 21.3ms | 14.2ms | 0.36ms | 42.5ms |
**33.3% faster**  |

### Total Execution Time Distribution

#### Before (Legacy Sequential)
```
Time (ms)    Count  Percentage  Visualization
< 150         18      16%       ████
150-180       32      29%       ███████
180-210       35      32%       ████████
210-250       17      15%       ████
250-350        6       5%       █
> 350          2       2%       ▌
```

#### After (Optimized Parallel)
```
Time (ms)    Count  Percentage  Visualization
< 70          28      31%       ████████
70-100        31      34%       █████████
100-150       18      20%       █████
150-200        8       9%       ██
200-300        4       4%       █
> 300          2       2%       ▌
```
---

## 2. Builder Performance Breakdown

### Field Metadata Builder

| Operation | Before (avg) | After (avg) | Improvement |
|-----------|-------------|-------------|-------------|
| Matrix computation | 3.5ms | 3.4ms | Similar |
| Creation validation | 15.2ms | 2.1ms | **86% faster**  |
| Deletion validation | 0.08ms | 0.06ms | Similar |
| Update validation | 1.3ms | 0.09ms | **93% faster**  |
| Entity processing | 18.6ms | 11.8ms | **37% faster**  |
| **Total validateAndBuild** | **21.3ms** | **14.2ms** | **33% faster**
 |

#### Performance Distribution
```
Before:  ▁▂▄█████▆▄▂▁     (wide spread, 15-28ms range)
After:   ▁▁▃█████▃▁▁       (tight clustering, 10-18ms range)
```

## 4. Cache Invalidation Performance Breakdown

### Cache Invalidation Summary

| Metric | Before (Legacy) | After (Optimized) | Improvement |
|--------|-----------------|-------------------|-------------|
| **Best Time** | 131.965ms | 47.833ms | **63.7% faster**  |
| **10th Percentile** | 140.2ms | 51.7ms | **63.1% faster**  |
| **25th Percentile** | 146.1ms | 54.4ms | **62.7% faster**  |
| **Median (50th)** | 155.1ms | 63.4ms | **59.1% faster**  |
| **Average** | 156.3ms | 76.8ms | **50.9% faster**  |
| **75th Percentile** | 160.2ms | 90.2ms | **43.7% faster**  |
| **90th Percentile** | 191.7ms | 100.2ms | **47.7% faster**  |
| **95th Percentile** | 235.6ms | 110.3ms | **53.2% faster**  |
| **99th Percentile** | 278.2ms | 224.9ms | **19.1% faster**  |
| **Worst Time** | 383.914ms | 285.102ms | **25.7% faster**  |

### Cache Invalidation Time Distribution

#### Before (Legacy Sequential)
```
Time (ms)    Count  Percentage  Visualization
130-140       3       3%        ▊
140-150      15      14%        ████
150-160      48      44%        ███████████
160-180      31      28%        ███████
180-220       8       7%        ██
220-280       3       3%        ▊
> 280         2       2%        ▌
```

#### After (Optimized Parallel + Intersection)
```
Time (ms)    Count  Percentage  Visualization
< 50          3       3%        ▊
50-60        27      25%        ███████
60-70        25      23%        ██████
70-90        25      23%        ██████
90-100       15      14%        ████
100-120       8       7%        ██
120-150       3       3%        ▊
> 150         4       4%        █
```

## 6. Performance Consistency Analysis

### Standard Deviation & Variance

| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| **Cache Invalidation Std Dev** | 42.1ms | 35.2ms | **16.4% more
consistent**  |
| **Total Execution Std Dev** | 68.3ms | 89.1ms | Slightly more variable
|
| **Coefficient of Variation (Cache)** | 26.9% | 45.8% | More variance |
| **Outliers (> 2σ)** | 5 cases | 3 cases | **40% fewer outliers**  |

---
2025-10-31 16:20:51 +00:00
Weiko cceeb6ed4d Add applicationId to syncableEntity and fix syncApp deletion (#15170)
## Context
- All flatEntity should extend SyncableEntity
- SyncableEntity should now have applicationId and application relation
- Fix syncApp deletion, should now properly use migration v2 to delete
syncable entities
2025-10-17 17:23:00 +00:00
Weiko c0ed246a03 Add is unique in migration v2 + refactor inferDeletionFromMissingEntities (#15070)
## Goal
- inferDeletionFromMissingEntities is now a map instead of a single
bool, allowing us to parameterise it based on the entity we want to
compare
- Adding index creation/update when field isUnique is set to true and
index deletion when isUnique is false (for now)

close https://github.com/twentyhq/core-team-issues/issues/1346
2025-10-15 16:08:13 +02:00
Paul Rastoin 6188c72f74 Simplify and enhance v2 type devxp (#15032)
# Introduction
This PR introduces a huge type refactor that will leverage dynamic intra
entity optimistic flat maps update in the future and also a more
granular cache invalidation enhancing performances

close https://github.com/twentyhq/core-team-issues/issues/1717
close https://github.com/twentyhq/core-team-issues/issues/1716
close https://github.com/twentyhq/core-team-issues/issues/1643

## What's done

### Comparators centralization
Comparator is now done through global configuration as const for each
metadata names
Thanks to 

Note:
Definition of standard is evolving, standard is now scoped to an app.
Meaning that a manifest should be able to update its own standards
objects but on other app standards ones ? Each synchronizable entities
will have a standardOverrides ?

## Typing refactor

### `AllFlatEntityTypesByMetadataName`

**Single source of truth for the complete type ecosystem**, mapping each
metadata name to its entity types, flat entities, and migration actions:

```typescript
export type AllFlatEntityTypesByMetadataName = {
  fieldMetadata: {
    actions: { created: CreateFieldAction; updated: UpdateFieldAction; deleted: DeleteFieldAction; };
    flatEntity: FlatFieldMetadata;
    entity: FieldMetadataEntity;
  };
  objectMetadata: { /* ... */ };
  // ... all 10 metadata types
};
```

### `ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS`

**Explicitly declares database relationships** between entities with
compile-time validation:

```typescript
export const ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS = {
  viewField: {
    view: 'viewId',
    fieldMetadata: 'fieldMetadataId',
  },
  cronTrigger: {
    serverlessFunction: 'serverlessFunctionId',
  },
  // ... all relations
} as const satisfies MetadataNameAndRelations;
```

### `ALL_FLAT_ENTITY_CONFIGURATION`

**Centralizes comparison and serialization logic** for each metadata
type:

```typescript
export const ALL_FLAT_ENTITY_CONFIGURATION = {
  fieldMetadata: {
    propertiesToCompare: ['name', 'type', 'label', 'defaultValue', /* ... */],
    propertiesToStringify: ['options', 'settings', 'defaultValue'],
  },
  objectMetadata: {
    propertiesToCompare: ['nameSingular', 'namePlural', 'isActive', /* ... */],
    propertiesToStringify: [],
  },
  // ... all metadata types
} as const satisfies AllFlatEntityConfiguration;
```

## Combined Impact

These three configurations work together to create a **strongly-typed,
centrally-managed metadata system**:

1. **`AllFlatEntityTypesByMetadataName`** defines *what exists*
2. **`ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS`** defines *how they
relate*
3. **`ALL_FLAT_ENTITY_CONFIGURATION`** defines *how to compare and
serialize them*

**Result:** Builders and validators become thin wrappers around
type-safe, configuration-driven logic instead of containing scattered,
error-prone manual implementations.

## What's next

### StandardOverrides standardization
Every metadata entity can be a standard one for a workspace if it's an
installed app, which means it might not expose the whole entity api to
be editable through an import dynamically
The standard overrides logic should not be applied to Fields and Objects
but to every entities

At the moment we have a logic of `EDITABLE_PROPERTIES` through the api,
and also `STANDARD_OVERRIDEDABLE_PROPERTIES`
This should be configuration centered like `propertiesToCompare` and
`propertiesToStringify`.
Scoping this PR to two last for the moment. As update dispatch to
standardOverrides could be considered as a side effect prefer waiting to
start the side effect refactor

### Granular Optimistic deprecation
With this new grain at runtime we will be able to add a flat entity and
dispatch its addition to related flat maps, so we don't have to describe
an optimistic method for each flat entity operations

See `addFlatEntityToFlatEntityAndRelatedEntityMapsOrThrow`

Note: Still in wip and included in this PR but about to create a new one
to integrate these utils and remove existing methods

### ValidateBuildAndRun dynamic args typed defintion
We should restrain the devxp to send expected flat maps entity as at
least from to or dependency as we now have the grain both a type lvl and
runtime to do so
It should not be possible in the devxp to forgot adding the views to the
v2 builder when passing the view field anymore ( that would lead to
permanent validation error in view field integrity checks )

## Conclusion
Thanks for reading and reviewing !
Any suggestions are more than welcomed ! ( same as for questions too ! )
2025-10-13 10:31:34 +02:00
Paul Rastoin 4ecc9c622d [WHEN_RELEASED_REQUIRES_CACHE_FLUSH] Object related record logic in v2 (#14937)
# Introduction
Initial motivation here was to migrate the object related records logic
from v1 to v2, please note that now in v2 views aren't records anymore
but core engine entities

## What's done
- Added specific label identifier targeting view field logic
- Handled side effects on viewField creation with lowest position on
object label identifier mutation
- Added viewField relations in field metadate entity + handled
optimistic in builder v2
- Added view relations in object metadata entity + handled optimistic in
builder v2
- Added integration tests covering the side effects and new validation
exceptions
- Sandardized cache computation
- Coverage on object metadata creation side effect on views and view
fields

## Coverage
```ts
 PASS  test/integration/graphql/suites/view/view-field/object-identifier-update-side-effect-on-view-field.integration-spec.ts
  View Field Resolver - Successful object metadata identifier update side effect on view field
    ✓ should create a view field on label identifier object metadata update if it does not exist on view (7 ms)
    ✓ Should not allow deleting a label identifier view field (17 ms)
    ✓ Should not allow destroying a label identifier view field (6 ms)
    ✓ Should not allow updating a label identifier view field visibility to false (8 ms)
    ✓ Should not allow creating a view field with a position lower than the label idenfitier view field (180 ms)
    ✓ Should not allow updated labelIdentifier view field with a position higher than existing other view field (346 ms)
    ✓ Should allow updated labelIdentifier view field with a position higher than existing other view field (434 ms)

Test Suites: 1 passed, 1 total
Tests:       7 passed, 7 total
Snapshots:   5 passed, 5 total
Time:        4.571 s, estimated 5 s
```

close https://github.com/twentyhq/core-team-issues/issues/1664
2025-10-08 16:23:37 +02:00
Paul Rastoin fbd3286792 Index v2 side effects (#14567)
# Introduction
Honestly this implem is a mess, discussing a potential side effect
handler with @weiko before the build and run that would handle each side
effect per entity and operation

Handling:
- [ ] unique index is generated when a field is updated with the
`isUnique`
- [x] an index is generated when a relation is created
- [x] search vector index creation on custom object creation
- [x] renaming a field metadata or an object should re-create all
related indexes which are composed by their namings
- [x] delete object should remove any related indexes
- [x] delete field should update related indexes ( if index ends up
empty it should be removed )
- [ ] on object renaming that contains morph fields -> triggers update
field -> trigger index recompute
- [x] on update name renaming should recompute all related indexes

## TODO
- [x] Integration testing
- [ ] Refactor the index maps cache to be storing a
`idsByObjectMetadataId`
- [x] Refactor deterministic name to use order sorting
- [x] Remove flat index from flat object

## What's next

Will handle morph indexes in a new dedicated PR for the moment will
stick to this
Same for the cache improvement and uniqueness
2025-09-23 10:57:11 +02:00
Paul Rastoin 2d8669ae71 Index v2 runner (#14537)
## Introduction
https://github.com/twentyhq/twenty/pull/14363
In this PR we're creating builder and runner for index metadata
Removing its previous integration within object builder

## Next:
- implem index impact on field and object metadata api
- Add integration testing coverage add integration test on index
- Implement uniqueness toggle in field metadata api
- add integration test on uniqueness

related to https://github.com/twentyhq/core-team-issues/issues/1344
2025-09-17 13:02:43 +00:00
Weiko 80347b7b38 Rename uniqueIdentifier to universalIdentifier (#14201) 2025-09-01 12:33:59 +02:00
Paul Rastoin 6629092645 Refactor builder to embed Object validation create/delete/update (#13934)
# Introduction
Moving validation directly in the builder that has the perfect
granularity to do it.
When importing we won't have to infer and dispatch on the operation
nature ( update delete create ) and validate accordingly

## Objects
Only migrated object validation for the moment even though create object
involves a validate flat field metadata creation call too

## TODO
- improve `otherFlatObjectMetadataMapsToValidate` naming too vague

## Next
- handle fields validation within fields actions build
- Unit test coverage validation issue on builder and validate
- integration test plugging with new feature flag

## Manual tested
- Update
- Delete
- Create TODO
2025-08-18 11:16:16 +02:00
Félix Malfait 8b4b9ef8da Change type import rule (#13751)
Forcing "type" to be explicit, works best will rollup on the frontend to
exclude depdendencies
2025-08-08 01:27:05 +02:00
Paul Rastoin ca3383cd86 Feat workspace migration maintains flat object metadata maps (#13620)
# Introduction
The workspace migration runner v2 now computes the next
flatObjectMetadataMaps post current ws migration action has been run
So the following one have an up to date informations

## Still TODO
~~Remove any contextual information from workspace migration v2 and
consume the optimistic cache to retrieve them~~

## Next steps
- Create flat-object-metadata-maps testing toolbox and flat-object/field
testing toolbox
- Implement strong coverage on every created utils/transpiler and
applyWorkspaceMigrationAction method
2025-08-06 13:10:14 +00:00
Paul Rastoin c1bf0a1fbf Object metadata API create one using workspace migration v2 (#13420)
# Introduction
In this PR we create basic transpilation methods and utils to handle
input to flat, entity to flat, object maps to flat. In order to
transpile everything into a common validation that will be implemented
in another PR

## FieldMetadataEntity typing
Added `never | null` to fields that should never be in order to ease
general abstracted method to pass null, as anw it's what is in the
database

## Todo
- ~~Create a feature flag~~
- Integration test for object creation through metadata api + pg col
introspection and snapshoting
2025-07-29 17:47:28 +02:00