feat(rust-provider): Add Rust-backed provider with XFS-safe durability via IPC bridge, TypeScript provider, tests and docs

This commit is contained in:
2026-03-05 19:36:11 +00:00
parent 61e3f3a0b6
commit 5283247bea
24 changed files with 14453 additions and 1248 deletions

View File

@@ -0,0 +1,270 @@
/**
* Tests for Rust provider
*/
import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import { tap, expect } from '@push.rocks/tapbundle';
import { SmartFs, SmartFsProviderRust } from '../ts/index.js';
// Create temp directory for tests
const tempDir = path.join(process.cwd(), '.nogit', 'test-temp-rust');
// Create test instance
const rustProvider = new SmartFsProviderRust();
const smartFs = new SmartFs(rustProvider);
tap.preTask('setup temp directory', async () => {
await fs.rm(tempDir, { recursive: true, force: true });
await fs.mkdir(tempDir, { recursive: true });
});
tap.test('should create SmartFS instance with Rust provider', async () => {
expect(smartFs).toBeInstanceOf(SmartFs);
expect(smartFs.getProviderName()).toEqual('rust');
});
tap.test('should write and read a file', async () => {
const filePath = path.join(tempDir, 'test.txt');
await smartFs.file(filePath).write('Hello, World!');
const content = await smartFs.file(filePath).encoding('utf8').read();
expect(content).toEqual('Hello, World!');
});
tap.test('should write atomically', async () => {
const filePath = path.join(tempDir, 'atomic.txt');
await smartFs.file(filePath).atomic().write('Atomic write test');
const content = await smartFs.file(filePath).encoding('utf8').read();
expect(content).toEqual('Atomic write test');
});
tap.test('should check if file exists', async () => {
const filePath = path.join(tempDir, 'exists-test.txt');
await smartFs.file(filePath).write('exists');
const exists = await smartFs.file(filePath).exists();
expect(exists).toEqual(true);
const notExists = await smartFs.file(path.join(tempDir, 'nonexistent.txt')).exists();
expect(notExists).toEqual(false);
});
tap.test('should get file stats', async () => {
const filePath = path.join(tempDir, 'stats-test.txt');
await smartFs.file(filePath).write('stats test');
const stats = await smartFs.file(filePath).stat();
expect(stats).toHaveProperty('size');
expect(stats).toHaveProperty('mtime');
expect(stats).toHaveProperty('birthtime');
expect(stats.isFile).toEqual(true);
expect(stats.isDirectory).toEqual(false);
});
tap.test('should delete a file', async () => {
const filePath = path.join(tempDir, 'delete-test.txt');
await smartFs.file(filePath).write('to delete');
await smartFs.file(filePath).delete();
const exists = await smartFs.file(filePath).exists();
expect(exists).toEqual(false);
});
tap.test('should copy a file', async () => {
const srcPath = path.join(tempDir, 'copy-src.txt');
const destPath = path.join(tempDir, 'copy-dest.txt');
await smartFs.file(srcPath).write('copy me');
await smartFs.file(srcPath).copy(destPath);
const content = await smartFs.file(destPath).encoding('utf8').read();
expect(content).toEqual('copy me');
});
tap.test('should move a file', async () => {
const srcPath = path.join(tempDir, 'move-src.txt');
const destPath = path.join(tempDir, 'move-dest.txt');
await smartFs.file(srcPath).write('move me');
await smartFs.file(srcPath).move(destPath);
const exists = await smartFs.file(srcPath).exists();
expect(exists).toEqual(false);
const content = await smartFs.file(destPath).encoding('utf8').read();
expect(content).toEqual('move me');
});
tap.test('should create and list a directory', async () => {
const dirPath = path.join(tempDir, 'list-test');
await smartFs.directory(dirPath).create();
await smartFs.file(path.join(dirPath, 'a.txt')).write('a');
await smartFs.file(path.join(dirPath, 'b.txt')).write('b');
const entries = await smartFs.directory(dirPath).list();
expect(entries.length).toEqual(2);
const names = entries.map(e => e.name).sort();
expect(names).toEqual(['a.txt', 'b.txt']);
});
tap.test('should check directory exists', async () => {
const dirPath = path.join(tempDir, 'exists-dir');
await smartFs.directory(dirPath).create();
const exists = await smartFs.directory(dirPath).exists();
expect(exists).toEqual(true);
const notExists = await smartFs.directory(path.join(tempDir, 'nonexistent-dir')).exists();
expect(notExists).toEqual(false);
});
tap.test('should delete directory recursively', async () => {
const dirPath = path.join(tempDir, 'delete-dir');
await smartFs.directory(dirPath).create();
await smartFs.file(path.join(dirPath, 'file.txt')).write('data');
await smartFs.directory(dirPath).delete();
const exists = await smartFs.directory(dirPath).exists();
expect(exists).toEqual(false);
});
tap.test('should list directory recursively', async () => {
const dirPath = path.join(tempDir, 'recursive-test');
await smartFs.directory(dirPath).create();
await smartFs.directory(path.join(dirPath, 'sub')).create();
await smartFs.file(path.join(dirPath, 'root.txt')).write('root');
await smartFs.file(path.join(dirPath, 'sub', 'child.txt')).write('child');
const entries = await smartFs.directory(dirPath).recursive().list();
const names = entries.map(e => e.name).sort();
expect(names).toContain('root.txt');
expect(names).toContain('child.txt');
expect(names).toContain('sub');
});
// ── Append file ──────────────────────────────────────────────────────────────
tap.test('should append to a file', async () => {
const filePath = path.join(tempDir, 'append-test.txt');
await smartFs.file(filePath).write('Hello');
await smartFs.file(filePath).append(' World!');
const content = await smartFs.file(filePath).encoding('utf8').read();
expect(content).toEqual('Hello World!');
});
// ── Binary Buffer round-trip ─────────────────────────────────────────────────
tap.test('should write and read binary data (Buffer)', async () => {
const filePath = path.join(tempDir, 'binary-test.bin');
const binaryData = Buffer.from([0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD, 0x80, 0x7F]);
await rustProvider.writeFile(filePath, binaryData);
const result = await rustProvider.readFile(filePath, { encoding: 'buffer' });
expect(Buffer.isBuffer(result)).toEqual(true);
expect(Buffer.compare(result as Buffer, binaryData)).toEqual(0);
});
// ── Streaming ────────────────────────────────────────────────────────────────
tap.test('should read a file via stream', async () => {
const filePath = path.join(tempDir, 'stream-read.txt');
const testData = 'Stream test data with enough content to verify streaming works correctly';
await smartFs.file(filePath).write(testData);
const readStream = await smartFs.file(filePath).readStream();
const chunks: Uint8Array[] = [];
const reader = readStream.getReader();
let done = false;
while (!done) {
const result = await reader.read();
done = result.done;
if (result.value) chunks.push(result.value);
}
const content = Buffer.concat(chunks.map(c => Buffer.from(c))).toString('utf8');
expect(content).toEqual(testData);
});
tap.test('should write a file via stream', async () => {
const filePath = path.join(tempDir, 'stream-write.txt');
const testData = 'Writing via stream';
const writeStream = await smartFs.file(filePath).writeStream();
const writer = writeStream.getWriter();
await writer.write(new Uint8Array(Buffer.from(testData)));
await writer.close();
const content = await smartFs.file(filePath).encoding('utf8').read();
expect(content).toEqual(testData);
});
// ── Watch ────────────────────────────────────────────────────────────────────
tap.test('should watch for file changes', async () => {
const dirPath = path.join(tempDir, 'watch-test');
await smartFs.directory(dirPath).create();
const filePath = path.join(dirPath, 'watched.txt');
const received: any[] = [];
const watcher = await smartFs
.watch(dirPath)
.onAll((event) => {
received.push(event);
})
.start();
// Give watcher time to start
await new Promise((resolve) => setTimeout(resolve, 300));
await smartFs.file(filePath).write('changed');
// Wait for event to propagate
await new Promise((resolve) => setTimeout(resolve, 500));
await watcher.stop();
expect(received.length).toBeGreaterThan(0);
expect(received[0]).toHaveProperty('type');
expect(received[0]).toHaveProperty('path');
});
// ── Transactions ─────────────────────────────────────────────────────────────
tap.test('should execute a transaction', async () => {
const file1 = path.join(tempDir, 'tx-file1.txt');
const file2 = path.join(tempDir, 'tx-file2.txt');
await smartFs.transaction()
.file(file1).write('tx content 1')
.file(file2).write('tx content 2')
.commit();
const c1 = await smartFs.file(file1).encoding('utf8').read();
const c2 = await smartFs.file(file2).encoding('utf8').read();
expect(c1).toEqual('tx content 1');
expect(c2).toEqual('tx content 2');
});
// ── Directory filter ─────────────────────────────────────────────────────────
tap.test('should filter directory listings with regex', async () => {
const dirPath = path.join(tempDir, 'filter-test');
await smartFs.directory(dirPath).create();
await smartFs.file(path.join(dirPath, 'file1.ts')).write('ts');
await smartFs.file(path.join(dirPath, 'file2.js')).write('js');
await smartFs.file(path.join(dirPath, 'file3.ts')).write('ts');
const entries = await smartFs.directory(dirPath).filter(/\.ts$/).list();
expect(entries.length).toEqual(2);
const allTs = entries.every(e => e.name.endsWith('.ts'));
expect(allTs).toEqual(true);
});
// ── Shutdown ─────────────────────────────────────────────────────────────────
tap.test('should shutdown the Rust provider', async () => {
await rustProvider.shutdown();
await fs.rm(tempDir, { recursive: true, force: true });
});
export default tap.start();