@roostjs/cloudflare

Typed wrappers around all Cloudflare Worker bindings: D1, KV, R2, Queues, AI, Vectorize, Durable Objects, and Hyperdrive.

Installation

bun add @roostjs/cloudflare

Configuration

Register CloudflareServiceProvider to make all binding wrappers available via the container. Bindings are resolved from the Worker env object by name.

import { Application } from '@roostjs/core';
import { CloudflareServiceProvider } from '@roostjs/cloudflare';

const app = Application.create(env);
app.register(CloudflareServiceProvider);

D1Database API

Typed wrapper around a Cloudflare D1 (SQLite) binding.

constructor(db: D1Database)

Construct with a raw D1 binding from the Worker environment.

run(query: string): Promise<D1ExecResult>

Execute raw SQL. Returns execution metadata. Use for DDL statements.

prepare(query: string): D1PreparedStatement

Create a prepared statement. Call .bind(...values) on the result to supply positional parameters, then .all(), .first(), or .run() to execute.

batch(statements: D1PreparedStatement[]): Promise<D1Result[]>

Execute multiple prepared statements in a single round-trip.

dump(): Promise<ArrayBuffer>

Export the entire database as a binary SQLite file.

KVStore API

Typed wrapper around a Cloudflare KV namespace binding.

constructor(kv: KVNamespace)

Construct with a raw KV namespace binding.

get<T>(key: string, type?: 'text' | 'json'): Promise<T | null>

Retrieve a value. Returns null if the key does not exist.

getWithMetadata<T, M>(key: string, type?: 'text' | 'json'): Promise<{ value: T | null; metadata: M | null }>

Retrieve a value along with its metadata object.

put(key: string, value: string | ArrayBuffer | ReadableStream, options?: KVPutOptions): Promise<void>

Store a value. KVPutOptions accepts expiration (Unix timestamp), expirationTtl (seconds from now), and metadata (arbitrary object).

putJson<T>(key: string, value: T, options?: KVPutOptions): Promise<void>

Store a value serialized as JSON.

delete(key: string): Promise<void>

Delete a key. No-op if the key does not exist.

list(options?: KVListOptions): Promise<KVNamespaceListResult>

List keys. KVListOptions accepts prefix, limit, and cursor for pagination.

R2Storage API

Typed wrapper around a Cloudflare R2 bucket binding.

constructor(bucket: R2Bucket)

Construct with a raw R2 bucket binding.

put(key: string, value: ReadableStream | ArrayBuffer | string, options?: R2PutOptions): Promise<R2Object | null>

Upload an object. Returns the stored R2Object metadata, or null on failure.

get(key: string): Promise<R2ObjectBody | null>

Download an object. Returns null if the key does not exist.

delete(keys: string | string[]): Promise<void>

Delete one or more objects.

list(options?: R2ListOptions): Promise<R2Objects>

List objects. Supports prefix, limit, and cursor.

head(key: string): Promise<R2Object | null>

Retrieve object metadata without downloading the body. Returns null if the key does not exist.

QueueSender API

Typed wrapper around a Cloudflare Queue producer binding.

constructor(queue: Queue)

Construct with a raw Queue binding.

send<T>(message: T, options?: QueueSendOptions): Promise<void>

Send a single message to the queue.

sendBatch<T>(messages: Iterable<MessageSendRequest<T>>): Promise<void>

Send multiple messages in a single operation.

AIClient API

Typed wrapper around the Cloudflare Workers Ai binding. This is the low-level client used internally by @roostjs/ai.

constructor(ai: Ai)

Construct with a raw Ai binding from the Worker environment.

run<T = string>(model: string, inputs: Record<string, unknown>, options?: AiOptions): Promise<T>

Execute inference on the specified model. The inputs shape is model-specific. The generic T parameter types the return value.

const ai = new AIClient(env.AI);

// Text generation
const text = await ai.run<string>('@cf/meta/llama-3.1-8b-instruct', {
  messages: [{ role: 'user', content: 'Hello' }],
});

// Image generation
const image = await ai.run<ArrayBuffer>('@cf/stabilityai/stable-diffusion-xl-base-1.0', {
  prompt: 'A sunset over mountains',
});

VectorStore API

Typed wrapper around a Cloudflare Vectorize binding.

constructor(index: VectorizeIndex)

Construct with a raw Vectorize index binding.

insert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>

Insert new vectors. Each vector requires an id (string) and values (number[]).

