@helyx/module-reactionroles 1.0.15 → 1.0.17

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 (49) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +37 -53
  3. package/dist/builder-interactions.d.ts +11 -0
  4. package/dist/builder-interactions.js +293 -0
  5. package/dist/builder-presentation.d.ts +30 -0
  6. package/dist/builder-presentation.js +343 -0
  7. package/dist/builder.d.ts +3 -0
  8. package/dist/builder.js +22 -16
  9. package/dist/cleanup-repository.d.ts +24 -0
  10. package/dist/cleanup-repository.js +118 -0
  11. package/dist/constants.d.ts +3 -0
  12. package/dist/constants.js +4 -1
  13. package/dist/health.d.ts +16 -0
  14. package/dist/health.js +85 -0
  15. package/dist/index.js +66 -1147
  16. package/dist/logging.d.ts +33 -0
  17. package/dist/logging.js +40 -0
  18. package/dist/managed-resource.d.ts +2 -0
  19. package/dist/managed-resource.js +72 -0
  20. package/dist/member-interactions.d.ts +27 -0
  21. package/dist/member-interactions.js +360 -0
  22. package/dist/module-lifecycle.d.ts +3 -0
  23. package/dist/module-lifecycle.js +48 -0
  24. package/dist/panel-cache.d.ts +30 -0
  25. package/dist/panel-cache.js +197 -0
  26. package/dist/panel-create.d.ts +10 -0
  27. package/dist/panel-create.js +80 -0
  28. package/dist/panel-lifecycle.d.ts +47 -0
  29. package/dist/panel-lifecycle.js +272 -0
  30. package/dist/panel-managed.d.ts +14 -0
  31. package/dist/panel-managed.js +27 -0
  32. package/dist/panel-service.d.ts +13 -36
  33. package/dist/panel-service.js +48 -722
  34. package/dist/panel-support.d.ts +35 -0
  35. package/dist/panel-support.js +291 -0
  36. package/dist/permissions.d.ts +2 -0
  37. package/dist/permissions.js +8 -0
  38. package/dist/privacy.d.ts +5 -0
  39. package/dist/privacy.js +12 -0
  40. package/dist/record-mapping.d.ts +28 -0
  41. package/dist/record-mapping.js +161 -0
  42. package/dist/record-schema.d.ts +203 -0
  43. package/dist/record-schema.js +137 -0
  44. package/dist/records.d.ts +9 -201
  45. package/dist/records.js +32 -383
  46. package/dist/runtime-state.d.ts +9 -0
  47. package/dist/runtime-state.js +30 -0
  48. package/manifest.json +1 -1
  49. package/package.json +2 -2
