class

DataDungeon::S3Storage

Inherits DataDungeon::Storage < Reference < Object

Stores objects in an S3-compatible bucket: Cloudflare R2, Garage, MinIO or AWS S3. Optional: require "data_dungeon/s3", and add awscr-s3 to the application's own shard.yml.

Objects are content-addressed, so they never change once written; they're uploaded with long immutable cache headers, which lets a CDN in front of public_url serve every repeat. Connections are pooled and kept alive, and a request the service turns away for load (429, 5xx) is retried with backoff. Give DataDungeon a bucket, or a prefix within one, of its own: the consistency sweep treats every object under it as DataDungeon's. A bucket several stores share is shared: true (see Storage#shared?).

Constants

IMMUTABLE = "public, max-age=31536000, immutable"
RETRYABLE = [429, 500, 502, 503, 504]

Constructors

new(bucket : String, *, endpoint : String, region : String, access_key : String, secret_key : String, prefix : String = "", public_url : String | Nil = nil, sign_key : String | Nil = nil, url_expires : Time::Span = 1.hour, retries : Int32 = 3, pool_size : Int32 = 8, shared : Bool = false)

public_url (e.g. a custom domain or CDN in front of the bucket) makes resolve return plain URLs; without it, resolve returns presigned URLs valid for url_expires.

sign_key turns on signed public_urls: resolve appends ?exp=&sig=, where sig is the lowercase hex HMAC-SHA256 of "<object>\n<exp>" under this key. A Worker in front of a private bucket (see worker/) verifies the signature and expiry before serving, so the bucket need not be public yet the URLs still carry your own domain and are CDN-cacheable. It only applies when public_url is set, and the object path must be the signed subject — so use it with an empty prefix (the Worker serves root-level content-addressed keys). Without sign_key, public_urls stay plain.

Source
r2(account_id : String, bucket : String, *, access_key : String, secret_key : String, prefix : String = "", public_url : String | Nil = nil, sign_key : String | Nil = nil, url_expires : Time::Span = 1.hour, retries : Int32 = 3, pool_size : Int32 = 8, shared : Bool = false) : S3Storage

A Cloudflare R2 bucket in the given account.

Source

Instance methods

bucket
Source
delete(key : String) : Nil

S3 reports success for a missing object too, so this is a no-op then.

Source
exists?(key : String) : Bool

Whether an object exists at the given key.

Source
list_keys

Enumerate every object key currently in the backend. Used by the consistency sweep to find objects with no owning row. May be expensive on large object stores; intended for maintenance, not the hot path.

Source
open(key : String, & : IO -> ) : Nil

A status the bucket turns a request away with comes before any bytes, so retrying it never runs the block twice.

Source
prefix
Source
read(key : String) : Bytes

Read the bytes stored at the given key.

Source
resolve(key : String) : String

Resolve a key to a usable location — a local absolute path here, a URL for object stores.

Source
write(key : String, bytes : Bytes, content_type : String | Nil = nil) : Nil

Write bytes at the given key, creating any containers as needed. The content type is a hint for backends that serve objects themselves.

Source

Nested types