Custom preconditions
Write typed, reusable checks with optional commit work.
For a check used by several commands, create a class in preconditionsDir. The file's default export is the class. SWAGcommands loads these before it loads command definitions.
import {
AllFlowsPrecondition,
createPreconditionFactory,
type ChatInputCommandUsage,
type ContextMenuCommandUsage,
type MessageCommandUsage,
type PreconditionCommand,
type PreconditionContext,
} from "swagcommands";
interface LevelContext extends PreconditionContext {
level: number;
}
class MinimumLevelPrecondition extends AllFlowsPrecondition {
static readonly preconditionName = "MinimumLevel";
messageRun(usage: MessageCommandUsage, _command: PreconditionCommand, context: LevelContext) {
return this.check(usage.user.id, context.level);
}
chatInputRun(usage: ChatInputCommandUsage, _command: PreconditionCommand, context: LevelContext) {
return this.check(usage.user.id, context.level);
}
contextMenuRun(usage: ContextMenuCommandUsage, _command: PreconditionCommand, context: LevelContext) {
return this.check(usage.user.id, context.level);
}
private check(userId: string, level: number) {
return getLevel(userId) >= level
? this.ok()
: this.error({ identifier: "LOW_LEVEL", message: `Level ${level} required.` });
}
}
export const MinimumLevel = createPreconditionFactory<LevelContext>(MinimumLevelPrecondition);
export default MinimumLevelPrecondition;getLevel is your own application function. Import the factory in a command and add MinimumLevel({ level: 3 }) to its preconditions. preconditionName gives the runtime check a stable name even if the file moves. If you omit it, the loader uses the filename.
AllFlowsPrecondition requires messageRun, chatInputRun, and contextMenuRun. Extend Precondition directly when a check only applies to selected flows. A missing method means that flow has no implementation for the check, so be intentional about where it is used.
Check, then commit
Some checks need to mutate state only if every check succeeds. Add messageCommit, chatInputCommit, or contextMenuCommit to your class. The framework first evaluates the checks, gathers commits from the successful branches, then runs those commits just before the callback. Cooldown uses this so a later failed guard does not consume a cooldown.
For typed string names, augment the exported Preconditions interface in a .ts or .d.ts file included by your project:
declare module "swagcommands" {
interface Preconditions {
MinimumLevel: { level: number };
}
}That only helps TypeScript. You still need a class loaded under the same runtime name. Factory entries are usually less work and keep the name and context together.