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,90 @@
# `otel/instruments`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `otel/instruments` module owns the OpenTelemetry instrument bundle
emitted by `@suaveplan/api-plugin-campaigns`. It binds a meter under the
`"@suaveplan/api-plugin-campaigns"` instrumentation-scope name against the
process-global `MeterProvider` and exposes a lazily-cached factory so the
plugin's route handlers, repositories, and lifecycle hooks share a single
counter / histogram pair without re-resolving the meter on every call.
The bundle is intentionally small in Chunk A: a request-count counter and a
request-duration histogram covering every route mounted by the plugin.
Chunks B and C extend it (in their own modules) with per-domain instruments
for campaign writes, suppression checks, and webhook ingestion outcomes.
## Features
- Lazy, cached meter binding — first call resolves the global meter; later
calls return the cached bundle so handlers can record metrics in their
hot path without provider lookup overhead.
- `resetCampaignsApiInstruments()` hook for tests that install a fresh
`MeterProvider` after the first cache write.
- Counter + histogram primitives only; aggregation strategy and views are
owned by the host's `MeterProvider`, not this package.
## Usage: Basic
```ts
import { getCampaignsApiInstruments } from "@suaveplan/api-plugin-campaigns";
const { requestsTotal, requestDurationMs } = getCampaignsApiInstruments();
const start = performance.now();
try {
await handler();
requestsTotal.add(1, { route: "/campaigns", outcome: "ok" });
} catch (err) {
requestsTotal.add(1, { route: "/campaigns", outcome: "error" });
throw err;
} finally {
requestDurationMs.record(performance.now() - start, {
route: "/campaigns",
});
}
```
## Usage: Advanced
```ts
import { metrics } from "@opentelemetry/api";
import { MeterProvider } from "@opentelemetry/sdk-metrics";
import {
getCampaignsApiInstruments,
resetCampaignsApiInstruments,
} from "@suaveplan/api-plugin-campaigns";
// Test set-up: rebind to a freshly-installed provider.
const provider = new MeterProvider({ readers: [] });
metrics.setGlobalMeterProvider(provider);
resetCampaignsApiInstruments();
// The next resolve binds to `provider`, not the implicit global from
// before the rebind.
const bundle = getCampaignsApiInstruments();
bundle.requestsTotal.add(1, { route: "/x", outcome: "ok" });
```
## API Reference
- `interface CampaignsApiInstruments { readonly requestsTotal: Counter; readonly requestDurationMs: Histogram }`
- `getCampaignsApiInstruments(): CampaignsApiInstruments` — cached factory.
- `resetCampaignsApiInstruments(): void` — drops the cache.
## Implementation Notes
The cache is a module-level mutable binding. That is intentional and
matches the upstream pattern in `@suaveplan/api-plugin-contacts`: an
inert no-op metric is cheap to recreate, but creating one per request is
not, so caching plus an explicit reset hook for tests is the simplest
honest design.
The bundle is `readonly` so handlers cannot accidentally mutate it; the
cache itself is mutable but only the reset hook reaches it. Reset is
test-only — production code never invalidates the cache because the
global provider is installed once at process start.