@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
package/lib/queue.js CHANGED
@@ -1,34 +1,45 @@
1
1
  "use strict";
2
2
  /**
3
- * Queue dispatcher — pluggable job queue with retry, lease semantics, and
4
- * graceful shutdown.
5
- *
6
- * Same dispatcher pattern as object-store: backends are configured per-name,
7
- * each with a protocol + protocol-specific options. The built-in 'local'
8
- * protocol is SQLite-backed (baked into the framework's main DB).
9
- * External protocols (redis, sqs, amqp, nats) are listed as deferred and
10
- * surface a clear error when selected.
11
- *
12
- * Public API:
13
- * queue.init({ backends: { name: { protocol: 'local' } }, defaultBackend? })
14
- * queue.enqueue(queueName, payload, opts?)
15
- * { jobId, queueName, ... }
16
- * queue.consume(queueName, handler, opts?)
17
- * consumer handle (with cancel())
18
- * queue.size(queueName, opts?) → number (pending + inflight)
19
- * queue.purge(queueName, opts?) → number deleted
20
- * queue.shutdown(opts?) → drain handlers gracefully
21
- * queue.listBackends() → [{ name, protocol }]
22
- *
23
- * Job lifecycle:
24
- * enqueued (status='pending')
25
- * availableAt reached + consumer leases
26
- * inflight (status='inflight', lease expires after leaseDurationMs)
27
- * ↓ handler returns ↓ handler throws
28
- * done (status='done') if attempts < maxAttempts:
29
- * pending (with backoff)
30
- * else:
31
- * failed (status='failed')
3
+ * @module b.queue
4
+ * @nav Data
5
+ * @title Queue
6
+ *
7
+ * @intro
8
+ * Durable, pluggable job queue with priority-aware leasing, retry +
9
+ * deterministic backoff, graceful shutdown, parent/child flows, and
10
+ * a dead-letter surface for jobs that exhaust their retries.
11
+ *
12
+ * Same dispatcher shape as `b.objectStore`: every operator-named
13
+ * backend declares a `protocol` plus protocol-specific options. The
14
+ * built-in `local` protocol is SQLite-backed (rows live in the
15
+ * framework's main DB so persistence survives crashes / restarts
16
+ * without external infrastructure). `redis` and `sqs` ship; `amqp`
17
+ * and `nats` are listed as deferred and surface a clear error if
18
+ * selected.
19
+ *
20
+ * Job lifecycle:
21
+ * enqueued (status='pending', availableAt set by delaySeconds)
22
+ * ↓ availableAt reached + consumer leases
23
+ * inflight (status='inflight', lease expires after leaseDurationMs)
24
+ * handler returns ↓ handler throws
25
+ * done (status='done') attempts < maxAttempts:
26
+ * pending (with deterministic backoff)
27
+ * else:
28
+ * failed DLQ row written
29
+ *
30
+ * A 30-second sweep timer re-pends inflight rows whose lease expired
31
+ * without completion (crashed handlers, OOM kills) so no job is
32
+ * abandoned. Within a single millisecond, higher `priority` jobs
33
+ * lease before lower-priority ones (deterministic — see
34
+ * `b.queue.enqueue` opts).
35
+ *
36
+ * Dead-letter handling: jobs that exhaust `maxAttempts` write a
37
+ * `system.queue.dlq.write` audit event and stay queryable via
38
+ * `b.queue.dlqList`. Operator decides whether to retry
39
+ * (`b.queue.dlqRetry`) — never automatic.
40
+ *
41
+ * @card
42
+ * Durable, pluggable job queue with priority-aware leasing, retry + deterministic backoff, graceful shutdown, parent/child flows, and a dead-letter surface for jobs that exhaust their retries.
32
43
  */
33
44
  var C = require("./constants");
34
45
  var clusterStorage = require("./cluster-storage");
@@ -68,6 +79,43 @@ var defaultBackend = null;
68
79
  var consumers = []; // [{ queueName, backendName, cancel(), running, inFlight: Set }]
69
80
  var sweepTimer = null;
70
81
 
