@roostjs/cloudflare
Typed wrappers around all Cloudflare Worker bindings: D1, KV, R2, Queues, AI, Vectorize, Durable Objects, and Hyperdrive.
Installation
bun add @roostjs/cloudflareConfiguration
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: DB → D1Database,
KV → KVStore, FILES → R2Storage,
AI → AIClient.
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 type | Registered wrapper |
|---|---|
R2Bucket | R2Storage |
KVNamespace | KVStore |
D1Database | D1Database |
Queue | QueueSender |
Ai | AIClient |
VectorizeIndex | VectorStore |
DurableObjectNamespace | DurableObjectClient |
Hyperdrive | HyperdriveClient |
DispatchNamespace | DispatchNamespaceClient |
Fetcher | ServiceClient |
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.