@palmares/databases
Version:
Add support for working with databases with palmares framework
372 lines (368 loc) • 16.2 kB
JavaScript
;
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
});