initial
This commit is contained in:
229
ts/classes/smartfs.watcher.ts
Normal file
229
ts/classes/smartfs.watcher.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* Watcher builder for file system watching
|
||||
*/
|
||||
|
||||
import type { ISmartFsProvider, IWatcherHandle } from '../interfaces/mod.provider.js';
|
||||
import type { IWatchEvent, IWatchOptions, TWatchEventType } from '../interfaces/mod.types.js';
|
||||
|
||||
/**
|
||||
* Event handler type
|
||||
*/
|
||||
type TEventHandler = (event: IWatchEvent) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Active watcher handle that allows stopping the watcher
|
||||
*/
|
||||
export class SmartFsActiveWatcher {
|
||||
constructor(private handle: IWatcherHandle) {}
|
||||
|
||||
/**
|
||||
* Stop watching for file system changes
|
||||
*/
|
||||
public async stop(): Promise<void> {
|
||||
return this.handle.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watcher builder class for file system watching
|
||||
* Configuration methods return `this` for chaining
|
||||
* Call `.start()` to begin watching
|
||||
*/
|
||||
export class SmartFsWatcher {
|
||||
private provider: ISmartFsProvider;
|
||||
private path: string;
|
||||
|
||||
// Configuration options
|
||||
private options: {
|
||||
recursive?: boolean;
|
||||
filter?: string | RegExp | ((path: string) => boolean);
|
||||
debounce?: number;
|
||||
} = {};
|
||||
|
||||
// Event handlers
|
||||
private handlers: {
|
||||
change?: TEventHandler[];
|
||||
add?: TEventHandler[];
|
||||
delete?: TEventHandler[];
|
||||
all?: TEventHandler[];
|
||||
} = {};
|
||||
|
||||
// Debounce state
|
||||
private debounceTimers: Map<string, NodeJS.Timeout> = new Map();
|
||||
|
||||
constructor(provider: ISmartFsProvider, path: string) {
|
||||
this.provider = provider;
|
||||
this.path = this.provider.normalizePath(path);
|
||||
|
||||
if (!this.provider.capabilities.supportsWatch) {
|
||||
throw new Error(`Provider '${this.provider.name}' does not support file watching`);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Configuration Methods (return this for chaining) ---
|
||||
|
||||
/**
|
||||
* Enable recursive watching (watch subdirectories)
|
||||
*/
|
||||
public recursive(): this {
|
||||
this.options.recursive = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter watched paths
|
||||
* @param filter - String pattern, RegExp, or filter function
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // String pattern (glob-like)
|
||||
* .filter('*.ts')
|
||||
*
|
||||
* // RegExp
|
||||
* .filter(/\.ts$/)
|
||||
*
|
||||
* // Function
|
||||
* .filter(path => path.endsWith('.ts'))
|
||||
* ```
|
||||
*/
|
||||
public filter(filter: string | RegExp | ((path: string) => boolean)): this {
|
||||
this.options.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce events (wait N milliseconds before firing)
|
||||
* Useful for avoiding rapid-fire events
|
||||
* @param ms - Debounce delay in milliseconds
|
||||
*/
|
||||
public debounce(ms: number): this {
|
||||
this.options.debounce = ms;
|
||||
return this;
|
||||
}
|
||||
|
||||
// --- Event Handler Registration (return this for chaining) ---
|
||||
|
||||
/**
|
||||
* Register handler for 'change' events (file modified)
|
||||
* @param handler - Event handler function
|
||||
*/
|
||||
public onChange(handler: TEventHandler): this {
|
||||
if (!this.handlers.change) {
|
||||
this.handlers.change = [];
|
||||
}
|
||||
this.handlers.change.push(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register handler for 'add' events (file created)
|
||||
* @param handler - Event handler function
|
||||
*/
|
||||
public onAdd(handler: TEventHandler): this {
|
||||
if (!this.handlers.add) {
|
||||
this.handlers.add = [];
|
||||
}
|
||||
this.handlers.add.push(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register handler for 'delete' events (file deleted)
|
||||
* @param handler - Event handler function
|
||||
*/
|
||||
public onDelete(handler: TEventHandler): this {
|
||||
if (!this.handlers.delete) {
|
||||
this.handlers.delete = [];
|
||||
}
|
||||
this.handlers.delete.push(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register handler for all events
|
||||
* @param handler - Event handler function
|
||||
*/
|
||||
public onAll(handler: TEventHandler): this {
|
||||
if (!this.handlers.all) {
|
||||
this.handlers.all = [];
|
||||
}
|
||||
this.handlers.all.push(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
// --- Action Method ---
|
||||
|
||||
/**
|
||||
* Start watching for file system changes
|
||||
* @returns Active watcher handle that can be stopped
|
||||
*/
|
||||
public async start(): Promise<SmartFsActiveWatcher> {
|
||||
const watchOptions: IWatchOptions = {
|
||||
recursive: this.options.recursive,
|
||||
filter: this.options.filter,
|
||||
debounce: this.options.debounce,
|
||||
};
|
||||
|
||||
// Create the callback that dispatches to handlers
|
||||
const callback = async (event: IWatchEvent) => {
|
||||
await this.handleEvent(event);
|
||||
};
|
||||
|
||||
const handle = await this.provider.watch(this.path, callback, watchOptions);
|
||||
return new SmartFsActiveWatcher(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming watch events (internal)
|
||||
*/
|
||||
private async handleEvent(event: IWatchEvent): Promise<void> {
|
||||
// Apply debouncing if configured
|
||||
if (this.options.debounce && this.options.debounce > 0) {
|
||||
const key = `${event.type}:${event.path}`;
|
||||
|
||||
// Clear existing timer
|
||||
const existingTimer = this.debounceTimers.get(key);
|
||||
if (existingTimer) {
|
||||
clearTimeout(existingTimer);
|
||||
}
|
||||
|
||||
// Set new timer
|
||||
const timer = setTimeout(async () => {
|
||||
this.debounceTimers.delete(key);
|
||||
await this.dispatchEvent(event);
|
||||
}, this.options.debounce);
|
||||
|
||||
this.debounceTimers.set(key, timer);
|
||||
} else {
|
||||
// No debouncing, dispatch immediately
|
||||
await this.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch event to registered handlers (internal)
|
||||
*/
|
||||
private async dispatchEvent(event: IWatchEvent): Promise<void> {
|
||||
// Dispatch to type-specific handlers
|
||||
const typeHandlers = this.handlers[event.type];
|
||||
if (typeHandlers) {
|
||||
for (const handler of typeHandlers) {
|
||||
await handler(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch to 'all' handlers
|
||||
if (this.handlers.all) {
|
||||
for (const handler of this.handlers.all) {
|
||||
await handler(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the watched path
|
||||
*/
|
||||
public getPath(): string {
|
||||
return this.path;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user