@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
@@ -1,11 +1,11 @@
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
- const { fs: ctx } = bindContext('/ctx');
8
+ const { fs: ctx } = bindContext({ root: '/ctx' });
9
9
 
10
10
  suite('Context', () => {
11
11
  test('create a file', () => {
@@ -1,25 +1,21 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
+ import { wait } from 'utilium';
3
4
  import { constants, type FileHandle, open } from '../../dist/vfs/promises.js';
4
5
 
5
6
  const content = 'The cake is a lie',
6
7
  appended = '\nAnother lie';
7
8
 
8
- await suite('FileHandle', () => {
9
- let handle: FileHandle;
10
- const filePath = './test.txt';
11
-
12
- test('open', async () => {
13
- handle = await open(filePath, 'w+');
14
- });
9
+ await using handle: FileHandle = await open('./test.txt', 'ws+');
15
10
 
11
+ await suite('FileHandle', () => {
16
12
  test('writeFile', async () => {
17
13
  await handle.writeFile(content);
18
14
  await handle.sync();
19
15
  });
20
16
 
21
17
  test('readFile', async () => {
22
- assert((await handle.readFile('utf8')) === content);
18
+ assert.equal(await handle.readFile('utf8'), content);
23
19
  });
24
20
 
25
21
  test('appendFile', async () => {
@@ -27,12 +23,12 @@ await suite('FileHandle', () => {
27
23
  });
28
24
 
29
25
  test('readFile after appendFile', async () => {
30
- assert((await handle.readFile({ encoding: 'utf8' })) === content + appended);
26
+ assert.equal(await handle.readFile({ encoding: 'utf8' }), content + appended);
31
27
  });
32
28
 
33
29
  test('truncate', async () => {
34
30
  await handle.truncate(5);
35
- assert((await handle.readFile({ encoding: 'utf8' })) === content.slice(0, 5));
31
+ assert.equal(await handle.readFile({ encoding: 'utf8' }), content.slice(0, 5));
36
32
  });
37
33
 
38
34
  test('stat', async () => {
@@ -54,7 +50,16 @@ await suite('FileHandle', () => {
54
50
  assert.equal(stats.gid, 5678);
55
51
  });
56
52
 
57
- test('close', async () => {
58
- await handle.close();
53
+ test('readLines', async () => {
54
+ await handle.writeFile('first line\nsecond line\nthird line');
55
+
56
+ await using rl = handle.readLines();
57
+
58
+ const lines: string[] = [];
59
+ rl.on('line', (line: string) => lines.push(line));
60
+
61
+ await wait(50);
62
+
63
+ assert.deepEqual(lines, ['first line', 'second line', 'third line']);
59
64
  });
60
65
  });
@@ -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
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
- import { basename, dirname, extname, join, normalize, resolve } from '../../dist/vfs/path.js';
3
+ import { basename, dirname, extname, join, normalize, resolve } from '../../dist/path.js';
4
4
  import * as fs from '../../dist/vfs/index.js';
5
5
 
6
6
  suite('Path emulation', () => {
@@ -0,0 +1,104 @@
1
+ import assert from 'node:assert/strict';
2
+ import { PassThrough } from 'node:stream';
3
+ import { suite, test } from 'node:test';
4
+ import { wait } from 'utilium';
5
+ import { createInterface, Interface } from '../../dist/readline.js';
6
+
7
+ suite('Readline interface', { skip: true }, () => {
8
+ test('creates interface with readable stream', async () => {
9
+ const input = new PassThrough();
10
+ await using rl = createInterface({ input });
11
+
12
+ assert.ok(rl instanceof Interface);
13
+ assert.equal(rl.input, input);
14
+ });
15
+
16
+ test('emits line events when receiving data', async () => {
17
+ const input = new PassThrough();
18
+ await using rl = createInterface({ input });
19
+
20
+ const lines: string[] = [];
21
+ rl.on('line', (line: string) => lines.push(line));
22
+
23
+ input.write('first line\n');
24
+ input.write('second line\r\n');
25
+ input.write('third line\n');
26
+
27
+ await wait(10);
28
+
29
+ assert.deepEqual(lines, ['first line', 'second line', 'third line']);
30
+ });
31
+
32
+ test('handles partial lines correctly', async () => {
33
+ const input = new PassThrough();
34
+ await using rl = createInterface({ input });
35
+
36
+ const lines: string[] = [];
37
+ rl.on('line', (line: string) => lines.push(line));
38
+
39
+ input.write('partial ');
40
+ input.write('line\n');
41
+ input.write('another ');
42
+ input.write('partial line\n');
43
+
44
+ await wait(10);
45
+
46
+ assert.deepEqual(lines, ['partial line', 'another partial line']);
47
+ });
48
+
49
+ test('emits remaining buffer on close', async () => {
50
+ const input = new PassThrough();
51
+ await using rl = createInterface({ input });
52
+
53
+ const lines: string[] = [];
54
+ rl.on('line', (line: string) => lines.push(line));
55
+
56
+ input.write('line with newline\n');
57
+ input.write('line without newline');
58
+
59
+ await wait(10);
60
+
61
+ assert.deepEqual(lines, ['line with newline']);
62
+
63
+ await wait(10);
64
+
65
+ assert.deepEqual(lines, ['line with newline', 'line without newline']);
66
+ });
67
+
68
+ test('tracks history correctly', async () => {
69
+ const input = new PassThrough();
70
+ await using rl = createInterface({ input });
71
+
72
+ let history: string[] = [];
73
+ rl.on('history', (h: string[]) => (history = h));
74
+
75
+ input.write('first command\n');
76
+ input.write('second command\n');
77
+
78
+ await wait(10);
79
+
80
+ assert.deepEqual(history, ['first command', 'second command']);
81
+ });
82
+
83
+ test('pause and resume functionality', async () => {
84
+ const input = new PassThrough();
85
+ await using rl = createInterface({ input });
86
+
87
+ const lines: string[] = [];
88
+ rl.on('line', (line: string) => lines.push(line));
89
+
90
+ rl.pause();
91
+ input.write('should not be processed\n');
92
+
93
+ await wait(10);
94
+
95
+ assert.deepEqual(lines, []);
96
+
97
+ rl.resume();
98
+ input.write('should be processed\n');
99
+
100
+ await wait(10);
101
+
102
+ assert.deepEqual(lines, ['should be processed']);
103
+ });
104
+ });
package/tests/common.ts CHANGED
@@ -1,26 +1,11 @@
1
1
  import { join, resolve } from 'node:path';
2
- import { fs as defaultFS, log } from '../dist/index.js';
2
+ import { fs as defaultFS } from '../dist/index.js';
3
+ import { setupLogs } from './logs.js';
3
4
  export type * from '../dist/index.js';
4
5
 
5
- const { ZENFS_LOG_LEVEL, SETUP } = process.env;
6
+ setupLogs();
6
7
 
7
- let level: log.Level | (typeof log.levels)[log.Level] = log.Level.CRIT;
8
-
9
- if (ZENFS_LOG_LEVEL) {
10
- const tmp = parseInt(ZENFS_LOG_LEVEL);
11
- if (Number.isSafeInteger(tmp)) level = tmp;
12
- else level = ZENFS_LOG_LEVEL as (typeof log.levels)[log.Level];
13
- }
14
-
15
- log.configure({
16
- enabled: true,
17
- format: log.formats.ansi_message,
18
- dumpBacklog: true,
19
- level,
20
- output: console.error,
21
- });
22
-
23
- const setupPath = resolve(SETUP || join(import.meta.dirname, 'setup/memory.ts'));
8
+ const setupPath = resolve(process.env.SETUP || join(import.meta.dirname, 'setup/memory.ts'));
24
9
 
25
10
  const setup = await import(setupPath).catch(error => {
26
11
  console.log('Failed to import test setup:');
@@ -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: {
@@ -13,7 +13,7 @@ await configure({
13
13
  log: {
14
14
  enabled: true,
15
15
  output: console.error,
16
- format: log.formats.ansi_message,
16
+ format: log.fancy({ style: 'ansi', colorize: 'message' }),
17
17
  level: log.Level.INFO,
18
18
  dumpBacklog: true,
19
19
  },
@@ -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', () => {
@@ -1,36 +1,32 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import test, { suite } from 'node:test';
3
3
  import { fs } from '../common.js';
4
- import type { ErrnoError } from '../../dist/index.js';
5
4
 
6
5
  const existingFile = '/exit.js';
7
6
 
8
7
  suite('Error messages', () => {
9
8
  const path = '/non-existent';
10
9
 
11
- fs.promises.stat(path).catch((error: ErrnoError) => {
12
- assert.equal(error.bufferSize(), 4 + JSON.stringify(error.toJSON()).length);
13
- });
14
-
15
10
  const missing = { path, code: 'ENOENT' };
16
- const existing = { path: existingFile };
11
+ const existing = { path: existingFile, code: 'EEXIST' };
12
+ const notDir = { path: existingFile, code: 'ENOTDIR' };
17
13
 
18
- test('stat', () => assert.rejects(() => fs.promises.stat(path), missing));
19
- test('mkdir', () => assert.rejects(() => fs.promises.mkdir(existingFile, 0o666), existing));
20
- test('rmdir', () => assert.rejects(() => fs.promises.rmdir(path), missing));
21
- test('rmdir', () => assert.rejects(() => fs.promises.rmdir(existingFile), existing));
22
- test('rename', () => assert.rejects(() => fs.promises.rename(path, 'foo'), missing));
23
- test('open', () => assert.rejects(() => fs.promises.open(path, 'r'), missing));
24
- test('readdir', () => assert.rejects(() => fs.promises.readdir(path), missing));
25
- test('unlink', () => assert.rejects(() => fs.promises.unlink(path), missing));
26
- test('link', () => assert.rejects(() => fs.promises.link(path, 'foo'), missing));
27
- test('chmod', () => assert.rejects(() => fs.promises.chmod(path, 0o666), missing));
28
- test('lstat', () => assert.rejects(() => fs.promises.lstat(path), missing));
29
- test('readlink', () => assert.rejects(() => fs.promises.readlink(path), missing));
14
+ test('stat', async () => await assert.rejects(() => fs.promises.stat(path), missing));
15
+ test('mkdir', async () => await assert.rejects(() => fs.promises.mkdir(existingFile, 0o666), existing));
16
+ test('rmdir (missing)', async () => await assert.rejects(() => fs.promises.rmdir(path), missing));
17
+ test('rmdir (existing)', async () => await assert.rejects(() => fs.promises.rmdir(existingFile), notDir));
18
+ test('rename', async () => await assert.rejects(() => fs.promises.rename(path, 'foo'), missing));
19
+ test('open', async () => await assert.rejects(() => fs.promises.open(path, 'r'), missing));
20
+ test('readdir', async () => await assert.rejects(() => fs.promises.readdir(path), missing));
21
+ test('unlink', async () => await assert.rejects(() => fs.promises.unlink(path), missing));
22
+ test('link', async () => await assert.rejects(() => fs.promises.link(path, 'foo'), missing));
23
+ test('chmod', async () => await assert.rejects(() => fs.promises.chmod(path, 0o666), missing));
24
+ test('lstat', async () => await assert.rejects(() => fs.promises.lstat(path), missing));
25
+ test('readlink', async () => await assert.rejects(() => fs.promises.readlink(path), missing));
30
26
  test('statSync', () => assert.throws(() => fs.statSync(path), missing));
31
27
  test('mkdirSync', () => assert.throws(() => fs.mkdirSync(existingFile, 0o666), existing));
32
28
  test('rmdirSync', () => assert.throws(() => fs.rmdirSync(path), missing));
33
- test('rmdirSync', () => assert.throws(() => fs.rmdirSync(existingFile), existing));
29
+ test('rmdirSync', () => assert.throws(() => fs.rmdirSync(existingFile), notDir));
34
30
  test('renameSync', () => assert.throws(() => fs.renameSync(path, 'foo'), missing));
35
31
  test('openSync', () => assert.throws(() => fs.openSync(path, 'r'), missing));
36
32
  test('readdirSync', () => assert.throws(() => fs.readdirSync(path), missing));
@@ -1,8 +1,8 @@
1
+ import type { Exception } from 'kerium';
1
2
  import assert from 'node:assert/strict';
2
3
  import { suite, test } from 'node:test';
3
- import { join } from '../../dist/vfs/path.js';
4
+ import { join } from '../../dist/path.js';
4
5
  import { fs } from '../common.js';
5
- import type { ErrnoError } from '../../dist/index.js';
6
6
 
7
7
  suite('Links', () => {
8
8
  const target = '/a1.js',
@@ -21,6 +21,7 @@ suite('Links', () => {
21
21
  test('readlink', async () => {
22
22
  const destination = await fs.promises.readlink(symlink);
23
23
  assert.equal(destination, target);
24
+ assert.throws(() => fs.readlinkSync(destination));
24
25
  });
25
26
 
26
27
  test('read target contents', async () => {
@@ -59,7 +60,7 @@ suite('Links', () => {
59
60
  });
60
61
 
61
62
  test('link', async t => {
62
- const _ = await fs.promises.link(target, hardlink).catch((e: ErrnoError) => {
63
+ const _ = await fs.promises.link(target, hardlink).catch((e: Exception) => {
63
64
  if (e.code == 'ENOSYS') return e;
64
65
  throw e;
65
66
  });
@@ -1,30 +1,13 @@
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
- suite('fs file opening', () => {
5
+ suite('Opening files', () => {
7
6
  const filename = 'a.js';
8
7
 
9
- test('throw ENOENT when opening non-existent file (sync)', () => {
10
- let caughtException = false;
11
- try {
12
- fs.openSync('/path/to/file/that/does/not/exist', 'r');
13
- } catch (error: any) {
14
- assert(error instanceof ErrnoError);
15
- assert.equal(error?.code, 'ENOENT');
16
- caughtException = true;
17
- }
18
- assert(caughtException);
19
- });
20
-
21
- test('throw ENOENT when opening non-existent file (async)', async () => {
22
- try {
23
- await fs.promises.open('/path/to/file/that/does/not/exist', 'r');
24
- } catch (error: any) {
25
- assert(error instanceof ErrnoError);
26
- assert.equal(error?.code, 'ENOENT');
27
- }
8
+ test('throw ENOENT when opening non-existent file', async () => {
9
+ assert.throws(() => fs.openSync('/path/to/file/that/does/not/exist', 'r'), { code: 'ENOENT' });
10
+ await assert.rejects(fs.promises.open('/path/to/file/that/does/not/exist', 'r'), { code: 'ENOENT' });
28
11
  });
29
12
 
30
13
  test('open file with mode "r"', async () => {
@@ -1,9 +1,10 @@
1
+ import { Exception } from 'kerium';
1
2
  import assert from 'node:assert/strict';
2
3
  import { suite, test } from 'node:test';
3
- import { credentials, ErrnoError } from '../../dist/index.js';
4
- import { encodeUTF8 } from '../../dist/utils.js';
4
+ import { encodeUTF8 } from 'utilium';
5
+ import { defaultContext } from '../../dist/internal/contexts.js';
6
+ import { join } from '../../dist/path.js';
5
7
  import { R_OK, W_OK, X_OK } from '../../dist/vfs/constants.js';
6
- import { join } from '../../dist/vfs/path.js';
7
8
  import { fs } from '../common.js';
8
9
 
9
10
  const asyncMode = 0o777;
@@ -47,19 +48,16 @@ suite('Permissions', () => {
47
48
  });
48
49
 
49
50
  async function test_item(path: string): Promise<void> {
50
- const stats = await fs.promises.stat(path).catch((error: ErrnoError) => {
51
- assert(error instanceof ErrnoError);
51
+ const stats = await fs.promises.stat(path).catch((error: Exception) => {
52
+ assert(error instanceof Exception);
52
53
  assert.equal(error.code, 'EACCES');
53
54
  });
54
- if (!stats) {
55
- return;
56
- }
55
+ if (!stats) return;
57
56
  assert(stats.hasAccess(X_OK));
58
57
 
59
58
  function checkError(access: number) {
60
- return function (error: ErrnoError) {
61
- assert(error instanceof ErrnoError);
62
- assert(error);
59
+ return function (error: Exception) {
60
+ assert(error instanceof Exception);
63
61
  assert(!stats!.hasAccess(access));
64
62
  };
65
63
  }
@@ -79,16 +77,14 @@ suite('Permissions', () => {
79
77
  await fs.promises.unlink(testFile).catch(checkError(W_OK));
80
78
  } else {
81
79
  const handle = await fs.promises.open(path, 'a').catch(checkError(W_OK));
82
- if (!handle) {
83
- return;
84
- }
80
+ if (!handle) return;
85
81
  await handle.close();
86
82
  }
87
- assert(stats.hasAccess(R_OK));
83
+ assert(stats.hasAccess(W_OK));
88
84
  }
89
85
 
90
- const copy = { ...credentials };
91
- Object.assign(credentials, { uid: 1000, gid: 1000, euid: 1000, egid: 1000 });
86
+ const copy = { ...defaultContext.credentials };
87
+ Object.assign(defaultContext.credentials, { uid: 1000, gid: 1000, euid: 1000, egid: 1000 });
92
88
  test('Access controls: /', () => test_item('/'));
93
- Object.assign(credentials, copy);
89
+ Object.assign(defaultContext.credentials, copy);
94
90
  });
@@ -1,10 +1,11 @@
1
+ import { Buffer } from 'buffer';
1
2
  import assert from 'node:assert/strict';
2
3
  import { suite, test } from 'node:test';
3
4
  import { fs } from '../common.js';
4
- import { Buffer } from 'buffer';
5
5
 
6
6
  const filepath = 'x.txt';
7
7
  const expected = 'xyz\n';
8
+ const ellipses = '…'.repeat(10_000);
8
9
 
9
10
  suite('read', () => {
10
11
  test('read file asynchronously', async () => {
@@ -22,25 +23,25 @@ suite('read', () => {
22
23
  assert.equal(bytesRead, expected.length);
23
24
  assert.equal(buffer.toString(), expected);
24
25
  });
25
- });
26
26
 
27
- suite('read binary', () => {
28
- test('Read a file and check its binary bytes (asynchronous)', async () => {
27
+ test('Read a file and check its binary bytes asynchronously', async () => {
29
28
  const buff = await fs.promises.readFile('elipses.txt');
29
+ assert.equal(buff.length, 30_000);
30
+ assert.equal(buff.toString(), ellipses);
30
31
  assert.equal((buff[1] << 8) | buff[0], 32994);
31
32
  });
32
33
 
33
- test('Read a file and check its binary bytes (synchronous)', () => {
34
+ test('Read a file and check its binary bytes synchronously', () => {
34
35
  const buff = fs.readFileSync('elipses.txt');
36
+ assert.equal(buff.length, 30_000);
37
+ assert.equal(buff.toString(), ellipses);
35
38
  assert.equal((buff[1] << 8) | buff[0], 32994);
36
39
  });
37
- });
38
40
 
39
- suite('read buffer', () => {
40
41
  const bufferAsync = Buffer.alloc(expected.length);
41
42
  const bufferSync = Buffer.alloc(expected.length);
42
43
 
43
- test('read file asynchronously', async () => {
44
+ test('read file from handle asynchronously', async () => {
44
45
  const handle = await fs.promises.open(filepath, 'r');
45
46
  const { bytesRead } = await handle.read(bufferAsync, 0, expected.length, 0);
46
47
 
@@ -48,7 +49,7 @@ suite('read buffer', () => {
48
49
  assert.equal(bufferAsync.toString(), expected);
49
50
  });
50
51
 
51
- test('read file synchronously', () => {
52
+ test('read file from handle synchronously', () => {
52
53
  const fd = fs.openSync(filepath, 'r');
53
54
  const bytesRead = fs.readSync(fd, bufferSync, 0, expected.length, 0);
54
55