@helyx/bot 0.4.9 → 0.4.13

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.
@@ -17,14 +17,18 @@ export function installInteractions(client, modules, registration, logger, servi
17
17
  if (guildCommands.length > 0 && !registration.internalGuildId) {
18
18
  throw new Error("DISCORD_INTERNAL_GUILD_ID is required when guild commands are loaded");
19
19
  }
20
- const buttons = uniqueMap(modules.flatMap((module) => (module.interactions?.buttons ?? []).map((contribution) => ({
20
+ const buttonItems = modules.flatMap((module) => (module.interactions?.buttons ?? []).map((contribution) => ({
21
21
  moduleId: module.manifest.id,
22
22
  contribution,
23
- }))), ({ contribution }) => contribution.customId, "button custom ID");
24
- const modals = uniqueMap(modules.flatMap((module) => (module.interactions?.modals ?? []).map((contribution) => ({
23
+ })));
24
+ const buttons = uniqueMap(buttonItems.filter(({ contribution }) => contribution.customId !== undefined), ({ contribution }) => contribution.customId, "button custom ID");
25
+ const buttonPrefixes = componentPrefixes(buttonItems, "button");
26
+ const modalItems = modules.flatMap((module) => (module.interactions?.modals ?? []).map((contribution) => ({
25
27
  moduleId: module.manifest.id,
26
28
  contribution,
27
- }))), ({ contribution }) => contribution.customId, "modal custom ID");
29
+ })));
30
+ const modals = uniqueMap(modalItems.filter(({ contribution }) => contribution.customId !== undefined), ({ contribution }) => contribution.customId, "modal custom ID");
31
+ const modalPrefixes = componentPrefixes(modalItems, "modal");
28
32
  const selects = uniqueMap(modules.flatMap((module) => (module.interactions?.selects ?? []).map((contribution) => ({
29
33
  moduleId: module.manifest.id,
30
34
  contribution,
@@ -124,7 +128,8 @@ export function installInteractions(client, modules, registration, logger, servi
124
128
  }
125
129
  }
126
130
  else if (interaction.isButton()) {
127
- const button = buttons.get(interaction.customId);
131
+ const button = buttons.get(interaction.customId) ??
132
+ resolveComponentPrefix(buttonPrefixes, interaction.customId);
128
133
  const context = button
129
134
  ? createContext(interaction, services)
130
135
  : undefined;
@@ -132,6 +137,11 @@ export function installInteractions(client, modules, registration, logger, servi
132
137
  button?.contribution.acknowledgement === "deferred-ephemeral") {
133
138
  await context.deferReply?.(true);
134
139
  }
140
+ if (button &&
141
+ interaction.isButton() &&
142
+ button.contribution.acknowledgement === "deferred-update") {
143
+ await interaction.deferUpdate();
144
+ }
135
145
  if (button &&
136
146
  context &&
137
147
  (await authorizeComponent(interaction, button.moduleId, services))) {
@@ -139,7 +149,8 @@ export function installInteractions(client, modules, registration, logger, servi
139
149
  }
140
150
  }
141
151
  else if (interaction.isModalSubmit()) {
142
- const modal = modals.get(interaction.customId);
152
+ const modal = modals.get(interaction.customId) ??
153
+ resolveComponentPrefix(modalPrefixes, interaction.customId);
143
154
  if (modal &&
144
155
  (await authorizeComponent(interaction, modal.moduleId, services))) {
145
156
  await modal.contribution.execute(createModalContext(interaction, services));
@@ -205,6 +216,9 @@ export function toCommandDefinition(registered) {
205
216
  if (!!command.execute === !!command.subcommands?.length) {
206
217
  throw new Error(`Command ${command.name} must define either execute or subcommands`);
207
218
  }
219
+ if (command.options?.length && command.subcommands?.length) {
220
+ throw new Error(`Command ${command.name} cannot define both direct options and subcommands`);
221
+ }
208
222
  const contextTypes = {
209
223
  guild: InteractionContextType.Guild,
210
224
  bot_dm: InteractionContextType.BotDM,
@@ -219,6 +233,9 @@ export function toCommandDefinition(registered) {
219
233
  .setDescription(command.description)
220
234
  .setContexts(command.contexts.map((context) => contextTypes[context]))
221
235
  .setIntegrationTypes(command.integrationTypes.map((integrationType) => integrationTypes[integrationType]));
236
+ for (const option of command.options ?? []) {
237
+ addCommandOption(builder, option);
238
+ }
222
239
  for (const subcommand of command.subcommands ?? []) {
223
240
  builder.addSubcommand((subcommandBuilder) => {
224
241
  subcommandBuilder
@@ -260,6 +277,13 @@ function addCommandOption(builder, option) {
260
277
  .setRequired(option.required ?? false));
261
278
  return;
262
279
  }
280
+ if (option.type === "user") {
281
+ builder.addUserOption((optionBuilder) => optionBuilder
282
+ .setName(option.name)
283
+ .setDescription(option.description)
284
+ .setRequired(option.required ?? false));
285
+ return;
286
+ }
263
287
  if (option.type === "role") {
264
288
  builder.addRoleOption((optionBuilder) => optionBuilder
265
289
  .setName(option.name)
@@ -270,6 +294,7 @@ function addCommandOption(builder, option) {
270
294
  const channelTypes = {
271
295
  guild_text: ChannelType.GuildText,
272
296
  guild_announcement: ChannelType.GuildAnnouncement,
297
+ guild_category: ChannelType.GuildCategory,
273
298
  };
274
299
  builder.addChannelOption((optionBuilder) => {
275
300
  optionBuilder
@@ -291,9 +316,43 @@ export function uniqueMap(items, keyOf, label) {
291
316
  }
292
317
  return result;
293
318
  }
319
+ function componentPrefixes(items, label) {
320
+ const prefixes = [];
321
+ for (const item of items) {
322
+ const { customId, customIdPrefix } = item.contribution;
323
+ if ((customId === undefined) === (customIdPrefix === undefined)) {
324
+ throw new Error(`${label} contribution must declare exactly one custom ID or prefix`);
325
+ }
326
+ const route = customId ?? customIdPrefix;
327
+ if (route.length < 1 ||
328
+ route.length > 100 ||
329
+ !/^[A-Za-z0-9._:-]+$/u.test(route)) {
330
+ throw new Error(`${label} custom ID routing value is invalid`);
331
+ }
332
+ if (customIdPrefix !== undefined)
333
+ prefixes.push(item);
334
+ }
335
+ for (let index = 0; index < prefixes.length; index += 1) {
336
+ const prefix = prefixes[index].contribution.customIdPrefix;
337
+ if (prefixes.some((candidate, candidateIndex) => candidateIndex !== index &&
338
+ (candidate.contribution.customIdPrefix ?? "").startsWith(prefix))) {
339
+ throw new Error(`Ambiguous ${label} custom ID prefix: ${prefix}`);
340
+ }
341
+ }
342
+ return prefixes;
343
+ }
344
+ function resolveComponentPrefix(items, customId) {
345
+ if (customId.length < 1 ||
346
+ customId.length > 100 ||
347
+ !/^[A-Za-z0-9._:-]+$/u.test(customId)) {
348
+ return undefined;
349
+ }
350
+ return items.find(({ contribution }) => customId.startsWith(contribution.customIdPrefix ?? ""));
351
+ }
294
352
  function createContext(interaction, services) {
295
353
  return {
296
354
  interactionId: interaction.id,
355
+ ...("customId" in interaction ? { customId: interaction.customId } : {}),
297
356
  userId: interaction.user.id,
298
357
  userDisplayName: interaction.user.globalName ?? interaction.user.username,
299
358
  userRoleIds: interaction.member instanceof GuildMember
@@ -318,10 +377,14 @@ function createContext(interaction, services) {
318
377
  ...(ephemeral ? { flags: MessageFlags.Ephemeral } : {}),
319
378
  });
320
379
  },
380
+ deleteReplyAfter: (milliseconds) => scheduleReplyDeletion(interaction, milliseconds),
321
381
  reply: (response) => replyToInteraction(interaction, response),
322
382
  ...(interaction.isMessageComponent()
323
383
  ? {
324
384
  updateResponse: async (response) => interaction.update(toUpdateOptions(response)).then(() => undefined),
385
+ editSourceMessage: async (response) => interaction.webhook
386
+ .editMessage(interaction.message.id, toUpdateOptions(response))
387
+ .then(() => undefined),
325
388
  }
326
389
  : {}),
327
390
  showModal: async (modal) => interaction.showModal(toModal(modal)).then(() => undefined),
@@ -336,6 +399,16 @@ function createCommandContext(interaction, services) {
336
399
  ...createContext(interaction, services),
337
400
  getStringOption: (name) => interaction.options.getString(name),
338
401
  getBooleanOption: (name) => interaction.options.getBoolean(name),
402
+ getUserOption: (name) => {
403
+ const user = interaction.options.getUser(name);
404
+ return user
405
+ ? {
406
+ id: user.id,
407
+ displayName: user.globalName ?? user.username,
408
+ isBot: user.bot,
409
+ }
410
+ : null;
411
+ },
339
412
  getRoleOption: (name) => {
340
413
  const role = interaction.options.getRole(name);
341
414
  return role ? { id: role.id, name: role.name } : null;
@@ -397,6 +470,7 @@ function createModalContext(interaction, services) {
397
470
  : undefined;
398
471
  return {
399
472
  interactionId: interaction.id,
473
+ customId: interaction.customId,
400
474
  userId: interaction.user.id,
401
475
  userDisplayName: interaction.user.globalName ?? interaction.user.username,
402
476
  userRoleIds: interaction.member instanceof GuildMember
@@ -418,6 +492,7 @@ function createModalContext(interaction, services) {
418
492
  ...(ephemeral ? { flags: MessageFlags.Ephemeral } : {}),
419
493
  });
420
494
  },
495
+ deleteReplyAfter: (milliseconds) => scheduleReplyDeletion(interaction, milliseconds),
421
496
  reply: (response) => replyToInteraction(interaction, response),
422
497
  ...(updateResponse ? { updateResponse } : {}),
423
498
  getField: (customId) => interaction.fields.getTextInputValue(customId),
@@ -445,6 +520,16 @@ function createModalContext(interaction, services) {
445
520
  addChannelMessageReaction: (channelId, messageId, emojiIdentifier) => addChannelMessageReaction(interaction.client, channelId, messageId, emojiIdentifier),
446
521
  };
447
522
  }
523
+ function scheduleReplyDeletion(interaction, milliseconds) {
524
+ if (!Number.isInteger(milliseconds) ||
525
+ milliseconds < 1_000 ||
526
+ milliseconds > 900_000)
527
+ throw new Error("Reply deletion delay must be between 1 second and 15 minutes");
528
+ const timer = setTimeout(() => {
529
+ void interaction.deleteReply().catch(() => undefined);
530
+ }, milliseconds);
531
+ timer.unref();
532
+ }
448
533
  function createSelectContext(interaction, services) {
449
534
  return {
450
535
  ...createContext(interaction, services),
@@ -667,9 +752,7 @@ function toSelectRows(response) {
667
752
  .setPlaceholder(select.placeholder)
668
753
  .setMinValues(select.minValues ?? 1)
669
754
  .setMaxValues(select.maxValues ?? 1)
670
- .setChannelTypes(...select.channelTypes.map((type) => type === "guild_announcement"
671
- ? ChannelType.GuildAnnouncement
672
- : ChannelType.GuildText))));
755
+ .setChannelTypes(...select.channelTypes.map(discordChannelType))));
673
756
  continue;
674
757
  }
675
758
  if (select.type === "discord-role") {
@@ -701,19 +784,10 @@ function toSelectRows(response) {
701
784
  return rows;
702
785
  }
703
786
  export function toUpdateOptions(response) {
704
- const options = toReplyOptions({
787
+ return toReplyOptions({
705
788
  ...response,
706
789
  ephemeral: false,
707
790
  });
708
- return response.componentsV2
709
- ? {
710
- ...options,
711
- // Discord requires legacy fields to be cleared when an existing
712
- // message is first converted to Components V2.
713
- content: null,
714
- embeds: [],
715
- }
716
- : options;
717
791
  }
718
792
  function toAllowedUserMentions(userIds) {
719
793
  if (userIds === undefined)
@@ -818,9 +892,7 @@ export function toModal(definition) {
818
892
  .setRequired(field.required ?? false)
819
893
  .setMinValues(field.minValues ?? (field.required ? 1 : 0))
820
894
  .setMaxValues(field.maxValues ?? 1)
821
- .setChannelTypes(...field.channelTypes.map((type) => type === "guild_announcement"
822
- ? ChannelType.GuildAnnouncement
823
- : ChannelType.GuildText));
895
+ .setChannelTypes(...field.channelTypes.map(discordChannelType));
824
896
  if (field.placeholder !== undefined)
825
897
  input.setPlaceholder(field.placeholder);
826
898
  if (field.defaultValues?.length)
@@ -831,6 +903,13 @@ export function toModal(definition) {
831
903
  }
832
904
  return modal;
833
905
  }
906
+ function discordChannelType(type) {
907
+ if (type === "guild_announcement")
908
+ return ChannelType.GuildAnnouncement;
909
+ if (type === "guild_category")
910
+ return ChannelType.GuildCategory;
911
+ return ChannelType.GuildText;
912
+ }
834
913
  async function sendFailure(interaction) {
835
914
  if (interaction.isAutocomplete()) {
836
915
  await interaction.respond([]).catch(() => undefined);
@@ -2,7 +2,7 @@ import { type ModuleDefinition } from "@helyx/sdk";
2
2
  export interface ManagementGuildResources {
3
3
  channels: readonly {
4
4
  id: string;
5
- type: "guild_text" | "guild_announcement";
5
+ type: "guild_text" | "guild_announcement" | "guild_category";
6
6
  }[];
7
7
  roles: readonly {
8
8
  id: string;
@@ -18,6 +18,7 @@ const VALUE_MODAL_ID = "helyx.management.value.modal";
18
18
  const VALUE_FIELD_ID = "value";
19
19
  const SESSION_PURPOSE = "modules.manage";
20
20
  const PAGE_SIZE = 25;
21
+ const MAX_DISCORD_SETTING_SELECTIONS = 25;
21
22
  const SESSION_LIFETIME_MS = 15 * 60 * 1_000;
22
23
  const VIOLET = 0x7c3aed;
23
24
  const SUCCESS = 0x16a34a;
@@ -177,7 +178,14 @@ export function createManagementModule(configurableModules, resources) {
177
178
  return;
178
179
  }
179
180
  try {
180
- module.configuration?.validate(value);
181
+ const validated = module.configuration?.validate(value);
182
+ if (validated && module.configuration?.validateUpdate) {
183
+ await module.configuration.validateUpdate({
184
+ guildId: context.serverId,
185
+ value: validated,
186
+ services: context.services,
187
+ });
188
+ }
181
189
  }
182
190
  catch (error) {
183
191
  await context.reply(validationResponse(error instanceof Error
@@ -563,8 +571,12 @@ function settingValueResponse(setting) {
563
571
  {
564
572
  type: "discord-channel",
565
573
  customId: VALUE_SELECT_ID,
566
- placeholder: "Choose a channel",
574
+ placeholder: setting.multiple
575
+ ? "Choose channels"
576
+ : "Choose a channel",
567
577
  channelTypes: setting.channelTypes,
578
+ minValues: 1,
579
+ maxValues: setting.multiple ? MAX_DISCORD_SETTING_SELECTIONS : 1,
568
580
  },
569
581
  ],
570
582
  };
@@ -631,6 +643,8 @@ function initialDraft(module, existing) {
631
643
  return result;
632
644
  }
633
645
  function emptyValue(setting) {
646
+ if (setting.type === "discord-channel" && setting.multiple)
647
+ return [];
634
648
  if (setting.type === "discord-role" && setting.multiple)
635
649
  return [];
636
650
  if (setting.type === "string" ||
@@ -655,10 +669,13 @@ function selectedValue(setting, values) {
655
669
  return values[0];
656
670
  }
657
671
  if (setting.type === "discord-channel") {
658
- if (values.length !== 1 || !isSnowflake(values[0])) {
659
- throw new Error("Selected channel is invalid");
672
+ if (values.length < 1 ||
673
+ values.length > (setting.multiple ? MAX_DISCORD_SETTING_SELECTIONS : 1) ||
674
+ new Set(values).size !== values.length ||
675
+ values.some((value) => !isSnowflake(value))) {
676
+ throw new Error("Selected channel value is invalid");
660
677
  }
661
- return values[0];
678
+ return setting.multiple ? [...values] : values[0];
662
679
  }
663
680
  if (setting.type === "discord-role") {
664
681
  if (values.length < 1 ||
@@ -723,9 +740,21 @@ async function validateDiscordSettings(directory, guildId, module, values) {
723
740
  if (configured === "" || configured === undefined)
724
741
  continue;
725
742
  if (setting.type === "discord-channel") {
726
- const channel = typeof configured === "string" ? channels.get(configured) : undefined;
727
- if (!channel || !setting.channelTypes.includes(channel.type)) {
728
- return `${setting.label} must be an available supported Discord channel.`;
743
+ if (setting.multiple !== Array.isArray(configured)) {
744
+ return `${setting.label} has an invalid Discord channel selection.`;
745
+ }
746
+ const configuredChannels = Array.isArray(configured)
747
+ ? configured
748
+ : [configured];
749
+ if (configuredChannels.length > MAX_DISCORD_SETTING_SELECTIONS ||
750
+ new Set(configuredChannels).size !== configuredChannels.length ||
751
+ configuredChannels.some((channelId) => {
752
+ if (typeof channelId !== "string")
753
+ return true;
754
+ const channel = channels.get(channelId);
755
+ return !channel || !setting.channelTypes.includes(channel.type);
756
+ })) {
757
+ return `${setting.label} must contain available supported Discord channels.`;
729
758
  }
730
759
  }
731
760
  if (setting.type === "discord-role") {
@@ -776,8 +805,11 @@ function settingStatus(setting, value) {
776
805
  }
777
806
  if (setting.type === "boolean")
778
807
  return value === true ? "Enabled" : "Disabled";
779
- if (setting.type === "discord-channel")
780
- return "Channel selected";
808
+ if (setting.type === "discord-channel") {
809
+ return Array.isArray(value)
810
+ ? `${String(value.length)} channels selected`
811
+ : "Channel selected";
812
+ }
781
813
  if (setting.type === "discord-role") {
782
814
  return Array.isArray(value)
783
815
  ? `${String(value.length)} roles selected`
@@ -0,0 +1,5 @@
1
+ import { type Message } from "discord.js";
2
+ import type { GuildMessageClassification } from "@helyx/sdk";
3
+ export declare function classifyGuildMessage(message: Pick<Message, "attachments" | "content" | "embeds" | "flags" | "stickers">): GuildMessageClassification;
4
+ export declare const unavailableGuildMessageClassification: GuildMessageClassification;
5
+ //# sourceMappingURL=message-classification.d.ts.map
@@ -0,0 +1,75 @@
1
+ import { MessageFlags } from "discord.js";
2
+ const LINK_PATTERN = /https?:\/\/[^\s<>]+/giu;
3
+ const GIF_LINK_PATTERN = /(?:https?:\/\/[^\s<>]*(?:\.gif(?:[?#][^\s<>]*)?|giphy\.com|tenor\.com|media\.discordapp\.net[^\s<>]*\.gif))/giu;
4
+ const CUSTOM_EMOJI_PATTERN = /<a?:[a-zA-Z0-9_]{2,32}:\d{17,20}>/gu;
5
+ const UNICODE_EMOJI_PATTERN = /(?:\p{Regional_Indicator}{2}|[0-9#*]\uFE0F?\u20E3|\p{Extended_Pictographic}(?:\uFE0F|\p{Emoji_Modifier})?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|\p{Emoji_Modifier})?)*)/gu;
6
+ export function classifyGuildMessage(message) {
7
+ const kinds = new Set();
8
+ const content = message.content.trim();
9
+ const withoutLinks = content.replace(LINK_PATTERN, " ").trim();
10
+ const withoutEmoji = withoutLinks
11
+ .replace(CUSTOM_EMOJI_PATTERN, " ")
12
+ .replace(UNICODE_EMOJI_PATTERN, " ")
13
+ .replace(/\u200D|\uFE0F|\p{Emoji_Modifier}|\s/gu, "");
14
+ if (matches(LINK_PATTERN, content))
15
+ kinds.add("link");
16
+ if (withoutLinks.length > 0 &&
17
+ withoutEmoji.length === 0 &&
18
+ (matches(CUSTOM_EMOJI_PATTERN, withoutLinks) ||
19
+ matches(UNICODE_EMOJI_PATTERN, withoutLinks)))
20
+ kinds.add("emoji");
21
+ else if (withoutEmoji.length > 0)
22
+ kinds.add("text");
23
+ const voiceMessage = message.flags.has(MessageFlags.IsVoiceMessage);
24
+ if (voiceMessage)
25
+ kinds.add("voice-message");
26
+ for (const attachment of message.attachments.values()) {
27
+ const contentType = attachment.contentType?.toLowerCase() ?? "";
28
+ const filename = attachment.name.toLowerCase();
29
+ if (voiceMessage)
30
+ continue;
31
+ if (contentType === "image/gif" || filename.endsWith(".gif"))
32
+ kinds.add("gif");
33
+ else if (contentType.startsWith("image/"))
34
+ kinds.add("image");
35
+ else
36
+ kinds.add("file");
37
+ }
38
+ for (const embed of message.embeds) {
39
+ const urls = [embed.url, embed.image?.url, embed.thumbnail?.url]
40
+ .filter((value) => Boolean(value))
41
+ .join(" ");
42
+ if (matches(GIF_LINK_PATTERN, urls))
43
+ kinds.add("gif");
44
+ else if (embed.image || embed.thumbnail)
45
+ kinds.add("image");
46
+ }
47
+ if (matches(GIF_LINK_PATTERN, content))
48
+ kinds.add("gif");
49
+ if (message.stickers.size > 0)
50
+ kinds.add("sticker");
51
+ return {
52
+ available: true,
53
+ kinds: [
54
+ "text",
55
+ "emoji",
56
+ "link",
57
+ "image",
58
+ "gif",
59
+ "file",
60
+ "sticker",
61
+ "voice-message",
62
+ ].filter((kind) => kinds.has(kind)),
63
+ };
64
+ }
65
+ export const unavailableGuildMessageClassification = {
66
+ available: false,
67
+ kinds: [],
68
+ };
69
+ function matches(pattern, value) {
70
+ pattern.lastIndex = 0;
71
+ const result = pattern.test(value);
72
+ pattern.lastIndex = 0;
73
+ return result;
74
+ }
75
+ //# sourceMappingURL=message-classification.js.map
package/dist/modules.js CHANGED
@@ -90,12 +90,21 @@ async function loadDefinitions(specifiers, importer) {
90
90
  return orderedManifests.map((manifest) => byId.get(manifest.id));
91
91
  }
92
92
  export function validateContributionParity(definition) {
93
+ const requiredDiscordCapabilities = definition.requiredDiscordCapabilities ?? [];
94
+ if (new Set(requiredDiscordCapabilities).size !==
95
+ requiredDiscordCapabilities.length ||
96
+ requiredDiscordCapabilities.some((capability) => capability !== "guild-members" && capability !== "message-content")) {
97
+ throw new Error("Module requests an unsupported or duplicate Discord capability");
98
+ }
93
99
  const executableCommands = (definition.interactions?.commands ?? []).map(({ name }) => name);
94
100
  const executableEvents = [
101
+ ...(definition.events?.privilegedMessageCreate ?? []),
102
+ ...(definition.events?.guildMessageCreate ?? []),
95
103
  ...(definition.events?.messageReactionAdd ?? []),
96
104
  ...(definition.events?.messageReactionRemove ?? []),
97
105
  ...(definition.events?.messageDelete ?? []),
98
106
  ...(definition.events?.guildMemberAdd ?? []),
107
+ ...(definition.events?.guildMemberRemove ?? []),
99
108
  ...(definition.events?.guildRoleDelete ?? []),
100
109
  ...(definition.events?.guildRoleUpdate ?? []),
101
110
  ...(definition.events?.channelDelete ?? []),
@@ -0,0 +1,62 @@
1
+ import { type NumericWriteBehindCommittedBatch, type NumericWriteBehindEnqueueInput, type NumericWriteBehindMemberScope, type NumericWriteBehindScope, type NumericWriteBehindService } from "@helyx/sdk";
2
+ import { type ShardIdentity } from "./shard-contracts.js";
3
+ export declare class WorkerNumericWriteBehindProxy implements NumericWriteBehindService {
4
+ #private;
5
+ constructor(identity: ShardIdentity);
6
+ enqueue(input: NumericWriteBehindEnqueueInput): Promise<{
7
+ outcome: "accepted" | "cooldown" | "suppressed" | "paused" | "capacity" | "unavailable";
8
+ }>;
9
+ pendingMemberSnapshot(input: NumericWriteBehindMemberScope): Promise<{
10
+ increments: Readonly<Record<string, number>>;
11
+ maxima: Readonly<Record<string, Date>>;
12
+ oldestAcceptedAt: Date | null;
13
+ }>;
14
+ flushMember(input: NumericWriteBehindMemberScope): Promise<void>;
15
+ flushGuild(input: NumericWriteBehindScope & {
16
+ guildId: string;
17
+ }): Promise<void>;
18
+ flushSubject(input: NumericWriteBehindScope & {
19
+ subjectId: string;
20
+ }): Promise<void>;
21
+ flushModule(input: NumericWriteBehindScope): Promise<void>;
22
+ runMemberBarrier<Result>(input: NumericWriteBehindMemberScope, operation: () => Promise<Result>): Promise<Result>;
23
+ runGuildBarrier<Result>(input: NumericWriteBehindScope & {
24
+ guildId: string;
25
+ }, operation: () => Promise<Result>): Promise<Result>;
26
+ runSubjectBarrier<Result>(input: NumericWriteBehindScope & {
27
+ subjectId: string;
28
+ }, operation: () => Promise<Result>): Promise<Result>;
29
+ suppressSubject(input: NumericWriteBehindScope & {
30
+ subjectId: string;
31
+ cutoff: Date;
32
+ }): Promise<{
33
+ suppressionId: string;
34
+ discarded: number;
35
+ }>;
36
+ suppressGuild(input: NumericWriteBehindScope & {
37
+ guildId: string;
38
+ }): Promise<{
39
+ suppressionId: string;
40
+ discarded: number;
41
+ }>;
42
+ releaseGuild(input: NumericWriteBehindScope & {
43
+ guildId: string;
44
+ suppressionId: string;
45
+ }): Promise<{
46
+ released: boolean;
47
+ }>;
48
+ releaseSubject(input: NumericWriteBehindScope & {
49
+ subjectId: string;
50
+ suppressionId: string;
51
+ }): Promise<{
52
+ released: boolean;
53
+ }>;
54
+ clearMemberCooldown(input: NumericWriteBehindMemberScope): Promise<void>;
55
+ clearSubjectCooldown(input: NumericWriteBehindScope & {
56
+ subjectId: string;
57
+ }): Promise<void>;
58
+ subscribeCommitted(input: NumericWriteBehindScope, handler: (batch: NumericWriteBehindCommittedBatch) => void | Promise<void>): () => void;
59
+ stop(): void;
60
+ }
61
+ export declare function encodeNumericIpcValue(input: unknown): unknown;
62
+ //# sourceMappingURL=numeric-write-behind-proxy.d.ts.map