@seedcord/core 0.1.3 → 0.2.0-next.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 (60) hide show
  1. package/README.md +35 -5
  2. package/dist/{subscribers-nEpbJj3Z.mjs → Bus-DcuUtOEL.mjs} +401 -31
  3. package/dist/Bus-DcuUtOEL.mjs.map +1 -0
  4. package/dist/{HmrModuleHandler-KxRP8lqJ.mjs → HmrModuleHandler-9AUfKXRl.mjs} +2 -2
  5. package/dist/{HmrModuleHandler-KxRP8lqJ.mjs.map → HmrModuleHandler-9AUfKXRl.mjs.map} +1 -1
  6. package/dist/{Plugin-CDAV7P9l.mjs → Plugin-Bkeqcjy6.mjs} +12 -3
  7. package/dist/Plugin-Bkeqcjy6.mjs.map +1 -0
  8. package/dist/{Plugin-qRc7Ch4g.d.mts → Plugin-Cl5P7-PO.d.mts} +19 -7
  9. package/dist/{cursor-D207VrRo.mjs → cursor-B07ArwDz.mjs} +4 -4
  10. package/dist/cursor-B07ArwDz.mjs.map +1 -0
  11. package/dist/{cursor-C77xH5QT.d.mts → cursor-__0_F4rW.d.mts} +10 -3
  12. package/dist/hmr.index.mjs +1 -1
  13. package/dist/{index-C0k7lRDi.d.mts → index-TYnQ-ztr.d.mts} +15 -4
  14. package/dist/index.d.mts +24 -11
  15. package/dist/index.mjs +26 -6
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/internal.index.d.mts +13 -18
  18. package/dist/internal.index.mjs +16 -48
  19. package/dist/internal.index.mjs.map +1 -1
  20. package/dist/node-internal.index.d.mts +6 -5
  21. package/dist/node-internal.index.mjs +54 -49
  22. package/dist/node-internal.index.mjs.map +1 -1
  23. package/dist/plugin.index.d.mts +1 -1
  24. package/dist/plugin.index.mjs +1 -1
  25. package/dist/subscribers-DuHg-bGT.mjs +3 -0
  26. package/package.json +27 -35
  27. package/dist/HmrModuleHandler-CxGOo1LT.cjs +0 -182
  28. package/dist/HmrModuleHandler-CxGOo1LT.cjs.map +0 -1
  29. package/dist/Plugin-CDAV7P9l.mjs.map +0 -1
  30. package/dist/Plugin-DVNh1kvX.cjs +0 -95
  31. package/dist/Plugin-DVNh1kvX.cjs.map +0 -1
  32. package/dist/cursor-D207VrRo.mjs.map +0 -1
  33. package/dist/cursor-DFgJ70_B.cjs +0 -1339
  34. package/dist/cursor-DFgJ70_B.cjs.map +0 -1
  35. package/dist/devChannel-Cdnmz6hd.cjs +0 -25
  36. package/dist/devChannel-Cdnmz6hd.cjs.map +0 -1
  37. package/dist/hmr.index.cjs +0 -4
  38. package/dist/hmr.index.d.cts +0 -1
  39. package/dist/index.cjs +0 -889
  40. package/dist/index.cjs.map +0 -1
  41. package/dist/index.d.cts +0 -1
  42. package/dist/internal.index.cjs +0 -717
  43. package/dist/internal.index.cjs.map +0 -1
  44. package/dist/internal.index.d.cts +0 -1
  45. package/dist/node-internal.index.cjs +0 -799
  46. package/dist/node-internal.index.cjs.map +0 -1
  47. package/dist/node-internal.index.d.cts +0 -1
  48. package/dist/phases-8qiiLKjS.cjs +0 -42
  49. package/dist/phases-8qiiLKjS.cjs.map +0 -1
  50. package/dist/plugin.index.cjs +0 -4
  51. package/dist/plugin.index.d.cts +0 -1
  52. package/dist/publishDefault-BCrMG9T8.mjs +0 -273
  53. package/dist/publishDefault-BCrMG9T8.mjs.map +0 -1
  54. package/dist/publishDefault-D52iT8mF.cjs +0 -368
  55. package/dist/publishDefault-D52iT8mF.cjs.map +0 -1
  56. package/dist/slashRouteLeaves-HVl6Ymr0.cjs +0 -28
  57. package/dist/slashRouteLeaves-HVl6Ymr0.cjs.map +0 -1
  58. package/dist/subscribers-6x4xw39Z.cjs +0 -531
  59. package/dist/subscribers-6x4xw39Z.cjs.map +0 -1
  60. package/dist/subscribers-nEpbJj3Z.mjs.map +0 -1
