@beignet/provider-inngest
v0.0.3
Published
Inngest provider for Beignet - adds inngest port for background jobs and events
Downloads
410
Maintainers
Readme
@beignet/provider-inngest
Inngest-backed JobDispatcherPort provider for Beignet applications.
The provider installs the app-facing ctx.ports.jobs dispatcher for durable
background jobs using Inngest and exposes
ctx.ports.inngest only as an escape hatch for raw Inngest access.
Install
bun add @beignet/provider-inngest @beignet/core inngestSetup
import { createNextServer } from "@beignet/next";
import { definePorts } from "@beignet/core/ports";
import { inngestProvider } from "@beignet/provider-inngest";
import { routes } from "@/server/routes";
// Set environment variables:
// INNGEST_APP_NAME=my-app (optional, defaults to "beignet-app")
// INNGEST_EVENT_KEY=your-event-key (optional, required for Inngest Cloud)
const appPorts = definePorts({});
export const server = await createNextServer({
ports: appPorts,
providers: [inngestProvider],
createContext: ({ ports }) => ({
ports,
}),
routes,
});Usage
Once the provider is registered, your ports include:
jobs: the canonicalJobDispatcherPortfor app code.inngest: the raw Inngest escape hatch for advanced usage.
Define jobs with @beignet/core/jobs, then dispatch them through
ctx.ports.jobs:
import { createJobHandlers, retry } from "@beignet/core/jobs";
import { z } from "zod";
const jobs = createJobHandlers<AppCtx>();
export const SendInviteEmailJob = jobs.defineJob("mail.invite.send", {
payload: z.object({
inviteId: z.string(),
inviteeEmail: z.string().email(),
}),
retry: retry.exponential({
attempts: 3,
}),
async handle({ payload, ctx }) {
await ctx.ports.mailer.send({
to: payload.inviteeEmail,
subject: "You were invited",
text: `Invite id: ${payload.inviteId}`,
});
},
});
async function inviteUser(ctx: AppCtx, input: InviteUserInput) {
const invite = await ctx.ports.db.invites.create({
inviterId: ctx.actor.id,
inviteeEmail: input.email,
});
await ctx.ports.jobs.dispatch(SendInviteEmailJob, {
inviteId: invite.id,
inviteeEmail: input.email,
});
return invite;
}Configuration
The Inngest provider reads configuration from environment variables with the INNGEST_ prefix:
| Variable | Required | Description | Default |
|----------|----------|-------------|---------|
| INNGEST_APP_NAME | No | Friendly application name shown in Inngest | "beignet-app" |
| INNGEST_EVENT_KEY | No | Event key / signing key for Inngest Cloud | - |
Note: INNGEST_EVENT_KEY is required when using Inngest Cloud for production deployments.
Ports
jobs: JobDispatcherPort
The jobs port is the recommended application API. It validates and parses the
job payload with the job definition before sending an Inngest event using the
job name.
await ctx.ports.jobs.dispatch(SendInviteEmailJob, {
inviteId: invite.id,
inviteeEmail: input.email,
});inngest: InngestPort
The inngest port is an escape hatch for direct Inngest usage.
send<TData>(args: { name: string; data: TData }): Promise<void>
Send a raw event to Inngest. Prefer ctx.ports.jobs.dispatch(...) for
first-class Beignet jobs.
await ctx.ports.inngest.send({
name: "user.invited",
data: {
inviterId: ctx.actor.id,
inviteeEmail: input.email,
inviteId: createdInvite.id,
},
});client: Inngest
Access the underlying Inngest client for advanced operations.
// Define Inngest functions using the client directly
const myFunction = ctx.ports.inngest.client.createFunction(
{ id: "my-function" },
{ event: "user.invited" },
async ({ event, step }) => {
// Your function logic
}
);Devtools
When @beignet/devtools is registered before this provider, calls to
ctx.ports.jobs.dispatch(...) and ctx.ports.inngest.send(...) are recorded
automatically under the jobs watcher. Successful enqueues are recorded as
scheduled; failed enqueues are recorded as failed with schedule-phase error
details.
Pass an instrumentation target to createInngestJobFunction(...) to record
worker execution as started, completed, and failed events:
const sendInviteEmail = createInngestJobFunction({
client: inngest,
job: SendInviteEmailJob,
ctx: () => createBackgroundContext(),
instrumentation: appPorts.devtools,
});TypeScript support
To get proper type inference, include both provider-contributed ports in your app ports type:
import { definePorts } from "@beignet/core/ports";
import type { JobDispatcherPort } from "@beignet/core/ports";
import type { InngestPort } from "@beignet/provider-inngest";
// Your base ports, if any
const basePorts = definePorts({});
type AppPorts = typeof basePorts & {
jobs: JobDispatcherPort;
inngest: InngestPort;
};Wiring domain events → Inngest jobs
This provider does NOT automatically subscribe to domain events. That is intentional: events are facts that happened, while jobs are explicit work to do.
To wire a domain event to a durable job, register a listener in your application and dispatch the job from the listener:
// features/users/listeners.ts
import { createEventHandlers } from "@beignet/core/events";
import { UserInvited } from "@/features/users/domain/events";
import { SendInviteEmailJob } from "@/features/users/jobs";
import type { AppCtx } from "@/app-context";
const events = createEventHandlers<AppCtx>();
export const sendInviteEmail = events.defineListener(UserInvited, {
name: "mail.send-invite-email",
async handle({ payload, ctx }) {
await ctx.ports.jobs.dispatch(SendInviteEmailJob, {
inviteId: payload.inviteId,
inviteeEmail: payload.inviteeEmail,
});
},
});Register that listener against your event bus during infrastructure startup.
In tests, use createInlineJobDispatcher(...) from @beignet/core/jobs; in
production, install inngestProvider.
Inngest functions
Use createInngestJobFunction(...) to turn a first-class Beignet job into
an Inngest function. The helper subscribes to job.name, validates incoming
event data with parseJobPayload, and then calls job.handle(...).
If the job defines a retry policy, the helper maps Beignet's total
retry.attempts value to Inngest's function-level retries option. Inngest
supports retry values from 0 to 20, so Beignet accepts total attempt counts
from 1 to 21 for Inngest-backed jobs.
Inngest-backed jobs support Beignet's retry attempt count only. Custom Beignet
backoff fields, jitter, and retryIf classification are outbox-worker
semantics; createInngestJobFunction(...) fails fast if a job policy includes
retry behavior that the adapter cannot honor.
Define functions separately from your Beignet server, usually in a serverless function or route handler:
// app/api/inngest/route.ts (Next.js App Router example)
import { createInngestJobFunction } from "@beignet/provider-inngest";
import { serve } from "inngest/next";
import { inngest } from "@/infra/inngest";
import { SendInviteEmailJob } from "@/features/users/jobs";
import { createBackgroundContext } from "@/infra/background-context";
const sendInviteEmail = createInngestJobFunction({
client: inngest,
job: SendInviteEmailJob,
ctx: () => createBackgroundContext(),
});
export const { GET, POST, PUT } = serve({
client: inngest,
functions: [sendInviteEmail],
});createBackgroundContext() is application-owned. Put it near your
infrastructure wiring and return the ports, logger, auth assumptions, and
devtools context your background workers need.
Lifecycle
The Inngest provider:
- During
setup:- Creates Inngest client
- Returns the
jobsport - Returns the
inngestescape hatch port
- No cleanup needed: Inngest client doesn't require explicit shutdown
Error handling
The provider will throw errors in these cases:
- Missing required configuration (though all fields have defaults or are optional)
- Invalid configuration values
Make sure to handle these during application startup.
Local development
For local development, you can run the Inngest Dev Server:
npx inngest-cli@latest devThis provides a local UI at http://localhost:8288 where you can:
- View events
- Trigger functions
- Debug execution
License
MIT
