atomically 1.7.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +0 -3
- package/README.md +3 -3
- package/dist/{consts.d.ts → constants.d.ts} +6 -4
- package/dist/constants.js +19 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +124 -105
- package/dist/types.d.ts +7 -7
- package/dist/types.js +2 -3
- package/dist/utils/lang.d.ts +6 -6
- package/dist/utils/lang.js +14 -14
- package/dist/utils/scheduler.d.ts +1 -1
- package/dist/utils/scheduler.js +4 -5
- package/dist/utils/temp.d.ts +1 -1
- package/dist/utils/temp.js +18 -15
- package/{LICENSE → license} +0 -0
- package/package.json +22 -32
- package/src/{consts.ts → constants.ts} +12 -4
- package/src/index.ts +153 -93
- package/src/types.ts +9 -9
- package/src/utils/lang.ts +18 -12
- package/src/utils/scheduler.ts +5 -3
- package/src/utils/temp.ts +18 -13
- package/tasks/benchmark.js +16 -12
- package/test/{basic.js → basic.cjs} +47 -49
- package/test/{concurrency.js → concurrency.cjs} +6 -8
- package/test/{integration.js → integration.cjs} +44 -46
- package/tsconfig.json +1 -26
- package/.nvmrc +0 -1
- package/dist/consts.js +0 -28
- package/dist/utils/attemptify.d.ts +0 -4
- package/dist/utils/attemptify.js +0 -25
- package/dist/utils/fs.d.ts +0 -34
- package/dist/utils/fs.js +0 -42
- package/dist/utils/fs_handlers.d.ts +0 -7
- package/dist/utils/fs_handlers.js +0 -28
- package/dist/utils/retryify.d.ts +0 -4
- package/dist/utils/retryify.js +0 -45
- package/dist/utils/retryify_queue.d.ts +0 -15
- package/dist/utils/retryify_queue.js +0 -58
- package/src/utils/attemptify.ts +0 -42
- package/src/utils/fs.ts +0 -51
- package/src/utils/fs_handlers.ts +0 -45
- package/src/utils/retryify.ts +0 -78
- package/src/utils/retryify_queue.ts +0 -95
package/package.json
CHANGED
|
@@ -1,30 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atomically",
|
|
3
|
+
"repository": "github:fabiospampinato/atomically",
|
|
3
4
|
"description": "Read and write files atomically and reliably.",
|
|
4
|
-
"version": "
|
|
5
|
+
"version": "2.0.0",
|
|
6
|
+
"type": "module",
|
|
5
7
|
"main": "dist/index.js",
|
|
6
|
-
"
|
|
8
|
+
"exports": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
7
10
|
"scripts": {
|
|
8
|
-
"benchmark": "
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"compile
|
|
12
|
-
"
|
|
13
|
-
"test:
|
|
11
|
+
"benchmark": "tsex benchmark",
|
|
12
|
+
"benchmarkLwatch": "tsex benchmark --watch",
|
|
13
|
+
"clean": "tsex clean",
|
|
14
|
+
"compile": "tsex compile",
|
|
15
|
+
"compile:watch": "tsex compile --watch",
|
|
16
|
+
"test:init": "esbuild --bundle --target=es2020 --platform=node --format=cjs src/index.ts > test/atomically.cjs",
|
|
17
|
+
"test": "npm run test:init && tap --no-check-coverage --no-coverage-report",
|
|
18
|
+
"test:watch": "npm run test:init && tap --no-check-coverage --no-coverage-report --watch",
|
|
14
19
|
"prepublishOnly": "npm run clean && npm run compile && npm run test"
|
|
15
20
|
},
|
|
16
|
-
"bugs": {
|
|
17
|
-
"url": "https://github.com/fabiospampinato/atomically/issues"
|
|
18
|
-
},
|
|
19
|
-
"license": "MIT",
|
|
20
|
-
"author": {
|
|
21
|
-
"name": "Fabio Spampinato",
|
|
22
|
-
"email": "spampinabio@gmail.com"
|
|
23
|
-
},
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "https://github.com/fabiospampinato/atomically.git"
|
|
27
|
-
},
|
|
28
21
|
"keywords": [
|
|
29
22
|
"atomic",
|
|
30
23
|
"read",
|
|
@@ -32,20 +25,17 @@
|
|
|
32
25
|
"file",
|
|
33
26
|
"reliable"
|
|
34
27
|
],
|
|
35
|
-
"
|
|
36
|
-
"
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"stubborn-fs": "^1.2.1",
|
|
30
|
+
"when-exit": "^2.0.0"
|
|
37
31
|
},
|
|
38
|
-
"dependencies": {},
|
|
39
32
|
"devDependencies": {
|
|
40
|
-
"@types/node": "^
|
|
41
|
-
"
|
|
42
|
-
"mkdirp": "^1.0.4",
|
|
43
|
-
"promise-resolve-timeout": "^1.2.1",
|
|
33
|
+
"@types/node": "^18.11.9",
|
|
34
|
+
"esbuild": "^0.15.13",
|
|
44
35
|
"require-inject": "^1.4.4",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"typescript": "^
|
|
48
|
-
"
|
|
49
|
-
"write-file-atomic": "^3.0.3"
|
|
36
|
+
"tap": "^16.3.0",
|
|
37
|
+
"tsex": "^1.1.2",
|
|
38
|
+
"typescript": "^4.8.4",
|
|
39
|
+
"write-file-atomic": "^5.0.0"
|
|
50
40
|
}
|
|
51
41
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
/*
|
|
2
|
+
/* IMPORT */
|
|
3
|
+
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
|
|
6
|
+
/* MAIN */
|
|
3
7
|
|
|
4
8
|
const DEFAULT_ENCODING = 'utf8';
|
|
5
9
|
|
|
@@ -11,9 +15,13 @@ const DEFAULT_READ_OPTIONS = {};
|
|
|
11
15
|
|
|
12
16
|
const DEFAULT_WRITE_OPTIONS = {};
|
|
13
17
|
|
|
14
|
-
const
|
|
18
|
+
const DEFAULT_USER_UID = os.userInfo ().uid;
|
|
19
|
+
|
|
20
|
+
const DEFAULT_USER_GID = os.userInfo ().gid;
|
|
21
|
+
|
|
22
|
+
const DEFAULT_TIMEOUT_ASYNC = 7500;
|
|
15
23
|
|
|
16
|
-
const DEFAULT_TIMEOUT_SYNC =
|
|
24
|
+
const DEFAULT_TIMEOUT_SYNC = 1000;
|
|
17
25
|
|
|
18
26
|
const IS_POSIX = !!process.getuid;
|
|
19
27
|
|
|
@@ -27,4 +35,4 @@ const NOOP = () => {};
|
|
|
27
35
|
|
|
28
36
|
/* EXPORT */
|
|
29
37
|
|
|
30
|
-
export {DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX, IS_USER_ROOT, LIMIT_BASENAME_LENGTH, LIMIT_FILES_DESCRIPTORS, NOOP};
|
|
38
|
+
export {DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_USER_UID, DEFAULT_USER_GID, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX, IS_USER_ROOT, LIMIT_BASENAME_LENGTH, LIMIT_FILES_DESCRIPTORS, NOOP};
|
package/src/index.ts
CHANGED
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
|
|
2
2
|
/* IMPORT */
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'stubborn-fs';
|
|
6
|
+
import {DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_USER_UID, DEFAULT_USER_GID, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX} from './constants';
|
|
7
|
+
import {isException, isFunction, isString, isUndefined} from './utils/lang';
|
|
8
8
|
import Scheduler from './utils/scheduler';
|
|
9
9
|
import Temp from './utils/temp';
|
|
10
|
-
import {Callback, Data, Disposer, Path, ReadOptions, WriteOptions} from './types';
|
|
10
|
+
import type {Callback, Data, Disposer, Encoding, Path, ReadOptions, WriteOptions} from './types';
|
|
11
11
|
|
|
12
|
-
/*
|
|
12
|
+
/* MAIN */
|
|
13
13
|
|
|
14
|
-
function readFile ( filePath: Path, options:
|
|
14
|
+
function readFile ( filePath: Path, options: Encoding | ReadOptions & { encoding: string } ): Promise<string>;
|
|
15
15
|
function readFile ( filePath: Path, options?: ReadOptions ): Promise<Buffer>;
|
|
16
|
-
function readFile ( filePath: Path, options:
|
|
16
|
+
function readFile ( filePath: Path, options: Encoding | ReadOptions = DEFAULT_READ_OPTIONS ): Promise<Buffer | string> {
|
|
17
17
|
|
|
18
|
-
if (
|
|
18
|
+
if ( isString ( options ) ) return readFile ( filePath, { encoding: options } );
|
|
19
19
|
|
|
20
|
-
const timeout = Date.now () + ( options.timeout ?? DEFAULT_TIMEOUT_ASYNC );
|
|
20
|
+
const timeout = Date.now () + ( ( options.timeout ?? DEFAULT_TIMEOUT_ASYNC ) || -1 );
|
|
21
21
|
|
|
22
|
-
return
|
|
22
|
+
return fs.retry.readFile ( timeout )( filePath, options );
|
|
23
23
|
|
|
24
|
-
}
|
|
24
|
+
}
|
|
25
25
|
|
|
26
|
-
function readFileSync ( filePath: Path, options:
|
|
26
|
+
function readFileSync ( filePath: Path, options: Encoding | ReadOptions & { encoding: string } ): string;
|
|
27
27
|
function readFileSync ( filePath: Path, options?: ReadOptions ): Buffer;
|
|
28
|
-
function readFileSync ( filePath: Path, options:
|
|
28
|
+
function readFileSync ( filePath: Path, options: Encoding | ReadOptions = DEFAULT_READ_OPTIONS ): Buffer | string {
|
|
29
29
|
|
|
30
|
-
if (
|
|
30
|
+
if ( isString ( options ) ) return readFileSync ( filePath, { encoding: options } );
|
|
31
31
|
|
|
32
|
-
const timeout = Date.now () + ( options.timeout ?? DEFAULT_TIMEOUT_SYNC );
|
|
32
|
+
const timeout = Date.now () + ( ( options.timeout ?? DEFAULT_TIMEOUT_SYNC ) || -1 );
|
|
33
33
|
|
|
34
|
-
return
|
|
34
|
+
return fs.retry.readFileSync ( timeout )( filePath, options );
|
|
35
35
|
|
|
36
|
-
}
|
|
36
|
+
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
function writeFile ( filePath: Path, data: Data, callback?: Callback ): Promise<void>;
|
|
39
|
+
function writeFile ( filePath: Path, data: Data, options?: Encoding | WriteOptions, callback?: Callback ): Promise<void>;
|
|
40
|
+
function writeFile ( filePath: Path, data: Data, options?: Encoding | WriteOptions | Callback, callback?: Callback ): Promise<void> {
|
|
39
41
|
|
|
40
|
-
if (
|
|
42
|
+
if ( isFunction ( options ) ) return writeFile ( filePath, data, DEFAULT_WRITE_OPTIONS, options );
|
|
41
43
|
|
|
42
44
|
const promise = writeFileAsync ( filePath, data, options );
|
|
43
45
|
|
|
@@ -45,19 +47,19 @@ const writeFile = ( filePath: Path, data: Data, options?: string | WriteOptions
|
|
|
45
47
|
|
|
46
48
|
return promise;
|
|
47
49
|
|
|
48
|
-
}
|
|
50
|
+
}
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
async function writeFileAsync ( filePath: Path, data: Data, options: Encoding | WriteOptions = DEFAULT_WRITE_OPTIONS ): Promise<void> {
|
|
51
53
|
|
|
52
|
-
if (
|
|
54
|
+
if ( isString ( options ) ) return writeFileAsync ( filePath, data, { encoding: options } );
|
|
53
55
|
|
|
54
|
-
const timeout = Date.now () + ( options.timeout ?? DEFAULT_TIMEOUT_ASYNC );
|
|
56
|
+
const timeout = Date.now () + ( ( options.timeout ?? DEFAULT_TIMEOUT_ASYNC ) || -1 );
|
|
55
57
|
|
|
56
|
-
let schedulerCustomDisposer: Disposer | null = null
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
let schedulerCustomDisposer: Disposer | null = null;
|
|
59
|
+
let schedulerDisposer: Disposer | null = null;
|
|
60
|
+
let tempDisposer: Disposer | null = null;
|
|
61
|
+
let tempPath: string | null = null;
|
|
62
|
+
let fd: number | null = null;
|
|
61
63
|
|
|
62
64
|
try {
|
|
63
65
|
|
|
@@ -65,47 +67,66 @@ const writeFileAsync = async ( filePath: Path, data: Data, options: string | Wri
|
|
|
65
67
|
|
|
66
68
|
schedulerDisposer = await Scheduler.schedule ( filePath );
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
const filePathReal = await fs.attempt.realpath ( filePath );
|
|
71
|
+
const filePathExists = !!filePathReal;
|
|
72
|
+
|
|
73
|
+
filePath = filePathReal || filePath;
|
|
69
74
|
|
|
70
75
|
[tempPath, tempDisposer] = Temp.get ( filePath, options.tmpCreate || Temp.create, !( options.tmpPurge === false ) );
|
|
71
76
|
|
|
72
|
-
const useStatChown = IS_POSIX &&
|
|
73
|
-
|
|
77
|
+
const useStatChown = IS_POSIX && isUndefined ( options.chown );
|
|
78
|
+
const useStatMode = isUndefined ( options.mode );
|
|
74
79
|
|
|
75
|
-
if ( useStatChown || useStatMode ) {
|
|
80
|
+
if ( filePathExists && ( useStatChown || useStatMode ) ) {
|
|
76
81
|
|
|
77
|
-
const
|
|
82
|
+
const stats = await fs.attempt.stat ( filePath );
|
|
78
83
|
|
|
79
|
-
if (
|
|
84
|
+
if ( stats ) {
|
|
80
85
|
|
|
81
86
|
options = { ...options };
|
|
82
87
|
|
|
83
|
-
if ( useStatChown )
|
|
88
|
+
if ( useStatChown ) {
|
|
89
|
+
|
|
90
|
+
options.chown = { uid: stats.uid, gid: stats.gid };
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if ( useStatMode ) {
|
|
95
|
+
|
|
96
|
+
options.mode = stats.mode;
|
|
84
97
|
|
|
85
|
-
|
|
98
|
+
}
|
|
86
99
|
|
|
87
100
|
}
|
|
88
101
|
|
|
89
102
|
}
|
|
90
103
|
|
|
91
|
-
|
|
104
|
+
if ( !filePathExists ) {
|
|
105
|
+
|
|
106
|
+
const parentPath = path.dirname ( filePath );
|
|
107
|
+
|
|
108
|
+
await fs.attempt.mkdir ( parentPath, {
|
|
109
|
+
mode: DEFAULT_FOLDER_MODE,
|
|
110
|
+
recursive: true
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fd = await fs.retry.open ( timeout )( tempPath, 'w', options.mode || DEFAULT_FILE_MODE );
|
|
92
116
|
|
|
93
|
-
|
|
94
|
-
mode: DEFAULT_FOLDER_MODE,
|
|
95
|
-
recursive: true
|
|
96
|
-
});
|
|
117
|
+
if ( options.tmpCreated ) {
|
|
97
118
|
|
|
98
|
-
|
|
119
|
+
options.tmpCreated ( tempPath );
|
|
99
120
|
|
|
100
|
-
|
|
121
|
+
}
|
|
101
122
|
|
|
102
|
-
if (
|
|
123
|
+
if ( isString ( data ) ) {
|
|
103
124
|
|
|
104
|
-
await
|
|
125
|
+
await fs.retry.write ( timeout )( fd, data, 0, options.encoding || DEFAULT_ENCODING );
|
|
105
126
|
|
|
106
|
-
} else if ( !
|
|
127
|
+
} else if ( !isUndefined ( data ) ) {
|
|
107
128
|
|
|
108
|
-
await
|
|
129
|
+
await fs.retry.write ( timeout )( fd, data, 0, data.length, 0 );
|
|
109
130
|
|
|
110
131
|
}
|
|
111
132
|
|
|
@@ -113,33 +134,43 @@ const writeFileAsync = async ( filePath: Path, data: Data, options: string | Wri
|
|
|
113
134
|
|
|
114
135
|
if ( options.fsyncWait !== false ) {
|
|
115
136
|
|
|
116
|
-
await
|
|
137
|
+
await fs.retry.fsync ( timeout )( fd );
|
|
117
138
|
|
|
118
139
|
} else {
|
|
119
140
|
|
|
120
|
-
|
|
141
|
+
fs.attempt.fsync ( fd );
|
|
121
142
|
|
|
122
143
|
}
|
|
123
144
|
|
|
124
145
|
}
|
|
125
146
|
|
|
126
|
-
await
|
|
147
|
+
await fs.retry.close ( timeout )( fd );
|
|
127
148
|
|
|
128
149
|
fd = null;
|
|
129
150
|
|
|
130
|
-
if ( options.chown
|
|
151
|
+
if ( options.chown && ( options.chown.uid !== DEFAULT_USER_UID || options.chown.gid !== DEFAULT_USER_GID ) ) {
|
|
152
|
+
|
|
153
|
+
await fs.attempt.chown ( tempPath, options.chown.uid, options.chown.gid );
|
|
154
|
+
|
|
155
|
+
}
|
|
131
156
|
|
|
132
|
-
if ( options.mode
|
|
157
|
+
if ( options.mode && options.mode !== DEFAULT_FILE_MODE ) {
|
|
158
|
+
|
|
159
|
+
await fs.attempt.chmod ( tempPath, options.mode );
|
|
160
|
+
|
|
161
|
+
}
|
|
133
162
|
|
|
134
163
|
try {
|
|
135
164
|
|
|
136
|
-
await
|
|
165
|
+
await fs.retry.rename ( timeout )( tempPath, filePath );
|
|
166
|
+
|
|
167
|
+
} catch ( error: unknown ) {
|
|
137
168
|
|
|
138
|
-
|
|
169
|
+
if ( !isException ( error ) ) throw error;
|
|
139
170
|
|
|
140
171
|
if ( error.code !== 'ENAMETOOLONG' ) throw error;
|
|
141
172
|
|
|
142
|
-
await
|
|
173
|
+
await fs.retry.rename ( timeout )( tempPath, Temp.truncate ( filePath ) );
|
|
143
174
|
|
|
144
175
|
}
|
|
145
176
|
|
|
@@ -149,7 +180,7 @@ const writeFileAsync = async ( filePath: Path, data: Data, options: string | Wri
|
|
|
149
180
|
|
|
150
181
|
} finally {
|
|
151
182
|
|
|
152
|
-
if ( fd ) await
|
|
183
|
+
if ( fd ) await fs.attempt.close ( fd );
|
|
153
184
|
|
|
154
185
|
if ( tempPath ) Temp.purge ( tempPath );
|
|
155
186
|
|
|
@@ -159,61 +190,80 @@ const writeFileAsync = async ( filePath: Path, data: Data, options: string | Wri
|
|
|
159
190
|
|
|
160
191
|
}
|
|
161
192
|
|
|
162
|
-
}
|
|
193
|
+
}
|
|
163
194
|
|
|
164
|
-
const writeFileSync = ( filePath: Path, data: Data, options:
|
|
195
|
+
const writeFileSync = ( filePath: Path, data: Data, options: Encoding | WriteOptions = DEFAULT_WRITE_OPTIONS ): void => {
|
|
165
196
|
|
|
166
|
-
if (
|
|
197
|
+
if ( isString ( options ) ) return writeFileSync ( filePath, data, { encoding: options } );
|
|
167
198
|
|
|
168
|
-
const timeout = Date.now () + ( options.timeout ?? DEFAULT_TIMEOUT_SYNC );
|
|
199
|
+
const timeout = Date.now () + ( ( options.timeout ?? DEFAULT_TIMEOUT_SYNC ) || -1 );
|
|
169
200
|
|
|
170
|
-
let tempDisposer: Disposer | null = null
|
|
171
|
-
|
|
172
|
-
|
|
201
|
+
let tempDisposer: Disposer | null = null;
|
|
202
|
+
let tempPath: string | null = null;
|
|
203
|
+
let fd: number | null = null;
|
|
173
204
|
|
|
174
205
|
try {
|
|
175
206
|
|
|
176
|
-
|
|
207
|
+
const filePathReal = fs.attempt.realpathSync ( filePath );
|
|
208
|
+
const filePathExists = !!filePathReal;
|
|
209
|
+
|
|
210
|
+
filePath = filePathReal || filePath;
|
|
177
211
|
|
|
178
212
|
[tempPath, tempDisposer] = Temp.get ( filePath, options.tmpCreate || Temp.create, !( options.tmpPurge === false ) );
|
|
179
213
|
|
|
180
|
-
const useStatChown = IS_POSIX &&
|
|
181
|
-
|
|
214
|
+
const useStatChown = IS_POSIX && isUndefined ( options.chown );
|
|
215
|
+
const useStatMode = isUndefined ( options.mode );
|
|
182
216
|
|
|
183
|
-
if ( useStatChown || useStatMode ) {
|
|
217
|
+
if ( filePathExists && ( useStatChown || useStatMode ) ) {
|
|
184
218
|
|
|
185
|
-
const
|
|
219
|
+
const stats = fs.attempt.statSync ( filePath );
|
|
186
220
|
|
|
187
|
-
if (
|
|
221
|
+
if ( stats ) {
|
|
188
222
|
|
|
189
223
|
options = { ...options };
|
|
190
224
|
|
|
191
|
-
if ( useStatChown )
|
|
225
|
+
if ( useStatChown ) {
|
|
226
|
+
|
|
227
|
+
options.chown = { uid: stats.uid, gid: stats.gid };
|
|
228
|
+
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if ( useStatMode ) {
|
|
232
|
+
|
|
233
|
+
options.mode = stats.mode;
|
|
192
234
|
|
|
193
|
-
|
|
235
|
+
}
|
|
194
236
|
|
|
195
237
|
}
|
|
196
238
|
|
|
197
239
|
}
|
|
198
240
|
|
|
199
|
-
|
|
241
|
+
if ( !filePathExists ) {
|
|
242
|
+
|
|
243
|
+
const parentPath = path.dirname ( filePath );
|
|
244
|
+
|
|
245
|
+
fs.attempt.mkdirSync ( parentPath, {
|
|
246
|
+
mode: DEFAULT_FOLDER_MODE,
|
|
247
|
+
recursive: true
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fd = fs.retry.openSync ( timeout )( tempPath, 'w', options.mode || DEFAULT_FILE_MODE );
|
|
200
253
|
|
|
201
|
-
|
|
202
|
-
mode: DEFAULT_FOLDER_MODE,
|
|
203
|
-
recursive: true
|
|
204
|
-
});
|
|
254
|
+
if ( options.tmpCreated ) {
|
|
205
255
|
|
|
206
|
-
|
|
256
|
+
options.tmpCreated ( tempPath );
|
|
207
257
|
|
|
208
|
-
|
|
258
|
+
}
|
|
209
259
|
|
|
210
|
-
if (
|
|
260
|
+
if ( isString ( data ) ) {
|
|
211
261
|
|
|
212
|
-
|
|
262
|
+
fs.retry.writeSync ( timeout )( fd, data, 0, options.encoding || DEFAULT_ENCODING );
|
|
213
263
|
|
|
214
|
-
} else if ( !
|
|
264
|
+
} else if ( !isUndefined ( data ) ) {
|
|
215
265
|
|
|
216
|
-
|
|
266
|
+
fs.retry.writeSync ( timeout )( fd, data, 0, data.length, 0 );
|
|
217
267
|
|
|
218
268
|
}
|
|
219
269
|
|
|
@@ -221,33 +271,43 @@ const writeFileSync = ( filePath: Path, data: Data, options: string | WriteOptio
|
|
|
221
271
|
|
|
222
272
|
if ( options.fsyncWait !== false ) {
|
|
223
273
|
|
|
224
|
-
|
|
274
|
+
fs.retry.fsyncSync ( timeout )( fd );
|
|
225
275
|
|
|
226
276
|
} else {
|
|
227
277
|
|
|
228
|
-
|
|
278
|
+
fs.attempt.fsync ( fd );
|
|
229
279
|
|
|
230
280
|
}
|
|
231
281
|
|
|
232
282
|
}
|
|
233
283
|
|
|
234
|
-
|
|
284
|
+
fs.retry.closeSync ( timeout )( fd );
|
|
235
285
|
|
|
236
286
|
fd = null;
|
|
237
287
|
|
|
238
|
-
if ( options.chown
|
|
288
|
+
if ( options.chown && ( options.chown.uid !== DEFAULT_USER_UID || options.chown.gid !== DEFAULT_USER_GID ) ) {
|
|
289
|
+
|
|
290
|
+
fs.attempt.chownSync ( tempPath, options.chown.uid, options.chown.gid );
|
|
291
|
+
|
|
292
|
+
}
|
|
239
293
|
|
|
240
|
-
if ( options.mode
|
|
294
|
+
if ( options.mode && options.mode !== DEFAULT_FILE_MODE ) {
|
|
295
|
+
|
|
296
|
+
fs.attempt.chmodSync ( tempPath, options.mode );
|
|
297
|
+
|
|
298
|
+
}
|
|
241
299
|
|
|
242
300
|
try {
|
|
243
301
|
|
|
244
|
-
|
|
302
|
+
fs.retry.renameSync ( timeout )( tempPath, filePath );
|
|
303
|
+
|
|
304
|
+
} catch ( error: unknown ) {
|
|
245
305
|
|
|
246
|
-
|
|
306
|
+
if ( !isException ( error ) ) throw error;
|
|
247
307
|
|
|
248
308
|
if ( error.code !== 'ENAMETOOLONG' ) throw error;
|
|
249
309
|
|
|
250
|
-
|
|
310
|
+
fs.retry.renameSync ( timeout )( tempPath, Temp.truncate ( filePath ) );
|
|
251
311
|
|
|
252
312
|
}
|
|
253
313
|
|
|
@@ -257,13 +317,13 @@ const writeFileSync = ( filePath: Path, data: Data, options: string | WriteOptio
|
|
|
257
317
|
|
|
258
318
|
} finally {
|
|
259
319
|
|
|
260
|
-
if ( fd )
|
|
320
|
+
if ( fd ) fs.attempt.closeSync ( fd );
|
|
261
321
|
|
|
262
322
|
if ( tempPath ) Temp.purge ( tempPath );
|
|
263
323
|
|
|
264
324
|
}
|
|
265
325
|
|
|
266
|
-
}
|
|
326
|
+
}
|
|
267
327
|
|
|
268
328
|
/* EXPORT */
|
|
269
329
|
|
package/src/types.ts
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
|
|
2
|
-
/*
|
|
2
|
+
/* MAIN */
|
|
3
3
|
|
|
4
|
-
type Callback = ( error: Exception | void ) =>
|
|
4
|
+
type Callback = ( error: Exception | void ) => void;
|
|
5
5
|
|
|
6
|
-
type Data =
|
|
6
|
+
type Data = Uint8Array | string | undefined;
|
|
7
7
|
|
|
8
8
|
type Disposer = () => void;
|
|
9
9
|
|
|
10
|
-
type
|
|
10
|
+
type Encoding = 'ascii' | 'base64' | 'binary' | 'hex' | 'latin1' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2';
|
|
11
11
|
|
|
12
|
-
type
|
|
12
|
+
type Exception = NodeJS.ErrnoException;
|
|
13
13
|
|
|
14
14
|
type Path = string;
|
|
15
15
|
|
|
16
16
|
type ReadOptions = {
|
|
17
|
-
encoding?:
|
|
17
|
+
encoding?: Encoding | null,
|
|
18
18
|
mode?: string | number | false,
|
|
19
19
|
timeout?: number
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
type WriteOptions = {
|
|
23
23
|
chown?: { gid: number, uid: number } | false,
|
|
24
|
-
encoding?:
|
|
24
|
+
encoding?: Encoding | null,
|
|
25
25
|
fsync?: boolean,
|
|
26
26
|
fsyncWait?: boolean,
|
|
27
27
|
mode?: string | number | false,
|
|
28
28
|
schedule?: ( filePath: string ) => Promise<Disposer>,
|
|
29
29
|
timeout?: number,
|
|
30
30
|
tmpCreate?: ( filePath: string ) => string,
|
|
31
|
-
tmpCreated?: ( filePath: string ) =>
|
|
31
|
+
tmpCreated?: ( filePath: string ) => void,
|
|
32
32
|
tmpPurge?: boolean
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
/* EXPORT */
|
|
36
36
|
|
|
37
|
-
export {Callback, Data, Disposer,
|
|
37
|
+
export type {Callback, Data, Disposer, Encoding, Exception, Path, ReadOptions, WriteOptions};
|
package/src/utils/lang.ts
CHANGED
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
|
|
2
|
-
/*
|
|
2
|
+
/* IMPORT */
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import type {Exception} from '../types';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/* MAIN */
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const isException = ( value: unknown ): value is Exception => {
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
return ( value instanceof Error ) && ( 'code' in value );
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const isFunction = ( value: unknown ): value is Function => {
|
|
15
|
+
|
|
16
|
+
return ( typeof value === 'function' );
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const isString = ( value: unknown ): value is string => {
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
return ( typeof value === 'string' );
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
};
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
const isUndefined = ( value: unknown ): value is undefined => {
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
return ( value === undefined );
|
|
23
29
|
|
|
24
30
|
};
|
|
25
31
|
|
|
26
32
|
/* EXPORT */
|
|
27
33
|
|
|
28
|
-
export
|
|
34
|
+
export {isException, isFunction, isString, isUndefined};
|
package/src/utils/scheduler.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
|
|
2
2
|
/* IMPORT */
|
|
3
3
|
|
|
4
|
-
import {Disposer} from '../types';
|
|
4
|
+
import type {Disposer} from '../types';
|
|
5
5
|
|
|
6
|
-
/*
|
|
6
|
+
/* HELPERS */
|
|
7
7
|
|
|
8
8
|
const Queues: Record<string, Function[] | undefined> = {};
|
|
9
9
|
|
|
10
|
-
/*
|
|
10
|
+
/* MAIN */
|
|
11
11
|
|
|
12
12
|
//TODO: Maybe publish this as a standalone package
|
|
13
13
|
|
|
14
14
|
const Scheduler = {
|
|
15
15
|
|
|
16
|
+
/* API */
|
|
17
|
+
|
|
16
18
|
next: ( id: string ): void => {
|
|
17
19
|
|
|
18
20
|
const queue = Queues[id];
|