@zenfs/core 1.11.4 → 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 (135) hide show
  1. package/dist/backends/backend.d.ts +19 -15
  2. package/dist/backends/backend.js +36 -19
  3. package/dist/backends/cow.d.ts +20 -30
  4. package/dist/backends/cow.js +83 -192
  5. package/dist/backends/fetch.d.ts +1 -0
  6. package/dist/backends/fetch.js +30 -30
  7. package/dist/backends/index.d.ts +1 -1
  8. package/dist/backends/index.js +1 -1
  9. package/dist/backends/memory.d.ts +5 -7
  10. package/dist/backends/memory.js +2 -3
  11. package/dist/backends/passthrough.d.ts +19 -23
  12. package/dist/backends/passthrough.js +98 -288
  13. package/dist/backends/port.d.ts +220 -0
  14. package/dist/backends/port.js +328 -0
  15. package/dist/backends/single_buffer.d.ts +59 -47
  16. package/dist/backends/single_buffer.js +468 -219
  17. package/dist/backends/store/fs.d.ts +25 -35
  18. package/dist/backends/store/fs.js +276 -315
  19. package/dist/backends/store/store.d.ts +10 -15
  20. package/dist/backends/store/store.js +11 -10
  21. package/dist/config.d.ts +3 -12
  22. package/dist/config.js +17 -19
  23. package/dist/context.d.ts +8 -21
  24. package/dist/context.js +33 -10
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +2 -1
  27. package/dist/internal/contexts.d.ts +63 -0
  28. package/dist/internal/contexts.js +15 -0
  29. package/dist/internal/credentials.d.ts +2 -11
  30. package/dist/internal/credentials.js +0 -19
  31. package/dist/internal/devices.d.ts +18 -80
  32. package/dist/internal/devices.js +103 -316
  33. package/dist/internal/error.d.ts +9 -204
  34. package/dist/internal/error.js +19 -288
  35. package/dist/internal/file_index.d.ts +1 -1
  36. package/dist/internal/file_index.js +11 -11
  37. package/dist/internal/filesystem.d.ts +51 -94
  38. package/dist/internal/filesystem.js +21 -20
  39. package/dist/internal/index.d.ts +1 -2
  40. package/dist/internal/index.js +1 -2
  41. package/dist/internal/index_fs.d.ts +12 -30
  42. package/dist/internal/index_fs.js +37 -69
  43. package/dist/internal/inode.d.ts +140 -24
  44. package/dist/internal/inode.js +515 -66
  45. package/dist/mixins/async.js +52 -112
  46. package/dist/mixins/mutexed.d.ts +19 -18
  47. package/dist/mixins/mutexed.js +62 -64
  48. package/dist/mixins/readonly.d.ts +7 -6
  49. package/dist/mixins/readonly.js +24 -18
  50. package/dist/mixins/sync.js +8 -8
  51. package/dist/{vfs/path.d.ts → path.d.ts} +3 -4
  52. package/dist/{vfs/path.js → path.js} +6 -9
  53. package/dist/polyfills.js +1 -1
  54. package/dist/readline.d.ts +134 -0
  55. package/dist/readline.js +623 -0
  56. package/dist/utils.d.ts +9 -37
  57. package/dist/utils.js +17 -85
  58. package/dist/vfs/acl.d.ts +42 -0
  59. package/dist/vfs/acl.js +268 -0
  60. package/dist/vfs/async.d.ts +9 -23
  61. package/dist/vfs/async.js +25 -27
  62. package/dist/vfs/config.d.ts +6 -18
  63. package/dist/vfs/config.js +8 -18
  64. package/dist/vfs/dir.d.ts +3 -3
  65. package/dist/vfs/dir.js +12 -12
  66. package/dist/vfs/file.d.ts +106 -0
  67. package/dist/vfs/file.js +244 -0
  68. package/dist/vfs/flags.d.ts +19 -0
  69. package/dist/vfs/flags.js +62 -0
  70. package/dist/vfs/index.d.ts +4 -10
  71. package/dist/vfs/index.js +4 -13
  72. package/dist/vfs/ioctl.d.ts +88 -0
  73. package/dist/vfs/ioctl.js +409 -0
  74. package/dist/vfs/promises.d.ts +81 -19
  75. package/dist/vfs/promises.js +404 -288
  76. package/dist/vfs/shared.d.ts +7 -37
  77. package/dist/vfs/shared.js +29 -85
  78. package/dist/{stats.d.ts → vfs/stats.d.ts} +14 -28
  79. package/dist/{stats.js → vfs/stats.js} +11 -66
  80. package/dist/vfs/streams.d.ts +1 -0
  81. package/dist/vfs/streams.js +32 -27
  82. package/dist/vfs/sync.d.ts +3 -3
  83. package/dist/vfs/sync.js +263 -260
  84. package/dist/vfs/watchers.d.ts +2 -2
  85. package/dist/vfs/watchers.js +12 -12
  86. package/dist/vfs/xattr.d.ts +116 -0
  87. package/dist/vfs/xattr.js +201 -0
  88. package/package.json +5 -3
  89. package/readme.md +1 -1
  90. package/scripts/test.js +2 -2
  91. package/tests/assignment.ts +1 -1
  92. package/tests/backend/config.worker.js +4 -1
  93. package/tests/backend/fetch.test.ts +3 -0
  94. package/tests/backend/port.test.ts +19 -33
  95. package/tests/backend/remote.worker.js +4 -1
  96. package/tests/backend/single-buffer.test.ts +53 -0
  97. package/tests/backend/single-buffer.worker.js +30 -0
  98. package/tests/common/context.test.ts +3 -3
  99. package/tests/common/handle.test.ts +17 -12
  100. package/tests/common/mutex.test.ts +9 -9
  101. package/tests/common/path.test.ts +1 -1
  102. package/tests/common/readline.test.ts +104 -0
  103. package/tests/common.ts +4 -19
  104. package/tests/fetch/fetch.ts +2 -2
  105. package/tests/fs/append.test.ts +4 -4
  106. package/tests/fs/directory.test.ts +25 -25
  107. package/tests/fs/errors.test.ts +15 -19
  108. package/tests/fs/links.test.ts +4 -3
  109. package/tests/fs/open.test.ts +4 -21
  110. package/tests/fs/permissions.test.ts +14 -18
  111. package/tests/fs/read.test.ts +10 -9
  112. package/tests/fs/readFile.test.ts +10 -26
  113. package/tests/fs/rename.test.ts +4 -9
  114. package/tests/fs/stat.test.ts +8 -8
  115. package/tests/fs/streams.test.ts +2 -11
  116. package/tests/fs/times.test.ts +7 -7
  117. package/tests/fs/truncate.test.ts +8 -36
  118. package/tests/fs/watch.test.ts +10 -10
  119. package/tests/fs/write.test.ts +77 -13
  120. package/tests/fs/xattr.test.ts +85 -0
  121. package/tests/logs.js +22 -0
  122. package/tests/setup/context.ts +1 -1
  123. package/tests/setup/index.ts +3 -3
  124. package/tests/setup/port.ts +7 -1
  125. package/dist/backends/port/fs.d.ts +0 -84
  126. package/dist/backends/port/fs.js +0 -151
  127. package/dist/backends/port/rpc.d.ts +0 -77
  128. package/dist/backends/port/rpc.js +0 -100
  129. package/dist/backends/store/simple.d.ts +0 -20
  130. package/dist/backends/store/simple.js +0 -13
  131. package/dist/internal/file.d.ts +0 -359
  132. package/dist/internal/file.js +0 -751
  133. package/dist/internal/log.d.ts +0 -133
  134. package/dist/internal/log.js +0 -218
  135. package/tests/fs/writeFile.test.ts +0 -70
