atomically 1.7.0 → 2.0.1
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/dist/{consts.d.ts → constants.d.ts} +6 -4
- package/dist/constants.js +20 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +125 -106
- package/dist/types.d.ts +12 -12
- 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/{README.md → readme.md} +3 -3
- package/src/constants.ts +39 -0
- 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/consts.ts +0 -30
- 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.1",
|
|
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.4",
|
|
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.13.0",
|
|
34
|
+
"esbuild": "^0.17.7",
|
|
44
35
|
"require-inject": "^1.4.4",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"typescript": "^
|
|
48
|
-
"
|
|
49
|
-
"write-file-atomic": "^3.0.3"
|
|
36
|
+
"tap": "^16.3.4",
|
|
37
|
+
"tsex": "^2.1.0",
|
|
38
|
+
"typescript": "^4.9.5",
|
|
39
|
+
"write-file-atomic": "^5.0.0"
|
|
50
40
|
}
|
|
51
41
|
}
|
package/{README.md → readme.md}
RENAMED
|
@@ -8,7 +8,7 @@ Read and write files atomically and reliably.
|
|
|
8
8
|
- This library is a rewrite of [`write-file-atomic`](https://github.com/npm/write-file-atomic), with some important enhancements on top, you can largely use this as a drop-in replacement.
|
|
9
9
|
- This library is written in TypeScript, so types aren't an afterthought but come with library.
|
|
10
10
|
- This library is slightly faster than [`write-file-atomic`](https://github.com/npm/write-file-atomic), and it can be 10x faster, while being essentially just as safe, by using the `fsyncWait` option.
|
|
11
|
-
- This library has 0 dependencies, so there's less code to vet and the entire thing is roughly 20% smaller than [`write-file-atomic`](https://github.com/npm/write-file-atomic).
|
|
11
|
+
- This library has 0 third-party dependencies, so there's less code to vet and the entire thing is roughly 20% smaller than [`write-file-atomic`](https://github.com/npm/write-file-atomic).
|
|
12
12
|
- This library tries harder to write files on disk than [`write-file-atomic`](https://github.com/npm/write-file-atomic) does, by default retrying some failed operations and handling some more errors.
|
|
13
13
|
- Reliability:
|
|
14
14
|
- Reads are retried, when appropriate, until they succeed or the timeout is reached.
|
|
@@ -49,8 +49,8 @@ Read and write files atomically and reliably.
|
|
|
49
49
|
- `schedule`: it's a function that returns a promise that resolves to a disposer function, basically it allows you to provide some custom queueing logic for the writing operation, allowing you to perhaps wire `atomically` with your app's main filesystem job scheduler:
|
|
50
50
|
- even when a custom `schedule` function is provided write operations will still be queued internally by the library too.
|
|
51
51
|
- `timeout`: it allows you to specify the amount of maximum milliseconds within which the library will retry some failed operations:
|
|
52
|
-
- when writing asynchronously by default it will keep retrying for
|
|
53
|
-
- when writing synchronously by default it will keep retrying for
|
|
52
|
+
- when writing asynchronously by default it will keep retrying for 7500 milliseconds.
|
|
53
|
+
- when writing synchronously by default it will keep retrying for 1000 milliseconds.
|
|
54
54
|
- if `0` or `-1` no failed operations will be retried.
|
|
55
55
|
- if another number is provided that will be the timeout interval.
|
|
56
56
|
- `tmpCreate`: it's a function that will be used to create the custom temporary file path in place of the default one:
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
/* IMPORT */
|
|
3
|
+
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
|
|
7
|
+
/* MAIN */
|
|
8
|
+
|
|
9
|
+
const DEFAULT_ENCODING = 'utf8';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_FILE_MODE = 0o666;
|
|
12
|
+
|
|
13
|
+
const DEFAULT_FOLDER_MODE = 0o777;
|
|
14
|
+
|
|
15
|
+
const DEFAULT_READ_OPTIONS = {};
|
|
16
|
+
|
|
17
|
+
const DEFAULT_WRITE_OPTIONS = {};
|
|
18
|
+
|
|
19
|
+
const DEFAULT_USER_UID = os.userInfo ().uid;
|
|
20
|
+
|
|
21
|
+
const DEFAULT_USER_GID = os.userInfo ().gid;
|
|
22
|
+
|
|
23
|
+
const DEFAULT_TIMEOUT_ASYNC = 7500;
|
|
24
|
+
|
|
25
|
+
const DEFAULT_TIMEOUT_SYNC = 1000;
|
|
26
|
+
|
|
27
|
+
const IS_POSIX = !!process.getuid;
|
|
28
|
+
|
|
29
|
+
const IS_USER_ROOT = process.getuid ? !process.getuid () : false;
|
|
30
|
+
|
|
31
|
+
const LIMIT_BASENAME_LENGTH = 128; //TODO: Fetch the real limit from the filesystem //TODO: Fetch the whole-path length limit too
|
|
32
|
+
|
|
33
|
+
const LIMIT_FILES_DESCRIPTORS = 10_000; //TODO: Fetch the real limit from the filesystem
|
|
34
|
+
|
|
35
|
+
const NOOP = () => {};
|
|
36
|
+
|
|
37
|
+
/* EXPORT */
|
|
38
|
+
|
|
39
|
+
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
|
-
|
|
195
|
+
function 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];
|