@seedcord/core 0.1.0-next.4 → 0.1.0-next.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,14 +1,15 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_CustomId = require('./CustomId-DV7daU7r.cjs');
2
+ const require_permissions = require('./permissions-DQL5ViZn.cjs');
3
3
  require("reflect-metadata");
4
4
  let _seedcord_errors = require("@seedcord/errors");
5
5
  let _seedcord_errors_internal = require("@seedcord/errors/internal");
6
+ let _seedcord_logger = require("@seedcord/logger");
6
7
  let _seedcord_utils = require("@seedcord/utils");
7
8
 
8
9
  //#region src/decorators/Command.ts
9
10
  function RegisterCommand(scope, guilds = []) {
10
11
  return (ctor) => {
11
- const existingMeta = Reflect.getOwnMetadata(require_CustomId.CommandMetadataKey, ctor);
12
+ const existingMeta = Reflect.getOwnMetadata(require_permissions.CommandMetadataKey, ctor);
12
13
  if (existingMeta) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorCommandAlreadyRegistered, [
13
14
  ctor.name,
14
15
  existingMeta.scope,
@@ -20,7 +21,138 @@ function RegisterCommand(scope, guilds = []) {
20
21
  scope,
21
22
  guilds
22
23
  };
23
- Reflect.defineMetadata(require_CustomId.CommandMetadataKey, meta, ctor);
24
+ Reflect.defineMetadata(require_permissions.CommandMetadataKey, meta, ctor);
25
+ };
26
+ }
27
+
28
+ //#endregion
29
+ //#region src/decorators/routes.ts
30
+ /**
31
+ * Routes one or more slash commands to a `SlashHandler`.
32
+ *
33
+ * Pass the same route string(s) as the handler's generic. A single command reads `this.options`, several
34
+ * commands branch with `this.match`. The decorator routes and the generic must match exactly, so listing
35
+ * fewer or more than the handler declares is a compile error. Subcommands use a slash path.
36
+ *
37
+ * @param routes - The route string(s) this handler serves, e.g. `'ban'`, `'ban', 'kick'`, or `'demo/setup'`.
38
+ * @decorator
39
+ * @example
40
+ * ```ts
41
+ * \@SlashRoute('ban')
42
+ * class BanHandler extends SlashHandler<'ban'> {
43
+ * async execute() {
44
+ * const target = this.options.getUser('target');
45
+ * }
46
+ * }
47
+ * ```
48
+ */
49
+ function SlashRoute(...routes) {
50
+ return function(constructor) {
51
+ require_permissions.storeInteractionRoute("slash", routes, constructor);
52
+ };
53
+ }
54
+ /**
55
+ * Routes one or more commands' autocomplete to an `AutocompleteHandler`.
56
+ *
57
+ * Pass the same command route string(s) as the handler's generic. One command branches with `this.match`
58
+ * over its autocompletable fields, several commands share one handler whose arms span every command's
59
+ * fields. The decorator routes and the generic must match exactly, so listing fewer or more than the
60
+ * handler declares is a compile error.
61
+ *
62
+ * @param routes - The command route string(s) this handler serves, e.g. `'search'` or `'search', 'find'`.
63
+ * @decorator
64
+ * @example
65
+ * ```ts
66
+ * \@AutocompleteRoute('search')
67
+ * class SearchAutocomplete extends AutocompleteHandler<'search'> {
68
+ * async execute() {
69
+ * await this.match({ query: (value, respond) => respond([{ name: value, value }]) });
70
+ * }
71
+ * }
72
+ * ```
73
+ */
74
+ function AutocompleteRoute(...routes) {
75
+ return function(constructor) {
76
+ require_permissions.storeInteractionRoute("autocomplete", routes, constructor);
77
+ };
78
+ }
79
+ /**
80
+ * Routes one or more context-menu commands to a `ContextMenuHandler`.
81
+ *
82
+ * Pass the kind (`ApplicationCommandType.User` or `ApplicationCommandType.Message`) and the command name(s),
83
+ * each checked against that kind's registry, so a typo is a compile error. The handler's generic must
84
+ * declare the same kind, cross-checked both directions. Context menus carry no options, so a multi-name
85
+ * handler reads `this.target` uniformly with no branch.
86
+ *
87
+ * @param kind - The context-menu kind, `ApplicationCommandType.User` or `ApplicationCommandType.Message`.
88
+ * @param names - The command name(s) this handler serves, keyed off the matching registry.
89
+ * @decorator
90
+ * @example
91
+ * ```ts
92
+ * \@ContextMenuRoute(ApplicationCommandType.User, 'View Profile')
93
+ * class ViewProfile extends ContextMenuHandler<ApplicationCommandType.User> {
94
+ * async execute() {
95
+ * const user = this.target;
96
+ * }
97
+ * }
98
+ * ```
99
+ */
100
+ function ContextMenuRoute(kind, ...names) {
101
+ return function(constructor) {
102
+ require_permissions.storeInteractionRoute(require_permissions.contextMenuRouteOf(kind), names, constructor);
103
+ };
104
+ }
105
+ /**
106
+ * Routes button interactions to handler classes.
107
+ *
108
+ * Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
109
+ * generic. Passing different definitions to the decorator and the generic is a compile error. Routing
110
+ * matches the stable prefix, so a wire minted from an older shape still reaches the handler, where
111
+ * reading this.params throws StaleCustomId and the controller boundary turns it into a reply.
112
+ *
113
+ * @param defs - The customId definition(s) this handler decodes, one per route.
114
+ * @decorator
115
+ */
116
+ function ButtonRoute(...defs) {
117
+ return function(constructor) {
118
+ require_permissions.storeComponentRoute("button", defs, constructor);
119
+ };
120
+ }
121
+ /**
122
+ * Routes modal submissions to handler classes.
123
+ *
124
+ * Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
125
+ * generic. Passing different definitions to the decorator and the generic is a compile error. Routing
126
+ * matches the stable prefix, so an older-shape wire still reaches the handler and throws StaleCustomId
127
+ * on read, the same as a button route.
128
+ *
129
+ * @param defs - The customId definition(s) this handler decodes, one per route.
130
+ * @decorator
131
+ */
132
+ function ModalRoute(...defs) {
133
+ return function(constructor) {
134
+ require_permissions.storeComponentRoute("modal", defs, constructor);
135
+ };
136
+ }
137
+ /**
138
+ * Routes select menu interactions to handler classes.
139
+ *
140
+ * Pass the select kind and the {@link CustomId} definition(s) this handler decodes. The handler's
141
+ * generic must list the same kind and definitions, or it is a compile error.
142
+ *
143
+ * @param type - Select menu kind from {@link SelectMenuKind}.
144
+ * @param defs - The customId definition(s) this handler decodes, one per route.
145
+ * @decorator
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * \@SelectMenuRoute(SelectMenuKind.User, AssignId)
150
+ * class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {}
151
+ * ```
152
+ */
153
+ function SelectMenuRoute(type, ...defs) {
154
+ return function(constructor) {
155
+ require_permissions.storeComponentRoute(require_permissions.selectMenuRouteOf(type), defs, constructor);
24
156
  };
25
157
  }
26
158
 
@@ -46,88 +178,75 @@ var DispatchContext = class {
46
178
  };
47
179
 
48
180
  //#endregion
49
- //#region src/gates/Gate.ts
181
+ //#region src/handlers/BaseHandler.ts
50
182
  /**
51
- * Builds a {@link Gate} from a check. The check refuses by throwing a Notice or a Silence and passes
52
- * by returning. The required context is inferred from `ctx`, so annotate it as narrowly as the fields
53
- * the check reads. No annotation defaults to {@link GateContextBase}, an agnostic gate that fits every
54
- * handler.
55
- *
56
- * @typeParam Name - The gate's name captured as a literal, so a mismatch error can name the gate.
57
- * @typeParam Ctx - The context the check reads, inferred from the `ctx` annotation.
58
- *
59
- * @param name - The gate's name, used in mismatch errors.
60
- * @param fn - The check, which refuses by throwing a Notice or a Silence and passes by returning.
61
- *
62
- * @see {@link defineEffectGate}
63
- * @see the `@Gated` decorator from `seedcord`
183
+ * Base class every transport handler extends. Don't register handlers directly, use the more specific
184
+ * handler subclasses.
64
185
  *
65
- * @example
66
- * ```ts
67
- * // a gate factory that takes its own option and closes over it
68
- * function MinRoles(min: number): Gate<GateContextBase, 'MinRoles'> {
69
- * return defineGate('MinRoles', (ctx) => {
70
- * if (ctx.memberRoleIds.length < min) throw new NotEnoughRoles(min);
71
- * });
72
- * }
73
- * ```
74
- *
75
- * @example
76
- * ```ts
77
- * // agnostic gate (no ctx annotation): fits any handler
78
- * const Owner = defineGate('owner', (ctx) => {
79
- * if (!ctx.userId) throw new NotOwner();
80
- * });
81
- * ```
186
+ * @typeParam Event - The event or interaction this handler processes
187
+ * @typeParam TCore - The transport's Core
82
188
  */
83
- function defineGate(name, fn) {
84
- return {
85
- name,
86
- check: async (ctx) => fn(ctx)
87
- };
88
- }
189
+ var BaseHandler = class {
190
+ core;
191
+ event;
192
+ logger;
193
+ dispatch;
194
+ constructor(event, core, dispatch) {
195
+ this.core = core;
196
+ this.event = event;
197
+ this.logger = new _seedcord_logger.Logger(this.constructor.name);
198
+ this.dispatch = dispatch;
199
+ }
200
+ };
201
+
202
+ //#endregion
203
+ //#region src/handlers/RepliableHandler.ts
89
204
  /**
90
- * Builds an {@link EffectGate} from a `check` and a `commit`. `check` peeks and refuses by throwing, and
91
- * `commit` applies the side effect, running only once the whole gate set passes so a later refusal never
92
- * commits. In an `or`, a refusing arm's queued commit is rolled back. This is how {@link Cooldown} peeks
93
- * in `check` and charges the slot in `commit`.
94
- *
95
- * @typeParam Name - The gate's name captured as a literal, so a mismatch error can name the gate.
96
- * @typeParam Ctx - The context both `check` and `commit` read, inferred from the `ctx` annotation.
97
- *
98
- * @param name - The gate's name, used in mismatch errors.
99
- * @param check - Peeks and refuses by throwing, without applying the side effect.
100
- * @param commit - Applies the side effect, running only after the whole gate set passes.
101
- *
102
- * @see {@link defineGate}
103
- * @see the `@Gated` decorator from `seedcord`
104
- * @see {@link Cooldown}
205
+ * Shared base the repliable handlers on both transports extend. Defines the reply members over the sender
206
+ * the transport builds.
105
207
  *
106
- * @example
107
- * ```ts
108
- * // an effect-gate factory that takes its own limit and closes over it
109
- * function UsesPerDay(max: number): EffectGate<GateContextBase, 'UsesPerDay'> {
110
- * return defineEffectGate(
111
- * 'UsesPerDay',
112
- * (ctx) => {
113
- * // peek and refuse by throwing, do not mutate yet
114
- * if (usedToday(ctx.userId) >= max) throw new OutOfUsesNotice(max);
115
- * },
116
- * (ctx) => {
117
- * // runs only after the whole gate set passes
118
- * recordUse(ctx.userId);
119
- * }
120
- * );
121
- * }
122
- * ```
208
+ * @typeParam Event - The repliable interaction type this handler processes
209
+ * @typeParam TCore - The transport's Core
210
+ * @typeParam TMessage - The transport's created-message lens
211
+ * @typeParam TSender - The transport's reply sender
123
212
  */
124
- function defineEffectGate(name, check, commit) {
125
- return {
126
- name,
127
- check: async (ctx) => check(ctx),
128
- commit: async (ctx) => commit(ctx)
129
- };
130
- }
213
+ var RepliableHandler = class extends BaseHandler {
214
+ sender;
215
+ routeId;
216
+ constructor(event, core, dispatch) {
217
+ super(event, core, dispatch);
218
+ this.routeId = dispatch?.routeId ?? this.constructor.name;
219
+ this.sender = this.buildSender(event, core, this.routeId);
220
+ }
221
+ /** @internal The dispatcher's fault boundary replies through the live ack state. */
222
+ getSender() {
223
+ return this.sender;
224
+ }
225
+ /** Send the initial response, exactly once. */
226
+ reply(response, opts) {
227
+ return this.sender.reply(response, opts);
228
+ }
229
+ /** Show a "thinking" placeholder, then fill it later with {@link edit}. */
230
+ defer(opts) {
231
+ return this.sender.defer(opts);
232
+ }
233
+ /** Send a new message once the interaction is acknowledged. */
234
+ followUp(response, opts) {
235
+ return this.sender.followUp(response, opts);
236
+ }
237
+ edit(targetOrResponse, maybeResponse) {
238
+ if (maybeResponse === void 0) return this.sender.edit(targetOrResponse);
239
+ return this.sender.edit(targetOrResponse, maybeResponse);
240
+ }
241
+ /** Routes to reply, followUp, or edit based on the current ack state. */
242
+ send(response, opts) {
243
+ return this.sender.send(response, opts);
244
+ }
245
+ /** Delete the initial reply or deferred placeholder. Pass a target to delete a message a prior send returned. */
246
+ delete(target) {
247
+ return target === void 0 ? this.sender.delete() : this.sender.delete(target);
248
+ }
249
+ };
131
250
 
132
251
  //#endregion
133
252
  //#region src/stops/Fault.ts
@@ -158,21 +277,21 @@ function defineEffectGate(name, check, commit) {
158
277
  * }
159
278
  * ```
160
279
  */
161
- var Fault = class extends require_CustomId.Notice {
280
+ var Fault = class extends require_permissions.Notice {
162
281
  constructor(options) {
163
282
  super("A fault occurred", options?.cause === void 0 ? void 0 : { cause: options.cause });
164
283
  this.report = options?.report ?? true;
165
284
  }
166
285
  render(ctx) {
167
- return { components: [new require_CustomId.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] };
286
+ 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] };
168
287
  }
169
288
  };
170
289
 
171
290
  //#endregion
172
291
  //#region src/gates/combinators.ts
173
292
  function and(...gates) {
174
- return defineGate(gates.map((gate) => gate.name).join(" & "), async (ctx) => {
175
- for (const gate of gates) await require_CustomId.runCheck(gate, ctx);
293
+ return require_permissions.defineGate(gates.map((gate) => gate.name).join(" & "), async (ctx) => {
294
+ for (const gate of gates) await require_permissions.runCheck(gate, ctx);
176
295
  });
177
296
  }
178
297
  function isOrOptions(arg) {
@@ -182,17 +301,17 @@ function or(...args) {
182
301
  const last = args.at(-1);
183
302
  const options = last !== void 0 && isOrOptions(last) ? last : void 0;
184
303
  const gates = options ? args.slice(0, -1) : args;
185
- return defineGate(gates.map((gate) => gate.name).join(" | "), async (ctx) => {
304
+ return require_permissions.defineGate(gates.map((gate) => gate.name).join(" | "), async (ctx) => {
186
305
  const summaries = [];
187
306
  let everyArmHasSummary = true;
188
307
  for (const gate of gates) {
189
- const mark = require_CustomId.markCommits(ctx);
308
+ const mark = require_permissions.markCommits(ctx);
190
309
  try {
191
- await require_CustomId.runCheck(gate, ctx);
310
+ await require_permissions.runCheck(gate, ctx);
192
311
  return;
193
312
  } catch (error) {
194
- if (error instanceof require_CustomId.Notice && !(error instanceof Fault) && !error.report) {
195
- require_CustomId.rollbackCommits(ctx, mark);
313
+ if (error instanceof require_permissions.Notice && !(error instanceof Fault) && !error.report) {
314
+ require_permissions.rollbackCommits(ctx, mark);
196
315
  if (error.summary === void 0) everyArmHasSummary = false;
197
316
  else summaries.push(error.summary);
198
317
  continue;
@@ -201,8 +320,8 @@ function or(...args) {
201
320
  }
202
321
  }
203
322
  if (options) throw typeof options.fail === "function" ? options.fail(ctx) : options.fail;
204
- if (everyArmHasSummary && summaries.length > 0) throw new require_CustomId.NeedsAny(summaries);
205
- throw new require_CustomId.NotAllowed();
323
+ if (everyArmHasSummary && summaries.length > 0) throw new require_permissions.NeedsAny(summaries);
324
+ throw new require_permissions.NotAllowed();
206
325
  });
207
326
  }
208
327
 
@@ -235,10 +354,10 @@ function or(...args) {
235
354
  * ```
236
355
  */
237
356
  function OwnerOnly(options) {
238
- return defineGate("OwnerOnly", (ctx) => {
357
+ return require_permissions.defineGate("OwnerOnly", (ctx) => {
239
358
  const owners = ctx.core.config.ownerIds ?? [];
240
359
  if (ctx.userId !== null && owners.includes(ctx.userId)) return;
241
- throw require_CustomId.pickNotice(options, (message) => new require_CustomId.NotOwner(message));
360
+ throw require_permissions.pickNotice(options, (message) => new require_permissions.NotOwner(message));
242
361
  });
243
362
  }
244
363
  /**
@@ -264,9 +383,9 @@ function OwnerOnly(options) {
264
383
  * ```
265
384
  */
266
385
  function GuildOnly(options) {
267
- return defineGate("GuildOnly", (ctx) => {
386
+ return require_permissions.defineGate("GuildOnly", (ctx) => {
268
387
  if (ctx.guildId) return;
269
- throw require_CustomId.pickNotice(options, (message) => new require_CustomId.NotInGuild(message));
388
+ throw require_permissions.pickNotice(options, (message) => new require_permissions.NotInGuild(message));
270
389
  });
271
390
  }
272
391
  /**
@@ -290,9 +409,9 @@ function GuildOnly(options) {
290
409
  * ```
291
410
  */
292
411
  function DmOnly(options) {
293
- return defineGate("DmOnly", (ctx) => {
412
+ return require_permissions.defineGate("DmOnly", (ctx) => {
294
413
  if (!ctx.guildId) return;
295
- throw require_CustomId.pickNotice(options, (message) => new require_CustomId.NotInDm(message));
414
+ throw require_permissions.pickNotice(options, (message) => new require_permissions.NotInDm(message));
296
415
  });
297
416
  }
298
417
 
@@ -364,11 +483,11 @@ function Cooldown(duration, options) {
364
483
  };
365
484
  const anonId = anonSeq++;
366
485
  const keyOf = (ctx) => `cooldown:${ctx.routeId ?? `anon${anonId}`}:${per}:w${windowMs}:l${options?.limit ?? 1}:${scopeValue(ctx, per)}`;
367
- return defineEffectGate("Cooldown", async (ctx) => {
486
+ return require_permissions.defineEffectGate("Cooldown", async (ctx) => {
368
487
  const result = await ctx.core.rateLimiter.peek(keyOf(ctx), window);
369
488
  if (!result.limited) return;
370
489
  if (options?.notice) throw options.notice(result.resetAt);
371
- throw new require_CustomId.OnCooldown(result.resetAt, options?.message?.(result.resetAt));
490
+ throw new require_permissions.OnCooldown(result.resetAt, options?.message?.(result.resetAt));
372
491
  }, async (ctx) => {
373
492
  await ctx.core.rateLimiter.charge(keyOf(ctx), window);
374
493
  });
@@ -432,24 +551,36 @@ function paginate(items, page, perPage) {
432
551
  //#endregion
433
552
  //#region src/index.ts
434
553
  /** Package version */
435
- const version = "0.1.0-next.4";
554
+ const version = "0.1.0-next.5";
436
555
 
437
556
  //#endregion
438
- exports.BuilderComponent = require_CustomId.BuilderComponent;
557
+ exports.AutocompleteRoute = AutocompleteRoute;
558
+ exports.BaseHandler = BaseHandler;
559
+ exports.BuilderComponent = require_permissions.BuilderComponent;
560
+ exports.ButtonRoute = ButtonRoute;
561
+ exports.ContextMenuRoute = ContextMenuRoute;
439
562
  exports.Cooldown = Cooldown;
440
- exports.CustomId = require_CustomId.CustomId;
563
+ exports.CustomId = require_permissions.CustomId;
441
564
  exports.DispatchContext = DispatchContext;
442
565
  exports.DmOnly = DmOnly;
443
566
  exports.Fault = Fault;
444
567
  exports.GuildOnly = GuildOnly;
445
- exports.Notice = require_CustomId.Notice;
568
+ exports.ModalRoute = ModalRoute;
569
+ exports.Notice = require_permissions.Notice;
446
570
  exports.OwnerOnly = OwnerOnly;
447
571
  exports.RegisterCommand = RegisterCommand;
448
- exports.RowComponent = require_CustomId.RowComponent;
572
+ exports.RepliableHandler = RepliableHandler;
573
+ exports.RequireBotPermissions = require_permissions.RequireBotPermissions;
574
+ exports.RequirePermissions = require_permissions.RequirePermissions;
575
+ exports.RequireRole = require_permissions.RequireRole;
576
+ exports.RowComponent = require_permissions.RowComponent;
577
+ exports.SelectMenuKind = require_permissions.SelectMenuKind;
578
+ exports.SelectMenuRoute = SelectMenuRoute;
449
579
  exports.Silence = Silence;
580
+ exports.SlashRoute = SlashRoute;
450
581
  exports.and = and;
451
- exports.defineEffectGate = defineEffectGate;
452
- exports.defineGate = defineGate;
582
+ exports.defineEffectGate = require_permissions.defineEffectGate;
583
+ exports.defineGate = require_permissions.defineGate;
453
584
  exports.or = or;
454
585
  exports.paginate = paginate;
455
586
  exports.version = version;