@mark1russell7/service 0.5.12 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongodb.js","names":["knob","t"],"sources":["../src/mongodb/connection.ts","../src/mongodb/meets.ts","../src/mongodb/client.ts","../src/mongodb/machine.ts","../src/mongodb/starting.ts"],"sourcesContent":["import type { Factory } from \"../client/core.ts\";\n\n/** A document is a BSON document as the driver gives it: a command, or the reply of the server. */\nexport type MongoDocument = Record<string, unknown>;\n\n/** A database is the part of a `Db` of the driver that the wrapper uses: `command` sends one command. */\nexport interface MongoDb {\n /** This method sends a command. Its signal ends the command, and an abort during the read closes the connection. */\n command(document: MongoDocument, options?: { readonly signal?: AbortSignal | undefined }): Promise<MongoDocument>;\n}\n\n/** A server is the part of a `ServerDescription` that the wrapper reads: its type, for example `RSPrimary`. */\nexport interface MongoServer {\n readonly type: string;\n}\n\n/** These are the events of the driver that the wrapper listens to, by name, with the fields that it reads. */\nexport interface MongoEvents {\n /** The view of the topology changed. A writable server is a primary, a standalone, a mongos or a load balancer. */\n topologyDescriptionChanged: { readonly newDescription: { readonly servers: ReadonlyMap<string, MongoServer> } };\n /** A connection of the pool finished its handshake. `connectionId` is the id that the pool gives. */\n connectionReady: { readonly connectionId: number | string; readonly address: string };\n /** A command went to a connection. `serverConnectionId` is the id that the server gave in its hello. */\n commandStarted: {\n readonly connectionId?: number | string | undefined;\n readonly address: string;\n readonly serverConnectionId?: bigint | number | null | undefined;\n };\n /** The pool of a server closed its connections, for example at the write-off of the server. */\n connectionPoolCleared: { readonly address: string; readonly interruptInUseConnections?: boolean | undefined };\n /** A hello of the monitor failed. `connectionId` is the address of the server. */\n serverHeartbeatFailed: { readonly connectionId: string; readonly failure: Error };\n /** An error of the topology. In Node, an error with no listener throws. */\n error: Error;\n}\n\n/** An event name is a name of `MongoEvents`. */\nexport type MongoEventName = keyof MongoEvents;\n\n/**\n * A driver client is the part of a `MongoClient` that the wrapper uses. A `MongoClient` of the\n * driver 7 has each part. A fake for tests has them too. Thus the package depends on no type of the driver.\n */\nexport interface MongoDriverClient {\n /** This method opens the topology and waits for a server to select. A failure closes the topology, and a later command opens it again. */\n connect(): Promise<unknown>;\n /** This method fails the commands in flight, sends `endSessions` to a selectable server, and closes the topology. */\n close(): Promise<void>;\n db(name: string): MongoDb;\n on<E extends MongoEventName>(event: E, listener: (payload: MongoEvents[E]) => void): unknown;\n off<E extends MongoEventName>(event: E, listener: (payload: MongoEvents[E]) => void): unknown;\n}\n\n/**\n * These are the options that the wrapper gives `MongoClient`. Each one comes from the declaration.\n * The wrapper sets each option that has an effect on a timer, thus no default of the driver decides\n * one. A knob of the driver at 0 has no timer. The wrapper writes \"infinite\" as 0, and it refuses a bound of 0.\n */\nexport interface MongoDriverOptions {\n /** The server shows this name in `$currentOp` and in its log, thus the prober can join on it too. */\n readonly appName: string;\n readonly replicaSet?: string | undefined;\n readonly auth?: { readonly username: string; readonly password: string } | undefined;\n readonly authSource?: string | undefined;\n readonly maxPoolSize: number;\n readonly minPoolSize: number;\n /** This time bounds the TCP connect and the handshake of each connection, and of the monitor. */\n readonly connectTimeoutMS: number;\n /** This time bounds a read on a connection that has a command in flight. At its end, the driver closes the connection. */\n readonly socketTimeoutMS: number;\n /** This time bounds the wait for a server to select, for each command and for `connect`. */\n readonly serverSelectionTimeoutMS: number;\n /** This time is the period of the monitor, and the wait of its streaming hello. */\n readonly heartbeatFrequencyMS: number;\n /** This time bounds a checkout from a full pool. 0 is no bound. */\n readonly waitQueueTimeoutMS: number;\n /** A checkout ends an idle connection older than this time. 0 is no limit. The driver has no timer for it. */\n readonly maxIdleTimeMS: number;\n /** This time is the idle time of TCP keepalive. The driver always turns keepalive on. Node truncates the time to whole seconds. */\n readonly keepAliveInitialDelay: number;\n /** The monitor streams its hello, as in the cells. Thus a silent server is written off at `connectTimeoutMS + heartbeatFrequencyMS`. */\n readonly serverMonitoringMode: \"stream\";\n /** The driver does not send a command again by itself. The core sends again only an idempotent operation. */\n readonly retryReads: false;\n readonly retryWrites: false;\n /** The driver emits `commandStarted`, which gives the id of the server for each connection (D1). */\n readonly monitorCommands: true;\n}\n\n/** These are the arguments of `new MongoClient(url, options)`. The URL gives only the hosts. */\nexport interface MongoConnectOptions {\n readonly url: string;\n readonly options: MongoDriverOptions;\n}\n\n/** A driver factory makes the client of the driver from its options. */\nexport type MongoFactory = Factory<MongoConnectOptions, MongoDriverClient>;\n\n/**\n * This factory makes a `MongoClient`. It loads the driver only when a client connects, thus a\n * service that does not use MongoDB does not need it. The specifier is a value, thus the package\n * needs no types of the driver. The driver gives `MongoClient` as a named export.\n */\nexport const nodeMongo: MongoFactory = async ({ url, options }) => {\n const specifier = \"mongodb\";\n const loaded = (await import(specifier)) as {\n readonly MongoClient: new (url: string, options: MongoDriverOptions) => MongoDriverClient;\n };\n return new loaded.MongoClient(url, options);\n};\n","import { quantity } from \"@mark1russell7/quantity\";\nimport type { Detector, Meeting } from \"../client/meets.ts\";\nimport { WHAT, immediate, keepaliveAnswered, keepaliveReset, keepaliveSilent, kernelGiveUp } from \"../client/meets.ts\";\nimport { ms } from \"../client/core.ts\";\nimport type { MongoKnobs } from \"./client.ts\";\n\n/**\n * This function gives the manifestations that a MongoDB client can meet, and the detectors of each,\n * from its knobs. The cells of S1 measured them on the driver 7.7.0 against mongod 9.0.4:\n * - The monitor writes off a silent server at `connectTimeoutMS + heartbeatFrequencyMS` after its last\n * answer: 40.002 s at the defaults, and 7 s for 5 s + 2 s. The driver then clears the pool, and\n * fails each command in flight. With `connectTimeoutMS` 0, nothing found a freeze in 60 s.\n * - At `tcp_retries2 = 5`, the kernel gave up at 12.936 s.\n * - A `keepAliveInitialDelay` of 27 s ended an idle stream at 37.255 s.\n *\n * The monitor tests the server, not each connection of the pool. Thus a write-off finds a failure of\n * the server in flight and idle, and a failure of one connection waits for its own detector.\n */\nexport const mongoMeetings = (name: string, knobs: MongoKnobs): Meeting[] => {\n const writeoffTime = quantity((ms(knobs.connect_bound) + ms(knobs.heartbeat_interval)) / 1000, \"s\");\n const writeoff = (situation: \"in flight\" | \"idle\"): Detector => ({\n by: \"driver_writeoff_time\",\n within: writeoffTime,\n situation,\n effect:\n situation === \"in flight\"\n ? `the monitor writes the server off, and the driver fails the command as indeterminate. ${name} goes to failing`\n : `the monitor writes the server off, and the driver closes each idle connection. ${name} goes to failing`,\n });\n const read: Detector = {\n by: \"request_timeout\",\n within: knobs.request_timeout,\n situation: \"in flight\",\n effect: \"the command fails with a timeout of the socket, as indeterminate, and the driver closes the connection\",\n };\n const idleExpiry: Detector = {\n by: \"pool_idle_expiry\",\n within: knobs.pool_idle_expiry,\n situation: \"idle\",\n effect: \"the next checkout ends the idle connection, thus no command gets the dead connection\",\n };\n const giveUp = kernelGiveUp(knobs.retrans_giveup_time, \"the command fails as indeterminate\");\n const silent = (manifestation: \"blackhole\" | \"freeze-host\"): Meeting => ({\n manifestation,\n what: WHAT[manifestation],\n detectors: [writeoff(\"in flight\"), read, giveUp, writeoff(\"idle\"), idleExpiry, keepaliveSilent(knobs.keepalive_idle)],\n });\n return [\n {\n manifestation: \"rst\",\n what: WHAT.rst,\n detectors: [\n immediate(\"in flight\", \"the command fails with ECONNRESET, and the driver clears the pool\"),\n {\n by: \"heartbeat_interval\",\n within: knobs.heartbeat_interval,\n situation: \"idle\",\n effect: \"the next answer of the monitor gets a reset, thus the driver writes the server off and clears the pool\",\n },\n idleExpiry,\n keepaliveReset(knobs.keepalive_idle),\n ],\n },\n {\n manifestation: \"fin\",\n what: WHAT.fin,\n detectors: [\n immediate(\"in flight\", \"the command fails: the connection closed\"),\n immediate(\"idle\", \"the stream of the monitor closes, thus the driver writes the server off and clears the pool\"),\n ],\n },\n silent(\"blackhole\"),\n silent(\"freeze-host\"),\n {\n manifestation: \"freeze-process\",\n what: WHAT[\"freeze-process\"],\n detectors: [writeoff(\"in flight\"), read, writeoff(\"idle\"), idleExpiry, keepaliveAnswered()],\n },\n {\n manifestation: \"app-stall\",\n what: \"the server stops its replies to commands for a time, for example behind a lock, and its monitor still answers\",\n detectors: [\n {\n by: \"statement_timeout\",\n within: knobs.statement_timeout,\n situation: \"in flight\",\n effect: \"the server ends the command with error 50 (MaxTimeMSExpired), a certain outcome, and the connection stays\",\n },\n read,\n idleExpiry,\n keepaliveAnswered(),\n ],\n },\n ];\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\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 deadlineProblem,\n dialCheck,\n finiteMs,\n inFlight,\n keepaliveOnProblems,\n localStateCheck,\n message,\n ms,\n notSent,\n operationTable,\n orZero,\n poolProblems,\n roundTripCheck,\n sendWithReissue,\n stepChecks,\n timeProblems,\n wholeProblems,\n withinBound,\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 { Operation } from \"../operation.ts\";\nimport type { MongoConnectOptions, MongoDocument, MongoDriverClient, MongoEventName, MongoEvents, MongoFactory } from \"./connection.ts\";\nimport { nodeMongo } from \"./connection.ts\";\nimport { mongoMeetings } 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. `MONGO_STARTING_KNOBS` gives a declaration from the cells of S1. The knobs\n * that each client has come from the core.\n */\nexport interface MongoKnobs extends ClientKnobs {\n /** This time bounds the TCP connect and the handshake of each connection (`connectTimeoutMS`). It also bounds the wait of the startup. */\n readonly connect_bound: Quantity;\n /** This time bounds a read of a command in flight (`socketTimeoutMS`). At its end, the driver closes the connection. */\n readonly request_timeout: Quantity;\n /**\n * This time is the deadline of each command on the server (`maxTimeMS`). The wrapper writes it in\n * each command. The server then ends the command with error 50, a certain outcome. \"infinite\" sends no deadline.\n */\n readonly statement_timeout: Extent;\n /** This time is the period of the monitor (`heartbeatFrequencyMS`). The driver takes 500 ms at least. */\n readonly heartbeat_interval: Quantity;\n /**\n * This time bounds the wait for a writable server (`serverSelectionTimeoutMS`). A command waits\n * through a failover in this time, thus it is the retry window of the client (O29).\n */\n readonly client_retry_window: Quantity;\n /** This time bounds a checkout from a full pool (`waitQueueTimeoutMS`). */\n readonly checkout_timeout: Extent;\n /** A checkout ends an idle connection older than this time (`maxIdleTimeMS`). The driver checks it only at a checkout. */\n readonly pool_idle_expiry: Extent;\n /** This time is the idle time of TCP keepalive (`keepAliveInitialDelay`), in whole seconds. The driver always turns keepalive on. */\n readonly keepalive_idle: Extent;\n /** The driver has no age for a connection, thus this knob must be \"infinite\". */\n readonly max_connection_age: Extent;\n /** This time bounds the close of the client at the shutdown. */\n readonly close_bound: Quantity;\n}\n\n/** A seed is one more host that the driver connects to first, for example a member of a replica set. */\nexport interface MongoSeed {\n readonly host: string;\n readonly port: number;\n}\n\n/** A MongoDB client spec is the declaration of one MongoDB client: where it connects, its pool, and its knobs. */\nexport interface MongoClientSpec extends ClientSpec<MongoKnobs> {\n /** This name is the database of the commands of the client. */\n readonly database: string;\n readonly username?: string | undefined;\n /** This name is the database of the user. The default of the driver is `database`, or `admin`. */\n readonly authSource?: string | undefined;\n readonly replicaSet?: string | undefined;\n /** These hosts are more seeds of the topology, after `host` and `port`. */\n readonly seeds?: readonly MongoSeed[] | undefined;\n /** These counts are the sizes of the pool of each server: at most `max` connections, and `min` that the driver keeps open. */\n readonly pool: { readonly max: number; readonly min: number };\n /**\n * These operations are the commands of the service, by name, with their idempotency. A command\n * name does not tell if a command changes a state, for example an `aggregate` with `$merge`. Thus\n * the client sends again only a command that this table names.\n */\n readonly operations?: Readonly<Record<string, Operation>> | undefined;\n}\n\n/** These options make a client. Without them, it uses the driver, the real clock, and a dial with a hello. */\nexport type MongoClientOptions = ClientOptions<MongoFactory>;\n\n/**\n * A join is one connection of the pool, when its handshake is done (D1, D2). The prober joins its\n * kernel records to the server's view of the connection through it. The driver gives no socket,\n * thus the local address and port are null. The server's view is the connection id of its hello.\n */\nexport interface MongoJoin extends ClientJoin {\n /** This text is the address of the server, as `host:port`. */\n readonly address: string;\n /** This number is the connection id that the server gave, from the first command on the connection. Null until that command. */\n readonly serverConnectionId: number | null;\n}\n\n/** The steps that the MongoDB client gives a check for. A pool of the driver has no checkout that the wrapper can use. */\nexport type MongoStep = ClientStep;\n\n/** A MongoDB client is a dependency of a service: its declaration, its handle, its checks and its commands. */\nexport interface MongoClient extends Client<MongoStep, MongoJoin> {\n /** This text is the state of the client, as `MONGO_CLIENT_MACHINE` names it. */\n readonly status: string;\n /**\n * This method sends a command to the database of the spec, and gives its outcome in three values.\n * `operation` names the command in the table of operations. The client sends again only an\n * idempotent operation, one time at most, after an outcome that is not certain.\n */\n command(operation: string, document: MongoDocument, options?: SendOptions): Promise<Sent<MongoDocument>>;\n}\n\n/** The driver takes a period of the monitor of 500 ms at least. */\nconst LEAST_HEARTBEAT_MS = 500;\n\n/** This function gives the problems of a spec: the knobs that the driver cannot use. */\nexport const specProblems = (name: string, spec: MongoClientSpec): string[] => {\n const { knobs } = spec;\n const problems = [\n ...timeProblems(name, knobs),\n // the driver reads 0 as no timer, thus a bound of 0 is not a bound\n ...zeroProblems(name, \"the driver\", knobs, {\n bounded: [\"connect_bound\", \"request_timeout\", \"heartbeat_interval\", \"client_retry_window\", \"close_bound\"],\n open: [\"statement_timeout\", \"checkout_timeout\", \"pool_idle_expiry\"],\n }),\n // the options are whole milliseconds, and Node truncates the keepalive delay to whole seconds\n ...wholeProblems(name, knobs, {\n millis: [\"connect_bound\", \"request_timeout\", \"statement_timeout\", \"heartbeat_interval\", \"client_retry_window\", \"checkout_timeout\", \"pool_idle_expiry\"],\n seconds: [\"keepalive_idle\"],\n }),\n ...poolProblems(name, spec.pool),\n ];\n if (ms(knobs.heartbeat_interval) < LEAST_HEARTBEAT_MS)\n problems.push(`${name}'s heartbeat_interval is below 500 ms, the least that the driver takes`);\n problems.push(...keepaliveOnProblems(name, knobs, \"the driver\"), ...ageProblems(name, knobs, \"the driver has no age for a connection\"));\n return problems;\n};\n\n/** This function gives the arguments of `new MongoClient` from a spec. Each timer of the driver comes from the declaration. */\nexport const mongoOptions = (name: string, spec: MongoClientSpec): MongoConnectOptions => {\n const { knobs } = spec;\n const hosts = [{ host: spec.host, port: spec.port }, ...(spec.seeds ?? [])].map((seed) => `${seed.host}:${seed.port}`);\n return {\n url: `mongodb://${hosts.join(\",\")}/`,\n options: {\n appName: name,\n ...(spec.replicaSet === undefined ? {} : { replicaSet: spec.replicaSet }),\n ...(spec.username === undefined ? {} : { auth: { username: spec.username, password: spec.password ?? \"\" } }),\n ...(spec.authSource === undefined ? {} : { authSource: spec.authSource }),\n maxPoolSize: spec.pool.max,\n minPoolSize: spec.pool.min,\n connectTimeoutMS: ms(knobs.connect_bound),\n socketTimeoutMS: ms(knobs.request_timeout),\n serverSelectionTimeoutMS: ms(knobs.client_retry_window),\n heartbeatFrequencyMS: ms(knobs.heartbeat_interval),\n waitQueueTimeoutMS: orZero(knobs.checkout_timeout),\n maxIdleTimeMS: orZero(knobs.pool_idle_expiry),\n keepAliveInitialDelay: ms(knobs.keepalive_idle),\n serverMonitoringMode: \"stream\",\n retryReads: false,\n retryWrites: false,\n monitorCommands: true,\n },\n };\n};\n\n/**\n * The bytes of an OP_MSG with the command `{ hello: 1, $db: \"admin\" }`. They are a header of 16\n * bytes with the op code 2013, the flags, a section of kind 0, and the BSON document. The server answers a\n * hello before the authentication, thus the dial needs no password.\n */\nexport const HELLO: Uint8Array = Uint8Array.of(\n // the header: the length 52, the request id 1, the response to 0, the op code 2013\n 52, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0xdd, 0x07, 0, 0,\n // the flags, and the section of kind 0\n 0, 0, 0, 0, 0,\n // the document: its length 31, the int32 `hello` 1, the string `$db` \"admin\", and its end\n 31, 0, 0, 0,\n 0x10, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0, 1, 0, 0, 0,\n 0x02, 0x24, 0x64, 0x62, 0, 6, 0, 0, 0, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0,\n 0,\n);\n\n/** The sizes of the values of the BSON types that a hello reply holds, by type. A null size has a length prefix. */\nconst FIXED: Readonly<Record<number, number>> = { 0x01: 8, 0x07: 12, 0x08: 1, 0x09: 8, 0x0a: 0, 0x10: 4, 0x11: 8, 0x12: 8, 0x13: 16 };\n\n/** This function gives the value of `ok` at the top of a BSON document, or an error. */\nconst okOf = (doc: Buffer): number | Error => {\n let at = 4;\n while (at < doc.length - 1) {\n const type = doc[at]!;\n const end = doc.indexOf(0, at + 1);\n if (end < 0) return new Error(\"the hello reply has a field with no end\");\n const key = doc.toString(\"latin1\", at + 1, end);\n at = end + 1;\n if (key === \"ok\") {\n if (type === 0x01) return doc.readDoubleLE(at);\n if (type === 0x10) return doc.readInt32LE(at);\n return new Error(`the ok of the hello reply has the BSON type ${type}`);\n }\n const fixed = FIXED[type];\n if (fixed !== undefined) at += fixed;\n else if (type === 0x02) at += 4 + doc.readInt32LE(at);\n else if (type === 0x03 || type === 0x04) at += doc.readInt32LE(at);\n else if (type === 0x05) at += 5 + doc.readInt32LE(at);\n else return new Error(`the hello reply has the BSON type ${type}, which the dial does not read`);\n }\n return new Error(\"the hello reply has no ok\");\n};\n\n/**\n * This judge reads the answer to the hello. It waits for the whole message. The message must be an\n * OP_MSG with a section of kind 0, and its document must have `ok` 1.\n */\nexport const helloJudge: Judge = (received) => {\n if (received.length < 4) return \"more\";\n const length = received.readInt32LE(0);\n if (length < 26 || length > 48 * 1024 * 1024) return new Error(`the answer to the hello has a length of ${length}`);\n if (received.length < length) return \"more\";\n const opCode = received.readInt32LE(12);\n if (opCode !== 2013) return new Error(`the answer to the hello has the op code ${opCode}, not 2013`);\n if (received[20] !== 0) return new Error(\"the answer to the hello has no section of kind 0\");\n const ok = okOf(received.subarray(21, length));\n if (ok instanceof Error) return ok;\n return ok === 1 ? null : new Error(`the hello reply has ok ${ok}`);\n};\n\n/** This dial function opens a TCP connection, sends a hello, and waits for its reply. The server opens no session for it. */\nexport const helloCommandDial: DialFn = helloDial(HELLO, helloJudge);\n\n/** These errors tell that the command did not leave the client: no server, a full pool, a cleared pool, or a closed client. */\nconst NOT_SENT = new Set([\n \"MongoServerSelectionError\",\n \"MongoWaitQueueTimeoutError\",\n \"MongoPoolClearedError\",\n \"MongoPoolClosedError\",\n \"MongoServerClosedError\",\n \"MongoTopologyClosedError\",\n \"MongoNotConnectedError\",\n \"MongoClientClosedError\",\n]);\n\n/**\n * These codes of the server tell that the server interrupted the command. The causes are its\n * shutdown, a step down, or a change of the state of the replica set. A part of the command can have applied.\n */\nconst INTERRUPTED = new Set([91, 189, 11600, 11602]);\n\n/** The writable servers: a primary, a standalone, a mongos, and a load balancer. */\nconst WRITABLE = new Set([\"RSPrimary\", \"Standalone\", \"Mongos\", \"LoadBalancer\"]);\n\n/** This function gives the outcome of a reply. A write concern error tells that the write can roll back, thus it is not certain. */\nconst replyOutcome = (reply: MongoDocument): Attempt<MongoDocument> => {\n const concern = reply[\"writeConcernError\"] as { code?: unknown; errmsg?: unknown } | undefined;\n if (concern === undefined || concern === null) return { outcome: \"ok\", value: reply };\n return { outcome: \"indeterminate\", error: `write concern ${String(concern.code)} ${String(concern.errmsg)}` };\n};\n\n/**\n * This function gives the outcome of a failure of the driver. The driver names each error class,\n * thus the wrapper reads the name, and depends on no class of the driver:\n * - A failure before the command left the client is certain: it did not apply.\n * - An error of the server is certain, except an interruption, which can leave a part applied.\n * - A write concern error, a network error after the handshake, and a timeout of the socket are not certain.\n */\nexport const failureOutcome = (error: unknown): Attempt<never> => {\n const text = message(error);\n const { name, code, beforeHandshake } = (typeof error === \"object\" && error !== null ? error : {}) as {\n name?: unknown;\n code?: unknown;\n beforeHandshake?: unknown;\n };\n if (typeof name === \"string\" && NOT_SENT.has(name)) return notSent(text);\n // a network error of a new connection, before its handshake: the command did not go to it\n if (name === \"MongoNetworkError\" && beforeHandshake === true) return notSent(text);\n if (name === \"MongoServerError\" && typeof code === \"number\") {\n const said = `${code} ${text}`;\n return INTERRUPTED.has(code) ? { outcome: \"indeterminate\", error: said } : { outcome: \"error\", error: said, reply: true };\n }\n return { outcome: \"indeterminate\", error: text };\n};\n\n/** This function gives the host and the port of an address `host:port`. */\nconst endsOf = (address: string): { readonly host: string | null; readonly port: number | null } => {\n const at = address.lastIndexOf(\":\");\n const port = Number(address.slice(at + 1));\n return at < 0 || !Number.isInteger(port) ? { host: address, port: null } : { host: address.slice(0, at), port };\n};\n\n/**\n * This function makes a MongoDB client, a dependency of a service over a `MongoClient`.\n * - It sets each timer of the driver from the declaration, and it turns off the retries of the driver.\n * The client sends again only an idempotent operation of its table.\n * - It tells the outcome of a command in three values, from the name of each error of the driver.\n * - It writes the deadline of the server in each command (`maxTimeMS`), thus the server ends a slow command first (O07).\n * - It gives the state from the view of the topology: ready while a server is writable.\n * - It listens to each error of the driver. In Node, an error with no listener throws, and library code does not end the process (A5).\n * - It records the connection id of the server for each connection of the pool (D1).\n *\n * The outcomes, the second try, the commands in flight, the bounded close and the checks come from\n * the core of the clients. This entry gives what the driver does in its own way.\n */\nexport const mongoClient = (name: string, spec: MongoClientSpec, options: MongoClientOptions = {}): MongoClient => {\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 ?? nodeMongo;\n const dial = options.dial ?? helloCommandDial;\n const operations = operationTable({}, spec.operations);\n const deadline = finiteMs(knobs.statement_timeout);\n // a command waits for a writable server in the retry window, then it reads in the request bound\n const callBound = ms(knobs.client_retry_window) + ms(knobs.request_timeout);\n\n let driver: MongoDriverClient | null = null;\n let status = \"none\";\n const joins: MongoJoin[] = [];\n // the index in `joins` of each connection, by its address and the id that the pool gives\n const joined = new Map<string, number>();\n const flight = inFlight();\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 // the state is the view of the topology: a writable server makes the client ready. The close ignores the topology\n const onTopology = (event: MongoEvents[\"topologyDescriptionChanged\"]): void => {\n const writable = [...event.newDescription.servers.values()].some((server) => WRITABLE.has(server.type));\n if (writable && (status === \"connecting\" || status === \"failing\")) record(\"topology.writable\", \"ready\");\n else if (!writable && status === \"ready\") record(\"topology.unwritable\", \"failing\");\n };\n const onReady = (event: MongoEvents[\"connectionReady\"]): void => {\n const generation = joins.length + 1;\n const { host, port } = endsOf(event.address);\n joined.set(`${event.address}#${String(event.connectionId)}`, joins.length);\n joins.push({\n generation,\n at: clock.now(),\n address: event.address,\n serverConnectionId: null,\n localAddress: null,\n localPort: null,\n remoteAddress: host,\n remotePort: port,\n });\n record(\"join\", status, `generation ${generation}, ${event.address}`);\n };\n // the hello of a connection gives the id of the server, and the driver gives it only in the events of a command\n const onStarted = (event: MongoEvents[\"commandStarted\"]): void => {\n const index = joined.get(`${event.address}#${String(event.connectionId)}`);\n const id = event.serverConnectionId;\n if (index === undefined || id === null || id === undefined) return;\n joined.delete(`${event.address}#${String(event.connectionId)}`);\n joins[index] = { ...joins[index]!, serverConnectionId: Number(id) };\n };\n const onCleared = (event: MongoEvents[\"connectionPoolCleared\"]): void =>\n record(\"pool.cleared\", status, `${event.address}${event.interruptInUseConnections === true ? \", with the commands in flight\" : \"\"}`);\n const onHeartbeat = (event: MongoEvents[\"serverHeartbeatFailed\"]): void =>\n record(\"heartbeat.failed\", status, `${event.connectionId}: ${message(event.failure)}`);\n // after the end, the listener only keeps the error from a throw\n const onError = (error: Error): void => {\n if (status !== \"end\") record(\"client.error\", status, message(error));\n };\n const listeners: ReadonlyArray<readonly [MongoEventName, (payload: never) => void]> = [\n [\"topologyDescriptionChanged\", onTopology],\n [\"connectionReady\", onReady],\n [\"commandStarted\", onStarted],\n [\"connectionPoolCleared\", onCleared],\n [\"serverHeartbeatFailed\", onHeartbeat],\n ];\n\n /** This function makes one try of a command. The driver selects a server, checks out a connection, and sends the command. */\n const attempt = (document: MongoDocument, signal: AbortSignal | undefined): Promise<Attempt<MongoDocument>> => {\n const c = driver;\n if (c === null) return Promise.resolve(notSent(`${name} has not connected`));\n if (closing()) return Promise.resolve(notSent(`${name} is ${status}: the command did not leave the client`));\n if (signal?.aborted) return Promise.resolve(notSent(\"the caller aborted the command before it left the client\"));\n // the deadline of the server goes in the command. The driver adds maxTimeMS only with timeoutMS, which the wrapper does not use\n const sent = deadline === undefined || \"maxTimeMS\" in document ? document : { ...document, maxTimeMS: deadline };\n return flight.track(signal, \"the caller abandoned the command after it went to the driver\", (settle) => {\n c.db(spec.database)\n .command(sent, signal === undefined ? {} : { signal })\n .then(\n (reply) => settle(replyOutcome(reply)),\n (error: unknown) => settle(failureOutcome(error)),\n );\n });\n };\n\n const command = (operation: string, document: MongoDocument, sendOptions: SendOptions = {}): Promise<Sent<MongoDocument>> =>\n sendWithReissue({\n clock,\n bound: callBound,\n operation: operations[operation],\n options: sendOptions,\n // the second try waits in the selection of a server, thus it needs no wait before it\n attempt: (signal) => attempt(document, signal),\n onReissue: () => record(\"command.reissue\", status, operation),\n });\n\n const handle: DependencyHandle = {\n async connect(signal) {\n record(\"status.connecting\", \"connecting\");\n const c = await create(mongoOptions(name, spec));\n driver = c;\n for (const [event, listener] of listeners) c.on(event, listener as never);\n c.on(\"error\", onError);\n if (signal.aborted) throw new Error(`${name}: the service stopped before the connect`);\n // the driver waits for a server in client_retry_window. The startup waits connect_bound at most, and the connect goes on.\n // A server that is down does not stop the startup: the checks keep readiness tripped until a server is writable\n const connecting = c.connect().then(\n () => undefined,\n (error: unknown) => {\n if (!closing() && status !== \"ready\") record(\"connect.failed\", \"failing\", message(error));\n },\n );\n await withinBound(clock, ms(knobs.connect_bound), connecting);\n if (status === \"connecting\") record(\"connect.failed\", \"failing\", \"no writable server in connect_bound\");\n },\n async close() {\n const c = driver;\n if (c === null || closing()) return;\n record(\"close.request\", \"closing\");\n // the close of the driver fails the commands in flight, then waits for endSessions. Behind a\n // black hole that wait has no bound. The driver gives no socket, thus the bound cannot destroy one\n if (!(await withinBound(clock, ms(knobs.close_bound), c.close()))) record(\"close.bound\", status, \"the close of the driver goes on\");\n flight.settleAll(\"the client closed with the command in flight\");\n for (const [event, listener] of listeners) c.off(event, listener as never);\n // the error listener stays: a socket that closes later can emit an error, and with no listener it throws\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 statement_timeout: knobs.statement_timeout,\n heartbeat_interval: knobs.heartbeat_interval,\n client_retry_window: knobs.client_retry_window,\n // the stream of the monitor times out at connectTimeoutMS + heartbeatFrequencyMS: the driver then\n // marks the server unknown, clears its pool and fails the commands in flight. The cells measured 7 s for 5 s + 2 s\n driver_writeoff_time: { value: (ms(knobs.connect_bound) + ms(knobs.heartbeat_interval)) / 1000, unit: \"s\" },\n checkout_timeout: knobs.checkout_timeout,\n pool_idle_expiry: knobs.pool_idle_expiry,\n keepalive_idle: knobs.keepalive_idle,\n // the driver sends nothing on an idle connection of the pool, 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<MongoStep>) =>\n stepChecks<MongoStep>(name, checkOptions, {\n // the view of the topology, without a round trip: the monitor keeps it current\n \"local-state\": localStateCheck(() => status === \"ready\"),\n // a ping through the pool of the application\n \"round-trip\": roundTripCheck((sendOptions) => command(\"check\", { ping: 1 }, sendOptions)),\n // a new connection each time, with a hello and no session\n dial: dialCheck(dial, spec.host, spec.port),\n });\n\n return {\n name,\n declaration,\n handle,\n meets: mongoMeetings(name, knobs),\n get status() {\n return status;\n },\n joins,\n command,\n checks,\n attach: (svc, checkOptions) => attachClient(svc, name, declaration, handle, checks(checkOptions)),\n problems(description) {\n // O07: the server must end a slow command first, with a certain error\n const late = deadlineProblem(name, knobs.request_timeout, knobs.statement_timeout, \"command\");\n return [...clientProblems(name, description, clientBounds(knobs)), ...(late === null ? [] : [late])];\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 driver exists and serves. */\nconst SERVING = [\"connecting\", \"ready\", \"failing\"] as const;\nconst OPEN = [...SERVING, \"closing\"] as const;\n\n/**\n * The MongoDB client of the service as a `mark1russell7.machine/1` document. It is the topology as\n * the wrapper sees it, not one connection: the document `mongodb-node` of `@mark1russell7/machines`\n * gives the monitor and each connection. The state is the view of the topology:\n * - `ready`: a server is writable: a primary, a standalone, a mongos, or a load balancer.\n * - `failing`: no server is writable, or the startup found none in `connect_bound`. The monitor\n * goes on, and a command waits for a writable server in `client_retry_window`.\n *\n * A connection that closes and a pool that the driver clears do not change the state by themselves.\n * The write-off of a server changes the view of the topology, and that changes the state. A test\n * records the transitions of the client and replays them against this document.\n */\nexport const MONGO_CLIENT_MACHINE: Machine = {\n format: \"mark1russell7.machine/1\",\n id: \"service.machine.mongodb-client\",\n title: \"The MongoDB client of the service, over a MongoClient of the driver 7\",\n layer: \"driver\",\n version: \"0.1.0\",\n sources: [{ ref: \"packages/service/src/mongodb (the client implements this document)\", rung: \"implemented\" }],\n states: {\n none: { initial: true, note: \"the client exists, and its MongoClient does not\" },\n connecting: { note: \"the monitor looks for the servers of the topology\" },\n ready: { note: \"a server is writable. A dead idle connection stays hidden until the monitor writes the server off\" },\n failing: { note: \"no server is writable. A command waits for one in client_retry_window\" },\n closing: { note: \"the close of the driver: it fails the commands in flight, sends endSessions, and closes the topology\" },\n end: { final: true, note: \"the service closed the client\" },\n },\n events: {\n \"status.connecting\": { kind: \"local\" },\n \"topology.writable\": { kind: \"rx\", note: \"the view of the topology has a writable server\" },\n \"topology.unwritable\": { kind: \"rx\", note: \"the view of the topology has no writable server, for example after a write-off or a step down\" },\n join: { kind: \"rx\", note: \"a connection of the pool finished its handshake (D1). The id of the server comes with its first command\" },\n \"connect.failed\": { kind: \"local\", note: \"the startup found no writable server in connect_bound, or the connect of the driver failed\" },\n \"pool.cleared\": { kind: \"rx\", note: \"the driver cleared the pool of a server, and with a write-off also failed its commands in flight\" },\n \"heartbeat.failed\": { kind: \"rx\", note: \"a hello of the monitor failed\" },\n \"client.error\": { kind: \"rx\", note: \"an error event of the driver\" },\n \"command.reissue\": { kind: \"local\" },\n \"close.request\": { kind: \"local\" },\n \"close.bound\": { kind: \"timer\", note: \"the close of the driver goes on after the bound, in the background\" },\n \"close.destroy\": { kind: \"local\", note: \"the client never records it: the driver gives no socket that the bound can destroy\" },\n \"status.end\": { kind: \"local\" },\n },\n knobs: {\n connect_bound: knob(\"connectTimeoutMS\"),\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: [\"topology.writable\", \"connect.failed\"],\n fires: \"connect.failed\",\n note: \"the startup stops its wait, and the connect of the driver goes on in client_retry_window\",\n },\n close: closeTimer(),\n },\n transitions: [\n t(\"connect\", \"none\", \"status.connecting\", \"connecting\"),\n t(\"writable\", [\"connecting\", \"failing\"], \"topology.writable\", \"ready\"),\n t(\"unwritable\", \"ready\", \"topology.unwritable\", \"failing\"),\n t(\"connect-failed\", [\"connecting\", \"failing\"], \"connect.failed\", \"failing\", { timer: \"connect_timeout\" }),\n ...OPEN.map((state) => t(`join-${state}`, state, \"join\", state)),\n ...OPEN.map((state) => t(`pool-cleared-${state}`, state, \"pool.cleared\", state)),\n ...OPEN.map((state) => t(`heartbeat-failed-${state}`, state, \"heartbeat.failed\", state)),\n ...OPEN.map((state) => t(`client-error-${state}`, state, \"client.error\", 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(\"end\", \"closing\", \"status.end\", \"end\"),\n ],\n};\n","import { STARTING_POLL } from \"../client/core.ts\";\nimport type { Poll } from \"../timing.ts\";\nimport type { MongoKnobs } from \"./client.ts\";\n\n/**\n * This poll is the poll of the checks that `MONGO_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 MONGO_STARTING_POLL: Poll = STARTING_POLL;\n\n/**\n * These knobs are a declaration to start from, from what the cells of S1 measured on the driver\n * 7.7.0 on Node 24.21.0 against mongod 9.0.4. The runtime has no defaults: a service gives these\n * knobs, or its own. Each value holds the borders that the cells measured:\n * - `connect_bound` 5 s and `heartbeat_interval` 2 s: the write-off of a silent server came 7 s after\n * its last answer, and the cells T01.border-off.75 and .76 held. At the defaults it came at 40.002 s.\n * - `request_timeout` 9.5 s: in the slowest poll of 10 s (O13). At the defaults the driver has no\n * bound on a read, and only the write-off ends the command.\n * - `statement_timeout` 9 s: below `request_timeout`, thus the server ends a slow command first,\n * with error 50 (O07).\n * - `client_retry_window` 30 s: after an election, the first write took 17 s to 18 s, and the band\n * ended at 26.58 s (T11.defaults.10). The default of the driver, 30 s, holds that band.\n * - `checkout_timeout` 5 s: a full pool fails a checkout in a startup budget. At the defaults the wait has no bound.\n * - `pool_idle_expiry` 10 s: no cell measured it. It is the bound of O03 and O01 that the pg client\n * uses: below a router that forgets an idle flow at 12 s. The driver applies it at the next checkout.\n * - `keepalive_idle` 30 s: the driver always turns keepalive on, at 120 s by default. This value\n * refreshes a held idle connection through the NAT forms of the cells (240 s and 350 s).\n * - `max_connection_age` \"infinite\": the driver has no age for a connection.\n * - `close_bound` 2 s: behind a black hole the close of the driver waits for `endSessions`.\n * - `retrans_giveup_time` 924.6 s: the give-up of Linux at `tcp_retries2 = 15`. The cells measured 12.936 s at `tcp_retries2 = 5`.\n */\nexport const MONGO_STARTING_KNOBS: MongoKnobs = {\n connect_bound: { value: 5, unit: \"s\" },\n request_timeout: { value: 9.5, unit: \"s\" },\n statement_timeout: { value: 9, unit: \"s\" },\n heartbeat_interval: { value: 2, unit: \"s\" },\n client_retry_window: { value: 30, unit: \"s\" },\n checkout_timeout: { value: 5, unit: \"s\" },\n pool_idle_expiry: { value: 10, unit: \"s\" },\n keepalive_idle: { value: 30, 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":";;;;;;;;;AAuGA,MAAa,YAA0B,OAAO,EAAE,KAAK,cAAc;CAKjE,OAAO,KAAI,OAHW,OAAO,YAAA,CAGX,YAAY,KAAK,OAAO;AAC5C;;;;;;;;;;;;;;;AC3FA,MAAa,iBAAiB,MAAc,UAAiC;CAC3E,MAAM,eAAe,UAAU,GAAG,MAAM,aAAa,IAAI,GAAG,MAAM,kBAAkB,KAAK,KAAM,GAAG;CAClG,MAAM,YAAY,eAA+C;EAC/D,IAAI;EACJ,QAAQ;EACR;EACA,QACE,cAAc,cACV,yFAAyF,KAAK,oBAC9F,kFAAkF,KAAK;CAC/F;CACA,MAAM,OAAiB;EACrB,IAAI;EACJ,QAAQ,MAAM;EACd,WAAW;EACX,QAAQ;CACV;CACA,MAAM,aAAuB;EAC3B,IAAI;EACJ,QAAQ,MAAM;EACd,WAAW;EACX,QAAQ;CACV;CACA,MAAM,SAAS,aAAa,MAAM,qBAAqB,oCAAoC;CAC3F,MAAM,UAAU,mBAAyD;EACvE;EACA,MAAM,KAAK;EACX,WAAW;GAAC,SAAS,WAAW;GAAG;GAAM;GAAQ,SAAS,MAAM;GAAG;GAAY,gBAAgB,MAAM,cAAc;EAAC;CACtH;CACA,OAAO;EACL;GACE,eAAe;GACf,MAAM,KAAK;GACX,WAAW;IACT,UAAU,aAAa,mEAAmE;IAC1F;KACE,IAAI;KACJ,QAAQ,MAAM;KACd,WAAW;KACX,QAAQ;IACV;IACA;IACA,eAAe,MAAM,cAAc;GACrC;EACF;EACA;GACE,eAAe;GACf,MAAM,KAAK;GACX,WAAW,CACT,UAAU,aAAa,0CAA0C,GACjE,UAAU,QAAQ,6FAA6F,CACjH;EACF;EACA,OAAO,WAAW;EAClB,OAAO,aAAa;EACpB;GACE,eAAe;GACf,MAAM,KAAK;GACX,WAAW;IAAC,SAAS,WAAW;IAAG;IAAM,SAAS,MAAM;IAAG;IAAY,kBAAkB;GAAC;EAC5F;EACA;GACE,eAAe;GACf,MAAM;GACN,WAAW;IACT;KACE,IAAI;KACJ,QAAQ,MAAM;KACd,WAAW;KACX,QAAQ;IACV;IACA;IACA;IACA,kBAAkB;GACpB;EACF;CACF;AACF;;;;ACoCA,MAAM,qBAAqB;;AAG3B,MAAa,gBAAgB,MAAc,SAAoC;CAC7E,MAAM,EAAE,UAAU;CAClB,MAAM,WAAW;EACf,GAAG,aAAa,MAAM,KAAK;EAE3B,GAAG,aAAa,MAAM,cAAc,OAAO;GACzC,SAAS;IAAC;IAAiB;IAAmB;IAAsB;IAAuB;GAAa;GACxG,MAAM;IAAC;IAAqB;IAAoB;GAAkB;EACpE,CAAC;EAED,GAAG,cAAc,MAAM,OAAO;GAC5B,QAAQ;IAAC;IAAiB;IAAmB;IAAqB;IAAsB;IAAuB;IAAoB;GAAkB;GACrJ,SAAS,CAAC,gBAAgB;EAC5B,CAAC;EACD,GAAG,aAAa,MAAM,KAAK,IAAI;CACjC;CACA,IAAI,GAAG,MAAM,kBAAkB,IAAI,oBACjC,SAAS,KAAK,GAAG,KAAK,uEAAuE;CAC/F,SAAS,KAAK,GAAG,oBAAoB,MAAM,OAAO,YAAY,GAAG,GAAG,YAAY,MAAM,OAAO,wCAAwC,CAAC;CACtI,OAAO;AACT;;AAGA,MAAa,gBAAgB,MAAc,SAA+C;CACxF,MAAM,EAAE,UAAU;CAElB,OAAO;EACL,KAAK,aAFO,CAAC;GAAE,MAAM,KAAK;GAAM,MAAM,KAAK;EAAK,GAAG,GAAI,KAAK,SAAS,CAAC,CAAE,CAAC,CAAC,KAAK,SAAS,GAAG,KAAK,KAAK,GAAG,KAAK,MAEvF,CAAC,CAAC,KAAK,GAAG,EAAE;EAClC,SAAS;GACP,SAAS;GACT,GAAI,KAAK,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,KAAK,WAAW;GACvE,GAAI,KAAK,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM;IAAE,UAAU,KAAK;IAAU,UAAU,KAAK,YAAY;GAAG,EAAE;GAC1G,GAAI,KAAK,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,KAAK,WAAW;GACvE,aAAa,KAAK,KAAK;GACvB,aAAa,KAAK,KAAK;GACvB,kBAAkB,GAAG,MAAM,aAAa;GACxC,iBAAiB,GAAG,MAAM,eAAe;GACzC,0BAA0B,GAAG,MAAM,mBAAmB;GACtD,sBAAsB,GAAG,MAAM,kBAAkB;GACjD,oBAAoB,OAAO,MAAM,gBAAgB;GACjD,eAAe,OAAO,MAAM,gBAAgB;GAC5C,uBAAuB,GAAG,MAAM,cAAc;GAC9C,sBAAsB;GACtB,YAAY;GACZ,aAAa;GACb,iBAAiB;EACnB;CACF;AACF;;;;;;AAOA,MAAa,QAAoB,WAAW,GAE1C,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,KAAM,GAAM,GAAG,GAEpD,GAAG,GAAG,GAAG,GAAG,GAEZ,IAAI,GAAG,GAAG,GACV,IAAM,KAAM,KAAM,KAAM,KAAM,KAAM,GAAG,GAAG,GAAG,GAAG,GAChD,GAAM,IAAM,KAAM,IAAM,GAAG,GAAG,GAAG,GAAG,GAAG,IAAM,KAAM,KAAM,KAAM,KAAM,GACrE,CACF;;AAGA,MAAM,QAA0C;CAAE,GAAM;CAAG,GAAM;CAAI,GAAM;CAAG,GAAM;CAAG,IAAM;CAAG,IAAM;CAAG,IAAM;CAAG,IAAM;CAAG,IAAM;AAAG;;AAGpI,MAAM,QAAQ,QAAgC;CAC5C,IAAI,KAAK;CACT,OAAO,KAAK,IAAI,SAAS,GAAG;EAC1B,MAAM,OAAO,IAAI;EACjB,MAAM,MAAM,IAAI,QAAQ,GAAG,KAAK,CAAC;EACjC,IAAI,MAAM,GAAG,uBAAO,IAAI,MAAM,yCAAyC;EACvE,MAAM,MAAM,IAAI,SAAS,UAAU,KAAK,GAAG,GAAG;EAC9C,KAAK,MAAM;EACX,IAAI,QAAQ,MAAM;GAChB,IAAI,SAAS,GAAM,OAAO,IAAI,aAAa,EAAE;GAC7C,IAAI,SAAS,IAAM,OAAO,IAAI,YAAY,EAAE;GAC5C,uBAAO,IAAI,MAAM,+CAA+C,MAAM;EACxE;EACA,MAAM,QAAQ,MAAM;EACpB,IAAI,UAAU,KAAA,GAAW,MAAM;OAC1B,IAAI,SAAS,GAAM,MAAM,IAAI,IAAI,YAAY,EAAE;OAC/C,IAAI,SAAS,KAAQ,SAAS,GAAM,MAAM,IAAI,YAAY,EAAE;OAC5D,IAAI,SAAS,GAAM,MAAM,IAAI,IAAI,YAAY,EAAE;OAC/C,uBAAO,IAAI,MAAM,qCAAqC,KAAK,+BAA+B;CACjG;CACA,uBAAO,IAAI,MAAM,2BAA2B;AAC9C;;;;;AAMA,MAAa,cAAqB,aAAa;CAC7C,IAAI,SAAS,SAAS,GAAG,OAAO;CAChC,MAAM,SAAS,SAAS,YAAY,CAAC;CACrC,IAAI,SAAS,MAAM,SAAS,UAAkB,uBAAO,IAAI,MAAM,2CAA2C,QAAQ;CAClH,IAAI,SAAS,SAAS,QAAQ,OAAO;CACrC,MAAM,SAAS,SAAS,YAAY,EAAE;CACtC,IAAI,WAAW,MAAM,uBAAO,IAAI,MAAM,2CAA2C,OAAO,WAAW;CACnG,IAAI,SAAS,QAAQ,GAAG,uBAAO,IAAI,MAAM,kDAAkD;CAC3F,MAAM,KAAK,KAAK,SAAS,SAAS,IAAI,MAAM,CAAC;CAC7C,IAAI,cAAc,OAAO,OAAO;CAChC,OAAO,OAAO,IAAI,uBAAO,IAAI,MAAM,0BAA0B,IAAI;AACnE;;AAGA,MAAa,mBAA2B,UAAU,OAAO,UAAU;;AAGnE,MAAM,2BAAW,IAAI,IAAI;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;AAMD,MAAM,8BAAc,IAAI,IAAI;CAAC;CAAI;CAAK;CAAO;AAAK,CAAC;;AAGnD,MAAM,2BAAW,IAAI,IAAI;CAAC;CAAa;CAAc;CAAU;AAAc,CAAC;;AAG9E,MAAM,gBAAgB,UAAiD;CACrE,MAAM,UAAU,MAAM;CACtB,IAAI,YAAY,KAAA,KAAa,YAAY,MAAM,OAAO;EAAE,SAAS;EAAM,OAAO;CAAM;CACpF,OAAO;EAAE,SAAS;EAAiB,OAAO,iBAAiB,OAAO,QAAQ,IAAI,EAAE,GAAG,OAAO,QAAQ,MAAM;CAAI;AAC9G;;;;;;;;AASA,MAAa,kBAAkB,UAAmC;CAChE,MAAM,OAAO,QAAQ,KAAK;CAC1B,MAAM,EAAE,MAAM,MAAM,oBAAqB,OAAO,UAAU,YAAY,UAAU,OAAO,QAAQ,CAAC;CAKhG,IAAI,OAAO,SAAS,YAAY,SAAS,IAAI,IAAI,GAAG,OAAO,QAAQ,IAAI;CAEvE,IAAI,SAAS,uBAAuB,oBAAoB,MAAM,OAAO,QAAQ,IAAI;CACjF,IAAI,SAAS,sBAAsB,OAAO,SAAS,UAAU;EAC3D,MAAM,OAAO,GAAG,KAAK,GAAG;EACxB,OAAO,YAAY,IAAI,IAAI,IAAI;GAAE,SAAS;GAAiB,OAAO;EAAK,IAAI;GAAE,SAAS;GAAS,OAAO;GAAM,OAAO;EAAK;CAC1H;CACA,OAAO;EAAE,SAAS;EAAiB,OAAO;CAAK;AACjD;;AAGA,MAAM,UAAU,YAAoF;CAClG,MAAM,KAAK,QAAQ,YAAY,GAAG;CAClC,MAAM,OAAO,OAAO,QAAQ,MAAM,KAAK,CAAC,CAAC;CACzC,OAAO,KAAK,KAAK,CAAC,OAAO,UAAU,IAAI,IAAI;EAAE,MAAM;EAAS,MAAM;CAAK,IAAI;EAAE,MAAM,QAAQ,MAAM,GAAG,EAAE;EAAG;CAAK;AAChH;;;;;;;;;;;;;;AAeA,MAAa,eAAe,MAAc,MAAuB,UAA8B,CAAC,MAAmB;CACjH,MAAM,WAAW,aAAa,MAAM,IAAI;CACxC,IAAI,SAAS,SAAS,GAAG,MAAM,IAAI,MAAM,SAAS,KAAK,IAAI,CAAC;CAC5D,MAAM,EAAE,UAAU;CAClB,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,aAAa,eAAe,CAAC,GAAG,KAAK,UAAU;CACrD,MAAM,WAAW,SAAS,MAAM,iBAAiB;CAEjD,MAAM,YAAY,GAAG,MAAM,mBAAmB,IAAI,GAAG,MAAM,eAAe;CAE1E,IAAI,SAAmC;CACvC,IAAI,SAAS;CACb,MAAM,QAAqB,CAAC;CAE5B,MAAM,yBAAS,IAAI,IAAoB;CACvC,MAAM,SAAS,SAAS;CAExB,MAAM,OAAO,eAAe,QAAQ,QAAQ,OAAO,IAAI;CACvD,MAAM,UAAU,IAAY,KAAa,QAAQ,WAA0B;EACzE,MAAM,OAAO;EACb,SAAS;EACT,KAAK,MAAM,IAAI,IAAI,MAAM;CAC3B;CACA,MAAM,gBAAyB,WAAW,aAAa,WAAW;CAGlE,MAAM,cAAc,UAA2D;EAC7E,MAAM,WAAW,CAAC,GAAG,MAAM,eAAe,QAAQ,OAAO,CAAC,CAAC,CAAC,MAAM,WAAW,SAAS,IAAI,OAAO,IAAI,CAAC;EACtG,IAAI,aAAa,WAAW,gBAAgB,WAAW,YAAY,OAAO,qBAAqB,OAAO;OACjG,IAAI,CAAC,YAAY,WAAW,SAAS,OAAO,uBAAuB,SAAS;CACnF;CACA,MAAM,WAAW,UAAgD;EAC/D,MAAM,aAAa,MAAM,SAAS;EAClC,MAAM,EAAE,MAAM,SAAS,OAAO,MAAM,OAAO;EAC3C,OAAO,IAAI,GAAG,MAAM,QAAQ,GAAG,OAAO,MAAM,YAAY,KAAK,MAAM,MAAM;EACzE,MAAM,KAAK;GACT;GACA,IAAI,MAAM,IAAI;GACd,SAAS,MAAM;GACf,oBAAoB;GACpB,cAAc;GACd,WAAW;GACX,eAAe;GACf,YAAY;EACd,CAAC;EACD,OAAO,QAAQ,QAAQ,cAAc,WAAW,IAAI,MAAM,SAAS;CACrE;CAEA,MAAM,aAAa,UAA+C;EAChE,MAAM,QAAQ,OAAO,IAAI,GAAG,MAAM,QAAQ,GAAG,OAAO,MAAM,YAAY,GAAG;EACzE,MAAM,KAAK,MAAM;EACjB,IAAI,UAAU,KAAA,KAAa,OAAO,QAAQ,OAAO,KAAA,GAAW;EAC5D,OAAO,OAAO,GAAG,MAAM,QAAQ,GAAG,OAAO,MAAM,YAAY,GAAG;EAC9D,MAAM,SAAS;GAAE,GAAG,MAAM;GAAS,oBAAoB,OAAO,EAAE;EAAE;CACpE;CACA,MAAM,aAAa,UACjB,OAAO,gBAAgB,QAAQ,GAAG,MAAM,UAAU,MAAM,8BAA8B,OAAO,kCAAkC,IAAI;CACrI,MAAM,eAAe,UACnB,OAAO,oBAAoB,QAAQ,GAAG,MAAM,aAAa,IAAI,QAAQ,MAAM,OAAO,GAAG;CAEvF,MAAM,WAAW,UAAuB;EACtC,IAAI,WAAW,OAAO,OAAO,gBAAgB,QAAQ,QAAQ,KAAK,CAAC;CACrE;CACA,MAAM,YAAgF;EACpF,CAAC,8BAA8B,UAAU;EACzC,CAAC,mBAAmB,OAAO;EAC3B,CAAC,kBAAkB,SAAS;EAC5B,CAAC,yBAAyB,SAAS;EACnC,CAAC,yBAAyB,WAAW;CACvC;;CAGA,MAAM,WAAW,UAAyB,WAAqE;EAC7G,MAAM,IAAI;EACV,IAAI,MAAM,MAAM,OAAO,QAAQ,QAAQ,QAAQ,GAAG,KAAK,mBAAmB,CAAC;EAC3E,IAAI,QAAQ,GAAG,OAAO,QAAQ,QAAQ,QAAQ,GAAG,KAAK,MAAM,OAAO,uCAAuC,CAAC;EAC3G,IAAI,QAAQ,SAAS,OAAO,QAAQ,QAAQ,QAAQ,0DAA0D,CAAC;EAE/G,MAAM,OAAO,aAAa,KAAA,KAAa,eAAe,WAAW,WAAW;GAAE,GAAG;GAAU,WAAW;EAAS;EAC/G,OAAO,OAAO,MAAM,QAAQ,iEAAiE,WAAW;GACtG,EAAE,GAAG,KAAK,QAAQ,CAAC,CAChB,QAAQ,MAAM,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CACrD,MACE,UAAU,OAAO,aAAa,KAAK,CAAC,IACpC,UAAmB,OAAO,eAAe,KAAK,CAAC,CAClD;EACJ,CAAC;CACH;CAEA,MAAM,WAAW,WAAmB,UAAyB,cAA2B,CAAC,MACvF,gBAAgB;EACd;EACA,OAAO;EACP,WAAW,WAAW;EACtB,SAAS;EAET,UAAU,WAAW,QAAQ,UAAU,MAAM;EAC7C,iBAAiB,OAAO,mBAAmB,QAAQ,SAAS;CAC9D,CAAC;CAEH,MAAM,SAA2B;EAC/B,MAAM,QAAQ,QAAQ;GACpB,OAAO,qBAAqB,YAAY;GACxC,MAAM,IAAI,MAAM,OAAO,aAAa,MAAM,IAAI,CAAC;GAC/C,SAAS;GACT,KAAK,MAAM,CAAC,OAAO,aAAa,WAAW,EAAE,GAAG,OAAO,QAAiB;GACxE,EAAE,GAAG,SAAS,OAAO;GACrB,IAAI,OAAO,SAAS,MAAM,IAAI,MAAM,GAAG,KAAK,yCAAyC;GAGrF,MAAM,aAAa,EAAE,QAAQ,CAAC,CAAC,WACvB,KAAA,IACL,UAAmB;IAClB,IAAI,CAAC,QAAQ,KAAK,WAAW,SAAS,OAAO,kBAAkB,WAAW,QAAQ,KAAK,CAAC;GAC1F,CACF;GACA,MAAM,YAAY,OAAO,GAAG,MAAM,aAAa,GAAG,UAAU;GAC5D,IAAI,WAAW,cAAc,OAAO,kBAAkB,WAAW,qCAAqC;EACxG;EACA,MAAM,QAAQ;GACZ,MAAM,IAAI;GACV,IAAI,MAAM,QAAQ,QAAQ,GAAG;GAC7B,OAAO,iBAAiB,SAAS;GAGjC,IAAI,CAAE,MAAM,YAAY,OAAO,GAAG,MAAM,WAAW,GAAG,EAAE,MAAM,CAAC,GAAI,OAAO,eAAe,QAAQ,iCAAiC;GAClI,OAAO,UAAU,8CAA8C;GAC/D,KAAK,MAAM,CAAC,OAAO,aAAa,WAAW,EAAE,IAAI,OAAO,QAAiB;GAEzE,OAAO,cAAc,KAAK;EAC5B;CACF;CAEA,MAAM,cAAwC;EAC5C,QAAQ,KAAK;EACb,OAAO;GACL,eAAe,MAAM;GACrB,iBAAiB,MAAM;GACvB,mBAAmB,MAAM;GACzB,oBAAoB,MAAM;GAC1B,qBAAqB,MAAM;GAG3B,sBAAsB;IAAE,QAAQ,GAAG,MAAM,aAAa,IAAI,GAAG,MAAM,kBAAkB,KAAK;IAAM,MAAM;GAAI;GAC1G,kBAAkB,MAAM;GACxB,kBAAkB,MAAM;GACxB,gBAAgB,MAAM;GAEtB,uBAAuB,MAAM;GAC7B,oBAAoB,MAAM;GAC1B,aAAa,MAAM;GACnB,qBAAqB,MAAM;EAC7B;EACA;CACF;CAEA,MAAM,UAAU,iBACd,WAAsB,MAAM,cAAc;EAExC,eAAe,sBAAsB,WAAW,OAAO;EAEvD,cAAc,gBAAgB,gBAAgB,QAAQ,SAAS,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC;EAExF,MAAM,UAAU,MAAM,KAAK,MAAM,KAAK,IAAI;CAC5C,CAAC;CAEH,OAAO;EACL;EACA;EACA;EACA,OAAO,cAAc,MAAM,KAAK;EAChC,IAAI,SAAS;GACX,OAAO;EACT;EACA;EACA;EACA;EACA,SAAS,KAAK,iBAAiB,aAAa,KAAK,MAAM,aAAa,QAAQ,OAAO,YAAY,CAAC;EAChG,SAAS,aAAa;GAEpB,MAAM,OAAO,gBAAgB,MAAM,MAAM,iBAAiB,MAAM,mBAAmB,SAAS;GAC5F,OAAO,CAAC,GAAG,eAAe,MAAM,aAAa,aAAa,KAAK,CAAC,GAAG,GAAI,SAAS,OAAO,CAAC,IAAI,CAAC,IAAI,CAAE;EACrG;CACF;AACF;;;;ACrfA,MAAM,UAAU;CAAC;CAAc;CAAS;AAAS;AACjD,MAAM,OAAO,CAAC,GAAG,SAAS,SAAS;;;;;;;;;;;;;AAcnC,MAAa,uBAAgC;CAC3C,QAAQ;CACR,IAAI;CACJ,OAAO;CACP,OAAO;CACP,SAAS;CACT,SAAS,CAAC;EAAE,KAAK;EAAsE,MAAM;CAAc,CAAC;CAC5G,QAAQ;EACN,MAAM;GAAE,SAAS;GAAM,MAAM;EAAkD;EAC/E,YAAY,EAAE,MAAM,oDAAoD;EACxE,OAAO,EAAE,MAAM,oGAAoG;EACnH,SAAS,EAAE,MAAM,wEAAwE;EACzF,SAAS,EAAE,MAAM,uGAAuG;EACxH,KAAK;GAAE,OAAO;GAAM,MAAM;EAAgC;CAC5D;CACA,QAAQ;EACN,qBAAqB,EAAE,MAAM,QAAQ;EACrC,qBAAqB;GAAE,MAAM;GAAM,MAAM;EAAiD;EAC1F,uBAAuB;GAAE,MAAM;GAAM,MAAM;EAAgG;EAC3I,MAAM;GAAE,MAAM;GAAM,MAAM;EAA0G;EACpI,kBAAkB;GAAE,MAAM;GAAS,MAAM;EAA6F;EACtI,gBAAgB;GAAE,MAAM;GAAM,MAAM;EAAmG;EACvI,oBAAoB;GAAE,MAAM;GAAM,MAAM;EAAgC;EACxE,gBAAgB;GAAE,MAAM;GAAM,MAAM;EAA+B;EACnE,mBAAmB,EAAE,MAAM,QAAQ;EACnC,iBAAiB,EAAE,MAAM,QAAQ;EACjC,eAAe;GAAE,MAAM;GAAS,MAAM;EAAqE;EAC3G,iBAAiB;GAAE,MAAM;GAAS,MAAM;EAAqF;EAC7H,cAAc,EAAE,MAAM,QAAQ;CAChC;CACA,OAAO;EACL,eAAeA,WAAK,kBAAkB;EACtC,aAAaA,WAAK,aAAa;CACjC;CACA,QAAQ;EACN,iBAAiB;GACf,UAAU;IAAE,MAAM;IAAiB,MAAM;GAAI;GAC7C,MAAM,CAAC,kBAAkB;GACzB,SAAS,CAAC,qBAAqB,gBAAgB;GAC/C,OAAO;GACP,MAAM;EACR;EACA,OAAO,WAAW;CACpB;CACA,aAAa;EACXC,WAAE,WAAW,QAAQ,qBAAqB,YAAY;EACtDA,WAAE,YAAY,CAAC,cAAc,SAAS,GAAG,qBAAqB,OAAO;EACrEA,WAAE,cAAc,SAAS,uBAAuB,SAAS;EACzDA,WAAE,kBAAkB,CAAC,cAAc,SAAS,GAAG,kBAAkB,WAAW,EAAE,OAAO,kBAAkB,CAAC;EACxG,GAAG,KAAK,KAAK,UAAUA,WAAE,QAAQ,SAAS,OAAO,QAAQ,KAAK,CAAC;EAC/D,GAAG,KAAK,KAAK,UAAUA,WAAE,gBAAgB,SAAS,OAAO,gBAAgB,KAAK,CAAC;EAC/E,GAAG,KAAK,KAAK,UAAUA,WAAE,oBAAoB,SAAS,OAAO,oBAAoB,KAAK,CAAC;EACvF,GAAG,KAAK,KAAK,UAAUA,WAAE,gBAAgB,SAAS,OAAO,gBAAgB,KAAK,CAAC;EAC/E,GAAG,KAAK,KAAK,UAAUA,WAAE,WAAW,SAAS,OAAO,mBAAmB,KAAK,CAAC;EAC7EA,WAAE,SAAS,CAAC,GAAG,OAAO,GAAG,iBAAiB,SAAS;EACnDA,WAAE,eAAe,WAAW,eAAe,WAAW,EAAE,OAAO,QAAQ,CAAC;EACxEA,WAAE,OAAO,WAAW,cAAc,KAAK;CACzC;AACF;;;;;;;ACrEA,MAAa,sBAA4B;;;;;;;;;;;;;;;;;;;;;;AAuBzC,MAAa,uBAAmC;CAC9C,eAAe;EAAE,OAAO;EAAG,MAAM;CAAI;CACrC,iBAAiB;EAAE,OAAO;EAAK,MAAM;CAAI;CACzC,mBAAmB;EAAE,OAAO;EAAG,MAAM;CAAI;CACzC,oBAAoB;EAAE,OAAO;EAAG,MAAM;CAAI;CAC1C,qBAAqB;EAAE,OAAO;EAAI,MAAM;CAAI;CAC5C,kBAAkB;EAAE,OAAO;EAAG,MAAM;CAAI;CACxC,kBAAkB;EAAE,OAAO;EAAI,MAAM;CAAI;CACzC,gBAAgB;EAAE,OAAO;EAAI,MAAM;CAAI;CACvC,oBAAoB;EAAE,OAAO;EAAY,MAAM;CAAI;CACnD,aAAa;EAAE,OAAO;EAAG,MAAM;CAAI;CACnC,qBAAqB;EAAE,OAAO;EAAO,MAAM;CAAI;AACjD"}
package/build/pg.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Y as Poll, wt as Operation } from "./lifecycle-fgYfxrHK.js";
2
- import { _ as Manifestation, a as ClientKnobs, b as firstDetector, c as ClientSpec, d as Factory, f as STARTING_POLL, g as MANIFESTATIONS, h as Detector, i as ClientJoin, l as ClientStep, m as Sent, n as CheckOptions, o as ClientOptions, p as SendOptions, r as Client, s as ClientSocket, t as Attempt, u as DialFn, v as Meeting, x as unwatched, y as Situation } from "./core-C2wnF4mg.js";
2
+ 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, h as STARTING_POLL, i as ClientJoin, l as ClientStep, n as CheckOptions, o as ClientOptions, r as Client, s as ClientSocket, t as Attempt, u as DialFn, w as Manifestation } from "./core-BPLRDcUJ.js";
3
3
  import { Extent, Quantity } from "@mark1russell7/quantity";
