@zenfs/core 2.6.2 → 2.6.4

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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/backends/single_buffer.js +34 -11
  3. package/dist/context.js +1 -0
  4. package/dist/internal/contexts.d.ts +3 -1
  5. package/dist/internal/contexts.js +11 -1
  6. package/dist/internal/inode.d.ts +2 -0
  7. package/dist/internal/inode.js +2 -0
  8. package/dist/internal/rpc.d.ts +4 -4
  9. package/dist/node/async.d.ts +11 -1
  10. package/dist/node/async.js +14 -35
  11. package/dist/node/index.d.ts +1 -0
  12. package/dist/node/index.js +1 -0
  13. package/dist/node/promises.d.ts +17 -3
  14. package/dist/node/promises.js +32 -16
  15. package/dist/node/stats.d.ts +12 -0
  16. package/dist/node/stats.js +35 -6
  17. package/dist/node/sync.d.ts +39 -11
  18. package/dist/node/sync.js +41 -17
  19. package/dist/node/utf8stream.d.ts +99 -0
  20. package/dist/node/utf8stream.js +690 -0
  21. package/dist/utils.d.ts +10 -0
  22. package/dist/utils.js +17 -0
  23. package/dist/vfs/shared.js +9 -7
  24. package/package.json +14 -10
  25. package/scripts/test.js +15 -8
  26. package/tests/backend/single-buffer-rotate.worker.js +19 -0
  27. package/tests/backend/single-buffer.test.ts +106 -0
  28. package/tests/common/context.test.ts +38 -0
  29. package/tests/common/glob.test.ts +86 -1
  30. package/tests/common/utf8stream.test.ts +164 -0
  31. package/tests/common.ts +4 -2
  32. package/tests/fetch/fetch.ts +1 -1
  33. package/tests/fetch/run.sh +1 -1
  34. package/tests/fs/append.test.ts +1 -1
  35. package/tests/fs/concurrency.test.ts +1 -1
  36. package/tests/fs/dir.test.ts +1 -1
  37. package/tests/fs/directory.test.ts +3 -3
  38. package/tests/fs/errors.test.ts +1 -1
  39. package/tests/fs/exists.test.ts +3 -3
  40. package/tests/fs/links.test.ts +5 -5
  41. package/tests/fs/open.test.ts +5 -5
  42. package/tests/fs/permissions.test.ts +5 -5
  43. package/tests/fs/read.test.ts +3 -3
  44. package/tests/fs/readFile.test.ts +2 -2
  45. package/tests/fs/rename.test.ts +5 -5
  46. package/tests/fs/scaling.test.ts +1 -1
  47. package/tests/fs/stat.test.ts +32 -3
  48. package/tests/fs/streams.test.ts +1 -1
  49. package/tests/fs/tempdir.test.ts +1 -1
  50. package/tests/fs/times.test.ts +2 -2
  51. package/tests/fs/truncate.test.ts +1 -1
  52. package/tests/fs/watch.test.ts +1 -1
  53. package/tests/fs/write.test.ts +2 -2
  54. package/tests/fs/xattr.test.ts +1 -1
  55. package/tests/{native/setup.ts → native-setup.ts} +5 -5
  56. package/tests/setup/context.ts +1 -1
  57. package/tests/setup/cow.ts +1 -1
  58. package/tests/setup/index.ts +1 -1
  59. package/tests/setup/memory.ts +1 -1
  60. package/tests/setup/port.ts +1 -1
  61. package/tests/setup/single-buffer.ts +1 -1
  62. package/tests/setup.ts +1 -1
  63. package/tests/tsconfig.json +2 -1
  64. package/tests/native/run.sh +0 -3
