8c951d3623
# 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>
134 lines
4.6 KiB
TypeScript
134 lines
4.6 KiB
TypeScript
import { t } from '@lingui/core/macro';
|
|
import { type AllMetadataName } from 'twenty-shared/metadata';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
|
|
import {
|
|
ALL_METADATA_RELATIONS,
|
|
type MetadataManyToOneRelationConfiguration,
|
|
} from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant';
|
|
import {
|
|
FlatEntityMapsException,
|
|
FlatEntityMapsExceptionCode,
|
|
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
|
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
|
import { type ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.type';
|
|
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
|
|
import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type';
|
|
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
|
|
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
|
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/remove-suffix.type';
|
|
|
|
type ManyToOneConfig<T extends AllMetadataName> =
|
|
(typeof ALL_METADATA_RELATIONS)[T]['manyToOne'];
|
|
|
|
type TargetMetadataNamesForForeignKeys<
|
|
T extends AllMetadataName,
|
|
TProvidedKeys extends string,
|
|
> = {
|
|
[K in keyof ManyToOneConfig<T>]: ManyToOneConfig<T>[K] extends {
|
|
foreignKey: infer _FK extends TProvidedKeys;
|
|
metadataName: infer MN extends AllMetadataName;
|
|
}
|
|
? MN
|
|
: never;
|
|
}[keyof ManyToOneConfig<T>];
|
|
|
|
type RequiredFlatEntityMapsForForeignKeys<
|
|
T extends AllMetadataName,
|
|
TProvidedKeys extends string,
|
|
> = Pick<
|
|
AllFlatEntityMaps,
|
|
MetadataToFlatEntityMapsKey<
|
|
TargetMetadataNamesForForeignKeys<T, TProvidedKeys>
|
|
>
|
|
>;
|
|
|
|
type ResolvedUniversalIdentifiers<
|
|
T extends AllMetadataName,
|
|
TProvidedKeys extends Extract<MetadataManyToOneJoinColumn<T>, string>,
|
|
> = {
|
|
[K in keyof ManyToOneConfig<T> as ManyToOneConfig<T>[K] extends {
|
|
foreignKey: infer FK extends string;
|
|
}
|
|
? FK extends TProvidedKeys
|
|
? `${RemoveSuffix<FK, 'Id'>}UniversalIdentifier`
|
|
: never
|
|
: never]: ManyToOneConfig<T>[K] extends { isNullable: true }
|
|
? string | null
|
|
: string;
|
|
};
|
|
|
|
export const resolveEntityRelationUniversalIdentifiers = <
|
|
T extends AllMetadataName,
|
|
TProvidedKeys extends Extract<
|
|
MetadataManyToOneJoinColumn<T>,
|
|
string
|
|
> = Extract<MetadataManyToOneJoinColumn<T>, string>,
|
|
>({
|
|
metadataName,
|
|
foreignKeyValues,
|
|
flatEntityMaps,
|
|
}: {
|
|
metadataName: T;
|
|
foreignKeyValues: Record<TProvidedKeys, string | null | undefined>;
|
|
flatEntityMaps: RequiredFlatEntityMapsForForeignKeys<T, TProvidedKeys>;
|
|
}): ResolvedUniversalIdentifiers<T, TProvidedKeys> => {
|
|
const relations = ALL_METADATA_RELATIONS[metadataName].manyToOne;
|
|
const result: Record<string, string | null> = {};
|
|
|
|
for (const relation of Object.values(
|
|
relations,
|
|
) as MetadataManyToOneRelationConfiguration<
|
|
T,
|
|
ExtractEntityManyToOneEntityRelationProperties<MetadataEntity<T>>
|
|
>[]) {
|
|
if (!isDefined(relation)) {
|
|
continue;
|
|
}
|
|
|
|
const {
|
|
foreignKey,
|
|
metadataName: targetMetadataName,
|
|
isNullable,
|
|
} = relation;
|
|
|
|
if (!Object.prototype.hasOwnProperty.call(foreignKeyValues, foreignKey)) {
|
|
continue;
|
|
}
|
|
|
|
const foreignKeyValue =
|
|
foreignKeyValues[foreignKey as keyof typeof foreignKeyValues];
|
|
|
|
const mapsKey = getMetadataFlatEntityMapsKey(
|
|
targetMetadataName,
|
|
) as keyof RequiredFlatEntityMapsForForeignKeys<T, TProvidedKeys>;
|
|
const targetFlatEntityMaps = flatEntityMaps[mapsKey];
|
|
|
|
// TODO refactor using the new ALL_METADATA_UNIVERSAL_RELATION afterwards
|
|
const universalIdentifierKey = foreignKey.replace(
|
|
/Id$/,
|
|
'UniversalIdentifier',
|
|
);
|
|
|
|
if (isNullable && !isDefined(foreignKeyValue)) {
|
|
result[universalIdentifierKey] = null;
|
|
|
|
continue;
|
|
}
|
|
|
|
const resolvedUniversalIdentifier =
|
|
targetFlatEntityMaps.universalIdentifierById[foreignKeyValue as string];
|
|
|
|
if (!isDefined(resolvedUniversalIdentifier)) {
|
|
throw new FlatEntityMapsException(
|
|
t`Could not find ${targetMetadataName} for given ${foreignKey}`,
|
|
FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
result[universalIdentifierKey] = resolvedUniversalIdentifier;
|
|
}
|
|
|
|
return result as ResolvedUniversalIdentifiers<T, TProvidedKeys>;
|
|
};
|