diff --git a/src/app/api/documents/[id]/parsed/route.ts b/src/app/api/documents/[id]/parsed/route.ts index 4fe7ea5..4c38631 100644 --- a/src/app/api/documents/[id]/parsed/route.ts +++ b/src/app/api/documents/[id]/parsed/route.ts @@ -1,3 +1,4 @@ +import { randomUUID } from 'node:crypto'; import { NextRequest, NextResponse } from 'next/server'; import { and, eq, inArray } from 'drizzle-orm'; import { db } from '@/db'; @@ -204,6 +205,7 @@ export async function POST(req: NextRequest, ctx: { params: Promise<{ id: string documentId: id, userId: row.userId, namespace: testNamespace, + forceToken: randomUUID(), }); return NextResponse.json( diff --git a/src/lib/server/compute/types.ts b/src/lib/server/compute/types.ts index 9b86c20..524857b 100644 --- a/src/lib/server/compute/types.ts +++ b/src/lib/server/compute/types.ts @@ -24,6 +24,7 @@ export interface PdfLayoutInput { namespace?: string | null; documentObjectKey?: string; pdfBytes?: ArrayBuffer; + forceToken?: string; onProgress?: (progress: PdfLayoutProgress) => void | Promise; } diff --git a/src/lib/server/compute/worker.ts b/src/lib/server/compute/worker.ts index 733c133..4ec9c79 100644 --- a/src/lib/server/compute/worker.ts +++ b/src/lib/server/compute/worker.ts @@ -166,13 +166,14 @@ function buildWhisperOpKey(input: WhisperAlignInput): string { ].join('|'); } -function buildPdfOpKey(input: PdfLayoutInput): string { +export function buildPdfOpKey(input: PdfLayoutInput): string { return [ 'pdf_layout', 'v1', input.documentId, input.namespace ?? '', input.documentObjectKey ?? '', + input.forceToken?.trim() || '', ].join('|'); } diff --git a/src/lib/server/jobs/parsePdfJob.ts b/src/lib/server/jobs/parsePdfJob.ts index bd4e3d3..87ac52c 100644 --- a/src/lib/server/jobs/parsePdfJob.ts +++ b/src/lib/server/jobs/parsePdfJob.ts @@ -11,6 +11,7 @@ interface ParsePdfJobInput { documentId: string; userId: string; namespace: string | null; + forceToken?: string; } const running = new Set(); @@ -59,6 +60,7 @@ export async function parsePdfJob(input: ParsePdfJobInput): Promise { documentId: input.documentId, namespace: input.namespace, documentObjectKey: documentKey(input.documentId, input.namespace), + forceToken: input.forceToken, onProgress: writeProgress, }); diff --git a/tests/unit/compute-worker-op-key.spec.ts b/tests/unit/compute-worker-op-key.spec.ts new file mode 100644 index 0000000..2101af6 --- /dev/null +++ b/tests/unit/compute-worker-op-key.spec.ts @@ -0,0 +1,29 @@ +import { expect, test } from '@playwright/test'; +import { buildPdfOpKey } from '../../src/lib/server/compute/worker'; + +test.describe('compute worker pdf opKey', () => { + test('keeps stable key when no force token is provided', () => { + const base = { + documentId: 'doc-123', + namespace: 'ns-1', + documentObjectKey: 'docs/ns-1/doc-123', + }; + expect(buildPdfOpKey(base)).toBe('pdf_layout|v1|doc-123|ns-1|docs/ns-1/doc-123|'); + expect(buildPdfOpKey(base)).toBe(buildPdfOpKey(base)); + }); + + test('cache-busts key when force token is provided', () => { + const base = { + documentId: 'doc-123', + namespace: 'ns-1', + documentObjectKey: 'docs/ns-1/doc-123', + }; + const opKeyA = buildPdfOpKey({ ...base, forceToken: 'force-a' }); + const opKeyB = buildPdfOpKey({ ...base, forceToken: 'force-b' }); + const normal = buildPdfOpKey(base); + + expect(opKeyA).not.toBe(opKeyB); + expect(opKeyA).not.toBe(normal); + expect(opKeyB).not.toBe(normal); + }); +});