@btravstack/core 0.9.0 → 0.10.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/index.cjs +47 -0
- package/dist/index.d.cts +211 -116
- package/dist/index.d.mts +211 -116
- package/dist/index.mjs +45 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -61,6 +61,50 @@ const runHealthChecks = (checks) => (0, unthrown.allAsync)(checks.map((health) =
|
|
|
61
61
|
components
|
|
62
62
|
}));
|
|
63
63
|
//#endregion
|
|
64
|
+
//#region src/observation.ts
|
|
65
|
+
/**
|
|
66
|
+
* The set port a starter contributes its observability to, and reads whole.
|
|
67
|
+
*
|
|
68
|
+
* **Called at the START and answering a finisher**, rather than told about a
|
|
69
|
+
* finished operation. That is what lets an observer open a span before the work
|
|
70
|
+
* and end it after — a span reconstructed afterwards from a duration is not the
|
|
71
|
+
* parent of anything that ran inside it.
|
|
72
|
+
*
|
|
73
|
+
* A set port rather than a `Meter` in every starter's `needs`: an observer
|
|
74
|
+
* package an application never composed contributes nothing, and a starter
|
|
75
|
+
* costs a graph no ports to have observability available. That is the
|
|
76
|
+
* difference between "instrumented by default" and "instrumented if you asked
|
|
77
|
+
* for observability", and only the second is free.
|
|
78
|
+
*
|
|
79
|
+
* **A module that reads this port contributes a no-op member of its own**, the
|
|
80
|
+
* way `otel()` does for `Instrumentations`: a collector depending on a set port
|
|
81
|
+
* NOTHING provides is an unmet dependency, at plan time and in `Needs` alike.
|
|
82
|
+
* Several no-ops in one graph cost a call each and nothing else.
|
|
83
|
+
*/
|
|
84
|
+
var Observers = class extends _btravstack_di.Port.many("Observers") {};
|
|
85
|
+
/** The no-op member every reader of {@link Observers} contributes, so the set is never empty. */
|
|
86
|
+
const noObserver = () => () => {};
|
|
87
|
+
/**
|
|
88
|
+
* Run every observer around one operation, and hand back the one finisher that
|
|
89
|
+
* settles them all.
|
|
90
|
+
*
|
|
91
|
+
* Observers are started in order and settled in order, and the finisher is
|
|
92
|
+
* **guarded**: a second call is dropped. "Called exactly once" is therefore a
|
|
93
|
+
* property of this function rather than a rule each starter re-keeps — which
|
|
94
|
+
* matters because the settling call sites are error paths, where a `tap` and a
|
|
95
|
+
* `tapFailure` on the same chain, or a retry, is exactly the shape that fires
|
|
96
|
+
* twice and doubles a failure count.
|
|
97
|
+
*/
|
|
98
|
+
const observe = (observers, operation) => {
|
|
99
|
+
const settlers = observers.map((observer) => observer(operation));
|
|
100
|
+
let done = false;
|
|
101
|
+
return (settled) => {
|
|
102
|
+
if (done) return;
|
|
103
|
+
done = true;
|
|
104
|
+
for (const settle of settlers) settle(settled);
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
64
108
|
//#region src/clock.ts
|
|
65
109
|
const systemClock = {
|
|
66
110
|
now: () => Date.now(),
|
|
@@ -652,11 +696,14 @@ exports.Instrumentations = Instrumentations;
|
|
|
652
696
|
exports.LEVELS = LEVELS;
|
|
653
697
|
exports.Logger = Logger;
|
|
654
698
|
exports.Meter = Meter;
|
|
699
|
+
exports.Observers = Observers;
|
|
655
700
|
exports.RuntimePort = RuntimePort;
|
|
656
701
|
exports.RuntimeStartFailed = RuntimeStartFailed;
|
|
657
702
|
exports.SPAN_STATUS = SPAN_STATUS;
|
|
658
703
|
exports.Tracer = Tracer;
|
|
659
704
|
exports.currentUnit = currentUnit;
|
|
705
|
+
exports.noObserver = noObserver;
|
|
706
|
+
exports.observe = observe;
|
|
660
707
|
exports.releasedBy = releasedBy;
|
|
661
708
|
exports.runHealthChecks = runHealthChecks;
|
|
662
709
|
exports.runMain = runMain;
|
package/dist/index.d.cts
CHANGED
|
@@ -83,6 +83,216 @@ type HealthReport = {
|
|
|
83
83
|
*/
|
|
84
84
|
declare const runHealthChecks: (checks: readonly HealthCheck[]) => AsyncResult<HealthReport, never>;
|
|
85
85
|
//#endregion
|
|
86
|
+
//#region src/observability.d.ts
|
|
87
|
+
/**
|
|
88
|
+
* The severity of one line, and the whole of the set: six levels, ordered, with
|
|
89
|
+
* no caller-defined additions — which is what lets `LOG_LEVEL` be validated at
|
|
90
|
+
* startup and `isEnabled` be a comparison rather than a lookup.
|
|
91
|
+
*/
|
|
92
|
+
type Level = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
93
|
+
/** The levels in order, least severe first — what `isEnabled` compares through. */
|
|
94
|
+
declare const LEVELS: readonly Level[];
|
|
95
|
+
/**
|
|
96
|
+
* What a line, a span or a measurement carries besides itself: a flat record of
|
|
97
|
+
* scalars.
|
|
98
|
+
*
|
|
99
|
+
* Flat and scalar on purpose. A nested object is where a field's name stops
|
|
100
|
+
* being stable, and an `unknown` value is where a logger starts stringifying
|
|
101
|
+
* whatever it is handed — which is how a log call becomes the thing that
|
|
102
|
+
* throws. A failure has a channel of its own, `cause`.
|
|
103
|
+
*/
|
|
104
|
+
type Attributes = Readonly<Record<string, string | number | boolean | undefined>>;
|
|
105
|
+
/**
|
|
106
|
+
* Every method takes the same three arguments in the same order:
|
|
107
|
+
* `(message, attributes?, cause?)`.
|
|
108
|
+
*
|
|
109
|
+
* Uniform on purpose. A failure is not a property of severity — an `info` line
|
|
110
|
+
* reporting a recovered fault carries one too — so every level can take a
|
|
111
|
+
* `cause`, at the cost of `logger.error("boom", undefined, cause)` for a
|
|
112
|
+
* failure with nothing else to say.
|
|
113
|
+
*/
|
|
114
|
+
type LoggerService = {
|
|
115
|
+
readonly log: (level: Level, message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
116
|
+
readonly trace: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
117
|
+
readonly debug: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
118
|
+
readonly info: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
119
|
+
readonly warn: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
120
|
+
/** `cause` is the failure itself — an `Error`, an `unthrown` `Err`'s error, a rejected value. */
|
|
121
|
+
readonly error: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
122
|
+
readonly fatal: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
123
|
+
/** A logger carrying `attributes` on every line it writes, on top of this one's. Never mutates this one. */
|
|
124
|
+
readonly with: (attributes: Attributes) => LoggerService;
|
|
125
|
+
/** Whether a line at `level` would be written — for a payload expensive enough to be worth not building. */
|
|
126
|
+
readonly isEnabled: (level: Level) => boolean;
|
|
127
|
+
};
|
|
128
|
+
declare const Logger_base: import("@btravstack/di").PortClass<"Logger">;
|
|
129
|
+
/**
|
|
130
|
+
* The application's logger, as a port — declared here and implemented in
|
|
131
|
+
* `@btravstack/observability`, because a contract every package may depend on
|
|
132
|
+
* has to be reachable without installing an implementation, and because the
|
|
133
|
+
* correlation an implementation stamps per line is the kernel's own ambient
|
|
134
|
+
* record.
|
|
135
|
+
*
|
|
136
|
+
* An implementation must not throw: a logger that throws turns an observability
|
|
137
|
+
* problem into an outage. Synchronous `void`, not an `AsyncResult` — thesis 6's
|
|
138
|
+
* one deliberate exemption, since a lost line is not a modeled error.
|
|
139
|
+
*/
|
|
140
|
+
declare class Logger extends Logger_base<LoggerService> {}
|
|
141
|
+
/** A span's outcome: unset until something says otherwise, then `ok` or `error`. */
|
|
142
|
+
declare const SPAN_STATUS: {
|
|
143
|
+
readonly unset: 0;
|
|
144
|
+
readonly ok: 1;
|
|
145
|
+
readonly error: 2;
|
|
146
|
+
};
|
|
147
|
+
/** The three codes {@link SPAN_STATUS} names, as the type a {@link Span} takes. */
|
|
148
|
+
type SpanStatusCode = (typeof SPAN_STATUS)[keyof typeof SPAN_STATUS];
|
|
149
|
+
/**
|
|
150
|
+
* One span, narrowed to what a framework package does with it: label it, say
|
|
151
|
+
* how it ended, end it. The numbers are OpenTelemetry's own, so an OTel span
|
|
152
|
+
* satisfies this structurally with no translation in between.
|
|
153
|
+
*/
|
|
154
|
+
type Span = {
|
|
155
|
+
readonly setAttributes: (attributes: Attributes) => unknown;
|
|
156
|
+
readonly setStatus: (status: {
|
|
157
|
+
readonly code: SpanStatusCode;
|
|
158
|
+
readonly message?: string;
|
|
159
|
+
}) => unknown;
|
|
160
|
+
readonly end: () => void;
|
|
161
|
+
};
|
|
162
|
+
/** What a {@link Tracer} does: start a span, by name. */
|
|
163
|
+
type TracerService = {
|
|
164
|
+
readonly startSpan: (name: string) => Span;
|
|
165
|
+
};
|
|
166
|
+
declare const Tracer_base: import("@btravstack/di").PortClass<"Tracer">;
|
|
167
|
+
/**
|
|
168
|
+
* The application's tracer, as a port — declared without naming OpenTelemetry,
|
|
169
|
+
* because a port typed as a vendor's type points the dependency arrow outwards
|
|
170
|
+
* and makes the whole family install that vendor.
|
|
171
|
+
*/
|
|
172
|
+
declare class Tracer extends Tracer_base<TracerService> {}
|
|
173
|
+
/** A monotonic count. */
|
|
174
|
+
type Counter = {
|
|
175
|
+
readonly add: (value: number, attributes?: Attributes) => void;
|
|
176
|
+
};
|
|
177
|
+
/** A distribution of measurements. */
|
|
178
|
+
type Histogram = {
|
|
179
|
+
readonly record: (value: number, attributes?: Attributes) => void;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* What a {@link Meter} does: mint the two instruments a framework package needs
|
|
183
|
+
* — it counts what happened and measures how long it took. A gauge is something
|
|
184
|
+
* an application declares about its own domain, reaching the vendor's meter for
|
|
185
|
+
* it as it would any other adapter.
|
|
186
|
+
*/
|
|
187
|
+
type MeterService = {
|
|
188
|
+
readonly createCounter: (name: string, options?: {
|
|
189
|
+
readonly description?: string;
|
|
190
|
+
readonly unit?: string;
|
|
191
|
+
}) => Counter;
|
|
192
|
+
readonly createHistogram: (name: string, options?: {
|
|
193
|
+
readonly description?: string;
|
|
194
|
+
readonly unit?: string;
|
|
195
|
+
}) => Histogram;
|
|
196
|
+
};
|
|
197
|
+
declare const Meter_base: import("@btravstack/di").PortClass<"Meter">;
|
|
198
|
+
/** The application's meter, as a port. See {@link Tracer} for why it does not name a vendor. */
|
|
199
|
+
declare class Meter extends Meter_base<MeterService> {}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/observation.d.ts
|
|
202
|
+
/**
|
|
203
|
+
* One operation worth observing, named before it runs.
|
|
204
|
+
*
|
|
205
|
+
* `component` is the starter's own word for itself — `"http"`, `"cache"`,
|
|
206
|
+
* `"amqp"` — and it is what lets an observer mint per-component instrument and
|
|
207
|
+
* span names without knowing anything about the component. `name` is the
|
|
208
|
+
* operation within it (`"get"`, `"unit"`), and `attributes` are the dimensions
|
|
209
|
+
* known before the call.
|
|
210
|
+
*
|
|
211
|
+
* The dimensions are the contributor's business and their **cardinality is its
|
|
212
|
+
* responsibility**: a request path or a workflow id here mints a time series
|
|
213
|
+
* per value, which is how a metrics bill becomes the incident.
|
|
214
|
+
*/
|
|
215
|
+
type Operation = {
|
|
216
|
+
readonly component: string;
|
|
217
|
+
readonly name: string;
|
|
218
|
+
/**
|
|
219
|
+
* The DIMENSIONS: bounded, and what a metrics observer may put on an
|
|
220
|
+
* instrument. A cache operation, an HTTP method, an activity type.
|
|
221
|
+
*
|
|
222
|
+
* Their cardinality is the contributor's responsibility — a request path or a
|
|
223
|
+
* cache key here mints a time series per value, which is how a metrics bill
|
|
224
|
+
* becomes the incident. That is what {@link Operation.details} is for.
|
|
225
|
+
*/
|
|
226
|
+
readonly attributes: Attributes;
|
|
227
|
+
/**
|
|
228
|
+
* Unbounded context: a cache key, a mail subject, a URL. It rides spans and
|
|
229
|
+
* log lines, where one more field costs one more field — and **never a
|
|
230
|
+
* metric**, where it would cost a time series per value.
|
|
231
|
+
*
|
|
232
|
+
* The split is the whole reason an observer can be shared: without it every
|
|
233
|
+
* contributor would have to choose between a useful span and a safe metric.
|
|
234
|
+
*/
|
|
235
|
+
readonly details?: Attributes;
|
|
236
|
+
/**
|
|
237
|
+
* Whether a tracing observer should open a span (default `true`).
|
|
238
|
+
*
|
|
239
|
+
* `false` is for a component whose spans already come from somewhere better:
|
|
240
|
+
* `@btravstack/prisma` says so because `@prisma/instrumentation` traces at the
|
|
241
|
+
* ENGINE level — the real SQL, the connection acquisition — all of it below
|
|
242
|
+
* what a client-level wrapper can see, so a second span would cost one more
|
|
243
|
+
* per query for strictly less information. Counting and timing still happen.
|
|
244
|
+
*
|
|
245
|
+
* Whether a span is worth opening is the CONTRIBUTOR's knowledge, which is
|
|
246
|
+
* why it rides the operation rather than being configured on the observer.
|
|
247
|
+
*/
|
|
248
|
+
readonly traced?: boolean;
|
|
249
|
+
};
|
|
250
|
+
/** How an operation came out, plus whatever was only knowable at the end. */
|
|
251
|
+
type Settled = {
|
|
252
|
+
readonly outcome: "ok" | "error";
|
|
253
|
+
/** Dimensions known only at the end, merged over the operation's own — an HTTP status, a cache hit or miss. */
|
|
254
|
+
readonly attributes?: Attributes;
|
|
255
|
+
/** The failure itself, for an observer that writes a line about it. */
|
|
256
|
+
readonly cause?: unknown;
|
|
257
|
+
};
|
|
258
|
+
/** What an observer answers: a finisher, called exactly once when the operation settles. */
|
|
259
|
+
type Settle = (settled: Settled) => void;
|
|
260
|
+
declare const Observers_base: import("@btravstack/di").ManyPortClass<"Observers">;
|
|
261
|
+
/**
|
|
262
|
+
* The set port a starter contributes its observability to, and reads whole.
|
|
263
|
+
*
|
|
264
|
+
* **Called at the START and answering a finisher**, rather than told about a
|
|
265
|
+
* finished operation. That is what lets an observer open a span before the work
|
|
266
|
+
* and end it after — a span reconstructed afterwards from a duration is not the
|
|
267
|
+
* parent of anything that ran inside it.
|
|
268
|
+
*
|
|
269
|
+
* A set port rather than a `Meter` in every starter's `needs`: an observer
|
|
270
|
+
* package an application never composed contributes nothing, and a starter
|
|
271
|
+
* costs a graph no ports to have observability available. That is the
|
|
272
|
+
* difference between "instrumented by default" and "instrumented if you asked
|
|
273
|
+
* for observability", and only the second is free.
|
|
274
|
+
*
|
|
275
|
+
* **A module that reads this port contributes a no-op member of its own**, the
|
|
276
|
+
* way `otel()` does for `Instrumentations`: a collector depending on a set port
|
|
277
|
+
* NOTHING provides is an unmet dependency, at plan time and in `Needs` alike.
|
|
278
|
+
* Several no-ops in one graph cost a call each and nothing else.
|
|
279
|
+
*/
|
|
280
|
+
declare class Observers extends Observers_base<(operation: Operation) => Settle> {}
|
|
281
|
+
/** The no-op member every reader of {@link Observers} contributes, so the set is never empty. */
|
|
282
|
+
declare const noObserver: () => Settle;
|
|
283
|
+
/**
|
|
284
|
+
* Run every observer around one operation, and hand back the one finisher that
|
|
285
|
+
* settles them all.
|
|
286
|
+
*
|
|
287
|
+
* Observers are started in order and settled in order, and the finisher is
|
|
288
|
+
* **guarded**: a second call is dropped. "Called exactly once" is therefore a
|
|
289
|
+
* property of this function rather than a rule each starter re-keeps — which
|
|
290
|
+
* matters because the settling call sites are error paths, where a `tap` and a
|
|
291
|
+
* `tapFailure` on the same chain, or a retry, is exactly the shape that fires
|
|
292
|
+
* twice and doubles a failure count.
|
|
293
|
+
*/
|
|
294
|
+
declare const observe: (observers: readonly ((operation: Operation) => Settle)[], operation: Operation) => Settle;
|
|
295
|
+
//#endregion
|
|
86
296
|
//#region src/clock.d.ts
|
|
87
297
|
type Clock = {
|
|
88
298
|
readonly now: () => number;
|
|
@@ -305,121 +515,6 @@ type KernelEvent = {
|
|
|
305
515
|
type EventSink = (event: KernelEvent) => void;
|
|
306
516
|
declare const stderrSink: EventSink;
|
|
307
517
|
//#endregion
|
|
308
|
-
//#region src/observability.d.ts
|
|
309
|
-
/**
|
|
310
|
-
* The severity of one line, and the whole of the set: six levels, ordered, with
|
|
311
|
-
* no caller-defined additions — which is what lets `LOG_LEVEL` be validated at
|
|
312
|
-
* startup and `isEnabled` be a comparison rather than a lookup.
|
|
313
|
-
*/
|
|
314
|
-
type Level = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
315
|
-
/** The levels in order, least severe first — what `isEnabled` compares through. */
|
|
316
|
-
declare const LEVELS: readonly Level[];
|
|
317
|
-
/**
|
|
318
|
-
* What a line, a span or a measurement carries besides itself: a flat record of
|
|
319
|
-
* scalars.
|
|
320
|
-
*
|
|
321
|
-
* Flat and scalar on purpose. A nested object is where a field's name stops
|
|
322
|
-
* being stable, and an `unknown` value is where a logger starts stringifying
|
|
323
|
-
* whatever it is handed — which is how a log call becomes the thing that
|
|
324
|
-
* throws. A failure has a channel of its own, `cause`.
|
|
325
|
-
*/
|
|
326
|
-
type Attributes = Readonly<Record<string, string | number | boolean | undefined>>;
|
|
327
|
-
/**
|
|
328
|
-
* Every method takes the same three arguments in the same order:
|
|
329
|
-
* `(message, attributes?, cause?)`.
|
|
330
|
-
*
|
|
331
|
-
* Uniform on purpose. A failure is not a property of severity — an `info` line
|
|
332
|
-
* reporting a recovered fault carries one too — so every level can take a
|
|
333
|
-
* `cause`, at the cost of `logger.error("boom", undefined, cause)` for a
|
|
334
|
-
* failure with nothing else to say.
|
|
335
|
-
*/
|
|
336
|
-
type LoggerService = {
|
|
337
|
-
readonly log: (level: Level, message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
338
|
-
readonly trace: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
339
|
-
readonly debug: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
340
|
-
readonly info: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
341
|
-
readonly warn: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
342
|
-
/** `cause` is the failure itself — an `Error`, an `unthrown` `Err`'s error, a rejected value. */
|
|
343
|
-
readonly error: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
344
|
-
readonly fatal: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
345
|
-
/** A logger carrying `attributes` on every line it writes, on top of this one's. Never mutates this one. */
|
|
346
|
-
readonly with: (attributes: Attributes) => LoggerService;
|
|
347
|
-
/** Whether a line at `level` would be written — for a payload expensive enough to be worth not building. */
|
|
348
|
-
readonly isEnabled: (level: Level) => boolean;
|
|
349
|
-
};
|
|
350
|
-
declare const Logger_base: import("@btravstack/di").PortClass<"Logger">;
|
|
351
|
-
/**
|
|
352
|
-
* The application's logger, as a port — declared here and implemented in
|
|
353
|
-
* `@btravstack/observability`, because a contract every package may depend on
|
|
354
|
-
* has to be reachable without installing an implementation, and because the
|
|
355
|
-
* correlation an implementation stamps per line is the kernel's own ambient
|
|
356
|
-
* record.
|
|
357
|
-
*
|
|
358
|
-
* An implementation must not throw: a logger that throws turns an observability
|
|
359
|
-
* problem into an outage. Synchronous `void`, not an `AsyncResult` — thesis 6's
|
|
360
|
-
* one deliberate exemption, since a lost line is not a modeled error.
|
|
361
|
-
*/
|
|
362
|
-
declare class Logger extends Logger_base<LoggerService> {}
|
|
363
|
-
/** A span's outcome: unset until something says otherwise, then `ok` or `error`. */
|
|
364
|
-
declare const SPAN_STATUS: {
|
|
365
|
-
readonly unset: 0;
|
|
366
|
-
readonly ok: 1;
|
|
367
|
-
readonly error: 2;
|
|
368
|
-
};
|
|
369
|
-
/** The three codes {@link SPAN_STATUS} names, as the type a {@link Span} takes. */
|
|
370
|
-
type SpanStatusCode = (typeof SPAN_STATUS)[keyof typeof SPAN_STATUS];
|
|
371
|
-
/**
|
|
372
|
-
* One span, narrowed to what a framework package does with it: label it, say
|
|
373
|
-
* how it ended, end it. The numbers are OpenTelemetry's own, so an OTel span
|
|
374
|
-
* satisfies this structurally with no translation in between.
|
|
375
|
-
*/
|
|
376
|
-
type Span = {
|
|
377
|
-
readonly setAttributes: (attributes: Attributes) => unknown;
|
|
378
|
-
readonly setStatus: (status: {
|
|
379
|
-
readonly code: SpanStatusCode;
|
|
380
|
-
readonly message?: string;
|
|
381
|
-
}) => unknown;
|
|
382
|
-
readonly end: () => void;
|
|
383
|
-
};
|
|
384
|
-
/** What a {@link Tracer} does: start a span, by name. */
|
|
385
|
-
type TracerService = {
|
|
386
|
-
readonly startSpan: (name: string) => Span;
|
|
387
|
-
};
|
|
388
|
-
declare const Tracer_base: import("@btravstack/di").PortClass<"Tracer">;
|
|
389
|
-
/**
|
|
390
|
-
* The application's tracer, as a port — declared without naming OpenTelemetry,
|
|
391
|
-
* because a port typed as a vendor's type points the dependency arrow outwards
|
|
392
|
-
* and makes the whole family install that vendor.
|
|
393
|
-
*/
|
|
394
|
-
declare class Tracer extends Tracer_base<TracerService> {}
|
|
395
|
-
/** A monotonic count. */
|
|
396
|
-
type Counter = {
|
|
397
|
-
readonly add: (value: number, attributes?: Attributes) => void;
|
|
398
|
-
};
|
|
399
|
-
/** A distribution of measurements. */
|
|
400
|
-
type Histogram = {
|
|
401
|
-
readonly record: (value: number, attributes?: Attributes) => void;
|
|
402
|
-
};
|
|
403
|
-
/**
|
|
404
|
-
* What a {@link Meter} does: mint the two instruments a framework package needs
|
|
405
|
-
* — it counts what happened and measures how long it took. A gauge is something
|
|
406
|
-
* an application declares about its own domain, reaching the vendor's meter for
|
|
407
|
-
* it as it would any other adapter.
|
|
408
|
-
*/
|
|
409
|
-
type MeterService = {
|
|
410
|
-
readonly createCounter: (name: string, options?: {
|
|
411
|
-
readonly description?: string;
|
|
412
|
-
readonly unit?: string;
|
|
413
|
-
}) => Counter;
|
|
414
|
-
readonly createHistogram: (name: string, options?: {
|
|
415
|
-
readonly description?: string;
|
|
416
|
-
readonly unit?: string;
|
|
417
|
-
}) => Histogram;
|
|
418
|
-
};
|
|
419
|
-
declare const Meter_base: import("@btravstack/di").PortClass<"Meter">;
|
|
420
|
-
/** The application's meter, as a port. See {@link Tracer} for why it does not name a vendor. */
|
|
421
|
-
declare class Meter extends Meter_base<MeterService> {}
|
|
422
|
-
//#endregion
|
|
423
518
|
//#region src/phase.d.ts
|
|
424
519
|
type Phase = "building" | "starting" | "serving" | "draining" | "stopping" | "exited";
|
|
425
520
|
//#endregion
|
|
@@ -553,4 +648,4 @@ declare const start: <X, E, UnitX = never, UnitNeeds = never>(module: Module<X,
|
|
|
553
648
|
*/
|
|
554
649
|
declare const runMain: <X, E, UnitX = never, UnitNeeds = never>(module: Module<X, E, Scope | Env> & StartGate<X, UnitNeeds>, options?: StartOptions<UnitX, UnitNeeds>, exit?: (code: number) => void) => Promise<void>;
|
|
555
650
|
//#endregion
|
|
556
|
-
export { type Attributes, type Clock, type ComponentHealth, type Counter, type DrainReport, type EventSink, type ExitReport, type HealthCheck, HealthCheckFailed, HealthChecks, type HealthReport, type Histogram, type InstrumentationLoader, Instrumentations, type KernelEvent, LEVELS, type Level, Logger, type LoggerService, Meter, type MeterService, type Phase, type RunUnit, type RunningApp, type Runtime, type RuntimeHost, type RuntimeInfoOf, RuntimePort, RuntimeStartFailed, SPAN_STATUS, type Serving, type Span, type SpanStatusCode, type StartGate, type StartOptions, type TeardownError, Tracer, type TracerService, type UnitMeta, type UnitRecord, type UnitRegistry, type UnitWork, currentUnit, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock };
|
|
651
|
+
export { type Attributes, type Clock, type ComponentHealth, type Counter, type DrainReport, type EventSink, type ExitReport, type HealthCheck, HealthCheckFailed, HealthChecks, type HealthReport, type Histogram, type InstrumentationLoader, Instrumentations, type KernelEvent, LEVELS, type Level, Logger, type LoggerService, Meter, type MeterService, Observers, type Operation, type Phase, type RunUnit, type RunningApp, type Runtime, type RuntimeHost, type RuntimeInfoOf, RuntimePort, RuntimeStartFailed, SPAN_STATUS, type Serving, type Settle, type Settled, type Span, type SpanStatusCode, type StartGate, type StartOptions, type TeardownError, Tracer, type TracerService, type UnitMeta, type UnitRecord, type UnitRegistry, type UnitWork, currentUnit, noObserver, observe, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock };
|
package/dist/index.d.mts
CHANGED
|
@@ -83,6 +83,216 @@ type HealthReport = {
|
|
|
83
83
|
*/
|
|
84
84
|
declare const runHealthChecks: (checks: readonly HealthCheck[]) => AsyncResult<HealthReport, never>;
|
|
85
85
|
//#endregion
|
|
86
|
+
//#region src/observability.d.ts
|
|
87
|
+
/**
|
|
88
|
+
* The severity of one line, and the whole of the set: six levels, ordered, with
|
|
89
|
+
* no caller-defined additions — which is what lets `LOG_LEVEL` be validated at
|
|
90
|
+
* startup and `isEnabled` be a comparison rather than a lookup.
|
|
91
|
+
*/
|
|
92
|
+
type Level = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
93
|
+
/** The levels in order, least severe first — what `isEnabled` compares through. */
|
|
94
|
+
declare const LEVELS: readonly Level[];
|
|
95
|
+
/**
|
|
96
|
+
* What a line, a span or a measurement carries besides itself: a flat record of
|
|
97
|
+
* scalars.
|
|
98
|
+
*
|
|
99
|
+
* Flat and scalar on purpose. A nested object is where a field's name stops
|
|
100
|
+
* being stable, and an `unknown` value is where a logger starts stringifying
|
|
101
|
+
* whatever it is handed — which is how a log call becomes the thing that
|
|
102
|
+
* throws. A failure has a channel of its own, `cause`.
|
|
103
|
+
*/
|
|
104
|
+
type Attributes = Readonly<Record<string, string | number | boolean | undefined>>;
|
|
105
|
+
/**
|
|
106
|
+
* Every method takes the same three arguments in the same order:
|
|
107
|
+
* `(message, attributes?, cause?)`.
|
|
108
|
+
*
|
|
109
|
+
* Uniform on purpose. A failure is not a property of severity — an `info` line
|
|
110
|
+
* reporting a recovered fault carries one too — so every level can take a
|
|
111
|
+
* `cause`, at the cost of `logger.error("boom", undefined, cause)` for a
|
|
112
|
+
* failure with nothing else to say.
|
|
113
|
+
*/
|
|
114
|
+
type LoggerService = {
|
|
115
|
+
readonly log: (level: Level, message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
116
|
+
readonly trace: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
117
|
+
readonly debug: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
118
|
+
readonly info: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
119
|
+
readonly warn: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
120
|
+
/** `cause` is the failure itself — an `Error`, an `unthrown` `Err`'s error, a rejected value. */
|
|
121
|
+
readonly error: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
122
|
+
readonly fatal: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
123
|
+
/** A logger carrying `attributes` on every line it writes, on top of this one's. Never mutates this one. */
|
|
124
|
+
readonly with: (attributes: Attributes) => LoggerService;
|
|
125
|
+
/** Whether a line at `level` would be written — for a payload expensive enough to be worth not building. */
|
|
126
|
+
readonly isEnabled: (level: Level) => boolean;
|
|
127
|
+
};
|
|
128
|
+
declare const Logger_base: import("@btravstack/di").PortClass<"Logger">;
|
|
129
|
+
/**
|
|
130
|
+
* The application's logger, as a port — declared here and implemented in
|
|
131
|
+
* `@btravstack/observability`, because a contract every package may depend on
|
|
132
|
+
* has to be reachable without installing an implementation, and because the
|
|
133
|
+
* correlation an implementation stamps per line is the kernel's own ambient
|
|
134
|
+
* record.
|
|
135
|
+
*
|
|
136
|
+
* An implementation must not throw: a logger that throws turns an observability
|
|
137
|
+
* problem into an outage. Synchronous `void`, not an `AsyncResult` — thesis 6's
|
|
138
|
+
* one deliberate exemption, since a lost line is not a modeled error.
|
|
139
|
+
*/
|
|
140
|
+
declare class Logger extends Logger_base<LoggerService> {}
|
|
141
|
+
/** A span's outcome: unset until something says otherwise, then `ok` or `error`. */
|
|
142
|
+
declare const SPAN_STATUS: {
|
|
143
|
+
readonly unset: 0;
|
|
144
|
+
readonly ok: 1;
|
|
145
|
+
readonly error: 2;
|
|
146
|
+
};
|
|
147
|
+
/** The three codes {@link SPAN_STATUS} names, as the type a {@link Span} takes. */
|
|
148
|
+
type SpanStatusCode = (typeof SPAN_STATUS)[keyof typeof SPAN_STATUS];
|
|
149
|
+
/**
|
|
150
|
+
* One span, narrowed to what a framework package does with it: label it, say
|
|
151
|
+
* how it ended, end it. The numbers are OpenTelemetry's own, so an OTel span
|
|
152
|
+
* satisfies this structurally with no translation in between.
|
|
153
|
+
*/
|
|
154
|
+
type Span = {
|
|
155
|
+
readonly setAttributes: (attributes: Attributes) => unknown;
|
|
156
|
+
readonly setStatus: (status: {
|
|
157
|
+
readonly code: SpanStatusCode;
|
|
158
|
+
readonly message?: string;
|
|
159
|
+
}) => unknown;
|
|
160
|
+
readonly end: () => void;
|
|
161
|
+
};
|
|
162
|
+
/** What a {@link Tracer} does: start a span, by name. */
|
|
163
|
+
type TracerService = {
|
|
164
|
+
readonly startSpan: (name: string) => Span;
|
|
165
|
+
};
|
|
166
|
+
declare const Tracer_base: import("@btravstack/di").PortClass<"Tracer">;
|
|
167
|
+
/**
|
|
168
|
+
* The application's tracer, as a port — declared without naming OpenTelemetry,
|
|
169
|
+
* because a port typed as a vendor's type points the dependency arrow outwards
|
|
170
|
+
* and makes the whole family install that vendor.
|
|
171
|
+
*/
|
|
172
|
+
declare class Tracer extends Tracer_base<TracerService> {}
|
|
173
|
+
/** A monotonic count. */
|
|
174
|
+
type Counter = {
|
|
175
|
+
readonly add: (value: number, attributes?: Attributes) => void;
|
|
176
|
+
};
|
|
177
|
+
/** A distribution of measurements. */
|
|
178
|
+
type Histogram = {
|
|
179
|
+
readonly record: (value: number, attributes?: Attributes) => void;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* What a {@link Meter} does: mint the two instruments a framework package needs
|
|
183
|
+
* — it counts what happened and measures how long it took. A gauge is something
|
|
184
|
+
* an application declares about its own domain, reaching the vendor's meter for
|
|
185
|
+
* it as it would any other adapter.
|
|
186
|
+
*/
|
|
187
|
+
type MeterService = {
|
|
188
|
+
readonly createCounter: (name: string, options?: {
|
|
189
|
+
readonly description?: string;
|
|
190
|
+
readonly unit?: string;
|
|
191
|
+
}) => Counter;
|
|
192
|
+
readonly createHistogram: (name: string, options?: {
|
|
193
|
+
readonly description?: string;
|
|
194
|
+
readonly unit?: string;
|
|
195
|
+
}) => Histogram;
|
|
196
|
+
};
|
|
197
|
+
declare const Meter_base: import("@btravstack/di").PortClass<"Meter">;
|
|
198
|
+
/** The application's meter, as a port. See {@link Tracer} for why it does not name a vendor. */
|
|
199
|
+
declare class Meter extends Meter_base<MeterService> {}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/observation.d.ts
|
|
202
|
+
/**
|
|
203
|
+
* One operation worth observing, named before it runs.
|
|
204
|
+
*
|
|
205
|
+
* `component` is the starter's own word for itself — `"http"`, `"cache"`,
|
|
206
|
+
* `"amqp"` — and it is what lets an observer mint per-component instrument and
|
|
207
|
+
* span names without knowing anything about the component. `name` is the
|
|
208
|
+
* operation within it (`"get"`, `"unit"`), and `attributes` are the dimensions
|
|
209
|
+
* known before the call.
|
|
210
|
+
*
|
|
211
|
+
* The dimensions are the contributor's business and their **cardinality is its
|
|
212
|
+
* responsibility**: a request path or a workflow id here mints a time series
|
|
213
|
+
* per value, which is how a metrics bill becomes the incident.
|
|
214
|
+
*/
|
|
215
|
+
type Operation = {
|
|
216
|
+
readonly component: string;
|
|
217
|
+
readonly name: string;
|
|
218
|
+
/**
|
|
219
|
+
* The DIMENSIONS: bounded, and what a metrics observer may put on an
|
|
220
|
+
* instrument. A cache operation, an HTTP method, an activity type.
|
|
221
|
+
*
|
|
222
|
+
* Their cardinality is the contributor's responsibility — a request path or a
|
|
223
|
+
* cache key here mints a time series per value, which is how a metrics bill
|
|
224
|
+
* becomes the incident. That is what {@link Operation.details} is for.
|
|
225
|
+
*/
|
|
226
|
+
readonly attributes: Attributes;
|
|
227
|
+
/**
|
|
228
|
+
* Unbounded context: a cache key, a mail subject, a URL. It rides spans and
|
|
229
|
+
* log lines, where one more field costs one more field — and **never a
|
|
230
|
+
* metric**, where it would cost a time series per value.
|
|
231
|
+
*
|
|
232
|
+
* The split is the whole reason an observer can be shared: without it every
|
|
233
|
+
* contributor would have to choose between a useful span and a safe metric.
|
|
234
|
+
*/
|
|
235
|
+
readonly details?: Attributes;
|
|
236
|
+
/**
|
|
237
|
+
* Whether a tracing observer should open a span (default `true`).
|
|
238
|
+
*
|
|
239
|
+
* `false` is for a component whose spans already come from somewhere better:
|
|
240
|
+
* `@btravstack/prisma` says so because `@prisma/instrumentation` traces at the
|
|
241
|
+
* ENGINE level — the real SQL, the connection acquisition — all of it below
|
|
242
|
+
* what a client-level wrapper can see, so a second span would cost one more
|
|
243
|
+
* per query for strictly less information. Counting and timing still happen.
|
|
244
|
+
*
|
|
245
|
+
* Whether a span is worth opening is the CONTRIBUTOR's knowledge, which is
|
|
246
|
+
* why it rides the operation rather than being configured on the observer.
|
|
247
|
+
*/
|
|
248
|
+
readonly traced?: boolean;
|
|
249
|
+
};
|
|
250
|
+
/** How an operation came out, plus whatever was only knowable at the end. */
|
|
251
|
+
type Settled = {
|
|
252
|
+
readonly outcome: "ok" | "error";
|
|
253
|
+
/** Dimensions known only at the end, merged over the operation's own — an HTTP status, a cache hit or miss. */
|
|
254
|
+
readonly attributes?: Attributes;
|
|
255
|
+
/** The failure itself, for an observer that writes a line about it. */
|
|
256
|
+
readonly cause?: unknown;
|
|
257
|
+
};
|
|
258
|
+
/** What an observer answers: a finisher, called exactly once when the operation settles. */
|
|
259
|
+
type Settle = (settled: Settled) => void;
|
|
260
|
+
declare const Observers_base: import("@btravstack/di").ManyPortClass<"Observers">;
|
|
261
|
+
/**
|
|
262
|
+
* The set port a starter contributes its observability to, and reads whole.
|
|
263
|
+
*
|
|
264
|
+
* **Called at the START and answering a finisher**, rather than told about a
|
|
265
|
+
* finished operation. That is what lets an observer open a span before the work
|
|
266
|
+
* and end it after — a span reconstructed afterwards from a duration is not the
|
|
267
|
+
* parent of anything that ran inside it.
|
|
268
|
+
*
|
|
269
|
+
* A set port rather than a `Meter` in every starter's `needs`: an observer
|
|
270
|
+
* package an application never composed contributes nothing, and a starter
|
|
271
|
+
* costs a graph no ports to have observability available. That is the
|
|
272
|
+
* difference between "instrumented by default" and "instrumented if you asked
|
|
273
|
+
* for observability", and only the second is free.
|
|
274
|
+
*
|
|
275
|
+
* **A module that reads this port contributes a no-op member of its own**, the
|
|
276
|
+
* way `otel()` does for `Instrumentations`: a collector depending on a set port
|
|
277
|
+
* NOTHING provides is an unmet dependency, at plan time and in `Needs` alike.
|
|
278
|
+
* Several no-ops in one graph cost a call each and nothing else.
|
|
279
|
+
*/
|
|
280
|
+
declare class Observers extends Observers_base<(operation: Operation) => Settle> {}
|
|
281
|
+
/** The no-op member every reader of {@link Observers} contributes, so the set is never empty. */
|
|
282
|
+
declare const noObserver: () => Settle;
|
|
283
|
+
/**
|
|
284
|
+
* Run every observer around one operation, and hand back the one finisher that
|
|
285
|
+
* settles them all.
|
|
286
|
+
*
|
|
287
|
+
* Observers are started in order and settled in order, and the finisher is
|
|
288
|
+
* **guarded**: a second call is dropped. "Called exactly once" is therefore a
|
|
289
|
+
* property of this function rather than a rule each starter re-keeps — which
|
|
290
|
+
* matters because the settling call sites are error paths, where a `tap` and a
|
|
291
|
+
* `tapFailure` on the same chain, or a retry, is exactly the shape that fires
|
|
292
|
+
* twice and doubles a failure count.
|
|
293
|
+
*/
|
|
294
|
+
declare const observe: (observers: readonly ((operation: Operation) => Settle)[], operation: Operation) => Settle;
|
|
295
|
+
//#endregion
|
|
86
296
|
//#region src/clock.d.ts
|
|
87
297
|
type Clock = {
|
|
88
298
|
readonly now: () => number;
|
|
@@ -305,121 +515,6 @@ type KernelEvent = {
|
|
|
305
515
|
type EventSink = (event: KernelEvent) => void;
|
|
306
516
|
declare const stderrSink: EventSink;
|
|
307
517
|
//#endregion
|
|
308
|
-
//#region src/observability.d.ts
|
|
309
|
-
/**
|
|
310
|
-
* The severity of one line, and the whole of the set: six levels, ordered, with
|
|
311
|
-
* no caller-defined additions — which is what lets `LOG_LEVEL` be validated at
|
|
312
|
-
* startup and `isEnabled` be a comparison rather than a lookup.
|
|
313
|
-
*/
|
|
314
|
-
type Level = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
315
|
-
/** The levels in order, least severe first — what `isEnabled` compares through. */
|
|
316
|
-
declare const LEVELS: readonly Level[];
|
|
317
|
-
/**
|
|
318
|
-
* What a line, a span or a measurement carries besides itself: a flat record of
|
|
319
|
-
* scalars.
|
|
320
|
-
*
|
|
321
|
-
* Flat and scalar on purpose. A nested object is where a field's name stops
|
|
322
|
-
* being stable, and an `unknown` value is where a logger starts stringifying
|
|
323
|
-
* whatever it is handed — which is how a log call becomes the thing that
|
|
324
|
-
* throws. A failure has a channel of its own, `cause`.
|
|
325
|
-
*/
|
|
326
|
-
type Attributes = Readonly<Record<string, string | number | boolean | undefined>>;
|
|
327
|
-
/**
|
|
328
|
-
* Every method takes the same three arguments in the same order:
|
|
329
|
-
* `(message, attributes?, cause?)`.
|
|
330
|
-
*
|
|
331
|
-
* Uniform on purpose. A failure is not a property of severity — an `info` line
|
|
332
|
-
* reporting a recovered fault carries one too — so every level can take a
|
|
333
|
-
* `cause`, at the cost of `logger.error("boom", undefined, cause)` for a
|
|
334
|
-
* failure with nothing else to say.
|
|
335
|
-
*/
|
|
336
|
-
type LoggerService = {
|
|
337
|
-
readonly log: (level: Level, message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
338
|
-
readonly trace: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
339
|
-
readonly debug: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
340
|
-
readonly info: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
341
|
-
readonly warn: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
342
|
-
/** `cause` is the failure itself — an `Error`, an `unthrown` `Err`'s error, a rejected value. */
|
|
343
|
-
readonly error: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
344
|
-
readonly fatal: (message: string, attributes?: Attributes, cause?: unknown) => void;
|
|
345
|
-
/** A logger carrying `attributes` on every line it writes, on top of this one's. Never mutates this one. */
|
|
346
|
-
readonly with: (attributes: Attributes) => LoggerService;
|
|
347
|
-
/** Whether a line at `level` would be written — for a payload expensive enough to be worth not building. */
|
|
348
|
-
readonly isEnabled: (level: Level) => boolean;
|
|
349
|
-
};
|
|
350
|
-
declare const Logger_base: import("@btravstack/di").PortClass<"Logger">;
|
|
351
|
-
/**
|
|
352
|
-
* The application's logger, as a port — declared here and implemented in
|
|
353
|
-
* `@btravstack/observability`, because a contract every package may depend on
|
|
354
|
-
* has to be reachable without installing an implementation, and because the
|
|
355
|
-
* correlation an implementation stamps per line is the kernel's own ambient
|
|
356
|
-
* record.
|
|
357
|
-
*
|
|
358
|
-
* An implementation must not throw: a logger that throws turns an observability
|
|
359
|
-
* problem into an outage. Synchronous `void`, not an `AsyncResult` — thesis 6's
|
|
360
|
-
* one deliberate exemption, since a lost line is not a modeled error.
|
|
361
|
-
*/
|
|
362
|
-
declare class Logger extends Logger_base<LoggerService> {}
|
|
363
|
-
/** A span's outcome: unset until something says otherwise, then `ok` or `error`. */
|
|
364
|
-
declare const SPAN_STATUS: {
|
|
365
|
-
readonly unset: 0;
|
|
366
|
-
readonly ok: 1;
|
|
367
|
-
readonly error: 2;
|
|
368
|
-
};
|
|
369
|
-
/** The three codes {@link SPAN_STATUS} names, as the type a {@link Span} takes. */
|
|
370
|
-
type SpanStatusCode = (typeof SPAN_STATUS)[keyof typeof SPAN_STATUS];
|
|
371
|
-
/**
|
|
372
|
-
* One span, narrowed to what a framework package does with it: label it, say
|
|
373
|
-
* how it ended, end it. The numbers are OpenTelemetry's own, so an OTel span
|
|
374
|
-
* satisfies this structurally with no translation in between.
|
|
375
|
-
*/
|
|
376
|
-
type Span = {
|
|
377
|
-
readonly setAttributes: (attributes: Attributes) => unknown;
|
|
378
|
-
readonly setStatus: (status: {
|
|
379
|
-
readonly code: SpanStatusCode;
|
|
380
|
-
readonly message?: string;
|
|
381
|
-
}) => unknown;
|
|
382
|
-
readonly end: () => void;
|
|
383
|
-
};
|
|
384
|
-
/** What a {@link Tracer} does: start a span, by name. */
|
|
385
|
-
type TracerService = {
|
|
386
|
-
readonly startSpan: (name: string) => Span;
|
|
387
|
-
};
|
|
388
|
-
declare const Tracer_base: import("@btravstack/di").PortClass<"Tracer">;
|
|
389
|
-
/**
|
|
390
|
-
* The application's tracer, as a port — declared without naming OpenTelemetry,
|
|
391
|
-
* because a port typed as a vendor's type points the dependency arrow outwards
|
|
392
|
-
* and makes the whole family install that vendor.
|
|
393
|
-
*/
|
|
394
|
-
declare class Tracer extends Tracer_base<TracerService> {}
|
|
395
|
-
/** A monotonic count. */
|
|
396
|
-
type Counter = {
|
|
397
|
-
readonly add: (value: number, attributes?: Attributes) => void;
|
|
398
|
-
};
|
|
399
|
-
/** A distribution of measurements. */
|
|
400
|
-
type Histogram = {
|
|
401
|
-
readonly record: (value: number, attributes?: Attributes) => void;
|
|
402
|
-
};
|
|
403
|
-
/**
|
|
404
|
-
* What a {@link Meter} does: mint the two instruments a framework package needs
|
|
405
|
-
* — it counts what happened and measures how long it took. A gauge is something
|
|
406
|
-
* an application declares about its own domain, reaching the vendor's meter for
|
|
407
|
-
* it as it would any other adapter.
|
|
408
|
-
*/
|
|
409
|
-
type MeterService = {
|
|
410
|
-
readonly createCounter: (name: string, options?: {
|
|
411
|
-
readonly description?: string;
|
|
412
|
-
readonly unit?: string;
|
|
413
|
-
}) => Counter;
|
|
414
|
-
readonly createHistogram: (name: string, options?: {
|
|
415
|
-
readonly description?: string;
|
|
416
|
-
readonly unit?: string;
|
|
417
|
-
}) => Histogram;
|
|
418
|
-
};
|
|
419
|
-
declare const Meter_base: import("@btravstack/di").PortClass<"Meter">;
|
|
420
|
-
/** The application's meter, as a port. See {@link Tracer} for why it does not name a vendor. */
|
|
421
|
-
declare class Meter extends Meter_base<MeterService> {}
|
|
422
|
-
//#endregion
|
|
423
518
|
//#region src/phase.d.ts
|
|
424
519
|
type Phase = "building" | "starting" | "serving" | "draining" | "stopping" | "exited";
|
|
425
520
|
//#endregion
|
|
@@ -553,4 +648,4 @@ declare const start: <X, E, UnitX = never, UnitNeeds = never>(module: Module<X,
|
|
|
553
648
|
*/
|
|
554
649
|
declare const runMain: <X, E, UnitX = never, UnitNeeds = never>(module: Module<X, E, Scope | Env> & StartGate<X, UnitNeeds>, options?: StartOptions<UnitX, UnitNeeds>, exit?: (code: number) => void) => Promise<void>;
|
|
555
650
|
//#endregion
|
|
556
|
-
export { type Attributes, type Clock, type ComponentHealth, type Counter, type DrainReport, type EventSink, type ExitReport, type HealthCheck, HealthCheckFailed, HealthChecks, type HealthReport, type Histogram, type InstrumentationLoader, Instrumentations, type KernelEvent, LEVELS, type Level, Logger, type LoggerService, Meter, type MeterService, type Phase, type RunUnit, type RunningApp, type Runtime, type RuntimeHost, type RuntimeInfoOf, RuntimePort, RuntimeStartFailed, SPAN_STATUS, type Serving, type Span, type SpanStatusCode, type StartGate, type StartOptions, type TeardownError, Tracer, type TracerService, type UnitMeta, type UnitRecord, type UnitRegistry, type UnitWork, currentUnit, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock };
|
|
651
|
+
export { type Attributes, type Clock, type ComponentHealth, type Counter, type DrainReport, type EventSink, type ExitReport, type HealthCheck, HealthCheckFailed, HealthChecks, type HealthReport, type Histogram, type InstrumentationLoader, Instrumentations, type KernelEvent, LEVELS, type Level, Logger, type LoggerService, Meter, type MeterService, Observers, type Operation, type Phase, type RunUnit, type RunningApp, type Runtime, type RuntimeHost, type RuntimeInfoOf, RuntimePort, RuntimeStartFailed, SPAN_STATUS, type Serving, type Settle, type Settled, type Span, type SpanStatusCode, type StartGate, type StartOptions, type TeardownError, Tracer, type TracerService, type UnitMeta, type UnitRecord, type UnitRegistry, type UnitWork, currentUnit, noObserver, observe, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock };
|
package/dist/index.mjs
CHANGED
|
@@ -60,6 +60,50 @@ const runHealthChecks = (checks) => allAsync(checks.map((health) => OkAsync().fl
|
|
|
60
60
|
components
|
|
61
61
|
}));
|
|
62
62
|
//#endregion
|
|
63
|
+
//#region src/observation.ts
|
|
64
|
+
/**
|
|
65
|
+
* The set port a starter contributes its observability to, and reads whole.
|
|
66
|
+
*
|
|
67
|
+
* **Called at the START and answering a finisher**, rather than told about a
|
|
68
|
+
* finished operation. That is what lets an observer open a span before the work
|
|
69
|
+
* and end it after — a span reconstructed afterwards from a duration is not the
|
|
70
|
+
* parent of anything that ran inside it.
|
|
71
|
+
*
|
|
72
|
+
* A set port rather than a `Meter` in every starter's `needs`: an observer
|
|
73
|
+
* package an application never composed contributes nothing, and a starter
|
|
74
|
+
* costs a graph no ports to have observability available. That is the
|
|
75
|
+
* difference between "instrumented by default" and "instrumented if you asked
|
|
76
|
+
* for observability", and only the second is free.
|
|
77
|
+
*
|
|
78
|
+
* **A module that reads this port contributes a no-op member of its own**, the
|
|
79
|
+
* way `otel()` does for `Instrumentations`: a collector depending on a set port
|
|
80
|
+
* NOTHING provides is an unmet dependency, at plan time and in `Needs` alike.
|
|
81
|
+
* Several no-ops in one graph cost a call each and nothing else.
|
|
82
|
+
*/
|
|
83
|
+
var Observers = class extends Port.many("Observers") {};
|
|
84
|
+
/** The no-op member every reader of {@link Observers} contributes, so the set is never empty. */
|
|
85
|
+
const noObserver = () => () => {};
|
|
86
|
+
/**
|
|
87
|
+
* Run every observer around one operation, and hand back the one finisher that
|
|
88
|
+
* settles them all.
|
|
89
|
+
*
|
|
90
|
+
* Observers are started in order and settled in order, and the finisher is
|
|
91
|
+
* **guarded**: a second call is dropped. "Called exactly once" is therefore a
|
|
92
|
+
* property of this function rather than a rule each starter re-keeps — which
|
|
93
|
+
* matters because the settling call sites are error paths, where a `tap` and a
|
|
94
|
+
* `tapFailure` on the same chain, or a retry, is exactly the shape that fires
|
|
95
|
+
* twice and doubles a failure count.
|
|
96
|
+
*/
|
|
97
|
+
const observe = (observers, operation) => {
|
|
98
|
+
const settlers = observers.map((observer) => observer(operation));
|
|
99
|
+
let done = false;
|
|
100
|
+
return (settled) => {
|
|
101
|
+
if (done) return;
|
|
102
|
+
done = true;
|
|
103
|
+
for (const settle of settlers) settle(settled);
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
//#endregion
|
|
63
107
|
//#region src/clock.ts
|
|
64
108
|
const systemClock = {
|
|
65
109
|
now: () => Date.now(),
|
|
@@ -645,4 +689,4 @@ const runMain = async (module, options = {}, exit = (code) => {
|
|
|
645
689
|
await awaitExit(start(module, options), exit);
|
|
646
690
|
};
|
|
647
691
|
//#endregion
|
|
648
|
-
export { HealthCheckFailed, HealthChecks, Instrumentations, LEVELS, Logger, Meter, RuntimePort, RuntimeStartFailed, SPAN_STATUS, Tracer, currentUnit, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock };
|
|
692
|
+
export { HealthCheckFailed, HealthChecks, Instrumentations, LEVELS, Logger, Meter, Observers, RuntimePort, RuntimeStartFailed, SPAN_STATUS, Tracer, currentUnit, noObserver, observe, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "The application kernel: boot a di module into a running process, and stop it without losing work",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"application",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"typescript": "7.0.2",
|
|
54
54
|
"unthrown": "5.7.0",
|
|
55
55
|
"vitest": "4.1.11",
|
|
56
|
-
"@btravstack/config": "0.
|
|
57
|
-
"@btravstack/di": "0.
|
|
56
|
+
"@btravstack/config": "0.10.0",
|
|
57
|
+
"@btravstack/di": "0.10.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"unthrown": "^5.0.0",
|
|
61
|
-
"@btravstack/config": "^0.
|
|
62
|
-
"@btravstack/di": "^0.
|
|
61
|
+
"@btravstack/config": "^0.10.0",
|
|
62
|
+
"@btravstack/di": "^0.10.0"
|
|
63
63
|
},
|
|
64
64
|
"engines": {
|
|
65
65
|
"node": ">=22"
|