Commit Graph

27 Commits

Author SHA1 Message Date
Paul Rastoin 1d9eb07ee0 Relation field metadata strict validation (#16890)
# Introduction

Now validating relation related object and field foreignkey integrity
2026-01-01 15:09:48 +01:00
Guillim b83dc3aaff TimelineActivity migration to morph (#15652)
# TimelineActivity migration to morph


- Creates `timelineActivities2` relations on Company, Dashboard, Note,
Opportunity, Person, Task, Workflow, WorkflowRun, and WorkflowVersion
entities with proper metadata and cascade delete behavior.

It was required to create standard fields as well since the
mapObjectMetadataByUniqueIdentifier needs it. otherwise the fields won't
be considered

- Feature Flag `IS_TIMELINE_ACTIVITY_MIGRATED` necessary to have the two
states in parallel. It is used as a stamp once the migration has been
run

- Migration is done using the coreDataSource. Why ?
even though is unsafe to use, the first implementation of the migration
took forever on each workspace. See [this
commit](https://github.com/twentyhq/twenty/pull/15652/commits/477011e8d7d4c580f79ba7ec4a8fb002a3ec86b2)

The plan for this complex migration is as follows :

![plan](https://github.com/user-attachments/assets/51c63ea6-fb0d-40b4-b99d-3e0b35a204e6)
Note: we will need to rename fields in the release 1.12 (there is no
easy way to do all this in one release)
2025-12-11 14:28:26 +01:00
Paul Rastoin 7b98804ec1 Fix flat field metadata date type (#16445)
# Introduction
Following https://github.com/twentyhq/twenty/pull/16420
2025-12-09 19:53:36 +01:00
Etienne 07cfaa78ef Fix unique standard field (#16371)
Fixes https://github.com/twentyhq/twenty/issues/15925

- update field metadata update logic
- uniformize the way index are named
- command to migrate v1-named unique index
- add integration testing

---------

Co-authored-by: prastoin <paul@twenty.com>
2025-12-08 18:05:28 +00:00
Paul Rastoin 28cdb02fbb Twenty standard application Objects and fields as allFlatEntityMaps ID non-agnostic (#16298)
# Introduction

Related to https://github.com/twentyhq/core-team-issues/issues/1995
This PR introduces the basis of the `twentyStandard` application as code
on demand, it's highly tied to `ids` where it will becomes workspace
agnostic following the builder and runner `universalIdentifier` refactor
later.

The goal here to allow computing the `allFlatEntityMaps` `to` of the
`twentyStandard` application on a empty workspace ( workspace creation
). Allowing installing the twenty standard app through a workspace
migration instead of passing by the sync metadata

Nothing done will be run in production for the moment if it's not the
small validation refactor we've introduced

Please note that everything introduced here will be replaced at some
point by a twenty app instance when the twenty sdk is mature enough to
handle of the edge cases we need here

## How we've proceeded
We've been iterating over every workspace entity both objects and their
fields, and transpiled them to flatEntity.
Being sure we migrate the defaultValue, settings and so on accordingly.
We've also compute all the ids in prior of the whole entities
computation so we don't face any hoisting issue.

## Current state
At the moment only handling all of the 29 standard objects and their
fields
Settings a unique universalIdentifier for all of them

Will come views, agent role targets and so on later

## `workspace:compute-twenty-standard-migration` command
This command allow generating a workspace migration that will result in
installing the twenty standard app in an empty workspace
It's temporary and aims to allow debugging for the moment we might not
keep it in the future as it is right now
It contains debug writeFileSync which is expected no worries greptile

## `LabelFieldMetadataIdentifierId`
Small refactor allowing defining the label identifier field metadata id
of a uuid field metadata type for system object, as some of our standard
object don't have a name field and don't aim to
Also please note that we might remove this build options later in the
sake of the currently installed universal identifier application that we
could compare with the deterministic twenty standard one

## `runFlatFieldMetadataValidators`
Deprecated this pattern which was redundant and not v2 friendly pattern

## Current errors that will address in upcoming PR
Current standard objects and fields metadata does not pass the
validation that we have in place, as historically the sync metadata
would directly consume the repositories and would just ignore the
validation. This is about to change.
Will handle the below errors in dedicated PRs as they will required
upgrade commands in order to migrate the data, or will handle that from
the sync metadata instead still to be determined but nothing critical
here

- camel case field metadata name
- options label invalid format

```json
{
  "status": "fail",
  "report": {
    "fieldMetadata": [
      {
        "status": "fail",
        "errors": [
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Name should be in camelCase",
            "userFriendlyMessage": {
              "id": "P+jdmX",
              "message": "Name should be in camelCase"
            },
            "value": "iCalUID"
          }
        ],
        "flatEntityMinimalInformation": {
          "id": "68dd83cd-92c8-4233-bb28-47939bab6124",
          "name": "iCalUID",
          "objectMetadataId": "11c16ab6-9176-439e-a2db-a12c5a58a524"
        },
        "type": "create_field"
      },
      {
        "status": "fail",
        "errors": [
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Value must be in UPPER_CASE and follow snake_case \"email\"",
            "userFriendlyMessage": {
              "id": "UBPzFQ",
              "message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
              "values": {
                "sanitizedValue": "email"
              }
            },
            "value": "email"
          },
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Value must be in UPPER_CASE and follow snake_case \"sms\"",
            "userFriendlyMessage": {
              "id": "UBPzFQ",
              "message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
              "values": {
                "sanitizedValue": "sms"
              }
            },
            "value": "sms"
          }
        ],
        "flatEntityMinimalInformation": {
          "id": "e3caaf2a-e07d-4146-8dfc-9eef904e82c9",
          "name": "type",
          "objectMetadataId": "4b777de5-4c7b-4af4-9b92-655c0f87512b"
        },
        "type": "create_field"
      },
      {
        "status": "fail",
        "errors": [
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Value must be in UPPER_CASE and follow snake_case \"incoming\"",
            "userFriendlyMessage": {
              "id": "UBPzFQ",
              "message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
              "values": {
                "sanitizedValue": "incoming"
              }
            },
            "value": "incoming"
          },
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Value must be in UPPER_CASE and follow snake_case \"outgoing\"",
            "userFriendlyMessage": {
              "id": "UBPzFQ",
              "message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
              "values": {
                "sanitizedValue": "outgoing"
              }
            },
            "value": "outgoing"
          }
        ],
        "flatEntityMinimalInformation": {
          "id": "d96233a4-93be-45ea-9548-3b50f3c700cf",
          "name": "direction",
          "objectMetadataId": "480a648a-d2e5-482a-992f-ef053e1b4bb0"
        },
        "type": "create_field"
      },
      {
        "status": "fail",
        "errors": [
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Value must be in UPPER_CASE and follow snake_case \"from\"",
            "userFriendlyMessage": {
              "id": "UBPzFQ",
              "message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
              "values": {
                "sanitizedValue": "from"
              }
            },
            "value": "from"
          },
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Value must be in UPPER_CASE and follow snake_case \"to\"",
            "userFriendlyMessage": {
              "id": "UBPzFQ",
              "message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
              "values": {
                "sanitizedValue": "to"
              }
            },
            "value": "to"
          },
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Value must be in UPPER_CASE and follow snake_case \"cc\"",
            "userFriendlyMessage": {
              "id": "UBPzFQ",
              "message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
              "values": {
                "sanitizedValue": "cc"
              }
            },
            "value": "cc"
          },
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Value must be in UPPER_CASE and follow snake_case \"bcc\"",
            "userFriendlyMessage": {
              "id": "UBPzFQ",
              "message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
              "values": {
                "sanitizedValue": "bcc"
              }
            },
            "value": "bcc"
          }
        ],
        "flatEntityMinimalInformation": {
          "id": "961c598e-67c3-452d-8bb2-b92c0bc64404",
          "name": "role",
          "objectMetadataId": "8af8a13c-ff97-4cd3-b70d-52a7dc2924b4"
        },
        "type": "create_field"
      },
      {
        "status": "fail",
        "errors": [
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Label must not contain a comma",
            "userFriendlyMessage": {
              "id": "k731jp",
              "message": "Label must not contain a comma"
            },
            "value": "Commas and dot (1,234.56)"
          },
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Label must not contain a comma",
            "userFriendlyMessage": {
              "id": "k731jp",
              "message": "Label must not contain a comma"
            },
            "value": "Spaces and comma (1 234,56)"
          },
          {
            "code": "INVALID_FIELD_INPUT",
            "message": "Label must not contain a comma",
            "userFriendlyMessage": {
              "id": "k731jp",
              "message": "Label must not contain a comma"
            },
            "value": "Dots and comma (1.234,56)"
          }
        ],
        "flatEntityMinimalInformation": {
          "id": "7fa20caf-2597-42e3-84e5-15a91b125b9b",
          "name": "numberFormat",
          "objectMetadataId": "a6974302-9e72-461c-aa09-9390f4ff16fc"
        },
        "type": "create_field"
      }
    ],
    "objectMetadata": [],
    "view": [],
    "viewField": [],
    "viewGroup": [],
    "index": [],
    "serverlessFunction": [],
    "cronTrigger": [],
    "databaseEventTrigger": [],
    "routeTrigger": [],
    "viewFilter": [],
    "role": [],
    "roleTarget": [],
    "agent": []
  }
}
```
2025-12-04 17:39:12 +01:00
Guillim 1a45576990 Morph-add-new-object-destination (#16027)
Add new object target to an existing morph relation (backend only)

Fixes https://github.com/twentyhq/core-team-issues/issues/1898

---------

Co-authored-by: prastoin <paul@twenty.com>
2025-11-27 11:10:13 +00:00
Paul Rastoin e5255df1a1 Fix and refactor relation field name collision validation (#15920)
# Introduction
Fixes https://github.com/twentyhq/private-issues/issues/371
We weren't strictly validating relation field collision on join column
name availability of the target field object

## Refactor
Extracted morph or relation specific condition out of the common flat
field metadata name validate availability to be located in the dedicated
morph or relation flat field validator

## Tests
Added two tests, ONE_TO_MANY and MANY_TO_ONE in order to cover the use
case
2025-11-20 15:10:35 +00:00
martmull 9880f192a5 Move composite types to twenty-shared (#15741) 2025-11-10 22:11:52 +01:00
martmull abde3c04ac 1630 extensibility twenty cli ability to create edit and delete fields (#15501)
As title

- adds decorators in twenty-sdk
- update twenty-cli load-manifest to it gets @FieldMetadata infos +
testing
- update twenty-server so it CRUD fields properly, using
universalIdentifier
- Fix UI so we can update managed objects records
- move FieldMetadata items from twenty-server to twenty-shared
2025-11-05 17:50:06 +01:00
Paul Rastoin 3514054235 V2 centralize relation optimistic logic (#15552)
# Introduction
This PR aims to deprecate having to manually handle optimistic side
effect foreign key addition in the whole v2 experience.
This PR implements the strong basis + builder refactor of the optimistic
computation of a given flat entity maps with its related flat entity
maps ( runner needs a small refactor on actions type definition first )
Flat entity maps updates through mutations are now only scoped to the
generic entity builder ( very isolated )

## What's next
- Refactor actions v2 type definition to gain grain over `metadataName`
and action operation ( `create` `delete` `update` ).
from `{type: 'create_view_field'}` to `{metadataName: 'view_field',
type: 'create' }`
- Use new optimistic tool computation tools
- Only invalidate impacted flat maps cache

## New tools
Strictly dynamically typed new flat entity maps tools
- `addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
-
`deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`

## Unit test
Adding basic unit testing coverage to introduced tools

## `FlatEntityValidationArgs`
From 
```ts
export type FlatEntityValidationArgs<T extends AllMetadataName> = {
  flatEntityToValidate: MetadataFlatEntity<T>;
  optimisticFlatEntityMaps: MetadataFlatEntityMaps<T>;
  mutableDependencyOptimisticFlatEntityMaps: MetadataValidationRelatedFlatEntityMaps<T>;
  workspaceId: string;
  remainingFlatEntityMapsToValidate: MetadataFlatEntityMaps<T>;
  buildOptions: WorkspaceMigrationBuilderOptions;
};

```

To
```ts
export type FlatEntityValidationArgs<T extends AllMetadataName> = {
  flatEntityToValidate: MetadataFlatEntity<T>;
  optimisticFlatEntityMapsAndRelatedFlatEntityMaps: MetadataFlatEntityAndRelatedFlatEntityMapsForValidation<T>;
  workspaceId: string;
  remainingFlatEntityMapsToValidate: MetadataFlatEntityMaps<T>;
  buildOptions: WorkspaceMigrationBuilderOptions;
};

```
2025-11-04 12:28:28 +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
Paul Rastoin 1bf40d9dca Fix v2 self relation field creation (#15382)
# Introduction
Fixing self relation field creation in v2
- When computing related flat field to delete on object metadata
deletion that has self relation fields
- On self relation creation validation name availability not searching
for the relation target field of the current object field if it's the
field being validated

## Coverage
- added CUD integration testing on self relation fields

close https://github.com/twentyhq/twenty/issues/15153
2025-10-27 16:07:58 +01: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
Félix Malfait e577c2d746 Update user friendly errors for translations (#15000)
Force msg typing instead of string for user friendly errors
2025-10-09 18:17:32 +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 23e21cbeea Label identifier validation v2 (#14867)
# Introduction
Adding a hacky way to validate object against fields before fields
validation ( bi-directional validation process )
If you encounter an identical setup we will add a specific devXp as
cleanup validation but for the moment this seems enough

close https://github.com/twentyhq/core-team-issues/issues/1639
2025-10-07 13:27:21 +02:00
Paul Rastoin 170f6d27c5 Extract field metadata build out of object metadata build (#14763)
# Introduction

Extracting the legacy fields build and dispatch out of the object one to
follow the generic flat entity build, also update caches entries for
object

## Main tasks
- flat field map cache
- flat field builder
- refactored the dispatch matrix to return flat entity maps instead of
flat entity arrays
- orchestrator aggregator
- removing legacy code
- making universal identifier aka standardId of standard field for
custom object deterministically dynamic
- perfs debug logs for v2

## TODO
- [x] Refactor the generic entity builder to be dependency flat maps in
order to main foreign keys list in flat parent
- [x] Refactor the flat object metadata to contain the array of related
fields and avoid costy find object fields
- [ ] Improve the create field handler to handle multiple field at once
- [ ] Refactor the dispatch to embbed the comparison
- [ ] Improve perf by extracting from elements out of existing
- [ ] Fix the labelIdentifierId validators on object before field
creation ( integ tests are in failing mode )

## Debug logs snippet

```ts
[EntityBuilder fieldMetadata] matrix computation: 0.027ms
[EntityBuilder fieldMetadata] creation validation: 0.001ms
[EntityBuilder fieldMetadata] deletion validation: 0.293ms
[EntityBuilder fieldMetadata] update validation: 0.006ms
[EntityBuilder fieldMetadata] entity processing: 0.363ms
[EntityBuilder fieldMetadata] validateAndBuild: 0.455ms
[EntityBuilder index] matrix computation: 0.005ms
[EntityBuilder index] creation validation: 0.001ms
[EntityBuilder index] deletion validation: 0.146ms
[EntityBuilder index] update validation: 0.004ms
[EntityBuilder index] entity processing: 0.199ms
[EntityBuilder index] validateAndBuild: 0.228ms
[Runner] Initial cache retrieval: 0.549ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_index executeForWorkspaceSchema: 11.665ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_index executeForMetadata: 12.864ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForWorkspaceSchema: 1.476ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForMetadata: 6.816ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForWorkspaceSchema: 0.062ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForMetadata: 0.889ms
[Runner] Transaction execution: 23.434ms
[Runner] Cache invalidation: 316.662ms
[Runner] Total execution: 340.767ms
```
As you can see cache invalidation is way to long, we could replace the
cache by the optimistic in the end
2025-10-02 16:07:34 +00:00
Paul Rastoin 4fbdfb6abc Activate v2 default seed (#14660)
## Introduction
After enabling flag by default got following errors:
```ts
Test Suites: 48 failed, 1 skipped, 97 passed, 145 of 146 total
Tests:       499 failed, 1 skipped, 644 passed, 1144 total
Snapshots:   61 failed, 133 passed, 194 total
Time:        363.226 s
Ran all test suites.
```

## From
<img width="2952" height="1510" alt="image"
src="https://github.com/user-attachments/assets/7e3b20c6-2552-40a7-90bb-2d7b3002c895"
/>

## To
<img width="3134" height="1510" alt="image"
src="https://github.com/user-attachments/assets/4fc9ada4-3c14-4333-a1db-11daf87db8d6"
/>

There's a huge test bundle in the latest shard that we could split up

## Notes
- Set as failing morph relation field rename as for the moment we do not
handle relation field mutation
- fixed the object update and creation validation adding label
identifier field metadata id checks
- and more

Some integrations tests are still on the v1 ( they have before and after
all disabling and re-enabling the flat ) but mainly we now have more
coverage on the v2 than the v1.
Mainly related records, uniqueness have to be migrated the v2 and so
tests too
2025-09-26 16:05:09 +02:00
Paul Rastoin 6522498df0 Failing morph relation creation v2 integration testing & fixes (#14253)
# Introduction
closes https://github.com/twentyhq/core-team-issues/issues/1408
2025-09-03 12:14:43 +02:00
Paul Rastoin 6d6ee939cf Morph relation field v2 fixes (#14216)
Fixes, depending on the contextual current operation in order to
retrieve relation target and all morph related fields to be delete:
- DELETE Field,
- DELETE Object, 
- UPDATE Field, that affects all others related fields
2025-09-02 12:17:20 +00:00
Paul Rastoin 7df9094939 RelationFieldMetadataType to MorphOrRelationFieldMetadataType and so on (#14160) 2025-08-29 15:25:12 +00:00
Paul Rastoin b364acf5bb Morph relation validation v2 (#14141)
## Introduction
Binding morph relation validation to relation validation code flow
Please note that ALL create field will go through basic atomic
validation, which involve name availability and so on

close https://github.com/twentyhq/core-team-issues/issues/1407
2025-08-28 16:05:58 +00:00
Paul Rastoin 7c481abf0c Handle input transpilation morph relation v2 (#14124)
# Introduction
From `morphRelationCreationPayload` to `flatFieldMetadatas`

close https://github.com/twentyhq/core-team-issues/issues/1406

## Unit testing
I'm not a fan of covering things this way, where we could have some
strong integration testing tests in the first place
But I wanted to freeze the optimistic path computation, also covered the
exception because it was cheap, doesn't mean I won't cover them through
integration tests later anw
2025-08-28 17:22:03 +02:00
Félix Malfait e264d7f32b fix: Enable Lingui recommended rules and fix all translation violations (#14133)
- Enable lingui/no-single-variables-to-translate and all other
recommended Lingui rules
- Fix single variable translation patterns (t`${variable}` → variable)
- Fix expression-in-message violations by extracting variables
- Fix t-call-in-function violations by moving translations inside
functions
- Update ESLint configs to use linguiPlugin.configs['flat/recommended']
- Clean up unused imports and improve translation patterns
2025-08-28 15:12:38 +02:00
Paul Rastoin 59d90bedaa Integration testing v2 enum field types fail and success path (#14010)
# Introduction
Migrating and improving performances of field enum integrations tests
success and failing tests cases to be using the new v2 api

## Discovered issue
When deleting an object in v1 it will leave related enums until the
object is re-created
Something not done anymore within the create in v2 but in the delete
operation
We should implem an upgrade command to remove such relicas

## Bugs
- Update/create default value multi select runner wrong sql query -> FIX
- Update default value multi select regression, we should allow option
without an id to be inserted -> FIX
- default value compare dynamic json stringify convertion or not in
compare tools for object and fields
2025-08-28 10:23:04 +00:00
Paul Rastoin e99c67d6b5 Fail slow + structured validator error response scoped by object and fields (#13980)
# Introduction
Example of returned response: ( snapshot copy don't mind jest expect any
values )
```json
{
  "extensions": {
    "code": "BAD_USER_INPUT",
    "errors": {
      "fieldMetadata": [
        {
          "errors": [
            {
              "code": "INVALID_FIELD_INPUT",
              "message": "Name is too long",
              "userFriendlyMessage": "Name is too long",
              "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            },
          ],
          "id": Any<String>,
          "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "objectMetadataId": Any<String>,
          "operation": "create_field",
        },
        {
          "errors": [
            {
              "code": "INVALID_FIELD_INPUT",
              "message": "Name is too long",
              "userFriendlyMessage": "Name is too long",
              "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            },
          ],
          "id": Any<String>,
          "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "objectMetadataId": Any<String>,
          "operation": "create_field",
        },
        {
          "errors": [
            {
              "code": "INVALID_FIELD_INPUT",
              "message": "Name is too long",
              "userFriendlyMessage": "Name is too long",
              "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            },
          ],
          "id": Any<String>,
          "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "objectMetadataId": Any<String>,
          "operation": "create_field",
        },
        {
          "errors": [
            {
              "code": "INVALID_FIELD_INPUT",
              "message": "Name is too long",
              "userFriendlyMessage": "Name is too long",
              "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            },
          ],
          "id": Any<String>,
          "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "objectMetadataId": Any<String>,
          "operation": "create_field",
        },
        {
          "errors": [
            {
              "code": "INVALID_FIELD_INPUT",
              "message": "Name is too long",
              "userFriendlyMessage": "Name is too long",
              "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            },
          ],
          "id": Any<String>,
          "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "objectMetadataId": Any<String>,
          "operation": "create_field",
        },
      ],
      "objectMetadata": [
        {
          "errors": [
            {
              "code": "INVALID_OBJECT_INPUT",
              "message": "Name is too long",
              "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            },
          ],
          "fields": [],
          "id": Any<String>,
          "namePlural": "listingas",
          "nameSingular": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "operation": "create_object",
        },
      ],
    },
    "message": "Validation failed for 1 object(s) and 5 field(s)",
    "summary": {
      "invalidFields": 5,
      "invalidObjects": 1,
      "totalErrors": 6,
    },
    "userFriendlyMessage": "Validation failed for 1 object(s) and 5 field(s)",
  },
  "message": "Multiple validation errors occurred while creating object",
  "name": "GraphQLError",
}
```

## Note
We should put in place integrations tests on REST API too, in order to
ensure we receive the same response error
2025-08-20 17:33:15 +00:00
Paul Rastoin ad77d2f462 ObjectMetadataServiceV2.updateOne (#13891)
# Introduction
Last service to get migrated, `updateOne` object metadata service v2
2025-08-14 09:37:52 +00:00