Solo transaction application synchronization service refactor (#17864)

# Introduction
Refactoring the application sync service to be making a single validate
build and run transaction instead of calling all the services n times
thanks to the universal workspace migration refactor that allow doing so

## What's next
Migrating all below entities to by syncableEntities so they can be
universalised too ( right now they're still calling the services n times
and won't be returned in the workspace migration )
-
[objectPermission](https://github.com/twentyhq/core-team-issues/issues/2223)
-
[fieldPermission](https://github.com/twentyhq/core-team-issues/issues/2224)
-
[permissionFlag](https://github.com/twentyhq/core-team-issues/issues/2225)
This commit is contained in:
Paul Rastoin
2026-02-11 17:30:01 +01:00
committed by GitHub
parent 0ee63a0525
commit b2f7c745f8
27 changed files with 848 additions and 1127 deletions
@@ -1,17 +1,16 @@
import {
type FieldMetadataType,
type FieldMetadataSettings,
type FieldMetadataOptions,
type FieldMetadataDefaultValue,
} from '@/types';
import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType';
import { type RelationOnDeleteAction } from '@/types/RelationOnDeleteAction.type';
import { type RelationType } from '@/types/RelationType';
import {
type FieldMetadataDefaultValue,
type FieldMetadataOptions,
type FieldMetadataType,
type FieldMetadataUniversalSettings,
type RelationAndMorphRelationFieldMetadataType,
} from '@/types';
export type RegularFieldManifest<
T extends FieldMetadataType = Exclude<
FieldMetadataType,
FieldMetadataType.RELATION
RelationAndMorphRelationFieldMetadataType
>,
> = SyncableEntityOptions & {
type: T;
@@ -21,23 +20,24 @@ export type RegularFieldManifest<
icon?: string;
defaultValue?: FieldMetadataDefaultValue<T>;
options?: FieldMetadataOptions<T>;
settings?: FieldMetadataSettings<T>;
universalSettings?: FieldMetadataUniversalSettings<T>;
isNullable?: boolean;
objectUniversalIdentifier: string;
};
export type RelationFieldManifest =
RegularFieldManifest<FieldMetadataType.RELATION> & {
relationType: RelationType;
targetObjectUniversalIdentifier: string;
targetFieldLabel: string;
targetFieldIcon?: string;
onDelete?: RelationOnDeleteAction;
};
// Both sides of a relation must be declared explicitly in the manifest.
// Relation-specific universal settings (relationType, onDelete, joinColumnName)
// are provided through the `universalSettings` field.
export type RelationFieldManifest<
T extends RelationAndMorphRelationFieldMetadataType = RelationAndMorphRelationFieldMetadataType,
> = RegularFieldManifest<T> & {
relationTargetFieldMetadataUniversalIdentifier: string;
relationTargetObjectMetadataUniversalIdentifier: string;
};
export type FieldManifest<
T extends FieldMetadataType = Exclude<
FieldMetadataType,
FieldMetadataType.RELATION
RelationAndMorphRelationFieldMetadataType
>,
> = RegularFieldManifest<T> | RelationFieldManifest;
@@ -36,5 +36,9 @@ export type {
export type { Manifest } from './manifestType';
export type { ObjectFieldManifest } from './objectFieldManifest.type';
export type { ObjectManifest } from './objectManifestType';
export type { RoleManifest } from './roleManifestType';
export type {
ObjectPermissionManifest,
FieldPermissionManifest,
RoleManifest,
} from './roleManifestType';
export type { SyncableEntityOptions } from './syncableEntityOptionsType';
@@ -1,7 +1,7 @@
import { type PermissionFlagType } from '@/constants';
import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType';
type ObjectPermission = {
export type ObjectPermissionManifest = {
objectUniversalIdentifier: string;
canReadObjectRecords?: boolean;
canUpdateObjectRecords?: boolean;
@@ -9,7 +9,7 @@ type ObjectPermission = {
canDestroyObjectRecords?: boolean;
};
type FieldPermission = {
export type FieldPermissionManifest = {
objectUniversalIdentifier: string;
fieldUniversalIdentifier: string;
canReadFieldValue?: boolean;
@@ -29,7 +29,7 @@ export type RoleManifest = SyncableEntityOptions & {
canBeAssignedToUsers?: boolean;
canBeAssignedToAgents?: boolean;
canBeAssignedToApiKeys?: boolean;
objectPermissions?: ObjectPermission[];
fieldPermissions?: FieldPermission[];
objectPermissions?: ObjectPermissionManifest[];
fieldPermissions?: FieldPermissionManifest[];
permissionFlags?: PermissionFlagType[];
};
@@ -0,0 +1,7 @@
import { type FieldMetadataType } from '@/types/FieldMetadataType';
import { type FieldMetadataSettings } from '@/types/FieldMetadataSettings';
import { type FormatRecordSerializedRelationProperties } from '@/types/FormatRecordSerializedRelationProperties.type';
export type FieldMetadataUniversalSettings<
T extends FieldMetadataType = FieldMetadataType,
> = FormatRecordSerializedRelationProperties<FieldMetadataSettings<T>>;
@@ -0,0 +1,38 @@
import { type ExtractSerializedRelationProperties } from '@/types/ExtractSerializedRelationProperties.type';
import { type IsSerializedRelation } from '@/types/IsSerializedRelation.type';
import { type RemoveSuffix } from '@/types/RemoveSuffix.type';
// Determines if a property should be transformed to a universal identifier
// A property is transformed only if:
// 1. It matches ExtractSerializedRelationProperties (has the serialized relation brand)
// 2. Its value does NOT have a string index signature (not a Record<string, X>)
type ShouldTransformToUniversalIdentifier<T, P extends keyof T> = [P] extends [
ExtractSerializedRelationProperties<T>,
]
? string extends keyof NonNullable<T[P]>
? false
: true
: false;
export type FormatRecordSerializedRelationProperties<T> = T extends unknown
? T extends (infer U)[]
? FormatRecordSerializedRelationProperties<U>[]
: T extends string
? // By definition we assume that any SerializedRelation are not enforced at pg scope through an FK
// Which mean that SerializedRelation might resolve to non-existent entities ( null )
IsSerializedRelation<T> extends true
? T | null
: T
: T extends object
? {
[P in keyof T as ShouldTransformToUniversalIdentifier<
T,
P
> extends true
? P extends string
? `${RemoveSuffix<P, 'Id'>}UniversalIdentifier`
: P
: P]: FormatRecordSerializedRelationProperties<T[P]>;
}
: T
: never;
@@ -0,0 +1,4 @@
export type RemoveSuffix<
T extends string,
P extends string,
> = T extends `${infer Prefix}${P}` ? Prefix : T;
@@ -94,6 +94,7 @@ export type {
} from './FieldMetadataSettings';
export { NumberDataType, DateDisplayFormat } from './FieldMetadataSettings';
export { FieldMetadataType } from './FieldMetadataType';
export type { FieldMetadataUniversalSettings } from './FieldMetadataUniversalSettings';
export type { FieldRatingValue } from './FieldRatingValue';
export type { FileCategory } from './FileCategory';
export { FILE_CATEGORIES } from './FileCategory';
@@ -104,6 +105,7 @@ export type {
} from './FilterableFieldType';
export { FILTERABLE_FIELD_TYPES } from './FilterableFieldType';
export { FirstDayOfTheWeek } from './FirstDayOfTheWeek';
export type { FormatRecordSerializedRelationProperties } from './FormatRecordSerializedRelationProperties.type';
export type { FromTo } from './FromToType';
export { HTTPMethod } from './HttpMethod';
export type { IsEmptyObject } from './IsEmptyObject.type';
@@ -190,6 +192,7 @@ export type { RelationCreationPayload } from './RelationCreationPayload';
export { RelationOnDeleteAction } from './RelationOnDeleteAction.type';
export { RelationType } from './RelationType';
export type { RelationUpdatePayload } from './RelationUpdatePayload';
export type { RemoveSuffix } from './RemoveSuffix.type';
export type { RestrictedFieldPermissions } from './RestrictedFieldPermissions';
export type { RestrictedFieldsPermissions } from './RestrictedFieldsPermissions';
export type { RowLevelPermissionPredicate } from './RowLevelPermissionPredicate';