4
4
  import { Machine } from "@mark1russell7/algebra";
5
5
  //#region src/pg/connection.d.ts
package/build/pg.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { realClock } from "./clock.js";
2
- import { A as keepaliveSilent, C as withinBound, D as immediate, E as firstDetector, M as unwatched, O as keepaliveAnswered, S as timeProblems, T as WHAT, _ as operationTable, a as STARTING_POLL, b as socketEnds, c as clientProblems, d as finiteMs, f as inFlight, g as notSent, h as ms, i as helloDial, j as kernelGiveUp, k as keepaliveReset, l as clientRecorder, m as message, n as closeTimer, o as attachClient, p as localStateCheck, r as transition, s as clientBounds, t as clientKnob, u as dialCheck, v as roundTripCheck, w as MANIFESTATIONS, x as stepChecks, y as sendWithReissue } from "./machine-Czfb6fV4.js";
3
- import { valueIn } from "@mark1russell7/quantity";
2
+ import { B as zeroProblems, C as notSent, E as poolProblems, F as timeProblems, G as immediate, J as keepaliveSilent, K as keepaliveAnswered, L as wholeProblems, M as sendWithReissue, N as socketEnds, P as stepChecks, R as withinBound, S as ms, T as orZero, U as WHAT, V as MANIFESTATIONS, W as firstDetector, X as unwatched, Y as kernelGiveUp, _ as inFlight, a as helloDial, b as message, d as clientProblems, f as clientRecorder, h as finiteMs, j as roundTripCheck, l as attachClient, m as dialCheck, n as closeTimer, p as deadlineProblem, q as keepaliveReset, r as transition, s as STARTING_POLL, t as clientKnob, u as clientBounds, w as operationTable, y as localStateCheck } from "./machine-BhTRu3u9.js";
4
3
  //#region src/pg/connection.ts
