@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.
@@ -445,6 +445,59 @@ interface Result {
445
445
  */
446
446
  type Trace = (from: string, to: string, on: string) => void;
447
447
  //#endregion
448
+ //#region src/metrics.d.ts
449
+ /**
450
+ * A histogram of durations counts the polls of a check by how long each one took (B18). Each bound
451
+ * is a fraction of the timeout of the poll, thus the last bound is the timeout. A poll that failed
452
+ * at its timeout goes into `timedOut`, not into a bucket. Thus the histogram marks the timeouts.
453
+ */
454
+ interface DurationHistogram {
455
+ /** These bounds are in milliseconds, from the smallest. A bucket counts each poll that took more than the bound before it, up to its own bound. */
456
+ readonly le: readonly number[];
457
+ /** These counts are not cumulative: one count for each bound. */
458
+ readonly counts: readonly number[];
459
+ readonly timedOut: number;
460
+ }
461
+ /** An episode of failure starts at the first error of a sequence, and ends at the next pass (B18). */
462
+ interface Episode {
463
+ /** This time is the first error, in milliseconds on the monotonic clock. */
464
+ readonly start: number;
465
+ /** This time is the pass that ended the episode, or null while the episode continues. */
466
+ readonly end: number | null;
467
+ /** This count is the polls that failed in the episode. */
468
+ readonly failures: number;
469
+ /** This cause is the cause of the first error, with each secret removed. */
470
+ readonly cause: string | null;
471
+ }
472
+ /** The metrics of one check: the count of each outcome, its durations and its last episodes. */
473
+ interface CheckMetrics {
474
+ readonly outcomes: {
475
+ readonly ok: number;
476
+ readonly error: number;
477
+ readonly indeterminate: number;
478
+ };
479
+ readonly thrown: number;
480
+ readonly forced: number;
481
+ readonly durations: DurationHistogram;
482
+ /** These are the last episodes, from the oldest. The runtime keeps `EPISODES_KEPT` of them. */
483
+ readonly episodes: readonly Episode[];
484
+ }
485
+ /** The metrics of a health runtime. Nothing in them starts a check. */
486
+ interface HealthMetrics {
487
+ readonly checks: Readonly<Record<string, CheckMetrics>>;
488
+ /** Each count is the transitions of one machine of the runtime, by the key `machine instance from -> to`. */
489
+ readonly transitions: Readonly<Record<string, number>>;
490
+ }
491
+ /** These fractions of the timeout of a poll are the bounds of its histogram. */
492
+ declare const DURATION_FRACTIONS: readonly number[];
493
+ /** The runtime keeps this many episodes of each check. */
494
+ declare const EPISODES_KEPT = 8;
495
+ /**
496
+ * This function removes the secrets that a cause can hold. These are the user and the password of a
497
+ * URL, and the value of a key, for example `password=`. A driver error can hold a connection string.
498
+ */
499
+ declare const redact: (cause: string) => string;
500
+ //#endregion
448
501
  //#region src/poller.d.ts
449
502
  /**
450
503
  * A check function measures one step of a connection. It ends with nothing or `"ok"` for a pass,
@@ -521,6 +574,11 @@ interface Health {
521
574
  * readiness probe with a controller asks about that controller. The report always answers 200.
522
575
  */
523
576
  probe(kind: ProbeKind, controller?: string): ProbeAnswer;
577
+ /**
578
+ * This method gives the counts of the runtime (B18). They are the outcomes, the durations and the
579
+ * episodes of failure of each check, and the transitions of its machines. It starts no check.
580
+ */
581
+ metrics(): HealthMetrics;
524
582
  /** This method gives each change of the verdict to `listener`, and gives the function that stops it. */
525
583
  subscribe(listener: (verdict: HealthVerdict) => void): () => void;
526
584
  /** This method adds a name to the force-fail list, or removes it. */
@@ -716,5 +774,5 @@ interface Lifecycle {
716
774
  keepAlive(): boolean;
717
775
  }
718
776
  //#endregion
719
- export { asWindow as $, LifecycleEvent as A, Gate as B, ProbeAnswer as C, Idempotency as Ct, Outcome as D, CheckFn as E, CooperativeFloor as F, ServiceDescription as G, Phase as H, Coupling as I, FailsBy as J, Step as K, Dependency as L, Recorder as M, Check as N, Result as O, Controller as P, asDebounce as Q, ExitCodes as R, HealthVerdict as S, processPortOf as St, StartOptions as T, mayReissue as Tt, ProgressMarks as U, MessageTrigger as V, RepeatSignal as W, Span as X, Poll as Y, Timing as Z, RequestOptions as _, isExitCode as _t, PhaseContext as a, RouteRequest as at, CheckVerdict as b, matchesMessage as bt, STAGES as c, ExitSignal as ct, TransportImpl as d, MessageShape as dt, failSpan as et, cooperativeAbortAt as f, PROCESS_TRIGGERS as ft, RequestFn as g, embeddedProcess as gt, RequestEvent as h, ProcessTrigger as ht, LifecycleState as i, RoutePattern as it, MachineName as j, Trace as k, ShutdownOptions as l, ExitTrigger as lt, RequestEnd as m, ProcessPort as mt, HealthServer as n, recoverSpan as nt, PhaseWork as o, EXIT_SIGNALS as ot, stageRank as p, ProcessHost as pt, Transport as q, Lifecycle as r, OperationOf as rt, RunOptions as s, EmbeddedProcess as st, DependencyHandle as t, heartbeatDetection as tt, Stage as u, HostStream as ut, RequestStream as v, isExitSignal as vt, ProbeKind as w, Operation as wt, Health as x, nodeProcess as xt, RequestWork as y, isMessageShape as yt, FORMAT as z };
720
- //# sourceMappingURL=lifecycle-fgYfxrHK.d.ts.map
777
+ export { Step as $, EPISODES_KEPT as A, Idempotency as At, Check as B, ProbeAnswer as C, embeddedProcess as Ct, CheckMetrics as D, matchesMessage as Dt, CheckFn as E, isMessageShape as Et, Result as F, ExitCodes as G, CooperativeFloor as H, Trace as I, MessageTrigger as J, FORMAT as K, LifecycleEvent as L, HealthMetrics as M, mayReissue as Mt, redact as N, DURATION_FRACTIONS as O, nodeProcess as Ot, Outcome as P, ServiceDescription as Q, MachineName as R, HealthVerdict as S, ProcessTrigger as St, StartOptions as T, isExitSignal as Tt, Coupling as U, Controller as V, Dependency as W, ProgressMarks as X, Phase as Y, RepeatSignal as Z, RequestOptions as _, HostStream as _t, PhaseContext as a, asDebounce as at, CheckVerdict as b, ProcessHost as bt, STAGES as c, heartbeatDetection as ct, TransportImpl as d, RoutePattern as dt, Transport as et, cooperativeAbortAt as f, RouteRequest as ft, RequestFn as g, ExitTrigger as gt, RequestEvent as h, ExitSignal as ht, LifecycleState as i, Timing as it, Episode as j, Operation as jt, DurationHistogram as k, processPortOf as kt, ShutdownOptions as l, recoverSpan as lt, RequestEnd as m, EmbeddedProcess as mt, HealthServer as n, Poll as nt, PhaseWork as o, asWindow as ot, stageRank as p, EXIT_SIGNALS as pt, Gate as q, Lifecycle as r, Span as rt, RunOptions as s, failSpan as st, DependencyHandle as t, FailsBy as tt, Stage as u, OperationOf as ut, RequestStream as v, MessageShape as vt, ProbeKind as w, isExitCode as wt, Health as x, ProcessPort as xt, RequestWork as y, PROCESS_TRIGGERS as yt, Recorder as z };
778
+ //# sourceMappingURL=lifecycle-CMedxaQd.d.ts.map
@@ -78,6 +78,8 @@ const unwatched = (meets) => meets.flatMap((meeting) => ["in flight", "idle"].fl
78
78
  }] : []));
79
79
  //#endregion
80
80
  //#region src/client/core.ts
81
+ /** This interval is the poll of a catalog check that gates nothing (B19). */
82
+ const CATALOG_INTERVAL = quantity(30, "s");
81
83
  /** This function gives an amount in milliseconds. "infinite" is `Infinity`. */
