import type { Context } from "../context.ts"; import type { ExecutionEnv } from "../types.ts"; import { getOrThrow } from "../types.ts"; type MutationQueueState = { queues: Map>; registration: Promise; }; const states = new WeakMap(); function getState(env: ExecutionEnv): MutationQueueState { let state = states.get(env); if (!state) { state = { queues: new Map(), registration: Promise.resolve() }; states.set(env, state); } return state; } async function getMutationQueueKey(env: ExecutionEnv, path: string, context: Context): Promise { const absolutePath = getOrThrow(await env.absolutePath(path, context)); const canonicalPath = await env.canonicalPath(absolutePath, context); if (canonicalPath.ok) return canonicalPath.value; if (canonicalPath.error.code === "not_found" || canonicalPath.error.code === "not_supported") return absolutePath; throw canonicalPath.error; } /** Serialize file mutations targeting the same environment and canonical path. */ export async function withFileMutationQueue( env: ExecutionEnv, path: string, fn: () => Promise, context: Context, ): Promise { const state = getState(env); const registration = state.registration.then(async () => { const key = await getMutationQueueKey(env, path, context); const currentQueue = state.queues.get(key) ?? Promise.resolve(); let releaseNext = () => {}; const nextQueue = new Promise((resolve) => { releaseNext = resolve; }); const chainedQueue = currentQueue.then(() => nextQueue); state.queues.set(key, chainedQueue); return { key, currentQueue, chainedQueue, releaseNext }; }); state.registration = registration.then( () => undefined, () => undefined, ); const { key, currentQueue, chainedQueue, releaseNext } = await registration; await currentQueue; try { return await fn(); } finally { releaseNext(); if (state.queues.get(key) === chainedQueue) state.queues.delete(key); } }