Open source vector database with HNSW indexing, graph relationships, and metadata facets. Features CLI with professional augmentation registry integration for discovering extensions and capabilities.
304 lines
No EOL
8.9 KiB
JavaScript
304 lines
No EOL
8.9 KiB
JavaScript
/**
|
|
* Universal File System implementation
|
|
* Browser: Uses OPFS (Origin Private File System)
|
|
* Node.js: Uses built-in fs/promises
|
|
* Serverless: Uses memory-based fallback
|
|
*/
|
|
import { isBrowser, isNode } from '../utils/environment.js';
|
|
let nodeFs = null;
|
|
// Dynamic import for Node.js fs (only in Node.js environment)
|
|
if (isNode()) {
|
|
try {
|
|
nodeFs = await import('fs/promises');
|
|
}
|
|
catch {
|
|
// Ignore import errors in non-Node environments
|
|
}
|
|
}
|
|
/**
|
|
* Browser implementation using OPFS
|
|
*/
|
|
class BrowserFS {
|
|
async getRoot() {
|
|
if ('storage' in navigator && 'getDirectory' in navigator.storage) {
|
|
return await navigator.storage.getDirectory();
|
|
}
|
|
throw new Error('OPFS not supported in this browser');
|
|
}
|
|
async getFileHandle(path, create = false) {
|
|
const root = await this.getRoot();
|
|
const parts = path.split('/').filter(p => p);
|
|
let dir = root;
|
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
dir = await dir.getDirectoryHandle(parts[i], { create });
|
|
}
|
|
const fileName = parts[parts.length - 1];
|
|
return await dir.getFileHandle(fileName, { create });
|
|
}
|
|
async getDirHandle(path, create = false) {
|
|
const root = await this.getRoot();
|
|
const parts = path.split('/').filter(p => p);
|
|
let dir = root;
|
|
for (const part of parts) {
|
|
dir = await dir.getDirectoryHandle(part, { create });
|
|
}
|
|
return dir;
|
|
}
|
|
async readFile(path, encoding) {
|
|
try {
|
|
const fileHandle = await this.getFileHandle(path);
|
|
const file = await fileHandle.getFile();
|
|
return await file.text();
|
|
}
|
|
catch (error) {
|
|
throw new Error(`File not found: ${path}`);
|
|
}
|
|
}
|
|
async writeFile(path, data, encoding) {
|
|
const fileHandle = await this.getFileHandle(path, true);
|
|
const writable = await fileHandle.createWritable();
|
|
await writable.write(data);
|
|
await writable.close();
|
|
}
|
|
async mkdir(path, options = { recursive: true }) {
|
|
await this.getDirHandle(path, true);
|
|
}
|
|
async exists(path) {
|
|
try {
|
|
await this.getFileHandle(path);
|
|
return true;
|
|
}
|
|
catch {
|
|
try {
|
|
await this.getDirHandle(path);
|
|
return true;
|
|
}
|
|
catch {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
async readdir(path, options) {
|
|
const dir = await this.getDirHandle(path);
|
|
if (options?.withFileTypes) {
|
|
const entries = [];
|
|
for await (const [name, handle] of dir.entries()) {
|
|
entries.push({
|
|
name,
|
|
isDirectory: () => handle.kind === 'directory',
|
|
isFile: () => handle.kind === 'file'
|
|
});
|
|
}
|
|
return entries;
|
|
}
|
|
else {
|
|
const entries = [];
|
|
for await (const [name] of dir.entries()) {
|
|
entries.push(name);
|
|
}
|
|
return entries;
|
|
}
|
|
}
|
|
async unlink(path) {
|
|
const parts = path.split('/').filter(p => p);
|
|
const fileName = parts.pop();
|
|
const dirPath = parts.join('/');
|
|
if (dirPath) {
|
|
const dir = await this.getDirHandle(dirPath);
|
|
await dir.removeEntry(fileName);
|
|
}
|
|
else {
|
|
const root = await this.getRoot();
|
|
await root.removeEntry(fileName);
|
|
}
|
|
}
|
|
async stat(path) {
|
|
try {
|
|
await this.getFileHandle(path);
|
|
return { isFile: () => true, isDirectory: () => false };
|
|
}
|
|
catch {
|
|
try {
|
|
await this.getDirHandle(path);
|
|
return { isFile: () => false, isDirectory: () => true };
|
|
}
|
|
catch {
|
|
throw new Error(`Path not found: ${path}`);
|
|
}
|
|
}
|
|
}
|
|
async access(path, mode) {
|
|
const exists = await this.exists(path);
|
|
if (!exists) {
|
|
throw new Error(`ENOENT: no such file or directory, access '${path}'`);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Node.js implementation using fs/promises
|
|
*/
|
|
class NodeFS {
|
|
async readFile(path, encoding = 'utf-8') {
|
|
return await nodeFs.readFile(path, encoding);
|
|
}
|
|
async writeFile(path, data, encoding = 'utf-8') {
|
|
await nodeFs.writeFile(path, data, encoding);
|
|
}
|
|
async mkdir(path, options = { recursive: true }) {
|
|
await nodeFs.mkdir(path, options);
|
|
}
|
|
async exists(path) {
|
|
try {
|
|
await nodeFs.access(path);
|
|
return true;
|
|
}
|
|
catch {
|
|
return false;
|
|
}
|
|
}
|
|
async readdir(path, options) {
|
|
if (options?.withFileTypes) {
|
|
return await nodeFs.readdir(path, { withFileTypes: true });
|
|
}
|
|
return await nodeFs.readdir(path);
|
|
}
|
|
async unlink(path) {
|
|
await nodeFs.unlink(path);
|
|
}
|
|
async stat(path) {
|
|
const stats = await nodeFs.stat(path);
|
|
return {
|
|
isFile: () => stats.isFile(),
|
|
isDirectory: () => stats.isDirectory()
|
|
};
|
|
}
|
|
async access(path, mode) {
|
|
await nodeFs.access(path, mode);
|
|
}
|
|
}
|
|
/**
|
|
* Memory-based fallback for serverless/edge environments
|
|
*/
|
|
class MemoryFS {
|
|
constructor() {
|
|
this.files = new Map();
|
|
this.dirs = new Set();
|
|
}
|
|
async readFile(path, encoding) {
|
|
const content = this.files.get(path);
|
|
if (content === undefined) {
|
|
throw new Error(`File not found: ${path}`);
|
|
}
|
|
return content;
|
|
}
|
|
async writeFile(path, data, encoding) {
|
|
this.files.set(path, data);
|
|
// Ensure parent directories exist
|
|
const parts = path.split('/').slice(0, -1);
|
|
for (let i = 1; i <= parts.length; i++) {
|
|
this.dirs.add(parts.slice(0, i).join('/'));
|
|
}
|
|
}
|
|
async mkdir(path, options = { recursive: true }) {
|
|
this.dirs.add(path);
|
|
if (options.recursive) {
|
|
const parts = path.split('/');
|
|
for (let i = 1; i <= parts.length; i++) {
|
|
this.dirs.add(parts.slice(0, i).join('/'));
|
|
}
|
|
}
|
|
}
|
|
async exists(path) {
|
|
return this.files.has(path) || this.dirs.has(path);
|
|
}
|
|
async readdir(path, options) {
|
|
const entries = new Set();
|
|
const pathPrefix = path + '/';
|
|
for (const filePath of this.files.keys()) {
|
|
if (filePath.startsWith(pathPrefix)) {
|
|
const relativePath = filePath.slice(pathPrefix.length);
|
|
const firstSegment = relativePath.split('/')[0];
|
|
entries.add(firstSegment);
|
|
}
|
|
}
|
|
for (const dirPath of this.dirs) {
|
|
if (dirPath.startsWith(pathPrefix)) {
|
|
const relativePath = dirPath.slice(pathPrefix.length);
|
|
const firstSegment = relativePath.split('/')[0];
|
|
if (firstSegment)
|
|
entries.add(firstSegment);
|
|
}
|
|
}
|
|
if (options?.withFileTypes) {
|
|
return Array.from(entries).map(name => ({
|
|
name,
|
|
isDirectory: () => this.dirs.has(path + '/' + name),
|
|
isFile: () => this.files.has(path + '/' + name)
|
|
}));
|
|
}
|
|
return Array.from(entries);
|
|
}
|
|
async unlink(path) {
|
|
this.files.delete(path);
|
|
}
|
|
async stat(path) {
|
|
const isFile = this.files.has(path);
|
|
const isDir = this.dirs.has(path);
|
|
if (!isFile && !isDir) {
|
|
throw new Error(`Path not found: ${path}`);
|
|
}
|
|
return {
|
|
isFile: () => isFile,
|
|
isDirectory: () => isDir
|
|
};
|
|
}
|
|
async access(path, mode) {
|
|
const exists = await this.exists(path);
|
|
if (!exists) {
|
|
throw new Error(`ENOENT: no such file or directory, access '${path}'`);
|
|
}
|
|
}
|
|
}
|
|
// Create the appropriate filesystem implementation
|
|
let fsImpl;
|
|
if (isBrowser()) {
|
|
fsImpl = new BrowserFS();
|
|
}
|
|
else if (isNode() && nodeFs) {
|
|
fsImpl = new NodeFS();
|
|
}
|
|
else {
|
|
fsImpl = new MemoryFS();
|
|
}
|
|
// Export the filesystem operations
|
|
export const readFile = fsImpl.readFile.bind(fsImpl);
|
|
export const writeFile = fsImpl.writeFile.bind(fsImpl);
|
|
export const mkdir = fsImpl.mkdir.bind(fsImpl);
|
|
export const exists = fsImpl.exists.bind(fsImpl);
|
|
export const readdir = fsImpl.readdir.bind(fsImpl);
|
|
export const unlink = fsImpl.unlink.bind(fsImpl);
|
|
export const stat = fsImpl.stat.bind(fsImpl);
|
|
export const access = fsImpl.access.bind(fsImpl);
|
|
// Default export with promises namespace compatibility
|
|
export default {
|
|
readFile,
|
|
writeFile,
|
|
mkdir,
|
|
exists,
|
|
readdir,
|
|
unlink,
|
|
stat,
|
|
access
|
|
};
|
|
// Named export for fs/promises compatibility
|
|
export const promises = {
|
|
readFile,
|
|
writeFile,
|
|
mkdir,
|
|
exists,
|
|
readdir,
|
|
unlink,
|
|
stat,
|
|
access
|
|
};
|
|
//# sourceMappingURL=fs.js.map
|