@zenfs/core 1.2.9 → 1.3.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.
Files changed (57) hide show
  1. package/dist/backends/fetch.js +1 -1
  2. package/dist/backends/memory.d.ts +1 -2
  3. package/dist/backends/overlay.js +2 -3
  4. package/dist/backends/port/fs.d.ts +2 -15
  5. package/dist/backends/store/fs.d.ts +17 -28
  6. package/dist/backends/store/fs.js +169 -191
  7. package/dist/backends/store/simple.d.ts +20 -21
  8. package/dist/backends/store/simple.js +24 -24
  9. package/dist/backends/store/store.d.ts +22 -23
  10. package/dist/backends/store/store.js +6 -6
  11. package/dist/config.d.ts +8 -0
  12. package/dist/config.js +2 -1
  13. package/dist/devices.d.ts +2 -3
  14. package/dist/emulation/cache.d.ts +40 -31
  15. package/dist/emulation/cache.js +62 -53
  16. package/dist/emulation/index.d.ts +1 -1
  17. package/dist/emulation/index.js +1 -1
  18. package/dist/emulation/promises.js +18 -17
  19. package/dist/emulation/shared.d.ts +6 -0
  20. package/dist/emulation/shared.js +13 -1
  21. package/dist/emulation/sync.d.ts +1 -1
  22. package/dist/emulation/sync.js +16 -15
  23. package/dist/file.d.ts +3 -15
  24. package/dist/file.js +7 -19
  25. package/dist/inode.d.ts +4 -13
  26. package/dist/inode.js +22 -29
  27. package/dist/mixins/async.d.ts +15 -10
  28. package/dist/mixins/async.js +3 -1
  29. package/dist/stats.js +30 -5
  30. package/dist/utils.d.ts +5 -7
  31. package/dist/utils.js +11 -20
  32. package/package.json +1 -1
  33. package/src/backends/fetch.ts +1 -1
  34. package/src/backends/memory.ts +1 -2
  35. package/src/backends/overlay.ts +2 -2
  36. package/src/backends/store/fs.ts +187 -220
  37. package/src/backends/store/simple.ts +36 -37
  38. package/src/backends/store/store.ts +25 -26
  39. package/src/config.ts +11 -1
  40. package/src/devices.ts +2 -3
  41. package/src/emulation/cache.ts +68 -60
  42. package/src/emulation/index.ts +1 -1
  43. package/src/emulation/promises.ts +20 -19
  44. package/src/emulation/shared.ts +13 -1
  45. package/src/emulation/sync.ts +16 -15
  46. package/src/file.ts +9 -21
  47. package/src/inode.ts +10 -31
  48. package/src/mixins/async.ts +27 -24
  49. package/src/stats.ts +47 -5
  50. package/src/utils.ts +11 -23
  51. package/tests/fs/dir.test.ts +21 -31
  52. package/tests/fs/directory.test.ts +6 -4
  53. package/tests/fs/links.test.ts +9 -2
  54. package/tests/fs/permissions.test.ts +2 -2
  55. package/tests/fs/stat.test.ts +42 -0
  56. package/tests/fs/times.test.ts +28 -28
  57. package/tests/setup/cow+fetch.ts +4 -2
