@zenfs/core 2.4.1 → 2.4.3

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 (101) hide show
  1. package/COPYING.md +24 -0
  2. package/{readme.md → README.md} +16 -74
  3. package/dist/backends/backend.d.ts +1 -1
  4. package/dist/backends/fetch.js +1 -1
  5. package/dist/backends/memory.js +1 -1
  6. package/dist/backends/passthrough.d.ts +1 -2
  7. package/dist/backends/single_buffer.d.ts +1 -1
  8. package/dist/backends/single_buffer.js +27 -24
  9. package/dist/backends/store/fs.js +4 -4
  10. package/dist/config.js +15 -15
  11. package/dist/context.js +3 -2
  12. package/dist/index.d.ts +9 -3
  13. package/dist/index.js +9 -4
  14. package/dist/internal/contexts.d.ts +5 -4
  15. package/dist/internal/devices.js +1 -1
  16. package/dist/internal/error.d.ts +11 -2
  17. package/dist/internal/error.js +38 -2
  18. package/dist/internal/file_index.js +1 -1
  19. package/dist/internal/index.d.ts +1 -0
  20. package/dist/internal/index.js +2 -1
  21. package/dist/internal/index_fs.js +1 -1
  22. package/dist/internal/inode.d.ts +51 -2
  23. package/dist/internal/inode.js +18 -2
  24. package/dist/mixins/shared.js +1 -1
  25. package/dist/node/async.d.ts +278 -0
  26. package/dist/node/async.js +518 -0
  27. package/dist/node/compat.d.ts +4 -0
  28. package/dist/node/compat.js +6 -0
  29. package/dist/node/dir.d.ts +78 -0
  30. package/dist/node/dir.js +150 -0
  31. package/dist/node/index.d.ts +8 -0
  32. package/dist/node/index.js +8 -0
  33. package/dist/{vfs → node}/promises.d.ts +10 -66
  34. package/dist/{vfs → node}/promises.js +141 -478
  35. package/dist/{vfs → node}/stats.d.ts +0 -4
  36. package/dist/{vfs → node}/stats.js +1 -16
  37. package/dist/{vfs → node}/streams.js +2 -2
  38. package/dist/node/sync.d.ts +252 -0
  39. package/dist/node/sync.js +682 -0
  40. package/dist/node/types.d.ts +21 -0
  41. package/dist/utils.d.ts +1 -7
  42. package/dist/utils.js +0 -6
  43. package/dist/vfs/acl.js +1 -1
  44. package/dist/vfs/async.d.ts +22 -278
  45. package/dist/vfs/async.js +212 -501
  46. package/dist/vfs/dir.d.ts +5 -82
  47. package/dist/vfs/dir.js +5 -233
  48. package/dist/vfs/file.d.ts +52 -13
  49. package/dist/vfs/file.js +167 -25
  50. package/dist/vfs/flags.js +1 -1
  51. package/dist/vfs/index.d.ts +2 -5
  52. package/dist/vfs/index.js +2 -5
  53. package/dist/vfs/shared.d.ts +25 -1
  54. package/dist/vfs/shared.js +6 -4
  55. package/dist/vfs/sync.d.ts +17 -245
  56. package/dist/vfs/sync.js +129 -773
  57. package/dist/vfs/watchers.d.ts +1 -1
  58. package/dist/vfs/watchers.js +2 -2
  59. package/dist/vfs/xattr.js +1 -1
  60. package/eslint.shared.js +1 -0
  61. package/package.json +7 -5
  62. package/scripts/make-index.js +5 -29
  63. package/scripts/test.js +59 -51
  64. package/tests/backend/fetch.test.ts +2 -2
  65. package/tests/backend/port.test.ts +2 -3
  66. package/tests/backend/single-buffer.test.ts +48 -1
  67. package/tests/common/casefold.test.ts +1 -1
  68. package/tests/common/context.test.ts +11 -4
  69. package/tests/common/devices.test.ts +3 -3
  70. package/tests/common/handle.test.ts +4 -3
  71. package/tests/common/inode.test.ts +2 -2
  72. package/tests/common/mounts.test.ts +1 -3
  73. package/tests/common/mutex.test.ts +1 -3
  74. package/tests/common/path.test.ts +2 -2
  75. package/tests/common/readline.test.ts +1 -1
  76. package/tests/common.ts +5 -4
  77. package/tests/fetch/fetch.ts +1 -1
  78. package/tests/fs/dir.test.ts +3 -43
  79. package/tests/fs/directory.test.ts +4 -4
  80. package/tests/fs/errors.test.ts +2 -2
  81. package/tests/fs/links.test.ts +1 -1
  82. package/tests/fs/permissions.test.ts +3 -3
  83. package/tests/fs/read.test.ts +1 -1
  84. package/tests/fs/scaling.test.ts +1 -1
  85. package/tests/fs/stat.test.ts +1 -2
  86. package/tests/fs/times.test.ts +1 -1
  87. package/tests/fs/watch.test.ts +3 -2
  88. package/tests/setup/context.ts +1 -2
  89. package/tests/setup/cow.ts +1 -1
  90. package/tests/setup/index.ts +2 -2
  91. package/tests/setup/port.ts +1 -1
  92. package/tests/setup/single-buffer.ts +1 -1
  93. package/tests/setup.ts +4 -3
  94. package/dist/vfs/types.d.ts +0 -24
  95. package/tests/assignment.ts +0 -21
  96. /package/dist/{vfs/constants.d.ts → constants.d.ts} +0 -0
  97. /package/dist/{vfs/constants.js → constants.js} +0 -0
  98. /package/dist/{readline.d.ts → node/readline.d.ts} +0 -0
  99. /package/dist/{readline.js → node/readline.js} +0 -0
  100. /package/dist/{vfs → node}/streams.d.ts +0 -0
  101. /package/dist/{vfs → node}/types.js +0 -0
