@zenfs/core 0.12.0 → 0.12.2

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 (42) hide show
  1. package/dist/backends/backend.d.ts +15 -5
  2. package/dist/backends/index/fs.d.ts +1 -0
  3. package/dist/backends/index/index.d.ts +5 -0
  4. package/dist/backends/index/index.js +1 -0
  5. package/dist/backends/overlay.js +1 -1
  6. package/dist/backends/port/fs.d.ts +3 -1
  7. package/dist/browser.min.js +4 -4
  8. package/dist/browser.min.js.map +4 -4
  9. package/dist/config.d.ts +33 -11
  10. package/dist/config.js +16 -7
  11. package/dist/emulation/async.d.ts +5 -4
  12. package/dist/emulation/async.js +3 -2
  13. package/dist/emulation/index.d.ts +1 -1
  14. package/dist/emulation/index.js +1 -1
  15. package/dist/emulation/path.d.ts +4 -1
  16. package/dist/emulation/promises.d.ts +4 -4
  17. package/dist/emulation/promises.js +27 -27
  18. package/dist/emulation/shared.d.ts +6 -0
  19. package/dist/emulation/shared.js +19 -1
  20. package/dist/emulation/sync.d.ts +4 -4
  21. package/dist/emulation/sync.js +76 -38
  22. package/dist/file.d.ts +5 -10
  23. package/dist/file.js +38 -55
  24. package/dist/filesystem.d.ts +45 -3
  25. package/dist/filesystem.js +37 -8
  26. package/dist/stats.d.ts +16 -16
  27. package/dist/stats.js +42 -49
  28. package/package.json +2 -2
  29. package/src/backends/backend.ts +15 -6
  30. package/src/backends/index/index.ts +5 -0
  31. package/src/backends/overlay.ts +1 -1
  32. package/src/config.ts +62 -21
  33. package/src/emulation/async.ts +19 -18
  34. package/src/emulation/index.ts +1 -1
  35. package/src/emulation/path.ts +4 -1
  36. package/src/emulation/promises.ts +33 -31
  37. package/src/emulation/shared.ts +22 -1
  38. package/src/emulation/sync.ts +83 -54
  39. package/src/error.ts +1 -1
  40. package/src/file.ts +39 -57
  41. package/src/filesystem.ts +133 -51
  42. package/src/stats.ts +48 -60
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
4
4
  "description": "A filesystem in your browser",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.ts",
@@ -51,7 +51,7 @@
51
51
  "buffer": "^6.0.3",
52
52
  "minimatch": "^9.0.3",
53
53
  "readable-stream": "^4.5.2",
54
- "utilium": "^0.2.1"
54
+ "utilium": "^0.4.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
@@ -8,7 +8,7 @@ type OptionType = 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undef
8
8
  /**
9
9
  * Resolves the type of Backend.options from the options interface
10
10
  */
