@coko/server
Version:
Reusable server for use by Coko's projects
328 lines • 11.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const objection_1 = require("objection");
const merge_1 = __importDefault(require("lodash/merge"));
const uuid_1 = require("uuid");
const ajv_formats_1 = __importDefault(require("ajv-formats"));
const db_1 = require("../db");
const logger_1 = __importDefault(require("../logger"));
const useTransaction_1 = __importDefault(require("./useTransaction"));
const types_1 = require("./_helpers/types");
objection_1.Model.knex(db_1.db);
class BaseModel extends objection_1.Model {
id;
created;
updated;
type;
static createValidator() {
return new objection_1.AjvValidator({
onCreateAjv: (ajv) => {
(0, ajv_formats_1.default)(ajv);
},
});
}
static get jsonSchema() {
let schema;
const mergeSchema = (additionalSchema) => {
if (additionalSchema) {
schema = (0, merge_1.default)(schema, additionalSchema);
}
};
// Crawls up the prototype chain to collect schema information from models and extended models
const getSchemasRecursively = (object) => {
mergeSchema(object.schema);
const proto = Object.getPrototypeOf(object);
if (proto.name !== 'BaseModel')
getSchemasRecursively(proto);
};
getSchemasRecursively(this);
const baseSchema = {
type: 'object',
properties: {
type: { type: 'string' },
id: { type: 'string', format: 'uuid' },
created: types_1.dateNotNullable,
updated: types_1.dateNotNullable,
},
additionalProperties: false,
};
if (schema) {
return (0, merge_1.default)(baseSchema, schema);
}
return baseSchema;
}
$beforeInsert() {
this.id = this.id || (0, uuid_1.v4)();
this.created = new Date().toISOString();
this.updated = this.created;
}
$beforeUpdate() {
this.updated = new Date().toISOString();
}
static async find(data, options = {}) {
try {
const ModelClass = this;
const { trx, related, orderBy, page, pageSize } = options;
return (0, useTransaction_1.default)(async (tr) => {
let queryBuilder = ModelClass.query(tr);
const isPaginated = Number.isInteger(page) && Number.isInteger(pageSize);
if (orderBy) {
queryBuilder = queryBuilder.orderBy(orderBy);
}
if ((Number.isInteger(page) && !Number.isInteger(pageSize)) ||
(!Number.isInteger(page) && Number.isInteger(pageSize))) {
throw new Error('both page and pageSize integers needed for paginated results');
}
if (isPaginated) {
if (page < 0) {
throw new Error('invalid index for page (page should be an integer and greater than or equal to 0)');
}
if (pageSize <= 0) {
throw new Error('invalid size for pageSize (pageSize should be an integer and greater than 0)');
}
queryBuilder = queryBuilder.page(page, pageSize);
}
if (related) {
queryBuilder = queryBuilder.withGraphFetched(related);
}
const rawResult = await queryBuilder.where(data);
if (isPaginated) {
const result = rawResult;
return {
result: result.results,
totalCount: result.total,
};
}
const result = rawResult;
return {
result: result,
totalCount: result.length,
};
}, {
trx,
passedTrxOnly: true,
});
}
catch (e) {
logger_1.default.error('Base model: find failed', e);
throw e;
}
}
static async findByIds(ids, options = {}) {
try {
const ModelClass = this;
const { trx, related } = options;
return (0, useTransaction_1.default)(async (tr) => {
let queryBuilder = ModelClass.query(tr);
if (related) {
queryBuilder = queryBuilder.withGraphFetched(related);
}
const result = await queryBuilder.findByIds(ids);
if (result.length < ids.length) {
const delta = ids.filter(id => !result.map(res => res.id).includes(id));
throw new Error(`id ${delta} not found`);
}
return result;
}, {
trx,
passedTrxOnly: true,
});
}
catch (e) {
logger_1.default.error('Base model: findByIds failed', e);
throw e;
}
}
static async findById(id, options = {}) {
try {
const ModelClass = this;
const { trx, related } = options;
return (0, useTransaction_1.default)(async (tr) => {
let queryBuilder = ModelClass.query(tr);
if (related) {
queryBuilder = queryBuilder.withGraphFetched(related);
}
const result = await queryBuilder.findById(id).throwIfNotFound();
return result;
}, {
trx,
passedTrxOnly: true,
});
}
catch (e) {
logger_1.default.error('Base model: findById failed', e);
throw e;
}
}
static async findOne(data, options = {}) {
try {
const ModelClass = this;
const { trx, related } = options;
return (0, useTransaction_1.default)(async (tr) => {
let queryBuilder = ModelClass.query(tr);
if (related) {
queryBuilder = queryBuilder.withGraphFetched(related);
}
return queryBuilder.findOne(data);
}, {
trx,
passedTrxOnly: true,
});
}
catch (e) {
logger_1.default.error('Base model: findOne failed', e);
throw e;
}
}
static async insert(data, options = {}) {
try {
const ModelClass = this;
const { trx, related } = options;
return (0, useTransaction_1.default)(async (tr) => {
let queryBuilder = ModelClass.query(tr);
if (related) {
queryBuilder = queryBuilder.withGraphFetched(related);
}
if (Array.isArray(data)) {
const res = await queryBuilder.insert(data);
return res;
}
const res = await queryBuilder.insert(data);
return res;
}, {
trx,
passedTrxOnly: true,
});
}
catch (e) {
logger_1.default.error('Base model: insert failed', e);
throw e;
}
}
// INSTANCE METHOD
async patch(data, options = {}) {
try {
const { trx } = options;
if (!data) {
throw new Error('Patch is empty');
}
return (0, useTransaction_1.default)(async (tr) => {
const q = this.$query(tr);
const result = await q.patchAndFetch(data);
return result;
}, {
trx,
passedTrxOnly: true,
});
}
catch (e) {
logger_1.default.error('Base model: patch failed', e);
throw e;
}
}
static async patchAndFetchById(id, data, options = {}) {
try {
const { trx, related } = options;
const ModelClass = this;
return (0, useTransaction_1.default)(async (tr) => {
let queryBuilder = ModelClass.query(tr);
if (related) {
queryBuilder = queryBuilder.withGraphFetched(related);
}
const result = await queryBuilder
.patchAndFetchById(id, data)
.throwIfNotFound();
return result;
}, {
trx,
passedTrxOnly: true,
});
}
catch (e) {
logger_1.default.error('Base model: patchAndFetchById failed', e);
throw e;
}
}
// INSTANCE METHOD
async update(data, options = {}) {
try {
const { trx } = options;
if (!data) {
throw new Error('Patch is empty');
}
return (0, useTransaction_1.default)(async (tr) => {
const q = this.$query(tr);
const result = await q.updateAndFetch(data);
return result;
}, {
trx,
passedTrxOnly: true,
});
}
catch (e) {
logger_1.default.error('Base model: update failed', e);
throw e;
}
}
static async updateAndFetchById(id, data, options = {}) {
try {
const ModelClass = this;
const { trx, related } = options;
return (0, useTransaction_1.default)(async (tr) => {
let queryBuilder = ModelClass.query(tr);
if (related) {
queryBuilder = queryBuilder.withGraphFetched(related);
}
const result = await queryBuilder
.updateAndFetchById(id, data)
.throwIfNotFound();
return result;
}, {
trx,
passedTrxOnly: true,
});
}
catch (e) {
logger_1.default.error('Base model: updateAndFetchById failed', e);
throw e;
}
}
static async deleteById(id, options = {}) {
try {
const ModelClass = this;
const res = await ModelClass.query(options.trx)
.deleteById(id)
.throwIfNotFound();
return res;
}
catch (e) {
logger_1.default.error(`${this.name} model: deleteById failed.`, e);
throw e;
}
}
static async deleteByIds(ids, options = {}) {
try {
const ModelClass = this;
const rows = await ModelClass.query(options.trx).findByIds(ids);
if (rows.length < ids.length) {
const diff = ids.filter(id => !rows.map(res => res.id).includes(id));
throw new Error(`id${diff.length > 1 ? 's' : ''} ${diff.join(', ')} not found`);
}
const result = await ModelClass.query(options.trx)
.delete()
.whereIn('id', ids)
.returning('id');
return result.length;
}
catch (e) {
logger_1.default.error(`${this.name} model: deleteByIds failed`, e);
throw e;
}
}
}
BaseModel.pickJsonSchemaProperties = false;
exports.default = BaseModel;
//# sourceMappingURL=base.model.js.map