@zenfs/core 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -78,9 +78,9 @@ function computeEntries(path) {
78
78
  }
79
79
 
80
80
  const stats = statSync(path);
81
- entries.set('/' + relative(resolvedRoot, path), stats);
82
81
 
83
82
  if (stats.isFile()) {
83
+ entries.set('/' + relative(resolvedRoot, path), stats);
84
84
  if (options.verbose) {
85
85
  console.log(`${color('green', 'file')} ${path}`);
86
86
  }
@@ -90,6 +90,7 @@ function computeEntries(path) {
90
90
  for (const file of readdirSync(path)) {
91
91
  computeEntries(join(path, file));
92
92
  }
93
+ entries.set('/' + relative(resolvedRoot, path), stats);
93
94
  if (options.verbose) {
94
95
  console.log(`${color('bright_green', ' dir')} ${path}`);
95
96
  }
@@ -1,9 +1,14 @@
1
- import { ErrnoError } from '../error.js';
2
1
  import type { Cred } from '../cred.js';
2
+ import { Errno, ErrnoError } from '../error.js';
3
3
  import type { File } from '../file.js';
4
- import type { FileSystem, FileSystemMetadata } from '../filesystem.js';
5
- import { Mutex } from '../mutex.js';
4
+ import type { FileSystemMetadata } from '../filesystem.js';
5
+ import { FileSystem } from '../filesystem.js';
6
6
  import type { Stats } from '../stats.js';
7
+ import type { Backend } from './backend.js';
8
+
9
+ export interface MutexLock extends PromiseWithResolvers<void> {
10
+ [Symbol.dispose](): void;
11
+ }
7
12
 
8
13
  /**
9
14
  * This class serializes access to an underlying async filesystem.
@@ -16,10 +21,81 @@ import type { Stats } from '../stats.js';
16
21
  * @internal
17
22
  */
18
23
  export class LockedFS<FS extends FileSystem> implements FileSystem {
19
- private mutex: Mutex = new Mutex();
20
-
21
24
  constructor(public readonly fs: FS) {}
22
25
 
26
+ /**
27
+ * The current locks
28
+ */
29
+ private locks: Map<string, MutexLock> = new Map();
30
+
31
+ protected addLock(path: string): MutexLock {
32
+ const lock: MutexLock = {
33
+ ...Promise.withResolvers(),
34
+ [Symbol.dispose]: () => {
35
+ this.unlock(path);
36
+ },
37
+ };
38
+ this.locks.set(path, lock);
39
+ return lock;
40
+ }
41
+
42
+ /**
43
+ * Locks `path` asynchronously.
44
+ * If the path is currently locked, waits for it to be unlocked.
45
+ * @internal
46
+ */
47
+ public async lock(path: string): Promise<MutexLock> {
48
+ if (this.locks.has(path)) {
49
+ // Non-null assertion: we already checked locks has path
50
+ await this.locks.get(path)!.promise;
51
+ }
52
+
53
+ return this.addLock(path);
54
+ }
55
+
56
+ /**
57
+ * Locks `path` asynchronously.
58
+ * If the path is currently locked, an error will be thrown
59
+ * @internal
60
+ */
61
+ public lockSync(path: string): MutexLock {
62
+ if (this.locks.has(path)) {
63
+ // Non-null assertion: we already checked locks has path
64
+ throw ErrnoError.With('EBUSY', path, 'lockSync');
65
+ }
66
+
67
+ return this.addLock(path);
68
+ }
69
+
70
+ /**
71
+ * Unlocks a path
72
+ * @param path The path to lock
73
+ * @param noThrow If true, an error will not be thrown if the path is already unlocked
74
+ * @returns Whether the path was unlocked
75
+ * @internal
76
+ */
77
+ public unlock(path: string, noThrow: boolean = false): boolean {
78
+ if (!this.locks.has(path)) {
79
+ if (noThrow) {
80
+ return false;
81
+ }
82
+ throw new ErrnoError(Errno.EPERM, 'Can not unlock an already unlocked path', path);
83
+ }
84
+
85
+ // Non-null assertion: we already checked locks has path
86
+ this.locks.get(path)!.resolve();
87
+ this.locks.delete(path);
88
+ return true;
89
+ }
90
+
91
+ /**
92
+ * Whether `path` is locked
93
+ * @internal
94
+ */
95
+ public isLocked(path: string): boolean {
96
+ return this.locks.has(path);
97
+ }
98
+
23
99
  public async ready(): Promise<void> {
24
100
  await this.fs.ready();
25
101
  }
@@ -32,150 +108,156 @@ export class LockedFS<FS extends FileSystem> implements FileSystem {
32
108
  }
33
109
 
34
110
  public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> {
35
- await this.mutex.lock(oldPath);
111
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
112
+ using _ = await this.lock(oldPath);
36
113
  await this.fs.rename(oldPath, newPath, cred);
37
- this.mutex.unlock(oldPath);
38
114
  }
39
115
 
40
116
  public renameSync(oldPath: string, newPath: string, cred: Cred): void {
41
- if (this.mutex.isLocked(oldPath)) {
42
- throw ErrnoError.With('EBUSY', oldPath, 'rename');
43
- }
117
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
118
+ using _ = this.lockSync(oldPath);
44
119
  return this.fs.renameSync(oldPath, newPath, cred);
45
120
  }
46
121
 
47
122
  public async stat(path: string, cred: Cred): Promise<Stats> {
48
- await this.mutex.lock(path);
49
- const stats = await this.fs.stat(path, cred);
50
- this.mutex.unlock(path);
51
- return stats;
123
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
124
+ using _ = await this.lock(path);
125
+ return await this.fs.stat(path, cred);
52
126
  }
53
127
 
54
128
  public statSync(path: string, cred: Cred): Stats {
55
- if (this.mutex.isLocked(path)) {
56
- throw ErrnoError.With('EBUSY', path, 'stat');
57
- }
129
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
130
+ using _ = this.lockSync(path);
58
131
  return this.fs.statSync(path, cred);
59
132
  }
60
133
 
61
134
  public async openFile(path: string, flag: string, cred: Cred): Promise<File> {
62
- await this.mutex.lock(path);
63
- const fd = await this.fs.openFile(path, flag, cred);
64
- this.mutex.unlock(path);
65
- return fd;
135
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
136
+ using _ = await this.lock(path);
137
+ return await this.fs.openFile(path, flag, cred);
66
138
  }
67
139
 
68
140
  public openFileSync(path: string, flag: string, cred: Cred): File {
69
- if (this.mutex.isLocked(path)) {
70
- throw ErrnoError.With('EBUSY', path, 'openFile');
71
- }
141
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
142
+ using _ = this.lockSync(path);
72
143
  return this.fs.openFileSync(path, flag, cred);
73
144
  }
74
145
 
75
146
  public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> {
76
- await this.mutex.lock(path);
77
- const fd = await this.fs.createFile(path, flag, mode, cred);
78
- this.mutex.unlock(path);
79
- return fd;
147
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
148
+ using _ = await this.lock(path);
149
+ return await this.fs.createFile(path, flag, mode, cred);
80
150
  }
81
151
 
82
152
  public createFileSync(path: string, flag: string, mode: number, cred: Cred): File {
83
- if (this.mutex.isLocked(path)) {
84
- throw ErrnoError.With('EBUSY', path, 'createFile');
85
- }
153
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
154
+ using _ = this.lockSync(path);
86
155
  return this.fs.createFileSync(path, flag, mode, cred);
87
156
  }
88
157
 
89
158
  public async unlink(path: string, cred: Cred): Promise<void> {
90
- await this.mutex.lock(path);
159
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
160
+ using _ = await this.lock(path);
91
161
  await this.fs.unlink(path, cred);
92
- this.mutex.unlock(path);
93
162
  }
94
163
 
95
164
  public unlinkSync(path: string, cred: Cred): void {
96
- if (this.mutex.isLocked(path)) {
97
- throw ErrnoError.With('EBUSY', path, 'unlink');
98
- }
165
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
166
+ using _ = this.lockSync(path);
99
167
  return this.fs.unlinkSync(path, cred);
100
168
  }
101
169
 
102
170
  public async rmdir(path: string, cred: Cred): Promise<void> {
103
- await this.mutex.lock(path);
171
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
172
+ using _ = await this.lock(path);
104
173
  await this.fs.rmdir(path, cred);
105
- this.mutex.unlock(path);
106
174
  }
107
175
 
108
176
  public rmdirSync(path: string, cred: Cred): void {
109
- if (this.mutex.isLocked(path)) {
110
- throw ErrnoError.With('EBUSY', path, 'rmdir');
111
- }
177
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
178
+ using _ = this.lockSync(path);
112
179
  return this.fs.rmdirSync(path, cred);
113
180
  }
114
181
 
115
182
  public async mkdir(path: string, mode: number, cred: Cred): Promise<void> {
116
- await this.mutex.lock(path);
183
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
184
+ using _ = await this.lock(path);
117
185
  await this.fs.mkdir(path, mode, cred);
118
- this.mutex.unlock(path);
119
186
  }
120
187
 
121
188
  public mkdirSync(path: string, mode: number, cred: Cred): void {
122
- if (this.mutex.isLocked(path)) {
123
- throw ErrnoError.With('EBUSY', path, 'mkdir');
124
- }
189
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
190
+ using _ = this.lockSync(path);
125
191
  return this.fs.mkdirSync(path, mode, cred);
126
192
  }
127
193
 
128
194
  public async readdir(path: string, cred: Cred): Promise<string[]> {
129
- await this.mutex.lock(path);
130
- const files = await this.fs.readdir(path, cred);
131
- this.mutex.unlock(path);
132
- return files;
195
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
196
+ using _ = await this.lock(path);
197
+ return await this.fs.readdir(path, cred);
133
198
  }
134
199
 
135
200
  public readdirSync(path: string, cred: Cred): string[] {
136
- if (this.mutex.isLocked(path)) {
137
- throw ErrnoError.With('EBUSY', path, 'readdir');
138
- }
201
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
202
+ using _ = this.lockSync(path);
139
203
  return this.fs.readdirSync(path, cred);
140
204
  }
141
205
 
142
206
  public async exists(path: string, cred: Cred): Promise<boolean> {
143
- await this.mutex.lock(path);
144
- const exists = await this.fs.exists(path, cred);
145
- this.mutex.unlock(path);
146
- return exists;
207
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
208
+ using _ = await this.lock(path);
209
+ return await this.fs.exists(path, cred);
147
210
  }
148
211
 
149
212
  public existsSync(path: string, cred: Cred): boolean {
150
- if (this.mutex.isLocked(path)) {
151
- throw ErrnoError.With('EBUSY', path, 'exists');
152
- }
213
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
214
+ using _ = this.lockSync(path);
153
215
  return this.fs.existsSync(path, cred);
154
216
  }
155
217
 
156
218
  public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> {
157
- await this.mutex.lock(srcpath);
219
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
220
+ using _ = await this.lock(srcpath);
158
221
  await this.fs.link(srcpath, dstpath, cred);
159
- this.mutex.unlock(srcpath);
160
222
  }
161
223
 
162
224
  public linkSync(srcpath: string, dstpath: string, cred: Cred): void {
163
- if (this.mutex.isLocked(srcpath)) {
164
- throw ErrnoError.With('EBUSY', srcpath, 'link');
165
- }
225
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
226
+ using _ = this.lockSync(srcpath);
166
227
  return this.fs.linkSync(srcpath, dstpath, cred);
167
228
  }
168
229
 
169
230
  public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
170
- await this.mutex.lock(path);
231
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
232
+ using _ = await this.lock(path);
171
233
  await this.fs.sync(path, data, stats);
172
- this.mutex.unlock(path);
173
234
  }
174
235
 
175
236
  public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void {
176
- if (this.mutex.isLocked(path)) {
177
- throw ErrnoError.With('EBUSY', path, 'sync');
178
- }
237
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
238
+ using _ = this.lockSync(path);
179
239
  return this.fs.syncSync(path, data, stats);
180
240
  }
181
241
  }
242
+
243
+ export const Locked = {
244
+ name: 'Locked',
245
+ options: {
246
+ fs: {
247
+ type: 'object',
248
+ required: true,
249
+ description: '',
250
+ validator(fs) {
251
+ if (!(fs instanceof FileSystem)) {
252
+ throw new ErrnoError(Errno.EINVAL, 'fs passed to LockedFS must be a FileSystem');
253
+ }
254
+ },
255
+ },
256
+ },
257
+ isAvailable() {
258
+ return true;
259
+ },
260
+ create({ fs }) {
261
+ return new LockedFS(fs);
262
+ },
263
+ } satisfies Backend<LockedFS<FileSystem>, { fs: FileSystem }>;
@@ -86,6 +86,9 @@ export class UnlockedOverlayFS extends FileSystem {
86
86
  public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
87
87
  const cred = stats.cred(0, 0);
88
88
  await this.createParentDirectories(path, cred);
89
+ if (!(await this._writable.exists(path, cred))) {
90
+ await this._writable.createFile(path, 'w', 0o644, cred);
91
+ }
89
92
  await this._writable.sync(path, data, stats);
90
93
  }
91
94
 
@@ -160,7 +163,7 @@ export class UnlockedOverlayFS extends FileSystem {
160
163
  public async stat(path: string, cred: Cred): Promise<Stats> {
161
164
  this.checkInitialized();
162
165
  try {
163
- return this._writable.stat(path, cred);
166
+ return await this._writable.stat(path, cred);
164
167
  } catch (e) {
165
168
  if (this._deletedFiles.has(path)) {
166
169
  throw ErrnoError.With('ENOENT', path, 'stat');
@@ -1,5 +1,6 @@
1
1
  import { ErrnoError } from '../../error.js';
2
2
  import type { Ino } from '../../inode.js';
3
+ import '../../symbol-dispose.js';
3
4
 
4
5
  /**
5
6
  * Represents a key-value store.
@@ -18,6 +18,7 @@ import { dirname, join, parse } from './path.js';
18
18
  import { _statfs, cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
19
19
  import { ReadStream, WriteStream } from './streams.js';
20
20
  export * as constants from './constants.js';
21
+ import '../symbol-dispose.js';
21
22
 
22
23
  export class FileHandle implements promises.FileHandle {
23
24
  /**
@@ -456,12 +457,8 @@ lstat satisfies typeof promises.lstat;
456
457
  * @param len
457
458
  */
458
459
  export async function truncate(path: fs.PathLike, len: number = 0): Promise<void> {
459
- const handle = await open(path, 'r+');
460
- try {
461
- await handle.truncate(len);
462
- } finally {
463
- await handle.close();
464
- }
460
+ await using handle = await open(path, 'r+');
461
+ await handle.truncate(len);
465
462
  }
466
463
  truncate satisfies typeof promises.truncate;
467
464
 
@@ -554,13 +551,8 @@ export async function readFile(
554
551
  _options?: (fs.ObjectEncodingOptions & { flag?: fs.OpenMode }) | BufferEncoding | null
555
552
  ): Promise<Buffer | string> {
556
553
  const options = normalizeOptions(_options, null, 'r', 0o644);
557
- const handle: FileHandle | promises.FileHandle = typeof path == 'object' && 'fd' in path ? path : await open(path as string, options.flag, options.mode);
558
-
559
- try {
560
- return await handle.readFile(options);
561
- } finally {
562
- await handle.close();
563
- }
554
+ await using handle: FileHandle | promises.FileHandle = typeof path == 'object' && 'fd' in path ? path : await open(path as string, options.flag, options.mode);
555
+ return await handle.readFile(options);
564
556
  }
565
557
  readFile satisfies typeof promises.readFile;
566
558
 
@@ -581,16 +573,13 @@ export async function writeFile(
581
573
  _options?: (fs.ObjectEncodingOptions & { mode?: fs.Mode; flag?: fs.OpenMode; flush?: boolean }) | BufferEncoding | null
582
574
  ): Promise<void> {
583
575
  const options = normalizeOptions(_options, 'utf8', 'w+', 0o644);
584
- const handle = path instanceof FileHandle ? path : await open(path.toString(), options.flag, options.mode);
585
- try {
586
- const _data = typeof data == 'string' ? data : data;
587
- if (typeof _data != 'string' && !(_data instanceof Uint8Array)) {
588
- throw new ErrnoError(Errno.EINVAL, 'Iterables and streams not supported', handle.file.path, 'writeFile');
589
- }
590
- await handle.writeFile(_data, options);
591
- } finally {
592
- await handle.close();
576
+ await using handle = path instanceof FileHandle ? path : await open(path.toString(), options.flag, options.mode);
577
+
578
+ const _data = typeof data == 'string' ? data : data;
579
+ if (typeof _data != 'string' && !(_data instanceof Uint8Array)) {
580
+ throw new ErrnoError(Errno.EINVAL, 'Iterables and streams not supported', handle.file.path, 'writeFile');
593
581
  }
582
+ await handle.writeFile(_data, options);
594
583
  }
595
584
  writeFile satisfies typeof promises.writeFile;
596
585
 
@@ -618,13 +607,9 @@ export async function appendFile(
618
607
  throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
619
608
  }
620
609
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
621
- const handle: FileHandle | promises.FileHandle = typeof path == 'object' && 'fd' in path ? path : await open(path as string, options.flag, options.mode);
610
+ await using handle: FileHandle | promises.FileHandle = typeof path == 'object' && 'fd' in path ? path : await open(path as string, options.flag, options.mode);
622
611
 
623
- try {
624
- await handle.appendFile(encodedData, options);
625
- } finally {
626
- await handle.close();
627
- }
612
+ await handle.appendFile(encodedData, options);
628
613
  }
629
614
  appendFile satisfies typeof promises.appendFile;
630
615
 
@@ -775,14 +760,10 @@ export async function readlink(path: fs.PathLike, options: fs.BufferEncodingOpti
775
760
  export async function readlink(path: fs.PathLike, options?: fs.EncodingOption | null): Promise<string>;
776
761
  export async function readlink(path: fs.PathLike, options?: fs.BufferEncodingOption | fs.EncodingOption | string | null): Promise<string | Buffer>;
777
762
  export async function readlink(path: fs.PathLike, options?: fs.BufferEncodingOption | fs.EncodingOption | string | null): Promise<string | Buffer> {
778
- const handle = await _open(normalizePath(path), 'r', 0o644, false);
779
- try {
780
- const value = await handle.readFile();
781
- const encoding = typeof options == 'object' ? options?.encoding : options;
782
- return encoding == 'buffer' ? value : value.toString(encoding! as BufferEncoding);
783
- } finally {
784
- await handle.close();
785
- }
763
+ await using handle = await _open(normalizePath(path), 'r', 0o644, false);
764
+ const value = await handle.readFile();
765
+ const encoding = typeof options == 'object' ? options?.encoding : options;
766
+ return encoding == 'buffer' ? value : value.toString(encoding! as BufferEncoding);
786
767
  }
787
768
  readlink satisfies typeof promises.readlink;
788
769
 
@@ -795,12 +776,8 @@ readlink satisfies typeof promises.readlink;
795
776
  * @param gid
796
777
  */
797
778
  export async function chown(path: fs.PathLike, uid: number, gid: number): Promise<void> {
798
- const handle = await open(path, 'r+');
799
- try {
800
- await handle.chown(uid, gid);
801
- } finally {
802
- await handle.close();
803
- }
779
+ await using handle = await open(path, 'r+');
780
+ await handle.chown(uid, gid);
804
781
  }
805
782
  chown satisfies typeof promises.chown;
806
783
 
@@ -811,12 +788,8 @@ chown satisfies typeof promises.chown;
811
788
  * @param gid
812
789
  */
813
790
  export async function lchown(path: fs.PathLike, uid: number, gid: number): Promise<void> {
814
- const handle: FileHandle = await _open(path, 'r+', 0o644, false);
815
- try {
816
- await handle.chown(uid, gid);
817
- } finally {
818
- await handle.close();
819
- }
791
+ await using handle: FileHandle = await _open(path, 'r+', 0o644, false);
792
+ await handle.chown(uid, gid);
820
793
  }
821
794
  lchown satisfies typeof promises.lchown;
822
795
 
@@ -826,12 +799,8 @@ lchown satisfies typeof promises.lchown;
826
799
  * @param mode
827
800
  */
828
801
  export async function chmod(path: fs.PathLike, mode: fs.Mode): Promise<void> {
829
- const handle = await open(path, 'r+');
830
- try {
831
- await handle.chmod(mode);
832
- } finally {
833
- await handle.close();
834
- }
802
+ await using handle = await open(path, 'r+');
803
+ await handle.chmod(mode);
835
804
  }
836
805
  chmod satisfies typeof promises.chmod;
837
806
 
@@ -841,12 +810,8 @@ chmod satisfies typeof promises.chmod;
841
810
  * @param mode
842
811
  */
843
812
  export async function lchmod(path: fs.PathLike, mode: fs.Mode): Promise<void> {
844
- const handle: FileHandle = await _open(path, 'r+', 0o644, false);
845
- try {
846
- await handle.chmod(mode);
847
- } finally {
848
- await handle.close();
849
- }
813
+ await using handle: FileHandle = await _open(path, 'r+', 0o644, false);
814
+ await handle.chmod(mode);
850
815
  }
851
816
  lchmod satisfies typeof promises.lchmod;
852
817
 
@@ -857,12 +822,8 @@ lchmod satisfies typeof promises.lchmod;
857
822
  * @param mtime
858
823
  */
859
824
  export async function utimes(path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date): Promise<void> {
860
- const handle = await open(path, 'r+');
861
- try {
862
- await handle.utimes(atime, mtime);
863
- } finally {
864
- await handle.close();
865
- }
825
+ await using handle = await open(path, 'r+');
826
+ await handle.utimes(atime, mtime);
866
827
  }
867
828
  utimes satisfies typeof promises.utimes;
868
829
 
@@ -873,12 +834,8 @@ utimes satisfies typeof promises.utimes;
873
834
  * @param mtime
874
835
  */
875
836
  export async function lutimes(path: fs.PathLike, atime: fs.TimeLike, mtime: fs.TimeLike): Promise<void> {
876
- const handle: FileHandle = await _open(path, 'r+', 0o644, false);
877
- try {
878
- await handle.utimes(new Date(atime), new Date(mtime));
879
- } finally {
880
- await handle.close();
881
- }
837
+ await using handle: FileHandle = await _open(path, 'r+', 0o644, false);
838
+ await handle.utimes(new Date(atime), new Date(mtime));
882
839
  }
883
840
  lutimes satisfies typeof promises.lutimes;
884
841
 
@@ -99,12 +99,12 @@ lstatSync satisfies typeof fs.lstatSync;
99
99
  * @param len
100
100
  */
101
101
  export function truncateSync(path: fs.PathLike, len: number | null = 0): void {
102
- const fd = openSync(path, 'r+');
103
- try {
104
- ftruncateSync(fd, len);
105
- } finally {
106
- closeSync(fd);
102
+ using file = _openSync(path, 'r+');
103
+ len ||= 0;
104
+ if (len < 0) {
105
+ throw new ErrnoError(Errno.EINVAL);
107
106
  }
107
+ file.truncateSync(len);
108
108
  }
109
109
  truncateSync satisfies typeof fs.truncateSync;
110
110
 
@@ -194,17 +194,13 @@ export function lopenSync(path: fs.PathLike, flag: string, mode?: fs.Mode | null
194
194
  */
195
195
  function _readFileSync(fname: string, flag: string, resolveSymlinks: boolean): Uint8Array {
196
196
  // Get file.
197
- const file = _openSync(fname, flag, 0o644, resolveSymlinks);
198
- try {
199
- const stat = file.statSync();
200
- // Allocate buffer.
201
- const data = new Uint8Array(stat.size);
202
- file.readSync(data, 0, stat.size, 0);
203
- file.closeSync();
204
- return data;
205
- } finally {
206
- file.closeSync();
207
- }
197
+ using file = _openSync(fname, flag, 0o644, resolveSymlinks);
198
+ const stat = file.statSync();
199
+ // Allocate buffer.
200
+ const data = new Uint8Array(stat.size);
201
+ file.readSync(data, 0, stat.size, 0);
202
+ file.closeSync();
203
+ return data;
208
204
  }
209
205
 
210
206
  /**
@@ -255,12 +251,8 @@ export function writeFileSync(path: fs.PathOrFileDescriptor, data: FileContents,
255
251
  if (!encodedData) {
256
252
  throw new ErrnoError(Errno.EINVAL, 'Data not specified');
257
253
  }
258
- const file = _openSync(typeof path == 'number' ? fd2file(path).path! : path.toString(), flag, options.mode, true);
259
- try {
260
- file.writeSync(encodedData, 0, encodedData.byteLength, 0);
261
- } finally {
262
- file.closeSync();
263
- }
254
+ using file = _openSync(typeof path == 'number' ? fd2file(path).path! : path.toString(), flag, options.mode, true);
255
+ file.writeSync(encodedData, 0, encodedData.byteLength, 0);
264
256
  }
265
257
  writeFileSync satisfies typeof fs.writeFileSync;
266
258
 
@@ -285,12 +277,8 @@ export function appendFileSync(filename: fs.PathOrFileDescriptor, data: FileCont
285
277
  throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
286
278
  }
287
279
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
288
- const file = _openSync(typeof filename == 'number' ? fd2file(filename).path! : filename.toString(), flag, options.mode, true);
289
- try {
290
- file.writeSync(encodedData, 0, encodedData.byteLength);
291
- } finally {
292
- file.closeSync();
293
- }
280
+ using file = _openSync(typeof filename == 'number' ? fd2file(filename).path! : filename.toString(), flag, options.mode, true);
281
+ file.writeSync(encodedData, 0, encodedData.byteLength);
294
282
  }
295
283
  appendFileSync satisfies typeof fs.appendFileSync;
296
284
 
package/src/file.ts CHANGED
@@ -4,6 +4,7 @@ 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';
7
+ import './symbol-dispose.js';
7
8
 
8
9
  /*
9
10
  Typescript does not include a type declaration for resizable array buffers.
package/src/filesystem.ts CHANGED
@@ -463,16 +463,11 @@ export function Async<T extends typeof FileSystem>(
463
463
  await this.crossCopy(join(path, file));
464
464
  }
465
465
  } else {
466
- const asyncFile = await this.openFile(path, parseFlag('r'), rootCred);
467
- const syncFile = this._sync.createFileSync(path, parseFlag('w'), stats.mode, stats.cred());
468
- try {
469
- const buffer = new Uint8Array(stats.size);
470
- await asyncFile.read(buffer);
471
- syncFile.writeSync(buffer, 0, stats.size);
472
- } finally {
473
- await asyncFile.close();
474
- syncFile.closeSync();
475
- }
466
+ await using asyncFile = await this.openFile(path, parseFlag('r'), rootCred);
467
+ using syncFile = this._sync.createFileSync(path, parseFlag('w'), stats.mode, stats.cred());
468
+ const buffer = new Uint8Array(stats.size);
469
+ await asyncFile.read(buffer);
470
+ syncFile.writeSync(buffer, 0, stats.size);
476
471
  }
477
472
  }
478
473
 
package/src/index.ts CHANGED
@@ -14,7 +14,6 @@ export * from './cred.js';
14
14
  export * from './file.js';
15
15
  export * from './filesystem.js';
16
16
  export * from './inode.js';
17
- export * from './mutex.js';
18
17
  export * from './stats.js';
19
18
  export * from './utils.js';
20
19