@helyx/bot 1.5.2 → 1.5.4
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/CHANGELOG.md +27 -0
- package/command-root-catalogue.json +452 -0
- package/dist/cached-message-deletion-authority.d.ts +27 -0
- package/dist/cached-message-deletion-authority.js +288 -0
- package/dist/channel-audit-configuration.d.ts +11 -1
- package/dist/channel-audit-configuration.js +28 -6
- package/dist/channel-audit-control.js +42 -2
- package/dist/channel-audit-permissions.js +76 -3
- package/dist/cloud-command-policy-sync.d.ts +26 -0
- package/dist/cloud-command-policy-sync.js +198 -0
- package/dist/cloud-connector.d.ts +6 -1
- package/dist/cloud-connector.js +224 -53
- package/dist/cloud-control-dispatcher.js +18 -0
- package/dist/command-policy-runtime.d.ts +23 -0
- package/dist/command-policy-runtime.js +53 -0
- package/dist/command-root-catalogue.d.ts +5 -0
- package/dist/command-root-catalogue.js +16 -0
- package/dist/control-error-mapping.d.ts +8 -0
- package/dist/control-error-mapping.js +60 -0
- package/dist/control-errors.d.ts +3 -1
- package/dist/control-errors.js +4 -1
- package/dist/control-health-routes.d.ts +10 -0
- package/dist/control-health-routes.js +28 -0
- package/dist/control-server-schemas.d.ts +7 -0
- package/dist/control-server-schemas.js +9 -0
- package/dist/control-server.d.ts +27 -6
- package/dist/control-server.js +103 -122
- package/dist/dashboard-feature-control.d.ts +2 -1
- package/dist/dashboard-feature-control.js +1 -0
- package/dist/dashboard-logging.d.ts +20 -0
- package/dist/dashboard-logging.js +39 -0
- package/dist/dashboard-module-projection.d.ts +1 -1
- package/dist/dashboard-module-projection.js +5 -2
- package/dist/dashboard-preview-import-routes.js +8 -0
- package/dist/database-bootstrap.d.ts +1 -1
- package/dist/database-bootstrap.js +4 -3
- package/dist/discord-channel-audit.js +1 -0
- package/dist/discord-user-identities.d.ts +14 -0
- package/dist/discord-user-identities.js +64 -0
- package/dist/emergency-deletion-dispatcher.d.ts +68 -0
- package/dist/emergency-deletion-dispatcher.js +502 -0
- package/dist/emergency-deletion-rest-transport.d.ts +24 -0
- package/dist/emergency-deletion-rest-transport.js +253 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.js +53 -10
- package/dist/guarded-discord-dispatcher.d.ts +29 -0
- package/dist/guarded-discord-dispatcher.js +204 -0
- package/dist/guild-command-discord-client.d.ts +2 -0
- package/dist/guild-command-discord-client.js +7 -0
- package/dist/guild-command-projection.d.ts +4 -1
- package/dist/guild-command-projection.js +25 -0
- package/dist/guild-command-read-service.d.ts +10 -1
- package/dist/guild-command-read-service.js +37 -0
- package/dist/guild-command-worker.d.ts +19 -4
- package/dist/guild-command-worker.js +356 -26
- package/dist/guild-message-protection-author-roles.d.ts +7 -0
- package/dist/guild-message-protection-author-roles.js +93 -0
- package/dist/guild-message-protection-deletion.d.ts +21 -0
- package/dist/guild-message-protection-deletion.js +138 -0
- package/dist/guild-message-protection-delivery.d.ts +41 -0
- package/dist/guild-message-protection-delivery.js +223 -0
- package/dist/guild-message-update-metadata.d.ts +32 -0
- package/dist/guild-message-update-metadata.js +255 -0
- package/dist/guilds.d.ts +2 -1
- package/dist/guilds.js +11 -1
- package/dist/hosted-server-moderation.d.ts +2 -2
- package/dist/index.js +68 -4
- package/dist/interactions.d.ts +3 -1
- package/dist/interactions.js +77 -12
- package/dist/invite-rollup-maintenance.d.ts +9 -0
- package/dist/invite-rollup-maintenance.js +12 -0
- package/dist/managed-resource-authorization.d.ts +15 -0
- package/dist/managed-resource-authorization.js +37 -0
- package/dist/managed-resource-create-defaults.d.ts +21 -0
- package/dist/managed-resource-create-defaults.js +83 -0
- package/dist/managed-resource-query.d.ts +6 -0
- package/dist/managed-resource-query.js +15 -0
- package/dist/member-role-ids.d.ts +17 -0
- package/dist/member-role-ids.js +66 -0
- package/dist/message-protection-admission.d.ts +60 -0
- package/dist/message-protection-admission.js +279 -0
- package/dist/message-protection-cold-data.d.ts +42 -0
- package/dist/message-protection-cold-data.js +180 -0
- package/dist/message-protection-coordinator.d.ts +73 -0
- package/dist/message-protection-coordinator.js +622 -0
- package/dist/message-protection-settings.d.ts +40 -0
- package/dist/message-protection-settings.js +275 -0
- package/dist/message-protection-summary.d.ts +8 -0
- package/dist/message-protection-summary.js +98 -0
- package/dist/module-availability-control.js +20 -12
- package/dist/module-logging.d.ts +3 -2
- package/dist/module-logging.js +6 -1
- package/dist/modules.js +56 -1
- package/dist/mysql-runtime-persistence.d.ts +1 -0
- package/dist/mysql-runtime-persistence.js +4 -1
- package/dist/nexus-command-recovery-control-routes.d.ts +5 -0
- package/dist/nexus-command-recovery-control-routes.js +48 -0
- package/dist/nexus-command-references.d.ts +6 -0
- package/dist/nexus-command-references.js +33 -0
- package/dist/scheduled-task-runner.js +23 -4
- package/dist/shard-command-startup.d.ts +13 -0
- package/dist/shard-command-startup.js +27 -0
- package/dist/shard-contracts.d.ts +84 -0
- package/dist/shard-contracts.js +33 -0
- package/dist/shard-core-readiness.d.ts +5 -0
- package/dist/shard-core-readiness.js +19 -0
- package/dist/shard-router.d.ts +3 -0
- package/dist/shard-router.js +10 -1
- package/dist/shard-supervisor.d.ts +1 -1
- package/dist/shard-supervisor.js +124 -43
- package/dist/shard-worker.js +25 -15
- package/documentation/README.md +1 -0
- package/documentation/examples/.env.example +0 -45
- package/documentation/guides/self-hosting.md +54 -0
- package/package.json +18 -17
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/** Dormant private queue. No SDK service, provider adapter or runtime registration. */
|
|
2
|
+
export class EmergencyDeletionDispatcher {
|
|
3
|
+
#entries = new Map();
|
|
4
|
+
#routes = new Map();
|
|
5
|
+
#guilds = new Map();
|
|
6
|
+
#options;
|
|
7
|
+
#now;
|
|
8
|
+
#timer;
|
|
9
|
+
#lastTime = 0;
|
|
10
|
+
#globalBlockedUntil = 0;
|
|
11
|
+
#inFlight = 0;
|
|
12
|
+
#dropped = 0;
|
|
13
|
+
#stopped = false;
|
|
14
|
+
#pumping = false;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
for (const [value, minimum, maximum] of [
|
|
17
|
+
[options.capacity, 1, 100_000],
|
|
18
|
+
[options.perChannelCapacity, 1, options.capacity],
|
|
19
|
+
[options.perGuildCapacity, 1, Math.max(1, options.capacity - 1)],
|
|
20
|
+
[options.concurrency, 1, options.capacity],
|
|
21
|
+
[options.maximumAgeMs, 1, 600_000],
|
|
22
|
+
[options.maximumAttempts, 1, 16],
|
|
23
|
+
[options.transientBackoffMs, 1, 600_000],
|
|
24
|
+
[options.maximumBackoffMs, options.transientBackoffMs, 600_000],
|
|
25
|
+
[options.maximumRetryAfterMs, 1, 86_400_000],
|
|
26
|
+
])
|
|
27
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum)
|
|
28
|
+
throw new TypeError("Invalid emergency deletion queue limits");
|
|
29
|
+
if (typeof options.isCurrent !== "function" ||
|
|
30
|
+
typeof options.transport?.deleteExact !== "function" ||
|
|
31
|
+
(options.now !== undefined && typeof options.now !== "function"))
|
|
32
|
+
throw new TypeError("Invalid emergency deletion queue ports");
|
|
33
|
+
this.#options = Object.freeze({ ...options });
|
|
34
|
+
this.#now = options.now ?? (() => performance.now());
|
|
35
|
+
}
|
|
36
|
+
enqueue(input, authority) {
|
|
37
|
+
return this.#enqueue(input, authority).result;
|
|
38
|
+
}
|
|
39
|
+
/** Synchronous Core ownership result; duplicate entries share their SDK promise. */
|
|
40
|
+
enqueueCaptured(input, authority) {
|
|
41
|
+
const accepted = this.#enqueue(input, authority);
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
result: accepted.sdkResult,
|
|
44
|
+
retainedProvidedAuthority: accepted.retainedProvidedAuthority,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
#enqueue(input, authority) {
|
|
48
|
+
if (this.#stopped)
|
|
49
|
+
return resolved("stopped");
|
|
50
|
+
let stable;
|
|
51
|
+
try {
|
|
52
|
+
stable = Object.freeze({
|
|
53
|
+
guildId: input.guildId,
|
|
54
|
+
moduleId: input.moduleId,
|
|
55
|
+
channelId: input.channelId,
|
|
56
|
+
messageId: input.messageId,
|
|
57
|
+
admissionToken: input.admissionToken,
|
|
58
|
+
});
|
|
59
|
+
if (![stable.guildId, stable.channelId, stable.messageId].every((id) => typeof id === "string" && /^\d{17,20}$/u.test(id)) ||
|
|
60
|
+
typeof stable.moduleId !== "string" ||
|
|
61
|
+
!/^helyx\.[a-z0-9][a-z0-9-]{0,79}$/u.test(stable.moduleId) ||
|
|
62
|
+
typeof stable.admissionToken !== "string" ||
|
|
63
|
+
!/^[A-Za-z0-9._:-]{1,256}$/u.test(stable.admissionToken))
|
|
64
|
+
return resolved("invalid-request");
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return resolved("invalid-request");
|
|
68
|
+
}
|
|
69
|
+
const capturedAuthority = authority === undefined ? undefined : captureAuthority(stable, authority);
|
|
70
|
+
if (capturedAuthority === null)
|
|
71
|
+
return resolved("invalid-request");
|
|
72
|
+
if (this.#stopped)
|
|
73
|
+
return resolved("stopped");
|
|
74
|
+
const now = this.#time();
|
|
75
|
+
if (now === null)
|
|
76
|
+
return resolved("stopped");
|
|
77
|
+
this.#pump();
|
|
78
|
+
if (this.#stopped)
|
|
79
|
+
return resolved("stopped");
|
|
80
|
+
const current = this.#current(stable, capturedAuthority);
|
|
81
|
+
if (this.#stopped)
|
|
82
|
+
return resolved("stopped");
|
|
83
|
+
if (!current)
|
|
84
|
+
return resolved("revoked");
|
|
85
|
+
const routeKey = `${stable.guildId}/${stable.channelId}`;
|
|
86
|
+
const key = `${routeKey}/${stable.messageId}/${stable.moduleId}/${stable.admissionToken}`;
|
|
87
|
+
const duplicate = this.#entries.get(key);
|
|
88
|
+
if (duplicate)
|
|
89
|
+
return submitted(duplicate, authority);
|
|
90
|
+
let route = this.#routes.get(routeKey);
|
|
91
|
+
const guildRoutes = this.#guilds.get(stable.guildId);
|
|
92
|
+
const guildEntries = [...(guildRoutes?.values() ?? [])].reduce((total, item) => total + item.entries.length, 0);
|
|
93
|
+
if (this.#entries.size >= this.#options.capacity ||
|
|
94
|
+
guildEntries >= this.#options.perGuildCapacity ||
|
|
95
|
+
(route?.entries.length ?? 0) >= this.#options.perChannelCapacity ||
|
|
96
|
+
(!route &&
|
|
97
|
+
(this.#routes.size >= this.#options.capacity ||
|
|
98
|
+
(guildRoutes?.size ?? 0) >= this.#options.perGuildCapacity))) {
|
|
99
|
+
this.#dropped = Math.min(Number.MAX_SAFE_INTEGER, this.#dropped + 1);
|
|
100
|
+
return resolved("capacity");
|
|
101
|
+
}
|
|
102
|
+
if (!route) {
|
|
103
|
+
route = {
|
|
104
|
+
key: routeKey,
|
|
105
|
+
guildId: stable.guildId,
|
|
106
|
+
entries: [],
|
|
107
|
+
blockedUntil: 0,
|
|
108
|
+
active: false,
|
|
109
|
+
};
|
|
110
|
+
this.#routes.set(routeKey, route);
|
|
111
|
+
let routes = this.#guilds.get(stable.guildId);
|
|
112
|
+
if (!routes) {
|
|
113
|
+
routes = new Map();
|
|
114
|
+
this.#guilds.set(stable.guildId, routes);
|
|
115
|
+
}
|
|
116
|
+
routes.set(routeKey, route);
|
|
117
|
+
}
|
|
118
|
+
let settle;
|
|
119
|
+
let settleSdk;
|
|
120
|
+
const result = new Promise((resolve) => {
|
|
121
|
+
settle = resolve;
|
|
122
|
+
});
|
|
123
|
+
const sdkResult = new Promise((resolve) => {
|
|
124
|
+
settleSdk = resolve;
|
|
125
|
+
});
|
|
126
|
+
const entry = {
|
|
127
|
+
key,
|
|
128
|
+
input: stable,
|
|
129
|
+
acceptedAt: now,
|
|
130
|
+
readyAt: now,
|
|
131
|
+
attempts: 0,
|
|
132
|
+
active: false,
|
|
133
|
+
authority: capturedAuthority,
|
|
134
|
+
providedAuthority: authority,
|
|
135
|
+
result,
|
|
136
|
+
sdkResult,
|
|
137
|
+
settle,
|
|
138
|
+
settleSdk,
|
|
139
|
+
};
|
|
140
|
+
this.#entries.set(key, entry);
|
|
141
|
+
route.entries.push(entry);
|
|
142
|
+
this.#pump();
|
|
143
|
+
return submitted(entry, authority);
|
|
144
|
+
}
|
|
145
|
+
/** Lifecycle callers can synchronously discard waiting work after revocation. */
|
|
146
|
+
revalidate() {
|
|
147
|
+
this.#pump();
|
|
148
|
+
}
|
|
149
|
+
stop() {
|
|
150
|
+
if (this.#stopped)
|
|
151
|
+
return;
|
|
152
|
+
this.#stopped = true;
|
|
153
|
+
this.#clearTimer();
|
|
154
|
+
for (const route of this.#routes.values()) {
|
|
155
|
+
for (const entry of [...route.entries]) {
|
|
156
|
+
this.#revoke(entry);
|
|
157
|
+
if (!entry.active)
|
|
158
|
+
this.#finish(route, entry, "stopped");
|
|
159
|
+
}
|
|
160
|
+
if (!route.active)
|
|
161
|
+
this.#removeRoute(route);
|
|
162
|
+
}
|
|
163
|
+
// Already-issued requests retain their actual outcome; they cannot be recalled.
|
|
164
|
+
}
|
|
165
|
+
stats() {
|
|
166
|
+
return Object.freeze({
|
|
167
|
+
pending: this.#entries.size - this.#inFlight,
|
|
168
|
+
inFlight: this.#inFlight,
|
|
169
|
+
retainedRoutes: this.#routes.size,
|
|
170
|
+
dropped: this.#dropped,
|
|
171
|
+
stopped: this.#stopped,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
#current(input, authority) {
|
|
175
|
+
try {
|
|
176
|
+
return (this.#options.isCurrent(input) === true &&
|
|
177
|
+
!this.#stopped &&
|
|
178
|
+
(authority === undefined || authority.isCurrent() === true) &&
|
|
179
|
+
!this.#stopped);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
#time() {
|
|
186
|
+
try {
|
|
187
|
+
const value = this.#now();
|
|
188
|
+
if (this.#stopped)
|
|
189
|
+
return null;
|
|
190
|
+
if (!Number.isFinite(value) ||
|
|
191
|
+
value < this.#lastTime ||
|
|
192
|
+
value > Number.MAX_SAFE_INTEGER - 600_000)
|
|
193
|
+
throw new Error("Invalid monotonic clock");
|
|
194
|
+
this.#lastTime = value;
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
this.stop();
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
#pump() {
|
|
203
|
+
if (this.#stopped || this.#pumping)
|
|
204
|
+
return;
|
|
205
|
+
this.#pumping = true;
|
|
206
|
+
try {
|
|
207
|
+
let now = this.#time();
|
|
208
|
+
if (now === null)
|
|
209
|
+
return;
|
|
210
|
+
for (const route of this.#routes.values()) {
|
|
211
|
+
for (const entry of [...route.entries]) {
|
|
212
|
+
if (entry.active)
|
|
213
|
+
continue;
|
|
214
|
+
if (now >= entry.acceptedAt + this.#options.maximumAgeMs)
|
|
215
|
+
this.#finish(route, entry, "expired");
|
|
216
|
+
else if (!this.#current(entry.input, entry.authority))
|
|
217
|
+
this.#finish(route, entry, "revoked");
|
|
218
|
+
}
|
|
219
|
+
if (!route.entries.length && route.blockedUntil <= now)
|
|
220
|
+
this.#removeRoute(route);
|
|
221
|
+
}
|
|
222
|
+
while (!this.#stopped &&
|
|
223
|
+
this.#inFlight < this.#options.concurrency &&
|
|
224
|
+
now >= this.#globalBlockedUntil) {
|
|
225
|
+
const route = this.#nextRoute(now);
|
|
226
|
+
if (!route)
|
|
227
|
+
break;
|
|
228
|
+
const entry = route.entries[0];
|
|
229
|
+
if (entry.attempts >= this.#options.maximumAttempts) {
|
|
230
|
+
this.#finish(route, entry, "attempts-exhausted");
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
// Revalidate immediately before handing anything to the transport.
|
|
234
|
+
if (!this.#current(entry.input, entry.authority)) {
|
|
235
|
+
this.#finish(route, entry, "revoked");
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (this.#stopped)
|
|
239
|
+
break;
|
|
240
|
+
now = this.#time();
|
|
241
|
+
if (now === null)
|
|
242
|
+
break;
|
|
243
|
+
if (now >= entry.acceptedAt + this.#options.maximumAgeMs) {
|
|
244
|
+
this.#finish(route, entry, "expired");
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
entry.active = true;
|
|
248
|
+
route.active = true;
|
|
249
|
+
entry.attempts++;
|
|
250
|
+
this.#inFlight++;
|
|
251
|
+
void this.#execute(route, entry);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
this.#pumping = false;
|
|
256
|
+
this.#schedule();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
#nextRoute(now) {
|
|
260
|
+
for (const [guildId, routes] of this.#guilds) {
|
|
261
|
+
for (const [key, route] of routes) {
|
|
262
|
+
const first = route.entries[0];
|
|
263
|
+
if (!route.active &&
|
|
264
|
+
first &&
|
|
265
|
+
now >= first.readyAt &&
|
|
266
|
+
now >= route.blockedUntil) {
|
|
267
|
+
// Move both selections to the tail; many channels do not buy extra turns.
|
|
268
|
+
routes.delete(key);
|
|
269
|
+
routes.set(key, route);
|
|
270
|
+
this.#guilds.delete(guildId);
|
|
271
|
+
this.#guilds.set(guildId, routes);
|
|
272
|
+
return route;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
async #execute(route, entry) {
|
|
279
|
+
let outcome;
|
|
280
|
+
const attemptNumber = entry.attempts;
|
|
281
|
+
let refused = false;
|
|
282
|
+
let attemptSettled = false;
|
|
283
|
+
const live = () => !this.#stopped &&
|
|
284
|
+
!attemptSettled &&
|
|
285
|
+
entry.active &&
|
|
286
|
+
entry.attempts === attemptNumber &&
|
|
287
|
+
this.#entries.get(entry.key) === entry;
|
|
288
|
+
const attempt = Object.freeze({
|
|
289
|
+
isCurrent: () => {
|
|
290
|
+
if (refused)
|
|
291
|
+
return false;
|
|
292
|
+
const now = live() ? this.#time() : null;
|
|
293
|
+
if (now === null ||
|
|
294
|
+
now >= entry.acceptedAt + this.#options.maximumAgeMs ||
|
|
295
|
+
!live() ||
|
|
296
|
+
!this.#current(entry.input, entry.authority) ||
|
|
297
|
+
!live()) {
|
|
298
|
+
refused = true;
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
const checkedAt = this.#time();
|
|
302
|
+
if (checkedAt === null ||
|
|
303
|
+
checkedAt >= entry.acceptedAt + this.#options.maximumAgeMs ||
|
|
304
|
+
!live()) {
|
|
305
|
+
refused = true;
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
try {
|
|
312
|
+
const response = await this.#options.transport.deleteExact(entry.input, attempt, entry.authority);
|
|
313
|
+
attemptSettled = true;
|
|
314
|
+
outcome = normalizeTransportResult(response);
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
attemptSettled = true;
|
|
318
|
+
outcome = { kind: "transient" };
|
|
319
|
+
}
|
|
320
|
+
entry.active = false;
|
|
321
|
+
route.active = false;
|
|
322
|
+
this.#inFlight--;
|
|
323
|
+
try {
|
|
324
|
+
if (isTerminal(outcome?.kind)) {
|
|
325
|
+
this.#finish(route, entry, outcome.kind);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (this.#stopped) {
|
|
329
|
+
this.#finish(route, entry, "stopped");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const now = this.#time();
|
|
333
|
+
if (now === null) {
|
|
334
|
+
this.#finish(route, entry, "stopped");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (outcome?.kind === "retry") {
|
|
338
|
+
if (!Number.isFinite(outcome.afterMs) ||
|
|
339
|
+
outcome.afterMs <= 0 ||
|
|
340
|
+
outcome.afterMs > this.#options.maximumRetryAfterMs ||
|
|
341
|
+
outcome.afterMs > Number.MAX_SAFE_INTEGER - now ||
|
|
342
|
+
(outcome.scope !== "route" && outcome.scope !== "global")) {
|
|
343
|
+
this.#finish(route, entry, "failed");
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
route.blockedUntil = Math.max(route.blockedUntil, now + outcome.afterMs);
|
|
347
|
+
if (outcome.scope === "global")
|
|
348
|
+
this.#globalBlockedUntil = Math.max(this.#globalBlockedUntil, route.blockedUntil);
|
|
349
|
+
entry.readyAt = route.blockedUntil;
|
|
350
|
+
}
|
|
351
|
+
else if (outcome?.kind === "transient") {
|
|
352
|
+
entry.readyAt =
|
|
353
|
+
now +
|
|
354
|
+
Math.min(this.#options.maximumBackoffMs, this.#options.transientBackoffMs * 2 ** (entry.attempts - 1));
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
this.#finish(route, entry, "failed");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (entry.attempts >= this.#options.maximumAttempts)
|
|
361
|
+
this.#finish(route, entry, "attempts-exhausted");
|
|
362
|
+
}
|
|
363
|
+
finally {
|
|
364
|
+
this.#pump();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
#finish(route, entry, kind) {
|
|
368
|
+
if (!this.#entries.delete(entry.key))
|
|
369
|
+
return;
|
|
370
|
+
const index = route.entries.indexOf(entry);
|
|
371
|
+
if (index >= 0)
|
|
372
|
+
route.entries.splice(index, 1);
|
|
373
|
+
entry.settle(Object.freeze({
|
|
374
|
+
kind,
|
|
375
|
+
issued: entry.attempts > 0,
|
|
376
|
+
attempts: entry.attempts,
|
|
377
|
+
}));
|
|
378
|
+
entry.settleSdk(Object.freeze({
|
|
379
|
+
outcome: kind,
|
|
380
|
+
issued: entry.attempts > 0,
|
|
381
|
+
attempts: entry.attempts,
|
|
382
|
+
}));
|
|
383
|
+
if (!route.entries.length && route.blockedUntil <= this.#lastTime)
|
|
384
|
+
this.#removeRoute(route);
|
|
385
|
+
this.#revoke(entry);
|
|
386
|
+
}
|
|
387
|
+
#revoke(entry) {
|
|
388
|
+
const authority = entry.authority;
|
|
389
|
+
entry.authority = undefined;
|
|
390
|
+
entry.providedAuthority = undefined;
|
|
391
|
+
try {
|
|
392
|
+
authority?.stop();
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
/* A cleanup port cannot replace the actual result. */
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
#removeRoute(route) {
|
|
399
|
+
if (this.#routes.get(route.key) !== route)
|
|
400
|
+
return;
|
|
401
|
+
this.#routes.delete(route.key);
|
|
402
|
+
const routes = this.#guilds.get(route.guildId);
|
|
403
|
+
routes?.delete(route.key);
|
|
404
|
+
if (!routes?.size)
|
|
405
|
+
this.#guilds.delete(route.guildId);
|
|
406
|
+
}
|
|
407
|
+
#clearTimer() {
|
|
408
|
+
if (this.#timer !== undefined)
|
|
409
|
+
clearTimeout(this.#timer);
|
|
410
|
+
this.#timer = undefined;
|
|
411
|
+
}
|
|
412
|
+
#schedule() {
|
|
413
|
+
this.#clearTimer();
|
|
414
|
+
if (this.#stopped)
|
|
415
|
+
return;
|
|
416
|
+
const now = this.#time();
|
|
417
|
+
if (now === null)
|
|
418
|
+
return;
|
|
419
|
+
let next = Infinity;
|
|
420
|
+
for (const route of this.#routes.values()) {
|
|
421
|
+
if (!route.entries.length && route.blockedUntil > now)
|
|
422
|
+
next = Math.min(next, route.blockedUntil);
|
|
423
|
+
for (const entry of route.entries)
|
|
424
|
+
if (!entry.active)
|
|
425
|
+
next = Math.min(next, entry.acceptedAt + this.#options.maximumAgeMs);
|
|
426
|
+
const first = route.entries[0];
|
|
427
|
+
if (!route.active &&
|
|
428
|
+
first &&
|
|
429
|
+
this.#inFlight < this.#options.concurrency) {
|
|
430
|
+
const ready = Math.max(first.readyAt, route.blockedUntil, this.#globalBlockedUntil);
|
|
431
|
+
if (ready > now)
|
|
432
|
+
next = Math.min(next, ready);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (Number.isFinite(next)) {
|
|
436
|
+
this.#timer = setTimeout(() => {
|
|
437
|
+
this.#timer = undefined;
|
|
438
|
+
this.#pump();
|
|
439
|
+
}, Math.min(2_147_483_647, Math.max(1, Math.ceil(next - now))));
|
|
440
|
+
this.#timer.unref?.();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function isTerminal(value) {
|
|
445
|
+
return (value === "deleted" ||
|
|
446
|
+
value === "already-absent" ||
|
|
447
|
+
value === "missing-permission" ||
|
|
448
|
+
value === "invalid-request" ||
|
|
449
|
+
value === "failed");
|
|
450
|
+
}
|
|
451
|
+
function normalizeTransportResult(value) {
|
|
452
|
+
try {
|
|
453
|
+
const kind = value?.kind;
|
|
454
|
+
if (isTerminal(kind) || kind === "transient")
|
|
455
|
+
return { kind };
|
|
456
|
+
if (kind === "retry")
|
|
457
|
+
return { kind, afterMs: value.afterMs, scope: value.scope };
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
// Never retain raw provider objects, accessors, diagnostics or content.
|
|
461
|
+
}
|
|
462
|
+
return { kind: "failed" };
|
|
463
|
+
}
|
|
464
|
+
function resolved(kind) {
|
|
465
|
+
return Object.freeze({
|
|
466
|
+
result: Promise.resolve(Object.freeze({ kind, issued: false, attempts: 0 })),
|
|
467
|
+
sdkResult: Promise.resolve(Object.freeze({ outcome: kind, issued: false, attempts: 0 })),
|
|
468
|
+
retainedProvidedAuthority: false,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
function submitted(entry, authority) {
|
|
472
|
+
return Object.freeze({
|
|
473
|
+
result: entry.result,
|
|
474
|
+
sdkResult: entry.sdkResult,
|
|
475
|
+
retainedProvidedAuthority: authority !== undefined &&
|
|
476
|
+
entry.authority !== undefined &&
|
|
477
|
+
entry.providedAuthority === authority,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
function captureAuthority(input, authority) {
|
|
481
|
+
try {
|
|
482
|
+
const original = authority.scope;
|
|
483
|
+
const scope = Object.freeze({
|
|
484
|
+
guildId: original.guildId,
|
|
485
|
+
moduleId: original.moduleId,
|
|
486
|
+
channelId: original.channelId,
|
|
487
|
+
messageId: original.messageId,
|
|
488
|
+
admissionToken: original.admissionToken,
|
|
489
|
+
});
|
|
490
|
+
const current = authority.isCurrent;
|
|
491
|
+
const stop = authority.stop;
|
|
492
|
+
if (typeof current !== "function" ||
|
|
493
|
+
typeof stop !== "function" ||
|
|
494
|
+
!Object.keys(input).every((key) => Reflect.get(scope, key) === Reflect.get(input, key)))
|
|
495
|
+
return null;
|
|
496
|
+
return Object.freeze({ scope, isCurrent: current, stop });
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
//# sourceMappingURL=emergency-deletion-dispatcher.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Client } from "discord.js";
|
|
2
|
+
import { Agent } from "undici";
|
|
3
|
+
import type { CachedMessageDeletionAuthority } from "./cached-message-deletion-authority.js";
|
|
4
|
+
import type { EmergencyDeletionAttemptContext, EmergencyDeletionTransport, EmergencyDeletionTransportResult, EmergencyMessageDeletion } from "./emergency-deletion-dispatcher.js";
|
|
5
|
+
interface Options {
|
|
6
|
+
client: Client;
|
|
7
|
+
agent: Agent;
|
|
8
|
+
expectedApplicationId: string;
|
|
9
|
+
maximumAgeMs: number;
|
|
10
|
+
maximumDispatches: number;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Dormant transport. Core owns grants/lifecycle and the standard shared REST manager.
|
|
15
|
+
* Its method/strategy/handler surfaces must remain stable: a custom replacement that
|
|
16
|
+
* ignores dispatcher during REST's internal waits is outside this wire-fence contract.
|
|
17
|
+
*/
|
|
18
|
+
export declare class EmergencyMessageDeletionRestTransport implements EmergencyDeletionTransport {
|
|
19
|
+
#private;
|
|
20
|
+
constructor(options: Options);
|
|
21
|
+
deleteExact(input: EmergencyMessageDeletion, attempt: EmergencyDeletionAttemptContext, grant?: CachedMessageDeletionAuthority): Promise<EmergencyDeletionTransportResult>;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=emergency-deletion-rest-transport.d.ts.map
|