@zenfs/core 2.6.1 → 2.6.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.
@@ -46,6 +46,7 @@ export declare class Journal extends EventEmitter<{
46
46
  fromString(value: string): this;
47
47
  add(op: JournalOperation, path: string): void;
48
48
  has(op: JournalOperation, path: string): boolean;
49
+ delete(op: JournalOperation, path: string): void;
49
50
  isDeleted(path: string): boolean;
50
51
  }
51
52
  /**
@@ -53,6 +53,9 @@ export class Journal extends EventEmitter {
53
53
  return true;
54
54
  return false;
55
55
  }
56
+ delete(op, path) {
57
+ this.entries = this.entries.filter(entry => entry.op != op || entry.path != path);
58
+ }
56
59
  isDeleted(path) {
57
60
  let deleted = false;
58
61
  for (const entry of this.entries) {
@@ -168,6 +171,9 @@ export class CopyOnWriteFS extends FileSystem {
168
171
  if (this.isDeleted(oldPath))
169
172
  throw withErrno('ENOENT');
170
173
  }
174
+ if (await this.readable.exists(oldPath))
175
+ this.journal.add('delete', oldPath);
176
+ this.journal.delete('delete', newPath);
171
177
  this.renameInos(oldPath, newPath);
172
178
  }
173
179
  renameSync(oldPath, newPath) {
@@ -179,6 +185,9 @@ export class CopyOnWriteFS extends FileSystem {
179
185
  if (this.isDeleted(oldPath))
180
186
  throw withErrno('ENOENT');
181
187
  }
188
+ if (this.readable.existsSync(oldPath))
189
+ this.journal.add('delete', oldPath);
190
+ this.journal.delete('delete', newPath);
182
191
  this.renameInos(oldPath, newPath);
183
192
  }
184
193
  async stat(path) {
@@ -211,19 +220,25 @@ export class CopyOnWriteFS extends FileSystem {
211
220
  }
212
221
  async createFile(path, options) {
213
222
  await this.createParentDirectories(path);
214
- return this.remapIno(path, await this.writable.createFile(path, options));
223
+ const inode = await this.writable.createFile(path, options);
224
+ this.journal.delete('delete', path);
225
+ return this.remapIno(path, inode);
215
226
  }
216
227
  createFileSync(path, options) {
217
228
  this.createParentDirectoriesSync(path);
218
- return this.remapIno(path, this.writable.createFileSync(path, options));
229
+ const inode = this.writable.createFileSync(path, options);
230
+ this.journal.delete('delete', path);
231
+ return this.remapIno(path, inode);
219
232
  }
220
233
  async link(srcpath, dstpath) {
221
234
  await this.copyForWrite(srcpath);
222
235
  await this.writable.link(srcpath, dstpath);
236
+ this.journal.delete('delete', dstpath);
223
237
  }
224
238
  linkSync(srcpath, dstpath) {
225
239
  this.copyForWriteSync(srcpath);
226
240
  this.writable.linkSync(srcpath, dstpath);
241
+ this.journal.delete('delete', dstpath);
227
242
  }
228
243
  async unlink(path) {
229
244
  if (!(await this.exists(path)))
@@ -285,19 +300,27 @@ export class CopyOnWriteFS extends FileSystem {
285
300
  if (await this.exists(path))
286
301
  throw withErrno('EEXIST');
287
302
  await this.createParentDirectories(path);
288
- return this.remapIno(path, await this.writable.mkdir(path, options));
303
+ const inode = await this.writable.mkdir(path, options);
304
+ this.journal.delete('delete', path);
305
+ return this.remapIno(path, inode);
289
306
  }
290
307
  mkdirSync(path, options) {
291
308
  if (this.existsSync(path))
292
309
  throw withErrno('EEXIST');
293
310
  this.createParentDirectoriesSync(path);
294
- return this.remapIno(path, this.writable.mkdirSync(path, options));
311
+ const inode = this.writable.mkdirSync(path, options);
312
+ this.journal.delete('delete', path);
313
+ return this.remapIno(path, inode);
295
314
  }
296
315
  async readdir(path) {
297
- if (this.isDeleted(path) || !(await this.exists(path)))
316
+ if (this.isDeleted(path))
317
+ throw withErrno('ENOENT');
318
+ const readable = await this.readable.stat(path).catch(() => null);
319
+ const writable = await this.writable.stat(path).catch(() => null);
320
+ if (!readable && !writable)
298
321
  throw withErrno('ENOENT');
299
- const entries = (await this.readable.exists(path)) ? await this.readable.readdir(path) : [];
300
- if (await this.writable.exists(path))
322
+ const entries = readable && isDirectory(readable) ? await this.readable.readdir(path) : [];
323
+ if (writable && isDirectory(writable))
301
324
  for (const entry of await this.writable.readdir(path)) {
302
325
  if (!entries.includes(entry))
303
326
  entries.push(entry);
@@ -305,10 +328,26 @@ export class CopyOnWriteFS extends FileSystem {
305
328
  return entries.filter(entry => !this.isDeleted(join(path, entry)));
306
329
  }
307
330
  readdirSync(path) {
308
- if (this.isDeleted(path) || !this.existsSync(path))
331
+ if (this.isDeleted(path))
332
+ throw withErrno('ENOENT');
333
+ let readable = null;
334
+ let writable = null;
335
+ try {
336
+ readable = this.readable.statSync(path);
337
+ }
338
+ catch {
339
+ // Not on the readable layer
340
+ }
341
+ try {
342
+ writable = this.writable.statSync(path);
343
+ }
344
+ catch {
345
+ // Not on the writable layer
346
+ }
347
+ if (!readable && !writable)
309
348
  throw withErrno('ENOENT');
310
- const entries = this.readable.existsSync(path) ? this.readable.readdirSync(path) : [];
311
- if (this.writable.existsSync(path))
349
+ const entries = readable && isDirectory(readable) ? this.readable.readdirSync(path) : [];
350
+ if (writable && isDirectory(writable))
312
351
  for (const entry of this.writable.readdirSync(path)) {
313
352
  if (!entries.includes(entry))
314
353
  entries.push(entry);
@@ -320,7 +320,7 @@ const sb_magic = 0x62732e7a; // 'z.sb'
320
320
  * Shortcut for minor perf. bump
321
321
  * @internal
322
322
  */
