@zenfs/core 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/backends/backend.js +3 -4
  2. package/dist/backends/fetch.d.ts +17 -18
  3. package/dist/backends/fetch.js +95 -58
  4. package/dist/backends/index.d.ts +2 -1
  5. package/dist/backends/index.js +2 -1
  6. package/dist/backends/memory.d.ts +1 -1
  7. package/dist/backends/overlay.d.ts +7 -2
  8. package/dist/backends/overlay.js +32 -9
  9. package/dist/backends/passthrough.d.ts +4 -0
  10. package/dist/backends/passthrough.js +128 -0
  11. package/dist/backends/port/fs.d.ts +9 -44
  12. package/dist/backends/port/fs.js +93 -116
  13. package/dist/backends/port/rpc.d.ts +8 -5
  14. package/dist/backends/port/rpc.js +9 -7
  15. package/dist/backends/store/file_index.d.ts +38 -0
  16. package/dist/backends/store/file_index.js +76 -0
  17. package/dist/backends/store/fs.d.ts +55 -34
  18. package/dist/backends/store/fs.js +417 -233
  19. package/dist/backends/store/index_fs.d.ts +34 -0
  20. package/dist/backends/store/index_fs.js +67 -0
  21. package/dist/backends/store/inode.d.ts +26 -8
  22. package/dist/backends/store/inode.js +92 -91
  23. package/dist/backends/store/simple.d.ts +20 -20
  24. package/dist/backends/store/simple.js +3 -4
  25. package/dist/backends/store/store.d.ts +12 -12
  26. package/dist/backends/store/store.js +4 -6
  27. package/dist/devices.d.ts +11 -10
  28. package/dist/devices.js +15 -11
  29. package/dist/file.d.ts +111 -7
  30. package/dist/file.js +319 -71
  31. package/dist/filesystem.d.ts +22 -4
  32. package/dist/mixins/mutexed.d.ts +7 -2
  33. package/dist/mixins/mutexed.js +56 -0
  34. package/dist/mixins/sync.d.ts +1 -1
  35. package/dist/stats.d.ts +12 -6
  36. package/dist/stats.js +14 -6
  37. package/dist/utils.d.ts +17 -3
  38. package/dist/utils.js +32 -10
  39. package/dist/vfs/constants.d.ts +2 -2
  40. package/dist/vfs/constants.js +2 -2
  41. package/dist/vfs/dir.js +3 -1
  42. package/dist/vfs/index.js +4 -1
  43. package/dist/vfs/promises.js +31 -11
  44. package/dist/vfs/shared.js +2 -0
  45. package/dist/vfs/sync.js +25 -13
  46. package/dist/vfs/types.d.ts +15 -0
  47. package/package.json +2 -3
  48. package/readme.md +2 -2
  49. package/scripts/test.js +73 -11
  50. package/tests/common/mutex.test.ts +1 -1
  51. package/tests/fetch/run.sh +16 -0
  52. package/tests/fetch/server.ts +49 -0
  53. package/tests/fetch/setup.ts +13 -0
  54. package/tests/fs/read.test.ts +10 -10
  55. package/tests/fs/times.test.ts +2 -2
  56. package/tests/setup/index.ts +38 -0
  57. package/tests/setup/port.ts +15 -0
  58. package/dist/backends/file_index.d.ts +0 -63
  59. package/dist/backends/file_index.js +0 -163
  60. package/tests/common/async.test.ts +0 -31
  61. package/tests/setup/cow+fetch.ts +0 -45
  62. /package/tests/fs/{appendFile.test.ts → append.test.ts} +0 -0
package/scripts/test.js CHANGED
@@ -16,7 +16,10 @@ const { values: options, positionals } = parseArgs({
16
16
  build: { short: 'b', type: 'boolean', default: false },
17
17
  common: { short: 'c', type: 'boolean', default: false },
18
18
  coverage: { type: 'string', default: 'tests/.coverage' },
19
+ preserve: { short: 'p', type: 'boolean' },
19
20
  'exit-on-fail': { short: 'e', type: 'boolean' },
21
+ report: { type: 'boolean' },
22
+ clean: { type: 'boolean' },
20
23
  },
21
24
  allowPositionals: true,
22
25
  });
