@maroonedsoftware/telemetry 0.0.0-stage → 0.1.1

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.
Files changed (44) hide show
  1. package/AGENTS.md +179 -0
  2. package/LICENSE +21 -0
  3. package/README.md +124 -2
  4. package/dist/chunk-7QVYU63E.js +7 -0
  5. package/dist/chunk-7QVYU63E.js.map +1 -0
  6. package/dist/chunk-E7OXUL3D.js +25 -0
  7. package/dist/chunk-E7OXUL3D.js.map +1 -0
  8. package/dist/finished.span.d.ts +44 -0
  9. package/dist/finished.span.d.ts.map +1 -0
  10. package/dist/index.d.ts +8 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +426 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/kysely.d.ts +2 -0
  15. package/dist/kysely.d.ts.map +1 -0
  16. package/dist/kysely.js +237 -0
  17. package/dist/kysely.js.map +1 -0
  18. package/dist/metric.aggregate.d.ts +41 -0
  19. package/dist/metric.aggregate.d.ts.map +1 -0
  20. package/dist/metric.bucket.d.ts +25 -0
  21. package/dist/metric.bucket.d.ts.map +1 -0
  22. package/dist/metric.labels.d.ts +18 -0
  23. package/dist/metric.labels.d.ts.map +1 -0
  24. package/dist/metric.resolution.d.ts +19 -0
  25. package/dist/metric.resolution.d.ts.map +1 -0
  26. package/dist/metrics.recorder.d.ts +96 -0
  27. package/dist/metrics.recorder.d.ts.map +1 -0
  28. package/dist/metrics.store.d.ts +143 -0
  29. package/dist/metrics.store.d.ts.map +1 -0
  30. package/dist/otlp.d.ts +4 -0
  31. package/dist/otlp.d.ts.map +1 -0
  32. package/dist/otlp.export.d.ts +8 -0
  33. package/dist/otlp.export.d.ts.map +1 -0
  34. package/dist/otlp.ids.d.ts +16 -0
  35. package/dist/otlp.ids.d.ts.map +1 -0
  36. package/dist/otlp.js +237 -0
  37. package/dist/otlp.js.map +1 -0
  38. package/dist/otlp.metrics.bridge.d.ts +64 -0
  39. package/dist/otlp.metrics.bridge.d.ts.map +1 -0
  40. package/dist/otlp.metrics.receiver.d.ts +79 -0
  41. package/dist/otlp.metrics.receiver.d.ts.map +1 -0
  42. package/dist/otlp.span.bridge.d.ts +44 -0
  43. package/dist/otlp.span.bridge.d.ts.map +1 -0
  44. package/package.json +103 -3