package/dist/index.cjs DELETED
@@ -1,889 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_subscribers = require('./subscribers-6x4xw39Z.cjs');
3
- const require_publishDefault = require('./publishDefault-D52iT8mF.cjs');
4
- const require_cursor = require('./cursor-DFgJ70_B.cjs');
5
- const require_phases = require('./phases-8qiiLKjS.cjs');
6
- require("reflect-metadata");
7
- let _discordjs_builders = require("@discordjs/builders");
8
- let _seedcord_logger = require("@seedcord/logger");
9
- let discord_api_types_v10 = require("discord-api-types/v10");
10
- let _seedcord_errors = require("@seedcord/errors");
11
- let _seedcord_errors_internal = require("@seedcord/errors/internal");
12
- let _seedcord_utils = require("@seedcord/utils");
13
-
14
- //#region src/decorators/Command.ts
15
- function RegisterCommand(scope, guilds = []) {
16
- return (ctor) => {
17
- const existingMeta = Reflect.getOwnMetadata(require_publishDefault.CommandMetadataKey, ctor);
18
- if (existingMeta) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorCommandAlreadyRegistered, [
19
- ctor.name,
20
- existingMeta.scope,
21
- scope
22
- ]);
23
- if (scope === "global" && guilds.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorCommandGlobalWithGuilds);
24
- if (scope === "guild" && (!Array.isArray(guilds) || guilds.length === 0)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorCommandGuildWithoutGuilds);
25
- const meta = scope === "global" ? { scope } : {
26
- scope,
27
- guilds
28
- };
29
- Reflect.defineMetadata(require_publishDefault.CommandMetadataKey, meta, ctor);
30
- };
31
- }
32
-
33
- //#endregion
34
- //#region src/decorators/routes.ts
35
- /**
36
- * Routes one or more slash commands to a `SlashHandler`.
37
- *
38
- * Pass the same route string(s) as the handler's generic. A single command reads `this.options`, several
39
- * commands branch with `this.match`. The decorator routes and the generic must match exactly. Listing
40
- * fewer or more than the handler declares is a compile error. Subcommands use a slash path.
41
- *
42
- * @param routes - The route string(s) this handler serves, e.g. `'ban'`, `'ban', 'kick'`, or `'demo/setup'`.
43
- * @decorator
44
- * @example
45
- * ```ts
46
- * \@SlashRoute('ban')
47
- * class BanHandler extends SlashHandler<'ban'> {
48
- * async execute() {
49
- * const target = this.options.getUser('target');
50
- * }
51
- * }
52
- * ```
53
- */
54
- function SlashRoute(...routes) {
55
- return function(constructor) {
56
- require_cursor.storeInteractionRoute("slash", routes, constructor);
57
- };
58
- }
59
- /**
60
- * Routes one or more commands' autocomplete to an `AutocompleteHandler`.
61
- *
62
- * Pass the same command route string(s) as the handler's generic. One command branches with `this.match`
63
- * over its autocompletable fields, several commands share one handler whose arms span every command's
64
- * fields. Listing fewer or more commands than the handler declares is a compile error, since the
65
- * decorator routes and the generic must match exactly.
66
- *
67
- * @param routes - The command route string(s) this handler serves, e.g. `'search'` or `'search', 'find'`.
68
- * @decorator
69
- * @example
70
- * ```ts
71
- * \@AutocompleteRoute('search')
72
- * class SearchAutocomplete extends AutocompleteHandler<'search'> {
73
- * async execute() {
74
- * await this.match({ query: (value, respond) => respond([{ name: value, value }]) });
75
- * }
76
- * }
77
- * ```
78
- */
79
- function AutocompleteRoute(...routes) {
80
- return function(constructor) {
81
- require_cursor.storeInteractionRoute("autocomplete", routes, constructor);
82
- };
83
- }
84
- /**
85
- * Routes one or more context-menu commands to a `ContextMenuHandler`.
86
- *
87
- * Pass the kind (`ApplicationCommandType.User` or `ApplicationCommandType.Message`) and the command name(s),
88
- * each checked against that kind's registry. A typo there is a compile error. The handler's generic must
89
- * declare the same kind, cross-checked both directions. Context menus carry no options, so a multi-name
90
- * handler reads `this.target` uniformly with no branch.
91
- *
92
- * @param kind - The context-menu kind, `ApplicationCommandType.User` or `ApplicationCommandType.Message`.
93
- * @param names - The command name(s) this handler serves, keyed off the matching registry.
94
- * @decorator
95
- * @example
96
- * ```ts
97
- * \@ContextMenuRoute(ApplicationCommandType.User, 'View Profile')
98
- * class ViewProfile extends ContextMenuHandler<ApplicationCommandType.User> {
99
- * async execute() {
100
- * const user = this.target;
101
- * }
102
- * }
103
- * ```
104
- */
105
- function ContextMenuRoute(kind, ...names) {
106
- return function(constructor) {
107
- require_cursor.storeInteractionRoute(require_cursor.contextMenuRouteOf(kind), names, constructor);
108
- };
109
- }
110
- /**
111
- * Routes button interactions to handler classes.
112
- *
113
- * Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
114
- * generic. Passing different definitions to the decorator and the generic is a compile error. Routing
115
- * matches the stable prefix, so a wire minted from an older shape still reaches the handler, where
116
- * reading this.params throws StaleCustomId and the controller boundary turns it into a reply.
117
- *
118
- * @param defs - The customId definition(s) this handler decodes, one per route.
119
- * @decorator
120
- */
121
- function ButtonRoute(...defs) {
122
- return function(constructor) {
123
- require_cursor.storeComponentRoute("button", defs, constructor);
124
- };
125
- }
126
- /**
127
- * Routes modal submissions to handler classes.
128
- *
129
- * Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
130
- * generic. Passing different definitions to the decorator and the generic is a compile error. Routing
131
- * matches the stable prefix here too, throwing StaleCustomId on read the same as a button route.
132
- *
133
- * @param defs - The customId definition(s) this handler decodes, one per route.
134
- * @decorator
135
- */
136
- function ModalRoute(...defs) {
137
- return function(constructor) {
138
- require_cursor.storeComponentRoute("modal", defs, constructor);
139
- };
140
- }
141
- /**
142
- * Routes select menu interactions to handler classes.
143
- *
144
- * Pass the select kind and the {@link CustomId} definition(s) this handler decodes. The handler's
145
- * generic must list the same kind and definitions, or it is a compile error.
146
- *
147
- * @param type - Select menu kind from {@link SelectMenuKind}.
148
- * @param defs - The customId definition(s) this handler decodes, one per route.
149
- * @decorator
150
- *
151
- * @example
152
- * ```typescript
153
- * \@SelectMenuRoute(SelectMenuKind.User, AssignId)
154
- * class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {}
155
- * ```
156
- */
157
- function SelectMenuRoute(type, ...defs) {
158
- return function(constructor) {
159
- require_cursor.storeComponentRoute(require_cursor.selectMenuRouteOf(type), defs, constructor);
160
- };
161
- }
162
-
163
- //#endregion
164
- //#region src/dispatch/DispatchContext.ts
165
- /**
166
- * A typed bag allocated once per interaction dispatch and handed to the handler. The middleware and i18n
167
- * work reads and writes it through {@link DispatchState}. Today the bag is empty, so it only carries the
168
- * dispatched `routeId`.
169
- */
170
- var DispatchContext = class {
171
- routeId;
172
- state = {};
173
- constructor(routeId) {
174
- this.routeId = routeId;
175
- }
176
- set(key, value) {
177
- this.state[key] = value;
178
- }
179
- get(key) {
180
- return this.state[key];
181
- }
182
- };
183
-
184
- //#endregion
185
- //#region src/handlers/BaseHandler.ts
186
- /**
187
- * Base class every transport handler extends. Don't register handlers directly. Use the more specific
188
- * handler subclasses.
189
- *
190
- * @typeParam Event - The event or interaction this handler processes
191
- * @typeParam TCore - The transport's Core
192
- */
193
- var BaseHandler = class {
194
- core;
195
- event;
196
- logger;
197
- dispatch;
198
- constructor(event, core, dispatch, channel) {
199
- this.core = core;
200
- this.event = event;
201
- this.logger = new _seedcord_logger.Logger(this.constructor.name, { channel });
202
- this.dispatch = dispatch;
203
- }
204
- };
205
-
206
- //#endregion
207
- //#region src/handlers/RepliableHandler.ts
208
- /**
209
- * Shared base the repliable handlers on both transports extend. Defines the reply members over the sender
210
- * the transport builds.
211
- *
212
- * @typeParam Event - The repliable interaction type this handler processes
213
- * @typeParam TCore - The transport's Core
214
- * @typeParam TMessage - The transport's created-message lens
215
- * @typeParam TNative - The transport's own file forms, beyond the portable `ReplyFile`
216
- * @typeParam TSender - The transport's reply sender
217
- */
218
- var RepliableHandler = class extends BaseHandler {
219
- sender;
220
- routeId;
221
- constructor(event, core, dispatch) {
222
- super(event, core, dispatch, "interactions");
223
- this.routeId = dispatch?.routeId ?? this.constructor.name;
224
- this.sender = this.buildSender(event, core, this.routeId);
225
- }
226
- /** @internal The dispatcher's fault boundary replies through the live ack state. */
227
- getSender() {
228
- return this.sender;
229
- }
230
- /** Send the initial response, exactly once. */
231
- reply(response, opts) {
232
- return this.sender.reply(response, opts);
233
- }
234
- /** Show a "thinking" placeholder, then fill it later with {@link edit}. */
235
- defer(opts) {
236
- return this.sender.defer(opts);
237
- }
238
- /** Send a new message once the interaction is acknowledged. */
239
- followUp(response, opts) {
240
- return this.sender.followUp(response, opts);
241
- }
242
- edit(targetOrResponse, maybeResponse) {
243
- if (maybeResponse === void 0) return this.sender.edit(targetOrResponse);
244
- return this.sender.edit(targetOrResponse, maybeResponse);
245
- }
246
- /** Routes to reply, followUp, or edit based on the current ack state. */
247
- send(response, opts) {
248
- return this.sender.send(response, opts);
249
- }
250
- /** Delete the initial reply or deferred placeholder. Pass a target to delete a message a prior send returned. */
251
- delete(target) {
252
- return target === void 0 ? this.sender.delete() : this.sender.delete(target);
253
- }
254
- };
255
-
256
- //#endregion
257
- //#region src/stops/Fault.ts
258
- /**
259
- * A generic fault you throw after catching an error you do not have a specific message for.
260
- *
261
- * The user sees a fixed generic reply with the tracking uuid, never the cause. `report` defaults to
262
- * `true`, so the framework logs it and publishes it to the `handledException` bus. Pass `report: false`
263
- * to show the generic reply without the bus publish. The original error is stored as the standard
264
- * `cause`, so the real stack reaches the webhook. When the user should see a real message for a fault,
265
- * subclass {@link Notice} and write your own `render` instead.
266
- *
267
- * The framework also renders this for an unhandled throw, where it points the user at
268
- * `ctx.developerUsername`.
269
- *
270
- * @example
271
- * ```ts
272
- * import { Fault } from '@seedcord/gateway';
273
- *
274
- * try {
275
- * await db.write(record);
276
- * } catch (cause) {
277
- * // user sees the generic reply with the uuid, and the real error rides along as cause for the webhook
278
- * throw new Fault({ cause });
279
- *
280
- * // pass report: false to show the same reply without publishing to the handledException bus
281
- * // throw new Fault({ cause, report: false });
282
- * }
283
- * ```
284
- */
285
- var Fault = class extends require_cursor.Notice {
286
- constructor(options) {
287
- super("A fault occurred", options?.cause === void 0 ? void 0 : { cause: options.cause });
288
- this.report = options?.report ?? true;
289
- }
290
- render(ctx) {
291
- const contact = ctx.developerUsername ?? "the developer";
292
- return { components: [new require_cursor.NoticeCard(`Something went wrong. Please reach out to ${contact} with a way to reproduce the error and the following:\n### UUID: \`${ctx.uuid}\``, "Error").component] };
293
- }
294
- };
295
-
296
- //#endregion
297
- //#region src/gates/combinators.ts
298
- function and(...gates) {
299
- return require_cursor.defineGate(gates.map((gate) => gate.name).join(" & "), async (ctx) => {
300
- for (const gate of gates) await require_cursor.runCheck(gate, ctx);
301
- });
302
- }
303
- function isOrOptions(arg) {
304
- return !("check" in arg);
305
- }
306
- function or(...args) {
307
- const last = args.at(-1);
308
- const options = last !== void 0 && isOrOptions(last) ? last : void 0;
309
- const gates = options ? args.slice(0, -1) : args;
310
- return require_cursor.defineGate(gates.map((gate) => gate.name).join(" | "), async (ctx) => {
311
- const summaries = [];
312
- let everyArmHasSummary = true;
313
- for (const gate of gates) {
314
- const mark = require_cursor.markCommits(ctx);
315
- try {
316
- await require_cursor.runCheck(gate, ctx);
317
- return;
318
- } catch (error) {
319
- if (error instanceof require_cursor.Notice && !(error instanceof Fault) && !error.report) {
320
- require_cursor.rollbackCommits(ctx, mark);
321
- if (error.summary === void 0) everyArmHasSummary = false;
322
- else summaries.push(error.summary);
323
- continue;
324
- }
325
- throw error;
326
- }
327
- }
328
- if (options) throw typeof options.fail === "function" ? options.fail(ctx) : options.fail;
329
- if (everyArmHasSummary && summaries.length > 0) throw new require_cursor.NeedsAny(summaries);
330
- throw new require_cursor.NotAllowed();
331
- });
332
- }
333
-
334
- //#endregion
335
- //#region src/gates/catalog/access.ts
336
- /**
337
- * Passes only for a user id listed in `config.ownerIds`, else refuses.
338
- *
339
- * 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.
340
- *
341
- * @param options - Reword the default refusal with `message`, or replace it with `notice`.
342
- *
343
- * @see the `@Gated` decorator from `seedcord`
344
- *
345
- * @example
346
- * ```ts
347
- * \@Gated(OwnerOnly())
348
- * \@SlashRoute('shutdown')
349
- * class ShutdownHandler extends SlashHandler<'shutdown'> {
350
- * async execute() {
351
- * // ...
352
- * }
353
- * }
354
- * ```
355
- *
356
- * @example
357
- * ```ts
358
- * // reword the refusal, keeping the embed styling
359
- * OwnerOnly({ message: 'Owners only.' });
360
- * ```
361
- */
362
- function OwnerOnly(options) {
363
- return require_cursor.defineGate("OwnerOnly", (ctx) => {
364
- const owners = ctx.core.config.ownerIds ?? [];
365
- if (ctx.userId !== null && owners.includes(ctx.userId)) return;
366
- throw require_cursor.pickNotice(options, (message) => new require_cursor.NotOwner(message));
367
- });
368
- }
369
- /**
370
- * Passes inside a guild, else refuses.
371
- *
372
- * Agnostic. Attaches to any handler kind. Often paired with `RequirePermissions` in an {@link and}.
373
- *
374
- * @param options - Reword the default refusal with `message`, or replace it with `notice`.
375
- *
376
- * @see the `@Gated` decorator from `seedcord`
377
- *
378
- * @example
379
- * ```ts
380
- * import { PermissionFlagsBits } from 'discord.js';
381
- *
382
- * \@Gated(GuildOnly(), RequirePermissions([PermissionFlagsBits.Administrator]))
383
- * \@SlashRoute('maintenance')
384
- * class Maintenance extends SlashHandler<'maintenance'> {
385
- * async execute() {
386
- * // ...
387
- * }
388
- * }
389
- * ```
390
- */
391
- function GuildOnly(options) {
392
- return require_cursor.defineGate("GuildOnly", (ctx) => {
393
- if (ctx.guildId) return;
394
- throw require_cursor.pickNotice(options, (message) => new require_cursor.NotInGuild(message));
395
- });
396
- }
397
- /**
398
- * Passes in a direct message, else refuses.
399
- *
400
- * Agnostic, fitting any handler kind. The inverse of {@link GuildOnly}, so combining the two in an {@link and} can never pass.
401
- *
402
- * @param options - Reword the default refusal with `message`, or replace it with `notice`.
403
- *
404
- * @see the `@Gated` decorator from `seedcord`
405
- *
406
- * @example
407
- * ```ts
408
- * \@Gated(DmOnly())
409
- * \@SlashRoute('verify')
410
- * class VerifyHandler extends SlashHandler<'verify'> {
411
- * async execute() {
412
- * // ...
413
- * }
414
- * }
415
- * ```
416
- */
417
- function DmOnly(options) {
418
- return require_cursor.defineGate("DmOnly", (ctx) => {
419
- if (!ctx.guildId) return;
420
- throw require_cursor.pickNotice(options, (message) => new require_cursor.NotInDm(message));
421
- });
422
- }
423
-
424
- //#endregion
425
- //#region src/gates/catalog/Cooldown.ts
426
- let anonSeq = 0;
427
- function scopeValue(ctx, per) {
428
- if (per === "guild") return ctx.guildId ?? "global";
429
- if (per === "channel") return ctx.channelId ?? "global";
430
- return ctx.userId ?? "global";
431
- }
432
- /**
433
- * Allows `limit` uses per window, scoped by `per`. A number `duration` is **seconds**, and a string is
434
- * a duration like `30m` or `24h`. An unparseable string throws a **SeedcordTypeError** at construction. The
435
- * slot is charged in commit, only after the whole gate set passes, so a later refusal never burns the cooldown.
436
- * That split also means two requests racing the same key can both pass before either charges the slot.
437
- * The key combines the handler's route with the window settings, keeping two different handlers on separate
438
- * windows. Reword the refusal with {@link CooldownOptions.message} or replace it with {@link CooldownOptions.notice}.
439
- *
440
- * @param duration - A number is seconds, and a string is a duration like `30m` or `24h`. An unparseable string throws a **SeedcordTypeError**.
441
- * @param options - Sets the scope with `per`, the uses per window with `limit`, and the refusal text with `message` or `notice`.
442
- *
443
- * @see the `@Gated` decorator from `seedcord`
444
- * @see {@link IRateLimiter}
445
- *
446
- * @example
447
- * ```ts
448
- * // a number argument is SECONDS
449
- * \@Gated(Cooldown(5))
450
- * \@SlashRoute('daily')
451
- * class DailyHandler extends SlashHandler<'daily'> {
452
- * async execute() {
453
- * // ...
454
- * }
455
- * }
456
- * ```
457
- *
458
- * @example
459
- * ```ts
460
- * // a string is a duration, here scoped per guild (the default is per user)
461
- * Cooldown('30m', { per: 'guild' });
462
- * ```
463
- *
464
- * @example
465
- * ```ts
466
- * // a burst, 3 uses per 10 minutes per user, then it refuses until a slot frees
467
- * \@Gated(Cooldown('10m', { limit: 3 }))
468
- * \@SlashRoute('report')
469
- * class ReportHandler extends SlashHandler<'report'> {
470
- * async execute() {
471
- * // ...
472
- * }
473
- * }
474
- * ```
475
- */
476
- function Cooldown(duration, options) {
477
- let windowMs;
478
- if (typeof duration === "number") windowMs = duration * 1e3;
479
- else {
480
- const parsed = (0, _seedcord_utils.parseDuration)(duration);
481
- if (parsed === null) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.GateInvalidCooldownDuration, [duration]);
482
- windowMs = parsed;
483
- }
484
- if (windowMs <= 0) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.GateInvalidCooldownDuration, [String(duration)]);
485
- const per = options?.per ?? "user";
486
- const window = options?.limit === void 0 ? { windowMs } : {
487
- windowMs,
488
- limit: options.limit
489
- };
490
- const anonId = anonSeq++;
491
- const keyOf = (ctx) => `cooldown:${ctx.routeId ?? `anon${anonId}`}:${per}:w${windowMs}:l${options?.limit ?? 1}:${scopeValue(ctx, per)}`;
492
- return require_cursor.defineEffectGate("Cooldown", async (ctx) => {
493
- const result = await ctx.core.rateLimiter.peek(keyOf(ctx), window);
494
- if (!result.limited) return;
495
- if (options?.notice) throw options.notice(result.resetAt);
496
- throw new require_cursor.OnCooldown(result.resetAt, options?.message?.(result.resetAt));
497
- }, async (ctx) => {
498
- await ctx.core.rateLimiter.charge(keyOf(ctx), window);
499
- });
500
- }
501
-
502
- //#endregion
503
- //#region src/permissions/assert.ts
504
- /**
505
- * Refuses when the subject is missing a permission in `scope`, or with `inverse` when it holds one.
506
- * The Administrator bit passes any scope, and on the inverse arm it reports every bit in `scope`.
507
- *
508
- * The caller computes `permissions`, gateway off a discord.js role, member, or channel, and HTTP off the
509
- * bitfield the interaction payload carries.
510
- *
511
- * @throws A {@link Notice} listing the missing or present permissions.
512
- *
513
- * @example
514
- * ```ts
515
- * import { assertPermissions } from '@seedcord/core';
516
- * import { PermissionFlagsBits } from 'discord-api-types/v10';
517
- *
518
- * // gateway, the bits come off a discord.js member
519
- * assertPermissions({
520
- * subject: `<@${member.id}>`,
521
- * permissions: member.permissions.bitfield,
522
- * scope: [PermissionFlagsBits.BanMembers]
523
- * });
524
- * ```
525
- *
526
- * @example
527
- * ```ts
528
- * // HTTP, the payload carries the member's bits as a string
529
- * assertPermissions({
530
- * subject: `<@${this.event.member.user.id}>`,
531
- * permissions: BigInt(this.event.member.permissions),
532
- * scope: [PermissionFlagsBits.ManageMessages]
533
- * });
534
- * ```
535
- *
536
- * @example
537
- * ```ts
538
- * // inverse, refuse a role that carries Administrator
539
- * assertPermissions({
540
- * subject: `<@&${role.id}>`,
541
- * permissions: role.permissions.bitfield,
542
- * scope: [PermissionFlagsBits.Administrator],
543
- * inverse: true
544
- * });
545
- * ```
546
- *
547
- * @example
548
- * ```ts
549
- * import { Notice } from '@seedcord/core';
550
- *
551
- * // a replacement notice takes the same three arguments the default one does
552
- * class CannotBan extends Notice {
553
- * public constructor(
554
- * message: string | undefined,
555
- * private readonly subject: string,
556
- * private readonly missing: readonly string[]
557
- * ) {
558
- * super(message ?? 'You cannot ban here.');
559
- * }
560
- *
561
- * public render(): ReplyResponse {
562
- * return { components: [new BanDenied(this.subject, this.missing).component] };
563
- * }
564
- * }
565
- *
566
- * assertPermissions({
567
- * subject: `<@${member.id}>`,
568
- * permissions: member.permissions.bitfield,
569
- * scope: [PermissionFlagsBits.BanMembers],
570
- * missingNotice: CannotBan
571
- * });
572
- * ```
573
- */
574
- function assertPermissions(check) {
575
- const { subject, permissions, scope, inverse = false, missingNotice, dangerousNotice } = check;
576
- if (inverse) {
577
- const present = require_cursor.presentNames(permissions, scope);
578
- if (present.length === 0) return;
579
- throw new (dangerousNotice ?? require_cursor.HasDangerousPermissions)(void 0, subject, present);
580
- }
581
- if (require_cursor.hasAll(permissions, scope)) return;
582
- throw new (missingNotice ?? require_cursor.MissingPermissions)(void 0, subject, require_cursor.missingNames(permissions, scope));
583
- }
584
-
585
- //#endregion
586
- //#region src/permissions/mergeRoles.ts
587
- /**
588
- * The role ids a member holds after adding and removing the given sets. Removals win over additions.
589
- *
590
- * This does not write the roles. Apply the result with `member.roles.set()` on gateway, or PATCH the member over HTTP.
591
- *
592
- * @param current - The role ids the member holds.
593
- * @param add - The role ids to add.
594
- * @param remove - The role ids to remove.
595
- * @returns The merged role ids.
596
- *
597
- * @example
598
- * ```ts
599
- * import { mergeRoles } from '@seedcord/core';
600
- *
601
- * // gateway
602
- * const ids = mergeRoles(member.roles.cache.map((role) => role.id), ['123'], ['456']);
603
- * await member.roles.set(ids);
604
- * ```
605
- */
606
- function mergeRoles(current, add, remove) {
607
- return [...new Set(current).union(new Set(add)).difference(new Set(remove))];
608
- }
609
-
610
- //#endregion
611
- //#region src/pagination/paginate.ts
612
- /**
613
- * Pure page math, usable headless. Clamps `page` into range and slices the window.
614
- *
615
- * @param items - The full list to page over.
616
- * @param page - The requested zero-based page, clamped into `[0, totalPages - 1]`.
617
- * @param perPage - The page size. Must be a positive integer.
618
- * @returns The sliced {@link PageView}, with `totalPages` always known.
619
- * @throws SeedcordRangeError when `perPage` is not a positive integer.
620
- */
621
- function paginate(items, page, perPage) {
622
- if (!Number.isSafeInteger(perPage) || perPage <= 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationInvalidPerPage, [perPage]);
623
- const totalPages = Math.max(1, Math.ceil(items.length / perPage));
624
- const clamped = Math.min(Math.max(Math.trunc(page), 0), totalPages - 1);
625
- const start = clamped * perPage;
626
- return {
627
- items: items.slice(start, start + perPage),
628
- page: clamped,
629
- perPage,
630
- totalPages,
631
- hasPrev: clamped > 0,
632
- hasNext: clamped < totalPages - 1
633
- };
634
- }
635
-
636
- //#endregion
637
- //#region src/pagination/controls.ts
638
- const DEFAULT_LABEL = {
639
- first: "First",
640
- prev: "Prev",
641
- indicator: "",
642
- next: "Next",
643
- last: "Last"
644
- };
645
- const CONTROL_SLOT = {
646
- first: 0,
647
- prev: 1,
648
- indicator: 2,
649
- next: 3,
650
- last: 4
651
- };
652
- const MAX_BUTTONS_PER_ROW = 5;
653
- var Controls = class {
654
- cursor;
655
- view;
656
- constructor(cursor, view) {
657
- this.cursor = cursor;
658
- this.view = view;
659
- }
660
- button(key, cosmetics) {
661
- const { target, disabled } = this.navState(key);
662
- const defaultLabel = key === "indicator" ? this.indicatorText() : DEFAULT_LABEL[key];
663
- const label = cosmetics?.label ?? (key === "indicator" || !cosmetics?.emoji ? defaultLabel : void 0);
664
- const button = new _discordjs_builders.ButtonBuilder().setCustomId(this.cursor.encode({
665
- page: Math.min(Math.max(0, target), require_cursor.PAGE_MAX),
666
- slot: CONTROL_SLOT[key]
667
- })).setStyle(cosmetics?.style ?? discord_api_types_v10.ButtonStyle.Secondary).setDisabled(disabled);
668
- if (label !== void 0) button.setLabel(label);
669
- if (cosmetics?.emoji) button.setEmoji(cosmetics.emoji);
670
- return button;
671
- }
672
- navState(key) {
673
- const { page, totalPages, hasPrev, hasNext } = this.view;
674
- const knownTotal = totalPages !== void 0;
675
- return {
676
- target: {
677
- first: 0,
678
- prev: page - 1,
679
- indicator: page,
680
- next: page + 1,
681
- last: knownTotal ? totalPages - 1 : page
682
- }[key],
683
- disabled: {
684
- first: !hasPrev,
685
- prev: !hasPrev,
686
- indicator: true,
687
- next: !hasNext || page + 1 > 1e6,
688
- last: !hasNext || !knownTotal || totalPages - 1 > 1e6
689
- }[key]
690
- };
691
- }
692
- row(...keys) {
693
- if (keys.length === 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationEmptyControls);
694
- if (keys.length > MAX_BUTTONS_PER_ROW) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationTooManyControls, [keys.length]);
695
- const seen = /* @__PURE__ */ new Set();
696
- for (const key of keys) {
697
- if (seen.has(key)) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.PaginationDuplicateControls, [key]);
698
- seen.add(key);
699
- }
700
- return new _discordjs_builders.ActionRowBuilder().addComponents(keys.map((key) => this.button(key)));
701
- }
702
- indicatorText() {
703
- const { page, totalPages } = this.view;
704
- return totalPages === void 0 ? `Page ${page + 1}` : `Page ${page + 1} of ${totalPages}`;
705
- }
706
- };
707
-
708
- //#endregion
709
- //#region src/pagination/render.ts
710
- var PageContainer = class extends require_publishDefault.BuilderComponent {
711
- constructor(view, renderItem, controlRow) {
712
- super("container");
713
- const base = view.page * view.perPage;
714
- let lines = [];
715
- const flush = () => {
716
- if (lines.length === 0) return;
717
- this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(lines.join("\n")));
718
- lines = [];
719
- };
720
- view.items.forEach((item, offset) => {
721
- const rendered = renderItem(item, base + offset);
722
- if (typeof rendered === "string") {
723
- lines.push(rendered);
724
- return;
725
- }
726
- flush();
727
- this.instance.addSectionComponents(rendered.component);
728
- });
729
- flush();
730
- this.instance.addActionRowComponents(controlRow);
731
- }
732
- };
733
- function toReplyResponse(renderable) {
734
- if (typeof renderable === "string") return { components: [new _discordjs_builders.TextDisplayBuilder().setContent(renderable)] };
735
- if (Array.isArray(renderable)) return { components: renderable };
736
- return renderable;
737
- }
738
- const ARRAY_KEYS = [
739
- "first",
740
- "prev",
741
- "indicator",
742
- "next",
743
- "last"
744
- ];
745
- const CURSOR_KEYS = [
746
- "prev",
747
- "indicator",
748
- "next"
749
- ];
750
- function renderPage(view, cursor, config) {
751
- const controls = new Controls(cursor, view);
752
- if (config.render) return toReplyResponse(config.render(view, controls));
753
- const renderItem = config.renderItem ?? String;
754
- const row = controls.row(...view.totalPages === void 0 ? CURSOR_KEYS : ARRAY_KEYS);
755
- return { components: [new PageContainer(view, renderItem, row).component] };
756
- }
757
-
758
- //#endregion
759
- //#region src/pagination/PaginatorBase.ts
760
- /**
761
- * Holds a paginator's cursor and renders one page. Each transport extends this to add the nav handler and
762
- * the method that sends the first page.
763
- *
764
- * @typeParam Item - The item type, inferred from the source.
765
- * @typeParam Prefix - The route prefix, inferred from `config.prefix`.
766
- * @typeParam Ctx - The transport's page context.
767
- */
768
- var PaginatorBase = class {
769
- /** The page cursor, pass it to your `@ButtonRoute`. */
770
- cursor;
771
- config;
772
- constructor(config) {
773
- this.cursor = require_cursor.pageCursor(config.prefix);
774
- this.config = config;
775
- }
776
- /** Render a page as a {@link ReplyResponse}. To post it elsewhere, add `flags: MessageFlags.IsComponentsV2`. */
777
- async page(ctx, n) {
778
- const view = await this.config.source.page(ctx, n);
779
- return renderPage(view, this.cursor, this.config);
780
- }
781
- };
782
-
783
- //#endregion
784
- //#region src/pagination/sources.ts
785
- const DEFAULT_PER_PAGE = 10;
786
- function perPageOf(opts) {
787
- const perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
788
- if (!Number.isSafeInteger(perPage) || perPage <= 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationInvalidPerPage, [perPage]);
789
- return perPage;
790
- }
791
- /**
792
- * A source for a bounded list you can load whole. It loads the full list on every click, then slices the
793
- * page via {@link paginate}, so the real total is known (a real last button, "Page X of Y"). Cache inside
794
- * your loader if the load is expensive.
795
- *
796
- * @typeParam Item - The item type, inferred from the loader's return.
797
- * @typeParam Ctx - The transport's page context. Each transport exports a subclass that fixes it.
798
- */
799
- var ArraySource = class {
800
- load;
801
- perPage;
802
- constructor(load, opts) {
803
- this.load = load;
804
- this.perPage = perPageOf(opts);
805
- }
806
- async page(ctx, n) {
807
- return paginate(await this.load(ctx), n, this.perPage);
808
- }
809
- };
810
- /**
811
- * A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
812
- * The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
813
- * source has no cheap total, so `totalPages` is undefined, the last button is omitted, and the indicator
814
- * reads "Page X".
815
- *
816
- * @typeParam Item - The item type, inferred from the fetcher's slice.
817
- * @typeParam Ctx - The transport's page context. Each transport exports a subclass that fixes it.
818
- */
819
- var CursorSource = class {
820
- fetch;
821
- perPage;
822
- constructor(fetch, opts) {
823
- this.fetch = fetch;
824
- this.perPage = perPageOf(opts);
825
- }
826
- async page(ctx, n) {
827
- const page = Math.max(0, Math.trunc(n));
828
- const { items, hasNext } = await this.fetch(ctx, page, this.perPage);
829
- return {
830
- items: [...items],
831
- page,
832
- perPage: this.perPage,
833
- hasPrev: page > 0,
834
- hasNext
835
- };
836
- }
837
- };
838
-
839
- //#endregion
840
- //#region src/index.ts
841
- /** Package version */
842
- const version = "0.1.3";
843
-
844
- //#endregion
845
- exports.ArraySource = ArraySource;
846
- exports.AutocompleteRoute = AutocompleteRoute;
847
- exports.BaseHandler = BaseHandler;
848
- exports.BuilderComponent = require_publishDefault.BuilderComponent;
849
- exports.Bus = require_subscribers.Bus;
850
- exports.ButtonRoute = ButtonRoute;
851
- exports.Commands = require_cursor.Commands;
852
- exports.ContextMenuRoute = ContextMenuRoute;
853
- exports.ContextMenus = require_cursor.ContextMenus;
854
- exports.Cooldown = Cooldown;
855
- exports.CursorSource = CursorSource;
856
- exports.CustomId = require_cursor.CustomId;
857
- exports.DispatchContext = DispatchContext;
858
- exports.DmOnly = DmOnly;
859
- exports.Fault = Fault;
860
- exports.GuildOnly = GuildOnly;
861
- exports.ModalRoute = ModalRoute;
862
- exports.Notice = require_cursor.Notice;
863
- exports.OwnerOnly = OwnerOnly;
864
- exports.PaginatorBase = PaginatorBase;
865
- exports.RegisterCommand = RegisterCommand;
866
- exports.RepliableHandler = RepliableHandler;
867
- exports.RequireBotPermissions = require_cursor.RequireBotPermissions;
868
- exports.RequirePermissions = require_cursor.RequirePermissions;
869
- exports.RequireRole = require_cursor.RequireRole;
870
- exports.RowComponent = require_publishDefault.RowComponent;
871
- exports.SelectMenuKind = require_cursor.SelectMenuKind;
872
- exports.SelectMenuRoute = SelectMenuRoute;
873
- exports.ShutdownPhase = require_phases.ShutdownPhase;
874
- exports.Silence = require_cursor.Silence;
875
- exports.SlashRoute = SlashRoute;
876
- exports.StartupPhase = require_phases.StartupPhase;
877
- exports.Subscribe = require_subscribers.Subscribe;
878
- exports.Subscriber = require_subscribers.Subscriber;
879
- exports.WebhookLog = require_subscribers.WebhookLog;
880
- exports.WebhookUrl = require_subscribers.WebhookUrl;
881
- exports.and = and;
882
- exports.assertPermissions = assertPermissions;
883
- exports.defineEffectGate = require_cursor.defineEffectGate;
884
- exports.defineGate = require_cursor.defineGate;
885
- exports.mergeRoles = mergeRoles;
886
- exports.or = or;
887
- exports.paginate = paginate;
888
- exports.version = version;
889
- //# sourceMappingURL=index.cjs.map