@blamejs/core 0.8.43 → 0.8.49

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 (222) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +10 -10
  3. package/index.js +52 -0
  4. package/lib/a2a.js +159 -34
  5. package/lib/acme.js +762 -0
  6. package/lib/ai-pref.js +166 -43
  7. package/lib/api-key.js +108 -47
  8. package/lib/api-snapshot.js +157 -40
  9. package/lib/app-shutdown.js +113 -77
  10. package/lib/archive.js +337 -40
  11. package/lib/arg-parser.js +697 -0
  12. package/lib/asyncapi.js +99 -55
  13. package/lib/atomic-file.js +465 -104
  14. package/lib/audit-chain.js +123 -34
  15. package/lib/audit-daily-review.js +389 -0
  16. package/lib/audit-sign.js +302 -56
  17. package/lib/audit-tools.js +412 -63
  18. package/lib/audit.js +656 -35
  19. package/lib/auth/jwt-external.js +17 -0
  20. package/lib/auth/oauth.js +7 -0
  21. package/lib/auth-bot-challenge.js +505 -0
  22. package/lib/auth-header.js +92 -25
  23. package/lib/backup/bundle.js +26 -0
  24. package/lib/backup/index.js +512 -89
  25. package/lib/backup/manifest.js +168 -7
  26. package/lib/break-glass.js +415 -39
  27. package/lib/budr.js +103 -30
  28. package/lib/bundler.js +86 -66
  29. package/lib/cache.js +192 -72
  30. package/lib/chain-writer.js +65 -40
  31. package/lib/circuit-breaker.js +56 -33
  32. package/lib/cli-helpers.js +106 -75
  33. package/lib/cli.js +6 -30
  34. package/lib/cloud-events.js +99 -32
  35. package/lib/cluster-storage.js +162 -37
  36. package/lib/cluster.js +340 -49
  37. package/lib/codepoint-class.js +66 -0
  38. package/lib/compliance.js +424 -24
  39. package/lib/config-drift.js +111 -46
  40. package/lib/config.js +94 -40
  41. package/lib/consent.js +165 -18
  42. package/lib/constants.js +1 -0
  43. package/lib/content-credentials.js +153 -48
  44. package/lib/cookies.js +154 -62
  45. package/lib/credential-hash.js +133 -61
  46. package/lib/crypto-field.js +702 -18
  47. package/lib/crypto-hpke.js +256 -0
  48. package/lib/crypto.js +744 -22
  49. package/lib/csv.js +178 -35
  50. package/lib/daemon.js +456 -0
  51. package/lib/dark-patterns.js +186 -55
  52. package/lib/db-query.js +79 -2
  53. package/lib/db.js +1431 -60
  54. package/lib/ddl-change-control.js +523 -0
  55. package/lib/deprecate.js +195 -40
  56. package/lib/dev.js +82 -39
  57. package/lib/dora.js +67 -48
  58. package/lib/dr-runbook.js +368 -0
  59. package/lib/dsr.js +142 -11
  60. package/lib/dual-control.js +91 -56
  61. package/lib/events.js +120 -41
  62. package/lib/external-db-migrate.js +192 -2
  63. package/lib/external-db.js +795 -50
  64. package/lib/fapi2.js +122 -1
  65. package/lib/fda-21cfr11.js +395 -0
  66. package/lib/fdx.js +132 -2
  67. package/lib/file-type.js +87 -0
  68. package/lib/file-upload.js +93 -0
  69. package/lib/flag.js +82 -20
  70. package/lib/forms.js +132 -29
  71. package/lib/framework-error.js +169 -0
  72. package/lib/framework-schema.js +163 -35
  73. package/lib/gate-contract.js +849 -175
  74. package/lib/graphql-federation.js +68 -7
  75. package/lib/guard-all.js +172 -55
  76. package/lib/guard-archive.js +286 -124
  77. package/lib/guard-auth.js +194 -21
  78. package/lib/guard-cidr.js +190 -28
  79. package/lib/guard-csv.js +397 -51
  80. package/lib/guard-domain.js +213 -91
  81. package/lib/guard-email.js +236 -29
  82. package/lib/guard-filename.js +307 -75
  83. package/lib/guard-graphql.js +263 -30
  84. package/lib/guard-html.js +310 -116
  85. package/lib/guard-image.js +243 -30
  86. package/lib/guard-json.js +260 -54
  87. package/lib/guard-jsonpath.js +235 -23
  88. package/lib/guard-jwt.js +284 -30
  89. package/lib/guard-markdown.js +204 -22
  90. package/lib/guard-mime.js +190 -26
  91. package/lib/guard-oauth.js +277 -28
  92. package/lib/guard-pdf.js +251 -27
  93. package/lib/guard-regex.js +226 -18
  94. package/lib/guard-shell.js +229 -26
  95. package/lib/guard-svg.js +177 -10
  96. package/lib/guard-template.js +232 -21
  97. package/lib/guard-time.js +195 -29
  98. package/lib/guard-uuid.js +189 -30
  99. package/lib/guard-xml.js +259 -36
  100. package/lib/guard-yaml.js +241 -44
  101. package/lib/honeytoken.js +63 -27
  102. package/lib/html-balance.js +83 -0
  103. package/lib/http-client.js +486 -59
  104. package/lib/http-message-signature.js +582 -0
  105. package/lib/i18n.js +102 -49
  106. package/lib/iab-mspa.js +112 -32
  107. package/lib/iab-tcf.js +107 -2
  108. package/lib/inbox.js +90 -52
  109. package/lib/keychain.js +865 -0
  110. package/lib/legal-hold.js +374 -0
  111. package/lib/local-db-thin.js +320 -0
  112. package/lib/log-stream.js +281 -51
  113. package/lib/log.js +184 -86
  114. package/lib/mail-bounce.js +107 -62
  115. package/lib/mail.js +295 -58
  116. package/lib/mcp.js +108 -27
  117. package/lib/metrics.js +98 -89
  118. package/lib/middleware/age-gate.js +36 -0
  119. package/lib/middleware/ai-act-disclosure.js +37 -0
  120. package/lib/middleware/api-encrypt.js +45 -0
  121. package/lib/middleware/assetlinks.js +40 -0
  122. package/lib/middleware/asyncapi-serve.js +35 -0
  123. package/lib/middleware/attach-user.js +40 -0
  124. package/lib/middleware/bearer-auth.js +40 -0
  125. package/lib/middleware/body-parser.js +230 -0
  126. package/lib/middleware/bot-disclose.js +34 -0
  127. package/lib/middleware/bot-guard.js +39 -0
  128. package/lib/middleware/compression.js +37 -0
  129. package/lib/middleware/cookies.js +32 -0
  130. package/lib/middleware/cors.js +40 -0
  131. package/lib/middleware/csp-nonce.js +40 -0
  132. package/lib/middleware/csp-report.js +34 -0
  133. package/lib/middleware/csrf-protect.js +43 -0
  134. package/lib/middleware/daily-byte-quota.js +53 -85
  135. package/lib/middleware/db-role-for.js +40 -0
  136. package/lib/middleware/dpop.js +40 -0
  137. package/lib/middleware/error-handler.js +37 -14
  138. package/lib/middleware/fetch-metadata.js +39 -0
  139. package/lib/middleware/flag-context.js +34 -0
  140. package/lib/middleware/gpc.js +33 -0
  141. package/lib/middleware/headers.js +35 -0
  142. package/lib/middleware/health.js +46 -0
  143. package/lib/middleware/host-allowlist.js +30 -0
  144. package/lib/middleware/network-allowlist.js +38 -0
  145. package/lib/middleware/openapi-serve.js +34 -0
  146. package/lib/middleware/rate-limit.js +160 -18
  147. package/lib/middleware/request-id.js +36 -18
  148. package/lib/middleware/request-log.js +37 -0
  149. package/lib/middleware/require-aal.js +29 -0
  150. package/lib/middleware/require-auth.js +32 -0
  151. package/lib/middleware/require-bound-key.js +41 -0
  152. package/lib/middleware/require-content-type.js +32 -0
  153. package/lib/middleware/require-methods.js +27 -0
  154. package/lib/middleware/require-mtls.js +33 -0
  155. package/lib/middleware/require-step-up.js +37 -0
  156. package/lib/middleware/security-headers.js +44 -0
  157. package/lib/middleware/security-txt.js +38 -0
  158. package/lib/middleware/span-http-server.js +37 -0
  159. package/lib/middleware/sse.js +36 -0
  160. package/lib/middleware/trace-log-correlation.js +33 -0
  161. package/lib/middleware/trace-propagate.js +32 -0
  162. package/lib/middleware/tus-upload.js +90 -0
  163. package/lib/middleware/web-app-manifest.js +53 -0
  164. package/lib/mtls-ca.js +100 -70
  165. package/lib/network-byte-quota.js +308 -0
  166. package/lib/network-heartbeat.js +135 -0
  167. package/lib/network-tls.js +534 -4
  168. package/lib/network.js +103 -0
  169. package/lib/notify.js +114 -43
  170. package/lib/ntp-check.js +192 -51
  171. package/lib/observability.js +145 -47
  172. package/lib/openapi.js +90 -44
  173. package/lib/outbox.js +99 -1
  174. package/lib/pagination.js +168 -86
  175. package/lib/parsers/index.js +16 -5
  176. package/lib/permissions.js +93 -40
  177. package/lib/pqc-agent.js +84 -8
  178. package/lib/pqc-software.js +94 -60
  179. package/lib/process-spawn.js +95 -21
  180. package/lib/pubsub.js +96 -66
  181. package/lib/queue.js +375 -54
  182. package/lib/redact.js +793 -21
  183. package/lib/render.js +139 -47
  184. package/lib/request-helpers.js +485 -121
  185. package/lib/restore-bundle.js +142 -39
  186. package/lib/restore-rollback.js +136 -45
  187. package/lib/retention.js +178 -50
  188. package/lib/retry.js +116 -33
  189. package/lib/router.js +475 -23
  190. package/lib/safe-async.js +543 -94
  191. package/lib/safe-buffer.js +337 -41
  192. package/lib/safe-json.js +467 -62
  193. package/lib/safe-jsonpath.js +285 -0
  194. package/lib/safe-schema.js +631 -87
  195. package/lib/safe-sql.js +221 -59
  196. package/lib/safe-url.js +278 -46
  197. package/lib/sandbox-worker.js +135 -0
  198. package/lib/sandbox.js +358 -0
  199. package/lib/scheduler.js +135 -70
  200. package/lib/self-update.js +647 -0
  201. package/lib/session-device-binding.js +431 -0
  202. package/lib/session.js +259 -49
  203. package/lib/slug.js +138 -26
  204. package/lib/ssrf-guard.js +316 -56
  205. package/lib/storage.js +433 -70
  206. package/lib/subject.js +405 -23
  207. package/lib/template.js +148 -8
  208. package/lib/tenant-quota.js +545 -0
  209. package/lib/testing.js +440 -53
  210. package/lib/time.js +291 -23
  211. package/lib/tls-exporter.js +239 -0
  212. package/lib/tracing.js +90 -74
  213. package/lib/uuid.js +97 -22
  214. package/lib/vault/index.js +284 -22
  215. package/lib/vault/seal-pem-file.js +66 -0
  216. package/lib/watcher.js +368 -0
  217. package/lib/webhook.js +196 -63
  218. package/lib/websocket.js +393 -68
  219. package/lib/wiki-concepts.js +338 -0
  220. package/lib/worker-pool.js +464 -0
  221. package/package.json +3 -3
  222. package/sbom.cyclonedx.json +7 -7
