web-csv-toolbox 0.13.0-next-bd865d6ddb1cf9691d7b9a83d0790651f074dd47 → 0.13.0-next-b21b6d89a7a3f18dcbf79ec04ffefde0d7ff4c4c

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 (187) hide show
  1. package/README.md +132 -6
  2. package/dist/CSVLexer.js.map +1 -1
  3. package/dist/CSVLexerTransformer.d.ts +52 -3
  4. package/dist/CSVLexerTransformer.js +58 -13
  5. package/dist/CSVLexerTransformer.js.map +1 -1
  6. package/dist/CSVRecordAssembler.js.map +1 -1
  7. package/dist/CSVRecordAssemblerTransformer.d.ts +49 -3
  8. package/dist/CSVRecordAssemblerTransformer.js +55 -18
  9. package/dist/CSVRecordAssemblerTransformer.js.map +1 -1
  10. package/dist/_virtual/web_csv_toolbox_wasm_bg.wasm.js +1 -1
  11. package/dist/assertCommonOptions.js.map +1 -1
  12. package/dist/common/constants.js.map +1 -1
  13. package/dist/common/errors.js.map +1 -1
  14. package/dist/common/types.d.ts +336 -14
  15. package/dist/commonParseErrorHandling.js.map +1 -1
  16. package/dist/constants.js.map +1 -1
  17. package/dist/createWorker.node.d.ts +2 -0
  18. package/dist/createWorker.web.d.ts +2 -0
  19. package/dist/execution/EnginePresets.d.ts +143 -0
  20. package/dist/execution/EnginePresets.js +129 -0
  21. package/dist/execution/EnginePresets.js.map +1 -0
  22. package/dist/execution/InternalEngineConfig.d.ts +89 -0
  23. package/dist/execution/InternalEngineConfig.js +175 -0
  24. package/dist/execution/InternalEngineConfig.js.map +1 -0
  25. package/dist/execution/main/parseBinaryInMain.d.ts +12 -0
  26. package/dist/execution/main/parseStreamInMain.d.ts +12 -0
  27. package/dist/execution/main/parseStringInMain.d.ts +12 -0
  28. package/dist/execution/main/parseUint8ArrayStreamInMain.d.ts +12 -0
  29. package/dist/execution/wasm/parseBinaryInWASM.d.ts +18 -0
  30. package/dist/execution/wasm/parseBinaryInWASM.js +15 -0
  31. package/dist/execution/wasm/parseBinaryInWASM.js.map +1 -0
  32. package/dist/execution/wasm/parseStringInWASM.d.ts +16 -0
  33. package/dist/execution/worker/helpers/ReusableWorkerPool.d.ts +152 -0
  34. package/dist/execution/worker/helpers/ReusableWorkerPool.js +238 -0
  35. package/dist/execution/worker/helpers/ReusableWorkerPool.js.map +1 -0
  36. package/dist/execution/worker/helpers/TransientWorkerPool.d.ts +89 -0
  37. package/dist/execution/worker/helpers/WorkerManager.d.ts +27 -0
  38. package/dist/execution/worker/helpers/WorkerPool.d.ts +50 -0
  39. package/dist/execution/worker/helpers/WorkerSession.d.ts +78 -0
  40. package/dist/execution/worker/helpers/WorkerSession.js +58 -0
  41. package/dist/execution/worker/helpers/WorkerSession.js.map +1 -0
  42. package/dist/execution/worker/helpers/createWorker.node.d.ts +8 -0
  43. package/dist/execution/worker/helpers/createWorker.node.js +15 -0
  44. package/dist/execution/worker/helpers/createWorker.node.js.map +1 -0
  45. package/dist/execution/worker/helpers/createWorker.web.d.ts +8 -0
  46. package/dist/execution/worker/helpers/createWorker.web.js +11 -0
  47. package/dist/execution/worker/helpers/createWorker.web.js.map +1 -0
  48. package/dist/execution/worker/helpers/worker.node.d.ts +1 -0
  49. package/dist/execution/worker/helpers/worker.node.js +11 -0
  50. package/dist/execution/worker/helpers/worker.node.js.map +1 -0
  51. package/dist/execution/worker/helpers/worker.shared.d.ts +90 -0
  52. package/dist/execution/worker/helpers/worker.shared.js +241 -0
  53. package/dist/execution/worker/helpers/worker.shared.js.map +1 -0
  54. package/dist/execution/worker/helpers/worker.web.d.ts +1 -0
  55. package/dist/execution/worker/helpers/worker.web.js +16 -0
  56. package/dist/execution/worker/helpers/worker.web.js.map +1 -0
  57. package/dist/execution/worker/parseBinaryInWorker.node.d.ts +8 -0
  58. package/dist/execution/worker/parseBinaryInWorker.node.js +24 -0
  59. package/dist/execution/worker/parseBinaryInWorker.node.js.map +1 -0
  60. package/dist/execution/worker/parseBinaryInWorker.web.d.ts +8 -0
  61. package/dist/execution/worker/parseBinaryInWorker.web.js +24 -0
  62. package/dist/execution/worker/parseBinaryInWorker.web.js.map +1 -0
  63. package/dist/execution/worker/parseBinaryInWorkerWASM.node.d.ts +8 -0
  64. package/dist/execution/worker/parseBinaryInWorkerWASM.node.js +24 -0
  65. package/dist/execution/worker/parseBinaryInWorkerWASM.node.js.map +1 -0
  66. package/dist/execution/worker/parseBinaryInWorkerWASM.web.d.ts +8 -0
  67. package/dist/execution/worker/parseBinaryInWorkerWASM.web.js +24 -0
  68. package/dist/execution/worker/parseBinaryInWorkerWASM.web.js.map +1 -0
  69. package/dist/execution/worker/parseStreamInWorker.node.d.ts +15 -0
  70. package/dist/execution/worker/parseStreamInWorker.node.js +26 -0
  71. package/dist/execution/worker/parseStreamInWorker.node.js.map +1 -0
  72. package/dist/execution/worker/parseStreamInWorker.web.d.ts +12 -0
  73. package/dist/execution/worker/parseStreamInWorker.web.js +25 -0
  74. package/dist/execution/worker/parseStreamInWorker.web.js.map +1 -0
  75. package/dist/execution/worker/parseStringInWorker.node.d.ts +11 -0
  76. package/dist/execution/worker/parseStringInWorker.node.js +24 -0
  77. package/dist/execution/worker/parseStringInWorker.node.js.map +1 -0
  78. package/dist/execution/worker/parseStringInWorker.web.d.ts +11 -0
  79. package/dist/execution/worker/parseStringInWorker.web.js +24 -0
  80. package/dist/execution/worker/parseStringInWorker.web.js.map +1 -0
  81. package/dist/execution/worker/parseStringInWorkerWASM.node.d.ts +8 -0
  82. package/dist/execution/worker/parseStringInWorkerWASM.node.js +24 -0
  83. package/dist/execution/worker/parseStringInWorkerWASM.node.js.map +1 -0
  84. package/dist/execution/worker/parseStringInWorkerWASM.web.d.ts +8 -0
  85. package/dist/execution/worker/parseStringInWorkerWASM.web.js +24 -0
  86. package/dist/execution/worker/parseStringInWorkerWASM.web.js.map +1 -0
  87. package/dist/execution/worker/parseUint8ArrayStreamInWorker.node.d.ts +12 -0
  88. package/dist/execution/worker/parseUint8ArrayStreamInWorker.node.js +26 -0
  89. package/dist/execution/worker/parseUint8ArrayStreamInWorker.node.js.map +1 -0
  90. package/dist/execution/worker/parseUint8ArrayStreamInWorker.web.d.ts +9 -0
  91. package/dist/execution/worker/parseUint8ArrayStreamInWorker.web.js +25 -0
  92. package/dist/execution/worker/parseUint8ArrayStreamInWorker.web.js.map +1 -0
  93. package/dist/execution/worker/strategies/MessageStreamingStrategy.d.ts +17 -0
  94. package/dist/execution/worker/strategies/MessageStreamingStrategy.js +58 -0
  95. package/dist/execution/worker/strategies/MessageStreamingStrategy.js.map +1 -0
  96. package/dist/execution/worker/strategies/TransferableStreamStrategy.d.ts +25 -0
  97. package/dist/execution/worker/strategies/TransferableStreamStrategy.js +159 -0
  98. package/dist/execution/worker/strategies/TransferableStreamStrategy.js.map +1 -0
  99. package/dist/execution/worker/strategies/WorkerStrategy.d.ts +27 -0
  100. package/dist/execution/worker/strategies/WorkerStrategySelector.d.ts +43 -0
  101. package/dist/execution/worker/strategies/WorkerStrategySelector.js +89 -0
  102. package/dist/execution/worker/strategies/WorkerStrategySelector.js.map +1 -0
  103. package/dist/execution/worker/utils/messageHandler.d.ts +21 -0
  104. package/dist/execution/worker/utils/messageHandler.js +109 -0
  105. package/dist/execution/worker/utils/messageHandler.js.map +1 -0
  106. package/dist/execution/worker/utils/serializeOptions.d.ts +9 -0
  107. package/dist/execution/worker/utils/serializeOptions.js +14 -0
  108. package/dist/execution/worker/utils/serializeOptions.js.map +1 -0
  109. package/dist/execution/worker/utils/streamCollector.node.d.ts +14 -0
  110. package/dist/execution/worker/utils/streamCollector.node.js +78 -0
  111. package/dist/execution/worker/utils/streamCollector.node.js.map +1 -0
  112. package/dist/execution/worker/utils/workerUtils.d.ts +14 -0
  113. package/dist/execution/worker/utils/workerUtils.js +25 -0
  114. package/dist/execution/worker/utils/workerUtils.js.map +1 -0
  115. package/dist/getOptionsFromResponse.constants.node.d.ts +10 -0
  116. package/dist/getOptionsFromResponse.constants.node.js +8 -0
  117. package/dist/getOptionsFromResponse.constants.node.js.map +1 -0
  118. package/dist/getOptionsFromResponse.constants.web.d.ts +30 -0
  119. package/dist/getOptionsFromResponse.constants.web.js +7 -0
  120. package/dist/getOptionsFromResponse.constants.web.js.map +1 -0
  121. package/dist/getOptionsFromResponse.d.ts +2 -1
  122. package/dist/getOptionsFromResponse.js +5 -9
  123. package/dist/getOptionsFromResponse.js.map +1 -1
  124. package/dist/loadWASM.js.map +1 -1
  125. package/dist/loadWASM.web.js.map +1 -1
  126. package/dist/parse.d.ts +1 -1
  127. package/dist/parse.js +29 -5
  128. package/dist/parse.js.map +1 -1
  129. package/dist/parseBinary.d.ts +2 -1
  130. package/dist/parseBinary.js +32 -3
  131. package/dist/parseBinary.js.map +1 -1
  132. package/dist/parseBinaryInWorker.node.d.ts +2 -0
  133. package/dist/parseBinaryInWorker.web.d.ts +2 -0
  134. package/dist/parseBinaryInWorkerWASM.node.d.ts +2 -0
  135. package/dist/parseBinaryInWorkerWASM.web.d.ts +2 -0
  136. package/dist/parseBinaryToArraySync.d.ts +2 -1
  137. package/dist/parseBinaryToArraySync.js.map +1 -1
  138. package/dist/parseBinaryToIterableIterator.d.ts +2 -1
  139. package/dist/parseBinaryToIterableIterator.js.map +1 -1
  140. package/dist/parseBinaryToStream.d.ts +2 -1
  141. package/dist/parseBinaryToStream.js.map +1 -1
  142. package/dist/parseResponse.d.ts +1 -1
  143. package/dist/parseResponse.js +15 -8
  144. package/dist/parseResponse.js.map +1 -1
  145. package/dist/parseResponseToStream.d.ts +2 -1
  146. package/dist/parseResponseToStream.js.map +1 -1
  147. package/dist/parseStreamInWorker.node.d.ts +2 -0
  148. package/dist/parseStreamInWorker.web.d.ts +2 -0
  149. package/dist/parseString.d.ts +31 -0
  150. package/dist/parseString.js +27 -1
  151. package/dist/parseString.js.map +1 -1
  152. package/dist/parseStringInWorker.node.d.ts +2 -0
  153. package/dist/parseStringInWorker.web.d.ts +2 -0
  154. package/dist/parseStringInWorkerWASM.node.d.ts +2 -0
  155. package/dist/parseStringInWorkerWASM.web.d.ts +2 -0
  156. package/dist/parseStringStream.d.ts +43 -1
  157. package/dist/parseStringStream.js +24 -3
  158. package/dist/parseStringStream.js.map +1 -1
  159. package/dist/parseStringStreamToStream.js.map +1 -1
  160. package/dist/parseStringToArraySync.js.map +1 -1
  161. package/dist/parseStringToArraySyncWASM.js.map +1 -1
  162. package/dist/parseStringToIterableIterator.js.map +1 -1
  163. package/dist/parseStringToStream.js.map +1 -1
  164. package/dist/parseUint8ArrayStream.d.ts +4 -3
  165. package/dist/parseUint8ArrayStream.js +24 -3
  166. package/dist/parseUint8ArrayStream.js.map +1 -1
  167. package/dist/parseUint8ArrayStreamInWorker.node.d.ts +2 -0
  168. package/dist/parseUint8ArrayStreamInWorker.web.d.ts +2 -0
  169. package/dist/parseUint8ArrayStreamToStream.d.ts +2 -1
  170. package/dist/parseUint8ArrayStreamToStream.js +11 -5
  171. package/dist/parseUint8ArrayStreamToStream.js.map +1 -1
  172. package/dist/utils/convertBinaryToString.js.map +1 -1
  173. package/dist/utils/convertIterableIteratorToAsync.js.map +1 -1
  174. package/dist/utils/convertStreamToAsyncIterableIterator.js +2 -2
  175. package/dist/utils/convertStreamToAsyncIterableIterator.js.map +1 -1
  176. package/dist/utils/convertThisAsyncIterableIteratorToArray.d.ts +1 -1
  177. package/dist/utils/convertThisAsyncIterableIteratorToArray.js.map +1 -1
  178. package/dist/utils/escapeRegExp.js.map +1 -1
  179. package/dist/utils/parseMime.js.map +1 -1
  180. package/dist/utils/pipeline.js.map +1 -1
  181. package/dist/web-csv-toolbox.d.ts +4 -0
  182. package/dist/web-csv-toolbox.js +3 -0
  183. package/dist/web-csv-toolbox.js.map +1 -1
  184. package/dist/web_csv_toolbox_wasm_bg.wasm +0 -0
  185. package/dist/worker.node.d.ts +1 -0
  186. package/dist/worker.web.d.ts +1 -0
  187. package/package.json +53 -10
