UNPKG

@palmares/databases

Version:

Add support for working with databases with palmares framework

365 lines (356 loc) 13 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/query/index.ts var query_exports = {}; __export(query_exports, { AdapterQuery: () => AdapterQuery, adapterQuery: () => adapterQuery }); module.exports = __toCommonJS(query_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/query/get.ts var AdapterGetQuery = class { static { __name(this, "AdapterGetQuery"); } /** * This is a simple query, by default you should always implement this function in your AdapterGetQuery. * * This will guarantee that you are able to retrieve the data, it's not much performatic because it will do * many small queries to the database, which might slow things down, but you will be guaranteed to work 100% * with the types. * * For a more performatic approach you should implement `queryDataNatively`. That will translate the query to the * native query, but the second can be harder to implement since it relies on knowing about palmares objects and * model structure. * * A simple Sequelize example: * @example * ```ts * async queryData( * _engine: DatabaseAdapter, * args: { * modelOfEngineInstance: ModelCtor<Model>; * search: any; * fields: readonly string[]; * ordering?: Order; * limit?: number; * offset?: number | string; * } * ) { * const findAllOptions: Parameters<ModelCtor<Model>['findAll']>[0] = { * attributes: args.fields as string[], * where: args.search, * nest: true, * raw: true, * }; * if (args.ordering) findAllOptions.order = args.ordering; * if (args.limit) findAllOptions.limit = args.limit; * if (args.offset) findAllOptions.offset = Number(args.offset); * return args.modelOfEngineInstance.findAll(findAllOptions); * } * ``` * * @param _engine - The engine instance that is running the query. * @param _args - The arguments of the query. * @param _args.modelOfEngineInstance - The model instance to query, this is what your ORM has translated on * `AdapterModel.translate` function. * @param _args.fields - The fields to retrieve from the database, sometimes the user doesn't want to retrieve * all of the fields from the database. * @param _args.ordering - The ordering to use on the query, this ordering is translated from the `parseOrdering` * inside {@link AdapterQueryOrdering} * @param _args.search - The search argument to search on the database. This was translated from the `parseSearch` * inside {@link AdapterQuerySearch} * @param _args.limit - The limit of the query, this is used for pagination. * @param _args.offset - The offset of the query, this is used for pagination. * * @returns - Returns an array, always should return an array, if the data doesn't exist, return an empty array * instead of undefined or null. */ // eslint-disable-next-line ts/require-await async queryData(_engine, _args) { return []; } // eslint-disable-next-line ts/require-await async queryDataNatively(_engine, _modelConstructor, _search, _fields, _includes, _defaultParseSearch) { throw new NotImplementedAdapterException("queryDataNatively"); } }; // src/engine/query/ordering.ts var AdapterOrderingQuery = class { static { __name(this, "AdapterOrderingQuery"); } /** * Ordering the query is as simple as passing an array of string, each string contains the name of the field alongside * a `-` if it's descending. * * - `['name']` - Order by name ascending. * - `['-name']` - Order by name descending. * * A simple Sequelize example would be: * @example * ```ts * import { Order } from 'sequelize'; * * function async parseOrdering(ordering): Promise<Order> => { * return ordering.map((order) => { * const isDescending = order.startsWith('-'); * return [isDescending ? order.slice(1) : order, isDescending ? 'DESC' : 'ASC']; * }); * } * ``` * * @param _ordering - The ordering to parse. * * @returns - Returns the parsed ordering to be used on your query. */ // eslint-disable-next-line ts/require-await async parseOrdering(_modelInstance, _ordering) { throw new NotImplementedAdapterException("parseOrdering"); } }; // src/engine/query/remove.ts var AdapterRemoveQuery = class { static { __name(this, "AdapterRemoveQuery"); } /** * This query is used to remove a certain data from the database. * * An example of how you can implement it on sequelize * @example * ```typescript * queryData: async (_, args) => { * async function remove() { * return args.modelOfEngineInstance.destroy({ * where: args.search, * transaction: args.transaction, * }); * } * * if (args.shouldReturnData) { * const deleted = await args.modelOfEngineInstance.findAll({ * where: args.search, * transaction: args.transaction, * }); * await remove(); * * return deleted.map((data: any) => data.toJSON()); * } * * await remove(); * return []; * } */ // eslint-disable-next-line ts/require-await async queryData(_engine, _args) { return [ {} ]; } }; // src/engine/query/search.ts var AdapterSearchQuery = class { static { __name(this, "AdapterSearchQuery"); } /** * This will pretty much receive the search value and parse it and translate that into an object for each field. * The nicest thing is that we send you the `result`. This way you can organize the `search` object the way that * you want and makes sense for your engine. * * @example * ```ts * async parseSearchFieldValue<OperationType extends OperatorsOfQuery>( * operationType: OperationType, * value?: (OperationType extends 'or' | 'and' | 'in' | 'between' ? unknown[] : unknown) | undefined, * result?: any, * options?: { isNot?: boolean | undefined; ignoreCase?: boolean | undefined } | undefined * ): Promise<any> { * switch (operationType) { * case 'like': * if (options?.ignoreCase) result[Op.iLike] = value; * else if (options?.isNot && options.ignoreCase) result[Op.notILike] = value; * else if (options?.isNot) result[Op.notLike] = value; * else result[Op.like] = value; * return; * case 'is': * if (value === null && options?.isNot) result[Op.not] = value; * else if (value === null) result[Op.is] = value; * else if (options?.isNot) result[Op.ne] = value; * else result[Op.eq] = value; * return; * case 'in': * if (options?.isNot) result[Op.notIn] = value; * else result[Op.in] = value; * return; * case 'between': * if (options?.isNot) result[Op.notBetween] = value; * else result[Op.between] = value; * return; * case 'and': * result[Op.and] = value; * return; * case 'or': * result[Op.or] = value; * return; * case 'greaterThan': * result[Op.gt] = value; * return; * case 'greaterThanOrEqual': * result[Op.gte] = value; * return; * case 'lessThan': * result[Op.lt] = value; * return; * case 'lessThanOrEqual': * result[Op.lte] = value; * return; * default: * return; * } * } * ``` * * @param operationType - The operation type of the query, this can be `like`, `in`, `is`, `between` and so on. * @param key - The key of the query, this is the field name that we are querying. * @param modelInstance - The model instance that we are querying. * @param value - The value of the query, if the operation is `or`, `and`, `in` or `between` this will be an array of * values, otherwise this can be a string, a number and such. Be aware that this respects the `inputParser` of the * field if you have implemented it on {@link AdapterFieldParser}. This is nice because we can maintain the types of * the fields through the framework and you can parse the way that makes sense for your engine. * @param result - This is the result we will use on the query. It's an object that you can append the result of the * parsing. * @param options - This is an object with some options that you can use to parse the query, like if it's a negative * query, if it's case insensitive and so on. * * @returns - Values are changed in-place, so you should not return anything. */ // eslint-disable-next-line ts/require-await async parseSearchFieldValue(_operationType, _key, _modelInstance, _value, _result, _options) { return Array.isArray(_value) ? _value[0] : _value; } }; // src/engine/query/set.ts var AdapterSetQuery = class { static { __name(this, "AdapterSetQuery"); } /** * This is a simple upsert query, by default you should always implement this function in your AdapterSetQuery. * * _Note_: If `args.search` is not null or undefined, you should update the data, otherwise you should create it. * _Note 2_: You should return an array, the first argument is true if the data was created, false otherwise. The * second argument is the data that was created or updated. * * @example * ```ts * async queryData( * _: DatabaseAdapter, * args: { * modelOfEngineInstance: ModelCtor<Model>; * search: any; * data?: any; * transaction?: Transaction; * } * ): Promise<[boolean, any][]> { * return Promise.all( * args.data.map(async (eachData: any) => { * if (args.search === undefined) * return [ * true, * ( * await args.modelOfEngineInstance.create(eachData, { * transaction: args.transaction, * }) * ).toJSON(), * ]; * const [instance, hasCreated] = await args.modelOfEngineInstance.upsert(eachData, { * transaction: args.transaction, * returning: true, * }); * return [hasCreated ? hasCreated : false, instance.toJSON()]; * }) * ); * } * ``` * * @param _engine - The engine instance that is running the query. * @param _args - The arguments of the query. * @param _args.modelOfEngineInstance - The model instance to query, this is what your ORM has translated * on `AdapterModel.translate` function. * @param _args.search - The search argument to search on the database. * @param _args.data - The data to be inserted or updated. * @param _args.transaction - The transaction to use to run the query, That's what you pass to the callback * function on `DatabaseAdapter.transaction`. * * @returns - Returns an array of tuples, the first argument is true if the data was created, false otherwise. * The second argument is the data that was created or updated. */ // eslint-disable-next-line ts/require-await async queryData(_engine, _args) { return _args.data.map((eachData) => [ true, { ...eachData } ]); } }; // src/engine/query/index.ts function adapterQuery(args) { let CustomAdapterQuery = class CustomAdapterQuery extends AdapterQuery { static { __name(this, "CustomAdapterQuery"); } get = args.get; set = args.set; remove = args.remove; search = args.search; ordering = args.ordering; }; return CustomAdapterQuery; } __name(adapterQuery, "adapterQuery"); var AdapterQuery = class { static { __name(this, "AdapterQuery"); } get = new AdapterGetQuery(); set = new AdapterSetQuery(); remove = new AdapterRemoveQuery(); search = new AdapterSearchQuery(); ordering = new AdapterOrderingQuery(); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AdapterQuery, adapterQuery });