# instruments _(no submodules)_ ## Purpose Bundle of OpenTelemetry instruments emitted by `@suaveplan/macos-contacts`. The accessor `getMacOSContactsInstruments()` lazily binds against the current global `MeterProvider`, caches the bundle, and returns the same object on every subsequent call. Tests call `resetMacOSContactsInstruments()` after installing a fresh `MeterProvider` to force a rebind; production callers never need to touch the reset path. ## Features - Six counters partitioned by `outcome` (`success` | `permission_denied` | `access_failed`): - `macos_contacts.list_total` — `MacOSContactsBridge.getAllContacts` - `macos_contacts.search_total` — `searchContacts` - `macos_contacts.get_total` — `getContactById` - `macos_contacts.groups_list_total` — `getGroups` - `macos_contacts.group_members_total` — `getGroupMembers` - `macos_contacts.sync_total` — `sync` - Two histograms (unit: ms): - `macos_contacts.applescript_duration_ms` — wraps every runner invocation - `macos_contacts.sync_duration_ms` — wraps `sync` end-to-end - Lazy + cached — the bundle is built on first access against the global `MeterProvider`. Subsequent calls return the same reference (`===`-equal). ## Usage: Basic ```ts import { getMacOSContactsInstruments, } from "@suaveplan/macos-contacts"; const instruments = getMacOSContactsInstruments(); instruments.listTotal.add(1, { outcome: "success" }); ``` ## Usage: Advanced ```ts import { metrics } from "@opentelemetry/api"; import { InMemoryMetricExporter, MeterProvider, PeriodicExportingMetricReader, } from "@opentelemetry/sdk-metrics"; import { getMacOSContactsInstruments, resetMacOSContactsInstruments, } from "@suaveplan/macos-contacts"; // Wire a real in-memory MeterProvider for tests. const exporter = new InMemoryMetricExporter(0); const reader = new PeriodicExportingMetricReader({ exporter, exportIntervalMillis: 60_000, }); const provider = new MeterProvider({ readers: [reader] }); metrics.disable(); metrics.setGlobalMeterProvider(provider); // Force the bundle to rebind against the new provider. resetMacOSContactsInstruments(); const instruments = getMacOSContactsInstruments(); ``` ## API Reference ### `MacOSContactsInstruments` ```ts interface MacOSContactsInstruments { readonly listTotal: Counter; readonly searchTotal: Counter; readonly getTotal: Counter; readonly groupsListTotal: Counter; readonly groupMembersTotal: Counter; readonly syncTotal: Counter; readonly applescriptDurationMs: Histogram; readonly syncDurationMs: Histogram; } ``` ### `getMacOSContactsInstruments(): MacOSContactsInstruments` Lazily builds and caches the bundle. Returns the same object reference on every subsequent call until `resetMacOSContactsInstruments()` is called. ### `resetMacOSContactsInstruments(): void` Drops the cached bundle. The next `getMacOSContactsInstruments()` call rebuilds it against whatever global `MeterProvider` is in effect. ## Implementation Notes The bundle is built against the global `MeterProvider` rather than a parameter so consumers do not need to thread a meter through every layer. Tests reset the cache after installing a new provider so each test sees a fresh instrument set bound to its own in-memory exporter. Counters partition by `outcome` so dashboards can distinguish revoked-permission incidents from transient `osascript` failures. Histograms record duration in milliseconds (`unit: "ms"`); the bridge wraps the runner call so the sample is recorded even when the runner rejects — observability survives platform-level failures. Instrument names use `snake_case` per OpenTelemetry semantic conventions. The `macos_contacts.` prefix mirrors the package name so multi-package dashboards can filter by namespace.