Docs/Guides/Database

Query scopes and soft deletes

The ORM supports model-level global scopes, local scopes and soft-delete filtering. These features can hide rows from a query, so document their effects in your domain layer.

2 min read

The ORM supports model-level global scopes, local scopes and soft-delete filtering. These features can hide rows from a query, so document their effects in your domain layer.

Soft deletes#

Enable soft deletes on a model:

ts
import { Model } from "@veap/database";

export class Article extends Model {
  static table = "articles";
  static softDeletes = true;
  static deletedAtColumn = "deleted_at";
}

The deleted_at column must exist in the database. By default, queries on a soft-delete model filter out rows with a non-null deleted-at value.

ts
const visible = await Article.query().get();
const includingDeleted = await Article.withTrashed().get();
const deletedOnly = await Article.onlyTrashed().get();

The exact delete/restore methods should be confirmed against the declaration for your installed package version before use.

Global scopes#

Model.addGlobalScope(name, scope) registers a named global scope. A scope can be a function or an object exposing apply; the query builder applies registered scopes when building the query.

ts
Article.addGlobalScope("published", (query) => {
  query.where("published", true);
});

A global scope is appropriate for invariant filtering only when every query should inherit it. Avoid using global scopes as the only authorization mechanism.

Excluding scopes#

The query builder exposes withoutGlobalScope(name) and withoutGlobalScopes(names?). Omitting the list disables all global scopes and also includes soft-deleted rows. Use these methods narrowly and only after authorization checks.

Local scopes#

The query builder's proxy resolves a method such as active() to a model scope named scopeActive. Follow the implementation's receiver convention: the scope receives the query builder as its first argument, followed by any scope parameters.

ts
export class Article extends Model {
  static table = "articles";

  static scopePublished(query: any) {
    query.where("published", true);
  }
}

const rows = await Article.query().published().get();

Because the local-scope mechanism is dynamically dispatched, compile this pattern against your installed declarations and include it in integration tests.

veap
Preparing0%