@mark1russell7/service 0.5.11 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rabbitmq.js","names":["knob","t"],"sources":["../src/rabbitmq/connection.ts","../src/rabbitmq/meets.ts","../src/rabbitmq/client.ts","../src/rabbitmq/machine.ts","../src/rabbitmq/starting.ts"],"sourcesContent":["import type { ClientSocket, Factory } from \"../client/core.ts\";\n\n/** A message is a message of the broker, as amqplib gives it from `get`. The wrapper reads only these fields. */\nexport interface RabbitMessage {\n readonly content: Buffer;\n readonly fields: {\n readonly deliveryTag: number;\n readonly redelivered: boolean;\n readonly exchange: string;\n readonly routingKey: string;\n /** This count is the number of messages that stay on the queue after this one. */\n readonly messageCount?: number | undefined;\n };\n readonly properties: { readonly headers?: Readonly<Record<string, unknown>> | undefined; readonly [property: string]: unknown };\n}\n\n/** These are the properties of a publish that amqplib takes, for example `persistent`, `messageId` and `headers`. */\nexport type RabbitPublishOptions = Readonly<Record<string, unknown>>;\n\n/** These are the events of a channel that the wrapper listens to. */\nexport type RabbitChannelEvent = \"error\" | \"close\";\n\n/**\n * A channel is the part of a `ConfirmChannel` of amqplib 2 that the wrapper uses. A confirm channel\n * gives one result to the callback of each publish. The result is null for a `basic.ack`. It is an\n * error for a `basic.nack` and for the end of the channel.\n */\nexport interface RabbitChannel {\n /** This method writes one message. It throws when the channel is closed or closing. */\n publish(exchange: string, routingKey: string, content: Buffer, options: RabbitPublishOptions, callback: (error: Error | null) => void): boolean;\n /** This method takes one message (`basic.get`), or gives false when the queue is empty. It has no timer of its own. */\n get(queue: string, options?: { readonly noAck?: boolean | undefined }): Promise<RabbitMessage | false>;\n /** This method declares an exchange in passive mode: an RPC that changes nothing. The round-trip check uses it. */\n checkExchange(exchange: string): Promise<unknown>;\n /** This method acknowledges one message. It is one frame, with no reply. */\n ack(message: RabbitMessage): void;\n /** This method gives one message back to the broker, to the queue or to its dead-letter route. It is one frame, with no reply. */\n nack(message: RabbitMessage, allUpTo?: boolean, requeue?: boolean): void;\n close(): Promise<void>;\n on(event: RabbitChannelEvent, listener: (error?: Error) => void): unknown;\n off(event: RabbitChannelEvent, listener: (error?: Error) => void): unknown;\n}\n\n/** These are the events of a connection that the wrapper listens to. The event `blocked` gives the reason of the broker. */\nexport type RabbitConnectionEvent = \"error\" | \"close\" | \"blocked\" | \"unblocked\";\n\n/**\n * A connection is the part of a `ChannelModel` of amqplib 2 that the wrapper uses. A `ChannelModel`\n * has each part. A fake for tests has them too. Thus the package depends on no type of amqplib.\n */\nexport interface RabbitConnection {\n /** This method opens a channel with confirms (`confirm.select`). */\n createConfirmChannel(): Promise<RabbitChannel>;\n /** This method sends `connection.close`, and waits for `connection.close-ok`. Behind a black hole, it waits with no bound. */\n close(): Promise<void>;\n on(event: RabbitConnectionEvent, listener: (detail?: unknown) => void): unknown;\n off(event: RabbitConnectionEvent, listener: (detail?: unknown) => void): unknown;\n /** This field holds the socket. The join reads its addresses, and the bounded close destroys it. */\n readonly connection: { readonly stream?: ClientSocket | undefined };\n}\n\n/**\n * These are the socket options that the wrapper gives amqplib. Each one comes from the declaration.\n * At its defaults, amqplib sets no connect timeout, no keepalive and no TCP_NODELAY.\n */\nexport interface RabbitSocketOptions {\n /** This time bounds the TCP connect and the handshake. The timer stops when the connection opens. A time of 0 is no bound. */\n readonly timeout: number;\n /** The library turns Nagle off only when the caller asks. A confirm and a reply then do not wait for a delayed ACK. */\n readonly noDelay: true;\n readonly keepAlive: boolean;\n /** This time is the idle time of TCP keepalive, in milliseconds. Node truncates it to whole seconds. */\n readonly keepAliveDelay?: number | undefined;\n /** The broker shows `connection_name` in its management view and its log, thus the prober can join on it too. */\n readonly clientProperties: { readonly connection_name: string };\n}\n\n/** These are the arguments of `connect(url, socketOptions)`. The URL gives the host, the vhost, the user and the heartbeat. */\nexport interface RabbitConnectOptions {\n readonly url: string;\n readonly socketOptions: RabbitSocketOptions;\n}\n\n/** A connection factory makes one connection of amqplib from its options. A failure to open rejects. */\nexport type RabbitFactory = Factory<RabbitConnectOptions, RabbitConnection>;\n\n/**\n * This factory opens a connection of amqplib. It loads amqplib only when a client connects, thus a\n * service that does not use RabbitMQ does not need it. The specifier is a value, thus the package\n * needs no types of amqplib. The built-in recovery of amqplib stays off, because the wrapper connects again by itself.\n */\nexport const nodeAmqp: RabbitFactory = async ({ url, socketOptions }) => {\n const specifier = \"amqplib\";\n type Connect = (url: string, options: RabbitSocketOptions) => Promise<RabbitConnection>;\n const loaded = (await import(specifier)) as { readonly connect?: Connect; readonly default?: { readonly connect: Connect } };\n const connect = loaded.connect ?? loaded.default?.connect;\n if (connect === undefined) throw new Error(\"amqplib gives no connect function\");\n return connect(url, socketOptions);\n};\n","import type { Extent } from \"@mark1russell7/quantity\";\nimport { quantity } from \"@mark1russell7/quantity\";\nimport type { Detector, Meeting, Situation } from \"../client/meets.ts\";\nimport { NEVER, WHAT, immediate, keepaliveAnswered, keepaliveReset, keepaliveSilent, kernelGiveUp } from \"../client/meets.ts\";\nimport { finiteMs } from \"../client/core.ts\";\nimport type { RabbitKnobs } from \"./client.ts\";\n\n/**\n * This function gives the manifestations that a RabbitMQ client can meet, and the detectors of each,\n * from its knobs. The cells of R3 measured them on amqplib 2.2.0 against RabbitMQ 4.3.6:\n * - The heartbeat ends a silent connection 2 to 3 heartbeats after the last data. The cells measured\n * 180.009 s at 60 s, and 45.009 s at 15 s (O17). It finds a silent broker in flight and idle.\n * - A stopped broker resets each client at once, because each socket of RabbitMQ has linger 0.\n * - At `tcp_retries2 = 7`, the kernel gave up at 53.016 s.\n * - A broker with a resource alarm blocks each connection that publishes, and it still sends its heartbeats.\n */\nexport const rabbitMeetings = (name: string, knobs: RabbitKnobs): Meeting[] => {\n const beat = finiteMs(knobs.heartbeat_interval);\n const detection: Extent = beat === undefined ? NEVER : quantity((3 * beat) / 1000, \"s\");\n const heartbeat = (situation: Situation): Detector => ({\n by: \"heartbeat_detection_time\",\n within: detection,\n situation,\n effect: `the heartbeat of amqplib ends the connection, each call in flight fails as indeterminate, and ${name} connects again`,\n });\n const call: Detector = {\n by: \"request_timeout\",\n within: knobs.request_timeout,\n situation: \"in flight\",\n effect: \"the timer of the wrapper ends the call as indeterminate, and the connection stays\",\n };\n const giveUp = kernelGiveUp(knobs.retrans_giveup_time, \"each call in flight fails as indeterminate\");\n const silent = (manifestation: \"blackhole\" | \"freeze-host\"): Meeting => ({\n manifestation,\n what: WHAT[manifestation],\n detectors: [call, heartbeat(\"in flight\"), giveUp, heartbeat(\"idle\"), keepaliveSilent(knobs.keepalive_idle)],\n });\n return [\n {\n manifestation: \"rst\",\n what: WHAT.rst,\n detectors: [\n immediate(\"in flight\", \"the call fails with ECONNRESET as indeterminate, and the client connects again\"),\n {\n by: \"heartbeat_interval\",\n within: beat === undefined ? NEVER : knobs.heartbeat_interval,\n situation: \"idle\",\n effect: \"the next heartbeat frame of the client gets a reset, and the client connects again\",\n },\n keepaliveReset(knobs.keepalive_idle),\n ],\n },\n {\n manifestation: \"fin\",\n what: WHAT.fin,\n detectors: [\n immediate(\"in flight\", \"the broker closes its socket with linger 0, thus a reset arrives, and the call fails as indeterminate\"),\n immediate(\"idle\", \"the reset of the broker arrives, and the client connects again\"),\n ],\n },\n silent(\"blackhole\"),\n silent(\"freeze-host\"),\n {\n manifestation: \"freeze-process\",\n what: WHAT[\"freeze-process\"],\n detectors: [call, heartbeat(\"in flight\"), heartbeat(\"idle\"), keepaliveAnswered()],\n },\n {\n manifestation: \"app-stall\",\n what: \"the broker stops its confirms and its replies for a time, and still sends its heartbeats: a resource alarm, or a quorum queue with no leader\",\n detectors: [\n call,\n {\n by: \"connection.blocked\",\n within: quantity(0, \"s\"),\n situation: \"idle\",\n effect: \"a resource alarm blocks a connection that publishes at once. The client then refuses each call as not sent, until the unblock\",\n },\n { ...heartbeat(\"idle\"), within: NEVER, effect: \"the broker still sends its heartbeats\" },\n keepaliveAnswered(),\n ],\n },\n ];\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport { quantity } from \"@mark1russell7/quantity\";\nimport { realClock } from \"../clock.ts\";\nimport type { Attempt, CheckOptions, Client, ClientJoin, ClientKnobs, ClientOptions, ClientSpec, ClientStep, DialFn, RetryKnobs, SendOptions, Sent } from \"../client/core.ts\";\nimport {\n ageProblems,\n attachClient,\n clientBounds,\n clientProblems,\n clientRecorder,\n dialCheck,\n finiteMs,\n inFlight,\n localStateCheck,\n message,\n ms,\n notSent,\n operationTable,\n readyWait,\n retryBackoff,\n retryProblems,\n retrySchedule,\n roundTripCheck,\n sendWithReissue,\n socketEnds,\n stepChecks,\n timeProblems,\n trackWithTimer,\n wholeProblems,\n withinBound,\n wrapperTimerProblems,\n zeroProblems,\n} from \"../client/core.ts\";\nimport type { Judge } from \"../client/dial.ts\";\nimport { helloDial } from \"../client/dial.ts\";\nimport type { Dependency } from \"../description.ts\";\nimport type { DependencyHandle } from \"../lifecycle.ts\";\nimport type { JobSettle } from \"../queue.ts\";\nimport type { RabbitChannel, RabbitConnectOptions, RabbitConnection, RabbitFactory, RabbitMessage, RabbitPublishOptions } from \"./connection.ts\";\nimport { nodeAmqp } from \"./connection.ts\";\nimport { rabbitMeetings } 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. A bound that nobody gives is\n * \"infinite\", not zero. The constant `RABBIT_STARTING_KNOBS` gives a declaration from the cells of R3. The knobs\n * that each client has come from the core.\n */\nexport interface RabbitKnobs extends ClientKnobs, RetryKnobs {\n /**\n * This time bounds the TCP connect and the handshake (`socketOptions.timeout`). The client also\n * bounds the open of its channel with it. The timer of amqplib stops when the connection opens.\n */\n readonly connect_bound: Quantity;\n /** This time bounds each call: a publish until its confirm, and an RPC until its reply. The wrapper has this timer, because amqplib has none. */\n readonly request_timeout: Quantity;\n /**\n * This time is the heartbeat that the client asks for (`heartbeat` in the URL), in whole seconds.\n * The connection uses the smaller of this value and the value of the broker. The value \"infinite\" asks for no heartbeat.\n */\n readonly heartbeat_interval: Extent;\n /** This time is the first delay before a new try to connect. The delay doubles after each try. */\n readonly retry_base: Quantity;\n /** This time is the most that one delay can be, before its jitter. */\n readonly retry_cap: Quantity;\n /** This time is the idle time of TCP keepalive (`keepAliveDelay`), in whole seconds. The value \"infinite\" turns keepalive off, as amqplib does by default. */\n readonly keepalive_idle: Extent;\n /** The client does not replace its connection at an age, thus this knob must be \"infinite\". */\n readonly max_connection_age: Extent;\n /** This time bounds the close of the client at the shutdown: `connection.close`, then a destroy of the socket. */\n readonly close_bound: Quantity;\n}\n\n/** A seed is one more node of the cluster. The client connects to the next node after each failure, in sequence. */\nexport interface RabbitSeed {\n readonly host: string;\n readonly port: number;\n}\n\n/** A RabbitMQ client spec is the declaration of one RabbitMQ client: where it connects, and its knobs. */\nexport interface RabbitClientSpec extends ClientSpec<RabbitKnobs> {\n /** This name is the virtual host. The default of the broker is \"/\". */\n readonly vhost?: string | undefined;\n /** This name is the user. Without it, amqplib connects as the user \"guest\". */\n readonly username?: string | undefined;\n /** These nodes are more nodes of the cluster, after `host` and `port`. */\n readonly seeds?: readonly RabbitSeed[] | undefined;\n /**\n * These operations are the calls of the service, by name, with their idempotency. A message does\n * not tell if a consumer removes its duplicates. Thus the client sends again only a call that this table names.\n */\n readonly operations?: ClientSpec<RabbitKnobs>[\"operations\"];\n}\n\n/** These options make a client. Without them, it uses amqplib, the real clock, `Math.random`, and a dial with the protocol header. */\nexport interface RabbitClientOptions extends ClientOptions<RabbitFactory> {\n readonly random?: (() => number) | undefined;\n}\n\n/**\n * A join is one generation of the client: one connection and its channel, when the channel is open\n * (D1, D2). The prober joins its kernel records to the view of the broker through it.\n */\nexport interface RabbitJoin extends ClientJoin {\n /** This text is the node of the connection, as `host:port`. */\n readonly address: string;\n /** This text is the name of the connection in the broker, `local:port -> remote:port`, or null when the socket gives no addresses. */\n readonly connectionName: string | null;\n}\n\n/** A publish is one message to an exchange. A string content goes as UTF-8. */\nexport interface RabbitPublish {\n readonly exchange: string;\n readonly routingKey: string;\n readonly content: Buffer | string;\n /** These properties go to amqplib, for example `persistent`, `messageId` and `headers`. */\n readonly properties?: RabbitPublishOptions | undefined;\n}\n\n/** A delivery is one message that the client took from a queue. It stays on the channel of its generation until a settle. */\nexport interface RabbitDelivery {\n readonly message: RabbitMessage;\n /** This number is the generation of the channel that holds the message. */\n readonly generation: number;\n /** This count is the delivery of the message, 1 at the first, from `x-delivery-count` or `redelivered`. */\n readonly delivery: number;\n}\n\n/** These are the source and the settle of a queue drain on one queue: `queueDrain({ source, settle, ... })`. */\nexport interface RabbitJobs {\n /** This function takes one job, or gives null when the queue is empty. It is a `JobSource` of the drain. */\n readonly source: (signal: AbortSignal) => Promise<RabbitDelivery | null>;\n readonly settle: JobSettle<RabbitDelivery>;\n}\n\n/** A RabbitMQ client is a dependency of a service: its declaration, its handle, its checks and its calls. */\nexport interface RabbitClient extends Client<ClientStep, RabbitJoin> {\n /** This text is the state of the client, as `RABBIT_CLIENT_MACHINE` names it. */\n readonly status: string;\n /**\n * This method publishes one message, and waits for its confirm. An ack is `ok`. The client sends\n * again only an idempotent operation, one time at most, after an outcome that is not certain.\n */\n publish(operation: string, message: RabbitPublish, options?: SendOptions): Promise<Sent<void>>;\n /** This method takes one message from a queue (`basic.get`), or gives null when the queue is empty. */\n get(operation: string, queue: string, options?: SendOptions): Promise<Sent<RabbitDelivery | null>>;\n /** This method gives the source and the settle of a queue drain. The source takes each job with `get`. */\n jobs(operation: string, queue: string): RabbitJobs;\n}\n\n/** This function gives the problems of a spec: the knobs that the client cannot use. */\nexport const specProblems = (name: string, spec: RabbitClientSpec): string[] => {\n const { knobs } = spec;\n return [\n ...timeProblems(name, knobs),\n // amqplib reads a socket timeout of 0 as no bound, and a heartbeat of 0 as no heartbeat\n ...zeroProblems(name, \"amqplib\", knobs, { bounded: [\"connect_bound\"], open: [\"heartbeat_interval\", \"keepalive_idle\"] }),\n // the URL takes the heartbeat in whole seconds, and Node truncates the keepalive delay to whole seconds\n ...wholeProblems(name, knobs, { millis: [\"connect_bound\"], seconds: [\"heartbeat_interval\", \"keepalive_idle\"] }),\n ...retryProblems(name, knobs),\n // amqplib has no timer of a call\n ...wrapperTimerProblems(name, knobs, { request_timeout: \"a call\" }),\n ...ageProblems(name, knobs, \"the client does not replace its connection at an age\"),\n ];\n};\n\n/** This function gives the node of a try: `host`, then each seed, in sequence. */\nconst nodeOf = (spec: RabbitClientSpec, index: number): RabbitSeed => {\n const nodes = [{ host: spec.host, port: spec.port }, ...(spec.seeds ?? [])];\n return nodes[index % nodes.length]!;\n};\n\n/**\n * This function gives the arguments of `connect` for one node of a spec. Each timer of amqplib\n * comes from the declaration. The heartbeat goes in the URL, and \"infinite\" is 0, no heartbeat.\n */\nexport const rabbitOptions = (name: string, spec: RabbitClientSpec, index = 0): RabbitConnectOptions => {\n const { knobs } = spec;\n const node = nodeOf(spec, index);\n const user = spec.username === undefined ? \"\" : `${encodeURIComponent(spec.username)}:${encodeURIComponent(spec.password ?? \"\")}@`;\n const heartbeat = finiteMs(knobs.heartbeat_interval);\n const keepalive = finiteMs(knobs.keepalive_idle);\n return {\n url: `amqp://${user}${node.host}:${node.port}/${encodeURIComponent(spec.vhost ?? \"/\")}?heartbeat=${heartbeat === undefined ? 0 : heartbeat / 1000}`,\n socketOptions: {\n timeout: ms(knobs.connect_bound),\n noDelay: true,\n keepAlive: keepalive !== undefined,\n ...(keepalive === undefined ? {} : { keepAliveDelay: keepalive }),\n clientProperties: { connection_name: name },\n },\n };\n};\n\n/** The bytes of the protocol header of AMQP 0-9-1. A broker answers it with `connection.start`, before the authentication. */\nexport const AMQP_HEADER: Uint8Array = Uint8Array.of(0x41, 0x4d, 0x51, 0x50, 0, 0, 9, 1);\n\n/**\n * This judge reads the answer to the protocol header. A broker that takes the version sends a method\n * frame on channel 0: `connection.start`, class 10 and method 10. A broker that refuses the version\n * sends its own protocol header, and closes the socket.\n */\nexport const startJudge: Judge = (received) => {\n if (received.length < 4) return \"more\";\n if (received.toString(\"latin1\", 0, 4) === \"AMQP\") return new Error(`the broker refused the version: it answered ${received.toString(\"hex\")}`);\n if (received.length < 11) return \"more\";\n const type = received[0];\n const channel = received.readUInt16BE(1);\n const classId = received.readUInt16BE(7);\n const methodId = received.readUInt16BE(9);\n if (type !== 1 || channel !== 0) return new Error(`the answer to the header is a frame of type ${type} on channel ${channel}, not a method on channel 0`);\n return classId === 10 && methodId === 10 ? null : new Error(`the answer to the header is the method ${classId}.${methodId}, not connection.start (10.10)`);\n};\n\n/** This dial function opens a TCP connection, sends the protocol header, and waits for `connection.start`. It needs no password. */\nexport const amqpDial: DialFn = helloDial(AMQP_HEADER, startJudge);\n\n/**\n * This function gives the outcome of a failure of amqplib. The library names few classes of error.\n * Thus the wrapper reads the fields:\n * - A throw before the write (`IllegalOperationError`: the channel is closed or closing) is certain: the call did not leave the client.\n * - A reply code of the broker, from the close of the channel, is certain. The broker refused the call, or a call before it.\n * - A nack, and an end of the channel before the reply or the confirm, are not certain.\n */\nexport const failureOutcome = (error: unknown): Attempt<never> => {\n const text = message(error);\n const { name, code } = (typeof error === \"object\" && error !== null ? error : {}) as { name?: unknown; code?: unknown };\n if (name === \"IllegalOperationError\") return notSent(text);\n if (typeof code === \"number\") return { outcome: \"error\", error: `${code} ${text}`, reply: true };\n if (text === \"message nacked\")\n return { outcome: \"indeterminate\", error: \"the broker nacked the message: a queue of the route can hold it, or not\" };\n return { outcome: \"indeterminate\", error: text };\n};\n\n/** This function gives the count of deliveries of a message, 1 at the first. A quorum queue counts in `x-delivery-count`. */\nexport const deliveryCount = (message: RabbitMessage): number => {\n const count = message.properties.headers?.[\"x-delivery-count\"];\n if (typeof count === \"number\" && Number.isInteger(count) && count >= 0) return count + 1;\n return message.fields.redelivered ? 2 : 1;\n};\n\n/** The exchange of each broker: the round-trip check declares it in passive mode, which changes nothing. */\nconst CHECK_EXCHANGE = \"amq.direct\";\n\n/**\n * This function makes a RabbitMQ client, a dependency of a service over amqplib 2.\n * - It has one connection and one confirm channel, as one generation. The end of either one ends\n * the generation. The client then connects again by itself, to the next node, after a backoff.\n * - It sets each timer of amqplib from the declaration. The built-in recovery of amqplib stays off.\n * - Its call is a confirmed publish, or an RPC with a reply. Each call has a timer of the wrapper, because amqplib has none.\n * - It tells the outcome of a call in three values, and it settles each call in flight at the end of its generation (C8, C10).\n * - It records the node and the name of each connection at the open of its channel (D1).\n *\n * These parts come from the core of the clients: the outcomes, the second try, the wait for a ready client and the backoff.\n * The calls in flight, the bounded close and the checks also come from the core. This entry gives what amqplib does in its own way.\n */\nexport const rabbitClient = (name: string, spec: RabbitClientSpec, options: RabbitClientOptions = {}): RabbitClient => {\n const problems = specProblems(name, spec);\n if (problems.length > 0) throw new Error(problems.join(\"; \"));\n const { knobs } = spec;\n const clock = options.clock ?? realClock;\n const random = options.random ?? Math.random;\n const create = options.create ?? nodeAmqp;\n const dial = options.dial ?? amqpDial;\n const operations = operationTable({}, spec.operations);\n const requestTimeout = ms(knobs.request_timeout);\n const backoff = retryBackoff(knobs);\n\n let status = \"none\";\n let connection: RabbitConnection | null = null;\n let channel: RabbitChannel | null = null;\n // the generation of the channel that is open now, and the token of the open that is current\n let generation = 0;\n let token = 0;\n // the index of the node of the next try, and the tries since the last join\n let next = 0;\n const retries = retrySchedule(clock, backoff, random);\n const joins: RabbitJoin[] = [];\n const flight = inFlight();\n const readied = readyWait(clock, () => status === \"ready\");\n\n const emit = clientRecorder(options.record, clock, name);\n const record = (on: string, to: string = status, detail?: string): void => {\n const from = status;\n status = to;\n emit(from, to, on, detail);\n };\n const closing = (): boolean => status === \"closing\" || status === \"end\";\n\n /** This function closes a connection that the client does not use, in `close_bound`, then destroys its socket. */\n const retire = (c: RabbitConnection): void => {\n void withinBound(\n clock,\n ms(knobs.close_bound),\n Promise.resolve().then(() => c.close()),\n ).then((ended) => {\n if (!ended) c.connection.stream?.destroy?.();\n });\n };\n\n /** This function waits for the backoff of the next try, then opens a new generation on the next node. */\n const scheduleRetry = (): void =>\n retries.schedule(() => {\n if (closing()) return;\n record(\"retry.connect\", \"connecting\", `try ${retries.tries}, ${nodeOf(spec, next).host}:${nodeOf(spec, next).port}`);\n void open();\n });\n\n /** This function ends the generation that is open. The calls in flight learn it after the rejections of amqplib, which can hold a reply code. */\n const lose = (on: string, detail: string): void => {\n const c = connection;\n connection = null;\n channel = null;\n if (closing() || c === null) return;\n record(on, \"failing\", detail);\n clock.after(0, () => flight.settleAll(`the generation ended with the call in flight (${detail})`));\n // the end of the channel leaves its connection open: the client closes it\n if (on === \"channel.end\") retire(c);\n scheduleRetry();\n };\n\n /** This function listens to a connection from its creation. The error listener stays: in Node, an error with no listener throws. */\n const watch = (c: RabbitConnection): void => {\n c.on(\"error\", (error) => {\n if (c === connection) record(\"connection.error\", status, message(error));\n });\n c.on(\"close\", (error) => {\n if (c === connection) lose(\"connection.end\", error === undefined ? \"the connection closed\" : message(error));\n });\n c.on(\"blocked\", (reason) => {\n if (c === connection && status === \"ready\") record(\"connection.blocked\", \"blocked\", String(reason));\n });\n c.on(\"unblocked\", () => {\n if (c !== connection || status !== \"blocked\") return;\n record(\"connection.unblocked\", \"ready\");\n readied.wake();\n });\n };\n\n const join = (c: RabbitConnection, ch: RabbitChannel, address: string): void => {\n connection = c;\n channel = ch;\n retries.reset();\n const ends = socketEnds(c.connection.stream);\n generation = joins.length + 1;\n const connectionName =\n ends.localAddress === null ? null : `${ends.localAddress}:${ends.localPort ?? \"?\"} -> ${ends.remoteAddress ?? \"?\"}:${ends.remotePort ?? \"?\"}`;\n joins.push({ generation, at: clock.now(), address, connectionName, ...ends });\n ch.on(\"error\", (error) => {\n if (ch === channel) record(\"channel.error\", status, message(error));\n });\n // amqplib closes each channel of a connection that ends, then it emits the close of the connection in the same turn.\n // Thus the client waits for that turn, and an end of the connection is not recorded as an end of the channel\n ch.on(\"close\", () => {\n queueMicrotask(() => {\n if (ch === channel) lose(\"channel.end\", \"the channel closed\");\n });\n });\n record(\"join\", \"ready\", `generation ${generation}, ${address}`);\n readied.wake();\n };\n\n /**\n * This function opens one generation: a connection, then its confirm channel, in `connect_bound`.\n * A failure goes to `failing`, and the backoff starts. A connection that arrives after its bound\n * or after the close is closed at once. The promise resolves when this try ends.\n */\n const open = (): Promise<void> => {\n const mine = (token += 1);\n const index = next;\n next += 1;\n const node = nodeOf(spec, index);\n const address = `${node.host}:${node.port}`;\n let opened: RabbitConnection | null = null;\n let finish: () => void = () => {};\n const ended = new Promise<void>((resolve) => (finish = resolve));\n const stale = (): boolean => mine !== token || closing();\n const fail = (detail: string): void => {\n bound();\n finish();\n if (stale()) return;\n token += 1;\n if (opened !== null) retire(opened);\n record(\"connect.failed\", \"failing\", `${address}: ${detail}`);\n scheduleRetry();\n };\n const bound = clock.after(ms(knobs.connect_bound), () => fail(\"no open connection and channel in connect_bound\"));\n void (async () => {\n const c = await create(rabbitOptions(name, spec, index));\n opened = c;\n watch(c);\n if (stale()) {\n retire(c);\n return;\n }\n const ch = await c.createConfirmChannel();\n if (stale()) {\n retire(c);\n return;\n }\n bound();\n join(c, ch, address);\n finish();\n })().catch((error: unknown) => fail(message(error)));\n return ended;\n };\n\n /** This function gives the channel for a call, or the reason that the call does not leave the client. */\n const usable = (signal: AbortSignal | undefined): RabbitChannel | Attempt<never> => {\n if (status === \"none\") return notSent(`${name} has not connected`);\n if (status !== \"ready\" || channel === null) return notSent(`${name} is ${status}, not ready: the call did not leave the client`);\n if (signal?.aborted) return notSent(\"the caller aborted the call before it left the client\");\n return channel;\n };\n\n /** This function makes one try of a publish: the write, then the confirm, in `request_timeout`. */\n const publishAttempt = (sent: RabbitPublish, signal: AbortSignal | undefined): Promise<Attempt<void>> => {\n const ch = usable(signal);\n if (\"outcome\" in ch) return Promise.resolve(ch);\n const content = typeof sent.content === \"string\" ? Buffer.from(sent.content, \"utf8\") : sent.content;\n return trackWithTimer<void>(\n flight,\n clock,\n requestTimeout,\n signal,\n \"the caller abandoned the publish after it was written\",\n \"no confirm in request_timeout: the broker can have the message\",\n (settle) => {\n try {\n ch.publish(sent.exchange, sent.routingKey, content, sent.properties ?? {}, (error) =>\n settle(error === null ? { outcome: \"ok\", value: undefined } : failureOutcome(error)),\n );\n } catch (error) {\n settle(failureOutcome(error));\n }\n },\n );\n };\n\n /**\n * This function makes one try of an RPC on the channel, in `request_timeout`. A reply that comes\n * after the call settled goes to `late`, thus a message that nobody receives goes back to its queue.\n */\n const rpcAttempt = <T, R>(\n signal: AbortSignal | undefined,\n what: string,\n run: (ch: RabbitChannel) => Promise<R>,\n value: (reply: R) => T,\n late?: (reply: R, ch: RabbitChannel) => void,\n ): Promise<Attempt<T>> => {\n const ch = usable(signal);\n if (\"outcome\" in ch) return Promise.resolve(ch);\n let finished = false;\n return trackWithTimer<T>(\n flight,\n clock,\n requestTimeout,\n signal,\n `the caller abandoned ${what} after it was sent`,\n `no reply to ${what} in request_timeout`,\n (settle) => {\n Promise.resolve()\n .then(() => run(ch))\n .then(\n (reply) => {\n if (finished) late?.(reply, ch);\n else settle({ outcome: \"ok\", value: value(reply) });\n },\n (error: unknown) => settle(failureOutcome(error)),\n );\n },\n () => {\n finished = true;\n },\n );\n };\n\n const publish = (operation: string, sent: RabbitPublish, sendOptions: SendOptions = {}): Promise<Sent<void>> =>\n sendWithReissue({\n clock,\n bound: requestTimeout,\n operation: operations[operation],\n options: sendOptions,\n attempt: (signal) => publishAttempt(sent, signal),\n // the client connects again by itself: the second try waits for a ready client, in the time that is left\n beforeAgain: (left, signal) => readied.wait(left, signal),\n onReissue: () => record(\"command.reissue\", status, operation),\n });\n\n const get = (operation: string, queue: string, sendOptions: SendOptions = {}): Promise<Sent<RabbitDelivery | null>> => {\n const at = (): number => generation;\n return sendWithReissue({\n clock,\n bound: requestTimeout,\n operation: operations[operation],\n options: sendOptions,\n attempt: (signal) => {\n const held = at();\n return rpcAttempt<RabbitDelivery | null, RabbitMessage | false>(\n signal,\n \"basic.get\",\n (ch) => ch.get(queue, { noAck: false }),\n (reply) => (reply === false ? null : { message: reply, generation: held, delivery: deliveryCount(reply) }),\n // a message after the timeout or the abort: nobody holds it, thus it goes back to its queue at once\n (reply, ch) => {\n if (reply === false) return;\n try {\n ch.nack(reply, false, true);\n } catch {\n // the channel ended, and the broker puts the message back by itself\n }\n },\n );\n },\n beforeAgain: (left, signal) => readied.wait(left, signal),\n onReissue: () => record(\"command.reissue\", status, operation),\n });\n };\n\n /** This function gives the channel that holds a delivery. A delivery of an ended generation is back on its queue already (at least once). */\n const holder = (job: RabbitDelivery): RabbitChannel => {\n const ch = channel;\n if (ch === null || job.generation !== generation)\n throw new Error(`${name}: the channel of generation ${job.generation} ended, thus the broker put the message back on its queue`);\n return ch;\n };\n\n const jobs = (operation: string, queue: string): RabbitJobs => ({\n source: async (signal) => {\n const taken = await get(operation, queue, { signal });\n if (taken.outcome === \"ok\") return taken.value;\n throw new Error(`${name} could not take a message from ${queue}: ${taken.error}`);\n },\n settle: {\n ack: (job) => holder(job).ack(job.message),\n requeue: (job) => holder(job).nack(job.message, false, true),\n reject: (job) => holder(job).nack(job.message, false, false),\n delivery: (job) => job.delivery,\n },\n });\n\n const handle: DependencyHandle = {\n async connect(signal) {\n if (signal.aborted) throw new Error(`${name}: the service stopped before the connect`);\n record(\"status.connecting\", \"connecting\");\n // a broker that is down does not stop the startup: the client connects again in the background,\n // and its checks keep readiness tripped until it is ready (readiness is earned)\n await open();\n },\n async close() {\n if (status === \"none\" || closing()) return;\n record(\"close.request\", \"closing\");\n retries.cancel();\n // an open in flight is stale now: its connection is closed when it arrives\n token += 1;\n const c = connection;\n if (c !== null) {\n // behind a black hole, connection.close waits for close-ok with no bound: the bound destroys the socket\n if (!(await withinBound(clock, ms(knobs.close_bound), Promise.resolve().then(() => c.close())))) {\n record(\"close.bound\", \"closing\");\n record(\"close.destroy\", \"closing\");\n c.connection.stream?.destroy?.();\n }\n }\n connection = null;\n channel = null;\n flight.settleAll(\"the client closed with the call in flight\");\n record(\"status.end\", \"end\");\n },\n };\n\n const heartbeat = finiteMs(knobs.heartbeat_interval);\n const declaration: Omit<Dependency, \"name\"> = {\n system: spec.system,\n knobs: {\n connect_bound: knobs.connect_bound,\n request_timeout: knobs.request_timeout,\n heartbeat_interval: knobs.heartbeat_interval,\n // each side checks for data at each heartbeat, and ends the connection after two checks with\n // none: 2 to 3 heartbeats after the last data. The cells measured 45.009 s at 15 s\n heartbeat_detection_time: heartbeat === undefined ? knobs.heartbeat_interval : quantity((3 * heartbeat) / 1000, \"s\"),\n retry_base: knobs.retry_base,\n retry_cap: knobs.retry_cap,\n keepalive_idle: knobs.keepalive_idle,\n // each side sends a heartbeat frame each half heartbeat when it sent nothing: that refreshes an idle flow (O03)\n idle_refresh_interval: heartbeat === undefined ? knobs.keepalive_idle : quantity(heartbeat / 2000, \"s\"),\n max_connection_age: knobs.max_connection_age,\n close_bound: knobs.close_bound,\n retrans_giveup_time: knobs.retrans_giveup_time,\n },\n operations,\n };\n\n const checks = (checkOptions: CheckOptions) =>\n stepChecks<ClientStep>(name, checkOptions, {\n // the state of the client, without a round trip\n \"local-state\": localStateCheck(() => status === \"ready\"),\n // a passive declare of an exchange of each broker, on the channel of the application\n \"round-trip\": roundTripCheck((sendOptions) =>\n sendWithReissue({\n clock,\n bound: requestTimeout,\n operation: undefined,\n options: sendOptions,\n attempt: (signal) => rpcAttempt(signal, \"a passive exchange.declare\", (ch) => ch.checkExchange(CHECK_EXCHANGE), () => undefined),\n onReissue: () => {},\n }),\n ),\n // a new connection each time, with the protocol header and no login\n dial: dialCheck(dial, spec.host, spec.port),\n });\n\n return {\n name,\n declaration,\n handle,\n meets: rabbitMeetings(name, knobs),\n get status() {\n return status;\n },\n joins,\n publish,\n get,\n jobs,\n checks,\n attach: (svc, checkOptions) => attachClient(svc, name, declaration, handle, checks(checkOptions)),\n problems: (description) => clientProblems(name, description, clientBounds(knobs)),\n };\n};\n","import type { Machine } from \"@mark1russell7/algebra\";\nimport { clientKnob as knob, closeTimer, transition as t } from \"../client/machine.ts\";\n\n/** The states where a generation is open, and the states where the client can close. */\nconst OPEN = [\"ready\", \"blocked\"] as const;\nconst SERVING = [\"connecting\", ...OPEN, \"failing\"] as const;\nconst LIVE = [...SERVING, \"closing\"] as const;\n\n/**\n * The RabbitMQ client of the service as a `mark1russell7.machine/1` document. It is one generation\n * at a time: one connection of amqplib and its confirm channel. The machines `amqplib` and\n * `rabbitmq-connection` of `@mark1russell7/machines` give the connection and its heartbeat. The states:\n * - `ready`: the channel is open, and the client sends each call.\n * - `blocked`: a resource alarm of the broker blocked the connection. Each call is not sent until the unblock.\n * - `failing`: no generation is open. The client tries the next node after its backoff.\n *\n * The end of the channel or of the connection ends the generation. A test records the transitions\n * of the client and replays them against this document.\n */\nexport const RABBIT_CLIENT_MACHINE: Machine = {\n format: \"mark1russell7.machine/1\",\n id: \"service.machine.rabbitmq-client\",\n title: \"The RabbitMQ client of the service, over amqplib 2\",\n layer: \"driver\",\n version: \"0.1.0\",\n sources: [{ ref: \"packages/service/src/rabbitmq (the client implements this document)\", rung: \"implemented\" }],\n states: {\n none: { initial: true, note: \"the client exists, and its first connection does not\" },\n connecting: { note: \"a connection, its handshake, and the open of its confirm channel, in connect_bound\" },\n ready: { note: \"the channel is open. A black hole stays hidden until the heartbeat or a timer of a call ends it\" },\n blocked: { note: \"a resource alarm blocked the connection: the broker does not read its socket\" },\n failing: { note: \"no generation is open. The client tries the next node after its backoff\" },\n closing: { note: \"connection.close, in close_bound, then a destroy of the socket\" },\n end: { final: true, note: \"the service closed the client\" },\n },\n events: {\n \"status.connecting\": { kind: \"local\" },\n join: { kind: \"rx\", note: \"the confirm channel is open: a new generation (D1)\" },\n \"connect.failed\": { kind: \"local\", note: \"the connection or its channel failed, or did not open in connect_bound\" },\n \"retry.connect\": { kind: \"timer\", note: \"the backoff ended: a try on the next node\" },\n \"connection.error\": { kind: \"rx\", note: \"an error event of the connection. Its close follows\" },\n \"connection.end\": { kind: \"rx\", note: \"the connection closed: a reset, a FIN, the heartbeat, or a close of the broker\" },\n \"channel.error\": { kind: \"rx\", note: \"the broker closed the channel with a reply code\" },\n \"channel.end\": { kind: \"rx\", note: \"the channel closed on a connection that stays: the client closes that connection too\" },\n \"connection.blocked\": { kind: \"rx\", note: \"connection.blocked of the broker: a resource alarm\" },\n \"connection.unblocked\": { kind: \"rx\" },\n \"command.reissue\": { kind: \"local\" },\n \"close.request\": { kind: \"local\" },\n \"close.bound\": { kind: \"timer\", note: \"connection.close got no close-ok in close_bound\" },\n \"close.destroy\": { kind: \"local\", note: \"the bound destroys a socket that a silent broker does not close\" },\n \"status.end\": { kind: \"local\" },\n },\n knobs: {\n connect_bound: knob(\"socketOptions.timeout\"),\n heartbeat_interval: knob(\"heartbeat\"),\n retry_base: knob(\"retry.base\"),\n retry_cap: knob(\"retry.cap\"),\n close_bound: knob(\"close.bound\"),\n },\n timers: {\n connect_timeout: {\n duration: { knob: \"connect_bound\", unit: \"s\" },\n arms: [\"enter:connecting\"],\n cancels: [\"join\", \"connect.failed\"],\n fires: \"connect.failed\",\n note: \"the bound of the wrapper on the connection and its channel. amqplib bounds the connect and the handshake with the same time\",\n },\n heartbeat: {\n duration: { expr: \"3 * heartbeat_interval\", unit: \"s\" },\n arms: [\"join\"],\n cancels: [\"connection.end\", \"close.request\"],\n fires: \"connection.end\",\n note: \"the heartbeat of amqplib ends a silent connection 2 to 3 heartbeats after the last data. Each frame that arrives starts it again\",\n },\n retry: {\n duration: { expr: \"min(retry_cap, retry_base * pow(2, fired)) + retry_base\", unit: \"s\" },\n arms: [\"connect.failed\", \"connection.end\", \"channel.end\"],\n cancels: [\"close.request\"],\n fires: \"retry.connect\",\n backoff: { resets: [\"join\"] },\n note: \"the most delay: the backoff doubles from retry_base to retry_cap, with a jitter up to retry_base\",\n },\n close: closeTimer(),\n },\n transitions: [\n t(\"connect\", \"none\", \"status.connecting\", \"connecting\"),\n t(\"join\", \"connecting\", \"join\", \"ready\"),\n t(\"connect-failed\", \"connecting\", \"connect.failed\", \"failing\", { timer: \"connect_timeout\" }),\n t(\"retry\", \"failing\", \"retry.connect\", \"connecting\", { timer: \"retry\" }),\n t(\"connection-end\", [...OPEN], \"connection.end\", \"failing\", { timer: \"heartbeat\" }),\n t(\"channel-end\", [...OPEN], \"channel.end\", \"failing\"),\n t(\"blocked\", \"ready\", \"connection.blocked\", \"blocked\"),\n t(\"unblocked\", \"blocked\", \"connection.unblocked\", \"ready\"),\n ...OPEN.map((state) => t(`connection-error-${state}`, state, \"connection.error\", state)),\n ...OPEN.map((state) => t(`channel-error-${state}`, state, \"channel.error\", state)),\n ...LIVE.map((state) => t(`reissue-${state}`, state, \"command.reissue\", state)),\n t(\"close\", [...SERVING], \"close.request\", \"closing\"),\n t(\"close-bound\", \"closing\", \"close.bound\", \"closing\", { timer: \"close\" }),\n t(\"close-destroy\", \"closing\", \"close.destroy\", \"closing\"),\n t(\"end\", \"closing\", \"status.end\", \"end\"),\n ],\n};\n","import { STARTING_POLL } from \"../client/core.ts\";\nimport type { Poll } from \"../timing.ts\";\nimport type { RabbitKnobs } from \"./client.ts\";\n\n/**\n * This poll is the poll of the checks that `RABBIT_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 RABBIT_STARTING_POLL: Poll = STARTING_POLL;\n\n/**\n * These knobs are a declaration to start from, from what the cells of R3 measured on amqplib 2.2.0\n * against RabbitMQ 4.3.6. The runtime has no defaults: a service gives these knobs, or its own.\n * Each value holds the borders that the cells measured:\n * - `connect_bound` 5 s: the connect timeout of DP44. The timer stops when the connection opens.\n * Thus it does not change a fault with a call in flight. At its defaults, amqplib has no connect timeout.\n * - `request_timeout` 9.5 s: in the slowest poll of 10 s (O13). After a loss of the leader of a quorum\n * queue, the first confirm came at 6.61 s, 6.68 s and 8.05 s (T11). A later confirm is indeterminate, not lost.\n * - `heartbeat_interval` 15 s: the heartbeat ended a silent connection 45.009 s after the last data,\n * before the kernel gave up at 53.016 s (O17). The frames each 7.5 s keep a flow through a router\n * that forgets it at 30 s (O03). At the 60 s of the broker, the heartbeat came at 180.009 s.\n * - `retry_base` 1 s: the cells connected to the next node 1 s after the end of a connection.\n * - `retry_cap` 8 s: no cell measured it. With its jitter of 1 s, the most delay is 9 s, in the slowest poll of 10 s.\n * - `keepalive_idle` \"infinite\": amqplib sets no keepalive, and the heartbeat refreshes an idle flow.\n * Linux sends no keepalive probe while data is in flight.\n * - `max_connection_age` \"infinite\": the client does not replace its connection at an age.\n * - `close_bound` 2 s: behind a black hole, `connection.close` waits for its close-ok with no bound.\n * - `retrans_giveup_time` 924.6 s: the give-up of Linux at `tcp_retries2 = 15`.\n */\nexport const RABBIT_STARTING_KNOBS: RabbitKnobs = {\n connect_bound: { value: 5, unit: \"s\" },\n request_timeout: { value: 9.5, unit: \"s\" },\n heartbeat_interval: { value: 15, unit: \"s\" },\n retry_base: { value: 1, unit: \"s\" },\n retry_cap: { value: 8, unit: \"s\" },\n keepalive_idle: { value: \"infinite\", unit: \"s\" },\n max_connection_age: { value: \"infinite\", unit: \"s\" },\n close_bound: { value: 2, unit: \"s\" },\n retrans_giveup_time: { value: 924.6, unit: \"s\" },\n};\n"],"mappings":"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"}
package/build/redis.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { t as Backoff } from "./backoff-B-jGWVDm.js";
2
1
  import { n as Clock } from "./clock-mVqhXD0b.js";