5
4
  /**
6
5
  * This factory makes a `pg.Pool`. It loads pg only when a client connects, thus a service that does
@@ -91,36 +90,35 @@ const pgMeetings = (name, knobs) => {
91
90
  };
92
91
  //#endregion
93
92
  //#region src/pg/client.ts
94
- const wholeMs = (amount) => amount.value === "infinite" || Number.isInteger(ms(amount));
95
- const wholeSeconds = (amount) => amount.value === "infinite" || Number.isInteger(valueIn(amount, "s"));
96
- /** This function writes a bound for pg: "infinite" is 0, which pg reads as no timer. */
97
- const orZero = (amount) => finiteMs(amount) ?? 0;
98
93
  /** This function gives the problems of a spec: the knobs that pg cannot use. */
99
94
  const specProblems = (name, spec) => {
100
- const { knobs, pool } = spec;
101
- const problems = timeProblems(name, knobs);
102
- for (const knob of [
103
- "connect_bound",
104
- "request_timeout",
105
- "close_bound"
106
- ]) if (!(ms(knobs[knob]) > 0) || !Number.isFinite(ms(knobs[knob]))) problems.push(`${name}'s ${knob} is not a finite time above zero: pg reads 0 as no bound`);
107
- for (const knob of [
108
- "statement_timeout",
109
- "pool_idle_expiry",
110
- "keepalive_idle",
111
- "max_connection_age"
112
- ]) if (knobs[knob].value !== "infinite" && !(ms(knobs[knob]) > 0)) problems.push(`${name}'s ${knob} is zero: give "infinite" for no bound`);
113
- for (const knob of [
114
- "connect_bound",
115
- "request_timeout",
116
- "statement_timeout",
117
- "pool_idle_expiry"
118
- ]) if (!wholeMs(knobs[knob])) problems.push(`${name}'s ${knob} is not a whole number of milliseconds`);
119
- if (!wholeSeconds(knobs.keepalive_idle)) problems.push(`${name}'s keepalive_idle is not a whole number of seconds`);
120
- if (!wholeSeconds(knobs.max_connection_age)) problems.push(`${name}'s max_connection_age is not a whole number of seconds`);
121
- if (!Number.isInteger(pool.max) || pool.max < 1) problems.push(`${name}'s pool has a max of ${pool.max}, not a whole number above zero`);
122
- if (!Number.isInteger(pool.min) || pool.min < 0 || pool.min > pool.max) problems.push(`${name}'s pool has a min of ${pool.min}, not a whole number from 0 to its max`);
123
- return problems;
95
+ const { knobs } = spec;
96
+ return [
97
+ ...timeProblems(name, knobs),
98
+ ...zeroProblems(name, "pg", knobs, {
99
+ bounded: [
100
+ "connect_bound",
101
+ "request_timeout",
102
+ "close_bound"
103
+ ],
104
+ open: [
105
+ "statement_timeout",
106
+ "pool_idle_expiry",
107
+ "keepalive_idle",
108
+ "max_connection_age"
109
+ ]
110
+ }),
111
+ ...wholeProblems(name, knobs, {
112
+ millis: [
113
+ "connect_bound",
114
+ "request_timeout",
115
+ "statement_timeout",
116
+ "pool_idle_expiry"
117
+ ],
118
+ seconds: ["keepalive_idle", "max_connection_age"]
119
+ }),
120
+ ...poolProblems(name, spec.pool)
121
+ ];
124
122
  };