package/dist/records.js CHANGED
@@ -1,151 +1,19 @@
1
- import { randomUUID } from "node:crypto";
2
- import { MODULE_ID } from "./constants.js";
3
- export const reactionRoleRecords = {
4
- panels: {
5
- table: "reactionroles_panels",
6
- fields: {
7
- panel_id: { type: "string" },
8
- guild_id: { type: "string" },
9
- channel_id: { type: "string" },
10
- message_id: { type: "string", optional: true },
11
- title: { type: "string" },
12
- message_body: { type: "string" },
13
- message_style: { type: "string" },
14
- control_type: { type: "string" },
15
- selection_mode: { type: "string" },
16
- persistent_reactions: { type: "boolean" },
17
- embed_colour: { type: "number", optional: true },
18
- dropdown_placeholder: { type: "string", optional: true },
19
- status: { type: "string" },
20
- revision: { type: "number" },
21
- created_by_user_id: { type: "string", optional: true },
22
- updated_by_user_id: { type: "string", optional: true },
23
- created_at: { type: "date" },
24
- updated_at: { type: "date" },
25
- },
26
- uniqueKeys: [["panel_id"], ["guild_id", "message_id"]],
27
- readableFields: [
28
- "panel_id",
29
- "guild_id",
30
- "channel_id",
31
- "message_id",
32
- "title",
33
- "message_body",
34
- "message_style",
35
- "control_type",
36
- "selection_mode",
37
- "persistent_reactions",
38
- "embed_colour",
39
- "dropdown_placeholder",
40
- "status",
41
- "revision",
42
- "created_by_user_id",
43
- "updated_by_user_id",
44
- "created_at",
45
- "updated_at",
46
- ],
47
- writableFields: [
48
- "channel_id",
49
- "message_id",
50
- "title",
51
- "message_body",
52
- "message_style",
53
- "control_type",
54
- "selection_mode",
55
- "persistent_reactions",
56
- "embed_colour",
57
- "dropdown_placeholder",
58
- "status",
59
- "revision",
60
- "updated_by_user_id",
61
- "updated_at",
62
- ],
63
- orderKeys: [["updated_at", "panel_id"]],
64
- groupableFields: ["status"],
65
- },
66
- entries: {
67
- table: "reactionroles_entries",
68
- fields: {
69
- entry_id: { type: "string" },
70
- panel_id: { type: "string" },
71
- position: { type: "number" },
72
- role_id: { type: "string" },
73
- nameplate: { type: "string" },
74
- emoji_kind: { type: "string", optional: true },
75
- emoji_name: { type: "string", optional: true },
76
- emoji_id: { type: "string", optional: true },
77
- emoji_animated: { type: "boolean", optional: true },
78
- button_style: { type: "string", optional: true },
79
- created_at: { type: "date" },
80
- updated_at: { type: "date" },
81
- },
82
- uniqueKeys: [
83
- ["entry_id"],
84
- ["panel_id", "position"],
85
- ["panel_id", "role_id"],
86
- ],
87
- readableFields: [
88
- "entry_id",
89
- "panel_id",
90
- "position",
91
- "role_id",
92
- "nameplate",
93
- "emoji_kind",
94
- "emoji_name",
95
- "emoji_id",
96
- "emoji_animated",
97
- "button_style",
98
- "created_at",
99
- "updated_at",
100
- ],
101
- orderKeys: [["position", "entry_id"]],
102
- },
103
- message_cleanup: {
104
- table: "reactionroles_message_cleanup",
105
- fields: {
106
- cleanup_id: { type: "string" },
107
- guild_id: { type: "string" },
108
- panel_id: { type: "string" },
109
- channel_id: { type: "string" },
110
- message_id: { type: "string" },
111
- reason: { type: "string" },
112
- attempts: { type: "number" },
113
- next_attempt_at: { type: "date" },
114
- created_at: { type: "date" },
115
- completed_at: { type: "date", optional: true },
116
- },
117
- uniqueKeys: [
118
- ["cleanup_id"],
119
- ["guild_id", "channel_id", "message_id", "reason"],
120
- ],
121
- readableFields: [
122
- "cleanup_id",
123
- "guild_id",
124
- "panel_id",
125
- "channel_id",
126
- "message_id",
127
- "reason",
128
- "attempts",
129
- "next_attempt_at",
130
- "created_at",
131
- "completed_at",
132
- ],
133
- writableFields: ["attempts", "next_attempt_at", "completed_at"],
134
- orderKeys: [["next_attempt_at", "cleanup_id"]],
135
- },
136
- };
137
- const panelSelect = reactionRoleRecords.panels.readableFields;
138
- const entrySelect = reactionRoleRecords.entries.readableFields;
139
- const cleanupSelect = reactionRoleRecords.message_cleanup.readableFields;
1
+ import { REACTION_ROLES_MODULE_ID } from "./constants.js";
2
+ import { MessageCleanupRepository } from "./cleanup-repository.js";
3
+ import { entrySelect, panelSelect } from "./record-schema.js";
4
+ import { entryValues, mapPanel, panelValues, requiredDate, requiredEnum, requiredString, } from "./record-mapping.js";
5
+ export { reactionRoleRecords, } from "./record-schema.js";
140
6
  export class ReactionRolePanelRepository {
141
7
  records;
8
+ #cleanup;
142
9
  constructor(records) {
143
10
  this.records = records;
11
+ this.#cleanup = new MessageCleanupRepository(records);
144
12
  }
145
13
  async create(input) {
146
14
  const now = input.now ?? new Date();
147
15
  const result = await this.records.replaceOwnedSet({
148
- moduleId: MODULE_ID,
16
+ moduleId: REACTION_ROLES_MODULE_ID,
149
17
  collection: "entries",
150
18
  owner: { panel_id: input.panelId },
151
19
  values: input.entries.map((entry, position) => entryValues(input.panelId, entry, position, now)),
@@ -171,7 +39,7 @@ export class ReactionRolePanelRepository {
171
39
  const now = input.now ?? new Date();
172
40
  const revision = input.panel.revision + 1;
173
41
  const result = await this.records.replaceOwnedSet({
174
- moduleId: MODULE_ID,
42
+ moduleId: REACTION_ROLES_MODULE_ID,
175
43
  collection: "entries",
176
44
  owner: { panel_id: input.panel.panelId },
177
45
  values: input.entries.map((entry, position) => entryValues(input.panel.panelId, entry, position, now)),
@@ -208,7 +76,7 @@ export class ReactionRolePanelRepository {
208
76
  }
209
77
  async findById(guildId, panelId) {
210
78
  const panel = await this.records.findOne({
211
- moduleId: MODULE_ID,
79
+ moduleId: REACTION_ROLES_MODULE_ID,
212
80
  collection: "panels",
213
81
  select: panelSelect,
214
82
  where: { panel_id: panelId },
@@ -219,7 +87,7 @@ export class ReactionRolePanelRepository {
219
87
  }
220
88
  async findByMessage(guildId, messageId) {
221
89
  const panel = await this.records.findOne({
222
- moduleId: MODULE_ID,
90
+ moduleId: REACTION_ROLES_MODULE_ID,
223
91
  collection: "panels",
224
92
  select: panelSelect,
225
93
  where: { guild_id: guildId, message_id: messageId },
@@ -228,7 +96,7 @@ export class ReactionRolePanelRepository {
228
96
  }
229
97
  async list(input) {
230
98
  const page = await this.records.findMany({
231
- moduleId: MODULE_ID,
99
+ moduleId: REACTION_ROLES_MODULE_ID,
232
100
  collection: "panels",
233
101
  select: panelSelect,
234
102
  where: { guild_id: input.guildId },
@@ -246,7 +114,7 @@ export class ReactionRolePanelRepository {
246
114
  }
247
115
  async listReferences(input) {
248
116
  const page = await this.records.findMany({
249
- moduleId: MODULE_ID,
117
+ moduleId: REACTION_ROLES_MODULE_ID,
250
118
  collection: "panels",
251
119
  select: ["panel_id", "title", "status", "updated_at"],
252
120
  where: { guild_id: input.guildId },
@@ -277,7 +145,7 @@ export class ReactionRolePanelRepository {
277
145
  }
278
146
  async listByChannel(guildId, channelId, limit = 100) {
279
147
  const page = await this.records.findMany({
280
- moduleId: MODULE_ID,
148
+ moduleId: REACTION_ROLES_MODULE_ID,
281
149
  collection: "panels",
282
150
  select: panelSelect,
283
151
  where: { guild_id: guildId, channel_id: channelId },
@@ -297,7 +165,7 @@ export class ReactionRolePanelRepository {
297
165
  }
298
166
  async listInteractive(input) {
299
167
  const page = await this.records.findMany({
300
- moduleId: MODULE_ID,
168
+ moduleId: REACTION_ROLES_MODULE_ID,
301
169
  collection: "panels",
302
170
  select: panelSelect,
303
171
  where: { status: input.status },
@@ -315,7 +183,7 @@ export class ReactionRolePanelRepository {
315
183
  }
316
184
  async markStatus(input) {
317
185
  return (await this.records.updateOne({
318
- moduleId: MODULE_ID,
186
+ moduleId: REACTION_ROLES_MODULE_ID,
319
187
  collection: "panels",
320
188
  where: { panel_id: input.panelId },
321
189
  expected: { field: "revision", value: input.expectedRevision },
@@ -345,94 +213,36 @@ export class ReactionRolePanelRepository {
345
213
  }
346
214
  async deleteClaimed(panelId) {
347
215
  return ((await this.records.deleteMany({
348
- moduleId: MODULE_ID,
216
+ moduleId: REACTION_ROLES_MODULE_ID,
349
217
  collection: "panels",
350
218
  where: { panel_id: panelId, status: "deleting" },
351
219
  limit: 1,
352
220
  })).deleted === 1);
353
221
  }
354
222
  async recordCleanup(input) {
355
- const created = await this.records.appendUnique({
356
- moduleId: MODULE_ID,
357
- collection: "message_cleanup",
358
- values: {
359
- cleanup_id: randomUUID(),
360
- guild_id: input.guildId,
361
- panel_id: input.panelId,
362
- channel_id: input.channelId,
363
- message_id: input.messageId,
364
- reason: input.reason,
365
- attempts: 0,
366
- next_attempt_at: input.now ?? new Date(),
367
- created_at: input.now ?? new Date(),
368
- },
369
- uniqueBy: ["guild_id", "channel_id", "message_id", "reason"],
370
- });
371
- return created.created;
223
+ return this.#cleanup.record(input);
372
224
  }
373
225
  async listPendingCleanup(limit = 100) {
374
- const page = await this.records.findMany({
375
- moduleId: MODULE_ID,
376
- collection: "message_cleanup",
377
- select: cleanupSelect,
378
- where: { completed_at: null },
379
- orderBy: [
380
- { field: "next_attempt_at", direction: "asc" },
381
- { field: "cleanup_id", direction: "asc" },
382
- ],
383
- limit: Math.min(100, Math.max(1, limit)),
384
- });
385
- return page.records.map((row) => ({
386
- cleanupId: requiredString(row, "cleanup_id"),
387
- guildId: requiredString(row, "guild_id"),
388
- panelId: requiredString(row, "panel_id"),
389
- channelId: requiredString(row, "channel_id"),
390
- messageId: requiredString(row, "message_id"),
391
- reason: requiredEnum(row, "reason", [
392
- "replacement",
393
- "rollback",
394
- "delete",
395
- ]),
396
- attempts: requiredNumber(row, "attempts"),
397
- nextAttemptAt: requiredDate(row, "next_attempt_at"),
398
- }));
226
+ return this.#cleanup.list(limit);
227
+ }
228
+ listPendingCleanupPage(limit = 100, cursor) {
229
+ return this.#cleanup.listPage(limit, cursor);
230
+ }
231
+ findPendingCleanup(cleanupId) {
232
+ return this.#cleanup.find(cleanupId);
399
233
  }
400
234
  async claimCleanup(cleanup, now = new Date()) {
401
- if (cleanup.attempts >= 10 ||
402
- cleanup.nextAttemptAt.getTime() > now.getTime())
403
- return null;
404
- const attempts = cleanup.attempts + 1;
405
- const claimed = await this.records.updateOne({
406
- moduleId: MODULE_ID,
407
- collection: "message_cleanup",
408
- where: { cleanup_id: cleanup.cleanupId },
409
- expected: { field: "attempts", value: cleanup.attempts },
410
- values: {
411
- attempts,
412
- next_attempt_at: new Date(now.getTime() + Math.min(3_600_000, 30_000 * 2 ** cleanup.attempts)),
413
- },
414
- });
415
- return claimed.updated ? { ...cleanup, attempts } : null;
235
+ return this.#cleanup.claim(cleanup, now);
416
236
  }
417
237
  async completeCleanup(cleanup, now = new Date()) {
418
- return (await this.records.updateOne({
419
- moduleId: MODULE_ID,
420
- collection: "message_cleanup",
421
- where: { cleanup_id: cleanup.cleanupId },
422
- expected: { field: "attempts", value: cleanup.attempts },
423
- values: { completed_at: now },
424
- })).updated;
238
+ return this.#cleanup.complete(cleanup, now);
425
239
  }
426
240
  pendingCleanupCount(panelId) {
427
- return this.records.count({
428
- moduleId: MODULE_ID,
429
- collection: "message_cleanup",
430
- where: { panel_id: panelId, completed_at: null },
431
- });
241
+ return this.#cleanup.count(panelId);
432
242
  }
433
243
  async #withEntries(panel) {
434
244
  const entries = await this.records.findMany({
435
- moduleId: MODULE_ID,
245
+ moduleId: REACTION_ROLES_MODULE_ID,
436
246
  collection: "entries",
437
247
  select: entrySelect,
438
248
  where: { panel_id: requiredString(panel, "panel_id") },
@@ -449,7 +259,7 @@ export class ReactionRolePanelRepository {
449
259
  return [];
450
260
  const panelIds = panels.map((panel) => requiredString(panel, "panel_id"));
451
261
  const entries = await this.records.findManyByValues({
452
- moduleId: MODULE_ID,
262
+ moduleId: REACTION_ROLES_MODULE_ID,
453
263
  collection: "entries",
454
264
  select: entrySelect,
455
265
  where: {},
@@ -472,7 +282,7 @@ export class ReactionRolePanelRepository {
472
282
  }
473
283
  async #listByEntryField(guildId, field, value, limit) {
474
284
  const entries = await this.records.findMany({
475
- moduleId: MODULE_ID,
285
+ moduleId: REACTION_ROLES_MODULE_ID,
476
286
  collection: "entries",
477
287
  select: ["panel_id"],
478
288
  where: { [field]: value },
@@ -487,7 +297,7 @@ export class ReactionRolePanelRepository {
487
297
  if (uniquePanelIds.length === 0)
488
298
  return [];
489
299
  const panels = await this.records.findManyByValues({
490
- moduleId: MODULE_ID,
300
+ moduleId: REACTION_ROLES_MODULE_ID,
491
301
  collection: "panels",
492
302
  select: panelSelect,
493
303
  where: { guild_id: guildId },
@@ -505,164 +315,3 @@ export class ReactionRolePanelRepository {
505
315
  });
506
316
  }
507
317
  }
508
- function panelValues(input) {
509
- return {
510
- panel_id: input.panelId,
511
- guild_id: input.guildId,
512
- channel_id: input.draft.channelId,
513
- ...(input.messageId ? { message_id: input.messageId } : {}),
514
- title: input.draft.title,
515
- message_body: input.draft.messageBody,
516
- message_style: input.draft.messageStyle,
517
- control_type: input.draft.controlType,
518
- selection_mode: input.draft.selectionMode,
519
- persistent_reactions: input.draft.persistentReactions,
520
- ...(input.draft.embedColour === undefined
521
- ? {}
522
- : { embed_colour: input.draft.embedColour }),
523
- ...(input.draft.dropdownPlaceholder === undefined
524
- ? {}
525
- : { dropdown_placeholder: input.draft.dropdownPlaceholder }),
526
- status: input.status,
527
- revision: input.revision,
528
- created_by_user_id: input.actorUserId,
529
- updated_by_user_id: input.actorUserId,
530
- created_at: input.now,
531
- updated_at: input.now,
532
- };
533
- }
534
- function entryValues(panelId, entry, position, now) {
535
- return {
536
- entry_id: entry.entryId,
537
- panel_id: panelId,
538
- position,
539
- role_id: entry.roleId,
540
- nameplate: entry.nameplate,
541
- ...(entry.emoji
542
- ? {
543
- emoji_kind: entry.emoji.kind,
544
- emoji_name: entry.emoji.name,
545
- ...(entry.emoji.id ? { emoji_id: entry.emoji.id } : {}),
546
- emoji_animated: entry.emoji.animated,
547
- }
548
- : {}),
549
- ...(entry.buttonStyle ? { button_style: entry.buttonStyle } : {}),
550
- created_at: now,
551
- updated_at: now,
552
- };
553
- }
554
- function mapPanel(row, entries) {
555
- return {
556
- panelId: requiredString(row, "panel_id"),
557
- guildId: requiredString(row, "guild_id"),
558
- channelId: requiredString(row, "channel_id"),
559
- messageId: nullableString(row, "message_id"),
560
- title: requiredString(row, "title"),
561
- messageBody: requiredString(row, "message_body"),
562
- messageStyle: requiredEnum(row, "message_style", ["text", "embed"]),
563
- controlType: requiredEnum(row, "control_type", [
564
- "buttons",
565
- "dropdown",
566
- "reactions",
567
- ]),
568
- selectionMode: requiredEnum(row, "selection_mode", [
569
- "multiple",
570
- "one_only",
571
- ]),
572
- persistentReactions: optionalBoolean(row, "persistent_reactions", true),
573
- ...(row.embed_colour === null
574
- ? {}
575
- : { embedColour: requiredNumber(row, "embed_colour") }),
576
- ...(row.dropdown_placeholder === null
577
- ? {}
578
- : {
579
- dropdownPlaceholder: requiredString(row, "dropdown_placeholder"),
580
- }),
581
- entries: entries.map((entry) => {
582
- const emojiKind = nullableString(entry, "emoji_kind");
583
- const emojiName = nullableString(entry, "emoji_name");
584
- const emojiId = nullableString(entry, "emoji_id");
585
- const emoji = emojiKind && emojiName
586
- ? {
587
- kind: requiredLiteral(emojiKind, ["unicode", "custom"]),
588
- name: emojiName,
589
- ...(emojiId ? { id: emojiId } : {}),
590
- animated: entry.emoji_animated === true,
591
- identifier: emojiId ? `${emojiName}:${emojiId}` : emojiName,
592
- key: emojiId ? `custom:${emojiId}` : `unicode:${emojiName}`,
593
- }
594
- : null;
595
- return {
596
- entryId: requiredString(entry, "entry_id"),
597
- roleId: requiredString(entry, "role_id"),
598
- nameplate: requiredString(entry, "nameplate"),
599
- ...(emoji ? { emoji: emoji.identifier } : {}),
600
- ...(entry.button_style
601
- ? {
602
- buttonStyle: requiredEnum(entry, "button_style", [
603
- "primary",
604
- "secondary",
605
- "success",
606
- "danger",
607
- ]),
608
- }
609
- : {}),
610
- };
611
- }),
612
- status: requiredEnum(row, "status", [
613
- "pending",
614
- "active",
615
- "disabled",
616
- "message_missing",
617
- "needs_attention",
618
- "cleanup_pending",
619
- "deleting",
620
- ]),
621
- revision: requiredNumber(row, "revision"),
622
- createdByUserId: nullableString(row, "created_by_user_id"),
623
- updatedByUserId: nullableString(row, "updated_by_user_id"),
624
- createdAt: requiredDate(row, "created_at"),
625
- updatedAt: requiredDate(row, "updated_at"),
626
- };
627
- }
628
- function requiredString(row, field) {
629
- const value = row[field];
630
- if (typeof value !== "string" || value.length === 0)
631
- throw new Error(`Invalid Reaction Roles record field: ${field}`);
632
- return value;
633
- }
634
- function nullableString(row, field) {
635
- const value = row[field];
636
- if (value === null || value === undefined)
637
- return null;
638
- return requiredString(row, field);
639
- }
640
- function requiredNumber(row, field) {
641
- const value = Number(row[field]);
642
- if (!Number.isSafeInteger(value))
643
- throw new Error(`Invalid Reaction Roles record field: ${field}`);
644
- return value;
645
- }
646
- function optionalBoolean(row, field, fallback) {
647
- const value = row[field];
648
- if (value === null || value === undefined)
649
- return fallback;
650
- if (typeof value !== "boolean")
651
- throw new Error(`Invalid Reaction Roles record field: ${field}`);
652
- return value;
653
- }
654
- function requiredDate(row, field) {
655
- const value = row[field];
656
- const date = value instanceof Date ? value : new Date(String(value));
657
- if (Number.isNaN(date.getTime()))
658
- throw new Error(`Invalid Reaction Roles record field: ${field}`);
659
- return date;
660
- }
661
- function requiredEnum(row, field, values) {
662
- return requiredLiteral(requiredString(row, field), values);
663
- }
664
- function requiredLiteral(value, values) {
665
- if (!values.includes(value))
666
- throw new Error("Invalid Reaction Roles record enum");
667
- return value;
668
- }
@@ -0,0 +1,9 @@
1
+ import { ReactionRolesHealthTracker } from "./health.js";
2
+ import type { ReactionRolesHealthSource } from "./health.js";
3
+ import { PanelSnapshotCache, ReactionRolePanelService } from "./panel-service.js";
4
+ export declare const reactionRoleCache: PanelSnapshotCache;
5
+ export declare const reactionRolePanels: ReactionRolePanelService;
6
+ export declare const reactionRoleMemberLocks: Map<string, Promise<void>>;
7
+ export declare const reactionRoleHealth: ReactionRolesHealthTracker;
8
+ export declare function runReactionRolePrimary<T>(operation: () => Promise<T>, source?: ReactionRolesHealthSource): Promise<T>;
9
+ export declare function runReactionRoleFailureOnly<T>(operation: () => Promise<T>, source: ReactionRolesHealthSource): Promise<T>;
@@ -0,0 +1,30 @@
1
+ import { DashboardActionValidationError } from "@helyx/sdk";
2
+ import { ReactionRolesHealthTracker } from "./health.js";
3
+ import { PanelSnapshotCache, ReactionRolePanelService, } from "./panel-service.js";
4
+ // Guild-worker ownership serializes dashboard and Discord mutations in one
5
+ // runtime; the short TTL remains the correctness fence after worker turnover.
6
+ export const reactionRoleCache = new PanelSnapshotCache();
7
+ export const reactionRolePanels = new ReactionRolePanelService(reactionRoleCache);
8
+ export const reactionRoleMemberLocks = new Map();
9
+ export const reactionRoleHealth = new ReactionRolesHealthTracker();
10
+ export async function runReactionRolePrimary(operation, source = "resource") {
11
+ try {
12
+ const result = await operation();
13
+ reactionRoleHealth.primarySuccess(source);
14
+ return result;
15
+ }
16
+ catch (error) {
17
+ if (!(error instanceof DashboardActionValidationError))
18
+ reactionRoleHealth.primaryFailure(source);
19
+ throw error;
20
+ }
21
+ }
22
+ export async function runReactionRoleFailureOnly(operation, source) {
23
+ try {
24
+ return await operation();
25
+ }
26
+ catch (error) {
27
+ reactionRoleHealth.primaryFailure(source);
28
+ throw error;
29
+ }
30
+ }
package/manifest.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "healthExposure": "direct",
11
11
  "id": "helyx.reactionroles",
12
12
  "name": "Reaction Roles",
13
- "version": "1.0.15",
13
+ "version": "1.0.17",
14
14
  "description": "Let members select approved roles themselves.",
15
15
  "entry": "./dist/index.js",
16
16
  "framework": "^0.1.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helyx/module-reactionroles",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "Configurable self-service Discord role panels for Helyx.",
5
5
  "keywords": [
6
6
  "helyx",
@@ -50,6 +50,6 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@helyx/module-manifest": ">=0.8.0 <0.9.0",
53
- "@helyx/sdk": ">=0.9.0 <0.10.0"
53
+ "@helyx/sdk": ">=0.10.0"
54
54
  }
55
55
  }