3
- import { B as Gate, Ct as Operation, E as CheckFn, G as Step, J as Poll, L as Dependency, M as Recorder, N as Check, W as ServiceDescription, et as heartbeatDetection, t as DependencyHandle } from "./lifecycle-CGEegMI0.js";
4
- import { n as ServiceBuilder } from "./builder-Dn0cby6Q.js";
2
+ import { tt as heartbeatDetection, wt as Operation } from "./lifecycle-fgYfxrHK.js";
3
+ import { C as MANIFESTATIONS, D as firstDetector, E as Situation, O as unwatched, S as Detector, T as Meeting, _ as Sent, a as ClientKnobs, c as ClientSpec, d as Factory, g as SendOptions, i as ClientJoin, l as ClientStep, n as CheckOptions, o as ClientOptions, p as RetryKnobs, r as Client, s as ClientSocket, t as Attempt, u as DialFn, w as Manifestation, y as retryBackoff } from "./core-BPLRDcUJ.js";
5
4
  import { n as LimiterStore } from "./store-D5HLgBnW.js";
6
5
  import { Extent, Quantity } from "@mark1russell7/quantity";
7
6
  import { Machine } from "@mark1russell7/algebra";
@@ -24,15 +23,8 @@ interface RedisConnection {
24
23
  on(event: string, listener: (...args: unknown[]) => void): unknown;
25
24
  off(event: string, listener: (...args: unknown[]) => void): unknown;
26
25
  }
