Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
export type {
RateLimitCounterName,
RateLimitInstruments,
RateLimitInstrumentsConfig,
} from "./instruments.js";
export {
createRateLimitInstruments,
RATE_LIMIT_COUNTER_NAMES,
} from "./instruments.js";

View File

@@ -0,0 +1,73 @@
# instruments
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Owns the rate-limit OTel instrument registry. Today the package emits
exactly one counter — `api.rate_limit.exceeded_total{plugin, route,
key_kind}` — which the design doc §12.4 lists as the canonical
rate-limit denial signal. The factory wraps the OTel global
meter-provider lookup so production gets real export when an SDK is
installed and tests get a no-op when one is not.
## Features
- `RATE_LIMIT_COUNTER_NAMES` frozen tuple — drives both factory
construction AND the conformance test that asserts each name is
present on the returned bundle.
- `createRateLimitInstruments({ meterProvider? })` — explicit override
precedence beats the global, so the middleware tests can assert
exact `.add` call counts against an injected fake provider.
- Returned object is `Object.freeze`-d so accidental mutation surfaces
immediately rather than silently breaking downstream label
invariants.
## Usage: Basic
```ts
import { createRateLimitInstruments } from "@suaveplan/api-rate-limit/instruments";
const instruments = createRateLimitInstruments();
instruments.counters["api.rate_limit.exceeded_total"].add(1, {
plugin: "billing",
route: "POST /invoices",
key_kind: "principal",
});
```
## Usage: Advanced
Wire a captured meter-provider to assert exactly-once denial
increments under a deterministic clock:
```ts
import type { MeterProvider } from "@opentelemetry/api";
import { createRateLimitInstruments } from "@suaveplan/api-rate-limit/instruments";
declare const fakeProvider: MeterProvider; // captures every .add() call
const instruments = createRateLimitInstruments({ meterProvider: fakeProvider });
// drive the middleware against the fake clock, then assert
// fakeProvider's recorded .add() invocations match the denial transitions
```
## API Reference
- `const RATE_LIMIT_COUNTER_NAMES: readonly ["api.rate_limit.exceeded_total"]`
- `type RateLimitCounterName = (typeof RATE_LIMIT_COUNTER_NAMES)[number]`
- `interface RateLimitInstruments { counters: Readonly<Record<RateLimitCounterName, Counter>> }`
- `interface RateLimitInstrumentsConfig { meterProvider?: MeterProvider }`
- `function createRateLimitInstruments(config?: RateLimitInstrumentsConfig): RateLimitInstruments`
## Implementation Notes
The package deliberately does NOT install an SDK — that's an operator
concern. When no SDK is registered, the OTel no-op meter-provider
returns no-op counters whose `.add()` is a function-call no-op, so
this module is hot-path safe even with default config. The host's
own `HostInstruments` already declares the same counter name; both
emit, both reach the same exporter — OTel aggregates correctly because
the meter name is the only difference and the SDK merges series by
name + label-set.

View File

@@ -0,0 +1,99 @@
import type { Meter, MeterProvider } from "@opentelemetry/api";
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
createRateLimitInstruments,
RATE_LIMIT_COUNTER_NAMES,
} from "./instruments.js";
interface RecordedCounter {
readonly name: string;
readonly addCalls: {
value: number;
attributes?: Readonly<Record<string, unknown>>;
}[];
}
function fakeProvider(): {
provider: MeterProvider;
counters: RecordedCounter[];
} {
const counters: RecordedCounter[] = [];
const provider: MeterProvider = {
getMeter(_name: string): Meter {
const meter: Meter = {
createCounter(n: string) {
const r: RecordedCounter = { name: n, addCalls: [] };
counters.push(r);
return {
add(
v: number,
attrs?: Readonly<Record<string, unknown>>
) {
r.addCalls.push({ value: v, attributes: attrs });
},
} as ReturnType<Meter["createCounter"]>;
},
createUpDownCounter() {
throw new Error("not used");
},
createHistogram() {
throw new Error("not used");
},
createObservableCounter() {
throw new Error("not used");
},
createObservableGauge() {
throw new Error("not used");
},
createObservableUpDownCounter() {
throw new Error("not used");
},
addBatchObservableCallback() {
/* noop */
},
removeBatchObservableCallback() {
/* noop */
},
} as Meter;
return meter;
},
};
return { provider, counters };
}
describe("instruments", () => {
it("creates the rate-limit exceeded counter", { timeout: 5000 }, () => {
const { provider, counters } = fakeProvider();
const inst = createRateLimitInstruments({ meterProvider: provider });
expect(counters.map((c) => c.name)).toEqual([
"api.rate_limit.exceeded_total",
]);
inst.counters["api.rate_limit.exceeded_total"].add(1, {
plugin: "p",
route: "GET /",
key_kind: "principal",
});
expect(counters[0]!.addCalls).toHaveLength(1);
expect(counters[0]!.addCalls[0]!.attributes).toEqual({
plugin: "p",
route: "GET /",
key_kind: "principal",
});
});
it("falls back to global meter-provider when none supplied", {
timeout: 5000,
}, () => {
const inst = createRateLimitInstruments();
// Global no-op provider returns no-op counters; .add must not throw.
expect(() =>
inst.counters["api.rate_limit.exceeded_total"].add(1, {})
).not.toThrow();
});
it("exposes the full counter-name tuple", { timeout: 5000 }, () => {
expect(RATE_LIMIT_COUNTER_NAMES).toEqual([
"api.rate_limit.exceeded_total",
]);
});
});

View File

@@ -0,0 +1,44 @@
import type { MeterProvider, Counter as OtelCounter } from "@opentelemetry/api";
import { metrics } from "@opentelemetry/api";
/**
* Counter names emitted by this package on production paths. Mirrors
* the design-doc §12.4 entry exactly so the host's metrics inventory
* stays a single source of truth.
*/
export const RATE_LIMIT_COUNTER_NAMES = [
"api.rate_limit.exceeded_total",
] as const;
export type RateLimitCounterName = (typeof RATE_LIMIT_COUNTER_NAMES)[number];
/** Frozen instrument bundle the middleware records denials against. */
export interface RateLimitInstruments {
readonly counters: Readonly<Record<RateLimitCounterName, OtelCounter>>;
}
/** Configuration for {@link createRateLimitInstruments}. */
export interface RateLimitInstrumentsConfig {
/** Optional OTel meter-provider override; defaults to the global one. */
readonly meterProvider?: MeterProvider;
}
/**
* Build the rate-limit instrument registry. The meter is named
* `@suaveplan/api-rate-limit` so exported metrics are unambiguous in
* dashboards. When no SDK is installed the OTel no-op provider
* silently swallows the `.add()` calls.
*/
export function createRateLimitInstruments(
config: RateLimitInstrumentsConfig = {}
): RateLimitInstruments {
const provider = config.meterProvider ?? metrics.getMeterProvider();
const meter = provider.getMeter("@suaveplan/api-rate-limit");
const counters = Object.create(null) as Record<
RateLimitCounterName,
OtelCounter
>;
for (const name of RATE_LIMIT_COUNTER_NAMES) {
counters[name] = meter.createCounter(name);
}
return Object.freeze({ counters: Object.freeze(counters) });
}