82
84
  const ms = (amount) => amount.value === "infinite" ? Number.POSITIVE_INFINITY : valueIn(amount, "ms");
83
85
  /** This function gives an amount in milliseconds, or undefined for "infinite". Each driver then writes its own form of "no timer". */
@@ -385,20 +387,34 @@ const sendWithReissue = async (call) => {
385
387
  };
386
388
  };
387
389
  /**
388
- * This function gives the checks of a client, for `svc.check(check, run)`. It gives a check only for
389
- * each step that the options name, and only when the client has a function for that step.
390
+ * This function gives the checks of a client, for `svc.check(check, run)`. Without a catalog, it gives
391
+ * a check for each step that the options name, when the client has a function for that step. With a
392
+ * catalog, it gives a check for each step of the client. It then throws for a nominated step that the
393
+ * client does not have (B19). The name of each check is the pair of the component and the step, a
394
+ * public contract.
390
395
  */
391
396
  const stepChecks = (name, options, runs) => {
397
+ const has = Object.entries(runs).filter(([, run]) => run !== void 0).map(([step]) => step);
398
+ const catalog = options.catalog === true ? {
399
+ ...options.poll,
400
+ interval: CATALOG_INTERVAL
401
+ } : options.catalog;
402
+ if (catalog !== void 0) {
403
+ const missing = Object.keys(options.steps).filter((step) => !has.includes(step));
404
+ if (missing.length > 0) throw new Error(`${name}: the nominated checks ${missing.join(", ")} are not in the catalog of the client (${has.join(", ")})`);
405
+ }
392
406
  const made = [];
393
- for (const [step, run] of Object.entries(runs)) {
394
- if (run === void 0 || !(step in options.steps)) continue;
407
+ for (const step of has) {
408
+ const run = runs[step];
409
+ const nominated = step in options.steps;
410
+ if (!nominated && catalog === void 0) continue;
395
411
  made.push([{
396
412
  name: `${name}.${step}`,
397
413
  dependency: name,
398
414
  step,
399
415
  ownConnection: run.ownConnection,
400
- gates: options.steps[step] ?? null,
401
- poll: options.poll
416
+ gates: nominated ? options.steps[step] ?? null : null,
417
+ poll: nominated || catalog === void 0 ? options.poll : catalog
402
418
  }, run.run]);
403
419
  }
404
420
  return made;
@@ -549,4 +565,4 @@ const closeTimer = () => ({
549
565
  //#endregion
550
566
  export { retrySchedule as A, zeroProblems as B, notSent as C, readyWait as D, poolProblems as E, timeProblems as F, immediate as G, NEVER as H, trackWithTimer as I, keepaliveSilent as J, keepaliveAnswered as K, wholeProblems as L, sendWithReissue as M, socketEnds as N, retryBackoff as O, stepChecks as P, withinBound as R, ms as S, orZero as T, WHAT as U, MANIFESTATIONS as V, firstDetector as W, unwatched as X, kernelGiveUp as Y, inFlight as _, helloDial as a, message as b, ageProblems as c, clientProblems as d, clientRecorder as f, idleCloseProblem as g, finiteMs as h, authority as i, roundTripCheck as j, retryProblems as k, attachClient as l, dialCheck as m, closeTimer as n, statusLineJudge as o, deadlineProblem as p, keepaliveReset as q, transition as r, STARTING_POLL as s, clientKnob as t, clientBounds as u, keepaliveOnProblems as v, operationTable as w, missesProblems as x, localStateCheck as y, wrapperTimerProblems as z };
551
567
 
552
- //# sourceMappingURL=machine-BhTRu3u9.js.map
568
+ //# sourceMappingURL=machine-DZMb_QEU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine-DZMb_QEU.js","names":["dialTcp"],"sources":["../src/client/meets.ts","../src/client/core.ts","../src/client/dial.ts","../src/client/machine.ts"],"sourcesContent":["// The manifestations that a client can meet, and their detectors (E9). The shapes, the two queries,\n// the detectors of the kernel and the words of each manifestation are the same for each client.\n// Each client entry gives its own list of meetings, because the timers of each driver differ.\n\nimport type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport { quantity, valueIn } from \"@mark1russell7/quantity\";\n\n/** A situation is what the connection did when the failure started: a command in flight, or idle. */\nexport type Situation = \"in flight\" | \"idle\";\n\n/** A detector is a mechanism that finds a failure, and the most time that it takes. */\nexport interface Detector {\n /** This name identifies the mechanism: a knob of the declaration, or the kernel. */\n readonly by: string;\n /** This time is the most that the detector takes, after the failure starts. \"infinite\" tells that it does not find the failure. */\n readonly within: Extent;\n readonly situation: Situation;\n /** This text tells what the detector does, for example that it fails a command without a change of state. */\n readonly effect: string;\n}\n\n/**\n * A meeting is a manifestation that the client can meet, and its detectors (E9). The names of the\n * manifestations are the names of the vocabulary of the harness and the corpus. `app-stall` is a\n * server whose process is alive and does not answer, a proposal of the vocabulary.\n */\nexport interface Meeting {\n readonly manifestation: string;\n /** This text tells the failure in words. */\n readonly what: string;\n readonly detectors: readonly Detector[];\n}\n\n/** These are the manifestations that each client gives a meeting for, in this sequence. */\nexport const MANIFESTATIONS = [\"rst\", \"fin\", \"blackhole\", \"freeze-host\", \"freeze-process\", \"app-stall\"] as const;\n\n/** A manifestation is one name of `MANIFESTATIONS`. */\nexport type Manifestation = (typeof MANIFESTATIONS)[number];\n\n/**\n * These words tell each manifestation that does not depend on the driver. The words of `app-stall`\n * name an example, and the example is a fact of each server, thus each client gives them.\n */\nexport const WHAT: Readonly<Record<Exclude<Manifestation, \"app-stall\">, string>> = {\n rst: \"the server answers each segment with a reset, and refuses each new connection\",\n fin: \"the process of the server ends, and its kernel closes the connection\",\n blackhole: \"the network drops each segment in the two directions\",\n \"freeze-host\": \"the host of the server stops, without a close: the death without a close\",\n \"freeze-process\": \"the process of the server stops, and its kernel stays up\",\n};\n\n/** This amount is a detector that does not find the failure. */\nexport const NEVER: Extent = { value: \"infinite\", unit: \"s\" };\n\nconst s = (value: number): Quantity => quantity(Math.round(value * 1e6) / 1e6, \"s\");\nconst seconds = (amount: Extent): number =>\n amount.value === \"infinite\" ? Number.POSITIVE_INFINITY : valueIn(amount as Quantity, \"s\");\n\n/** Node gives keepalive 10 probes, 1 s apart, after its idle time: a silent peer is found 10 s after the idle time. */\nconst NODE_PROBES = 10;\nconst NODE_INTERVAL = 1;\n\n/** This function gives the time at which Node's TCP keepalive gives up a silent peer: the idle time, then 10 probes 1 s apart. */\nexport const keepaliveGiveUp = (idle: Extent): Extent => (idle.value === \"infinite\" ? NEVER : s(seconds(idle) + NODE_PROBES * NODE_INTERVAL));\n\n/** This detector is a segment that arrives at once: a reset or a FIN. */\nexport const immediate = (situation: Situation, effect: string): Detector => ({ by: \"the next segment\", within: s(0), situation, effect });\n\n/** This detector is TCP keepalive on a silent peer. It gives up after its probes, and the socket closes. */\nexport const keepaliveSilent = (idle: Extent): Detector => ({\n by: \"keepalive_idle\",\n within: keepaliveGiveUp(idle),\n situation: \"idle\",\n effect: \"TCP keepalive gives up, and the socket closes\",\n});\n\n/** This detector is TCP keepalive on a peer whose kernel stays up. That kernel answers each probe, thus keepalive does not find the failure. */\nexport const keepaliveAnswered = (): Detector => ({\n by: \"keepalive_idle\",\n within: NEVER,\n situation: \"idle\",\n effect: \"the kernel of the server answers each probe\",\n});\n\n/** This detector is TCP keepalive on a peer that resets: the first probe gets the reset. */\nexport const keepaliveReset = (idle: Extent): Detector => ({\n by: \"keepalive_idle\",\n within: idle.value === \"infinite\" ? NEVER : idle,\n situation: \"idle\",\n effect: \"the first probe gets a reset\",\n});\n\n/** This detector is the kernel, which gives up the retransmissions of a segment in flight. A driver can add what follows. */\nexport const kernelGiveUp = (within: Extent, after?: string): Detector => ({\n by: \"retrans_giveup_time\",\n within,\n situation: \"in flight\",\n effect: `the kernel gives up the retransmissions and closes the socket${after === undefined ? \"\" : `, and ${after}`}`,\n});\n\n/** This function gives the first detector of a manifestation in a situation, or null when no detector has an end (C3). */\nexport const firstDetector = (meeting: Meeting, situation: Situation): Detector | null => {\n const ranked = meeting.detectors\n .filter((detector) => detector.situation === situation && detector.within.value !== \"infinite\")\n .sort((a, b) => seconds(a.within) - seconds(b.within));\n return ranked[0] ?? null;\n};\n\n/** This function gives each manifestation and situation that no detector of the client finds: the standing rule, as a query (C3, E9). */\nexport const unwatched = (meets: readonly Meeting[]): Array<{ readonly manifestation: string; readonly situation: Situation }> =>\n meets.flatMap((meeting) =>\n ([\"in flight\", \"idle\"] as const).flatMap((situation) =>\n firstDetector(meeting, situation) === null ? [{ manifestation: meeting.manifestation, situation }] : [],\n ),\n );\n","// The parts of a client wrapper that do not change from one client to the next. Each client entry\n// (`./redis`, `./pg`, `./http-client`, `./websocket`, and each later client of S1) fills them in with its driver. The core gives\n// the shapes of a client, the outcome of a call in three values, the rule that sends a call again,\n// the record of the calls in flight, the bounded close, the checks of the steps of a connection,\n// and the problems of a spec and of a client in a description. It loads only quantity.\n// The section \"Unified or separate\" of docs/S6.md gives each part, and the reason for each part that stays in a driver.\n\nimport type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport { quantity, valueIn } from \"@mark1russell7/quantity\";\nimport type { Backoff } from \"../backoff.ts\";\nimport { backoffDelay } from \"../backoff.ts\";\nimport type { ServiceBuilder } from \"../builder.ts\";\nimport type { Cancel, Clock } from \"../clock.ts\";\nimport type { Check, Dependency, Gate, ServiceDescription, Step } from \"../description.ts\";\nimport type { Recorder } from \"../events.ts\";\nimport type { Outcome } from \"../latch.ts\";\nimport type { DependencyHandle } from \"../lifecycle.ts\";\nimport type { Operation } from \"../operation.ts\";\nimport { mayReissue } from \"../operation.ts\";\nimport type { CheckFn } from \"../poller.ts\";\nimport type { Poll } from \"../timing.ts\";\nimport type { Meeting } from \"./meets.ts\";\n\n/**\n * A sent result is the outcome of a call, in three values (C8):\n * - `ok`: the server answered.\n * - `error`: the call did not apply. The server answered with an error (`reply`), or the call did\n * not leave the client.\n * - `indeterminate`: the call can have reached the server, and nothing tells if it applied.\n */\nexport type Sent<T> =\n | { readonly outcome: \"ok\"; readonly value: T; readonly attempts: number }\n | { readonly outcome: \"error\"; readonly error: string; readonly reply: boolean; readonly attempts: number }\n | { readonly outcome: \"indeterminate\"; readonly error: string; readonly attempts: number };\n\n/** This type is the outcome of one try of a call, before the core counts the tries. */\nexport type Attempt<T = unknown> =\n | { readonly outcome: \"ok\"; readonly value: T }\n | { readonly outcome: \"error\"; readonly error: string; readonly reply: boolean }\n | { readonly outcome: \"indeterminate\"; readonly error: string };\n\n/** Send options tell how the client sends one call. */\nexport interface SendOptions {\n /** This signal abandons the call. A call that left the client is then indeterminate. */\n readonly signal?: AbortSignal | undefined;\n /** False stops the client: it does not send the call again. A check that must fail at once uses it. */\n readonly reissue?: boolean | undefined;\n}\n\n/** A dial function opens a new connection to the server, and reads one reply of the protocol. The `dial` check uses it. */\nexport type DialFn = (host: string, port: number, signal: AbortSignal) => Promise<void>;\n\n/** The steps of a connection that each client gives a check for (B14). A client can add more steps. */\nexport type ClientStep = Extract<Step, \"dial\" | \"round-trip\" | \"local-state\">;\n\n/** Check options tell which checks the client gives the health runtime, and which policy each one controls. */\nexport interface CheckOptions<S extends Step = ClientStep> {\n readonly poll: Poll;\n /** 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. */\n readonly steps: Partial<Record<S, Gate | null>>;\n /**\n * With a catalog, the client gives a check for each step that it has (B19). The steps of `steps` are\n * the nominated checks. Each other step reports only, and polls at the catalog poll. `true` is a poll\n * of 30 s with the jitter and the timeout of `poll`. A nominated step that the client has no check\n * for then fails the attach. Without a catalog, the client gives only the steps of `steps`.\n */\n readonly catalog?: true | Poll | undefined;\n}\n\n/** This interval is the poll of a catalog check that gates nothing (B19). */\nexport const CATALOG_INTERVAL: Quantity = quantity(30, \"s\");\n\n/** A step check is the function of one step, and if it uses the connection of the application. */\nexport interface StepCheck {\n readonly ownConnection: boolean;\n readonly run: CheckFn;\n}\n\n/** This function gives an amount in milliseconds. \"infinite\" is `Infinity`. */\nexport const ms = (amount: Extent): number =>\n amount.value === \"infinite\" ? Number.POSITIVE_INFINITY : valueIn(amount as Quantity, \"ms\");\n\n/** This function gives an amount in milliseconds, or undefined for \"infinite\". Each driver then writes its own form of \"no timer\". */\nexport const finiteMs = (amount: Extent): number | undefined => {\n const value = ms(amount);\n return Number.isFinite(value) ? value : undefined;\n};\n\n/** This function gives the text of an error. */\nexport const message = (error: unknown): string => (error instanceof Error ? error.message : String(error));\n\n/**\n * These are the knobs that each client has, by the names of the ordering law. Each driver adds its\n * own knobs. The comments of each driver tell which option of the driver a knob sets.\n */\nexport interface ClientKnobs {\n /** This time bounds a new connection. */\n readonly connect_bound: Quantity;\n /** This time bounds each call on the connection of the application. */\n readonly request_timeout: Quantity;\n /** This time is the idle time of TCP keepalive. Node then sends 10 probes 1 s apart. */\n readonly keepalive_idle: Extent;\n /** This time is the age at which the client replaces a connection. */\n readonly max_connection_age: Extent;\n /** This time bounds the close of the client at the shutdown. */\n readonly close_bound: Quantity;\n /**\n * This time is when the kernel gives up a segment that it retransmits. Node cannot set it, thus\n * the declaration states it for the ordering law (O17), from the platform.\n */\n readonly retrans_giveup_time: Extent;\n}\n\n/** A client spec is the declaration of one client: where it connects, and its knobs. Each driver adds its own fields. */\nexport interface ClientSpec<K extends ClientKnobs = ClientKnobs> {\n readonly host: string;\n readonly port: number;\n readonly password?: string | undefined;\n /** This identifier is the component in the corpus, for example `systems.component.redis`, or null. */\n readonly system: string | null;\n readonly knobs: K;\n /** These operations add to the table of the client, or change it. */\n readonly operations?: Readonly<Record<string, Operation>> | undefined;\n}\n\n/** A factory makes the connection or the pool of a client from the options of its driver. */\nexport type Factory<O, C> = (options: O) => C | Promise<C>;\n\n/** These options make a client. Without them, a client uses its driver, the real clock, and the dial of its protocol. */\nexport interface ClientOptions<F> {\n readonly create?: F | undefined;\n readonly clock?: Clock | undefined;\n /** This function receives each transition of the client, for the prober (D3). */\n readonly record?: Recorder | undefined;\n readonly dial?: DialFn | undefined;\n}\n\n/** A client socket is the part of the socket of a connection that the join of the prober and the bounded close read. */\nexport interface ClientSocket {\n readonly localAddress?: string | undefined;\n readonly localPort?: number | undefined;\n readonly remoteAddress?: string | undefined;\n readonly remotePort?: number | undefined;\n /** This method closes the socket at once. A close of the driver only half-closes it, and a silent peer does not answer that. */\n destroy?(): void;\n}\n\n/**\n * A client join is one connection, at the time when it can serve (D1, D2). The prober joins its\n * kernel records to the server's view of the connection through it. Each driver adds the server's id of the connection.\n */\nexport interface ClientJoin {\n readonly generation: number;\n /** This time is when the connection became ready, in milliseconds on the monotonic clock. */\n readonly at: number;\n readonly localAddress: string | null;\n readonly localPort: number | null;\n readonly remoteAddress: string | null;\n readonly remotePort: number | null;\n}\n\n/** This function gives the addresses and the ports of a socket for a join. Each one is null when the socket does not give it. */\nexport const socketEnds = (\n socket: ClientSocket | undefined,\n): Pick<ClientJoin, \"localAddress\" | \"localPort\" | \"remoteAddress\" | \"remotePort\"> => ({\n localAddress: socket?.localAddress ?? null,\n localPort: socket?.localPort ?? null,\n remoteAddress: socket?.remoteAddress ?? null,\n remotePort: socket?.remotePort ?? null,\n});\n\n/** A client is a dependency of a service: its declaration, its handle and its checks. Each driver adds the method that sends a call. */\nexport interface Client<S extends Step = ClientStep, J extends ClientJoin = ClientJoin> {\n readonly name: string;\n /** This declaration goes to `svc.dependency(name, declaration, handle)`. */\n readonly declaration: Omit<Dependency, \"name\">;\n readonly handle: DependencyHandle;\n /** These are the manifestations that the client can meet, and the detectors of each (E9). */\n readonly meets: readonly Meeting[];\n /** This text is the state of the client, as the machine document of the client names it. */\n readonly status: string;\n /** These are the connections of the client, the latest last. */\n readonly joins: readonly J[];\n /** This method gives the checks of the client and their functions, for `svc.check(check, run)`. */\n checks(options: CheckOptions<S>): ReadonlyArray<readonly [Check, CheckFn]>;\n /** This method declares the client in a service: its dependency, its handle and its checks. */\n attach(svc: ServiceBuilder, options: CheckOptions<S>): ServiceBuilder;\n /** This method gives the problems of the client in a description: a close that does not fit in its phase, for example. */\n problems(description: ServiceDescription): string[];\n}\n\n/** This function declares a client in a service: its dependency, its handle, and each of its checks. */\nexport const attachClient = (\n svc: ServiceBuilder,\n name: string,\n declaration: Omit<Dependency, \"name\">,\n handle: DependencyHandle,\n checks: ReadonlyArray<readonly [Check, CheckFn]>,\n): ServiceBuilder => {\n svc.dependency(name, declaration, handle);\n for (const [check, run] of checks) svc.check(check, run);\n return svc;\n};\n\n/** This function gives the table of operations of a client: the defaults of its driver, then the operations of its spec. */\nexport const operationTable = (\n defaults: Readonly<Record<string, Operation>>,\n more: Readonly<Record<string, Operation>> | undefined,\n): Readonly<Record<string, Operation>> => ({ ...defaults, ...more });\n\n/** A client recorder gives one transition of a client to the recorder of the options, for the prober (D3). */\nexport type ClientRecorder = (from: string, to: string, on: string, detail?: string) => void;\n\n/** This function makes the recorder of a client. Each event has the machine \"client\", and the name of the client as its instance. */\nexport const clientRecorder =\n (record: Recorder | undefined, clock: Clock, name: string): ClientRecorder =>\n (from, to, on, detail) =>\n record?.({ at: clock.now(), machine: \"client\", instance: name, from, to, on, ...(detail === undefined ? {} : { detail }) });\n\n/** This function gives the outcome of a call that did not leave the client. It is certain: the call did not apply. */\nexport const notSent = (error: string): Attempt<never> => ({ outcome: \"error\", error, reply: false });\n\n/** A settle function gives the outcome of a call in flight. A driver can give a cause, for example the error that ends a client of a pool. */\nexport type Settle<T = unknown> = (value: Attempt<T>, cause?: Error) => void;\n\n/**\n * This record holds the calls in flight of a client. Each call settles one time. A call that the\n * caller abandons after it left the client is indeterminate, because a sent call cannot be\n * cancelled (principle 9). The close and a closed connection settle each call that stays (C10).\n */\nexport interface InFlight {\n /** This number is the count of the calls in flight. */\n readonly size: number;\n /**\n * This method sends one call, and records it until it settles. `start` sends the call, and gives\n * its outcome to `settle`. `after` operates one time, after the call settles and before its promise resolves.\n */\n track<T>(\n signal: AbortSignal | undefined,\n abandoned: string,\n start: (settle: Settle<T>) => void,\n after?: (value: Attempt<T>, cause: Error | undefined) => void,\n ): Promise<Attempt<T>>;\n /** This method settles each call in flight as indeterminate, with this error. */\n settleAll(error: string): void;\n /** This method resolves when no call is in flight. A close that waits for its calls uses it in its bound. */\n idle(): Promise<void>;\n}\n\n/** This function makes the record of the calls in flight of a client. */\nexport const inFlight = (): InFlight => {\n const closes = new Set<(error: string) => void>();\n const waiters = new Set<() => void>();\n const wake = (): void => {\n if (closes.size > 0) return;\n for (const waiter of [...waiters]) waiter();\n waiters.clear();\n };\n return {\n get size() {\n return closes.size;\n },\n idle: () =>\n new Promise<void>((resolve) => {\n if (closes.size === 0) resolve();\n else waiters.add(resolve);\n }),\n track<T>(\n signal: AbortSignal | undefined,\n abandoned: string,\n start: (settle: Settle<T>) => void,\n after?: (value: Attempt<T>, cause: Error | undefined) => void,\n ): Promise<Attempt<T>> {\n return new Promise<Attempt<T>>((resolve) => {\n let settled = false;\n const settle: Settle<T> = (value, cause) => {\n if (settled) return;\n settled = true;\n closes.delete(close);\n signal?.removeEventListener(\"abort\", aborted);\n after?.(value, cause);\n resolve(value);\n wake();\n };\n const close = (error: string): void => settle({ outcome: \"indeterminate\", error }, new Error(error));\n const aborted = (): void => close(abandoned);\n closes.add(close);\n signal?.addEventListener(\"abort\", aborted, { once: true });\n start(settle);\n });\n },\n settleAll(error) {\n for (const close of [...closes]) close(error);\n },\n };\n};\n\n/**\n * This function sends one call through the record of the calls in flight, with a timer of the\n * wrapper. A driver with no timer for its call uses it: amqplib, and the agent of Node. At `bound`\n * milliseconds the call is indeterminate with the error `timedOut`, because it left the client.\n * The timer stops when the call settles, before `after` operates.\n */\nexport const trackWithTimer = <T>(\n flight: InFlight,\n clock: Clock,\n bound: number,\n signal: AbortSignal | undefined,\n abandoned: string,\n timedOut: string,\n start: (settle: Settle<T>) => void,\n after?: (value: Attempt<T>, cause: Error | undefined) => void,\n): Promise<Attempt<T>> => {\n let stop: Cancel = () => {};\n return flight.track<T>(\n signal,\n abandoned,\n (settle) => {\n stop = clock.after(bound, () => settle({ outcome: \"indeterminate\", error: timedOut }));\n start(settle);\n },\n (value, cause) => {\n stop();\n after?.(value, cause);\n },\n );\n};\n\n/**\n * This function waits for the work of a close, for `bound` milliseconds at most. It gives true when\n * the work ended in its bound, with a success or a failure. It gives false at the bound. The driver\n * then destroys each socket that stays, because a silent server does not close its half.\n */\nexport const withinBound = (clock: Clock, bound: number, work: Promise<unknown>): Promise<boolean> => {\n const done = work.then(\n () => true,\n () => true,\n );\n let cancel: Cancel = () => {};\n const timer = new Promise<boolean>((resolve) => {\n cancel = clock.after(bound, () => resolve(false));\n });\n return Promise.race([done, timer]).then((ended) => {\n cancel();\n return ended;\n });\n};\n\n/**\n * A ready wait holds the calls that wait for a ready connection before their second try. A client\n * that connects again by itself uses it: Redis and RabbitMQ.\n */\nexport interface ReadyWait {\n /** This method waits until the client is ready, for `within` milliseconds at most. It gives true when the client is ready. */\n wait(within: number, signal?: AbortSignal): Promise<boolean>;\n /** This method ends each wait with true. The client uses it when its connection is ready. */\n wake(): void;\n}\n\n/** This function makes the ready wait of a client. The function `ready` tells if the client is ready. */\nexport const readyWait = (clock: Clock, ready: () => boolean): ReadyWait => {\n const waiters = new Set<() => void>();\n return {\n wait: (within, signal) =>\n new Promise((resolve) => {\n if (ready()) {\n resolve(true);\n return;\n }\n let cancel: Cancel = () => {};\n const done = (value: boolean): void => {\n waiters.delete(wake);\n cancel();\n signal?.removeEventListener(\"abort\", aborted);\n resolve(value);\n };\n const wake = (): void => done(true);\n const aborted = (): void => done(false);\n waiters.add(wake);\n cancel = clock.after(within, () => done(ready()));\n signal?.addEventListener(\"abort\", aborted, { once: true });\n }),\n wake() {\n for (const wake of [...waiters]) wake();\n },\n };\n};\n\n/** These are the knobs of a client that connects again by itself, after a delay that doubles. */\nexport interface RetryKnobs {\n /** This time is the first delay before a new try to connect. The delay doubles after each try. */\n readonly retry_base: Quantity;\n /** This time is the most that one delay can be, before its jitter. */\n readonly retry_cap: Quantity;\n}\n\n/**\n * This function gives the backoff of the tries to connect: from `retry_base`, doubling to\n * `retry_cap`, each with a jitter up to the base. The client rounds each delay to a whole millisecond.\n */\nexport const retryBackoff = (knobs: RetryKnobs): Backoff => ({\n base: ms(knobs.retry_base),\n factor: 2,\n cap: ms(knobs.retry_cap),\n jitter: { kind: \"add\", amount: ms(knobs.retry_base) },\n});\n\n/**\n * A retry schedule holds the next try to connect, for a client that connects again by itself. It\n * counts the tries since the last join. Each delay comes from the backoff, rounded to a whole millisecond.\n */\nexport interface RetrySchedule {\n /** This count is the number of tries since the last reset. */\n readonly tries: number;\n /** This method counts one more try, and starts `due` after the delay of that try. It stops a try that waits. */\n schedule(due: () => void): void;\n /** This method stops the try that waits. The close uses it. */\n cancel(): void;\n /** This method sets the count of tries to zero. The client uses it at each join. */\n reset(): void;\n}\n\n/** This function makes the retry schedule of a client, from its backoff and its random numbers. RabbitMQ and WebSocket use it. */\nexport const retrySchedule = (clock: Clock, backoff: Backoff, random: () => number): RetrySchedule => {\n let tries = 0;\n let timer: Cancel | null = null;\n return {\n get tries() {\n return tries;\n },\n schedule(due) {\n tries += 1;\n const delay = Math.round(backoffDelay(backoff, tries, random));\n timer?.();\n timer = clock.after(delay, () => {\n timer = null;\n due();\n });\n },\n cancel() {\n timer?.();\n timer = null;\n },\n reset() {\n tries = 0;\n },\n };\n};\n\n/** This function gives the problems of the retry knobs: the cap must not be below the base. */\nexport const retryProblems = (name: string, knobs: RetryKnobs): string[] =>\n ms(knobs.retry_cap) < ms(knobs.retry_base) ? [`${name}'s retry_cap is below its retry_base`] : [];\n\n/**\n * This function gives the problem of a count of misses that is not a whole number above zero. A\n * client that closes its connection after a count of heartbeat misses uses it. `mechanism` names the heartbeat, for example \"watchdog\".\n */\nexport const missesProblems = (name: string, misses: number, mechanism: string): string[] =>\n Number.isInteger(misses) && misses >= 1 ? [] : [`${name}'s ${mechanism} closes the connection after ${misses} misses, not a whole number above zero`];\n\n/** This function gives the problems of the knobs of a spec that each client has: each knob must be a time of zero or more. */\nexport const timeProblems = (name: string, knobs: object): string[] => {\n const problems: string[] = [];\n for (const [knob, amount] of Object.entries(knobs) as Array<[string, Extent]>) {\n if (amount.value !== \"infinite\" && !(ms(amount) >= 0)) problems.push(`${name}'s ${knob} is not a time of zero or more`);\n }\n return problems;\n};\n\n/** This function writes a bound for a driver that reads 0 as no timer: \"infinite\" is 0. */\nexport const orZero = (amount: Extent): number => finiteMs(amount) ?? 0;\n\n/** This function tells if an amount is a whole number of milliseconds. \"infinite\" is whole. */\nexport const wholeMs = (amount: Extent): boolean => amount.value === \"infinite\" || Number.isInteger(ms(amount));\n\n/** This function tells if an amount is a whole number of seconds. \"infinite\" is whole. */\nexport const wholeSeconds = (amount: Extent): boolean =>\n amount.value === \"infinite\" || Number.isInteger(valueIn(amount as Quantity, \"s\"));\n\n/** These are the knobs of a spec that `zeroProblems` and `wholeProblems` examine, by their kind. */\nexport interface KnobKinds {\n /** These knobs must be a finite time above zero, because the driver reads 0 as no bound. */\n readonly bounded?: readonly string[] | undefined;\n /** These knobs can be \"infinite\", which the driver writes as 0. Thus 0 itself is not a bound. */\n readonly open?: readonly string[] | undefined;\n /** These knobs go to the driver in whole milliseconds. */\n readonly millis?: readonly string[] | undefined;\n /** These knobs go to the driver or to Node in whole seconds. */\n readonly seconds?: readonly string[] | undefined;\n}\n\nconst knobOf = (knobs: object, knob: string): Extent => (knobs as Record<string, Extent>)[knob]!;\n\n/**\n * This function gives the problems of the knobs of a driver that reads 0 as no timer. pg and the\n * MongoDB driver read 0 so. A bounded knob must be a finite time above zero. An open knob can be\n * \"infinite\", and 0 is then a mistake.\n */\nexport const zeroProblems = (name: string, driver: string, knobs: object, kinds: KnobKinds): string[] => {\n const problems: string[] = [];\n for (const knob of kinds.bounded ?? []) {\n const value = ms(knobOf(knobs, knob));\n if (!(value > 0) || !Number.isFinite(value)) problems.push(`${name}'s ${knob} is not a finite time above zero: ${driver} reads 0 as no bound`);\n }\n for (const knob of kinds.open ?? []) {\n const amount = knobOf(knobs, knob);\n if (amount.value !== \"infinite\" && !(ms(amount) > 0)) problems.push(`${name}'s ${knob} is zero: give \"infinite\" for no bound`);\n }\n return problems;\n};\n\n/** This function gives the problems of the knobs that a driver takes in whole milliseconds or in whole seconds. */\nexport const wholeProblems = (name: string, knobs: object, kinds: KnobKinds): string[] => [\n ...(kinds.millis ?? []).filter((knob) => !wholeMs(knobOf(knobs, knob))).map((knob) => `${name}'s ${knob} is not a whole number of milliseconds`),\n ...(kinds.seconds ?? []).filter((knob) => !wholeSeconds(knobOf(knobs, knob))).map((knob) => `${name}'s ${knob} is not a whole number of seconds`),\n];\n\n/** This function gives the problems of the sizes of a pool: at most `max` connections, and `min` from 0 to `max`. */\nexport const poolProblems = (name: string, pool: { readonly max: number; readonly min: number }): string[] => {\n const problems: string[] = [];\n if (!Number.isInteger(pool.max) || pool.max < 1) problems.push(`${name}'s pool has a max of ${pool.max}, not a whole number above zero`);\n if (!Number.isInteger(pool.min) || pool.min < 0 || pool.min > pool.max)\n problems.push(`${name}'s pool has a min of ${pool.min}, not a whole number from 0 to its max`);\n return problems;\n};\n\n/**\n * This function gives the problem O07 of a client whose server has a deadline, or null. The client\n * must wait longer than the deadline of the server. Then the server ends a slow call with an error\n * first. Otherwise the client gives up first, and the server goes on with the call. `call` names\n * the call of the server, for example a statement.\n */\nexport const deadlineProblem = (name: string, request: Quantity, deadline: Extent, call: string): string | null =>\n deadline.value !== \"infinite\" && ms(request) <= ms(deadline)\n ? `${name}'s request_timeout is not above its statement_timeout: the server goes on with a ${call} that the client gave up (O07)`\n : null;\n\n/**\n * This function gives the problem O01 of a client pool, or null. The pool must close an idle\n * connection before its server does. Otherwise a request can go on a connection that the server\n * closed. A server that keeps an idle connection with no bound gives no problem. `knob` names the\n * idle time of the client: `pool_idle_expiry` of a pool, or `client_idle_timeout` of a gRPC channel.\n */\nexport const idleCloseProblem = (name: string, expiry: Extent, close: Extent, knob = \"pool_idle_expiry\"): string | null =>\n close.value !== \"infinite\" && !(ms(expiry) < ms(close))\n ? `${name}'s ${knob} is not below the idle close of its server: a request can go on a connection that the server closed (O01)`\n : null;\n\n/**\n * This function gives the problems of the timers that the wrapper has, because its driver has none.\n * Each one must be a finite time above zero. `bounds` gives what each knob bounds, for example \"a call\".\n */\nexport const wrapperTimerProblems = (name: string, knobs: object, bounds: Readonly<Record<string, string>>): string[] =>\n Object.entries(bounds)\n .filter(([knob]) => {\n const value = ms(knobOf(knobs, knob));\n return !(value > 0) || !Number.isFinite(value);\n })\n .map(([knob, what]) => `${name}'s ${knob} is not a finite time above zero: the timer of the wrapper is the only bound of ${what}`);\n\n/** This function gives the problem of a finite connection age, for a client that does not replace a connection at an age. `reason` tells why. */\nexport const ageProblems = (name: string, knobs: Pick<ClientKnobs, \"max_connection_age\">, reason: string): string[] =>\n knobs.max_connection_age.value === \"infinite\" ? [] : [`${name}'s max_connection_age is not \"infinite\": ${reason}`];\n\n/** This function gives the problem of a keepalive that is not a finite time above zero, for a driver that always turns keepalive on. */\nexport const keepaliveOnProblems = (name: string, knobs: Pick<ClientKnobs, \"keepalive_idle\">, driver: string): string[] => {\n const value = ms(knobs.keepalive_idle);\n return value > 0 && Number.isFinite(value) ? [] : [`${name}'s keepalive_idle is not a finite time above zero: ${driver} always turns keepalive on`];\n};\n\n/** These are the parts of one call that the core uses to send it, and to send it again. */\nexport interface Reissue<T> {\n readonly clock: Clock;\n /** This time bounds the call and its second try, in milliseconds from its start. */\n readonly bound: number;\n /** This operation is the declaration of the call, or undefined when the table does not name it. */\n readonly operation: Operation | undefined;\n readonly options: SendOptions;\n /** This function makes one try. Its signal ends the try. */\n readonly attempt: (signal: AbortSignal | undefined) => Promise<Attempt<T>>;\n /** This function operates before the second try, with the time that is left. False stops the second try. */\n readonly beforeAgain?: ((left: number, signal: AbortSignal | undefined) => Promise<boolean>) | undefined;\n /** This function receives the start of a second try, for the record of the client. */\n readonly onReissue: () => void;\n}\n\n/**\n * This function sends a call, and gives its outcome in three values. It sends again only an\n * idempotent operation, one time at most, after an outcome that is not certain. A reply of the\n * server is certain. The second try ends at the bound of the call, not one bound after its own start.\n */\nexport const sendWithReissue = async <T>(call: Reissue<T>): Promise<Sent<T>> => {\n const { clock, bound } = call;\n const { signal } = call.options;\n const operation = call.options.reissue === false ? undefined : call.operation;\n const started = clock.now();\n const first = await call.attempt(signal);\n if (first.outcome === \"ok\") return { outcome: \"ok\", value: first.value, attempts: 1 };\n const again =\n !(first.outcome === \"error\" && first.reply) &&\n operation !== undefined &&\n signal?.aborted !== true &&\n mayReissue(operation, quantity((clock.now() - started) / 1000, \"s\"));\n const left = bound - (clock.now() - started);\n if (!again || left <= 0) return { ...first, attempts: 1 };\n if (call.beforeAgain !== undefined && !(await call.beforeAgain(left, signal))) return { ...first, attempts: 1 };\n call.onReissue();\n const end = new AbortController();\n const stop = clock.after(Math.max(0, bound - (clock.now() - started)), () => end.abort());\n const forward = (): void => end.abort();\n signal?.addEventListener(\"abort\", forward, { once: true });\n const second = await call.attempt(end.signal);\n stop();\n signal?.removeEventListener(\"abort\", forward);\n return second.outcome === \"ok\" ? { outcome: \"ok\", value: second.value, attempts: 2 } : { ...second, attempts: 2 };\n};\n\n/**\n * This function gives the checks of a client, for `svc.check(check, run)`. Without a catalog, it gives\n * a check for each step that the options name, when the client has a function for that step. With a\n * catalog, it gives a check for each step of the client. It then throws for a nominated step that the\n * client does not have (B19). The name of each check is the pair of the component and the step, a\n * public contract.\n */\nexport const stepChecks = <S extends Step>(\n name: string,\n options: CheckOptions<S>,\n runs: Readonly<Partial<Record<S, StepCheck>>>,\n): ReadonlyArray<readonly [Check, CheckFn]> => {\n const has = (Object.entries(runs) as Array<[S, StepCheck | undefined]>).filter(([, run]) => run !== undefined).map(([step]) => step);\n const catalog = options.catalog === true ? { ...options.poll, interval: CATALOG_INTERVAL } : options.catalog;\n if (catalog !== undefined) {\n const missing = Object.keys(options.steps).filter((step) => !has.includes(step as S));\n if (missing.length > 0) {\n throw new Error(`${name}: the nominated checks ${missing.join(\", \")} are not in the catalog of the client (${has.join(\", \")})`);\n }\n }\n const made: Array<readonly [Check, CheckFn]> = [];\n for (const step of has) {\n const run = runs[step] as StepCheck;\n const nominated = step in options.steps;\n if (!nominated && catalog === undefined) continue;\n made.push([\n {\n name: `${name}.${step}`,\n dependency: name,\n step,\n ownConnection: run.ownConnection,\n gates: nominated ? (options.steps[step] ?? null) : null,\n poll: nominated || catalog === undefined ? options.poll : catalog,\n },\n run.run,\n ]);\n }\n return made;\n};\n\n/** This function makes a check outcome from a sent result: only `ok` passes. */\nexport const passes = (sent: Sent<unknown>): Outcome => (sent.outcome === \"ok\" ? \"ok\" : \"error\");\n\n/** This check gives the state of the client, without a round trip. */\nexport const localStateCheck = (ready: () => boolean): StepCheck => ({ ownConnection: true, run: () => (ready() ? \"ok\" : \"error\") });\n\n/**\n * This check sends one call on the connection of the application. A check measures the step at this time.\n * Thus it does not send the call again, and only `ok` passes.\n */\nexport const roundTripCheck = (send: (options: SendOptions) => Promise<Sent<unknown>>): StepCheck => ({\n ownConnection: true,\n run: async (signal) => passes(await send({ signal, reissue: false })),\n});\n\n/**\n * This check opens a new connection each time. It finds a server that refuses connections, but not\n * a stalled connection of the application. A failed dial throws, and the poller counts the throw as an error.\n */\nexport const dialCheck = (dial: DialFn, host: string, port: number): StepCheck => ({\n ownConnection: false,\n run: async (signal) => {\n await dial(host, port, signal);\n return \"ok\" as const;\n },\n});\n\n/** These are the bounds of a client that the core examines in a description. */\nexport interface ClientBounds {\n /** This time bounds the close of the client, in milliseconds. */\n readonly close: number;\n /** This time bounds a call on the connection of the application, in milliseconds. */\n readonly request: number;\n}\n\n/** This function gives the bounds of a client that `clientProblems` examines, from its knobs. */\nexport const clientBounds = (knobs: ClientKnobs): ClientBounds => ({ close: ms(knobs.close_bound), request: ms(knobs.request_timeout) });\n\n/**\n * This function gives the problems of a client in a description:\n * - The client needs the phase \"tear down dependencies\", and its close must fit in the cap of that phase.\n * - A round-trip check must end in one jittered poll (B7, O13).\n */\nexport const clientProblems = (name: string, description: ServiceDescription, bounds: ClientBounds): string[] => {\n const found: string[] = [];\n const teardown = description.shutdown.phases.find((phase) => phase.name === \"tear down dependencies\");\n if (teardown === undefined) found.push(`${name} needs the phase \"tear down dependencies\" to close`);\n else if (bounds.close >= valueIn(teardown.cap, \"ms\"))\n found.push(`${name}'s close_bound of ${bounds.close} ms does not fit in the cap of \"tear down dependencies\"`);\n for (const check of description.checks) {\n if (check.dependency !== name || check.step !== \"round-trip\") continue;\n const slowest = valueIn(check.poll.timeout, \"ms\") + valueIn(check.poll.interval, \"ms\") * (1 + check.poll.jitter);\n if (bounds.request > slowest) found.push(`${name}'s request_timeout outlives one jittered poll of ${check.name} (B7, O13)`);\n }\n return found;\n};\n\n/**\n * This poll is a poll of the checks to start from: each 5 s with a jitter of 20 %, and a timeout of\n * 4 s (B6, O12). Its slowest poll is 4 s + 5 s × 1.2 = 10 s, the slowest poll of the cells of O13.\n * It does not depend on a driver. The starting knobs of each driver fit it.\n */\nexport const STARTING_POLL: Poll = { interval: { value: 5, unit: \"s\" }, jitter: 0.2, timeout: { value: 4, unit: \"s\" } };\n","// The dial of a client: a new TCP connection that sends the first bytes of its protocol and reads\n// the answer. Each driver gives its bytes and its rule for the answer. The core keeps node:net out\n// of `core.ts`, thus only an entry that gives a dial loads it.\n\nimport { connect as dialTcp } from \"node:net\";\nimport type { DialFn } from \"./core.ts\";\n\n/**\n * A judge reads the bytes that the server sent so far. It gives null for a good answer, an error for\n * a bad answer, or \"more\" when it must wait for more bytes.\n */\nexport type Judge = (received: Buffer) => Error | null | \"more\";\n\n/**\n * This function makes a dial function. The dial opens a TCP connection, sends `hello`, and reads the\n * answer until `judge` decides. An abort, an error of the socket, and a close before the decision fail the dial.\n * The dial destroys its socket at its end.\n */\nexport const helloDial =\n (hello: string | Uint8Array, judge: Judge): DialFn =>\n (host, port, signal) =>\n new Promise((resolve, reject) => {\n if (signal.aborted) {\n reject(new Error(\"the dial was aborted\"));\n return;\n }\n const socket = dialTcp({ host, port });\n let received = Buffer.alloc(0);\n const end = (error?: Error): void => {\n signal.removeEventListener(\"abort\", onAbort);\n socket.destroy();\n if (error === undefined) resolve();\n else reject(error);\n };\n const onAbort = (): void => end(new Error(\"the dial was aborted\"));\n signal.addEventListener(\"abort\", onAbort, { once: true });\n socket.once(\"connect\", () => socket.write(hello));\n socket.on(\"data\", (chunk: Buffer) => {\n received = Buffer.concat([received, chunk]);\n const verdict = judge(received);\n if (verdict === \"more\") return;\n end(verdict ?? undefined);\n });\n socket.once(\"error\", (error) => end(error));\n socket.once(\"close\", () => end(new Error(\"the server closed the dial\")));\n });\n\n/** This function gives the authority of a host and a port for a Host header. An IPv6 address goes in brackets. */\nexport const authority = (host: string, port: number): string => `${host.includes(\":\") ? `[${host}]` : host}:${port}`;\n\n/**\n * This function makes a judge of the status line of an HTTP/1.1 answer. The judge waits for the end\n * of the line, in the first 8 KiB. Then `accept` reads the status code, and gives null for a good status or an error.\n * The HTTP client accepts each status. The WebSocket client accepts only 101.\n */\nexport const statusLineJudge =\n (accept: (status: number, line: string) => Error | null): Judge =>\n (received) => {\n const end = received.indexOf(\"\\r\\n\");\n if (end < 0) return received.length > 8192 ? new Error(\"the answer has no status line in its first 8 KiB\") : \"more\";\n const line = received.toString(\"latin1\", 0, end);\n const status = /^HTTP\\/1\\.[01] (\\d{3})/.exec(line);\n return status === null ? new Error(`the answer ${JSON.stringify(line)} is not an HTTP/1.1 status line`) : accept(Number(status[1]), line);\n };\n","// The parts of the machine document of a client that each client has. Each client entry writes its\n// own states and transitions, because the states are the states of its driver. The helpers, the\n// knob shape, the timer of the bounded close and the events that the core records are the same.\n\nimport type { TransitionDef } from \"@mark1russell7/algebra\";\n\n/** This function gives one transition of a machine document. */\nexport const transition = (\n id: string,\n from: string | readonly string[],\n on: string | readonly string[],\n to: string,\n extra: Partial<TransitionDef> = {},\n): TransitionDef => ({\n id,\n from,\n on,\n to,\n ...extra,\n});\n\n/** A client knob is a knob of a client in a machine document: a time in seconds, with no default. */\nexport interface ClientKnob {\n readonly key: string;\n readonly scope: \"client\";\n readonly unit: \"s\";\n readonly default: null;\n}\n\n/** This function gives a knob of a client in a machine document, by the option of the driver that it sets. */\nexport const clientKnob = (key: string): ClientKnob => ({ key, scope: \"client\", unit: \"s\", default: null });\n\n/** A close timer is the timer of the bounded close in a machine document. */\nexport interface CloseTimer {\n readonly duration: { readonly knob: \"close_bound\"; readonly unit: \"s\" };\n readonly arms: readonly [\"close.request\"];\n readonly cancels: readonly [\"status.end\"];\n readonly fires: \"close.bound\";\n}\n\n/** This timer is the bound of the close. The close request arms it, and the end cancels it. At its end, the driver destroys each socket. */\nexport const closeTimer = (): CloseTimer => ({\n duration: { knob: \"close_bound\", unit: \"s\" },\n arms: [\"close.request\"],\n cancels: [\"status.end\"],\n fires: \"close.bound\",\n});\n\n/**\n * These are the events that each machine document of a client declares. The core and the shared\n * parts of each client record them: a second try, a failed connect, and the steps of the bounded close.\n */\nexport const CLIENT_EVENTS = [\"status.connecting\", \"connect.failed\", \"command.reissue\", \"close.request\", \"close.bound\", \"close.destroy\", \"status.end\"] as const;\n"],"mappings":"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"}
@@ -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/mongodb/connection.d.ts
package/build/mongodb.js CHANGED
@@ -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, J as keepaliveSilent, K as keepaliveAnswered, L as wholeProblems, M as sendWithReissue, P as stepChecks, R as withinBound, S as ms, T as orZero, 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, h as finiteMs, j as roundTripCheck, l as attachClient, m as dialCheck, n as closeTimer, p as deadlineProblem, 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 } from "./machine-BhTRu3u9.js";
2
+ import { B as zeroProblems, C as notSent, E as poolProblems, F as timeProblems, G as immediate, J as keepaliveSilent, K as keepaliveAnswered, L as wholeProblems, M as sendWithReissue, P as stepChecks, R as withinBound, S as ms, T as orZero, 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, h as finiteMs, j as roundTripCheck, l as attachClient, m as dialCheck, n as closeTimer, p as deadlineProblem, 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 } from "./machine-DZMb_QEU.js";
3
3
  import { quantity } from "@mark1russell7/quantity";
4
4
  //#region src/mongodb/connection.ts
5
5
  /**
package/build/pg.d.ts CHANGED
@@ -1,5 +1,5 @@
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";
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
3
  import { Extent, Quantity } from "@mark1russell7/quantity";
4
4
  import { Machine } from "@mark1russell7/algebra";
5
5
  //#region src/pg/connection.d.ts
package/build/pg.js CHANGED
@@ -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, J as keepaliveSilent, K as keepaliveAnswered, L as wholeProblems, M as sendWithReissue, N as socketEnds, P as stepChecks, R as withinBound, S as ms, T as orZero, U as WHAT, V as MANIFESTATIONS, W as firstDetector, X as unwatched, Y as kernelGiveUp, _ as inFlight, a as helloDial, b as message, d as clientProblems, f as clientRecorder, h as finiteMs, j as roundTripCheck, l as attachClient, m as dialCheck, n as closeTimer, p as deadlineProblem, q as keepaliveReset, r as transition, s as STARTING_POLL, t as clientKnob, u as clientBounds, w as operationTable, y as localStateCheck } from "./machine-BhTRu3u9.js";
2
+ import { B as zeroProblems, C as notSent, E as poolProblems, F as timeProblems, G as immediate, J as keepaliveSilent, K as keepaliveAnswered, L as wholeProblems, M as sendWithReissue, N as socketEnds, P as stepChecks, R as withinBound, S as ms, T as orZero, U as WHAT, V as MANIFESTATIONS, W as firstDetector, X as unwatched, Y as kernelGiveUp, _ as inFlight, a as helloDial, b as message, d as clientProblems, f as clientRecorder, h as finiteMs, j as roundTripCheck, l as attachClient, m as dialCheck, n as closeTimer, p as deadlineProblem, q as keepaliveReset, r as transition, s as STARTING_POLL, t as clientKnob, u as clientBounds, w as operationTable, y as localStateCheck } from "./machine-DZMb_QEU.js";
3
3
  //#region src/pg/connection.ts
4
4
  /**
5
5
  * This factory makes a `pg.Pool`. It loads pg only when a client connects, thus a service that does
@@ -1,5 +1,5 @@
1
1
  import { n as Clock } from "./clock-mVqhXD0b.js";
2
- import { G as ServiceDescription, d as TransportImpl, r as Lifecycle } from "./lifecycle-fgYfxrHK.js";
2
+ import { Q as ServiceDescription, d as TransportImpl, r as Lifecycle } from "./lifecycle-CMedxaQd.js";
3
3
  import { Extent, Quantity } from "@mark1russell7/quantity";
4
4
  //#region src/units.d.ts
5
5
  /**
@@ -147,4 +147,4 @@ declare const redeliverOnce: <J>(then: JobAction | RedeliveryPolicy<J>, poison?:
147
147
  declare const queueDrain: <J>(knobs: QueueKnobs, options: QueueDrainOptions<J>) => QueueDrain;
148
148
  //#endregion
149
149
  export { JobSource as a, QueueKnobs as c, queueDrain as d, redeliverOnce as f, JobSettle as i, RedeliveryPolicy as l, JobContext as n, QueueDrain as o, UnitRequest as p, JobFailure as r, QueueDrainOptions as s, JobAction as t, RequeueCause as u };
150
- //# sourceMappingURL=queue-snRHK3Jm.d.ts.map
150
+ //# sourceMappingURL=queue-CndiCVaY.d.ts.map
@@ -1,7 +1,7 @@
1
- import { Y as Poll } 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, y as retryBackoff } from "./core-BPLRDcUJ.js";
3
- import { t as Judge } from "./dial-DA8vJfh2.js";
4
- import { i as JobSettle } from "./queue-snRHK3Jm.js";
1
+ import { 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, y as retryBackoff } from "./core-D8iXeoE4.js";
3
+ import { t as Judge } from "./dial-DJolDmOK.js";
4
+ import { i as JobSettle } from "./queue-CndiCVaY.js";
5
5
  import { Extent, Quantity } from "@mark1russell7/quantity";
6
6
  import { Machine } from "@mark1russell7/algebra";
7
7
  //#region src/rabbitmq/connection.d.ts
package/build/rabbitmq.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { realClock } from "./clock.js";
2
- import { A as retrySchedule, B as zeroProblems, C as notSent, D as readyWait, F as timeProblems, G as immediate, H as NEVER, I as trackWithTimer, J as keepaliveSilent, K as keepaliveAnswered, L as wholeProblems, M as sendWithReissue, N as socketEnds, O as retryBackoff, 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, h as finiteMs, j as roundTripCheck, k as retryProblems, l as attachClient, m as dialCheck, n as closeTimer, q as keepaliveReset, 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";
2
+ import { A as retrySchedule, B as zeroProblems, C as notSent, D as readyWait, F as timeProblems, G as immediate, H as NEVER, I as trackWithTimer, J as keepaliveSilent, K as keepaliveAnswered, L as wholeProblems, M as sendWithReissue, N as socketEnds, O as retryBackoff, 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, h as finiteMs, j as roundTripCheck, k as retryProblems, l as attachClient, m as dialCheck, n as closeTimer, q as keepaliveReset, 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";
3
3
  import { quantity } from "@mark1russell7/quantity";
4
4
  //#region src/rabbitmq/connection.ts
5
5
  /**
package/build/redis.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as Clock } from "./clock-mVqhXD0b.js";
2
- import { tt as heartbeatDetection, wt as Operation } from "./lifecycle-fgYfxrHK.js";
3
- 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, 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, w as Manifestation, y as retryBackoff } from "./core-BPLRDcUJ.js";
2
+ import { ct as heartbeatDetection, jt as Operation } from "./lifecycle-CMedxaQd.js";
3
+ 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, 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, w as Manifestation, y as retryBackoff } from "./core-D8iXeoE4.js";
4
4
  import { n as LimiterStore } from "./store-D5HLgBnW.js";
5
5
  import { Extent, Quantity } from "@mark1russell7/quantity";
6
6
  import { Machine } from "@mark1russell7/algebra";
package/build/redis.js CHANGED
@@ -2,7 +2,7 @@ import { realClock } from "./clock.js";
2
2
  import { heartbeat } from "./heartbeat.js";
3
3
  import { i as heartbeatDetection } from "./timing-DGRi3XCI.js";
4
4
  import { backoffDelay } from "./backoff.js";
5
- import { C as notSent, D as readyWait, F as timeProblems, G as immediate, J as keepaliveSilent, K as keepaliveAnswered, M as sendWithReissue, N as socketEnds, O as retryBackoff, 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, d as clientProblems, f as clientRecorder, h as finiteMs, j as roundTripCheck, k as retryProblems, l as attachClient, m as dialCheck, n as closeTimer, q as keepaliveReset, r as transition, t as clientKnob, u as clientBounds, w as operationTable, x as missesProblems, y as localStateCheck } from "./machine-BhTRu3u9.js";
5
+ import { C as notSent, D as readyWait, F as timeProblems, G as immediate, J as keepaliveSilent, K as keepaliveAnswered, M as sendWithReissue, N as socketEnds, O as retryBackoff, 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, d as clientProblems, f as clientRecorder, h as finiteMs, j as roundTripCheck, k as retryProblems, l as attachClient, m as dialCheck, n as closeTimer, q as keepaliveReset, r as transition, t as clientKnob, u as clientBounds, w as operationTable, x as missesProblems, y as localStateCheck } from "./machine-DZMb_QEU.js";
6
6
  //#region src/redis/connection.ts
7
7
  /**
8
8
  * This factory makes an ioredis client. It loads ioredis only when a client connects, thus a
@@ -1,6 +1,6 @@
1
1
  import { n as Clock } from "./clock-mVqhXD0b.js";
2
- import { D as Outcome, E as CheckFn, ct as ExitSignal, d as TransportImpl, lt as ExitTrigger, mt as ProcessPort, r as Lifecycle } from "./lifecycle-fgYfxrHK.js";
3
- import { n as ServiceBuilder } from "./builder-DawJhq4p.js";
2
+ import { E as CheckFn, P as Outcome, d as TransportImpl, gt as ExitTrigger, ht as ExitSignal, r as Lifecycle, xt as ProcessPort } from "./lifecycle-CMedxaQd.js";
3
+ import { n as ServiceBuilder } from "./builder-CsSO4LzB.js";
4
4
  //#region src/fakes.d.ts
5
5
  /** A fake clock moves only when a test tells it to. */
6
6
  interface FakeClock extends Clock {
package/build/testing.js CHANGED
@@ -330,6 +330,7 @@ const detachedLifecycle = () => {
330
330
  health: {
331
331
  verdict: none,
332
332
  probe: none,
333
+ metrics: none,
333
334
  subscribe: none,
334
335
  forceFail: none,
335
336
  drain: none,