@seedcord/core 0.1.0-next.6 → 0.1.0-next.8

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 (64) hide show
  1. package/dist/HmrModuleHandler-3sNgKHdf.mjs +163 -0
  2. package/dist/HmrModuleHandler-3sNgKHdf.mjs.map +1 -0
  3. package/dist/HmrModuleHandler-CTBng7wV.cjs +170 -0
  4. package/dist/HmrModuleHandler-CTBng7wV.cjs.map +1 -0
  5. package/dist/Plugin-BZit5HUI.d.mts +555 -0
  6. package/dist/{Plugin-C2-VHrsX.cjs → Plugin-BlkhHNRm.cjs} +15 -34
  7. package/dist/Plugin-BlkhHNRm.cjs.map +1 -0
  8. package/dist/{Plugin-PcdPjAzc.mjs → Plugin-CDAV7P9l.mjs} +16 -29
  9. package/dist/Plugin-CDAV7P9l.mjs.map +1 -0
  10. package/dist/basic-1hebYVcN.d.mts +22 -0
  11. package/dist/contextMenuLeaves-f2rbyo83.d.mts +19 -0
  12. package/dist/{permissions-DSW_EZfN.cjs → cursor-BMkFAwO2.cjs} +308 -300
  13. package/dist/cursor-BMkFAwO2.cjs.map +1 -0
  14. package/dist/{permissions-RVCBhL2n.mjs → cursor-BvSH8tgw.mjs} +249 -247
  15. package/dist/cursor-BvSH8tgw.mjs.map +1 -0
  16. package/dist/{AutocompleteOptions-eKhIe6Qt.d.mts → cursor-D6nBjwaE.d.mts} +207 -116
  17. package/dist/hmr.index.cjs +2 -164
  18. package/dist/hmr.index.mjs +2 -162
  19. package/dist/index-Cr4-VV4u.d.mts +142 -0
  20. package/dist/index.cjs +362 -63
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.mts +239 -38
  23. package/dist/index.mjs +313 -26
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/internal.index.cjs +278 -83
  26. package/dist/internal.index.cjs.map +1 -1
  27. package/dist/internal.index.d.mts +111 -42
  28. package/dist/internal.index.mjs +223 -43
  29. package/dist/internal.index.mjs.map +1 -1
  30. package/dist/node-internal.index.cjs +314 -76
  31. package/dist/node-internal.index.cjs.map +1 -1
  32. package/dist/node-internal.index.d.mts +123 -2164
  33. package/dist/node-internal.index.mjs +313 -77
  34. package/dist/node-internal.index.mjs.map +1 -1
  35. package/dist/{phases-BfpAG6w3.mjs → phases-BauKJOTT.mjs} +2 -2
  36. package/dist/phases-BauKJOTT.mjs.map +1 -0
  37. package/dist/{phases-C_BGNyVo.cjs → phases-DcuDHoQl.cjs} +2 -2
  38. package/dist/phases-DcuDHoQl.cjs.map +1 -0
  39. package/dist/plugin.index.cjs +1 -1
  40. package/dist/plugin.index.d.mts +2 -2
  41. package/dist/plugin.index.mjs +1 -1
  42. package/dist/publishDefault-CVlwYUS5.cjs +382 -0
  43. package/dist/publishDefault-CVlwYUS5.cjs.map +1 -0
  44. package/dist/publishDefault-CldnxcT2.mjs +287 -0
  45. package/dist/publishDefault-CldnxcT2.mjs.map +1 -0
  46. package/dist/slashRouteLeaves-BANDtPFm.mjs +23 -0
  47. package/dist/slashRouteLeaves-BANDtPFm.mjs.map +1 -0
  48. package/dist/slashRouteLeaves-nfxOAM7Q.cjs +28 -0
  49. package/dist/slashRouteLeaves-nfxOAM7Q.cjs.map +1 -0
  50. package/dist/subscribers-BSJoyKNN.cjs +517 -0
  51. package/dist/subscribers-BSJoyKNN.cjs.map +1 -0
  52. package/dist/subscribers-DuxdM2cS.mjs +480 -0
  53. package/dist/subscribers-DuxdM2cS.mjs.map +1 -0
  54. package/package.json +7 -6
  55. package/dist/Plugin-C2-VHrsX.cjs.map +0 -1
  56. package/dist/Plugin-CNyRKDHT.d.mts +0 -226
  57. package/dist/Plugin-PcdPjAzc.mjs.map +0 -1
  58. package/dist/hmr.index.cjs.map +0 -1
  59. package/dist/hmr.index.mjs.map +0 -1
  60. package/dist/is-never-Ddaba1Su.d.mts +0 -58
  61. package/dist/permissions-DSW_EZfN.cjs.map +0 -1
  62. package/dist/permissions-RVCBhL2n.mjs.map +0 -1
  63. package/dist/phases-BfpAG6w3.mjs.map +0 -1
  64. package/dist/phases-C_BGNyVo.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,16 +1,20 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_permissions = require('./permissions-DSW_EZfN.cjs');
