@seedcord/core 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/{Bus-BvF4VShN.mjs → Bus-BVwmKc-A.mjs} +36 -17
  2. package/dist/Bus-BVwmKc-A.mjs.map +1 -0
  3. package/dist/Pluggable-Cf5-WWmW.mjs +280 -0
  4. package/dist/Pluggable-Cf5-WWmW.mjs.map +1 -0
  5. package/dist/Pluggable-DFNUIlJM.d.mts +148 -0
  6. package/dist/{Plugin-D9ykXA5g.mjs → Plugin-C6oMqWf8.mjs} +7 -3
  7. package/dist/Plugin-C6oMqWf8.mjs.map +1 -0
  8. package/dist/{Plugin-CQ9IEj5X.d.mts → Plugin-CG9pWbPF.d.mts} +2 -2
  9. package/dist/{cursor-BANzac53.d.mts → cursor-BcRfmTSo.d.mts} +26 -72
  10. package/dist/{cursor-CuoA9cRh.mjs → cursor-GBUYz3jk.mjs} +378 -30
  11. package/dist/cursor-GBUYz3jk.mjs.map +1 -0
  12. package/dist/{index-rt8rZg4d.d.mts → index-ZzdQafFv.d.mts} +2 -2
  13. package/dist/index.d.mts +77 -19
  14. package/dist/index.mjs +186 -19
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/internal.index.d.mts +11 -5
  17. package/dist/internal.index.mjs +8 -455
  18. package/dist/internal.index.mjs.map +1 -1
  19. package/dist/node-internal.index.d.mts +6 -133
  20. package/dist/node-internal.index.mjs +15 -472
  21. package/dist/node-internal.index.mjs.map +1 -1
  22. package/dist/node.index.d.mts +2 -0
  23. package/dist/node.index.mjs +256 -0
  24. package/dist/node.index.mjs.map +1 -0
  25. package/dist/{phases-hBxMCD2V.d.mts → phases-F8yvPd60.d.mts} +104 -72
  26. package/dist/plugin.index.d.mts +1 -1
  27. package/dist/plugin.index.mjs +1 -1
  28. package/dist/publishDefault-D826XXDQ.mjs +14 -0
  29. package/dist/publishDefault-D826XXDQ.mjs.map +1 -0
  30. package/dist/{slashRouteLeaves-De1SxqnX.mjs → slashRouteLeaves-CbYRwqMY.mjs} +10 -2
  31. package/dist/slashRouteLeaves-CbYRwqMY.mjs.map +1 -0
  32. package/dist/{subscribers-wnMyNYZd.mjs → subscribers-CGGQkUfM.mjs} +2 -2
  33. package/dist/{subscribers-wnMyNYZd.mjs.map → subscribers-CGGQkUfM.mjs.map} +1 -1
  34. package/package.json +14 -8
  35. package/dist/Bus-BvF4VShN.mjs.map +0 -1
  36. package/dist/Plugin-D9ykXA5g.mjs.map +0 -1
  37. package/dist/cursor-CuoA9cRh.mjs.map +0 -1
  38. package/dist/slashRouteLeaves-De1SxqnX.mjs.map +0 -1
