Skip to content

trigger ​

Runs actions for the items of create, update, patch and remove calls that match the conditions of a subscription. The conditions are predicates, tested against the data before the call, the items before the call and the items after it. The items come from defineStash, so subscriptions whose fetches have equal params share them.

Register it as a before and an after hook, or as an around hook.

ts
  import { 
trigger
} from 'feathers-utils/hooks';

Example ​

ts
import { trigger } from 'feathers-utils/hooks'

const notifyOnPublish = trigger<PostsContext>({
  name: 'notifyOnPublish',
  before: (post) => !post.published,
  result: (post) => post.published,
  action: async ({ result }) => sendMail(result),
})

app.service('posts').hooks({ around: { patch: [notifyOnPublish] } })

Type declaration ​

Show Type Declarations
ts
export type TriggerActionOptions<H extends HookContext, T> = {
  context: H
  /** All items of the call, matching or not */
  items: StashedItem<T>[]
  subscription: TriggerSubscription<H, T>
}
export type TriggerAction<H extends HookContext, T> = (
  item: StashedItem<T>,
  options: TriggerActionOptions<H, T>,
) => Promisable<void>
export type TriggerBatchAction<H extends HookContext, T> = (
  items: StashedItem<T>[],
  options: TriggerActionOptions<H, T>,
) => Promisable<void>
export type TriggerSubscriptionOptions<
  H extends HookContext = HookContext,
  T = ResultSingleHookContext<H>,
> = {
  /**
   * Names the subscription: `params.skipHooks` skips it by this name (see
   * `shouldSkip`), and the debug log shows it.
   */
  name?: string
  /**
   * Whether the subscription applies to the call, tested once before it -
   * e.g. with `isContext` or `isProvider`.
   *
   * @example
   * ```ts
   * trigger({ iff: isContext({ path: 'posts', method: 'patch' }), action })
   * ```
   */
  iff?: boolean | PredicateFn<H>
  /**
   * Tests `context.data` before the call - on multi create every item on its
   * own, and the action only runs for the items whose data matched. Doesn't
   * match without `context.data`, e.g. on `remove`.
   *
   * On multi create, the data is paired with its result by its id, or without
   * one by its position. Data without an id doesn't match if a later hook adds
   * or removes an item, since the positions no longer line up.
   */
  data?: (data: DataSingleHookContext<H>, context: H) => Promisable<boolean>
  /**
   * Tests the item before the call. Doesn't match an item without one, e.g.
   * on `create`. Fetches the items before the call.
   *
   * @example
   * ```ts
   * trigger({ before: (post) => !post.published, action })
   * ```
   */
  before?: (before: T, context: H) => Promisable<boolean>
  /**
   * Tests the item after the call.
   *
   * @example
   * ```ts
   * trigger({ result: (post) => post.published, action })
   * ```
   */
  result?: (result: T, context: H) => Promisable<boolean>
  /**
   * Tests the item before and after the call, to compare them. Fetches the
   * items before the call.
   *
   * @example
   * ```ts
   * trigger({
   *   change: ({ before, result }) => before?.status !== result?.status,
   *   action,
   * })
   * ```
   */
  change?: (item: StashedItem<T>, context: H) => Promisable<boolean>
  /**
   * Adjusts the params of the fetches before and after the call - e.g. to
   * populate the items. See `defineStash`.
   */
  transformParams?: StashTransformParams<H>
  /**
   * Fetches the items before the call. Resolved once per call, before it.
   *
   * @default `true` with `before` or `change`, `false` otherwise
   */
  fetchBefore?: boolean | PredicateFn<H>
  /**
   * Waits for the actions before the call returns, and fails the call if one
   * of them fails. Without, the errors of the actions are logged with
   * `console.error`. Resolved once per call, before it.
   *
   * @default true
   */
  isBlocking?: boolean | PredicateFn<H>
  /**
   * Logs why the subscription skips the call or an item. Resolved once per
   * call, before it.
   *
   * @default false
   */
  debug?: boolean | PredicateFn<H>
  /**
   * Runs for every matching item. Use either `action` or `batchAction`.
   */
  action?: TriggerAction<H, T>
  /**
   * Runs once with all matching items, not at all without any. Use either
   * `action` or `batchAction`.
   */
  batchAction?: TriggerBatchAction<H, T>
}
export type TriggerSubscription<
  H extends HookContext = HookContext,
  T = ResultSingleHookContext<H>,
> = TriggerSubscriptionOptions<H, T> &
  (
    | {
        action: TriggerAction<H, T>
        batchAction?: never
      }
    | {
        batchAction: TriggerBatchAction<H, T>
        action?: never
      }
  )
export type TriggerOptions<
  H extends HookContext = HookContext,
  T = ResultSingleHookContext<H>,
> =
  | MaybeArray<TriggerSubscription<H, T>>
  | ((
      context: H,
    ) => Promisable<MaybeArray<TriggerSubscription<H, T>> | undefined>)