@@ -0,0 +1,238 @@
1
+ import { createWorker } from './createWorker.web.js';
2
+
3
+ class ReusableWorkerPool {
4
+ workers = [];
5
+ requestId = 0;
6
+ currentWorkerIndex = 0;
7
+ maxWorkers;
8
+ customWorkerURL;
9
+ pendingWorkerCreations = /* @__PURE__ */ new Map();
10
+ pendingCreationsByURL = /* @__PURE__ */ new Map();
11
+ disposed = false;
12
+ nextPendingId = 0;
13
+ /**
14
+ * Create a new ReusableWorkerPool.
15
+ *
16
+ * @param options - Configuration options for the pool
17
+ */
18
+ constructor(options = {}) {
19
+ this.maxWorkers = options.maxWorkers ?? 1;
20
+ this.customWorkerURL = options.workerURL;
21
+ if (this.maxWorkers < 1) {
22
+ throw new Error("maxWorkers must be at least 1");
23
+ }
24
+ }
25
+ /**
26
+ * Get a worker instance from the pool using round-robin load balancing.
27
+ *
28
+ * @param workerURL - Optional custom worker URL (overrides pool's workerURL)
29
+ * @returns A worker instance from the pool
30
+ * @internal
31
+ */
32
+ async getWorker(workerURL) {
33
+ if (this.disposed) {
34
+ throw new Error("Worker pool has been disposed");
35
+ }
36
+ const effectiveURL = workerURL ?? this.customWorkerURL;
37
+ const urlKey = effectiveURL ? String(effectiveURL) : "default";
38
+ const matchingWorkers = this.workers.filter(
39
+ (entry) => entry.url === urlKey
40
+ );
41
+ const totalWorkers = this.workers.length + this.pendingWorkerCreations.size;
42
+ if (totalWorkers < this.maxWorkers) {
43
+ const pendingId = `${urlKey}-${this.nextPendingId++}`;
44
+ const workerPromise = createWorker(effectiveURL).then((worker) => {
45
+ if (this.disposed) {
46
+ worker.terminate();
47
+ throw new Error("Worker pool was disposed during worker creation");
48
+ }
49
+ this.workers.push({ worker, url: urlKey });
50
+ this.pendingWorkerCreations.delete(pendingId);
51
+ const urlPendings2 = this.pendingCreationsByURL.get(urlKey);
52
+ if (urlPendings2) {
53
+ urlPendings2.delete(pendingId);
54
+ if (urlPendings2.size === 0) {
55
+ this.pendingCreationsByURL.delete(urlKey);
56
+ }
57
+ }
58
+ return worker;
59
+ }).catch((error) => {
60
+ this.pendingWorkerCreations.delete(pendingId);
61
+ const urlPendings2 = this.pendingCreationsByURL.get(urlKey);
62
+ if (urlPendings2) {
63
+ urlPendings2.delete(pendingId);
64
+ if (urlPendings2.size === 0) {
65
+ this.pendingCreationsByURL.delete(urlKey);
66
+ }
67
+ }
68
+ throw error;
69
+ });
70
+ this.pendingWorkerCreations.set(pendingId, workerPromise);
71
+ if (!this.pendingCreationsByURL.has(urlKey)) {
72
+ this.pendingCreationsByURL.set(urlKey, /* @__PURE__ */ new Set());
73
+ }
74
+ this.pendingCreationsByURL.get(urlKey).add(pendingId);
75
+ return workerPromise;
76
+ }
77
+ if (matchingWorkers.length === 0) {
78
+ const urlPendings2 = this.pendingCreationsByURL.get(urlKey);
79
+ if (urlPendings2 && urlPendings2.size > 0) {
80
+ const pendingId = Array.from(urlPendings2)[0];
81
+ const pendingWorker = this.pendingWorkerCreations.get(pendingId);
82
+ if (pendingWorker) {
83
+ return pendingWorker;
84
+ }
85
+ }
86
+ throw new Error(
87
+ `Worker pool is at maximum capacity (${this.maxWorkers}) and no worker with URL "${urlKey}" is available`
88
+ );
89
+ }
90
+ const urlPendings = this.pendingCreationsByURL.get(urlKey);
91
+ if (urlPendings && urlPendings.size > 0) {
92
+ const pendingId = Array.from(urlPendings)[0];
93
+ const pendingWorker = this.pendingWorkerCreations.get(pendingId);
94
+ if (pendingWorker) {
95
+ await pendingWorker;
96
+ const updatedMatchingWorkers = this.workers.filter(
97
+ (entry) => entry.url === urlKey
98
+ );
99
+ if (updatedMatchingWorkers.length === 0) {
100
+ throw new Error(
101
+ `Worker pool was disposed or worker creation failed for URL "${urlKey}"`
102
+ );
103
+ }
104
+ }
105
+ }
106
+ const matchingIndices = this.workers.map((entry, index) => entry.url === urlKey ? index : -1).filter((index) => index !== -1);
107
+ let selectedIndex = matchingIndices[0];
108
+ for (const index of matchingIndices) {
109
+ if (index >= this.currentWorkerIndex) {
110
+ selectedIndex = index;
111
+ break;
112
+ }
113
+ }
114
+ this.currentWorkerIndex = (selectedIndex + 1) % this.workers.length;
115
+ return this.workers[selectedIndex].worker;
116
+ }
117
+ /**
118
+ * Get the next request ID for this pool.
119
+ *
120
+ * @returns The next request ID
121
+ * @internal
122
+ */
123
+ getNextRequestId() {
124
+ return this.requestId++;
125
+ }
126
+ /**
127
+ * Release a worker back to the pool.
128
+ * For ReusableWorkerPool, this does nothing as workers are kept alive and reused.
129
+ *
130
+ * @param _worker - The worker to release
131
+ * @internal
132
+ */
133
+ releaseWorker(_worker) {
134
+ }
135
+ /**
136
+ * Get the current number of workers in the pool.
137
+ *
138
+ * @returns The number of active workers
139
+ */
140
+ get size() {
141
+ return this.workers.length;
142
+ }
143
+ /**
144
+ * Check if the pool has reached its maximum capacity.
145
+ *
146
+ * @returns True if the pool is at maximum capacity, false otherwise
147
+ *
148
+ * @remarks
149
+ * This method is useful for implementing early rejection of requests
150
+ * when the worker pool is saturated, preventing resource exhaustion.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * import { Hono } from 'hono';
155
+ * import { ReusableWorkerPool } from 'web-csv-toolbox';
156
+ *
157
+ * const pool = new ReusableWorkerPool({ maxWorkers: 4 });
158
+ *
159
+ * app.post('/validate-csv', async (c) => {
160
+ * // Early rejection if pool is saturated
161
+ * if (pool.isFull()) {
162
+ * return c.json({ error: 'Service busy, please try again later' }, 503);
163
+ * }
164
+ *
165
+ * // Process CSV...
166
+ * });
167
+ * ```
168
+ */
169
+ isFull() {
170
+ const totalWorkers = this.workers.length + this.pendingWorkerCreations.size;
171
+ return totalWorkers >= this.maxWorkers;
172
+ }
173
+ /**
174
+ * Terminate all workers in the pool and clean up resources.
175
+ *
176
+ * This method should be called when the pool is no longer needed,
177
+ * typically during application shutdown.
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const pool = new ReusableWorkerPool({ maxWorkers: 4 });
182
+ *
183
+ * // When shutting down
184
+ * pool.terminate();
185
+ * ```
186
+ *
187
+ * @example With Hono
188
+ * ```ts
189
+ * import { Hono } from 'hono';
190
+ * import { ReusableWorkerPool } from 'web-csv-toolbox';
191
+ *
192
+ * const app = new Hono();
193
+ * const pool = new ReusableWorkerPool({ maxWorkers: 4 });
194
+ *
195
+ * app.onShutdown(() => {
196
+ * pool.terminate();
197
+ * });
198
+ * ```
199
+ */
200
+ terminate() {
201
+ this.disposed = true;
202
+ for (const entry of this.workers) {
203
+ entry.worker.terminate();
204
+ }
205
+ this.workers = [];
206
+ this.currentWorkerIndex = 0;
207
+ this.pendingWorkerCreations.clear();
208
+ this.pendingCreationsByURL.clear();
209
+ }
210
+ /**
211
+ * Dispose of the worker pool, terminating all workers.
212
+ *
213
+ * This method is called automatically when using the `using` syntax.
214
+ * For manual cleanup, use {@link terminate} instead.
215
+ *
216
+ * @example With `using` syntax (automatic cleanup)
217
+ * ```ts
218
+ * using pool = new ReusableWorkerPool({ maxWorkers: 4 });
219
+ * // Workers are automatically terminated when leaving scope
220
+ * ```
221
+ *
222
+ * @example Manual cleanup
223
+ * ```ts
224
+ * const pool = new ReusableWorkerPool({ maxWorkers: 4 });
225
+ * try {
226
+ * // Use pool
227
+ * } finally {
228
+ * pool.terminate(); // Preferred over pool[Symbol.dispose]()
229
+ * }
230
+ * ```
231
+ */
232
+ [Symbol.dispose]() {
233
+ this.terminate();
234
+ }
235
+ }
236
+
237
+ export { ReusableWorkerPool };
238
+ //# sourceMappingURL=ReusableWorkerPool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReusableWorkerPool.js","sources":["../../../../src/execution/worker/helpers/ReusableWorkerPool.ts"],"sourcesContent":["import { createWorker } from \"#execution/worker/createWorker.js\";\nimport type { WorkerPool } from \"./WorkerPool.ts\";\n\n/**\n * Options for configuring the ReusableWorkerPool.\n */\nexport interface ReusableWorkerPoolOptions {\n /**\n * Maximum number of worker instances in the pool.\n *\n * @default 1\n *\n * @remarks\n * **Security Recommendation:**\n * For production applications that accept user uploads, set this to a reasonable limit (e.g., 2-4)\n * to prevent resource exhaustion attacks. Without limits, malicious users could spawn unlimited\n * workers by uploading multiple large CSV files simultaneously, leading to memory exhaustion and DoS.\n *\n * @example\n * ```ts\n * // Recommended for production\n * const pool = new ReusableReusableWorkerPool({ maxWorkers: 4 });\n * ```\n */\n maxWorkers?: number;\n\n /**\n * Custom worker URL to use for all workers in the pool.\n */\n workerURL?: string | URL;\n}\n\n/**\n * A pool that manages multiple worker instances with automatic cleanup and load balancing.\n *\n * This class implements the Disposable interface, allowing automatic\n * worker termination when the pool goes out of scope using the `using` syntax.\n *\n * @remarks\n * **⚠️ Security: Resource Protection**\n *\n * When building applications that accept user-uploaded CSV files, it is **strongly recommended**\n * to use `ReusableWorkerPool` with a limited `maxWorkers` setting to protect against resource exhaustion attacks.\n *\n * **Why this matters:**\n * - Attackers can upload multiple large CSV files simultaneously to overwhelm your application\n * - Without `ReusableWorkerPool` limits, each request could spawn unlimited workers\n * - This leads to excessive memory consumption, CPU exhaustion, and potential DoS\n *\n * **Recommended settings:**\n * - Web applications: `maxWorkers: 2-4`\n * - Server applications: `Math.min(4, os.cpus().length)`\n * - High-security environments: `maxWorkers: 1`\n *\n * See {@link https://github.com/kamiazya/web-csv-toolbox/blob/main/SECURITY.md | SECURITY.md} for detailed security guidelines.\n *\n * @example Basic usage with automatic cleanup (single worker)\n * ```ts\n * import { ReusableWorkerPool, parseString } from 'web-csv-toolbox';\n *\n * async function processCSV(csv: string) {\n * using pool = new ReusableWorkerPool();\n *\n * const records = [];\n * for await (const record of parseString(csv, {\n * execution: ['worker'],\n * workerPool: pool\n * })) {\n * records.push(record);\n * }\n *\n * return records;\n * // Worker is automatically terminated when leaving this scope\n * }\n * ```\n *\n * @example Parallel processing with multiple workers\n * ```ts\n * import { ReusableWorkerPool, parseString } from 'web-csv-toolbox';\n *\n * async function processMultipleCSVs(csvFiles: string[]) {\n * using pool = new ReusableWorkerPool({ maxWorkers: 4 });\n *\n * // Process all CSV files in parallel\n * const results = await Promise.all(\n * csvFiles.map(async (csv) => {\n * const records = [];\n * for await (const record of parseString(csv, {\n * execution: ['worker'],\n * workerPool: pool\n * })) {\n * records.push(record);\n * }\n * return records;\n * })\n * );\n *\n * return results;\n * // All workers are automatically terminated when leaving this scope\n * }\n * ```\n *\n * @example Manual cleanup (if not using `using` syntax)\n * ```ts\n * import { ReusableWorkerPool, parseString } from 'web-csv-toolbox';\n *\n * async function processCSV(csv: string) {\n * const pool = new ReusableWorkerPool({ maxWorkers: 2 });\n *\n * try {\n * const records = [];\n * for await (const record of parseString(csv, {\n * execution: ['worker'],\n * workerPool: pool\n * })) {\n * records.push(record);\n * }\n * return records;\n * } finally {\n * pool[Symbol.dispose]();\n * }\n * }\n * ```\n */\ninterface WorkerEntry {\n worker: Worker;\n url: string;\n}\n\nexport class ReusableWorkerPool implements WorkerPool, Disposable {\n private workers: WorkerEntry[] = [];\n private requestId = 0;\n private currentWorkerIndex = 0;\n private readonly maxWorkers: number;\n private readonly customWorkerURL?: string | URL;\n private pendingWorkerCreations: Map<string, Promise<Worker>> = new Map();\n private pendingCreationsByURL: Map<string, Set<string>> = new Map();\n private disposed = false;\n private nextPendingId = 0;\n\n /**\n * Create a new ReusableWorkerPool.\n *\n * @param options - Configuration options for the pool\n */\n constructor(options: ReusableWorkerPoolOptions = {}) {\n this.maxWorkers = options.maxWorkers ?? 1;\n this.customWorkerURL = options.workerURL;\n\n if (this.maxWorkers < 1) {\n throw new Error(\"maxWorkers must be at least 1\");\n }\n }\n\n /**\n * Get a worker instance from the pool using round-robin load balancing.\n *\n * @param workerURL - Optional custom worker URL (overrides pool's workerURL)\n * @returns A worker instance from the pool\n * @internal\n */\n async getWorker(workerURL?: string | URL): Promise<Worker> {\n if (this.disposed) {\n throw new Error(\"Worker pool has been disposed\");\n }\n\n const effectiveURL = workerURL ?? this.customWorkerURL;\n const urlKey = effectiveURL ? String(effectiveURL) : \"default\";\n\n // Find workers that match the requested URL\n const matchingWorkers = this.workers.filter(\n (entry) => entry.url === urlKey,\n );\n\n // Calculate total workers including pending creations\n const totalWorkers = this.workers.length + this.pendingWorkerCreations.size;\n\n // If pool is not yet full, create a new worker\n if (totalWorkers < this.maxWorkers) {\n // Generate unique key for this pending creation\n const pendingId = `${urlKey}-${this.nextPendingId++}`;\n\n // Create a new worker and cache the promise\n const workerPromise = createWorker(effectiveURL)\n .then((worker) => {\n if (this.disposed) {\n // If disposed during creation, terminate the new worker immediately\n worker.terminate();\n throw new Error(\"Worker pool was disposed during worker creation\");\n }\n this.workers.push({ worker, url: urlKey });\n this.pendingWorkerCreations.delete(pendingId);\n // Remove from URL-based tracking\n const urlPendings = this.pendingCreationsByURL.get(urlKey);\n if (urlPendings) {\n urlPendings.delete(pendingId);\n if (urlPendings.size === 0) {\n this.pendingCreationsByURL.delete(urlKey);\n }\n }\n return worker;\n })\n .catch((error) => {\n // Clean up on error\n this.pendingWorkerCreations.delete(pendingId);\n const urlPendings = this.pendingCreationsByURL.get(urlKey);\n if (urlPendings) {\n urlPendings.delete(pendingId);\n if (urlPendings.size === 0) {\n this.pendingCreationsByURL.delete(urlKey);\n }\n }\n throw error;\n });\n\n this.pendingWorkerCreations.set(pendingId, workerPromise);\n // Track by URL for future matching\n if (!this.pendingCreationsByURL.has(urlKey)) {\n this.pendingCreationsByURL.set(urlKey, new Set());\n }\n this.pendingCreationsByURL.get(urlKey)!.add(pendingId);\n\n return workerPromise;\n }\n\n // If pool is full and no matching workers exist, check for pending workers with this URL\n if (matchingWorkers.length === 0) {\n const urlPendings = this.pendingCreationsByURL.get(urlKey);\n if (urlPendings && urlPendings.size > 0) {\n // Wait for one of the pending workers with this URL\n const pendingId = Array.from(urlPendings)[0];\n const pendingWorker = this.pendingWorkerCreations.get(pendingId);\n if (pendingWorker) {\n return pendingWorker;\n }\n }\n\n throw new Error(\n `Worker pool is at maximum capacity (${this.maxWorkers}) and no worker with URL \"${urlKey}\" is available`,\n );\n }\n\n // Wait for any pending worker creations with this URL to complete\n const urlPendings = this.pendingCreationsByURL.get(urlKey);\n if (urlPendings && urlPendings.size > 0) {\n const pendingId = Array.from(urlPendings)[0];\n const pendingWorker = this.pendingWorkerCreations.get(pendingId);\n if (pendingWorker) {\n await pendingWorker;\n // Re-fetch matching workers as the pending creation may have completed\n const updatedMatchingWorkers = this.workers.filter(\n (entry) => entry.url === urlKey,\n );\n if (updatedMatchingWorkers.length === 0) {\n throw new Error(\n `Worker pool was disposed or worker creation failed for URL \"${urlKey}\"`,\n );\n }\n }\n }\n\n // Use round-robin among matching workers\n const matchingIndices = this.workers\n .map((entry, index) => (entry.url === urlKey ? index : -1))\n .filter((index) => index !== -1);\n\n // Find next matching worker in round-robin order\n let selectedIndex = matchingIndices[0];\n for (const index of matchingIndices) {\n if (index >= this.currentWorkerIndex) {\n selectedIndex = index;\n break;\n }\n }\n\n this.currentWorkerIndex = (selectedIndex + 1) % this.workers.length;\n return this.workers[selectedIndex].worker;\n }\n\n /**\n * Get the next request ID for this pool.\n *\n * @returns The next request ID\n * @internal\n */\n getNextRequestId(): number {\n return this.requestId++;\n }\n\n /**\n * Release a worker back to the pool.\n * For ReusableWorkerPool, this does nothing as workers are kept alive and reused.\n *\n * @param _worker - The worker to release\n * @internal\n */\n releaseWorker(_worker: Worker): void {\n // ReusableWorkerPool keeps workers alive for reuse\n }\n\n /**\n * Get the current number of workers in the pool.\n *\n * @returns The number of active workers\n */\n get size(): number {\n return this.workers.length;\n }\n\n /**\n * Check if the pool has reached its maximum capacity.\n *\n * @returns True if the pool is at maximum capacity, false otherwise\n *\n * @remarks\n * This method is useful for implementing early rejection of requests\n * when the worker pool is saturated, preventing resource exhaustion.\n *\n * @example\n * ```ts\n * import { Hono } from 'hono';\n * import { ReusableWorkerPool } from 'web-csv-toolbox';\n *\n * const pool = new ReusableWorkerPool({ maxWorkers: 4 });\n *\n * app.post('/validate-csv', async (c) => {\n * // Early rejection if pool is saturated\n * if (pool.isFull()) {\n * return c.json({ error: 'Service busy, please try again later' }, 503);\n * }\n *\n * // Process CSV...\n * });\n * ```\n */\n isFull(): boolean {\n const totalWorkers = this.workers.length + this.pendingWorkerCreations.size;\n return totalWorkers >= this.maxWorkers;\n }\n\n /**\n * Terminate all workers in the pool and clean up resources.\n *\n * This method should be called when the pool is no longer needed,\n * typically during application shutdown.\n *\n * @example\n * ```ts\n * const pool = new ReusableWorkerPool({ maxWorkers: 4 });\n *\n * // When shutting down\n * pool.terminate();\n * ```\n *\n * @example With Hono\n * ```ts\n * import { Hono } from 'hono';\n * import { ReusableWorkerPool } from 'web-csv-toolbox';\n *\n * const app = new Hono();\n * const pool = new ReusableWorkerPool({ maxWorkers: 4 });\n *\n * app.onShutdown(() => {\n * pool.terminate();\n * });\n * ```\n */\n terminate(): void {\n this.disposed = true;\n\n // Terminate all existing workers\n for (const entry of this.workers) {\n entry.worker.terminate();\n }\n this.workers = [];\n this.currentWorkerIndex = 0;\n\n // Reject and clear all pending worker creations\n // Note: The pending promises will handle cleanup via their catch blocks\n this.pendingWorkerCreations.clear();\n this.pendingCreationsByURL.clear();\n }\n\n /**\n * Dispose of the worker pool, terminating all workers.\n *\n * This method is called automatically when using the `using` syntax.\n * For manual cleanup, use {@link terminate} instead.\n *\n * @example With `using` syntax (automatic cleanup)\n * ```ts\n * using pool = new ReusableWorkerPool({ maxWorkers: 4 });\n * // Workers are automatically terminated when leaving scope\n * ```\n *\n * @example Manual cleanup\n * ```ts\n * const pool = new ReusableWorkerPool({ maxWorkers: 4 });\n * try {\n * // Use pool\n * } finally {\n * pool.terminate(); // Preferred over pool[Symbol.dispose]()\n * }\n * ```\n */\n [Symbol.dispose](): void {\n this.terminate();\n }\n}\n"],"names":["urlPendings"],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { WorkerPool } from './WorkerPool.ts';
2
+ /**
3
+ * Options for configuring the TransientWorkerPool.
4
+ */
5
+ export interface TransientWorkerPoolOptions {
6
+ /**
7
+ * Custom worker URL to use for all workers in the pool.
8
+ */
9
+ workerURL?: string | URL;
10
+ }
11
+ /**
12
+ * A pool that creates transient workers which are automatically terminated after each job.
13
+ *
14
+ * This pool is designed for the default worker pool to prevent workers from staying alive
15
+ * and blocking process exit. Each worker is terminated immediately after the job completes.
16
+ *
17
+ * @remarks
18
+ * **Design Rationale:**
19
+ *
20
+ * Unlike {@link ReusableWorkerPool} which keeps workers alive for reuse, `TransientWorkerPool`
21
+ * terminates workers after each job. This is specifically designed for the internal default pool
22
+ * to avoid requiring users to call `terminateWorkers()` for process cleanup.
23
+ *
24
+ * **Characteristics:**
25
+ * - Workers are created on-demand
26
+ * - Workers are terminated immediately after job completion
27
+ * - No persistent worker instances
28
+ * - Prevents process from hanging due to active workers
29
+ *
30
+ * @internal
31
+ */
32
+ export declare class TransientWorkerPool implements WorkerPool, Disposable {
33
+ private requestId;
34
+ private readonly customWorkerURL?;
35
+ /**
36
+ * Create a new TransientWorkerPool.
37
+ *
38
+ * @param options - Configuration options for the pool
39
+ */
40
+ constructor(options?: TransientWorkerPoolOptions);
41
+ /**
42
+ * Get a worker instance.
43
+ * Always creates a new worker for transient usage.
44
+ *
45
+ * @param workerURL - Optional custom worker URL (overrides pool's workerURL)
46
+ * @returns A new worker instance
47
+ * @internal
48
+ */
49
+ getWorker(workerURL?: string | URL): Promise<Worker>;
50
+ /**
51
+ * Get the next request ID for this pool.
52
+ *
53
+ * @returns The next request ID
54
+ * @internal
55
+ */
56
+ getNextRequestId(): number;
57
+ /**
58
+ * Release a worker back to the pool.
59
+ * For TransientWorkerPool, this terminates the worker immediately.
60
+ *
61
+ * @param worker - The worker to release
62
+ * @internal
63
+ */
64
+ releaseWorker(worker: Worker): void;
65
+ /**
66
+ * Get the current number of workers in the pool.
67
+ * For TransientWorkerPool, this is always 0 as workers are not kept alive.
68
+ *
69
+ * @returns Always 0
70
+ */
71
+ get size(): number;
72
+ /**
73
+ * Check if the pool has reached its maximum capacity.
74
+ * For TransientWorkerPool, this is always false as workers are transient.
75
+ *
76
+ * @returns Always false
77
+ */
78
+ isFull(): boolean;
79
+ /**
80
+ * Terminate all workers in the pool.
81
+ * For TransientWorkerPool, this is a no-op as workers are not kept alive.
82
+ */
83
+ terminate(): void;
84
+ /**
85
+ * Dispose of the worker pool.
86
+ * For TransientWorkerPool, this is a no-op as workers are not kept alive.
87
+ */
88
+ [Symbol.dispose](): void;
89
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Get or create a worker instance from the default pool.
3
+ * If WorkerPool is provided in options, use it instead of default pool.
4
+ *
5
+ * @internal
6
+ */
7
+ export declare function getWorker(workerURL?: string | URL): Promise<Worker>;
8
+ /**
9
+ * Get next request ID for message tracking from the default pool.
10
+ *
11
+ * @internal
12
+ */
13
+ export declare function getNextRequestId(): number;
14
+ /**
15
+ * Release a worker back to the default pool.
16
+ * For the default transient pool, this terminates the worker.
17
+ *
18
+ * @internal
19
+ */
20
+ export declare function releaseWorker(worker: Worker): void;
21
+ /**
22
+ * Get the current size of the default pool.
23
+ * This is mainly for testing purposes.
24
+ *
25
+ * @internal
26
+ */
27
+ export declare function getPoolSize(): number;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Common interface for worker pools.
3
+ * Both ReusableWorkerPool and TransientWorkerPool implement this interface.
4
+ *
5
+ * @remarks
6
+ * This interface defines the contract for worker pool implementations.
7
+ * Users typically use {@link ReusableWorkerPool} for persistent worker pools,
8
+ * while the internal default pool uses {@link TransientWorkerPool} for automatic cleanup.
9
+ */
10
+ export interface WorkerPool {
11
+ /**
12
+ * Get a worker instance from the pool.
13
+ *
14
+ * @param workerURL - Optional custom worker URL
15
+ * @returns A worker instance
16
+ */
17
+ getWorker(workerURL?: string | URL): Promise<Worker>;
18
+ /**
19
+ * Get the next request ID for this pool.
20
+ *
21
+ * @returns The next request ID
22
+ */
23
+ getNextRequestId(): number;
24
+ /**
25
+ * Release a worker back to the pool.
26
+ *
27
+ * @param worker - The worker to release
28
+ */
29
+ releaseWorker(worker: Worker): void;
30
+ /**
31
+ * Get the current number of workers in the pool.
32
+ *
33
+ * @returns The number of active workers
34
+ */
35
+ readonly size: number;
36
+ /**
37
+ * Check if the pool has reached its maximum capacity.
38
+ *
39
+ * @returns True if the pool is at maximum capacity, false otherwise
40
+ */
41
+ isFull(): boolean;
42
+ /**
43
+ * Terminate all workers in the pool and clean up resources.
44
+ */
45
+ terminate(): void;
46
+ /**
47
+ * Dispose of the worker pool, terminating all workers.
48
+ */
49
+ [Symbol.dispose](): void;
50
+ }
@@ -0,0 +1,78 @@
1
+ import { WorkerPool } from './WorkerPool.ts';
2
+ /**
3
+ * Options for creating a WorkerSession.
4
+ */
5
+ export interface WorkerSessionOptions {
6
+ /**
7
+ * Custom worker URL.
8
+ */
9
+ workerURL?: string | URL;
10
+ /**
11
+ * Worker pool for reusable workers.
12
+ */
13
+ workerPool?: WorkerPool;
14
+ }
15
+ /**
16
+ * WorkerSession manages the lifecycle of a single worker instance.
17
+ *
18
+ * Hybrid approach:
19
+ * - If workerPool is provided: Use pool's worker (reusable, manual pool cleanup)
20
+ * - If workerPool is NOT provided: Create disposable worker (auto-cleanup on dispose)
21
+ *
22
+ * Use `using` syntax for automatic cleanup:
23
+ * ```typescript
24
+ * using session = await WorkerSession.create();
25
+ * const records = await sendWorkerMessage(session, { ... });
26
+ * // Worker is automatically terminated when leaving scope (if disposable)
27
+ * ```
28
+ *
29
+ * @example Disposable worker (one-time use)
30
+ * ```typescript
31
+ * using session = await WorkerSession.create();
32
+ * const records = await sendWorkerMessage(session.getWorker(), {
33
+ * id: session.getNextRequestId(),
34
+ * type: "parseString",
35
+ * data: csv,
36
+ * options: serializeOptions(options),
37
+ * });
38
+ * // Worker automatically terminated
39
+ * ```
40
+ *
41
+ * @example With WorkerPool (reusable)
42
+ * ```typescript
43
+ * using pool = new WorkerPool({ maxWorkers: 3 });
44
+ * using session = await WorkerSession.create({ workerPool: pool });
45
+ * const records1 = await sendWorkerMessage(session.getWorker(), { ... });
46
+ * const records2 = await sendWorkerMessage(session.getWorker(), { ... });
47
+ * // Worker returned to pool, pool cleanup happens when pool disposes
48
+ * ```
49
+ */
50
+ export declare class WorkerSession implements Disposable {
51
+ private worker;
52
+ private requestIdCounter;
53
+ private readonly workerPool?;
54
+ private constructor();
55
+ /**
56
+ * Create a new WorkerSession.
57
+ *
58
+ * @param options Session options
59
+ * @returns Promise that resolves to a WorkerSession instance
60
+ */
61
+ static create(options?: WorkerSessionOptions): Promise<WorkerSession>;
62
+ /**
63
+ * Get the worker instance.
64
+ */
65
+ getWorker(): Worker;
66
+ /**
67
+ * Get the next request ID for this session.
68
+ * - If using WorkerPool: Delegates to pool's getNextRequestId()
69
+ * - If disposable: Uses internal counter
70
+ */
71
+ getNextRequestId(): number;
72
+ /**
73
+ * Dispose the session.
74
+ * - If using a pool: Releases the worker back to the pool (behavior depends on pool type)
75
+ * - If disposable: Terminates the worker
76
+ */
77
+ [Symbol.dispose](): void;
78
+ }
@@ -0,0 +1,58 @@
1
+ import { createWorker } from './createWorker.web.js';
2
+
3
+ class WorkerSession {
4
+ worker;
5
+ requestIdCounter = 0;
6
+ workerPool;
7
+ constructor(worker, workerPool) {
8
+ this.worker = worker;
9
+ this.workerPool = workerPool;
10
+ }
11
+ /**
12
+ * Create a new WorkerSession.
13
+ *
14
+ * @param options Session options
15
+ * @returns Promise that resolves to a WorkerSession instance
16
+ */
17
+ static async create(options) {
18
+ let worker;
19
+ if (options?.workerPool) {
20
+ worker = await options.workerPool.getWorker(options.workerURL);
21
+ return new WorkerSession(worker, options.workerPool);
22
+ }
23
+ worker = await createWorker(options?.workerURL);
24
+ return new WorkerSession(worker);
25
+ }
26
+ /**
27
+ * Get the worker instance.
28
+ */
29
+ getWorker() {
30
+ return this.worker;
31
+ }
32
+ /**
33
+ * Get the next request ID for this session.
34
+ * - If using WorkerPool: Delegates to pool's getNextRequestId()
35
+ * - If disposable: Uses internal counter
36
+ */
37
+ getNextRequestId() {
38
+ if (this.workerPool) {
39
+ return this.workerPool.getNextRequestId();
40
+ }
41
+ return this.requestIdCounter++;
42
+ }
43
+ /**
44
+ * Dispose the session.
45
+ * - If using a pool: Releases the worker back to the pool (behavior depends on pool type)
46
+ * - If disposable: Terminates the worker
47
+ */
48
+ [Symbol.dispose]() {
49
+ if (this.workerPool) {
50
+ this.workerPool.releaseWorker(this.worker);
51
+ } else {
52
+ this.worker.terminate();
53
+ }
54
+ }
55
+ }
56
+
57
+ export { WorkerSession };
58
+ //# sourceMappingURL=WorkerSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkerSession.js","sources":["../../../../src/execution/worker/helpers/WorkerSession.ts"],"sourcesContent":["import { createWorker } from \"#execution/worker/createWorker.js\";\nimport type { WorkerPool } from \"./WorkerPool.ts\";\n\n/**\n * Options for creating a WorkerSession.\n */\nexport interface WorkerSessionOptions {\n /**\n * Custom worker URL.\n */\n workerURL?: string | URL;\n\n /**\n * Worker pool for reusable workers.\n */\n workerPool?: WorkerPool;\n}\n\n/**\n * WorkerSession manages the lifecycle of a single worker instance.\n *\n * Hybrid approach:\n * - If workerPool is provided: Use pool's worker (reusable, manual pool cleanup)\n * - If workerPool is NOT provided: Create disposable worker (auto-cleanup on dispose)\n *\n * Use `using` syntax for automatic cleanup:\n * ```typescript\n * using session = await WorkerSession.create();\n * const records = await sendWorkerMessage(session, { ... });\n * // Worker is automatically terminated when leaving scope (if disposable)\n * ```\n *\n * @example Disposable worker (one-time use)\n * ```typescript\n * using session = await WorkerSession.create();\n * const records = await sendWorkerMessage(session.getWorker(), {\n * id: session.getNextRequestId(),\n * type: \"parseString\",\n * data: csv,\n * options: serializeOptions(options),\n * });\n * // Worker automatically terminated\n * ```\n *\n * @example With WorkerPool (reusable)\n * ```typescript\n * using pool = new WorkerPool({ maxWorkers: 3 });\n * using session = await WorkerSession.create({ workerPool: pool });\n * const records1 = await sendWorkerMessage(session.getWorker(), { ... });\n * const records2 = await sendWorkerMessage(session.getWorker(), { ... });\n * // Worker returned to pool, pool cleanup happens when pool disposes\n * ```\n */\nexport class WorkerSession implements Disposable {\n private worker: Worker;\n private requestIdCounter = 0;\n private readonly workerPool?: WorkerPool;\n\n private constructor(worker: Worker, workerPool?: WorkerPool) {\n this.worker = worker;\n this.workerPool = workerPool;\n }\n\n /**\n * Create a new WorkerSession.\n *\n * @param options Session options\n * @returns Promise that resolves to a WorkerSession instance\n */\n static async create(options?: WorkerSessionOptions): Promise<WorkerSession> {\n let worker: Worker;\n if (options?.workerPool) {\n // Use worker from pool\n worker = await options.workerPool.getWorker(options.workerURL);\n return new WorkerSession(worker, options.workerPool);\n }\n // Create disposable worker\n worker = await createWorker(options?.workerURL);\n return new WorkerSession(worker);\n }\n\n /**\n * Get the worker instance.\n */\n getWorker(): Worker {\n return this.worker;\n }\n\n /**\n * Get the next request ID for this session.\n * - If using WorkerPool: Delegates to pool's getNextRequestId()\n * - If disposable: Uses internal counter\n */\n getNextRequestId(): number {\n if (this.workerPool) {\n return this.workerPool.getNextRequestId();\n }\n return this.requestIdCounter++;\n }\n\n /**\n * Dispose the session.\n * - If using a pool: Releases the worker back to the pool (behavior depends on pool type)\n * - If disposable: Terminates the worker\n */\n [Symbol.dispose](): void {\n if (this.workerPool) {\n this.workerPool.releaseWorker(this.worker);\n } else {\n this.worker.terminate();\n }\n }\n}\n"],"names":[],"mappings":";;AAqDO,MAAM,aAAA,CAAoC;AAAA,EACvC,MAAA;AAAA,EACA,gBAAA,GAAmB,CAAA;AAAA,EACV,UAAA;AAAA,EAET,WAAA,CAAY,QAAgB,UAAA,EAAyB;AAC3D,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,OAAO,OAAA,EAAwD;AAC1E,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI,SAAS,UAAA,EAAY;AAEvB,MAAA,MAAA,GAAS,MAAM,OAAA,CAAQ,UAAA,CAAW,SAAA,CAAU,QAAQ,SAAS,CAAA;AAC7D,MAAA,OAAO,IAAI,aAAA,CAAc,MAAA,EAAQ,OAAA,CAAQ,UAAU,CAAA;AAAA,IACrD;AAEA,IAAA,MAAA,GAAS,MAAM,YAAA,CAAa,OAAA,EAAS,SAAS,CAAA;AAC9C,IAAA,OAAO,IAAI,cAAc,MAAM,CAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,GAAoB;AAClB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAA,GAA2B;AACzB,IAAA,IAAI,KAAK,UAAA,EAAY;AACnB,MAAA,OAAO,IAAA,CAAK,WAAW,gBAAA,EAAiB;AAAA,IAC1C;AACA,IAAA,OAAO,IAAA,CAAK,gBAAA,EAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,CAAC,MAAA,CAAO,OAAO,CAAA,GAAU;AACvB,IAAA,IAAI,KAAK,UAAA,EAAY;AACnB,MAAA,IAAA,CAAK,UAAA,CAAW,aAAA,CAAc,IAAA,CAAK,MAAM,CAAA;AAAA,IAC3C,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,OAAO,SAAA,EAAU;AAAA,IACxB;AAAA,EACF;AACF;;;;"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Create a Worker instance for Node.js environment.
3
+ *
4
+ * @internal
5
+ * @param workerURL Custom worker URL or undefined to use bundled worker
6
+ * @returns Worker instance
7
+ */
8
+ export declare function createWorker(workerURL?: string | URL): Promise<Worker>;
@@ -0,0 +1,15 @@
1
+ async function createWorker(workerURL) {
2
+ const { Worker } = await import('node:worker_threads');
3
+ const { fileURLToPath } = await import('node:url');
4
+ const { dirname, join } = await import('node:path');
5
+ if (workerURL) {
6
+ return new Worker(workerURL, { type: "module" });
7
+ }
8
+ const currentFilePath = fileURLToPath(import.meta.url);
9
+ const currentDir = dirname(currentFilePath);
10
+ const workerPath = join(currentDir, "worker.node.js");
11
+ return new Worker(workerPath, { type: "module" });
12
+ }
13
+
14
+ export { createWorker };
15
+ //# sourceMappingURL=createWorker.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createWorker.node.js","sources":["../../../../src/execution/worker/helpers/createWorker.node.ts"],"sourcesContent":["/**\n * Create a Worker instance for Node.js environment.\n *\n * @internal\n * @param workerURL Custom worker URL or undefined to use bundled worker\n * @returns Worker instance\n */\nexport async function createWorker(workerURL?: string | URL): Promise<Worker> {\n // Dynamic import for Node.js Worker and URL utilities\n // @ts-ignore: node:worker_threads is only available in Node.js\n const { Worker } = await import(\"node:worker_threads\");\n // @ts-ignore: node:url is only available in Node.js\n const { fileURLToPath } = await import(\"node:url\");\n // @ts-ignore: node:path is only available in Node.js\n const { dirname, join } = await import(\"node:path\");\n\n if (workerURL) {\n // Use provided worker URL\n return new Worker(workerURL, { type: \"module\" });\n }\n\n // Compute worker.node.js path relative to this module\n // In Node.js, import.meta.url is a file:// URL pointing to this module\n const currentFilePath = fileURLToPath(import.meta.url);\n const currentDir = dirname(currentFilePath);\n const workerPath = join(currentDir, \"worker.node.js\");\n\n // @ts-ignore\n return new Worker(workerPath, { type: \"module\" });\n}\n"],"names":[],"mappings":"AAOA,eAAsB,aAAa,SAAA,EAA2C;AAG5E,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,MAAM,OAAO,qBAAqB,CAAA;AAErD,EAAA,MAAM,EAAE,aAAA,EAAc,GAAI,MAAM,OAAO,UAAU,CAAA;AAEjD,EAAA,MAAM,EAAE,OAAA,EAAS,IAAA,EAAK,GAAI,MAAM,OAAO,WAAW,CAAA;AAElD,EAAA,IAAI,SAAA,EAAW;AAEb,IAAA,OAAO,IAAI,MAAA,CAAO,SAAA,EAAW,EAAE,IAAA,EAAM,UAAU,CAAA;AAAA,EACjD;AAIA,EAAA,MAAM,eAAA,GAAkB,aAAA,CAAc,MAAA,CAAA,IAAA,CAAY,GAAG,CAAA;AACrD,EAAA,MAAM,UAAA,GAAa,QAAQ,eAAe,CAAA;AAC1C,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,UAAA,EAAY,gBAAgB,CAAA;AAGpD,EAAA,OAAO,IAAI,MAAA,CAAO,UAAA,EAAY,EAAE,IAAA,EAAM,UAAU,CAAA;AAClD;;;;"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Create a Worker instance for browser/Deno environment.
3
+ *
4
+ * @internal
5
+ * @param workerURL Custom worker URL or undefined to use bundled worker
6
+ * @returns Worker instance
7
+ */
8
+ export declare function createWorker(workerURL?: string | URL): Promise<Worker>;
@@ -0,0 +1,11 @@
1
+ async function createWorker(workerURL) {
2
+ const url = workerURL || new URL(
3
+ /* @vite-ignore */
4
+ "./worker.web.js",
5
+ import.meta.url
6
+ );
7
+ return new Worker(url, { type: "module" });
8
+ }
9
+
10
+ export { createWorker };
11
+ //# sourceMappingURL=createWorker.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createWorker.web.js","sources":["../../../../src/execution/worker/helpers/createWorker.web.ts"],"sourcesContent":["/**\n * Create a Worker instance for browser/Deno environment.\n *\n * @internal\n * @param workerURL Custom worker URL or undefined to use bundled worker\n * @returns Worker instance\n */\nexport async function createWorker(workerURL?: string | URL): Promise<Worker> {\n // Use @vite-ignore to prevent Vite from inlining the worker as a data URL\n // In production, import.meta.url points to dist/execution/worker/helpers/createWorker.web.js\n // so \"./worker.web.js\" correctly resolves to dist/execution/worker/helpers/worker.web.js\n const url =\n workerURL || new URL(/* @vite-ignore */ \"./worker.web.js\", import.meta.url);\n return new Worker(url, { type: \"module\" });\n}\n"],"names":[],"mappings":"AAOA,eAAsB,aAAa,SAAA,EAA2C;AAI5E,EAAA,MAAM,GAAA,GACJ,aAAa,IAAI,GAAA;AAAA;AAAA,IAAuB,iBAAA;AAAA,IAAmB,MAAA,CAAA,IAAA,CAAY;AAAA,GAAG;AAC5E,EAAA,OAAO,IAAI,MAAA,CAAO,GAAA,EAAK,EAAE,IAAA,EAAM,UAAU,CAAA;AAC3C;;;;"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import { parentPort } from 'node:worker_threads';
2
+ import { createMessageHandler } from './worker.shared.js';
3
+
4
+ if (!parentPort) {
5
+ throw new Error("This module must be run in a Worker Thread context");
6
+ }
7
+ const messageHandler = createMessageHandler(parentPort);
8
+ parentPort.on("message", (message) => {
9
+ messageHandler(message);
10
+ });
11
+ //# sourceMappingURL=worker.node.js.map