@ls-stack/utils 3.9.0 → 3.10.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/README.md +82 -1
- package/lib/asyncQueue.cjs +61 -55
- package/lib/asyncQueue.d.cts +20 -20
- package/lib/asyncQueue.d.ts +20 -20
- package/lib/asyncQueue.js +60 -46
- package/lib/concurrentCalls.cjs +1 -1
- package/lib/concurrentCalls.js +1 -1
- package/lib/testUtils.cjs +29 -2
- package/lib/testUtils.d.cts +6 -1
- package/lib/testUtils.d.ts +6 -1
- package/lib/testUtils.js +19 -1
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -1 +1,82 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ls-stack/utils
|
|
2
|
+
|
|
3
|
+
Generic TypeScript utilities for modern JavaScript/TypeScript projects.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ls-stack/utils
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @ls-stack/utils
|
|
11
|
+
# or
|
|
12
|
+
yarn add @ls-stack/utils
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Import specific utilities from their modules:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createAsyncQueue } from '@ls-stack/utils/asyncQueue';
|
|
21
|
+
import { deepEqual } from '@ls-stack/utils/deepEqual';
|
|
22
|
+
import { debounce } from '@ls-stack/utils/debounce';
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Available Utilities
|
|
26
|
+
|
|
27
|
+
This package includes a wide range of utilities for:
|
|
28
|
+
|
|
29
|
+
- **Array manipulation** (`arrayUtils`) - sorting, grouping, filtering
|
|
30
|
+
- **Async operations** (`asyncQueue`, `parallelAsyncCalls`, `promiseUtils`) - queue management and promise utilities
|
|
31
|
+
- **Type assertions** (`assertions`) - runtime type checking
|
|
32
|
+
- **Caching** (`cache`) - efficient caching with TTL support
|
|
33
|
+
- **Concurrency control** (`concurrentCalls`, `createThrottleController`) - rate limiting and throttling
|
|
34
|
+
- **Object utilities** (`objUtils`) - deep operations on objects
|
|
35
|
+
- **String utilities** (`stringUtils`) - string manipulation and formatting
|
|
36
|
+
- **Math utilities** (`mathUtils`) - mathematical operations and calculations
|
|
37
|
+
- **And many more...**
|
|
38
|
+
|
|
39
|
+
## Documentation
|
|
40
|
+
|
|
41
|
+
Comprehensive API documentation is available in the [`docs/`](docs/) folder. Start with the [modules overview](docs/modules.md) to explore all available utilities.
|
|
42
|
+
|
|
43
|
+
### Generating Documentation
|
|
44
|
+
|
|
45
|
+
To regenerate the documentation after making changes:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm docs
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For continuous updates during development:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm docs:watch
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Install dependencies
|
|
61
|
+
pnpm install
|
|
62
|
+
|
|
63
|
+
# Run tests
|
|
64
|
+
pnpm test
|
|
65
|
+
|
|
66
|
+
# Run tests with UI
|
|
67
|
+
pnpm test:ui
|
|
68
|
+
|
|
69
|
+
# Build the library
|
|
70
|
+
pnpm build
|
|
71
|
+
|
|
72
|
+
# Lint code
|
|
73
|
+
pnpm lint
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
|
79
|
+
|
|
80
|
+
## Repository
|
|
81
|
+
|
|
82
|
+
[github:lucasols/utils](https://github.com/lucasols/utils)
|
package/lib/asyncQueue.cjs
CHANGED
|
@@ -21,23 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var asyncQueue_exports = {};
|
|
22
22
|
__export(asyncQueue_exports, {
|
|
23
23
|
createAsyncQueue: () => createAsyncQueue,
|
|
24
|
-
|
|
24
|
+
createAsyncQueueWithMeta: () => createAsyncQueueWithMeta
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(asyncQueue_exports);
|
|
27
27
|
var import_evtmitter = require("evtmitter");
|
|
28
28
|
var import_t_result = require("t-result");
|
|
29
29
|
|
|
30
|
-
// src/assertions.ts
|
|
31
|
-
function isObject(value) {
|
|
32
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
|
-
}
|
|
34
|
-
function isFunction(value) {
|
|
35
|
-
return typeof value === "function";
|
|
36
|
-
}
|
|
37
|
-
function isPromise(value) {
|
|
38
|
-
return isObject(value) && "then" in value && isFunction(value.then);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
30
|
// src/promiseUtils.ts
|
|
42
31
|
function defer() {
|
|
43
32
|
let resolve;
|
|
@@ -74,36 +63,48 @@ var AsyncQueue = class {
|
|
|
74
63
|
this.#queue.push(task);
|
|
75
64
|
this.#size++;
|
|
76
65
|
}
|
|
77
|
-
add(fn, options) {
|
|
66
|
+
async add(fn, options) {
|
|
67
|
+
if (this.#signal?.aborted) {
|
|
68
|
+
return import_t_result.Result.err(
|
|
69
|
+
this.#signal.reason instanceof Error ? this.#signal.reason : new DOMException("Queue aborted", "AbortError")
|
|
70
|
+
);
|
|
71
|
+
}
|
|
78
72
|
const deferred = defer();
|
|
79
73
|
const taskTimeout = this.#taskTimeout ?? options?.timeout;
|
|
80
74
|
const task = {
|
|
81
75
|
run: async (ctx) => {
|
|
82
|
-
|
|
83
|
-
return fn;
|
|
84
|
-
}
|
|
85
|
-
return await fn(ctx);
|
|
76
|
+
return fn(ctx);
|
|
86
77
|
},
|
|
87
78
|
resolve: deferred.resolve,
|
|
88
79
|
reject: deferred.reject,
|
|
89
80
|
signal: options?.signal,
|
|
90
|
-
|
|
81
|
+
meta: options?.meta,
|
|
91
82
|
timeout: taskTimeout
|
|
92
83
|
};
|
|
93
84
|
this.#enqueue(task);
|
|
94
85
|
this.#processQueue();
|
|
95
|
-
|
|
86
|
+
const r = await deferred.promise;
|
|
87
|
+
if (options?.onComplete) {
|
|
88
|
+
r.onOk(options.onComplete);
|
|
89
|
+
}
|
|
90
|
+
if (options?.onError) {
|
|
91
|
+
r.onErr(options.onError);
|
|
92
|
+
}
|
|
93
|
+
return r;
|
|
96
94
|
}
|
|
97
95
|
resultifyAdd(fn, options) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return this.add((ctx) => (0, import_t_result.resultify)(cb(ctx)), options);
|
|
96
|
+
return this.add(
|
|
97
|
+
(ctx) => (0, import_t_result.resultify)(async () => {
|
|
98
|
+
return fn(ctx);
|
|
99
|
+
}),
|
|
100
|
+
options
|
|
101
|
+
);
|
|
105
102
|
}
|
|
106
103
|
async #processQueue() {
|
|
104
|
+
if (this.#signal?.aborted) {
|
|
105
|
+
this.clear();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
107
108
|
if (this.#pending >= this.#concurrency || this.#queue.length === 0) {
|
|
108
109
|
return;
|
|
109
110
|
}
|
|
@@ -125,45 +126,54 @@ var AsyncQueue = class {
|
|
|
125
126
|
}
|
|
126
127
|
const signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
|
|
127
128
|
let abortListener;
|
|
128
|
-
const signalAbortPromise = new Promise((_, reject) => {
|
|
129
|
-
if (signal) {
|
|
130
|
-
const error = signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
|
|
131
|
-
if (signal.aborted) {
|
|
132
|
-
reject(error);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
abortListener = () => {
|
|
136
|
-
reject(error);
|
|
137
|
-
};
|
|
138
|
-
signal.addEventListener("abort", abortListener, { once: true });
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
129
|
try {
|
|
142
|
-
|
|
143
|
-
|
|
130
|
+
if (signal?.aborted) {
|
|
131
|
+
const error = signal.reason instanceof Error ? signal.reason : new DOMException("This operation was aborted", "AbortError");
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
const signalAbortPromise = new Promise((_, reject) => {
|
|
135
|
+
if (signal) {
|
|
136
|
+
const error = signal.reason instanceof Error ? signal.reason : new DOMException("This operation was aborted", "AbortError");
|
|
137
|
+
abortListener = () => {
|
|
138
|
+
reject(error);
|
|
139
|
+
};
|
|
140
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
const taskRunPromise = task.run({ signal, meta: task.meta });
|
|
144
|
+
this.events.emit("start", { meta: task.meta });
|
|
144
145
|
const result = await Promise.race([taskRunPromise, signalAbortPromise]);
|
|
145
146
|
if ((0, import_t_result.isResult)(result)) {
|
|
146
147
|
task.resolve(result);
|
|
147
148
|
if (result.error) {
|
|
148
149
|
this.#failed++;
|
|
149
|
-
this.events.emit("error", {
|
|
150
|
+
this.events.emit("error", {
|
|
151
|
+
meta: task.meta,
|
|
152
|
+
error: result.error
|
|
153
|
+
});
|
|
150
154
|
} else {
|
|
151
155
|
this.#completed++;
|
|
152
|
-
this.events.emit("complete", {
|
|
156
|
+
this.events.emit("complete", {
|
|
157
|
+
meta: task.meta,
|
|
158
|
+
value: result.value
|
|
159
|
+
});
|
|
153
160
|
}
|
|
154
161
|
} else {
|
|
155
162
|
const error = new Error("Response not a Result");
|
|
156
163
|
task.resolve(import_t_result.Result.err(error));
|
|
157
164
|
this.#failed++;
|
|
158
165
|
this.events.emit("error", {
|
|
159
|
-
|
|
160
|
-
error
|
|
166
|
+
meta: task.meta,
|
|
167
|
+
error
|
|
161
168
|
});
|
|
162
169
|
}
|
|
163
170
|
} catch (error) {
|
|
164
171
|
task.resolve(import_t_result.Result.err(error));
|
|
165
172
|
this.#failed++;
|
|
166
|
-
this.events.emit("error", {
|
|
173
|
+
this.events.emit("error", {
|
|
174
|
+
meta: task.meta,
|
|
175
|
+
error: (0, import_t_result.unknownToError)(error)
|
|
176
|
+
});
|
|
167
177
|
} finally {
|
|
168
178
|
if (signal && abortListener) {
|
|
169
179
|
signal.removeEventListener("abort", abortListener);
|
|
@@ -191,13 +201,9 @@ var AsyncQueue = class {
|
|
|
191
201
|
this.#idleResolvers.push(resolve);
|
|
192
202
|
});
|
|
193
203
|
}
|
|
194
|
-
clear(
|
|
204
|
+
clear() {
|
|
195
205
|
this.#queue = [];
|
|
196
206
|
this.#size = 0;
|
|
197
|
-
if (resetCounters) {
|
|
198
|
-
this.#completed = 0;
|
|
199
|
-
this.#failed = 0;
|
|
200
|
-
}
|
|
201
207
|
if (this.#pending === 0) {
|
|
202
208
|
this.#resolveIdleWaiters();
|
|
203
209
|
}
|
|
@@ -215,7 +221,7 @@ var AsyncQueue = class {
|
|
|
215
221
|
return this.#size;
|
|
216
222
|
}
|
|
217
223
|
};
|
|
218
|
-
var
|
|
224
|
+
var AsyncQueueWithMeta = class extends AsyncQueue {
|
|
219
225
|
constructor(options) {
|
|
220
226
|
super(options);
|
|
221
227
|
}
|
|
@@ -229,11 +235,11 @@ var AsyncQueueWithId = class extends AsyncQueue {
|
|
|
229
235
|
function createAsyncQueue(options) {
|
|
230
236
|
return new AsyncQueue(options);
|
|
231
237
|
}
|
|
232
|
-
function
|
|
233
|
-
return new
|
|
238
|
+
function createAsyncQueueWithMeta(options) {
|
|
239
|
+
return new AsyncQueueWithMeta(options);
|
|
234
240
|
}
|
|
235
241
|
// Annotate the CommonJS export names for ESM import in node:
|
|
236
242
|
0 && (module.exports = {
|
|
237
243
|
createAsyncQueue,
|
|
238
|
-
|
|
244
|
+
createAsyncQueueWithMeta
|
|
239
245
|
});
|
package/lib/asyncQueue.d.cts
CHANGED
|
@@ -6,51 +6,51 @@ type AsyncQueueOptions = {
|
|
|
6
6
|
signal?: AbortSignal;
|
|
7
7
|
timeout?: number;
|
|
8
8
|
};
|
|
9
|
-
type AddOptions<I> = {
|
|
9
|
+
type AddOptions<I, T, E extends ResultValidErrors> = {
|
|
10
10
|
signal?: AbortSignal;
|
|
11
11
|
timeout?: number;
|
|
12
|
-
|
|
12
|
+
meta?: I;
|
|
13
|
+
onComplete?: (value: T) => void;
|
|
14
|
+
onError?: (error: E | Error) => void;
|
|
13
15
|
};
|
|
14
16
|
type RunCtx<I> = {
|
|
15
17
|
signal?: AbortSignal;
|
|
16
|
-
|
|
18
|
+
meta?: I;
|
|
17
19
|
};
|
|
18
|
-
declare class AsyncQueue<T, E extends ResultValidErrors = Error, I =
|
|
20
|
+
declare class AsyncQueue<T, E extends ResultValidErrors = Error, I = unknown> {
|
|
19
21
|
#private;
|
|
20
22
|
events: evtmitter.Emitter<{
|
|
21
23
|
error: {
|
|
22
|
-
|
|
24
|
+
meta: I;
|
|
23
25
|
error: E | Error;
|
|
24
26
|
};
|
|
25
27
|
complete: {
|
|
26
|
-
|
|
28
|
+
meta: I;
|
|
27
29
|
value: T;
|
|
28
30
|
};
|
|
29
31
|
start: {
|
|
30
|
-
|
|
32
|
+
meta: I;
|
|
31
33
|
};
|
|
32
34
|
}>;
|
|
33
35
|
constructor({ concurrency, signal, timeout: taskTimeout, }?: AsyncQueueOptions);
|
|
34
|
-
add(fn: (
|
|
35
|
-
resultifyAdd(fn: (
|
|
36
|
+
add(fn: (ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>, options?: AddOptions<I, T, E>): Promise<Result<T, E | Error>>;
|
|
37
|
+
resultifyAdd(fn: (ctx: RunCtx<I>) => Promise<T> | T, options?: AddOptions<I, T, E>): Promise<Result<T, E | Error>>;
|
|
36
38
|
onIdle(): Promise<void>;
|
|
37
|
-
clear(
|
|
38
|
-
resetCounters?: boolean;
|
|
39
|
-
}): void;
|
|
39
|
+
clear(): void;
|
|
40
40
|
get completed(): number;
|
|
41
41
|
get failed(): number;
|
|
42
42
|
get pending(): number;
|
|
43
43
|
get size(): number;
|
|
44
44
|
}
|
|
45
|
-
type AddOptionsWithId<I> = Omit<AddOptions<I>, '
|
|
46
|
-
|
|
45
|
+
type AddOptionsWithId<I, T, E extends ResultValidErrors> = Omit<AddOptions<I, T, E>, 'meta'> & {
|
|
46
|
+
meta: I;
|
|
47
47
|
};
|
|
48
|
-
declare class
|
|
48
|
+
declare class AsyncQueueWithMeta<T, I, E extends ResultValidErrors = Error> extends AsyncQueue<T, E, I> {
|
|
49
49
|
constructor(options?: AsyncQueueOptions);
|
|
50
|
-
add(fn: (
|
|
51
|
-
resultifyAdd(fn: (
|
|
50
|
+
add(fn: (ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>, options?: AddOptionsWithId<I, T, E>): Promise<Result<T, E | Error>>;
|
|
51
|
+
resultifyAdd(fn: (ctx: RunCtx<I>) => Promise<T> | T, options?: AddOptionsWithId<I, T, E>): Promise<Result<T, E | Error>>;
|
|
52
52
|
}
|
|
53
|
-
declare function createAsyncQueue<T, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueue<T, E
|
|
54
|
-
declare function
|
|
53
|
+
declare function createAsyncQueue<T, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueue<T, E>;
|
|
54
|
+
declare function createAsyncQueueWithMeta<T, I, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueueWithMeta<T, I, E>;
|
|
55
55
|
|
|
56
|
-
export { createAsyncQueue,
|
|
56
|
+
export { createAsyncQueue, createAsyncQueueWithMeta };
|
package/lib/asyncQueue.d.ts
CHANGED
|
@@ -6,51 +6,51 @@ type AsyncQueueOptions = {
|
|
|
6
6
|
signal?: AbortSignal;
|
|
7
7
|
timeout?: number;
|
|
8
8
|
};
|
|
9
|
-
type AddOptions<I> = {
|
|
9
|
+
type AddOptions<I, T, E extends ResultValidErrors> = {
|
|
10
10
|
signal?: AbortSignal;
|
|
11
11
|
timeout?: number;
|
|
12
|
-
|
|
12
|
+
meta?: I;
|
|
13
|
+
onComplete?: (value: T) => void;
|
|
14
|
+
onError?: (error: E | Error) => void;
|
|
13
15
|
};
|
|
14
16
|
type RunCtx<I> = {
|
|
15
17
|
signal?: AbortSignal;
|
|
16
|
-
|
|
18
|
+
meta?: I;
|
|
17
19
|
};
|
|
18
|
-
declare class AsyncQueue<T, E extends ResultValidErrors = Error, I =
|
|
20
|
+
declare class AsyncQueue<T, E extends ResultValidErrors = Error, I = unknown> {
|
|
19
21
|
#private;
|
|
20
22
|
events: evtmitter.Emitter<{
|
|
21
23
|
error: {
|
|
22
|
-
|
|
24
|
+
meta: I;
|
|
23
25
|
error: E | Error;
|
|
24
26
|
};
|
|
25
27
|
complete: {
|
|
26
|
-
|
|
28
|
+
meta: I;
|
|
27
29
|
value: T;
|
|
28
30
|
};
|
|
29
31
|
start: {
|
|
30
|
-
|
|
32
|
+
meta: I;
|
|
31
33
|
};
|
|
32
34
|
}>;
|
|
33
35
|
constructor({ concurrency, signal, timeout: taskTimeout, }?: AsyncQueueOptions);
|
|
34
|
-
add(fn: (
|
|
35
|
-
resultifyAdd(fn: (
|
|
36
|
+
add(fn: (ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>, options?: AddOptions<I, T, E>): Promise<Result<T, E | Error>>;
|
|
37
|
+
resultifyAdd(fn: (ctx: RunCtx<I>) => Promise<T> | T, options?: AddOptions<I, T, E>): Promise<Result<T, E | Error>>;
|
|
36
38
|
onIdle(): Promise<void>;
|
|
37
|
-
clear(
|
|
38
|
-
resetCounters?: boolean;
|
|
39
|
-
}): void;
|
|
39
|
+
clear(): void;
|
|
40
40
|
get completed(): number;
|
|
41
41
|
get failed(): number;
|
|
42
42
|
get pending(): number;
|
|
43
43
|
get size(): number;
|
|
44
44
|
}
|
|
45
|
-
type AddOptionsWithId<I> = Omit<AddOptions<I>, '
|
|
46
|
-
|
|
45
|
+
type AddOptionsWithId<I, T, E extends ResultValidErrors> = Omit<AddOptions<I, T, E>, 'meta'> & {
|
|
46
|
+
meta: I;
|
|
47
47
|
};
|
|
48
|
-
declare class
|
|
48
|
+
declare class AsyncQueueWithMeta<T, I, E extends ResultValidErrors = Error> extends AsyncQueue<T, E, I> {
|
|
49
49
|
constructor(options?: AsyncQueueOptions);
|
|
50
|
-
add(fn: (
|
|
51
|
-
resultifyAdd(fn: (
|
|
50
|
+
add(fn: (ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>, options?: AddOptionsWithId<I, T, E>): Promise<Result<T, E | Error>>;
|
|
51
|
+
resultifyAdd(fn: (ctx: RunCtx<I>) => Promise<T> | T, options?: AddOptionsWithId<I, T, E>): Promise<Result<T, E | Error>>;
|
|
52
52
|
}
|
|
53
|
-
declare function createAsyncQueue<T, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueue<T, E
|
|
54
|
-
declare function
|
|
53
|
+
declare function createAsyncQueue<T, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueue<T, E>;
|
|
54
|
+
declare function createAsyncQueueWithMeta<T, I, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueueWithMeta<T, I, E>;
|
|
55
55
|
|
|
56
|
-
export { createAsyncQueue,
|
|
56
|
+
export { createAsyncQueue, createAsyncQueueWithMeta };
|
package/lib/asyncQueue.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defer
|
|
3
3
|
} from "./chunk-DFXNVEH6.js";
|
|
4
|
-
import {
|
|
5
|
-
isPromise
|
|
6
|
-
} from "./chunk-3XCS7FVO.js";
|
|
7
4
|
|
|
8
5
|
// src/asyncQueue.ts
|
|
9
6
|
import { evtmitter } from "evtmitter";
|
|
@@ -37,36 +34,48 @@ var AsyncQueue = class {
|
|
|
37
34
|
this.#queue.push(task);
|
|
38
35
|
this.#size++;
|
|
39
36
|
}
|
|
40
|
-
add(fn, options) {
|
|
37
|
+
async add(fn, options) {
|
|
38
|
+
if (this.#signal?.aborted) {
|
|
39
|
+
return Result.err(
|
|
40
|
+
this.#signal.reason instanceof Error ? this.#signal.reason : new DOMException("Queue aborted", "AbortError")
|
|
41
|
+
);
|
|
42
|
+
}
|
|
41
43
|
const deferred = defer();
|
|
42
44
|
const taskTimeout = this.#taskTimeout ?? options?.timeout;
|
|
43
45
|
const task = {
|
|
44
46
|
run: async (ctx) => {
|
|
45
|
-
|
|
46
|
-
return fn;
|
|
47
|
-
}
|
|
48
|
-
return await fn(ctx);
|
|
47
|
+
return fn(ctx);
|
|
49
48
|
},
|
|
50
49
|
resolve: deferred.resolve,
|
|
51
50
|
reject: deferred.reject,
|
|
52
51
|
signal: options?.signal,
|
|
53
|
-
|
|
52
|
+
meta: options?.meta,
|
|
54
53
|
timeout: taskTimeout
|
|
55
54
|
};
|
|
56
55
|
this.#enqueue(task);
|
|
57
56
|
this.#processQueue();
|
|
58
|
-
|
|
57
|
+
const r = await deferred.promise;
|
|
58
|
+
if (options?.onComplete) {
|
|
59
|
+
r.onOk(options.onComplete);
|
|
60
|
+
}
|
|
61
|
+
if (options?.onError) {
|
|
62
|
+
r.onErr(options.onError);
|
|
63
|
+
}
|
|
64
|
+
return r;
|
|
59
65
|
}
|
|
60
66
|
resultifyAdd(fn, options) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return this.add((ctx) => resultify(cb(ctx)), options);
|
|
67
|
+
return this.add(
|
|
68
|
+
(ctx) => resultify(async () => {
|
|
69
|
+
return fn(ctx);
|
|
70
|
+
}),
|
|
71
|
+
options
|
|
72
|
+
);
|
|
68
73
|
}
|
|
69
74
|
async #processQueue() {
|
|
75
|
+
if (this.#signal?.aborted) {
|
|
76
|
+
this.clear();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
70
79
|
if (this.#pending >= this.#concurrency || this.#queue.length === 0) {
|
|
71
80
|
return;
|
|
72
81
|
}
|
|
@@ -88,45 +97,54 @@ var AsyncQueue = class {
|
|
|
88
97
|
}
|
|
89
98
|
const signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
|
|
90
99
|
let abortListener;
|
|
91
|
-
const signalAbortPromise = new Promise((_, reject) => {
|
|
92
|
-
if (signal) {
|
|
93
|
-
const error = signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
|
|
94
|
-
if (signal.aborted) {
|
|
95
|
-
reject(error);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
abortListener = () => {
|
|
99
|
-
reject(error);
|
|
100
|
-
};
|
|
101
|
-
signal.addEventListener("abort", abortListener, { once: true });
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
100
|
try {
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
if (signal?.aborted) {
|
|
102
|
+
const error = signal.reason instanceof Error ? signal.reason : new DOMException("This operation was aborted", "AbortError");
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
const signalAbortPromise = new Promise((_, reject) => {
|
|
106
|
+
if (signal) {
|
|
107
|
+
const error = signal.reason instanceof Error ? signal.reason : new DOMException("This operation was aborted", "AbortError");
|
|
108
|
+
abortListener = () => {
|
|
109
|
+
reject(error);
|
|
110
|
+
};
|
|
111
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const taskRunPromise = task.run({ signal, meta: task.meta });
|
|
115
|
+
this.events.emit("start", { meta: task.meta });
|
|
107
116
|
const result = await Promise.race([taskRunPromise, signalAbortPromise]);
|
|
108
117
|
if (isResult(result)) {
|
|
109
118
|
task.resolve(result);
|
|
110
119
|
if (result.error) {
|
|
111
120
|
this.#failed++;
|
|
112
|
-
this.events.emit("error", {
|
|
121
|
+
this.events.emit("error", {
|
|
122
|
+
meta: task.meta,
|
|
123
|
+
error: result.error
|
|
124
|
+
});
|
|
113
125
|
} else {
|
|
114
126
|
this.#completed++;
|
|
115
|
-
this.events.emit("complete", {
|
|
127
|
+
this.events.emit("complete", {
|
|
128
|
+
meta: task.meta,
|
|
129
|
+
value: result.value
|
|
130
|
+
});
|
|
116
131
|
}
|
|
117
132
|
} else {
|
|
118
133
|
const error = new Error("Response not a Result");
|
|
119
134
|
task.resolve(Result.err(error));
|
|
120
135
|
this.#failed++;
|
|
121
136
|
this.events.emit("error", {
|
|
122
|
-
|
|
123
|
-
error
|
|
137
|
+
meta: task.meta,
|
|
138
|
+
error
|
|
124
139
|
});
|
|
125
140
|
}
|
|
126
141
|
} catch (error) {
|
|
127
142
|
task.resolve(Result.err(error));
|
|
128
143
|
this.#failed++;
|
|
129
|
-
this.events.emit("error", {
|
|
144
|
+
this.events.emit("error", {
|
|
145
|
+
meta: task.meta,
|
|
146
|
+
error: unknownToError(error)
|
|
147
|
+
});
|
|
130
148
|
} finally {
|
|
131
149
|
if (signal && abortListener) {
|
|
132
150
|
signal.removeEventListener("abort", abortListener);
|
|
@@ -154,13 +172,9 @@ var AsyncQueue = class {
|
|
|
154
172
|
this.#idleResolvers.push(resolve);
|
|
155
173
|
});
|
|
156
174
|
}
|
|
157
|
-
clear(
|
|
175
|
+
clear() {
|
|
158
176
|
this.#queue = [];
|
|
159
177
|
this.#size = 0;
|
|
160
|
-
if (resetCounters) {
|
|
161
|
-
this.#completed = 0;
|
|
162
|
-
this.#failed = 0;
|
|
163
|
-
}
|
|
164
178
|
if (this.#pending === 0) {
|
|
165
179
|
this.#resolveIdleWaiters();
|
|
166
180
|
}
|
|
@@ -178,7 +192,7 @@ var AsyncQueue = class {
|
|
|
178
192
|
return this.#size;
|
|
179
193
|
}
|
|
180
194
|
};
|
|
181
|
-
var
|
|
195
|
+
var AsyncQueueWithMeta = class extends AsyncQueue {
|
|
182
196
|
constructor(options) {
|
|
183
197
|
super(options);
|
|
184
198
|
}
|
|
@@ -192,10 +206,10 @@ var AsyncQueueWithId = class extends AsyncQueue {
|
|
|
192
206
|
function createAsyncQueue(options) {
|
|
193
207
|
return new AsyncQueue(options);
|
|
194
208
|
}
|
|
195
|
-
function
|
|
196
|
-
return new
|
|
209
|
+
function createAsyncQueueWithMeta(options) {
|
|
210
|
+
return new AsyncQueueWithMeta(options);
|
|
197
211
|
}
|
|
198
212
|
export {
|
|
199
213
|
createAsyncQueue,
|
|
200
|
-
|
|
214
|
+
createAsyncQueueWithMeta
|
|
201
215
|
};
|
package/lib/concurrentCalls.cjs
CHANGED
|
@@ -288,7 +288,7 @@ var ConcurrentCallsWithMetadata = class {
|
|
|
288
288
|
`${failedProcessing.length}/${total} calls failed: ${truncateArray(
|
|
289
289
|
failedProcessing.map((f) => truncateString(f.error.message, 20)),
|
|
290
290
|
5,
|
|
291
|
-
|
|
291
|
+
"..."
|
|
292
292
|
).join("; ")}`
|
|
293
293
|
) : null;
|
|
294
294
|
return {
|
package/lib/concurrentCalls.js
CHANGED
|
@@ -235,7 +235,7 @@ var ConcurrentCallsWithMetadata = class {
|
|
|
235
235
|
`${failedProcessing.length}/${total} calls failed: ${truncateArray(
|
|
236
236
|
failedProcessing.map((f) => truncateString(f.error.message, 20)),
|
|
237
237
|
5,
|
|
238
|
-
|
|
238
|
+
"..."
|
|
239
239
|
).join("; ")}`
|
|
240
240
|
) : null;
|
|
241
241
|
return {
|
package/lib/testUtils.cjs
CHANGED
|
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var testUtils_exports = {};
|
|
22
22
|
__export(testUtils_exports, {
|
|
23
23
|
createLoggerStore: () => createLoggerStore,
|
|
24
|
-
getResultFn: () => getResultFn
|
|
24
|
+
getResultFn: () => getResultFn,
|
|
25
|
+
waitController: () => waitController
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(testUtils_exports);
|
|
27
28
|
|
|
@@ -141,6 +142,17 @@ function omit(obj, keys) {
|
|
|
141
142
|
return result;
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
// src/promiseUtils.ts
|
|
146
|
+
function defer() {
|
|
147
|
+
let resolve;
|
|
148
|
+
let reject;
|
|
149
|
+
const promise = new Promise((_resolve, _reject) => {
|
|
150
|
+
resolve = _resolve;
|
|
151
|
+
reject = _reject;
|
|
152
|
+
});
|
|
153
|
+
return { resolve, reject, promise };
|
|
154
|
+
}
|
|
155
|
+
|
|
144
156
|
// src/testUtils.ts
|
|
145
157
|
function createLoggerStore({
|
|
146
158
|
filterKeys: defaultFilterKeys,
|
|
@@ -353,8 +365,23 @@ function getResultFn(fnGetter, wrapper) {
|
|
|
353
365
|
}
|
|
354
366
|
};
|
|
355
367
|
}
|
|
368
|
+
function waitController() {
|
|
369
|
+
const { promise, resolve } = defer();
|
|
370
|
+
return {
|
|
371
|
+
wait: promise,
|
|
372
|
+
stopWaiting: () => {
|
|
373
|
+
resolve();
|
|
374
|
+
},
|
|
375
|
+
stopWaitingAfter: (ms) => {
|
|
376
|
+
setTimeout(() => {
|
|
377
|
+
resolve();
|
|
378
|
+
}, ms);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
356
382
|
// Annotate the CommonJS export names for ESM import in node:
|
|
357
383
|
0 && (module.exports = {
|
|
358
384
|
createLoggerStore,
|
|
359
|
-
getResultFn
|
|
385
|
+
getResultFn,
|
|
386
|
+
waitController
|
|
360
387
|
});
|
package/lib/testUtils.d.cts
CHANGED
|
@@ -34,5 +34,10 @@ declare function createLoggerStore({ filterKeys: defaultFilterKeys, rejectKeys:
|
|
|
34
34
|
addMark: (label: string) => void;
|
|
35
35
|
};
|
|
36
36
|
declare function getResultFn<T extends (...args: any[]) => any>(fnGetter: () => T, wrapper?: (...args: any[]) => any): T;
|
|
37
|
+
declare function waitController(): {
|
|
38
|
+
wait: Promise<void>;
|
|
39
|
+
stopWaiting: () => void;
|
|
40
|
+
stopWaitingAfter: (ms: number) => void;
|
|
41
|
+
};
|
|
37
42
|
|
|
38
|
-
export { createLoggerStore, getResultFn };
|
|
43
|
+
export { createLoggerStore, getResultFn, waitController };
|
package/lib/testUtils.d.ts
CHANGED
|
@@ -34,5 +34,10 @@ declare function createLoggerStore({ filterKeys: defaultFilterKeys, rejectKeys:
|
|
|
34
34
|
addMark: (label: string) => void;
|
|
35
35
|
};
|
|
36
36
|
declare function getResultFn<T extends (...args: any[]) => any>(fnGetter: () => T, wrapper?: (...args: any[]) => any): T;
|
|
37
|
+
declare function waitController(): {
|
|
38
|
+
wait: Promise<void>;
|
|
39
|
+
stopWaiting: () => void;
|
|
40
|
+
stopWaitingAfter: (ms: number) => void;
|
|
41
|
+
};
|
|
37
42
|
|
|
38
|
-
export { createLoggerStore, getResultFn };
|
|
43
|
+
export { createLoggerStore, getResultFn, waitController };
|
package/lib/testUtils.js
CHANGED
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
deepEqual
|
|
7
7
|
} from "./chunk-JQFUKJU5.js";
|
|
8
|
+
import {
|
|
9
|
+
defer
|
|
10
|
+
} from "./chunk-DFXNVEH6.js";
|
|
8
11
|
import {
|
|
9
12
|
clampMin
|
|
10
13
|
} from "./chunk-HTCYUMDR.js";
|
|
@@ -228,7 +231,22 @@ function getResultFn(fnGetter, wrapper) {
|
|
|
228
231
|
}
|
|
229
232
|
};
|
|
230
233
|
}
|
|
234
|
+
function waitController() {
|
|
235
|
+
const { promise, resolve } = defer();
|
|
236
|
+
return {
|
|
237
|
+
wait: promise,
|
|
238
|
+
stopWaiting: () => {
|
|
239
|
+
resolve();
|
|
240
|
+
},
|
|
241
|
+
stopWaitingAfter: (ms) => {
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
resolve();
|
|
244
|
+
}, ms);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
231
248
|
export {
|
|
232
249
|
createLoggerStore,
|
|
233
|
-
getResultFn
|
|
250
|
+
getResultFn,
|
|
251
|
+
waitController
|
|
234
252
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ls-stack/utils",
|
|
3
3
|
"description": "Typescript utils",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.10.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
7
7
|
"lib"
|
|
@@ -179,6 +179,9 @@
|
|
|
179
179
|
"prettier-plugin-organize-imports": "^4.1.0",
|
|
180
180
|
"tsm": "^2.3.0",
|
|
181
181
|
"tsup": "^8.3.5",
|
|
182
|
+
"typedoc": "^0.28.4",
|
|
183
|
+
"typedoc-plugin-markdown": "^4.6.3",
|
|
184
|
+
"typedoc-plugin-missing-exports": "^4.0.0",
|
|
182
185
|
"typescript": "^5.7.3",
|
|
183
186
|
"typescript-eslint": "^8.21.0",
|
|
184
187
|
"vite": "^6.0.11",
|
|
@@ -195,12 +198,15 @@
|
|
|
195
198
|
"tsc": "tsc -p tsconfig.prod.json",
|
|
196
199
|
"tsc:watch": "tsc -p tsconfig.prod.json --watch",
|
|
197
200
|
"eslint": "CI=true eslint src/ scripts/ --color --max-warnings=0",
|
|
198
|
-
"build": "pnpm test && pnpm lint && pnpm build:no-test && pnpm build:update-exports",
|
|
201
|
+
"build": "pnpm test && pnpm lint && pnpm build:no-test && pnpm run docs && pnpm run docs:commit && pnpm build:update-exports",
|
|
199
202
|
"build:no-test": "tsup",
|
|
200
203
|
"build:update-exports": "tsm --no-warnings scripts/updatePackageExports.ts",
|
|
201
204
|
"build-test": "tsup --config tsup.test.config.ts",
|
|
202
205
|
"pre-publish": "./scripts/check-if-is-sync.sh && pnpm build",
|
|
203
206
|
"test:console-fmt": "tsm --no-warnings scripts/testConsoleFmt.ts",
|
|
204
|
-
"bench:deepEqual": "tsm --no-warnings benchmarks/deepEqual.ts"
|
|
207
|
+
"bench:deepEqual": "tsm --no-warnings benchmarks/deepEqual.ts",
|
|
208
|
+
"docs": "typedoc",
|
|
209
|
+
"docs:watch": "typedoc --watch",
|
|
210
|
+
"docs:commit": "git add docs && git diff --cached --quiet docs || git commit -m 'docs: update docs'"
|
|
205
211
|
}
|
|
206
212
|
}
|