package/AGENTS.md ADDED
@@ -0,0 +1,179 @@
1
+ # AGENTS.md — @maroonedsoftware/telemetry
2
+
3
+ Machine-oriented guide for AI agents. Human prose and long-form examples live in [README.md](./README.md).
4
+ Repo-wide conventions live in the [root AGENTS.md](../../AGENTS.md).
5
+
6
+ ## Purpose
7
+
8
+ Collects application metrics into one-minute buckets in memory (`MetricsRecorder`), so an app can keep
9
+ its own numbers over time and chart them. Not an OpenTelemetry SDK replacement: it holds aggregates, not
10
+ raw observations, and has no percentiles. Reach for the OTel SDK directly when you only need to ship
11
+ metrics to a collector and never read them back.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pnpm add @maroonedsoftware/telemetry injectkit reflect-metadata
17
+ ```
18
+
19
+ Required peers: none beyond `injectkit`'s own `reflect-metadata`.
20
+ Optional peers: `kysely` — unlocks the `./kysely` subpath (`MetricsStore`). `@opentelemetry/api`, `/core`,
21
+ `/resources`, `/sdk-metrics` and `/sdk-trace` — unlock the `./otlp` subpath. The OTLP exporters themselves
22
+ (`@opentelemetry/exporter-metrics-otlp-http` and the like) are the app's choice and not peers.
23
+
24
+ ## Position in the graph
25
+
26
+ - **Depends on:** nothing internal
27
+ - **Depended on by:** nothing internal
28
+ - **Subpath exports:** `./kysely` — `MetricsStore`, the PostgreSQL store; a subpath so the root entry stays free of `kysely`
29
+ - **Subpath exports:** `./otlp` — `OtlpMetricsBridge`, `OtlpSpanBridge` and the id helpers; a subpath so the root entry stays free of the OTel SDK
30
+
31
+ ## API surface
32
+
33
+ | Export | Kind | Shape | Notes |
34
+ | ---------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
35
+ | `MetricsRecorder` | class | `observe(name, value, labels?, at?)`, `gauge(name, read, options?)`, `sampleGauges(now?)`, `drain()`, `pending`, `onGaugeError?` | `@Injectable()`; register as a singleton |
36
+ | `GaugeReader` | type | `() => number \| undefined \| Promise<number \| undefined>` | `undefined` records nothing |
37
+ | `GaugeOptions` | interface | `{ labels?: MetricLabels; every?: Duration }` | `every` defaults to zero (every pass) |
38
+ | `GaugeErrorHandler` | type | `(name: string, error: unknown) => void` | |
39
+ | `MetricAggregate` | interface | `{ count, sum, min, max, last }` | |
40
+ | `aggregateOf` | function | `(value: number) => MetricAggregate` | |
41
+ | `mergeAggregates` | function | `(previous, next) => MetricAggregate` | associative; `last` is taken from `next` |
42
+ | `MetricBucket` | interface | `{ name, labels, labelsKey, start: DateTime, aggregate }` | `start` is the UTC minute |
43
+ | `minuteOf` | function | `(at: DateTime) => DateTime` | UTC start of minute |
44
+ | `MetricLabels` | type | `Readonly<Record<string, string>>` | string values only |
45
+ | `labelsKey` | function | `(labels: MetricLabels) => string` | sorted JSON; `'{}'` for none |
46
+ | `FinishedSpan` | interface | `{ traceId, spanId, parentSpanId?, name, start, end, ok, error?, attributes?, links? }` | the app's own ids; work already done |
47
+ | `SpanLink` | interface | `{ traceId, spanId }` | |
48
+ | `SpanAttributeValue` | type | `string \| number \| boolean` | |
49
+ | `OtlpMetricsReceiver` | class | `new OtlpMetricsReceiver({ maxSeriesPerName?, maxNames? })`; `receive(body, now?) => OtlpReceiveResult` | OTLP/JSON only; holds cumulative baselines and the caps; one per process |
50
+ | `OtlpMetricsReceiverOptions` | interface | `{ maxSeriesPerName?: number; maxNames?: number }` | defaults 50 and 500 |
51
+ | `OtlpReceiveResult` | interface | `{ buckets: MetricBucket[]; dropped: number; unsupported: number }` | |
52
+ | `OtlpPayloadError` | class | `extends Error` | body is not a metrics export; answer 400 |
53
+ | `MetricResolution` | type | `'minute' \| 'hour'` | |
54
+ | `resolutionFor` | function | `(from, to, limit?) => MetricResolution` | minutes up to `limit` (48h), else hours |
55
+ | `MINUTE_RESOLUTION_LIMIT` | const | `Duration` (48 hours) | |
56
+
57
+ ### `./kysely`
58
+
59
+ | Export | Kind | Shape | Notes |
60
+ | --------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
61
+ | `MetricsStore<DB>` | class | `new MetricsStore(db: Kysely<DB>, options)`; `upsertMinutes(buckets, source?)`, `rollupHour(hour)`, `prune(resolution, olderThan, now?)`, `series(query)`, `names()` | runs on `db.withoutPlugins()`; not `@Injectable()`, construct it in a factory |
62
+ | `MetricsStoreOptions` | interface | `{ table: string; scope?: Record<string, string>; batchSize?: number }` | `scope` columns must be in the primary key |
63
+ | `MetricSeriesQuery` | interface | `{ names, from, to, resolution, labels? }` | `to` is exclusive; `labels` matches with `@>` |
64
+ | `MetricSeries` | interface | `{ name, labels, labelsKey, source, points: MetricPoint[] }` | points oldest first |
65
+ | `MetricPoint` | interface | `{ start: DateTime; aggregate: MetricAggregate }` | |
66
+ | `MetricName` | interface | `{ name, source, labelKeys: string[], lastSeen: DateTime }` | |
67
+
68
+ ### `./otlp`
69
+
70
+ | Export | Kind | Shape | Notes |
71
+ | -------------------------- | --------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
72
+ | `OtlpMetricsBridge` | class | `new OtlpMetricsBridge({ exporter, resource?, scope?, kindOf? })`; `export(buckets)`, `shutdown()` | builds `ResourceMetrics` directly, no `MeterProvider`; delta temporality |
73
+ | `OtlpMetricsBridgeOptions` | interface | as above | `kindOf` defaults to `gauge` |
74
+ | `MetricKind` | type | `'gauge' \| 'counter' \| 'histogram'` | gauge → `last`, counter → `sum`, histogram → count/sum/min/max |
75
+ | `OtlpSpanBridge` | class | `new OtlpSpanBridge({ exporter, resource?, scope? })`; `export(spans)`, `shutdown()` | builds finished `ReadableSpan`s with recorded times |
76
+ | `OtlpSpanBridgeOptions` | interface | as above | |
77
+ | `otlpTraceId` | function | `(id: string) => string` | 32 hex; kept if already, else sha256-derived |
78
+ | `otlpSpanId` | function | `(id: string) => string` | 16 hex; same rule |
79
+
80
+ ## Canonical usage
81
+
82
+ ```typescript
83
+ import { MetricsRecorder } from '@maroonedsoftware/telemetry';
84
+
85
+ registry.register(MetricsRecorder).useClass(MetricsRecorder).asSingleton();
86
+
87
+ const recorder = container.get(MetricsRecorder);
88
+ recorder.observe('work.job.ms', 412, { queue: 'catalog.sync' });
89
+ recorder.gauge('station.listeners', () => audience.count());
90
+
91
+ // your loop, once a minute
92
+ await recorder.sampleGauges();
93
+ await write(recorder.drain());
94
+ ```
95
+
96
+ With the store, in the app's own loop and jobs:
97
+
98
+ ```typescript
99
+ import { MetricsStore } from '@maroonedsoftware/telemetry/kysely';
100
+
101
+ const store = new MetricsStore(db, { table: 'app.metric_samples' });
102
+ await store.upsertMinutes(recorder.drain()); // every minute
103
+ await store.rollupHour(DateTime.utc().minus({ hours: 1 })); // hourly job
104
+ await store.prune('minute', Duration.fromObject({ days: 7 })); // nightly job
105
+ ```
106
+
107
+ Exporting over OTLP:
108
+
109
+ ```typescript
110
+ import { OtlpMetricsBridge, OtlpSpanBridge } from '@maroonedsoftware/telemetry/otlp';
111
+ import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
112
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
113
+
114
+ const metrics = new OtlpMetricsBridge({ exporter: new OTLPMetricExporter({ url }), kindOf: name => (name.endsWith('.ms') ? 'histogram' : 'gauge') });
115
+ const spans = new OtlpSpanBridge({ exporter: new OTLPTraceExporter({ url }) });
116
+
117
+ const buckets = recorder.drain();
118
+ await store.upsertMinutes(buckets);
119
+ await metrics.export(buckets).catch(log); // a dead collector must not stop the store write
120
+ await spans.export(finishedSinceLastFlush);
121
+ ```
122
+
123
+ Receiving OTLP/JSON from other services, in the app's own route:
124
+
125
+ ```typescript
126
+ import { OtlpMetricsReceiver, OtlpPayloadError } from '@maroonedsoftware/telemetry';
127
+
128
+ const receiver = new OtlpMetricsReceiver({ maxSeriesPerName: 50 }); // a singleton
129
+
130
+ const { buckets, dropped } = receiver.receive(ctx.request.body); // throws OtlpPayloadError → 400
131
+ await store.upsertMinutes(buckets, 'otlp');
132
+ ctx.body = dropped > 0 ? { partialSuccess: { rejectedDataPoints: String(dropped) } } : {};
133
+ ```
134
+
135
+ ## Rules for generated code
136
+
137
+ - Register `MetricsRecorder` as a singleton. A scoped or transient recorder splits observations across
138
+ instances and the loop drains only one of them.
139
+ - Name metrics in dot notation (`work.job.ms`), with the unit as the last segment when there is one.
140
+ - Keep label values from a small, known set (a queue name, a plugin id). Never put an id, a user, or
141
+ free text in a label: every distinct label set is a row per minute.
142
+ - Drain on shutdown too, after producers have stopped, or the last partial minute is lost.
143
+ - Create the table from the reference DDL in the README, in the app's own migration. Column names are
144
+ fixed; add `scope` columns to the primary key, first.
145
+ - Roll an hour up only once it is complete. Rolling up early is not wrong (the next run replaces the row)
146
+ but the hour row lags until then.
147
+
148
+ - Export to OTLP after the store write, and never let an export failure skip or retry the store write.
149
+ - Give every metric that is a duration or a count a `kindOf` answer; the default sends everything as a gauge.
150
+
151
+ - Keep one `OtlpMetricsReceiver` per process. A new one per request loses every cumulative baseline, so
152
+ cumulative sums and histograms never produce a point.
153
+ - Put received metrics behind authentication and a size limit on the body: the caps bound rows, not bytes.
154
+
155
+ ## Gotchas
156
+
157
+ - `drain()` includes the still-open minute. A store must merge (upsert) rather than insert, or a
158
+ minute flushed twice is double-counted or rejected.
159
+ - The OTLP metrics are delta. A collector feeding Prometheus needs the `deltatocumulative` processor.
160
+ - `otlpTraceId` hashes, so a trace id is only stable for the same app id string. Two apps sharing a trace
161
+ must agree on the id string, or pass real 32-hex ids.
162
+ - The receiver reads OTLP/JSON only. A sender on the default `http/protobuf` encoding must be switched to
163
+ `http/json` (`OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/json`).
164
+ - A cumulative series' first point, and its first after a sender restart, produce nothing: they set the baseline.
165
+ - The caps last for the receiver's life. A dropped series stays dropped until the process restarts.
166
+ - `observe()` silently drops `NaN` and `Infinity`.
167
+ - `MetricsStore` ignores the app's Kysely plugins. Rows from `series()` are mapped by the store, never
168
+ read raw, so a `CamelCasePlugin` cannot rename its columns underneath it.
169
+ - A gauge whose reader throws is marked sampled anyway, so it is retried at its `every` cadence, not on
170
+ the next pass.
171
+
172
+ ## Working inside this package
173
+
174
+ `src/` holds one concern per file (`metric.aggregate.ts`, `metric.labels.ts`, `metric.bucket.ts`,
175
+ `metric.resolution.ts`, `metrics.recorder.ts`) behind the `src/index.ts` barrel, `metrics.store.ts` behind the
176
+ `src/kysely.ts` subpath entry, and the `otlp.*.ts` bridges behind `src/otlp.ts`.
177
+ `otlp.metrics.receiver.ts` is in the root entry on purpose: it parses JSON and needs no OTel SDK; tests mirror them in `tests/`. Keep the root
178
+ entry free of I/O and of external SDKs. A new subpath needs an `exports` entry, a tsup entry in
179
+ `build`, and its SDK as an optional peer. Every user-visible change needs a changeset.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marooned Software
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,125 @@
1
- # Temporary Holding Version
1
+ # @maroonedsoftware/telemetry
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Minute-bucket metrics for ServerKit. Producers observe numbers into an in-process recorder; a loop you own drains it once a minute and writes what it drained wherever you keep metrics.
4
+
5
+ ## Features
6
+
7
+ - **One shape for every metric**: each series' minute is five numbers (`count`, `sum`, `min`, `max`, `last`), which read as a gauge, a counter or a duration
8
+ - **Gauge registry**: register a reader once, sample it on every pass or at its own cadence
9
+ - **Merge-friendly**: aggregates are associative, so a later flush of the same minute merges into the row already stored, and minutes fold into hours
10
+ - **No I/O in the recorder**: storage and export are separate, so a dead database or collector never blocks a producer
11
+ - **A PostgreSQL store** (`/kysely`): merging upserts, idempotent hourly rollups, pruning, range reads
12
+ - **OpenTelemetry bridges** (`/otlp`): send the same buckets, and finished spans, to any OTLP exporter
13
+ - **An OTLP/JSON receiver**: read other services' metric exports into the same buckets, with cumulative-to-delta and a cardinality cap
14
+ - **Zero internal dependencies**: [injectkit](https://www.npmjs.com/package/injectkit) and [luxon](https://moment.github.io/luxon/) only
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pnpm add @maroonedsoftware/telemetry injectkit reflect-metadata
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```typescript
25
+ import { MetricsRecorder } from '@maroonedsoftware/telemetry';
26
+ import { Duration } from 'luxon';
27
+
28
+ const recorder = new MetricsRecorder(); // register as a singleton in your container
29
+
30
+ // Anywhere work happens:
31
+ recorder.observe('work.job.ms', 412, { queue: 'catalog.sync' });
32
+
33
+ // Once, where a reading lives:
34
+ recorder.gauge('disk.free.bytes', () => disk.free(), { every: Duration.fromObject({ minutes: 15 }) });
35
+
36
+ // In a loop you own, once a minute:
37
+ await recorder.sampleGauges();
38
+ const buckets = recorder.drain();
39
+ ```
40
+
41
+ ## Storing metrics in PostgreSQL
42
+
43
+ `@maroonedsoftware/telemetry/kysely` needs `kysely` installed. The table belongs to your app; create it in your own migration:
44
+
45
+ ```sql
46
+ create table app.metric_samples (
47
+ resolution text not null check (resolution in ('minute', 'hour')),
48
+ bucket timestamptz not null,
49
+ name text not null,
50
+ labels jsonb not null default '{}',
51
+ labels_key text not null,
52
+ source text not null default 'app',
53
+ count double precision not null,
54
+ sum double precision not null,
55
+ min double precision not null,
56
+ max double precision not null,
57
+ last double precision not null,
58
+ primary key (resolution, name, labels_key, bucket)
59
+ );
60
+ create index metric_samples_bucket on app.metric_samples (resolution, bucket);
61
+ ```
62
+
63
+ To keep several tenants in one table, add a column per tenant key to the front of the primary key and the index, and pass it as `scope`.
64
+
65
+ ```typescript
66
+ import { MetricsStore } from '@maroonedsoftware/telemetry/kysely';
67
+ import { resolutionFor } from '@maroonedsoftware/telemetry';
68
+
69
+ const store = new MetricsStore(db, { table: 'app.metric_samples' });
70
+
71
+ await store.upsertMinutes(recorder.drain()); // a later flush of the same minute merges in
72
+ await store.rollupHour(DateTime.utc().minus({ hours: 1 })); // idempotent: replaces the hour row
73
+ await store.prune('minute', Duration.fromObject({ days: 7 }));
74
+ await store.prune('hour', Duration.fromObject({ days: 400 }));
75
+
76
+ const series = await store.series({ names: ['work.job.ms'], from, to, resolution: resolutionFor(from, to) });
77
+ ```
78
+
79
+ ## Exporting over OTLP
80
+
81
+ `@maroonedsoftware/telemetry/otlp` needs `@opentelemetry/api`, `/core`, `/resources`, `/sdk-metrics` and `/sdk-trace`, plus whichever exporter you send with.
82
+
83
+ ```typescript
84
+ import { OtlpMetricsBridge, OtlpSpanBridge } from '@maroonedsoftware/telemetry/otlp';
85
+
86
+ const metrics = new OtlpMetricsBridge({
87
+ exporter: new OTLPMetricExporter({ url: 'http://collector:4318/v1/metrics' }),
88
+ kindOf: name => (name.endsWith('.ms') ? 'histogram' : name.endsWith('.count') ? 'counter' : 'gauge'),
89
+ });
90
+ await metrics.export(buckets);
91
+
92
+ const spans = new OtlpSpanBridge({ exporter: new OTLPTraceExporter({ url: 'http://collector:4318/v1/traces' }) });
93
+ await spans.export([{ traceId: 'job-42', spanId: 'job-42/run', name: 'job.run', start, end, ok: true }]);
94
+ ```
95
+
96
+ Metrics go out with delta temporality: each bucket is one point spanning its minute. Spans keep the times you recorded, and your own ids are hashed into OTLP form so the same id always lands in the same trace.
97
+
98
+ ## Receiving OTLP/JSON
99
+
100
+ `OtlpMetricsReceiver` turns the body of an OTLP/HTTP `POST /v1/metrics` sent as JSON into minute buckets, so other services' metrics land in the same store. It needs no OTel packages.
101
+
102
+ ```typescript
103
+ import { OtlpMetricsReceiver } from '@maroonedsoftware/telemetry';
104
+
105
+ const receiver = new OtlpMetricsReceiver({ maxSeriesPerName: 50, maxNames: 500 }); // keep one per process
106
+
107
+ const { buckets, dropped, unsupported } = receiver.receive(body);
108
+ await store.upsertMinutes(buckets, 'otlp');
109
+ ```
110
+
111
+ Gauges and up-down sums are read as values; monotonic sums and histograms contribute their increase, with cumulative series differenced against their previous point. Exponential histograms and summaries are counted as unsupported. Senders must use `http/json`, not protobuf.
112
+
113
+ ## Reading an aggregate
114
+
115
+ | Kind | Read |
116
+ | -------- | ------------------------------------------- |
117
+ | Gauge | `last`, or `sum / count` for the mean |
118
+ | Counter | `sum` |
119
+ | Duration | `sum / count` for the mean, `max` for worst |
120
+
121
+ There are no percentiles: those need a histogram or sketch per bucket.
122
+
123
+ ## License
124
+
125
+ MIT
@@ -0,0 +1,7 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ export {
5
+ __name
6
+ };
7
+ //# sourceMappingURL=chunk-7QVYU63E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,25 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-7QVYU63E.js";
4
+
5
+ // src/metric.aggregate.ts
6
+ var aggregateOf = /* @__PURE__ */ __name((value) => ({
7
+ count: 1,
8
+ sum: value,
9
+ min: value,
10
+ max: value,
11
+ last: value
12
+ }), "aggregateOf");
13
+ var mergeAggregates = /* @__PURE__ */ __name((previous, next) => ({
14
+ count: previous.count + next.count,
15
+ sum: previous.sum + next.sum,
16
+ min: Math.min(previous.min, next.min),
17
+ max: Math.max(previous.max, next.max),
18
+ last: next.last
19
+ }), "mergeAggregates");
20
+
21
+ export {
22
+ aggregateOf,
23
+ mergeAggregates
24
+ };
25
+ //# sourceMappingURL=chunk-E7OXUL3D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/metric.aggregate.ts"],"sourcesContent":["/**\n * Five numbers that summarise every observation of one series inside one bucket.\n *\n * One shape covers all three kinds of metric, so a store needs a single row type:\n * - a **gauge** reads `last` (or `sum / count` for the bucket's mean),\n * - a **counter** reads `sum`,\n * - a **duration** reads `sum / count` for the mean and `max` for the worst.\n *\n * There are no percentiles. They need a histogram or a sketch per bucket, and the\n * mean beside the max is enough to see that something got slow.\n */\nexport interface MetricAggregate {\n /** How many observations were folded in. */\n count: number;\n /** Their total. */\n sum: number;\n /** The smallest observation. */\n min: number;\n /** The largest observation. */\n max: number;\n /** The most recent observation. */\n last: number;\n}\n\n/**\n * The aggregate of a single observation.\n *\n * @param value - The observed value. The caller is expected to have rejected non-finite values.\n */\nexport const aggregateOf = (value: number): MetricAggregate => ({ count: 1, sum: value, min: value, max: value, last: value });\n\n/**\n * Folds `next` into `previous`.\n *\n * Associative, so buckets can be merged in any grouping (a flush into a stored row, minutes into an\n * hour). Not commutative in `last`: `next` is taken to be the later of the two.\n *\n * @param previous - The earlier aggregate.\n * @param next - The later aggregate.\n * @returns A new aggregate; neither input is modified.\n */\nexport const mergeAggregates = (previous: MetricAggregate, next: MetricAggregate): MetricAggregate => ({\n count: previous.count + next.count,\n sum: previous.sum + next.sum,\n min: Math.min(previous.min, next.min),\n max: Math.max(previous.max, next.max),\n last: next.last,\n});\n"],"mappings":";;;;;AA6BO,IAAMA,cAAc,wBAACC,WAAoC;EAAEC,OAAO;EAAGC,KAAKF;EAAOG,KAAKH;EAAOI,KAAKJ;EAAOK,MAAML;AAAM,IAAjG;AAYpB,IAAMM,kBAAkB,wBAACC,UAA2BC,UAA4C;EACrGP,OAAOM,SAASN,QAAQO,KAAKP;EAC7BC,KAAKK,SAASL,MAAMM,KAAKN;EACzBC,KAAKM,KAAKN,IAAII,SAASJ,KAAKK,KAAKL,GAAG;EACpCC,KAAKK,KAAKL,IAAIG,SAASH,KAAKI,KAAKJ,GAAG;EACpCC,MAAMG,KAAKH;AACb,IAN+B;","names":["aggregateOf","value","count","sum","min","max","last","mergeAggregates","previous","next","Math"]}
@@ -0,0 +1,44 @@
1
+ import { DateTime } from 'luxon';
2
+ /**
3
+ * An attribute value on a {@link FinishedSpan}.
4
+ */
5
+ export type SpanAttributeValue = string | number | boolean;
6
+ /**
7
+ * A reference from one span to another that is not its parent: work one decision caused in another
8
+ * trace, say.
9
+ */
10
+ export interface SpanLink {
11
+ traceId: string;
12
+ spanId: string;
13
+ }
14
+ /**
15
+ * A unit of work that has already happened, described after the fact.
16
+ *
17
+ * Apps that time their own work (a job runner wrapping each run, a tool loop timing each call) can
18
+ * describe it in this plain shape and hand it to an exporter, rather than threading a live tracer
19
+ * through the work. The ids are the app's own strings: an exporter that needs a particular format
20
+ * (OTLP's 16- and 8-byte hex) derives one from them.
21
+ */
22
+ export interface FinishedSpan {
23
+ /** Groups the spans of one piece of work. */
24
+ traceId: string;
25
+ /** Identifies this span within its trace. */
26
+ spanId: string;
27
+ /** The span this one ran inside, in the same trace. */
28
+ parentSpanId?: string;
29
+ /** What the work was: `job.run`, `plugin.invoke`. */
30
+ name: string;
31
+ /** When it began. */
32
+ start: DateTime;
33
+ /** When it ended. */
34
+ end: DateTime;
35
+ /** Whether it succeeded. */
36
+ ok: boolean;
37
+ /** Why it failed, when it did. Never sent as anything but a status message. */
38
+ error?: string;
39
+ /** Anything else worth keeping about it. */
40
+ attributes?: Readonly<Record<string, SpanAttributeValue>>;
41
+ /** Spans in other traces this one relates to. */
42
+ links?: readonly SpanLink[];
43
+ }
44
+ //# sourceMappingURL=finished.span.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finished.span.d.ts","sourceRoot":"","sources":["../src/finished.span.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,KAAK,EAAE,QAAQ,CAAC;IAChB,qBAAqB;IACrB,GAAG,EAAE,QAAQ,CAAC;IACd,4BAA4B;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC1D,iDAAiD;IACjD,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;CAC7B"}
@@ -0,0 +1,8 @@
1
+ export * from './finished.span.js';
2
+ export * from './metric.aggregate.js';
3
+ export * from './metric.bucket.js';
4
+ export * from './metric.labels.js';
5
+ export * from './metric.resolution.js';
6
+ export * from './metrics.recorder.js';
7
+ export * from './otlp.metrics.receiver.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC"}