instrumentality 0.0.7 → 0.0.8

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/dist/road.js CHANGED
@@ -1,25 +1,24 @@
1
- import * as rl from "node:readline";
1
+ import * as cr from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import { constants as fsc } from "node:fs";
4
4
  import * as fp from "node:fs/promises";
5
5
  import * as ph from "node:path";
6
6
  import * as os from "node:os";
7
- import * as cr from "node:crypto";
8
7
  import { on } from "node:events";
9
- import * as bs from "./base.js";
10
- /** Subclass of {@link bs.InsErr} that represents an error thrown from this specific module of the library */
11
- export class RdErr extends bs.InsErr {
8
+ import { InsErr } from "./base.js";
9
+ /** Subclass of {@link InsErr} that represents an error thrown from this specific module of the library */
10
+ export class Err extends InsErr {
12
11
  name = "Instrumentality-Road-Error";
13
12
  }
14
- export { RdErr as RoadError };
13
+ export { Err as RoadError, Err as RdErr };
15
14
  /**
16
15
  * Returns the constructor function corresponding to the file mode (statmode).
17
16
  *
18
- * @param statmode_ The file mode to check.
17
+ * @param statmode_ The file mode or {@link fs.Dirent} to check.
19
18
  * @returns The constructor function corresponding to the road type (e.g., {@link File}, {@link Folder}, etc.).
20
- * @throws If the file mode is unknown, throws a {@link RdErr}.
19
+ * @throws If the file mode is unknown, throws a {@link Err}.
21
20
  */
22
- export function resolveMode(statmode_) {
21
+ export function resolveStat(statmode_) {
23
22
  switch (statmode_ & fsc.S_IFMT) {
24
23
  case fsc.S_IFREG: return File;
25
24
  case fsc.S_IFDIR: return Folder;
@@ -28,9 +27,36 @@ export function resolveMode(statmode_) {
28
27
  case fsc.S_IFLNK: return SymbolicLink;
29
28
  case fsc.S_IFIFO: return Fifo;
30
29
  case fsc.S_IFSOCK: return Socket;
31
- default: throw new RdErr(`Unknown mode type ${statmode_} (statmode is most likely corrupted)`);
30
+ default: throw new Err(`Unknown mode type ${statmode_} (statmode is most likely corrupted)`);
32
31
  }
33
32
  }
33
+ export { resolveStat as resStat };
34
+ /**
35
+ * Returns the constructor function corresponding to the type of the given {@link fs.Dirent}.
36
+ *
37
+ * @param dirent The directory entry to check.
38
+ * @returns The constructor function corresponding to the road type (e.g., {@link File}, {@link Folder}, etc.).
39
+ * @throws If the directory entry type is unknown, throws a {@link Err}.
40
+ */
41
+ export function resolveDirent(dirent) {
42
+ // Order by likelihood: files/dicts are most common, followed by symbolic links
43
+ if (dirent.isFile())
44
+ return File;
45
+ if (dirent.isDirectory())
46
+ return Folder;
47
+ if (dirent.isSymbolicLink())
48
+ return SymbolicLink;
49
+ if (dirent.isBlockDevice())
50
+ return BlockDevice;
51
+ if (dirent.isCharacterDevice())
52
+ return CharacterDevice;
53
+ if (dirent.isFIFO())
54
+ return Fifo;
55
+ if (dirent.isSocket())
56
+ return Socket;
57
+ throw new Err(`Unknown dirent type for ${dirent.name}`);
58
+ }
59
+ export { resolveDirent as resDirent };
34
60
  /**
35
61
  * Creates the appropriate subclass of {@link Road} based on the file mode of the specified path.
36
62
  *
@@ -39,16 +65,18 @@ export function resolveMode(statmode_) {
39
65
  * @throws If {@link fp.lstat}/{@link fs.lstatSync} fails to retrieved the status of {@link path_}.
40
66
  */
41
67
  export async function factory(path_) {
42
- return new (resolveMode((await fp.lstat(path_)).mode))(path_, false);
68
+ return new (resolveStat((await fp.lstat(path_)).mode))(path_, false);
43
69
  }
70
+ export { factory as fac, factory as mk };
44
71
  /** Sync version of {@link factory}. */
45
72
  export function factorySync(path_) {
46
- return new (resolveMode(fs.lstatSync(path_).mode))(path_, false);
73
+ return new (resolveStat(fs.lstatSync(path_).mode))(path_, false);
47
74
  }
75
+ export { factorySync as facSync, factorySync as mkSync };
48
76
  /**
49
77
  * A map that keeps track of locked roads to prevent concurrent modifications.
50
78
  *
51
- * @key The absolute path of the road that is currently locked.
79
+ * @key The **absolute** and **normalized** (**resolved**) path of the road that is currently locked.
52
80
  * @value A promise that resolves when the lock on the road is released.
53
81
  *
54
82
  * @remarks The map is initialized lazily when the first lock is created to minimize import-time side effects.
@@ -84,82 +112,78 @@ export class Road {
84
112
  * @param path_ Any path-like string that can be resolved to an absolute path. It will be resolved to an absolute path using {@link ph.resolve}.
85
113
  * @param typeCheck_ If true, the constructor will check if the path corresponds to the expected type of road (e.g., file, folder, etc.) and throw an error if it doesn't. If false, no type checking will be performed.
86
114
  *
87
- * @throws If {@link typeCheck_} is true and the path does not correspond to the expected type of road, a {@link RdErr} will be thrown.
115
+ * @throws If {@link typeCheck_} is true and the path does not correspond to the expected type of road, a {@link Err} will be thrown.
88
116
  */
89
117
  constructor(path_, typeCheck_) {
90
118
  this.pointsTo = ph.resolve(path_);
91
119
  if (typeCheck_ && !this.checkSync())
92
- throw new RdErr(`Type mismatch: '${this.isAt}'`);
120
+ throw new Err(`Type mismatch: '${this.isAt}'`);
121
+ }
122
+ /** @returns An instance of {@link Folder} representing the parent directory of the current road. */
123
+ parent() { return new Folder(ph.dirname(this.isAt), false); }
124
+ /**
125
+ * An async generator that yields the ancestors of the current road, starting from its parent and moving up the directory tree until it reaches the root.
126
+ *
127
+ * @yields Each ancestor folder as a {@link Folder} instance.
128
+ */
129
+ *ancestorsIt() {
130
+ let current = this.parent();
131
+ let parent = current.parent();
132
+ while (current.isAt !== parent.isAt) {
133
+ yield current;
134
+ current = parent;
135
+ parent = current.parent();
136
+ }
93
137
  }
138
+ /** @returns An array of {@link Folder} instances representing the ancestors of the current road. */
139
+ ancestors() { return [...this.ancestorsIt()]; }
94
140
  /**
95
141
  * Aquires a lock for the path represented by this Road instance, preventing concurrent modifications from this and other Road instances pointing to the same path.
96
142
  *
97
- * @param cb_ A callback function that will be called when the lock is released. This is used internally to manage the lock state.
98
143
  * @returns An object with a dispose method that releases the lock when called.
99
- * @throws If the road is immutable, a {@link RdErr} will be thrown.
144
+ * @throws If the road is immutable, a {@link Err} will be thrown.
100
145
  *
101
- * @remarks This method MUST be used with a `using` statement to ensure that the lock is released properly. Failing to do so may result in deadlocks or other concurrency issues.
102
- * Non-deterministic release is unfortunately not possible due to the nature of JavaScript's garbage collection, thus the lock must be released deterministically by the user (to which the `using` statement is a convenient way to do so).
146
+ * @remarks This method MUST be used with a `using` statement to ensure that the lock is released properly. Failing to do so WILL result in deadlocks and other concurrency issues.
147
+ * Non-deterministic release is unfortunately not possible due to the nature of JavaScript's garbage collection, thus the lock must be released deterministically by the user (for which the `using` statement is a convenient way to do so).
148
+ * @remarks Methods may call other methods that each acquire their own locks, and release them independently, meaning from the perspective of other instances, the lock may appear to be free even if the original method still has work to do.
149
+ * To fix this, it's recommeneded to aquire a lock at the beginning and use fs/fp operations within the locked context.
103
150
  */
104
- async lock(cb_ = () => { }) {
151
+ async lock() {
105
152
  if (!this.mutable)
106
- throw new RdErr(`Road to '${this.isAt}' is immutable.`);
153
+ throw new Err(`Road to '${this.isAt}' is immutable.`);
107
154
  lockedRoads ??= new Map();
155
+ const { promise, resolve } = Promise.withResolvers();
156
+ const previous = lockedRoads.get(this.isAt);
157
+ lockedRoads.set(this.isAt, promise);
108
158
  const isAt = this.isAt;
109
- await lockedRoads.get(isAt); // will skip if `undefined` (no lock)
110
- lockedRoads.set(isAt, new Promise(res => cb_ = res));
111
- return {
112
- [Symbol.dispose]() {
113
- cb_();
114
- lockedRoads.delete(isAt);
115
- },
116
- async [Symbol.asyncDispose]() {
117
- cb_();
159
+ const dispose = () => {
160
+ resolve();
161
+ if (lockedRoads.get(isAt) === promise)
118
162
  lockedRoads.delete(isAt);
119
- }
120
163
  };
164
+ await previous;
165
+ return { [Symbol.dispose]: dispose, [Symbol.asyncDispose]: dispose };
121
166
  }
122
167
  /**
123
168
  * Sync version of {@link lock}.
124
- * @throws Also throws a {@link RdErr} if the road is currently locked by another operation as it cannot wait for the lock to be released in a synchronous context.
169
+ * @throws If the road is currently locked by another operation as it cannot wait for the lock to be released in a synchronous context.
125
170
  */
126
- lockSync(cb_ = () => { }) {
171
+ lockSync() {
127
172
  if (!this.mutable)
128
- throw new RdErr(`Road to '${this.isAt}' is immutable.`);
173
+ throw new Err(`Road to '${this.isAt}' is immutable.`);
129
174
  lockedRoads ??= new Map();
175
+ const { promise, resolve } = Promise.withResolvers();
176
+ if (lockedRoads.has(this.isAt))
177
+ throw new Err(`Road to '${this.isAt}' is already locked.`);
178
+ lockedRoads.set(this.isAt, promise);
130
179
  const isAt = this.isAt;
131
- if (lockedRoads.has(isAt))
132
- throw new RdErr(`Road to '${this.isAt}' is currently locked by another operation.`);
133
- lockedRoads.set(isAt, new Promise(res => cb_ = res));
134
- return {
135
- [Symbol.dispose]() {
136
- lockedRoads.delete(isAt);
137
- cb_();
138
- },
139
- async [Symbol.asyncDispose]() {
180
+ const dispose = () => {
181
+ resolve();
182
+ if (lockedRoads.get(isAt) === promise)
140
183
  lockedRoads.delete(isAt);
141
- cb_();
142
- }
143
184
  };
185
+ return { [Symbol.dispose]: dispose, [Symbol.asyncDispose]: dispose };
144
186
  }
145
- /** @returns An instance of {@link Folder} representing the parent directory of the current road. */
146
- parent() { return new Folder(ph.dirname(this.isAt), false); }
147
- /**
148
- * An async generator that yields the ancestors of the current road, starting from its parent and moving up the directory tree until it reaches the root.
149
- *
150
- * @yields Each ancestor folder as a {@link Folder} instance.
151
- */
152
- *ancestorsIt() {
153
- let current = this.parent();
154
- let parent = current.parent();
155
- while (current.isAt !== parent.isAt) {
156
- yield current;
157
- current = parent;
158
- parent = current.parent();
159
- }
160
- }
161
- /** @returns An array of {@link Folder} instances representing the ancestors of the current road. */
162
- ancestors() { return [...this.ancestorsIt()]; }
163
187
  /**
164
188
  * Watches the current entry for changes and resolves when the entry becomes accessible (i.e., exists and can be accessed).
165
189
  *
@@ -215,10 +239,50 @@ export class Road {
215
239
  lstat() { return fp.lstat(this.isAt); }
216
240
  /** @returns The result of {@link fs.lstatSync} for the current entry. */
217
241
  lstatSync() { return fs.lstatSync(this.isAt); }
218
- /** @returns The result of {@link fp.stat} for the current entry. */
219
- stat() { return fp.stat(this.isAt); }
220
- /** @returns The result of {@link fs.statSync} for the current entry. */
221
- statSync() { return fs.statSync(this.isAt); }
242
+ /** Wrapper around {@link fp.rm} with locking. */
243
+ async delete() {
244
+ using _ = await this.lock();
245
+ await fp.rm(this.isAt, { recursive: true, force: true });
246
+ }
247
+ /** Wrapper around {@link fs.rmSync} with locking. */
248
+ deleteSync() {
249
+ using _ = this.lockSync();
250
+ fs.rmSync(this.isAt, { recursive: true, force: true });
251
+ }
252
+ async copy(into_, options_) {
253
+ const newPath = into_.join(this.name);
254
+ await fp.cp(this.isAt, newPath, { recursive: true, ...options_ });
255
+ return new this.constructor(newPath, false);
256
+ }
257
+ copySync(into_, options_) {
258
+ const newPath = into_.join(this.name);
259
+ fs.cpSync(this.isAt, newPath, { recursive: true, ...options_ });
260
+ return new this.constructor(newPath, false);
261
+ }
262
+ async move(into_) {
263
+ using _ = await this.lock();
264
+ const newPath = into_.join(this.name);
265
+ await fp.rename(this.isAt, newPath);
266
+ this.pointsTo = newPath;
267
+ }
268
+ moveSync(into_) {
269
+ using _ = this.lockSync();
270
+ const newPath = into_.join(this.name);
271
+ fs.renameSync(this.isAt, newPath);
272
+ this.pointsTo = newPath;
273
+ }
274
+ async rename(newName_) {
275
+ using _ = await this.lock();
276
+ const newPath = this.parent().join(newName_);
277
+ await fp.rename(this.isAt, newPath);
278
+ this.pointsTo = newPath;
279
+ }
280
+ renameSync(newName_) {
281
+ using _ = this.lockSync();
282
+ const newPath = this.parent().join(newName_);
283
+ fs.renameSync(this.isAt, newPath);
284
+ this.pointsTo = newPath;
285
+ }
222
286
  /** Type narrowing for {@link File} (similar to `instanceof` without unnecessary runtime checks). */
223
287
  isFile() { return false; }
224
288
  /** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
@@ -227,10 +291,6 @@ export class Road {
227
291
  isFolder() { return false; }
228
292
  /** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
229
293
  isDirectory() { return false; }
230
- /** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
231
- isDict() { return false; }
232
- /** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
233
- isDictionary() { return false; }
234
294
  /** Type narrowing for {@link SymbolicLink} (similar to `instanceof` without unnecessary runtime checks). */
235
295
  isSymlink() { return false; }
236
296
  /** Type narrowing for {@link SymbolicLink} (similar to `instanceof` without unnecessary runtime checks). */
@@ -248,6 +308,13 @@ export class Road {
248
308
  }
249
309
  /** Subclass of {@link Road} that represents a file. */
250
310
  export class File extends Road {
311
+ /**
312
+ * Creates a new file at the specified path if it does not already exist.
313
+ *
314
+ * @param at_ The path at which to create the file.
315
+ * @returns A promise that resolves to the newly created `File` instance.
316
+ * @throws if {@link fp.writeFile} throws.
317
+ */
251
318
  static async create(at_) {
252
319
  try {
253
320
  await fp.access(at_, fsc.W_OK);
@@ -257,7 +324,9 @@ export class File extends Road {
257
324
  }
258
325
  return new File(at_, true);
259
326
  }
327
+ /** Alias for {@link File.create}. */
260
328
  static mk = File.create;
329
+ /** Synchronous version of {@link File.create}. */
261
330
  static createSync(at_) {
262
331
  try {
263
332
  fs.accessSync(at_, fs.constants.W_OK);
@@ -267,8 +336,11 @@ export class File extends Road {
267
336
  }
268
337
  return new File(at_, true);
269
338
  }
339
+ /** Alias for {@link File.createSync}. */
270
340
  static mkSync = File.createSync;
341
+ /** The file extension of this file, including the leading dot. */
271
342
  get ext() { return ph.extname(this.isAt); }
343
+ /** The file name without its extension. */
272
344
  get noExt() { return ph.basename(this.isAt, this.ext); }
273
345
  async read(encoding_, flag_) {
274
346
  if (encoding_)
@@ -282,16 +354,50 @@ export class File extends Road {
282
354
  else
283
355
  return fs.readFileSync(this.isAt);
284
356
  }
357
+ async sameAs(other_) {
358
+ if (this.isAt === other_.isAt)
359
+ return true;
360
+ else if ((await fp.lstat(this.isAt)).size !== (await fp.lstat(other_.isAt)).size)
361
+ return false;
362
+ const iter1 = this.itBuff();
363
+ const iter2 = other_.itBuff();
364
+ while (true) {
365
+ const [a, b] = await Promise.all([iter1.next(), iter2.next()]);
366
+ if (a.done && b.done)
367
+ return true;
368
+ if (a.done !== b.done)
369
+ return false;
370
+ if (!a.value.equals(b.value))
371
+ return false;
372
+ }
373
+ }
374
+ sameAsSync(other_) {
375
+ if (this.isAt === other_.isAt)
376
+ return true;
377
+ else if (fs.lstatSync(this.isAt).size !== fs.lstatSync(other_.isAt).size)
378
+ return false;
379
+ const iter1 = this.itBuffSync();
380
+ const iter2 = other_.itBuffSync();
381
+ while (true) {
382
+ const a = iter1.next();
383
+ const b = iter2.next();
384
+ if (a.done && b.done)
385
+ return true;
386
+ if (a.done !== b.done)
387
+ return false;
388
+ if (!a.value.equals(b.value))
389
+ return false;
390
+ }
391
+ }
285
392
  async *itBuff(chunkSize_ = 64 * 1024, flags_ = 'r', mode_) {
286
393
  const fd = await fp.open(this.isAt, flags_, mode_);
287
394
  try {
288
395
  const buffer = Buffer.alloc(chunkSize_);
289
396
  let bytesRead;
290
397
  do {
291
- const readResult = await fd.read(buffer, 0, chunkSize_, null);
292
- bytesRead = readResult.bytesRead;
398
+ bytesRead = (await fd.read(buffer, 0, chunkSize_, null)).bytesRead;
293
399
  if (bytesRead > 0)
294
- yield buffer.subarray(0, bytesRead);
400
+ yield Buffer.from(buffer.subarray(0, bytesRead));
295
401
  } while (bytesRead === chunkSize_);
296
402
  }
297
403
  finally {
@@ -306,13 +412,73 @@ export class File extends Road {
306
412
  do {
307
413
  bytesRead = fs.readSync(fd, buffer, 0, chunkSize_, null);
308
414
  if (bytesRead > 0)
309
- yield buffer.subarray(0, bytesRead);
415
+ yield Buffer.from(buffer.subarray(0, bytesRead));
310
416
  } while (bytesRead === chunkSize_);
311
417
  }
312
418
  finally {
313
419
  fs.closeSync(fd);
314
420
  }
315
421
  }
422
+ async *itLines(chunkSize_ = 64 * 1024, flags_ = 'r', mode_) {
423
+ const decoder = new TextDecoder("utf-8", { fatal: true });
424
+ let carry = "";
425
+ for await (const chunk of this.itBuff(chunkSize_, flags_, mode_)) {
426
+ carry += decoder.decode(chunk, { stream: true });
427
+ let newlineINdex;
428
+ while ((newlineINdex = carry.indexOf("\n")) !== -1) {
429
+ const line = carry.slice(0, newlineINdex);
430
+ yield line.endsWith("\r") ? line.slice(0, -1) : line;
431
+ carry = carry.slice(newlineINdex + 1);
432
+ }
433
+ }
434
+ carry += decoder.decode();
435
+ if (carry.length > 0)
436
+ yield carry.endsWith("\r") ? carry.slice(0, -1) : carry;
437
+ }
438
+ *itLinesSync(chunkSize_ = 64 * 1024, flags_ = 'r', mode_) {
439
+ const decoder = new TextDecoder("utf-8", { fatal: true });
440
+ let carry = "";
441
+ for (const chunk of this.itBuffSync(chunkSize_, flags_, mode_)) {
442
+ carry += decoder.decode(chunk, { stream: true });
443
+ let newlineINdex;
444
+ while ((newlineINdex = carry.indexOf("\n")) !== -1) {
445
+ const line = carry.slice(0, newlineINdex);
446
+ yield line.endsWith("\r") ? line.slice(0, -1) : line;
447
+ carry = carry.slice(newlineINdex + 1);
448
+ }
449
+ }
450
+ carry += decoder.decode();
451
+ if (carry.length > 0)
452
+ yield carry.endsWith("\r") ? carry.slice(0, -1) : carry;
453
+ }
454
+ async hash(algorithm_ = "sha256", options_, encoding_) {
455
+ const hash = cr.createHash(algorithm_, options_);
456
+ for await (const chunk of this.itBuff())
457
+ hash.update(chunk);
458
+ return encoding_ ? hash.digest(encoding_) : hash.digest();
459
+ }
460
+ hashSync(algorithm_ = "sha256", options_, encoding_) {
461
+ const hash = cr.createHash(algorithm_, options_);
462
+ for (const chunk of this.itBuffSync())
463
+ hash.update(chunk);
464
+ return encoding_ ? hash.digest(encoding_) : hash.digest();
465
+ }
466
+ async size() { return (await this.lstat()).size; }
467
+ sizeSync() { return this.lstatSync().size; }
468
+ async writeAtomic(data_, options_) {
469
+ using _ = await this.lock();
470
+ const temp = await Temp(File); // Don't use `using` as we won't clean it up
471
+ await fp.rename(temp.isAt, this.parent().join(temp.name)); // Move temp file to the same directory as the target
472
+ await fp.writeFile(temp.isAt, data_, options_); // Write data to the temp file
473
+ await fp.rename(temp.isAt, this.isAt); // Replace the target file with the temp file atomically
474
+ }
475
+ writeAtomicSync(data_, options_) {
476
+ using _ = this.lockSync();
477
+ const temp = TempSync(File); // Don't use `using` as we won't clean it up
478
+ fs.renameSync(temp.isAt, this.parent().join(temp.name)); // Move temp file to the same directory as the target
479
+ fs.writeFileSync(temp.isAt, data_, options_); // Write data to the temp file
480
+ fs.renameSync(temp.isAt, this.isAt); // Replace the target file with the temp file atomically
481
+ }
316
482
  async write(data_, options_) {
317
483
  using _ = await this.lock();
318
484
  await fp.writeFile(this.isAt, data_, options_);
@@ -329,122 +495,19 @@ export class File extends Road {
329
495
  using _ = this.lockSync();
330
496
  fs.appendFileSync(this.isAt, data_, options_);
331
497
  }
332
- async delete() {
333
- using _ = await this.lock();
334
- await fp.rm(this.isAt, { force: true });
335
- }
336
- deleteSync() {
337
- using _ = this.lockSync();
338
- fs.rmSync(this.isAt, { force: true });
339
- }
340
- async move(into_) {
341
- using _ = await this.lock();
342
- const newPath = into_.join(this.name);
343
- await fp.rename(this.isAt, newPath);
344
- this.pointsTo = newPath;
498
+ async check() { try {
499
+ return (await fp.lstat(this.isAt)).isFile();
345
500
  }
346
- moveSync(into_) {
347
- using _ = this.lockSync();
348
- const newPath = into_.join(this.name);
349
- fs.renameSync(this.isAt, newPath);
350
- this.pointsTo = newPath;
351
- }
352
- async copy(into_) {
353
- const newPath = into_.join(this.name);
354
- await fp.copyFile(this.isAt, newPath);
355
- return new File(newPath, false);
356
- }
357
- copySync(into_) {
358
- const newPath = into_.join(this.name);
359
- fs.copyFileSync(this.isAt, newPath);
360
- return new File(newPath, false);
361
- }
362
- async rename(to_) {
363
- using _ = await this.lock();
364
- const newPath = this.parent().join(to_);
365
- await fp.rename(this.isAt, newPath);
366
- this.pointsTo = newPath;
367
- }
368
- renameSync(to_) {
369
- using _ = this.lockSync();
370
- const newPath = this.parent().join(to_);
371
- fs.renameSync(this.isAt, newPath);
372
- this.pointsTo = newPath;
373
- }
374
- async check() { return (await fp.lstat(this.isAt)).isFile(); }
375
- checkSync() { return fs.lstatSync(this.isAt).isFile(); }
376
- isFile() { return true; }
377
- }
378
- export async function hash(f_, algorithm_ = "sha256", options_, encoding_) {
379
- const hash = cr.createHash(algorithm_, options_);
380
- for await (const chunk of f_.itBuff())
381
- hash.update(chunk);
382
- return encoding_ ? hash.digest(encoding_) : hash.digest();
383
- }
384
- export function hashSync(f_, algorithm_ = "sha256", options_, encoding_) {
385
- const hash = cr.createHash(algorithm_, options_);
386
- for (const chunk of f_.itBuffSync())
387
- hash.update(chunk);
388
- return encoding_ ? hash.digest(encoding_) : hash.digest();
389
- }
390
- export async function streamHash(f_, algorithm_ = "sha256", options_, encoding_) {
391
- const hash = cr.createHash(algorithm_, options_);
392
- for await (const chunk of f_.itBuff())
393
- hash.update(chunk);
394
- return encoding_ ? hash.digest(encoding_) : hash.digest();
395
- }
396
- export function streamHashSync(f_, algorithm_ = "sha256", options_, encoding_) {
397
- const hash = cr.createHash(algorithm_, options_);
398
- for (const chunk of f_.itBuffSync())
399
- hash.update(chunk);
400
- return encoding_ ? hash.digest(encoding_) : hash.digest();
401
- }
402
- export async function* itLines(f_, options_ = { encoding: 'utf-8' }) {
403
- const readStream = fs.createReadStream(f_.isAt, options_);
404
- const rlInterface = rl.createInterface({ input: readStream, crlfDelay: Infinity });
405
- try {
406
- for await (const line of rlInterface)
407
- yield line;
408
- }
409
- finally {
410
- rlInterface.close();
411
- readStream.destroy();
412
- }
413
- }
414
- export async function fileSameAs(f1_, f2_) {
415
- if (f1_.isAt === f2_.isAt)
416
- return true;
417
- else if ((await fp.lstat(f1_.isAt)).size !== (await fp.lstat(f2_.isAt)).size)
501
+ catch {
418
502
  return false;
419
- const iter1 = f1_.itBuff();
420
- const iter2 = f2_.itBuff();
421
- while (true) {
422
- const [a, b] = await Promise.all([iter1.next(), iter2.next()]);
423
- if (a.done && b.done)
424
- return true;
425
- if (a.done !== b.done)
426
- return false;
427
- if (!a.value.equals(b.value))
428
- return false;
503
+ } }
504
+ checkSync() { try {
505
+ return fs.lstatSync(this.isAt).isFile();
429
506
  }
430
- }
431
- export function fileSameAsSync(f1_, f2_) {
432
- if (f1_.isAt === f2_.isAt)
433
- return true;
434
- else if (fs.statSync(f1_.isAt).size !== fs.statSync(f2_.isAt).size)
507
+ catch {
435
508
  return false;
436
- const iter1 = f1_.itBuffSync();
437
- const iter2 = f2_.itBuffSync();
438
- while (true) {
439
- const a = iter1.next();
440
- const b = iter2.next();
441
- if (a.done && b.done)
442
- return true;
443
- if (a.done !== b.done)
444
- return false;
445
- if (!a.value.equals(b.value))
446
- return false;
447
- }
509
+ } }
510
+ isFile() { return true; }
448
511
  }
449
512
  export class Folder extends Road {
450
513
  static async create(at_) {
@@ -470,40 +533,55 @@ export class Folder extends Road {
470
533
  join(...paths_) {
471
534
  return ph.join(this.isAt, ...paths_);
472
535
  }
473
- async *it(expectedType_) {
474
- for (const entryName of await fp.readdir(this.isAt)) {
475
- const road = await factory(this.join(entryName));
476
- if (!expectedType_ || road instanceof expectedType_)
536
+ async *it(filter_) {
537
+ for (const entry of await fp.readdir(this.isAt, { withFileTypes: true })) {
538
+ const road = new (resolveDirent(entry))(this.join(entry.name), false);
539
+ if (!filter_ || filter_(road))
477
540
  yield road;
478
541
  }
479
542
  }
480
- *itSync(expectedType_) {
481
- for (const entry of fs.readdirSync(this.isAt)) {
482
- const road = factorySync(this.join(entry));
483
- if (!expectedType_ || road instanceof expectedType_)
543
+ *itSync(filter_) {
544
+ for (const entry of fs.readdirSync(this.isAt, { withFileTypes: true })) {
545
+ const road = new (resolveDirent(entry))(this.join(entry.name), false);
546
+ if (!filter_ || filter_(road))
484
547
  yield road;
485
548
  }
486
549
  }
487
- async list(expectedType_) {
488
- const entries = (await fp.readdir(this.isAt)).map(async (entry) => factory(this.join(entry)));
550
+ async list(filter_) {
551
+ const entries = (await fp.readdir(this.isAt, { withFileTypes: true })).map(e => new (resolveDirent(e))(this.join(e.name), false));
489
552
  const resolvedEntries = await Promise.all(entries);
490
- if (!expectedType_)
553
+ if (!filter_)
491
554
  return resolvedEntries;
492
- return resolvedEntries.filter(entry => entry instanceof expectedType_);
555
+ return resolvedEntries.filter(entry => filter_(entry));
493
556
  }
494
- listSync(expectedType_) {
495
- const entries = fs.readdirSync(this.isAt).map(entry => factorySync(this.join(entry)));
496
- if (!expectedType_)
557
+ listSync(filter_) {
558
+ const entries = fs.readdirSync(this.isAt, { withFileTypes: true }).map(e => new (resolveDirent(e))(this.join(e.name), false));
559
+ if (!filter_)
497
560
  return entries;
498
- return entries.filter(entry => entry instanceof expectedType_);
561
+ return entries.filter(entry => filter_(entry));
562
+ }
563
+ async *walk(filter_) {
564
+ for await (const entry of this.it()) {
565
+ if (!filter_ || filter_(entry))
566
+ yield entry;
567
+ if (entry.isDir())
568
+ yield* entry.walk(filter_);
569
+ }
570
+ }
571
+ *walkSync(filter_) {
572
+ for (const entry of this.itSync()) {
573
+ if (!filter_ || filter_(entry))
574
+ yield entry;
575
+ if (entry.isDir())
576
+ yield* entry.walkSync(filter_);
577
+ }
499
578
  }
500
- async find(name_, expectedType_) {
579
+ async find(name_, expect_) {
501
580
  try {
502
- await fp.access(this.join(name_), fs.constants.F_OK);
503
581
  const found = await factory(this.join(name_));
504
- if (!expectedType_)
582
+ if (!expect_)
505
583
  return found;
506
- if (found instanceof expectedType_)
584
+ if (found instanceof expect_)
507
585
  return found;
508
586
  return null;
509
587
  }
@@ -511,16 +589,16 @@ export class Folder extends Road {
511
589
  return null;
512
590
  }
513
591
  }
514
- findSync(name_, expectedType_) {
592
+ findSync(name_, expect_) {
515
593
  try {
516
594
  const found = factorySync(this.join(name_));
517
- if (!expectedType_)
595
+ if (!expect_)
518
596
  return found;
519
- if (found instanceof expectedType_)
597
+ if (found instanceof expect_)
520
598
  return found;
521
599
  return null;
522
600
  }
523
- catch {
601
+ catch (e) {
524
602
  return null;
525
603
  }
526
604
  }
@@ -534,55 +612,33 @@ export class Folder extends Road {
534
612
  createable_.createSync(newPath);
535
613
  return factorySync(newPath);
536
614
  }
537
- async delete(options_ = { recursive: true }) {
538
- using _ = await this.lock();
539
- await fp.rm(this.isAt, options_);
615
+ async size() {
616
+ let size = 0;
617
+ for await (const entry of this.it())
618
+ size += await entry.size();
619
+ return size;
540
620
  }
541
- deleteSync(options_ = { recursive: true }) {
542
- using _ = this.lockSync();
543
- fs.rmSync(this.isAt, options_);
621
+ sizeSync() {
622
+ let size = 0;
623
+ for (const entry of this.itSync())
624
+ size += entry.sizeSync();
625
+ return size;
544
626
  }
545
- async move(into_) {
546
- using _ = await this.lock();
547
- const newPath = into_.join(this.name);
548
- await fp.rename(this.isAt, newPath);
549
- this.pointsTo = newPath;
627
+ async check() { try {
628
+ return (await fp.lstat(this.isAt)).isDirectory();
550
629
  }
551
- moveSync(into_) {
552
- using _ = this.lockSync();
553
- const newPath = into_.join(this.name);
554
- fs.renameSync(this.isAt, newPath);
555
- this.pointsTo = newPath;
556
- }
557
- async copy(into_) {
558
- const newPath = into_.join(this.name);
559
- await fp.cp(this.isAt, newPath, { recursive: true });
560
- return new Folder(newPath, false);
561
- }
562
- copySync(into_) {
563
- const newPath = into_.join(this.name);
564
- fs.cpSync(this.isAt, newPath, { recursive: true });
565
- return new Folder(newPath, false);
566
- }
567
- async rename(to_) {
568
- using _ = await this.lock();
569
- const newPath = this.parent().join(to_);
570
- await fp.rename(this.isAt, newPath);
571
- this.pointsTo = newPath;
572
- }
573
- renameSync(to_) {
574
- using _ = this.lockSync();
575
- const newPath = this.parent().join(to_);
576
- fs.renameSync(this.isAt, newPath);
577
- this.pointsTo = newPath;
630
+ catch {
631
+ return false;
632
+ } }
633
+ checkSync() { try {
634
+ return fs.lstatSync(this.isAt).isDirectory();
578
635
  }
579
- async check() { return (await fp.lstat(this.isAt)).isDirectory(); }
580
- checkSync() { return fs.lstatSync(this.isAt).isDirectory(); }
636
+ catch {
637
+ return false;
638
+ } }
581
639
  isFolder() { return true; }
582
640
  isDir() { return true; }
583
641
  isDirectory() { return true; }
584
- isDict() { return true; }
585
- isDictionary() { return true; }
586
642
  }
587
643
  export function sysRoot() { return new Folder(ph.parse(process.cwd()).root, false); }
588
644
  export function home() { return new Folder(os.homedir(), false); }
@@ -617,13 +673,17 @@ export class SymbolicLink extends Road {
617
673
  return factorySync(ph.resolve(ph.dirname(this.isAt), fs.readlinkSync(this.isAt)));
618
674
  }
619
675
  async retarget(to_) {
620
- await this.delete();
621
- return fp.symlink(to_.isAt, this.isAt);
676
+ using _ = await this.lock();
677
+ await fp.unlink(this.isAt);
678
+ await fp.symlink(to_.isAt, this.isAt);
622
679
  }
623
680
  retargetSync(to_) {
624
- this.deleteSync();
681
+ using _ = this.lockSync();
682
+ fs.unlinkSync(this.isAt);
625
683
  fs.symlinkSync(to_.isAt, this.isAt);
626
684
  }
685
+ async size() { return (await this.lstat()).size; }
686
+ sizeSync() { return this.lstatSync().size; }
627
687
  async delete() {
628
688
  using _ = await this.lock();
629
689
  await fp.unlink(this.isAt);
@@ -632,87 +692,136 @@ export class SymbolicLink extends Road {
632
692
  using _ = this.lockSync();
633
693
  fs.unlinkSync(this.isAt);
634
694
  }
635
- async move(into_) {
636
- using _ = await this.lock();
637
- const newPath = into_.join(this.name);
638
- await fp.rename(this.isAt, newPath);
639
- this.pointsTo = newPath;
695
+ async check() { try {
696
+ return (await fp.lstat(this.isAt)).isSymbolicLink();
640
697
  }
641
- moveSync(into_) {
642
- using _ = this.lockSync();
643
- const newPath = into_.join(this.name);
644
- fs.renameSync(this.isAt, newPath);
645
- this.pointsTo = newPath;
646
- }
647
- async copy(into_) {
648
- const newPath = into_.join(this.name);
649
- const target = await this.target();
650
- await fp.symlink(target.isAt, newPath);
651
- return new SymbolicLink(newPath, false);
652
- }
653
- copySync(into_) {
654
- const newPath = into_.join(this.name);
655
- const target = this.targetSync();
656
- fs.symlinkSync(target.isAt, newPath);
657
- return new SymbolicLink(newPath, false);
658
- }
659
- async rename(to_) {
660
- using _ = await this.lock();
661
- const newPath = this.parent().join(to_);
662
- await fp.rename(this.isAt, newPath);
663
- this.pointsTo = newPath;
664
- }
665
- renameSync(to_) {
666
- using _ = this.lockSync();
667
- const newPath = this.parent().join(to_);
668
- fs.renameSync(this.isAt, newPath);
669
- this.pointsTo = newPath;
698
+ catch {
699
+ return false;
700
+ } }
701
+ checkSync() { try {
702
+ return fs.lstatSync(this.isAt).isSymbolicLink();
670
703
  }
671
- async check() { return (await fp.lstat(this.isAt)).isSymbolicLink(); }
672
- checkSync() { return fs.lstatSync(this.isAt).isSymbolicLink(); }
704
+ catch {
705
+ return false;
706
+ } }
673
707
  isSymlink() { return true; }
674
708
  isSymbolicLink() { return true; }
675
709
  }
676
710
  export { SymbolicLink as Symlink };
711
+ here().walk(r => !r.isSymlink());
677
712
  export class UnusableRoad extends Road {
678
713
  mutable = false; // Modification will cause system issues (e.g. deleting a device file)
679
- constructor(...args_) {
680
- super(...args_);
681
- Object.freeze(this);
682
- }
683
- error() { throw new RdErr(`${this.constructor.name} at '${this.isAt}' is a system-level resource thus not subject to modification.`); }
684
- async lock() { return this.error(); }
714
+ async size() { return 0; }
715
+ sizeSync() { return 0; }
716
+ error() { throw new Err(`${this.constructor.name} at '${this.isAt}' is a system-level resource thus not subject to modification.`); }
717
+ /** @deprecated System-level resources (UnusableRoad) can't/shouldn't be locked */
718
+ lock() { return this.error(); }
719
+ /** @deprecated System-level resources (UnusableRoad) can't/shouldn't be locked */
685
720
  lockSync() { return this.error(); }
686
- async delete() { return this.error(); }
721
+ /** @deprecated System-level resources (UnusableRoad) can't/shouldn't be deleted */
722
+ delete() { return this.error(); }
723
+ /** @deprecated System-level resources (UnusableRoad) can't/shouldn't be deleted */
687
724
  deleteSync() { return this.error(); }
688
- async move() { return this.error(); }
725
+ /** @deprecated System-level resources (UnusableRoad) can't/shouldn't be moved */
726
+ move() { return this.error(); }
727
+ /** @deprecated System-level resources (UnusableRoad) can't/shouldn't be moved */
689
728
  moveSync() { return this.error(); }
690
- async copy() { return this.error(); }
729
+ /** @deprecated System-level resources (UnusableRoad) can't/shouldn't be copied */
730
+ copy() { return this.error(); }
731
+ /** @deprecated System-level resources (UnusableRoad) can't/shouldn't be copied */
691
732
  copySync() { return this.error(); }
692
- async rename() { return this.error(); }
733
+ /** @deprecated System-level resources (UnusableRoad) can't/shouldn't be renamed */
734
+ rename() { return this.error(); }
735
+ /** @deprecated System-level resources (UnusableRoad) can't/shouldn't be renamed */
693
736
  renameSync() { return this.error(); }
694
737
  isUnusable() { return true; }
695
738
  }
696
739
  export class BlockDevice extends UnusableRoad {
697
- async check() { return (await fp.lstat(this.isAt)).isBlockDevice(); }
698
- checkSync() { return fs.lstatSync(this.isAt).isBlockDevice(); }
740
+ async check() { try {
741
+ return (await fp.lstat(this.isAt)).isBlockDevice();
742
+ }
743
+ catch {
744
+ return false;
745
+ } }
746
+ checkSync() { try {
747
+ return fs.lstatSync(this.isAt).isBlockDevice();
748
+ }
749
+ catch {
750
+ return false;
751
+ } }
699
752
  isBlockDevice() { return true; }
700
753
  }
701
754
  export class CharacterDevice extends UnusableRoad {
702
- async check() { return (await fp.lstat(this.isAt)).isCharacterDevice(); }
703
- checkSync() { return fs.lstatSync(this.isAt).isCharacterDevice(); }
755
+ async check() { try {
756
+ return (await fp.lstat(this.isAt)).isCharacterDevice();
757
+ }
758
+ catch {
759
+ return false;
760
+ } }
761
+ checkSync() { try {
762
+ return fs.lstatSync(this.isAt).isCharacterDevice();
763
+ }
764
+ catch {
765
+ return false;
766
+ } }
704
767
  isCharacterDevice() { return true; }
705
768
  }
706
769
  export class Fifo extends UnusableRoad {
707
- async check() { return (await fp.lstat(this.isAt)).isFIFO(); }
708
- checkSync() { return fs.lstatSync(this.isAt).isFIFO(); }
770
+ async check() { try {
771
+ return (await fp.lstat(this.isAt)).isFIFO();
772
+ }
773
+ catch {
774
+ return false;
775
+ } }
776
+ checkSync() { try {
777
+ return fs.lstatSync(this.isAt).isFIFO();
778
+ }
779
+ catch {
780
+ return false;
781
+ } }
709
782
  isFifo() { return true; }
710
783
  }
711
784
  export class Socket extends UnusableRoad {
712
- async check() { return (await fp.lstat(this.isAt)).isSocket(); }
713
- checkSync() { return fs.lstatSync(this.isAt).isSocket(); }
785
+ async check() { try {
786
+ return (await fp.lstat(this.isAt)).isSocket();
787
+ }
788
+ catch {
789
+ return false;
790
+ } }
791
+ checkSync() { try {
792
+ return fs.lstatSync(this.isAt).isSocket();
793
+ }
794
+ catch {
795
+ return false;
796
+ } }
714
797
  isSocket() { return true; }
715
798
  }
799
+ export async function Temp(createable_) {
800
+ let t = await createable_.create(tmp().join(`instrumentality@${cr.randomUUID()}`));
801
+ return Object.assign(t, {
802
+ [Symbol.dispose]() { try {
803
+ t.deleteSync();
804
+ }
805
+ catch { } toDelete?.delete(t.isAt); finalizer?.unregister(t); },
806
+ async [Symbol.asyncDispose]() { try {
807
+ await t.delete();
808
+ }
809
+ catch { } toDelete?.delete(t.isAt); finalizer?.unregister(t); }
810
+ });
811
+ }
812
+ export function TempSync(createable_) {
813
+ let t = createable_.createSync(tmp().join(`instrumentality@${cr.randomUUID()}`));
814
+ return Object.assign(t, {
815
+ [Symbol.dispose]() { try {
816
+ t.deleteSync();
817
+ }
818
+ catch { } toDelete?.delete(t.isAt); finalizer?.unregister(t); },
819
+ async [Symbol.asyncDispose]() { try {
820
+ await t.delete();
821
+ }
822
+ catch { } toDelete?.delete(t.isAt); finalizer?.unregister(t); }
823
+ });
824
+ }
716
825
  let finalizer = null;
717
826
  let toDelete = null;
718
827
  let exitHandlerRegistered = null;
@@ -745,8 +854,22 @@ export function registerToCleanup(self_) {
745
854
  toDelete.add(self_.isAt);
746
855
  finalizer.register(self_, self_.isAt, self_);
747
856
  }
748
- export function Temp(createable_, autoCleanup_) {
749
- let t = createable_.createSync(tmp().join(`instrumentality@${cr.randomUUID()}`));
857
+ export async function AutoTemp(createable_) {
858
+ let t = await createable_.mk(tmp().join(`instrumentality@${cr.randomUUID()}`));
859
+ registerToCleanup(t);
860
+ return Object.freeze(Object.assign(t, {
861
+ [Symbol.dispose]() { try {
862
+ t.deleteSync();
863
+ }
864
+ catch { } toDelete?.delete(t.isAt); finalizer?.unregister(t); },
865
+ async [Symbol.asyncDispose]() { try {
866
+ await t.delete();
867
+ }
868
+ catch { } toDelete?.delete(t.isAt); finalizer?.unregister(t); }
869
+ }));
870
+ }
871
+ export function AutoTempSync(createable_, autoCleanup_) {
872
+ let t = createable_.mk(tmp().join(`instrumentality@${cr.randomUUID()}`));
750
873
  if (autoCleanup_)
751
874
  registerToCleanup(t);
752
875
  return Object.freeze(Object.assign(t, {
@@ -760,4 +883,3 @@ export function Temp(createable_, autoCleanup_) {
760
883
  catch { } toDelete?.delete(t.isAt); finalizer?.unregister(t); }
761
884
  }));
762
885
  }
763
- 11;