@mark1russell7/service 0.5.12 → 0.5.13

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","names":["knob","t"],"sources":["../src/websocket/connection.ts","../src/websocket/meets.ts","../src/websocket/client.ts","../src/websocket/machine.ts","../src/websocket/starting.ts"],"sourcesContent":["import type { ClientSocket, Factory } from \"../client/core.ts\";\n\n/**\n * These are the listeners of one connection. The connection holds each event until the client\n * listens. Thus an event that comes between the creation and the listen does not go away.\n */\nexport interface WsListeners {\n /** This event tells that the upgrade ended with 101, and the connection can send. */\n open(): void;\n /** This event gives one message of the server. A text message is UTF-8 in the buffer. */\n message(data: Buffer, binary: boolean): void;\n /** This event gives the payload of one pong frame. */\n pong(data: Buffer): void;\n /** This event tells that the connection closed, with the code and the reason of the close frame. Code 1006 tells that no close frame came. */\n close(code: number, reason: string): void;\n /** This event gives an error of the connection. A close follows it. */\n error(error: Error): void;\n}\n\n/**\n * A connection is the part of a `WebSocket` of ws 8 that the wrapper uses. A fake for tests has\n * each part too. Thus the package depends on no type of ws.\n */\nexport interface WsConnection {\n /** This method gives the listeners of the connection. The connection then gives each event that it holds. */\n listen(listeners: WsListeners): void;\n /** This method writes one message. The callback gets an error when the write fails. It throws while the connection is not open. */\n send(data: string | Buffer, callback: (error?: Error) => void): void;\n /** This method writes one ping frame with this payload. It throws while the connection is not open. */\n ping(data: Buffer): void;\n /** This method writes a close frame, and waits for the close frame of the server. It destroys nothing. */\n close(code: number, reason: string): void;\n /** This method destroys the socket at once. The cells measured 0.3 ms to 1 ms from the call to the close of the socket. */\n terminate(): void;\n /** This field holds the socket after the upgrade, or undefined before it. The join reads its addresses. */\n readonly socket: ClientSocket | undefined;\n /** This text is the subprotocol that the server chose, or \"\" when it chose none. */\n readonly protocol: string;\n}\n\n/**\n * These are the options that the wrapper gives `new WebSocket(url, protocols, options)` of ws. Each\n * time comes from the declaration. ws gives the options that it does not read to `net.connect`.\n */\nexport interface WsDriverOptions {\n /** This time bounds the TCP connect and the upgrade, in milliseconds. */\n readonly handshakeTimeout: number;\n /** ws answers each ping of the server with a pong. The heartbeat of the server then finds the client alive. */\n readonly autoPong: true;\n /** This time bounds the close handshake of ws, in milliseconds. The wrapper gives its own bound, `close_bound`. */\n readonly closeTimeout: number;\n /** ws sets no keepalive on its socket. The wrapper turns it on only when the declaration gives an idle time. */\n readonly keepAlive: boolean;\n /** This time is the idle time of TCP keepalive, in milliseconds. Node truncates it to whole seconds. */\n readonly keepAliveInitialDelay?: number | undefined;\n /** These headers go with the upgrade request, for example `authorization`. */\n readonly headers?: Readonly<Record<string, string>> | undefined;\n}\n\n/** These are the arguments of `new WebSocket(url, protocols, options)`. */\nexport interface WsConnectOptions {\n readonly url: string;\n readonly protocols: readonly string[];\n readonly options: WsDriverOptions;\n}\n\n/** A connection factory makes one connection from its options. The connection opens later, and tells it with the event `open`. */\nexport type WsFactory = Factory<WsConnectOptions, WsConnection>;\n\n/** This type is the part of a `WebSocket` of ws 8 that the adapter uses. */\ninterface WsDriver {\n on(event: string, listener: (...args: never[]) => void): unknown;\n send(data: string | Buffer, callback: (error?: Error | null) => void): void;\n ping(data: Buffer): void;\n close(code: number, reason: string): void;\n terminate(): void;\n readonly protocol: string;\n}\n\n/** This function gives a message of ws as one buffer. At the default `binaryType` of ws, a message is a buffer already. */\nconst asBuffer = (data: unknown): Buffer => {\n if (Buffer.isBuffer(data)) return data;\n if (Array.isArray(data)) return Buffer.concat(data as Buffer[]);\n return Buffer.from(data as ArrayBuffer);\n};\n\n/**\n * This function adapts a `WebSocket` of ws to a connection. It listens to `error` at once, because\n * in Node an error with no listener throws. It takes the socket from the event `upgrade`.\n */\nexport const adaptWs = (ws: WsDriver): WsConnection => {\n let socket: ClientSocket | undefined;\n let listeners: WsListeners | null = null;\n const held: Array<(given: WsListeners) => void> = [];\n const deliver = (event: (given: WsListeners) => void): void => {\n if (listeners === null) held.push(event);\n else event(listeners);\n };\n ws.on(\"upgrade\", (response: { readonly socket?: ClientSocket }) => {\n socket = response.socket;\n });\n ws.on(\"open\", () => deliver((given) => given.open()));\n ws.on(\"message\", (data: unknown, binary: boolean) => deliver((given) => given.message(asBuffer(data), binary)));\n ws.on(\"pong\", (data: Buffer) => deliver((given) => given.pong(data)));\n ws.on(\"close\", (code: number, reason: Buffer) => deliver((given) => given.close(code, reason.toString(\"utf8\"))));\n ws.on(\"error\", (error: Error) => deliver((given) => given.error(error)));\n return {\n listen(given) {\n listeners = given;\n for (const event of held.splice(0)) event(given);\n },\n send: (data, callback) => ws.send(data, (error) => callback(error ?? undefined)),\n ping: (data) => ws.ping(data),\n close: (code, reason) => ws.close(code, reason),\n terminate: () => ws.terminate(),\n get socket() {\n return socket;\n },\n get protocol() {\n return ws.protocol;\n },\n };\n};\n\n/**\n * This factory makes a `WebSocket` of ws. It loads ws only when a client connects, thus a service\n * that does not use WebSocket does not need it. The specifier is a value, thus the package needs no types of ws.\n */\nexport const nodeWs: WsFactory = async ({ url, protocols, options }) => {\n const specifier = \"ws\";\n type Constructor = new (url: string, protocols: string[], options: WsDriverOptions) => WsDriver;\n const loaded = (await import(specifier)) as { readonly WebSocket?: Constructor; readonly default?: Constructor };\n const WebSocket = loaded.WebSocket ?? loaded.default;\n if (WebSocket === undefined) throw new Error(\"ws gives no WebSocket class\");\n return adaptWs(new WebSocket(url, [...protocols], options));\n};\n","import type { Detector, Meeting, Situation } from \"../client/meets.ts\";\nimport { NEVER, WHAT, immediate, keepaliveAnswered, keepaliveReset, keepaliveSilent, kernelGiveUp } from \"../client/meets.ts\";\nimport { finiteMs } from \"../client/core.ts\";\nimport { heartbeatDetection } from \"../timing.ts\";\nimport type { WsKnobs } from \"./client.ts\";\n\n/**\n * This function gives the manifestations that a WebSocket client can meet, and the detectors of\n * each, from its knobs. The WS cells measured them on ws 8:\n * - At a heartbeat of 2 s, a timeout of 1 s and 1 miss, the drop came 0.999 s to 1.0035 s after the\n * ping that got no pong. The slowest of 495 runs took 1.003761 s.\n * - `terminate()` closes the socket 0.3 ms to 1 ms after the drop. The close of the global WebSocket\n * only writes a close frame, and its socket stayed ESTABLISHED.\n * - The kernel of a frozen server acknowledges each ping, thus only the heartbeat finds it.\n * - At `tcp_retries2 = 3`, the kernel gave up before a pong timeout of 10 s.\n * - Without a heartbeat, the client waited for the keepalive of undici: 70 s. ws sets no keepalive.\n */\nexport const wsMeetings = (name: string, knobs: WsKnobs, misses: number): Meeting[] => {\n const interval = finiteMs(knobs.heartbeat_interval);\n const detection = heartbeatDetection(knobs.heartbeat_interval, knobs.heartbeat_timeout, misses);\n const heartbeat = (situation: Situation): Detector => ({\n by: \"heartbeat_detection_time\",\n within: detection,\n situation,\n effect: `the heartbeat destroys the socket with terminate(), each call in flight fails as indeterminate, and ${name} connects again`,\n });\n const call: Detector = {\n by: \"request_timeout\",\n within: knobs.request_timeout,\n situation: \"in flight\",\n effect: \"the timer of the wrapper ends the call as indeterminate, and the connection stays\",\n };\n const giveUp = kernelGiveUp(knobs.retrans_giveup_time, \"each call in flight fails as indeterminate\");\n const silent = (manifestation: \"blackhole\" | \"freeze-host\"): Meeting => ({\n manifestation,\n what: WHAT[manifestation],\n detectors: [call, heartbeat(\"in flight\"), giveUp, heartbeat(\"idle\"), keepaliveSilent(knobs.keepalive_idle)],\n });\n return [\n {\n manifestation: \"rst\",\n what: WHAT.rst,\n detectors: [\n immediate(\"in flight\", \"ws closes with code 1006, the call fails as indeterminate, and the client connects again\"),\n {\n by: \"heartbeat_interval\",\n within: interval === undefined ? NEVER : knobs.heartbeat_interval,\n situation: \"idle\",\n effect: \"the next ping frame gets a reset, and the client connects again\",\n },\n keepaliveReset(knobs.keepalive_idle),\n ],\n },\n {\n manifestation: \"fin\",\n what: WHAT.fin,\n detectors: [\n immediate(\"in flight\", \"the FIN arrives, ws closes with code 1006, and the call fails as indeterminate\"),\n immediate(\"idle\", \"the FIN arrives, ws closes with code 1006, and the client connects again\"),\n ],\n },\n silent(\"blackhole\"),\n silent(\"freeze-host\"),\n {\n manifestation: \"freeze-process\",\n what: WHAT[\"freeze-process\"],\n detectors: [call, heartbeat(\"in flight\"), heartbeat(\"idle\"), keepaliveAnswered()],\n },\n {\n manifestation: \"app-stall\",\n what: \"the server holds its reply, for example in a slow handler, and its library still answers each ping\",\n detectors: [call, { ...heartbeat(\"idle\"), within: NEVER, effect: \"the library of the server still answers each ping with a pong\" }, keepaliveAnswered()],\n },\n ];\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport { realClock } from \"../clock.ts\";\nimport type {\n Attempt,\n CheckOptions,\n Client,\n ClientJoin,\n ClientKnobs,\n ClientOptions,\n ClientSpec,\n ClientStep,\n DialFn,\n RetryKnobs,\n SendOptions,\n Sent,\n Settle,\n} from \"../client/core.ts\";\nimport {\n ageProblems,\n attachClient,\n clientBounds,\n clientProblems,\n clientRecorder,\n dialCheck,\n finiteMs,\n inFlight,\n localStateCheck,\n message,\n missesProblems,\n ms,\n notSent,\n operationTable,\n readyWait,\n retryBackoff,\n retryProblems,\n retrySchedule,\n roundTripCheck,\n sendWithReissue,\n socketEnds,\n stepChecks,\n timeProblems,\n trackWithTimer,\n wholeProblems,\n withinBound,\n wrapperTimerProblems,\n zeroProblems,\n} from \"../client/core.ts\";\nimport type { Judge } from \"../client/dial.ts\";\nimport { authority, helloDial, statusLineJudge } from \"../client/dial.ts\";\nimport type { Dependency } from \"../description.ts\";\nimport type { Heartbeat } from \"../heartbeat.ts\";\nimport { heartbeat } from \"../heartbeat.ts\";\nimport type { DependencyHandle } from \"../lifecycle.ts\";\nimport type { Operation } from \"../operation.ts\";\nimport { heartbeatDetection } from \"../timing.ts\";\nimport type { WsConnectOptions, WsConnection, WsFactory } from \"./connection.ts\";\nimport { nodeWs } from \"./connection.ts\";\nimport { wsMeetings } from \"./meets.ts\";\n\n/**\n * These are the knobs of the client, each a time, by the names of the ordering law. The runtime has\n * no defaults, thus each knob is necessary. A bound that nobody gives is \"infinite\", not zero. The\n * constant `WS_CLIENT_STARTING_KNOBS` gives a declaration from the WS cells. The knobs that each client has come from the core.\n */\nexport interface WsKnobs extends ClientKnobs, RetryKnobs {\n /** This time bounds the TCP connect and the upgrade (`handshakeTimeout` of ws). The wrapper also bounds the open with it. */\n readonly connect_bound: Quantity;\n /** This time bounds each call: a request until its reply, and a ping until its pong. The wrapper has this timer, because ws has none. */\n readonly request_timeout: Quantity;\n /** This time is the interval of the ping frames of the heartbeat, from the start of one to the start of the next. \"infinite\" sends no ping. */\n readonly heartbeat_interval: Extent;\n /** This time bounds each ping of the heartbeat. A ping with no pong in this time is a miss. */\n readonly heartbeat_timeout: Quantity;\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 /** This time is the idle time of TCP keepalive (`keepAliveInitialDelay`), in whole seconds. \"infinite\" keeps keepalive off, as ws does. */\n readonly keepalive_idle: Extent;\n /** The client does not replace its connection at an age, thus this knob must be \"infinite\". */\n readonly max_connection_age: Extent;\n /** This time bounds the close at the shutdown: the calls in flight, then the close handshake. At the bound, `terminate()` destroys the socket. */\n readonly close_bound: Quantity;\n}\n\n/** A reply is the id of the request that a message answers, and the error of the server when the reply tells one. */\nexport interface WsReply {\n readonly id: string;\n readonly error?: string | undefined;\n}\n\n/**\n * The replies of a protocol tell which message of the server answers which request. WebSocket has no\n * reply of its own: each application protocol puts an id in its messages, for example JSON-RPC 2.0.\n */\nexport interface WsReplies {\n /** This function reads one message of the server. It gives its reply, or null for a message that answers no request. */\n reply(data: Buffer, binary: boolean): WsReply | null;\n}\n\n/** A WebSocket client spec is the declaration of one WebSocket client: where it connects, and its knobs. */\nexport interface WsClientSpec extends ClientSpec<WsKnobs> {\n /** This path is the path of the upgrade request. The default is \"/\". */\n readonly path?: string | undefined;\n /** These names are the subprotocols that the client offers (`Sec-WebSocket-Protocol`). */\n readonly protocols?: readonly string[] | undefined;\n /** These headers go with the upgrade request and with the dial, for example `authorization`. */\n readonly headers?: Readonly<Record<string, string>> | undefined;\n /** This count is the number of heartbeat misses in sequence that end the connection. The cells used 1. */\n readonly heartbeatMisses: number;\n /** These replies match a message of the server to its request. Without them, the client sends no request. */\n readonly replies?: WsReplies | undefined;\n /**\n * These operations are the calls of the service, by name, with their idempotency. A message does\n * not tell if the server removes its duplicates. Thus the client sends again only a call that this table names, and a ping.\n */\n readonly operations?: ClientSpec<WsKnobs>[\"operations\"];\n}\n\n/** These options make a client. Without them, it uses ws, the real clock, `Math.random`, and a dial with the upgrade request. */\nexport interface WsClientOptions extends ClientOptions<WsFactory> {\n readonly random?: (() => number) | undefined;\n /**\n * This function waits one turn of the event loop. The heartbeat waits it before it counts a miss,\n * thus a pong that is in the socket counts first. The default is `setImmediate`.\n */\n readonly turn?: (() => Promise<void>) | undefined;\n}\n\n/** A join is one generation of the client: one connection, when its upgrade ended with 101 (D1, D2). WebSocket gives no id of a connection. */\nexport interface WsJoin extends ClientJoin {\n /** This URL is the URL of the connection. */\n readonly url: string;\n /** This text is the subprotocol that the server chose, or \"\" when it chose none. */\n readonly protocol: string;\n}\n\n/** A request is one message to the server, with the id that its reply gives back. */\nexport interface WsRequest {\n readonly id: string;\n /** This data goes as one message. A string goes as a text message, and a buffer as a binary message. */\n readonly data: string | Buffer;\n}\n\n/** A response is the message of the server that answered a request. */\nexport interface WsResponse {\n readonly data: Buffer;\n readonly binary: boolean;\n}\n\n/** A message listener receives each message of the server that answers no request. */\nexport type WsMessageListener = (data: Buffer, binary: boolean) => void;\n\n/** A WebSocket client is a dependency of a service: its declaration, its handle, its checks and its calls. */\nexport interface WsClient extends Client<ClientStep, WsJoin> {\n /** This text is the state of the client, as `WS_CLIENT_MACHINE` names it. */\n readonly status: string;\n /**\n * This method sends a request, and waits for its reply. A reply is `ok`, or a certain error when it\n * tells one. The client sends again only an idempotent operation, one time at most, after an outcome that is not certain.\n */\n request(operation: string, call: WsRequest, options?: SendOptions): Promise<Sent<WsResponse>>;\n /** This method sends a ping frame, and waits for a pong. A pong is `ok`. A ping is idempotent, thus the client can send it again. */\n ping(options?: SendOptions): Promise<Sent<void>>;\n /** This method sends one message with no reply. It gives true when ws took the message, and false when the client is not ready. */\n send(data: string | Buffer): boolean;\n /** This method adds a listener of the messages that answer no request. It gives the function that removes it. */\n onMessage(listener: WsMessageListener): () => void;\n}\n\n/** These operations are in each table: a ping frame is idempotent (RFC 6455 §5.5.2). */\nexport const WS_OPERATIONS: Readonly<Record<string, Operation>> = { ping: { idempotent: true } };\n\n/** This function gives the problems of a spec: the knobs that the client cannot use. */\nexport const specProblems = (name: string, spec: WsClientSpec): string[] => {\n const { knobs } = spec;\n const problems = [\n ...timeProblems(name, knobs),\n // ws has no timer of a call. The open and the pings have a timer of the wrapper too\n ...wrapperTimerProblems(name, knobs, { connect_bound: \"an open\", request_timeout: \"a call\", heartbeat_timeout: \"a ping of the heartbeat\" }),\n ...zeroProblems(name, \"the client\", knobs, { open: [\"heartbeat_interval\", \"keepalive_idle\"] }),\n // Node truncates the keepalive delay to whole seconds\n ...wholeProblems(name, knobs, { seconds: [\"keepalive_idle\"] }),\n ...retryProblems(name, knobs),\n ...missesProblems(name, spec.heartbeatMisses, \"heartbeat\"),\n ...ageProblems(name, knobs, \"the client does not replace its connection at an age\"),\n ];\n // one ping at a time: the time of detection holds only when each tick sends a ping\n const interval = finiteMs(knobs.heartbeat_interval);\n const timeout = finiteMs(knobs.heartbeat_timeout);\n if (interval !== undefined && interval > 0 && timeout !== undefined && spec.heartbeatMisses > 1 && timeout >= interval)\n problems.push(`${name}'s heartbeat_timeout is not below its heartbeat_interval: with one ping at a time, a tick sends no ping while a ping waits`);\n if (spec.path !== undefined && !spec.path.startsWith(\"/\")) problems.push(`${name}'s path does not start with \"/\"`);\n if (spec.password !== undefined) problems.push(`${name} takes no password: give the credentials in headers, for example authorization`);\n return problems;\n};\n\n/** This function gives the URL of a spec. The client speaks ws:// only. */\nexport const wsUrl = (spec: WsClientSpec): string => `ws://${authority(spec.host, spec.port)}${spec.path ?? \"/\"}`;\n\n/** This function gives the arguments of `new WebSocket` from a spec. Each timer of ws comes from the declaration. */\nexport const wsOptions = (spec: WsClientSpec): WsConnectOptions => {\n const { knobs } = spec;\n const keepalive = finiteMs(knobs.keepalive_idle);\n return {\n url: wsUrl(spec),\n protocols: spec.protocols ?? [],\n options: {\n handshakeTimeout: ms(knobs.connect_bound),\n autoPong: true,\n closeTimeout: ms(knobs.close_bound),\n keepAlive: keepalive !== undefined,\n ...(keepalive === undefined ? {} : { keepAliveInitialDelay: keepalive }),\n ...(spec.headers === undefined ? {} : { headers: spec.headers }),\n },\n };\n};\n\n/** The key of the dial. RFC 6455 §1.3 gives it as its example. The dial does not read the accept of the server. */\nconst DIAL_KEY = \"dGhlIHNhbXBsZSBub25jZQ==\";\n\n/** This function gives the bytes of the dial: the upgrade request of RFC 6455 §4.1, with the subprotocols and the headers of the spec. */\nexport const upgradeRequest = (spec: Pick<WsClientSpec, \"host\" | \"port\" | \"path\" | \"protocols\" | \"headers\">): string => {\n const lines = [\n `GET ${spec.path ?? \"/\"} HTTP/1.1`,\n `Host: ${authority(spec.host, spec.port)}`,\n \"Upgrade: websocket\",\n \"Connection: Upgrade\",\n `Sec-WebSocket-Key: ${DIAL_KEY}`,\n \"Sec-WebSocket-Version: 13\",\n ...(spec.protocols === undefined || spec.protocols.length === 0 ? [] : [`Sec-WebSocket-Protocol: ${spec.protocols.join(\", \")}`]),\n ...Object.entries(spec.headers ?? {}).map(([header, value]) => `${header}: ${value}`),\n ];\n return `${lines.join(\"\\r\\n\")}\\r\\n\\r\\n`;\n};\n\n/** This judge reads the status line of the answer to the upgrade. Only 101 passes: a WebSocket server takes the upgrade. */\nexport const upgradeJudge: Judge = statusLineJudge((status, line) =>\n status === 101 ? null : new Error(`the server answered ${JSON.stringify(line)} to the upgrade, not 101`),\n);\n\n/** This function makes the dial of a spec: a new TCP connection, the upgrade request, and a wait for 101. The dial then destroys its socket. */\nexport const wsDial =\n (spec: Pick<WsClientSpec, \"path\" | \"protocols\" | \"headers\">): DialFn =>\n (host, port, signal) =>\n helloDial(upgradeRequest({ ...spec, host, port }), upgradeJudge)(host, port, signal);\n\n/** This function gives the payload of a ping: its sequence number as text. */\nconst payload = (sequence: number): Buffer => Buffer.from(String(sequence), \"latin1\");\n\n/**\n * This function makes a WebSocket client, a dependency of a service over ws 8.\n * - It has one connection as one generation. The end of the connection ends the generation. The\n * client then connects again by itself, after a backoff.\n * - Its calls are a request until its reply, and a ping until its pong. Each call has a timer of\n * the wrapper, because ws has none.\n * - Its heartbeat sends a ping frame each interval, one at a time. A ping with no pong in its timeout\n * is a miss. At the last miss, `terminate()` destroys the socket at once, and the client connects again.\n * - It tells the outcome of a call in three values, and it settles each call in flight at the end of its generation (C8, C10).\n * - It records the URL and the ends of the socket of each connection at its open (D1).\n *\n * These parts come from the core of the clients: the outcomes, the second try and the wait for a ready\n * client. The backoff and its schedule, the calls in flight, the bounded close and the checks come from\n * the core too. The heartbeat is the heartbeat of the package. This entry gives what ws does in its own way.\n */\nexport const wsClient = (name: string, spec: WsClientSpec, options: WsClientOptions = {}): WsClient => {\n const problems = specProblems(name, spec);\n if (problems.length > 0) throw new Error(problems.join(\"; \"));\n const { knobs } = spec;\n const clock = options.clock ?? realClock;\n const random = options.random ?? Math.random;\n const create = options.create ?? nodeWs;\n const dial = options.dial ?? wsDial(spec);\n const turn = options.turn ?? (() => new Promise<void>((resolve) => setImmediate(resolve)));\n const operations = operationTable(WS_OPERATIONS, spec.operations);\n const requestTimeout = ms(knobs.request_timeout);\n const url = wsUrl(spec);\n\n let status = \"none\";\n let connection: WsConnection | null = null;\n // the token of the open that is current, and the sequence of the ping frames\n let token = 0;\n let sequence = 0;\n let beat: Heartbeat | null = null;\n // this function ends the wait of the close for the close frame of the server\n let closed: (() => void) | null = null;\n const retries = retrySchedule(clock, retryBackoff(knobs), random);\n const joins: WsJoin[] = [];\n const flight = inFlight();\n const readied = readyWait(clock, () => status === \"ready\");\n const replies = new Map<string, Settle<WsResponse>>();\n const pings = new Map<number, Settle<void>>();\n const listeners = new Set<WsMessageListener>();\n\n const emit = clientRecorder(options.record, clock, name);\n const record = (on: string, to: string = status, detail?: string): void => {\n const from = status;\n status = to;\n emit(from, to, on, detail);\n };\n const closing = (): boolean => status === \"closing\" || status === \"end\";\n\n /** This function waits for the backoff of the next try, then opens a new generation. */\n const scheduleRetry = (): void =>\n retries.schedule(() => {\n if (closing()) return;\n record(\"retry.connect\", \"connecting\", `try ${retries.tries}, ${url}`);\n void open();\n });\n\n /** This function ends the generation that is open. It destroys the socket, because a close frame to a silent server gets no answer. */\n const lose = (on: string, detail: string): void => {\n const c = connection;\n if (c === null) return;\n connection = null;\n beat?.stop();\n beat = null;\n if (closing()) return;\n record(on, \"failing\", detail);\n flight.settleAll(`the generation ended with the call in flight (${detail})`);\n c.terminate();\n scheduleRetry();\n };\n\n /** This function gives a message of the server to the call that waits for it, or to the listeners. A late reply goes to nobody. */\n const receive = (data: Buffer, binary: boolean): void => {\n let matched: WsReply | null = null;\n try {\n matched = spec.replies?.reply(data, binary) ?? null;\n } catch {\n // a message that the replies cannot read answers no request\n }\n if (matched !== null) {\n replies.get(matched.id)?.(\n matched.error === undefined ? { outcome: \"ok\", value: { data, binary } } : { outcome: \"error\", error: matched.error, reply: true },\n );\n return;\n }\n for (const listener of [...listeners]) listener(data, binary);\n };\n\n /** This function counts a pong. Each pong answers the heartbeat. A pong for a ping answers each earlier ping too, as RFC 6455 §5.5.3 lets it. */\n const pong = (data: Buffer): void => {\n beat?.answer();\n const text = data.toString(\"latin1\");\n if (!/^\\d{1,15}$/.test(text)) return;\n const upTo = Number(text);\n for (const [at, settle] of [...pings]) if (at <= upTo) settle({ outcome: \"ok\", value: undefined });\n };\n\n /** This function starts the heartbeat of a generation: one ping at a time, while the client is ready. */\n const startBeat = (): Heartbeat =>\n heartbeat({\n interval: ms(knobs.heartbeat_interval),\n timeout: ms(knobs.heartbeat_timeout),\n misses: spec.heartbeatMisses,\n clock,\n turn,\n due: () => status === \"ready\",\n live: () => status === \"ready\",\n ping: () => {\n if (connection === null) throw new Error(`${name} has no connection`);\n connection.ping(payload((sequence += 1)));\n },\n onMiss: (misses) => record(\"heartbeat.miss\", status, `${misses} of ${spec.heartbeatMisses}`),\n onDead: () => lose(\"heartbeat.dead\", `no pong in heartbeat_timeout, ${spec.heartbeatMisses} times in sequence`),\n });\n\n const join = (c: WsConnection): void => {\n connection = c;\n retries.reset();\n const generation = joins.length + 1;\n joins.push({ generation, at: clock.now(), url, protocol: c.protocol, ...socketEnds(c.socket) });\n record(\"join\", \"ready\", `generation ${generation}, ${url}`);\n beat = startBeat();\n readied.wake();\n };\n\n /**\n * This function opens one generation in `connect_bound`. A failure goes to `failing`, and the\n * backoff starts. A connection that opens after its bound or after the close is destroyed at once.\n * The promise resolves when this try ends.\n */\n const open = (): Promise<void> => {\n const mine = (token += 1);\n let opened: WsConnection | null = null;\n let joined = false;\n let finish: () => void = () => {};\n const ended = new Promise<void>((resolve) => (finish = resolve));\n const stale = (): boolean => mine !== token || closing();\n const fail = (detail: string): void => {\n bound();\n finish();\n if (stale()) return;\n token += 1;\n opened?.terminate();\n record(\"connect.failed\", \"failing\", detail);\n scheduleRetry();\n };\n const bound = clock.after(ms(knobs.connect_bound), () => fail(\"no open connection in connect_bound\"));\n void (async () => {\n const c = await create(wsOptions(spec));\n opened = c;\n if (stale()) {\n c.terminate();\n return;\n }\n c.listen({\n open: () => {\n if (stale()) {\n c.terminate();\n return;\n }\n bound();\n joined = true;\n join(c);\n finish();\n },\n message: (data, binary) => {\n if (c === connection) receive(data, binary);\n },\n pong: (data) => {\n if (c === connection) pong(data);\n },\n close: (code, reason) => {\n if (c === connection) closed?.();\n if (joined) {\n if (c === connection) lose(\"connection.end\", `code ${code}${reason === \"\" ? \"\" : `, ${reason}`}`);\n } else if (!stale()) fail(`the connection closed before its open, with code ${code}`);\n },\n error: (error) => {\n if (joined) {\n if (c === connection) record(\"connection.error\", status, message(error));\n } else if (!stale()) fail(message(error));\n },\n });\n })().catch((error: unknown) => fail(message(error)));\n return ended;\n };\n\n /** This function gives the connection for a call, or the reason that the call does not leave the client. */\n const usable = (signal: AbortSignal | undefined): WsConnection | Attempt<never> => {\n if (status === \"none\") return notSent(`${name} has not connected`);\n if (status !== \"ready\" || connection === null) return notSent(`${name} is ${status}, not ready: the call did not leave the client`);\n if (signal?.aborted) return notSent(\"the caller aborted the call before it left the client\");\n return connection;\n };\n\n /** This function makes one try of a request: the write, then the reply, in `request_timeout`. */\n const requestAttempt = (call: WsRequest, signal: AbortSignal | undefined): Promise<Attempt<WsResponse>> => {\n const c = usable(signal);\n if (\"outcome\" in c) return Promise.resolve(c);\n if (spec.replies === undefined) return Promise.resolve(notSent(`${name} has no replies in its spec: no message can answer a request`));\n if (replies.has(call.id)) return Promise.resolve(notSent(`${name} has a request with the id ${call.id} in flight`));\n return trackWithTimer<WsResponse>(\n flight,\n clock,\n requestTimeout,\n signal,\n \"the caller abandoned the request after it was written\",\n \"no reply in request_timeout: the server can have applied the request\",\n (settle) => {\n replies.set(call.id, settle);\n try {\n c.send(call.data, (error) => {\n if (error !== undefined) settle({ outcome: \"indeterminate\", error: `the write failed: ${message(error)}` });\n });\n } catch (error) {\n settle(notSent(message(error)));\n }\n },\n () => {\n replies.delete(call.id);\n },\n );\n };\n\n /** This function makes one try of a ping: the ping frame, then a pong, in `request_timeout`. */\n const pingAttempt = (signal: AbortSignal | undefined): Promise<Attempt<void>> => {\n const c = usable(signal);\n if (\"outcome\" in c) return Promise.resolve(c);\n const at = (sequence += 1);\n return trackWithTimer<void>(\n flight,\n clock,\n requestTimeout,\n signal,\n \"the caller abandoned the ping after it was written\",\n \"no pong in request_timeout\",\n (settle) => {\n pings.set(at, settle);\n try {\n c.ping(payload(at));\n } catch (error) {\n settle(notSent(message(error)));\n }\n },\n () => {\n pings.delete(at);\n },\n );\n };\n\n const request = (operation: string, call: WsRequest, sendOptions: SendOptions = {}): Promise<Sent<WsResponse>> =>\n sendWithReissue({\n clock,\n bound: requestTimeout,\n operation: operations[operation],\n options: sendOptions,\n attempt: (signal) => requestAttempt(call, signal),\n // the client connects again by itself: the second try waits for a ready client, in the time that is left\n beforeAgain: (left, signal) => readied.wait(left, signal),\n onReissue: () => record(\"command.reissue\", status, operation),\n });\n\n const ping = (sendOptions: SendOptions = {}): Promise<Sent<void>> =>\n sendWithReissue({\n clock,\n bound: requestTimeout,\n operation: operations[\"ping\"],\n options: sendOptions,\n attempt: (signal) => pingAttempt(signal),\n beforeAgain: (left, signal) => readied.wait(left, signal),\n onReissue: () => record(\"command.reissue\", status, \"ping\"),\n });\n\n const send = (data: string | Buffer): boolean => {\n const c = usable(undefined);\n if (\"outcome\" in c) return false;\n try {\n c.send(data, () => {});\n return true;\n } catch {\n return false;\n }\n };\n\n const handle: DependencyHandle = {\n async connect(signal) {\n if (signal.aborted) throw new Error(`${name}: the service stopped before the connect`);\n record(\"status.connecting\", \"connecting\");\n // a server that is down does not stop the startup: the client connects again in the background,\n // and its checks keep readiness tripped until it is ready (readiness is earned)\n await open();\n },\n async close() {\n if (status === \"none\" || closing()) return;\n record(\"close.request\", \"closing\");\n retries.cancel();\n // an open in flight is stale now: its connection is destroyed when it opens\n token += 1;\n beat?.stop();\n beat = null;\n const c = connection;\n if (c !== null) {\n // the calls in flight end first, then the close handshake. A silent server answers neither: the bound destroys the socket\n const handshake = flight.idle().then(\n () =>\n new Promise<void>((resolve) => {\n closed = resolve;\n c.close(1000, \"the client closed\");\n }),\n );\n if (!(await withinBound(clock, ms(knobs.close_bound), handshake))) {\n record(\"close.bound\", \"closing\");\n record(\"close.destroy\", \"closing\");\n c.terminate();\n }\n closed = null;\n }\n connection = null;\n flight.settleAll(\"the client closed with the call in flight\");\n record(\"status.end\", \"end\");\n },\n };\n\n const interval = finiteMs(knobs.heartbeat_interval);\n const declaration: Omit<Dependency, \"name\"> = {\n system: spec.system,\n knobs: {\n connect_bound: knobs.connect_bound,\n request_timeout: knobs.request_timeout,\n heartbeat_interval: knobs.heartbeat_interval,\n heartbeat_timeout: knobs.heartbeat_timeout,\n // the last of the misses in sequence fails at its timeout: misses × interval + timeout\n heartbeat_detection_time: heartbeatDetection(knobs.heartbeat_interval, knobs.heartbeat_timeout, spec.heartbeatMisses),\n retry_base: knobs.retry_base,\n retry_cap: knobs.retry_cap,\n keepalive_idle: knobs.keepalive_idle,\n // a ping frame each interval refreshes an idle flow (O03). With no heartbeat, keepalive does\n idle_refresh_interval: interval === undefined ? knobs.keepalive_idle : knobs.heartbeat_interval,\n max_connection_age: knobs.max_connection_age,\n close_bound: knobs.close_bound,\n retrans_giveup_time: knobs.retrans_giveup_time,\n },\n operations,\n };\n\n const checks = (checkOptions: CheckOptions) =>\n stepChecks<ClientStep>(name, checkOptions, {\n // the state of the client, without a round trip\n \"local-state\": localStateCheck(() => status === \"ready\"),\n // a ping frame on the connection of the application, until its pong\n \"round-trip\": roundTripCheck((sendOptions) => ping(sendOptions)),\n // a new connection each time, with the upgrade request\n dial: dialCheck(dial, spec.host, spec.port),\n });\n\n return {\n name,\n declaration,\n handle,\n meets: wsMeetings(name, knobs, spec.heartbeatMisses),\n get status() {\n return status;\n },\n joins,\n request,\n ping,\n send,\n onMessage(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n checks,\n attach: (svc, checkOptions) => attachClient(svc, name, declaration, handle, checks(checkOptions)),\n problems: (description) => clientProblems(name, description, clientBounds(knobs)),\n };\n};\n","import type { Machine } from \"@mark1russell7/algebra\";\nimport { clientKnob as knob, closeTimer, transition as t } from \"../client/machine.ts\";\n\n/** The states where the client can close, and the states where a call can start its second try. */\nconst SERVING = [\"connecting\", \"ready\", \"failing\"] as const;\nconst LIVE = [...SERVING, \"closing\"] as const;\n\n/**\n * The WebSocket client of the service as a `mark1russell7.machine/1` document. It is one generation\n * at a time: one `WebSocket` of ws 8. The states:\n * - `ready`: the upgrade ended with 101, and the client sends each call. Its heartbeat sends a ping frame each interval.\n * - `failing`: no generation is open. The client connects again after its backoff.\n * - `closing`: the calls in flight, then the close handshake, in `close_bound`. At the bound, `terminate()`.\n *\n * The end of the connection and the last miss of the heartbeat end the generation. A test records\n * the transitions of the client and replays them against this document.\n */\nexport const WS_CLIENT_MACHINE: Machine = {\n format: \"mark1russell7.machine/1\",\n id: \"service.machine.websocket-client\",\n title: \"The WebSocket client of the service, over ws 8\",\n layer: \"driver\",\n version: \"0.1.0\",\n sources: [{ ref: \"packages/service/src/websocket (the client implements this document)\", rung: \"implemented\" }],\n states: {\n none: { initial: true, note: \"the client exists, and its first connection does not\" },\n connecting: { note: \"the TCP connect and the upgrade, in connect_bound\" },\n ready: { note: \"the connection is open. The heartbeat finds a silent server, and the timer of a call ends a call that waits\" },\n failing: { note: \"no generation is open. The client connects again after its backoff\" },\n closing: { note: \"the calls in flight, then the close handshake, in close_bound. At the bound, terminate() destroys the socket\" },\n end: { final: true, note: \"the service closed the client\" },\n },\n events: {\n \"status.connecting\": { kind: \"local\" },\n join: { kind: \"rx\", note: \"the upgrade ended with 101: a new generation (D1)\" },\n \"connect.failed\": { kind: \"local\", note: \"the connect or the upgrade failed, or did not end in connect_bound\" },\n \"retry.connect\": { kind: \"timer\", note: \"the backoff ended: a new try\" },\n \"connection.error\": { kind: \"rx\", note: \"an error event of the connection. Its close follows\" },\n \"connection.end\": { kind: \"rx\", note: \"the connection closed: a reset, a FIN, a close frame of the server, or the kernel\" },\n \"heartbeat.miss\": { kind: \"timer\", note: \"a ping frame got no pong in heartbeat_timeout\" },\n \"heartbeat.dead\": { kind: \"local\", note: \"the last miss in sequence: terminate() destroys the socket at once\" },\n \"command.reissue\": { kind: \"local\" },\n \"close.request\": { kind: \"local\" },\n \"close.bound\": { kind: \"timer\", note: \"the calls in flight or the close handshake did not end in close_bound\" },\n \"close.destroy\": { kind: \"local\", note: \"terminate() destroys a socket that a silent server does not close\" },\n \"status.end\": { kind: \"local\" },\n },\n knobs: {\n connect_bound: knob(\"handshakeTimeout\"),\n heartbeat_interval: knob(\"heartbeat.interval\"),\n heartbeat_timeout: knob(\"heartbeat.timeout\"),\n retry_base: knob(\"retry.base\"),\n retry_cap: knob(\"retry.cap\"),\n close_bound: knob(\"close.bound\"),\n },\n timers: {\n connect_timeout: {\n duration: { knob: \"connect_bound\", unit: \"s\" },\n arms: [\"enter:connecting\"],\n cancels: [\"join\", \"connect.failed\"],\n fires: \"connect.failed\",\n note: \"the bound of the wrapper on the open. ws bounds the connect and the upgrade with the same time\",\n },\n heartbeat: {\n duration: { expr: \"heartbeat_interval + heartbeat_timeout\", unit: \"s\" },\n arms: [\"join\", \"heartbeat.miss\"],\n cancels: [\"connection.end\", \"heartbeat.dead\", \"close.request\"],\n fires: \"heartbeat.miss\",\n note: \"one ping at a time: a ping frame each interval, and a miss at its timeout with no pong. Each pong starts it again\",\n },\n retry: {\n duration: { expr: \"min(retry_cap, retry_base * pow(2, fired)) + retry_base\", unit: \"s\" },\n arms: [\"connect.failed\", \"connection.end\", \"heartbeat.dead\"],\n cancels: [\"close.request\"],\n fires: \"retry.connect\",\n backoff: { resets: [\"join\"] },\n note: \"the most delay: the backoff doubles from retry_base to retry_cap, with a jitter up to retry_base\",\n },\n close: closeTimer(),\n },\n transitions: [\n t(\"connect\", \"none\", \"status.connecting\", \"connecting\"),\n t(\"join\", \"connecting\", \"join\", \"ready\"),\n t(\"connect-failed\", \"connecting\", \"connect.failed\", \"failing\", { timer: \"connect_timeout\" }),\n t(\"retry\", \"failing\", \"retry.connect\", \"connecting\", { timer: \"retry\" }),\n t(\"connection-end\", \"ready\", \"connection.end\", \"failing\"),\n t(\"heartbeat-miss\", \"ready\", \"heartbeat.miss\", \"ready\", { timer: \"heartbeat\" }),\n t(\"heartbeat-dead\", \"ready\", \"heartbeat.dead\", \"failing\"),\n t(\"connection-error-ready\", \"ready\", \"connection.error\", \"ready\"),\n t(\"connection-error-closing\", \"closing\", \"connection.error\", \"closing\"),\n ...LIVE.map((state) => t(`reissue-${state}`, state, \"command.reissue\", state)),\n t(\"close\", [...SERVING], \"close.request\", \"closing\"),\n t(\"close-bound\", \"closing\", \"close.bound\", \"closing\", { timer: \"close\" }),\n t(\"close-destroy\", \"closing\", \"close.destroy\", \"closing\"),\n t(\"end\", \"closing\", \"status.end\", \"end\"),\n ],\n};\n","import { STARTING_POLL } from \"../client/core.ts\";\nimport type { Poll } from \"../timing.ts\";\nimport type { WsKnobs } from \"./client.ts\";\n\n/**\n * This poll is the poll of the checks that `WS_CLIENT_STARTING_KNOBS` fits. It is `STARTING_POLL` of\n * the core: each 5 s with a jitter of 20 %, and a timeout of 4 s (B6, O12). The poll does not depend on the driver.\n */\nexport const WS_CLIENT_STARTING_POLL: Poll = STARTING_POLL;\n\n/** This count is the heartbeat misses to start from: 1, as in the WS cells. The drop came 0.999 s to 1.0035 s after the ping with no pong. */\nexport const WS_CLIENT_STARTING_MISSES = 1;\n\n/**\n * These knobs are a declaration to start from, from what the WS cells measured on ws 8. The runtime\n * has no defaults: a service gives these knobs, or its own. Each value holds the borders that the cells measured:\n * - `connect_bound` 5 s: the connect timeout of DP44. ws sets no handshake timeout by default.\n * - `request_timeout` 9.5 s: in the slowest poll of 10 s (O13).\n * - `heartbeat_interval` 4 s: in the WST cells, a ping each 4 s kept a flow through a router that\n * forgets it at 5 s. A ping each 6 s did not (O03).\n * - `heartbeat_timeout` 3 s: a stall of 1 s with a pong timeout of 3 s dropped nothing. The detection is 4 s + 3 s = 7 s.\n * - `retry_base` 0.5 s and `retry_cap` 4 s: the reconnect of the cells, from 500 ms, doubling to 4 s.\n * The client adds a jitter up to the base. Thus the most delay is 4.5 s, in the slowest poll of 10 s.\n * - `keepalive_idle` \"infinite\": ws sets no keepalive, and the heartbeat refreshes an idle flow.\n * - `max_connection_age` \"infinite\": the client does not replace its connection at an age.\n * - `close_bound` 2 s: a close frame to a silent server gets no answer. The `closeTimeout` of ws is 30 s.\n * - `retrans_giveup_time` 924.6 s: the give-up of Linux at `tcp_retries2 = 15`.\n */\nexport const WS_CLIENT_STARTING_KNOBS: WsKnobs = {\n connect_bound: { value: 5, unit: \"s\" },\n request_timeout: { value: 9.5, unit: \"s\" },\n heartbeat_interval: { value: 4, unit: \"s\" },\n heartbeat_timeout: { value: 3, unit: \"s\" },\n retry_base: { value: 0.5, unit: \"s\" },\n retry_cap: { value: 4, unit: \"s\" },\n keepalive_idle: { value: \"infinite\", unit: \"s\" },\n max_connection_age: { value: \"infinite\", unit: \"s\" },\n close_bound: { value: 2, unit: \"s\" },\n retrans_giveup_time: { value: 924.6, unit: \"s\" },\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/service",
4
- "version": "0.5.12",
4
+ "version": "0.5.13",
5
5
  "description": "A service declared as data before it starts, and its runtime: checks on jittered loops, a latch for each policy, probes from the cache, the startup in its sequence, and a staged, capped drain. The timer-ordering law checks the declaration. Its clock, heartbeat and backoff also work alone, in a browser too.",
