@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bquery/bquery",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "The jQuery for the Modern Web Platform - Zero build, TypeScript-first library with reactivity, components, and motion",
5
5
  "type": "module",
6
6
  "main": "./dist/full.umd.js",
@@ -27,6 +27,10 @@
27
27
  "import": "./dist/reactive.es.mjs",
28
28
  "types": "./dist/reactive/index.d.ts"
29
29
  },
30
+ "./concurrency": {
31
+ "import": "./dist/concurrency.es.mjs",
32
+ "types": "./dist/concurrency/index.d.ts"
33
+ },
30
34
  "./component": {
31
35
  "import": "./dist/component.es.mjs",
32
36
  "types": "./dist/component/index.d.ts"
@@ -94,6 +98,10 @@
94
98
  "./ssr": {
95
99
  "import": "./dist/ssr.es.mjs",
96
100
  "types": "./dist/ssr/index.d.ts"
101
+ },
102
+ "./server": {
103
+ "import": "./dist/server.es.mjs",
104
+ "types": "./dist/server/index.d.ts"
97
105
  }
98
106
  },
99
107
  "files": [
@@ -113,6 +121,7 @@
113
121
  "build:lib": "vite build",
114
122
  "build:umd": "vite build --config vite.umd.config.ts",
115
123
  "build:types": "tsc --emitDeclarationOnly --outDir dist",
124
+ "check:ai-guidance": "bun scripts/check-ai-guidance.mjs",
116
125
  "test": "bun test",
117
126
  "test:types": "tsc -p tsconfig.component-test.json --noEmit",
118
127
  "test:watch": "bun test --watch",
@@ -155,24 +164,24 @@
155
164
  },
156
165
  "engines": {
157
166
  "node": ">=24.0.0",
158
- "bun": ">=1.3.11"
167
+ "bun": ">=1.3.13"
159
168
  },
160
169
  "devDependencies": {
161
- "@storybook/addon-docs": "^10.3.4",
162
- "@storybook/web-components-vite": "^10.3.4",
163
- "@typescript-eslint/eslint-plugin": "^8.58.0",
164
- "@typescript-eslint/parser": "^8.58.0",
165
- "bun-types": "^1.3.11",
166
- "eslint": "^10.2.0",
170
+ "@storybook/addon-docs": "^10.3.6",
171
+ "@storybook/web-components-vite": "^10.3.6",
172
+ "@typescript-eslint/eslint-plugin": "^8.59.1",
173
+ "@typescript-eslint/parser": "^8.59.1",
174
+ "bun-types": "^1.3.13",
175
+ "eslint": "^10.2.1",
167
176
  "eslint-config-prettier": "^10.1.8",
168
- "globals": "^17.4.0",
169
- "happy-dom": "^20.8.9",
170
- "prettier": "^3.8.1",
177
+ "globals": "^17.5.0",
178
+ "happy-dom": "^20.9.0",
179
+ "prettier": "^3.8.3",
171
180
  "rimraf": "^6.1.3",
172
- "storybook": "^10.3.4",
173
- "typedoc": "^0.28.18",
174
- "typescript": "^5.9.3",
175
- "vite": "^8.0.3",
181
+ "storybook": "^10.3.6",
182
+ "typedoc": "^0.28.19",
183
+ "typescript": "^6.0.3",
184
+ "vite": "^8.0.10",
176
185
  "vitepress": "^1.6.4"
177
186
  }
178
187
  }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Error types for the concurrency module.
