instrumentality 0.0.2 → 0.0.4
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 -3
- package/dist/base.d.ts +30 -47
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +29 -44
- package/dist/dom.d.ts +19 -28
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +26 -41
- package/dist/road.d.ts +219 -179
- package/dist/road.d.ts.map +1 -1
- package/dist/road.js +450 -476
- package/package.json +1 -1
- package/src/base.ts +33 -51
- package/src/dom.ts +29 -44
- package/src/road.ts +502 -529
package/src/road.ts
CHANGED
|
@@ -4,28 +4,26 @@ import * as fp from "node:fs/promises"
|
|
|
4
4
|
import * as ph from "node:path"
|
|
5
5
|
import * as os from "node:os"
|
|
6
6
|
import * as cr from "node:crypto"
|
|
7
|
-
import * as sp from "node:stream/promises"
|
|
8
7
|
import { on } from "node:events"
|
|
9
8
|
import * as bs from "./base.ts"
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
/**
|
|
14
|
-
* Subclass of {@link bs.InsErr} that represents an error thrown from this specific module of the library
|
|
15
|
-
*/
|
|
12
|
+
/** Subclass of {@link bs.InsErr} that represents an error thrown from this specific module of the library */
|
|
16
13
|
export class RdErr extends bs.InsErr { override name = "Instrumentality-Road-Error" }
|
|
14
|
+
export { RdErr as RoadError }
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
|
-
* Returns the constructor function corresponding to the file mode.
|
|
19
|
+
* Returns the constructor function corresponding to the file mode (statmode).
|
|
22
20
|
*
|
|
23
|
-
* @param
|
|
24
|
-
* @returns The constructor function corresponding to the
|
|
21
|
+
* @param statmode_ The file mode to check.
|
|
22
|
+
* @returns The constructor function corresponding to the road type (e.g., {@link File}, {@link Folder}, etc.).
|
|
25
23
|
* @throws If the file mode is unknown, throws a {@link RdErr}.
|
|
26
24
|
*/
|
|
27
|
-
export function
|
|
28
|
-
switch (
|
|
25
|
+
export function resolveMode(statmode_: number): typeof File | typeof Folder | typeof BlockDevice | typeof CharacterDevice | typeof SymbolicLink | typeof Fifo | typeof Socket {
|
|
26
|
+
switch (statmode_ & fsc.S_IFMT) {
|
|
29
27
|
case fsc.S_IFREG: return File
|
|
30
28
|
case fsc.S_IFDIR: return Folder
|
|
31
29
|
case fsc.S_IFBLK: return BlockDevice
|
|
@@ -33,196 +31,140 @@ export function modeCtor(statmode: number) {
|
|
|
33
31
|
case fsc.S_IFLNK: return SymbolicLink
|
|
34
32
|
case fsc.S_IFIFO: return Fifo
|
|
35
33
|
case fsc.S_IFSOCK: return Socket
|
|
36
|
-
default: throw new RdErr(`Unknown mode type ${
|
|
34
|
+
default: throw new RdErr(`Unknown mode type ${statmode_} (statmode is most likely corrupted)`)
|
|
37
35
|
}
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
|
|
41
39
|
|
|
42
40
|
/**
|
|
43
|
-
* Creates
|
|
41
|
+
* Creates the appropriate subclass of {@link Road} based on the file mode of the specified path.
|
|
44
42
|
*
|
|
45
|
-
* @param
|
|
46
|
-
* @returns A new instance of
|
|
47
|
-
* @throws If
|
|
43
|
+
* @param path_ The path to follow.
|
|
44
|
+
* @returns A new instance of {@link Road}.
|
|
45
|
+
* @throws If {@link fp.lstat}/{@link fs.lstatSync} fails to retrieved the status of {@link path_}.
|
|
48
46
|
*/
|
|
49
|
-
export function
|
|
50
|
-
|
|
51
|
-
return new (modeCtor(fs.lstatSync(lookFor).mode))(lookFor, false)
|
|
47
|
+
export async function factory(path_: string) {
|
|
48
|
+
return new (resolveMode((await fp.lstat(path_)).mode))(path_, false)
|
|
52
49
|
}
|
|
53
|
-
/**
|
|
54
|
-
export
|
|
55
|
-
|
|
56
|
-
return new (modeCtor((await fp.lstat(lookFor)).mode))(lookFor, false)
|
|
50
|
+
/** Sync version of {@link factory}. */
|
|
51
|
+
export function factorySync(path_: string) {
|
|
52
|
+
return new (resolveMode(fs.lstatSync(path_).mode))(path_, false)
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
|
|
60
56
|
|
|
61
57
|
/**
|
|
62
|
-
* A map that keeps track of locked roads to prevent concurrent modifications.
|
|
58
|
+
* A map that keeps track of locked roads to prevent concurrent modifications.
|
|
63
59
|
*
|
|
64
|
-
* @
|
|
65
|
-
*
|
|
66
|
-
*/
|
|
67
|
-
let lockedRoads: Map<string, Promise<void>> | null = null
|
|
68
|
-
/**
|
|
69
|
-
* Getter for the locked roads map.
|
|
60
|
+
* @key The absolute path of the road that is currently locked.
|
|
61
|
+
* @value A promise that resolves when the lock on the road is released.
|
|
70
62
|
*
|
|
71
|
-
* @
|
|
72
|
-
*
|
|
63
|
+
* @remarks The map is initialized lazily when the first lock is created to minimize import-time side effects.
|
|
64
|
+
* It's generally meant for read-only purposes. It's not advised to modify this map directly if there are built-in mechanisms that do the job for you as well.
|
|
73
65
|
*/
|
|
74
|
-
export
|
|
75
|
-
return lockedRoads?.get(roadOrPath.toString())
|
|
76
|
-
}
|
|
66
|
+
export let lockedRoads: Map<string, Promise<void>> | null = null
|
|
77
67
|
|
|
78
68
|
|
|
79
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Road is an OOP, pointer-like representation of an entry in the local file system. It wraps around the Node.js fs module and provides a more convenient access to it.
|
|
72
|
+
* It's meant to help the user mentally model an entry and help them reason about it, as well as provide a more convenient API and guardrails for common operations.
|
|
73
|
+
*
|
|
74
|
+
* @remarks This is by no means a one-to-one mapping of the underlying file system (after initialization).
|
|
75
|
+
* It is more like a memory representation, similar to a pointer in low-level programming languages; other processes might mess with the underlying entry. There are methods to check for consistency, but they are not guaranteed to be foolproof.
|
|
76
|
+
*/
|
|
80
77
|
export abstract class Road {
|
|
81
78
|
/** The absolute path to the file or directory that this Road instance represents.
|
|
82
|
-
* Intentionally made protected to prevent external modification, as changing this value could lead to inconsistencies and unexpected behavior. */
|
|
79
|
+
* @remarks Intentionally made protected to prevent external modification, as changing this value could lead to inconsistencies and unexpected behavior. */
|
|
83
80
|
protected pointsTo: string
|
|
84
81
|
/** Indicates whether the file or directory represented by this Road instance can be modified.
|
|
85
82
|
* Changing this value does not affect the actual file system permissions, but rather serves as a safeguard within the application to prevent accidental modifications. */
|
|
86
83
|
mutable: boolean = true
|
|
87
84
|
|
|
88
85
|
// Quick accessors
|
|
89
|
-
/**
|
|
86
|
+
/** Copy of the absolute path. */
|
|
90
87
|
get isAt() { return this.pointsTo }
|
|
91
|
-
/**
|
|
92
|
-
get name() { return
|
|
93
|
-
/**
|
|
88
|
+
/** Name of the road without the path (including extensions). */
|
|
89
|
+
get name() { return this.isAt.slice(this.isAt.lastIndexOf(ph.sep) + 1) }
|
|
90
|
+
/** 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
|
+
get depth() { return this.isAt.split(ph.sep).length - 1 }
|
|
92
|
+
/** Same as {@link isAt} but for compatibility with external APIs. */
|
|
94
93
|
toString() { return this.isAt }
|
|
95
|
-
/** Returns the OS file type of the file or directory.
|
|
96
|
-
* Return value (OS type) and the type of this instance are not guaranteed to be the same, as the file system may have changed since this instance was created. */
|
|
97
|
-
typeSync() { return (modeCtor(fs.lstatSync(this.isAt).mode)) }
|
|
98
|
-
/** Async version of {@link typeSync}. */
|
|
99
|
-
async type() { return (modeCtor((await fp.lstat(this.isAt)).mode)) }
|
|
100
94
|
|
|
101
95
|
/**
|
|
102
|
-
*
|
|
96
|
+
* Creates a new instance of the Road class.
|
|
103
97
|
*
|
|
104
|
-
* @param
|
|
105
|
-
* @param
|
|
106
|
-
* This can be skipped for performance reasons if the type is known to be correct, but it is recommended to keep it enabled for safety.
|
|
107
|
-
* @throws If the specified path does not exist, throws a fs.{@link Error}.
|
|
108
|
-
* @throws If the type of the file or directory at the specified path does not match the type of this instance, throws a {@link RdErr}. Useful for subclasses.
|
|
109
|
-
*/
|
|
110
|
-
constructor(lookFor: string, typeCheck: boolean) {
|
|
111
|
-
this.pointsTo = ph.resolve(lookFor)
|
|
112
|
-
if (typeCheck && !(this instanceof this.typeSync())) // `this` directly refers to the subclass
|
|
113
|
-
throw new RdErr(`Type missmatch: Path '${this.isAt}' is not of constructed type ${this.constructor.name}.`)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Verifies that the file or directory represented by this Road instance exists, is of the same type as this instance, and (optionally) is writable.
|
|
98
|
+
* @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
|
+
* @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.
|
|
118
100
|
*
|
|
119
|
-
* @
|
|
120
|
-
* @returns Result of the verification.
|
|
101
|
+
* @throws If {@link typeCheck_} is true and the path does not correspond to the expected type of road, a {@link RdErr} will be thrown.
|
|
121
102
|
*/
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
} catch {
|
|
127
|
-
return false
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/** Sync version of {@link verify}. */
|
|
131
|
-
verifySync(expectMode: number, typeCheck: boolean): boolean {
|
|
132
|
-
try {
|
|
133
|
-
fs.accessSync(this.isAt, fsc.F_OK | expectMode)
|
|
134
|
-
return typeCheck || this instanceof this.typeSync()
|
|
135
|
-
} catch {
|
|
136
|
-
return false
|
|
137
|
-
}
|
|
103
|
+
constructor(path_: string, typeCheck_: boolean | 1 | 0) {
|
|
104
|
+
this.pointsTo = ph.resolve(path_)
|
|
105
|
+
if (typeCheck_ && !this.checkSync())
|
|
106
|
+
throw new RdErr(`Type mismatch: '${this.isAt}'`)
|
|
138
107
|
}
|
|
139
108
|
|
|
140
109
|
/**
|
|
141
|
-
*
|
|
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.
|
|
142
115
|
*
|
|
143
|
-
* @
|
|
144
|
-
*
|
|
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).
|
|
145
118
|
*/
|
|
146
|
-
protected async
|
|
147
|
-
if (!await this.verify(fsc.R_OK | fsc.W_OK, true))
|
|
148
|
-
throw new RdErr(`Road to '${this.isAt}' (${this.constructor.name}) isn't the same as during construction, can't modify (OS type: ${fs.existsSync(this.isAt) ? this.typeSync().name : 'nonexistent'})`)
|
|
119
|
+
protected async lock(cb_ = () => {}): Promise<AsyncDisposable & Disposable> {
|
|
149
120
|
if (!this.mutable)
|
|
150
|
-
throw new RdErr(`
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
await lockFor(lockedPath)
|
|
156
|
-
lockedRoads.set(lockedPath, new Promise<void>(res => releaseLock = res))
|
|
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))
|
|
157
126
|
return {
|
|
158
127
|
[Symbol.dispose]() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
lockedRoads!.delete(lockedPath)
|
|
128
|
+
cb_()
|
|
129
|
+
lockedRoads!.delete(isAt)
|
|
162
130
|
},
|
|
163
131
|
async [Symbol.asyncDispose]() {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
lockedRoads!.delete(lockedPath)
|
|
132
|
+
cb_()
|
|
133
|
+
lockedRoads!.delete(isAt)
|
|
167
134
|
}
|
|
168
135
|
}
|
|
169
136
|
}
|
|
170
137
|
/**
|
|
171
|
-
* Sync version of {@link
|
|
172
|
-
*
|
|
173
|
-
* @remarks This sync version will throw if the road is already locked by another operation.
|
|
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.
|
|
174
140
|
*/
|
|
175
|
-
protected
|
|
176
|
-
if (!this.verifySync(fsc.R_OK | fsc.W_OK, true))
|
|
177
|
-
throw new RdErr(`Road to '${this.isAt}' (${this.constructor.name}) isn't the same as during construction, can't modify (OS type: ${fs.existsSync(this.isAt) ? this.typeSync().name : 'nonexistent'})`)
|
|
141
|
+
protected lockSync(cb_ = () => {}): Disposable & AsyncDisposable {
|
|
178
142
|
if (!this.mutable)
|
|
179
|
-
throw new RdErr(`
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
let releaseLock = () => {}
|
|
186
|
-
lockedRoads.set(lockedPath, new Promise<void>(res => releaseLock = res))
|
|
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))
|
|
187
149
|
return {
|
|
188
150
|
[Symbol.dispose]() {
|
|
189
|
-
lockedRoads!.delete(
|
|
190
|
-
|
|
191
|
-
releaseLock = () => { throw new RdErr("Lock already released, can't dispose") }
|
|
151
|
+
lockedRoads!.delete(isAt)
|
|
152
|
+
cb_()
|
|
192
153
|
},
|
|
193
154
|
async [Symbol.asyncDispose]() {
|
|
194
|
-
lockedRoads!.delete(
|
|
195
|
-
|
|
196
|
-
releaseLock = () => { throw new RdErr("Lock already released, can't dispose") }
|
|
155
|
+
lockedRoads!.delete(isAt)
|
|
156
|
+
cb_()
|
|
197
157
|
}
|
|
198
158
|
}
|
|
199
159
|
}
|
|
200
160
|
|
|
161
|
+
/** @returns An instance of {@link Folder} representing the parent directory of the current road. */
|
|
162
|
+
parent(): Folder { return new Folder(ph.dirname(this.isAt), false) }
|
|
201
163
|
/**
|
|
202
|
-
*
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
async exists() { return this.verify(fsc.F_OK, true) }
|
|
206
|
-
/**
|
|
207
|
-
* @returns The file system stats for the file or directory.
|
|
208
|
-
* @see {@link fs.lstatSync}
|
|
209
|
-
*/
|
|
210
|
-
statsSync() { return fs.lstatSync(this.isAt) }
|
|
211
|
-
/**
|
|
212
|
-
* @returns The file system stats for the file or directory.
|
|
213
|
-
* @see {@link fs.promises.lstat}
|
|
214
|
-
*/
|
|
215
|
-
async stats() {
|
|
216
|
-
return fp.lstat(this.isAt)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* @returns 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 file or directory in the file system hierarchy).
|
|
221
|
-
* @remarks As subclasses of {@link Road} require all paths to be absolute/normalized and valid, this method is guaranteed to return a non-negative integer.
|
|
164
|
+
* 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.
|
|
165
|
+
*
|
|
166
|
+
* @yields Each ancestor folder as a {@link Folder} instance.
|
|
222
167
|
*/
|
|
223
|
-
depth() { return this.isAt.split(ph.sep).length - 1 }
|
|
224
|
-
/** @returns The parent folder of the file or directory represented by this Road instance. */
|
|
225
|
-
parent() { return new Folder(ph.dirname(this.isAt), false) }
|
|
226
168
|
*ancestorsIt() {
|
|
227
169
|
let current: Folder = this.parent()
|
|
228
170
|
let parent = current.parent()
|
|
@@ -232,361 +174,388 @@ export abstract class Road {
|
|
|
232
174
|
parent = current.parent()
|
|
233
175
|
}
|
|
234
176
|
}
|
|
235
|
-
|
|
177
|
+
/** @returns An array of {@link Folder} instances representing the ancestors of the current road. */
|
|
178
|
+
ancestors(): Folder[] { return [...this.ancestorsIt()] }
|
|
236
179
|
|
|
237
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Watches the current entry for changes and resolves when the entry becomes accessible (i.e., exists and can be accessed).
|
|
182
|
+
*
|
|
183
|
+
* @param abs An {@link AbortSignal} that stops the watching process when aborted.
|
|
184
|
+
* @param expectMode The expected access mode for the entry, defaults to {@link fsc.F_OK} (existence check).
|
|
185
|
+
* @param cb_ An optional callback function that will be called with any errors encountered while checking for accessibility.
|
|
186
|
+
* @see {@link fp.access} on how the check for accessibility is performed.
|
|
187
|
+
* @see {@link fs.watch} for more information on how the watching process works.
|
|
188
|
+
* @see {@link on} for more information on how the event listener is set up.
|
|
189
|
+
*/
|
|
190
|
+
async untilAccessible(abs: AbortSignal, expectMode = fsc.F_OK, cb_?: (err: unknown) => unknown): Promise<void> {
|
|
238
191
|
const watcher = fs.watch(this.isAt)
|
|
239
192
|
try {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
for await (let _ of on(watcher, 'change', { signal: abs }))
|
|
244
|
-
if (await this.verify(mode, true))
|
|
193
|
+
for await (let _ of on(watcher, 'change', { signal: abs })) {
|
|
194
|
+
try {
|
|
195
|
+
await fp.access(this.isAt, expectMode)
|
|
245
196
|
return
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
watcher.close()
|
|
197
|
+
}
|
|
198
|
+
catch(err: unknown) { await cb_?.(err) }
|
|
199
|
+
}
|
|
250
200
|
}
|
|
201
|
+
catch(e) { throw e }
|
|
202
|
+
finally { watcher.close() }
|
|
251
203
|
}
|
|
252
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Watches the current entry for changes and resolves when the entry is changed.
|
|
206
|
+
*
|
|
207
|
+
* @param abs_ An {@link AbortSignal} that stops the watching process when aborted.
|
|
208
|
+
* @param cb_ An optional callback function that will be called when the entry is changed.
|
|
209
|
+
* @returns The return value of the callback function, or null if no callback is provided.
|
|
210
|
+
*/
|
|
211
|
+
async onChange<T>(abs_: AbortSignal, cb_?: () => T) {
|
|
253
212
|
const watcher = fs.watch(this.isAt)
|
|
254
213
|
try {
|
|
255
|
-
for await (let _ of on(watcher, 'change', { signal:
|
|
256
|
-
return await
|
|
214
|
+
for await (let _ of on(watcher, 'change', { signal: abs_ }))
|
|
215
|
+
return await cb_?.() ?? null
|
|
257
216
|
return null
|
|
258
217
|
}
|
|
259
218
|
catch(e) { throw e }
|
|
260
219
|
finally { watcher.close() }
|
|
261
220
|
}
|
|
262
221
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return JSON.parse(await fp.readFile(`${this.isAt}:${suffixID}`, 'utf-8'))
|
|
272
|
-
else
|
|
273
|
-
throw new RdErr("Extended attributes are not supported on this platform")
|
|
274
|
-
}
|
|
275
|
-
setMetaSync(meta: Record<string, unknown>, suffixID = "tsInstrumentalityMeta") {
|
|
276
|
-
using _ = this.initChangeSync()
|
|
277
|
-
if (os.platform() === "win32")
|
|
278
|
-
fs.writeFileSync(`${this.isAt}:${suffixID}`, JSON.stringify(meta), 'utf-8')
|
|
279
|
-
else
|
|
280
|
-
throw new RdErr("Extended attributes are not supported on this platform")
|
|
281
|
-
}
|
|
282
|
-
async setMeta(meta: Record<string, unknown>, suffixID = "tsInstrumentalityMeta") {
|
|
283
|
-
using _ = await this.initChange()
|
|
284
|
-
if (os.platform() === "win32")
|
|
285
|
-
await fp.writeFile(`${this.isAt}:${suffixID}`, JSON.stringify(meta), 'utf-8')
|
|
286
|
-
else
|
|
287
|
-
throw new RdErr("Extended attributes are not supported on this platform")
|
|
288
|
-
}
|
|
222
|
+
/** @returns The result of {@link fp.lstat} for the current entry. */
|
|
223
|
+
lstat() { return fp.lstat(this.isAt) }
|
|
224
|
+
/** @returns The result of {@link fs.lstatSync} for the current entry. */
|
|
225
|
+
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) }
|
|
289
230
|
|
|
290
|
-
abstract
|
|
231
|
+
// jsdocs for the abstract methods are in the subclasses
|
|
291
232
|
abstract delete(): Promise<void>
|
|
292
|
-
abstract
|
|
293
|
-
abstract
|
|
294
|
-
abstract
|
|
295
|
-
abstract
|
|
296
|
-
abstract
|
|
297
|
-
abstract
|
|
298
|
-
abstract
|
|
299
|
-
|
|
233
|
+
abstract deleteSync(): void
|
|
234
|
+
abstract moveSync(into_: Folder): void
|
|
235
|
+
abstract move(into_: Folder): Promise<void>
|
|
236
|
+
abstract copySync(into_: Folder): this
|
|
237
|
+
abstract copy(into_: Folder): Promise<this>
|
|
238
|
+
abstract renameSync(to_: string): void
|
|
239
|
+
abstract rename(to_: string): Promise<void>
|
|
240
|
+
|
|
241
|
+
// jsdocs for the abstract methods are in the subclasses
|
|
242
|
+
abstract check(): Promise<boolean>
|
|
243
|
+
abstract checkSync(): boolean
|
|
244
|
+
|
|
245
|
+
/** Type narrowing for {@link File} (similar to `instanceof` without unnecessary runtime checks). */
|
|
246
|
+
isFile(): this is File { return false as const }
|
|
247
|
+
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
248
|
+
isDir(): this is Folder { return false as const }
|
|
249
|
+
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
250
|
+
isFolder(): this is Folder { return false as const }
|
|
251
|
+
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
252
|
+
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
|
+
/** Type narrowing for {@link SymbolicLink} (similar to `instanceof` without unnecessary runtime checks). */
|
|
258
|
+
isSymlink(): this is SymbolicLink { return false as const }
|
|
259
|
+
/** Type narrowing for {@link SymbolicLink} (similar to `instanceof` without unnecessary runtime checks). */
|
|
260
|
+
isSymbolicLink(): this is SymbolicLink { return false as const }
|
|
261
|
+
/** Type narrowing for {@link UnusableRoad} (similar to `instanceof` without unnecessary runtime checks). */
|
|
262
|
+
isUnusable(): this is UnusableRoad { return false as const }
|
|
263
|
+
/** Type narrowing for {@link BlockDevice} (similar to `instanceof` without unnecessary runtime checks). */
|
|
264
|
+
isBlockDevice(): this is BlockDevice { return false as const }
|
|
265
|
+
/** Type narrowing for {@link CharacterDevice} (similar to `instanceof` without unnecessary runtime checks). */
|
|
266
|
+
isCharacterDevice(): this is CharacterDevice { return false as const }
|
|
267
|
+
/** Type narrowing for {@link Fifo} (similar to `instanceof` without unnecessary runtime checks). */
|
|
268
|
+
isFifo(): this is Fifo { return false as const }
|
|
269
|
+
/** Type narrowing for {@link Socket} (similar to `instanceof` without unnecessary runtime checks). */
|
|
270
|
+
isSocket(): this is Socket { return false as const }
|
|
300
271
|
}
|
|
301
272
|
|
|
302
273
|
|
|
303
274
|
|
|
275
|
+
/** Subclass of {@link Road} that represents a file. */
|
|
304
276
|
export class File extends Road {
|
|
277
|
+
static async create(at_: string) {
|
|
278
|
+
try { await fp.access(at_, fsc.W_OK) }
|
|
279
|
+
catch { await fp.writeFile(at_, "") }
|
|
280
|
+
return new File(at_, true)
|
|
281
|
+
}
|
|
282
|
+
static createSync(at_: string) {
|
|
283
|
+
try { fs.accessSync(at_, fs.constants.W_OK) }
|
|
284
|
+
catch { fs.writeFileSync(at_, "") }
|
|
285
|
+
return new File(at_, true)
|
|
286
|
+
}
|
|
287
|
+
|
|
305
288
|
get ext() { return ph.extname(this.isAt) }
|
|
306
289
|
get noExt() { return ph.basename(this.isAt, this.ext) }
|
|
307
290
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
static async create(at: string) {
|
|
317
|
-
try {
|
|
318
|
-
await fp.access(at, fsc.W_OK)
|
|
319
|
-
} catch {
|
|
320
|
-
await fp.writeFile(at, "")
|
|
321
|
-
}
|
|
322
|
-
return new File(at, false)
|
|
291
|
+
async read(): Promise<Buffer>
|
|
292
|
+
async read(encoding_: BufferEncoding, flag_?: string): Promise<string>
|
|
293
|
+
async read(encoding_?: BufferEncoding, flag_?: string): Promise<Buffer | string> {
|
|
294
|
+
if (encoding_)
|
|
295
|
+
return fp.readFile(this.isAt, { encoding: encoding_, flag: flag_ })
|
|
296
|
+
else
|
|
297
|
+
return fp.readFile(this.isAt)
|
|
323
298
|
}
|
|
324
|
-
|
|
325
299
|
readSync(): Buffer
|
|
326
|
-
readSync(
|
|
327
|
-
readSync(
|
|
328
|
-
if (
|
|
329
|
-
return fs.readFileSync(this.isAt, { encoding:
|
|
300
|
+
readSync(encoding_: BufferEncoding, flag_?: string): string
|
|
301
|
+
readSync(encoding_?: BufferEncoding, flag_?: string): Buffer | string {
|
|
302
|
+
if (encoding_)
|
|
303
|
+
return fs.readFileSync(this.isAt, { encoding: encoding_, flag: flag_ })
|
|
330
304
|
else
|
|
331
305
|
return fs.readFileSync(this.isAt)
|
|
332
306
|
}
|
|
333
|
-
async read(): Promise<Buffer>
|
|
334
|
-
async read(encoding: BufferEncoding, flag?: string): Promise<string>
|
|
335
|
-
async read(encoding?: BufferEncoding, flag?: string): Promise<Buffer | string> {
|
|
336
|
-
if (encoding)
|
|
337
|
-
return fp.readFile(this.isAt, { encoding: encoding, flag: flag })
|
|
338
|
-
else
|
|
339
|
-
return fp.readFile(this.isAt)
|
|
340
|
-
}
|
|
341
307
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const fd = fs.openSync(this.isAt, flags, mode)
|
|
308
|
+
async *itBuff(chunkSize_: number = 64 * 1024, flags_: string | number = 'r', mode_?: fs.Mode) {
|
|
309
|
+
const fd = await fp.open(this.isAt, flags_, mode_)
|
|
345
310
|
try {
|
|
346
|
-
const buffer = Buffer.alloc(
|
|
311
|
+
const buffer = Buffer.alloc(chunkSize_)
|
|
347
312
|
let bytesRead: number
|
|
348
313
|
do {
|
|
349
|
-
|
|
314
|
+
const readResult = await fd.read(buffer, 0, chunkSize_, null)
|
|
315
|
+
bytesRead = readResult.bytesRead
|
|
350
316
|
if (bytesRead > 0)
|
|
351
317
|
yield buffer.subarray(0, bytesRead)
|
|
352
|
-
} while (bytesRead ===
|
|
318
|
+
} while (bytesRead === chunkSize_)
|
|
353
319
|
} finally {
|
|
354
|
-
|
|
320
|
+
await fd.close()
|
|
355
321
|
}
|
|
356
322
|
}
|
|
357
|
-
|
|
358
|
-
const fd =
|
|
323
|
+
*itBuffSync(chunkSize_: number = 64 * 1024, flags_: string | number = 'r', mode_?: fs.Mode) {
|
|
324
|
+
const fd = fs.openSync(this.isAt, flags_, mode_)
|
|
359
325
|
try {
|
|
360
|
-
const buffer = Buffer.alloc(
|
|
326
|
+
const buffer = Buffer.alloc(chunkSize_)
|
|
361
327
|
let bytesRead: number
|
|
362
328
|
do {
|
|
363
|
-
|
|
364
|
-
bytesRead = readResult.bytesRead
|
|
329
|
+
bytesRead = fs.readSync(fd, buffer, 0, chunkSize_, null)
|
|
365
330
|
if (bytesRead > 0)
|
|
366
331
|
yield buffer.subarray(0, bytesRead)
|
|
367
|
-
} while (bytesRead ===
|
|
332
|
+
} while (bytesRead === chunkSize_)
|
|
368
333
|
} finally {
|
|
369
|
-
|
|
334
|
+
fs.closeSync(fd)
|
|
370
335
|
}
|
|
371
336
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
for await (const line of rlInterface)
|
|
377
|
-
yield line
|
|
378
|
-
} finally {
|
|
379
|
-
rlInterface.close()
|
|
380
|
-
readStream.destroy()
|
|
381
|
-
}
|
|
337
|
+
|
|
338
|
+
async write(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
339
|
+
using _ = await this.lock()
|
|
340
|
+
await fp.writeFile(this.isAt, data_, options_)
|
|
382
341
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const hash = cr.createHash(algorithm, options)
|
|
387
|
-
for (const chunk of this.itBuffSync())
|
|
388
|
-
hash.update(chunk)
|
|
389
|
-
return encoding ? hash.digest(encoding) : hash.digest()
|
|
390
|
-
}
|
|
391
|
-
async computeHash(algorithm?: string, options?: cr.HashOptions): Promise<Buffer>
|
|
392
|
-
async computeHash(algorithm?: string, options?: cr.HashOptions, encoding?: BufferEncoding): Promise<string>
|
|
393
|
-
async computeHash(algorithm = "sha256", options?: cr.HashOptions, encoding?: BufferEncoding): Promise<Buffer | string> {
|
|
394
|
-
const hash = cr.createHash(algorithm, options)
|
|
395
|
-
for await (const chunk of this.itBuff())
|
|
396
|
-
hash.update(chunk)
|
|
397
|
-
return encoding ? hash.digest(encoding) : hash.digest()
|
|
398
|
-
}
|
|
399
|
-
async streamHash(algorithm = "sha256", options?: cr.HashOptions, encoding?: BufferEncoding): Promise<Buffer | string> {
|
|
400
|
-
const hash = cr.createHash(algorithm, options)
|
|
401
|
-
await sp.pipeline(fs.createReadStream(this.isAt), hash)
|
|
402
|
-
return encoding ? hash.digest(encoding) : hash.digest()
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
writeSync(data: Buffer | string, options?: fs.WriteFileOptions) {
|
|
406
|
-
using _ = this.initChangeSync()
|
|
407
|
-
fs.writeFileSync(this.isAt, data, options)
|
|
408
|
-
}
|
|
409
|
-
async write(data: Buffer | string, options?: fs.WriteFileOptions) {
|
|
410
|
-
using _ = await this.initChange()
|
|
411
|
-
await fp.writeFile(this.isAt, data, options)
|
|
412
|
-
}
|
|
413
|
-
appendSync(data: Buffer | string, options?: fs.WriteFileOptions) {
|
|
414
|
-
using _ = this.initChangeSync()
|
|
415
|
-
fs.appendFileSync(this.isAt, data, options)
|
|
416
|
-
}
|
|
417
|
-
async append(data: Buffer | string, options?: fs.WriteFileOptions) {
|
|
418
|
-
using _ = await this.initChange()
|
|
419
|
-
await fp.appendFile(this.isAt, data, options)
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
async sameAs(other: File) {
|
|
423
|
-
if (this.isAt === other.isAt)
|
|
424
|
-
return true
|
|
425
|
-
else if ((await fp.lstat(this.isAt)).size !== (await fp.lstat(other.isAt)).size)
|
|
426
|
-
return false
|
|
427
|
-
const thisIter = this.itBuff()
|
|
428
|
-
const otherIter = other.itBuff()
|
|
429
|
-
while (true) {
|
|
430
|
-
const [a, b] = await Promise.all([thisIter.next(), otherIter.next()])
|
|
431
|
-
if (a.done && b.done) return true
|
|
432
|
-
if (a.done !== b.done) return false
|
|
433
|
-
if (!a.value!.equals(b.value!)) return false
|
|
434
|
-
}
|
|
342
|
+
writeSync(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
343
|
+
using _ = this.lockSync()
|
|
344
|
+
fs.writeFileSync(this.isAt, data_, options_)
|
|
435
345
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
else if (fs.statSync(this.isAt).size !== fs.statSync(other.isAt).size)
|
|
440
|
-
return false
|
|
441
|
-
const thisIter = this.itBuffSync()
|
|
442
|
-
const otherIter = other.itBuffSync()
|
|
443
|
-
while (true) {
|
|
444
|
-
const a = thisIter.next()
|
|
445
|
-
const b = otherIter.next()
|
|
446
|
-
if (a.done && b.done) return true
|
|
447
|
-
if (a.done !== b.done) return false
|
|
448
|
-
if (!a.value!.equals(b.value!)) return false
|
|
449
|
-
}
|
|
346
|
+
async append(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
347
|
+
using _ = await this.lock()
|
|
348
|
+
await fp.appendFile(this.isAt, data_, options_)
|
|
450
349
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
fs.rmSync(this.isAt, { force: true })
|
|
350
|
+
appendSync(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
351
|
+
using _ = this.lockSync()
|
|
352
|
+
fs.appendFileSync(this.isAt, data_, options_)
|
|
455
353
|
}
|
|
354
|
+
|
|
456
355
|
async delete() {
|
|
457
|
-
using _ = await this.
|
|
356
|
+
using _ = await this.lock()
|
|
458
357
|
await fp.rm(this.isAt, { force: true })
|
|
459
358
|
}
|
|
460
|
-
|
|
461
|
-
using _ = this.
|
|
462
|
-
|
|
463
|
-
fs.renameSync(this.isAt, newPath)
|
|
464
|
-
this.pointsTo = newPath
|
|
359
|
+
deleteSync() {
|
|
360
|
+
using _ = this.lockSync()
|
|
361
|
+
fs.rmSync(this.isAt, { force: true })
|
|
465
362
|
}
|
|
466
|
-
async move(
|
|
467
|
-
using _ = await this.
|
|
468
|
-
const newPath =
|
|
363
|
+
async move(into_: Folder) {
|
|
364
|
+
using _ = await this.lock()
|
|
365
|
+
const newPath = into_.join(this.name)
|
|
469
366
|
await fp.rename(this.isAt, newPath)
|
|
470
367
|
this.pointsTo = newPath
|
|
471
368
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
369
|
+
moveSync(into_: Folder) {
|
|
370
|
+
using _ = this.lockSync()
|
|
371
|
+
const newPath = into_.join(this.name)
|
|
372
|
+
fs.renameSync(this.isAt, newPath)
|
|
373
|
+
this.pointsTo = newPath
|
|
476
374
|
}
|
|
477
|
-
async copy(
|
|
478
|
-
const newPath =
|
|
375
|
+
async copy(into_: Folder): Promise<this> {
|
|
376
|
+
const newPath = into_.join(this.name)
|
|
479
377
|
await fp.copyFile(this.isAt, newPath)
|
|
480
378
|
return new File(newPath, false) as this
|
|
481
379
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
this.pointsTo = newPath
|
|
380
|
+
copySync(into_: Folder): this {
|
|
381
|
+
const newPath = into_.join(this.name)
|
|
382
|
+
fs.copyFileSync(this.isAt, newPath)
|
|
383
|
+
return new File(newPath, false) as this
|
|
487
384
|
}
|
|
488
|
-
async rename(
|
|
489
|
-
using _ = await this.
|
|
490
|
-
const newPath = this.parent().join(
|
|
385
|
+
async rename(to_: string) {
|
|
386
|
+
using _ = await this.lock()
|
|
387
|
+
const newPath = this.parent().join(to_)
|
|
491
388
|
await fp.rename(this.isAt, newPath)
|
|
492
389
|
this.pointsTo = newPath
|
|
493
390
|
}
|
|
494
|
-
|
|
495
|
-
using _ = this.
|
|
496
|
-
|
|
391
|
+
renameSync(to_: string) {
|
|
392
|
+
using _ = this.lockSync()
|
|
393
|
+
const newPath = this.parent().join(to_)
|
|
394
|
+
fs.renameSync(this.isAt, newPath)
|
|
395
|
+
this.pointsTo = newPath
|
|
497
396
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
397
|
+
|
|
398
|
+
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isFile() }
|
|
399
|
+
checkSync(): boolean { return fs.lstatSync(this.isAt).isFile() }
|
|
400
|
+
|
|
401
|
+
override isFile(): this is File { return true as const }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
// Bizarre functions for file I/O
|
|
406
|
+
export async function computeHash(f_: File, algorithm_?: string, options_?: cr.HashOptions): Promise<Buffer>
|
|
407
|
+
export async function computeHash(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<string>
|
|
408
|
+
export async function computeHash(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<Buffer | string> {
|
|
409
|
+
const hash = cr.createHash(algorithm_, options_)
|
|
410
|
+
for await (const chunk of f_.itBuff())
|
|
411
|
+
hash.update(chunk)
|
|
412
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
413
|
+
}
|
|
414
|
+
export function computeHashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions): Buffer
|
|
415
|
+
export function computeHashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): string
|
|
416
|
+
export function computeHashSync(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Buffer | string {
|
|
417
|
+
const hash = cr.createHash(algorithm_, options_)
|
|
418
|
+
for (const chunk of f_.itBuffSync())
|
|
419
|
+
hash.update(chunk)
|
|
420
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export async function streamHash(f_: File, algorithm_?: string, options_?: cr.HashOptions): Promise<Buffer>
|
|
424
|
+
export async function streamHash(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<string>
|
|
425
|
+
export async function streamHash(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<Buffer | string> {
|
|
426
|
+
const hash = cr.createHash(algorithm_, options_)
|
|
427
|
+
for await (const chunk of f_.itBuff())
|
|
428
|
+
hash.update(chunk)
|
|
429
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
430
|
+
}
|
|
431
|
+
export function streamHashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions): Buffer
|
|
432
|
+
export function streamHashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): string
|
|
433
|
+
export function streamHashSync(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Buffer | string {
|
|
434
|
+
const hash = cr.createHash(algorithm_, options_)
|
|
435
|
+
for (const chunk of f_.itBuffSync())
|
|
436
|
+
hash.update(chunk)
|
|
437
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export async function* itLines(f_: File, options_: fs.ReadStreamOptions = { encoding: 'utf-8' }) {
|
|
441
|
+
const readStream = fs.createReadStream(f_.isAt, options_)
|
|
442
|
+
const rlInterface = rl.createInterface({ input: readStream, crlfDelay: Infinity })
|
|
443
|
+
try {
|
|
444
|
+
for await (const line of rlInterface)
|
|
445
|
+
yield line
|
|
446
|
+
} finally {
|
|
447
|
+
rlInterface.close()
|
|
448
|
+
readStream.destroy()
|
|
501
449
|
}
|
|
502
450
|
}
|
|
503
451
|
|
|
504
|
-
export function
|
|
452
|
+
export async function fileSameAs(f1_: File, f2_: File): Promise<boolean> {
|
|
453
|
+
if (f1_.isAt === f2_.isAt)
|
|
454
|
+
return true
|
|
455
|
+
else if ((await fp.lstat(f1_.isAt)).size !== (await fp.lstat(f2_.isAt)).size)
|
|
456
|
+
return false
|
|
457
|
+
const iter1 = f1_.itBuff()
|
|
458
|
+
const iter2 = f2_.itBuff()
|
|
459
|
+
while (true) {
|
|
460
|
+
const [a, b] = await Promise.all([iter1.next(), iter2.next()])
|
|
461
|
+
if (a.done && b.done) return true
|
|
462
|
+
if (a.done !== b.done) return false
|
|
463
|
+
if (!a.value!.equals(b.value!)) return false
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
export function fileSameAsSync(f1_: File, f2_: File): boolean {
|
|
467
|
+
if (f1_.isAt === f2_.isAt)
|
|
468
|
+
return true
|
|
469
|
+
else if (fs.statSync(f1_.isAt).size !== fs.statSync(f2_.isAt).size)
|
|
470
|
+
return false
|
|
471
|
+
const iter1 = f1_.itBuffSync()
|
|
472
|
+
const iter2 = f2_.itBuffSync()
|
|
473
|
+
while (true) {
|
|
474
|
+
const a = iter1.next()
|
|
475
|
+
const b = iter2.next()
|
|
476
|
+
if (a.done && b.done) return true
|
|
477
|
+
if (a.done !== b.done) return false
|
|
478
|
+
if (!a.value!.equals(b.value!)) return false
|
|
479
|
+
}
|
|
480
|
+
}
|
|
505
481
|
|
|
506
482
|
|
|
507
483
|
|
|
508
484
|
export class Folder extends Road {
|
|
509
|
-
static async create(
|
|
510
|
-
try {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
await fp.mkdir(at, { recursive: true })
|
|
514
|
-
}
|
|
515
|
-
return new Folder(at, false)
|
|
485
|
+
static async create(at_: string) {
|
|
486
|
+
try { await fp.access(at_, fsc.W_OK) }
|
|
487
|
+
catch { await fp.mkdir(at_, { recursive: true }) }
|
|
488
|
+
return new Folder(at_, false)
|
|
516
489
|
}
|
|
517
|
-
static createSync(
|
|
518
|
-
try {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
fs.mkdirSync(at, { recursive: true })
|
|
522
|
-
}
|
|
523
|
-
return new Folder(at, false)
|
|
490
|
+
static createSync(at_: string) {
|
|
491
|
+
try { fs.accessSync(at_, fsc.W_OK) }
|
|
492
|
+
catch { fs.mkdirSync(at_, { recursive: true }) }
|
|
493
|
+
return new Folder(at_, false)
|
|
524
494
|
}
|
|
525
495
|
|
|
526
|
-
join(...
|
|
527
|
-
return ph.join(this.isAt, ...
|
|
496
|
+
join(...paths_: string[]) {
|
|
497
|
+
return ph.join(this.isAt, ...paths_)
|
|
528
498
|
}
|
|
529
499
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
*
|
|
533
|
-
for (const
|
|
534
|
-
const road =
|
|
535
|
-
if (!
|
|
500
|
+
it(): AsyncIterable<Road>
|
|
501
|
+
it<T extends Road>(expectedType_: new () => T): AsyncIterable<T>
|
|
502
|
+
async *it<T extends Road>(expectedType_?: new () => T): AsyncIterable<Road> | AsyncIterable<T> {
|
|
503
|
+
for (const entryName of await fp.readdir(this.isAt)) {
|
|
504
|
+
const road = await factory(this.join(entryName))
|
|
505
|
+
if (!expectedType_ || road instanceof expectedType_)
|
|
536
506
|
yield road
|
|
537
507
|
}
|
|
538
508
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
for (const entry of
|
|
543
|
-
const road =
|
|
544
|
-
if (!
|
|
509
|
+
itSync(): Iterable<Road>
|
|
510
|
+
itSync<T extends Road>(expectedType_: new () => T): Iterable<T>
|
|
511
|
+
*itSync<T extends Road>(expectedType_?: new () => T): Iterable<Road> | Iterable<T> {
|
|
512
|
+
for (const entry of fs.readdirSync(this.isAt)) {
|
|
513
|
+
const road = factorySync(this.join(entry))
|
|
514
|
+
if (!expectedType_ || road instanceof expectedType_)
|
|
545
515
|
yield road
|
|
546
516
|
}
|
|
547
517
|
}
|
|
548
|
-
listSync(): Road[]
|
|
549
|
-
listSync<T extends Road>(expectedType: new () => T): T[]
|
|
550
|
-
listSync<T extends Road>(expectedType?: new () => T): Road[] | T[] {
|
|
551
|
-
const entries = fs.readdirSync(this.isAt).map(entry => factorySync(this.join(entry)))
|
|
552
|
-
if (!expectedType)
|
|
553
|
-
return entries
|
|
554
|
-
return entries.filter(entry => entry instanceof expectedType) as unknown as T[]
|
|
555
|
-
}
|
|
556
518
|
async list(): Promise<Road[]>
|
|
557
|
-
async list<T extends Road>(
|
|
558
|
-
async list<T extends Road>(
|
|
519
|
+
async list<T extends Road>(expectedType_: new () => T): Promise<T[]>
|
|
520
|
+
async list<T extends Road>(expectedType_?: new () => T): Promise<Road[] | T[]> {
|
|
559
521
|
const entries = (await fp.readdir(this.isAt)).map(async entry => factory(this.join(entry)))
|
|
560
522
|
const resolvedEntries = await Promise.all(entries)
|
|
561
|
-
if (!
|
|
523
|
+
if (!expectedType_)
|
|
562
524
|
return resolvedEntries
|
|
563
|
-
return resolvedEntries.filter(entry => entry instanceof
|
|
525
|
+
return resolvedEntries.filter(entry => entry instanceof expectedType_) as unknown as T[]
|
|
526
|
+
}
|
|
527
|
+
listSync(): Road[]
|
|
528
|
+
listSync<T extends Road>(expectedType_: new () => T): T[]
|
|
529
|
+
listSync<T extends Road>(expectedType_?: new () => T): Road[] | T[] {
|
|
530
|
+
const entries = fs.readdirSync(this.isAt).map(entry => factorySync(this.join(entry)))
|
|
531
|
+
if (!expectedType_)
|
|
532
|
+
return entries
|
|
533
|
+
return entries.filter(entry => entry instanceof expectedType_) as unknown as T[]
|
|
564
534
|
}
|
|
565
535
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
536
|
+
async find(name_: string): Promise<Road | null>
|
|
537
|
+
async find<T extends Road>(name_: string, expectedType_: new () => T): Promise<T | null>
|
|
538
|
+
async find<T extends Road>(name_: string, expectedType_?: new () => T): Promise<Road | T | null> {
|
|
569
539
|
try {
|
|
570
|
-
|
|
571
|
-
|
|
540
|
+
await fp.access(this.join(name_), fs.constants.F_OK)
|
|
541
|
+
const found = await factory(this.join(name_))
|
|
542
|
+
if (!expectedType_)
|
|
572
543
|
return found
|
|
573
|
-
if (found instanceof
|
|
544
|
+
if (found instanceof expectedType_)
|
|
574
545
|
return found as T
|
|
575
546
|
return null
|
|
576
547
|
} catch {
|
|
577
548
|
return null
|
|
578
549
|
}
|
|
579
550
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
async find<T extends Road>(name: string, _expectedType?: new () => T): Promise<Road | T | null> {
|
|
551
|
+
findSync(name_: string): Road | null
|
|
552
|
+
findSync<T extends Road>(name_: string, expectedType_: new () => T): T | null
|
|
553
|
+
findSync<T extends Road>(name_: string, expectedType_?: new () => T): Road | T | null {
|
|
584
554
|
try {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
if (!_expectedType)
|
|
555
|
+
const found = factorySync(this.join(name_))
|
|
556
|
+
if (!expectedType_)
|
|
588
557
|
return found
|
|
589
|
-
if (found instanceof
|
|
558
|
+
if (found instanceof expectedType_)
|
|
590
559
|
return found as T
|
|
591
560
|
return null
|
|
592
561
|
} catch {
|
|
@@ -594,69 +563,71 @@ export class Folder extends Road {
|
|
|
594
563
|
}
|
|
595
564
|
}
|
|
596
565
|
|
|
597
|
-
|
|
598
|
-
const newPath = this.join(
|
|
599
|
-
|
|
600
|
-
return
|
|
566
|
+
async add<T extends Road>(name_: string, createable_: { create: (at: string) => Promise<T> }): Promise<T> {
|
|
567
|
+
const newPath = this.join(name_)
|
|
568
|
+
await createable_.create(newPath)
|
|
569
|
+
return (await factory(newPath)) as unknown as T
|
|
601
570
|
}
|
|
602
|
-
|
|
603
|
-
const newPath = this.join(
|
|
604
|
-
|
|
605
|
-
return
|
|
571
|
+
addSync<T extends Road>(name_: string, createable_: { createSync: (at: string) => T }): T {
|
|
572
|
+
const newPath = this.join(name_)
|
|
573
|
+
createable_.createSync(newPath)
|
|
574
|
+
return factorySync(newPath) as unknown as T
|
|
606
575
|
}
|
|
607
576
|
|
|
608
|
-
|
|
609
|
-
using _ = this.
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
async delete(options: fs.RmOptions = { recursive: true }) {
|
|
613
|
-
using _ = await this.initChange()
|
|
614
|
-
await fp.rm(this.isAt, options)
|
|
577
|
+
async delete(options_: fs.RmOptions = { recursive: true }) {
|
|
578
|
+
using _ = await this.lock()
|
|
579
|
+
await fp.rm(this.isAt, options_)
|
|
615
580
|
}
|
|
616
|
-
|
|
617
|
-
using _ = this.
|
|
618
|
-
|
|
619
|
-
fs.renameSync(this.isAt, newPath)
|
|
620
|
-
this.pointsTo = newPath
|
|
581
|
+
deleteSync(options_: fs.RmOptions = { recursive: true }) {
|
|
582
|
+
using _ = this.lockSync()
|
|
583
|
+
fs.rmSync(this.isAt, options_)
|
|
621
584
|
}
|
|
622
|
-
async move(
|
|
623
|
-
using _ = await this.
|
|
624
|
-
const newPath =
|
|
585
|
+
async move(into_: Folder) {
|
|
586
|
+
using _ = await this.lock()
|
|
587
|
+
const newPath = into_.join(this.name)
|
|
625
588
|
await fp.rename(this.isAt, newPath)
|
|
626
589
|
this.pointsTo = newPath
|
|
627
590
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
591
|
+
moveSync(into_: Folder) {
|
|
592
|
+
using _ = this.lockSync()
|
|
593
|
+
const newPath = into_.join(this.name)
|
|
594
|
+
fs.renameSync(this.isAt, newPath)
|
|
595
|
+
this.pointsTo = newPath
|
|
632
596
|
}
|
|
633
|
-
async copy(
|
|
634
|
-
const newPath =
|
|
597
|
+
async copy(into_: Folder): Promise<this> {
|
|
598
|
+
const newPath = into_.join(this.name)
|
|
635
599
|
await fp.cp(this.isAt, newPath, { recursive: true })
|
|
636
600
|
return new Folder(newPath, false) as this
|
|
637
601
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
this.pointsTo = newPath
|
|
602
|
+
copySync(into_: Folder): this {
|
|
603
|
+
const newPath = into_.join(this.name)
|
|
604
|
+
fs.cpSync(this.isAt, newPath, { recursive: true })
|
|
605
|
+
return new Folder(newPath, false) as this
|
|
643
606
|
}
|
|
644
|
-
async rename(
|
|
645
|
-
using _ = await this.
|
|
646
|
-
const newPath = this.parent().join(
|
|
607
|
+
async rename(to_: string) {
|
|
608
|
+
using _ = await this.lock()
|
|
609
|
+
const newPath = this.parent().join(to_)
|
|
647
610
|
await fp.rename(this.isAt, newPath)
|
|
648
611
|
this.pointsTo = newPath
|
|
649
612
|
}
|
|
650
|
-
|
|
651
|
-
using _ = this.
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
using _ = await this.initChange()
|
|
656
|
-
await fp.mkdir(this.isAt, { recursive: true })
|
|
613
|
+
renameSync(to_: string) {
|
|
614
|
+
using _ = this.lockSync()
|
|
615
|
+
const newPath = this.parent().join(to_)
|
|
616
|
+
fs.renameSync(this.isAt, newPath)
|
|
617
|
+
this.pointsTo = newPath
|
|
657
618
|
}
|
|
619
|
+
|
|
620
|
+
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isDirectory() }
|
|
621
|
+
checkSync(): boolean { return fs.lstatSync(this.isAt).isDirectory() }
|
|
622
|
+
|
|
623
|
+
override isFolder(): this is Folder { return true as const }
|
|
624
|
+
override isDir(): this is Folder { return true as const }
|
|
625
|
+
override isDirectory(): this is Folder { return true as const }
|
|
626
|
+
override isDict(): this is Folder { return true as const }
|
|
627
|
+
override isDictionary(): this is Folder { return true as const }
|
|
658
628
|
}
|
|
659
629
|
|
|
630
|
+
|
|
660
631
|
export function sysRoot() { return new Folder(ph.parse(process.cwd()).root, false) }
|
|
661
632
|
export function home() { return new Folder(os.homedir(), false) }
|
|
662
633
|
export function tmp() { return new Folder(os.tmpdir(), false) }
|
|
@@ -666,133 +637,137 @@ export { Folder as Dir, Folder as Directory, Folder as Dict, Folder as Dictionar
|
|
|
666
637
|
|
|
667
638
|
|
|
668
639
|
export class SymbolicLink extends Road {
|
|
669
|
-
static async create(
|
|
670
|
-
try {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
await fp.symlink(target.isAt, at)
|
|
674
|
-
}
|
|
675
|
-
return new SymbolicLink(at, false)
|
|
640
|
+
static async create(at_: string, target_: string | Road) {
|
|
641
|
+
try { await fp.access(at_, fs.constants.F_OK) }
|
|
642
|
+
catch { await fp.symlink(target_.toString(), at_) }
|
|
643
|
+
return new SymbolicLink(at_, false)
|
|
676
644
|
}
|
|
677
|
-
static createSync(
|
|
678
|
-
try {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
fs.symlinkSync(_target.isAt, _at)
|
|
682
|
-
}
|
|
683
|
-
return new SymbolicLink(_at, false)
|
|
645
|
+
static createSync(at_: string, target_: string | Road) {
|
|
646
|
+
try { fs.accessSync(at_, fs.constants.F_OK) }
|
|
647
|
+
catch { fs.symlinkSync(target_.toString(), at_) }
|
|
648
|
+
return new SymbolicLink(at_, false)
|
|
684
649
|
}
|
|
685
650
|
|
|
686
|
-
targetSync() {
|
|
687
|
-
return factorySync(ph.resolve(ph.dirname(this.isAt), fs.readlinkSync(this.isAt)))
|
|
688
|
-
}
|
|
689
651
|
async target() {
|
|
690
652
|
return factory(ph.resolve(ph.dirname(this.isAt), await fp.readlink(this.isAt)))
|
|
691
653
|
}
|
|
692
|
-
|
|
693
|
-
this.
|
|
694
|
-
fs.symlinkSync(_newTarget.isAt, this.isAt)
|
|
654
|
+
targetSync() {
|
|
655
|
+
return factorySync(ph.resolve(ph.dirname(this.isAt), fs.readlinkSync(this.isAt)))
|
|
695
656
|
}
|
|
696
|
-
async retarget(
|
|
657
|
+
async retarget(to_: Road) {
|
|
697
658
|
await this.delete()
|
|
698
|
-
return fp.symlink(
|
|
659
|
+
return fp.symlink(to_.isAt, this.isAt)
|
|
699
660
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
fs.unlinkSync(this.isAt)
|
|
661
|
+
retargetSync(to_: Road) {
|
|
662
|
+
this.deleteSync()
|
|
663
|
+
fs.symlinkSync(to_.isAt, this.isAt)
|
|
704
664
|
}
|
|
665
|
+
|
|
705
666
|
async delete() {
|
|
706
|
-
using _ = await this.
|
|
667
|
+
using _ = await this.lock()
|
|
707
668
|
await fp.unlink(this.isAt)
|
|
708
669
|
}
|
|
709
|
-
|
|
710
|
-
using _ = this.
|
|
711
|
-
|
|
712
|
-
fs.renameSync(this.isAt, newPath)
|
|
713
|
-
this.pointsTo = newPath
|
|
670
|
+
deleteSync() {
|
|
671
|
+
using _ = this.lockSync()
|
|
672
|
+
fs.unlinkSync(this.isAt)
|
|
714
673
|
}
|
|
715
|
-
async move(
|
|
716
|
-
using _ = await this.
|
|
717
|
-
const newPath =
|
|
674
|
+
async move(into_: Folder) {
|
|
675
|
+
using _ = await this.lock()
|
|
676
|
+
const newPath = into_.join(this.name)
|
|
718
677
|
await fp.rename(this.isAt, newPath)
|
|
719
678
|
this.pointsTo = newPath
|
|
720
679
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const
|
|
724
|
-
fs.
|
|
725
|
-
|
|
680
|
+
moveSync(into_: Folder) {
|
|
681
|
+
using _ = this.lockSync()
|
|
682
|
+
const newPath = into_.join(this.name)
|
|
683
|
+
fs.renameSync(this.isAt, newPath)
|
|
684
|
+
this.pointsTo = newPath
|
|
726
685
|
}
|
|
727
|
-
async copy(
|
|
728
|
-
const newPath =
|
|
686
|
+
async copy(into_: Folder): Promise<this> {
|
|
687
|
+
const newPath = into_.join(this.name)
|
|
729
688
|
const target = await this.target()
|
|
730
689
|
await fp.symlink(target.isAt, newPath)
|
|
731
690
|
return new SymbolicLink(newPath, false) as this
|
|
732
691
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const
|
|
736
|
-
fs.
|
|
737
|
-
|
|
692
|
+
copySync(into_: Folder): this {
|
|
693
|
+
const newPath = into_.join(this.name)
|
|
694
|
+
const target = this.targetSync()
|
|
695
|
+
fs.symlinkSync(target.isAt, newPath)
|
|
696
|
+
return new SymbolicLink(newPath, false) as this
|
|
738
697
|
}
|
|
739
|
-
async rename(
|
|
740
|
-
using _ = await this.
|
|
741
|
-
const newPath = this.parent().join(
|
|
698
|
+
async rename(to_: string) {
|
|
699
|
+
using _ = await this.lock()
|
|
700
|
+
const newPath = this.parent().join(to_)
|
|
742
701
|
await fp.rename(this.isAt, newPath)
|
|
743
702
|
this.pointsTo = newPath
|
|
744
703
|
}
|
|
745
|
-
|
|
746
|
-
using _ = this.
|
|
747
|
-
const
|
|
748
|
-
fs.
|
|
749
|
-
|
|
750
|
-
async resurrect() {
|
|
751
|
-
using _ = await this.initChange()
|
|
752
|
-
const target = await this.target()
|
|
753
|
-
await fp.symlink(target.isAt, this.isAt)
|
|
704
|
+
renameSync(to_: string) {
|
|
705
|
+
using _ = this.lockSync()
|
|
706
|
+
const newPath = this.parent().join(to_)
|
|
707
|
+
fs.renameSync(this.isAt, newPath)
|
|
708
|
+
this.pointsTo = newPath
|
|
754
709
|
}
|
|
710
|
+
|
|
711
|
+
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isSymbolicLink() }
|
|
712
|
+
checkSync(): boolean { return fs.lstatSync(this.isAt).isSymbolicLink() }
|
|
713
|
+
|
|
714
|
+
override isSymlink(): this is SymbolicLink { return true as const }
|
|
715
|
+
override isSymbolicLink(): this is SymbolicLink { return true as const }
|
|
755
716
|
}
|
|
756
717
|
export { SymbolicLink as Symlink }
|
|
757
718
|
|
|
758
719
|
|
|
759
720
|
|
|
760
721
|
export abstract class UnusableRoad extends Road {
|
|
761
|
-
override readonly mutable: boolean = false // Modification
|
|
762
|
-
constructor(
|
|
763
|
-
super(
|
|
722
|
+
override readonly mutable: boolean = false // Modification will cause system issues (e.g. deleting a device file)
|
|
723
|
+
constructor(...args_: ConstructorParameters<typeof Road>) {
|
|
724
|
+
super(...args_)
|
|
764
725
|
Object.freeze(this)
|
|
765
726
|
}
|
|
766
|
-
error(): never { throw new RdErr(`${this.constructor.name} at '${this.isAt}' is a system-level resource thus
|
|
767
|
-
override
|
|
768
|
-
override
|
|
769
|
-
override deleteSync(): never { return this.error() }
|
|
727
|
+
error(): never { throw new RdErr(`${this.constructor.name} at '${this.isAt}' is a system-level resource thus not subject to modification.`) }
|
|
728
|
+
override async lock(): Promise<never> { return this.error() }
|
|
729
|
+
override lockSync(): never { return this.error() }
|
|
770
730
|
override async delete(): Promise<never> { return this.error() }
|
|
771
|
-
override
|
|
731
|
+
override deleteSync(): never { return this.error() }
|
|
772
732
|
override async move(): Promise<never> { return this.error() }
|
|
773
|
-
override
|
|
733
|
+
override moveSync(): never { return this.error() }
|
|
774
734
|
override async copy(): Promise<never> { return this.error() }
|
|
775
|
-
override
|
|
735
|
+
override copySync(): never { return this.error() }
|
|
776
736
|
override async rename(): Promise<never> { return this.error() }
|
|
777
|
-
override
|
|
778
|
-
|
|
737
|
+
override renameSync(): never { return this.error() }
|
|
738
|
+
|
|
739
|
+
override isUnusable(): this is UnusableRoad { return true as const }
|
|
740
|
+
}
|
|
741
|
+
export class BlockDevice extends UnusableRoad {
|
|
742
|
+
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isBlockDevice() }
|
|
743
|
+
checkSync(): boolean { return fs.lstatSync(this.isAt).isBlockDevice() }
|
|
744
|
+
override isBlockDevice(): this is BlockDevice { return true as const }
|
|
745
|
+
}
|
|
746
|
+
export class CharacterDevice extends UnusableRoad {
|
|
747
|
+
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isCharacterDevice() }
|
|
748
|
+
checkSync(): boolean { return fs.lstatSync(this.isAt).isCharacterDevice() }
|
|
749
|
+
override isCharacterDevice(): this is CharacterDevice { return true as const }
|
|
750
|
+
}
|
|
751
|
+
export class Fifo extends UnusableRoad {
|
|
752
|
+
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isFIFO() }
|
|
753
|
+
checkSync(): boolean { return fs.lstatSync(this.isAt).isFIFO() }
|
|
754
|
+
override isFifo(): this is Fifo { return true as const }
|
|
755
|
+
}
|
|
756
|
+
export class Socket extends UnusableRoad {
|
|
757
|
+
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isSocket() }
|
|
758
|
+
checkSync(): boolean { return fs.lstatSync(this.isAt).isSocket() }
|
|
759
|
+
override isSocket(): this is Socket { return true as const }
|
|
779
760
|
}
|
|
780
|
-
export class BlockDevice extends UnusableRoad { }
|
|
781
|
-
export class CharacterDevice extends UnusableRoad { }
|
|
782
|
-
export class Fifo extends UnusableRoad { }
|
|
783
|
-
export class Socket extends UnusableRoad { }
|
|
784
761
|
|
|
785
762
|
|
|
786
763
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
let exitHandlerRegistered =
|
|
764
|
+
let finalizer: FinalizationRegistry<string> | null = null
|
|
765
|
+
let toDelete: Set<string> | null = null
|
|
766
|
+
let exitHandlerRegistered: boolean | null = null
|
|
790
767
|
/**
|
|
791
768
|
* Forcefully cleans up all files and folders registered for cleanup on exit.
|
|
792
|
-
*
|
|
793
|
-
* @remarks This function is not recommended to be called manually, as it will delete all files and folders registered for cleanup on exit, which may lead to data loss if called at the wrong time. This function is intended to be called automatically when the process exits.
|
|
794
769
|
*/
|
|
795
|
-
|
|
770
|
+
function forceCleanupToDelete() {
|
|
796
771
|
for (const path of toDelete ?? [])
|
|
797
772
|
try { fs.rmSync(path, { force: true, recursive: true }) } catch {}
|
|
798
773
|
toDelete?.clear()
|
|
@@ -802,26 +777,24 @@ export function forceCleanupToDelete() {
|
|
|
802
777
|
process.off('exit', forceCleanupToDelete)
|
|
803
778
|
exitHandlerRegistered = false
|
|
804
779
|
}
|
|
805
|
-
export function registerToCleanup(
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
if (!toDelete)
|
|
809
|
-
toDelete = new Set()
|
|
780
|
+
export function registerToCleanup(self_: Road) {
|
|
781
|
+
finalizer ??= new FinalizationRegistry<string>(p => { try { fs.rmSync(p, { force: true, recursive: true }) } catch {}; toDelete?.delete(p) })
|
|
782
|
+
toDelete ??= new Set()
|
|
810
783
|
if (!exitHandlerRegistered) {
|
|
811
784
|
process.once('exit', forceCleanupToDelete)
|
|
812
785
|
exitHandlerRegistered = true
|
|
813
786
|
}
|
|
814
|
-
toDelete.add(
|
|
815
|
-
finalizer.register(
|
|
787
|
+
toDelete.add(self_.isAt)
|
|
788
|
+
finalizer.register(self_, self_.isAt, self_)
|
|
816
789
|
}
|
|
817
790
|
|
|
818
791
|
|
|
819
|
-
export function Temp<T extends Road>(
|
|
820
|
-
|
|
821
|
-
if (
|
|
792
|
+
export function Temp<T extends Road>(createable_: { createSync: (at: string) => T }, autoCleanup_: boolean): T & Disposable & AsyncDisposable {
|
|
793
|
+
let t = createable_.createSync(tmp().join(`instrumentality@${cr.randomUUID()}`))
|
|
794
|
+
if (autoCleanup_)
|
|
822
795
|
registerToCleanup(t)
|
|
823
796
|
return Object.freeze(Object.assign(t, {
|
|
824
797
|
[Symbol.dispose]() { try { t.deleteSync() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) },
|
|
825
798
|
async [Symbol.asyncDispose]() { try { await t.delete() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) }
|
|
826
799
|
}))
|
|
827
|
-
}
|
|
800
|
+
}11
|