MeshWeaver takes a pragmatic stance on data versioning: rather than imposing a single strategy across all backends, it delegates to each store's native capabilities wherever they exist. The result is richer history, lower application complexity, and better performance than any cross-cutting shim could deliver.
Scope — read this first. This page is about historical versions of the data a NodeType holds (a pricing table, a claim, a contract) in whatever store backs it, and it is largely a guide to the backends' own mechanisms — the code samples below are the shape you would implement in a storage adapter, not a framework API you can call. There is no
VersionedEntityReferenceand no@V{n}path resolution in the framework today. For the liveMeshNodegraph's revision counter — which is fully implemented — see MeshNode Versioning; for a node's edit history and restore, see the version tooling on the node itself (get_versions/restore_version).
Four versioning strategies — native time-travel on the left, explicit path-based snapshots on the right.
Choosing a Strategy
Your data store determines the versioning model:
| Technology | Method | Retention | Query Syntax |
|---|---|---|---|
| Snowflake | Time Travel | 1–90 days | AT(TIMESTAMP => ...) |
| SQL Server | Temporal Tables | Unlimited | FOR SYSTEM_TIME AS OF |
| Cosmos DB | Manual | Unlimited | path@V{n} |
| Blob Storage | Manual / Native | Configurable | Folder or blob versioning |
Snowflake: Time Travel
Snowflake's Time Travel gives you transparent, zero-effort history for any table — no triggers, no shadow tables, no ETL.
Capabilities
| Feature | Description |
|---|---|
| Time Travel | Query data as it existed at any point, up to 90 days in the past |
| Fail-safe | A 7-day recovery window after the Time Travel period expires |
| Zero-Copy Cloning | Instant snapshots with no data duplication |
| Retention | Configurable per table from 1 to 90 days |
Querying Historical Data
-- Data as it was 1 hour ago
SELECT * FROM pricing
AT(OFFSET => -3600);
-- Data at a specific timestamp
SELECT * FROM pricing
AT(TIMESTAMP => '2024-01-15 10:00:00');
-- Data as it existed before a specific statement ran
SELECT * FROM pricing
BEFORE(STATEMENT => '01234567-89ab-cdef-0123-456789abcdef');
Cloning for Snapshots
Zero-copy clones let you take an instant point-in-time snapshot without consuming additional storage:
CREATE TABLE pricing_q4_snapshot
CLONE pricing
AT(TIMESTAMP => '2024-12-31 23:59:59');
Reaching it from MeshWeaver
There is no built-in time-travel workspace reference — VersionedEntityReference does not
exist. A NodeType that wants "as of" reads over a Time-Travel-capable store adds it the ordinary
way: an AT(TIMESTAMP => …) clause in the query its own data source issues, exposed to callers as
a normal reactive read (an asOf parameter on the type's own request/observable). Keep the
AT(...) inside the storage leaf — the leaf is where the async I/O lives, and it goes through
IIoPool like every other I/O edge (Controlled I/O Pooling).
SQL Server: Temporal Tables
SQL Server's system-versioned temporal tables automatically maintain a complete row-level history with no application-layer changes required after the initial schema setup.
Creating a Temporal Table
CREATE TABLE Contracts
(
Id INT PRIMARY KEY,
Name NVARCHAR(100),
Amount DECIMAL(18,2),
ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START,
ValidTo DATETIME2 GENERATED ALWAYS AS ROW END,
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.ContractsHistory));
Querying Historical Data
-- Current data (unchanged syntax)
SELECT * FROM Contracts;
-- State at a specific point in time
SELECT * FROM Contracts
FOR SYSTEM_TIME AS OF '2024-06-01 12:00:00';
-- All rows that were active within a date range
SELECT * FROM Contracts
FOR SYSTEM_TIME BETWEEN '2024-01-01' AND '2024-12-31';
-- Complete change history
SELECT * FROM Contracts
FOR SYSTEM_TIME ALL;
Why Temporal Tables Work Well Here
- History is captured automatically on every
UPDATEandDELETE— no application code involved. - The query optimizer understands temporal predicates and plans accordingly.
- The history table can be partitioned independently for cost control.
- Retention is unlimited by default; old rows stay until you explicitly purge them.
Manual Versioning: Path Pattern
When the underlying store has no built-in history mechanism (Cosmos DB, Azure Blob Storage), the recommended convention is explicit path-based versioning. This is a convention for an adapter you write — nothing in the framework parses or resolves @V{n} today.
Path Format
{path}@V{version}
The undecorated path always points to the current version; decorated paths are immutable snapshots:
| Path | Meaning |
|---|---|
pricing/MS-2024 |
Current version |
pricing/MS-2024@V1 |
Version 1 (immutable) |
pricing/MS-2024@V2 |
Version 2 (immutable) |
contracts/deal-123@V5 |
Version 5 of deal-123 |
Implementation Pattern
🚨 The snippet below is the storage-leaf shape — the innermost Task-returning methods that
actually talk to Cosmos/blob. It must not surface as a Task API: the adapter's public surface
returns IObservable<T> and bridges these leaves through IIoPool
(pool.Invoke(ct => SaveVersionAsync(path, data, ct))), never Observable.FromAsync, and no
hub-reachable or Blazor code ever awaits them. See
Asynchronous Calls and
Controlled I/O Pooling.
// Storage leaf — save a new version
private async Task SaveVersionAsync(string path, object data, CancellationToken ct)
{
// Determine the next version number
var current = await GetCurrentVersionAsync(path, ct);
var newVersion = current + 1;
// Write the immutable versioned snapshot
await SaveAsync($"{path}@V{newVersion}", data, ct);
// Advance the current pointer
await SaveAsync(path, data, ct);
}
// Public surface — reactive, pooled
public IObservable<Unit> SaveVersion(string path, object data) =>
ioPool.Invoke(ct => SaveVersionAsync(path, data, ct));
public IObservable<T> GetVersion<T>(string path, int version) =>
ioPool.Invoke(ct => GetAsync<T>($"{path}@V{version}", ct));
Cosmos DB
Include the version in the document itself (and optionally in the partition key):
{
"id": "MS-2024@V3",
"partitionKey": "pricing",
"version": 3,
"createdAt": "2024-03-15T10:00:00Z",
"data": { }
}
Blob Storage
Use a folder-per-entity layout with a current.json pointer, or enable Azure Blob versioning to get automatic version tracking at the storage layer:
pricing/
MS-2024/
current.json ← always the latest version
v1.json
v2.json
v3.json
Version Metadata
Regardless of which storage technology is in use, each version snapshot should carry consistent metadata so the audit trail is human-readable:
{
"version": 3,
"createdAt": "2024-03-15T10:00:00Z",
"createdBy": "user@example.com",
"comment": "Updated Q1 projections",
"previousVersion": 2
}
Best Practices
- Prefer native features. Snowflake Time Travel and SQL Server temporal tables provide history at zero application cost — use them before reaching for manual versioning.
- Use consistent naming. Manual versions always use the
@V{n}suffix so path-parsing code has a single, unambiguous pattern to follow. - Always record metadata. Capture who created the version, when, and why — debugging and auditing are far easier with this context.
- Set retention policies. Native Time Travel and blob versioning can accumulate storage costs; configure table-level retention in Snowflake and lifecycle rules in Azure Storage accordingly.
- Lean on point-in-time queries. Reconstructing state as of a specific timestamp is the cleanest way to answer audit questions — avoid reconstructing it manually from event logs when the store offers
AS OFsemantics natively.