PicoRuby × Cloudflare Workers

Bindings reference.

Examples and API boundaries for the Cloudflare bindings available from PicoRuby.

01 — THE SHAPE

The request owns
the connection.

Every Rack request receives a Cloudflare::Environment proxy at env["cloudflare.env"]. It resolves typed resource wrappers against the current Worker env; it is not a Ruby pointer to a JavaScript object.

Text variables and secrets use ENV. Resource bindings such as KV, D1, AI, or R2 do not.

01

Request-scoped

Wrappers resolve against the current Worker environment and are cached only for that Rack request.

02

Typed by config

The generated registry reads declared binding types. The runtime never guesses a resource from its methods.

03

Sync-looking Ruby

Promise-backed host operations cross the Wasm boundary through JSPI and resume the same Ruby stack.

01 / FOUNDATION

Environment values and named resources

Reach declared resources from the proxy. Use ENV for Worker text values and secrets only; writing to it creates a request-local Ruby overlay and does not mutate Cloudflare.

cloudflare = env["cloudflare.env"]

cache = cloudflare.CACHE_KV       # Cloudflare::KV
database = cloudflare.DB          # Cloudflare::D1
ai = cloudflare.AI                # Cloudflare::AI

api_url = ENV["API_URL"]         # variable or secret
same_cache = Cloudflare::KV.from_env(env, "CACHE_KV")
BoundaryUnknown resource names return nil through []; property syntax raises NoMethodError. A missing or wrong typed explicit resource raises Cloudflare::BindingError.
cloudflare-env.md ↗
02 / DATA

Cloudflare D1

D1 exposes prepared statements. Build a statement with prepare, derive a bound one with bind, then choose a terminal operation such as run, rows, or first.

db = env["cloudflare.env"].DB
statement = db.prepare("SELECT id, name FROM users WHERE id = ?1")

user = statement.bind(1).first
created = db
  .prepare("INSERT INTO users (name) VALUES (?1)")
  .bind("Alice")
  .run
BoundaryParameters are scalar JSON values only: nil, String, Integer, finite Float, true, or false. Query builders, Ruby transaction blocks, BLOB parameters, and D1 Sessions are outside the current API.
cloudflare-d1.md ↗
03 / DATA

Workers KV

Use a named namespace for binary-safe reads and writes. The optional ttl: maps to the Worker API’s relative expiration.

cache = env["cloudflare.env"].CACHE_KV

cache.put("greeting", "Hello from PicoRuby", ttl: 300)
value = cache.get("greeting")

[200, { "content-type" => "text/plain" },
  [value || "missing"]]
BoundaryOnly get and put are exposed today. TTL must be at least 60 seconds; values are capped at 8 MiB at the Wasm boundary.
cloudflare-kv.md ↗
04 / OBJECTS

R2

Put Ruby Strings as raw bytes, inspect objects, list and delete keys, or return an original R2 body as a host-owned Worker stream.

bucket = env["cloudflare.env"].BUCKET
object = bucket.get("report.pdf")

return [404, { "content-type" => "text/plain" },
  ["not found"]] unless object

env["cloudflare.hijack"] = object.body
[200, { "content-type" => "application/pdf" }, []]
BoundaryA streamed R2 body is JavaScript-owned: Ruby cannot read or transform it. Do not set content-length or transfer-encoding; Workers controls framing.
cloudflare-r2.md ↗
05 / INFERENCE

Workers AI

Keep run model-agnostic. Use generate or embed when their explicit response helpers fit. For streaming, hand the returned descriptor to the Rack extension.

ai = env["cloudflare.env"].AI
stream = ai.generate(
  "@cf/meta/llama-3.1-8b-instruct",
  { "prompt" => "Tell a short Ruby story", "stream" => true }
)

env["cloudflare.hijack"] = stream
[200, { "content-type" => "text/event-stream" }, []]
BoundaryThe original SSE bytes pass from the host to the client. Ruby cannot read, transform, or observe completion of that stream; the Rack body must still be a valid enumerable.
cloudflare-ai.md ↗
06 / RETRIEVAL

Vectorize

Query by vector or ID, then insert, upsert, inspect, and delete vectors with JSON-compatible values.

index = env["cloudflare.env"].VECTOR_INDEX
matches = index.query(
  [0.1, 0.2, 0.3],
  top_k: 5,
  return_metadata: :indexed,
  namespace: "docs"
)
BoundaryThe bridge is buffered and JSON-only. top_k is 1–100, or at most 50 when returning values or all metadata.
cloudflare-vectorize.md ↗
07 / EVENTS

Queues

A producer sends one UTF-8 text message. Consumers receive a batch environment and settle messages with ack or retry.

# request path: producer
env["cloudflare.env"].EVENTS_QUEUE.send("event-created")

# queue consumer
class EventConsumer < Cloudflare::QueueConsumer::Base
  def call(_env)
    current_batch.messages.each { |message| message.ack }
  end
end

Cloudflare::Queues.run(EventConsumer)
BoundaryMessages are limited to 128 KiB. The producer currently supports one UTF-8 String at a time; batch sends, binary and JSON bodies, and delay options are not included.
cloudflare-queue.md ↗
08 / STATE

Durable Objects

The current adapter is a small JSON-backed object store. Select a named instance and read or write a POJO, Hash, Array, or to_pojo value.

store = Cloudflare::DurableObject.from_env(env, "OBJECTS")

profile = Cloudflare::DurableObject::POJO.new
profile["name"] = "Alice"
store.put("user-1", profile)

stored = store.get("user-1")
name = stored["name"]
BoundaryValues must be JSON-compatible. Payloads are capped at 1 MiB at the Wasm boundary, and the Worker module must export PicoRubyDurableObject.
cloudflare-durable-object.md ↗
09 / IDENTITY

Cloudflare Access

Use the Rack middleware to turn the CF_Authorization cookie into an AccessIdentity at env["cloudflare.identity"].

app = Rack::Builder.new do
  use Rack::Cloudflare::Access, team: "my-team"
  run lambda { |env|
    identity = env["cloudflare.identity"]
    [200, { "content-type" => "text/plain" }, [identity.email]]
  }
end

Rackup::Handler::CloudflareWorker.run(app)
BoundaryThis fetches and decodes the Access identity response; it does not locally validate JWT signatures, issuer, expiry, or audience, and it does not implement service-token authentication.
cloudflare-access.md ↗
10 / HOST UTILITIES

Fetch and Web Crypto

Call HTTP(S) through the Worker host, and use the browser-compatible Web Crypto API for secure random bytes and AES-GCM.

response = Cloudflare.fetch("https://example.com/api",
  method: "POST", body: "hello")

secret = SecureRandom.random_bytes(32)
iv, encrypted = Crypto.encrypt(:AES_GCM, secret, "private data")
plain = Crypto.decrypt(:AES_GCM, secret, iv, encrypted)
BoundaryFetch is buffered UTF-8 text only: no credentials in URLs, no redirects, a 10-second timeout, and a 1 MiB response cap. AES-GCM supports 16-, 24-, or 32-byte raw keys.