@zenfs/core 1.11.4 → 2.0.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 (116) hide show
  1. package/dist/backends/backend.d.ts +19 -15
  2. package/dist/backends/backend.js +31 -15
  3. package/dist/backends/cow.d.ts +20 -30
  4. package/dist/backends/cow.js +52 -142
  5. package/dist/backends/fetch.d.ts +1 -0
  6. package/dist/backends/fetch.js +3 -1
  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 -22
  12. package/dist/backends/passthrough.js +85 -160
  13. package/dist/backends/port.d.ts +207 -0
  14. package/dist/backends/port.js +297 -0
  15. package/dist/backends/single_buffer.d.ts +11 -5
  16. package/dist/backends/single_buffer.js +18 -12
  17. package/dist/backends/store/fs.d.ts +11 -27
  18. package/dist/backends/store/fs.js +67 -91
  19. package/dist/backends/store/store.d.ts +7 -12
  20. package/dist/config.d.ts +1 -10
  21. package/dist/config.js +7 -8
  22. package/dist/context.d.ts +8 -21
  23. package/dist/context.js +33 -10
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.js +2 -1
  26. package/dist/internal/contexts.d.ts +63 -0
  27. package/dist/internal/contexts.js +15 -0
  28. package/dist/internal/credentials.d.ts +2 -11
  29. package/dist/internal/credentials.js +0 -19
  30. package/dist/internal/devices.d.ts +18 -80
  31. package/dist/internal/devices.js +76 -279
  32. package/dist/internal/file_index.js +3 -3
  33. package/dist/internal/filesystem.d.ts +31 -89
  34. package/dist/internal/filesystem.js +21 -20
  35. package/dist/internal/index.d.ts +0 -1
  36. package/dist/internal/index.js +0 -1
  37. package/dist/internal/index_fs.d.ts +12 -30
  38. package/dist/internal/index_fs.js +23 -55
  39. package/dist/internal/inode.d.ts +147 -9
  40. package/dist/internal/inode.js +333 -25
  41. package/dist/internal/log.d.ts +19 -13
  42. package/dist/internal/log.js +81 -80
  43. package/dist/mixins/async.js +26 -90
  44. package/dist/mixins/mutexed.d.ts +17 -16
  45. package/dist/mixins/mutexed.js +29 -31
  46. package/dist/mixins/readonly.d.ts +7 -6
  47. package/dist/mixins/readonly.js +6 -0
  48. package/dist/mixins/sync.js +8 -8
  49. package/dist/{vfs/path.d.ts → path.d.ts} +3 -4
  50. package/dist/{vfs/path.js → path.js} +6 -9
  51. package/dist/readline.d.ts +134 -0
  52. package/dist/readline.js +623 -0
  53. package/dist/utils.d.ts +4 -35
  54. package/dist/utils.js +8 -73
  55. package/dist/vfs/acl.d.ts +42 -0
  56. package/dist/vfs/acl.js +249 -0
  57. package/dist/vfs/async.d.ts +7 -21
  58. package/dist/vfs/async.js +19 -19
  59. package/dist/vfs/config.d.ts +6 -18
  60. package/dist/vfs/config.js +8 -18
  61. package/dist/vfs/dir.d.ts +3 -3
  62. package/dist/vfs/dir.js +9 -8
  63. package/dist/vfs/file.d.ts +106 -0
  64. package/dist/vfs/file.js +235 -0
  65. package/dist/vfs/flags.d.ts +19 -0
  66. package/dist/vfs/flags.js +62 -0
  67. package/dist/vfs/index.d.ts +4 -10
  68. package/dist/vfs/index.js +4 -13
  69. package/dist/vfs/ioctl.d.ts +87 -0
  70. package/dist/vfs/ioctl.js +304 -0
  71. package/dist/vfs/promises.d.ts +78 -16
  72. package/dist/vfs/promises.js +273 -122
  73. package/dist/vfs/shared.d.ts +7 -26
  74. package/dist/vfs/shared.js +25 -53
  75. package/dist/{stats.d.ts → vfs/stats.d.ts} +14 -28
  76. package/dist/{stats.js → vfs/stats.js} +11 -66
  77. package/dist/vfs/streams.d.ts +1 -0
  78. package/dist/vfs/streams.js +24 -19
  79. package/dist/vfs/sync.d.ts +4 -3
  80. package/dist/vfs/sync.js +143 -128
  81. package/dist/vfs/watchers.d.ts +2 -2
  82. package/dist/vfs/watchers.js +6 -6
  83. package/dist/vfs/xattr.d.ts +116 -0
  84. package/dist/vfs/xattr.js +218 -0
  85. package/package.json +3 -3
  86. package/readme.md +1 -1
  87. package/tests/backend/config.worker.js +4 -1
  88. package/tests/backend/fetch.test.ts +3 -0
  89. package/tests/backend/port.test.ts +21 -35
  90. package/tests/backend/remote.worker.js +4 -1
  91. package/tests/backend/single-buffer.test.ts +24 -0
  92. package/tests/common/context.test.ts +1 -1
  93. package/tests/common/handle.test.ts +17 -12
  94. package/tests/common/path.test.ts +1 -1
  95. package/tests/common/readline.test.ts +104 -0
  96. package/tests/common.ts +4 -19
  97. package/tests/fetch/fetch.ts +1 -1
  98. package/tests/fs/links.test.ts +1 -1
  99. package/tests/fs/permissions.test.ts +7 -6
  100. package/tests/fs/readFile.test.ts +3 -3
  101. package/tests/fs/stat.test.ts +6 -6
  102. package/tests/fs/streams.test.ts +2 -11
  103. package/tests/fs/times.test.ts +1 -1
  104. package/tests/fs/xattr.test.ts +85 -0
  105. package/tests/logs.js +22 -0
  106. package/tests/setup/context.ts +1 -1
  107. package/tests/setup/index.ts +3 -3
  108. package/tests/setup/port.ts +1 -1
  109. package/dist/backends/port/fs.d.ts +0 -84
  110. package/dist/backends/port/fs.js +0 -151
  111. package/dist/backends/port/rpc.d.ts +0 -77
  112. package/dist/backends/port/rpc.js +0 -100
  113. package/dist/backends/store/simple.d.ts +0 -20
  114. package/dist/backends/store/simple.js +0 -13
  115. package/dist/internal/file.d.ts +0 -359
  116. package/dist/internal/file.js +0 -751
