@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.
@@ -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 };
package/dist/stats.js ADDED
Binary file
@@ -0,0 +1,54 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * # Type utilities
4
+ *
5
+ * Small, dependency-free type-level helpers used across the library. None of
6
+ * these emit runtime code — they exist purely to make the public generic
7
+ * surface infer precisely without leaking `any`/`unknown` to consumers.
8
+ *
9
+ * @module
10
+ */
11
+ /**
12
+ * Flatten an intersection (`A & B & …`) into a single object literal so editor
13
+ * tooltips and `Equal<>` comparisons see one clean shape instead of a chain of
14
+ * intersections.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * type Messy = { a: 1 } & { b: 2 }
19
+ * type Clean = Simplify<Messy> // { a: 1; b: 2 }
20
+ * ```
21
+ */
22
+ type Simplify<T> = { [K in keyof T]: T[K] } & {};
23
+ /** A value that may be returned either synchronously or as a `Promise`. */
24
+ type MaybePromise<T> = T | Promise<T>;
25
+ /**
26
+ * The empty object type. Used as the identity element when merging contributed
27
+ * shapes (middleware context, path params, etc.) so an absent contribution adds
28
+ * nothing rather than widening the result.
29
+ */
30
+ type EmptyObject = {};
31
+ /**
32
+ * Convert a union `A | B | C` into the intersection `A & B & C`.
33
+ *
34
+ * Drives middleware-context merging: each middleware contributes a shape, and
35
+ * the handler sees the intersection of every contribution in its chain.
36
+ */
37
+ type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never;
38
+ /** Distribute `keyof` across a union, yielding the union of every member's keys. */
39
+
40
+ /**
41
+ * Safe indexed access: resolves to `T[K]` when `K` is a key of `T`, otherwise
42
+ * `undefined`. Lets optional config properties be read without first proving
43
+ * they exist.
44
+ */
45
+ type Get<T, K extends string> = K extends keyof T ? T[K] : undefined;
46
+ /**
47
+ * A JSON-shaped value — the closed set of things the OpenAPI/AsyncAPI doc
48
+ * generators produce and accept in their patch callbacks.
49
+ */
50
+ type Json = string | number | boolean | null | Json[] | {
51
+ [k: string]: Json;
52
+ };
53
+ //#endregion
54
+ export { Simplify as a, MaybePromise as i, Get as n, UnionToIntersection as o, Json as r, EmptyObject as t };
@@ -0,0 +1,54 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * # Type utilities
4
+ *
5
+ * Small, dependency-free type-level helpers used across the library. None of
6
+ * these emit runtime code — they exist purely to make the public generic
7
+ * surface infer precisely without leaking `any`/`unknown` to consumers.
8
+ *
9
+ * @module
10
+ */
11
+ /**
12
+ * Flatten an intersection (`A & B & …`) into a single object literal so editor
13
+ * tooltips and `Equal<>` comparisons see one clean shape instead of a chain of
14
+ * intersections.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * type Messy = { a: 1 } & { b: 2 }
19
+ * type Clean = Simplify<Messy> // { a: 1; b: 2 }
20
+ * ```
21
+ */
22
+ type Simplify<T> = { [K in keyof T]: T[K] } & {};
23
+ /** A value that may be returned either synchronously or as a `Promise`. */
24
+ type MaybePromise<T> = T | Promise<T>;
25
+ /**
26
+ * The empty object type. Used as the identity element when merging contributed
27
+ * shapes (middleware context, path params, etc.) so an absent contribution adds
28
+ * nothing rather than widening the result.
29
+ */
30
+ type EmptyObject = {};
31
+ /**
32
+ * Convert a union `A | B | C` into the intersection `A & B & C`.
33
+ *
34
+ * Drives middleware-context merging: each middleware contributes a shape, and
35
+ * the handler sees the intersection of every contribution in its chain.
36
+ */
37
+ type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never;
38
+ /** Distribute `keyof` across a union, yielding the union of every member's keys. */
39
+
40
+ /**
41
+ * Safe indexed access: resolves to `T[K]` when `K` is a key of `T`, otherwise
42
+ * `undefined`. Lets optional config properties be read without first proving
43
+ * they exist.
44
+ */
45
+ type Get<T, K extends string> = K extends keyof T ? T[K] : undefined;
46
+ /**
47
+ * A JSON-shaped value — the closed set of things the OpenAPI/AsyncAPI doc
48
+ * generators produce and accept in their patch callbacks.
49
+ */
50
+ type Json = string | number | boolean | null | Json[] | {
51
+ [k: string]: Json;
52
+ };
53
+ //#endregion
54
+ export { Simplify as a, MaybePromise as i, Get as n, UnionToIntersection as o, Json as r, EmptyObject as t };