onDelete
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.
import { onDelete } from 'feathers-utils/hooks';Examples
Example 1
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
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
Type declaration
Show Type Declarations
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>| Argument | Type | Description |
|---|---|---|
| options | MaybeArray<OnDeleteOptionsDistributed<H>> |
| type | methods | multi |
|---|---|---|
| before, after, around | remove | yes |
Before and after
Register one onDelete() in before and after, or as around:
- Before the removal, it fetches the items with
stash, through thefindhooks. - After it, it changes the related items of the items the call removed, by their keys before the removal: a
$selectof the call or a trimmed result don't hidekeyHere. A removal that fails changes nothing. - Only in
after, it takes the keys fromcontext.result. Only inbefore, 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:
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] },
})