3
+ *
4
+ * @module bquery/concurrency
5
+ */
6
+
7
+ import type { TaskWorkerErrorCode } from './types';
8
+
9
+ /** Base error for concurrency task failures. */
10
+ export class TaskWorkerError extends Error {
11
+ /** Stable error code for programmatic handling. */
12
+ code: TaskWorkerErrorCode;
13
+
14
+ constructor(message: string, code: TaskWorkerErrorCode, cause?: unknown) {
15
+ super(message);
16
+ this.name = 'TaskWorkerError';
17
+ this.code = code;
18
+ if (cause !== undefined) {
19
+ (this as Error & { cause?: unknown }).cause = cause;
20
+ }
21
+ }
22
+ }
23
+
24
+ /** Thrown when the environment cannot create inline Web Workers. */
25
+ export class TaskWorkerUnsupportedError extends TaskWorkerError {
26
+ constructor(
27
+ message = 'Concurrency tasks are not supported in this environment.',
28
+ cause?: unknown
29
+ ) {
30
+ super(message, 'UNSUPPORTED', cause);
31
+ this.name = 'TaskWorkerUnsupportedError';
32
+ }
33
+ }
34
+
35
+ /** Thrown when the task handler or payload cannot be serialized safely. */
36
+ export class TaskWorkerSerializationError extends TaskWorkerError {
37
+ constructor(message: string, cause?: unknown) {
38
+ super(message, 'SERIALIZATION', cause);
39
+ this.name = 'TaskWorkerSerializationError';
40
+ }
41
+ }
42
+
43
+ /** Thrown when a task exceeds its configured timeout. */
44
+ export class TaskWorkerTimeoutError extends TaskWorkerError {
45
+ constructor(message: string, cause?: unknown) {
46
+ super(message, 'TIMEOUT', cause);
47
+ this.name = 'TaskWorkerTimeoutError';
48
+ }
49
+ }
50
+
51
+ /** Thrown when a task is aborted via `AbortSignal`. */
52
+ export class TaskWorkerAbortError extends TaskWorkerError {
53
+ constructor(message = 'The worker task was aborted.', cause?: unknown) {
54
+ super(message, 'ABORT', cause);
55
+ this.name = 'TaskWorkerAbortError';
56
+ }
57
+ }
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Thin high-level helpers layered on top of the explicit worker primitives.
3
+ *
4
+ * @module bquery/concurrency
5
+ */
6
+
7
+ import { createTaskPool } from './pool';
8
+ import { runTask } from './task';
9
+ import { validateTaskHandler } from './internal';
10
+ import type {
11
+ ParallelCollectionOptions,
12
+ ParallelMapHandler,
13
+ ParallelMapOptions,
14
+ ParallelOptions,
15
+ ParallelPredicateHandler,
16
+ ParallelReduceHandler,
17
+ ParallelResults,
18
+ ParallelTask,
19
+ TaskPool,
20
+ TaskRunOptions,
21
+ WorkerTaskHandler,
22
+ } from './types';
23
+
24
+ interface SerializedParallelTask {
25
+ handlerSource: string;
26
+ input: unknown;
27
+ }
28
+
29
+ interface SerializedChunk<TInput = unknown> {
30
+ items: Array<{
31
+ index: number;
32
+ value: TInput;
33
+ }>;
34
+ handlerSource: string;
35
+ }
36
+
37
+ interface IndexedMapResult<TResult> {
38
+ index: number;
39
+ value: TResult;
40
+ }
41
+
42
+ const executeSerializedTask = async (job: SerializedParallelTask): Promise<unknown> => {
43
+ const revive = new Function(`return (${job.handlerSource});`);
44
+ const handler = revive() as ((input: unknown) => unknown | Promise<unknown>) | undefined;
45
+
46
+ if (typeof handler !== 'function') {
47
+ throw new TypeError('The serialized task handler did not revive as a function.');
48
+ }
49
+
50
+ return await handler(job.input);
51
+ };
52
+
53
+ interface SerializedReduceJob<TInput = unknown, TAccumulator = unknown> {
54
+ initialValue: TAccumulator;
55
+ reducerSource: string;
56
+ values: readonly TInput[];
57
+ }
58
+
59
+ const executeSerializedChunk = async (
60
+ job: SerializedChunk
61
+ ): Promise<Array<IndexedMapResult<unknown>>> => {
62
+ const revive = new Function(`return (${job.handlerSource});`);
63
+ const handler = revive() as
64
+ | ((value: unknown, index: number) => unknown | Promise<unknown>)
65
+ | undefined;
66
+
67
+ if (typeof handler !== 'function') {
68
+ throw new TypeError('The serialized collection handler did not revive as a function.');
69
+ }
70
+
71
+ const results: Array<IndexedMapResult<unknown>> = [];
72
+ for (const item of job.items) {
73
+ results.push({
74
+ index: item.index,
75
+ value: await handler(item.value, item.index),
76
+ });
77
+ }
78
+
79
+ return results;
80
+ };
81
+
82
+ const executeSerializedReduce = async (job: SerializedReduceJob): Promise<unknown> => {
83
+ const revive = new Function(`return (${job.reducerSource});`);
84
+ const reducer = revive() as
85
+ | ((accumulator: unknown, value: unknown, index: number) => unknown | Promise<unknown>)
86
+ | undefined;
87
+
88
+ if (typeof reducer !== 'function') {
89
+ throw new TypeError('The serialized reducer did not revive as a function.');
90
+ }
91
+
92
+ let accumulator = job.initialValue;
93
+ for (let index = 0; index < job.values.length; index++) {
94
+ accumulator = await reducer(accumulator, job.values[index], index);
95
+ }
96
+
97
+ return accumulator;
98
+ };
99
+
100
+ const normalizeBatchSize = (batchSize: number | undefined, label: string): number => {
101
+ if (batchSize === undefined) {
102
+ return 1;
103
+ }
104
+
105
+ if (!Number.isInteger(batchSize) || batchSize < 1) {
106
+ throw new RangeError(`${label} batchSize must be a positive integer.`);
107
+ }
108
+
109
+ return batchSize;
110
+ };
111
+
112
+ const createSerializedTaskPool = (
113
+ options: ParallelOptions
114
+ ): TaskPool<SerializedParallelTask, unknown> => {
115
+ return createTaskPool(executeSerializedTask, options);
116
+ };
117
+
118
+ const serializeTask = <TInput, TResult>(
119
+ task: ParallelTask<TInput, TResult>
120
+ ): SerializedParallelTask => ({
121
+ handlerSource: validateTaskHandler(task.handler),
122
+ input: task.input,
123
+ });
124
+
125
+ const runChunkedHandler = async <TInput, TResult>(
126
+ values: readonly TInput[],
127
+ handler: (value: TInput, index: number) => TResult | Promise<TResult>,
128
+ options: ParallelCollectionOptions = {},
129
+ label: string
130
+ ): Promise<TResult[]> => {
131
+ if (values.length === 0) {
132
+ return [];
133
+ }
134
+
135
+ const handlerSource = validateTaskHandler(
136
+ handler as unknown as WorkerTaskHandler<TInput, TResult>
137
+ );
138
+ const { batchSize, signal, ...poolOptions } = options;
139
+ const normalizedBatchSize = normalizeBatchSize(batchSize, label);
140
+ const pool = createTaskPool(executeSerializedChunk, poolOptions);
141
+ const chunks: Array<SerializedChunk<TInput>> = [];
142
+
143
+ for (let index = 0; index < values.length; index += normalizedBatchSize) {
144
+ const end = Math.min(index + normalizedBatchSize, values.length);
145
+ const items: SerializedChunk<TInput>['items'] = [];
146
+
147
+ for (let itemIndex = index; itemIndex < end; itemIndex += 1) {
148
+ items.push({
149
+ index: itemIndex,
150
+ value: values[itemIndex],
151
+ });
152
+ }
153
+
154
+ chunks.push({ items, handlerSource });
155
+ }
156
+
157
+ try {
158
+ const chunkResults = await Promise.all(
159
+ chunks.map((chunk) => pool.run(chunk, signal ? { signal } : undefined))
160
+ );
161
+ const mapped = new Array<TResult>(values.length);
162
+
163
+ for (const chunk of chunkResults) {
164
+ for (const item of chunk) {
165
+ mapped[item.index] = item.value as TResult;
166
+ }
167
+ }
168
+
169
+ return mapped;
170
+ } finally {
171
+ pool.terminate();
172
+ }
173
+ };
174
+
175
+ /**
176
+ * Executes multiple standalone tasks in parallel using a bounded worker pool.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * import { parallel } from '@bquery/bquery/concurrency';
181
+ *
182
+ * const results = await parallel([
183
+ * { handler: (value: number) => value * 2, input: 5 },
184
+ * { handler: ({ a, b }: { a: number; b: number }) => a + b, input: { a: 1, b: 2 } },
185
+ * ]);
186
+ * ```
187
+ */
188
+ export async function parallel<TTasks extends readonly ParallelTask[]>(
189
+ tasks: TTasks,
190
+ options: ParallelOptions = {}
191
+ ): Promise<ParallelResults<TTasks>> {
192
+ if (tasks.length === 0) {
193
+ return [] as unknown as ParallelResults<TTasks>;
194
+ }
195
+
196
+ const pool = createSerializedTaskPool(options);
197
+
198
+ try {
199
+ const results = await Promise.all(
200
+ tasks.map((task) => pool.run(serializeTask(task), task.options))
201
+ );
202
+ return results as ParallelResults<TTasks>;
203
+ } finally {
204
+ pool.terminate();
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Executes tasks in sequential batches while each batch still uses parallel workers.
210
+ *
211
+ * This adapts `threadts-universal`'s batch helper to bQuery without colliding
212
+ * with the reactive module's existing `batch()` export.
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * import { batchTasks } from '@bquery/bquery/concurrency';
217
+ *
218
+ * const results = await batchTasks(
219
+ * [
220
+ * { handler: (value: number) => value * 2, input: 1 },
221
+ * { handler: (value: number) => value * 2, input: 2 },
222
+ * { handler: (value: number) => value * 2, input: 3 },
223
+ * ],
224
+ * 2
225
+ * );
226
+ * ```
227
+ */
228
+ export async function batchTasks<TTasks extends readonly ParallelTask[]>(
229
+ tasks: TTasks,
230
+ batchSize?: number,
231
+ options: ParallelOptions = {}
232
+ ): Promise<ParallelResults<TTasks>> {
233
+ if (tasks.length === 0) {
234
+ return [] as unknown as ParallelResults<TTasks>;
235
+ }
236
+
237
+ const normalizedBatchSize = normalizeBatchSize(batchSize, 'batchTasks');
238
+ const pool = createSerializedTaskPool(options);
239
+ const results: unknown[] = [];
240
+
241
+ try {
242
+ for (let index = 0; index < tasks.length; index += normalizedBatchSize) {
243
+ const batch = tasks.slice(index, index + normalizedBatchSize);
244
+ const batchResults = await Promise.all(
245
+ batch.map((task) => pool.run(serializeTask(task), task.options))
246
+ );
247
+ results.push(...batchResults);
248
+ }
249
+
250
+ return results as ParallelResults<TTasks>;
251
+ } finally {
252
+ pool.terminate();
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Maps an array in parallel using optional chunking on top of `createTaskPool()`.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * import { map } from '@bquery/bquery/concurrency';
262
+ *
263
+ * const results = await map([1, 2, 3], (value, index) => value + index, {
264
+ * batchSize: 2,
265
+ * concurrency: 2,
266
+ * });
267
+ * ```
268
+ */
269
+ export async function map<TInput, TResult>(
270
+ values: readonly TInput[],
271
+ mapper: ParallelMapHandler<TInput, TResult>,
272
+ options: ParallelMapOptions = {}
273
+ ): Promise<TResult[]> {
274
+ return runChunkedHandler(values, mapper, options, 'map');
275
+ }
276
+
277
+ /**
278
+ * Filters an array in parallel using a standalone predicate with optional chunking.
279
+ */
280
+ export async function filter<TInput>(
281
+ values: readonly TInput[],
282
+ predicate: ParallelPredicateHandler<TInput>,
283
+ options: ParallelCollectionOptions = {}
284
+ ): Promise<TInput[]> {
285
+ const matches = await runChunkedHandler(values, predicate, options, 'filter');
286
+ const filtered: TInput[] = [];
287
+
288
+ for (let index = 0; index < values.length; index += 1) {
289
+ if (!matches[index]) {
290
+ continue;
291
+ }
292
+
293
+ if (index in values) {
294
+ filtered.push(values[index] as TInput);
295
+ } else {
296
+ filtered.length += 1;
297
+ }
298
+ }
299
+
300
+ return filtered;
301
+ }
302
+
303
+ /**
304
+ * Returns whether at least one array item matches a standalone predicate.
305
+ *
306
+ * The current implementation evaluates predicate chunks explicitly and reduces
307
+ * the final boolean result on the main thread instead of using hidden globals
308
+ * or speculative worker cancellation.
309
+ */
310
+ export async function some<TInput>(
311
+ values: readonly TInput[],
312
+ predicate: ParallelPredicateHandler<TInput>,
313
+ options: ParallelCollectionOptions = {}
314
+ ): Promise<boolean> {
315
+ if (values.length === 0) {
316
+ return false;
317
+ }
318
+
319
+ const matches = await runChunkedHandler(values, predicate, options, 'some');
320
+ return matches.some(Boolean);
321
+ }
322
+
323
+ /**
324
+ * Returns whether every array item matches a standalone predicate.
325
+ *
326
+ * The current implementation evaluates predicate chunks explicitly and reduces
327
+ * the final boolean result on the main thread instead of using hidden globals
328
+ * or speculative worker cancellation.
329
+ */
330
+ export async function every<TInput>(
331
+ values: readonly TInput[],
332
+ predicate: ParallelPredicateHandler<TInput>,
333
+ options: ParallelCollectionOptions = {}
334
+ ): Promise<boolean> {
335
+ if (values.length === 0) {
336
+ return true;
337
+ }
338
+
339
+ const matches = await runChunkedHandler(values, predicate, options, 'every');
340
+ return matches.every(Boolean);
341
+ }
342
+
343
+ /**
344
+ * Finds the first array item that matches a standalone predicate.
345
+ */
346
+ export async function find<TInput>(
347
+ values: readonly TInput[],
348
+ predicate: ParallelPredicateHandler<TInput>,
349
+ options: ParallelCollectionOptions = {}
350
+ ): Promise<TInput | undefined> {
351
+ if (values.length === 0) {
352
+ return undefined;
353
+ }
354
+
355
+ const matches = await runChunkedHandler(values, predicate, options, 'find');
356
+ const index = matches.findIndex(Boolean);
357
+ return index === -1 ? undefined : values[index];
358
+ }
359
+
360
+ /**
361
+ * Reduces an array inside one isolated worker while preserving standard
362
+ * left-to-right accumulator semantics.
363
+ */
364
+ export async function reduce<TInput, TAccumulator>(
365
+ values: readonly TInput[],
366
+ reducer: ParallelReduceHandler<TAccumulator, TInput>,
367
+ initialValue: TAccumulator,
368
+ options: TaskRunOptions = {}
369
+ ): Promise<TAccumulator> {
370
+ if (values.length === 0) {
371
+ return initialValue;
372
+ }
373
+
374
+ const reducerSource = validateTaskHandler(
375
+ reducer as unknown as WorkerTaskHandler<unknown, unknown>
376
+ );
377
+
378
+ return runTask(
379
+ executeSerializedReduce,
380
+ {
381
+ initialValue,
382
+ reducerSource,
383
+ values,
384
+ },
385
+ options
386
+ ) as Promise<TAccumulator>;
387
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Optional concurrency helpers built on zero-build Web Workers.
3
+ *
4
+ * The concurrency surface intentionally stays browser-first and explicit:
5
+ * worker tasks, RPC helpers, bounded pools, and thin high-level helpers
6
+ * without decorators, hidden global runtimes, or build-time worker glue.
7
+ *
8
+ * @module bquery/concurrency
9
+ */
10
+
11
+ export {
12
+ TaskWorkerAbortError,
13
+ TaskWorkerError,
14
+ TaskWorkerSerializationError,
15
+ TaskWorkerTimeoutError,
16
+ TaskWorkerUnsupportedError,
17
+ } from './errors';
18
+ export { batchTasks, every, filter, find, map, parallel, reduce, some } from './high-level';
19
+ export { pipeline } from './pipeline';
20
+ export { createRpcPool, createTaskPool } from './pool';
21
+ export {
22
+ createReactiveRpcPool,
23
+ createReactiveRpcWorker,
24
+ createReactiveTaskPool,
25
+ createReactiveTaskWorker,
26
+ } from './reactive';
27
+ export { callWorkerMethod, createRpcWorker } from './rpc';
28
+ export { getConcurrencySupport, isConcurrencySupported } from './support';
29
+ export { createTaskWorker, runTask } from './task';
30
+
31
+ export type {
32
+ CallWorkerMethodOptions,
33
+ ConcurrencyPipeline,
34
+ ConcurrencyPipelineOptions,
35
+ ConcurrencySupport,
36
+ CreateRpcPoolOptions,
37
+ CreateRpcWorkerOptions,
38
+ CreateTaskPoolOptions,
39
+ CreateTaskWorkerOptions,
40
+ ParallelCollectionOptions,
41
+ ParallelMapHandler,
42
+ ParallelMapOptions,
43
+ ParallelOptions,
44
+ ParallelPredicateHandler,
45
+ ParallelReduceHandler,
46
+ ParallelResults,
47
+ ParallelTask,
48
+ ReactiveRpcPool,
49
+ ReactiveRpcWorker,
50
+ ReactiveTaskPool,
51
+ ReactiveTaskWorker,
52
+ RpcPool,
53
+ RpcWorker,
54
+ RunTaskOptions,
55
+ TaskPool,
56
+ TaskRunOptions,
57
+ TaskWorker,
58
+ TaskWorkerErrorCode,
59
+ TaskWorkerState,
60
+ WorkerRpcHandler,
61
+ WorkerRpcHandlers,
62
+ WorkerTaskHandler,
63
+ } from './types';
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Internal helpers shared across concurrency implementations.
3
+ *
4
+ * @internal
5
+ */
6
+
7
+ import { TaskWorkerError, TaskWorkerSerializationError, TaskWorkerTimeoutError } from './errors';
8
+ import type { TaskWorkerErrorCode, WorkerTaskHandler } from './types';
9
+
10
+ /** @internal */
11
+ export interface SerializedWorkerError {
12
+ /** Untrusted serialized worker payload; validate against TaskWorkerErrorCode before use. */
13
+ code?: string;
14
+ message?: string;
15
+ name?: string;
16
+ stack?: string;
17
+ }
18
+
19
+ const TASK_WORKER_ERROR_CODES = new Set<TaskWorkerErrorCode>([
20
+ 'ABORT',
21
+ 'BUSY',
22
+ 'METHOD_NOT_FOUND',
23
+ 'QUEUE_CLEARED',
24
+ 'QUEUE_FULL',
25
+ 'SERIALIZATION',
26
+ 'TERMINATED',
27
+ 'TIMEOUT',
28
+ 'UNSUPPORTED',
29
+ 'WORKER',
30
+ ]);
31
+
32
+ const NATIVE_FUNCTION_SOURCE_RE = /\{\s*\[native code\]\s*\}$/u;
33
+
34
+ /** @internal */
35
+ export const isTaskWorkerErrorCode = (code: string | undefined): code is TaskWorkerErrorCode => {
36
+ return typeof code === 'string' && TASK_WORKER_ERROR_CODES.has(code as TaskWorkerErrorCode);
37
+ };
38
+
39
+ /** @internal */
40
+ export const normalizeTimeout = (timeout?: number): number | undefined => {
41
+ if (typeof timeout !== 'number' || !Number.isFinite(timeout) || timeout <= 0) {
42
+ return undefined;
43
+ }
44
+
45
+ return timeout;
46
+ };
47
+
48
+ /** @internal */
49
+ export const validateTaskHandler = <TInput, TResult>(
50
+ handler: WorkerTaskHandler<TInput, TResult>
51
+ ): string => {
52
+ const source = Function.prototype.toString.call(handler).trim();
53
+
54
+ if (!source || NATIVE_FUNCTION_SOURCE_RE.test(source)) {
55
+ throw new TaskWorkerSerializationError(
56
+ 'Task handlers must be standalone user-defined functions or arrow functions.'
57
+ );
58
+ }
59
+
60
+ try {
61
+ const revived = new Function(`return (${source});`)() as unknown;
62
+ if (typeof revived !== 'function') {
63
+ throw new TypeError('Task handler did not revive as a function.');
64
+ }
65
+ } catch (error) {
66
+ throw new TaskWorkerSerializationError(
67
+ 'Task handlers must be standalone functions that can be reconstructed in a worker context.',
68
+ error
69
+ );
70
+ }
71
+
72
+ return source;
73
+ };
74
+
75
+ /** @internal */
76
+ export const createWorkerInstance = (scriptSource: string, name?: string): Worker => {
77
+ const blob = new Blob([scriptSource], { type: 'text/javascript' });
78
+ const scriptUrl = URL.createObjectURL(blob);
79
+
80
+ try {
81
+ return new Worker(scriptUrl, name ? { name } : undefined);
82
+ } finally {
83
+ URL.revokeObjectURL(scriptUrl);
84
+ }
85
+ };
86
+
87
+ /** @internal */
88
+ export const restoreWorkerError = (payload: SerializedWorkerError | undefined): TaskWorkerError => {
89
+ const message = payload?.message || 'Worker task failed.';
90
+ const code = isTaskWorkerErrorCode(payload?.code) ? payload.code : 'WORKER';
91
+ const error =
92
+ code === 'TIMEOUT' ? new TaskWorkerTimeoutError(message) : new TaskWorkerError(message, code);
93
+
94
+ error.name = payload?.name || error.name;
95
+ if (payload?.stack) {
96
+ error.stack = payload.stack;
97
+ }
98
+
99
+ return error;
100
+ };