@zenfs/core 1.7.2 → 1.8.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/dist/backends/backend.js +3 -4
- package/dist/backends/fetch.d.ts +17 -18
- package/dist/backends/fetch.js +95 -58
- package/dist/backends/index.d.ts +2 -1
- package/dist/backends/index.js +2 -1
- package/dist/backends/memory.d.ts +1 -1
- package/dist/backends/overlay.d.ts +7 -2
- package/dist/backends/overlay.js +32 -9
- package/dist/backends/passthrough.d.ts +4 -0
- package/dist/backends/passthrough.js +128 -0
- package/dist/backends/port/fs.d.ts +9 -44
- package/dist/backends/port/fs.js +93 -116
- package/dist/backends/port/rpc.d.ts +8 -5
- package/dist/backends/port/rpc.js +9 -7
- package/dist/backends/store/file_index.d.ts +38 -0
- package/dist/backends/store/file_index.js +76 -0
- package/dist/backends/store/fs.d.ts +55 -34
- package/dist/backends/store/fs.js +417 -233
- package/dist/backends/store/index_fs.d.ts +34 -0
- package/dist/backends/store/index_fs.js +67 -0
- package/dist/backends/store/inode.d.ts +26 -8
- package/dist/backends/store/inode.js +92 -91
- package/dist/backends/store/simple.d.ts +20 -20
- package/dist/backends/store/simple.js +3 -4
- package/dist/backends/store/store.d.ts +12 -12
- package/dist/backends/store/store.js +4 -6
- package/dist/devices.d.ts +11 -10
- package/dist/devices.js +15 -11
- package/dist/file.d.ts +111 -7
- package/dist/file.js +319 -71
- package/dist/filesystem.d.ts +22 -4
- package/dist/mixins/mutexed.d.ts +7 -2
- package/dist/mixins/mutexed.js +56 -0
- package/dist/mixins/sync.d.ts +1 -1
- package/dist/stats.d.ts +12 -6
- package/dist/stats.js +14 -6
- package/dist/utils.d.ts +17 -3
- package/dist/utils.js +32 -10
- package/dist/vfs/constants.d.ts +2 -2
- package/dist/vfs/constants.js +2 -2
- package/dist/vfs/dir.js +3 -1
- package/dist/vfs/index.js +4 -1
- package/dist/vfs/promises.js +31 -11
- package/dist/vfs/shared.js +2 -0
- package/dist/vfs/sync.js +25 -13
- package/dist/vfs/types.d.ts +15 -0
- package/package.json +2 -3
- package/readme.md +2 -2
- package/scripts/test.js +73 -11
- package/tests/common/mutex.test.ts +1 -1
- package/tests/fetch/run.sh +16 -0
- package/tests/fetch/server.ts +49 -0
- package/tests/fetch/setup.ts +13 -0
- package/tests/fs/read.test.ts +10 -10
- package/tests/fs/times.test.ts +2 -2
- package/tests/setup/index.ts +38 -0
- package/tests/setup/port.ts +15 -0
- package/dist/backends/file_index.d.ts +0 -63
- package/dist/backends/file_index.js +0 -163
- package/tests/common/async.test.ts +0 -31
- package/tests/setup/cow+fetch.ts +0 -45
- /package/tests/fs/{appendFile.test.ts → append.test.ts} +0 -0
package/dist/stats.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type * as Node from 'node:fs';
|
|
2
2
|
import type { V_Context } from './context.js';
|
|
3
3
|
import * as c from './vfs/constants.js';
|
|
4
|
+
import type { InodeFields, InodeLike } from './backends/store/inode.js';
|
|
4
5
|
/**
|
|
5
6
|
* Indicates the type of a file. Applied to 'mode'.
|
|
6
7
|
*/
|
|
@@ -91,8 +92,10 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
|
|
|
91
92
|
gid: T;
|
|
92
93
|
/**
|
|
93
94
|
* Some file systems stash data on stats objects.
|
|
95
|
+
* @todo [BREAKING] Remove this
|
|
96
|
+
* @deprecated @hidden
|
|
94
97
|
*/
|
|
95
|
-
fileData?:
|
|
98
|
+
fileData?: unknown;
|
|
96
99
|
/**
|
|
97
100
|
* Time of last access, since epoch
|
|
98
101
|
*/
|
|
@@ -122,10 +125,12 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
|
|
|
122
125
|
* For directories/symlinks, this is normally the size of the struct that represents the item.
|
|
123
126
|
*/
|
|
124
127
|
size: T;
|
|
128
|
+
data?: number;
|
|
129
|
+
flags?: number;
|
|
125
130
|
/**
|
|
126
131
|
* Creates a new stats instance from a stats-like object. Can be used to copy stats (note)
|
|
127
132
|
*/
|
|
128
|
-
constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino }?: Partial<
|
|
133
|
+
constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, ...rest }?: Partial<InodeLike>);
|
|
129
134
|
isFile(): boolean;
|
|
130
135
|
isDirectory(): boolean;
|
|
131
136
|
isSymbolicLink(): boolean;
|
|
@@ -133,6 +138,7 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
|
|
|
133
138
|
isBlockDevice(): boolean;
|
|
134
139
|
isCharacterDevice(): boolean;
|
|
135
140
|
isFIFO(): boolean;
|
|
141
|
+
toJSON(): StatsLike<T> & InodeFields;
|
|
136
142
|
/**
|
|
137
143
|
* Checks if a given user/group has access to this item
|
|
138
144
|
* @param mode The requested access, combination of W_OK, R_OK, and X_OK
|
|
@@ -143,15 +149,15 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
|
|
|
143
149
|
/**
|
|
144
150
|
* Change the mode of the file.
|
|
145
151
|
* We use this helper function to prevent messing up the type of the file.
|
|
146
|
-
* @internal
|
|
147
|
-
* @
|
|
152
|
+
* @internal @deprecated
|
|
153
|
+
* @todo [BREAKING] Remove
|
|
148
154
|
*/
|
|
149
155
|
chmod(mode: number): void;
|
|
150
156
|
/**
|
|
151
157
|
* Change the owner user/group of the file.
|
|
152
158
|
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
|
|
153
|
-
* @internal
|
|
154
|
-
* @
|
|
159
|
+
* @internal @deprecated
|
|
160
|
+
* @todo [BREAKING] Remove
|
|
155
161
|
*/
|
|
156
162
|
chown(uid: number, gid: number): void;
|
|
157
163
|
get atimeNs(): bigint;
|
package/dist/stats.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { credentials } from './credentials.js';
|
|
2
2
|
import * as c from './vfs/constants.js';
|
|
3
|
+
import { pick } from 'utilium';
|
|
4
|
+
import { _inode_fields } from './backends/store/inode.js';
|
|
3
5
|
const n1000 = BigInt(1000);
|
|
4
6
|
/**
|
|
5
7
|
* Provides information about a particular entry in the file system.
|
|
@@ -39,7 +41,7 @@ export class StatsCommon {
|
|
|
39
41
|
/**
|
|
40
42
|
* Creates a new stats instance from a stats-like object. Can be used to copy stats (note)
|
|
41
43
|
*/
|
|
42
|
-
constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino } = {}) {
|
|
44
|
+
constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, ...rest } = {}) {
|
|
43
45
|
/**
|
|
44
46
|
* ID of device containing file
|
|
45
47
|
*/
|
|
@@ -81,6 +83,7 @@ export class StatsCommon {
|
|
|
81
83
|
if ((this.mode & c.S_IFMT) == 0) {
|
|
82
84
|
this.mode = (this.mode | this._convert(c.S_IFREG));
|
|
83
85
|
}
|
|
86
|
+
Object.assign(this, rest);
|
|
84
87
|
}
|
|
85
88
|
isFile() {
|
|
86
89
|
return (this.mode & c.S_IFMT) === c.S_IFREG;
|
|
@@ -103,6 +106,9 @@ export class StatsCommon {
|
|
|
103
106
|
isFIFO() {
|
|
104
107
|
return (this.mode & c.S_IFMT) === c.S_IFIFO;
|
|
105
108
|
}
|
|
109
|
+
toJSON() {
|
|
110
|
+
return pick(this, _inode_fields);
|
|
111
|
+
}
|
|
106
112
|
/**
|
|
107
113
|
* Checks if a given user/group has access to this item
|
|
108
114
|
* @param mode The requested access, combination of W_OK, R_OK, and X_OK
|
|
@@ -142,11 +148,12 @@ export class StatsCommon {
|
|
|
142
148
|
// Perform the access check
|
|
143
149
|
return (perm & mode) === mode;
|
|
144
150
|
}
|
|
151
|
+
/* node:coverage disable */
|
|
145
152
|
/**
|
|
146
153
|
* Change the mode of the file.
|
|
147
154
|
* We use this helper function to prevent messing up the type of the file.
|
|
148
|
-
* @internal
|
|
149
|
-
* @
|
|
155
|
+
* @internal @deprecated
|
|
156
|
+
* @todo [BREAKING] Remove
|
|
150
157
|
*/
|
|
151
158
|
chmod(mode) {
|
|
152
159
|
this.mode = this._convert((this.mode & c.S_IFMT) | mode);
|
|
@@ -154,8 +161,8 @@ export class StatsCommon {
|
|
|
154
161
|
/**
|
|
155
162
|
* Change the owner user/group of the file.
|
|
156
163
|
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
|
|
157
|
-
* @internal
|
|
158
|
-
* @
|
|
164
|
+
* @internal @deprecated
|
|
165
|
+
* @todo [BREAKING] Remove
|
|
159
166
|
*/
|
|
160
167
|
chown(uid, gid) {
|
|
161
168
|
uid = Number(uid);
|
|
@@ -167,6 +174,7 @@ export class StatsCommon {
|
|
|
167
174
|
this.gid = this._convert(gid);
|
|
168
175
|
}
|
|
169
176
|
}
|
|
177
|
+
/* node:coverage enable */
|
|
170
178
|
get atimeNs() {
|
|
171
179
|
return BigInt(this.atimeMs) * n1000;
|
|
172
180
|
}
|
|
@@ -184,7 +192,7 @@ export class StatsCommon {
|
|
|
184
192
|
* @hidden @internal
|
|
185
193
|
*/
|
|
186
194
|
export function _chown(stats, uid, gid) {
|
|
187
|
-
if (!isNaN(uid) && 0 <= uid && uid <
|
|
195
|
+
if (!isNaN(uid) && 0 <= uid && uid < c.size_max) {
|
|
188
196
|
stats.uid = uid;
|
|
189
197
|
}
|
|
190
198
|
if (!isNaN(gid) && 0 <= gid && gid < 2 ** 32) {
|
package/dist/utils.d.ts
CHANGED
|
@@ -32,15 +32,16 @@ export { /** @deprecated @hidden */ decodeUTF8 as decode };
|
|
|
32
32
|
* Decodes a directory listing
|
|
33
33
|
* @hidden
|
|
34
34
|
*/
|
|
35
|
-
export declare function decodeDirListing(data: Uint8Array): Record<string,
|
|
35
|
+
export declare function decodeDirListing(data: Uint8Array): Record<string, number>;
|
|
36
36
|
/**
|
|
37
37
|
* Encodes a directory listing
|
|
38
38
|
* @hidden
|
|
39
39
|
*/
|
|
40
|
-
export declare function encodeDirListing(data: Record<string,
|
|
40
|
+
export declare function encodeDirListing(data: Record<string, number>): Uint8Array;
|
|
41
41
|
export type Callback<Args extends unknown[] = [], NoError = undefined | void> = (e: ErrnoError | NoError, ...args: OptionalTuple<Args>) => unknown;
|
|
42
42
|
/**
|
|
43
43
|
* Normalizes a mode
|
|
44
|
+
* @param def default
|
|
44
45
|
* @internal
|
|
45
46
|
*/
|
|
46
47
|
export declare function normalizeMode(mode: unknown, def?: number): number;
|
|
@@ -48,7 +49,7 @@ export declare function normalizeMode(mode: unknown, def?: number): number;
|
|
|
48
49
|
* Normalizes a time
|
|
49
50
|
* @internal
|
|
50
51
|
*/
|
|
51
|
-
export declare function normalizeTime(time: string | number | Date):
|
|
52
|
+
export declare function normalizeTime(time: string | number | Date): number;
|
|
52
53
|
/**
|
|
53
54
|
* Normalizes a path
|
|
54
55
|
* @internal
|
|
@@ -75,3 +76,16 @@ export type Concrete<T extends ClassLike> = Pick<T, keyof T> & (new (...args: an
|
|
|
75
76
|
* @internal
|
|
76
77
|
*/
|
|
77
78
|
export declare function randomBigInt(): bigint;
|
|
79
|
+
/**
|
|
80
|
+
* Prevents infinite loops
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
export declare function canary(path?: string, syscall?: string): () => void;
|
|
84
|
+
/**
|
|
85
|
+
* A wrapper for throwing things.
|
|
86
|
+
* Used in expressions.
|
|
87
|
+
* @todo Remove once `throw` is allowed in expressions
|
|
88
|
+
* @see https://github.com/tc39/proposal-throw-expressions
|
|
89
|
+
* @internal @hidden
|
|
90
|
+
*/
|
|
91
|
+
export declare function _throw(e: unknown): never;
|
package/dist/utils.js
CHANGED
|
@@ -34,7 +34,9 @@ export function encodeUTF8(input) {
|
|
|
34
34
|
}
|
|
35
35
|
return encoder.encode(input);
|
|
36
36
|
}
|
|
37
|
+
/* node:coverage disable */
|
|
37
38
|
export { /** @deprecated @hidden */ encodeUTF8 as encode };
|
|
39
|
+
/* node:coverage enable */
|
|
38
40
|
const decoder = new TextDecoder();
|
|
39
41
|
/**
|
|
40
42
|
* Decodes a string from a buffer
|
|
@@ -46,38 +48,39 @@ export function decodeUTF8(input) {
|
|
|
46
48
|
}
|
|
47
49
|
return decoder.decode(input);
|
|
48
50
|
}
|
|
51
|
+
/* node:coverage disable */
|
|
49
52
|
export { /** @deprecated @hidden */ decodeUTF8 as decode };
|
|
53
|
+
/* node:coverage enable */
|
|
50
54
|
/**
|
|
51
55
|
* Decodes a directory listing
|
|
52
56
|
* @hidden
|
|
53
57
|
*/
|
|
54
58
|
export function decodeDirListing(data) {
|
|
55
|
-
return JSON.parse(decodeUTF8(data), (k, v) => (k == '' ? v : BigInt(v)));
|
|
59
|
+
return JSON.parse(decodeUTF8(data), (k, v) => (k == '' ? v : typeof v == 'string' ? BigInt(v).toString(16).slice(0, Math.min(v.length, 8)) : v));
|
|
56
60
|
}
|
|
57
61
|
/**
|
|
58
62
|
* Encodes a directory listing
|
|
59
63
|
* @hidden
|
|
60
64
|
*/
|
|
61
65
|
export function encodeDirListing(data) {
|
|
62
|
-
return encodeUTF8(JSON.stringify(data
|
|
66
|
+
return encodeUTF8(JSON.stringify(data));
|
|
63
67
|
}
|
|
64
68
|
/**
|
|
65
69
|
* Normalizes a mode
|
|
70
|
+
* @param def default
|
|
66
71
|
* @internal
|
|
67
72
|
*/
|
|
68
73
|
export function normalizeMode(mode, def) {
|
|
69
|
-
if (typeof mode == 'number')
|
|
74
|
+
if (typeof mode == 'number')
|
|
70
75
|
return mode;
|
|
71
|
-
}
|
|
72
76
|
if (typeof mode == 'string') {
|
|
73
77
|
const parsed = parseInt(mode, 8);
|
|
74
78
|
if (!isNaN(parsed)) {
|
|
75
79
|
return parsed;
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
|
-
if (typeof def == 'number')
|
|
82
|
+
if (typeof def == 'number')
|
|
79
83
|
return def;
|
|
80
|
-
}
|
|
81
84
|
throw new ErrnoError(Errno.EINVAL, 'Invalid mode: ' + (mode === null || mode === void 0 ? void 0 : mode.toString()));
|
|
82
85
|
}
|
|
83
86
|
/**
|
|
@@ -85,11 +88,10 @@ export function normalizeMode(mode, def) {
|
|
|
85
88
|
* @internal
|
|
86
89
|
*/
|
|
87
90
|
export function normalizeTime(time) {
|
|
88
|
-
if (time instanceof Date)
|
|
89
|
-
return time;
|
|
90
|
-
}
|
|
91
|
+
if (time instanceof Date)
|
|
92
|
+
return time.getTime();
|
|
91
93
|
try {
|
|
92
|
-
return
|
|
94
|
+
return Number(time);
|
|
93
95
|
}
|
|
94
96
|
catch {
|
|
95
97
|
throw new ErrnoError(Errno.EINVAL, 'Invalid time.');
|
|
@@ -138,3 +140,23 @@ export function normalizeOptions(options, encoding = 'utf8', flag, mode = 0) {
|
|
|
138
140
|
export function randomBigInt() {
|
|
139
141
|
return BigInt('0x' + randomHex(8));
|
|
140
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Prevents infinite loops
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
export function canary(path, syscall) {
|
|
148
|
+
const timeout = setTimeout(() => {
|
|
149
|
+
throw ErrnoError.With('EDEADLK', path, syscall);
|
|
150
|
+
}, 5000);
|
|
151
|
+
return () => clearTimeout(timeout);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* A wrapper for throwing things.
|
|
155
|
+
* Used in expressions.
|
|
156
|
+
* @todo Remove once `throw` is allowed in expressions
|
|
157
|
+
* @see https://github.com/tc39/proposal-throw-expressions
|
|
158
|
+
* @internal @hidden
|
|
159
|
+
*/
|
|
160
|
+
export function _throw(e) {
|
|
161
|
+
throw e;
|
|
162
|
+
}
|
package/dist/vfs/constants.d.ts
CHANGED
|
@@ -111,7 +111,7 @@ export declare const S_IXOTH = 1;
|
|
|
111
111
|
*/
|
|
112
112
|
export declare const UV_FS_O_FILEMAP = 0;
|
|
113
113
|
/**
|
|
114
|
-
* Max 32-bit integer
|
|
114
|
+
* Max 32-bit unsigned integer
|
|
115
115
|
* @hidden
|
|
116
116
|
*/
|
|
117
|
-
export declare const size_max
|
|
117
|
+
export declare const size_max = 4294967295;
|
package/dist/vfs/constants.js
CHANGED
package/dist/vfs/dir.js
CHANGED
|
@@ -66,7 +66,9 @@ export class Dir {
|
|
|
66
66
|
async _read() {
|
|
67
67
|
var _a, _b;
|
|
68
68
|
this.checkClosed();
|
|
69
|
-
(_a = this._entries) !== null && _a !== void 0 ? _a : (this._entries = await readdir.call(this.context, this.path, {
|
|
69
|
+
(_a = this._entries) !== null && _a !== void 0 ? _a : (this._entries = await readdir.call(this.context, this.path, {
|
|
70
|
+
withFileTypes: true,
|
|
71
|
+
}));
|
|
70
72
|
if (!this._entries.length)
|
|
71
73
|
return null;
|
|
72
74
|
return (_b = this._entries.shift()) !== null && _b !== void 0 ? _b : null;
|
package/dist/vfs/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * as constants from './constants.js';
|
|
|
4
4
|
export * from './dir.js';
|
|
5
5
|
export * as promises from './promises.js';
|
|
6
6
|
export { chroot, mount, mountObject,
|
|
7
|
+
/* node:coverage disable */
|
|
7
8
|
/**
|
|
8
9
|
* The map of mount points.
|
|
9
10
|
* Using `fs.mounts` instead of the `mounts` export is a security issue and not recommended.
|
|
@@ -11,6 +12,8 @@ export { chroot, mount, mountObject,
|
|
|
11
12
|
* @todo [BREAKING] remove `fs.mounts` for security.
|
|
12
13
|
* @deprecated Use the `mounts` export that isn't an `fs` property!
|
|
13
14
|
*/
|
|
14
|
-
mounts,
|
|
15
|
+
mounts,
|
|
16
|
+
/* node:coverage enable */
|
|
17
|
+
umount, } from './shared.js';
|
|
15
18
|
export * from './streams.js';
|
|
16
19
|
export * from './sync.js';
|
package/dist/vfs/promises.js
CHANGED
|
@@ -213,7 +213,10 @@ export class FileHandle {
|
|
|
213
213
|
if (!('ReadableStream' in _gt)) {
|
|
214
214
|
throw new ErrnoError(Errno.ENOSYS, 'ReadableStream is missing on globalThis');
|
|
215
215
|
}
|
|
216
|
-
return new _gt.ReadableStream({
|
|
216
|
+
return new _gt.ReadableStream({
|
|
217
|
+
start,
|
|
218
|
+
type: options.type,
|
|
219
|
+
});
|
|
217
220
|
}
|
|
218
221
|
/**
|
|
219
222
|
* @todo Implement
|
|
@@ -478,11 +481,11 @@ async function applySetId(file, uid, gid) {
|
|
|
478
481
|
* Opens a file. This helper handles the complexity of file flags.
|
|
479
482
|
* @internal
|
|
480
483
|
*/
|
|
481
|
-
async function _open(path,
|
|
484
|
+
async function _open(path, opt) {
|
|
482
485
|
var _a;
|
|
483
486
|
path = normalizePath(path);
|
|
484
|
-
const mode = normalizeMode(
|
|
485
|
-
path =
|
|
487
|
+
const mode = normalizeMode(opt.mode, 0o644), flag = parseFlag(opt.flag);
|
|
488
|
+
path = opt.preserveSymlinks ? path : await realpath.call(this, path);
|
|
486
489
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
487
490
|
const stats = await fs.stat(resolved).catch(() => null);
|
|
488
491
|
if (!stats) {
|
|
@@ -527,7 +530,7 @@ async function _open(path, _flag, _mode = 0o644, resolveSymlinks) {
|
|
|
527
530
|
* @param mode Mode to use to open the file. Can be ignored if the filesystem doesn't support permissions.
|
|
528
531
|
*/
|
|
529
532
|
export async function open(path, flag = 'r', mode = 0o644) {
|
|
530
|
-
return await _open.call(this, path, flag, mode
|
|
533
|
+
return await _open.call(this, path, { flag, mode });
|
|
531
534
|
}
|
|
532
535
|
open;
|
|
533
536
|
export async function readFile(path, _options) {
|
|
@@ -771,7 +774,7 @@ export async function symlink(target, path, type = 'file') {
|
|
|
771
774
|
if (await exists.call(this, path)) {
|
|
772
775
|
throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
|
|
773
776
|
}
|
|
774
|
-
const handle = __addDisposableResource(env_5, await _open.call(this, path, 'w+', 0o644,
|
|
777
|
+
const handle = __addDisposableResource(env_5, await _open.call(this, path, { flag: 'w+', mode: 0o644, preserveSymlinks: true }), true);
|
|
775
778
|
await handle.writeFile(target.toString());
|
|
776
779
|
await handle.file.chmod(constants.S_IFLNK);
|
|
777
780
|
}
|
|
@@ -789,7 +792,7 @@ symlink;
|
|
|
789
792
|
export async function readlink(path, options) {
|
|
790
793
|
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
791
794
|
try {
|
|
792
|
-
const handle = __addDisposableResource(env_6, await _open.call(this, normalizePath(path), 'r', 0o644,
|
|
795
|
+
const handle = __addDisposableResource(env_6, await _open.call(this, normalizePath(path), { flag: 'r', mode: 0o644, preserveSymlinks: true }), true);
|
|
793
796
|
const value = await handle.readFile();
|
|
794
797
|
const encoding = typeof options == 'object' ? options === null || options === void 0 ? void 0 : options.encoding : options;
|
|
795
798
|
// always defaults to utf-8 to avoid wrangler (cloudflare) worker "unknown encoding" exception
|
|
@@ -826,7 +829,12 @@ chown;
|
|
|
826
829
|
export async function lchown(path, uid, gid) {
|
|
827
830
|
const env_8 = { stack: [], error: void 0, hasError: false };
|
|
828
831
|
try {
|
|
829
|
-
const handle = __addDisposableResource(env_8, await _open.call(this, path,
|
|
832
|
+
const handle = __addDisposableResource(env_8, await _open.call(this, path, {
|
|
833
|
+
flag: 'r+',
|
|
834
|
+
mode: 0o644,
|
|
835
|
+
preserveSymlinks: true,
|
|
836
|
+
allowDirectory: true,
|
|
837
|
+
}), true);
|
|
830
838
|
await handle.chown(uid, gid);
|
|
831
839
|
}
|
|
832
840
|
catch (e_8) {
|
|
@@ -860,7 +868,12 @@ chmod;
|
|
|
860
868
|
export async function lchmod(path, mode) {
|
|
861
869
|
const env_10 = { stack: [], error: void 0, hasError: false };
|
|
862
870
|
try {
|
|
863
|
-
const handle = __addDisposableResource(env_10, await _open.call(this, path,
|
|
871
|
+
const handle = __addDisposableResource(env_10, await _open.call(this, path, {
|
|
872
|
+
flag: 'r+',
|
|
873
|
+
mode: 0o644,
|
|
874
|
+
preserveSymlinks: true,
|
|
875
|
+
allowDirectory: true,
|
|
876
|
+
}), true);
|
|
864
877
|
await handle.chmod(mode);
|
|
865
878
|
}
|
|
866
879
|
catch (e_10) {
|
|
@@ -900,7 +913,12 @@ utimes;
|
|
|
900
913
|
export async function lutimes(path, atime, mtime) {
|
|
901
914
|
const env_12 = { stack: [], error: void 0, hasError: false };
|
|
902
915
|
try {
|
|
903
|
-
const handle = __addDisposableResource(env_12, await _open.call(this, path,
|
|
916
|
+
const handle = __addDisposableResource(env_12, await _open.call(this, path, {
|
|
917
|
+
flag: 'r+',
|
|
918
|
+
mode: 0o644,
|
|
919
|
+
preserveSymlinks: true,
|
|
920
|
+
allowDirectory: true,
|
|
921
|
+
}), true);
|
|
904
922
|
await handle.utimes(new Date(atime), new Date(mtime));
|
|
905
923
|
}
|
|
906
924
|
catch (e_12) {
|
|
@@ -1006,7 +1024,9 @@ export async function rm(path, options) {
|
|
|
1006
1024
|
switch (stats.mode & constants.S_IFMT) {
|
|
1007
1025
|
case constants.S_IFDIR:
|
|
1008
1026
|
if (options === null || options === void 0 ? void 0 : options.recursive) {
|
|
1009
|
-
for (const entry of await readdir.call(this, path, {
|
|
1027
|
+
for (const entry of await readdir.call(this, path, {
|
|
1028
|
+
_isIndirect: true,
|
|
1029
|
+
})) {
|
|
1010
1030
|
await rm.call(this, join(path, entry), { ...options, _isIndirect: true });
|
|
1011
1031
|
}
|
|
1012
1032
|
}
|
package/dist/vfs/shared.js
CHANGED
|
@@ -113,6 +113,7 @@ export function fixError(e, paths) {
|
|
|
113
113
|
e.path = fixPaths(e.path, paths);
|
|
114
114
|
return e;
|
|
115
115
|
}
|
|
116
|
+
/* node:coverage disable */
|
|
116
117
|
/**
|
|
117
118
|
* @internal @deprecated
|
|
118
119
|
*/
|
|
@@ -124,6 +125,7 @@ export function mountObject(mounts) {
|
|
|
124
125
|
mount(point, fs);
|
|
125
126
|
}
|
|
126
127
|
}
|
|
128
|
+
/* node:coverage enable */
|
|
127
129
|
/**
|
|
128
130
|
* @internal @hidden
|
|
129
131
|
*/
|
package/dist/vfs/sync.js
CHANGED
|
@@ -134,7 +134,7 @@ lstatSync;
|
|
|
134
134
|
export function truncateSync(path, len = 0) {
|
|
135
135
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
136
136
|
try {
|
|
137
|
-
const file = __addDisposableResource(env_1, _openSync.call(this, path, 'r+'), false);
|
|
137
|
+
const file = __addDisposableResource(env_1, _openSync.call(this, path, { flag: 'r+' }), false);
|
|
138
138
|
len || (len = 0);
|
|
139
139
|
if (len < 0) {
|
|
140
140
|
throw new ErrnoError(Errno.EINVAL);
|
|
@@ -176,11 +176,11 @@ function applySetId(file, uid, gid) {
|
|
|
176
176
|
file.chownSync(parent.mode & constants.S_ISUID ? parent.uid : uid, // manually apply setuid/setgid
|
|
177
177
|
parent.mode & constants.S_ISGID ? parent.gid : gid);
|
|
178
178
|
}
|
|
179
|
-
function _openSync(path,
|
|
179
|
+
function _openSync(path, opt) {
|
|
180
180
|
var _a;
|
|
181
181
|
path = normalizePath(path);
|
|
182
|
-
const mode = normalizeMode(
|
|
183
|
-
path =
|
|
182
|
+
const mode = normalizeMode(opt.mode, 0o644), flag = parseFlag(opt.flag);
|
|
183
|
+
path = opt.preserveSymlinks ? path : realpathSync.call(this, path);
|
|
184
184
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
185
185
|
let stats;
|
|
186
186
|
try {
|
|
@@ -203,6 +203,8 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
|
|
|
203
203
|
}
|
|
204
204
|
const { euid: uid, egid: gid } = (_a = this === null || this === void 0 ? void 0 : this.credentials) !== null && _a !== void 0 ? _a : credentials;
|
|
205
205
|
const file = fs.createFileSync(resolved, flag, mode, { uid, gid });
|
|
206
|
+
if (!opt.allowDirectory && mode & constants.S_IFDIR)
|
|
207
|
+
throw ErrnoError.With('EISDIR', path, '_open');
|
|
206
208
|
applySetId(file, uid, gid);
|
|
207
209
|
return file;
|
|
208
210
|
}
|
|
@@ -216,6 +218,8 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
|
|
|
216
218
|
if (isTruncating(flag)) {
|
|
217
219
|
file.truncateSync(0);
|
|
218
220
|
}
|
|
221
|
+
if (!opt.allowDirectory && stats.mode & constants.S_IFDIR)
|
|
222
|
+
throw ErrnoError.With('EISDIR', path, '_open');
|
|
219
223
|
return file;
|
|
220
224
|
}
|
|
221
225
|
/**
|
|
@@ -223,7 +227,7 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
|
|
|
223
227
|
* @see http://www.manpagez.com/man/2/open/
|
|
224
228
|
*/
|
|
225
229
|
export function openSync(path, flag, mode = constants.F_OK) {
|
|
226
|
-
return file2fd(_openSync.call(this, path, flag, mode
|
|
230
|
+
return file2fd(_openSync.call(this, path, { flag, mode }));
|
|
227
231
|
}
|
|
228
232
|
openSync;
|
|
229
233
|
/**
|
|
@@ -231,13 +235,13 @@ openSync;
|
|
|
231
235
|
* @internal
|
|
232
236
|
*/
|
|
233
237
|
export function lopenSync(path, flag, mode) {
|
|
234
|
-
return file2fd(_openSync.call(this, path, flag, mode,
|
|
238
|
+
return file2fd(_openSync.call(this, path, { flag, mode, preserveSymlinks: true }));
|
|
235
239
|
}
|
|
236
|
-
function _readFileSync(fname, flag,
|
|
240
|
+
function _readFileSync(fname, flag, preserveSymlinks) {
|
|
237
241
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
238
242
|
try {
|
|
239
243
|
// Get file.
|
|
240
|
-
const file = __addDisposableResource(env_2, _openSync.call(this, fname, flag, 0o644,
|
|
244
|
+
const file = __addDisposableResource(env_2, _openSync.call(this, fname, { flag, mode: 0o644, preserveSymlinks }), false);
|
|
241
245
|
const stat = file.statSync();
|
|
242
246
|
// Allocate buffer.
|
|
243
247
|
const data = new Uint8Array(stat.size);
|
|
@@ -258,7 +262,7 @@ export function readFileSync(path, _options = {}) {
|
|
|
258
262
|
if (!isReadable(flag)) {
|
|
259
263
|
throw new ErrnoError(Errno.EINVAL, 'Flag passed to readFile must allow for reading.');
|
|
260
264
|
}
|
|
261
|
-
const data = Buffer.from(_readFileSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), options.flag,
|
|
265
|
+
const data = Buffer.from(_readFileSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), options.flag, false));
|
|
262
266
|
return options.encoding ? data.toString(options.encoding) : data;
|
|
263
267
|
}
|
|
264
268
|
readFileSync;
|
|
@@ -277,7 +281,11 @@ export function writeFileSync(path, data, _options = {}) {
|
|
|
277
281
|
if (!encodedData) {
|
|
278
282
|
throw new ErrnoError(Errno.EINVAL, 'Data not specified');
|
|
279
283
|
}
|
|
280
|
-
const file = __addDisposableResource(env_3, _openSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(),
|
|
284
|
+
const file = __addDisposableResource(env_3, _openSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), {
|
|
285
|
+
flag,
|
|
286
|
+
mode: options.mode,
|
|
287
|
+
preserveSymlinks: true,
|
|
288
|
+
}), false);
|
|
281
289
|
file.writeSync(encodedData, 0, encodedData.byteLength, 0);
|
|
282
290
|
emitChange('change', path.toString());
|
|
283
291
|
}
|
|
@@ -308,7 +316,11 @@ export function appendFileSync(filename, data, _options = {}) {
|
|
|
308
316
|
throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
|
|
309
317
|
}
|
|
310
318
|
const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
311
|
-
const file = __addDisposableResource(env_4, _openSync.call(this, typeof filename == 'number' ? fd2file(filename).path : filename.toString(),
|
|
319
|
+
const file = __addDisposableResource(env_4, _openSync.call(this, typeof filename == 'number' ? fd2file(filename).path : filename.toString(), {
|
|
320
|
+
flag,
|
|
321
|
+
mode: options.mode,
|
|
322
|
+
preserveSymlinks: true,
|
|
323
|
+
}), false);
|
|
312
324
|
file.writeSync(encodedData, 0, encodedData.byteLength);
|
|
313
325
|
}
|
|
314
326
|
catch (e_4) {
|
|
@@ -560,12 +572,12 @@ export function symlinkSync(target, path, type = 'file') {
|
|
|
560
572
|
throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
|
|
561
573
|
}
|
|
562
574
|
writeFileSync.call(this, path, target.toString());
|
|
563
|
-
const file = _openSync.call(this, path, 'r+', 0o644,
|
|
575
|
+
const file = _openSync.call(this, path, { flag: 'r+', mode: 0o644, preserveSymlinks: true });
|
|
564
576
|
file.chmodSync(constants.S_IFLNK);
|
|
565
577
|
}
|
|
566
578
|
symlinkSync;
|
|
567
579
|
export function readlinkSync(path, options) {
|
|
568
|
-
const value = Buffer.from(_readFileSync.call(this, path.toString(), 'r',
|
|
580
|
+
const value = Buffer.from(_readFileSync.call(this, path.toString(), 'r', true));
|
|
569
581
|
const encoding = typeof options == 'object' ? options === null || options === void 0 ? void 0 : options.encoding : options;
|
|
570
582
|
if (encoding == 'buffer') {
|
|
571
583
|
return value;
|
package/dist/vfs/types.d.ts
CHANGED
|
@@ -11,6 +11,21 @@ export interface InternalOptions {
|
|
|
11
11
|
*/
|
|
12
12
|
_isIndirect?: boolean;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* @internal @hidden Used for the internal `_open` functions
|
|
16
|
+
*/
|
|
17
|
+
export interface OpenOptions {
|
|
18
|
+
flag: fs.OpenMode;
|
|
19
|
+
mode?: fs.Mode | null;
|
|
20
|
+
/**
|
|
21
|
+
* If true, do not resolve symlinks
|
|
22
|
+
*/
|
|
23
|
+
preserveSymlinks?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* If true, allows opening directories
|
|
26
|
+
*/
|
|
27
|
+
allowDirectory?: boolean;
|
|
28
|
+
}
|
|
14
29
|
export interface ReaddirOptions extends InternalOptions {
|
|
15
30
|
withFileTypes?: boolean;
|
|
16
31
|
recursive?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "A filesystem, anywhere",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -57,8 +57,7 @@
|
|
|
57
57
|
"format": "prettier --write .",
|
|
58
58
|
"format:check": "prettier --check .",
|
|
59
59
|
"lint": "eslint src tests",
|
|
60
|
-
"test": "
|
|
61
|
-
"pretest": "npm run build",
|
|
60
|
+
"test": "npx zenfs-test --clean && npx zenfs-test -abcfp && tests/fetch/run.sh && npx zenfs-test --report",
|
|
62
61
|
"build": "tsc -p tsconfig.json",
|
|
63
62
|
"build:docs": "typedoc",
|
|
64
63
|
"dev": "npm run build -- --watch",
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ZenFS
|
|
2
2
|
|
|
3
|
-
ZenFS is a
|
|
3
|
+
ZenFS is a cross-platform library that emulates the [NodeJS filesystem API](http://nodejs.org/api/fs.html). It works using a system of backends, which are used by ZenFS to store and retrieve data. ZenFS can also integrate with other tools.
|
|
4
4
|
|
|
5
5
|
## Backends
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ ZenFS is modular and extensible. The core includes some built-in backends:
|
|
|
8
8
|
|
|
9
9
|
- `InMemory`: Stores files in-memory. This is cleared when the runtime ends (e.g. a user navigating away from a web page or a Node process exiting)
|
|
10
10
|
- `Overlay`: Use a read-only file system as read-write by overlaying a writable file system on top of it. ([copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write))
|
|
11
|
-
- `Fetch`: Downloads files over HTTP with the `fetch` API
|
|
11
|
+
- `Fetch`: Downloads files over HTTP with the `fetch` API
|
|
12
12
|
- `Port`: Interacts with a remote over a `MessagePort`-like interface (e.g. a worker)
|
|
13
13
|
- `Passthrough`: Use an existing `node:fs` interface with ZenFS
|
|
14
14
|
|