Skip to content

defineStash ​

Category
Tags
Export size
min 5.71 kB · gzip 2.37 kB
See also

Stashes the items of a call before it runs, to compare them with the items after it. Returns a pair: stash fetches the items before the call, stashed returns them - paired with the items after the call once it ran.

Every defineStash() is its own stash. Stashes of one call with equal params share their fetches. Nothing is stored on params, so concurrent calls with one params object and nested calls stay apart.

stash and stashed without options are exported as well.

ts
  import { 
defineStash
} from 'feathers-utils/utils';

Example ​

ts
import { defineStash } from 'feathers-utils/utils'

const { stash, stashed } = defineStash<User>()

app.service('users').hooks({
  before: { patch: [stash] },
  after: {
    patch: [
      async (context) => {
        for (const { before, result } of await stashed(context)) {
          if (before?.email !== result?.email) await sendMail(result)
        }
      },
    ],
  },
})

Type declaration ​

Show Type Declarations
ts
/**
 * Like `TransformParamsFn`, but may be async and gets the context
 */
export type StashTransformParams<H extends HookContext = HookContext> = (
  params: Params,
  context: H,
) => Params | void | Promise<Params | void>
export type StashOptions<H extends HookContext = HookContext> = {
  /**
   * Fetches the items before the call. Without it, `before` stays
   * `undefined`, but `result` is still refetched for `transformParams` or
   * `$select`.
   *
   * @default true
   */
  fetchBefore?: boolean
  /**
   * Adjusts the params of both fetches, before and after the call - e.g. to
   * populate the items. Gets a copy of the params, so it may change them in
   * place, nested objects included. The query of the call is still in
   * `context.params.query`.
   *
   * @example
   * ```ts
   * defineStash({
   *   transformParams: (params) => ({ ...params, $populate: ['profile'] }),
   * })
   * ```
   */
  transformParams?: StashTransformParams<H>
}
export type StashedItem<T = any> = {
  id: Id
  /**
   * The item before the call. `undefined` on `create`, without `fetchBefore`,
   * or if it wasn't found before the call.
   */
  before: T | undefined
  /**
   * The item after the call. `undefined` until the call ran, or if the
   * refetch didn't return it. On `remove`, the removed item.
   */
  result: T | undefined
}
export type Stash<T = any, H extends HookContext = HookContext> = {
  /**
   * Fetches the items before the call and waits for them, so the call can't
   * overtake the fetch. A `before` or `around` hook, or called from one
   * before `next()`.
   */
  stash: (context: H, next?: NextFunction) => Promise<void>
  /**
   * The items of the call: before the call with `before` only, after the
   * call paired with `result` by id.
   */
  stashed: (context: H) => Promise<StashedItem<T>[]>
}
/**
 * Stashes the items of a call before it runs, to compare them with the items
 * after it. Returns a pair: `stash` fetches the items before the call,
 * `stashed` returns them - paired with the items after the call once it ran.
 *
 * Every `defineStash()` is its own stash. Stashes of one call with equal
 * params share their fetches. Nothing is stored on `params`, so concurrent
 * calls with one `params` object and nested calls stay apart.
 *
 * `stash` and `stashed` without options are exported as well.
 *
 * @example
 * ```ts
 *
 *
 * const { stash, stashed } = defineStash<User>()
 *
 * app.service('users').hooks({
 *   before: { patch: [stash] },
 *   after: {
 *     patch: [
 *       async (context) => {
 *         for (const { before, result } of await stashed(context)) {
 *           if (before?.email !== result?.email) await sendMail(result)
 *         }
 *       },
 *     ],
 *   },
 * })
 * ```
 *
 * @see https://utils.feathersjs.com/utils/define-stash.html
 */
export declare function defineStash<
  T = any,
  H extends HookContext = HookContext,
>(options?: StashOptions<H>): Stash<T, H>
/**
 * `stash` and `stashed` without options
 *
 * @example
 * ```ts
 *
 *
 * app.service('users').hooks({
 *   before: {
 *     patch: [
 *       stash,
 *       async (context) => {
 *         const [{ before }] = await stashed(context)
 *         if (before?.locked) throw new Forbidden('locked')
 *       },
 *     ],
 *   },
 * })
 * ```
 */
export declare const stash: (
    context: HookContext<Application<any, any>, any>,
    next?: NextFunction,
  ) => Promise<void>,
  stashed: (
    context: HookContext<Application<any, any>, any>,
  ) => Promise<StashedItem<any>[]>
ArgumentTypeDescription
optionsStashOptions<H>

Without options, stash and stashed can be imported directly:

ts
import { stash, stashed } from 'feathers-utils/utils'

Options ​

fetchBeforebooleandefaulttrue

Fetches the items before the call. Without it, before stays undefined, but result is still refetched for transformParams or $select.

transformParamsStashTransformParams<H>

Adjusts the params of both fetches, before and after the call - e.g. to populate the items. Gets a copy of the params, so it may change them in place, nested objects included. The query of the call is still in context.params.query.

Example
ts
defineStash({
  transformParams: (params) => ({ ...params, $populate: ['profile'] }),
})

stash ​

  • Use it as a before or around hook, or call it from one before next(): await stash(context, next).
  • Works on create, update, patch and remove. On create there is nothing to fetch, before stays undefined.
  • Fetches with find, a call with an id by its id, so the items before and after the call go through the same hooks.
  • Waits for the fetch, so the call can't overtake it. If the fetch fails, the call fails.
  • Throws after the call ran, e.g. in an around hook after next().
  • Calling the same stash again in one call does nothing.

stashed ​

Returns an array of { id, before, result }, with one entry for a call with an id:

ts
const [{ before }] = await stashed(context)
  • Before the call ran, only before is set — e.g. to validate a patch against the stored item.
  • After the call, the items of context.result are paired with the items before by id, in the order of the result. On remove, result is the removed item.
  • With transformParams or $select, the items after the call are refetched by id, with the params of the call. Otherwise context.result is used as it is.
  • Throws if stash didn't run for this call.

Migrating from stashable ​

ts
// before
import { stashable } from 'feathers-utils/hooks'

app.service('users').hooks({
  before: { patch: [stashable()] },
})
const before = await context.params.stashed()

// after
import { stash, stashed } from 'feathers-utils/utils'

app.service('users').hooks({
  before: { patch: [stash] },
})
const [{ before }] = await stashed(context)
stashabledefineStash
context.params.stashed()stashed(context)
propName optionnot needed, every defineStash() is its own stash
stashFunc optiontransformParams adjusts the params of the fetch
get for a call with an idalways find, a call with an id by its id
multi: the array of findan array of { id, before, result }
starts the fetch without waiting for itwaits for the fetch, so the call can't overtake it
a failing fetch returns undefineda failing fetch fails the call

Released under the MIT License.