6
6
  "keywords": [
7
7
  "service",
@@ -46,6 +46,26 @@
46
46
  "types": "./build/pg.d.ts",
47
47
  "default": "./build/pg.js"
48
48
  },
49
+ "./mongodb": {
50
+ "types": "./build/mongodb.d.ts",
51
+ "default": "./build/mongodb.js"
52
+ },
53
+ "./rabbitmq": {
54
+ "types": "./build/rabbitmq.d.ts",
55
+ "default": "./build/rabbitmq.js"
56
+ },
57
+ "./http-client": {
58
+ "types": "./build/http-client.d.ts",
59
+ "default": "./build/http-client.js"
60
+ },
61
+ "./websocket": {
62
+ "types": "./build/websocket.d.ts",
63
+ "default": "./build/websocket.js"
64
+ },
65
+ "./grpc": {
66
+ "types": "./build/grpc.d.ts",
67
+ "default": "./build/grpc.js"
68
+ },
49
69
  "./clock": {
50
70
  "types": "./build/clock.d.ts",
51
71
  "default": "./build/clock.js"
@@ -81,22 +101,39 @@
81
101
  "@mark1russell7/quantity": "^0.2.0"
82
102
  },
83
103
  "peerDependencies": {
104
+ "@grpc/grpc-js": "^1.14.6",
105
+ "amqplib": "^2.2.0",
84
106
  "ioredis": "^6.0.0",
107
+ "mongodb": "^7.7.0",
85
108
  "pg": "^8.23.1",
109
+ "ws": "^8.22.0",
86
110
  "zod": "^4.1.0"
87
111
  },
88
112
  "peerDependenciesMeta": {
113
+ "@grpc/grpc-js": {
114
+ "optional": true
115
+ },
116
+ "amqplib": {
117
+ "optional": true
118
+ },
89
119
  "ioredis": {
90
120
  "optional": true
91
121
  },
122
+ "mongodb": {
123
+ "optional": true
124
+ },
92
125
  "pg": {
93
126
  "optional": true
94
127
  },
128
+ "ws": {
129
+ "optional": true
130
+ },
95
131
  "zod": {
96
132
  "optional": true
97
133
  }
98
134
  },
99
135
  "devDependencies": {
136
+ "@grpc/grpc-js": "1.14.6",
100
137
  "@mark1russell7/cue": "github:mark1russell7/cue",
101
138
  "@mark1russell7/harness": "^0.12.4",
102
139
  "@types/node": "^26.6.4",
@@ -1 +0,0 @@
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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"}