@mark1russell7/service 0.5.13 → 0.5.14

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/README.md CHANGED
@@ -10,6 +10,7 @@ This release has the declaration, its export, the law, the health runtime, the l
10
10
  - **No defaults.** The runtime has no defaults of its own. Thus `describe()` refuses a service that does not declare its readiness, its liveness, its startup budget, its shutdown phases or its cooperative window. It also refuses a service that does not declare what a repeat signal does. The error tells which. `start()` also refuses a check without its function.
11
11
  - **One timing shape.** Each policy has a `Timing`: a debounce of polls, or a window of time. Each converts to the other (`asWindow`, `asDebounce`). `failSpan` and `recoverSpan` give the earliest and the latest time to detect a change. At 3 polls of 5 s ± 20 %, a latch fails in 8–18 s on an error, and in 12–22 s on a 4 s timeout.
12
12
  - **Checks.** A check names the step of the connection that it examines (`dial`, `round-trip`, `checkout` and others). It also tells if it uses the connection of the application, and which policy it controls. The policy has no default.
13
+ - **The catalog of a client.** The checks of each client name the steps that they gate: `steps: { "round-trip": "readiness" }`. With `catalog: true`, the client also gives a check for each other step that it has. These checks gate nothing and poll each 30 s. A nominated step that the client does not have then fails the attach. The name of each check is the pair of the component and the step (`db.round-trip`).
13
14
  - **Idempotency for each operation.** One dependency has the two kinds of operation. A Redis GET is safe to send again. An INCR that goes again after a timeout counts two times. Thus each operation of a dependency, and each route of a controller, declares if it can go again: `true`, `false`, or keyed.
14
15
  - **Keyed operations.** A keyed operation has the window that the server keeps its key for. `mayReissue` answers from the declaration of an operation. An operation without a declaration does not go again.
15
16
  - **The law.** `evaluate` and `verdictOf` decide one rule of the timer-ordering law on one binding. A rule is data: linear inequalities over `symbol@hop`, with a margin. A verdict is `holds`, `violated`, `unbounded` (it holds only because a timer is absent) or `depends` (nothing is bound).
@@ -29,6 +30,7 @@ The format of a description is `mark1russell7.service/1`.
29
30
 
30
31
  - **Checks on their own loops.** Each check polls on a fixed delay from the start of each poll, with its jitter. Each poll has a timeout, and the runtime aborts the signal of the check at that timeout. An answer is `"ok"`, `"error"` or `"indeterminate"`. A throw is an error with a flag. An indeterminate poll measured nothing, thus it moves no streak.
31
32
  - **A pending use is not started again.** While a use of a check is pending, each later poll fails at once with the same cause. Thus a check that hangs fails a debounce of two polls or more as fast as an error does (`failSpan(timing, poll, "hang")`).
33
+ - **The metrics.** `health.metrics()` gives the counts of the runtime, and starts no check. For each check, it counts each outcome, each throw and each forced failure. Its histogram counts the durations of the polls. The bounds are fractions of the timeout of the poll, and a poll that timed out goes into `timedOut`. Its last 8 episodes of failure each go from the first error to the next pass. The cause of an episode has no user, password or token (`redact`). It also counts each transition of the machines of the runtime.
32
34
  - **A latch for each check that gates a policy.** A debounce counts polls in sequence. A window measures time on the monotonic clock from the first failure of a sequence. A readiness latch starts tripped, thus readiness is earned. A liveness latch starts released.
33
35
  - **The start.** `start()` makes one warm-up sweep of each check, bounded by each timeout, and then starts the loops. The startup probe fails until the sweep ends. `svc.health` gives the runtime from the start of `start()`, thus the probes can answer during the sweep. The verdict says when the sweep outlasts the startup budget.
34
36
  - **The verdicts.** `health.verdict()` gives started, draining, ready, alive, each controller, each dependency and each check. The service is ready when it started, it does not drain, and each readiness latch is released. A controller is ready when its own dependencies are ready. Thus a failed dependency fails only the controllers that need it.
@@ -1,6 +1,6 @@
1
1
  import { n as Clock } from "./clock-mVqhXD0b.js";
2
2
  import { c as LimiterSpec } from "./policy-G4QJ1ZZq.js";
3
- import { B as Gate, D as Outcome, E as CheckFn, F as CooperativeFloor, G as ServiceDescription, H as Phase, I as Coupling, L as Dependency, N as Check, R as ExitCodes, T as StartOptions, V as MessageTrigger, W as RepeatSignal, Y as Poll, Z as Timing, d as TransportImpl, ht as ProcessTrigger, k as Trace, lt as ExitTrigger, n as HealthServer, o as PhaseWork, r as Lifecycle, rt as OperationOf, s as RunOptions, t as DependencyHandle, wt as Operation, x as Health } from "./lifecycle-fgYfxrHK.js";
3
+ import { B as Check, E as CheckFn, G as ExitCodes, H as CooperativeFloor, I as Trace, J as MessageTrigger, P as Outcome, Q as ServiceDescription, St as ProcessTrigger, T as StartOptions, U as Coupling, W as Dependency, Y as Phase, Z as RepeatSignal, d as TransportImpl, gt as ExitTrigger, it as Timing, jt as Operation, n as HealthServer, nt as Poll, o as PhaseWork, q as Gate, r as Lifecycle, s as RunOptions, t as DependencyHandle, ut as OperationOf, x as Health } from "./lifecycle-CMedxaQd.js";
4
4
  import { Extent, Quantity } from "@mark1russell7/quantity";
