@mark1russell7/service 0.5.11 → 0.5.12

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.
@@ -255,6 +255,21 @@ interface Check {
255
255
  /** This gate is the policy that it controls, or null for a check that only reports. It has no default. */
256
256
  readonly gates: Gate | null;
257
257
  readonly poll: Poll;
258
+ /**
259
+ * These are the loops that mark the progress of a check of the step `progress`, and its stall
260
+ * time. Without a declaration, the check names no loops, and `lint()` does not compare its stall time.
261
+ */
262
+ readonly marks?: ProgressMarks | undefined;
263
+ }
264
+ /**
265
+ * A progress mark declaration tells which loops mark the progress of a check, and how long the
266
+ * check waits for a mark. The check function fails when no loop marked in the stall time.
267
+ */
268
+ interface ProgressMarks {
269
+ /** These are the names of the loops: a check (its poller) or a worker of the service. */
270
+ readonly loops: readonly string[];
271
+ /** This time is the most time from one mark to the next before the check fails. */
272
+ readonly stall: Quantity;
258
273
  }
259
274
  /**
260
275
  * A dependency is something that the service uses, with its client. Its knobs have the names that
@@ -701,5 +716,5 @@ interface Lifecycle {
701
716
  keepAlive(): boolean;
702
717
  }
703
718
  //#endregion
704
- export { failSpan as $, LifecycleEvent as A, Gate as B, ProbeAnswer as C, Operation as Ct, Outcome as D, CheckFn as E, CooperativeFloor as F, Step as G, Phase as H, Coupling as I, Poll as J, Transport as K, Dependency as L, Recorder as M, Check as N, Result as O, Controller as P, asWindow as Q, ExitCodes as R, HealthVerdict as S, Idempotency as St, StartOptions as T, RepeatSignal as U, MessageTrigger as V, ServiceDescription as W, Timing as X, Span as Y, asDebounce as Z, RequestOptions as _, isExitSignal as _t, PhaseContext as a, EXIT_SIGNALS as at, CheckVerdict as b, nodeProcess as bt, STAGES as c, ExitTrigger as ct, TransportImpl as d, PROCESS_TRIGGERS as dt, heartbeatDetection as et, cooperativeAbortAt as f, ProcessHost as ft, RequestFn as g, isExitCode as gt, RequestEvent as h, embeddedProcess as ht, LifecycleState as i, RouteRequest as it, MachineName as j, Trace as k, ShutdownOptions as l, HostStream as lt, RequestEnd as m, ProcessTrigger as mt, HealthServer as n, OperationOf as nt, PhaseWork as o, EmbeddedProcess as ot, stageRank as p, ProcessPort as pt, FailsBy as q, Lifecycle as r, RoutePattern as rt, RunOptions as s, ExitSignal as st, DependencyHandle as t, recoverSpan as tt, Stage as u, MessageShape as ut, RequestStream as v, isMessageShape as vt, ProbeKind as w, mayReissue as wt, Health as x, processPortOf as xt, RequestWork as y, matchesMessage as yt, FORMAT as z };
705
- //# sourceMappingURL=lifecycle-CGEegMI0.d.ts.map
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
@@ -0,0 +1,372 @@
1
+ import { t as mayReissue } from "./operation-DL5RxFds.js";
2
+ import { quantity, valueIn } from "@mark1russell7/quantity";
3
+ import { connect } from "node:net";
4
+ //#region src/client/meets.ts
5
+ /** These are the manifestations that each client gives a meeting for, in this sequence. */
6
+ const MANIFESTATIONS = [
7
+ "rst",
8
+ "fin",
9
+ "blackhole",
10
+ "freeze-host",
11
+ "freeze-process",
12
+ "app-stall"
13
+ ];
14
+ /**
15
+ * These words tell each manifestation that does not depend on the driver. The words of `app-stall`
16
+ * name an example, and the example is a fact of each server, thus each client gives them.
17
+ */
18
+ const WHAT = {
19
+ rst: "the server answers each segment with a reset, and refuses each new connection",
20
+ fin: "the process of the server ends, and its kernel closes the connection",
21
+ blackhole: "the network drops each segment in the two directions",
22
+ "freeze-host": "the host of the server stops, without a close: the death without a close",
23
+ "freeze-process": "the process of the server stops, and its kernel stays up"
24
+ };
25
+ /** This amount is a detector that does not find the failure. */
26
+ const NEVER = {
27
+ value: "infinite",
28
+ unit: "s"
29
+ };
30
+ const s = (value) => quantity(Math.round(value * 1e6) / 1e6, "s");
31
+ const seconds = (amount) => amount.value === "infinite" ? Number.POSITIVE_INFINITY : valueIn(amount, "s");
32
+ /** 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. */
33
+ const keepaliveGiveUp = (idle) => idle.value === "infinite" ? NEVER : s(seconds(idle) + 10);
34
+ /** This detector is a segment that arrives at once: a reset or a FIN. */
35
+ const immediate = (situation, effect) => ({
36
+ by: "the next segment",
37
+ within: s(0),
38
+ situation,
39
+ effect
40
+ });
41
+ /** This detector is TCP keepalive on a silent peer. It gives up after its probes, and the socket closes. */
42
+ const keepaliveSilent = (idle) => ({
43
+ by: "keepalive_idle",
44
+ within: keepaliveGiveUp(idle),
45
+ situation: "idle",
46
+ effect: "TCP keepalive gives up, and the socket closes"
47
+ });
48
+ /** This detector is TCP keepalive on a peer whose kernel stays up. That kernel answers each probe, thus keepalive does not find the failure. */
49
+ const keepaliveAnswered = () => ({
50
+ by: "keepalive_idle",
51
+ within: NEVER,
52
+ situation: "idle",
53
+ effect: "the kernel of the server answers each probe"
54
+ });
55
+ /** This detector is TCP keepalive on a peer that resets: the first probe gets the reset. */
56
+ const keepaliveReset = (idle) => ({
57
+ by: "keepalive_idle",
58
+ within: idle.value === "infinite" ? NEVER : idle,
59
+ situation: "idle",
60
+ effect: "the first probe gets a reset"
61
+ });
62
+ /** This detector is the kernel, which gives up the retransmissions of a segment in flight. A driver can add what follows. */
63
+ const kernelGiveUp = (within, after) => ({
64
+ by: "retrans_giveup_time",
65
+ within,
66
+ situation: "in flight",
67
+ effect: `the kernel gives up the retransmissions and closes the socket${after === void 0 ? "" : `, and ${after}`}`
68
+ });
69
+ /** This function gives the first detector of a manifestation in a situation, or null when no detector has an end (C3). */
70
+ const firstDetector = (meeting, situation) => {
71
+ return meeting.detectors.filter((detector) => detector.situation === situation && detector.within.value !== "infinite").sort((a, b) => seconds(a.within) - seconds(b.within))[0] ?? null;
72
+ };
73
+ /** This function gives each manifestation and situation that no detector of the client finds: the standing rule, as a query (C3, E9). */
74
+ const unwatched = (meets) => meets.flatMap((meeting) => ["in flight", "idle"].flatMap((situation) => firstDetector(meeting, situation) === null ? [{
75
+ manifestation: meeting.manifestation,
76
+ situation
77
+ }] : []));
78
+ //#endregion
79
+ //#region src/client/core.ts
80
+ /** This function gives an amount in milliseconds. "infinite" is `Infinity`. */
81
+ const ms = (amount) => amount.value === "infinite" ? Number.POSITIVE_INFINITY : valueIn(amount, "ms");
82
+ /** This function gives an amount in milliseconds, or undefined for "infinite". Each driver then writes its own form of "no timer". */
83
+ const finiteMs = (amount) => {
84
+ const value = ms(amount);
85
+ return Number.isFinite(value) ? value : void 0;
86
+ };
87
+ /** This function gives the text of an error. */
88
+ const message = (error) => error instanceof Error ? error.message : String(error);
89
+ /** 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. */
90
+ const socketEnds = (socket) => ({
91
+ localAddress: socket?.localAddress ?? null,
92
+ localPort: socket?.localPort ?? null,
93
+ remoteAddress: socket?.remoteAddress ?? null,
94
+ remotePort: socket?.remotePort ?? null
95
+ });
96
+ /** This function declares a client in a service: its dependency, its handle, and each of its checks. */
97
+ const attachClient = (svc, name, declaration, handle, checks) => {
98
+ svc.dependency(name, declaration, handle);
99
+ for (const [check, run] of checks) svc.check(check, run);
100
+ return svc;
101
+ };
102
+ /** This function gives the table of operations of a client: the defaults of its driver, then the operations of its spec. */
103
+ const operationTable = (defaults, more) => ({
104
+ ...defaults,
105
+ ...more
106
+ });
107
+ /** This function makes the recorder of a client. Each event has the machine "client", and the name of the client as its instance. */
108
+ const clientRecorder = (record, clock, name) => (from, to, on, detail) => record?.({
109
+ at: clock.now(),
110
+ machine: "client",
111
+ instance: name,
112
+ from,
113
+ to,
114
+ on,
115
+ ...detail === void 0 ? {} : { detail }
116
+ });
117
+ /** This function gives the outcome of a call that did not leave the client. It is certain: the call did not apply. */
118
+ const notSent = (error) => ({
119
+ outcome: "error",
120
+ error,
121
+ reply: false
122
+ });
123
+ /** This function makes the record of the calls in flight of a client. */
124
+ const inFlight = () => {
125
+ const closes = /* @__PURE__ */ new Set();
126
+ return {
127
+ get size() {
128
+ return closes.size;
129
+ },
130
+ track(signal, abandoned, start, after) {
131
+ return new Promise((resolve) => {
132
+ let settled = false;
133
+ const settle = (value, cause) => {
134
+ if (settled) return;
135
+ settled = true;
136
+ closes.delete(close);
137
+ signal?.removeEventListener("abort", aborted);
138
+ after?.(value, cause);
139
+ resolve(value);
140
+ };
141
+ const close = (error) => settle({
142
+ outcome: "indeterminate",
143
+ error
144
+ }, new Error(error));
145
+ const aborted = () => close(abandoned);
146
+ closes.add(close);
147
+ signal?.addEventListener("abort", aborted, { once: true });
148
+ start(settle);
149
+ });
150
+ },
151
+ settleAll(error) {
152
+ for (const close of [...closes]) close(error);
153
+ }
154
+ };
155
+ };
156
+ /**
157
+ * This function waits for the work of a close, for `bound` milliseconds at most. It gives true when
158
+ * the work ended in its bound, with a success or a failure. It gives false at the bound. The driver
159
+ * then destroys each socket that stays, because a silent server does not close its half.
160
+ */
161
+ const withinBound = (clock, bound, work) => {
162
+ const done = work.then(() => true, () => true);
163
+ let cancel = () => {};
164
+ const timer = new Promise((resolve) => {
165
+ cancel = clock.after(bound, () => resolve(false));
166
+ });
167
+ return Promise.race([done, timer]).then((ended) => {
168
+ cancel();
169
+ return ended;
170
+ });
171
+ };
172
+ /** 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. */
173
+ const timeProblems = (name, knobs) => {
174
+ const problems = [];
175
+ for (const [knob, amount] of Object.entries(knobs)) if (amount.value !== "infinite" && !(ms(amount) >= 0)) problems.push(`${name}'s ${knob} is not a time of zero or more`);
176
+ return problems;
177
+ };
178
+ /**
179
+ * This function sends a call, and gives its outcome in three values. It sends again only an
180
+ * idempotent operation, one time at most, after an outcome that is not certain. A reply of the
181
+ * server is certain. The second try ends at the bound of the call, not one bound after its own start.
182
+ */
183
+ const sendWithReissue = async (call) => {
184
+ const { clock, bound } = call;
185
+ const { signal } = call.options;
186
+ const operation = call.options.reissue === false ? void 0 : call.operation;
187
+ const started = clock.now();
188
+ const first = await call.attempt(signal);
189
+ if (first.outcome === "ok") return {
190
+ outcome: "ok",
191
+ value: first.value,
192
+ attempts: 1
193
+ };
194
+ const again = !(first.outcome === "error" && first.reply) && operation !== void 0 && signal?.aborted !== true && mayReissue(operation, quantity((clock.now() - started) / 1e3, "s"));
195
+ const left = bound - (clock.now() - started);
196
+ if (!again || left <= 0) return {
197
+ ...first,
198
+ attempts: 1
199
+ };
200
+ if (call.beforeAgain !== void 0 && !await call.beforeAgain(left, signal)) return {
201
+ ...first,
202
+ attempts: 1
203
+ };
204
+ call.onReissue();
205
+ const end = new AbortController();
206
+ const stop = clock.after(Math.max(0, bound - (clock.now() - started)), () => end.abort());
207
+ const forward = () => end.abort();
208
+ signal?.addEventListener("abort", forward, { once: true });
209
+ const second = await call.attempt(end.signal);
210
+ stop();
211
+ signal?.removeEventListener("abort", forward);
212
+ return second.outcome === "ok" ? {
213
+ outcome: "ok",
214
+ value: second.value,
215
+ attempts: 2
216
+ } : {
217
+ ...second,
218
+ attempts: 2
219
+ };
220
+ };
221
+ /**
222
+ * This function gives the checks of a client, for `svc.check(check, run)`. It gives a check only for
223
+ * each step that the options name, and only when the client has a function for that step.
224
+ */
225
+ const stepChecks = (name, options, runs) => {
226
+ const made = [];
227
+ for (const [step, run] of Object.entries(runs)) {
228
+ if (run === void 0 || !(step in options.steps)) continue;
229
+ made.push([{
230
+ name: `${name}.${step}`,
231
+ dependency: name,
232
+ step,
233
+ ownConnection: run.ownConnection,
234
+ gates: options.steps[step] ?? null,
235
+ poll: options.poll
236
+ }, run.run]);
237
+ }
238
+ return made;
239
+ };
240
+ /** This function makes a check outcome from a sent result: only `ok` passes. */
241
+ const passes = (sent) => sent.outcome === "ok" ? "ok" : "error";
242
+ /** This check gives the state of the client, without a round trip. */
243
+ const localStateCheck = (ready) => ({
244
+ ownConnection: true,
245
+ run: () => ready() ? "ok" : "error"
246
+ });
247
+ /**
248
+ * This check sends one call on the connection of the application. A check measures the step at this time.
249
+ * Thus it does not send the call again, and only `ok` passes.
250
+ */
251
+ const roundTripCheck = (send) => ({
252
+ ownConnection: true,
253
+ run: async (signal) => passes(await send({
254
+ signal,
255
+ reissue: false
256
+ }))
257
+ });
258
+ /**
259
+ * This check opens a new connection each time. It finds a server that refuses connections, but not
260
+ * a stalled connection of the application. A failed dial throws, and the poller counts the throw as an error.
261
+ */
262
+ const dialCheck = (dial, host, port) => ({
263
+ ownConnection: false,
264
+ run: async (signal) => {
265
+ await dial(host, port, signal);
266
+ return "ok";
267
+ }
268
+ });
269
+ /** This function gives the bounds of a client that `clientProblems` examines, from its knobs. */
270
+ const clientBounds = (knobs) => ({
271
+ close: ms(knobs.close_bound),
272
+ request: ms(knobs.request_timeout)
273
+ });
274
+ /**
275
+ * This function gives the problems of a client in a description:
276
+ * - The client needs the phase "tear down dependencies", and its close must fit in the cap of that phase.
277
+ * - A round-trip check must end in one jittered poll (B7, O13).
278
+ */
279
+ const clientProblems = (name, description, bounds) => {
280
+ const found = [];
281
+ const teardown = description.shutdown.phases.find((phase) => phase.name === "tear down dependencies");
282
+ if (teardown === void 0) found.push(`${name} needs the phase "tear down dependencies" to close`);
283
+ else if (bounds.close >= valueIn(teardown.cap, "ms")) found.push(`${name}'s close_bound of ${bounds.close} ms does not fit in the cap of "tear down dependencies"`);
284
+ for (const check of description.checks) {
285
+ if (check.dependency !== name || check.step !== "round-trip") continue;
286
+ const slowest = valueIn(check.poll.timeout, "ms") + valueIn(check.poll.interval, "ms") * (1 + check.poll.jitter);
287
+ if (bounds.request > slowest) found.push(`${name}'s request_timeout outlives one jittered poll of ${check.name} (B7, O13)`);
288
+ }
289
+ return found;
290
+ };
291
+ /**
292
+ * This poll is a poll of the checks to start from: each 5 s with a jitter of 20 %, and a timeout of
293
+ * 4 s (B6, O12). Its slowest poll is 4 s + 5 s × 1.2 = 10 s, the slowest poll of the cells of O13.
294
+ * It does not depend on a driver. The starting knobs of each driver fit it.
295
+ */
296
+ const STARTING_POLL = {
297
+ interval: {
298
+ value: 5,
299
+ unit: "s"
300
+ },
301
+ jitter: .2,
302
+ timeout: {
303
+ value: 4,
304
+ unit: "s"
305
+ }
306
+ };
307
+ //#endregion
308
+ //#region src/client/dial.ts
309
+ /**
310
+ * This function makes a dial function. The dial opens a TCP connection, sends `hello`, and reads the
311
+ * answer until `judge` decides. An abort, an error of the socket, and a close before the decision fail the dial.
312
+ * The dial destroys its socket at its end.
313
+ */
314
+ const helloDial = (hello, judge) => (host, port, signal) => new Promise((resolve, reject) => {
315
+ if (signal.aborted) {
316
+ reject(/* @__PURE__ */ new Error("the dial was aborted"));
317
+ return;
318
+ }
319
+ const socket = connect({
320
+ host,
321
+ port
322
+ });
323
+ let received = Buffer.alloc(0);
324
+ const end = (error) => {
325
+ signal.removeEventListener("abort", onAbort);
326
+ socket.destroy();
327
+ if (error === void 0) resolve();
328
+ else reject(error);
329
+ };
330
+ const onAbort = () => end(/* @__PURE__ */ new Error("the dial was aborted"));
331
+ signal.addEventListener("abort", onAbort, { once: true });
332
+ socket.once("connect", () => socket.write(hello));
333
+ socket.on("data", (chunk) => {
334
+ received = Buffer.concat([received, chunk]);
335
+ const verdict = judge(received);
336
+ if (verdict === "more") return;
337
+ end(verdict ?? void 0);
338
+ });
339
+ socket.once("error", (error) => end(error));
340
+ socket.once("close", () => end(/* @__PURE__ */ new Error("the server closed the dial")));
341
+ });
342
+ //#endregion
343
+ //#region src/client/machine.ts
344
+ /** This function gives one transition of a machine document. */
345
+ const transition = (id, from, on, to, extra = {}) => ({
346
+ id,
347
+ from,
348
+ on,
349
+ to,
350
+ ...extra
351
+ });
352
+ /** This function gives a knob of a client in a machine document, by the option of the driver that it sets. */
353
+ const clientKnob = (key) => ({
354
+ key,
355
+ scope: "client",
356
+ unit: "s",
357
+ default: null
358
+ });
359
+ /** 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. */
360
+ const closeTimer = () => ({
361
+ duration: {
362
+ knob: "close_bound",
363
+ unit: "s"
364
+ },
365
+ arms: ["close.request"],
366
+ cancels: ["status.end"],
367
+ fires: "close.bound"
368
+ });
369
+ //#endregion
370
+ export { keepaliveSilent as A, withinBound as C, immediate as D, firstDetector as E, unwatched as M, keepaliveAnswered as O, timeProblems as S, WHAT as T, operationTable as _, STARTING_POLL as a, socketEnds as b, clientProblems as c, finiteMs as d, inFlight as f, notSent as g, ms as h, helloDial as i, kernelGiveUp as j, keepaliveReset as k, clientRecorder as l, message as m, closeTimer as n, attachClient as o, localStateCheck as p, transition as r, clientBounds as s, clientKnob as t, dialCheck as u, roundTripCheck as v, MANIFESTATIONS as w, stepChecks as x, sendWithReissue as y };
371
+
372
+ //# sourceMappingURL=machine-Czfb6fV4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine-Czfb6fV4.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`, 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 { 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\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}\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 return {\n get size() {\n return closes.size;\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 };\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 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/** 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/** 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)`. It gives a check only for\n * each step that the options name, and only when the client has a function for that step.\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 made: Array<readonly [Check, CheckFn]> = [];\n for (const [step, run] of Object.entries(runs) as Array<[S, StepCheck | undefined]>) {\n if (run === undefined || !(step in options.steps)) continue;\n made.push([\n { name: `${name}.${step}`, dependency: name, step, ownConnection: run.ownConnection, gates: options.steps[step] ?? null, poll: options.poll },\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","// 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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"}