Runtime and state

Weeble runs each deployed revision inside a managed V8 isolate. Your script registers commands, event handlers, and scheduled tasks; Weeble handles the gateway, routing, Discord rate limits, and isolate lifecycle.

Runtime globals

discord and weeble are global namespaces.

The runtime also provides web-style APIs including fetch, crypto, crypto.subtle, URL, URLSearchParams, Headers, Request, Response, Blob, timers, sleep, TextEncoder, and TextDecoder.

Node.js server APIs and built-in modules are not available. Do not design a deployment around fs, http, process, Buffer, or a process that stays alive forever.

Top-level code

Top-level code runs when Weeble warms a revision. Use it to construct reusable objects and register handlers:

const settings = new weeble.KVNamespace("settings");

discord.on(discord.events.MESSAGE_CREATE, async (message) => {
  const prefix = (await settings.get<string>("prefix")) ?? "!";
  if (message.content === `${prefix}ping`) {
    await message.reply("pong");
  }
});

A warm isolate may handle multiple events, but Weeble can evict or recycle it. Values held only in module variables are caches, not durable storage.

Choose where state belongs

State

Use

Constants and imported project files

Configuration shipped with one revision

Module variables

Temporary caches that are safe to lose

weeble.KVNamespace

Durable JSON state that changes while the bot runs

Scheduled tasks

Work that must run later, even after an isolate is recycled

KV persists across publishes and rollbacks until a key expires, is deleted, its namespace is cleared, or the deployment is deleted. See KV storage.

Short delays and durable work

Use sleep() or setTimeout() only for short work that fits inside the current execution deadline.

For a reminder or delayed action, define a task at the top level and schedule it durably:

weeble.tasks.define("send-reminder", async (event) => {
  const channelId = String(event.payload?.channelId ?? "");
  const text = String(event.payload?.text ?? "");
  const channel = await discord.fetchGuildTextChannel(channelId);
  if (channel) await channel.send(text);
});

async function scheduleReminder(
  instanceId: string,
  channelId: string,
  text: string,
) {
  await weeble.tasks.runAt(
    "send-reminder",
    instanceId,
    Date.now() + 60_000,
    { channelId, text },
  );
}

Call scheduleReminder() from a command or event handler. Do not schedule it at the top level, because top-level code can run again whenever Weeble warms the revision. The task handler must be registered by the active revision when the run becomes due.

CPU-heavy work

Normal event handlers have a 500ms CPU budget by default. Use weeble.compute.run() for bounded work that needs more CPU time, such as sorting a large leaderboard or transforming a large data set:

const ranked = await weeble.compute.run(
  "rank-scores",
  () => scores.toSorted((left, right) => right.score - left.score),
);

console.log(ranked.result, ranked.usedCpuMs, ranked.bucketRemainingMs);

Compute jobs share a separate CPU bucket and still have wall-clock limits. They are not background jobs: await the result in the current command or event. Use a scheduled task when work must survive beyond the current execution.

Execution boundaries

Each event has CPU, wall-time, Discord request, KV, fetch, and response-size limits. Await only the work needed to answer the event, reuse payload data, and move durable delayed work into tasks.

See Limits for the current defaults and Debugging for investigating execution failures.