DOCS/STATE.MD
Per-Guild State and Persistence
One GlassFrame instance can serve many Discord servers at once. Three
things are tracked independently per server, and all three survive a bot
restart if you give GlassFrame a persistent stateStore:
- Which layers are on. Server A can run AntiRaid + AntiNuke while Server B runs nothing, or everything, independently. There is no such thing as "GlassFrame is on" globally - only "layer X is on for guild Y."
- The whitelist. Exempting a user in one server does not exempt them anywhere else.
- The command prefix (v2.1.0+), if a server has set its own via
!gf prefix set.config.prefixis still the fallback everywhere a server hasn't customized it.
Everything else that's naturally guild-scoped already was before this -
threat scores, open cases, join-rate tracking, audit-log burst tracking are
all keyed by guildId internally. The two items above are the ones that
used to be accidentally global; see CHANGELOG.md for the 2.0.0 entry if
you're upgrading from an earlier version.
How it's stored
Layer.enabledGuilds is a Set<guildId> per layer (not one shared
boolean). GlassFrame.whitelist is a Map<guildId, Set<userId>>. Both are
read through the frame's own methods rather than touched directly:
frame.enableLayer("antiRaid", guildId);
frame.disableLayer("antiRaid", guildId);
frame.getStatus(guildId); // { basicSecurity: true, antiRaid: false, ... }
frame.addToWhitelist(guildId, userId);
frame.removeFromWhitelist(guildId, userId);
frame.isWhitelisted(guildId, userId);
Persistence across restarts
Pass a stateStore when constructing GlassFrame - MemoryStateStore
(default, lost on restart) or JSONFileStateStore (a single JSON file, no
database, ARM64/Termux-friendly):
const { JSONFileStateStore } = require("glassframe-protocol");
const frame = new GlassFrame(client, {
getLogChannel: /* ... */,
stateStore: new JSONFileStateStore("./glassframe-state.json"),
autoStart: ["basicSecurity", "antiRaid"]
});
Every time a layer is toggled or the whitelist changes for a guild,
GlassFrame writes that guild's full state (which layers are on, its
whitelist) to the store. At startup, for every guild the bot is already in,
and again automatically whenever the bot joins a new guild
(guildCreate), GlassFrame:
- Checks the store for saved state for that guild.
- If found, restores exactly that - the layers that were on stay on.
- If nothing is saved yet (a guild GlassFrame has never seen before),
applies
autoStartand the constructor'swhitelistoption (see below) as that guild's starting defaults.
Restoring is async (a real stateStore might read from disk or a
database), so it can't finish before the constructor returns. In practice
this resolves well before a Discord event could possibly arrive, so most
code never needs to think about it - but if you want a guarantee, await
frame.ready after construction; it resolves once every guild the bot was
already in has been restored (guilds joined later via guildCreate
restore independently and aren't part of this promise, since there's
nothing they could race against).
autoStart and the whitelist option are only ever defaults for a
guild's first run - once a guild has any saved state (even "everything
off"), neither applies to it again; the saved state is the source of truth
from then on.
The whitelist constructor option
new GlassFrame(client, { getLogChannel: /* ... */, whitelist: ["123456789012345678"] });
This seeds a starting whitelist for every guild - both the ones already in
client.guilds.cache and any the bot joins later - the first time
GlassFrame sees that guild (i.e., it never overrides a guild's own saved
whitelist). Think of it as "these users are trusted everywhere by default";
frame.removeFromWhitelist(guildId, userId) still works normally
per-guild afterward.
Writing your own store
Implement two async methods and pass an instance as stateStore:
class MyStore {
async get(guildId) { /* return the saved state object, or null */ }
async set(guildId, state) { /* persist `state` for this guildId */ }
}
state is { layers: { basicSecurity: true, antiRaid: false, ... }, whitelist: ["userId1", "userId2"], prefix: "!custom " } - prefix is null for a server using the default.