Version History and Document Diffs

Browse, compare, pin, and restore document versions — every save is preserved automatically so you can diff any two snapshots or roll back to any point in history.

beginner11 min read

What You'll Learn

Version history gives you a complete audit trail for every document in your vault. Every time you save a document — whether through the editor, the API, WebDAV, or a connector sync — a new version is created automatically. You can browse the full history, read any past snapshot, compare any two versions, pin milestones to prevent cleanup, and restore the document to any earlier state.

By the end of this guide you will be able to:

  • List all versions for a document and inspect their metadata
  • Read the full content of any historical version by version number
  • Diff two versions to see exactly what changed as a unified diff
  • Pin important versions to protect them from automated pruning
  • Restore a document to any prior version (non-destructively)

Available on all tiers. Version history is enabled for every document on every subscription tier — no upgrade required. Versions are created automatically on every document save; there is nothing to configure. HMAC request signing applies only to API-key consumers of mutating document operations — JWT sessions (including login()) are exempt from requireSignature and never sign requests.

List and Browse Versions

To explore the history of a document, start by listing all of its versions. The response includes each version's number, creation timestamp, and pin status — giving you a full timeline of every save.

typescript
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';

const { client } = await LifestreamVaultClient.login(
  'https://vault.lifestreamdynamics.com',
  'you@example.com',
  'your-password',
);

const VAULT_ID = 'vault-uuid';
const DOC_PATH = 'notes/my-doc.md';

// listVersions returns DocumentVersion[] directly (no wrapper object)
const versions = await client.documents.listVersions(VAULT_ID, DOC_PATH);

console.log(`Found ${versions.length} versions:`);

for (const v of versions) {
  const pinned = v.isPinned ? ' [PINNED]' : '';
  console.log(`  v${v.versionNum} — ${v.createdAt}${pinned}`);
}

Read a Specific Version

Once you know which version you want to inspect, you can fetch its full Markdown content by version number. This is a read-only operation — it does not modify the document.

typescript
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';

const { client } = await LifestreamVaultClient.login(
  'https://vault.lifestreamdynamics.com',
  'you@example.com',
  'your-password',
);

// getVersion returns a DocumentVersionWithContent directly (no wrapper object)
const version = await client.documents.getVersion(
  'vault-uuid',
  'notes/my-doc.md',
  3, // version number
);

console.log('Version number:', version.versionNum);
console.log('Created at:', version.createdAt);
console.log('Pinned:', version.isPinned);
console.log('Content length:', version.content?.length ?? 0, 'characters');
console.log('\nContent preview:');
console.log((version.content ?? '').slice(0, 200));

Diff Two Versions

The diff endpoint compares any two versions of a document and returns a unified diff string — the same format used by git diff. Pass from and to version numbers in the request body. You can diff non-adjacent versions to see the cumulative change across multiple saves.

typescript
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';

const { client } = await LifestreamVaultClient.login(
  'https://vault.lifestreamdynamics.com',
  'you@example.com',
  'your-password',
);

// diffVersions takes positional version numbers: (vaultId, docPath, from, to)
const result = await client.documents.diffVersions(
  'vault-uuid',
  'notes/my-doc.md',
  1, // from version
  3, // to version
);

console.log(`Diff from v${result.fromVersion} to v${result.toVersion}:`);

// 'changes' is an array of diff hunks ({ value, added?, removed? })
for (const change of result.changes) {
  const marker = change.added ? '+' : change.removed ? '-' : ' ';
  process.stdout.write(`${marker} ${change.value}`);
}

Pin Versions to Prevent Pruning

Lifestream Vault runs a version-pruning worker periodically to remove old unpinned versions and keep storage manageable. Pinned versions are never pruned — they are preserved indefinitely regardless of how many newer versions accumulate.

Use pinning to protect milestone snapshots: a completed draft, the version you shared with a client, or the state of a document before a major rewrite.

typescript
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';

const { client } = await LifestreamVaultClient.login(
  'https://vault.lifestreamdynamics.com',
  'you@example.com',
  'your-password',
);

const VAULT_ID = 'vault-uuid';
const DOC_PATH = 'notes/my-doc.md';

// pinVersion / unpinVersion return the updated DocumentVersion directly
const pinned = await client.documents.pinVersion(VAULT_ID, DOC_PATH, 3);
console.log(`v${pinned.versionNum} is now pinned: ${pinned.isPinned}`); // true

