Arke
Build

Versioning & CAS

How content-addressed versioning and Compare-And-Swap work in Arke.

Version Chains

Every update creates a new immutable version. Versions are linked through prev CID references:

v3 (tip) → v2 → v1 → null

Each version is stored in KV by its content hash (CID). The tip pointer in a Durable Object always points to the latest version.

Compare-And-Swap (CAS)

CAS prevents lost updates. Every update request must include expect_tip -- the CID you believe is the current tip.

Success flow:

  1. Read entity: { cid: "abc", ver: 2, ... }
  2. Update with expect_tip: "abc" -- succeeds, creates version 3

Conflict flow:

  1. Read entity: { cid: "abc", ver: 2, ... }
  2. Someone else updates: tip changes to "def"
  3. Your update with expect_tip: "abc" -- fails with 409
  4. Re-read, reapply changes, retry with expect_tip: "def"

Version links use the IPLD convention:

{
  "prev": { "/": "bafyreig..." }
}

This provides compatibility with content-addressed systems while storage is on Cloudflare infrastructure.

Traversing History

List Version Metadata

GET /versions/{id}

Returns version metadata in reverse chronological order (newest first). Supports pagination.

Query parameters:

  • limit: Maximum versions to return (1-10, default 10)
  • from: CID to start from (for pagination)

Response:

{
  "entity_id": "01KDETYWYWM0MJVKM8DK3AEXPY",
  "versions": [
    {
      "ver": 3,
      "cid": "bafyrei...",
      "ts": "2025-01-15T10:00:00Z",
      "edited_by": {
        "user_id": "01JUSER...",
        "method": "manual"
      },
      "note": "Updated description",
      "arweave_tx": "abc123...",
      "arweave_url": "https://arweave.net/abc123..."
    }
  ],
  "has_more": true,
  "next_cursor": "bafyrei..."
}

The arweave_tx and arweave_url fields are included if the version has been attested to Arweave.

Get Full Manifest by CID

GET /versions/manifest/{cid}

Retrieves the complete manifest for any historical version by its CID. Useful for viewing the full state at a specific point in time.

Response:

{
  "cid": "bafyrei...",
  "manifest": {
    "schema": "arke/eidos@v1",
    "id": "01KDETYWYWM0MJVKM8DK3AEXPY",
    "type": "document",
    "created_at": "2025-01-10T08:00:00Z",
    "ver": 2,
    "ts": "2025-01-12T14:30:00Z",
    "prev": { "/": "bafyrei..." },
    "properties": { "label": "My Document", "text": "..." },
    "relationships": [],
    "edited_by": { "user_id": "01JUSER...", "method": "manual" }
  }
}

Comparing Versions (Diff)

GET /entities/{id}/diff

Computes the difference between two versions of an entity.

Query parameters:

  • from: CID of the "from" version (defaults to prev of "to" version)
  • to: CID of the "to" version (defaults to current tip)
  • format: Output format - semantic (default) or patch (RFC 6902)

Modes:

  • No params: Compare current tip with its previous version
  • to only: Compare that version with its prev
  • from only: Compare from that version to current tip
  • Both: Compare any two versions

Semantic format response:

{
  "entity_id": "01KDETYWYWM0MJVKM8DK3AEXPY",
  "from": { "cid": "bafyrei...", "ver": 2, "ts": "..." },
  "to": { "cid": "bafyrei...", "ver": 3, "ts": "..." },
  "format": "semantic",
  "changes": {
    "properties": {
      "added": { "new_field": "value" },
      "removed": { "old_field": "was this" },
      "changed": { "label": { "from": "Old", "to": "New" } }
    },
    "relationships": {
      "added": [{ "predicate": "contains", "peer": "01KFILE..." }],
      "removed": [],
      "changed": []
    }
  }
}

Patch format response (RFC 6902):

{
  "entity_id": "01KDETYWYWM0MJVKM8DK3AEXPY",
  "from": { "cid": "bafyrei...", "ver": 2, "ts": "..." },
  "to": { "cid": "bafyrei...", "ver": 3, "ts": "..." },
  "format": "patch",
  "patch": [
    { "op": "replace", "path": "/properties/label", "value": "New" },
    { "op": "add", "path": "/properties/new_field", "value": "value" }
  ]
}

For version 1 entities (no previous version), from is null and all content appears as added.

Soft Delete and Restore

Entities are soft-deleted by creating a tombstone version. The original data is preserved in the version chain.

Delete

DELETE /entities/{id}

Request body:

{
  "expect_tip": "bafyrei...",
  "reason": "Optional deletion reason"
}

Response:

{
  "id": "01KDETYWYWM0MJVKM8DK3AEXPY",
  "cid": "bafyrei...",
  "deleted_at": "2025-01-15T12:00:00Z",
  "ver": 4,
  "prev_cid": "bafyrei..."
}

Restore

POST /entities/{id}/restore

Restores a deleted entity by finding the last non-deleted version in the chain and creating a new version with that data.

Request body:

{
  "expect_tip": "bafyrei..."
}

Response:

{
  "id": "01KDETYWYWM0MJVKM8DK3AEXPY",
  "cid": "bafyrei...",
  "type": "document",
  "properties": { "label": "My Document", "..." },
  "relationships": [],
  "ver": 5,
  "created_at": "2025-01-10T08:00:00Z",
  "ts": "...",
  "edited_by": { "user_id": "01JUSER...", "method": "manual" },
  "prev_cid": "bafyrei...",
  "restored_from_ver": 3
}

The restored_from_ver field indicates which version the entity was restored from.

Lightweight Tip Lookup

GET /entities/{id}/tip

Returns only the current CID without fetching the full manifest. Useful for CAS operations where you only need the tip for expect_tip.

Response:

{
  "id": "01KDETYWYWM0MJVKM8DK3AEXPY",
  "cid": "bafyrei..."
}

This endpoint has no authentication requirement and performs a single Durable Object lookup.

On this page