3
- const require_phases = require('./phases-C_BGNyVo.cjs');
2
+ const require_publishDefault = require('./publishDefault-CVlwYUS5.cjs');
3
+ const require_cursor = require('./cursor-BMkFAwO2.cjs');
4
+ const require_phases = require('./phases-DcuDHoQl.cjs');
5
+ const require_subscribers = require('./subscribers-BSJoyKNN.cjs');
4
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");
5
10
  let _seedcord_errors = require("@seedcord/errors");
6
11
  let _seedcord_errors_internal = require("@seedcord/errors/internal");
7
- let _seedcord_logger = require("@seedcord/logger");
8
12
  let _seedcord_utils = require("@seedcord/utils");
9
13
 
10
14
  //#region src/decorators/Command.ts
11
15
  function RegisterCommand(scope, guilds = []) {
12
16
  return (ctor) => {
13
- const existingMeta = Reflect.getOwnMetadata(require_permissions.CommandMetadataKey, ctor);
17
+ const existingMeta = Reflect.getOwnMetadata(require_publishDefault.CommandMetadataKey, ctor);
14
18
  if (existingMeta) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorCommandAlreadyRegistered, [
15
19
  ctor.name,
16
20
  existingMeta.scope,
@@ -22,7 +26,7 @@ function RegisterCommand(scope, guilds = []) {
22
26
  scope,
23
27
  guilds
24
28
  };
25
- Reflect.defineMetadata(require_permissions.CommandMetadataKey, meta, ctor);
29
+ Reflect.defineMetadata(require_publishDefault.CommandMetadataKey, meta, ctor);
26
30
  };
27
31
  }
28
32
 
@@ -49,7 +53,7 @@ function RegisterCommand(scope, guilds = []) {
49
53
  */
50
54
  function SlashRoute(...routes) {
51
55
  return function(constructor) {
52
- require_permissions.storeInteractionRoute("slash", routes, constructor);
56
+ require_cursor.storeInteractionRoute("slash", routes, constructor);
53
57
  };
54
58
  }
55
59
  /**
@@ -74,7 +78,7 @@ function SlashRoute(...routes) {
74
78
  */
75
79
  function AutocompleteRoute(...routes) {
76
80
  return function(constructor) {
77
- require_permissions.storeInteractionRoute("autocomplete", routes, constructor);
81
+ require_cursor.storeInteractionRoute("autocomplete", routes, constructor);
78
82
  };
79
83
  }
80
84
  /**
@@ -100,7 +104,7 @@ function AutocompleteRoute(...routes) {
100
104
  */
101
105
  function ContextMenuRoute(kind, ...names) {
102
106
  return function(constructor) {
103
- require_permissions.storeInteractionRoute(require_permissions.contextMenuRouteOf(kind), names, constructor);
107
+ require_cursor.storeInteractionRoute(require_cursor.contextMenuRouteOf(kind), names, constructor);
104
108
  };
105
109
  }
106
110
  /**
@@ -116,7 +120,7 @@ function ContextMenuRoute(kind, ...names) {
116
120
  */
117
121
  function ButtonRoute(...defs) {
118
122
  return function(constructor) {
119
- require_permissions.storeComponentRoute("button", defs, constructor);
123
+ require_cursor.storeComponentRoute("button", defs, constructor);
120
124
  };
121
125
  }
122
126
  /**
@@ -132,7 +136,7 @@ function ButtonRoute(...defs) {
132
136
  */
133
137
  function ModalRoute(...defs) {
134
138
  return function(constructor) {
135
- require_permissions.storeComponentRoute("modal", defs, constructor);
139
+ require_cursor.storeComponentRoute("modal", defs, constructor);
136
140
  };
137
141
  }
138
142
  /**
@@ -153,7 +157,7 @@ function ModalRoute(...defs) {
153
157
  */
154
158
  function SelectMenuRoute(type, ...defs) {
155
159
  return function(constructor) {
156
- require_permissions.storeComponentRoute(require_permissions.selectMenuRouteOf(type), defs, constructor);
160
+ require_cursor.storeComponentRoute(require_cursor.selectMenuRouteOf(type), defs, constructor);
157
161
  };
158
162
  }
159
163
 
@@ -192,10 +196,10 @@ var BaseHandler = class {
192
196
  event;
193
197
  logger;
194
198
  dispatch;
195
- constructor(event, core, dispatch) {
199
+ constructor(event, core, dispatch, channel) {
196
200
  this.core = core;
197
201
  this.event = event;
198
- this.logger = new _seedcord_logger.Logger(this.constructor.name);
202
+ this.logger = new _seedcord_logger.Logger(this.constructor.name, { channel });
199
203
  this.dispatch = dispatch;
200
204
  }
201
205
  };
@@ -215,7 +219,7 @@ var RepliableHandler = class extends BaseHandler {
215
219
  sender;
216
220
  routeId;
217
221
  constructor(event, core, dispatch) {
218
- super(event, core, dispatch);
222
+ super(event, core, dispatch, "interactions");
219
223
  this.routeId = dispatch?.routeId ?? this.constructor.name;
220
224
  this.sender = this.buildSender(event, core, this.routeId);
221
225
  }
@@ -278,21 +282,21 @@ var RepliableHandler = class extends BaseHandler {
278
282
  * }
279
283
  * ```
280
284
  */
281
- var Fault = class extends require_permissions.Notice {
285
+ var Fault = class extends require_cursor.Notice {
282
286
  constructor(options) {
283
287
  super("A fault occurred", options?.cause === void 0 ? void 0 : { cause: options.cause });
284
288
  this.report = options?.report ?? true;
285
289
  }
286
290
  render(ctx) {
287
- return { components: [new require_permissions.NoticeCard(`Something went wrong. Please reach out to ${ctx.developerUsername ?? "the developer"} with a way to reproduce the error and the following:\n### UUID: \`${ctx.uuid}\``, "Error").component] };
291
+ return { components: [new require_cursor.NoticeCard(`Something went wrong. Please reach out to ${ctx.developerUsername ?? "the developer"} with a way to reproduce the error and the following:\n### UUID: \`${ctx.uuid}\``, "Error").component] };
288
292
  }
289
293
  };
290
294
 
291
295
  //#endregion
292
296
  //#region src/gates/combinators.ts
293
297
  function and(...gates) {
294
- return require_permissions.defineGate(gates.map((gate) => gate.name).join(" & "), async (ctx) => {
295
- for (const gate of gates) await require_permissions.runCheck(gate, ctx);
298
+ return require_cursor.defineGate(gates.map((gate) => gate.name).join(" & "), async (ctx) => {
299
+ for (const gate of gates) await require_cursor.runCheck(gate, ctx);
296
300
  });
297
301
  }
298
302
  function isOrOptions(arg) {
@@ -302,17 +306,17 @@ function or(...args) {
302
306
  const last = args.at(-1);
303
307
  const options = last !== void 0 && isOrOptions(last) ? last : void 0;
304
308
  const gates = options ? args.slice(0, -1) : args;
305
- return require_permissions.defineGate(gates.map((gate) => gate.name).join(" | "), async (ctx) => {
309
+ return require_cursor.defineGate(gates.map((gate) => gate.name).join(" | "), async (ctx) => {
306
310
  const summaries = [];
307
311
  let everyArmHasSummary = true;
308
312
  for (const gate of gates) {
309
- const mark = require_permissions.markCommits(ctx);
313
+ const mark = require_cursor.markCommits(ctx);
310
314
  try {
311
- await require_permissions.runCheck(gate, ctx);
315
+ await require_cursor.runCheck(gate, ctx);
312
316
  return;
313
317
  } catch (error) {
314
- if (error instanceof require_permissions.Notice && !(error instanceof Fault) && !error.report) {
315
- require_permissions.rollbackCommits(ctx, mark);
318
+ if (error instanceof require_cursor.Notice && !(error instanceof Fault) && !error.report) {
319
+ require_cursor.rollbackCommits(ctx, mark);
316
320
  if (error.summary === void 0) everyArmHasSummary = false;
317
321
  else summaries.push(error.summary);
318
322
  continue;
@@ -321,8 +325,8 @@ function or(...args) {
321
325
  }
322
326
  }
323
327
  if (options) throw typeof options.fail === "function" ? options.fail(ctx) : options.fail;
324
- if (everyArmHasSummary && summaries.length > 0) throw new require_permissions.NeedsAny(summaries);
325
- throw new require_permissions.NotAllowed();
328
+ if (everyArmHasSummary && summaries.length > 0) throw new require_cursor.NeedsAny(summaries);
329
+ throw new require_cursor.NotAllowed();
326
330
  });
327
331
  }
328
332
 
@@ -355,10 +359,10 @@ function or(...args) {
355
359
  * ```
356
360
  */
357
361
  function OwnerOnly(options) {
358
- return require_permissions.defineGate("OwnerOnly", (ctx) => {
362
+ return require_cursor.defineGate("OwnerOnly", (ctx) => {
359
363
  const owners = ctx.core.config.ownerIds ?? [];
360
364
  if (ctx.userId !== null && owners.includes(ctx.userId)) return;
361
- throw require_permissions.pickNotice(options, (message) => new require_permissions.NotOwner(message));
365
+ throw require_cursor.pickNotice(options, (message) => new require_cursor.NotOwner(message));
362
366
  });
363
367
  }
364
368
  /**
@@ -384,9 +388,9 @@ function OwnerOnly(options) {
384
388
  * ```
385
389
  */
386
390
  function GuildOnly(options) {
387
- return require_permissions.defineGate("GuildOnly", (ctx) => {
391
+ return require_cursor.defineGate("GuildOnly", (ctx) => {
388
392
  if (ctx.guildId) return;
389
- throw require_permissions.pickNotice(options, (message) => new require_permissions.NotInGuild(message));
393
+ throw require_cursor.pickNotice(options, (message) => new require_cursor.NotInGuild(message));
390
394
  });
391
395
  }
392
396
  /**
@@ -410,9 +414,9 @@ function GuildOnly(options) {
410
414
  * ```
411
415
  */
412
416
  function DmOnly(options) {
413
- return require_permissions.defineGate("DmOnly", (ctx) => {
417
+ return require_cursor.defineGate("DmOnly", (ctx) => {
414
418
  if (!ctx.guildId) return;
415
- throw require_permissions.pickNotice(options, (message) => new require_permissions.NotInDm(message));
419
+ throw require_cursor.pickNotice(options, (message) => new require_cursor.NotInDm(message));
416
420
  });
417
421
  }
418
422
 
@@ -484,44 +488,123 @@ function Cooldown(duration, options) {
484
488
  };
485
489
  const anonId = anonSeq++;
486
490
  const keyOf = (ctx) => `cooldown:${ctx.routeId ?? `anon${anonId}`}:${per}:w${windowMs}:l${options?.limit ?? 1}:${scopeValue(ctx, per)}`;
487
- return require_permissions.defineEffectGate("Cooldown", async (ctx) => {
491
+ return require_cursor.defineEffectGate("Cooldown", async (ctx) => {
488
492
  const result = await ctx.core.rateLimiter.peek(keyOf(ctx), window);
489
493
  if (!result.limited) return;
490
494
  if (options?.notice) throw options.notice(result.resetAt);
491
- throw new require_permissions.OnCooldown(result.resetAt, options?.message?.(result.resetAt));
495
+ throw new require_cursor.OnCooldown(result.resetAt, options?.message?.(result.resetAt));
492
496
  }, async (ctx) => {
493
497
  await ctx.core.rateLimiter.charge(keyOf(ctx), window);
494
498
  });
495
499
  }
496
500
 
497
501
  //#endregion
498
- //#region src/stops/Silence.ts
502
+ //#region src/permissions/assert.ts
499
503
  /**
500
- * Throw to stop a handler with no reply and no report.
504
+ * Refuses when the subject is missing a permission in `scope`, or with `inverse` when it holds one.
505
+ * The Administrator bit passes any scope, and on the inverse arm it reports every bit in `scope`.
501
506
  *
502
- * The framework boundary catches `Silence` before {@link Notice}, makes zero Discord calls, and stops.
503
- * Ideally you'd only throw this in `EventHandlers` (or `Gates` for those), because it doesn't make sense to
504
- * leave the user with no reply for an interaction.
507
+ * The caller computes `permissions`, gateway off a discord.js role, member, or channel, and HTTP off the
508
+ * bitfield the interaction payload carries.
509
+ *
510
+ * @throws A {@link Notice} listing the missing or present permissions.
505
511
  *
506
512
  * @example
507
513
  * ```ts
508
- * import { Silence } from '@seedcord/gateway';
514
+ * import { assertPermissions } from '@seedcord/core';
515
+ * import { PermissionFlagsBits } from 'discord-api-types/v10';
516
+ *
517
+ * // gateway, the bits come off a discord.js member
518
+ * assertPermissions({
519
+ * subject: `<@${member.id}>`,
520
+ * permissions: member.permissions.bitfield,
521
+ * scope: [PermissionFlagsBits.BanMembers]
522
+ * });
523
+ * ```
509
524
  *
510
- * // before any reply or defer, drop the interaction with no reply and no report
511
- * if (await isBlacklisted(interaction.user.id)) throw new Silence('blacklisted user');
525
+ * @example
526
+ * ```ts
527
+ * // HTTP, the payload carries the member's bits as a string
528
+ * assertPermissions({
529
+ * subject: `<@${this.event.member.user.id}>`,
530
+ * permissions: BigInt(this.event.member.permissions),
531
+ * scope: [PermissionFlagsBits.ManageMessages]
532
+ * });
533
+ * ```
534
+ *
535
+ * @example
536
+ * ```ts
537
+ * // inverse, refuse a role that carries Administrator
538
+ * assertPermissions({
539
+ * subject: `<@&${role.id}>`,
540
+ * permissions: role.permissions.bitfield,
541
+ * scope: [PermissionFlagsBits.Administrator],
542
+ * inverse: true
543
+ * });
544
+ * ```
545
+ *
546
+ * @example
547
+ * ```ts
548
+ * import { Notice } from '@seedcord/core';
549
+ *
550
+ * // a replacement notice takes the same three arguments the default one does
551
+ * class CannotBan extends Notice {
552
+ * public constructor(
553
+ * message: string | undefined,
554
+ * private readonly subject: string,
555
+ * private readonly missing: readonly string[]
556
+ * ) {
557
+ * super(message ?? 'You cannot ban here.');
558
+ * }
559
+ *
560
+ * public render(): ReplyResponse {
561
+ * return { components: [new BanDenied(this.subject, this.missing).component] };
562
+ * }
563
+ * }
564
+ *
565
+ * assertPermissions({
566
+ * subject: `<@${member.id}>`,
567
+ * permissions: member.permissions.bitfield,
568
+ * scope: [PermissionFlagsBits.BanMembers],
569
+ * missingNotice: CannotBan
570
+ * });
512
571
  * ```
513
572
  */
514
- var Silence = class extends Error {
515
- reason;
516
- /**
517
- * @param reason - Optional note written only to a debug log, never shown to the user or reported.
518
- */
519
- constructor(reason) {
520
- super(reason ?? "Silence");
521
- this.reason = reason;
522
- Error.captureStackTrace(this, this.constructor);
573
+ function assertPermissions(check) {
574
+ const { subject, permissions, scope, inverse = false, missingNotice, dangerousNotice } = check;
575
+ if (inverse) {
576
+ const present = require_cursor.presentNames(permissions, scope);
577
+ if (present.length === 0) return;
578
+ throw new (dangerousNotice ?? require_cursor.HasDangerousPermissions)(void 0, subject, present);
523
579
  }
524
- };
580
+ if (require_cursor.hasAll(permissions, scope)) return;
581
+ throw new (missingNotice ?? require_cursor.MissingPermissions)(void 0, subject, require_cursor.missingNames(permissions, scope));
582
+ }
583
+
584
+ //#endregion
585
+ //#region src/permissions/mergeRoles.ts
586
+ /**
587
+ * The role ids a member holds after adding and removing the given sets. Removals win over additions.
588
+ *
589
+ * This does not write the roles. Apply the result with `member.roles.set()` on gateway, or PATCH the member over HTTP.
590
+ *
591
+ * @param current - The role ids the member holds.
592
+ * @param add - The role ids to add.
593
+ * @param remove - The role ids to remove.
594
+ * @returns The merged role ids.
595
+ *
596
+ * @example
597
+ * ```ts
598
+ * import { mergeRoles } from '@seedcord/core';
599
+ *
600
+ * // gateway
601
+ * const ids = mergeRoles(member.roles.cache.map((role) => role.id), ['123'], ['456']);
602
+ * await member.roles.set(ids);
603
+ * ```
604
+ */
605
+ function mergeRoles(current, add, remove) {
606
+ return [...new Set(current).union(new Set(add)).difference(new Set(remove))];
607
+ }
525
608
 
526
609
  //#endregion
527
610
  //#region src/pagination/paginate.ts
@@ -549,41 +632,257 @@ function paginate(items, page, perPage) {
549
632
  };
550
633
  }
551
634
 
635
+ //#endregion
636
+ //#region src/pagination/controls.ts
637
+ const DEFAULT_LABEL = {
638
+ first: "First",
639
+ prev: "Prev",
640
+ indicator: "",
641
+ next: "Next",
642
+ last: "Last"
643
+ };
644
+ const CONTROL_SLOT = {
645
+ first: 0,
646
+ prev: 1,
647
+ indicator: 2,
648
+ next: 3,
649
+ last: 4
650
+ };
651
+ const MAX_BUTTONS_PER_ROW = 5;
652
+ var Controls = class {
653
+ cursor;
654
+ view;
655
+ constructor(cursor, view) {
656
+ this.cursor = cursor;
657
+ this.view = view;
658
+ }
659
+ button(key, cosmetics) {
660
+ const { target, disabled } = this.navState(key);
661
+ const defaultLabel = key === "indicator" ? this.indicatorText() : DEFAULT_LABEL[key];
662
+ const label = cosmetics?.label ?? (key === "indicator" || !cosmetics?.emoji ? defaultLabel : void 0);
663
+ const button = new _discordjs_builders.ButtonBuilder().setCustomId(this.cursor.encode({
664
+ page: Math.min(Math.max(0, target), require_cursor.PAGE_MAX),
665
+ slot: CONTROL_SLOT[key]
666
+ })).setStyle(cosmetics?.style ?? discord_api_types_v10.ButtonStyle.Secondary).setDisabled(disabled);
667
+ if (label !== void 0) button.setLabel(label);
668
+ if (cosmetics?.emoji) button.setEmoji(cosmetics.emoji);
669
+ return button;
670
+ }
671
+ navState(key) {
672
+ const { page, totalPages, hasPrev, hasNext } = this.view;
673
+ const knownTotal = totalPages !== void 0;
674
+ return {
675
+ target: {
676
+ first: 0,
677
+ prev: page - 1,
678
+ indicator: page,
679
+ next: page + 1,
680
+ last: knownTotal ? totalPages - 1 : page
681
+ }[key],
682
+ disabled: {
683
+ first: !hasPrev,
684
+ prev: !hasPrev,
685
+ indicator: true,
686
+ next: !hasNext || page + 1 > 1e6,
687
+ last: !hasNext || !knownTotal || totalPages - 1 > 1e6
688
+ }[key]
689
+ };
690
+ }
691
+ row(...keys) {
692
+ if (keys.length === 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationEmptyControls);
693
+ if (keys.length > MAX_BUTTONS_PER_ROW) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationTooManyControls, [keys.length]);
694
+ const seen = /* @__PURE__ */ new Set();
695
+ for (const key of keys) {
696
+ if (seen.has(key)) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.PaginationDuplicateControls, [key]);
697
+ seen.add(key);
698
+ }
699
+ return new _discordjs_builders.ActionRowBuilder().addComponents(keys.map((key) => this.button(key)));
700
+ }
701
+ indicatorText() {
702
+ const { page, totalPages } = this.view;
703
+ return totalPages === void 0 ? `Page ${page + 1}` : `Page ${page + 1} of ${totalPages}`;
704
+ }
705
+ };
706
+
707
+ //#endregion
708
+ //#region src/pagination/render.ts
709
+ var PageContainer = class extends require_publishDefault.BuilderComponent {
710
+ constructor(view, renderItem, controlRow) {
711
+ super("container");
712
+ const base = view.page * view.perPage;
713
+ let lines = [];
714
+ const flush = () => {
715
+ if (lines.length === 0) return;
716
+ this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(lines.join("\n")));
717
+ lines = [];
718
+ };
719
+ view.items.forEach((item, offset) => {
720
+ const rendered = renderItem(item, base + offset);
721
+ if (typeof rendered === "string") {
722
+ lines.push(rendered);
723
+ return;
724
+ }
725
+ flush();
726
+ this.instance.addSectionComponents(rendered.component);
727
+ });
728
+ flush();
729
+ this.instance.addActionRowComponents(controlRow);
730
+ }
731
+ };
732
+ function toReplyResponse(renderable) {
733
+ if (typeof renderable === "string") return { components: [new _discordjs_builders.TextDisplayBuilder().setContent(renderable)] };
734
+ if (Array.isArray(renderable)) return { components: renderable };
735
+ return renderable;
736
+ }
737
+ const ARRAY_KEYS = [
738
+ "first",
739
+ "prev",
740
+ "indicator",
741
+ "next",
742
+ "last"
743
+ ];
744
+ const CURSOR_KEYS = [
745
+ "prev",
746
+ "indicator",
747
+ "next"
748
+ ];
749
+ /**
750
+ * A `render` override builds the whole tree, otherwise the default container lists the items and appends
751
+ * the controls (all five for a known total, prev/indicator/next for a cursor source).
752
+ */
753
+ function renderPage(view, cursor, config) {
754
+ const controls = new Controls(cursor, view);
755
+ if (config.render) return toReplyResponse(config.render(view, controls));
756
+ return { components: [new PageContainer(view, config.renderItem ?? String, controls.row(...view.totalPages === void 0 ? CURSOR_KEYS : ARRAY_KEYS)).component] };
757
+ }
758
+
759
+ //#endregion
760
+ //#region src/pagination/PaginatorBase.ts
761
+ /**
762
+ * Holds a paginator's cursor and renders one page. Each transport extends this to add the nav handler and
763
+ * the method that sends the first page.
764
+ *
765
+ * @typeParam Item - The item type, inferred from the source.
766
+ * @typeParam Prefix - The route prefix, inferred from `config.prefix`.
767
+ * @typeParam Ctx - The transport's page context.
768
+ */
769
+ var PaginatorBase = class {
770
+ /** The page cursor, pass it to your `@ButtonRoute`. */
771
+ cursor;
772
+ config;
773
+ constructor(config) {
774
+ this.cursor = require_cursor.pageCursor(config.prefix);
775
+ this.config = config;
776
+ }
777
+ /** Render a page as a {@link ReplyResponse}. To post it elsewhere, add `flags: MessageFlags.IsComponentsV2`. */
778
+ async page(ctx, n) {
779
+ return renderPage(await this.config.source.page(ctx, n), 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
+
552
839
  //#endregion
553
840
  //#region src/index.ts
554
841
  /** Package version */
555
- const version = "0.1.0-next.6";
842
+ const version = "0.1.0-next.8";
556
843
 
557
844
  //#endregion
845
+ exports.ArraySource = ArraySource;
558
846
  exports.AutocompleteRoute = AutocompleteRoute;
559
847
  exports.BaseHandler = BaseHandler;
560
- exports.BuilderComponent = require_permissions.BuilderComponent;
848
+ exports.BuilderComponent = require_publishDefault.BuilderComponent;
849
+ exports.Bus = require_subscribers.Bus;
561
850
  exports.ButtonRoute = ButtonRoute;
851
+ exports.Commands = require_cursor.Commands;
562
852
  exports.ContextMenuRoute = ContextMenuRoute;
853
+ exports.ContextMenus = require_cursor.ContextMenus;
563
854
  exports.Cooldown = Cooldown;
564
- exports.CustomId = require_permissions.CustomId;
855
+ exports.CursorSource = CursorSource;
856
+ exports.CustomId = require_cursor.CustomId;
565
857
  exports.DispatchContext = DispatchContext;
566
858
  exports.DmOnly = DmOnly;
567
859
  exports.Fault = Fault;
568
860
  exports.GuildOnly = GuildOnly;
569
861
  exports.ModalRoute = ModalRoute;
570
- exports.Notice = require_permissions.Notice;
862
+ exports.Notice = require_cursor.Notice;
571
863
  exports.OwnerOnly = OwnerOnly;
864
+ exports.PaginatorBase = PaginatorBase;
572
865
  exports.RegisterCommand = RegisterCommand;
573
866
  exports.RepliableHandler = RepliableHandler;
574
- exports.RequireBotPermissions = require_permissions.RequireBotPermissions;
575
- exports.RequirePermissions = require_permissions.RequirePermissions;
576
- exports.RequireRole = require_permissions.RequireRole;
577
- exports.RowComponent = require_permissions.RowComponent;
578
- exports.SelectMenuKind = require_permissions.SelectMenuKind;
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;
579
872
  exports.SelectMenuRoute = SelectMenuRoute;
580
873
  exports.ShutdownPhase = require_phases.ShutdownPhase;
581
- exports.Silence = Silence;
874
+ exports.Silence = require_cursor.Silence;
582
875
  exports.SlashRoute = SlashRoute;
583
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;
584
881
  exports.and = and;
585
- exports.defineEffectGate = require_permissions.defineEffectGate;
586
- exports.defineGate = require_permissions.defineGate;
882
+ exports.assertPermissions = assertPermissions;
883
+ exports.defineEffectGate = require_cursor.defineEffectGate;
884
+ exports.defineGate = require_cursor.defineGate;
885
+ exports.mergeRoles = mergeRoles;
587
886
  exports.or = or;
588
887
  exports.paginate = paginate;
589
888
  exports.version = version;