Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | 3x 1x 2x 3x 3x 3x 3x 3x 3x 2x 2x | /**
* API directory watcher
* Core logic - abstracted to be runtime agnostic
*/
import * as fs from 'fs';
import * as path from 'path';
import { ROUTE_FILE_PATTERN, MIDDLEWARE_FILE_PATTERN } from '../../shared/constants.js';
export type FileChangeEvent = 'add' | 'change' | 'unlink';
export type FileChangeCallback = (event: FileChangeEvent, filePath: string) => void;
export interface WatchApiDirectoryOptions {
debounceMs?: number;
}
/**
* Interface for watcher (allows different implementations)
*/
export interface ApiWatcher {
close(): void;
}
/**
* Creates a watcher for the API directory using Node.js fs.watch
* Returns a function to stop watching
*/
export function watchApiDirectory(
apiDir: string,
callback: FileChangeCallback,
options: WatchApiDirectoryOptions = {}
): ApiWatcher {
if (!fs.existsSync(apiDir)) {
return { close: () => {} };
}
const debounceMs = options.debounceMs ?? 0;
let pending: Array<{ event: FileChangeEvent; filePath: string }> = [];
let timer: ReturnType<typeof setTimeout> | null = null;
const flush = () => {
if (timer != null) {
clearTimeout(timer);
timer = null;
}
const toEmit = pending;
pending = [];
for (const { event, filePath } of toEmit) {
callback(event, filePath);
}
};
const schedule = (event: FileChangeEvent, filePath: string) => {
pending.push({ event, filePath });
if (debounceMs <= 0) {
flush();
return;
}
if (timer != null) clearTimeout(timer);
timer = setTimeout(flush, debounceMs);
};
const watcher = fs.watch(apiDir, { recursive: true }, (eventType, filename) => {
if (!filename) return;
const filePath = path.join(apiDir, filename);
const isRouteFile = ROUTE_FILE_PATTERN.test(filename);
const isMiddlewareFile = MIDDLEWARE_FILE_PATTERN.test(filename);
if (!isRouteFile && !isMiddlewareFile) {
return;
}
let normalizedEvent: FileChangeEvent;
if (eventType === 'rename') {
normalizedEvent = fs.existsSync(filePath) ? 'add' : 'unlink';
} else {
normalizedEvent = eventType as FileChangeEvent;
}
schedule(normalizedEvent, filePath);
});
return {
close: () => {
Iif (timer != null) clearTimeout(timer);
watcher.close();
},
};
}
|