Skip to content

Producers

A captured producer makes a graph label fetchable on demand by calling one of the realm’s own handlers.

producers: {
"incidents-by-service": {
handler: "oncall.incidents",
keyArgument: "serviceIds",
joins: [
{ targetLabel: "Incident", anchorLabel: "Service", relationship: "HAS_INCIDENT",
keyField: "serviceId", recordKeyField: "serviceId" },
],
page: { argument: "cursor", maxPages: 4 },
pushdown: [
{ property: "impact", argument: "impact" },
{ property: "status", argument: "status" },
],
},
},

The handler contract. The handler receives an object holding the anchor keys as a list of strings under keyArgument. Without page, it returns an array of record objects. Each record carries its anchor’s key under recordKeyField, which is how the host links it back. Records may not supply userId, worldId, workspaceId, visibleTo or any property starting with __vc; the host owns those.

import type { IncidentsHandler } from "../.embabel/realm.d.ts";
interface IncidentPage {
items: Record<string, unknown>[];
nextCursor?: string;
}
export const incidents: IncidentsHandler = async (input, ctx) => {
const page = (await ctx.gateway.statuspage.listIncidents({
services: input.serviceIds.join(","),
impact: input.impact?.join(","), // present only when the query pinned impact
status: input.status?.join(","),
after: input.cursor, // absent on the first page
})) as IncidentPage;
return { rows: page.items, next: page.nextCursor ?? null };
};

Joins. 1 to 8 distinct joins, each naming the label it produces (targetLabel), the stored label it starts from (anchorLabel), the relationship, the anchor property whose values become keys (keyField), and the record field carrying the key back (recordKeyField).

Paging. With page, the handler returns {rows, next}. The host calls it again with next under page.argument until next is null or maxPages (1 to 16) is reached. A repeated cursor, a cursor over 2048 bytes, or a non-null next on the last allowed page refuses the whole fetch; the host never returns a truncated result. The details are in the paging profile.

Pushdown. Each rule maps a record property to a handler argument. When a query pins the property with = or IN, the handler receives the allowed values as a list of strings under that argument, on every page. The graph applies every filter to the returned rows again, so pushdown changes cost and never answers. Declare a rule for every property your source can filter on. A filter left undeclared makes the handler fetch every record the key allows, and the answer is identical, so nothing tells you it happened. Where the host shows query diagnostics, they name the arguments that reached the handler on each call; a filter missing from that list is the next rule to declare. See the pushdown profile.

Names and arguments. The producer’s key is [a-z][a-z0-9._-]{0,63}. keyArgument, every join field and every pushdown property are identifiers, [A-Za-z_][A-Za-z0-9_]{0,63}. page.argument and every pushdown argument match [a-z][A-Za-z0-9]{0,63}, and no two of the key, page and pushdown arguments may share a name. At most 16 pushdown rules, each listed once. handler must be a handler the realm declares: defineRealm checks it at compile time, and synth refuses one the realm does not declare, since the host refuses the producer. If the handler’s input schema sets additionalProperties: false, list the page and pushdown arguments in its properties, or the host refuses the producer at bind time.

Reaching a producer. A query reaches the target label only by traversing one of its joins from a bound anchor: a stored node pinned by a value or narrowed by a predicate. A bare MATCH (i:Incident) is refused. A producer’s target may anchor another producer’s join, so producers chain, and the engine stages each hop after the one before it. See Virtual Cypher §2.

Limits and scope. Each fetch is at most 256 keys of up to 2048 characters, 64 KiB of arguments, 1 MiB of output and 1,024 rows, across all pages together. A refused fetch carries a reason code. Producer results are never cached. Only producers from the querying handler’s own installation run, each as a nested call that needs that handler’s grants as well as its own.