@zenfs/core 2.7.5 → 2.7.7

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 (72) hide show
  1. package/dist/backends/backend.js +0 -2
  2. package/dist/backends/cow.d.ts +2 -2
  3. package/dist/backends/cow.js +6 -14
  4. package/dist/backends/passthrough.d.ts +0 -54
  5. package/dist/backends/passthrough.js +0 -54
  6. package/dist/backends/port.d.ts +2 -6
  7. package/dist/backends/port.js +3 -11
  8. package/dist/backends/store/fs.d.ts +0 -9
  9. package/dist/backends/store/fs.js +2 -24
  10. package/dist/backends/store/store.d.ts +1 -1
  11. package/dist/backends/store/store.js +2 -8
  12. package/dist/config.d.ts +7 -0
  13. package/dist/config.js +9 -3
  14. package/dist/internal/contexts.d.ts +1 -2
  15. package/dist/internal/credentials.d.ts +0 -3
  16. package/dist/internal/error.d.ts +5 -0
  17. package/dist/internal/error.js +10 -0
  18. package/dist/internal/file_index.js +0 -1
  19. package/dist/internal/index_fs.d.ts +2 -2
  20. package/dist/internal/index_fs.js +2 -2
  21. package/dist/internal/inode.d.ts +46 -43
  22. package/dist/internal/inode.js +75 -10
  23. package/dist/internal/rpc.d.ts +16 -7
  24. package/dist/internal/rpc.js +2 -11
  25. package/dist/mixins/async.js +22 -24
  26. package/dist/mixins/mutexed.d.ts +6 -41
  27. package/dist/mixins/mutexed.js +36 -109
  28. package/dist/mixins/sync.js +0 -2
  29. package/dist/node/async.js +2 -12
  30. package/dist/node/index.d.ts +1 -1
  31. package/dist/node/promises.d.ts +1 -6
  32. package/dist/node/promises.js +18 -22
  33. package/dist/node/stats.d.ts +25 -99
  34. package/dist/node/stats.js +22 -56
  35. package/dist/node/streams.d.ts +1 -37
  36. package/dist/node/sync.d.ts +1 -6
  37. package/dist/node/sync.js +16 -20
  38. package/dist/polyfills.js +25 -0
  39. package/dist/utils.d.ts +0 -4
  40. package/dist/vfs/acl.d.ts +1 -3
  41. package/dist/vfs/acl.js +1 -3
  42. package/dist/vfs/async.js +77 -69
  43. package/dist/vfs/config.d.ts +10 -1
  44. package/dist/vfs/config.js +5 -2
  45. package/dist/vfs/dir.d.ts +3 -0
  46. package/dist/vfs/dir.js +4 -1
  47. package/dist/vfs/file.d.ts +1 -8
  48. package/dist/vfs/file.js +11 -7
  49. package/dist/vfs/ioctl.js +2 -2
  50. package/dist/vfs/shared.d.ts +2 -0
  51. package/dist/vfs/shared.js +3 -4
  52. package/dist/vfs/sync.js +77 -61
  53. package/dist/vfs/vcache.d.ts +14 -14
  54. package/dist/vfs/vcache.js +65 -58
  55. package/dist/vfs/watchers.d.ts +1 -0
  56. package/dist/vfs/watchers.js +2 -4
  57. package/dist/vfs/xattr.js +18 -17
  58. package/package.json +2 -4
  59. package/scripts/make-index.js +1 -1
  60. package/tests/common/async.test.ts +35 -0
  61. package/tests/common/config.test.ts +1 -1
  62. package/tests/common/context.test.ts +1 -1
  63. package/tests/common/file_index.test.ts +1 -1
  64. package/tests/common/mounts.test.ts +12 -0
  65. package/tests/common/vcache.test.ts +13 -0
  66. package/tests/fs/concurrency.test.ts +3 -3
  67. package/tests/fs/links.test.ts +76 -0
  68. package/tests/fs/permissions.test.ts +232 -0
  69. package/tests/fs/stat.test.ts +1 -1
  70. package/tests/fs/streams.test.ts +1 -1
  71. package/tests/fs/xattr.test.ts +10 -0
  72. package/tests/common/mutex.test.ts +0 -61
