@palmares/databases
Version:
Add support for working with databases with palmares framework
339 lines (331 loc) • 12 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// 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();
};
export {
AdapterQuery,
adapterQuery
};