@@ -36,7 +39,12 @@ Options:
36
39
  -q, --quiet Don't output normal messages
37
40
  -t, --test <glob> Which FS test suite(s) to run
38
41
  -f, --force Whether to use --test-force-exit
39
- --coverage <dir> Override the default coverage data directory`);
42
+
43
+ Coverage:
44
+ --coverage <dir> Override the default coverage data directory
45
+ -p,--preserve Do not delete or report coverage data
46
+ --report ONLY report coverage
47
+ --clean ONLY clean up coverage directory`);
40
48
  process.exit();
41
49
  }
42
50
 
@@ -45,6 +53,19 @@ if (options.quiet && options.verbose) {
45
53
  process.exit(1);
46
54
  }
47
55
 
56
+ process.env.NODE_V8_COVERAGE = options.coverage;
57
+
58
+ if (options.clean) {
59
+ rmSync(options.coverage, { recursive: true, force: true });
60
+ process.exit();
61
+ }
62
+
63
+ if (options.report) {
64
+ execSync('npx c8 report --reporter=text', { stdio: 'inherit' });
65
+ rmSync(options.coverage, { recursive: true, force: true });
66
+ process.exit();
67
+ }
68
+
48
69
  options.verbose && options.force && console.debug('Forcing tests to exit (--test-force-exit)');
49
70
 
50
71
  if (options.build) {
@@ -73,19 +94,56 @@ if (options.auto) {
73
94
  !options.quiet && console.log(`Auto-detected ${sum} test setup files`);
74
95
  }
75
96
 
76
- if (existsSync(options.coverage)) rmSync(options.coverage, { recursive: true });
77
- mkdirSync(options.coverage);
78
- process.env.NODE_V8_COVERAGE = options.coverage;
97
+ /**
98
+ * Colorizes some text
99
+ * @param {string} text Text to color
100
+ * @param {string | number} code ANSI escape code
101
+ * @returns
102
+ */
103
+ function color(text, code) {
104
+ return `\x1b[${code}m${text}\x1b[0m`;
105
+ }
106
+
107
+ function status(name) {
108
+ const start = performance.now();
109
+
110
+ const time = () => {
111
+ let delta = Math.round(performance.now() - start),
112
+ unit = 'ms';
113
+
114
+ if (delta > 5000) {
115
+ delta /= 1000;
116
+ unit = 's';
117
+ }
118
+
119
+ return color(`(${delta} ${unit})`, '2;37');
120
+ };
121
+
122
+ return {
123
+ pass() {
124
+ if (!options.quiet) console.log(`${color('passed', 32)}: ${name} ${time()}`);
125
+ },
126
+ fail() {
127
+ console.error(`${color('failed', '1;31')}: ${name} ${time()}`);
128
+ process.exitCode = 1;
129
+ if (options['exit-on-fail']) process.exit();
130
+ },
131
+ };
132
+ }
133
+
134
+ if (!options.preserve) rmSync(options.coverage, { force: true, recursive: true });
135
+ mkdirSync(options.coverage, { recursive: true });
79
136
 
80
137
  if (options.common) {
81
138
  !options.quiet && console.log('Running common tests...');
139
+ const { pass, fail } = status('Common tests');
82
140
  try {
83
141
  execSync("tsx --test --experimental-test-coverage 'tests/*.test.ts' 'tests/**/!(fs)/*.test.ts'", {
84
142
  stdio: ['ignore', options.verbose ? 'inherit' : 'ignore', 'inherit'],
85
143
  });
144
+ pass();
86
145
  } catch {
87
- console.error('Common tests failed');
88
- if (options['exit-on-fail']) process.exit(1);
146
+ fail();
89
147
  }
90
148
  }
91
149
 
@@ -97,18 +155,22 @@ for (const setupFile of positionals) {
97
155
  continue;
98
156
  }
99
157
 
100
- !options.quiet && console.debug('Running tests using setup:', setupFile);
158
+ !options.quiet && console.log('Running tests:', setupFile);
101
159
  process.env.SETUP = setupFile;
102
160
 
161
+ const { pass, fail } = status(setupFile);
162
+
103
163
  try {
104
164
  execSync(['tsx --test --experimental-test-coverage', options.force ? '--test-force-exit' : '', testsGlob, process.env.CMD].join(' '), {
105
165
  stdio: ['ignore', options.verbose ? 'inherit' : 'ignore', 'inherit'],
106
166
  });
167
+ pass();
107
168
  } catch {
108
- !options.quiet && console.error('Tests failed:', setupFile);
109
- if (options['exit-on-fail']) process.exit(1);
169
+ fail();
110
170
  }
111
171
  }
