@gjsify/fs 0.3.21 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/lib/esm/_virtual/_rolldown/runtime.js +1 -1
  2. package/lib/esm/callback.js +1 -1
  3. package/lib/esm/cp.js +1 -1
  4. package/lib/esm/dir.js +1 -1
  5. package/lib/esm/dirent.js +1 -1
  6. package/lib/esm/encoding.js +1 -1
  7. package/lib/esm/errors.js +1 -1
  8. package/lib/esm/fd-ops.js +1 -1
  9. package/lib/esm/file-handle.js +1 -1
  10. package/lib/esm/fs-watcher.js +1 -1
  11. package/lib/esm/glob.js +1 -1
  12. package/lib/esm/read-stream.js +1 -1
  13. package/lib/esm/stat-watcher.js +1 -1
  14. package/lib/esm/statfs.js +1 -1
  15. package/lib/esm/stats.js +1 -1
  16. package/lib/esm/sync.js +1 -1
  17. package/lib/esm/utils.js +1 -1
  18. package/lib/esm/utimes.js +1 -1
  19. package/lib/esm/write-stream.js +1 -1
  20. package/package.json +51 -48
  21. package/src/callback.spec.ts +0 -296
  22. package/src/callback.ts +0 -684
  23. package/src/cp.spec.ts +0 -181
  24. package/src/cp.ts +0 -328
  25. package/src/dir.spec.ts +0 -204
  26. package/src/dir.ts +0 -199
  27. package/src/dirent.ts +0 -165
  28. package/src/encoding.ts +0 -45
  29. package/src/errors.spec.ts +0 -389
  30. package/src/errors.ts +0 -19
  31. package/src/extended.spec.ts +0 -706
  32. package/src/fd-ops.spec.ts +0 -234
  33. package/src/fd-ops.ts +0 -251
  34. package/src/file-handle.spec.ts +0 -115
  35. package/src/file-handle.ts +0 -856
  36. package/src/fs-watcher.ts +0 -198
  37. package/src/glob.spec.ts +0 -201
  38. package/src/glob.ts +0 -205
  39. package/src/index.ts +0 -313
  40. package/src/new-apis.spec.ts +0 -505
  41. package/src/promises.spec.ts +0 -812
  42. package/src/promises.ts +0 -686
  43. package/src/read-stream.ts +0 -128
  44. package/src/stat-watcher.ts +0 -116
  45. package/src/stat.spec.ts +0 -87
  46. package/src/statfs.spec.ts +0 -67
  47. package/src/statfs.ts +0 -92
  48. package/src/stats.ts +0 -207
  49. package/src/streams.spec.ts +0 -513
  50. package/src/symlink.spec.ts +0 -188
  51. package/src/sync.spec.ts +0 -377
  52. package/src/sync.ts +0 -562
  53. package/src/test.mts +0 -27
  54. package/src/types/encoding-option.ts +0 -3
  55. package/src/types/file-read-options.ts +0 -15
  56. package/src/types/file-read-result.ts +0 -4
  57. package/src/types/flag-and-open-mode.ts +0 -6
  58. package/src/types/index.ts +0 -6
  59. package/src/types/open-flags.ts +0 -14
  60. package/src/types/read-options.ts +0 -9
  61. package/src/utils.ts +0 -31
  62. package/src/utimes.spec.ts +0 -113
  63. package/src/utimes.ts +0 -97
  64. package/src/watch.spec.ts +0 -171
  65. package/src/watchfile.spec.ts +0 -185
  66. package/src/write-stream.ts +0 -142
  67. package/test/file.txt +0 -1
  68. package/tsconfig.json +0 -29
  69. package/tsconfig.tsbuildinfo +0 -1
