@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.
Files changed (87) hide show
  1. package/dist/backends/backend.js +6 -5
  2. package/dist/backends/cow.d.ts +2 -2
  3. package/dist/backends/cow.js +39 -58
  4. package/dist/backends/fetch.js +27 -29
  5. package/dist/backends/passthrough.d.ts +2 -3
  6. package/dist/backends/passthrough.js +84 -199
  7. package/dist/backends/port.d.ts +16 -3
  8. package/dist/backends/port.js +61 -30
  9. package/dist/backends/single_buffer.d.ts +52 -46
  10. package/dist/backends/single_buffer.js +462 -219
  11. package/dist/backends/store/fs.d.ts +16 -10
  12. package/dist/backends/store/fs.js +227 -242
  13. package/dist/backends/store/store.d.ts +3 -3
  14. package/dist/backends/store/store.js +11 -10
  15. package/dist/config.d.ts +2 -2
  16. package/dist/config.js +10 -11
  17. package/dist/internal/devices.d.ts +2 -2
  18. package/dist/internal/devices.js +39 -49
  19. package/dist/internal/error.d.ts +9 -204
  20. package/dist/internal/error.js +19 -288
  21. package/dist/internal/file_index.d.ts +1 -1
  22. package/dist/internal/file_index.js +9 -9
  23. package/dist/internal/filesystem.d.ts +23 -8
  24. package/dist/internal/index.d.ts +1 -1
  25. package/dist/internal/index.js +1 -1
  26. package/dist/internal/index_fs.d.ts +2 -2
  27. package/dist/internal/index_fs.js +19 -19
  28. package/dist/internal/inode.d.ts +81 -103
  29. package/dist/internal/inode.js +336 -195
  30. package/dist/mixins/async.js +32 -28
  31. package/dist/mixins/mutexed.d.ts +4 -4
  32. package/dist/mixins/mutexed.js +39 -39
  33. package/dist/mixins/readonly.d.ts +2 -2
  34. package/dist/mixins/readonly.js +20 -20
  35. package/dist/mixins/sync.js +2 -2
  36. package/dist/polyfills.js +1 -1
  37. package/dist/readline.js +1 -1
  38. package/dist/utils.d.ts +8 -5
  39. package/dist/utils.js +14 -17
  40. package/dist/vfs/acl.d.ts +8 -8
  41. package/dist/vfs/acl.js +66 -47
  42. package/dist/vfs/async.d.ts +2 -2
  43. package/dist/vfs/async.js +6 -8
  44. package/dist/vfs/dir.d.ts +1 -1
  45. package/dist/vfs/dir.js +3 -4
  46. package/dist/vfs/file.js +33 -24
  47. package/dist/vfs/flags.js +3 -3
  48. package/dist/vfs/ioctl.d.ts +8 -7
  49. package/dist/vfs/ioctl.js +132 -27
  50. package/dist/vfs/promises.d.ts +3 -3
  51. package/dist/vfs/promises.js +200 -235
  52. package/dist/vfs/shared.d.ts +1 -12
  53. package/dist/vfs/shared.js +7 -35
  54. package/dist/vfs/streams.js +9 -9
  55. package/dist/vfs/sync.d.ts +1 -2
  56. package/dist/vfs/sync.js +158 -170
  57. package/dist/vfs/watchers.js +8 -8
  58. package/dist/vfs/xattr.js +89 -106
  59. package/package.json +4 -2
  60. package/scripts/test.js +2 -2
  61. package/tests/assignment.ts +1 -1
  62. package/tests/backend/port.test.ts +4 -4
  63. package/tests/backend/single-buffer.test.ts +39 -10
  64. package/tests/backend/single-buffer.worker.js +30 -0
  65. package/tests/common/context.test.ts +2 -2
  66. package/tests/common/mutex.test.ts +9 -9
  67. package/tests/fetch/fetch.ts +1 -1
  68. package/tests/fs/append.test.ts +4 -4
  69. package/tests/fs/directory.test.ts +25 -25
  70. package/tests/fs/errors.test.ts +15 -19
  71. package/tests/fs/links.test.ts +3 -2
  72. package/tests/fs/open.test.ts +4 -21
  73. package/tests/fs/permissions.test.ts +8 -13
  74. package/tests/fs/read.test.ts +10 -9
  75. package/tests/fs/readFile.test.ts +8 -24
  76. package/tests/fs/rename.test.ts +4 -9
  77. package/tests/fs/stat.test.ts +2 -2
  78. package/tests/fs/times.test.ts +6 -6
  79. package/tests/fs/truncate.test.ts +8 -36
  80. package/tests/fs/watch.test.ts +10 -10
  81. package/tests/fs/write.test.ts +77 -13
  82. package/tests/fs/xattr.test.ts +7 -7
  83. package/tests/logs.js +2 -2
  84. package/tests/setup/port.ts +6 -0
  85. package/dist/internal/log.d.ts +0 -139
  86. package/dist/internal/log.js +0 -219
  87. 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 { pick } from 'utilium';
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 { fixError, resolveMount } from './shared.js';
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 EPERM for attributes in namespaces other than 'user'
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 new ErrnoError(Errno.EPERM, 'Only attributes in the user namespace are supported', path, syscall);
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
- try {
25
- const inode = await fs.stat(resolved);
26
- if (checkAccess && !hasAccess(this, inode, R_OK)) {
27
- throw ErrnoError.With('EACCES', resolved, 'xattr.get');
28
- }
29
- (_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
30
- const attr = inode.attributes.get(name);
31
- if (!attr)
32
- throw ErrnoError.With('ENODATA', resolved, 'xattr.get');
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
- const inode = fs.statSync(resolved);
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 fixError(e, { [resolved]: path });
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
- try {
75
- const inode = await fs.stat(resolved);
76
- if (checkAccess && !hasAccess(this, inode, W_OK)) {
77
- throw ErrnoError.With('EACCES', resolved, 'xattr.set');
78
- }
79
- (_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
80
- const attr = inode.attributes.get(name);
81
- if (opt.create && attr) {
82
- throw ErrnoError.With('EEXIST', resolved, 'xattr.set');
83
- }
84
- if (opt.replace && !attr) {
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
- const inode = fs.statSync(resolved);
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 fixError(e, { [resolved]: path });
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
- try {
139
- const inode = await fs.stat(resolved);
140
- if (checkAccess && !hasAccess(this, inode, W_OK)) {
141
- throw ErrnoError.With('EACCES', resolved, 'xattr.remove');
142
- }
143
- (_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
144
- const attr = inode.attributes.get(name);
145
- if (!attr)
146
- throw ErrnoError.With('ENODATA', resolved, 'xattr.remove');
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
- const inode = fs.statSync(resolved);
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 fixError(e, { [resolved]: path });
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
- try {
191
- const inode = await fs.stat(resolved);
192
- if (!inode.attributes)
193
- return [];
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
- const inode = fs.statSync(resolved);
211
- if (!inode.attributes)
212
- return [];
213
- return inode.attributes.keys();
193
+ inode = fs.statSync(resolved);
214
194
  }
215
195
  catch (e) {
216
- throw fixError(e, { [resolved]: path });
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.0.0",
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": "^1.8.0"
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: 'w', type: 'boolean', default: false },
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
- -w, --verbose Output verbose messages
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
@@ -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('should be able to restore filesystem (with same metadata) from original buffer', async () => {
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 { suite, test } from 'node:test';
6
- import assert from 'node:assert/strict';
5
+ import { StoreFS } from '../../dist/backends/store/fs.js';
6
+ import { Mutexed } from '../../dist/mixins/mutexed.js';
7
7
 
8
- suite('LockFS mutex', () => {
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('/test', 'lock');
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('/queued', 'test').then(lock => {
23
+ const lock1 = fs.lock().then(lock => {
24
24
  lock1Resolved = true;
25
25
  lock.unlock();
26
26
  });
27
- const lock2 = fs.lock('/queued', 'test').then(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('raceConditions', 'test');
53
+ const lock = await fs.lock();
54
54
  await wait(25);
55
55
  x++;
56
56
  lock.unlock();
@@ -1,6 +1,6 @@
1
+ import { log } from 'kerium';
1
2
  import { configure, Fetch } from '../../dist/index.js';
2
3
  import { baseUrl } from './config.js';
3
- import * as log from '../../dist/internal/log.js';
4
4
 
5
5
  await configure({
6
6
  mounts: {
@@ -5,15 +5,15 @@ import { fs } from '../common.js';
5
5
  const content = 'Sample content',
6
6
  original = 'ABCD';
7
7
 
8
- suite('appendFile', () => {
9
- test('create an empty file and add content', async () => {
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('append data to a non-empty file', async () => {
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('append a buffer to the file', async () => {
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-existant directory', () => {
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-existant directory', () => {
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 asynchronously', async () => {
82
- await fs.promises.mkdir('/rmDirRecusrively');
83
- await fs.promises.mkdir('/rmDirRecusrively/rmDirNested');
84
- await fs.promises.writeFile('/rmDirRecusrively/rmDirNested/test.txt', 'hello world!');
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('/rmDirRecusrively', { recursive: true });
86
+ await fs.promises.rm('/rmDirRecursively', { recursive: true });
87
87
  });
88
88
 
89
- test('rm recursively synchronously', () => {
90
- fs.mkdirSync('/rmDirRecusrively');
91
- fs.mkdirSync('/rmDirRecusrively/rmDirNested');
92
- fs.writeFileSync('/rmDirRecusrively/rmDirNested/test.txt', 'hello world!');
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('/rmDirRecusrively', { recursive: true });
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
- assert(entries.find(entry => entry.path === 'file1.txt'));
147
- assert(entries.find(entry => entry.path === 'subdir1/file4.txt'));
148
- assert(entries.find(entry => entry.path === 'subdir2/file5.txt'));
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.includes('file1.txt'));
155
- assert(entries.includes('subdir1/file4.txt'));
156
- assert(entries.includes('subdir2/file5.txt'));
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', () => {