# instruments _(no submodules)_ ## Purpose Bundles the seven OpenTelemetry counters emitted by `@suaveplan/api-plugin-events`. Every counter is lazily built on the first `getEventsInstruments()` call and cached against the global `MeterProvider` in effect at that point. Tests that install their own in-memory `MeterProvider` call `resetEventsInstruments()` before re-resolving the bundle so the counters bind to the freshly-installed provider. The seven instruments form the canonical observability surface for the plugin: they count successful event lifecycle events (create / update / delete), RSVP writes partitioned by status, invitations sent, ICS exports served partitioned by `with` variant, and ICS export failures partitioned by reason. Histograms (e.g. route duration) are intentionally not part of the 0.1 surface; the canonical histogram for HTTP routes lives in the host `@suaveplan/api` middleware layer. ## Features - Seven `Counter`s emitted from the route handlers: `events.created_total`, `events.updated_total`, `events.deleted_total`, `events.rsvps_written_total{status}`, `events.invitations_sent_total`, `events.ics.served_total{with}`, `events.ics.failed_total{reason}`. - Lazily built and cached — no instrument is constructed before the first `getEventsInstruments()` call. - `resetEventsInstruments()` clears the cache so tests can re-bind against a fresh `MeterProvider`. - Stable name shape (`events.*_total`) for OTel collector scraping. ## Usage: Basic Example ```ts import { getEventsInstruments } from "@suaveplan/api-plugin-events"; const inst = getEventsInstruments(); inst.eventsCreatedTotal.add(1); inst.rsvpsWrittenTotal.add(1, { status: "going" }); ``` ## Usage: Advanced Example ```ts import { metrics } from "@opentelemetry/api"; import { MeterProvider, PeriodicExportingMetricReader, InMemoryMetricExporter, AggregationTemporality, } from "@opentelemetry/sdk-metrics"; import { getEventsInstruments, resetEventsInstruments, } from "@suaveplan/api-plugin-events"; // Install a fresh in-memory exporter for assertions const exporter = new InMemoryMetricExporter( AggregationTemporality.CUMULATIVE ); const provider = new MeterProvider({ readers: [new PeriodicExportingMetricReader({ exporter })], }); metrics.setGlobalMeterProvider(provider); resetEventsInstruments(); // Drive a counter and flush getEventsInstruments().invitationsSentTotal.add(3); await provider.forceFlush(); const snapshot = exporter.getMetrics(); // snapshot now contains `events.invitations_sent_total` with value 3 ``` ## API Reference ### `interface EventsInstruments` - `eventsCreatedTotal: Counter` — incremented on every successful `POST /`. - `eventsUpdatedTotal: Counter` — incremented on every successful `PUT /:id`. - `eventsDeletedTotal: Counter` — incremented on every successful `DELETE /:id`. - `rsvpsWrittenTotal: Counter` — incremented on every successful `POST /:id/rsvp`, labelled by the new status (`going` / `maybe` / `invited` / `not_going`). - `invitationsSentTotal: Counter` — incremented by the count of newly-created invitations on every successful `POST /:id/invite`. - `icsServedTotal: Counter` — incremented on every successful `GET /:id/ics` response. Labelled by `with` (`"none"` or `"rsvps"`). - `icsFailedTotal: Counter` — incremented on every error response served by the ICS route. Labelled by `reason` (`"not_found"`, `"forbidden"`, `"unauthenticated"`, or `"invalid_query"`). ### `getEventsInstruments(): EventsInstruments` Lazily build and cache the instrument bundle. Repeated calls return the same object reference. ### `resetEventsInstruments(): void` Drop the cached bundle. The next `getEventsInstruments()` call rebinds against whatever `MeterProvider` is in effect. ## Implementation Notes The bundle uses singleton caching because OTel `Counter` objects are themselves bound to the meter that created them — calling `meter.createCounter("events.created_total")` twice does not produce a new metric, but it does produce a new JavaScript object that is slightly more expensive than the cached reference. The lazy construction also lets the polyfill in `tests/setup/polyfills.ts` install a `MeterProvider` before any source module that touches metrics imports is loaded, which is necessary because OTel binds the meter to the active provider at counter-creation time.