@@ -1,856 +0,0 @@
1
- // Reference: Node.js lib/internal/fs/promises.js (FileHandle)
2
- // Reimplemented for GJS using Gio.File
3
-
4
- import { warnNotImplemented, notImplemented, createGLibFileError } from '@gjsify/utils';
5
- import { ReadStream } from "./read-stream.js";
6
- import { WriteStream } from "./write-stream.js";
7
- import { Stats, BigIntStats, STAT_ATTRIBUTES } from "./stats.js";
8
- import { getEncodingFromOptions, encodeUint8Array } from './encoding.js';
9
- import { normalizePath } from './utils.js';
10
- import { chmodSync, chownSync } from './sync.js';
11
- import GLib from '@girs/glib-2.0';
12
- import Gio from '@girs/gio-2.0';
13
- import { createInterface } from 'node:readline';
14
- // Type-only import for ReadableStream — the runtime constructor is resolved
15
- // via globalThis inside readableWebStream() to avoid bundling the entire
16
- // WHATWG streams implementation for apps that never call this method.
17
- import type { ReadableStream } from "node:stream/web";
18
- import { Buffer } from "node:buffer";
19
-
20
- import type { Abortable } from 'node:events';
21
- import type {
22
- FlagAndOpenMode,
23
- FileReadResult,
24
- FileReadOptions,
25
- OpenFlags,
26
- } from './types/index.js';
27
- import type { FileHandle as IFileHandle, CreateReadStreamOptions, CreateWriteStreamOptions } from 'node:fs/promises';
28
- import type {
29
- ObjectEncodingOptions,
30
- Mode,
31
- OpenMode,
32
- PathLike,
33
- StatOptions,
34
- WriteVResult,
35
- ReadVResult,
36
- ReadPosition,
37
- } from 'node:fs';
38
- import type { Interface as ReadlineInterface } from 'node:readline';
39
-
40
- // POSIX numeric open(2) flags (values on Linux x86-64).
41
- const O_WRONLY = 1;
42
- const O_RDWR = 2;
43
- const O_CREAT = 64;
44
- const O_TRUNC = 512;
45
- const O_APPEND = 1024;
46
-
47
- type IOMode = 'r' | 'r+' | 'w' | 'w+' | 'a' | 'a+';
48
-
49
- /**
50
- * Convert open flags (Node.js string or POSIX numeric) to a GLib.IOChannel mode.
51
- * IOChannel.new_file() takes fopen(3) modes: 'r', 'r+', 'w', 'w+', 'a', 'a+'.
52
- */
53
- function resolveIOMode(flags: OpenFlags | number | undefined): IOMode {
54
- if (flags === undefined || flags === null) return 'r';
55
- if (typeof flags === 'number') {
56
- const rdwr = (flags & O_RDWR) !== 0;
57
- const wronly = (flags & O_WRONLY) !== 0;
58
- const append = (flags & O_APPEND) !== 0;
59
- const trunc = (flags & O_TRUNC) !== 0;
60
- if (rdwr) return trunc ? 'w+' : 'r+';
61
- if (wronly) return append ? 'a' : 'w';
62
- return 'r';
63
- }
64
- // Node.js string flags — map extras to IOChannel equivalents.
65
- switch (flags) {
66
- case 'ax': case 'wx': return 'w';
67
- case 'ax+': case 'wx+': return 'w+';
68
- case 'as': case 'rs+': return 'r+';
69
- case 'as+': return 'a+';
70
- default: return flags as IOMode;
71
- }
72
- }
73
-
74
- /**
75
- * Open the file with the given IOChannel mode. When the flags request
76
- * create-if-missing + read/write without truncation (numeric O_CREAT | O_RDWR,
77
- * which maps to IOChannel 'r+' — a mode that requires the file to exist), we
78
- * catch the ENOENT and create an empty file, then retry. This avoids a TOCTOU
79
- * existence check and keeps the common "file exists" path to a single syscall.
80
- */
81
- function openIOChannel(path: string, mode: IOMode, creat: boolean): GLib.IOChannel {
82
- try {
83
- return GLib.IOChannel.new_file(path, mode);
84
- } catch (err) {
85
- const gErr = err as { code?: number } | null | undefined;
86
- if (creat && mode === 'r+' && gErr?.code === GLib.FileError.NOENT) {
87
- GLib.file_set_contents(path, new Uint8Array(0));
88
- return GLib.IOChannel.new_file(path, mode);
89
- }
90
- throw err;
91
- }
92
- }
93
-
94
- function mapOpenError(err: unknown, path: string): NodeJS.ErrnoException {
95
- // GLib.IOChannel.new_file() always throws GLib.FileError (not Gio.IOErrorEnum).
96
- return createGLibFileError(err, 'open', { path }) as NodeJS.ErrnoException;
97
- }
98
-
99
- export class FileHandle implements IFileHandle {
100
-
101
- /** Not part of the default implementation, used internal by gjsify */
102
- private _file: GLib.IOChannel;
103
-
104
- /**
105
- * Lazily-opened Gio streams for positional read() / write() so each call does
106
- * not re-load the entire file via Gio.File.load_contents(). The IOStream is
107
- * used when the handle was opened with write capability (r+, w, w+, a, a+) —
108
- * it shares seek state between input and output so writes are visible to
109
- * subsequent reads without a flush. For read-only handles we only open a
110
- * FileInputStream; trying to open_readwrite on a read-only file can fall
111
- * back to create_readwrite(REPLACE_DESTINATION) which would truncate it.
112
- */
113
- private _ioStream: Gio.FileIOStream | null = null;
114
- private _readStream: Gio.FileInputStream | null = null;
115
- private readonly _gFile: Gio.File;
116
- private readonly _ioMode: IOMode;
117
- // Serialize async I/O on the shared FileIOStream. Concurrent write_bytes_async
118
- // calls hit Gio.IOErrorEnum.PENDING ("Datenstrom hat noch einen ausstehenden
119
- // Vorgang"); overlapping seek()s on the shared cursor also corrupt positions.
120
- // random-access-file (used by fs-chunk-store / webtorrent) issues many
121
- // concurrent positional writes, so every async op on this handle chains
122
- // through _ioLock.
123
- private _ioLock: Promise<unknown> = Promise.resolve();
124
-
125
- /** Not part of the default implementation, used internal by gjsify */
126
- private static instances: {[fd: number]: FileHandle} = {};
127
-
128
- constructor(readonly options: {
129
- path: PathLike,
130
- flags?: OpenFlags | number,
131
- mode?: Mode
132
- }) {
133
- this.options.flags ||= "r";
134
- this.options.mode ||= 0o666;
135
- const pathStr = normalizePath(options.path);
136
- const creat = typeof options.flags === 'number' && (options.flags & O_CREAT) !== 0;
137
- const ioMode = resolveIOMode(options.flags);
138
- try {
139
- this._file = openIOChannel(pathStr, ioMode, creat);
140
- } catch (err: unknown) {
141
- throw mapOpenError(err, pathStr);
142
- }
143
- // Binary mode: prevent GLib from doing any character set conversion.
144
- this._file.set_encoding(null as unknown as string);
145
- this.fd = this._file.unix_get_fd();
146
- this._gFile = Gio.File.new_for_path(pathStr);
147
- this._ioMode = ioMode;
148
-
149
- FileHandle.instances[this.fd] = this;
150
- return FileHandle.getInstance(this.fd);
151
- }
152
-
153
- /**
154
- * Lazy-open the read-capable stream and return both the input stream and
155
- * its seekable view. Both FileInputStream (read-only handle) and
156
- * FileIOStream (read/write handle) implement Gio.Seekable, but we return
157
- * both to avoid callers needing to know which concrete type they got.
158
- */
159
- private _getReadStream(): { input: Gio.InputStream; seekable: Gio.Seekable } {
160
- if (this._ioStream) {
161
- return {
162
- input: this._ioStream.get_input_stream(),
163
- seekable: this._ioStream as unknown as Gio.Seekable,
164
- };
165
- }
166
- if (this._ioMode === 'r') {
167
- if (!this._readStream) this._readStream = this._gFile.read(null);
168
- return {
169
- input: this._readStream,
170
- seekable: this._readStream as unknown as Gio.Seekable,
171
- };
172
- }
173
- // open_readwrite requires the file to exist. For modes that imply
174
- // create-if-missing (w, w+, a, a+) the IOChannel already created it
175
- // above; for 'r+' openIOChannel() catches ENOENT and pre-creates it.
176
- this._ioStream = this._gFile.open_readwrite(null);
177
- return {
178
- input: this._ioStream.get_input_stream(),
179
- seekable: this._ioStream as unknown as Gio.Seekable,
180
- };
181
- }
182
-
183
- /** Lazy-open the write-capable stream (IOStream) for this handle. Only valid
184
- * when the handle was opened with a write-capable mode. */
185
- private _getWriteStream(): Gio.FileIOStream {
186
- if (this._ioStream) return this._ioStream;
187
- if (this._ioMode === 'r') {
188
- throw new Error('FileHandle opened read-only; cannot write');
189
- }
190
- this._ioStream = this._gFile.open_readwrite(null);
191
- return this._ioStream;
192
- }
193
-
194
- /** Serialize an async operation on the shared FileIOStream. */
195
- private _serialize<T>(op: () => Promise<T>): Promise<T> {
196
- const prev = this._ioLock;
197
- const next = prev.catch(() => {}).then(op);
198
- this._ioLock = next;
199
- return next;
200
- }
201
-
202
-
203
- /**
204
- * The numeric file descriptor managed by the {FileHandle} object.
205
- * @since v10.0.0
206
- */
207
- readonly fd: number;
208
-
209
- /** Not part of the default implementation, used internal by gjsify */
210
- static getInstance(fd: number) {
211
- const instance = FileHandle.instances[fd];
212
- if(!instance) {
213
- throw new Error("No instance found for fd!");
214
- }
215
- return FileHandle.instances[fd];
216
- }
217
-
218
- /**
219
- * Alias of `filehandle.writeFile()`.
220
- *
221
- * When operating on file handles, the mode cannot be changed from what it was set
222
- * to with `fsPromises.open()`. Therefore, this is equivalent to `filehandle.writeFile()`.
223
- * @since v10.0.0
224
- * @return Fulfills with `undefined` upon success.
225
- */
226
- async appendFile(data: string | Uint8Array, options?: (ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding | null): Promise<void> {
227
- const encoding = getEncodingFromOptions(options);
228
- if (typeof data === 'string') {
229
- data = Buffer.from(data);
230
- }
231
-
232
- if (encoding) this._file.set_encoding(encoding);
233
-
234
- const [status, written] = this._file.write_chars(data, data.length);
235
-
236
- if(status === GLib.IOStatus.ERROR) {
237
- throw new Error("Error on append to file!")
238
- }
239
-
240
- }
241
- /**
242
- * Changes the ownership of the file. A wrapper for [`chown(2)`](http://man7.org/linux/man-pages/man2/chown.2.html).
243
- * @since v10.0.0
244
- * @param uid The file's new owner's user id.
245
- * @param gid The file's new group's group id.
246
- * @return Fulfills with `undefined` upon success.
247
- */
248
- async chown(uid: number, gid: number): Promise<void> {
249
- chownSync(normalizePath(this.options.path), uid, gid);
250
- }
251
- /**
252
- * Modifies the permissions on the file. See [`chmod(2)`](http://man7.org/linux/man-pages/man2/chmod.2.html).
253
- * @since v10.0.0
254
- * @param mode the file mode bit mask.
255
- * @return Fulfills with `undefined` upon success.
256
- */
257
- async chmod(mode: Mode): Promise<void> {
258
- chmodSync(normalizePath(this.options.path), mode);
259
- }
260
- /**
261
- * Unlike the 16 kb default `highWaterMark` for a `stream.Readable`, the stream
262
- * returned by this method has a default `highWaterMark` of 64 kb.
263
- *
264
- * `options` can include `start` and `end` values to read a range of bytes from
265
- * the file instead of the entire file. Both `start` and `end` are inclusive and
266
- * start counting at 0, allowed values are in the
267
- * \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. If `start` is
268
- * omitted or `undefined`, `filehandle.createReadStream()` reads sequentially from
269
- * the current file position. The `encoding` can be any one of those accepted by `Buffer`.
270
- *
271
- * If the `FileHandle` points to a character device that only supports blocking
272
- * reads (such as keyboard or sound card), read operations do not finish until data
273
- * is available. This can prevent the process from exiting and the stream from
274
- * closing naturally.
275
- *
276
- * By default, the stream will emit a `'close'` event after it has been
277
- * destroyed. Set the `emitClose` option to `false` to change this behavior.
278
- *
279
- * ```js
280
- * import { open } from 'node:fs/promises';
281
- *
282
- * const fd = await open('/dev/input/event0');
283
- * // Create a stream from some character device.
284
- * const stream = fd.createReadStream();
285
- * setTimeout(() => {
286
- * stream.close(); // This may not close the stream.
287
- * // Artificially marking end-of-stream, as if the underlying resource had
288
- * // indicated end-of-file by itself, allows the stream to close.
289
- * // This does not cancel pending read operations, and if there is such an
290
- * // operation, the process may still not be able to exit successfully
291
- * // until it finishes.
292
- * stream.push(null);
293
- * stream.read(0);
294
- * }, 100);
295
- * ```
296
- *
297
- * If `autoClose` is false, then the file descriptor won't be closed, even if
298
- * there's an error. It is the application's responsibility to close it and make
299
- * sure there's no file descriptor leak. If `autoClose` is set to true (default
300
- * behavior), on `'error'` or `'end'` the file descriptor will be closed
301
- * automatically.
302
- *
303
- * An example to read the last 10 bytes of a file which is 100 bytes long:
304
- *
305
- * ```js
306
- * import { open } from 'node:fs/promises';
307
- *
308
- * const fd = await open('sample.txt');
309
- * fd.createReadStream({ start: 90, end: 99 });
310
- * ```
311
- * @since v16.11.0
312
- */
313
- createReadStream(options?: CreateReadStreamOptions): ReadStream {
314
- return new ReadStream(this.options.path, options);
315
- }
316
- /**
317
- * `options` may also include a `start` option to allow writing data at some
318
- * position past the beginning of the file, allowed values are in the
319
- * \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. Modifying a file rather than
320
- * replacing it may require the `flags` `open` option to be set to `r+` rather than
321
- * the default `r`. The `encoding` can be any one of those accepted by `Buffer`.
322
- *
323
- * If `autoClose` is set to true (default behavior) on `'error'` or `'finish'`the file descriptor will be closed automatically. If `autoClose` is false,
324
- * then the file descriptor won't be closed, even if there's an error.
325
- * It is the application's responsibility to close it and make sure there's no
326
- * file descriptor leak.
327
- *
328
- * By default, the stream will emit a `'close'` event after it has been
329
- * destroyed. Set the `emitClose` option to `false` to change this behavior.
330
- * @since v16.11.0
331
- */
332
- createWriteStream(options?: CreateWriteStreamOptions): WriteStream {
333
- return new WriteStream(this.options.path, options);
334
- }
335
- /**
336
- * Forces all currently queued I/O operations associated with the file to the
337
- * operating system's synchronized I/O completion state. Refer to the POSIX [`fdatasync(2)`](http://man7.org/linux/man-pages/man2/fdatasync.2.html) documentation for details.
338
- *
339
- * Unlike `filehandle.sync` this method does not flush modified metadata.
340
- * @since v10.0.0
341
- * @return Fulfills with `undefined` upon success.
342
- */
343
- async datasync(): Promise<void> {
344
- this._file.flush();
345
- }
346
- /**
347
- * Request that all data for the open file descriptor is flushed to the storage
348
- * device. The specific implementation is operating system and device specific.
349
- * Refer to the POSIX [`fsync(2)`](http://man7.org/linux/man-pages/man2/fsync.2.html) documentation for more detail.
350
- * @since v10.0.0
351
- * @return Fufills with `undefined` upon success.
352
- */
353
- async sync(): Promise<void> {
354
- this._file.flush();
355
- }
356
- /**
357
- * Reads data from the file and stores that in the given buffer.
358
- *
359
- * If the file is not modified concurrently, the end-of-file is reached when the
360
- * number of bytes read is zero.
361
- * @since v10.0.0
362
- * @param buffer A buffer that will be filled with the file data read.
363
- * @param offset The location in the buffer at which to start filling.
364
- * @param length The number of bytes to read.
365
- * @param position The location where to begin reading data from the file. If `null`, data will be read from the current file position, and the position will be updated. If `position` is an
366
- * integer, the current file position will remain unchanged.
367
- * @return Fulfills upon success with an object with two properties:
368
- */
369
- async read<T extends NodeJS.ArrayBufferView>(buffer: T, offset?: number | null, length?: number | null, position?: ReadPosition | null): Promise<FileReadResult<T>>
370
- async read<T extends NodeJS.ArrayBufferView = Buffer>(options?: FileReadOptions<T>): Promise<FileReadResult<T>>
371
-
372
- async read<T extends NodeJS.ArrayBufferView = Buffer>(...args: any[]): Promise<FileReadResult<T>> {
373
- let buffer: T | undefined;
374
- let offset: number | null | undefined;
375
- let length: number | null | undefined;
376
- let position: number | null | undefined;
377
-
378
- if (typeof args[0] === 'object' && !(args[0] instanceof Uint8Array) && !(args[0] instanceof Buffer)) {
379
- const options: FileReadOptions<T> = args[0];
380
- buffer = options.buffer;
381
- offset = options.offset;
382
- length = options.length;
383
- position = options.position;
384
- } else {
385
- buffer = args[0];
386
- offset = args[1];
387
- length = args[2];
388
- position = args[3];
389
- }
390
-
391
- const bufView = buffer as unknown as Uint8Array;
392
- const bufOffset = offset ?? 0;
393
- const readLength = length ?? bufView?.byteLength ?? 65536;
394
- const startPos = (position as number | null) ?? 0;
395
-
396
- // Positional read — seek + read_bytes on the appropriate Gio stream,
397
- // touching only the requested region. Replaces the old load_contents()
398
- // path that read the entire file on every call (O(N²) over streamed
399
- // workloads like WebTorrent piece hashing or random-access-file).
400
- // Serialized with writes: seek() on the shared FileIOStream cursor must
401
- // not interleave with a pending write_bytes_async.
402
- return this._serialize(async () => {
403
- const { input, seekable } = this._getReadStream();
404
- seekable.seek(BigInt(startPos), GLib.SeekType.SET, null);
405
- const bytes = input.read_bytes(readLength, null);
406
- const data = bytes.get_data() as Uint8Array | null;
407
- const bytesRead = data?.length ?? 0;
408
- if (bufView && data && bytesRead > 0) {
409
- bufView.set(data, bufOffset);
410
- }
411
- return { bytesRead, buffer: buffer as T };
412
- });
413
- }
414
- /**
415
- * Returns a `ReadableStream` that may be used to read the files data.
416
- *
417
- * An error will be thrown if this method is called more than once or is called after the `FileHandle` is closed
418
- * or closing.
419
- *
420
- * ```js
421
- * import { open } from 'node:fs/promises';
422
- *
423
- * const file = await open('./some/file/to/read');
424
- *
425
- * for await (const chunk of file.readableWebStream())
426
- * console.log(chunk);
427
- *
428
- * await file.close();
429
- * ```
430
- *
431
- * While the `ReadableStream` will read the file to completion, it will not close the `FileHandle` automatically. User code must still call the `fileHandle.close()` method.
432
- *
433
- * @since v17.0.0
434
- * @experimental
435
- */
436
- readableWebStream(): ReadableStream {
437
- // Resolve ReadableStream lazily from globalThis to keep the
438
- // WHATWG streams implementation out of the bundle when this method
439
- // is not actually used.
440
- const Ctor = (globalThis as { ReadableStream?: typeof globalThis.ReadableStream }).ReadableStream;
441
- if (typeof Ctor !== 'function') {
442
- throw new Error(
443
- 'readableWebStream() requires a global ReadableStream. Import "node:stream/web" or "@gjsify/streams" before calling this method.',
444
- );
445
- }
446
- return new Ctor() as unknown as ReadableStream;
447
- }
448
- /**
449
- * Asynchronously reads the entire contents of a file.
450
- *
451
- * If `options` is a string, then it specifies the `encoding`.
452
- *
453
- * The `FileHandle` has to support reading.
454
- *
455
- * If one or more `filehandle.read()` calls are made on a file handle and then a`filehandle.readFile()` call is made, the data will be read from the current
456
- * position till the end of the file. It doesn't always read from the beginning
457
- * of the file.
458
- * @since v10.0.0
459
- * @return Fulfills upon a successful read with the contents of the file. If no encoding is specified (using `options.encoding`), the data is returned as a {Buffer} object. Otherwise, the
460
- * data will be a string.
461
- */
462
- async readFile(
463
- options?: {
464
- encoding?: null | undefined;
465
- flag?: OpenMode | undefined;
466
- } | null
467
- ): Promise<Buffer<ArrayBuffer>>
468
- /**
469
- * Asynchronously reads the entire contents of a file. The underlying file will _not_ be closed automatically.
470
- * The `FileHandle` must have been opened for reading.
471
- * @param options An object that may contain an optional flag.
472
- * If a flag is not provided, it defaults to `'r'`.
473
- */
474
- async readFile(
475
- options:
476
- | {
477
- encoding: BufferEncoding;
478
- flag?: OpenMode | undefined;
479
- }
480
- | BufferEncoding
481
- ): Promise<string>
482
- /**
483
- * Asynchronously reads the entire contents of a file. The underlying file will _not_ be closed automatically.
484
- * The `FileHandle` must have been opened for reading.
485
- * @param options An object that may contain an optional flag.
486
- * If a flag is not provided, it defaults to `'r'`.
487
- */
488
- async readFile(
489
- options?:
490
- | (ObjectEncodingOptions & {
491
- flag?: OpenMode | undefined;
492
- })
493
- | BufferEncoding
494
- | null
495
- ): Promise<string | Buffer<ArrayBuffer>> {
496
- const encoding = getEncodingFromOptions(options, 'buffer');
497
- if (encoding) this._file.set_encoding(encoding);
498
-
499
- const [status, buf] = this._file.read_to_end();
500
-
501
- if(status === GLib.IOStatus.ERROR) {
502
- throw new Error("Error on read from file!")
503
- }
504
-
505
- const res = encodeUint8Array(encoding, buf);
506
-
507
- return res;
508
- }
509
- /**
510
- * Convenience method to create a `readline` interface and stream over the file. For example:
511
- *
512
- * ```js
513
- * import { open } from 'node:fs/promises';
514
- *
515
- * const file = await open('./some/file/to/read');
516
- *
517
- * for await (const line of file.readLines()) {
518
- * console.log(line);
519
- * }
520
- * ```
521
- *
522
- * @since v18.11.0
523
- * @param options See `filehandle.createReadStream()` for the options.
524
- */
525
- readLines(options?: CreateReadStreamOptions): ReadlineInterface {
526
- return createInterface({ input: this.createReadStream(options), crlfDelay: Infinity });
527
- }
528
- /**
529
- * @since v10.0.0
530
- * @return Fulfills with an {fs.Stats} for the file.
531
- */
532
- async stat(
533
- opts?: StatOptions & {
534
- bigint?: false | undefined;
535
- }
536
- ): Promise<Stats>
537
- async stat(
538
- opts: StatOptions & {
539
- bigint: true;
540
- }
541
- ): Promise<BigIntStats>
542
- async stat(opts?: StatOptions): Promise<Stats | BigIntStats> {
543
- const info = await new Promise<Gio.FileInfo>((resolve, reject) => {
544
- this._gFile.query_info_async(STAT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null, (_s: unknown, res: Gio.AsyncResult) => {
545
- try { resolve(this._gFile.query_info_finish(res)); } catch (e) { reject(e); }
546
- });
547
- });
548
- const pathStr = normalizePath(this.options.path);
549
- return opts?.bigint
550
- ? new BigIntStats(info, pathStr)
551
- : new Stats(info, pathStr);
552
- }
553
- /**
554
- * Truncates the file.
555
- *
556
- * If the file was larger than `len` bytes, only the first `len` bytes will be
557
- * retained in the file.
558
- *
559
- * The following example retains only the first four bytes of the file:
560
- *
561
- * ```js
562
- * import { open } from 'node:fs/promises';
563
- *
564
- * let filehandle = null;
565
- * try {
566
- * filehandle = await open('temp.txt', 'r+');
567
- * await filehandle.truncate(4);
568
- * } finally {
569
- * await filehandle?.close();
570
- * }
571
- * ```
572
- *
573
- * If the file previously was shorter than `len` bytes, it is extended, and the
574
- * extended part is filled with null bytes (`'\0'`):
575
- *
576
- * If `len` is negative then `0` will be used.
577
- * @since v10.0.0
578
- * @param [len=0]
579
- * @return Fulfills with `undefined` upon success.
580
- */
581
- async truncate(len: number = 0): Promise<void> {
582
- const effectiveLen = Math.max(0, len);
583
- this._file.flush();
584
- // Gio.FileOutputStream implements Seekable.truncate — extends with zeros
585
- // when growing, matches POSIX ftruncate(2).
586
- const out = this._getWriteStream().get_output_stream() as Gio.FileOutputStream;
587
- out.truncate(effectiveLen, null);
588
- }
589
- /**
590
- * Change the file system timestamps of the object referenced by the `FileHandle` then resolves the promise with no arguments upon success.
591
- * @since v10.0.0
592
- */
593
- async utimes(atime: string | number | Date, mtime: string | number | Date): Promise<void> {
594
- const { utimesSync } = await import('./utimes.js');
595
- utimesSync(normalizePath(this.options.path), atime, mtime);
596
- }
597
- /**
598
- * Asynchronously writes data to a file, replacing the file if it already exists.`data` can be a string, a buffer, an
599
- * [AsyncIterable](https://tc39.github.io/ecma262/#sec-asynciterable-interface) or
600
- * [Iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol) object.
601
- * The promise is resolved with no arguments upon success.
602
- *
603
- * If `options` is a string, then it specifies the `encoding`.
604
- *
605
- * The `FileHandle` has to support writing.
606
- *
607
- * It is unsafe to use `filehandle.writeFile()` multiple times on the same file
608
- * without waiting for the promise to be resolved (or rejected).
609
- *
610
- * If one or more `filehandle.write()` calls are made on a file handle and then a`filehandle.writeFile()` call is made, the data will be written from the
611
- * current position till the end of the file. It doesn't always write from the
612
- * beginning of the file.
613
- * @since v10.0.0
614
- */
615
- async writeFile(data: string | Uint8Array, options?: (ObjectEncodingOptions & FlagAndOpenMode & Abortable) | BufferEncoding | null): Promise<void> {
616
- const encoding = getEncodingFromOptions(options);
617
- let buf: Uint8Array;
618
- if (typeof data === 'string') {
619
- buf = Buffer.from(data, (encoding as BufferEncoding) || 'utf8');
620
- } else {
621
- buf = data;
622
- }
623
- this._file.seek_position(0, GLib.SeekType.SET);
624
- const [status] = this._file.write_chars(buf, buf.length);
625
- if (status === GLib.IOStatus.ERROR) {
626
- throw new Error("Error writing to file!");
627
- }
628
- this._file.flush();
629
- }
630
- /**
631
- * Write `buffer` to the file.
632
- *
633
- * The promise is resolved with an object containing two properties:
634
- *
635
- * It is unsafe to use `filehandle.write()` multiple times on the same file
636
- * without waiting for the promise to be resolved (or rejected). For this
637
- * scenario, use `filehandle.createWriteStream()`.
638
- *
639
- * On Linux, positional writes do not work when the file is opened in append mode.
640
- * The kernel ignores the position argument and always appends the data to
641
- * the end of the file.
642
- * @since v10.0.0
643
- * @param [offset=0] The start position from within `buffer` where the data to write begins.
644
- * @param [length=buffer.byteLength - offset] The number of bytes from `buffer` to write.
645
- * @param position The offset from the beginning of the file where the data from `buffer` should be written. If `position` is not a `number`, the data will be written at the current position.
646
- * See the POSIX pwrite(2) documentation for more detail.
647
- */
648
- async write<TBuffer extends NodeJS.ArrayBufferView>(
649
- buffer: TBuffer,
650
- offset?: number | null,
651
- length?: number | null,
652
- position?: number | null
653
- ): Promise<{
654
- bytesWritten: number;
655
- buffer: TBuffer;
656
- }>;
657
- async write<TBuffer extends Uint8Array>(
658
- buffer: TBuffer,
659
- options?: { offset?: number; length?: number; position?: number },
660
- ): Promise<{
661
- bytesWritten: number;
662
- buffer: TBuffer;
663
- }>;
664
- async write(
665
- data: string,
666
- position?: number | null,
667
- encoding?: BufferEncoding | null
668
- ): Promise<{
669
- bytesWritten: number;
670
- buffer: string;
671
- }>
672
- async write<TBuffer extends NodeJS.ArrayBufferView>(
673
- data: string | TBuffer,
674
- ...args: any[]
675
- ): Promise<{
676
- bytesWritten: number;
677
- buffer: string | TBuffer;
678
- }> {
679
- let position: number | null = null;
680
- let encoding: BufferEncoding | 'buffer' | null = null;
681
- let offset: number | null = null;
682
- let length: number | null = null;
683
-
684
- if(typeof data === 'string') {
685
- position = args[0];
686
- encoding = args[1];
687
- } else {
688
- offset = args[0];
689
- length = args[1];
690
- position = args[2];
691
- }
692
-
693
- encoding = getEncodingFromOptions(encoding, typeof data === 'string' ? 'utf8' : null);
694
-
695
- // Convert data to Uint8Array bytes
696
- let writeBuf: Uint8Array;
697
- if (typeof data === 'string') {
698
- writeBuf = new TextEncoder().encode(data);
699
- } else {
700
- writeBuf = data as unknown as Uint8Array;
701
- }
702
- const bufOffset = offset ?? 0;
703
- const writeLength = length ?? (writeBuf.byteLength - bufOffset);
704
- const writeSlice = writeBuf.slice(bufOffset, bufOffset + writeLength);
705
- const writePos = position ?? 0;
706
-
707
- // Positional write — seek + write_bytes_async on the IOStream, touches
708
- // only the requested region. Uses async Gio I/O so the GLib main loop
709
- // (and GTK events) are not blocked during the write. Serialized via
710
- // _serialize() so concurrent callers (e.g. random-access-file) don't
711
- // trigger GIO_ERROR_PENDING or corrupt the shared seek cursor.
712
- const bytesWritten = await this._serialize(async () => {
713
- const stream = this._getWriteStream();
714
- stream.seek(BigInt(writePos), GLib.SeekType.SET, null);
715
- const output = stream.get_output_stream();
716
- const written = await new Promise<number>((resolve, reject) => {
717
- output.write_bytes_async(new GLib.Bytes(writeSlice), GLib.PRIORITY_DEFAULT, null, (_source, asyncResult) => {
718
- try { resolve(output.write_bytes_finish(asyncResult)); }
719
- catch (err) { reject(err); }
720
- });
721
- });
722
- await new Promise<void>((resolve, reject) => {
723
- output.flush_async(GLib.PRIORITY_DEFAULT, null, (_source, asyncResult) => {
724
- try { output.flush_finish(asyncResult); resolve(); }
725
- catch (err) { reject(err); }
726
- });
727
- });
728
- return written;
729
- });
730
-
731
- return {
732
- bytesWritten,
733
- buffer: data
734
- }
735
- }
736
-
737
-
738
- /**
739
- * Write an array of [ArrayBufferView](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView) s to the file.
740
- *
741
- * The promise is resolved with an object containing a two properties:
742
- *
743
- * It is unsafe to call `writev()` multiple times on the same file without waiting
744
- * for the promise to be resolved (or rejected).
745
- *
746
- * On Linux, positional writes don't work when the file is opened in append mode.
747
- * The kernel ignores the position argument and always appends the data to
748
- * the end of the file.
749
- * @since v12.9.0
750
- * @param position The offset from the beginning of the file where the data from `buffers` should be written. If `position` is not a `number`, the data will be written at the current
751
- * position.
752
- */
753
- async writev<TBuffers extends readonly NodeJS.ArrayBufferView[]>(buffers: TBuffers, position?: number): Promise<WriteVResult<TBuffers>> {
754
- let bytesWritten = 0;
755
- for (const buf of buffers) {
756
- const b = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength);
757
- const res = await this.write(
758
- b, 0, b.byteLength,
759
- position != null ? position + bytesWritten : null,
760
- );
761
- bytesWritten += res.bytesWritten;
762
- }
763
- return { bytesWritten, buffers: buffers as unknown as TBuffers };
764
- }
765
- /**
766
- * Read from a file and write to an array of [ArrayBufferView](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView) s
767
- * @since v13.13.0, v12.17.0
768
- * @param position The offset from the beginning of the file where the data should be read from. If `position` is not a `number`, the data will be read from the current position.
769
- * @return Fulfills upon success an object containing two properties:
770
- */
771
- async readv<TBuffers extends readonly NodeJS.ArrayBufferView[]>(buffers: TBuffers, position?: number): Promise<ReadVResult<TBuffers>> {
772
- let bytesRead = 0;
773
- for (const buf of buffers) {
774
- const res = await this.read({
775
- buffer: Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength),
776
- position: position != null ? position + bytesRead : null,
777
- });
778
- bytesRead += res.bytesRead;
779
- if (res.bytesRead < buf.byteLength) break;
780
- }
781
- return { bytesRead, buffers: buffers as unknown as TBuffers };
782
- }
783
- /** @internal */ _flushSync(): void {
784
- this._file.flush();
785
- }
786
-
787
- /** @internal */ _closeSync(): void {
788
- try { this._ioStream?.close(null); } catch { /* best-effort */ }
789
- try { this._readStream?.close(null); } catch { /* best-effort */ }
790
- this._ioStream = null;
791
- this._readStream = null;
792
- try { this._file.shutdown(true); } catch { /* best-effort */ }
793
- delete (FileHandle as any).instances[this.fd];
794
- }
795
-
796
- /** @internal */ _readSync(buffer: NodeJS.ArrayBufferView, offset: number, length: number, position: number | null): number {
797
- const stream = this._gFile.read(null);
798
- try {
799
- if (position !== null && position >= 0) {
800
- (stream as unknown as Gio.Seekable).seek(position, GLib.SeekType.SET, null);
801
- }
802
- const bytes = stream.read_bytes(length, null);
803
- const arr = bytes.get_data()!;
804
- new Uint8Array((buffer as any).buffer, (buffer as any).byteOffset + offset).set(arr.subarray(0, arr.length));
805
- return arr.length;
806
- } finally {
807
- stream.close(null);
808
- }
809
- }
810
-
811
- /** @internal */ _writeSync(data: Uint8Array, position: number | null): number {
812
- const stream = this._gFile.open_readwrite(null);
813
- try {
814
- if (position !== null && position >= 0) {
815
- (stream as unknown as Gio.Seekable).seek(position, GLib.SeekType.SET, null);
816
- }
817
- return stream.get_output_stream().write_bytes(GLib.Bytes.new(data), null);
818
- } finally {
819
- stream.close(null);
820
- }
821
- }
822
-
823
- /**
824
- * Closes the file handle after waiting for any pending operation on the handle to
825
- * complete.
826
- *
827
- * ```js
828
- * import { open } from 'node:fs/promises';
829
- *
830
- * let filehandle;
831
- * try {
832
- * filehandle = await open('thefile.txt', 'r');
833
- * } finally {
834
- * await filehandle?.close();
835
- * }
836
- * ```
837
- * @since v10.0.0
838
- * @return Fulfills with `undefined` upon success.
839
- */
840
- async close(): Promise<void> {
841
- // Close the Gio streams first; they own an fd wrapping the same file
842
- // as the IOChannel. IOChannel.shutdown(true) flushes + closes its own
843
- // fd — safe to call even if the Gio streams already released theirs,
844
- // but guarded here so a throw from shutdown doesn't strand the stream
845
- // references in a "closed but still pinned" state.
846
- try { this._ioStream?.close(null); } catch { /* best-effort */ }
847
- try { this._readStream?.close(null); } catch { /* best-effort */ }
848
- this._ioStream = null;
849
- this._readStream = null;
850
- try { this._file.shutdown(true); } catch { /* best-effort */ }
851
- }
852
-
853
- async [Symbol.asyncDispose](): Promise<void> {
854
- await this.close();
855
- }
856
- }