@minesa-org/mini-interaction 0.2.23 → 0.2.25
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.
|
@@ -37,6 +37,25 @@ export type InteractionHandlerResult = {
|
|
|
37
37
|
body: APIInteractionResponse | {
|
|
38
38
|
error: string;
|
|
39
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Promise that resolves when all background work (like editReply) completes.
|
|
42
|
+
* Pass this to Vercel's waitUntil() to prevent premature termination.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* // In Next.js App Router
|
|
47
|
+
* import { waitUntil } from '@vercel/functions';
|
|
48
|
+
*
|
|
49
|
+
* export async function POST(request: Request) {
|
|
50
|
+
* const result = await client.handleRequest({ ... });
|
|
51
|
+
* if (result.backgroundWork) {
|
|
52
|
+
* waitUntil(result.backgroundWork);
|
|
53
|
+
* }
|
|
54
|
+
* return Response.json(result.body, { status: result.status });
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
backgroundWork?: Promise<void>;
|
|
40
59
|
};
|
|
41
60
|
/** Configuration for interaction timeout handling. */
|
|
42
61
|
export type InteractionTimeoutConfig = {
|
|
@@ -171,6 +190,8 @@ export declare class MiniInteraction {
|
|
|
171
190
|
private loadComponentsPromise;
|
|
172
191
|
private registerCommandsPromise;
|
|
173
192
|
private registerCommandsSignature;
|
|
193
|
+
private vercelWaitUntil;
|
|
194
|
+
private searchedForVercel;
|
|
174
195
|
/**
|
|
175
196
|
* Creates a new MiniInteraction client with optional command auto-loading and custom runtime hooks.
|
|
176
197
|
*/
|
|
@@ -189,6 +210,10 @@ export declare class MiniInteraction {
|
|
|
189
210
|
* Call this periodically to clean up old interaction data.
|
|
190
211
|
*/
|
|
191
212
|
cleanupExpiredInteractions(): number;
|
|
213
|
+
/**
|
|
214
|
+
* Attempt to find a waitUntil implementation in the environment (e.g. Vercel or Cloudflare).
|
|
215
|
+
*/
|
|
216
|
+
private getAutoWaitUntil;
|
|
192
217
|
private normalizeCommandData;
|
|
193
218
|
private registerCommand;
|
|
194
219
|
/**
|
|
@@ -267,7 +292,9 @@ export declare class MiniInteraction {
|
|
|
267
292
|
* Creates a Node.js style request handler compatible with Express, Next.js API routes,
|
|
268
293
|
* Vercel serverless functions, and any runtime that expects a `(req, res)` listener.
|
|
269
294
|
*/
|
|
270
|
-
createNodeHandler(
|
|
295
|
+
createNodeHandler(options?: {
|
|
296
|
+
waitUntil?: (promise: Promise<void>) => void;
|
|
297
|
+
}): InteractionNodeHandler;
|
|
271
298
|
/**
|
|
272
299
|
* Generates a lightweight verification handler that serves an HTML page with an embedded OAuth link.
|
|
273
300
|
*
|
|
@@ -321,7 +348,13 @@ export declare class MiniInteraction {
|
|
|
321
348
|
/**
|
|
322
349
|
* Creates a Fetch API compatible handler for runtimes like Workers or Deno.
|
|
323
350
|
*/
|
|
324
|
-
|
|
351
|
+
/**
|
|
352
|
+
* Generates a Fetch-standard request handler compatible with Cloudflare Workers,
|
|
353
|
+
* Bun, Deno, and Next.js Edge Runtime.
|
|
354
|
+
*/
|
|
355
|
+
createFetchHandler(options?: {
|
|
356
|
+
waitUntil?: (promise: Promise<void>) => void;
|
|
357
|
+
}): InteractionFetchHandler;
|
|
325
358
|
/**
|
|
326
359
|
* Checks if the provided directory path exists on disk.
|
|
327
360
|
*/
|
|
@@ -43,6 +43,8 @@ export class MiniInteraction {
|
|
|
43
43
|
loadComponentsPromise = null;
|
|
44
44
|
registerCommandsPromise = null;
|
|
45
45
|
registerCommandsSignature = null;
|
|
46
|
+
vercelWaitUntil = null;
|
|
47
|
+
searchedForVercel = false;
|
|
46
48
|
/**
|
|
47
49
|
* Creates a new MiniInteraction client with optional command auto-loading and custom runtime hooks.
|
|
48
50
|
*/
|
|
@@ -129,6 +131,33 @@ export class MiniInteraction {
|
|
|
129
131
|
}
|
|
130
132
|
return cleaned;
|
|
131
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Attempt to find a waitUntil implementation in the environment (e.g. Vercel or Cloudflare).
|
|
136
|
+
*/
|
|
137
|
+
async getAutoWaitUntil() {
|
|
138
|
+
if (this.searchedForVercel)
|
|
139
|
+
return this.vercelWaitUntil;
|
|
140
|
+
this.searchedForVercel = true;
|
|
141
|
+
// Try Vercel's @vercel/functions
|
|
142
|
+
try {
|
|
143
|
+
// @ts-ignore - Dynamic import to avoid hard dependency or build errors in non-vercel environments
|
|
144
|
+
const { waitUntil } = await import("@vercel/functions");
|
|
145
|
+
if (typeof waitUntil === "function") {
|
|
146
|
+
this.vercelWaitUntil = waitUntil;
|
|
147
|
+
return waitUntil;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Ignore if not found
|
|
152
|
+
}
|
|
153
|
+
// Try globalThis.waitUntil (some edge runtimes)
|
|
154
|
+
// @ts-ignore
|
|
155
|
+
if (typeof globalThis.waitUntil === "function") {
|
|
156
|
+
// @ts-ignore
|
|
157
|
+
return globalThis.waitUntil;
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
132
161
|
normalizeCommandData(data) {
|
|
133
162
|
if (typeof data === "object" && data !== null) {
|
|
134
163
|
return resolveJSONEncodable(data);
|
|
@@ -471,7 +500,8 @@ export class MiniInteraction {
|
|
|
471
500
|
* Creates a Node.js style request handler compatible with Express, Next.js API routes,
|
|
472
501
|
* Vercel serverless functions, and any runtime that expects a `(req, res)` listener.
|
|
473
502
|
*/
|
|
474
|
-
createNodeHandler() {
|
|
503
|
+
createNodeHandler(options) {
|
|
504
|
+
const { waitUntil } = options ?? {};
|
|
475
505
|
return (request, response) => {
|
|
476
506
|
if (request.method !== "POST") {
|
|
477
507
|
response.statusCode = 405;
|
|
@@ -508,6 +538,12 @@ export class MiniInteraction {
|
|
|
508
538
|
signature,
|
|
509
539
|
timestamp,
|
|
510
540
|
});
|
|
541
|
+
if (result.backgroundWork) {
|
|
542
|
+
const resolvedWaitUntil = waitUntil ?? await this.getAutoWaitUntil();
|
|
543
|
+
if (resolvedWaitUntil) {
|
|
544
|
+
resolvedWaitUntil(result.backgroundWork);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
511
547
|
response.statusCode = result.status;
|
|
512
548
|
response.setHeader("content-type", "application/json");
|
|
513
549
|
response.end(JSON.stringify(result.body));
|
|
@@ -756,7 +792,12 @@ export class MiniInteraction {
|
|
|
756
792
|
/**
|
|
757
793
|
* Creates a Fetch API compatible handler for runtimes like Workers or Deno.
|
|
758
794
|
*/
|
|
759
|
-
|
|
795
|
+
/**
|
|
796
|
+
* Generates a Fetch-standard request handler compatible with Cloudflare Workers,
|
|
797
|
+
* Bun, Deno, and Next.js Edge Runtime.
|
|
798
|
+
*/
|
|
799
|
+
createFetchHandler(options) {
|
|
800
|
+
const { waitUntil } = options ?? {};
|
|
760
801
|
return async (request) => {
|
|
761
802
|
if (request.method !== "POST") {
|
|
762
803
|
return new Response(JSON.stringify({
|
|
@@ -776,6 +817,12 @@ export class MiniInteraction {
|
|
|
776
817
|
signature,
|
|
777
818
|
timestamp,
|
|
778
819
|
});
|
|
820
|
+
if (result.backgroundWork) {
|
|
821
|
+
const resolvedWaitUntil = waitUntil ?? await this.getAutoWaitUntil();
|
|
822
|
+
if (resolvedWaitUntil) {
|
|
823
|
+
resolvedWaitUntil(result.backgroundWork);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
779
826
|
return new Response(JSON.stringify(result.body), {
|
|
780
827
|
status: result.status,
|
|
781
828
|
headers: { "content-type": "application/json" },
|
|
@@ -1142,10 +1189,11 @@ export class MiniInteraction {
|
|
|
1142
1189
|
}
|
|
1143
1190
|
return resolvedResponse;
|
|
1144
1191
|
}, this.timeoutConfig.initialResponseTimeout, `Component "${customId}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
|
|
1145
|
-
const resolvedResponse = await timeoutWrapper();
|
|
1192
|
+
const { response: resolvedResponse, backgroundWork } = await timeoutWrapper();
|
|
1146
1193
|
return {
|
|
1147
1194
|
status: 200,
|
|
1148
1195
|
body: resolvedResponse,
|
|
1196
|
+
backgroundWork,
|
|
1149
1197
|
};
|
|
1150
1198
|
}
|
|
1151
1199
|
catch (error) {
|
|
@@ -1208,10 +1256,11 @@ export class MiniInteraction {
|
|
|
1208
1256
|
}
|
|
1209
1257
|
return resolvedResponse;
|
|
1210
1258
|
}, this.timeoutConfig.initialResponseTimeout, `Modal "${customId}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
|
|
1211
|
-
const resolvedResponse = await timeoutWrapper();
|
|
1259
|
+
const { response: resolvedResponse, backgroundWork } = await timeoutWrapper();
|
|
1212
1260
|
return {
|
|
1213
1261
|
status: 200,
|
|
1214
1262
|
body: resolvedResponse,
|
|
1263
|
+
backgroundWork,
|
|
1215
1264
|
};
|
|
1216
1265
|
}
|
|
1217
1266
|
catch (error) {
|
|
@@ -1324,7 +1373,7 @@ export class MiniInteraction {
|
|
|
1324
1373
|
}
|
|
1325
1374
|
return resolvedResponse;
|
|
1326
1375
|
}, this.timeoutConfig.initialResponseTimeout, `Command "${commandName}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
|
|
1327
|
-
const finalResponse = await timeoutWrapper();
|
|
1376
|
+
const { response: finalResponse, backgroundWork } = await timeoutWrapper();
|
|
1328
1377
|
if (this.timeoutConfig.enableResponseDebugLogging) {
|
|
1329
1378
|
console.log(`[MiniInteraction] handleApplicationCommand: initial response determined (type=${finalResponse?.type})`);
|
|
1330
1379
|
}
|
|
@@ -1344,6 +1393,7 @@ export class MiniInteraction {
|
|
|
1344
1393
|
return {
|
|
1345
1394
|
status: 200,
|
|
1346
1395
|
body: finalResponse,
|
|
1396
|
+
backgroundWork,
|
|
1347
1397
|
};
|
|
1348
1398
|
}
|
|
1349
1399
|
catch (error) {
|
|
@@ -1607,12 +1657,16 @@ function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings =
|
|
|
1607
1657
|
});
|
|
1608
1658
|
// Start handler execution immediately (don't await yet)
|
|
1609
1659
|
const handlerPromise = Promise.resolve(handler(...args));
|
|
1660
|
+
// Attach a default error handler to prevent unhandled rejections
|
|
1661
|
+
const backgroundWork = handlerPromise.catch((error) => {
|
|
1662
|
+
console.error(`[MiniInteraction] ${handlerName} background execution failed:`, error instanceof Error ? error.message : String(error));
|
|
1663
|
+
}).then(() => {
|
|
1664
|
+
// Ensure it always resolves to void
|
|
1665
|
+
});
|
|
1610
1666
|
// If we have an ackPromise, race between ACK and timeout
|
|
1611
|
-
// When ACK is received, return IMMEDIATELY so Discord gets the response
|
|
1612
|
-
// Handler continues in background
|
|
1613
1667
|
if (ackPromise) {
|
|
1614
1668
|
try {
|
|
1615
|
-
const
|
|
1669
|
+
const response = await Promise.race([
|
|
1616
1670
|
ackPromise,
|
|
1617
1671
|
timeoutPromise,
|
|
1618
1672
|
]);
|
|
@@ -1620,11 +1674,7 @@ function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings =
|
|
|
1620
1674
|
if (timeoutId) {
|
|
1621
1675
|
clearTimeout(timeoutId);
|
|
1622
1676
|
}
|
|
1623
|
-
|
|
1624
|
-
handlerPromise.catch((error) => {
|
|
1625
|
-
console.error(`[MiniInteraction] ${handlerName} background execution failed:`, error instanceof Error ? error.message : String(error));
|
|
1626
|
-
});
|
|
1627
|
-
return ackResult;
|
|
1677
|
+
return { response, backgroundWork };
|
|
1628
1678
|
}
|
|
1629
1679
|
catch (error) {
|
|
1630
1680
|
// Timeout occurred before ACK - fall through to check handler
|
|
@@ -1636,7 +1686,7 @@ function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings =
|
|
|
1636
1686
|
}
|
|
1637
1687
|
// No ACK promise - wait for handler with timeout
|
|
1638
1688
|
try {
|
|
1639
|
-
const
|
|
1689
|
+
const response = await Promise.race([
|
|
1640
1690
|
handlerPromise,
|
|
1641
1691
|
timeoutPromise,
|
|
1642
1692
|
]);
|
|
@@ -1647,7 +1697,7 @@ function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings =
|
|
|
1647
1697
|
if (enableWarnings && elapsed > timeoutMs * 0.8) {
|
|
1648
1698
|
console.warn(`[MiniInteraction] ${handlerName} completed in ${elapsed}ms (${Math.round((elapsed / timeoutMs) * 100)}% of timeout limit)`);
|
|
1649
1699
|
}
|
|
1650
|
-
return
|
|
1700
|
+
return { response, backgroundWork };
|
|
1651
1701
|
}
|
|
1652
1702
|
catch (error) {
|
|
1653
1703
|
if (timeoutId) {
|
package/package.json
CHANGED