27
- /** A Redis socket is the part of the socket of a connection that the join of the prober reads. */
28
- interface RedisSocket {
29
- readonly localAddress?: string | undefined;
30
- readonly localPort?: number | undefined;
31
- readonly remoteAddress?: string | undefined;
32
- readonly remotePort?: number | undefined;
33
- /** This method closes the socket at once. A disconnect only half-closes it, and a silent peer does not answer that. */
34
- destroy?(): void;
35
- }
26
+ /** A Redis socket is the socket of a client in the core: the join of the prober and the bounded close read it. */
27
+ type RedisSocket = ClientSocket;
36
28
  /**
37
29
  * These are the options that the wrapper gives ioredis. Each one comes from the declaration. The
38
30
  * wrapper sets each option that has an effect on a timer or on a send again, thus no default of
@@ -63,67 +55,26 @@ interface RedisConnectionOptions {
63
55
  readonly retryStrategy: (times: number) => number | null;
64
56
  }
65
57
  /** A connection factory makes the connection of the client from its options. */
66
- type ConnectionFactory = (options: RedisConnectionOptions) => RedisConnection | Promise<RedisConnection>;
58
+ type ConnectionFactory = Factory<RedisConnectionOptions, RedisConnection>;
67
59
  /**
68
60
  * This factory makes an ioredis client. It loads ioredis only when a client connects, thus a
69
61
  * service that does not use Redis does not need ioredis.
70
62
  */
