@gjsify/fs 0.0.3 → 0.1.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 (91) hide show
  1. package/README.md +31 -2
  2. package/lib/esm/callback.js +251 -15
  3. package/lib/esm/dirent.js +47 -6
  4. package/lib/esm/encoding.js +2 -3
  5. package/lib/esm/errors.js +13 -0
  6. package/lib/esm/file-handle.js +108 -66
  7. package/lib/esm/fs-watcher.js +44 -7
  8. package/lib/esm/index.js +140 -5
  9. package/lib/esm/promises.js +290 -69
  10. package/lib/esm/read-stream.js +82 -57
  11. package/lib/esm/stats.js +138 -18
  12. package/lib/esm/sync.js +293 -44
  13. package/lib/esm/write-stream.js +4 -4
  14. package/lib/types/callback.d.ts +233 -0
  15. package/lib/types/dirent.d.ts +77 -0
  16. package/lib/types/encoding.d.ts +6 -0
  17. package/lib/types/errors.d.ts +7 -0
  18. package/lib/types/file-handle.d.ts +367 -0
  19. package/lib/types/fs-watcher.d.ts +17 -0
  20. package/lib/types/index.d.ts +149 -0
  21. package/lib/types/promises.d.ts +158 -0
  22. package/lib/types/read-stream.d.ts +21 -0
  23. package/lib/types/stats.d.ts +67 -0
  24. package/lib/types/sync.d.ts +109 -0
  25. package/lib/types/types/encoding-option.d.ts +2 -0
  26. package/lib/types/types/file-read-options.d.ts +15 -0
  27. package/lib/types/types/file-read-result.d.ts +4 -0
  28. package/lib/types/types/flag-and-open-mode.d.ts +5 -0
  29. package/lib/types/types/index.d.ts +6 -0
  30. package/lib/types/types/open-flags.d.ts +1 -0
  31. package/lib/types/types/read-options.d.ts +5 -0
  32. package/lib/types/utils.d.ts +2 -0
  33. package/lib/types/write-stream.d.ts +45 -0
  34. package/package.json +22 -34
  35. package/src/callback.spec.ts +284 -30
  36. package/src/callback.ts +352 -39
  37. package/src/dirent.ts +56 -8
  38. package/src/encoding.ts +7 -2
  39. package/src/errors.spec.ts +389 -0
  40. package/src/errors.ts +19 -0
  41. package/src/extended.spec.ts +706 -0
  42. package/src/file-handle.spec.ts +104 -23
  43. package/src/file-handle.ts +147 -79
  44. package/src/fs-watcher.ts +55 -8
  45. package/src/index.ts +146 -2
  46. package/src/new-apis.spec.ts +505 -0
  47. package/src/promises.spec.ts +651 -11
  48. package/src/promises.ts +353 -81
  49. package/src/read-stream.ts +98 -74
  50. package/src/stat.spec.ts +22 -14
  51. package/src/stats.ts +176 -75
  52. package/src/streams.spec.ts +455 -0
  53. package/src/symlink.spec.ts +176 -26
  54. package/src/sync.spec.ts +204 -32
  55. package/src/sync.ts +363 -58
  56. package/src/test.mts +7 -2
  57. package/src/types/encoding-option.ts +1 -1
  58. package/src/types/flag-and-open-mode.ts +1 -1
  59. package/src/types/read-options.ts +2 -2
  60. package/src/utils.ts +2 -0
  61. package/src/write-stream.ts +9 -7
  62. package/tsconfig.json +23 -10
  63. package/tsconfig.tsbuildinfo +1 -0
  64. package/lib/cjs/callback.js +0 -112
  65. package/lib/cjs/dirent.js +0 -98
  66. package/lib/cjs/encoding.js +0 -34
  67. package/lib/cjs/file-handle.js +0 -444
  68. package/lib/cjs/fs-watcher.js +0 -50
  69. package/lib/cjs/index.js +0 -95
  70. package/lib/cjs/promises.js +0 -160
  71. package/lib/cjs/read-stream.js +0 -78
  72. package/lib/cjs/stats.js +0 -45
  73. package/lib/cjs/sync.js +0 -126
  74. package/lib/cjs/types/encoding-option.js +0 -0
  75. package/lib/cjs/types/file-read-options.js +0 -0
  76. package/lib/cjs/types/file-read-result.js +0 -0
  77. package/lib/cjs/types/flag-and-open-mode.js +0 -0
  78. package/lib/cjs/types/index.js +0 -6
  79. package/lib/cjs/types/open-flags.js +0 -0
  80. package/lib/cjs/types/read-options.js +0 -0
  81. package/lib/cjs/utils.js +0 -18
  82. package/lib/cjs/write-stream.js +0 -116
  83. package/test/watch.js +0 -1
  84. package/test.gjs.js +0 -35359
  85. package/test.gjs.js.map +0 -7
  86. package/test.gjs.mjs +0 -40570
  87. package/test.gjs.mjs.meta.json +0 -1
  88. package/test.node.js +0 -1479
  89. package/test.node.js.map +0 -7
  90. package/test.node.mjs +0 -710
  91. package/tsconfig.types.json +0 -8
