instrumentality 0.0.7 → 0.0.9
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/README.md +3 -5
- package/dist/base.d.ts +11 -12
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +23 -39
- package/dist/dom.d.ts +1 -6
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +1 -6
- package/dist/road.d.ts +121 -103
- package/dist/road.d.ts.map +1 -1
- package/dist/road.js +401 -388
- package/package.json +5 -3
- package/src/base.ts +22 -43
- package/src/dom.ts +1 -10
- package/src/road.ts +352 -426
package/src/road.ts
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as cr from "node:crypto"
|
|
2
2
|
import * as fs from "node:fs"; import { constants as fsc } from "node:fs"
|
|
3
3
|
import * as fp from "node:fs/promises"
|
|
4
4
|
import * as ph from "node:path"
|
|
5
5
|
import * as os from "node:os"
|
|
6
|
-
import * as cr from "node:crypto"
|
|
7
6
|
import { on } from "node:events"
|
|
8
|
-
import
|
|
7
|
+
import { InsErr } from "./base.ts"
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
/** Subclass of {@link
|
|
13
|
-
export class
|
|
14
|
-
export {
|
|
11
|
+
/** Subclass of {@link InsErr} that represents an error thrown from this specific module of the library */
|
|
12
|
+
export class Err extends InsErr { override name = "Instrumentality-Road-Error" }
|
|
13
|
+
export { Err as RoadError, Err as RdErr }
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* Returns the constructor function corresponding to the file mode (statmode).
|
|
20
19
|
*
|
|
21
|
-
* @param statmode_ The file mode to check.
|
|
20
|
+
* @param statmode_ The file mode or {@link fs.Dirent} to check.
|
|
22
21
|
* @returns The constructor function corresponding to the road type (e.g., {@link File}, {@link Folder}, etc.).
|
|
23
|
-
* @throws If the file mode is unknown, throws a {@link
|
|
22
|
+
* @throws If the file mode is unknown, throws a {@link Err}.
|
|
24
23
|
*/
|
|
25
|
-
export function
|
|
24
|
+
export function resolveStat(statmode_: number): typeof File | typeof Folder | typeof BlockDevice | typeof CharacterDevice | typeof SymbolicLink | typeof Fifo | typeof Socket {
|
|
26
25
|
switch (statmode_ & fsc.S_IFMT) {
|
|
27
26
|
case fsc.S_IFREG: return File
|
|
28
27
|
case fsc.S_IFDIR: return Folder
|
|
@@ -31,9 +30,30 @@ export function resolveMode(statmode_: number): typeof File | typeof Folder | ty
|
|
|
31
30
|
case fsc.S_IFLNK: return SymbolicLink
|
|
32
31
|
case fsc.S_IFIFO: return Fifo
|
|
33
32
|
case fsc.S_IFSOCK: return Socket
|
|
34
|
-
default: throw new
|
|
33
|
+
default: throw new Err(`Unknown mode type ${statmode_} (statmode is most likely corrupted)`)
|
|
35
34
|
}
|
|
36
35
|
}
|
|
36
|
+
export { resolveStat as resStat }
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns the constructor function corresponding to the type of the given {@link fs.Dirent}.
|
|
40
|
+
*
|
|
41
|
+
* @param dirent The directory entry to check.
|
|
42
|
+
* @returns The constructor function corresponding to the road type (e.g., {@link File}, {@link Folder}, etc.).
|
|
43
|
+
* @throws If the directory entry type is unknown, throws a {@link Err}.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveDirent(dirent: fs.Dirent): typeof File | typeof Folder | typeof BlockDevice | typeof CharacterDevice | typeof SymbolicLink | typeof Fifo | typeof Socket {
|
|
46
|
+
// Order by likelihood: files/dicts are most common, followed by symbolic links
|
|
47
|
+
if (dirent.isFile()) return File
|
|
48
|
+
if (dirent.isDirectory()) return Folder
|
|
49
|
+
if (dirent.isSymbolicLink()) return SymbolicLink
|
|
50
|
+
if (dirent.isBlockDevice()) return BlockDevice
|
|
51
|
+
if (dirent.isCharacterDevice()) return CharacterDevice
|
|
52
|
+
if (dirent.isFIFO()) return Fifo
|
|
53
|
+
if (dirent.isSocket()) return Socket
|
|
54
|
+
throw new Err(`Unknown dirent type for ${dirent.name}`)
|
|
55
|
+
}
|
|
56
|
+
export { resolveDirent as resDirent }
|
|
37
57
|
|
|
38
58
|
|
|
39
59
|
|
|
@@ -45,19 +65,21 @@ export function resolveMode(statmode_: number): typeof File | typeof Folder | ty
|
|
|
45
65
|
* @throws If {@link fp.lstat}/{@link fs.lstatSync} fails to retrieved the status of {@link path_}.
|
|
46
66
|
*/
|
|
47
67
|
export async function factory(path_: string) {
|
|
48
|
-
return new (
|
|
68
|
+
return new (resolveStat((await fp.lstat(path_)).mode))(path_, false)
|
|
49
69
|
}
|
|
70
|
+
export { factory as fac, factory as mk }
|
|
50
71
|
/** Sync version of {@link factory}. */
|
|
51
72
|
export function factorySync(path_: string) {
|
|
52
|
-
return new (
|
|
73
|
+
return new (resolveStat(fs.lstatSync(path_).mode))(path_, false)
|
|
53
74
|
}
|
|
75
|
+
export { factorySync as facSync, factorySync as mkSync }
|
|
54
76
|
|
|
55
77
|
|
|
56
78
|
|
|
57
79
|
/**
|
|
58
80
|
* A map that keeps track of locked roads to prevent concurrent modifications.
|
|
59
81
|
*
|
|
60
|
-
* @key The absolute path of the road that is currently locked.
|
|
82
|
+
* @key The **absolute** and **normalized** (**resolved**) path of the road that is currently locked.
|
|
61
83
|
* @value A promise that resolves when the lock on the road is released.
|
|
62
84
|
*
|
|
63
85
|
* @remarks The map is initialized lazily when the first lock is created to minimize import-time side effects.
|
|
@@ -90,7 +112,7 @@ export abstract class Road {
|
|
|
90
112
|
/** The amount of path segments in the absolute path to the file or directory represented by this Road instance, minus one (i.e., the depth of the path in the file system hierarchy). */
|
|
91
113
|
get depth() { return this.isAt.split(ph.sep).length - 1 }
|
|
92
114
|
/** Same as {@link isAt} but for compatibility with external APIs. */
|
|
93
|
-
toString() { return this.isAt }
|
|
115
|
+
toString(): string { return this.isAt }
|
|
94
116
|
|
|
95
117
|
/**
|
|
96
118
|
* Creates a new instance of the Road class.
|
|
@@ -98,64 +120,12 @@ export abstract class Road {
|
|
|
98
120
|
* @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}.
|
|
99
121
|
* @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.
|
|
100
122
|
*
|
|
101
|
-
* @throws If {@link typeCheck_} is true and the path does not correspond to the expected type of road, a {@link
|
|
123
|
+
* @throws If {@link typeCheck_} is true and the path does not correspond to the expected type of road, a {@link Err} will be thrown.
|
|
102
124
|
*/
|
|
103
|
-
constructor(path_: string, typeCheck_: boolean
|
|
125
|
+
constructor(path_: string, typeCheck_: boolean) {
|
|
104
126
|
this.pointsTo = ph.resolve(path_)
|
|
105
127
|
if (typeCheck_ && !this.checkSync())
|
|
106
|
-
throw new
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 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.
|
|
111
|
-
*
|
|
112
|
-
* @param cb_ A callback function that will be called when the lock is released. This is used internally to manage the lock state.
|
|
113
|
-
* @returns An object with a dispose method that releases the lock when called.
|
|
114
|
-
* @throws If the road is immutable, a {@link RdErr} will be thrown.
|
|
115
|
-
*
|
|
116
|
-
* @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.
|
|
117
|
-
* 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).
|
|
118
|
-
*/
|
|
119
|
-
protected async lock(cb_ = () => {}): Promise<AsyncDisposable & Disposable> {
|
|
120
|
-
if (!this.mutable)
|
|
121
|
-
throw new RdErr(`Road to '${this.isAt}' is immutable.`)
|
|
122
|
-
lockedRoads ??= new Map<string, Promise<void>>()
|
|
123
|
-
const isAt = this.isAt
|
|
124
|
-
await lockedRoads.get(isAt) // will skip if `undefined` (no lock)
|
|
125
|
-
lockedRoads.set(isAt, new Promise<void>(res => cb_ = res))
|
|
126
|
-
return {
|
|
127
|
-
[Symbol.dispose]() {
|
|
128
|
-
cb_()
|
|
129
|
-
lockedRoads!.delete(isAt)
|
|
130
|
-
},
|
|
131
|
-
async [Symbol.asyncDispose]() {
|
|
132
|
-
cb_()
|
|
133
|
-
lockedRoads!.delete(isAt)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Sync version of {@link lock}.
|
|
139
|
-
* @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.
|
|
140
|
-
*/
|
|
141
|
-
protected lockSync(cb_ = () => {}): Disposable & AsyncDisposable {
|
|
142
|
-
if (!this.mutable)
|
|
143
|
-
throw new RdErr(`Road to '${this.isAt}' is immutable.`)
|
|
144
|
-
lockedRoads ??= new Map<string, Promise<void>>()
|
|
145
|
-
const isAt = this.isAt
|
|
146
|
-
if (lockedRoads.has(isAt))
|
|
147
|
-
throw new RdErr(`Road to '${this.isAt}' is currently locked by another operation.`)
|
|
148
|
-
lockedRoads.set(isAt, new Promise<void>(res => cb_ = res))
|
|
149
|
-
return {
|
|
150
|
-
[Symbol.dispose]() {
|
|
151
|
-
lockedRoads!.delete(isAt)
|
|
152
|
-
cb_()
|
|
153
|
-
},
|
|
154
|
-
async [Symbol.asyncDispose]() {
|
|
155
|
-
lockedRoads!.delete(isAt)
|
|
156
|
-
cb_()
|
|
157
|
-
}
|
|
158
|
-
}
|
|
128
|
+
throw new Err(`Type mismatch: '${this.isAt}'`)
|
|
159
129
|
}
|
|
160
130
|
|
|
161
131
|
/** @returns An instance of {@link Folder} representing the parent directory of the current road. */
|
|
@@ -177,6 +147,34 @@ export abstract class Road {
|
|
|
177
147
|
/** @returns An array of {@link Folder} instances representing the ancestors of the current road. */
|
|
178
148
|
ancestors(): Folder[] { return [...this.ancestorsIt()] }
|
|
179
149
|
|
|
150
|
+
protected reserveLock(allowConcurrent: boolean): Disposable & { previous: Promise<void> | undefined } {
|
|
151
|
+
if (!this.mutable)
|
|
152
|
+
throw new Err(`Road to '${this.isAt}' is immutable.`)
|
|
153
|
+
lockedRoads ??= new Map()
|
|
154
|
+
const { promise, resolve } = Promise.withResolvers<void>()
|
|
155
|
+
const isAt = this.isAt
|
|
156
|
+
const previous = lockedRoads.get(this.isAt)
|
|
157
|
+
if (!allowConcurrent && previous)
|
|
158
|
+
throw new Err(`Road to '${this.isAt}' is already locked.`)
|
|
159
|
+
lockedRoads.set(this.isAt, promise)
|
|
160
|
+
return {
|
|
161
|
+
[Symbol.dispose]: () => {
|
|
162
|
+
resolve()
|
|
163
|
+
if (lockedRoads!.get(isAt) === promise)
|
|
164
|
+
lockedRoads!.delete(isAt)
|
|
165
|
+
},
|
|
166
|
+
previous,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async lock(): Promise<Disposable> {
|
|
170
|
+
const l = this.reserveLock(true)
|
|
171
|
+
try { await l.previous } catch {}
|
|
172
|
+
return l
|
|
173
|
+
}
|
|
174
|
+
lockSync(): Disposable {
|
|
175
|
+
return this.reserveLock(false)
|
|
176
|
+
}
|
|
177
|
+
|
|
180
178
|
/**
|
|
181
179
|
* Watches the current entry for changes and resolves when the entry becomes accessible (i.e., exists and can be accessed).
|
|
182
180
|
*
|
|
@@ -223,20 +221,55 @@ export abstract class Road {
|
|
|
223
221
|
lstat() { return fp.lstat(this.isAt) }
|
|
224
222
|
/** @returns The result of {@link fs.lstatSync} for the current entry. */
|
|
225
223
|
lstatSync() { return fs.lstatSync(this.isAt) }
|
|
226
|
-
/** @returns The result of {@link fp.stat} for the current entry. */
|
|
227
|
-
stat() { return fp.stat(this.isAt) }
|
|
228
|
-
/** @returns The result of {@link fs.statSync} for the current entry. */
|
|
229
|
-
statSync() { return fs.statSync(this.isAt) }
|
|
230
224
|
|
|
231
|
-
|
|
232
|
-
abstract
|
|
233
|
-
abstract
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
225
|
+
/** Get the size of the this entry in bytes. */
|
|
226
|
+
abstract size(): Promise<number>
|
|
227
|
+
abstract sizeSync(): number
|
|
228
|
+
|
|
229
|
+
/** Wrapper around {@link fp.rm} with locking. */
|
|
230
|
+
async delete(): Promise<void> {
|
|
231
|
+
using _ = await this.lock()
|
|
232
|
+
await fp.rm(this.isAt, { recursive: true, force: true })
|
|
233
|
+
}
|
|
234
|
+
/** Wrapper around {@link fs.rmSync} with locking. */
|
|
235
|
+
deleteSync(): void {
|
|
236
|
+
using _ = this.lockSync()
|
|
237
|
+
fs.rmSync(this.isAt, { recursive: true, force: true })
|
|
238
|
+
}
|
|
239
|
+
async copy(into_: Folder): Promise<this> {
|
|
240
|
+
const newPath = into_.join(this.name)
|
|
241
|
+
await fp.cp(this.isAt, newPath, { recursive: true, force: true })
|
|
242
|
+
return new (this.constructor as new (path: string, typeCheck: boolean) => this)(newPath, false)
|
|
243
|
+
}
|
|
244
|
+
copySync(into_: Folder): this {
|
|
245
|
+
const newPath = into_.join(this.name)
|
|
246
|
+
fs.cpSync(this.isAt, newPath, { recursive: true, force: true })
|
|
247
|
+
return new (this.constructor as new (path: string, typeCheck: boolean) => this)(newPath, false)
|
|
248
|
+
}
|
|
249
|
+
async move(into_: Folder): Promise<void> {
|
|
250
|
+
using _ = await this.lock()
|
|
251
|
+
const newPath = into_.join(this.name)
|
|
252
|
+
await fp.rename(this.isAt, newPath)
|
|
253
|
+
this.pointsTo = newPath
|
|
254
|
+
}
|
|
255
|
+
moveSync(into_: Folder): void {
|
|
256
|
+
using _ = this.lockSync()
|
|
257
|
+
const newPath = into_.join(this.name)
|
|
258
|
+
fs.renameSync(this.isAt, newPath)
|
|
259
|
+
this.pointsTo = newPath
|
|
260
|
+
}
|
|
261
|
+
async rename(newName_: string): Promise<void> {
|
|
262
|
+
using _ = await this.lock()
|
|
263
|
+
const newPath = this.parent().join(newName_)
|
|
264
|
+
await fp.rename(this.isAt, newPath)
|
|
265
|
+
this.pointsTo = newPath
|
|
266
|
+
}
|
|
267
|
+
renameSync(newName_: string): void {
|
|
268
|
+
using _ = this.lockSync()
|
|
269
|
+
const newPath = this.parent().join(newName_)
|
|
270
|
+
fs.renameSync(this.isAt, newPath)
|
|
271
|
+
this.pointsTo = newPath
|
|
272
|
+
}
|
|
240
273
|
|
|
241
274
|
// jsdocs for the abstract methods are in the subclasses
|
|
242
275
|
abstract check(): Promise<boolean>
|
|
@@ -250,10 +283,6 @@ export abstract class Road {
|
|
|
250
283
|
isFolder(): this is Folder { return false as const }
|
|
251
284
|
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
252
285
|
isDirectory(): this is Folder { return false as const }
|
|
253
|
-
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
254
|
-
isDict(): this is Folder { return false as const }
|
|
255
|
-
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
256
|
-
isDictionary(): this is Folder { return false as const }
|
|
257
286
|
/** Type narrowing for {@link SymbolicLink} (similar to `instanceof` without unnecessary runtime checks). */
|
|
258
287
|
isSymlink(): this is SymbolicLink { return false as const }
|
|
259
288
|
/** Type narrowing for {@link SymbolicLink} (similar to `instanceof` without unnecessary runtime checks). */
|
|
@@ -269,61 +298,67 @@ export abstract class Road {
|
|
|
269
298
|
/** Type narrowing for {@link Socket} (similar to `instanceof` without unnecessary runtime checks). */
|
|
270
299
|
isSocket(): this is Socket { return false as const }
|
|
271
300
|
}
|
|
272
|
-
|
|
301
|
+
/** Constructor type for a subclass of {@link Road}. */
|
|
302
|
+
export type road_t<T extends Road> = new (...args_: ConstructorParameters<typeof Road>) => T
|
|
273
303
|
|
|
274
304
|
|
|
275
305
|
|
|
276
306
|
/** Subclass of {@link Road} that represents a file. */
|
|
277
307
|
export class File extends Road {
|
|
278
|
-
|
|
308
|
+
/**
|
|
309
|
+
* Creates a new file at the specified path if it does not already exist.
|
|
310
|
+
*
|
|
311
|
+
* @param at_ The path at which to create the file.
|
|
312
|
+
* @returns A promise that resolves to the newly created `File` instance.
|
|
313
|
+
* @throws if {@link fp.writeFile} throws.
|
|
314
|
+
*/
|
|
315
|
+
static async create(at_: string, data: string | Buffer = "") {
|
|
279
316
|
try { await fp.access(at_, fsc.W_OK) }
|
|
280
|
-
catch { await fp.writeFile(at_,
|
|
317
|
+
catch { await fp.writeFile(at_, data) }
|
|
281
318
|
return new File(at_, true)
|
|
282
319
|
}
|
|
320
|
+
/** Alias for {@link File.create}. */
|
|
283
321
|
static readonly mk: typeof File.create = File.create
|
|
284
|
-
|
|
322
|
+
/** Synchronous version of {@link File.create}. */
|
|
323
|
+
static createSync(at_: string, data: string | Buffer = "") {
|
|
285
324
|
try { fs.accessSync(at_, fs.constants.W_OK) }
|
|
286
|
-
catch { fs.writeFileSync(at_,
|
|
325
|
+
catch { fs.writeFileSync(at_, data) }
|
|
287
326
|
return new File(at_, true)
|
|
288
327
|
}
|
|
328
|
+
/** Alias for {@link File.createSync}. */
|
|
289
329
|
static readonly mkSync: typeof File.createSync = File.createSync
|
|
290
330
|
|
|
331
|
+
/** The file extension of this file, including the leading dot. */
|
|
291
332
|
get ext() { return ph.extname(this.isAt) }
|
|
333
|
+
/** The file name without its extension. */
|
|
292
334
|
get noExt() { return ph.basename(this.isAt, this.ext) }
|
|
293
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Reads the contents of the file.
|
|
338
|
+
*
|
|
339
|
+
* @returns A promise that resolves to the contents of the file as a `Buffer` or `string`, depending on the specified encoding.
|
|
340
|
+
* @throws If the file can't be read due to permission issues or other filesystem errors.
|
|
341
|
+
*/
|
|
294
342
|
async read(): Promise<Buffer>
|
|
295
|
-
async read(
|
|
296
|
-
async read(
|
|
297
|
-
|
|
298
|
-
return fp.readFile(this.isAt, { encoding: encoding_, flag: flag_ })
|
|
299
|
-
else
|
|
300
|
-
return fp.readFile(this.isAt)
|
|
343
|
+
async read(options_: Parameters<typeof fp.readFile>[1]): Promise<string>
|
|
344
|
+
async read(options_?: Parameters<typeof fp.readFile>[1]): Promise<Buffer | string> {
|
|
345
|
+
return fp.readFile(this.isAt, options_!)
|
|
301
346
|
}
|
|
302
347
|
readSync(): Buffer
|
|
303
|
-
readSync(
|
|
304
|
-
readSync(
|
|
305
|
-
|
|
306
|
-
return fs.readFileSync(this.isAt, { encoding: encoding_, flag: flag_ })
|
|
307
|
-
else
|
|
308
|
-
return fs.readFileSync(this.isAt)
|
|
348
|
+
readSync(options_: Parameters<typeof fs.readFileSync>[1]): string
|
|
349
|
+
readSync(options_?: Parameters<typeof fs.readFileSync>[1]): Buffer | string {
|
|
350
|
+
return fs.readFileSync(this.isAt, options_!)
|
|
309
351
|
}
|
|
310
352
|
|
|
311
|
-
async *itBuff(
|
|
312
|
-
const
|
|
353
|
+
async *itBuff(options_?: fs.ReadStreamOptions): AsyncGenerator<Buffer> {
|
|
354
|
+
const stream = fs.createReadStream(this.isAt, { ...options_, encoding: undefined })
|
|
313
355
|
try {
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
do {
|
|
317
|
-
const readResult = await fd.read(buffer, 0, chunkSize_, null)
|
|
318
|
-
bytesRead = readResult.bytesRead
|
|
319
|
-
if (bytesRead > 0)
|
|
320
|
-
yield buffer.subarray(0, bytesRead)
|
|
321
|
-
} while (bytesRead === chunkSize_)
|
|
322
|
-
} finally {
|
|
323
|
-
await fd.close()
|
|
356
|
+
for await (const chunk of stream)
|
|
357
|
+
yield chunk
|
|
324
358
|
}
|
|
359
|
+
finally { if (!stream.destroyed) stream.destroy() }
|
|
325
360
|
}
|
|
326
|
-
*itBuffSync(chunkSize_: number = 64 * 1024, flags_: string | number = 'r', mode_?: fs.Mode) {
|
|
361
|
+
*itBuffSync(chunkSize_: number = 64 * 1024, flags_: string | number = 'r', mode_?: fs.Mode): Generator<Buffer> {
|
|
327
362
|
const fd = fs.openSync(this.isAt, flags_, mode_)
|
|
328
363
|
try {
|
|
329
364
|
const buffer = Buffer.alloc(chunkSize_)
|
|
@@ -331,158 +366,115 @@ export class File extends Road {
|
|
|
331
366
|
do {
|
|
332
367
|
bytesRead = fs.readSync(fd, buffer, 0, chunkSize_, null)
|
|
333
368
|
if (bytesRead > 0)
|
|
334
|
-
yield buffer.subarray(0, bytesRead)
|
|
369
|
+
yield Buffer.from(buffer.subarray(0, bytesRead))
|
|
335
370
|
} while (bytesRead === chunkSize_)
|
|
336
|
-
} finally {
|
|
337
|
-
fs.closeSync(fd)
|
|
338
371
|
}
|
|
372
|
+
finally { fs.closeSync(fd) }
|
|
339
373
|
}
|
|
340
374
|
|
|
341
|
-
async
|
|
342
|
-
|
|
343
|
-
await
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
375
|
+
async sameAs(other_: File): Promise<boolean> {
|
|
376
|
+
if (this.isAt === other_.isAt) return true
|
|
377
|
+
const [s1, s2] = await Promise.all([this.size(), other_.size()])
|
|
378
|
+
if (s1 !== s2) return false
|
|
379
|
+
if (s1 === 0) return true
|
|
380
|
+
const iter1 = this.itBuff()
|
|
381
|
+
const iter2 = other_.itBuff()
|
|
382
|
+
try {
|
|
383
|
+
while (true) {
|
|
384
|
+
const [a, b] = await Promise.all([iter1.next(), iter2.next()])
|
|
385
|
+
if (a.done && b.done) return true
|
|
386
|
+
if (a.done !== b.done) return false
|
|
387
|
+
if (!a.value.equals(b.value)) return false
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
finally { await Promise.all([iter1.return?.(undefined), iter2.return?.(undefined)]) }
|
|
391
|
+
}
|
|
392
|
+
sameAsSync(other_: File): boolean {
|
|
393
|
+
if (this.isAt === other_.isAt) return true
|
|
394
|
+
const [s1, s2] = [this.sizeSync(), other_.sizeSync()]
|
|
395
|
+
if (s1 !== s2) return false
|
|
396
|
+
if (s1 === 0) return true
|
|
397
|
+
const iter1 = this.itBuffSync()
|
|
398
|
+
const iter2 = other_.itBuffSync()
|
|
399
|
+
try {
|
|
400
|
+
while (true) {
|
|
401
|
+
const a = iter1.next()
|
|
402
|
+
const b = iter2.next()
|
|
403
|
+
if (a.done && b.done) return true
|
|
404
|
+
if (a.done !== b.done) return false
|
|
405
|
+
if (!a.value!.equals(b.value!)) return false
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
finally {
|
|
409
|
+
try { iter1.return?.(undefined) } catch {}
|
|
410
|
+
try { iter2.return?.(undefined) } catch {}
|
|
411
|
+
}
|
|
348
412
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
413
|
+
|
|
414
|
+
async hash(algorithm_?: string, options_?: cr.HashOptions): Promise<Buffer>
|
|
415
|
+
async hash(algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<string>
|
|
416
|
+
async hash(algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<Buffer | string> {
|
|
417
|
+
const hash = cr.createHash(algorithm_, options_)
|
|
418
|
+
for await (const chunk of this.itBuff())
|
|
419
|
+
hash.update(chunk)
|
|
420
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
352
421
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
422
|
+
hashSync(algorithm_?: string, options_?: cr.HashOptions): Buffer
|
|
423
|
+
hashSync(algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): string
|
|
424
|
+
hashSync(algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Buffer | string {
|
|
425
|
+
const hash = cr.createHash(algorithm_, options_)
|
|
426
|
+
for (const chunk of this.itBuffSync())
|
|
427
|
+
hash.update(chunk)
|
|
428
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
356
429
|
}
|
|
357
430
|
|
|
358
|
-
async
|
|
431
|
+
async size(): Promise<number> { return (await this.lstat()).size }
|
|
432
|
+
sizeSync(): number { return this.lstatSync().size }
|
|
433
|
+
|
|
434
|
+
async writeAtomic(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
359
435
|
using _ = await this.lock()
|
|
360
|
-
await
|
|
436
|
+
await this.parent().borrow(File, async tmp => {
|
|
437
|
+
using _ = await tmp.lock()
|
|
438
|
+
await fp.writeFile(tmp.isAt, data_, options_)
|
|
439
|
+
await fp.rename(tmp.isAt, this.isAt)
|
|
440
|
+
})
|
|
361
441
|
}
|
|
362
|
-
|
|
442
|
+
writeAtomicSync(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
363
443
|
using _ = this.lockSync()
|
|
364
|
-
|
|
444
|
+
this.parent().borrowSync(File, tmp => {
|
|
445
|
+
using _ = tmp.lockSync()
|
|
446
|
+
fs.writeFileSync(tmp.isAt, data_, options_)
|
|
447
|
+
fs.renameSync(tmp.isAt, this.isAt)
|
|
448
|
+
})
|
|
365
449
|
}
|
|
366
|
-
|
|
450
|
+
|
|
451
|
+
async write(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
367
452
|
using _ = await this.lock()
|
|
368
|
-
|
|
369
|
-
await fp.rename(this.isAt, newPath)
|
|
370
|
-
this.pointsTo = newPath
|
|
453
|
+
await fp.writeFile(this.isAt, data_, options_)
|
|
371
454
|
}
|
|
372
|
-
|
|
455
|
+
writeSync(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
373
456
|
using _ = this.lockSync()
|
|
374
|
-
|
|
375
|
-
fs.renameSync(this.isAt, newPath)
|
|
376
|
-
this.pointsTo = newPath
|
|
377
|
-
}
|
|
378
|
-
async copy(into_: Folder): Promise<this> {
|
|
379
|
-
const newPath = into_.join(this.name)
|
|
380
|
-
await fp.copyFile(this.isAt, newPath)
|
|
381
|
-
return new File(newPath, false) as this
|
|
382
|
-
}
|
|
383
|
-
copySync(into_: Folder): this {
|
|
384
|
-
const newPath = into_.join(this.name)
|
|
385
|
-
fs.copyFileSync(this.isAt, newPath)
|
|
386
|
-
return new File(newPath, false) as this
|
|
457
|
+
fs.writeFileSync(this.isAt, data_, options_)
|
|
387
458
|
}
|
|
388
|
-
async
|
|
459
|
+
async append(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
389
460
|
using _ = await this.lock()
|
|
390
|
-
|
|
391
|
-
await fp.rename(this.isAt, newPath)
|
|
392
|
-
this.pointsTo = newPath
|
|
461
|
+
await fp.appendFile(this.isAt, data_, options_)
|
|
393
462
|
}
|
|
394
|
-
|
|
463
|
+
appendSync(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
395
464
|
using _ = this.lockSync()
|
|
396
|
-
|
|
397
|
-
fs.renameSync(this.isAt, newPath)
|
|
398
|
-
this.pointsTo = newPath
|
|
465
|
+
fs.appendFileSync(this.isAt, data_, options_)
|
|
399
466
|
}
|
|
400
467
|
|
|
401
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isFile() }
|
|
402
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isFile() }
|
|
468
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isFile() } catch { return false } }
|
|
469
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isFile() } catch { return false } }
|
|
403
470
|
|
|
404
471
|
override isFile(): this is File { return true as const }
|
|
405
472
|
}
|
|
406
473
|
|
|
407
474
|
|
|
408
|
-
// Bizarre functions for file I/O
|
|
409
|
-
export async function hash(f_: File, algorithm_?: string, options_?: cr.HashOptions): Promise<Buffer>
|
|
410
|
-
export async function hash(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<string>
|
|
411
|
-
export async function hash(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<Buffer | string> {
|
|
412
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
413
|
-
for await (const chunk of f_.itBuff())
|
|
414
|
-
hash.update(chunk)
|
|
415
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
416
|
-
}
|
|
417
|
-
export function hashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions): Buffer
|
|
418
|
-
export function hashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): string
|
|
419
|
-
export function hashSync(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Buffer | string {
|
|
420
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
421
|
-
for (const chunk of f_.itBuffSync())
|
|
422
|
-
hash.update(chunk)
|
|
423
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
export async function streamHash(f_: File, algorithm_?: string, options_?: cr.HashOptions): Promise<Buffer>
|
|
427
|
-
export async function streamHash(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<string>
|
|
428
|
-
export async function streamHash(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<Buffer | string> {
|
|
429
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
430
|
-
for await (const chunk of f_.itBuff())
|
|
431
|
-
hash.update(chunk)
|
|
432
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
433
|
-
}
|
|
434
|
-
export function streamHashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions): Buffer
|
|
435
|
-
export function streamHashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): string
|
|
436
|
-
export function streamHashSync(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Buffer | string {
|
|
437
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
438
|
-
for (const chunk of f_.itBuffSync())
|
|
439
|
-
hash.update(chunk)
|
|
440
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
export async function* itLines(f_: File, options_: fs.ReadStreamOptions = { encoding: 'utf-8' }) {
|
|
444
|
-
const readStream = fs.createReadStream(f_.isAt, options_)
|
|
445
|
-
const rlInterface = rl.createInterface({ input: readStream, crlfDelay: Infinity })
|
|
446
|
-
try {
|
|
447
|
-
for await (const line of rlInterface)
|
|
448
|
-
yield line
|
|
449
|
-
} finally {
|
|
450
|
-
rlInterface.close()
|
|
451
|
-
readStream.destroy()
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
export async function fileSameAs(f1_: File, f2_: File): Promise<boolean> {
|
|
456
|
-
if (f1_.isAt === f2_.isAt)
|
|
457
|
-
return true
|
|
458
|
-
else if ((await fp.lstat(f1_.isAt)).size !== (await fp.lstat(f2_.isAt)).size)
|
|
459
|
-
return false
|
|
460
|
-
const iter1 = f1_.itBuff()
|
|
461
|
-
const iter2 = f2_.itBuff()
|
|
462
|
-
while (true) {
|
|
463
|
-
const [a, b] = await Promise.all([iter1.next(), iter2.next()])
|
|
464
|
-
if (a.done && b.done) return true
|
|
465
|
-
if (a.done !== b.done) return false
|
|
466
|
-
if (!a.value!.equals(b.value!)) return false
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
export function fileSameAsSync(f1_: File, f2_: File): boolean {
|
|
470
|
-
if (f1_.isAt === f2_.isAt)
|
|
471
|
-
return true
|
|
472
|
-
else if (fs.statSync(f1_.isAt).size !== fs.statSync(f2_.isAt).size)
|
|
473
|
-
return false
|
|
474
|
-
const iter1 = f1_.itBuffSync()
|
|
475
|
-
const iter2 = f2_.itBuffSync()
|
|
476
|
-
while (true) {
|
|
477
|
-
const a = iter1.next()
|
|
478
|
-
const b = iter2.next()
|
|
479
|
-
if (a.done && b.done) return true
|
|
480
|
-
if (a.done !== b.done) return false
|
|
481
|
-
if (!a.value!.equals(b.value!)) return false
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
475
|
|
|
476
|
+
/** Helper type for filtering {@link Road} instances. */
|
|
477
|
+
export type filter_t<T extends Road> = ((road: Road) => road is T)
|
|
486
478
|
|
|
487
479
|
export class Folder extends Road {
|
|
488
480
|
static async create(at_: string) {
|
|
@@ -503,133 +495,130 @@ export class Folder extends Road {
|
|
|
503
495
|
}
|
|
504
496
|
|
|
505
497
|
it(): AsyncIterable<Road>
|
|
506
|
-
it<T extends Road>(
|
|
507
|
-
async *it<T extends Road>(
|
|
508
|
-
for (const
|
|
509
|
-
const road =
|
|
510
|
-
if (!
|
|
498
|
+
it<T extends Road>(filter_: filter_t<T>): AsyncIterable<T>
|
|
499
|
+
async *it<T extends Road>(filter_?: filter_t<T>): AsyncIterable<Road> | AsyncIterable<T> {
|
|
500
|
+
for (const entry of await fp.readdir(this.isAt, { withFileTypes: true })) {
|
|
501
|
+
const road = new (resolveDirent(entry))(this.join(entry.name), false)
|
|
502
|
+
if (!filter_ || filter_(road))
|
|
511
503
|
yield road
|
|
512
504
|
}
|
|
513
505
|
}
|
|
514
506
|
itSync(): Iterable<Road>
|
|
515
|
-
itSync<T extends Road>(
|
|
516
|
-
*itSync<T extends Road>(
|
|
517
|
-
for (const entry of fs.readdirSync(this.isAt)) {
|
|
518
|
-
const road =
|
|
519
|
-
if (!
|
|
507
|
+
itSync<T extends Road>(filter_: filter_t<T>): Iterable<T>
|
|
508
|
+
*itSync<T extends Road>(filter_?: filter_t<T>): Iterable<Road> | Iterable<T> {
|
|
509
|
+
for (const entry of fs.readdirSync(this.isAt, { withFileTypes: true })) {
|
|
510
|
+
const road = new (resolveDirent(entry))(this.join(entry.name), false)
|
|
511
|
+
if (!filter_ || filter_(road))
|
|
520
512
|
yield road
|
|
521
513
|
}
|
|
522
514
|
}
|
|
515
|
+
|
|
523
516
|
async list(): Promise<Road[]>
|
|
524
|
-
async list<T extends Road>(
|
|
525
|
-
async list<T extends Road>(
|
|
526
|
-
const entries = (await fp.readdir(this.isAt)).map(
|
|
527
|
-
|
|
528
|
-
if (!expectedType_)
|
|
529
|
-
return resolvedEntries
|
|
530
|
-
return resolvedEntries.filter(entry => entry instanceof expectedType_) as unknown as T[]
|
|
517
|
+
async list<T extends Road>(filter_: filter_t<T>): Promise<T[]>
|
|
518
|
+
async list<T extends Road>(filter_?: filter_t<T>): Promise<Road[] | T[]> {
|
|
519
|
+
const entries = (await fp.readdir(this.isAt, { withFileTypes: true })).map(e => new (resolveDirent(e))(this.join(e.name), false))
|
|
520
|
+
return filter_ ? entries.filter(entry => filter_(entry)) : entries
|
|
531
521
|
}
|
|
532
522
|
listSync(): Road[]
|
|
533
|
-
listSync<T extends Road>(
|
|
534
|
-
listSync<T extends Road>(
|
|
535
|
-
const entries = fs.readdirSync(this.isAt).map(
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
523
|
+
listSync<T extends Road>(filter_: filter_t<T>): T[]
|
|
524
|
+
listSync<T extends Road>(filter_?: filter_t<T>): Road[] | T[] {
|
|
525
|
+
const entries = fs.readdirSync(this.isAt, { withFileTypes: true }).map(e => new (resolveDirent(e))(this.join(e.name), false))
|
|
526
|
+
return filter_ ? entries.filter(entry => filter_(entry)) : entries
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
walk(): AsyncIterable<Road>
|
|
530
|
+
walk<T extends Road>(filter_: filter_t<T>): AsyncIterable<T>
|
|
531
|
+
walk(filter_: (r: Road) => boolean): AsyncIterable<Road>
|
|
532
|
+
async *walk<T extends Road>(filter_?: filter_t<T> | ((r: Road) => boolean)): AsyncIterable<T> | AsyncIterable<Road> {
|
|
533
|
+
for await (const entry of this.it()) {
|
|
534
|
+
if (!filter_ || filter_(entry))
|
|
535
|
+
yield entry
|
|
536
|
+
|
|
537
|
+
if (entry.isDir())
|
|
538
|
+
yield* entry.walk(filter_!)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
walkSync(): Iterable<Road>
|
|
542
|
+
walkSync<T extends Road>(filter_: filter_t<T>): Iterable<T>
|
|
543
|
+
walkSync(filter_: (r: Road) => boolean): Iterable<Road>
|
|
544
|
+
*walkSync<T extends Road>(filter_?: filter_t<T> | ((r: Road) => boolean)): Iterable<T> | Iterable<Road> {
|
|
545
|
+
for (const entry of this.itSync()) {
|
|
546
|
+
if (!filter_ || filter_(entry))
|
|
547
|
+
yield entry
|
|
548
|
+
|
|
549
|
+
if (entry.isDir())
|
|
550
|
+
yield* entry.walkSync(filter_!)
|
|
551
|
+
}
|
|
539
552
|
}
|
|
540
553
|
|
|
541
554
|
async find(name_: string): Promise<Road | null>
|
|
542
|
-
async find<T extends Road>(name_: string,
|
|
543
|
-
async find<T extends Road>(name_: string,
|
|
555
|
+
async find<T extends Road>(name_: string, expect_: road_t<T>): Promise<T | null>
|
|
556
|
+
async find<T extends Road>(name_: string, expect_?: road_t<T>): Promise<Road | T | null> {
|
|
544
557
|
try {
|
|
545
|
-
await fp.access(this.join(name_), fs.constants.F_OK)
|
|
546
558
|
const found = await factory(this.join(name_))
|
|
547
|
-
if (!
|
|
559
|
+
if (!expect_)
|
|
548
560
|
return found
|
|
549
|
-
if (found instanceof
|
|
561
|
+
if (found instanceof expect_)
|
|
550
562
|
return found as T
|
|
551
563
|
return null
|
|
552
|
-
} catch {
|
|
553
|
-
return null
|
|
554
564
|
}
|
|
565
|
+
catch { return null }
|
|
555
566
|
}
|
|
556
567
|
findSync(name_: string): Road | null
|
|
557
|
-
findSync<T extends Road>(name_: string,
|
|
558
|
-
findSync<T extends Road>(name_: string,
|
|
568
|
+
findSync<T extends Road>(name_: string, expect_: road_t<T>): T | null
|
|
569
|
+
findSync<T extends Road>(name_: string, expect_?: road_t<T>): Road | T | null {
|
|
559
570
|
try {
|
|
560
571
|
const found = factorySync(this.join(name_))
|
|
561
|
-
if (!
|
|
572
|
+
if (!expect_)
|
|
562
573
|
return found
|
|
563
|
-
if (found instanceof
|
|
574
|
+
if (found instanceof expect_)
|
|
564
575
|
return found as T
|
|
565
576
|
return null
|
|
566
|
-
} catch {
|
|
567
|
-
return null
|
|
568
577
|
}
|
|
578
|
+
catch(e: unknown) { return null }
|
|
569
579
|
}
|
|
570
580
|
|
|
571
|
-
async add<T extends Road>(name_: string, createable_: {
|
|
581
|
+
async add<T extends Road>(name_: string, createable_: { mk: (at: string) => Promise<T> }): Promise<T> {
|
|
572
582
|
const newPath = this.join(name_)
|
|
573
|
-
await createable_.
|
|
583
|
+
await createable_.mk(newPath)
|
|
574
584
|
return (await factory(newPath)) as unknown as T
|
|
575
585
|
}
|
|
576
|
-
addSync<T extends Road>(name_: string, createable_: {
|
|
586
|
+
addSync<T extends Road>(name_: string, createable_: { mkSync: (at: string) => T }): T {
|
|
577
587
|
const newPath = this.join(name_)
|
|
578
|
-
createable_.
|
|
588
|
+
createable_.mkSync(newPath)
|
|
579
589
|
return factorySync(newPath) as unknown as T
|
|
580
590
|
}
|
|
581
591
|
|
|
582
|
-
async
|
|
583
|
-
|
|
584
|
-
await
|
|
585
|
-
|
|
586
|
-
deleteSync(options_: fs.RmOptions = { recursive: true }) {
|
|
587
|
-
using _ = this.lockSync()
|
|
588
|
-
fs.rmSync(this.isAt, options_)
|
|
589
|
-
}
|
|
590
|
-
async move(into_: Folder) {
|
|
591
|
-
using _ = await this.lock()
|
|
592
|
-
const newPath = into_.join(this.name)
|
|
593
|
-
await fp.rename(this.isAt, newPath)
|
|
594
|
-
this.pointsTo = newPath
|
|
595
|
-
}
|
|
596
|
-
moveSync(into_: Folder) {
|
|
597
|
-
using _ = this.lockSync()
|
|
598
|
-
const newPath = into_.join(this.name)
|
|
599
|
-
fs.renameSync(this.isAt, newPath)
|
|
600
|
-
this.pointsTo = newPath
|
|
592
|
+
async borrow<T extends Road>(createable_: { mk: (at: string) => Promise<T> }, cb_: (r: T) => Promise<void> | void): Promise<void> {
|
|
593
|
+
const path = this.join(`instrumentality@${crypto.randomUUID()}`)
|
|
594
|
+
try { await cb_(await createable_.mk(path)) }
|
|
595
|
+
finally { await fp.rm(path, { recursive: true, force: true }) }
|
|
601
596
|
}
|
|
602
|
-
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
597
|
+
borrowSync<T extends Road>(createable_: { mkSync: (at: string) => T }, cb_: (r: T) => void): void {
|
|
598
|
+
const path = this.join(`instrumentality@${crypto.randomUUID()}`)
|
|
599
|
+
try { cb_(createable_.mkSync(path)) }
|
|
600
|
+
finally { fs.rmSync(path, { recursive: true, force: true }) }
|
|
606
601
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
using _ = await this.lock()
|
|
614
|
-
const newPath = this.parent().join(to_)
|
|
615
|
-
await fp.rename(this.isAt, newPath)
|
|
616
|
-
this.pointsTo = newPath
|
|
602
|
+
|
|
603
|
+
async size(): Promise<number> {
|
|
604
|
+
let size = 0
|
|
605
|
+
for await (const entry of this.it())
|
|
606
|
+
size += await entry.size()
|
|
607
|
+
return size
|
|
617
608
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
609
|
+
sizeSync(): number {
|
|
610
|
+
let size = 0
|
|
611
|
+
for (const entry of this.itSync())
|
|
612
|
+
size += entry.sizeSync()
|
|
613
|
+
return size
|
|
623
614
|
}
|
|
624
615
|
|
|
625
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isDirectory() }
|
|
626
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isDirectory() }
|
|
616
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isDirectory() } catch { return false } }
|
|
617
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isDirectory() } catch { return false } }
|
|
627
618
|
|
|
628
619
|
override isFolder(): this is Folder { return true as const }
|
|
629
620
|
override isDir(): this is Folder { return true as const }
|
|
630
621
|
override isDirectory(): this is Folder { return true as const }
|
|
631
|
-
override isDict(): this is Folder { return true as const }
|
|
632
|
-
override isDictionary(): this is Folder { return true as const }
|
|
633
622
|
}
|
|
634
623
|
|
|
635
624
|
|
|
@@ -662,61 +651,30 @@ export class SymbolicLink extends Road {
|
|
|
662
651
|
return factorySync(ph.resolve(ph.dirname(this.isAt), fs.readlinkSync(this.isAt)))
|
|
663
652
|
}
|
|
664
653
|
async retarget(to_: Road) {
|
|
665
|
-
await this.
|
|
666
|
-
|
|
654
|
+
using _ = await this.lock()
|
|
655
|
+
await fp.unlink(this.isAt)
|
|
656
|
+
await fp.symlink(to_.isAt, this.isAt)
|
|
667
657
|
}
|
|
668
658
|
retargetSync(to_: Road) {
|
|
669
|
-
this.
|
|
659
|
+
using _ = this.lockSync()
|
|
660
|
+
fs.unlinkSync(this.isAt)
|
|
670
661
|
fs.symlinkSync(to_.isAt, this.isAt)
|
|
671
662
|
}
|
|
672
663
|
|
|
673
|
-
async
|
|
664
|
+
async size(): Promise<number> { return (await this.lstat()).size }
|
|
665
|
+
sizeSync(): number { return this.lstatSync().size }
|
|
666
|
+
|
|
667
|
+
override async delete() {
|
|
674
668
|
using _ = await this.lock()
|
|
675
669
|
await fp.unlink(this.isAt)
|
|
676
670
|
}
|
|
677
|
-
deleteSync() {
|
|
671
|
+
override deleteSync() {
|
|
678
672
|
using _ = this.lockSync()
|
|
679
673
|
fs.unlinkSync(this.isAt)
|
|
680
674
|
}
|
|
681
|
-
async move(into_: Folder) {
|
|
682
|
-
using _ = await this.lock()
|
|
683
|
-
const newPath = into_.join(this.name)
|
|
684
|
-
await fp.rename(this.isAt, newPath)
|
|
685
|
-
this.pointsTo = newPath
|
|
686
|
-
}
|
|
687
|
-
moveSync(into_: Folder) {
|
|
688
|
-
using _ = this.lockSync()
|
|
689
|
-
const newPath = into_.join(this.name)
|
|
690
|
-
fs.renameSync(this.isAt, newPath)
|
|
691
|
-
this.pointsTo = newPath
|
|
692
|
-
}
|
|
693
|
-
async copy(into_: Folder): Promise<this> {
|
|
694
|
-
const newPath = into_.join(this.name)
|
|
695
|
-
const target = await this.target()
|
|
696
|
-
await fp.symlink(target.isAt, newPath)
|
|
697
|
-
return new SymbolicLink(newPath, false) as this
|
|
698
|
-
}
|
|
699
|
-
copySync(into_: Folder): this {
|
|
700
|
-
const newPath = into_.join(this.name)
|
|
701
|
-
const target = this.targetSync()
|
|
702
|
-
fs.symlinkSync(target.isAt, newPath)
|
|
703
|
-
return new SymbolicLink(newPath, false) as this
|
|
704
|
-
}
|
|
705
|
-
async rename(to_: string) {
|
|
706
|
-
using _ = await this.lock()
|
|
707
|
-
const newPath = this.parent().join(to_)
|
|
708
|
-
await fp.rename(this.isAt, newPath)
|
|
709
|
-
this.pointsTo = newPath
|
|
710
|
-
}
|
|
711
|
-
renameSync(to_: string) {
|
|
712
|
-
using _ = this.lockSync()
|
|
713
|
-
const newPath = this.parent().join(to_)
|
|
714
|
-
fs.renameSync(this.isAt, newPath)
|
|
715
|
-
this.pointsTo = newPath
|
|
716
|
-
}
|
|
717
675
|
|
|
718
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isSymbolicLink() }
|
|
719
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isSymbolicLink() }
|
|
676
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isSymbolicLink() } catch { return false } }
|
|
677
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isSymbolicLink() } catch { return false } }
|
|
720
678
|
|
|
721
679
|
override isSymlink(): this is SymbolicLink { return true as const }
|
|
722
680
|
override isSymbolicLink(): this is SymbolicLink { return true as const }
|
|
@@ -727,81 +685,49 @@ export { SymbolicLink as Symlink }
|
|
|
727
685
|
|
|
728
686
|
export abstract class UnusableRoad extends Road {
|
|
729
687
|
override readonly mutable: boolean = false // Modification will cause system issues (e.g. deleting a device file)
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
688
|
+
async size(): Promise<0> { return 0 }
|
|
689
|
+
sizeSync(): 0 { return 0 }
|
|
690
|
+
error(): never { throw new Err(`${this.constructor.name} at '${this.isAt}' is a system-level resource thus not subject to modification.`) }
|
|
691
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be locked */
|
|
692
|
+
override lock(): never { return this.error() }
|
|
693
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be locked */
|
|
736
694
|
override lockSync(): never { return this.error() }
|
|
737
|
-
|
|
695
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be deleted */
|
|
696
|
+
override delete(): never { return this.error() }
|
|
697
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be deleted */
|
|
738
698
|
override deleteSync(): never { return this.error() }
|
|
739
|
-
|
|
699
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be moved */
|
|
700
|
+
override move(): never { return this.error() }
|
|
701
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be moved */
|
|
740
702
|
override moveSync(): never { return this.error() }
|
|
741
|
-
|
|
703
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be copied */
|
|
704
|
+
override copy(): never { return this.error() }
|
|
705
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be copied */
|
|
742
706
|
override copySync(): never { return this.error() }
|
|
743
|
-
|
|
707
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be renamed */
|
|
708
|
+
override rename(): never { return this.error() }
|
|
709
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be renamed */
|
|
744
710
|
override renameSync(): never { return this.error() }
|
|
745
|
-
|
|
711
|
+
|
|
746
712
|
override isUnusable(): this is UnusableRoad { return true as const }
|
|
747
713
|
}
|
|
748
714
|
export class BlockDevice extends UnusableRoad {
|
|
749
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isBlockDevice() }
|
|
750
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isBlockDevice() }
|
|
715
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isBlockDevice() } catch { return false } }
|
|
716
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isBlockDevice() } catch { return false } }
|
|
751
717
|
override isBlockDevice(): this is BlockDevice { return true as const }
|
|
752
718
|
}
|
|
753
719
|
export class CharacterDevice extends UnusableRoad {
|
|
754
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isCharacterDevice() }
|
|
755
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isCharacterDevice() }
|
|
720
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isCharacterDevice() } catch { return false } }
|
|
721
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isCharacterDevice() } catch { return false } }
|
|
756
722
|
override isCharacterDevice(): this is CharacterDevice { return true as const }
|
|
757
723
|
}
|
|
758
724
|
export class Fifo extends UnusableRoad {
|
|
759
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isFIFO() }
|
|
760
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isFIFO() }
|
|
725
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isFIFO() } catch { return false } }
|
|
726
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isFIFO() } catch { return false } }
|
|
761
727
|
override isFifo(): this is Fifo { return true as const }
|
|
762
728
|
}
|
|
763
729
|
export class Socket extends UnusableRoad {
|
|
764
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isSocket() }
|
|
765
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isSocket() }
|
|
730
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isSocket() } catch { return false } }
|
|
731
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isSocket() } catch { return false } }
|
|
766
732
|
override isSocket(): this is Socket { return true as const }
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
let finalizer: FinalizationRegistry<string> | null = null
|
|
772
|
-
let toDelete: Set<string> | null = null
|
|
773
|
-
let exitHandlerRegistered: boolean | null = null
|
|
774
|
-
/**
|
|
775
|
-
* Forcefully cleans up all files and folders registered for cleanup on exit.
|
|
776
|
-
*/
|
|
777
|
-
function forceCleanupToDelete() {
|
|
778
|
-
for (const path of toDelete ?? [])
|
|
779
|
-
try { fs.rmSync(path, { force: true, recursive: true }) } catch {}
|
|
780
|
-
toDelete?.clear()
|
|
781
|
-
toDelete = null
|
|
782
|
-
finalizer = null
|
|
783
|
-
if (exitHandlerRegistered)
|
|
784
|
-
process.off('exit', forceCleanupToDelete)
|
|
785
|
-
exitHandlerRegistered = false
|
|
786
|
-
}
|
|
787
|
-
export function registerToCleanup(self_: Road) {
|
|
788
|
-
finalizer ??= new FinalizationRegistry<string>(p => { try { fs.rmSync(p, { force: true, recursive: true }) } catch {}; toDelete?.delete(p) })
|
|
789
|
-
toDelete ??= new Set()
|
|
790
|
-
if (!exitHandlerRegistered) {
|
|
791
|
-
process.once('exit', forceCleanupToDelete)
|
|
792
|
-
exitHandlerRegistered = true
|
|
793
|
-
}
|
|
794
|
-
toDelete.add(self_.isAt)
|
|
795
|
-
finalizer.register(self_, self_.isAt, self_)
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
export function Temp<T extends Road>(createable_: { createSync: (at: string) => T }, autoCleanup_: boolean): T & Disposable & AsyncDisposable {
|
|
800
|
-
let t = createable_.createSync(tmp().join(`instrumentality@${cr.randomUUID()}`))
|
|
801
|
-
if (autoCleanup_)
|
|
802
|
-
registerToCleanup(t)
|
|
803
|
-
return Object.freeze(Object.assign(t, {
|
|
804
|
-
[Symbol.dispose]() { try { t.deleteSync() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) },
|
|
805
|
-
async [Symbol.asyncDispose]() { try { await t.delete() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) }
|
|
806
|
-
}))
|
|
807
|
-
}11
|
|
733
|
+
}
|