71
63
  export declare const ioredis: ConnectionFactory;
72
64
  //#endregion
73
- //#region src/redis/meets.d.ts
74
- /** A situation is what the connection did when the failure started: a command in flight, or idle. */
75
- type Situation = "in flight" | "idle";
76
- /** A detector is a mechanism that finds a failure, and the most time that it takes. */
77
- interface Detector {
78
- /** This name identifies the mechanism: a knob of the declaration, or the kernel. */
79
- readonly by: string;
80
- /** This time is the most that the detector takes, after the failure starts. "infinite" tells that it does not find the failure. */
81
- readonly within: Extent;
82
- readonly situation: Situation;
83
- /** This text tells what the detector does, for example that it fails a command without a change of state. */
84
- readonly effect: string;
85
- }
86
- /**
87
- * A meeting is a manifestation that the client can meet, and its detectors (E9). The names of the
88
- * manifestations are the names of the vocabulary of the harness and the corpus. `app-stall` is a
89
- * server whose process is alive and does not answer, a proposal of the vocabulary.
90
- */
91
- interface Meeting {
92
- readonly manifestation: string;
93
- /** This text tells the failure in words. */
94
- readonly what: string;
95
- readonly detectors: readonly Detector[];
96
- }
97
- /**
98
- * This function gives the manifestations that a Redis client can meet, and the detectors of each, from
99
- * its knobs. A reset or a FIN arrives on the next segment. A black hole and a host that is gone give
100
- * no answer at all. A frozen process and a stalled server keep a kernel that acknowledges each
101
- * segment. Thus TCP keepalive does not fire on them, and only the client's own timers find them.
102
- */
103
- export declare const meetings: (name: string, knobs: RedisKnobs, heartbeat: Extent) => Meeting[];
104
- /** This function gives the first detector of a manifestation in a situation, or null when no detector has an end (C3). */
105
- export declare const firstDetector: (meeting: Meeting, situation: Situation) => Detector | null;
106
- /** This function gives each manifestation and situation that no detector of the client finds: the standing rule, as a query (C3, E9). */
107
- export declare const unwatched: (meets: readonly Meeting[]) => Array<{
108
- readonly manifestation: string;
109
- readonly situation: Situation;
110
- }>;
111
- //#endregion
112
65
  //#region src/redis/client.d.ts
