UNPKG

@palmares/databases

Version:

Add support for working with databases with palmares framework

372 lines (368 loc) 16.2 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/model.ts var model_exports = {}; __export(model_exports, { AdapterModels: () => AdapterModels, adapterModels: () => adapterModels }); module.exports = __toCommonJS(model_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/model.ts function adapterModels(args) { let CustomAdapterModel = class CustomAdapterModel extends AdapterModels { static { __name(this, "CustomAdapterModel"); } translateOptions = args.translateOptions; translateFields = args.translateFields; translate = args.translate; afterModelsTranslation = args.afterModelsTranslation; compare = args.compare; modelToString = args.modelToString; static customOptions = args.customOptions; }; return CustomAdapterModel; } __name(adapterModels, "adapterModels"); var AdapterModels = class { static { __name(this, "AdapterModels"); } /** * Used for translating the options of the model. Options of the model are things like the `tableName`, `indexes`, * `timestamps`, etc. If your engine does not offer the option to implement the options, just return the `options` * argument as is. * * @example * ```ts * async translateOptions( * _engine: SequelizeEngine, * modelName: string, * options: ModelOptionsType * ): Promise<ModelOptions> { * const indexes = this.#indexes[modelName] ? this.#indexes[modelName] : []; * return { * underscored: options?.underscored || true, * indexes: indexes, * timestamps: false, * tableName: options?.tableName, * ...options?.customOptions, * }; * } * ``` * * @param engine - Your custom engine instance. * @param modelName - The name of the model that is being translated. * @param modelOptions - The options of the model that is being translated. */ // eslint-disable-next-line ts/require-await async translateOptions(_engine, _modelName, _modelOptions) { throw new NotImplementedAdapterException("translateOptions"); } /** * This method is completely optional, we already try to solve that for you. What this method does is that it is * used to translate the fields of the model. We already give you the field entries of the model on * `_fieldEntriesOfModel`. Since we already has a default implementation you can opt to use it by * calling `_defaultTranslateFieldsCallback`. * * If you opt to NOT use it, you should call `engine.fields.translateField` for each field of the model. If * `translateField` is not implemented on EngineFields, you can call `_defaultTranslateFieldCallback` that * already has a default implementation for you. * * This should return an object with the fields translated to something that YOUR ORM can understand. Each * key of the object is the field name and the value is the translated field. * * **IMPORTANT:** By default, if the `translate` method on any of your FieldsParser returns `undefined` or * `null`, it will **NOT** be added to the object. That's useful if you want to add it later and lazy evaluate that. * * _Note: All examples below are considering that we are translating to sequelize._ * * - **If you opt out of the default implementation of both the `_defaultTranslateFieldCallback` and * `_defaultTranslateFieldsCallback`, this is how you can do it:** * * @example * ```ts * async function translateFields( * engine: DatabaseAdapter, * modelName: string, * fieldEntriesOfModel: [string, Field][], * model: Model, * defaultTranslateFieldCallback: (field: Field) => Promise<any>, * _: () => Promise<{ [key: string]: any }> * ) { * const fieldAttributes: { [key: string]: ModelAttributeColumnOptions } = {}; * for (const [fieldName, field] of fieldEntriesOfModel) { * const translatedAttributes = await engine.fields.translateField(engine, field, defaultTranslateFieldCallback); * const isTranslatedAttributeDefined = translatedAttributes !== undefined && * translatedAttributes !== null && typeof translatedAttributes === 'object'; * if (isTranslatedAttributeDefined) fieldAttributes[fieldName] = translatedAttributes; * } * * return fieldAttributes; * } * ``` * * - **If you opt in of the default implementation of just the `_defaultTranslateFieldCallback`, * this is how you can do it: * (this assumes that `translateField` was not defined on your _EngineFields_ implementation)** * * @example * ```ts * async function translateFields( * engine: DatabaseAdapter, * modelName: string, * fieldEntriesOfModel: [string, Field][], * model: Model, * defaultTranslateFieldCallback: (field: Field) => Promise<any>, * _: () => Promise<{ [key: string]: any }> * ) { * const fieldAttributes: { [key: string]: ModelAttributeColumnOptions } = {}; * for (const [fieldName, field] of fieldEntriesOfModel) { * const translatedAttributes = await defaultTranslateFieldCallback(field); * const isTranslatedAttributeDefined = translatedAttributes !== undefined && * translatedAttributes !== null && typeof translatedAttributes === 'object'; * if (isTranslatedAttributeDefined) fieldAttributes[fieldName] = translatedAttributes; * } * * return fieldAttributes; * } * ``` * * - **If you opt in of the default implementation of just the `_defaultTranslateFieldsCallback`, this * is how you can do it: * (assuming that you want to let it translate first and then do anything with the fields afterwards)** * * @example * ```ts * async function translateFields( * _engine: DatabaseAdapter, * _modelName: string, * _fieldEntriesOfModel: [string, Field][], * _model: Model, * _: (field: Field) => Promise<any>, * defaultTranslateFieldsCallback: () => Promise<{ [key: string]: any }> * ) { * const fieldAttributes: { [key: string]: ModelAttributeColumnOptions } = await defaultTranslateFieldsCallback(); * * // Do something with the fields after they were translated * * return fieldAttributes; * } * ``` * * Last but not least, you can totally opt out of using it. If that's your choice, just don't implement it and we will * use the default implementation. * On your `translate` method you should see that `fields` object will be an object where the keys are the field names * and the values are the translated fields. * * @param engine - Your custom engine instance. * @param modelName - The name of the model that is being translated. * @param fieldEntriesOfModel - The field entries of the model. It's an array of tuples where the first element is the * field name and the second is the field. * @param model - The model that is being translated. * @param defaultTranslateFieldCallback - The default implementation of the `translateField` method that Palmares * provides. If you have a `translateField` implementation on your `EngineFields` implementation, you need to make * sure that you pass it to this method. * @param defaultTranslateFieldsCallback - The default implementation of the `translateFields` method that Palmares * provides. * * @returns - An object where the keys are the field names and the values are the translated values. */ // eslint-disable-next-line ts/require-await async translateFields(_engine, _modelName, _fieldEntriesOfModel, _model, _defaultTranslateFieldCallback, _defaultTranslateFieldsCallback) { throw new NotImplementedAdapterException("translateFields"); } /** * The `translate` method will be called to translate the model to a instance of something that your engine/ORM can * understand. In other words, we will transform a Palmares model to YOUR model. * * ## first, a little explanation what it does: * * On Palmares, we DO NOT OFFER an ORM by default, we are really bad coders and we trust others (like you) to do that * for us. Translating a model means taking what we offer for them and passing all that data to you. You will decide * what to do with that. Some ORMs like DrizzleORM, Sequelize, TypeORM, etc. Will have a default implementation of * how a model should be implemented. That's what this method does, it will take the palmares model and translate * to your own ORM. * * - On Sequelize this would be the `User` on this example: * * @example * ```ts * const { Sequelize, Model, DataTypes } = require("sequelize"); * const sequelize = new Sequelize("sqlite::memory:"); * * const User = sequelize.define("user", { * name: DataTypes.TEXT, * favoriteColor: { * type: DataTypes.TEXT, * defaultValue: 'green' * }, * age: DataTypes.INTEGER, * cash: DataTypes.INTEGER * }); * ``` * * - On prisma, this would be `prisma.user` * * @example * ```ts * const { PrismaClient } = require('@prisma/client') * * const prisma = new PrismaClient() * * const users = await prisma.user.findMany() // here prisma.user is what we would need you to return. * ``` * * Prisma, actually has a `gotcha` there. Because you might want to transform the data to a string before actually * returning the actual model implementation. That's why we have the {@link AdapterModels['afterModelsTranslation']} * method. You can return a string from here, and on the `afterModelsTranslation` method you can build the schema * file and run the `prisma generate` command to generate the models. And just after that return the models. * * **This is an example assuming that you are translating sequelize** * @example * ```ts * async translate( * engine: SequelizeEngine, * modelName: string, * model: ModelBaseClass, * defaultTranslateCallback: () => Promise<{ options: ModelOptions; fields: ModelAttributes<any> }>, * _: (_field: Field) => Promise<any>, * __: () => Promise<{ [key: string]: ModelAttributeColumnOptions }> * ): Promise<ModelCtor<Model> | undefined> { * const { options: translatedOptions, fields: translatedAttributes } = await defaultTranslateCallback(); * * translatedOptions.indexes = getIndexes(engine.connectionName, modelName); * * const sequelizeModel = new Function('sequelizeModel', `return class ${modelName} extends sequelizeModel {}`)( * Model * ); * * const translatedModel = sequelizeModel.init(translatedAttributes, { * sequelize: engine.instance, * ...translatedOptions, * }); * * if (translatedModel !== undefined) await this.#translateOrdering(model, translatedModel); * return translatedModel; * } * ``` * * @param engine - The instance of your DatabaseAdapter. * @param modelName - The name of the model that is being translated. * @param model - The Palmares model instance so we can translate it. * @param fieldEntriesOfModel - The field entries of the model. It's an array of tuples where the first element is * the field name and the second is the field. * @param modelOptions - The options of the model that is being translated. * @param customOptions - Custom options that you can pass for your model. * @param defaultTranslateCallback - Instead of manually calling the `translateFields` and `translateOptions` methods, * you can call this function and it will do that for you. It will return an object * with the `options` and `fields` keys. The `options` key will be the return of the `translateOptions` method and the * `fields` key will be the return of the `translateFields` method. * @param defaultTranslateFieldCallback - This is passed here so you can pass to `translateFields` if you wish to call * it manually. * @param defaultTranslateFieldsCallback - This is passed here so you can pass to `translateFields` if you wish to * call it manually. * * @returns - The instance of the translated model. */ // eslint-disable-next-line ts/require-await async translate(_engine, _modelName, _model, _fieldEntriesOfModel, _modelOptions, _customOptions, _defaultTranslateCallback, _defaultTranslateFieldCallback, _defaultTranslateFieldsCallback) { throw new NotImplementedAdapterException("translate"); } /** * Some ORMs like Prisma requires you to run a generator command to generate the models to something that can be used * inside Typescript. With this method you can run this generator command. * This is called just once after all your models were translated. * * You have to options to return: * 1. You can return an array with all your models translated again (that's useful if you want to do one last change * to your models). * 2. You can return undefined and we will use the returned models from the `translate` method. * * @example * ```ts * async afterModelsTranslation( * _engine: DatabaseAdapter, _models: [string, any][] * ): Promise<[string, any][] | undefined> { * spawn('npx', ['prisma', 'generate'], { stdio: 'inherit' }); * return undefined; * } * ``` * * @param engine - The engine instance. * @param models - An array of tuples where the first value is the modelName and the second is the value returned from * `translate` method. * * @returns - An array of tuples where the first value is the modelName and the second is the value returned from * `translate` method, or undefined if you don't want to modify the models. */ // eslint-disable-next-line ts/require-await async afterModelsTranslation(_engine, _models) { throw new NotImplementedAdapterException("afterModelsTranslation"); } // eslint-disable-next-line ts/require-await async getModelInstanceForCustomHooks(_engine, _modelName, _translatedModel) { throw new NotImplementedAdapterException("getModelInstanceForCustomHooks"); } getTranslatedModels() { return void 0; } /** * Used for comparing two custom arguments so we can know if we need to update the field or not. * * This is part of the migration, don't need to implement if you are not using Palmares Migrations. */ compare(_oldCustomArguments, _newCustomArguments) { throw new NotImplementedAdapterException("compare"); } /** * Used for stringfying the custom arguments so we can store them in the database. If you do not implement this * and implement compare we will throw an error, otherwise we will just ignore the custom arguments. * * This is part of the migration, don't need to implement if you are not using Palmares Migrations. */ modelToString(_customArguments) { throw new NotImplementedAdapterException("modelToString"); } /** * This method is used just for giving typesafety. If you implement this method, those are custom options that you can * pass for your model. * * For example, if you are using sequelize, those would be the `third` argument from `sequelize.define`. */ static customOptions(args) { return args; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AdapterModels, adapterModels });