nodejs-event-driven
Version:
NodeJS agnostic event driven with EventEmitter support
171 lines • 5.03 kB
JavaScript
import { mkdirSync, statSync } from 'node:fs';
import { mkdir, readFile, readdir, stat, unlink } from 'node:fs/promises';
import { join } from 'node:path';
import { setInterval } from 'node:timers';
import { lock } from 'proper-lockfile';
export const checkIsDir = async (filePath) => {
const filePathStats = await stat(filePath);
if (!filePathStats.isDirectory()) {
throw new Error(`File ${filePath} is not a directory`);
}
};
export const checkIsDirSync = (filePath) => {
const filePathStats = statSync(filePath);
if (!filePathStats.isDirectory()) {
throw new Error(`File ${filePath} is not a directory`);
}
};
export const createDirIfNeeded = async (filePath) => {
try {
await checkIsDir(filePath);
}
catch (err) {
const error = err;
if (error.code !== 'ENOENT') {
throw error;
}
await mkdir(filePath, { recursive: true });
}
};
export const createDirSyncIfNeeded = (filePath) => {
try {
checkIsDirSync(filePath);
}
catch (err) {
const error = err;
if (error.code === 'ENOENT') {
mkdirSync(filePath, { recursive: true });
return;
}
throw error;
}
};
export const relativePathIsFile = async (baseDir, fileRelPath, filenamePattern) => {
const filePath = join(baseDir, fileRelPath);
let filePathRelease = null;
try {
filePathRelease = await lock(filePath);
const fileStat = await stat(filePath);
if (!fileStat.isFile()) {
return false;
}
return !!filenamePattern?.test(fileRelPath);
}
catch (err) {
const error = err;
if (error.code !== 'ENOENT') {
throw err;
}
return false;
}
finally {
if (filePathRelease) {
await filePathRelease();
}
}
};
export const listFilesInDirectory = async (baseDir, filenamePattern) => {
const files = [];
let releaseBaseDir = null;
try {
releaseBaseDir = await lock(baseDir);
const filePaths = await readdir(baseDir, { recursive: true });
for (const fileRelPath of filePaths) {
const filePath = join(baseDir, fileRelPath);
const isFile = await relativePathIsFile(baseDir, fileRelPath, filenamePattern);
if (isFile) {
files.push(filePath);
}
}
}
catch (err) {
const error = err;
if (error.code !== 'ELOCKED') {
throw err;
}
}
finally {
if (releaseBaseDir) {
await releaseBaseDir();
}
}
return files;
};
export const pollFile = async (fileInfos, filePath, fileType) => {
const timestampMatch = /event-(\d+)\.data$/.exec(filePath);
if (!timestampMatch) {
return Promise.resolve();
}
const timestamp = Number(timestampMatch[1]);
let filePathRelease = null;
try {
filePathRelease = await lock(filePath);
const isJsonOrText = fileType === 'json' || fileType === 'text';
const readFileOptions = {};
if (isJsonOrText) {
readFileOptions.encoding = 'utf-8';
}
const content = await readFile(filePath, readFileOptions);
const isJson = fileType === 'json' && typeof content === 'string';
let data;
if (isJson) {
data = JSON.parse(content);
}
else {
data = content;
}
fileInfos.push({ timestamp, filePath, data });
await unlink(filePath);
}
catch (err) {
const error = err;
if (error.code !== 'ENOENT') {
throw err;
}
console.warn(err);
}
finally {
if (filePathRelease) {
await filePathRelease();
}
}
return Promise.resolve();
};
export const watchFiles = (config) => {
const { baseDir, filenamePattern, fileType, logger, pollingDelayMs = 500, signal, } = config;
let fileListener;
let timer = null;
if (signal) {
signal.onabort = () => {
if (logger) {
logger.debug('Received signal abort: going to stop watching');
}
if (timer) {
clearTimeout(timer);
timer = null;
}
};
}
const poll = async () => {
const filePaths = await listFilesInDirectory(baseDir, filenamePattern);
const fileInfos = [];
for (const filePath of filePaths) {
await pollFile(fileInfos, filePath, fileType);
}
fileInfos.sort((a, b) => a.timestamp - b.timestamp);
for (const { filePath, data } of fileInfos) {
if (fileListener) {
fileListener(filePath, data);
}
}
};
timer = setInterval(() => {
void poll();
}, pollingDelayMs);
return {
onFile: (listener) => {
fileListener = listener;
},
};
};
//# sourceMappingURL=fs.helper.js.map