pub struct Ctx { /* private fields */ }Expand description
Per-request application context: the Worker environment with typed access to its bindings.
serve builds one per request and passes it to the router
as axum state, so handlers take State(ctx): State<Ctx>. Queue consumers,
cron runs and inbound email handlers receive one too. Cloning is cheap (the
environment is a JavaScript handle) and the type is Send, so it can be
held across .await in plain axum handlers.
Creating a Ctx costs nothing on the free plan; each binding call
(db, ocre::cache, ocre::storage…) only looks the
binding up.
A Ctx and its clones share two per-request memories, like Rails’ query
cache and local cache: identical SELECTs through db are
answered from memory until a write (see Db), and ocre::cache
reads each KV key at most once. Each request, queued job and cron run
gets its own.
§Examples
use axum::{Router, extract::State, routing::get};
use ocre::{ApiResult, Ctx};
async fn count(State(ctx): State<Ctx>) -> ApiResult<String> {
let db = ctx.db()?;
let rows: Vec<serde_json::Value> = db.all("SELECT COUNT(*) AS n FROM posts", ocre::params![]).await?;
Ok(rows[0]["n"].to_string())
}
fn routes() -> Router<Ctx> {
Router::new().route("/count", get(count))
}Implementations§
Source§impl Ctx
impl Ctx
Sourcepub fn log(&self) -> &Logger
pub fn log(&self) -> &Logger
The logger of this request, job batch or cron run (see ocre::log).
In a request its lines carry request_id, method and path, the
same request id as the RequestId extractor and
the X-Request-Id response header. Add fields with
Logger::with. No binding call; each line is one Workers Logs event.
§Examples
use axum::extract::State;
use ocre::Ctx;
async fn import(State(ctx): State<Ctx>) -> &'static str {
let log = ctx.log().with("import_id", 12);
log.info("import started");
log.warn(format_args!("{} rows skipped", 3));
"OK"
}Sourcepub fn errors(&self) -> &Reporter
pub fn errors(&self) -> &Reporter
The error reporter of this request, job batch or cron run (see ocre::errors).
Reports are logged at once and sent to the registered subscribers when the response is ready (or the job or cron run ends).
§Examples
use axum::extract::State;
use ocre::{Ctx, Result};
async fn refresh(State(ctx): State<Ctx>) -> Result<&'static str> {
ctx.errors().set_context("feed", "rss");
let body: Option<String> = ctx.errors().handle("<rss/>".parse::<String>());
Ok(if body.is_some() { "refreshed" } else { "kept the old feed" })
}Sourcepub fn events(&self) -> &Events
pub fn events(&self) -> &Events
Structured events of this request or job (Rails’ Rails.event), see ocre::events.
§Examples
use axum::extract::State;
use ocre::Ctx;
async fn signup(State(ctx): State<Ctx>) -> &'static str {
ctx.events().notify("user.signed_up", serde_json::json!({ "plan": "free" }));
"welcome"
}Sourcepub fn config<T: DeserializeOwned>(&self) -> Result<T>
pub fn config<T: DeserializeOwned>(&self) -> Result<T>
The app’s settings, read from Worker variables and secrets into T (see ocre::config).
Each field reads the variable of the same name in upper case, or else the secret. No binding call: one environment lookup per field.
§Errors
Error::Internal (500, logged) naming the variable when a required
one is missing or does not convert to its field’s type.
§Examples
use axum::extract::State;
use ocre::{Ctx, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct Settings {
support_email: String,
max_uploads: Option<u32>,
}
async fn contact(State(ctx): State<Ctx>) -> Result<String> {
let settings: Settings = ctx.config()?;
Ok(settings.support_email)
}Sourcepub fn env(&self) -> &Env
pub fn env(&self) -> &Env
The raw Workers environment, for bindings Ocre does not wrap yet.
Use it for vars, secrets and bindings such as AI or Vectorize; prefer the Ocre helpers when one exists, since their errors name the cloudflare.config.ts fix.
§Examples
use axum::extract::State;
use ocre::{Ctx, Result};
async fn app_name(State(ctx): State<Ctx>) -> Result<String> {
let name = ctx.env().var("APP_NAME")?;
Ok(name.to_string())
}Sourcepub fn secret(
&self,
name: &str,
) -> impl Future<Output = Result<String>> + Send + use<>
pub fn secret( &self, name: &str, ) -> impl Future<Output = Result<String>> + Send + use<>
The secret name, wherever it is kept: a Worker secret (ocre secrets push), a .dev.vars value in ocre dev, or a secret of the
account’s Secrets Store bound to the Worker in cloudflare.config.ts
(NAME: bindings.secretsStoreSecret({ storeId, secretName }), which
ocre secrets push NAME --store writes). Code reads it the same way
wherever it lives, so moving a secret to the store changes no code.
A Worker secret is read without I/O; a Secrets Store secret costs one
call to the store. Secrets Ocre reads without awaiting
(SECRET_KEY_BASE, R2_*) must stay Worker secrets.
§Errors
Error::Internal (500, logged) when the secret is missing or
empty, naming where to set it.
§Examples
use axum::extract::State;
use ocre::{Ctx, Result};
async fn rates(State(ctx): State<Ctx>) -> Result<String> {
let key = ctx.secret("RATES_API_KEY").await?;
Ok(format!("{} characters", key.len()))
}Sourcepub fn db(&self) -> Result<Db>
pub fn db(&self) -> Result<Db>
The application database: the D1 binding DB.
Looking the binding up runs no query and costs no D1 rows; see Db
for what each query reads and writes.
§Errors
Error::Internal (500, logged) when the Worker has no DB binding;
the message says to add DB: bindings.d1({ name: "<app>" }) to
cloudflare.config.ts.
§Examples
use axum::extract::State;
use ocre::{Ctx, Result};
async fn handler(State(ctx): State<Ctx>) -> Result<String> {
let db = ctx.db()?;
let changed = db.execute("DELETE FROM sessions WHERE expires_at < ?1", ocre::params![ocre::now()]).await?;
Ok(format!("{changed} expired"))
}Sourcepub fn db_named(&self, binding: &str) -> Result<Db>
pub fn db_named(&self, binding: &str) -> Result<Db>
Another D1 database of the app, by its binding name (Rails’ multiple databases).
Each database is a KEY: bindings.d1({ name: "..." }) entry in
cloudflare.config.ts with its own binding key and database name (see the Models
guide, “Several databases”). Queries cannot join across databases:
load ids from one, then find_many in the other.
§Free plan
Up to 10 databases per account, 5 GB of storage and the daily row quotas shared by all of them.
§Errors
Error::Internal (500, logged) when the Worker has no D1 binding
named binding; the message names the cloudflare.config.ts entry to add.
§Examples
use axum::extract::State;
use ocre::{Ctx, Result, params};
async fn track(State(ctx): State<Ctx>) -> Result<()> {
let analytics = ctx.db_named("ANALYTICS")?;
analytics.execute("INSERT INTO page_views (path) VALUES (?1)", params!["/"]).await?;
Ok(())
}