@gjsify/fs 0.1.15 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/lib/esm/callback.js +22 -13
  2. package/lib/esm/cp.js +253 -0
  3. package/lib/esm/dir.js +160 -0
  4. package/lib/esm/fd-ops.js +189 -0
  5. package/lib/esm/file-handle.js +263 -84
  6. package/lib/esm/fs-watcher.js +88 -4
  7. package/lib/esm/glob.js +164 -0
  8. package/lib/esm/index.js +128 -2
  9. package/lib/esm/promises.js +90 -27
  10. package/lib/esm/read-stream.js +53 -43
  11. package/lib/esm/stat-watcher.js +121 -0
  12. package/lib/esm/statfs.js +57 -0
  13. package/lib/esm/sync.js +70 -52
  14. package/lib/esm/utils.js +7 -0
  15. package/lib/esm/utimes.js +62 -0
  16. package/lib/esm/write-stream.js +2 -5
  17. package/lib/types/cp.d.ts +18 -0
  18. package/lib/types/cp.spec.d.ts +2 -0
  19. package/lib/types/dir.d.ts +29 -0
  20. package/lib/types/dir.spec.d.ts +2 -0
  21. package/lib/types/fd-ops.d.ts +57 -0
  22. package/lib/types/fd-ops.spec.d.ts +2 -0
  23. package/lib/types/file-handle.d.ts +34 -4
  24. package/lib/types/fs-watcher.d.ts +9 -2
  25. package/lib/types/glob.d.ts +8 -0
  26. package/lib/types/glob.spec.d.ts +2 -0
  27. package/lib/types/index.d.ts +51 -1
  28. package/lib/types/promises.d.ts +31 -4
  29. package/lib/types/read-stream.d.ts +3 -1
  30. package/lib/types/stat-watcher.d.ts +21 -0
  31. package/lib/types/statfs.d.ts +35 -0
  32. package/lib/types/statfs.spec.d.ts +2 -0
  33. package/lib/types/sync.d.ts +4 -7
  34. package/lib/types/utils.d.ts +2 -0
  35. package/lib/types/utimes.d.ts +13 -0
  36. package/lib/types/utimes.spec.d.ts +2 -0
  37. package/lib/types/watch.spec.d.ts +2 -0
  38. package/lib/types/watchfile.spec.d.ts +2 -0
  39. package/lib/types/write-stream.d.ts +1 -2
  40. package/package.json +12 -12
  41. package/src/callback.ts +22 -13
  42. package/src/cp.spec.ts +181 -0
  43. package/src/cp.ts +328 -0
  44. package/src/dir.spec.ts +204 -0
  45. package/src/dir.ts +199 -0
  46. package/src/fd-ops.spec.ts +234 -0
  47. package/src/fd-ops.ts +251 -0
  48. package/src/file-handle.ts +264 -94
  49. package/src/fs-watcher.ts +101 -6
  50. package/src/glob.spec.ts +201 -0
  51. package/src/glob.ts +205 -0
  52. package/src/index.ts +74 -0
  53. package/src/promises.ts +94 -29
  54. package/src/read-stream.ts +49 -43
  55. package/src/stat-watcher.ts +116 -0
  56. package/src/statfs.spec.ts +67 -0
  57. package/src/statfs.ts +92 -0
  58. package/src/streams.spec.ts +58 -0
  59. package/src/sync.ts +75 -57
  60. package/src/test.mts +13 -2
  61. package/src/utils.ts +10 -0
  62. package/src/utimes.spec.ts +113 -0
  63. package/src/utimes.ts +97 -0
  64. package/src/watch.spec.ts +171 -0
  65. package/src/watchfile.spec.ts +185 -0
  66. package/src/write-stream.ts +5 -8
  67. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,204 @@
