@firesystem/core
Version:
Virtual File System core interfaces and types
452 lines (448 loc) • 12.8 kB
JavaScript
// src/types/FileSystemEvents.ts
var FileSystemEvents = {
// Initialization events
INITIALIZING: "fs:initializing",
INITIALIZED: "fs:initialized",
INIT_PROGRESS: "fs:init:progress",
INIT_ERROR: "fs:init:error",
// Operation lifecycle events
OPERATION_START: "fs:operation:start",
OPERATION_END: "fs:operation:end",
OPERATION_ERROR: "fs:operation:error",
// File events (complement to watch)
FILE_READING: "fs:file:reading",
FILE_READ: "fs:file:read",
FILE_WRITING: "fs:file:writing",
FILE_WRITTEN: "fs:file:written",
FILE_DELETING: "fs:file:deleting",
FILE_DELETED: "fs:file:deleted",
// Directory events
DIR_READING: "fs:dir:reading",
DIR_READ: "fs:dir:read",
DIR_CREATING: "fs:dir:creating",
DIR_CREATED: "fs:dir:created",
DIR_DELETING: "fs:dir:deleting",
DIR_DELETED: "fs:dir:deleted",
// Batch operations
BATCH_START: "fs:batch:start",
BATCH_PROGRESS: "fs:batch:progress",
BATCH_END: "fs:batch:end",
// Storage events
STORAGE_CLEARING: "fs:storage:clearing",
STORAGE_CLEARED: "fs:storage:cleared",
STORAGE_SIZE_CALCULATED: "fs:storage:size"
};
// src/utils/paths.ts
function normalizePath(path) {
if (!path) return "/";
if (!path.startsWith("/")) {
path = "/" + path;
}
path = path.replace(/\/+/g, "/");
if (path.length > 1 && path.endsWith("/")) {
path = path.slice(0, -1);
}
return path;
}
function dirname(path) {
const normalized = normalizePath(path);
const lastSlash = normalized.lastIndexOf("/");
return lastSlash === 0 ? "/" : normalized.slice(0, lastSlash);
}
function basename(path) {
const normalized = normalizePath(path);
const lastSlash = normalized.lastIndexOf("/");
return normalized.slice(lastSlash + 1);
}
function join(...segments) {
const joined = segments.join("/");
return normalizePath(joined);
}
function isAbsolute(path) {
return path.startsWith("/");
}
function extname(path) {
const base = basename(path);
const lastDot = base.lastIndexOf(".");
return lastDot === -1 ? "" : base.slice(lastDot);
}
// src/utils/EventEmitter.ts
var EventEmitter = class {
constructor() {
this.events = /* @__PURE__ */ new Map();
}
on(event, handler) {
if (!this.events.has(event)) {
this.events.set(event, /* @__PURE__ */ new Set());
}
this.events.get(event).add(handler);
return {
dispose: () => this.off(event, handler)
};
}
once(event, handler) {
const wrappedHandler = (...args) => {
handler(...args);
this.off(event, wrappedHandler);
};
return this.on(event, wrappedHandler);
}
emit(event, ...args) {
const handlers = this.events.get(event);
if (!handlers || handlers.size === 0) {
return false;
}
handlers.forEach((handler) => {
try {
handler(...args);
} catch (error) {
console.error(`Error in event handler for ${String(event)}:`, error);
}
});
return true;
}
off(event, handler) {
if (!handler) {
this.events.delete(event);
return;
}
const handlers = this.events.get(event);
if (handlers) {
handlers.delete(handler);
if (handlers.size === 0) {
this.events.delete(event);
}
}
}
removeAllListeners(event) {
if (event) {
this.events.delete(event);
} else {
this.events.clear();
}
}
};
var TypedEventEmitter = class {
constructor() {
this.emitter = new EventEmitter();
}
on(event, handler) {
return this.emitter.on(event, handler);
}
once(event, handler) {
return this.emitter.once(event, handler);
}
emit(event, payload) {
return this.emitter.emit(event, payload);
}
off(event, handler) {
this.emitter.off(event, handler);
}
removeAllListeners(event) {
this.emitter.removeAllListeners(event);
}
};
// src/mixins/withEvents.ts
function withEvents(Base) {
return class extends Base {
constructor() {
super(...arguments);
this.events = new TypedEventEmitter();
}
// Override methods to emit events
async readFile(path) {
const startTime = Date.now();
const operationId = `read-${path}-${startTime}`;
this.events.emit(FileSystemEvents.OPERATION_START, {
operation: "readFile",
path,
id: operationId
});
this.events.emit(FileSystemEvents.FILE_READING, { path });
try {
const result = await super.readFile(path);
this.events.emit(FileSystemEvents.FILE_READ, {
path,
size: result.size || 0
});
this.events.emit(FileSystemEvents.OPERATION_END, {
operation: "readFile",
path,
id: operationId,
duration: Date.now() - startTime
});
return result;
} catch (error) {
this.events.emit(FileSystemEvents.OPERATION_ERROR, {
operation: "readFile",
path,
error
});
throw error;
}
}
async writeFile(path, content, metadata) {
const startTime = Date.now();
const operationId = `write-${path}-${startTime}`;
const size = typeof content === "string" ? content.length : JSON.stringify(content).length;
this.events.emit(FileSystemEvents.OPERATION_START, {
operation: "writeFile",
path,
id: operationId
});
this.events.emit(FileSystemEvents.FILE_WRITING, { path, size });
try {
const result = await super.writeFile(path, content, metadata);
this.events.emit(FileSystemEvents.FILE_WRITTEN, { path, size });
this.events.emit(FileSystemEvents.OPERATION_END, {
operation: "writeFile",
path,
id: operationId,
duration: Date.now() - startTime
});
return result;
} catch (error) {
this.events.emit(FileSystemEvents.OPERATION_ERROR, {
operation: "writeFile",
path,
error
});
throw error;
}
}
async deleteFile(path) {
const startTime = Date.now();
const operationId = `delete-${path}-${startTime}`;
this.events.emit(FileSystemEvents.OPERATION_START, {
operation: "deleteFile",
path,
id: operationId
});
this.events.emit(FileSystemEvents.FILE_DELETING, { path });
try {
await super.deleteFile(path);
this.events.emit(FileSystemEvents.FILE_DELETED, { path });
this.events.emit(FileSystemEvents.OPERATION_END, {
operation: "deleteFile",
path,
id: operationId,
duration: Date.now() - startTime
});
} catch (error) {
this.events.emit(FileSystemEvents.OPERATION_ERROR, {
operation: "deleteFile",
path,
error
});
throw error;
}
}
async readDir(path) {
const startTime = Date.now();
const operationId = `readDir-${path}-${startTime}`;
this.events.emit(FileSystemEvents.OPERATION_START, {
operation: "readDir",
path,
id: operationId
});
this.events.emit(FileSystemEvents.DIR_READING, { path });
try {
const result = await super.readDir(path);
this.events.emit(FileSystemEvents.DIR_READ, {
path,
count: result.length
});
this.events.emit(FileSystemEvents.OPERATION_END, {
operation: "readDir",
path,
id: operationId,
duration: Date.now() - startTime
});
return result;
} catch (error) {
this.events.emit(FileSystemEvents.OPERATION_ERROR, {
operation: "readDir",
path,
error
});
throw error;
}
}
async mkdir(path, recursive = false) {
const startTime = Date.now();
const operationId = `mkdir-${path}-${startTime}`;
this.events.emit(FileSystemEvents.OPERATION_START, {
operation: "mkdir",
path,
id: operationId
});
this.events.emit(FileSystemEvents.DIR_CREATING, { path, recursive });
try {
const result = await super.mkdir(path, recursive);
this.events.emit(FileSystemEvents.DIR_CREATED, { path });
this.events.emit(FileSystemEvents.OPERATION_END, {
operation: "mkdir",
path,
id: operationId,
duration: Date.now() - startTime
});
return result;
} catch (error) {
this.events.emit(FileSystemEvents.OPERATION_ERROR, {
operation: "mkdir",
path,
error
});
throw error;
}
}
async rmdir(path, recursive = false) {
const startTime = Date.now();
const operationId = `rmdir-${path}-${startTime}`;
this.events.emit(FileSystemEvents.OPERATION_START, {
operation: "rmdir",
path,
id: operationId
});
this.events.emit(FileSystemEvents.DIR_DELETING, { path, recursive });
try {
await super.rmdir(path, recursive);
this.events.emit(FileSystemEvents.DIR_DELETED, { path });
this.events.emit(FileSystemEvents.OPERATION_END, {
operation: "rmdir",
path,
id: operationId,
duration: Date.now() - startTime
});
} catch (error) {
this.events.emit(FileSystemEvents.OPERATION_ERROR, {
operation: "rmdir",
path,
error
});
throw error;
}
}
async clear() {
const startTime = Date.now();
const operationId = `clear-${startTime}`;
this.events.emit(FileSystemEvents.OPERATION_START, {
operation: "clear",
id: operationId
});
this.events.emit(FileSystemEvents.STORAGE_CLEARING, void 0);
try {
await super.clear();
const duration = Date.now() - startTime;
this.events.emit(FileSystemEvents.STORAGE_CLEARED, { duration });
this.events.emit(FileSystemEvents.OPERATION_END, {
operation: "clear",
id: operationId,
duration
});
} catch (error) {
this.events.emit(FileSystemEvents.OPERATION_ERROR, {
operation: "clear",
error
});
throw error;
}
}
async size() {
const startTime = Date.now();
const operationId = `size-${startTime}`;
this.events.emit(FileSystemEvents.OPERATION_START, {
operation: "size",
id: operationId
});
try {
const size = await super.size();
this.events.emit(FileSystemEvents.STORAGE_SIZE_CALCULATED, { size });
this.events.emit(FileSystemEvents.OPERATION_END, {
operation: "size",
id: operationId,
duration: Date.now() - startTime
});
return size;
} catch (error) {
this.events.emit(FileSystemEvents.OPERATION_ERROR, {
operation: "size",
error
});
throw error;
}
}
};
}
// src/base/BaseFileSystem.ts
var BaseFileSystem = class {
// Implementações padrão dos novos métodos
/**
* Por padrão, permite modificação se o arquivo existe ou se pode ser criado
*/
async canModify(path) {
try {
const stat = await this.stat(path);
return !stat.readonly;
} catch {
const parentPath = this.getParentPath(path);
if (parentPath === "/") {
return true;
}
return this.canCreateIn(parentPath);
}
}
/**
* Por padrão, permite criar se o diretório existe e não é readonly
*/
async canCreateIn(parentPath) {
try {
const stat = await this.stat(parentPath);
return stat.type === "directory" && !stat.readonly;
} catch {
return false;
}
}
/**
* Implementação padrão de escrita atômica usando temp + rename
*/
async writeFileAtomic(path, content, metadata) {
const normalizedPath = normalizePath(path);
const tempPath = `${normalizedPath}.tmp${Date.now()}`;
try {
await this.writeFile(tempPath, content, metadata);
if (await this.exists(normalizedPath)) {
await this.deleteFile(normalizedPath);
}
return await this.rename(tempPath, normalizedPath);
} catch (error) {
try {
await this.deleteFile(tempPath);
} catch {
}
throw error;
}
}
/**
* Helper para extrair diretório pai
*/
getParentPath(path) {
const normalized = normalizePath(path);
const lastSlash = normalized.lastIndexOf("/");
return lastSlash <= 0 ? "/" : normalized.substring(0, lastSlash);
}
};
export {
BaseFileSystem,
EventEmitter,
FileSystemEvents,
TypedEventEmitter,
basename,
dirname,
extname,
isAbsolute,
join,
normalizePath,
withEvents
};
//# sourceMappingURL=index.js.map