@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.mjs CHANGED
@@ -1,9 +1,13 @@
1
- import { D as storeComponentRoute, E as selectMenuRouteOf, F as BuilderComponent, I as RowComponent, O as storeInteractionRoute, P as NoticeCard, R as Notice, S as SelectMenuKind, W as InteractionRoutes, _ as NotInGuild, a as pickNotice, b as defineEffectGate, c as rollbackCommits, g as NotInDm, h as NotAllowed, i as RequireRole, j as CustomId, l as runCheck, m as NeedsAny, n as RequireBotPermissions, r as RequirePermissions, s as markCommits, v as NotOwner, w as contextMenuRouteOf, x as defineGate, y as OnCooldown, z as CommandMetadataKey } from "./permissions-RVCBhL2n.mjs";
2
- import { n as StartupPhase, t as ShutdownPhase } from "./phases-BfpAG6w3.mjs";
1
+ import { a as RowComponent, f as InteractionRoutes, i as BuilderComponent, s as CommandMetadataKey } from "./publishDefault-CldnxcT2.mjs";
2
+ import { C as NotInDm, D as defineEffectGate, E as OnCooldown, F as storeInteractionRoute, G as ContextMenus, H as Notice, N as selectMenuRouteOf, O as defineGate, P as storeComponentRoute, R as CustomId, S as NotAllowed, T as NotOwner, V as NoticeCard, W as Commands, a as RequirePermissions, c as hasAll, d as pickNotice, h as runCheck, i as RequireBotPermissions, j as contextMenuRouteOf, k as SelectMenuKind, l as missingNames, m as rollbackCommits, n as pageCursor, o as RequireRole, p as markCommits, r as Silence, t as PAGE_MAX, u as presentNames, v as HasDangerousPermissions, w as NotInGuild, x as NeedsAny, y as MissingPermissions } from "./cursor-BvSH8tgw.mjs";
3
+ import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
4
+ import { a as WebhookLog, i as Subscribe, o as Subscriber, r as WebhookUrl, t as Bus } from "./subscribers-DuxdM2cS.mjs";
3
5
  import "reflect-metadata";
6
+ import { ActionRowBuilder, ButtonBuilder, TextDisplayBuilder } from "@discordjs/builders";
7
+ import { Logger } from "@seedcord/logger";
8
+ import { ButtonStyle } from "discord-api-types/v10";
4
9
  import { SeedcordErrorCode } from "@seedcord/errors";
