@zenfs/core 2.0.0 → 2.1.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 +6 -5
- package/dist/backends/cow.d.ts +2 -2
- package/dist/backends/cow.js +39 -58
- package/dist/backends/fetch.js +27 -29
- package/dist/backends/passthrough.d.ts +2 -3
- package/dist/backends/passthrough.js +84 -199
- package/dist/backends/port.d.ts +16 -3
- package/dist/backends/port.js +61 -30
- package/dist/backends/single_buffer.d.ts +52 -46
- package/dist/backends/single_buffer.js +462 -219
- package/dist/backends/store/fs.d.ts +16 -10
- package/dist/backends/store/fs.js +227 -242
- package/dist/backends/store/store.d.ts +3 -3
- package/dist/backends/store/store.js +11 -10
- package/dist/config.d.ts +2 -2
- package/dist/config.js +10 -11
- package/dist/internal/devices.d.ts +2 -2
- package/dist/internal/devices.js +39 -49
- package/dist/internal/error.d.ts +9 -204
- package/dist/internal/error.js +19 -288
- package/dist/internal/file_index.d.ts +1 -1
- package/dist/internal/file_index.js +9 -9
- package/dist/internal/filesystem.d.ts +23 -8
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/internal/index_fs.d.ts +2 -2
- package/dist/internal/index_fs.js +19 -19
- package/dist/internal/inode.d.ts +81 -103
- package/dist/internal/inode.js +336 -195
- package/dist/mixins/async.js +32 -28
- package/dist/mixins/mutexed.d.ts +4 -4
- package/dist/mixins/mutexed.js +39 -39
- package/dist/mixins/readonly.d.ts +2 -2
- package/dist/mixins/readonly.js +20 -20
- package/dist/mixins/sync.js +2 -2
- package/dist/polyfills.js +1 -1
- package/dist/readline.js +1 -1
- package/dist/utils.d.ts +8 -5
- package/dist/utils.js +14 -17
- package/dist/vfs/acl.d.ts +8 -8
- package/dist/vfs/acl.js +66 -47
- package/dist/vfs/async.d.ts +2 -2
- package/dist/vfs/async.js +6 -8
- package/dist/vfs/dir.d.ts +1 -1
- package/dist/vfs/dir.js +3 -4
- package/dist/vfs/file.js +33 -24
- package/dist/vfs/flags.js +3 -3
- package/dist/vfs/ioctl.d.ts +8 -7
- package/dist/vfs/ioctl.js +132 -27
- package/dist/vfs/promises.d.ts +3 -3
- package/dist/vfs/promises.js +200 -235
- package/dist/vfs/shared.d.ts +1 -12
- package/dist/vfs/shared.js +7 -35
- package/dist/vfs/streams.js +9 -9
- package/dist/vfs/sync.d.ts +1 -2
- package/dist/vfs/sync.js +158 -170
- package/dist/vfs/watchers.js +8 -8
- package/dist/vfs/xattr.js +89 -106
- package/package.json +4 -2
- package/scripts/test.js +2 -2
- package/tests/assignment.ts +1 -1
- package/tests/backend/port.test.ts +4 -4
- package/tests/backend/single-buffer.test.ts +39 -10
- package/tests/backend/single-buffer.worker.js +30 -0
- package/tests/common/context.test.ts +2 -2
- package/tests/common/mutex.test.ts +9 -9
- package/tests/fetch/fetch.ts +1 -1
- package/tests/fs/append.test.ts +4 -4
- package/tests/fs/directory.test.ts +25 -25
- package/tests/fs/errors.test.ts +15 -19
- package/tests/fs/links.test.ts +3 -2
- package/tests/fs/open.test.ts +4 -21
- package/tests/fs/permissions.test.ts +8 -13
- package/tests/fs/read.test.ts +10 -9
- package/tests/fs/readFile.test.ts +8 -24
- package/tests/fs/rename.test.ts +4 -9
- package/tests/fs/stat.test.ts +2 -2
- package/tests/fs/times.test.ts +6 -6
- package/tests/fs/truncate.test.ts +8 -36
- package/tests/fs/watch.test.ts +10 -10
- package/tests/fs/write.test.ts +77 -13
- package/tests/fs/xattr.test.ts +7 -7
- package/tests/logs.js +2 -2
- package/tests/setup/port.ts +6 -0
- package/dist/internal/log.d.ts +0 -139
- package/dist/internal/log.js +0 -219
- package/tests/fs/writeFile.test.ts +0 -70
package/dist/vfs/xattr.js
CHANGED
|
@@ -1,62 +1,55 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
|
-
import {
|
|
3
|
-
import { Errno, ErrnoError } from '../internal/error.js';
|
|
2
|
+
import { rethrow, setUVMessage, UV } from 'kerium';
|
|
4
3
|
import { Attributes, hasAccess } from '../internal/inode.js';
|
|
5
4
|
import { normalizePath } from '../utils.js';
|
|
6
5
|
import { checkAccess } from './config.js';
|
|
7
6
|
import { R_OK, W_OK } from './constants.js';
|
|
8
|
-
import {
|
|
7
|
+
import { resolveMount } from './shared.js';
|
|
9
8
|
const _allowedRestrictedNames = [];
|
|
10
9
|
/**
|
|
11
10
|
* Check permission for the attribute name.
|
|
12
11
|
* For now, only attributes in the 'user' namespace are supported.
|
|
13
|
-
* @throws
|
|
12
|
+
* @throws ENOTSUP for attributes in namespaces other than 'user'
|
|
14
13
|
*/
|
|
15
14
|
function checkName($, name, path, syscall) {
|
|
16
15
|
if (!name.startsWith('user.') && !_allowedRestrictedNames.includes(name))
|
|
17
|
-
throw
|
|
16
|
+
throw UV('ENOTSUP', syscall, path);
|
|
18
17
|
}
|
|
19
18
|
export async function get(path, name, opt = {}) {
|
|
20
19
|
var _a;
|
|
21
20
|
path = normalizePath(path);
|
|
22
21
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
23
22
|
checkName(this, name, path, 'xattr.get');
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const buffer = Buffer.from(attr.value);
|
|
34
|
-
return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding);
|
|
35
|
-
}
|
|
36
|
-
catch (e) {
|
|
37
|
-
throw fixError(e, { [resolved]: path });
|
|
38
|
-
}
|
|
23
|
+
const inode = await fs.stat(resolved).catch(rethrow('xattr.get', path));
|
|
24
|
+
if (checkAccess && !hasAccess(this, inode, R_OK))
|
|
25
|
+
throw UV('EACCES', 'xattr.get', path);
|
|
26
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
27
|
+
const value = inode.attributes.get(name);
|
|
28
|
+
if (!value)
|
|
29
|
+
throw UV('ENODATA', 'xattr.get', path);
|
|
30
|
+
const buffer = Buffer.from(value);
|
|
31
|
+
return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding);
|
|
39
32
|
}
|
|
40
33
|
export function getSync(path, name, opt = {}) {
|
|
41
34
|
var _a;
|
|
42
35
|
path = normalizePath(path);
|
|
43
36
|
checkName(this, name, path, 'xattr.get');
|
|
44
37
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
38
|
+
let inode;
|
|
45
39
|
try {
|
|
46
|
-
|
|
47
|
-
if (checkAccess && !hasAccess(this, inode, R_OK)) {
|
|
48
|
-
throw ErrnoError.With('EACCES', resolved, 'xattr.get');
|
|
49
|
-
}
|
|
50
|
-
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
51
|
-
const attr = inode.attributes.get(name);
|
|
52
|
-
if (!attr)
|
|
53
|
-
throw ErrnoError.With('ENODATA', resolved, 'xattr.get');
|
|
54
|
-
const buffer = Buffer.from(attr.value);
|
|
55
|
-
return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding);
|
|
40
|
+
inode = fs.statSync(resolved);
|
|
56
41
|
}
|
|
57
42
|
catch (e) {
|
|
58
|
-
throw
|
|
59
|
-
}
|
|
43
|
+
throw setUVMessage(Object.assign(e, { path }));
|
|
44
|
+
}
|
|
45
|
+
if (checkAccess && !hasAccess(this, inode, R_OK))
|
|
46
|
+
throw UV('EACCES', 'xattr.get', path);
|
|
47
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
48
|
+
const value = inode.attributes.get(name);
|
|
49
|
+
if (!value)
|
|
50
|
+
throw UV('ENODATA', 'xattr.get', path);
|
|
51
|
+
const buffer = Buffer.from(value);
|
|
52
|
+
return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding);
|
|
60
53
|
}
|
|
61
54
|
/**
|
|
62
55
|
* Sets the value of an extended attribute.
|
|
@@ -71,25 +64,17 @@ export async function set(path, name, value, opt = {}) {
|
|
|
71
64
|
path = normalizePath(path);
|
|
72
65
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
73
66
|
checkName(this, name, path, 'xattr.set');
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
throw ErrnoError.With('ENODATA', resolved, 'xattr.set');
|
|
86
|
-
}
|
|
87
|
-
inode.attributes.set(name, Buffer.from(value));
|
|
88
|
-
await fs.touch(resolved, pick(inode, 'attributes'));
|
|
89
|
-
}
|
|
90
|
-
catch (e) {
|
|
91
|
-
throw fixError(e, { [resolved]: path });
|
|
92
|
-
}
|
|
67
|
+
const inode = await fs.stat(resolved).catch(rethrow('xattr.set', path));
|
|
68
|
+
if (checkAccess && !hasAccess(this, inode, W_OK))
|
|
69
|
+
throw UV('EACCES', 'xattr.set', path);
|
|
70
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
71
|
+
const attr = inode.attributes.get(name);
|
|
72
|
+
if (opt.create && attr)
|
|
73
|
+
throw UV('EEXIST', 'xattr.set', path);
|
|
74
|
+
if (opt.replace && !attr)
|
|
75
|
+
throw UV('ENODATA', 'xattr.set', path);
|
|
76
|
+
inode.attributes.set(name, Buffer.from(value));
|
|
77
|
+
await fs.touch(resolved, inode).catch(rethrow('xattr.set', path));
|
|
93
78
|
}
|
|
94
79
|
/**
|
|
95
80
|
* Synchronously sets the value of an extended attribute.
|
|
@@ -104,24 +89,27 @@ export function setSync(path, name, value, opt = {}) {
|
|
|
104
89
|
path = normalizePath(path);
|
|
105
90
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
106
91
|
checkName(this, name, path, 'xattr.set');
|
|
92
|
+
let inode;
|
|
93
|
+
try {
|
|
94
|
+
inode = fs.statSync(resolved);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
throw setUVMessage(Object.assign(e, { path }));
|
|
98
|
+
}
|
|
99
|
+
if (checkAccess && !hasAccess(this, inode, W_OK))
|
|
100
|
+
throw UV('EACCES', 'xattr.set', path);
|
|
101
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
102
|
+
const attr = inode.attributes.get(name);
|
|
103
|
+
if (opt.create && attr)
|
|
104
|
+
throw UV('EEXIST', 'xattr.set', path);
|
|
105
|
+
if (opt.replace && !attr)
|
|
106
|
+
throw UV('ENODATA', 'xattr.set', path);
|
|
107
|
+
inode.attributes.set(name, Buffer.from(value));
|
|
107
108
|
try {
|
|
108
|
-
|
|
109
|
-
if (checkAccess && !hasAccess(this, inode, W_OK)) {
|
|
110
|
-
throw ErrnoError.With('EACCES', resolved, 'xattr.set');
|
|
111
|
-
}
|
|
112
|
-
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
113
|
-
const attr = inode.attributes.get(name);
|
|
114
|
-
if (opt.create && attr) {
|
|
115
|
-
throw ErrnoError.With('EEXIST', resolved, 'xattr.set');
|
|
116
|
-
}
|
|
117
|
-
if (opt.replace && !attr) {
|
|
118
|
-
throw ErrnoError.With('ENODATA', resolved, 'xattr.set');
|
|
119
|
-
}
|
|
120
|
-
inode.attributes.set(name, Buffer.from(value));
|
|
121
|
-
fs.touchSync(resolved, pick(inode, 'attributes'));
|
|
109
|
+
fs.touchSync(resolved, inode);
|
|
122
110
|
}
|
|
123
111
|
catch (e) {
|
|
124
|
-
throw
|
|
112
|
+
throw setUVMessage(Object.assign(e, { path }));
|
|
125
113
|
}
|
|
126
114
|
}
|
|
127
115
|
/**
|
|
@@ -135,21 +123,15 @@ export async function remove(path, name) {
|
|
|
135
123
|
path = normalizePath(path);
|
|
136
124
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
137
125
|
checkName(this, name, path, 'xattr.remove');
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
inode.attributes.remove(name);
|
|
148
|
-
await fs.touch(resolved, pick(inode, 'attributes'));
|
|
149
|
-
}
|
|
150
|
-
catch (e) {
|
|
151
|
-
throw fixError(e, { [resolved]: path });
|
|
152
|
-
}
|
|
126
|
+
const inode = await fs.stat(resolved).catch(rethrow('xattr.remove', path));
|
|
127
|
+
if (checkAccess && !hasAccess(this, inode, W_OK))
|
|
128
|
+
throw UV('EACCES', 'xattr.remove', path);
|
|
129
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
130
|
+
const attr = inode.attributes.get(name);
|
|
131
|
+
if (!attr)
|
|
132
|
+
throw UV('ENODATA', 'xattr.remove', path);
|
|
133
|
+
inode.attributes.remove(name);
|
|
134
|
+
await fs.touch(resolved, inode);
|
|
153
135
|
}
|
|
154
136
|
/**
|
|
155
137
|
* Synchronously removes an extended attribute from a file.
|
|
@@ -162,20 +144,25 @@ export function removeSync(path, name) {
|
|
|
162
144
|
path = normalizePath(path);
|
|
163
145
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
164
146
|
checkName(this, name, path, 'xattr.remove');
|
|
147
|
+
let inode;
|
|
165
148
|
try {
|
|
166
|
-
|
|
167
|
-
if (checkAccess && !hasAccess(this, inode, W_OK)) {
|
|
168
|
-
throw ErrnoError.With('EACCES', resolved, 'xattr.remove');
|
|
169
|
-
}
|
|
170
|
-
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
171
|
-
const attr = inode.attributes.get(name);
|
|
172
|
-
if (!attr)
|
|
173
|
-
throw ErrnoError.With('ENODATA', resolved, 'xattr.remove');
|
|
174
|
-
inode.attributes.remove(name);
|
|
175
|
-
fs.touchSync(resolved, pick(inode, 'attributes'));
|
|
149
|
+
inode = fs.statSync(resolved);
|
|
176
150
|
}
|
|
177
151
|
catch (e) {
|
|
178
|
-
throw
|
|
152
|
+
throw setUVMessage(Object.assign(e, { path }));
|
|
153
|
+
}
|
|
154
|
+
if (checkAccess && !hasAccess(this, inode, W_OK))
|
|
155
|
+
throw UV('EACCES', 'xattr.remove', path);
|
|
156
|
+
(_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
|
|
157
|
+
const attr = inode.attributes.get(name);
|
|
158
|
+
if (!attr)
|
|
159
|
+
throw UV('ENODATA', 'xattr.remove', path);
|
|
160
|
+
inode.attributes.remove(name);
|
|
161
|
+
try {
|
|
162
|
+
fs.touchSync(resolved, inode);
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
throw setUVMessage(Object.assign(e, { path }));
|
|
179
166
|
}
|
|
180
167
|
}
|
|
181
168
|
/**
|
|
@@ -187,15 +174,10 @@ export function removeSync(path, name) {
|
|
|
187
174
|
export async function list(path) {
|
|
188
175
|
path = normalizePath(path);
|
|
189
176
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return inode.attributes.keys();
|
|
195
|
-
}
|
|
196
|
-
catch (e) {
|
|
197
|
-
throw fixError(e, { [resolved]: path });
|
|
198
|
-
}
|
|
177
|
+
const inode = await fs.stat(resolved).catch(rethrow('xattr.list', path));
|
|
178
|
+
if (!inode.attributes)
|
|
179
|
+
return [];
|
|
180
|
+
return inode.attributes.keys().toArray();
|
|
199
181
|
}
|
|
200
182
|
/**
|
|
201
183
|
* Synchronously lists all extended attributes of a file.
|
|
@@ -206,13 +188,14 @@ export async function list(path) {
|
|
|
206
188
|
export function listSync(path) {
|
|
207
189
|
path = normalizePath(path);
|
|
208
190
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
191
|
+
let inode;
|
|
209
192
|
try {
|
|
210
|
-
|
|
211
|
-
if (!inode.attributes)
|
|
212
|
-
return [];
|
|
213
|
-
return inode.attributes.keys();
|
|
193
|
+
inode = fs.statSync(resolved);
|
|
214
194
|
}
|
|
215
195
|
catch (e) {
|
|
216
|
-
throw
|
|
196
|
+
throw setUVMessage(Object.assign(e, { path }));
|
|
217
197
|
}
|
|
198
|
+
if (!inode.attributes)
|
|
199
|
+
return [];
|
|
200
|
+
return inode.attributes.keys().toArray();
|
|
218
201
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A filesystem, anywhere",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -70,8 +70,10 @@
|
|
|
70
70
|
"@types/node": "^22.10.1 <22.13.7",
|
|
71
71
|
"buffer": "^6.0.3",
|
|
72
72
|
"eventemitter3": "^5.0.1",
|
|
73
|
+
"kerium": "^1.3.4",
|
|
74
|
+
"memium": "^0.2.0",
|
|
73
75
|
"readable-stream": "^4.5.2",
|
|
74
|
-
"utilium": "^
|
|
76
|
+
"utilium": "^2.3.3"
|
|
75
77
|
},
|
|
76
78
|
"devDependencies": {
|
|
77
79
|
"@eslint/js": "^9.8.0",
|
package/scripts/test.js
CHANGED
|
@@ -9,7 +9,7 @@ const { values: options, positionals } = parseArgs({
|
|
|
9
9
|
options: {
|
|
10
10
|
// Output
|
|
11
11
|
help: { short: 'h', type: 'boolean', default: false },
|
|
12
|
-
verbose: { short: '
|
|
12
|
+
verbose: { short: 'v', type: 'boolean', default: false },
|
|
13
13
|
quiet: { short: 'q', type: 'boolean', default: false },
|
|
14
14
|
log: { short: 'l', type: 'string', default: '' },
|
|
15
15
|
'file-names': { short: 'N', type: 'boolean', default: false },
|
|
@@ -49,7 +49,7 @@ Behavior:
|
|
|
49
49
|
|
|
50
50
|
Output:
|
|
51
51
|
-h, --help Outputs this help message
|
|
52
|
-
-
|
|
52
|
+
-v, --verbose Output verbose messages
|
|
53
53
|
-q, --quiet Don't output normal messages
|
|
54
54
|
-l, --logs <level> Change the default log level for test output. Level can be a number or string
|
|
55
55
|
-N, --file-names Use full file paths for tests from setup files instead of the base name
|
package/tests/assignment.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
- ReadStream and WriteStream are excluded since they are polyfilled from another module
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { fs as zen } from '../src/index.js';
|
|
14
13
|
import type * as node from 'node:fs';
|
|
14
|
+
import { fs as zen } from '../src/index.js';
|
|
15
15
|
|
|
16
16
|
type Mock = {
|
|
17
17
|
[K in Exclude<keyof typeof node, 'ReadStream' | 'WriteStream'>]: Omit<(typeof node)[K], '__promisify__' | 'native'>;
|
|
@@ -13,7 +13,7 @@ const timeoutChannel = new MessageChannel();
|
|
|
13
13
|
timeoutChannel.port2.unref();
|
|
14
14
|
|
|
15
15
|
await suite('Timeout', { timeout: 1000 }, () => {
|
|
16
|
-
test('Misconfiguration', () => {
|
|
16
|
+
test('Misconfiguration', async () => {
|
|
17
17
|
const configured = configure({
|
|
18
18
|
mounts: {
|
|
19
19
|
'/tmp-timeout': { backend: InMemory, label: 'tmp' },
|
|
@@ -21,13 +21,13 @@ await suite('Timeout', { timeout: 1000 }, () => {
|
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
assert.rejects(configured, { code: 'EIO', message: /RPC Failed/ });
|
|
24
|
+
await assert.rejects(configured, { code: 'EIO', message: /RPC Failed/ });
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
test('Remote not attached', () => {
|
|
27
|
+
test('Remote not attached', async () => {
|
|
28
28
|
const configured = configureSingle({ backend: Port, port: timeoutChannel.port1, timeout: 100 });
|
|
29
29
|
|
|
30
|
-
assert.rejects(configured, { code: 'EIO', message: /RPC Failed/ });
|
|
30
|
+
await assert.rejects(configured, { code: 'EIO', message: /RPC Failed/ });
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
33
|
|
|
@@ -1,24 +1,53 @@
|
|
|
1
|
-
import { test, suite } from 'node:test';
|
|
2
|
-
import { fs, mount, resolveMountConfig, SingleBuffer, umount } from '../../dist/index.js';
|
|
3
1
|
import assert from 'node:assert';
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
3
|
+
import { Worker } from 'worker_threads';
|
|
4
|
+
import { fs, mount, resolveMountConfig, SingleBuffer } from '../../dist/index.js';
|
|
5
|
+
import { setupLogs } from '../logs.js';
|
|
6
|
+
|
|
7
|
+
setupLogs();
|
|
4
8
|
|
|
5
9
|
await suite('SingleBuffer', () => {
|
|
6
|
-
test('
|
|
10
|
+
test('filesystem restoration from original buffer (with same metadata)', async () => {
|
|
7
11
|
const buffer = new ArrayBuffer(0x100000);
|
|
8
12
|
|
|
9
|
-
umount('/');
|
|
10
13
|
const writable = await resolveMountConfig({ backend: SingleBuffer, buffer });
|
|
11
|
-
mount('/', writable);
|
|
14
|
+
mount('/mnt', writable);
|
|
12
15
|
|
|
13
|
-
fs.writeFileSync('/example.ts', 'console.log("hello world")', 'utf-8');
|
|
14
|
-
const stats = fs.statSync('/example.ts');
|
|
16
|
+
fs.writeFileSync('/mnt/example.ts', 'console.log("hello world")', 'utf-8');
|
|
17
|
+
const stats = fs.statSync('/mnt/example.ts');
|
|
15
18
|
|
|
16
|
-
umount('/');
|
|
17
19
|
const snapshot = await resolveMountConfig({ backend: SingleBuffer, buffer });
|
|
18
|
-
mount('/', snapshot);
|
|
20
|
+
mount('/snapshot', snapshot);
|
|
19
21
|
|
|
20
|
-
const snapshotStats = fs.statSync('/example.ts');
|
|
22
|
+
const snapshotStats = fs.statSync('/snapshot/example.ts');
|
|
21
23
|
|
|
22
24
|
assert.deepEqual(snapshotStats, stats);
|
|
23
25
|
});
|
|
26
|
+
|
|
27
|
+
test('cross-thread SharedArrayBuffer', async () => {
|
|
28
|
+
const sharedBuffer = new SharedArrayBuffer(0x100000);
|
|
29
|
+
|
|
30
|
+
const writable = await resolveMountConfig({ backend: SingleBuffer, buffer: sharedBuffer });
|
|
31
|
+
fs.mkdirSync('/shared');
|
|
32
|
+
mount('/shared', writable);
|
|
33
|
+
|
|
34
|
+
const worker = new Worker(import.meta.dirname + '/single-buffer.worker.js', { workerData: sharedBuffer });
|
|
35
|
+
|
|
36
|
+
// Pause while we wait for the worker to emit the 'continue' message, which
|
|
37
|
+
// means it has mounted the filesystem and created /worker-file.ts
|
|
38
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
39
|
+
|
|
40
|
+
setTimeout(reject, 1000);
|
|
41
|
+
worker.on('message', message => {
|
|
42
|
+
if (message === 'continue') resolve();
|
|
43
|
+
else reject(message ?? new Error('Failed'));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await promise;
|
|
47
|
+
|
|
48
|
+
await worker.terminate();
|
|
49
|
+
worker.unref();
|
|
50
|
+
|
|
51
|
+
assert(fs.existsSync('/shared/worker-file.ts'));
|
|
52
|
+
});
|
|
24
53
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { writeFileSync as _write } from 'node:fs';
|
|
3
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
4
|
+
import { configureSingle, fs, SingleBuffer } from '../../dist/index.js';
|
|
5
|
+
import { setupLogs } from '../logs.js';
|
|
6
|
+
|
|
7
|
+
setupLogs('<worker>');
|
|
8
|
+
|
|
9
|
+
const content = 'console.log("this file was created by the worker")';
|
|
10
|
+
|
|
11
|
+
const view = new Uint8Array(workerData);
|
|
12
|
+
|
|
13
|
+
if (process.env.DEBUG) _write('tmp/shared.bin', view);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
await configureSingle({
|
|
17
|
+
backend: SingleBuffer,
|
|
18
|
+
buffer: workerData,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
fs.writeFileSync('/worker-file.ts', content, 'utf-8');
|
|
22
|
+
|
|
23
|
+
assert.equal(fs.readFileSync('/worker-file.ts', 'utf-8'), content);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
if (process.env.DEBUG) _write('tmp/shared.bin', view);
|
|
26
|
+
console.error(e);
|
|
27
|
+
parentPort.postMessage(e);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
parentPort.postMessage('continue');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { suite, test } from 'node:test';
|
|
2
1
|
import assert from 'node:assert/strict';
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
3
|
+
import { canary } from 'utilium';
|
|
3
4
|
import { bindContext } from '../../dist/context.js';
|
|
4
5
|
import * as fs from '../../dist/vfs/index.js';
|
|
5
|
-
import { canary } from 'utilium';
|
|
6
6
|
|
|
7
7
|
fs.mkdirSync('/ctx');
|
|
8
8
|
const { fs: ctx } = bindContext({ root: '/ctx' });
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
1
3
|
import { wait } from 'utilium';
|
|
2
|
-
import { Mutexed } from '../../dist/mixins/mutexed.js';
|
|
3
|
-
import { StoreFS } from '../../dist/backends/store/fs.js';
|
|
4
4
|
import { InMemoryStore } from '../../dist/backends/memory.js';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { StoreFS } from '../../dist/backends/store/fs.js';
|
|
6
|
+
import { Mutexed } from '../../dist/mixins/mutexed.js';
|
|
7
7
|
|
|
8
|
-
suite('
|
|
8
|
+
suite('Mutexed FS', () => {
|
|
9
9
|
const fs = new (Mutexed(StoreFS))(new InMemoryStore(0x10000, 'test'));
|
|
10
10
|
fs._fs.checkRootSync();
|
|
11
11
|
|
|
12
12
|
test('lock/unlock', () => {
|
|
13
|
-
const lock = fs.lockSync(
|
|
13
|
+
const lock = fs.lockSync();
|
|
14
14
|
assert(fs.isLocked);
|
|
15
15
|
lock.unlock();
|
|
16
16
|
assert(!fs.isLocked);
|
|
@@ -20,11 +20,11 @@ suite('LockFS mutex', () => {
|
|
|
20
20
|
let lock1Resolved = false;
|
|
21
21
|
let lock2Resolved = false;
|
|
22
22
|
|
|
23
|
-
const lock1 = fs.lock(
|
|
23
|
+
const lock1 = fs.lock().then(lock => {
|
|
24
24
|
lock1Resolved = true;
|
|
25
25
|
lock.unlock();
|
|
26
26
|
});
|
|
27
|
-
const lock2 = fs.lock(
|
|
27
|
+
const lock2 = fs.lock().then(lock => {
|
|
28
28
|
lock2Resolved = true;
|
|
29
29
|
lock.unlock();
|
|
30
30
|
});
|
|
@@ -50,7 +50,7 @@ suite('LockFS mutex', () => {
|
|
|
50
50
|
let x = 1;
|
|
51
51
|
|
|
52
52
|
async function foo() {
|
|
53
|
-
const lock = await fs.lock(
|
|
53
|
+
const lock = await fs.lock();
|
|
54
54
|
await wait(25);
|
|
55
55
|
x++;
|
|
56
56
|
lock.unlock();
|
package/tests/fetch/fetch.ts
CHANGED
package/tests/fs/append.test.ts
CHANGED
|
@@ -5,15 +5,15 @@ import { fs } from '../common.js';
|
|
|
5
5
|
const content = 'Sample content',
|
|
6
6
|
original = 'ABCD';
|
|
7
7
|
|
|
8
|
-
suite('
|
|
9
|
-
test('
|
|
8
|
+
suite('Appends', () => {
|
|
9
|
+
test('Create an empty file and add content', async () => {
|
|
10
10
|
const filename = 'append.txt';
|
|
11
11
|
await fs.promises.appendFile(filename, content);
|
|
12
12
|
const data = await fs.promises.readFile(filename, 'utf8');
|
|
13
13
|
assert.equal(data, content);
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
test('
|
|
16
|
+
test('Append data to a non-empty file', async () => {
|
|
17
17
|
const filename = 'append2.txt';
|
|
18
18
|
|
|
19
19
|
await fs.promises.writeFile(filename, original);
|
|
@@ -22,7 +22,7 @@ suite('appendFile', () => {
|
|
|
22
22
|
assert.equal(data, original + content);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
test('
|
|
25
|
+
test('Append a buffer to the file', async () => {
|
|
26
26
|
const filename = 'append3.txt';
|
|
27
27
|
|
|
28
28
|
await fs.promises.writeFile(filename, original);
|
|
@@ -27,7 +27,7 @@ suite('Directories', () => {
|
|
|
27
27
|
test('mkdirSync', async () => await fs.promises.mkdir('/two', 0o000));
|
|
28
28
|
|
|
29
29
|
test('mkdir, nested', async () => {
|
|
30
|
-
assert.rejects(fs.promises.mkdir('/nested/dir'), { code: 'ENOENT', path: '/nested' });
|
|
30
|
+
await assert.rejects(fs.promises.mkdir('/nested/dir'), { code: 'ENOENT', path: '/nested' });
|
|
31
31
|
assert(!(await fs.promises.exists('/nested/dir')));
|
|
32
32
|
});
|
|
33
33
|
|
|
@@ -59,39 +59,39 @@ suite('Directories', () => {
|
|
|
59
59
|
await fs.promises.mkdir('/rmdirTest');
|
|
60
60
|
await fs.promises.mkdir('/rmdirTest/rmdirTest2');
|
|
61
61
|
|
|
62
|
-
assert.rejects(fs.promises.rmdir('/rmdirTest'), { code: 'ENOTEMPTY' });
|
|
62
|
+
await assert.rejects(fs.promises.rmdir('/rmdirTest'), { code: 'ENOTEMPTY' });
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
test('readdirSync on file', () => {
|
|
66
66
|
assert.throws(() => fs.readdirSync('a.js'), { code: 'ENOTDIR' });
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
test('readdir on file', () => {
|
|
70
|
-
assert.rejects(fs.promises.readdir('a.js'), { code: 'ENOTDIR' });
|
|
69
|
+
test('readdir on file', async () => {
|
|
70
|
+
await assert.rejects(fs.promises.readdir('a.js'), { code: 'ENOTDIR' });
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
test('readdirSync on non-
|
|
73
|
+
test('readdirSync on non-existent directory', () => {
|
|
74
74
|
assert.throws(() => fs.readdirSync('/does/not/exist'), { code: 'ENOENT' });
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
test('readdir on non-
|
|
78
|
-
assert.rejects(fs.promises.readdir('/does/not/exist'), { code: 'ENOENT' });
|
|
77
|
+
test('readdir on non-existent directory', async () => {
|
|
78
|
+
await assert.rejects(fs.promises.readdir('/does/not/exist'), { code: 'ENOENT' });
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
test('rm recursively
|
|
82
|
-
await fs.promises.mkdir('/
|
|
83
|
-
await fs.promises.mkdir('/
|
|
84
|
-
await fs.promises.writeFile('/
|
|
81
|
+
test('rm recursively', async () => {
|
|
82
|
+
await fs.promises.mkdir('/rmDirRecursively');
|
|
83
|
+
await fs.promises.mkdir('/rmDirRecursively/rmDirNested');
|
|
84
|
+
await fs.promises.writeFile('/rmDirRecursively/rmDirNested/test.txt', 'hello world!');
|
|
85
85
|
|
|
86
|
-
await fs.promises.rm('/
|
|
86
|
+
await fs.promises.rm('/rmDirRecursively', { recursive: true });
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
test('
|
|
90
|
-
fs.mkdirSync('/
|
|
91
|
-
fs.mkdirSync('/
|
|
92
|
-
fs.writeFileSync('/
|
|
89
|
+
test('rmSync recursively', () => {
|
|
90
|
+
fs.mkdirSync('/rmDirRecursively');
|
|
91
|
+
fs.mkdirSync('/rmDirRecursively/rmDirNested');
|
|
92
|
+
fs.writeFileSync('/rmDirRecursively/rmDirNested/test.txt', 'hello world!');
|
|
93
93
|
|
|
94
|
-
fs.rmSync('/
|
|
94
|
+
fs.rmSync('/rmDirRecursively', { recursive: true });
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
test('readdir returns files and directories', async () => {
|
|
@@ -143,17 +143,17 @@ suite('Directories', () => {
|
|
|
143
143
|
|
|
144
144
|
test('readdir returns Dirent recursively', async () => {
|
|
145
145
|
const entries = await fs.promises.readdir(testDir, { recursive: true, withFileTypes: true });
|
|
146
|
-
|
|
147
|
-
assert
|
|
148
|
-
assert
|
|
146
|
+
const paths = entries.map(entry => entry.path).sort();
|
|
147
|
+
assert.equal(paths[0], 'file1.txt');
|
|
148
|
+
assert.equal(paths[4], 'subdir1/file4.txt');
|
|
149
|
+
assert.equal(paths[8], 'subdir2/file5.txt');
|
|
149
150
|
});
|
|
150
151
|
|
|
151
|
-
// New test for readdirSync with recursive: true
|
|
152
152
|
test('readdirSync returns files recursively', () => {
|
|
153
|
-
const entries = fs.readdirSync(testDir, { recursive: true });
|
|
154
|
-
assert(entries
|
|
155
|
-
assert(entries
|
|
156
|
-
assert(entries
|
|
153
|
+
const entries = fs.readdirSync(testDir, { recursive: true }).sort();
|
|
154
|
+
assert.equal(entries[0], 'file1.txt');
|
|
155
|
+
assert.equal(entries[4], 'subdir1/file4.txt');
|
|
156
|
+
assert.equal(entries[8], 'subdir2/file5.txt');
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
test('Cyrillic file names', () => {
|