113
66
  /**
114
67
  * These are the knobs of the client, each a time, by the names of the ordering law where the law
115
68
  * has them. The runtime has no defaults, thus each knob is necessary, and a bound that nobody gives
116
- * is "infinite", not zero.
69
+ * is "infinite", not zero. The knobs that each client has come from the core.
117
70
  */
118
- interface RedisKnobs {
71
+ interface RedisKnobs extends ClientKnobs, RetryKnobs {
119
72
  /** This time bounds a try to connect, until the TCP connection exists (`connectTimeout`). */
120
73
  readonly connect_bound: Quantity;
121
74
  /** This time bounds each command, the handshake commands too (`commandTimeout`). */
122
75
  readonly request_timeout: Quantity;
123
76
  /** This time destroys a socket that gives no data while a command waits (`socketTimeout`). */
124
77
  readonly driver_writeoff_time: Extent;
125
- /** This time is the idle time of TCP keepalive. Node then sends 10 probes 1 s apart. */
126
- readonly keepalive_idle: Extent;
127
78
  /** This time is the interval of the in-band watchdog: a PING on the connection of the application. */
128
79
  readonly heartbeat_interval: Extent;
129
80
  /** This time bounds each PING of the watchdog. */
@@ -138,37 +89,17 @@ interface RedisKnobs {
138
89
  readonly close_bound: Quantity;
139
90
  /** This time is the age at which the client replaces its connection. The client supports only "infinite". */
140
91
  readonly max_connection_age: Extent;
141
- /**
142
- * This time is when the kernel gives up a segment that it retransmits. Node cannot set it, thus
143
- * the declaration states it for the ordering law (O17), from the platform.
144
- */
145
- readonly retrans_giveup_time: Extent;
146
92
  }
147
93
  /** A Redis client spec is the declaration of one Redis client: where it connects, and its knobs. */
148
- interface RedisClientSpec {
149
- readonly host: string;
150
- readonly port: number;
94
+ interface RedisClientSpec extends ClientSpec<RedisKnobs> {
151
95
  readonly db?: number | undefined;
152
96
  readonly username?: string | undefined;
153
- readonly password?: string | undefined;
154
- /** This identifier is the component in the corpus (`systems.component.redis`), or null. */
155
- readonly system: string | null;
156
- readonly knobs: RedisKnobs;
157
97
  /** This count is the number of PINGs in sequence without an answer that make the watchdog connect again. */
158
98
  readonly heartbeatMisses: number;
159
- /** These operations add to the table of the client, or change it. */
160
- readonly operations?: Readonly<Record<string, Operation>> | undefined;
161
99
  }
162
- /** A dial function opens a new TCP connection, sends one PING, and reads the reply. The `dial` check uses it. */
163
- type DialFn = (host: string, port: number, signal: AbortSignal) => Promise<void>;
164
100
  /** These options make a client. Without them, it uses ioredis, the real clock and `Math.random`. */
165
- interface RedisClientOptions {
166
- readonly create?: ConnectionFactory | undefined;
167
- readonly clock?: Clock | undefined;
101
+ interface RedisClientOptions extends ClientOptions<ConnectionFactory> {
168
102
  readonly random?: (() => number) | undefined;
169
- /** This function receives each transition of the client, for the prober (D3). */
170
- readonly record?: Recorder | undefined;
171
- readonly dial?: DialFn | undefined;
172
103
  /**
173
104
  * This function waits one turn of the event loop. The watchdog waits it before it counts a miss,
174
105
  * thus a reply that is in the socket is read first. Without it, the client uses `setImmediate`.
@@ -180,65 +111,14 @@ interface RedisClientOptions {
180
111
  * records to the server's view of the connection through it. A join holds the server's id of the
181
112
  * connection, and the local address and port of the socket.
182
113
  */
183
- interface Join {
184
- readonly generation: number;
185
- /** This time is when the connection became ready, in milliseconds on the monotonic clock. */
186
- readonly at: number;
114
+ interface Join extends ClientJoin {
187
115
  /** This number is the reply of CLIENT ID, or null when the server gave none. */
188
116
  readonly clientId: number | null;
189
- readonly localAddress: string | null;
190
- readonly localPort: number | null;
191
- readonly remoteAddress: string | null;
192
- readonly remotePort: number | null;
193
- }
194
- /**
195
- * A sent result is the outcome of a command, in three values (C8):
196
- * - `ok`: the server answered.
197
- * - `error`: the command did not apply. The server answered with an error (`reply`), or the command
198
- * did not leave the client.
199
- * - `indeterminate`: the command can have reached the server, and nothing tells if it applied.
200
- */
201
- type Sent<T> = {
202
- readonly outcome: "ok";
203
- readonly value: T;
204
- readonly attempts: number;
205
- } | {
206
- readonly outcome: "error";
207
- readonly error: string;
208
- readonly reply: boolean;
209
- readonly attempts: number;
210
- } | {
211
- readonly outcome: "indeterminate";
212
- readonly error: string;
213
- readonly attempts: number;
214
- };
215
- /** Send options tell how the client sends one command. */
216
- interface SendOptions {
217
- /** This signal abandons the command. A command that left the client is then indeterminate. */
218
- readonly signal?: AbortSignal | undefined;
219
- /** False stops the client: it does not send the command again. A check that must fail at once uses it. */
220
- readonly reissue?: boolean | undefined;
221
- }
222
- /** The steps of a connection that the client gives a check for (B14). */
223
- type ClientStep = Extract<Step, "dial" | "round-trip" | "local-state">;
224
- /** Check options tell which checks the client gives the health runtime, and which policy each one controls. */
225
- interface CheckOptions {
226
- readonly poll: Poll;
227
- /** For each step, the policy that its check controls, or null for a check that only reports. A step that is absent has no check. */
228
- readonly steps: Partial<Record<ClientStep, Gate | null>>;
229
117
  }
230
118
  /** A Redis client is a dependency of a service: its declaration, its handle, its checks and its commands. */
231
- interface RedisClient {
232
- readonly name: string;
233
- /** This declaration goes to `svc.dependency(name, declaration, handle)`. */
234
- readonly declaration: Omit<Dependency, "name">;
235
- readonly handle: DependencyHandle;
236
- /** These are the manifestations that the client can meet, and the detector of each (E9). */
237
- readonly meets: readonly Meeting[];
119
+ interface RedisClient extends Client<ClientStep, Join> {
238
120
  /** This text is the state of the connection, as ioredis names it, or "none" before the connect. */
239
121
  readonly status: string;
240
- /** These are the generations of the connection, the latest last. */
241
- readonly joins: readonly Join[];
242
122
  /**
243
123
  * This method sends a command, and gives its outcome in three values. The client sends again
244
124
  * only an idempotent operation, one time at most, after an outcome that is not certain. The
@@ -246,18 +126,7 @@ interface RedisClient {
246
126
  * the table does not name.
247
127
  */
248
128
  send<T = unknown>(command: string, args?: ReadonlyArray<string | number>, options?: SendOptions): Promise<Sent<T>>;
249
- /** This method gives the checks of the client and their functions, for `svc.check(check, run)`. */
250
- checks(options: CheckOptions): ReadonlyArray<readonly [Check, CheckFn]>;
251
- /** This method declares the client in a service: its dependency, its handle and its checks. */
252
- attach(svc: ServiceBuilder, options: CheckOptions): ServiceBuilder;
253
- /** This method gives the problems of the client in a description: a close that does not fit in its phase, for example. */
254
- problems(description: ServiceDescription): string[];
255
129
  }
256
- /**
257
- * This function gives the backoff of the tries to connect: from `retry_base`, doubling to
258
- * `retry_cap`, each with a jitter up to the base. The client rounds each delay to a whole millisecond.
259
- */
260
- export declare const retryBackoff: (knobs: RedisKnobs) => Backoff;
261
130
  /** This function gives the problems of a spec: the knobs that the client cannot use. */
262
131
  export declare const specProblems: (name: string, spec: RedisClientSpec) => string[];
263
132
  /** This dial function opens a TCP connection, sends an inline PING, and waits for +PONG. */
@@ -271,9 +140,21 @@ export declare const tcpDial: DialFn;
271
140
  * - It records the server's id of each connection at `ready` (D1).
272
141
  * - A watchdog sends a PING in the band of the connection, and it connects again after the misses
273
142
  * of the declaration. It also starts a client that stopped (principle 19).
143
+ *
144
+ * The outcomes, the second try, the commands in flight, the bounded close and the checks come
145
+ * from the core of the clients. This entry gives what ioredis does in its own way.
274
146
  */
275
147
  export declare const redisClient: (name: string, spec: RedisClientSpec, options?: RedisClientOptions) => RedisClient;
276
148
  //#endregion
149
+ //#region src/redis/meets.d.ts
150
+ /**
151
+ * This function gives the manifestations that a Redis client can meet, and the detectors of each, from
152
+ * its knobs. A reset or a FIN arrives on the next segment. A black hole and a host that is gone give
153
+ * no answer at all. A frozen process and a stalled server keep a kernel that acknowledges each
154
+ * segment. Thus TCP keepalive does not fire on them, and only the client's own timers find them.
155
+ */
156
+ export declare const meetings: (name: string, knobs: RedisKnobs, heartbeat: Extent) => Meeting[];
157
+ //#endregion
277
158
  //#region src/redis/limiter.d.ts
278
159
  /**
279
160
  * This script decides one take in one atomic step of Redis. It does the arithmetic of `decide`, and
@@ -332,5 +213,5 @@ export declare const REDIS_OPERATIONS: Readonly<Record<string, Operation>>;
332
213
  /** This function gives the name of a command as the table writes it: upper case, and CLIENT ID with its subcommand. */
333
214
  export declare const commandName: (command: string, args: readonly unknown[]) => string;
334
215
  //#endregion
335
- export { type CheckOptions, type ClientStep, type ConnectionFactory, type Detector, type DialFn, type Join, type Meeting, type RedisClient, type RedisClientOptions, type RedisClientSpec, type RedisConnection, type RedisConnectionOptions, type RedisKnobs, type RedisLimiterOptions, type RedisSender, type RedisSocket, type Sent, type Situation, heartbeatDetection };
216
+ export { type Attempt, type CheckOptions, type Client, type ClientJoin, type ClientKnobs, type ClientOptions, type ClientSocket, type ClientSpec, type ClientStep, type ConnectionFactory, type Detector, type DialFn, type Factory, type Join, MANIFESTATIONS, type Manifestation, type Meeting, type RedisClient, type RedisClientOptions, type RedisClientSpec, type RedisConnection, type RedisConnectionOptions, type RedisKnobs, type RedisLimiterOptions, type RedisSender, type RedisSocket, type Sent, type Situation, firstDetector, heartbeatDetection, retryBackoff, unwatched };
336
217
  //# sourceMappingURL=redis.d.ts.map