@@ -106,7 +106,7 @@ export function unlinkSync(path: fs.PathLike): void {
106
106
  path = normalizePath(path);
107
107
  const { fs, path: resolved } = resolveMount(path);
108
108
  try {
109
- if (config.checkAccess && !(cache.getStatsSync(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
109
+ if (config.checkAccess && !(cache.stats.getSync(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
110
110
  throw ErrnoError.With('EACCES', resolved, 'unlink');
111
111
  }
112
112
  fs.unlinkSync(resolved);
@@ -243,10 +243,10 @@ writeFileSync satisfies typeof fs.writeFileSync;
243
243
  * Asynchronously append data to a file, creating the file if it not yet exists.
244
244
  * @option encoding Defaults to `'utf8'`.
245
245
  * @option mode Defaults to `0644`.
246
- * @option flag Defaults to `'a'`.
246
+ * @option flag Defaults to `'a+'`.
247
247
  */
248
248
  export function appendFileSync(filename: fs.PathOrFileDescriptor, data: FileContents, _options: fs.WriteFileOptions = {}): void {
249
- const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
249
+ const options = normalizeOptions(_options, 'utf8', 'a+', 0o644);
250
250
  const flag = parseFlag(options.flag);
251
251
  if (!isAppendable(flag)) {
252
252
  throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.');
@@ -386,7 +386,7 @@ export function rmdirSync(path: fs.PathLike): void {
386
386
  path = normalizePath(path);
387
387
  const { fs, path: resolved } = resolveMount(realpathSync(path));
388
388
  try {
389
- const stats = cache.getStatsSync(path) || fs.statSync(resolved);
389
+ const stats = cache.stats.getSync(path) || fs.statSync(resolved);
390
390
  if (!stats.isDirectory()) {
391
391
  throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
392
392
  }
@@ -459,8 +459,8 @@ export function readdirSync(
459
459
  const { fs, path: resolved } = resolveMount(realpathSync(path));
460
460
  let entries: string[];
461
461
  try {
462
- const stats = cache.getStatsSync(path) || fs.statSync(resolved);
463
- cache.setStatsSync(path, stats);
462
+ const stats = cache.stats.getSync(path) || fs.statSync(resolved);
463
+ cache.stats.setSync(path, stats);
464
464
  if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
465
465
  throw ErrnoError.With('EACCES', resolved, 'readdir');
466
466
  }
@@ -475,8 +475,8 @@ export function readdirSync(
475
475
  // Iterate over entries and handle recursive case if needed
476
476
  const values: (string | Dirent | Buffer)[] = [];
477
477
  for (const entry of entries) {
478
- const entryStat = cache.getStatsSync(join(path, entry)) || fs.statSync(join(resolved, entry));
479
- cache.setStatsSync(join(path, entry), entryStat);
478
+ const entryStat = cache.stats.getSync(join(path, entry)) || fs.statSync(join(resolved, entry));
479
+ cache.stats.setSync(join(path, entry), entryStat);
480
480
 
481
481
  if (options?.withFileTypes) {
482
482
  values.push(new Dirent(entry, entryStat));
@@ -500,7 +500,7 @@ export function readdirSync(
500
500
  }
501
501
 
502
502
  if (!options?._isIndirect) {
503
- cache.clearStatsSync();
503
+ cache.stats.clearSync();
504
504
  }
505
505
  return values as string[] | Dirent[] | Buffer[];
506
506
  }
@@ -622,7 +622,7 @@ export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption): st
622
622
  export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs.BufferEncodingOption): string | Buffer {
623
623
  path = normalizePath(path);
624
624
  const { base, dir } = parse(path);
625
- const lpath = join(dir == '/' ? '/' : realpathSync(dir), base);
625
+ const lpath = join(dir == '/' ? '/' : cache.paths.getSync(dir) || realpathSync(dir), base);
626
626
  const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath);
627
627
 
628
628
  try {
@@ -631,7 +631,8 @@ export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs
631
631
  return lpath;
632
632
  }
633
633
 
634
- return realpathSync(mountPoint + readlinkSync(lpath, options).toString());
634
+ const target = mountPoint + readlinkSync(lpath, options).toString();
635
+ return cache.paths.getSync(target) || realpathSync(target);
635
636
  } catch (e) {
636
637
  if ((e as ErrnoError).code == 'ENOENT') {
637
638
  return path;
@@ -658,7 +659,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
658
659
 
659
660
  let stats: Stats | undefined;
660
661
  try {
661
- stats = cache.getStatsSync(path) || statSync(path);
662
+ stats = cache.stats.getSync(path) || statSync(path);
662
663
  } catch (error) {
663
664
  if ((error as ErrnoError).code != 'ENOENT' || !options?.force) throw error;
664
665
  }
@@ -667,7 +668,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
667
668
  return;
668
669
  }
669
670
 
670
- cache.setStatsSync(path, stats);
671
+ cache.stats.setSync(path, stats);
671
672
 
672
673
  switch (stats.mode & constants.S_IFMT) {
673
674
  case constants.S_IFDIR:
@@ -688,12 +689,12 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
688
689
  case constants.S_IFIFO:
689
690
  case constants.S_IFSOCK:
690
691
  default:
691
- cache.clearStatsSync();
692
+ cache.stats.clearSync();
692
693
  throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
693
694
  }
694
695
 
695
696
  if (!options?._isIndirect) {
696
- cache.clearStatsSync();
697
+ cache.stats.clearSync();
697
698
  }
698
699
  }
699
700
  rmSync satisfies typeof fs.rmSync;
package/src/file.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { FileReadResult } from 'node:fs/promises';
2
- import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT, size_max } from './emulation/constants.js';
3
2
  import { config } from './emulation/config.js';
3
+ import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT, size_max } from './emulation/constants.js';
4
4
  import { Errno, ErrnoError } from './error.js';
5
5
  import type { FileSystem } from './filesystem.js';
6
6
  import './polyfills.js';
@@ -145,7 +145,7 @@ export function isExclusive(flag: string): boolean {
145
145
  return flag.indexOf('x') !== -1;
146
146
  }
147
147
 
148
- export abstract class File {
148
+ export abstract class File<FS extends FileSystem = FileSystem> {
149
149
  public constructor(
150
150
  /**
151
151
  * @internal
@@ -166,12 +166,12 @@ export abstract class File {
166
166
  public abstract close(): Promise<void>;
167
167
  public abstract closeSync(): void;
168
168
 
169
- public [Symbol.asyncDispose](): Promise<void> {
170
- return this.close();
169
+ public async [Symbol.asyncDispose](): Promise<void> {
170
+ await this.close();
171
171
  }
172
172
 
173
173
  public [Symbol.dispose](): void {
174
- return this.closeSync();
174
+ this.closeSync();
175
175
  }
176
176
 
177
177
  public abstract truncate(len: number): Promise<void>;
@@ -269,7 +269,7 @@ export abstract class File {
269
269
  * An implementation of `File` that operates completely in-memory.
270
270
  * `PreloadFile`s are backed by a `Uint8Array`.
271
271
  */
272
- export class PreloadFile<FS extends FileSystem> extends File {
272
+ export class PreloadFile<FS extends FileSystem> extends File<FS> {
273
273
  /**
274
274
  * Current position
275
275
  */
@@ -290,11 +290,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
290
290
  * Note that, if contents is specified, it will be mutated by the file.
291
291
  */
292
292
  public constructor(
293
- /**
294
- * The file system that created the file.
295
- * @internal
296
- */
297
- public fs: FS,
293
+ fs: FS,
298
294
  path: string,
299
295
  public readonly flag: string,
300
296
  public readonly stats: Stats,
@@ -431,12 +427,12 @@ export class PreloadFile<FS extends FileSystem> extends File {
431
427
  if (length > this._buffer.length) {
432
428
  const data = new Uint8Array(length - this._buffer.length);
433
429
  // Write will set stats.size and handle syncing.
434
- this.writeSync(data, 0, data.length, this._buffer.length);
430
+ this._write(data, 0, data.length, this._buffer.length);
435
431
  return;
436
432
  }
437
433
  this.stats.size = length;
438
434
  // Truncate.
439
- this._buffer = this._buffer.slice(0, length);
435
+ this._buffer = length ? this._buffer.slice(0, length) : new Uint8Array();
440
436
  }
441
437
 
442
438
  public async truncate(length: number): Promise<void> {
@@ -645,14 +641,6 @@ export class PreloadFile<FS extends FileSystem> extends File {
645
641
  this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
646
642
  this.syncSync();
647
643
  }
648
-
649
- public async [Symbol.asyncDispose]() {
650
- await this.close();
651
- }
652
-
653
- public [Symbol.dispose]() {
654
- this.closeSync();
655
- }
656
644
  }
657
645
 
658
646
  /**
package/src/inode.ts CHANGED
@@ -1,11 +1,6 @@
1
+ import { deserialize, sizeof, struct, types as t } from 'utilium';
1
2
  import { Stats, type StatsLike } from './stats.js';
2
- import { types as t, struct, sizeof, serialize, deserialize } from 'utilium';
3
-
4
- /**
5
- * Alias for an ino.
6
- * This will be helpful if in the future inode numbers/IDs are changed to strings or numbers.
7
- */
8
- export type Ino = bigint;
3
+ import { randomBigInt } from './utils.js';
9
4
 
10
5
  /**
11
6
  * Room inode
@@ -13,31 +8,13 @@ export type Ino = bigint;
13
8
  */
14
9
  export const rootIno = 0n;
15
10
 
16
- /**
17
- * Generates a random 32 bit integer, then converts to a hex string
18
- */
19
- function _random() {
20
- return Math.round(Math.random() * 2 ** 32).toString(16);
21
- }
22
-
23
- /**
24
- * Generate a random ino
25
- * @internal
26
- */
27
- export function randomIno(): Ino {
28
- return BigInt('0x' + _random() + _random());
29
- }
30
-
31
11
  /**
32
12
  * Generic inode definition that can easily be serialized.
13
+ * @internal
33
14
  */
34
15
  @struct()
35
16
  export class Inode implements StatsLike {
36
- public get data(): Uint8Array {
37
- return serialize(this);
38
- }
39
-
40
- public constructor(buffer?: ArrayBufferLike) {
17
+ public constructor(buffer?: ArrayBufferLike | ArrayBufferView) {
41
18
  if (buffer) {
42
19
  if (buffer.byteLength < sizeof(Inode)) {
43
20
  throw new RangeError(`Can not create an inode from a buffer less than ${sizeof(Inode)} bytes`);
@@ -48,7 +25,8 @@ export class Inode implements StatsLike {
48
25
  }
49
26
 
50
27
  // set defaults on a fresh inode
51
- this.ino = randomIno();
28
+ this.ino = randomBigInt();
29
+ this.data = randomBigInt();
52
30
  this.nlink = 1;
53
31
  this.size = 4096;
54
32
  const now = Date.now();
@@ -58,7 +36,7 @@ export class Inode implements StatsLike {
58
36
  this.birthtimeMs = now;
59
37
  }
60
38
 
61
- @t.uint64 public ino!: Ino;
39
+ @t.uint64 public data!: bigint;
62
40
  @t.uint32 public size!: number;
63
41
  @t.uint16 public mode!: number;
64
42
  @t.uint32 public nlink!: number;
@@ -68,6 +46,7 @@ export class Inode implements StatsLike {
68
46
  @t.float64 public birthtimeMs!: number;
69
47
  @t.float64 public mtimeMs!: number;
70
48
  @t.float64 public ctimeMs!: number;
49
+ @t.uint64 public ino!: bigint;
71
50
 
72
51
  /**
73
52
  * Handy function that converts the Inode to a Node Stats object.
@@ -108,8 +87,8 @@ export class Inode implements StatsLike {
108
87
  hasChanged = true;
109
88
  }
110
89
 
111
- if (this.uid !== stats.uid) {
112
- this.uid = stats.uid;
90
+ if (this.gid !== stats.gid) {
91
+ this.gid = stats.gid;
113
92
  hasChanged = true;
114
93
  }
115
94
 
@@ -12,6 +12,28 @@ export type AsyncOperation = {
12
12
  [K in keyof AsyncFSMethods]: [K, ...Parameters<FileSystem[K]>];
13
13
  }[keyof AsyncFSMethods];
14
14
 
15
+ /**
16
+ * @internal
17
+ */
18
+ export interface Async {
19
+ /**
20
+ * @internal @protected
21
+ */
22
+ _sync?: FileSystem;
23
+ queueDone(): Promise<void>;
24
+ ready(): Promise<void>;
25
+ renameSync(oldPath: string, newPath: string): void;
26
+ statSync(path: string): Stats;
27
+ createFileSync(path: string, flag: string, mode: number): File;
28
+ openFileSync(path: string, flag: string): File;
29
+ unlinkSync(path: string): void;
30
+ rmdirSync(path: string): void;
31
+ mkdirSync(path: string, mode: number): void;
32
+ readdirSync(path: string): string[];
33
+ linkSync(srcpath: string, dstpath: string): void;
34
+ syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
35
+ }
36
+
15
37
  /**
16
38
  * Async() implements synchronous methods on an asynchronous file system
17
39
  *
@@ -22,29 +44,7 @@ export type AsyncOperation = {
22
44
  * During loading, the contents of the async file system are preloaded into the synchronous store.
23
45
  *
24
46
  */
25
- export function Async<T extends typeof FileSystem>(
26
- FS: T
27
- ): Mixin<
28
- T,
29
- {
30
- /**
31
- * @internal @protected
32
- */
33
- _sync?: FileSystem;
34
- queueDone(): Promise<void>;
35
- ready(): Promise<void>;
36
- renameSync(oldPath: string, newPath: string): void;
37
- statSync(path: string): Stats;
38
- createFileSync(path: string, flag: string, mode: number): File;
39
- openFileSync(path: string, flag: string): File;
40
- unlinkSync(path: string): void;
41
- rmdirSync(path: string): void;
42
- mkdirSync(path: string, mode: number): void;
43
- readdirSync(path: string): string[];
44
- linkSync(srcpath: string, dstpath: string): void;
45
- syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
46
- }
47
- > {
47
+ export function Async<const T extends typeof FileSystem>(FS: T): Mixin<T, Async> {
48
48
  abstract class AsyncFS extends FS {
49
49
  /**
50
50
  * Queue of pending asynchronous operations.
@@ -67,6 +67,7 @@ export function Async<T extends typeof FileSystem>(
67
67
 
68
68
  public async ready(): Promise<void> {
69
69
  await super.ready();
70
+ await this.queueDone();
70
71
  if (this._isInitialized || this._disableSync) {
71
72
  return;
72
73
  }
@@ -128,7 +129,7 @@ export function Async<T extends typeof FileSystem>(
128
129
 
129
130
  public openFileSync(path: string, flag: string): PreloadFile<this> {
130
131
  this.checkSync(path, 'openFile');
131
- const file = this._sync.openFileSync(path, flag);
132
+ const file = this._sync.openFileSync(path, flag + '+');
132
133
  const stats = file.statSync();
133
134
  const buffer = new Uint8Array(stats.size);
134
135
  file.readSync(buffer);
@@ -225,3 +226,5 @@ export function Async<T extends typeof FileSystem>(
225
226
 
226
227
  return AsyncFS;
227
228
  }
229
+
230
+ export function asyncPatch<T extends typeof FileSystem>(fs: Mixin<T, Async>) {}
package/src/stats.ts CHANGED
@@ -1,6 +1,28 @@
1
1
  import type * as Node from 'node:fs';
2
2
  import { credentials, type Credentials } from './credentials.js';
3
- import { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRWXG, S_IRWXO, S_IRWXU, size_max } from './emulation/constants.js';
3
+ import {
4
+ R_OK,
5
+ S_IFBLK,
6
+ S_IFCHR,
7
+ S_IFDIR,
8
+ S_IFIFO,
9
+ S_IFLNK,
10
+ S_IFMT,
11
+ S_IFREG,
12
+ S_IFSOCK,
13
+ S_IRGRP,
14
+ S_IROTH,
15
+ S_IRUSR,
16
+ S_IWGRP,
17
+ S_IWOTH,
18
+ S_IWUSR,
19
+ S_IXGRP,
20
+ S_IXOTH,
21
+ S_IXUSR,
22
+ size_max,
23
+ W_OK,
24
+ X_OK,
25
+ } from './emulation/constants.js';
4
26
 
5
27
  /**
6
28
  * Indicates the type of a file. Applied to 'mode'.
@@ -222,13 +244,33 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
222
244
  */
223
245
  public hasAccess(mode: number): boolean {
224
246
  if (credentials.euid === 0 || credentials.egid === 0) {
225
- //Running as root
247
+ // Running as root
226
248
  return true;
227
249
  }
228
250
 
229
- // Mask for
230
- const adjusted = (credentials.uid == this.uid ? S_IRWXU : 0) | (credentials.gid == this.gid ? S_IRWXG : 0) | S_IRWXO;
231
- return (mode & this.mode & adjusted) == mode;
251
+ let perm = 0;
252
+
253
+ // Owner permissions
254
+ if (credentials.uid === this.uid) {
255
+ if (this.mode & S_IRUSR) perm |= R_OK;
256
+ if (this.mode & S_IWUSR) perm |= W_OK;
257
+ if (this.mode & S_IXUSR) perm |= X_OK;
258
+ }
259
+
260
+ // Group permissions
261
+ if (credentials.gid === this.gid) {
262
+ if (this.mode & S_IRGRP) perm |= R_OK;
263
+ if (this.mode & S_IWGRP) perm |= W_OK;
264
+ if (this.mode & S_IXGRP) perm |= X_OK;
265
+ }
266
+
267
+ // Others permissions
268
+ if (this.mode & S_IROTH) perm |= R_OK;
269
+ if (this.mode & S_IWOTH) perm |= W_OK;
270
+ if (this.mode & S_IXOTH) perm |= X_OK;
271
+
272
+ // Perform the access check
273
+ return (perm & mode) === mode;
232
274
  }
233
275
 
234
276
  /**
package/src/utils.ts CHANGED
@@ -182,22 +182,6 @@ export function encodeDirListing(data: Record<string, bigint>): Uint8Array {
182
182
 
183
183
  export type Callback<Args extends unknown[] = []> = (e?: ErrnoError, ...args: OptionalTuple<Args>) => unknown;
184
184
 
185
- /**
186
- * converts Date or number to a integer UNIX timestamp
187
- * Grabbed from NodeJS sources (lib/fs.js)
188
- *
189
- * @internal
190
- */
191
- export function _toUnixTimestamp(time: Date | number): number {
192
- if (typeof time === 'number') {
193
- return Math.floor(time);
194
- }
195
- if (time instanceof Date) {
196
- return Math.floor(time.getTime() / 1000);
197
- }
198
- throw new Error('Cannot parse time');
199
- }
200
-
201
185
  /**
202
186
  * Normalizes a mode
203
187
  * @internal
@@ -230,15 +214,11 @@ export function normalizeTime(time: string | number | Date): Date {
230
214
  return time;
231
215
  }
232
216
 
233
- if (typeof time == 'number') {
234
- return new Date(time * 1000);
235
- }
236
-
237
- if (typeof time == 'string') {
217
+ try {
238
218
  return new Date(time);
219
+ } catch {
220
+ throw new ErrnoError(Errno.EINVAL, 'Invalid time.');
239
221
  }
240
-
241
- throw new ErrnoError(Errno.EINVAL, 'Invalid time.');
242
222
  }
243
223
 
244
224
  /**
@@ -286,3 +266,11 @@ export function normalizeOptions(
286
266
  }
287
267
 
288
268
  export type Concrete<T extends ClassLike> = Pick<T, keyof T> & (new (...args: any[]) => InstanceType<T>);
269
+
270
+ /**
271
+ * Generate a random ino
272
+ * @internal
273
+ */
274
+ export function randomBigInt(): bigint {
275
+ return crypto.getRandomValues(new BigUint64Array(1))[0];
276
+ }
@@ -3,18 +3,20 @@ import { suite, test } from 'node:test';
3
3
  import { fs } from '../common.js';
4
4
 
5
5
  const testFile = 'test-file.txt';
6
- await fs.promises.writeFile(testFile, 'Sample content');
7
- await fs.promises.mkdir('test-directory');
8
- await fs.promises.symlink(testFile, 'test-symlink');
6
+ fs.writeFileSync(testFile, 'Sample content');
7
+ fs.mkdirSync('test-directory');
8
+ fs.symlinkSync(testFile, 'test-symlink');
9
9
  const testDirPath = 'test-dir';
10
10
  const testFiles = ['file1.txt', 'file2.txt'];
11
- await fs.promises.mkdir(testDirPath);
11
+ fs.mkdirSync(testDirPath);
12
12
  for (const file of testFiles) {
13
- await fs.promises.writeFile(`${testDirPath}/${file}`, 'Sample content');
13
+ fs.writeFileSync(`${testDirPath}/${file}`, 'Sample content');
14
14
  }
15
15
 
16
+ await fs._synced();
17
+
16
18
  suite('Dirent', () => {
17
- test('Dirent name and parentPath getters', async () => {
19
+ test('name and parentPath getters', async () => {
18
20
  const stats = await fs.promises.lstat(testFile);
19
21
  const dirent = new fs.Dirent(testFile, stats);
20
22
 
@@ -22,7 +24,7 @@ suite('Dirent', () => {
22
24
  assert.equal(dirent.parentPath, testFile);
23
25
  });
24
26
 
25
- test('Dirent.isFile', async () => {
27
+ test('isFile', async () => {
26
28
  const fileStats = await fs.promises.lstat(testFile);
27
29
  const fileDirent = new fs.Dirent(testFile, fileStats);
28
30
 
@@ -30,7 +32,7 @@ suite('Dirent', () => {
30
32
  assert(!fileDirent.isDirectory());
31
33
  });
32
34
 
33
- test('Dirent.isDirectory', async () => {
35
+ test('isDirectory', async () => {
34
36
  const dirStats = await fs.promises.lstat('test-directory');
35
37
  const dirDirent = new fs.Dirent('test-directory', dirStats);
36
38
 
@@ -38,14 +40,14 @@ suite('Dirent', () => {
38
40
  assert(dirDirent.isDirectory());
39
41
  });
40
42
 
41
- test('Dirent.isSymbolicLink', async () => {
43
+ test('isSymbolicLink', async () => {
42
44
  const symlinkStats = await fs.promises.lstat('test-symlink');
43
45
  const symlinkDirent = new fs.Dirent('test-symlink', symlinkStats);
44
46
 
45
47
  assert(symlinkDirent.isSymbolicLink());
46
48
  });
47
49
 
48
- test('Dirent other methods return false', async () => {
50
+ test('other methods return false', async () => {
49
51
  const fileStats = await fs.promises.lstat(testFile);
50
52
  const fileDirent = new fs.Dirent(testFile, fileStats);
51
53
 
@@ -56,7 +58,7 @@ suite('Dirent', () => {
56
58
  });
57
59
 
58
60
  suite('Dir', () => {
59
- test('Dir read() method (Promise varient)', async () => {
61
+ test('read()', async () => {
60
62
  const dir = new fs.Dir(testDirPath);
61
63
 
62
64
  const dirent1 = await dir.read();
@@ -73,19 +75,7 @@ suite('Dir', () => {
73
75
  await dir.close();
74
76
  });
75
77
 
76
- test('Dir read() method (Callback varient)', (_, done) => {
77
- const dir = new fs.Dir(testDirPath);
78
- dir.read((err, dirent) => {
79
- assert.strictEqual(err, undefined);
80
- assert.notEqual(dirent, undefined);
81
- assert(dirent instanceof fs.Dirent);
82
- assert(testFiles.includes(dirent?.name));
83
- dir.closeSync();
84
- done();
85
- });
86
- });
87
-
88
- test('Dir readSync() method', () => {
78
+ test('readSync()', () => {
89
79
  const dir = new fs.Dir(testDirPath);
90
80
 
91
81
  const dirent1 = dir.readSync();
@@ -102,19 +92,19 @@ suite('Dir', () => {
102
92
  dir.closeSync();
103
93
  });
104
94
 
105
- test('Dir close() method (Promise version)', async () => {
95
+ test('close()', async () => {
106
96
  const dir = new fs.Dir(testDirPath);
107
97
  await dir.close();
108
98
  rejects(dir.read(), 'Can not use closed Dir');
109
99
  });
110
100
 
111
- test('Dir closeSync() method', () => {
101
+ test('closeSync()', () => {
112
102
  const dir = new fs.Dir(testDirPath);
113
103
  dir.closeSync();
114
104
  assert.throws(() => dir.readSync(), 'Can not use closed Dir');
115
105
  });
116
106
 
117
- test('Dir asynchronous iteration', async () => {
107
+ test('asynchronous iteration', async () => {
118
108
  const dir = new fs.Dir(testDirPath);
119
109
  const dirents: fs.Dirent[] = [];
120
110
 
@@ -128,26 +118,26 @@ suite('Dir', () => {
128
118
  assert(testFiles.includes(dirents[1].name));
129
119
  });
130
120
 
131
- test('Dir read after directory is closed', async () => {
121
+ test('read after directory is closed', async () => {
132
122
  const dir = new fs.Dir(testDirPath);
133
123
  await dir.close();
134
124
  await assert.rejects(dir.read(), 'Can not use closed Dir');
135
125
  });
136
126
 
137
- test('Dir readSync after directory is closed', () => {
127
+ test('readSync after directory is closed', () => {
138
128
  const dir = new fs.Dir(testDirPath);
139
129
  dir.closeSync();
140
130
  assert.throws(() => dir.readSync(), 'Can not use closed Dir');
141
131
  });
142
132
 
143
- test('Dir close multiple times', async () => {
133
+ test('close multiple times', async () => {
144
134
  const dir = new fs.Dir(testDirPath);
145
135
  await dir.close();
146
136
  await dir.close(); // Should not throw an error
147
137
  assert(dir['closed']);
148
138
  });
149
139
 
150
- test('Dir closeSync multiple times', () => {
140
+ test('closeSync multiple times', () => {
151
141
  const dir = new fs.Dir(testDirPath);
152
142
  dir.closeSync();
153
143
  dir.closeSync(); // Should not throw an error
@@ -7,17 +7,19 @@ const testDir = 'test-dir';
7
7
  const testFiles = ['file1.txt', 'file2.txt', 'file3.txt'];
8
8
  const testDirectories = ['subdir1', 'subdir2'];
9
9
 
10
- await fs.promises.mkdir(testDir);
10
+ fs.mkdirSync(testDir);
11
11
  for (const file of testFiles) {
12
- await fs.promises.writeFile(`${testDir}/${file}`, 'Sample content');
12
+ fs.writeFileSync(`${testDir}/${file}`, 'Sample content');
13
13
  }
14
14
  for (const dir of testDirectories) {
15
- await fs.promises.mkdir(`${testDir}/${dir}`);
15
+ fs.mkdirSync(`${testDir}/${dir}`);
16
16
  for (const file of ['file4.txt', 'file5.txt']) {
17
- await fs.promises.writeFile(`${testDir}/${dir}/${file}`, 'Sample content');
17
+ fs.writeFileSync(`${testDir}/${dir}/${file}`, 'Sample content');
18
18
  }
19
19
  }
20
20
 
21
+ await fs._synced();
22
+
21
23
  suite('Directories', () => {
22
24
  test('mkdir', async () => {
23
25
  await fs.promises.mkdir('/one', 0o755);
@@ -2,6 +2,7 @@ import assert from 'node:assert';
2
2
  import { suite, test } from 'node:test';
3
3
  import { join } from '../../dist/emulation/path.js';
4
4
  import { fs } from '../common.js';
5
+ import type { ErrnoError } from '../../dist/error.js';
5
6
 
6
7
  suite('Links', () => {
7
8
  const target = '/a1.js',
@@ -28,8 +29,14 @@ suite('Links', () => {
28
29
  assert(await fs.promises.exists(target));
29
30
  });
30
31
 
31
- test('link', async () => {
32
- await fs.promises.link(target, hardlink);
32
+ test('link', async t => {
33
+ const _ = await fs.promises.link(target, hardlink).catch((e: ErrnoError) => {
34
+ if (e.code == 'ENOSYS') return e;
35
+ throw e;
36
+ });
37
+ if (_) {
38
+ return t.skip('Backend does not support hard links');
39
+ }
33
40
  const targetContent = await fs.promises.readFile(target, 'utf8');
34
41
  const linkContent = await fs.promises.readFile(hardlink, 'utf8');
35
42
  assert.strictEqual(targetContent, linkContent);
@@ -43,8 +43,8 @@ suite('Permissions', () => {
43
43
  const stats = await fs.promises.lstat(link);
44
44
  assert.equal(stats.mode & 0o777, asyncMode);
45
45
 
46
- fs.lchmodSync(link, syncMode);
47
- assert.equal(fs.lstatSync(link).mode & 0o777, syncMode);
46
+ await fs.promises.lchmod(link, syncMode);
47
+ assert.equal((await fs.promises.lstat(link)).mode & 0o777, syncMode);
48
48
  });
49
49
 
50
50
  async function test_item(path: string): Promise<void> {