@ayepi/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/retry.cjs ADDED
@@ -0,0 +1,135 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/retry.ts
3
+ /**
4
+ * Throw this from a {@link retry} operation to **stop retrying immediately**: the remaining
5
+ * attempts (and their backoff) are skipped, `onError` fires once, and `retry` then re-throws the
6
+ * abort's `cause` (or returns `errorResult` if one was configured). Use it for a permanent failure
7
+ * a retry can't fix — a 4xx, a validation error, a missing resource.
8
+ *
9
+ * ```ts
10
+ * await retry(async () => {
11
+ * const res = await fetch(url);
12
+ * if (res.status === 404) throw new RetryAbort(new Error('not found')); // don't retry a 404
13
+ * if (!res.ok) throw new Error(`http ${res.status}`); // transient → retried
14
+ * return res.json();
15
+ * });
16
+ * ```
17
+ */
18
+ var RetryAbort = class extends Error {
19
+ constructor(cause, message = "retry aborted") {
20
+ super(message, { cause });
21
+ this.name = "RetryAbort";
22
+ }
23
+ };
24
+ /** The default {@link RetryOptions.on}: a {@link RetryAbort} stops the loop, anything else retries with the normal backoff. */
25
+ const defaultOn = (err) => err instanceof RetryAbort ? false : 0;
26
+ /** The built-in defaults — the floor under {@link setDefaultRetryOptions} and per-call options. */
27
+ const DEFAULT_RETRY_OPTIONS = {
28
+ attempts: 3,
29
+ base: 1e3,
30
+ factor: 2,
31
+ max: 3e4,
32
+ jitter: .5
33
+ };
34
+ /** Process-wide overrides, applied between {@link DEFAULT_RETRY_OPTIONS} and per-call options. */
35
+ let globalDefaults = {};
36
+ /** Set process-wide default {@link RetryOptions} (merged over previous overrides). Per-call options still win. */
37
+ function setDefaultRetryOptions(options) {
38
+ globalDefaults = {
39
+ ...globalDefaults,
40
+ ...options
41
+ };
42
+ }
43
+ /** The effective global defaults ({@link DEFAULT_RETRY_OPTIONS} plus any {@link setDefaultRetryOptions} overrides). */
44
+ function getDefaultRetryOptions() {
45
+ return {
46
+ ...DEFAULT_RETRY_OPTIONS,
47
+ ...globalDefaults
48
+ };
49
+ }
50
+ const defaultSleep = (ms) => new Promise((resolve) => {
51
+ setTimeout(resolve, ms).unref?.();
52
+ });
53
+ /**
54
+ * Backoff delay for retry `attempt` (1 = the first retry):
55
+ * `min(base · factor^(attempt-1), max) · (1 − jitter · random())`.
56
+ */
57
+ function backoff(attempt, opts = {}, random = Math.random) {
58
+ const base = opts.base ?? DEFAULT_RETRY_OPTIONS.base;
59
+ const factor = opts.factor ?? DEFAULT_RETRY_OPTIONS.factor;
60
+ const max = opts.max ?? DEFAULT_RETRY_OPTIONS.max;
61
+ const jitter = opts.jitter ?? DEFAULT_RETRY_OPTIONS.jitter;
62
+ const raw = base * Math.pow(factor, Math.max(0, attempt - 1));
63
+ return Math.round(Math.min(raw, max) * (1 - jitter * random()));
64
+ }
65
+ /**
66
+ * Run `fn`, retrying on rejection with exponential backoff + jitter up to `attempts`
67
+ * times. Resolves with the first success; on exhaustion it returns `errorResult` if one
68
+ * was provided, otherwise re-throws the last error.
69
+ */
70
+ async function retry(fn, options = {}) {
71
+ const o = {
72
+ ...DEFAULT_RETRY_OPTIONS,
73
+ ...globalDefaults,
74
+ ...options
75
+ };
76
+ const now = o.now ?? Date.now;
77
+ const random = o.random ?? Math.random;
78
+ const sleep = o.sleep ?? defaultSleep;
79
+ const decide = o.on ?? defaultOn;
80
+ const hasErrorResult = "errorResult" in options;
81
+ const startAt = now();
82
+ let lastError;
83
+ for (let attempt = 1; attempt <= o.attempts; attempt++) {
84
+ const lastAttemptAt = now();
85
+ const state = {
86
+ startAt,
87
+ attempt,
88
+ attempts: o.attempts,
89
+ lastAttemptAt,
90
+ lastError
91
+ };
92
+ try {
93
+ const result = await fn(state);
94
+ o.onSuccess?.(result, state);
95
+ return result;
96
+ } catch (err) {
97
+ const decision = await decide(err);
98
+ if (decision === false) {
99
+ const cause = err instanceof RetryAbort ? err.cause ?? err : err;
100
+ lastError = cause;
101
+ o.onError?.(cause, {
102
+ startAt,
103
+ attempt,
104
+ attempts: o.attempts,
105
+ lastAttemptAt,
106
+ lastError: cause
107
+ });
108
+ break;
109
+ }
110
+ lastError = err;
111
+ const failed = {
112
+ startAt,
113
+ attempt,
114
+ attempts: o.attempts,
115
+ lastAttemptAt,
116
+ lastError: err
117
+ };
118
+ if (attempt < o.attempts) {
119
+ o.onRetry?.(err, failed);
120
+ await sleep(Math.max(decision, backoff(attempt, o, random)));
121
+ continue;
122
+ }
123
+ o.onError?.(err, failed);
124
+ }
125
+ }
126
+ if (hasErrorResult) return options.errorResult;
127
+ throw lastError;
128
+ }
129
+ //#endregion
130
+ exports.DEFAULT_RETRY_OPTIONS = DEFAULT_RETRY_OPTIONS;
131
+ exports.RetryAbort = RetryAbort;
132
+ exports.backoff = backoff;
133
+ exports.getDefaultRetryOptions = getDefaultRetryOptions;
134
+ exports.retry = retry;
135
+ exports.setDefaultRetryOptions = setDefaultRetryOptions;
@@ -0,0 +1,90 @@
1
+ import { i as MaybePromise } from "./types.cjs";
2
+
3
+ //#region src/retry.d.ts
4
+
5
+ /**
6
+ * Throw this from a {@link retry} operation to **stop retrying immediately**: the remaining
7
+ * attempts (and their backoff) are skipped, `onError` fires once, and `retry` then re-throws the
8
+ * abort's `cause` (or returns `errorResult` if one was configured). Use it for a permanent failure
9
+ * a retry can't fix — a 4xx, a validation error, a missing resource.
10
+ *
11
+ * ```ts
12
+ * await retry(async () => {
13
+ * const res = await fetch(url);
14
+ * if (res.status === 404) throw new RetryAbort(new Error('not found')); // don't retry a 404
15
+ * if (!res.ok) throw new Error(`http ${res.status}`); // transient → retried
16
+ * return res.json();
17
+ * });
18
+ * ```
19
+ */
20
+ declare class RetryAbort extends Error {
21
+ constructor(cause?: unknown, message?: string);
22
+ }
23
+ /** Live state passed to the operation and to the {@link RetryOptions} hooks. */
24
+ interface RetryState {
25
+ /** When the first attempt started (epoch ms). */
26
+ readonly startAt: number;
27
+ /** Current attempt number (1-based). */
28
+ readonly attempt: number;
29
+ /** Total attempts allowed. */
30
+ readonly attempts: number;
31
+ /** When the current attempt started (epoch ms). */
32
+ readonly lastAttemptAt: number;
33
+ /** The most recent error (undefined before any failure). */
34
+ readonly lastError?: unknown;
35
+ }
36
+ /** Options for {@link retry}. Every numeric field has a default (see {@link DEFAULT_RETRY_OPTIONS}). */
37
+ interface RetryOptions<R = unknown> {
38
+ /** Total attempts including the first (default 3). */
39
+ attempts?: number;
40
+ /** First-retry delay in ms (default 1000). */
41
+ base?: number;
42
+ /** Multiplier applied per attempt (default 2). */
43
+ factor?: number;
44
+ /** Delay cap in ms (default 30000). */
45
+ max?: number;
46
+ /** Jitter fraction in `[0,1]`: each delay is scaled down by up to this much (default 0.5). */
47
+ jitter?: number;
48
+ /** If every attempt fails, resolve with this value instead of throwing. */
49
+ errorResult?: R;
50
+ /**
51
+ * Decide what to do with a thrown error: return `false` to **stop** retrying (abort), or a number
52
+ * of **milliseconds to wait at least** before the next attempt — a floor under the normal backoff
53
+ * (e.g. honor a `Retry-After`; `0` = just use the backoff). May be async. Default:
54
+ * `(err) => (err instanceof RetryAbort ? false : 0)` — overriding it **replaces** that check
55
+ * (e.g. `on: (e) => (e.status === 404 ? false : 0)` aborts on a 404 with no `RetryAbort` wrapper).
56
+ * To keep retrying through a `RetryAbort`, override it (e.g. `on: () => 0`).
57
+ */
58
+ on?: (err: unknown) => MaybePromise<number | false>;
59
+ /** Called after a successful attempt. */
60
+ onSuccess?: (result: R, state: RetryState) => void;
61
+ /** Called after a failed attempt that will be retried, before backing off. */
62
+ onRetry?: (error: unknown, state: RetryState) => void;
63
+ /** Called when all attempts are exhausted, before throwing or returning `errorResult`. */
64
+ onError?: (error: unknown, state: RetryState) => void;
65
+ /** Sleep implementation (ms) — injectable for tests (default an unref'd timer). */
66
+ sleep?: (ms: number) => Promise<void>;
67
+ /** Randomness for jitter (default `Math.random`). */
68
+ random?: () => number;
69
+ /** Clock (default `Date.now`). */
70
+ now?: () => number;
71
+ }
72
+ /** The built-in defaults — the floor under {@link setDefaultRetryOptions} and per-call options. */
73
+ declare const DEFAULT_RETRY_OPTIONS: Required<Pick<RetryOptions, 'attempts' | 'base' | 'factor' | 'max' | 'jitter'>>;
74
+ /** Set process-wide default {@link RetryOptions} (merged over previous overrides). Per-call options still win. */
75
+ declare function setDefaultRetryOptions(options: RetryOptions): void;
76
+ /** The effective global defaults ({@link DEFAULT_RETRY_OPTIONS} plus any {@link setDefaultRetryOptions} overrides). */
77
+ declare function getDefaultRetryOptions(): RetryOptions;
78
+ /**
79
+ * Backoff delay for retry `attempt` (1 = the first retry):
80
+ * `min(base · factor^(attempt-1), max) · (1 − jitter · random())`.
81
+ */
82
+ declare function backoff(attempt: number, opts?: Pick<RetryOptions, 'base' | 'factor' | 'max' | 'jitter'>, random?: () => number): number;
83
+ /**
84
+ * Run `fn`, retrying on rejection with exponential backoff + jitter up to `attempts`
85
+ * times. Resolves with the first success; on exhaustion it returns `errorResult` if one
86
+ * was provided, otherwise re-throws the last error.
87
+ */
88
+ declare function retry<R>(fn: (state: RetryState) => Promise<R>, options?: RetryOptions<R>): Promise<R>;
89
+ //#endregion
90
+ export { DEFAULT_RETRY_OPTIONS, RetryAbort, RetryOptions, RetryState, backoff, getDefaultRetryOptions, retry, setDefaultRetryOptions };
@@ -0,0 +1,90 @@
1
+ import { i as MaybePromise } from "./types.js";
2
+
3
+ //#region src/retry.d.ts
4
+
5
+ /**
6
+ * Throw this from a {@link retry} operation to **stop retrying immediately**: the remaining
7
+ * attempts (and their backoff) are skipped, `onError` fires once, and `retry` then re-throws the
8
+ * abort's `cause` (or returns `errorResult` if one was configured). Use it for a permanent failure
9
+ * a retry can't fix — a 4xx, a validation error, a missing resource.
10
+ *
11
+ * ```ts
12
+ * await retry(async () => {
13
+ * const res = await fetch(url);
14
+ * if (res.status === 404) throw new RetryAbort(new Error('not found')); // don't retry a 404
15
+ * if (!res.ok) throw new Error(`http ${res.status}`); // transient → retried
16
+ * return res.json();
17
+ * });
18
+ * ```
19
+ */
20
+ declare class RetryAbort extends Error {
21
+ constructor(cause?: unknown, message?: string);
22
+ }
23
+ /** Live state passed to the operation and to the {@link RetryOptions} hooks. */
24
+ interface RetryState {
25
+ /** When the first attempt started (epoch ms). */
26
+ readonly startAt: number;
27
+ /** Current attempt number (1-based). */
28
+ readonly attempt: number;
29
+ /** Total attempts allowed. */
30
+ readonly attempts: number;
31
+ /** When the current attempt started (epoch ms). */
32
+ readonly lastAttemptAt: number;
33
+ /** The most recent error (undefined before any failure). */
34
+ readonly lastError?: unknown;
35
+ }
36
+ /** Options for {@link retry}. Every numeric field has a default (see {@link DEFAULT_RETRY_OPTIONS}). */
37
+ interface RetryOptions<R = unknown> {
38
+ /** Total attempts including the first (default 3). */
39
+ attempts?: number;
40
+ /** First-retry delay in ms (default 1000). */
41
+ base?: number;
42
+ /** Multiplier applied per attempt (default 2). */
43
+ factor?: number;
44
+ /** Delay cap in ms (default 30000). */
45
+ max?: number;
46
+ /** Jitter fraction in `[0,1]`: each delay is scaled down by up to this much (default 0.5). */
47
+ jitter?: number;
48
+ /** If every attempt fails, resolve with this value instead of throwing. */
49
+ errorResult?: R;
50
+ /**
51
+ * Decide what to do with a thrown error: return `false` to **stop** retrying (abort), or a number
52
+ * of **milliseconds to wait at least** before the next attempt — a floor under the normal backoff
53
+ * (e.g. honor a `Retry-After`; `0` = just use the backoff). May be async. Default:
54
+ * `(err) => (err instanceof RetryAbort ? false : 0)` — overriding it **replaces** that check
55
+ * (e.g. `on: (e) => (e.status === 404 ? false : 0)` aborts on a 404 with no `RetryAbort` wrapper).
56
+ * To keep retrying through a `RetryAbort`, override it (e.g. `on: () => 0`).
57
+ */
58
+ on?: (err: unknown) => MaybePromise<number | false>;
59
+ /** Called after a successful attempt. */
60
+ onSuccess?: (result: R, state: RetryState) => void;
61
+ /** Called after a failed attempt that will be retried, before backing off. */
62
+ onRetry?: (error: unknown, state: RetryState) => void;
63
+ /** Called when all attempts are exhausted, before throwing or returning `errorResult`. */
64
+ onError?: (error: unknown, state: RetryState) => void;
65
+ /** Sleep implementation (ms) — injectable for tests (default an unref'd timer). */
66
+ sleep?: (ms: number) => Promise<void>;
67
+ /** Randomness for jitter (default `Math.random`). */
68
+ random?: () => number;
69
+ /** Clock (default `Date.now`). */
70
+ now?: () => number;
71
+ }
72
+ /** The built-in defaults — the floor under {@link setDefaultRetryOptions} and per-call options. */
73
+ declare const DEFAULT_RETRY_OPTIONS: Required<Pick<RetryOptions, 'attempts' | 'base' | 'factor' | 'max' | 'jitter'>>;
74
+ /** Set process-wide default {@link RetryOptions} (merged over previous overrides). Per-call options still win. */
75
+ declare function setDefaultRetryOptions(options: RetryOptions): void;
76
+ /** The effective global defaults ({@link DEFAULT_RETRY_OPTIONS} plus any {@link setDefaultRetryOptions} overrides). */
77
+ declare function getDefaultRetryOptions(): RetryOptions;
78
+ /**
79
+ * Backoff delay for retry `attempt` (1 = the first retry):
80
+ * `min(base · factor^(attempt-1), max) · (1 − jitter · random())`.
81
+ */
82
+ declare function backoff(attempt: number, opts?: Pick<RetryOptions, 'base' | 'factor' | 'max' | 'jitter'>, random?: () => number): number;
83
+ /**
84
+ * Run `fn`, retrying on rejection with exponential backoff + jitter up to `attempts`
85
+ * times. Resolves with the first success; on exhaustion it returns `errorResult` if one
86
+ * was provided, otherwise re-throws the last error.
87
+ */
88
+ declare function retry<R>(fn: (state: RetryState) => Promise<R>, options?: RetryOptions<R>): Promise<R>;
89
+ //#endregion
90
+ export { DEFAULT_RETRY_OPTIONS, RetryAbort, RetryOptions, RetryState, backoff, getDefaultRetryOptions, retry, setDefaultRetryOptions };
package/dist/retry.js ADDED
@@ -0,0 +1,129 @@
1
+ //#region src/retry.ts
2
+ /**
3
+ * Throw this from a {@link retry} operation to **stop retrying immediately**: the remaining
4
+ * attempts (and their backoff) are skipped, `onError` fires once, and `retry` then re-throws the
5
+ * abort's `cause` (or returns `errorResult` if one was configured). Use it for a permanent failure
6
+ * a retry can't fix — a 4xx, a validation error, a missing resource.
7
+ *
8
+ * ```ts
9
+ * await retry(async () => {
10
+ * const res = await fetch(url);
11
+ * if (res.status === 404) throw new RetryAbort(new Error('not found')); // don't retry a 404
12
+ * if (!res.ok) throw new Error(`http ${res.status}`); // transient → retried
13
+ * return res.json();
14
+ * });
15
+ * ```
16
+ */
17
+ var RetryAbort = class extends Error {
18
+ constructor(cause, message = "retry aborted") {
19
+ super(message, { cause });
20
+ this.name = "RetryAbort";
21
+ }
22
+ };
23
+ /** The default {@link RetryOptions.on}: a {@link RetryAbort} stops the loop, anything else retries with the normal backoff. */
24
+ const defaultOn = (err) => err instanceof RetryAbort ? false : 0;
25
+ /** The built-in defaults — the floor under {@link setDefaultRetryOptions} and per-call options. */
26
+ const DEFAULT_RETRY_OPTIONS = {
27
+ attempts: 3,
28
+ base: 1e3,
29
+ factor: 2,
30
+ max: 3e4,
31
+ jitter: .5
32
+ };
33
+ /** Process-wide overrides, applied between {@link DEFAULT_RETRY_OPTIONS} and per-call options. */
34
+ let globalDefaults = {};
35
+ /** Set process-wide default {@link RetryOptions} (merged over previous overrides). Per-call options still win. */
36
+ function setDefaultRetryOptions(options) {
37
+ globalDefaults = {
38
+ ...globalDefaults,
39
+ ...options
40
+ };
41
+ }
42
+ /** The effective global defaults ({@link DEFAULT_RETRY_OPTIONS} plus any {@link setDefaultRetryOptions} overrides). */
43
+ function getDefaultRetryOptions() {
44
+ return {
45
+ ...DEFAULT_RETRY_OPTIONS,
46
+ ...globalDefaults
47
+ };
48
+ }
49
+ const defaultSleep = (ms) => new Promise((resolve) => {
50
+ setTimeout(resolve, ms).unref?.();
51
+ });
52
+ /**
53
+ * Backoff delay for retry `attempt` (1 = the first retry):
54
+ * `min(base · factor^(attempt-1), max) · (1 − jitter · random())`.
55
+ */
56
+ function backoff(attempt, opts = {}, random = Math.random) {
57
+ const base = opts.base ?? DEFAULT_RETRY_OPTIONS.base;
58
+ const factor = opts.factor ?? DEFAULT_RETRY_OPTIONS.factor;
59
+ const max = opts.max ?? DEFAULT_RETRY_OPTIONS.max;
60
+ const jitter = opts.jitter ?? DEFAULT_RETRY_OPTIONS.jitter;
61
+ const raw = base * Math.pow(factor, Math.max(0, attempt - 1));
62
+ return Math.round(Math.min(raw, max) * (1 - jitter * random()));
63
+ }
64
+ /**
65
+ * Run `fn`, retrying on rejection with exponential backoff + jitter up to `attempts`
66
+ * times. Resolves with the first success; on exhaustion it returns `errorResult` if one
67
+ * was provided, otherwise re-throws the last error.
68
+ */
69
+ async function retry(fn, options = {}) {
70
+ const o = {
71
+ ...DEFAULT_RETRY_OPTIONS,
72
+ ...globalDefaults,
73
+ ...options
74
+ };
75
+ const now = o.now ?? Date.now;
76
+ const random = o.random ?? Math.random;
77
+ const sleep = o.sleep ?? defaultSleep;
78
+ const decide = o.on ?? defaultOn;
79
+ const hasErrorResult = "errorResult" in options;
80
+ const startAt = now();
81
+ let lastError;
82
+ for (let attempt = 1; attempt <= o.attempts; attempt++) {
83
+ const lastAttemptAt = now();
84
+ const state = {
85
+ startAt,
86
+ attempt,
87
+ attempts: o.attempts,
88
+ lastAttemptAt,
89
+ lastError
90
+ };
91
+ try {
92
+ const result = await fn(state);
93
+ o.onSuccess?.(result, state);
94
+ return result;
95
+ } catch (err) {
96
+ const decision = await decide(err);
97
+ if (decision === false) {
98
+ const cause = err instanceof RetryAbort ? err.cause ?? err : err;
99
+ lastError = cause;
100
+ o.onError?.(cause, {
101
+ startAt,
102
+ attempt,
103
+ attempts: o.attempts,
104
+ lastAttemptAt,
105
+ lastError: cause
106
+ });
107
+ break;
108
+ }
109
+ lastError = err;
110
+ const failed = {
111
+ startAt,
112
+ attempt,
113
+ attempts: o.attempts,
114
+ lastAttemptAt,
115
+ lastError: err
116
+ };
117
+ if (attempt < o.attempts) {
118
+ o.onRetry?.(err, failed);
119
+ await sleep(Math.max(decision, backoff(attempt, o, random)));
120
+ continue;
121
+ }
122
+ o.onError?.(err, failed);
123
+ }
124
+ }
125
+ if (hasErrorResult) return options.errorResult;
126
+ throw lastError;
127
+ }
128
+ //#endregion
129
+ export { DEFAULT_RETRY_OPTIONS, RetryAbort, backoff, getDefaultRetryOptions, retry, setDefaultRetryOptions };
package/dist/stats.cjs ADDED
Binary file
@@ -0,0 +1,150 @@
1
+ //#region src/stats.d.ts
2
+ /**
3
+ * # Stats — a tiny, runtime-agnostic metrics primitive
4
+ *
5
+ * A dependency-free way to track named, typed measurements and hand them to whatever the
6
+ * end user already runs — a periodic log, StatsD, Prometheus. Three metric kinds cover
7
+ * the field:
8
+ *
9
+ * - **counter** — a monotonic tally (`inc`): requests served, jobs failed.
10
+ * - **gauge** — a value that moves both ways (`set`/`add`/`max`): in-flight count, a peak.
11
+ * - **summary** — a distribution of observations (`observe`): always count/total/min/max/avg,
12
+ * and — when buckets/quantiles are configured — histogram buckets + approximate
13
+ * percentiles (p50/p95/p99). Histogram-backed, so it's deterministic and bounded-memory.
14
+ *
15
+ * Every metric carries metadata (`name`, `kind`, `description`, `unit`) and is **labelled**
16
+ * (e.g. `{ type: 'email' }`), so one name spans many series. {@link createMetrics} is the
17
+ * registry: create handles, snapshot every series as a flat {@link StatValue} list, and
18
+ * {@link Metrics.subscribe} to **coalesced** change notifications (a burst of mutations
19
+ * yields one batched callback). {@link formatPrometheus} renders a snapshot as Prometheus
20
+ * text exposition.
21
+ *
22
+ * ```ts
23
+ * import { createMetrics, formatPrometheus } from '@ayepi/core'
24
+ *
25
+ * const m = createMetrics({ quantiles: [0.5, 0.95, 0.99] })
26
+ * m.counter('jobs_done', { type: 'email' }).inc()
27
+ * m.summary('job_ms', { type: 'email' }, { unit: 'ms' }).observe(42)
28
+ *
29
+ * setInterval(() => console.log(formatPrometheus(m.list())), 15_000) // scrape/log loop
30
+ * ```
31
+ *
32
+ * @module
33
+ */
34
+ /** The three metric shapes. */
35
+ type StatKind = 'counter' | 'gauge' | 'summary';
36
+ /** A metric's label set (a series within a metric family). Values are strings, order-insensitive. */
37
+ type Labels = Readonly<Record<string, string>>;
38
+ /** Static metadata describing a metric family (shared across its label series). */
39
+ interface StatMeta {
40
+ /** The metric name (the family key). */
41
+ readonly name: string;
42
+ /** Which kind of metric this is. */
43
+ readonly kind: StatKind;
44
+ /** Human-readable description (exported as Prometheus `# HELP`). */
45
+ readonly description?: string;
46
+ /** Unit of measure, e.g. `'ms'`, `'bytes'`, `'count'` (informational). */
47
+ readonly unit?: string;
48
+ }
49
+ /** One histogram bucket: the cumulative count of observations `<= le` (`le` = upper bound, `Infinity` for the overflow). */
50
+ interface StatBucket {
51
+ readonly le: number;
52
+ readonly count: number;
53
+ }
54
+ /** A summary's distribution snapshot. `quantiles`/`buckets` are present only when configured. */
55
+ interface StatSummary {
56
+ /** Number of observations. */
57
+ readonly count: number;
58
+ /** Sum of all observations. */
59
+ readonly total: number;
60
+ /** Smallest observation (0 when none). */
61
+ readonly min: number;
62
+ /** Largest observation (0 when none). */
63
+ readonly max: number;
64
+ /** Mean (0 when none). */
65
+ readonly avg: number;
66
+ /** Approximate quantiles by probability key, e.g. `{ '0.95': 180 }` — when `quantiles` were configured. */
67
+ readonly quantiles?: Readonly<Record<string, number>>;
68
+ /** Cumulative histogram buckets — when buckets were configured. */
69
+ readonly buckets?: readonly StatBucket[];
70
+ }
71
+ /** A point-in-time snapshot of a single metric series (one name + label set). */
72
+ interface StatValue {
73
+ /** The owning family's metadata. */
74
+ readonly meta: StatMeta;
75
+ /** This series' labels. */
76
+ readonly labels: Labels;
77
+ /** Counter/gauge value; for a summary, its observation `count` (full detail in {@link summary}). */
78
+ readonly value: number;
79
+ /** Present iff `meta.kind === 'summary'`. */
80
+ readonly summary?: StatSummary;
81
+ }
82
+ /** A monotonic tally. */
83
+ interface Counter {
84
+ /** Add `by` (default 1). */
85
+ inc(by?: number): void;
86
+ /** Current total. */
87
+ value(): number;
88
+ }
89
+ /** A value that moves up and down. */
90
+ interface Gauge {
91
+ /** Replace the value. */
92
+ set(v: number): void;
93
+ /** Add `by` (may be negative). */
94
+ add(by: number): void;
95
+ /** Raise to `v` if larger (a running high-water mark). */
96
+ max(v: number): void;
97
+ /** Current value. */
98
+ value(): number;
99
+ }
100
+ /** A distribution of observations. */
101
+ interface Summary {
102
+ /** Record one observation. */
103
+ observe(v: number): void;
104
+ /** Current distribution snapshot. */
105
+ snapshot(): StatSummary;
106
+ }
107
+ /** Options for {@link createMetrics}. */
108
+ interface MetricsOptions {
109
+ /**
110
+ * Probabilities (0–1) to estimate for every summary, e.g. `[0.5, 0.95, 0.99]`. Enables
111
+ * histogram bucketing (default {@link DEFAULT_BUCKETS} unless `buckets` is given) and fills
112
+ * {@link StatSummary.quantiles}. Omit for count/total/min/max/avg only (no per-observation cost).
113
+ */
114
+ readonly quantiles?: readonly number[];
115
+ /** Histogram bucket upper bounds for summaries (ascending). Defaults to {@link DEFAULT_BUCKETS} when `quantiles` is set. */
116
+ readonly buckets?: readonly number[];
117
+ /**
118
+ * Schedules the coalesced flush of change notifications. Default batches via `queueMicrotask`
119
+ * (one callback per synchronous burst). Inject `(fn) => fn()` for synchronous delivery, or a
120
+ * manual collector in tests.
121
+ */
122
+ readonly schedule?: (flush: () => void) => void;
123
+ }
124
+ /** The metrics registry returned by {@link createMetrics}. */
125
+ interface Metrics {
126
+ /** Get-or-create a {@link Counter} series. */
127
+ counter(name: string, labels?: Labels, meta?: Omit<StatMeta, 'name' | 'kind'>): Counter;
128
+ /** Get-or-create a {@link Gauge} series. */
129
+ gauge(name: string, labels?: Labels, meta?: Omit<StatMeta, 'name' | 'kind'>): Gauge;
130
+ /** Get-or-create a {@link Summary} series. */
131
+ summary(name: string, labels?: Labels, meta?: Omit<StatMeta, 'name' | 'kind'>): Summary;
132
+ /** Snapshot every series as a flat list (one {@link StatValue} per name + label set). */
133
+ list(): StatValue[];
134
+ /** Snapshot one series, or `undefined` if it was never created. */
135
+ get(name: string, labels?: Labels): StatValue | undefined;
136
+ /** Subscribe to **coalesced** change notifications; the listener gets the batch of changed series. Returns an unsubscribe fn. */
137
+ subscribe(listener: (changed: readonly StatValue[]) => void): () => void;
138
+ }
139
+ /** Default histogram bucket upper bounds (ms-oriented), used when `quantiles` is set without explicit `buckets`. */
140
+ declare const DEFAULT_BUCKETS: readonly number[];
141
+ /** Create a metrics registry. */
142
+ declare function createMetrics(opts?: MetricsOptions): Metrics;
143
+ /**
144
+ * Render a {@link Metrics.list} snapshot as Prometheus text exposition format. Counters and gauges
145
+ * map directly; summaries are emitted as **histograms** (`_bucket`/`_count`/`_sum`) when buckets are
146
+ * present, else as bare `_count`/`_sum`. Names are sanitized to valid Prometheus identifiers.
147
+ */
148
+ declare function formatPrometheus(stats: readonly StatValue[]): string;
149
+ //#endregion
150
+ export { Counter, DEFAULT_BUCKETS, Gauge, Labels, Metrics, MetricsOptions, StatBucket, StatKind, StatMeta, StatSummary, StatValue, Summary, createMetrics, formatPrometheus };