@gjsify/fs 0.36.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/file-handle.js +1 -1
- package/lib/types/fd-io.d.ts +90 -137
- package/lib/types/file-handle.d.ts +68 -299
- package/lib/types/sync.d.ts +27 -69
- package/package.json +8 -8
package/lib/esm/file-handle.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{encodeUint8Array as e,getEncodingFromOptions as t}from"./encoding.js";import{normalizePath as n}from"./utils.js";import{ReadStream as r}from"./read-stream.js";import{normalizeMode as i,parseOpenFlags as a}from"./posix-flags.js";import{WriteStream as o}from"./write-stream.js";import{closeFd as s,fdPath as c,fsError as l,fsyncFd as u,isSeekableFd as d,openFd as f,readFd as p,releaseFd as m,seekFd as h,sizeOfFd as g,tellFd as _,truncateFd as v,writeFd as y}from"./fd-io.js";import{STAT_ATTRIBUTES as b,statsFrom as x}from"./stats.js";import{chmodSync as S,chownSync as C}from"./sync.js";import w from"@girs/glib-2.0";import T from"@girs/gio-2.0";import{Buffer as E}from"node:buffer";import{createInterface as D}from"node:readline";var O=class FileHandle{options;_gFile;_pathStr;_spec;_pos=0;_closed=!1;static instances={};constructor(e){this.options=e,this.options.flags??=`r`;let t=n(e.path);this._spec=a(this.options.flags),this.options.mode??=438;let r=i(this.options.mode,438);return this.fd=f(t,this._spec,r),this._pathStr=t,this._gFile=T.File.new_for_path(t),FileHandle.instances[this.fd]=this,FileHandle.getInstance(this.fd)}_readCore(e,t,n,r){if(this._assertOpen(`read`),!this._spec.readable)throw l(`EBADF`,`read`,this._pathStr);if(n>e.byteLength-t){let r=RangeError(`The value of "length" is out of range. It must be <= ${e.byteLength-t}. Received ${n}`);throw r.code=`ERR_OUT_OF_RANGE`,r}let i=r!=null&&r>=0,a=d(this.fd,this._pathStr);if(i&&!a)throw l(`ESPIPE`,`read`,this._pathStr);let o=i?r:this._pos;a&&h(this.fd,o);let s=p(this.fd,n);return s.length>0&&new Uint8Array(e.buffer,e.byteOffset+t,s.length).set(s),!i&&a&&(this._pos=o+s.length),s.length}_writeCore(e,t){if(this._assertOpen(`write`),!this._spec.writable)throw l(`EBADF`,`write`,this._pathStr);if(this._spec.append){let t=y(this.fd,e);return this._pos=_(this.fd)??g(this.fd,this._pathStr),t}let n=t!=null&&t>=0,r=d(this.fd,this._pathStr);if(n&&!r)throw l(`ESPIPE`,`write`,this._pathStr);let i=n?t:this._pos;r&&h(this.fd,i);let a=y(this.fd,e);return!n&&r&&(this._pos=i+a),a}_assertOpen(e){if(this._closed)throw l(`EBADF`,e,this._pathStr)}_truncateCore(e){if(this._assertOpen(`ftruncate`),!this._spec.writable)throw l(`EINVAL`,`ftruncate`,this._pathStr);v(this.fd,Math.max(0,e),this._pathStr)}_readToEnd(){let e=[],t=0;for(;;){let n=new Uint8Array(64*1024),r=this._readCore(n,0,n.length,null);if(r===0)break;e.push(n.subarray(0,r)),t+=r}let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return n}_fdTarget(){return c(this.fd)??this._pathStr}fd;static getInstance(e,t=`read`){let n=e instanceof FileHandle?e.fd:e,r=FileHandle.instances[n];if(!r)throw l(`EBADF`,t);return r}async appendFile(e,t){await this.writeFile(e,t)}async chown(e,t){this._assertOpen(`fchown`),C(n(this.options.path),e,t)}async chmod(e){this._assertOpen(`fchmod`),S(this._fdTarget(),e)}createReadStream(e){return new r(this.options.path,{...e,fd:this.fd})}createWriteStream(e){return new o(this.options.path,{...e,fd:this.fd,fdFromHandle:!0})}async datasync(){this._assertOpen(`fdatasync`),u(this.fd)}async sync(){this._assertOpen(`fsync`),u(this.fd)}async read(...e){let t,n,r,i;if(typeof e[0]==`object`&&!(e[0]instanceof Uint8Array)&&!(e[0]instanceof E)){let a=e[0];t=a.buffer,n=a.offset,r=a.length,i=a.position}else t=e[0],n=e[1],r=e[2],i=e[3];let a=t,o=n??0,s=r??(a?a.byteLength-o:65536);return{bytesRead:this._readCore(t,o,s,i??null),buffer:t}}readableWebStream(){let e=globalThis.ReadableStream;if(typeof e!=`function`)throw Error(`readableWebStream() requires a global ReadableStream. Import "node:stream/web" or "@gjsify/streams" before calling this method.`);return new e}async readFile(n){return e(t(n,`buffer`),this._readToEnd())}readLines(e){return D({input:this.createReadStream(e),crlfDelay:1/0})}async stat(e){this._assertOpen(`fstat`);let t=T.File.new_for_path(this._fdTarget());return x(await new Promise((e,n)=>{t.query_info_async(b,T.FileQueryInfoFlags.NONE,w.PRIORITY_DEFAULT,null,(r,i)=>{try{e(t.query_info_finish(i))}catch(e){n(e)}})}),this._pathStr,`fstat`,e?.bigint)}async truncate(e=0){this._truncateCore(e)}async utimes(e,t){this._assertOpen(`futimes`);let{utimesSync:r}=await import(`./utimes.js`);r(n(this.options.path),e,t)}async writeFile(e,n){let r=t(n),i;i=typeof e==`string`?E.from(e,r||`utf8`):e,this._writeCore(i,null)}async write(e,...n){let r=null,i=null,a=null,o=null;typeof e==`string`?(r=n[0],i=n[1]):(a=n[0],o=n[1],r=n[2]),i=t(i,typeof e==`string`?`utf8`:null);let s;s=typeof e==`string`?new TextEncoder().encode(e):e;let c=a??0,l=o??s.byteLength-c,u=s.subarray(c,c+l);return{bytesWritten:this._writeCore(u,r??null),buffer:e}}async writev(e,t){let n=0;for(let r of e){let e=E.from(r.buffer,r.byteOffset,r.byteLength),i=await this.write(e,0,e.byteLength,t==null?null:t+n);n+=i.bytesWritten}return{bytesWritten:n,buffers:e}}async readv(e,t){let n=0;for(let r of e){let e=await this.read({buffer:E.from(r.buffer,r.byteOffset,r.byteLength),position:t==null?null:t+n});if(n+=e.bytesRead,e.bytesRead<r.byteLength)break}return{bytesRead:n,buffers:e}}_flushSync(){u(this.fd)}_closeSync(){this._teardown()}_readSync(e,t,n,r){return this._readCore(e,t,n,r)}_writeSync(e,t){return this._writeCore(e,t)}_readToEndSync(){return this._readToEnd()}_truncateSync(e){this._truncateCore(e)}_fdStatTarget(){return this._fdTarget()}_teardown(){if(this._closed)return;this._closed=!0,m(this.fd),s(this.fd);let e=FileHandle;delete e.instances[this.fd]}async close(){this._teardown()}async[Symbol.asyncDispose](){await this.close()}pull(...e){throw Error('FileHandle.pull() is not implemented in @gjsify/fs yet — requires `node:stream/iter` (Node 25.9+) which @gjsify/stream has not ported. Track gjsify Open TODO "Node 25 stream/iter integration".')}writer(e){throw Error('FileHandle.writer() is not implemented in @gjsify/fs yet — requires `node:stream/iter` (Node 25.9+) which @gjsify/stream has not ported. Track gjsify Open TODO "Node 25 stream/iter integration".')}};export{O as FileHandle};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{encodeUint8Array as e,getEncodingFromOptions as t}from"./encoding.js";import{normalizePath as n}from"./utils.js";import{ReadStream as r}from"./read-stream.js";import{normalizeMode as i,parseOpenFlags as a}from"./posix-flags.js";import{WriteStream as o}from"./write-stream.js";import{closeFd as s,fdPath as c,fsError as l,fsyncFd as u,isSeekableFd as d,openFd as f,readFd as p,releaseFd as m,seekFd as h,sizeOfFd as g,tellFd as _,truncateFd as v,writeFd as y}from"./fd-io.js";import{STAT_ATTRIBUTES as b,statsFrom as x}from"./stats.js";import{chmodSync as S,chownSync as C}from"./sync.js";import w from"@girs/glib-2.0";import T from"@girs/gio-2.0";import{Buffer as E}from"node:buffer";import{createInterface as D}from"node:readline";var O=class FileHandle{options;_gFile;_pathStr;_spec;_pos=0;_closed=!1;static instances={};constructor(e){this.options=e,this.options.flags??=`r`;let t=n(e.path);this._spec=a(this.options.flags),this.options.mode??=438;let r=i(this.options.mode,438);return this.fd=f(t,this._spec,r),this._pathStr=t,this._gFile=T.File.new_for_path(t),FileHandle.instances[this.fd]=this,FileHandle.getInstance(this.fd)}_readCore(e,t,n,r){if(this._assertOpen(`read`),!this._spec.readable)throw l(`EBADF`,`read`,this._pathStr);if(n>e.byteLength-t){let r=RangeError(`The value of "length" is out of range. It must be <= ${e.byteLength-t}. Received ${n}`);throw r.code=`ERR_OUT_OF_RANGE`,r}let i=r!=null&&r>=0,a=d(this.fd,this._pathStr);if(i&&!a)throw l(`ESPIPE`,`read`,this._pathStr);let o=i?r:this._pos;a&&h(this.fd,o);let s=p(this.fd,n);return s.length>0&&new Uint8Array(e.buffer,e.byteOffset+t,s.length).set(s),!i&&a&&(this._pos=o+s.length),s.length}_writeCore(e,t){if(this._assertOpen(`write`),!this._spec.writable)throw l(`EBADF`,`write`,this._pathStr);if(this._spec.append){let t=y(this.fd,e);return this._pos=_(this.fd)??g(this.fd,this._pathStr),t}let n=t!=null&&t>=0,r=d(this.fd,this._pathStr);if(n&&!r)throw l(`ESPIPE`,`write`,this._pathStr);let i=n?t:this._pos;r&&h(this.fd,i);let a=y(this.fd,e);return!n&&r&&(this._pos=i+a),a}_assertOpen(e){if(this._closed)throw l(`EBADF`,e,this._pathStr)}_truncateCore(e){if(this._assertOpen(`ftruncate`),!this._spec.writable)throw l(`EINVAL`,`ftruncate`,this._pathStr);v(this.fd,Math.max(0,e),this._pathStr)}_readToEnd(){let e=[],t=0;for(;;){let n=new Uint8Array(64*1024),r=this._readCore(n,0,n.length,null);if(r===0)break;e.push(n.subarray(0,r)),t+=r}let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return n}_fdTarget(){return c(this.fd)??this._pathStr}fd;static getInstance(e,t=`read`){let n=e instanceof FileHandle?e.fd:e,r=FileHandle.instances[n];if(!r)throw l(`EBADF`,t);return r}async appendFile(e,t){await this.writeFile(e,t)}async chown(e,t){this._assertOpen(`fchown`),C(n(this.options.path),e,t)}async chmod(e){this._assertOpen(`fchmod`),S(this._fdTarget(),e)}createReadStream(e){return new r(this.options.path,{...e,fd:this.fd})}createWriteStream(e){return new o(this.options.path,{...e,fd:this.fd,fdFromHandle:!0})}async datasync(){this._assertOpen(`fdatasync`),u(this.fd)}async sync(){this._assertOpen(`fsync`),u(this.fd)}async read(...e){let t,n,r,i;if(typeof e[0]==`object`&&!(e[0]instanceof Uint8Array)&&!(e[0]instanceof E)){let a=e[0];t=a.buffer,n=a.offset,r=a.length,i=a.position}else t=e[0],n=e[1],r=e[2],i=e[3];let a=t,o=n??0,s=r??(a?a.byteLength-o:65536);return{bytesRead:this._readCore(t,o,s,i??null),buffer:t}}readableWebStream(){let e=globalThis.ReadableStream;if(typeof e!=`function`)throw Error(`readableWebStream() requires a global ReadableStream. Import "node:stream/web" or "@gjsify/web-streams/register" before calling this method.`);return new e}async readFile(n){return e(t(n,`buffer`),this._readToEnd())}readLines(e){return D({input:this.createReadStream(e),crlfDelay:1/0})}async stat(e){this._assertOpen(`fstat`);let t=T.File.new_for_path(this._fdTarget());return x(await new Promise((e,n)=>{t.query_info_async(b,T.FileQueryInfoFlags.NONE,w.PRIORITY_DEFAULT,null,(r,i)=>{try{e(t.query_info_finish(i))}catch(e){n(e)}})}),this._pathStr,`fstat`,e?.bigint)}async truncate(e=0){this._truncateCore(e)}async utimes(e,t){this._assertOpen(`futimes`);let{utimesSync:r}=await import(`./utimes.js`);r(n(this.options.path),e,t)}async writeFile(e,n){let r=t(n),i;i=typeof e==`string`?E.from(e,r||`utf8`):e,this._writeCore(i,null)}async write(e,...n){let r=null,i=null,a=null,o=null;typeof e==`string`?(r=n[0],i=n[1]):(a=n[0],o=n[1],r=n[2]),i=t(i,typeof e==`string`?`utf8`:null);let s;s=typeof e==`string`?new TextEncoder().encode(e):e;let c=a??0,l=o??s.byteLength-c,u=s.subarray(c,c+l);return{bytesWritten:this._writeCore(u,r??null),buffer:e}}async writev(e,t){let n=0;for(let r of e){let e=E.from(r.buffer,r.byteOffset,r.byteLength),i=await this.write(e,0,e.byteLength,t==null?null:t+n);n+=i.bytesWritten}return{bytesWritten:n,buffers:e}}async readv(e,t){let n=0;for(let r of e){let e=await this.read({buffer:E.from(r.buffer,r.byteOffset,r.byteLength),position:t==null?null:t+n});if(n+=e.bytesRead,e.bytesRead<r.byteLength)break}return{bytesRead:n,buffers:e}}_flushSync(){u(this.fd)}_closeSync(){this._teardown()}_readSync(e,t,n,r){return this._readCore(e,t,n,r)}_writeSync(e,t){return this._writeCore(e,t)}_readToEndSync(){return this._readToEnd()}_truncateSync(e){this._truncateCore(e)}_fdStatTarget(){return this._fdTarget()}_teardown(){if(this._closed)return;this._closed=!0,m(this.fd),s(this.fd);let e=FileHandle;delete e.instances[this.fd]}async close(){this._teardown()}async[Symbol.asyncDispose](){await this.close()}pull(...e){throw Error('FileHandle.pull() is not implemented in @gjsify/fs yet — requires `node:stream/iter` (Node 25.9+) which @gjsify/stream has not ported. Track gjsify Open TODO "Node 25 stream/iter integration".')}writer(e){throw Error('FileHandle.writer() is not implemented in @gjsify/fs yet — requires `node:stream/iter` (Node 25.9+) which @gjsify/stream has not ported. Track gjsify Open TODO "Node 25 stream/iter integration".')}};export{O as FileHandle};
|
package/lib/types/fd-io.d.ts
CHANGED
|
@@ -5,72 +5,52 @@ export declare function fsError(code: string, syscall: string, path?: string, de
|
|
|
5
5
|
/**
|
|
6
6
|
* Can this host name an open descriptor as a path?
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Lazy, not at module eval: a probe there runs in every consumer that merely
|
|
9
|
+
* imports `node:fs`. Do not assume procfs exists — in `respond-with-file.ts` the
|
|
10
|
+
* path resolved to nothing on macOS, `statSync` threw, and a swallowed catch meant
|
|
11
|
+
* the check silently never ran.
|
|
12
12
|
*/
|
|
13
13
|
export declare function hasProcFd(): boolean;
|
|
14
14
|
/**
|
|
15
|
-
* A path
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* unlink: `/proc/self/fd/<fd>` resolves to the inode the fd holds, even after
|
|
20
|
-
* the directory entry is gone.
|
|
15
|
+
* A path naming the DESCRIPTOR rather than the name it was opened from, or `null`
|
|
16
|
+
* where the host cannot express one. `/proc/self/fd/<fd>` resolves to the inode
|
|
17
|
+
* the fd holds even after the directory entry is gone, which is what lets
|
|
18
|
+
* `fstat`/`fchmod`/`ftruncate` survive a rename or unlink.
|
|
21
19
|
*/
|
|
22
20
|
export declare function fdPath(fd: number): string | null;
|
|
23
|
-
/**
|
|
24
|
-
* `open(2)`.
|
|
25
|
-
*
|
|
26
|
-
* The mode reaches the kernel, so it is applied ATOMICALLY at creation, masked
|
|
27
|
-
* by the live umask, and it survives a crash — none of which a post-create or
|
|
28
|
-
* close-time `chmod` can offer. `O_EXCL` is likewise the kernel's, so it is
|
|
29
|
-
* atomic and it refuses a symlink whatever the link points at.
|
|
30
|
-
*/
|
|
21
|
+
/** `open(2)`. */
|
|
31
22
|
export declare function openFd(path: string, spec: OpenSpec, mode: number): number;
|
|
32
23
|
export declare function closeFd(fd: number): void;
|
|
33
24
|
/**
|
|
34
|
-
* Name the reason `open(2)` refused
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* check-then-act: it never decides whether to create anything, so it carries no
|
|
39
|
-
* TOCTOU. The `lstat`-equivalent lookup (`NOFOLLOW_SYMLINKS`) matters — a
|
|
40
|
-
* dangling symlink must read as "the name is taken", which is exactly why
|
|
41
|
-
* `file_test(EXISTS)` was the wrong instrument for the check this replaces.
|
|
25
|
+
* Name the reason `open(2)` refused, reconstructed from what is observable AFTER
|
|
26
|
+
* the kernel refused. A classification, not a check-then-act: it decides nothing
|
|
27
|
+
* about creating anything, so it carries no TOCTOU. The lookup must be
|
|
28
|
+
* `NOFOLLOW_SYMLINKS` so a dangling symlink reads as "the name is taken".
|
|
42
29
|
*/
|
|
43
30
|
export declare function classifyOpenFailure(path: string, spec: OpenSpec): NodeJS.ErrnoException;
|
|
44
31
|
/**
|
|
45
32
|
* Does this `GFileInfo` actually ANSWER, or is it an empty shell?
|
|
46
33
|
*
|
|
47
|
-
* `g_file_query_info()` does NOT fail when the kernel refuses to stat a name.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* `
|
|
51
|
-
* `
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* SIGABRT before the caller's `catch` can run
|
|
59
|
-
* the
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* `
|
|
67
|
-
*
|
|
68
|
-
* Exported because the stat family needs the identical gate and used to lack
|
|
69
|
-
* it: `statSync` under an unsearchable directory built a `Stats` from the empty
|
|
70
|
-
* shell, which is BOTH of the failures above at once — four CRITICALs (hence a
|
|
71
|
-
* SIGABRT under `G_DEBUG=fatal-criticals`) and then a fabricated `{mode: 0,
|
|
72
|
-
* size: 0, ino: 0}` returned as fact where Node raises EACCES. See
|
|
73
|
-
* `statsFrom()` in `stats.ts`, which is the one place that decision now lives.
|
|
34
|
+
* `g_file_query_info()` does NOT fail when the kernel refuses to stat a name. A
|
|
35
|
+
* path under a directory the process cannot search returns a non-NULL `GFileInfo`
|
|
36
|
+
* with NO attributes at all — measured: `list_attributes(null)` is `[]` and
|
|
37
|
+
* `has_attribute('standard::type')` is `false`, where an ordinary lookup gives
|
|
38
|
+
* `['standard::type']`. Every attribute read in this module is therefore gated on
|
|
39
|
+
* this, and `false` means "no answer", NEVER "no".
|
|
40
|
+
*
|
|
41
|
+
* Both ways of skipping the gate are live hazards:
|
|
42
|
+
* - reading the type off the shell logs `GFileInfo created without
|
|
43
|
+
* standard::type` and `g_file_info_get_file_type: should not be reached`, two
|
|
44
|
+
* `GLib-GIO-CRITICAL`s that `G_DEBUG=fatal-criticals` (standard in GNOME CI)
|
|
45
|
+
* turns into a SIGABRT before the caller's `catch` can run;
|
|
46
|
+
* - taking the shell as proof the name EXISTS answers EEXIST for a plain
|
|
47
|
+
* permission denial, so an `openSync(lock,'wx')` retry loop spins forever and
|
|
48
|
+
* the ubiquitous `catch (e) { if (e.code !== 'EEXIST') throw }` around
|
|
49
|
+
* `mkdirSync` swallows a hard EACCES.
|
|
50
|
+
*
|
|
51
|
+
* Exported because the stat family needs the identical gate; without it `statSync`
|
|
52
|
+
* hits both hazards at once and returns a fabricated `{mode: 0, size: 0, ino: 0}`
|
|
53
|
+
* as fact where Node raises EACCES. `statsFrom()` in `stats.ts` owns that decision.
|
|
74
54
|
*/
|
|
75
55
|
export declare function answered(info: Gio.FileInfo | null): boolean;
|
|
76
56
|
/** The same reconstruction for `mkdir(2)`, which also returns a bare `-1`. */
|
|
@@ -78,53 +58,37 @@ export declare function classifyMkdirFailure(path: string): NodeJS.ErrnoExceptio
|
|
|
78
58
|
/**
|
|
79
59
|
* Can this descriptor be positioned at all?
|
|
80
60
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* `
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* asking one to seek is the `g_io_channel_seek_position: assertion failed`
|
|
113
|
-
* CRITICAL this function exists to avoid.
|
|
114
|
-
*
|
|
115
|
-
* Without the second test, writing to a terminal by path was impossible: the
|
|
116
|
-
* unconditional `seekFd` threw a RAW `Gio.IOErrorEnum` — `code === 8` as a
|
|
117
|
-
* NUMBER, `instanceof Error === false` — straight out of `writeFileSync`,
|
|
118
|
-
* `writeSync` and `createWriteStream`, so `catch (e) { if (e.code === 'ESPIPE') }`
|
|
119
|
-
* could not see it and the classic "write to the terminal even when stdout is
|
|
120
|
-
* redirected" idiom simply failed.
|
|
121
|
-
*
|
|
122
|
-
* The process's own stdin/stdout/stderr — the overwhelmingly common tty case —
|
|
123
|
-
* never reach this code: `isStdFd()` intercepts them first.
|
|
124
|
-
*
|
|
125
|
-
* Cached per fd: the type behind an open file description cannot change under
|
|
126
|
-
* it, and the cache is dropped by {@link releaseFd} so a recycled fd number
|
|
127
|
-
* never inherits the previous holder's answer.
|
|
61
|
+
* Seeking unconditionally before every read and write emits one
|
|
62
|
+
* `g_io_channel_seek_position: assertion 'channel->is_seekable' failed` per I/O
|
|
63
|
+
* call on a FIFO / pipe / socket / tty — a `GLib-CRITICAL`, which
|
|
64
|
+
* `G_DEBUG=fatal-criticals` (standard in GNOME CI) turns into an abort. The bytes
|
|
65
|
+
* survive, because a pipe ignores the offset, so `_pos` silently accumulates a
|
|
66
|
+
* number that means nothing.
|
|
67
|
+
*
|
|
68
|
+
* The answer must therefore be ASKED FOR, not discovered by tripping the
|
|
69
|
+
* assertion, and it takes TWO tests in this order:
|
|
70
|
+
*
|
|
71
|
+
* 1. The file TYPE, refusing ONLY `S_IFIFO` and `S_IFSOCK` — the two whose channel
|
|
72
|
+
* is not `is_seekable`, so asking them at all is the CRITICAL above. The
|
|
73
|
+
* tempting "seekable means `S_IFREG` or `S_IFBLK`" is WRONG: a character device
|
|
74
|
+
* usually seeks fine (`noop_llseek`) and Node does positional I/O on
|
|
75
|
+
* `/dev/null`, `/dev/zero` and `/dev/urandom` (measured, v24.15.0), so
|
|
76
|
+
* refusing those raises an ESPIPE Node never raises.
|
|
77
|
+
* 2. The DESCRIPTOR itself, via the same `lseek(fd, 0, SEEK_CUR)` `GIOChannel` uses
|
|
78
|
+
* internally — a pure query that moves nothing. Needed because a TTY is
|
|
79
|
+
* `S_IFCHR` exactly like `/dev/zero` but has `no_llseek`. Measured under gjs
|
|
80
|
+
* 1.88.1: `G_IO_STATUS_NORMAL` on a regular file, `/dev/zero` and `/dev/null`;
|
|
81
|
+
* a clean `GLib.IOChannelError` ("Illegal seek") on a pty slave, with no
|
|
82
|
+
* CRITICAL, because that channel IS `is_seekable` and the kernel refuses.
|
|
83
|
+
*
|
|
84
|
+
* Skipping test 2 makes writing to a terminal by path impossible: the seek throws
|
|
85
|
+
* a RAW `Gio.IOErrorEnum` — numeric `code`, `instanceof Error === false` — out of
|
|
86
|
+
* `writeFileSync`/`writeSync`/`createWriteStream`, invisible to
|
|
87
|
+
* `catch (e) { if (e.code === 'ESPIPE') }`.
|
|
88
|
+
*
|
|
89
|
+
* The process's own stdin/stdout/stderr never arrive here; `isStdFd()` takes them
|
|
90
|
+
* first. Cached per fd — the type behind an open file description cannot change —
|
|
91
|
+
* and dropped by {@link releaseFd} so a recycled fd number inherits nothing.
|
|
128
92
|
*/
|
|
129
93
|
export declare function isSeekableFd(fd: number, fallbackPath: string): boolean;
|
|
130
94
|
/** Absolute `lseek(2)`. Only valid on a descriptor {@link isSeekableFd} accepts. */
|
|
@@ -132,11 +96,9 @@ export declare function seekFd(fd: number, position: number): void;
|
|
|
132
96
|
/**
|
|
133
97
|
* The descriptor's true offset, or `null` where the host cannot report it.
|
|
134
98
|
*
|
|
135
|
-
* Only needed where the KERNEL moved the offset behind us
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* away from the file after the first append. `seek_position()` cannot answer
|
|
139
|
-
* this: it returns a `GIOStatus`, not an offset.
|
|
99
|
+
* Only needed where the KERNEL moved the offset behind us: an `O_APPEND` write jumps
|
|
100
|
+
* to EOF silently, and computing the new position from the pre-write value drifts.
|
|
101
|
+
* `seek_position()` cannot answer this — it returns a `GIOStatus`, not an offset.
|
|
140
102
|
*/
|
|
141
103
|
export declare function tellFd(fd: number): number | null;
|
|
142
104
|
/** `read(2)` at the descriptor's current offset. The kernel advances it. */
|
|
@@ -145,11 +107,9 @@ export declare function readFd(fd: number, length: number): Uint8Array;
|
|
|
145
107
|
export declare function writeFd(fd: number, data: Uint8Array): number;
|
|
146
108
|
export declare function fsyncFd(fd: number): void;
|
|
147
109
|
/**
|
|
148
|
-
* Release the cached per-fd views.
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* its fd would answer a later call against whatever the OS handed that number
|
|
152
|
-
* to next.
|
|
110
|
+
* Release the cached per-fd views. They must go before the descriptor does: a
|
|
111
|
+
* GioUnix stream outliving its fd answers later calls against whatever the OS
|
|
112
|
+
* handed that number to next.
|
|
153
113
|
*/
|
|
154
114
|
export declare function releaseFd(fd: number): void;
|
|
155
115
|
/** The size of the file behind `fd`, via the descriptor where the host allows. */
|
|
@@ -157,32 +117,25 @@ export declare function sizeOfFd(fd: number, fallbackPath: string): number;
|
|
|
157
117
|
/**
|
|
158
118
|
* `ftruncate(2)`, as closely as GJS can express it.
|
|
159
119
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* (
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* `
|
|
180
|
-
* — it is the difference between working and not on a write-only file: mode
|
|
181
|
-
* 0o200 truncates through the first and is refused by the second (measured).
|
|
182
|
-
* What remains unreachable is a file whose mode denies its own owner write
|
|
183
|
-
* (0o444, 0o400, 0o000): Node truncates those through the descriptor, we cannot.
|
|
184
|
-
* The failure is at least REPORTED now — it used to escape as a bare
|
|
185
|
-
* `Gio.IOErrorEnum` with a numeric `code` and `instanceof Error === false`, so
|
|
186
|
-
* `catch (e) { if (e.code === 'EACCES') }` could not see it.
|
|
120
|
+
* The access-mode check `ftruncate(2)` performs — descriptor open for writing, mode
|
|
121
|
+
* never consulted — is bookkeeping we already hold, so it lives in `FileHandle`
|
|
122
|
+
* beside the identical gates in `_readCore`/`_writeCore`, NOT here. Without it a
|
|
123
|
+
* handle opened `'r'` destroys the file, because the re-open below asks the kernel
|
|
124
|
+
* for fresh permission and gets it.
|
|
125
|
+
*
|
|
126
|
+
* A re-open is unavoidable: there is no `g_ftruncate` and no route from an fd to a
|
|
127
|
+
* `GSeekable` — `GioUnix.OutputStream` does not implement it, and
|
|
128
|
+
* `GBufferedOutputStream` / `GDataOutputStream` around one both report
|
|
129
|
+
* `can_truncate() == false` (measured, gjs 1.88.1). It goes by the descriptor's
|
|
130
|
+
* procfs name so it lands on the right inode after a rename or unlink, and being a
|
|
131
|
+
* second open it IS checked against the file's mode.
|
|
132
|
+
*
|
|
133
|
+
* Hence the least privilege that can truncate: `append_to()` is `O_WRONLY`,
|
|
134
|
+
* `open_readwrite()` is `O_RDWR`. Not an optimisation — on a write-only file, mode
|
|
135
|
+
* 0o200 truncates through the first and is refused by the second (measured). Still
|
|
136
|
+
* unreachable: a mode denying its own owner write (0o444, 0o400, 0o000), which Node
|
|
137
|
+
* truncates through the descriptor. That failure must at least be REPORTED as an
|
|
138
|
+
* ErrnoException — raw it escapes as a `Gio.IOErrorEnum` with a numeric `code` and
|
|
139
|
+
* `instanceof Error === false`, invisible to `catch (e) { if (e.code === 'EACCES') }`.
|
|
187
140
|
*/
|
|
188
141
|
export declare function truncateFd(fd: number, length: number, fallbackPath: string): void;
|
|
@@ -19,20 +19,14 @@ export declare class FileHandle implements IFileHandle {
|
|
|
19
19
|
/** What the caller asked open(2) for — the ORIGINAL flags, nothing flattened. */
|
|
20
20
|
private readonly _spec;
|
|
21
21
|
/**
|
|
22
|
-
* The one byte position this handle has
|
|
22
|
+
* The one byte position this handle has — a descriptor has exactly ONE offset,
|
|
23
|
+
* shared by reads and writes. `_readCore`/`_writeCore` seek to it absolutely
|
|
24
|
+
* before every operation, so the kernel's offset is a consequence of this
|
|
25
|
+
* value and cannot drift from it.
|
|
23
26
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* can never drift away from it. It replaces four carriers that used to
|
|
28
|
-
* disagree — a read-only `_readPos`, the IOChannel's buffered offset, a
|
|
29
|
-
* cached Gio stream's cursor, and a fresh per-call stream that always
|
|
30
|
-
* started at 0.
|
|
31
|
-
*
|
|
32
|
-
* The single exception is an `O_APPEND` write: there the kernel repositions
|
|
33
|
-
* to EOF without telling us, so the new value is READ BACK rather than
|
|
34
|
-
* computed from the pre-write one. Computing it is how the previous attempt
|
|
35
|
-
* let the cursor drift away from the file after the first append.
|
|
27
|
+
* Exception: an `O_APPEND` write, where the kernel repositions to EOF without
|
|
28
|
+
* telling us. There the new value must be READ BACK; computing it from the
|
|
29
|
+
* pre-write value drifts after the very first append.
|
|
36
30
|
*/
|
|
37
31
|
private _pos;
|
|
38
32
|
private _closed;
|
|
@@ -46,14 +40,11 @@ export declare class FileHandle implements IFileHandle {
|
|
|
46
40
|
/**
|
|
47
41
|
* Read at `position`, or at the cursor when it is `null`/negative.
|
|
48
42
|
*
|
|
49
|
-
* SYNCHRONOUS ON PURPOSE
|
|
50
|
-
*
|
|
51
|
-
* indivisible by construction
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* lost. That class cannot exist here: there is no suspension point for
|
|
55
|
-
* anything to interleave with, which is why the async lock this replaces is
|
|
56
|
-
* gone rather than tightened.
|
|
43
|
+
* SYNCHRONOUS ON PURPOSE: the cursor's read-modify-write — read `_pos`, seek,
|
|
44
|
+
* transfer, store `_pos` — contains no `await`, so on a single-threaded runtime
|
|
45
|
+
* it is indivisible by construction. An async lock cannot substitute for that;
|
|
46
|
+
* one held around the I/O while the offset was computed outside it let two
|
|
47
|
+
* concurrent writers capture the same offset and lose a write.
|
|
57
48
|
*/
|
|
58
49
|
private _readCore;
|
|
59
50
|
/** Write at `position`, or at the cursor when it is `null`/negative. See {@link _readCore}. */
|
|
@@ -62,250 +53,93 @@ export declare class FileHandle implements IFileHandle {
|
|
|
62
53
|
/**
|
|
63
54
|
* `ftruncate(2)` — ONE body for the sync and async halves.
|
|
64
55
|
*
|
|
65
|
-
* The access-mode gate belongs HERE
|
|
66
|
-
* the descriptor
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* destroyed six bytes through a READ-ONLY descriptor and returned normally.
|
|
71
|
-
* Node reports EINVAL, and so does this.
|
|
72
|
-
*
|
|
73
|
-
* `_readCore`/`_writeCore` have carried the identical gate since this
|
|
74
|
-
* redesign began; truncate was the one byte-mover left outside it, which is
|
|
75
|
-
* exactly the shape of divergence the single-core rule exists to prevent.
|
|
56
|
+
* The access-mode gate belongs HERE, not in `truncateFd`: it is a property of
|
|
57
|
+
* the descriptor, and `truncateFd` re-opens the file (see there), so the kernel
|
|
58
|
+
* grants it fresh permission of its own. Without this line
|
|
59
|
+
* `ftruncateSync(openSync(f,'r'), 2)` destroys bytes through a READ-ONLY
|
|
60
|
+
* descriptor and returns normally. Node reports EINVAL, and so does this.
|
|
76
61
|
*/
|
|
77
62
|
private _truncateCore;
|
|
78
63
|
/** Read from the cursor to EOF, advancing it — Node's whole-file read on a handle. */
|
|
79
64
|
private _readToEnd;
|
|
80
65
|
/** The path that names this DESCRIPTOR, falling back to the name it was opened from. */
|
|
81
66
|
private _fdTarget;
|
|
82
|
-
/**
|
|
83
|
-
* The numeric file descriptor managed by the {FileHandle} object.
|
|
84
|
-
* @since v10.0.0
|
|
85
|
-
*/
|
|
67
|
+
/** The numeric file descriptor managed by this object. */
|
|
86
68
|
readonly fd: number;
|
|
87
69
|
/**
|
|
88
70
|
* The handle behind a descriptor number. Not part of Node's API.
|
|
89
71
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* closed descriptor landed here rather than on `_assertOpen`, which made
|
|
97
|
-
* that guard dead for every fd-number call site. `catch (e) { if (e.code
|
|
98
|
-
* === 'EBADF') }` — the idiom for tolerating a racing close — fell through
|
|
99
|
-
* to the generic branch instead. The syscall is threaded in from the caller
|
|
100
|
-
* because Node names it in the message, and we know it there and only there.
|
|
72
|
+
* Must fail as `EBADF`, not a bare Error: the common way to get here is an fd
|
|
73
|
+
* that WAS valid, since `_teardown()` deletes the instance, so every call on a
|
|
74
|
+
* closed descriptor lands here instead of on `_assertOpen`. Without the code,
|
|
75
|
+
* `catch (e) { if (e.code === 'EBADF') }` — the idiom for tolerating a racing
|
|
76
|
+
* close — falls through. `syscall` comes from the caller because Node names it
|
|
77
|
+
* in the message and only the caller knows it.
|
|
101
78
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* machinery. Unwrapping is one line and makes every fd call site accept
|
|
107
|
-
* both spellings.
|
|
79
|
+
* A `FileHandle` is accepted as well as a number because `openSync()` returns
|
|
80
|
+
* the handle where Node returns a number (a divergence this does not change);
|
|
81
|
+
* without unwrapping, `fs.write(fs.openSync(p,'w'), …)` stringifies the object
|
|
82
|
+
* into a key and misses.
|
|
108
83
|
*/
|
|
109
84
|
static getInstance(fd: number | FileHandle, syscall?: string): FileHandle;
|
|
110
85
|
/**
|
|
111
|
-
* Alias of `
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* @since v10.0.0
|
|
116
|
-
* @return Fulfills with `undefined` upon success.
|
|
86
|
+
* Alias of `writeFile()`: a handle's mode cannot be changed after
|
|
87
|
+
* `fsPromises.open()`, so it appends only because the descriptor carries
|
|
88
|
+
* O_APPEND. It must therefore be a plain CURSOR write — writing at some
|
|
89
|
+
* separately-tracked offset overwrites from byte 0 on a fresh handle.
|
|
117
90
|
*/
|
|
118
91
|
appendFile(data: string | Uint8Array, options?: (ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding | null): Promise<void>;
|
|
119
|
-
/**
|
|
120
|
-
* Changes the ownership of the file. A wrapper for [`chown(2)`](http://man7.org/linux/man-pages/man2/chown.2.html).
|
|
121
|
-
* @since v10.0.0
|
|
122
|
-
* @param uid The file's new owner's user id.
|
|
123
|
-
* @param gid The file's new group's group id.
|
|
124
|
-
* @return Fulfills with `undefined` upon success.
|
|
125
|
-
*/
|
|
92
|
+
/** [`chown(2)`](http://man7.org/linux/man-pages/man2/chown.2.html). */
|
|
126
93
|
chown(uid: number, gid: number): Promise<void>;
|
|
127
|
-
/**
|
|
128
|
-
* Modifies the permissions on the file. See [`chmod(2)`](http://man7.org/linux/man-pages/man2/chmod.2.html).
|
|
129
|
-
* @since v10.0.0
|
|
130
|
-
* @param mode the file mode bit mask.
|
|
131
|
-
* @return Fulfills with `undefined` upon success.
|
|
132
|
-
*/
|
|
94
|
+
/** [`chmod(2)`](http://man7.org/linux/man-pages/man2/chmod.2.html). */
|
|
133
95
|
chmod(mode: Mode): Promise<void>;
|
|
134
96
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* `options` can include `start` and `end` values to read a range of bytes from
|
|
139
|
-
* the file instead of the entire file. Both `start` and `end` are inclusive and
|
|
140
|
-
* start counting at 0, allowed values are in the
|
|
141
|
-
* \[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
|
|
142
|
-
* omitted or `undefined`, `filehandle.createReadStream()` reads sequentially from
|
|
143
|
-
* the current file position. The `encoding` can be any one of those accepted by `Buffer`.
|
|
144
|
-
*
|
|
145
|
-
* If the `FileHandle` points to a character device that only supports blocking
|
|
146
|
-
* reads (such as keyboard or sound card), read operations do not finish until data
|
|
147
|
-
* is available. This can prevent the process from exiting and the stream from
|
|
148
|
-
* closing naturally.
|
|
149
|
-
*
|
|
150
|
-
* By default, the stream will emit a `'close'` event after it has been
|
|
151
|
-
* destroyed. Set the `emitClose` option to `false` to change this behavior.
|
|
152
|
-
*
|
|
153
|
-
* ```js
|
|
154
|
-
* import { open } from 'node:fs/promises';
|
|
155
|
-
*
|
|
156
|
-
* const fd = await open('/dev/input/event0');
|
|
157
|
-
* // Create a stream from some character device.
|
|
158
|
-
* const stream = fd.createReadStream();
|
|
159
|
-
* setTimeout(() => {
|
|
160
|
-
* stream.close(); // This may not close the stream.
|
|
161
|
-
* // Artificially marking end-of-stream, as if the underlying resource had
|
|
162
|
-
* // indicated end-of-file by itself, allows the stream to close.
|
|
163
|
-
* // This does not cancel pending read operations, and if there is such an
|
|
164
|
-
* // operation, the process may still not be able to exit successfully
|
|
165
|
-
* // until it finishes.
|
|
166
|
-
* stream.push(null);
|
|
167
|
-
* stream.read(0);
|
|
168
|
-
* }, 100);
|
|
169
|
-
* ```
|
|
170
|
-
*
|
|
171
|
-
* If `autoClose` is false, then the file descriptor won't be closed, even if
|
|
172
|
-
* there's an error. It is the application's responsibility to close it and make
|
|
173
|
-
* sure there's no file descriptor leak. If `autoClose` is set to true (default
|
|
174
|
-
* behavior), on `'error'` or `'end'` the file descriptor will be closed
|
|
175
|
-
* automatically.
|
|
176
|
-
*
|
|
177
|
-
* An example to read the last 10 bytes of a file which is 100 bytes long:
|
|
178
|
-
*
|
|
179
|
-
* ```js
|
|
180
|
-
* import { open } from 'node:fs/promises';
|
|
181
|
-
*
|
|
182
|
-
* const fd = await open('sample.txt');
|
|
183
|
-
* fd.createReadStream({ start: 90, end: 99 });
|
|
184
|
-
* ```
|
|
185
|
-
* @since v16.11.0
|
|
97
|
+
* `highWaterMark` defaults to 64 kb here rather than a Readable's 16 kb. With
|
|
98
|
+
* no `start`, reading is sequential from the current file position.
|
|
186
99
|
*/
|
|
187
100
|
createReadStream(options?: CreateReadStreamOptions): ReadStream;
|
|
188
101
|
/**
|
|
189
|
-
* `options`
|
|
190
|
-
*
|
|
191
|
-
* \[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
|
|
192
|
-
* replacing it may require the `flags` `open` option to be set to `r+` rather than
|
|
193
|
-
* the default `r`. The `encoding` can be any one of those accepted by `Buffer`.
|
|
194
|
-
*
|
|
195
|
-
* If `autoClose` is set to true (default behavior) on `'error'` or `'finish'`the file descriptor will be closed automatically. If `autoClose` is false,
|
|
196
|
-
* then the file descriptor won't be closed, even if there's an error.
|
|
197
|
-
* It is the application's responsibility to close it and make sure there's no
|
|
198
|
-
* file descriptor leak.
|
|
199
|
-
*
|
|
200
|
-
* By default, the stream will emit a `'close'` event after it has been
|
|
201
|
-
* destroyed. Set the `emitClose` option to `false` to change this behavior.
|
|
202
|
-
* @since v16.11.0
|
|
102
|
+
* `options.start` writes past the beginning of the file; modifying rather than
|
|
103
|
+
* replacing may need `flags: 'r+'` instead of the default `'r'`.
|
|
203
104
|
*/
|
|
204
105
|
createWriteStream(options?: CreateWriteStreamOptions): WriteStream;
|
|
205
|
-
/**
|
|
206
|
-
*
|
|
207
|
-
* 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.
|
|
208
|
-
*
|
|
209
|
-
* Unlike `filehandle.sync` this method does not flush modified metadata.
|
|
210
|
-
* @since v10.0.0
|
|
211
|
-
* @return Fulfills with `undefined` upon success.
|
|
212
|
-
*/
|
|
106
|
+
/** [`fdatasync(2)`](http://man7.org/linux/man-pages/man2/fdatasync.2.html) —
|
|
107
|
+
* unlike `sync()` this does not flush modified metadata. */
|
|
213
108
|
datasync(): Promise<void>;
|
|
214
|
-
/**
|
|
215
|
-
* Request that all data for the open file descriptor is flushed to the storage
|
|
216
|
-
* device. The specific implementation is operating system and device specific.
|
|
217
|
-
* Refer to the POSIX [`fsync(2)`](http://man7.org/linux/man-pages/man2/fsync.2.html) documentation for more detail.
|
|
218
|
-
* @since v10.0.0
|
|
219
|
-
* @return Fufills with `undefined` upon success.
|
|
220
|
-
*/
|
|
109
|
+
/** [`fsync(2)`](http://man7.org/linux/man-pages/man2/fsync.2.html). */
|
|
221
110
|
sync(): Promise<void>;
|
|
222
111
|
/**
|
|
223
|
-
*
|
|
112
|
+
* Read into `buffer`. Absent concurrent modification, EOF is `bytesRead === 0`.
|
|
224
113
|
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* @since v10.0.0
|
|
228
|
-
* @param buffer A buffer that will be filled with the file data read.
|
|
229
|
-
* @param offset The location in the buffer at which to start filling.
|
|
230
|
-
* @param length The number of bytes to read.
|
|
231
|
-
* @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
|
|
232
|
-
* integer, the current file position will remain unchanged.
|
|
233
|
-
* @return Fulfills upon success with an object with two properties:
|
|
114
|
+
* A `null` `position` reads from the current file position AND advances it; an
|
|
115
|
+
* integer leaves the position unchanged.
|
|
234
116
|
*/
|
|
235
117
|
read<T extends NodeJS.ArrayBufferView>(buffer: T, offset?: number | null, length?: number | null, position?: ReadPosition | null): Promise<FileReadResult<T>>;
|
|
236
118
|
read<T extends NodeJS.ArrayBufferView = Buffer>(options?: FileReadOptions<T>): Promise<FileReadResult<T>>;
|
|
237
119
|
/**
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* ```js
|
|
244
|
-
* import { open } from 'node:fs/promises';
|
|
245
|
-
*
|
|
246
|
-
* const file = await open('./some/file/to/read');
|
|
247
|
-
*
|
|
248
|
-
* for await (const chunk of file.readableWebStream())
|
|
249
|
-
* console.log(chunk);
|
|
250
|
-
*
|
|
251
|
-
* await file.close();
|
|
252
|
-
* ```
|
|
120
|
+
* INCOMPLETE. Node returns a `ReadableStream` over the file's bytes and throws
|
|
121
|
+
* on a second call or a closed handle; this returns a SOURCELESS stream that
|
|
122
|
+
* yields nothing, and enforces neither rule. Reading the file needs a source
|
|
123
|
+
* wired to `_readCore` — until then prefer `createReadStream()`.
|
|
253
124
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* @since v17.0.0
|
|
257
|
-
* @experimental
|
|
125
|
+
* Node also never closes the handle for you: user code still calls `close()`.
|
|
258
126
|
*/
|
|
259
127
|
readableWebStream(): ReadableStream;
|
|
260
128
|
/**
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
* The `FileHandle` has to support reading.
|
|
266
|
-
*
|
|
267
|
-
* 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
|
|
268
|
-
* position till the end of the file. It doesn't always read from the beginning
|
|
269
|
-
* of the file.
|
|
270
|
-
* @since v10.0.0
|
|
271
|
-
* @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
|
|
272
|
-
* data will be a string.
|
|
129
|
+
* Read from the CURRENT position to EOF — not from the beginning, so earlier
|
|
130
|
+
* `read()` calls on this handle move the starting point. The handle must have
|
|
131
|
+
* been opened for reading, and is not closed for you.
|
|
273
132
|
*/
|
|
274
133
|
readFile(options?: {
|
|
275
134
|
encoding?: null | undefined;
|
|
276
135
|
flag?: OpenMode | undefined;
|
|
277
136
|
} | null): Promise<Buffer<ArrayBuffer>>;
|
|
278
|
-
/**
|
|
279
|
-
* Asynchronously reads the entire contents of a file. The underlying file will _not_ be closed automatically.
|
|
280
|
-
* The `FileHandle` must have been opened for reading.
|
|
281
|
-
* @param options An object that may contain an optional flag.
|
|
282
|
-
* If a flag is not provided, it defaults to `'r'`.
|
|
283
|
-
*/
|
|
284
137
|
readFile(options: {
|
|
285
138
|
encoding: BufferEncoding;
|
|
286
139
|
flag?: OpenMode | undefined;
|
|
287
140
|
} | BufferEncoding): Promise<string>;
|
|
288
|
-
/**
|
|
289
|
-
* Convenience method to create a `readline` interface and stream over the file. For example:
|
|
290
|
-
*
|
|
291
|
-
* ```js
|
|
292
|
-
* import { open } from 'node:fs/promises';
|
|
293
|
-
*
|
|
294
|
-
* const file = await open('./some/file/to/read');
|
|
295
|
-
*
|
|
296
|
-
* for await (const line of file.readLines()) {
|
|
297
|
-
* console.log(line);
|
|
298
|
-
* }
|
|
299
|
-
* ```
|
|
300
|
-
*
|
|
301
|
-
* @since v18.11.0
|
|
302
|
-
* @param options See `filehandle.createReadStream()` for the options.
|
|
303
|
-
*/
|
|
141
|
+
/** A `readline` interface over the file; `options` are `createReadStream()`'s. */
|
|
304
142
|
readLines(options?: CreateReadStreamOptions): ReadlineInterface;
|
|
305
|
-
/**
|
|
306
|
-
* @since v10.0.0
|
|
307
|
-
* @return Fulfills with an {fs.Stats} for the file.
|
|
308
|
-
*/
|
|
309
143
|
stat(opts?: StatOptions & {
|
|
310
144
|
bigint?: false | undefined;
|
|
311
145
|
}): Promise<Stats>;
|
|
@@ -313,75 +147,25 @@ export declare class FileHandle implements IFileHandle {
|
|
|
313
147
|
bigint: true;
|
|
314
148
|
}): Promise<BigIntStats>;
|
|
315
149
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* If the file was larger than `len` bytes, only the first `len` bytes will be
|
|
319
|
-
* retained in the file.
|
|
320
|
-
*
|
|
321
|
-
* The following example retains only the first four bytes of the file:
|
|
322
|
-
*
|
|
323
|
-
* ```js
|
|
324
|
-
* import { open } from 'node:fs/promises';
|
|
325
|
-
*
|
|
326
|
-
* let filehandle = null;
|
|
327
|
-
* try {
|
|
328
|
-
* filehandle = await open('temp.txt', 'r+');
|
|
329
|
-
* await filehandle.truncate(4);
|
|
330
|
-
* } finally {
|
|
331
|
-
* await filehandle?.close();
|
|
332
|
-
* }
|
|
333
|
-
* ```
|
|
334
|
-
*
|
|
335
|
-
* If the file previously was shorter than `len` bytes, it is extended, and the
|
|
336
|
-
* extended part is filled with null bytes (`'\0'`):
|
|
337
|
-
*
|
|
338
|
-
* If `len` is negative then `0` will be used.
|
|
339
|
-
* @since v10.0.0
|
|
340
|
-
* @param [len=0]
|
|
341
|
-
* @return Fulfills with `undefined` upon success.
|
|
150
|
+
* Keep only the first `len` bytes, or extend with NUL bytes when the file was
|
|
151
|
+
* shorter. A negative `len` is treated as 0.
|
|
342
152
|
*/
|
|
343
153
|
truncate(len?: number): Promise<void>;
|
|
344
|
-
/**
|
|
345
|
-
* Change the file system timestamps of the object referenced by the `FileHandle` then resolves the promise with no arguments upon success.
|
|
346
|
-
* @since v10.0.0
|
|
347
|
-
*/
|
|
154
|
+
/** Change the file system timestamps of the referenced object. */
|
|
348
155
|
utimes(atime: string | number | Date, mtime: string | number | Date): Promise<void>;
|
|
349
156
|
/**
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
* The promise is resolved with no arguments upon success.
|
|
354
|
-
*
|
|
355
|
-
* If `options` is a string, then it specifies the `encoding`.
|
|
356
|
-
*
|
|
357
|
-
* The `FileHandle` has to support writing.
|
|
358
|
-
*
|
|
359
|
-
* It is unsafe to use `filehandle.writeFile()` multiple times on the same file
|
|
360
|
-
* without waiting for the promise to be resolved (or rejected).
|
|
361
|
-
*
|
|
362
|
-
* 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
|
|
363
|
-
* current position till the end of the file. It doesn't always write from the
|
|
364
|
-
* beginning of the file.
|
|
365
|
-
* @since v10.0.0
|
|
157
|
+
* Write from the CURRENT position to the end — not from the beginning, so
|
|
158
|
+
* earlier `write()` calls on this handle move the starting point. Unsafe to
|
|
159
|
+
* call again before the promise settles.
|
|
366
160
|
*/
|
|
367
161
|
writeFile(data: string | Uint8Array, options?: (ObjectEncodingOptions & FlagAndOpenMode & Abortable) | BufferEncoding | null): Promise<void>;
|
|
368
162
|
/**
|
|
369
|
-
* Write `buffer`
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* It is unsafe to use `filehandle.write()` multiple times on the same file
|
|
374
|
-
* without waiting for the promise to be resolved (or rejected). For this
|
|
375
|
-
* scenario, use `filehandle.createWriteStream()`.
|
|
163
|
+
* Write `buffer` at `position`, or at the current position when `position` is
|
|
164
|
+
* not a number (POSIX pwrite(2)). Unsafe to call again before the promise
|
|
165
|
+
* settles — use `createWriteStream()` for that.
|
|
376
166
|
*
|
|
377
|
-
* On Linux
|
|
378
|
-
*
|
|
379
|
-
* the end of the file.
|
|
380
|
-
* @since v10.0.0
|
|
381
|
-
* @param [offset=0] The start position from within `buffer` where the data to write begins.
|
|
382
|
-
* @param [length=buffer.byteLength - offset] The number of bytes from `buffer` to write.
|
|
383
|
-
* @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.
|
|
384
|
-
* See the POSIX pwrite(2) documentation for more detail.
|
|
167
|
+
* On Linux a positional write is IGNORED on an append-mode file: the kernel
|
|
168
|
+
* always appends.
|
|
385
169
|
*/
|
|
386
170
|
write<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number | null, length?: number | null, position?: number | null): Promise<{
|
|
387
171
|
bytesWritten: number;
|
|
@@ -400,27 +184,12 @@ export declare class FileHandle implements IFileHandle {
|
|
|
400
184
|
buffer: string;
|
|
401
185
|
}>;
|
|
402
186
|
/**
|
|
403
|
-
* Write
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
* It is unsafe to call `writev()` multiple times on the same file without waiting
|
|
408
|
-
* for the promise to be resolved (or rejected).
|
|
409
|
-
*
|
|
410
|
-
* On Linux, positional writes don't work when the file is opened in append mode.
|
|
411
|
-
* The kernel ignores the position argument and always appends the data to
|
|
412
|
-
* the end of the file.
|
|
413
|
-
* @since v12.9.0
|
|
414
|
-
* @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
|
|
415
|
-
* position.
|
|
187
|
+
* Write several views in sequence. `position` is where the FIRST one lands, the
|
|
188
|
+
* current position when it is not a number; unsafe to call again before the
|
|
189
|
+
* promise settles. As with `write()`, Linux ignores it in append mode.
|
|
416
190
|
*/
|
|
417
191
|
writev<TBuffers extends readonly NodeJS.ArrayBufferView[]>(buffers: TBuffers, position?: number): Promise<WriteVResult<TBuffers>>;
|
|
418
|
-
/**
|
|
419
|
-
* Read from a file and write to an array of [ArrayBufferView](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView) s
|
|
420
|
-
* @since v13.13.0, v12.17.0
|
|
421
|
-
* @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.
|
|
422
|
-
* @return Fulfills upon success an object containing two properties:
|
|
423
|
-
*/
|
|
192
|
+
/** Fill several views in sequence, from `position` or the current position. */
|
|
424
193
|
readv<TBuffers extends readonly NodeJS.ArrayBufferView[]>(buffers: TBuffers, position?: number): Promise<ReadVResult<TBuffers>>;
|
|
425
194
|
/** @internal */ _flushSync(): void;
|
|
426
195
|
/** @internal */ _closeSync(): void;
|
package/lib/types/sync.d.ts
CHANGED
|
@@ -17,19 +17,17 @@ export declare function readdirSync(path: PathLike, options?: {
|
|
|
17
17
|
/**
|
|
18
18
|
* POSIX `realpath(3)`: resolve EVERY component, not merely a trailing symlink.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* incidental: reproduced on Linux with nothing but a symlinked parent directory.
|
|
20
|
+
* Checking only the LEAF lets a symlinked ANCESTOR survive into the result, which is
|
|
21
|
+
* invisible on a typical Linux box — `/tmp` is a real directory, so the identity
|
|
22
|
+
* answer is accidentally right. On macOS `/var` is a symlink to `private/var`, so
|
|
23
|
+
* `realpathSync(os.tmpdir())` answered `/var/folders/…` while the child process
|
|
24
|
+
* asked for its own cwd reported `/private/var/folders/…`: six
|
|
25
|
+
* `@gjsify/child_process` cwd failures on main's macOS leg, the only leg that runs
|
|
26
|
+
* it. The platform was incidental — a symlinked parent directory reproduces it on
|
|
27
|
+
* Linux.
|
|
29
28
|
*
|
|
30
|
-
* The symlink budget is shared across the whole walk because POSIX counts
|
|
31
|
-
* resolutions
|
|
32
|
-
* component still terminates.
|
|
29
|
+
* The symlink budget is shared across the whole walk because POSIX counts TOTAL
|
|
30
|
+
* resolutions, not per-component ones, so a cycle through any component terminates.
|
|
33
31
|
*/
|
|
34
32
|
export declare function realpathSync(path: PathLike): string;
|
|
35
33
|
export declare function symlinkSync(target: PathLike, path: PathLike, _type?: 'file' | 'dir' | 'junction'): void;
|
|
@@ -38,39 +36,21 @@ export declare function readFileSync(path: PathLike | number | FileHandle, optio
|
|
|
38
36
|
flag?: string;
|
|
39
37
|
} | string | null): string | Buffer<ArrayBuffer>;
|
|
40
38
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* See the POSIX [`mkdir(2)`](http://man7.org/linux/man-pages/man2/mkdir.2.html) documentation for more details.
|
|
45
|
-
* @since v0.1.21
|
|
39
|
+
* [`mkdir(2)`](http://man7.org/linux/man-pages/man2/mkdir.2.html). Returns the FIRST
|
|
40
|
+
* directory created under `recursive: true`, otherwise `undefined`. A string mode is
|
|
41
|
+
* parsed as octal; the default is `0o777`.
|
|
46
42
|
*/
|
|
47
43
|
export declare function mkdirSync(path: PathLike, options: MakeDirectoryOptions & {
|
|
48
44
|
recursive: true;
|
|
49
45
|
}): string | undefined;
|
|
50
|
-
/**
|
|
51
|
-
* Synchronous mkdir(2) - create a directory.
|
|
52
|
-
* @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
|
|
53
|
-
* @param options Either the file mode, or an object optionally specifying the file mode and whether parent folders
|
|
54
|
-
* should be created. If a string is passed, it is parsed as an octal integer. If not specified, defaults to `0o777`.
|
|
55
|
-
*/
|
|
56
46
|
export declare function mkdirSync(path: PathLike, options?: Mode | (MakeDirectoryOptions & {
|
|
57
47
|
recursive?: false | undefined;
|
|
58
48
|
}) | null): void;
|
|
59
|
-
/**
|
|
60
|
-
* Synchronous mkdir(2) - create a directory.
|
|
61
|
-
* @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
|
|
62
|
-
* @param options Either the file mode, or an object optionally specifying the file mode and whether parent folders
|
|
63
|
-
* should be created. If a string is passed, it is parsed as an octal integer. If not specified, defaults to `0o777`.
|
|
64
|
-
*/
|
|
65
49
|
export declare function mkdirSync(path: PathLike, options?: Mode | MakeDirectoryOptions | null): string | undefined | void;
|
|
66
50
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* on Windows and an `ENOTDIR` error on POSIX.
|
|
71
|
-
*
|
|
72
|
-
* To get a behavior similar to the `rm -rf` Unix command, use {@link rmSync} with options `{ recursive: true, force: true }`.
|
|
73
|
-
* @since v0.1.21
|
|
51
|
+
* [`rmdir(2)`](http://man7.org/linux/man-pages/man2/rmdir.2.html) — empty directories
|
|
52
|
+
* only; `rm -rf` behaviour is {@link rmSync} with `{ recursive: true, force: true }`.
|
|
53
|
+
* On a FILE this is ENOTDIR on POSIX but ENOENT on Windows.
|
|
74
54
|
*/
|
|
75
55
|
export declare function rmdirSync(path: PathLike, _options?: RmDirOptions): void;
|
|
76
56
|
export declare function unlinkSync(path: PathLike): void;
|
|
@@ -99,47 +79,25 @@ export declare function watch(filename: PathLike, options: {
|
|
|
99
79
|
} | undefined, listener: ((eventType: string, filename: string | null) => void) | undefined): FSWatcher;
|
|
100
80
|
export declare function openSync(path: PathLike, flags?: OpenFlags | number, mode?: Mode): FileHandle;
|
|
101
81
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* For detailed information, see the documentation of the asynchronous version of
|
|
105
|
-
* this API: {@link mkdtemp}.
|
|
106
|
-
*
|
|
107
|
-
* The optional `options` argument can be a string specifying an encoding, or an
|
|
108
|
-
* object with an `encoding` property specifying the character encoding to use.
|
|
109
|
-
* @since v5.10.0
|
|
82
|
+
* Create a unique temporary directory: six random characters appended to `prefix`.
|
|
83
|
+
* Returns its path, encoded per `options` (default `'utf8'`).
|
|
110
84
|
*/
|
|
111
85
|
export declare function mkdtempSync(prefix: string, options?: EncodingOption): string;
|
|
112
|
-
/**
|
|
113
|
-
* Synchronously creates a unique temporary directory.
|
|
114
|
-
* Generates six random characters to be appended behind a required prefix to create a unique temporary directory.
|
|
115
|
-
* @param options The encoding (or an object specifying the encoding), used as the encoding of the result. If not provided, `'utf8'` is used.
|
|
116
|
-
*/
|
|
117
86
|
export declare function mkdtempSync(prefix: string, options: BufferEncodingOption): Buffer;
|
|
118
|
-
/**
|
|
119
|
-
* Synchronously creates a unique temporary directory.
|
|
120
|
-
* Generates six random characters to be appended behind a required prefix to create a unique temporary directory.
|
|
121
|
-
* @param options The encoding (or an object specifying the encoding), used as the encoding of the result. If not provided, `'utf8'` is used.
|
|
122
|
-
*/
|
|
123
87
|
export declare function mkdtempSync(prefix: string, options?: EncodingOption): string | Buffer;
|
|
124
88
|
/**
|
|
125
89
|
* `mkdtemp(3)`: claim a unique name and create it 0700, atomically.
|
|
126
90
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* `
|
|
132
|
-
*
|
|
133
|
-
* any other process regardless. `mkdir(2)` decides both questions in one
|
|
134
|
-
* syscall: it fails EEXIST if the name is taken, so the loop retries instead
|
|
135
|
-
* of guessing.
|
|
91
|
+
* The mode is not negotiable — this is the one API whose whole purpose is a PRIVATE
|
|
92
|
+
* scratch space, and 0o777 hands it to everyone. Nor is the atomicity: a
|
|
93
|
+
* `while (existsSync(path))` test-then-create is racy against any other process, and
|
|
94
|
+
* `existsSync` is `Gio.File.query_exists()`, true even for a dangling symlink.
|
|
95
|
+
* `mkdir(2)` answers both in one syscall, failing EEXIST on a taken name, so the loop
|
|
96
|
+
* retries rather than guesses.
|
|
136
97
|
*
|
|
137
|
-
* Both entry points call THIS
|
|
138
|
-
*
|
|
98
|
+
* Both entry points call THIS so `mkdtempSync` and `promises.mkdtemp` cannot disagree
|
|
99
|
+
* on the mode — they once did, by 0o077.
|
|
139
100
|
*/
|
|
140
101
|
export declare function mkdtempAt(prefix: string): string;
|
|
141
|
-
/**
|
|
142
|
-
* Synchronously removes files and directories (modeled on the standard POSIX `rm`utility). Returns `undefined`.
|
|
143
|
-
* @since v14.14.0
|
|
144
|
-
*/
|
|
102
|
+
/** Remove files and directories, modelled on POSIX `rm`. */
|
|
145
103
|
export declare function rmSync(path: PathLike, options?: RmOptions): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/fs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Node.js fs module for Gjs",
|
|
5
5
|
"module": "lib/esm/index.js",
|
|
6
6
|
"types": "lib/types/index.d.ts",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"fs"
|
|
67
67
|
],
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@gjsify/cli": "^0.
|
|
70
|
-
"@gjsify/unit": "^0.
|
|
69
|
+
"@gjsify/cli": "^0.37.0",
|
|
70
|
+
"@gjsify/unit": "^0.37.0",
|
|
71
71
|
"@types/node": "^25.9.2",
|
|
72
72
|
"typescript": "^6.0.3"
|
|
73
73
|
},
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
"@girs/gio-2.0": "^4.1.0",
|
|
76
76
|
"@girs/giounix-2.0": "^4.1.0",
|
|
77
77
|
"@girs/glib-2.0": "^4.1.0",
|
|
78
|
-
"@gjsify/buffer": "^0.
|
|
79
|
-
"@gjsify/events": "^0.
|
|
80
|
-
"@gjsify/stream": "^0.
|
|
81
|
-
"@gjsify/url": "^0.
|
|
82
|
-
"@gjsify/utils": "^0.
|
|
78
|
+
"@gjsify/buffer": "^0.37.0",
|
|
79
|
+
"@gjsify/events": "^0.37.0",
|
|
80
|
+
"@gjsify/stream": "^0.37.0",
|
|
81
|
+
"@gjsify/url": "^0.37.0",
|
|
82
|
+
"@gjsify/utils": "^0.37.0"
|
|
83
83
|
},
|
|
84
84
|
"gjsify": {
|
|
85
85
|
"runtimes": {
|