@@ -0,0 +1,116 @@
1
+ import type { BufferEncodingOption, ObjectEncodingOptions } from 'node:fs';
2
+ import type { V_Context } from '../context.js';
3
+ /**
4
+ * Extended attribute name with namespace prefix.
5
+ * Format is namespace.attributename where namespace is one of:
6
+ * - user: User attributes
7
+ * - trusted: Trusted attributes (privileged)
8
+ * - system: System attributes
9
+ * - security: Security attributes
10
+ *
11
+ * Note: Currently only the 'user' namespace is supported.
12
+ */
13
+ export type Name = `${'user' | 'trusted' | 'system' | 'security'}.${string}`;
14
+ /**
15
+ * Options for xattr operations.
16
+ */
17
+ export interface Options {
18
+ /**
19
+ * If true, don't follow symlinks.
20
+ * @default false
21
+ */
22
+ noFollow?: boolean;
23
+ /**
24
+ * Encoding for attribute values.
25
+ * If 'buffer' or undefined, the value is returned as a Buffer.
26
+ * Otherwise, the value is returned as a string using the specified encoding.
27
+ * @default undefined
28
+ */
29
+ encoding?: BufferEncoding | 'buffer';
30
+ }
31
+ /**
32
+ * Options for setting extended attributes.
33
+ * Extends the base Options with additional flags for create/replace behavior.
34
+ */
35
+ export interface SetOptions extends Options {
36
+ /**
37
+ * If true, fail if the attribute already exists.
38
+ * @default false
39
+ */
40
+ create?: boolean;
41
+ /**
42
+ * If true, fail if the attribute does not exist.
43
+ * @default false
44
+ */
45
+ replace?: boolean;
46
+ }
47
+ /**
48
+ * Gets the value of an extended attribute.
49
+ *
50
+ * @param path Path to the file
51
+ * @param name Name of the attribute to get
52
+ * @param opt Options for the operation
53
+ * @returns A buffer containing the attribute value when encoding is 'buffer' or undefined, or a string when a string encoding is specified
54
+ */
55
+ export declare function get(this: V_Context, path: string, name: Name, opt?: Options & (BufferEncodingOption | {
56
+ encoding?: null;
57
+ })): Promise<Uint8Array>;
58
+ export declare function get(this: V_Context, path: string, name: Name, opt: Options & ObjectEncodingOptions): Promise<string>;
59
+ /**
60
+ * Synchronously gets the value of an extended attribute.
61
+ *
62
+ * @param path Path to the file
63
+ * @param name Name of the attribute to get
64
+ * @param opt Options for the operation
65
+ * @returns A buffer containing the attribute value when encoding is 'buffer' or undefined, or a string when a string encoding is specified
66
+ */
67
+ export declare function getSync(this: V_Context, path: string, name: Name, opt?: Options & (BufferEncodingOption | {
68
+ encoding?: null;
69
+ })): Uint8Array;
70
+ export declare function getSync(this: V_Context, path: string, name: Name, opt: Options & ObjectEncodingOptions): string;
71
+ /**
72
+ * Sets the value of an extended attribute.
73
+ *
74
+ * @param path Path to the file
75
+ * @param name Name of the attribute to set
76
+ * @param value Value to set
77
+ * @param opt Options for the operation
78
+ */
79
+ export declare function set(this: V_Context, path: string, name: Name, value: string | Uint8Array, opt?: SetOptions): Promise<void>;
80
+ /**
81
+ * Synchronously sets the value of an extended attribute.
82
+ *
83
+ * @param path Path to the file
84
+ * @param name Name of the attribute to set
85
+ * @param value Value to set
86
+ * @param opt Options for the operation
87
+ */
88
+ export declare function setSync(this: V_Context, path: string, name: Name, value: string | Uint8Array, opt?: SetOptions): void;
89
+ /**
90
+ * Removes an extended attribute from a file.
91
+ *
92
+ * @param path Path to the file
93
+ * @param name Name of the attribute to remove
94
+ */
95
+ export declare function remove(this: V_Context, path: string, name: Name): Promise<void>;
96
+ /**
97
+ * Synchronously removes an extended attribute from a file.
98
+ *
99
+ * @param path Path to the file
100
+ * @param name Name of the attribute to remove
101
+ */
102
+ export declare function removeSync(this: V_Context, path: string, name: Name): void;
103
+ /**
104
+ * Lists all extended attributes of a file.
105
+ *
106
+ * @param path Path to the file
107
+ * @returns Array of attribute names
108
+ */
109
+ export declare function list(this: V_Context, path: string): Promise<Name[]>;
110
+ /**
111
+ * Synchronously lists all extended attributes of a file.
112
+ *
113
+ * @param path Path to the file
114
+ * @returns Array of attribute names
115
+ */
116
+ export declare function listSync(this: V_Context, path: string): Name[];
@@ -0,0 +1,218 @@
1
+ import { Buffer } from 'buffer';
2
+ import { pick } from 'utilium';
3
+ import { Errno, ErrnoError } from '../internal/error.js';
4
+ import { Attributes, hasAccess } from '../internal/inode.js';
5
+ import { normalizePath } from '../utils.js';
6
+ import { checkAccess } from './config.js';
7
+ import { R_OK, W_OK } from './constants.js';
8
+ import { fixError, resolveMount } from './shared.js';
9
+ const _allowedRestrictedNames = [];
10
+ /**
11
+ * Check permission for the attribute name.
12
+ * For now, only attributes in the 'user' namespace are supported.
13
+ * @throws EPERM for attributes in namespaces other than 'user'
14
+ */
15
+ function checkName($, name, path, syscall) {
16
+ if (!name.startsWith('user.') && !_allowedRestrictedNames.includes(name))
17
+ throw new ErrnoError(Errno.EPERM, 'Only attributes in the user namespace are supported', path, syscall);
18
+ }
19
+ export async function get(path, name, opt = {}) {
20
+ var _a;
21
+ path = normalizePath(path);
22
+ const { fs, path: resolved } = resolveMount(path, this);
23
+ 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
+ }
39
+ }
40
+ export function getSync(path, name, opt = {}) {
41
+ var _a;
42
+ path = normalizePath(path);
43
+ checkName(this, name, path, 'xattr.get');
44
+ const { fs, path: resolved } = resolveMount(path, this);
45
+ 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);
56
+ }
57
+ catch (e) {
58
+ throw fixError(e, { [resolved]: path });
59
+ }
60
+ }
61
+ /**
62
+ * Sets the value of an extended attribute.
63
+ *
64
+ * @param path Path to the file
65
+ * @param name Name of the attribute to set
66
+ * @param value Value to set
67
+ * @param opt Options for the operation
68
+ */
69
+ export async function set(path, name, value, opt = {}) {
70
+ var _a;
71
+ path = normalizePath(path);
72
+ const { fs, path: resolved } = resolveMount(path, this);
73
+ 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
+ }
93
+ }
94
+ /**
95
+ * Synchronously sets the value of an extended attribute.
96
+ *
97
+ * @param path Path to the file
98
+ * @param name Name of the attribute to set
99
+ * @param value Value to set
100
+ * @param opt Options for the operation
101
+ */
102
+ export function setSync(path, name, value, opt = {}) {
103
+ var _a;
104
+ path = normalizePath(path);
105
+ const { fs, path: resolved } = resolveMount(path, this);
106
+ checkName(this, name, path, 'xattr.set');
107
+ 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'));
122
+ }
123
+ catch (e) {
124
+ throw fixError(e, { [resolved]: path });
125
+ }
126
+ }
127
+ /**
128
+ * Removes an extended attribute from a file.
129
+ *
130
+ * @param path Path to the file
131
+ * @param name Name of the attribute to remove
132
+ */
133
+ export async function remove(path, name) {
134
+ var _a;
135
+ path = normalizePath(path);
136
+ const { fs, path: resolved } = resolveMount(path, this);
137
+ 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
+ }
153
+ }
154
+ /**
155
+ * Synchronously removes an extended attribute from a file.
156
+ *
157
+ * @param path Path to the file
158
+ * @param name Name of the attribute to remove
159
+ */
160
+ export function removeSync(path, name) {
161
+ var _a;
162
+ path = normalizePath(path);
163
+ const { fs, path: resolved } = resolveMount(path, this);
164
+ checkName(this, name, path, 'xattr.remove');
165
+ 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'));
176
+ }
177
+ catch (e) {
178
+ throw fixError(e, { [resolved]: path });
179
+ }
180
+ }
181
+ /**
182
+ * Lists all extended attributes of a file.
183
+ *
184
+ * @param path Path to the file
185
+ * @returns Array of attribute names
186
+ */
187
+ export async function list(path) {
188
+ path = normalizePath(path);
189
+ 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
+ }
199
+ }
200
+ /**
201
+ * Synchronously lists all extended attributes of a file.
202
+ *
203
+ * @param path Path to the file
204
+ * @returns Array of attribute names
205
+ */
206
+ export function listSync(path) {
207
+ path = normalizePath(path);
208
+ const { fs, path: resolved } = resolveMount(path, this);
209
+ try {
210
+ const inode = fs.statSync(resolved);
211
+ if (!inode.attributes)
212
+ return [];
213
+ return inode.attributes.keys();
214
+ }
215
+ catch (e) {
216
+ throw fixError(e, { [resolved]: path });
217
+ }
218
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.11.4",
3
+ "version": "2.0.0",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -67,11 +67,11 @@
67
67
  "prepublishOnly": "npm run build"
68
68
  },
