@seedcord/http 0.9.1 → 0.10.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.
@@ -1,5 +1,5 @@
1
1
  import "reflect-metadata";
2
- import { GatedMetadataKey, MiddlewareRegistry, PublishDefault, RegisterDefaults, RegisterSubscriber, accessorStore, asError, clearStore, decodeComponentRoute, deferFlags, guardedAccessor, interactionMiddleware, isEmojiTuple, outcomeFor, queuedMsFor, reportDispatch, reportedWrite, resultFor, runAfter, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
2
+ import { GatedMetadataKey, MiddlewareRegistry, PublishDefault, RegisterDefaults, RegisterSubscriber, SubscribeMetadataKey, accessorStore, asError, clearStore, decodeComponentRoute, deferFlags, guardedAccessor, interactionMiddleware, interactionRoutesOf, isEmojiTuple, outcomeFor, queuedMsFor, registrationFor, reportDispatch, reportedWrite, resultFor, runAfter, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
3
3
  import { SeedcordError, SeedcordTypeError, applicationIdFromToken, validateDiscordToken } from "@seedcord/errors/internal";
4
4
  import { Logger } from "@seedcord/logger";
5
5
  import { Converters, Envapter } from "envapt";
@@ -296,9 +296,6 @@ function createCore(config, token) {
296
296
  draft.bus = new Bus(draft);
297
297
  return draft;
298
298
  }
299
- function isHandlerCtor(value) {
300
- return typeof value === "function" && value.prototype instanceof BaseHandler;
301
- }
302
299
  const HARMLESS_API_CODES = /* @__PURE__ */ new Set([
303
300
  RESTJSONErrorCodes.UnknownInteraction,
304
301
  RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
@@ -426,21 +423,6 @@ async function answer(caught, scope, report, outcome = outcomeFor(caught)) {
426
423
  report(outcome);
427
424
  }
428
425
  }
429
- async function loadHandlerCtor(match, payload, core, dispatch, report) {
430
- const routeId = unhandledRouteId(match);
431
- let exported;
432
- try {
433
- exported = await match.load();
434
- } catch (caught) {
435
- logger().error(`Route ${paint.sky.bold(routeId)} failed to load its handler.`, caught);
436
- await answer(caught, freshScope(match, payload, core, dispatch), report, "failed");
437
- return null;
438
- }
439
- if (isHandlerCtor(exported)) return exported;
440
- logger().error(`Route ${paint.sky.bold(routeId)} loaded an export that is not a handler class.`);
441
- await answer(/* @__PURE__ */ new Error(`route ${routeId} loaded an export that is not a handler class`), freshScope(match, payload, core, dispatch), report, "failed");
442
- return null;
443
- }
444
426
  async function runMiddlewares(step, kind, sender) {
445
427
  const { args, dispatch, ran } = step;
446
428
  for (const Middleware of args.middlewares.chainFor(kind)) {
@@ -465,8 +447,7 @@ async function dispatchInteraction(args) {
465
447
  const routeId = unhandledRouteId(match);
466
448
  const dispatch = new DispatchContext(routeId);
467
449
  const report = dispatchReporter(match, payload, core, dispatch.id);
468
- const Handler = await loadHandlerCtor(match, payload, core, dispatch, report);
469
- if (!Handler) return null;
450
+ const Handler = match.ctor;
470
451
  logger().debug(`Processing ${paint.sky.bold(routeId)} with ${paint.mute(Handler.name)}`);
471
452
  let handler;
472
453
  try {
@@ -526,36 +507,6 @@ async function gateRefusal(step) {
526
507
  }
527
508
  }
528
509
 
529
- //#endregion
530
- //#region src/dispatch/registerSubscribers.ts
531
- function registerSubscribers(bus, manifest) {
532
- for (const row of manifest.subscriberRoutes) bus[RegisterSubscriber]({
533
- keys: row.keys,
534
- frequency: row.frequency,
535
- resolve: async () => {
536
- const keys = row.keys.join(", ");
537
- let module;
538
- try {
539
- module = await row.load();
540
- } catch (caught) {
541
- throw new SeedcordError(SeedcordErrorCode.RouteModuleLoadFailed, [keys, row.from], { cause: caught });
542
- }
543
- if (!Object.hasOwn(module, row.exportName)) throw new SeedcordError(SeedcordErrorCode.InteractionRouteExportMissing, [
544
- keys,
545
- row.exportName,
546
- row.from
547
- ]);
548
- const exported = module[row.exportName];
549
- if (typeof exported !== "function" || !(exported.prototype instanceof Subscriber)) throw new SeedcordTypeError(SeedcordErrorCode.SubscriberRouteNotASubscriber, [
550
- keys,
551
- row.exportName,
552
- row.from
553
- ]);
554
- return exported;
555
- }
556
- });
557
- }
558
-
559
510
  //#endregion
560
511
  //#region src/inputs/HttpSlashOptions.ts
561
512
  var HttpSlashOptions = class {
@@ -711,14 +662,6 @@ var AutocompleteHandler = class extends BaseHandler {
711
662
  }
712
663
  };
713
664
 
714
- //#endregion
715
- //#region src/handlers/defaults/UnhandledAutocomplete.ts
716
- var UnhandledAutocomplete = class extends AutocompleteHandler {
717
- async execute() {
718
- await this.respond([]);
719
- }
720
- };
721
-
722
665
  //#endregion
723
666
  //#region src/handlers/interaction/InteractionHandler.ts
724
667
  /**
@@ -743,6 +686,80 @@ var InteractionHandler = class extends RepliableHandler$1 {
743
686
  }
744
687
  };
745
688
 
689
+ //#endregion
690
+ //#region src/handlers/interaction/InteractionMiddleware.ts
691
+ /**
692
+ * Base class for interaction middleware on the HTTP transport.
693
+ *
694
+ * Middleware runs after the dispatcher builds the handler and before the gates, over the handler's own
695
+ * reply surface. Name the kinds in the generic and list the same ones in `{ kinds }`. Several kinds give
696
+ * `this.event` a union. Narrow it on `type` and `data.component_type`. A catchall reads every repliable
697
+ * kind.
698
+ *
699
+ * @typeParam Kind - One or more {@link InteractionKind} members. Defaults to every repliable kind.
700
+ *
701
+ * @example
702
+ * ```ts
703
+ * \@RegisterInteractionMiddleware({ kinds: [InteractionKind.Button] })
704
+ * class Audit extends InteractionMiddleware<InteractionKind.Button> {
705
+ * async execute() {
706
+ * this.logger.info(this.event.data.custom_id);
707
+ * }
708
+ * }
709
+ * ```
710
+ */
711
+ var InteractionMiddleware = class extends RepliableHandler$1 {
712
+ constructor(event, core, dispatch, sender) {
713
+ super(event, core, dispatch, sender);
714
+ }
715
+ };
716
+
717
+ //#endregion
718
+ //#region src/manifest/entries.ts
719
+ function isHandlerClass(value) {
720
+ if (typeof value !== "function") return false;
721
+ return value.prototype instanceof InteractionHandler || value.prototype instanceof AutocompleteHandler;
722
+ }
723
+ function isMiddlewareClass(value) {
724
+ return typeof value === "function" && value.prototype instanceof InteractionMiddleware;
725
+ }
726
+ function isSubscriberClass(value) {
727
+ return typeof value === "function" && value.prototype instanceof Subscriber;
728
+ }
729
+ function wrongClass(array, value, base) {
730
+ const name = typeof value === "function" ? value.name : String(value);
731
+ return new SeedcordTypeError(SeedcordErrorCode.ManifestEntryWrongClass, [
732
+ array,
733
+ name,
734
+ base
735
+ ]);
736
+ }
737
+ function noRoutes(array, className, decorator) {
738
+ return new SeedcordError(SeedcordErrorCode.ManifestEntryNoRoutes, [
739
+ array,
740
+ className,
741
+ decorator
742
+ ]);
743
+ }
744
+
745
+ //#endregion
746
+ //#region src/dispatch/registerSubscribers.ts
747
+ function registerSubscribers(bus, subscribers) {
748
+ for (const ctor of subscribers) {
749
+ if (!isSubscriberClass(ctor)) throw wrongClass("subscribers", ctor, "Subscriber");
750
+ if (!Reflect.hasMetadata(SubscribeMetadataKey, ctor)) throw noRoutes("subscribers", ctor.name, "@Subscribe");
751
+ bus[RegisterSubscriber](registrationFor(ctor));
752
+ }
753
+ }
754
+
755
+ //#endregion
756
+ //#region src/handlers/defaults/UnhandledAutocomplete.ts
757
+ var UnhandledAutocomplete = class extends AutocompleteHandler {
758
+ async execute() {
759
+ await this.respond([]);
760
+ }
761
+ };
762
+
746
763
  //#endregion
747
764
  //#region src/handlers/defaults/UnhandledRepliable.ts
748
765
  var UnhandledRepliable = class extends InteractionHandler {
@@ -772,32 +789,8 @@ function componentMapKey(type) {
772
789
  default: return null;
773
790
  }
774
791
  }
775
- function namedExport(routeId, row) {
776
- return async () => {
777
- let moduleExports;
778
- try {
779
- moduleExports = await row.load();
780
- } catch (caught) {
781
- throw new SeedcordError(SeedcordErrorCode.RouteModuleLoadFailed, [routeId, row.from], { cause: caught });
782
- }
783
- if (!Object.hasOwn(moduleExports, row.exportName)) throw new SeedcordError(SeedcordErrorCode.InteractionRouteExportMissing, [
784
- routeId,
785
- row.exportName,
786
- row.from
787
- ]);
788
- return moduleExports[row.exportName];
789
- };
790
- }
791
- function describeRow(row) {
792
- return `${row.exportName} (${row.from})`;
793
- }
794
- /**
795
- * Builds the per-kind lookup maps.
796
- *
797
- * @throws A **SeedcordError** when two rows resolve to the same route.
798
- */
799
- function buildRouteMaps(manifest) {
800
- const maps = {
792
+ function emptyRouteMaps() {
793
+ return {
801
794
  [InteractionKind.Slash]: /* @__PURE__ */ new Map(),
802
795
  [InteractionKind.UserContextMenu]: /* @__PURE__ */ new Map(),
803
796
  [InteractionKind.MessageContextMenu]: /* @__PURE__ */ new Map(),
@@ -810,44 +803,20 @@ function buildRouteMaps(manifest) {
810
803
  [InteractionKind.MentionableMenu]: /* @__PURE__ */ new Map(),
811
804
  [InteractionKind.Modal]: /* @__PURE__ */ new Map()
812
805
  };
813
- const owners = /* @__PURE__ */ new Map();
814
- function set(kind, key, row) {
815
- const routeId = `${kind}:${key}`;
816
- const owner = owners.get(routeId);
817
- if (owner) throw new SeedcordError(SeedcordErrorCode.InteractionDuplicateRoute, [
818
- routeId,
819
- describeRow(owner),
820
- describeRow(row)
821
- ]);
822
- owners.set(routeId, row);
823
- maps[kind].set(key, {
824
- kind,
825
- routeId,
826
- load: namedExport(routeId, row)
827
- });
828
- }
829
- for (const row of manifest.commandRoutes) {
830
- const kind = commandKind(row.type);
831
- if (kind) set(kind, row.name, row);
832
- }
833
- for (const row of manifest.autocompleteRoutes) set(InteractionKind.Autocomplete, row.name, row);
834
- for (const row of manifest.componentRoutes) set(row.kind, row.prefix, row);
835
- return maps;
836
806
  }
837
807
  function unhandled(kind, attemptedKey) {
838
808
  return {
839
809
  kind,
840
810
  routeId: null,
841
811
  attemptedKey,
842
- load: () => Promise.resolve(kind === InteractionKind.Autocomplete ? UnhandledAutocomplete : UnhandledRepliable)
812
+ ctor: kind === InteractionKind.Autocomplete ? UnhandledAutocomplete : UnhandledRepliable
843
813
  };
844
814
  }
845
815
  /**
846
- * Matches a verified non-PING interaction to its manifest row. A known kind with no row resolves to the
847
- * unhandled default, whose handler replies "Feature not implemented yet." (empty choices on autocomplete).
848
- * Null is an unrecognized payload shape, which the engine acks with a 202 without dispatching.
849
- * Components and modals route by the stable customId prefix, so a wire whose layout hash drifted still
850
- * routes to its handler, where decode refuses with `StaleCustomId`.
816
+ * Matches a verified non-PING interaction to a registered handler. A known kind with no handler resolves
817
+ * to the unhandled default. Null is an unrecognized payload shape, which the engine acks with a 202
818
+ * without dispatching. Components and modals route by the stable customId prefix. A wire whose layout
819
+ * hash drifted still reaches its handler, where decode refuses with `StaleCustomId`.
851
820
  */
852
821
  function resolve(maps, interaction) {
853
822
  switch (interaction.type) {
@@ -877,6 +846,65 @@ function resolve(maps, interaction) {
877
846
  }
878
847
  }
879
848
 
849
+ //#endregion
850
+ //#region src/dispatch/RouteRegistry.ts
851
+ var RouteRegistry = class {
852
+ maps = emptyRouteMaps();
853
+ owners = /* @__PURE__ */ new Map();
854
+ get size() {
855
+ return this.owners.size;
856
+ }
857
+ /**
858
+ * Writes every route the class declares. A class declaring none returns false.
859
+ *
860
+ * @throws A **SeedcordError** when another class already holds one of them.
861
+ */
862
+ register(ctor, from) {
863
+ const writes = [];
864
+ for (const [kind, keys] of interactionRoutesOf(ctor)) for (const key of keys) {
865
+ const existing = this.owners.get(`${kind}:${key}`);
866
+ if (existing && existing.ctor !== ctor) throw new SeedcordError(SeedcordErrorCode.InteractionDuplicateRoute, [
867
+ `${kind}:${key}`,
868
+ `${existing.ctor.name} (${existing.from})`,
869
+ `${ctor.name} (${from})`
870
+ ]);
871
+ writes.push({
872
+ kind,
873
+ key
874
+ });
875
+ }
876
+ if (writes.length === 0) return false;
877
+ for (const { kind, key } of writes) {
878
+ const routeId = `${kind}:${key}`;
879
+ this.maps[kind].set(key, {
880
+ kind,
881
+ routeId,
882
+ ctor
883
+ });
884
+ this.owners.set(routeId, {
885
+ ctor,
886
+ kind,
887
+ key,
888
+ from
889
+ });
890
+ }
891
+ return true;
892
+ }
893
+ unregister(ctor, routeIds) {
894
+ for (const routeId of routeIds ?? this.routesOf(ctor)) {
895
+ const owner = this.owners.get(routeId);
896
+ if (owner?.ctor !== ctor) continue;
897
+ this.owners.delete(routeId);
898
+ this.maps[owner.kind].delete(owner.key);
899
+ }
900
+ }
901
+ routesOf(ctor) {
902
+ const routeIds = [];
903
+ for (const [routeId, owner] of this.owners) if (owner.ctor === ctor) routeIds.push(routeId);
904
+ return routeIds;
905
+ }
906
+ };
907
+
880
908
  //#endregion
881
909
  //#region src/receiver/Ed25519Verifier.ts
882
910
  const PUBLIC_KEY_BYTES = 32;
@@ -1020,6 +1048,7 @@ function buildEngine(core, maps, middlewares) {
1020
1048
 
1021
1049
  //#endregion
1022
1050
  //#region src/createSeedcord.ts
1051
+ const MANIFEST_ORIGIN = "the manifest";
1023
1052
  /**
1024
1053
  * Builds the HTTP-interactions engine, a `(request, ctx?) => Promise<Response>` handler.
1025
1054
  *
@@ -1041,8 +1070,18 @@ function createSeedcord(config, manifest) {
1041
1070
  const token = validateDiscordToken(Envapter.get("DISCORD_BOT_TOKEN"));
1042
1071
  const core = createCore(config, token);
1043
1072
  core.bus[RegisterDefaults]();
1044
- registerSubscribers(core.bus, manifest);
1045
- return buildEngine(core, buildRouteMaps(manifest), new MiddlewareRegistry(interactionMiddleware)).handle;
1073
+ registerSubscribers(core.bus, manifest.subscribers);
1074
+ const routes = new RouteRegistry();
1075
+ for (const handler of manifest.handlers) {
1076
+ if (!isHandlerClass(handler)) throw wrongClass("handlers", handler, "InteractionHandler or AutocompleteHandler");
1077
+ if (!routes.register(handler, MANIFEST_ORIGIN)) throw noRoutes("handlers", handler.name, "route decorator");
1078
+ }
1079
+ const middlewares = new MiddlewareRegistry(interactionMiddleware);
1080
+ for (const middleware of manifest.middleware) {
1081
+ if (!isMiddlewareClass(middleware)) throw wrongClass("middleware", middleware, "InteractionMiddleware");
1082
+ if (!middlewares.register(middleware)) throw noRoutes("middleware", middleware.name, "@RegisterInteractionMiddleware");
1083
+ }
1084
+ return buildEngine(core, routes.maps, middlewares).handle;
1046
1085
  }
1047
1086
 
1048
1087
  //#endregion
@@ -1290,34 +1329,6 @@ var EmojiInjector = class {
1290
1329
  }
1291
1330
  };
1292
1331
 
1293
- //#endregion
1294
- //#region src/handlers/interaction/InteractionMiddleware.ts
1295
- /**
1296
- * Base class for interaction middleware on the HTTP transport.
1297
- *
1298
- * Middleware runs after the dispatcher builds the handler and before the gates, over the handler's own
1299
- * reply surface. Name the kinds in the generic and list the same ones in `{ kinds }`. Several kinds give
1300
- * `this.event` a union. Narrow it on `type` and `data.component_type`. A catchall reads every repliable
1301
- * kind.
1302
- *
1303
- * @typeParam Kind - One or more {@link InteractionKind} members. Defaults to every repliable kind.
1304
- *
1305
- * @example
1306
- * ```ts
1307
- * \@RegisterInteractionMiddleware({ kinds: [InteractionKind.Button] })
1308
- * class Audit extends InteractionMiddleware<InteractionKind.Button> {
1309
- * async execute() {
1310
- * this.logger.info(this.event.data.custom_id);
1311
- * }
1312
- * }
1313
- * ```
1314
- */
1315
- var InteractionMiddleware = class extends RepliableHandler$1 {
1316
- constructor(event, core, dispatch, sender) {
1317
- super(event, core, dispatch, sender);
1318
- }
1319
- };
1320
-
1321
1332
  //#endregion
1322
1333
  //#region src/handlers/interaction/SlashHandler.ts
1323
1334
  /**
@@ -1895,8 +1906,8 @@ function Gated(...gates) {
1895
1906
 
1896
1907
  //#endregion
1897
1908
  //#region src/version.ts
1898
- const version = "0.9.1";
1909
+ const version = "0.10.0";
1899
1910
 
1900
1911
  //#endregion
1901
- export { ReplySender as A, ModalFields as C, InteractionHandler as D, buildRouteMaps as E, AutocompleteHandler as O, Emojis as S, buildEngine as T, MessageContextMenuHandler as _, Paginator as a, InteractionMiddleware as b, ChannelMenuHandler as c, SelectMenuHandler as d, StringMenuHandler as f, ComponentHandler as g, ButtonHandler as h, CursorSource as i, RepliableHandler$1 as k, MentionableMenuHandler as l, ModalHandler as m, Gated as n, Subscriber$1 as o, UserMenuHandler as p, ArraySource as r, WebhookLog$1 as s, version as t, RoleMenuHandler as u, UserContextMenuHandler as v, createSeedcord as w, EmojiInjector as x, SlashHandler as y };
1902
- //# sourceMappingURL=edge.index-BjggaX6p.mjs.map
1912
+ export { InteractionHandler as A, createSeedcord as C, isHandlerClass as D, emptyRouteMaps as E, RepliableHandler$1 as M, ReplySender as N, isMiddlewareClass as O, ModalFields as S, RouteRegistry as T, MessageContextMenuHandler as _, Paginator as a, EmojiInjector as b, ChannelMenuHandler as c, SelectMenuHandler as d, StringMenuHandler as f, ComponentHandler as g, ButtonHandler as h, CursorSource as i, AutocompleteHandler as j, InteractionMiddleware as k, MentionableMenuHandler as l, ModalHandler as m, Gated as n, Subscriber$1 as o, UserMenuHandler as p, ArraySource as r, WebhookLog$1 as s, version as t, RoleMenuHandler as u, UserContextMenuHandler as v, buildEngine as w, Emojis as x, SlashHandler as y };
1913
+ //# sourceMappingURL=edge.index-Cmd1CLAd.mjs.map