@@ -0,0 +1,148 @@
1
+ import { i as Bus, n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-F8yvPd60.mjs";
2
+ import { a as PluginCtor, d as Transport, f as TransportAssert, i as PluginArgs, l as Runtime, o as PluginLike, s as ChannelKeyAssert, t as CoreParamAssert, u as RuntimeAssert } from "./Plugin-CG9pWbPF.mjs";
3
+ import { Logger } from "@seedcord/logger";
4
+ import { REST } from "@discordjs/rest";
5
+ import { HostPluginKeys, HostShutdown, HostStartup } from "@seedcord/types/internal";
6
+ import { Config, IRateLimiter } from "@seedcord/types";
7
+ //#region src/node/Lifecycle/LifecycleTypes.d.ts
8
+ interface LifecycleTask {
9
+ name: string;
10
+ task: () => Promise<void>;
11
+ timeout: number;
12
+ }
13
+ //#endregion
14
+ //#region src/node/Lifecycle/CoordinatedLifecycle.d.ts
15
+ declare abstract class CoordinatedLifecycle<TPhase extends number> {
16
+ protected readonly phaseOrder: TPhase[];
17
+ protected readonly phaseEnum: Record<number, string>;
18
+ protected readonly logger: Logger;
19
+ protected readonly tasksMap: Map<TPhase, LifecycleTask[]>;
20
+ protected constructor(loggerName: string, phaseOrder: TPhase[], phaseEnum: Record<number, string>);
21
+ addTask(phase: TPhase, taskName: string, task: () => Promise<void>, timeoutMs: number): void;
22
+ removeTask(phase: TPhase, taskName: string): boolean;
23
+ protected runPhase(phase: TPhase): Promise<void>;
24
+ protected runTaskWithTimeout(phase: TPhase, task: LifecycleTask): Promise<void>;
25
+ protected isAborted(): boolean;
26
+ protected abstract canAddTask(): boolean;
27
+ protected abstract canRemoveTask(): boolean;
28
+ protected abstract getTaskType(): string;
29
+ protected abstract executeTasksInPhase(phase: TPhase, tasks: LifecycleTask[]): Promise<PromiseSettledResult<void>[]>;
30
+ }
31
+ //#endregion
32
+ //#region src/node/Lifecycle/CoordinatedShutdown.d.ts
33
+ declare class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase> {
34
+ private isShuttingDown;
35
+ private hasShutdown;
36
+ private exitCode;
37
+ private onSigTerm;
38
+ private onSigInt;
39
+ private startupGate?;
40
+ constructor();
41
+ protected canAddTask(): boolean;
42
+ protected canRemoveTask(): boolean;
43
+ protected getTaskType(): string;
44
+ protected executeTasksInPhase(phase: ShutdownPhase, tasks: LifecycleTask[]): Promise<PromiseSettledResult<void>[]>;
45
+ private registerSignalHandlers;
46
+ /** @internal */
47
+ removeSignalHandlers(): void;
48
+ /** @internal run() awaits this so boot finishes registering its dispose tasks first */
49
+ gateOnStartup(settled: Promise<void>): void;
50
+ addTask(phase: ShutdownPhase, taskName: string, task: () => Promise<void>, timeoutMs?: number): void;
51
+ /** @internal */
52
+ removeTask(phase: ShutdownPhase, taskName: string): boolean;
53
+ /** @internal */
54
+ run(exitCode?: number, exitProcess?: boolean): Promise<void>;
55
+ }
56
+ //#endregion
57
+ //#region src/node/Lifecycle/CoordinatedStartup.d.ts
58
+ declare class CoordinatedStartup extends CoordinatedLifecycle<StartupPhase> {
59
+ private isStartingUp;
60
+ private hasStarted;
61
+ constructor();
62
+ addTask(phase: StartupPhase, taskName: string, task: () => Promise<void>, timeoutMs?: number): void;
63
+ protected canAddTask(): boolean;
64
+ protected canRemoveTask(): boolean;
65
+ protected getTaskType(): string;
66
+ protected executeTasksInPhase(phase: StartupPhase, tasks: LifecycleTask[]): Promise<PromiseSettledResult<void>[]>;
67
+ /** @internal */
68
+ run(): Promise<void>;
69
+ protected isAborted(): boolean;
70
+ /** @internal */
71
+ abort(): void;
72
+ /** @internal */
73
+ get isReady(): boolean;
74
+ }
75
+ //#endregion
76
+ //#region src/node/Pluggable.d.ts
77
+ /**
78
+ * Base class for a plugin host, a transport `Seedcord` class.
79
+ *
80
+ * You attach plugins while configuring the bot. Within one startup phase, their `init()` calls run
81
+ * one after another in attach order.
82
+ */
83
+ declare abstract class Pluggable<BotT extends Transport, BotRt extends Runtime> implements CoreBase {
84
+ abstract readonly config: Config;
85
+ abstract readonly rest: REST;
86
+ abstract readonly applicationId: string;
87
+ abstract readonly rateLimiter: IRateLimiter;
88
+ abstract readonly bus: Bus;
89
+ /** @internal */
90
+ readonly [HostShutdown]: CoordinatedShutdown;
91
+ /** @internal */
92
+ readonly [HostStartup]: CoordinatedStartup;
93
+ /** Add a task that runs while the bot shuts down. */
94
+ readonly shutdown: Pick<CoordinatedShutdown, 'addTask'>;
95
+ /** Add a task that runs while the bot starts. */
96
+ readonly startup: Pick<CoordinatedStartup, 'addTask'>;
97
+ protected isInitialized: boolean;
98
+ protected startFailed: boolean;
99
+ protected readonly plugins: PluginLike[];
100
+ private readonly pluginLogger;
101
+ private readonly attachments;
102
+ private readonly completedInits;
103
+ private readonly disposePhases;
104
+ private pluginTasksRegistered;
105
+ private initPromise?;
106
+ private static isInstantiated;
107
+ private static liveHost?;
108
+ private static liveShutdown?;
109
+ private static liveProcessErrors?;
110
+ constructor(shutdown: CoordinatedShutdown, startup: CoordinatedStartup);
111
+ /** @internal */
112
+ protected init(): Promise<this>;
113
+ private runInit;
114
+ /** @internal codegen reads this to emit the `Core` augmentation */
115
+ get [HostPluginKeys](): readonly string[];
116
+ /** @internal */
117
+ protected static reset(host?: object): boolean;
118
+ /**
119
+ * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`
120
+ * writes the `Core` augmentation that types it there.
121
+ *
122
+ * Startup runs each plugin's `init()` in attach order within its phase.
123
+ *
124
+ * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.
125
+ * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).
126
+ * A narrower one fails to compile here.
127
+ *
128
+ * @param key - Also the channel the plugin logs on. Reserved channel names throw.
129
+ * @param args - Whatever your constructor takes after the host.
130
+ * @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.
131
+ * @example
132
+ * ```ts
133
+ * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });
134
+ * ```
135
+ */
136
+ attach<Key extends string, Ctor extends PluginCtor>(this: this, key: ChannelKeyAssert<Key>, Plugin: Ctor & TransportAssert<InstanceType<Ctor>, BotT> & RuntimeAssert<InstanceType<Ctor>, BotRt> & CoreParamAssert<Ctor>, ...args: PluginArgs<Ctor>): this & Record<Key, InstanceType<Ctor>>;
137
+ private registerPluginTasks;
138
+ private runInits;
139
+ private registerReadyTask;
140
+ private registerDisposeTask;
141
+ private runDisposals;
142
+ private disposeCompleted;
143
+ private rollback;
144
+ }
145
+ declare function shutdownOf(host: Pick<Pluggable<Transport, Runtime>, typeof HostShutdown>): CoordinatedShutdown;
146
+ //#endregion
147
+ export { LifecycleTask as a, CoordinatedShutdown as i, shutdownOf as n, CoordinatedStartup as r, Pluggable as t };
148
+ //# sourceMappingURL=Pluggable-DFNUIlJM.d.mts.map
@@ -2,7 +2,7 @@ import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
2
2
  import { t as getDevChannel } from "./devChannel-CQU7hq7Q.mjs";
3
3
  import { SeedcordErrorCode } from "@seedcord/errors";
4
4
  import { Logger } from "@seedcord/logger";
5
- import { SeedcordError } from "@seedcord/errors/internal";
5
+ import { SeedcordError, SeedcordRangeError } from "@seedcord/errors/internal";
6
6
 
7
7
  //#region src/plugin/lifecycle.ts
8
8
  const DEFAULT_INIT_TIMEOUT_MS = 15e3;
@@ -10,7 +10,11 @@ const DEFAULT_READY_TIMEOUT_MS = 15e3;
10
10
  const DEFAULT_DISPOSE_TIMEOUT_MS = 1e4;
11
11
  function timeoutOf(field, declared, fallback, pluginName) {
12
12
  if (declared === void 0) return fallback;
13
- if (!Number.isFinite(declared) || declared <= 0) throw new SeedcordError(SeedcordErrorCode.PluginOptionsRejected, [pluginName, `lifecycle ${field}.timeout must be a positive number of milliseconds`]);
13
+ if (!Number.isFinite(declared) || declared <= 0) throw new SeedcordRangeError(SeedcordErrorCode.PluginInvalidLifecycleTimeout, [
14
+ pluginName,
15
+ field,
16
+ declared
17
+ ]);
14
18
  return declared;
15
19
  }
16
20
  /** @internal */
@@ -111,4 +115,4 @@ function pluginLoggerOf(plugin) {
111
115
 
112
116
  //#endregion
113
117
  export { pluginLoggerOf as n, resolvedLifecycleSpecOf as r, Plugin as t };
114
- //# sourceMappingURL=Plugin-D9ykXA5g.mjs.map
118
+ //# sourceMappingURL=Plugin-C6oMqWf8.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Plugin-C6oMqWf8.mjs","names":[],"sources":["../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '#src/lifecycle/phases';\n\n/**\n * Sets when a plugin's lifecycle methods run. Pass it as the second argument to `super()`. Omit a\n * field to keep its default.\n *\n * @see {@link StartupPhase}\n * @see {@link ShutdownPhase}\n * @example\n * ```ts\n * super(host, { init: { phase: StartupPhase.Login }, dispose: { timeout: 30_000 } });\n * ```\n */\nexport interface PluginLifecycleSpec {\n /**\n * Defaults to `StartupPhase.Configuration` and 15000ms. Set the phase to `Ready` and `init()`\n * runs ahead of every plugin's `ready()`.\n */\n init?: { phase?: StartupPhase; timeout?: number };\n /** `ready()` always runs in `StartupPhase.Ready`. Defaults to a 15000ms timeout. */\n ready?: { timeout?: number };\n /** Defaults to `ShutdownPhase.Disconnect` and 10000ms. */\n dispose?: { phase?: ShutdownPhase; timeout?: number };\n}\n\n/** @internal */\nexport interface ResolvedPluginLifecycleSpec {\n readonly init: { readonly phase: StartupPhase; readonly timeout: number };\n readonly ready: { readonly timeout: number };\n readonly dispose: { readonly phase: ShutdownPhase; readonly timeout: number };\n}\n\nconst DEFAULT_INIT_TIMEOUT_MS = 15_000;\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst DEFAULT_DISPOSE_TIMEOUT_MS = 10_000;\n\nfunction timeoutOf(\n field: 'init' | 'ready' | 'dispose',\n declared: number | undefined,\n fallback: number,\n pluginName: string\n): number {\n if (declared === undefined) return fallback;\n if (!Number.isFinite(declared) || declared <= 0) {\n throw new SeedcordRangeError(SeedcordErrorCode.PluginInvalidLifecycleTimeout, [pluginName, field, declared]);\n }\n return declared;\n}\n\n/** @internal */\nexport function resolveLifecycleSpec(\n spec: PluginLifecycleSpec | undefined,\n pluginName: string\n): ResolvedPluginLifecycleSpec {\n return {\n init: {\n phase: spec?.init?.phase ?? StartupPhase.Configuration,\n timeout: timeoutOf('init', spec?.init?.timeout, DEFAULT_INIT_TIMEOUT_MS, pluginName)\n },\n ready: {\n timeout: timeoutOf('ready', spec?.ready?.timeout, DEFAULT_READY_TIMEOUT_MS, pluginName)\n },\n dispose: {\n phase: spec?.dispose?.phase ?? ShutdownPhase.Disconnect,\n timeout: timeoutOf('dispose', spec?.dispose?.timeout, DEFAULT_DISPOSE_TIMEOUT_MS, pluginName)\n }\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { getDevChannel } from '#hmr/devChannel';\n\nimport { RuntimeBrand, TransportBrand } from './brands';\nimport { resolveLifecycleSpec } from './lifecycle';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ResolvedPluginLifecycleSpec, PluginLifecycleSpec } from './lifecycle';\nimport type { TransportOf, PluginOptions, RuntimeOf } from './options';\nimport type { Tail, HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\nexport interface Initializeable {\n init(): Promise<void>;\n}\n\n/** @internal */\nconst resolvedSpecSlot = Symbol('seedcord.plugin.spec');\n/** @internal */\nconst loggerSlot = Symbol('seedcord.plugin.logger');\n\n/**\n * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running\n * bot.\n *\n * Your constructor takes the host first and passes it to `super()`. Add your own parameters after\n * it. `attach()` types them at the call site.\n *\n * @typeParam Opts - Where this plugin may attach, checked only at compile time.\n * @typeParam TCore - The `Core` each transport binds `this.core` to.\n *\n * @example\n * ```ts\n * class Analytics extends Plugin<{ transport: 'gateway' }> {\n * constructor(\n * host: CoreBase,\n * private readonly apiKey: string\n * ) {\n * super(host, { init: { phase: StartupPhase.Login } });\n * }\n *\n * public async init(): Promise<void> {\n * await this.connect(this.apiKey);\n * }\n * }\n *\n * seedcord.attach('analytics', Analytics, apiKey);\n * ```\n */\nexport abstract class Plugin<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase>\n implements Initializeable, HmrAware\n{\n // phantom, never set at runtime.\n /** @internal */\n declare readonly [TransportBrand]?: TransportOf<Opts>;\n /** @internal */\n declare readonly [RuntimeBrand]?: RuntimeOf<Opts>;\n\n /** @internal */\n readonly [resolvedSpecSlot]: ResolvedPluginLifecycleSpec;\n /** @internal */\n readonly [loggerSlot]: Logger;\n\n /** Logs under the plugin's class name, on the channel its attach key sets. */\n protected readonly logger: Logger;\n\n // CoreBase here keeps the augmented Core out of ConstructorParameters, which attach reads\n constructor(\n private readonly host: CoreBase,\n spec?: PluginLifecycleSpec\n ) {\n this.logger = new Logger(this.constructor.name);\n this[loggerSlot] = this.logger;\n this[resolvedSpecSlot] = resolveLifecycleSpec(spec, this.constructor.name);\n }\n\n /** The host, typed to the transport whose `Plugin` base this class extends. */\n protected get core(): TCore {\n // justified: each transport binds TCore to the Core its own host satisfies\n return this.host as TCore;\n }\n\n /**\n * Runs in `StartupPhase.Configuration` by default.\n * Move it to a different phase with the {@link PluginLifecycleSpec}.\n */\n abstract init(): Promise<void>;\n\n /**\n * Runs in the Ready phase, after every attached plugin's `init()` has resolved. The other Ready\n * tasks run alongside it, including the http server binding its port.\n *\n * Override it when your work needs a logged-in client or a resolved application id.\n */\n ready?(): Promise<void>;\n\n /** Runs during teardown, in `ShutdownPhase.Disconnect` by default. It never runs when `init()` has thrown. */\n dispose?(): Promise<void>;\n\n /**\n * Throws a `SeedcordError` that contains the plugin class name and the reason.\n *\n * @param reason - Why the options are invalid.\n */\n protected rejectOptions(reason: string): never {\n throw new SeedcordError(SeedcordErrorCode.PluginOptionsRejected, [this.constructor.name, reason]);\n }\n\n /** Override to reload plugin state on an HMR update. */\n public onHmr(_event: HmrUpdateEvent): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.\n * @param patterns - Glob patterns relative to the project root\n */\n protected registerCriticalFiles(patterns: string[]): void {\n getDevChannel()?.send('seedcord:register-critical-files', { patterns });\n }\n}\n\n/** @internal */\nexport function resolvedLifecycleSpecOf(plugin: PluginLike): ResolvedPluginLifecycleSpec {\n return plugin[resolvedSpecSlot];\n}\n\n/** @internal */\nexport function pluginLoggerOf(plugin: PluginLike): Logger {\n return plugin[loggerSlot];\n}\n\nexport type { PluginLifecycleSpec } from './lifecycle';\nexport type { PluginOptions } from './options';\n\n// a bound of Plugin<{}> rejects every plugin that declares an option, since Plugin<A> and Plugin<B>\n// are mutually unassignable. every member here must stay Opts-independent.\n/** @internal */\nexport type PluginLike = Pick<\n Plugin,\n 'init' | 'ready' | 'dispose' | 'onHmr' | typeof resolvedSpecSlot | typeof loggerSlot\n>;\n\n// a `CoreBase` first parameter rejects a narrowing ctor on its own, and it also collapses\n// `InstanceType<Ctor>` to `PluginLike` at every attach call. CoreParamAssert checks it instead.\n/** @internal */\nexport type PluginCtor<TPlugin extends PluginLike = PluginLike> = new (...args: any[]) => TPlugin;\n\n/** @internal */\nexport type PluginArgs<Ctor extends PluginCtor> = Tail<ConstructorParameters<Ctor>>;\n\ntype CoreParamTooNarrow = Record<\n 'this plugin constructor must take CoreBase as its first parameter and read the transport Core off this.core',\n never\n>;\n\n// a narrowed first parameter fails `CoreBase extends Param`\n/** @internal */\nexport type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0]\n ? unknown\n : CoreParamTooNarrow;\n"],"mappings":";;;;;;;AAmCA,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;AACjC,MAAM,6BAA6B;AAEnC,SAAS,UACL,OACA,UACA,UACA,YACM;CACN,IAAI,aAAa,QAAW,OAAO;CACnC,IAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,GAC1C,MAAM,IAAI,mBAAmB,kBAAkB,+BAA+B;EAAC;EAAY;EAAO;CAAQ,CAAC;CAE/G,OAAO;AACX;;AAGA,SAAgB,qBACZ,MACA,YAC2B;CAC3B,OAAO;EACH,MAAM;GACF,OAAO,MAAM,MAAM;GACnB,SAAS,UAAU,QAAQ,MAAM,MAAM,SAAS,yBAAyB,UAAU;EACvF;EACA,OAAO,EACH,SAAS,UAAU,SAAS,MAAM,OAAO,SAAS,0BAA0B,UAAU,EAC1F;EACA,SAAS;GACL,OAAO,MAAM,SAAS;GACtB,SAAS,UAAU,WAAW,MAAM,SAAS,SAAS,4BAA4B,UAAU;EAChG;CACJ;AACJ;;;;;ACnDA,MAAM,mBAAmB,OAAO,sBAAsB;;AAEtD,MAAM,aAAa,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BlD,IAAsB,SAAtB,MAEA;CAiByB;;CATrB,CAAU;;CAEV,CAAU;;CAGV,AAAmB;CAGnB,YACI,AAAiB,MACjB,MACF;EAFmB;EAGjB,KAAK,SAAS,IAAI,OAAO,KAAK,YAAY,IAAI;EAC9C,KAAK,cAAc,KAAK;EACxB,KAAK,oBAAoB,qBAAqB,MAAM,KAAK,YAAY,IAAI;CAC7E;;CAGA,IAAc,OAAc;EAExB,OAAO,KAAK;CAChB;;;;;;CAwBA,AAAU,cAAc,QAAuB;EAC3C,MAAM,IAAI,cAAc,kBAAkB,uBAAuB,CAAC,KAAK,YAAY,MAAM,MAAM,CAAC;CACpG;;CAGA,AAAO,MAAM,QAAuC;EAChD,OAAO,QAAQ,QAAQ;CAC3B;;;;;CAMA,AAAU,sBAAsB,UAA0B;EACtD,cAAc,CAAC,EAAE,KAAK,oCAAoC,EAAE,SAAS,CAAC;CAC1E;AACJ;;AAGA,SAAgB,wBAAwB,QAAiD;CACrF,OAAO,OAAO;AAClB;;AAGA,SAAgB,eAAe,QAA4B;CACvD,OAAO,OAAO;AAClB"}
@@ -1,4 +1,4 @@
1
- import { n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-hBxMCD2V.mjs";
1
+ import { n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-F8yvPd60.mjs";
2
2
  import { Logger } from "@seedcord/logger";
3
3
  import { FrameworkChannel, HmrAware, HmrUpdateEvent, Tail, TypedExclude } from "@seedcord/types";
4
4
  //#region src/plugin/brands.d.ts
@@ -190,4 +190,4 @@ type CoreParamTooNarrow = Record<'this plugin constructor must take CoreBase as
190
190
  type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0] ? unknown : CoreParamTooNarrow;
191
191
  //#endregion
192
192
  export { PluginCtor as a, PluginOptions as c, Transport as d, TransportAssert as f, PluginArgs as i, Runtime as l, Initializeable as n, PluginLike as o, PluginLifecycleSpec as p, Plugin as r, ChannelKeyAssert as s, CoreParamAssert as t, RuntimeAssert as u };
193
- //# sourceMappingURL=Plugin-CQ9IEj5X.d.mts.map
193
+ //# sourceMappingURL=Plugin-CG9pWbPF.d.mts.map
@@ -1,9 +1,9 @@
1
1
  import { t as Constructor } from "./basic-Dng__Ena.mjs";
2
- import { g as ReplyTelemetry, h as Notice, k as InteractionRoutes, r as CoreBase, v as AckState } from "./phases-hBxMCD2V.mjs";
2
+ import { b as Notice, r as CoreBase, z as InteractionRoutes } from "./phases-F8yvPd60.mjs";
3
3
  import "reflect-metadata";
4
4
  import { ActionRowBuilder, ButtonBuilder, ChannelSelectMenuBuilder, CheckboxBuilder, CheckboxGroupBuilder, CheckboxGroupOptionBuilder, ContainerBuilder, ContextMenuCommandBuilder, EmbedBuilder, FileBuilder, FileUploadBuilder, LabelBuilder, MediaGalleryBuilder, MentionableSelectMenuBuilder, ModalBuilder, RadioGroupBuilder, RadioGroupOptionBuilder, RoleSelectMenuBuilder, SectionBuilder, SeparatorBuilder, SlashCommandBuilder, SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextDisplayBuilder, TextInputBuilder, UserSelectMenuBuilder } from "@discordjs/builders";
5
- import { APIMessageTopLevelComponent, APIModalInteractionResponseCallbackData, ApplicationCommandType, ChannelType, Snowflake } from "discord-api-types/v10";
6
- import { DeferOpts, ReplyResponse, SendOpts } from "@seedcord/types";
5
+ import { APIMessageTopLevelComponent, ApplicationCommandType, ChannelType, Snowflake } from "discord-api-types/v10";
6
+ import { ReplyResponse } from "@seedcord/types";
7
7
  //#region src/registries/ContextMenuRegistry.d.ts
8
8
  /** The two context-menu command kinds Discord defines. */
9
9
  type ContextMenuKind = ApplicationCommandType.User | ApplicationCommandType.Message;
@@ -363,6 +363,8 @@ interface CustomIdField<Decoded> {
363
363
  readonly max?: number;
364
364
  /** The allowed values, for a oneOf field. */
365
365
  readonly choices?: readonly string[];
366
+ /** Whether this field also carries null. */
367
+ readonly nullable?: boolean;
366
368
  /** Phantom only, carries the decoded type and is never set at runtime. */
367
369
  readonly decoded?: Decoded;
368
370
  }
@@ -380,6 +382,16 @@ type CustomIdShape = Record<string, CustomIdField<unknown>>;
380
382
  type DecodedParams<Shape extends CustomIdShape> = { [Name in keyof Shape]: Shape[Name] extends CustomIdField<infer Decoded> ? Decoded : never; };
381
383
  //#endregion
382
384
  //#region src/customId/CustomId.d.ts
385
+ /** Per-field options every {@link CustomId} chain method takes. */
386
+ interface FieldOptions<Nullable extends boolean = boolean> {
387
+ /**
388
+ * Whether the field also carries null. A nullable field costs one extra slot on the wire.
389
+ *
390
+ * @defaultValue `false`
391
+ */
392
+ nullable?: Nullable;
393
+ }
394
+ type Nullish<Decoded, Nullable extends boolean> = Nullable extends true ? Decoded | null : Decoded;
383
395
  /** Strip the layout hash off the routeKey to recover the stable prefix the controller routes by. @internal */
384
396
  declare function prefixOf(wire: string): string;
385
397
  /**
@@ -421,7 +433,7 @@ declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}>
421
433
  * new CustomId('ban').snowflake('userId');
422
434
  * ```
423
435
  */
424
- snowflake<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<Snowflake>>>;
436
+ snowflake<Name extends string, const Nullable extends boolean = false>(name: Name, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<Snowflake, Nullable>>>>;
425
437
  /**
426
438
  * Add a UUID field, decoded as a lowercase uuid string.
427
439
  *
@@ -430,7 +442,7 @@ declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}>
430
442
  * new CustomId('ticket').uuid('ticketId');
431
443
  * ```
432
444
  */
433
- uuid<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<string>>>;
445
+ uuid<Name extends string, const Nullable extends boolean = false>(name: Name, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<string, Nullable>>>>;
434
446
  /**
435
447
  * Add an integer field with no bounds, for a value up to 2^53.
436
448
  *
@@ -439,7 +451,7 @@ declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}>
439
451
  * new CustomId('shop').int('amount');
440
452
  * ```
441
453
  */
442
- int<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<number>>>;
454
+ int<Name extends string, const Nullable extends boolean = false>(name: Name, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<number, Nullable>>>>;
443
455
  /**
444
456
  * Add an integer field bounded by min and max, so it packs into fewer characters on the wire.
445
457
  *
@@ -448,7 +460,7 @@ declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}>
448
460
  * new CustomId('paginate').int('page', 1, 50);
449
461
  * ```
450
462
  */
451
- int<Name extends string>(name: Name, min: number, max: number): CustomId<Prefix, Shape & Record<Name, CustomIdField<number>>>;
463
+ int<Name extends string, const Nullable extends boolean = false>(name: Name, min: number, max: number, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<number, Nullable>>>>;
452
464
  /**
453
465
  * Add a boolean flag.
454
466
  *
@@ -457,7 +469,7 @@ declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}>
457
469
  * new CustomId('settings').bool('silent');
458
470
  * ```
459
471
  */
460
- bool<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<boolean>>>;
472
+ bool<Name extends string, const Nullable extends boolean = false>(name: Name, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<boolean, Nullable>>>>;
461
473
  /**
462
474
  * Add a field that is one value from a fixed list, decoded as the literal union. No `as const` needed.
463
475
  *
@@ -466,7 +478,7 @@ declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}>
466
478
  * new CustomId('poll').oneOf('choice', ['yes', 'no', 'abstain']);
467
479
  * ```
468
480
  */
469
- oneOf<Name extends string, const Choices extends NonEmptyTuple<string>>(name: Name, choices: Choices): CustomId<Prefix, Shape & Record<Name, CustomIdField<Choices[number]>>>;
481
+ oneOf<Name extends string, const Choices extends NonEmptyTuple<string>, const Nullable extends boolean = false>(name: Name, choices: Choices, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<Choices[number], Nullable>>>>;
470
482
  /**
471
483
  * Add a free short text field. Avoid it where possible. It cannot be packed, which makes it the most
472
484
  * expensive field on the wire.
@@ -476,7 +488,7 @@ declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}>
476
488
  * new CustomId('note').str('message');
477
489
  * ```
478
490
  */
479
- str<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<string>>>;
491
+ str<Name extends string, const Nullable extends boolean = false>(name: Name, opts?: FieldOptions<Nullable>): CustomId<Prefix, Shape & Record<Name, CustomIdField<Nullish<string, Nullable>>>>;
480
492
  /**
481
493
  * Mint a wire string from values. Throws if a value is out of its field's range or the wire is over 100 chars.
482
494
  *
@@ -548,65 +560,6 @@ interface SerializedReply<TNative = never> {
548
560
  }
549
561
  declare function serializeReply<TNative>(response: ReplyResponse<TNative> | string, routeId: string): SerializedReply<TNative>;
550
562
  //#endregion
551
- //#region src/reply/BaseReplySender.d.ts
552
- /** The shape djs modal builders emit from `toJSON()`. */
553
- interface ModalLike {
554
- toJSON(): APIModalInteractionResponseCallbackData;
555
- }
556
- /**
557
- * Tracks acknowledgement state and throws a translated `SeedcordError` on an illegal verb before any
558
- * transport call. Every verb runs the legality check, calls the transport's wire writer, then updates
559
- * the state and the ack trace. Each transport binds `TMessage` to its own created-message type and
560
- * supplies the writers.
561
- */
562
- declare abstract class BaseReplySender<TMessage extends {
563
- id: string;
564
- }, TNative = never> {
565
- protected readonly routeId: string;
566
- private readonly telemetry;
567
- private state;
568
- private readonly sent;
569
- private ackedBy?;
570
- protected constructor(routeId: string, telemetry: ReplyTelemetry, initialState?: AckState);
571
- private report;
572
- private attempt;
573
- reply(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage>;
574
- defer(opts?: DeferOpts): Promise<void>;
575
- deferUpdate(): Promise<void>;
576
- /**
577
- * After a deferUpdate the state stays deferred-update. Calling this again rewrites. The returned message
578
- * is the source message, editable only through a bare `update` or `edit`. A targeted `edit` or `delete`
579
- * of it throws the foreign-target error.
580
- */
581
- update(response: ReplyResponse<TNative> | string): Promise<TMessage>;
582
- followUp(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage>;
583
- edit(response: ReplyResponse<TNative> | string): Promise<TMessage>;
584
- edit(target: TMessage, response: ReplyResponse<TNative> | string): Promise<TMessage>;
585
- delete(): Promise<void>;
586
- delete(target: TMessage): Promise<void>;
587
- /** Routes to the verb the current ack state permits. Every state has a route, so the illegal-ack throw is unreachable. */
588
- send(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage>;
589
- /** Must be the initial response. */
590
- showModal(modal: ModalLike): Promise<void>;
591
- protected serialize(response: ReplyResponse<TNative> | string): SerializedReply<TNative>;
592
- protected remember(created: TMessage): TMessage;
593
- private requireMessage;
594
- private editOriginal;
595
- private checkLegality;
596
- private transition;
597
- protected guardModalSource(): void;
598
- protected abstract writeReply(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage | undefined>;
599
- protected abstract writeDefer(opts?: DeferOpts): Promise<void>;
600
- protected abstract writeDeferUpdate(): Promise<void>;
601
- protected abstract writeUpdate(response: ReplyResponse<TNative> | string): Promise<TMessage | undefined>;
602
- protected abstract writeFollowUp(response: ReplyResponse<TNative> | string, opts?: SendOpts): Promise<TMessage>;
603
- protected abstract writeEditOriginal(response: ReplyResponse<TNative> | string): Promise<TMessage>;
604
- protected abstract writeEditTarget(targetId: string, response: ReplyResponse<TNative> | string): Promise<TMessage>;
605
- protected abstract writeDeleteOriginal(): Promise<void>;
606
- protected abstract writeDeleteTarget(targetId: string): Promise<void>;
607
- protected abstract writeModal(data: APIModalInteractionResponseCallbackData): Promise<void>;
608
- }
609
- //#endregion
610
563
  //#region src/gates/Gate.d.ts
611
564
  /**
612
565
  * The transport-agnostic gate context, the scalar identity of the acting user resolvable from any
@@ -821,7 +774,8 @@ type RequiredOf<TGate> = TGate extends Gate<infer Ctx> ? Ctx : never;
821
774
  type IntersectRequired<Gates extends readonly Gate<GateContextBase>[]> = Gates extends readonly [infer First extends Gate<GateContextBase>, ...infer Rest extends readonly Gate<GateContextBase>[]] ? RequiredOf<First> & IntersectRequired<Rest> : unknown;
822
775
  type TwoOrMore<Item> = readonly [Item, Item, ...Item[]];
823
776
  type GateName<TGate> = TGate extends Gate<GateContextBase, infer Name> ? Name : string;
824
- type JoinNames<Gates extends readonly Gate<GateContextBase>[], Sep extends string> = Gates extends readonly [infer Only extends Gate<GateContextBase>] ? GateName<Only> : Gates extends readonly [infer First extends Gate<GateContextBase>, ...infer Rest extends readonly Gate<GateContextBase>[]] ? `${GateName<First>}${Sep}${JoinNames<Rest, Sep>}` : string;
777
+ type Grouped<Name extends string> = Name extends `${string} & ${string}` | `${string} | ${string}` ? `(${Name})` : Name;
778
+ type JoinNames<Gates extends readonly Gate<GateContextBase>[], Sep extends string> = Gates extends readonly [infer Only extends Gate<GateContextBase>] ? Grouped<GateName<Only>> : Gates extends readonly [infer First extends Gate<GateContextBase>, ...infer Rest extends readonly Gate<GateContextBase>[]] ? `${Grouped<GateName<First>>}${Sep}${JoinNames<Rest, Sep>}` : string;
825
779
  type GateMismatch<Name extends string, Want extends string, Got extends string> = `gate '${Name}' requires a ${Want} handler, and this handler is ${Got}`;
826
780
  type GateFitsWith<Provided, ProvidedLabel extends string, TGate, ReqLabel extends string> = [Provided] extends [RequiredOf<TGate>] ? TGate : Constructor<[GateMismatch<GateName<TGate>, ReqLabel, ProvidedLabel>]>;
827
781
  //#endregion
@@ -1029,5 +983,5 @@ type PageCursor<Prefix extends string> = CustomId<Prefix, {
1029
983
  /** Build the page cursor for a paginator. */
1030
984
  declare function pageCursor<Prefix extends string>(prefix: Prefix): PageCursor<Prefix>;
1031
985
  //#endregion
1032
- export { RegisterCommand as $, ModalLike as A, SelectMenuKind as B, Gate as C, defineEffectGate as D, RequiredOf as E, ComponentKindBrand as F, storeComponentRoute as G, contextMenuRouteOf as H, ContextMenuKindBrand as I, CustomId as J, storeInteractionRoute as K, ContextMenuNamesBrand as L, serializeReply as M, AutocompleteRouteBrand as N, defineGate as O, ComponentDefsBrand as P, CommandMeta as Q, SlashRouteBrand as R, EffectGate as S, GuildPermissionsContext as T, interactionRoutesOf as U, areRoutes as V, selectMenuRouteOf as W, prefixOf as X, decodeFor as Y, DecodedParams as Z, pickNotice as _, AutocompleteOptions as a, RowType as at, JoinNames as b, FocusedField as c, RouteCache as ct, RequirePermissionsOptions as d, SlashRouteEntry as dt, Promisable as et, RequireRole as f, ContextMenuKind as ft, GateNoticeOptions as g, UserContextMenuRegistry as gt, PermissionScope as h, NamesFor as ht, AutocompletableNames as i, BuilderType as it, SerializedReply as j, BaseReplySender as k, RequireBotPermissions as l, SlashOption as lt, PermissionNames as m, MessageContextMenuRegistry as mt, PageCursor as n, BuilderComponent as nt, ChoiceValueOf as o, CacheFor as ot, RequireRoleOptions as p, MenuCacheFor as pt, AnyCustomId as q, pageCursor as r, RowComponent as rt, EntryFor as s, OptionKind as st, PAGE_MAX as t, IsNever as tt, RequirePermissions as u, SlashRegistry as ut, GateFitsWith as v, GateContextBase as w, TwoOrMore as x, IntersectRequired as y, RoutableConstructor as z };
1033
- //# sourceMappingURL=cursor-BANzac53.d.mts.map
986
+ export { Promisable as $, serializeReply as A, contextMenuRouteOf as B, Gate as C, defineEffectGate as D, RequiredOf as E, ContextMenuNamesBrand as F, AnyCustomId as G, selectMenuRouteOf as H, SlashRouteBrand as I, decodeFor as J, CustomId as K, RoutableConstructor as L, ComponentDefsBrand as M, ComponentKindBrand as N, defineGate as O, ContextMenuKindBrand as P, RegisterCommand as Q, SelectMenuKind as R, EffectGate as S, GuildPermissionsContext as T, storeComponentRoute as U, interactionRoutesOf as V, storeInteractionRoute as W, DecodedParams as X, prefixOf as Y, CommandMeta as Z, pickNotice as _, AutocompleteOptions as a, CacheFor as at, JoinNames as b, FocusedField as c, SlashOption as ct, RequirePermissionsOptions as d, ContextMenuKind as dt, IsNever as et, RequireRole as f, MenuCacheFor as ft, GateNoticeOptions as g, PermissionScope as h, UserContextMenuRegistry as ht, AutocompletableNames as i, RowType as it, AutocompleteRouteBrand as j, SerializedReply as k, RequireBotPermissions as l, SlashRegistry as lt, PermissionNames as m, NamesFor as mt, PageCursor as n, RowComponent as nt, ChoiceValueOf as o, OptionKind as ot, RequireRoleOptions as p, MessageContextMenuRegistry as pt, FieldOptions as q, pageCursor as r, BuilderType as rt, EntryFor as s, RouteCache as st, PAGE_MAX as t, BuilderComponent as tt, RequirePermissions as u, SlashRouteEntry as ut, GateFitsWith as v, GateContextBase as w, TwoOrMore as x, IntersectRequired as y, areRoutes as z };
987
+ //# sourceMappingURL=cursor-BcRfmTSo.d.mts.map