@seedcord/core 0.1.0-next.1 → 0.1.0-next.3

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.
package/dist/index.d.mts CHANGED
@@ -1,10 +1,12 @@
1
- import { c as RowComponent, l as BuilderType, n as CustomId, s as BuilderComponent, u as RowType } from "./CustomId-BJF6mbJs.mjs";
2
- import { RenderContext, ReplyResponse } from "@seedcord/types";
1
+ import { C as BuilderComponent, E as RowType, S as RegisterCommand, T as BuilderType, _ as RequiredOf, b as CoreBase, d as JoinNames, f as TwoOrMore, g as GateContextBase, h as Gate, m as EffectGate, n as CustomId, p as Notice, s as GateNoticeOptions, u as IntersectRequired, v as defineEffectGate, w as RowComponent, y as defineGate } from "./CustomId--QizzBOn.mjs";
2
+ import { ValidDuration } from "@seedcord/utils";
3
+ import { ChannelType } from "discord-api-types/v10";
4
+ import { EpochMs, RenderContext, ReplyResponse } from "@seedcord/types";
3
5
 
4
6
  //#region src/registries/ContextMenuRegistry.d.ts
5
7
  /**
6
8
  * Maps each user context-menu command name to a presence marker. Populated by `seedcord codegen`, which
7
- * reads every command's `toJSON()` and emits a `declare module 'seedcord'` augmentation, so
9
+ * reads every command's `toJSON()` and emits a `declare module '@seedcord/gateway'` augmentation, so
8
10
  * `keyof UserContextMenuRegistry` is the compile-time union of every registered user context-menu name.
9
11
  * Do not augment it by hand, run `seedcord codegen`.
10
12
  *
@@ -14,7 +16,7 @@ import { RenderContext, ReplyResponse } from "@seedcord/types";
14
16
  * @example
15
17
  * ```ts
16
18
  * // seedcord-gen.d.ts (emitted, committed)
17
- * declare module 'seedcord' {
19
+ * declare module '@seedcord/gateway' {
18
20
  * interface UserContextMenuRegistry {
19
21
  * 'View Profile': true;
20
22
  * }
@@ -32,7 +34,7 @@ interface UserContextMenuRegistry {}
32
34
  interface MessageContextMenuRegistry {}
33
35
  //#endregion
34
36
  //#region src/registries/SlashOptionRegistry.d.ts
35
- /** A slash option kind, named for the registry rather than the raw discord.js `ApplicationCommandOptionType` int. */
37
+ /** A slash option kind as a plain string union (`string`, `channel`, ...), keyed to the registry. */
36
38
  type OptionKind = 'string' | 'integer' | 'number' | 'boolean' | 'user' | 'channel' | 'role' | 'mentionable' | 'attachment';
37
39
  /** The type-relevant shape of one slash option, as emitted by `seedcord codegen` into a registry row. */