@@ -55,9 +55,7 @@ export function checkOptions(backend, options) {
55
55
  : typeof value === type || value?.constructor?.name === type;
56
56
  if (Array.isArray(opt.type) ? opt.type.some(v => isType(v)) : isType(opt.type))
57
57
  continue;
58
- // The type of the value as a string
59
58
  const type = typeof value == 'object' && 'constructor' in value ? value.constructor.name : typeof value;
60
- // The expected type (as a string)
61
59
  const name = (type) => (typeof type == 'function' ? (type.name != 'type' ? type.name : type.toString()) : type);
62
60
  const expected = Array.isArray(opt.type) ? `one of ${opt.type.map(name).join(', ')}` : name(opt.type);
63
61
  throw err(withErrno('EINVAL', `Incorrect type for "${optName}": ${type} (expected ${expected})`));
@@ -162,14 +162,14 @@ declare const _CopyOnWrite: {
162
162
  };
163
163
  type _CopyOnWrite = typeof _CopyOnWrite;
164
164
  /**
165
- * Overlay makes a read-only filesystem writable by storing writes on a second, writable file system.
165
+ * Makes a read-only filesystem writable by storing writes on a second, writable file system.
166
166
  * Deletes are persisted via metadata stored on the writable file system.
167
167
  * @category Backends and Configuration
168
168
  */
169
169
  export interface CopyOnWrite extends _CopyOnWrite {
170
170
  }
171
171
  /**
172
- * Overlay makes a read-only filesystem writable by storing writes on a second, writable file system.
172
+ * Makes a read-only filesystem writable by storing writes on a second, writable file system.
173
173
  * Deletes are persisted via metadata stored on the writable file system.
174
174
  * @category Backends and Configuration
175
175
  * @internal
@@ -141,7 +141,7 @@ export class CopyOnWriteFS extends FileSystem {
141
141
  }
142
142
  /* Note the layer's inode is not mutated, since it may be a persistent object.
143
143
  `Inode` fields are accessors, so a copy must be explicit rather than a spread. */
144
- return { ...pick(inode, _inode_fields), attributes: inode.attributes, ino };
144
+ return Object.assign(pick(inode, _inode_fields), { attributes: inode.attributes, ino });
145
145
  }
146
146
  /**
147
147
  * @todo Consider trying to track information on the writable as well
@@ -252,25 +252,19 @@ export class CopyOnWriteFS extends FileSystem {
252
252
  async unlink(path) {
253
253
  if (!(await this.exists(path)))
254
254
  throw withErrno('ENOENT');
255
- if (await this.writable.exists(path)) {
255
+ if (await this.writable.exists(path))
256
256
  await this.writable.unlink(path);
257
- }
258
- // if it still exists add to the delete log
259
- if (await this.exists(path)) {
257
+ if (await this.exists(path))
260
258
  this.journal.add('delete', path);
261
- }
262
259
  this.inos.delete(path);
263
260
  }
264
261
  unlinkSync(path) {
265
262
  if (!this.existsSync(path))
266
263
  throw withErrno('ENOENT');
267
- if (this.writable.existsSync(path)) {
264
+ if (this.writable.existsSync(path))
268
265
  this.writable.unlinkSync(path);
269
- }
270
- // if it still exists add to the delete log
271
- if (this.existsSync(path)) {
266
+ if (this.existsSync(path))
272
267
  this.journal.add('delete', path);
273
- }
274
268
  this.inos.delete(path);
275
269
  }
276
270
  async rmdir(path) {
@@ -283,7 +277,6 @@ export class CopyOnWriteFS extends FileSystem {
283
277
  this.inos.delete(path);
284
278
  return;
285
279
  }
286
- // Check if directory is empty.
287
280
  if ((await this.readdir(path)).length)
288
281
  throw withErrno('ENOTEMPTY');
289
282
  this.journal.add('delete', path);
@@ -299,7 +292,6 @@ export class CopyOnWriteFS extends FileSystem {
299
292
  this.inos.delete(path);
300
293
  return;
301
294
  }
302
- // Check if directory is empty.
303
295
  if (this.readdirSync(path).length)
304
296
  throw withErrno('ENOTEMPTY');
305
297
  this.journal.add('delete', path);
@@ -488,7 +480,7 @@ const _CopyOnWrite = {
488
480
  },
489
481
  };
490
482
  /**
491
- * Overlay makes a read-only filesystem writable by storing writes on a second, writable file system.
483
+ * Makes a read-only filesystem writable by storing writes on a second, writable file system.
492
484
  * Deletes are persisted via metadata stored on the writable file system.
493
485
  * @category Backends and Configuration
494
486
  * @internal
@@ -16,21 +16,9 @@ export declare class PassthroughFS extends FileSystem {
16
16
  constructor(nodeFS: NodeFS, prefix: string);
17
17
  usage(): UsageInfo;
18
18
  path(path: string): string;
19
- /**
20
- * Rename a file or directory.
21
- */
22
19
  rename(oldPath: string, newPath: string): Promise<void>;
