@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.
Files changed (216) hide show
  1. package/README.md +221 -40
  2. package/dist/{a11y-_9X-kt-_.js → a11y-DgUQ8-fI.js} +3 -3
  3. package/dist/{a11y-_9X-kt-_.js.map → a11y-DgUQ8-fI.js.map} +1 -1
  4. package/dist/a11y.es.mjs +1 -1
  5. package/dist/{component-L3-JfOFz.js → component-D8ydhe58.js} +20 -19
  6. package/dist/{component-L3-JfOFz.js.map → component-D8ydhe58.js.map} +1 -1
  7. package/dist/component.es.mjs +1 -1
  8. package/dist/concurrency/errors.d.ts +29 -0
  9. package/dist/concurrency/errors.d.ts.map +1 -0
  10. package/dist/concurrency/high-level.d.ts +85 -0
  11. package/dist/concurrency/high-level.d.ts.map +1 -0
  12. package/dist/concurrency/index.d.ts +19 -0
  13. package/dist/concurrency/index.d.ts.map +1 -0
  14. package/dist/concurrency/internal.d.ts +26 -0
  15. package/dist/concurrency/internal.d.ts.map +1 -0
  16. package/dist/concurrency/pipeline.d.ts +30 -0
  17. package/dist/concurrency/pipeline.d.ts.map +1 -0
  18. package/dist/concurrency/pool.d.ts +48 -0
  19. package/dist/concurrency/pool.d.ts.map +1 -0
  20. package/dist/concurrency/reactive.d.ts +107 -0
  21. package/dist/concurrency/reactive.d.ts.map +1 -0
  22. package/dist/concurrency/rpc.d.ts +46 -0
  23. package/dist/concurrency/rpc.d.ts.map +1 -0
  24. package/dist/concurrency/support.d.ts +23 -0
  25. package/dist/concurrency/support.d.ts.map +1 -0
  26. package/dist/concurrency/task.d.ts +31 -0
  27. package/dist/concurrency/task.d.ts.map +1 -0
  28. package/dist/concurrency/types.d.ts +343 -0
  29. package/dist/concurrency/types.d.ts.map +1 -0
  30. package/dist/concurrency-BU1wPEsZ.js +826 -0
  31. package/dist/concurrency-BU1wPEsZ.js.map +1 -0
  32. package/dist/concurrency.es.mjs +29 -0
  33. package/dist/{constraints-D5RHQLmP.js → constraints-Dlbx_m1b.js} +1 -1
  34. package/dist/{constraints-D5RHQLmP.js.map → constraints-Dlbx_m1b.js.map} +1 -1
  35. package/dist/core-CongXJuo.js +87 -0
  36. package/dist/core-CongXJuo.js.map +1 -0
  37. package/dist/{core-EMYSLzaT.js → core-tOP6QOrY.js} +2 -2
  38. package/dist/{core-EMYSLzaT.js.map → core-tOP6QOrY.js.map} +1 -1
  39. package/dist/core.es.mjs +1 -1
  40. package/dist/{custom-directives-Dr4C5lVV.js → custom-directives-5DlKqvd2.js} +1 -1
  41. package/dist/{custom-directives-Dr4C5lVV.js.map → custom-directives-5DlKqvd2.js.map} +1 -1
  42. package/dist/{devtools-BhB2iDPT.js → devtools-QosAqo0T.js} +2 -2
  43. package/dist/{devtools-BhB2iDPT.js.map → devtools-QosAqo0T.js.map} +1 -1
  44. package/dist/devtools.es.mjs +1 -1
  45. package/dist/{dnd-NwZBYh4l.js → dnd-d2OU4len.js} +1 -1
  46. package/dist/{dnd-NwZBYh4l.js.map → dnd-d2OU4len.js.map} +1 -1
  47. package/dist/dnd.es.mjs +1 -1
  48. package/dist/effect-Cc51IH91.js +87 -0
  49. package/dist/effect-Cc51IH91.js.map +1 -0
  50. package/dist/{env-CTdvLaH2.js → env-PvwYHnJq.js} +1 -1
  51. package/dist/{env-CTdvLaH2.js.map → env-PvwYHnJq.js.map} +1 -1
  52. package/dist/{forms-UhAeJEoO.js → forms-BLx4ZzT7.js} +41 -40
  53. package/dist/{forms-UhAeJEoO.js.map → forms-BLx4ZzT7.js.map} +1 -1
  54. package/dist/forms.es.mjs +1 -1
  55. package/dist/full.d.ts +6 -2
  56. package/dist/full.d.ts.map +1 -1
  57. package/dist/full.es.mjs +282 -214
  58. package/dist/full.iife.js +108 -20
  59. package/dist/full.iife.js.map +1 -1
  60. package/dist/full.umd.js +108 -20
  61. package/dist/full.umd.js.map +1 -1
  62. package/dist/{i18n-kuF6Ekj6.js → i18n--p7PM-9r.js} +3 -3
  63. package/dist/{i18n-kuF6Ekj6.js.map → i18n--p7PM-9r.js.map} +1 -1
  64. package/dist/i18n.es.mjs +1 -1
  65. package/dist/index.d.ts +2 -0
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.es.mjs +320 -252
  68. package/dist/match-CrZRVC4z.js +174 -0
  69. package/dist/match-CrZRVC4z.js.map +1 -0
  70. package/dist/media/observers.d.ts.map +1 -1
  71. package/dist/{media-D4zLj9t-.js → media-gjbWNq50.js} +3 -3
  72. package/dist/{media-D4zLj9t-.js.map → media-gjbWNq50.js.map} +1 -1
  73. package/dist/media.es.mjs +1 -1
  74. package/dist/{motion-BJsAuULb.js → motion-BBMso9Ir.js} +1 -1
  75. package/dist/{motion-BJsAuULb.js.map → motion-BBMso9Ir.js.map} +1 -1
  76. package/dist/motion.es.mjs +1 -1
  77. package/dist/{mount-B-JvH6Y0.js → mount-0A9qtcRJ.js} +11 -10
  78. package/dist/{mount-B-JvH6Y0.js.map → mount-0A9qtcRJ.js.map} +1 -1
  79. package/dist/{platform-Dw2gE3zI.js → platform-BPHIXbw8.js} +17 -16
  80. package/dist/{platform-Dw2gE3zI.js.map → platform-BPHIXbw8.js.map} +1 -1
  81. package/dist/platform.es.mjs +1 -1
  82. package/dist/{plugin-C2WuC8SF.js → plugin-SZEirbwq.js} +2 -2
  83. package/dist/{plugin-C2WuC8SF.js.map → plugin-SZEirbwq.js.map} +1 -1
  84. package/dist/plugin.es.mjs +1 -1
  85. package/dist/reactive/watch.d.ts.map +1 -1
  86. package/dist/reactive/websocket.d.ts +6 -3
  87. package/dist/reactive/websocket.d.ts.map +1 -1
  88. package/dist/{reactive-BjpLkclt.js → reactive-BAd2hfl8.js} +436 -449
  89. package/dist/reactive-BAd2hfl8.js.map +1 -0
  90. package/dist/reactive.es.mjs +42 -40
  91. package/dist/readonly-C0ZwS1Tf.js +35 -0
  92. package/dist/readonly-C0ZwS1Tf.js.map +1 -0
  93. package/dist/{registry-B08iilIh.js → registry-jpUQHf4E.js} +1 -1
  94. package/dist/{registry-B08iilIh.js.map → registry-jpUQHf4E.js.map} +1 -1
  95. package/dist/router-C4weu0QL.js +333 -0
  96. package/dist/router-C4weu0QL.js.map +1 -0
  97. package/dist/router.es.mjs +1 -1
  98. package/dist/{sanitize-B1V4JswB.js → sanitize-DOMkRO9G.js} +12 -7
  99. package/dist/{sanitize-B1V4JswB.js.map → sanitize-DOMkRO9G.js.map} +1 -1
  100. package/dist/security.es.mjs +1 -1
  101. package/dist/server/create-server.d.ts +25 -0
  102. package/dist/server/create-server.d.ts.map +1 -0
  103. package/dist/server/index.d.ts +11 -0
  104. package/dist/server/index.d.ts.map +1 -0
  105. package/dist/server/types.d.ts +396 -0
  106. package/dist/server/types.d.ts.map +1 -0
  107. package/dist/server-QdyKtCS1.js +349 -0
  108. package/dist/server-QdyKtCS1.js.map +1 -0
  109. package/dist/server.es.mjs +6 -0
  110. package/dist/ssr/adapters.d.ts +74 -0
  111. package/dist/ssr/adapters.d.ts.map +1 -0
  112. package/dist/ssr/async.d.ts +40 -0
  113. package/dist/ssr/async.d.ts.map +1 -0
  114. package/dist/ssr/config.d.ts +60 -0
  115. package/dist/ssr/config.d.ts.map +1 -0
  116. package/dist/ssr/context.d.ts +73 -0
  117. package/dist/ssr/context.d.ts.map +1 -0
  118. package/dist/ssr/defer-brand.d.ts +5 -0
  119. package/dist/ssr/defer-brand.d.ts.map +1 -0
  120. package/dist/ssr/escape.d.ts +17 -0
  121. package/dist/ssr/escape.d.ts.map +1 -0
  122. package/dist/ssr/expression.d.ts +44 -0
  123. package/dist/ssr/expression.d.ts.map +1 -0
  124. package/dist/ssr/hash.d.ts +39 -0
  125. package/dist/ssr/hash.d.ts.map +1 -0
  126. package/dist/ssr/head.d.ts +102 -0
  127. package/dist/ssr/head.d.ts.map +1 -0
  128. package/dist/ssr/html-parser.d.ts +58 -0
  129. package/dist/ssr/html-parser.d.ts.map +1 -0
  130. package/dist/ssr/index.d.ts +49 -43
  131. package/dist/ssr/index.d.ts.map +1 -1
  132. package/dist/ssr/mismatch.d.ts +60 -0
  133. package/dist/ssr/mismatch.d.ts.map +1 -0
  134. package/dist/ssr/render-async.d.ts +84 -0
  135. package/dist/ssr/render-async.d.ts.map +1 -0
  136. package/dist/ssr/render.d.ts.map +1 -1
  137. package/dist/ssr/renderer.d.ts +25 -0
  138. package/dist/ssr/renderer.d.ts.map +1 -0
  139. package/dist/ssr/resumability.d.ts +65 -0
  140. package/dist/ssr/resumability.d.ts.map +1 -0
  141. package/dist/ssr/router-bridge.d.ts +101 -0
  142. package/dist/ssr/router-bridge.d.ts.map +1 -0
  143. package/dist/ssr/runtime.d.ts +63 -0
  144. package/dist/ssr/runtime.d.ts.map +1 -0
  145. package/dist/ssr/serialize.d.ts.map +1 -1
  146. package/dist/ssr/store-snapshot.d.ts +87 -0
  147. package/dist/ssr/store-snapshot.d.ts.map +1 -0
  148. package/dist/ssr/strategies.d.ts +43 -0
  149. package/dist/ssr/strategies.d.ts.map +1 -0
  150. package/dist/ssr/suspense.d.ts +47 -0
  151. package/dist/ssr/suspense.d.ts.map +1 -0
  152. package/dist/ssr/types.d.ts +17 -0
  153. package/dist/ssr/types.d.ts.map +1 -1
  154. package/dist/ssr-Bt6BQA3J.js +2127 -0
  155. package/dist/ssr-Bt6BQA3J.js.map +1 -0
  156. package/dist/ssr.es.mjs +42 -7
  157. package/dist/{store-CY6sjTW3.js → store-DnXuu6Li.js} +6 -6
  158. package/dist/{store-CY6sjTW3.js.map → store-DnXuu6Li.js.map} +1 -1
  159. package/dist/store.es.mjs +2 -2
  160. package/dist/storybook.es.mjs +1 -1
  161. package/dist/{testing-UjAtu9aQ.js → testing-CeMUwrRD.js} +7 -7
  162. package/dist/{testing-UjAtu9aQ.js.map → testing-CeMUwrRD.js.map} +1 -1
  163. package/dist/testing.es.mjs +1 -1
  164. package/dist/{untrack-D0fnO5k2.js → untrack-bjWDNdyE.js} +11 -10
  165. package/dist/{untrack-D0fnO5k2.js.map → untrack-bjWDNdyE.js.map} +1 -1
  166. package/dist/view.es.mjs +12 -11
  167. package/package.json +24 -15
  168. package/src/concurrency/errors.ts +57 -0
  169. package/src/concurrency/high-level.ts +387 -0
  170. package/src/concurrency/index.ts +63 -0
  171. package/src/concurrency/internal.ts +100 -0
  172. package/src/concurrency/pipeline.ts +133 -0
  173. package/src/concurrency/pool.ts +450 -0
  174. package/src/concurrency/reactive.ts +339 -0
  175. package/src/concurrency/rpc.ts +380 -0
  176. package/src/concurrency/support.ts +44 -0
  177. package/src/concurrency/task.ts +318 -0
  178. package/src/concurrency/types.ts +431 -0
  179. package/src/full.ts +164 -0
  180. package/src/index.ts +6 -0
  181. package/src/media/observers.ts +5 -8
  182. package/src/reactive/watch.ts +10 -9
  183. package/src/reactive/websocket.ts +31 -8
  184. package/src/server/create-server.ts +754 -0
  185. package/src/server/index.ts +33 -0
  186. package/src/server/types.ts +490 -0
  187. package/src/ssr/adapters.ts +330 -0
  188. package/src/ssr/async.ts +125 -0
  189. package/src/ssr/config.ts +86 -0
  190. package/src/ssr/context.ts +245 -0
  191. package/src/ssr/defer-brand.ts +3 -0
  192. package/src/ssr/escape.ts +25 -0
  193. package/src/ssr/expression.ts +669 -0
  194. package/src/ssr/hash.ts +71 -0
  195. package/src/ssr/head.ts +240 -0
  196. package/src/ssr/html-parser.ts +387 -0
  197. package/src/ssr/index.ts +136 -43
  198. package/src/ssr/mismatch.ts +110 -0
  199. package/src/ssr/render-async.ts +286 -0
  200. package/src/ssr/render.ts +130 -59
  201. package/src/ssr/renderer.ts +453 -0
  202. package/src/ssr/resumability.ts +142 -0
  203. package/src/ssr/router-bridge.ts +177 -0
  204. package/src/ssr/runtime.ts +131 -0
  205. package/src/ssr/serialize.ts +1 -27
  206. package/src/ssr/store-snapshot.ts +209 -0
  207. package/src/ssr/strategies.ts +245 -0
  208. package/src/ssr/suspense.ts +504 -0
  209. package/src/ssr/types.ts +18 -0
  210. package/dist/core-DdtZHzsS.js +0 -168
  211. package/dist/core-DdtZHzsS.js.map +0 -1
  212. package/dist/reactive-BjpLkclt.js.map +0 -1
  213. package/dist/router-BieVwgci.js +0 -492
  214. package/dist/router-BieVwgci.js.map +0 -1
  215. package/dist/ssr-CrGSJySz.js +0 -248
  216. 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
+ }