A Bot That Stays Up

Sharding, session resume, a clean shutdown and a restarting supervisor are all in the SDK

A compact server rack with wave lights on its front

An Automatic Shard Count

A shard is one gateway connection, carrying the events of part of the bot's communities. With sharding: "auto", the SDK counts the communities at the first connect and opens one shard for every 2,000

When the bot outgrows that plan while it runs, Fluxer closes a shard with code 4011, and the SDK moves every shard to a larger plan in the same process. The bot keeps running. Events sent during the move are missed, and the move is logged once at Warn

Sharding in the Docs
bot.ts
import { runBot } from "@neontechspace/fluxerly"await runBot({    token: process.env.FLUXER_BOT_TOKEN,    sharding: "auto",    events: {        messageCreate: ({ message, reply }) => {            if (message.content !== "!ping") return undefined            return reply("Pong!")        },    },})

Restarts That Resume

A restarted bot normally starts new sessions and misses what happened while it was down. Fluxer keeps a disconnected session for 60 seconds, and fileSessionStore saves each shard's session at a clean shutdown and offers it at the next start. Its folder, sessions here, holds session IDs that let the token resume a session, so keep it out of version control and readable only by the bot's account

A quick restart, such as a deploy, then resumes and receives the missed events within Fluxer's replay limits. Each snapshot is written to a temporary file and renamed into place, so a crash never leaves a half-written one, and a store problem never stops the bot

Session Resume in the Sharding Guide
bot.ts
import { fileSessionStore, runBot } from "@neontechspace/fluxerly"await runBot({    token: process.env.FLUXER_BOT_TOKEN,    sharding: { totalShards: "auto", sessions: fileSessionStore("sessions") },    events: {        messageCreate: ({ message, reply }) => {            if (message.content !== "!ping") return undefined            return reply("Pong!")        },    },})

A Stop That Finishes the Work

Ctrl+C, or the SIGTERM a process manager sends, asks the bot to stop. It stops taking new events, lets running handlers and their requests finish for up to five seconds, a time drainMs changes, then cancels what is left and closes the connection

runBot never exits the process. Once it resolves, the application closes what it opened, and Node.js ends on its own

From Start to Stop
A power switch with a clean off position
bot.js
import { runBot } from "@neontechspace/fluxerly"// Something the application opened itself, such as a database connection or a timerconst heartbeat = setInterval(() => console.info("Still running"), 60_000)// Ctrl+C, or SIGTERM from a process manager, asks the bot to stop, and running// handlers then get up to 10 seconds to finishawait runBot({    token: process.env.FLUXER_BOT_TOKEN,    drainMs: 10_000,    events: {        messageCreate: ({ message, reply }) => {            if (message.content !== "!ping") return            return reply("Pong!")        },    },})// runBot never exits the process. Close what the application opened, then Node.js endsclearInterval(heartbeat)

Crashed Processes Come Back

When one process is not enough, the SDK's supervisor starts several Node.js child processes, gives each its shards and stops them all on shutdown

A child that exits unexpectedly is replaced, up to three times in a row with a growing delay. A child that runs for a minute after becoming ready gets its full budget back, so only a crash loop uses it up

Run Shards in Several Processes
TypeScript
import { describeError, supervisor } from "@neontechspace/fluxerly"const workers = supervisor.create({    entry: new URL("./shard-worker.js", import.meta.url),    token: process.env.FLUXER_BOT_TOKEN,    totalShards: "auto",    processes: 2,})const stop = () => {    workers.shutdown().then(undefined, (defect: unknown) => {        console.error(describeError(defect))        process.exitCode = 1    })}process.once("SIGINT", stop)process.once("SIGTERM", stop)const started = await workers.start()const closed = started.isOk() ? await workers.waitForClose() : startedprocess.removeListener("SIGINT", stop)process.removeListener("SIGTERM", stop)if (closed.isErr()) {    console.error(describeError(closed.error))    process.exitCode = 1}

Gateway Health and Worker Health, Apart

A connected bot can still have a stopped subscription, the registration that delivers one event to its handler. A report on the connection alone would call that bot healthy

The supervision guide reports both side by side: The gateway's state, and the critical worker's state with its failure count. Health reports carry states and counts, never event bodies or raw exceptions

The Application Supervision Guide
TypeScript
import type { ClientDiagnostics, ConnectionState } from "@neontechspace/fluxerly";export interface BotHealth {    readonly gateway: ConnectionState;    readonly criticalWorker: "starting" | "running" | "failed" | "stopped";    readonly handlerFailures: number;}export type SupervisedOutcome =    | { readonly kind: "stopped" }    | { readonly kind: "connection-failed"; readonly failure: string }    | { readonly kind: "critical-worker-failed"; readonly failure: string };// The health callback receives both reports. Neither one stands in for the otherexport type ReportHealth = (health: BotHealth, diagnostics: ClientDiagnostics) => void;

Deploy with the Token in the Environment

A deployed bot is an ordinary Node.js process. Here systemd reads the token from a file that only root can read, so the bot's own account never needs access to it, and the token never sits in the code

A stop sends SIGTERM and waits 30 seconds before forcing it, which leaves the drain and cleanup room to finish. systemd restarts the bot after a crash or a failing exit code, but not after a clean stop

Deploying in the Docs
fluxer-bot.service
[Unit]Description=Fluxer botWants=network-online.targetAfter=network-online.target[Service]Type=simpleUser=fluxer-botWorkingDirectory=/srv/fluxer-botEnvironmentFile=/etc/fluxer-bot.envExecStart=/usr/bin/node bot.jsRestart=on-failureRestartSec=5TimeoutStopSec=30[Install]WantedBy=multi-user.target