package/README.md CHANGED
@@ -1,7 +1,36 @@
1
1
  # @gjsify/fs
2
2
 
3
- ## Inspirations
3
+ GJS implementation of the Node.js `fs` module using Gio. Supports sync, callback, and promises APIs, streams, and FSWatcher.
4
+
5
+ Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @gjsify/fs
11
+ # or
12
+ yarn add @gjsify/fs
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript
18
+ import { readFileSync, writeFileSync } from '@gjsify/fs';
19
+ import { readFile } from '@gjsify/fs/promises';
20
+
21
+ const content = readFileSync('/path/to/file', 'utf-8');
22
+ writeFileSync('/path/to/output', 'hello world');
23
+
24
+ const data = await readFile('/path/to/file', 'utf-8');
25
+ ```
26
+
27
+ ## Inspirations and credits
28
+
4
29
  - https://github.com/cgjs/cgjs/blob/master/packages/fs/index.js
5
30
  - https://github.com/geut/brode/blob/main/packages/browser-node-core/src/fs.js
6
31
  - https://github.com/mafintosh/level-filesystem/blob/master/index.js
7
- - https://github.com/denoland/deno_std/blob/main/node/fs.ts
32
+ - https://github.com/denoland/deno_std/blob/main/node/fs.ts
33
+
34
+ ## License
35
+
36
+ MIT
@@ -1,12 +1,68 @@
1
+ import GLib from "@girs/glib-2.0";
2
+ import Gio from "@girs/gio-2.0";
1
3
  import { open as openP, rm as rmP } from "./promises.js";
2
- import { warnNotImplemented } from "@gjsify/utils";
3
4
  import { FileHandle } from "./file-handle.js";
4
- import { Buffer } from "buffer";
5
- import { realpath } from "@gjsify/deno_std/node/_fs/_fs_realpath";
6
- import { readdir } from "@gjsify/deno_std/node/_fs/_fs_readdir";
7
- import { symlink } from "@gjsify/deno_std/node/_fs/_fs_symlink";
8
- import { lstat } from "@gjsify/deno_std/node/_fs/_fs_lstat";
9
- import { stat } from "@gjsify/deno_std/node/_fs/_fs_stat";
5
+ import { Buffer } from "node:buffer";
6
+ import { Stats, BigIntStats, STAT_ATTRIBUTES } from "./stats.js";
7
+ import { createNodeError } from "./errors.js";
8
+ import { realpathSync, readdirSync, renameSync, copyFileSync, accessSync, appendFileSync, readlinkSync, truncateSync, chmodSync, chownSync, mkdirSync, rmdirSync, readFileSync, writeFileSync } from "./sync.js";
9
+ function parseOptsCb(optionsOrCallback, maybeCallback) {
10
+ return typeof optionsOrCallback === "function" ? { options: {}, callback: optionsOrCallback } : { options: optionsOrCallback ?? {}, callback: maybeCallback };
11
+ }
12
+ function statImpl(path, flags, syscall, options, callback) {
13
+ const file = Gio.File.new_for_path(path.toString());
14
+ file.query_info_async(STAT_ATTRIBUTES, flags, GLib.PRIORITY_DEFAULT, null, (_s, res) => {
15
+ try {
16
+ const info = file.query_info_finish(res);
17
+ callback(null, options?.bigint ? new BigIntStats(info, path) : new Stats(info, path));
18
+ } catch (err) {
19
+ callback(createNodeError(err, syscall, path));
20
+ }
21
+ });
22
+ }
23
+ function stat(path, optionsOrCallback, maybeCallback) {
24
+ const { options, callback } = parseOptsCb(optionsOrCallback, maybeCallback);
25
+ statImpl(path, Gio.FileQueryInfoFlags.NONE, "stat", options, callback);
26
+ }
27
+ function lstat(path, optionsOrCallback, maybeCallback) {
28
+ const { options, callback } = parseOptsCb(optionsOrCallback, maybeCallback);
29
+ statImpl(path, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, "lstat", options, callback);
30
+ }
31
+ function readdir(path, optionsOrCallback, maybeCallback) {
32
+ const { options, callback } = parseOptsCb(optionsOrCallback, maybeCallback);
33
+ Promise.resolve().then(() => {
34
+ try {
35
+ callback(null, readdirSync(path, options));
36
+ } catch (err) {
37
+ callback(createNodeError(err, "readdir", path));
38
+ }
39
+ });
40
+ }
41
+ function realpath(path, optionsOrCallback, maybeCallback) {
42
+ const { callback } = parseOptsCb(optionsOrCallback, maybeCallback);
43
+ Promise.resolve().then(() => {
44
+ try {
45
+ callback(null, realpathSync(path));
46
+ } catch (err) {
47
+ callback(err);
48
+ }
49
+ });
50
+ }
51
+ function symlink(target, path, typeOrCallback, maybeCallback) {
52
+ const callback = typeof typeOrCallback === "function" ? typeOrCallback : maybeCallback;
53
+ if (typeof callback !== "function") {
54
+ throw new TypeError("Callback must be a function. Received " + typeof callback);
55
+ }
56
+ const file = Gio.File.new_for_path(path.toString());
57
+ file.make_symbolic_link_async(target.toString(), GLib.PRIORITY_DEFAULT, null, (_s, res) => {
58
+ try {
59
+ file.make_symbolic_link_finish(res);
60
+ callback(null);
61
+ } catch (err) {
62
+ callback(createNodeError(err, "symlink", target, path));
63
+ }
64
+ });
65
+ }
10
66
  function open(path, ...args) {
11
67
  let flags;
12
68
  let mode;
@@ -36,8 +92,10 @@ function open(path, ...args) {
36
92
  function write(fd, data, ...args) {
37
93
  const fileHandle = FileHandle.getInstance(fd);
38
94
  if (typeof data === "string") {
39
- const callback2 = args[args.length - 1];
40
- fileHandle.write(data, ...args.pop()).then((res) => {
95
+ const callback2 = args.pop();
96
+ const position2 = args[0];
97
+ const encoding = args[1];
98
+ fileHandle.write(data, position2, encoding).then((res) => {
41
99
  callback2(null, res.bytesWritten, res.buffer);
42
100
  }).catch((err) => {
43
101
  callback2(err, 0, "");
@@ -57,13 +115,12 @@ function write(fd, data, ...args) {
57
115
  function read(fd, ...args) {
58
116
  const fileHandle = FileHandle.getInstance(fd);
59
117
  const callback = args[args.length - 1];
60
- const err = new Error(warnNotImplemented("fs.read"));
61
- callback(err, 0, Buffer.from(""));
62
118
  let buffer;
63
119
  let offset;
64
120
  let length;
65
121
  let position;
66
- if (typeof args[0] === "object") {
122
+ if (args.length <= 1) {
123
+ } else if (typeof args[0] === "object" && !ArrayBuffer.isView(args[0])) {
67
124
  const options = args[0];
68
125
  buffer = options.buffer;
69
126
  offset = options.offset;
@@ -77,8 +134,8 @@ function read(fd, ...args) {
77
134
  }
78
135
  fileHandle.read(buffer, offset, length, position).then((res) => {
79
136
  callback(null, res.bytesRead, res.buffer);
80
- }).catch((err2) => {
81
- callback(err2, 0, Buffer.from([]));
137
+ }).catch((err) => {
138
+ callback(err, 0, Buffer.from([]));
82
139
  });
83
140
  }
84
141
  function close(fd, callback) {
@@ -98,15 +155,194 @@ function rm(path, ...args) {
98
155
  callback(err);
99
156
  });
100
157
  }
158
+ function rename(oldPath, newPath, callback) {
159
+ Promise.resolve().then(() => {
160
+ try {
161
+ renameSync(oldPath, newPath);
162
+ callback(null);
163
+ } catch (err) {
164
+ callback(err);
165
+ }
166
+ });
167
+ }
168
+ function copyFile(src, dest, modeOrCb, maybeCb) {
169
+ const mode = typeof modeOrCb === "function" ? 0 : modeOrCb;
170
+ const callback = typeof modeOrCb === "function" ? modeOrCb : maybeCb;
171
+ Promise.resolve().then(() => {
172
+ try {
173
+ copyFileSync(src, dest, mode);
174
+ callback(null);
175
+ } catch (err) {
176
+ callback(err);
177
+ }
178
+ });
179
+ }
180
+ function access(path, modeOrCb, maybeCb) {
181
+ const mode = typeof modeOrCb === "function" ? void 0 : modeOrCb;
182
+ const callback = typeof modeOrCb === "function" ? modeOrCb : maybeCb;
183
+ Promise.resolve().then(() => {
184
+ try {
185
+ accessSync(path, mode);
186
+ callback(null);
187
+ } catch (err) {
188
+ callback(err);
189
+ }
190
+ });
191
+ }
192
+ function appendFile(path, data, optsOrCb, maybeCb) {
193
+ const callback = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
194
+ const options = typeof optsOrCb === "function" ? void 0 : optsOrCb;
195
+ Promise.resolve().then(() => {
196
+ try {
197
+ appendFileSync(path, data, options);
198
+ callback(null);
199
+ } catch (err) {
200
+ callback(err);
201
+ }
202
+ });
203
+ }
204
+ function readlink(path, optsOrCb, maybeCb) {
205
+ const callback = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
206
+ const options = typeof optsOrCb === "function" ? void 0 : optsOrCb;
207
+ Promise.resolve().then(() => {
208
+ try {
209
+ callback(null, readlinkSync(path, options));
210
+ } catch (err) {
211
+ callback(err, "");
212
+ }
213
+ });
214
+ }
215
+ function truncate(path, lenOrCb, maybeCb) {
216
+ const len = typeof lenOrCb === "function" ? 0 : lenOrCb;
217
+ const callback = typeof lenOrCb === "function" ? lenOrCb : maybeCb;
218
+ Promise.resolve().then(() => {
219
+ try {
220
+ truncateSync(path, len);
221
+ callback(null);
222
+ } catch (err) {
223
+ callback(err);
224
+ }
225
+ });
226
+ }
227
+ function chmod(path, mode, callback) {
228
+ Promise.resolve().then(() => {
229
+ try {
230
+ chmodSync(path, mode);
231
+ callback(null);
232
+ } catch (err) {
233
+ callback(err);
234
+ }
235
+ });
236
+ }
237
+ function chown(path, uid, gid, callback) {
238
+ Promise.resolve().then(() => {
239
+ try {
240
+ chownSync(path, uid, gid);
241
+ callback(null);
242
+ } catch (err) {
243
+ callback(err);
244
+ }
245
+ });
246
+ }
247
+ function mkdir(path, optsOrCb, maybeCb) {
248
+ const callback = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
249
+ const options = typeof optsOrCb === "function" ? void 0 : optsOrCb;
250
+ Promise.resolve().then(() => {
251
+ try {
252
+ mkdirSync(path, options);
253
+ callback(null);
254
+ } catch (err) {
255
+ callback(err);
256
+ }
257
+ });
258
+ }
259
+ function rmdir(path, optsOrCb, maybeCb) {
260
+ const callback = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
261
+ const options = typeof optsOrCb === "function" ? void 0 : optsOrCb;
262
+ Promise.resolve().then(() => {
263
+ try {
264
+ rmdirSync(path, options);
265
+ callback(null);
266
+ } catch (err) {
267
+ callback(err);
268
+ }
269
+ });
270
+ }
271
+ function readFile(path, optsOrCb, maybeCb) {
272
+ const callback = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
273
+ const options = typeof optsOrCb === "function" ? void 0 : optsOrCb;
274
+ Promise.resolve().then(() => {
275
+ try {
276
+ const readOpts = typeof options === "string" ? { encoding: options, flag: "r" } : { encoding: options?.encoding ?? null, flag: options?.flag ?? "r" };
277
+ callback(null, readFileSync(path.toString(), readOpts));
278
+ } catch (err) {
279
+ callback(err, null);
280
+ }
281
+ });
282
+ }
283
+ function writeFile(path, data, optsOrCb, maybeCb) {
284
+ const callback = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
285
+ Promise.resolve().then(() => {
286
+ try {
287
+ writeFileSync(path.toString(), data);
288
+ callback(null);
289
+ } catch (err) {
290
+ callback(err);
291
+ }
292
+ });
293
+ }
294
+ function link(existingPath, newPath, callback) {
295
+ Promise.resolve().then(() => {
296
+ try {
297
+ const result = GLib.spawn_command_line_sync(`ln ${existingPath.toString()} ${newPath.toString()}`);
298
+ if (!result[0]) {
299
+ throw Object.assign(new Error(`EPERM: operation not permitted, link '${existingPath}' -> '${newPath}'`), {
300
+ code: "EPERM",
301
+ errno: -1,
302
+ syscall: "link",
303
+ path: existingPath.toString(),
304
+ dest: newPath.toString()
305
+ });
306
+ }
307
+ callback(null);
308
+ } catch (err) {
309
+ callback(err);
310
+ }
311
+ });
312
+ }
313
+ function unlink(path, callback) {
314
+ Promise.resolve().then(() => {
315
+ try {
316
+ GLib.unlink(path.toString());
317
+ callback(null);
318
+ } catch (err) {
319
+ callback(err);
320
+ }
321
+ });
322
+ }
101
323
  export {
324
+ access,
325
+ appendFile,
326
+ chmod,
327
+ chown,
102
328
  close,
329
+ copyFile,
330
+ link,
103
331
  lstat,
332
+ mkdir,
104
333
  open,
105
334
  read,
335
+ readFile,
106
336
  readdir,
337
+ readlink,
107
338
  realpath,
339
+ rename,
108
340
  rm,
341
+ rmdir,
109
342
  stat,
110
343
  symlink,
111
- write
344
+ truncate,
345
+ unlink,
346
+ write,
347
+ writeFile
112
348
  };
package/lib/esm/dirent.js CHANGED
@@ -1,5 +1,13 @@
1
1
  import Gio from "@girs/gio-2.0";
2
- import { basename } from "path";
2
+ import { basename, dirname } from "node:path";
3
+ const S_IFMT = 61440;
4
+ const S_IFSOCK = 49152;
5
+ const S_IFLNK = 40960;
6
+ const S_IFREG = 32768;
7
+ const S_IFBLK = 24576;
8
+ const S_IFDIR = 16384;
9
+ const S_IFCHR = 8192;
10
+ const S_IFIFO = 4096;
3
11
  class Dirent {
4
12
  /**
5
13
  * The file name that this `fs.Dirent` object refers to. The type of this
@@ -7,6 +15,11 @@ class Dirent {
7
15
  * @since v10.10.0
8
16
  */
9
17
  name;
18
+ /**
19
+ * The path to the parent directory of the file this `fs.Dirent` object refers to.
20
+ * @since v20.12.0, v18.20.0
21
+ */
22
+ parentPath;
10
23
  _isFile = false;
11
24
  _isDirectory = false;
12
25
  _isBlockDevice = false;
@@ -17,12 +30,12 @@ class Dirent {
17
30
  /** This is not part of node.fs and is used internal by gjsify */
18
31
  _file;
19
32
  /** This is not part of node.fs and is used internal by gjsify */
20
- constructor(path, filename) {
21
- if (!filename)
22
- filename = basename(path);
33
+ constructor(path, filename, fileType) {
34
+ if (!filename) filename = basename(path);
23
35
  this.name = filename;
36
+ this.parentPath = dirname(path);
24
37
  this._file = Gio.File.new_for_path(path);
25
- const type = this._file.query_file_type(Gio.FileQueryInfoFlags.NONE, null);
38
+ const type = fileType ?? this._file.query_file_type(Gio.FileQueryInfoFlags.NONE, null);
26
39
  switch (type) {
27
40
  case Gio.FileType.DIRECTORY:
28
41
  this._isDirectory = true;
@@ -37,10 +50,38 @@ class Dirent {
37
50
  this._isSymbolicLink = true;
38
51
  break;
39
52
  case Gio.FileType.SPECIAL:
40
- this._isBlockDevice = Gio.unix_is_system_device_path(path);
53
+ this._classifySpecialFile(path);
41
54
  break;
42
55
  }
43
56
  }
57
+ /**
58
+ * Classify a SPECIAL file type using the unix::mode attribute from Gio.FileInfo.
59
+ * Falls back to marking nothing if the mode attribute is unavailable.
60
+ */
61
+ _classifySpecialFile(path) {
62
+ try {
63
+ const file = Gio.File.new_for_path(path);
64
+ const info = file.query_info("unix::mode", Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
65
+ const mode = info.get_attribute_uint32("unix::mode");
66
+ if (mode === 0) return;
67
+ const fmt = mode & S_IFMT;
68
+ switch (fmt) {
69
+ case S_IFBLK:
70
+ this._isBlockDevice = true;
71
+ break;
72
+ case S_IFCHR:
73
+ this._isCharacterDevice = true;
74
+ break;
75
+ case S_IFSOCK:
76
+ this._isSocket = true;
77
+ break;
78
+ case S_IFIFO:
79
+ this._isFIFO = true;
80
+ break;
81
+ }
82
+ } catch {
83
+ }
84
+ }
44
85
  /**
45
86
  * Returns `true` if the `fs.Dirent` object describes a regular file.
46
87
  * @since v10.10.0
@@ -1,4 +1,4 @@
1
- import { Buffer } from "buffer";
1
+ import { Buffer } from "node:buffer";
2
2
  function getEncodingFromOptions(options = { encoding: null, flag: "r" }, defaultEncoding = "utf8") {
3
3
  if (options === null) {
4
4
  return defaultEncoding;
@@ -19,8 +19,7 @@ function encodeUint8Array(encoding, data) {
19
19
  return decoder.decode(data);
20
20
  }
21
21
  function decode(str, encoding) {
22
- if (!encoding)
23
- return str;
22
+ if (!encoding) return str;
24
23
  else {
25
24
  const decoder = new TextDecoder(encoding);
26
25
  const encoder = new TextEncoder();
@@ -0,0 +1,13 @@
1
+ import { createNodeError as createNodeErrorGeneric, isNotFoundError } from "@gjsify/utils";
2
+ function createNodeError(err, syscall, path, dest) {
3
+ const pathStr = path.toString();
4
+ const error = createNodeErrorGeneric(err, syscall, {
5
+ path: pathStr,
6
+ dest: dest?.toString()
7
+ });
8
+ return error;
9
+ }
10
+ export {
11
+ createNodeError,
12
+ isNotFoundError
13
+ };