@mark1russell7/service 0.5.8 → 0.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -395,7 +395,7 @@ if (!decision.allowed) return limitedResponse(decision); // 429, with Retry-Afte
395
395
  - **The hint.** The buffer stays, thus the hint of each response (`Keep-Alive: timeout=…`) is less than the real close. A Node without a buffer gives 0. The transport refuses a `server_idle_close` at or below the buffer. A time without a bound stops the timer.
396
396
  - **The three times.** `transport.knobs` adds `server_idle_close_hint` and `server_idle_close_buffer` to the knobs of the caller. Thus the declaration shows the hint, the buffer and the real close.
397
397
  - **The count.** The count of the work in flight comes from events. Each response counts until its `close` event, and each upgraded connection counts until it closes. The transport polls nothing.
398
- - **The stop of the intake.** `stopIntake()` closes the listener, thus each new connection gets a refusal. It closes the idle connections at once, and again at each `server_idle_sweep_interval`. Each later response gets `Connection: close`, and each later request gets a retryable 503.
398
+ - **The stop of the intake.** `stopIntake()` closes the listener, thus each new connection gets a refusal. It closes the idle connections at once, and again at each `server_idle_sweep_interval`. Each later response gets `Connection: close`, and each later request gets a retryable 503. A connection is not idle while the bytes of its answer go, thus a slow client gets the whole body (SYS-33).
399
399
  - **The drain and the close.** `drain(deadline, signal)` ends when no work is in flight, or at the deadline. It gives the cooperative abort to the handlers. `close()` destroys each connection, the upgraded connections too. A second close gives the same promise.
400
400
  - **The answers at the deadline.** At its deadline, the drain also waits for each answer that is to come or to go out. That is a response that `writeOutcome` writes, or an ended response whose bytes did not go.
401
401
  - **The streams of the lifecycle.** A response of `writeOutcome` holds the drain only when `life.request` of the lifecycle of the transport made its stream. `handle` gives such a stream. Another stream does not hold the drain, and the drain counts it.
@@ -411,7 +411,8 @@ if (!decision.allowed) return limitedResponse(decision); // 429, with Retry-Afte
411
411
  - **A mount through the drain.** A mount with `{ until: "close transports" }` answers until the close. While it is mounted, the listener stays open through the drain, and each other request gets a retryable 503.
412
412
  - **The probes on their own port.** `probeServer({ port, host, server_retry_after })` is a health server. It starts first and closes last, thus its probes answer through the whole shutdown. It has no defaults, thus `server_retry_after` is necessary, and each 503 of a probe gets its `Retry-After`.
413
413
  - **The probes on the port of the application.** `probeHandler(health, { prefix })` answers the same probes. Its mount answers until `close transports`, thus it answers through the drain. After the close of the transports, only a probe server answers.
414
- - **The probe paths.** The paths are `/startup`, `/readiness`, `/readiness/<controller>`, `/liveness` and `/report`, after the prefix. Each answer comes from the cache, as `application/health+json` with 200 or 503.
414
+ - **The probe paths.** The paths are `/startup`, `/readiness`, `/readiness/<controller>`, `/liveness` and `/report`, after the prefix. Each answer comes from the cache, as `application/health+json` with 200 or 503. A controller that the service does not have gets 404.
415
+ - **The safe answers.** Each JSON body escapes `<`, `>` and `&`, thus a part of the path in a body is not markup. Each answer gets `X-Content-Type-Options: nosniff` (SYS-34).
415
416
  - **The probe kinds.** The option `kinds` names the probes that answer, for example `["startup", "readiness", "liveness"]`. The default is all four of `PROBE_KINDS`.
416
417
  - **A kind that the list does not name.** Its path is not a probe. `probeHandler` gives it to the next handler, and `probeServer` answers it with 404. Thus a probe server can keep the report off its port.
417
418
  - **A bad list of kinds.** `probeHandler` and `probeServer` refuse an empty list, an unknown kind and a kind two times. The error names the bad part.
@@ -635,6 +636,7 @@ for await (const event of stream) {
635
636
  - **The process port.** The port of Node, on a fake host: the signals, the end of stdin and the close of the IPC channel. The tests also cover the messages, the hold, the flush and the exit. The lifecycle holds its fake process from its start to its exit.
636
637
  - **The HTTP edge.** The transport, the mounts, the upgrades, the probes and `handle` run on fake servers and the fake clock, with no socket. A service under `run()` drains a held request, and its probes answer through the drain. A request during the warm-up gets 503, and a stream ends in-band after its first item.
637
638
  - **The drain of the HTTP edge (SYS-29).** Each call that the deadline cuts gets its answer before its socket closes, with the cut first and with the transport first. Answers that do not go out by the cap give exit 0, and work that did not end by the cap gives exit 1. A stream outside the lifecycle does not hold the drain, and the record of the drain counts it.
639
+ - **The idle close of a slow answer (SYS-33).** A client that does not read gets each byte of a body of 64 MB. The stop of the intake and a sweep after it come before the client reads. These tests use real sockets of the loopback, but not on Windows.
638
640
  - **The headers of the outcomes.** Each path of the request tracker gets its status. Each 503 gets `Retry-After`: the refusals, the retryable cut and the cut of a unary stream. No 500 gets it.
639
641
  - **The headers on the port.** The transport adds `Retry-After` to each 503 of a probe and of a handler that writes its own status, in each form of `writeHead`. It takes `Retry-After` from each 500. The probe server gives its 503s the time of its knob.
640
642
  - **The conformance of a transport.** A reference transport passes each case, and each of nine mutations of it fails the case of its part. The HTTP transport passes each case on a fake server, for its requests and its upgrades.
package/build/http.d.ts CHANGED
@@ -117,6 +117,7 @@ export declare const pathOf: (url: string | undefined) => string;
117
117
  * connection until its `close`.
118
118
  * - `stopIntake` closes the listener, and new connections get a refusal. It closes the idle connections
119
119
  * at once and at each `server_idle_sweep_interval`. Each later response gets `Connection: close`.
120
+ * A connection is not idle while the bytes of its answer go (SYS-33).
120
121
  * - `drain` waits until no work is in flight, or until the deadline. It gives the cooperative abort to
121
122
  * the handlers through their context. At the deadline, it also waits for each answer that is to come
122
123
  * or to go out (SYS-29). The cap of the phase bounds that wait. Answers that did not go out by the
package/build/http.js CHANGED
@@ -97,6 +97,7 @@ const STATUS_TEXT = {
97
97
  * connection until its `close`.
98
98
  * - `stopIntake` closes the listener, and new connections get a refusal. It closes the idle connections
99
99
  * at once and at each `server_idle_sweep_interval`. Each later response gets `Connection: close`.
100
+ * A connection is not idle while the bytes of its answer go (SYS-33).
100
101
  * - `drain` waits until no work is in flight, or until the deadline. It gives the cooperative abort to
101
102
  * the handlers through their context. At the deadline, it also waits for each answer that is to come
102
103
  * or to go out (SYS-29). The cap of the phase bounds that wait. Answers that did not go out by the
@@ -300,6 +301,23 @@ const httpTransport = (server, knobs, options = {}) => {
300
301
  settled();
301
302
  });
302
303
  };
304
+ /**
305
+ * Node counts a connection idle at the `end()` of its response, also while the bytes of the body
306
+ * still go. Then `closeIdleConnections` destroys the socket and cuts the answer (SYS-33). This
307
+ * method replaces it on the server, thus `server.close()` uses it too. Node keeps the socket of
308
+ * a response that did not end. Thus the method shows each answer whose bytes did not go as not
309
+ * ended, for the time of the call.
310
+ */
311
+ const closeIdle = server.closeIdleConnections;
312
+ server.closeIdleConnections = function closeIdleConnections() {
313
+ const going = [...responses].filter((res) => res.writableEnded && !res.writableFinished && !res.destroyed);
314
+ for (const res of going) res.finished = false;
315
+ try {
316
+ closeIdle.call(this);
317
+ } finally {
318
+ for (const res of going) res.finished = true;
319
+ }
320
+ };
303
321
  server.on("request", onRequest);
304
322
  server.on("upgrade", onUpgrade);
305
323
  server.on("connection", onConnection);
@@ -472,13 +490,23 @@ const kindsOf = (kinds) => {
472
490
  return Object.freeze([...kinds]);
473
491
  };
474
492
  const prefixProblem = (prefix) => prefix === "" || prefix.startsWith("/") && !prefix.endsWith("/") ? null : `the probe prefix "${prefix}" does not start with "/", or it ends with "/"`;
493
+ /** These escapes replace `<`, `>` and `&` in a JSON body. The JSON stays valid, with the same value, and no part of it is markup. */
494
+ const ESCAPES = {
495
+ "<": "\\u003c",
496
+ ">": "\\u003e",
497
+ "&": "\\u0026"
498
+ };
499
+ /**
500
+ * This function gives a JSON answer, and its body escapes `<`, `>` and `&`. A body can repeat a part
501
+ * of the path. An example is the name of a controller that the service does not have.
502
+ */
475
503
  const json = (status, body) => ({
476
504
  status,
477
505
  headers: {
478
506
  "content-type": "application/health+json",
479
507
  "cache-control": "no-store"
480
508
  },
481
- body: JSON.stringify(body)
509
+ body: JSON.stringify(body).replace(/[<>&]/g, (char) => ESCAPES[char])
482
510
  });
483
511
  /**
484
512
  * This function gives the response of a probe path from the cache of the health runtime. For a path
@@ -522,13 +550,18 @@ const probeResponse = (health, method, path, prefix = "", kinds = PROBE_KINDS) =
522
550
  });
523
551
  }
524
552
  };
525
- /** This function writes a probe response. A 503 gets `Retry-After`, when the writer has the time of a retry. */
553
+ /**
554
+ * This function writes a probe response. Each response gets `X-Content-Type-Options: nosniff`, thus a
555
+ * browser does not read it as a different type. A 503 gets `Retry-After`, when the writer has the time of a retry.
556
+ */
526
557
  const write = (req, res, response, retryAfter) => {
527
558
  const retry = response.status === 503 && retryAfter !== void 0 ? { "retry-after": retryAfter } : {};
559
+ const length = String(Buffer.byteLength(response.body));
528
560
  res.writeHead(response.status, {
529
561
  ...response.headers,
530
562
  ...retry,
531
- "content-length": String(Buffer.byteLength(response.body))
563
+ "x-content-type-options": "nosniff",
564
+ "content-length": length
532
565
  });
533
566
  res.end(req.method === "HEAD" ? void 0 : response.body);
534
567
  };