@@ -4,7 +4,7 @@ import { fs } from '../common.js';
4
4
 
5
5
  suite('Reading', () => {
6
6
  test('Cannot read a file with an invalid encoding', () => {
7
- assert.throws(() => fs.readFileSync('a.js', 'wrongencoding' as BufferEncoding));
7
+ assert.throws(() => fs.readFileSync('a.js', 'wrong-encoding' as BufferEncoding));
8
8
  });
9
9
 
10
10
  test('Reading past the end of a file should not be an error', async () => {
@@ -12,9 +12,7 @@ suite('Reading', () => {
12
12
  const { bytesRead } = await handle.read(new Uint8Array(10), 0, 10, 10000);
13
13
  assert.equal(bytesRead, 0);
14
14
  });
15
- });
16
15
 
17
- suite('Read and Unlink', () => {
18
16
  const dir = 'test-readfile-unlink';
19
17
  const file = 'test-readfile-unlink/test.bin';
20
18
  const data = new Uint8Array(512).fill(42);
@@ -34,40 +32,26 @@ suite('Read and Unlink', () => {
34
32
  await fs.promises.unlink(file);
35
33
  await fs.promises.rmdir(dir);
36
34
  });
37
- });
38
-
39
- suite('Read File Test', () => {
40
- const fn = 'empty.txt';
41
35
 
42
- test('read file asynchronously', async () => {
43
- const data: Uint8Array = await fs.promises.readFile(fn);
44
- assert(data != undefined);
45
- });
36
+ const fileName = 'empty.txt';
46
37
 
47
- test('read file with utf-8 encoding asynchronously', async () => {
48
- const data: string = await fs.promises.readFile(fn, 'utf8');
49
- assert.equal(data, '');
38
+ test('read file', async () => {
39
+ assert.equal((await fs.promises.readFile(fileName)).toString(), '');
40
+ assert.equal(fs.readFileSync(fileName).toString(), '');
50
41
  });
51
42
 
52
- test('read file synchronously', () => {
53
- const data: Uint8Array = fs.readFileSync(fn);
54
- assert(data != undefined);
43
+ test('read file with utf-8 encoding', async () => {
44
+ assert.equal(await fs.promises.readFile(fileName, 'utf8'), '');
45
+ assert.equal(fs.readFileSync(fileName, 'utf8'), '');
55
46
  });
56
47
 
57
- test('read file with utf-8 encoding synchronously', () => {
58
- const data: string = fs.readFileSync(fn, 'utf8');
59
- assert.equal(data, '');
60
- });
61
- });
62
-
63
- suite('fs file reading', () => {
64
48
  test('read file synchronously and verify the content', () => {
65
49
  const content = fs.readFileSync('elipses.txt', 'utf8');
66
50
 
51
+ assert.equal(content.length, 10000);
52
+
67
53
  for (let i = 0; i < content.length; i++) {
68
54
  assert.equal(content[i], '…');
69
55
  }
70
-
71
- assert.equal(content.length, 10000);
72
56
  });
73
57
  });
@@ -1,6 +1,5 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
- import { ErrnoError } from '../../dist/index.js';
4
3
  import { fs } from '../common.js';
5
4
 
6
5
  suite('Rename', () => {
@@ -77,10 +76,8 @@ suite('Rename', () => {
77
76
  await fs.promises.mkdir(dir);
78
77
  await fs.promises.writeFile(file, 'file contents go here');
79
78
 
80
- await fs.promises.rename(file, dir).catch((error: ErrnoError) => {
81
- assert(error instanceof ErrnoError);
82
- assert.match(error.code, /EISDIR|EPERM/);
83
- });
79
+ await assert.rejects(fs.promises.rename(file, dir), { code: 'EISDIR' });
80
+ assert.throws(() => fs.renameSync(file, dir), { code: 'EISDIR' });
84
81
  });
85
82
 
86
83
  test('rename directory inside itself', async () => {
@@ -89,9 +86,7 @@ suite('Rename', () => {
89
86
 
90
87
  await fs.promises.mkdir(renDir1);
91
88
 
92
- await fs.promises.rename(renDir1, renDir2).catch((error: ErrnoError) => {
93
- assert(error instanceof ErrnoError);
94
- assert.equal(error.code, 'EBUSY');
95
- });
89
+ await assert.rejects(fs.promises.rename(renDir1, renDir2), { code: 'EBUSY' });
90
+ assert.throws(() => fs.renameSync(renDir1, renDir2), { code: 'EBUSY' });
96
91
  });
97
92
  });
@@ -1,14 +1,14 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
- import { credentials } from '../../dist/index.js';
4
- import { Stats } from '../../dist/stats.js';
3
+ import { defaultContext } from '../../dist/internal/contexts.js';
4
+ import { Stats } from '../../dist/vfs/stats.js';
5
5
  import { fs } from '../common.js';
6
6
 
7
7
  suite('Stats', () => {
8
8
  const existing_file = 'x.txt';
9
9
 
10
- test('stat empty path', () => {
11
- assert.rejects(fs.promises.stat(''));
10
+ test('stat empty path', async () => {
11
+ await assert.rejects(fs.promises.stat(''));
12
12
  });
13
13
 
14
14
  test('stat directory', async () => {
@@ -40,7 +40,7 @@ suite('Stats', () => {
40
40
 
41
41
  fs.writeFileSync(newFile, 'hello', { mode: 0o640 });
42
42
 
43
- const prevCredentials = { ...credentials };
43
+ const prevCredentials = { ...defaultContext.credentials };
44
44
  const uid = 33;
45
45
  const nonRootCredentials = {
46
46
  uid,
@@ -53,7 +53,7 @@ suite('Stats', () => {
53
53
 
54
54
  fs.chownSync(newFile, 0, nonRootCredentials.gid); // creating with root-user so that non-root user can access
55
55
 
56
- Object.assign(credentials, nonRootCredentials);
56
+ Object.assign(defaultContext.credentials, nonRootCredentials);
57
57
  const stat = fs.statSync(newFile);
58
58
 
59
59
  assert.equal(stat.gid, nonRootCredentials.gid);
@@ -63,13 +63,13 @@ suite('Stats', () => {
63
63
  assert.equal(stat.hasAccess(fs.constants.X_OK), false);
64
64
  // changing group
65
65
 
66
- Object.assign(credentials, { ...nonRootCredentials, gid: 44 });
66
+ Object.assign(defaultContext.credentials, { ...nonRootCredentials, gid: 44 });
67
67
 
68
68
  assert.equal(stat.hasAccess(fs.constants.R_OK), false);
69
69
  assert.equal(stat.hasAccess(fs.constants.W_OK), false);
70
70
  assert.equal(stat.hasAccess(fs.constants.X_OK), false);
71
71
 
72
- Object.assign(credentials, prevCredentials);
72
+ Object.assign(defaultContext.credentials, prevCredentials);
73
73
  });
74
74
 
75
75
  test('stat file', async () => {
@@ -129,21 +129,12 @@ suite('Streams', () => {
129
129
  test('FileHandle.createReadStream after close should give an error', async () => {
130
130
  const fileHandle = await fs.promises.open(testFilePath, 'r');
131
131
  await fileHandle.close();
132
- const stream = fileHandle.createReadStream();
133
- const { promise, resolve, reject } = Promise.withResolvers();
134
- setTimeout(resolve, 100);
135
- stream.on('error', reject);
136
- assert.rejects(promise);
132
+ assert.throws(() => fileHandle.createReadStream(), { code: 'EBADF' });
137
133
  });
138
134
 
139
135
  test('FileHandle.createWriteStream after close should give an error', async () => {
140
136
  const fileHandle = await fs.promises.open(testFilePathWrite, 'w');
141
137
  await fileHandle.close();
142
- const stream = fileHandle.createWriteStream();
143
- const { promise, resolve, reject } = Promise.withResolvers();
144
- setTimeout(resolve, 100);
145
- stream.on('error', reject);
146
- assert.rejects(promise);
147
- stream.write('Nuh-uh');
138
+ assert.throws(() => fileHandle.createWriteStream(), { code: 'EBADF' });
148
139
  });
149
140
  });
@@ -1,8 +1,8 @@
1
+ import { Exception } from 'kerium';
1
2
  import assert from 'node:assert/strict';
2
3
  import { suite, test } from 'node:test';
3
4
  import { wait } from 'utilium';
4
- import { ErrnoError } from '../../dist/index.js';
5
- import type { StatsLike } from '../../dist/stats.js';
5
+ import type { StatsLike } from '../../dist/vfs/stats.js';
6
6
  import { fs } from '../common.js';
7
7
 
8
8
  const path = 'x.txt';
@@ -19,7 +19,7 @@ export function unixTimestamps(stats: StatsLike<number>): Record<'atime' | 'mtim
19
19
  };
20
20
  }
21
21
 
22
- suite('times', () => {
22
+ suite('Times', () => {
23
23
  async function runTest(atime: Date | number, mtime: Date | number): Promise<void> {
24
24
  const times = {
25
25
  atime: typeof atime == 'number' ? Math.floor(atime) : atime.getTime(),
@@ -30,8 +30,8 @@ suite('times', () => {
30
30
 
31
31
  assert.deepEqual(unixTimestamps(await fs.promises.stat(path)), times);
32
32
 
33
- await fs.promises.utimes('foobarbaz', atime, mtime).catch((error: ErrnoError) => {
34
- assert(error instanceof ErrnoError);
33
+ await fs.promises.utimes('foobarbaz', atime, mtime).catch((error: Exception) => {
34
+ assert(error instanceof Exception);
35
35
  assert.equal(error.code, 'ENOENT');
36
36
  });
37
37
 
@@ -46,14 +46,14 @@ suite('times', () => {
46
46
  try {
47
47
  fs.utimesSync('foobarbaz', atime, mtime);
48
48
  } catch (error: any) {
49
- assert(error instanceof ErrnoError);
49
+ assert(error instanceof Exception);
50
50
  assert.equal(error.code, 'ENOENT');
51
51
  }
52
52
 
53
53
  try {
54
54
  fs.futimesSync(-1, atime, mtime);
55
55
  } catch (error: any) {
56
- assert(error instanceof ErrnoError);
56
+ assert(error instanceof Exception);
57
57
  assert.equal(error.code, 'EBADF');
58
58
  }
59
59
  }
@@ -1,94 +1,66 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
3
  import { fs } from '../common.js';
4
- import type { FileHandle } from '../../dist/vfs/promises.js';
5
4
 
6
5
  const path: string = 'truncate-file.txt',
7
6
  size = 1024 * 16,
8
7
  data = new Uint8Array(size).fill('x'.charCodeAt(0));
9
8
 
10
- suite('Truncate, sync', () => {
11
- test('initial write', () => {
9
+ suite('Truncating', () => {
10
+ test('Sync path functions', () => {
12
11
  fs.writeFileSync(path, data);
13
12
  assert.equal(fs.statSync(path).size, size);
14
- });
15
13
 
16
- test('truncate to 1024', () => {
17
14
  fs.truncateSync(path, 1024);
18
15
  assert.equal(fs.statSync(path).size, 1024);
19
- });
20
16
 
21
- test('truncate to 0', () => {
22
17
  fs.truncateSync(path);
23
18
  assert.equal(fs.statSync(path).size, 0);
24
- });
25
19
 
26
- test('write', () => {
27
20
  fs.writeFileSync(path, data);
28
21
  assert.equal(fs.statSync(path).size, size);
29
22
  });
30
23
 
31
- let fd: number;
32
- test('open r+', () => {
33
- fd = fs.openSync(path, 'r+');
34
- });
24
+ test('FD functions', () => {
25
+ const fd = fs.openSync(path, 'r+');
35
26
 
36
- test('ftruncate to 1024', () => {
37
27
  fs.ftruncateSync(fd, 1024);
38
28
  assert.equal(fs.fstatSync(fd).size, 1024);
39
- });
40
29
 
41
- test('ftruncate to 0', () => {
42
30
  fs.ftruncateSync(fd);
43
31
  assert.equal(fs.fstatSync(fd).size, 0);
44
- });
45
32
 
46
- test('close fd', () => {
47
33
  fs.closeSync(fd);
48
34
  });
49
- });
50
- suite('Truncate, async', () => {
35
+
51
36
  const statSize = async (path: string) => (await fs.promises.stat(path)).size;
52
37
 
53
- test('initial write', async () => {
38
+ test('Async path functions', async () => {
54
39
  await fs.promises.writeFile(path, data);
55
40
 
56
41
  assert.equal(await statSize(path), 1024 * 16);
57
- });
58
42
 
59
- test('truncate to 1024', async () => {
60
43
  await fs.promises.truncate(path, 1024);
61
44
  assert.equal(await statSize(path), 1024);
62
- });
63
45
 
64
- test('truncate to 0', async () => {
65
46
  await fs.promises.truncate(path);
66
47
  assert.equal(await statSize(path), 0);
67
- });
68
48
 
69
- test('write', async () => {
70
49
  await fs.promises.writeFile(path, data);
71
50
  assert.equal(await statSize(path), size);
72
51
  });
73
52
 
74
- let handle: FileHandle;
75
- test('open w', async () => {
76
- handle = await fs.promises.open(path, 'w');
77
- });
53
+ test('FileHandle', async () => {
54
+ const handle = await fs.promises.open(path, 'w');
78
55
 
79
- test('handle.truncate to 1024', async () => {
80
56
  await handle.truncate(1024);
81
57
  await handle.sync();
82
58
  assert.equal(await statSize(path), 1024);
83
- });
84
59
 
85
- test('handle.truncate to 0', async () => {
86
60
  await handle.truncate();
87
61
  await handle.sync();
88
62
  assert.equal(await statSize(path), 0);
89
- });
90
63
 
91
- test('close handle', async () => {
92
64
  await handle.close();
93
65
  });
94
66
  });
@@ -11,8 +11,8 @@ await fs.promises.writeFile(testFile, 'Initial content');
11
11
  /**
12
12
  * @todo convert using watcher to void discards pending ES proposal
13
13
  */
14
- await suite('Watch Features', () => {
15
- test('fs.watch should emit events on file change', async () => {
14
+ await suite('Watch', () => {
15
+ test('Events emitted on file change', async () => {
16
16
  const { promise, resolve } = Promise.withResolvers<[string, string]>();
17
17
 
18
18
  using watcher = fs.watch(testFile, (eventType, filename) => {
@@ -27,7 +27,7 @@ await suite('Watch Features', () => {
27
27
  assert.equal(filename, 'test.txt');
28
28
  });
29
29
 
30
- test('fs.watch should emit events on file rename (delete)', async () => {
30
+ test('Events are emitted on delete', async () => {
31
31
  using watcher = fs.watch(testFile, (eventType, filename) => {
32
32
  assert.equal(eventType, 'rename');
33
33
  assert.equal(filename, 'test.txt');
@@ -37,7 +37,7 @@ await suite('Watch Features', () => {
37
37
  await fs.promises.unlink(testFile);
38
38
  });
39
39
 
40
- test('fs.watchFile should detect changes to a file', async () => {
40
+ test('Changes are detected with watchFile()', async () => {
41
41
  const listener = (curr: Stats, prev: Stats) => {
42
42
  assert(curr.mtimeMs != prev.mtimeMs);
43
43
  fs.unwatchFile(testFile, listener);
@@ -49,7 +49,7 @@ await suite('Watch Features', () => {
49
49
  await fs.promises.writeFile(testFile, 'Changed content');
50
50
  });
51
51
 
52
- test('fs.unwatchFile should stop watching the file', async () => {
52
+ test('unwatchFile() works', async () => {
53
53
  let changeDetected = false;
54
54
 
55
55
  const listener = () => {
@@ -66,7 +66,7 @@ await suite('Watch Features', () => {
66
66
  assert(!changeDetected);
67
67
  });
68
68
 
69
- test('fs.watch should work with directories', async () => {
69
+ test('Directories can be watched', async () => {
70
70
  using watcher = fs.watch(testDir, (eventType, filename) => {
71
71
  assert.equal(eventType, 'change');
72
72
  assert.equal(filename, 'newFile.txt');
@@ -75,7 +75,7 @@ await suite('Watch Features', () => {
75
75
  await fs.promises.writeFile(testDir + '/newFile.txt', 'Content');
76
76
  });
77
77
 
78
- test('fs.watch should detect file renames', async () => {
78
+ test('File renames are detected', async () => {
79
79
  const oldFileName = 'oldFile.txt';
80
80
  const newFileName = 'newFile.txt';
81
81
  const oldFile = testDir + '/' + oldFileName;
@@ -102,7 +102,7 @@ await suite('Watch Features', () => {
102
102
  await Promise.all([newFileResolver.promise, oldFileResolver.promise]);
103
103
  });
104
104
 
105
- test('fs.watch should detect file deletions', async () => {
105
+ test('File deletions are detected', async () => {
106
106
  const tempFile = `${testDir}/tempFile.txt`;
107
107
 
108
108
  await fs.promises.writeFile(tempFile, 'Temporary content');
@@ -115,7 +115,7 @@ await suite('Watch Features', () => {
115
115
  await fs.promises.unlink(tempFile);
116
116
  });
117
117
 
118
- test('fs.promises.watch should detect file deletions', async () => {
118
+ test('File deletions are detected by promises API', async () => {
119
119
  const tempFile = `${testDir}/tempFile.txt`;
120
120
 
121
121
  await fs.promises.writeFile(tempFile, 'Temporary content');
@@ -135,7 +135,7 @@ await suite('Watch Features', () => {
135
135
  await promise;
136
136
  });
137
137
 
138
- test('fs.promises.watch should detect file creations recursively', async () => {
138
+ test('File creations are detected recursively', async () => {
139
139
  const subDir = `${testDir}/sub-dir`;
140
140
  const tempFile = `${subDir}/tempFile.txt`;
141
141
  await fs.promises.mkdir(subDir);
@@ -1,27 +1,33 @@
1
1
  import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path/posix';
2
4
  import { suite, test } from 'node:test';
3
5
  import { fs } from '../common.js';
4
- const fn = 'write.txt';
5
- suite('write', () => {
6
- test('write file with specified content', async () => {
6
+ import { data as dataPath } from '../setup.js';
7
+
8
+ const fileName = 'write.txt';
9
+ const utf8example = readFileSync(join(dataPath, 'utf8.txt'), 'utf8');
10
+
11
+ suite('Writes', () => {
12
+ test('Using FileHandle and UTF-8', async () => {
7
13
  const expected = 'ümlaut.';
8
14
 
9
- const handle = await fs.promises.open(fn, 'w', 0o644);
15
+ const handle = await fs.promises.open(fileName, 'w', 0o644);
10
16
  await handle.write('', 0, 'utf8');
11
17
  const { bytesWritten } = await handle.write(expected, 0, 'utf8');
12
18
  assert.equal(bytesWritten, Buffer.from(expected).length);
13
19
  await handle.close();
14
20
 
15
- const data = await fs.promises.readFile(fn, 'utf8');
21
+ const data = await fs.promises.readFile(fileName, 'utf8');
16
22
  assert.equal(data, expected);
17
23
 
18
- await fs.promises.unlink(fn);
24
+ await fs.promises.unlink(fileName);
19
25
  });
20
26
 
21
- test('write a buffer to a file', async () => {
27
+ test('Using FileHandle with buffer', async () => {
22
28
  const expected = Buffer.from('hello');
23
29
 
24
- const handle = await fs.promises.open(fn, 'w', 0o644);
30
+ const handle = await fs.promises.open(fileName, 'w', 0o644);
25
31
 
26
32
  const written = await handle.write(expected, 0, expected.length, null);
27
33
 
@@ -29,13 +35,13 @@ suite('write', () => {
29
35
 
30
36
  await handle.close();
31
37
 
32
- assert.deepEqual(await fs.promises.readFile(fn), expected);
38
+ assert.deepEqual(await fs.promises.readFile(fileName), expected);
33
39
 
34
- await fs.promises.unlink(fn);
40
+ await fs.promises.unlink(fileName);
35
41
  });
36
42
 
37
- test('writeSync file with specified content', () => {
38
- const fd = fs.openSync(fn, 'w');
43
+ test('Using sync path functions', () => {
44
+ const fd = fs.openSync(fileName, 'w');
39
45
 
40
46
  let written = fs.writeSync(fd, '');
41
47
  assert.equal(written, 0);
@@ -48,6 +54,64 @@ suite('write', () => {
48
54
 
49
55
  fs.closeSync(fd);
50
56
 
51
- assert.equal(fs.readFileSync(fn, 'utf8'), 'foobár');
57
+ assert.equal(fs.readFileSync(fileName, 'utf8'), 'foobár');
58
+ });
59
+
60
+ test('Using promises API', async () => {
61
+ const filename = 'test.txt';
62
+ await fs.promises.writeFile(filename, utf8example);
63
+ const data = await fs.promises.readFile(filename);
64
+ assert.equal(data.length, Buffer.from(utf8example).length);
65
+ await fs.promises.unlink(filename);
66
+ });
67
+
68
+ test('Using promises API with buffer', async () => {
69
+ const filename = 'test2.txt';
70
+ const expected = Buffer.from(utf8example, 'utf8');
71
+
72
+ await fs.promises.writeFile(filename, expected);
73
+ const actual = await fs.promises.readFile(filename);
74
+ assert.equal(actual.length, expected.length);
75
+
76
+ await fs.promises.unlink(filename);
77
+ });
78
+
79
+ test('Promises API with base64 data', async () => {
80
+ const data = readFileSync(join(dataPath, 'image.jpg'), 'base64');
81
+
82
+ const buffer = Buffer.from(data, 'base64');
83
+ const filePath = 'test.jpg';
84
+
85
+ await fs.promises.writeFile(filePath, buffer);
86
+
87
+ const read = await fs.promises.readFile(filePath, 'base64');
88
+ assert.equal(read, data);
89
+ });
90
+
91
+ test('Using sync path functions with custom mode', () => {
92
+ const file = 'testWriteFileSync.txt';
93
+ const mode = 0o755;
94
+
95
+ fs.writeFileSync(file, '123', { mode });
96
+
97
+ const content = fs.readFileSync(file, 'utf8');
98
+ assert.equal(content, '123');
99
+ assert.equal(fs.statSync(file).mode & 0o777, mode);
100
+
101
+ fs.unlinkSync(file);
102
+ });
103
+
104
+ test('Appending to a file synchronously with custom mode', () => {
105
+ const file = 'testAppendFileSync.txt';
106
+ const mode = 0o755;
107
+
108
+ fs.appendFileSync(file, 'abc', { mode });
109
+
110
+ const content = fs.readFileSync(file, { encoding: 'utf8' });
111
+ assert.equal(content, 'abc');
112
+
113
+ assert.equal(fs.statSync(file).mode & 0o777, mode);
114
+
115
+ fs.unlinkSync(file);
52
116
  });
53
117
  });
@@ -0,0 +1,85 @@
1
+ import { Buffer } from 'buffer';
2
+ import assert from 'node:assert/strict';
3
+ import { suite, test } from 'node:test';
4
+ import { fs } from '../common.js';
5
+
6
+ suite('Extended Attributes', () => {
7
+ const testFile = 'xattr-test.txt';
8
+ const testValue = 'test value';
9
+ const testName = 'user.test';
10
+
11
+ test.before(() => fs.promises.writeFile(testFile, 'test content'));
12
+ test.after(() => fs.promises.unlink(testFile));
13
+
14
+ test('Non-user attribute set fails', async () => {
15
+ await assert.rejects(fs.xattr.set(testFile, 'system.test', 'value'), { code: 'ENOTSUP' });
16
+ });
17
+
18
+ test('set and get attributes', async () => {
19
+ await fs.xattr.set(testFile, testName, testValue);
20
+ const value = await fs.xattr.get(testFile, testName, { encoding: 'utf8' });
21
+ assert.equal(value, testValue);
22
+ });
23
+
24
+ test('get attributes with buffer encoding', async () => {
25
+ await fs.xattr.set(testFile, 'user.buffer', 'buffer value');
26
+ const buffer = await fs.xattr.get(testFile, 'user.buffer', { encoding: 'buffer' });
27
+ assert(buffer instanceof Uint8Array);
28
+ assert.equal(Buffer.from(buffer).toString(), 'buffer value');
29
+ });
30
+
31
+ test('remove attributes', async () => {
32
+ await fs.xattr.set(testFile, 'user.to-remove', testValue);
33
+ await fs.xattr.remove(testFile, 'user.to-remove');
34
+
35
+ await assert.rejects(fs.xattr.get(testFile, 'user.to-remove', { encoding: 'utf8' }), { code: 'ENODATA' });
36
+ });
37
+
38
+ test('list attributes', async () => {
39
+ await fs.xattr.set(testFile, 'user.list1', 'value1');
40
+ await fs.xattr.set(testFile, 'user.list2', 'value2');
41
+
42
+ const attrs = await fs.xattr.list(testFile);
43
+ assert(attrs.includes('user.list1'));
44
+ assert(attrs.includes('user.list2'));
45
+ });
46
+
47
+ test('handle create and replace options', async () => {
48
+ const flagTestName = 'user.flag-test';
49
+
50
+ await fs.xattr.set(testFile, flagTestName, 'original', { create: true });
51
+
52
+ await assert.rejects(fs.xattr.set(testFile, flagTestName, 'new value', { create: true }), { code: 'EEXIST' });
53
+
54
+ await fs.xattr.set(testFile, flagTestName, 'updated', { replace: true });
55
+ const value = await fs.xattr.get(testFile, flagTestName, { encoding: 'utf8' });
56
+ assert.equal(value, 'updated');
57
+
58
+ await assert.rejects(fs.xattr.set(testFile, 'user.nonexistent', 'value', { replace: true }), { code: 'ENODATA' });
59
+ });
60
+
61
+ test('file must exist', async () => {
62
+ await assert.rejects(fs.xattr.set('nonexistent-file.txt', testName, 'value'), { code: 'ENOENT' });
63
+ });
64
+
65
+ test('synchronous operations', () => {
66
+ const syncAttrName = 'user.sync-test';
67
+
68
+ fs.xattr.setSync(testFile, syncAttrName, testValue);
69
+ const value = fs.xattr.getSync(testFile, syncAttrName, { encoding: 'utf8' });
70
+ assert.equal(value, testValue);
71
+
72
+ fs.xattr.removeSync(testFile, syncAttrName);
73
+
74
+ assert.throws(() => fs.xattr.getSync(testFile, syncAttrName, { encoding: 'utf8' }), { code: 'ENODATA' });
75
+
76
+ const syncList1 = 'user.sync-list1';
77
+ const syncList2 = 'user.sync-list2';
78
+ fs.xattr.setSync(testFile, syncList1, 'value1');
79
+ fs.xattr.setSync(testFile, syncList2, 'value2');
80
+
81
+ const attrs = fs.xattr.listSync(testFile);
82
+ assert(attrs.includes(syncList1));
83
+ assert(attrs.includes(syncList2));
84
+ });
85
+ });
package/tests/logs.js ADDED
@@ -0,0 +1,22 @@
1
+ import { log } from 'kerium';
2
+
3
+ export function setupLogs(prefix) {
4
+ const { ZENFS_LOG_LEVEL } = process.env;
5
+
6
+ let level = log.Level.ERR;
7
+
8
+ if (ZENFS_LOG_LEVEL) {
9
+ const tmp = parseInt(ZENFS_LOG_LEVEL);
10
+ if (Number.isSafeInteger(tmp)) level = tmp;
11
+ else level = ZENFS_LOG_LEVEL;
12
+ }
13
+
14
+ log.configure({
15
+ enabled: true,
16
+ format: log.fancy({ style: 'ansi', colorize: 'message' }),
17
+ dumpBacklog: true,
18
+ level,
19
+ stack: true,
20
+ output: (...msg) => (prefix ? console.error(prefix, ...msg) : console.error(...msg)),
21
+ });
22
+ }
@@ -4,6 +4,6 @@ import { copySync, data } from '../setup.js';
4
4
 
5
5
  _fs.mkdirSync('/new_root');
6
6
 
7
- export const { fs } = bindContext('/new_root');
7
+ export const { fs } = bindContext({ root: '/new_root' });
8
8
 
9
9
  copySync(data, fs);
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path/posix';
3
- import { configureSingle, InMemory, InMemoryStore, mounts, Overlay, Readonly, resolveMountConfig, StoreFS } from '../../dist/index.js';
3
+ import { configureSingle, CopyOnWrite, InMemory, InMemoryStore, mounts, Readonly, StoreFS } from '../../dist/index.js';
4
4
  import { S_IFDIR } from '../../dist/vfs/constants.js';
5
5
  import { copySync, data } from '../setup.js';
6
6
 
@@ -32,7 +32,7 @@ const readable = new MockFS();
32
32
  await readable.ready();
33
33
 
34
34
  await configureSingle({
35
- backend: Overlay,
35
+ backend: CopyOnWrite,
36
36
  readable,
37
- writable: await resolveMountConfig({ backend: InMemory, name: 'cow' }),
37
+ writable: { backend: InMemory, label: 'cow' },
38
38
  });