Merge pull request #1823 from strongloop/feat/fix-observer-api
Add ObserverMixin to ModelBase typings
This commit is contained in:
commit
33884062f4
|
@ -1 +1,2 @@
|
|||
coverage
|
||||
dist
|
||||
|
|
|
@ -12,3 +12,4 @@ npm-debug.log
|
|||
.travis.yml
|
||||
.nyc_output
|
||||
dist
|
||||
types/__test__.ts
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
// 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 => {});
|
||||
|
||||
// Context is typed and provides `Model` property
|
||||
Data.observe('before save', async ctx => {
|
||||
console.log(ctx.Model.modelName);
|
||||
});
|
||||
|
||||
// ModelBaseClass can be assigned to `typeof ModelBase`
|
||||
// Please note that both `ModelBaseClass` and typeof ModelBase`
|
||||
// are different ways how to describe a class constructor of a model.
|
||||
// In this test we are verifying that the value returned by `createModel`
|
||||
// can be assigned to both types.
|
||||
const modelTypeof: typeof ModelBase = Data;
|
||||
const modelCls: ModelBaseClass = modelTypeof;
|
||||
});
|
||||
|
||||
//-------
|
||||
// PersistedModel should provide ObserverMixin APIs as static methods
|
||||
//-------
|
||||
(function () {
|
||||
const Product = db.createModel<PersistedModelClass>(
|
||||
'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'));
|
||||
});
|
||||
|
||||
// ctx.Model is a PersistedModel class constructor
|
||||
Product.observe('before save', async ctx => {
|
||||
await ctx.Model.findOne();
|
||||
});
|
||||
|
||||
// PersistedModelClass can be assigned to `typeof PersistedModel`
|
||||
// Please note that both `PersistedModelClass` and typeof PersistedModel`
|
||||
// are different ways how to describe a class constructor of a model.
|
||||
// In this test we are verifying that the value returned by `createModel`
|
||||
// can be assigned to both types.
|
||||
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<typeof KeyValueModel>('CacheItem');
|
||||
|
||||
// An operation hook can be installed
|
||||
CacheItem.observe('before save', async ctx => {});
|
||||
|
||||
// ctx.Model is a KeyValueModel class constructor
|
||||
CacheItem.observe('before save', async ctx => {
|
||||
await ctx.Model.expire('key', 100);
|
||||
});
|
||||
});
|
|
@ -6,6 +6,7 @@
|
|||
import {EventEmitter} from 'events';
|
||||
import {AnyObject, Options} from './common';
|
||||
import {DataSource} from './datasource';
|
||||
import {Listener, OperationHookContext} from './observer-mixin';
|
||||
|
||||
/**
|
||||
* Property types
|
||||
|
@ -243,6 +244,90 @@ 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.
|
||||
//
|
||||
// Ideally, we want to describe the context argument as
|
||||
// `OperationHookContext<this>`. Unfortunately, that's not supported by
|
||||
// TypeScript for static members. A nice workaround is described in
|
||||
// https://github.com/microsoft/TypeScript/issues/5863#issuecomment-410887254
|
||||
// - Describe the context using a generic argument `T`.
|
||||
// - Use `this: T` argument to let the compiler infer what's the target
|
||||
// model class we are going to observe.
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
static observe<T extends typeof ModelBase>(
|
||||
this: T,
|
||||
operation: string,
|
||||
listener: Listener<OperationHookContext<T>>,
|
||||
): 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
|
||||
*/
|
||||
static removeObserver<T extends typeof ModelBase>(
|
||||
this: T,
|
||||
operation: string,
|
||||
listener: Listener<OperationHookContext<T>>,
|
||||
): Listener<OperationHookContext<T>> | 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
|
||||
*/
|
||||
static clearObservers(operation: string): void;
|
||||
}
|
||||
|
||||
export type ModelBaseClass = typeof ModelBase;
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
// License text available at https://opensource.org/licenses/MIT
|
||||
|
||||
import {Callback, PromiseOrVoid} from './common';
|
||||
import {PersistedModel, PersistedModelClass} from './persisted-model';
|
||||
import {ModelBase} from './model';
|
||||
|
||||
export interface OperationHookContext<T extends typeof PersistedModel> {
|
||||
export interface OperationHookContext<T extends typeof ModelBase> {
|
||||
/**
|
||||
* The constructor of the model that triggered the operation.
|
||||
*/
|
||||
|
@ -19,7 +19,7 @@ export interface OperationHookContext<T extends typeof PersistedModel> {
|
|||
[property: string]: any;
|
||||
}
|
||||
|
||||
export type Listener<Ctx = OperationHookContext<PersistedModelClass>> = (
|
||||
export type Listener<Ctx = OperationHookContext<typeof ModelBase>> = (
|
||||
ctx: Ctx, next: (err?: any) => void
|
||||
) => void;
|
||||
|
||||
|
@ -52,7 +52,11 @@ export interface ObserverMixin {
|
|||
* `this` set to the model constructor, e.g. `User`.
|
||||
* @end
|
||||
*/
|
||||
observe(operation: string, listener: Listener<OperationHookContext<PersistedModelClass>>): void;
|
||||
observe<T extends typeof ModelBase>(
|
||||
this: T,
|
||||
operation: string,
|
||||
listener: Listener<OperationHookContext<T>>,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Unregister an asynchronous observer for the given operation (event).
|
||||
|
@ -70,7 +74,11 @@ export interface ObserverMixin {
|
|||
* @callback {function} listener The listener function.
|
||||
* @end
|
||||
*/
|
||||
removeObserver(operation: string, listener: Listener<OperationHookContext<PersistedModelClass>>): Listener<OperationHookContext<PersistedModelClass>> | undefined;
|
||||
removeObserver<T extends typeof ModelBase>(
|
||||
this: T,
|
||||
operation: string,
|
||||
listener: Listener<OperationHookContext<T>>,
|
||||
): Listener<OperationHookContext<T>> | undefined;
|
||||
|
||||
/**
|
||||
* Unregister all asynchronous observers for the given operation (event).
|
||||
|
|
Loading…
Reference in New Issue