UNPKG

@palmares/databases

Version:

Add support for working with databases with palmares framework

428 lines (424 loc) 18.5 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/engine/migrations.ts var migrations_exports = {}; __export(migrations_exports, { AdapterMigrations: () => AdapterMigrations, adapterMigrations: () => adapterMigrations }); module.exports = __toCommonJS(migrations_exports); // src/engine/exceptions.ts var NotImplementedAdapterException = class _NotImplementedAdapterException extends Error { static { __name(this, "NotImplementedAdapterException"); } constructor(methodName) { super(`Method ${methodName} was not implemented in your Adapter, it should be in order to fully work.`); this.name = _NotImplementedAdapterException.name; } }; // src/engine/migrations.ts function adapterMigrations(args) { let CustomAdapterMigrations = class CustomAdapterMigrations extends AdapterMigrations { static { __name(this, "CustomAdapterMigrations"); } batchAll = args.batchAll; init = args.init; addModel = args.addModel; removeModel = args.removeModel; changeModel = args.changeModel; addField = args.addField; changeField = args.changeField; renameField = args.renameField; removeField = args.removeField; finish = args.finish; }; return CustomAdapterMigrations; } __name(adapterMigrations, "adapterMigrations"); var AdapterMigrations = class { static { __name(this, "AdapterMigrations"); } /** * This function is COMPLETELY optional and allows you to batch all of the migrations on a single function. By * default we run each migration file one by one, but for stuff like Prisma we do not have this option. Prisma * already migrates everything on a single command. So what we do is that instead of running each migration file * one by one we run all of them at once. * * We just generate the current state of the database and pass it to the batch function. Imagine that the state holds * the strings of each model, so with that we just need to create the .schema file and let the ORM do the rest. * * A simple prisma Example (it can be more complicated than that but you get the idea): * @example * ```ts * async currentBatch( * engine: DatabaseAdapter, * toStateModels: OriginalOrStateModelsByNameType, * returnOfInit: any * ): Promise<void> { * for (const model of Object.values(toStateModels)) { * fs.appendFileSync('./prisma/schema.prisma', model.initialized); * } * * execSync('npx prisma migrate dev'); * } * ``` * * @param _engine - The engine instance that is running the migrations. * @param _toStateModels - All of the models on a key/value pair where the key is the name of the model and the * value is the model itself. * @param _returnOfInit - The return of the init function, if you implemented it, otherwise it will be undefined. */ // eslint-disable-next-line ts/require-await async batchAll(_engine, _toStateModels, _returnOfInit) { throw new NotImplementedAdapterException("batch"); } /** * This is called before the migrations are run. If you want to initialize something before the migrations run * you can use this function, but it's totally optional and not required. * * **Be aware: what you return from this function will be passed to all of the other functions as the LAST argument.** * * On Sequelize implementation we use this to initialize the queryInterface that is used on the migrations. * @example * ``` * async init(engine: DatabaseAdapter<Sequelize>): Promise<QueryInterface> { * return engine.instance?.getQueryInterface() as QueryInterface; * } * ``` * * @param _engine - The engine instance that is running the migrations. */ // eslint-disable-next-line ts/require-await async init(_engine) { return; } /** * This is called when we are creating a new column on the database. A model represent a table on the database * (if we are talking about SQL databases). * * @example * ```ts * async addModel( * engine: DatabaseAdapter<Sequelize>, * toModel: InitializedModelsType<ModelCtor<Model>>, * migration: Migration * ): Promise<void> { * const model = toModel.initialized; * * await this.#queryInterface.createTable( * model.options.tableName as string, * model.getAttributes(), * Object.assign(model.options, { * transaction: migration.transaction, * }) * ); * * await this.#handleCircularDependencies(engine, migration.transaction, { toModel }); * await this.#handleIndexes(migration.transaction, { toModel }); * } * ``` * * @param _engine - The engine instance that is running the migrations. * @param _toModel - The model that will be created on the database. * @param _migration - The migration instance that is running the migrations. * @param _returnOfInit - The return of the init function, if you implemented it, otherwise it will be undefined. */ // eslint-disable-next-line ts/require-await async addModel(_engine, _toModel, _migration, _returnOfInit) { return; } /** * Removes a model/table from the database. This removes everything from the database, no worries about the data. * * A simple sequelize implementation: * @example * ```ts * async removeModel( * _: DatabaseAdapter<Sequelize>, * fromModel: InitializedModelsType<ModelCtor<Model>>, * migration: Migration * ): Promise<void> { * const tableNameToRemove = fromModel.initialized.options.tableName as string; * const transaction = migration.transaction; * await this.#queryInterface.dropTable(tableNameToRemove, { transaction }); * } * ``` * * @param _engine - The engine instance that is running the migrations. * @param _fromModel - How the model WAS structured before running the migration. * @param _migration - The migration instance that is running the migrations. * @param _returnOfInit - The return of the init function, if you implemented it, otherwise it will be undefined. */ // eslint-disable-next-line ts/require-await async removeModel(_engine, _fromModel, _migration, _returnOfInit) { return; } /** * Used when the user changes the model. Let's say that the user changed the model name, added a new index, * changed the ordering, changed pretty much any model configuration. * * This is called when the model had changed, not the fields but the options. * * @example * ```ts * async changeModel( * engine: DatabaseAdapter<Sequelize>, * toModel: InitializedModelsType<ModelCtor<Model>>, * fromModel: InitializedModelsType<ModelCtor<Model>>, * migration: Migration * ): Promise<void> { * const fromTableName = fromModel.initialized.tableName; * const toTableName = toModel.initialized.tableName; * const hasTheNameOfTheTableChanged = fromTableName !== toTableName; * * if (hasTheNameOfTheTableChanged) { * await this.#queryInterface.renameTable(fromTableName, toTableName, { * transaction: migration.transaction, * }); * } * * await this.#handleCircularDependencies(engine, migration, { fromModel, toModel }); * await this.#handleIndexes(migration, { fromModel, toModel }); * } * ``` * * @param _engine - The engine instance that is running the migrations. * @param _toModel - How the model state IS right now when running the migration, this is how the model will be after * running the migration. * @param _fromModel - How the model state WILL BE before running the migration. * @param _migration - The migration instance that is running the migrations. * @param _returnOfInit - The return of the init function, if you implemented it, otherwise it will be undefined. */ // eslint-disable-next-line ts/require-await async changeModel(_engine, _toModel, _fromModel, _migration, _returnOfInit) { return; } /** * When the user already has a model and you add a new field to the model this is used. So, in other words, this is * used to add new fields to existing models. * * Here is a sequelize example: * @example * ```ts * async addField( * engine: DatabaseAdapter<Sequelize>, * toModel: InitializedModelsType<ModelCtor<Model>>, * fromModel: InitializedModelsType<ModelCtor<Model>>, * fieldName: string, * migration: Migration * ): Promise<void> { * engine = engine as InstanceType<typeof SequelizeEngine>; * let sequelizeAttribute = toModel.initialized.getAttributes()[fieldName]; * const doesNotExistSequelizeAttribute = sequelizeAttribute === undefined; * if (doesNotExistSequelizeAttribute) { * const originalFieldName = toModel.original.fields[fieldName]?.fieldName; * sequelizeAttribute = toModel.initialized.rawAttributes[originalFieldName]; * } * * await this.#queryInterface.addColumn( * toModel.initialized.options.tableName as string, * sequelizeAttribute.field as string, * sequelizeAttribute, * { transaction: migration.transaction } * ); * * await this.#handleCircularDependencies(engine, migration, { fromModel, toModel }); * await this.#handleIndexes(migration, { fromModel, toModel }); * } * ``` * * @param _engine - The engine instance that is running the migrations. * @param _toModel - How the model state IS right now when running the migration, this is how the model will be after * running the migration. You can take the field data from the actual model instance. * @param _fromModel - How the model state WAS before running the migration. * @param _fieldName -THe name of the field that is being added to the database. * @param _migration - The migration instance that is running the migrations. * @param _returnOfInit - The return of the init function, if you implemented it, otherwise it will be undefined. */ // eslint-disable-next-line ts/require-await async addField(_engine, _toModel, _fromModel, _fieldName, _migration, _returnOfInit) { return; } /** * When the user already has a model and a field but he makes changes to this field adding another attribute, * changing the type, changing the name, etc. * * This is called when the field had changed, not the model but the field specifically. * * Here is a Sequelize example: * @example * ```ts * async changeField( * engine: DatabaseAdapter<Sequelize>, * toModel: InitializedModelsType<ModelCtor<Model>>, * fromModel: InitializedModelsType<ModelCtor<Model>>, * fieldBefore: Field<any, any, any, any, any, any, any, any>, * fieldAfter: Field<any, any, any, any, any, any, any, any>, * migration: Migration * ): Promise<void> { * engine = engine as InstanceType<typeof SequelizeEngine>; * const attributesAsArray = Object.values(toModel.initialized.getAttributes()); * const initializedAttribute = attributesAsArray.find((attribute) => attribute.field === fieldAfter.databaseName); * const tableName = toModel.initialized.options.tableName as string; * if (initializedAttribute) { * const isOfTypeRelation = fieldBefore instanceof ForeignKeyField; * // This removes the constraint, when we change the column sequelize automatically creates a new constraint * // because of that we remove the old one. * if (isOfTypeRelation) { * const constraints: GetForeignKeyReferencesForTableReturnType[] | undefined = * (await this.#queryInterface.getForeignKeyReferencesForTable(tableName, { * transaction: migration.transaction, * })) as GetForeignKeyReferencesForTableReturnType[] | undefined; * if (constraints) { * const constraintsToRemove = constraints?.filter( * (constraint) => constraint.columnName === fieldBefore.databaseName * ); * for (const constraintToRemove of constraintsToRemove) { * await this.#queryInterface.removeConstraint(tableName, constraintToRemove.constraintName as string, { * transaction: migration.transaction, * }); * } * } * } * } * await this.#queryInterface.changeColumn( * tableName, * fieldAfter.databaseName as unknown as string, * initializedAttribute, * { * transaction: migration.transaction, * } * ); * await this.#handleCircularDependencies(engine, migration, { fromModel, toModel }); * await this.#handleIndexes(migration, { toModel, fromModel }); * } * ``` * * @param _engine - The engine instance that is running the migrations. * @param _toModel - How the model state IS right now when running the migration, this is how the model will be * after running the migration. * @param _fromModel - How the model state WAS before running the migration. * @param _fieldBefore - How the field WAS * @param _fieldAfter - How the field WILL BE. * @param _migration - The migration instance that is running the migrations. * @param _returnOfInit - The return of the init function, if you implemented it, otherwise it will be undefined. */ // eslint-disable-next-line ts/require-await async changeField(_engine, _toModel, _fromModel, _fieldBefore, _fieldAfter, _migration, _returnOfInit) { return; } /** * Pretty much called whenever the user renames a field. Why this is done outside of the `changeField`? Because * renaming a field can do change some values on the database. Some databases might prefer to recreate the field * from scratch, we opt for maintaining the data and renaming the field only. * * Here is a Sequelize example: * @example * ```ts * async renameField( * engine: DatabaseAdapter<Sequelize>, * toModel: InitializedModelsType<ModelCtor<Model>>, * fromModel: InitializedModelsType<ModelCtor<Model>>, * fieldNameBefore: string, * fieldNameAfter: string, * migration: Migration * ): Promise<void> { * engine = engine as InstanceType<typeof SequelizeEngine>; * const databaseNameAfter = toModel.initialized.getAttributes()[fieldNameAfter].field as string; * const databaseNameBefore = toModel.initialized.getAttributes()[fieldNameBefore].field as string; * const tableNameWhereRenameHappened = toModel.initialized.options.tableName as string; * * await this.#queryInterface.renameColumn(tableNameWhereRenameHappened, databaseNameBefore, databaseNameAfter, { * transaction: migration.transaction, * }); * await this.#handleCircularDependencies(engine, migration, { fromModel, toModel }); * await this.#handleIndexes(migration, { toModel, fromModel }); * } * ``` * * @param _engine - The engine instance that is running the migrations. * @param _toModel - How the model state IS right now when running the migration, this is how the model will * be after running the migration. * @param _fromModel - How the model state WAS before running the migration. * @param _fieldNameBefore - How the name of the model WAS. * @param _fieldNameAfter - How the name of the model WILL BE. * @param _migration - The migration instance that is running the migrations. * @param _returnOfInit - The return of the init function, if you implemented it, otherwise it will be undefined. */ // eslint-disable-next-line ts/require-await async renameField(_engine, _toModel, _fromModel, _fieldNameBefore, _fieldNameAfter, _migration, _returnOfInit) { return; } /** * When a model already exists but we just want to remove an existing field that was created (if it was renamed * we call renamed. We actually ask the user for what happened) * * Here is a Sequelize example: * @example * ```ts * async removeField( * engine: DatabaseAdapter<Sequelize>, * toModel: InitializedModelsType<ModelCtor<Model>>, * fromModel: InitializedModelsType<ModelCtor<Model>>, * fieldName: string, * migration: Migration * ): Promise<void> { * engine = engine as InstanceType<typeof SequelizeEngine>; * const columnName = fromModel.initialized.getAttributes()[fieldName].field as string; * const tableName = fromModel.initialized.options.tableName as string; * await this.#queryInterface.removeColumn(tableName, columnName, { * transaction: migration.transaction, * }); * await this.#handleIndexes(migration, { toModel, fromModel }); * } * ``` * * @param _engine - The engine instance that is running the migrations. * @param _toModel - How the model state IS right now when running the migration, this is how the model will be * after running the migration. * @param _fromModel - How the model state WAS before running the migration. * @param _fieldName - The name of the field that will be removed (THAT'S NOT THE DB NAME, BUT THE JS NAME) * @param _migration - The migration instance that is running the migrations. * @param _returnOfInit - The return of the init function, if you implemented it, otherwise it will be undefined. */ // eslint-disable-next-line ts/require-await async removeField(_engine, _toModel, _fromModel, _fieldName, _migration, _returnOfInit) { return; } /** * When the migration file finishes running and you want to do some cleanup we call this function. If you don't have * any cleanup to do, don't implement this function. * * @param _engine - The engine instance that is running the migrations. * @param _returnOfInit - The return of the init function, if you implemented it, otherwise it will be undefined. */ // eslint-disable-next-line ts/require-await async finish(_engine, _returnOfInit) { return; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AdapterMigrations, adapterMigrations });