UNPKG

trilogy

Version:

TypeScript SQLite layer with support for both native C++ & pure JavaScript drivers.

186 lines (185 loc) 7.11 kB
import { invariant } from './util'; import { normalizeCriteria } from './helpers'; export var Hook; (function (Hook) { Hook["OnQuery"] = "ON_QUERY"; Hook["BeforeCreate"] = "BEFORE_CREATE"; Hook["AfterCreate"] = "AFTER_CREATE"; Hook["BeforeUpdate"] = "BEFORE_UPDATE"; Hook["AfterUpdate"] = "AFTER_UPDATE"; Hook["BeforeRemove"] = "BEFORE_REMOVE"; Hook["AfterRemove"] = "AFTER_REMOVE"; })(Hook || (Hook = {})); export const EventCancellation = Symbol('trilogy.EventCancellation'); /** * Base implementation of lifecycle hooks inherited by all model instances. */ export class Hooks { constructor() { this._onQuery = new Set(); this._onQueryAll = new Set(); this._beforeCreate = new Set(); this._afterCreate = new Set(); this._beforeUpdate = new Set(); this._afterUpdate = new Set(); this._beforeRemove = new Set(); this._afterRemove = new Set(); } /** * The `onQuery` hook is called each time a query is run on the database, * and receives the query in string form. * * @param fn Function called when the hook is triggered * @param [options] * * @returns Unsubscribe function that removes the subscriber when called */ onQuery(fn, options = {}) { invariant(typeof fn === 'function', 'hook callbacks must be of type function'); if (options.includeInternal) { this._onQuery.add(fn); this._onQueryAll.add(fn); return () => this._onQueryAll.delete(fn) && this._onQuery.delete(fn); } else { this._onQuery.add(fn); return () => this._onQuery.delete(fn); } } /** * Before an object is created, the beforeCreate hook is called with the * object. * * @remarks * This hook occurs before casting, so if a subscriber to this hook * modifies the incoming object those changes will be subject to casting. * It's also possible to prevent the object from being created entirely * by returning the EventCancellation symbol from a subscriber callback. * * @param fn Function called when the hook is triggered * * @returns Unsubscribe function that removes the subscriber when called */ beforeCreate(fn) { invariant(typeof fn === 'function', 'hook callbacks must be of type function'); this._beforeCreate.add(fn); return () => this._beforeCreate.delete(fn); } /** * When an object is created, that object is returned to you and the * `afterCreate` hook is called with it. * * @param fn Function called when the hook is triggered * * @returns Unsubscribe function that removes the subscriber when called */ afterCreate(fn) { invariant(typeof fn === 'function', 'hook callbacks must be of type function'); this._afterCreate.add(fn); return () => this._afterCreate.delete(fn); } /** * Prior to an object being updated the `beforeUpdate` hook is called with the * update delta, or the incoming changes to be made, as well as the criteria. * * @remarks * Casting occurs after this hook. A subscriber could choose to cancel the * update by returning the EventCancellation symbol or alter the selection * criteria. * * @param fn Function called when the hook is triggered * * @returns Unsubscribe function that removes the subscriber when called */ beforeUpdate(fn) { invariant(typeof fn === 'function', 'hook callbacks must be of type function'); this._beforeUpdate.add(fn); return () => this._beforeUpdate.delete(fn); } /** * Subscribers to the `afterUpdate` hook receive modified objects after they * are updated. * * @param fn Function called when the hook is triggered * * @returns Unsubscribe function that removes the subscriber when called */ afterUpdate(fn) { invariant(typeof fn === 'function', 'hook callbacks must be of type function'); this._afterUpdate.add(fn); return () => this._afterUpdate.delete(fn); } /** * Before object removal, the criteria for selecting those objects is passed * to the `beforeRemove` hook. * * @remarks * Casting occurs after this hook. Subscribers can modify the selection * criteria or prevent the removal entirely by returning the `EventCancellation` * symbol. * * @param fn Function called when the hook is triggered * * @returns Unsubscribe function that removes the subscriber when called */ beforeRemove(fn) { invariant(typeof fn === 'function', 'hook callbacks must be of type function'); this._beforeRemove.add(fn); return () => this._beforeRemove.delete(fn); } /** * A list of any removed objects is passed to the `afterRemove` hook. * * @param fn Function called when the hook is triggered * * @returns Unsubscribe function that removes the subscriber when called */ afterRemove(fn) { invariant(typeof fn === 'function', 'hook callbacks must be of type function'); this._afterRemove.add(fn); return () => this._afterRemove.delete(fn); } async _callHook(hook, arg, options) { const result = { prevented: false }; if (hook === Hook.OnQuery) { const [query, internal] = arg; const fns = internal ? this._onQueryAll : this._onQuery; for (const fn of fns) { if (await fn(query) === EventCancellation) { result.prevented = true; } } return result; } const fns = { [Hook.BeforeCreate]: this._beforeCreate, [Hook.AfterCreate]: this._afterCreate, [Hook.BeforeUpdate]: this._beforeUpdate, [Hook.AfterUpdate]: this._afterUpdate, [Hook.BeforeRemove]: this._beforeRemove, [Hook.AfterRemove]: this._afterRemove }[hook]; for (const fn of fns) { let thisResult; if (hook === Hook.BeforeUpdate) { const [data, criteria] = arg; thisResult = await fn(data, normalizeCriteria(criteria), options || {}); } else if (hook === Hook.BeforeRemove) { thisResult = await fn(normalizeCriteria(arg), options || {}); } else if (hook === Hook.BeforeCreate || hook === Hook.AfterCreate) { thisResult = await fn(arg, options || {}); } else { thisResult = await fn(arg, options || {}); } if (thisResult === EventCancellation) { result.prevented = true; } } return result; } }