import * as KeyedLock from "@t3tools/shared/KeyedLock"; import * as Effect from "effect/Effect"; import * as Ref from "effect/Ref"; export interface ProviderMaintenanceCommandCoordinatorShape { readonly withCommandLock: (input: { readonly targetKey: string; readonly lockKey: string; readonly onQueued?: Effect.Effect; readonly run: Effect.Effect; }) => Effect.Effect; } export const makeProviderMaintenanceCommandCoordinator = Effect.fn( "makeProviderMaintenanceCommandCoordinator", )(function* (input: { readonly makeAlreadyRunningError: (targetKey: string) => E }) { const runningTargetsRef = yield* Ref.make>(new Set()); const locks = yield* KeyedLock.make(); const acquireTarget = Effect.fn("acquireTarget")(function* (targetKey: string) { return yield* Ref.modify(runningTargetsRef, (runningTargets) => { if (runningTargets.has(targetKey)) { return [false, runningTargets] as const; } const next = new Set(runningTargets); next.add(targetKey); return [true, next] as const; }); }); const releaseTarget = (targetKey: string) => Ref.update(runningTargetsRef, (runningTargets) => { const next = new Set(runningTargets); next.delete(targetKey); return next; }); const withCommandLock: ProviderMaintenanceCommandCoordinatorShape["withCommandLock"] = Effect.fn("providerMaintenanceCommandCoordinator.withCommandLock")(function* ({ targetKey, lockKey, onQueued, run, }) { const acquired = yield* acquireTarget(targetKey); if (!acquired) { return yield* Effect.fail(input.makeAlreadyRunningError(targetKey)); } return yield* (onQueued ?? Effect.void).pipe( Effect.andThen(locks.withLock(lockKey, run)), Effect.ensuring(releaseTarget(targetKey)), ); }); return { withCommandLock, } satisfies ProviderMaintenanceCommandCoordinatorShape; });