upsert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>

Insert or update vectors by ID.

query(vector: number[], options?: VectorizeQueryOptions): Promise<VectorizeMatches>

Find the closest vectors to the query vector. Options include topK (number of results) and returnMetadata.

deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation>

Delete vectors by their IDs.

DurableObjectClient API

Typed wrapper around a Durable Object namespace binding.

constructor(namespace: DurableObjectNamespace)

Construct with a raw Durable Object namespace binding.

get(id: DurableObjectId): DurableObjectStub

Get a stub for a specific Durable Object instance by ID.

HyperdriveClient API

Typed wrapper around a Cloudflare Hyperdrive binding for external database connections.

constructor(hyperdrive: Hyperdrive)

Construct with a raw Hyperdrive binding.

query(sql: string, params?: unknown[]): Promise<{ results: unknown[] }>

Execute a parameterized SQL query against the external database.

CloudflareServiceProvider

Service provider that registers all binding wrappers in the container. Binding names are read from the Worker env passed to Application.create(). The following bindings are registered by convention: DBD1Database, KVKVStore, FILESR2Storage, AIAIClient.

Auto-detection covers all supported binding types by duck-typing each value in env. The detection order is significant — more-specific guards run first:

Detected typeRegistered wrapper
R2BucketR2Storage
KVNamespaceKVStore
D1DatabaseD1Database
QueueQueueSender
AiAIClient
VectorizeIndexVectorStore
DurableObjectNamespaceDurableObjectClient
HyperdriveHyperdriveClient
DispatchNamespaceDispatchNamespaceClient
FetcherServiceClient

ServiceClient API

Typed wrapper around a Cloudflare Worker-to-Worker service binding (Fetcher). Provides HTTP convenience methods and a simple RPC helper.

constructor(fetcher: Fetcher)

get raw(): Fetcher

The underlying Fetcher binding.

fetch(url: string, init?: RequestInit): Promise<Response>

Low-level fetch. Use when the convenience methods don't fit.

get(path: string, options?: ServiceFetchOptions): Promise<Response>

Send a GET request to http://service{path}.

post(path: string, body?: unknown, options?: ServiceFetchOptions): Promise<Response>

Send a POST request with a JSON-serialized body and Content-Type: application/json.

put(path: string, body?: unknown, options?: ServiceFetchOptions): Promise<Response>

Send a PUT request with a JSON-serialized body.

patch(path: string, body?: unknown, options?: ServiceFetchOptions): Promise<Response>

Send a PATCH request with a JSON-serialized body.

delete(path: string, options?: ServiceFetchOptions): Promise<Response>

Send a DELETE request.

call<T = unknown>(method: string, ...args: unknown[]): Promise<T>

POST to /rpc/{method} with { args } as the JSON body. Throws ServiceCallError if the response status is not 2xx.

type ServiceFetchOptions = Omit<RequestInit, 'method'>;

ServiceCallError

class ServiceCallError extends Error {
  readonly method: string;
  readonly status: number;
  readonly body: string;
}

Thrown by ServiceClient.call() when the remote service returns a non-2xx status.

DispatchNamespaceClient API

Typed wrapper around a Cloudflare Workers for Platforms dispatch namespace binding.

constructor(namespace: DispatchNamespace)

get raw(): DispatchNamespace

The underlying DispatchNamespace binding.

dispatch(scriptName: string, options?: DispatchOptions): Fetcher

Resolve a Fetcher for the named customer script. Returns the raw Fetcher — use this when you need full control over the request.

dispatchClient(scriptName: string, options?: DispatchOptions): ServiceClient

Resolve the named customer script and wrap it in a ServiceClient for convenience method access.

interface DispatchOptions {
  trust?: 'untrusted' | 'trusted';
  outboundArgs?: unknown[];
}

ContainerClient API

Typed wrapper for Cloudflare Containers. Containers run via a DurableObjectNamespace binding — ContainerClient provides named-instance access without managing IDs manually.

constructor(namespace: DurableObjectNamespace)

get raw(): DurableObjectNamespace

The underlying namespace binding.

getStub(name: string): DurableObjectStub

Resolve a DurableObjectStub for the given name using idFromName.

send(name: string, path: string, options?: ContainerSendOptions): Promise<Response>

Send an HTTP request to the named container instance at the given path.

warmup(name: string): Promise<boolean>

