UNPKG

@coko/server

Version:

Reusable server for use by Coko's projects

328 lines 11.7 kB
"use strict"; 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