From 2928b95fd84dcb956e91a2520bcefd76279a5836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 15 Jan 2025 12:47:22 +0100 Subject: [PATCH] feat: add `dropCollection` migration method (#168) --- adonis-typings/migration.ts | 12 +++++-- reset-dev.mjs | 4 +-- src/Migration.ts | 33 ++++++++++++++++-- src/Model/__tests__/Model.test.ts | 3 +- src/__tests__/Migration.test.ts | 58 +++++++++++++++++++++++-------- 5 files changed, 89 insertions(+), 21 deletions(-) diff --git a/adonis-typings/migration.ts b/adonis-typings/migration.ts index 3317664..84268ca 100644 --- a/adonis-typings/migration.ts +++ b/adonis-typings/migration.ts @@ -1,14 +1,22 @@ declare module '@ioc:Zakodium/Mongodb/Migration' { import type { - Db, ClientSession, - IndexSpecification, CreateIndexesOptions, + Db, DropIndexesOptions, + IndexSpecification, } from 'mongodb'; export default abstract class Migration { public createCollections(collectionNames: string[]): void; + + /** + * Drop a collection. + * This operation will be done last in the migration. + * It cannot be run in a transaction, so we recommend doing it in a separate migration file. + * @param collectionName + */ + public dropCollection(collectionName: string): void; public createCollection(collectionName: string): void; public createIndex( collectionName: string, diff --git a/reset-dev.mjs b/reset-dev.mjs index 5952ae6..1844d87 100644 --- a/reset-dev.mjs +++ b/reset-dev.mjs @@ -21,7 +21,7 @@ try { stdio: 'inherit', }, ); -} catch (error) { - process.stderr.write(error.stderr); +} catch { + // Ignore error, it is already piped to the console. process.exit(1); } diff --git a/src/Migration.ts b/src/Migration.ts index c1aa81b..d0ccd9b 100644 --- a/src/Migration.ts +++ b/src/Migration.ts @@ -1,10 +1,10 @@ import type { Logger } from '@poppinss/cliui/build/src/Logger'; import type { - CreateIndexesOptions, ClientSession, + CreateIndexesOptions, Db, - IndexSpecification, DropIndexesOptions, + IndexSpecification, } from 'mongodb'; import type { @@ -13,12 +13,18 @@ import type { } from '@ioc:Zakodium/Mongodb/Database'; enum MigrationType { + DropCollection = 'DropCollection', CreateCollection = 'CreateCollection', DropIndex = 'DropIndex', CreateIndex = 'CreateIndex', Custom = 'Custom', } +interface DropCollectionOperation { + type: MigrationType.DropCollection; + collectionName: string; +} + interface CreateCollectionOperation { type: MigrationType.CreateCollection; collectionName: string; @@ -44,6 +50,7 @@ interface CustomOperation { } type MigrationOperation = + | DropCollectionOperation | CreateCollectionOperation | DropIndexOperation | CreateIndexOperation @@ -68,6 +75,13 @@ export default function createMigration(Database: DatabaseContract): any { } } + public dropCollection(collectionName: string): void { + this.$operations.push({ + type: MigrationType.DropCollection, + collectionName, + }); + } + public createCollection(collectionName: string): void { this.$operations.push({ type: MigrationType.CreateCollection, @@ -114,6 +128,7 @@ export default function createMigration(Database: DatabaseContract): any { await this._dropIndexes(session); await this._createIndexes(session); await this._executeDeferred(session); + await this._dropCollections(); } private async _listCollections() { @@ -128,6 +143,14 @@ export default function createMigration(Database: DatabaseContract): any { return this.$collectionList; } + private async _dropCollections(): Promise { + const db = await this.$connection.database(); + for (const op of this.$operations.filter(isDropCollection)) { + this.$logger.info(`Dropping collection ${op.collectionName}`); + await db.dropCollection(op.collectionName); + } + } + private async _createCollections(session: ClientSession): Promise { const db = await this.$connection.database(); for (const op of this.$operations.filter(isCreateCollection)) { @@ -179,6 +202,12 @@ export default function createMigration(Database: DatabaseContract): any { return Migration; } +function isDropCollection( + op: MigrationOperation, +): op is DropCollectionOperation { + return op.type === MigrationType.DropCollection; +} + function isCreateCollection( op: MigrationOperation, ): op is CreateCollectionOperation { diff --git a/src/Model/__tests__/Model.test.ts b/src/Model/__tests__/Model.test.ts index 15c9223..42e080a 100644 --- a/src/Model/__tests__/Model.test.ts +++ b/src/Model/__tests__/Model.test.ts @@ -4,7 +4,7 @@ import { ObjectId } from 'mongodb'; import { setupDatabase } from '../../../test-utils/TestUtils'; import { computed, field } from '../../Odm/decorators'; -import { BaseModel, BaseAutoIncrementModel } from '../Model'; +import { BaseAutoIncrementModel, BaseModel } from '../Model'; const db = setupDatabase(); @@ -444,6 +444,7 @@ test('toJSON method', async () => { test('spreading a model should throw', async () => { const post = await Post.query().firstOrFail(); + // eslint-disable-next-line @typescript-eslint/no-misused-spread expect(() => ({ ...post })).toThrow(/Getting model keys is disallowed/); }); diff --git a/src/__tests__/Migration.test.ts b/src/__tests__/Migration.test.ts index 4ccdc7b..a573108 100644 --- a/src/__tests__/Migration.test.ts +++ b/src/__tests__/Migration.test.ts @@ -7,27 +7,57 @@ const logger = new Logger(); const db = getMongodb(); const BaseMigration = createMigration(db); -class TestMigration extends BaseMigration { +class TestMigration1 extends BaseMigration { public constructor(connection: string | undefined, logger: Logger) { super(connection, logger); } public up(): void { - this.createCollection('migration'); + this.createCollection('migration1'); + this.createCollections(['migration2', 'migration3']); } } -afterAll(async () => { - const database = await db.connection('mongo').database(); - await database.dropDatabase(); - await db.manager.closeAll(); -}); +class TestMigration2 extends BaseMigration { + public constructor(connection: string | undefined, logger: Logger) { + super(connection, logger); + } + public up(): void { + this.dropCollection('migration2'); + } +} -test('runs a migration correctly edit database', async () => { - await db.connection('mongo').transaction(async (session) => { - const migration = new TestMigration('mongo', logger); - await migration.execUp(session); +describe('running migrations correctly changes database', () => { + afterAll(async () => { + const database = await db.connection('mongo').database(); + await database.dropDatabase(); + await db.manager.closeAll(); + }); + + it('should create collections', async () => { + await db.connection('mongo').transaction(async (session) => { + const migration1 = new TestMigration1('mongo', logger); + await migration1.execUp(session); + }); + const database = await db.connection('mongo').database(); + const collections = await database.listCollections().map(getName).toArray(); + expect(collections.sort()).toStrictEqual([ + 'migration1', + 'migration2', + 'migration3', + ]); + }); + + it('should drop collection', async () => { + await db.connection('mongo').transaction(async (session) => { + const migration2 = new TestMigration2('mongo', logger); + await migration2.execUp(session); + }); + const database = await db.connection('mongo').database(); + const collections = await database.listCollections().map(getName).toArray(); + expect(collections.sort()).toStrictEqual(['migration1', 'migration3']); }); - const database = await db.connection('mongo').database(); - const collections = await database.listCollections().toArray(); - expect(collections).toHaveLength(1); }); + +function getName(collection: { name: string }) { + return collection.name; +}