23
- /**
24
- * Rename a file or directory synchronously.
25
- */
26
20
  renameSync(oldPath: string, newPath: string): void;
27
- /**
28
- * Get file statistics.
29
- */
30
21
  stat(path: string): Promise<InodeLike>;
31
- /**
32
- * Get file statistics synchronously.
33
- */
34
22
  statSync(path: string): InodeLike;
35
23
  /**
36
24
  * @privateRemarks
@@ -42,61 +30,19 @@ export declare class PassthroughFS extends FileSystem {
42
30
  * Timestamps should be updated by the underlying file system.
43
31
  */
44
32
  touchSync(path: string, metadata: InodeLike): void;
45
- /**
46
- * Unlink (delete) a file.
47
- */
48
33
  unlink(path: string): Promise<void>;
49
- /**
50
- * Unlink (delete) a file synchronously.
51
- */
52
34
  unlinkSync(path: string): void;
53
- /**
54
- * Create a directory.
55
- */
56
35
  mkdir(path: string, options: CreationOptions): Promise<InodeLike>;
57
- /**
58
- * Create a directory synchronously.
59
- */
60
36
  mkdirSync(path: string, options: CreationOptions): InodeLike;
61
- /**
62
- * Read the contents of a directory.
63
- */
64
37
  readdir(path: string): Promise<string[]>;
65
- /**
66
- * Read the contents of a directory synchronously.
67
- */
68
38
  readdirSync(path: string): string[];
69
- /**
70
- * Create a file.
71
- */
72
39
  createFile(path: string, options: CreationOptions): Promise<InodeLike>;
73
- /**
74
- * Create a file synchronously.
75
- */
76
40
  createFileSync(path: string, options: CreationOptions): InodeLike;
77
- /**
78
- * Remove a directory.
79
- */
80
41
  rmdir(path: string): Promise<void>;
81
- /**
82
- * Remove a directory synchronously.
83
- */
84
42
  rmdirSync(path: string): void;
85
- /**
86
- * Synchronize data to the file system.
87
- */
88
43
  sync(): Promise<void>;
89
- /**
90
- * Synchronize data to the file system synchronously.
91
- */
92
44
  syncSync(): void;
93
- /**
94
- * Create a hard link.
95
- */
96
45
  link(target: string, link: string): Promise<void>;
97
- /**
98
- * Create a hard link synchronously.
99
- */
100
46
  linkSync(target: string, link: string): void;
101
47
  read(path: string, buffer: Uint8Array, offset: number, end: number): Promise<void>;
102
48
  readSync(path: string, buffer: Uint8Array, offset: number, end: number): void;