5
5
  //#region src/profiles.d.ts
6
6
  /**
@@ -208,4 +208,4 @@ interface ServiceBuilder {
208
208
  declare const service: (name: string) => ServiceBuilder;
209
209
  //#endregion
210
210
  export { WatchdogSpec as a, Profile as c, kubernetes as d, localDev as f, Watchdog as i, daemon as l, ServiceBuilder as n, watchdog as o, service as r, PodTiming as s, ControllerBuilder as t, ephemeral as u };
211
- //# sourceMappingURL=builder-DawJhq4p.d.ts.map
211
+ //# sourceMappingURL=builder-CsSO4LzB.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { t as Backoff } from "./backoff-B-jGWVDm.js";
2
2
  import { n as Clock } from "./clock-mVqhXD0b.js";
3
- import { B as Gate, E as CheckFn, G as ServiceDescription, K as Step, L as Dependency, M as Recorder, N as Check, Y as Poll, t as DependencyHandle, wt as Operation } from "./lifecycle-fgYfxrHK.js";
4
- import { n as ServiceBuilder } from "./builder-DawJhq4p.js";
3
+ import { $ as Step, B as Check, E as CheckFn, Q as ServiceDescription, W as Dependency, jt as Operation, nt as Poll, q as Gate, t as DependencyHandle, z as Recorder } from "./lifecycle-CMedxaQd.js";
4
+ import { n as ServiceBuilder } from "./builder-CsSO4LzB.js";
5
5
  import { Extent, Quantity } from "@mark1russell7/quantity";
6
6
  //#region src/client/meets.d.ts
7
7
  /** A situation is what the connection did when the failure started: a command in flight, or idle. */
@@ -89,6 +89,13 @@ interface CheckOptions<S extends Step = ClientStep> {
89
89
  readonly poll: Poll;
90
90
  /** For each step, the policy that its check controls, or null for a check that only reports. A step that is absent has no check. */
91
91
  readonly steps: Partial<Record<S, Gate | null>>;
92
+ /**
93
+ * With a catalog, the client gives a check for each step that it has (B19). The steps of `steps` are
94
+ * the nominated checks. Each other step reports only, and polls at the catalog poll. `true` is a poll
95
+ * of 30 s with the jitter and the timeout of `poll`. A nominated step that the client has no check
96
+ * for then fails the attach. Without a catalog, the client gives only the steps of `steps`.
97
+ */
98
+ readonly catalog?: true | Poll | undefined;
92
99
  }
