@zenfs/core 1.2.0 → 1.2.2
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/dist/config.d.ts +13 -4
- package/dist/config.js +2 -1
- package/dist/emulation/cache.d.ts +14 -2
- package/dist/emulation/cache.js +25 -0
- package/dist/emulation/config.d.ts +10 -0
- package/dist/emulation/config.js +10 -0
- package/dist/emulation/promises.js +24 -13
- package/dist/emulation/shared.d.ts +0 -6
- package/dist/emulation/shared.js +0 -6
- package/dist/emulation/sync.js +13 -12
- package/dist/file.d.ts +1 -1
- package/dist/file.js +6 -3
- package/package.json +1 -1
- package/readme.md +1 -1
- package/scripts/test.js +5 -0
- package/src/config.ts +17 -5
- package/src/emulation/cache.ts +32 -3
- package/src/emulation/config.ts +11 -0
- package/src/emulation/promises.ts +25 -13
- package/src/emulation/shared.ts +0 -7
- package/src/emulation/sync.ts +13 -12
- package/src/file.ts +4 -3
- package/tests/common.ts +1 -11
- package/tests/fs/appendFile.test.ts +3 -3
- package/tests/fs/dir.test.ts +7 -7
- package/tests/fs/directory.test.ts +8 -8
- package/tests/fs/errors.test.ts +1 -1
- package/tests/fs/links.test.ts +4 -4
- package/tests/fs/open.test.ts +3 -3
- package/tests/fs/permissions.test.ts +5 -5
- package/tests/fs/read.test.ts +5 -5
- package/tests/fs/readFile.test.ts +2 -2
- package/tests/fs/rename.test.ts +5 -5
- package/tests/fs/stat.test.ts +1 -1
- package/tests/fs/streams.test.ts +19 -19
- package/tests/fs/times.test.ts +6 -6
- package/tests/fs/truncate.test.ts +1 -1
- package/tests/fs/watch.test.ts +10 -10
- package/tests/fs/write.test.ts +5 -5
- package/tests/fs/writeFile.test.ts +5 -5
- package/tests/handle.test.ts +2 -2
- package/tests/mutex.test.ts +1 -1
- package/tests/port/channel.test.ts +3 -3
- package/tests/port/config.test.ts +4 -5
- package/tests/port/config.worker.js +5 -0
- package/tests/port/remote.test.ts +4 -5
- package/tests/port/remote.worker.js +5 -0
- package/tests/port/timeout.test.ts +4 -4
- package/tests/setup/common.ts +1 -1
- package/tests/setup/cow+fetch.ts +1 -1
- package/tests/port/config.worker.ts +0 -5
- package/tests/port/remote.worker.ts +0 -5
package/dist/config.d.ts
CHANGED
|
@@ -34,24 +34,33 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
|
|
|
34
34
|
gid: number;
|
|
35
35
|
/**
|
|
36
36
|
* Whether to automatically add normal Linux devices
|
|
37
|
-
* @default false
|
|
38
37
|
* @experimental
|
|
38
|
+
* @default false
|
|
39
39
|
*/
|
|
40
40
|
addDevices: boolean;
|
|
41
41
|
/**
|
|
42
42
|
* If true, enables caching stats for certain operations.
|
|
43
43
|
* This should reduce the number of stat calls performed.
|
|
44
|
-
* @default false
|
|
45
44
|
* @experimental
|
|
45
|
+
* @default false
|
|
46
46
|
*/
|
|
47
47
|
cacheStats: boolean;
|
|
48
48
|
/**
|
|
49
49
|
* If true, disables *all* permissions checking.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
50
|
+
*
|
|
51
|
+
* This can increase performance.
|
|
52
52
|
* @experimental
|
|
53
|
+
* @default false
|
|
53
54
|
*/
|
|
54
55
|
disableAccessChecks: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* If true, disables `read` and `readSync` from immediately syncing the updated atime to the file system.
|
|
58
|
+
*
|
|
59
|
+
* This can increase performance.
|
|
60
|
+
* @experimental
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
disableSyncOnRead: boolean;
|
|
55
64
|
}
|
|
56
65
|
/**
|
|
57
66
|
* Configures ZenFS with single mount point /
|
package/dist/config.js
CHANGED
|
@@ -3,7 +3,7 @@ import { credentials } from './credentials.js';
|
|
|
3
3
|
import { DeviceFS, fullDevice, nullDevice, randomDevice, zeroDevice } from './devices.js';
|
|
4
4
|
import * as cache from './emulation/cache.js';
|
|
5
5
|
import * as fs from './emulation/index.js';
|
|
6
|
-
import { config } from './emulation/
|
|
6
|
+
import { config } from './emulation/config.js';
|
|
7
7
|
import { Errno, ErrnoError } from './error.js';
|
|
8
8
|
import { FileSystem } from './filesystem.js';
|
|
9
9
|
function isMountConfig(arg) {
|
|
@@ -70,6 +70,7 @@ export async function configure(configuration) {
|
|
|
70
70
|
Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
|
|
71
71
|
cache.setEnabled(configuration.cacheStats ?? false);
|
|
72
72
|
config.checkAccess = !configuration.disableAccessChecks;
|
|
73
|
+
config.syncOnRead = !configuration.disableSyncOnRead;
|
|
73
74
|
if (configuration.addDevices) {
|
|
74
75
|
const devfs = new DeviceFS();
|
|
75
76
|
devfs.createDevice('/null', nullDevice);
|
|
@@ -10,11 +10,23 @@ export declare function setEnabled(value: boolean): void;
|
|
|
10
10
|
/**
|
|
11
11
|
* Gets stats from the cache, if they exist and the cache is enabled.
|
|
12
12
|
*/
|
|
13
|
-
export declare function
|
|
13
|
+
export declare function getStatsSync(path: string): Stats | undefined;
|
|
14
14
|
/**
|
|
15
15
|
* Adds stats if the cache is enabled
|
|
16
16
|
*/
|
|
17
|
-
export declare function
|
|
17
|
+
export declare function setStatsSync(path: string, value: Stats): void;
|
|
18
|
+
/**
|
|
19
|
+
* Clears the cache if it is enabled
|
|
20
|
+
*/
|
|
21
|
+
export declare function clearStatsSync(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Gets stats from the cache, if they exist and the cache is enabled.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getStats(path: string): Promise<Stats | undefined> | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Adds stats if the cache is enabled
|
|
28
|
+
*/
|
|
29
|
+
export declare function setStats(path: string, value: Promise<Stats | undefined>): void;
|
|
18
30
|
/**
|
|
19
31
|
* Clears the cache if it is enabled
|
|
20
32
|
*/
|
package/dist/emulation/cache.js
CHANGED
|
@@ -9,6 +9,31 @@ export let isEnabled = false;
|
|
|
9
9
|
export function setEnabled(value) {
|
|
10
10
|
isEnabled = value;
|
|
11
11
|
}
|
|
12
|
+
const statsSync = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Gets stats from the cache, if they exist and the cache is enabled.
|
|
15
|
+
*/
|
|
16
|
+
export function getStatsSync(path) {
|
|
17
|
+
if (!isEnabled)
|
|
18
|
+
return;
|
|
19
|
+
return statsSync.get(path);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Adds stats if the cache is enabled
|
|
23
|
+
*/
|
|
24
|
+
export function setStatsSync(path, value) {
|
|
25
|
+
if (!isEnabled)
|
|
26
|
+
return;
|
|
27
|
+
statsSync.set(path, value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Clears the cache if it is enabled
|
|
31
|
+
*/
|
|
32
|
+
export function clearStatsSync() {
|
|
33
|
+
if (!isEnabled)
|
|
34
|
+
return;
|
|
35
|
+
statsSync.clear();
|
|
36
|
+
}
|
|
12
37
|
const stats = new Map();
|
|
13
38
|
/**
|
|
14
39
|
* Gets stats from the cache, if they exist and the cache is enabled.
|
|
@@ -56,7 +56,8 @@ import * as cache from './cache.js';
|
|
|
56
56
|
import * as constants from './constants.js';
|
|
57
57
|
import { Dir, Dirent } from './dir.js';
|
|
58
58
|
import { dirname, join, parse } from './path.js';
|
|
59
|
-
import { _statfs,
|
|
59
|
+
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
|
|
60
|
+
import { config } from './config.js';
|
|
60
61
|
import { ReadStream, WriteStream } from './streams.js';
|
|
61
62
|
import { FSWatcher, emitChange } from './watchers.js';
|
|
62
63
|
export * as constants from './constants.js';
|
|
@@ -434,7 +435,7 @@ export async function unlink(path) {
|
|
|
434
435
|
path = normalizePath(path);
|
|
435
436
|
const { fs, path: resolved } = resolveMount(path);
|
|
436
437
|
try {
|
|
437
|
-
if (config.checkAccess && !(cache.getStats(path) ||
|
|
438
|
+
if (config.checkAccess && !(await (cache.getStats(path) || fs.stat(resolved))).hasAccess(constants.W_OK)) {
|
|
438
439
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
439
440
|
}
|
|
440
441
|
await fs.unlink(resolved);
|
|
@@ -583,7 +584,10 @@ export async function rmdir(path) {
|
|
|
583
584
|
path = await realpath(path);
|
|
584
585
|
const { fs, path: resolved } = resolveMount(path);
|
|
585
586
|
try {
|
|
586
|
-
const stats = cache.getStats(path) ||
|
|
587
|
+
const stats = await (cache.getStats(path) || fs.stat(resolved));
|
|
588
|
+
if (!stats) {
|
|
589
|
+
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
590
|
+
}
|
|
587
591
|
if (!stats.isDirectory()) {
|
|
588
592
|
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
589
593
|
}
|
|
@@ -639,8 +643,12 @@ export async function readdir(path, options) {
|
|
|
639
643
|
throw fixError(e, { [resolved]: path });
|
|
640
644
|
};
|
|
641
645
|
const { fs, path: resolved } = resolveMount(path);
|
|
642
|
-
const
|
|
643
|
-
cache.setStats(path,
|
|
646
|
+
const _stats = cache.getStats(path) || fs.stat(resolved).catch(handleError);
|
|
647
|
+
cache.setStats(path, _stats);
|
|
648
|
+
const stats = await _stats;
|
|
649
|
+
if (!stats) {
|
|
650
|
+
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
651
|
+
}
|
|
644
652
|
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
645
653
|
throw ErrnoError.With('EACCES', path, 'readdir');
|
|
646
654
|
}
|
|
@@ -662,8 +670,9 @@ export async function readdir(path, options) {
|
|
|
662
670
|
const addEntry = async (entry) => {
|
|
663
671
|
let entryStats;
|
|
664
672
|
if (options?.recursive || options?.withFileTypes) {
|
|
665
|
-
|
|
666
|
-
cache.setStats(join(path, entry),
|
|
673
|
+
const _entryStats = cache.getStats(join(path, entry)) || fs.stat(join(resolved, entry)).catch(handleError);
|
|
674
|
+
cache.setStats(join(path, entry), _entryStats);
|
|
675
|
+
entryStats = await _entryStats;
|
|
667
676
|
}
|
|
668
677
|
if (options?.withFileTypes) {
|
|
669
678
|
values.push(new Dirent(entry, entryStats));
|
|
@@ -933,15 +942,17 @@ access;
|
|
|
933
942
|
*/
|
|
934
943
|
export async function rm(path, options) {
|
|
935
944
|
path = normalizePath(path);
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
if (error.code
|
|
939
|
-
|
|
940
|
-
|
|
945
|
+
const _stats = cache.getStats(path) ||
|
|
946
|
+
stat(path).catch((error) => {
|
|
947
|
+
if (error.code == 'ENOENT' && options?.force)
|
|
948
|
+
return undefined;
|
|
949
|
+
throw error;
|
|
950
|
+
});
|
|
951
|
+
cache.setStats(path, _stats);
|
|
952
|
+
const stats = await _stats;
|
|
941
953
|
if (!stats) {
|
|
942
954
|
return;
|
|
943
955
|
}
|
|
944
|
-
cache.setStats(path, stats);
|
|
945
956
|
switch (stats.mode & constants.S_IFMT) {
|
|
946
957
|
case constants.S_IFDIR:
|
|
947
958
|
if (options?.recursive) {
|
|
@@ -43,12 +43,6 @@ export declare function mountObject(mounts: MountObject): void;
|
|
|
43
43
|
* @hidden
|
|
44
44
|
*/
|
|
45
45
|
export declare function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T extends true ? BigIntStatsFs : StatsFs;
|
|
46
|
-
export declare const config: {
|
|
47
|
-
/**
|
|
48
|
-
* Whether to perform access checks
|
|
49
|
-
*/
|
|
50
|
-
checkAccess: boolean;
|
|
51
|
-
};
|
|
52
46
|
/**
|
|
53
47
|
* Options used for caching, among other things.
|
|
54
48
|
* @internal *UNSTABLE*
|
package/dist/emulation/shared.js
CHANGED
package/dist/emulation/sync.js
CHANGED
|
@@ -53,7 +53,8 @@ import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTi
|
|
|
53
53
|
import * as constants from './constants.js';
|
|
54
54
|
import { Dir, Dirent } from './dir.js';
|
|
55
55
|
import { dirname, join, parse } from './path.js';
|
|
56
|
-
import { _statfs,
|
|
56
|
+
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
|
|
57
|
+
import { config } from './config.js';
|
|
57
58
|
import { emitChange } from './watchers.js';
|
|
58
59
|
import * as cache from './cache.js';
|
|
59
60
|
export function renameSync(oldPath, newPath) {
|
|
@@ -146,7 +147,7 @@ export function unlinkSync(path) {
|
|
|
146
147
|
path = normalizePath(path);
|
|
147
148
|
const { fs, path: resolved } = resolveMount(path);
|
|
148
149
|
try {
|
|
149
|
-
if (config.checkAccess && !(cache.
|
|
150
|
+
if (config.checkAccess && !(cache.getStatsSync(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
|
|
150
151
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
151
152
|
}
|
|
152
153
|
fs.unlinkSync(resolved);
|
|
@@ -393,7 +394,7 @@ export function rmdirSync(path) {
|
|
|
393
394
|
path = normalizePath(path);
|
|
394
395
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
395
396
|
try {
|
|
396
|
-
const stats = cache.
|
|
397
|
+
const stats = cache.getStatsSync(path) || fs.statSync(resolved);
|
|
397
398
|
if (!stats.isDirectory()) {
|
|
398
399
|
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
399
400
|
}
|
|
@@ -446,8 +447,8 @@ export function readdirSync(path, options) {
|
|
|
446
447
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
447
448
|
let entries;
|
|
448
449
|
try {
|
|
449
|
-
const stats = cache.
|
|
450
|
-
cache.
|
|
450
|
+
const stats = cache.getStatsSync(path) || fs.statSync(resolved);
|
|
451
|
+
cache.setStatsSync(path, stats);
|
|
451
452
|
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
452
453
|
throw ErrnoError.With('EACCES', resolved, 'readdir');
|
|
453
454
|
}
|
|
@@ -473,8 +474,8 @@ export function readdirSync(path, options) {
|
|
|
473
474
|
// Iterate over entries and handle recursive case if needed
|
|
474
475
|
const values = [];
|
|
475
476
|
for (const entry of entries) {
|
|
476
|
-
const entryStat = cache.
|
|
477
|
-
cache.
|
|
477
|
+
const entryStat = cache.getStatsSync(join(path, entry)) || fs.statSync(join(resolved, entry));
|
|
478
|
+
cache.setStatsSync(join(path, entry), entryStat);
|
|
478
479
|
if (options?.withFileTypes) {
|
|
479
480
|
values.push(new Dirent(entry, entryStat));
|
|
480
481
|
}
|
|
@@ -500,7 +501,7 @@ export function readdirSync(path, options) {
|
|
|
500
501
|
}
|
|
501
502
|
}
|
|
502
503
|
if (!options?._isIndirect) {
|
|
503
|
-
cache.
|
|
504
|
+
cache.clearStatsSync();
|
|
504
505
|
}
|
|
505
506
|
return values;
|
|
506
507
|
}
|
|
@@ -637,7 +638,7 @@ export function rmSync(path, options) {
|
|
|
637
638
|
path = normalizePath(path);
|
|
638
639
|
let stats;
|
|
639
640
|
try {
|
|
640
|
-
stats = cache.
|
|
641
|
+
stats = cache.getStatsSync(path) || statSync(path);
|
|
641
642
|
}
|
|
642
643
|
catch (error) {
|
|
643
644
|
if (error.code != 'ENOENT' || !options?.force)
|
|
@@ -646,7 +647,7 @@ export function rmSync(path, options) {
|
|
|
646
647
|
if (!stats) {
|
|
647
648
|
return;
|
|
648
649
|
}
|
|
649
|
-
cache.
|
|
650
|
+
cache.setStatsSync(path, stats);
|
|
650
651
|
switch (stats.mode & constants.S_IFMT) {
|
|
651
652
|
case constants.S_IFDIR:
|
|
652
653
|
if (options?.recursive) {
|
|
@@ -665,11 +666,11 @@ export function rmSync(path, options) {
|
|
|
665
666
|
case constants.S_IFIFO:
|
|
666
667
|
case constants.S_IFSOCK:
|
|
667
668
|
default:
|
|
668
|
-
cache.
|
|
669
|
+
cache.clearStatsSync();
|
|
669
670
|
throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
|
|
670
671
|
}
|
|
671
672
|
if (!options?._isIndirect) {
|
|
672
|
-
cache.
|
|
673
|
+
cache.clearStatsSync();
|
|
673
674
|
}
|
|
674
675
|
}
|
|
675
676
|
rmSync;
|
package/dist/file.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FileReadResult } from 'node:fs/promises';
|
|
2
2
|
import type { FileSystem } from './filesystem.js';
|
|
3
|
-
import { Stats, type FileType } from './stats.js';
|
|
4
3
|
import './polyfills.js';
|
|
4
|
+
import { Stats, type FileType } from './stats.js';
|
|
5
5
|
/**
|
|
6
6
|
Typescript does not include a type declaration for resizable array buffers.
|
|
7
7
|
It has been standardized into ECMAScript though
|
package/dist/file.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT, size_max } from './emulation/constants.js';
|
|
2
|
+
import { config } from './emulation/config.js';
|
|
2
3
|
import { Errno, ErrnoError } from './error.js';
|
|
3
|
-
import { Stats } from './stats.js';
|
|
4
4
|
import './polyfills.js';
|
|
5
|
+
import { Stats } from './stats.js';
|
|
5
6
|
const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+'];
|
|
6
7
|
export function parseFlag(flag) {
|
|
7
8
|
if (typeof flag === 'number') {
|
|
@@ -383,7 +384,8 @@ export class PreloadFile extends File {
|
|
|
383
384
|
*/
|
|
384
385
|
async read(buffer, offset, length, position) {
|
|
385
386
|
const bytesRead = this._read(buffer, offset, length, position);
|
|
386
|
-
|
|
387
|
+
if (config.syncOnRead)
|
|
388
|
+
await this.sync();
|
|
387
389
|
return { bytesRead, buffer };
|
|
388
390
|
}
|
|
389
391
|
/**
|
|
@@ -397,7 +399,8 @@ export class PreloadFile extends File {
|
|
|
397
399
|
*/
|
|
398
400
|
readSync(buffer, offset, length, position) {
|
|
399
401
|
const bytesRead = this._read(buffer, offset, length, position);
|
|
400
|
-
|
|
402
|
+
if (config.syncOnRead)
|
|
403
|
+
this.syncSync();
|
|
401
404
|
return bytesRead;
|
|
402
405
|
}
|
|
403
406
|
async chmod(mode) {
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -32,7 +32,7 @@ npm install @zenfs/core
|
|
|
32
32
|
```js
|
|
33
33
|
import { fs } from '@zenfs/core'; // You can also use the default export
|
|
34
34
|
|
|
35
|
-
fs.writeFileSync('/test.txt', 'You can do this
|
|
35
|
+
fs.writeFileSync('/test.txt', 'You can do this anywhere, including browsers!');
|
|
36
36
|
|
|
37
37
|
const contents = fs.readFileSync('/test.txt', 'utf-8');
|
|
38
38
|
console.log(contents);
|
package/scripts/test.js
CHANGED
|
@@ -31,6 +31,11 @@ options:
|
|
|
31
31
|
|
|
32
32
|
if (options.verbose) console.debug('Forcing tests to exit (--test-force-exit)');
|
|
33
33
|
|
|
34
|
+
if (!existsSync(join(import.meta.dirname, '../dist'))) {
|
|
35
|
+
console.log('ERROR: Missing build. If you are using an installed package, please submit a bug report.');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
const testsGlob = join(import.meta.dirname, `../tests/fs/${options.test || '*'}.test.ts`);
|
|
35
40
|
|
|
36
41
|
for (const setupFile of positionals) {
|
package/src/config.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { DeviceFS, fullDevice, nullDevice, randomDevice, zeroDevice } from './de
|
|
|
5
5
|
import * as cache from './emulation/cache.js';
|
|
6
6
|
import * as fs from './emulation/index.js';
|
|
7
7
|
import type { AbsolutePath } from './emulation/path.js';
|
|
8
|
-
import {
|
|
8
|
+
import { type MountObject } from './emulation/shared.js';
|
|
9
|
+
import { config } from './emulation/config.js';
|
|
9
10
|
import { Errno, ErrnoError } from './error.js';
|
|
10
11
|
import { FileSystem } from './filesystem.js';
|
|
11
12
|
|
|
@@ -95,26 +96,36 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
|
|
|
95
96
|
|
|
96
97
|
/**
|
|
97
98
|
* Whether to automatically add normal Linux devices
|
|
98
|
-
* @default false
|
|
99
99
|
* @experimental
|
|
100
|
+
* @default false
|
|
100
101
|
*/
|
|
101
102
|
addDevices: boolean;
|
|
102
103
|
|
|
103
104
|
/**
|
|
104
105
|
* If true, enables caching stats for certain operations.
|
|
105
106
|
* This should reduce the number of stat calls performed.
|
|
106
|
-
* @default false
|
|
107
107
|
* @experimental
|
|
108
|
+
* @default false
|
|
108
109
|
*/
|
|
109
110
|
cacheStats: boolean;
|
|
110
111
|
|
|
111
112
|
/**
|
|
112
113
|
* If true, disables *all* permissions checking.
|
|
113
|
-
*
|
|
114
|
-
*
|
|
114
|
+
*
|
|
115
|
+
* This can increase performance.
|
|
115
116
|
* @experimental
|
|
117
|
+
* @default false
|
|
116
118
|
*/
|
|
117
119
|
disableAccessChecks: boolean;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* If true, disables `read` and `readSync` from immediately syncing the updated atime to the file system.
|
|
123
|
+
*
|
|
124
|
+
* This can increase performance.
|
|
125
|
+
* @experimental
|
|
126
|
+
* @default false
|
|
127
|
+
*/
|
|
128
|
+
disableSyncOnRead: boolean;
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
/**
|
|
@@ -142,6 +153,7 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
|
|
|
142
153
|
|
|
143
154
|
cache.setEnabled(configuration.cacheStats ?? false);
|
|
144
155
|
config.checkAccess = !configuration.disableAccessChecks;
|
|
156
|
+
config.syncOnRead = !configuration.disableSyncOnRead;
|
|
145
157
|
|
|
146
158
|
if (configuration.addDevices) {
|
|
147
159
|
const devfs = new DeviceFS();
|
package/src/emulation/cache.ts
CHANGED
|
@@ -14,12 +14,41 @@ export function setEnabled(value: boolean): void {
|
|
|
14
14
|
isEnabled = value;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const statsSync = new Map<string, Stats>();
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Gets stats from the cache, if they exist and the cache is enabled.
|
|
21
21
|
*/
|
|
22
|
-
export function
|
|
22
|
+
export function getStatsSync(path: string): Stats | undefined {
|
|
23
|
+
if (!isEnabled) return;
|
|
24
|
+
|
|
25
|
+
return statsSync.get(path);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Adds stats if the cache is enabled
|
|
30
|
+
*/
|
|
31
|
+
export function setStatsSync(path: string, value: Stats): void {
|
|
32
|
+
if (!isEnabled) return;
|
|
33
|
+
|
|
34
|
+
statsSync.set(path, value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Clears the cache if it is enabled
|
|
39
|
+
*/
|
|
40
|
+
export function clearStatsSync(): void {
|
|
41
|
+
if (!isEnabled) return;
|
|
42
|
+
|
|
43
|
+
statsSync.clear();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const stats = new Map<string, Promise<Stats | undefined>>();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Gets stats from the cache, if they exist and the cache is enabled.
|
|
50
|
+
*/
|
|
51
|
+
export function getStats(path: string): Promise<Stats | undefined> | undefined {
|
|
23
52
|
if (!isEnabled) return;
|
|
24
53
|
|
|
25
54
|
return stats.get(path);
|
|
@@ -28,7 +57,7 @@ export function getStats(path: string): Stats | undefined {
|
|
|
28
57
|
/**
|
|
29
58
|
* Adds stats if the cache is enabled
|
|
30
59
|
*/
|
|
31
|
-
export function setStats(path: string, value: Stats): void {
|
|
60
|
+
export function setStats(path: string, value: Promise<Stats | undefined>): void {
|
|
32
61
|
if (!isEnabled) return;
|
|
33
62
|
|
|
34
63
|
stats.set(path, value);
|
|
@@ -16,7 +16,8 @@ import * as cache from './cache.js';
|
|
|
16
16
|
import * as constants from './constants.js';
|
|
17
17
|
import { Dir, Dirent } from './dir.js';
|
|
18
18
|
import { dirname, join, parse } from './path.js';
|
|
19
|
-
import { _statfs,
|
|
19
|
+
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
|
|
20
|
+
import { config } from './config.js';
|
|
20
21
|
import { ReadStream, WriteStream } from './streams.js';
|
|
21
22
|
import { FSWatcher, emitChange } from './watchers.js';
|
|
22
23
|
export * as constants from './constants.js';
|
|
@@ -470,7 +471,7 @@ export async function unlink(path: fs.PathLike): Promise<void> {
|
|
|
470
471
|
path = normalizePath(path);
|
|
471
472
|
const { fs, path: resolved } = resolveMount(path);
|
|
472
473
|
try {
|
|
473
|
-
if (config.checkAccess && !(cache.getStats(path) ||
|
|
474
|
+
if (config.checkAccess && !(await (cache.getStats(path) || fs.stat(resolved)))!.hasAccess(constants.W_OK)) {
|
|
474
475
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
475
476
|
}
|
|
476
477
|
await fs.unlink(resolved);
|
|
@@ -623,7 +624,10 @@ export async function rmdir(path: fs.PathLike): Promise<void> {
|
|
|
623
624
|
path = await realpath(path);
|
|
624
625
|
const { fs, path: resolved } = resolveMount(path);
|
|
625
626
|
try {
|
|
626
|
-
const stats = cache.getStats(path) ||
|
|
627
|
+
const stats = await (cache.getStats(path) || fs.stat(resolved));
|
|
628
|
+
if (!stats) {
|
|
629
|
+
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
630
|
+
}
|
|
627
631
|
if (!stats.isDirectory()) {
|
|
628
632
|
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
629
633
|
}
|
|
@@ -715,8 +719,13 @@ export async function readdir(
|
|
|
715
719
|
|
|
716
720
|
const { fs, path: resolved } = resolveMount(path);
|
|
717
721
|
|
|
718
|
-
const
|
|
719
|
-
cache.setStats(path,
|
|
722
|
+
const _stats = cache.getStats(path) || fs.stat(resolved).catch(handleError);
|
|
723
|
+
cache.setStats(path, _stats);
|
|
724
|
+
const stats = await _stats;
|
|
725
|
+
|
|
726
|
+
if (!stats) {
|
|
727
|
+
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
728
|
+
}
|
|
720
729
|
|
|
721
730
|
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
722
731
|
throw ErrnoError.With('EACCES', path, 'readdir');
|
|
@@ -743,8 +752,9 @@ export async function readdir(
|
|
|
743
752
|
const addEntry = async (entry: string) => {
|
|
744
753
|
let entryStats: Stats | undefined;
|
|
745
754
|
if (options?.recursive || options?.withFileTypes) {
|
|
746
|
-
|
|
747
|
-
cache.setStats(join(path, entry),
|
|
755
|
+
const _entryStats = cache.getStats(join(path, entry)) || fs.stat(join(resolved, entry)).catch(handleError);
|
|
756
|
+
cache.setStats(join(path, entry), _entryStats);
|
|
757
|
+
entryStats = await _entryStats;
|
|
748
758
|
}
|
|
749
759
|
if (options?.withFileTypes) {
|
|
750
760
|
values.push(new Dirent(entry, entryStats!));
|
|
@@ -967,18 +977,20 @@ access satisfies typeof promises.access;
|
|
|
967
977
|
export async function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOptions) {
|
|
968
978
|
path = normalizePath(path);
|
|
969
979
|
|
|
970
|
-
const
|
|
980
|
+
const _stats =
|
|
971
981
|
cache.getStats(path) ||
|
|
972
|
-
|
|
973
|
-
if (error.code
|
|
974
|
-
|
|
982
|
+
stat(path).catch((error: ErrnoError) => {
|
|
983
|
+
if (error.code == 'ENOENT' && options?.force) return undefined;
|
|
984
|
+
throw error;
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
cache.setStats(path, _stats);
|
|
988
|
+
const stats = await _stats;
|
|
975
989
|
|
|
976
990
|
if (!stats) {
|
|
977
991
|
return;
|
|
978
992
|
}
|
|
979
993
|
|
|
980
|
-
cache.setStats(path, stats);
|
|
981
|
-
|
|
982
994
|
switch (stats.mode & constants.S_IFMT) {
|
|
983
995
|
case constants.S_IFDIR:
|
|
984
996
|
if (options?.recursive) {
|
package/src/emulation/shared.ts
CHANGED
|
@@ -134,13 +134,6 @@ export function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T
|
|
|
134
134
|
} as T extends true ? BigIntStatsFs : StatsFs;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
export const config = {
|
|
138
|
-
/**
|
|
139
|
-
* Whether to perform access checks
|
|
140
|
-
*/
|
|
141
|
-
checkAccess: true,
|
|
142
|
-
};
|
|
143
|
-
|
|
144
137
|
/**
|
|
145
138
|
* Options used for caching, among other things.
|
|
146
139
|
* @internal *UNSTABLE*
|