5
10
  import { SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
6
- import { Logger } from "@seedcord/logger";
7
11
  import { parseDuration } from "@seedcord/utils";
8
12
 
9
13
  //#region src/decorators/Command.ts
@@ -191,10 +195,10 @@ var BaseHandler = class {
191
195
  event;
192
196
  logger;
193
197
  dispatch;
194
- constructor(event, core, dispatch) {
198
+ constructor(event, core, dispatch, channel) {
195
199
  this.core = core;
196
200
  this.event = event;
197
- this.logger = new Logger(this.constructor.name);
201
+ this.logger = new Logger(this.constructor.name, { channel });
198
202
  this.dispatch = dispatch;
199
203
  }
200
204
  };
@@ -214,7 +218,7 @@ var RepliableHandler = class extends BaseHandler {
214
218
  sender;
215
219
  routeId;
216
220
  constructor(event, core, dispatch) {
217
- super(event, core, dispatch);
221
+ super(event, core, dispatch, "interactions");
218
222
  this.routeId = dispatch?.routeId ?? this.constructor.name;
219
223
  this.sender = this.buildSender(event, core, this.routeId);
220
224
  }
@@ -494,33 +498,112 @@ function Cooldown(duration, options) {
494
498
  }
495
499
 
496
500
  //#endregion
497
- //#region src/stops/Silence.ts
501
+ //#region src/permissions/assert.ts
498
502
  /**
499
- * Throw to stop a handler with no reply and no report.
503
+ * Refuses when the subject is missing a permission in `scope`, or with `inverse` when it holds one.
504
+ * The Administrator bit passes any scope, and on the inverse arm it reports every bit in `scope`.
500
505
  *
501
- * The framework boundary catches `Silence` before {@link Notice}, makes zero Discord calls, and stops.
502
- * Ideally you'd only throw this in `EventHandlers` (or `Gates` for those), because it doesn't make sense to
503
- * leave the user with no reply for an interaction.
506
+ * The caller computes `permissions`, gateway off a discord.js role, member, or channel, and HTTP off the
507
+ * bitfield the interaction payload carries.
508
+ *
509
+ * @throws A {@link Notice} listing the missing or present permissions.
504
510
  *
505
511
  * @example
506
512
  * ```ts
507
- * import { Silence } from '@seedcord/gateway';
513
+ * import { assertPermissions } from '@seedcord/core';
514
+ * import { PermissionFlagsBits } from 'discord-api-types/v10';
515
+ *
516
+ * // gateway, the bits come off a discord.js member
517
+ * assertPermissions({
518
+ * subject: `<@${member.id}>`,
519
+ * permissions: member.permissions.bitfield,
520
+ * scope: [PermissionFlagsBits.BanMembers]
521
+ * });
522
+ * ```
508
523
  *
509
- * // before any reply or defer, drop the interaction with no reply and no report
510
- * if (await isBlacklisted(interaction.user.id)) throw new Silence('blacklisted user');
524
+ * @example
525
+ * ```ts
526
+ * // HTTP, the payload carries the member's bits as a string
527
+ * assertPermissions({
528
+ * subject: `<@${this.event.member.user.id}>`,
529
+ * permissions: BigInt(this.event.member.permissions),
530
+ * scope: [PermissionFlagsBits.ManageMessages]
531
+ * });
532
+ * ```
533
+ *
534
+ * @example
535
+ * ```ts
536
+ * // inverse, refuse a role that carries Administrator
537
+ * assertPermissions({
538
+ * subject: `<@&${role.id}>`,
539
+ * permissions: role.permissions.bitfield,
540
+ * scope: [PermissionFlagsBits.Administrator],
541
+ * inverse: true
542
+ * });
543
+ * ```
544
+ *
545
+ * @example
546
+ * ```ts
547
+ * import { Notice } from '@seedcord/core';
548
+ *
549
+ * // a replacement notice takes the same three arguments the default one does
550
+ * class CannotBan extends Notice {
551
+ * public constructor(
552
+ * message: string | undefined,
553
+ * private readonly subject: string,
554
+ * private readonly missing: readonly string[]
555
+ * ) {
556
+ * super(message ?? 'You cannot ban here.');
557
+ * }
558
+ *
559
+ * public render(): ReplyResponse {
560
+ * return { components: [new BanDenied(this.subject, this.missing).component] };
561
+ * }
562
+ * }
563
+ *
564
+ * assertPermissions({
565
+ * subject: `<@${member.id}>`,
566
+ * permissions: member.permissions.bitfield,
567
+ * scope: [PermissionFlagsBits.BanMembers],
568
+ * missingNotice: CannotBan
569
+ * });
511
570
  * ```
512
571
  */
513
- var Silence = class extends Error {
514
- reason;
515
- /**
516
- * @param reason - Optional note written only to a debug log, never shown to the user or reported.
517
- */
518
- constructor(reason) {
519
- super(reason ?? "Silence");
520
- this.reason = reason;
521
- Error.captureStackTrace(this, this.constructor);
572
+ function assertPermissions(check) {
573
+ const { subject, permissions, scope, inverse = false, missingNotice, dangerousNotice } = check;
574
+ if (inverse) {
575
+ const present = presentNames(permissions, scope);
576
+ if (present.length === 0) return;
577
+ throw new (dangerousNotice ?? HasDangerousPermissions)(void 0, subject, present);
522
578
  }
523
- };
579
+ if (hasAll(permissions, scope)) return;
580
+ throw new (missingNotice ?? MissingPermissions)(void 0, subject, missingNames(permissions, scope));
581
+ }
582
+
583
+ //#endregion
584
+ //#region src/permissions/mergeRoles.ts
585
+ /**
586
+ * The role ids a member holds after adding and removing the given sets. Removals win over additions.
587
+ *
588
+ * This does not write the roles. Apply the result with `member.roles.set()` on gateway, or PATCH the member over HTTP.
589
+ *
590
+ * @param current - The role ids the member holds.
591
+ * @param add - The role ids to add.
592
+ * @param remove - The role ids to remove.
593
+ * @returns The merged role ids.
594
+ *
595
+ * @example
596
+ * ```ts
597
+ * import { mergeRoles } from '@seedcord/core';
598
+ *
599
+ * // gateway
600
+ * const ids = mergeRoles(member.roles.cache.map((role) => role.id), ['123'], ['456']);
601
+ * await member.roles.set(ids);
602
+ * ```
603
+ */
604
+ function mergeRoles(current, add, remove) {
605
+ return [...new Set(current).union(new Set(add)).difference(new Set(remove))];
606
+ }
524
607
 
525
608
  //#endregion
526
609
  //#region src/pagination/paginate.ts
@@ -548,11 +631,215 @@ function paginate(items, page, perPage) {
548
631
  };
549
632
  }
550
633
 
634
+ //#endregion
635
+ //#region src/pagination/controls.ts
636
+ const DEFAULT_LABEL = {
637
+ first: "First",
638
+ prev: "Prev",
639
+ indicator: "",
640
+ next: "Next",
641
+ last: "Last"
642
+ };
643
+ const CONTROL_SLOT = {
644
+ first: 0,
645
+ prev: 1,
646
+ indicator: 2,
647
+ next: 3,
648
+ last: 4
649
+ };
650
+ const MAX_BUTTONS_PER_ROW = 5;
651
+ var Controls = class {
652
+ cursor;
653
+ view;
654
+ constructor(cursor, view) {
655
+ this.cursor = cursor;
656
+ this.view = view;
657
+ }
658
+ button(key, cosmetics) {
659
+ const { target, disabled } = this.navState(key);
660
+ const defaultLabel = key === "indicator" ? this.indicatorText() : DEFAULT_LABEL[key];
661
+ const label = cosmetics?.label ?? (key === "indicator" || !cosmetics?.emoji ? defaultLabel : void 0);
662
+ const button = new ButtonBuilder().setCustomId(this.cursor.encode({
663
+ page: Math.min(Math.max(0, target), PAGE_MAX),
664
+ slot: CONTROL_SLOT[key]
665
+ })).setStyle(cosmetics?.style ?? ButtonStyle.Secondary).setDisabled(disabled);
666
+ if (label !== void 0) button.setLabel(label);
667
+ if (cosmetics?.emoji) button.setEmoji(cosmetics.emoji);
668
+ return button;
669
+ }
670
+ navState(key) {
671
+ const { page, totalPages, hasPrev, hasNext } = this.view;
672
+ const knownTotal = totalPages !== void 0;
673
+ return {
674
+ target: {
675
+ first: 0,
676
+ prev: page - 1,
677
+ indicator: page,
678
+ next: page + 1,
679
+ last: knownTotal ? totalPages - 1 : page
680
+ }[key],
681
+ disabled: {
682
+ first: !hasPrev,
683
+ prev: !hasPrev,
684
+ indicator: true,
685
+ next: !hasNext || page + 1 > 1e6,
686
+ last: !hasNext || !knownTotal || totalPages - 1 > 1e6
687
+ }[key]
688
+ };
689
+ }
690
+ row(...keys) {
691
+ if (keys.length === 0) throw new SeedcordRangeError(SeedcordErrorCode.PaginationEmptyControls);
692
+ if (keys.length > MAX_BUTTONS_PER_ROW) throw new SeedcordRangeError(SeedcordErrorCode.PaginationTooManyControls, [keys.length]);
693
+ const seen = /* @__PURE__ */ new Set();
694
+ for (const key of keys) {
695
+ if (seen.has(key)) throw new SeedcordTypeError(SeedcordErrorCode.PaginationDuplicateControls, [key]);
696
+ seen.add(key);
697
+ }
698
+ return new ActionRowBuilder().addComponents(keys.map((key) => this.button(key)));
699
+ }
700
+ indicatorText() {
701
+ const { page, totalPages } = this.view;
702
+ return totalPages === void 0 ? `Page ${page + 1}` : `Page ${page + 1} of ${totalPages}`;
703
+ }
704
+ };
705
+
706
+ //#endregion
707
+ //#region src/pagination/render.ts
708
+ var PageContainer = class extends BuilderComponent {
709
+ constructor(view, renderItem, controlRow) {
710
+ super("container");
711
+ const base = view.page * view.perPage;
712
+ let lines = [];
713
+ const flush = () => {
714
+ if (lines.length === 0) return;
715
+ this.instance.addTextDisplayComponents(new TextDisplayBuilder().setContent(lines.join("\n")));
716
+ lines = [];
717
+ };
718
+ view.items.forEach((item, offset) => {
719
+ const rendered = renderItem(item, base + offset);
720
+ if (typeof rendered === "string") {
721
+ lines.push(rendered);
722
+ return;
723
+ }
724
+ flush();
725
+ this.instance.addSectionComponents(rendered.component);
726
+ });
727
+ flush();
728
+ this.instance.addActionRowComponents(controlRow);
729
+ }
730
+ };
731
+ function toReplyResponse(renderable) {
732
+ if (typeof renderable === "string") return { components: [new TextDisplayBuilder().setContent(renderable)] };
733
+ if (Array.isArray(renderable)) return { components: renderable };
734
+ return renderable;
735
+ }
736
+ const ARRAY_KEYS = [
737
+ "first",
738
+ "prev",
739
+ "indicator",
740
+ "next",
741
+ "last"
742
+ ];
743
+ const CURSOR_KEYS = [
744
+ "prev",
745
+ "indicator",
746
+ "next"
747
+ ];
748
+ /**
749
+ * A `render` override builds the whole tree, otherwise the default container lists the items and appends
750
+ * the controls (all five for a known total, prev/indicator/next for a cursor source).
751
+ */
752
+ function renderPage(view, cursor, config) {
753
+ const controls = new Controls(cursor, view);
754
+ if (config.render) return toReplyResponse(config.render(view, controls));
755
+ return { components: [new PageContainer(view, config.renderItem ?? String, controls.row(...view.totalPages === void 0 ? CURSOR_KEYS : ARRAY_KEYS)).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 = 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
+ return renderPage(await this.config.source.page(ctx, n), this.cursor, this.config);
779
+ }
780
+ };
781
+
782
+ //#endregion
783
+ //#region src/pagination/sources.ts
784
+ const DEFAULT_PER_PAGE = 10;
785
+ function perPageOf(opts) {
786
+ const perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
787
+ if (!Number.isSafeInteger(perPage) || perPage <= 0) throw new SeedcordRangeError(SeedcordErrorCode.PaginationInvalidPerPage, [perPage]);
788
+ return perPage;
789
+ }
790
+ /**
791
+ * A source for a bounded list you can load whole. It loads the full list on every click, then slices the
792
+ * page via {@link paginate}, so the real total is known (a real last button, "Page X of Y"). Cache inside
793
+ * your loader if the load is expensive.
794
+ *
795
+ * @typeParam Item - The item type, inferred from the loader's return.
796
+ * @typeParam Ctx - The transport's page context. Each transport exports a subclass that fixes it.
797
+ */
798
+ var ArraySource = class {
799
+ load;
800
+ perPage;
801
+ constructor(load, opts) {
802
+ this.load = load;
803
+ this.perPage = perPageOf(opts);
804
+ }
805
+ async page(ctx, n) {
806
+ return paginate(await this.load(ctx), n, this.perPage);
807
+ }
808
+ };
809
+ /**
810
+ * A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
811
+ * The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
812
+ * source has no cheap total, so `totalPages` is undefined, the last button is omitted, and the indicator
813
+ * reads "Page X".
814
+ *
815
+ * @typeParam Item - The item type, inferred from the fetcher's slice.
816
+ * @typeParam Ctx - The transport's page context. Each transport exports a subclass that fixes it.
817
+ */
818
+ var CursorSource = class {
819
+ fetch;
820
+ perPage;
821
+ constructor(fetch, opts) {
822
+ this.fetch = fetch;
823
+ this.perPage = perPageOf(opts);
824
+ }
825
+ async page(ctx, n) {
826
+ const page = Math.max(0, Math.trunc(n));
827
+ const { items, hasNext } = await this.fetch(ctx, page, this.perPage);
828
+ return {
829
+ items: [...items],
830
+ page,
831
+ perPage: this.perPage,
832
+ hasPrev: page > 0,
833
+ hasNext
834
+ };
835
+ }
836
+ };
837
+
551
838
  //#endregion
552
839
  //#region src/index.ts
553
840
  /** Package version */
554
- const version = "0.1.0-next.6";
841
+ const version = "0.1.0-next.8";
555
842
 
556
843
  //#endregion
557
- export { AutocompleteRoute, BaseHandler, BuilderComponent, ButtonRoute, ContextMenuRoute, Cooldown, CustomId, DispatchContext, DmOnly, Fault, GuildOnly, ModalRoute, Notice, OwnerOnly, RegisterCommand, RepliableHandler, RequireBotPermissions, RequirePermissions, RequireRole, RowComponent, SelectMenuKind, SelectMenuRoute, ShutdownPhase, Silence, SlashRoute, StartupPhase, and, defineEffectGate, defineGate, or, paginate, version };
844
+ export { ArraySource, AutocompleteRoute, BaseHandler, BuilderComponent, Bus, ButtonRoute, Commands, ContextMenuRoute, ContextMenus, Cooldown, CursorSource, CustomId, DispatchContext, DmOnly, Fault, GuildOnly, ModalRoute, Notice, OwnerOnly, PaginatorBase, RegisterCommand, RepliableHandler, RequireBotPermissions, RequirePermissions, RequireRole, RowComponent, SelectMenuKind, SelectMenuRoute, ShutdownPhase, Silence, SlashRoute, StartupPhase, Subscribe, Subscriber, WebhookLog, WebhookUrl, and, assertPermissions, defineEffectGate, defineGate, mergeRoles, or, paginate, version };
558
845
  //# sourceMappingURL=index.mjs.map