@@ -72,27 +72,15 @@ export class PassthroughFS extends FileSystem {
72
72
  path(path) {
73
73
  return this.prefix + path;
74
74
  }
75
- /**
76
- * Rename a file or directory.
77
- */
78
75
  async rename(oldPath, newPath) {
79
76
  await this.nodeFS.promises.rename(this.path(oldPath), this.path(newPath));
80
77
  }
81
- /**
82
- * Rename a file or directory synchronously.
83
- */
84
78
  renameSync(oldPath, newPath) {
85
79
  this.nodeFS.renameSync(this.path(oldPath), this.path(newPath));
86
80
  }
87
- /**
88
- * Get file statistics.
89
- */
90
81
  async stat(path) {
91
82
  return await this.nodeFS.promises.stat(this.path(path));
92
83
  }
93
- /**
94
- * Get file statistics synchronously.
95
- */
96
84
  statSync(path) {
97
85
  return this.nodeFS.statSync(this.path(path));
98
86
  }
@@ -135,47 +123,26 @@ export class PassthroughFS extends FileSystem {
135
123
  err('Failed to chown passthrough file: ' + error.message);
136
124
  }
137
125
  }
138
- /**
139
- * Unlink (delete) a file.
140
- */
141
126
  async unlink(path) {
142
127
  await this.nodeFS.promises.unlink(this.path(path));
143
128
  }
144
- /**
145
- * Unlink (delete) a file synchronously.
146
- */
147
129
  unlinkSync(path) {
148
130
  this.nodeFS.unlinkSync(this.path(path));
149
131
  }
150
- /**
151
- * Create a directory.
152
- */
153
132
  async mkdir(path, options) {
154
133
  await this.nodeFS.promises.mkdir(this.path(path), options);
155
134
  return await this.nodeFS.promises.stat(this.path(path));
156
135
  }
157
- /**
158
- * Create a directory synchronously.
159
- */
160
136
  mkdirSync(path, options) {
161
137
  this.nodeFS.mkdirSync(this.path(path), options);
162
138
  return this.nodeFS.statSync(this.path(path));
163
139
  }
164
- /**
165
- * Read the contents of a directory.
166
- */
167
140
  async readdir(path) {
168
141
  return await this.nodeFS.promises.readdir(this.path(path));
169
142
  }
170
- /**
171
- * Read the contents of a directory synchronously.
172
- */
173
143
  readdirSync(path) {
174
144
  return this.nodeFS.readdirSync(this.path(path));
175
145
  }
