3.9 KiB
plugin
(no submodules)
Purpose
Defines createEventsPlugin, the factory that returns the
PluginDescriptor<EventsPluginConfig> consumed by @suaveplan/api's
plugin registry. The factory Zod-validates options at the boundary,
constructs the runtime closure, and returns the four lifecycle hooks
(onInit, onReady, onClose) that the registry's topological
sorter wires up after the database dependency has booted.
The lifecycle is intentionally split: onInit reads the db slot
and registers the two repositories so downstream plugins (search,
audit, notifications) can resolve eventRepository /
rsvpRepository during their own onInit; onReady is the latest
phase the registry runs, so the app slot is guaranteed-populated
and the route mount is the last side effect before the host starts
listening.
Features
- Zod-validated config at construction time — invalid options raise
EventsPluginError(INVALID_CONFIG)before the descriptor leaves the factory. - Lifecycle dedup —
onReadyrefuses to mount when the configuredbasePathalready has routes registered, raisingEventsPluginError(ROUTE_PREFIX_COLLISION)instead of silently shadowing the first mount. - Forwards typed route events to the host's shared
ctx.eventBus(fixes the upstream "local emitter" smell flagged inenterprise/api-plugin-events.md). onCloseclears repository references so the GC can collect them.
Usage: Basic Example
import { createEventsPlugin } from "@suaveplan/api-plugin-events";
import { quickstart } from "@suaveplan/api";
import { createDatabasePlugin } from "@suaveplan/api-plugin-database";
const app = await quickstart({
plugins: [
createDatabasePlugin({
/* ... */
}),
createEventsPlugin(), // mounts under /events by default
],
});
Usage: Advanced Example
import { createEventsPlugin } from "@suaveplan/api-plugin-events";
const plugin = createEventsPlugin({
basePath: "/v1/events",
enableNotifications: false,
maxInvitesPerRequest: 25,
});
// Inspect the descriptor before handing it to the registry
console.log(plugin.name); // "events"
console.log(plugin.dependencies); // ["database"]
console.log(plugin.defaults.maxInvitesPerRequest); // 25
// Subscribe to lifecycle and domain events on the shared bus:
host.eventBus.on("events:ready", () => {
/* routes are now mounted */
});
host.eventBus.on("event:created", ({ eventId, creatorId }) => {
/* fan out to notifications, search index, audit log */
});
API Reference
createEventsPlugin(options?: EventsPluginConfigInput): PluginDescriptor<EventsPluginConfig>
Builds the plugin descriptor.
- options — Partial
EventsPluginConfig. Each field has a default; passing{}(or no argument) produces the default config (/events,enableNotifications: false,maxInvitesPerRequest: 50). - throws
EventsPluginError(INVALID_CONFIG)ifoptionsfailsEventsPluginOptionsSchema.
Lifecycle hooks (attached to the returned descriptor)
onInit(ctx)— pullsdbfrom the registry, constructs the repositories, registerseventRepositoryandrsvpRepository, emitsevents:initialized.onReady(ctx)— pullsapp, builds routes viacreateEventRoutes, mounts underconfig.basePath, emitsevents:ready.onClose()— clears repository refs.
Implementation Notes
The plugin's dependencies: ["database"] declaration makes the
suaveplan registry topo-sort the events plugin strictly after the
database plugin. Without that ordering, onInit would fail because
ctx.getService("db") would resolve to undefined. The app slot
does not need to be declared as a dependency because every host
seeds it during registry construction (per the foundation patch
shipped on 2026-05-17); the runtime check inside onReady is
defensive against custom hosts that don't pre-seed.