@decaf-ts/core
Version:
Core persistence module for the decaf framework
439 lines • 17.5 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var _a, _b, _c, _d;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Statement = void 0;
const decorator_validation_1 = require("@decaf-ts/decorator-validation");
const Condition_1 = require("./Condition.cjs");
const db_decorators_1 = require("@decaf-ts/db-decorators");
const logging_1 = require("@decaf-ts/logging");
const persistence_1 = require("./../persistence/index.cjs");
const errors_1 = require("./errors.cjs");
const index_1 = require("./../utils/index.cjs");
const Context_1 = require("./../persistence/Context.cjs");
const types_1 = require("./types.cjs");
const constants_1 = require("./constants.cjs");
const constants_2 = require("./../repository/constants.cjs");
const Repository_1 = require("./../repository/Repository.cjs");
/**
* @description Base class for database query statements
* @summary Provides a foundation for building and executing database queries
*
* This abstract class implements the query builder pattern for constructing
* database queries. It supports various query operations like select, from,
* where, orderBy, groupBy, limit, and offset. It also provides methods for
* executing queries and handling pagination.
*
* @template Q - The query type specific to the database adapter
* @template M - The model type this statement operates on
* @template R - The return type of the query
* @param {Adapter<any, Q, any, any>} adapter - The database adapter to use for executing queries
* @class Statement
* @example
* // Create a statement to query users
* const statement = new SQLStatement(adapter);
* const users = await statement
* .select()
* .from(User)
* .where(Condition.attribute("status").eq("active"))
* .orderBy(["createdAt", "DESC"])
* .limit(10)
* .execute();
*
* // Use pagination
* const paginator = await statement
* .select()
* .from(User)
* .paginate(20); // 20 users per page
*
* @mermaid
* sequenceDiagram
* participant Client
* participant Statement
* participant Adapter
* participant Database
*
* Client->>Statement: select()
* Client->>Statement: from(Model)
* Client->>Statement: where(condition)
* Client->>Statement: orderBy([field, direction])
* Client->>Statement: limit(value)
* Client->>Statement: execute()
* Statement->>Statement: build()
* Statement->>Adapter: raw(query)
* Adapter->>Database: execute query
* Database-->>Adapter: return results
* Adapter-->>Statement: return processed results
* Statement-->>Client: return final results
*/
class Statement extends index_1.ContextualLoggedClass {
constructor(adapter, overrides) {
super();
this.adapter = adapter;
this.overrides = overrides;
[this.execute, this.paginate].forEach((m) => {
(0, db_decorators_1.prefixMethod)(this, m, async (...args) => {
let execArgs = args;
if ((!execArgs.length ||
!(execArgs[execArgs.length - 1] instanceof Context_1.Context)) &&
this.fromSelector) {
const ctx = await this.adapter.context(persistence_1.PersistenceKeys.QUERY, this.overrides || {}, this.fromSelector);
execArgs = [...execArgs, ctx];
}
const { ctx, ctxArgs } = persistence_1.Adapter.logCtx(execArgs, m.name);
const forceSimple = ctx.get("forcePrepareSimpleQueries");
const forceComplex = ctx.get("forcePrepareComplexQueries");
if ((forceSimple && this.isSimpleQuery()) || forceComplex)
await this.prepare(ctx);
return ctxArgs;
}, m.name);
});
}
get log() {
return this.adapter.log.for(Statement);
}
select(selector) {
Object.defineProperty(this, "selectSelector", {
value: selector,
writable: false,
});
return this;
}
distinct(selector) {
this.distinctSelector = selector;
return this;
}
max(selector) {
this.maxSelector = selector;
return this;
}
min(selector) {
this.minSelector = selector;
return this;
}
count(selector) {
this.countSelector = selector;
return this;
}
from(selector) {
this.fromSelector = (typeof selector === "string" ? decorator_validation_1.Model.get(selector) : selector);
if (!this.fromSelector)
throw new errors_1.QueryError(`Could not find selector model: ${selector}`);
return this;
}
where(condition) {
this.whereCondition = condition;
return this;
}
orderBy(selector) {
this.orderBySelector = selector;
return this;
}
groupBy(selector) {
this.groupBySelector = selector;
return this;
}
limit(value) {
this.limitSelector = value;
return this;
}
offset(value) {
this.offsetSelector = value;
return this;
}
async execute(...args) {
try {
if (this.prepared)
return this.executePrepared(...args);
const query = this.build();
return (await this.raw(query, ...args));
}
catch (e) {
throw new errors_1.QueryError(e);
}
}
async executePrepared(...argz) {
const repo = Repository_1.Repository.forModel(this.fromSelector, this.adapter.alias);
const { method, args, params } = this.prepared;
return repo.statement(method, ...args, params, ...argz);
}
async raw(rawInput, ...args) {
const { ctx, ctxArgs } = this.logCtx(args, this.raw);
const allowRawStatements = ctx.get("allowRawStatements");
if (!allowRawStatements)
throw new persistence_1.UnsupportedError("Raw statements are not allowed in the current configuration");
const results = await this.adapter.raw(rawInput, true, ...ctxArgs);
if (!this.selectSelector) {
return results;
}
const pkAttr = decorator_validation_1.Model.pk(this.fromSelector);
const processor = function recordProcessor(r) {
const id = r[pkAttr];
return this.adapter.revert(r, this.fromSelector, id, undefined, ctx);
}.bind(this);
if (Array.isArray(results))
return results.map(processor);
return processor(results);
}
prepareCondition(condition, ctx) {
// @ts-expect-error accessing protected properties
// eslint-disable-next-line prefer-const
let { attr1, operator, comparison } = condition;
const result = {};
switch (operator) {
case constants_1.GroupOperator.AND:
case constants_1.GroupOperator.OR: {
let side1 = attr1, side2 = comparison;
if (typeof attr1 !== "string") {
const condition1 = this.prepareCondition(attr1, ctx);
side1 = condition1.method;
result.args = [...(result.args || []), ...(condition1.args || [])];
}
if (comparison instanceof Condition_1.Condition) {
const condition2 = this.prepareCondition(comparison, ctx);
side2 = condition2.method;
result.args = [...(result.args || []), ...(condition2.args || [])];
}
result.method = `${side1} ${operator.toLowerCase()} ${side2}`;
break;
}
case constants_1.Operator.EQUAL:
result.method = attr1;
result.args = [...(result.args || []), comparison];
break;
case constants_1.Operator.DIFFERENT:
result.method = `${attr1} diff`;
result.args = [...(result.args || []), comparison];
break;
case constants_1.Operator.REGEXP:
result.method = `${attr1} matches`;
result.args = [...(result.args || []), comparison];
break;
case constants_1.Operator.BIGGER:
result.method = `${attr1} bigger`;
result.args = [...(result.args || []), comparison];
break;
case constants_1.Operator.BIGGER_EQ:
result.method = `${attr1} bigger than equal`;
break;
case constants_1.Operator.SMALLER:
result.method = `${attr1} less`;
result.args = [...(result.args || []), comparison];
break;
case constants_1.Operator.SMALLER_EQ:
result.method = `${attr1} less than equal`;
result.args = [...(result.args || []), comparison];
break;
case constants_1.Operator.IN:
result.method = `${attr1} in`;
result.args = [...(result.args || []), comparison];
break;
default:
throw new errors_1.QueryError(`Unsupported operator ${operator}`);
}
return result;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
squash(ctx) {
if (this.selectSelector && this.selectSelector.length)
return undefined;
if (this.groupBySelector)
return undefined;
if (this.countSelector)
return undefined;
if (this.maxSelector)
return undefined;
if (this.minSelector)
return undefined;
let attrFromWhere;
if (this.whereCondition) {
// if (this.orderBySelector) return undefined;
if (this.whereCondition["comparison"] instanceof Condition_1.Condition)
return undefined;
attrFromWhere = this.whereCondition["attr1"];
}
const order = this.orderBySelector
? this.orderBySelector
: attrFromWhere
? [attrFromWhere, constants_2.OrderDirection.DSC]
: [decorator_validation_1.Model.pk(this.fromSelector), constants_2.OrderDirection.DSC];
const [attrFromOrderBy, sort] = order;
const params = {
direction: sort,
};
if (this.limitSelector)
params.limit = this.limitSelector;
if (this.offsetSelector)
params.offset = this.offsetSelector;
const squashed = {
// listBy
class: this.fromSelector,
method: constants_1.PreparedStatementKeys.LIST_BY,
args: [attrFromOrderBy],
params: params,
};
if (attrFromWhere) {
// findBy
squashed.method = constants_1.PreparedStatementKeys.FIND_BY;
squashed.args = [
attrFromWhere,
this.whereCondition["comparison"],
];
squashed.params = params;
}
return squashed;
}
async prepare(ctx) {
ctx =
ctx ||
(await this.adapter.context(persistence_1.PersistenceKeys.QUERY, this.overrides || {}, this.fromSelector));
if (this.isSimpleQuery() &&
ctx.get("forcePrepareSimpleQueries")) {
const squashed = this.squash(ctx);
if (squashed) {
this.prepared = squashed;
return this;
}
}
const args = [];
const params = {};
const prepared = {
class: this.fromSelector,
args,
params,
};
const method = [types_1.QueryClause.FIND_BY];
if (this.whereCondition) {
const parsed = this.prepareCondition(this.whereCondition, ctx);
method.push(parsed.method);
if (parsed.args && parsed.args.length)
args.push(...parsed.args);
}
if (this.selectSelector)
method.push(types_1.QueryClause.SELECT, this.selectSelector.join(` ${types_1.QueryClause.AND.toLowerCase()} `));
if (this.orderBySelector) {
method.push(types_1.QueryClause.ORDER_BY, this.orderBySelector[0]);
params.direction = this.orderBySelector[1];
}
if (this.groupBySelector)
method.push(types_1.QueryClause.GROUP_BY, this.groupBySelector);
if (this.limitSelector)
params.limit = this.limitSelector;
if (this.offsetSelector) {
params.skip = this.offsetSelector;
}
prepared.method = (0, logging_1.toCamelCase)(method.join(" "));
prepared.params = params;
this.prepared = prepared;
return this;
}
isSimpleQuery() {
return !((this.selectSelector && this.selectSelector.length) ||
this.groupBySelector ||
this.countSelector ||
this.maxSelector ||
this.minSelector);
}
/**
* @description Creates a paginator for the query
* @summary Builds the query and wraps it in a RamPaginator to enable pagination of results.
* This allows retrieving large result sets in smaller chunks.
* @param {number} size - The page size (number of results per page)
* @return {Promise<Paginator<M, R, RawRamQuery<M>>>} A promise that resolves to a paginator for the query
*/
async paginate(size, ...args) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const ctx = args.pop(); // handled by prefix. kept for example for overrides
try {
return this.adapter.Paginator(this.prepared || this.build(), size, this.fromSelector);
}
catch (e) {
throw new errors_1.QueryError(e);
}
}
toString() {
return `${this.adapter.flavour} statement`;
}
}
exports.Statement = Statement;
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Array]),
__metadata("design:returntype", Object)
], Statement.prototype, "select", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [typeof (_a = typeof S !== "undefined" && S) === "function" ? _a : Object]),
__metadata("design:returntype", Object)
], Statement.prototype, "distinct", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [typeof (_b = typeof S !== "undefined" && S) === "function" ? _b : Object]),
__metadata("design:returntype", Object)
], Statement.prototype, "max", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [typeof (_c = typeof S !== "undefined" && S) === "function" ? _c : Object]),
__metadata("design:returntype", Object)
], Statement.prototype, "min", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [typeof (_d = typeof S !== "undefined" && S) === "function" ? _d : Object]),
__metadata("design:returntype", Object)
], Statement.prototype, "count", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Object)
], Statement.prototype, "from", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Condition_1.Condition]),
__metadata("design:returntype", Object)
], Statement.prototype, "where", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Array]),
__metadata("design:returntype", Object)
], Statement.prototype, "orderBy", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Object)
], Statement.prototype, "groupBy", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", Object)
], Statement.prototype, "limit", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", Object)
], Statement.prototype, "offset", null);
__decorate([
(0, logging_1.final)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [void 0]),
__metadata("design:returntype", Promise)
], Statement.prototype, "execute", null);
//# sourceMappingURL=Statement.js.map