Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions src/memory/database.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mkdir } from 'node:fs/promises';
import { dirname } from 'node:path';
import { ensureDirectory } from '../utils/ensure-directory.js';
import type {
MemoryChunk,
MemoryKeywordCandidate,
Expand Down Expand Up @@ -117,7 +117,7 @@ export class MemoryDatabase {
private constructor(private readonly db: SqliteDatabase) {}

static async create(path: string): Promise<MemoryDatabase> {
await mkdir(dirname(path), { recursive: true });
await ensureDirectory(dirname(path));
const db = await MemoryDatabase.openSqlite(path);
const memoryDb = new MemoryDatabase(db);
memoryDb.db.exec(CREATE_SCHEMA_SQL);
Expand Down
7 changes: 4 additions & 3 deletions src/memory/store.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
import { readdir, readFile, writeFile } from 'node:fs/promises';
import { dirname, join, normalize, relative } from 'node:path';
import type { MemoryReadOptions, MemoryReadResult, MemorySessionContext } from './types.js';
import { estimateTokens } from '../utils/tokens.js';
import { getDexterDir } from '../utils/paths.js';
import { ensureDirectory } from '../utils/ensure-directory.js';

const MEMORY_DIRNAME = 'memory';
const LONG_TERM_FILE = 'MEMORY.md';
Expand Down Expand Up @@ -36,7 +37,7 @@ export class MemoryStore {
}

async ensureDirectoryExists(): Promise<void> {
await mkdir(this.getMemoryDir(), { recursive: true });
await ensureDirectory(this.getMemoryDir());
}

async readMemoryFile(path: string): Promise<string> {
Expand All @@ -50,7 +51,7 @@ export class MemoryStore {

async writeMemoryFile(path: string, content: string): Promise<void> {
const resolved = this.resolveMemoryPath(path);
await mkdir(dirname(resolved), { recursive: true });
await ensureDirectory(dirname(resolved));
await writeFile(resolved, content, 'utf-8');
}

Expand Down
37 changes: 37 additions & 0 deletions src/utils/ensure-directory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { ensureDirectory } from './ensure-directory.js';

describe('ensureDirectory', () => {
let rootDir: string;

beforeEach(() => {
rootDir = mkdtempSync(join(tmpdir(), 'dexter-ensure-dir-'));
});

afterEach(() => {
rmSync(rootDir, { recursive: true, force: true });
});

test('succeeds when the directory already exists', async () => {
const dir = join(rootDir, 'memory');
mkdirSync(dir, { recursive: true });

await expect(ensureDirectory(dir)).resolves.toBeUndefined();
});

test('creates missing nested directories', async () => {
const dir = join(rootDir, '.dexter', 'memory', 'nested');

await expect(ensureDirectory(dir)).resolves.toBeUndefined();
});

test('throws when path exists as a file', async () => {
const filePath = join(rootDir, 'not-a-dir');
writeFileSync(filePath, 'x');

await expect(ensureDirectory(filePath)).rejects.toThrow();
});
});
24 changes: 24 additions & 0 deletions src/utils/ensure-directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { mkdir, stat } from 'node:fs/promises';

type NodeErrorWithCode = Error & { code?: string };

/**
* Work around Bun-on-Windows mkdir recursive EEXIST behavior by accepting
* EEXIST only when the target already exists as a directory.
*/
export async function ensureDirectory(path: string): Promise<void> {
try {
await mkdir(path, { recursive: true });
return;
} catch (error) {
const err = error as NodeErrorWithCode;
if (err.code !== 'EEXIST') {
throw error;
}
const existing = await stat(path).catch(() => null);
if (existing?.isDirectory()) {
return;
}
throw error;
}
}
Loading