R2 Storage
Keep migration histories in an R2 bucket
Quick Start
import { createMigrationClient } from "viborm/migrations";
import { createR2StorageWriter } from "viborm/migrations/storage/r2";
const migrations = createMigrationClient(client, {
storage: createR2StorageWriter(env.HISTORY, { prefix: `tenants/${id}/` }),
});
// wrangler.jsonc
{
"r2_buckets": [{ "binding": "HISTORY", "bucket_name": "migration-history" }]
}
Use R2 when histories must be shared or inspected outside one object. Every
read is a request to R2, so a command costs more than with
Durable Object storage. The entry
loads without nodejs_compat.
Configuration
const storage = createR2StorageWriter(bucket, { prefix });
| Parameter | Type | Description |
|---|---|---|
bucket |
R2Bucket |
The bucket binding |
prefix |
string |
Optional key prefix for this estate, such as tenants/42/. Defaults to "". |
Each estate needs its own prefix. Under one prefix, two estates are one history: a different target is refused, and a second tenant with the same target shares the first tenant’s states.
How Publication Stays Atomic
A publish is a put conditioned on etagDoesNotMatch: "*"
(If-None-Match: *). R2 checks the condition and stores the object in one
step; when the key already exists it stores nothing and returns null.
Under contention for one key, hosted R2 may instead reject the losing put with
error 10058 (“Reduce your concurrent request rate for the same object”),
having stored nothing; once the winner’s object is there, VibORM treats that
put as lost the same way. VibORM then reads the stored bytes: identical bytes
are an idempotent publish, different bytes are refused as corruption.
R2 writes and listings are strongly consistent, so a state is listed as soon as its publish returns. Listing follows R2’s cursor until R2 reports the listing complete: a page may hold fewer keys than its limit while more remain (hosted R2 returned 1,001 keys as pages of 485, 485 and 31).
Layout
<prefix>estate.json
<prefix>snapshots/<snapshot-hash>.json
<prefix>sql/<sql-hash>.sql
<prefix>states/<state-id>.json
Do not write, overwrite or delete these keys yourself, and do not apply lifecycle rules that expire them.