-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
536 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Node FS | ||
|
||
Enhanced file system operations in Node.js with asynchronous queue to prevent parallel writes. | ||
|
||
## Installation | ||
|
||
```bash | ||
yarn add @alwatr/node-fs | ||
``` | ||
|
||
## Features | ||
|
||
- Checks if a directory exists. If it doesn't, it creates the directory and all necessary subdirectories. | ||
- Before writing a file successfully, first writes it to a temporary path (`path.tmp`). | ||
- If a file already exists, renames and keeps the existing file at a backup path (`path.bak`). | ||
- If a write operation fails, the original file remains unchanged. | ||
- Includes `readJson` and `writeJson` functions that automatically parse and stringify JSON data. | ||
- Supports both synchronous and asynchronous read/write operations. | ||
- An asynchronous queue is used to prevent simultaneous write operations. | ||
- Fully written in TypeScript, includes type definitions. | ||
- Separate builds are provided for ESModule and CommonJS. | ||
- Zero dependencies, except for the nanolib library. | ||
- Includes a beautiful log feature, which uses the [logger](https://github.com/Alwatr/nanolib/tree/next/packages/logger) package from nanolib. | ||
|
||
## Usage | ||
|
||
```typescript | ||
import {writeJson} from '@alwatr/node-fs'; | ||
|
||
const path = 'file.json'; | ||
await writeJson(path, {a: 1}); // wait to finish | ||
writeJson(path, {a: 2}); // asynchronous write in queue | ||
writeJson(path, {a: 3}); // asynchronous write in queue | ||
|
||
const data = await readJson(path); // automatically wait for the queue to finish | ||
console.log(data.a); // 3 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import {existsSync, makeEmptyFile, resolve} from '@alwatr/node-fs'; | ||
|
||
import {mkdir, rm} from 'node:fs/promises'; | ||
|
||
(async () => { | ||
const temp = resolve('./.tmp'); | ||
|
||
await mkdir(temp); | ||
|
||
console.log('start bench'); | ||
|
||
console.time('bench'); | ||
for (let i = 0; i < 10_000; i++) { | ||
await makeEmptyFile(`${temp}/file-${i}.asn`); | ||
} | ||
console.timeEnd('bench'); | ||
|
||
await rm(temp, {recursive: true, force: true}); | ||
|
||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import {readJson, writeJson} from '@alwatr/node-fs'; | ||
|
||
for (let i = 0; i < 100; i++) { | ||
console.log('writeJson %s without waiting...', i); | ||
writeJson('file.json', {i, a: 'b'}); | ||
if (i===70) { | ||
console.log('readJson %s', i); | ||
console.dir(await readJson('file.json')); | ||
} | ||
} | ||
|
||
console.dir(await readJson('file.json')); | ||
|
||
console.log('loop done, wait for queue process') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
{ | ||
"name": "@alwatr/node-fs", | ||
"version": "0.0.0", | ||
"description": "Enhanced file system operations in Node.js with asynchronous queue to prevent parallel writes.", | ||
"author": "S. Ali Mihandoost <[email protected]>", | ||
"keywords": [ | ||
"node-fs", | ||
"fs", | ||
"file", | ||
"filesystem", | ||
"readFile", | ||
"writeFile", | ||
"readJson", | ||
"writeJson", | ||
"JSON", | ||
"async", | ||
"queue", | ||
"cross-platform", | ||
"ECMAScript", | ||
"typescript", | ||
"javascript", | ||
"node", | ||
"nodejs", | ||
"esm", | ||
"module", | ||
"utility", | ||
"util", | ||
"utils", | ||
"nanolib", | ||
"alwatr" | ||
], | ||
"type": "module", | ||
"main": "./dist/main.cjs", | ||
"module": "./dist/main.mjs", | ||
"types": "./dist/main.d.ts", | ||
"exports": { | ||
".": { | ||
"import": "./dist/main.mjs", | ||
"require": "./dist/main.cjs", | ||
"types": "./dist/main.d.ts" | ||
} | ||
}, | ||
"license": "MIT", | ||
"files": [ | ||
"**/*.{js,mjs,cjs,map,d.ts,html,md}", | ||
"!demo/**/*" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/Alwatr/nanolib", | ||
"directory": "packages/node-fs" | ||
}, | ||
"homepage": "https://github.com/Alwatr/nanolib/tree/next/packages/node-fs#readme", | ||
"bugs": { | ||
"url": "https://github.com/Alwatr/nanolib/issues" | ||
}, | ||
"prettier": "@alwatr/prettier-config", | ||
"scripts": { | ||
"b": "yarn run build", | ||
"w": "yarn run watch", | ||
"c": "yarn run clean", | ||
"cb": "yarn run clean && yarn run build", | ||
"d": "yarn run build:es && yarn node --enable-source-maps --trace-warnings", | ||
"build": "yarn run build:ts & yarn run build:es", | ||
"build:es": "nano-build --preset=module", | ||
"build:ts": "tsc --build", | ||
"watch": "yarn run watch:ts & yarn run watch:es", | ||
"watch:es": "yarn run build:es --watch", | ||
"watch:ts": "yarn run build:ts --watch --preserveWatchOutput", | ||
"clean": "rm -rfv dist *.tsbuildinfo" | ||
}, | ||
"dependencies": { | ||
"@alwatr/async-queue": "workspace:^", | ||
"@alwatr/flat-string": "workspace:^", | ||
"@alwatr/logger": "workspace:^" | ||
}, | ||
"devDependencies": { | ||
"@alwatr/nano-build": "workspace:^", | ||
"@alwatr/prettier-config": "workspace:^", | ||
"@alwatr/tsconfig-base": "workspace:^", | ||
"@alwatr/type-helper": "workspace:^", | ||
"@types/node": "^20.10.7", | ||
"typescript": "^5.3.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import {AsyncQueue} from '@alwatr/async-queue'; | ||
import {definePackage} from '@alwatr/logger'; | ||
|
||
import type {} from '@alwatr/nano-build'; | ||
|
||
export const logger = definePackage('@alwatr/node-fs', __package_version__); | ||
|
||
export const asyncQueue = new AsyncQueue(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import {logger} from './common'; | ||
|
||
import type { Dictionary } from '@alwatr/type-helper'; | ||
|
||
/** | ||
* Parse json string. | ||
* | ||
* @param content - json string | ||
* @returns json object | ||
* @example | ||
* ```typescript | ||
* const json = parseJson('{"a":1,"b":2}'); | ||
* console.log(json.a); // 1 | ||
* ``` | ||
*/ | ||
export function parseJson<T extends Dictionary>(content: string): T { | ||
try { | ||
return JSON.parse(content); | ||
} | ||
catch (err) { | ||
logger.error('parseJson', 'invalid_json', err); | ||
throw new Error('invalid_json', {cause: (err as Error).cause}); | ||
} | ||
} | ||
|
||
/** | ||
* Stringify json object. | ||
* | ||
* @param data - json object | ||
* @returns json string | ||
* @example | ||
* ```typescript | ||
* const json = jsonStringify({a:1, b:2}); | ||
* console.log(json); // '{"a":1,"b":2}' | ||
* ``` | ||
*/ | ||
export function jsonStringify<T extends Dictionary>(data: T): string { | ||
try { | ||
return JSON.stringify(data); | ||
} | ||
catch (err) { | ||
logger.error('jsonStringify', 'stringify_failed', err); | ||
throw new Error('stringify_failed', {cause: (err as Error).cause}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export * from './read-file'; | ||
export * from './write-file'; | ||
export * from './read-json'; | ||
export * from './write-json'; | ||
export * from './make-file'; | ||
|
||
export {resolve} from 'node:path'; | ||
export {existsSync} from 'node:fs'; | ||
export {unlink} from 'node:fs/promises'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import {open} from 'node:fs/promises'; | ||
|
||
import {logger} from './common'; | ||
|
||
/** | ||
* Make empty file. | ||
* | ||
* @param path - file path | ||
* | ||
* @example | ||
* ```ts | ||
* await makeFile('./file.txt'); | ||
* ``` | ||
*/ | ||
export async function makeEmptyFile(path: string): Promise<void> { | ||
logger.logMethodArgs?.('makeEmptyFile', '...' + path.slice(-32)); | ||
return (await open(path, 'w')).close(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import {readFileSync as readFileSync_} from 'node:fs'; | ||
import {readFile as readFile_} from 'node:fs/promises'; | ||
|
||
import {flatString} from '@alwatr/flat-string'; | ||
|
||
import {asyncQueue, logger} from './common'; | ||
|
||
/** | ||
* Enhanced read File (Synchronous). | ||
* | ||
* @param path - file path | ||
* @returns file content | ||
* @example | ||
* ```typescript | ||
* const fileContent = readFileSync('./file.txt', sync); | ||
* ``` | ||
*/ | ||
export function readFileSync(path: string): string { | ||
logger.logMethodArgs?.('readFileSync', '...' + path.slice(-32)); | ||
// if (!existsSync(path)) throw new Error('file_not_found'); | ||
try { | ||
return flatString(readFileSync_(path, {encoding: 'utf-8', flag: 'r'})); | ||
} | ||
catch (err) { | ||
logger.error('readFileSync', 'read_file_failed', {path}, err); | ||
throw new Error('read_file_failed', {cause: (err as Error).cause}); | ||
} | ||
} | ||
|
||
/** | ||
* Enhanced read File (Asynchronous). | ||
* | ||
* - If writing queue is running for target path, it will wait for it to finish. | ||
* | ||
* @param path - file path | ||
* @returns file content | ||
* @example | ||
* ```typescript | ||
* const fileContent = await readFile('./file.txt', sync); | ||
* ``` | ||
*/ | ||
export function readFile(path: string): Promise<string> { | ||
logger.logMethodArgs?.('readFile', '...' + path.slice(-32)); | ||
// if (!existsSync(path)) throw new Error('file_not_found'); | ||
return asyncQueue.push(path, async () => { | ||
try { | ||
return flatString(await readFile_(path, {encoding: 'utf-8', flag: 'r'})); | ||
} | ||
catch (err) { | ||
logger.error('readFile', 'read_file_failed', {path}, err); | ||
throw new Error('read_file_failed', {cause: (err as Error).cause}); | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import {logger} from './common'; | ||
import {parseJson} from './json'; | ||
import {readFile, readFileSync} from './read-file'; | ||
|
||
import type {Dictionary, MaybePromise} from '@alwatr/type-helper'; | ||
|
||
/** | ||
* Enhanced read json file (async). | ||
* | ||
* @param path - file path | ||
* @returns json object | ||
* @example | ||
* ```typescript | ||
* const fileContent = await readJson('./file.json'); | ||
* ``` | ||
*/ | ||
export function readJson<T extends Dictionary>(path: string): Promise<T>; | ||
/** | ||
* Enhanced read json file (sync). | ||
* | ||
* @param path - file path | ||
* @param sync - sync mode | ||
* @returns json object | ||
* @example | ||
* ```typescript | ||
* const fileContent = readJson('./file.json', true); | ||
* ``` | ||
*/ | ||
export function readJson<T extends Dictionary>(path: string, sync: true): T; | ||
/** | ||
* Enhanced read json file. | ||
* | ||
* @param path - file path | ||
* @param sync - sync mode | ||
* @returns json object | ||
* @example | ||
* ```typescript | ||
* const fileContent = await readJson('./file.json', sync); | ||
* ``` | ||
*/ | ||
export function readJson<T extends Dictionary>(path: string, sync: boolean): MaybePromise<T>; | ||
/** | ||
* Enhanced read json file. | ||
* | ||
* @param path - file path | ||
* @param sync - sync mode | ||
* @returns json object | ||
* @example | ||
* ```typescript | ||
* const fileContent = await readJson('./file.json'); | ||
* ``` | ||
*/ | ||
export function readJson<T extends Dictionary>(path: string, sync = false): MaybePromise<T> { | ||
logger.logMethodArgs?.('readJson', {path: path.slice(-32), sync}); | ||
if (sync === true) { | ||
return parseJson<T>(readFileSync(path)); | ||
} | ||
else { | ||
return readFile(path).then((content) => parseJson<T>(content)); | ||
} | ||
} |
Oops, something went wrong.