first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
# instruments
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.