112
172
 
113
- execSync('npx c8 report --reporter=text', { stdio: 'inherit' });
114
- rmSync(options.coverage, { recursive: true });
173
+ if (!options.preserve) {
174
+ execSync('npx c8 report --reporter=text', { stdio: 'inherit' });
175
+ rmSync(options.coverage, { recursive: true });
176
+ }
@@ -51,7 +51,7 @@ suite('LockFS mutex', () => {
51
51
 
52
52
  async function foo() {
53
53
  const lock = await fs.lock('raceConditions', 'test');
54
- await wait(100);
54
+ await wait(25);
55
55
  x++;
56
56
  lock.unlock();
57
57
  }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/bash
2
+
3
+ # Credit: Dave Dopson, https://stackoverflow.com/a/246128/17637456
4
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
5
+
6
+ npx tsx $SCRIPT_DIR/server.ts &
7
+ PID=$!
8
+
9
+ echo "Waiting for server to start..."
10
+ until nc -z localhost 26514; do
11
+ sleep 0.5
12
+ done
13
+
14
+ npx zenfs-test $SCRIPT_DIR/setup.ts --preserve --force $@
15
+
16
+ kill $PID
@@ -0,0 +1,49 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import { createServer } from 'node:http';
4
+ import { join } from 'node:path';
5
+ import { data, tmp } from '../setup.js';
6
+
7
+ // If you change the port please update the setup file as well
8
+ const port = 26514;
9
+
10
+ const statusCodes = {
11
+ ENOENT: 404,
12
+ };
13
+
14
+ try {
15
+ execSync(`npm exec make-index -- ${data} --output ${tmp}/index.json --quiet`, { stdio: 'inherit' });
16
+ } catch (e: any) {
17
+ if (e.signal == 'SIGINT') {
18
+ console.log('Aborted whilst creating index.');
19
+ process.exit(0);
20
+ } else {
21
+ console.error('Index creation failed: ' + e.message);
22
+ process.exit(1);
23
+ }
24
+ }
25
+
26
+ const server = createServer((request, response) => {
27
+ const { url = '' } = request;
28
+
29
+ if (url == '/.ping') {
30
+ response.statusCode = 200;
31
+ response.end('pong');
32
+ return;
33
+ }
34
+
35
+ const path = url == '/.index.json' ? join(tmp, 'index.json') : join(data, url.slice(1) || '');
36
+ try {
37
+ response.statusCode = 200;
38
+ response.end(readFileSync(path));
39
+ } catch (e: any) {
40
+ response.statusCode = statusCodes[e.code as keyof typeof statusCodes] || 400;
41
+ response.end();
42
+ }
43
+ });
44
+
45
+ server.listen(port);
46
+
47
+ process.on('beforeExit', () => {
48
+ server.close().unref();
49
+ });
@@ -0,0 +1,13 @@
1
+ import { configureSingle, Fetch, InMemory, Overlay, resolveMountConfig } from '../../dist/index.js';
2
+
3
+ const baseUrl = 'http://localhost:26514';
4
+
5
+ await configureSingle({
6
+ backend: Overlay,
7
+ readable: await resolveMountConfig({
8
+ backend: Fetch,
9
+ baseUrl,
10
+ index: baseUrl + '/.index.json',
11
+ }),
12
+ writable: InMemory.create({ name: 'cow' }),
13
+ });
@@ -1,17 +1,17 @@
1
1
  import assert from 'node:assert';
2
2
  import { suite, test } from 'node:test';
3
3
  import { fs } from '../common.js';
4
+ import { Buffer } from 'buffer';
4
5
 
5
- const filepath: string = 'x.txt';
6
- const expected: string = 'xyz\n';
6
+ const filepath = 'x.txt';
7
+ const expected = 'xyz\n';
7
8
 
8
9
  suite('read', () => {
9
10
  test('read file asynchronously', async () => {
10
11
  const handle = await fs.promises.open(filepath, 'r');
11
12
  const { bytesRead, buffer } = await handle.read(Buffer.alloc(expected.length), 0, expected.length, 0);
12
-
13
13
  assert.equal(bytesRead, expected.length);
14
- assert(buffer.toString() == expected);
14
+ assert.equal(buffer.toString(), expected);
15
15
  });
16
16
 
17
17
  test('read file synchronously', () => {
@@ -20,19 +20,19 @@ suite('read', () => {
20
20
  const bytesRead = fs.readSync(fd, buffer, 0, expected.length, 0);
21
21
 
22
22
  assert.equal(bytesRead, expected.length);
23
- assert(buffer.toString() == expected);
23
+ assert.equal(buffer.toString(), expected);
24
24
  });
25
25
  });
26
26
 
27
27
  suite('read binary', () => {
28
28
  test('Read a file and check its binary bytes (asynchronous)', async () => {
29
29
  const buff = await fs.promises.readFile('elipses.txt');
30
- assert(((buff[1] << 8) | buff[0]) === 32994);
30
+ assert.strictEqual((buff[1] << 8) | buff[0], 32994);
31
31
  });
32
32
 
33
33
  test('Read a file and check its binary bytes (synchronous)', () => {
34
34
  const buff = fs.readFileSync('elipses.txt');
35
- assert(((buff[1] << 8) | buff[0]) === 32994);
35
+ assert.strictEqual((buff[1] << 8) | buff[0], 32994);
36
36
  });
37
37
  });
38
38
 
@@ -45,14 +45,14 @@ suite('read buffer', () => {
45
45
  const { bytesRead } = await handle.read(bufferAsync, 0, expected.length, 0);
46
46
 
47
47
  assert.strictEqual(bytesRead, expected.length);
48
- assert(bufferAsync.toString() === expected);
48
+ assert.strictEqual(bufferAsync.toString(), expected);
49
49
  });
50
50
 
51
51
  test('read file synchronously', () => {
52
52
  const fd = fs.openSync(filepath, 'r');
53
53
  const bytesRead = fs.readSync(fd, bufferSync, 0, expected.length, 0);
54
54
 
55
- assert(bufferSync.toString() === expected);
55
+ assert.strictEqual(bufferSync.toString(), expected);
56
56
  assert.strictEqual(bytesRead, expected.length);
57
57
  });
58
58
 
@@ -61,7 +61,7 @@ suite('read buffer', () => {
61
61
  const buffer = Buffer.alloc(expected.length + 10);
62
62
  const bytesRead = fs.readSync(fd, buffer, 10, expected.length, 0);
63
63
 
64
- assert(buffer.subarray(10, buffer.length).toString() === expected);
64
+ assert.strictEqual(buffer.subarray(10, buffer.length).toString(), expected);
65
65
  assert.strictEqual(bytesRead, expected.length);
66
66
  });
67
67
  });
@@ -69,7 +69,7 @@ suite('times', () => {
69
69
  test('read changes atime', async () => {
70
70
  const before = fs.statSync(path).atimeMs;
71
71
  fs.readFileSync(path);
72
- await wait(100);
72
+ await wait(25);
73
73
  const after = fs.statSync(path).atimeMs;
74
74
  assert(before < after);
75
75
  });
@@ -77,7 +77,7 @@ suite('times', () => {
77
77
  test('write changes mtime', async () => {
78
78
  const before = fs.statSync(path).mtimeMs;
79
79
  fs.writeFileSync(path, 'cool');
80
- await wait(100);
80
+ await wait(25);
81
81
  const after = fs.statSync(path).mtimeMs;
82
82
  assert(before < after);
83
83
  });
@@ -0,0 +1,38 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path/posix';
3
+ import { configureSingle, InMemory, InMemoryStore, mounts, Overlay, Readonly, resolveMountConfig, StoreFS } from '../../dist/index.js';
4
+ import { S_IFDIR } from '../../dist/vfs/constants.js';
5
+ import { copy, data } from '../setup.js';
6
+
7
+ copy(data);
8
+
9
+ const index = (mounts.get('/') as StoreFS).createIndexSync();
10
+
11
+ class MockFS extends Readonly(StoreFS) {
12
+ constructor() {
13
+ super(new InMemoryStore());
14
+ this.loadIndexSync(index);
15
+
16
+ using tx = this.store.transaction();
17
+
18
+ for (const [path, node] of index) {
19
+ if (node.mode & S_IFDIR) continue;
20
+
21
+ const content = readFileSync(join(data, path));
22
+
23
+ tx.setSync(node.data, content);
24
+ }
25
+
26
+ tx.commitSync();
27
+ }
28
+ }
29
+
30
+ const readable = new MockFS();
31
+
32
+ await readable.ready();
33
+
34
+ await configureSingle({
35
+ backend: Overlay,
36
+ readable,
37
+ writable: await resolveMountConfig({ backend: InMemory, name: 'cow' }),
38
+ });
@@ -0,0 +1,15 @@
1
+ import { MessageChannel } from 'node:worker_threads';
2
+ import { InMemory, Port, configureSingle, fs, resolveMountConfig, resolveRemoteMount } from '../../dist/index.js';
3
+ import { copy, data } from '../setup.js';
4
+
5
+ const { port1: localPort, port2: remotePort } = new MessageChannel();
6
+
7
+ fs.umount('/');
8
+ const tmpfs = await resolveMountConfig({ backend: InMemory, name: 'tmp' });
9
+
10
+ fs.mount('/', tmpfs);
11
+ copy(data, fs);
12
+
13
+ await resolveRemoteMount(remotePort, tmpfs);
14
+
15
+ await configureSingle({ backend: Port, port: localPort });
@@ -1,63 +0,0 @@
1
- import { NoSyncFile } from '../file.js';
2
- import { FileSystem } from '../filesystem.js';
3
- import type { StatsLike } from '../stats.js';
4
- import { Stats } from '../stats.js';
5
- /**
6
- * An Index in JSON form
7
- * @internal
8
- */
9
- export interface IndexData {
10
- version: number;
11
- entries: Record<string, StatsLike<number>>;
12
- }
13
- export declare const version = 1;
14
- /**
15
- * An index of files
16
- * @internal
17
- */
18
- export declare class Index extends Map<string, Stats> {
19
- /**
20
- * Convenience method
21
- */
22
- files(): Map<string, Stats>;
23
- /**
24
- * Converts the index to JSON
25
- */
26
- toJSON(): IndexData;
27
- /**
28
- * Converts the index to a string
29
- */
30
- toString(): string;
31
- /**
32
- * Returns the files in the directory `dir`.
33
- * This is expensive so it is only called once per directory.
34
- */
35
- protected dirEntries(dir: string): string[];
36
- /**
37
- * Loads the index from JSON data
38
- */
39
- fromJSON(json: IndexData): void;
40
- /**
41
- * Parses an index from a string
42
- */
43
- static parse(data: string): Index;
44
- }
45
- declare const IndexFS_base: import("../index.js").Mixin<typeof FileSystem, import("../mixins/readonly.js").ReadonlyMixin>;
46
- export declare abstract class IndexFS extends IndexFS_base {
47
- private indexData;
48
- protected index: Index;
49
- protected _isInitialized: boolean;
50
- ready(): Promise<void>;
51
- constructor(indexData: IndexData | Promise<IndexData>);
52
- reloadFiles(): Promise<void>;
53
- reloadFilesSync(): void;
54
- stat(path: string): Promise<Stats>;
55
- statSync(path: string): Stats;
56
- openFile(path: string, flag: string): Promise<NoSyncFile<this>>;
57
- openFileSync(path: string, flag: string): NoSyncFile<this>;
58
- readdir(path: string): Promise<string[]>;
59
- readdirSync(path: string): string[];
60
- protected abstract getData(path: string, stats: Stats): Promise<Uint8Array>;
61
- protected abstract getDataSync(path: string, stats: Stats): Uint8Array;
62
- }
63
- export {};
@@ -1,163 +0,0 @@
1
- /* Note: this file is named file_index.ts because Typescript has special behavior regarding index.ts which can't be disabled. */
2
- import { isJSON } from 'utilium';
3
- import { Errno, ErrnoError } from '../error.js';
4
- import { NoSyncFile, isWriteable } from '../file.js';
5
- import { FileSystem } from '../filesystem.js';
6
- import { Readonly } from '../mixins/readonly.js';
7
- import { Stats } from '../stats.js';
8
- import { decodeUTF8, encodeUTF8 } from '../utils.js';
9
- import { basename, dirname } from '../vfs/path.js';
10
- export const version = 1;
11
- /**
12
- * An index of files
13
- * @internal
14
- */
15
- export class Index extends Map {
16
- /**
17
- * Convenience method
18
- */
19
- files() {
20
- const files = new Map();
21
- for (const [path, stats] of this) {
22
- if (stats.isFile()) {
23
- files.set(path, stats);
24
- }
25
- }
26
- return files;
27
- }
28
- /**
29
- * Converts the index to JSON
30
- */
31
- toJSON() {
32
- return {
33
- version,
34
- entries: Object.fromEntries(this),
35
- };
36
- }
37
- /**
38
- * Converts the index to a string
39
- */
40
- toString() {
41
- return JSON.stringify(this.toJSON());
42
- }
43
- /**
44
- * Returns the files in the directory `dir`.
45
- * This is expensive so it is only called once per directory.
46
- */
47
- dirEntries(dir) {
48
- const entries = [];
49
- for (const entry of this.keys()) {
50
- if (dirname(entry) == dir) {
51
- entries.push(basename(entry));
52
- }
53
- }
54
- return entries;
55
- }
56
- /**
57
- * Loads the index from JSON data
58
- */
59
- fromJSON(json) {
60
- if (json.version != version) {
61
- throw new ErrnoError(Errno.EINVAL, 'Index version mismatch');
62
- }
63
- this.clear();
64
- for (const [path, data] of Object.entries(json.entries)) {
65
- const stats = new Stats(data);
66
- if (stats.isDirectory()) {
67
- stats.fileData = encodeUTF8(JSON.stringify(this.dirEntries(path)));
68
- }
69
- this.set(path, stats);
70
- }
71
- }
72
- /**
73
- * Parses an index from a string
74
- */
75
- static parse(data) {
76
- if (!isJSON(data)) {
77
- throw new ErrnoError(Errno.EINVAL, 'Invalid JSON');
78
- }
79
- const json = JSON.parse(data);
80
- const index = new Index();
81
- index.fromJSON(json);
82
- return index;
83
- }
84
- }
85
- export class IndexFS extends Readonly(FileSystem) {
86
- async ready() {
87
- await super.ready();
88
- if (this._isInitialized) {
89
- return;
90
- }
91
- this.index.fromJSON(await this.indexData);
92
- this._isInitialized = true;
93
- }
94
- constructor(indexData) {
95
- super();
96
- this.indexData = indexData;
97
- this.index = new Index();
98
- this._isInitialized = false;
99
- }
100
- async reloadFiles() {
101
- for (const [path, stats] of this.index.files()) {
102
- delete stats.fileData;
103
- stats.fileData = await this.getData(path, stats);
104
- }
105
- }
106
- reloadFilesSync() {
107
- for (const [path, stats] of this.index.files()) {
108
- delete stats.fileData;
109
- stats.fileData = this.getDataSync(path, stats);
110
- }
111
- }
112
- stat(path) {
113
- return Promise.resolve(this.statSync(path));
114
- }
115
- statSync(path) {
116
- if (!this.index.has(path)) {
117
- throw ErrnoError.With('ENOENT', path, 'stat');
118
- }
119
- return this.index.get(path);
120
- }
121
- async openFile(path, flag) {
122
- if (isWriteable(flag)) {
123
- // You can't write to files on this file system.
124
- throw new ErrnoError(Errno.EPERM, path);
125
- }
126
- // Check if the path exists, and is a file.
127
- const stats = this.index.get(path);
128
- if (!stats) {
129
- throw ErrnoError.With('ENOENT', path, 'openFile');
130
- }
131
- return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : await this.getData(path, stats));
132
- }
133
- openFileSync(path, flag) {
134
- if (isWriteable(flag)) {
135
- // You can't write to files on this file system.
136
- throw new ErrnoError(Errno.EPERM, path);
137
- }
138
- // Check if the path exists, and is a file.
139
- const stats = this.index.get(path);
140
- if (!stats) {
141
- throw ErrnoError.With('ENOENT', path, 'openFile');
142
- }
143
- return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : this.getDataSync(path, stats));
144
- }
145
- readdir(path) {
146
- return Promise.resolve(this.readdirSync(path));
147
- }
148
- readdirSync(path) {
149
- // Check if it exists.
150
- const stats = this.index.get(path);
151
- if (!stats) {
152
- throw ErrnoError.With('ENOENT', path, 'readdir');
153
- }
154
- const content = JSON.parse(decodeUTF8(stats.fileData));
155
- if (!Array.isArray(content)) {
156
- throw ErrnoError.With('ENODATA', path, 'readdir');
157
- }
158
- if (!content.every(item => typeof item == 'string')) {
159
- throw ErrnoError.With('ENODATA', path, 'readdir');
160
- }
161
- return content;
162
- }
163
- }
@@ -1,31 +0,0 @@
1
- import assert from 'node:assert';
2
- import { suite, test } from 'node:test';
3
- import { configure } from '../../dist/config.js';
4
- import * as fs from '../../dist/vfs/index.js';
5
- import { InMemoryStore, StoreFS } from '../../dist/index.js';
6
- import { Async } from '../../dist/mixins/async.js';
7
-
8
- class ExampleAsyncFS extends Async(StoreFS) {
9
- _sync = new StoreFS(new InMemoryStore('cache'));
10
-
11
- public constructor() {
12
- super(new InMemoryStore('test'));
13
- }
14
- }
15
-
16
- const asyncFS = new ExampleAsyncFS();
17
-
18
- await configure({ mounts: { '/': asyncFS } });
19
-
20
- suite('Async Mixin', () => {
21
- test('async -> cache syncing', async () => {
22
- await fs.promises.writeFile('test', 'test');
23
- assert.strictEqual(fs.readFileSync('test', 'utf8'), 'test');
24
- });
25
-
26
- test('cache -> async syncing', async () => {
27
- fs.unlinkSync('test');
28
- await asyncFS.queueDone();
29
- assert(!(await fs.promises.exists('test')));
30
- });
31
- });
@@ -1,45 +0,0 @@
1
- import { execSync } from 'node:child_process';
2
- import { readFileSync } from 'node:fs';
3
- import { createServer } from 'node:http';
4
- import { join } from 'node:path';
5
- import { configureSingle, Fetch, InMemory, Overlay } from '../../dist/index.js';
6
- import { data, tmp } from '../setup.js';
7
-
8
- const port = 26514,
9
- index = tmp + '/index.json';
10
-
11
- const statusCodes = {
12
- ENOENT: 404,
13
- };
14
-
15
- execSync(`npm exec make-index -- ${data} --output ${index} --quiet`, { stdio: 'inherit' });
16
-
17
- const server = createServer((request, response) => {
18
- const path = request.url == '/.index.json' ? index : join(data, request.url?.slice(1) || '');
19
- try {
20
- response.statusCode = 200;
21
- response.end(readFileSync(path));
22
- } catch (e: any) {
23
- response.statusCode = statusCodes[e.code as keyof typeof statusCodes] || 400;
24
- response.end();
25
- }
26
- });
27
-
28
- server
29
- .once('error', (error: NodeJS.ErrnoException) => {
30
- if (error.code == 'EADDRINUSE') return;
31
- throw error;
32
- })
33
- .listen(port)
34
- .unref();
35
-
36
- const baseUrl = 'http://localhost:' + port;
37
-
38
- await configureSingle({
39
- backend: Overlay,
40
- readable: Fetch.create({
41
- baseUrl,
42
- index: baseUrl + '/.index.json',
43
- }),
44
- writable: InMemory.create({ name: 'cow' }),
45
- });
File without changes