# add-api-plugin-events **Package**: `@suaveplan/api-plugin-events` **Status**: PROPOSED **Type**: new-package **Design**: (none — straightforward migration; see this proposal + spec delta) **Spec delta**: ./specs/api-plugin-events/spec.md ## Why Genesis ships the foundational pieces of `@suaveplan/api` (database, search, auth, etc.) but is missing the **event-management** plugin (calendar-style entity: schedule events, RSVP, invite users). Without this plugin, downstream apps that need meetup / RSVP / attendee flows have to hand-roll the surface — there is no other `@suaveplan/*` capability that covers it. Upstream `enterprise/api-plugin-events.md` documents the audit (3 `*.test.ts` files, parity-only migration target). This change brings the package across at full functional parity, adapted to the genesis `@suaveplan/api` plugin shape (`PluginDescriptor`, `ctx.getService("app")`, `ctx.getService("db")`) and genesis `DatabaseAdapter` positional-param surface, with the named-emitter wart replaced by the shared `ctx.eventBus` so other plugins can subscribe. The event-management plugin is distinct from `@suaveplan/events` (the typed event-bus, already shipped on genesis) and from `@suaveplan/api-plugin-event-bus` (the API-side adapter for the event-bus, also shipped). The name collision is unfortunate but the plugin's `defineCodes` namespace is `EVENTS_PLUGIN` (not `EVENTS`) precisely to avoid the registry clash with `@suaveplan/events`. ## What changes - ADDED: `createEventsPlugin(options?)` — factory returning a `PluginDescriptor` for `@suaveplan/api`. - ADDED: `EventsPluginOptionsSchema` / `EventsPluginConfig` / `EventsPluginConfigInput` — Zod-validated config (basePath, enableNotifications, maxInvitesPerRequest). - ADDED: `EventRepository` + `IEventRepository` — SurrealDB-backed event CRUD repository over the genesis `SurrealAdapter` service slot. - ADDED: `RsvpRepository` + `IRsvpRepository` — SurrealDB-backed RSVP / invitation / attendee-counting repository. - ADDED: `createEventRoutes` — Hono OpenAPI route module with 9 endpoints (list / get / create / update / delete / rsvp / cancel-rsvp / my-rsvp / attendees / invite). - ADDED: `EVENTS_PLUGIN_CODES` registry + `EventsPluginError` class — 12 stable codes spanning configuration, lifecycle, repository, and route paths. - ADDED: OTel instruments — 5 counters / histograms emitted from repository + route code (events created, events updated, events deleted, rsvps written, invitations sent). - ADDED: ServiceRegistry augmentation — `eventRepository` and `rsvpRepository` slots become available to downstream plugins via typed `ctx.getService(...)`. - ADDED: Local Zod schemas (`EventSchema`, `CreateEventSchema`, `UpdateEventSchema`, `EventQuerySchema`, `EventIdParamSchema`, `EventLocationSchema`, `EventStatusSchema`, `AttendeeCountSchema`, `EventListResponseSchema`, `RsvpSchema`, `RsvpStatusSchema`, `CreateRsvpSchema`, `RsvpResponseSchema`, `AttendeeSchema`, `AttendeeQuerySchema`, `AttendeeListResponseSchema`, `InviteUsersSchema`, `InviteResponseSchema`) — `@suaveplan/api-schemas` on genesis only ships event-bus envelope schemas, not event-entity schemas, so the plugin owns its boundary schemas. - ADDED: `surql/tables/event.surql` migration — `event` and `event_rsvp` SCHEMAFULL tables plus the two indexes the repositories rely on (`event_start_idx`, `event_rsvp_unique_idx`). See `## Out of scope` for the deferred upstream permission rules / helper functions / `event_update` table. - ADDED: `EventsPluginEvents` event-map keys — `events:initialized`, `events:ready`, `event:created`, `event:updated`, `event:cancelled`, `rsvp:created`, `rsvp:cancelled`, `event:invitations:sent`. ## Out of scope Items from `enterprise/api-plugin-events.md` "Required enterprise-quality improvements" and "Feature-rich enhancements" are deferred to follow-up changes because they each require new cross-package dependencies that don't exist yet on genesis (or are migrated under their own change): - ICS / iCalendar export — requires a `@suaveplan/templates` ICS builder or a hand-rolled module; deferred to `add-api-plugin-events-ics-export`. - Recurring events (RRULE / RFC-5545) — requires `@suaveplan/datetime` RRULE expansion which is not yet wired; deferred to `add-api-plugin-events-recurrence`. - Notification fan-out via `@suaveplan/notification-channel` — `enableNotifications` is wired through the plugin config and accepted at construction, but the actual channel binding lands when `@suaveplan/notification-channel` migrates (see STATUS.md). The config flag is non-load-bearing in this change but reserved by Zod so consumers configuring it today won't fail validation later. - Rate-limiter on `/events/:id/invite` — depends on `@suaveplan/api-plugin-rate-limit` which is migrating concurrently; will be composed at the host level once both land. - Audit-log on create / cancel / RSVP — depends on `@suaveplan/api-plugin-audit` (concurrent migration). - Auth gating via `@suaveplan/api-plugin-auth` — this plugin reads `c.get("user")` (set by the host's auth middleware) and raises `UNAUTHENTICATED` when missing; explicit `requireAuth()` composition is a host-level concern. - Calendar sync (Google / Outlook / iCloud), waitlist / capacity, invite tokens, reminders, geo, check-in QR, ticketing, SSE live updates — each is a feature-rich enhancement called out in the upstream enterprise audit, none of which are present in the upstream code itself; they belong in follow-up changes that don't ride on this parity migration. - Upstream `surql/tables/event.surql` ships row-level `PERMISSIONS` rules and stored helper functions (`fn::event::can_view`, `fn::event::can_moderate`, `fn::event::create`, `fn::event::update_rsvp`, `fn::event::invite`, `fn::event::post_update`, `fn::event::get_upcoming`) plus an `event_update` table — all of which transitively depend on `fn::bitwise::*`, `fn::notification::*`, `fn::group::*`, and `record` / `record` references from sibling plugins (`api-plugin-site`, `api-plugin-notifications`, `api-plugin-groups`, `api-plugin-bitwise`). None of those plugins exist on genesis yet, and the upstream test harness papers over the gap with a `loadCrossPluginStubs` helper that is itself absent from genesis's `@suaveplan/surrealdb`. To avoid hand-rolling four downstream plugins as a side-quest, the migration ships a slimmer `surql/tables/event.surql` covering only the `event` and `event_rsvp` tables that the repositories actually read / write; permissions enforcement moves to the route layer (auth guard + creator check) and the `event_update` table + helper functions migrate when their dependent plugins land. ## Source mapping This change derives from `../suaveplan/enterprise/api-plugin-events.md` and the upstream source at `../suaveplan/packages/server/api-plugin-events/`. | Requirement | Source lines | Notes | |---|---|---| | Plugin factory `createEventsPlugin(config?)` | enterprise.md L18-L23; src/index.ts L35-L101 | reworded — genesis uses `PluginDescriptor` not `Plugin` | | Config schema (basePath, enableNotifications, maxInvitesPerRequest) | enterprise.md L12; src/index.ts L23-L29 | verbatim | | Plugin dependencies: `["database"]` | enterprise.md L16; src/index.ts L46 | verbatim | | `migrationsPath: "./surql"` | enterprise.md L16; src/index.ts L58 | verbatim — surql payload migrated unchanged | | EventRepository: findAll/findById/create/update/delete/isCreator/updateAttendeeCount | src/repositories/event.repository/index.ts | verbatim — semantics preserved, query helper rewritten for genesis adapter | | RsvpRepository: findByEventAndUser/findAttendees/upsert/delete/createInvitations/countByStatus | src/repositories/rsvp.repository/index.ts | verbatim | | createEventRoutes (9 endpoints) | src/routes/events/index.ts | verbatim — `ctx.eventBus` replaces local `EventEmitter` | | `event` / `event_rsvp` / `event_update` tables + indexes | surql/tables/event.surql | verbatim | | Public re-export of repositories + routes + types | src/index.ts L103-L106 | reworded — explicit named exports per genesis rule 10 | ### Requirements with no source line | Requirement | Justification | |---|---| | 100% test / branch / function / line coverage | Genesis-wide policy (CLAUDE.md "Quality", AGENTS.md §7). | | `defineCodes("EVENTS_PLUGIN", [...])` namespace | Genesis convention; `EVENTS` collides with the `@suaveplan/events` event-bus registry. | | OTel instruments via `metrics.getMeter("@suaveplan/api-plugin-events")` | Genesis-wide observability policy; emitted from production paths and asserted with a real in-memory `MeterProvider` (AGENTS.md §13). | | Use `ctx.eventBus` from `@suaveplan/api-plugin` context, not a local `EventEmitter` | Fixes the smell flagged in `enterprise/api-plugin-events.md` "Notable smells" L41 (local emitter not visible to other plugins). | | Subpath exports per feature module | AGENTS.md §6 — required so consumers can tree-shake. | | Schemas owned locally | `@suaveplan/api-schemas` on genesis ships only event-bus envelope schemas, not event-entity schemas (verified by inspection: `packages/api/api-schemas/src/events/` contains `domain-event.ts`, `envelope.ts`, `metadata.ts` only). Migrating the entity schemas into `@suaveplan/api-schemas` is out of scope for a plugin migration. | | Positional `$p0`-style params instead of named `$title` | Genesis `DatabaseAdapter.query(sql, params: readonly unknown[])` wraps params as `{p0: ..., p1: ...}` via `paramsToRecord`. Upstream uses named placeholders that don't survive the canonical surface (Path-A escape valve documented in CLAUDE.md). | ## Verification approach - `src/repositories/event.repository/event.repository.test.ts` — covers EventRepository requirements (CRUD, normalisation, isCreator branches, attendee count). - `src/repositories/rsvp.repository/rsvp.repository.test.ts` — covers RsvpRepository requirements (find / upsert / delete / invitations / countByStatus). - `src/routes/events-routes/events-routes.test.ts` — covers route surface, status codes, auth guard, event emission. - `src/plugin/plugin.test.ts` — covers plugin lifecycle (onInit / onReady / onClose), service registration, route mounting. - `src/error-codes/error-codes.test.ts` — pins every code is reachable from a real call site. - `src/otel/instruments.test.ts` — real `MeterProvider`, asserts instrument shape and emission. - `src/schemas/event-schemas.test.ts` + `src/schemas/rsvp-schemas.test.ts` + `src/schemas/options.test.ts` — Zod boundary tests. - `surql/surql-validation.test.ts` — discovers and loads the surql migration against a real SurrealDB testcontainer (`isSurrealDBAvailable` skip-aware). - `tests/integration/full-boot.test.ts` — full plugin boot + CRUD roundtrip against a real SurrealDB container. - `src/api-plugin-events.spec-conformance.test.ts` — pins every public symbol re-exported by the package root. ## Migration impact None — additive only. This is a brand-new package on genesis. Consumers will opt-in via `createEventsPlugin()` once they need event-management routes. ## Impact ### Affected dependents None on genesis today — package is additive. Downstream apps that previously had no event-management capability gain access.