11 KiB
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 aPluginDescriptor<EventsPluginConfig>for@suaveplan/api. - ADDED:
EventsPluginOptionsSchema/EventsPluginConfig/EventsPluginConfigInput— Zod-validated config (basePath, enableNotifications, maxInvitesPerRequest). - ADDED:
EventRepository+IEventRepository— SurrealDB-backed event CRUD repository over the genesisSurrealAdapterservice 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_CODESregistry +EventsPluginErrorclass — 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 —
eventRepositoryandrsvpRepositoryslots become available to downstream plugins via typedctx.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-schemason genesis only ships event-bus envelope schemas, not event-entity schemas, so the plugin owns its boundary schemas. - ADDED:
surql/tables/event.surqlmigration —eventandevent_rsvpSCHEMAFULL tables plus the two indexes the repositories rely on (event_start_idx,event_rsvp_unique_idx). See## Out of scopefor the deferred upstream permission rules / helper functions /event_updatetable. - ADDED:
EventsPluginEventsevent-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/templatesICS builder or a hand-rolled module; deferred toadd-api-plugin-events-ics-export. - Recurring events (RRULE / RFC-5545) — requires
@suaveplan/datetimeRRULE expansion which is not yet wired; deferred toadd-api-plugin-events-recurrence. - Notification fan-out via
@suaveplan/notification-channel—enableNotificationsis wired through the plugin config and accepted at construction, but the actual channel binding lands when@suaveplan/notification-channelmigrates (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-limitwhich 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 readsc.get("user")(set by the host's auth middleware) and raisesUNAUTHENTICATEDwhen missing; explicitrequireAuth()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.surqlships row-levelPERMISSIONSrules 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 anevent_updatetable — all of which transitively depend onfn::bitwise::*,fn::notification::*,fn::group::*, andrecord<site>/record<group>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 aloadCrossPluginStubshelper that is itself absent from genesis's@suaveplan/surrealdb. To avoid hand-rolling four downstream plugins as a side-quest, the migration ships a slimmersurql/tables/event.surqlcovering only theeventandevent_rsvptables that the repositories actually read / write; permissions enforcement moves to the route layer (auth guard + creator check) and theevent_updatetable + 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<EventsConfig> |
| 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— realMeterProvider, 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 (isSurrealDBAvailableskip-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.