@harlan-zw/nuxt-domain-events

Experimental lazy, layer-aware domain events for Nuxt server runtimes.
14,376 13
Install
npm install @harlan-zw/nuxt-domain-events
Latest Version:0.2.1
Vue:3
License:MIT
Last Updated:Oct 21, 2026
Links: GitHub  ·  npm
Maintainer: harlan_zw

Nuxt Domain Events

npm version npm downloads License Nuxt

Lazy, layer-aware domain events for Nuxt server runtimes.

Why Nuxt Domain Events?

If a producer calls its listeners directly, it must import every one of them. That coupling grows with each listener you add:

  • 🧶 Coupled producers: A new listener in another layer forces an edit to the producer.
  • 🎲 Surprise execution: A listener moves to deferred or queued work, and the producer does not know.
  • 🧳 Leaked request state: Request-scoped or mutable state ends up in a queue message.
  • 👻 Rolled-back events: A listener acts on a write that the transaction never committed.

Nuxt Domain Events lets a producer fire a server-side domain event without importing any listener that handles it. The module generates a registry for each layer and imports it lazily.

Features

  • 🗂️ Generated lazy registries: A producer never imports its listeners, so a new listener in any layer leaves the producer untouched.
  • 🎚️ Explicit execution modes: A listener never runs later or in a queue by surprise, because every mode except serial synchronous is an opt-in.
  • 📦 Two contract kinds: Request-scoped state never leaks into a queue, because only transfer contracts reach queued listeners.
  • 🏷️ Errors as tagged values: You branch on a known tag, such as payload mismatch or registry drift, instead of parsing error messages.
  • 💾 After-commit publication: A rolled-back transaction leaves no queue rows, so no listener acts on data that never committed.
  • 🚰 One-call deferred drain: Producers stop repeating the same collect-and-drain loop for deferred work.

Installation

npx nuxi@latest module add @harlan-zw/nuxt-domain-events

[!TIP] Generate an Agent Skill for this package using skilld:

npx skilld add @harlan-zw/nuxt-domain-events

The command adds the module to nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@harlan-zw/nuxt-domain-events'],
})

Execution modes

Listeners work like Laravel listeners. If you omit execution, listeners run one at a time, synchronously, and a failure propagates. That failure aborts the producer. It also stops deferred work and queue publication. To change this, opt into sync isolation, deferred, or queued.

A queued listener cannot declare shouldHandle. A synchronous or deferred listener can use it as an in-process condition. A queued listener can declare a failed(payload, context, error) callback. The module calls it after the delivery reaches its final outcome.

Deferred dispatch

dispatchEvent schedules deferred listeners through context.waitUntil. dispatchEventAndDrain saves each producer from writing its own collect-and-drain loop:

await dispatchEventAndDrain('user:registered', payload, {
  waitUntil: event.context.cloudflare?.context?.waitUntil?.bind(event.context.cloudflare.context),
})

If the host supplies waitUntil, the call hands deferred work to it. If not, the call awaits deferred work before it resolves. In both cases, a deferred failure stays isolated.

Queues

domainEvents.queues names the logical queues that queued listeners may use. To derive the list from cfJobs.queues, omit it or set it to [].

Observer

domainEvents.observer names a server module that exports observeEventListener. A relative path resolves against the layer that declares it, so a layer can ship its own observer. If you configure no observer, the build warns. Listener and dispatch failures then reach stderr only.

Event contracts

A local contract can carry request-scoped or mutable state. It supports synchronous listeners and deferred waitUntil listeners in the same isolate. It never supports queued delivery.

A transfer contract owns a versioned JSON codec and a byte limit. Queued delivery parses the payload with that contract before it imports or calls the listener.

Errors

A runtime error is a tagged Error value, and it rejects dispatch. The expected tags are unknown event, payload mismatch, lazy import failure, registry drift, queue failure, and after-commit misuse. If the observer itself fails, the module calls the configured fallback, or console.error. The business outcome and its label stay the same.

Queued listeners

A queued listener needs explicit idempotency, and the caller must pass a stable eventId. The module derives a stable delivery ID from eventId + listenerName. Producer dispatch never imports a queued listener implementation.

After-commit events

The after-commit flow calls planEvent, then commitEventPlan. Every listener for that event must be queued with publication: 'after-commit'. A D1 batch is non-interactive. So it cannot run ordinary listeners after domain SQL and still let their failure roll back that SQL. If one producer needs both ordinary and after-commit listeners, split the event contract.

The caller supplies the unit of work. It stages the adapter's unpublished D1 statements beside the domain writes. On rollback, it returns rolled-back and leaves no publication rows. Otherwise, it returns the adapter's exact staged-delivery receipt, and only after D1 resolves. A failed send stays as an unpublished durable row, so you can recover it.

Cloudflare Jobs adapter

You pass the public @harlan-zw/nuxt-cf-jobs/outbox functions to the ./cf-jobs adapter, which types them by shape. So the event core stays runtime-neutral, and the dependency runs one way. The adapter's generic delivery definition declares the static maintenance queue, for @harlan-zw/nuxt-cf-jobs registry locality. The public outbox route override then stores each listener job on the queue that listener declares.

Not supported

The module does not support these: mixed ordinary and after-commit listeners on one transaction-bound event, producer-time Laravel shouldQueue, cooperative listener timeouts, listener ordering contracts, grouped subscriber modules, dashboards, CLI, and compatibility shims.

Sponsors

License

Licensed under the MIT license.

Related Packages

@harlan-zw/nuxt-cloudflare

Opinionated Cloudflare deployment defaults and diagnostics for Nuxt.

24,264 13
@harlan-zw/comark-content

Markdown-only Nuxt content powered by Comark.

21,053 13
@harlan-zw/nuxt-dx

Experimental Nuxt diagnostics: dev error overlay and runtime JavaScript bundle s...

19,993 13
@harlan-zw/nuxt-cf-jobs

Experimental Nuxt module for typed Cloudflare queue jobs.

19,143 13
@harlan-zw/nuxt-use-query

Experimental Nuxt-native query composables with SWR, invalidation, polling, and...

18,565 13

Version History

Version Released License
0.2.1 MIT
0.2.0 MIT
0.1.2 MIT
0.1.1 MIT
0.1.0 MIT