# plugin _(no submodules)_ ## Purpose Defines `createEventsPlugin`, the factory that returns the `PluginDescriptor` 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 — `onReady` refuses to mount when the configured `basePath` already has routes registered, raising `EventsPluginError(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 in `enterprise/api-plugin-events.md`). - `onClose` clears repository references so the GC can collect them. ## Usage: Basic Example ```ts 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 ```ts 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` 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)` if `options` fails `EventsPluginOptionsSchema`. ### Lifecycle hooks (attached to the returned descriptor) - `onInit(ctx)` — pulls `db` from the registry, constructs the repositories, registers `eventRepository` and `rsvpRepository`, emits `events:initialized`. - `onReady(ctx)` — pulls `app`, builds routes via `createEventRoutes`, mounts under `config.basePath`, emits `events: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.