Versioning & Time Travel

Track changes to your data over time and query historical states

Overview

Stabilize ORM provides built-in versioning capabilities that allow you to track every change made to your records. This enables powerful features like time-travel queries, audit trails, and rollback functionality.

Enabling Versioning

Enable versioning on a model by setting the versioning option:

models/product.tstypescript
import { defineModel, DataTypes } from "stabilize-orm";
export const Product = defineModel({
tableName: "products",
versioning: true, // Enable versioning
columns: {
id: {
type: DataTypes.Integer,
primaryKey: true,
autoIncrement: true,
},
name: {
type: DataTypes.String,
length: 255,
},
price: {
type: DataTypes.Decimal,
precision: 10,
scale: 2,
},
},
});

How It Works

When versioning is enabled, Stabilize automatically creates a shadow table (e.g., products_history) that stores all historical versions of your records. Each version includes:

  • All column values at that point in time
  • Version number (auto-incremented)
  • Timestamp of when the version was created
  • Operation type (INSERT, UPDATE, DELETE)

Time Travel Queries

Query the state of your data at any point in time using the asOf method:

const repo = orm.getRepository(Product);
// Get product state as of a specific date
const product = await repo.asOf(new Date("2025-10-01")).findById(1);
// Get all products as they were yesterday
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const products = await repo.asOf(yesterday).findAll();
// Query with conditions at a specific time
const expensiveProducts = await repo
.asOf(new Date("2025-10-01"))
.query()
.where("price", ">", 100)
.findAll();

Version History

Retrieve the complete history of changes for a record:

const repo = orm.getRepository(Product);
// Get all versions of a product
const history = await repo.history(1);
// Returns array of versions:
// [
// { id: 1, name: "Widget", price: 19.99, version: 1, timestamp: "2025-01-01T10:00:00Z" },
// { id: 1, name: "Widget Pro", price: 24.99, version: 2, timestamp: "2025-02-15T14:30:00Z" },
// { id: 1, name: "Widget Pro", price: 29.99, version: 3, timestamp: "2025-03-20T09:15:00Z" },
// ]
// Get history with filters
const recentHistory = await repo.history(1, {
since: new Date("2025-02-01"),
until: new Date("2025-03-01"),
});

Rollback

Restore a record to a previous version:

const repo = orm.getRepository(Product);
// Rollback to a specific version
await repo.rollback(1, 2); // Rollback product #1 to version 2
// Rollback to a specific date
await repo.rollback(1, new Date("2025-10-01"));
// Rollback creates a new version with the old data
const product = await repo.findById(1);
console.log(product.version); // Will be the latest version number

Performance Considerations

  • Versioning adds storage overhead as all versions are retained
  • Write operations are slightly slower due to history table inserts
  • Consider archiving old versions for long-running applications
  • Use indexes on the history table's timestamp column for faster queries