Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 48 additions & 13 deletions apps/daemon/src/collab/active-workspace-selection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from 'node:fs';
import { randomUUID } from 'node:crypto';
import path from 'node:path';

interface ActiveWorkspaceSelectionFile {
Expand All @@ -10,6 +11,7 @@ export interface ActiveWorkspaceSelectionStore {
snapshot(): { workspaceId: string | null; generation: number };
set(workspaceId: string): Promise<void>;
clear(): Promise<void>;
clearIf(workspaceId: string): Promise<boolean>;
subscribe(listener: (workspaceId: string | null) => void): () => void;
}

Expand Down Expand Up @@ -53,6 +55,7 @@ export function createActiveWorkspaceSelectionStore(
const filePath = path.join(dataDir, 'workspace-selection.json');
let cached: string | null | undefined;
let generation = 0;
let mutationTail = Promise.resolve();
const listeners = new Set<(workspaceId: string | null) => void>();

const read = (): string | null => {
Expand All @@ -79,6 +82,15 @@ export function createActiveWorkspaceSelectionStore(
}
};

const enqueueMutation = <T>(mutation: () => Promise<T>): Promise<T> => {
const result = mutationTail.then(mutation);
mutationTail = result.then(
() => undefined,
() => undefined,
);
return result;
};

return {
get: read,
snapshot() {
Expand All @@ -87,21 +99,44 @@ export function createActiveWorkspaceSelectionStore(
async set(workspaceId: string) {
const next = workspaceId.trim();
if (!next) throw new Error('workspaceId is required');
cached = next;
generation += 1;
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
await fs.promises.writeFile(
filePath,
JSON.stringify({ workspaceId: next }, null, 2),
'utf8',
);
notify(next);
await enqueueMutation(async () => {
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
try {
await fs.promises.writeFile(
tempPath,
JSON.stringify({ workspaceId: next }, null, 2),
'utf8',
);
await fs.promises.rename(tempPath, filePath);
} catch (error) {
await fs.promises.rm(tempPath, { force: true }).catch(() => undefined);
throw error;
}
cached = next;
generation += 1;
notify(next);
});
},
async clear() {
cached = null;
generation += 1;
await fs.promises.rm(filePath, { force: true });
notify(null);
await enqueueMutation(async () => {
await fs.promises.rm(filePath, { force: true });
cached = null;
generation += 1;
notify(null);
});
},
async clearIf(workspaceId: string) {
const expected = workspaceId.trim();
if (!expected) return false;
return enqueueMutation(async () => {
if (read() !== expected) return false;
await fs.promises.rm(filePath, { force: true });
cached = null;
generation += 1;
notify(null);
return true;
});
},
subscribe(listener) {
listeners.add(listener);
Expand Down
Loading
Loading