openreader/src/lib/server/documents/mutation-lock.ts
Richard R c0de2156fe refactor(documents): centralize owned document deletion and add mutation locking
Introduce `deleteOwnedDocument` utility to encapsulate all logic for removing a user's ownership of a document, including TTS segment cache cleanup, preview artifact removal, and S3 blob deletion for last-owner cases. Add `withDocumentMutationLock` to serialize concurrent mutations on the same document, using advisory locks in Postgres and a local queue fallback. Refactor all API routes and user data flows to use these utilities, ensuring transactional safety and preventing race conditions during document deletion or transfer. Update tests for new flows and add coverage for document cleanup sequencing and locking behavior.
2026-06-06 18:09:47 -06:00

40 lines
1.1 KiB
TypeScript

import { sql } from 'drizzle-orm';
import { db } from '@/db';
const localTails = new Map<string, Promise<void>>();
async function withLocalDocumentLock<T>(documentId: string, fn: () => Promise<T>): Promise<T> {
const previous = localTails.get(documentId) ?? Promise.resolve();
let release!: () => void;
const current = new Promise<void>((resolve) => {
release = resolve;
});
localTails.set(documentId, current);
await previous;
try {
return await fn();
} finally {
release();
if (localTails.get(documentId) === current) {
localTails.delete(documentId);
}
}
}
export async function withDocumentMutationLock<T>(
documentId: string,
fn: () => Promise<T>,
): Promise<T> {
return withLocalDocumentLock(documentId, async () => {
if (!process.env.POSTGRES_URL) {
return fn();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (db as any).transaction(async (tx: any) => {
await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtextextended(${documentId}, 0))`);
return fn();
});
});
}