176
- /**
177
- * Create a file.
178
- */
179
146
  async createFile(path, options) {
180
147
  if (isDirectory(options)) {
181
148
  await this.nodeFS.promises.mkdir(this.path(path), { mode: options.mode });
@@ -198,9 +165,6 @@ export class PassthroughFS extends FileSystem {
198
165
  }
199
166
  return await this.nodeFS.promises.stat(this.path(path));
200
167
  }
201
- /**
202
- * Create a file synchronously.
203
- */
204
168
  createFileSync(path, options) {
205
169
  if (isDirectory(options)) {
206
170
  this.nodeFS.mkdirSync(this.path(path), { mode: options.mode });
@@ -211,39 +175,21 @@ export class PassthroughFS extends FileSystem {
211
175
  }
212
176
  return this.nodeFS.statSync(this.path(path));
213
177
  }
214
- /**
215
- * Remove a directory.
216
- */
217
178
  async rmdir(path) {
218
179
  await this.nodeFS.promises.rmdir(this.path(path));
219
180
  }
220
- /**
221
- * Remove a directory synchronously.
222
- */
223
181
  rmdirSync(path) {
224
182
  this.nodeFS.rmdirSync(this.path(path));
225
183
  }
226
- /**
227
- * Synchronize data to the file system.
228
- */
229
184
  async sync() {
230
185
  warn('Sync on passthrough is unnecessary');
231
186
  }
232
- /**
233
- * Synchronize data to the file system synchronously.
234
- */
235
187
  syncSync() {
236
188
  warn('Sync on passthrough is unnecessary');
237
189
  }
238
- /**
239
- * Create a hard link.
240
- */
241
190
  async link(target, link) {
242
191
  await this.nodeFS.promises.link(this.path(target), this.path(link));
243
192
  }
244
- /**
245
- * Create a hard link synchronously.
246
- */
247
193
  linkSync(target, link) {
248
194
  this.nodeFS.linkSync(this.path(target), this.path(link));
249
195
  }
@@ -33,13 +33,9 @@ export declare class PortFS<T extends RPC.Channel = RPC.Channel> extends PortFS_
33
33
  readonly channel: T;
34
34
  readonly timeout: number;
35
35
  readonly port: RPC.Port<T>;
36
- /**
37
- * @hidden
38
- */
36
+ /** @hidden */
39
37
  _sync: import("./index.js").StoreFS<import("./memory.js").InMemoryStore>;
40
- /**
41
- * Constructs a new PortFS instance that connects with the FS running on `options.port`.
42
- */
38
+ /** Constructs a new PortFS instance that connects with the FS running on `options.port`. */
43
39
  constructor(channel: T, timeout?: number);
44
40
  protected rpc<const T extends RPC.Method>(method: T, ...args: Parameters<RPC.Methods[T]>): Promise<Awaited<ReturnType<RPC.Methods[T]>>>;
45
41
  ready(): Promise<void>;
@@ -19,13 +19,9 @@ export class PortFS extends Async(FileSystem) {
19
19
  channel;
20
20
  timeout;
21
21
  port;
22
- /**
23
- * @hidden
24
- */
22
+ /** @hidden */
25
23
  _sync = InMemory.create({ label: 'tmpfs:port' });
26
- /**
27
- * Constructs a new PortFS instance that connects with the FS running on `options.port`.
28
- */
24
+ /** Constructs a new PortFS instance that connects with the FS running on `options.port`. */
29
25
  constructor(channel, timeout = 250) {
30
26
  super(0x706f7274, 'portfs');
31
27
  this.channel = channel;
@@ -34,11 +30,7 @@ export class PortFS extends Async(FileSystem) {
34
30
  RPC.attach(this.port, RPC.handleResponse);
35
31
  }
36
32
  rpc(method, ...args) {
37
- return RPC.request({ method, args }, {
38
- port: this.port,
39
- timeout: this.timeout,
40
- fs: this,
41
- });
33
+ return RPC.request({ method, args }, { port: this.port, timeout: this.timeout, fs: this });
42
34
  }
43
35
  async ready() {
44
36
  await this.rpc('ready');
@@ -42,9 +42,6 @@ export declare class StoreFS<T extends Store = Store> extends FileSystem {
42
42
  ready(): Promise<void>;
43
43
  readySync(): void;
44
44
  constructor(store: T);
45
- /**
46
- * @experimental
47
- */
48
45
  usage(): UsageInfo;
49
46
  /**
50
47
  * Load an index into the StoreFS.
@@ -77,13 +74,7 @@ export declare class StoreFS<T extends Store = Store> extends FileSystem {
77
74
  mkdirSync(path: string, options: CreationOptions): Inode;
78
75
  readdir(path: string): Promise<string[]>;
79
76
  readdirSync(path: string): string[];
80
- /**
81
- * Updated the inode and data node at `path`
82
- */
83
77
  sync(): Promise<void>;
84
- /**
85
- * Updated the inode and data node at `path`
86
- */
87
78
  syncSync(): void;
88
79
  link(target: string, link: string): Promise<void>;
89
80
  linkSync(target: string, link: string): void;
@@ -93,9 +93,7 @@ export class StoreFS extends FileSystem {
93
93
  * Add a inode/path pair
94
94
  */
95
95
  _add(ino, path) {
96
- if (!this._paths.has(ino))
97
- this._paths.set(ino, new Set());
98
- this._paths.get(ino).add(path);
96
+ this._paths.getOrInsertComputed(ino, () => new Set()).add(path);
99
97
  this._ids.set(path, ino);
100
98
  }
101
99
  /**
@@ -162,9 +160,6 @@ export class StoreFS extends FileSystem {
162
160
  this.label = store.label;
163
161
  debug(this.name + ': supports features: ' + this.store.flags?.join(', '));
164
162
  }
165
- /**
166
- * @experimental
167
- */
168
163
  usage() {
169
164
  return (this.store.usage?.() || {
170
165
  totalSpace: 0,
@@ -525,13 +520,7 @@ export class StoreFS extends FileSystem {
525
520
  __disposeResources(env_12);
526
521
  }
527
522
  }
528
- /**
529
- * Updated the inode and data node at `path`
530
- */
531
523
  async sync() { }
532
- /**
533
- * Updated the inode and data node at `path`
534
- */
535
524
  syncSync() { }
536
525
  async link(target, link) {
537
526
  const env_13 = { stack: [], error: void 0, hasError: false };
@@ -758,16 +747,13 @@ export class StoreFS extends FileSystem {
758
747
  crit('Store contains an invalid root inode. Refusing to populate tables');
759
748
  return;
760
749
  }
761
- // Keep track of directories we have already traversed to avoid loops
762
750
  const visitedDirectories = new Set();
763
751
  let i = 0;
764
- // Start BFS from root
765
752
  const queue = [['/', rootIno]];
766
753
  while (queue.length > 0) {
767
754
  i++;
768
755
  const [path, ino] = queue.shift();
769
756
  this._add(ino, path);
770
- // Get the inode data from the store
771
757
  const inodeData = await tx.get(ino);
772
758
  if (!inodeData) {
773
759
  warn('Store is missing data for inode: ' + ino);
@@ -777,14 +763,10 @@ export class StoreFS extends FileSystem {
777
763
  warn(`Invalid inode size for ino ${ino}: ${inodeData.length}`);
778
764
  continue;
779
765
  }
780
- // Parse the raw data into our Inode object
781
766
  const inode = new Inode(inodeData);
782
- // If it is a directory and not yet visited, read its directory listing
783
- if (!isDirectory(inode) || visitedDirectories.has(ino)) {
767
+ if (!isDirectory(inode) || visitedDirectories.has(ino))
784
768
  continue;
785
- }
786
769
  visitedDirectories.add(ino);
787
- // Grab the directory listing from the store
788
770
  const dirData = await tx.get(inode.data);
789
771
  if (!dirData) {
790
772
  warn('Store is missing directory data: ' + inode.data);
@@ -963,11 +945,9 @@ export class StoreFS extends FileSystem {
963
945
  const { dir: parentPath, base: fname } = parse(path);
964
946
  const parent = await this.findInode(tx, parentPath);
965
947
  const listing = decodeDirListing((await tx.get(parent.data)) ?? _throw(withErrno('ENOENT')));
966
- // Check if file already exists.
967
948
  if (listing[fname])
968
949
  throw withErrno('EEXIST');
969
950
  const id = this.allocNew(path);
970
- // Commit data.
971
951
  const inode = new Inode({
972
952
  ...options,
973
953
  ino: id,
@@ -977,7 +957,6 @@ export class StoreFS extends FileSystem {
977
957
  });
978
958
  await tx.set(inode.ino, inode);
979
959
  await tx.set(inode.data, data);
980
- // Update and commit parent directory listing.
981
960
  listing[fname] = inode.ino;
982
961
  await tx.set(parent.data, encodeDirListing(listing));
983
962
  await tx.commit();
@@ -1025,7 +1004,6 @@ export class StoreFS extends FileSystem {
1025
1004
  size: data.byteLength,
1026
1005
  nlink: 1,
1027
1006
  });
1028
- // Update and commit parent directory listing.
1029
1007
  tx.setSync(inode.ino, inode);
1030
1008
  tx.setSync(inode.data, data);
1031
1009
  listing[fname] = inode.ino;
@@ -162,7 +162,7 @@ export declare class WrappedTransaction<T extends Store = Store> {
162
162
  data?: Uint8Array;
163
163
  offset: number;
164
164
  }[]>;
165
- /**TransactionEntry
165
+ /**
166
166
  * List of keys modified in this transaction, if any.
167
167
  */
168
168
  protected modifiedKeys: Set<number>;
@@ -109,7 +109,7 @@ export class WrappedTransaction {
109
109
  * Allows us to roll back commits.
110
110
  */
111
111
  originalData = new Map();
112
- /**TransactionEntry
112
+ /**
113
113
  * List of keys modified in this transaction, if any.
114
114
  */
115
115
  modifiedKeys = new Set();
@@ -154,11 +154,9 @@ export class WrappedTransaction {
154
154
  async abort() {
155
155
  if (this.done)
156
156
  return;
157
- // Rollback old values.
158
157
  for (const [id, entries] of this.originalData) {
159
158
  if (!this.modifiedKeys.has(id))
160
159
  continue;
161
- // Key didn't exist.
162
160
  if (entries.some(ent => !ent.data)) {
163
161
  await this.raw.remove(id);
164
162
  this.fs._remove(id);
@@ -173,11 +171,9 @@ export class WrappedTransaction {
173
171
  abortSync() {
174
172
  if (this.done)
175
173
  return;
176
- // Rollback old values.
177
174
  for (const [id, entries] of this.originalData) {
178
175
  if (!this.modifiedKeys.has(id))
179
176
  continue;
180
- // Key didn't exist.
181
177
  if (entries.some(ent => !ent.data)) {
182
178
  this.raw.removeSync(id);
183
179
  this.fs._remove(id);
@@ -206,9 +202,7 @@ export class WrappedTransaction {
206
202
  * on during the transaction.
207
203
  */
208
204
  stash(id, data, offset = 0) {
209
- if (!this.originalData.has(id))
210
- this.originalData.set(id, []);
211
- this.originalData.get(id).push({ data, offset });
205
+ this.originalData.getOrInsertComputed(id, () => []).push({ data, offset });
212
206
  }
213
207
  /**
214
208
  * Marks an id as modified, and stashes its value if it has not been stashed already.
package/dist/config.d.ts CHANGED
@@ -72,6 +72,12 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
72
72
  * @default false
73
73
  */
74
74
  disableAccessChecks: boolean;
75
+ /**
76
+ * If true, path resolution always walks each directory in a path, checking search permission on each.
77
+ * This is more correct but can be much slower.
78
+ * @default false
79
+ */
80
+ resolveFullWalk: boolean;
75
81
  /**
76
82
  * If true, files will only sync to the file system when closed.
77
83
  * This overrides `disableUpdateOnRead`
@@ -79,6 +85,7 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
79
85
  * This can increase performance.
80
86
  * @experimental
81
87
  * @default false
88
+ * @deprecated Replaced by the `Sync` inode flag in v2.0.0
82
89
  */
83
90
  onlySyncOnClose: boolean;
84
91
  /**
package/dist/config.js CHANGED
@@ -6,7 +6,7 @@ import { DeviceFS } from './internal/devices.js';
6
6
  import { FileSystem } from './internal/filesystem.js';
7
7
  import { exists, mkdir, stat } from './node/promises.js';
8
8
  import { existsSync, mkdirSync, statSync } from './node/sync.js';
9
- import { _setAccessChecks } from './vfs/config.js';
9
+ import { _setVFSConfig } from './vfs/config.js';
10
10
  import { mount, umount } from './vfs/shared.js';
11
11
  /**
12
12
  * Update the configuration of a file system.
@@ -209,7 +209,10 @@ export async function configure(configuration) {
209
209
  uid: configuration.uid || 0,
210
210
  gid: configuration.gid || 0,
211
211
  }));
212
- _setAccessChecks(!configuration.disableAccessChecks);
212
+ _setVFSConfig({
213
+ checkAccess: !configuration.disableAccessChecks,
214
+ resolveFullWalk: !!configuration.resolveFullWalk,
215
+ });
213
216
  if (configuration.log)
214
217
  log.configure(configuration.log);
215
218
  if (configuration.mounts) {
@@ -257,7 +260,10 @@ export function configureSync(configuration) {
257
260
  uid: configuration.uid || 0,
258
261
  gid: configuration.gid || 0,
259
262
  }));
260
- _setAccessChecks(!configuration.disableAccessChecks);
263
+ _setVFSConfig({
264
+ checkAccess: !configuration.disableAccessChecks,
265
+ resolveFullWalk: !!configuration.resolveFullWalk,
266
+ });
261
267
  if (configuration.log)
262
268
  log.configure(configuration.log);
263
269
  if (configuration.mounts) {
@@ -20,8 +20,7 @@ export interface FSContext {
20
20
  /** The unique ID of the context */
21
21
  readonly id: number;
22
22
  /**
23
- * The absolute root path of the context
24
- *
23
+ * The absolute root path of the context.
25
24
  * Note the parent's root is not considered
26
25
  */
27
26
  root: string;
@@ -11,9 +11,6 @@ export interface Credentials {
11
11
  sgid: number;
12
12
  euid: number;
13
13
  egid: number;
14
- /**
15
- * List of group IDs.
16
- */
17
14
  groups: number[];
18
15
  }
19
16
  /**
@@ -25,3 +25,8 @@ export declare function wrap<const FS, const Prop extends keyof FS & string>(fs:
25
25
  * Wraps an `fs` so that thrown errors aren't empty
26
26
  */
27
27
  export declare function withExceptionContext<const FS extends FileSystem>(fs: FS, context: ExceptionExtra): FS;
28
+ /**
29
+ * @internal
30
+ * Returns the file system wrapped by {@link withExceptionContext}, or `fs` if it is not wrapped
31
+ */
32
+ export declare function withoutExceptionContext<const FS extends FileSystem>(fs: FS): FS;
@@ -26,6 +26,7 @@ export function wrap(fs, prop, path, dest) {
26
26
  }
27
27
  };
28
28
  }
29
+ const kTarget = Symbol('withExceptionContext:target');
29
30
  /**
30
31
  * @internal
31
32
  * Wraps an `fs` so that thrown errors aren't empty
@@ -33,6 +34,8 @@ export function wrap(fs, prop, path, dest) {
33
34
  export function withExceptionContext(fs, context) {
34
35
  return new Proxy(fs, {
35
36
  get(target, prop) {
37
+ if (prop === kTarget)
38
+ return target;
36
39
  const value = Reflect.get(target, prop);
37
40
  if (typeof value != 'function')
38
41
  return value;
@@ -58,3 +61,10 @@ export function withExceptionContext(fs, context) {
58
61
  },
59
62
  });
60
63
  }
64
+ /**
65
+ * @internal
66
+ * Returns the file system wrapped by {@link withExceptionContext}, or `fs` if it is not wrapped
67
+ */
68
+ export function withoutExceptionContext(fs) {
69
+ return fs[kTarget] ?? fs;
70
+ }
@@ -1,5 +1,4 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
- /* Note: this file is named file_index.ts because Typescript has special behavior regarding index.ts which can't be disabled. */
3
2
  import { withErrno } from 'kerium';
4
3
  import { sizeof } from 'memium';
5
4
  import { isJSON, randomInt } from 'utilium';
@@ -40,8 +40,8 @@ export declare abstract class IndexFS extends FileSystem {
40
40
  protected _mkdirSync?(path: string, options: CreationOptions): void;
41
41
  mkdir(path: string, options: CreationOptions): Promise<Inode>;
42
42
  mkdirSync(path: string, options: CreationOptions): Inode;
43
- link(target: string, link: string): Promise<void>;
44
- linkSync(target: string, link: string): void;
43
+ link(): Promise<void>;
44
+ linkSync(): void;
45
45
  readdir(path: string): Promise<string[]>;
46
46
  readdirSync(path: string): string[];
47
47
  sync(): Promise<void>;
@@ -200,10 +200,10 @@ export class IndexFS extends FileSystem {
200
200
  this._mkdirSync?.(path, options);
201
201
  return inode;
202
202
  }
203
- link(target, link) {
203
+ link() {
204
204
  throw withErrno('ENOSYS');
205
205
  }
206
- linkSync(target, link) {
206
+ linkSync() {
207
207
  throw withErrno('ENOSYS');
208
208
  }
209
209
  async readdir(path) {