/**
 * Runs actions for the items of `create`, `update`, `patch` and `remove`
 * calls that match the conditions of a subscription. The conditions are
 * predicates, tested against the data before the call, the items before the
 * call and the items after it. The items come from {@link defineStash}, so
 * subscriptions whose fetches have equal params share them.
 *
 * Register it as a `before` and an `after` hook, or as an `around` hook.
 *
 * @example
 * ```ts
 *
 *
 * const notifyOnPublish = trigger<PostsContext>({
 *   name: 'notifyOnPublish',
 *   before: (post) => !post.published,
 *   result: (post) => post.published,
 *   action: async ({ result }) => sendMail(result),
 * })
 *
 * app.service('posts').hooks({ around: { patch: [notifyOnPublish] } })
 * ```
 *
 * @see https://utils.feathersjs.com/hooks/trigger.html
 */
export declare function trigger<
  H extends HookContext = HookContext,
  T = ResultSingleHookContext<H>,
>(
  options: TriggerOptions<H, T>,
): {
  (context: H): Promise<void>
  (context: H, next: NextFunction): Promise<void>
}
ArgumentTypeDescription
optionsTriggerOptions<H, T>
typemethodsmulti
before, after, aroundcreate, update, patch, removeyes

Options ​

namestring

Names the subscription: params.skipHooks skips it by this name (see shouldSkip), and the debug log shows it.

iffboolean | PredicateFn<H>

Whether the subscription applies to the call, tested once before it - e.g. with isContext or isProvider.

Example
ts
trigger({ iff: isContext({ path: 'posts', method: 'patch' }), action })
data(data: DataSingleHookContext<H>, context: H) => Promisable<boolean>

Tests context.data before the call - on multi create every item on its own, and the action only runs for the items whose data matched. Doesn't match without context.data, e.g. on remove.

On multi create, the data is paired with its result by its id, or without one by its position. Data without an id doesn't match if a later hook adds or removes an item, since the positions no longer line up.

before(before: T, context: H) => Promisable<boolean>

Tests the item before the call. Doesn't match an item without one, e.g. on create. Fetches the items before the call.

Example
ts
trigger({ before: (post) => !post.published, action })
result(result: T, context: H) => Promisable<boolean>

Tests the item after the call.

Example
ts
trigger({ result: (post) => post.published, action })
change(item: StashedItem<T>, context: H) => Promisable<boolean>

Tests the item before and after the call, to compare them. Fetches the items before the call.

Example
ts
trigger({
  change: ({ before, result }) => before?.status !== result?.status,
  action,
})
transformParamsStashTransformParams<H>

Adjusts the params of the fetches before and after the call - e.g. to populate the items. See defineStash.

fetchBeforeboolean | PredicateFn<H>default`true` with `before` or `change`, `false` otherwise

Fetches the items before the call. Resolved once per call, before it.

isBlockingboolean | PredicateFn<H>defaulttrue

Waits for the actions before the call returns, and fails the call if one of them fails. Without, the errors of the actions are logged with console.error. Resolved once per call, before it.

debugboolean | PredicateFn<H>defaultfalse

Logs why the subscription skips the call or an item. Resolved once per call, before it.

actionTriggerAction<H, T>

Runs for every matching item. Use either action or batchAction.

batchActionTriggerBatchAction<H, T>

Runs once with all matching items, not at all without any. Use either action or batchAction.

Conditions ​

All conditions a subscription has must match. They may be async.

  • Before the call: iff tests the call, e.g. with isContext or isProvider, and data tests context.data — on multi create every item on its own.
  • After the call, for every item: result tests the item after the call, before the item before it, and change both of them, to compare them.

Items Before and After the Call ​

The items come from defineStash:

  • The items before the call are only fetched when needed: with before, with change, or with fetchBefore.
  • Subscriptions whose fetches have equal params share them, across trigger hooks as well.
  • transformParams adjusts the params of the fetches, e.g. to populate the items.

An action gets { id, before, result } for every matching item. batchAction runs once, with all of them.

By default, the call waits for the actions and fails if one of them fails. With isBlocking: false it doesn't wait, and trigger logs the errors of the actions with console.error — nothing else would catch them.

Skipping ​

A subscription with a name is skipped by params.skipHooks, just like a hook wrapped in skippable:

ts
await app.service('posts').patch(id, data, { skipHooks: ['notifyOnPublish'] })

Migrating from feathers-trigger ​

feathers-triggerfeathers-utils
service: 'posts', method: 'patch'iff: isContext({ path: 'posts', method: 'patch' })
params: { provider: 'rest' }iff: isProvider('rest')
data: { … }, before: { … }, result: { … }data: (data) => …, before: (before) => …, result: (result) => …
result: ({ item, before }) => …, a query or a booleanchange: ({ before, result }) => boolean
manipulateParamstransformParams
params.skipTrigger: 'name'params.skipHooks: ['name']
action({ before, item }, options)action({ id, before, result }, options)
batchAction([[change, options], …], context)batchAction(items, { context, items, subscription })

feathers-trigger matched query objects with sift. To keep a query, pass sift(query) — it returns a predicate.

Released under the MIT License.