diff --git a/packages/agent/src/builder/agent.ts b/packages/agent/src/builder/agent.ts index dd513b4cfd..e5a5bd4c5c 100644 --- a/packages/agent/src/builder/agent.ts +++ b/packages/agent/src/builder/agent.ts @@ -2,9 +2,8 @@ import { BaseDataSource, ChartDefinition, Collection, - DataSourceDecorator, DataSourceFactory, - RenameCollectionCollectionDecorator, + RenameCollectionDataSourceDecorator, TCollectionName, TSchema, } from '@forestadmin/datasource-toolkit'; @@ -70,22 +69,15 @@ export default class AgentBuilder { addDataSource(factory: DataSourceFactory, options?: DataSourceOptions): this { this.customizations.push(async () => { const dataSource = await factory(this.options.logger); - const rename = options?.rename ?? {}; + const decorated = new RenameCollectionDataSourceDecorator(dataSource); - const names = dataSource.collections.map(({ name }) => name); - const notExistName = Object.keys(rename).find(toRename => !names.includes(toRename)); - - if (notExistName) { - throw new Error(`The given collection name "${notExistName}" does not exist`); + for (const [oldName, newName] of Object.entries(options?.rename ?? {})) { + decorated.renameCollection(oldName, newName); } - const decorated = new DataSourceDecorator(dataSource, RenameCollectionCollectionDecorator); - decorated.collections.forEach(collection => { - const newName = rename[collection.name]; - if (newName) collection.rename(newName); - + for (const collection of decorated.collections) { this.compositeDataSource.addCollection(collection); - }); + } }); return this; diff --git a/packages/datasource-toolkit/src/decorators/rename-collection/collection.ts b/packages/datasource-toolkit/src/decorators/rename-collection/collection.ts index 08afa9130e..187cbdfe99 100644 --- a/packages/datasource-toolkit/src/decorators/rename-collection/collection.ts +++ b/packages/datasource-toolkit/src/decorators/rename-collection/collection.ts @@ -1,22 +1,28 @@ import { CollectionSchema, FieldSchema } from '../../interfaces/schema'; import CollectionDecorator from '../collection-decorator'; -import DataSourceDecorator from '../datasource-decorator'; +import CollectionRenameDataSourceDecorator from './datasource'; /** - * This decorator renames the collection name. + * This decorator renames collections. + * It should be used with RenameCollectionDataSourceDecorator, and not the raw DataSourceDecorator */ export default class RenameCollectionCollectionDecorator extends CollectionDecorator { - override readonly dataSource: DataSourceDecorator; + override readonly dataSource: CollectionRenameDataSourceDecorator; - private substitutedName: string; + private substitutedName: string = null; override get name() { - return this.substitutedName || this.childCollection.name; + return this.substitutedName ?? this.childCollection.name; } - /** Rename the collection name */ + /** @internal */ rename(name: string): void { this.substitutedName = name; + + // Invalidate all schemas + for (const collection of this.dataSource.collections) { + collection.markSchemaAsDirty(); + } } protected override refineSchema(childSchema: CollectionSchema): CollectionSchema { @@ -26,16 +32,12 @@ export default class RenameCollectionCollectionDecorator extends CollectionDecor const schema = { ...oldSchema }; if (schema.type === 'ManyToOne') { - const relation = this.dataSource.getCollection(schema.foreignCollection); - schema.foreignCollection = relation.name; + schema.foreignCollection = this.getNewName(schema.foreignCollection); } else if (schema.type === 'OneToMany' || schema.type === 'OneToOne') { - const relation = this.dataSource.getCollection(schema.foreignCollection); - schema.foreignCollection = relation.name; + schema.foreignCollection = this.getNewName(schema.foreignCollection); } else if (schema.type === 'ManyToMany') { - const through = this.dataSource.getCollection(schema.throughCollection); - const relation = this.dataSource.getCollection(schema.foreignCollection); - schema.throughCollection = through.name; - schema.foreignCollection = relation.name; + schema.throughCollection = this.getNewName(schema.throughCollection); + schema.foreignCollection = this.getNewName(schema.foreignCollection); } fields[name] = schema; @@ -43,4 +45,8 @@ export default class RenameCollectionCollectionDecorator extends CollectionDecor return { ...childSchema, fields }; } + + private getNewName(oldName: string): string { + return this.dataSource.collections.find(c => c.childCollection.name === oldName).name; + } } diff --git a/packages/datasource-toolkit/src/decorators/rename-collection/datasource.ts b/packages/datasource-toolkit/src/decorators/rename-collection/datasource.ts new file mode 100644 index 0000000000..b753f87162 --- /dev/null +++ b/packages/datasource-toolkit/src/decorators/rename-collection/datasource.ts @@ -0,0 +1,23 @@ +import { DataSource } from '../../interfaces/collection'; +import DataSourceDecorator from '../datasource-decorator'; +import RenameCollectionCollectionDecorator from './collection'; + +export default class RenameCollectionDataSourceDecorator extends DataSourceDecorator { + constructor(childDataSource: DataSource) { + super(childDataSource, RenameCollectionCollectionDecorator); + } + + renameCollection(oldName: string, newName: string) { + if (!this._collections[oldName]) { + throw new Error(`The given collection name "${oldName}" does not exist`); + } + + if (oldName !== newName) { + const collection = this._collections[oldName] as RenameCollectionCollectionDecorator; + collection.rename(newName); + + this._collections[newName] = collection; + delete this._collections[oldName]; + } + } +} diff --git a/packages/datasource-toolkit/src/decorators/rename-field/collection.ts b/packages/datasource-toolkit/src/decorators/rename-field/collection.ts index 8561bbaa1d..af2109e699 100644 --- a/packages/datasource-toolkit/src/decorators/rename-field/collection.ts +++ b/packages/datasource-toolkit/src/decorators/rename-field/collection.ts @@ -35,14 +35,14 @@ export default class RenameFieldCollectionDecorator extends CollectionDecorator delete this.toChildCollection[currentName]; delete this.fromChildCollection[childName]; initialName = childName; - this.markSchemaAsDirty(); + this.markAllSchemaAsDirty(); } // Do not update arrays if renaming is a no-op (ie: customer is cancelling a previous rename). if (initialName !== newName) { this.fromChildCollection[initialName] = newName; this.toChildCollection[newName] = initialName; - this.markSchemaAsDirty(); + this.markAllSchemaAsDirty(); } } @@ -130,6 +130,12 @@ export default class RenameFieldCollectionDecorator extends CollectionDecorator })); } + private markAllSchemaAsDirty(): void { + for (const collection of this.dataSource.collections) { + collection.markSchemaAsDirty(); + } + } + /** Convert field path from child collection to this collection */ private pathFromChildCollection(childPath: string): string { if (childPath.includes(':')) { diff --git a/packages/datasource-toolkit/src/index.ts b/packages/datasource-toolkit/src/index.ts index d015ce9362..ef8ea6a570 100644 --- a/packages/datasource-toolkit/src/index.ts +++ b/packages/datasource-toolkit/src/index.ts @@ -12,6 +12,7 @@ export { default as CollectionCustomizationContext } from './context/collection- // Decorators (datasource) export { default as DataSourceDecorator } from './decorators/datasource-decorator'; export { default as ChartDataSourceDecorator } from './decorators/chart/datasource'; +export { default as RenameCollectionDataSourceDecorator } from './decorators/rename-collection/datasource'; // Decorators (collections) export { default as ActionCollectionDecorator } from './decorators/actions/collection'; @@ -22,7 +23,6 @@ export { default as OperatorsEmulateCollectionDecorator } from './decorators/ope export { default as OperatorsReplaceCollectionDecorator } from './decorators/operators-replace/collection'; export { default as PublicationCollectionDecorator } from './decorators/publication/collection'; export { default as RenameFieldCollectionDecorator } from './decorators/rename-field/collection'; -export { default as RenameCollectionCollectionDecorator } from './decorators/rename-collection/collection'; export { default as SearchCollectionDecorator } from './decorators/search/collection'; export { default as WriteCollectionDecorator } from './decorators/write/collection'; export { default as SchemaCollectionDecorator } from './decorators/schema/collection'; diff --git a/packages/datasource-toolkit/test/decorators/rename-collection/collection.test.ts b/packages/datasource-toolkit/test/decorators/rename-collection/datasource.test.ts similarity index 80% rename from packages/datasource-toolkit/test/decorators/rename-collection/collection.test.ts rename to packages/datasource-toolkit/test/decorators/rename-collection/datasource.test.ts index 9a01d66094..5cb3aec717 100644 --- a/packages/datasource-toolkit/test/decorators/rename-collection/collection.test.ts +++ b/packages/datasource-toolkit/test/decorators/rename-collection/datasource.test.ts @@ -1,6 +1,5 @@ import * as factories from '../../__factories__'; -import DataSourceDecorator from '../../../src/decorators/datasource-decorator'; -import RenameCollectionCollectionDecorator from '../../../src/decorators/rename-collection/collection'; +import RenameCollectionDataSourceDecorator from '../../../src/decorators/rename-collection/datasource'; describe('RenameCollectionDecorator', () => { const setupWithOneToOneRelation = () => { @@ -33,7 +32,7 @@ describe('RenameCollectionDecorator', () => { }), ]); - return new DataSourceDecorator(dataSource, RenameCollectionCollectionDecorator); + return new RenameCollectionDataSourceDecorator(dataSource); }; const setupWithManyToManyRelation = () => { @@ -91,10 +90,16 @@ describe('RenameCollectionDecorator', () => { }), }); - return new DataSourceDecorator( + const dataSource = new RenameCollectionDataSourceDecorator( factories.dataSource.buildWithCollections([librariesBooks, books, libraries]), - RenameCollectionCollectionDecorator, ); + + // hydrate cache to ensure invalidation is working + void dataSource.getCollection('librariesBooks').schema; + void dataSource.getCollection('books').schema; + void dataSource.getCollection('libraries').schema; + + return dataSource; }; const setupWithManyToOneAndOneToManyRelations = () => { @@ -126,23 +131,23 @@ describe('RenameCollectionDecorator', () => { }), }); - return new DataSourceDecorator( + const dataSource = new RenameCollectionDataSourceDecorator( factories.dataSource.buildWithCollections([persons, books]), - RenameCollectionCollectionDecorator, ); + + // hydrate cache to ensure invalidation is working + void dataSource.getCollection('persons').schema; + void dataSource.getCollection('books').schema; + + return dataSource; }; test('should return the real name when it is not renamed', () => { const dataSource = factories.dataSource.buildWithCollection( factories.collection.build({ name: 'name 1' }), ); - const decoratedDataSource = new DataSourceDecorator( - dataSource, - RenameCollectionCollectionDecorator, - ); - - const collection: RenameCollectionCollectionDecorator = - decoratedDataSource.getCollection('name 1'); + const decoratedDataSource = new RenameCollectionDataSourceDecorator(dataSource); + const collection = decoratedDataSource.getCollection('name 1'); expect(collection.name).toEqual('name 1'); }); @@ -151,22 +156,18 @@ describe('RenameCollectionDecorator', () => { const dataSource = factories.dataSource.buildWithCollection( factories.collection.build({ name: 'name 1' }), ); - const decoratedDataSource = new DataSourceDecorator( - dataSource, - RenameCollectionCollectionDecorator, - ); - - const collection: RenameCollectionCollectionDecorator = - decoratedDataSource.getCollection('name 1'); - collection.rename('name 2'); + const decoratedDataSource = new RenameCollectionDataSourceDecorator(dataSource); + decoratedDataSource.renameCollection('name 1', 'name 2'); - expect(collection.name).toEqual('name 2'); + expect(decoratedDataSource.getCollection('name 2')).toMatchObject({ name: 'name 2' }); + expect(() => decoratedDataSource.getCollection('name 1')).toThrow( + `Collection 'name 1' not found.`, + ); }); test('should change the foreign collection when it is a many to one', () => { const dataSource = setupWithManyToOneAndOneToManyRelations(); - - dataSource.getCollection('persons').rename('renamedPersons'); + dataSource.renameCollection('persons', 'renamedPersons'); const collection = dataSource.getCollection('books'); @@ -181,7 +182,7 @@ describe('RenameCollectionDecorator', () => { test('should change the foreign collection when it is a one to many', () => { const dataSource = setupWithManyToOneAndOneToManyRelations(); - dataSource.getCollection('books').rename('renamedBooks'); + dataSource.renameCollection('books', 'renamedBooks'); const collection = dataSource.getCollection('persons'); @@ -196,7 +197,7 @@ describe('RenameCollectionDecorator', () => { test('should change the foreign collection when it is a one to one', () => { const dataSource = setupWithOneToOneRelation(); - dataSource.getCollection('owner').rename('renamedOwner'); + dataSource.renameCollection('owner', 'renamedOwner'); const collection = dataSource.getCollection('book'); @@ -211,8 +212,8 @@ describe('RenameCollectionDecorator', () => { test('should change the foreign collection when it is a many to many', () => { const dataSource = setupWithManyToManyRelation(); - dataSource.getCollection('librariesBooks').rename('renamedLibrariesBooks'); - dataSource.getCollection('books').rename('renamedBooks'); + dataSource.renameCollection('librariesBooks', 'renamedLibrariesBooks'); + dataSource.renameCollection('books', 'renamedBooks'); const collection = dataSource.getCollection('libraries'); diff --git a/packages/datasource-toolkit/test/decorators/rename-field/collection.test.ts b/packages/datasource-toolkit/test/decorators/rename-field/collection.test.ts index 470ba15cb6..a0ce18de9a 100644 --- a/packages/datasource-toolkit/test/decorators/rename-field/collection.test.ts +++ b/packages/datasource-toolkit/test/decorators/rename-field/collection.test.ts @@ -208,6 +208,12 @@ describe('RenameFieldCollectionDecorator', () => { // Rename stuff beforeEach(() => { + // Cache the schemas to ensure they refresh property + void newBooks.schema; + void newBookPersons.schema; + void newPersons.schema; + + // Rename stuff newPersons.renameField('id', 'primaryKey'); newPersons.renameField('myBookPerson', 'myNovelAuthor'); newBookPersons.renameField('date', 'createdAt'); @@ -316,6 +322,12 @@ describe('RenameFieldCollectionDecorator', () => { describe('when renaming foreign keys', () => { beforeEach(() => { + // Cache the schemas to ensure they refresh property + void newBooks.schema; + void newBookPersons.schema; + void newPersons.schema; + + // Rename stuff newBookPersons.renameField('bookId', 'novelId'); newBookPersons.renameField('personId', 'authorId'); });