From e11fd7c8972491b1292611bef59d6be4b98c68cb Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Mon, 11 May 2026 11:57:56 +0200 Subject: [PATCH] feat(core-shared): IMetrics interface + NoopMetrics impl --- .../src/instrumentation/metrics.interface.ts | 36 +++++++++++++++++++ .../src/instrumentation/noop-metrics.test.ts | 29 +++++++++++++++ .../src/instrumentation/noop-metrics.ts | 21 +++++++++++ 3 files changed, 86 insertions(+) create mode 100644 packages/core-shared/src/instrumentation/metrics.interface.ts create mode 100644 packages/core-shared/src/instrumentation/noop-metrics.test.ts create mode 100644 packages/core-shared/src/instrumentation/noop-metrics.ts diff --git a/packages/core-shared/src/instrumentation/metrics.interface.ts b/packages/core-shared/src/instrumentation/metrics.interface.ts new file mode 100644 index 0000000..c21c250 --- /dev/null +++ b/packages/core-shared/src/instrumentation/metrics.interface.ts @@ -0,0 +1,36 @@ +export type MetricAttributeValue = string | number | boolean; + +/** + * Vendor-neutral metrics signal interface. Mirrors the pattern of ITracer / ILogger. + * Three impls: NoopMetrics (noop), OtelMetrics (OTel API), RecordingMetrics (core-testing). + * + * gauge() limitation: uses UpDownCounter under the hood, which accumulates deltas. + * True "set to absolute value" semantics require ObservableGauge with a callback — + * deferred to a v2 interface when the first true-gauge use case lands. + */ +export interface IMetrics { + /** Monotonic counter. Use for event counts (signups, errors, requests). */ + counter( + name: string, + value?: number, + attributes?: Record, + ): void; + + /** Distribution. Use for measured quantities (latency, payload size). */ + histogram( + name: string, + value: number, + attributes?: Record, + ): void; + + /** + * Point-in-time value. UpDownCounter under the hood — true gauge semantics + * (set to absolute value) require ObservableGauge with an async callback; + * that is deferred to a future v2 spec when the first true-gauge use case arrives. + */ + gauge( + name: string, + value: number, + attributes?: Record, + ): void; +} diff --git a/packages/core-shared/src/instrumentation/noop-metrics.test.ts b/packages/core-shared/src/instrumentation/noop-metrics.test.ts new file mode 100644 index 0000000..5777dc1 --- /dev/null +++ b/packages/core-shared/src/instrumentation/noop-metrics.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from "vitest"; +import { NoopMetrics } from "./noop-metrics"; + +describe("NoopMetrics", () => { + it("counter() returns undefined without throwing", () => { + const metrics = new NoopMetrics(); + expect(() => metrics.counter("my.counter")).not.toThrow(); + expect(() => metrics.counter("my.counter", 5)).not.toThrow(); + expect(() => + metrics.counter("my.counter", 1, { feature: "auth", success: true }), + ).not.toThrow(); + }); + + it("histogram() returns undefined without throwing", () => { + const metrics = new NoopMetrics(); + expect(() => metrics.histogram("my.latency", 42)).not.toThrow(); + expect(() => + metrics.histogram("my.latency", 100, { route: "/api/me" }), + ).not.toThrow(); + }); + + it("gauge() returns undefined without throwing", () => { + const metrics = new NoopMetrics(); + expect(() => metrics.gauge("queue.depth", 7)).not.toThrow(); + expect(() => + metrics.gauge("queue.depth", 3, { queue: "emails" }), + ).not.toThrow(); + }); +}); diff --git a/packages/core-shared/src/instrumentation/noop-metrics.ts b/packages/core-shared/src/instrumentation/noop-metrics.ts new file mode 100644 index 0000000..10ff514 --- /dev/null +++ b/packages/core-shared/src/instrumentation/noop-metrics.ts @@ -0,0 +1,21 @@ +import type { IMetrics, MetricAttributeValue } from "./metrics.interface"; + +export class NoopMetrics implements IMetrics { + counter( + _name: string, + _value?: number, + _attributes?: Record, + ): void {} + + histogram( + _name: string, + _value: number, + _attributes?: Record, + ): void {} + + gauge( + _name: string, + _value: number, + _attributes?: Record, + ): void {} +}