Files
skills/docs/api-stack/api/api-plugin-events/src/plugin/plugin.md
2026-05-29 08:20:56 +01:00

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 — 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

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) 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.