Inngest Runtime Mapping
Recipe-only code sketch for mapping Crux runtime targets onto Inngest functions and events.
Inngest is a recipe and adapter-author target in v1, not a bundled Crux runtime package. The goal is to keep Crux authoring stable while an adapter maps wake delivery and retries onto Inngest infrastructure.
User-authored Crux targets
Users should still write Crux flows and durable tasks, not Inngest-specific functions.
import { flow } from "@use-crux/core/flow";
import { durableTask, type RuntimeTaskContext } from "@use-crux/core/runtime";
export const embedDocument = durableTask("embed-document", {
run: async (input: { documentId: string }, _context: RuntimeTaskContext) => {
const { documentId } = input;
await embeddings.write(documentId);
},
});
export const reviewFlow = flow(
"review",
async (flow, input: { documentId: string }) => {
await flow.defer(embedDocument, { documentId: input.documentId });
const approval = await flow.waitFor(
{ name: "document.approved" },
{ match: { documentId: input.documentId }, timeout: "24h" },
);
return approval;
},
);Wake bridge sketch
An Inngest-backed wake adapter can publish one event per Crux wake. Use the Crux idempotency key as the event id when the substrate supports it.
import { Inngest } from "inngest";
import type { RuntimeWakeAdapter } from "@use-crux/core/runtime";
const inngest = new Inngest({ id: "app" });
export function inngestWake(): RuntimeWakeAdapter {
return {
id: "inngest",
capabilities: {
signed: true,
maxPayloadBytes: 4 * 1024,
maxDelayMs: 7 * 24 * 60 * 60 * 1000,
},
createWake({ url }) {
return async (envelope) => {
await inngest.send({
name: "crux/runtime.wake",
id: envelope.idempotencyKey,
data: {
url,
envelope,
},
});
};
},
};
}Inngest function sketch
The Inngest function acts as a delivery worker. It forwards the wake into the same fetch-compatible Crux handler used by generated serverless entries.
import { createRuntimeHandler } from "@use-crux/core/runtime";
import { embedDocument, reviewFlow } from "./targets";
const handler = createRuntimeHandler({
targets: [reviewFlow, embedDocument],
});
export const cruxWake = inngest.createFunction(
{ id: "crux-runtime-wake" },
{ event: "crux/runtime.wake" },
async ({ event, step }) => {
const body = JSON.stringify(event.data.envelope);
const response = await step.run("deliver-to-crux", () =>
handler.POST(
new Request(event.data.url, {
method: "POST",
body,
headers: {
"content-type": "application/json",
// A real adapter should sign this request or bind directly without HTTP.
"x-crux-signature": signCruxWake(body),
},
}),
),
);
if (response.status >= 500 || response.status === 409) {
throw new Error(`Crux wake retryable status ${response.status}`);
}
},
);Mapping notes
| Runtime concern | Inngest substrate |
|---|---|
| Wake/tasks/timers/retries | Inngest functions, events, and steps |
| Event waits | Inngest wait-for-event style waits or Crux waiter state |
| State/events/leases | Crux store or adapter-owned Inngest-backed state |
Do not split one invariant silently between Crux and Inngest. If Inngest owns retries or timers, the adapter must still surface Crux diagnostics such as TARGET_NOT_FOUND, REPLAY_DIVERGED, and WORK_DEAD_LETTERED.
Related
- Guide: Runtime Adapter Authoring
- Reference: Runtime Engine