@@ -0,0 +1,690 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-or-later
2
+ import { Buffer } from 'buffer';
3
+ import { EventEmitter } from 'eventemitter3';
4
+ import { UV } from 'kerium';
5
+ import { bindFunctions } from 'utilium';
6
+ import { assertContext } from '../internal/contexts.js';
7
+ import { dirname } from '../path.js';
8
+ import { close, fsync, mkdir, open, write } from './async.js';
9
+ import { fsyncSync, mkdirSync, openSync, writeSync } from './sync.js';
10
+ /* Everything below is a port of Node's `internal/fs/utf8stream`,
11
+ which is itself derived from the SonicBoom module (https://github.com/pinojs/sonic-boom).
12
+ MIT License, Copyright (c) 2017 Matteo Collina */
13
+ /** An error with a Node-style `code`, e.g. `ERR_INVALID_STATE` @hidden */
14
+ function error(code, message) {
15
+ return Object.assign(new Error(message), { code });
16
+ }
17
+ /** @hidden */
18
+ function invalidArgType(name, expected, value) {
19
+ return error('ERR_INVALID_ARG_TYPE', `The "${name}" argument must be of type ${expected}. Received ${typeof value}`);
20
+ }
21
+ /** @hidden */
22
+ function invalidState(message) {
23
+ return error('ERR_INVALID_STATE', 'Invalid state: ' + message);
24
+ }
25
+ /** @hidden */
26
+ function validateUint32(value, name) {
27
+ if (typeof value != 'number')
28
+ throw invalidArgType(name, 'number', value);
29
+ if (!Number.isInteger(value) || value < 0 || value > 0xffffffff)
30
+ throw error('ERR_OUT_OF_RANGE', `The value of "${name}" is out of range. It must be an integer >= 0 and <= 4294967295. Received ${value}`);
31
+ }
32
+ /** 16 KB. Don't write more than the docker buffer size. */
33
+ const maxWriteDefault = 16 * 1024;
34
+ /** How long to wait before retrying a write that failed with `EAGAIN` or `EBUSY` */
35
+ const busyWriteTimeout = 100;
36
+ const emptyBuffer = Buffer.alloc(0);
37
+ const _utf8StreamFS = { open, openSync, close, write, writeSync, fsync, fsyncSync, mkdir, mkdirSync };
38
+ /**
39
+ * Release `writingBuf` after `n` bytes of it have been written.
40
+ * @hidden
41
+ */
42
+ function _release(writingBuf, len, n) {
43
+ if (typeof writingBuf == 'string') {
44
+ const byteLength = Buffer.byteLength(writingBuf);
45
+ /*
46
+ `write` returns the number of bytes written, but `len` is tracked in characters
47
+ and `writingBuf` is sliced by character index below,
48
+ so `n` must be converted from bytes to characters in both cases.
49
+ */
50
+ if (byteLength === n) {
51
+ // The whole string was written: advance past every character.
52
+ n = writingBuf.length;
53
+ }
54
+ else {
55
+ /*
56
+ A partial write may split a multi-byte UTF-8 character, so we must back up to the start of that character.
57
+ Continuation bytes have the pattern 10xxxxxx (0x80-0xbf).
58
+ */
59
+ const buf = Buffer.from(writingBuf);
60
+ while (n > 0 && (buf[n] & 0xc0) === 0x80)
61
+ n--;
62
+ // Decode the properly-aligned bytes to get the character count.
63
+ n = buf.subarray(0, n).toString().length;
64
+ }
65
+ }
66
+ return { writingBuf: writingBuf.slice(n), len: Math.max(len - n, 0) };
67
+ }
68
+ /** @hidden */
69
+ function _mergeBuffers(bufs, len) {
70
+ if (!bufs.length)
71
+ return emptyBuffer;
72
+ if (bufs.length == 1)
73
+ return bufs[0];
74
+ return Buffer.concat(bufs, len);
75
+ }
76
+ const kUtf8StreamFS = Symbol('Utf8StreamFS');
77
+ /**
78
+ * An optimized UTF-8 stream writer that allows for flushing all of the internal buffering on demand.
79
+ */
80
+ export class Utf8Stream extends EventEmitter {
81
+ #len = 0;
82
+ #fd = -1;
83
+ /** Pending chunks. Strings when `contentMode` is `utf8`, groups of buffers when it is `buffer`. */
84
+ #bufs = [];
85
+ /** The byte length of each group in `#bufs`. Only used when `contentMode` is `buffer`. */
86
+ #lens = [];
87
+ #writing = false;
88
+ #ending = false;
89
+ #reopening = false;
90
+ #asyncDrainScheduled = false;
91
+ #flushPending = false;
92
+ /** 16 KB */
93
+ #hwm = 16387;
94
+ #file = null;
95
+ #destroyed = false;
96
+ #minLength = 0;
97
+ #maxLength = 0;
98
+ #maxWrite = maxWriteDefault;
99
+ #opening = false;
100
+ #periodicFlush = 0;
101
+ #periodicFlushTimer;
102
+ #sync = false;
103
+ #fsync = false;
104
+ #append = true;
105
+ #mode;
106
+ #retryEAGAIN = () => true;
107
+ #mkdir = false;
108
+ #contentMode;
109
+ #writingBuf = '';
110
+ #fs;
111
+ static [kUtf8StreamFS] = _utf8StreamFS;
112
+ /** @hidden @internal */
113
+ static _withContext(ctx) {
114
+ assertContext(ctx);
115
+ class $Utf8Stream extends Utf8Stream {
116
+ static [kUtf8StreamFS] = bindFunctions(_utf8StreamFS, ctx);
117
+ }
118
+ return $Utf8Stream;
119
+ }
120
+ constructor(options = {}) {
121
+ if (typeof options != 'object' || options === null)
122
+ throw invalidArgType('options', 'object', options);
123
+ const { dest, minLength, maxLength, maxWrite, periodicFlush, sync, append = true, mkdir, retryEAGAIN, fsync, contentMode = 'utf8', mode,
124
+ // Provides for a custom fs implementation. Mostly useful for testing.
125
+ fs: overrideFS = {}, } = options;
126
+ super();
127
+ const fd = options.fd ?? dest;
128
+ if (typeof overrideFS != 'object' || overrideFS === null)
129
+ throw invalidArgType('options.fs', 'object', overrideFS);
130
+ this.#fs = { ...this.constructor[kUtf8StreamFS], ...overrideFS };
131
+ const _fs = this.#fs;
132
+ for (const key of ['write', 'writeSync', 'fsync', 'fsyncSync', 'close', 'open', 'mkdir', 'mkdirSync']) {
133
+ if (typeof _fs[key] != 'function')
134
+ throw invalidArgType(`options.fs.${key}`, 'function', _fs[key]);
135
+ }
136
+ this.#hwm = Math.max(minLength || 0, this.#hwm);
137
+ this.#minLength = minLength || 0;
138
+ this.#maxLength = maxLength || 0;
139
+ this.#maxWrite = maxWrite || maxWriteDefault;
140
+ this.#periodicFlush = periodicFlush || 0;
141
+ this.#sync = sync || false;
142
+ this.#fsync = fsync || false;
143
+ this.#append = append || false;
144
+ this.#mode = mode;
145
+ this.#retryEAGAIN = retryEAGAIN || (() => true);
146
+ this.#mkdir = mkdir || false;
147
+ validateUint32(this.#hwm, 'options.hwm');
148
+ validateUint32(this.#minLength, 'options.minLength');
149
+ validateUint32(this.#maxLength, 'options.maxLength');
150
+ validateUint32(this.#maxWrite, 'options.maxWrite');
151
+ validateUint32(this.#periodicFlush, 'options.periodicFlush');
152
+ for (const [key, value] of [
153
+ ['sync', this.#sync],
154
+ ['fsync', this.#fsync],
155
+ ['append', this.#append],
156
+ ['mkdir', this.#mkdir],
157
+ ]) {
158
+ if (typeof value != 'boolean')
159
+ throw invalidArgType(`options.${key}`, 'boolean', value);
160
+ }
161
+ if (typeof this.#retryEAGAIN != 'function')
162
+ throw invalidArgType('options.retryEAGAIN', 'function', this.#retryEAGAIN);
163
+ if (contentMode != 'buffer' && contentMode != 'utf8')
164
+ throw error('ERR_INVALID_ARG_VALUE', `The property 'options.contentMode' must be one of: 'buffer', 'utf8'. Received ${JSON.stringify(contentMode)}`);
165
+ this.#contentMode = contentMode;
166
+ this.#writingBuf = contentMode == 'buffer' ? emptyBuffer : '';
167
+ if (typeof fd == 'number') {
168
+ this.#fd = fd;
169
+ queueMicrotask(() => this.emit('ready'));
170
+ }
171
+ else if (typeof fd == 'string') {
172
+ this.#openFile(fd);
173
+ }
174
+ else {
175
+ throw invalidArgType('fd', 'number or string', fd);
176
+ }
177
+ if (this.#minLength >= this.#maxWrite)
178
+ throw error('ERR_INVALID_ARG_VALUE', `The argument 'minLength' should be smaller than maxWrite (${this.#maxWrite}). Received ${this.#minLength}`);
179
+ if (this.#periodicFlush !== 0) {
180
+ this.#periodicFlushTimer = setInterval(() => this.flush(), this.#periodicFlush);
181
+ this.#periodicFlushTimer.unref?.();
182
+ }
183
+ }
184
+ /**
185
+ * When `contentMode` is `utf8`, `data` must be a string. When it is `buffer`, `data` must be a `Buffer`.
186
+ * @returns Whether the internal buffer is below the high water mark
187
+ */
188
+ write(data) {
189
+ if (this.#destroyed)
190
+ throw invalidState('Utf8Stream is destroyed');
191
+ if (this.#contentMode == 'buffer') {
192
+ if (!Buffer.isBuffer(data))
193
+ throw invalidArgType('data', 'Buffer', data);
194
+ }
195
+ else if (typeof data != 'string') {
196
+ throw invalidArgType('data', 'string', data);
197
+ }
198
+ const len = this.#len + data.length;
199
+ if (this.#maxLength && len > this.#maxLength) {
200
+ this.emit('drop', data);
201
+ return this.#len < this.#hwm;
202
+ }
203
+ const last = this.#bufs.length - 1;
204
+ if (this.#contentMode == 'buffer') {
205
+ const bufs = this.#bufs;
206
+ if (!bufs.length || this.#lens[last] + data.length > this.#maxWrite) {
207
+ bufs.push([data]);
208
+ this.#lens.push(data.length);
209
+ }
210
+ else {
211
+ bufs[last].push(data);
212
+ this.#lens[last] += data.length;
213
+ }
214
+ }
215
+ else {
216
+ const bufs = this.#bufs;
217
+ if (!bufs.length || bufs[last].length + data.length > this.#maxWrite)
218
+ bufs.push(data);
219
+ else
220
+ bufs[last] += data;
221
+ }
222
+ this.#len = len;
223
+ if (!this.#writing && this.#len >= this.#minLength)
224
+ this.#actualWrite();
225
+ return this.#len < this.#hwm;
226
+ }
227
+ /**
228
+ * Writes the current buffer to the file if a write was not in progress.
229
+ * Does nothing if `minLength` is zero or if it is already writing.
230
+ */
231
+ flush(cb) {
232
+ if (cb !== undefined && cb !== null && typeof cb != 'function')
233
+ throw invalidArgType('cb', 'function', cb);
234
+ if (this.#destroyed) {
235
+ const error = invalidState('Utf8Stream is destroyed');
236
+ if (!cb)
237
+ throw error;
238
+ cb(error);
239
+ return;
240
+ }
241
+ if (this.#minLength <= 0) {
242
+ cb?.(null);
243
+ return;
244
+ }
245
+ if (cb)
246
+ this.#callFlushCallbackOnDrain(cb);
247
+ if (this.#writing)
248
+ return;
249
+ if (!this.#bufs.length) {
250
+ this.#bufs.push(this.#contentMode == 'buffer' ? [] : '');
251
+ if (this.#contentMode == 'buffer')
252
+ this.#lens.push(0);
253
+ }
254
+ this.#actualWrite();
255
+ }
256
+ /**
257
+ * Flushes the buffered data synchronously. This is a costly operation.
258
+ */
259
+ flushSync() {
260
+ if (this.#destroyed)
261
+ throw invalidState('Utf8Stream is destroyed');
262
+ if (this.#fd < 0)
263
+ throw invalidState('Invalid file descriptor');
264
+ if (this.#contentMode == 'buffer')
265
+ this.#flushSyncBuffer();
266
+ else
267
+ this.#flushSyncUtf8();
268
+ }
269
+ #flushSyncBuffer() {
270
+ const bufs = this.#bufs;
271
+ if (!this.#writing && this.#writingBuf.length > 0) {
272
+ bufs.unshift([this.#writingBuf]);
273
+ this.#writingBuf = emptyBuffer;
274
+ }
275
+ let buf = emptyBuffer;
276
+ while (bufs.length || buf.length) {
277
+ if (buf.length <= 0)
278
+ buf = _mergeBuffers(bufs[0], this.#lens[0]);
279
+ const n = this.#fs.writeSync(this.#fd, buf);
280
+ buf = buf.subarray(n);
281
+ this.#len = Math.max(this.#len - n, 0);
282
+ if (buf.length <= 0) {
283
+ bufs.shift();
284
+ this.#lens.shift();
285
+ }
286
+ }
287
+ }
288
+ #flushSyncUtf8() {
289
+ const bufs = this.#bufs;
290
+ if (!this.#writing && this.#writingBuf.length > 0) {
291
+ bufs.unshift(this.#writingBuf);
292
+ this.#writingBuf = '';
293
+ }
294
+ let buf = '';
295
+ while (bufs.length || buf) {
296
+ if (buf.length <= 0)
297
+ buf = bufs[0];
298
+ const n = this.#fs.writeSync(this.#fd, buf, 'utf8');
299
+ const released = _release(buf, this.#len, n);
300
+ buf = released.writingBuf;
301
+ this.#len = released.len;
302
+ if (buf.length <= 0)
303
+ bufs.shift();
304
+ }
305
+ try {
306
+ this.#fs.fsyncSync(this.#fd);
307
+ }
308
+ catch {
309
+ // Skip the error. The fd might not support fsync.
310
+ }
311
+ }
312
+ /**
313
+ * Reopen the file in place, useful for log rotation.
314
+ */
315
+ reopen(file) {
316
+ if (this.#destroyed)
317
+ throw invalidState('Utf8Stream is destroyed');
318
+ if (this.#opening) {
319
+ this.once('ready', () => this.reopen(file));
320
+ return;
321
+ }
322
+ if (this.#ending)
323
+ return;
324
+ if (!this.#file)
325
+ throw error('ERR_OPERATION_FAILED', 'Unable to reopen a file descriptor, you must pass a file to Utf8Stream');
326
+ if (file)
327
+ this.#file = file.toString();
328
+ this.#reopening = true;
329
+ if (this.#writing)
330
+ return;
331
+ const fd = this.#fd;
332
+ this.once('ready', () => {
333
+ if (fd === this.#fd)
334
+ return;
335
+ this.#fs.close(fd, err => {
336
+ if (err)
337
+ this.emit('error', err);
338
+ });
339
+ });
340
+ this.#openFile(this.#file);
341
+ }
342
+ /**
343
+ * Close the stream gracefully, flushing the internal buffer before closing.
344
+ */
345
+ end() {
346
+ if (this.#destroyed)
347
+ throw invalidState('Utf8Stream is destroyed');
348
+ if (this.#opening) {
349
+ this.once('ready', () => this.end());
350
+ return;
351
+ }
352
+ if (this.#ending)
353
+ return;
354
+ this.#ending = true;
355
+ if (this.#writing)
356
+ return;
357
+ if (this.#len > 0 && this.#fd >= 0)
358
+ this.#actualWrite();
359
+ else
360
+ this.#actualClose();
361
+ }
362
+ /**
363
+ * Close the stream immediately, without flushing the internal buffer.
364
+ */
365
+ destroy() {
366
+ if (this.#destroyed)
367
+ return;
368
+ this.#actualClose();
369
+ }
370
+ /** The mode of the file that is being written to */
371
+ get mode() {
372
+ return this.#mode;
373
+ }
374
+ /** The file that is being written to */
375
+ get file() {
376
+ return this.#file;
377
+ }
378
+ /** The file descriptor that is being written to */
379
+ get fd() {
380
+ return this.#fd;
381
+ }
382
+ /** The minimum length of the internal buffer that is required to be full before flushing */
383
+ get minLength() {
384
+ return this.#minLength;
385
+ }
386
+ /**
387
+ * The maximum length of the internal buffer.
388
+ * If a write would cause the buffer to exceed this, the data is dropped and a `drop` event is emitted with it.
389
+ */
390
+ get maxLength() {
391
+ return this.#maxLength;
392
+ }
393
+ /** Whether the stream is currently writing data to the file */
394
+ get writing() {
395
+ return this.#writing;
396
+ }
397
+ /** Whether the stream is writing synchronously or asynchronously */
398
+ get sync() {
399
+ return this.#sync;
400
+ }
401
+ /** Whether the stream performs a `fsyncSync` after every write */
402
+ get fsync() {
403
+ return this.#fsync;
404
+ }
405
+ /** Whether the stream is appending to the file or truncating it */
406
+ get append() {
407
+ return this.#append;
408
+ }
409
+ /** The number of milliseconds between flushes. `0` means no periodic flushes are performed. */
410
+ get periodicFlush() {
411
+ return this.#periodicFlush;
412
+ }
413
+ /** The type of data that can be written to the stream */
414
+ get contentMode() {
415
+ return this.#contentMode;
416
+ }
417
+ /** Whether the directory for `file` is created if it does not exist */
418
+ get mkdir() {
419
+ return this.#mkdir;
420
+ }
421
+ [Symbol.dispose]() {
422
+ this.destroy();
423
+ }
424
+ #emitDrain() {
425
+ if (!this.listenerCount('drain'))
426
+ return;
427
+ this.#asyncDrainScheduled = false;
428
+ this.emit('drain');
429
+ }
430
+ #released(err, n = 0) {
431
+ if (err) {
432
+ // Retrying is only possible for asynchronous writes.
433
+ if (!this.#sync
434
+ && (err.code === 'EAGAIN' || err.code === 'EBUSY')
435
+ && this.#retryEAGAIN(err, this.#writingBuf.length, this.#len - this.#writingBuf.length)) {
436
+ // Let's give the destination some time to process the chunk.
437
+ setTimeout(() => this.#writeToFS(), busyWriteTimeout);
438
+ }
439
+ else {
440
+ this.#writing = false;
441
+ this.emit('error', err);
442
+ }
443
+ return;
444
+ }
445
+ this.emit('write', n);
446
+ const released = _release(this.#writingBuf, this.#len, n);
447
+ this.#len = released.len;
448
+ this.#writingBuf = released.writingBuf;
449
+ if (this.#writingBuf.length) {
450
+ if (!this.#sync) {
451
+ this.#writeToFS();
452
+ return;
453
+ }
454
+ try {
455
+ do {
456
+ const written = this.#writeToFSSync();
457
+ const released = _release(this.#writingBuf, this.#len, written);
458
+ this.#len = released.len;
459
+ this.#writingBuf = released.writingBuf;
460
+ } while (this.#writingBuf.length);
461
+ }
462
+ catch (e) {
463
+ this.#released(e);
464
+ return;
465
+ }
466
+ }
467
+ if (this.#fsync)
468
+ this.#fs.fsyncSync(this.#fd);
469
+ const len = this.#len;
470
+ if (this.#reopening) {
471
+ this.#writing = false;
472
+ this.#reopening = false;
473
+ this.reopen();
474
+ }
475
+ else if (len > this.#minLength) {
476
+ this.#actualWrite();
477
+ }
478
+ else if (this.#ending) {
479
+ if (len > 0) {
480
+ this.#actualWrite();
481
+ }
482
+ else {
483
+ this.#writing = false;
484
+ this.#actualClose();
485
+ }
486
+ }
487
+ else {
488
+ this.#writing = false;
489
+ if (!this.#sync) {
490
+ this.emit('drain');
491
+ }
492
+ else if (!this.#asyncDrainScheduled) {
493
+ this.#asyncDrainScheduled = true;
494
+ queueMicrotask(() => this.#emitDrain());
495
+ }
496
+ }
497
+ }
498
+ #writeToFS() {
499
+ if (this.#contentMode == 'buffer')
500
+ this.#fs.write(this.#fd, this.#writingBuf, (err, n) => this.#released(err, n));
501
+ else
502
+ this.#fs.write(this.#fd, this.#writingBuf, 'utf8', (err, n) => this.#released(err, n));
503
+ }
504
+ #writeToFSSync() {
505
+ return this.#contentMode == 'buffer'
506
+ ? this.#fs.writeSync(this.#fd, this.#writingBuf)
507
+ : this.#fs.writeSync(this.#fd, this.#writingBuf, 'utf8');
508
+ }
509
+ #openFile(file) {
510
+ this.#opening = true;
511
+ this.#writing = true;
512
+ this.#asyncDrainScheduled = false;
513
+ // Note: `error` and `ready` are only relevant when `sync` is false.
514
+ // For sync mode, there is no way to add a listener that will receive these.
515
+ const fileOpened = (err, fd = -1) => {
516
+ if (err) {
517
+ this.#reopening = false;
518
+ this.#writing = false;
519
+ this.#opening = false;
520
+ if (!this.#sync)
521
+ this.emit('error', err);
522
+ else
523
+ queueMicrotask(() => void (this.listenerCount('error') && this.emit('error', err)));
524
+ return;
525
+ }
526
+ const reopening = this.#reopening;
527
+ this.#fd = fd;
528
+ this.#file = file;
529
+ this.#reopening = false;
530
+ this.#opening = false;
531
+ this.#writing = false;
532
+ if (!this.#sync)
533
+ this.emit('ready');
534
+ else
535
+ queueMicrotask(() => this.emit('ready'));
536
+ if (this.#destroyed)
537
+ return;
538
+ if ((!this.#writing && this.#len > this.#minLength) || this.#flushPending)
539
+ this.#actualWrite();
540
+ else if (reopening)
541
+ queueMicrotask(() => this.emit('drain'));
542
+ };
543
+ const flags = this.#append ? 'a' : 'w';
544
+ const mode = this.#mode ?? 0o666;
545
+ if (this.#sync) {
546
+ try {
547
+ if (this.#mkdir)
548
+ this.#fs.mkdirSync(dirname(file), { recursive: true });
549
+ fileOpened(null, this.#fs.openSync(file, flags, mode));
550
+ }
551
+ catch (e) {
552
+ fileOpened(e);
553
+ throw e;
554
+ }
555
+ }
556
+ else if (this.#mkdir) {
557
+ this.#fs.mkdir(dirname(file), { recursive: true }, err => {
558
+ if (err)
559
+ return fileOpened(err);
560
+ this.#fs.open(file, flags, mode, fileOpened);
561
+ });
562
+ }
563
+ else {
564
+ this.#fs.open(file, flags, mode, fileOpened);
565
+ }
566
+ }
567
+ #actualClose() {
568
+ if (this.#fd === -1) {
569
+ this.once('ready', () => this.#actualClose());
570
+ return;
571
+ }
572
+ if (this.#periodicFlushTimer !== undefined)
573
+ clearInterval(this.#periodicFlushTimer);
574
+ this.#destroyed = true;
575
+ this.#bufs = [];
576
+ this.#lens = [];
577
+ const done = (err) => {
578
+ if (err) {
579
+ this.emit('error', err);
580
+ return;
581
+ }
582
+ if (this.#ending && !this.#writing)
583
+ this.emit('finish');
584
+ this.emit('close');
585
+ };
586
+ const closeWrapped = () => {
587
+ // Don't close stdout or stderr
588
+ if (this.#fd !== 1 && this.#fd !== 2)
589
+ this.#fs.close(this.#fd, done);
590
+ else
591
+ done(null);
592
+ };
593
+ try {
594
+ // We skip errors in fsync
595
+ this.#fs.fsync(this.#fd, closeWrapped);
596
+ }
597
+ catch {
598
+ // Intentionally empty.
599
+ }
600
+ }
601
+ #actualWrite() {
602
+ this.#writing = true;
603
+ if (this.#contentMode == 'buffer') {
604
+ this.#writingBuf = this.#writingBuf.length
605
+ ? this.#writingBuf
606
+ : _mergeBuffers(this.#bufs.shift() ?? [], this.#lens.shift() ?? 0);
607
+ }
608
+ else {
609
+ this.#writingBuf ||= this.#bufs.shift() || '';
610
+ }
611
+ if (!this.#sync) {
612
+ this.#writeToFS();
613
+ return;
614
+ }
615
+ try {
616
+ this.#released(null, this.#writeToFSSync());
617
+ }
618
+ catch (e) {
619
+ this.#released(e);
620
+ }
621
+ }
622
+ #callFlushCallbackOnDrain(cb) {
623
+ this.#flushPending = true;
624
+ const onDrain = () => {
625
+ // Only if fsync is false to avoid double fsync
626
+ if (this.#fsync || this.#destroyed) {
627
+ this.#flushPending = false;
628
+ cb(null);
629
+ }
630
+ else {
631
+ try {
632
+ this.#fs.fsync(this.#fd, err => {
633
+ this.#flushPending = false;
634
+ // If the fd is closed, we ignore the error.
635
+ cb(err?.code == 'EBADF' ? null : err);
636
+ });
637
+ }
638
+ catch (e) {
639
+ this.#flushPending = false;
640
+ cb(e);
641
+ }
642
+ }
643
+ this.off('error', onError);
644
+ };
645
+ const onError = (err) => {
646
+ this.#flushPending = false;
647
+ cb(err);
648
+ this.off('drain', onDrain);
649
+ };
650
+ this.once('drain', onDrain);
651
+ this.once('error', onError);
652
+ }
653
+ // Node relies on the `newListener` event to know when a `drain` listener is added.
654
+ // `eventemitter3` does not have it, so the listener methods are patched instead.
655
+ on(event, fn, context) {
656
+ if (event === 'drain')
657
+ this.#asyncDrainScheduled = false;
658
+ return super.on(event, fn, context);
659
+ }
660
+ addListener(event, fn, context) {
661
+ return this.on(event, fn, context);
662
+ }
663
+ once(event, fn, context) {
664
+ if (event === 'drain')
665
+ this.#asyncDrainScheduled = false;
666
+ return super.once(event, fn, context);
667
+ }
668
+ off(event, fn, context, once) {
669
+ return super.off(event, fn, context, once);
670
+ }
671
+ removeListener(event, fn, context, once) {
672
+ return super.removeListener(event, fn, context, once);
673
+ }
674
+ setMaxListeners() {
675
+ throw UV('ENOSYS', 'Utf8Stream.setMaxListeners');
676
+ }
677
+ getMaxListeners() {
678
+ throw UV('ENOSYS', 'Utf8Stream.getMaxListeners');
679
+ }
680
+ prependListener() {
681
+ throw UV('ENOSYS', 'Utf8Stream.prependListener');
682
+ }
683
+ prependOnceListener() {
684
+ throw UV('ENOSYS', 'Utf8Stream.prependOnceListener');
685
+ }
686
+ rawListeners() {
687
+ throw UV('ENOSYS', 'Utf8Stream.rawListeners');
688
+ }
689
+ }
690
+ Utf8Stream;
package/dist/utils.d.ts CHANGED
@@ -30,6 +30,11 @@ export declare function normalizeMode(mode: unknown, def?: number): number;
30
30
  * @internal
31
31
  */
32
32
  export declare function validateFD(fd: number): void;
33
+ /**
34
+ * Whether an error means the path does not resolve to an entry. Used for `throwIfNoEntry`.
35
+ * @internal @hidden
36
+ */
37
+ export declare function _isNoEntry(e: unknown): boolean;
33
38
  /**
34
39
  * Normalizes a time to milliseconds since the epoch.
35
40
  * Note numbers and numeric strings are seconds, like Node's `toUnixTimestamp`.
@@ -66,3 +71,8 @@ export declare function waitOnline(worker: NodeWorker): Promise<void>;
66
71
  * @internal @hidden
67
72
  */
68
73
  export declare function _tempDirName(prefix: fs.PathLike): string;
74
+ /**
75
+ * Resolves the global `Temporal`, which is not implemented by all runtimes.
76
+ * @internal @hidden
77
+ */
78
+ export declare function _temporal(): typeof Temporal;