feat: add worker threads, persistent Python sidecar, graceful shutdown, and architectural improvements

- Graceful shutdown: SIGTERM/SIGINT handlers drain HTTP, stop workers, close DB
- Thumbnail caching: disk-cached thumbnails with immutable Cache-Control headers
- Worker thread pool: Piscina offloads Sharp processing off the main event loop
- Persistent Python dispatcher: pre-imports ML libraries, eliminates cold-start latency
- Tool page registry: declarative tool-to-component mapping replaces 750-line switch
- File store cleanup: remove dead derived fields, stable files array reference
- Job persistence: progress written to SQLite jobs table, stale jobs recovered on startup
This commit is contained in:
Siddharth Kumar Sah
2026-03-29 17:23:41 +08:00
parent 88729e255d
commit 1cbdfa1590
20 changed files with 1575 additions and 530 deletions
+15 -9
View File
@@ -416,25 +416,31 @@ describe("FileStore", () => {
expect(useFileStore.getState().processedSize).toBe(500);
});
it("hasFiles returns true when entries exist", () => {
expect(useFileStore.getState().hasFiles).toBe(false);
it("entries.length reflects file count", () => {
expect(useFileStore.getState().entries.length).toBe(0);
useFileStore.getState().setFiles([makeFile("a.png")]);
expect(useFileStore.getState().hasFiles).toBe(true);
expect(useFileStore.getState().entries.length).toBe(1);
});
it("allProcessed returns true when all entries are completed", () => {
it("all entries completed can be derived from entries", () => {
useFileStore.getState().setFiles([makeFile("a.png"), makeFile("b.png")]);
expect(useFileStore.getState().allProcessed).toBe(false);
const allDone = () =>
useFileStore.getState().entries.length > 0 &&
useFileStore.getState().entries.every((e) => e.status === "completed");
expect(allDone()).toBe(false);
useFileStore.getState().updateEntry(0, { status: "completed" });
expect(useFileStore.getState().allProcessed).toBe(false);
expect(allDone()).toBe(false);
useFileStore.getState().updateEntry(1, { status: "completed" });
expect(useFileStore.getState().allProcessed).toBe(true);
expect(allDone()).toBe(true);
});
it("allProcessed returns false when no entries", () => {
expect(useFileStore.getState().allProcessed).toBe(false);
it("empty entries means nothing is processed", () => {
const allDone =
useFileStore.getState().entries.length > 0 &&
useFileStore.getState().entries.every((e) => e.status === "completed");
expect(allDone).toBe(false);
});
// -- setProcessedUrl (backward compat, updates current entry) -------------