diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/decorators/__tests__/was-introduced-in-upgrade.decorator.spec.ts b/packages/twenty-server/src/engine/core-modules/upgrade/decorators/__tests__/was-introduced-in-upgrade.decorator.spec.ts deleted file mode 100644 index ac2fcc859c..0000000000 --- a/packages/twenty-server/src/engine/core-modules/upgrade/decorators/__tests__/was-introduced-in-upgrade.decorator.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - WasIntroducedInUpgrade, - getWasIntroducedInUpgradeClassMetadata, - getWasIntroducedInUpgradePropertyMetadata, -} from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator'; - -describe('WasIntroducedInUpgrade', () => { - it('records class-level entity introduction metadata', () => { - @WasIntroducedInUpgrade({ upgradeCommandName: 'upgrade-step-class' }) - class Example {} - - expect(getWasIntroducedInUpgradeClassMetadata(Example)).toEqual({ - upgradeCommandName: 'upgrade-step-class', - }); - expect(getWasIntroducedInUpgradePropertyMetadata(Example)).toEqual({}); - }); - - it('records property-level metadata keyed by property name', () => { - class Example { - @WasIntroducedInUpgrade({ upgradeCommandName: 'upgrade-step-foo' }) - foo!: string; - - @WasIntroducedInUpgrade({ upgradeCommandName: 'upgrade-step-bar' }) - bar!: string; - - untouched!: string; - } - - expect(getWasIntroducedInUpgradePropertyMetadata(Example)).toEqual({ - foo: { upgradeCommandName: 'upgrade-step-foo' }, - bar: { upgradeCommandName: 'upgrade-step-bar' }, - }); - expect(getWasIntroducedInUpgradeClassMetadata(Example)).toBeUndefined(); - }); - - describe('class-level properties form (for inherited columns)', () => { - it('registers the listed properties in the property-introduction map', () => { - @WasIntroducedInUpgrade({ - upgradeCommandName: 'upgrade-step-columns', - properties: ['inheritedA', 'inheritedB'], - }) - class Example {} - - expect(getWasIntroducedInUpgradePropertyMetadata(Example)).toEqual({ - inheritedA: { upgradeCommandName: 'upgrade-step-columns' }, - inheritedB: { upgradeCommandName: 'upgrade-step-columns' }, - }); - }); - - it('does not mark the entity itself as introduced', () => { - @WasIntroducedInUpgrade({ - upgradeCommandName: 'upgrade-step-columns', - properties: ['inheritedA'], - }) - class Example {} - - expect(getWasIntroducedInUpgradeClassMetadata(Example)).toBeUndefined(); - }); - - it('merges with property-level decorators on the same entity', () => { - @WasIntroducedInUpgrade({ - upgradeCommandName: 'upgrade-step-inherited', - properties: ['inheritedA'], - }) - class Example { - @WasIntroducedInUpgrade({ upgradeCommandName: 'upgrade-step-local' }) - localColumn!: string; - } - - expect(getWasIntroducedInUpgradePropertyMetadata(Example)).toEqual({ - localColumn: { upgradeCommandName: 'upgrade-step-local' }, - inheritedA: { upgradeCommandName: 'upgrade-step-inherited' }, - }); - }); - }); - - it('returns an empty map for classes with no decorated properties', () => { - class Example {} - - expect(getWasIntroducedInUpgradePropertyMetadata(Example)).toEqual({}); - expect(getWasIntroducedInUpgradeClassMetadata(Example)).toBeUndefined(); - }); -}); diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator.ts b/packages/twenty-server/src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator.ts index 58e74fde52..1631391cb9 100644 --- a/packages/twenty-server/src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator.ts +++ b/packages/twenty-server/src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator.ts @@ -1,19 +1,9 @@ import 'reflect-metadata'; -import { isDefined } from 'twenty-shared/utils'; - import { defineUpgradeMetadataOnClassOrProperty } from 'src/engine/core-modules/upgrade/decorators/upgrade-decorator-metadata.util'; export type WasIntroducedInUpgradeOptions = { upgradeCommandName: string; - // Class-level escape hatch for columns inherited from a base entity (e.g. - // SyncableEntity.universalIdentifier) that cannot carry a property decorator - // in place - properties?: readonly string[]; -}; - -export type WasIntroducedInUpgradeClassMetadata = { - upgradeCommandName: string; }; export const WAS_INTRODUCED_IN_UPGRADE_CLASS_METADATA_KEY = @@ -24,64 +14,24 @@ export const WAS_INTRODUCED_IN_UPGRADE_PROPERTIES_METADATA_KEY = export type WasIntroducedInUpgradePropertyMap = Record< string, - WasIntroducedInUpgradeClassMetadata + WasIntroducedInUpgradeOptions >; export const WasIntroducedInUpgrade = (options: WasIntroducedInUpgradeOptions) => (target: object, propertyKey?: string | symbol): void => { - const value: WasIntroducedInUpgradeClassMetadata = { - upgradeCommandName: options.upgradeCommandName, - }; - - if (!isDefined(propertyKey) && isDefined(options.properties)) { - defineIntroducedPropertiesOnClass({ - entityClass: target as Function, - propertyNames: options.properties, - value, - }); - - return; - } - defineUpgradeMetadataOnClassOrProperty({ classMetadataKey: WAS_INTRODUCED_IN_UPGRADE_CLASS_METADATA_KEY, propertyMetadataKey: WAS_INTRODUCED_IN_UPGRADE_PROPERTIES_METADATA_KEY, - value, + value: options, target, propertyKey, }); }; -const defineIntroducedPropertiesOnClass = ({ - entityClass, - propertyNames, - value, -}: { - entityClass: Function; - propertyNames: readonly string[]; - value: WasIntroducedInUpgradeClassMetadata; -}): void => { - const existing: WasIntroducedInUpgradePropertyMap = - Reflect.getMetadata( - WAS_INTRODUCED_IN_UPGRADE_PROPERTIES_METADATA_KEY, - entityClass, - ) ?? {}; - - const additions: WasIntroducedInUpgradePropertyMap = Object.fromEntries( - propertyNames.map((propertyName) => [propertyName, value]), - ); - - Reflect.defineMetadata( - WAS_INTRODUCED_IN_UPGRADE_PROPERTIES_METADATA_KEY, - { ...existing, ...additions }, - entityClass, - ); -}; - export const getWasIntroducedInUpgradeClassMetadata = ( target: Function, -): WasIntroducedInUpgradeClassMetadata | undefined => +): WasIntroducedInUpgradeOptions | undefined => Reflect.getMetadata(WAS_INTRODUCED_IN_UPGRADE_CLASS_METADATA_KEY, target); export const getWasIntroducedInUpgradePropertyMetadata = ( diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/utils/__tests__/resolve-entity-shape-at-upgrade-cursor.util.spec.ts b/packages/twenty-server/src/engine/core-modules/upgrade/utils/__tests__/resolve-entity-shape-at-upgrade-cursor.util.spec.ts index c992c65961..9fb1a2bd97 100644 --- a/packages/twenty-server/src/engine/core-modules/upgrade/utils/__tests__/resolve-entity-shape-at-upgrade-cursor.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/upgrade/utils/__tests__/resolve-entity-shape-at-upgrade-cursor.util.spec.ts @@ -169,58 +169,6 @@ describe('resolveEntityShapeAtUpgradeCursor', () => { }); }); - describe('class-level properties form (inherited columns)', () => { - @WasIntroducedInUpgrade({ - upgradeCommandName: PROP_INTRODUCE_CMD, - properties: ['inheritedColumn', 'localColumn'], - }) - class EntityWithInheritedColumns {} - - const currentColumns = [ - { propertyName: 'inheritedColumn', databaseName: 'inheritedColumn' }, - { propertyName: 'localColumn', databaseName: 'localColumn' }, - { propertyName: 'baselineColumn', databaseName: 'baselineColumn' }, - ]; - - it('hides the listed inherited columns before introduction applied', () => { - const result = resolveEntityShapeAtUpgradeCursor({ - entityClass: EntityWithInheritedColumns, - currentTableName: 'entityWithInheritedColumns', - currentColumns, - isStepApplied: buildPredicate([]), - }); - - expect(result.isAvailable).toBe(true); - expect(result.hiddenPropertyNames).toEqual( - new Set(['inheritedColumn', 'localColumn']), - ); - }); - - it('exposes the listed columns once introduction applied', () => { - const result = resolveEntityShapeAtUpgradeCursor({ - entityClass: EntityWithInheritedColumns, - currentTableName: 'entityWithInheritedColumns', - currentColumns, - isStepApplied: buildPredicate([PROP_INTRODUCE_CMD]), - }); - - expect(result.hiddenPropertyNames.size).toBe(0); - }); - - it('never hides undecorated baseline columns', () => { - for (const applied of [[], [PROP_INTRODUCE_CMD]] as string[][]) { - const result = resolveEntityShapeAtUpgradeCursor({ - entityClass: EntityWithInheritedColumns, - currentTableName: 'entityWithInheritedColumns', - currentColumns, - isStepApplied: buildPredicate(applied), - }); - - expect(result.hiddenPropertyNames.has('baselineColumn')).toBe(false); - } - }); - }); - describe('property-level @WasRemovedInUpgrade', () => { class EntityWithRemovedColumn { @WasRemovedInUpgrade({ upgradeCommandName: PROP_REMOVE_CMD }) diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/utils/__tests__/validate-upgrade-aware-entity-decorators.util.spec.ts b/packages/twenty-server/src/engine/core-modules/upgrade/utils/__tests__/validate-upgrade-aware-entity-decorators.util.spec.ts index 896b7d1975..1c20e9f730 100644 --- a/packages/twenty-server/src/engine/core-modules/upgrade/utils/__tests__/validate-upgrade-aware-entity-decorators.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/upgrade/utils/__tests__/validate-upgrade-aware-entity-decorators.util.spec.ts @@ -115,92 +115,6 @@ describe('validateUpgradeAwareEntityDecorators', () => { expect(problems).toEqual([]); }); - describe('column property name validation', () => { - it('reports an introduced property name that does not exist on the entity', () => { - @WasIntroducedInUpgrade({ - upgradeCommandName: KNOWN_CMD, - properties: ['universalIdentifier', 'typoColumn'], - }) - class EntityWithTypo {} - - const problems = validateUpgradeAwareEntityDecorators({ - entityClasses: [EntityWithTypo], - stepNameToIndex: buildStepNameToIndex([KNOWN_CMD]), - columnPropertyNamesByEntityClass: new Map([ - [EntityWithTypo, new Set(['universalIdentifier', 'position'])], - ]), - }); - - expect(problems).toEqual([ - { - kind: 'unknown-property-name', - entityName: 'EntityWithTypo', - decorator: '@WasIntroducedInUpgrade', - propertyName: 'typoColumn', - }, - ]); - }); - - it('reports a relation property name (resolution only acts on columns)', () => { - @WasIntroducedInUpgrade({ - upgradeCommandName: KNOWN_CMD, - properties: ['application'], - }) - class EntityTargetingRelation {} - - const problems = validateUpgradeAwareEntityDecorators({ - entityClasses: [EntityTargetingRelation], - stepNameToIndex: buildStepNameToIndex([KNOWN_CMD]), - // 'application' is a relation, not a column, so it is not in the set. - columnPropertyNamesByEntityClass: new Map([ - [EntityTargetingRelation, new Set(['applicationId'])], - ]), - }); - - expect(problems).toEqual([ - { - kind: 'unknown-property-name', - entityName: 'EntityTargetingRelation', - decorator: '@WasIntroducedInUpgrade', - propertyName: 'application', - }, - ]); - }); - - it('reports no problem when all introduced property names are columns', () => { - @WasIntroducedInUpgrade({ - upgradeCommandName: KNOWN_CMD, - properties: ['universalIdentifier', 'position'], - }) - class ValidEntity {} - - const problems = validateUpgradeAwareEntityDecorators({ - entityClasses: [ValidEntity], - stepNameToIndex: buildStepNameToIndex([KNOWN_CMD]), - columnPropertyNamesByEntityClass: new Map([ - [ValidEntity, new Set(['universalIdentifier', 'position'])], - ]), - }); - - expect(problems).toEqual([]); - }); - - it('skips property name validation when no column property names are provided', () => { - @WasIntroducedInUpgrade({ - upgradeCommandName: KNOWN_CMD, - properties: ['anythingGoes'], - }) - class UnvalidatedEntity {} - - const problems = validateUpgradeAwareEntityDecorators({ - entityClasses: [UnvalidatedEntity], - stepNameToIndex: buildStepNameToIndex([KNOWN_CMD]), - }); - - expect(problems).toEqual([]); - }); - }); - it('should report a rename history that is out of order versus the sequence', () => { @WasRenamedInUpgrade([ { previousName: 'first', upgradeCommandName: KNOWN_LATER_RENAME_CMD }, diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/utils/validate-upgrade-aware-entity-decorators.util.ts b/packages/twenty-server/src/engine/core-modules/upgrade/utils/validate-upgrade-aware-entity-decorators.util.ts index 7fe29d3a0b..4ee9508167 100644 --- a/packages/twenty-server/src/engine/core-modules/upgrade/utils/validate-upgrade-aware-entity-decorators.util.ts +++ b/packages/twenty-server/src/engine/core-modules/upgrade/utils/validate-upgrade-aware-entity-decorators.util.ts @@ -38,29 +38,19 @@ export type UpgradeAwareDecoratorReferenceProblem = scope: 'class' | `property:${string}`; introductionUpgradeCommandName: string; removalUpgradeCommandName: string; - } - | { - kind: 'unknown-property-name'; - entityName: string; - decorator: '@WasIntroducedInUpgrade'; - propertyName: string; }; export const validateUpgradeAwareEntityDecorators = ({ entityClasses, stepNameToIndex, - columnPropertyNamesByEntityClass, }: { entityClasses: Function[]; stepNameToIndex: ReadonlyMap; - columnPropertyNamesByEntityClass?: ReadonlyMap>; }): UpgradeAwareDecoratorReferenceProblem[] => { const problems: UpgradeAwareDecoratorReferenceProblem[] = []; for (const entityClass of entityClasses) { const entityName = entityClass.name; - const columnPropertyNames = - columnPropertyNamesByEntityClass?.get(entityClass); const classIntroduced = getWasIntroducedInUpgradeClassMetadata(entityClass); @@ -125,18 +115,6 @@ export const validateUpgradeAwareEntityDecorators = ({ upgradeCommandName: options.upgradeCommandName, }); } - - if ( - isDefined(columnPropertyNames) && - !columnPropertyNames.has(propertyName) - ) { - problems.push({ - kind: 'unknown-property-name', - entityName, - decorator: '@WasIntroducedInUpgrade', - propertyName, - }); - } } const propRemovedMap = getWasRemovedInUpgradePropertyMetadata(entityClass); @@ -273,10 +251,6 @@ export const formatUpgradeAwareDecoratorReferenceProblems = ( return ` - ${problem.entityName} @WasRenamedInUpgrade (${problem.scope}): "${problem.offendingUpgradeCommandName}" must come after "${problem.precedingUpgradeCommandName}" in the upgrade sequence`; } - if (problem.kind === 'unknown-property-name') { - return ` - ${problem.entityName} ${problem.decorator} (property:${problem.propertyName}): "${problem.propertyName}" is not a known property on the entity (check the spelling in the properties array)`; - } - return ` - ${problem.entityName} @WasRemovedInUpgrade (${problem.scope}): removal step "${problem.removalUpgradeCommandName}" must come after introduction step "${problem.introductionUpgradeCommandName}" in the upgrade sequence`; }) .join('\n'); diff --git a/packages/twenty-server/src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity.ts index ea6a12d814..3145e99fb3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity.ts @@ -21,7 +21,6 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti @WasIntroducedInUpgrade({ upgradeCommandName: ADD_UNIVERSAL_IDENTIFIER_AND_APPLICATION_ID_TO_SEARCH_FIELD_METADATA_UPGRADE_COMMAND_NAME, - properties: ['universalIdentifier', 'applicationId', 'position'], }) @Unique('IDX_SEARCH_FIELD_METADATA_OBJECT_FIELD_UNIQUE', [ 'objectMetadataId', diff --git a/packages/twenty-server/src/engine/twenty-orm/upgrade-aware/upgrade-aware-entity-metadata.adapter.ts b/packages/twenty-server/src/engine/twenty-orm/upgrade-aware/upgrade-aware-entity-metadata.adapter.ts index ff09bc5a10..88cee8685f 100644 --- a/packages/twenty-server/src/engine/twenty-orm/upgrade-aware/upgrade-aware-entity-metadata.adapter.ts +++ b/packages/twenty-server/src/engine/twenty-orm/upgrade-aware/upgrade-aware-entity-metadata.adapter.ts @@ -4,8 +4,8 @@ import { InjectDataSource } from '@nestjs/typeorm'; import { type ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'; import { type EntityMetadata } from 'typeorm/metadata/EntityMetadata'; -import { isDefined } from 'twenty-shared/utils'; import { DataSource } from 'typeorm'; +import { isDefined } from 'twenty-shared/utils'; import { UpgradeMigrationService } from 'src/engine/core-modules/upgrade/services/upgrade-migration.service'; import { UpgradeSequenceReaderService } from 'src/engine/core-modules/upgrade/services/upgrade-sequence-reader.service'; @@ -346,28 +346,13 @@ export class UpgradeAwareEntityMetadataAdapter implements OnModuleInit { } private validateDecoratorsAgainstSequence(): void { - const entityClasses: Function[] = []; - const columnPropertyNamesByEntityClass = new Map< - Function, - ReadonlySet - >(); - - for (const metadata of this.coreDataSource.entityMetadatas) { - if (typeof metadata.target !== 'function') { - continue; - } - - entityClasses.push(metadata.target); - columnPropertyNamesByEntityClass.set( - metadata.target, - new Set(metadata.columns.map((column) => column.propertyName)), - ); - } + const entityClasses = this.coreDataSource.entityMetadatas + .map((metadata) => metadata.target) + .filter((target): target is Function => typeof target === 'function'); const problems = validateUpgradeAwareEntityDecorators({ entityClasses, stepNameToIndex: this.stepNameToIndex, - columnPropertyNamesByEntityClass, }); if (problems.length === 0) {