93
100
  /**
94
101
  * These are the knobs that each client has, by the names of the ordering law. Each driver adds its
@@ -223,4 +230,4 @@ declare const retryProblems: (name: string, knobs: RetryKnobs) => string[];
223
230
  declare const STARTING_POLL: Poll;
224
231
  //#endregion
225
232
  export { MANIFESTATIONS as C, firstDetector as D, Situation as E, unwatched as O, Detector as S, Meeting as T, Sent as _, ClientKnobs as a, retryProblems as b, ClientSpec as c, Factory as d, ReadyWait as f, SendOptions as g, STARTING_POLL as h, ClientJoin as i, ClientStep as l, RetrySchedule as m, CheckOptions as n, ClientOptions as o, RetryKnobs as p, Client as r, ClientSocket as s, Attempt as t, DialFn as u, readyWait as v, Manifestation as w, retrySchedule as x, retryBackoff as y };
226
- //# sourceMappingURL=core-BPLRDcUJ.d.ts.map
233
+ //# sourceMappingURL=core-D8iXeoE4.d.ts.map
@@ -1,4 +1,4 @@
1
- import "./core-BPLRDcUJ.js";
1
+ import "./core-D8iXeoE4.js";
2
2
  //#region src/client/dial.d.ts
3
3
  /**
4
4
  * A judge reads the bytes that the server sent so far. It gives null for a good answer, an error for
@@ -13,4 +13,4 @@ type Judge = (received: Buffer) => Error | null | "more";
13
13
  declare const statusLineJudge: (accept: (status: number, line: string) => Error | null) => Judge;
14
14
  //#endregion
15
15
  export { statusLineJudge as n, Judge as t };
16
- //# sourceMappingURL=dial-DA8vJfh2.d.ts.map
16
+ //# sourceMappingURL=dial-DJolDmOK.d.ts.map
package/build/grpc.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { Y as Poll, wt as Operation } from "./lifecycle-fgYfxrHK.js";
2
- import { C as MANIFESTATIONS, D as firstDetector, E as Situation, O as unwatched, S as Detector, T as Meeting, _ as Sent, a as ClientKnobs, b as retryProblems, c as ClientSpec, d as Factory, f as ReadyWait, g as SendOptions, h as STARTING_POLL, i as ClientJoin, l as ClientStep, n as CheckOptions, o as ClientOptions, p as RetryKnobs, r as Client, s as ClientSocket, t as Attempt, u as DialFn, v as readyWait, w as Manifestation } from "./core-BPLRDcUJ.js";
3
- import { t as Judge } from "./dial-DA8vJfh2.js";
1
+ import { jt as Operation, nt as Poll } from "./lifecycle-CMedxaQd.js";
2
+ import { C as MANIFESTATIONS, D as firstDetector, E as Situation, O as unwatched, S as Detector, T as Meeting, _ as Sent, a as ClientKnobs, b as retryProblems, c as ClientSpec, d as Factory, f as ReadyWait, g as SendOptions, h as STARTING_POLL, i as ClientJoin, l as ClientStep, n as CheckOptions, o as ClientOptions, p as RetryKnobs, r as Client, s as ClientSocket, t as Attempt, u as DialFn, v as readyWait, w as Manifestation } from "./core-D8iXeoE4.js";
3
+ import { t as Judge } from "./dial-DJolDmOK.js";
4
4
  import { Extent, Quantity } from "@mark1russell7/quantity";
5
5
  import { Machine } from "@mark1russell7/algebra";
6
6
  //#region src/grpc/connection.d.ts
package/build/grpc.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { realClock } from "./clock.js";
2
2
  import { i as heartbeatDetection } from "./timing-DGRi3XCI.js";
3
- import { B as zeroProblems, C as notSent, D as readyWait, F as timeProblems, G as immediate, H as NEVER, L as wholeProblems, M as sendWithReissue, N as socketEnds, P as stepChecks, R as withinBound, S as ms, U as WHAT, V as MANIFESTATIONS, W as firstDetector, X as unwatched, Y as kernelGiveUp, _ as inFlight, a as helloDial, b as message, c as ageProblems, d as clientProblems, f as clientRecorder, g as idleCloseProblem, h as finiteMs, i as authority, j as roundTripCheck, k as retryProblems, l as attachClient, m as dialCheck, n as closeTimer, r as transition, s as STARTING_POLL, t as clientKnob, u as clientBounds, w as operationTable, y as localStateCheck, z as wrapperTimerProblems } from "./machine-BhTRu3u9.js";
3
+ import { B as zeroProblems, C as notSent, D as readyWait, F as timeProblems, G as immediate, H as NEVER, L as wholeProblems, M as sendWithReissue, N as socketEnds, P as stepChecks, R as withinBound, S as ms, U as WHAT, V as MANIFESTATIONS, W as firstDetector, X as unwatched, Y as kernelGiveUp, _ as inFlight, a as helloDial, b as message, c as ageProblems, d as clientProblems, f as clientRecorder, g as idleCloseProblem, h as finiteMs, i as authority, j as roundTripCheck, k as retryProblems, l as attachClient, m as dialCheck, n as closeTimer, r as transition, s as STARTING_POLL, t as clientKnob, u as clientBounds, w as operationTable, y as localStateCheck, z as wrapperTimerProblems } from "./machine-DZMb_QEU.js";
4
4
  //#region src/grpc/connection.ts
5
5
  /** These states are in the sequence of the enum of grpc-js: the index of a state is its value. */
