From 1a8af555b999da31642127a1e912830a2ec5ca93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 28 Feb 2020 10:07:26 +0100 Subject: [PATCH] Add ObserverMixin to PersistedModelClass typings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miroslav Bajtoš --- .eslintignore | 1 + .npmignore | 1 + types/__test__.ts | 67 +++++++++++++++++++++++++++++++++++++++ types/model.d.ts | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 types/__test__.ts diff --git a/.eslintignore b/.eslintignore index 4ebc8aea5..29f44348c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ coverage +dist diff --git a/.npmignore b/.npmignore index 0718b8a28..6a667c22e 100644 --- a/.npmignore +++ b/.npmignore @@ -12,3 +12,4 @@ npm-debug.log .travis.yml .nyc_output dist +types/__test__.ts diff --git a/types/__test__.ts b/types/__test__.ts new file mode 100644 index 000000000..e1c3dbc8f --- /dev/null +++ b/types/__test__.ts @@ -0,0 +1,67 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// A test file to verify types described by our .d.ts files. +// The code in this file is only compiled, we don't run it via Mocha. + +import { + DataSource, + KeyValueModel, + ModelBase, + ModelBaseClass, + PersistedModel, + PersistedModelClass, +} from '..'; + +const db = new DataSource('db', {connector: 'memory'}); + +//------- +// ModelBase should provide ObserverMixin APIs as static methods +//------- +// +(function() { + const Data = db.createModel('Data'); + + // An operation hook can be installed + Data.observe('before save', async ctx => {}); + + // ModelBaseClass can be assigned to `typeof ModelBase` + const modelTypeof: typeof ModelBase = Data; + const modelCls: ModelBaseClass = modelTypeof; +}); + +//------- +// PersistedModel should provide ObserverMixin APIs as static methods +//------- +(function () { + const Product = db.createModel( + 'Product', + {name: String}, + {strict: true} + ); + + // It accepts async function + Product.observe('before save', async ctx => {}); + + // It accepts callback-based function + Product.observe('before save', (ctx, next) => { + next(new Error('test error')); + }); + + // PersistedModelClass can be assigned to `typeof PersistedModel` + const modelTypeof: typeof PersistedModel = Product; + const modelCls: PersistedModelClass = modelTypeof; +}); + +//------- +// KeyValueModel should provide ObserverMixin APIs as static methods +//------- +(function () { + const kvdb = new DataSource({connector: 'kv-memory'}); + const CacheItem = kvdb.createModel('CacheItem'); + + // An operation hook can be installed + CacheItem.observe('before save', async ctx => {}); +}); diff --git a/types/model.d.ts b/types/model.d.ts index 9543ec366..96dd73e6c 100644 --- a/types/model.d.ts +++ b/types/model.d.ts @@ -6,6 +6,7 @@ import {EventEmitter} from 'events'; import {AnyObject, Options} from './common'; import {DataSource} from './datasource'; +import {Listener} from './observer-mixin'; /** * Property types @@ -243,6 +244,85 @@ export declare class ModelBase { anotherClass: string | ModelBaseClass | object, options?: Options, ): ModelBaseClass; + + // ObserverMixin members are added as static methods, this is difficult to + // describe in TypeScript in a way that's easy to use by consumers. + // As a workaround, we include a copy of ObserverMixin members here. + // + // See also https://github.com/microsoft/TypeScript/issues/5863#issuecomment-410887254 + // for more information about using `this` in static members. + + /** + * Register an asynchronous observer for the given operation (event). + * + * Example: + * + * Registers a `before save` observer for a given model. + * + * ```javascript + * MyModel.observe('before save', function filterProperties(ctx, next) { + * if (ctx.options && ctx.options.skipPropertyFilter) return next(); + * if (ctx.instance) { + * FILTERED_PROPERTIES.forEach(function(p) { + * ctx.instance.unsetAttribute(p); + * }); + * } else { + * FILTERED_PROPERTIES.forEach(function(p) { + * delete ctx.data[p]; + * }); + * } + * next(); + * }); + * ``` + * + * @param {String} operation The operation name. + * @callback {function} listener The listener function. It will be invoked with + * `this` set to the model constructor, e.g. `User`. + * @end + */ + observe( + this: T, + operation: string, + listener: Listener + ): void; + + /** + * Unregister an asynchronous observer for the given operation (event). + * + * Example: + * + * ```javascript + * MyModel.removeObserver('before save', function removedObserver(ctx, next) { + * // some logic user want to apply to the removed observer... + * next(); + * }); + * ``` + * + * @param {String} operation The operation name. + * @callback {function} listener The listener function. + * @end + */ + removeObserver( + this: T, + operation: string, + listener: Listener + ): Listener | undefined; + + /** + * Unregister all asynchronous observers for the given operation (event). + * + * Example: + * + * Remove all observers connected to the `before save` operation. + * + * ```javascript + * MyModel.clearObservers('before save'); + * ``` + * + * @param {String} operation The operation name. + * @end + */ + clearObservers(operation: string): void; } export type ModelBaseClass = typeof ModelBase;