Skip to content
Stand With Ukraine

Soft Delete ​

softDelete configures the table to set deletedAt to current time instead of deleting records. All queries on such table will filter out deleted records by default.

ts
import { defineTable } from './table-factory';

export const SomeTable = defineTable('some', (t) => ({
  id: t.identity().primaryKey(),
  deletedAt: t.timestamp().nullable(),
}))
  // true is for using `deletedAt` column
  .softDelete();

// or provide a different column name
export const OtherTable = defineTable('other', (t) => ({
  id: t.identity().primaryKey(),
  myDeletedAt: t.timestamp().nullable(),
})).softDelete('myDeletedAt');

const db = orchidORM(
  { databaseURL: '...' },
  {
    someTable: SomeTable,
  },
);

// deleted records are ignored by default
const onlyNonDeleted = await db.someTable;

includeDeleted disables the default deletedAt filter:

ts
const allRecords = await db.someTable.includeDeleted();

delete behavior is altered:

ts
await db.someTable.find(1).delete();
// is equivalent to:
await db.someTable.find(1).update({ deletedAt: sql`now()` });

Soft delete still follows the mutation condition safety rules. The default deletedAt IS NULL filter is implicit and does not authorize a mutation by itself, so an empty effective user filter throws:

ts
await db.someTable.where({}).delete(); // throws
await db.someTable.where({ id: undefined }).delete(); // throws
await db.someTable.where({ id: 1 }).delete(); // allowed
await db.someTable.all().delete(); // soft-deletes all non-deleted records

hardDelete deletes records bypassing the softDelete behavior:

ts
await db.someTable.find(1).hardDelete();
await db.someTable.all().hardDelete();

hardDelete also requires an effective condition or explicit all().