-
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.
feat(node-fs): rewrite new writeFile method
- Loading branch information
Showing
1 changed file
with
50 additions
and
230 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,262 +1,82 @@ | ||
import {writeFileSync as writeFileSync_, existsSync, mkdirSync, copyFileSync, renameSync} from 'node:fs'; | ||
import {copyFile, mkdir, rename, writeFile as writeFile_} from 'node:fs/promises'; | ||
import {writeFileSync as writeFileSync_, existsSync, mkdirSync, renameSync} from 'node:fs'; | ||
import {mkdir, rename, writeFile as writeFile_} from 'node:fs/promises'; | ||
import {dirname} from 'node:path'; | ||
|
||
import {flatString} from '@alwatr/flat-string'; | ||
|
||
import {jsonStringify} from './json'; | ||
import {logger} from './logger'; | ||
|
||
import type { MaybePromise } from '@alwatr/type-helper'; | ||
|
||
/** | ||
* Write file mode for exists file. | ||
*/ | ||
export enum WriteFileMode { | ||
Replace = 'replace', | ||
Rename = 'rename', | ||
Copy = 'copy', | ||
} | ||
|
||
/** | ||
* Enhanced write file (async). | ||
* Enhanced write file (Synchronous). | ||
* | ||
* @param path - file path | ||
* @param content - file content | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @example | ||
* ```typescript | ||
* await writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace); | ||
* ``` | ||
*/ | ||
export function writeFile(path: string, content: string, mode: WriteFileMode, sync?: false): Promise<void>; | ||
/** | ||
* Enhanced write file (sync). | ||
* - If directory not exists, create it recursively. | ||
* - Write file to `path.tmp` before write success. | ||
* - If file exists, renamed (keep) to `path.bak`. | ||
* - If write failed, original file will not be changed. | ||
* | ||
* @param path - file path | ||
* @param content - file content | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @param sync - sync mode | ||
* @example | ||
* ```typescript | ||
* writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace, true); | ||
* writeFileSync('./file.txt', 'Hello World!'); | ||
* ``` | ||
*/ | ||
export function writeFile(path: string, content: string, mode: WriteFileMode, sync: true): void; | ||
/** | ||
* Enhanced write file. | ||
* | ||
* @param path - file path | ||
* @param content - file content | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @param sync - sync mode | ||
* @example | ||
* ```typescript | ||
* await writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace, sync); | ||
* ``` | ||
*/ | ||
export function writeFile(path: string, content: string, mode: WriteFileMode, sync: boolean): MaybePromise<void>; | ||
/** | ||
* Enhanced write file. | ||
* | ||
* @param path - file path | ||
* @param content - file content | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @param sync - sync mode | ||
* @example | ||
* ```typescript | ||
* await writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace); | ||
* ``` | ||
*/ | ||
export function writeFile(path: string, content: string, mode: WriteFileMode, sync = false): MaybePromise<void> { | ||
logger.logMethodArgs?.('writeFile', {path: path.slice(-32), mode, sync}); | ||
if (sync === true) { | ||
handleExistsFile(path, mode, true); | ||
try { | ||
logger.logOther?.('write_file_start', {path: path.slice(-32), sync}); | ||
writeFileSync_(path, content, {encoding: 'utf-8', flag: 'w'}); | ||
logger.logOther?.('write_file_success', {path: path.slice(-32), sync}); | ||
return; | ||
export function writeFileSync(path: string, content: string): void { | ||
logger.logMethodArgs?.('writeFileSync', '...' + path.slice(-32)); | ||
try { | ||
const pathExists = existsSync(path); | ||
if (!pathExists) { | ||
const dir = dirname(path); | ||
if (!existsSync(dir)) { | ||
mkdirSync(dir, {recursive: true}); | ||
} | ||
} | ||
catch (err) { | ||
logger.error('writeFile', 'write_file_failed', err); | ||
throw new Error('write_file_failed', {cause: (err as Error).cause}); | ||
writeFileSync_(path + '.tmp', content, {encoding: 'utf-8', flag: 'w'}); | ||
if (pathExists) { | ||
renameSync(path, path + '.bak'); | ||
} | ||
renameSync(path + '.tmp', path); | ||
logger.logOther?.('writeFileSync success', '...' + path.slice(-32)); | ||
} | ||
catch (err) { | ||
logger.error('writeFileSync', 'write_file_failed', {path}, err); | ||
throw new Error('write_file_failed', {cause: (err as Error).cause}); | ||
} | ||
// else, async mode | ||
return handleExistsFile(path, mode) | ||
.then(() => { | ||
logger.logOther?.('write_file_start', {path: path.slice(-32), sync}); | ||
return writeFile_(path, content, {encoding: 'utf-8', flag: 'w'}); | ||
}) | ||
.then(() => { | ||
logger.logOther?.('write_file_success', {path: path.slice(-32), sync}); | ||
}) | ||
.catch((err) => { | ||
logger.error('writeFile', 'write_file_failed', err); | ||
throw new Error('write_file_failed', {cause: (err as Error).cause}); | ||
}); | ||
} | ||
|
||
/** | ||
* Handle exists file (async). | ||
* | ||
* @param path - file path | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @param sync - sync mode | ||
* @example | ||
* ```typescript | ||
* await handleExistsFile('./file.txt', WriteFileMode.Rename); | ||
* ``` | ||
*/ | ||
export function handleExistsFile(path: string, mode: WriteFileMode): Promise<void>; | ||
/** | ||
* Handle exists file (sync). | ||
* | ||
* @param path - file path | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @param sync - sync mode | ||
* @example | ||
* ```typescript | ||
* handleExistsFile('./file.txt', WriteFileMode.Rename, true); | ||
* ``` | ||
*/ | ||
export function handleExistsFile(path: string, mode: WriteFileMode, sync: true): void; | ||
/** | ||
* Handle exists file. | ||
* Enhanced write file (Asynchronous). | ||
* | ||
* @param path - file path | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @param sync - sync mode | ||
* @example | ||
* ```typescript | ||
* await handleExistsFile('./file.txt', WriteFileMode.Rename, sync); | ||
* ``` | ||
*/ | ||
export function handleExistsFile(path: string, mode: WriteFileMode, sync: boolean): MaybePromise<void>; | ||
/** | ||
* Handle exists file. | ||
* - If directory not exists, create it recursively. | ||
* - Write file to `path.tmp` before write success. | ||
* - If file exists, renamed (keep) to `path.bak`. | ||
* - If write failed, original file will not be changed. | ||
* | ||
* @param path - file path | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @param sync - sync mode | ||
* @param content - file content | ||
* @example | ||
* ```typescript | ||
* await handleExistsFile('./file.txt', WriteFileMode.Rename); | ||
* writeFile('./file.txt', 'Hello World!'); | ||
* ``` | ||
*/ | ||
export function handleExistsFile(path: string, mode: WriteFileMode, sync = false): MaybePromise<void> { | ||
logger.logMethodArgs?.('handleExistsFile', {path: path.slice(-32), mode, sync}); | ||
if (sync === true) { | ||
if (existsSync(path)) { | ||
if (mode === WriteFileMode.Copy) { | ||
try { | ||
copyFileSync(path, path + '.bk'); | ||
} | ||
catch (err) { | ||
logger.error('handleExistsFile', 'copy_failed', err); | ||
} | ||
} | ||
else if (mode === WriteFileMode.Rename) { | ||
try { | ||
renameSync(path, path + '.bk'); | ||
} | ||
catch (err) { | ||
logger.error('handleExistsFile', 'rename_failed', err); | ||
} | ||
export async function writeFile(path: string, content: string): Promise<void> { | ||
logger.logMethodArgs?.('writeFile', '...' + path.slice(-32)); | ||
try { | ||
logger.logOther?.('writeFile start', '...' + path.slice(-32)); | ||
const pathExists = existsSync(path); | ||
if (!pathExists) { | ||
const dir = dirname(path); | ||
if (!existsSync(dir)) { | ||
await mkdir(dir, {recursive: true}); | ||
} | ||
} | ||
else { | ||
try { | ||
mkdirSync(dirname(path), {recursive: true}); | ||
} | ||
catch (err) { | ||
logger.error('handleExistsFile', 'make_dir_failed', err); | ||
throw new Error('make_dir_failed', {cause: (err as Error).cause}); | ||
} | ||
await writeFile_(path + '.tmp', content, {encoding: 'utf-8', flag: 'w'}); | ||
if (pathExists) { | ||
await rename(path, path + '.bak'); | ||
} | ||
await rename(path + '.tmp', path); | ||
logger.logOther?.('writeFile success', '...' + path.slice(-32)); | ||
} | ||
else { | ||
// async mode | ||
if (existsSync(path)) { | ||
// existsSync is much faster than access. | ||
if (mode === WriteFileMode.Copy) { | ||
return copyFile(path, path + '.bk').catch((err) => { | ||
logger.error('handleExistsFile', 'copy_failed', err); | ||
}); | ||
} | ||
else if (mode === WriteFileMode.Rename) { | ||
return rename(path, path + '.bk').catch((err) => { | ||
logger.error('handleExistsFile', 'rename_failed', err); | ||
}); | ||
} | ||
} | ||
else { | ||
return mkdir(dirname(path), {recursive: true}) | ||
.then(() => { | ||
logger.logOther?.('handleExistsFile', 'make_dir_success'); | ||
}) | ||
.catch((err) => { | ||
logger.error('handleExistsFile', 'make_dir_failed', err); | ||
throw new Error('make_dir_failed', {cause: (err as Error).cause}); | ||
}); | ||
} | ||
|
||
return Promise.resolve(); // do nothing but return a resolved promise. | ||
catch (err) { | ||
logger.error('writeFile', 'write_file_failed', {path}, err); | ||
throw new Error('write_file_failed', {cause: (err as Error).cause}); | ||
} | ||
} | ||
|
||
/** | ||
* Enhanced write json file (async). | ||
* | ||
* @param path - file path | ||
* @param data - json object | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @example | ||
* ```typescript | ||
* await writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace); | ||
* ``` | ||
*/ | ||
export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync?: false): Promise<void>; | ||
/** | ||
* Enhanced write json file (sync). | ||
* | ||
* @param path - file path | ||
* @param data - json object | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @param sync - sync mode | ||
* @example | ||
* ```typescript | ||
* writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace, true); | ||
* ``` | ||
*/ | ||
export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync: true): void; | ||
/** | ||
* Enhanced write json file. | ||
* | ||
* @param path - file path | ||
* @param data - json object | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @param sync - sync mode | ||
* @example | ||
* ```typescript | ||
* await writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace, sync); | ||
* ``` | ||
*/ | ||
export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync: boolean): MaybePromise<void>; | ||
/** | ||
* Enhanced write json file. | ||
* | ||
* @param path - file path | ||
* @param data - json object | ||
* @param mode - handle exists file (replace, copy, rename) | ||
* @param sync - sync mode | ||
* @example | ||
* ```typescript | ||
* await writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace); | ||
* ``` | ||
*/ | ||
export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync = false): MaybePromise<void> { | ||
logger.logMethodArgs?.('writeJsonFile', {path: path.slice(-32), mode, sync}); | ||
return writeFile(path, flatString(jsonStringify(data)), mode, sync); | ||
} |