@mark1russell7/collections 0.5.0 → 0.7.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/build/dag/index.d.ts +13 -1
- package/build/dag/index.d.ts.map +1 -1
- package/build/dag/index.js +29 -3
- package/build/dag/index.js.map +1 -1
- package/build/index.d.ts +32 -214
- package/build/index.d.ts.map +1 -1
- package/build/index.js +43 -233
- package/build/index.js.map +1 -1
- package/package.json +4 -1
- package/src/behaviors/safe.ts +78 -132
- package/src/dag/executor.ts +24 -2
- package/src/dag/types.ts +10 -1
- package/src/impl/hash-map.ts +18 -28
- package/src/index.ts +0 -16
- package/src/core/effects.ts +0 -327
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/collections",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"description": "Collections with no dependencies: lists, maps, queues, a summary tree, channels, lazy operations and dependency graphs.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"collections",
|
|
@@ -78,6 +78,9 @@
|
|
|
78
78
|
"typescript": "^7.0.2",
|
|
79
79
|
"vitest": "^5.0.3"
|
|
80
80
|
},
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"@mark1russell7/optional": "^1.2.0"
|
|
83
|
+
},
|
|
81
84
|
"scripts": {
|
|
82
85
|
"build": "tsdown",
|
|
83
86
|
"test": "vitest run",
|
package/src/behaviors/safe.ts
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* This module wraps collection operations that throw errors to instead return
|
|
5
|
-
* Option<T> or Result<T, E>, enabling functional error handling.
|
|
2
|
+
* The safe middleware. Each operation that throws on a missing element has a safe form in the property `safe`. The safe
|
|
3
|
+
* form gives an `Optional` or a `Result` of `@mark1russell7/optional`, and does not throw.
|
|
6
4
|
*
|
|
7
5
|
* @example
|
|
8
|
-
*
|
|
9
|
-
* safeList<number>()
|
|
10
|
-
* )(arrayList<number>())
|
|
6
|
+
* import { isSome } from "@mark1russell7/optional";
|
|
11
7
|
*
|
|
12
|
-
* const
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* }
|
|
8
|
+
* const list = compose(safeList<number>())(arrayList<number>());
|
|
9
|
+
* const value = list.safe.get(10); // an Optional<number>, not an exception
|
|
10
|
+
* if (isSome(value)) console.log(value.value);
|
|
16
11
|
*/
|
|
17
12
|
|
|
18
13
|
import type { Middleware } from "../core/middleware.js";
|
|
19
14
|
import type { List } from "../interfaces/list.js";
|
|
20
15
|
import type { Queue, Deque } from "../interfaces/queue.js";
|
|
21
16
|
import type { MapLike } from "../interfaces/map.js";
|
|
22
|
-
import { type
|
|
23
|
-
import { None, Some, tryCatch } from "../core/effects.js";
|
|
17
|
+
import { none, some, toOptional, tryCatch, type Optional, type Result } from "@mark1russell7/optional";
|
|
24
18
|
|
|
25
19
|
/**
|
|
26
20
|
* Safe operations for List.
|
|
27
21
|
*/
|
|
28
22
|
export interface SafeListOperations<T> {
|
|
29
23
|
/**
|
|
30
|
-
* Get element at index,
|
|
24
|
+
* Get element at index, as an optional.
|
|
31
25
|
*/
|
|
32
|
-
get(index: number):
|
|
26
|
+
get(index: number): Optional<T>;
|
|
33
27
|
|
|
34
28
|
/**
|
|
35
29
|
* Set element at index, giving Result with old value or error.
|
|
@@ -42,24 +36,24 @@ export interface SafeListOperations<T> {
|
|
|
42
36
|
removeAt(index: number): Result<T, string>;
|
|
43
37
|
|
|
44
38
|
/**
|
|
45
|
-
* Get first element as
|
|
39
|
+
* Get first element as an optional.
|
|
46
40
|
*/
|
|
47
|
-
first():
|
|
41
|
+
first(): Optional<T>;
|
|
48
42
|
|
|
49
43
|
/**
|
|
50
|
-
* Get last element as
|
|
44
|
+
* Get last element as an optional.
|
|
51
45
|
*/
|
|
52
|
-
last():
|
|
46
|
+
last(): Optional<T>;
|
|
53
47
|
|
|
54
48
|
/**
|
|
55
|
-
* Pop element,
|
|
49
|
+
* Pop element, as an optional.
|
|
56
50
|
*/
|
|
57
|
-
pop():
|
|
51
|
+
pop(): Optional<T>;
|
|
58
52
|
|
|
59
53
|
/**
|
|
60
|
-
* Shift element,
|
|
54
|
+
* Shift element, as an optional.
|
|
61
55
|
*/
|
|
62
|
-
shift():
|
|
56
|
+
shift(): Optional<T>;
|
|
63
57
|
}
|
|
64
58
|
|
|
65
59
|
/**
|
|
@@ -67,19 +61,19 @@ export interface SafeListOperations<T> {
|
|
|
67
61
|
*/
|
|
68
62
|
export interface SafeQueueOperations<T> {
|
|
69
63
|
/**
|
|
70
|
-
* Peek at head element as
|
|
64
|
+
* Peek at head element as an optional.
|
|
71
65
|
*/
|
|
72
|
-
peek():
|
|
66
|
+
peek(): Optional<T>;
|
|
73
67
|
|
|
74
68
|
/**
|
|
75
|
-
* Poll element as
|
|
69
|
+
* Poll element as an optional.
|
|
76
70
|
*/
|
|
77
|
-
poll():
|
|
71
|
+
poll(): Optional<T>;
|
|
78
72
|
|
|
79
73
|
/**
|
|
80
|
-
* Dequeue element as
|
|
74
|
+
* Dequeue element as an optional.
|
|
81
75
|
*/
|
|
82
|
-
dequeue():
|
|
76
|
+
dequeue(): Optional<T>;
|
|
83
77
|
}
|
|
84
78
|
|
|
85
79
|
/**
|
|
@@ -87,29 +81,29 @@ export interface SafeQueueOperations<T> {
|
|
|
87
81
|
*/
|
|
88
82
|
export interface SafeDequeOperations<T> extends SafeQueueOperations<T> {
|
|
89
83
|
/**
|
|
90
|
-
* Peek at first element as
|
|
84
|
+
* Peek at first element as an optional.
|
|
91
85
|
*/
|
|
92
|
-
peekFirst():
|
|
86
|
+
peekFirst(): Optional<T>;
|
|
93
87
|
|
|
94
88
|
/**
|
|
95
|
-
* Peek at last element as
|
|
89
|
+
* Peek at last element as an optional.
|
|
96
90
|
*/
|
|
97
|
-
peekLast():
|
|
91
|
+
peekLast(): Optional<T>;
|
|
98
92
|
|
|
99
93
|
/**
|
|
100
|
-
* Poll first element as
|
|
94
|
+
* Poll first element as an optional.
|
|
101
95
|
*/
|
|
102
|
-
pollFirst():
|
|
96
|
+
pollFirst(): Optional<T>;
|
|
103
97
|
|
|
104
98
|
/**
|
|
105
|
-
* Poll last element as
|
|
99
|
+
* Poll last element as an optional.
|
|
106
100
|
*/
|
|
107
|
-
pollLast():
|
|
101
|
+
pollLast(): Optional<T>;
|
|
108
102
|
|
|
109
103
|
/**
|
|
110
|
-
* Pop element as
|
|
104
|
+
* Pop element as an optional.
|
|
111
105
|
*/
|
|
112
|
-
pop():
|
|
106
|
+
pop(): Optional<T>;
|
|
113
107
|
}
|
|
114
108
|
|
|
115
109
|
/**
|
|
@@ -117,9 +111,9 @@ export interface SafeDequeOperations<T> extends SafeQueueOperations<T> {
|
|
|
117
111
|
*/
|
|
118
112
|
export interface SafeMapOperations<K, V> {
|
|
119
113
|
/**
|
|
120
|
-
* Get value for key as
|
|
114
|
+
* Get value for key as an optional.
|
|
121
115
|
*/
|
|
122
|
-
get(key: K):
|
|
116
|
+
get(key: K): Optional<V>;
|
|
123
117
|
|
|
124
118
|
/**
|
|
125
119
|
* Set value for key, giving Result with old value or success.
|
|
@@ -135,7 +129,7 @@ export interface SafeMapOperations<K, V> {
|
|
|
135
129
|
/**
|
|
136
130
|
* This function makes a safe List middleware.
|
|
137
131
|
*
|
|
138
|
-
* This function exposes a `safe` property with
|
|
132
|
+
* This function exposes a `safe` property with Optional/Result-based operations.
|
|
139
133
|
*/
|
|
140
134
|
export function safeList<T>(): Middleware<
|
|
141
135
|
List<T>,
|
|
@@ -143,14 +137,8 @@ export function safeList<T>(): Middleware<
|
|
|
143
137
|
> {
|
|
144
138
|
return (next: List<T>): List<T> & { safe: SafeListOperations<T> } => {
|
|
145
139
|
const safe: SafeListOperations<T> = {
|
|
146
|
-
get(index: number):
|
|
147
|
-
return tryCatch(
|
|
148
|
-
() => next.get(index),
|
|
149
|
-
() => "Index out of bounds"
|
|
150
|
-
).match<Option<T>>({
|
|
151
|
-
Ok: (value) => Some(value),
|
|
152
|
-
Err: () => None,
|
|
153
|
-
}) as Option<T>;
|
|
140
|
+
get(index: number): Optional<T> {
|
|
141
|
+
return toOptional(tryCatch(() => next.get(index)));
|
|
154
142
|
},
|
|
155
143
|
|
|
156
144
|
set(index: number, element: T): Result<T, string> {
|
|
@@ -167,34 +155,22 @@ export function safeList<T>(): Middleware<
|
|
|
167
155
|
);
|
|
168
156
|
},
|
|
169
157
|
|
|
170
|
-
first():
|
|
171
|
-
return next.isEmpty ?
|
|
158
|
+
first(): Optional<T> {
|
|
159
|
+
return next.isEmpty ? none : some(next.first());
|
|
172
160
|
},
|
|
173
161
|
|
|
174
|
-
last():
|
|
175
|
-
return next.isEmpty ?
|
|
162
|
+
last(): Optional<T> {
|
|
163
|
+
return next.isEmpty ? none : some(next.last());
|
|
176
164
|
},
|
|
177
165
|
|
|
178
|
-
pop():
|
|
179
|
-
if (next.isEmpty) return
|
|
180
|
-
return tryCatch(
|
|
181
|
-
() => next.pop(),
|
|
182
|
-
() => "List is empty"
|
|
183
|
-
).match<Option<T>>({
|
|
184
|
-
Ok: (value) => Some(value),
|
|
185
|
-
Err: () => None,
|
|
186
|
-
}) as Option<T>;
|
|
166
|
+
pop(): Optional<T> {
|
|
167
|
+
if (next.isEmpty) return none;
|
|
168
|
+
return toOptional(tryCatch(() => next.pop()));
|
|
187
169
|
},
|
|
188
170
|
|
|
189
|
-
shift():
|
|
190
|
-
if (next.isEmpty) return
|
|
191
|
-
return tryCatch(
|
|
192
|
-
() => next.shift(),
|
|
193
|
-
() => "List is empty"
|
|
194
|
-
).match<Option<T>>({
|
|
195
|
-
Ok: (value) => Some(value),
|
|
196
|
-
Err: () => None,
|
|
197
|
-
}) as Option<T>;
|
|
171
|
+
shift(): Optional<T> {
|
|
172
|
+
if (next.isEmpty) return none;
|
|
173
|
+
return toOptional(tryCatch(() => next.shift()));
|
|
198
174
|
},
|
|
199
175
|
};
|
|
200
176
|
|
|
@@ -212,37 +188,19 @@ export function safeQueue<T>(): Middleware<
|
|
|
212
188
|
> {
|
|
213
189
|
return (next: Queue<T>): Queue<T> & { safe: SafeQueueOperations<T> } => {
|
|
214
190
|
const safe: SafeQueueOperations<T> = {
|
|
215
|
-
peek():
|
|
216
|
-
if (next.isEmpty) return
|
|
217
|
-
return tryCatch(
|
|
218
|
-
() => next.peek(),
|
|
219
|
-
() => "Queue is empty"
|
|
220
|
-
).match<Option<T>>({
|
|
221
|
-
Ok: (value) => Some(value),
|
|
222
|
-
Err: () => None,
|
|
223
|
-
}) as Option<T>;
|
|
191
|
+
peek(): Optional<T> {
|
|
192
|
+
if (next.isEmpty) return none;
|
|
193
|
+
return toOptional(tryCatch(() => next.peek()));
|
|
224
194
|
},
|
|
225
195
|
|
|
226
|
-
poll():
|
|
227
|
-
if (next.isEmpty) return
|
|
228
|
-
return tryCatch(
|
|
229
|
-
() => next.poll(),
|
|
230
|
-
() => "Queue is empty"
|
|
231
|
-
).match<Option<T>>({
|
|
232
|
-
Ok: (value) => Some(value),
|
|
233
|
-
Err: () => None,
|
|
234
|
-
}) as Option<T>;
|
|
196
|
+
poll(): Optional<T> {
|
|
197
|
+
if (next.isEmpty) return none;
|
|
198
|
+
return toOptional(tryCatch(() => next.poll()));
|
|
235
199
|
},
|
|
236
200
|
|
|
237
|
-
dequeue():
|
|
238
|
-
if (next.isEmpty) return
|
|
239
|
-
return tryCatch(
|
|
240
|
-
() => next.dequeue(),
|
|
241
|
-
() => "Queue is empty"
|
|
242
|
-
).match<Option<T>>({
|
|
243
|
-
Ok: (value) => Some(value),
|
|
244
|
-
Err: () => None,
|
|
245
|
-
}) as Option<T>;
|
|
201
|
+
dequeue(): Optional<T> {
|
|
202
|
+
if (next.isEmpty) return none;
|
|
203
|
+
return toOptional(tryCatch(() => next.dequeue()));
|
|
246
204
|
},
|
|
247
205
|
};
|
|
248
206
|
|
|
@@ -259,55 +217,43 @@ export function safeDeque<T>(): Middleware<
|
|
|
259
217
|
> {
|
|
260
218
|
return (next: Deque<T>): Deque<T> & { safe: SafeDequeOperations<T> } => {
|
|
261
219
|
const safe: SafeDequeOperations<T> = {
|
|
262
|
-
peek():
|
|
263
|
-
return next.isEmpty ?
|
|
220
|
+
peek(): Optional<T> {
|
|
221
|
+
return next.isEmpty ? none : some(next.peekFirst());
|
|
264
222
|
},
|
|
265
223
|
|
|
266
|
-
poll():
|
|
224
|
+
poll(): Optional<T> {
|
|
267
225
|
// One poll: before, the test and the result each called pollFirst(), so poll() removed
|
|
268
226
|
// two elements and returned the second (BUGS-2026-07 L12)
|
|
269
|
-
if (next.isEmpty) return
|
|
270
|
-
return
|
|
227
|
+
if (next.isEmpty) return none;
|
|
228
|
+
return some(next.pollFirst() as T);
|
|
271
229
|
},
|
|
272
230
|
|
|
273
|
-
dequeue():
|
|
274
|
-
if (next.isEmpty) return
|
|
275
|
-
return tryCatch(
|
|
276
|
-
() => next.dequeue(),
|
|
277
|
-
() => "Deque is empty"
|
|
278
|
-
).match<Option<T>>({
|
|
279
|
-
Ok: (value) => Some(value),
|
|
280
|
-
Err: () => None,
|
|
281
|
-
}) as Option<T>;
|
|
231
|
+
dequeue(): Optional<T> {
|
|
232
|
+
if (next.isEmpty) return none;
|
|
233
|
+
return toOptional(tryCatch(() => next.dequeue()));
|
|
282
234
|
},
|
|
283
235
|
|
|
284
|
-
peekFirst():
|
|
285
|
-
return next.isEmpty ?
|
|
236
|
+
peekFirst(): Optional<T> {
|
|
237
|
+
return next.isEmpty ? none : some(next.peekFirst());
|
|
286
238
|
},
|
|
287
239
|
|
|
288
|
-
peekLast():
|
|
289
|
-
return next.isEmpty ?
|
|
240
|
+
peekLast(): Optional<T> {
|
|
241
|
+
return next.isEmpty ? none : some(next.peekLast());
|
|
290
242
|
},
|
|
291
243
|
|
|
292
|
-
pollFirst():
|
|
244
|
+
pollFirst(): Optional<T> {
|
|
293
245
|
const result = next.pollFirst();
|
|
294
|
-
return result !== undefined ?
|
|
246
|
+
return result !== undefined ? some(result) : none;
|
|
295
247
|
},
|
|
296
248
|
|
|
297
|
-
pollLast():
|
|
249
|
+
pollLast(): Optional<T> {
|
|
298
250
|
const result = next.pollLast();
|
|
299
|
-
return result !== undefined ?
|
|
251
|
+
return result !== undefined ? some(result) : none;
|
|
300
252
|
},
|
|
301
253
|
|
|
302
|
-
pop():
|
|
303
|
-
if (next.isEmpty) return
|
|
304
|
-
return tryCatch(
|
|
305
|
-
() => next.pop(),
|
|
306
|
-
() => "Deque is empty"
|
|
307
|
-
).match<Option<T>>({
|
|
308
|
-
Ok: (value) => Some(value),
|
|
309
|
-
Err: () => None,
|
|
310
|
-
}) as Option<T>;
|
|
254
|
+
pop(): Optional<T> {
|
|
255
|
+
if (next.isEmpty) return none;
|
|
256
|
+
return toOptional(tryCatch(() => next.pop()));
|
|
311
257
|
},
|
|
312
258
|
};
|
|
313
259
|
|
|
@@ -326,8 +272,8 @@ export function safeMap<K, V>(): Middleware<
|
|
|
326
272
|
next: MapLike<K, V>
|
|
327
273
|
): MapLike<K, V> & { safe: SafeMapOperations<K, V> } => {
|
|
328
274
|
const safe: SafeMapOperations<K, V> = {
|
|
329
|
-
get(key: K):
|
|
330
|
-
return next.has(key) ?
|
|
275
|
+
get(key: K): Optional<V> {
|
|
276
|
+
return next.has(key) ? some(next.get(key)) : none;
|
|
331
277
|
},
|
|
332
278
|
|
|
333
279
|
set(key: K, value: V): Result<V | undefined, string> {
|
package/src/dag/executor.ts
CHANGED
|
@@ -68,6 +68,9 @@ function notify<T>(name: string, listener: ((arg: T) => void) | undefined, arg:
|
|
|
68
68
|
* A processor that throws does NOT reject the whole run. The run records the throw
|
|
69
69
|
* as a failed NodeResult, so partial results are always given.
|
|
70
70
|
*
|
|
71
|
+
* With `skipDependents` (the default), a node whose dependency failed or was skipped does not start. Its result has
|
|
72
|
+
* `skipped: true` and an error that names those dependencies. A dependency outside the DAG does not count.
|
|
73
|
+
*
|
|
71
74
|
* `failFast` operates at level granularity. When a node in a level fails, each
|
|
72
75
|
* node already in flight in that level continues to completion. No later level
|
|
73
76
|
* starts. (There is no mid-level cancellation.)
|
|
@@ -80,20 +83,37 @@ export async function executeDAG<TNode extends DAGNode>(
|
|
|
80
83
|
const {
|
|
81
84
|
concurrency = 4,
|
|
82
85
|
failFast = true,
|
|
86
|
+
skipDependents = true,
|
|
83
87
|
onNodeStart,
|
|
84
88
|
onNodeComplete,
|
|
85
89
|
} = options;
|
|
86
90
|
|
|
87
91
|
const results = new Map<string, NodeResult<TNode>>();
|
|
88
92
|
const failedNodes: string[] = [];
|
|
93
|
+
const skippedNodes: string[] = [];
|
|
94
|
+
// The ids of the nodes that failed or were skipped
|
|
95
|
+
const blocked = new Set<string>();
|
|
89
96
|
const startTime = Date.now();
|
|
90
97
|
let shouldStop = false;
|
|
91
98
|
|
|
92
99
|
for (const level of dag.levels) {
|
|
93
100
|
if (shouldStop) break;
|
|
94
101
|
|
|
102
|
+
const runnable: TNode[] = [];
|
|
103
|
+
for (const node of level) {
|
|
104
|
+
const blockers = skipDependents ? node.dependencies.filter((dep) => blocked.has(dep)) : [];
|
|
105
|
+
if (blockers.length === 0) {
|
|
106
|
+
runnable.push(node);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const message = "Skipped " + node.id + ": the dependencies " + blockers.join(", ") + " failed or were skipped";
|
|
110
|
+
results.set(node.id, { node, success: false, skipped: true, error: new Error(message), duration: 0, logs: [message] });
|
|
111
|
+
skippedNodes.push(node.id);
|
|
112
|
+
blocked.add(node.id);
|
|
113
|
+
}
|
|
114
|
+
|
|
95
115
|
const levelResults = await executeWithConcurrency(
|
|
96
|
-
|
|
116
|
+
runnable,
|
|
97
117
|
async (node) => {
|
|
98
118
|
const startError = notify("onNodeStart", onNodeStart, node);
|
|
99
119
|
const nodeStart = Date.now();
|
|
@@ -127,6 +147,7 @@ export async function executeDAG<TNode extends DAGNode>(
|
|
|
127
147
|
results.set(result.node.id, result);
|
|
128
148
|
if (!result.success) {
|
|
129
149
|
failedNodes.push(result.node.id);
|
|
150
|
+
blocked.add(result.node.id);
|
|
130
151
|
if (failFast) {
|
|
131
152
|
shouldStop = true;
|
|
132
153
|
}
|
|
@@ -135,9 +156,10 @@ export async function executeDAG<TNode extends DAGNode>(
|
|
|
135
156
|
}
|
|
136
157
|
|
|
137
158
|
return {
|
|
138
|
-
success: failedNodes.length === 0,
|
|
159
|
+
success: failedNodes.length === 0 && skippedNodes.length === 0,
|
|
139
160
|
results,
|
|
140
161
|
failedNodes,
|
|
162
|
+
skippedNodes,
|
|
141
163
|
totalDuration: Date.now() - startTime,
|
|
142
164
|
};
|
|
143
165
|
}
|
package/src/dag/types.ts
CHANGED
|
@@ -41,6 +41,11 @@ export interface DAGExecutionOptions<TNode extends DAGNode = DAGNode> {
|
|
|
41
41
|
concurrency?: number;
|
|
42
42
|
/** When this value is true, the run stops on the first error. The default is true. */
|
|
43
43
|
failFast?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* When this value is true, a node whose dependency failed or was skipped does not start. Its result has `skipped: true`.
|
|
46
|
+
* The default is true. Give false only when the nodes do not use the work of their dependencies.
|
|
47
|
+
*/
|
|
48
|
+
skipDependents?: boolean;
|
|
44
49
|
/** Callback for progress reporting */
|
|
45
50
|
onNodeStart?: (node: TNode) => void;
|
|
46
51
|
/** Callback when a node completes */
|
|
@@ -63,6 +68,8 @@ export interface NodeResult<TNode extends DAGNode = DAGNode> {
|
|
|
63
68
|
logs: string[];
|
|
64
69
|
/** Optional output from the processor */
|
|
65
70
|
output?: unknown | undefined;
|
|
71
|
+
/** This value is true when the node did not start, because a dependency failed or was skipped. */
|
|
72
|
+
skipped?: boolean | undefined;
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
/**
|
|
@@ -73,8 +80,10 @@ export interface DAGResult<TNode extends DAGNode = DAGNode> {
|
|
|
73
80
|
success: boolean;
|
|
74
81
|
/** Results for each node */
|
|
75
82
|
results: Map<string, NodeResult<TNode>>;
|
|
76
|
-
/**
|
|
83
|
+
/** The ids of the nodes whose processor failed */
|
|
77
84
|
failedNodes: string[];
|
|
85
|
+
/** The ids of the nodes that did not start, because a dependency failed or was skipped */
|
|
86
|
+
skippedNodes: string[];
|
|
78
87
|
/** Total duration in milliseconds */
|
|
79
88
|
totalDuration: number;
|
|
80
89
|
}
|
package/src/impl/hash-map.ts
CHANGED
|
@@ -136,46 +136,36 @@ export class HashMap<K, V> implements MapLike<K, V> {
|
|
|
136
136
|
// ========================================================================
|
|
137
137
|
|
|
138
138
|
has(key: K): boolean {
|
|
139
|
-
|
|
140
|
-
const index = this.bucketIndex(hash);
|
|
141
|
-
let node = this._buckets[index] ?? null;
|
|
142
|
-
|
|
143
|
-
while (node !== null) {
|
|
144
|
-
if (node.hash === hash && this._keyEq(node.key, key)) {
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
node = node.next;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return false;
|
|
139
|
+
return this.findNode(key) !== null;
|
|
151
140
|
}
|
|
152
141
|
|
|
153
142
|
get(key: K): V {
|
|
154
|
-
|
|
155
|
-
|
|
143
|
+
// The node, not the value, tells if the key is present: a key can have the value undefined (C8)
|
|
144
|
+
const node = this.findNode(key);
|
|
145
|
+
if (node === null) {
|
|
156
146
|
throw new Error(`Key not found: ${key}`);
|
|
157
147
|
}
|
|
158
|
-
return value;
|
|
148
|
+
return node.value;
|
|
159
149
|
}
|
|
160
150
|
|
|
161
151
|
getOrUndefined(key: K): V | undefined {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
let node = this._buckets[index] ?? null;
|
|
152
|
+
return this.findNode(key)?.value;
|
|
153
|
+
}
|
|
165
154
|
|
|
155
|
+
getOrDefault(key: K, defaultValue: V): V {
|
|
156
|
+
const node = this.findNode(key);
|
|
157
|
+
return node !== null ? node.value : defaultValue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** This method finds the node of a key, or gives `null`. */
|
|
161
|
+
private findNode(key: K): Node<K, V> | null {
|
|
162
|
+
const hash = this._keyHash(key);
|
|
163
|
+
let node = this._buckets[this.bucketIndex(hash)] ?? null;
|
|
166
164
|
while (node !== null) {
|
|
167
|
-
if (node.hash === hash && this._keyEq(node.key, key))
|
|
168
|
-
return node.value;
|
|
169
|
-
}
|
|
165
|
+
if (node.hash === hash && this._keyEq(node.key, key)) return node;
|
|
170
166
|
node = node.next;
|
|
171
167
|
}
|
|
172
|
-
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
getOrDefault(key: K, defaultValue: V): V {
|
|
177
|
-
const value = this.getOrUndefined(key);
|
|
178
|
-
return value !== undefined ? value : defaultValue;
|
|
168
|
+
return null;
|
|
179
169
|
}
|
|
180
170
|
|
|
181
171
|
containsValue(value: V): boolean {
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
// ============================================================================
|
|
11
11
|
|
|
12
12
|
export * from "./core/traits.js";
|
|
13
|
-
export * from "./core/effects.js";
|
|
14
13
|
export * from "./core/policies.js";
|
|
15
14
|
export * from "./core/middleware.js";
|
|
16
15
|
export * from "./core/events.js";
|
|
@@ -220,18 +219,3 @@ export {
|
|
|
220
219
|
groupBy,
|
|
221
220
|
} from "./utils/factories.js";
|
|
222
221
|
|
|
223
|
-
// Effects
|
|
224
|
-
export {
|
|
225
|
-
None,
|
|
226
|
-
Some,
|
|
227
|
-
type Option,
|
|
228
|
-
Ok,
|
|
229
|
-
Err,
|
|
230
|
-
type Result,
|
|
231
|
-
isSome,
|
|
232
|
-
isNone,
|
|
233
|
-
isOk,
|
|
234
|
-
isErr,
|
|
235
|
-
getOrElse,
|
|
236
|
-
unwrap,
|
|
237
|
-
} from "./core/effects.js";
|