// Unpin it later if it no longer needs protection
const unpinned = await client.documents.unpinVersion(VAULT_ID, DOC_PATH, 3);
console.log(`v${unpinned.versionNum} is now pinned: ${unpinned.isPinned}`); // false

Pin milestone versions — such as launch drafts, major rewrites, or versions shared externally — so they survive automated pruning. A pinned version is always retrievable by number, no matter how much time passes or how many newer versions accumulate.

Restore a Version

Restoring a version is non-destructive: the API copies the content of the selected historical version into a brand-new version at the head of the document's history. Your current version and all intermediate versions remain intact — restoration is always reversible.

Restore is HMAC-signed for API-key consumers. The requireSignature middleware enforces cryptographic signing on mutating document operations to prevent replay attacks — but it applies only to API-key authentication, where the API key itself is the signing secret. JWT sessions (e.g. from login()) are exempt and pass through unsigned. When authenticating with an API key, set enableRequestSigning: true and the SDK signs automatically. For cURL, you must compute and include the X-Signature, X-Signature-Timestamp, and X-Signature-Nonce headers manually — see the HMAC signing documentation for the algorithm.

typescript
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';

// Signing applies to API-key auth (the API key is the signing secret).
// A JWT session from login() is exempt and works without enableRequestSigning.
const client = new LifestreamVaultClient({
  baseUrl: 'https://vault.lifestreamdynamics.com',
  apiKey: process.env.VAULT_API_KEY!,
  enableRequestSigning: true, // the SDK signs the restore request automatically
});

// Restore the document to the content from version 3.
// This creates a new version (e.g. v5) containing v3's content — v4 is preserved.
// restoreVersion returns the updated Document directly (no wrapper object).
const document = await client.documents.restoreVersion(
  'vault-uuid',
  'notes/my-doc.md',
  3, // version number to restore from
);

console.log('Document restored successfully.');
console.log('Document ID:', document.id);
console.log('Document path:', document.path);
console.log('Document title:', document.title);

Version Pruning Policy

Lifestream Vault manages version storage automatically through the version-pruning BullMQ worker, which runs on a periodic schedule in the background.

How pruning works:

  • The version-pruning worker scans documents for old, unpinned versions that exceed the configured retention threshold
  • Pinned versions are never pruned — they are preserved indefinitely regardless of age or count
  • The most recent N versions are always kept for every document, regardless of pin status — you are never left without recent history. By default, the server retains the most recent 50 versions per document and prunes versions older than 90 days. These thresholds are configurable via the VERSION_RETENTION_COUNT and VERSION_RETENTION_DAYS environment variables.
  • Pruning operates per-document and runs asynchronously, so there is no impact on normal read/write operations

What this means in practice:

  • Frequently edited documents accumulate versions quickly — pin any snapshot you may need later
  • Documents with few saves are unlikely to be pruned at all within any reasonable period
  • You do not need to manually clean up version history — pruning keeps storage manageable automatically

Tips & Best Practices

  • Pin important milestones before they get pruned. Once a version is pruned it is gone. If you have a version you may want to reference later — a completed draft, a pre-refactor snapshot, a version sent to a reviewer — pin it immediately.

  • Use diff before restoring. Restoration is non-destructive (it creates a new version), but it is still cleaner to confirm what you are restoring. Diff the candidate version against the current head before calling restore.

  • Version numbers are sequential and represent save order. Version 1 is the first save, version 2 is the next, and so on. Numbers are never reused, even after restoration.

  • Enable HMAC request signing in the SDK for restore operations. Construct the client with enableRequestSigning: true to have the SDK sign all mutating requests automatically:

const client = new LifestreamVaultClient({
  baseUrl: 'https://vault.lifestreamdynamics.com',
  apiKey: process.env.VAULT_API_KEY,
  enableRequestSigning: true,
});
  • Versions are per-document — renaming or moving a document preserves its version history. The history travels with the document's identity in the database, not its path on disk.

What's Next

  • Export Your Data — package your entire vault — including all document versions — into a portable archive for backup or migration
  • Build a Custom Integration — use the SDK to build programmatic workflows around version history, including automated pinning and diff-based changelogs
  • Automate Your Vault — subscribe to document.created and document.updated events to trigger hooks whenever a new version is saved