Skip to content
Source
Sign inSign up

vcsRefInvalidation.ts

81 lines2.8 KB

1import type { EnvironmentId } from "@t3tools/contracts";
2import * as Effect from "effect/Effect";
3import * as PartitionedSemaphore from "effect/PartitionedSemaphore";
4import { Atom, type AtomRegistry } from "effect/reactivity";
5​
6import { safeErrorLogAttributes } from "../errors/safeLog.ts";
7import * as Persistence from "../platform/persistence.ts";
8​
9export interface VcsRefsInvalidationTarget {
10 readonly environmentId: EnvironmentId;
12​
13export interface CachedVcsRefsInvalidationTarget extends VcsRefsInvalidationTarget {
14 readonly cwd: string;
16​
17export interface VcsRefsCacheState {
18 readonly revision: number;
19 readonly persistedCacheReadable: boolean;
21​
22const stateByEnvironment = Atom.family((environmentId: EnvironmentId) =>
23 Atom.make<VcsRefsCacheState>({
24 revision: 0,
25 persistedCacheReadable: true,
26 }).pipe(Atom.keepAlive, Atom.withLabel(`environment-data:vcs:list-refs-state:${environmentId}`)),
27);
28const persistenceLock = PartitionedSemaphore.makeUnsafe<EnvironmentId>({ permits: 1 });
29​
30export function vcsRefsCacheStateAtom(target: VcsRefsInvalidationTarget) {
31 return stateByEnvironment(target.environmentId);
33​
34export function invalidateVcsRefs(
35 registry: AtomRegistry.AtomRegistry,
36 target: VcsRefsInvalidationTarget,
37 persistedCacheReadable?: boolean,
38): void {
39 registry.update(vcsRefsCacheStateAtom(target), (state) => ({
40 revision: state.revision + 1,
41 persistedCacheReadable: persistedCacheReadable ?? state.persistedCacheReadable,
42 }));
44​
45export function withVcsRefsPersistenceLock<A, E, R>(
46 environmentId: EnvironmentId,
47 effect: Effect.Effect<A, E, R>,
48): Effect.Effect<A, E, R> {
49 return persistenceLock.withPermit(environmentId)(effect);
51​
52/**
53 * Clearing first prevents the restarted stream from rehydrating the invalidated
54 * snapshot. A refresh that wins the lock is cleared afterward; one that loses
55 * observes the new revision and cannot repersist its pre-mutation result.
56 */
57export const invalidateCachedVcsRefs = Effect.fn("VcsRefsState.invalidateCached")(function* (
58 registry: AtomRegistry.AtomRegistry,
59 target: CachedVcsRefsInvalidationTarget,
60) {
61 const cache = yield* Persistence.EnvironmentCacheStore;
62 yield* withVcsRefsPersistenceLock(
63 target.environmentId,
64 Effect.gen(function* () {
65 const persistedCacheReadable = yield* cache.clearVcsRefs(target.environmentId).pipe(
66 Effect.as(true),
67 Effect.catch((error) =>
68 Effect.logWarning("Could not remove invalidated cached Git refs.").pipe(
69 Effect.annotateLogs({
70 environmentId: target.environmentId,
71 cwd: target.cwd,
72 ...safeErrorLogAttributes(error),
73 }),
74 Effect.as(false),
75 ),
76 ),
77 );
78 invalidateVcsRefs(registry, target, persistedCacheReadable);
79 }),
80 );
81});