Skip to content

Commit 7534ed1

Browse files
committed
feat(node-fs): rewrite new writeFile method
1 parent 7bddaa0 commit 7534ed1

File tree

1 file changed

+50
-230
lines changed

1 file changed

+50
-230
lines changed

packages/node-fs/src/write-file.ts

+50-230
Original file line numberDiff line numberDiff line change
@@ -1,262 +1,82 @@
1-
import {writeFileSync as writeFileSync_, existsSync, mkdirSync, copyFileSync, renameSync} from 'node:fs';
2-
import {copyFile, mkdir, rename, writeFile as writeFile_} from 'node:fs/promises';
1+
import {writeFileSync as writeFileSync_, existsSync, mkdirSync, renameSync} from 'node:fs';
2+
import {mkdir, rename, writeFile as writeFile_} from 'node:fs/promises';
33
import {dirname} from 'node:path';
44

5-
import {flatString} from '@alwatr/flat-string';
6-
7-
import {jsonStringify} from './json';
85
import {logger} from './logger';
96

10-
import type { MaybePromise } from '@alwatr/type-helper';
11-
12-
/**
13-
* Write file mode for exists file.
14-
*/
15-
export enum WriteFileMode {
16-
Replace = 'replace',
17-
Rename = 'rename',
18-
Copy = 'copy',
19-
}
20-
217
/**
22-
* Enhanced write file (async).
8+
* Enhanced write file (Synchronous).
239
*
24-
* @param path - file path
25-
* @param content - file content
26-
* @param mode - handle exists file (replace, copy, rename)
27-
* @example
28-
* ```typescript
29-
* await writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace);
30-
* ```
31-
*/
32-
export function writeFile(path: string, content: string, mode: WriteFileMode, sync?: false): Promise<void>;
33-
/**
34-
* Enhanced write file (sync).
10+
* - If directory not exists, create it recursively.
11+
* - Write file to `path.tmp` before write success.
12+
* - If file exists, renamed (keep) to `path.bak`.
13+
* - If write failed, original file will not be changed.
3514
*
3615
* @param path - file path
3716
* @param content - file content
38-
* @param mode - handle exists file (replace, copy, rename)
39-
* @param sync - sync mode
4017
* @example
4118
* ```typescript
42-
* writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace, true);
19+
* writeFileSync('./file.txt', 'Hello World!');
4320
* ```
4421
*/
45-
export function writeFile(path: string, content: string, mode: WriteFileMode, sync: true): void;
46-
/**
47-
* Enhanced write file.
48-
*
49-
* @param path - file path
50-
* @param content - file content
51-
* @param mode - handle exists file (replace, copy, rename)
52-
* @param sync - sync mode
53-
* @example
54-
* ```typescript
55-
* await writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace, sync);
56-
* ```
57-
*/
58-
export function writeFile(path: string, content: string, mode: WriteFileMode, sync: boolean): MaybePromise<void>;
59-
/**
60-
* Enhanced write file.
61-
*
62-
* @param path - file path
63-
* @param content - file content
64-
* @param mode - handle exists file (replace, copy, rename)
65-
* @param sync - sync mode
66-
* @example
67-
* ```typescript
68-
* await writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace);
69-
* ```
70-
*/
71-
export function writeFile(path: string, content: string, mode: WriteFileMode, sync = false): MaybePromise<void> {
72-
logger.logMethodArgs?.('writeFile', {path: path.slice(-32), mode, sync});
73-
if (sync === true) {
74-
handleExistsFile(path, mode, true);
75-
try {
76-
logger.logOther?.('write_file_start', {path: path.slice(-32), sync});
77-
writeFileSync_(path, content, {encoding: 'utf-8', flag: 'w'});
78-
logger.logOther?.('write_file_success', {path: path.slice(-32), sync});
79-
return;
22+
export function writeFileSync(path: string, content: string): void {
23+
logger.logMethodArgs?.('writeFileSync', '...' + path.slice(-32));
24+
try {
25+
const pathExists = existsSync(path);
26+
if (!pathExists) {
27+
const dir = dirname(path);
28+
if (!existsSync(dir)) {
29+
mkdirSync(dir, {recursive: true});
30+
}
8031
}
81-
catch (err) {
82-
logger.error('writeFile', 'write_file_failed', err);
83-
throw new Error('write_file_failed', {cause: (err as Error).cause});
32+
writeFileSync_(path + '.tmp', content, {encoding: 'utf-8', flag: 'w'});
33+
if (pathExists) {
34+
renameSync(path, path + '.bak');
8435
}
36+
renameSync(path + '.tmp', path);
37+
logger.logOther?.('writeFileSync success', '...' + path.slice(-32));
38+
}
39+
catch (err) {
40+
logger.error('writeFileSync', 'write_file_failed', {path}, err);
41+
throw new Error('write_file_failed', {cause: (err as Error).cause});
8542
}
86-
// else, async mode
87-
return handleExistsFile(path, mode)
88-
.then(() => {
89-
logger.logOther?.('write_file_start', {path: path.slice(-32), sync});
90-
return writeFile_(path, content, {encoding: 'utf-8', flag: 'w'});
91-
})
92-
.then(() => {
93-
logger.logOther?.('write_file_success', {path: path.slice(-32), sync});
94-
})
95-
.catch((err) => {
96-
logger.error('writeFile', 'write_file_failed', err);
97-
throw new Error('write_file_failed', {cause: (err as Error).cause});
98-
});
9943
}
10044

10145
/**
102-
* Handle exists file (async).
103-
*
104-
* @param path - file path
105-
* @param mode - handle exists file (replace, copy, rename)
106-
* @param sync - sync mode
107-
* @example
108-
* ```typescript
109-
* await handleExistsFile('./file.txt', WriteFileMode.Rename);
110-
* ```
111-
*/
112-
export function handleExistsFile(path: string, mode: WriteFileMode): Promise<void>;
113-
/**
114-
* Handle exists file (sync).
115-
*
116-
* @param path - file path
117-
* @param mode - handle exists file (replace, copy, rename)
118-
* @param sync - sync mode
119-
* @example
120-
* ```typescript
121-
* handleExistsFile('./file.txt', WriteFileMode.Rename, true);
122-
* ```
123-
*/
124-
export function handleExistsFile(path: string, mode: WriteFileMode, sync: true): void;
125-
/**
126-
* Handle exists file.
46+
* Enhanced write file (Asynchronous).
12747
*
128-
* @param path - file path
129-
* @param mode - handle exists file (replace, copy, rename)
130-
* @param sync - sync mode
131-
* @example
132-
* ```typescript
133-
* await handleExistsFile('./file.txt', WriteFileMode.Rename, sync);
134-
* ```
135-
*/
136-
export function handleExistsFile(path: string, mode: WriteFileMode, sync: boolean): MaybePromise<void>;
137-
/**
138-
* Handle exists file.
48+
* - If directory not exists, create it recursively.
49+
* - Write file to `path.tmp` before write success.
50+
* - If file exists, renamed (keep) to `path.bak`.
51+
* - If write failed, original file will not be changed.
13952
*
14053
* @param path - file path
141-
* @param mode - handle exists file (replace, copy, rename)
142-
* @param sync - sync mode
54+
* @param content - file content
14355
* @example
14456
* ```typescript
145-
* await handleExistsFile('./file.txt', WriteFileMode.Rename);
57+
* writeFile('./file.txt', 'Hello World!');
14658
* ```
14759
*/
148-
export function handleExistsFile(path: string, mode: WriteFileMode, sync = false): MaybePromise<void> {
149-
logger.logMethodArgs?.('handleExistsFile', {path: path.slice(-32), mode, sync});
150-
if (sync === true) {
151-
if (existsSync(path)) {
152-
if (mode === WriteFileMode.Copy) {
153-
try {
154-
copyFileSync(path, path + '.bk');
155-
}
156-
catch (err) {
157-
logger.error('handleExistsFile', 'copy_failed', err);
158-
}
159-
}
160-
else if (mode === WriteFileMode.Rename) {
161-
try {
162-
renameSync(path, path + '.bk');
163-
}
164-
catch (err) {
165-
logger.error('handleExistsFile', 'rename_failed', err);
166-
}
60+
export async function writeFile(path: string, content: string): Promise<void> {
61+
logger.logMethodArgs?.('writeFile', '...' + path.slice(-32));
62+
try {
63+
logger.logOther?.('writeFile start', '...' + path.slice(-32));
64+
const pathExists = existsSync(path);
65+
if (!pathExists) {
66+
const dir = dirname(path);
67+
if (!existsSync(dir)) {
68+
await mkdir(dir, {recursive: true});
16769
}
16870
}
169-
else {
170-
try {
171-
mkdirSync(dirname(path), {recursive: true});
172-
}
173-
catch (err) {
174-
logger.error('handleExistsFile', 'make_dir_failed', err);
175-
throw new Error('make_dir_failed', {cause: (err as Error).cause});
176-
}
71+
await writeFile_(path + '.tmp', content, {encoding: 'utf-8', flag: 'w'});
72+
if (pathExists) {
73+
await rename(path, path + '.bak');
17774
}
75+
await rename(path + '.tmp', path);
76+
logger.logOther?.('writeFile success', '...' + path.slice(-32));
17877
}
179-
else {
180-
// async mode
181-
if (existsSync(path)) {
182-
// existsSync is much faster than access.
183-
if (mode === WriteFileMode.Copy) {
184-
return copyFile(path, path + '.bk').catch((err) => {
185-
logger.error('handleExistsFile', 'copy_failed', err);
186-
});
187-
}
188-
else if (mode === WriteFileMode.Rename) {
189-
return rename(path, path + '.bk').catch((err) => {
190-
logger.error('handleExistsFile', 'rename_failed', err);
191-
});
192-
}
193-
}
194-
else {
195-
return mkdir(dirname(path), {recursive: true})
196-
.then(() => {
197-
logger.logOther?.('handleExistsFile', 'make_dir_success');
198-
})
199-
.catch((err) => {
200-
logger.error('handleExistsFile', 'make_dir_failed', err);
201-
throw new Error('make_dir_failed', {cause: (err as Error).cause});
202-
});
203-
}
204-
205-
return Promise.resolve(); // do nothing but return a resolved promise.
78+
catch (err) {
79+
logger.error('writeFile', 'write_file_failed', {path}, err);
80+
throw new Error('write_file_failed', {cause: (err as Error).cause});
20681
}
20782
}
208-
209-
/**
210-
* Enhanced write json file (async).
211-
*
212-
* @param path - file path
213-
* @param data - json object
214-
* @param mode - handle exists file (replace, copy, rename)
215-
* @example
216-
* ```typescript
217-
* await writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace);
218-
* ```
219-
*/
220-
export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync?: false): Promise<void>;
221-
/**
222-
* Enhanced write json file (sync).
223-
*
224-
* @param path - file path
225-
* @param data - json object
226-
* @param mode - handle exists file (replace, copy, rename)
227-
* @param sync - sync mode
228-
* @example
229-
* ```typescript
230-
* writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace, true);
231-
* ```
232-
*/
233-
export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync: true): void;
234-
/**
235-
* Enhanced write json file.
236-
*
237-
* @param path - file path
238-
* @param data - json object
239-
* @param mode - handle exists file (replace, copy, rename)
240-
* @param sync - sync mode
241-
* @example
242-
* ```typescript
243-
* await writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace, sync);
244-
* ```
245-
*/
246-
export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync: boolean): MaybePromise<void>;
247-
/**
248-
* Enhanced write json file.
249-
*
250-
* @param path - file path
251-
* @param data - json object
252-
* @param mode - handle exists file (replace, copy, rename)
253-
* @param sync - sync mode
254-
* @example
255-
* ```typescript
256-
* await writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace);
257-
* ```
258-
*/
259-
export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync = false): MaybePromise<void> {
260-
logger.logMethodArgs?.('writeJsonFile', {path: path.slice(-32), mode, sync});
261-
return writeFile(path, flatString(jsonStringify(data)), mode, sync);
262-
}

0 commit comments

Comments
 (0)