6
6
  const GRPC_STATES = [
@@ -1,6 +1,6 @@
1
- import { Y as Poll, wt as Operation } from "./lifecycle-fgYfxrHK.js";
2
- import { C as MANIFESTATIONS, D as firstDetector, E as Situation, O as unwatched, S as Detector, T as Meeting, _ as Sent, a as ClientKnobs, c as ClientSpec, d as Factory, g as SendOptions, h as STARTING_POLL, i as ClientJoin, l as ClientStep, n as CheckOptions, o as ClientOptions, r as Client, s as ClientSocket, t as Attempt, u as DialFn, w as Manifestation } from "./core-BPLRDcUJ.js";
3
- import { t as Judge } from "./dial-DA8vJfh2.js";
1
+ import { jt as Operation, nt as Poll } from "./lifecycle-CMedxaQd.js";
2
+ import { C as MANIFESTATIONS, D as firstDetector, E as Situation, O as unwatched, S as Detector, T as Meeting, _ as Sent, a as ClientKnobs, c as ClientSpec, d as Factory, g as SendOptions, h as STARTING_POLL, i as ClientJoin, l as ClientStep, n as CheckOptions, o as ClientOptions, r as Client, s as ClientSocket, t as Attempt, u as DialFn, w as Manifestation } from "./core-D8iXeoE4.js";
3
+ import { t as Judge } from "./dial-DJolDmOK.js";
4
4
  import { Extent, Quantity } from "@mark1russell7/quantity";
5
5
  import { Machine } from "@mark1russell7/algebra";
6
6
  //#region src/http-client/connection.d.ts
@@ -1,5 +1,5 @@
1
1
  import { realClock } from "./clock.js";
2
- import { B as zeroProblems, C as notSent, E as poolProblems, F as timeProblems, G as immediate, I as trackWithTimer, J as keepaliveSilent, K as keepaliveAnswered, L as wholeProblems, M as sendWithReissue, N as socketEnds, P as stepChecks, R as withinBound, S as ms, U as WHAT, V as MANIFESTATIONS, W as firstDetector, X as unwatched, Y as kernelGiveUp, _ as inFlight, a as helloDial, b as message, c as ageProblems, d as clientProblems, f as clientRecorder, g as idleCloseProblem, i as authority, j as roundTripCheck, l as attachClient, m as dialCheck, n as closeTimer, o as statusLineJudge, q as keepaliveReset, r as transition, s as STARTING_POLL, t as clientKnob, u as clientBounds, v as keepaliveOnProblems, w as operationTable, y as localStateCheck, z as wrapperTimerProblems } from "./machine-BhTRu3u9.js";
2
+ import { B as zeroProblems, C as notSent, E as poolProblems, F as timeProblems, G as immediate, I as trackWithTimer, J as keepaliveSilent, K as keepaliveAnswered, L as wholeProblems, M as sendWithReissue, N as socketEnds, P as stepChecks, R as withinBound, S as ms, U as WHAT, V as MANIFESTATIONS, W as firstDetector, X as unwatched, Y as kernelGiveUp, _ as inFlight, a as helloDial, b as message, c as ageProblems, d as clientProblems, f as clientRecorder, g as idleCloseProblem, i as authority, j as roundTripCheck, l as attachClient, m as dialCheck, n as closeTimer, o as statusLineJudge, q as keepaliveReset, r as transition, s as STARTING_POLL, t as clientKnob, u as clientBounds, v as keepaliveOnProblems, w as operationTable, y as localStateCheck, z as wrapperTimerProblems } from "./machine-DZMb_QEU.js";
3
3
  //#region src/http-client/connection.ts
4
4
  /**
5
5
  * This factory makes an `http.Agent` of Node. It loads node:http only when a client connects.
package/build/http.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as Clock } from "./clock-mVqhXD0b.js";
2
2
  import { d as Refused } from "./policy-G4QJ1ZZq.js";
3
- import { d as TransportImpl, m as RequestEnd, n as HealthServer, r as Lifecycle, v as RequestStream, w as ProbeKind, x as Health, y as RequestWork } from "./lifecycle-fgYfxrHK.js";
3
+ import { d as TransportImpl, m as RequestEnd, n as HealthServer, r as Lifecycle, v as RequestStream, w as ProbeKind, x as Health, y as RequestWork } from "./lifecycle-CMedxaQd.js";
4
4
  import { Extent, Quantity } from "@mark1russell7/quantity";
5
5
  import { ListenOptions } from "node:net";
6
6
  import { IncomingMessage, Server, ServerEventMap, ServerResponse } from "node:http";
package/build/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { a as backoffDelay, i as backoffBand, n as DelayBand, o as backoffProblems, r as Jitter, t as Backoff } from "./backoff-B-jGWVDm.js";
2
2
  import { a as realClock, i as platformClock, n as Clock, r as PlatformClockOptions, t as Cancel } from "./clock-mVqhXD0b.js";
3
3
  import { S as retryAfterSeconds, _ as freeAt, a as LimitRule, b as limiterProblems, c as LimiterSpec, d as Refused, f as SlidingWindow, g as decide, h as TokenBucket, i as LimitPolicy, l as LimiterStoreSpec, m as TakeStep, n as Decision, o as Limiter, p as StoreFailure, r as KeyState, s as LimiterEviction, t as Allowed, u as RefusalCause, v as idleSpan, x as longestRetryAfter, y as limitRule } from "./policy-G4QJ1ZZq.js";
4
- import { $ as asWindow, A as LifecycleEvent, B as Gate, C as ProbeAnswer, Ct as Idempotency, D as Outcome, E as CheckFn, F as CooperativeFloor, G as ServiceDescription, H as Phase, I as Coupling, J as FailsBy, K as Step, L as Dependency, M as Recorder, N as Check, O as Result, P as Controller, Q as asDebounce, R as ExitCodes, S as HealthVerdict, St as processPortOf, T as StartOptions, Tt as mayReissue, U as ProgressMarks, V as MessageTrigger, W as RepeatSignal, X as Span, Y as Poll, Z as Timing, _ as RequestOptions, _t as isExitCode, a as PhaseContext, at as RouteRequest, b as CheckVerdict, bt as matchesMessage, c as STAGES, ct as ExitSignal, d as TransportImpl, dt as MessageShape, et as failSpan, f as cooperativeAbortAt, ft as PROCESS_TRIGGERS, g as RequestFn, gt as embeddedProcess, h as RequestEvent, ht as ProcessTrigger, i as LifecycleState, it as RoutePattern, j as MachineName, l as ShutdownOptions, lt as ExitTrigger, m as RequestEnd, mt as ProcessPort, n as HealthServer, nt as recoverSpan, o as PhaseWork, ot as EXIT_SIGNALS, p as stageRank, pt as ProcessHost, q as Transport, r as Lifecycle, rt as OperationOf, s as RunOptions, st as EmbeddedProcess, t as DependencyHandle, tt as heartbeatDetection, u as Stage, ut as HostStream, v as RequestStream, vt as isExitSignal, w as ProbeKind, wt as Operation, x as Health, xt as nodeProcess, y as RequestWork, yt as isMessageShape, z as FORMAT } from "./lifecycle-fgYfxrHK.js";
5
- import { a as WatchdogSpec, c as Profile, d as kubernetes, f as localDev, i as Watchdog, l as daemon, n as ServiceBuilder, o as watchdog, r as service, s as PodTiming, t as ControllerBuilder, u as ephemeral } from "./builder-DawJhq4p.js";
4
+ import { $ as Step, A as EPISODES_KEPT, At as Idempotency, B as Check, C as ProbeAnswer, Ct as embeddedProcess, D as CheckMetrics, Dt as matchesMessage, E as CheckFn, Et as isMessageShape, F as Result, G as ExitCodes, H as CooperativeFloor, J as MessageTrigger, K as FORMAT, L as LifecycleEvent, M as HealthMetrics, Mt as mayReissue, N as redact, O as DURATION_FRACTIONS, Ot as nodeProcess, P as Outcome, Q as ServiceDescription, R as MachineName, S as HealthVerdict, St as ProcessTrigger, T as StartOptions, Tt as isExitSignal, U as Coupling, V as Controller, W as Dependency, X as ProgressMarks, Y as Phase, Z as RepeatSignal, _ as RequestOptions, _t as HostStream, a as PhaseContext, at as asDebounce, b as CheckVerdict, bt as ProcessHost, c as STAGES, ct as heartbeatDetection, d as TransportImpl, dt as RoutePattern, et as Transport, f as cooperativeAbortAt, ft as RouteRequest, g as RequestFn, gt as ExitTrigger, h as RequestEvent, ht as ExitSignal, i as LifecycleState, it as Timing, j as Episode, jt as Operation, k as DurationHistogram, kt as processPortOf, l as ShutdownOptions, lt as recoverSpan, m as RequestEnd, mt as EmbeddedProcess, n as HealthServer, nt as Poll, o as PhaseWork, ot as asWindow, p as stageRank, pt as EXIT_SIGNALS, q as Gate, r as Lifecycle, rt as Span, s as RunOptions, st as failSpan, t as DependencyHandle, tt as FailsBy, u as Stage, ut as OperationOf, v as RequestStream, vt as MessageShape, w as ProbeKind, wt as isExitCode, x as Health, xt as ProcessPort, y as RequestWork, yt as PROCESS_TRIGGERS, z as Recorder } from "./lifecycle-CMedxaQd.js";
5
+ import { a as WatchdogSpec, c as Profile, d as kubernetes, f as localDev, i as Watchdog, l as daemon, n as ServiceBuilder, o as watchdog, r as service, s as PodTiming, t as ControllerBuilder, u as ephemeral } from "./builder-CsSO4LzB.js";
6
6
  import { Heartbeat, HeartbeatOptions, detectionTime, heartbeat } from "./heartbeat.js";
7
7
  import { a as keyedLimiter, i as TakeRequest, n as LimiterStore, o as memoryStore, r as MemoryStore, t as KeyedLimiter } from "./store-D5HLgBnW.js";
8
8
  import { a as limiterStateOf, i as limiterMachine, n as SLIDING_WINDOW_MACHINE, r as TOKEN_BUCKET_MACHINE, t as LimiterState } from "./limiter-B0849Vhp.js";
9
- import { a as JobSource, c as QueueKnobs, d as queueDrain, f as redeliverOnce, i as JobSettle, l as RedeliveryPolicy, n as JobContext, o as QueueDrain, p as UnitRequest, r as JobFailure, s as QueueDrainOptions, t as JobAction, u as RequeueCause } from "./queue-snRHK3Jm.js";
9
+ import { a as JobSource, c as QueueKnobs, d as queueDrain, f as redeliverOnce, i as JobSettle, l as RedeliveryPolicy, n as JobContext, o as QueueDrain, p as UnitRequest, r as JobFailure, s as QueueDrainOptions, t as JobAction, u as RequeueCause } from "./queue-CndiCVaY.js";
10
10
  import { Extent, Quantity } from "@mark1russell7/quantity";
11
11
  import { replay } from "@mark1russell7/algebra/replay";
12
12
  import { Machine } from "@mark1russell7/algebra";
@@ -292,5 +292,5 @@ export declare const drainMachine: (description: ServiceDescription) => Machine;
292
292
  /** These are the static machines of the runtime, by the name that their events carry. */
293
293
  export declare const MACHINES: Readonly<Record<Exclude<MachineName, "drain" | "client">, Machine>>;
294
294
  //#endregion
295
- export { type Allowed, type Backoff, type Binding, type Bound, type Cancel, type Check, type CheckFn, type CheckVerdict, type Clock, type Controller, type ControllerBuilder, type CooperativeFloor, type Coupling, type Decision, type DelayBand, type Dependency, type DependencyHandle, EXIT_SIGNALS, type EmbeddedProcess, type Evaluated, type ExitCodes, type ExitSignal, type ExitTrigger, FORMAT, type FailsBy, type Finding, type Gate, type Health, type HealthServer, type HealthVerdict, type Heartbeat, type HeartbeatOptions, type HostStream, type Idempotency, type Inequality, type IterationContext, type Jitter, type JobAction, type JobContext, type JobFailure, type JobSettle, type JobSource, type KeyState, type KeyedLimiter, type Lifecycle, type LifecycleEvent, type LifecycleState, type LimitPolicy, type LimitRule, type Limiter, type LimiterEviction, type LimiterSpec, type LimiterState, type LimiterStore, type LimiterStoreSpec, type MachineName, type MemoryStore, type MessageShape, type MessageTrigger, type Operation, type OperationOf, type Outcome, PROCESS_TRIGGERS, type Phase, type PhaseContext, type PhaseWork, type PlatformClockOptions, type PodTiming, type Poll, type ProbeAnswer, type ProbeKind, type ProcessHost, type ProcessPort, type ProcessTrigger, type Profile, type ProgressMarks, type QueueDrain, type QueueDrainOptions, type QueueKnobs, type Recorder, type RedeliveryPolicy, type RefusalCause, type Refused, type RepeatSignal, type RequestEnd, type RequestEvent, type RequestFn, type RequestOptions, type RequestStream, type RequestWork, type RequeueCause, type Result, type RoutePattern, type RouteRequest, type Rule, type RunOptions, SLIDING_WINDOW_MACHINE, STAGES, type Scenario, type ServiceBuilder, type ServiceDescription, type ShutdownOptions, type Side, type SlidingWindow, type Span, type Stage, type StartOptions, type Step, type StoreFailure, TOKEN_BUCKET_MACHINE, type TakeRequest, type TakeStep, type Term, type Timing, type TokenBucket, type Transport, type TransportImpl, type UnitRequest, type Verdict, type Watchdog, type WatchdogSpec, type WorkerKnobs, type WorkerLoop, type WorkerLoopOptions, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, cooperativeAbortAt, daemon, decide, detectionTime, embeddedProcess, ephemeral, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redeliverOnce, replay, retryAfterSeconds, service, stageRank, watchdog };
295
+ export { type Allowed, type Backoff, type Binding, type Bound, type Cancel, type Check, type CheckFn, type CheckMetrics, type CheckVerdict, type Clock, type Controller, type ControllerBuilder, type CooperativeFloor, type Coupling, DURATION_FRACTIONS, type Decision, type DelayBand, type Dependency, type DependencyHandle, type DurationHistogram, EPISODES_KEPT, EXIT_SIGNALS, type EmbeddedProcess, type Episode, type Evaluated, type ExitCodes, type ExitSignal, type ExitTrigger, FORMAT, type FailsBy, type Finding, type Gate, type Health, type HealthMetrics, type HealthServer, type HealthVerdict, type Heartbeat, type HeartbeatOptions, type HostStream, type Idempotency, type Inequality, type IterationContext, type Jitter, type JobAction, type JobContext, type JobFailure, type JobSettle, type JobSource, type KeyState, type KeyedLimiter, type Lifecycle, type LifecycleEvent, type LifecycleState, type LimitPolicy, type LimitRule, type Limiter, type LimiterEviction, type LimiterSpec, type LimiterState, type LimiterStore, type LimiterStoreSpec, type MachineName, type MemoryStore, type MessageShape, type MessageTrigger, type Operation, type OperationOf, type Outcome, PROCESS_TRIGGERS, type Phase, type PhaseContext, type PhaseWork, type PlatformClockOptions, type PodTiming, type Poll, type ProbeAnswer, type ProbeKind, type ProcessHost, type ProcessPort, type ProcessTrigger, type Profile, type ProgressMarks, type QueueDrain, type QueueDrainOptions, type QueueKnobs, type Recorder, type RedeliveryPolicy, type RefusalCause, type Refused, type RepeatSignal, type RequestEnd, type RequestEvent, type RequestFn, type RequestOptions, type RequestStream, type RequestWork, type RequeueCause, type Result, type RoutePattern, type RouteRequest, type Rule, type RunOptions, SLIDING_WINDOW_MACHINE, STAGES, type Scenario, type ServiceBuilder, type ServiceDescription, type ShutdownOptions, type Side, type SlidingWindow, type Span, type Stage, type StartOptions, type Step, type StoreFailure, TOKEN_BUCKET_MACHINE, type TakeRequest, type TakeStep, type Term, type Timing, type TokenBucket, type Transport, type TransportImpl, type UnitRequest, type Verdict, type Watchdog, type WatchdogSpec, type WorkerKnobs, type WorkerLoop, type WorkerLoopOptions, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, cooperativeAbortAt, daemon, decide, detectionTime, embeddedProcess, ephemeral, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redact, redeliverOnce, replay, retryAfterSeconds, service, stageRank, watchdog };
296
296
  //# sourceMappingURL=index.d.ts.map
package/build/index.js CHANGED
@@ -136,6 +136,114 @@ const latch = (timing, clock, tripped, changed, trace) => {
136
136
  };
137
137
  };
