instrumentality 0.0.1
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 +35 -0
- package/dist/base.d.ts +146 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +209 -0
- package/dist/dom.d.ts +69 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +103 -0
- package/dist/road.d.ts +261 -0
- package/dist/road.d.ts.map +1 -0
- package/dist/road.js +783 -0
- package/package.json +40 -0
- package/src/base.ts +301 -0
- package/src/dom.ts +120 -0
- package/src/road.ts +827 -0
package/src/road.ts
ADDED
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
import * as rl from "node:readline"
|
|
2
|
+
import * as fs from "node:fs"; import { constants as fsc } from "node:fs"
|
|
3
|
+
import * as fp from "node:fs/promises"
|
|
4
|
+
import * as ph from "node:path"
|
|
5
|
+
import * as os from "node:os"
|
|
6
|
+
import * as cr from "node:crypto"
|
|
7
|
+
import * as sp from "node:stream/promises"
|
|
8
|
+
import { on } from "node:events"
|
|
9
|
+
import * as bs from "./base.ts"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Subclass of {@link bs.InsErr} that represents an error thrown from this specific module of the library
|
|
15
|
+
*/
|
|
16
|
+
export class RdErr extends bs.InsErr { override name = "Instrumentality-Road-Error" }
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the constructor function corresponding to the file mode.
|
|
22
|
+
*
|
|
23
|
+
* @param statmode The file mode to check.
|
|
24
|
+
* @returns The constructor function corresponding to the file mode.
|
|
25
|
+
* @throws If the file mode is unknown, throws a {@link RdErr}.
|
|
26
|
+
*/
|
|
27
|
+
export function modeCtor(statmode: number) {
|
|
28
|
+
switch (statmode & fsc.S_IFMT) {
|
|
29
|
+
case fsc.S_IFREG: return File
|
|
30
|
+
case fsc.S_IFDIR: return Folder
|
|
31
|
+
case fsc.S_IFBLK: return BlockDevice
|
|
32
|
+
case fsc.S_IFCHR: return CharacterDevice
|
|
33
|
+
case fsc.S_IFLNK: return SymbolicLink
|
|
34
|
+
case fsc.S_IFIFO: return Fifo
|
|
35
|
+
case fsc.S_IFSOCK: return Socket
|
|
36
|
+
default: throw new RdErr(`Unknown mode type ${statmode} (statmode is most likely corrupted)`)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a new instance of the appropriate subclass of {@link Road} based on the file mode of the specified path.
|
|
44
|
+
*
|
|
45
|
+
* @param lookFor The path to check.
|
|
46
|
+
* @returns A new instance of the appropriate subclass of {@link Road}.
|
|
47
|
+
* @throws If the path does not exist, throws a fs {@link Error}.
|
|
48
|
+
*/
|
|
49
|
+
export function factorySync(lookFor: string) {
|
|
50
|
+
fs.accessSync(lookFor, fsc.F_OK)
|
|
51
|
+
return new (modeCtor(fs.lstatSync(lookFor).mode))(lookFor, false)
|
|
52
|
+
}
|
|
53
|
+
/** Async version of {@link factorySync}. */
|
|
54
|
+
export async function factory(lookFor: string) {
|
|
55
|
+
await fp.access(lookFor, fsc.F_OK)
|
|
56
|
+
return new (modeCtor((await fp.lstat(lookFor)).mode))(lookFor, false)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A map that keeps track of locked roads to prevent concurrent modifications. The keys are the absolute paths of the roads, and the values are promises that resolve when the lock is released.
|
|
63
|
+
*
|
|
64
|
+
* @remarks Don't manually modify this map. Use the {@link Road.initChange} and {@link Road.initChangeSync} methods to acquire and release locks on roads.
|
|
65
|
+
* For read-only purposes, you should use the {@link lockFor} function to await the optional lock on a road.
|
|
66
|
+
*/
|
|
67
|
+
let lockedRoads: Map<string, Promise<void>> | null = null
|
|
68
|
+
/**
|
|
69
|
+
* Getter for the locked roads map.
|
|
70
|
+
*
|
|
71
|
+
* @param roadOrPath - A {@link Road} instance or a string representing the absolute path of the road to check.
|
|
72
|
+
* @returns The promise associated with the locked road, or `undefined` if the road is not currently locked.
|
|
73
|
+
*/
|
|
74
|
+
export function lockFor(roadOrPath: Road | string) {
|
|
75
|
+
return lockedRoads?.get(roadOrPath.toString())
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
export abstract class Road {
|
|
81
|
+
/** 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. */
|
|
83
|
+
protected pointsTo: string
|
|
84
|
+
/** Indicates whether the file or directory represented by this Road instance can be modified.
|
|
85
|
+
* 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
|
+
mutable: boolean = true
|
|
87
|
+
|
|
88
|
+
// Quick accessors
|
|
89
|
+
/** Accessor for the absolute path to the file or directory that this Road instance represents. */
|
|
90
|
+
get isAt() { return this.pointsTo }
|
|
91
|
+
/** Accessor for the name of the file or directory that this Road instance represents. */
|
|
92
|
+
get name() { return ph.basename(this.isAt) }
|
|
93
|
+
/** Same as {@link isAt} but for compatibility with external libraries that try to convert the object to a string. */
|
|
94
|
+
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
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Constructs a new Road instance representing the file or directory at the specified path.
|
|
103
|
+
*
|
|
104
|
+
* @param lookFor The path to the file or directory that this Road instance will represent.
|
|
105
|
+
* @param typeCheck Whether to check if the type of the file or directory at the specified path matches the type of this instance.
|
|
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.
|
|
118
|
+
*
|
|
119
|
+
* @param expectMode The expected access mode for this Road other than visibility by this process.
|
|
120
|
+
* @returns Result of the verification.
|
|
121
|
+
*/
|
|
122
|
+
async verify(expectMode: number, typeCheck: boolean): Promise<boolean> {
|
|
123
|
+
try {
|
|
124
|
+
await fp.access(this.isAt, fsc.F_OK | expectMode)
|
|
125
|
+
return typeCheck || this instanceof (await this.type())
|
|
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
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Creates a disposable lock for the file or directory represented by this Road instance, preventing concurrent modifications.
|
|
142
|
+
*
|
|
143
|
+
* @returns An object with a `dispose` method that releases the lock when called. The lock is automatically released when the object is garbage collected.
|
|
144
|
+
* @remarks Please use this method with the `await using` statement to ensure that the lock is properly released after the operation is complete. This method is intended for internal use and shouldn't be called directly in most cases. (that's why it's protected)
|
|
145
|
+
*/
|
|
146
|
+
protected async initChange() {
|
|
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'})`)
|
|
149
|
+
if (!this.mutable)
|
|
150
|
+
throw new RdErr(`Attempting to modify road to '${this.isAt}' of type ${this.constructor.name} which's marked as immutable (unrelated to the actual OS file permissions)`)
|
|
151
|
+
if (!lockedRoads)
|
|
152
|
+
lockedRoads = new Map<string, Promise<void>>()
|
|
153
|
+
const lockedPath = this.isAt
|
|
154
|
+
let releaseLock = () => {}
|
|
155
|
+
await lockFor(lockedPath)
|
|
156
|
+
lockedRoads.set(lockedPath, new Promise<void>(res => releaseLock = res))
|
|
157
|
+
return {
|
|
158
|
+
[Symbol.dispose]() {
|
|
159
|
+
releaseLock()
|
|
160
|
+
releaseLock = () => { throw new RdErr("Lock already released, can't dispose") }
|
|
161
|
+
lockedRoads!.delete(lockedPath)
|
|
162
|
+
},
|
|
163
|
+
async [Symbol.asyncDispose]() {
|
|
164
|
+
releaseLock()
|
|
165
|
+
releaseLock = () => { throw new RdErr("Lock already released, can't dispose") }
|
|
166
|
+
lockedRoads!.delete(lockedPath)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Sync version of {@link initChangeSync}.
|
|
172
|
+
*
|
|
173
|
+
* @remarks This sync version will throw if the road is already locked by another operation.
|
|
174
|
+
*/
|
|
175
|
+
protected initChangeSync() {
|
|
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'})`)
|
|
178
|
+
if (!this.mutable)
|
|
179
|
+
throw new RdErr(`Attempting to modify road to '${this.isAt}' of type ${this.constructor.name} which's marked as immutable (unrelated to the actual OS file permissions)`)
|
|
180
|
+
if (!lockedRoads)
|
|
181
|
+
lockedRoads = new Map<string, Promise<void>>()
|
|
182
|
+
const lockedPath = this.isAt
|
|
183
|
+
if (lockedRoads.has(lockedPath))
|
|
184
|
+
throw new RdErr(`Road to '${this.isAt}' is currently locked by another operation, can't modify synchronously`)
|
|
185
|
+
let releaseLock = () => {}
|
|
186
|
+
lockedRoads.set(lockedPath, new Promise<void>(res => releaseLock = res))
|
|
187
|
+
return {
|
|
188
|
+
[Symbol.dispose]() {
|
|
189
|
+
lockedRoads!.delete(lockedPath)
|
|
190
|
+
releaseLock()
|
|
191
|
+
releaseLock = () => { throw new RdErr("Lock already released, can't dispose") }
|
|
192
|
+
},
|
|
193
|
+
async [Symbol.asyncDispose]() {
|
|
194
|
+
lockedRoads!.delete(lockedPath)
|
|
195
|
+
releaseLock()
|
|
196
|
+
releaseLock = () => { throw new RdErr("Lock already released, can't dispose") }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Checks if the file or directory represented by this Road is both visible and of the same type as expected.
|
|
203
|
+
*/
|
|
204
|
+
existsSync() { return this.verifySync(fsc.F_OK, true) }
|
|
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.
|
|
222
|
+
*/
|
|
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
|
+
*ancestorsIt() {
|
|
227
|
+
let current: Folder = this.parent()
|
|
228
|
+
let parent = current.parent()
|
|
229
|
+
while (current.isAt !== parent.isAt) {
|
|
230
|
+
yield current
|
|
231
|
+
current = parent
|
|
232
|
+
parent = current.parent()
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
ancestors() { return [...this.ancestorsIt()] }
|
|
236
|
+
|
|
237
|
+
async untilAccessible(mode = fsc.F_OK, abs: AbortSignal, onEachAttempt?: () => unknown) {
|
|
238
|
+
const watcher = fs.watch(this.isAt)
|
|
239
|
+
try {
|
|
240
|
+
if (await this.verify(mode, true))
|
|
241
|
+
return
|
|
242
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
243
|
+
for await (let _ of on(watcher, 'change', { signal: abs }))
|
|
244
|
+
if (await this.verify(mode, true))
|
|
245
|
+
return
|
|
246
|
+
else
|
|
247
|
+
await onEachAttempt?.()
|
|
248
|
+
} finally {
|
|
249
|
+
watcher.close()
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async onChange<T>(abs: AbortSignal, cb?: () => T) {
|
|
253
|
+
const watcher = fs.watch(this.isAt)
|
|
254
|
+
try {
|
|
255
|
+
for await (let _ of on(watcher, 'change', { signal: abs }))
|
|
256
|
+
return await cb?.() || null
|
|
257
|
+
return null
|
|
258
|
+
}
|
|
259
|
+
catch(e) { throw e }
|
|
260
|
+
finally { watcher.close() }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
metaSync(suffixID = "tsInstrumentalityMeta") {
|
|
264
|
+
if (os.platform() === "win32")
|
|
265
|
+
return fs.readFileSync(`${this.isAt}:${suffixID}`)
|
|
266
|
+
else
|
|
267
|
+
throw new RdErr("Extended attributes are not supported on this platform")
|
|
268
|
+
}
|
|
269
|
+
async meta(suffixID = "tsInstrumentalityMeta"): Promise<Record<string, unknown>> {
|
|
270
|
+
if (os.platform() === "win32")
|
|
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
|
+
}
|
|
289
|
+
|
|
290
|
+
abstract deleteSync(): void
|
|
291
|
+
abstract delete(): Promise<void>
|
|
292
|
+
abstract moveSync(into: Folder): void
|
|
293
|
+
abstract move(into: Folder): Promise<void>
|
|
294
|
+
abstract copySync(into: Folder): this
|
|
295
|
+
abstract copy(into: Folder): Promise<this>
|
|
296
|
+
abstract renameSync(to: string): void
|
|
297
|
+
abstract rename(to: string): Promise<void>
|
|
298
|
+
abstract resurrectSync(): void
|
|
299
|
+
abstract resurrect(): Promise<void>
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
export class File extends Road {
|
|
305
|
+
get ext() { return ph.extname(this.isAt) }
|
|
306
|
+
get noExt() { return ph.basename(this.isAt, this.ext) }
|
|
307
|
+
|
|
308
|
+
static createSync(at: string) {
|
|
309
|
+
try {
|
|
310
|
+
fs.accessSync(at, fsc.W_OK)
|
|
311
|
+
} catch {
|
|
312
|
+
fs.writeFileSync(at, "")
|
|
313
|
+
}
|
|
314
|
+
return new File(at, false)
|
|
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)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
readSync(): Buffer
|
|
326
|
+
readSync(encoding: BufferEncoding, flag?: string): string
|
|
327
|
+
readSync(encoding?: BufferEncoding, flag?: string): Buffer | string {
|
|
328
|
+
if (encoding)
|
|
329
|
+
return fs.readFileSync(this.isAt, { encoding: encoding, flag: flag })
|
|
330
|
+
else
|
|
331
|
+
return fs.readFileSync(this.isAt)
|
|
332
|
+
}
|
|
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
|
+
|
|
342
|
+
// Bizarre reading
|
|
343
|
+
*itBuffSync(chunkSize: number = 64 * 1024, flags: string | number = 'r', mode?: fs.Mode) {
|
|
344
|
+
const fd = fs.openSync(this.isAt, flags, mode)
|
|
345
|
+
try {
|
|
346
|
+
const buffer = Buffer.alloc(chunkSize)
|
|
347
|
+
let bytesRead: number
|
|
348
|
+
do {
|
|
349
|
+
bytesRead = fs.readSync(fd, buffer, 0, chunkSize, null)
|
|
350
|
+
if (bytesRead > 0)
|
|
351
|
+
yield buffer.subarray(0, bytesRead)
|
|
352
|
+
} while (bytesRead === chunkSize)
|
|
353
|
+
} finally {
|
|
354
|
+
fs.closeSync(fd)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async *itBuff(chunkSize: number = 64 * 1024, flags: string | number = 'r', mode?: fs.Mode) {
|
|
358
|
+
const fd = await fp.open(this.isAt, flags, mode)
|
|
359
|
+
try {
|
|
360
|
+
const buffer = Buffer.alloc(chunkSize)
|
|
361
|
+
let bytesRead: number
|
|
362
|
+
do {
|
|
363
|
+
const readResult = await fd.read(buffer, 0, chunkSize, null)
|
|
364
|
+
bytesRead = readResult.bytesRead
|
|
365
|
+
if (bytesRead > 0)
|
|
366
|
+
yield buffer.subarray(0, bytesRead)
|
|
367
|
+
} while (bytesRead === chunkSize)
|
|
368
|
+
} finally {
|
|
369
|
+
await fd.close()
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async *itLines(options: Parameters<typeof fs.createReadStream>[1] = { encoding: 'utf-8' }) {
|
|
373
|
+
const readStream = fs.createReadStream(this.isAt, options)
|
|
374
|
+
const rlInterface = rl.createInterface({ input: readStream, crlfDelay: Infinity })
|
|
375
|
+
try {
|
|
376
|
+
for await (const line of rlInterface)
|
|
377
|
+
yield line
|
|
378
|
+
} finally {
|
|
379
|
+
rlInterface.close()
|
|
380
|
+
readStream.destroy()
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
computeHashSync(algorithm?: string, options?: cr.HashOptions): Buffer
|
|
384
|
+
computeHashSync(algorithm?: string, options?: cr.HashOptions, encoding?: BufferEncoding): string
|
|
385
|
+
computeHashSync(algorithm = "sha256", options?: cr.HashOptions, encoding?: BufferEncoding): Buffer | string {
|
|
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
|
+
}
|
|
435
|
+
}
|
|
436
|
+
sameAsSync(other: File) {
|
|
437
|
+
if (this.isAt === other.isAt)
|
|
438
|
+
return true
|
|
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
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
deleteSync() {
|
|
453
|
+
using _ = this.initChangeSync()
|
|
454
|
+
fs.rmSync(this.isAt, { force: true })
|
|
455
|
+
}
|
|
456
|
+
async delete() {
|
|
457
|
+
using _ = await this.initChange()
|
|
458
|
+
await fp.rm(this.isAt, { force: true })
|
|
459
|
+
}
|
|
460
|
+
moveSync(into: Folder) {
|
|
461
|
+
using _ = this.initChangeSync()
|
|
462
|
+
const newPath = into.join(this.name)
|
|
463
|
+
fs.renameSync(this.isAt, newPath)
|
|
464
|
+
this.pointsTo = newPath
|
|
465
|
+
}
|
|
466
|
+
async move(into: Folder) {
|
|
467
|
+
using _ = await this.initChange()
|
|
468
|
+
const newPath = into.join(this.name)
|
|
469
|
+
await fp.rename(this.isAt, newPath)
|
|
470
|
+
this.pointsTo = newPath
|
|
471
|
+
}
|
|
472
|
+
copySync(into: Folder): this {
|
|
473
|
+
const newPath = into.join(this.name)
|
|
474
|
+
fs.copyFileSync(this.isAt, newPath)
|
|
475
|
+
return new File(newPath, false) as this
|
|
476
|
+
}
|
|
477
|
+
async copy(into: Folder): Promise<this> {
|
|
478
|
+
const newPath = into.join(this.name)
|
|
479
|
+
await fp.copyFile(this.isAt, newPath)
|
|
480
|
+
return new File(newPath, false) as this
|
|
481
|
+
}
|
|
482
|
+
renameSync(to: string) {
|
|
483
|
+
using _ = this.initChangeSync()
|
|
484
|
+
const newPath = this.parent().join(to)
|
|
485
|
+
fs.renameSync(this.isAt, newPath)
|
|
486
|
+
this.pointsTo = newPath
|
|
487
|
+
}
|
|
488
|
+
async rename(to: string) {
|
|
489
|
+
using _ = await this.initChange()
|
|
490
|
+
const newPath = this.parent().join(to)
|
|
491
|
+
await fp.rename(this.isAt, newPath)
|
|
492
|
+
this.pointsTo = newPath
|
|
493
|
+
}
|
|
494
|
+
resurrectSync() {
|
|
495
|
+
using _ = this.initChangeSync()
|
|
496
|
+
fs.writeFileSync(this.isAt, "")
|
|
497
|
+
}
|
|
498
|
+
async resurrect() {
|
|
499
|
+
using _ = await this.initChange()
|
|
500
|
+
await fp.writeFile(this.isAt, "")
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function entry() { return new File(process.argv[1]!, false) }
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
export class Folder extends Road {
|
|
509
|
+
static async create(at: string): Promise<Folder> {
|
|
510
|
+
try {
|
|
511
|
+
await fp.access(at, fs.constants.F_OK)
|
|
512
|
+
} catch {
|
|
513
|
+
await fp.mkdir(at, { recursive: true })
|
|
514
|
+
}
|
|
515
|
+
return new Folder(at, false)
|
|
516
|
+
}
|
|
517
|
+
static createSync(at: string): Folder {
|
|
518
|
+
try {
|
|
519
|
+
fs.accessSync(at, fs.constants.F_OK)
|
|
520
|
+
} catch {
|
|
521
|
+
fs.mkdirSync(at, { recursive: true })
|
|
522
|
+
}
|
|
523
|
+
return new Folder(at, false)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
join(...paths: string[]) {
|
|
527
|
+
return ph.join(this.isAt, ...paths)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
itSync(): Iterable<Road>
|
|
531
|
+
itSync<T extends Road>(expectedType: new () => T): Iterable<T>
|
|
532
|
+
*itSync<T extends Road>(expectedType?: new () => T): Iterable<Road> | Iterable<T> {
|
|
533
|
+
for (const entry of fs.readdirSync(this.isAt)) {
|
|
534
|
+
const road = factorySync(this.join(entry))
|
|
535
|
+
if (!expectedType || road instanceof expectedType)
|
|
536
|
+
yield road
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
it(): AsyncIterable<Road>
|
|
540
|
+
it<T extends Road>(expectedType: new () => T): AsyncIterable<T>
|
|
541
|
+
async *it<T extends Road>(expectedType?: new () => T): AsyncIterable<Road> | AsyncIterable<T> {
|
|
542
|
+
for (const entry of await fp.readdir(this.isAt)) {
|
|
543
|
+
const road = await factory(this.join(entry))
|
|
544
|
+
if (!expectedType || road instanceof expectedType)
|
|
545
|
+
yield road
|
|
546
|
+
}
|
|
547
|
+
}
|
|
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
|
+
async list(): Promise<Road[]>
|
|
557
|
+
async list<T extends Road>(_expectedType: new () => T): Promise<T[]>
|
|
558
|
+
async list<T extends Road>(_expectedType?: new () => T): Promise<Road[] | T[]> {
|
|
559
|
+
const entries = (await fp.readdir(this.isAt)).map(async entry => factory(this.join(entry)))
|
|
560
|
+
const resolvedEntries = await Promise.all(entries)
|
|
561
|
+
if (!_expectedType)
|
|
562
|
+
return resolvedEntries
|
|
563
|
+
return resolvedEntries.filter(entry => entry instanceof _expectedType) as unknown as T[]
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
findSync(name: string): Road | null
|
|
567
|
+
findSync<T extends Road>(name: string, _expectedType: new () => T): T | null
|
|
568
|
+
findSync<T extends Road>(name: string, _expectedType?: new () => T): Road | T | null {
|
|
569
|
+
try {
|
|
570
|
+
const found = factorySync(this.join(name))
|
|
571
|
+
if (!_expectedType)
|
|
572
|
+
return found
|
|
573
|
+
if (found instanceof _expectedType)
|
|
574
|
+
return found as T
|
|
575
|
+
return null
|
|
576
|
+
} catch {
|
|
577
|
+
return null
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async find(name: string): Promise<Road | null>
|
|
582
|
+
async find<T extends Road>(name: string, _expectedType: new () => T): Promise<T | null>
|
|
583
|
+
async find<T extends Road>(name: string, _expectedType?: new () => T): Promise<Road | T | null> {
|
|
584
|
+
try {
|
|
585
|
+
await fp.access(this.join(name), fs.constants.F_OK)
|
|
586
|
+
const found = await factory(this.join(name))
|
|
587
|
+
if (!_expectedType)
|
|
588
|
+
return found
|
|
589
|
+
if (found instanceof _expectedType)
|
|
590
|
+
return found as T
|
|
591
|
+
return null
|
|
592
|
+
} catch {
|
|
593
|
+
return null
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
addSync<T extends Road>(name: string, createable: { createSync: (at: string) => T }): T {
|
|
598
|
+
const newPath = this.join(name)
|
|
599
|
+
createable.createSync(newPath)
|
|
600
|
+
return factorySync(newPath) as unknown as T
|
|
601
|
+
}
|
|
602
|
+
async add<T extends Road>(name: string, createable: { create: (at: string) => Promise<T> }): Promise<T> {
|
|
603
|
+
const newPath = this.join(name)
|
|
604
|
+
await createable.create(newPath)
|
|
605
|
+
return factory(newPath) as unknown as Promise<T>
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
deleteSync(options: fs.RmOptions = { recursive: true }) {
|
|
609
|
+
using _ = this.initChangeSync()
|
|
610
|
+
fs.rmSync(this.isAt, options)
|
|
611
|
+
}
|
|
612
|
+
async delete(options: fs.RmOptions = { recursive: true }) {
|
|
613
|
+
using _ = await this.initChange()
|
|
614
|
+
await fp.rm(this.isAt, options)
|
|
615
|
+
}
|
|
616
|
+
moveSync(into: Folder) {
|
|
617
|
+
using _ = this.initChangeSync()
|
|
618
|
+
const newPath = into.join(this.name)
|
|
619
|
+
fs.renameSync(this.isAt, newPath)
|
|
620
|
+
this.pointsTo = newPath
|
|
621
|
+
}
|
|
622
|
+
async move(into: Folder) {
|
|
623
|
+
using _ = await this.initChange()
|
|
624
|
+
const newPath = into.join(this.name)
|
|
625
|
+
await fp.rename(this.isAt, newPath)
|
|
626
|
+
this.pointsTo = newPath
|
|
627
|
+
}
|
|
628
|
+
copySync(into: Folder): this {
|
|
629
|
+
const newPath = into.join(this.name)
|
|
630
|
+
fs.cpSync(this.isAt, newPath, { recursive: true })
|
|
631
|
+
return new Folder(newPath, false) as this
|
|
632
|
+
}
|
|
633
|
+
async copy(into: Folder): Promise<this> {
|
|
634
|
+
const newPath = into.join(this.name)
|
|
635
|
+
await fp.cp(this.isAt, newPath, { recursive: true })
|
|
636
|
+
return new Folder(newPath, false) as this
|
|
637
|
+
}
|
|
638
|
+
renameSync(to: string) {
|
|
639
|
+
using _ = this.initChangeSync()
|
|
640
|
+
const newPath = this.parent().join(to)
|
|
641
|
+
fs.renameSync(this.isAt, newPath)
|
|
642
|
+
this.pointsTo = newPath
|
|
643
|
+
}
|
|
644
|
+
async rename(to: string) {
|
|
645
|
+
using _ = await this.initChange()
|
|
646
|
+
const newPath = this.parent().join(to)
|
|
647
|
+
await fp.rename(this.isAt, newPath)
|
|
648
|
+
this.pointsTo = newPath
|
|
649
|
+
}
|
|
650
|
+
resurrectSync() {
|
|
651
|
+
using _ = this.initChangeSync()
|
|
652
|
+
fs.mkdirSync(this.isAt, { recursive: true })
|
|
653
|
+
}
|
|
654
|
+
async resurrect() {
|
|
655
|
+
using _ = await this.initChange()
|
|
656
|
+
await fp.mkdir(this.isAt, { recursive: true })
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export function sysRoot() { return new Folder(ph.parse(process.cwd()).root, false) }
|
|
661
|
+
export function home() { return new Folder(os.homedir(), false) }
|
|
662
|
+
export function tmp() { return new Folder(os.tmpdir(), false) }
|
|
663
|
+
export function here() { return new Folder(process.cwd(), false) }
|
|
664
|
+
export { Folder as Dir, Folder as Directory, Folder as Dict, Folder as Dictionary }
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
export class SymbolicLink extends Road {
|
|
669
|
+
static async create(at: string, target: Road) {
|
|
670
|
+
try {
|
|
671
|
+
await fp.access(at, fs.constants.F_OK)
|
|
672
|
+
} catch {
|
|
673
|
+
await fp.symlink(target.isAt, at)
|
|
674
|
+
}
|
|
675
|
+
return new SymbolicLink(at, false)
|
|
676
|
+
}
|
|
677
|
+
static createSync(_at: string, _target: Road) {
|
|
678
|
+
try {
|
|
679
|
+
fs.accessSync(_at, fs.constants.F_OK)
|
|
680
|
+
} catch {
|
|
681
|
+
fs.symlinkSync(_target.isAt, _at)
|
|
682
|
+
}
|
|
683
|
+
return new SymbolicLink(_at, false)
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
targetSync() {
|
|
687
|
+
return factorySync(ph.resolve(ph.dirname(this.isAt), fs.readlinkSync(this.isAt)))
|
|
688
|
+
}
|
|
689
|
+
async target() {
|
|
690
|
+
return factory(ph.resolve(ph.dirname(this.isAt), await fp.readlink(this.isAt)))
|
|
691
|
+
}
|
|
692
|
+
retargetSync(_newTarget: Road) {
|
|
693
|
+
this.deleteSync()
|
|
694
|
+
fs.symlinkSync(_newTarget.isAt, this.isAt)
|
|
695
|
+
}
|
|
696
|
+
async retarget(_newTarget: Road) {
|
|
697
|
+
await this.delete()
|
|
698
|
+
return fp.symlink(_newTarget.isAt, this.isAt)
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
deleteSync() {
|
|
702
|
+
using _ = this.initChangeSync()
|
|
703
|
+
fs.unlinkSync(this.isAt)
|
|
704
|
+
}
|
|
705
|
+
async delete() {
|
|
706
|
+
using _ = await this.initChange()
|
|
707
|
+
await fp.unlink(this.isAt)
|
|
708
|
+
}
|
|
709
|
+
moveSync(_into: Folder) {
|
|
710
|
+
using _ = this.initChangeSync()
|
|
711
|
+
const newPath = _into.join(this.name)
|
|
712
|
+
fs.renameSync(this.isAt, newPath)
|
|
713
|
+
this.pointsTo = newPath
|
|
714
|
+
}
|
|
715
|
+
async move(_into: Folder) {
|
|
716
|
+
using _ = await this.initChange()
|
|
717
|
+
const newPath = _into.join(this.name)
|
|
718
|
+
await fp.rename(this.isAt, newPath)
|
|
719
|
+
this.pointsTo = newPath
|
|
720
|
+
}
|
|
721
|
+
copySync(_into: Folder): this {
|
|
722
|
+
const newPath = _into.join(this.name)
|
|
723
|
+
const target = this.targetSync()
|
|
724
|
+
fs.symlinkSync(target.isAt, newPath)
|
|
725
|
+
return new SymbolicLink(newPath, false) as this
|
|
726
|
+
}
|
|
727
|
+
async copy(_into: Folder): Promise<this> {
|
|
728
|
+
const newPath = _into.join(this.name)
|
|
729
|
+
const target = await this.target()
|
|
730
|
+
await fp.symlink(target.isAt, newPath)
|
|
731
|
+
return new SymbolicLink(newPath, false) as this
|
|
732
|
+
}
|
|
733
|
+
renameSync(_to: string) {
|
|
734
|
+
using _ = this.initChangeSync()
|
|
735
|
+
const newPath = this.parent().join(_to)
|
|
736
|
+
fs.renameSync(this.isAt, newPath)
|
|
737
|
+
this.pointsTo = newPath
|
|
738
|
+
}
|
|
739
|
+
async rename(_to: string) {
|
|
740
|
+
using _ = await this.initChange()
|
|
741
|
+
const newPath = this.parent().join(_to)
|
|
742
|
+
await fp.rename(this.isAt, newPath)
|
|
743
|
+
this.pointsTo = newPath
|
|
744
|
+
}
|
|
745
|
+
resurrectSync() {
|
|
746
|
+
using _ = this.initChangeSync()
|
|
747
|
+
const target = this.targetSync()
|
|
748
|
+
fs.symlinkSync(target.isAt, this.isAt)
|
|
749
|
+
}
|
|
750
|
+
async resurrect() {
|
|
751
|
+
using _ = await this.initChange()
|
|
752
|
+
const target = await this.target()
|
|
753
|
+
await fp.symlink(target.isAt, this.isAt)
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
export { SymbolicLink as Symlink }
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
export abstract class UnusableRoad extends Road {
|
|
761
|
+
override readonly mutable: boolean = false // Modification is most likely to cause system issues (e.g. deleting a device file)
|
|
762
|
+
constructor(_at: string, typeCheck: boolean) {
|
|
763
|
+
super(_at, typeCheck)
|
|
764
|
+
Object.freeze(this)
|
|
765
|
+
}
|
|
766
|
+
error(): never { throw new RdErr(`${this.constructor.name} at '${this.isAt}' is a system-level resource thus intentionally made immutable.`) }
|
|
767
|
+
override initChangeSync(): never { return this.error() }
|
|
768
|
+
override async initChange(): Promise<never> { return this.error() }
|
|
769
|
+
override deleteSync(): never { return this.error() }
|
|
770
|
+
override async delete(): Promise<never> { return this.error() }
|
|
771
|
+
override moveSync(): never { return this.error() }
|
|
772
|
+
override async move(): Promise<never> { return this.error() }
|
|
773
|
+
override copySync(): never { return this.error() }
|
|
774
|
+
override async copy(): Promise<never> { return this.error() }
|
|
775
|
+
override renameSync(): never { return this.error() }
|
|
776
|
+
override async rename(): Promise<never> { return this.error() }
|
|
777
|
+
override resurrectSync(): never { return this.error() }
|
|
778
|
+
override async resurrect(): Promise<never> { return this.error() }
|
|
779
|
+
}
|
|
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
|
+
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
export let finalizer: FinalizationRegistry<string> | null = null
|
|
788
|
+
export let toDelete: Set<string> | null = null
|
|
789
|
+
let exitHandlerRegistered = false
|
|
790
|
+
/**
|
|
791
|
+
* 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
|
+
*/
|
|
795
|
+
export function forceCleanupToDelete() {
|
|
796
|
+
for (const path of toDelete ?? [])
|
|
797
|
+
try { fs.rmSync(path, { force: true, recursive: true }) } catch {}
|
|
798
|
+
toDelete?.clear()
|
|
799
|
+
toDelete = null
|
|
800
|
+
finalizer = null
|
|
801
|
+
if (exitHandlerRegistered)
|
|
802
|
+
process.off('exit', forceCleanupToDelete)
|
|
803
|
+
exitHandlerRegistered = false
|
|
804
|
+
}
|
|
805
|
+
export function registerToCleanup(self: Road) {
|
|
806
|
+
if (!finalizer)
|
|
807
|
+
finalizer = new FinalizationRegistry<string>(p => { try { fs.rmSync(p, { force: true, recursive: true }) } catch {}; toDelete?.delete(p) })
|
|
808
|
+
if (!toDelete)
|
|
809
|
+
toDelete = new Set()
|
|
810
|
+
if (!exitHandlerRegistered) {
|
|
811
|
+
process.once('exit', forceCleanupToDelete)
|
|
812
|
+
exitHandlerRegistered = true
|
|
813
|
+
}
|
|
814
|
+
toDelete.add(self.isAt)
|
|
815
|
+
finalizer.register(self, self.isAt, self)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
export function Temp<T extends Road>(createable: { createSync: (at: string) => T }, autoCleanup: boolean): T & Disposable & AsyncDisposable {
|
|
820
|
+
const t = createable.createSync(tmp().join(`instrumentality@${cr.randomUUID()}`))
|
|
821
|
+
if (autoCleanup)
|
|
822
|
+
registerToCleanup(t)
|
|
823
|
+
return Object.freeze(Object.assign(t, {
|
|
824
|
+
[Symbol.dispose]() { try { t.deleteSync() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) },
|
|
825
|
+
async [Symbol.asyncDispose]() { try { await t.delete() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) }
|
|
826
|
+
}))
|
|
827
|
+
}
|