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/.editorconfig
CHANGED
package/README.md
CHANGED
|
@@ -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:
|
|
@@ -3,11 +3,13 @@ declare const DEFAULT_FILE_MODE = 438;
|
|
|
3
3
|
declare const DEFAULT_FOLDER_MODE = 511;
|
|
4
4
|
declare const DEFAULT_READ_OPTIONS: {};
|
|
5
5
|
declare const DEFAULT_WRITE_OPTIONS: {};
|
|
6
|
-
declare const
|
|
7
|
-
declare const
|
|
8
|
-
declare const
|
|
6
|
+
declare const DEFAULT_USER_UID: number;
|
|
7
|
+
declare const DEFAULT_USER_GID: number;
|
|
8
|
+
declare const DEFAULT_TIMEOUT_ASYNC = 7500;
|
|
9
|
+
declare const DEFAULT_TIMEOUT_SYNC = 1000;
|
|
10
|
+
declare const IS_POSIX: boolean;
|
|
9
11
|
declare const IS_USER_ROOT: boolean;
|
|
10
12
|
declare const LIMIT_BASENAME_LENGTH = 128;
|
|
11
13
|
declare const LIMIT_FILES_DESCRIPTORS = 10000;
|
|
12
14
|
declare const NOOP: () => void;
|
|
13
|
-
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 };
|
|
15
|
+
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 };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* IMPORT */
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
/* MAIN */
|
|
4
|
+
const DEFAULT_ENCODING = 'utf8';
|
|
5
|
+
const DEFAULT_FILE_MODE = 0o666;
|
|
6
|
+
const DEFAULT_FOLDER_MODE = 0o777;
|
|
7
|
+
const DEFAULT_READ_OPTIONS = {};
|
|
8
|
+
const DEFAULT_WRITE_OPTIONS = {};
|
|
9
|
+
const DEFAULT_USER_UID = os.userInfo().uid;
|
|
10
|
+
const DEFAULT_USER_GID = os.userInfo().gid;
|
|
11
|
+
const DEFAULT_TIMEOUT_ASYNC = 7500;
|
|
12
|
+
const DEFAULT_TIMEOUT_SYNC = 1000;
|
|
13
|
+
const IS_POSIX = !!process.getuid;
|
|
14
|
+
const IS_USER_ROOT = process.getuid ? !process.getuid() : false;
|
|
15
|
+
const LIMIT_BASENAME_LENGTH = 128; //TODO: fetch the real limit from the filesystem //TODO: fetch the whole-path length limit too
|
|
16
|
+
const LIMIT_FILES_DESCRIPTORS = 10000; //TODO: fetch the real limit from the filesystem
|
|
17
|
+
const NOOP = () => { };
|
|
18
|
+
/* EXPORT */
|
|
19
|
+
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/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { Callback, Data, Path, ReadOptions, WriteOptions } from './types';
|
|
3
|
-
declare function readFile(filePath: Path, options:
|
|
2
|
+
import type { Callback, Data, Encoding, Path, ReadOptions, WriteOptions } from './types';
|
|
3
|
+
declare function readFile(filePath: Path, options: Encoding | ReadOptions & {
|
|
4
4
|
encoding: string;
|
|
5
5
|
}): Promise<string>;
|
|
6
6
|
declare function readFile(filePath: Path, options?: ReadOptions): Promise<Buffer>;
|
|
7
|
-
declare function readFileSync(filePath: Path, options:
|
|
7
|
+
declare function readFileSync(filePath: Path, options: Encoding | ReadOptions & {
|
|
8
8
|
encoding: string;
|
|
9
9
|
}): string;
|
|
10
10
|
declare function readFileSync(filePath: Path, options?: ReadOptions): Buffer;
|
|
11
|
-
declare
|
|
12
|
-
declare
|
|
11
|
+
declare function writeFile(filePath: Path, data: Data, callback?: Callback): Promise<void>;
|
|
12
|
+
declare function writeFile(filePath: Path, data: Data, options?: Encoding | WriteOptions, callback?: Callback): Promise<void>;
|
|
13
|
+
declare const writeFileSync: (filePath: Path, data: Data, options?: Encoding | WriteOptions) => void;
|
|
13
14
|
export { readFile, readFileSync, writeFile, writeFileSync };
|
package/dist/index.js
CHANGED
|
@@ -1,177 +1,196 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/* IMPORT */
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function readFile(filePath, options = consts_1.DEFAULT_READ_OPTIONS) {
|
|
12
|
-
var _a;
|
|
13
|
-
if (lang_1.default.isString(options))
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'stubborn-fs';
|
|
4
|
+
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.js';
|
|
5
|
+
import { isException, isFunction, isString, isUndefined } from './utils/lang.js';
|
|
6
|
+
import Scheduler from './utils/scheduler.js';
|
|
7
|
+
import Temp from './utils/temp.js';
|
|
8
|
+
function readFile(filePath, options = DEFAULT_READ_OPTIONS) {
|
|
9
|
+
if (isString(options))
|
|
14
10
|
return readFile(filePath, { encoding: options });
|
|
15
|
-
const timeout = Date.now() + ((
|
|
16
|
-
return
|
|
11
|
+
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_ASYNC) || -1);
|
|
12
|
+
return fs.retry.readFile(timeout)(filePath, options);
|
|
17
13
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
function readFileSync(filePath, options = consts_1.DEFAULT_READ_OPTIONS) {
|
|
21
|
-
var _a;
|
|
22
|
-
if (lang_1.default.isString(options))
|
|
14
|
+
function readFileSync(filePath, options = DEFAULT_READ_OPTIONS) {
|
|
15
|
+
if (isString(options))
|
|
23
16
|
return readFileSync(filePath, { encoding: options });
|
|
24
|
-
const timeout = Date.now() + ((
|
|
25
|
-
return
|
|
17
|
+
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_SYNC) || -1);
|
|
18
|
+
return fs.retry.readFileSync(timeout)(filePath, options);
|
|
26
19
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (lang_1.default.isFunction(options))
|
|
31
|
-
return writeFile(filePath, data, consts_1.DEFAULT_WRITE_OPTIONS, options);
|
|
20
|
+
function writeFile(filePath, data, options, callback) {
|
|
21
|
+
if (isFunction(options))
|
|
22
|
+
return writeFile(filePath, data, DEFAULT_WRITE_OPTIONS, options);
|
|
32
23
|
const promise = writeFileAsync(filePath, data, options);
|
|
33
24
|
if (callback)
|
|
34
25
|
promise.then(callback, callback);
|
|
35
26
|
return promise;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
var _a;
|
|
40
|
-
if (lang_1.default.isString(options))
|
|
27
|
+
}
|
|
28
|
+
async function writeFileAsync(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
|
|
29
|
+
if (isString(options))
|
|
41
30
|
return writeFileAsync(filePath, data, { encoding: options });
|
|
42
|
-
const timeout = Date.now() + ((
|
|
43
|
-
let schedulerCustomDisposer = null
|
|
31
|
+
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_ASYNC) || -1);
|
|
32
|
+
let schedulerCustomDisposer = null;
|
|
33
|
+
let schedulerDisposer = null;
|
|
34
|
+
let tempDisposer = null;
|
|
35
|
+
let tempPath = null;
|
|
36
|
+
let fd = null;
|
|
44
37
|
try {
|
|
45
38
|
if (options.schedule)
|
|
46
39
|
schedulerCustomDisposer = await options.schedule(filePath);
|
|
47
|
-
schedulerDisposer = await
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
schedulerDisposer = await Scheduler.schedule(filePath);
|
|
41
|
+
const filePathReal = await fs.attempt.realpath(filePath);
|
|
42
|
+
const filePathExists = !!filePathReal;
|
|
43
|
+
filePath = filePathReal || filePath;
|
|
44
|
+
[tempPath, tempDisposer] = Temp.get(filePath, options.tmpCreate || Temp.create, !(options.tmpPurge === false));
|
|
45
|
+
const useStatChown = IS_POSIX && isUndefined(options.chown);
|
|
46
|
+
const useStatMode = isUndefined(options.mode);
|
|
47
|
+
if (filePathExists && (useStatChown || useStatMode)) {
|
|
48
|
+
const stats = await fs.attempt.stat(filePath);
|
|
49
|
+
if (stats) {
|
|
54
50
|
options = { ...options };
|
|
55
|
-
if (useStatChown)
|
|
56
|
-
options.chown = { uid:
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
if (useStatChown) {
|
|
52
|
+
options.chown = { uid: stats.uid, gid: stats.gid };
|
|
53
|
+
}
|
|
54
|
+
if (useStatMode) {
|
|
55
|
+
options.mode = stats.mode;
|
|
56
|
+
}
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
if (!filePathExists) {
|
|
60
|
+
const parentPath = path.dirname(filePath);
|
|
61
|
+
await fs.attempt.mkdir(parentPath, {
|
|
62
|
+
mode: DEFAULT_FOLDER_MODE,
|
|
63
|
+
recursive: true
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
fd = await fs.retry.open(timeout)(tempPath, 'w', options.mode || DEFAULT_FILE_MODE);
|
|
67
|
+
if (options.tmpCreated) {
|
|
68
68
|
options.tmpCreated(tempPath);
|
|
69
|
-
if (lang_1.default.isString(data)) {
|
|
70
|
-
await fs_1.default.writeRetry(timeout)(fd, data, 0, options.encoding || consts_1.DEFAULT_ENCODING);
|
|
71
69
|
}
|
|
72
|
-
|
|
73
|
-
await
|
|
70
|
+
if (isString(data)) {
|
|
71
|
+
await fs.retry.write(timeout)(fd, data, 0, options.encoding || DEFAULT_ENCODING);
|
|
72
|
+
}
|
|
73
|
+
else if (!isUndefined(data)) {
|
|
74
|
+
await fs.retry.write(timeout)(fd, data, 0, data.length, 0);
|
|
74
75
|
}
|
|
75
76
|
if (options.fsync !== false) {
|
|
76
77
|
if (options.fsyncWait !== false) {
|
|
77
|
-
await
|
|
78
|
+
await fs.retry.fsync(timeout)(fd);
|
|
78
79
|
}
|
|
79
80
|
else {
|
|
80
|
-
|
|
81
|
+
fs.attempt.fsync(fd);
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
|
-
await
|
|
84
|
+
await fs.retry.close(timeout)(fd);
|
|
84
85
|
fd = null;
|
|
85
|
-
if (options.chown)
|
|
86
|
-
await
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
if (options.chown && (options.chown.uid !== DEFAULT_USER_UID || options.chown.gid !== DEFAULT_USER_GID)) {
|
|
87
|
+
await fs.attempt.chown(tempPath, options.chown.uid, options.chown.gid);
|
|
88
|
+
}
|
|
89
|
+
if (options.mode && options.mode !== DEFAULT_FILE_MODE) {
|
|
90
|
+
await fs.attempt.chmod(tempPath, options.mode);
|
|
91
|
+
}
|
|
89
92
|
try {
|
|
90
|
-
await
|
|
93
|
+
await fs.retry.rename(timeout)(tempPath, filePath);
|
|
91
94
|
}
|
|
92
95
|
catch (error) {
|
|
96
|
+
if (!isException(error))
|
|
97
|
+
throw error;
|
|
93
98
|
if (error.code !== 'ENAMETOOLONG')
|
|
94
99
|
throw error;
|
|
95
|
-
await
|
|
100
|
+
await fs.retry.rename(timeout)(tempPath, Temp.truncate(filePath));
|
|
96
101
|
}
|
|
97
102
|
tempDisposer();
|
|
98
103
|
tempPath = null;
|
|
99
104
|
}
|
|
100
105
|
finally {
|
|
101
106
|
if (fd)
|
|
102
|
-
await
|
|
107
|
+
await fs.attempt.close(fd);
|
|
103
108
|
if (tempPath)
|
|
104
|
-
|
|
109
|
+
Temp.purge(tempPath);
|
|
105
110
|
if (schedulerCustomDisposer)
|
|
106
111
|
schedulerCustomDisposer();
|
|
107
112
|
if (schedulerDisposer)
|
|
108
113
|
schedulerDisposer();
|
|
109
114
|
}
|
|
110
|
-
}
|
|
111
|
-
const writeFileSync = (filePath, data, options =
|
|
112
|
-
|
|
113
|
-
if (lang_1.default.isString(options))
|
|
115
|
+
}
|
|
116
|
+
const writeFileSync = (filePath, data, options = DEFAULT_WRITE_OPTIONS) => {
|
|
117
|
+
if (isString(options))
|
|
114
118
|
return writeFileSync(filePath, data, { encoding: options });
|
|
115
|
-
const timeout = Date.now() + ((
|
|
116
|
-
let tempDisposer = null
|
|
119
|
+
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_SYNC) || -1);
|
|
120
|
+
let tempDisposer = null;
|
|
121
|
+
let tempPath = null;
|
|
122
|
+
let fd = null;
|
|
117
123
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
const filePathReal = fs.attempt.realpathSync(filePath);
|
|
125
|
+
const filePathExists = !!filePathReal;
|
|
126
|
+
filePath = filePathReal || filePath;
|
|
127
|
+
[tempPath, tempDisposer] = Temp.get(filePath, options.tmpCreate || Temp.create, !(options.tmpPurge === false));
|
|
128
|
+
const useStatChown = IS_POSIX && isUndefined(options.chown);
|
|
129
|
+
const useStatMode = isUndefined(options.mode);
|
|
130
|
+
if (filePathExists && (useStatChown || useStatMode)) {
|
|
131
|
+
const stats = fs.attempt.statSync(filePath);
|
|
132
|
+
if (stats) {
|
|
124
133
|
options = { ...options };
|
|
125
|
-
if (useStatChown)
|
|
126
|
-
options.chown = { uid:
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
if (useStatChown) {
|
|
135
|
+
options.chown = { uid: stats.uid, gid: stats.gid };
|
|
136
|
+
}
|
|
137
|
+
if (useStatMode) {
|
|
138
|
+
options.mode = stats.mode;
|
|
139
|
+
}
|
|
129
140
|
}
|
|
130
141
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
if (!filePathExists) {
|
|
143
|
+
const parentPath = path.dirname(filePath);
|
|
144
|
+
fs.attempt.mkdirSync(parentPath, {
|
|
145
|
+
mode: DEFAULT_FOLDER_MODE,
|
|
146
|
+
recursive: true
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
fd = fs.retry.openSync(timeout)(tempPath, 'w', options.mode || DEFAULT_FILE_MODE);
|
|
150
|
+
if (options.tmpCreated) {
|
|
138
151
|
options.tmpCreated(tempPath);
|
|
139
|
-
if (lang_1.default.isString(data)) {
|
|
140
|
-
fs_1.default.writeSyncRetry(timeout)(fd, data, 0, options.encoding || consts_1.DEFAULT_ENCODING);
|
|
141
152
|
}
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
if (isString(data)) {
|
|
154
|
+
fs.retry.writeSync(timeout)(fd, data, 0, options.encoding || DEFAULT_ENCODING);
|
|
155
|
+
}
|
|
156
|
+
else if (!isUndefined(data)) {
|
|
157
|
+
fs.retry.writeSync(timeout)(fd, data, 0, data.length, 0);
|
|
144
158
|
}
|
|
145
159
|
if (options.fsync !== false) {
|
|
146
160
|
if (options.fsyncWait !== false) {
|
|
147
|
-
|
|
161
|
+
fs.retry.fsyncSync(timeout)(fd);
|
|
148
162
|
}
|
|
149
163
|
else {
|
|
150
|
-
|
|
164
|
+
fs.attempt.fsync(fd);
|
|
151
165
|
}
|
|
152
166
|
}
|
|
153
|
-
|
|
167
|
+
fs.retry.closeSync(timeout)(fd);
|
|
154
168
|
fd = null;
|
|
155
|
-
if (options.chown)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
169
|
+
if (options.chown && (options.chown.uid !== DEFAULT_USER_UID || options.chown.gid !== DEFAULT_USER_GID)) {
|
|
170
|
+
fs.attempt.chownSync(tempPath, options.chown.uid, options.chown.gid);
|
|
171
|
+
}
|
|
172
|
+
if (options.mode && options.mode !== DEFAULT_FILE_MODE) {
|
|
173
|
+
fs.attempt.chmodSync(tempPath, options.mode);
|
|
174
|
+
}
|
|
159
175
|
try {
|
|
160
|
-
|
|
176
|
+
fs.retry.renameSync(timeout)(tempPath, filePath);
|
|
161
177
|
}
|
|
162
178
|
catch (error) {
|
|
179
|
+
if (!isException(error))
|
|
180
|
+
throw error;
|
|
163
181
|
if (error.code !== 'ENAMETOOLONG')
|
|
164
182
|
throw error;
|
|
165
|
-
|
|
183
|
+
fs.retry.renameSync(timeout)(tempPath, Temp.truncate(filePath));
|
|
166
184
|
}
|
|
167
185
|
tempDisposer();
|
|
168
186
|
tempPath = null;
|
|
169
187
|
}
|
|
170
188
|
finally {
|
|
171
189
|
if (fd)
|
|
172
|
-
|
|
190
|
+
fs.attempt.closeSync(fd);
|
|
173
191
|
if (tempPath)
|
|
174
|
-
|
|
192
|
+
Temp.purge(tempPath);
|
|
175
193
|
}
|
|
176
194
|
};
|
|
177
|
-
|
|
195
|
+
/* EXPORT */
|
|
196
|
+
export { readFile, readFileSync, writeFile, writeFileSync };
|
package/dist/types.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
declare type Callback = (error: Exception | void) =>
|
|
3
|
-
declare type Data =
|
|
2
|
+
declare type Callback = (error: Exception | void) => void;
|
|
3
|
+
declare type Data = Uint8Array | string | undefined;
|
|
4
4
|
declare type Disposer = () => void;
|
|
5
|
+
declare type Encoding = 'ascii' | 'base64' | 'binary' | 'hex' | 'latin1' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2';
|
|
5
6
|
declare type Exception = NodeJS.ErrnoException;
|
|
6
|
-
declare type FN<Arguments extends any[] = any[], Return = any> = (...args: Arguments) => Return;
|
|
7
7
|
declare type Path = string;
|
|
8
8
|
declare type ReadOptions = {
|
|
9
|
-
encoding?:
|
|
9
|
+
encoding?: Encoding | null;
|
|
10
10
|
mode?: string | number | false;
|
|
11
11
|
timeout?: number;
|
|
12
12
|
};
|
|
@@ -15,14 +15,14 @@ declare type WriteOptions = {
|
|
|
15
15
|
gid: number;
|
|
16
16
|
uid: number;
|
|
17
17
|
} | false;
|
|
18
|
-
encoding?:
|
|
18
|
+
encoding?: Encoding | null;
|
|
19
19
|
fsync?: boolean;
|
|
20
20
|
fsyncWait?: boolean;
|
|
21
21
|
mode?: string | number | false;
|
|
22
22
|
schedule?: (filePath: string) => Promise<Disposer>;
|
|
23
23
|
timeout?: number;
|
|
24
24
|
tmpCreate?: (filePath: string) => string;
|
|
25
|
-
tmpCreated?: (filePath: string) =>
|
|
25
|
+
tmpCreated?: (filePath: string) => void;
|
|
26
26
|
tmpPurge?: boolean;
|
|
27
27
|
};
|
|
28
|
-
export { Callback, Data, Disposer,
|
|
28
|
+
export type { Callback, Data, Disposer, Encoding, Exception, Path, ReadOptions, WriteOptions };
|
package/dist/types.js
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
/* MAIN */
|
|
2
|
+
export {};
|
package/dist/utils/lang.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
declare const isException: (value: unknown) => value is NodeJS.ErrnoException;
|
|
3
|
+
declare const isFunction: (value: unknown) => value is Function;
|
|
4
|
+
declare const isString: (value: unknown) => value is string;
|
|
5
|
+
declare const isUndefined: (value: unknown) => value is undefined;
|
|
6
|
+
export { isException, isFunction, isString, isUndefined };
|
package/dist/utils/lang.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
/*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
/* IMPORT */
|
|
2
|
+
/* MAIN */
|
|
3
|
+
const isException = (value) => {
|
|
4
|
+
return (value instanceof Error) && ('code' in value);
|
|
5
|
+
};
|
|
6
|
+
const isFunction = (value) => {
|
|
7
|
+
return (typeof value === 'function');
|
|
8
|
+
};
|
|
9
|
+
const isString = (value) => {
|
|
10
|
+
return (typeof value === 'string');
|
|
11
|
+
};
|
|
12
|
+
const isUndefined = (value) => {
|
|
13
|
+
return (value === undefined);
|
|
14
14
|
};
|
|
15
15
|
/* EXPORT */
|
|
16
|
-
|
|
16
|
+
export { isException, isFunction, isString, isUndefined };
|
package/dist/utils/scheduler.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/* IMPORT */
|
|
3
|
-
|
|
4
|
-
/* VARIABLES */
|
|
2
|
+
/* HELPERS */
|
|
5
3
|
const Queues = {};
|
|
6
|
-
/*
|
|
4
|
+
/* MAIN */
|
|
7
5
|
//TODO: Maybe publish this as a standalone package
|
|
8
6
|
const Scheduler = {
|
|
7
|
+
/* API */
|
|
9
8
|
next: (id) => {
|
|
10
9
|
const queue = Queues[id];
|
|
11
10
|
if (!queue)
|
|
@@ -32,4 +31,4 @@ const Scheduler = {
|
|
|
32
31
|
}
|
|
33
32
|
};
|
|
34
33
|
/* EXPORT */
|
|
35
|
-
|
|
34
|
+
export default Scheduler;
|
package/dist/utils/temp.d.ts
CHANGED
package/dist/utils/temp.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/* IMPORT */
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/*
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'stubborn-fs';
|
|
4
|
+
import whenExit from 'when-exit';
|
|
5
|
+
import { LIMIT_BASENAME_LENGTH } from '../constants.js';
|
|
6
|
+
/* MAIN */
|
|
8
7
|
//TODO: Maybe publish this as a standalone package
|
|
9
8
|
const Temp = {
|
|
9
|
+
/* VARIABLES */
|
|
10
10
|
store: {},
|
|
11
|
+
/* API */
|
|
11
12
|
create: (filePath) => {
|
|
12
|
-
const randomness = `000000${Math.floor(Math.random() * 16777215).toString(16)}`.slice(-6)
|
|
13
|
-
timestamp = Date.now().toString().slice(-10)
|
|
14
|
-
prefix = 'tmp-'
|
|
13
|
+
const randomness = `000000${Math.floor(Math.random() * 16777215).toString(16)}`.slice(-6); // 6 random-enough hex characters
|
|
14
|
+
const timestamp = Date.now().toString().slice(-10); // 10 precise timestamp digits
|
|
15
|
+
const prefix = 'tmp-';
|
|
16
|
+
const suffix = `.${prefix}${timestamp}${randomness}`;
|
|
17
|
+
const tempPath = `${filePath}${suffix}`;
|
|
15
18
|
return tempPath;
|
|
16
19
|
},
|
|
17
20
|
get: (filePath, creator, purge = true) => {
|
|
@@ -26,13 +29,13 @@ const Temp = {
|
|
|
26
29
|
if (!Temp.store[filePath])
|
|
27
30
|
return;
|
|
28
31
|
delete Temp.store[filePath];
|
|
29
|
-
|
|
32
|
+
fs.attempt.unlink(filePath);
|
|
30
33
|
},
|
|
31
34
|
purgeSync: (filePath) => {
|
|
32
35
|
if (!Temp.store[filePath])
|
|
33
36
|
return;
|
|
34
37
|
delete Temp.store[filePath];
|
|
35
|
-
|
|
38
|
+
fs.attempt.unlinkSync(filePath);
|
|
36
39
|
},
|
|
37
40
|
purgeSyncAll: () => {
|
|
38
41
|
for (const filePath in Temp.store) {
|
|
@@ -41,16 +44,16 @@ const Temp = {
|
|
|
41
44
|
},
|
|
42
45
|
truncate: (filePath) => {
|
|
43
46
|
const basename = path.basename(filePath);
|
|
44
|
-
if (basename.length <=
|
|
47
|
+
if (basename.length <= LIMIT_BASENAME_LENGTH)
|
|
45
48
|
return filePath; //FIXME: Rough and quick attempt at detecting ok lengths
|
|
46
49
|
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename);
|
|
47
50
|
if (!truncable)
|
|
48
51
|
return filePath; //FIXME: No truncable part detected, can't really do much without also changing the parent path, which is unsafe, hoping for the best here
|
|
49
|
-
const truncationLength = basename.length -
|
|
52
|
+
const truncationLength = basename.length - LIMIT_BASENAME_LENGTH;
|
|
50
53
|
return `${filePath.slice(0, -basename.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`; //FIXME: The truncable part might be shorter than needed here
|
|
51
54
|
}
|
|
52
55
|
};
|
|
53
56
|
/* INIT */
|
|
54
|
-
|
|
57
|
+
whenExit(Temp.purgeSyncAll); // Ensuring purgeable temp files are purged on exit
|
|
55
58
|
/* EXPORT */
|
|
56
|
-
|
|
59
|
+
export default Temp;
|
package/{LICENSE → license}
RENAMED
|
File without changes
|