The handler context
A handler is called as handler(input, ctx). What ctx carries depends on how it was called, and
the generated types say which members each handler gets.
| Member | Where | What it does |
|---|---|---|
ctx.log(message) |
every handler | Writes one line to the host log under the realm’s name. |
ctx.gateway.<api>.<operation>(args) |
every handler | Calls an approved API operation. The host attaches the owner’s bound credential; the guest never sees it. See APIs. |
ctx.gateway.<graphql name>.<operation>({variables}) |
every handler | Runs one of the realm’s persisted GraphQL queries. See GraphQL. |
ctx.gateway.cypher.query({cypher, params}) |
every handler | Reads the owner’s graph under the separate cypher_query approval. See graph queries. |
ctx.gateway.channel.publish(...), .position(...), .publishBatch(...) |
every handler | Files events on the realm’s own sources. See publishing events. |
ctx.deps.<name> |
every handler, when dependencies are declared | A mounted dependency, such as SQLite. See dependencies. |
ctx.writePropose(proposal) |
every handler | Files a write for the owner to accept. See write proposals. |
ctx.frame, ctx.cursor, ctx.publish(...) |
channel handlers | The frame that caused the dispatch, the cursor the last dispatch kept, and a publish shortcut. |
ctx.headers |
webhook handlers | The headers of the verified request. |
ctx.stream.send(text), ctx.stream.close() |
websocket handlers | Writes to the channel’s own socket, or asks the host to reconnect it. |
ctx.frame, ctx.cursor, ctx.publish(...) |
consumers declared in connecting |
The published event, the consumer’s cursor, and a publish shortcut. |
ctx.assistant.chat(text, thread) |
consumers that declared assistant: true |
Asks the owner’s assistant and resolves to its final message. |
The generated ctx.gateway types each API namespace and the GraphQL namespace the realm
declares, and ctx.gateway.channel with the receipts the host returns (see
publishing events). Every other namespace, cypher included, is typed
unknown, so a handler that calls one gives it a type of its own; the
graph queries section shows one.
A channel or consumer handler has an alias of its own, named after the verb it points at:
| Declared as | Alias | Called as | Resolves to |
|---|---|---|---|
websocket onFrame or keepalive.handler |
<Verb>FrameHandler |
(frame, ctx: ChannelCtx & StreamCtx) |
{cursor?} |
long-poll onResponse |
<Verb>ResponseHandler |
(frame, ctx: ChannelCtx) |
{cursor?, cursorParam?} |
webhook onRequest |
<Verb>RequestHandler |
(frame, ctx: ChannelCtx & RequestCtx) |
{cursor?} |
connecting consumer |
<Verb>ConsumerHandler |
(event, ctx: HandlerContext & ConsumerCtx), plus AssistantCtx with assistant: true |
a JSON object |
A frame is the provider’s text or a control frame (Frame). Only the handler a channel names as
its keepalive.handler is typed to receive the keepalive tick; every other channel handler gets
FrameWithoutKeepalive. Two channels or consumers pointing at one verb share its alias, and synth
refuses them when they would need different ones. A dataPipes consumer gets the plain
<Verb>Handler and HandlerContext. The generated names may not collide with a type the realm
declares, so synth refuses a type named RealmTypes, HandlerContext, Handlers or
WriteProposal, and, in a realm with channels or consumers, Frame and the other channel
declarations.
Every ctx.gateway.<namespace>.<operation> call and ctx.writePropose returns a promise, which
resolves to the result or rejects. A refusal carries one fixed message and never says which rule
failed, so treat it as “not allowed at the moment” and carry on. The assistant call is the one
exception: it rejects with an AssistantRefusal, an Error whose code is
ASSISTANT_NOT_GRANTED (the consumer has no assistant grant) or SENDER_NOT_PAIRED (the owner
has not paired this sender), so a realm can answer with its own pairing hint. The code is a
property of the error and never part of its message. A refusal that gives no reason has no
code. The generated types export AssistantRefusal and AssistantRefusalCode for a realm that
declares channels or consumers.
import type { AssistantRefusal, ReplyConsumerHandler } from "../.embabel/realm.d.ts";
export const reply: ReplyConsumerHandler = async (event, ctx) => { const chatId = String(event.chatId); try { const answer = await ctx.assistant.chat(String(event.text), chatId); await ctx.gateway.telegram.sendMessage({ body: { chat_id: chatId, text: answer } }); } catch (e) { if ((e as AssistantRefusal).code === "SENDER_NOT_PAIRED") { await ctx.gateway.telegram.sendMessage({ body: { chat_id: chatId, text: "Ask the owner for a pairing code, then send: pair <code>" }, }); } } return {};};
