UNPKG

nodejs-event-driven

Version:

NodeJS agnostic event driven with EventEmitter support

171 lines 5.03 kB
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