38
40
  interface SlashOption {
@@ -40,16 +42,17 @@ interface SlashOption {
40
42
  required: boolean;
41
43
  choices?: readonly (string | number)[];
42
44
  autocomplete?: true;
45
+ channelTypes?: readonly ChannelType[];
43
46
  }
44
47
  /**
45
48
  * Maps each slash route string to its option table. Populated by `seedcord codegen`, which reads every
46
- * command's `toJSON()` and emits a `declare module 'seedcord'` augmentation, so `keyof SlashOptionRegistry`
49
+ * command's `toJSON()` and emits a `declare module '@seedcord/gateway'` augmentation, so `keyof SlashOptionRegistry`
47
50
  * is the compile-time union of every registered route. Do not augment it by hand, run `seedcord codegen`.
48
51
  *
49
52
  * @example
50
53
  * ```ts
51
54
  * // seedcord-gen.d.ts (emitted, committed)
52
- * declare module 'seedcord' {
55
+ * declare module '@seedcord/gateway' {
53
56
  * interface SlashOptionRegistry {
54
57
  * ban: { target: { kind: 'user'; required: true }; reason: { kind: 'string'; required: false } };
55
58
  * 'demo/setup': { channel: { kind: 'channel'; required: true } };
@@ -59,71 +62,250 @@ interface SlashOption {
59
62
  */
60
63
  interface SlashOptionRegistry {}
61
64
  //#endregion
62
- //#region src/stops/Notice.d.ts
65
+ //#region src/dispatch/DispatchContext.d.ts
63
66
  /**
64
- * Base class for a user-facing refusal or a reported fault.
67
+ * The per-dispatch bag. Any code can merge fields into it by declaration merging, the
68
+ * same pattern as {@link SlashOptionRegistry}. It stays empty until one does, so `keyof DispatchState` is
69
+ * `never` and the bag is inert.
70
+ */
71
+ interface DispatchState {}
72
+ /**
73
+ * A typed bag allocated once per interaction dispatch and handed to the handler. The middleware and i18n
74
+ * work reads and writes it through {@link DispatchState}. Today the bag is empty, so it only carries the
75
+ * dispatched `routeId`.
76
+ */
77
+ declare class DispatchContext {
78
+ readonly routeId: string | null;
79
+ private readonly state;
80
+ constructor(routeId: string | null);
81
+ set<Key extends keyof DispatchState>(key: Key, value: DispatchState[Key]): void;
82
+ get<Key extends keyof DispatchState>(key: Key): DispatchState[Key] | undefined;
83
+ }
84
+ //#endregion
85
+ //#region src/gates/combinators.d.ts
86
+ /**
87
+ * Runs each gate in order and refuses on the first refusal. Takes two or more arms. The required
88
+ * context is the intersection of the arms, so an event-only and an interaction-only gate cannot be
89
+ * combined.
65
90
  *
66
- * Throw a `Notice` to stop a handler and reply to the user. The framework catches it at the controller
67
- * boundary and renders {@link Notice.render}, which always decides what the user sees. With `report`
68
- * false that render is all that happens. With `report` true the framework also logs the fault and
69
- * publishes it to the `handledException` bus. A raw, non-Notice throw shows the generic message.
91
+ * @typeParam Gates - The tuple of two or more gate arms, run left to right.
92
+ * @param gates - The gate arms to run in order, all refused on the first refusal.
93
+ *
94
+ * @see {@link or}
70
95
  *
71
96
  * @example
72
97
  * ```ts
73
- * import { Notice, BuilderComponent, type RenderContext, type ReplyResponse } from 'seedcord';
74
- * import { TextDisplayBuilder } from '@discordjs/builders';
98
+ * import { PermissionFlagsBits } from 'discord.js';
75
99
  *
76
- * // reading `.component` applies the configured bot color to the container accent
77
- * class TooPoorCard extends BuilderComponent<'container'> {
78
- * constructor(balance: number) {
79
- * super('container');
80
- * this.instance.addTextDisplayComponents(
81
- * new TextDisplayBuilder().setContent(`### Insufficient balance\nYou need more than ${balance} coins.`)
82
- * );
100
+ * \@Gated(and(GuildOnly(), RequirePermissions([PermissionFlagsBits.BanMembers])))
101
+ * \@SlashRoute('ban')
102
+ * class BanHandler extends SlashHandler<'ban'> {
103
+ * async execute() {
104
+ * // ...
83
105
  * }
84
106
  * }
107
+ * ```
108
+ */
109
+ declare function and<Gates extends TwoOrMore<Gate<GateContextBase>>>(...gates: Gates): Gate<IntersectRequired<Gates> & GateContextBase, JoinNames<Gates, ' & '>>;
110
+ interface OrOptions {
111
+ fail: Notice | ((ctx: GateContextBase) => Notice);
112
+ }
113
+ /**
114
+ * Runs each gate in order and passes on the first arm that passes. Takes two or more arms. The
115
+ * required context is the union of the arms, so a handler that matches any one arm fits. When every
116
+ * arm refuses it throws the trailing {@link OrOptions} `fail` if given, else an auto list of the
117
+ * arms derived from the summary field, else a default refusal. The trailing options object does not
118
+ * count as an arm.
85
119
  *
86
- * class TooPoor extends Notice {
87
- * constructor(private readonly balance: number) {
88
- * super(`balance ${balance} is below the cost`);
120
+ * @typeParam Gates - The tuple of two or more gate arms, tried left to right.
121
+ * @param gates - The gate arms to try in order, with an optional trailing {@link OrOptions} object.
122
+ *
123
+ * @see {@link and}
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * \@Gated(or(OwnerOnly(), RequireRole('123456789012345678')))
128
+ * \@SlashRoute('admin')
129
+ * class AdminHandler extends SlashHandler<'admin'> {
130
+ * async execute() {
131
+ * // ...
89
132
  * }
133
+ * }
134
+ * ```
135
+ */
136
+ declare function or<Gates extends TwoOrMore<Gate<GateContextBase>>>(...gates: Gates): Gate<RequiredOf<Gates[number]>, JoinNames<Gates, ' | '>>;
137
+ /**
138
+ * @example
139
+ * ```ts
140
+ * // custom refusal when every arm refuses
141
+ * or(OwnerOnly(), RequireRole('123456789012345678'), { fail: new NotAllowedNotice() });
142
+ * ```
143
+ */
144
+ declare function or<Gates extends TwoOrMore<Gate<GateContextBase>>>(...args: [...Gates, OrOptions]): Gate<RequiredOf<Gates[number]>, JoinNames<Gates, ' | '>>;
145
+ //#endregion
146
+ //#region src/gates/catalog/access.d.ts
147
+ /**
148
+ * Passes only for a user id listed in `config.ownerIds`, else refuses.
149
+ *
150
+ * Agnostic, so it attaches to interaction and event handlers alike. With no `ownerIds` configured it refuses every caller. Pass {@link GateNoticeOptions} to reword or replace the refusal.
151
+ *
152
+ * @param options - Reword the default refusal with `message`, or replace it with `notice`.
153
+ *
154
+ * @see the `@Gated` decorator from `seedcord`
90
155
  *
91
- * render(_ctx: RenderContext): ReplyResponse {
92
- * return { components: [new TooPoorCard(this.balance).component] };
156
+ * @example
157
+ * ```ts
158
+ * \@Gated(OwnerOnly())
159
+ * \@SlashRoute('shutdown')
160
+ * class ShutdownHandler extends SlashHandler<'shutdown'> {
161
+ * async execute() {
162
+ * // ...
93
163
  * }
94
164
  * }
165
+ * ```
95
166
  *
96
- * // in a handler, throwing stops the handler and replies with render(ctx)
97
- * if (wallet.balance < cost) throw new TooPoor(wallet.balance);
167
+ * @example
168
+ * ```ts
169
+ * // reword the refusal, keeping the embed styling
170
+ * OwnerOnly({ message: 'Owners only.' });
98
171
  * ```
99
172
  */
100
- declare abstract class Notice extends Error {
173
+ declare function OwnerOnly(options?: GateNoticeOptions): Gate<GateContextBase, 'OwnerOnly'>;
174
+ /**
175
+ * Passes inside a guild, else refuses.
176
+ *
177
+ * Agnostic, so it attaches to any handler kind. Often paired with `RequirePermissions` in an {@link and}.
178
+ *
179
+ * @param options - Reword the default refusal with `message`, or replace it with `notice`.
180
+ *
181
+ * @see the `@Gated` decorator from `seedcord`
182
+ *
183
+ * @example
184
+ * ```ts
185
+ * import { PermissionFlagsBits } from 'discord.js';
186
+ *
187
+ * \@Gated(GuildOnly(), RequirePermissions([PermissionFlagsBits.Administrator]))
188
+ * \@SlashRoute('maintenance')
189
+ * class Maintenance extends SlashHandler<'maintenance'> {
190
+ * async execute() {
191
+ * // ...
192
+ * }
193
+ * }
194
+ * ```
195
+ */
196
+ declare function GuildOnly(options?: GateNoticeOptions): Gate<GateContextBase, 'GuildOnly'>;
197
+ /**
198
+ * Passes in a direct message, else refuses.
199
+ *
200
+ * Agnostic, so it attaches to any handler kind. The inverse of {@link GuildOnly}, so combining the two in an {@link and} can never pass.
201
+ *
202
+ * @param options - Reword the default refusal with `message`, or replace it with `notice`.
203
+ *
204
+ * @see the `@Gated` decorator from `seedcord`
205
+ *
206
+ * @example
207
+ * ```ts
208
+ * \@Gated(DmOnly())
209
+ * \@SlashRoute('verify')
210
+ * class VerifyHandler extends SlashHandler<'verify'> {
211
+ * async execute() {
212
+ * // ...
213
+ * }
214
+ * }
215
+ * ```
216
+ */
217
+ declare function DmOnly(options?: GateNoticeOptions): Gate<GateContextBase, 'DmOnly'>;
218
+ //#endregion
219
+ //#region src/gates/catalog/Cooldown.d.ts
220
+ /**
221
+ * Options for {@link Cooldown}. `per` sets the bucket the window applies to and `limit` the uses allowed per
222
+ * window. `message` rewords the refusal and `notice` replaces it, both receiving the epoch ms the key frees
223
+ * up so the refusal can show the retry time.
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * // one use per channel, rewording the refusal with the retry time
228
+ * Cooldown('10s', {
229
+ * per: 'channel',
230
+ * message: (resetAt) => `Slow down. Try again <t:${Math.round(resetAt / 1000)}:R>.`
231
+ * });
232
+ * ```
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * // five uses per minute, per user
237
+ * Cooldown('1m', { limit: 5 });
238
+ * ```
239
+ */
240
+ interface CooldownOptions {
101
241
  /**
102
- * Whether this denial is a reported fault. True also logs it and publishes it to the `handledException`
103
- * bus. The user always sees {@link Notice.render} either way.
242
+ * The bucket the cooldown window applies to. 'user' scopes by user ID, falls back to global when the source carries no user id. 'guild' scopes by guild ID (falls back to global if no guild), and 'channel' scopes by channel ID (falls back to global if no channel).
104
243
  *
105
- * @defaultValue `false`
244
+ * @defaultValue `'user'`
106
245
  */
107
- report: boolean;
246
+ per?: 'user' | 'guild' | 'channel';
108
247
  /**
109
- * Whether the reply is ephemeral, so only the invoking user sees it. Set it false for a refusal the
110
- * whole channel should see.
248
+ * Uses allowed inside one window before the gate refuses.
111
249
  *
112
- * @defaultValue `true`
250
+ * @defaultValue `1`
113
251
  */
114
- ephemeral: boolean;
252
+ limit?: number;
115
253
  /**
116
- * A short one-line reason. When every arm of an `or` gate refuses and each refusal sets this, `or`
117
- * lists them instead of showing a neutral message.
254
+ * Reword the refusal, keeping the standard notice card. Receives the epoch ms the key frees up, so the
255
+ * text can include the retry time with `<t:${Math.round(resetAt / 1000)}:R>`.
118
256
  */
119
- summary?: string;
120
- protected constructor(message: string, options?: ErrorOptions);
257
+ message?: (resetAt: EpochMs) => string;
121
258
  /**
122
- * Builds what the user sees. Called fresh each time the denial is shown, so the builders are new
123
- * and the bot color resolves at render time rather than at construction time.
259
+ * Replace the refusal Notice entirely, for full control or a translated copy. Receives the epoch ms the
260
+ * key frees up.
124
261
  */
125
- abstract render(ctx: RenderContext): ReplyResponse;
262
+ notice?: (resetAt: EpochMs) => Notice;
126
263
  }
264
+ /**
265
+ * Allows `limit` uses per window, scoped by `per`. A number `duration` is **seconds**, a string is
266
+ * a duration like `30m` or `24h`. An unparseable string throws a **SeedcordTypeError** at construction. The
267
+ * slot is charged in commit, only after the whole gate set passes, so a later refusal never burns the cooldown.
268
+ * That split also means two requests racing the same key can both pass before either charges the slot.
269
+ * The key combines the handler's route with the window settings, so two different handlers keep separate
270
+ * windows. Reword the refusal with {@link CooldownOptions.message} or replace it with {@link CooldownOptions.notice}.
271
+ *
272
+ * @param duration - A number is seconds, a string is a duration like `30m` or `24h`. An unparseable string throws a **SeedcordTypeError**.
273
+ * @param options - Sets the scope with `per`, the uses per window with `limit`, and the refusal text with `message` or `notice`.
274
+ *
275
+ * @see the `@Gated` decorator from `seedcord`
276
+ * @see {@link IRateLimiter}
277
+ *
278
+ * @example
279
+ * ```ts
280
+ * // a number argument is SECONDS
281
+ * \@Gated(Cooldown(5))
282
+ * \@SlashRoute('daily')
283
+ * class DailyHandler extends SlashHandler<'daily'> {
284
+ * async execute() {
285
+ * // ...
286
+ * }
287
+ * }
288
+ * ```
289
+ *
290
+ * @example
291
+ * ```ts
292
+ * // a string is a duration, here scoped per guild (the default is per user)
293
+ * Cooldown('30m', { per: 'guild' });
294
+ * ```
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * // a burst, 3 uses per 10 minutes per user, then it refuses until a slot frees
299
+ * \@Gated(Cooldown('10m', { limit: 3 }))
300
+ * \@SlashRoute('report')
301
+ * class ReportHandler extends SlashHandler<'report'> {
302
+ * async execute() {
303
+ * // ...
304
+ * }
305
+ * }
306
+ * ```
307
+ */
308
+ declare function Cooldown(duration: number | ValidDuration, options?: CooldownOptions): EffectGate<GateContextBase, 'Cooldown'>;
127
309
  //#endregion
128
310
  //#region src/stops/Fault.d.ts
129
311
  /**
@@ -140,7 +322,7 @@ declare abstract class Notice extends Error {
140
322
  *
141
323
  * @example
142
324
  * ```ts
143
- * import { Fault } from 'seedcord';
325
+ * import { Fault } from '@seedcord/gateway';
144
326
  *
145
327
  * try {
146
328
  * await db.write(record);
@@ -171,7 +353,7 @@ declare class Fault extends Notice {
171
353
  *
172
354
  * @example
173
355
  * ```ts
174
- * import { Silence } from 'seedcord';
356
+ * import { Silence } from '@seedcord/gateway';
175
357
  *
176
358
  * // before any reply or defer, drop the interaction with no reply and no report
177
359
  * if (await isBlacklisted(interaction.user.id)) throw new Silence('blacklisted user');
@@ -218,5 +400,5 @@ declare function paginate<Item>(items: readonly Item[], page: number, perPage: n
218
400
  /** Package version */
219
401
  declare const version: string;
220
402
  //#endregion
221
- export { BuilderComponent, type BuilderType, CustomId, Fault, type MessageContextMenuRegistry, Notice, type OptionKind, type PageView, RowComponent, type RowType, Silence, type SlashOption, type SlashOptionRegistry, type UserContextMenuRegistry, paginate, version };
403
+ export { BuilderComponent, type BuilderType, Cooldown, type CooldownOptions, type CoreBase, CustomId, DispatchContext, type DispatchState, DmOnly, type EffectGate, Fault, type Gate, type GateContextBase, type GateNoticeOptions, GuildOnly, type MessageContextMenuRegistry, Notice, type OptionKind, OwnerOnly, type PageView, RegisterCommand, type RequiredOf, RowComponent, type RowType, Silence, type SlashOption, type SlashOptionRegistry, type UserContextMenuRegistry, and, defineEffectGate, defineGate, or, paginate, version };
222
404
  //# sourceMappingURL=index.d.mts.map