The ctx object
Everything a handler is given, and which kind of function gets what.
Every handler's first argument is ctx. What it holds depends on the kind of
function, and the difference is enforced by the types rather than by convention.
| Query | Mutation | Action | What it is | |
|---|---|---|---|---|
ctx.db | ● | ● | ● | The tenant's database |
ctx.identity | ● | ● | ● | The verified caller |
ctx.requestId | ● | ● | ● | The correlation ID for this request |
ctx.now() | — | ● | ● | The clock. Deliberately absent from queries |
ctx.fetch | — | — | ● | HTTP, with the request deadline attached |
ctx.global | — | — | ● | The D1 read model |
ctx.run | — | — | ● | Run a mutation as its own transaction |
ctx.saga | — | — | ● | A durable, replayable sequence of steps |
ctx.signal | — | — | ● | Aborts when the deadline passes |
ctx.identity
The only trustworthy source of who is calling:
interface Identity {
readonly provider: string; // who vouched for this person
readonly subject: string; // their stable ID with that provider
readonly userId: UserId;
readonly tenantId: TenantId;
readonly roles: readonly string[];
}An accountId in args is a client-supplied string and means nothing. Read
identity off ctx, always.
ctx.db
ctx.db.query("notes").withIndex("byColor", { color }).take(20); // indexed read
ctx.db.table("notes").get(id);
ctx.db.table("notes").insert(row); // mutations only
ctx.db.table("notes").update(id, changes);
ctx.db.table("notes").delete(id);
ctx.db.table("notes").list(); // unindexed scanBoth forms record what they read, which is how a subscription knows what wakes it. → Reactivity
ctx.now()
A query has no clock. That is not an oversight: a query must return the same result for the same data, or the engine could not re-run it on your behalf and diff the outcome. Write the timestamp in the mutation and read the field in the query.
ctx.requestId
Pass it to your logger. It correlates a mutation, the patches it produced, and anything the client reported, across one request.