package/COPYING.md ADDED
@@ -0,0 +1,24 @@
1
+ _This document is supplemental to the license._
2
+
3
+ Feel free to reach out if you have any concerns regarding licensing. You can create a new discussion [here](https://github.com/orgs/zen-fs/discussions/new?category=q-a).
4
+
5
+ Regarding section 4(b) of version 3 of the LGPL, you are permitted to link to a copy of the LGPL and this document rather than including the full text of both.
6
+
7
+ #### Exception for web applications
8
+
9
+ You are permitted to convey the Combined Work without meeting the requirements of section 4(d) of version 3 of the LGPL if
10
+
11
+ - a) the Combined Work is only accessed by users from other computers over a network (i.e. a web application or website), and
12
+ - b) you also provide the [Corresponding Source](https://www.gnu.org/licenses/gpl-3.0.html#:~:text=“Corresponding%20Source”) of The Library or an offer thereof.
13
+
14
+ This exception does not excuse or waive any other requirement of the LGPL.
15
+
16
+ The practical goal of this exception is to allow ZenFS to be used in modern web apps. For the avoidance of doubt, you are permitted to bundle and minify ZenFS in a web application. Linking to ZenFS' GitHub or npm page is sufficient to satisfy the above section (b) provided you do not modify ZenFS.
17
+
18
+ For example, in a list of libraries you could have:
19
+
20
+ > - [ZenFS](https://github.com/zen-fs/core), Licensed under the [LGPL 3.0 or later](https://www.gnu.org/licenses/lgpl-3.0.html) and [COPYING.md](https://github.com/zen-fs/core/blob/main/COPYING.md), Copyright © James Prevett and other ZenFS contributors
21
+
22
+ **This is a very easy requirement, please respect it.**
23
+
24
+ Before v2.4.0, ZenFS was licensed under the MIT. This still means you need to include the copyright notice and license.
@@ -19,7 +19,12 @@ ZenFS supports a number of other backends.
19
19
  Many are provided as separate packages under `@zenfs`.
20
20
  More backends can be defined by separate libraries by extending the `FileSystem` class and providing a `Backend` object.
21
21
 
22
- You can find all of the packages available over on [NPM](https://www.npmjs.com/org/zenfs).
22
+ You can find all of the packages available over on [NPM](https://www.npmjs.com/org/zenfs). Below is a list of the backends included with some of them:
23
+
24
+ - @zenfs/archives: `Zip`, `Iso`
25
+ - @zenfs/cloud: `Dropbox`, `GoogleDrive`, `S3Bucket`
26
+ - @zenfs/dom: `WebAccess` (Web [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API)/OPFS), `IndexedDB`, `WebStorage` (`localStorage`/`sessionStorage`), `XML` (DOM elements)
27
+ - @zenfs/emscripten: `Emscripten` and a plugin for Emscripten's file system API
23
28
 
24
29
  As an added bonus, all ZenFS backends support synchronous operations.
25
30
  Additionally, all of the backends included with the core are cross-platform.
@@ -34,7 +39,7 @@ npm install @zenfs/core
34
39
 
35
40
  If you're using ZenFS, especially for big projects, please consider supporting the project.
36
41
  Thousands of hours have been dedicated to its development.
37
- Your acknowledgment or financial support would go a long way toward improving ZenFS and its community.
42
+ Your financial support would go a long way toward improving ZenFS and its community.
38
43
 
39
44
  ## Usage
40
45
 
@@ -69,8 +74,8 @@ await configure({
69
74
  '/mnt/zip': { backend: Zip, data: await res.arrayBuffer() },
70
75
  '/tmp': InMemory,
71
76
  '/home': IndexedDB,
72
- }
73
- };
77
+ },
78
+ });
74
79
  ```
75
80
 
76
81
  Note that while you aren't required to use absolute paths for the keys of `mounts`, it is a good practice to do so.
@@ -140,7 +145,7 @@ await configure({
140
145
  },
141
146
  });
142
147
 
143
- fs.mkdirSync('/mnt');
148
+ fs.mkdirSync('/mnt/zip', { recursive: true });
144
149
 
145
150
  const res = await fetch('mydata.zip');
146
151
  const zipfs = await resolveMountConfig({ backend: Zip, data: await res.arrayBuffer() });
@@ -156,82 +161,19 @@ fs.umount('/mnt/zip'); // finished using the zip
156
161
 
157
162
  ### Devices and device files
158
163
 
159
- ZenFS includes support for device files. These are designed to follow Linux's device file behavior, for consistency and ease of use. You can automatically add some normal devices with the `addDevices` configuration option:
160
-
161
- ```ts
162
- await configure({
163
- mounts: {
164
- /* ... */
165
- },
166
- addDevices: true,
167
- });
168
-
169
- fs.writeFileSync('/dev/null', 'Some data to be discarded');
170
-
171
- const randomData = new Uint8Array(100);
172
-
173
- const random = fs.openSync('/dev/random', 'r');
174
- fs.readSync(random, randomData);
175
- fs.closeSync(random);
176
- ```
177
-
178
- You can create your own devices by implementing a `DeviceDriver`. For example, the null device looks similar to this:
179
-
180
- ```ts
181
- const customNullDevice = {
182
- name: 'custom_null',
183
- // only 1 can exist per DeviceFS
184
- singleton: true,
185
- // optional if false
186
- isBuffered: false,
187
- read() {
188
- return 0;
189
- },
190
- write() {
191
- return 0;
192
- },
193
- };
194
- ```
195
-
196
- Note the actual implementation's write is slightly more complicated since it adds to the file position. You can find more information on the docs.
197
-
198
- Finally, if you'd like to use your custom device with the file system:
164
+ ZenFS includes support for device files. These are designed to follow Linux's device file behavior, for consistency and ease of use. Check out the [Devices and Device Drivers](https://zenfs.dev/core/documents/Devices_and_Device_Drivers) documentation for more information.
199
165
 
200
- ```ts
201
- import { addDevice, fs } from '@zenfs/core';
202
-
203
- addDevice(customNullDevice);
204
-
205
- fs.writeFileSync('/dev/custom', 'This gets discarded.');
206
- ```
166
+ ## Bundling
207
167
 
208
- ## Using with bundlers
168
+ ZenFS exports a drop-in for Node's `fs` module, so you can use it for your bundler of preference using the default export.
209
169
 
210
- ZenFS exports a drop-in for Node's `fs` module (up to the version of `@types/node` in package.json), so you can use it for your bundler of preference using the default export.
170
+ > [!IMPORTANT]
171
+ > See [COPYING.md](./COPYING.md)
211
172
 
212
173
  ## Sponsors
213
174
 
214
- A huge thank you to [![Deco.cx logo](https://avatars.githubusercontent.com/deco-cx?size=20) Deco.cx](https://github.com/deco-cx) for sponsoring ZenFS and helping to make this possible.
215
-
216
- ## Building
217
-
218
- - Make sure you have Node and NPM installed. You must have Node v22 or newer.
219
- - Install dependencies with `npm install`
220
- - Build using `npx tsc` or `npm run build`
221
- - You can find the built code in `dist`.
175
+ A huge thank you to [deco.cx](https://github.com/deco-cx) for sponsoring ZenFS and helping to make this possible.
222
176
 
223
177
  ## Contact and Support
224
178
 
225
179
  You can reach out [on Discord](https://zenfs.dev/discord) or by emailing jp@zenfs.dev
226
-
227
- ### Testing
228
-
229
- Run unit tests with:
230
-
231
- - `npm test` to run all tests using the default configuration
232
- - `npx zenfs-test -abc` to run the common tests and run the full FS suite against all included backends
233
- - You can also run this command to test your own backends, the `--auto` (`-a`) flag will automatically detect any setup scripts matching `tests/setup/*` or `tests/setup-*.ts`. If you do, you'll need to include the `c8` dependency for coverage.
234
-
235
- ### BrowserFS Fork
236
-
237
- ZenFS is a fork of [BrowserFS](https://github.com/jvilk/BrowserFS). If you are using ZenFS in a research paper, you may want to [cite BrowserFS](https://github.com/jvilk/BrowserFS#citing).
@@ -9,7 +9,7 @@ type OptionType = 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undef
9
9
  * @category Backends and Configuration
10
10
  */
11
11
  export type OptionsConfig<T> = {
12
- [K in keyof T]: {
12
+ [K in Exclude<keyof T, keyof SharedConfig>]: {
13
13
  /**
14
14
  * The type of the option. Can be a:
15
15
  * - string given by `typeof`
@@ -6,7 +6,7 @@ import * as requests from 'utilium/requests.js';
6
6
  import { Index } from '../internal/file_index.js';
7
7
  import { IndexFS } from '../internal/index_fs.js';
8
8
  import { normalizePath } from '../utils.js';
9
- import { S_IFREG } from '../vfs/constants.js';
9
+ import { S_IFREG } from '../constants.js';
10
10
  /** Parse and throw */
11
11
  function parseError(error) {
12
12
  if (!('tag' in error))
@@ -1,4 +1,4 @@
1
- import { size_max } from '../vfs/constants.js';
1
+ import { size_max } from '../constants.js';
2
2
  import { StoreFS } from './store/fs.js';
3
3
  import { SyncMapTransaction } from './store/map.js';
4
4
  /**
@@ -1,8 +1,7 @@
1
- import type * as fs from 'node:fs';
2
1
  import type { CreationOptions, UsageInfo } from '../internal/filesystem.js';
3
2
  import { FileSystem } from '../internal/filesystem.js';
4
3
  import { type InodeLike } from '../internal/inode.js';
5
- export type NodeFS = typeof fs;
4
+ import type { NodeFS } from '../node/types.js';
6
5
  /**
7
6
  * Passthrough backend options
8
7
  * @category Backends and Configuration
@@ -26,6 +26,7 @@ declare const MetadataBlock_base: import("memium/decorators").StructFromTypedArr
26
26
  */
27
27
  export declare class MetadataBlock extends MetadataBlock_base<ArrayBufferLike> {
28
28
  readonly ['constructor']: typeof MetadataBlock;
29
+ private static readonly lockIndex;
29
30
  /**
30
31
  * The crc32c checksum for the metadata block.
31
32
  * @privateRemarks Keep this first!
@@ -37,7 +38,6 @@ export declare class MetadataBlock extends MetadataBlock_base<ArrayBufferLike> {
37
38
  accessor previous_offset: number;
38
39
  protected _previous?: MetadataBlock;
39
40
  get previous(): MetadataBlock | undefined;
40
- protected get offsetHex(): string;
41
41
  /** Metadata entries. */
42
42
  accessor items: ArrayOf<MetadataEntry>;
43
43
  toString(long?: boolean): string;
@@ -86,8 +86,8 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
86
86
  });
87
87
  // SPDX-License-Identifier: LGPL-3.0-or-later
88
88
  import { withErrno } from 'kerium';
89
- import { alert, crit, err, warn } from 'kerium/log';
90
- import { array, offsetof, packed, sizeof } from 'memium';
89
+ import { alert, crit, debug, err, warn } from 'kerium/log';
90
+ import { array, offsetof, sizeof } from 'memium';
91
91
  import { $from, field, struct, types as t } from 'memium/decorators';
92
92
  import { BufferView } from 'utilium/buffer.js';
93
93
  import { crc32c } from 'utilium/checksum.js';
@@ -95,6 +95,7 @@ import { decodeUUID, encodeUUID } from 'utilium/string.js';
95
95
  import { _inode_version, Inode } from '../internal/inode.js';
96
96
  import { StoreFS } from './store/fs.js';
97
97
  import { SyncMapTransaction } from './store/map.js';
98
+ const hex = (value) => '0x' + value.toString(16).padStart(8, '0');
98
99
  // eslint-disable-next-line @typescript-eslint/unbound-method
99
100
  const { format } = new Intl.NumberFormat('en-US', {
100
101
  notation: 'compact',
@@ -155,7 +156,7 @@ let MetadataEntry = (() => {
155
156
  get size() { return this.#size_accessor_storage; }
156
157
  set size(value) { this.#size_accessor_storage = value; }
157
158
  toString() {
158
- return `<MetadataEntry @ 0x${this.byteOffset.toString(16).padStart(8, '0')}>`;
159
+ return `<MetadataEntry @ ${hex(this.byteOffset)}>`;
159
160
  }
160
161
  constructor() {
161
162
  super(...arguments);
@@ -216,8 +217,8 @@ let MetadataBlock = (() => {
216
217
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
217
218
  MetadataBlock = _classThis = _classDescriptor.value;
218
219
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
219
- __runInitializers(_classThis, _classExtraInitializers);
220
220
  }
221
+ static lockIndex = offsetof(MetadataBlock, 'locked') / Int32Array.BYTES_PER_ELEMENT;
221
222
  #checksum_accessor_storage = __runInitializers(this, _checksum_initializers, void 0);
222
223
  /**
223
224
  * The crc32c checksum for the metadata block.
@@ -240,27 +241,24 @@ let MetadataBlock = (() => {
240
241
  this._previous ??= new MetadataBlock(this.buffer, this.previous_offset);
241
242
  return this._previous;
242
243
  }
243
- get offsetHex() {
244
- return '0x' + this.byteOffset.toString(16).padStart(8, '0');
245
- }
246
244
  #items_accessor_storage = __runInitializers(this, _items_initializers, void 0);
247
245
  /** Metadata entries. */
248
246
  get items() { return this.#items_accessor_storage; }
249
247
  set items(value) { this.#items_accessor_storage = value; }
250
248
  toString(long = false) {
251
249
  if (!long)
252
- return `<MetadataBlock @ ${this.offsetHex}>`;
250
+ return `<MetadataBlock @ ${hex(this.byteOffset)}>`;
253
251
  let text = [
254
- `---- Metadata block at ${this.offsetHex} ----`,
255
- `Checksum: 0x${this.checksum.toString(16).padStart(8, '0')}`,
252
+ `---- Metadata block at ${hex(this.byteOffset)} ----`,
253
+ `Checksum: ${hex(this.checksum)}`,
256
254
  `Last updated: ${new Date(Number(this.timestamp)).toLocaleString()}`,
257
- `Previous block: 0x${this.previous_offset.toString(16).padStart(8, '0')}`,
255
+ `Previous block: ${hex(this.previous_offset)}`,
258
256
  'Entries:',
259
257
  ].join('\n');
260
258
  for (const entry of this.items) {
261
259
  if (!entry.offset)
262
260
  continue;
263
- text += `\n\t0x${entry.id.toString(16).padStart(8, '0')}: ${format(entry.size).padStart(5)} at 0x${entry.offset.toString(16).padStart(8, '0')}`;
261
+ text += `\n\t${hex(entry.id)}: ${format(entry.size).padStart(5)} at ${hex(entry.offset)}`;
264
262
  }
265
263
  return text;
266
264
  }
@@ -276,28 +274,26 @@ let MetadataBlock = (() => {
276
274
  */
277
275
  waitUnlocked(depth = 0) {
278
276
  if (depth > max_lock_attempts)
279
- throw crit(withErrno('EBUSY', `sbfs: exceeded max attempts waiting for metadata block at ${this.offsetHex} to be unlocked`));
280
- const i = this.length - 1;
281
- if (!Atomics.load(this, i))
277
+ throw crit(withErrno('EBUSY', `sbfs: exceeded max attempts waiting for metadata block at ${hex(this.byteOffset)} to be unlocked`));
278
+ if (!Atomics.load(this, MetadataBlock.lockIndex))
282
279
  return;
283
- switch (Atomics.wait(this, i, 1)) {
280
+ switch (Atomics.wait(this, MetadataBlock.lockIndex, 1)) {
284
281
  case 'ok':
285
282
  break;
286
283
  case 'not-equal':
287
284
  depth++;
288
- err(`sbfs: waiting for metadata block at ${this.offsetHex} to be unlocked (${depth}/${max_lock_attempts})`);
285
+ err(`sbfs: waiting for metadata block at ${hex(this.byteOffset)} to be unlocked (${depth}/${max_lock_attempts})`);
289
286
  return this.waitUnlocked(depth);
290
287
  case 'timed-out':
291
- throw crit(withErrno('EBUSY', `sbfs: timed out waiting for metadata block at ${this.offsetHex} to be unlocked`));
288
+ throw crit(withErrno('EBUSY', `sbfs: timed out waiting for metadata block at ${hex(this.byteOffset)} to be unlocked`));
292
289
  }
293
290
  }
294
291
  lock() {
295
292
  this.waitUnlocked();
296
- const i = offsetof(this, 'locked');
297
- Atomics.store(this, i, 1);
293
+ Atomics.store(this, MetadataBlock.lockIndex, 1);
298
294
  const release = () => {
299
- Atomics.store(this, i, 0);
300
- Atomics.notify(this, i, 1);
295
+ Atomics.store(this, MetadataBlock.lockIndex, 0);
296
+ Atomics.notify(this, MetadataBlock.lockIndex, 1);
301
297
  };
302
298
  release[Symbol.dispose] = release;
303
299
  return release;
@@ -306,6 +302,9 @@ let MetadataBlock = (() => {
306
302
  super(...arguments);
307
303
  __runInitializers(this, _locked_extraInitializers);
308
304
  }
305
+ static {
306
+ __runInitializers(_classThis, _classExtraInitializers);
307
+ }
309
308
  };
310
309
  return MetadataBlock = _classThis;
311
310
  })();
@@ -425,7 +424,7 @@ let SuperBlock = (() => {
425
424
  throw crit(withErrno('EIO', 'sbfs: checksum mismatch for super block'));
426
425
  this.metadata = new MetadataBlock(this.buffer, this.metadata_offset);
427
426
  if (this.metadata.checksum !== checksum(this.metadata))
428
- throw crit(withErrno('EIO', `sbfs: checksum mismatch for metadata block (saved ${this.metadata.checksum.toString(16).padStart(8, '0')}, computed ${checksum(this.metadata).toString(16).padStart(8, '0')})`));
427
+ throw crit(withErrno('EIO', `sbfs: checksum mismatch for metadata block (saved ${hex(this.metadata.checksum)}, computed ${hex(checksum(this.metadata))})`));
429
428
  if (this.inode_format != _inode_version)
430
429
  throw crit(withErrno('EIO', 'sbfs: inode format mismatch'));
431
430
  if (this.metadata_block_size != sizeof(MetadataBlock))
@@ -506,6 +505,7 @@ let SuperBlock = (() => {
506
505
  this.metadata_offset = metadata.byteOffset;
507
506
  _update(metadata);
508
507
  _update(this);
508
+ debug(`sbfs: rotated metadata block at ${hex(metadata.previous_offset)} with new block at ${hex(offset)}`);
509
509
  return metadata;
510
510
  }
511
511
  /**
@@ -541,7 +541,10 @@ export { SuperBlock };
541
541
  * Note we don't include the checksum when computing a new one.
542
542
  */
543
543
  function checksum(value) {
544
- return crc32c(new Uint8Array(value.buffer, value.byteOffset + 4, sizeof(value) - 4));
544
+ let length = sizeof(value) - 4;
545
+ if (value instanceof MetadataBlock)
546
+ length -= Int32Array.BYTES_PER_ELEMENT;
547
+ return crc32c(new Uint8Array(value.buffer, value.byteOffset + 4, length));
545
548
  }
546
549
  /**
547
550
  * Update a block's checksum and timestamp.
@@ -58,10 +58,10 @@ import { _throw, canary, encodeUTF8 } from 'utilium';
58
58
  import { extendBuffer } from 'utilium/buffer.js';
59
59
  import { Index } from '../../internal/file_index.js';
60
60
  import { FileSystem } from '../../internal/filesystem.js';
61
- import { Inode, isDirectory, rootIno } from '../../internal/inode.js';
61
+ import { Inode, isDirectory, isFile, rootIno } from '../../internal/inode.js';
62
62
  import { basename, dirname, join, parse, relative } from '../../path.js';
63
63
  import { decodeDirListing, encodeDirListing } from '../../utils.js';
64
- import { S_IFDIR, S_IFREG, size_max } from '../../vfs/constants.js';
64
+ import { S_IFDIR, S_IFREG, size_max } from '../../constants.js';
65
65
  import { WrappedTransaction } from './store.js';
66
66
  /**
67
67
  * A file system which uses a `Store`
@@ -304,7 +304,7 @@ export class StoreFS extends FileSystem {
304
304
  : decodeDirListing((await tx.get(newDirNode.data)) ?? _throw(withErrno('ENODATA')));
305
305
  if (newDirList[_new.base]) {
306
306
  const existing = new Inode((await tx.get(newDirList[_new.base])) ?? _throw(withErrno('ENOENT')));
307
- if (!existing.toStats().isFile())
307
+ if (!isFile(existing))
308
308
  throw withErrno('EISDIR');
309
309
  await tx.remove(existing.data);
310
310
  await tx.remove(newDirList[_new.base]);
@@ -353,7 +353,7 @@ export class StoreFS extends FileSystem {
353
353
  const newDirList = sameParent ? oldDirList : decodeDirListing(tx.getSync(newDirNode.data) ?? _throw(withErrno('ENODATA')));
354
354
  if (newDirList[_new.base]) {
355
355
  const existing = new Inode(tx.getSync(newDirList[_new.base]) ?? _throw(withErrno('ENOENT')));
356
- if (!existing.toStats().isFile())
356
+ if (!isFile(existing))
357
357
  throw withErrno('EISDIR');
358
358
  tx.removeSync(existing.data);
359
359
  tx.removeSync(newDirList[_new.base]);
package/dist/config.js CHANGED
@@ -4,9 +4,9 @@ import { defaultContext } from './internal/contexts.js';
4
4
  import { createCredentials } from './internal/credentials.js';
5
5
  import { DeviceFS } from './internal/devices.js';
6
6
  import { FileSystem } from './internal/filesystem.js';
7
+ import { exists, mkdir, stat } from './node/promises.js';
7
8
  import { _setAccessChecks } from './vfs/config.js';
8
- import * as fs from './vfs/index.js';
9
- import { mounts } from './vfs/shared.js';
9
+ import { mount, mounts, umount } from './vfs/shared.js';
10
10
  /**
11
11
  * Update the configuration of a file system.
12
12
  * @category Backends and Configuration
@@ -69,8 +69,8 @@ export async function configureSingle(configuration) {
69
69
  throw new TypeError('Invalid single mount point configuration');
70
70
  }
71
71
  const resolved = await resolveMountConfig(configuration);
72
- fs.umount('/');
73
- fs.mount('/', resolved);
72
+ umount('/');
73
+ mount('/', resolved);
74
74
  }
75
75
  /**
76
76
  * Like `fs.mount`, but it also creates missing directories.
@@ -78,19 +78,19 @@ export async function configureSingle(configuration) {
78
78
  * This is implemented as a separate function to avoid a circular dependency between vfs/shared.ts and other vfs layer files.
79
79
  * @internal
80
80
  */
81
- async function mount(path, mount) {
81
+ async function mountWithMkdir(path, fs) {
82
82
  if (path == '/') {
83
- fs.mount(path, mount);
83
+ mount(path, fs);
84
84
  return;
85
85
  }
86
- const stats = await fs.promises.stat(path).catch(() => null);
86
+ const stats = await stat(path).catch(() => null);
87
87
  if (!stats) {
88
- await fs.promises.mkdir(path, { recursive: true });
88
+ await mkdir(path, { recursive: true });
89
89
  }
90
90
  else if (!stats.isDirectory()) {
91
91
  throw withErrno('ENOTDIR', 'Missing directory at mount point: ' + path);
92
92
  }
93
- fs.mount(path, mount);
93
+ mount(path, fs);
94
94
  }
95
95
  /**
96
96
  * @category Backends and Configuration
@@ -124,8 +124,8 @@ export async function configure(configuration) {
124
124
  mountConfig.caseFold ??= configuration.caseFold;
125
125
  }
126
126
  if (point == '/')
127
- fs.umount('/');
128
- await mount(point, await resolveMountConfig(mountConfig));
127
+ umount('/');
128
+ await mountWithMkdir(point, await resolveMountConfig(mountConfig));
129
129
  }
130
130
  }
131
131
  for (const fs of mounts.values()) {
@@ -135,17 +135,17 @@ export async function configure(configuration) {
135
135
  const devfs = new DeviceFS();
136
136
  devfs.addDefaults();
137
137
  await devfs.ready();
138
- await mount('/dev', devfs);
138
+ await mountWithMkdir('/dev', devfs);
139
139
  }
140
140
  if (configuration.defaultDirectories) {
141
141
  for (const dir of _defaultDirectories) {
142
- if (await fs.promises.exists(dir)) {
143
- const stats = await fs.promises.stat(dir);
142
+ if (await exists(dir)) {
143
+ const stats = await stat(dir);
144
144
  if (!stats.isDirectory())
145
145
  log.warn('Default directory exists but is not a directory: ' + dir);
146
146
  }
147
147
  else
148
- await fs.promises.mkdir(dir);
148
+ await mkdir(dir);
149
149
  }
150
150
  }
151
151
  }
package/dist/context.js CHANGED
@@ -3,7 +3,8 @@ import { bindFunctions } from 'utilium';
3
3
  import { defaultContext } from './internal/contexts.js';
4
4
  import { createCredentials } from './internal/credentials.js';
5
5
  import * as path from './path.js';
6
- import * as fs from './vfs/index.js';
6
+ import * as fs from './node/index.js';
7
+ import * as xattr from './vfs/xattr.js';
7
8
  // 0 is reserved for the global/default context
8
9
  let _nextId = 1;
9
10
  /**
@@ -33,7 +34,7 @@ export function bindContext({ root = this?.root || '/', pwd = this?.pwd || '/',
33
34
  fs: {
34
35
  ...bindFunctions(fs, ctx),
35
36
  promises: bindFunctions(fs.promises, ctx),
36
- xattr: bindFunctions(fs.xattr, ctx),
37
+ xattr: bindFunctions(xattr, ctx),
37
38
  },
38
39
  path: bindFunctions(path, ctx),
39
40
  bind: (init) => {
package/dist/index.d.ts CHANGED
@@ -1,12 +1,18 @@
1
+ /*!
2
+ * @zenfs/core — https://npmjs.com/package/@zenfs/core
3
+ * Copyright © James Prevett and other ZenFS contributors.
4
+ * SPDX-License-Identifier: LGPL-3.0-or-later
5
+ */
1
6
  export * from './backends/index.js';
2
7
  export * from './config.js';
3
8
  export * from './context.js';
4
9
  export * from './internal/index.js';
5
10
  export * from './mixins/index.js';
6
- export * from './vfs/stats.js';
7
11
  export * from './utils.js';
8
12
  export { mounts } from './vfs/shared.js';
9
- export * from './vfs/index.js';
13
+ import * as fs from './node/compat.js';
14
+ /** @primaryExport */
10
15
  export { fs };
11
- import * as fs from './vfs/index.js';
12
16
  export default fs;
17
+ export * from './node/compat.js';
18
+ export * as vfs from './vfs/index.js';
package/dist/index.js CHANGED
@@ -1,15 +1,20 @@
1
- // SPDX-License-Identifier: LGPL-3.0-or-later
1
+ /*!
2
+ * @zenfs/core — https://npmjs.com/package/@zenfs/core
3
+ * Copyright © James Prevett and other ZenFS contributors.
4
+ * SPDX-License-Identifier: LGPL-3.0-or-later
5
+ */
2
6
  export * from './backends/index.js';
3
7
  export * from './config.js';
4
8
  export * from './context.js';
5
9
  export * from './internal/index.js';
6
10
  export * from './mixins/index.js';
7
- export * from './vfs/stats.js';
8
11
  export * from './utils.js';
9
12
  export { mounts } from './vfs/shared.js';
10
- export * from './vfs/index.js';
13
+ import * as fs from './node/compat.js';
14
+ /** @primaryExport */
11
15
  export { fs };
12
- import * as fs from './vfs/index.js';
13
16
  export default fs;
17
+ export * from './node/compat.js';
18
+ export * as vfs from './vfs/index.js';
14
19
  import $pkg from '../package.json' with { type: 'json' };
15
20
  globalThis.__zenfs__ = Object.assign(Object.create(fs), { _version: $pkg.version });
@@ -1,7 +1,8 @@
1
1
  import type { Bound } from 'utilium';
2
+ import type * as fs from '../node/index.js';
2
3
  import type * as path from '../path.js';
3
- import type { SyncHandle } from '../vfs/file.js';
4
- import type * as fs from '../vfs/index.js';
4
+ import type { Handle } from '../vfs/file.js';
5
+ import type * as xattr from '../vfs/xattr.js';
5
6
  import type { Credentials, CredentialsInit } from './credentials.js';
6
7
  /**
7
8
  * A context used for FS operations
@@ -21,7 +22,7 @@ export interface FSContext {
21
22
  /** The credentials of the context, used for access checks */
22
23
  readonly credentials: Credentials;
23
24
  /** A map of open file descriptors to their handles */
24
- descriptors: Map<number, SyncHandle>;
25
+ descriptors: Map<number, Handle>;
25
26
  /** The parent context, if any. */
26
27
  parent: V_Context;
27
28
  /** The child contexts */
@@ -38,7 +39,7 @@ export type V_Context = void | null | (Partial<FSContext> & object);
38
39
  export interface BoundContext extends FSContext {
39
40
  fs: Bound<typeof fs, FSContext> & {
40
41
  promises: Bound<typeof fs.promises, FSContext>;
41
- xattr: Bound<typeof fs.xattr, FSContext>;
42
+ xattr: Bound<typeof xattr, FSContext>;
42
43
  };
43
44
  /** Path functions, bound to the context */
44
45
  path: Bound<typeof path, FSContext>;
@@ -8,7 +8,7 @@ import { decodeUTF8, omit } from 'utilium';
8
8
  import { InMemoryStore } from '../backends/memory.js';
9
9
  import { StoreFS } from '../backends/store/fs.js';
10
10
  import { basename, dirname } from '../path.js';
11
- import { S_IFCHR } from '../vfs/constants.js';
11
+ import { S_IFCHR } from '../constants.js';
12
12
  import { Inode } from './inode.js';
13
13
  /**
14
14
  * A temporary file system that manages and interfaces with devices
@@ -1,4 +1,5 @@
1
- import { Exception, type ExceptionJSON } from 'kerium';
1
+ import { Exception, type ExceptionExtra, type ExceptionJSON } from 'kerium';
2
+ import type { FileSystem } from './filesystem.js';
2
3
  /**
3
4
  * @deprecated Use {@link ExceptionJSON} instead
4
5
  * @category Internals
@@ -15,4 +16,12 @@ export declare const ErrnoError: typeof Exception;
15
16
  */
16
17
  export type ErrnoError = Exception;
17
18
  export declare function withPath<E extends Exception>(e: E, path: string): E;
18
- export declare function wrap<const FS, const Prop extends keyof FS & string>(fs: FS, prop: Prop, path: string, dest?: string): FS[Prop];
19
+ /**
20
+ * @internal @hidden
21
+ */
22
+ export declare function wrap<const FS, const Prop extends keyof FS & string>(fs: FS, prop: Prop, path: string | ExceptionExtra, dest?: string): FS[Prop];
23
+ /**
24
+ * @internal
25
+ * Wraps an `fs` so that thrown errors aren't empty
26
+ */
27
+ export declare function withExceptionContext<const FS extends FileSystem>(fs: FS, context: ExceptionExtra): FS;