mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(documents): make auto-index enqueue never fail a stored upload
The upload route awaited an unguarded enqueueDocumentIndex after the document was already committed. A transient boss.send rejection (a DB hiccup) would then surface as a 500 for an upload that in fact stored the file. Guard the send inside the enqueue (a failure annotates and no-ops, matching the missing-boss path) and drop the await at the call site so the response never blocks on or fails because of background indexing.
This commit is contained in:
1 parent
0b973f4f76
commit
eed705be7b
3 files changed
+32
-11
No files matched your search
@@ -365,10 +365,11 @@ async function postUpload(request: Request): Promise<Response> {
|
||||
|
||||
// Auto-index the freshly stored document for content search: enqueue a
|
||||
// fire-and-forget background job (provider-first, local text-layer fallback).
|
||||
// The upload response never blocks on or fails because of indexing; a missing
|
||||
// boss (worker not up) is a silent no-op. Only fresh inserts enqueue — a
|
||||
// The upload response never blocks on or fails because of indexing — the
|
||||
// enqueue is not awaited and swallows its own errors (a missing boss or a
|
||||
// transient send failure is a silent no-op). Only fresh inserts enqueue — a
|
||||
// duplicate upload returns early above and never reaches here.
|
||||
await enqueueDocumentIndex(user.id, document.id);
|
||||
void enqueueDocumentIndex(user.id, document.id);
|
||||
|
||||
const links = await loadConditionLinks(user.id, [document.id]);
|
||||
return apiSuccess(
|
||||
|
||||
@@ -43,6 +43,15 @@ describe("enqueueDocumentIndex", () => {
|
||||
const result = await enqueueDocumentIndex("user-1", "doc-1");
|
||||
expect(result).toEqual({ enqueued: false });
|
||||
});
|
||||
|
||||
it("swallows a boss.send failure to a no-op (never fails the upload)", async () => {
|
||||
const send = vi.fn().mockRejectedValue(new Error("db down"));
|
||||
vi.mocked(getGlobalBoss).mockReturnValue({ send } as never);
|
||||
// Must resolve, never reject — the upload has already committed.
|
||||
await expect(enqueueDocumentIndex("user-1", "doc-1")).resolves.toEqual({
|
||||
enqueued: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("runDocumentIndex", () => {
|
||||
|
||||
@@ -49,7 +49,10 @@ export async function runDocumentIndex(
|
||||
/**
|
||||
* Enqueue a per-document index job. Coalesced by `singletonKey` per document so
|
||||
* a duplicate/idempotent re-upload cannot pile up parallel runs. Fire-and-
|
||||
* forget — a missing boss (worker not up) is a no-op, never an upload failure.
|
||||
* forget — a missing boss (worker not up) is a no-op, and a `boss.send` failure
|
||||
* (a transient DB hiccup) is swallowed to a no-op too, so enqueue can NEVER fail
|
||||
* an already-stored upload. Indexing is a background nicety; the stored document
|
||||
* is the contract. A dropped enqueue is recoverable via the corpus backfill.
|
||||
*/
|
||||
export async function enqueueDocumentIndex(
|
||||
userId: string,
|
||||
@@ -62,11 +65,19 @@ export async function enqueueDocumentIndex(
|
||||
documentId,
|
||||
enqueuedAt: new Date().toISOString(),
|
||||
};
|
||||
const jobId = await boss.send(DOCUMENT_INDEX_QUEUE, payload, {
|
||||
retryLimit: 2,
|
||||
retryDelay: 30,
|
||||
retryBackoff: true,
|
||||
singletonKey: `document-index|${documentId}`,
|
||||
});
|
||||
return { enqueued: Boolean(jobId) };
|
||||
try {
|
||||
const jobId = await boss.send(DOCUMENT_INDEX_QUEUE, payload, {
|
||||
retryLimit: 2,
|
||||
retryDelay: 30,
|
||||
retryBackoff: true,
|
||||
singletonKey: `document-index|${documentId}`,
|
||||
});
|
||||
return { enqueued: Boolean(jobId) };
|
||||
} catch (err) {
|
||||
annotate({
|
||||
action: { name: "documents.autoIndex.enqueueFailed" },
|
||||
meta: { documentId, reason: err instanceof Error ? err.name : "unknown" },
|
||||
});
|
||||
return { enqueued: false };
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user