Send a GET /health request to the named instance. Returns true if the response is 2xx, false on error or non-2xx. Use this to pre-warm containers before handling traffic.

interface ContainerSendOptions {
  method?: string;
  headers?: Record<string, string>;
  body?: BodyInit | null;
}

HtmlTransformer API

Chainable wrapper around Cloudflare's HTMLRewriter for streaming HTML transformation.

constructor()

injectScript(src: string, position?: 'head' | 'body'): this

Append a <script src="..."> tag inside the specified element. Defaults to 'head'.

setMetaTag(name: string, content: string): this

Update the content attribute of an existing <meta name="..."> tag, or insert a new one before </head> if absent.

replaceElement(selector: string, html: string): this

Replace the inner content of every matching element with the provided HTML string.

removeElement(selector: string): this

Remove every element matching the selector from the document.

abTest(selector: string, variants: Record<string, string>, assignmentFn: (request: Request) => string): this

Replace the inner content of the selector with a variant chosen by assignmentFn. The function receives the Request and returns a key into variants. Requires request to be passed to transform().

transform(response: Response, request?: Request): Response

Apply all registered transformations to the response stream. request is required when abTest() has been used; passing it is harmless otherwise.

const transformer = new HtmlTransformer()
  .injectScript('/analytics.js')
  .setMetaTag('description', 'Updated description')
  .removeElement('#legacy-banner');

return transformer.transform(response);

VersionedKVStore API

Content-addressable KV wrapper. Each put stores the serialized value under a SHA-256 content hash, then writes a pointer key. Reads follow the pointer. Enables cache invalidation by hash comparison without re-reading the full value.

constructor(kv: KVStore | KVNamespace, options?: VersionedKVOptions)

Accepts either a raw KVNamespace or a KVStore instance.

interface VersionedKVOptions {
  contentTtl?: number; // seconds; default 86400 (24h)
}

put<T>(key: string, value: T): Promise<string>

Serialize value as JSON, compute its SHA-256 hex hash, write the content under content:{hash} with contentTtl expiry, and update the pointer ptr:{key} to the hash. Returns the hash string.

get<T>(key: string): Promise<T | null>

Read the current pointer then fetch and parse the content. Returns null if the pointer or content is missing.

getVersion(key: string): Promise<string | null>

Return the current SHA-256 hash for the pointer without fetching content.

isCurrent(key: string, hash: string): Promise<boolean>

Return true if the stored pointer matches hash. Use to skip re-fetching when the caller already has a copy.

delete(key: string): Promise<void>

Delete the pointer key. Content keys expire naturally via TTL.

Rate Limiting API

KVRateLimiter

Fixed-window rate limiter backed by KV. Implements the Middleware interface.

constructor(kv: KVStore, config: RateLimiterConfig)

interface RateLimiterConfig {
  limit: number;
  window: number; // seconds
  keyExtractor?: (request: Request) => string;
}

Defaults to extracting CF-Connecting-IP (falling back to X-Forwarded-For, then 'unknown'). Returns 429 Too Many Requests with a Retry-After header when the limit is exceeded.

DORateLimiter

Fixed-window rate limiter backed by a Durable Object. Uses DurableObjectClient.get() to route each check to a named DO instance. More consistent than KV under concurrent traffic from the same key.

constructor(doClient: DurableObjectClient, config: RateLimiterConfig)

RateLimiterDO

The Durable Object class that DORateLimiter communicates with. Export it from your Worker and declare it as a durable_objects binding in wrangler.jsonc.

constructor(state: DurableObjectState)

fetch(request: Request): Promise<Response>

Handles POST /check with { key, limit, window } JSON body. Returns { allowed: boolean, remaining: number, retryAfter?: number }.

RateLimiterFake

In-memory test double that bypasses KV and DO. Activate via fakeRateLimiter().

limitKey(key: string): void

Mark a key as rate-limited for subsequent checks.

allowKey(key: string): void

Remove a key from the limited set.

assertLimited(key: string): void

Throw if the key was not checked, or was allowed.

assertAllowed(key: string): void

Throw if the key was not checked, or was limited.

assertChecked(key: string): void

Throw if the key was never checked by a rate limiter.

reset(): void

Clear all state. Call between tests.

fakeRateLimiter(): RateLimiterFake

Activate the global fake and return it. Both KVRateLimiter and DORateLimiter detect the active fake and defer to it instead of hitting KV or DO.

restoreRateLimiter(): void

Deactivate the global fake.