138
138
  //#endregion
139
+ //#region src/metrics.ts
140
+ /** These fractions of the timeout of a poll are the bounds of its histogram. */
141
+ const DURATION_FRACTIONS = [
142
+ .05,
143
+ .1,
144
+ .25,
145
+ .5,
146
+ .75,
147
+ 1
148
+ ];
149
+ /** The runtime keeps this many episodes of each check. */
150
+ const EPISODES_KEPT = 8;
151
+ /**
152
+ * This function removes the secrets that a cause can hold. These are the user and the password of a
153
+ * URL, and the value of a key, for example `password=`. A driver error can hold a connection string.
154
+ */
155
+ const redact = (cause) => cause.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@]*@/gi, "$1***@").replace(/\b(password|passwd|pwd|secret|token|api[_-]?key|auth)(\s*[=:]\s*)("[^"]*"|'[^']*'|[^\s,;&]+)/gi, "$1$2***");
156
+ /** This function makes a meter. */
157
+ const meter = () => {
158
+ const checks = /* @__PURE__ */ new Map();
159
+ const transitions = /* @__PURE__ */ new Map();
160
+ const add = (check) => {
161
+ if (checks.has(check.name)) return;
162
+ const timeout = valueIn(check.poll.timeout, "ms");
163
+ checks.set(check.name, {
164
+ ok: 0,
165
+ error: 0,
166
+ indeterminate: 0,
167
+ thrown: 0,
168
+ forced: 0,
169
+ le: DURATION_FRACTIONS.map((fraction) => fraction * timeout),
170
+ counts: DURATION_FRACTIONS.map(() => 0),
171
+ timedOut: 0,
172
+ episodes: [],
173
+ open: null
174
+ });
175
+ };
176
+ const close = (counted, end) => {
177
+ if (counted.open === null) return null;
178
+ return {
179
+ start: counted.open.start,
180
+ end,
181
+ failures: counted.open.failures,
182
+ cause: counted.open.cause
183
+ };
184
+ };
185
+ const poll = (check, result) => {
186
+ add(check);
187
+ const counted = checks.get(check.name);
188
+ counted[result.outcome] += 1;
189
+ if (result.thrown) counted.thrown += 1;
190
+ if (result.forced) counted.forced += 1;
191
+ if (result.timedOut) counted.timedOut += 1;
192
+ else {
193
+ const at = counted.le.findIndex((bound) => result.took <= bound);
194
+ const bucket = at === -1 ? counted.counts.length - 1 : at;
195
+ counted.counts[bucket] = (counted.counts[bucket] ?? 0) + 1;
196
+ }
197
+ if (result.outcome === "error") {
198
+ if (counted.open === null) counted.open = {
199
+ start: result.at,
200
+ failures: 0,
201
+ cause: result.cause === null ? null : redact(result.cause)
202
+ };
203
+ counted.open.failures += 1;
204
+ } else if (result.outcome === "ok" && counted.open !== null) {
205
+ counted.episodes.push(close(counted, result.at));
206
+ counted.open = null;
207
+ if (counted.episodes.length > 8) counted.episodes.shift();
208
+ }
209
+ };
210
+ const event = (event) => {
211
+ const key = `${event.machine} ${event.instance} ${event.from} -> ${event.to}`;
212
+ transitions.set(key, (transitions.get(key) ?? 0) + 1);
213
+ };
214
+ const metrics = () => {
215
+ const out = {};
216
+ for (const [name, counted] of checks) {
217
+ const open = close(counted, null);
218
+ out[name] = {
219
+ outcomes: {
220
+ ok: counted.ok,
221
+ error: counted.error,
222
+ indeterminate: counted.indeterminate
223
+ },
224
+ thrown: counted.thrown,
225
+ forced: counted.forced,
226
+ durations: {
227
+ le: [...counted.le],
228
+ counts: [...counted.counts],
229
+ timedOut: counted.timedOut
230
+ },
231
+ episodes: open === null ? [...counted.episodes] : [...counted.episodes, open].slice(-8)
232
+ };
233
+ }
234
+ return {
235
+ checks: out,
236
+ transitions: Object.fromEntries(transitions)
237
+ };
238
+ };
239
+ return {
240
+ add,
241
+ poll,
242
+ event,
243
+ metrics
244
+ };
245
+ };
246
+ //#endregion
139
247
  //#region src/poller.ts
140
248
  const message$1 = (error) => error instanceof Error ? error.message : String(error);
141
249
  /**
@@ -304,7 +412,12 @@ const healthRuntime = (description, runs, options = {}) => {
304
412
  let draining = false;
305
413
  let stopped = false;
306
414
  let gate = "closed";
307
- const tracer = (machine, instance) => (from, to, on) => options.record?.({
415
+ const counts = meter();
416
+ const record = (event) => {
417
+ counts.event(event);
418
+ options.record?.(event);
419
+ };
420
+ const tracer = (machine, instance) => (from, to, on) => record({
308
421
  at: clock.now(),
309
422
  machine,
310
423
  instance,
@@ -316,7 +429,7 @@ const healthRuntime = (description, runs, options = {}) => {
316
429
  const moveGate = (to, on, detail) => {
317
430
  const from = gate;
318
431
  gate = to;
319
- options.record?.({
432
+ record({
320
433
  at: clock.now(),
321
434
  machine: "gate",
322
435
  instance: description.name,
@@ -350,6 +463,7 @@ const healthRuntime = (description, runs, options = {}) => {
350
463
  random,
351
464
  forced: () => forced.has(check.name) || check.dependency !== null && forced.has(check.dependency),
352
465
  record: (result) => {
466
+ counts.poll(check, result);
353
467
  made?.record(result);
354
468
  if (result.outcome === "ok" && check.dependency !== null) {
355
469
  if (lateDependencies.delete(check.dependency)) changed();
@@ -359,6 +473,7 @@ const healthRuntime = (description, runs, options = {}) => {
359
473
  })
360
474
  };
361
475
  entries.set(check.name, entry);
476
+ counts.add(check);
362
477
  if (late && !stopped) {
363
478
  entry.poller.pollNow();
364
479
  entry.poller.start(entry.poller.jittered());
@@ -425,6 +540,7 @@ const healthRuntime = (description, runs, options = {}) => {
425
540
  });
426
541
  return {
427
542
  verdict,
543
+ metrics: counts.metrics,
428
544
  probe(kind, controller) {
429
545
  const now = verdict();
430
546
  if (kind === "startup") return answer(kind, now.started);
@@ -3555,6 +3671,6 @@ const MACHINES = {
3555
3671
  watchdog: WATCHDOG_MACHINE
3556
3672
  };
3557
3673
  //#endregion
3558
- export { DECLARATION_RULE, EXIT_MACHINE, EXIT_SIGNALS, FORMAT, GATE_MACHINE, LATCH_MACHINE, LIMITER_RULES, MACHINES, POLLER_MACHINE, PROCESS_TRIGGERS, PROGRESS_RULE, SERVICE_RULES, SLIDING_WINDOW_MACHINE, STAGES, TOKEN_BUCKET_MACHINE, WATCHDOG_MACHINE, WINDOW_MACHINE, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, capKnob, cooperativeAbortAt, daemon, decide, detectionTime, drainMachine, embeddedProcess, ephemeral, evaluate, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, inequalities, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, lint, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redeliverOnce, replay, retryAfterSeconds, service, serviceBindings, stageRank, symbolsOf, verdictOf, watchdog, workerLoop };
3674
+ export { DECLARATION_RULE, DURATION_FRACTIONS, EPISODES_KEPT, EXIT_MACHINE, EXIT_SIGNALS, FORMAT, GATE_MACHINE, LATCH_MACHINE, LIMITER_RULES, MACHINES, POLLER_MACHINE, PROCESS_TRIGGERS, PROGRESS_RULE, SERVICE_RULES, SLIDING_WINDOW_MACHINE, STAGES, TOKEN_BUCKET_MACHINE, WATCHDOG_MACHINE, WINDOW_MACHINE, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, capKnob, cooperativeAbortAt, daemon, decide, detectionTime, drainMachine, embeddedProcess, ephemeral, evaluate, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, inequalities, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, lint, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redact, redeliverOnce, replay, retryAfterSeconds, service, serviceBindings, stageRank, symbolsOf, verdictOf, watchdog, workerLoop };
3559
3675
 
3560
3676
  //# sourceMappingURL=index.js.map