@@ -0,0 +1,464 @@
1
+ "use strict";
2
+ /**
3
+ * b.workerPool — generic worker_threads pool with bounded concurrency.
4
+ *
5
+ * Reusable harness for operator-defined workers that need to run
6
+ * CPU-bound work (compression, hashing, parser fan-out, batch render)
7
+ * off the main event loop without rolling per-feature lifecycle code.
8
+ * Wraps node:worker_threads with:
9
+ *
10
+ * - Bounded concurrency — `size` workers, default
11
+ * `Math.max(2, os.cpus().length)`, clamped to 1..256.
12
+ * - Bounded queue — `maxQueueDepth` (default 1024) refuses new
13
+ * `run()` calls when the in-memory queue is full so a slow worker
14
+ * pool can't accumulate unbounded backlog.
15
+ * - Per-task timeout — `taskTimeoutMs` (default `C.TIME.minutes(5)`)
16
+ * terminates the worker on overrun; the pool spawns a replacement
17
+ * so steady-state size stays stable.
18
+ * - Worker recycle on uncaught error — same: terminate + spawn
19
+ * replacement; in-flight task on that worker rejects.
20
+ * - Audit-everything — every task lifecycle event emits to the
21
+ * audit chain: workerpool.task.completed / .failed / .timeout +
22
+ * workerpool.created / .terminated.
23
+ *
24
+ * var pool = b.workerPool.create("/abs/path/to/worker.js", {
25
+ * size: 4,
26
+ * maxQueueDepth: C.BYTES.kib(1), // 1024 max queued tasks
27
+ * taskTimeoutMs: b.constants.TIME.minutes(2),
28
+ * onExit: function (code, workerId) { ... },
29
+ * });
30
+ * var result = await pool.run({ kind: "hash", payload: buf },
31
+ * [buf.buffer]); // optional transferList
32
+ * await pool.drain();
33
+ * await pool.terminate();
34
+ *
35
+ * Worker contract (operator-supplied script at scriptPath):
36
+ *
37
+ * var { parentPort } = require("node:worker_threads");
38
+ * parentPort.on("message", function (msg) {
39
+ * try {
40
+ * var result = doWork(msg);
41
+ * parentPort.postMessage({ ok: true, result: result });
42
+ * } catch (e) {
43
+ * parentPort.postMessage({ ok: false, message: e.message });
44
+ * }
45
+ * });
46
+ *
47
+ * The pool tracks each task by an internal taskId (monotonic), pairs
48
+ * the next reply from the assigned worker with that id, and resolves
49
+ * the run() promise. The worker's reply must be a single
50
+ * `{ ok: true, result }` or `{ ok: false, message }` envelope per
51
+ * inbound message.
52
+ *
53
+ * Failure modes (every one throws WorkerPoolError):
54
+ * - workerpool/bad-script-path — non-string / non-absolute / contains eval marker
55
+ * - workerpool/bad-size — non-int / out of 1..256 range
56
+ * - workerpool/bad-max-queue-depth — non-int / out of range
57
+ * - workerpool/bad-task-timeout — non-positive-finite / out of range
58
+ * - workerpool/bad-on-exit — onExit is not a function
59
+ * - workerpool/queue-full — run() called past maxQueueDepth
60
+ * - workerpool/timeout — task exceeded taskTimeoutMs
61
+ * - workerpool/worker-error — worker emitted "error" mid-task
62
+ * - workerpool/worker-exit — worker exited mid-task
63
+ * - workerpool/worker-bad-message — worker reply was not envelope-shaped
64
+ * - workerpool/task-failed — worker reported { ok: false }
65
+ * - workerpool/terminated — pool.terminate() aborted in-flight tasks
66
+ * - workerpool/no-worker-threads — runtime lacks node:worker_threads
67
+ */
68
+
69
+ var os = require("node:os");
70
+ var path = require("node:path");
71
+ var lazyRequire = require("./lazy-require");
72
+ var validateOpts = require("./validate-opts");
73
+ var numericBounds = require("./numeric-bounds");
74
+ var constants = require("./constants");
75
+ var { WorkerPoolError } = require("./framework-error");
76
+
77
+ var audit = lazyRequire(function () { return require("./audit"); });
78
+
79
+ var MIN_SIZE = 1;
80
+ var MAX_SIZE = 256; // allow:raw-byte-literal — sanity ceiling on worker count, not bytes
81
+ var DEFAULT_MAX_QUEUE_DEPTH = 1024; // allow:raw-byte-literal — task-queue depth, not bytes
82
+ var MAX_QUEUE_DEPTH_CAP = 1048576; // allow:raw-byte-literal — task-queue depth ceiling, not bytes
83
+ var DEFAULT_TASK_TIMEOUT_MS = constants.TIME.minutes(5);
84
+ var MAX_TASK_TIMEOUT_MS = constants.TIME.hours(1);
85
+
86
+ // Refuse operator-supplied `eval`-style script paths. Worker_threads
87
+ // supports `{ eval: true }` to spawn from a string; this primitive
88
+ // only accepts a real absolute filesystem path so a typo / operator-
89
+ // supplied input can't be coerced into eval.
90
+ function _validateScriptPath(scriptPath) {
91
+ validateOpts.requireNonEmptyString(scriptPath,
92
+ "workerPool.create: scriptPath", WorkerPoolError, "workerpool/bad-script-path");
93
+ if (!path.isAbsolute(scriptPath)) {
94
+ throw new WorkerPoolError("workerpool/bad-script-path",
95
+ "workerPool.create: scriptPath must be an absolute path; got " +
96
+ JSON.stringify(scriptPath));
97
+ }
98
+ // Defense-in-depth: refuse any path that looks like a data URL / eval
99
+ // marker. Real filesystem paths never contain these.
100
+ if (/^data:/i.test(scriptPath) || /^eval:/i.test(scriptPath)) {
101
+ throw new WorkerPoolError("workerpool/bad-script-path",
102
+ "workerPool.create: scriptPath must be a filesystem path, not an eval/data URL");
103
+ }
104
+ }
105
+
106
+ function _emitAudit(action, outcome, metadata) {
107
+ try {
108
+ audit().safeEmit({
109
+ action: action,
110
+ outcome: outcome,
111
+ metadata: metadata || {},
112
+ });
113
+ } catch (_e) { /* drop-silent — audit best-effort */ }
114
+ }
115
+
116
+ function create(scriptPath, opts) {
117
+ opts = opts || {};
118
+ validateOpts(opts, ["size", "onExit", "maxQueueDepth", "taskTimeoutMs"], "workerPool.create");
119
+ _validateScriptPath(scriptPath);
120
+
121
+ var defaultSize = Math.max(2, (os.cpus() || []).length || 2);
122
+ var size = (opts.size === undefined) ? defaultSize : opts.size;
123
+ if (!numericBounds.isPositiveFiniteInt(size) || size < MIN_SIZE || size > MAX_SIZE) {
124
+ throw new WorkerPoolError("workerpool/bad-size",
125
+ "workerPool.create: opts.size must be a positive finite integer in [" +
126
+ MIN_SIZE + ".." + MAX_SIZE + "]; got " + numericBounds.shape(size));
127
+ }
128
+
129
+ var maxQueueDepth = (opts.maxQueueDepth === undefined) ? DEFAULT_MAX_QUEUE_DEPTH : opts.maxQueueDepth;
130
+ if (!numericBounds.isPositiveFiniteInt(maxQueueDepth) || maxQueueDepth > MAX_QUEUE_DEPTH_CAP) {
131
+ throw new WorkerPoolError("workerpool/bad-max-queue-depth",
132
+ "workerPool.create: opts.maxQueueDepth must be a positive finite integer <= " +
133
+ MAX_QUEUE_DEPTH_CAP + "; got " + numericBounds.shape(maxQueueDepth));
134
+ }
135
+
136
+ var taskTimeoutMs = (opts.taskTimeoutMs === undefined) ? DEFAULT_TASK_TIMEOUT_MS : opts.taskTimeoutMs;
137
+ if (!numericBounds.isPositiveFiniteInt(taskTimeoutMs) || taskTimeoutMs > MAX_TASK_TIMEOUT_MS) {
138
+ throw new WorkerPoolError("workerpool/bad-task-timeout",
139
+ "workerPool.create: opts.taskTimeoutMs must be a positive finite integer <= " +
140
+ MAX_TASK_TIMEOUT_MS + "; got " + numericBounds.shape(taskTimeoutMs));
141
+ }
142
+
143
+ var onExit = opts.onExit;
144
+ if (onExit !== undefined && onExit !== null && typeof onExit !== "function") {
145
+ throw new WorkerPoolError("workerpool/bad-on-exit",
146
+ "workerPool.create: opts.onExit must be a function; got " + typeof onExit);
147
+ }
148
+
149
+ var workerThreads;
150
+ try { workerThreads = require("node:worker_threads"); }
151
+ catch (_e) {
152
+ throw new WorkerPoolError("workerpool/no-worker-threads",
153
+ "workerPool.create: node:worker_threads is unavailable in this runtime");
154
+ }
155
+
156
+ // Per-pool state. Workers carry { id, worker, busy, currentTaskId,
157
+ // currentTimer }. Queue holds { message, transferList, resolve,
158
+ // reject } envelopes.
159
+ var workerSlots = [];
160
+ var workerSeq = 0;
161
+ var taskSeq = 0;
162
+ var queue = [];
163
+ var totalTasks = 0;
164
+ var totalErrors = 0;
165
+ var terminated = false;
166
+ var drainResolvers = [];
167
+
168
+ function _spawnWorker() {
169
+ var id = ++workerSeq;
170
+ var worker;
171
+ try {
172
+ worker = new workerThreads.Worker(scriptPath);
173
+ } catch (eSpawn) {
174
+ _emitAudit("workerpool.spawn.failed", "failure", {
175
+ scriptPath: scriptPath,
176
+ message: (eSpawn && eSpawn.message) || String(eSpawn),
177
+ });
178
+ throw new WorkerPoolError("workerpool/spawn-failed",
179
+ "workerPool.create: failed to spawn worker: " + (eSpawn && eSpawn.message));
180
+ }
181
+ var slot = {
182
+ id: id,
183
+ worker: worker,
184
+ busy: false,
185
+ currentTaskId: null,
186
+ currentTimer: null,
187
+ currentTask: null,
188
+ };
189
+ worker.on("message", function (msg) { _onWorkerMessage(slot, msg); });
190
+ worker.on("error", function (err) { _onWorkerError(slot, err); });
191
+ worker.on("exit", function (code) { _onWorkerExit(slot, code); });
192
+ workerSlots.push(slot);
193
+ _emitAudit("workerpool.created", "success", { workerId: id, size: size });
194
+ return slot;
195
+ }
196
+
197
+ function _findIdleSlot() {
198
+ for (var i = 0; i < workerSlots.length; i += 1) {
199
+ if (!workerSlots[i].busy && !workerSlots[i].recycling) return workerSlots[i];
200
+ }
201
+ return null;
202
+ }
203
+
204
+ function _dispatch(slot, task) {
205
+ slot.busy = true;
206
+ slot.currentTaskId = task.id;
207
+ slot.currentTask = task;
208
+ slot.currentTimer = setTimeout(function () {
209
+ _onTaskTimeout(slot);
210
+ }, taskTimeoutMs);
211
+ if (slot.currentTimer && typeof slot.currentTimer.unref === "function") {
212
+ // Don't keep the event loop open just for the timeout.
213
+ slot.currentTimer.unref();
214
+ }
215
+ try {
216
+ slot.worker.postMessage(task.message, task.transferList || undefined);
217
+ } catch (ePost) {
218
+ _finishTask(slot, true,
219
+ new WorkerPoolError("workerpool/post-failed",
220
+ "workerPool.run: postMessage failed: " + (ePost && ePost.message)));
221
+ }
222
+ }
223
+
224
+ function _drainQueue() {
225
+ while (!terminated && queue.length > 0) {
226
+ var slot = _findIdleSlot();
227
+ if (!slot) return;
228
+ var task = queue.shift();
229
+ _dispatch(slot, task);
230
+ }
231
+ }
232
+
233
+ function _finishTask(slot, isError, payloadOrError) {
234
+ var task = slot.currentTask;
235
+ if (!task) return;
236
+ if (slot.currentTimer) { clearTimeout(slot.currentTimer); slot.currentTimer = null; }
237
+ slot.busy = false;
238
+ slot.currentTaskId = null;
239
+ slot.currentTask = null;
240
+ totalTasks += 1;
241
+ if (isError) {
242
+ totalErrors += 1;
243
+ task.reject(payloadOrError);
244
+ } else {
245
+ task.resolve(payloadOrError);
246
+ }
247
+ _maybeResolveDrain();
248
+ _drainQueue();
249
+ }
250
+
251
+ function _onWorkerMessage(slot, msg) {
252
+ if (!slot.currentTask) {
253
+ // Stray message — worker posted before any task; ignore.
254
+ return;
255
+ }
256
+ if (!msg || typeof msg !== "object" || typeof msg.ok !== "boolean") {
257
+ _emitAudit("workerpool.task.failed", "failure", {
258
+ workerId: slot.id, taskId: slot.currentTaskId, reason: "workerpool/worker-bad-message",
259
+ });
260
+ _finishTask(slot, true,
261
+ new WorkerPoolError("workerpool/worker-bad-message",
262
+ "workerPool: worker reply was not { ok, ... } envelope-shaped"));
263
+ return;
264
+ }
265
+ if (msg.ok) {
266
+ _emitAudit("workerpool.task.completed", "success", {
267
+ workerId: slot.id, taskId: slot.currentTaskId,
268
+ });
269
+ _finishTask(slot, false, msg.result);
270
+ } else {
271
+ _emitAudit("workerpool.task.failed", "failure", {
272
+ workerId: slot.id, taskId: slot.currentTaskId,
273
+ reason: "workerpool/task-failed",
274
+ message: msg.message || "",
275
+ });
276
+ _finishTask(slot, true,
277
+ new WorkerPoolError("workerpool/task-failed",
278
+ "workerPool: worker reported failure: " +
279
+ (msg.message || "(no message)")));
280
+ }
281
+ }
282
+
283
+ function _onWorkerError(slot, err) {
284
+ var failingTask = slot.currentTask;
285
+ _emitAudit("workerpool.task.failed", "failure", {
286
+ workerId: slot.id, taskId: slot.currentTaskId,
287
+ reason: "workerpool/worker-error",
288
+ message: (err && err.message) || String(err),
289
+ });
290
+ if (failingTask) {
291
+ _finishTask(slot, true,
292
+ new WorkerPoolError("workerpool/worker-error",
293
+ "workerPool: worker errored: " +
294
+ (err && err.message ? err.message : String(err))));
295
+ }
296
+ // Worker is now in an indeterminate state; recycle it.
297
+ _recycleWorker(slot);
298
+ }
299
+
300
+ function _onWorkerExit(slot, code) {
301
+ var failingTask = slot.currentTask;
302
+ if (failingTask) {
303
+ _emitAudit("workerpool.task.failed", "failure", {
304
+ workerId: slot.id, taskId: slot.currentTaskId,
305
+ reason: "workerpool/worker-exit", code: code,
306
+ });
307
+ _finishTask(slot, true,
308
+ new WorkerPoolError("workerpool/worker-exit",
309
+ "workerPool: worker exited (code " + code + ") mid-task"));
310
+ }
311
+ _emitAudit("workerpool.terminated", "success", {
312
+ workerId: slot.id, code: code,
313
+ });
314
+ if (typeof onExit === "function") {
315
+ try { onExit(code, slot.id); } catch (_e) { /* drop-silent — operator hook */ }
316
+ }
317
+ // Remove from active set. If the pool is still live, spawn a replacement.
318
+ var idx = workerSlots.indexOf(slot);
319
+ if (idx !== -1) workerSlots.splice(idx, 1);
320
+ if (!terminated && workerSlots.length < size) {
321
+ try { _spawnWorker(); } catch (_e) { /* spawn already audited */ }
322
+ _drainQueue();
323
+ } else {
324
+ _maybeResolveDrain();
325
+ }
326
+ }
327
+
328
+ function _onTaskTimeout(slot) {
329
+ var taskId = slot.currentTaskId;
330
+ _emitAudit("workerpool.task.timeout", "failure", {
331
+ workerId: slot.id, taskId: taskId, taskTimeoutMs: taskTimeoutMs,
332
+ });
333
+ var failingTask = slot.currentTask;
334
+ if (failingTask) {
335
+ _finishTask(slot, true,
336
+ new WorkerPoolError("workerpool/timeout",
337
+ "workerPool: task " + taskId + " exceeded taskTimeoutMs=" + taskTimeoutMs));
338
+ }
339
+ _recycleWorker(slot);
340
+ }
341
+
342
+ function _recycleWorker(slot) {
343
+ // Mark the slot as dying so _findIdleSlot skips it before exit
344
+ // fires. Without this, a new run() between terminate() and the
345
+ // exit event would dispatch to a worker that's about to die and
346
+ // surface as workerpool/worker-exit on a freshly-queued task.
347
+ slot.busy = true;
348
+ slot.recycling = true;
349
+ try { slot.worker.terminate(); } catch (_e) { /* terminate best-effort */ }
350
+ }
351
+
352
+ function _maybeResolveDrain() {
353
+ if (drainResolvers.length === 0) return;
354
+ var anyBusy = false;
355
+ for (var i = 0; i < workerSlots.length; i += 1) {
356
+ if (workerSlots[i].busy) { anyBusy = true; break; }
357
+ }
358
+ if (anyBusy || queue.length > 0) return;
359
+ var pending = drainResolvers.splice(0, drainResolvers.length);
360
+ for (var j = 0; j < pending.length; j += 1) {
361
+ try { pending[j](); } catch (_e) { /* drop-silent — drain best-effort */ }
362
+ }
363
+ }
364
+
365
+ function run(message, transferList) {
366
+ if (terminated) {
367
+ return Promise.reject(new WorkerPoolError("workerpool/terminated",
368
+ "workerPool.run: pool has been terminated"));
369
+ }
370
+ if (transferList !== undefined && transferList !== null && !Array.isArray(transferList)) {
371
+ return Promise.reject(new WorkerPoolError("workerpool/bad-transfer-list",
372
+ "workerPool.run: transferList must be an array if supplied"));
373
+ }
374
+ if (queue.length >= maxQueueDepth) {
375
+ return Promise.reject(new WorkerPoolError("workerpool/queue-full",
376
+ "workerPool.run: queue is full (depth=" + queue.length +
377
+ " >= maxQueueDepth=" + maxQueueDepth + ")"));
378
+ }
379
+ var taskId = ++taskSeq;
380
+ return new Promise(function (resolve, reject) {
381
+ var task = {
382
+ id: taskId,
383
+ message: message,
384
+ transferList: transferList || null,
385
+ resolve: resolve,
386
+ reject: reject,
387
+ };
388
+ var slot = _findIdleSlot();
389
+ if (slot) {
390
+ _dispatch(slot, task);
391
+ } else {
392
+ queue.push(task);
393
+ }
394
+ });
395
+ }
396
+
397
+ function drain() {
398
+ return new Promise(function (resolve) {
399
+ var anyBusy = false;
400
+ for (var i = 0; i < workerSlots.length; i += 1) {
401
+ if (workerSlots[i].busy) { anyBusy = true; break; }
402
+ }
403
+ if (!anyBusy && queue.length === 0) { resolve(); return; }
404
+ drainResolvers.push(resolve);
405
+ });
406
+ }
407
+
408
+ function terminate() {
409
+ terminated = true;
410
+ // Reject queued tasks first so the caller sees a deterministic error.
411
+ var pending = queue.splice(0, queue.length);
412
+ for (var i = 0; i < pending.length; i += 1) {
413
+ try {
414
+ pending[i].reject(new WorkerPoolError("workerpool/terminated",
415
+ "workerPool.terminate: task aborted before dispatch"));
416
+ } catch (_e) { /* drop-silent — caller already has rejection */ }
417
+ }
418
+ // Then terminate every worker. _onWorkerExit will reject any in-flight task.
419
+ var promises = [];
420
+ for (var j = 0; j < workerSlots.length; j += 1) {
421
+ var slot = workerSlots[j];
422
+ if (slot.currentTimer) { clearTimeout(slot.currentTimer); slot.currentTimer = null; }
423
+ try { promises.push(slot.worker.terminate()); }
424
+ catch (_e) { /* terminate best-effort */ }
425
+ }
426
+ return Promise.all(promises).then(function () { /* swallow undefined returns */ });
427
+ }
428
+
429
+ function stats() {
430
+ var busy = 0;
431
+ for (var i = 0; i < workerSlots.length; i += 1) {
432
+ if (workerSlots[i].busy) busy += 1;
433
+ }
434
+ return {
435
+ size: workerSlots.length,
436
+ busy: busy,
437
+ idle: workerSlots.length - busy,
438
+ queued: queue.length,
439
+ totalTasks: totalTasks,
440
+ totalErrors: totalErrors,
441
+ };
442
+ }
443
+
444
+ // Bring up the pool eagerly so the first run() doesn't pay spawn cost.
445
+ for (var k = 0; k < size; k += 1) _spawnWorker();
446
+
447
+ return {
448
+ run: run,
449
+ drain: drain,
450
+ terminate: terminate,
451
+ stats: stats,
452
+ };
453
+ }
454
+
455
+ module.exports = {
456
+ create: create,
457
+ MIN_SIZE: MIN_SIZE,
458
+ MAX_SIZE: MAX_SIZE,
459
+ DEFAULT_MAX_QUEUE_DEPTH: DEFAULT_MAX_QUEUE_DEPTH,
460
+ MAX_QUEUE_DEPTH_CAP: MAX_QUEUE_DEPTH_CAP,
461
+ DEFAULT_TASK_TIMEOUT_MS: DEFAULT_TASK_TIMEOUT_MS,
462
+ MAX_TASK_TIMEOUT_MS: MAX_TASK_TIMEOUT_MS,
463
+ WorkerPoolError: WorkerPoolError,
464
+ };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.8.43",
3
+ "version": "0.8.49",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
7
- "homepage": "https://github.com/blamejs/blamejs",
7
+ "homepage": "https://blamejs.com",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/blamejs/blamejs.git"
@@ -54,7 +54,7 @@
54
54
  "owns-its-stack"
55
55
  ],
56
56
  "engines": {
57
- "node": ">=24.4.0"
57
+ "node": ">=24.14.1"
58
58
  },
59
59
  "files": [
60
60
  "index.js",
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:ee1628bb-0575-4a66-b601-38962996ca75",
5
+ "serialNumber": "urn:uuid:34a5042e-06d2-4db8-8f87-c16c95d50c13",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-07T20:54:17.715Z",
8
+ "timestamp": "2026-05-09T14:56:33.086Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/core@0.8.43",
22
+ "bom-ref": "@blamejs/core@0.8.49",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.8.43",
25
+ "version": "0.8.49",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "The Node framework that owns its stack.",
29
- "purl": "pkg:npm/%40blamejs/core@0.8.43",
29
+ "purl": "pkg:npm/%40blamejs/core@0.8.49",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -35,7 +35,7 @@
35
35
  },
36
36
  {
37
37
  "type": "website",
38
- "url": "https://github.com/blamejs/blamejs"
38
+ "url": "https://blamejs.com"
39
39
  },
40
40
  {
41
41
  "type": "issue-tracker",
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/core@0.8.43",
57
+ "ref": "@blamejs/core@0.8.49",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]