@mark1russell7/service 0.5.12 → 0.5.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -1
- package/build/{builder-DawJhq4p.d.ts → builder-CsSO4LzB.d.ts} +2 -2
- package/build/{core-C2wnF4mg.d.ts → core-D8iXeoE4.d.ts} +54 -4
- package/build/dial-DJolDmOK.d.ts +16 -0
- package/build/grpc.d.ts +307 -0
- package/build/grpc.js +859 -0
- package/build/grpc.js.map +1 -0
- package/build/http-client.d.ts +248 -0
- package/build/http-client.js +612 -0
- package/build/http-client.js.map +1 -0
- package/build/http.d.ts +1 -1
- package/build/index.d.ts +5 -149
- package/build/index.js +119 -3
- package/build/index.js.map +1 -1
- package/build/{lifecycle-fgYfxrHK.d.ts → lifecycle-CMedxaQd.d.ts} +60 -2
- package/build/{machine-Czfb6fV4.js → machine-DZMb_QEU.js} +204 -8
- package/build/machine-DZMb_QEU.js.map +1 -0
- package/build/mongodb.d.ts +307 -0
- package/build/mongodb.js +688 -0
- package/build/mongodb.js.map +1 -0
- package/build/pg.d.ts +2 -2
- package/build/pg.js +30 -33
- package/build/pg.js.map +1 -1
- package/build/queue-CndiCVaY.d.ts +150 -0
- package/build/rabbitmq.d.ts +294 -0
- package/build/rabbitmq.js +748 -0
- package/build/rabbitmq.js.map +1 -0
- package/build/redis.d.ts +4 -10
- package/build/redis.js +6 -38
- package/build/redis.js.map +1 -1
- package/build/testing.d.ts +2 -2
- package/build/testing.js +1 -0
- package/build/testing.js.map +1 -1
- package/build/websocket.d.ts +275 -0
- package/build/websocket.js +780 -0
- package/build/websocket.js.map +1 -0
- package/package.json +38 -1
- package/build/machine-Czfb6fV4.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-client.js","names":["knob","t"],"sources":["../src/http-client/connection.ts","../src/http-client/meets.ts","../src/http-client/client.ts","../src/http-client/machine.ts","../src/http-client/starting.ts"],"sourcesContent":["import type { ClientSocket, Factory } from \"../client/core.ts\";\n\n/** An HTTP socket is the socket of a client in the core: the join of the prober and the bounded close read it. */\nexport type HttpSocket = ClientSocket;\n\n/** An HTTP response is a whole response: its status, its headers, and its whole body. */\nexport interface HttpResponse {\n readonly status: number;\n /** These are the headers of the response, by their names in lower case, as Node gives them. */\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>;\n readonly body: Buffer;\n}\n\n/** An agent request is one request that the wrapper gives the agent. */\nexport interface AgentRequest {\n readonly host: string;\n readonly port: number;\n readonly method: string;\n readonly path: string;\n readonly headers: Readonly<Record<string, string>>;\n readonly body?: Uint8Array | undefined;\n}\n\n/**\n * These are the events of one request that the wrapper reads. The agent gives each one at most one\n * time. `connect` comes only for a new connection. A request on a free socket left the client when it got that socket.\n */\nexport interface AgentEvents {\n /** The agent gave the request a socket. `reused` is true for a socket from the free list (`req.reusedSocket`). */\n socket(socket: HttpSocket, reused: boolean): void;\n /** The new socket of the request connected. The request then leaves the client. */\n connect(): void;\n}\n\n/** An agent exchange is one request in the agent: the promise of its whole response, and the abort that destroys its socket. */\nexport interface AgentExchange {\n /** This promise resolves with the whole response, or rejects with the error of the request, the socket or the body. */\n readonly response: Promise<HttpResponse>;\n /** This method destroys the request and its socket. A request that waits for a socket leaves the queue of the agent. */\n abort(error: Error): void;\n}\n\n/**\n * An agent is the part of an `http.Agent` of Node that the wrapper uses, with `http.request` on it.\n * A fake for tests has each part too. Thus the wrapper does not load node:http until it connects.\n */\nexport interface HttpAgent {\n /** This method sends one request on the agent. */\n request(request: AgentRequest, events: AgentEvents): AgentExchange;\n /** This method gives each socket of the agent that is open: the busy ones and the free ones. */\n sockets(): readonly HttpSocket[];\n /** This method destroys each socket of the agent. A request on a socket then fails. */\n destroy(): void;\n}\n\n/**\n * These are the options that the wrapper gives `new http.Agent`. Each one comes from the declaration.\n * The wrapper sets each option that has an effect on a timer, thus no default of Node decides one.\n */\nexport interface HttpAgentOptions {\n readonly keepAlive: true;\n /**\n * This time is the free socket timer (`timeout`). The agent closes a free socket at the smaller of\n * this time and the hint of the server less `agentKeepAliveTimeoutBuffer`. 0 is no timer, and then the hint does not apply.\n */\n readonly timeout: number;\n /** This time is subtracted from the hint of the server (`Keep-Alive: timeout=…`). A hint at or below it gives no reuse. */\n readonly agentKeepAliveTimeoutBuffer: number;\n /** This time is the idle time of TCP keepalive. The agent always turns keepalive on, and Node truncates the time to whole seconds. */\n readonly keepAliveMsecs: number;\n /** This count is the most sockets for one origin. A request waits in the agent for a socket above it. */\n readonly maxSockets: number;\n /** This count is the most free sockets for one origin. */\n readonly maxFreeSockets: number;\n /** The agent gives the socket that was free last, as the global agent does. */\n readonly scheduling: \"lifo\";\n}\n\n/** An agent factory makes the agent of the client from its options. */\nexport type AgentFactory = Factory<HttpAgentOptions, HttpAgent>;\n\n/** These are the parts of `node:http` that the factory uses. The specifier is a value, thus the entry loads node:http only at the connect. */\ninterface NodeHttp {\n readonly Agent: new (options: HttpAgentOptions) => NodeAgent;\n request(options: object): NodeRequest;\n}\n\ninterface NodeAgent {\n readonly sockets: Readonly<Record<string, readonly HttpSocket[] | undefined>>;\n readonly freeSockets: Readonly<Record<string, readonly HttpSocket[] | undefined>>;\n destroy(): void;\n}\n\ninterface NodeSocket extends HttpSocket {\n readonly connecting: boolean;\n once(event: \"connect\", listener: () => void): unknown;\n}\n\ninterface NodeIncoming {\n readonly statusCode?: number | undefined;\n readonly headers: Readonly<Record<string, string | string[] | undefined>>;\n readonly complete: boolean;\n on(event: \"data\", listener: (chunk: Buffer) => void): unknown;\n on(event: \"end\" | \"close\", listener: () => void): unknown;\n on(event: \"error\", listener: (error: Error) => void): unknown;\n}\n\ninterface NodeRequest {\n readonly reusedSocket: boolean;\n on(event: \"socket\", listener: (socket: NodeSocket) => void): unknown;\n on(event: \"response\", listener: (response: NodeIncoming) => void): unknown;\n on(event: \"error\", listener: (error: Error) => void): unknown;\n end(body?: Uint8Array): void;\n destroy(error?: Error): void;\n}\n\n/**\n * This factory makes an `http.Agent` of Node. It loads node:http only when a client connects.\n * Each request goes through `http.request` with the agent. The response settles at the end of its body.\n * A body that ends before it is complete rejects the response.\n */\nexport const nodeAgent: AgentFactory = async (options) => {\n const specifier = \"node:http\";\n const http = (await import(specifier)) as NodeHttp;\n const agent = new http.Agent(options);\n const all = (lists: Readonly<Record<string, readonly HttpSocket[] | undefined>>): HttpSocket[] => Object.values(lists).flatMap((list) => list ?? []);\n return {\n request(request, events) {\n const req = http.request({ host: request.host, port: request.port, method: request.method, path: request.path, headers: request.headers, agent });\n const response = new Promise<HttpResponse>((resolve, reject) => {\n req.on(\"socket\", (socket) => {\n events.socket(socket, req.reusedSocket);\n if (socket.connecting) socket.once(\"connect\", () => events.connect());\n else if (!req.reusedSocket) events.connect();\n });\n req.on(\"response\", (incoming) => {\n const chunks: Buffer[] = [];\n incoming.on(\"data\", (chunk) => chunks.push(chunk));\n incoming.on(\"error\", reject);\n incoming.on(\"end\", () => resolve({ status: incoming.statusCode ?? 0, headers: incoming.headers, body: Buffer.concat(chunks) }));\n incoming.on(\"close\", () => {\n if (!incoming.complete) reject(new Error(\"the connection closed before the end of the body\"));\n });\n });\n req.on(\"error\", reject);\n });\n req.end(request.body);\n return { response, abort: (error) => req.destroy(error) };\n },\n sockets: () => [...all(agent.sockets), ...all(agent.freeSockets)],\n destroy: () => agent.destroy(),\n };\n};\n","import type { Detector, Meeting, Situation } from \"../client/meets.ts\";\nimport { WHAT, immediate, keepaliveAnswered, keepaliveReset, keepaliveSilent, kernelGiveUp } from \"../client/meets.ts\";\nimport type { HttpClientKnobs } from \"./client.ts\";\n\n/**\n * This function gives the manifestations that an HTTP client can meet, and the detectors of each,\n * from its knobs. The HTTP cells measured them on the agent of Node 24.21.0:\n * - The agent has no timer of a call, thus the timer of the wrapper finds each silent call in flight.\n * - The free socket timer closes an idle socket at `pool_idle_expiry` at most. Thus no request goes\n * later on a socket that went silent while it was free.\n * - A FIN on a busy socket fails the call with \"socket hang up\". A FIN on a free socket destroys it.\n */\nexport const httpClientMeetings = (name: string, knobs: HttpClientKnobs): Meeting[] => {\n const call: Detector = {\n by: \"request_timeout\",\n within: knobs.request_timeout,\n situation: \"in flight\",\n effect: `the timer of the wrapper ends the call as indeterminate, and ${name} destroys its socket`,\n };\n const idleExpiry = (situation: Situation): Detector => ({\n by: \"pool_idle_expiry\",\n within: knobs.pool_idle_expiry,\n situation,\n effect: \"the free socket timer of the agent closes the socket, and the next request opens a new socket\",\n });\n const giveUp = kernelGiveUp(knobs.retrans_giveup_time, \"the call fails as indeterminate\");\n const silent = (manifestation: \"blackhole\" | \"freeze-host\"): Meeting => ({\n manifestation,\n what: WHAT[manifestation],\n detectors: [call, giveUp, idleExpiry(\"idle\"), keepaliveSilent(knobs.keepalive_idle)],\n });\n return [\n {\n manifestation: \"rst\",\n what: WHAT.rst,\n detectors: [\n immediate(\"in flight\", \"the call fails with ECONNRESET as indeterminate, and the next request opens a new socket\"),\n idleExpiry(\"idle\"),\n keepaliveReset(knobs.keepalive_idle),\n ],\n },\n {\n manifestation: \"fin\",\n what: WHAT.fin,\n detectors: [\n immediate(\"in flight\", 'the call fails with \"socket hang up\" as indeterminate'),\n immediate(\"idle\", \"the agent destroys the free socket, and the next request opens a new socket\"),\n ],\n },\n silent(\"blackhole\"),\n silent(\"freeze-host\"),\n {\n manifestation: \"freeze-process\",\n what: WHAT[\"freeze-process\"],\n detectors: [call, idleExpiry(\"idle\"), keepaliveAnswered()],\n },\n {\n manifestation: \"app-stall\",\n what: \"the server holds its reply, for example in a slow handler, and its kernel stays up\",\n detectors: [call, idleExpiry(\"idle\"), keepaliveAnswered()],\n },\n ];\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport type { Cancel } from \"../clock.ts\";\nimport { realClock } from \"../clock.ts\";\nimport type { Attempt, CheckOptions, Client, ClientJoin, ClientKnobs, ClientOptions, ClientSpec, ClientStep, DialFn, SendOptions, Sent } from \"../client/core.ts\";\nimport {\n ageProblems,\n attachClient,\n clientBounds,\n clientProblems,\n clientRecorder,\n dialCheck,\n idleCloseProblem,\n inFlight,\n keepaliveOnProblems,\n localStateCheck,\n message,\n ms,\n notSent,\n operationTable,\n poolProblems,\n roundTripCheck,\n sendWithReissue,\n socketEnds,\n stepChecks,\n timeProblems,\n trackWithTimer,\n wholeProblems,\n withinBound,\n wrapperTimerProblems,\n zeroProblems,\n} from \"../client/core.ts\";\nimport type { Judge } from \"../client/dial.ts\";\nimport { authority, helloDial, statusLineJudge } from \"../client/dial.ts\";\nimport type { Dependency } from \"../description.ts\";\nimport type { DependencyHandle } from \"../lifecycle.ts\";\nimport type { Operation } from \"../operation.ts\";\nimport type { AgentExchange, AgentFactory, HttpAgent, HttpAgentOptions, HttpResponse, HttpSocket } from \"./connection.ts\";\nimport { nodeAgent } from \"./connection.ts\";\nimport { httpClientMeetings } from \"./meets.ts\";\n\n/**\n * These are the knobs of the client, each a time, by the names of the ordering law where the law\n * has them. The runtime has no defaults, thus each knob is necessary. The constant\n * `HTTP_CLIENT_STARTING_KNOBS` gives a declaration from the HTTP cells. The knobs that each client has come from the core.\n */\nexport interface HttpClientKnobs extends ClientKnobs {\n /** This time bounds the TCP connect of a new socket. The wrapper has this timer, because the agent has none. */\n readonly connect_bound: Quantity;\n /**\n * This time bounds each call: from its start until the end of the body of its response. It also\n * bounds the wait for a socket. The wrapper has this timer, because the agent has none.\n */\n readonly request_timeout: Quantity;\n /**\n * This time is the free socket timer of the agent (`timeout`). The agent closes a free socket at the\n * smaller of this time and the hint of the server less `hint_buffer`. It must be below `server_idle_close` (O01).\n */\n readonly pool_idle_expiry: Extent;\n /** This time is what the agent subtracts from the hint of the server (`agentKeepAliveTimeoutBuffer`). A hint at or below it gives no reuse. */\n readonly hint_buffer: Quantity;\n /**\n * This time is when the server closes an idle connection, after its response. A Node server closes\n * at `keepAliveTimeout` plus `keepAliveTimeoutBuffer`. \"infinite\" is a server that keeps an idle\n * connection with no bound. The client does not set it: `problems` compares it with `pool_idle_expiry` (O01).\n */\n readonly server_idle_close: Extent;\n /** This time is the idle time of TCP keepalive (`keepAliveMsecs`), in whole seconds. The agent always turns keepalive on. */\n readonly keepalive_idle: Extent;\n /** The agent does not replace a connection at an age, thus this knob must be \"infinite\". */\n readonly max_connection_age: Extent;\n /** This time bounds the close of the client: the wait for the calls in flight, then a destroy of each socket. */\n readonly close_bound: Quantity;\n}\n\n/** A check request is the request of the round-trip check, and of the first connection at the connect. */\nexport interface CheckRequest {\n readonly method: string;\n readonly path: string;\n}\n\n/**\n * This request asks about the server, not about a resource (RFC 9110 §9.3.7). Thus it changes no\n * state. A server that does not route it still answers with a status, and the status tells that it is alive.\n */\nexport const OPTIONS_STAR: CheckRequest = { method: \"OPTIONS\", path: \"*\" };\n\n/** An HTTP client spec is the declaration of one HTTP client: the origin that it connects to, its pool, and its knobs. */\nexport interface HttpClientSpec extends ClientSpec<HttpClientKnobs> {\n /** This count is the most sockets of the pool (`maxSockets`). It is also the most free sockets. */\n readonly pool: { readonly max: number };\n /** This request is the request of the round-trip check and of the first connection. Without it, the client uses `OPTIONS *`. */\n readonly check?: CheckRequest | undefined;\n /**\n * These operations add to the table of the client, or change it. The table names each method by\n * its idempotency in RFC 9110. A caller can name an operation of its own, for example a POST with an `Idempotency-Key`.\n */\n readonly operations?: Readonly<Record<string, Operation>> | undefined;\n}\n\n/** These options make a client. Without them, it uses the agent of Node, the real clock, and a dial with `OPTIONS *`. */\nexport type HttpClientOptions = ClientOptions<AgentFactory>;\n\n/** An HTTP call is one request. A string body goes as UTF-8, with its `content-length`. */\nexport interface HttpCall {\n readonly method: string;\n readonly path: string;\n readonly headers?: Readonly<Record<string, string>> | undefined;\n readonly body?: string | Uint8Array | undefined;\n /** This name is the operation of the call in the table. Without it, the operation is the method. */\n readonly operation?: string | undefined;\n}\n\n/** An HTTP client is a dependency of a service: its declaration, its handle, its checks and its requests. */\nexport interface HttpClient extends Client<ClientStep, ClientJoin> {\n /** This text is the state of the client, as `HTTP_CLIENT_MACHINE` names it. */\n readonly status: string;\n /**\n * This method sends a request, and gives its outcome in three values. A whole response of each\n * status is `ok`. The client sends again only an idempotent operation, one time at most, after an\n * outcome that is not certain. The second try stays in the bound of the request.\n */\n request(call: HttpCall, options?: SendOptions): Promise<Sent<HttpResponse>>;\n}\n\n/**\n * These operations are the methods of HTTP, by their idempotency in RFC 9110 §9.2.2. A second\n * request of GET, HEAD, OPTIONS, TRACE, PUT or DELETE has the same effect as one. POST and PATCH do not.\n */\nexport const HTTP_OPERATIONS: Readonly<Record<string, Operation>> = {\n GET: { idempotent: true },\n HEAD: { idempotent: true },\n OPTIONS: { idempotent: true },\n TRACE: { idempotent: true },\n PUT: { idempotent: true },\n DELETE: { idempotent: true },\n POST: { idempotent: false },\n PATCH: { idempotent: false },\n};\n\n/** This function gives the problems of a spec: the knobs that the agent cannot use. */\nexport const specProblems = (name: string, spec: HttpClientSpec): string[] => {\n const { knobs } = spec;\n const problems = [\n ...timeProblems(name, knobs),\n // the agent has no timer of a connect or of a call\n ...wrapperTimerProblems(name, knobs, { connect_bound: \"a connect\", request_timeout: \"a call\" }),\n // the agent reads a timeout of 0 as no free socket timer, and then the hint of the server does not apply\n ...zeroProblems(name, \"the agent\", knobs, { bounded: [\"pool_idle_expiry\"] }),\n ...keepaliveOnProblems(name, knobs, \"the agent\"),\n // Node truncates the keepalive delay to whole seconds\n ...wholeProblems(name, knobs, { millis: [\"connect_bound\", \"request_timeout\", \"pool_idle_expiry\", \"hint_buffer\"], seconds: [\"keepalive_idle\"] }),\n ...poolProblems(name, { max: spec.pool.max, min: 0 }),\n ...ageProblems(name, knobs, \"the agent does not replace a connection at an age\"),\n ];\n if (!Number.isFinite(ms(knobs.hint_buffer))) problems.push(`${name}'s hint_buffer is not a finite time: the agent subtracts it from the hint of the server`);\n return problems;\n};\n\n/** This function gives the options of `new http.Agent` from a spec. Each timer of the agent comes from the declaration. */\nexport const agentOptions = (spec: HttpClientSpec): HttpAgentOptions => {\n const { knobs } = spec;\n return {\n keepAlive: true,\n timeout: ms(knobs.pool_idle_expiry),\n agentKeepAliveTimeoutBuffer: ms(knobs.hint_buffer),\n keepAliveMsecs: ms(knobs.keepalive_idle),\n maxSockets: spec.pool.max,\n maxFreeSockets: spec.pool.max,\n scheduling: \"lifo\",\n };\n};\n\n/** This function gives the bytes of the dial: `OPTIONS *` with the Host header, and a close after the answer. */\nexport const optionsRequest = (host: string, port: number): string => `OPTIONS * HTTP/1.1\\r\\nHost: ${authority(host, port)}\\r\\nConnection: close\\r\\n\\r\\n`;\n\n/** This judge reads the status line of the answer. Any status passes, because it tells that an HTTP server answered. */\nexport const statusJudge: Judge = statusLineJudge(() => null);\n\n/** This dial function opens a TCP connection, sends `OPTIONS *`, and waits for the status line. It changes no state on the server. */\nexport const httpDial: DialFn = (host, port, signal) => helloDial(optionsRequest(host, port), statusJudge)(host, port, signal);\n\n/**\n * This function makes an HTTP client, a dependency of a service over an `http.Agent` of Node.\n * - It sets each timer of the agent from the declaration. The agent sends no request again by itself,\n * and the client sends again only an idempotent operation of its table.\n * - Its call is a request until the end of the body of its response. The wrapper has a timer for the\n * connect and one for the call, because the agent has none.\n * - It tells the outcome of a call in three values. A request that did not get a connected socket\n * did not leave the client. A failure after that is not certain.\n * - It destroys the socket of each call that is not `ok`, thus no later request gets that socket.\n * - It records each new socket of the pool (D1). HTTP gives no id of a connection.\n *\n * The core of the clients gives the outcomes, the second try, the calls in flight and the timer of a\n * call. It also gives the bounded close and the checks. This entry gives what the agent does in its own way.\n */\nexport const httpClient = (name: string, spec: HttpClientSpec, options: HttpClientOptions = {}): HttpClient => {\n const problems = specProblems(name, spec);\n if (problems.length > 0) throw new Error(problems.join(\"; \"));\n const { knobs } = spec;\n const clock = options.clock ?? realClock;\n const create = options.create ?? nodeAgent;\n const dial = options.dial ?? httpDial;\n const operations = operationTable(HTTP_OPERATIONS, spec.operations);\n const requestTimeout = ms(knobs.request_timeout);\n const connectBound = ms(knobs.connect_bound);\n const check = spec.check ?? OPTIONS_STAR;\n\n let agent: HttpAgent | null = null;\n let status = \"none\";\n const joins: ClientJoin[] = [];\n // the calls that left the client: the close waits for them, in its bound\n const flight = inFlight();\n // the calls that did not leave the client: the close ends each one as not sent\n const waiting = new Set<(error: string) => void>();\n\n const emit = clientRecorder(options.record, clock, name);\n // the state of the client is the outcome of the last connect of a socket of its pool\n const record = (on: string, to: string = status, detail?: string): void => {\n const from = status;\n status = to;\n emit(from, to, on, detail);\n };\n const closing = (): boolean => status === \"closing\" || status === \"end\";\n\n /**\n * This function makes one try of a call. The try waits for a socket of the agent: a free one, or a\n * new one in `connect_bound`. A failure before the socket is ready is certain: the request did not\n * leave the client. After that, the try waits for the whole response in the rest of `request_timeout`.\n */\n const attempt = async (call: HttpCall, signal: AbortSignal | undefined): Promise<Attempt<HttpResponse>> => {\n const a = agent;\n if (a === null) return notSent(`${name} has not connected`);\n if (closing()) return notSent(`${name} is ${status}: the request did not leave the client`);\n if (signal?.aborted) return notSent(\"the caller aborted the request before it left the client\");\n const started = clock.now();\n const body = typeof call.body === \"string\" ? Buffer.from(call.body, \"utf8\") : call.body;\n const headers = { ...call.headers, ...(body === undefined ? {} : { \"content-length\": String(body.byteLength) }) };\n\n let socket: HttpSocket | undefined;\n let reused = false;\n let left = false;\n // true when the wrapper ended the try itself: the error of the agent that follows is not a failed connect\n let ending = false;\n let connectTimer: Cancel = () => {};\n let decide: (value: true | Attempt<never>) => void = () => {};\n const leaving = new Promise<true | Attempt<never>>((resolve) => {\n let decided = false;\n decide = (value) => {\n if (decided) return;\n decided = true;\n resolve(value);\n };\n });\n // a result of the agent that arrives before the try records it\n let deliver: ((value: Attempt<HttpResponse>, cause?: Error) => void) | null = null;\n let early: { readonly value: Attempt<HttpResponse>; readonly cause: Error | undefined } | null = null;\n let settled = false;\n const settleWith = (value: Attempt<HttpResponse>, cause?: Error): void => {\n if (deliver === null) early ??= { value, cause };\n else deliver(value, cause);\n };\n\n let exchange: AgentExchange | null = null;\n const stop = (text: string): void => {\n if (left) return;\n ending = true;\n connectTimer();\n exchange?.abort(new Error(text));\n decide(notSent(text));\n };\n const ready = (): void => {\n left = true;\n connectTimer();\n decide(true);\n };\n exchange = a.request(\n { host: spec.host, port: spec.port, method: call.method, path: call.path, headers, body },\n {\n socket(given, isReused) {\n socket = given;\n reused = isReused;\n if (isReused) {\n ready();\n return;\n }\n connectTimer = clock.after(connectBound, () => {\n if (left) return;\n record(\"connect.failed\", closing() ? status : \"failing\", \"no connection in connect_bound\");\n stop(\"no connection in connect_bound: the request did not leave the client\");\n });\n },\n connect() {\n const generation = joins.length + 1;\n joins.push({ generation, at: clock.now(), ...socketEnds(socket) });\n record(\"join\", closing() ? status : \"ready\", `socket ${generation}`);\n ready();\n },\n },\n );\n // the agent can fail a request in its queue or at its connect: it did not leave the client\n exchange.response.then(\n (response) => settleWith({ outcome: \"ok\", value: response }),\n (error: unknown) => {\n const cause = error instanceof Error ? error : new Error(message(error));\n if (!left) {\n connectTimer();\n if (!ending) record(\"connect.failed\", closing() ? status : \"failing\", message(error));\n decide(notSent(message(error)));\n return;\n }\n // the abort of the wrapper after the call settled is not a failure of the socket\n if (settled) return;\n // a reused socket that fails before the response: the server closed it, as in the keep-alive race\n if (reused) record(\"reuse.failed\", status, message(error));\n settleWith({ outcome: \"indeterminate\", error: message(error) }, cause);\n },\n );\n\n const abandoned = (): void => stop(\"the caller abandoned the request before it left the client\");\n signal?.addEventListener(\"abort\", abandoned, { once: true });\n const queued = clock.after(requestTimeout, () => stop(\"no socket in request_timeout: the request did not leave the client\"));\n waiting.add(stop);\n const got = await leaving;\n signal?.removeEventListener(\"abort\", abandoned);\n queued();\n waiting.delete(stop);\n if (got !== true) return got;\n\n const sent = exchange;\n return trackWithTimer<HttpResponse>(\n flight,\n clock,\n Math.max(0, requestTimeout - (clock.now() - started)),\n signal,\n \"the caller abandoned the request after it left the client\",\n \"no whole response in request_timeout: the server can have the request\",\n (settle) => {\n deliver = settle;\n if (early !== null) settle(early.value, early.cause);\n },\n (value, cause) => {\n settled = true;\n // the socket of a call that is not ok can hold a part of a response: it goes, and no later request gets it\n if (value.outcome !== \"ok\") sent.abort(cause ?? new Error(value.error));\n },\n );\n };\n\n const request = (call: HttpCall, sendOptions: SendOptions = {}): Promise<Sent<HttpResponse>> => {\n const method = call.method.toUpperCase();\n const named = call.operation ?? method;\n return sendWithReissue({\n clock,\n bound: requestTimeout,\n operation: operations[named],\n options: sendOptions,\n attempt: (signal) => attempt({ ...call, method }, signal),\n onReissue: () => record(\"command.reissue\", status, named),\n });\n };\n\n const handle: DependencyHandle = {\n async connect(signal) {\n record(\"status.connecting\", \"connecting\");\n agent = await create(agentOptions(spec));\n if (signal.aborted) throw new Error(`${name}: the service stopped before the connect`);\n // the check request opens the first socket of the pool. A server that is down does not stop the\n // startup: the checks keep readiness tripped until a connect succeeds (readiness is earned)\n await request(check, { signal, reissue: false });\n },\n async close() {\n const a = agent;\n if (a === null || closing()) return;\n record(\"close.request\", \"closing\");\n for (const stop of [...waiting]) stop(\"the client closed before the request left\");\n // the agent has no close that waits: the client waits for its calls in flight, in its bound\n if (!(await withinBound(clock, ms(knobs.close_bound), flight.idle()))) {\n record(\"close.bound\");\n const open = a.sockets().length;\n flight.settleAll(\"the client closed with the request in flight\");\n a.destroy();\n record(\"close.destroy\", status, `open sockets: ${open}`);\n } else {\n // each free socket closes with a FIN\n a.destroy();\n }\n record(\"status.end\", \"end\");\n },\n };\n\n const declaration: Omit<Dependency, \"name\"> = {\n system: spec.system,\n knobs: {\n connect_bound: knobs.connect_bound,\n request_timeout: knobs.request_timeout,\n pool_idle_expiry: knobs.pool_idle_expiry,\n hint_buffer: knobs.hint_buffer,\n keepalive_idle: knobs.keepalive_idle,\n // the agent sends nothing on a free socket, thus its keepalive is the only refresh of an idle flow (O03)\n idle_refresh_interval: knobs.keepalive_idle,\n max_connection_age: knobs.max_connection_age,\n close_bound: knobs.close_bound,\n retrans_giveup_time: knobs.retrans_giveup_time,\n },\n operations,\n };\n\n const checks = (checkOptions: CheckOptions) =>\n stepChecks<ClientStep>(name, checkOptions, {\n // the outcome of the last connect of a socket, without a round trip\n \"local-state\": localStateCheck(() => status === \"ready\"),\n // the check request through the pool of the application. A status of 500 or more fails it\n \"round-trip\": roundTripCheck(async (sendOptions) => {\n const sent = await request(check, sendOptions);\n return sent.outcome === \"ok\" && sent.value.status >= 500\n ? { outcome: \"error\", error: `the server answered ${sent.value.status}`, reply: true, attempts: sent.attempts }\n : sent;\n }),\n // a new connection each time, with OPTIONS *\n dial: dialCheck(dial, spec.host, spec.port),\n });\n\n return {\n name,\n declaration,\n handle,\n meets: httpClientMeetings(name, knobs),\n get status() {\n return status;\n },\n joins,\n request,\n checks,\n attach: (svc, checkOptions) => attachClient(svc, name, declaration, handle, checks(checkOptions)),\n problems(description) {\n // O01: the pool must close an idle socket before the server does\n const idle = idleCloseProblem(name, knobs.pool_idle_expiry, knobs.server_idle_close);\n return [...clientProblems(name, description, clientBounds(knobs)), ...(idle === null ? [] : [idle])];\n },\n };\n};\n","import type { Machine } from \"@mark1russell7/algebra\";\nimport { clientKnob as knob, closeTimer, transition as t } from \"../client/machine.ts\";\n\n/** The states where the agent exists and serves. */\nconst SERVING = [\"connecting\", \"ready\", \"failing\"] as const;\nconst OPEN = [...SERVING, \"closing\"] as const;\n\n/**\n * The HTTP client of the service as a `mark1russell7.machine/1` document. It is the agent as the\n * wrapper sees it, not one socket: the document `node-http-agent` of `@mark1russell7/machines`\n * gives each socket of the agent. The state is the outcome of the last connect of a socket:\n * - `ready`: the last connect succeeded.\n * - `failing`: the last connect failed, or did not end in connect_bound. The agent does not connect\n * again by itself. The next request tries again.\n *\n * A free socket that the agent closes does not change the state. A request on a reused socket that\n * fails records `reuse.failed`, and the state stays. A test records the transitions of the client\n * and replays them against this document.\n */\nexport const HTTP_CLIENT_MACHINE: Machine = {\n format: \"mark1russell7.machine/1\",\n id: \"service.machine.http-client\",\n title: \"The HTTP client of the service, over an http.Agent of Node\",\n layer: \"driver\",\n version: \"0.1.0\",\n sources: [{ ref: \"packages/service/src/http-client (the client implements this document)\", rung: \"implemented\" }],\n states: {\n none: { initial: true, note: \"the client exists, and its agent does not\" },\n connecting: { note: \"the check request of the startup opens the first socket\" },\n ready: { note: \"the last connect succeeded. A dead free socket stays hidden until the free socket timer or a request meets it\" },\n failing: { note: \"the last connect failed. A request that waits for a socket of a full pool does not change the state\" },\n closing: { note: \"the client waits for its calls in flight in close_bound, then destroys each socket of the agent\" },\n end: { final: true, note: \"the service closed the client\" },\n },\n events: {\n \"status.connecting\": { kind: \"local\" },\n join: { kind: \"rx\", note: \"a new socket of the agent connected: the request on it leaves the client (D1)\" },\n \"connect.failed\": { kind: \"local\", note: \"a connect failed: its error, or connect_bound\" },\n \"reuse.failed\": { kind: \"rx\", note: \"a request on a free socket that the agent reused failed: the server closed that socket\" },\n \"command.reissue\": { kind: \"local\" },\n \"close.request\": { kind: \"local\" },\n \"close.bound\": { kind: \"timer\", note: \"a call in flight did not end in close_bound\" },\n \"close.destroy\": { kind: \"local\", note: \"the bound destroys each socket of the agent, and each call in flight is indeterminate\" },\n \"status.end\": { kind: \"local\" },\n },\n knobs: {\n connect_bound: knob(\"connect.bound\"),\n request_timeout: knob(\"request.bound\"),\n pool_idle_expiry: knob(\"timeout\"),\n hint_buffer: knob(\"agentKeepAliveTimeoutBuffer\"),\n keepalive_idle: knob(\"keepAliveMsecs\"),\n close_bound: knob(\"close.bound\"),\n },\n timers: {\n connect_timeout: {\n duration: { knob: \"connect_bound\", unit: \"s\" },\n arms: [\"enter:connecting\"],\n cancels: [\"join\", \"connect.failed\"],\n fires: \"connect.failed\",\n note: \"the timer of the wrapper on the connect of a new socket. The agent has no connect timer\",\n },\n close: closeTimer(),\n },\n transitions: [\n t(\"connect\", \"none\", \"status.connecting\", \"connecting\"),\n t(\"join\", [...SERVING], \"join\", \"ready\"),\n t(\"connect-failed\", [...SERVING], \"connect.failed\", \"failing\", { timer: \"connect_timeout\" }),\n // a request that is in flight at the close can still connect, or fail\n t(\"join-closing\", \"closing\", \"join\", \"closing\"),\n t(\"connect-failed-closing\", \"closing\", \"connect.failed\", \"closing\"),\n ...OPEN.map((state) => t(`reuse-failed-${state}`, state, \"reuse.failed\", state)),\n ...OPEN.map((state) => t(`reissue-${state}`, state, \"command.reissue\", state)),\n t(\"close\", [...SERVING], \"close.request\", \"closing\"),\n t(\"close-bound\", \"closing\", \"close.bound\", \"closing\", { timer: \"close\" }),\n t(\"close-destroy\", \"closing\", \"close.destroy\", \"closing\"),\n t(\"end\", \"closing\", \"status.end\", \"end\"),\n ],\n};\n","import { STARTING_POLL } from \"../client/core.ts\";\nimport type { Poll } from \"../timing.ts\";\nimport type { HttpClientKnobs } from \"./client.ts\";\n\n/**\n * This poll is the poll of the checks that `HTTP_CLIENT_STARTING_KNOBS` fits. It is `STARTING_POLL` of\n * the core: each 5 s with a jitter of 20 %, and a timeout of 4 s (B6, O12). The poll does not depend on the driver.\n */\nexport const HTTP_CLIENT_STARTING_POLL: Poll = STARTING_POLL;\n\n/**\n * These knobs are a declaration to start from. They come from what the HTTP cells measured on the\n * agent of Node 24.21.0 against a Node server at its defaults. The runtime has no defaults: a service gives\n * these knobs, or its own. Each value holds the borders that the cells measured:\n * - `connect_bound` 5 s: the connect timeout of DP44. The agent has no connect timer.\n * - `request_timeout` 9.5 s: in the slowest poll of 10 s (O13). The agent has no timer of a call.\n * A frozen server held the call of undici until its headersTimeout, at 10.187 s.\n * - `pool_idle_expiry` 4 s: the free socket timer. The agent then closes a free socket at 4 s, the\n * hint of 5 s less `hint_buffer`. That is below the idle close of the server.\n * - `hint_buffer` 1 s: the default of the agent.\n * - `server_idle_close` 6 s: a Node server closes an idle connection at its `keepAliveTimeout` of 5 s\n * plus its buffer of 1 s. The cells measured 6.0003 s. O01 then has a margin of 2 s.\n * - `keepalive_idle` 1 s: the default of the agent. Node truncates it to whole seconds.\n * - `max_connection_age` \"infinite\": the agent does not replace a connection at an age.\n * - `close_bound` 2 s: a call in flight behind a black hole holds its socket with no bound.\n * - `retrans_giveup_time` 924.6 s: the give-up of Linux at `tcp_retries2 = 15`.\n */\nexport const HTTP_CLIENT_STARTING_KNOBS: HttpClientKnobs = {\n connect_bound: { value: 5, unit: \"s\" },\n request_timeout: { value: 9.5, unit: \"s\" },\n pool_idle_expiry: { value: 4, unit: \"s\" },\n hint_buffer: { value: 1, unit: \"s\" },\n server_idle_close: { value: 6, unit: \"s\" },\n keepalive_idle: { value: 1, unit: \"s\" },\n max_connection_age: { value: \"infinite\", unit: \"s\" },\n close_bound: { value: 2, unit: \"s\" },\n retrans_giveup_time: { value: 924.6, unit: \"s\" },\n};\n"],"mappings":"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"}
|
package/build/http.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as Clock } from "./clock-mVqhXD0b.js";
|
|
2
2
|
import { d as Refused } from "./policy-G4QJ1ZZq.js";
|
|
3
|
-
import { d as TransportImpl, m as RequestEnd, n as HealthServer, r as Lifecycle, v as RequestStream, w as ProbeKind, x as Health, y as RequestWork } from "./lifecycle-
|
|
3
|
+
import { d as TransportImpl, m as RequestEnd, n as HealthServer, r as Lifecycle, v as RequestStream, w as ProbeKind, x as Health, y as RequestWork } from "./lifecycle-CMedxaQd.js";
|
|
4
4
|
import { Extent, Quantity } from "@mark1russell7/quantity";
|
|
5
5
|
import { ListenOptions } from "node:net";
|
|
6
6
|
import { IncomingMessage, Server, ServerEventMap, ServerResponse } from "node:http";
|
package/build/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { a as backoffDelay, i as backoffBand, n as DelayBand, o as backoffProblems, r as Jitter, t as Backoff } from "./backoff-B-jGWVDm.js";
|
|
2
2
|
import { a as realClock, i as platformClock, n as Clock, r as PlatformClockOptions, t as Cancel } from "./clock-mVqhXD0b.js";
|
|
3
|
-
import { Heartbeat, HeartbeatOptions, detectionTime, heartbeat } from "./heartbeat.js";
|
|
4
3
|
import { S as retryAfterSeconds, _ as freeAt, a as LimitRule, b as limiterProblems, c as LimiterSpec, d as Refused, f as SlidingWindow, g as decide, h as TokenBucket, i as LimitPolicy, l as LimiterStoreSpec, m as TakeStep, n as Decision, o as Limiter, p as StoreFailure, r as KeyState, s as LimiterEviction, t as Allowed, u as RefusalCause, v as idleSpan, x as longestRetryAfter, y as limitRule } from "./policy-G4QJ1ZZq.js";
|
|
5
|
-
import { $ as
|
|
6
|
-
import { a as WatchdogSpec, c as Profile, d as kubernetes, f as localDev, i as Watchdog, l as daemon, n as ServiceBuilder, o as watchdog, r as service, s as PodTiming, t as ControllerBuilder, u as ephemeral } from "./builder-
|
|
4
|
+
import { $ as Step, A as EPISODES_KEPT, At as Idempotency, B as Check, C as ProbeAnswer, Ct as embeddedProcess, D as CheckMetrics, Dt as matchesMessage, E as CheckFn, Et as isMessageShape, F as Result, G as ExitCodes, H as CooperativeFloor, J as MessageTrigger, K as FORMAT, L as LifecycleEvent, M as HealthMetrics, Mt as mayReissue, N as redact, O as DURATION_FRACTIONS, Ot as nodeProcess, P as Outcome, Q as ServiceDescription, R as MachineName, S as HealthVerdict, St as ProcessTrigger, T as StartOptions, Tt as isExitSignal, U as Coupling, V as Controller, W as Dependency, X as ProgressMarks, Y as Phase, Z as RepeatSignal, _ as RequestOptions, _t as HostStream, a as PhaseContext, at as asDebounce, b as CheckVerdict, bt as ProcessHost, c as STAGES, ct as heartbeatDetection, d as TransportImpl, dt as RoutePattern, et as Transport, f as cooperativeAbortAt, ft as RouteRequest, g as RequestFn, gt as ExitTrigger, h as RequestEvent, ht as ExitSignal, i as LifecycleState, it as Timing, j as Episode, jt as Operation, k as DurationHistogram, kt as processPortOf, l as ShutdownOptions, lt as recoverSpan, m as RequestEnd, mt as EmbeddedProcess, n as HealthServer, nt as Poll, o as PhaseWork, ot as asWindow, p as stageRank, pt as EXIT_SIGNALS, q as Gate, r as Lifecycle, rt as Span, s as RunOptions, st as failSpan, t as DependencyHandle, tt as FailsBy, u as Stage, ut as OperationOf, v as RequestStream, vt as MessageShape, w as ProbeKind, wt as isExitCode, x as Health, xt as ProcessPort, y as RequestWork, yt as PROCESS_TRIGGERS, z as Recorder } from "./lifecycle-CMedxaQd.js";
|
|
5
|
+
import { a as WatchdogSpec, c as Profile, d as kubernetes, f as localDev, i as Watchdog, l as daemon, n as ServiceBuilder, o as watchdog, r as service, s as PodTiming, t as ControllerBuilder, u as ephemeral } from "./builder-CsSO4LzB.js";
|
|
6
|
+
import { Heartbeat, HeartbeatOptions, detectionTime, heartbeat } from "./heartbeat.js";
|
|
7
7
|
import { a as keyedLimiter, i as TakeRequest, n as LimiterStore, o as memoryStore, r as MemoryStore, t as KeyedLimiter } from "./store-D5HLgBnW.js";
|
|
8
8
|
import { a as limiterStateOf, i as limiterMachine, n as SLIDING_WINDOW_MACHINE, r as TOKEN_BUCKET_MACHINE, t as LimiterState } from "./limiter-B0849Vhp.js";
|
|
9
|
+
import { a as JobSource, c as QueueKnobs, d as queueDrain, f as redeliverOnce, i as JobSettle, l as RedeliveryPolicy, n as JobContext, o as QueueDrain, p as UnitRequest, r as JobFailure, s as QueueDrainOptions, t as JobAction, u as RequeueCause } from "./queue-CndiCVaY.js";
|
|
9
10
|
import { Extent, Quantity } from "@mark1russell7/quantity";
|
|
10
11
|
import { replay } from "@mark1russell7/algebra/replay";
|
|
11
12
|
import { Machine } from "@mark1russell7/algebra";
|
|
@@ -162,21 +163,6 @@ export declare const PROGRESS_RULE = "progress";
|
|
|
162
163
|
*/
|
|
163
164
|
export declare const lint: (svc: ServiceDescription, scenario: Scenario, rules: readonly Rule[]) => Finding[];
|
|
164
165
|
//#endregion
|
|
165
|
-
//#region src/units.d.ts
|
|
166
|
-
/**
|
|
167
|
-
* A unit request names the route of each unit of work in the request tracking of the service. Then
|
|
168
|
-
* the service admits, counts, drains and cuts each unit as it does each request (`life.request`).
|
|
169
|
-
* The controller declares the route, thus its idempotency tells if a cut unit can go again.
|
|
170
|
-
*/
|
|
171
|
-
interface UnitRequest<U> {
|
|
172
|
-
/** This name is the controller that declares the route. */
|
|
173
|
-
readonly controller: string;
|
|
174
|
-
/** This name is the route of each unit, or a function that gives it from the unit. A route can match a route pattern. */
|
|
175
|
-
readonly route: string | ((unit: U) => string);
|
|
176
|
-
/** This function gives the detail of a unit, for the function of a route pattern. */
|
|
177
|
-
readonly detail?: ((unit: U) => unknown) | undefined;
|
|
178
|
-
}
|
|
179
|
-
//#endregion
|
|
180
166
|
//#region src/worker.d.ts
|
|
181
167
|
/**
|
|
182
168
|
* These are the knobs of a worker loop, by the names of the ordering law where the law has them.
|
|
@@ -266,136 +252,6 @@ interface WorkerLoop extends TransportImpl {
|
|
|
266
252
|
*/
|
|
267
253
|
export declare const workerLoop: <S = undefined>(knobs: WorkerKnobs, options: WorkerLoopOptions<S>) => WorkerLoop;
|
|
268
254
|
//#endregion
|
|
269
|
-
//#region src/queue.d.ts
|
|
270
|
-
/**
|
|
271
|
-
* These are the knobs of a queue drain, by the names of the ordering law where the law has them.
|
|
272
|
-
* The drain has no defaults, thus each knob is necessary. A bound that nobody gives is "infinite".
|
|
273
|
-
*/
|
|
274
|
-
interface QueueKnobs {
|
|
275
|
-
/** This time is the wait after a take that gives no job, before the next take. A time without a bound waits for `wake()`. */
|
|
276
|
-
readonly queue_poll_interval: Extent;
|
|
277
|
-
/**
|
|
278
|
-
* This time is the most that one job can take. At it, the drain aborts the signal of the job, and
|
|
279
|
-
* a throw after it is a failure. The law reads it as `unit_processing_time@consumer` (O20).
|
|
280
|
-
*/
|
|
281
|
-
readonly unit_processing_time: Extent;
|
|
282
|
-
/** This time is the delay after the first failed take in sequence, or after a refusal of the service. The delay is two times longer after each one. */
|
|
283
|
-
readonly retry_base: Quantity;
|
|
284
|
-
/** This time is the most that one delay can be, before its jitter. */
|
|
285
|
-
readonly retry_cap: Quantity;
|
|
286
|
-
/** This time bounds the close: the end of the jobs after their abort, and then the close of the source. */
|
|
287
|
-
readonly close_bound: Quantity;
|
|
288
|
-
/** This count (the unit `count`) is the most jobs in flight at one time. A count without a bound takes each job that comes. */
|
|
289
|
-
readonly concurrency: Extent;
|
|
290
|
-
/** The other knobs go to the declaration, for example `lease_duration@broker`. The drain does not read them. */
|
|
291
|
-
readonly [knob: string]: Extent;
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* A job source gives the jobs of a queue drain:
|
|
295
|
-
* - A function takes the next job, or gives null when no job is ready. Its signal aborts when the intake stops.
|
|
296
|
-
* - An async iterable gives the jobs in sequence. The drain asks for the next job only when it has a free slot.
|
|
297
|
-
* At the stop of the intake, the drain ends the iterator through `return()`.
|
|
298
|
-
*/
|
|
299
|
-
type JobSource<J> = ((signal: AbortSignal) => J | null | undefined | PromiseLike<J | null | undefined>) | AsyncIterable<J>;
|
|
300
|
-
/**
|
|
301
|
-
* A job action is what the drain does with a job that failed:
|
|
302
|
-
* - `requeue`: the job goes back on the queue.
|
|
303
|
-
* - `reject`: the job leaves the queue without a success, for example to the dead-letter route of the queue.
|
|
304
|
-
* - `ack`: the job leaves the queue, as a poison message does.
|
|
305
|
-
*/
|
|
306
|
-
type JobAction = "requeue" | "reject" | "ack";
|
|
307
|
-
/** A job failure is a job whose handler failed, with the count of its deliveries, 1 at the first. */
|
|
308
|
-
interface JobFailure<J> {
|
|
309
|
-
readonly job: J;
|
|
310
|
-
readonly error: unknown;
|
|
311
|
-
readonly delivery: number;
|
|
312
|
-
}
|
|
313
|
-
/** A redelivery policy gives the action for a job that failed. The queue declares it, and it has no default (S6 A11). */
|
|
314
|
-
type RedeliveryPolicy<J> = (failure: JobFailure<J>) => JobAction;
|
|
315
|
-
/**
|
|
316
|
-
* A requeue cause tells why a job goes back on the queue:
|
|
317
|
-
* - `failed`: the redelivery policy sent it back after a failure.
|
|
318
|
-
* - `unstarted`: the drain took the job and did not start it, because its intake stopped or the service refused it.
|
|
319
|
-
* - `cut`: the shutdown cut the job, and its route declares that it can go again.
|
|
320
|
-
*/
|
|
321
|
-
type RequeueCause = "failed" | "unstarted" | "cut";
|
|
322
|
-
/** A job settle is how the drain tells the queue the end of each job. Each function can give a promise. */
|
|
323
|
-
interface JobSettle<J> {
|
|
324
|
-
/** This function removes the job from the queue: a success, or the action `ack`. */
|
|
325
|
-
ack(job: J): void | PromiseLike<void>;
|
|
326
|
-
/** This function puts the job back on the queue, or hands it back to the queue at the stop. */
|
|
327
|
-
requeue(job: J, cause: RequeueCause): void | PromiseLike<void>;
|
|
328
|
-
/** This function removes the job from the queue without a success: the action `reject`, or a cut job that cannot go again. */
|
|
329
|
-
reject(job: J, error: unknown): void | PromiseLike<void>;
|
|
330
|
-
/**
|
|
331
|
-
* This function gives the count of deliveries of a job from the queue, 1 at the first. The queue
|
|
332
|
-
* knows it, for example `x-delivery-count` of RabbitMQ or `ApproximateReceiveCount` of SQS. A
|
|
333
|
-
* queue in memory counts for itself. The drain has no default, because a broker gives a new object at each delivery.
|
|
334
|
-
*/
|
|
335
|
-
delivery(job: J): number;
|
|
336
|
-
}
|
|
337
|
-
/** A job context is what the drain gives the handler of each job. */
|
|
338
|
-
interface JobContext {
|
|
339
|
-
/** This signal aborts at the cooperative abort of the drain, at the bound of the job, and at the close. */
|
|
340
|
-
readonly signal: AbortSignal;
|
|
341
|
-
/** This signal aborts when the intake stops. */
|
|
342
|
-
readonly intake: AbortSignal;
|
|
343
|
-
/** This count is the delivery of the job, 1 at the first. */
|
|
344
|
-
readonly delivery: number;
|
|
345
|
-
/** This lifecycle is the lifecycle that the drain got at its start. */
|
|
346
|
-
readonly life: Lifecycle;
|
|
347
|
-
}
|
|
348
|
-
/** These options make a queue drain. Each function can give a promise. */
|
|
349
|
-
interface QueueDrainOptions<J> {
|
|
350
|
-
readonly source: JobSource<J>;
|
|
351
|
-
/** This function does one job. A throw is a failure, and the redelivery policy then gives its action. */
|
|
352
|
-
readonly handle: (job: J, context: JobContext) => unknown;
|
|
353
|
-
readonly settle: JobSettle<J>;
|
|
354
|
-
/** This policy gives the action for each failed job. `redeliverOnce` gives the policy of S6 A11. */
|
|
355
|
-
readonly redelivery: RedeliveryPolicy<J>;
|
|
356
|
-
/** This function closes the source at the close, after the jobs end: for example, it closes a channel. */
|
|
357
|
-
readonly close?: (() => void | PromiseLike<void>) | undefined;
|
|
358
|
-
/** This clock is the clock of the lifecycle. The polls, the backoffs and the bounds use it. The default is `realClock`. */
|
|
359
|
-
readonly clock?: Clock | undefined;
|
|
360
|
-
/** This function gives the random numbers of the jitter, from 0 up to 1. The default is `Math.random`. */
|
|
361
|
-
readonly random?: (() => number) | undefined;
|
|
362
|
-
/**
|
|
363
|
-
* This route puts each job through `life.request`: the service then admits it, counts it, drains
|
|
364
|
-
* it and can cut it. The idempotency of the route tells if a cut job goes back on the queue.
|
|
365
|
-
*/
|
|
366
|
-
readonly request?: UnitRequest<J> | undefined;
|
|
367
|
-
/**
|
|
368
|
-
* This function receives each failure: of a take, of a job, of a settle and of a policy. It also
|
|
369
|
-
* receives the end of a source while the intake is open. The job is undefined for a take and for that end.
|
|
370
|
-
*/
|
|
371
|
-
readonly onError?: ((error: unknown, job: J | undefined) => void) | undefined;
|
|
372
|
-
}
|
|
373
|
-
/** A queue drain is a transport of the lifecycle whose work is the jobs of a queue (S6 A11). */
|
|
374
|
-
interface QueueDrain extends TransportImpl {
|
|
375
|
-
/** These knobs go to the declaration of the consumer: `svc.consumer(name, drain.knobs, drain)`. */
|
|
376
|
-
readonly knobs: QueueKnobs;
|
|
377
|
-
/** This method ends the wait of a poll at once, for example when a job comes. It gives false after the intake stops. */
|
|
378
|
-
wake(): boolean;
|
|
379
|
-
/** This method gives the problems of the knobs in a declaration: the close bound and the bound of a job against the caps. */
|
|
380
|
-
problems(description: ServiceDescription): string[];
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* This function gives the redelivery policy of S6 A11. A poison message gets `ack`. Each other job
|
|
384
|
-
* goes back on the queue after its first failure, one time. After a later failure, `then` gives the
|
|
385
|
-
* action: the declared policy of the queue.
|
|
386
|
-
*/
|
|
387
|
-
export declare const redeliverOnce: <J>(then: JobAction | RedeliveryPolicy<J>, poison?: (error: unknown, job: J) => boolean) => RedeliveryPolicy<J>;
|
|
388
|
-
/**
|
|
389
|
-
* This function makes a queue drain as a transport (S6 A11). One taker takes the jobs from the
|
|
390
|
-
* source while a slot is free, and each job operates in its slot.
|
|
391
|
-
* - A success gets `ack`. A failure gets the action of the redelivery policy.
|
|
392
|
-
* - `stopIntake` takes no new job, and a job that the drain took and did not start goes back (`requeue`, `unstarted`).
|
|
393
|
-
* - `drain` waits for each job in flight until the deadline, and gives it the cooperative abort.
|
|
394
|
-
* - `close` aborts each job, waits for its end, and closes the source, all in `close_bound`. A job
|
|
395
|
-
* that does not end in the bound gets `reject`.
|
|
396
|
-
*/
|
|
397
|
-
export declare const queueDrain: <J>(knobs: QueueKnobs, options: QueueDrainOptions<J>) => QueueDrain;
|
|
398
|
-
//#endregion
|
|
399
255
|
//#region src/machines.d.ts
|
|
400
256
|
/**
|
|
401
257
|
* The startup gate opens one time. The lifecycle moves it from `closed` to `starting` while its
|
|
@@ -436,5 +292,5 @@ export declare const drainMachine: (description: ServiceDescription) => Machine;
|
|
|
436
292
|
/** These are the static machines of the runtime, by the name that their events carry. */
|
|
437
293
|
export declare const MACHINES: Readonly<Record<Exclude<MachineName, "drain" | "client">, Machine>>;
|
|
438
294
|
//#endregion
|
|
439
|
-
export { type Allowed, type Backoff, type Binding, type Bound, type Cancel, type Check, type CheckFn, type CheckVerdict, type Clock, type Controller, type ControllerBuilder, type CooperativeFloor, type Coupling, type Decision, type DelayBand, type Dependency, type DependencyHandle, EXIT_SIGNALS, type EmbeddedProcess, type Evaluated, type ExitCodes, type ExitSignal, type ExitTrigger, FORMAT, type FailsBy, type Finding, type Gate, type Health, type HealthServer, type HealthVerdict, type Heartbeat, type HeartbeatOptions, type HostStream, type Idempotency, type Inequality, type IterationContext, type Jitter, type JobAction, type JobContext, type JobFailure, type JobSettle, type JobSource, type KeyState, type KeyedLimiter, type Lifecycle, type LifecycleEvent, type LifecycleState, type LimitPolicy, type LimitRule, type Limiter, type LimiterEviction, type LimiterSpec, type LimiterState, type LimiterStore, type LimiterStoreSpec, type MachineName, type MemoryStore, type MessageShape, type MessageTrigger, type Operation, type OperationOf, type Outcome, PROCESS_TRIGGERS, type Phase, type PhaseContext, type PhaseWork, type PlatformClockOptions, type PodTiming, type Poll, type ProbeAnswer, type ProbeKind, type ProcessHost, type ProcessPort, type ProcessTrigger, type Profile, type ProgressMarks, type QueueDrain, type QueueDrainOptions, type QueueKnobs, type Recorder, type RedeliveryPolicy, type RefusalCause, type Refused, type RepeatSignal, type RequestEnd, type RequestEvent, type RequestFn, type RequestOptions, type RequestStream, type RequestWork, type RequeueCause, type Result, type RoutePattern, type RouteRequest, type Rule, type RunOptions, SLIDING_WINDOW_MACHINE, STAGES, type Scenario, type ServiceBuilder, type ServiceDescription, type ShutdownOptions, type Side, type SlidingWindow, type Span, type Stage, type StartOptions, type Step, type StoreFailure, TOKEN_BUCKET_MACHINE, type TakeRequest, type TakeStep, type Term, type Timing, type TokenBucket, type Transport, type TransportImpl, type UnitRequest, type Verdict, type Watchdog, type WatchdogSpec, type WorkerKnobs, type WorkerLoop, type WorkerLoopOptions, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, cooperativeAbortAt, daemon, decide, detectionTime, embeddedProcess, ephemeral, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, realClock, recoverSpan, replay, retryAfterSeconds, service, stageRank, watchdog };
|
|
295
|
+
export { type Allowed, type Backoff, type Binding, type Bound, type Cancel, type Check, type CheckFn, type CheckMetrics, type CheckVerdict, type Clock, type Controller, type ControllerBuilder, type CooperativeFloor, type Coupling, DURATION_FRACTIONS, type Decision, type DelayBand, type Dependency, type DependencyHandle, type DurationHistogram, EPISODES_KEPT, EXIT_SIGNALS, type EmbeddedProcess, type Episode, type Evaluated, type ExitCodes, type ExitSignal, type ExitTrigger, FORMAT, type FailsBy, type Finding, type Gate, type Health, type HealthMetrics, type HealthServer, type HealthVerdict, type Heartbeat, type HeartbeatOptions, type HostStream, type Idempotency, type Inequality, type IterationContext, type Jitter, type JobAction, type JobContext, type JobFailure, type JobSettle, type JobSource, type KeyState, type KeyedLimiter, type Lifecycle, type LifecycleEvent, type LifecycleState, type LimitPolicy, type LimitRule, type Limiter, type LimiterEviction, type LimiterSpec, type LimiterState, type LimiterStore, type LimiterStoreSpec, type MachineName, type MemoryStore, type MessageShape, type MessageTrigger, type Operation, type OperationOf, type Outcome, PROCESS_TRIGGERS, type Phase, type PhaseContext, type PhaseWork, type PlatformClockOptions, type PodTiming, type Poll, type ProbeAnswer, type ProbeKind, type ProcessHost, type ProcessPort, type ProcessTrigger, type Profile, type ProgressMarks, type QueueDrain, type QueueDrainOptions, type QueueKnobs, type Recorder, type RedeliveryPolicy, type RefusalCause, type Refused, type RepeatSignal, type RequestEnd, type RequestEvent, type RequestFn, type RequestOptions, type RequestStream, type RequestWork, type RequeueCause, type Result, type RoutePattern, type RouteRequest, type Rule, type RunOptions, SLIDING_WINDOW_MACHINE, STAGES, type Scenario, type ServiceBuilder, type ServiceDescription, type ShutdownOptions, type Side, type SlidingWindow, type Span, type Stage, type StartOptions, type Step, type StoreFailure, TOKEN_BUCKET_MACHINE, type TakeRequest, type TakeStep, type Term, type Timing, type TokenBucket, type Transport, type TransportImpl, type UnitRequest, type Verdict, type Watchdog, type WatchdogSpec, type WorkerKnobs, type WorkerLoop, type WorkerLoopOptions, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, cooperativeAbortAt, daemon, decide, detectionTime, embeddedProcess, ephemeral, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redact, redeliverOnce, replay, retryAfterSeconds, service, stageRank, watchdog };
|
|
440
296
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.js
CHANGED
|
@@ -136,6 +136,114 @@ const latch = (timing, clock, tripped, changed, trace) => {
|
|
|
136
136
|
};
|
|
137
137
|
};
|
|
138
138
|
//#endregion
|
|
139
|
+
//#region src/metrics.ts
|
|
140
|
+
/** These fractions of the timeout of a poll are the bounds of its histogram. */
|
|
141
|
+
const DURATION_FRACTIONS = [
|
|
142
|
+
.05,
|
|
143
|
+
.1,
|
|
144
|
+
.25,
|
|
145
|
+
.5,
|
|
146
|
+
.75,
|
|
147
|
+
1
|
|
148
|
+
];
|
|
149
|
+
/** The runtime keeps this many episodes of each check. */
|
|
150
|
+
const EPISODES_KEPT = 8;
|
|
151
|
+
/**
|
|
152
|
+
* This function removes the secrets that a cause can hold. These are the user and the password of a
|
|
153
|
+
* URL, and the value of a key, for example `password=`. A driver error can hold a connection string.
|
|
154
|
+
*/
|
|
155
|
+
const redact = (cause) => cause.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@]*@/gi, "$1***@").replace(/\b(password|passwd|pwd|secret|token|api[_-]?key|auth)(\s*[=:]\s*)("[^"]*"|'[^']*'|[^\s,;&]+)/gi, "$1$2***");
|
|
156
|
+
/** This function makes a meter. */
|
|
157
|
+
const meter = () => {
|
|
158
|
+
const checks = /* @__PURE__ */ new Map();
|
|
159
|
+
const transitions = /* @__PURE__ */ new Map();
|
|
160
|
+
const add = (check) => {
|
|
161
|
+
if (checks.has(check.name)) return;
|
|
162
|
+
const timeout = valueIn(check.poll.timeout, "ms");
|
|
163
|
+
checks.set(check.name, {
|
|
164
|
+
ok: 0,
|
|
165
|
+
error: 0,
|
|
166
|
+
indeterminate: 0,
|
|
167
|
+
thrown: 0,
|
|
168
|
+
forced: 0,
|
|
169
|
+
le: DURATION_FRACTIONS.map((fraction) => fraction * timeout),
|
|
170
|
+
counts: DURATION_FRACTIONS.map(() => 0),
|
|
171
|
+
timedOut: 0,
|
|
172
|
+
episodes: [],
|
|
173
|
+
open: null
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
const close = (counted, end) => {
|
|
177
|
+
if (counted.open === null) return null;
|
|
178
|
+
return {
|
|
179
|
+
start: counted.open.start,
|
|
180
|
+
end,
|
|
181
|
+
failures: counted.open.failures,
|
|
182
|
+
cause: counted.open.cause
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
const poll = (check, result) => {
|
|
186
|
+
add(check);
|
|
187
|
+
const counted = checks.get(check.name);
|
|
188
|
+
counted[result.outcome] += 1;
|
|
189
|
+
if (result.thrown) counted.thrown += 1;
|
|
190
|
+
if (result.forced) counted.forced += 1;
|
|
191
|
+
if (result.timedOut) counted.timedOut += 1;
|
|
192
|
+
else {
|
|
193
|
+
const at = counted.le.findIndex((bound) => result.took <= bound);
|
|
194
|
+
const bucket = at === -1 ? counted.counts.length - 1 : at;
|
|
195
|
+
counted.counts[bucket] = (counted.counts[bucket] ?? 0) + 1;
|
|
196
|
+
}
|
|
197
|
+
if (result.outcome === "error") {
|
|
198
|
+
if (counted.open === null) counted.open = {
|
|
199
|
+
start: result.at,
|
|
200
|
+
failures: 0,
|
|
201
|
+
cause: result.cause === null ? null : redact(result.cause)
|
|
202
|
+
};
|
|
203
|
+
counted.open.failures += 1;
|
|
204
|
+
} else if (result.outcome === "ok" && counted.open !== null) {
|
|
205
|
+
counted.episodes.push(close(counted, result.at));
|
|
206
|
+
counted.open = null;
|
|
207
|
+
if (counted.episodes.length > 8) counted.episodes.shift();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const event = (event) => {
|
|
211
|
+
const key = `${event.machine} ${event.instance} ${event.from} -> ${event.to}`;
|
|
212
|
+
transitions.set(key, (transitions.get(key) ?? 0) + 1);
|
|
213
|
+
};
|
|
214
|
+
const metrics = () => {
|
|
215
|
+
const out = {};
|
|
216
|
+
for (const [name, counted] of checks) {
|
|
217
|
+
const open = close(counted, null);
|
|
218
|
+
out[name] = {
|
|
219
|
+
outcomes: {
|
|
220
|
+
ok: counted.ok,
|
|
221
|
+
error: counted.error,
|
|
222
|
+
indeterminate: counted.indeterminate
|
|
223
|
+
},
|
|
224
|
+
thrown: counted.thrown,
|
|
225
|
+
forced: counted.forced,
|
|
226
|
+
durations: {
|
|
227
|
+
le: [...counted.le],
|
|
228
|
+
counts: [...counted.counts],
|
|
229
|
+
timedOut: counted.timedOut
|
|
230
|
+
},
|
|
231
|
+
episodes: open === null ? [...counted.episodes] : [...counted.episodes, open].slice(-8)
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
checks: out,
|
|
236
|
+
transitions: Object.fromEntries(transitions)
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
add,
|
|
241
|
+
poll,
|
|
242
|
+
event,
|
|
243
|
+
metrics
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
//#endregion
|
|
139
247
|
//#region src/poller.ts
|
|
140
248
|
const message$1 = (error) => error instanceof Error ? error.message : String(error);
|
|
141
249
|
/**
|
|
@@ -304,7 +412,12 @@ const healthRuntime = (description, runs, options = {}) => {
|
|
|
304
412
|
let draining = false;
|
|
305
413
|
let stopped = false;
|
|
306
414
|
let gate = "closed";
|
|
307
|
-
const
|
|
415
|
+
const counts = meter();
|
|
416
|
+
const record = (event) => {
|
|
417
|
+
counts.event(event);
|
|
418
|
+
options.record?.(event);
|
|
419
|
+
};
|
|
420
|
+
const tracer = (machine, instance) => (from, to, on) => record({
|
|
308
421
|
at: clock.now(),
|
|
309
422
|
machine,
|
|
310
423
|
instance,
|
|
@@ -316,7 +429,7 @@ const healthRuntime = (description, runs, options = {}) => {
|
|
|
316
429
|
const moveGate = (to, on, detail) => {
|
|
317
430
|
const from = gate;
|
|
318
431
|
gate = to;
|
|
319
|
-
|
|
432
|
+
record({
|
|
320
433
|
at: clock.now(),
|
|
321
434
|
machine: "gate",
|
|
322
435
|
instance: description.name,
|
|
@@ -350,6 +463,7 @@ const healthRuntime = (description, runs, options = {}) => {
|
|
|
350
463
|
random,
|
|
351
464
|
forced: () => forced.has(check.name) || check.dependency !== null && forced.has(check.dependency),
|
|
352
465
|
record: (result) => {
|
|
466
|
+
counts.poll(check, result);
|
|
353
467
|
made?.record(result);
|
|
354
468
|
if (result.outcome === "ok" && check.dependency !== null) {
|
|
355
469
|
if (lateDependencies.delete(check.dependency)) changed();
|
|
@@ -359,6 +473,7 @@ const healthRuntime = (description, runs, options = {}) => {
|
|
|
359
473
|
})
|
|
360
474
|
};
|
|
361
475
|
entries.set(check.name, entry);
|
|
476
|
+
counts.add(check);
|
|
362
477
|
if (late && !stopped) {
|
|
363
478
|
entry.poller.pollNow();
|
|
364
479
|
entry.poller.start(entry.poller.jittered());
|
|
@@ -425,6 +540,7 @@ const healthRuntime = (description, runs, options = {}) => {
|
|
|
425
540
|
});
|
|
426
541
|
return {
|
|
427
542
|
verdict,
|
|
543
|
+
metrics: counts.metrics,
|
|
428
544
|
probe(kind, controller) {
|
|
429
545
|
const now = verdict();
|
|
430
546
|
if (kind === "startup") return answer(kind, now.started);
|
|
@@ -3555,6 +3671,6 @@ const MACHINES = {
|
|
|
3555
3671
|
watchdog: WATCHDOG_MACHINE
|
|
3556
3672
|
};
|
|
3557
3673
|
//#endregion
|
|
3558
|
-
export { DECLARATION_RULE, EXIT_MACHINE, EXIT_SIGNALS, FORMAT, GATE_MACHINE, LATCH_MACHINE, LIMITER_RULES, MACHINES, POLLER_MACHINE, PROCESS_TRIGGERS, PROGRESS_RULE, SERVICE_RULES, SLIDING_WINDOW_MACHINE, STAGES, TOKEN_BUCKET_MACHINE, WATCHDOG_MACHINE, WINDOW_MACHINE, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, capKnob, cooperativeAbortAt, daemon, decide, detectionTime, drainMachine, embeddedProcess, ephemeral, evaluate, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, inequalities, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, lint, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redeliverOnce, replay, retryAfterSeconds, service, serviceBindings, stageRank, symbolsOf, verdictOf, watchdog, workerLoop };
|
|
3674
|
+
export { DECLARATION_RULE, DURATION_FRACTIONS, EPISODES_KEPT, EXIT_MACHINE, EXIT_SIGNALS, FORMAT, GATE_MACHINE, LATCH_MACHINE, LIMITER_RULES, MACHINES, POLLER_MACHINE, PROCESS_TRIGGERS, PROGRESS_RULE, SERVICE_RULES, SLIDING_WINDOW_MACHINE, STAGES, TOKEN_BUCKET_MACHINE, WATCHDOG_MACHINE, WINDOW_MACHINE, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, capKnob, cooperativeAbortAt, daemon, decide, detectionTime, drainMachine, embeddedProcess, ephemeral, evaluate, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, inequalities, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, lint, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redact, redeliverOnce, replay, retryAfterSeconds, service, serviceBindings, stageRank, symbolsOf, verdictOf, watchdog, workerLoop };
|
|
3559
3675
|
|
|
3560
3676
|
//# sourceMappingURL=index.js.map
|