lunibee 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/builders/commands.d.ts +1 -1
- package/dist/builders/components.d.ts +9 -11
- package/dist/builders/embed.d.ts +1 -1
- package/dist/builders/index.d.ts +1 -1
- package/dist/builders/index.js +87 -1298
- package/dist/builders/index.js.map +3 -11
- package/dist/chunks/index-0wn81eng.js +2492 -0
- package/dist/chunks/index-0wn81eng.js.map +17 -0
- package/dist/chunks/index-2en489j6.js +40 -0
- package/dist/chunks/index-2en489j6.js.map +10 -0
- package/dist/chunks/index-3hyps48a.js +203 -0
- package/dist/chunks/index-3hyps48a.js.map +11 -0
- package/dist/chunks/index-57c5n3ms.js +2274 -0
- package/dist/chunks/index-57c5n3ms.js.map +19 -0
- package/dist/chunks/index-5yx92e36.js +1170 -0
- package/dist/chunks/index-5yx92e36.js.map +14 -0
- package/dist/chunks/index-690bp093.js +95 -0
- package/dist/chunks/index-690bp093.js.map +10 -0
- package/dist/chunks/index-7fp2hvmg.js +2155 -0
- package/dist/chunks/index-7fp2hvmg.js.map +24 -0
- package/dist/chunks/index-cnmsajvk.js +1119 -0
- package/dist/chunks/index-cnmsajvk.js.map +12 -0
- package/dist/chunks/index-fdvgk2sw.js +1310 -0
- package/dist/chunks/index-fdvgk2sw.js.map +21 -0
- package/dist/chunks/index-gyvps8b9.js +214 -0
- package/dist/chunks/index-gyvps8b9.js.map +12 -0
- package/dist/chunks/index-jzg4bh99.js +1533 -0
- package/dist/chunks/index-jzg4bh99.js.map +21 -0
- package/dist/chunks/index-mgw2yvrm.js +3547 -0
- package/dist/chunks/index-mgw2yvrm.js.map +25 -0
- package/dist/chunks/index-yh76b55h.js +3449 -0
- package/dist/chunks/index-yh76b55h.js.map +29 -0
- package/dist/collection/index.js +9 -1118
- package/dist/collection/index.js.map +3 -6
- package/dist/core/api.d.ts +3 -3
- package/dist/core/channel-create.d.ts +2 -2
- package/dist/core/commands.d.ts +3 -3
- package/dist/core/events.d.ts +4 -4
- package/dist/core/handles.d.ts +4 -4
- package/dist/core/index.d.ts +25 -20
- package/dist/core/index.js +68 -10130
- package/dist/core/index.js.map +3 -65
- package/dist/core/permissions.d.ts +1 -1
- package/dist/core/redis-cache.d.ts +1 -1
- package/dist/formatters/index.js +41 -110
- package/dist/formatters/index.js.map +3 -4
- package/dist/handlers/index.js +5 -37
- package/dist/handlers/index.js.map +3 -4
- package/dist/index.d.ts +21 -21
- package/dist/index.js +641 -16325
- package/dist/index.js.map +3 -92
- package/dist/managers/advanced.d.ts +3 -3
- package/dist/managers/application.d.ts +2 -2
- package/dist/managers/base.d.ts +1 -1
- package/dist/managers/emoji.d.ts +2 -2
- package/dist/managers/guild-resources.d.ts +6 -6
- package/dist/managers/guild.d.ts +3 -3
- package/dist/managers/index.d.ts +4 -8
- package/dist/managers/index.js +51 -4060
- package/dist/managers/index.js.map +3 -29
- package/dist/managers/member.d.ts +2 -2
- package/dist/managers/message.d.ts +14 -6
- package/dist/managers/role.d.ts +2 -2
- package/dist/managers/thread.d.ts +3 -3
- package/dist/managers/user.d.ts +7 -4
- package/dist/rest/index.d.ts +10 -35
- package/dist/rest/index.js +57 -2184
- package/dist/rest/index.js.map +3 -18
- package/dist/rest/limiter.d.ts +9 -1
- package/dist/rest/webhook.d.ts +2 -8
- package/dist/sharding/bus.d.ts +2 -2
- package/dist/sharding/cluster.d.ts +45 -5
- package/dist/sharding/context.d.ts +45 -3
- package/dist/sharding/gateway-bot.d.ts +2 -1
- package/dist/sharding/health.d.ts +1 -1
- package/dist/sharding/identify.d.ts +12 -0
- package/dist/sharding/index.d.ts +44 -11
- package/dist/sharding/index.js +60 -4620
- package/dist/sharding/index.js.map +3 -33
- package/dist/sharding/supervisor.d.ts +4 -2
- package/dist/structures/audit-log.d.ts +1 -1
- package/dist/structures/base.d.ts +6 -6
- package/dist/structures/channels.d.ts +1 -1
- package/dist/structures/embed.d.ts +1 -1
- package/dist/structures/index.d.ts +14 -14
- package/dist/structures/index.js +109 -2382
- package/dist/structures/index.js.map +3 -16
- package/dist/structures/interactions.d.ts +13 -9
- package/dist/structures/options.d.ts +1 -1
- package/dist/structures/permissions.d.ts +2 -2
- package/dist/structures/resources.d.ts +10 -10
- package/dist/structures/sticker.d.ts +1 -1
- package/dist/types/gateway.d.ts +2 -79
- package/dist/types/index.d.ts +0 -6
- package/dist/types/index.js +41 -272
- package/dist/types/index.js.map +3 -6
- package/dist/utils/index.js +23 -209
- package/dist/utils/index.js.map +3 -5
- package/dist/voice/connection.d.ts +200 -0
- package/dist/voice/crypto.d.ts +50 -0
- package/dist/voice/dave.d.ts +85 -0
- package/dist/voice/effects.d.ts +31 -0
- package/dist/voice/emitter.d.ts +14 -0
- package/dist/voice/gateway.d.ts +59 -0
- package/dist/voice/index.d.ts +25 -228
- package/dist/voice/index.js +111 -442
- package/dist/voice/index.js.map +3 -4
- package/dist/voice/libopus.d.ts +88 -0
- package/dist/voice/networking.d.ts +80 -0
- package/dist/voice/ogg.d.ts +24 -0
- package/dist/voice/opus.d.ts +18 -0
- package/dist/voice/player.d.ts +79 -0
- package/dist/voice/protocol.d.ts +76 -0
- package/dist/voice/receiver.d.ts +59 -0
- package/dist/voice/resource.d.ts +102 -0
- package/dist/voice/udp.d.ts +40 -0
- package/dist/ws/close-codes.d.ts +1 -1
- package/dist/ws/index.d.ts +25 -4
- package/dist/ws/index.js +47 -1336
- package/dist/ws/index.js.map +3 -17
- package/dist/ws/protocol.d.ts +1 -1
- package/dist/ws/send-budget.d.ts +0 -2
- package/dist/ws/state.d.ts +0 -10
- package/package.json +19 -16
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../packages/sharding/src/gateway-bot.ts", "../packages/sharding/src/context.ts", "../packages/sharding/src/protocol.ts", "../packages/sharding/src/thread.ts", "../packages/sharding/src/worker.ts", "../packages/sharding/src/transport.ts", "../packages/sharding/src/bus.ts", "../packages/sharding/src/supervisor.ts", "../packages/sharding/src/cluster.ts", "../packages/sharding/src/identify.ts", "../packages/sharding/src/remote.ts", "../packages/sharding/src/remote-protocol.ts", "../packages/sharding/src/requests.ts", "../packages/sharding/src/host.ts", "../packages/sharding/src/leader.ts", "../packages/sharding/src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import packageJson from \"../package.json\" with { type: \"json\" };\n\n/** `/gateway/bot` information used to pace shard startup. */\nexport interface GatewayBotInfo {\n /** Recommended shard count. */\n shards: number;\n /** IDENTIFY budget, when Discord reported it. */\n sessionStartLimit?: {\n total: number;\n remaining: number;\n /** Milliseconds until `remaining` resets. */\n resetAfter: number;\n maxConcurrency: number;\n };\n}\n\n/**\n * Asks Discord for the recommended shard count and the IDENTIFY budget.\n * @throws {Error} If the request fails or the answer is not usable.\n */\nexport async function fetchGatewayBot(token: string): Promise<GatewayBotInfo> {\n const response = await fetch(\"https://discord.com/api/v10/gateway/bot\", {\n headers: {\n Authorization: `Bot ${token}`,\n \"User-Agent\": `Lunibee/${packageJson.version}`,\n },\n });\n if (!response.ok)\n throw new Error(\n `Gateway discovery failed with status ${response.status}`,\n );\n const data = (await response.json()) as {\n shards?: unknown;\n session_start_limit?: {\n total?: unknown;\n remaining?: unknown;\n reset_after?: unknown;\n max_concurrency?: unknown;\n };\n };\n if (\n typeof data.shards !== \"number\" ||\n !Number.isInteger(data.shards) ||\n data.shards < 1\n )\n throw new Error(\"Gateway discovery returned an invalid shard count.\");\n const limit = data.session_start_limit;\n const info: GatewayBotInfo = { shards: data.shards };\n if (\n limit &&\n typeof limit.total === \"number\" &&\n typeof limit.remaining === \"number\" &&\n typeof limit.reset_after === \"number\" &&\n typeof limit.max_concurrency === \"number\"\n )\n info.sessionStartLimit = {\n total: limit.total,\n remaining: limit.remaining,\n resetAfter: limit.reset_after,\n maxConcurrency: Math.max(1, limit.max_concurrency),\n };\n return info;\n}\n\n/** Discord's hard limit: a shard may serve at most this many guilds. */\nexport const MAX_GUILDS_PER_SHARD = 2500;\n\n/** Checks a `guildsPerShard` option. @throws {RangeError} Unless it is an integer from 1 to 2500. */\nexport function assertGuildsPerShard(value: number): void {\n if (!Number.isInteger(value) || value < 1 || value > MAX_GUILDS_PER_SHARD)\n throw new RangeError(\n `guildsPerShard must be an integer from 1 to ${MAX_GUILDS_PER_SHARD}.`,\n );\n}\n\n/** How many shards keep each at or under `guildsPerShard` guilds (at least one). */\nexport function shardsForGuilds(\n guilds: number,\n guildsPerShard: number,\n): number {\n assertGuildsPerShard(guildsPerShard);\n if (!Number.isInteger(guilds) || guilds < 0)\n throw new RangeError(\"guildCount must be a non-negative integer.\");\n return Math.max(1, Math.ceil(guilds / guildsPerShard));\n}\n\n/**\n * Asks Discord how many guilds the bot is in (`approximate_guild_count` of the application).\n * @throws {Error} If the request fails or the answer is not usable.\n */\nexport async function fetchGuildCount(token: string): Promise<number> {\n const response = await fetch(\n \"https://discord.com/api/v10/applications/@me\",\n {\n headers: {\n Authorization: `Bot ${token}`,\n \"User-Agent\": `Lunibee/${packageJson.version}`,\n },\n },\n );\n if (!response.ok)\n throw new Error(\n `Guild count discovery failed with status ${response.status}`,\n );\n const data = (await response.json()) as {\n approximate_guild_count?: unknown;\n };\n const count = data.approximate_guild_count;\n if (typeof count !== \"number\" || !Number.isInteger(count) || count < 0)\n throw new Error(\"Discord did not report a guild count.\");\n return count;\n}\n\n/**\n * The shard count to use for `\"auto\"`: Discord's recommendation, or, with\n * `guildsPerShard`, enough shards for the bot's guild count (`guildCount`, or\n * what Discord reports), but never fewer than the recommendation: a stale or\n * low guild count must not leave shards over Discord's 2,500-guild limit.\n */\nexport async function resolveShardCount(\n token: string,\n info: GatewayBotInfo,\n options: { guildsPerShard?: number; guildCount?: number },\n): Promise<number> {\n if (options.guildsPerShard === undefined) return info.shards;\n return Math.max(\n info.shards,\n shardsForGuilds(\n options.guildCount ?? (await fetchGuildCount(token)),\n options.guildsPerShard,\n ),\n );\n}\n",
|
|
6
|
+
"/**\n * What a cluster worker knows about itself. `ClusterManager` sets it through\n * environment variables; {@link readClusterContext} reads it back.\n */\nexport interface ClusterContext {\n /** This cluster's ID. */\n clusterId: number;\n /** How many clusters the deployment started. */\n clusterCount: number;\n /** Total shards in the deployment (what Discord calls `num_shards`). */\n shardCount: number;\n /** Shards this cluster owns. */\n shardIds: number[];\n /** Whether the parent hands out IDENTIFY slots for this cluster. */\n coordinatedIdentify: boolean;\n /**\n * `\"sequential\"`: hold each IDENTIFY slot until the shard is READY, so the\n * deployment starts one shard at a time. `\"safe\"` (default): release it once\n * the IDENTIFY is sent.\n */\n startup?: \"safe\" | \"sequential\";\n /** Sequential startup: ms a shard may take to become READY (`LUNIBEE_READY_TIMEOUT`, the parent's `identify.holdTimeout`). */\n readyTimeout?: number;\n}\n\n/** Set to `1` in every process `ClusterManager` forks, so a worker can tell it is one. */\nexport const WORKER_MARKER = \"LUNIBEE_CLUSTER_WORKER\";\n\n/** Whether this process was forked by a `ClusterManager`. */\nexport function isClusterWorker(\n env: Record<string, string | undefined> = process.env,\n): boolean {\n return env[WORKER_MARKER] === \"1\";\n}\n\n/** Checks that shard IDs are unique integers in `[0, total)`. @throws {RangeError} Otherwise. */\nexport function validateShardIds(\n shardIds: readonly number[],\n total: number,\n): void {\n if (!Number.isInteger(total) || total < 1)\n throw new RangeError(\"Shard count must be a positive integer.\");\n const seen = new Set<number>();\n for (const id of shardIds) {\n if (!Number.isInteger(id) || id < 0 || id >= total)\n throw new RangeError(\n `Shard ID ${String(id)} is outside 0-${total - 1}.`,\n );\n if (seen.has(id))\n throw new RangeError(`Shard ID ${id} is listed twice.`);\n seen.add(id);\n }\n}\n\n/**\n * Splits shard IDs across clusters in contiguous blocks, in order. When they do\n * not divide evenly the first clusters get one more: 10 shards over 3 clusters\n * are `[0-3] [4-6] [7-9]`. The same input always gives the same output.\n * @returns One list per cluster (empty ones at the end when there are more clusters than shards).\n * @throws {RangeError} If `clusterCount` is not a positive integer.\n */\nexport function allocateShards(\n shardIds: readonly number[],\n clusterCount: number,\n): number[][] {\n if (!Number.isInteger(clusterCount) || clusterCount < 1)\n throw new RangeError(\"clusterCount must be a positive integer.\");\n const base = Math.floor(shardIds.length / clusterCount);\n const extra = shardIds.length % clusterCount;\n const chunks: number[][] = [];\n let offset = 0;\n for (let cluster = 0; cluster < clusterCount; cluster++) {\n const size = base + (cluster < extra ? 1 : 0);\n chunks.push(shardIds.slice(offset, offset + size));\n offset += size;\n }\n return chunks;\n}\n\n/** How {@link planClusters} chose the cluster count. */\nexport type ClusterCountSource =\n \"clusterCount\" | \"shardsPerCluster\" | \"default\";\n\n/** Options for {@link planClusters}; set `clusterCount` or `shardsPerCluster`, not both. */\nexport interface ClusterPlanOptions {\n /** The deployment's shards, in order. */\n shardIds: readonly number[];\n /** Split the shards over this many clusters (Mode A). */\n clusterCount?: number;\n /** Fill clusters with this many shards each; the last may have fewer (Mode B). */\n shardsPerCluster?: number;\n /** Cluster count when neither is set (Mode C), e.g. the number of CPUs. */\n defaultClusterCount: number;\n}\n\n/** The result of {@link planClusters}. */\nexport interface ClusterPlan {\n /** Shards of each cluster, by cluster ID. Never empty: there are at most as many clusters as shards. */\n clusters: number[][];\n /** Where the cluster count came from. */\n source: ClusterCountSource;\n /** The count asked for, before capping at the number of shards. */\n requested: number;\n}\n\nfunction positiveInteger(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 1)\n throw new RangeError(\n `${name} must be a positive integer, got ${String(value)}.`,\n );\n}\n\n/**\n * Decides which cluster runs which shard, without starting anything:\n * - `clusterCount`: every shard, split in contiguous blocks over that many clusters;\n * - `shardsPerCluster`: `ceil(shards / shardsPerCluster)` clusters of that size, the last one partial;\n * - neither: `defaultClusterCount` clusters.\n *\n * There are never more clusters than shards: a larger count is capped (an\n * empty cluster would hold a process for nothing). Every shard is in exactly one cluster.\n * @throws {RangeError} For both options at once, or a count that is not a positive integer.\n */\nexport function planClusters(options: ClusterPlanOptions): ClusterPlan {\n const { shardIds, clusterCount, shardsPerCluster } = options;\n if (clusterCount !== undefined && shardsPerCluster !== undefined)\n throw new RangeError(\n \"Set either clusterCount or shardsPerCluster, not both: the cluster count follows from shardsPerCluster.\",\n );\n if (shardIds.length === 0)\n throw new RangeError(\"There are no shards to allocate.\");\n let requested: number;\n let source: ClusterCountSource;\n let clusters: number[][];\n if (shardsPerCluster !== undefined) {\n positiveInteger(\"shardsPerCluster\", shardsPerCluster);\n source = \"shardsPerCluster\";\n requested = Math.ceil(shardIds.length / shardsPerCluster);\n clusters = [];\n for (let i = 0; i < shardIds.length; i += shardsPerCluster)\n clusters.push(shardIds.slice(i, i + shardsPerCluster));\n } else {\n requested = clusterCount ?? options.defaultClusterCount;\n positiveInteger(\n clusterCount === undefined ? \"defaultClusterCount\" : \"clusterCount\",\n requested,\n );\n source = clusterCount === undefined ? \"default\" : \"clusterCount\";\n clusters = allocateShards(\n shardIds,\n Math.min(requested, shardIds.length),\n );\n }\n assertCompleteAllocation(clusters, shardIds);\n return { clusters, source, requested };\n}\n\n/**\n * Proves an allocation covers `expected` exactly once.\n * @throws {Error} Naming the missing, duplicate or unexpected shard IDs.\n */\nexport function assertCompleteAllocation(\n chunks: readonly (readonly number[])[],\n expected: readonly number[],\n): void {\n const owners = new Map<number, number>();\n const duplicate: number[] = [];\n chunks.forEach((chunk, cluster) => {\n for (const id of chunk) {\n if (owners.has(id)) duplicate.push(id);\n owners.set(id, cluster);\n }\n });\n const want = new Set(expected);\n const missing = expected.filter((id) => !owners.has(id));\n const extra = [...owners.keys()].filter((id) => !want.has(id));\n if (duplicate.length || missing.length || extra.length)\n throw new Error(\n `Shard allocation is not exact: duplicate [${duplicate}], missing [${missing}], unexpected [${extra}].`,\n );\n}\n\nfunction parseInteger(name: string, value: string | undefined): number {\n if (value === undefined || !/^\\d+$/.test(value))\n throw new RangeError(`${name} must be a non-negative integer.`);\n return Number(value);\n}\n\n/**\n * Reads the worker contract from the environment: `CLUSTER_ID`, `CLUSTER_COUNT`,\n * `SHARD_COUNT`, `SHARD_LIST` (comma separated) and `LUNIBEE_IDENTIFY`.\n * @returns The context, or `null` when this process is not a cluster worker.\n * @throws {RangeError} If the variables are present but malformed.\n */\nexport function readClusterContext(\n env: Record<string, string | undefined> = process.env,\n): ClusterContext | null {\n if (!isClusterWorker(env)) return null;\n const shardCount = parseInteger(\"SHARD_COUNT\", env.SHARD_COUNT);\n const shardIds = (env.SHARD_LIST ?? \"\")\n .split(\",\")\n .filter((part) => part !== \"\")\n .map((part) => parseInteger(\"SHARD_LIST\", part));\n validateShardIds(shardIds, shardCount);\n if (shardIds.length === 0)\n throw new RangeError(\"SHARD_LIST names no shards for this cluster.\");\n const clusterId = parseInteger(\"CLUSTER_ID\", env.CLUSTER_ID);\n const clusterCount = parseInteger(\"CLUSTER_COUNT\", env.CLUSTER_COUNT);\n if (clusterCount < 1 || clusterId >= clusterCount)\n throw new RangeError(\n `CLUSTER_ID ${clusterId} is outside the ${clusterCount} cluster(s).`,\n );\n return {\n clusterId,\n clusterCount,\n shardCount,\n shardIds,\n coordinatedIdentify: env.LUNIBEE_IDENTIFY === \"coordinated\",\n startup: env.LUNIBEE_STARTUP === \"sequential\" ? \"sequential\" : \"safe\",\n ...(env.LUNIBEE_READY_TIMEOUT === undefined\n ? {}\n : {\n readyTimeout: Math.max(\n 1,\n parseInteger(\n \"LUNIBEE_READY_TIMEOUT\",\n env.LUNIBEE_READY_TIMEOUT,\n ),\n ),\n }),\n };\n}\n",
|
|
7
|
+
"import type { WorkerReport } from \"./health.js\";\n\n/** Marks a cluster control frame on the IPC channel, next to the shard-bus frames. */\nexport const CLUSTER_FRAME = \"__lunibeeCluster\";\n/** The `source` and reply `target` of the parent process on the shard bus. */\nexport const PARENT_ID = -1;\n/** Built-in cluster request: the worker answers with a {@link WorkerReport}. */\nexport const REQUEST_HEALTH = \"__lunibee:health\";\n/** Built-in cluster request: the worker starts the given shards (recovery). */\nexport const REQUEST_ASSIGN = \"__lunibee:assign\";\n\n/** Frames a worker sends to the parent. */\nexport type ChildFrame =\n | { kind: \"ready\"; shardIds: number[] }\n | { kind: \"identify\"; id: number; shardId: number }\n | { kind: \"identify-done\"; shardId: number }\n | { kind: \"reply\"; id: string; result?: unknown; error?: string }\n | { kind: \"health\"; report: WorkerReport };\n\n/** Frames the parent sends to a worker. */\nexport type ParentFrame =\n | { kind: \"identify-grant\"; id: number }\n | { kind: \"identify-deny\"; id: number; error: string }\n | { kind: \"request\"; id: string; type: string; data: unknown };\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\nconst isShardId = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n\n/** Validates the body of a control frame from a worker; `null` when it is malformed. */\nexport function parseChildFrame(value: unknown): ChildFrame | null {\n if (!isRecord(value)) return null;\n switch (value.kind) {\n case \"ready\":\n return Array.isArray(value.shardIds) &&\n value.shardIds.every(isShardId)\n ? { kind: \"ready\", shardIds: value.shardIds }\n : null;\n case \"identify\":\n return typeof value.id === \"number\" && isShardId(value.shardId)\n ? { kind: \"identify\", id: value.id, shardId: value.shardId }\n : null;\n case \"identify-done\":\n return isShardId(value.shardId)\n ? { kind: \"identify-done\", shardId: value.shardId }\n : null;\n case \"reply\":\n return typeof value.id === \"string\" &&\n (value.error === undefined || typeof value.error === \"string\")\n ? {\n kind: \"reply\",\n id: value.id,\n ...(value.error !== undefined\n ? { error: value.error }\n : { result: value.result }),\n }\n : null;\n case \"health\":\n return isRecord(value.report) &&\n Array.isArray(value.report.shards) &&\n typeof value.report.reportedAt === \"number\"\n ? { kind: \"health\", report: value.report as never }\n : null;\n default:\n return null;\n }\n}\n\n/** Validates the body of a control frame from the parent; `null` when it is malformed. */\nexport function parseParentFrame(value: unknown): ParentFrame | null {\n if (!isRecord(value)) return null;\n switch (value.kind) {\n case \"identify-grant\":\n return typeof value.id === \"number\"\n ? { kind: \"identify-grant\", id: value.id }\n : null;\n case \"identify-deny\":\n return typeof value.id === \"number\" &&\n typeof value.error === \"string\"\n ? { kind: \"identify-deny\", id: value.id, error: value.error }\n : null;\n case \"request\":\n return typeof value.id === \"string\" &&\n typeof value.type === \"string\"\n ? {\n kind: \"request\",\n id: value.id,\n type: value.type,\n data: value.data,\n }\n : null;\n default:\n return null;\n }\n}\n",
|
|
8
|
+
"import type { ChildProcess } from \"node:child_process\";\nimport { EventEmitter } from \"node:events\";\nimport {\n isMainThread,\n parentPort,\n Worker,\n workerData,\n type ResourceLimits,\n} from \"node:worker_threads\";\n\n/** Marks the `workerData` of a cluster that runs as a thread. */\nconst THREAD_MARKER = \"__lunibeeClusterThread\";\n\n/**\n * A cluster running as a worker thread, shaped like the `ChildProcess` that\n * `ClusterManager` supervises: frames travel with `postMessage`, and `kill()`\n * terminates the thread (a thread cannot run a signal handler, so `SIGTERM`\n * ends it at once, like a process without a handler).\n */\nclass ThreadChild extends EventEmitter {\n public readonly pid: number;\n public exitCode: number | null = null;\n public signalCode: NodeJS.Signals | null = null;\n public connected = true;\n readonly #worker: Worker;\n #signal?: NodeJS.Signals;\n\n public constructor(worker: Worker) {\n super();\n this.#worker = worker;\n this.pid = worker.threadId;\n worker.on(\"message\", (frame: unknown) => this.emit(\"message\", frame));\n worker.on(\"error\", (error: unknown) => this.emit(\"error\", error));\n worker.once(\"exit\", (code: number) => {\n this.connected = false;\n if (this.#signal) {\n this.signalCode = this.#signal;\n this.emit(\"exit\", null, this.#signal);\n } else {\n this.exitCode = code;\n this.emit(\"exit\", code, null);\n }\n });\n }\n\n public send(frame: unknown): boolean {\n if (!this.connected) return false;\n try {\n this.#worker.postMessage(frame);\n return true;\n } catch {\n return false;\n }\n }\n\n public kill(signal: NodeJS.Signals = \"SIGTERM\"): boolean {\n if (!this.connected) return false;\n this.#signal ??= signal;\n void this.#worker.terminate();\n return true;\n }\n}\n\n/** Options for {@link threadFork}. */\nexport interface ThreadForkOptions {\n /** Heap limits for each cluster thread (`worker_threads` `resourceLimits`). */\n resourceLimits?: ResourceLimits;\n}\n\n/**\n * Runs each cluster as a worker thread of the manager's process instead of a\n * forked process: pass it as `ClusterManager`'s `fork`. Threads start faster\n * and use less memory; a crash that takes the process down (an out-of-memory\n * abort, a native fault) takes every cluster with it. The worker script is the\n * same: `ShardManager`, `ShardBus` and `ClusterWorker` find the parent through\n * `parentPort`.\n */\nexport function threadFork(\n options: ThreadForkOptions = {},\n): (script: string, env: Record<string, string | undefined>) => ChildProcess {\n return (script, env) => {\n const defined: Record<string, string> = {};\n for (const [key, value] of Object.entries(env))\n if (value !== undefined) defined[key] = value;\n const worker = new Worker(script, {\n env: defined,\n workerData: { [THREAD_MARKER]: true },\n ...(options.resourceLimits\n ? { resourceLimits: options.resourceLimits }\n : {}),\n });\n return new ThreadChild(worker) as unknown as ChildProcess;\n };\n}\n\n/** The parent link of a cluster thread: the part of `process` a worker uses. */\nexport interface ThreadEndpoint {\n send(message: unknown): boolean;\n on(event: string, listener: (...args: never[]) => void): ThreadEndpoint;\n off(event: string, listener: (...args: never[]) => void): ThreadEndpoint;\n}\n\nlet endpoint: ThreadEndpoint | undefined;\n\n/** The parent link when this code runs in a cluster thread started by {@link threadFork}, otherwise `undefined`. */\nexport function threadEndpoint(): ThreadEndpoint | undefined {\n const port = parentPort;\n if (\n isMainThread ||\n !port ||\n !(workerData as Record<string, unknown> | null)?.[THREAD_MARKER]\n )\n return undefined;\n endpoint ??= portEndpoint(port);\n return endpoint;\n}\n\n/** Wraps a message port as a worker endpoint. @internal */\nexport function portEndpoint(port: {\n postMessage(message: unknown): void;\n on(event: \"message\", listener: (frame: unknown) => void): unknown;\n off(event: \"message\", listener: (frame: unknown) => void): unknown;\n}): ThreadEndpoint {\n const wrapped: ThreadEndpoint = {\n send: (message) => {\n port.postMessage(message);\n return true;\n },\n // Only messages cross a thread boundary; `disconnect` never fires.\n on: (event, listener) => {\n if (event === \"message\")\n port.on(\"message\", listener as (frame: unknown) => void);\n return wrapped;\n },\n off: (event, listener) => {\n if (event === \"message\")\n port.off(\"message\", listener as (frame: unknown) => void);\n return wrapped;\n },\n };\n return wrapped;\n}\n",
|
|
9
|
+
"import { readClusterContext, type ClusterContext } from \"./context.js\";\nimport type { WorkerReport } from \"./health.js\";\nimport { CLUSTER_FRAME, parseParentFrame } from \"./protocol.js\";\nimport { threadEndpoint } from \"./thread.js\";\n\n/** Asks for the right to IDENTIFY, and gives it back. */\nexport interface IdentifyGate {\n /** Resolves when `shardId` may IDENTIFY now. Rejects if the parent refuses or goes away. */\n acquire(shardId: number): Promise<void>;\n /** `shardId` has sent its IDENTIFY. */\n release(shardId: number): void;\n}\n\n/** The part of `process` a worker uses. */\nexport interface WorkerEndpoint {\n send?(message: unknown): unknown;\n on(event: string, listener: (...args: never[]) => void): unknown;\n off(event: string, listener: (...args: never[]) => void): unknown;\n}\n\ntype RequestHandler = (data: unknown) => unknown;\n\n/**\n * The worker side of a cluster. It tells the parent when the process is\n * ready, answers the parent's requests, and asks the parent for IDENTIFY\n * slots. `ShardManager` uses it automatically inside a cluster; use it\n * directly when you run your own Gateways.\n */\nexport class ClusterWorker {\n static #current: ClusterWorker | undefined;\n\n /** The worker of this process, or `undefined` when it was not forked by a `ClusterManager`. */\n public static current(): ClusterWorker | undefined {\n if (!ClusterWorker.#current) {\n const context = readClusterContext();\n const endpoint =\n typeof process.send === \"function\"\n ? (process as unknown as WorkerEndpoint)\n : threadEndpoint();\n if (context && endpoint)\n ClusterWorker.#current = new ClusterWorker(context, endpoint);\n }\n return ClusterWorker.#current;\n }\n\n /** Forgets the process-wide worker (tests). */\n public static reset(): void {\n ClusterWorker.#current?.close();\n ClusterWorker.#current = undefined;\n }\n\n public readonly context: ClusterContext;\n readonly #endpoint: WorkerEndpoint;\n readonly #handlers = new Map<string, RequestHandler>();\n readonly #waiting = new Map<\n number,\n { resolve: () => void; reject: (error: Error) => void }\n >();\n readonly #onMessage: (frame: unknown) => void;\n readonly #onDisconnect: () => void;\n #counter = 0;\n #closed = false;\n\n public constructor(\n context: ClusterContext,\n endpoint: WorkerEndpoint = process as unknown as WorkerEndpoint,\n options: {\n /**\n * Exit when the parent's IPC channel closes. Without a parent\n * nothing coordinates IDENTIFY or fences this process, and its\n * Gateway sessions would keep running beside a replacement.\n * Default: true for the real process, false for an injected endpoint.\n */\n exitOnParentLoss?: boolean;\n /** Replaces `process.exit`; for tests. */\n exit?: (code: number) => void;\n } = {},\n ) {\n if (typeof endpoint.send !== \"function\")\n throw new Error(\n \"ClusterWorker needs an IPC channel: run it in a process forked by ClusterManager.\",\n );\n this.context = context;\n this.#endpoint = endpoint;\n this.#onMessage = (frame) => this.#handle(frame);\n const exitOnLoss =\n options.exitOnParentLoss ??\n endpoint === (process as unknown as WorkerEndpoint);\n const exit = options.exit ?? ((code: number) => process.exit(code));\n this.#onDisconnect = () => {\n this.#failAll(\"The parent went away.\");\n if (exitOnLoss && !this.#closed) exit(1);\n };\n endpoint.on(\"message\", this.#onMessage);\n endpoint.on(\"disconnect\", this.#onDisconnect);\n }\n\n /** The gate to use when the parent coordinates IDENTIFY, otherwise `undefined`. */\n public get identifyGate(): IdentifyGate | undefined {\n return this.context.coordinatedIdentify ? this.#gate : undefined;\n }\n\n readonly #gate: IdentifyGate = {\n acquire: (shardId) => {\n if (this.#closed)\n return Promise.reject(\n new Error(\"The cluster worker is closed.\"),\n );\n const id = ++this.#counter;\n return new Promise<void>((resolve, reject) => {\n this.#waiting.set(id, { resolve, reject });\n const error = this.#send({ kind: \"identify\", id, shardId });\n if (error) {\n this.#waiting.delete(id);\n reject(error);\n }\n });\n },\n release: (shardId) => this.#send({ kind: \"identify-done\", shardId }),\n };\n\n /** Tells the parent every shard of this process finished its handshake. */\n public ready(shardIds: readonly number[] = this.context.shardIds): void {\n this.#send({ kind: \"ready\", shardIds: [...shardIds] });\n }\n\n /** Pushes a health report to the parent without being asked. */\n public reportHealth(report: WorkerReport): void {\n this.#send({ kind: \"health\", report });\n }\n\n /**\n * Answers requests of `type` sent with `ClusterManager.requestCluster()`.\n * The return value (or thrown error) goes back to the caller.\n */\n public onRequest(type: string, handler: RequestHandler): this {\n if (!type.trim() || typeof handler !== \"function\")\n throw new TypeError(\"A request type and handler are required.\");\n this.#handlers.set(type, handler);\n return this;\n }\n\n /** Stops listening and rejects pending IDENTIFY requests. */\n public close(): void {\n this.#closed = true;\n this.#endpoint.off(\"message\", this.#onMessage);\n this.#endpoint.off(\"disconnect\", this.#onDisconnect);\n this.#failAll(\"The cluster worker closed.\");\n }\n\n /** @returns The error when the frame could not be sent. */\n #send(body: Record<string, unknown>): Error | undefined {\n try {\n this.#endpoint.send!({ [CLUSTER_FRAME]: body });\n return undefined;\n } catch (thrown) {\n const error =\n thrown instanceof Error ? thrown : new Error(String(thrown));\n // Only a closed channel ends every wait. A frame that cannot be\n // serialized (a BigInt under JSON IPC) fails on its own.\n const endpoint = this.#endpoint as { connected?: boolean };\n if (\n endpoint.connected === false ||\n (error as { code?: unknown }).code === \"ERR_IPC_CHANNEL_CLOSED\"\n )\n this.#failAll(error.message);\n return error;\n }\n }\n\n #failAll(reason: string): void {\n for (const waiter of this.#waiting.values())\n waiter.reject(new Error(reason));\n this.#waiting.clear();\n }\n\n #handle(frame: unknown): void {\n if (!frame || typeof frame !== \"object\" || !(CLUSTER_FRAME in frame))\n return;\n const parsed = parseParentFrame(\n (frame as Record<string, unknown>)[CLUSTER_FRAME],\n );\n if (!parsed) return;\n if (\n parsed.kind === \"identify-grant\" ||\n parsed.kind === \"identify-deny\"\n ) {\n const waiter = this.#waiting.get(parsed.id);\n if (!waiter) return;\n this.#waiting.delete(parsed.id);\n if (parsed.kind === \"identify-grant\") waiter.resolve();\n else waiter.reject(new Error(parsed.error));\n return;\n }\n const handler = this.#handlers.get(parsed.type);\n const id = parsed.id;\n if (!handler) {\n this.#send({\n kind: \"reply\",\n id,\n error: `No handler for \"${parsed.type}\".`,\n });\n return;\n }\n void (async () => {\n try {\n const failed = this.#send({\n kind: \"reply\",\n id,\n result: await handler(parsed.data),\n });\n // Answer anyway, so the caller sees why instead of a timeout.\n if (failed)\n this.#send({\n kind: \"reply\",\n id,\n error: `The reply could not be sent: ${failed.message}`,\n });\n } catch (error) {\n this.#send({\n kind: \"reply\",\n id,\n error:\n error instanceof Error ? error.message : String(error),\n });\n }\n })();\n }\n}\n",
|
|
10
|
+
"import type { ShardMessage } from \"./bus.js\";\nimport { threadEndpoint } from \"./thread.js\";\n\n/** Carries {@link ShardMessage}s between the processes or threads that run shards. */\nexport interface ShardBusTransport {\n /** Sends a message to the other shards. Delivery to the sender itself is not required. */\n post(message: ShardMessage): void;\n /** Listens for messages from other shards. */\n onMessage(listener: (message: ShardMessage) => void): void;\n /** Listens for the shards of a cluster that died, so pending requests to them can fail early. */\n onPeerDown?(listener: (shardIds: readonly number[]) => void): void;\n /** Stops listening and releases the channel. */\n close(): void;\n}\n\n/** One process: shards in the same process (or worker threads of it) talk over a `BroadcastChannel`. */\nexport class BroadcastChannelTransport implements ShardBusTransport {\n readonly #channel: BroadcastChannel;\n\n public constructor(name: string) {\n this.#channel = new BroadcastChannel(name);\n }\n\n public post(message: ShardMessage): void {\n this.#channel.postMessage(message);\n }\n\n public onMessage(listener: (message: ShardMessage) => void): void {\n this.#channel.addEventListener(\"message\", (event) =>\n listener(event.data as ShardMessage),\n );\n }\n\n public close(): void {\n this.#channel.close();\n }\n}\n\n/** Marks a bus message on the IPC channel, so it cannot be confused with the bot's own `process.send()` traffic. */\nexport const BUS_FRAME = \"__lunibeeBus\";\n/** Marks the parent's notice that a cluster's shards are gone. */\nexport const PEER_DOWN_FRAME = \"__lunibeePeerDown\";\n\n/** The part of `process` (or a child process handle) the IPC transport uses. */\nexport interface IpcEndpoint {\n send?(message: unknown): unknown;\n on(event: \"message\", listener: (message: unknown) => void): unknown;\n off(event: \"message\", listener: (message: unknown) => void): unknown;\n}\n\n/**\n * Forked clusters: a worker `send()`s to its parent, and the parent\n * ({@link ClusterManager}) relays to every other worker. Selected\n * automatically in a worker forked by `ClusterManager`.\n */\n/** The IPC transports open on each endpoint, so buses in one process can reach each other. */\nconst LOCAL = new WeakMap<IpcEndpoint, Set<IpcTransport>>();\n\nexport class IpcTransport implements ShardBusTransport {\n readonly #endpoint: IpcEndpoint;\n readonly #listener: (frame: unknown) => void;\n #message?: (message: ShardMessage) => void;\n #down?: (shardIds: readonly number[]) => void;\n\n public constructor(endpoint: IpcEndpoint = process as IpcEndpoint) {\n if (typeof endpoint.send !== \"function\")\n throw new Error(\n \"IpcTransport needs an IPC channel: run the shard in a process forked by ClusterManager.\",\n );\n this.#endpoint = endpoint;\n this.#listener = (frame) => {\n const record = frame as Record<string, unknown> | null;\n if (!record || typeof record !== \"object\") return;\n if (BUS_FRAME in record)\n this.#message?.(record[BUS_FRAME] as ShardMessage);\n else if (PEER_DOWN_FRAME in record)\n this.#down?.(record[PEER_DOWN_FRAME] as number[]);\n };\n endpoint.on(\"message\", this.#listener);\n let local = LOCAL.get(endpoint);\n if (!local) LOCAL.set(endpoint, (local = new Set()));\n local.add(this);\n }\n\n /**\n * Sends to the parent, which relays to other clusters, and delivers to the\n * other buses in this process: the parent never sends a message back to the\n * cluster it came from, so without this a shard could not reach a shard in\n * its own worker. Each bus drops what is not addressed to it.\n */\n public post(message: ShardMessage): void {\n this.#endpoint.send!({ [BUS_FRAME]: message });\n for (const peer of LOCAL.get(this.#endpoint) ?? [])\n if (peer !== this) {\n const copy = structuredClone(message);\n queueMicrotask(() => peer.#message?.(copy));\n }\n }\n\n public onMessage(listener: (message: ShardMessage) => void): void {\n this.#message = listener;\n }\n\n public onPeerDown(listener: (shardIds: readonly number[]) => void): void {\n this.#down = listener;\n }\n\n public close(): void {\n this.#endpoint.off(\"message\", this.#listener);\n LOCAL.get(this.#endpoint)?.delete(this);\n }\n}\n\n/** Picks the transport for a bus: IPC inside a forked cluster, `BroadcastChannel` otherwise. */\nexport function defaultTransport(name: string): ShardBusTransport {\n if (process.env.LUNIBEE_SHARD_BUS === \"ipc\") {\n if (typeof process.send === \"function\") return new IpcTransport();\n const thread = threadEndpoint();\n if (thread) return new IpcTransport(thread);\n }\n return new BroadcastChannelTransport(name);\n}\n",
|
|
11
|
+
"import { defaultTransport, type ShardBusTransport } from \"./transport.js\";\n\n/** Message envelope exchanged between Lunibee shards. */\nexport interface ShardMessage<T = unknown> {\n /** Sending shard ID. */\n source: number;\n /** Target shard ID, or null for broadcast. */\n target: number | null;\n /** Application message type. */\n type: string;\n /** Message payload. */\n data: T;\n /** Unique message ID. */\n id: string;\n /** Set on requests that expect a reply (see {@link ShardBus.request}). */\n expectsReply?: boolean;\n}\n/** A reply collected by {@link ShardBus.broadcastRequest}. */\nexport interface ShardReply<R = unknown> {\n /** Replying shard ID. */\n shardId: number;\n /** Handler result, when it succeeded. */\n result?: R;\n /** Handler error message, when it failed. */\n error?: string;\n}\n/** Internal reply envelope type. */\nexport const REPLY_TYPE = \"__lunibee:reply\";\n/** Payload carried by a reply envelope. */\nexport type ReplyPayload = {\n requestId: string;\n result?: unknown;\n error?: string;\n};\n/** Handler invoked for shard messages. @typeParam T Message payload type. */\nexport type ShardMessageHandler<T = unknown> = (\n message: ShardMessage<T>,\n) => unknown;\n/** Listener for errors thrown or rejected by shard message handlers. */\nexport type ShardBusErrorHandler = (\n error: unknown,\n message: ShardMessage,\n) => void;\n/** Options for a {@link ShardBus}. */\nexport interface ShardBusOptions {\n /**\n * How messages travel. Defaults to IPC inside a cluster forked by\n * `ClusterManager`, and to a `BroadcastChannel` otherwise.\n */\n transport?: ShardBusTransport;\n /** Most requests waiting for a reply at once (default 10,000); more are rejected. */\n maxPending?: number;\n}\n/** Longest message type or ID a bus accepts. */\nconst MAX_NAME_LENGTH = 256;\n\n/** Checks the envelope of a message that arrived from a transport, which is untrusted input. */\nfunction isShardMessage(message: unknown): message is ShardMessage {\n if (!message || typeof message !== \"object\") return false;\n const m = message as Record<string, unknown>;\n return (\n typeof m.type === \"string\" &&\n m.type.length > 0 &&\n m.type.length <= MAX_NAME_LENGTH &&\n typeof m.id === \"string\" &&\n m.id.length <= MAX_NAME_LENGTH &&\n // -1 is the parent process (see ClusterManager.request()).\n Number.isInteger(m.source) &&\n (m.source as number) >= -1 &&\n (m.target === null ||\n (Number.isInteger(m.target) && (m.target as number) >= -1))\n );\n}\n\n/** Cross-shard messaging: send, broadcast, and request/reply over a {@link ShardBusTransport}. */\nexport class ShardBus {\n /** Underlying transport. */\n readonly #transport: ShardBusTransport;\n /** Most requests waiting for a reply at once. */\n readonly #maxPending: number;\n /** Message handlers by message type. */\n readonly #handlers = new Map<string, Set<ShardMessageHandler>>();\n /** `respond()` handlers by type, mapped to the wrappers actually registered. */\n readonly #responders = new Map<string, Map<unknown, ShardMessageHandler>>();\n /** Handler error listeners. */\n readonly #errorHandlers = new Set<ShardBusErrorHandler>();\n /** Current shard ID. */\n readonly #shardId: number;\n /** Application-specific channel namespace. */\n readonly #namespace: string;\n /** BroadcastChannel name used by this bus. */\n public readonly channelName: string;\n /** Monotonic message counter. */\n #counter = 0;\n /** Reply collectors for in-flight requests, by request message ID. */\n readonly #pending = new Map<\n string,\n {\n target: number | null;\n /** Shards that already answered; a shard counts once. */\n seen: Set<number>;\n reply: (reply: ShardReply) => void;\n fail: (error: Error) => void;\n /** Ends the wait now: a request rejects, a broadcast resolves with what it has. */\n cancel: (error: Error) => void;\n }\n >();\n #closed = false;\n /** Creates a shard bus. @param shardId Shard ID. @param channelName Application-specific channel name. @throws {RangeError} If shard ID is invalid. @throws {TypeError} If channel name is empty. */\n public constructor(\n shardId: number,\n channelName: string,\n options: ShardBusOptions = {},\n ) {\n if (!Number.isInteger(shardId) || shardId < 0)\n throw new RangeError(\"Shard ID must be a non-negative integer.\");\n if (!channelName?.trim())\n throw new TypeError(\n \"An application-specific shard channel name is required.\",\n );\n this.#shardId = shardId;\n this.#namespace = channelName.trim();\n this.channelName = this.#namespace;\n this.#maxPending = options.maxPending ?? 10_000;\n this.#transport =\n options.transport ?? defaultTransport(this.channelName);\n this.#transport.onMessage((message) => this.#dispatch(message));\n this.#transport.onPeerDown?.((shardIds) => this.#peerDown(shardIds));\n }\n /** Registers a message handler. @param type Message type. @param handler Handler callback. @returns This bus. @throws {TypeError} If type or handler is invalid. */\n public on<T>(type: string, handler: ShardMessageHandler<T>): this {\n if (!type.trim() || typeof handler !== \"function\")\n throw new TypeError(\"Shard message type and handler are required.\");\n let handlers = this.#handlers.get(type);\n if (!handlers) this.#handlers.set(type, (handlers = new Set()));\n handlers.add(handler as ShardMessageHandler);\n return this;\n }\n /** Removes a message handler. @param type Message type. @param handler Handler callback. @returns This bus. */\n public off<T>(\n type: string,\n handler:\n | ShardMessageHandler<T>\n | ((data: T, message: ShardMessage<T>) => unknown),\n ): this {\n const handlers = this.#handlers.get(type);\n handlers?.delete(handler as ShardMessageHandler);\n // A `respond()` handler is registered as a wrapper; find it by the original.\n const responder = this.#responders.get(type)?.get(handler);\n if (responder) {\n handlers?.delete(responder);\n this.#responders.get(type)!.delete(handler);\n }\n return this;\n }\n /** Registers a listener for errors thrown or rejected by message handlers. Without one, handler errors are isolated and dropped. @param handler Error listener. @returns This bus. @throws {TypeError} If handler is invalid. */\n public onError(handler: ShardBusErrorHandler): this {\n if (typeof handler !== \"function\")\n throw new TypeError(\"Shard bus error handler is required.\");\n this.#errorHandlers.add(handler);\n return this;\n }\n /** Removes a handler error listener. @param handler Error listener. @returns This bus. */\n public offError(handler: ShardBusErrorHandler): this {\n this.#errorHandlers.delete(handler);\n return this;\n }\n /** Sends a targeted shard message. @param target Target shard ID. @param type Message type. @param data Payload. @returns Unique message ID. */\n public send<T>(target: number, type: string, data: T): string {\n if (!Number.isInteger(target) || target < 0)\n throw new RangeError(\n \"Shard target must be a non-negative integer.\",\n );\n return this.#publish(target, type, data);\n }\n /** Broadcasts to all other shards. @param type Message type. @param data Payload. @returns Unique message ID. */\n public broadcast<T>(type: string, data: T): string {\n return this.#publish(null, type, data);\n }\n /**\n * Registers a handler that answers requests of `type`. Its return value (or\n * thrown error) is sent back to the requesting shard. Received code is\n * never evaluated.\n * @returns This bus.\n */\n public respond<T, R>(\n type: string,\n handler: (data: T, message: ShardMessage<T>) => R | Promise<R>,\n ): this {\n const wrapper: ShardMessageHandler<T> = async (message) => {\n if (!message.expectsReply) return;\n const reply: ReplyPayload = { requestId: message.id };\n try {\n reply.result = await handler(message.data, message);\n } catch (error) {\n reply.error =\n error instanceof Error ? error.message : String(error);\n }\n this.#publish(message.source, REPLY_TYPE, reply);\n };\n let byType = this.#responders.get(type);\n if (!byType) this.#responders.set(type, (byType = new Map()));\n byType.set(handler, wrapper as ShardMessageHandler);\n return this.on<T>(type, wrapper);\n }\n /**\n * Sends a request to one shard and resolves with its handler's result.\n * @throws {Error} If the handler failed or no reply arrives in time.\n */\n public async request<R = unknown, T = unknown>(\n target: number,\n type: string,\n data: T,\n timeoutMs = 5000,\n ): Promise<R> {\n if (!Number.isInteger(target) || target < 0)\n throw new RangeError(\n \"Shard target must be a non-negative integer.\",\n );\n const [reply] = await this.#collect<R>(\n target,\n type,\n data,\n timeoutMs,\n 1,\n true,\n );\n if (!reply)\n throw new Error(\n `Shard ${target} did not reply to \"${type}\" within ${timeoutMs}ms.`,\n );\n if (reply.error !== undefined) throw new Error(reply.error);\n return reply.result as R;\n }\n /**\n * Sends a request to every other shard and collects replies until\n * `expected` have arrived or `timeoutMs` elapses. A timeout or a shard\n * going away does not reject; a closed bus, a blank type or too many\n * pending requests does.\n */\n public broadcastRequest<R = unknown, T = unknown>(\n type: string,\n data: T,\n options: { timeoutMs?: number; expected?: number } = {},\n ): Promise<ShardReply<R>[]> {\n return this.#collect<R>(\n null,\n type,\n data,\n options.timeoutMs ?? 5000,\n options.expected ?? Number.POSITIVE_INFINITY,\n );\n }\n /** Publishes a request and gathers replies. */\n #collect<R>(\n target: number | null,\n type: string,\n data: unknown,\n timeoutMs: number,\n expected: number,\n rejectOnPeerDown = false,\n ): Promise<ShardReply<R>[]> {\n const replies: ShardReply<R>[] = [];\n if (this.#closed)\n return Promise.reject(new Error(\"ShardBus is closed.\"));\n if (this.#pending.size >= this.#maxPending)\n return Promise.reject(\n new Error(\n `Too many shard requests waiting for a reply (${this.#maxPending}).`,\n ),\n );\n return new Promise((resolve, reject) => {\n let requestId = \"\";\n const finish = (): void => {\n clearTimeout(timer);\n this.#pending.delete(requestId);\n resolve(replies);\n };\n const timer = setTimeout(finish, timeoutMs);\n requestId = this.#publish(target, type, data, true);\n const fail = (error: Error): void => {\n if (!rejectOnPeerDown) return;\n clearTimeout(timer);\n this.#pending.delete(requestId);\n reject(error);\n };\n this.#pending.set(requestId, {\n target,\n seen: new Set(),\n reply: (reply) => {\n replies.push(reply as ShardReply<R>);\n if (replies.length >= expected) finish();\n },\n fail,\n cancel: (error) => (rejectOnPeerDown ? fail(error) : finish()),\n });\n });\n }\n /** Closes the transport. @returns Nothing. */\n public close(): void {\n if (this.#closed) return;\n this.#closed = true;\n // Nothing can answer once the transport is gone, so end the waits now\n // instead of leaving their timers to fire later.\n for (const pending of [...this.#pending.values()])\n pending.cancel(new Error(\"ShardBus is closed.\"));\n this.#pending.clear();\n this.#transport.close();\n this.#handlers.clear();\n this.#errorHandlers.clear();\n }\n /** Publishes a message. @param target Target shard ID or null. @param type Message type. @param data Payload. @returns Unique message ID. */\n #publish<T>(\n target: number | null,\n type: string,\n data: T,\n expectsReply = false,\n ): string {\n if (!type.trim())\n throw new TypeError(\"Shard message type is required.\");\n if (this.#closed) throw new Error(\"ShardBus is closed.\");\n const id = `${this.#namespace}:${this.#shardId}:${++this.#counter}`;\n this.#transport.post({\n source: this.#shardId,\n target,\n type,\n data,\n id,\n ...(expectsReply ? { expectsReply } : {}),\n } satisfies ShardMessage<T>);\n return id;\n }\n /** Routes an incoming message to matching handlers. @param message Message envelope. @returns Nothing. */\n #dispatch(message: ShardMessage): void {\n if (this.#closed || !isShardMessage(message)) return;\n if (\n message.source === this.#shardId ||\n (message.target !== null && message.target !== this.#shardId)\n )\n return;\n if (message.type === REPLY_TYPE) {\n const reply = message.data as ReplyPayload | null;\n if (\n !reply ||\n typeof reply !== \"object\" ||\n typeof reply.requestId !== \"string\"\n )\n return;\n const pending = this.#pending.get(reply.requestId);\n // Late, unknown and duplicate answers are dropped, and so is an\n // answer from a shard other than the one that was asked: nobody\n // can resolve a request by guessing its ID.\n if (\n !pending ||\n pending.seen.has(message.source) ||\n (pending.target !== null && pending.target !== message.source)\n )\n return;\n pending.seen.add(message.source);\n pending.reply({\n shardId: message.source,\n ...(reply.error !== undefined\n ? { error: String(reply.error) }\n : { result: reply.result }),\n });\n return;\n }\n for (const handler of this.#handlers.get(message.type) ?? []) {\n try {\n const result = handler(message);\n if (\n result &&\n typeof (result as PromiseLike<unknown>).then === \"function\"\n )\n void Promise.resolve(result).catch((error) =>\n this.#reportError(error, message),\n );\n } catch (error) {\n this.#reportError(error, message);\n }\n }\n }\n /** Fails requests waiting on shards that no longer exist. */\n #peerDown(shardIds: readonly number[]): void {\n const down = new Set(shardIds);\n for (const [id, pending] of this.#pending)\n if (pending.target !== null && down.has(pending.target)) {\n pending.fail(\n new Error(\n `Shard ${pending.target} went away before it replied.`,\n ),\n );\n this.#pending.delete(id);\n }\n }\n /** Forwards a handler error to error listeners, isolating listener failures. Without a listener it is raised as a process warning, never dropped. @param error Handler error. @param message Message being handled. @returns Nothing. */\n #reportError(error: unknown, message: ShardMessage): void {\n if (this.#errorHandlers.size === 0) {\n process.emitWarning(\n error instanceof Error ? error : new Error(String(error)),\n { code: \"LUNIBEE_SHARD_BUS_ERROR\" },\n );\n return;\n }\n for (const handler of this.#errorHandlers) {\n try {\n handler(error, message);\n } catch {\n /* Error listener failures are isolated. */\n }\n }\n }\n}\n",
|
|
12
|
+
"/** How a crashed cluster is restarted. The defaults restart every crash after a fixed delay. */\nexport interface SupervisorOptions {\n /** Delay before the first restart, in milliseconds (default 5000). */\n restartDelay?: number;\n /** Each further crash in the window multiplies the delay by this (default 1: a fixed delay). */\n backoff?: number;\n /** The longest delay, jitter included (default 5 minutes). */\n maxDelay?: number;\n /** Random spread added to each delay, as a fraction of it (0-1, default 0). */\n jitter?: number;\n /** Give up after this many restarts without a stable `window` between them (default unlimited). */\n maxRestarts?: number;\n /** How long a restarted cluster must run before its earlier crashes are forgiven (default 60,000). */\n window?: number;\n}\n\n/**\n * Decides when, and whether, a crashed cluster comes back. It only does\n * arithmetic on timestamps (pass `now` in tests), so `ClusterManager` stays in\n * charge of forking.\n */\nexport class ShardSupervisor {\n readonly #options: Required<SupervisorOptions>;\n /** Restarts since the cluster last ran for a whole window, and when the last one starts. */\n readonly #restarts = new Map<\n number,\n { count: number; restartAt: number }\n >();\n readonly #random: () => number;\n\n public constructor(\n options: SupervisorOptions = {},\n random: () => number = Math.random,\n ) {\n this.#options = {\n restartDelay: options.restartDelay ?? 5000,\n backoff: options.backoff ?? 1,\n maxDelay: options.maxDelay ?? 300_000,\n jitter: options.jitter ?? 0,\n maxRestarts: options.maxRestarts ?? Number.POSITIVE_INFINITY,\n window: options.window ?? 60_000,\n };\n const { restartDelay, backoff, jitter, maxDelay, window, maxRestarts } =\n this.#options;\n if (!(restartDelay >= 0) || !(maxDelay >= 0) || !(window > 0))\n throw new RangeError(\n \"restartDelay and maxDelay cannot be negative, and window must be positive.\",\n );\n if (!(backoff >= 1))\n throw new RangeError(\"backoff must be at least 1.\");\n if (!(jitter >= 0 && jitter <= 1))\n throw new RangeError(\"jitter must be between 0 and 1.\");\n if (!(maxRestarts >= 0))\n throw new RangeError(\"maxRestarts cannot be negative.\");\n this.#random = random;\n }\n\n /**\n * A cluster exited. @returns How many milliseconds to wait before\n * restarting it, or `null` to give up because it crashed more than\n * `maxRestarts` times inside `window`.\n */\n public next(id: number, now = Date.now()): number | null {\n const { window, maxRestarts, restartDelay, backoff, maxDelay, jitter } =\n this.#options;\n // A cluster is forgiven only after it ran for a whole `window` since\n // its last restart. Counting from the crash instead would let the\n // backoff delay itself age crashes out, so a crash-on-boot loop\n // would never reach `maxRestarts` or `maxDelay`.\n let history = this.#restarts.get(id);\n if (history && now - history.restartAt >= window) history = undefined;\n const count = history?.count ?? 0;\n if (count >= maxRestarts) {\n this.#restarts.delete(id);\n return null;\n }\n const base = Math.min(maxDelay, restartDelay * backoff ** count);\n // The cap applies after jitter: `maxDelay` is the longest wait, always.\n const delay = Math.min(\n maxDelay,\n Math.round(base * (1 + jitter * this.#random())),\n );\n this.#restarts.set(id, { count: count + 1, restartAt: now + delay });\n return delay;\n }\n\n /** Restarts of a cluster since it last ran stably for a whole `window`. */\n public attempts(id: number): number {\n return this.#restarts.get(id)?.count ?? 0;\n }\n\n /** Forgets a cluster's crashes, e.g. after a deliberate respawn. */\n public reset(id?: number): void {\n if (id === undefined) this.#restarts.clear();\n else this.#restarts.delete(id);\n }\n}\n",
|
|
13
|
+
"import { EventEmitter } from \"node:events\";\nimport {\n assertGuildsPerShard,\n fetchGatewayBot,\n resolveShardCount,\n type GatewayBotInfo,\n} from \"./gateway-bot.js\";\nimport { fork, type ChildProcess } from \"node:child_process\";\nimport { cpus } from \"node:os\";\nimport { REPLY_TYPE, type ReplyPayload, type ShardReply } from \"./bus.js\";\nimport {\n planClusters,\n type ClusterCountSource,\n isClusterWorker,\n validateShardIds,\n WORKER_MARKER,\n} from \"./context.js\";\nimport type {\n ClusterHealth,\n ClusterManagerHealth,\n ClusterState,\n ShardHealth,\n WorkerMetrics,\n WorkerReport,\n} from \"./health.js\";\nimport {\n IdentifyCoordinator,\n type IdentifyBudget,\n type IdentifyEvent,\n} from \"./identify.js\";\nimport type { LeaderLock } from \"./leader.js\";\nimport {\n CLUSTER_FRAME,\n PARENT_ID,\n REQUEST_ASSIGN,\n REQUEST_HEALTH,\n parseChildFrame,\n type ChildFrame,\n} from \"./protocol.js\";\nimport { RemoteHosts, type HostStatus, type RemoteOptions } from \"./remote.js\";\nimport { RequestTracker } from \"./requests.js\";\nimport { ShardSupervisor, type SupervisorOptions } from \"./supervisor.js\";\nimport { BUS_FRAME, PEER_DOWN_FRAME } from \"./transport.js\";\n\n/** Stands in for a cluster process that could not be forked: already exited, kill() a no-op. */\nfunction notStarted(): ChildProcess {\n return Object.assign(new EventEmitter(), {\n exitCode: 1,\n signalCode: null,\n connected: false,\n pid: undefined,\n kill: () => false,\n }) as unknown as ChildProcess;\n}\n\n/** What the `plan` event reports. */\nexport interface ClusterPlanInfo {\n /** The deployment's shard count (Discord's `num_shards`), the same in every worker. */\n totalShards: number;\n clusterCount: number;\n /** `\"clusterCount\"`, `\"shardsPerCluster\"` or `\"default\"` (the number of CPUs). */\n clusterCountSource: ClusterCountSource;\n /** The count asked for; larger than `clusterCount` when there were more clusters than shards. */\n requestedClusterCount: number;\n clusters: { id: number; shards: number[] }[];\n maxConcurrency: number;\n startup: \"safe\" | \"sequential\";\n identifyCoordinated: boolean;\n}\n\n/** Configuration for managing multiple shard clusters. */\nexport interface ClusterManagerOptions {\n /** Discord bot token. */\n token: string;\n /** Path to the worker script to execute for each cluster. */\n script: string;\n /** Total number of shards. Use \"auto\" to request Discord's recommended count. */\n shardCount?: number | \"auto\";\n /**\n * A sizing hint for `shardCount: \"auto\"`: run at least\n * `ceil(guildCount / guildsPerShard)` shards, and never fewer than Discord\n * recommends. 1 to 2500 (Discord's limit); Discord recommends about 1000.\n * It only picks the shard count: Discord puts each guild on shard\n * `(guild_id >> 22) % shardCount`, so shards get about this many guilds,\n * not exactly, and ownership never follows from it.\n */\n guildsPerShard?: number;\n /** The bot's guild count for `guildsPerShard`, instead of asking Discord for it. */\n guildCount?: number;\n /**\n * The shards this deployment runs, out of `shardCount`. Use it to split one\n * bot across hosts: each host runs a `ClusterManager` with its own range.\n * Defaults to every shard.\n */\n shardIds?: number[];\n /** Number of clusters to spawn. Defaults to the number of logical CPUs. Not combinable with `shardsPerCluster`. */\n clusterCount?: number;\n /** Shards per cluster; the cluster count follows. Not combinable with `clusterCount`. */\n shardsPerCluster?: number;\n /**\n * Discord's `max_concurrency` for IDENTIFY pacing. Fetched from `/gateway/bot`\n * when `shardCount` is \"auto\" (this value may only lower it); otherwise defaults to 1.\n */\n maxConcurrency?: number;\n /** Ask `/gateway/bot` for `max_concurrency` and the session budget even with a fixed `shardCount`. */\n fetchLimits?: boolean;\n /**\n * How shards start across the whole deployment.\n * - `\"safe\"` (default): as fast as Discord allows. One IDENTIFY per\n * rate-limit key (`shard_id % max_concurrency`) every `identify.interval`,\n * keys in parallel; a slot is freed as soon as the IDENTIFY is sent.\n * - `\"sequential\"`: one shard at a time across all clusters, in the order\n * they ask (cluster by cluster, as they spawn). Each slot is held until that shard is READY (or\n * `identify.holdTimeout` passes; workers wait that long for READY), then the next shard waits the interval.\n * Slow, for debugging and small bots. Needs IDENTIFY coordination.\n */\n startup?: \"safe\" | \"sequential\";\n /** IDENTIFY coordination across clusters. On by default. */\n identify?: {\n /** Set `false` to let every cluster pace its own IDENTIFYs. */\n coordinate?: boolean;\n /** Milliseconds between IDENTIFYs on one key. Default 5000. */\n interval?: number;\n /** A slot a worker never releases is released after this many ms. Default 60,000. */\n holdTimeout?: number;\n };\n /** Milliseconds between forking two clusters, to spread the CPU cost of starting. Default 500. IDENTIFY pacing does not depend on it. */\n spawnInterval?: number;\n /**\n * A cluster that has not reported ready this many ms after it was forked is\n * killed and restarted like a crash. Off by default, because a worker that\n * never reports ready (no `ShardManager`, no `ClusterWorker`) would be killed.\n */\n startTimeout?: number;\n /** Make `spawn()` wait until every cluster reported ready. Default false. */\n waitForReady?: boolean;\n /**\n * When a cluster crashes more often than `supervisor.maxRestarts` allows,\n * move its shards to the healthiest ready cluster instead of leaving them\n * offline. Default false.\n */\n reassignOnGiveUp?: boolean;\n /**\n * Largest shard-bus message, request payload or worker reply the manager\n * handles, in UTF-8 bytes (a string as it is, anything else as its JSON text). Bigger ones from a worker are\n * dropped before they are relayed (an `error` event says so; a request\n * waiting for the answer times out as usual), and bigger ones passed to\n * `request()`, `broadcast()` or `requestCluster()` throw a `RangeError`.\n * Default 8 MiB, the same limit as the host link.\n */\n maxMessageBytes?: number;\n /** Default timeout for `request()`, `requestCluster()` and `broadcast()`. Default 5000. */\n requestTimeout?: number;\n /** Most requests waiting for answers at once. Default 10,000. */\n maxPendingRequests?: number;\n /**\n * Accept hosts on other machines. The coordinator listens for\n * `ClusterHost` agents, places clusters on them and supervises those\n * workers like local ones. See {@link RemoteOptions}.\n */\n remote?: RemoteOptions;\n /**\n * Run several coordinators of one deployment, one active at a time, so a\n * standby takes over when the active one dies (`RedisLeaderLock`, or your\n * own {@link LeaderLock}). `spawn()` waits until this coordinator holds\n * the lock. The leader renews it every quarter of `ttl`; if it cannot for\n * half of `ttl` (or another holds it) it kills every cluster at once,\n * closes the host listener, and waits to lead again. A new leader waits\n * two `remote.lease`s before it starts clusters, so the previous leader's\n * hosts have fenced themselves. Point hosts at every coordinator\n * (`ClusterHost` `url` as a list).\n */\n leader?: {\n lock: LeaderLock;\n /** Lock lifetime in ms. Default 10,000; at least 100. */\n ttl?: number;\n };\n /** Starts a worker process. Defaults to `child_process.fork()`; replace it for tests or custom launchers. */\n fork?: (\n script: string,\n env: Record<string, string | undefined>,\n ) => ChildProcess;\n /** Interval in milliseconds to automatically check for recommended shard count and re-scale if needed. */\n autoScaleInterval?: number;\n /** Optional handler invoked when a background auto-scale check fails. Receives the thrown error. */\n onAutoScaleError?: (error: Error) => void;\n /** Grace period in milliseconds to await a cluster's clean exit after SIGTERM before force-killing. Defaults to 5000. */\n shutdownTimeout?: number;\n /**\n * Whether a cluster that exits unexpectedly is re-forked with the same\n * shard assignment. Defaults to true: without it a crashed child leaves\n * its shards permanently offline and nothing reports it.\n */\n restartOnExit?: boolean;\n /** Delay in milliseconds before re-forking a crashed cluster. Defaults to 5000. */\n restartDelay?: number;\n /**\n * Backoff, jitter and a give-up limit for restarts. `restartDelay` is its\n * first delay unless it sets its own. The default restarts every crash\n * after a fixed delay, as before.\n */\n supervisor?: Omit<SupervisorOptions, \"restartDelay\"> & {\n restartDelay?: number;\n };\n /** Called when a cluster crashed more often than `supervisor.maxRestarts` allows and will not be restarted. */\n onGiveUp?(cluster: ClusterInfo): void;\n /** Called whenever a cluster process exits, before any restart. */\n onClusterExit?(\n cluster: ClusterInfo,\n code: number | null,\n signal: NodeJS.Signals | null,\n ): void;\n}\n\n/** Information about a cluster. The object is live: it is updated as the cluster changes. */\nexport interface ClusterInfo {\n /** Cluster identifier. */\n id: number;\n /** The current child process (the last one while a restart is pending). For a cluster on another host this is a proxy; `pid` is the pid on that host. */\n process: ChildProcess;\n /** Where it runs: `\"local\"` or a host ID. */\n host: string;\n /** The shard IDs managed by this cluster. */\n shards: number[];\n /** Where the cluster is in its life. */\n state: ClusterState;\n /** How many times it was restarted after a crash. */\n restarts: number;\n /** When the current process was forked (epoch ms). */\n startedAt: number;\n /** When the current process reported ready. */\n readyAt?: number;\n /** When any process of this cluster last reported ready. */\n lastReadyAt?: number;\n /** The worker's last health report. */\n report?: WorkerReport;\n}\n\n/** What `ClusterManager` emits. */\nexport interface ClusterManagerEvents {\n /** A cluster process was forked. */\n clusterSpawn: [cluster: ClusterInfo];\n /** A cluster's shards all finished their handshake. */\n clusterReady: [cluster: ClusterInfo];\n /** Every cluster is ready. Emitted again after a recovery. */\n ready: [];\n /** A cluster process exited. `expected` is true for a deliberate shutdown. */\n clusterExit: [\n cluster: ClusterInfo,\n code: number | null,\n signal: NodeJS.Signals | null,\n expected: boolean,\n ];\n /** A restart was scheduled after a crash. */\n clusterRestart: [\n cluster: ClusterInfo,\n info: { delay: number; attempt: number },\n ];\n /** A cluster keeps crashing: three or more restarts are pending or recent. */\n clusterUnstable: [\n cluster: ClusterInfo,\n info: { attempt: number; delay: number },\n ];\n /** A cluster did not report ready within `startTimeout` and was killed. */\n clusterStartTimeout: [cluster: ClusterInfo];\n /** A cluster crashed too often and will not be restarted. */\n clusterGiveUp: [cluster: ClusterInfo];\n /** A failed cluster's shards were moved to another cluster. */\n shardsReassigned: [info: { shardIds: number[]; from: number; to: number }];\n /** A host joined the deployment. */\n hostJoin: [hostId: string];\n /** A host left or was lost. Its clusters are treated as crashed. */\n hostLeave: [hostId: string, reason: string];\n /** A lost host's cluster is restarting on another host (`remote.moveClusters`). */\n clusterMoved: [cluster: ClusterInfo, info: { from: string; to: string }];\n /** This coordinator took the `leader` lock and will start the clusters. */\n leaderAcquired: [];\n /** This coordinator lost the `leader` lock and killed its clusters; it waits to lead again. */\n leaderLost: [reason: string];\n /** An IDENTIFY is waiting for the session-start budget to reset. */\n identifyWait: [info: { shardId: number; waitMs: number }];\n /** The allocation of a start: total shards, the cluster count and where it came from, each cluster's shards. */\n plan: [plan: ClusterPlanInfo];\n /** The IDENTIFY queue: a shard asked for a slot, got it, or gave it back. */\n identify: [event: IdentifyEvent];\n /** `shutdownAll()` finished: every cluster process has exited (or was killed after the timeout). */\n shutdown: [info: { clusters: number; durationMs: number }];\n /** Something went wrong that did not stop the deployment (a bad frame, a failed recovery). */\n error: [error: Error];\n}\n\n/** Options for a request to a shard or cluster. */\nexport interface RequestOptions {\n /** Milliseconds to wait. Defaults to `requestTimeout`. */\n timeoutMs?: number;\n /** Cancels the wait. */\n signal?: AbortSignal;\n}\n\n/** What a broadcast collected. It never rejects for a slow or failed shard. */\nexport interface BroadcastResult<R = unknown> {\n /** One reply per answering shard: its result, or the error its handler threw. */\n replies: ShardReply<R>[];\n /** Shards that did not answer: down, restarting, or too slow. */\n missing: number[];\n}\n\ntype LiveState = \"starting\" | \"ready\";\n\n/** Default for `maxMessageBytes`. */\nconst DEFAULT_MAX_MESSAGE_BYTES = 8 * 1024 * 1024;\n\n/**\n * The size of a payload in UTF-8 bytes: a string as it is, anything else as\n * its JSON text. `Infinity` for one that cannot be serialized (a cycle).\n */\nfunction payloadSize(value: unknown): number {\n if (value === undefined || value === null) return 0;\n if (typeof value === \"string\") return Buffer.byteLength(value, \"utf8\");\n if (typeof value !== \"object\") return String(value).length;\n try {\n const text = JSON.stringify(value);\n return text === undefined ? 0 : Buffer.byteLength(text, \"utf8\");\n } catch {\n return Number.POSITIVE_INFINITY;\n }\n}\n\nconst isLive = (state: ClusterState): state is LiveState =>\n state === \"starting\" || state === \"ready\";\n\n/**\n * Orchestrates cluster processes: forks one worker per cluster, gives each a\n * fixed set of shards, restarts crashed clusters with backoff, hands out\n * IDENTIFY slots across all of them, routes shard-bus messages to the cluster\n * that owns the target shard, and answers requests, broadcasts and health\n * queries for the whole deployment.\n *\n * Ownership: the `ClusterManager` decides which cluster owns which shard and\n * keeps processes alive; the `ShardManager` inside each worker runs those\n * shards' Gateways. A shard is never owned by two clusters at once.\n *\n * Works under Node.js and Bun (`child_process.fork()` with IPC).\n */\nexport class ClusterManager {\n /** Live clusters (starting or ready) indexed by cluster ID. A crashed cluster leaves this map until it is restarted. */\n public readonly clusters = new Map<number, ClusterInfo>();\n /** Total number of shards in the deployment. */\n public shardCount = 0;\n\n readonly #options: ClusterManagerOptions;\n /** Whether the manager was created in auto shard-count mode. Preserved across respawns so auto-scaling keeps running. */\n readonly #auto: boolean;\n #autoScaleTimer?: ReturnType<typeof setInterval>;\n /** Every cluster this deployment knows, whatever its state. */\n readonly #infos = new Map<number, ClusterInfo>();\n /** Which cluster owns each shard. */\n readonly #owner = new Map<number, number>();\n #startPromise?: Promise<void>;\n #stopPromise?: Promise<void>;\n #wasStarted = false;\n /** Set while a deliberate shutdown is in progress so exits are not restarted. */\n #stopping = false;\n /** Bumped by every stop, so a start that was waiting notices it is stale. */\n #generation = 0;\n #leading = false;\n #renewTimer?: ReturnType<typeof setInterval>;\n /** When the last successful renewal was sent (epoch ms). */\n #lastRenew = 0;\n #renewing = false;\n /** Bumped by every leadership term, so a late renewal of an old one is ignored. */\n #term = 0;\n /** Pending restart timers, kept so shutdown can cancel them. */\n readonly #restartTimers = new Set<ReturnType<typeof setTimeout>>();\n /** Restarts waiting for a lost host to fence itself, by cluster. */\n readonly #fencing = new Map<number, ReturnType<typeof setTimeout>>();\n readonly #startTimers = new Map<number, ReturnType<typeof setTimeout>>();\n readonly #pauses = new Set<() => void>();\n readonly #readyWaiters = new Set<{\n resolve: () => void;\n reject: (error: Error) => void;\n timer?: ReturnType<typeof setTimeout>;\n }>();\n readonly #supervisor: ShardSupervisor;\n readonly #tracker: RequestTracker;\n #coordinator?: IdentifyCoordinator;\n #allReady = false;\n #clusterCount = 0;\n /** Shards of each cluster that are waiting for an IDENTIFY slot. */\n readonly #identifyWaiting = new Map<number, Set<number>>();\n readonly #remote?: RemoteHosts;\n /** Which host runs each cluster. Fixed for the life of a spawn, so a cluster returns to its host. */\n readonly #hostOf = new Map<number, string>();\n #lastWait = \"\";\n readonly #listeners = new Map<\n keyof ClusterManagerEvents,\n Set<(...args: never[]) => void>\n >();\n\n /** Creates a cluster manager. @param options Clustering configuration. @throws {TypeError} If token or script is missing. @throws {RangeError} If a numeric option is invalid. */\n public constructor(options: ClusterManagerOptions) {\n if (!options.token?.trim())\n throw new TypeError(\"A Discord bot token is required.\");\n if (!options.script?.trim())\n throw new TypeError(\"A script path is required for clustering.\");\n if (\n options.autoScaleInterval !== undefined &&\n (!Number.isInteger(options.autoScaleInterval) ||\n options.autoScaleInterval < 1000)\n ) {\n throw new RangeError(\n \"autoScaleInterval must be an integer of at least 1000 milliseconds.\",\n );\n }\n if (\n options.clusterCount !== undefined &&\n options.shardsPerCluster !== undefined\n )\n throw new RangeError(\n \"Set either clusterCount or shardsPerCluster, not both.\",\n );\n for (const [name, value] of [\n [\"clusterCount\", options.clusterCount],\n [\"shardsPerCluster\", options.shardsPerCluster],\n [\"maxConcurrency\", options.maxConcurrency],\n ] as const)\n if (value !== undefined && (!Number.isInteger(value) || value < 1))\n throw new RangeError(`${name} must be a positive integer.`);\n if (\n options.maxMessageBytes !== undefined &&\n (!Number.isInteger(options.maxMessageBytes) ||\n options.maxMessageBytes < 1)\n )\n throw new RangeError(\"maxMessageBytes must be a positive integer.\");\n for (const [name, value] of [\n [\"spawnInterval\", options.spawnInterval],\n [\"startTimeout\", options.startTimeout],\n [\"requestTimeout\", options.requestTimeout],\n ] as const)\n if (value !== undefined && !(value >= 0))\n throw new RangeError(`${name} cannot be negative.`);\n if (\n options.startup !== undefined &&\n options.startup !== \"safe\" &&\n options.startup !== \"sequential\"\n )\n throw new RangeError('startup must be \"safe\" or \"sequential\".');\n if (\n options.startup === \"sequential\" &&\n options.identify?.coordinate === false\n )\n throw new RangeError(\n 'startup: \"sequential\" needs IDENTIFY coordination; remove identify.coordinate: false.',\n );\n if (options.guildsPerShard !== undefined) {\n assertGuildsPerShard(options.guildsPerShard);\n if (typeof options.shardCount === \"number\")\n throw new RangeError(\n 'guildsPerShard needs shardCount: \"auto\".',\n );\n }\n if (options.shardIds && typeof options.shardCount === \"number\")\n validateShardIds(options.shardIds, options.shardCount);\n\n this.#auto = options.shardCount === \"auto\";\n const ttl = options.leader?.ttl;\n if (\n options.leader &&\n (typeof options.leader.lock?.acquire !== \"function\" ||\n typeof options.leader.lock.release !== \"function\")\n )\n throw new TypeError(\n \"leader.lock must have acquire() and release().\",\n );\n if (ttl !== undefined && (!Number.isInteger(ttl) || ttl < 100))\n throw new RangeError(\n \"leader.ttl must be an integer of at least 100.\",\n );\n this.#options = { ...options };\n this.#supervisor = new ShardSupervisor({\n restartDelay: options.restartDelay,\n ...options.supervisor,\n });\n this.#tracker = new RequestTracker(\n \"parent\",\n options.maxPendingRequests,\n );\n if (options.remote)\n this.#remote = new RemoteHosts(options.remote, {\n join: (id) => this.#hostJoined(id),\n leave: (id, reason) => this.#emit(\"hostLeave\", id, reason),\n error: (error) => this.#emit(\"error\", error),\n });\n }\n\n /** Listens for a deployment event. @returns This manager. */\n public on<E extends keyof ClusterManagerEvents>(\n event: E,\n listener: (...args: ClusterManagerEvents[E]) => void,\n ): this {\n let set = this.#listeners.get(event);\n if (!set) this.#listeners.set(event, (set = new Set()));\n set.add(listener as never);\n return this;\n }\n\n /** Stops listening for a deployment event. @returns This manager. */\n public off<E extends keyof ClusterManagerEvents>(\n event: E,\n listener: (...args: ClusterManagerEvents[E]) => void,\n ): this {\n this.#listeners.get(event)?.delete(listener as never);\n return this;\n }\n\n #emit<E extends keyof ClusterManagerEvents>(\n event: E,\n ...args: ClusterManagerEvents[E]\n ): void {\n for (const listener of this.#listeners.get(event) ?? [])\n try {\n (listener as (...a: ClusterManagerEvents[E]) => void)(...args);\n } catch {\n // A faulty listener must not break supervision.\n }\n }\n\n /** Retrieves Discord's recommended shard count. @returns Recommended shard count. @throws {Error} If discovery fails or returns invalid data. */\n public async fetchRecommendedShardCount(): Promise<number> {\n return resolveShardCount(\n this.#options.token,\n await fetchGatewayBot(this.#options.token),\n this.#options,\n );\n }\n\n /** The cluster that owns a shard, or `undefined` when the shard is not part of this deployment. */\n public clusterForShard(shardId: number): ClusterInfo | undefined {\n const id = this.#owner.get(shardId);\n return id === undefined ? undefined : this.#infos.get(id);\n }\n\n /** Which cluster owns each shard. A copy: change ownership through recovery, not by writing to it. */\n public assignments(): ReadonlyMap<number, number> {\n return new Map(this.#owner);\n }\n\n /**\n * Spawns all clusters. Calling it again while clusters run, or while the\n * first call is still starting them, returns the same promise.\n * @throws {Error} If called from a cluster worker (a worker must not start the fleet again).\n */\n public spawn(): Promise<void> {\n if (isClusterWorker())\n return Promise.reject(\n new Error(\n \"A cluster worker cannot start a ClusterManager: it would fork the whole fleet again.\",\n ),\n );\n if (this.#stopPromise)\n return this.#stopPromise.then(() => this.spawn());\n if (!this.#startPromise) {\n const attempt = this.#start();\n this.#startPromise = attempt;\n attempt.catch(() => {\n if (this.#startPromise === attempt)\n this.#startPromise = undefined;\n });\n }\n return this.#startPromise;\n }\n\n async #resolveGateway(): Promise<{\n count: number;\n maxConcurrency: number;\n budget?: IdentifyBudget;\n }> {\n const { shardCount, maxConcurrency, fetchLimits } = this.#options;\n let info: GatewayBotInfo | undefined;\n let count: number | undefined;\n if (shardCount === \"auto\" || shardCount === undefined) {\n info = await fetchGatewayBot(this.#options.token);\n count = await resolveShardCount(\n this.#options.token,\n info,\n this.#options,\n );\n } else if (fetchLimits || this.#auto)\n // A respawn of an \"auto\" deployment has a number by now, but it\n // still needs `max_concurrency` and the session budget.\n try {\n info = await fetchGatewayBot(this.#options.token);\n } catch (error) {\n this.#emit(\n \"error\",\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n const limit = info?.sessionStartLimit;\n return {\n count: count ?? (shardCount as number),\n maxConcurrency: Math.min(\n maxConcurrency ?? limit?.maxConcurrency ?? 1,\n limit?.maxConcurrency ?? Number.POSITIVE_INFINITY,\n ),\n ...(limit\n ? {\n budget: {\n total: limit.total,\n remaining: limit.remaining,\n resetAfter: limit.resetAfter,\n },\n }\n : {}),\n };\n }\n\n async #start(): Promise<void> {\n const generation = this.#generation;\n const stale = (): boolean =>\n this.#stopping || generation !== this.#generation;\n if (this.#options.leader && !this.#leading) {\n await this.#lead();\n if (stale()) return;\n }\n const { count, maxConcurrency, budget } = await this.#resolveGateway();\n if (stale()) return;\n\n const ids =\n this.#options.shardIds ??\n Array.from({ length: count }, (_, i) => i);\n validateShardIds(ids, count);\n const plan = planClusters({\n shardIds: ids,\n clusterCount: this.#options.clusterCount,\n shardsPerCluster: this.#options.shardsPerCluster,\n defaultClusterCount: Math.max(1, cpus().length),\n });\n const chunks = plan.clusters;\n\n const placement = await this.#placeHosts().catch((error: unknown) => {\n // A shutdown that interrupts the wait is not a failure of the start.\n if (stale()) return undefined;\n throw error;\n });\n if (!placement || stale()) return;\n\n this.shardCount = count;\n this.#wasStarted = true;\n this.#owner.clear();\n this.#infos.clear();\n const identify = this.#options.identify;\n this.#coordinator =\n identify?.coordinate === false\n ? undefined\n : new IdentifyCoordinator({\n // Sequential: every shard shares one key, so one starts at a time.\n maxConcurrency:\n this.#options.startup === \"sequential\"\n ? 1\n : maxConcurrency,\n onEvent: (event) => this.#emit(\"identify\", event),\n interval: identify?.interval,\n holdTimeout: identify?.holdTimeout,\n budget,\n onBudgetWait: (wait) => {\n const key = `${wait.shardId}:${Math.round(wait.waitMs / 1000)}`;\n if (key === this.#lastWait) return;\n this.#lastWait = key;\n this.#emit(\"identifyWait\", wait);\n },\n });\n\n const active = chunks.map((shards, id) => ({ id, shards }));\n this.#clusterCount = active.length;\n this.#emit(\"plan\", {\n totalShards: count,\n clusterCount: active.length,\n clusterCountSource: plan.source,\n requestedClusterCount: plan.requested,\n clusters: active.map((chunk) => ({\n id: chunk.id,\n shards: chunk.shards,\n })),\n maxConcurrency,\n startup: this.#options.startup ?? \"safe\",\n identifyCoordinated: this.#coordinator !== undefined,\n });\n this.#hostOf.clear();\n for (const chunk of active)\n this.#hostOf.set(chunk.id, placement[chunk.id % placement.length]!);\n for (const [index, chunk] of active.entries()) {\n if (stale()) return;\n for (const shard of chunk.shards) this.#owner.set(shard, chunk.id);\n this.#launchOrRecover(chunk.id, chunk.shards);\n // Spread the CPU cost of starting processes. IDENTIFY pacing is\n // the coordinator's job and does not depend on this.\n const gap = this.#options.spawnInterval ?? 500;\n if (index < active.length - 1 && gap > 0) await this.#pause(gap);\n }\n if (stale()) return;\n\n if (this.#options.waitForReady) await this.ready();\n if (\n this.#options.autoScaleInterval &&\n this.#options.autoScaleInterval > 0\n ) {\n this.#autoScaleTimer = setInterval(() => {\n void this.checkAutoScale();\n }, this.#options.autoScaleInterval);\n }\n }\n\n /** Whether this coordinator holds the `leader` lock; always true without `leader`. */\n public get isLeader(): boolean {\n return !this.#options.leader || this.#leading;\n }\n\n /**\n * Waits until this coordinator holds the leader lock, then for the\n * previous leader's hosts to fence themselves.\n */\n async #lead(): Promise<void> {\n const { lock, ttl = 10_000 } = this.#options.leader!;\n const generation = this.#generation;\n while (!this.#stopping && generation === this.#generation) {\n const started = Date.now();\n let held = false;\n try {\n // A lock that never answers must not hold up a shutdown.\n held = await withTimeout(lock.acquire(ttl), ttl / 2);\n } catch (error) {\n this.#emit(\"error\", asError(error));\n }\n if (this.#stopping || generation !== this.#generation) {\n if (held) void lock.release().catch(() => undefined);\n return;\n }\n if (held) {\n this.#leading = true;\n this.#lastRenew = started;\n this.#startRenewing(ttl);\n this.#emit(\"leaderAcquired\");\n const lease = this.#options.remote\n ? (this.#options.remote.lease ?? 15_000)\n : 0;\n if (lease > 0) await this.#pause(lease * 2);\n return;\n }\n await this.#pause(Math.floor(ttl / 2));\n }\n }\n\n #startRenewing(ttl: number): void {\n this.#stopRenewing();\n const lock = this.#options.leader!.lock;\n // Each term renews on its own: a renewal of an earlier term that\n // settles late (or never) must neither block nor end this one.\n const term = ++this.#term;\n this.#renewing = false;\n this.#renewTimer = setInterval(\n () => {\n // Stand down well before the lock can lapse and another\n // coordinator can take it: by 3/4 of ttl at the latest.\n if (Date.now() - this.#lastRenew > ttl / 2) {\n this.#standDown(\"The leader lock could not be renewed.\");\n return;\n }\n if (this.#renewing) return;\n this.#renewing = true;\n const started = Date.now();\n withTimeout(lock.acquire(ttl), ttl / 4).then(\n (held) => {\n if (term !== this.#term) return;\n this.#renewing = false;\n if (!this.#leading) return;\n if (held) this.#lastRenew = started;\n else\n this.#standDown(\n \"Another coordinator holds the leader lock.\",\n );\n },\n (error: unknown) => {\n if (term !== this.#term) return;\n this.#renewing = false;\n this.#emit(\"error\", asError(error));\n },\n );\n },\n Math.max(10, Math.floor(ttl / 4)),\n );\n (this.#renewTimer as { unref?: () => void }).unref?.();\n }\n\n #stopRenewing(): void {\n if (this.#renewTimer) clearInterval(this.#renewTimer);\n this.#renewTimer = undefined;\n }\n\n /** Gives the lock back on a deliberate stop. */\n async #resign(): Promise<void> {\n this.#stopRenewing();\n if (!this.#leading) return;\n this.#leading = false;\n const { lock, ttl = 10_000 } = this.#options.leader!;\n try {\n await withTimeout(lock.release(), ttl / 2);\n } catch (error) {\n this.#emit(\"error\", asError(error));\n }\n }\n\n /**\n * Lost the lock: kill every cluster now (no grace period, a successor may\n * already be counting down), close the host listener so hosts fence, and\n * wait to lead again.\n */\n #standDown(reason: string): void {\n if (!this.#leading) return;\n this.#stopRenewing();\n this.#leading = false;\n this.#stopping = true;\n this.#halt();\n for (const cluster of this.#infos.values()) {\n cluster.state = \"stopped\";\n try {\n cluster.process.kill(\"SIGKILL\");\n } catch {\n // Already gone.\n }\n }\n this.clusters.clear();\n this.#startPromise = undefined;\n this.#remote?.stop();\n this.#stopping = false;\n this.#emit(\"leaderLost\", reason);\n this.spawn().catch((error: unknown) =>\n this.#emit(\"error\", asError(error)),\n );\n }\n\n /** Opens the listener for hosts and waits for them. @returns The places clusters run, in placement order. */\n async #placeHosts(): Promise<string[]> {\n const remote = this.#remote;\n const options = this.#options.remote;\n if (!remote || !options) return [\"local\"];\n remote.start();\n try {\n await remote.waitFor(\n options.hosts,\n options.minHosts ?? 1,\n options.joinTimeout ?? 60_000,\n );\n } catch (error) {\n remote.stop();\n throw error;\n }\n const ids = options.hosts ?? remote.connected();\n return options.local ? [\"local\", ...ids] : ids;\n }\n\n /** The port hosts connect to, once the listener is open (useful with `remote.port: 0`). */\n public get remotePort(): number | undefined {\n return this.#remote?.port;\n }\n\n /** The hosts of this deployment: connected or not, and the clusters on each. Empty without `remote`. */\n public hosts(): HostStatus[] {\n return this.#remote?.status(this.#options.remote?.hosts) ?? [];\n }\n\n /** Waits `ms`, or less when a shutdown starts. */\n #pause(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const wake = (): void => {\n clearTimeout(timer);\n this.#pauses.delete(wake);\n resolve();\n };\n const timer = setTimeout(wake, ms);\n this.#pauses.add(wake);\n });\n }\n\n /**\n * Forks one cluster child and supervises its exit.\n *\n * A child that dies (crash, OOM kill, uncaught rejection) takes its shards\n * offline for good, so unless the exit came from a deliberate shutdown the\n * same shard assignment is re-forked after the supervisor's delay.\n */\n #launch(id: number, shards: number[]): ClusterInfo {\n // Only these cross to a remote host; the rest of a worker's environment is the host's own.\n const contract: Record<string, string> = {\n SHARD_LIST: shards.join(\",\"),\n SHARD_COUNT: this.shardCount.toString(),\n CLUSTER_ID: id.toString(),\n CLUSTER_COUNT: this.#clusterCount.toString(),\n LUNIBEE_SHARD_BUS: \"ipc\",\n [WORKER_MARKER]: \"1\",\n LUNIBEE_IDENTIFY: this.#coordinator ? \"coordinated\" : \"local\",\n // Only when set: hosts older than this option reject unknown keys.\n ...(this.#options.startup === \"sequential\"\n ? {\n LUNIBEE_STARTUP: \"sequential\",\n // Workers wait for READY as long as this parent holds the slot.\n LUNIBEE_READY_TIMEOUT: String(\n this.#options.identify?.holdTimeout ?? 60_000,\n ),\n }\n : {}),\n };\n const host = this.#hostOf.get(id) ?? \"local\";\n const env: Record<string, string | undefined> = {\n ...process.env,\n ...contract,\n };\n const child =\n host !== \"local\" && this.#remote\n ? this.#remote.createChild(host, id, contract)\n : this.#options.fork\n ? this.#options.fork(this.#options.script, env)\n : fork(this.#options.script, [], {\n env: env as NodeJS.ProcessEnv,\n });\n const known = this.#infos.get(id);\n const info: ClusterInfo = known ?? {\n id,\n host,\n process: child,\n shards,\n state: \"starting\",\n restarts: 0,\n startedAt: Date.now(),\n };\n info.process = child;\n info.state = \"starting\";\n info.startedAt = Date.now();\n info.readyAt = undefined;\n this.#infos.set(id, info);\n this.clusters.set(id, info);\n this.#allReady = false;\n\n // `send()` can fail after it returned: a worker that exits while a frame\n // is being written emits `error` (EPIPE, \"Channel closed\") later. With no\n // listener that is an uncaught exception, and it would end the manager.\n // The worker's `exit` event does the actual recovery.\n child.on(\"error\", (error: Error) =>\n this.#emit(\n \"error\",\n new Error(`Cluster ${id} process error: ${error.message}`, {\n cause: error,\n }),\n ),\n );\n child.on(\"message\", (frame) => this.#onFrame(info, child, frame));\n child.once(\"exit\", (code, signal) =>\n this.#onExit(info, child, code, signal),\n );\n const timeout = this.#options.startTimeout;\n if (timeout) {\n const timer = setTimeout(() => {\n this.#startTimers.delete(id);\n if (info.process !== child || info.state !== \"starting\") return;\n this.#emit(\"clusterStartTimeout\", info);\n try {\n child.kill(\"SIGKILL\");\n } catch {\n // Already gone; the exit handler takes over.\n }\n }, timeout);\n (timer as { unref?: () => void }).unref?.();\n this.#startTimers.set(id, timer);\n }\n this.#emit(\"clusterSpawn\", info);\n return info;\n }\n\n #onExit(\n info: ClusterInfo,\n child: ChildProcess,\n code: number | null,\n signal: NodeJS.Signals | null,\n ): void {\n // Only act on the process still registered as this cluster: a\n // restart or respawn may already have replaced it, and after\n // `killAll()` + `spawn()` a new `ClusterInfo` holds the same id.\n if (!this.#current(info, child)) {\n child.removeAllListeners(\"message\");\n return;\n }\n child.removeAllListeners(\"message\");\n this.#clearStartTimer(info.id);\n this.#coordinator?.drop(info.id, `Cluster ${info.id} exited.`);\n this.#identifyWaiting.delete(info.id);\n this.#tracker.failOwner(info.id, `Cluster ${info.id} exited.`);\n this.clusters.delete(info.id);\n this.#allReady = false;\n try {\n this.#options.onClusterExit?.(info, code, signal);\n } catch {\n // A faulty consumer callback must not break supervision.\n }\n // A deliberate shutdown (graceful or `killAll()`) is never restarted.\n const expected =\n this.#stopping ||\n info.state === \"stopping\" ||\n info.state === \"stopped\";\n this.#emit(\"clusterExit\", info, code, signal, expected);\n if (expected) {\n info.state = \"stopped\";\n return;\n }\n this.#announceDown(info.id, info.shards);\n this.#recover(info);\n }\n\n /**\n * Forks a cluster; a fork that throws (EAGAIN, EMFILE, a bad script path\n * for a custom `fork`) is a failed start of that cluster only. It goes\n * through the restart policy like a crash, and the clusters after it are\n * still started.\n */\n #launchOrRecover(id: number, shards: number[]): void {\n try {\n this.#launch(id, shards);\n } catch (error) {\n const known = this.#infos.get(id);\n if (known) known.process = notStarted();\n const info: ClusterInfo = this.#infos.get(id) ?? {\n id,\n host: this.#hostOf.get(id) ?? \"local\",\n process: notStarted(),\n shards,\n state: \"starting\",\n restarts: 0,\n startedAt: Date.now(),\n };\n this.#infos.set(id, info);\n this.clusters.delete(id);\n this.#allReady = false;\n this.#emit(\n \"error\",\n new Error(\n `Cluster ${id} (shards ${shards.join(\",\")}) could not be spawned: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n ),\n );\n this.#recover(info);\n }\n }\n\n /** Applies the restart policy to a cluster that exited or failed to start. */\n #recover(info: ClusterInfo): void {\n if (this.#options.restartOnExit === false) {\n info.state = \"failed\";\n this.#rejectReady(info, \"restartOnExit is false\");\n return;\n }\n const delay = this.#supervisor.next(info.id);\n if (delay === null) {\n info.state = \"failed\";\n try {\n this.#options.onGiveUp?.(info);\n } catch {\n // A faulty consumer callback must not break supervision.\n }\n this.#emit(\"clusterGiveUp\", info);\n if (this.#options.reassignOnGiveUp) void this.#reassign(info);\n else this.#rejectReady(info, \"restarts exhausted\");\n return;\n }\n info.state = \"restarting\";\n info.restarts++;\n this.#emit(\"clusterRestart\", info, { delay, attempt: info.restarts });\n // Unstable means recent: crashes forgiven after a stable run do not count.\n if (this.#supervisor.attempts(info.id) >= 3)\n this.#emit(\"clusterUnstable\", info, {\n attempt: info.restarts,\n delay,\n });\n this.#restartLater(info, delay);\n }\n\n /** Re-forks a restarting cluster after `delay`, on another host if its own is lost. */\n #restartLater(\n info: ClusterInfo,\n delay: number,\n ): ReturnType<typeof setTimeout> {\n const timer = setTimeout(() => {\n this.#restartTimers.delete(timer);\n this.#fencing.delete(info.id);\n if (this.#stopping || info.state !== \"restarting\") return;\n const wait = this.#relocate(info);\n if (wait > 0)\n this.#fencing.set(info.id, this.#restartLater(info, wait));\n else this.#launchOrRecover(info.id, info.shards);\n }, delay);\n // Do not hold the event loop open purely for a pending restart.\n (timer as { unref?: () => void }).unref?.();\n this.#restartTimers.add(timer);\n return timer;\n }\n\n /** A host joined: clusters waiting for it to fence start there now. */\n #hostJoined(hostId: string): void {\n this.#emit(\"hostJoin\", hostId);\n for (const [id, timer] of [...this.#fencing]) {\n const info = this.#infos.get(id);\n if (this.#hostOf.get(id) !== hostId || !info) continue;\n clearTimeout(timer);\n this.#restartTimers.delete(timer);\n this.#fencing.delete(id);\n if (!this.#stopping && info.state === \"restarting\")\n this.#launchOrRecover(info.id, info.shards);\n }\n }\n\n /**\n * Moves a cluster whose host is lost to the connected place running the\n * fewest clusters (`remote.moveClusters`). Its old workers must be gone\n * first, so a shard never runs on two hosts.\n * @returns Milliseconds to wait before the lost host is certainly fenced, or 0.\n */\n #relocate(info: ClusterInfo): number {\n const remote = this.#remote;\n const host = this.#hostOf.get(info.id) ?? \"local\";\n if (\n !remote ||\n this.#options.remote?.moveClusters === false ||\n host === \"local\" ||\n remote.isConnected(host)\n )\n return 0;\n const wait = remote.fencedAt(host) - Date.now();\n if (wait > 0) return wait;\n const places = this.#options.remote?.local\n ? [\"local\", ...remote.connected()]\n : remote.connected();\n // Nowhere to go: the launch fails and the supervisor keeps trying.\n if (places.length === 0) return 0;\n const load = new Map(places.map((place) => [place, 0]));\n for (const [cluster, place] of this.#hostOf) {\n const state = this.#infos.get(cluster)?.state;\n if (load.has(place) && state !== \"failed\" && state !== \"stopped\")\n load.set(place, load.get(place)! + 1);\n }\n const target = places.reduce((best, place) =>\n load.get(place)! < load.get(best)! ? place : best,\n );\n this.#hostOf.set(info.id, target);\n info.host = target;\n this.#emit(\"clusterMoved\", info, { from: host, to: target });\n return 0;\n }\n\n /**\n * Moves a failed cluster's shards to the ready cluster that owns the\n * fewest. Ownership changes only after that worker confirmed it started\n * them, and the failed cluster's process is already dead, so no shard\n * ever has two owners.\n */\n async #reassign(failed: ClusterInfo): Promise<void> {\n const shardIds = [...failed.shards];\n const target = [...this.#infos.values()]\n .filter((cluster) => cluster.state === \"ready\")\n .sort((a, b) => a.shards.length - b.shards.length)[0];\n if (!target) {\n this.#emit(\n \"error\",\n new Error(\n `No ready cluster can take over shards [${shardIds}] of cluster ${failed.id}.`,\n ),\n );\n this.#rejectReady(failed, \"no ready cluster to take them\");\n return;\n }\n try {\n await this.requestCluster(\n target.id,\n REQUEST_ASSIGN,\n { shardIds },\n { timeoutMs: this.#options.startTimeout ?? 120_000 },\n );\n } catch (error) {\n this.#emit(\n \"error\",\n new Error(\n `Cluster ${target.id} could not take over shards [${shardIds}]: ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n this.#rejectReady(failed, \"reassignment failed\");\n return;\n }\n if (this.#stopping || failed.state !== \"failed\") return;\n for (const shard of shardIds) this.#owner.set(shard, target.id);\n target.shards.push(...shardIds);\n failed.shards = [];\n this.#emit(\"shardsReassigned\", {\n shardIds,\n from: failed.id,\n to: target.id,\n });\n this.#checkReady();\n }\n\n #clearStartTimer(id: number): void {\n const timer = this.#startTimers.get(id);\n if (timer) clearTimeout(timer);\n this.#startTimers.delete(id);\n }\n\n #sendFrame(cluster: ClusterInfo, frame: unknown): boolean {\n const child = cluster.process;\n if (\n !isLive(cluster.state) ||\n typeof child.send !== \"function\" ||\n !child.connected\n )\n return false;\n try {\n child.send(frame as never);\n return true;\n } catch {\n // The child is exiting; its own exit handler takes over.\n return false;\n }\n }\n\n /** Tells the surviving clusters that these shards are gone, so requests to them fail now instead of timing out. */\n #announceDown(exited: number, shards: number[]): void {\n for (const [id, cluster] of this.clusters)\n if (id !== exited)\n this.#sendFrame(cluster, { [PEER_DOWN_FRAME]: shards });\n }\n\n /** Whether `child` is the process of the cluster as this deployment knows it now. */\n #current(info: ClusterInfo, child: ChildProcess): boolean {\n return info.process === child && this.#infos.get(info.id) === info;\n }\n\n #onFrame(info: ClusterInfo, child: ChildProcess, frame: unknown): void {\n if (!this.#current(info, child)) return;\n if (!frame || typeof frame !== \"object\") return;\n if (BUS_FRAME in frame) {\n this.#onBus(info, (frame as Record<string, unknown>)[BUS_FRAME]);\n return;\n }\n if (CLUSTER_FRAME in frame) {\n const body = parseChildFrame(\n (frame as Record<string, unknown>)[CLUSTER_FRAME],\n );\n if (body) this.#onControl(info, child, body);\n else\n this.#emit(\n \"error\",\n new Error(\n `Cluster ${info.id} sent a malformed control frame.`,\n ),\n );\n }\n // Anything else is the bot's own process.send() traffic.\n }\n\n #onControl(\n info: ClusterInfo,\n child: ChildProcess,\n frame: ChildFrame,\n ): void {\n switch (frame.kind) {\n case \"ready\": {\n if (!frame.shardIds.every((id) => info.shards.includes(id))) {\n this.#emit(\n \"error\",\n new Error(\n `Cluster ${info.id} reported shards it does not own: [${frame.shardIds}].`,\n ),\n );\n return;\n }\n this.#clearStartTimer(info.id);\n info.state = \"ready\";\n info.readyAt = info.lastReadyAt = Date.now();\n this.#emit(\"clusterReady\", info);\n this.#checkReady();\n return;\n }\n case \"identify\": {\n const reply = (body: Record<string, unknown>): void => {\n if (this.#current(info, child))\n this.#sendFrame(info, { [CLUSTER_FRAME]: body });\n };\n if (!this.#coordinator) {\n reply({\n kind: \"identify-deny\",\n id: frame.id,\n error: \"IDENTIFY coordination is off.\",\n });\n return;\n }\n if (!info.shards.includes(frame.shardId)) {\n reply({\n kind: \"identify-deny\",\n id: frame.id,\n error: `Shard ${frame.shardId} does not belong to cluster ${info.id}.`,\n });\n return;\n }\n // A shard asks for one slot at a time, so a worker cannot queue\n // more requests than it has shards.\n let waiting = this.#identifyWaiting.get(info.id);\n if (!waiting)\n this.#identifyWaiting.set(info.id, (waiting = new Set()));\n if (waiting.has(frame.shardId)) {\n reply({\n kind: \"identify-deny\",\n id: frame.id,\n error: `Shard ${frame.shardId} is already waiting for a slot.`,\n });\n return;\n }\n waiting.add(frame.shardId);\n const settled = (): void => {\n waiting.delete(frame.shardId);\n };\n this.#coordinator.acquire(info.id, frame.shardId).then(\n () => {\n settled();\n reply({ kind: \"identify-grant\", id: frame.id });\n },\n (error: unknown) => {\n settled();\n reply({\n kind: \"identify-deny\",\n id: frame.id,\n error:\n error instanceof Error\n ? error.message\n : String(error),\n });\n },\n );\n return;\n }\n case \"identify-done\":\n this.#coordinator?.release(info.id, frame.shardId);\n return;\n case \"reply\": {\n // An oversized answer settles the request with an error now\n // instead of being held until its timeout.\n const tooBig =\n frame.error === undefined && this.#tooBig(frame.result);\n this.#tracker.answer(frame.id, `c:${info.id}`, info.id, {\n ...(frame.error !== undefined\n ? { error: frame.error }\n : tooBig\n ? {\n error: `Cluster ${info.id} answered with more than ${this.#maxBytes} bytes.`,\n }\n : { result: frame.result }),\n });\n return;\n }\n case \"health\":\n if (this.#tooBig(frame.report)) {\n this.#emit(\n \"error\",\n new Error(\n `Cluster ${info.id} sent a health report of more than ${this.#maxBytes} bytes.`,\n ),\n );\n return;\n }\n info.report = frame.report;\n return;\n }\n }\n\n get #maxBytes(): number {\n return this.#options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;\n }\n\n #tooBig(value: unknown): boolean {\n return payloadSize(value) > this.#maxBytes;\n }\n\n /** @throws {RangeError} If a payload this process is about to send is over the limit. */\n #assertSize(data: unknown, what: string): void {\n if (this.#tooBig(data))\n throw new RangeError(\n `${what} is larger than maxMessageBytes (${this.#maxBytes}).`,\n );\n }\n\n /** Routes one shard-bus message: replies for the parent are consumed, shard-targeted messages go to the owning cluster only, broadcasts to every other cluster. */\n #onBus(from: ClusterInfo, raw: unknown): void {\n let message = raw as {\n source?: unknown;\n target?: unknown;\n type?: unknown;\n data?: unknown;\n } | null;\n if (\n !message ||\n typeof message !== \"object\" ||\n typeof message.type !== \"string\" ||\n message.type.length === 0 ||\n message.type.length > 256 ||\n (message as { id?: unknown }).id === undefined ||\n typeof (message as { id?: unknown }).id !== \"string\" ||\n (message as { id: string }).id.length > 256 ||\n !Number.isInteger(message.source) ||\n (message.target !== null && !Number.isInteger(message.target))\n )\n return;\n // A cluster speaks only for its own shards: a `source` it does not\n // own would let it answer requests meant for another cluster's shard.\n if (!from.shards.includes(message.source as number)) return;\n // Relay only the documented envelope, so undeclared fields cannot\n // ride along to every other cluster.\n const { type, id, source, target, data, expectsReply } =\n message as Record<string, unknown>;\n message = {\n type,\n id,\n source,\n target,\n data,\n ...(expectsReply === undefined ? {} : { expectsReply }),\n } as typeof message;\n if (this.#tooBig(message.data)) {\n this.#emit(\n \"error\",\n new Error(\n `Cluster ${from.id} sent a shard-bus message of more than ${this.#maxBytes} bytes; it was not relayed.`,\n ),\n );\n return;\n }\n const frame = { [BUS_FRAME]: message };\n if (message.target === PARENT_ID) {\n const reply = message.data as ReplyPayload | undefined;\n if (\n message.type === REPLY_TYPE &&\n reply &&\n typeof reply.requestId === \"string\"\n )\n this.#tracker.answer(\n reply.requestId,\n `s:${message.source}`,\n from.id,\n reply.error !== undefined\n ? { error: String(reply.error) }\n : { result: reply.result },\n );\n return;\n }\n if (message.target === null) {\n for (const [id, cluster] of this.clusters)\n if (id !== from.id) this.#sendFrame(cluster, frame);\n return;\n }\n const owner = this.clusterForShard(message.target as number);\n if (!owner || owner.id === from.id) return;\n // Fail fast: a request to a shard whose cluster is down rejects now.\n if (!this.#sendFrame(owner, frame))\n this.#sendFrame(from, {\n [PEER_DOWN_FRAME]: [message.target as number],\n });\n }\n\n /**\n * Sends a request to one shard and resolves with its handler's result.\n * @throws {RangeError} If the shard is not part of this deployment.\n * @throws {Error} If its cluster is down, the handler failed, or no answer arrives in time.\n */\n public async request<R = unknown, T = unknown>(\n shardId: number,\n type: string,\n data: T,\n options: RequestOptions = {},\n ): Promise<R> {\n this.#assertSize(data, \"The request data\");\n const owner = this.clusterForShard(shardId);\n if (!owner)\n throw new RangeError(\n `Shard ${shardId} is not part of this deployment.`,\n );\n const { id, done } = this.#tracker.begin<R>({\n keys: new Map([[`s:${shardId}`, owner.id]]),\n timeoutMs:\n options.timeoutMs ?? this.#options.requestTimeout ?? 5000,\n signal: options.signal,\n });\n const sent = this.#sendFrame(owner, {\n [BUS_FRAME]: {\n source: PARENT_ID,\n target: shardId,\n type,\n data,\n id,\n expectsReply: true,\n },\n });\n if (!sent) {\n this.#tracker.failOwner(\n owner.id,\n `Shard ${shardId} is down: cluster ${owner.id} is ${owner.state}.`,\n );\n }\n const { replies } = await done;\n const reply = replies[0];\n if (!reply)\n throw new Error(\n `Shard ${shardId} did not reply to \"${type}\" in time.`,\n );\n if (reply.error !== undefined) throw new Error(reply.error);\n return reply.result as R;\n }\n\n /**\n * Asks every shard of every live cluster and collects the answers. Shards\n * whose cluster is down, or that do not answer in time, are listed in\n * `missing`; it does not reject for them.\n */\n public async broadcast<R = unknown, T = unknown>(\n type: string,\n data: T,\n options: RequestOptions = {},\n ): Promise<BroadcastResult<R>> {\n this.#assertSize(data, \"The request data\");\n const keys = new Map<string, number>();\n const down: number[] = [];\n for (const [shard, clusterId] of this.#owner) {\n const cluster = this.#infos.get(clusterId);\n if (cluster && isLive(cluster.state))\n keys.set(`s:${shard}`, clusterId);\n else down.push(shard);\n }\n const { id, done } = this.#tracker.begin<R>({\n keys,\n timeoutMs:\n options.timeoutMs ?? this.#options.requestTimeout ?? 5000,\n signal: options.signal,\n });\n const frame = {\n [BUS_FRAME]: {\n source: PARENT_ID,\n target: null,\n type,\n data,\n id,\n expectsReply: true,\n },\n };\n for (const cluster of this.clusters.values())\n if (!this.#sendFrame(cluster, frame))\n this.#tracker.failOwner(\n cluster.id,\n `Cluster ${cluster.id} is ${cluster.state}.`,\n );\n const { replies, missing } = await done;\n return {\n replies: replies.map((reply) => ({\n shardId: Number(reply.key.slice(2)),\n ...(reply.error !== undefined\n ? { error: reply.error }\n : { result: reply.result as R }),\n })),\n missing: [\n ...down,\n ...missing.map((key) => Number(key.slice(2))),\n ].sort((a, b) => a - b),\n };\n }\n\n /**\n * Sends a request to a cluster's worker (see `ClusterWorker.onRequest()`).\n * @throws {Error} If the cluster is unknown or down, the handler failed, or no answer arrives in time.\n */\n public async requestCluster<R = unknown, T = unknown>(\n clusterId: number,\n type: string,\n data: T,\n options: RequestOptions = {},\n ): Promise<R> {\n this.#assertSize(data, \"The request data\");\n const cluster = this.#infos.get(clusterId);\n if (!cluster)\n throw new RangeError(`Cluster ${clusterId} does not exist.`);\n const { id, done } = this.#tracker.begin<R>({\n keys: new Map([[`c:${clusterId}`, clusterId]]),\n timeoutMs:\n options.timeoutMs ?? this.#options.requestTimeout ?? 5000,\n signal: options.signal,\n });\n if (\n !this.#sendFrame(cluster, {\n [CLUSTER_FRAME]: { kind: \"request\", id, type, data },\n })\n )\n this.#tracker.failOwner(\n clusterId,\n `Cluster ${clusterId} is ${cluster.state}.`,\n );\n const { replies } = await done;\n const reply = replies[0];\n if (!reply)\n throw new Error(\n `Cluster ${clusterId} did not reply to \"${type}\" in time.`,\n );\n if (reply.error !== undefined) throw new Error(reply.error);\n return reply.result as R;\n }\n\n /** Resolves when every cluster is ready. @throws {Error} On timeout, or if the manager stops first. */\n public ready(timeoutMs?: number): Promise<void> {\n if (this.#isReady()) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n const waiter: {\n resolve: () => void;\n reject: (error: Error) => void;\n timer?: ReturnType<typeof setTimeout>;\n } = {\n resolve: () => {\n if (waiter.timer) clearTimeout(waiter.timer);\n this.#readyWaiters.delete(waiter);\n resolve();\n },\n reject: (error) => {\n if (waiter.timer) clearTimeout(waiter.timer);\n this.#readyWaiters.delete(waiter);\n reject(error);\n },\n };\n if (timeoutMs !== undefined)\n waiter.timer = setTimeout(\n () =>\n waiter.reject(\n new Error(\n `Clusters were not all ready within ${timeoutMs}ms.`,\n ),\n ),\n timeoutMs,\n );\n this.#readyWaiters.add(waiter);\n });\n }\n\n /**\n * Every shard of the deployment is served by a ready cluster. A failed\n * cluster whose shards were reassigned no longer counts.\n */\n #isReady(): boolean {\n let ready = 0;\n for (const cluster of this.#infos.values()) {\n if (cluster.state === \"ready\") ready++;\n else if (!(\n cluster.state === \"failed\" && cluster.shards.length === 0\n ))\n return false;\n }\n return ready > 0;\n }\n\n /**\n * Rejects pending `ready()` calls: a failed cluster's shards have no owner\n * and nothing will give them one, so waiting longer would never end.\n */\n #rejectReady(failed: ClusterInfo, why: string): void {\n if (failed.shards.length === 0) return;\n const error = new Error(\n `Cluster ${failed.id} failed and shards [${failed.shards}] have no owner (${why}).`,\n );\n for (const waiter of [...this.#readyWaiters]) waiter.reject(error);\n }\n\n #checkReady(): void {\n if (!this.#isReady()) return;\n for (const waiter of [...this.#readyWaiters]) waiter.resolve();\n if (!this.#allReady) {\n this.#allReady = true;\n this.#emit(\"ready\");\n }\n }\n\n /**\n * The last known health of every cluster and shard. Reads what the\n * workers last reported; nothing is sent. Use {@link refreshHealth} for fresh numbers.\n */\n public health(): ClusterManagerHealth {\n const now = Date.now();\n const clusters: ClusterHealth[] = [...this.#infos.values()]\n .sort((a, b) => a.id - b.id)\n .map((cluster) => ({\n id: cluster.id,\n state: cluster.state,\n ...(cluster.process.pid !== undefined && isLive(cluster.state)\n ? { pid: cluster.process.pid }\n : {}),\n host: cluster.host,\n shardIds: [...cluster.shards],\n uptimeMs: isLive(cluster.state) ? now - cluster.startedAt : 0,\n restarts: cluster.restarts,\n startedAt: cluster.startedAt,\n ...(cluster.readyAt !== undefined\n ? { readyAt: cluster.readyAt }\n : {}),\n ...(cluster.lastReadyAt !== undefined\n ? { lastReadyAt: cluster.lastReadyAt }\n : {}),\n shards: cluster.report?.shards ?? ([] as ShardHealth[]),\n ...(cluster.report?.metrics\n ? { metrics: cluster.report.metrics as WorkerMetrics }\n : {}),\n ...(cluster.report\n ? { reportedAt: cluster.report.reportedAt }\n : {}),\n }));\n const missingShards = [...this.#owner]\n .filter(([, id]) => this.#infos.get(id)?.state !== \"ready\")\n .map(([shard]) => shard)\n .sort((a, b) => a - b);\n const states = clusters.map((cluster) => cluster.state);\n const state: ClusterManagerHealth[\"state\"] = this.#stopping\n ? \"stopping\"\n : clusters.length === 0\n ? this.#wasStarted\n ? \"stopped\"\n : \"idle\"\n : states.every((s) => s === \"stopped\")\n ? \"stopped\"\n : states.every((s) => s === \"ready\")\n ? \"ready\"\n : states.some((s) => s === \"restarting\" || s === \"failed\")\n ? \"degraded\"\n : \"starting\";\n return {\n state,\n totalShards: this.shardCount,\n clusters,\n readyClusters: states.filter((s) => s === \"ready\").length,\n missingShards,\n };\n }\n\n /**\n * Asks every live cluster for a fresh report, then returns {@link health}.\n * A cluster that does not answer in time keeps its last report; it is not\n * treated as failed (only its process exiting does that).\n */\n public async refreshHealth(\n options: { metrics?: boolean; timeoutMs?: number } = {},\n ): Promise<ClusterManagerHealth> {\n await Promise.all(\n [...this.clusters.values()].map(async (cluster) => {\n try {\n cluster.report = await this.requestCluster<WorkerReport>(\n cluster.id,\n REQUEST_HEALTH,\n { metrics: options.metrics === true },\n { timeoutMs: options.timeoutMs ?? 2000 },\n );\n } catch {\n // Keep the previous report.\n }\n }),\n );\n return this.health();\n }\n\n /** Checks if the recommended shard count has changed and respawns if so. */\n public async checkAutoScale(): Promise<void> {\n if (!this.#auto) return; // Only auto-scale in auto mode\n try {\n const recommended = await this.fetchRecommendedShardCount();\n if (recommended !== this.shardCount) {\n await this.respawn(recommended);\n }\n } catch (error) {\n // Surface the failure instead of silently swallowing it; the next\n // interval tick retries.\n const normalized =\n error instanceof Error ? error : new Error(String(error));\n this.#options.onAutoScaleError?.(normalized);\n }\n }\n\n /** Gracefully shuts the existing clusters down and respawns them with the new shard count. @param newShardCount The new total shard count. */\n public async respawn(newShardCount: number): Promise<void> {\n this.#options.shardCount = newShardCount;\n // Hosts stay connected across a respawn, so they need not rejoin.\n await this.#stop(this.#options.shutdownTimeout ?? 5000, true);\n this.#supervisor.reset();\n await this.spawn();\n }\n\n /**\n * Gracefully stops all active clusters: sends SIGTERM, awaits each child's\n * clean exit, and force-kills (SIGKILL) only children that outlast the\n * shutdown timeout. Safe to call twice or concurrently; cancels restarts,\n * timers and pending requests.\n * @param timeoutMs Grace period per child before force-kill. Defaults to the configured `shutdownTimeout` (5000 ms).\n */\n public shutdownAll(\n timeoutMs = this.#options.shutdownTimeout ?? 5000,\n ): Promise<void> {\n return this.#stop(timeoutMs, false);\n }\n\n #stop(timeoutMs: number, keepRemote: boolean): Promise<void> {\n this.#stopPromise ??= this.#shutdown(timeoutMs, keepRemote).finally(\n () => {\n this.#stopPromise = undefined;\n },\n );\n return this.#stopPromise;\n }\n\n async #shutdown(timeoutMs: number, keepRemote: boolean): Promise<void> {\n this.#stopping = true;\n this.#halt();\n // A spawn that is still forking sees `#stopping` and stops on its own.\n await this.#startPromise?.catch(() => undefined);\n const started = Date.now();\n const live = [...this.clusters.values()];\n await Promise.all(\n live.map((cluster) => this.#shutdownCluster(cluster, timeoutMs)),\n );\n this.#emit(\"shutdown\", {\n clusters: live.length,\n durationMs: Date.now() - started,\n });\n for (const cluster of this.#infos.values()) cluster.state = \"stopped\";\n this.clusters.clear();\n this.#startPromise = undefined;\n if (!keepRemote) {\n this.#remote?.stop();\n await this.#resign();\n }\n this.#stopping = false;\n }\n\n /** Cancels everything that could start work or keep the loop alive. */\n #halt(): void {\n this.#generation++;\n for (const timer of this.#restartTimers) clearTimeout(timer);\n this.#restartTimers.clear();\n this.#fencing.clear();\n for (const id of [...this.#startTimers.keys()])\n this.#clearStartTimer(id);\n for (const wake of [...this.#pauses]) wake();\n if (this.#autoScaleTimer) {\n clearInterval(this.#autoScaleTimer);\n this.#autoScaleTimer = undefined;\n }\n this.#coordinator?.close();\n this.#coordinator = undefined;\n this.#remote?.abortWaits();\n this.#tracker.close();\n for (const waiter of [...this.#readyWaiters])\n waiter.reject(new Error(\"The cluster manager is stopping.\"));\n }\n\n /** Gracefully terminates a single cluster child, escalating to SIGKILL after the timeout. */\n async #shutdownCluster(\n cluster: ClusterInfo,\n timeoutMs: number,\n ): Promise<void> {\n const child = cluster.process;\n cluster.state = \"stopping\";\n if (child.exitCode !== null || child.signalCode !== null) return;\n await new Promise<void>((resolve) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const finish = (): void => {\n if (timer) clearTimeout(timer);\n resolve();\n };\n child.once(\"exit\", finish);\n try {\n child.kill(\"SIGTERM\");\n } catch {\n child.off(\"exit\", finish);\n finish();\n return;\n }\n timer = setTimeout(() => {\n try {\n child.kill(\"SIGKILL\");\n } catch {\n // Ignore force-kill errors.\n }\n // A process whose exit is never reported (a remote host that\n // lost the kill frame) must not hang the shutdown.\n timer = setTimeout(finish, Math.max(timeoutMs, 1000));\n }, timeoutMs);\n });\n }\n\n /** Immediately sends SIGTERM to every cluster process (no grace period, no SIGKILL escalation) and clears the cluster map. Prefer {@link shutdownAll} for a graceful stop. */\n public killAll(): void {\n this.#stopping = true;\n this.#halt();\n for (const cluster of this.#infos.values()) {\n cluster.state = \"stopped\";\n try {\n cluster.process.kill();\n } catch {\n // Ignore kill errors\n }\n }\n this.clusters.clear();\n this.#startPromise = undefined;\n this.#remote?.stop();\n void this.#resign();\n this.#stopping = false;\n }\n}\n\n/** Rejects when `promise` has not settled within `ms`. */\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Error(\n `The leader lock did not answer within ${ms} ms.`,\n ),\n ),\n ms,\n );\n });\n return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n",
|
|
14
|
+
"/** Discord allows one IDENTIFY per rate-limit key every five seconds. */\nexport const IDENTIFY_INTERVAL = 5000;\n\n/** What `/gateway/bot` says about the session budget. */\nexport interface IdentifyBudget {\n total: number;\n remaining: number;\n /** Milliseconds until `remaining` resets, counted from construction. */\n resetAfter: number;\n /** Milliseconds between resets after that one. Discord resets the budget every 24 hours (the default). */\n period?: number;\n}\n\n/** Options for an {@link IdentifyCoordinator}. */\nexport interface IdentifyCoordinatorOptions {\n /** Discord's `max_concurrency`: shards with the same `id % maxConcurrency` share a key. Default 1. */\n maxConcurrency?: number;\n /** Milliseconds between two IDENTIFYs on one key. Default 5000. */\n interval?: number;\n /** The session-start budget, when known. Without it the coordinator paces but never blocks. */\n budget?: IdentifyBudget;\n /** A granted slot that is not released within this many ms is released for the holder. Default 60,000. */\n holdTimeout?: number;\n /** Clock, injectable for tests. */\n now?: () => number;\n /** Timer, injectable for tests. Must return a handle `cancel` understands. */\n schedule?: (callback: () => void, ms: number) => unknown;\n cancel?: (handle: unknown) => void;\n /** Called when a request has to wait for the budget to reset. */\n onBudgetWait?: (info: { shardId: number; waitMs: number }) => void;\n /** Called for every queue change, for logs and metrics. */\n onEvent?: (event: IdentifyEvent) => void;\n}\n\n/** One change in the IDENTIFY queue. `bucket` is the rate-limit key, `shardId % maxConcurrency`. */\nexport interface IdentifyEvent {\n type: \"queued\" | \"granted\" | \"released\" | \"expired\" | \"dropped\";\n /** The cluster that asked. */\n owner: number;\n shardId: number;\n bucket: number;\n /** `granted`: how long the request waited. */\n waitedMs?: number;\n}\n\ninterface Waiter {\n owner: number;\n shardId: number;\n since: number;\n grant: () => void;\n fail: (error: Error) => void;\n}\n\ninterface Key {\n queue: Waiter[];\n /** The grant that has not been released yet. */\n holder?: { owner: number; shardId: number; timer: unknown };\n /** Earliest time the next IDENTIFY may start. */\n nextAt: number;\n}\n\n/**\n * Hands out IDENTIFY slots for a whole deployment. Every cluster asks the\n * parent, so the limit holds across processes: one IDENTIFY per rate-limit key\n * per interval, and no more IDENTIFYs than `session_start_limit.remaining`.\n * It only does arithmetic on timestamps and holds one timer, so it can be\n * tested with a fake clock.\n */\nexport class IdentifyCoordinator {\n readonly maxConcurrency: number;\n readonly #interval: number;\n readonly #holdTimeout: number;\n readonly #now: () => number;\n readonly #schedule: (callback: () => void, ms: number) => unknown;\n readonly #cancel: (handle: unknown) => void;\n readonly #onBudgetWait?: IdentifyCoordinatorOptions[\"onBudgetWait\"];\n readonly #onEvent?: IdentifyCoordinatorOptions[\"onEvent\"];\n readonly #keys = new Map<number, Key>();\n #remaining: number | undefined;\n #total: number | undefined;\n #resetAt = Number.POSITIVE_INFINITY;\n #period = 24 * 60 * 60 * 1000;\n #timer: unknown;\n #timerAt = Number.POSITIVE_INFINITY;\n #closed = false;\n\n public constructor(options: IdentifyCoordinatorOptions = {}) {\n const concurrency = options.maxConcurrency ?? 1;\n if (!Number.isInteger(concurrency) || concurrency < 1)\n throw new RangeError(\"maxConcurrency must be a positive integer.\");\n const interval = options.interval ?? IDENTIFY_INTERVAL;\n const hold = options.holdTimeout ?? 60_000;\n if (!(interval >= 0) || !(hold > 0))\n throw new RangeError(\n \"interval cannot be negative and holdTimeout must be positive.\",\n );\n this.maxConcurrency = concurrency;\n this.#interval = interval;\n this.#holdTimeout = hold;\n this.#now = options.now ?? Date.now;\n this.#schedule =\n options.schedule ??\n ((callback, ms) => {\n const timer = setTimeout(callback, ms);\n (timer as { unref?: () => void }).unref?.();\n return timer;\n });\n this.#cancel =\n options.cancel ??\n ((handle) => clearTimeout(handle as ReturnType<typeof setTimeout>));\n this.#onBudgetWait = options.onBudgetWait;\n this.#onEvent = options.onEvent;\n if (options.budget) {\n this.#total = options.budget.total;\n this.#remaining = options.budget.remaining;\n this.#resetAt = this.#now() + options.budget.resetAfter;\n this.#period = options.budget.period ?? this.#period;\n if (!(this.#period > 0))\n throw new RangeError(\"budget.period must be positive.\");\n }\n }\n\n /** Slots left in the session budget, or `undefined` when it is unknown. */\n public get remaining(): number | undefined {\n return this.#remaining;\n }\n\n /** Requests waiting for a slot. */\n public get waiting(): number {\n let count = 0;\n for (const key of this.#keys.values()) count += key.queue.length;\n return count;\n }\n\n /** Slots granted and not yet released. */\n public get holding(): number {\n let count = 0;\n for (const key of this.#keys.values()) if (key.holder) count++;\n return count;\n }\n\n /**\n * Waits for the shard's turn. Resolves when the shard may IDENTIFY now;\n * call {@link release} once it has. Rejects if `owner` is dropped or the\n * coordinator closes first.\n */\n public acquire(owner: number, shardId: number): Promise<void> {\n if (this.#closed)\n return Promise.reject(new Error(\"IDENTIFY coordinator is closed.\"));\n const index = shardId % this.maxConcurrency;\n let key = this.#keys.get(index);\n if (!key) this.#keys.set(index, (key = { queue: [], nextAt: 0 }));\n return new Promise<void>((grant, fail) => {\n key.queue.push({ owner, shardId, grant, fail, since: this.#now() });\n this.#report({ type: \"queued\", owner, shardId, bucket: index });\n this.pump();\n });\n }\n\n /** The shard has sent its IDENTIFY: its key rests for one interval. Ignored unless `owner` holds the slot. */\n public release(owner: number, shardId: number): void {\n const key = this.#keys.get(shardId % this.maxConcurrency);\n const holder = key?.holder;\n if (\n !key ||\n !holder ||\n holder.owner !== owner ||\n holder.shardId !== shardId\n )\n return;\n this.#free(key);\n this.#report({\n type: \"released\",\n owner,\n shardId,\n bucket: shardId % this.maxConcurrency,\n });\n this.pump();\n }\n\n #report(event: IdentifyEvent): void {\n try {\n this.#onEvent?.(event);\n } catch {\n // A faulty listener must not break the queue.\n }\n }\n\n /** A cluster is gone: forget its waiting requests and free the slots it held. */\n public drop(owner: number, reason = \"The cluster went away.\"): void {\n for (const key of this.#keys.values()) {\n const keep: Waiter[] = [];\n for (const waiter of key.queue)\n if (waiter.owner === owner) {\n waiter.fail(new Error(reason));\n this.#report({\n type: \"dropped\",\n owner,\n shardId: waiter.shardId,\n bucket: waiter.shardId % this.maxConcurrency,\n });\n } else keep.push(waiter);\n key.queue = keep;\n if (key.holder?.owner === owner) this.#free(key);\n }\n this.pump();\n }\n\n /** Rejects every waiter and stops the timer. */\n public close(): void {\n this.#closed = true;\n if (this.#timer !== undefined) this.#cancel(this.#timer);\n this.#timer = undefined;\n this.#timerAt = Number.POSITIVE_INFINITY;\n for (const key of this.#keys.values()) {\n if (key.holder) this.#cancel(key.holder.timer);\n key.holder = undefined;\n for (const waiter of key.queue)\n waiter.fail(new Error(\"IDENTIFY coordinator is closed.\"));\n key.queue = [];\n }\n }\n\n /** Grants every slot that is due and arms one timer for the next. Called internally and by tests after moving the clock. */\n public pump(): void {\n if (this.#closed) return;\n const now = this.#now();\n if (this.#total !== undefined && now >= this.#resetAt) {\n this.#remaining = this.#total;\n // The budget refills every period, so the next reset is known: a\n // later exhaustion waits for it instead of failing for good.\n while (now >= this.#resetAt) this.#resetAt += this.#period;\n }\n let next = Number.POSITIVE_INFINITY;\n for (const key of this.#keys.values()) {\n if (key.holder || key.queue.length === 0) continue;\n if (key.nextAt > now) {\n next = Math.min(next, key.nextAt);\n continue;\n }\n if (this.#remaining !== undefined && this.#remaining <= 0) {\n const waiter = key.queue[0]!;\n this.#onBudgetWait?.({\n shardId: waiter.shardId,\n waitMs: this.#resetAt - now,\n });\n next = Math.min(next, this.#resetAt);\n continue;\n }\n const waiter = key.queue.shift()!;\n if (this.#remaining !== undefined) this.#remaining--;\n const timer = this.#schedule(() => {\n if (key.holder?.timer === timer) {\n this.#report({\n type: \"expired\",\n owner: waiter.owner,\n shardId: waiter.shardId,\n bucket: waiter.shardId % this.maxConcurrency,\n });\n this.#free(key);\n this.pump();\n }\n }, this.#holdTimeout);\n key.holder = {\n owner: waiter.owner,\n shardId: waiter.shardId,\n timer,\n };\n waiter.grant();\n this.#report({\n type: \"granted\",\n owner: waiter.owner,\n shardId: waiter.shardId,\n bucket: waiter.shardId % this.maxConcurrency,\n waitedMs: now - waiter.since,\n });\n }\n this.#arm(next);\n }\n\n #free(key: Key): void {\n if (key.holder) this.#cancel(key.holder.timer);\n key.holder = undefined;\n key.nextAt = this.#now() + this.#interval;\n }\n\n #arm(at: number): void {\n if (at === this.#timerAt && this.#timer !== undefined) return;\n if (this.#timer !== undefined) this.#cancel(this.#timer);\n this.#timer = undefined;\n this.#timerAt = Number.POSITIVE_INFINITY;\n if (at === Number.POSITIVE_INFINITY) return;\n this.#timerAt = at;\n this.#timer = this.#schedule(\n () => {\n this.#timer = undefined;\n this.#timerAt = Number.POSITIVE_INFINITY;\n this.pump();\n },\n Math.max(0, at - this.#now()),\n );\n }\n}\n",
|
|
15
|
+
"import { EventEmitter } from \"node:events\";\nimport { randomBytes } from \"node:crypto\";\nimport type { ChildProcess } from \"node:child_process\";\nimport type { Server, ServerWebSocket } from \"bun\";\nimport {\n HOST_ID,\n MAX_REMOTE_FRAME_BYTES,\n isLoopbackHost,\n REMOTE_PROTOCOL_VERSION,\n RemoteClose,\n parseToCoordinator,\n verifyChallenge,\n type ToHost,\n} from \"./remote-protocol.js\";\n\n/** Where, and how, a coordinator listens for hosts. */\nexport interface RemoteOptions {\n /** TCP port. `0` picks a free one (read it from `ClusterManager#remotePort`). */\n port: number;\n /**\n * Interface to bind. Default `127.0.0.1`, which only this machine can\n * reach. Any other address needs `tls` or `allowInsecure`.\n */\n hostname?: string;\n /** Shared secret, at least 16 characters. Hosts prove they know it; it never crosses the wire. */\n secret: string;\n /** TLS for `wss://`. Required to listen on a non-loopback address unless `allowInsecure` is set. */\n tls?: { cert: string; key: string };\n /**\n * Listen on a non-loopback address without TLS: frames (shard messages\n * and worker output) travel unencrypted and unsigned. Only for a network\n * you fully trust, or when a TLS proxy in front terminates the connection.\n * Default false.\n */\n allowInsecure?: boolean;\n /**\n * The host IDs that make up the deployment. Clusters are placed on them in\n * this order, and an unlisted host is refused. Without it, the hosts that\n * have joined when `spawn()` starts are used, sorted by ID.\n */\n hosts?: string[];\n /** How many hosts must join before clusters are placed when `hosts` is not set. Default 1. */\n minHosts?: number;\n /** Milliseconds to wait for the hosts to join before `spawn()` fails. Default 60,000. */\n joinTimeout?: number;\n /**\n * Fencing lease in milliseconds. A host that hears nothing from the\n * coordinator for this long kills its workers; the coordinator waits twice\n * as long before it treats the host as lost and restarts its clusters, so\n * a shard never runs in two places. Default 15,000.\n */\n lease?: number;\n /** Milliseconds a new connection has to authenticate before it is closed. Default 3,000. */\n helloTimeout?: number;\n /**\n * Most connections that may be waiting to authenticate at once. When full,\n * the one that has waited longest is closed to make room: a host answers\n * the challenge within a round trip, a connection that holds a slot idle\n * does not. Default 16.\n */\n maxUnauthenticated?: number;\n /** The same limit for one client address (see `clientAddress`). Default 4. */\n maxUnauthenticatedPerAddress?: number;\n /** New connections one client address may open per second; more are refused. Default 10. */\n connectionsPerSecond?: number;\n /**\n * The client address the per-address limits count by. Default: the peer\n * address of the TCP connection. Behind a TLS proxy every peer is the\n * proxy, so return the client address the proxy forwards (only from a\n * header your proxy sets and overwrites).\n */\n clientAddress?: (request: Request, peer: string | undefined) => string;\n /** Also run clusters on the coordinator's own machine. Default false: clusters run on the hosts only. */\n local?: boolean;\n /**\n * When a host is lost, restart its clusters on the connected host (or\n * `local`) running the fewest, once the lost host has certainly fenced\n * itself (two leases after the coordinator dropped it). They stay there\n * when the host comes back. Default true; `false` restarts them on the\n * same host only, so they wait for it.\n */\n moveClusters?: boolean;\n}\n\n/** A host as the coordinator sees it. */\nexport interface HostStatus {\n id: string;\n /** Whether its socket is open and answering. */\n connected: boolean;\n /** When it last sent anything (epoch ms). */\n lastSeen: number;\n /** Clusters currently running on it. */\n clusters: number[];\n}\n\ninterface SocketData {\n nonce: string;\n /** The client address the per-address limits count by. */\n address: string;\n id?: string;\n /** Whether this socket is counted among the pending (unauthenticated) ones. */\n counted?: boolean;\n helloTimer?: ReturnType<typeof setTimeout>;\n}\n\ninterface Link {\n id: string;\n ws: ServerWebSocket<SocketData>;\n lastSeen: number;\n children: Map<number, RemoteChild>;\n}\n\ntype Kill = (signal: string) => boolean;\n\n/** What the coordinator wants to hear about its hosts. */\nexport interface RemoteHooks {\n join(id: string): void;\n leave(id: string, reason: string): void;\n error(error: Error): void;\n}\n\n/**\n * A worker process on another host, shaped like the `ChildProcess` that\n * `ClusterManager` supervises. `send()` and `kill()` travel to the host's\n * agent; its messages and exit travel back, so the manager's restart,\n * IDENTIFY, routing and health logic needs no special case for it.\n */\nexport class RemoteChild extends EventEmitter {\n public pid: number | undefined;\n public exitCode: number | null = null;\n public signalCode: NodeJS.Signals | null = null;\n public connected = true;\n\n readonly #send: (frame: unknown) => boolean;\n readonly #kill: Kill;\n\n public constructor(\n public readonly host: string,\n public readonly cluster: number,\n public readonly epoch: number,\n send: (frame: unknown) => boolean,\n kill: Kill,\n ) {\n super();\n this.#send = send;\n this.#kill = kill;\n }\n\n /** Sends an IPC frame to the worker. */\n public send(frame: unknown): boolean {\n return this.connected &&\n this.exitCode === null &&\n this.signalCode === null\n ? this.#send(frame)\n : false;\n }\n\n /** Asks the host to signal the worker; the exit arrives as an event. */\n public kill(signal: string = \"SIGTERM\"): boolean {\n if (this.exitCode !== null || this.signalCode !== null) return false;\n return this.#kill(signal);\n }\n\n /** The agent says the worker started. */\n public spawned(pid: number | undefined): void {\n this.pid = pid;\n }\n\n /** The worker sent a message. */\n public deliver(frame: unknown): void {\n if (this.exitCode === null && this.signalCode === null)\n this.emit(\"message\", frame);\n }\n\n /** The worker exited, or its host was lost. Emits `exit` once. */\n public finish(code: number | null, signal: string | null): void {\n if (this.exitCode !== null || this.signalCode !== null) return;\n this.connected = false;\n // `exitCode` and `signalCode` must not both stay null once finished.\n this.exitCode = code ?? (signal === null ? 1 : null);\n this.signalCode = (signal as NodeJS.Signals | null) ?? null;\n this.emit(\"exit\", code, signal);\n }\n}\n\n/** The coordinator's listener and the hosts that joined it. */\nexport class RemoteHosts {\n readonly #options: Required<\n Pick<RemoteOptions, \"port\" | \"secret\" | \"lease\">\n > &\n RemoteOptions;\n readonly #links = new Map<string, Link>();\n /** When each lost host was dropped (epoch ms); cleared when it joins again. */\n readonly #droppedAt = new Map<string, number>();\n readonly #waiters = new Set<{ check(): void; abort(error: Error): void }>();\n readonly #hooks: RemoteHooks;\n #server?: Server<SocketData>;\n #watchdog?: ReturnType<typeof setInterval>;\n #epoch = 0;\n #ping = 0;\n /** Sockets that have not said hello yet, oldest first. */\n readonly #pending = new Set<ServerWebSocket<SocketData>>();\n /** Pending sockets per client address. */\n readonly #pendingByAddress = new Map<string, number>();\n /** Connection allowance per client address (token buckets). */\n readonly #rates = new Map<string, { tokens: number; at: number }>();\n\n public constructor(options: RemoteOptions, hooks: RemoteHooks) {\n if (typeof options.secret !== \"string\" || options.secret.length < 16)\n throw new RangeError(\n \"remote.secret must be a string of at least 16 characters.\",\n );\n if (\n !Number.isInteger(options.port) ||\n options.port < 0 ||\n options.port > 65535\n )\n throw new RangeError(\n \"remote.port must be an integer from 0 to 65535.\",\n );\n if (\n options.tls &&\n (typeof options.tls.cert !== \"string\" ||\n typeof options.tls.key !== \"string\" ||\n !options.tls.cert ||\n !options.tls.key)\n )\n throw new TypeError(\"remote.tls needs a non-empty cert and key.\");\n if (\n !options.tls &&\n !options.allowInsecure &&\n !isLoopbackHost(options.hostname ?? \"127.0.0.1\")\n )\n throw new RangeError(\n \"remote listens on a network address without TLS: set remote.tls, bind 127.0.0.1 behind a TLS proxy, or set remote.allowInsecure for a network you fully trust.\",\n );\n const lease = options.lease ?? 15_000;\n if (!Number.isFinite(lease) || lease < 30)\n throw new RangeError(\n \"remote.lease must be at least 30 milliseconds.\",\n );\n for (const id of options.hosts ?? [])\n if (!HOST_ID.test(id))\n throw new RangeError(`\"${id}\" is not a valid host ID.`);\n if (\n options.hosts &&\n (options.hosts.length === 0 ||\n new Set(options.hosts).size !== options.hosts.length)\n )\n throw new RangeError(\"remote.hosts must be unique and not empty.\");\n if (\n options.minHosts !== undefined &&\n (!Number.isInteger(options.minHosts) || options.minHosts < 1)\n )\n throw new RangeError(\"remote.minHosts must be a positive integer.\");\n this.#options = { ...options, lease };\n this.#hooks = hooks;\n }\n\n /** The port in use, once started. */\n public get port(): number | undefined {\n return this.#server?.port;\n }\n\n /** Host IDs that are connected now, sorted. */\n public connected(): string[] {\n return [...this.#links.keys()].sort();\n }\n\n /** Whether `hostId` is connected now. */\n public isConnected(hostId: string): boolean {\n return this.#links.has(hostId);\n }\n\n /**\n * When workers that ran on a lost host are certainly gone (epoch ms), or 0\n * for a host that is connected or was never dropped. The host fences\n * itself one lease after it last heard from the coordinator, checking\n * every third of a lease; the coordinator sends nothing after the drop,\n * and two leases leave room for the kill itself.\n */\n public fencedAt(hostId: string): number {\n const dropped = this.#droppedAt.get(hostId);\n return dropped === undefined || this.#links.has(hostId)\n ? 0\n : dropped + this.#options.lease * 2;\n }\n\n /** Opens the listener. Safe to call twice. */\n public start(): void {\n if (this.#server) return;\n const { port, hostname, tls, lease } = this.#options;\n this.#server = Bun.serve<SocketData>({\n port,\n hostname: hostname ?? \"127.0.0.1\",\n ...(tls ? { tls } : {}),\n fetch: (request, server) => {\n const peer = server.requestIP(request)?.address;\n const address =\n this.#options.clientAddress?.(request, peer) ??\n peer ??\n \"unknown\";\n // Refused before the upgrade: the cheapest answer to a flood.\n if (!this.#allowConnection(address))\n return new Response(\"Too many connections.\", {\n status: 429,\n });\n if (\n server.upgrade(request, {\n data: {\n nonce: randomBytes(16).toString(\"hex\"),\n address,\n },\n })\n )\n return undefined;\n return new Response(\"Lunibee cluster coordinator\", {\n status: 426,\n });\n },\n websocket: {\n maxPayloadLength: MAX_REMOTE_FRAME_BYTES,\n idleTimeout: 0,\n open: (ws) => this.#open(ws),\n message: (ws, raw) => this.#message(ws, raw),\n close: (ws, _code, reason) => this.#closed(ws, reason),\n },\n });\n const pingMs = Math.max(10, Math.floor(lease / 3));\n this.#watchdog = setInterval(() => this.#tick(), pingMs);\n (this.#watchdog as { unref?: () => void }).unref?.();\n }\n\n /** Closes the listener and every host connection. */\n public stop(): void {\n if (this.#watchdog) clearInterval(this.#watchdog);\n this.#watchdog = undefined;\n for (const link of [...this.#links.values()]) {\n this.#post(link, { t: \"bye\", reason: \"The coordinator stopped.\" });\n this.#drop(link, \"The coordinator stopped.\", RemoteClose.Shutdown);\n }\n void this.#server?.stop(true);\n this.#server = undefined;\n this.abortWaits();\n }\n\n /** Resolves when the given hosts (or `count` hosts) are connected. @throws {Error} On timeout. */\n public waitFor(\n hosts: readonly string[] | undefined,\n count: number,\n timeoutMs: number,\n ): Promise<void> {\n const done = (): boolean =>\n hosts\n ? hosts.every((id) => this.#links.has(id))\n : this.#links.size >= count;\n if (done()) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n const waiter = {\n check: (): void => {\n if (!done()) return;\n clearTimeout(timer);\n this.#waiters.delete(waiter);\n resolve();\n },\n abort: (error: Error): void => {\n clearTimeout(timer);\n this.#waiters.delete(waiter);\n reject(error);\n },\n };\n const timer = setTimeout(() => {\n const missing = hosts?.filter((id) => !this.#links.has(id));\n waiter.abort(\n new Error(\n hosts\n ? `Hosts did not join within ${timeoutMs}ms: [${missing}].`\n : `Only ${this.#links.size} of ${count} hosts joined within ${timeoutMs}ms.`,\n ),\n );\n }, timeoutMs);\n this.#waiters.add(waiter);\n });\n }\n\n /** Fails everything waiting for hosts to join (a shutdown). */\n public abortWaits(): void {\n for (const waiter of [...this.#waiters])\n waiter.abort(new Error(\"The coordinator stopped.\"));\n }\n\n /** Hosts and the clusters running on them. */\n public status(declared: readonly string[] = []): HostStatus[] {\n const ids = new Set([...declared, ...this.#links.keys()]);\n return [...ids].sort().map((id) => {\n const link = this.#links.get(id);\n return {\n id,\n connected: link !== undefined,\n lastSeen: link?.lastSeen ?? 0,\n clusters: link\n ? [...link.children.keys()].sort((a, b) => a - b)\n : [],\n };\n });\n }\n\n /**\n * Starts a worker on `hostId`. If the host is not connected the returned\n * child exits at once, which `ClusterManager` handles like a crash.\n */\n public createChild(\n hostId: string,\n cluster: number,\n env: Record<string, string>,\n ): ChildProcess {\n const epoch = ++this.#epoch;\n const link = this.#links.get(hostId);\n const child = new RemoteChild(\n hostId,\n cluster,\n epoch,\n (frame) =>\n link !== undefined &&\n this.#post(link, { t: \"frame\", cluster, epoch, frame }),\n (signal) =>\n link !== undefined &&\n this.#post(link, { t: \"kill\", cluster, epoch, signal }),\n );\n if (!link) {\n queueMicrotask(() => child.finish(null, \"SIGHUP\"));\n return child as unknown as ChildProcess;\n }\n link.children.get(cluster)?.finish(null, \"SIGKILL\");\n link.children.set(cluster, child);\n if (!this.#post(link, { t: \"spawn\", cluster, epoch, env }))\n queueMicrotask(() => child.finish(null, \"SIGHUP\"));\n return child as unknown as ChildProcess;\n }\n\n #post(link: Link, message: ToHost): boolean {\n try {\n return link.ws.send(JSON.stringify(message)) > 0;\n } catch {\n return false;\n }\n }\n\n /** Takes one connection from the address's per-second allowance. */\n #allowConnection(address: string): boolean {\n const rate = this.#options.connectionsPerSecond ?? 10;\n const now = Date.now();\n let bucket = this.#rates.get(address);\n if (!bucket) {\n bucket = { tokens: rate, at: now };\n this.#rates.set(address, bucket);\n // A bounded map: forget the address idle longest.\n if (this.#rates.size > 10_000)\n this.#rates.delete(this.#rates.keys().next().value as string);\n }\n bucket.tokens = Math.min(\n rate,\n bucket.tokens + ((now - bucket.at) / 1000) * rate,\n );\n bucket.at = now;\n if (bucket.tokens < 1) return false;\n bucket.tokens--;\n return true;\n }\n\n /** Closes a pending socket to make room for a newer one. */\n #evict(victim: ServerWebSocket<SocketData>): void {\n this.#uncount(victim);\n clearTimeout(victim.data.helloTimer);\n victim.close(RemoteClose.TooManyConnections, \"Too many connections.\");\n }\n\n #open(ws: ServerWebSocket<SocketData>): void {\n // An address at its limit gives up its oldest pending socket, then\n // the whole listener does: a connection that holds a slot without\n // answering cannot keep a host that answers at once from joining.\n const address = ws.data.address;\n if (\n (this.#pendingByAddress.get(address) ?? 0) >=\n (this.#options.maxUnauthenticatedPerAddress ?? 4)\n )\n for (const other of this.#pending)\n if (other.data.address === address) {\n this.#evict(other);\n break;\n }\n if (this.#pending.size >= (this.#options.maxUnauthenticated ?? 16)) {\n const oldest = this.#pending.values().next().value;\n if (oldest) this.#evict(oldest);\n }\n this.#pending.add(ws);\n this.#pendingByAddress.set(\n address,\n (this.#pendingByAddress.get(address) ?? 0) + 1,\n );\n ws.data.counted = true;\n ws.data.helloTimer = setTimeout(\n () => ws.close(RemoteClose.HelloTimeout, \"No hello.\"),\n this.#options.helloTimeout ?? 3_000,\n );\n ws.send(\n JSON.stringify({\n t: \"challenge\",\n nonce: ws.data.nonce,\n version: REMOTE_PROTOCOL_VERSION,\n } satisfies ToHost),\n );\n }\n\n #message(ws: ServerWebSocket<SocketData>, raw: string | Buffer): void {\n if (typeof raw !== \"string\") {\n ws.close(RemoteClose.BadRequest, \"Text frames only.\");\n return;\n }\n const message = parseToCoordinator(raw);\n if (!message) {\n ws.close(RemoteClose.BadRequest, \"Malformed frame.\");\n return;\n }\n if (ws.data.id === undefined) {\n this.#hello(ws, message);\n return;\n }\n const link = this.#links.get(ws.data.id);\n if (!link || link.ws !== ws) return;\n link.lastSeen = Date.now();\n if (message.t === \"pong\" || message.t === \"hello\") return;\n const child = link.children.get(message.cluster);\n // A frame from an earlier process of the same cluster is stale.\n if (!child || child.epoch !== message.epoch) return;\n switch (message.t) {\n case \"spawned\":\n child.spawned(message.pid);\n break;\n case \"frame\":\n child.deliver(message.frame);\n break;\n case \"exit\":\n link.children.delete(message.cluster);\n child.finish(message.code, message.signal);\n break;\n case \"error\":\n this.#hooks.error(\n new Error(\n `Host ${link.id} could not run cluster ${message.cluster}: ${message.message}`,\n ),\n );\n link.children.delete(message.cluster);\n child.finish(1, null);\n break;\n }\n }\n\n #hello(\n ws: ServerWebSocket<SocketData>,\n message: ReturnType<typeof parseToCoordinator>,\n ): void {\n if (message?.t !== \"hello\") {\n ws.close(RemoteClose.BadRequest, \"Expected hello.\");\n return;\n }\n const { id } = message;\n if (message.version !== REMOTE_PROTOCOL_VERSION) {\n ws.close(RemoteClose.VersionMismatch, \"Protocol version mismatch.\");\n return;\n }\n if (\n !HOST_ID.test(id) ||\n !verifyChallenge(\n this.#options.secret,\n ws.data.nonce,\n id,\n message.mac,\n )\n ) {\n ws.close(RemoteClose.AuthFailed, \"Authentication failed.\");\n return;\n }\n if (this.#options.hosts && !this.#options.hosts.includes(id)) {\n ws.close(\n RemoteClose.UnknownHost,\n \"Host is not part of this deployment.\",\n );\n return;\n }\n if (this.#links.has(id)) {\n ws.close(\n RemoteClose.DuplicateHost,\n \"A host with this ID is connected.\",\n );\n return;\n }\n clearTimeout(ws.data.helloTimer);\n this.#uncount(ws);\n ws.data.id = id;\n const link: Link = {\n id,\n ws,\n lastSeen: Date.now(),\n children: new Map(),\n };\n this.#links.set(id, link);\n this.#droppedAt.delete(id);\n const lease = this.#options.lease;\n this.#post(link, {\n t: \"welcome\",\n leaseMs: lease,\n pingMs: Math.max(10, Math.floor(lease / 3)),\n });\n this.#hooks.join(id);\n for (const waiter of [...this.#waiters]) waiter.check();\n }\n\n #uncount(ws: ServerWebSocket<SocketData>): void {\n if (!ws.data.counted) return;\n ws.data.counted = false;\n this.#pending.delete(ws);\n const left = (this.#pendingByAddress.get(ws.data.address) ?? 1) - 1;\n if (left > 0) this.#pendingByAddress.set(ws.data.address, left);\n else this.#pendingByAddress.delete(ws.data.address);\n }\n\n #closed(ws: ServerWebSocket<SocketData>, reason: string): void {\n clearTimeout(ws.data.helloTimer);\n this.#uncount(ws);\n const link = ws.data.id ? this.#links.get(ws.data.id) : undefined;\n if (link && link.ws === ws)\n this.#drop(link, reason || \"The connection closed.\");\n }\n\n /** Forgets a host and ends every worker it ran. */\n #drop(link: Link, reason: string, code?: number): void {\n if (this.#links.get(link.id) !== link) return;\n this.#links.delete(link.id);\n this.#droppedAt.set(link.id, Date.now());\n // Bounded: forget the oldest losses (long fenced) past 1,000 hosts.\n if (this.#droppedAt.size > 1000)\n this.#droppedAt.delete(this.#droppedAt.keys().next().value!);\n if (code !== undefined)\n try {\n link.ws.close(code, reason.slice(0, 100));\n } catch {\n // Already closed.\n }\n for (const child of [...link.children.values()])\n child.finish(null, \"SIGHUP\");\n link.children.clear();\n this.#hooks.leave(link.id, reason);\n }\n\n #tick(): void {\n const now = Date.now();\n for (const link of [...this.#links.values()]) {\n // The host fences itself after one lease of silence; the\n // coordinator waits for a second before it lets anything restart.\n if (now - link.lastSeen > this.#options.lease * 2) {\n this.#drop(\n link,\n \"The host stopped answering.\",\n RemoteClose.HostLost,\n );\n continue;\n }\n this.#post(link, { t: \"ping\", n: ++this.#ping });\n }\n }\n}\n",
|
|
16
|
+
"import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n/** Version of the coordinator ↔ host wire protocol. Both sides must agree. */\nexport const REMOTE_PROTOCOL_VERSION = 1;\n/** Largest text frame either side accepts, in UTF-8 bytes. */\nexport const MAX_REMOTE_FRAME_BYTES = 8 * 1024 * 1024;\n\n/** WebSocket close codes used between a coordinator and a host. */\nexport const RemoteClose = {\n BadRequest: 4400,\n AuthFailed: 4401,\n HelloTimeout: 4408,\n DuplicateHost: 4409,\n TooManyConnections: 4429,\n UnknownHost: 4410,\n VersionMismatch: 4426,\n HostLost: 4504,\n Shutdown: 4500,\n} as const;\n\n/**\n * The only environment variables a coordinator may set in a remote worker.\n * Everything else comes from the host's own environment, so a coordinator\n * cannot inject `NODE_OPTIONS`, `LD_PRELOAD` or a token.\n */\nexport const SPAWN_ENV_KEYS: ReadonlySet<string> = new Set([\n \"SHARD_LIST\",\n \"SHARD_COUNT\",\n \"CLUSTER_ID\",\n \"CLUSTER_COUNT\",\n \"LUNIBEE_SHARD_BUS\",\n \"LUNIBEE_CLUSTER_WORKER\",\n \"LUNIBEE_IDENTIFY\",\n \"LUNIBEE_STARTUP\",\n \"LUNIBEE_READY_TIMEOUT\",\n]);\n\n/** Signals a coordinator may send to a remote worker. */\nexport const KILL_SIGNALS: ReadonlySet<string> = new Set([\n \"SIGTERM\",\n \"SIGKILL\",\n \"SIGINT\",\n]);\n\n/** Whether a host name is this machine, where an unencrypted link is acceptable. */\nexport function isLoopbackHost(hostname: string): boolean {\n return /^(?:localhost|127(?:\\.\\d{1,3}){3}|\\[::1\\]|::1)$/i.test(hostname);\n}\n\n/** A host ID: letters, digits, `.`, `_` and `-`, up to 64 characters. */\nexport const HOST_ID = /^[A-Za-z0-9._-]{1,64}$/;\n\n/** Frames the coordinator sends to a host. */\nexport type ToHost =\n | { t: \"challenge\"; nonce: string; version: number }\n | { t: \"welcome\"; leaseMs: number; pingMs: number }\n | { t: \"ping\"; n: number }\n | {\n t: \"spawn\";\n cluster: number;\n epoch: number;\n env: Record<string, string>;\n }\n | { t: \"frame\"; cluster: number; epoch: number; frame: unknown }\n | { t: \"kill\"; cluster: number; epoch: number; signal: string }\n | { t: \"bye\"; reason: string };\n\n/** Frames a host sends to the coordinator. */\nexport type ToCoordinator =\n | { t: \"hello\"; id: string; mac: string; version: number }\n | { t: \"pong\"; n: number }\n | { t: \"spawned\"; cluster: number; epoch: number; pid?: number }\n | { t: \"frame\"; cluster: number; epoch: number; frame: unknown }\n | {\n t: \"exit\";\n cluster: number;\n epoch: number;\n code: number | null;\n signal: string | null;\n }\n | { t: \"error\"; cluster: number; epoch: number; message: string };\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\nconst isInt = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n\n/** The size of a string on the wire, in UTF-8 bytes. */\nexport function utf8Bytes(text: string): number {\n return Buffer.byteLength(text, \"utf8\");\n}\n\nfunction parseJson(\n text: unknown,\n maxBytes: number,\n): Record<string, unknown> | null {\n if (typeof text !== \"string\") return null;\n // A UTF-16 code unit takes at least one byte and at most three, so the\n // length decides most frames without counting bytes.\n if (text.length > maxBytes) return null;\n if (text.length * 3 > maxBytes && utf8Bytes(text) > maxBytes) return null;\n try {\n const value: unknown = JSON.parse(text);\n return isRecord(value) ? value : null;\n } catch {\n return null;\n }\n}\n\n/** The proof a host sends: an HMAC of the coordinator's nonce and the host ID, keyed by the shared secret. The secret itself never travels. */\nexport function signChallenge(\n secret: string,\n nonce: string,\n hostId: string,\n): string {\n return createHmac(\"sha256\", secret)\n .update(`${nonce}:${hostId}`)\n .digest(\"hex\");\n}\n\n/** Checks a host's proof in constant time. */\nexport function verifyChallenge(\n secret: string,\n nonce: string,\n hostId: string,\n mac: unknown,\n): boolean {\n if (typeof mac !== \"string\") return false;\n const expected = Buffer.from(signChallenge(secret, nonce, hostId), \"hex\");\n const given = Buffer.from(mac, \"hex\");\n return given.length === expected.length && timingSafeEqual(given, expected);\n}\n\n/** Validates a text frame from the coordinator; `null` when malformed. */\nexport function parseToHost(\n text: unknown,\n maxBytes = MAX_REMOTE_FRAME_BYTES,\n): ToHost | null {\n const v = parseJson(text, maxBytes);\n if (!v) return null;\n switch (v.t) {\n case \"challenge\":\n return typeof v.nonce === \"string\" && isInt(v.version)\n ? { t: \"challenge\", nonce: v.nonce, version: v.version }\n : null;\n case \"welcome\":\n return isInt(v.leaseMs) && isInt(v.pingMs)\n ? { t: \"welcome\", leaseMs: v.leaseMs, pingMs: v.pingMs }\n : null;\n case \"ping\":\n return isInt(v.n) ? { t: \"ping\", n: v.n } : null;\n case \"spawn\": {\n if (!isInt(v.cluster) || !isInt(v.epoch) || !isRecord(v.env))\n return null;\n const env: Record<string, string> = {};\n for (const [key, value] of Object.entries(v.env)) {\n if (!SPAWN_ENV_KEYS.has(key) || typeof value !== \"string\")\n return null;\n env[key] = value;\n }\n return { t: \"spawn\", cluster: v.cluster, epoch: v.epoch, env };\n }\n case \"frame\":\n return isInt(v.cluster) && isInt(v.epoch)\n ? {\n t: \"frame\",\n cluster: v.cluster,\n epoch: v.epoch,\n frame: v.frame,\n }\n : null;\n case \"kill\":\n return isInt(v.cluster) &&\n isInt(v.epoch) &&\n typeof v.signal === \"string\" &&\n KILL_SIGNALS.has(v.signal)\n ? {\n t: \"kill\",\n cluster: v.cluster,\n epoch: v.epoch,\n signal: v.signal,\n }\n : null;\n case \"bye\":\n return typeof v.reason === \"string\"\n ? { t: \"bye\", reason: v.reason }\n : null;\n default:\n return null;\n }\n}\n\n/** Validates a text frame from a host; `null` when malformed. */\nexport function parseToCoordinator(\n text: unknown,\n maxBytes = MAX_REMOTE_FRAME_BYTES,\n): ToCoordinator | null {\n const v = parseJson(text, maxBytes);\n if (!v) return null;\n switch (v.t) {\n case \"hello\":\n return typeof v.id === \"string\" &&\n typeof v.mac === \"string\" &&\n isInt(v.version)\n ? { t: \"hello\", id: v.id, mac: v.mac, version: v.version }\n : null;\n case \"pong\":\n return isInt(v.n) ? { t: \"pong\", n: v.n } : null;\n case \"spawned\":\n return isInt(v.cluster) &&\n isInt(v.epoch) &&\n (v.pid === undefined || isInt(v.pid))\n ? {\n t: \"spawned\",\n cluster: v.cluster,\n epoch: v.epoch,\n ...(v.pid !== undefined ? { pid: v.pid as number } : {}),\n }\n : null;\n case \"frame\":\n return isInt(v.cluster) && isInt(v.epoch)\n ? {\n t: \"frame\",\n cluster: v.cluster,\n epoch: v.epoch,\n frame: v.frame,\n }\n : null;\n case \"exit\":\n return isInt(v.cluster) &&\n isInt(v.epoch) &&\n (v.code === null || typeof v.code === \"number\") &&\n (v.signal === null || typeof v.signal === \"string\")\n ? {\n t: \"exit\",\n cluster: v.cluster,\n epoch: v.epoch,\n code: v.code,\n signal: v.signal,\n }\n : null;\n case \"error\":\n return isInt(v.cluster) &&\n isInt(v.epoch) &&\n typeof v.message === \"string\"\n ? {\n t: \"error\",\n cluster: v.cluster,\n epoch: v.epoch,\n message: v.message,\n }\n : null;\n default:\n return null;\n }\n}\n",
|
|
17
|
+
"/** One answer collected for a request. */\nexport interface Settled<R = unknown> {\n key: string;\n result?: R;\n error?: string;\n}\n\n/** What a finished request collected. */\nexport interface Collected<R = unknown> {\n /** Answers by key, in arrival order. */\n replies: Settled<R>[];\n /** Keys that never answered (timeout, abort or shutdown). */\n missing: string[];\n}\n\ninterface Entry {\n keys: Map<string, number>;\n replies: Settled[];\n /** Keys that already have an answer, for constant-time checks on large broadcasts. */\n answered: Set<string>;\n finish: () => void;\n timer: ReturnType<typeof setTimeout>;\n cleanup?: () => void;\n}\n\n/**\n * Pending requests of the parent process. Each request waits for answers from\n * a set of keys (a shard, a cluster, or every shard of the live clusters) and\n * settles when all arrived, the timeout passed, or the caller aborted.\n *\n * Answers are accepted only from the owner of the key, once per key; late,\n * duplicate and unknown answers are ignored. The number of waiting requests is\n * bounded, and a timer per request guarantees the entry is removed.\n */\nexport class RequestTracker {\n readonly #pending = new Map<string, Entry>();\n readonly #maxPending: number;\n readonly #prefix: string;\n #counter = 0;\n\n public constructor(prefix = \"parent\", maxPending = 10_000) {\n if (!Number.isInteger(maxPending) || maxPending < 1)\n throw new RangeError(\"maxPending must be a positive integer.\");\n this.#prefix = prefix;\n this.#maxPending = maxPending;\n }\n\n /** Requests waiting for answers. */\n public get size(): number {\n return this.#pending.size;\n }\n\n /**\n * Starts waiting for answers.\n * @param keys Each key that should answer, with the cluster that owns it.\n * @throws {Error} If `maxPending` requests are already waiting.\n */\n public begin<R>(options: {\n keys: ReadonlyMap<string, number>;\n timeoutMs: number;\n signal?: AbortSignal;\n }): { id: string; done: Promise<Collected<R>> } {\n if (this.#pending.size >= this.#maxPending)\n throw new Error(\n `Too many requests waiting for a reply (${this.#maxPending}).`,\n );\n const id = `${this.#prefix}:${++this.#counter}`;\n const done = new Promise<Collected<R>>((resolve) => {\n const finish = (): void => {\n const entry = this.#pending.get(id);\n if (!entry) return;\n this.#pending.delete(id);\n clearTimeout(entry.timer);\n entry.cleanup?.();\n resolve({\n replies: entry.replies as Settled<R>[],\n missing: [...entry.keys.keys()].filter(\n (key) => !entry.answered.has(key),\n ),\n });\n };\n const entry: Entry = {\n keys: new Map(options.keys),\n replies: [],\n answered: new Set(),\n finish,\n timer: setTimeout(finish, options.timeoutMs),\n };\n if (options.signal) {\n const signal = options.signal;\n if (signal.aborted) queueMicrotask(finish);\n else {\n signal.addEventListener(\"abort\", finish, { once: true });\n entry.cleanup = () =>\n signal.removeEventListener(\"abort\", finish);\n }\n }\n this.#pending.set(id, entry);\n if (entry.keys.size === 0) queueMicrotask(finish);\n });\n return { id, done };\n }\n\n /**\n * Records one answer.\n * @param from The cluster the answer came from; it must own `key`.\n * @returns Whether the answer was accepted.\n */\n public answer(\n id: string,\n key: string,\n from: number,\n value: { result?: unknown; error?: string },\n ): boolean {\n const entry = this.#pending.get(id);\n if (!entry || entry.keys.get(key) !== from) return false;\n if (entry.answered.has(key)) return false;\n entry.answered.add(key);\n entry.replies.push({ key, ...value });\n if (entry.replies.length >= entry.keys.size) entry.finish();\n return true;\n }\n\n /** A cluster went away: every key it owns that has not answered settles with `error`. */\n public failOwner(owner: number, error: string): void {\n for (const entry of [...this.#pending.values()]) {\n for (const [key, keyOwner] of entry.keys)\n if (keyOwner === owner && !entry.answered.has(key)) {\n entry.answered.add(key);\n entry.replies.push({ key, error });\n }\n if (entry.replies.length >= entry.keys.size) entry.finish();\n }\n }\n\n /** Settles every waiting request with what it has (shutdown). */\n public close(): void {\n for (const entry of [...this.#pending.values()]) entry.finish();\n }\n}\n",
|
|
18
|
+
"import type { ChildProcess } from \"node:child_process\";\nimport { fork } from \"node:child_process\";\nimport {\n KILL_SIGNALS,\n REMOTE_PROTOCOL_VERSION,\n RemoteClose,\n HOST_ID,\n isLoopbackHost,\n parseToHost,\n signChallenge,\n type ToCoordinator,\n} from \"./remote-protocol.js\";\n\n/** Options for a {@link ClusterHost}. */\nexport interface ClusterHostOptions {\n /**\n * The coordinator's address: `wss://host:port`. `ws://` is accepted for\n * this machine, or anywhere with `allowInsecure`. A list names every\n * coordinator of a deployment with a `leader` lock: the host tries them in\n * turn, and moves to the next whenever one cannot be reached or drops it.\n */\n url: string | string[];\n /** Connect with `ws://` to a coordinator on another machine, over a network you fully trust. Default false. */\n allowInsecure?: boolean;\n /** The coordinator's shared secret. */\n secret: string;\n /** This host's ID: letters, digits, `.`, `_`, `-`. Must be unique in the deployment. */\n id: string;\n /**\n * The worker script this host runs for every cluster. A coordinator cannot\n * choose it: remote workers run only what the host owner configured.\n */\n script: string;\n /** Starts a worker process. Defaults to `child_process.fork()`. */\n fork?: (\n script: string,\n env: Record<string, string | undefined>,\n ) => ChildProcess;\n /** First delay before reconnecting, in ms. Default 1000. */\n reconnectDelay?: number;\n /** Longest delay between reconnect attempts, in ms. Default 30,000. */\n maxReconnectDelay?: number;\n /**\n * Attempts `connect()` makes while the coordinator is busy (it answers\n * \"too many connections\" or the hello timed out) before it gives up.\n * Default 10. A refusal (bad secret, unknown or duplicate id) is final.\n */\n connectAttempts?: number;\n}\n\n/** What a {@link ClusterHost} emits. */\nexport interface ClusterHostEvents {\n /** The coordinator accepted this host. */\n connected: [];\n /** The connection ended. Every worker was killed first. */\n disconnected: [info: { code: number; reason: string }];\n /** A worker started for a cluster. */\n clusterSpawn: [cluster: number];\n /** A worker exited. */\n clusterExit: [cluster: number, code: number | null, signal: string | null];\n /** A refused frame, a failed start or a rejected connection. */\n error: [error: Error];\n}\n\ninterface Running {\n epoch: number;\n child: ChildProcess;\n}\n\n/**\n * The agent that makes a machine part of a deployment. It connects to the\n * coordinator, starts the worker script for the clusters the coordinator\n * places here, and relays their IPC frames both ways. The coordinator's\n * `ClusterManager` supervises those workers exactly like local ones.\n *\n * Fencing: when the connection closes, or the coordinator stays silent for a\n * whole lease, the host kills every worker at once. The coordinator waits\n * longer than that before it restarts the clusters elsewhere, so a shard is\n * never owned in two places.\n */\nexport class ClusterHost {\n readonly #options: ClusterHostOptions;\n readonly #running = new Map<number, Running>();\n readonly #listeners = new Map<\n keyof ClusterHostEvents,\n Set<(...args: never[]) => void>\n >();\n #socket?: WebSocket;\n readonly #urls: string[];\n /** The coordinator tried next. */\n #urlIndex = 0;\n /** Coordinators that could not be reached during the first connect. */\n #unreachable = 0;\n /** Whether the coordinator accepted the current socket. */\n #accepted = false;\n #lease = 15_000;\n #lastRx = 0;\n #watchdog?: ReturnType<typeof setInterval>;\n #retry?: ReturnType<typeof setTimeout>;\n #delay: number;\n /** Busy answers to the first connect so far. */\n #busyAttempts = 0;\n #closed = true;\n #connected = false;\n #firstConnect?: { resolve: () => void; reject: (error: Error) => void };\n\n public constructor(options: ClusterHostOptions) {\n if (!HOST_ID.test(options.id))\n throw new RangeError(\n \"id must be 1-64 letters, digits, '.', '_' or '-'.\",\n );\n if (!options.secret || options.secret.length < 16)\n throw new RangeError(\"secret must be at least 16 characters.\");\n if (!options.script?.trim())\n throw new TypeError(\"A worker script path is required.\");\n const urls = Array.isArray(options.url)\n ? [...options.url]\n : [options.url];\n if (urls.length === 0 || urls.length > 16)\n throw new TypeError(\"url must list 1 to 16 coordinators.\");\n for (const raw of urls) {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new TypeError(\n \"url must be an absolute ws:// or wss:// URL.\",\n );\n }\n if (url.protocol !== \"ws:\" && url.protocol !== \"wss:\")\n throw new TypeError(\"url must start with ws:// or wss://.\");\n if (url.username || url.password)\n throw new TypeError(\"url must not contain credentials.\");\n if (\n url.protocol === \"ws:\" &&\n !options.allowInsecure &&\n !isLoopbackHost(url.hostname)\n )\n throw new TypeError(\n \"url must use wss:// to reach another machine (allowInsecure: true overrides this for a network you fully trust).\",\n );\n }\n this.#urls = urls;\n this.#options = options;\n this.#delay = options.reconnectDelay ?? 1000;\n }\n\n /** Whether the coordinator has accepted this host and the link is open. */\n public get connected(): boolean {\n return this.#connected;\n }\n\n /** Cluster IDs running on this host. */\n public clusters(): number[] {\n return [...this.#running.keys()].sort((a, b) => a - b);\n }\n\n /** Listens for a host event. @returns This host. */\n public on<E extends keyof ClusterHostEvents>(\n event: E,\n listener: (...args: ClusterHostEvents[E]) => void,\n ): this {\n let set = this.#listeners.get(event);\n if (!set) this.#listeners.set(event, (set = new Set()));\n set.add(listener as never);\n return this;\n }\n\n /** Stops listening for a host event. @returns This host. */\n public off<E extends keyof ClusterHostEvents>(\n event: E,\n listener: (...args: ClusterHostEvents[E]) => void,\n ): this {\n this.#listeners.get(event)?.delete(listener as never);\n return this;\n }\n\n #emit<E extends keyof ClusterHostEvents>(\n event: E,\n ...args: ClusterHostEvents[E]\n ): void {\n for (const listener of this.#listeners.get(event) ?? [])\n try {\n (listener as (...a: ClusterHostEvents[E]) => void)(...args);\n } catch {\n // A faulty listener must not disturb the host.\n }\n }\n\n /**\n * Connects and resolves once the coordinator accepts this host. After\n * that, a lost connection is retried with backoff until {@link close}.\n * @throws {Error} If the first attempt is refused (bad secret, unknown or duplicate ID).\n */\n public connect(): Promise<void> {\n if (!this.#closed) return Promise.resolve();\n this.#closed = false;\n this.#busyAttempts = 0;\n this.#unreachable = 0;\n return new Promise<void>((resolve, reject) => {\n this.#firstConnect = { resolve, reject };\n this.#open();\n });\n }\n\n /** Kills every worker, closes the connection and stops reconnecting. */\n public close(): Promise<void> {\n this.#closed = true;\n if (this.#retry) clearTimeout(this.#retry);\n this.#retry = undefined;\n this.#fence();\n if (this.#watchdog) clearInterval(this.#watchdog);\n this.#watchdog = undefined;\n this.#connected = false;\n const socket = this.#socket;\n this.#socket = undefined;\n this.#firstConnect?.reject(new Error(\"The host was closed.\"));\n this.#firstConnect = undefined;\n if (!socket || socket.readyState === WebSocket.CLOSED)\n return Promise.resolve();\n return new Promise<void>((resolve) => {\n socket.addEventListener(\"close\", () => resolve(), { once: true });\n try {\n socket.close(1000, \"Host closed.\");\n } catch {\n resolve();\n }\n });\n }\n\n #open(): void {\n this.#accepted = false;\n const socket = new WebSocket(this.#urls[this.#urlIndex]!);\n this.#socket = socket;\n socket.addEventListener(\"message\", (event) =>\n this.#message(socket, event.data),\n );\n socket.addEventListener(\"error\", () => {\n // The close event that follows carries the outcome.\n });\n socket.addEventListener(\"close\", (event) => {\n if (this.#socket === socket) this.#lost(event.code, event.reason);\n });\n }\n\n #send(socket: WebSocket, message: ToCoordinator): boolean {\n if (socket.readyState !== WebSocket.OPEN) return false;\n try {\n socket.send(JSON.stringify(message));\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * A spawn this host will not run (an environment variable outside the\n * allowlist, say) is answered with an error, so the coordinator fails the\n * cluster at once instead of waiting for it to start.\n */\n #refuseSpawn(socket: WebSocket, data: unknown): void {\n if (typeof data !== \"string\" || data.length > 65_536) return;\n let frame: { t?: unknown; cluster?: unknown; epoch?: unknown };\n try {\n frame = JSON.parse(data) as typeof frame;\n } catch {\n return;\n }\n if (\n frame?.t !== \"spawn\" ||\n !Number.isInteger(frame.cluster) ||\n !Number.isInteger(frame.epoch)\n )\n return;\n this.#send(socket, {\n t: \"error\",\n cluster: frame.cluster as number,\n epoch: frame.epoch as number,\n message:\n \"This host refused the spawn request (malformed or not allowed).\",\n });\n }\n\n #message(socket: WebSocket, data: unknown): void {\n if (this.#socket !== socket) return;\n const message = parseToHost(data);\n if (!message) {\n this.#emit(\n \"error\",\n new Error(\"The coordinator sent a malformed frame.\"),\n );\n this.#refuseSpawn(socket, data);\n return;\n }\n this.#lastRx = Date.now();\n switch (message.t) {\n case \"challenge\":\n if (message.version !== REMOTE_PROTOCOL_VERSION) {\n socket.close(\n RemoteClose.VersionMismatch,\n \"Protocol version mismatch.\",\n );\n return;\n }\n this.#send(socket, {\n t: \"hello\",\n id: this.#options.id,\n mac: signChallenge(\n this.#options.secret,\n message.nonce,\n this.#options.id,\n ),\n version: REMOTE_PROTOCOL_VERSION,\n });\n return;\n case \"welcome\":\n this.#lease = message.leaseMs;\n this.#accepted = true;\n this.#connected = true;\n this.#delay = this.#options.reconnectDelay ?? 1000;\n this.#startWatchdog();\n this.#firstConnect?.resolve();\n this.#firstConnect = undefined;\n this.#emit(\"connected\");\n return;\n case \"ping\":\n this.#send(socket, { t: \"pong\", n: message.n });\n return;\n case \"bye\":\n this.#fence();\n return;\n case \"spawn\":\n this.#spawn(\n socket,\n message.cluster,\n message.epoch,\n message.env,\n );\n return;\n case \"frame\": {\n const run = this.#running.get(message.cluster);\n if (run?.epoch === message.epoch && run.child.connected)\n try {\n run.child.send(message.frame as never);\n } catch {\n // The worker is exiting; its exit event reports it.\n }\n return;\n }\n case \"kill\": {\n const run = this.#running.get(message.cluster);\n if (\n run?.epoch === message.epoch &&\n KILL_SIGNALS.has(message.signal)\n )\n try {\n run.child.kill(message.signal as NodeJS.Signals);\n } catch {\n // Already gone.\n }\n return;\n }\n }\n }\n\n #spawn(\n socket: WebSocket,\n cluster: number,\n epoch: number,\n env: Record<string, string>,\n ): void {\n const reply = (message: ToCoordinator): void => {\n if (this.#socket === socket) this.#send(socket, message);\n };\n if (this.#running.has(cluster)) {\n reply({\n t: \"error\",\n cluster,\n epoch,\n message: `Cluster ${cluster} is already running here.`,\n });\n return;\n }\n let child: ChildProcess;\n try {\n const forkWorker = this.#options.fork ?? defaultFork;\n child = forkWorker(this.#options.script, {\n ...process.env,\n ...env,\n });\n } catch (error) {\n reply({\n t: \"error\",\n cluster,\n epoch,\n message: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n this.#running.set(cluster, { epoch, child });\n child.on(\"message\", (frame) =>\n reply({ t: \"frame\", cluster, epoch, frame }),\n );\n child.once(\"exit\", (code, signal) => {\n if (this.#running.get(cluster)?.child === child)\n this.#running.delete(cluster);\n this.#emit(\"clusterExit\", cluster, code, signal);\n reply({ t: \"exit\", cluster, epoch, code, signal });\n });\n child.on(\"error\", (error) =>\n this.#emit(\n \"error\",\n error instanceof Error ? error : new Error(String(error)),\n ),\n );\n reply({\n t: \"spawned\",\n cluster,\n epoch,\n ...(child.pid ? { pid: child.pid } : {}),\n });\n this.#emit(\"clusterSpawn\", cluster);\n }\n\n /** Kills every worker now. Used whenever the coordinator might consider the host gone. */\n #fence(): void {\n for (const { child } of [...this.#running.values()])\n try {\n child.kill(\"SIGKILL\");\n } catch {\n // Already gone.\n }\n }\n\n #startWatchdog(): void {\n if (this.#watchdog) clearInterval(this.#watchdog);\n this.#lastRx = Date.now();\n this.#watchdog = setInterval(\n () => {\n if (Date.now() - this.#lastRx > this.#lease) {\n this.#fence();\n // Closing triggers the reconnect path.\n this.#socket?.close(\n RemoteClose.HostLost,\n \"Coordinator silent.\",\n );\n }\n },\n Math.max(10, Math.floor(this.#lease / 3)),\n );\n (this.#watchdog as { unref?: () => void }).unref?.();\n }\n\n #lost(code: number, reason: string): void {\n if (this.#watchdog) clearInterval(this.#watchdog);\n this.#watchdog = undefined;\n this.#socket = undefined;\n const wasConnected = this.#connected;\n this.#connected = false;\n this.#fence();\n if (wasConnected) this.#emit(\"disconnected\", { code, reason });\n if (this.#closed) return;\n const refused =\n code === RemoteClose.AuthFailed ||\n code === RemoteClose.UnknownHost ||\n code === RemoteClose.DuplicateHost ||\n code === RemoteClose.VersionMismatch;\n const first = this.#firstConnect;\n // A busy coordinator is not a refusal: try again, with backoff.\n const busy =\n code === RemoteClose.TooManyConnections ||\n code === RemoteClose.HelloTimeout;\n // With several coordinators, the next one may be the leader now.\n const accepted = this.#accepted;\n if (this.#urls.length > 1)\n this.#urlIndex = (this.#urlIndex + 1) % this.#urls.length;\n if (\n first &&\n !refused &&\n !busy &&\n !accepted &&\n ++this.#unreachable < this.#urls.length\n ) {\n this.#open();\n return;\n }\n if (\n first &&\n busy &&\n ++this.#busyAttempts < (this.#options.connectAttempts ?? 10)\n ) {\n this.#retryLater();\n return;\n }\n if (first) {\n this.#firstConnect = undefined;\n this.#closed = true;\n first.reject(\n new Error(\n `The coordinator refused this host (${code}${reason ? `: ${reason}` : \"\"}).`,\n ),\n );\n return;\n }\n if (refused)\n this.#emit(\n \"error\",\n new Error(\n `The coordinator refused this host (${code}: ${reason}).`,\n ),\n );\n this.#retryLater();\n }\n\n #retryLater(): void {\n const wait = Math.round(this.#delay * (1 + Math.random() * 0.2));\n this.#delay = Math.min(\n this.#options.maxReconnectDelay ?? 30_000,\n this.#delay * 2,\n );\n this.#retry = setTimeout(() => {\n this.#retry = undefined;\n if (!this.#closed) this.#open();\n }, wait);\n }\n}\n\nfunction defaultFork(\n script: string,\n env: Record<string, string | undefined>,\n): ChildProcess {\n return fork(script, [], { env: env as NodeJS.ProcessEnv });\n}\n",
|
|
19
|
+
"/**\n * Leadership between several coordinators, so a standby `ClusterManager` can\n * take over when the active one dies. Only the holder of the lock runs\n * clusters; see `ClusterManagerOptions.leader`.\n */\nexport interface LeaderLock {\n /**\n * Takes the lock for `ttlMs`, or extends it when this process already\n * holds it. Resolves `true` while this process is the leader, `false` when\n * another one holds it. A rejection counts as \"unknown\".\n */\n acquire(ttlMs: number): Promise<boolean>;\n /** Gives the lock up if this process holds it. */\n release(): Promise<void>;\n}\n\n/** The part of a Redis client the lock uses. `Bun.RedisClient` works as it is; for ioredis pass `{ send: (c, a) => redis.call(c, ...a) }`. */\nexport interface LeaderRedisClient {\n send(command: string, args: string[]): Promise<unknown>;\n}\n\n/** Options for {@link RedisLeaderLock}. */\nexport interface RedisLeaderLockOptions {\n client: LeaderRedisClient;\n /** The key every coordinator of one deployment shares. Default `lunibee:cluster:leader`. */\n key?: string;\n /** This coordinator's identity in the key. Default: a random UUID. Must differ between coordinators. */\n token?: string;\n}\n\n// Take the key if it is free, extend it if it is ours: one atomic step, so\n// two coordinators can never both see themselves as the holder.\nconst ACQUIRE = `local v = redis.call('GET', KEYS[1])\nif v == ARGV[1] then redis.call('PEXPIRE', KEYS[1], ARGV[2]) return 1 end\nif not v then redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2]) return 1 end\nreturn 0`;\nconst RELEASE = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end\nreturn 0`;\n\n/** A {@link LeaderLock} on one Redis key, with an expiry so a dead leader's lock lapses on its own. */\nexport class RedisLeaderLock implements LeaderLock {\n readonly #client: LeaderRedisClient;\n readonly #key: string;\n readonly #token: string;\n\n public constructor(options: RedisLeaderLockOptions) {\n if (typeof options?.client?.send !== \"function\")\n throw new TypeError(\"RedisLeaderLock needs a client with send().\");\n this.#client = options.client;\n this.#key = options.key ?? \"lunibee:cluster:leader\";\n this.#token = options.token ?? crypto.randomUUID();\n if (!this.#key || !this.#token)\n throw new TypeError(\"key and token must not be empty.\");\n }\n\n public async acquire(ttlMs: number): Promise<boolean> {\n if (!Number.isInteger(ttlMs) || ttlMs < 1)\n throw new RangeError(\"ttlMs must be a positive integer.\");\n const reply = await this.#client.send(\"EVAL\", [\n ACQUIRE,\n \"1\",\n this.#key,\n this.#token,\n String(ttlMs),\n ]);\n return Number(reply) === 1;\n }\n\n public async release(): Promise<void> {\n await this.#client.send(\"EVAL\", [RELEASE, \"1\", this.#key, this.#token]);\n }\n}\n",
|
|
20
|
+
"import {\n assertGuildsPerShard,\n fetchGatewayBot,\n resolveShardCount,\n type GatewayBotInfo,\n} from \"./gateway-bot.js\";\nimport { Gateway, GatewayState } from \"@lunibee/ws\";\nimport {\n readClusterContext,\n validateShardIds,\n type ClusterContext,\n} from \"./context.js\";\nimport type {\n ShardHealth,\n ShardStatus,\n WorkerMetrics,\n WorkerReport,\n} from \"./health.js\";\nimport { REQUEST_ASSIGN, REQUEST_HEALTH } from \"./protocol.js\";\nimport { ClusterWorker, type IdentifyGate } from \"./worker.js\";\n\n/** Runtime-agnostic delay used between shard starts (works under Node and Bun). @param ms Milliseconds to wait. */\nconst sleep = (ms: number): Promise<void> =>\n new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nexport {\n MAX_GUILDS_PER_SHARD,\n fetchGatewayBot,\n fetchGuildCount,\n shardsForGuilds,\n type GatewayBotInfo,\n} from \"./gateway-bot.js\";\nexport { ShardBus } from \"./bus.js\";\nexport {\n BroadcastChannelTransport,\n IpcTransport,\n type IpcEndpoint,\n type ShardBusTransport,\n} from \"./transport.js\";\nexport { ShardSupervisor, type SupervisorOptions } from \"./supervisor.js\";\nexport type {\n ShardBusErrorHandler,\n ShardBusOptions,\n ShardMessage,\n ShardMessageHandler,\n ShardReply,\n} from \"./bus.js\";\n\nexport { ClusterManager } from \"./cluster.js\";\nexport type {\n ClusterManagerOptions,\n ClusterManagerEvents,\n ClusterInfo,\n BroadcastResult,\n RequestOptions,\n ClusterPlanInfo,\n} from \"./cluster.js\";\nexport {\n allocateShards,\n planClusters,\n type ClusterPlan,\n type ClusterPlanOptions,\n type ClusterCountSource,\n assertCompleteAllocation,\n isClusterWorker,\n readClusterContext,\n validateShardIds,\n type ClusterContext,\n} from \"./context.js\";\nexport { ClusterWorker, type IdentifyGate } from \"./worker.js\";\nexport {\n threadEndpoint,\n threadFork,\n type ThreadEndpoint,\n type ThreadForkOptions,\n} from \"./thread.js\";\nexport {\n ClusterHost,\n type ClusterHostEvents,\n type ClusterHostOptions,\n} from \"./host.js\";\nexport type { HostStatus, RemoteOptions } from \"./remote.js\";\nexport {\n RedisLeaderLock,\n type LeaderLock,\n type LeaderRedisClient,\n type RedisLeaderLockOptions,\n} from \"./leader.js\";\nexport { RemoteClose, REMOTE_PROTOCOL_VERSION } from \"./remote-protocol.js\";\nexport {\n IdentifyCoordinator,\n IDENTIFY_INTERVAL,\n type IdentifyBudget,\n type IdentifyCoordinatorOptions,\n type IdentifyEvent,\n} from \"./identify.js\";\nexport { RequestTracker } from \"./requests.js\";\nexport type {\n ClusterHealth,\n ClusterManagerHealth,\n ClusterState,\n ShardHealth,\n ShardStatus,\n WorkerMetrics,\n WorkerReport,\n} from \"./health.js\";\n\n/** Whether a shard has no live socket, so connecting it costs an IDENTIFY. */\nfunction needsConnection(gateway: Gateway): boolean {\n return (\n gateway.state === GatewayState.Connect ||\n gateway.state === GatewayState.Reconnect ||\n gateway.state === GatewayState.Closed\n );\n}\n\n/** States reached only after IDENTIFY or RESUME has been sent. */\nconst HANDSHAKE_SENT = new Set<string>([\n GatewayState.Identify,\n GatewayState.Resume,\n GatewayState.Ready,\n GatewayState.Dispatch,\n GatewayState.Heartbeat,\n GatewayState.Closed,\n]);\n\n/** Resolves once the shard has sent IDENTIFY/RESUME (or closed), or after `timeout` ms. */\nfunction handshakeSent(gateway: Gateway, timeout: number): Promise<void> {\n if (HANDSHAKE_SENT.has(gateway.state)) return Promise.resolve();\n return new Promise((resolve) => {\n const done = (): void => {\n clearTimeout(timer);\n gateway.off(\"stateChange\", onState);\n resolve();\n };\n const onState = (change: unknown): void => {\n if (HANDSHAKE_SENT.has((change as { next: string }).next)) done();\n };\n const timer = setTimeout(done, timeout);\n gateway.on(\"stateChange\", onState);\n });\n}\n\n/** Resolves once the shard is READY (or resumed), or after `timeout` ms. */\nfunction shardReady(gateway: Gateway, timeout: number): Promise<boolean> {\n if (gateway.state === GatewayState.Dispatch) return Promise.resolve(true);\n return new Promise((resolve) => {\n const done = (ready: boolean): void => {\n clearTimeout(timer);\n gateway.off(\"ready\", onReady);\n gateway.off(\"resumed\", onReady);\n resolve(ready);\n };\n const onReady = (): void => done(true);\n const timer = setTimeout(() => done(false), timeout);\n gateway.on(\"ready\", onReady);\n gateway.on(\"resumed\", onReady);\n });\n}\n\n/** How long sequential startup waits for a shard's READY before moving on, by default. */\nexport const SEQUENTIAL_READY_TIMEOUT = 60_000;\n\n/** An IDENTIFY slot source: a cluster worker's `identifyGate`. */\nexport interface IdentifySlotGate {\n acquire(shardId: number): Promise<void>;\n release(shardId: number): void;\n}\n\n/**\n * Connects one shard inside the IDENTIFY queue: takes a slot, connects, and\n * gives the slot back once the IDENTIFY is sent, or, with `waitForReady`\n * (sequential startup), once the shard is READY. The slot is always released,\n * also when connecting fails.\n * @returns Whether the shard became READY in time (`true` without `waitForReady`).\n */\nexport async function connectInQueue(\n gateway: Gateway,\n shardId: number,\n gate: IdentifySlotGate,\n options: {\n handshakeTimeout?: number;\n waitForReady?: boolean;\n readyTimeout?: number;\n /** Checked after the slot is granted: true skips connecting (a stale start). */\n cancelled?: () => boolean;\n } = {},\n): Promise<boolean> {\n await gate.acquire(shardId);\n try {\n if (options.cancelled?.()) return false;\n await gateway.connect();\n await handshakeSent(gateway, options.handshakeTimeout ?? 15_000);\n if (!options.waitForReady) return true;\n return await shardReady(\n gateway,\n options.readyTimeout ?? SEQUENTIAL_READY_TIMEOUT,\n );\n } finally {\n gate.release(shardId);\n }\n}\n\n/** Configuration for a sharded Gateway client. */\nexport interface ShardManagerOptions {\n /** Bot token. */\n token: string;\n /** Gateway intents as an array, e.g. `[GatewayIntentBits.Guilds]`. */\n intents: ConstructorParameters<typeof Gateway>[0][\"intents\"];\n /** Number of shards. Use `\"auto\"` to request Discord's recommended count. */\n shardCount?: number | \"auto\";\n /**\n * A sizing hint for `shardCount: \"auto\"`: run at least\n * `ceil(guildCount / guildsPerShard)` shards, and never fewer than Discord\n * recommends. 1 to 2500 (Discord's limit); Discord recommends about 1000.\n * It only picks the shard count: Discord puts each guild on shard\n * `(guild_id >> 22) % shardCount`, so shards get about this many guilds,\n * not exactly, and ownership never follows from it.\n */\n guildsPerShard?: number;\n /** The bot's guild count for `guildsPerShard`, instead of asking Discord for it. */\n guildCount?: number;\n /** Gateway reconnect behavior. */\n reconnect?: boolean;\n /**\n * Delay between shard starts in milliseconds. Defaults to 5000 to respect\n * Discord's IDENTIFY rate limit (one per 5s per rate-limit key); set 0 to\n * opt out when an external scheduler already paces the handshakes.\n */ spawnDelay?: number;\n /**\n * Shards that may IDENTIFY at once (Discord's `max_concurrency`). Defaults\n * to the value from `/gateway/bot` when it was fetched (auto shard count),\n * else 1.\n */ maxConcurrency?: number;\n /**\n * How long a startup round waits for its shards to send IDENTIFY/RESUME\n * before the next round's `spawnDelay` starts. Defaults to 15000 ms.\n */ handshakeTimeout?: number;\n /**\n * `\"safe\"` (default): start shards as fast as Discord's IDENTIFY limits\n * allow. `\"sequential\"`: one shard at a time, each waiting for the previous\n * one's READY (up to `readyTimeout`) and then `spawnDelay`. In a cluster\n * worker this follows the `ClusterManager`'s `startup` option.\n */\n startup?: \"safe\" | \"sequential\";\n /**\n * Sequential startup: how long a shard may take to become READY before the\n * next one starts (a `shardError` reports it), in ms. Default 60000; in a\n * cluster worker, the parent's `identify.holdTimeout`.\n */\n readyTimeout?: number;\n /** Interval in milliseconds to automatically check for recommended shard count and re-scale if needed. Must be an integer >= 1000. */\n autoScaleInterval?: number;\n /** Optional handler invoked when a background auto-scale check fails. Receives the thrown error. */\n onAutoScaleError?: (error: unknown) => void;\n /**\n * The shards this manager runs, out of `shardCount`. Defaults to all of\n * them, or to the cluster's own list inside a worker forked by\n * `ClusterManager`. Must be unique integers in `[0, shardCount)`.\n */\n shardIds?: number[];\n /**\n * The worker contract. Read from the environment inside a worker forked by\n * `ClusterManager`; pass `false` to ignore it, or a context to override it.\n */\n cluster?: ClusterContext | false;\n /**\n * Where to get IDENTIFY slots. Inside a coordinated cluster this is the\n * parent, so the limit holds across all clusters. Pass `false` to pace\n * locally with `spawnDelay` instead.\n */\n identify?: IdentifyGate | false;\n /** The worker to report to. Defaults to this process's `ClusterWorker` inside a cluster. */\n worker?: ClusterWorker | false;\n}\n/** What `ShardManager` emits about its shards. */\nexport interface ShardManagerEvents {\n /** A shard finished its handshake and is receiving events. */\n shardReady: [shardId: number];\n /** A shard's connection closed (it may reconnect by itself). */\n shardDisconnect: [shardId: number, close: { code: number; action: string }];\n /** A shard resumed its session after a reconnect. */\n shardResume: [shardId: number];\n /** A shard reported an error. */\n shardError: [shardId: number, error: Error];\n /** Every shard this manager owns is ready. Emitted again after a shard drops and recovers. */\n ready: [];\n /**\n * A shard's `Gateway` was created and has not connected yet: attach dispatch\n * listeners here so none of its events are missed. Fires again for the new\n * Gateways after `destroy()` + `connect()` or `reshard()`.\n */\n shardCreate: [shardId: number, gateway: Gateway];\n /** A shard reconnected too often too fast and is backing off (see `Gateway` `unstable`). */\n shardUnstable: [\n shardId: number,\n info: { reconnects: number; delay: number },\n ];\n}\n\n/** Runtime state for a managed shard. */\nexport interface ShardInfo {\n /** Shard identifier. */\n id: number;\n /** Gateway instance. */\n gateway: Gateway;\n}\n\n/** Manages independent Discord Gateway shards with explicit destruction and reinitialization semantics. */\nexport class ShardManager {\n /** Discord's minimum interval between IDENTIFY payloads, in milliseconds. */\n public static readonly IDENTIFY_INTERVAL = 5000;\n /** Active Gateway shards indexed by shard identifier. */\n public readonly shards = new Map<number, Gateway>();\n /** Total shards in the deployment once initialized, 0 before and after `destroy()`. */\n public get shardCount(): number {\n return this.#resolved ? this.#total : 0;\n }\n /** IDs of the shards this manager owns. */\n public get shardIds(): number[] {\n return [...this.shards.keys()].sort((a, b) => a - b);\n }\n readonly #options: ShardManagerOptions;\n /** Shards this manager was asked to own; every shard when `undefined`. */\n #ids: number[] | undefined;\n readonly #gate: IdentifyGate | undefined;\n readonly #worker: ClusterWorker | undefined;\n readonly #lifecycle = new Map<\n number,\n { status: ShardStatus; readyAt?: number; disconnectedAt?: number }\n >();\n /** Whether `ready` was announced since the last shard dropped. */\n #announced = false;\n /** Whether the shard set has been initialized. */\n #resolved = false;\n /** Whether this manager is currently destroyed. */\n #destroyed = false;\n /** Bumped by `destroy()`, so a start that is still running notices it is stale. */\n #generation = 0;\n /** Whether the manager was created in auto shard-count mode. Preserved across reshards so auto-scaling keeps running. */\n readonly #auto: boolean;\n /** The shard count when it is not \"auto\" (from the options or the cluster context). */\n readonly #fixedCount: number;\n readonly #startup: \"safe\" | \"sequential\";\n readonly #readyTimeout: number;\n #autoScaleTimer?: ReturnType<typeof setInterval>;\n /** The shard count of the current shard set; kept while a reshard has no shards so `getShardIdForGuild` stays right. */\n #total: number;\n readonly #listeners = new Map<\n keyof ShardManagerEvents,\n Set<(...args: unknown[]) => void>\n >();\n /** Last `/gateway/bot` answer, used for concurrency and the start limit. */\n #gatewayInfo?: GatewayBotInfo;\n /** Creates a shard manager. @param options Sharding configuration. @throws {TypeError} If token or intents are invalid. @throws {RangeError} If shard count is invalid. */\n public constructor(options: ShardManagerOptions) {\n if (!options.token?.trim())\n throw new TypeError(\"A Discord bot token is required.\");\n if (!Array.isArray(options.intents))\n throw new TypeError(\n \"Gateway intents must be an array, e.g. [GatewayIntentBits.Guilds].\",\n );\n const context =\n options.cluster === false\n ? null\n : (options.cluster ?? readClusterContext());\n if (context) {\n if (options.shardCount === \"auto\")\n throw new RangeError(\n 'shardCount \"auto\" is the ClusterManager\\'s decision inside a cluster worker.',\n );\n if (\n options.shardCount !== undefined &&\n options.shardCount !== context.shardCount\n )\n throw new RangeError(\n `shardCount ${options.shardCount} differs from the cluster's ${context.shardCount}.`,\n );\n if (options.autoScaleInterval !== undefined)\n throw new TypeError(\n \"autoScaleInterval belongs to ClusterManager inside a cluster worker.\",\n );\n if (\n options.shardIds &&\n !options.shardIds.every((id) => context.shardIds.includes(id))\n )\n throw new RangeError(\n \"shardIds must be a subset of the cluster's own shards.\",\n );\n }\n const count = context\n ? context.shardCount\n : options.shardCount === \"auto\" || options.shardCount === undefined\n ? 1\n : options.shardCount;\n if (!Number.isInteger(count) || count < 1)\n throw new RangeError(\"Shard count must be a positive integer.\");\n if (options.guildsPerShard !== undefined) {\n assertGuildsPerShard(options.guildsPerShard);\n if (options.shardCount !== \"auto\")\n throw new RangeError(\n 'guildsPerShard needs shardCount: \"auto\".',\n );\n }\n this.#ids = options.shardIds ?? context?.shardIds;\n if (this.#ids && options.shardCount !== \"auto\")\n validateShardIds(this.#ids, count);\n this.#worker =\n options.worker === false\n ? undefined\n : (options.worker ??\n (context && options.cluster === undefined\n ? ClusterWorker.current()\n : undefined));\n this.#gate =\n options.identify === false\n ? undefined\n : (options.identify ??\n (context?.coordinatedIdentify\n ? this.#worker?.identifyGate\n : undefined));\n if (\n options.autoScaleInterval !== undefined &&\n (!Number.isInteger(options.autoScaleInterval) ||\n options.autoScaleInterval < 1000)\n )\n throw new RangeError(\n \"autoScaleInterval must be an integer of at least 1000 milliseconds.\",\n );\n if (\n options.spawnDelay !== undefined &&\n (!Number.isFinite(options.spawnDelay) || options.spawnDelay < 0)\n )\n throw new RangeError(\n \"spawnDelay must be a non-negative finite number of milliseconds.\",\n );\n if (\n options.maxConcurrency !== undefined &&\n (!Number.isInteger(options.maxConcurrency) ||\n options.maxConcurrency < 1)\n )\n throw new RangeError(\"maxConcurrency must be a positive integer.\");\n if (\n options.handshakeTimeout !== undefined &&\n (!Number.isFinite(options.handshakeTimeout) ||\n options.handshakeTimeout < 0)\n )\n throw new RangeError(\n \"handshakeTimeout must be a non-negative number of milliseconds.\",\n );\n if (\n options.startup !== undefined &&\n options.startup !== \"safe\" &&\n options.startup !== \"sequential\"\n )\n throw new RangeError('startup must be \"safe\" or \"sequential\".');\n this.#startup = options.startup ?? context?.startup ?? \"safe\";\n if (\n options.readyTimeout !== undefined &&\n (!Number.isFinite(options.readyTimeout) ||\n options.readyTimeout <= 0)\n )\n throw new RangeError(\n \"readyTimeout must be a positive number of milliseconds.\",\n );\n this.#readyTimeout =\n options.readyTimeout ??\n context?.readyTimeout ??\n SEQUENTIAL_READY_TIMEOUT;\n this.#total = count;\n this.#fixedCount = count;\n this.#auto = options.shardCount === \"auto\";\n // Discord allows one IDENTIFY per 5s per rate-limit key. Spawning\n // shards back-to-back trips that limit and the gateway answers with\n // close 4008 / invalid session, so 5s is the default rather than an\n // opt-in.\n this.#options = {\n spawnDelay: ShardManager.IDENTIFY_INTERVAL,\n ...options,\n };\n this.#worker?.onRequest(REQUEST_HEALTH, (data) =>\n this.report(\n (data as { metrics?: boolean } | null | undefined)?.metrics ===\n true,\n ),\n );\n this.#worker?.onRequest(REQUEST_ASSIGN, async (data) => {\n const ids = (data as { shardIds?: unknown } | null)?.shardIds;\n if (!Array.isArray(ids))\n throw new TypeError(\"shardIds must be an array.\");\n await this.addShards(ids as number[]);\n return { shardIds: ids };\n });\n if (options.shardCount !== \"auto\") this.#initialize(count);\n }\n /** Retrieves Discord's recommended shard count. @returns Recommended shard count. @throws {Error} If discovery fails or returns invalid data. */\n public async fetchRecommendedShardCount(): Promise<number> {\n return resolveShardCount(\n this.#options.token,\n await this.fetchGatewayInfo(),\n this.#options,\n );\n }\n /** Retrieves `/gateway/bot`: recommended shards and the IDENTIFY budget. Remembered for pacing the next connect. @throws {Error} If discovery fails or returns invalid data. */\n public async fetchGatewayInfo(): Promise<GatewayBotInfo> {\n const info = await fetchGatewayBot(this.#options.token);\n this.#gatewayInfo = info;\n return info;\n }\n /** Connects all shards sequentially. A destroyed manager is reinitialized before connecting. @returns A promise fulfilled after all shards connect. @throws {Error} If a shard fails to connect. */\n public async connect(): Promise<void> {\n // Clear any timer from a previous connect() so a second call cannot leak one.\n if (this.#autoScaleTimer) {\n clearInterval(this.#autoScaleTimer);\n this.#autoScaleTimer = undefined;\n }\n await this.#ensureInitialized();\n // Only shards without a live socket spend an IDENTIFY; connect() is a\n // no-op for the rest, so they are neither counted nor paced.\n await this.#start(\n [...this.shards]\n .filter(([, gateway]) => needsConnection(gateway))\n .map(([id]) => id),\n );\n // Auto-scaling only means something when the count is \"auto\".\n if (\n this.#options.autoScaleInterval &&\n this.#auto &&\n !this.#autoScaleTimer\n ) {\n this.#autoScaleTimer = setInterval(() => {\n void this.checkAutoScale();\n }, this.#options.autoScaleInterval);\n }\n }\n /**\n * Takes over more shards of the same deployment (recovery after another\n * cluster gave up) and connects them through the same IDENTIFY path.\n * @throws {RangeError} If an ID is invalid, already owned, or the manager is not initialized.\n */\n public async addShards(shardIds: readonly number[]): Promise<void> {\n await this.#ensureInitialized();\n validateShardIds(shardIds, this.#total);\n for (const id of shardIds)\n if (this.shards.has(id))\n throw new RangeError(`Shard ${id} is already owned.`);\n for (const id of shardIds)\n this.shards.set(id, this.#createShard(id, this.#total));\n if (this.#ids) this.#ids = [...this.#ids, ...shardIds];\n this.#announced = false;\n try {\n // Only the new shards are torn down on failure: the ones that were\n // already running keep their sessions.\n await this.#start([...shardIds], false);\n } catch (error) {\n for (const id of shardIds) {\n this.shards.get(id)?.close();\n this.shards.delete(id);\n this.#lifecycle.delete(id);\n }\n if (this.#ids)\n this.#ids = this.#ids.filter((id) => !shardIds.includes(id));\n throw error;\n }\n }\n /** Starts the given shards: through the coordinated IDENTIFY gate when there is one, else in paced rounds. */\n async #start(ids: number[], destroyOnFailure = true): Promise<void> {\n // A `destroy()` (or `reshard()`) while this runs makes it stale: it\n // then stops quietly, and never touches the shard set that replaced\n // the one it was started for.\n const generation = this.#generation;\n const stale = (): boolean => generation !== this.#generation;\n const gateways = new Map(ids.map((id) => [id, this.shards.get(id)!]));\n const gate = this.#gate;\n const sequential = this.#startup === \"sequential\";\n if (gate) {\n const results = await Promise.allSettled(\n ids.map(async (id) => {\n const gateway = gateways.get(id)!;\n this.#setStatus(id, \"connecting\");\n const ready = await connectInQueue(gateway, id, gate, {\n handshakeTimeout: this.#options.handshakeTimeout,\n waitForReady: sequential,\n readyTimeout: this.#readyTimeout,\n cancelled: stale,\n });\n if (!ready && sequential && !stale())\n this.#emit(\n \"shardError\",\n id,\n new Error(\n `Shard ${id} was not READY within ${this.#readyTimeout} ms; the next shard starts.`,\n ),\n );\n }),\n );\n if (stale()) return;\n const failed = results.findIndex(\n (result) => result.status === \"rejected\",\n );\n if (failed !== -1) {\n if (destroyOnFailure) this.destroy();\n throw new Error(`Failed to connect shard ${ids[failed]}.`, {\n cause: (results[failed] as PromiseRejectedResult).reason,\n });\n }\n return;\n }\n const limit = this.#gatewayInfo?.sessionStartLimit;\n if (limit && limit.remaining < ids.length)\n // Live shards are left running: the budget only blocks new ones.\n throw new Error(\n `Session start limit exhausted: ${limit.remaining} of ${limit.total} IDENTIFYs left for ${ids.length} shards; resets in ${Math.ceil(limit.resetAfter / 1000)}s.`,\n );\n // Shards whose IDs differ modulo max_concurrency use different\n // IDENTIFY rate-limit keys, so each round of consecutive IDs may start\n // together; rounds are spaced by spawnDelay. An override may lower the\n // concurrency but never exceed what Discord reported.\n const concurrency = sequential\n ? 1\n : Math.min(\n this.#options.maxConcurrency ?? limit?.maxConcurrency ?? 1,\n limit?.maxConcurrency ?? Infinity,\n );\n // Discord's IDENTIFY rate-limit key is `shard_id % max_concurrency`: one\n // IDENTIFY per key per 5 seconds. Each round starts the next shard of\n // every bucket, so a round never puts two shards on one key.\n const buckets = new Map<number, number[]>();\n for (const id of ids) {\n const key = id % concurrency;\n const bucket = buckets.get(key);\n if (bucket) bucket.push(id);\n else buckets.set(key, [id]);\n }\n const rounds = Math.max(\n 0,\n ...[...buckets.values()].map((bucket) => bucket.length),\n );\n for (let index = 0; index < rounds; index++) {\n if (stale()) return;\n const round = [...buckets.values()]\n .map((bucket) => bucket[index])\n .filter((id): id is number => id !== undefined);\n for (const id of round) this.#setStatus(id, \"connecting\");\n const results = await Promise.allSettled(\n round.map((id) => gateways.get(id)!.connect()),\n );\n if (stale()) return;\n const failed = results.findIndex(\n (result) => result.status === \"rejected\",\n );\n // Each started shard spends one IDENTIFY of the remembered budget,\n // so a later connect() checks against what is actually left.\n if (limit)\n limit.remaining = Math.max(\n 0,\n limit.remaining -\n results.filter((r) => r.status === \"fulfilled\").length,\n );\n if (failed !== -1) {\n if (destroyOnFailure) this.destroy();\n throw new Error(`Failed to connect shard ${round[failed]}.`, {\n cause: (results[failed] as PromiseRejectedResult).reason,\n });\n }\n if (sequential && index + 1 < rounds)\n for (const id of round)\n if (\n !(await shardReady(\n gateways.get(id)!,\n this.#readyTimeout,\n )) &&\n !stale()\n )\n this.#emit(\n \"shardError\",\n id,\n new Error(\n `Shard ${id} was not READY within ${this.#readyTimeout} ms; the next shard starts.`,\n ),\n );\n if (\n index + 1 < rounds &&\n this.#options.spawnDelay &&\n this.#options.spawnDelay > 0\n ) {\n // connect() resolves when the socket opens; IDENTIFY follows\n // HELLO. Start the interval from the handshake itself so a late\n // HELLO cannot squeeze two rounds' IDENTIFYs together.\n await Promise.all(\n round.map((id) =>\n handshakeSent(\n gateways.get(id)!,\n this.#options.handshakeTimeout ?? 15_000,\n ),\n ),\n );\n await sleep(this.#options.spawnDelay);\n }\n }\n }\n /** Checks if the recommended shard count has changed and reconnects if so. */\n public async checkAutoScale(): Promise<void> {\n if (!this.#auto) return;\n try {\n const recommended = await this.fetchRecommendedShardCount();\n if (recommended !== this.shardCount) {\n await this.reshard(recommended);\n }\n } catch (error) {\n // Surface the failure instead of silently swallowing it; the next\n // interval tick retries.\n this.#options.onAutoScaleError?.(error);\n }\n }\n /** Forces a reshard to a new shard count, replacing all active shards. @param count New shard count. */\n public async reshard(count: number): Promise<void> {\n this.#options.shardCount = count;\n this.destroy();\n await this.connect();\n }\n /** Permanently closes the current shard set and releases Gateway resources. The next connect recreates them. @returns Nothing. */\n public destroy(): void {\n for (const shard of this.shards.values()) shard.close();\n for (const id of this.shards.keys()) this.#setStatus(id, \"destroyed\");\n this.shards.clear();\n this.#lifecycle.clear();\n this.#announced = false;\n this.#resolved = false;\n this.#destroyed = true;\n this.#generation++;\n if (this.#autoScaleTimer) {\n clearInterval(this.#autoScaleTimer);\n this.#autoScaleTimer = undefined;\n }\n }\n /** Listens for an aggregated shard event. @returns This manager. */\n public on<E extends keyof ShardManagerEvents>(\n event: E,\n listener: (...args: ShardManagerEvents[E]) => void,\n ): this {\n let set = this.#listeners.get(event);\n if (!set) this.#listeners.set(event, (set = new Set()));\n set.add(listener as unknown as (...args: unknown[]) => void);\n return this;\n }\n /** Stops listening for a shard event. @returns This manager. */\n public off<E extends keyof ShardManagerEvents>(\n event: E,\n listener: (...args: ShardManagerEvents[E]) => void,\n ): this {\n this.#listeners\n .get(event)\n ?.delete(listener as unknown as (...args: unknown[]) => void);\n return this;\n }\n #emit<E extends keyof ShardManagerEvents>(\n event: E,\n ...args: ShardManagerEvents[E]\n ): void {\n for (const listener of this.#listeners.get(event) ?? [])\n try {\n listener(...args);\n } catch {\n // A faulty listener must not disturb the shard.\n }\n }\n /** Calculates the target shard ID for a Discord guild snowflake. @param guildId Guild snowflake. @returns Shard identifier. */\n public getShardIdForGuild(guildId: string): number {\n const id = BigInt(guildId);\n return Number((id >> 22n) % BigInt(this.#total));\n }\n /** Sends a voice state update (op 4) on the shard that owns the guild. @returns Whether it was sent; `false` if that shard is absent or disconnected. */\n public sendVoiceState(\n guildId: string,\n channelId: string | null,\n options: { selfMute?: boolean; selfDeaf?: boolean } = {},\n ): boolean {\n return (\n this.shards\n .get(this.getShardIdForGuild(guildId))\n ?.sendVoiceState(guildId, channelId, options) ?? false\n );\n }\n /** Gets a shard by ID. @param id Shard identifier. @returns Gateway instance or undefined. */\n public get(id: number): Gateway | undefined {\n return this.shards.get(id);\n }\n /** Returns each shard's connection state, lifecycle status and heartbeat latency. */\n public health(): ShardHealth[] {\n return [...this.shards].map(([id, gateway]) => {\n const life = this.#lifecycle.get(id);\n return {\n id,\n state: gateway.state,\n ping: gateway.ping,\n status: life?.status ?? \"idle\",\n ...(life?.readyAt !== undefined\n ? { readyAt: life.readyAt }\n : {}),\n ...(life?.disconnectedAt !== undefined\n ? { disconnectedAt: life.disconnectedAt }\n : {}),\n };\n });\n }\n /** A health report for the parent: shard health and, on request, process metrics. */\n public report(metrics = false): WorkerReport {\n let numbers: WorkerMetrics | undefined;\n if (metrics) {\n const memory = process.memoryUsage();\n const cpu = process.cpuUsage();\n numbers = {\n rssBytes: memory.rss,\n heapUsedBytes: memory.heapUsed,\n cpuUserMicros: cpu.user,\n cpuSystemMicros: cpu.system,\n };\n }\n return {\n shards: this.health(),\n ...(numbers ? { metrics: numbers } : {}),\n reportedAt: Date.now(),\n };\n }\n #setStatus(id: number, status: ShardStatus): void {\n const life = this.#lifecycle.get(id) ?? { status };\n life.status = status;\n const now = Date.now();\n if (status === \"ready\") life.readyAt = now;\n if (status === \"disconnected\") life.disconnectedAt = now;\n this.#lifecycle.set(id, life);\n if (status !== \"ready\") this.#announced = false;\n else this.#maybeAnnounce();\n }\n /** Emits `ready` and tells the parent once every owned shard is ready. */\n #maybeAnnounce(): void {\n if (this.#announced || this.shards.size === 0) return;\n for (const id of this.shards.keys())\n if (this.#lifecycle.get(id)?.status !== \"ready\") return;\n this.#announced = true;\n this.#emit(\"ready\");\n this.#worker?.ready(this.shardIds);\n this.#worker?.reportHealth(this.report());\n }\n /** Returns information for all managed shards. @returns Shard information snapshots. */\n public values(): ShardInfo[] {\n return [...this.shards].map(([id, gateway]) => ({ id, gateway }));\n }\n /** Ensures a live shard set exists. @returns A promise fulfilled after initialization. @throws {Error} If initialization fails. */\n async #ensureInitialized(): Promise<void> {\n if (this.#resolved) return;\n if (this.#destroyed) this.#destroyed = false;\n // A number here is the constructor's or the last `reshard()`'s.\n const count =\n this.#options.shardCount === \"auto\"\n ? await this.fetchRecommendedShardCount()\n : (this.#options.shardCount ?? this.#fixedCount);\n this.#initialize(count);\n }\n /** Creates every shard for a resolved shard count. @param count Number of shards. @returns Nothing. */\n #initialize(count: number): void {\n if (this.#resolved) return;\n this.#total = count;\n const ids = this.#ids ?? Array.from({ length: count }, (_, i) => i);\n validateShardIds(ids, count);\n for (const id of ids) this.shards.set(id, this.#createShard(id, count));\n this.#resolved = true;\n }\n /** Creates one Gateway instance with its shard identity. @param id Shard identifier. @param count Total shard count. @returns Configured Gateway. */\n #createShard(id: number, count: number): Gateway {\n const gateway = new Gateway({\n token: this.#options.token,\n intents: this.#options.intents,\n shardId: id,\n shardCount: count,\n reconnect: this.#options.reconnect ?? true,\n // A re-IDENTIFY after a lost session takes a slot like the first.\n ...(this.#gate\n ? {\n identifyGate: {\n acquire: () => this.#gate!.acquire(id),\n release: () => this.#gate!.release(id),\n },\n }\n : {}),\n });\n gateway.on(\"ready\", () => {\n this.#setStatus(id, \"ready\");\n this.#emit(\"shardReady\", id);\n });\n gateway.on(\"resumed\", () => {\n this.#setStatus(id, \"ready\");\n this.#emit(\"shardResume\", id);\n });\n gateway.on(\"close\", (close) => {\n this.#setStatus(id, \"disconnected\");\n this.#emit(\n \"shardDisconnect\",\n id,\n close as { code: number; action: string },\n );\n });\n gateway.on(\"error\", (error) =>\n this.#emit(\"shardError\", id, error as Error),\n );\n gateway.on(\"unstable\", (info) => {\n this.#setStatus(id, \"unstable\");\n this.#emit(\n \"shardUnstable\",\n id,\n info as { reconnects: number; delay: number },\n );\n });\n this.#emit(\"shardCreate\", id, gateway);\n return gateway;\n }\n}\n"
|
|
21
|
+
],
|
|
22
|
+
"mappings": "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",
|
|
23
|
+
"debugId": "C7DC56012C5DC05564756E2164756E21",
|
|
24
|
+
"names": []
|
|
25
|
+
}
|