@bquery/bquery 1.9.0 → 1.11.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 +221 -40
- package/dist/{a11y-_9X-kt-_.js → a11y-DgUQ8-fI.js} +3 -3
- package/dist/{a11y-_9X-kt-_.js.map → a11y-DgUQ8-fI.js.map} +1 -1
- package/dist/a11y.es.mjs +1 -1
- package/dist/{component-L3-JfOFz.js → component-D8ydhe58.js} +20 -19
- package/dist/{component-L3-JfOFz.js.map → component-D8ydhe58.js.map} +1 -1
- package/dist/component.es.mjs +1 -1
- package/dist/concurrency/errors.d.ts +29 -0
- package/dist/concurrency/errors.d.ts.map +1 -0
- package/dist/concurrency/high-level.d.ts +85 -0
- package/dist/concurrency/high-level.d.ts.map +1 -0
- package/dist/concurrency/index.d.ts +19 -0
- package/dist/concurrency/index.d.ts.map +1 -0
- package/dist/concurrency/internal.d.ts +26 -0
- package/dist/concurrency/internal.d.ts.map +1 -0
- package/dist/concurrency/pipeline.d.ts +30 -0
- package/dist/concurrency/pipeline.d.ts.map +1 -0
- package/dist/concurrency/pool.d.ts +48 -0
- package/dist/concurrency/pool.d.ts.map +1 -0
- package/dist/concurrency/reactive.d.ts +107 -0
- package/dist/concurrency/reactive.d.ts.map +1 -0
- package/dist/concurrency/rpc.d.ts +46 -0
- package/dist/concurrency/rpc.d.ts.map +1 -0
- package/dist/concurrency/support.d.ts +23 -0
- package/dist/concurrency/support.d.ts.map +1 -0
- package/dist/concurrency/task.d.ts +31 -0
- package/dist/concurrency/task.d.ts.map +1 -0
- package/dist/concurrency/types.d.ts +343 -0
- package/dist/concurrency/types.d.ts.map +1 -0
- package/dist/concurrency-BU1wPEsZ.js +826 -0
- package/dist/concurrency-BU1wPEsZ.js.map +1 -0
- package/dist/concurrency.es.mjs +29 -0
- package/dist/{constraints-D5RHQLmP.js → constraints-Dlbx_m1b.js} +1 -1
- package/dist/{constraints-D5RHQLmP.js.map → constraints-Dlbx_m1b.js.map} +1 -1
- package/dist/core-CongXJuo.js +87 -0
- package/dist/core-CongXJuo.js.map +1 -0
- package/dist/{core-EMYSLzaT.js → core-tOP6QOrY.js} +2 -2
- package/dist/{core-EMYSLzaT.js.map → core-tOP6QOrY.js.map} +1 -1
- package/dist/core.es.mjs +1 -1
- package/dist/{custom-directives-Dr4C5lVV.js → custom-directives-5DlKqvd2.js} +1 -1
- package/dist/{custom-directives-Dr4C5lVV.js.map → custom-directives-5DlKqvd2.js.map} +1 -1
- package/dist/{devtools-BhB2iDPT.js → devtools-QosAqo0T.js} +2 -2
- package/dist/{devtools-BhB2iDPT.js.map → devtools-QosAqo0T.js.map} +1 -1
- package/dist/devtools.es.mjs +1 -1
- package/dist/{dnd-NwZBYh4l.js → dnd-d2OU4len.js} +1 -1
- package/dist/{dnd-NwZBYh4l.js.map → dnd-d2OU4len.js.map} +1 -1
- package/dist/dnd.es.mjs +1 -1
- package/dist/effect-Cc51IH91.js +87 -0
- package/dist/effect-Cc51IH91.js.map +1 -0
- package/dist/{env-CTdvLaH2.js → env-PvwYHnJq.js} +1 -1
- package/dist/{env-CTdvLaH2.js.map → env-PvwYHnJq.js.map} +1 -1
- package/dist/{forms-UhAeJEoO.js → forms-BLx4ZzT7.js} +41 -40
- package/dist/{forms-UhAeJEoO.js.map → forms-BLx4ZzT7.js.map} +1 -1
- package/dist/forms.es.mjs +1 -1
- package/dist/full.d.ts +6 -2
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +282 -214
- package/dist/full.iife.js +108 -20
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +108 -20
- package/dist/full.umd.js.map +1 -1
- package/dist/{i18n-kuF6Ekj6.js → i18n--p7PM-9r.js} +3 -3
- package/dist/{i18n-kuF6Ekj6.js.map → i18n--p7PM-9r.js.map} +1 -1
- package/dist/i18n.es.mjs +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.mjs +320 -252
- package/dist/match-CrZRVC4z.js +174 -0
- package/dist/match-CrZRVC4z.js.map +1 -0
- package/dist/media/observers.d.ts.map +1 -1
- package/dist/{media-D4zLj9t-.js → media-gjbWNq50.js} +3 -3
- package/dist/{media-D4zLj9t-.js.map → media-gjbWNq50.js.map} +1 -1
- package/dist/media.es.mjs +1 -1
- package/dist/{motion-BJsAuULb.js → motion-BBMso9Ir.js} +1 -1
- package/dist/{motion-BJsAuULb.js.map → motion-BBMso9Ir.js.map} +1 -1
- package/dist/motion.es.mjs +1 -1
- package/dist/{mount-B-JvH6Y0.js → mount-0A9qtcRJ.js} +11 -10
- package/dist/{mount-B-JvH6Y0.js.map → mount-0A9qtcRJ.js.map} +1 -1
- package/dist/{platform-Dw2gE3zI.js → platform-BPHIXbw8.js} +17 -16
- package/dist/{platform-Dw2gE3zI.js.map → platform-BPHIXbw8.js.map} +1 -1
- package/dist/platform.es.mjs +1 -1
- package/dist/{plugin-C2WuC8SF.js → plugin-SZEirbwq.js} +2 -2
- package/dist/{plugin-C2WuC8SF.js.map → plugin-SZEirbwq.js.map} +1 -1
- package/dist/plugin.es.mjs +1 -1
- package/dist/reactive/watch.d.ts.map +1 -1
- package/dist/reactive/websocket.d.ts +6 -3
- package/dist/reactive/websocket.d.ts.map +1 -1
- package/dist/{reactive-BjpLkclt.js → reactive-BAd2hfl8.js} +436 -449
- package/dist/reactive-BAd2hfl8.js.map +1 -0
- package/dist/reactive.es.mjs +42 -40
- package/dist/readonly-C0ZwS1Tf.js +35 -0
- package/dist/readonly-C0ZwS1Tf.js.map +1 -0
- package/dist/{registry-B08iilIh.js → registry-jpUQHf4E.js} +1 -1
- package/dist/{registry-B08iilIh.js.map → registry-jpUQHf4E.js.map} +1 -1
- package/dist/router-C4weu0QL.js +333 -0
- package/dist/router-C4weu0QL.js.map +1 -0
- package/dist/router.es.mjs +1 -1
- package/dist/{sanitize-B1V4JswB.js → sanitize-DOMkRO9G.js} +12 -7
- package/dist/{sanitize-B1V4JswB.js.map → sanitize-DOMkRO9G.js.map} +1 -1
- package/dist/security.es.mjs +1 -1
- package/dist/server/create-server.d.ts +25 -0
- package/dist/server/create-server.d.ts.map +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/types.d.ts +396 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server-QdyKtCS1.js +349 -0
- package/dist/server-QdyKtCS1.js.map +1 -0
- package/dist/server.es.mjs +6 -0
- package/dist/ssr/adapters.d.ts +74 -0
- package/dist/ssr/adapters.d.ts.map +1 -0
- package/dist/ssr/async.d.ts +40 -0
- package/dist/ssr/async.d.ts.map +1 -0
- package/dist/ssr/config.d.ts +60 -0
- package/dist/ssr/config.d.ts.map +1 -0
- package/dist/ssr/context.d.ts +73 -0
- package/dist/ssr/context.d.ts.map +1 -0
- package/dist/ssr/defer-brand.d.ts +5 -0
- package/dist/ssr/defer-brand.d.ts.map +1 -0
- package/dist/ssr/escape.d.ts +17 -0
- package/dist/ssr/escape.d.ts.map +1 -0
- package/dist/ssr/expression.d.ts +44 -0
- package/dist/ssr/expression.d.ts.map +1 -0
- package/dist/ssr/hash.d.ts +39 -0
- package/dist/ssr/hash.d.ts.map +1 -0
- package/dist/ssr/head.d.ts +102 -0
- package/dist/ssr/head.d.ts.map +1 -0
- package/dist/ssr/html-parser.d.ts +58 -0
- package/dist/ssr/html-parser.d.ts.map +1 -0
- package/dist/ssr/index.d.ts +49 -43
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/ssr/mismatch.d.ts +60 -0
- package/dist/ssr/mismatch.d.ts.map +1 -0
- package/dist/ssr/render-async.d.ts +84 -0
- package/dist/ssr/render-async.d.ts.map +1 -0
- package/dist/ssr/render.d.ts.map +1 -1
- package/dist/ssr/renderer.d.ts +25 -0
- package/dist/ssr/renderer.d.ts.map +1 -0
- package/dist/ssr/resumability.d.ts +65 -0
- package/dist/ssr/resumability.d.ts.map +1 -0
- package/dist/ssr/router-bridge.d.ts +101 -0
- package/dist/ssr/router-bridge.d.ts.map +1 -0
- package/dist/ssr/runtime.d.ts +63 -0
- package/dist/ssr/runtime.d.ts.map +1 -0
- package/dist/ssr/serialize.d.ts.map +1 -1
- package/dist/ssr/store-snapshot.d.ts +87 -0
- package/dist/ssr/store-snapshot.d.ts.map +1 -0
- package/dist/ssr/strategies.d.ts +43 -0
- package/dist/ssr/strategies.d.ts.map +1 -0
- package/dist/ssr/suspense.d.ts +47 -0
- package/dist/ssr/suspense.d.ts.map +1 -0
- package/dist/ssr/types.d.ts +17 -0
- package/dist/ssr/types.d.ts.map +1 -1
- package/dist/ssr-Bt6BQA3J.js +2127 -0
- package/dist/ssr-Bt6BQA3J.js.map +1 -0
- package/dist/ssr.es.mjs +42 -7
- package/dist/{store-CY6sjTW3.js → store-DnXuu6Li.js} +6 -6
- package/dist/{store-CY6sjTW3.js.map → store-DnXuu6Li.js.map} +1 -1
- package/dist/store.es.mjs +2 -2
- package/dist/storybook.es.mjs +1 -1
- package/dist/{testing-UjAtu9aQ.js → testing-CeMUwrRD.js} +7 -7
- package/dist/{testing-UjAtu9aQ.js.map → testing-CeMUwrRD.js.map} +1 -1
- package/dist/testing.es.mjs +1 -1
- package/dist/{untrack-D0fnO5k2.js → untrack-bjWDNdyE.js} +11 -10
- package/dist/{untrack-D0fnO5k2.js.map → untrack-bjWDNdyE.js.map} +1 -1
- package/dist/view.es.mjs +12 -11
- package/package.json +24 -15
- package/src/concurrency/errors.ts +57 -0
- package/src/concurrency/high-level.ts +387 -0
- package/src/concurrency/index.ts +63 -0
- package/src/concurrency/internal.ts +100 -0
- package/src/concurrency/pipeline.ts +133 -0
- package/src/concurrency/pool.ts +450 -0
- package/src/concurrency/reactive.ts +339 -0
- package/src/concurrency/rpc.ts +380 -0
- package/src/concurrency/support.ts +44 -0
- package/src/concurrency/task.ts +318 -0
- package/src/concurrency/types.ts +431 -0
- package/src/full.ts +164 -0
- package/src/index.ts +6 -0
- package/src/media/observers.ts +5 -8
- package/src/reactive/watch.ts +10 -9
- package/src/reactive/websocket.ts +31 -8
- package/src/server/create-server.ts +754 -0
- package/src/server/index.ts +33 -0
- package/src/server/types.ts +490 -0
- package/src/ssr/adapters.ts +330 -0
- package/src/ssr/async.ts +125 -0
- package/src/ssr/config.ts +86 -0
- package/src/ssr/context.ts +245 -0
- package/src/ssr/defer-brand.ts +3 -0
- package/src/ssr/escape.ts +25 -0
- package/src/ssr/expression.ts +669 -0
- package/src/ssr/hash.ts +71 -0
- package/src/ssr/head.ts +240 -0
- package/src/ssr/html-parser.ts +387 -0
- package/src/ssr/index.ts +136 -43
- package/src/ssr/mismatch.ts +110 -0
- package/src/ssr/render-async.ts +286 -0
- package/src/ssr/render.ts +130 -59
- package/src/ssr/renderer.ts +453 -0
- package/src/ssr/resumability.ts +142 -0
- package/src/ssr/router-bridge.ts +177 -0
- package/src/ssr/runtime.ts +131 -0
- package/src/ssr/serialize.ts +1 -27
- package/src/ssr/store-snapshot.ts +209 -0
- package/src/ssr/strategies.ts +245 -0
- package/src/ssr/suspense.ts +504 -0
- package/src/ssr/types.ts +18 -0
- package/dist/core-DdtZHzsS.js +0 -168
- package/dist/core-DdtZHzsS.js.map +0 -1
- package/dist/reactive-BjpLkclt.js.map +0 -1
- package/dist/router-BieVwgci.js +0 -492
- package/dist/router-BieVwgci.js.map +0 -1
- package/dist/ssr-CrGSJySz.js +0 -248
- package/dist/ssr-CrGSJySz.js.map +0 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional fluent pipeline helpers layered on top of the explicit collection helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/concurrency
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { every, filter, find, map, reduce, some } from './high-level';
|
|
8
|
+
import type {
|
|
9
|
+
ConcurrencyPipeline,
|
|
10
|
+
ConcurrencyPipelineOptions,
|
|
11
|
+
ParallelCollectionOptions,
|
|
12
|
+
ParallelMapHandler,
|
|
13
|
+
ParallelPredicateHandler,
|
|
14
|
+
ParallelReduceHandler,
|
|
15
|
+
TaskRunOptions,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
const mergeCollectionOptions = (
|
|
19
|
+
defaults: ConcurrencyPipelineOptions,
|
|
20
|
+
overrides: ParallelCollectionOptions = {}
|
|
21
|
+
): ParallelCollectionOptions => ({
|
|
22
|
+
...defaults,
|
|
23
|
+
...overrides,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const mergeTaskRunOptions = (
|
|
27
|
+
defaults: ConcurrencyPipelineOptions,
|
|
28
|
+
overrides: TaskRunOptions = {}
|
|
29
|
+
): TaskRunOptions => ({
|
|
30
|
+
signal: 'signal' in overrides ? overrides.signal : defaults.signal,
|
|
31
|
+
timeout: 'timeout' in overrides ? overrides.timeout : defaults.timeout,
|
|
32
|
+
transfer: overrides.transfer,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
class FluentConcurrencyPipeline<TValue> implements ConcurrencyPipeline<TValue> {
|
|
36
|
+
constructor(
|
|
37
|
+
private readonly valuesPromise: Promise<readonly TValue[]>,
|
|
38
|
+
private readonly defaults: ConcurrencyPipelineOptions
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
private createNext<TNext>(
|
|
42
|
+
transform: (values: readonly TValue[]) => Promise<readonly TNext[]>
|
|
43
|
+
): ConcurrencyPipeline<TNext> {
|
|
44
|
+
return new FluentConcurrencyPipeline(
|
|
45
|
+
this.valuesPromise.then((values) => transform(values)),
|
|
46
|
+
this.defaults
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
map<TResult>(
|
|
51
|
+
mapper: ParallelMapHandler<TValue, TResult>,
|
|
52
|
+
options?: ParallelCollectionOptions
|
|
53
|
+
): ConcurrencyPipeline<TResult> {
|
|
54
|
+
const resolvedOptions = mergeCollectionOptions(this.defaults, options);
|
|
55
|
+
return this.createNext((values) => map(values, mapper, resolvedOptions));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
filter(
|
|
59
|
+
predicate: ParallelPredicateHandler<TValue>,
|
|
60
|
+
options?: ParallelCollectionOptions
|
|
61
|
+
): ConcurrencyPipeline<TValue> {
|
|
62
|
+
const resolvedOptions = mergeCollectionOptions(this.defaults, options);
|
|
63
|
+
return this.createNext((values) => filter(values, predicate, resolvedOptions));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
toArray(): Promise<TValue[]> {
|
|
67
|
+
return this.valuesPromise.then((values) => values.slice());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
some(
|
|
71
|
+
predicate: ParallelPredicateHandler<TValue>,
|
|
72
|
+
options?: ParallelCollectionOptions
|
|
73
|
+
): Promise<boolean> {
|
|
74
|
+
const resolvedOptions = mergeCollectionOptions(this.defaults, options);
|
|
75
|
+
return this.valuesPromise.then((values) => some(values, predicate, resolvedOptions));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
every(
|
|
79
|
+
predicate: ParallelPredicateHandler<TValue>,
|
|
80
|
+
options?: ParallelCollectionOptions
|
|
81
|
+
): Promise<boolean> {
|
|
82
|
+
const resolvedOptions = mergeCollectionOptions(this.defaults, options);
|
|
83
|
+
return this.valuesPromise.then((values) => every(values, predicate, resolvedOptions));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
find(
|
|
87
|
+
predicate: ParallelPredicateHandler<TValue>,
|
|
88
|
+
options?: ParallelCollectionOptions
|
|
89
|
+
): Promise<TValue | undefined> {
|
|
90
|
+
const resolvedOptions = mergeCollectionOptions(this.defaults, options);
|
|
91
|
+
return this.valuesPromise.then((values) => find(values, predicate, resolvedOptions));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
reduce<TAccumulator>(
|
|
95
|
+
reducer: ParallelReduceHandler<TAccumulator, TValue>,
|
|
96
|
+
initialValue: TAccumulator,
|
|
97
|
+
options?: TaskRunOptions
|
|
98
|
+
): Promise<TAccumulator> {
|
|
99
|
+
const resolvedOptions = mergeTaskRunOptions(this.defaults, options);
|
|
100
|
+
return this.valuesPromise.then((values) =>
|
|
101
|
+
reduce(values, reducer, initialValue, resolvedOptions)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Creates an optional fluent pipeline over the existing concurrency collection helpers.
|
|
108
|
+
*
|
|
109
|
+
* The pipeline itself does not create hidden global workers or proxies. Each stage
|
|
110
|
+
* delegates to the already explicit `map()`, `filter()`, `some()`, `every()`,
|
|
111
|
+
* `find()`, and `reduce()` helpers when the pipeline is executed.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* import { pipeline } from '@bquery/bquery/concurrency';
|
|
116
|
+
*
|
|
117
|
+
* const results = await pipeline([1, 2, 3, 4], {
|
|
118
|
+
* batchSize: 2,
|
|
119
|
+
* concurrency: 2,
|
|
120
|
+
* })
|
|
121
|
+
* .map((value) => value * 2)
|
|
122
|
+
* .filter((value) => value > 4)
|
|
123
|
+
* .toArray();
|
|
124
|
+
*
|
|
125
|
+
* console.log(results); // [6, 8]
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export function pipeline<TValue>(
|
|
129
|
+
values: readonly TValue[],
|
|
130
|
+
options: ConcurrencyPipelineOptions = {}
|
|
131
|
+
): ConcurrencyPipeline<TValue> {
|
|
132
|
+
return new FluentConcurrencyPipeline(Promise.resolve(values.slice()), options);
|
|
133
|
+
}
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker pools with bounded concurrency and explicit queueing.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/concurrency
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { TaskWorkerAbortError, TaskWorkerError } from './errors';
|
|
8
|
+
import { createRpcWorker } from './rpc';
|
|
9
|
+
import { createTaskWorker } from './task';
|
|
10
|
+
import type {
|
|
11
|
+
CreateRpcPoolOptions,
|
|
12
|
+
CreateTaskPoolOptions,
|
|
13
|
+
RpcPool,
|
|
14
|
+
RpcWorker,
|
|
15
|
+
TaskPool,
|
|
16
|
+
TaskRunOptions,
|
|
17
|
+
TaskWorker,
|
|
18
|
+
TaskWorkerState,
|
|
19
|
+
WorkerRpcHandlers,
|
|
20
|
+
WorkerTaskHandler,
|
|
21
|
+
} from './types';
|
|
22
|
+
|
|
23
|
+
const DEFAULT_POOL_CONCURRENCY = 4;
|
|
24
|
+
|
|
25
|
+
interface QueueEntry<TJob, TResult> {
|
|
26
|
+
abortHandler?: () => void;
|
|
27
|
+
job: TJob;
|
|
28
|
+
options: TaskRunOptions;
|
|
29
|
+
reject: (reason?: unknown) => void;
|
|
30
|
+
resolve: (value: TResult | PromiseLike<TResult>) => void;
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface PoolRuntime<TJob, TResult> {
|
|
35
|
+
readonly state: TaskWorkerState;
|
|
36
|
+
readonly busy: boolean;
|
|
37
|
+
readonly concurrency: number;
|
|
38
|
+
readonly pending: number;
|
|
39
|
+
readonly size: number;
|
|
40
|
+
enqueue(job: TJob, options?: TaskRunOptions): Promise<TResult>;
|
|
41
|
+
clear(): void;
|
|
42
|
+
terminate(): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface CreatePoolRuntimeOptions<TWorker extends { busy: boolean }, TJob, TResult> {
|
|
46
|
+
abortedWhileQueuedMessage: string;
|
|
47
|
+
clearMessage: string;
|
|
48
|
+
concurrency: number;
|
|
49
|
+
createWorkers: (concurrency: number) => TWorker[];
|
|
50
|
+
queueFullMessage: string;
|
|
51
|
+
terminatedMessage: string;
|
|
52
|
+
workerTerminatedMessage: string;
|
|
53
|
+
runWorker: (worker: TWorker, job: TJob, options: TaskRunOptions) => Promise<TResult>;
|
|
54
|
+
maxQueue: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const normalizeConcurrency = (concurrency: number | undefined, label: string): number => {
|
|
58
|
+
if (concurrency === undefined) {
|
|
59
|
+
return DEFAULT_POOL_CONCURRENCY;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
63
|
+
throw new RangeError(`${label} concurrency must be a positive integer.`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return concurrency;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const normalizeMaxQueue = (maxQueue: number | undefined, label: string): number => {
|
|
70
|
+
if (maxQueue === undefined) {
|
|
71
|
+
return Number.POSITIVE_INFINITY;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (maxQueue === Number.POSITIVE_INFINITY) {
|
|
75
|
+
return maxQueue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!Number.isInteger(maxQueue) || maxQueue < 0) {
|
|
79
|
+
throw new RangeError(`${label} maxQueue must be a non-negative integer or Infinity.`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return maxQueue;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const detachAbortListener = <TJob, TResult>(entry: QueueEntry<TJob, TResult>): void => {
|
|
86
|
+
if (entry.abortHandler && entry.signal) {
|
|
87
|
+
entry.signal.removeEventListener('abort', entry.abortHandler);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
entry.abortHandler = undefined;
|
|
91
|
+
entry.signal = undefined;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const createPoolRuntime = <TWorker extends { busy: boolean }, TJob, TResult>({
|
|
95
|
+
abortedWhileQueuedMessage,
|
|
96
|
+
clearMessage,
|
|
97
|
+
concurrency,
|
|
98
|
+
createWorkers,
|
|
99
|
+
queueFullMessage,
|
|
100
|
+
terminatedMessage,
|
|
101
|
+
workerTerminatedMessage,
|
|
102
|
+
runWorker,
|
|
103
|
+
maxQueue,
|
|
104
|
+
}: CreatePoolRuntimeOptions<TWorker, TJob, TResult>): PoolRuntime<TJob, TResult> => {
|
|
105
|
+
const queue: Array<QueueEntry<TJob, TResult>> = [];
|
|
106
|
+
const workers = createWorkers(concurrency);
|
|
107
|
+
let disposed = false;
|
|
108
|
+
let running = 0;
|
|
109
|
+
|
|
110
|
+
const drain = (): void => {
|
|
111
|
+
if (disposed || queue.length === 0) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const worker of workers) {
|
|
116
|
+
if (queue.length === 0) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (worker.busy) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const entry = queue.shift()!;
|
|
125
|
+
if (entry.signal?.aborted) {
|
|
126
|
+
detachAbortListener(entry);
|
|
127
|
+
entry.reject(new TaskWorkerAbortError(abortedWhileQueuedMessage));
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
detachAbortListener(entry);
|
|
132
|
+
running++;
|
|
133
|
+
void runWorker(worker, entry.job, entry.options)
|
|
134
|
+
.then(entry.resolve, entry.reject)
|
|
135
|
+
.finally(() => {
|
|
136
|
+
running--;
|
|
137
|
+
drain();
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const rejectQueued = (error: TaskWorkerError): void => {
|
|
143
|
+
const queued = queue.splice(0);
|
|
144
|
+
for (const entry of queued) {
|
|
145
|
+
detachAbortListener(entry);
|
|
146
|
+
entry.reject(error);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
get state(): TaskWorkerState {
|
|
152
|
+
if (disposed) {
|
|
153
|
+
return 'terminated';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return running > 0 || queue.length > 0 ? 'running' : 'idle';
|
|
157
|
+
},
|
|
158
|
+
get busy(): boolean {
|
|
159
|
+
return running > 0 || queue.length > 0;
|
|
160
|
+
},
|
|
161
|
+
concurrency,
|
|
162
|
+
get pending(): number {
|
|
163
|
+
return running;
|
|
164
|
+
},
|
|
165
|
+
get size(): number {
|
|
166
|
+
return queue.length;
|
|
167
|
+
},
|
|
168
|
+
enqueue(job: TJob, options: TaskRunOptions = {}): Promise<TResult> {
|
|
169
|
+
if (disposed) {
|
|
170
|
+
return Promise.reject(new TaskWorkerError(workerTerminatedMessage, 'TERMINATED'));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (options.signal?.aborted) {
|
|
174
|
+
return Promise.reject(new TaskWorkerAbortError());
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return new Promise<TResult>((resolve, reject) => {
|
|
178
|
+
const entry: QueueEntry<TJob, TResult> = {
|
|
179
|
+
job,
|
|
180
|
+
options,
|
|
181
|
+
reject,
|
|
182
|
+
resolve,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const idleWorker = workers.find((worker) => !worker.busy);
|
|
186
|
+
if (idleWorker) {
|
|
187
|
+
running++;
|
|
188
|
+
void runWorker(idleWorker, job, options)
|
|
189
|
+
.then(resolve, reject)
|
|
190
|
+
.finally(() => {
|
|
191
|
+
running--;
|
|
192
|
+
drain();
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (queue.length >= maxQueue) {
|
|
198
|
+
reject(new TaskWorkerError(queueFullMessage, 'QUEUE_FULL'));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (options.signal) {
|
|
203
|
+
entry.signal = options.signal;
|
|
204
|
+
entry.abortHandler = () => {
|
|
205
|
+
const index = queue.indexOf(entry);
|
|
206
|
+
if (index === -1) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
queue.splice(index, 1);
|
|
211
|
+
detachAbortListener(entry);
|
|
212
|
+
reject(new TaskWorkerAbortError(abortedWhileQueuedMessage));
|
|
213
|
+
};
|
|
214
|
+
options.signal.addEventListener('abort', entry.abortHandler, { once: true });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
queue.push(entry);
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
clear(): void {
|
|
221
|
+
if (disposed || queue.length === 0) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
rejectQueued(new TaskWorkerError(clearMessage, 'QUEUE_CLEARED'));
|
|
226
|
+
},
|
|
227
|
+
terminate(): void {
|
|
228
|
+
if (disposed) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
disposed = true;
|
|
233
|
+
rejectQueued(new TaskWorkerError(terminatedMessage, 'TERMINATED'));
|
|
234
|
+
for (const worker of workers) {
|
|
235
|
+
if ('terminate' in worker && typeof worker.terminate === 'function') {
|
|
236
|
+
worker.terminate();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const createWorkerNames = (
|
|
244
|
+
name: string | undefined,
|
|
245
|
+
concurrency: number
|
|
246
|
+
): Array<string | undefined> => {
|
|
247
|
+
if (!name) {
|
|
248
|
+
return Array.from({ length: concurrency }, () => undefined);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (concurrency === 1) {
|
|
252
|
+
return [name];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return Array.from({ length: concurrency }, (_, index) => `${name}-${index + 1}`);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Creates a reusable pool of task workers with bounded concurrency and FIFO queueing.
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```ts
|
|
263
|
+
* import { createTaskPool } from '@bquery/bquery/concurrency';
|
|
264
|
+
*
|
|
265
|
+
* const pool = createTaskPool(
|
|
266
|
+
* ({ value }: { value: number }) => value * 2,
|
|
267
|
+
* { concurrency: 4, maxQueue: 16, name: 'double-pool' }
|
|
268
|
+
* );
|
|
269
|
+
*
|
|
270
|
+
* const results = await Promise.all([
|
|
271
|
+
* pool.run({ value: 1 }),
|
|
272
|
+
* pool.run({ value: 2 }),
|
|
273
|
+
* pool.run({ value: 3 }),
|
|
274
|
+
* ]);
|
|
275
|
+
*
|
|
276
|
+
* pool.terminate();
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export function createTaskPool<TInput = void, TResult = unknown>(
|
|
280
|
+
handler: WorkerTaskHandler<TInput, TResult>,
|
|
281
|
+
options: CreateTaskPoolOptions = {}
|
|
282
|
+
): TaskPool<TInput, TResult> {
|
|
283
|
+
const { concurrency: concurrencyOption, maxQueue: maxQueueOption, ...workerOptions } = options;
|
|
284
|
+
const concurrency = normalizeConcurrency(concurrencyOption, 'Task pool');
|
|
285
|
+
const maxQueue = normalizeMaxQueue(maxQueueOption, 'Task pool');
|
|
286
|
+
|
|
287
|
+
const runtime = createPoolRuntime<TaskWorker<TInput, TResult>, TInput, TResult>({
|
|
288
|
+
abortedWhileQueuedMessage: 'The queued task was aborted before execution started.',
|
|
289
|
+
clearMessage: 'The task pool queue was cleared.',
|
|
290
|
+
concurrency,
|
|
291
|
+
createWorkers(poolConcurrency) {
|
|
292
|
+
const workers: Array<TaskWorker<TInput, TResult>> = [];
|
|
293
|
+
const names = createWorkerNames(workerOptions.name, poolConcurrency);
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
for (let index = 0; index < poolConcurrency; index++) {
|
|
297
|
+
workers.push(
|
|
298
|
+
createTaskWorker(handler, {
|
|
299
|
+
...workerOptions,
|
|
300
|
+
name: names[index],
|
|
301
|
+
})
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
for (const worker of workers) {
|
|
306
|
+
worker.terminate();
|
|
307
|
+
}
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return workers;
|
|
312
|
+
},
|
|
313
|
+
queueFullMessage:
|
|
314
|
+
'The task pool queue is full. Increase maxQueue, wait for pending tasks, or raise pool concurrency.',
|
|
315
|
+
terminatedMessage: 'The task pool was terminated.',
|
|
316
|
+
workerTerminatedMessage: 'The task pool has already been terminated.',
|
|
317
|
+
runWorker(worker, job, runOptions) {
|
|
318
|
+
return worker.run(job, runOptions);
|
|
319
|
+
},
|
|
320
|
+
maxQueue,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
get state(): TaskWorkerState {
|
|
325
|
+
return runtime.state;
|
|
326
|
+
},
|
|
327
|
+
get busy(): boolean {
|
|
328
|
+
return runtime.busy;
|
|
329
|
+
},
|
|
330
|
+
get concurrency(): number {
|
|
331
|
+
return runtime.concurrency;
|
|
332
|
+
},
|
|
333
|
+
get pending(): number {
|
|
334
|
+
return runtime.pending;
|
|
335
|
+
},
|
|
336
|
+
get size(): number {
|
|
337
|
+
return runtime.size;
|
|
338
|
+
},
|
|
339
|
+
run(input: TInput, runOptions?: TaskRunOptions): Promise<TResult> {
|
|
340
|
+
return runtime.enqueue(input, runOptions);
|
|
341
|
+
},
|
|
342
|
+
clear(): void {
|
|
343
|
+
runtime.clear();
|
|
344
|
+
},
|
|
345
|
+
terminate(): void {
|
|
346
|
+
runtime.terminate();
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Creates a reusable pool of RPC workers with bounded concurrency and FIFO queueing.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```ts
|
|
356
|
+
* import { createRpcPool } from '@bquery/bquery/concurrency';
|
|
357
|
+
*
|
|
358
|
+
* const pool = createRpcPool(
|
|
359
|
+
* {
|
|
360
|
+
* sum: ({ values }: { values: number[] }) => values.reduce((total, value) => total + value, 0),
|
|
361
|
+
* },
|
|
362
|
+
* { concurrency: 2, maxQueue: 8 }
|
|
363
|
+
* );
|
|
364
|
+
*
|
|
365
|
+
* const total = await pool.call('sum', { values: [1, 2, 3] });
|
|
366
|
+
* pool.terminate();
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
export function createRpcPool<TRoutes extends WorkerRpcHandlers>(
|
|
370
|
+
handlers: TRoutes,
|
|
371
|
+
options: CreateRpcPoolOptions = {}
|
|
372
|
+
): RpcPool<TRoutes> {
|
|
373
|
+
const { concurrency: concurrencyOption, maxQueue: maxQueueOption, ...workerOptions } = options;
|
|
374
|
+
const concurrency = normalizeConcurrency(concurrencyOption, 'RPC pool');
|
|
375
|
+
const maxQueue = normalizeMaxQueue(maxQueueOption, 'RPC pool');
|
|
376
|
+
|
|
377
|
+
type RpcJob = {
|
|
378
|
+
input: unknown;
|
|
379
|
+
method: keyof TRoutes & string;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const runtime = createPoolRuntime<RpcWorker<TRoutes>, RpcJob, unknown>({
|
|
383
|
+
abortedWhileQueuedMessage: 'The queued RPC call was aborted before execution started.',
|
|
384
|
+
clearMessage: 'The RPC pool queue was cleared.',
|
|
385
|
+
concurrency,
|
|
386
|
+
createWorkers(poolConcurrency) {
|
|
387
|
+
const workers: Array<RpcWorker<TRoutes>> = [];
|
|
388
|
+
const names = createWorkerNames(workerOptions.name, poolConcurrency);
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
for (let index = 0; index < poolConcurrency; index++) {
|
|
392
|
+
workers.push(
|
|
393
|
+
createRpcWorker(handlers, {
|
|
394
|
+
...workerOptions,
|
|
395
|
+
name: names[index],
|
|
396
|
+
})
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
for (const worker of workers) {
|
|
401
|
+
worker.terminate();
|
|
402
|
+
}
|
|
403
|
+
throw error;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return workers;
|
|
407
|
+
},
|
|
408
|
+
queueFullMessage:
|
|
409
|
+
'The RPC pool queue is full. Increase maxQueue, wait for pending calls, or raise pool concurrency.',
|
|
410
|
+
terminatedMessage: 'The RPC pool was terminated.',
|
|
411
|
+
workerTerminatedMessage: 'The RPC pool has already been terminated.',
|
|
412
|
+
runWorker(worker, job, runOptions) {
|
|
413
|
+
return worker.call(job.method, job.input as never, runOptions);
|
|
414
|
+
},
|
|
415
|
+
maxQueue,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
get state(): TaskWorkerState {
|
|
420
|
+
return runtime.state;
|
|
421
|
+
},
|
|
422
|
+
get busy(): boolean {
|
|
423
|
+
return runtime.busy;
|
|
424
|
+
},
|
|
425
|
+
get concurrency(): number {
|
|
426
|
+
return runtime.concurrency;
|
|
427
|
+
},
|
|
428
|
+
get pending(): number {
|
|
429
|
+
return runtime.pending;
|
|
430
|
+
},
|
|
431
|
+
get size(): number {
|
|
432
|
+
return runtime.size;
|
|
433
|
+
},
|
|
434
|
+
call<TMethod extends keyof TRoutes & string>(
|
|
435
|
+
method: TMethod,
|
|
436
|
+
input: Parameters<TRoutes[TMethod]>[0],
|
|
437
|
+
runOptions?: TaskRunOptions
|
|
438
|
+
): Promise<Awaited<ReturnType<TRoutes[TMethod]>>> {
|
|
439
|
+
return runtime.enqueue({ input, method }, runOptions) as Promise<
|
|
440
|
+
Awaited<ReturnType<TRoutes[TMethod]>>
|
|
441
|
+
>;
|
|
442
|
+
},
|
|
443
|
+
clear(): void {
|
|
444
|
+
runtime.clear();
|
|
445
|
+
},
|
|
446
|
+
terminate(): void {
|
|
447
|
+
runtime.terminate();
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|