@zenfs/core 0.12.2 → 0.12.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 (49) hide show
  1. package/dist/backends/backend.d.ts +1 -1
  2. package/dist/backends/fetch.d.ts +1 -1
  3. package/dist/backends/index/fs.d.ts +2 -2
  4. package/dist/backends/index/index.d.ts +2 -1
  5. package/dist/backends/overlay.d.ts +4 -3
  6. package/dist/backends/port/fs.d.ts +5 -2
  7. package/dist/backends/port/fs.js +13 -10
  8. package/dist/browser.min.js +4 -4
  9. package/dist/browser.min.js.map +3 -3
  10. package/dist/config.d.ts +1 -1
  11. package/dist/config.js +1 -1
  12. package/dist/cred.d.ts +2 -1
  13. package/dist/emulation/async.d.ts +2 -1
  14. package/dist/emulation/promises.d.ts +1 -1
  15. package/dist/emulation/promises.js +27 -35
  16. package/dist/emulation/shared.d.ts +2 -2
  17. package/dist/emulation/shared.js +1 -2
  18. package/dist/emulation/streams.d.ts +1 -1
  19. package/dist/emulation/sync.d.ts +1 -1
  20. package/dist/emulation/sync.js +24 -32
  21. package/dist/error.d.ts +1 -1
  22. package/dist/error.js +1 -1
  23. package/dist/file.d.ts +2 -13
  24. package/dist/file.js +3 -33
  25. package/dist/filesystem.d.ts +18 -36
  26. package/dist/filesystem.js +9 -13
  27. package/dist/stats.d.ts +1 -1
  28. package/dist/utils.d.ts +2 -2
  29. package/license.md +2 -26
  30. package/package.json +2 -2
  31. package/readme.md +1 -1
  32. package/src/backends/backend.ts +1 -1
  33. package/src/backends/fetch.ts +1 -1
  34. package/src/backends/index/fs.ts +2 -1
  35. package/src/backends/index/index.ts +2 -1
  36. package/src/backends/overlay.ts +6 -3
  37. package/src/backends/port/fs.ts +14 -10
  38. package/src/config.ts +1 -1
  39. package/src/cred.ts +2 -1
  40. package/src/emulation/async.ts +2 -1
  41. package/src/emulation/promises.ts +28 -33
  42. package/src/emulation/shared.ts +4 -4
  43. package/src/emulation/streams.ts +1 -1
  44. package/src/emulation/sync.ts +28 -33
  45. package/src/error.ts +1 -1
  46. package/src/file.ts +5 -39
  47. package/src/filesystem.ts +19 -38
  48. package/src/stats.ts +1 -1
  49. package/src/utils.ts +2 -2
package/readme.md CHANGED
@@ -4,7 +4,7 @@ ZenFS is a file system that emulates the [NodeJS filesystem API](http://nodejs.o
4
4
 
5
5
  It works using a system of backends, which are used by ZenFS to store and retrieve data. ZenFS can also integrate with other tools.
6
6
 
7
- 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?tab=readme-ov-file#citing).
7
+ 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).
8
8
 
9
9
  ## Backends
10
10
 
@@ -1,6 +1,6 @@
1
1
  import type { RequiredKeys } from 'utilium';
2
2
  import { ErrnoError, Errno } from '../error.js';
3
- import { FileSystem } from '../filesystem.js';
3
+ import type { FileSystem } from '../filesystem.js';
4
4
  import { levenshtein } from '../utils.js';
5
5
 
6
6
  type OptionType = 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function';
@@ -1,6 +1,6 @@
1
1
  import { Errno, ErrnoError } from '../error.js';
2
2
  import type { FileSystemMetadata } from '../filesystem.js';
3
- import { Stats } from '../stats.js';
3
+ import type { Stats } from '../stats.js';
4
4
  import type { Backend } from './backend.js';
5
5
  import { IndexFS } from './index/fs.js';
