Skip to content

onDelete ​

Category
Tags
Export size
min 9.30 kB · gzip 3.72 kB
See also

Manipulates related items when a record is deleted, similar to SQL foreign key actions. Supports 'cascade' (remove related records) and 'set null' (nullify the foreign key). Unlike database-level cascades, this hook triggers service events and hooks for related items. Registered in before and after, or as around, it fetches the items before the removal and changes the related items of those it removed after it.

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

Examples ​

Example 1 ​

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

// remove the user's posts
app.service('users').hooks({
  after: {
    remove: [onDelete({ service: 'posts', keyHere: 'id', keyThere: 'userId', onDelete: 'cascade' })]
  }
})

Example 2 ​

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

// set `posts.userId` to `null` and wait for it before returning
app.service('users').hooks({
  after: {
    remove: [
      onDelete({
        service: 'posts',
        keyHere: 'id',
        keyThere: 'userId',
        onDelete: 'set null',
        blocking: true,
      }),
    ]
  }
})

Options ​

serviceSrequired

The related service where related items should be manipulated

keyThereNeverFallback<keyof InferGetResult<Service>, string>required

The propertyKey in the related service

keyHerekeyof ResultSingleHookContext<H>required

The propertyKey in the current service.

onDeleteOnDeleteActionrequired

The action to perform on the related items.

  • cascade: remove related items
  • set null: set the related property to null
queryInferFindParams<Service>['query']

Additional query to merge into the service call. Typed based on the related service's query type.

params(context: H) => InferFindParams<Service>

Returns the params of the patch/remove calls in the related service - e.g. to change the related items in the call's transaction. Their query is merged with query and the relation, which win.

A transaction only holds them while blocking: in the background, they may be changed after it ended.

Example

(context) => ({ transaction: context.params.transaction })

multiMultidefaultservice.options.multi

Whether the related service allows manipulating multiple items in a single patch/remove call.

Defaults to the multi option of the related service. If multi is not allowed, the related items are fetched and manipulated with one patch/remove call per item.

concurrencynumberdefault10

How many patch/remove calls run at the same time when the related items are changed with one call per item, so a large cascade does not exhaust the database pool.

blockingboolean | PredicateFn<H>defaultfalse

Whether the hook waits for the related-service call to finish before the call continues. Can be a boolean or a predicate that receives the HookContext.

Example

isProvider('external')

onError(error: any, context: H) => void

Called when a related-service call rejects while not blocking. Without this, fire-and-forget rejections are swallowed (but never leak as an unhandled rejection). In blocking mode the error is thrown to the caller instead.

Type declaration ​

Show Type Declarations
ts
export type OnDeleteAction = "cascade" | "set null"
export interface OnDeleteOptions<
  H extends HookContext = HookContext,
  S extends keyof H["app"]["services"] = keyof H["app"]["services"],
  Service = H["app"]["services"][S],
> {
  /**
   * The related service where related items should be manipulated
   */
  service: S
  /**
   * The propertyKey in the related service
   */
  keyThere: NeverFallback<keyof InferGetResult<Service>, string>
  /**
   * The propertyKey in the current service.
   */
  keyHere: keyof ResultSingleHookContext<H>
  /**
   * The action to perform on the related items.
   *
   * - `cascade`: remove related items
   * - `set null`: set the related property to null
   */
  onDelete: OnDeleteAction
  /**
   * Additional query to merge into the service call.
   * Typed based on the related service's query type.
   */
  query?: InferFindParams<Service>["query"]
  /**
   * Returns the params of the `patch`/`remove` calls in the related service -
   * e.g. to change the related items in the call's transaction. Their `query`
   * is merged with `query` and the relation, which win.
   *
   * A transaction only holds them while `blocking`: in the background, they
   * may be changed after it ended.
   *
   * @example (context) => ({ transaction: context.params.transaction })
   */
  params?: (context: H) => InferFindParams<Service>
  /**
   * Whether the related service allows manipulating multiple items in a single
   * `patch`/`remove` call.
   *
   * Defaults to the `multi` option of the related service. If multi is not
   * allowed, the related items are fetched and manipulated with one
   * `patch`/`remove` call per item.
   *
   * @default service.options.multi
   */
  multi?: Multi
  /**
   * How many `patch`/`remove` calls run at the same time when the related
   * items are changed with one call per item, so a large cascade does not
   * exhaust the database pool.
   *
   * @default 10
   */
  concurrency?: number
  /**
   * Whether the hook waits for the related-service call to finish before the
   * call continues. Can be a boolean or a predicate that receives the
   * `HookContext`.
   *
   * @example isProvider('external')
   * @default false
   */
  blocking?: boolean | PredicateFn<H>
  /**
   * Called when a related-service call rejects while not blocking.
   * Without this, fire-and-forget rejections are swallowed (but never leak as an
   * unhandled rejection). In `blocking` mode the error is thrown to the caller instead.
   */
  onError?: (error: any, context: H) => void
}
type OnDeleteOptionsDistributed<H extends HookContext> = {
  [S in keyof H["app"]["services"] & string]: OnDeleteOptions<H, S>
}[keyof H["app"]["services"] & string]
/**
 * Manipulates related items when a record is deleted, similar to SQL foreign key actions.
 * Supports `'cascade'` (remove related records) and `'set null'` (nullify the foreign key).
 * Unlike database-level cascades, this hook triggers service events and hooks for related items.
 * Registered in `before` and `after`, or as `around`, it fetches the items before the removal and
 * changes the related items of those it removed after it.
 *
 * @example
 * ```ts
 *
 *
 * // remove the user's posts
 * app.service('users').hooks({
 *   after: {
 *     remove: [onDelete({ service: 'posts', keyHere: 'id', keyThere: 'userId', onDelete: 'cascade' })]
 *   }
 * })
 * ```
 *
 * @example
 * ```ts
 *
 *
 * // set `posts.userId` to `null` and wait for it before returning
 * app.service('users').hooks({
 *   after: {
 *     remove: [
 *       onDelete({
 *         service: 'posts',
 *         keyHere: 'id',
 *         keyThere: 'userId',
 *         onDelete: 'set null',
 *         blocking: true,
 *       }),
 *     ]
 *   }
 * })
 * ```
 *
 * @see https://utils.feathersjs.com/hooks/on-delete.html
 */
export declare const onDelete: <H extends HookContext = HookContext>(
  options: MaybeArray<OnDeleteOptionsDistributed<H>>,
) => (context: H, next?: NextFunction) => Promise<void>
ArgumentTypeDescription
optionsMaybeArray<OnDeleteOptionsDistributed<H>>
typemethodsmulti
before, after, aroundremoveyes

Before and after ​

Register one onDelete() in before and after, or as around:

  • Before the removal, it fetches the items with stash, through the find hooks.
  • After it, it changes the related items of the items the call removed, by their keys before the removal: a $select of the call or a trimmed result don't hide keyHere. A removal that fails changes nothing.
  • Only in after, it takes the keys from context.result. Only in before, it does nothing.

A foreign key that the database cascades or sets null itself (ON DELETE CASCADE) has changed the related items before the after part runs - without service events or hooks. To change them through their service instead, let the database check the key at the end of the transaction (DEFERRABLE INITIALLY DEFERRED) and change them in it with blocking and params:

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

const removePosts = onDelete({
  service: 'posts',
  keyHere: 'id',
  keyThere: 'userId',
  onDelete: 'cascade',
  blocking: true,
  params: (context) => ({ transaction: context.params.transaction }),
})

app.service('users').hooks({
  before: { remove: [removePosts] },
  after: { remove: [removePosts] },
})

Released under the MIT License.