@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.
- package/CHANGELOG.md +92 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/queue.js
CHANGED
|
@@ -1,34 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* surface
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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;
|