82
+ /**
83
+ * @primitive b.queue.init
84
+ * @signature b.queue.init(opts)
85
+ * @since 0.4.0
86
+ * @status stable
87
+ * @related b.queue.bootFromEnv, b.queue.shutdown, b.queue.listBackends
88
+ *
89
+ * One-time initialization. Wires every named backend through the
90
+ * protocol dispatcher, wraps mutating ops with the retry helper +
91
+ * circuit breaker, and starts the 30-second expired-lease sweep.
92
+ * Idempotent — calling `init` after the queue is already initialized
93
+ * is a no-op (boot order doesn't have to be exact).
94
+ *
95
+ * Throws when `opts.backends` is missing — operators catch the typo
96
+ * at boot rather than discovering it on first enqueue.
97
+ *
98
+ * @opts
99
+ * backends: {
100
+ * [name: string]: {
101
+ * protocol: "local" | "redis" | "sqs",
102
+ * breaker?: { ... }, // see b.retry.CircuitBreaker opts
103
+ * retry?: { ... }, // see b.retry.withRetry opts
104
+ * // ...protocol-specific opts (e.g. redis url, sqs queueUrl)
105
+ * },
106
+ * },
107
+ * defaultBackend?: string, // name to use when enqueue/consume omit { backend }
108
+ *
109
+ * @example
110
+ * b.queue.init({
111
+ * backends: {
112
+ * primary: { protocol: "local" },
113
+ * },
114
+ * defaultBackend: "primary",
115
+ * });
116
+ * b.queue.listBackends();
117
+ * // → [{ name: "primary", protocol: "local", breakerState: "closed" }]
118
+ */
71
119
  function init(opts) {
72
120
  if (initialized) return;
73
121
  if (!opts || !opts.backends) {
@@ -151,6 +199,42 @@ function _backendFor(opts) {
151
199
 
152
200
  // ---- Public API ----
153
201
 
202
+ /**
203
+ * @primitive b.queue.enqueue
204
+ * @signature b.queue.enqueue(queueName, payload, opts)
205
+ * @since 0.4.0
206
+ * @status stable
207
+ * @related b.queue.consume, b.queue.enqueueFlow, b.queue.size
208
+ *
209
+ * Persists a single job to the named queue and returns a promise that
210
+ * resolves with the assigned `jobId`. The job's `payload` is stored
211
+ * verbatim by the backend (the `local` protocol JSON-encodes; redis
212
+ * and sqs follow their wire formats). Resolves before any consumer
213
+ * actually leases the job — `enqueue` is durable handoff, not
214
+ * synchronous execution.
215
+ *
216
+ * Higher `priority` jobs lease ahead of lower ones within the same
217
+ * `availableAt` window. `delaySeconds` parks the job until the
218
+ * timestamp arrives. `maxAttempts` overrides the queue default; on
219
+ * the final attempt the job moves to the dead-letter view rather
220
+ * than retrying again.
221
+ *
222
+ * @opts
223
+ * backend?: string, // backend name; defaults to defaultBackend
224
+ * priority?: number, // higher leases first (default 0)
225
+ * delaySeconds?: number, // park before becoming leaseable
226
+ * maxAttempts?: number, // retries before DLQ (backend default applies)
227
+ * classification?: string, // operator metadata, surfaced in audit
228
+ * traceId?: string, // cross-request correlation id
229
+ *
230
+ * @example
231
+ * var result = await b.queue.enqueue("ingest", { url: "https://example.com" }, {
232
+ * priority: 5,
233
+ * maxAttempts: 3,
234
+ * });
235
+ * result.jobId;
236
+ * // → "job-7c2f8e1a..."
237
+ */
154
238
  function enqueue(queueName, payload, opts) {
155
239
  _requireInit();
156
240
  if (!queueName) throw _err("MISSING_QUEUE", "enqueue requires queueName", true);
@@ -176,6 +260,50 @@ function enqueue(queueName, payload, opts) {
176
260
  );
177
261
  }
178
262
 
263
+ /**
264
+ * @primitive b.queue.consume
265
+ * @signature b.queue.consume(queueName, handler, opts)
266
+ * @since 0.4.0
267
+ * @status stable
268
+ * @related b.queue.enqueue, b.queue.shutdown, b.queue.dlqRetry
269
+ *
270
+ * Starts a long-running consumer that leases jobs and runs them
271
+ * through `handler(job, ctx)`. Handler resolution marks the job
272
+ * `done`; rejection bumps the attempt counter and either re-pends
273
+ * with deterministic exponential backoff (1s base, 5min cap, no
274
+ * jitter) or routes to the DLQ when `attempts >= maxAttempts`.
275
+ *
276
+ * Returns a consumer state handle whose `.cancel()` aborts the poll
277
+ * loop immediately (without waiting for the next `pollIntervalMs`
278
+ * tick) and stops leasing new work. In-flight handlers complete on
279
+ * their own; `b.queue.shutdown` waits for them with a deadline.
280
+ *
281
+ * `ctx` carries `extendLease(additionalMs)` for long-running
282
+ * handlers about to overrun their lease, and `progress(0..100)` for
283
+ * audit-chain progress markers (rate-limited so a chatty handler
284
+ * can't flood the chain).
285
+ *
286
+ * @opts
287
+ * backend?: string, // backend name; defaults to defaultBackend
288
+ * concurrency?: number, // max in-flight handlers (default 1)
289
+ * leaseDurationMs?: number, // lease window before sweep re-pends (default 30s)
290
+ * pollIntervalMs?: number, // idle backoff between empty leases (default 1s)
291
+ * fastPollMs?: number, // delay between non-empty lease batches (default 50ms)
292
+ * rateLimit?: {
293
+ * max: number, // positive integer
294
+ * perSeconds: number, // positive finite seconds
295
+ * },
296
+ *
297
+ * @example
298
+ * var consumer = b.queue.consume("ingest", async function (job, ctx) {
299
+ * ctx.progress(10);
300
+ * // ...do work...
301
+ * ctx.progress(100);
302
+ * }, { concurrency: 4 });
303
+ *
304
+ * // Later, on shutdown signal:
305
+ * consumer.cancel();
306
+ */
179
307
  function consume(queueName, handler, opts) {
180
308
  _requireInit();
181
309
  if (!queueName) throw _err("MISSING_QUEUE", "consume requires queueName", true);
@@ -402,11 +530,48 @@ function _backoffDelay(attempt) {
402
530
  return retryHelper.backoffDelay(attempt, _QUEUE_BACKOFF_OPTS);
403
531
  }
404
532
 
533
+ /**
534
+ * @primitive b.queue.size
535
+ * @signature b.queue.size(queueName, opts)
536
+ * @since 0.4.0
537
+ * @status stable
538
+ * @related b.queue.dlqSize, b.queue.purge
539
+ *
540
+ * Resolves with the number of pending + inflight jobs in the queue —
541
+ * the live backlog. Excludes `done` and `failed` rows. Operators wire
542
+ * this to dashboards and autoscalers.
543
+ *
544
+ * @opts
545
+ * backend?: string, // backend name; defaults to defaultBackend
546
+ *
547
+ * @example
548
+ * var pending = await b.queue.size("ingest");
549
+ * // → 42
550
+ */
405
551
  function size(queueName, opts) {
406
552
  _requireInit();
407
553
  return _backendFor(opts).size(queueName);
408
554
  }
409
555
 
556
+ /**
557
+ * @primitive b.queue.purge
558
+ * @signature b.queue.purge(queueName, opts)
559
+ * @since 0.4.0
560
+ * @status stable
561
+ * @related b.queue.size, b.queue.dlqList
562
+ *
563
+ * Deletes every job in the named queue and resolves with the deleted
564
+ * count. Emits a `system.queue.purge` audit event for forensic
565
+ * traceability. Use during operator-driven cleanups; never in normal
566
+ * traffic — purged jobs are not recoverable.
567
+ *
568
+ * @opts
569
+ * backend?: string, // backend name; defaults to defaultBackend
570
+ *
571
+ * @example
572
+ * var deleted = await b.queue.purge("ingest");
573
+ * // → 42
574
+ */
410
575
  function purge(queueName, opts) {
411
576
  _requireInit();
412
577
  var b = _backendFor(opts);
@@ -424,6 +589,28 @@ function purge(queueName, opts) {
424
589
  // `dlqRetry` resets a single job back to 'pending' so it gets picked
425
590
  // up by consumers again (operator-driven — never automatic).
426
591
 
592
+ /**
593
+ * @primitive b.queue.dlqList
594
+ * @signature b.queue.dlqList(queueName, opts)
595
+ * @since 0.4.0
596
+ * @status stable
597
+ * @related b.queue.dlqRetry, b.queue.dlqSize
598
+ *
599
+ * Resolves with an array of dead-letter rows — jobs that exhausted
600
+ * their retries and were parked for human review. Each row carries
601
+ * the original payload, attempt count, last failure reason, and
602
+ * trace correlation id. Rejects with `DLQ_UNSUPPORTED` when the
603
+ * configured backend does not implement a dead-letter view.
604
+ *
605
+ * @opts
606
+ * backend?: string, // backend name; defaults to defaultBackend
607
+ * limit?: number, // backend-specific paging cap
608
+ *
609
+ * @example
610
+ * var dead = await b.queue.dlqList("ingest", { limit: 50 });
611
+ * dead.length;
612
+ * // → 3
613
+ */
427
614
  function dlqList(queueName, opts) {
428
615
  _requireInit();
429
616
  var b = _backendFor(opts);
@@ -434,6 +621,26 @@ function dlqList(queueName, opts) {
434
621
  return b.dlqList(queueName, opts);
435
622
  }
436
623
 
624
+ /**
625
+ * @primitive b.queue.dlqRetry
626
+ * @signature b.queue.dlqRetry(jobId, opts)
627
+ * @since 0.4.0
628
+ * @status stable
629
+ * @related b.queue.dlqList, b.queue.dlqSize
630
+ *
631
+ * Resets a single dead-letter row back to `pending` so consumers
632
+ * pick it up again. Operator-driven only — the framework never
633
+ * auto-retries failed-after-retries jobs because the failure mode
634
+ * usually requires human investigation. Resolves with `true` when
635
+ * the row was found and reset, `false` otherwise.
636
+ *
637
+ * @opts
638
+ * backend?: string, // backend name; defaults to defaultBackend
639
+ *
640
+ * @example
641
+ * var ok = await b.queue.dlqRetry("job-7c2f8e1a");
642
+ * // → true
643
+ */
437
644
  function dlqRetry(jobId, opts) {
438
645
  _requireInit();
439
646
  var b = _backendFor(opts);
@@ -451,6 +658,26 @@ function dlqRetry(jobId, opts) {
451
658
  });
452
659
  }
453
660
 
661
+ /**
662
+ * @primitive b.queue.dlqSize
663
+ * @signature b.queue.dlqSize(queueName, opts)
664
+ * @since 0.4.0
665
+ * @status stable
666
+ * @related b.queue.dlqList, b.queue.dlqRetry
667
+ *
668
+ * Resolves with the number of dead-letter rows for the named queue —
669
+ * jobs that exhausted their retries and were parked for human review.
670
+ * Operators wire this to dashboards / alerting so a growing DLQ
671
+ * surfaces before it becomes a backlog. Rejects with `DLQ_UNSUPPORTED`
672
+ * when the configured backend does not implement a dead-letter view.
673
+ *
674
+ * @opts
675
+ * backend?: string, // backend name; defaults to defaultBackend
676
+ *
677
+ * @example
678
+ * var stuck = await b.queue.dlqSize("ingest");
679
+ * // → 3
680
+ */
454
681
  function dlqSize(queueName, opts) {
455
682
  _requireInit();
456
683
  var b = _backendFor(opts);
@@ -461,6 +688,28 @@ function dlqSize(queueName, opts) {
461
688
  return b.dlqSize(queueName);
462
689
  }
463
690
 
691
+ /**
692
+ * @primitive b.queue.shutdown
693
+ * @signature b.queue.shutdown(opts)
694
+ * @since 0.4.0
695
+ * @status stable
696
+ * @related b.queue.consume, b.queue.init
697
+ *
698
+ * Cancels every active consumer and waits for in-flight handlers to
699
+ * drain, then stops the expired-lease sweep timer. Honors a deadline
700
+ * — handlers that exceed `timeoutMs` are abandoned (their leases
701
+ * expire and the sweep re-pends them on the next process). Idempotent
702
+ * — calling `shutdown` before `init` is a no-op so SIGTERM handlers
703
+ * can be wired unconditionally.
704
+ *
705
+ * @opts
706
+ * timeoutMs?: number, // drain deadline in ms (default 30000)
707
+ *
708
+ * @example
709
+ * process.on("SIGTERM", async function () {
710
+ * await b.queue.shutdown({ timeoutMs: 15000 });
711
+ * });
712
+ */
464
713
  async function shutdown(opts) {
465
714
  if (!initialized) return;
466
715
  opts = opts || {};
@@ -477,6 +726,23 @@ async function shutdown(opts) {
477
726
  if (sweepTimer) { sweepTimer.stop(); sweepTimer = null; }
478
727
  }
479
728
 
729
+ /**
730
+ * @primitive b.queue.listBackends
731
+ * @signature b.queue.listBackends()
732
+ * @since 0.4.0
733
+ * @status stable
734
+ * @related b.queue.init, b.queue.bootFromEnv
735
+ *
736
+ * Returns an array of `{ name, protocol, breakerState }` rows — one
737
+ * per configured backend. `breakerState` is `"closed"` / `"open"` /
738
+ * `"half-open"` from the per-backend circuit breaker. Operators wire
739
+ * this to a `/health/queue` endpoint or readiness probe so a tripped
740
+ * breaker surfaces in the orchestrator before silent backlog growth.
741
+ *
742
+ * @example
743
+ * var status = b.queue.listBackends();
744
+ * // → [{ name: "primary", protocol: "local", breakerState: "closed" }]
745
+ */
480
746
  function listBackends() {
481
747
  _requireInit();
482
748
  return Object.keys(backends).map(function (name) {
@@ -502,20 +768,52 @@ function _resetForTest() {
502
768
  audit.reset();
503
769
  }
504
770
 
505
- // enqueueFlow — atomic registration of a parent-child job graph.
506
- //
507
- // await b.queue.enqueueFlow({
508
- // queueName: "ingest",
509
- // children: [
510
- // { name: "fetch", payload: { url } },
511
- // { name: "transform", payload: { ... }, dependsOn: ["fetch"] },
512
- // { name: "publish", payload: { ... }, dependsOn: ["transform"] },
513
- // ],
514
- // });
515
- //
516
- // Cycle detection runs at registration (throws at call site). Each child enters
517
- // the queue with availableAt = MAX_SAFE_INTEGER until parent completion
518
- // bumps it. Returns { flowId, jobs: [{ name, jobId }, ...] }.
771
+ /**
772
+ * @primitive b.queue.enqueueFlow
773
+ * @signature b.queue.enqueueFlow(spec)
774
+ * @since 0.4.0
775
+ * @status stable
776
+ * @related b.queue.enqueue, b.queue.consume
777
+ *
778
+ * Atomically registers a parent-child job graph. Each child enqueues
779
+ * with a parking-lot `availableAt = MAX_SAFE_INTEGER` until every
780
+ * `dependsOn` row reaches `done`, at which point the dependent's
781
+ * availableAt drops to "now" and consumers pick it up. Cycle detection
782
+ * runs at registration time bad graphs reject with `FLOW_CYCLE` /
783
+ * `FLOW_UNKNOWN_DEP` before any row lands.
784
+ *
785
+ * Resolves with `{ flowId, jobs: [{ name, jobId }, ...] }`. The
786
+ * returned `jobId` array is in declaration order, not topological
787
+ * order — callers that need a specific child's id look it up by
788
+ * `name`.
789
+ *
790
+ * @opts
791
+ * queueName: string,
792
+ * children: [
793
+ * {
794
+ * name: string, // unique within the flow
795
+ * payload: any,
796
+ * dependsOn?: string[], // sibling names this child waits on
797
+ * priority?: number,
798
+ * maxAttempts?: number,
799
+ * classification?: string,
800
+ * traceId?: string,
801
+ * },
802
+ * ...
803
+ * ],
804
+ *
805
+ * @example
806
+ * var flow = await b.queue.enqueueFlow({
807
+ * queueName: "ingest",
808
+ * children: [
809
+ * { name: "fetch", payload: { url: "https://example.com" } },
810
+ * { name: "transform", payload: { stage: 1 }, dependsOn: ["fetch"] },
811
+ * { name: "publish", payload: { topic: "out" }, dependsOn: ["transform"] },
812
+ * ],
813
+ * });
814
+ * flow.jobs.length;
815
+ * // → 3
816
+ */
519
817
  function enqueueFlow(spec) {
520
818
  _requireInit();
521
819
  if (!spec || typeof spec !== "object") {
@@ -632,17 +930,40 @@ function enqueueFlow(spec) {
632
930
  );
633
931
  }
634
932
 
635
- // bootFromEnv — env-driven init mirroring b.network.bootFromEnv and
636
- // b.logStream.bootFromEnv. Reads the BLAMEJS_QUEUE_* env vars and
637
- // calls queue.init({ backends }) accordingly. Operators get a working
638
- // queue backend without writing build-app code.
639
- //
640
- // BLAMEJS_QUEUE_PROTOCOL local | redis (default: local)
641
- // BLAMEJS_QUEUE_REDIS_URL redis://host:port/db (required when protocol=redis)
642
- // BLAMEJS_QUEUE_REDIS_PASSWORD auth password
643
- // BLAMEJS_QUEUE_REDIS_USERNAME ACL username (optional)
644
- // BLAMEJS_QUEUE_REDIS_TLS "1"/"true" forces TLS (else inferred from rediss://)
645
- // BLAMEJS_QUEUE_REDIS_KEY_PREFIX key prefix (default "blamejs:queue")
933
+ /**
934
+ * @primitive b.queue.bootFromEnv
935
+ * @signature b.queue.bootFromEnv(opts)
936
+ * @since 0.4.0
937
+ * @status stable
938
+ * @related b.queue.init, b.queue.listBackends
939
+ *
940
+ * Env-driven `init` mirroring `b.network.bootFromEnv` and
941
+ * `b.logStream.bootFromEnv`. Reads `BLAMEJS_QUEUE_*` and calls
942
+ * `queue.init({ backends: { default: ... } })` so operators get a
943
+ * working queue without writing build-app code. Idempotent — a
944
+ * second call after `init` already ran is a no-op.
945
+ *
946
+ * Recognized env vars:
947
+ * BLAMEJS_QUEUE_PROTOCOL "local" | "redis" (default "local")
948
+ * BLAMEJS_QUEUE_REDIS_URL redis://host:port/db (required when protocol=redis)
949
+ * BLAMEJS_QUEUE_REDIS_PASSWORD auth password
950
+ * BLAMEJS_QUEUE_REDIS_USERNAME ACL username
951
+ * BLAMEJS_QUEUE_REDIS_TLS "1" / "true" forces TLS (else inferred from rediss://)
952
+ * BLAMEJS_QUEUE_REDIS_KEY_PREFIX key prefix (default "blamejs:queue")
953
+ *
954
+ * Throws `INVALID_CONFIG` when `BLAMEJS_QUEUE_PROTOCOL` is unknown
955
+ * or when `redis` is selected without `BLAMEJS_QUEUE_REDIS_URL` —
956
+ * operators catch the typo at boot rather than first enqueue.
957
+ *
958
+ * @opts
959
+ * env?: object, // override process.env for testing / fixtures
960
+ *
961
+ * @example
962
+ * process.env.BLAMEJS_QUEUE_PROTOCOL = "local";
963
+ * b.queue.bootFromEnv();
964
+ * b.queue.listBackends().length;
965
+ * // → 1
966
+ */
646
967
  function bootFromEnv(opts) {
647
968
  opts = opts || {};
648
969
  var env = opts.env || process.env;