package/build/http.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","names":["knobMilliseconds"],"sources":["../src/http/answers.ts","../src/http/transport.ts","../src/http/probes.ts","../src/http/outcome.ts"],"sourcesContent":["// The answers that the request tracking still writes (SYS-29). `writeOutcome` marks each response\n// while it waits for the end of its request, with the lifecycle that made the stream. At the deadline\n// of its drain, the transport waits for each response of its own lifecycle: the cut of the drain gives\n// that end at the same time. A stream of another lifecycle, or of none, does not hold the drain.\n\nconst marked = new WeakMap<object, object | null>();\nconst watchers = new Set<() => void>();\n\n/**\n * This function marks a response that waits for the end of its request. `owner` is the lifecycle\n * that made the stream, or null. It gives the function that removes the mark.\n */\nexport const markAnswer = (res: object, owner: object | null): (() => void) => {\n marked.set(res, owner);\n return () => {\n if (!marked.delete(res)) return;\n for (const watcher of [...watchers]) watcher();\n };\n};\n\n/**\n * This function gives the mark of a response: the lifecycle that made its stream. It gives null for a\n * stream outside each lifecycle, and undefined for a response with no mark.\n */\nexport const answerOwner = (res: object): object | null | undefined => marked.get(res);\n\n/** This function starts `watcher` after each removal of a mark. It gives the function that stops it. */\nexport const watchAnswers = (watcher: () => void): (() => void) => {\n watchers.add(watcher);\n return () => {\n watchers.delete(watcher);\n };\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport type { IncomingMessage, Server, ServerEventMap, ServerResponse } from \"node:http\";\nimport type { ListenOptions, Socket } from \"node:net\";\nimport type { Cancel, Clock } from \"../clock.ts\";\nimport { realClock } from \"../clock.ts\";\nimport type { LateHold } from \"../drains.ts\";\nimport { holdLate, noteOutside } from \"../drains.ts\";\nimport { millisecondsOf as knobMilliseconds } from \"../knobs.ts\";\nimport type { Lifecycle, TransportImpl } from \"../lifecycle.ts\";\nimport { answerOwner, watchAnswers } from \"./answers.ts\";\n\n/**\n * These are the knobs of an HTTP transport, by the names of the ordering law. The transport has no\n * defaults, thus each knob is necessary. The declaration of the server gets the same record.\n */\nexport interface HttpKnobs {\n /**\n * This time is the real idle close of the server, which O01 compares with the idle time of a\n * client. Node closes an idle connection at `keepAliveTimeout` plus `keepAliveTimeoutBuffer`.\n * Thus the transport sets `keepAliveTimeout` to this time less the buffer of the server.\n * A time without a bound stops the timer.\n */\n readonly server_idle_close: Extent;\n /**\n * This time is the interval of the sweep of idle connections, from the stop of the intake until the\n * close. A time without a bound gives one sweep, at the stop of the intake.\n */\n readonly server_idle_sweep_interval: Extent;\n /** This time goes in the `Retry-After` of each 503 that the transport gives after its intake stops. */\n readonly server_retry_after: Quantity;\n /** The other knobs of the server go to the declaration. The transport does not read them. */\n readonly [knob: string]: Extent;\n}\n\n/**\n * These are the knobs that the transport gives the declaration: the knobs of the caller, and the two\n * times that the transport found. Thus the declaration shows the three times of the idle close.\n */\nexport interface HttpTransportKnobs extends HttpKnobs {\n /** This time is the hint that each response sends (`Keep-Alive: timeout=…`), in whole seconds. A time without a bound tells that no hint goes. */\n readonly server_idle_close_hint: Extent;\n /** This time is the `keepAliveTimeoutBuffer` of the server, which Node adds to the timer and not to the hint. */\n readonly server_idle_close_buffer: Quantity;\n}\n\n/** A mount stage is the stage of the shutdown at which a mount stops: the stop of the intake, or the close. */\nexport type MountStage = \"stop intake\" | \"close transports\";\n\n/** A mount context gives each handler the signals of the shutdown, the lifecycle and the time of a retry. */\nexport interface MountContext {\n /** This signal aborts when the intake stops. A long stream ends in its own protocol at it. */\n readonly intake: AbortSignal;\n /** This signal aborts at the cooperative abort of the drain, or at the close. The work in flight stops at it. */\n readonly signal: AbortSignal;\n /** This lifecycle is the lifecycle that the transport got at its start, or null before the start. */\n readonly life: Lifecycle | null;\n /** This time is the knob `server_retry_after`: the `Retry-After` of each 503 of the handlers. */\n readonly retryAfter: Quantity;\n}\n\n/**\n * A request handler answers a request and gives true, or it gives false and does not touch the\n * response. Then the next handler gets the request. `until` tells when the handler stops.\n */\nexport interface RequestHandler {\n (req: IncomingMessage, res: ServerResponse, context: MountContext): boolean | Promise<boolean>;\n readonly until?: MountStage | undefined;\n}\n\ntype UpgradeArgs = ServerEventMap[\"upgrade\"];\n\n/** An upgrade socket is the stream of an upgraded connection, as the `upgrade` event of Node gives it. */\nexport type UpgradeSocket = UpgradeArgs[1];\n\n/** An upgrade handler takes an upgraded connection, for example a WebSocket server in its `noServer` mode. */\nexport interface UpgradeHandler {\n (req: IncomingMessage, socket: UpgradeSocket, head: UpgradeArgs[2], context: MountContext): void;\n readonly until?: MountStage | undefined;\n}\n\n/** An upgrade match is a path, without its query, or a function that tells if a handler takes a request. */\nexport type UpgradeMatch = string | ((req: IncomingMessage) => boolean);\n\n/** These options change one mount. */\nexport interface MountOptions {\n /**\n * This stage is when the mount stops. Without it, the stage of the handler applies, and then\n * `stop intake`. A mount until `close transports` keeps the listener open through the drain.\n */\n readonly until?: MountStage | undefined;\n}\n\n/** These options make an HTTP transport. */\nexport interface HttpTransportOptions {\n /**\n * These are the listen options of `start()`, or a function that makes the server listen. Without\n * them, `start()` takes a server that listens already.\n */\n readonly listen?: ListenOptions | ((server: Server) => Promise<void> | void) | undefined;\n /** This clock is the clock of the lifecycle. The deadline of the drain and the sweep use it. */\n readonly clock?: Clock | undefined;\n /**\n * This function tells if a response can keep its connection open. Without it, the transport reads\n * `life.keepAlive()` of the lifecycle of its start. The transport reads it when the headers of\n * each response go, and false gives `Connection: close`.\n */\n readonly keepAlive?: (() => boolean) | undefined;\n /** This function receives each error of a handler. The transport then answers 500, or it destroys the response. */\n readonly onError?: ((error: unknown, req: IncomingMessage) => void) | undefined;\n}\n\n/** An HTTP transport is a server of `node:http` as a transport of the lifecycle, with a list of mounts on its listener. */\nexport interface HttpTransport extends TransportImpl {\n readonly server: Server;\n /** These knobs go to the declaration of the server: the knobs of the caller, the hint and the buffer. */\n readonly knobs: HttpTransportKnobs;\n /** This method adds a request handler at the end of the list, and gives the function that removes it. */\n mount(handler: RequestHandler, options?: MountOptions): () => void;\n /** This method adds an upgrade handler for a path, and gives the function that removes it. */\n upgrade(match: UpgradeMatch, handler: UpgradeHandler, options?: MountOptions): () => void;\n /** This count is the number of open connections, idle connections and upgraded connections too. */\n readonly connections: number;\n /** This flag tells if the transport stopped its intake. */\n readonly intakeStopped: boolean;\n}\n\ninterface RequestMount {\n readonly handler: RequestHandler;\n readonly until: MountStage;\n}\n\ninterface UpgradeMount {\n readonly matches: (req: IncomingMessage) => boolean;\n readonly handler: UpgradeHandler;\n readonly until: MountStage;\n}\n\n/** This function gives the path of a request target, without its query and fragment. */\nexport const pathOf = (url: string | undefined): string => {\n const raw = url ?? \"/\";\n const end = raw.search(/[?#]/);\n const path = end === -1 ? raw : raw.slice(0, end);\n if (path.startsWith(\"/\")) return path;\n // an absolute target, as a proxy sends it\n try {\n return new URL(raw).pathname;\n } catch {\n return path;\n }\n};\n\n/** This function gives a knob in milliseconds, or infinity for a time without a bound. */\nconst millisecondsOf = (knobs: Readonly<Record<string, Extent | undefined>>, name: string, owner = \"httpTransport\"): number =>\n knobMilliseconds(knobs, name, owner);\n\n/**\n * This function gives the `Retry-After` of the knob `server_retry_after`, in whole seconds, rounded\n * up. The knob is necessary, and it has a bound.\n */\nexport const retryAfterOf = (knobs: Readonly<Record<string, Extent | undefined>>, owner: string): string => {\n const retryAfter = millisecondsOf(knobs, \"server_retry_after\", owner);\n if (!Number.isFinite(retryAfter)) throw new Error(\"the knob server_retry_after has no bound: a Retry-After needs a time\");\n return String(Math.ceil(retryAfter / 1000));\n};\n\n/** This function tells if the headers of a call of `writeHead` name a header, an object or a flat list of names and values. */\nconst names = (headers: unknown, name: string): boolean => {\n if (Array.isArray(headers)) return headers.some((each, at) => at % 2 === 0 && String(each).toLowerCase() === name);\n return typeof headers === \"object\" && headers !== null && Object.keys(headers).some((key) => key.toLowerCase() === name);\n};\n\n/** This function gives the headers of a call of `writeHead` without one header. */\nconst without = (headers: unknown, name: string): unknown => {\n if (Array.isArray(headers)) {\n const kept: unknown[] = [];\n for (let at = 0; at + 1 < headers.length; at += 2)\n if (String(headers[at]).toLowerCase() !== name) kept.push(headers[at], headers[at + 1]);\n return kept;\n }\n if (typeof headers !== \"object\" || headers === null) return headers;\n return Object.fromEntries(Object.entries(headers).filter(([key]) => key.toLowerCase() !== name));\n};\n\n/**\n * This function applies the rule of `Retry-After` to a call of `writeHead`, and gives its arguments.\n * A 503 without `Retry-After` gets the time of `server_retry_after` (RFC 9110). A 500 loses each\n * `Retry-After`, from the call and from the response.\n */\nconst retryRule = (res: ServerResponse, args: unknown[], retryAfter: string): unknown[] => {\n const status = typeof args[0] === \"number\" ? args[0] : res.statusCode;\n const at = typeof args[1] === \"string\" ? 2 : 1;\n if (status === 503 && !res.hasHeader(\"retry-after\") && !names(args[at], \"retry-after\")) res.setHeader(\"retry-after\", retryAfter);\n if (status !== 500) return args;\n res.removeHeader(\"retry-after\");\n if (!names(args[at], \"retry-after\")) return args;\n const given = [...args];\n given[at] = without(args[at], \"retry-after\");\n return given;\n};\n\nconst STATUS_TEXT: Readonly<Record<number, string>> = { 404: \"Not Found\", 500: \"Internal Server Error\", 503: \"Service Unavailable\" };\n\n/**\n * This function makes a server of `node:http` a transport of the lifecycle (S6 A8 and A13). It is not\n * a framework: it has no routes, no body parser and no middleware.\n * - The count of work in flight comes from events: each response until its `close`, and each upgraded\n * connection until its `close`.\n * - `stopIntake` closes the listener, and new connections get a refusal. It closes the idle connections\n * at once and at each `server_idle_sweep_interval`. Each later response gets `Connection: close`.\n * - `drain` waits until no work is in flight, or until the deadline. It gives the cooperative abort to\n * the handlers through their context. At the deadline, it also waits for each answer that is to come\n * or to go out (SYS-29). The cap of the phase bounds that wait. Answers that did not go out by the\n * cap are not a failure of the phase. A stream outside the lifecycle of the transport does not hold it.\n * - `close` destroys each connection. A second close gives the same promise.\n * - The mounts share one port. The first request handler that gives true answers. After the stop of\n * the intake, only the mounts until `close transports` answer, and each other request gets a 503.\n */\nexport const httpTransport = (server: Server, knobs: HttpKnobs, options: HttpTransportOptions = {}): HttpTransport => {\n const clock = options.clock ?? realClock;\n const idleClose = millisecondsOf(knobs, \"server_idle_close\");\n const sweepEvery = millisecondsOf(knobs, \"server_idle_sweep_interval\");\n if (sweepEvery === 0)\n throw new Error(\"the knob server_idle_sweep_interval is zero: a sweep needs an interval above zero\");\n const retryAfterHeader = retryAfterOf(knobs, \"httpTransport\");\n for (const derived of [\"server_idle_close_hint\", \"server_idle_close_buffer\"])\n if ((knobs as Readonly<Record<string, Extent | undefined>>)[derived] !== undefined)\n throw new Error(`the knob ${derived} comes from the transport: give only server_idle_close`);\n if (server.listenerCount(\"request\") > 0 || server.listenerCount(\"upgrade\") > 0)\n throw new Error(\"the server has a request or upgrade listener of its own: mount it as a handler\");\n // Node closes an idle connection at keepAliveTimeout + keepAliveTimeoutBuffer, and its hint is\n // keepAliveTimeout. The buffer stays, thus the hint is less than the real close. An older Node has no buffer\n const buffer = (server as { keepAliveTimeoutBuffer?: number }).keepAliveTimeoutBuffer ?? 0;\n if (Number.isFinite(idleClose) && idleClose <= buffer)\n throw new Error(\n `the knob server_idle_close is ${idleClose} ms, at or below the keepAliveTimeoutBuffer of the server, ${buffer} ms: ` +\n \"the server closes an idle connection at keepAliveTimeout + keepAliveTimeoutBuffer\",\n );\n // 0 stops the keep-alive timer and the hint in Node\n server.keepAliveTimeout = Number.isFinite(idleClose) ? idleClose - buffer : 0;\n const declared: HttpTransportKnobs = {\n ...knobs,\n server_idle_close_hint: Number.isFinite(idleClose)\n ? { value: Math.floor(server.keepAliveTimeout / 1000), unit: \"s\" }\n : { value: \"infinite\", unit: \"s\" },\n server_idle_close_buffer: { value: buffer, unit: \"ms\" },\n };\n\n const requestMounts: RequestMount[] = [];\n const upgradeMounts: UpgradeMount[] = [];\n const responses = new Set<ServerResponse>();\n const upgraded = new Set<UpgradeSocket>();\n const sockets = new Set<Socket>();\n // the check of each drain: it ends its drain when nothing holds it\n const drainers = new Set<() => void>();\n const closedWaiters = new Set<() => void>();\n const intake = new AbortController();\n const cooperative = new AbortController();\n // the lifecycle comes at the start, thus the context reads it at each request\n let life: Lifecycle | null = null;\n const context: MountContext = {\n intake: intake.signal,\n signal: cooperative.signal,\n get life() {\n return life;\n },\n retryAfter: knobs.server_retry_after,\n };\n let intakeOpen = true;\n let listenerOpen = server.listening;\n let serverClosed = !server.listening;\n let sweep: Cancel | null = null;\n let closing: Promise<void> | null = null;\n\n const inFlight = (): number => responses.size + upgraded.size;\n /**\n * This function gives what holds a drain after its deadline. An answer is unflushed when its handler\n * ended it and its bytes did not all go. It is unflushed too while `writeOutcome` waits for a client\n * that does not read. A call runs while `writeOutcome` waits for the end of its request, which the\n * cut of the drain gives. Only a stream of the lifecycle of the transport counts, because only its\n * cut ends the stream. A response that is open for another reason does not hold the drain.\n */\n const lateHold = (): LateHold & { readonly outside: number } => {\n let running = 0;\n let unflushed = 0;\n let outside = 0;\n for (const res of responses) {\n if (res.destroyed) continue;\n if (res.writableEnded) {\n if (!res.writableFinished) unflushed += 1;\n continue;\n }\n const owner = answerOwner(res);\n if (owner === undefined) continue;\n if (life === null || owner !== life) outside += 1;\n else if (res.writableNeedDrain) unflushed += 1;\n else running += 1;\n }\n return { running, unflushed, outside };\n };\n const answering = (): boolean => {\n const hold = lateHold();\n return hold.running + hold.unflushed > 0;\n };\n const report = (error: unknown, req: IncomingMessage): void => {\n try {\n options.onError?.(error, req);\n } catch {\n // an error of the error hook does not stop the transport\n }\n };\n const settled = (): void => {\n for (const check of [...drainers]) check();\n if (closing !== null && inFlight() === 0 && sockets.size === 0 && serverClosed)\n for (const done of [...closedWaiters]) done();\n };\n // before its start, a transport has no lifecycle: a server that listens already keeps its connections\n const keepsAlive = (): boolean => intakeOpen && (options.keepAlive?.() ?? life?.keepAlive() ?? true);\n const live = <T extends { readonly until: MountStage }>(mounts: readonly T[]): T[] =>\n intakeOpen ? [...mounts] : mounts.filter((mount) => mount.until === \"close transports\");\n const throughDrain = (): boolean =>\n requestMounts.some((mount) => mount.until === \"close transports\") ||\n upgradeMounts.some((mount) => mount.until === \"close transports\");\n const closeListener = (): void => {\n if (!listenerOpen) return;\n listenerOpen = false;\n server.close();\n };\n const startSweep = (): void => {\n if (!Number.isFinite(sweepEvery) || closing !== null) return;\n sweep = clock.after(sweepEvery, () => {\n sweep = null;\n if (closing !== null) return;\n server.closeIdleConnections();\n startSweep();\n });\n };\n\n /**\n * This function watches the headers of each response when they go, whatever wrote them. Each 503\n * gets `Retry-After`, and a 500 gets none. The response closes its connection while the transport\n * cannot keep it.\n */\n const watchHeaders = (res: ServerResponse): void => {\n const writeHead = res.writeHead as (...args: unknown[]) => ServerResponse;\n res.writeHead = ((...args: unknown[]): ServerResponse => {\n if (res.headersSent) return writeHead.apply(res, args);\n if (!keepsAlive()) {\n res.shouldKeepAlive = false;\n res.setHeader(\"connection\", \"close\");\n }\n return writeHead.apply(res, retryRule(res, args, retryAfterHeader));\n }) as ServerResponse[\"writeHead\"];\n };\n const answer = (res: ServerResponse, status: number, headers: Readonly<Record<string, string>> = {}): void => {\n if (res.headersSent) {\n res.destroy();\n return;\n }\n const body = STATUS_TEXT[status] ?? \"\";\n res.writeHead(status, { \"content-type\": \"text/plain; charset=utf-8\", \"content-length\": String(Buffer.byteLength(body)), ...headers });\n res.end(body);\n };\n const refuse = (res: ServerResponse): void => answer(res, 503, { \"retry-after\": retryAfterHeader, connection: \"close\" });\n\n const route = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const mounts = live(requestMounts);\n try {\n for (const mount of mounts) {\n const given = mount.handler(req, res, context);\n const taken = typeof given === \"boolean\" ? given : await given;\n // a handler that wrote its response took the request, whatever it gave\n if (taken || res.headersSent || res.writableEnded) return;\n }\n } catch (error) {\n report(error, req);\n if (!res.writableEnded) answer(res, 500);\n return;\n }\n if (intakeOpen) answer(res, 404);\n else refuse(res);\n };\n\n const onRequest = (req: IncomingMessage, res: ServerResponse): void => {\n responses.add(res);\n // the bytes of an answer went: a drain after its deadline can end\n res.once(\"finish\", settled);\n res.once(\"close\", () => {\n responses.delete(res);\n settled();\n });\n watchHeaders(res);\n void route(req, res);\n };\n\n const onUpgrade = (req: IncomingMessage, socket: UpgradeSocket, head: UpgradeArgs[2]): void => {\n socket.on(\"error\", () => {});\n const mount = live(upgradeMounts).find((each) => each.matches(req));\n if (mount === undefined) {\n const status = intakeOpen ? 404 : 503;\n const extra = intakeOpen ? \"\" : `Retry-After: ${retryAfterHeader}\\r\\n`;\n socket.end(`HTTP/1.1 ${status} ${STATUS_TEXT[status]!}\\r\\nConnection: close\\r\\nContent-Length: 0\\r\\n${extra}\\r\\n`);\n return;\n }\n if (socket.destroyed) return;\n upgraded.add(socket);\n socket.once(\"close\", () => {\n upgraded.delete(socket);\n settled();\n });\n try {\n mount.handler(req, socket, head, context);\n } catch (error) {\n report(error, req);\n socket.destroy();\n }\n };\n\n const onConnection = (socket: Socket): void => {\n sockets.add(socket);\n socket.once(\"close\", () => {\n sockets.delete(socket);\n settled();\n });\n };\n\n server.on(\"request\", onRequest);\n server.on(\"upgrade\", onUpgrade);\n server.on(\"connection\", onConnection);\n server.on(\"listening\", () => {\n listenerOpen = true;\n serverClosed = false;\n });\n server.on(\"close\", () => {\n listenerOpen = false;\n serverClosed = true;\n settled();\n });\n\n const remover = <T>(list: T[], entry: T) => (): void => {\n const at = list.indexOf(entry);\n if (at >= 0) list.splice(at, 1);\n // the last mount through the drain went: the listener closes as the stop of the intake asks\n if (!intakeOpen && closing === null && !throughDrain()) closeListener();\n };\n\n return {\n server,\n knobs: declared,\n get connections() {\n return sockets.size;\n },\n get intakeStopped() {\n return !intakeOpen;\n },\n mount(handler, mountOptions = {}) {\n const entry: RequestMount = { handler, until: mountOptions.until ?? handler.until ?? \"stop intake\" };\n requestMounts.push(entry);\n return remover(requestMounts, entry);\n },\n upgrade(match, handler, mountOptions = {}) {\n const matches = typeof match === \"string\" ? (req: IncomingMessage) => pathOf(req.url) === match : match;\n const entry: UpgradeMount = { matches, handler, until: mountOptions.until ?? handler.until ?? \"stop intake\" };\n upgradeMounts.push(entry);\n return remover(upgradeMounts, entry);\n },\n async start(_signal, given) {\n life = given;\n if (closing !== null) throw new Error(\"the transport closed: a closed transport does not start again\");\n // a listener that another owner adds after the transport answers too, thus the start refuses it\n if (server.listenerCount(\"request\") > 1 || server.listenerCount(\"upgrade\") > 1)\n throw new Error(\"the server has a request or upgrade listener of its own: mount it as a handler\");\n if (server.listening) return;\n const listen = options.listen;\n if (listen === undefined) throw new Error(\"the server does not listen, and the transport has no listen options\");\n if (typeof listen === \"function\") {\n await listen(server);\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const failed = (error: Error): void => {\n server.off(\"listening\", listened);\n reject(error);\n };\n const listened = (): void => {\n server.off(\"error\", failed);\n resolve();\n };\n server.once(\"error\", failed);\n server.once(\"listening\", listened);\n server.listen(listen);\n });\n },\n stopIntake() {\n if (!intakeOpen) return;\n intakeOpen = false;\n intake.abort(new Error(\"the transport stopped its intake\"));\n if (!throughDrain()) closeListener();\n server.closeIdleConnections();\n startSweep();\n },\n drain(deadline, signal) {\n const abort = (): void => {\n if (!cooperative.signal.aborted) cooperative.abort(signal.reason);\n };\n if (signal.aborted) abort();\n else signal.addEventListener(\"abort\", abort, { once: true });\n // nothing in flight: the drain ends at once, with no timer\n if (inFlight() === 0) {\n signal.removeEventListener(\"abort\", abort);\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n let cancel: Cancel = () => {};\n let unwatch: () => void = () => {};\n let unhold: () => void = () => {};\n let late = false;\n let ended = false;\n // before the deadline, the work in flight holds the drain. After it, only an answer to come or to go out holds it\n const check = (): void => {\n if (ended || (inFlight() > 0 && (!late || answering()))) return;\n ended = true;\n drainers.delete(check);\n cancel();\n unwatch();\n unhold();\n signal.removeEventListener(\"abort\", abort);\n resolve();\n };\n // the cut of the requests comes at the same time, before or after this timer: the drain waits for its answers in each case\n const reached = (): void => {\n late = true;\n unwatch = watchAnswers(check);\n // the lifecycle reads the hold at the cap: answers that do not go out are not a failure of the phase\n if (life !== null) {\n unhold = holdLate(life, () => {\n const { running, unflushed } = lateHold();\n return { running, unflushed };\n });\n // a stream outside the lifecycle does not hold the drain: the record of the phase gives the count\n noteOutside(life, lateHold().outside);\n }\n check();\n };\n drainers.add(check);\n if (deadline <= clock.now()) reached();\n else cancel = clock.after(deadline - clock.now(), reached);\n });\n },\n inFlight,\n close() {\n if (closing !== null) return closing;\n intakeOpen = false;\n if (!intake.signal.aborted) intake.abort(new Error(\"the transport closed\"));\n if (!cooperative.signal.aborted) cooperative.abort(new Error(\"the transport closed\"));\n sweep?.();\n sweep = null;\n closing = new Promise<void>((resolve) => {\n closedWaiters.add(resolve);\n });\n closeListener();\n server.closeAllConnections();\n for (const socket of [...upgraded]) socket.destroy();\n for (const socket of [...sockets]) socket.destroy();\n settled();\n return closing;\n },\n };\n};\n","import type { IncomingMessage, Server, ServerResponse } from \"node:http\";\nimport { createServer } from \"node:http\";\nimport type { ListenOptions, Socket } from \"node:net\";\nimport type { Health, ProbeKind } from \"../health.ts\";\nimport type { HealthServer } from \"../lifecycle.ts\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport type { MountContext, RequestHandler } from \"./transport.ts\";\nimport { pathOf, retryAfterOf } from \"./transport.ts\";\n\n/** A probe response is the status, the headers and the body of one probe, for any HTTP framework or none. */\nexport interface ProbeResponse {\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: string;\n}\n\n/** A health source is the health runtime, or a function that gives it when it exists. */\nexport type HealthSource = Health | (() => Health | null | undefined);\n\n/**\n * These are the four probe kinds, in the sequence of their paths. A probe server and a probe handler\n * answer all four, unless their option `kinds` names fewer.\n */\nexport const PROBE_KINDS: readonly ProbeKind[] = Object.freeze([\"startup\", \"readiness\", \"liveness\", \"report\"] as const);\n\nconst named = PROBE_KINDS.map((kind) => `\"${kind}\"`).join(\", \");\n\n/**\n * This function gives the probe kinds of an option `kinds`: all four when it is undefined, or else a\n * frozen copy. It refuses a value that is not a list, an empty list, an unknown kind and a kind two times.\n */\nconst kindsOf = (kinds: readonly ProbeKind[] | undefined): readonly ProbeKind[] => {\n if (kinds === undefined) return PROBE_KINDS;\n if (!Array.isArray(kinds)) throw new Error(`the probe kinds ${JSON.stringify(kinds)} are not a list of ${named}`);\n if (kinds.length === 0) throw new Error(`the probe kinds are an empty list: name one or more of ${named}`);\n const seen = new Set<ProbeKind>();\n for (const kind of kinds) {\n if (!PROBE_KINDS.includes(kind)) throw new Error(`the probe kind ${JSON.stringify(kind)} is not one of ${named}`);\n if (seen.has(kind)) throw new Error(`the probe kind \"${kind}\" is two times in the probe kinds`);\n seen.add(kind);\n }\n return Object.freeze([...kinds]);\n};\n\nconst prefixProblem = (prefix: string): string | null =>\n prefix === \"\" || (prefix.startsWith(\"/\") && !prefix.endsWith(\"/\"))\n ? null\n : `the probe prefix \"${prefix}\" does not start with \"/\", or it ends with \"/\"`;\n\nconst json = (status: number, body: unknown): ProbeResponse => ({\n status,\n headers: { \"content-type\": \"application/health+json\", \"cache-control\": \"no-store\" },\n body: JSON.stringify(body),\n});\n\n/**\n * This function gives the response of a probe path from the cache of the health runtime. For a path\n * that is not a probe, it gives null. The paths are `/startup`, `/readiness`, `/liveness` and\n * `/report` after the prefix. `/readiness/<controller>` gives the readiness of one controller.\n * `kinds` names the probes that answer. The path of a kind that it does not name is not a probe.\n */\nexport const probeResponse = (\n health: Health | null | undefined,\n method: string | undefined,\n path: string,\n prefix = \"\",\n kinds: readonly ProbeKind[] = PROBE_KINDS,\n): ProbeResponse | null => {\n if (!path.startsWith(`${prefix}/`)) return null;\n const parts = path.slice(prefix.length + 1).split(\"/\");\n const kind = PROBE_KINDS.find((each) => each === parts[0] && kinds.includes(each));\n if (kind === undefined) return null;\n const controller = parts[1];\n if (parts.length > 2 || (controller !== undefined && (kind !== \"readiness\" || controller === \"\"))) return null;\n if (method !== \"GET\" && method !== \"HEAD\")\n return { status: 405, headers: { allow: \"GET, HEAD\", \"content-length\": \"0\" }, body: \"\" };\n // before the runtime exists, each probe fails: the service has not started\n if (health === null || health === undefined) return json(503, { status: \"fail\", probe: kind });\n try {\n const answer = health.probe(kind, controller === undefined ? undefined : decodeURIComponent(controller));\n return { ...json(answer.status, answer.body), headers: { \"content-type\": answer.contentType, \"cache-control\": \"no-store\" } };\n } catch {\n // a controller that the service does not have\n return json(404, { status: \"fail\", probe: kind, controller });\n }\n};\n\n/** This function writes a probe response. A 503 gets `Retry-After`, when the writer has the time of a retry. */\nconst write = (req: IncomingMessage, res: ServerResponse, response: ProbeResponse, retryAfter: string | undefined): void => {\n const retry = response.status === 503 && retryAfter !== undefined ? { \"retry-after\": retryAfter } : {};\n res.writeHead(response.status, { ...response.headers, ...retry, \"content-length\": String(Buffer.byteLength(response.body)) });\n res.end(req.method === \"HEAD\" ? undefined : response.body);\n};\n\n/** These options mount the probes on the port of the application. */\nexport interface ProbeHandlerOptions {\n /** This path goes before each probe path, for example `/health`. It starts with `/`, or it is empty. */\n readonly prefix: string;\n /**\n * These are the probes that the handler answers. The default is all four of `PROBE_KINDS`. The\n * path of a kind that the list does not name goes to the next handler.\n */\n readonly kinds?: readonly ProbeKind[] | undefined;\n}\n\n/**\n * This function gives a request handler that answers the probes on the port of the application. It\n * answers until `close transports`, thus its mount keeps the listener open through the drain. A path\n * that is not a probe goes to the next handler. Each 503 gets the `Retry-After` of the context.\n */\nexport const probeHandler = (health: HealthSource, options: ProbeHandlerOptions): RequestHandler => {\n const problem = prefixProblem(options.prefix);\n if (problem !== null) throw new Error(problem);\n const kinds = kindsOf(options.kinds);\n const handler = (req: IncomingMessage, res: ServerResponse, context?: MountContext): boolean => {\n const runtime = typeof health === \"function\" ? health() : health;\n const response = probeResponse(runtime, req.method, pathOf(req.url), options.prefix, kinds);\n if (response === null) return false;\n // the transport gives server_retry_after through the context, and it adds the header to each 503 too\n const retryAfter = context?.retryAfter === undefined ? undefined : retryAfterOf({ server_retry_after: context.retryAfter }, \"probeHandler\");\n write(req, res, response, retryAfter);\n return true;\n };\n return Object.assign(handler, { until: \"close transports\" as const });\n};\n\n/** These options make a probe server: the listen options of its port, the prefix of its paths, and the time of a retry. */\nexport interface ProbeServerOptions extends ListenOptions {\n /**\n * This time goes in the `Retry-After` of each 503 of a probe, as on the port of the application.\n * The probe server has no defaults, thus the knob is necessary.\n */\n readonly server_retry_after: Quantity;\n /** This path goes before each probe path. The default is no prefix, because the port has only the probes. */\n readonly prefix?: string | undefined;\n /** This server answers the probes. Without it, the probe server makes a server of `node:http`. */\n readonly server?: Server | undefined;\n /**\n * These are the probes that the server answers. The default is all four of `PROBE_KINDS`. The path\n * of a kind that the list does not name gets 404, as each other path.\n */\n readonly kinds?: readonly ProbeKind[] | undefined;\n}\n\n/** A probe server is a health server on its own port. Its server is visible for the address of the port. */\nexport interface ProbeServer extends HealthServer {\n readonly server: Server;\n}\n\n/**\n * This function makes a health server on a port of its own (S6 decision D5). It starts first and\n * closes last, thus the probes answer through the whole drain. Each probe answers from the cache, and\n * each 503 gets the `Retry-After` of `server_retry_after`.\n */\nexport const probeServer = (options: ProbeServerOptions): ProbeServer => {\n const { prefix = \"\", server = createServer(), server_retry_after, kinds: chosen, ...listen } = options;\n const retryAfter = retryAfterOf({ server_retry_after }, \"probeServer\");\n const problem = prefixProblem(prefix);\n if (problem !== null) throw new Error(problem);\n const kinds = kindsOf(chosen);\n if (server.listenerCount(\"request\") > 0) throw new Error(\"the probe server has a request listener of its own\");\n let health: Health | null = null;\n let closing: Promise<void> | null = null;\n const sockets = new Set<Socket>();\n server.on(\"connection\", (socket: Socket) => {\n sockets.add(socket);\n socket.once(\"close\", () => sockets.delete(socket));\n });\n server.on(\"request\", (req: IncomingMessage, res: ServerResponse) => {\n const response = probeResponse(health, req.method, pathOf(req.url), prefix, kinds);\n write(req, res, response ?? { status: 404, headers: { \"content-type\": \"text/plain; charset=utf-8\" }, body: \"Not Found\" }, retryAfter);\n });\n return {\n server,\n listen(runtime) {\n health = runtime;\n if (server.listening) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n const failed = (error: Error): void => {\n server.off(\"listening\", listened);\n reject(error);\n };\n const listened = (): void => {\n server.off(\"error\", failed);\n resolve();\n };\n server.once(\"error\", failed);\n server.once(\"listening\", listened);\n server.listen(listen);\n });\n },\n close() {\n if (closing !== null) return closing;\n closing = new Promise<void>((resolve) => {\n if (!server.listening) {\n resolve();\n return;\n }\n server.close(() => resolve());\n server.closeAllConnections();\n for (const socket of [...sockets]) socket.destroy();\n });\n return closing;\n },\n };\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { ownerOf, ownStream } from \"../drains.ts\";\nimport type { Refused } from \"../limiter/policy.ts\";\nimport { retryAfterSeconds } from \"../limiter/policy.ts\";\nimport type { RequestEnd, RequestEvent, RequestOptions, RequestStream, RequestWork } from \"../requests.ts\";\nimport { markAnswer } from \"./answers.ts\";\nimport type { MountContext, RequestHandler } from \"./transport.ts\";\nimport { pathOf } from \"./transport.ts\";\n\n/** An outcome response is the status, the headers and the body of a request, for any HTTP framework or none. */\nexport interface OutcomeResponse {\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: string | Uint8Array;\n}\n\n/** A response part is what a hook gives for one value or one failure. The parts that it does not give keep the values of the mapping. */\nexport interface ResponsePart {\n readonly status?: number | undefined;\n readonly headers?: Readonly<Record<string, string>> | undefined;\n readonly body?: string | Uint8Array | undefined;\n}\n\n/**\n * A stream format writes the items of a stream and its end in the body, as they come. An end after\n * the first item goes in the body, because the status went with the first item.\n */\nexport interface StreamFormat<T = unknown> {\n readonly contentType: string;\n /** This function gives the bytes of one item. */\n item(value: T): string | Uint8Array;\n /** This function gives the bytes of the end, the last part of the body. */\n end(end: RequestEnd): string | Uint8Array;\n}\n\n/** These options map a request to a response. */\nexport interface OutcomeOptions<T> {\n /**\n * This value tells if the response can keep its connection open, as `life.keepAlive()` does. False\n * gives `Connection: close`. A function is read when the headers go.\n */\n readonly keepAlive: boolean | (() => boolean);\n /** This time goes in the `Retry-After` of each 503, in whole seconds, rounded up. It is the knob `server_retry_after`. */\n readonly retryAfter: Quantity;\n /**\n * This function gives the response of the value of a unary request. Without it, the mapping gives\n * 200 and the value as JSON, or 204 for no value.\n */\n readonly done?: ((value: T) => ResponsePart) | undefined;\n /**\n * This function gives the response of a failure of the route before the first byte. Without it,\n * the mapping gives 500 with no detail, because an error can hold private data.\n */\n readonly failed?: ((error: unknown) => ResponsePart) | undefined;\n /**\n * This format makes the response a stream: the status and each item go as they come, and the end\n * goes in the body. Without it, the response is unary: one status and the one value.\n */\n readonly stream?: StreamFormat<T> | undefined;\n}\n\nconst JSON_TYPE = \"application/json; charset=utf-8\";\n\n/**\n * This function gives an end as a record for a body: its kind and the count of its items. A cut and\n * a refusal also give their cause, and if the caller can send the request again. A failure gives\n * no detail.\n */\nexport const endRecord = (end: RequestEnd): Readonly<Record<string, unknown>> => {\n if (end.kind === \"refused\" || end.kind === \"cut\")\n return { end: end.kind, delivered: end.delivered, retryable: end.retryable, cause: end.cause };\n return { end: end.kind, delivered: end.delivered };\n};\n\n/** This format writes each item and the end as one line of JSON (NDJSON). */\nexport const ndjson: StreamFormat = {\n contentType: \"application/x-ndjson; charset=utf-8\",\n item: (value) => `${JSON.stringify(value)}\\n`,\n end: (end) => `${JSON.stringify(endRecord(end))}\\n`,\n};\n\n/** This format writes each item as an event of server-sent events, and the end as the event `end`. */\nexport const sse: StreamFormat = {\n contentType: \"text/event-stream; charset=utf-8\",\n item: (value) => `data: ${JSON.stringify(value)}\\n\\n`,\n end: (end) => `event: end\\ndata: ${JSON.stringify(endRecord(end))}\\n\\n`,\n};\n\nconst valueResponse = (value: unknown): OutcomeResponse => {\n if (value === undefined) return { status: 204, headers: {}, body: \"\" };\n if (typeof value === \"string\") return { status: 200, headers: { \"content-type\": \"text/plain; charset=utf-8\" }, body: value };\n if (value instanceof Uint8Array) return { status: 200, headers: { \"content-type\": \"application/octet-stream\" }, body: value };\n return { status: 200, headers: { \"content-type\": JSON_TYPE }, body: JSON.stringify(value) };\n};\n\nconst merge = (base: OutcomeResponse, part: ResponsePart | undefined): OutcomeResponse =>\n part === undefined\n ? base\n : { status: part.status ?? base.status, headers: { ...base.headers, ...part.headers }, body: part.body ?? base.body };\n\nconst keeps = (keepAlive: boolean | (() => boolean)): boolean => (typeof keepAlive === \"function\" ? keepAlive() : keepAlive);\n\n/**\n * This function applies the rules of the headers to a response. Each 503 gets `Retry-After`, and a\n * 500 gets none. The response closes its connection while the service cannot keep it.\n */\nconst ruled = (response: OutcomeResponse, retryAfter: Quantity, keepAlive: boolean | (() => boolean)): OutcomeResponse => {\n const headers: Record<string, string> = { ...response.headers };\n if (response.status === 503 && headers[\"retry-after\"] === undefined)\n headers[\"retry-after\"] = String(Math.max(0, Math.ceil(valueIn(retryAfter, \"s\"))));\n if (response.status === 500) delete headers[\"retry-after\"];\n if (!keeps(keepAlive)) headers[\"connection\"] = \"close\";\n return { ...response, headers };\n};\n\n/** This function gives the status response of an end before the first byte of the body. */\nconst endStatus = <T>(end: RequestEnd, options: OutcomeOptions<T>): OutcomeResponse => {\n const body = JSON.stringify(endRecord(end));\n switch (end.kind) {\n case \"refused\":\n return { status: 503, headers: { \"content-type\": JSON_TYPE }, body };\n case \"cut\":\n // a cut after the first item has no status of its own, but no byte went: its route decides\n return { status: end.status ?? (end.retryable ? 503 : 500), headers: { \"content-type\": JSON_TYPE }, body };\n case \"failed\":\n return merge({ status: 500, headers: { \"content-type\": JSON_TYPE }, body }, options.failed?.(end.error));\n case \"done\":\n return { status: 204, headers: {}, body: \"\" };\n }\n};\n\n/**\n * This function maps a unary request to its response, from its items and its end (S6 A12 and A13).\n * - One value and the end `done`: 200 with the value, or the response of the `done` hook.\n * - No value and the end `done`: 204. More than one value: 500, because the route is not a stream.\n * - `refused`: 503. The work did not start, thus the caller can send it again. The service did not\n * start, it stopped its intake, or a controller is not ready.\n * - `cut`: 503 for a route whose callers can send it again (S6 A12), and 500 for each other route.\n * - `failed`: 500 with no detail, or the response of the `failed` hook. A failure of the route is not a 503.\n *\n * Each 503 gets `Retry-After` (RFC 9110), and a 500 gets none. Each response gets `Connection: close`\n * while the service cannot keep the connection.\n */\nexport const outcomeResponse = <T>(items: readonly T[], end: RequestEnd, options: OutcomeOptions<T>): OutcomeResponse => {\n let response: OutcomeResponse;\n if (end.kind !== \"done\") response = endStatus(end, options);\n else if (items.length === 1) response = merge(valueResponse(items[0]), options.done?.(items[0]!));\n else if (items.length === 0) response = endStatus(end, options);\n else\n response = endStatus(\n { kind: \"failed\", error: new Error(`the work gave ${items.length} values, and a unary response has one`), delivered: items.length },\n options,\n );\n return ruled(response, options.retryAfter, options.keepAlive);\n};\n\n/**\n * This function gives the response of a take that a keyed limiter refused: 429 (RFC 6585), with\n * `Retry-After` in whole seconds, rounded up (RFC 9110). The body tells the cause (`limit`, `cap` or\n * `store`) and the same whole seconds, but not the failure of a store. The work did not start, thus\n * the caller can send it again.\n */\nexport const limitedResponse = (refused: Refused): OutcomeResponse => {\n const seconds = retryAfterSeconds(refused.retryAfter);\n return {\n status: 429,\n headers: { \"content-type\": JSON_TYPE, \"retry-after\": String(seconds) },\n body: JSON.stringify({ end: \"refused\", delivered: 0, retryable: true, cause: `the limiter refused the take: ${refused.cause}`, retryAfter: seconds }),\n };\n};\n\n/** This function writes a whole response of `node:http`. A response whose headers went gets no second status: it is destroyed. */\nconst writeWhole = (res: ServerResponse, response: OutcomeResponse): void => {\n if (res.headersSent) {\n res.destroy();\n return;\n }\n if (response.headers[\"connection\"] === \"close\") res.shouldKeepAlive = false;\n // a 204 has no body, thus no length\n if (response.status === 204) {\n res.writeHead(204, response.headers);\n res.end();\n return;\n }\n const length = typeof response.body === \"string\" ? Buffer.byteLength(response.body) : response.body.byteLength;\n res.writeHead(response.status, { ...response.headers, \"content-length\": String(length) });\n res.end(response.body);\n};\n\n/**\n * This function writes the stream of a request to a response of `node:http`, and gives its end. It\n * gives null when the client closed the connection first. Then the work stops through `return()`.\n * - Unary (no format): it waits for the end, and it writes the status and the body of `outcomeResponse`.\n * - Stream: the status 200 goes with the first item, then each item goes as it comes, then the end\n * goes in the body (in-band). The next item waits until the client reads, thus a slow client\n * slows the work. An end before the first item gets a status, as a unary end does.\n * - The answer to come: until it writes the end, the response holds the drain of `httpTransport` past\n * its deadline. The cut of the drain gives that end, and the drain waits until the end goes out.\n * Only a stream of `life.request` of the lifecycle of the transport holds the drain.\n */\nexport const writeOutcome = async <T>(res: ServerResponse, stream: RequestStream<T>, options: OutcomeOptions<T>): Promise<RequestEnd | null> => {\n const iterator = stream[Symbol.asyncIterator]();\n let gone: () => void = () => {};\n const closed = new Promise<\"gone\">((resolve) => {\n gone = () => resolve(\"gone\");\n });\n // a close before the end of the response is a client that went away\n const onClose = (): void => {\n if (!res.writableFinished) gone();\n };\n res.once(\"close\", onClose);\n if (res.destroyed) gone();\n const leave = async (): Promise<null> => {\n await iterator.return?.();\n return null;\n };\n const next = async (): Promise<RequestEvent<T> | \"gone\"> => {\n const step = await Promise.race([iterator.next(), closed]);\n if (step === \"gone\") return step;\n if (step.done === true) throw new Error(\"the stream of the request ended without its end\");\n return step.value;\n };\n /** This function writes one part of the body, and waits while the client reads slower than the work. */\n const send = async (chunk: string | Uint8Array): Promise<boolean> => {\n if (res.write(chunk)) return true;\n const drained = new Promise<\"drained\">((resolve) => res.once(\"drain\", () => resolve(\"drained\")));\n return (await Promise.race([drained, closed])) === \"drained\";\n };\n const answered = markAnswer(res, ownerOf(stream));\n try {\n const format = options.stream;\n if (format === undefined) {\n const items: T[] = [];\n for (;;) {\n const event = await next();\n if (event === \"gone\") return await leave();\n if (event.kind === \"item\") {\n items.push(event.value);\n continue;\n }\n writeWhole(res, outcomeResponse(items, event, options));\n return event;\n }\n }\n const first = await next();\n if (first === \"gone\") return await leave();\n if (first.kind !== \"item\") {\n // an empty stream that ended well gives 200 and its end. Each other end before the first item gets a status\n if (first.kind === \"done\")\n writeWhole(res, ruled({ status: 200, headers: { \"content-type\": format.contentType, \"cache-control\": \"no-store\" }, body: format.end(first) }, options.retryAfter, options.keepAlive));\n else writeWhole(res, outcomeResponse([], first, options));\n return first;\n }\n if (res.headersSent) {\n res.destroy();\n return await leave();\n }\n const head = ruled({ status: 200, headers: { \"content-type\": format.contentType, \"cache-control\": \"no-store\" }, body: \"\" }, options.retryAfter, options.keepAlive);\n if (head.headers[\"connection\"] === \"close\") res.shouldKeepAlive = false;\n res.writeHead(200, head.headers);\n if (!(await send(format.item(first.value)))) return await leave();\n for (;;) {\n const event = await next();\n if (event === \"gone\") return await leave();\n if (event.kind === \"item\") {\n if (!(await send(format.item(event.value)))) return await leave();\n continue;\n }\n // the end after the first item is in-band: the status went with the first item\n res.end(format.end(event));\n return event;\n }\n } finally {\n res.off(\"close\", onClose);\n answered();\n }\n};\n\n/** A route of a handler is the name of the route that a controller declares, or a function that gives it from the request. */\nexport type HandledRoute = string | ((req: IncomingMessage) => string | null);\n\n/** A handled work does the work of one request of `node:http`. Its signal aborts when the shutdown or the client stops the request. */\nexport type HandledWork<T> = (req: IncomingMessage, signal: AbortSignal) => RequestWork<T>;\n\n/** These options bind a request of `node:http` to the request tracking of the service. */\nexport interface HandleOptions<T> extends Omit<OutcomeOptions<T>, \"keepAlive\" | \"retryAfter\"> {\n /** This function gives the detail of a request, for the function of a route pattern. */\n readonly detail?: ((req: IncomingMessage) => unknown) | undefined;\n}\n\n/**\n * This function gives a request handler that does each request of a route through `life.request`\n * (S6 A12 and A13). The transport gives the lifecycle and `server_retry_after` through the context.\n * - A route name takes a request whose method and path are that name, for example `POST /orders`.\n * A function gives the name of a route or of a pattern, or null for a request of another handler.\n * - Before the start of the transport, each request gets a retryable 503.\n * - The response is unary, or a stream with the option `stream`, as `writeOutcome` writes it.\n */\nexport const handle = <T>(controller: string, route: HandledRoute, work: HandledWork<T>, options: HandleOptions<T> = {}): RequestHandler => {\n const nameOf = (req: IncomingMessage): string | null => {\n if (typeof route === \"function\") return route(req);\n return `${req.method ?? \"GET\"} ${pathOf(req.url)}` === route ? route : null;\n };\n return async (req: IncomingMessage, res: ServerResponse, context: MountContext): Promise<boolean> => {\n const name = nameOf(req);\n if (name === null) return false;\n const life = context.life;\n const written: OutcomeOptions<T> = {\n ...options,\n keepAlive: () => life?.keepAlive() ?? false,\n retryAfter: context.retryAfter,\n };\n if (life === null) {\n const refused: RequestEnd = { kind: \"refused\", status: 503, retryable: true, cause: \"the transport has not started\", delivered: 0 };\n writeWhole(res, outcomeResponse<T>([], refused, written));\n return true;\n }\n const given: RequestOptions = options.detail === undefined ? {} : { detail: options.detail(req) };\n // the stream comes from the lifecycle of the transport, thus the drain of the transport can cut it\n await writeOutcome(res, ownStream(life.request(controller, name, (signal) => work(req, signal), given), life), written);\n return true;\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"http.js","names":["knobMilliseconds"],"sources":["../src/http/answers.ts","../src/http/transport.ts","../src/http/probes.ts","../src/http/outcome.ts"],"sourcesContent":["// The answers that the request tracking still writes (SYS-29). `writeOutcome` marks each response\n// while it waits for the end of its request, with the lifecycle that made the stream. At the deadline\n// of its drain, the transport waits for each response of its own lifecycle: the cut of the drain gives\n// that end at the same time. A stream of another lifecycle, or of none, does not hold the drain.\n\nconst marked = new WeakMap<object, object | null>();\nconst watchers = new Set<() => void>();\n\n/**\n * This function marks a response that waits for the end of its request. `owner` is the lifecycle\n * that made the stream, or null. It gives the function that removes the mark.\n */\nexport const markAnswer = (res: object, owner: object | null): (() => void) => {\n marked.set(res, owner);\n return () => {\n if (!marked.delete(res)) return;\n for (const watcher of [...watchers]) watcher();\n };\n};\n\n/**\n * This function gives the mark of a response: the lifecycle that made its stream. It gives null for a\n * stream outside each lifecycle, and undefined for a response with no mark.\n */\nexport const answerOwner = (res: object): object | null | undefined => marked.get(res);\n\n/** This function starts `watcher` after each removal of a mark. It gives the function that stops it. */\nexport const watchAnswers = (watcher: () => void): (() => void) => {\n watchers.add(watcher);\n return () => {\n watchers.delete(watcher);\n };\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport type { IncomingMessage, Server, ServerEventMap, ServerResponse } from \"node:http\";\nimport type { ListenOptions, Socket } from \"node:net\";\nimport type { Cancel, Clock } from \"../clock.ts\";\nimport { realClock } from \"../clock.ts\";\nimport type { LateHold } from \"../drains.ts\";\nimport { holdLate, noteOutside } from \"../drains.ts\";\nimport { millisecondsOf as knobMilliseconds } from \"../knobs.ts\";\nimport type { Lifecycle, TransportImpl } from \"../lifecycle.ts\";\nimport { answerOwner, watchAnswers } from \"./answers.ts\";\n\n/**\n * These are the knobs of an HTTP transport, by the names of the ordering law. The transport has no\n * defaults, thus each knob is necessary. The declaration of the server gets the same record.\n */\nexport interface HttpKnobs {\n /**\n * This time is the real idle close of the server, which O01 compares with the idle time of a\n * client. Node closes an idle connection at `keepAliveTimeout` plus `keepAliveTimeoutBuffer`.\n * Thus the transport sets `keepAliveTimeout` to this time less the buffer of the server.\n * A time without a bound stops the timer.\n */\n readonly server_idle_close: Extent;\n /**\n * This time is the interval of the sweep of idle connections, from the stop of the intake until the\n * close. A time without a bound gives one sweep, at the stop of the intake.\n */\n readonly server_idle_sweep_interval: Extent;\n /** This time goes in the `Retry-After` of each 503 that the transport gives after its intake stops. */\n readonly server_retry_after: Quantity;\n /** The other knobs of the server go to the declaration. The transport does not read them. */\n readonly [knob: string]: Extent;\n}\n\n/**\n * These are the knobs that the transport gives the declaration: the knobs of the caller, and the two\n * times that the transport found. Thus the declaration shows the three times of the idle close.\n */\nexport interface HttpTransportKnobs extends HttpKnobs {\n /** This time is the hint that each response sends (`Keep-Alive: timeout=…`), in whole seconds. A time without a bound tells that no hint goes. */\n readonly server_idle_close_hint: Extent;\n /** This time is the `keepAliveTimeoutBuffer` of the server, which Node adds to the timer and not to the hint. */\n readonly server_idle_close_buffer: Quantity;\n}\n\n/** A mount stage is the stage of the shutdown at which a mount stops: the stop of the intake, or the close. */\nexport type MountStage = \"stop intake\" | \"close transports\";\n\n/** A mount context gives each handler the signals of the shutdown, the lifecycle and the time of a retry. */\nexport interface MountContext {\n /** This signal aborts when the intake stops. A long stream ends in its own protocol at it. */\n readonly intake: AbortSignal;\n /** This signal aborts at the cooperative abort of the drain, or at the close. The work in flight stops at it. */\n readonly signal: AbortSignal;\n /** This lifecycle is the lifecycle that the transport got at its start, or null before the start. */\n readonly life: Lifecycle | null;\n /** This time is the knob `server_retry_after`: the `Retry-After` of each 503 of the handlers. */\n readonly retryAfter: Quantity;\n}\n\n/**\n * A request handler answers a request and gives true, or it gives false and does not touch the\n * response. Then the next handler gets the request. `until` tells when the handler stops.\n */\nexport interface RequestHandler {\n (req: IncomingMessage, res: ServerResponse, context: MountContext): boolean | Promise<boolean>;\n readonly until?: MountStage | undefined;\n}\n\ntype UpgradeArgs = ServerEventMap[\"upgrade\"];\n\n/** An upgrade socket is the stream of an upgraded connection, as the `upgrade` event of Node gives it. */\nexport type UpgradeSocket = UpgradeArgs[1];\n\n/** An upgrade handler takes an upgraded connection, for example a WebSocket server in its `noServer` mode. */\nexport interface UpgradeHandler {\n (req: IncomingMessage, socket: UpgradeSocket, head: UpgradeArgs[2], context: MountContext): void;\n readonly until?: MountStage | undefined;\n}\n\n/** An upgrade match is a path, without its query, or a function that tells if a handler takes a request. */\nexport type UpgradeMatch = string | ((req: IncomingMessage) => boolean);\n\n/** These options change one mount. */\nexport interface MountOptions {\n /**\n * This stage is when the mount stops. Without it, the stage of the handler applies, and then\n * `stop intake`. A mount until `close transports` keeps the listener open through the drain.\n */\n readonly until?: MountStage | undefined;\n}\n\n/** These options make an HTTP transport. */\nexport interface HttpTransportOptions {\n /**\n * These are the listen options of `start()`, or a function that makes the server listen. Without\n * them, `start()` takes a server that listens already.\n */\n readonly listen?: ListenOptions | ((server: Server) => Promise<void> | void) | undefined;\n /** This clock is the clock of the lifecycle. The deadline of the drain and the sweep use it. */\n readonly clock?: Clock | undefined;\n /**\n * This function tells if a response can keep its connection open. Without it, the transport reads\n * `life.keepAlive()` of the lifecycle of its start. The transport reads it when the headers of\n * each response go, and false gives `Connection: close`.\n */\n readonly keepAlive?: (() => boolean) | undefined;\n /** This function receives each error of a handler. The transport then answers 500, or it destroys the response. */\n readonly onError?: ((error: unknown, req: IncomingMessage) => void) | undefined;\n}\n\n/** An HTTP transport is a server of `node:http` as a transport of the lifecycle, with a list of mounts on its listener. */\nexport interface HttpTransport extends TransportImpl {\n readonly server: Server;\n /** These knobs go to the declaration of the server: the knobs of the caller, the hint and the buffer. */\n readonly knobs: HttpTransportKnobs;\n /** This method adds a request handler at the end of the list, and gives the function that removes it. */\n mount(handler: RequestHandler, options?: MountOptions): () => void;\n /** This method adds an upgrade handler for a path, and gives the function that removes it. */\n upgrade(match: UpgradeMatch, handler: UpgradeHandler, options?: MountOptions): () => void;\n /** This count is the number of open connections, idle connections and upgraded connections too. */\n readonly connections: number;\n /** This flag tells if the transport stopped its intake. */\n readonly intakeStopped: boolean;\n}\n\ninterface RequestMount {\n readonly handler: RequestHandler;\n readonly until: MountStage;\n}\n\ninterface UpgradeMount {\n readonly matches: (req: IncomingMessage) => boolean;\n readonly handler: UpgradeHandler;\n readonly until: MountStage;\n}\n\n/** This function gives the path of a request target, without its query and fragment. */\nexport const pathOf = (url: string | undefined): string => {\n const raw = url ?? \"/\";\n const end = raw.search(/[?#]/);\n const path = end === -1 ? raw : raw.slice(0, end);\n if (path.startsWith(\"/\")) return path;\n // an absolute target, as a proxy sends it\n try {\n return new URL(raw).pathname;\n } catch {\n return path;\n }\n};\n\n/** This function gives a knob in milliseconds, or infinity for a time without a bound. */\nconst millisecondsOf = (knobs: Readonly<Record<string, Extent | undefined>>, name: string, owner = \"httpTransport\"): number =>\n knobMilliseconds(knobs, name, owner);\n\n/**\n * This function gives the `Retry-After` of the knob `server_retry_after`, in whole seconds, rounded\n * up. The knob is necessary, and it has a bound.\n */\nexport const retryAfterOf = (knobs: Readonly<Record<string, Extent | undefined>>, owner: string): string => {\n const retryAfter = millisecondsOf(knobs, \"server_retry_after\", owner);\n if (!Number.isFinite(retryAfter)) throw new Error(\"the knob server_retry_after has no bound: a Retry-After needs a time\");\n return String(Math.ceil(retryAfter / 1000));\n};\n\n/** This function tells if the headers of a call of `writeHead` name a header, an object or a flat list of names and values. */\nconst names = (headers: unknown, name: string): boolean => {\n if (Array.isArray(headers)) return headers.some((each, at) => at % 2 === 0 && String(each).toLowerCase() === name);\n return typeof headers === \"object\" && headers !== null && Object.keys(headers).some((key) => key.toLowerCase() === name);\n};\n\n/** This function gives the headers of a call of `writeHead` without one header. */\nconst without = (headers: unknown, name: string): unknown => {\n if (Array.isArray(headers)) {\n const kept: unknown[] = [];\n for (let at = 0; at + 1 < headers.length; at += 2)\n if (String(headers[at]).toLowerCase() !== name) kept.push(headers[at], headers[at + 1]);\n return kept;\n }\n if (typeof headers !== \"object\" || headers === null) return headers;\n return Object.fromEntries(Object.entries(headers).filter(([key]) => key.toLowerCase() !== name));\n};\n\n/**\n * This function applies the rule of `Retry-After` to a call of `writeHead`, and gives its arguments.\n * A 503 without `Retry-After` gets the time of `server_retry_after` (RFC 9110). A 500 loses each\n * `Retry-After`, from the call and from the response.\n */\nconst retryRule = (res: ServerResponse, args: unknown[], retryAfter: string): unknown[] => {\n const status = typeof args[0] === \"number\" ? args[0] : res.statusCode;\n const at = typeof args[1] === \"string\" ? 2 : 1;\n if (status === 503 && !res.hasHeader(\"retry-after\") && !names(args[at], \"retry-after\")) res.setHeader(\"retry-after\", retryAfter);\n if (status !== 500) return args;\n res.removeHeader(\"retry-after\");\n if (!names(args[at], \"retry-after\")) return args;\n const given = [...args];\n given[at] = without(args[at], \"retry-after\");\n return given;\n};\n\nconst STATUS_TEXT: Readonly<Record<number, string>> = { 404: \"Not Found\", 500: \"Internal Server Error\", 503: \"Service Unavailable\" };\n\n/**\n * This function makes a server of `node:http` a transport of the lifecycle (S6 A8 and A13). It is not\n * a framework: it has no routes, no body parser and no middleware.\n * - The count of work in flight comes from events: each response until its `close`, and each upgraded\n * connection until its `close`.\n * - `stopIntake` closes the listener, and new connections get a refusal. It closes the idle connections\n * at once and at each `server_idle_sweep_interval`. Each later response gets `Connection: close`.\n * A connection is not idle while the bytes of its answer go (SYS-33).\n * - `drain` waits until no work is in flight, or until the deadline. It gives the cooperative abort to\n * the handlers through their context. At the deadline, it also waits for each answer that is to come\n * or to go out (SYS-29). The cap of the phase bounds that wait. Answers that did not go out by the\n * cap are not a failure of the phase. A stream outside the lifecycle of the transport does not hold it.\n * - `close` destroys each connection. A second close gives the same promise.\n * - The mounts share one port. The first request handler that gives true answers. After the stop of\n * the intake, only the mounts until `close transports` answer, and each other request gets a 503.\n */\nexport const httpTransport = (server: Server, knobs: HttpKnobs, options: HttpTransportOptions = {}): HttpTransport => {\n const clock = options.clock ?? realClock;\n const idleClose = millisecondsOf(knobs, \"server_idle_close\");\n const sweepEvery = millisecondsOf(knobs, \"server_idle_sweep_interval\");\n if (sweepEvery === 0)\n throw new Error(\"the knob server_idle_sweep_interval is zero: a sweep needs an interval above zero\");\n const retryAfterHeader = retryAfterOf(knobs, \"httpTransport\");\n for (const derived of [\"server_idle_close_hint\", \"server_idle_close_buffer\"])\n if ((knobs as Readonly<Record<string, Extent | undefined>>)[derived] !== undefined)\n throw new Error(`the knob ${derived} comes from the transport: give only server_idle_close`);\n if (server.listenerCount(\"request\") > 0 || server.listenerCount(\"upgrade\") > 0)\n throw new Error(\"the server has a request or upgrade listener of its own: mount it as a handler\");\n // Node closes an idle connection at keepAliveTimeout + keepAliveTimeoutBuffer, and its hint is\n // keepAliveTimeout. The buffer stays, thus the hint is less than the real close. An older Node has no buffer\n const buffer = (server as { keepAliveTimeoutBuffer?: number }).keepAliveTimeoutBuffer ?? 0;\n if (Number.isFinite(idleClose) && idleClose <= buffer)\n throw new Error(\n `the knob server_idle_close is ${idleClose} ms, at or below the keepAliveTimeoutBuffer of the server, ${buffer} ms: ` +\n \"the server closes an idle connection at keepAliveTimeout + keepAliveTimeoutBuffer\",\n );\n // 0 stops the keep-alive timer and the hint in Node\n server.keepAliveTimeout = Number.isFinite(idleClose) ? idleClose - buffer : 0;\n const declared: HttpTransportKnobs = {\n ...knobs,\n server_idle_close_hint: Number.isFinite(idleClose)\n ? { value: Math.floor(server.keepAliveTimeout / 1000), unit: \"s\" }\n : { value: \"infinite\", unit: \"s\" },\n server_idle_close_buffer: { value: buffer, unit: \"ms\" },\n };\n\n const requestMounts: RequestMount[] = [];\n const upgradeMounts: UpgradeMount[] = [];\n const responses = new Set<ServerResponse>();\n const upgraded = new Set<UpgradeSocket>();\n const sockets = new Set<Socket>();\n // the check of each drain: it ends its drain when nothing holds it\n const drainers = new Set<() => void>();\n const closedWaiters = new Set<() => void>();\n const intake = new AbortController();\n const cooperative = new AbortController();\n // the lifecycle comes at the start, thus the context reads it at each request\n let life: Lifecycle | null = null;\n const context: MountContext = {\n intake: intake.signal,\n signal: cooperative.signal,\n get life() {\n return life;\n },\n retryAfter: knobs.server_retry_after,\n };\n let intakeOpen = true;\n let listenerOpen = server.listening;\n let serverClosed = !server.listening;\n let sweep: Cancel | null = null;\n let closing: Promise<void> | null = null;\n\n const inFlight = (): number => responses.size + upgraded.size;\n /**\n * This function gives what holds a drain after its deadline. An answer is unflushed when its handler\n * ended it and its bytes did not all go. It is unflushed too while `writeOutcome` waits for a client\n * that does not read. A call runs while `writeOutcome` waits for the end of its request, which the\n * cut of the drain gives. Only a stream of the lifecycle of the transport counts, because only its\n * cut ends the stream. A response that is open for another reason does not hold the drain.\n */\n const lateHold = (): LateHold & { readonly outside: number } => {\n let running = 0;\n let unflushed = 0;\n let outside = 0;\n for (const res of responses) {\n if (res.destroyed) continue;\n if (res.writableEnded) {\n if (!res.writableFinished) unflushed += 1;\n continue;\n }\n const owner = answerOwner(res);\n if (owner === undefined) continue;\n if (life === null || owner !== life) outside += 1;\n else if (res.writableNeedDrain) unflushed += 1;\n else running += 1;\n }\n return { running, unflushed, outside };\n };\n const answering = (): boolean => {\n const hold = lateHold();\n return hold.running + hold.unflushed > 0;\n };\n const report = (error: unknown, req: IncomingMessage): void => {\n try {\n options.onError?.(error, req);\n } catch {\n // an error of the error hook does not stop the transport\n }\n };\n const settled = (): void => {\n for (const check of [...drainers]) check();\n if (closing !== null && inFlight() === 0 && sockets.size === 0 && serverClosed)\n for (const done of [...closedWaiters]) done();\n };\n // before its start, a transport has no lifecycle: a server that listens already keeps its connections\n const keepsAlive = (): boolean => intakeOpen && (options.keepAlive?.() ?? life?.keepAlive() ?? true);\n const live = <T extends { readonly until: MountStage }>(mounts: readonly T[]): T[] =>\n intakeOpen ? [...mounts] : mounts.filter((mount) => mount.until === \"close transports\");\n const throughDrain = (): boolean =>\n requestMounts.some((mount) => mount.until === \"close transports\") ||\n upgradeMounts.some((mount) => mount.until === \"close transports\");\n const closeListener = (): void => {\n if (!listenerOpen) return;\n listenerOpen = false;\n server.close();\n };\n const startSweep = (): void => {\n if (!Number.isFinite(sweepEvery) || closing !== null) return;\n sweep = clock.after(sweepEvery, () => {\n sweep = null;\n if (closing !== null) return;\n server.closeIdleConnections();\n startSweep();\n });\n };\n\n /**\n * This function watches the headers of each response when they go, whatever wrote them. Each 503\n * gets `Retry-After`, and a 500 gets none. The response closes its connection while the transport\n * cannot keep it.\n */\n const watchHeaders = (res: ServerResponse): void => {\n const writeHead = res.writeHead as (...args: unknown[]) => ServerResponse;\n res.writeHead = ((...args: unknown[]): ServerResponse => {\n if (res.headersSent) return writeHead.apply(res, args);\n if (!keepsAlive()) {\n res.shouldKeepAlive = false;\n res.setHeader(\"connection\", \"close\");\n }\n return writeHead.apply(res, retryRule(res, args, retryAfterHeader));\n }) as ServerResponse[\"writeHead\"];\n };\n const answer = (res: ServerResponse, status: number, headers: Readonly<Record<string, string>> = {}): void => {\n if (res.headersSent) {\n res.destroy();\n return;\n }\n const body = STATUS_TEXT[status] ?? \"\";\n res.writeHead(status, { \"content-type\": \"text/plain; charset=utf-8\", \"content-length\": String(Buffer.byteLength(body)), ...headers });\n res.end(body);\n };\n const refuse = (res: ServerResponse): void => answer(res, 503, { \"retry-after\": retryAfterHeader, connection: \"close\" });\n\n const route = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const mounts = live(requestMounts);\n try {\n for (const mount of mounts) {\n const given = mount.handler(req, res, context);\n const taken = typeof given === \"boolean\" ? given : await given;\n // a handler that wrote its response took the request, whatever it gave\n if (taken || res.headersSent || res.writableEnded) return;\n }\n } catch (error) {\n report(error, req);\n if (!res.writableEnded) answer(res, 500);\n return;\n }\n if (intakeOpen) answer(res, 404);\n else refuse(res);\n };\n\n const onRequest = (req: IncomingMessage, res: ServerResponse): void => {\n responses.add(res);\n // the bytes of an answer went: a drain after its deadline can end\n res.once(\"finish\", settled);\n res.once(\"close\", () => {\n responses.delete(res);\n settled();\n });\n watchHeaders(res);\n void route(req, res);\n };\n\n const onUpgrade = (req: IncomingMessage, socket: UpgradeSocket, head: UpgradeArgs[2]): void => {\n socket.on(\"error\", () => {});\n const mount = live(upgradeMounts).find((each) => each.matches(req));\n if (mount === undefined) {\n const status = intakeOpen ? 404 : 503;\n const extra = intakeOpen ? \"\" : `Retry-After: ${retryAfterHeader}\\r\\n`;\n socket.end(`HTTP/1.1 ${status} ${STATUS_TEXT[status]!}\\r\\nConnection: close\\r\\nContent-Length: 0\\r\\n${extra}\\r\\n`);\n return;\n }\n if (socket.destroyed) return;\n upgraded.add(socket);\n socket.once(\"close\", () => {\n upgraded.delete(socket);\n settled();\n });\n try {\n mount.handler(req, socket, head, context);\n } catch (error) {\n report(error, req);\n socket.destroy();\n }\n };\n\n const onConnection = (socket: Socket): void => {\n sockets.add(socket);\n socket.once(\"close\", () => {\n sockets.delete(socket);\n settled();\n });\n };\n\n /**\n * Node counts a connection idle at the `end()` of its response, also while the bytes of the body\n * still go. Then `closeIdleConnections` destroys the socket and cuts the answer (SYS-33). This\n * method replaces it on the server, thus `server.close()` uses it too. Node keeps the socket of\n * a response that did not end. Thus the method shows each answer whose bytes did not go as not\n * ended, for the time of the call.\n */\n const closeIdle = server.closeIdleConnections;\n server.closeIdleConnections = function closeIdleConnections(this: Server): void {\n const going = [...responses].filter((res) => res.writableEnded && !res.writableFinished && !res.destroyed);\n for (const res of going) res.finished = false;\n try {\n closeIdle.call(this);\n } finally {\n for (const res of going) res.finished = true;\n }\n };\n\n server.on(\"request\", onRequest);\n server.on(\"upgrade\", onUpgrade);\n server.on(\"connection\", onConnection);\n server.on(\"listening\", () => {\n listenerOpen = true;\n serverClosed = false;\n });\n server.on(\"close\", () => {\n listenerOpen = false;\n serverClosed = true;\n settled();\n });\n\n const remover = <T>(list: T[], entry: T) => (): void => {\n const at = list.indexOf(entry);\n if (at >= 0) list.splice(at, 1);\n // the last mount through the drain went: the listener closes as the stop of the intake asks\n if (!intakeOpen && closing === null && !throughDrain()) closeListener();\n };\n\n return {\n server,\n knobs: declared,\n get connections() {\n return sockets.size;\n },\n get intakeStopped() {\n return !intakeOpen;\n },\n mount(handler, mountOptions = {}) {\n const entry: RequestMount = { handler, until: mountOptions.until ?? handler.until ?? \"stop intake\" };\n requestMounts.push(entry);\n return remover(requestMounts, entry);\n },\n upgrade(match, handler, mountOptions = {}) {\n const matches = typeof match === \"string\" ? (req: IncomingMessage) => pathOf(req.url) === match : match;\n const entry: UpgradeMount = { matches, handler, until: mountOptions.until ?? handler.until ?? \"stop intake\" };\n upgradeMounts.push(entry);\n return remover(upgradeMounts, entry);\n },\n async start(_signal, given) {\n life = given;\n if (closing !== null) throw new Error(\"the transport closed: a closed transport does not start again\");\n // a listener that another owner adds after the transport answers too, thus the start refuses it\n if (server.listenerCount(\"request\") > 1 || server.listenerCount(\"upgrade\") > 1)\n throw new Error(\"the server has a request or upgrade listener of its own: mount it as a handler\");\n if (server.listening) return;\n const listen = options.listen;\n if (listen === undefined) throw new Error(\"the server does not listen, and the transport has no listen options\");\n if (typeof listen === \"function\") {\n await listen(server);\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const failed = (error: Error): void => {\n server.off(\"listening\", listened);\n reject(error);\n };\n const listened = (): void => {\n server.off(\"error\", failed);\n resolve();\n };\n server.once(\"error\", failed);\n server.once(\"listening\", listened);\n server.listen(listen);\n });\n },\n stopIntake() {\n if (!intakeOpen) return;\n intakeOpen = false;\n intake.abort(new Error(\"the transport stopped its intake\"));\n if (!throughDrain()) closeListener();\n server.closeIdleConnections();\n startSweep();\n },\n drain(deadline, signal) {\n const abort = (): void => {\n if (!cooperative.signal.aborted) cooperative.abort(signal.reason);\n };\n if (signal.aborted) abort();\n else signal.addEventListener(\"abort\", abort, { once: true });\n // nothing in flight: the drain ends at once, with no timer\n if (inFlight() === 0) {\n signal.removeEventListener(\"abort\", abort);\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n let cancel: Cancel = () => {};\n let unwatch: () => void = () => {};\n let unhold: () => void = () => {};\n let late = false;\n let ended = false;\n // before the deadline, the work in flight holds the drain. After it, only an answer to come or to go out holds it\n const check = (): void => {\n if (ended || (inFlight() > 0 && (!late || answering()))) return;\n ended = true;\n drainers.delete(check);\n cancel();\n unwatch();\n unhold();\n signal.removeEventListener(\"abort\", abort);\n resolve();\n };\n // the cut of the requests comes at the same time, before or after this timer: the drain waits for its answers in each case\n const reached = (): void => {\n late = true;\n unwatch = watchAnswers(check);\n // the lifecycle reads the hold at the cap: answers that do not go out are not a failure of the phase\n if (life !== null) {\n unhold = holdLate(life, () => {\n const { running, unflushed } = lateHold();\n return { running, unflushed };\n });\n // a stream outside the lifecycle does not hold the drain: the record of the phase gives the count\n noteOutside(life, lateHold().outside);\n }\n check();\n };\n drainers.add(check);\n if (deadline <= clock.now()) reached();\n else cancel = clock.after(deadline - clock.now(), reached);\n });\n },\n inFlight,\n close() {\n if (closing !== null) return closing;\n intakeOpen = false;\n if (!intake.signal.aborted) intake.abort(new Error(\"the transport closed\"));\n if (!cooperative.signal.aborted) cooperative.abort(new Error(\"the transport closed\"));\n sweep?.();\n sweep = null;\n closing = new Promise<void>((resolve) => {\n closedWaiters.add(resolve);\n });\n closeListener();\n server.closeAllConnections();\n for (const socket of [...upgraded]) socket.destroy();\n for (const socket of [...sockets]) socket.destroy();\n settled();\n return closing;\n },\n };\n};\n","import type { IncomingMessage, Server, ServerResponse } from \"node:http\";\nimport { createServer } from \"node:http\";\nimport type { ListenOptions, Socket } from \"node:net\";\nimport type { Health, ProbeKind } from \"../health.ts\";\nimport type { HealthServer } from \"../lifecycle.ts\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport type { MountContext, RequestHandler } from \"./transport.ts\";\nimport { pathOf, retryAfterOf } from \"./transport.ts\";\n\n/** A probe response is the status, the headers and the body of one probe, for any HTTP framework or none. */\nexport interface ProbeResponse {\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: string;\n}\n\n/** A health source is the health runtime, or a function that gives it when it exists. */\nexport type HealthSource = Health | (() => Health | null | undefined);\n\n/**\n * These are the four probe kinds, in the sequence of their paths. A probe server and a probe handler\n * answer all four, unless their option `kinds` names fewer.\n */\nexport const PROBE_KINDS: readonly ProbeKind[] = Object.freeze([\"startup\", \"readiness\", \"liveness\", \"report\"] as const);\n\nconst named = PROBE_KINDS.map((kind) => `\"${kind}\"`).join(\", \");\n\n/**\n * This function gives the probe kinds of an option `kinds`: all four when it is undefined, or else a\n * frozen copy. It refuses a value that is not a list, an empty list, an unknown kind and a kind two times.\n */\nconst kindsOf = (kinds: readonly ProbeKind[] | undefined): readonly ProbeKind[] => {\n if (kinds === undefined) return PROBE_KINDS;\n if (!Array.isArray(kinds)) throw new Error(`the probe kinds ${JSON.stringify(kinds)} are not a list of ${named}`);\n if (kinds.length === 0) throw new Error(`the probe kinds are an empty list: name one or more of ${named}`);\n const seen = new Set<ProbeKind>();\n for (const kind of kinds) {\n if (!PROBE_KINDS.includes(kind)) throw new Error(`the probe kind ${JSON.stringify(kind)} is not one of ${named}`);\n if (seen.has(kind)) throw new Error(`the probe kind \"${kind}\" is two times in the probe kinds`);\n seen.add(kind);\n }\n return Object.freeze([...kinds]);\n};\n\nconst prefixProblem = (prefix: string): string | null =>\n prefix === \"\" || (prefix.startsWith(\"/\") && !prefix.endsWith(\"/\"))\n ? null\n : `the probe prefix \"${prefix}\" does not start with \"/\", or it ends with \"/\"`;\n\n/** These escapes replace `<`, `>` and `&` in a JSON body. The JSON stays valid, with the same value, and no part of it is markup. */\nconst ESCAPES: Readonly<Record<string, string>> = { \"<\": \"\\\\u003c\", \">\": \"\\\\u003e\", \"&\": \"\\\\u0026\" };\n\n/**\n * This function gives a JSON answer, and its body escapes `<`, `>` and `&`. A body can repeat a part\n * of the path. An example is the name of a controller that the service does not have.\n */\nconst json = (status: number, body: unknown): ProbeResponse => ({\n status,\n headers: { \"content-type\": \"application/health+json\", \"cache-control\": \"no-store\" },\n body: JSON.stringify(body).replace(/[<>&]/g, (char) => ESCAPES[char]!),\n});\n\n/**\n * This function gives the response of a probe path from the cache of the health runtime. For a path\n * that is not a probe, it gives null. The paths are `/startup`, `/readiness`, `/liveness` and\n * `/report` after the prefix. `/readiness/<controller>` gives the readiness of one controller.\n * `kinds` names the probes that answer. The path of a kind that it does not name is not a probe.\n */\nexport const probeResponse = (\n health: Health | null | undefined,\n method: string | undefined,\n path: string,\n prefix = \"\",\n kinds: readonly ProbeKind[] = PROBE_KINDS,\n): ProbeResponse | null => {\n if (!path.startsWith(`${prefix}/`)) return null;\n const parts = path.slice(prefix.length + 1).split(\"/\");\n const kind = PROBE_KINDS.find((each) => each === parts[0] && kinds.includes(each));\n if (kind === undefined) return null;\n const controller = parts[1];\n if (parts.length > 2 || (controller !== undefined && (kind !== \"readiness\" || controller === \"\"))) return null;\n if (method !== \"GET\" && method !== \"HEAD\")\n return { status: 405, headers: { allow: \"GET, HEAD\", \"content-length\": \"0\" }, body: \"\" };\n // before the runtime exists, each probe fails: the service has not started\n if (health === null || health === undefined) return json(503, { status: \"fail\", probe: kind });\n try {\n const answer = health.probe(kind, controller === undefined ? undefined : decodeURIComponent(controller));\n return { ...json(answer.status, answer.body), headers: { \"content-type\": answer.contentType, \"cache-control\": \"no-store\" } };\n } catch {\n // a controller that the service does not have\n return json(404, { status: \"fail\", probe: kind, controller });\n }\n};\n\n/**\n * This function writes a probe response. Each response gets `X-Content-Type-Options: nosniff`, thus a\n * browser does not read it as a different type. A 503 gets `Retry-After`, when the writer has the time of a retry.\n */\nconst write = (req: IncomingMessage, res: ServerResponse, response: ProbeResponse, retryAfter: string | undefined): void => {\n const retry = response.status === 503 && retryAfter !== undefined ? { \"retry-after\": retryAfter } : {};\n const length = String(Buffer.byteLength(response.body));\n res.writeHead(response.status, { ...response.headers, ...retry, \"x-content-type-options\": \"nosniff\", \"content-length\": length });\n res.end(req.method === \"HEAD\" ? undefined : response.body);\n};\n\n/** These options mount the probes on the port of the application. */\nexport interface ProbeHandlerOptions {\n /** This path goes before each probe path, for example `/health`. It starts with `/`, or it is empty. */\n readonly prefix: string;\n /**\n * These are the probes that the handler answers. The default is all four of `PROBE_KINDS`. The\n * path of a kind that the list does not name goes to the next handler.\n */\n readonly kinds?: readonly ProbeKind[] | undefined;\n}\n\n/**\n * This function gives a request handler that answers the probes on the port of the application. It\n * answers until `close transports`, thus its mount keeps the listener open through the drain. A path\n * that is not a probe goes to the next handler. Each 503 gets the `Retry-After` of the context.\n */\nexport const probeHandler = (health: HealthSource, options: ProbeHandlerOptions): RequestHandler => {\n const problem = prefixProblem(options.prefix);\n if (problem !== null) throw new Error(problem);\n const kinds = kindsOf(options.kinds);\n const handler = (req: IncomingMessage, res: ServerResponse, context?: MountContext): boolean => {\n const runtime = typeof health === \"function\" ? health() : health;\n const response = probeResponse(runtime, req.method, pathOf(req.url), options.prefix, kinds);\n if (response === null) return false;\n // the transport gives server_retry_after through the context, and it adds the header to each 503 too\n const retryAfter = context?.retryAfter === undefined ? undefined : retryAfterOf({ server_retry_after: context.retryAfter }, \"probeHandler\");\n write(req, res, response, retryAfter);\n return true;\n };\n return Object.assign(handler, { until: \"close transports\" as const });\n};\n\n/** These options make a probe server: the listen options of its port, the prefix of its paths, and the time of a retry. */\nexport interface ProbeServerOptions extends ListenOptions {\n /**\n * This time goes in the `Retry-After` of each 503 of a probe, as on the port of the application.\n * The probe server has no defaults, thus the knob is necessary.\n */\n readonly server_retry_after: Quantity;\n /** This path goes before each probe path. The default is no prefix, because the port has only the probes. */\n readonly prefix?: string | undefined;\n /** This server answers the probes. Without it, the probe server makes a server of `node:http`. */\n readonly server?: Server | undefined;\n /**\n * These are the probes that the server answers. The default is all four of `PROBE_KINDS`. The path\n * of a kind that the list does not name gets 404, as each other path.\n */\n readonly kinds?: readonly ProbeKind[] | undefined;\n}\n\n/** A probe server is a health server on its own port. Its server is visible for the address of the port. */\nexport interface ProbeServer extends HealthServer {\n readonly server: Server;\n}\n\n/**\n * This function makes a health server on a port of its own (S6 decision D5). It starts first and\n * closes last, thus the probes answer through the whole drain. Each probe answers from the cache, and\n * each 503 gets the `Retry-After` of `server_retry_after`.\n */\nexport const probeServer = (options: ProbeServerOptions): ProbeServer => {\n const { prefix = \"\", server = createServer(), server_retry_after, kinds: chosen, ...listen } = options;\n const retryAfter = retryAfterOf({ server_retry_after }, \"probeServer\");\n const problem = prefixProblem(prefix);\n if (problem !== null) throw new Error(problem);\n const kinds = kindsOf(chosen);\n if (server.listenerCount(\"request\") > 0) throw new Error(\"the probe server has a request listener of its own\");\n let health: Health | null = null;\n let closing: Promise<void> | null = null;\n const sockets = new Set<Socket>();\n server.on(\"connection\", (socket: Socket) => {\n sockets.add(socket);\n socket.once(\"close\", () => sockets.delete(socket));\n });\n server.on(\"request\", (req: IncomingMessage, res: ServerResponse) => {\n const response = probeResponse(health, req.method, pathOf(req.url), prefix, kinds);\n write(req, res, response ?? { status: 404, headers: { \"content-type\": \"text/plain; charset=utf-8\" }, body: \"Not Found\" }, retryAfter);\n });\n return {\n server,\n listen(runtime) {\n health = runtime;\n if (server.listening) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n const failed = (error: Error): void => {\n server.off(\"listening\", listened);\n reject(error);\n };\n const listened = (): void => {\n server.off(\"error\", failed);\n resolve();\n };\n server.once(\"error\", failed);\n server.once(\"listening\", listened);\n server.listen(listen);\n });\n },\n close() {\n if (closing !== null) return closing;\n closing = new Promise<void>((resolve) => {\n if (!server.listening) {\n resolve();\n return;\n }\n server.close(() => resolve());\n server.closeAllConnections();\n for (const socket of [...sockets]) socket.destroy();\n });\n return closing;\n },\n };\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { ownerOf, ownStream } from \"../drains.ts\";\nimport type { Refused } from \"../limiter/policy.ts\";\nimport { retryAfterSeconds } from \"../limiter/policy.ts\";\nimport type { RequestEnd, RequestEvent, RequestOptions, RequestStream, RequestWork } from \"../requests.ts\";\nimport { markAnswer } from \"./answers.ts\";\nimport type { MountContext, RequestHandler } from \"./transport.ts\";\nimport { pathOf } from \"./transport.ts\";\n\n/** An outcome response is the status, the headers and the body of a request, for any HTTP framework or none. */\nexport interface OutcomeResponse {\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: string | Uint8Array;\n}\n\n/** A response part is what a hook gives for one value or one failure. The parts that it does not give keep the values of the mapping. */\nexport interface ResponsePart {\n readonly status?: number | undefined;\n readonly headers?: Readonly<Record<string, string>> | undefined;\n readonly body?: string | Uint8Array | undefined;\n}\n\n/**\n * A stream format writes the items of a stream and its end in the body, as they come. An end after\n * the first item goes in the body, because the status went with the first item.\n */\nexport interface StreamFormat<T = unknown> {\n readonly contentType: string;\n /** This function gives the bytes of one item. */\n item(value: T): string | Uint8Array;\n /** This function gives the bytes of the end, the last part of the body. */\n end(end: RequestEnd): string | Uint8Array;\n}\n\n/** These options map a request to a response. */\nexport interface OutcomeOptions<T> {\n /**\n * This value tells if the response can keep its connection open, as `life.keepAlive()` does. False\n * gives `Connection: close`. A function is read when the headers go.\n */\n readonly keepAlive: boolean | (() => boolean);\n /** This time goes in the `Retry-After` of each 503, in whole seconds, rounded up. It is the knob `server_retry_after`. */\n readonly retryAfter: Quantity;\n /**\n * This function gives the response of the value of a unary request. Without it, the mapping gives\n * 200 and the value as JSON, or 204 for no value.\n */\n readonly done?: ((value: T) => ResponsePart) | undefined;\n /**\n * This function gives the response of a failure of the route before the first byte. Without it,\n * the mapping gives 500 with no detail, because an error can hold private data.\n */\n readonly failed?: ((error: unknown) => ResponsePart) | undefined;\n /**\n * This format makes the response a stream: the status and each item go as they come, and the end\n * goes in the body. Without it, the response is unary: one status and the one value.\n */\n readonly stream?: StreamFormat<T> | undefined;\n}\n\nconst JSON_TYPE = \"application/json; charset=utf-8\";\n\n/**\n * This function gives an end as a record for a body: its kind and the count of its items. A cut and\n * a refusal also give their cause, and if the caller can send the request again. A failure gives\n * no detail.\n */\nexport const endRecord = (end: RequestEnd): Readonly<Record<string, unknown>> => {\n if (end.kind === \"refused\" || end.kind === \"cut\")\n return { end: end.kind, delivered: end.delivered, retryable: end.retryable, cause: end.cause };\n return { end: end.kind, delivered: end.delivered };\n};\n\n/** This format writes each item and the end as one line of JSON (NDJSON). */\nexport const ndjson: StreamFormat = {\n contentType: \"application/x-ndjson; charset=utf-8\",\n item: (value) => `${JSON.stringify(value)}\\n`,\n end: (end) => `${JSON.stringify(endRecord(end))}\\n`,\n};\n\n/** This format writes each item as an event of server-sent events, and the end as the event `end`. */\nexport const sse: StreamFormat = {\n contentType: \"text/event-stream; charset=utf-8\",\n item: (value) => `data: ${JSON.stringify(value)}\\n\\n`,\n end: (end) => `event: end\\ndata: ${JSON.stringify(endRecord(end))}\\n\\n`,\n};\n\nconst valueResponse = (value: unknown): OutcomeResponse => {\n if (value === undefined) return { status: 204, headers: {}, body: \"\" };\n if (typeof value === \"string\") return { status: 200, headers: { \"content-type\": \"text/plain; charset=utf-8\" }, body: value };\n if (value instanceof Uint8Array) return { status: 200, headers: { \"content-type\": \"application/octet-stream\" }, body: value };\n return { status: 200, headers: { \"content-type\": JSON_TYPE }, body: JSON.stringify(value) };\n};\n\nconst merge = (base: OutcomeResponse, part: ResponsePart | undefined): OutcomeResponse =>\n part === undefined\n ? base\n : { status: part.status ?? base.status, headers: { ...base.headers, ...part.headers }, body: part.body ?? base.body };\n\nconst keeps = (keepAlive: boolean | (() => boolean)): boolean => (typeof keepAlive === \"function\" ? keepAlive() : keepAlive);\n\n/**\n * This function applies the rules of the headers to a response. Each 503 gets `Retry-After`, and a\n * 500 gets none. The response closes its connection while the service cannot keep it.\n */\nconst ruled = (response: OutcomeResponse, retryAfter: Quantity, keepAlive: boolean | (() => boolean)): OutcomeResponse => {\n const headers: Record<string, string> = { ...response.headers };\n if (response.status === 503 && headers[\"retry-after\"] === undefined)\n headers[\"retry-after\"] = String(Math.max(0, Math.ceil(valueIn(retryAfter, \"s\"))));\n if (response.status === 500) delete headers[\"retry-after\"];\n if (!keeps(keepAlive)) headers[\"connection\"] = \"close\";\n return { ...response, headers };\n};\n\n/** This function gives the status response of an end before the first byte of the body. */\nconst endStatus = <T>(end: RequestEnd, options: OutcomeOptions<T>): OutcomeResponse => {\n const body = JSON.stringify(endRecord(end));\n switch (end.kind) {\n case \"refused\":\n return { status: 503, headers: { \"content-type\": JSON_TYPE }, body };\n case \"cut\":\n // a cut after the first item has no status of its own, but no byte went: its route decides\n return { status: end.status ?? (end.retryable ? 503 : 500), headers: { \"content-type\": JSON_TYPE }, body };\n case \"failed\":\n return merge({ status: 500, headers: { \"content-type\": JSON_TYPE }, body }, options.failed?.(end.error));\n case \"done\":\n return { status: 204, headers: {}, body: \"\" };\n }\n};\n\n/**\n * This function maps a unary request to its response, from its items and its end (S6 A12 and A13).\n * - One value and the end `done`: 200 with the value, or the response of the `done` hook.\n * - No value and the end `done`: 204. More than one value: 500, because the route is not a stream.\n * - `refused`: 503. The work did not start, thus the caller can send it again. The service did not\n * start, it stopped its intake, or a controller is not ready.\n * - `cut`: 503 for a route whose callers can send it again (S6 A12), and 500 for each other route.\n * - `failed`: 500 with no detail, or the response of the `failed` hook. A failure of the route is not a 503.\n *\n * Each 503 gets `Retry-After` (RFC 9110), and a 500 gets none. Each response gets `Connection: close`\n * while the service cannot keep the connection.\n */\nexport const outcomeResponse = <T>(items: readonly T[], end: RequestEnd, options: OutcomeOptions<T>): OutcomeResponse => {\n let response: OutcomeResponse;\n if (end.kind !== \"done\") response = endStatus(end, options);\n else if (items.length === 1) response = merge(valueResponse(items[0]), options.done?.(items[0]!));\n else if (items.length === 0) response = endStatus(end, options);\n else\n response = endStatus(\n { kind: \"failed\", error: new Error(`the work gave ${items.length} values, and a unary response has one`), delivered: items.length },\n options,\n );\n return ruled(response, options.retryAfter, options.keepAlive);\n};\n\n/**\n * This function gives the response of a take that a keyed limiter refused: 429 (RFC 6585), with\n * `Retry-After` in whole seconds, rounded up (RFC 9110). The body tells the cause (`limit`, `cap` or\n * `store`) and the same whole seconds, but not the failure of a store. The work did not start, thus\n * the caller can send it again.\n */\nexport const limitedResponse = (refused: Refused): OutcomeResponse => {\n const seconds = retryAfterSeconds(refused.retryAfter);\n return {\n status: 429,\n headers: { \"content-type\": JSON_TYPE, \"retry-after\": String(seconds) },\n body: JSON.stringify({ end: \"refused\", delivered: 0, retryable: true, cause: `the limiter refused the take: ${refused.cause}`, retryAfter: seconds }),\n };\n};\n\n/** This function writes a whole response of `node:http`. A response whose headers went gets no second status: it is destroyed. */\nconst writeWhole = (res: ServerResponse, response: OutcomeResponse): void => {\n if (res.headersSent) {\n res.destroy();\n return;\n }\n if (response.headers[\"connection\"] === \"close\") res.shouldKeepAlive = false;\n // a 204 has no body, thus no length\n if (response.status === 204) {\n res.writeHead(204, response.headers);\n res.end();\n return;\n }\n const length = typeof response.body === \"string\" ? Buffer.byteLength(response.body) : response.body.byteLength;\n res.writeHead(response.status, { ...response.headers, \"content-length\": String(length) });\n res.end(response.body);\n};\n\n/**\n * This function writes the stream of a request to a response of `node:http`, and gives its end. It\n * gives null when the client closed the connection first. Then the work stops through `return()`.\n * - Unary (no format): it waits for the end, and it writes the status and the body of `outcomeResponse`.\n * - Stream: the status 200 goes with the first item, then each item goes as it comes, then the end\n * goes in the body (in-band). The next item waits until the client reads, thus a slow client\n * slows the work. An end before the first item gets a status, as a unary end does.\n * - The answer to come: until it writes the end, the response holds the drain of `httpTransport` past\n * its deadline. The cut of the drain gives that end, and the drain waits until the end goes out.\n * Only a stream of `life.request` of the lifecycle of the transport holds the drain.\n */\nexport const writeOutcome = async <T>(res: ServerResponse, stream: RequestStream<T>, options: OutcomeOptions<T>): Promise<RequestEnd | null> => {\n const iterator = stream[Symbol.asyncIterator]();\n let gone: () => void = () => {};\n const closed = new Promise<\"gone\">((resolve) => {\n gone = () => resolve(\"gone\");\n });\n // a close before the end of the response is a client that went away\n const onClose = (): void => {\n if (!res.writableFinished) gone();\n };\n res.once(\"close\", onClose);\n if (res.destroyed) gone();\n const leave = async (): Promise<null> => {\n await iterator.return?.();\n return null;\n };\n const next = async (): Promise<RequestEvent<T> | \"gone\"> => {\n const step = await Promise.race([iterator.next(), closed]);\n if (step === \"gone\") return step;\n if (step.done === true) throw new Error(\"the stream of the request ended without its end\");\n return step.value;\n };\n /** This function writes one part of the body, and waits while the client reads slower than the work. */\n const send = async (chunk: string | Uint8Array): Promise<boolean> => {\n if (res.write(chunk)) return true;\n const drained = new Promise<\"drained\">((resolve) => res.once(\"drain\", () => resolve(\"drained\")));\n return (await Promise.race([drained, closed])) === \"drained\";\n };\n const answered = markAnswer(res, ownerOf(stream));\n try {\n const format = options.stream;\n if (format === undefined) {\n const items: T[] = [];\n for (;;) {\n const event = await next();\n if (event === \"gone\") return await leave();\n if (event.kind === \"item\") {\n items.push(event.value);\n continue;\n }\n writeWhole(res, outcomeResponse(items, event, options));\n return event;\n }\n }\n const first = await next();\n if (first === \"gone\") return await leave();\n if (first.kind !== \"item\") {\n // an empty stream that ended well gives 200 and its end. Each other end before the first item gets a status\n if (first.kind === \"done\")\n writeWhole(res, ruled({ status: 200, headers: { \"content-type\": format.contentType, \"cache-control\": \"no-store\" }, body: format.end(first) }, options.retryAfter, options.keepAlive));\n else writeWhole(res, outcomeResponse([], first, options));\n return first;\n }\n if (res.headersSent) {\n res.destroy();\n return await leave();\n }\n const head = ruled({ status: 200, headers: { \"content-type\": format.contentType, \"cache-control\": \"no-store\" }, body: \"\" }, options.retryAfter, options.keepAlive);\n if (head.headers[\"connection\"] === \"close\") res.shouldKeepAlive = false;\n res.writeHead(200, head.headers);\n if (!(await send(format.item(first.value)))) return await leave();\n for (;;) {\n const event = await next();\n if (event === \"gone\") return await leave();\n if (event.kind === \"item\") {\n if (!(await send(format.item(event.value)))) return await leave();\n continue;\n }\n // the end after the first item is in-band: the status went with the first item\n res.end(format.end(event));\n return event;\n }\n } finally {\n res.off(\"close\", onClose);\n answered();\n }\n};\n\n/** A route of a handler is the name of the route that a controller declares, or a function that gives it from the request. */\nexport type HandledRoute = string | ((req: IncomingMessage) => string | null);\n\n/** A handled work does the work of one request of `node:http`. Its signal aborts when the shutdown or the client stops the request. */\nexport type HandledWork<T> = (req: IncomingMessage, signal: AbortSignal) => RequestWork<T>;\n\n/** These options bind a request of `node:http` to the request tracking of the service. */\nexport interface HandleOptions<T> extends Omit<OutcomeOptions<T>, \"keepAlive\" | \"retryAfter\"> {\n /** This function gives the detail of a request, for the function of a route pattern. */\n readonly detail?: ((req: IncomingMessage) => unknown) | undefined;\n}\n\n/**\n * This function gives a request handler that does each request of a route through `life.request`\n * (S6 A12 and A13). The transport gives the lifecycle and `server_retry_after` through the context.\n * - A route name takes a request whose method and path are that name, for example `POST /orders`.\n * A function gives the name of a route or of a pattern, or null for a request of another handler.\n * - Before the start of the transport, each request gets a retryable 503.\n * - The response is unary, or a stream with the option `stream`, as `writeOutcome` writes it.\n */\nexport const handle = <T>(controller: string, route: HandledRoute, work: HandledWork<T>, options: HandleOptions<T> = {}): RequestHandler => {\n const nameOf = (req: IncomingMessage): string | null => {\n if (typeof route === \"function\") return route(req);\n return `${req.method ?? \"GET\"} ${pathOf(req.url)}` === route ? route : null;\n };\n return async (req: IncomingMessage, res: ServerResponse, context: MountContext): Promise<boolean> => {\n const name = nameOf(req);\n if (name === null) return false;\n const life = context.life;\n const written: OutcomeOptions<T> = {\n ...options,\n keepAlive: () => life?.keepAlive() ?? false,\n retryAfter: context.retryAfter,\n };\n if (life === null) {\n const refused: RequestEnd = { kind: \"refused\", status: 503, retryable: true, cause: \"the transport has not started\", delivered: 0 };\n writeWhole(res, outcomeResponse<T>([], refused, written));\n return true;\n }\n const given: RequestOptions = options.detail === undefined ? {} : { detail: options.detail(req) };\n // the stream comes from the lifecycle of the transport, thus the drain of the transport can cut it\n await writeOutcome(res, ownStream(life.request(controller, name, (signal) => work(req, signal), given), life), written);\n return true;\n };\n};\n"],"mappings":"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"}
package/build/index.js CHANGED
@@ -431,7 +431,7 @@ const healthRuntime = (description, runs, options = {}) => {
431
431
  if (kind === "liveness") return answer(kind, now.alive);
432
432
  if (kind === "report") return answer(kind, now.ready && now.alive);
433
433
  if (controller === void 0) return answer(kind, now.ready);
434
- const known = now.controllers[controller];
434
+ const known = Object.hasOwn(now.controllers, controller) ? now.controllers[controller] : void 0;
435
435
  if (known === void 0) throw new Error(`${description.name} has no controller ${controller}`);
436
436
  return answer(kind, known, controller);
437
437
  },