@anabranch/fs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/esm/_dnt.polyfills.d.ts +7 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +1 -0
- package/esm/anabranch/index.d.ts +44 -0
- package/esm/anabranch/index.d.ts.map +1 -0
- package/esm/anabranch/index.js +41 -0
- package/esm/anabranch/streams/channel.d.ts +15 -0
- package/esm/anabranch/streams/channel.d.ts.map +1 -0
- package/esm/anabranch/streams/channel.js +122 -0
- package/esm/anabranch/streams/source.d.ts +68 -0
- package/esm/anabranch/streams/source.d.ts.map +1 -0
- package/esm/anabranch/streams/source.js +72 -0
- package/esm/anabranch/streams/stream.d.ts +431 -0
- package/esm/anabranch/streams/stream.d.ts.map +1 -0
- package/esm/anabranch/streams/stream.js +625 -0
- package/esm/anabranch/streams/task.d.ts +117 -0
- package/esm/anabranch/streams/task.d.ts.map +1 -0
- package/esm/anabranch/streams/task.js +416 -0
- package/esm/anabranch/streams/util.d.ts +33 -0
- package/esm/anabranch/streams/util.d.ts.map +1 -0
- package/esm/anabranch/streams/util.js +18 -0
- package/esm/fs/dir.d.ts +17 -0
- package/esm/fs/dir.d.ts.map +1 -0
- package/esm/fs/dir.js +90 -0
- package/esm/fs/errors.d.ts +64 -0
- package/esm/fs/errors.d.ts.map +1 -0
- package/esm/fs/errors.js +125 -0
- package/esm/fs/glob_match.d.ts +15 -0
- package/esm/fs/glob_match.d.ts.map +1 -0
- package/esm/fs/glob_match.js +73 -0
- package/esm/fs/index.d.ts +38 -0
- package/esm/fs/index.d.ts.map +1 -0
- package/esm/fs/index.js +31 -0
- package/esm/fs/read.d.ts +22 -0
- package/esm/fs/read.d.ts.map +1 -0
- package/esm/fs/read.js +75 -0
- package/esm/fs/types.d.ts +67 -0
- package/esm/fs/types.d.ts.map +1 -0
- package/esm/fs/types.js +1 -0
- package/esm/fs/watch.d.ts +17 -0
- package/esm/fs/watch.d.ts.map +1 -0
- package/esm/fs/watch.js +37 -0
- package/esm/fs/write.d.ts +16 -0
- package/esm/fs/write.d.ts.map +1 -0
- package/esm/fs/write.js +42 -0
- package/esm/package.json +3 -0
- package/package.json +24 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Promisable, Result } from "./util.js";
|
|
2
|
+
/**
|
|
3
|
+
* A single async task with error-aware utilities like retries and timeouts.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Task<T, E> {
|
|
6
|
+
private runTask;
|
|
7
|
+
private constructor();
|
|
8
|
+
/**
|
|
9
|
+
* Creates a {@link Task} from an async function.
|
|
10
|
+
* The function receives an optional AbortSignal that is active when
|
|
11
|
+
* the task is run with a signal via {@link withSignal} or {@link run}.
|
|
12
|
+
*
|
|
13
|
+
* Note: the error type `E` is unchecked and represents the expected error
|
|
14
|
+
* shape rather than a runtime guarantee.
|
|
15
|
+
*/
|
|
16
|
+
static of<R, E>(task: (signal?: AbortSignal) => Promise<R>): Task<R, E>;
|
|
17
|
+
/**
|
|
18
|
+
* Wraps the task with an external abort signal.
|
|
19
|
+
*/
|
|
20
|
+
withSignal(signal: AbortSignal): Task<T, E>;
|
|
21
|
+
/**
|
|
22
|
+
* Executes the task and returns a tagged result instead of throwing.
|
|
23
|
+
*/
|
|
24
|
+
result(): Promise<Result<T, E>>;
|
|
25
|
+
/**
|
|
26
|
+
* Executes the task and resolves the value or throws the error.
|
|
27
|
+
*/
|
|
28
|
+
run(): Promise<T>;
|
|
29
|
+
/**
|
|
30
|
+
* Maps the successful value. Errors are passed through unchanged.
|
|
31
|
+
*/
|
|
32
|
+
map<U>(fn: (value: T) => Promisable<U>): Task<U, E>;
|
|
33
|
+
/**
|
|
34
|
+
* Chains another task based on the successful value.
|
|
35
|
+
*/
|
|
36
|
+
flatMap<U>(fn: (value: T) => Task<U, E>): Task<U, E>;
|
|
37
|
+
/**
|
|
38
|
+
* Maps the error value. Successful values are passed through unchanged.
|
|
39
|
+
*/
|
|
40
|
+
mapErr<F>(fn: (error: E) => Promisable<F>): Task<T, F>;
|
|
41
|
+
/**
|
|
42
|
+
* Runs a side effect on the successful value.
|
|
43
|
+
*/
|
|
44
|
+
tap(fn: (value: T) => Promisable<void>): Task<T, E>;
|
|
45
|
+
/**
|
|
46
|
+
* Runs a side effect on the error value.
|
|
47
|
+
*/
|
|
48
|
+
tapErr(fn: (error: E) => Promisable<void>): Task<T, E>;
|
|
49
|
+
/**
|
|
50
|
+
* Recovers from errors by mapping them to a successful value.
|
|
51
|
+
*/
|
|
52
|
+
recover<U>(fn: (error: E) => Promisable<U>): Task<T | U, never>;
|
|
53
|
+
/**
|
|
54
|
+
* Recovers from specific error types by mapping them to a successful value.
|
|
55
|
+
*/
|
|
56
|
+
recoverWhen<E2 extends E, U>(guard: (error: E) => error is E2, fn: (error: E2) => Promisable<U>): Task<T | U, Exclude<E, E2>>;
|
|
57
|
+
/**
|
|
58
|
+
* Retries the task when the predicate returns true.
|
|
59
|
+
*/
|
|
60
|
+
retry(options: {
|
|
61
|
+
attempts: number;
|
|
62
|
+
delay?: number | ((attempt: number, error: E) => number);
|
|
63
|
+
when?: (error: E) => boolean;
|
|
64
|
+
}): Task<T, E>;
|
|
65
|
+
/**
|
|
66
|
+
* Fails if the task does not complete within the specified time.
|
|
67
|
+
*/
|
|
68
|
+
timeout(ms: number, error?: E): Task<T, E>;
|
|
69
|
+
/**
|
|
70
|
+
* Runs multiple tasks and collects results. Rejects on the first failure.
|
|
71
|
+
*/
|
|
72
|
+
static all<T, E>(tasks: Task<T, E>[]): Task<T[], E>;
|
|
73
|
+
/**
|
|
74
|
+
* Runs multiple tasks and collects all results without throwing.
|
|
75
|
+
*/
|
|
76
|
+
static allSettled<T, E>(tasks: Task<T, E>[]): Task<Result<T, E>[], never>;
|
|
77
|
+
/**
|
|
78
|
+
* Runs tasks concurrently and resolves with the first settled result.
|
|
79
|
+
*
|
|
80
|
+
* Note: If all tasks fail, the errors array is in completion order (the order
|
|
81
|
+
* tasks finished), not task order. This is nondeterministic due to
|
|
82
|
+
* concurrent execution.
|
|
83
|
+
*/
|
|
84
|
+
static race<T, E>(tasks: Task<T, E>[]): Task<Result<T, E[]>, never>;
|
|
85
|
+
/**
|
|
86
|
+
* Acquires a resource, runs a task that uses it, and releases it regardless
|
|
87
|
+
* of success or failure. Useful for resource lifecycle management when the
|
|
88
|
+
* use computation is a composed Task chain.
|
|
89
|
+
*
|
|
90
|
+
* The `acquire` function receives an optional AbortSignal that is active when
|
|
91
|
+
* the task is run with a signal. The `release` function always runs and does
|
|
92
|
+
* not receive a signal — cleanup should not be cancellable.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* const task = Task.acquireRelease({
|
|
97
|
+
* acquire: (signal) => db.connect(signal),
|
|
98
|
+
* release: (conn) => conn.close(),
|
|
99
|
+
* use: (conn) => Task.of(() => query(conn))
|
|
100
|
+
* .retry({ attempts: 3 })
|
|
101
|
+
* .timeout(5_000),
|
|
102
|
+
* });
|
|
103
|
+
*
|
|
104
|
+
* const result = await task.result();
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
static acquireRelease<R, T, E>({ acquire, release, use, }: {
|
|
108
|
+
acquire: (signal?: AbortSignal) => Promise<R>;
|
|
109
|
+
release: (resource: R) => Promise<void>;
|
|
110
|
+
use: (resource: R) => Task<T, E>;
|
|
111
|
+
}): Task<T, E>;
|
|
112
|
+
protected runWithSignal(signal?: AbortSignal): Promise<T>;
|
|
113
|
+
private delayWithSignal;
|
|
114
|
+
private mergeSignals;
|
|
115
|
+
private static mergeExternalSignals;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=task.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../../src/anabranch/streams/task.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single async task with error-aware utilities like retries and timeouts.
|
|
3
|
+
*/
|
|
4
|
+
export class Task {
|
|
5
|
+
constructor(task) {
|
|
6
|
+
Object.defineProperty(this, "runTask", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
writable: true,
|
|
10
|
+
value: void 0
|
|
11
|
+
});
|
|
12
|
+
this.runTask = task;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a {@link Task} from an async function.
|
|
16
|
+
* The function receives an optional AbortSignal that is active when
|
|
17
|
+
* the task is run with a signal via {@link withSignal} or {@link run}.
|
|
18
|
+
*
|
|
19
|
+
* Note: the error type `E` is unchecked and represents the expected error
|
|
20
|
+
* shape rather than a runtime guarantee.
|
|
21
|
+
*/
|
|
22
|
+
static of(task) {
|
|
23
|
+
return new Task(task);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Wraps the task with an external abort signal.
|
|
27
|
+
*/
|
|
28
|
+
withSignal(signal) {
|
|
29
|
+
return new Task((innerSignal) => {
|
|
30
|
+
const { signal: merged, cleanup } = this.mergeSignals(signal, innerSignal);
|
|
31
|
+
return this.runWithSignal(merged).finally(cleanup);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Executes the task and returns a tagged result instead of throwing.
|
|
36
|
+
*/
|
|
37
|
+
async result() {
|
|
38
|
+
try {
|
|
39
|
+
const value = await this.run();
|
|
40
|
+
return { type: "success", value };
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return { type: "error", error: error };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Executes the task and resolves the value or throws the error.
|
|
48
|
+
*/
|
|
49
|
+
run() {
|
|
50
|
+
return this.runWithSignal();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Maps the successful value. Errors are passed through unchanged.
|
|
54
|
+
*/
|
|
55
|
+
map(fn) {
|
|
56
|
+
return new Task(async (signal) => {
|
|
57
|
+
const value = await this.runWithSignal(signal);
|
|
58
|
+
return await fn(value);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Chains another task based on the successful value.
|
|
63
|
+
*/
|
|
64
|
+
flatMap(fn) {
|
|
65
|
+
return new Task(async (signal) => {
|
|
66
|
+
const value = await this.runWithSignal(signal);
|
|
67
|
+
const next = fn(value);
|
|
68
|
+
return await next.runWithSignal(signal);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Maps the error value. Successful values are passed through unchanged.
|
|
73
|
+
*/
|
|
74
|
+
mapErr(fn) {
|
|
75
|
+
return new Task(async (signal) => {
|
|
76
|
+
try {
|
|
77
|
+
return await this.runWithSignal(signal);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throw await fn(error);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Runs a side effect on the successful value.
|
|
86
|
+
*/
|
|
87
|
+
tap(fn) {
|
|
88
|
+
return new Task(async (signal) => {
|
|
89
|
+
const value = await this.runWithSignal(signal);
|
|
90
|
+
await fn(value);
|
|
91
|
+
return value;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Runs a side effect on the error value.
|
|
96
|
+
*/
|
|
97
|
+
tapErr(fn) {
|
|
98
|
+
return new Task(async (signal) => {
|
|
99
|
+
try {
|
|
100
|
+
return await this.runWithSignal(signal);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
await fn(error);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Recovers from errors by mapping them to a successful value.
|
|
110
|
+
*/
|
|
111
|
+
recover(fn) {
|
|
112
|
+
return new Task(async (signal) => {
|
|
113
|
+
try {
|
|
114
|
+
return await this.runWithSignal(signal);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return await fn(error);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Recovers from specific error types by mapping them to a successful value.
|
|
123
|
+
*/
|
|
124
|
+
recoverWhen(guard, fn) {
|
|
125
|
+
return new Task(async (signal) => {
|
|
126
|
+
try {
|
|
127
|
+
return await this.runWithSignal(signal);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (guard(error)) {
|
|
131
|
+
return await fn(error);
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Retries the task when the predicate returns true.
|
|
139
|
+
*/
|
|
140
|
+
retry(options) {
|
|
141
|
+
const { attempts, delay, when } = options;
|
|
142
|
+
return new Task(async (signal) => {
|
|
143
|
+
let lastError;
|
|
144
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
145
|
+
try {
|
|
146
|
+
return await this.runWithSignal(signal);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
lastError = error;
|
|
150
|
+
if (when && !when(lastError)) {
|
|
151
|
+
throw lastError;
|
|
152
|
+
}
|
|
153
|
+
if (attempt + 1 < attempts) {
|
|
154
|
+
const delayMs = typeof delay === "function"
|
|
155
|
+
? delay(attempt, lastError)
|
|
156
|
+
: (delay ?? 0);
|
|
157
|
+
if (delayMs > 0) {
|
|
158
|
+
await this.delayWithSignal(delayMs, signal);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
throw lastError;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Fails if the task does not complete within the specified time.
|
|
168
|
+
*/
|
|
169
|
+
timeout(ms, error) {
|
|
170
|
+
return new Task(async (signal) => {
|
|
171
|
+
let timeoutId;
|
|
172
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
173
|
+
timeoutId = setTimeout(() => {
|
|
174
|
+
reject(error ?? new Error(`Timeout after ${ms}ms`));
|
|
175
|
+
}, ms);
|
|
176
|
+
});
|
|
177
|
+
try {
|
|
178
|
+
return await Promise.race([
|
|
179
|
+
this.runWithSignal(signal),
|
|
180
|
+
timeoutPromise,
|
|
181
|
+
]);
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
if (timeoutId !== undefined) {
|
|
185
|
+
clearTimeout(timeoutId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Runs multiple tasks and collects results. Rejects on the first failure.
|
|
192
|
+
*/
|
|
193
|
+
static all(tasks) {
|
|
194
|
+
return new Task(async (signal) => {
|
|
195
|
+
const runTask = signal
|
|
196
|
+
? (task) => task.withSignal(signal).run()
|
|
197
|
+
: (task) => task.run();
|
|
198
|
+
const results = await Promise.all(tasks.map(runTask));
|
|
199
|
+
return results;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Runs multiple tasks and collects all results without throwing.
|
|
204
|
+
*/
|
|
205
|
+
static allSettled(tasks) {
|
|
206
|
+
return new Task(async (signal) => {
|
|
207
|
+
const getResult = signal
|
|
208
|
+
? (task) => task.withSignal(signal).result()
|
|
209
|
+
: (task) => task.result();
|
|
210
|
+
const results = await Promise.all(tasks.map(getResult));
|
|
211
|
+
return results;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Runs tasks concurrently and resolves with the first settled result.
|
|
216
|
+
*
|
|
217
|
+
* Note: If all tasks fail, the errors array is in completion order (the order
|
|
218
|
+
* tasks finished), not task order. This is nondeterministic due to
|
|
219
|
+
* concurrent execution.
|
|
220
|
+
*/
|
|
221
|
+
static race(tasks) {
|
|
222
|
+
return new Task(async (signal) => {
|
|
223
|
+
if (tasks.length === 0) {
|
|
224
|
+
throw new Error("Task.race requires at least one task");
|
|
225
|
+
}
|
|
226
|
+
const errors = [];
|
|
227
|
+
const controllers = tasks.map(() => new AbortController());
|
|
228
|
+
const cleanup = new Map();
|
|
229
|
+
const merged = tasks.map((task, index) => {
|
|
230
|
+
if (!signal) {
|
|
231
|
+
return task.withSignal(controllers[index].signal);
|
|
232
|
+
}
|
|
233
|
+
const mergedSignals = Task.mergeExternalSignals(signal, controllers[index].signal);
|
|
234
|
+
cleanup.set(index, mergedSignals.cleanup);
|
|
235
|
+
return task.withSignal(mergedSignals.signal);
|
|
236
|
+
});
|
|
237
|
+
return await new Promise((resolve, reject) => {
|
|
238
|
+
let remaining = merged.length;
|
|
239
|
+
let resolved = false;
|
|
240
|
+
const abortListener = signal
|
|
241
|
+
? () => reject(signal.reason ?? new Error("Task aborted"))
|
|
242
|
+
: undefined;
|
|
243
|
+
if (abortListener && signal) {
|
|
244
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
245
|
+
}
|
|
246
|
+
merged.forEach((task, index) => {
|
|
247
|
+
task.result().then((value) => {
|
|
248
|
+
if (resolved) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (value.type === "success") {
|
|
252
|
+
resolved = true;
|
|
253
|
+
controllers.forEach((controller, controllerIndex) => {
|
|
254
|
+
if (controllerIndex !== index) {
|
|
255
|
+
controller.abort();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
cleanup.forEach((cleanupFn) => cleanupFn());
|
|
259
|
+
if (abortListener && signal) {
|
|
260
|
+
signal.removeEventListener("abort", abortListener);
|
|
261
|
+
}
|
|
262
|
+
resolve({ type: "success", value: value.value });
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
errors.push(value.error);
|
|
266
|
+
remaining -= 1;
|
|
267
|
+
if (remaining === 0) {
|
|
268
|
+
resolved = true;
|
|
269
|
+
cleanup.forEach((cleanupFn) => cleanupFn());
|
|
270
|
+
if (abortListener && signal) {
|
|
271
|
+
signal.removeEventListener("abort", abortListener);
|
|
272
|
+
}
|
|
273
|
+
resolve({ type: "error", error: errors });
|
|
274
|
+
controllers.forEach((controller) => controller.abort());
|
|
275
|
+
}
|
|
276
|
+
}).catch((error) => {
|
|
277
|
+
if (resolved) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
resolved = true;
|
|
281
|
+
cleanup.forEach((cleanupFn) => cleanupFn());
|
|
282
|
+
if (abortListener && signal) {
|
|
283
|
+
signal.removeEventListener("abort", abortListener);
|
|
284
|
+
}
|
|
285
|
+
controllers.forEach((controller) => controller.abort());
|
|
286
|
+
reject(error);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Acquires a resource, runs a task that uses it, and releases it regardless
|
|
294
|
+
* of success or failure. Useful for resource lifecycle management when the
|
|
295
|
+
* use computation is a composed Task chain.
|
|
296
|
+
*
|
|
297
|
+
* The `acquire` function receives an optional AbortSignal that is active when
|
|
298
|
+
* the task is run with a signal. The `release` function always runs and does
|
|
299
|
+
* not receive a signal — cleanup should not be cancellable.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```ts
|
|
303
|
+
* const task = Task.acquireRelease({
|
|
304
|
+
* acquire: (signal) => db.connect(signal),
|
|
305
|
+
* release: (conn) => conn.close(),
|
|
306
|
+
* use: (conn) => Task.of(() => query(conn))
|
|
307
|
+
* .retry({ attempts: 3 })
|
|
308
|
+
* .timeout(5_000),
|
|
309
|
+
* });
|
|
310
|
+
*
|
|
311
|
+
* const result = await task.result();
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
static acquireRelease({ acquire, release, use, }) {
|
|
315
|
+
return new Task(async (signal) => {
|
|
316
|
+
const resource = await acquire(signal);
|
|
317
|
+
try {
|
|
318
|
+
const innerTask = use(resource);
|
|
319
|
+
return signal
|
|
320
|
+
? await innerTask.withSignal(signal).run()
|
|
321
|
+
: await innerTask.run();
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
await release(resource);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
runWithSignal(signal) {
|
|
329
|
+
if (!signal) {
|
|
330
|
+
return this.runTask();
|
|
331
|
+
}
|
|
332
|
+
if (signal.aborted) {
|
|
333
|
+
return Promise.reject(signal.reason ?? new Error("Task aborted"));
|
|
334
|
+
}
|
|
335
|
+
let onAbort;
|
|
336
|
+
const abortPromise = new Promise((_, reject) => {
|
|
337
|
+
onAbort = () => {
|
|
338
|
+
signal.removeEventListener("abort", onAbort);
|
|
339
|
+
reject(signal.reason ?? new Error("Task aborted"));
|
|
340
|
+
};
|
|
341
|
+
signal.addEventListener("abort", onAbort, {
|
|
342
|
+
once: true,
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
return Promise.race([this.runTask(signal), abortPromise]).finally(() => {
|
|
346
|
+
if (onAbort) {
|
|
347
|
+
signal.removeEventListener("abort", onAbort);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
delayWithSignal(ms, signal) {
|
|
352
|
+
if (!signal) {
|
|
353
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
354
|
+
}
|
|
355
|
+
if (signal.aborted) {
|
|
356
|
+
return Promise.reject(signal.reason ?? new Error("Task aborted"));
|
|
357
|
+
}
|
|
358
|
+
let timeoutId;
|
|
359
|
+
let onAbort;
|
|
360
|
+
const sleepPromise = new Promise((resolve) => {
|
|
361
|
+
timeoutId = setTimeout(resolve, ms);
|
|
362
|
+
});
|
|
363
|
+
const abortPromise = new Promise((_, reject) => {
|
|
364
|
+
onAbort = () => {
|
|
365
|
+
if (timeoutId !== undefined) {
|
|
366
|
+
clearTimeout(timeoutId);
|
|
367
|
+
}
|
|
368
|
+
signal.removeEventListener("abort", onAbort);
|
|
369
|
+
reject(signal.reason ?? new Error("Task aborted"));
|
|
370
|
+
};
|
|
371
|
+
signal.addEventListener("abort", onAbort, {
|
|
372
|
+
once: true,
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
return Promise.race([sleepPromise, abortPromise]).finally(() => {
|
|
376
|
+
if (onAbort) {
|
|
377
|
+
signal.removeEventListener("abort", onAbort);
|
|
378
|
+
}
|
|
379
|
+
if (timeoutId !== undefined) {
|
|
380
|
+
clearTimeout(timeoutId);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
mergeSignals(outer, inner) {
|
|
385
|
+
if (!inner) {
|
|
386
|
+
return { signal: outer, cleanup: () => { } };
|
|
387
|
+
}
|
|
388
|
+
if (outer.aborted) {
|
|
389
|
+
return { signal: outer, cleanup: () => { } };
|
|
390
|
+
}
|
|
391
|
+
if (inner.aborted) {
|
|
392
|
+
return { signal: inner, cleanup: () => { } };
|
|
393
|
+
}
|
|
394
|
+
return Task.mergeExternalSignals(outer, inner);
|
|
395
|
+
}
|
|
396
|
+
static mergeExternalSignals(outer, inner) {
|
|
397
|
+
if (outer.aborted) {
|
|
398
|
+
return { signal: outer, cleanup: () => { } };
|
|
399
|
+
}
|
|
400
|
+
if (inner.aborted) {
|
|
401
|
+
return { signal: inner, cleanup: () => { } };
|
|
402
|
+
}
|
|
403
|
+
const controller = new AbortController();
|
|
404
|
+
const onOuterAbort = () => controller.abort(outer.reason);
|
|
405
|
+
const onInnerAbort = () => controller.abort(inner.reason);
|
|
406
|
+
outer.addEventListener("abort", onOuterAbort, { once: true });
|
|
407
|
+
inner.addEventListener("abort", onInnerAbort, { once: true });
|
|
408
|
+
return {
|
|
409
|
+
signal: controller.signal,
|
|
410
|
+
cleanup: () => {
|
|
411
|
+
outer.removeEventListener("abort", onOuterAbort);
|
|
412
|
+
inner.removeEventListener("abort", onInnerAbort);
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by {@link Stream.collect} and {@link Stream.fold} when one or more
|
|
3
|
+
* error results were collected during stream processing.
|
|
4
|
+
*/
|
|
5
|
+
export declare class AggregateError extends Error {
|
|
6
|
+
errors: unknown[];
|
|
7
|
+
/** The errors collected during stream processing. */
|
|
8
|
+
constructor(errors: unknown[]);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A result emitted by a {@link Stream}. Either a successful value or a
|
|
12
|
+
* collected error.
|
|
13
|
+
*/
|
|
14
|
+
export type Result<T, E> = E extends never ? SuccessResult<T, E> : T extends never ? ErrorResult<T, E> : SuccessResult<T, E> | ErrorResult<T, E>;
|
|
15
|
+
/**
|
|
16
|
+
* A value that is either already resolved or a `Promise` that resolves to it. Used throughout the API to allow callbacks to be synchronous or asynchronous.
|
|
17
|
+
*/
|
|
18
|
+
export type Promisable<T> = T | Promise<T>;
|
|
19
|
+
/**
|
|
20
|
+
* An error result emitted by a {@link Stream}.
|
|
21
|
+
*/
|
|
22
|
+
export type ErrorResult<_T, E> = {
|
|
23
|
+
type: "error";
|
|
24
|
+
error: E;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* A successful result emitted by a {@link Stream}.
|
|
28
|
+
*/
|
|
29
|
+
export type SuccessResult<T, _E> = {
|
|
30
|
+
type: "success";
|
|
31
|
+
value: T;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/anabranch/streams/util.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,cAAe,SAAQ,KAAK;IAEpB,MAAM,EAAE,OAAO,EAAE;IADpC,qDAAqD;gBAClC,MAAM,EAAE,OAAO,EAAE;CAMrC;AAED;;;GAGG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,KAAK,GAAG,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5D,CAAC,SAAS,KAAK,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GACnC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE5C;;GAEG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE3C;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,EAAE,EAAE,CAAC,IAAI;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;CACV,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,CAAC,CAAC;CACV,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by {@link Stream.collect} and {@link Stream.fold} when one or more
|
|
3
|
+
* error results were collected during stream processing.
|
|
4
|
+
*/
|
|
5
|
+
export class AggregateError extends Error {
|
|
6
|
+
/** The errors collected during stream processing. */
|
|
7
|
+
constructor(errors) {
|
|
8
|
+
super(`AggregateError: ${errors.length} errors`);
|
|
9
|
+
Object.defineProperty(this, "errors", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: errors
|
|
14
|
+
});
|
|
15
|
+
this.name = "AggregateError";
|
|
16
|
+
this.errors = errors;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/esm/fs/dir.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Source } from "../anabranch/index.js";
|
|
2
|
+
import type { DirEntry, GlobOptions, WalkEntry, WalkOptions } from "./types.js";
|
|
3
|
+
import { type NotDirectory, type NotFound, type PermissionDenied, type Unknown } from "./errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* Lists the immediate children of a directory.
|
|
6
|
+
*/
|
|
7
|
+
export declare function readDir(path: string | URL): Source<DirEntry, DirError>;
|
|
8
|
+
/**
|
|
9
|
+
* Recursively walks a directory tree, yielding each entry.
|
|
10
|
+
*/
|
|
11
|
+
export declare function walk(root: string | URL, options?: WalkOptions): Source<WalkEntry, DirError>;
|
|
12
|
+
/**
|
|
13
|
+
* Finds all entries under `root` whose relative path matches the glob `pattern`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function glob(root: string | URL, pattern: string, options?: GlobOptions): Source<WalkEntry, DirError>;
|
|
16
|
+
export type DirError = NotFound | NotDirectory | PermissionDenied | Unknown;
|
|
17
|
+
//# sourceMappingURL=dir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dir.d.ts","sourceRoot":"","sources":["../../src/fs/dir.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAE/C,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,OAAO,EACb,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAgBtE;AAED;;GAEG;AACH,wBAAgB,IAAI,CAClB,IAAI,EAAE,MAAM,GAAG,GAAG,EAClB,OAAO,CAAC,EAAE,WAAW,GACpB,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CA6D7B;AAED;;GAEG;AACH,wBAAgB,IAAI,CAClB,IAAI,EAAE,MAAM,GAAG,GAAG,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,WAAW,GACpB,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAG7B;AAED,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,YAAY,GAAG,gBAAgB,GAAG,OAAO,CAAC"}
|