Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# 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<EventsPluginConfig>` 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<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 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<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` — 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.