Timestamps
Automatic createdAt / updatedAt management for better-drizzle, with app-managed and database-managed modes.
better-drizzle/timestamps keeps timestamp handling out of your services. It can manage timestamps in application code, or stay out of the way when your database already owns them via defaults, generated columns, or triggers.
Install
npm install better-drizzlepnpm add better-drizzleyarn add better-drizzlebun add better-drizzleUsage
import { better } from 'better-drizzle';
import { timestamps } from 'better-drizzle/timestamps';
const client = better(db, {
schema,
plugins: [
timestamps({
createdAt: 'createdAt',
updatedAt: 'updatedAt',
mode: 'app',
}),
],
});Options
Every option is optional.
| Option | Default | Description |
|---|---|---|
createdAt | 'createdAt' | column set on insert |
updatedAt | 'updatedAt' | column set on insert and update |
mode | 'app' | 'app' sets the columns in plugin hooks; 'database' registers no hooks |
Modes
mode: 'app'
The plugin fills timestamp columns before the database call. Values are JavaScript Date objects, one now per call.
| Operation | Behavior |
|---|---|
create | sets createdAt and updatedAt |
createMany | sets both on every row |
update / updateMany | sets updatedAt |
updateEach | adds an updatedAt resolver to update, so every matched row gets the same now |
upsert | sets both on create, sets updatedAt on update |
upsertMany | sets both on every row; on conflict, createdAt is never updated and updatedAt is |
reads, delete, deleteMany | untouched |
For upsertMany, the conflict update is rewritten by shape: 'all' becomes every column except createdAt, a column list drops createdAt and adds updatedAt, an object drops createdAt and sets updatedAt, and a function result gets the same treatment.
The plugin always writes its own values. A createdAt or updatedAt passed by the caller is overwritten:
const { createdAt, updatedAt } = await client.posts.create({
data: { title: 'Hello', createdAt: new Date('2020-01-01') },
}); // both are the current time, not 2020Use $withoutPlugins() for a one-off write that must keep caller-provided timestamps, such as a data import.
mode: 'database'
The plugin becomes a no-op - use it when the database owns timestamps (column defaults like DEFAULT now(), ON UPDATE, triggers, or generated values):
timestamps({ mode: 'database' });In this mode it adds effectively zero runtime behavior; the database stays the source of truth.
Custom column names
Names are the property keys in your Drizzle table definition, not the database column names.
timestamps({
createdAt: 'created_at',
updatedAt: 'updated_at',
});Behavior details
- Each column is handled on its own: a table with only
updatedAtgets onlyupdatedAt, and tables with neither are skipped. - It only mutates write payloads - reads, filters, and result shapes are untouched.
- Soft deletes from
better-drizzle/soft-deleterun beneath the plugins, so they do not changeupdatedAt.