69
69
  "dependencies": {
70
- "@types/node": "^22.10.1",
70
+ "@types/node": "^22.10.1 <22.13.7",
71
71
  "buffer": "^6.0.3",
72
72
  "eventemitter3": "^5.0.1",
73
73
  "readable-stream": "^4.5.2",
74
- "utilium": "^1.3.3"
74
+ "utilium": "^1.8.0"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@eslint/js": "^9.8.0",
package/readme.md CHANGED
@@ -7,7 +7,7 @@ ZenFS is a cross-platform library that emulates the [NodeJS filesystem API](http
7
7
  ZenFS is modular and extensible. The core includes some built-in backends:
8
8
 
9
9
  - `InMemory`: Stores files in-memory. This is cleared when the runtime ends (e.g. a user navigating away from a web page or a Node process exiting)
10
- - `Overlay`: Use a read-only file system as read-write by overlaying a writable file system on top of it. ([copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write))
10
+ - `CopyOnWrite`: Use readable and writable file systems with ([copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write)).
11
11
  - `Fetch`: Downloads files over HTTP with the `fetch` API
12
12
  - `Port`: Interacts with a remote over a `MessagePort`-like interface (e.g. a worker)
13
13
  - `Passthrough`: Use an existing `node:fs` interface with ZenFS
@@ -1,5 +1,8 @@
1
1
  import { parentPort } from 'node:worker_threads';
2
- import { resolveRemoteMount } from '../../dist/backends/port/fs.js';
2
+ import { resolveRemoteMount } from '../../dist/backends/port.js';
3
3
  import { InMemory } from '../../dist/backends/memory.js';
4
+ import { setupLogs } from '../logs.js';
5
+
6
+ setupLogs('<config>');
4
7
 
5
8
  await resolveRemoteMount(parentPort, { backend: InMemory });
@@ -4,6 +4,9 @@ import { suite, test } from 'node:test';
4
4
  import { Worker } from 'node:worker_threads';
5
5
  import { Fetch, configureSingle, fs, mounts, type FetchFS } from '../../dist/index.js';
6
6
  import { baseUrl, defaultEntries, indexPath, whenServerReady } from '../fetch/config.js';
7
+ import { setupLogs } from '../logs.js';
8
+
9
+ setupLogs();
7
10
 
8
11
  const server = new Worker(join(import.meta.dirname, '../fetch/server.js'));
9
12
 
@@ -1,10 +1,11 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
3
  import { MessageChannel, Worker } from 'node:worker_threads';
4
- import { Port, attachFS } from '../../dist/backends/port/fs.js';
5
- import { waitOnline } from '../../dist/backends/port/rpc.js';
4
+ import { Port, attachFS, waitOnline } from '../../dist/backends/port.js';
6
5
  import type { InMemoryStore, StoreFS } from '../../dist/index.js';
7
- import { ErrnoError, InMemory, configure, configureSingle, fs, resolveMountConfig } from '../../dist/index.js';
6
+ import { InMemory, configure, configureSingle, fs, resolveMountConfig } from '../../dist/index.js';
7
+ import { setupLogs } from '../logs.js';
8
+ setupLogs();
8
9
 
9
10
  // Tests a mis-configured `Port` using a MessageChannel
10
11
 
@@ -12,36 +13,21 @@ const timeoutChannel = new MessageChannel();
12
13
  timeoutChannel.port2.unref();
13
14
 
14
15
  await suite('Timeout', { timeout: 1000 }, () => {
15
- test('Misconfiguration', async () => {
16
- let error: ErrnoError;
17
- try {
18
- await configure({
19
- mounts: {
20
- '/tmp-timeout': { backend: InMemory, name: 'tmp' },
21
- '/port': { backend: Port, port: timeoutChannel.port1, timeout: 100 },
22
- },
23
- });
24
- } catch (e) {
25
- assert(e instanceof ErrnoError);
26
- error = e;
27
- }
28
- assert(error! instanceof ErrnoError);
29
- assert.equal(error.code, 'EIO');
30
- assert(error.message.includes('RPC Failed'));
16
+ test('Misconfiguration', () => {
17
+ const configured = configure({
18
+ mounts: {
19
+ '/tmp-timeout': { backend: InMemory, label: 'tmp' },
20
+ '/port': { backend: Port, port: timeoutChannel.port1, timeout: 100 },
21
+ },
22
+ });
23
+
24
+ assert.rejects(configured, { code: 'EIO', message: /RPC Failed/ });
31
25
  });
32
26
 
33
- test('Remote not attached', async () => {
34
- let error: ErrnoError;
35
- try {
36
- await configureSingle({ backend: Port, port: timeoutChannel.port1, timeout: 100 });
37
- await fs.promises.writeFile('/test', 'anything');
38
- } catch (e) {
39
- assert(e instanceof ErrnoError);
40
- error = e;
41
- }
42
- assert(error! instanceof ErrnoError);
43
- assert.equal(error.code, 'EIO');
44
- assert(error.message.includes('RPC Failed'));
27
+ test('Remote not attached', () => {
28
+ const configured = configureSingle({ backend: Port, port: timeoutChannel.port1, timeout: 100 });
29
+
30
+ assert.rejects(configured, { code: 'EIO', message: /RPC Failed/ });
45
31
  });
46
32
  });
47
33
 
@@ -56,7 +42,7 @@ await suite('Remote FS with resolveRemoteMount', () => {
56
42
  const content = 'FS is in a port';
57
43
 
58
44
  test('Configuration', async () => {
59
- await configureSingle({ backend: Port, port: configPort, timeout: 100 });
45
+ await configureSingle({ backend: Port, port: configPort, timeout: 500 });
60
46
  });
61
47
 
62
48
  test('Write', async () => {
@@ -79,9 +65,9 @@ let tmpfs: StoreFS<InMemoryStore>;
79
65
 
80
66
  await suite('FS with MessageChannel', () => {
81
67
  test('configuration', async () => {
82
- tmpfs = await resolveMountConfig({ backend: InMemory, name: 'tmp' });
68
+ tmpfs = await resolveMountConfig({ backend: InMemory, label: 'tmp' });
83
69
  attachFS(channel.port2, tmpfs);
84
- await configureSingle({ backend: Port, port: channel.port1, disableAsyncCache: true, timeout: 100 });
70
+ await configureSingle({ backend: Port, port: channel.port1, disableAsyncCache: true, timeout: 500 });
85
71
  });
86
72
 
87
73
  test('write', async () => {
@@ -116,7 +102,7 @@ await suite('Remote FS', () => {
116
102
  const content = 'FS is in a port';
117
103
 
118
104
  test('Configuration', async () => {
119
- await configureSingle({ backend: Port, port: remotePort, timeout: 100 });
105
+ await configureSingle({ backend: Port, port: remotePort, timeout: 500 });
120
106
  });
121
107
 
122
108
  test('Write', async () => {
@@ -1,5 +1,8 @@
1
1
  import { parentPort } from 'node:worker_threads';
2
- import { attachFS } from '../../dist/backends/port/fs.js';
2
+ import { attachFS } from '../../dist/backends/port.js';
3
3
  import { mounts } from '../../dist/index.js';
4
+ import { setupLogs } from '../logs.js';
5
+
6
+ setupLogs('<remote>');
4
7
 
5
8
  attachFS(parentPort, mounts.get('/'));
@@ -0,0 +1,24 @@
1
+ import { test, suite } from 'node:test';
2
+ import { fs, mount, resolveMountConfig, SingleBuffer, umount } from '../../dist/index.js';
3
+ import assert from 'node:assert';
4
+
5
+ await suite('SingleBuffer', () => {
6
+ test('should be able to restore filesystem (with same metadata) from original buffer', async () => {
7
+ const buffer = new ArrayBuffer(0x100000);
8
+
9
+ umount('/');
10
+ const writable = await resolveMountConfig({ backend: SingleBuffer, buffer });
11
+ mount('/', writable);
12
+
13
+ fs.writeFileSync('/example.ts', 'console.log("hello world")', 'utf-8');
14
+ const stats = fs.statSync('/example.ts');
15
+
16
+ umount('/');
17
+ const snapshot = await resolveMountConfig({ backend: SingleBuffer, buffer });
18
+ mount('/', snapshot);
19
+
20
+ const snapshotStats = fs.statSync('/example.ts');
21
+
22
+ assert.deepEqual(snapshotStats, stats);
23
+ });
24
+ });
@@ -5,7 +5,7 @@ import * as fs from '../../dist/vfs/index.js';
5
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,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', () => {