Effect-native
Jobs are schemas, handlers are Effects, workers are Layers. Interruption, retries, and timeouts use the runtime — not bolted-on process machinery.
Schema-typed payloads. Swappable storage. At-least-once execution. One package.
import { Job, Worker } from "effect-mq"
import { DrizzleJobStore } from "effect-mq/drizzle-postgres"
import { Effect, Layer, Schema } from "effect"
// 1. Define once — shared by producers and runners.
class GenerateInvoice extends Job.make("generate-invoice", {
payload: { invoiceId: Schema.String },
idempotencyKey: ({ invoiceId }) => invoiceId,
defaults: { attempts: 5, backoff: { type: "exponential", delay: "1 second" } }
}) {}
// 2. Produce — needs the store, never the worker.
const jobId = yield* GenerateInvoice.enqueue({ invoiceId: "inv_123" })
// 3. Run — a layer, wherever you deploy it.
const RunnerLive = GenerateInvoice.toLayer(
({ invoiceId }) => Effect.log(`rendering ${invoiceId}`),
{ concurrency: 5 }
).pipe(Layer.provideMerge(Worker.layer()))bun add effect-mq # or npm / pnpm / yarn