125
123
  /** This function gives the options of `pg.Pool` from a spec. Each timer of pg comes from the declaration. */
126
124
  const poolOptions = (name, spec) => {
@@ -355,9 +353,8 @@ const pgClient = (name, spec, options = {}) => {
355
353
  checks,
356
354
  attach: (svc, checkOptions) => attachClient(svc, name, declaration, handle, checks(checkOptions)),
357
355
  problems(description) {
358
- const found = clientProblems(name, description, clientBounds(knobs));
359
- if (knobs.statement_timeout.value !== "infinite" && requestTimeout <= ms(knobs.statement_timeout)) found.push(`${name}'s request_timeout is not above its statement_timeout: the server goes on with a statement that the client gave up (O07)`);
360
- return found;
356
+ const deadline = deadlineProblem(name, knobs.request_timeout, knobs.statement_timeout, "statement");
357
+ return [...clientProblems(name, description, clientBounds(knobs)), ...deadline === null ? [] : [deadline]];
361
358
  }
362
359
  };
363
360
  };
package/build/pg.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pg.js","names":["knob","t"],"sources":["../src/pg/connection.ts","../src/pg/meets.ts","../src/pg/client.ts","../src/pg/machine.ts","../src/pg/starting.ts"],"sourcesContent":["import type { ClientSocket, Factory } from \"../client/core.ts\";\n\n/** A pg socket is the socket of a client in the core: the join of the prober and the bounded close read it. */\nexport type PgSocket = ClientSocket;\n\n/** A query result is the part of a result of pg that the wrapper gives back. */\nexport interface PgResult<Row = Record<string, unknown>> {\n readonly rows: Row[];\n readonly rowCount: number | null;\n}\n\n/**\n * A pool client is the part of a `pg.PoolClient` that the wrapper uses. A pg 8 client has each part.\n * A fake for tests has them too. Thus the package depends on no type of pg.\n */\nexport interface PgPoolClient {\n /** This number is the id of the backend from BackendKeyData, which the server sends at the startup. */\n readonly processID?: number | null | undefined;\n /** This connection holds the socket of the client. */\n readonly connection?: { readonly stream?: PgSocket | undefined } | undefined;\n query(text: string, values?: readonly unknown[]): Promise<PgResult<never>>;\n /** This method gives the client back to the pool. With an error, the pool ends the client. */\n release(error?: Error | boolean): void;\n on(event: \"error\", listener: (error: Error) => void): unknown;\n off(event: \"error\", listener: (error: Error) => void): unknown;\n}\n\n/** A pool is the part of a `pg.Pool` that the wrapper uses. */\nexport interface PgPool {\n readonly totalCount: number;\n readonly idleCount: number;\n readonly waitingCount: number;\n connect(): Promise<PgPoolClient>;\n end(): Promise<void>;\n on(event: \"connect\" | \"remove\", listener: (client: PgPoolClient) => void): unknown;\n on(event: \"error\", listener: (error: Error, client: PgPoolClient) => void): unknown;\n off(event: \"connect\" | \"remove\", listener: (client: PgPoolClient) => void): unknown;\n off(event: \"error\", listener: (error: Error, client: PgPoolClient) => void): unknown;\n}\n\n/**\n * These are the options that the wrapper gives `pg.Pool`. Each one comes from the declaration. The\n * wrapper sets each option that has an effect on a timer, thus no default of pg decides one. A knob\n * of pg at 0 has no timer. The wrapper writes \"infinite\" as 0, and it refuses a bound of 0.\n */\nexport interface PgPoolOptions {\n readonly host: string;\n readonly port: number;\n readonly database?: string | undefined;\n readonly user?: string | undefined;\n readonly password?: string | undefined;\n /** The server shows this name in `pg_stat_activity`, thus the prober can join on it too. */\n readonly application_name: string;\n readonly max: number;\n readonly min: number;\n /** This time bounds the TCP connect, the startup and the authentication. It also bounds a checkout from a full pool. */\n readonly connectionTimeoutMillis: number;\n /** This time bounds each query on the client, from `client.query`. The client sends no cancel at its end. */\n readonly query_timeout: number;\n /** This time is the deadline of each statement on the server. The client sends it in the startup message. Undefined does not send it. */\n readonly statement_timeout: number | undefined;\n /** This time ends an idle client of the pool. 0 is no idle timer. */\n readonly idleTimeoutMillis: number;\n /** This time ends a client at this age from its connect, in whole seconds. 0 is no age. */\n readonly maxLifetimeSeconds: number;\n readonly keepAlive: boolean;\n /** This time is the idle time of TCP keepalive. Node truncates it to whole seconds. */\n readonly keepAliveInitialDelayMillis: number;\n /** The idle timer and the idle sockets do not keep the process alive (A14). The lifecycle holds the process. */\n readonly allowExitOnIdle: true;\n}\n\n/** A pool factory makes the pool of the client from its options. */\nexport type PoolFactory = Factory<PgPoolOptions, PgPool>;\n\n/**\n * This factory makes a `pg.Pool`. It loads pg only when a client connects, thus a service that does\n * not use PostgreSQL does not need pg. The specifier is a value, thus the package needs no types of pg.\n */\nexport const nodePostgres: PoolFactory = async (options) => {\n const specifier = \"pg\";\n const loaded = (await import(specifier)) as { readonly default: { readonly Pool: new (options: PgPoolOptions) => PgPool } };\n return new loaded.default.Pool(options);\n};\n","import type { Detector, Meeting } from \"../client/meets.ts\";\nimport { WHAT, immediate, keepaliveAnswered, keepaliveReset, keepaliveSilent, kernelGiveUp } from \"../client/meets.ts\";\nimport type { PgKnobs } from \"./client.ts\";\n\n/**\n * This function gives the manifestations that a pg client can meet, and the detectors of each, from\n * its knobs. The cells of S1 measured each one on node-postgres 8.23.1:\n * - A reset of a query gives ECONNRESET in 0.6 ms, and a FIN to an idle client gives EOF in 18 µs.\n * - A frozen server and a black hole have no detector in 60 s at the defaults: pg has no timer on a query and no keepalive.\n * - `query_timeout` ends a query in a black hole on time (9.503 s for 9.5 s).\n * - `statement_timeout` ends a slow statement on the server with ERROR 57014 (10.002 s for 10 s).\n *\n * pg has no watchdog and does not test an idle client. In place of a watchdog, the pool ends an\n * idle client at its idle timer. A later checkout then opens a new connection, which `connect_bound` bounds.\n */\nexport const pgMeetings = (name: string, knobs: PgKnobs): Meeting[] => {\n const query: Detector = {\n by: \"request_timeout\",\n within: knobs.request_timeout,\n situation: \"in flight\",\n effect: `the query fails as indeterminate, and ${name} gives the client back with an error, thus the pool ends it`,\n };\n const idleExpiry: Detector = {\n by: \"pool_idle_expiry\",\n within: knobs.pool_idle_expiry,\n situation: \"idle\",\n effect: \"the pool ends the idle client, thus no checkout gets the dead connection\",\n };\n const giveUp = kernelGiveUp(knobs.retrans_giveup_time, \"the query fails with its error\");\n const silent = (manifestation: \"blackhole\" | \"freeze-host\"): Meeting => ({\n manifestation,\n what: WHAT[manifestation],\n detectors: [query, giveUp, idleExpiry, keepaliveSilent(knobs.keepalive_idle)],\n });\n return [\n {\n manifestation: \"rst\",\n what: WHAT.rst,\n detectors: [\n immediate(\"in flight\", \"the query fails with ECONNRESET, and each new connect is refused\"),\n idleExpiry,\n keepaliveReset(knobs.keepalive_idle),\n ],\n },\n {\n manifestation: \"fin\",\n what: WHAT.fin,\n detectors: [\n immediate(\"in flight\", \"the query fails: 'Connection terminated unexpectedly'\"),\n immediate(\"idle\", \"the pool removes the idle client, and emits error\"),\n ],\n },\n silent(\"blackhole\"),\n silent(\"freeze-host\"),\n {\n manifestation: \"freeze-process\",\n what: WHAT[\"freeze-process\"],\n detectors: [query, idleExpiry, keepaliveAnswered()],\n },\n {\n manifestation: \"app-stall\",\n what: \"the server stops its replies for a time, for example behind a lock, and its kernel stays up\",\n detectors: [\n {\n by: \"statement_timeout\",\n within: knobs.statement_timeout,\n situation: \"in flight\",\n effect: \"the server ends the statement with ERROR 57014, a certain outcome, and the client stays\",\n },\n query,\n idleExpiry,\n keepaliveAnswered(),\n ],\n },\n ];\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport { realClock } from \"../clock.ts\";\nimport type { Attempt, CheckOptions, Client, ClientJoin, ClientKnobs, ClientOptions, ClientSpec, ClientStep, DialFn, SendOptions, Sent } from \"../client/core.ts\";\nimport {\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 roundTripCheck,\n sendWithReissue,\n socketEnds,\n stepChecks,\n timeProblems,\n withinBound,\n} from \"../client/core.ts\";\nimport { helloDial } from \"../client/dial.ts\";\nimport type { Dependency } from \"../description.ts\";\nimport type { DependencyHandle } from \"../lifecycle.ts\";\nimport type { Operation } from \"../operation.ts\";\nimport type { PgPool, PgPoolClient, PgPoolOptions, PgResult, PoolFactory } from \"./connection.ts\";\nimport { nodePostgres } from \"./connection.ts\";\nimport { pgMeetings } 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. `PG_STARTING_KNOBS` gives a declaration from the cells of S1. The knobs\n * that each client has come from the core.\n */\nexport interface PgKnobs extends ClientKnobs {\n /**\n * This time bounds a new connection (`connectionTimeoutMillis`): the TCP connect, the startup and\n * the authentication. The pool uses the same knob to bound a checkout from a full pool.\n * At the defaults pg has no bound, and the kernel gives up on the SYNs only after 131 s (A3, O15).\n */\n readonly connect_bound: Quantity;\n /** This time bounds each query on the client (`query_timeout`). pg sends no cancel at its end. */\n readonly request_timeout: Quantity;\n /**\n * This time is the deadline of each statement on the server (`statement_timeout`). The server then\n * ends the statement with ERROR 57014, a certain outcome. \"infinite\" sends no deadline.\n */\n readonly statement_timeout: Extent;\n /** This time ends an idle client of the pool (`idleTimeoutMillis`). The pool sends no test on an idle client. */\n readonly pool_idle_expiry: Extent;\n /** This time is the idle time of TCP keepalive, in whole seconds. Node then sends 10 probes 1 s apart. */\n readonly keepalive_idle: Extent;\n /** This time is the age at which the pool replaces a client (`maxLifetimeSeconds`), in whole seconds. */\n readonly max_connection_age: Extent;\n /** This time bounds the close of the pool at the shutdown: its end, then a destroy of each socket that stays. */\n readonly close_bound: Quantity;\n}\n\n/** A pg client spec is the declaration of one PostgreSQL client: where it connects, its pool, and its knobs. */\nexport interface PgClientSpec extends ClientSpec<PgKnobs> {\n readonly database?: string | undefined;\n readonly user?: string | undefined;\n /** These counts are the sizes of the pool: at most `max` clients, and an idle timer only above `min`. */\n readonly pool: { readonly max: number; readonly min: number };\n /**\n * These operations are the queries of the service, by name, with their idempotency. SQL text does\n * not tell if a statement changes a state, thus the client sends again only a query that this table names.\n */\n readonly operations?: Readonly<Record<string, Operation>> | undefined;\n}\n\n/** These options make a client. Without them, it uses pg, the real clock, and a dial with an SSLRequest. */\nexport type PgClientOptions = ClientOptions<PoolFactory>;\n\n/**\n * A join is one connection of the pool, at its connect (D1, D2). The prober joins its kernel records\n * to the server's view of the connection through it. The server's view is the process id of the backend.\n */\nexport interface PgJoin extends ClientJoin {\n /** This number is the process id of the backend, or null when the server gave none. Through a pooler, it is the id that the pooler gives. */\n readonly backendPid: number | null;\n}\n\n/** The steps that the pg client gives a check for: the steps of each client, and a checkout from the pool (B14). */\nexport type PgStep = ClientStep | \"checkout\";\n\n/** A pg client is a dependency of a service: its declaration, its handle, its checks and its queries. */\nexport interface PgClient extends Client<PgStep, PgJoin> {\n /** This text is the state of the client, as `PG_CLIENT_MACHINE` names it. */\n readonly status: string;\n /**\n * This method sends a query, and gives its outcome in three values. `operation` names the query\n * in the table of operations. The client sends again only an idempotent operation, one time at\n * most, after an outcome that is not certain. The second try stays in the bound of the request.\n */\n query<Row = Record<string, unknown>>(\n operation: string,\n text: string,\n values?: readonly unknown[],\n options?: SendOptions,\n ): Promise<Sent<PgResult<Row>>>;\n}\n\nconst wholeMs = (amount: Extent): boolean => amount.value === \"infinite\" || Number.isInteger(ms(amount));\nconst wholeSeconds = (amount: Extent): boolean =>\n amount.value === \"infinite\" || Number.isInteger(valueIn(amount as Quantity, \"s\"));\n/** This function writes a bound for pg: \"infinite\" is 0, which pg reads as no timer. */\nconst orZero = (amount: Extent): number => finiteMs(amount) ?? 0;\n\n/** This function gives the problems of a spec: the knobs that pg cannot use. */\nexport const specProblems = (name: string, spec: PgClientSpec): string[] => {\n const { knobs, pool } = spec;\n const problems = timeProblems(name, knobs);\n // pg reads 0 as no timer, thus a bound of 0 is not a bound\n for (const knob of [\"connect_bound\", \"request_timeout\", \"close_bound\"] as const) {\n if (!(ms(knobs[knob]) > 0) || !Number.isFinite(ms(knobs[knob])))\n problems.push(`${name}'s ${knob} is not a finite time above zero: pg reads 0 as no bound`);\n }\n for (const knob of [\"statement_timeout\", \"pool_idle_expiry\", \"keepalive_idle\", \"max_connection_age\"] as const) {\n if (knobs[knob].value !== \"infinite\" && !(ms(knobs[knob]) > 0))\n problems.push(`${name}'s ${knob} is zero: give \"infinite\" for no bound`);\n }\n for (const knob of [\"connect_bound\", \"request_timeout\", \"statement_timeout\", \"pool_idle_expiry\"] as const) {\n if (!wholeMs(knobs[knob])) problems.push(`${name}'s ${knob} is not a whole number of milliseconds`);\n }\n // Node truncates the keepalive delay to whole seconds, and pg takes the age in whole seconds\n if (!wholeSeconds(knobs.keepalive_idle)) problems.push(`${name}'s keepalive_idle is not a whole number of seconds`);\n if (!wholeSeconds(knobs.max_connection_age)) problems.push(`${name}'s max_connection_age is not a whole number of seconds`);\n if (!Number.isInteger(pool.max) || pool.max < 1) problems.push(`${name}'s pool has a max of ${pool.max}, not a whole number above zero`);\n if (!Number.isInteger(pool.min) || pool.min < 0 || pool.min > pool.max)\n problems.push(`${name}'s pool has a min of ${pool.min}, not a whole number from 0 to its max`);\n return problems;\n};\n\n/** This function gives the options of `pg.Pool` from a spec. Each timer of pg comes from the declaration. */\nexport const poolOptions = (name: string, spec: PgClientSpec): PgPoolOptions => {\n const { knobs } = spec;\n return {\n host: spec.host,\n port: spec.port,\n ...(spec.database === undefined ? {} : { database: spec.database }),\n ...(spec.user === undefined ? {} : { user: spec.user }),\n ...(spec.password === undefined ? {} : { password: spec.password }),\n application_name: name,\n max: spec.pool.max,\n min: spec.pool.min,\n connectionTimeoutMillis: ms(knobs.connect_bound),\n query_timeout: ms(knobs.request_timeout),\n statement_timeout: finiteMs(knobs.statement_timeout),\n idleTimeoutMillis: orZero(knobs.pool_idle_expiry),\n maxLifetimeSeconds: orZero(knobs.max_connection_age) / 1000,\n keepAlive: knobs.keepalive_idle.value !== \"infinite\",\n keepAliveInitialDelayMillis: orZero(knobs.keepalive_idle),\n allowExitOnIdle: true,\n };\n};\n\n/** The bytes of an SSLRequest: a length of 8, then the code 80877103. A server answers with one byte, S or N. */\nconst SSL_REQUEST = Uint8Array.of(0, 0, 0, 8, 0x04, 0xd2, 0x16, 0x2f);\n\n/**\n * This dial function opens a TCP connection, sends an SSLRequest, and waits for its one byte. The\n * server answers it before the authentication, thus the dial needs no password and opens no backend.\n */\nexport const sslRequestDial: DialFn = helloDial(SSL_REQUEST, (received) => {\n const answer = String.fromCharCode(received[0] ?? 0);\n return answer === \"S\" || answer === \"N\" ? null : new Error(`the answer to the SSLRequest was ${JSON.stringify(answer)}`);\n});\n\n/** A database error is an ErrorResponse of the server: pg gives its severity and its SQLSTATE code. */\nconst replyOf = (error: unknown): { readonly severity: string; readonly code: string } | null => {\n if (typeof error !== \"object\" || error === null) return null;\n const { severity, code } = error as { severity?: unknown; code?: unknown };\n return typeof severity === \"string\" && typeof code === \"string\" && /^[0-9A-Z]{5}$/.test(code) ? { severity, code } : null;\n};\n\n/**\n * This function makes a PostgreSQL client, a dependency of a service over a `pg.Pool`.\n * - It sets each timer of pg from the declaration. The driver sends nothing again by itself, and the client\n * sends again only an idempotent operation of its table.\n * - It tells the outcome of a query in three values. An ERROR of the server is certain. A FATAL\n * ends the session, thus its outcome is not certain.\n * - It gives back a client whose query has no certain outcome with an error. The pool then ends it,\n * thus an orphaned query does not hold the next checkout.\n * - It listens to each error of the pool and of a client that it holds. In Node, an error with no\n * listener throws, and library code does not end the process (A5).\n * - It records the process id of the backend of each connection (D1).\n *\n * The outcomes, the second try, the queries in flight, the bounded close and the checks come from\n * the core of the clients. This entry gives what pg does in its own way.\n */\nexport const pgClient = (name: string, spec: PgClientSpec, options: PgClientOptions = {}): PgClient => {\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 ?? nodePostgres;\n const dial = options.dial ?? sslRequestDial;\n const operations = operationTable({}, spec.operations);\n const requestTimeout = ms(knobs.request_timeout);\n\n let pool: PgPool | null = null;\n let status = \"none\";\n const joins: PgJoin[] = [];\n // the clients of the pool whose socket did not close yet: the bound of the close destroys them\n const open = new Set<PgPoolClient>();\n // the queries in flight: the end of the close settles each one\n const flight = inFlight();\n\n const emit = clientRecorder(options.record, clock, name);\n // the state of the pg client is the outcome of its last connect: each record can move it\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 const onConnect = (client: PgPoolClient): void => {\n open.add(client);\n const generation = joins.length + 1;\n const pid = client.processID;\n joins.push({ generation, at: clock.now(), backendPid: typeof pid === \"number\" ? pid : null, ...socketEnds(client.connection?.stream) });\n record(\"join\", closing() ? status : \"ready\", `generation ${generation}, backend ${pid ?? \"unknown\"}`);\n };\n const onRemove = (client: PgPoolClient): void => {\n open.delete(client);\n record(\"client.removed\");\n };\n // an idle client failed. The pool removed it, and the next checkout opens a new connection\n // after the end, the listener only keeps the error from a throw\n const onPoolError = (error: Error): void => {\n if (status !== \"end\") record(\"client.error\", status, message(error));\n };\n\n /** This function records a failed checkout: a full pool at its bound, or a connect that failed. */\n const checkoutFailed = (error: unknown): void => {\n const text = message(error);\n if (/timeout exceeded when trying to connect/i.test(text)) record(\"checkout.timeout\", status, text);\n else record(\"connect.failed\", closing() ? status : \"failing\", text);\n };\n\n /** This function checks out a client, or gives the reason why the query cannot leave the client. */\n const checkout = (signal: AbortSignal | undefined): Promise<PgPoolClient | Attempt> => {\n const p = pool;\n if (p === null) return Promise.resolve(notSent(`${name} has not connected`));\n if (closing()) return Promise.resolve(notSent(`${name} is ${status}: the query did not leave the client`));\n if (signal?.aborted) return Promise.resolve(notSent(\"the caller aborted the query before it left the client\"));\n return new Promise((resolve) => {\n let done = false;\n const aborted = (): void => {\n done = true;\n resolve(notSent(\"the caller abandoned the query before it left the client\"));\n };\n signal?.addEventListener(\"abort\", aborted, { once: true });\n p.connect().then(\n (client) => {\n signal?.removeEventListener(\"abort\", aborted);\n // the caller left while the pool connected: the client goes back unused\n if (done) client.release();\n else resolve(client);\n },\n (error: unknown) => {\n signal?.removeEventListener(\"abort\", aborted);\n checkoutFailed(error);\n if (!done) resolve(notSent(message(error)));\n },\n );\n });\n };\n\n /** This function makes one try of a query: a checkout, the query, and the release. */\n const attempt = async (text: string, values: readonly unknown[] | undefined, signal: AbortSignal | undefined): Promise<Attempt> => {\n const got = await checkout(signal);\n if (!(\"release\" in got)) return got;\n const client = got;\n // a client that the application holds emits its own errors, and the pool does not listen to them\n const onClientError = (error: Error): void => record(\"client.error\", status, message(error));\n client.on(\"error\", onClientError);\n return flight.track(\n signal,\n \"the caller abandoned the query after it was sent\",\n (settle) => {\n client.query(text, values).then(\n (result) => settle({ outcome: \"ok\", value: { rows: result.rows, rowCount: result.rowCount } }),\n (error: unknown) => {\n const reply = replyOf(error);\n const said = reply === null ? message(error) : `${reply.code} ${message(error)}`;\n // an ERROR ends the statement and keeps the session. A FATAL or a PANIC ends the session, and the commit is not certain\n if (reply !== null && reply.severity === \"ERROR\") settle({ outcome: \"error\", error: said, reply: true });\n else settle({ outcome: \"indeterminate\", error: said }, error instanceof Error ? error : new Error(said));\n },\n );\n },\n (_value, cause) => {\n client.off(\"error\", onClientError);\n // pg keeps a query active on the server after its timeout: an error makes the pool end the client, and the end destroys the socket\n client.release(cause);\n },\n );\n };\n\n const query = <Row>(operation: string, text: string, values?: readonly unknown[], sendOptions: SendOptions = {}): Promise<Sent<PgResult<Row>>> =>\n sendWithReissue({\n clock,\n bound: requestTimeout,\n operation: operations[operation],\n options: sendOptions,\n attempt: (signal) => attempt(text, values, signal) as Promise<Attempt<PgResult<Row>>>,\n onReissue: () => record(\"command.reissue\", status, operation),\n });\n\n const handle: DependencyHandle = {\n async connect(signal) {\n record(\"status.connecting\", \"connecting\");\n const p = await create(poolOptions(name, spec));\n pool = p;\n p.on(\"connect\", onConnect);\n p.on(\"remove\", onRemove);\n p.on(\"error\", onPoolError);\n if (signal.aborted) throw new Error(`${name}: the service stopped before the connect`);\n // one checkout opens the first connection. A server that is down does not stop the startup:\n // the checks keep readiness tripped until a connect succeeds (readiness is earned)\n const first = await checkout(signal);\n if (\"release\" in first) first.release();\n },\n async close() {\n const p = pool;\n if (p === null || closing()) return;\n record(\"close.request\", \"closing\");\n if (!(await withinBound(clock, ms(knobs.close_bound), p.end()))) {\n // the end of a client sends Terminate and half-closes the socket. Behind a black hole the\n // socket never closes, and the pool waits with no bound: the bound destroys each socket\n record(\"close.bound\");\n for (const client of [...open]) client.connection?.stream?.destroy?.();\n record(\"close.destroy\", status, `${open.size} sockets`);\n }\n flight.settleAll(\"the client closed with the query in flight\");\n p.off(\"connect\", onConnect);\n p.off(\"remove\", onRemove);\n // the error listener stays: a socket that closes later emits an error, and with no listener it throws\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 statement_timeout: knobs.statement_timeout,\n pool_idle_expiry: knobs.pool_idle_expiry,\n keepalive_idle: knobs.keepalive_idle,\n // pg sends nothing on an idle connection, 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<PgStep>) =>\n stepChecks<PgStep>(name, checkOptions, {\n // the outcome of the last connect, without a round trip. pg does not connect again by itself,\n // thus pair it with a check that uses the pool\n \"local-state\": localStateCheck(() => status === \"ready\"),\n // a checkout and its release, with no query: it finds a full pool and a connect that fails,\n // but the pool sends no test on an idle client, thus not a dead idle connection\n checkout: {\n ownConnection: true,\n run: async (signal) => {\n const got = await checkout(signal);\n if (!(\"release\" in got)) return \"error\";\n got.release();\n return \"ok\";\n },\n },\n // a SELECT 1 through the pool of the application\n \"round-trip\": roundTripCheck((sendOptions) => query(\"check\", \"SELECT 1\", [], sendOptions)),\n // a new connection each time, with no backend\n dial: dialCheck(dial, spec.host, spec.port),\n });\n\n return {\n name,\n declaration,\n handle,\n meets: pgMeetings(name, knobs),\n get status() {\n return status;\n },\n joins,\n query,\n checks,\n attach: (svc, checkOptions) => attachClient(svc, name, declaration, handle, checks(checkOptions)),\n problems(description) {\n const found = clientProblems(name, description, clientBounds(knobs));\n // O07: the client waits longer than the deadline of the server. Then the server ends a slow\n // statement with a certain error. Otherwise the client gives up first, and the server goes on\n if (knobs.statement_timeout.value !== \"infinite\" && requestTimeout <= ms(knobs.statement_timeout))\n found.push(`${name}'s request_timeout is not above its statement_timeout: the server goes on with a statement that the client gave up (O07)`);\n return found;\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 pool exists and serves. */\nconst SERVING = [\"connecting\", \"ready\", \"failing\"] as const;\nconst OPEN = [...SERVING, \"closing\"] as const;\n\n/**\n * The pg client of the service as a `mark1russell7.machine/1` document. It is the pool as the\n * wrapper sees it, not one connection: the document `node-postgres` of `@mark1russell7/machines`\n * gives each client of the pool. The state is the outcome of the last connect:\n * - `ready`: the last connect of the pool succeeded.\n * - `failing`: the last connect failed. pg does not connect again by itself. The next checkout tries again.\n *\n * An idle client that fails and a client that the pool removes do not change the state. The pool\n * removed that client, and the next checkout opens a new connection. A test records the transitions\n * of the client and replays them against this document.\n */\nexport const PG_CLIENT_MACHINE: Machine = {\n format: \"mark1russell7.machine/1\",\n id: \"service.machine.pg-client\",\n title: \"The PostgreSQL client of the service, over a pool of node-postgres 8\",\n layer: \"driver\",\n version: \"0.1.0\",\n sources: [{ ref: \"packages/service/src/pg (the client implements this document)\", rung: \"implemented\" }],\n states: {\n none: { initial: true, note: \"the client exists, and its pool does not\" },\n connecting: { note: \"the first checkout of the startup opens the first connection\" },\n ready: { note: \"the last connect succeeded. A dead idle client stays hidden until the pool ends it or a query meets it\" },\n failing: { note: \"the last connect failed. A checkout of a full pool that times out does not change the state\" },\n closing: { note: \"the end of the pool: each idle client sends Terminate, and each held client goes at its release\" },\n end: { final: true, note: \"the service closed the client\" },\n },\n events: {\n \"status.connecting\": { kind: \"local\" },\n join: { kind: \"rx\", note: \"the first ReadyForQuery of a new connection, with the process id of its backend (D1)\" },\n \"connect.failed\": { kind: \"local\", note: \"a connect failed: its error, a close, a FATAL of the server, or connect_bound\" },\n \"checkout.timeout\": { kind: \"timer\", note: \"a checkout of a full pool waited connect_bound\" },\n \"client.error\": { kind: \"rx\", note: \"an error of an idle client, or of a client that the service holds\" },\n \"client.removed\": { kind: \"local\", note: \"the socket of a client of the pool closed\" },\n \"command.reissue\": { kind: \"local\" },\n \"close.request\": { kind: \"local\" },\n \"close.bound\": { kind: \"timer\" },\n \"close.destroy\": { kind: \"local\", note: \"the bound destroys each socket that a silent server does not close\" },\n \"status.end\": { kind: \"local\" },\n },\n knobs: {\n connect_bound: knob(\"connectionTimeoutMillis\"),\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: \"pg-pool destroys the socket of the attempt: 'Connection terminated due to connection timeout'\",\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 checkout 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(`checkout-timeout-${state}`, state, \"checkout.timeout\", state)),\n ...OPEN.map((state) => t(`client-error-${state}`, state, \"client.error\", state)),\n ...OPEN.map((state) => t(`removed-${state}`, state, \"client.removed\", 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 { PgKnobs } from \"./client.ts\";\n\n/**\n * This poll is the poll of the checks that `PG_STARTING_KNOBS` fits. It is `STARTING_POLL` of the\n * 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 PG_STARTING_POLL: Poll = STARTING_POLL;\n\n/**\n * These knobs are a declaration to start from, from what the cells of S1 measured on node-postgres\n * 8.23.1 against PostgreSQL 17.11. The runtime has no defaults: a service gives these knobs, or its\n * own. Each value holds the borders that the cells measured:\n * - `connect_bound` 5 s: at the defaults pg has no bound, and the kernel gives up on the SYNs only\n * after 131 s. The timer of the pool fired 7 ms late at most. Thus 5 s is in a startup budget of 30 s (O15).\n * - `request_timeout` 9.5 s: the cells ended a query in a black hole at 9.503 s. That is in the slowest poll of 10 s (O13).\n * - `statement_timeout` 9 s: below `request_timeout`, thus the server ends a slow statement first,\n * with ERROR 57014 (O07). The cells measured that end 2 ms after the deadline.\n * - `pool_idle_expiry` 10 s: the idle timer of pg-pool at its defaults, measured at 10.004 s. It is\n * below a router that forgets an idle flow at 12 s (O03), and below a `client_idle_timeout` of PgBouncer at 12 s (O01).\n * - `keepalive_idle` 30 s: at the defaults pg has no keepalive. This value refreshes a held idle\n * connection through the NAT forms of the cells (240 s and 350 s).\n * - `max_connection_age` \"infinite\": no cell measured an age, thus the declaration gives none.\n * - `close_bound` 2 s: behind a black hole the end of the pool waits with no bound. The cells saw\n * the pool emit `remove` only when the socket closes.\n * - `retrans_giveup_time` 924.6 s: the give-up of Linux at `tcp_retries2 = 15`. The cells measured 3.120 s at `tcp_retries2 = 3`.\n */\nexport const PG_STARTING_KNOBS: PgKnobs = {\n connect_bound: { value: 5, unit: \"s\" },\n request_timeout: { value: 9.5, unit: \"s\" },\n statement_timeout: { value: 9, unit: \"s\" },\n pool_idle_expiry: { value: 10, unit: \"s\" },\n keepalive_idle: { value: 30, 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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"}
1
+ {"version":3,"file":"pg.js","names":["knob","t"],"sources":["../src/pg/connection.ts","../src/pg/meets.ts","../src/pg/client.ts","../src/pg/machine.ts","../src/pg/starting.ts"],"sourcesContent":["import type { ClientSocket, Factory } from \"../client/core.ts\";\n\n/** A pg socket is the socket of a client in the core: the join of the prober and the bounded close read it. */\nexport type PgSocket = ClientSocket;\n\n/** A query result is the part of a result of pg that the wrapper gives back. */\nexport interface PgResult<Row = Record<string, unknown>> {\n readonly rows: Row[];\n readonly rowCount: number | null;\n}\n\n/**\n * A pool client is the part of a `pg.PoolClient` that the wrapper uses. A pg 8 client has each part.\n * A fake for tests has them too. Thus the package depends on no type of pg.\n */\nexport interface PgPoolClient {\n /** This number is the id of the backend from BackendKeyData, which the server sends at the startup. */\n readonly processID?: number | null | undefined;\n /** This connection holds the socket of the client. */\n readonly connection?: { readonly stream?: PgSocket | undefined } | undefined;\n query(text: string, values?: readonly unknown[]): Promise<PgResult<never>>;\n /** This method gives the client back to the pool. With an error, the pool ends the client. */\n release(error?: Error | boolean): void;\n on(event: \"error\", listener: (error: Error) => void): unknown;\n off(event: \"error\", listener: (error: Error) => void): unknown;\n}\n\n/** A pool is the part of a `pg.Pool` that the wrapper uses. */\nexport interface PgPool {\n readonly totalCount: number;\n readonly idleCount: number;\n readonly waitingCount: number;\n connect(): Promise<PgPoolClient>;\n end(): Promise<void>;\n on(event: \"connect\" | \"remove\", listener: (client: PgPoolClient) => void): unknown;\n on(event: \"error\", listener: (error: Error, client: PgPoolClient) => void): unknown;\n off(event: \"connect\" | \"remove\", listener: (client: PgPoolClient) => void): unknown;\n off(event: \"error\", listener: (error: Error, client: PgPoolClient) => void): unknown;\n}\n\n/**\n * These are the options that the wrapper gives `pg.Pool`. Each one comes from the declaration. The\n * wrapper sets each option that has an effect on a timer, thus no default of pg decides one. A knob\n * of pg at 0 has no timer. The wrapper writes \"infinite\" as 0, and it refuses a bound of 0.\n */\nexport interface PgPoolOptions {\n readonly host: string;\n readonly port: number;\n readonly database?: string | undefined;\n readonly user?: string | undefined;\n readonly password?: string | undefined;\n /** The server shows this name in `pg_stat_activity`, thus the prober can join on it too. */\n readonly application_name: string;\n readonly max: number;\n readonly min: number;\n /** This time bounds the TCP connect, the startup and the authentication. It also bounds a checkout from a full pool. */\n readonly connectionTimeoutMillis: number;\n /** This time bounds each query on the client, from `client.query`. The client sends no cancel at its end. */\n readonly query_timeout: number;\n /** This time is the deadline of each statement on the server. The client sends it in the startup message. Undefined does not send it. */\n readonly statement_timeout: number | undefined;\n /** This time ends an idle client of the pool. 0 is no idle timer. */\n readonly idleTimeoutMillis: number;\n /** This time ends a client at this age from its connect, in whole seconds. 0 is no age. */\n readonly maxLifetimeSeconds: number;\n readonly keepAlive: boolean;\n /** This time is the idle time of TCP keepalive. Node truncates it to whole seconds. */\n readonly keepAliveInitialDelayMillis: number;\n /** The idle timer and the idle sockets do not keep the process alive (A14). The lifecycle holds the process. */\n readonly allowExitOnIdle: true;\n}\n\n/** A pool factory makes the pool of the client from its options. */\nexport type PoolFactory = Factory<PgPoolOptions, PgPool>;\n\n/**\n * This factory makes a `pg.Pool`. It loads pg only when a client connects, thus a service that does\n * not use PostgreSQL does not need pg. The specifier is a value, thus the package needs no types of pg.\n */\nexport const nodePostgres: PoolFactory = async (options) => {\n const specifier = \"pg\";\n const loaded = (await import(specifier)) as { readonly default: { readonly Pool: new (options: PgPoolOptions) => PgPool } };\n return new loaded.default.Pool(options);\n};\n","import type { Detector, Meeting } from \"../client/meets.ts\";\nimport { WHAT, immediate, keepaliveAnswered, keepaliveReset, keepaliveSilent, kernelGiveUp } from \"../client/meets.ts\";\nimport type { PgKnobs } from \"./client.ts\";\n\n/**\n * This function gives the manifestations that a pg client can meet, and the detectors of each, from\n * its knobs. The cells of S1 measured each one on node-postgres 8.23.1:\n * - A reset of a query gives ECONNRESET in 0.6 ms, and a FIN to an idle client gives EOF in 18 µs.\n * - A frozen server and a black hole have no detector in 60 s at the defaults: pg has no timer on a query and no keepalive.\n * - `query_timeout` ends a query in a black hole on time (9.503 s for 9.5 s).\n * - `statement_timeout` ends a slow statement on the server with ERROR 57014 (10.002 s for 10 s).\n *\n * pg has no watchdog and does not test an idle client. In place of a watchdog, the pool ends an\n * idle client at its idle timer. A later checkout then opens a new connection, which `connect_bound` bounds.\n */\nexport const pgMeetings = (name: string, knobs: PgKnobs): Meeting[] => {\n const query: Detector = {\n by: \"request_timeout\",\n within: knobs.request_timeout,\n situation: \"in flight\",\n effect: `the query fails as indeterminate, and ${name} gives the client back with an error, thus the pool ends it`,\n };\n const idleExpiry: Detector = {\n by: \"pool_idle_expiry\",\n within: knobs.pool_idle_expiry,\n situation: \"idle\",\n effect: \"the pool ends the idle client, thus no checkout gets the dead connection\",\n };\n const giveUp = kernelGiveUp(knobs.retrans_giveup_time, \"the query fails with its error\");\n const silent = (manifestation: \"blackhole\" | \"freeze-host\"): Meeting => ({\n manifestation,\n what: WHAT[manifestation],\n detectors: [query, giveUp, idleExpiry, keepaliveSilent(knobs.keepalive_idle)],\n });\n return [\n {\n manifestation: \"rst\",\n what: WHAT.rst,\n detectors: [\n immediate(\"in flight\", \"the query fails with ECONNRESET, and each new connect is refused\"),\n idleExpiry,\n keepaliveReset(knobs.keepalive_idle),\n ],\n },\n {\n manifestation: \"fin\",\n what: WHAT.fin,\n detectors: [\n immediate(\"in flight\", \"the query fails: 'Connection terminated unexpectedly'\"),\n immediate(\"idle\", \"the pool removes the idle client, and emits error\"),\n ],\n },\n silent(\"blackhole\"),\n silent(\"freeze-host\"),\n {\n manifestation: \"freeze-process\",\n what: WHAT[\"freeze-process\"],\n detectors: [query, idleExpiry, keepaliveAnswered()],\n },\n {\n manifestation: \"app-stall\",\n what: \"the server stops its replies for a time, for example behind a lock, and its kernel stays up\",\n detectors: [\n {\n by: \"statement_timeout\",\n within: knobs.statement_timeout,\n situation: \"in flight\",\n effect: \"the server ends the statement with ERROR 57014, a certain outcome, and the client stays\",\n },\n query,\n idleExpiry,\n keepaliveAnswered(),\n ],\n },\n ];\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport { realClock } from \"../clock.ts\";\nimport type { Attempt, CheckOptions, Client, ClientJoin, ClientKnobs, ClientOptions, ClientSpec, ClientStep, DialFn, SendOptions, Sent } from \"../client/core.ts\";\nimport {\n attachClient,\n clientBounds,\n clientProblems,\n clientRecorder,\n deadlineProblem,\n dialCheck,\n finiteMs,\n inFlight,\n localStateCheck,\n message,\n ms,\n notSent,\n operationTable,\n orZero,\n poolProblems,\n roundTripCheck,\n sendWithReissue,\n socketEnds,\n stepChecks,\n timeProblems,\n wholeProblems,\n withinBound,\n zeroProblems,\n} from \"../client/core.ts\";\nimport { helloDial } from \"../client/dial.ts\";\nimport type { Dependency } from \"../description.ts\";\nimport type { DependencyHandle } from \"../lifecycle.ts\";\nimport type { Operation } from \"../operation.ts\";\nimport type { PgPool, PgPoolClient, PgPoolOptions, PgResult, PoolFactory } from \"./connection.ts\";\nimport { nodePostgres } from \"./connection.ts\";\nimport { pgMeetings } 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. `PG_STARTING_KNOBS` gives a declaration from the cells of S1. The knobs\n * that each client has come from the core.\n */\nexport interface PgKnobs extends ClientKnobs {\n /**\n * This time bounds a new connection (`connectionTimeoutMillis`): the TCP connect, the startup and\n * the authentication. The pool uses the same knob to bound a checkout from a full pool.\n * At the defaults pg has no bound, and the kernel gives up on the SYNs only after 131 s (A3, O15).\n */\n readonly connect_bound: Quantity;\n /** This time bounds each query on the client (`query_timeout`). pg sends no cancel at its end. */\n readonly request_timeout: Quantity;\n /**\n * This time is the deadline of each statement on the server (`statement_timeout`). The server then\n * ends the statement with ERROR 57014, a certain outcome. \"infinite\" sends no deadline.\n */\n readonly statement_timeout: Extent;\n /** This time ends an idle client of the pool (`idleTimeoutMillis`). The pool sends no test on an idle client. */\n readonly pool_idle_expiry: Extent;\n /** This time is the idle time of TCP keepalive, in whole seconds. Node then sends 10 probes 1 s apart. */\n readonly keepalive_idle: Extent;\n /** This time is the age at which the pool replaces a client (`maxLifetimeSeconds`), in whole seconds. */\n readonly max_connection_age: Extent;\n /** This time bounds the close of the pool at the shutdown: its end, then a destroy of each socket that stays. */\n readonly close_bound: Quantity;\n}\n\n/** A pg client spec is the declaration of one PostgreSQL client: where it connects, its pool, and its knobs. */\nexport interface PgClientSpec extends ClientSpec<PgKnobs> {\n readonly database?: string | undefined;\n readonly user?: string | undefined;\n /** These counts are the sizes of the pool: at most `max` clients, and an idle timer only above `min`. */\n readonly pool: { readonly max: number; readonly min: number };\n /**\n * These operations are the queries of the service, by name, with their idempotency. SQL text does\n * not tell if a statement changes a state, thus the client sends again only a query that this table names.\n */\n readonly operations?: Readonly<Record<string, Operation>> | undefined;\n}\n\n/** These options make a client. Without them, it uses pg, the real clock, and a dial with an SSLRequest. */\nexport type PgClientOptions = ClientOptions<PoolFactory>;\n\n/**\n * A join is one connection of the pool, at its connect (D1, D2). The prober joins its kernel records\n * to the server's view of the connection through it. The server's view is the process id of the backend.\n */\nexport interface PgJoin extends ClientJoin {\n /** This number is the process id of the backend, or null when the server gave none. Through a pooler, it is the id that the pooler gives. */\n readonly backendPid: number | null;\n}\n\n/** The steps that the pg client gives a check for: the steps of each client, and a checkout from the pool (B14). */\nexport type PgStep = ClientStep | \"checkout\";\n\n/** A pg client is a dependency of a service: its declaration, its handle, its checks and its queries. */\nexport interface PgClient extends Client<PgStep, PgJoin> {\n /** This text is the state of the client, as `PG_CLIENT_MACHINE` names it. */\n readonly status: string;\n /**\n * This method sends a query, and gives its outcome in three values. `operation` names the query\n * in the table of operations. The client sends again only an idempotent operation, one time at\n * most, after an outcome that is not certain. The second try stays in the bound of the request.\n */\n query<Row = Record<string, unknown>>(\n operation: string,\n text: string,\n values?: readonly unknown[],\n options?: SendOptions,\n ): Promise<Sent<PgResult<Row>>>;\n}\n\n/** This function gives the problems of a spec: the knobs that pg cannot use. */\nexport const specProblems = (name: string, spec: PgClientSpec): string[] => {\n const { knobs } = spec;\n return [\n ...timeProblems(name, knobs),\n // pg reads 0 as no timer, thus a bound of 0 is not a bound\n ...zeroProblems(name, \"pg\", knobs, {\n bounded: [\"connect_bound\", \"request_timeout\", \"close_bound\"],\n open: [\"statement_timeout\", \"pool_idle_expiry\", \"keepalive_idle\", \"max_connection_age\"],\n }),\n // Node truncates the keepalive delay to whole seconds, and pg takes the age in whole seconds\n ...wholeProblems(name, knobs, {\n millis: [\"connect_bound\", \"request_timeout\", \"statement_timeout\", \"pool_idle_expiry\"],\n seconds: [\"keepalive_idle\", \"max_connection_age\"],\n }),\n ...poolProblems(name, spec.pool),\n ];\n};\n\n/** This function gives the options of `pg.Pool` from a spec. Each timer of pg comes from the declaration. */\nexport const poolOptions = (name: string, spec: PgClientSpec): PgPoolOptions => {\n const { knobs } = spec;\n return {\n host: spec.host,\n port: spec.port,\n ...(spec.database === undefined ? {} : { database: spec.database }),\n ...(spec.user === undefined ? {} : { user: spec.user }),\n ...(spec.password === undefined ? {} : { password: spec.password }),\n application_name: name,\n max: spec.pool.max,\n min: spec.pool.min,\n connectionTimeoutMillis: ms(knobs.connect_bound),\n query_timeout: ms(knobs.request_timeout),\n statement_timeout: finiteMs(knobs.statement_timeout),\n idleTimeoutMillis: orZero(knobs.pool_idle_expiry),\n maxLifetimeSeconds: orZero(knobs.max_connection_age) / 1000,\n keepAlive: knobs.keepalive_idle.value !== \"infinite\",\n keepAliveInitialDelayMillis: orZero(knobs.keepalive_idle),\n allowExitOnIdle: true,\n };\n};\n\n/** The bytes of an SSLRequest: a length of 8, then the code 80877103. A server answers with one byte, S or N. */\nconst SSL_REQUEST = Uint8Array.of(0, 0, 0, 8, 0x04, 0xd2, 0x16, 0x2f);\n\n/**\n * This dial function opens a TCP connection, sends an SSLRequest, and waits for its one byte. The\n * server answers it before the authentication, thus the dial needs no password and opens no backend.\n */\nexport const sslRequestDial: DialFn = helloDial(SSL_REQUEST, (received) => {\n const answer = String.fromCharCode(received[0] ?? 0);\n return answer === \"S\" || answer === \"N\" ? null : new Error(`the answer to the SSLRequest was ${JSON.stringify(answer)}`);\n});\n\n/** A database error is an ErrorResponse of the server: pg gives its severity and its SQLSTATE code. */\nconst replyOf = (error: unknown): { readonly severity: string; readonly code: string } | null => {\n if (typeof error !== \"object\" || error === null) return null;\n const { severity, code } = error as { severity?: unknown; code?: unknown };\n return typeof severity === \"string\" && typeof code === \"string\" && /^[0-9A-Z]{5}$/.test(code) ? { severity, code } : null;\n};\n\n/**\n * This function makes a PostgreSQL client, a dependency of a service over a `pg.Pool`.\n * - It sets each timer of pg from the declaration. The driver sends nothing again by itself, and the client\n * sends again only an idempotent operation of its table.\n * - It tells the outcome of a query in three values. An ERROR of the server is certain. A FATAL\n * ends the session, thus its outcome is not certain.\n * - It gives back a client whose query has no certain outcome with an error. The pool then ends it,\n * thus an orphaned query does not hold the next checkout.\n * - It listens to each error of the pool and of a client that it holds. In Node, an error with no\n * listener throws, and library code does not end the process (A5).\n * - It records the process id of the backend of each connection (D1).\n *\n * The outcomes, the second try, the queries in flight, the bounded close and the checks come from\n * the core of the clients. This entry gives what pg does in its own way.\n */\nexport const pgClient = (name: string, spec: PgClientSpec, options: PgClientOptions = {}): PgClient => {\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 ?? nodePostgres;\n const dial = options.dial ?? sslRequestDial;\n const operations = operationTable({}, spec.operations);\n const requestTimeout = ms(knobs.request_timeout);\n\n let pool: PgPool | null = null;\n let status = \"none\";\n const joins: PgJoin[] = [];\n // the clients of the pool whose socket did not close yet: the bound of the close destroys them\n const open = new Set<PgPoolClient>();\n // the queries in flight: the end of the close settles each one\n const flight = inFlight();\n\n const emit = clientRecorder(options.record, clock, name);\n // the state of the pg client is the outcome of its last connect: each record can move it\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 const onConnect = (client: PgPoolClient): void => {\n open.add(client);\n const generation = joins.length + 1;\n const pid = client.processID;\n joins.push({ generation, at: clock.now(), backendPid: typeof pid === \"number\" ? pid : null, ...socketEnds(client.connection?.stream) });\n record(\"join\", closing() ? status : \"ready\", `generation ${generation}, backend ${pid ?? \"unknown\"}`);\n };\n const onRemove = (client: PgPoolClient): void => {\n open.delete(client);\n record(\"client.removed\");\n };\n // an idle client failed. The pool removed it, and the next checkout opens a new connection\n // after the end, the listener only keeps the error from a throw\n const onPoolError = (error: Error): void => {\n if (status !== \"end\") record(\"client.error\", status, message(error));\n };\n\n /** This function records a failed checkout: a full pool at its bound, or a connect that failed. */\n const checkoutFailed = (error: unknown): void => {\n const text = message(error);\n if (/timeout exceeded when trying to connect/i.test(text)) record(\"checkout.timeout\", status, text);\n else record(\"connect.failed\", closing() ? status : \"failing\", text);\n };\n\n /** This function checks out a client, or gives the reason why the query cannot leave the client. */\n const checkout = (signal: AbortSignal | undefined): Promise<PgPoolClient | Attempt> => {\n const p = pool;\n if (p === null) return Promise.resolve(notSent(`${name} has not connected`));\n if (closing()) return Promise.resolve(notSent(`${name} is ${status}: the query did not leave the client`));\n if (signal?.aborted) return Promise.resolve(notSent(\"the caller aborted the query before it left the client\"));\n return new Promise((resolve) => {\n let done = false;\n const aborted = (): void => {\n done = true;\n resolve(notSent(\"the caller abandoned the query before it left the client\"));\n };\n signal?.addEventListener(\"abort\", aborted, { once: true });\n p.connect().then(\n (client) => {\n signal?.removeEventListener(\"abort\", aborted);\n // the caller left while the pool connected: the client goes back unused\n if (done) client.release();\n else resolve(client);\n },\n (error: unknown) => {\n signal?.removeEventListener(\"abort\", aborted);\n checkoutFailed(error);\n if (!done) resolve(notSent(message(error)));\n },\n );\n });\n };\n\n /** This function makes one try of a query: a checkout, the query, and the release. */\n const attempt = async (text: string, values: readonly unknown[] | undefined, signal: AbortSignal | undefined): Promise<Attempt> => {\n const got = await checkout(signal);\n if (!(\"release\" in got)) return got;\n const client = got;\n // a client that the application holds emits its own errors, and the pool does not listen to them\n const onClientError = (error: Error): void => record(\"client.error\", status, message(error));\n client.on(\"error\", onClientError);\n return flight.track(\n signal,\n \"the caller abandoned the query after it was sent\",\n (settle) => {\n client.query(text, values).then(\n (result) => settle({ outcome: \"ok\", value: { rows: result.rows, rowCount: result.rowCount } }),\n (error: unknown) => {\n const reply = replyOf(error);\n const said = reply === null ? message(error) : `${reply.code} ${message(error)}`;\n // an ERROR ends the statement and keeps the session. A FATAL or a PANIC ends the session, and the commit is not certain\n if (reply !== null && reply.severity === \"ERROR\") settle({ outcome: \"error\", error: said, reply: true });\n else settle({ outcome: \"indeterminate\", error: said }, error instanceof Error ? error : new Error(said));\n },\n );\n },\n (_value, cause) => {\n client.off(\"error\", onClientError);\n // pg keeps a query active on the server after its timeout: an error makes the pool end the client, and the end destroys the socket\n client.release(cause);\n },\n );\n };\n\n const query = <Row>(operation: string, text: string, values?: readonly unknown[], sendOptions: SendOptions = {}): Promise<Sent<PgResult<Row>>> =>\n sendWithReissue({\n clock,\n bound: requestTimeout,\n operation: operations[operation],\n options: sendOptions,\n attempt: (signal) => attempt(text, values, signal) as Promise<Attempt<PgResult<Row>>>,\n onReissue: () => record(\"command.reissue\", status, operation),\n });\n\n const handle: DependencyHandle = {\n async connect(signal) {\n record(\"status.connecting\", \"connecting\");\n const p = await create(poolOptions(name, spec));\n pool = p;\n p.on(\"connect\", onConnect);\n p.on(\"remove\", onRemove);\n p.on(\"error\", onPoolError);\n if (signal.aborted) throw new Error(`${name}: the service stopped before the connect`);\n // one checkout opens the first connection. A server that is down does not stop the startup:\n // the checks keep readiness tripped until a connect succeeds (readiness is earned)\n const first = await checkout(signal);\n if (\"release\" in first) first.release();\n },\n async close() {\n const p = pool;\n if (p === null || closing()) return;\n record(\"close.request\", \"closing\");\n if (!(await withinBound(clock, ms(knobs.close_bound), p.end()))) {\n // the end of a client sends Terminate and half-closes the socket. Behind a black hole the\n // socket never closes, and the pool waits with no bound: the bound destroys each socket\n record(\"close.bound\");\n for (const client of [...open]) client.connection?.stream?.destroy?.();\n record(\"close.destroy\", status, `${open.size} sockets`);\n }\n flight.settleAll(\"the client closed with the query in flight\");\n p.off(\"connect\", onConnect);\n p.off(\"remove\", onRemove);\n // the error listener stays: a socket that closes later emits an error, and with no listener it throws\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 statement_timeout: knobs.statement_timeout,\n pool_idle_expiry: knobs.pool_idle_expiry,\n keepalive_idle: knobs.keepalive_idle,\n // pg sends nothing on an idle connection, 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<PgStep>) =>\n stepChecks<PgStep>(name, checkOptions, {\n // the outcome of the last connect, without a round trip. pg does not connect again by itself,\n // thus pair it with a check that uses the pool\n \"local-state\": localStateCheck(() => status === \"ready\"),\n // a checkout and its release, with no query: it finds a full pool and a connect that fails,\n // but the pool sends no test on an idle client, thus not a dead idle connection\n checkout: {\n ownConnection: true,\n run: async (signal) => {\n const got = await checkout(signal);\n if (!(\"release\" in got)) return \"error\";\n got.release();\n return \"ok\";\n },\n },\n // a SELECT 1 through the pool of the application\n \"round-trip\": roundTripCheck((sendOptions) => query(\"check\", \"SELECT 1\", [], sendOptions)),\n // a new connection each time, with no backend\n dial: dialCheck(dial, spec.host, spec.port),\n });\n\n return {\n name,\n declaration,\n handle,\n meets: pgMeetings(name, knobs),\n get status() {\n return status;\n },\n joins,\n query,\n checks,\n attach: (svc, checkOptions) => attachClient(svc, name, declaration, handle, checks(checkOptions)),\n problems(description) {\n // O07: the server must end a slow statement first, with a certain error\n const deadline = deadlineProblem(name, knobs.request_timeout, knobs.statement_timeout, \"statement\");\n return [...clientProblems(name, description, clientBounds(knobs)), ...(deadline === null ? [] : [deadline])];\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 pool exists and serves. */\nconst SERVING = [\"connecting\", \"ready\", \"failing\"] as const;\nconst OPEN = [...SERVING, \"closing\"] as const;\n\n/**\n * The pg client of the service as a `mark1russell7.machine/1` document. It is the pool as the\n * wrapper sees it, not one connection: the document `node-postgres` of `@mark1russell7/machines`\n * gives each client of the pool. The state is the outcome of the last connect:\n * - `ready`: the last connect of the pool succeeded.\n * - `failing`: the last connect failed. pg does not connect again by itself. The next checkout tries again.\n *\n * An idle client that fails and a client that the pool removes do not change the state. The pool\n * removed that client, and the next checkout opens a new connection. A test records the transitions\n * of the client and replays them against this document.\n */\nexport const PG_CLIENT_MACHINE: Machine = {\n format: \"mark1russell7.machine/1\",\n id: \"service.machine.pg-client\",\n title: \"The PostgreSQL client of the service, over a pool of node-postgres 8\",\n layer: \"driver\",\n version: \"0.1.0\",\n sources: [{ ref: \"packages/service/src/pg (the client implements this document)\", rung: \"implemented\" }],\n states: {\n none: { initial: true, note: \"the client exists, and its pool does not\" },\n connecting: { note: \"the first checkout of the startup opens the first connection\" },\n ready: { note: \"the last connect succeeded. A dead idle client stays hidden until the pool ends it or a query meets it\" },\n failing: { note: \"the last connect failed. A checkout of a full pool that times out does not change the state\" },\n closing: { note: \"the end of the pool: each idle client sends Terminate, and each held client goes at its release\" },\n end: { final: true, note: \"the service closed the client\" },\n },\n events: {\n \"status.connecting\": { kind: \"local\" },\n join: { kind: \"rx\", note: \"the first ReadyForQuery of a new connection, with the process id of its backend (D1)\" },\n \"connect.failed\": { kind: \"local\", note: \"a connect failed: its error, a close, a FATAL of the server, or connect_bound\" },\n \"checkout.timeout\": { kind: \"timer\", note: \"a checkout of a full pool waited connect_bound\" },\n \"client.error\": { kind: \"rx\", note: \"an error of an idle client, or of a client that the service holds\" },\n \"client.removed\": { kind: \"local\", note: \"the socket of a client of the pool closed\" },\n \"command.reissue\": { kind: \"local\" },\n \"close.request\": { kind: \"local\" },\n \"close.bound\": { kind: \"timer\" },\n \"close.destroy\": { kind: \"local\", note: \"the bound destroys each socket that a silent server does not close\" },\n \"status.end\": { kind: \"local\" },\n },\n knobs: {\n connect_bound: knob(\"connectionTimeoutMillis\"),\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: \"pg-pool destroys the socket of the attempt: 'Connection terminated due to connection timeout'\",\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 checkout 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(`checkout-timeout-${state}`, state, \"checkout.timeout\", state)),\n ...OPEN.map((state) => t(`client-error-${state}`, state, \"client.error\", state)),\n ...OPEN.map((state) => t(`removed-${state}`, state, \"client.removed\", 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 { PgKnobs } from \"./client.ts\";\n\n/**\n * This poll is the poll of the checks that `PG_STARTING_KNOBS` fits. It is `STARTING_POLL` of the\n * 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 PG_STARTING_POLL: Poll = STARTING_POLL;\n\n/**\n * These knobs are a declaration to start from, from what the cells of S1 measured on node-postgres\n * 8.23.1 against PostgreSQL 17.11. The runtime has no defaults: a service gives these knobs, or its\n * own. Each value holds the borders that the cells measured:\n * - `connect_bound` 5 s: at the defaults pg has no bound, and the kernel gives up on the SYNs only\n * after 131 s. The timer of the pool fired 7 ms late at most. Thus 5 s is in a startup budget of 30 s (O15).\n * - `request_timeout` 9.5 s: the cells ended a query in a black hole at 9.503 s. That is in the slowest poll of 10 s (O13).\n * - `statement_timeout` 9 s: below `request_timeout`, thus the server ends a slow statement first,\n * with ERROR 57014 (O07). The cells measured that end 2 ms after the deadline.\n * - `pool_idle_expiry` 10 s: the idle timer of pg-pool at its defaults, measured at 10.004 s. It is\n * below a router that forgets an idle flow at 12 s (O03), and below a `client_idle_timeout` of PgBouncer at 12 s (O01).\n * - `keepalive_idle` 30 s: at the defaults pg has no keepalive. This value refreshes a held idle\n * connection through the NAT forms of the cells (240 s and 350 s).\n * - `max_connection_age` \"infinite\": no cell measured an age, thus the declaration gives none.\n * - `close_bound` 2 s: behind a black hole the end of the pool waits with no bound. The cells saw\n * the pool emit `remove` only when the socket closes.\n * - `retrans_giveup_time` 924.6 s: the give-up of Linux at `tcp_retries2 = 15`. The cells measured 3.120 s at `tcp_retries2 = 3`.\n */\nexport const PG_STARTING_KNOBS: PgKnobs = {\n connect_bound: { value: 5, unit: \"s\" },\n request_timeout: { value: 9.5, unit: \"s\" },\n statement_timeout: { value: 9, unit: \"s\" },\n pool_idle_expiry: { value: 10, unit: \"s\" },\n keepalive_idle: { value: 30, 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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"}
@@ -0,0 +1,150 @@
1
+ import { n as Clock } from "./clock-mVqhXD0b.js";
2
+ import { G as ServiceDescription, d as TransportImpl, r as Lifecycle } from "./lifecycle-fgYfxrHK.js";
3
+ import { Extent, Quantity } from "@mark1russell7/quantity";
4
+ //#region src/units.d.ts
5
+ /**
6
+ * A unit request names the route of each unit of work in the request tracking of the service. Then
7
+ * the service admits, counts, drains and cuts each unit as it does each request (`life.request`).
8
+ * The controller declares the route, thus its idempotency tells if a cut unit can go again.
9
+ */
10
+ interface UnitRequest<U> {
11
+ /** This name is the controller that declares the route. */
12
+ readonly controller: string;
13
+ /** This name is the route of each unit, or a function that gives it from the unit. A route can match a route pattern. */
14
+ readonly route: string | ((unit: U) => string);
15
+ /** This function gives the detail of a unit, for the function of a route pattern. */
16
+ readonly detail?: ((unit: U) => unknown) | undefined;
17
+ }
18
+ //#endregion
19
+ //#region src/queue.d.ts
20
+ /**
21
+ * These are the knobs of a queue drain, by the names of the ordering law where the law has them.
22
+ * The drain has no defaults, thus each knob is necessary. A bound that nobody gives is "infinite".
23
+ */
24
+ interface QueueKnobs {
25
+ /** This time is the wait after a take that gives no job, before the next take. A time without a bound waits for `wake()`. */
26
+ readonly queue_poll_interval: Extent;
27
+ /**
28
+ * This time is the most that one job can take. At it, the drain aborts the signal of the job, and
29
+ * a throw after it is a failure. The law reads it as `unit_processing_time@consumer` (O20).
30
+ */
31
+ readonly unit_processing_time: Extent;
32
+ /** 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. */
33
+ readonly retry_base: Quantity;
34
+ /** This time is the most that one delay can be, before its jitter. */
35
+ readonly retry_cap: Quantity;
36
+ /** This time bounds the close: the end of the jobs after their abort, and then the close of the source. */
37
+ readonly close_bound: Quantity;
38
+ /** This count (the unit `count`) is the most jobs in flight at one time. A count without a bound takes each job that comes. */
39
+ readonly concurrency: Extent;
40
+ /** The other knobs go to the declaration, for example `lease_duration@broker`. The drain does not read them. */
41
+ readonly [knob: string]: Extent;
42
+ }
43
+ /**
44
+ * A job source gives the jobs of a queue drain:
45
+ * - A function takes the next job, or gives null when no job is ready. Its signal aborts when the intake stops.
46
+ * - An async iterable gives the jobs in sequence. The drain asks for the next job only when it has a free slot.
47
+ * At the stop of the intake, the drain ends the iterator through `return()`.
48
+ */
49
+ type JobSource<J> = ((signal: AbortSignal) => J | null | undefined | PromiseLike<J | null | undefined>) | AsyncIterable<J>;
50
+ /**
51
+ * A job action is what the drain does with a job that failed:
52
+ * - `requeue`: the job goes back on the queue.
53
+ * - `reject`: the job leaves the queue without a success, for example to the dead-letter route of the queue.
54
+ * - `ack`: the job leaves the queue, as a poison message does.
55
+ */
56
+ type JobAction = "requeue" | "reject" | "ack";
57
+ /** A job failure is a job whose handler failed, with the count of its deliveries, 1 at the first. */
58
+ interface JobFailure<J> {
59
+ readonly job: J;
60
+ readonly error: unknown;
61
+ readonly delivery: number;
62
+ }
63
+ /** A redelivery policy gives the action for a job that failed. The queue declares it, and it has no default (S6 A11). */
64
+ type RedeliveryPolicy<J> = (failure: JobFailure<J>) => JobAction;
65
+ /**
66
+ * A requeue cause tells why a job goes back on the queue:
67
+ * - `failed`: the redelivery policy sent it back after a failure.
68
+ * - `unstarted`: the drain took the job and did not start it, because its intake stopped or the service refused it.
69
+ * - `cut`: the shutdown cut the job, and its route declares that it can go again.
70
+ */
71
+ type RequeueCause = "failed" | "unstarted" | "cut";
72
+ /** A job settle is how the drain tells the queue the end of each job. Each function can give a promise. */
73
+ interface JobSettle<J> {
74
+ /** This function removes the job from the queue: a success, or the action `ack`. */
75
+ ack(job: J): void | PromiseLike<void>;
76
+ /** This function puts the job back on the queue, or hands it back to the queue at the stop. */
77
+ requeue(job: J, cause: RequeueCause): void | PromiseLike<void>;
78
+ /** This function removes the job from the queue without a success: the action `reject`, or a cut job that cannot go again. */
79
+ reject(job: J, error: unknown): void | PromiseLike<void>;
80
+ /**
81
+ * This function gives the count of deliveries of a job from the queue, 1 at the first. The queue
82
+ * knows it, for example `x-delivery-count` of RabbitMQ or `ApproximateReceiveCount` of SQS. A
83
+ * queue in memory counts for itself. The drain has no default, because a broker gives a new object at each delivery.
84
+ */
85
+ delivery(job: J): number;
86
+ }
87
+ /** A job context is what the drain gives the handler of each job. */
88
+ interface JobContext {
89
+ /** This signal aborts at the cooperative abort of the drain, at the bound of the job, and at the close. */
90
+ readonly signal: AbortSignal;
91
+ /** This signal aborts when the intake stops. */
92
+ readonly intake: AbortSignal;
93
+ /** This count is the delivery of the job, 1 at the first. */
94
+ readonly delivery: number;
95
+ /** This lifecycle is the lifecycle that the drain got at its start. */
96
+ readonly life: Lifecycle;
97
+ }
98
+ /** These options make a queue drain. Each function can give a promise. */
99
+ interface QueueDrainOptions<J> {
100
+ readonly source: JobSource<J>;
101
+ /** This function does one job. A throw is a failure, and the redelivery policy then gives its action. */
102
+ readonly handle: (job: J, context: JobContext) => unknown;
103
+ readonly settle: JobSettle<J>;
104
+ /** This policy gives the action for each failed job. `redeliverOnce` gives the policy of S6 A11. */
105
+ readonly redelivery: RedeliveryPolicy<J>;
106
+ /** This function closes the source at the close, after the jobs end: for example, it closes a channel. */
107
+ readonly close?: (() => void | PromiseLike<void>) | undefined;
108
+ /** This clock is the clock of the lifecycle. The polls, the backoffs and the bounds use it. The default is `realClock`. */
109
+ readonly clock?: Clock | undefined;
110
+ /** This function gives the random numbers of the jitter, from 0 up to 1. The default is `Math.random`. */
111
+ readonly random?: (() => number) | undefined;
112
+ /**
113
+ * This route puts each job through `life.request`: the service then admits it, counts it, drains
114
+ * it and can cut it. The idempotency of the route tells if a cut job goes back on the queue.
115
+ */
116
+ readonly request?: UnitRequest<J> | undefined;
117
+ /**
118
+ * This function receives each failure: of a take, of a job, of a settle and of a policy. It also
119
+ * receives the end of a source while the intake is open. The job is undefined for a take and for that end.
120
+ */
121
+ readonly onError?: ((error: unknown, job: J | undefined) => void) | undefined;
122
+ }
123
+ /** A queue drain is a transport of the lifecycle whose work is the jobs of a queue (S6 A11). */
124
+ interface QueueDrain extends TransportImpl {
125
+ /** These knobs go to the declaration of the consumer: `svc.consumer(name, drain.knobs, drain)`. */
126
+ readonly knobs: QueueKnobs;
127
+ /** This method ends the wait of a poll at once, for example when a job comes. It gives false after the intake stops. */
128
+ wake(): boolean;
129
+ /** This method gives the problems of the knobs in a declaration: the close bound and the bound of a job against the caps. */
130
+ problems(description: ServiceDescription): string[];
131
+ }
132
+ /**
133
+ * This function gives the redelivery policy of S6 A11. A poison message gets `ack`. Each other job
134
+ * goes back on the queue after its first failure, one time. After a later failure, `then` gives the
135
+ * action: the declared policy of the queue.
136
+ */
137
+ declare const redeliverOnce: <J>(then: JobAction | RedeliveryPolicy<J>, poison?: (error: unknown, job: J) => boolean) => RedeliveryPolicy<J>;
138
+ /**
139
+ * This function makes a queue drain as a transport (S6 A11). One taker takes the jobs from the
140
+ * source while a slot is free, and each job operates in its slot.
141
+ * - A success gets `ack`. A failure gets the action of the redelivery policy.
142
+ * - `stopIntake` takes no new job, and a job that the drain took and did not start goes back (`requeue`, `unstarted`).
143
+ * - `drain` waits for each job in flight until the deadline, and gives it the cooperative abort.
144
+ * - `close` aborts each job, waits for its end, and closes the source, all in `close_bound`. A job
145
+ * that does not end in the bound gets `reject`.
146
+ */
147
+ declare const queueDrain: <J>(knobs: QueueKnobs, options: QueueDrainOptions<J>) => QueueDrain;
148
+ //#endregion
149
+ export { JobSource as a, QueueKnobs as c, queueDrain as d, redeliverOnce as f, JobSettle as i, RedeliveryPolicy as l, JobContext as n, QueueDrain as o, UnitRequest as p, JobFailure as r, QueueDrainOptions as s, JobAction as t, RequeueCause as u };
150
+ //# sourceMappingURL=queue-snRHK3Jm.d.ts.map