1
+ // Ported from refs/bun/test/js/node/fs/dir.test.ts
2
+ // Original: MIT, Oven & contributors.
3
+ // Rewritten for @gjsify/unit — behavior preserved, assertion dialect adapted.
4
+
5
+ import { describe, it, expect } from '@gjsify/unit';
6
+ import {
7
+ Dir,
8
+ opendirSync,
9
+ opendir,
10
+ promises,
11
+ mkdirSync,
12
+ writeFileSync,
13
+ mkdtempSync,
14
+ rmSync,
15
+ } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { tmpdir } from 'node:os';
18
+
19
+ function makeTmp(): string {
20
+ return mkdtempSync(join(tmpdir(), 'gjsify-dir-'));
21
+ }
22
+
23
+ export default async () => {
24
+ await describe('fs.opendirSync', async () => {
25
+ await it('returns a Dir instance', async () => {
26
+ const tmp = makeTmp();
27
+ const dir = opendirSync(tmp);
28
+ expect(dir).toBeDefined();
29
+ expect(dir instanceof Dir).toBe(true);
30
+ dir.closeSync();
31
+ rmSync(tmp, { recursive: true, force: true });
32
+ });
33
+
34
+ await it('dir.path reflects the opened path', async () => {
35
+ const tmp = makeTmp();
36
+ const dir = opendirSync(tmp);
37
+ expect(dir.path).toBe(tmp);
38
+ dir.closeSync();
39
+ rmSync(tmp, { recursive: true, force: true });
40
+ });
41
+
42
+ await it('readSync returns null on empty directory', async () => {
43
+ const tmp = makeTmp();
44
+ const dir = opendirSync(tmp);
45
+ const entry = dir.readSync();
46
+ expect(entry).toBeNull();
47
+ dir.closeSync();
48
+ rmSync(tmp, { recursive: true, force: true });
49
+ });
50
+
51
+ await it('readSync returns Dirent entries for directory contents', async () => {
52
+ const tmp = makeTmp();
53
+ writeFileSync(join(tmp, 'a.txt'), 'a');
54
+ writeFileSync(join(tmp, 'b.txt'), 'b');
55
+
56
+ const dir = opendirSync(tmp);
57
+ const names: string[] = [];
58
+ let entry = dir.readSync();
59
+ while (entry !== null) {
60
+ names.push(entry.name);
61
+ entry = dir.readSync();
62
+ }
63
+ dir.closeSync();
64
+
65
+ expect(names.sort()).toStrictEqual(['a.txt', 'b.txt']);
66
+ rmSync(tmp, { recursive: true, force: true });
67
+ });
68
+
69
+ await it('Dirent has correct type flags', async () => {
70
+ const tmp = makeTmp();
71
+ writeFileSync(join(tmp, 'file.txt'), 'x');
72
+ mkdirSync(join(tmp, 'subdir'));
73
+
74
+ const dir = opendirSync(tmp);
75
+ const entries: { name: string; isFile: boolean; isDirectory: boolean }[] = [];
76
+ let entry = dir.readSync();
77
+ while (entry !== null) {
78
+ entries.push({ name: entry.name, isFile: entry.isFile(), isDirectory: entry.isDirectory() });
79
+ entry = dir.readSync();
80
+ }
81
+ dir.closeSync();
82
+
83
+ entries.sort((a, b) => a.name.localeCompare(b.name));
84
+ expect(entries).toStrictEqual([
85
+ { name: 'file.txt', isFile: true, isDirectory: false },
86
+ { name: 'subdir', isFile: false, isDirectory: true },
87
+ ]);
88
+ rmSync(tmp, { recursive: true, force: true });
89
+ });
90
+
91
+ await it('closeSync throws after close', async () => {
92
+ const tmp = makeTmp();
93
+ const dir = opendirSync(tmp);
94
+ dir.closeSync();
95
+
96
+ let threw = false;
97
+ try {
98
+ dir.closeSync();
99
+ } catch (e: any) {
100
+ threw = true;
101
+ expect(e.code).toBe('ERR_DIR_CLOSED');
102
+ }
103
+ expect(threw).toBe(true);
104
+ rmSync(tmp, { recursive: true, force: true });
105
+ });
106
+
107
+ await it('readSync throws after close', async () => {
108
+ const tmp = makeTmp();
109
+ const dir = opendirSync(tmp);
110
+ dir.closeSync();
111
+
112
+ let threw = false;
113
+ try {
114
+ dir.readSync();
115
+ } catch (e: any) {
116
+ threw = true;
117
+ expect(e.code).toBe('ERR_DIR_CLOSED');
118
+ }
119
+ expect(threw).toBe(true);
120
+ rmSync(tmp, { recursive: true, force: true });
121
+ });
122
+
123
+ await it('read() returns Dirent or null asynchronously', async () => {
124
+ const tmp = makeTmp();
125
+ writeFileSync(join(tmp, 'hello.txt'), 'hi');
126
+
127
+ const dir = opendirSync(tmp);
128
+ const entry = await dir.read();
129
+ expect(entry).toBeDefined();
130
+ expect(entry!.name).toBe('hello.txt');
131
+
132
+ const next = await dir.read();
133
+ expect(next).toBeNull();
134
+ await dir.close();
135
+ rmSync(tmp, { recursive: true, force: true });
136
+ });
137
+
138
+ await it('[Symbol.asyncIterator] iterates all entries', async () => {
139
+ const tmp = makeTmp();
140
+ writeFileSync(join(tmp, 'x.ts'), '');
141
+ writeFileSync(join(tmp, 'y.ts'), '');
142
+
143
+ const dir = opendirSync(tmp);
144
+ const names: string[] = [];
145
+ for await (const entry of dir) {
146
+ names.push(entry.name);
147
+ }
148
+
149
+ expect(names.sort()).toStrictEqual(['x.ts', 'y.ts']);
150
+ rmSync(tmp, { recursive: true, force: true });
151
+ });
152
+ });
153
+
154
+ await describe('fs.opendir (callback)', async () => {
155
+ await it('opens directory and returns Dir instance via callback', async () => {
156
+ const tmp = makeTmp();
157
+ await new Promise<void>((resolve, reject) => {
158
+ opendir(tmp, (err, dir) => {
159
+ if (err) return reject(err);
160
+ expect(dir instanceof Dir).toBe(true);
161
+ expect(dir.path).toBe(tmp);
162
+ dir.closeSync();
163
+ rmSync(tmp, { recursive: true, force: true });
164
+ resolve();
165
+ });
166
+ });
167
+ });
168
+
169
+ await it('throws if callback is not a function', async () => {
170
+ let threw = false;
171
+ try {
172
+ (opendir as any)('/tmp');
173
+ } catch (e: any) {
174
+ threw = true;
175
+ }
176
+ expect(threw).toBe(true);
177
+ });
178
+ });
179
+
180
+ await describe('fs.promises.opendir', async () => {
181
+ await it('opens directory and returns Dir instance', async () => {
182
+ const tmp = makeTmp();
183
+ const dir = await promises.opendir(tmp);
184
+ expect(dir instanceof Dir).toBe(true);
185
+ await dir.close();
186
+ rmSync(tmp, { recursive: true, force: true });
187
+ });
188
+
189
+ await it('async iterates directory entries', async () => {
190
+ const tmp = makeTmp();
191
+ writeFileSync(join(tmp, 'alpha.txt'), '');
192
+ writeFileSync(join(tmp, 'beta.txt'), '');
193
+
194
+ const dir = await promises.opendir(tmp);
195
+ const names: string[] = [];
196
+ for await (const entry of dir) {
197
+ names.push(entry.name);
198
+ }
199
+
200
+ expect(names.sort()).toStrictEqual(['alpha.txt', 'beta.txt']);
201
+ rmSync(tmp, { recursive: true, force: true });
202
+ });
203
+ });
204
+ };
package/src/dir.ts ADDED
@@ -0,0 +1,199 @@
1
+ // Reference: Node.js lib/internal/fs/dir.js
2
+ // Reimplemented for GJS using Gio.FileEnumerator
3
+
4
+ import Gio from '@girs/gio-2.0';
5
+ import GLib from '@girs/glib-2.0';
6
+ import { normalizePath } from './utils.js';
7
+ import { Dirent } from './dirent.js';
8
+ import { createNodeError } from './errors.js';
9
+ import type { PathLike } from 'node:fs';
10
+
11
+ const DIR_ATTRS = 'standard::name,standard::type,standard::is-symlink,standard::size,standard::symlink-target,unix::uid,unix::gid,unix::mode,time::modified,time::access,time::created';
12
+
13
+ export class Dir {
14
+ readonly path: string;
15
+ private _enumerator: Gio.FileEnumerator | null;
16
+ private _closed = false;
17
+
18
+ constructor(path: string, enumerator: Gio.FileEnumerator) {
19
+ this.path = path;
20
+ this._enumerator = enumerator;
21
+ }
22
+
23
+ private _assertOpen(): void {
24
+ if (this._closed) {
25
+ const err = new Error('Directory handle was closed') as NodeJS.ErrnoException;
26
+ err.code = 'ERR_DIR_CLOSED';
27
+ throw err;
28
+ }
29
+ }
30
+
31
+ readSync(): Dirent | null {
32
+ this._assertOpen();
33
+ try {
34
+ const info = this._enumerator!.next_file(null);
35
+ if (info === null) return null;
36
+ const name = info.get_name();
37
+ const fileType = info.get_file_type();
38
+ const childPath = this.path.endsWith('/') ? this.path + name : this.path + '/' + name;
39
+ return new Dirent(childPath, name, fileType);
40
+ } catch (err: unknown) {
41
+ throw createNodeError(err, 'readdir', this.path);
42
+ }
43
+ }
44
+
45
+ read(): Promise<Dirent | null>;
46
+ read(callback: (err: NodeJS.ErrnoException | null, dirent: Dirent | null) => void): void;
47
+ read(callback?: (err: NodeJS.ErrnoException | null, dirent: Dirent | null) => void): Promise<Dirent | null> | void {
48
+ if (callback !== undefined) {
49
+ if (typeof callback !== 'function') {
50
+ throw new TypeError('The "callback" argument must be of type function');
51
+ }
52
+ try {
53
+ this._assertOpen();
54
+ const dirent = this.readSync();
55
+ Promise.resolve().then(() => callback(null, dirent));
56
+ } catch (err: unknown) {
57
+ Promise.resolve().then(() => callback(err as NodeJS.ErrnoException, null));
58
+ }
59
+ return;
60
+ }
61
+
62
+ return new Promise((resolve, reject) => {
63
+ try {
64
+ this._assertOpen();
65
+ resolve(this.readSync());
66
+ } catch (err: unknown) {
67
+ reject(err);
68
+ }
69
+ });
70
+ }
71
+
72
+ closeSync(): void {
73
+ this._assertOpen();
74
+ this._closed = true;
75
+ try {
76
+ this._enumerator!.close(null);
77
+ } catch {
78
+ // ignore close errors
79
+ }
80
+ this._enumerator = null;
81
+ }
82
+
83
+ close(): Promise<void>;
84
+ close(callback: (err: NodeJS.ErrnoException | null) => void): void;
85
+ close(callback?: (err: NodeJS.ErrnoException | null) => void): Promise<void> | void {
86
+ if (callback !== undefined) {
87
+ if (typeof callback !== 'function') {
88
+ throw new TypeError('The "callback" argument must be of type function');
89
+ }
90
+ try {
91
+ this.closeSync();
92
+ Promise.resolve().then(() => callback(null));
93
+ } catch (err: unknown) {
94
+ Promise.resolve().then(() => callback(err as NodeJS.ErrnoException));
95
+ }
96
+ return;
97
+ }
98
+
99
+ return new Promise((resolve, reject) => {
100
+ try {
101
+ this.closeSync();
102
+ resolve();
103
+ } catch (err: unknown) {
104
+ reject(err);
105
+ }
106
+ });
107
+ }
108
+
109
+ async *[Symbol.asyncIterator](): AsyncIterableIterator<Dirent> {
110
+ try {
111
+ while (true) {
112
+ const dirent = await this.read();
113
+ if (dirent === null) break;
114
+ yield dirent;
115
+ }
116
+ } finally {
117
+ if (!this._closed) {
118
+ await this.close();
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ function _openDir(pathStr: string): Dir {
125
+ const file = Gio.File.new_for_path(pathStr);
126
+ let enumerator: Gio.FileEnumerator;
127
+ try {
128
+ enumerator = file.enumerate_children(DIR_ATTRS, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
129
+ } catch (err: unknown) {
130
+ throw createNodeError(err, 'opendir', pathStr);
131
+ }
132
+ return new Dir(pathStr, enumerator);
133
+ }
134
+
135
+ export function opendirSync(path: PathLike): Dir {
136
+ return _openDir(normalizePath(path));
137
+ }
138
+
139
+ export function opendir(
140
+ path: PathLike,
141
+ callback: (err: NodeJS.ErrnoException | null, dir: Dir) => void,
142
+ ): void;
143
+ export function opendir(
144
+ path: PathLike,
145
+ options: { encoding?: BufferEncoding | null; bufferSize?: number; recursive?: boolean },
146
+ callback: (err: NodeJS.ErrnoException | null, dir: Dir) => void,
147
+ ): void;
148
+ export function opendir(
149
+ path: PathLike,
150
+ optionsOrCallback:
151
+ | { encoding?: BufferEncoding | null; bufferSize?: number; recursive?: boolean }
152
+ | ((err: NodeJS.ErrnoException | null, dir: Dir) => void),
153
+ callback?: (err: NodeJS.ErrnoException | null, dir: Dir) => void,
154
+ ): void {
155
+ let cb: (err: NodeJS.ErrnoException | null, dir: Dir) => void;
156
+ if (typeof optionsOrCallback === 'function') {
157
+ cb = optionsOrCallback;
158
+ } else {
159
+ cb = callback!;
160
+ }
161
+
162
+ if (typeof cb !== 'function') {
163
+ throw new TypeError('The "callback" argument must be of type function');
164
+ }
165
+
166
+ Promise.resolve().then(() => {
167
+ try {
168
+ const dir = opendirSync(path);
169
+ cb(null, dir);
170
+ } catch (err: unknown) {
171
+ cb(err as NodeJS.ErrnoException, null as unknown as Dir);
172
+ }
173
+ });
174
+ }
175
+
176
+ // promises.opendir
177
+ export async function opendirAsync(
178
+ path: PathLike,
179
+ _options?: { encoding?: BufferEncoding | null; bufferSize?: number; recursive?: boolean },
180
+ ): Promise<Dir> {
181
+ return new Promise<Dir>((resolve, reject) => {
182
+ const pathStr = normalizePath(path);
183
+ const file = Gio.File.new_for_path(pathStr);
184
+ file.enumerate_children_async(
185
+ DIR_ATTRS,
186
+ Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
187
+ GLib.PRIORITY_DEFAULT,
188
+ null,
189
+ (_source: unknown, result: Gio.AsyncResult) => {
190
+ try {
191
+ const enumerator = file.enumerate_children_finish(result);
192
+ resolve(new Dir(pathStr, enumerator));
193
+ } catch (err: unknown) {
194
+ reject(createNodeError(err, 'opendir', pathStr));
195
+ }
196
+ },
197
+ );
198
+ });
199
+ }
@@ -0,0 +1,234 @@
1
+ // Ported from refs/node-test/parallel/test-fs-read-sync.js + test-fs-write-sync.js (behavior)
2
+ // Original: MIT, Node.js contributors.
3
+ // Rewritten for @gjsify/unit — behavior preserved, assertion dialect adapted.
4
+
5
+ import { describe, it, expect } from '@gjsify/unit';
6
+ import {
7
+ openSync, closeSync,
8
+ fstatSync, fstat,
9
+ ftruncateSync,
10
+ fdatasync, fsync,
11
+ fchmodSync,
12
+ readSync, writeSync,
13
+ readv, writev,
14
+ exists,
15
+ openAsBlob,
16
+ promises,
17
+ writeFileSync, unlinkSync, readFileSync,
18
+ } from 'node:fs';
19
+ import { tmpdir } from 'node:os';
20
+ import { join } from 'node:path';
21
+
22
+ const TMP = tmpdir();
23
+
24
+ function tmpFile(name: string, content = 'hello world'): string {
25
+ const p = join(TMP, `gjsify-fdops-${name}-${process.pid}`);
26
+ writeFileSync(p, content);
27
+ return p;
28
+ }
29
+
30
+ export default async () => {
31
+ await describe('fs fd-based operations', async () => {
32
+ await it('fstatSync returns Stats with correct size', async () => {
33
+ const f = tmpFile('fstat', 'hello');
34
+ const fd = openSync(f, 'r');
35
+ try {
36
+ const st = fstatSync(fd);
37
+ expect(st.size).toBe(5);
38
+ expect(typeof st.mtime).toBe('object');
39
+ } finally {
40
+ closeSync(fd);
41
+ unlinkSync(f);
42
+ }
43
+ });
44
+
45
+ await it('fstat callback returns Stats', async () => {
46
+ const f = tmpFile('fstat-cb', 'world');
47
+ const fd = openSync(f, 'r');
48
+ try {
49
+ const st = await new Promise<any>((resolve, reject) => {
50
+ fstat(fd, (err, s) => err ? reject(err) : resolve(s));
51
+ });
52
+ expect(st.size).toBe(5);
53
+ } finally {
54
+ closeSync(fd);
55
+ unlinkSync(f);
56
+ }
57
+ });
58
+
59
+ await it('promises.fstat returns Stats', async () => {
60
+ const f = tmpFile('fstat-p', 'gjsify');
61
+ const fh = await promises.open(f, 'r');
62
+ try {
63
+ const st = await fh.stat();
64
+ expect(st.size).toBe(6);
65
+ } finally {
66
+ await fh.close();
67
+ unlinkSync(f);
68
+ }
69
+ });
70
+
71
+ await it('ftruncateSync truncates file', async () => {
72
+ const f = tmpFile('ftrunc', '0123456789');
73
+ const fd = openSync(f, 'r+');
74
+ try {
75
+ ftruncateSync(fd, 4);
76
+ const data = readFileSync(f, 'utf8');
77
+ expect(data).toBe('0123');
78
+ } finally {
79
+ closeSync(fd);
80
+ unlinkSync(f);
81
+ }
82
+ });
83
+
84
+ await it('fdatasync callback completes without error', async () => {
85
+ const f = tmpFile('fdatasync', 'data');
86
+ const fd = openSync(f, 'r+');
87
+ try {
88
+ await new Promise<void>((resolve, reject) => {
89
+ fdatasync(fd, (err) => err ? reject(err) : resolve());
90
+ });
91
+ } finally {
92
+ closeSync(fd);
93
+ unlinkSync(f);
94
+ }
95
+ });
96
+
97
+ await it('fsync callback completes without error', async () => {
98
+ const f = tmpFile('fsync', 'data');
99
+ const fd = openSync(f, 'r+');
100
+ try {
101
+ await new Promise<void>((resolve, reject) => {
102
+ fsync(fd, (err) => err ? reject(err) : resolve());
103
+ });
104
+ } finally {
105
+ closeSync(fd);
106
+ unlinkSync(f);
107
+ }
108
+ });
109
+
110
+ await it('fchmodSync changes file permissions', async () => {
111
+ const f = tmpFile('fchmod', 'x');
112
+ const fd = openSync(f, 'r');
113
+ try {
114
+ fchmodSync(fd, 0o600);
115
+ const st = fstatSync(fd);
116
+ expect(st.mode & 0o777).toBe(0o600);
117
+ } finally {
118
+ closeSync(fd);
119
+ unlinkSync(f);
120
+ }
121
+ });
122
+
123
+ await it('closeSync closes the fd', async () => {
124
+ const f = tmpFile('close', 'c');
125
+ const fd = openSync(f, 'r');
126
+ closeSync(fd);
127
+ expect(() => fstatSync(fd)).toThrow();
128
+ unlinkSync(f);
129
+ });
130
+
131
+ await it('readSync reads bytes from position 0', async () => {
132
+ const f = tmpFile('rsync', 'abcdef');
133
+ const fd = openSync(f, 'r');
134
+ try {
135
+ const buf = Buffer.alloc(3);
136
+ const n = readSync(fd, buf, 0, 3, 0);
137
+ expect(n).toBe(3);
138
+ expect(buf.toString()).toBe('abc');
139
+ } finally {
140
+ closeSync(fd);
141
+ unlinkSync(f);
142
+ }
143
+ });
144
+
145
+ await it('readSync reads into buffer at offset', async () => {
146
+ const f = tmpFile('rsync-off', 'hello');
147
+ const fd = openSync(f, 'r');
148
+ try {
149
+ const buf = Buffer.alloc(8);
150
+ const n = readSync(fd, buf, 2, 5, 0);
151
+ expect(n).toBe(5);
152
+ expect(buf.slice(2, 7).toString()).toBe('hello');
153
+ } finally {
154
+ closeSync(fd);
155
+ unlinkSync(f);
156
+ }
157
+ });
158
+
159
+ await it('writeSync writes bytes to file', async () => {
160
+ const f = tmpFile('wsync', '-----');
161
+ const fd = openSync(f, 'r+');
162
+ try {
163
+ const n = writeSync(fd, Buffer.from('XYZ'), 0, 3, 0);
164
+ expect(n).toBe(3);
165
+ closeSync(fd);
166
+ expect(readFileSync(f, 'utf8').slice(0, 3)).toBe('XYZ');
167
+ } finally {
168
+ unlinkSync(f);
169
+ }
170
+ });
171
+
172
+ await it('readv reads into multiple buffers', async () => {
173
+ const f = tmpFile('readv', 'abcdef');
174
+ const fd = openSync(f, 'r');
175
+ try {
176
+ const b1 = Buffer.alloc(2);
177
+ const b2 = Buffer.alloc(3);
178
+ await new Promise<void>((resolve, reject) => {
179
+ readv(fd, [b1, b2], 0, (err, bytesRead) => {
180
+ if (err) return reject(err);
181
+ expect(bytesRead).toBe(5);
182
+ expect(b1.toString()).toBe('ab');
183
+ expect(b2.toString()).toBe('cde');
184
+ resolve();
185
+ });
186
+ });
187
+ } finally {
188
+ closeSync(fd);
189
+ unlinkSync(f);
190
+ }
191
+ });
192
+
193
+ await it('writev writes multiple buffers', async () => {
194
+ const f = tmpFile('writev', '------');
195
+ const fd = openSync(f, 'r+');
196
+ try {
197
+ const b1 = Buffer.from('AB');
198
+ const b2 = Buffer.from('CDE');
199
+ await new Promise<void>((resolve, reject) => {
200
+ writev(fd, [b1, b2], 0, (err, bytesWritten) => {
201
+ if (err) return reject(err);
202
+ expect(bytesWritten).toBe(5);
203
+ resolve();
204
+ });
205
+ });
206
+ closeSync(fd);
207
+ expect(readFileSync(f, 'utf8').slice(0, 5)).toBe('ABCDE');
208
+ } finally {
209
+ unlinkSync(f);
210
+ }
211
+ });
212
+
213
+ await it('exists returns true for existing path, false for missing', async () => {
214
+ const f = tmpFile('exists', 'e');
215
+ const existing = await new Promise<boolean>((resolve) => {
216
+ exists(f, (v) => resolve(v));
217
+ });
218
+ expect(existing).toBe(true);
219
+ const missing = await new Promise<boolean>((resolve) => {
220
+ exists(join(TMP, 'gjsify-nonexistent-xyz-123'), (v) => resolve(v));
221
+ });
222
+ expect(missing).toBe(false);
223
+ unlinkSync(f);
224
+ });
225
+
226
+ await it('openAsBlob returns Blob with correct size', async () => {
227
+ const f = tmpFile('blob', 'blobdata');
228
+ const blob = await openAsBlob(f);
229
+ expect(blob.size).toBe(8);
230
+ expect(blob instanceof Blob).toBe(true);
231
+ unlinkSync(f);
232
+ });
233
+ });
234
+ };