6
6
  import type { IndexData } from './index/index.js';
@@ -4,7 +4,8 @@ import { NoSyncFile, isWriteable, flagToMode } from '../../file.js';
4
4
  import { Readonly, FileSystem } from '../../filesystem.js';
5
5
  import type { Stats } from '../../stats.js';
6
6
  import { decode } from '../../utils.js';
7
- import { Index, IndexData } from './index.js';
7
+ import type { IndexData } from './index.js';
8
+ import { Index } from './index.js';
8
9
 
9
10
  export abstract class IndexFS extends Readonly(FileSystem) {
10
11
  protected index: Index = new Index();
@@ -1,6 +1,7 @@
1
1
  import { isJSON } from 'utilium';
2
2
  import { Errno, ErrnoError } from '../../error.js';
3
- import { Stats, StatsLike } from '../../stats.js';
3
+ import type { StatsLike } from '../../stats.js';
4
+ import { Stats } from '../../stats.js';
4
5
  import { encode } from '../../utils.js';
5
6
  import { basename, dirname } from '../../emulation/path.js';
6
7
 
@@ -1,10 +1,13 @@
1
- import { FileSystem, FileSystemMetadata } from '../filesystem.js';
1
+ import type { FileSystemMetadata } from '../filesystem.js';
2
+ import { FileSystem } from '../filesystem.js';
2
3
  import { ErrnoError, Errno } from '../error.js';
3
- import { File, PreloadFile, parseFlag } from '../file.js';
4
+ import type { File } from '../file.js';
5
+ import { PreloadFile, parseFlag } from '../file.js';
4
6
  import { Stats } from '../stats.js';
5
7
  import { LockedFS } from './locked.js';
6
8
  import { dirname } from '../emulation/path.js';
7
- import { Cred, rootCred } from '../cred.js';
9
+ import type { Cred } from '../cred.js';
10
+ import { rootCred } from '../cred.js';
8
11
  import { decode, encode } from '../utils.js';
9
12
  import type { Backend } from './backend.js';
10
13
  /**
@@ -38,12 +38,16 @@ export class PortFile extends File {
38
38
  );
39
39
  }
40
40
 
41
+ protected _throwNoSync(syscall: string): never {
42
+ throw new ErrnoError(Errno.ENOTSUP, 'Syncrohnous operations not support on PortFile', this.path, syscall);
43
+ }
44
+
41
45
  public stat(): Promise<Stats> {
42
46
  return this.rpc('stat');
43
47
  }
44
48
 
45
49
  public statSync(): Stats {
46
- throw ErrnoError.With('ENOSYS', this.path, 'PortFile.stat');
50
+ this._throwNoSync('stat');
47
51
  }
48
52
 
49
53
  public truncate(len: number): Promise<void> {
@@ -51,7 +55,7 @@ export class PortFile extends File {
51
55
  }
52
56
 
53
57
  public truncateSync(): void {
54
- throw ErrnoError.With('ENOSYS', this.path, 'PortFile.truncate');
58
+ this._throwNoSync('truncate');
55
59
  }
56
60
 
57
61
  public write(buffer: Uint8Array, offset?: number, length?: number, position?: number): Promise<number> {
@@ -59,7 +63,7 @@ export class PortFile extends File {
59
63
  }
60
64
 
61
65
  public writeSync(): number {
62
- throw ErrnoError.With('ENOSYS', this.path, 'PortFile.write');
66
+ this._throwNoSync('write');
63
67
  }
64
68
 
65
69
  public async read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<FileReadResult<TBuffer>> {
@@ -68,7 +72,7 @@ export class PortFile extends File {
68
72
  }
69
73
 
70
74
  public readSync(): number {
71
- throw ErrnoError.With('ENOSYS', this.path, 'PortFile.read');
75
+ this._throwNoSync('read');
72
76
  }
73
77
 
74
78
  public chown(uid: number, gid: number): Promise<void> {
@@ -76,7 +80,7 @@ export class PortFile extends File {
76
80
  }
77
81
 
78
82
  public chownSync(): void {
79
- throw ErrnoError.With('ENOSYS', this.path, 'PortFile.chown');
83
+ this._throwNoSync('chown');
80
84
  }
81
85
 
82
86
  public chmod(mode: number): Promise<void> {
@@ -84,7 +88,7 @@ export class PortFile extends File {
84
88
  }
85
89
 
86
90
  public chmodSync(): void {
87
- throw ErrnoError.With('ENOSYS', this.path, 'PortFile.chmod');
91
+ this._throwNoSync('chmod');
88
92
  }
89
93
 
90
94
  public utimes(atime: Date, mtime: Date): Promise<void> {
@@ -92,7 +96,7 @@ export class PortFile extends File {
92
96
  }
93
97
 
94
98
  public utimesSync(): void {
95
- throw ErrnoError.With('ENOSYS', this.path, 'PortFile.utimes');
99
+ this._throwNoSync('utimes');
96
100
  }
97
101
 
98
102
  public _setType(type: FileType): Promise<void> {
@@ -100,7 +104,7 @@ export class PortFile extends File {
100
104
  }
101
105
 
102
106
  public _setTypeSync(): void {
103
- throw ErrnoError.With('ENOSYS', this.path, 'PortFile._setType');
107
+ this._throwNoSync('_setType');
104
108
  }
105
109
 
106
110
  public close(): Promise<void> {
@@ -108,7 +112,7 @@ export class PortFile extends File {
108
112
  }
109
113
 
110
114
  public closeSync(): void {
111
- throw ErrnoError.With('ENOSYS', this.path, 'PortFile.close');
115
+ this._throwNoSync('close');
112
116
  }
113
117
 
114
118
  public sync(): Promise<void> {
@@ -116,7 +120,7 @@ export class PortFile extends File {
116
120
  }
117
121
 
118
122
  public syncSync(): void {
119
- throw ErrnoError.With('ENOSYS', this.path, 'PortFile.sync');
123
+ this._throwNoSync('sync');
120
124
  }
121
125
  }
122
126
 
package/src/config.ts CHANGED
@@ -18,7 +18,7 @@ function isMountConfig<T extends Backend>(arg: unknown): arg is MountConfigurati
18
18
 
19
19
  /**
20
20
  * Retrieve a file system with the given configuration.
21
- * @param config A BackendConfig object.
21
+ * @see MountConfiguration
22
22
  */
23
23
  export async function resolveMountConfig<T extends Backend>(config: MountConfiguration<T>, _depth = 0): Promise<FilesystemOf<T>> {
24
24
  if (typeof config !== 'object' || config == null) {
package/src/cred.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Credentials used for various operations.
3
- * Similar to Linux's cred struct. See https://github.com/torvalds/linux/blob/master/include/linux/cred.h
3
+ * Similar to Linux's cred struct.
4
+ * @see https://github.com/torvalds/linux/blob/master/include/linux/cred.h
4
5
  */
5
6
  export interface Cred {
6
7
  uid: number;
@@ -5,7 +5,8 @@ import type { FileContents } from '../filesystem.js';
5
5
  import { BigIntStats, type Stats } from '../stats.js';
6
6
  import { nop, normalizeMode, type Callback } from '../utils.js';
7
7
  import { R_OK } from './constants.js';
8
- import { Dirent, type Dir } from './dir.js';
8
+ import type { Dirent } from './dir.js';
9
+ import { type Dir } from './dir.js';
9
10
  import * as promises from './promises.js';
10
11
  import { fd2file } from './shared.js';
11
12
  import { ReadStream, WriteStream } from './streams.js';
@@ -7,7 +7,8 @@ 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
9
  import { Errno, ErrnoError } from '../error.js';
10
- import { ActionType, File, isAppendable, isReadable, isWriteable, parseFlag, pathExistsAction, pathNotExistsAction } from '../file.js';
10
+ import type { File } from '../file.js';
11
+ import { isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
11
12
  import type { FileContents } from '../filesystem.js';
12
13
  import { BigIntStats, FileType, type Stats } from '../stats.js';
13
14
  import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
@@ -110,7 +111,7 @@ export class FileHandle implements promises.FileHandle {
110
111
  throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
111
112
  }
112
113
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : data;
113
- await this.file.write(encodedData, 0, encodedData.length, null);
114
+ await this.file.write(encodedData, 0, encodedData.length);
114
115
  }
115
116
 
116
117
  /**
@@ -492,41 +493,35 @@ async function _open(path: fs.PathLike, _flag: fs.OpenMode, _mode: fs.Mode = 0o6
492
493
  const { fs, path: resolved } = resolveMount(path);
493
494
 
494
495
  if (!(await fs.exists(path, cred))) {
495
- switch (pathNotExistsAction(flag)) {
496
- case ActionType.CREATE:
497
- // Ensure parent exists.
498
- const parentStats: Stats = await fs.stat(dirname(resolved), cred);
499
- if (parentStats && !parentStats.isDirectory()) {
500
- throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
501
- }
502
- return new FileHandle(await fs.createFile(resolved, flag, mode, cred));
503
- case ActionType.THROW:
504
- throw ErrnoError.With('ENOENT', path, '_open');
505
- default:
506
- throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
496
+ if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') {
497
+ throw ErrnoError.With('ENOENT', path, '_open');
498
+ }
499
+ // Create the file
500
+ const parentStats: Stats = await fs.stat(dirname(resolved), cred);
501
+ if (parentStats && !parentStats.isDirectory()) {
502
+ throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
507
503
  }
504
+ return new FileHandle(await fs.createFile(resolved, flag, mode, cred));
508
505
  }
509
506
 
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');
507
+ if (isExclusive(flag)) {
508
+ throw ErrnoError.With('EEXIST', path, '_open');
529
509
  }
510
+
511
+ if (!isTruncating(flag)) {
512
+ return new FileHandle(await fs.openFile(resolved, flag, cred));
513
+ }
514
+
515
+ /*
516
+ In a previous implementation, we deleted the file and
517
+ re-created it. However, this created a race condition if another
518
+ asynchronous request was trying to read the file, as the file
519
+ would not exist for a small period of time.
520
+ */
521
+ const file: File = await fs.openFile(resolved, flag, cred);
522
+ await file.truncate(0);
523
+ await file.sync();
524
+ return new FileHandle(file);
530
525
  }
531
526
 
532
527
  /**
@@ -2,12 +2,12 @@
2
2
 
3
3
  import type { BigIntStatsFs, StatsFs } from 'node:fs';
4
4
  import { InMemory } from '../backends/memory.js';
5
- import { Cred, rootCred } from '../cred.js';
5
+ import type { Cred } from '../cred.js';
6
+ import { rootCred } from '../cred.js';
6
7
  import { Errno, ErrnoError } from '../error.js';
7
8
  import type { File } from '../file.js';
8
- import { FileSystem } from '../filesystem.js';
9
+ import type { FileSystem } from '../filesystem.js';
9
10
  import { size_max } from '../inode.js';
10
- import { ZenFsType } from '../stats.js';
11
11
  import { normalizePath } from '../utils.js';
12
12
  import { resolve, type AbsolutePath } from './path.js';
13
13
 
@@ -129,7 +129,7 @@ export function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T
129
129
  const bs = md.blockSize || 4096;
130
130
 
131
131
  return {
132
- type: (bigint ? BigInt : Number)(md.type || ZenFsType),
132
+ type: (bigint ? BigInt : Number)(md.type),
133
133
  bsize: (bigint ? BigInt : Number)(bs),
134
134
  ffree: (bigint ? BigInt : Number)(md.freeNodes || size_max),
135
135
  files: (bigint ? BigInt : Number)(md.totalNodes || size_max),
@@ -1,6 +1,6 @@
1
1
  import type * as Node from 'fs';
2
2
  import { Readable, Writable } from 'readable-stream';
3
- import { Callback } from '../utils.js';
3
+ import type { Callback } from '../utils.js';
4
4
  import { ErrnoError, Errno } from '../error.js';
5
5
 
6
6
  export class ReadStream extends Readable implements Node.ReadStream {
@@ -1,8 +1,9 @@
1
1
  import { Buffer } from 'buffer';
2
2
  import type * as fs from 'node:fs';
3
3
  import { Errno, ErrnoError } from '../error.js';
4
- import { ActionType, File, isAppendable, isReadable, isWriteable, parseFlag, pathExistsAction, pathNotExistsAction } from '../file.js';
5
- import { FileContents } from '../filesystem.js';
4
+ import type { File } from '../file.js';
5
+ import { isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
6
+ import type { FileContents } from '../filesystem.js';
6
7
  import { BigIntStats, FileType, type Stats } from '../stats.js';
7
8
  import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
8
9
  import { COPYFILE_EXCL, F_OK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK } from './constants.js';
@@ -131,19 +132,15 @@ function _openSync(path: fs.PathLike, _flag: fs.OpenMode, _mode?: fs.Mode | null
131
132
  const { fs, path: resolved } = resolveMount(path);
132
133
 
133
134
  if (!fs.existsSync(resolved, cred)) {
134
- switch (pathNotExistsAction(flag)) {
135
- case ActionType.CREATE:
136
- // Ensure parent exists.
137
- const parentStats: Stats = fs.statSync(dirname(resolved), cred);
138
- if (!parentStats.isDirectory()) {
139
- throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
140
- }
141
- return fs.createFileSync(resolved, flag, mode, cred);
142
- case ActionType.THROW:
143
- throw ErrnoError.With('ENOENT', path, '_open');
144
- default:
145
- throw new ErrnoError(Errno.EINVAL, 'Invalid FileFlag object.');
135
+ if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') {
136
+ throw ErrnoError.With('ENOENT', path, '_open');
137
+ }
138
+ // Create the file
139
+ const parentStats: Stats = fs.statSync(dirname(resolved), cred);
140
+ if (!parentStats.isDirectory()) {
141
+ throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
146
142
  }
143
+ return fs.createFileSync(resolved, flag, mode, cred);
147
144
  }
148
145
 
149
146
  const stats: Stats = fs.statSync(resolved, cred);
@@ -152,25 +149,23 @@ function _openSync(path: fs.PathLike, _flag: fs.OpenMode, _mode?: fs.Mode | null
152
149
  throw ErrnoError.With('EACCES', path, '_open');
153
150
  }
154
151
 
155
- // File exists.
156
- switch (pathExistsAction(flag)) {
157
- case ActionType.THROW:
158
- throw ErrnoError.With('EEXIST', path, '_open');
159
- case ActionType.TRUNCATE:
160
- // Delete file.
161
- fs.unlinkSync(resolved, cred);
162
- /*
163
- Create file. Use the same mode as the old file.
164
- Node itself modifies the ctime when this occurs, so this action
165
- will preserve that behavior if the underlying file system
166
- supports those properties.
167
- */
168
- return fs.createFileSync(resolved, flag, stats.mode, cred);
169
- case ActionType.NOP:
170
- return fs.openFileSync(resolved, flag, cred);
171
- default:
172
- throw new ErrnoError(Errno.EINVAL, 'Invalid FileFlag object.');
152
+ if (isExclusive(flag)) {
153
+ throw ErrnoError.With('EEXIST', path, '_open');
173
154
  }
155
+
156
+ if (!isTruncating(flag)) {
157
+ return fs.openFileSync(resolved, flag, cred);
158
+ }
159
+
160
+ // Delete file.
161
+ fs.unlinkSync(resolved, cred);
162
+ /*
163
+ Create file. Use the same mode as the old file.
164
+ Node itself modifies the ctime when this occurs, so this action
165
+ will preserve that behavior if the underlying file system
166
+ supports those properties.
167
+ */
168
+ return fs.createFileSync(resolved, flag, stats.mode, cred);
174
169
  }
175
170
 
176
171
  /**
@@ -292,7 +287,7 @@ export function appendFileSync(filename: fs.PathOrFileDescriptor, data: FileCont
292
287
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
293
288
  const file = _openSync(typeof filename == 'number' ? fd2file(filename).path! : filename.toString(), flag, options.mode, true);
294
289
  try {
295
- file.writeSync(encodedData, 0, encodedData.byteLength, null);
290
+ file.writeSync(encodedData, 0, encodedData.byteLength);
296
291
  } finally {
297
292
  file.closeSync();
298
293
  }
package/src/error.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Standard libc error codes. More will be added to this enum and error strings as they are
3
3
  * needed.
4
- * @url https://en.wikipedia.org/wiki/Errno.h
4
+ * @see https://en.wikipedia.org/wiki/Errno.h
5
5
  */
6
6
  export enum Errno {
7
7
  /** Operation not permitted */
package/src/file.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { FileReadResult } from 'node:fs/promises';
2
- import { ErrnoError, Errno } from './error.js';
3
2
  import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT } from './emulation/constants.js';
3
+ import { Errno, ErrnoError } from './error.js';
4
4
  import type { FileSystem } from './filesystem.js';
5
5
  import { size_max } from './inode.js';
6
6
  import { Stats, type FileType } from './stats.js';
@@ -32,20 +32,6 @@ declare global {
32
32
  }
33
33
  }
34
34
 
35
- /**
36
- * @hidden
37
- */
38
- export enum ActionType {
39
- // Indicates that the code should not do anything.
40
- NOP = 0,
41
- // Indicates that the code should throw an exception.
42
- THROW = 1,
43
- // Indicates that the code should truncate the file, but only if it is a file.
44
- TRUNCATE = 2,
45
- // Indicates that the code should create the file.
46
- CREATE = 3,
47
- }
48
-
49
35
  const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+'];
50
36
 
51
37
  export function parseFlag(flag: string | number): string {
@@ -158,25 +144,6 @@ export function isExclusive(flag: string): boolean {
158
144
  return flag.indexOf('x') !== -1;
159
145
  }
160
146
 
161
- export function pathExistsAction(flag: string): ActionType {
162
- if (isExclusive(flag)) {
163
- return ActionType.THROW;
164
- }
165
-
166
- if (isTruncating(flag)) {
167
- return ActionType.TRUNCATE;
168
- }
169
-
170
- return ActionType.NOP;
171
- }
172
-
173
- export function pathNotExistsAction(flag: string): ActionType {
174
- if ((isWriteable(flag) || isAppendable(flag)) && flag !== 'r+') {
175
- return ActionType.CREATE;
176
- }
177
- return ActionType.THROW;
178
- }
179
-
180
147
  export abstract class File {
181
148
  /**
182
149
  * Get the current file position.
@@ -249,7 +216,7 @@ export abstract class File {
249
216
  * the current position.
250
217
  * @returns Promise resolving to the new length of the buffer
251
218
  */
252
- public abstract write(buffer: Uint8Array, offset?: number, length?: number, position?: number | null): Promise<number>;
219
+ public abstract write(buffer: Uint8Array, offset?: number, length?: number, position?: number): Promise<number>;
253
220
 
254
221
  /**
255
222
  * Write buffer to the file.
@@ -263,7 +230,7 @@ export abstract class File {
263
230
  * data should be written. If position is null, the data will be written at
264
231
  * the current position.
265
232
  */
266
- public abstract writeSync(buffer: Uint8Array, offset?: number, length?: number, position?: number | null): number;
233
+ public abstract writeSync(buffer: Uint8Array, offset?: number, length?: number, position?: number): number;
267
234
 
268
235
  /**
269
236
  * Read data from the file.
@@ -515,7 +482,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
515
482
  * data should be written. If position is null, the data will be written at
516
483
  * the current position.
517
484
  */
518
- public async write(buffer: Uint8Array, offset: number = 0, length: number = this.stats.size, position: number = 0): Promise<number> {
485
+ public async write(buffer: Uint8Array, offset: number = 0, length: number = this.stats.size, position: number = this.position): Promise<number> {
519
486
  const bytesWritten = this.writeSync(buffer, offset, length, position);
520
487
  await this.sync();
521
488
  return bytesWritten;
@@ -534,9 +501,8 @@ export class PreloadFile<FS extends FileSystem> extends File {
534
501
  * the current position.
535
502
  * @returns bytes written
536
503
  */
537
- public writeSync(buffer: Uint8Array, offset: number = 0, length: number = this.stats.size, position: number = 0): number {
504
+ public writeSync(buffer: Uint8Array, offset: number = 0, length: number = this.stats.size, position: number = this.position): number {
538
505
  this.dirty = true;
539
- position ??= this.position;
540
506
  if (!isWriteable(this.flag)) {
541
507
  throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
542
508
  }
package/src/filesystem.ts CHANGED
@@ -3,7 +3,7 @@ import { ErrnoError, Errno } from './error.js';
3
3
  import { rootCred, type Cred } from './cred.js';
4
4
  import { join } from './emulation/path.js';
5
5
  import { PreloadFile, parseFlag, type File } from './file.js';
6
- import type { Stats } from './stats.js';
6
+ import { ZenFsType, type Stats } from './stats.js';
7
7
 
8
8
  export type FileContents = ArrayBufferView | string;
9
9
 
@@ -64,26 +64,17 @@ export interface FileSystemMetadata {
64
64
  /**
65
65
  * The type of the FS
66
66
  */
67
- type?: number;
67
+ type: number;
68
68
  }
69
69
 
70
70
  /**
71
71
  * Structure for a filesystem. All ZenFS backends must extend this.
72
72
  *
73
- * This class includes some default implementations
73
+ * This class includes default implementations for `exists` and `existsSync`
74
74
  *
75
- * Assume the following about arguments passed to each API method:
76
- *
77
- * - Every path is an absolute path. `.`, `..`, and other items are resolved into an absolute form.
78
- * - All arguments are present. Any optional arguments at the Node API level have been passed in with their default values.
75
+ * If you are extending this class, note that every path is an absolute path and all arguments are present.
79
76
  */
80
77
  export abstract class FileSystem {
81
- /**
82
- * Numeric type, used for statfs
83
- * @internal @protected
84
- */
85
- _type?: number;
86
-
87
78
  /**
88
79
  * Get metadata about the current file system
89
80
  */
@@ -95,7 +86,7 @@ export abstract class FileSystem {
95
86
  freeSpace: 0,
96
87
  noResizableBuffers: false,
97
88
  noAsyncCache: false,
98
- type: this._type,
89
+ type: ZenFsType,
99
90
  };
100
91
  }
101
92
 
@@ -104,8 +95,7 @@ export abstract class FileSystem {
104
95
  public async ready(): Promise<void> {}
105
96
 
106
97
  /**
107
- * Asynchronous rename. No arguments other than a possible exception
108
- * are given to the completion callback.
98
+ * Asynchronous rename.
109
99
  */
110
100
  public abstract rename(oldPath: string, newPath: string, cred: Cred): Promise<void>;
111
101
  /**
@@ -124,29 +114,27 @@ export abstract class FileSystem {
124
114
  public abstract statSync(path: string, cred: Cred): Stats;
125
115
 
126
116
  /**
127
- * Opens the file at path p with the given flag. The file must exist.
128
- * @param p The path to open.
117
+ * Opens the file at `path` with the given flag. The file must exist.
118
+ * @param path The path to open.
129
119
  * @param flag The flag to use when opening the file.
130
120
  */
131
121
  public abstract openFile(path: string, flag: string, cred: Cred): Promise<File>;
132
122
 
133
123
  /**
134
- * Opens the file at path p with the given flag. The file must exist.
135
- * @param p The path to open.
124
+ * Opens the file at `path` with the given flag. The file must exist.
125
+ * @param path The path to open.
136
126
  * @param flag The flag to use when opening the file.
137
127
  * @return A File object corresponding to the opened file.
138
128
  */
139
129
  public abstract openFileSync(path: string, flag: string, cred: Cred): File;
140
130
 
141
131
  /**
142
- * Create the file at path p with the given mode. Then, open it with the given
143
- * flag.
132
+ * Create the file at `path` with the given mode. Then, open it with the given flag.
144
133
  */
145
134
  public abstract createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>;
146
135
 
147
136
  /**
148
- * Create the file at path p with the given mode. Then, open it with the given
149
- * flag.
137
+ * Create the file at `path` with the given mode. Then, open it with the given flag.
150
138
  */
151
139
  public abstract createFileSync(path: string, flag: string, mode: number, cred: Cred): File;
152
140
 
@@ -169,21 +157,16 @@ export abstract class FileSystem {
169
157
  public abstract rmdirSync(path: string, cred: Cred): void;
170
158
  /**
171
159
  * Asynchronous `mkdir`.
172
- * @param mode Mode to make the directory using. Can be ignored if
173
- * the filesystem doesn't support permissions.
160
+ * @param mode Mode to make the directory using.
174
161
  */
175
162
  public abstract mkdir(path: string, mode: number, cred: Cred): Promise<void>;
176
163
  /**
177
164
  * Synchronous `mkdir`.
178
- * @param mode Mode to make the directory using. Can be ignored if
179
- * the filesystem doesn't support permissions.
165
+ * @param mode Mode to make the directory using.
180
166
  */
181
167
  public abstract mkdirSync(path: string, mode: number, cred: Cred): void;
182
168
  /**
183
169
  * Asynchronous `readdir`. Reads the contents of a directory.
184
- *
185
- * The callback gets two arguments `(err, files)` where `files` is an array of
186
- * the names of the files in the directory excluding `'.'` and `'..'`.
187
170
  */
188
171
  public abstract readdir(path: string, cred: Cred): Promise<string[]>;
189
172
  /**
@@ -352,13 +335,11 @@ type AsyncOperation = {
352
335
  /**
353
336
  * Async() implements synchronous methods on an asynchronous file system
354
337
  *
355
- * Implementing classes must define a protected _sync property for the synchronous file system used as a cache.
356
- * by:
357
- *
358
- * - Performing operations over the in-memory copy, while asynchronously pipelining them
359
- * to the backing store.
360
- * - During application loading, the contents of the async file system can be reloaded into
361
- * the synchronous store, if desired.
338
+ * Implementing classes must define `_sync` for the synchronous file system used as a cache.
339
+ * Synchronous methods on an asynchronous FS are implemented by:
340
+ * - Performing operations over the in-memory copy,
341
+ * while asynchronously pipelining them to the backing store.
342
+ * - During loading, the contents of the async file system are eloaded into the synchronous store.
362
343
  *
363
344
  */
364
345
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
package/src/stats.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type * as Node from 'fs';
2
- import { Cred } from './cred.js';
2
+ import type { Cred } from './cred.js';
3
3
  import { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRWXG, S_IRWXO, S_IRWXU } from './emulation/constants.js';
4
4
  import { size_max } from './inode.js';
5
5