/** Per-tier values: `free` is the floor everyone resolves to. */ export type TierMap = { free: T; vip?: T; } & { [rung: `vip.${number}`]: T; }; type KindValue = { bool: boolean; number: number; string: string; }; export type FlagKind = keyof KindValue; export type FlagSpec = { kind: K; /** Short human label for admin surfaces ("rich text (global)"). */ label: string; /** One-line operator hint, shown next to the control. */ help?: string; /** Code default — a scalar, or a tier map for per-tier values. */ default: KindValue[K] | TierMap; /** Number flags: inclusive write-time bounds. Many writers touch a flag (bot admin * taps, web consoles, fat fingers) — bounds make an insane value unwritable at the * shared rule ({@link flagValueError}) instead of trusting every panel separately. */ min?: number; max?: number; /** * String flags: the closed set of allowed values — the flag becomes an ENUM. Panels * render a picker instead of free text, and in-bot admin menus can ROTATE through the * values on tap (off → admins → all → off …). The audience-gating convention lives in * {@link audienceAllows}. */ choices?: readonly string[]; }; /** What `describe()` returns per flag — JSON-safe, for panels/consoles. */ export type FlagDescriptor = { key: string; kind: FlagKind; label: string; help?: string; default: unknown; /** Whether the DECLARED default is per-tier (panels offer tier inputs). */ tiered: boolean; min?: number; max?: number; choices?: readonly string[]; }; export type FlagsBackend = { /** Read the bot's operator-config record (plain JSON object). */ read: (ctx: unknown) => Promise>; /** * Merge a patch into that record (RFC 7386 style: a `null` value deletes * the key — SQLite's `json_patch` and most merge-patch writers already * behave this way). Required for `flags.set`. */ write?: (ctx: unknown, patch: Record) => Promise; }; type Resolver = (ctx: unknown) => Promise; export type Flags> = { [K in keyof Spec]: Resolver; } & { /** Every flag's declaration, JSON-safe — the schema panels render from. */ describe: () => FlagDescriptor[]; /** Raw stored overrides (only keys that are actually overridden). */ overrides: (ctx: unknown) => Promise>; /** * Resolve a flag by RUNTIME key — same resolution as the typed accessor, * loosely typed (unknown key panics). For generic admin surfaces iterating * `describe()`; call sites use `await flags.(ctx)`. */ get: (ctx: unknown, key: string) => Promise; /** * Write a live override (scalar or tier map, kind-checked), or `null` * to clear it back to the code default. Runtime key (unknown panics), so * generic panels can write what `describe()` lists. Panics without a * `write` backend. */ set: (ctx: unknown, key: string, value: unknown) => Promise; }; /** Scalars are never objects, so `free` presence is the whole test. Exported so * external panels can render a tier map as per-tier inputs. */ export declare const isTierMap: (v: unknown) => v is TierMap; /** * A scalar of the right kind, or a tier map whose every value is. THE shape half * of the write rule; see {@link flagValueError} for the full rule with constraints. */ export declare const flagValueOk: (kind: FlagKind, v: unknown) => boolean; /** The constraint surface {@link flagValueError} checks — a FlagDescriptor satisfies it. */ export type FlagConstraints = { kind: FlagKind; min?: number; max?: number; choices?: readonly string[]; }; /** * THE write rule, complete: shape (kind, tier maps) plus constraints (bounds, choices). * Returns null when the value is writable, else a human-readable reason. `flags.set` * enforces it, and it's exported so any external writer (a web console patching the * same config record directly, an in-bot input prompt) validates with the SAME function * instead of restating it. Constraints apply at WRITE time only — reads keep shape-checking * so tightening a bound later never bricks an already-stored value. */ export declare const flagValueError: (spec: FlagConstraints, v: unknown) => string | null; /** * The audience-gating convention for a `choices: ["off", "admins", "all"]` flag: * a staged rollout an in-bot menu rotates through on tap. `off` gates everyone, * `admins` opens it to operators/testers, `all` ships it. */ export declare const audienceAllows: (value: string, opts: { admin: boolean; }) => boolean; export declare function defineFlags>(spec: Spec, backend: FlagsBackend): Flags; export {}; //# sourceMappingURL=flags.d.ts.map