11
- type OptionsConfig<T> = {
11
+ export type OptionsConfig<T> = {
12
12
  [K in keyof T]: {
13
13
  /**
14
14
  * The basic JavaScript type(s) for this option.
@@ -66,9 +66,17 @@ export interface Backend<FS extends FileSystem = FileSystem, TOptions extends ob
66
66
  isAvailable(): boolean | Promise<boolean>;
67
67
  }
68
68
 
69
- type OptionsOf<T extends Backend> = T extends Backend<FileSystem, infer TOptions> ? TOptions : never;
69
+ /**
70
+ * Gets the options type of a backend
71
+ * @internal
72
+ */
73
+ export type OptionsOf<T extends Backend> = T extends Backend<FileSystem, infer TOptions> ? TOptions : never;
70
74
 
71
- type FilesystemOf<T extends Backend> = T extends Backend<infer FS> ? FS : never;
75
+ /**
76
+ * Gets the FileSystem type for a backend
77
+ * @internal
78
+ */
79
+ export type FilesystemOf<T extends Backend> = T extends Backend<infer FS> ? FS : never;
72
80
 
73
81
  /**
74
82
  * @internal
@@ -81,7 +89,7 @@ export function isBackend(arg: unknown): arg is Backend {
81
89
  * Checks that the given options object is valid for the file system options.
82
90
  * @internal
83
91
  */
84
- export async function checkOptions<T extends Backend>(backend: T, opts: Partial<OptionsOf<T>> & Record<string, unknown>): Promise<void> {
92
+ export async function checkOptions<T extends Backend>(backend: T, opts: Record<string, unknown>): Promise<void> {
85
93
  if (typeof opts != 'object' || opts === null) {
86
94
  throw new ErrnoError(Errno.EINVAL, 'Invalid options');
87
95
  }
@@ -138,13 +146,14 @@ export async function checkOptions<T extends Backend>(backend: T, opts: Partial<
138
146
  *
139
147
  * The option object for each file system corresponds to that file system's option object passed to its `Create()` method.
140
148
  */
141
- export type BackendConfiguration<T extends Backend = Backend> = OptionsOf<T> & {
149
+ export type BackendConfiguration<T extends Backend> = OptionsOf<T> & {
142
150
  backend: T;
151
+ disableAsyncCache?: boolean;
143
152
  };
144
153
 
145
154
  /**
146
155
  * @internal
147
156
  */
148
- export function isBackendConfig(arg: unknown): arg is BackendConfiguration {
157
+ export function isBackendConfig<T extends Backend>(arg: unknown): arg is BackendConfiguration<T> {
149
158
  return arg != null && typeof arg == 'object' && 'backend' in arg && isBackend(arg.backend);
150
159
  }
@@ -4,6 +4,10 @@ import { Stats, StatsLike } from '../../stats.js';
4
4
  import { encode } from '../../utils.js';
5
5
  import { basename, dirname } from '../../emulation/path.js';
6
6
 
7
+ /**
8
+ * An Index in JSON form
9
+ * @internal
10
+ */
7
11
  export interface IndexData {
8
12
  version: 1;
9
13
  entries: Record<string, StatsLike<number>>;
@@ -13,6 +17,7 @@ export const version = 1;
13
17
 
14
18
  /**
15
19
  * An index of files
20
+ * @internal
16
21
  */
17
22
  export class Index extends Map<string, Stats> {
18
23
  public constructor() {
@@ -201,7 +201,7 @@ export class UnlockedOverlayFS extends FileSystem {
201
201
  }
202
202
  // Create an OverlayFile.
203
203
  const file = this._readable.openFileSync(path, parseFlag('r'), cred);
204
- const stats = Stats.clone(file.statSync());
204
+ const stats = new Stats(file.statSync());
205
205
  const data = new Uint8Array(stats.size);
206
206
  file.readSync(data);
207
207
  return new PreloadFile(this, path, flag, stats, data);
package/src/config.ts CHANGED
@@ -1,17 +1,18 @@
1
- import { ErrnoError, Errno } from './error.js';
2
- import type { Backend, BackendConfiguration } from './backends/backend.js';
1
+ import { type Entries } from 'utilium';
2
+ import type { Backend, BackendConfiguration, FilesystemOf } from './backends/backend.js';
3
3
  import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
4
4
  import * as fs from './emulation/index.js';
5
- import { setCred, type MountObject } from './emulation/shared.js';
6
- import { FileSystem } from './filesystem.js';
7
5
  import type { AbsolutePath } from './emulation/path.js';
6
+ import { setCred, type MountObject } from './emulation/shared.js';
7
+ import { Errno, ErrnoError } from './error.js';
8
+ import { FileSystem, type Async } from './filesystem.js';
8
9
 
9
10
  /**
10
11
  * Configuration for a specific mount point
11
12
  */
12
- export type MountConfiguration<FS extends FileSystem = FileSystem, TOptions extends object = object> = FS | BackendConfiguration<Backend<FS, TOptions>> | Backend<FS, TOptions>;
13
+ export type MountConfiguration<T extends Backend> = FilesystemOf<T> | BackendConfiguration<T> | T;
13
14
 
14
- function isMountConfig(arg: unknown): arg is MountConfiguration {
15
+ function isMountConfig<T extends Backend>(arg: unknown): arg is MountConfiguration<T> {
15
16
  return isBackendConfig(arg) || isBackend(arg) || arg instanceof FileSystem;
16
17
  }
17
18
 
@@ -19,7 +20,7 @@ function isMountConfig(arg: unknown): arg is MountConfiguration {
19
20
  * Retrieve a file system with the given configuration.
20
21
  * @param config A BackendConfig object.
21
22
  */
22
- export async function resolveMountConfig<FS extends FileSystem, TOptions extends object = object>(config: MountConfiguration<FS, TOptions>, _depth = 0): Promise<FS> {
23
+ export async function resolveMountConfig<T extends Backend>(config: MountConfiguration<T>, _depth = 0): Promise<FilesystemOf<T>> {
23
24
  if (typeof config !== 'object' || config == null) {
24
25
  throw new ErrnoError(Errno.EINVAL, 'Invalid options on mount configuration');
25
26
  }
@@ -33,7 +34,7 @@ export async function resolveMountConfig<FS extends FileSystem, TOptions extends
33
34
  }
34
35
 
35
36
  if (isBackend(config)) {
36
- config = { backend: config } as BackendConfiguration<Backend<FS, TOptions>>;
37
+ config = { backend: config } as BackendConfiguration<T>;
37
38
  }
38
39
 
39
40
  for (const [key, value] of Object.entries(config)) {
@@ -49,7 +50,7 @@ export async function resolveMountConfig<FS extends FileSystem, TOptions extends
49
50
  throw new ErrnoError(Errno.EINVAL, 'Invalid configuration, too deep and possibly infinite');
50
51
  }
51
52
 
52
- (<Record<string, FileSystem>>config)[key] = await resolveMountConfig(value, ++_depth);
53
+ (config as Record<string, FileSystem>)[key] = await resolveMountConfig(value, ++_depth);
53
54
  }
54
55
 
55
56
  const { backend } = config;
@@ -58,40 +59,80 @@ export async function resolveMountConfig<FS extends FileSystem, TOptions extends
58
59
  throw new ErrnoError(Errno.EPERM, 'Backend not available: ' + backend);
59
60
  }
60
61
  checkOptions(backend, config);
61
- const mount = await backend.create(config);
62
+ const mount = (await backend.create(config)) as FilesystemOf<T>;
63
+ if ('_disableSync' in mount) {
64
+ type AsyncFS = InstanceType<ReturnType<typeof Async<new () => FilesystemOf<T>>>>;
65
+ (mount as AsyncFS)._disableSync = config.disableAsyncCache || false;
66
+ }
62
67
  await mount.ready();
63
68
  return mount;
64
69
  }
65
70
 
71
+ type ConfigMounts = { [K in AbsolutePath]: Backend };
72
+
66
73
  /**
67
74
  * Configuration
68
75
  */
69
- export interface Configuration {
70
- mounts: Record<AbsolutePath, MountConfiguration>;
71
- uid?: number;
72
- gid?: number;
76
+ export interface Configuration<T extends ConfigMounts> {
77
+ /**
78
+ * An object mapping mount points to mount configuration
79
+ */
80
+ mounts: { [K in keyof T & AbsolutePath]: MountConfiguration<T[K]> };
81
+ /**
82
+ * The uid to use
83
+ */
84
+ uid: number;
85
+ /**
86
+ * The gid to use
87
+ */
88
+ gid: number;
89
+ /**
90
+ * If set, disables the sync cache and sync operations on async file systems.
91
+ */
92
+ disableAsyncCache: boolean;
73
93
  }
74
94
 
75
95
  /**
76
- * Creates filesystems with the given configuration, and initializes ZenFS with it.
77
- * @see Configuration for more info on the configuration object.
96
+ * Configures ZenFS with single mount point /
97
+ */
98
+ export async function configure<T extends Backend>(config: MountConfiguration<T>): Promise<void>;
99
+
100
+ /**
101
+ * Configures ZenFS with the given configuration
102
+ * @see Configuration
103
+ */
104
+ export async function configure<T extends ConfigMounts>(config: Partial<Configuration<T>>): Promise<void>;
105
+
106
+ /**
107
+ * Configures ZenFS with the given configuration
108
+ * @see Configuration
78
109
  */
79
- export async function configure<T extends MountConfiguration | Configuration>(config: T | Configuration): Promise<void> {
110
+ export async function configure(config: MountConfiguration<Backend> | Partial<Configuration<ConfigMounts>>): Promise<void> {
80
111
  const uid = 'uid' in config ? config.uid || 0 : 0;
81
112
  const gid = 'gid' in config ? config.gid || 0 : 0;
82
113
 
83
114
  if (isMountConfig(config)) {
84
115
  // single FS
85
- config = { mounts: { '/': config } };
116
+ config = { mounts: { '/': config } } as Partial<Configuration<ConfigMounts>>;
117
+ }
118
+
119
+ setCred({ uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
120
+
121
+ if (!config.mounts) {
122
+ return;
86
123
  }
87
124
 
88
- for (const [point, value] of Object.entries(config.mounts) as [AbsolutePath, MountConfiguration][]) {
125
+ for (const [point, mountConfig] of Object.entries(config.mounts) as Entries<typeof config.mounts>) {
89
126
  if (!point.startsWith('/')) {
90
127
  throw new ErrnoError(Errno.EINVAL, 'Mount points must have absolute paths');
91
128
  }
92
- config.mounts[point] = await resolveMountConfig(value);
129
+
130
+ if (isBackendConfig(mountConfig)) {
131
+ mountConfig.disableAsyncCache ??= config.disableAsyncCache || false;
132
+ }
133
+
134
+ config.mounts[point] = await resolveMountConfig(mountConfig);
93
135
  }
94
136
 
95
137
  fs.mountObject(config.mounts as MountObject);
96
- setCred({ uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
97
138
  }
@@ -1,7 +1,8 @@
1
+ import { Buffer } from 'buffer';
1
2
  import type * as fs from 'node:fs';
2
3
  import { ErrnoError, Errno } from '../error.js';
3
4
  import type { FileContents } from '../filesystem.js';
4
- import { BigIntStats, type BigIntStatsFs, type Stats, type StatsFs } from '../stats.js';
5
+ import { BigIntStats, type Stats } from '../stats.js';
5
6
  import { nop, normalizeMode, type Callback } from '../utils.js';
6
7
  import { R_OK } from './constants.js';
7
8
  import { Dirent, type Dir } from './dir.js';
@@ -52,7 +53,7 @@ export function stat(path: fs.PathLike, options?: fs.StatOptions | Callback<[Sta
52
53
  callback = typeof options == 'function' ? options : callback;
53
54
  promises
54
55
  .stat(path, typeof options != 'function' ? options : {})
55
- .then(stats => (<Callback<[Stats] | [BigIntStats]>>callback)(undefined, <any>stats))
56
+ .then(stats => (callback as Callback<[Stats] | [BigIntStats]>)(undefined, stats as any))
56
57
  .catch(callback);
57
58
  }
58
59
  stat satisfies Omit<typeof fs.stat, '__promisify__'>;
@@ -71,8 +72,8 @@ export function lstat(path: fs.PathLike, options: fs.StatOptions, callback: Call
71
72
  export function lstat(path: fs.PathLike, options?: fs.StatOptions | Callback<[Stats]>, callback: Callback<[Stats]> | Callback<[BigIntStats]> = nop): void {
72
73
  callback = typeof options == 'function' ? options : callback;
73
74
  promises
74
- .lstat(path, typeof options != 'function' ? options : <object>{})
75
- .then(stats => (<Callback<[Stats] | [BigIntStats]>>callback)(undefined, stats))
75
+ .lstat(path, typeof options != 'function' ? options : ({} as object))
76
+ .then(stats => (callback as Callback<[Stats] | [BigIntStats]>)(undefined, stats))
76
77
  .catch(callback);
77
78
  }
78
79
  lstat satisfies Omit<typeof fs.lstat, '__promisify__'>;
@@ -161,7 +162,7 @@ export function readFile(filename: fs.PathLike, options?: fs.WriteFileOptions |
161
162
 
162
163
  promises
163
164
  .readFile(filename, typeof options === 'function' ? null : options)
164
- .then(data => (<Callback<[string | Uint8Array]>>cb)(undefined, data))
165
+ .then(data => (cb as Callback<[string | Uint8Array]>)(undefined, data))
165
166
  .catch(cb);
166
167
  }
167
168
  readFile satisfies Omit<typeof fs.readFile, '__promisify__'>;
@@ -231,7 +232,7 @@ export function fstat(fd: number, options?: fs.StatOptions | Callback<[Stats]>,
231
232
 
232
233
  fd2file(fd)
233
234
  .stat()
234
- .then(stats => (<Callback<[Stats | BigIntStats]>>cb)(undefined, typeof options == 'object' && options?.bigint ? new BigIntStats(stats) : stats))
235
+ .then(stats => (cb as Callback<[Stats | BigIntStats]>)(undefined, typeof options == 'object' && options?.bigint ? new BigIntStats(stats) : stats))
235
236
  .catch(cb);
236
237
  }
237
238
  fstat satisfies Omit<typeof fs.fstat, '__promisify__'>;
@@ -329,20 +330,20 @@ export function write(fd: number, data: FileContents, cbPosOff?: any, cbLenEnc?:
329
330
  case 'number':
330
331
  // (fd, string, position, encoding?, cb?)
331
332
  position = cbPosOff;
332
- encoding = <BufferEncoding>(typeof cbLenEnc === 'string' ? cbLenEnc : 'utf8');
333
+ encoding = typeof cbLenEnc === 'string' ? (cbLenEnc as BufferEncoding) : 'utf8';
333
334
  cb = typeof cbPos === 'function' ? cbPos : cb;
334
335
  break;
335
336
  default:
336
337
  // ...try to find the callback and get out of here!
337
338
  cb = typeof cbLenEnc === 'function' ? cbLenEnc : typeof cbPos === 'function' ? cbPos : cb;
338
- (<Callback<[number, Uint8Array | string]>>cb)(new ErrnoError(Errno.EINVAL, 'Invalid arguments.'));
339
+ (cb as Callback<[number, Uint8Array | string]>)(new ErrnoError(Errno.EINVAL, 'Invalid arguments.'));
339
340
  return;
340
341
  }
341
342
  buffer = Buffer.from(data);
342
343
  offset = 0;
343
344
  length = buffer.length;
344
345
 
345
- const _cb = <Callback<[number, string]>>cb;
346
+ const _cb = cb as Callback<[number, string]>;
346
347
 
347
348
  handle
348
349
  .write(buffer, offset, length, position)
@@ -354,7 +355,7 @@ export function write(fd: number, data: FileContents, cbPosOff?: any, cbLenEnc?:
354
355
  offset = cbPosOff;
355
356
  length = cbLenEnc;
356
357
  position = typeof cbPos === 'number' ? cbPos : null;
357
- const _cb = <Callback<[number, Uint8Array]>>(typeof cbPos === 'function' ? cbPos : cb);
358
+ const _cb = typeof cbPos === 'function' ? cbPos : (cb as Callback<[number, Uint8Array]>);
358
359
  handle
359
360
  .write(buffer, offset, length, position)
360
361
  .then(({ bytesWritten }) => _cb(undefined, bytesWritten, buffer))
@@ -526,7 +527,7 @@ export function readlink(
526
527
  callback = typeof options == 'function' ? options : callback;
527
528
  promises
528
529
  .readlink(path)
529
- .then(result => (<Callback<[string | Uint8Array]>>callback)(undefined, result))
530
+ .then(result => (callback as Callback<[string | Uint8Array]>)(undefined, result))
530
531
  .catch(callback);
531
532
  }
532
533
  readlink satisfies Omit<typeof fs.readlink, '__promisify__'>;
@@ -816,8 +817,8 @@ export function mkdtemp(prefix: string, options: fs.BufferEncodingOption, callba
816
817
  export function mkdtemp(prefix: string, options: fs.EncodingOption | fs.BufferEncodingOption | Callback<[string]>, callback: Callback<[Buffer]> | Callback<[string]> = nop): void {
817
818
  callback = typeof options === 'function' ? options : callback;
818
819
  promises
819
- .mkdtemp(prefix, typeof options != 'function' ? <fs.EncodingOption>options : null)
820
- .then(result => (<Callback<[string | Buffer]>>callback)(undefined, result))
820
+ .mkdtemp(prefix, typeof options != 'function' ? (options as fs.EncodingOption) : null)
821
+ .then(result => (callback as Callback<[string | Buffer]>)(undefined, result))
821
822
  .catch(callback);
822
823
  }
823
824
  mkdtemp satisfies Omit<typeof fs.mkdtemp, '__promisify__'>;
@@ -881,14 +882,14 @@ export function cp(source: fs.PathLike, destination: fs.PathLike, opts: fs.CopyO
881
882
  }
882
883
  cp satisfies Omit<typeof fs.cp, '__promisify__'>;
883
884
 
884
- export function statfs(path: fs.PathLike, callback: Callback<[StatsFs]>): void;
885
- export function statfs(path: fs.PathLike, options: fs.StatFsOptions & { bigint?: false }, callback: Callback<[StatsFs]>): void;
886
- export function statfs(path: fs.PathLike, options: fs.StatFsOptions & { bigint: true }, callback: Callback<[BigIntStatsFs]>): void;
887
- export function statfs(path: fs.PathLike, options?: fs.StatFsOptions | Callback<[StatsFs]>, callback: Callback<[StatsFs]> | Callback<[BigIntStatsFs]> = nop): void {
885
+ export function statfs(path: fs.PathLike, callback: Callback<[fs.StatsFs]>): void;
886
+ export function statfs(path: fs.PathLike, options: fs.StatFsOptions & { bigint?: false }, callback: Callback<[fs.StatsFs]>): void;
887
+ export function statfs(path: fs.PathLike, options: fs.StatFsOptions & { bigint: true }, callback: Callback<[fs.BigIntStatsFs]>): void;
888
+ export function statfs(path: fs.PathLike, options?: fs.StatFsOptions | Callback<[fs.StatsFs]>, callback: Callback<[fs.StatsFs]> | Callback<[fs.BigIntStatsFs]> = nop): void {
888
889
  callback = typeof options === 'function' ? options : callback;
889
890
  promises
890
891
  .statfs(path, typeof options === 'function' ? undefined : options)
891
- .then(result => (<Callback<[StatsFs | BigIntStatsFs]>>callback)(undefined, result))
892
+ .then(result => (callback as Callback<[fs.StatsFs | fs.BigIntStatsFs]>)(undefined, result))
892
893
  .catch(callback);
893
894
  }
894
895
  statfs satisfies Omit<typeof fs.statfs, '__promisify__'>;
@@ -5,4 +5,4 @@ export * as constants from './constants.js';
5
5
  export * from './streams.js';
6
6
  export * from './dir.js';
7
7
  export { mountObject, mounts, mount, umount } from './shared.js';
8
- export { Stats, BigIntStats, StatsFs } from '../stats.js';
8
+ export { Stats, StatsFs, BigIntStatsFs } from '../stats.js';
@@ -28,6 +28,9 @@ https://raw.githubusercontent.com/nodejs/node/3907bd1/lib/path.js
28
28
 
29
29
  import type { ParsedPath } from 'node:path';
30
30
 
31
+ /**
32
+ * An absolute path
33
+ */
31
34
  export type AbsolutePath = `/${string}`;
32
35
 
33
36
  export let cwd = '/';
@@ -156,7 +159,7 @@ export function normalize(path: string): string {
156
159
  return isAbsolute ? `/${path}` : path;
157
160
  }
158
161
 
159
- export function isAbsolute(path: string): boolean {
162
+ export function isAbsolute(path: string): path is AbsolutePath {
160
163
  return path.startsWith('/');
161
164
  }
162
165
 
@@ -6,15 +6,15 @@ import type { Stream } from 'node:stream';
6
6
  import type { ReadableStream as TReadableStream } from 'node:stream/web';
7
7
  import type { Interface as ReadlineInterface } from 'readline';
8
8
  import type { ReadableStreamController } from 'stream/web';
9
- import { ErrnoError, Errno } from '../error.js';
9
+ import { Errno, ErrnoError } from '../error.js';
10
10
  import { ActionType, File, isAppendable, isReadable, isWriteable, parseFlag, pathExistsAction, pathNotExistsAction } from '../file.js';
11
11
  import type { FileContents } from '../filesystem.js';
12
- import { BigIntStats, FileType, type BigIntStatsFs, type Stats, type StatsFs } from '../stats.js';
12
+ import { BigIntStats, FileType, type Stats } from '../stats.js';
13
13
  import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
14
14
  import * as constants from './constants.js';
15
15
  import { Dir, Dirent } from './dir.js';
16
16
  import { dirname, join, parse } from './path.js';
17
- import { cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
17
+ import { _statfs, cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
18
18
  import { ReadStream, WriteStream } from './streams.js';
19
19
  export * as constants from './constants.js';
20
20
 
@@ -233,7 +233,7 @@ export class FileHandle implements promises.FileHandle {
233
233
  if (typeof data === 'string') {
234
234
  // Signature 1: (fd, string, [position?, [encoding?]])
235
235
  position = typeof posOrOff === 'number' ? posOrOff : null;
236
- const encoding = <BufferEncoding>(typeof lenOrEnc === 'string' ? lenOrEnc : 'utf8');
236
+ const encoding = typeof lenOrEnc === 'string' ? lenOrEnc : ('utf8' as BufferEncoding);
237
237
  offset = 0;
238
238
  buffer = Buffer.from(data, encoding);
239
239
  length = buffer.length;
@@ -491,28 +491,7 @@ async function _open(path: fs.PathLike, _flag: fs.OpenMode, _mode: fs.Mode = 0o6
491
491
  path = resolveSymlinks && (await exists(path)) ? await realpath(path) : path;
492
492
  const { fs, path: resolved } = resolveMount(path);
493
493
 
494
- try {
495
- switch (pathExistsAction(flag)) {
496
- case ActionType.THROW:
497
- throw ErrnoError.With('EEXIST', path, '_open');
498
- case ActionType.TRUNCATE:
499
- /*
500
- In a previous implementation, we deleted the file and
501
- re-created it. However, this created a race condition if another
502
- asynchronous request was trying to read the file, as the file
503
- would not exist for a small period of time.
504
- */
505
- const file: File = await fs.openFile(resolved, flag, cred);
506
- await file.truncate(0);
507
- await file.sync();
508
- return new FileHandle(file);
509
- case ActionType.NOP:
510
- // Must await so thrown errors are caught by the catch below
511
- return new FileHandle(await fs.openFile(resolved, flag, cred));
512
- default:
513
- throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
514
- }
515
- } catch (e) {
494
+ if (!(await fs.exists(path, cred))) {
516
495
  switch (pathNotExistsAction(flag)) {
517
496
  case ActionType.CREATE:
518
497
  // Ensure parent exists.
@@ -527,6 +506,27 @@ async function _open(path: fs.PathLike, _flag: fs.OpenMode, _mode: fs.Mode = 0o6
527
506
  throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
528
507
  }
529
508
  }
509
+
510
+ switch (pathExistsAction(flag)) {
511
+ case ActionType.THROW:
512
+ throw ErrnoError.With('EEXIST', path, '_open');
513
+ case ActionType.TRUNCATE:
514
+ /*
515
+ In a previous implementation, we deleted the file and
516
+ re-created it. However, this created a race condition if another
517
+ asynchronous request was trying to read the file, as the file
518
+ would not exist for a small period of time.
519
+ */
520
+ const file: File = await fs.openFile(resolved, flag, cred);
521
+ await file.truncate(0);
522
+ await file.sync();
523
+ return new FileHandle(file);
524
+ case ActionType.NOP:
525
+ // Must await so thrown errors are caught by the catch below
526
+ return new FileHandle(await fs.openFile(resolved, flag, cred));
527
+ default:
528
+ throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
529
+ }
530
530
  }
531
531
 
532
532
  /**
@@ -1061,9 +1061,11 @@ cp satisfies typeof promises.cp;
1061
1061
  * @since v18.15.0
1062
1062
  * @return Fulfills with an {fs.StatFs} for the file system.
1063
1063
  */
1064
- export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions & { bigint?: false }): Promise<StatsFs>;
1065
- export async function statfs(path: fs.PathLike, opts: fs.StatFsOptions & { bigint: true }): Promise<BigIntStatsFs>;
1066
- export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions): Promise<StatsFs | BigIntStatsFs>;
1067
- export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions): Promise<StatsFs | BigIntStatsFs> {
1068
- throw ErrnoError.With('ENOSYS', path.toString(), 'statfs');
1064
+ export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions & { bigint?: false }): Promise<fs.StatsFs>;
1065
+ export async function statfs(path: fs.PathLike, opts: fs.StatFsOptions & { bigint: true }): Promise<fs.BigIntStatsFs>;
1066
+ export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions): Promise<fs.StatsFs | fs.BigIntStatsFs>;
1067
+ export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions): Promise<fs.StatsFs | fs.BigIntStatsFs> {
1068
+ path = normalizePath(path);
1069
+ const { fs } = resolveMount(path);
1070
+ return _statfs(fs, opts?.bigint);
1069
1071
  }
@@ -1,10 +1,13 @@
1
1
  // Utilities and shared data
2
2
 
3
- import { ErrnoError, Errno } from '../error.js';
3
+ import type { BigIntStatsFs, StatsFs } from 'node:fs';
4
4
  import { InMemory } from '../backends/memory.js';
5
5
  import { Cred, rootCred } from '../cred.js';
6
+ import { Errno, ErrnoError } from '../error.js';
6
7
  import type { File } from '../file.js';
7
8
  import { FileSystem } from '../filesystem.js';
9
+ import { size_max } from '../inode.js';
10
+ import { ZenFsType } from '../stats.js';
8
11
  import { normalizePath } from '../utils.js';
9
12
  import { resolve, type AbsolutePath } from './path.js';
10
13
 
@@ -117,3 +120,21 @@ export function mountObject(mounts: MountObject): void {
117
120
  mount(point, fs);
118
121
  }
119
122
  }
123
+
124
+ /**
125
+ * @hidden
126
+ */
127
+ export function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T extends true ? BigIntStatsFs : StatsFs {
128
+ const md = fs.metadata();
129
+ const bs = md.blockSize || 4096;
130
+
131
+ return {
132
+ type: (bigint ? BigInt : Number)(md.type || ZenFsType),
133
+ bsize: (bigint ? BigInt : Number)(bs),
134
+ ffree: (bigint ? BigInt : Number)(md.freeNodes || size_max),
135
+ files: (bigint ? BigInt : Number)(md.totalNodes || size_max),
136
+ bavail: (bigint ? BigInt : Number)(md.freeSpace / bs),
137
+ bfree: (bigint ? BigInt : Number)(md.freeSpace / bs),
138
+ blocks: (bigint ? BigInt : Number)(md.totalSpace / bs),
139
+ } as T extends true ? BigIntStatsFs : StatsFs;
140
+ }