323
- const usedBytes = 2;
323
+ const kUsedBytes = 2;
324
324
  /**
325
325
  * The super block structure for a single-buffer file system
326
326
  */
@@ -502,16 +502,34 @@ let SuperBlock = (() => {
502
502
  * @returns the new metadata block
503
503
  */
504
504
  rotateMetadata() {
505
- const padding = this.used_bytes % BigInt(4);
506
- Atomics.add(this, usedBytes, padding);
507
- const offset = Number(Atomics.add(this, usedBytes, BigInt(sizeof(MetadataBlock))));
508
- const metadata = new MetadataBlock(this.buffer, offset);
509
- metadata.previous_offset = this.metadata_offset;
505
+ const alignment = BigInt(Int32Array.BYTES_PER_ELEMENT), blockSize = BigInt(sizeof(MetadataBlock));
506
+ let used = Atomics.load(this, kUsedBytes), offset;
507
+ // Padding and block space must be reserved together because other writers can advance used_bytes at any time.
508
+ for (;;) {
509
+ const padding = (alignment - (used % alignment)) % alignment;
510
+ offset = used + padding;
511
+ const next = offset + blockSize;
512
+ if (next > this.total_bytes)
513
+ throw err(withErrno('ENOSPC', 'sbfs: not enough space for a new metadata block'));
514
+ const observed = Atomics.compareExchange(this, kUsedBytes, used, next);
515
+ if (observed === used)
516
+ break;
517
+ used = observed;
518
+ }
519
+ const metadata = new MetadataBlock(this.buffer, Number(offset));
520
+ const offsets = new Uint32Array(this.buffer, this.byteOffset, sizeof(SuperBlock) / Uint32Array.BYTES_PER_ELEMENT);
521
+ let previous = Atomics.load(offsets, kMetadataOffset);
522
+ for (;;) {
523
+ metadata.previous_offset = previous;
524
+ _update(metadata);
525
+ const observed = Atomics.compareExchange(offsets, kMetadataOffset, previous, metadata.byteOffset);
526
+ if (observed === previous)
527
+ break;
528
+ previous = observed;
529
+ }
510
530
  this.metadata = metadata;
511
- this.metadata_offset = metadata.byteOffset;
512
- _update(metadata);
513
531
  _update(this);
514
- debug(`sbfs: rotated metadata block at ${hex(metadata.previous_offset)} with new block at ${hex(offset)}`);
532
+ debug(`sbfs: rotated metadata block at ${hex(metadata.previous_offset)} with new block at ${hex(metadata.byteOffset)}`);
515
533
  return metadata;
516
534
  }
517
535
  /**
@@ -545,6 +563,11 @@ let SuperBlock = (() => {
545
563
  return SuperBlock = _classThis;
546
564
  })();
547
565
  export { SuperBlock };
566
+ /**
567
+ * Index of `SuperBlock.metadata_offset` in a `Uint32Array` view of the super block.
568
+ * @internal
569
+ */
570
+ const kMetadataOffset = offsetof(SuperBlock, 'metadata_offset') / Uint32Array.BYTES_PER_ELEMENT;
548
571
  /**
549
572
  * Compute the checksum for a super block or metadata block.
550
573
  * Note we don't include the checksum when computing a new one.
@@ -635,7 +658,7 @@ export class SingleBufferStore extends BufferView {
635
658
  _update(block);
636
659
  return;
637
660
  }
638
- entry.offset = Number(Atomics.add(this.superblock, usedBytes, BigInt(data.length)));
661
+ entry.offset = Number(Atomics.add(this.superblock, kUsedBytes, BigInt(data.length)));
639
662
  entry.size = data.length;
640
663
  this._u8.set(data, entry.offset);
641
664
  _update(block);
@@ -657,7 +680,7 @@ export class SingleBufferStore extends BufferView {
657
680
  entry = this.superblock.metadata.items[0];
658
681
  }
659
682
  const lock = __addDisposableResource(env_1, this.superblock.metadata.lock(), false);
660
- const offset = Number(Atomics.add(this.superblock, usedBytes, BigInt(data.length)));
683
+ const offset = Number(Atomics.add(this.superblock, kUsedBytes, BigInt(data.length)));
661
684
  entry.id = id;
662
685
  entry.offset = offset;
663
686
  entry.size = data.length;
package/dist/context.js CHANGED
@@ -24,6 +24,7 @@ export function bindContext(init = {}) {
24
24
  const bound = Object.assign(ctx, {
25
25
  fs: {
26
26
  ...bindFunctions(fs, ctx),
27
+ Utf8Stream: fs.Utf8Stream._withContext(ctx),
27
28
  promises: bindFunctions(fs.promises, ctx),
28
29
  xattr: bindFunctions(xattr, ctx),
29
30
  },
@@ -14,7 +14,7 @@ declare const kIsContext: unique symbol;
14
14
  * @category Contexts
15
15
  */
16
16
  export interface FSContext {
17
- /** The unique ID of the context */
17
+ /** Brand */
18
18
  readonly [kIsContext]: boolean;
19
19
  /** The unique ID of the context */
20
20
  readonly id: number;
@@ -35,6 +35,8 @@ export interface FSContext {
35
35
  /** The child contexts */
36
36
  readonly children: FSContext[];
37
37
  }
38
+ export declare function isContext(obj: unknown): obj is FSContext;
39
+ export declare function assertContext(ctx: unknown): asserts ctx is FSContext;
38
40
  /**
39
41
  * maybe an FS context
40
42
  */
@@ -1,9 +1,18 @@
1
1
  import { createCredentials } from './credentials.js';
2
+ import { warn } from 'kerium/log';
3
+ import { withErrno } from 'kerium';
2
4
  /**
3
5
  * Symbol used for context branding
4
6
  * @internal @hidden
5
7
  */
6
8
  const kIsContext = Symbol('ZenFSContext');
9
+ export function isContext(obj) {
10
+ return typeof obj === 'object' && obj !== null && kIsContext in obj;
11
+ }
12
+ export function assertContext(ctx) {
13
+ if (!isContext(ctx))
14
+ throw warn(withErrno('EINVAL', 'Invalid context provided'));
15
+ }
7
16
  /**
8
17
  * The default/global context.
9
18
  * @internal @hidden
@@ -20,7 +29,7 @@ export const defaultContext = {
20
29
  children: [],
21
30
  };
22
31
  export function contextOf($) {
23
- return typeof $ === 'object' && $ !== null && kIsContext in $ ? $ : defaultContext;
32
+ return isContext($) ? $ : defaultContext;
24
33
  }
25
34
  // 0 is reserved for the global/default context
26
35
  let _nextId = 1;
@@ -33,6 +42,7 @@ let _nextId = 1;
33
42
  * This exists so that `kIsContext` is not exported and to make sure the context is "secure".
34
43
  */
35
44
  export function createChildContext(parent, init = {}) {
45
+ assertContext(parent);
36
46
  const { root = parent.root, pwd = parent.pwd, credentials = structuredClone(parent.credentials) } = init;
37
47
  const ctx = {
38
48
  [kIsContext]: true,
@@ -243,6 +243,8 @@ export declare function isFIFO(metadata: {
243
243
  export declare function hasAccess($: V_Context, inode: Pick<InodeLike, 'mode' | 'uid' | 'gid'>, access: number): boolean;
244
244
  /**
245
245
  * @hidden @internal
246
+ *
247
+ * @todo https://github.com/nodejs/node/pull/58836 support -1 for `*chown`
246
248
  */
247
249
  export declare function _chown(stats: Partial<InodeLike>, uid: number, gid: number): boolean;
248
250
  export {};
@@ -662,6 +662,8 @@ export function hasAccess($, inode, access) {
662
662
  }
663
663
  /**
664
664
  * @hidden @internal
665
+ *
666
+ * @todo https://github.com/nodejs/node/pull/58836 support -1 for `*chown`
665
667
  */
666
668
  export function _chown(stats, uid, gid) {
667
669
  let valid = true;
@@ -1,12 +1,12 @@
1
1
  import type { ExceptionJSON } from 'kerium';
2
- import type { TransferListItem } from 'node:worker_threads';
2
+ import type { Transferable } from 'node:worker_threads';
3
3
  import type { WithOptional } from 'utilium';
4
4
  import type { Backend, FilesystemOf } from '../backends/backend.js';
5
5
  import type { PortFS } from '../backends/port.js';
6
6
  import type { CreationOptions, FileSystem, UsageInfo } from '../internal/filesystem.js';
7
7
  import '../polyfills.js';
8
8
  export interface WebMessagePort {
9
- postMessage(value: unknown, transfer?: TransferListItem[]): void;
9
+ postMessage(value: unknown, transfer?: Transferable[]): void;
10
10
  addEventListener(type: 'message', listener: (ev: {
11
11
  data: any;
12
12
  }) => void): void;
@@ -15,7 +15,7 @@ export interface WebMessagePort {
15
15
  }) => void): void;
16
16
  }
17
17
  export interface NodeMessagePort {
18
- postMessage(value: unknown, transfer?: TransferListItem[]): void;
18
+ postMessage(value: unknown, transfer?: Transferable[]): void;
19
19
  on(type: 'message', listener: (ev: any) => void): void;
20
20
  off(type: 'message', listener: (ev: any) => void): void;
21
21
  }
@@ -24,7 +24,7 @@ export type Channel = NodeMessagePort | WebMessagePort | WebSocket;
24
24
  export interface Port<T extends Channel = Channel> {
25
25
  readonly channel: T;
26
26
  /** Send a request */
27
- send<M extends Message>(message: M, transfer?: TransferListItem[]): void;
27
+ send<M extends Message>(message: M, transfer?: Transferable[]): void;
28
28
  /** Add a response handler */
29
29
  addHandler<M extends Message>(handler: (message: M) => void): void;
30
30
  /** Remove a response handler */
@@ -22,11 +22,21 @@ export declare function exists(this: V_Context, path: fs.PathLike, cb?: (exists:
22
22
  export declare function stat(this: V_Context, path: fs.PathLike, callback: Callback<[Stats]>): void;
23
23
  export declare function stat(this: V_Context, path: fs.PathLike, options: {
24
24
  bigint?: false;
25
+ throwIfNoEntry?: true;
25
26
  }, callback: Callback<[Stats]>): void;
26
27
  export declare function stat(this: V_Context, path: fs.PathLike, options: {
27
28
  bigint: true;
29
+ throwIfNoEntry?: true;
28
30
  }, callback: Callback<[BigIntStats]>): void;
29
- export declare function stat(this: V_Context, path: fs.PathLike, options: fs.StatOptions, callback: Callback<[Stats] | [BigIntStats]>): void;
31
+ export declare function stat(this: V_Context, path: fs.PathLike, options: {
32
+ bigint?: false;
33
+ throwIfNoEntry: false;
34
+ }, callback: Callback<[Stats | undefined]>): void;
35
+ export declare function stat(this: V_Context, path: fs.PathLike, options: {
36
+ bigint: true;
37
+ throwIfNoEntry: false;
38
+ }, callback: Callback<[BigIntStats | undefined]>): void;
39
+ export declare function stat(this: V_Context, path: fs.PathLike, options: fs.StatOptions, callback: Callback<[Stats | BigIntStats | undefined]>): void;
30
40
  /**
31
41
  * Asynchronous `lstat`.
32
42
  * `lstat()` is identical to `stat()`, except that if path is a symbolic link,
@@ -148,47 +148,26 @@ fdatasync;
148
148
  export function write(fd, data, cbPosOff, cbLenEnc, cbPosEnc, cb = nop) {
149
149
  let buffer, offset, length, position, encoding;
150
150
  const handle = new promises.FileHandle(this, fd);
151
+ const _cb = ([cbPosOff, cbLenEnc, cbPosEnc].find(arg => typeof arg === 'function') ?? cb);
151
152
  if (typeof data === 'string') {
152
- // Signature 1: (fd, string, [position?, [encoding?]], cb?)
153
- encoding = 'utf8';
154
- switch (typeof cbPosOff) {
155
- case 'function':
156
- // (fd, string, cb)
157
- cb = cbPosOff;
158
- break;
159
- case 'number':
160
- // (fd, string, position, encoding?, cb?)
161
- position = cbPosOff;
162
- encoding = typeof cbLenEnc === 'string' ? cbLenEnc : 'utf8';
163
- cb = typeof cbPosEnc === 'function' ? cbPosEnc : cb;
164
- break;
165
- default:
166
- // ...try to find the callback and get out of here!
167
- cb = (typeof cbLenEnc === 'function' ? cbLenEnc : typeof cbPosEnc === 'function' ? cbPosEnc : cb);
168
- cb(withErrno('EINVAL'));
169
- return;
170
- }
171
- buffer = Buffer.from(data);
153
+ // (fd, string, [position?, [encoding?]], cb?)
154
+ position = typeof cbPosOff === 'number' ? cbPosOff : null;
155
+ encoding = typeof cbLenEnc === 'string' ? cbLenEnc : 'utf8';
156
+ buffer = Buffer.from(data, encoding);
172
157
  offset = 0;
173
- length = buffer.length;
174
- const _cb = cb;
175
- handle
176
- .write(buffer, offset, length, position)
177
- .then(({ bytesWritten }) => _cb(null, bytesWritten, buffer.toString(encoding)))
178
- .catch(_cb);
158
+ length = buffer.byteLength;
179
159
  }
180
160
  else {
181
- // Signature 2: (fd, buffer, offset, length, position?, cb?)
182
- buffer = Buffer.from(data.buffer);
183
- offset = cbPosOff;
184
- length = cbLenEnc;
161
+ // (fd, buffer, [offset?, [length?, [position?]]], cb?)
162
+ buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
163
+ offset = typeof cbPosOff === 'number' ? cbPosOff : 0;
164
+ length = typeof cbLenEnc === 'number' ? cbLenEnc : buffer.byteLength - offset;
185
165
  position = typeof cbPosEnc === 'number' ? cbPosEnc : null;
186
- const _cb = (typeof cbPosEnc === 'function' ? cbPosEnc : cb);
187
- void handle
188
- .write(buffer, offset, length, position)
189
- .then(({ bytesWritten }) => _cb(null, bytesWritten, buffer))
190
- .catch(_cb);
191
166
  }
167
+ handle
168
+ .write(buffer, offset, length, position)
169
+ .then(({ bytesWritten }) => _cb(null, bytesWritten, typeof data === 'string' ? data : buffer))
170
+ .catch(_cb);
192
171
  }
193
172
  write;
194
173
  /**
@@ -6,3 +6,4 @@ export { BigIntStatsFs, Stats, StatsFs } from './stats.js';
6
6
  export type { StatsLike } from './stats.js';
7
7
  export * from './streams.js';
8
8
  export * from './sync.js';
9
+ export { Utf8Stream } from './utf8stream.js';
@@ -6,3 +6,4 @@ export * as promises from './promises.js';
6
6
  export { BigIntStatsFs, Stats, StatsFs } from './stats.js';
7
7
  export * from './streams.js';
8
8
  export * from './sync.js';
9
+ export { Utf8Stream } from './utf8stream.js';
@@ -180,11 +180,25 @@ export declare function rename(this: V_Context, oldPath: fs.PathLike, newPath: f
180
180
  * Test whether or not `path` exists by checking with the file system.
181
181
  */
182
182
  export declare function exists(this: V_Context, path: fs.PathLike): Promise<boolean>;
183
- export declare function stat(this: V_Context, path: fs.PathLike, options: fs.BigIntOptions): Promise<BigIntStats>;
184
- export declare function stat(this: V_Context, path: fs.PathLike, options?: {
183
+ export declare function stat(this: V_Context, path: fs.PathLike, options: fs.BigIntOptions & {
184
+ throwIfNoEntry?: true;
185
+ }): Promise<BigIntStats>;
186
+ export declare function stat(this: V_Context, path: fs.PathLike, options?: fs.StatOptions & {
185
187
  bigint?: false;
188
+ throwIfNoEntry?: true;
186
189
  }): Promise<Stats>;
187
- export declare function stat(this: V_Context, path: fs.PathLike, options?: fs.StatOptions): Promise<Stats | BigIntStats>;
190
+ export declare function stat(this: V_Context, path: fs.PathLike, options: fs.StatOptions & {
191
+ bigint?: false;
192
+ throwIfNoEntry: false;
193
+ }): Promise<Stats | undefined>;
194
+ export declare function stat(this: V_Context, path: fs.PathLike, options: fs.StatOptions & {
195
+ bigint: true;
196
+ throwIfNoEntry: false;
197
+ }): Promise<BigIntStats | undefined>;
198
+ export declare function stat(this: V_Context, path: fs.PathLike, options: fs.StatOptions & {
199
+ throwIfNoEntry?: true;
200
+ }): Promise<Stats | BigIntStats>;
201
+ export declare function stat(this: V_Context, path: fs.PathLike, options?: fs.StatOptions): Promise<Stats | BigIntStats | undefined>;
188
202
  /**
189
203
  * `lstat`.
190
204
  * `lstat()` is identical to `stat()`, except that if path is a symbolic link,
@@ -54,10 +54,11 @@ import { Buffer } from 'buffer';
54
54
  import { Exception, rethrow, UV } from 'kerium';
55
55
  import { encodeUTF8 } from 'utilium';
56
56
  import * as constants from '../constants.js';
57
+ import { contextOf } from '../internal/contexts.js';
57
58
  import { hasAccess, InodeFlags, isDirectory } from '../internal/inode.js';
58
- import { dirname, join, matchesGlob } from '../path.js';
59
+ import { dirname, join, matchesGlob, relative, resolve } from '../path.js';
59
60
  import '../polyfills.js';
60
- import { _tempDirName, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime, validateFD } from '../utils.js';
61
+ import { _isNoEntry, _tempDirName, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime, validateFD } from '../utils.js';
61
62
  import * as _async from '../vfs/async.js';
62
63
  import { checkAccess } from '../vfs/config.js';
63
64
  import { deleteFD, fromFD, toFD } from '../vfs/file.js';
@@ -541,7 +542,15 @@ export async function exists(path) {
541
542
  }
542
543
  }
543
544
  export async function stat(path, options) {
544
- const stats = await _async.stat.call(this, path, false);
545
+ let stats;
546
+ try {
547
+ stats = await _async.stat.call(this, path, false);
548
+ }
549
+ catch (e) {
550
+ if (options?.throwIfNoEntry === false && _isNoEntry(e))
551
+ return;
552
+ throw e;
553
+ }
545
554
  return options?.bigint ? new BigIntStats(stats) : new Stats(stats);
546
555
  }
547
556
  stat;
@@ -1081,8 +1090,16 @@ export async function cp(source, destination, opts) {
1081
1090
  await Promise.all(entries.map(_cp));
1082
1091
  break;
1083
1092
  }
1084
- case constants.S_IFREG:
1085
1093
  case constants.S_IFLNK:
1094
+ if (opts?.dereference) {
1095
+ await copyFile.call(this, source, destination);
1096
+ }
1097
+ else {
1098
+ const link = await readlink.call(this, source);
1099
+ await symlink.call(this, opts?.verbatimSymlinks ? link : resolve.call(this, dirname(source), link), destination);
1100
+ }
1101
+ break;
1102
+ case constants.S_IFREG:
1086
1103
  await copyFile.call(this, source, destination);
1087
1104
  break;
1088
1105
  case constants.S_IFBLK:
@@ -1105,8 +1122,10 @@ export async function statfs(path, opts) {
1105
1122
  }
1106
1123
  export function glob(pattern, opt) {
1107
1124
  pattern = Array.isArray(pattern) ? pattern : [pattern];
1108
- const { cwd = '/', withFileTypes = false, exclude = () => false } = opt || {};
1109
- const normalizedPatterns = pattern.map(p => p.replace(/^\/+/g, ''));
1125
+ const $ = contextOf(this);
1126
+ const { cwd = $.pwd, withFileTypes = false, exclude = () => false, followSymlinks = false } = opt || {};
1127
+ const base = resolve.call($, cwd instanceof URL ? cwd.pathname : cwd);
1128
+ const normalizedPatterns = pattern.map(p => (p.startsWith('/') ? relative.call($, base, p) : p));
1110
1129
  const hasGlobStar = normalizedPatterns.some(p => p.includes('**'));
1111
1130
  const patternBases = normalizedPatterns.map(p => {
1112
1131
  const firstGlob = p.search(/[*?[\]{]/);
@@ -1116,23 +1135,28 @@ export function glob(pattern, opt) {
1116
1135
  return lastSlash === -1 ? '' : p.slice(0, lastSlash);
1117
1136
  });
1118
1137
  const regexPatterns = normalizedPatterns.map(globToRegex);
1138
+ async function shouldDescend(path, relativePath) {
1139
+ const isNamed = patternBases.some(base => relativePath === base || base.startsWith(relativePath + '/'));
1140
+ const inScope = hasGlobStar || isNamed;
1141
+ const stats = await lstat.call($, path);
1142
+ if (!stats.isSymbolicLink())
1143
+ return inScope && stats.isDirectory();
1144
+ return inScope && (followSymlinks || isNamed) && !!(await stat.call($, path, { throwIfNoEntry: false }))?.isDirectory();
1145
+ }
1119
1146
  async function* recursiveList(dir) {
1120
- const entries = await readdir(dir, { withFileTypes, encoding: 'utf8' });
1147
+ const entries = await readdir.call($, dir, { withFileTypes, encoding: 'utf8' });
1121
1148
  for (const entry of entries) {
1122
1149
  const fullPath = join(dir, withFileTypes ? entry.name : entry);
1123
- if (typeof exclude != 'function' ? exclude.some(p => matchesGlob(p, fullPath)) : exclude((withFileTypes ? entry : fullPath)))
1150
+ const relPath = relative.call($, base, fullPath);
1151
+ if (typeof exclude != 'function' ? exclude.some(p => matchesGlob(p, relPath)) : exclude((withFileTypes ? entry : relPath)))
1124
1152
  continue;
1125
- const relativePath = fullPath.replace(/^\/+/g, '');
1126
- if ((await stat(fullPath)).isDirectory()) {
1127
- if (hasGlobStar || patternBases.some(base => relativePath === base || base.startsWith(relativePath + '/'))) {
1128
- yield* recursiveList(fullPath);
1129
- }
1130
- }
1131
- if (regexPatterns.some(rx => rx.test(relativePath))) {
1132
- yield withFileTypes ? entry : relativePath;
1153
+ if (await shouldDescend(fullPath, relPath))
1154
+ yield* recursiveList(fullPath);
1155
+ if (regexPatterns.some(rx => rx.test(relPath))) {
1156
+ yield withFileTypes ? entry : relPath;
1133
1157
  }
1134
1158
  }
1135
1159
  }
1136
- return recursiveList(cwd instanceof URL ? cwd.pathname : cwd);
1160
+ return recursiveList(base);
1137
1161
  }
1138
1162
  glob;
@@ -91,24 +91,32 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
91
91
  atimeMs: T;
92
92
  get atime(): Date;
93
93
  set atime(value: Date);
94
+ get atimeInstant(): Temporal.Instant;
95
+ set atimeInstant(value: Temporal.Instant);
94
96
  /**
95
97
  * Time of last modification, since epoch
96
98
  */
97
99
  mtimeMs: T;
98
100
  get mtime(): Date;
99
101
  set mtime(value: Date);
102
+ get mtimeInstant(): Temporal.Instant;
103
+ set mtimeInstant(value: Temporal.Instant);
100
104
  /**
101
105
  * Time of last time file status was changed, since epoch
102
106
  */
103
107
  ctimeMs: T;
104
108
  get ctime(): Date;
105
109
  set ctime(value: Date);
110
+ get ctimeInstant(): Temporal.Instant;
111
+ set ctimeInstant(value: Temporal.Instant);
106
112
  /**
107
113
  * Time of file creation, since epoch
108
114
  */
109
115
  birthtimeMs: T;
110
116
  get birthtime(): Date;
111
117
  set birthtime(value: Date);
118
+ get birthtimeInstant(): Temporal.Instant;
119
+ set birthtimeInstant(value: Temporal.Instant);
112
120
  /**
113
121
  * Size of the item in bytes.
114
122
  * For directories/symlinks, this is normally the size of the struct that represents the item.
@@ -193,6 +201,8 @@ export declare class StatsFs implements Node.StatsFsBase<number> {
193
201
  bavail: number;
194
202
  /** Total file nodes in file system. */
195
203
  files: number;
204
+ /** Fundamental file system block size. */
205
+ frsize: number;
196
206
  /** Free file nodes in file system. */
197
207
  ffree: number;
198
208
  }
@@ -212,6 +222,8 @@ export declare class BigIntStatsFs implements Node.StatsFsBase<bigint> {
212
222
  bavail: bigint;
213
223
  /** Total file nodes in file system. */
214
224
  files: bigint;
225
+ /** Fundamental file system block size. */
226
+ frsize: bigint;
215
227
  /** Free file nodes in file system. */
216
228
  ffree: bigint;
217
229
  }