Skip to content

Commit 58c9f4b

Browse files
committed
chore: keepalive
1 parent 5489b9a commit 58c9f4b

File tree

3 files changed

+121
-31
lines changed

3 files changed

+121
-31
lines changed

nuxt.config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ export default defineNuxtConfig({
1919
jwpubKey: process.env.JWPUB_KEY,
2020
},
2121
compatibilityDate: "2026-01-15",
22+
routeRules: {
23+
"/api/outlines": {
24+
// Mark as long-running operation
25+
cache: false,
26+
},
27+
"/api/export": {
28+
// Mark as long-running operation
29+
cache: false,
30+
},
31+
},
2232
piniaPluginPersistedstate: {
2333
storage: "localStorage",
2434
debug: true,

server/api/outlines.post.ts

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,50 @@
1+
import { type KeepAlive, withKeepAlive } from "../utils/keepalive";
2+
13
export default defineEventHandler(async (event) => {
2-
event.node.req.setTimeout(1 * 60 * 60 * 1000);
3-
console.log("Getting outlines...");
4-
const database = await processFileUpload(event, {
5-
maxSize: 256 * 1024 * 1024,
6-
processor: async (fileStream) => {
7-
console.log("Processing file stream...");
8-
return await getJWPUBDatabase(fileStream);
9-
},
10-
});
4+
return withKeepAlive(
5+
event,
6+
async (keepalive: KeepAlive) => {
7+
keepalive.progress("Starting file upload processing...");
118

12-
const outlines = queryDatabase<{ Title: string }>(
13-
database,
14-
"SELECT Title FROM Document",
15-
);
16-
const htmlOutlines = await parseJWPUB(database);
17-
const parsedOutlines = outlines
18-
.map((outline) => {
19-
const [number, ...title] = outline.Title.split(". ");
20-
return {
21-
number: parseInt(number?.trim() ?? "0"),
22-
title: title.join(". "),
23-
};
24-
})
25-
.filter((outline) => outline.number > 0);
9+
const database = await processFileUpload(event, {
10+
maxSize: 256 * 1024 * 1024,
11+
processor: async (fileStream) => {
12+
keepalive.progress("Extracting database from JWPUB file...");
13+
return await getJWPUBDatabase(fileStream);
14+
},
15+
});
16+
17+
keepalive.progress("Database extracted, querying documents...");
18+
const outlines = queryDatabase<{ Title: string }>(
19+
database,
20+
"SELECT Title FROM Document",
21+
);
2622

27-
const match =
28-
htmlOutlines.length === parsedOutlines.length &&
29-
htmlOutlines.every(
30-
(htmlOutline, index) =>
31-
htmlOutline.number === parsedOutlines[index]?.number &&
32-
htmlOutline.title === parsedOutlines[index]?.title,
33-
);
23+
keepalive.progress("Parsing JWPUB content...");
24+
const htmlOutlines = await parseJWPUB(database);
3425

35-
return match ? htmlOutlines : parsedOutlines;
26+
keepalive.progress("Processing outlines...");
27+
const parsedOutlines = outlines
28+
.map((outline) => {
29+
const [number, ...title] = outline.Title.split(". ");
30+
return {
31+
number: parseInt(number?.trim() ?? "0"),
32+
title: title.join(". "),
33+
};
34+
})
35+
.filter((outline) => outline.number > 0);
36+
37+
const match =
38+
htmlOutlines.length === parsedOutlines.length &&
39+
htmlOutlines.every(
40+
(htmlOutline, index) =>
41+
htmlOutline.number === parsedOutlines[index]?.number &&
42+
htmlOutline.title === parsedOutlines[index]?.title,
43+
);
44+
45+
keepalive.progress("Outlines processed successfully");
46+
return match ? htmlOutlines : parsedOutlines;
47+
},
48+
"Processing JWPUB file upload...",
49+
);
3650
});

server/utils/keepalive.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { H3Event } from "h3";
2+
3+
/**
4+
* Creates a keep-alive mechanism to prevent inactivity timeouts during long operations.
5+
* Logs progress messages at regular intervals.
6+
*/
7+
export class KeepAlive {
8+
private interval: NodeJS.Timeout | null = null;
9+
private lastLog = 0;
10+
private startTime = Date.now();
11+
12+
constructor(
13+
private event: H3Event,
14+
private intervalMs = 10000,
15+
) {}
16+
17+
/**
18+
* Log progress update
19+
*/
20+
progress(message: string) {
21+
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
22+
console.log(`[KeepAlive] ${message} (${elapsed}s elapsed)`);
23+
}
24+
25+
/**
26+
* Start sending keep-alive signals
27+
*/
28+
start(message = "Processing...") {
29+
console.log(`[KeepAlive] Started: ${message}`);
30+
this.lastLog = Date.now();
31+
32+
this.interval = setInterval(() => {
33+
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
34+
console.log(`[KeepAlive] Still processing... (${elapsed}s elapsed)`);
35+
}, this.intervalMs);
36+
}
37+
38+
/**
39+
* Stop the keep-alive mechanism
40+
*/
41+
stop() {
42+
if (this.interval) {
43+
clearInterval(this.interval);
44+
this.interval = null;
45+
}
46+
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
47+
console.log(`[KeepAlive] Completed (${elapsed}s total)`);
48+
}
49+
}
50+
51+
/**
52+
* Creates a keep-alive instance for an event handler.
53+
* Automatically starts logging and cleans up when the promise settles.
54+
*/
55+
export function withKeepAlive<T>(
56+
event: H3Event,
57+
operation: (keepalive: KeepAlive) => Promise<T>,
58+
startMessage = "Processing request...",
59+
): Promise<T> {
60+
const keepalive = new KeepAlive(event);
61+
keepalive.start(startMessage);
62+
63+
return operation(keepalive).finally(() => {
64+
keepalive.stop();
65+
});
66+
}

0 commit comments

Comments
 (0)