better-effect
better-effect-mq

Enqueueing Jobs

Run typed producer programs against a configured JobStore.

A Job is a typed producer program. It needs a Runtime that provides its bound JobStore token. The producer owns the Runtime and decides when it is disposed.

The following continuation uses the canonical SendEmail descriptor from Defining Jobs, where its schema-first better-effect-schema payload codec is declared.

import { Effect, Layer, Runtime } from 'better-effect'
import { ClockLive } from 'better-effect/standard-services'
import { Result } from 'better-result'
import { MemoryJobStore } from 'better-effect-mq'

const runtime = await Runtime.make(Layer.merge(MemoryJobStore.layer, ClockLive))
try {
  const result = await runtime.run(() =>
    Effect.gen(async function* () {
      const id = yield* SendEmail.enqueue({
        messageId: 'message-123',
        recipient: 'ada@example.test'
      })
      return Result.ok(id)
    })
  )
  if (Result.isError(result)) throw result.error
} finally {
  await runtime.dispose()
}

enqueue validates and encodes the payload before insertion. enqueueMany processes bounded chunks, preserves input order and can therefore leave an intentional partial batch if a later store operation fails. poll, awaitResult, attempts, cancel, promote and retry are separate typed operations; execute means enqueue then await, not exactly-once RPC.

The canonical SendEmail descriptor from Defining Jobs should normally use a provider-backed Codec.standardSchema codec, such as a Zod 4 schema connected through better-effect-schema. The trusted JSON escape hatch remains available for already-trusted JSON values, but is not the boundary default.

Publishing idempotency and handler idempotency are different. An idempotencyKey prevents duplicate publication for the same store scope. It does not make an external side effect safe when a Worker crashes before its settlement is persisted. See Idempotency and delivery.

For a producer-only process, import the Job definitions and producer APIs but do not import or start a Worker. A durable adapter can replace MemoryJobStore.layer without changing this producer program.