Easily create Pre
and Post
hooks on JavaScript objects.
npm install surrogate
or
yarn add surrogate
There are a couple ways to manage Surrogate. The first is to utilize an exposed helper function, wrapSurrogate
, to wrap objects and register pre and post methods through it.
import { wrapSurrogate, Surrogate, NextParameters } from 'surrogate';
const guitar: Surrogate<Guitar> = wrapSurrogate(new Guitar(), options);
guitar.getSurrogate().registerPreHook('play', ({ next }: NextParameters<Guitar>) => {
// do things before running 'play'
next.next();
});
Check examples for expanded samples.
Option | Type | Default Value | Description |
---|---|---|---|
useSingleton? | boolean | true | Informs Surrogate to operate as a Singleton |
useContext? | any | T | The context in which to call surrogate handlers. Handler specific contexts take precedence. |
provide? | any | null | User provided content to pass to handlers and conditionals. Handler specific values take precedence |
silenceErrors? | boolean | SilenceErrors | false | Specify if Surrogate error output should be silenced. Accepts boolean or function that will receive the error and return a boolean |
maintainContext? | boolean | string | string[] | false | Maintain context for methods without hooks. Can be a method name or array of method names |
After wrapping your instance with Surrogate new methods are available, getSurrogate
, which, when called will return an instance of Surrogate Event Manager that allows management of pre and post methods and disposeSurrogate
which will restore all methods, deregister hooks and remove the current instance from Surrogate management.
Method | Parameters | Returns | Description |
---|---|---|---|
getSurrogate | n/a | SurrogateEventManager | Provides capabilities for managing method hooks |
disposeSurrogate | n/a | T | Cleans current instance of Surrogate handlers |
bypassSurrogate | n/a | T | Allows calling target methods without running handlers |
Method | Parameters | Returns | Description |
---|---|---|---|
registerPreHook | (event: string, handler: SurrogateHandlers, options?: SurrogateHandlerOptions) | SurrogateEventManager | Registers a pre hook |
registerPostHook | (event: string, handler: SurrogateHandlers, options?: SurrogateHandlerOptions) | SurrogateEventManager | Registers a post hook |
deregisterPreHooks | (event: string) | SurrogateEventManager | Deregisters pre hooks for the given event |
deregisterPostHooks | (event: string) | SurrogateEventManager | Deregisters post hooks for the given event |
deregisterHooks | n/a | SurrogateEventManager | Removes all hooks for the current instance. |
SurrogateHandlers is any function that accepts a NextParameters
object which can be used to control flow through pre and post hooks. It may also be the name of a method on the target object.
Common parameters passed to all handlers and conditional functions. (runConditions, runOnError, runOnBail)
Property | Member Of | Type | Description |
---|---|---|---|
action | ProviderParameters | string | The current target method. |
correlationId | ProviderParameters | string | Unique identifier for the current hook pipeline |
instance | ProviderParameters | T | The current unwrapped instance |
error? | ProviderParameters | Error | An error object, if passed |
hookType | ProviderParameters | string | Provides the current hook type as a string. |
receivedArguments | ProviderParameters | any[] | Arguments received from the previous handler. |
currentArgs | ProviderParameters | any[] | Array of potentially modified arguments passed to original method invoked by surrogate |
originalArgs | ProviderParameters | any[] | Array of arguments passed to the instance invoked method |
timeTracker | ProviderParameters | TimeTracker | Provides access to the current time tracker |
result | ProviderParameters | any | Result of original method invocation if run |
provide | ProviderParameters | any | User provided content to pass to handlers and conditionals. Default value is null |
NextParameters
is passed to all hook handlers. In addition to CommonParameters
you'll receive the current Surrogate wrapped instance and an INext
object that provides functionality to skip hooks or continue to the next hook for execution.
Property | Type | Description |
---|---|---|
surrogate | Surrogate | Provides handler access to surrogate wrapped instance. |
next | INext | Object that provides flow control capabilities |
Method | Member Of | Parameters | Return Value | Description |
---|---|---|---|---|
geStartTime | TimeTracker | none | number | Returns the start time of the current pipeline. |
getTotalDuration | TimeTracker | none | number | Returns the total duration of the current pipeline. |
getHookStartTime | TimeTracker | none | number | Returns the start time of the current hook. |
getLastRunDuration | TimeTracker | none | number | Returns the duration of the last hook. |
getTimeSinceLastRun | TimeTracker | none | number | Returns the duration since the last hook completed. |
Method | Member Of | Parameters | Default Value | Description |
---|---|---|---|---|
skip | INext | (skipAmount?: number) | 1 | Method that will skip the next 'skipAmount' handlers |
skipWith | INext | (skipAmount: number, ...args: any[]) | [] | Same as skip but will accept any number of arguments, passing to the next executed handler |
next | INext | (nextOptions?: NextOptions) | n/a | Calling next may advance to the next hook. |
Property | Type | Description |
---|---|---|
error? | Error | Passing an Error may result in the error being thrown, depending on supplied handler options |
using? | any[] | An array of values to pass to the next handler |
bail? | boolean | Indicates all subsequent handler executions should stop immediately. Target method is not called if bailing in pre hook |
replace? | any | Replaces the original arguments passed to the target method. |
bailWith? | any | If bailing, the supplied value should be returned |
When registering a hook you may provide any of the following options.
Property | Type | Default Value | Description |
---|---|---|---|
useNext? | boolean | true | true indicates usage of the INext object to control flow, otherwise Surrogate makes a determination when to advance |
noArgs? | boolean | false | Specify that NextParameters should NOT be passed to a handler |
ignoreErrors? | boolean | false | If true and an Error is passed or caught Surrogate will not throw. |
useContext? | any | T | The context in which to call surrogate handlers. |
wrapper? | MethodWrappers | sync | Tells Surrogate if it is managing synchronous or asynchronous methods. |
runConditions? | RunCondition | RunCondition[] | n/a | Conditions to determine if a handler should be executed. |
runOnError? | RunOnError | RunOnError[] | n/a | Functions to run in the event of handler error. Runs regardless of ignoreError |
runOnBail? | RunOnBail | RunOnBail[] | n/a | Functions to run in the event of handler bailing. |
priority? | number | 0 | Used to determine the order in which handlers are executed. Larger numbers have higher priority |
useContext
option defaults to the current instance. Specifying context should be surrogate
will allow hooks in the pipeline to trigger additional hooks. This can be extraordinarily useful but has the potential to cause recursive loops.
A RunCondition is a function that receives RunConditionParameters
, which includes CommonParameters and returns a boolean indicating if the current handler should be executed(true
) or skipped(false
). All run conditions are executed synchronously and all conditions must be true for the handler to execute.
Property | Member Of | Type | Description |
---|---|---|---|
didError | RunConditionParameters | boolean | Indicates if the previous handler passed an Error. |
valueFromCondition | RunConditionParameters | any | Value passed forward from previous run condition |
didReceiveFromLastCondition | RunConditionParameters | boolean | Indicates if the previous condition passed a value. |
passToNextCondition() | RunConditionParameters | any | Function to pass a value to next condition |
RunOnError
is a function that receives RunOnErrorParameters
, which includes CommonParameters and the ability to recover from an error.
Property | Member Of | Type | Description |
---|---|---|---|
error | RunOnErrorParameters | Error | An error object received or caught from a handler |
recoverFromError() | RunOnErrorParameters | boolean | Function to recover from an error |
RunOnBail
is a function that receives RunOnBailParameters
, which includes CommonParameters and the ability to recover from a bailing handler.
Property | Member Of | Type | Description |
---|---|---|---|
bailWith() | RunOnBailParameters | any | Function that accepts a value to bail with |
recoverFromBail() | RunOnBailParameters | boolean | Function to recover from a bailing handler |
Perhaps a more convenient way to register hooks is with decorators.
import { SurrogateDelegate } from 'surrogate';
@SurrogateDelegate()
class Guitar {}
SurrogateDelegate
registers your class and will automatically wrap instances of that class with Surrogate.
It supports all options from SurrogateOptions
as well as option locateWith
which may be provided to assist Surrogate in
locating method decorators for a particular class. Should only be necessary if multiple class decorators are utilized.
import { SurrogateDelegate } from 'surrogate';
@SurrogateDelegate({
locateWith: Guitar,
})
@MyOtherClassDecorator()
class Guitar {}
If you wish to use Surrogate
methods inside this instance of your class you must extend SurrogateMethods
interface.
import { SurrogateDelegate, SurrogateMethods } from 'surrogate';
export interface Guitar extends SurrogateMethods<Guitar> {}
@SurrogateDelegate()
export class Guitar {}
Registering hooks:
import { NextParameters, SurrogatePre, SurrogatePost, SurrogateDelegate } from 'surrogate';
@SurrogateDelegate()
class Guitar {
@SurrogatePre(({ next }: NextParameters<Guitar>) => {
console.log(`Tuning guitar`);
next.next();
})
@SurrogatePost<Guitar>({
handler: () => {
console.log(`Put guitar away`);
},
options: {
useNext: false,
},
})
play() {
console.log(`playing guitar`);
}
}
All Surrogate[Async](Pre|Post)
decorators accept SurrogateDecoratorOptions
or an array of options and must decorate the intended hooked method.
Option | Type | Default Value | Description |
---|---|---|---|
handler | SurrogateHandlers | n/a | This function or array of functions will run before or after the decorated method |
options? | SurrogateHandlerOptions | {} | Options defining Surrogate handler behavior |
All Next[Async](Pre|Post)
decorators accept NextDecoratorOptions
or an array of options. Any method decorated with Next* will be registered as a Surrogate handler. Therefore, you must supply the target method the Next* method will run with.
Option | Type | Default Value | Description |
---|---|---|---|
action | keyof T | string | (keyof T | string )[] | n/a | Name of the target decorated method, accepts regexp string for matching |
options? | SurrogateHandlerOptions | {} | Options defining Surrogate handler behavior |
@SurrogateDelegate({
locateWith: Account,
})
@Table({
timestamps: true,
})
class Account extends Model<Account> {
@NextAsyncPreAndPost<Account>({
action: ['update', 'save'],
options: {
useNext: false,
},
})
protected async logActions({
instance: model,
originalArgs,
timeTracker,
hookType,
action,
}: NextParameters<Account>) {
const keys = (model.changed() || []) as (keyof Account)[];
const changes = keys.reduce(
(changed, key) => ({
...changed,
[key]: {
current: model.getDataValue(key),
previous: model.previous(key),
},
}),
{},
);
telemetry.trackEvent({
name: `${hookType}|${action}|Logging`,
properties: {
action,
changes,
hookType,
model: model.toJSON(),
arguments: originalArgs,
},
measurements: {
lastRunDuration: timeTracker.getDurationSinceLastRun(),
},
});
}
}
Many thanks to Dale for transferring the npm package name.