@blamejs/core 0.14.27 → 0.15.1
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 +6 -0
- package/README.md +2 -2
- package/index.js +4 -0
- package/lib/ai-content-detect.js +9 -10
- package/lib/api-key.js +158 -77
- package/lib/atomic-file.js +29 -1
- package/lib/audit-chain.js +47 -11
- package/lib/audit-sign.js +77 -2
- package/lib/audit-tools.js +79 -51
- package/lib/audit.js +228 -100
- package/lib/backup/index.js +13 -10
- package/lib/break-glass.js +202 -144
- package/lib/cache.js +174 -105
- package/lib/chain-writer.js +38 -16
- package/lib/cli.js +19 -14
- package/lib/cluster-provider-db.js +130 -104
- package/lib/cluster-storage.js +119 -22
- package/lib/cluster.js +119 -71
- package/lib/compliance.js +22 -0
- package/lib/consent.js +82 -29
- package/lib/constants.js +16 -11
- package/lib/crypto-field.js +387 -91
- package/lib/db-declare-row-policy.js +35 -22
- package/lib/db-file-lifecycle.js +3 -2
- package/lib/db-query.js +517 -256
- package/lib/db-schema.js +209 -44
- package/lib/db.js +202 -95
- package/lib/external-db-migrate.js +229 -139
- package/lib/external-db.js +25 -15
- package/lib/framework-error.js +11 -0
- package/lib/framework-files.js +73 -0
- package/lib/framework-schema.js +695 -394
- package/lib/gate-contract.js +596 -1
- package/lib/guard-agent-registry.js +26 -44
- package/lib/guard-all.js +1 -0
- package/lib/guard-auth.js +42 -112
- package/lib/guard-cidr.js +33 -154
- package/lib/guard-csv.js +46 -113
- package/lib/guard-domain.js +34 -157
- package/lib/guard-dsn.js +27 -43
- package/lib/guard-email.js +47 -69
- package/lib/guard-envelope.js +19 -32
- package/lib/guard-event-bus-payload.js +24 -42
- package/lib/guard-event-bus-topic.js +25 -43
- package/lib/guard-filename.js +42 -106
- package/lib/guard-graphql.js +42 -123
- package/lib/guard-html.js +53 -108
- package/lib/guard-idempotency-key.js +24 -42
- package/lib/guard-image.js +46 -103
- package/lib/guard-imap-command.js +18 -32
- package/lib/guard-jmap.js +16 -30
- package/lib/guard-json.js +38 -108
- package/lib/guard-jsonpath.js +38 -171
- package/lib/guard-jwt.js +49 -179
- package/lib/guard-list-id.js +25 -41
- package/lib/guard-list-unsubscribe.js +27 -43
- package/lib/guard-mail-compose.js +24 -42
- package/lib/guard-mail-move.js +26 -44
- package/lib/guard-mail-query.js +28 -46
- package/lib/guard-mail-reply.js +24 -42
- package/lib/guard-mail-sieve.js +24 -42
- package/lib/guard-managesieve-command.js +17 -31
- package/lib/guard-markdown.js +37 -104
- package/lib/guard-message-id.js +26 -45
- package/lib/guard-mime.js +39 -151
- package/lib/guard-oauth.js +54 -135
- package/lib/guard-pdf.js +45 -101
- package/lib/guard-pop3-command.js +21 -31
- package/lib/guard-posture-chain.js +24 -42
- package/lib/guard-regex.js +33 -107
- package/lib/guard-saga-config.js +24 -42
- package/lib/guard-shell.js +42 -172
- package/lib/guard-smtp-command.js +48 -54
- package/lib/guard-snapshot-envelope.js +24 -42
- package/lib/guard-sql.js +1491 -0
- package/lib/guard-stream-args.js +24 -43
- package/lib/guard-svg.js +47 -65
- package/lib/guard-template.js +35 -172
- package/lib/guard-tenant-id.js +26 -45
- package/lib/guard-time.js +32 -154
- package/lib/guard-trace-context.js +25 -44
- package/lib/guard-uuid.js +32 -153
- package/lib/guard-xml.js +38 -113
- package/lib/guard-yaml.js +51 -163
- package/lib/http-client.js +14 -0
- package/lib/inbox.js +120 -107
- package/lib/legal-hold.js +107 -50
- package/lib/log-stream-cloudwatch.js +47 -31
- package/lib/log-stream-otlp.js +32 -18
- package/lib/mail-crypto-smime.js +2 -6
- package/lib/mail-greylist.js +2 -6
- package/lib/mail-helo.js +2 -6
- package/lib/mail-journal.js +85 -64
- package/lib/mail-rbl.js +2 -6
- package/lib/mail-scan.js +2 -6
- package/lib/mail-spam-score.js +2 -6
- package/lib/mail-store.js +293 -154
- package/lib/middleware/fetch-metadata.js +17 -7
- package/lib/middleware/idempotency-key.js +54 -38
- package/lib/middleware/rate-limit.js +102 -32
- package/lib/middleware/security-headers.js +21 -5
- package/lib/migrations.js +108 -66
- package/lib/network-heartbeat.js +7 -0
- package/lib/nonce-store.js +31 -9
- package/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/object-store/azure-blob.js +31 -3
- package/lib/object-store/sigv4.js +10 -0
- package/lib/outbox.js +136 -82
- package/lib/pqc-agent.js +44 -0
- package/lib/pubsub-cluster.js +42 -20
- package/lib/queue-local.js +202 -139
- package/lib/queue-redis.js +9 -1
- package/lib/queue-sqs.js +6 -0
- package/lib/retention.js +82 -39
- package/lib/safe-dns.js +29 -45
- package/lib/safe-ical.js +18 -33
- package/lib/safe-icap.js +27 -43
- package/lib/safe-sieve.js +21 -40
- package/lib/safe-sql.js +124 -3
- package/lib/safe-vcard.js +18 -33
- package/lib/scheduler.js +35 -12
- package/lib/seeders.js +122 -74
- package/lib/session-stores.js +42 -14
- package/lib/session.js +116 -72
- package/lib/sql.js +3885 -0
- package/lib/static.js +45 -7
- package/lib/subject.js +89 -49
- package/lib/vault/index.js +3 -2
- package/lib/vault/passphrase-ops.js +3 -2
- package/lib/vault/rotate.js +104 -64
- package/lib/vendor-data.js +2 -0
- package/lib/websocket.js +16 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/queue-local.js
CHANGED
|
@@ -50,6 +50,7 @@ var lazyRequire = require("./lazy-require");
|
|
|
50
50
|
var numericBounds = require("./numeric-bounds");
|
|
51
51
|
var safeJson = require("./safe-json");
|
|
52
52
|
var safeSql = require("./safe-sql");
|
|
53
|
+
var sql = require("./sql");
|
|
53
54
|
var scheduler = require("./scheduler");
|
|
54
55
|
var { QueueError } = require("./framework-error");
|
|
55
56
|
|
|
@@ -59,10 +60,16 @@ var _err = QueueError.factory;
|
|
|
59
60
|
// COLUMN→seal map registered in db.js's FRAMEWORK_SCHEMA, NOT the
|
|
60
61
|
// physical table the SQL writes to. An operator who points the backend
|
|
61
62
|
// at their own table still seals payload + lastError through this map,
|
|
62
|
-
// so a bring-your-own table inherits the same at-rest protection.
|
|
63
|
+
// so a bring-your-own table inherits the same at-rest protection. The KEY
|
|
64
|
+
// must stay byte-identical to db.js's registerTable literal.
|
|
65
|
+
// allow:hand-rolled-sql — cryptoField seal-table registry KEY, not a SQL table.
|
|
63
66
|
var SEAL_TABLE = "_blamejs_jobs";
|
|
64
67
|
|
|
65
|
-
// Default
|
|
68
|
+
// Default LOGICAL table for the local backend. Passed BARE to b.sql so
|
|
69
|
+
// clusterStorage.resolveTables rewrites it to the configured cluster name
|
|
70
|
+
// (applying the configurable prefix); a custom config.table is quoted at
|
|
71
|
+
// build time instead. b.sql owns the quoting; this is the logical name.
|
|
72
|
+
// allow:hand-rolled-sql — framework logical jobs-table name handed to b.sql, not a SQL literal.
|
|
66
73
|
var DEFAULT_TABLE = "_blamejs_jobs";
|
|
67
74
|
|
|
68
75
|
// vault is lazy-required because some flows (sealed lastError) only
|
|
@@ -111,14 +118,6 @@ var LEASE_RETURN_COLS = [
|
|
|
111
118
|
"repeatCron", "repeatTimezone", "flowId", "flowChildName",
|
|
112
119
|
];
|
|
113
120
|
|
|
114
|
-
function _quotedList(cols) {
|
|
115
|
-
return cols.map(function (c) { return '"' + c + '"'; }).join(", ");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function _placeholders(cols) {
|
|
119
|
-
return cols.map(function () { return "?"; }).join(", ");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
121
|
function _shapeLeasedRow(raw) {
|
|
123
122
|
// raw is a row coming back from RETURNING — payload is sealed if
|
|
124
123
|
// present. Run through cryptoField's unseal pipeline so the caller
|
|
@@ -162,12 +161,19 @@ function _resolveStore(handle) {
|
|
|
162
161
|
return handle;
|
|
163
162
|
}
|
|
164
163
|
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
// the
|
|
169
|
-
// the framework's cluster-mode
|
|
170
|
-
// on the
|
|
164
|
+
// Resolve the b.sql table-builder options from config.table + config.schema.
|
|
165
|
+
// Every SQL statement is composed through b.sql, which quotes identifiers
|
|
166
|
+
// through b.safeSql so an operator-supplied name cannot interpolate SQL
|
|
167
|
+
// through the identifier slot (CWE-89). The DEFAULT logical table is passed
|
|
168
|
+
// BARE (quoteName off) so the framework's cluster-mode rewrite
|
|
169
|
+
// (clusterStorage.resolveTables) still fires on the jobs table and applies
|
|
170
|
+
// the configurable prefix; any custom table/schema is validated + quoted at
|
|
171
|
+
// build time instead (no rewrite — it is the operator's own table).
|
|
172
|
+
//
|
|
173
|
+
// Returns { name, opts } where `opts` is spread into every b.sql verb call:
|
|
174
|
+
// default → { dialect: "sqlite" } (bare name, rewritten)
|
|
175
|
+
// custom → { dialect: "sqlite", quoteName: true } (quoted, no rewrite)
|
|
176
|
+
// custom+schema → adds { schema } (b.sql emits the quoted schema.table form)
|
|
171
177
|
function _resolveTableRef(config) {
|
|
172
178
|
var table = config.table !== undefined && config.table !== null
|
|
173
179
|
? config.table : DEFAULT_TABLE;
|
|
@@ -183,23 +189,33 @@ function _resolveTableRef(config) {
|
|
|
183
189
|
var usingDefault = (table === DEFAULT_TABLE) &&
|
|
184
190
|
(schema === undefined || schema === null);
|
|
185
191
|
if (usingDefault) {
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
return DEFAULT_TABLE;
|
|
192
|
+
// Bare default — b.sql leaves it unquoted so cluster-mode resolveTables
|
|
193
|
+
// recognizes + rewrites the jobs table (and applies the prefix).
|
|
194
|
+
return { name: DEFAULT_TABLE, opts: { dialect: "sqlite" } };
|
|
189
195
|
}
|
|
190
|
-
// Any custom table/schema is validated + dialect-quoted.
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
196
|
+
// Any custom table/schema is validated + dialect-quoted by b.sql at build
|
|
197
|
+
// time. validateIdentifier (run inside b.sql's TableRef) THROWs
|
|
198
|
+
// (SqlBuilderError / SafeSqlError) on a bad identifier; surface that as the
|
|
199
|
+
// queue's config-time error so the operator catches the typo at boot rather
|
|
200
|
+
// than on first enqueue.
|
|
201
|
+
var opts = { dialect: "sqlite", quoteName: true };
|
|
202
|
+
if (schema !== undefined && schema !== null && schema !== "") opts.schema = schema;
|
|
194
203
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
// Validate the custom identifier(s) at config time with the STRICTER
|
|
205
|
+
// policy (allowReserved off — a reserved word like `select` is refused
|
|
206
|
+
// for a bring-your-own queue table, matching the prior
|
|
207
|
+
// quoteIdentifier / quoteQualified contract). b.sql then quotes the
|
|
208
|
+
// already-validated name at build time. validateIdentifier THROWs
|
|
209
|
+
// (SafeSqlError) on a bad shape / reserved word / injection-shaped
|
|
210
|
+
// schema, surfaced here as the queue's config-time error so the
|
|
211
|
+
// operator catches the typo at boot rather than on first enqueue.
|
|
212
|
+
safeSql.validateIdentifier(table);
|
|
213
|
+
if (opts.schema) safeSql.validateIdentifier(opts.schema);
|
|
199
214
|
} catch (e) {
|
|
200
215
|
throw _err("INVALID_TABLE",
|
|
201
216
|
"queue local table/schema failed identifier validation: " + e.message, true);
|
|
202
217
|
}
|
|
218
|
+
return { name: table, opts: opts };
|
|
203
219
|
}
|
|
204
220
|
|
|
205
221
|
function create(config) {
|
|
@@ -208,11 +224,20 @@ function create(config) {
|
|
|
208
224
|
// exactly: cluster-storage dispatch to the framework's main DB
|
|
209
225
|
// (single-node) / external-db (cluster), table "_blamejs_jobs".
|
|
210
226
|
var store = _resolveStore(config.db);
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
|
|
227
|
+
// ref = { name, opts } for every b.sql verb call — the bare default
|
|
228
|
+
// jobs table (clusterStorage rewrites it + applies the configurable
|
|
229
|
+
// prefix) or a validated + quoted custom table/schema. Small helpers
|
|
230
|
+
// open each verb builder pre-bound to this table so the table reference
|
|
231
|
+
// is resolved in exactly one place.
|
|
232
|
+
var ref = _resolveTableRef(config);
|
|
233
|
+
function _select() { return sql.select(ref.name, ref.opts); }
|
|
234
|
+
function _insert() { return sql.insert(ref.name, ref.opts); }
|
|
235
|
+
function _update() { return sql.update(ref.name, ref.opts); }
|
|
236
|
+
function _delete() { return sql.delete(ref.name, ref.opts); }
|
|
237
|
+
// Quoted column expression for a setRaw RHS that references the column's
|
|
238
|
+
// own pre-update value (attempts/availableAt). dialect-sqlite quoting is
|
|
239
|
+
// the double-quote form clusterStorage's Postgres path keeps.
|
|
240
|
+
function _qc(col) { return safeSql.quoteIdentifier(col, "sqlite", { allowReserved: true }); }
|
|
216
241
|
|
|
217
242
|
async function enqueue(queueName, payload, opts) {
|
|
218
243
|
cluster.requireLeader();
|
|
@@ -280,13 +305,16 @@ function create(config) {
|
|
|
280
305
|
dependsOn: dependsOn,
|
|
281
306
|
};
|
|
282
307
|
var sealed = cryptoField.sealRow(SEAL_TABLE, row);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
308
|
+
// Build the full column→value map in JOB_COLS order (a missing sealed
|
|
309
|
+
// column binds NULL, matching the prior positional-values shape). b.sql
|
|
310
|
+
// quotes every column + binds every value as a placeholder.
|
|
311
|
+
var insertRow = {};
|
|
312
|
+
for (var ci = 0; ci < JOB_COLS.length; ci++) {
|
|
313
|
+
var col = JOB_COLS[ci];
|
|
314
|
+
insertRow[col] = col in sealed ? sealed[col] : null;
|
|
315
|
+
}
|
|
316
|
+
var insertBuilt = _insert().columns(JOB_COLS).values(insertRow).toSql();
|
|
317
|
+
await store.execute(insertBuilt.sql, insertBuilt.params);
|
|
290
318
|
return {
|
|
291
319
|
jobId: row._id,
|
|
292
320
|
queueName: queueName,
|
|
@@ -307,21 +335,28 @@ function create(config) {
|
|
|
307
335
|
// rows that still match status='pending' after the lock acquires
|
|
308
336
|
// (Postgres EvalPlanQual; SQLite is single-writer so the same row
|
|
309
337
|
// can't be picked twice). RETURNING hands back the leased columns
|
|
310
|
-
// so we don't need a separate SELECT after the UPDATE.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
"
|
|
316
|
-
"
|
|
317
|
-
"
|
|
318
|
-
"
|
|
319
|
-
"
|
|
320
|
-
"
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
338
|
+
// so we don't need a separate SELECT after the UPDATE. maxRows is a
|
|
339
|
+
// framework-computed integer emitted inline via b.sql's .limit() (a
|
|
340
|
+
// bound LIMIT param has no portable form across the subquery path);
|
|
341
|
+
// attempts = attempts + 1 is a setRaw over the column's own value.
|
|
342
|
+
var leaseInner = _select()
|
|
343
|
+
.columns(["_id"])
|
|
344
|
+
.where("queueName", queueName)
|
|
345
|
+
.where("status", "pending")
|
|
346
|
+
.whereOp("availableAt", "<=", nowMs)
|
|
347
|
+
.orderBy("priority", "desc")
|
|
348
|
+
.orderBy("availableAt", "asc")
|
|
349
|
+
.orderBy("enqueuedAt", "asc")
|
|
350
|
+
.limit(maxRows);
|
|
351
|
+
var leaseBuilt = _update()
|
|
352
|
+
.set("status", "inflight")
|
|
353
|
+
.set("leasedAt", nowMs)
|
|
354
|
+
.set("leaseExpiresAt", leaseExpiresAt)
|
|
355
|
+
.setRaw("attempts", _qc("attempts") + " + 1", [])
|
|
356
|
+
.whereIn("_id", leaseInner)
|
|
357
|
+
.returning(LEASE_RETURN_COLS)
|
|
358
|
+
.toSql();
|
|
359
|
+
var result = await store.execute(leaseBuilt.sql, leaseBuilt.params);
|
|
325
360
|
var leased = [];
|
|
326
361
|
for (var i = 0; i < result.rows.length; i++) {
|
|
327
362
|
leased.push(_shapeLeasedRow(result.rows[i]));
|
|
@@ -340,11 +375,12 @@ function create(config) {
|
|
|
340
375
|
"extendLease: additionalMs must be a positive number", true);
|
|
341
376
|
}
|
|
342
377
|
var newExpiry = Date.now() + additionalMs;
|
|
343
|
-
var
|
|
344
|
-
"
|
|
345
|
-
"
|
|
346
|
-
|
|
347
|
-
|
|
378
|
+
var built = _update()
|
|
379
|
+
.set("leaseExpiresAt", newExpiry)
|
|
380
|
+
.where("_id", jobId)
|
|
381
|
+
.where("status", "inflight")
|
|
382
|
+
.toSql();
|
|
383
|
+
var result = await store.execute(built.sql, built.params);
|
|
348
384
|
return (result.rowCount || 0) > 0;
|
|
349
385
|
}
|
|
350
386
|
|
|
@@ -355,19 +391,22 @@ function create(config) {
|
|
|
355
391
|
// the status flip. Single SELECT + UPDATE pair under the same
|
|
356
392
|
// jobId — race-free under SQLite (single-writer); cluster-storage
|
|
357
393
|
// dispatches both calls to the same backend.
|
|
358
|
-
var
|
|
359
|
-
"
|
|
360
|
-
|
|
361
|
-
"
|
|
362
|
-
|
|
363
|
-
);
|
|
394
|
+
var rowBuilt = _select()
|
|
395
|
+
.columns(["_id", "queueName", "payload", "repeatCron", "repeatTimezone",
|
|
396
|
+
"flowId", "flowChildName", "priority", "classification", "traceId"])
|
|
397
|
+
.where("_id", jobId)
|
|
398
|
+
.toSql();
|
|
399
|
+
var rowRes = await store.execute(rowBuilt.sql, rowBuilt.params);
|
|
364
400
|
var row = (rowRes && rowRes.rows && rowRes.rows[0]) || null;
|
|
365
401
|
|
|
366
|
-
|
|
367
|
-
"
|
|
368
|
-
"
|
|
369
|
-
|
|
370
|
-
|
|
402
|
+
var doneBuilt = _update()
|
|
403
|
+
.set("status", "done")
|
|
404
|
+
.set("finishedAt", nowMs)
|
|
405
|
+
.set("leaseExpiresAt", null)
|
|
406
|
+
.where("_id", jobId)
|
|
407
|
+
.where("status", "inflight")
|
|
408
|
+
.toSql();
|
|
409
|
+
await store.execute(doneBuilt.sql, doneBuilt.params);
|
|
371
410
|
|
|
372
411
|
// Repeat-in-queue: cron-recurring job re-enqueues itself for the
|
|
373
412
|
// next firing time. Failures (which take the fail() path) don't
|
|
@@ -404,11 +443,13 @@ function create(config) {
|
|
|
404
443
|
}
|
|
405
444
|
|
|
406
445
|
async function _maybeReleaseFlowChildren(flowId, completedJobId, completedChildName, nowMs) {
|
|
407
|
-
var
|
|
408
|
-
"
|
|
409
|
-
"
|
|
410
|
-
|
|
411
|
-
|
|
446
|
+
var siblingsBuilt = _select()
|
|
447
|
+
.columns(["_id", "dependsOn", "flowChildName", "status", "availableAt"])
|
|
448
|
+
.where("flowId", flowId)
|
|
449
|
+
.where("status", "pending")
|
|
450
|
+
.whereOp("availableAt", ">", nowMs)
|
|
451
|
+
.toSql();
|
|
452
|
+
var siblingsRes = await store.execute(siblingsBuilt.sql, siblingsBuilt.params);
|
|
412
453
|
var siblings = (siblingsRes && siblingsRes.rows) || [];
|
|
413
454
|
for (var i = 0; i < siblings.length; i++) {
|
|
414
455
|
var sib = siblings[i];
|
|
@@ -424,19 +465,25 @@ function create(config) {
|
|
|
424
465
|
var dep = deps[d];
|
|
425
466
|
// Quick path: just-completed job matches by id or child name.
|
|
426
467
|
if (dep === completedJobId || (completedChildName && dep === completedChildName)) continue;
|
|
427
|
-
// Otherwise SELECT to confirm done.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
[
|
|
432
|
-
|
|
468
|
+
// Otherwise SELECT to confirm done. The (_id = ? OR flowChildName = ?)
|
|
469
|
+
// disjunction is a whereGroup so it AND-composes at one precedence
|
|
470
|
+
// level with the flowId + status equalities.
|
|
471
|
+
var depBuilt = _select()
|
|
472
|
+
.columns(["_id"])
|
|
473
|
+
.where("flowId", flowId)
|
|
474
|
+
.where("status", "done")
|
|
475
|
+
.whereGroup(function (g) { g.where("_id", dep).orWhere("flowChildName", dep); })
|
|
476
|
+
.limit(1)
|
|
477
|
+
.toSql();
|
|
478
|
+
var depRes = await store.execute(depBuilt.sql, depBuilt.params);
|
|
433
479
|
if (!depRes || !depRes.rows || depRes.rows.length === 0) { allDone = false; break; }
|
|
434
480
|
}
|
|
435
481
|
if (allDone) {
|
|
436
|
-
|
|
437
|
-
"
|
|
438
|
-
|
|
439
|
-
|
|
482
|
+
var releaseBuilt = _update()
|
|
483
|
+
.set("availableAt", nowMs)
|
|
484
|
+
.where("_id", sib._id)
|
|
485
|
+
.toSql();
|
|
486
|
+
await store.execute(releaseBuilt.sql, releaseBuilt.params);
|
|
440
487
|
}
|
|
441
488
|
}
|
|
442
489
|
}
|
|
@@ -452,36 +499,46 @@ function create(config) {
|
|
|
452
499
|
// row's current attempts/maxAttempts. CASE expressions split the
|
|
453
500
|
// status / availableAt / finishedAt updates per branch — same
|
|
454
501
|
// semantics as the previous SELECT-then-UPDATE-in-transaction
|
|
455
|
-
// path, but no cross-dialect transaction primitive needed.
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
"
|
|
463
|
-
"
|
|
464
|
-
|
|
465
|
-
|
|
502
|
+
// path, but no cross-dialect transaction primitive needed. Each CASE
|
|
503
|
+
// is a b.sql setRaw value-expression (guarded by b.guardSql) over the
|
|
504
|
+
// row's own columns; the branch values bind as `?` placeholders (the
|
|
505
|
+
// prior 'pending'/'failed' SQL literals now bind, which keeps the raw
|
|
506
|
+
// fragment literal-free).
|
|
507
|
+
var attemptsLt = _qc("attempts") + " < " + _qc("maxAttempts");
|
|
508
|
+
var failBuilt = _update()
|
|
509
|
+
.setRaw("status", "CASE WHEN " + attemptsLt + " THEN ? ELSE ? END", ["pending", "failed"])
|
|
510
|
+
.set("lastError", sealedErr)
|
|
511
|
+
.set("leaseExpiresAt", null)
|
|
512
|
+
.setRaw("availableAt", "CASE WHEN " + attemptsLt + " THEN ? ELSE " + _qc("availableAt") + " END",
|
|
513
|
+
[nowMs + retryDelayMs])
|
|
514
|
+
.setRaw("finishedAt", "CASE WHEN " + attemptsLt + " THEN NULL ELSE ? END", [nowMs])
|
|
515
|
+
.where("_id", jobId)
|
|
516
|
+
.toSql();
|
|
517
|
+
await store.execute(failBuilt.sql, failBuilt.params);
|
|
466
518
|
return true;
|
|
467
519
|
}
|
|
468
520
|
|
|
469
521
|
async function sweepExpired() {
|
|
470
522
|
cluster.requireLeader();
|
|
471
|
-
var
|
|
472
|
-
"
|
|
473
|
-
"
|
|
474
|
-
|
|
475
|
-
|
|
523
|
+
var built = _update()
|
|
524
|
+
.set("status", "pending")
|
|
525
|
+
.set("leaseExpiresAt", null)
|
|
526
|
+
.where("status", "inflight")
|
|
527
|
+
.whereOp("leaseExpiresAt", "<", Date.now())
|
|
528
|
+
.toSql();
|
|
529
|
+
var result = await store.execute(built.sql, built.params);
|
|
476
530
|
return result.rowCount || 0;
|
|
477
531
|
}
|
|
478
532
|
|
|
479
533
|
async function size(queueName) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
534
|
+
// (status = 'pending' OR status = 'inflight') is an IN-list over the two
|
|
535
|
+
// active states — b.sql expands it to (?, ?) bound placeholders.
|
|
536
|
+
var built = _select()
|
|
537
|
+
.count("*", "n")
|
|
538
|
+
.where("queueName", queueName)
|
|
539
|
+
.whereIn("status", ["pending", "inflight"])
|
|
540
|
+
.toSql();
|
|
541
|
+
var row = await store.executeOne(built.sql, built.params);
|
|
485
542
|
return row ? Number(row.n) : 0;
|
|
486
543
|
}
|
|
487
544
|
|
|
@@ -503,14 +560,15 @@ function create(config) {
|
|
|
503
560
|
}
|
|
504
561
|
limit = opts.limit;
|
|
505
562
|
}
|
|
506
|
-
var
|
|
507
|
-
"
|
|
508
|
-
|
|
509
|
-
"
|
|
510
|
-
"
|
|
511
|
-
"
|
|
512
|
-
|
|
513
|
-
|
|
563
|
+
var built = _select()
|
|
564
|
+
.columns(["_id", "queueName", "payload", "status", "enqueuedAt", "finishedAt",
|
|
565
|
+
"attempts", "maxAttempts", "lastError", "traceId", "classification"])
|
|
566
|
+
.where("queueName", queueName)
|
|
567
|
+
.where("status", "failed")
|
|
568
|
+
.orderBy("finishedAt", "desc")
|
|
569
|
+
.limit(limit)
|
|
570
|
+
.toSql();
|
|
571
|
+
var rows = await store.executeAll(built.sql, built.params);
|
|
514
572
|
return rows.map(function (row) {
|
|
515
573
|
var unsealed = cryptoField.unsealRow(SEAL_TABLE, row);
|
|
516
574
|
return {
|
|
@@ -532,36 +590,39 @@ function create(config) {
|
|
|
532
590
|
async function dlqRetry(jobId) {
|
|
533
591
|
cluster.requireLeader();
|
|
534
592
|
var nowMs = Date.now();
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
593
|
+
// NULL resets bind as null params (the prior SQL-literal NULLs); the
|
|
594
|
+
// string-literal statuses bind too.
|
|
595
|
+
var built = _update()
|
|
596
|
+
.set({
|
|
597
|
+
status: "pending",
|
|
598
|
+
attempts: 0,
|
|
599
|
+
availableAt: nowMs,
|
|
600
|
+
finishedAt: null,
|
|
601
|
+
leasedAt: null,
|
|
602
|
+
leaseExpiresAt: null,
|
|
603
|
+
lastError: null,
|
|
604
|
+
})
|
|
605
|
+
.where("_id", jobId)
|
|
606
|
+
.where("status", "failed")
|
|
607
|
+
.toSql();
|
|
608
|
+
var result = await store.execute(built.sql, built.params);
|
|
547
609
|
return (result.rowCount || 0) > 0;
|
|
548
610
|
}
|
|
549
611
|
|
|
550
612
|
async function dlqSize(queueName) {
|
|
551
|
-
var
|
|
552
|
-
"
|
|
553
|
-
"
|
|
554
|
-
|
|
555
|
-
|
|
613
|
+
var built = _select()
|
|
614
|
+
.count("*", "n")
|
|
615
|
+
.where("queueName", queueName)
|
|
616
|
+
.where("status", "failed")
|
|
617
|
+
.toSql();
|
|
618
|
+
var row = await store.executeOne(built.sql, built.params);
|
|
556
619
|
return row ? Number(row.n) : 0;
|
|
557
620
|
}
|
|
558
621
|
|
|
559
622
|
async function purge(queueName) {
|
|
560
623
|
cluster.requireLeader();
|
|
561
|
-
var
|
|
562
|
-
|
|
563
|
-
[queueName]
|
|
564
|
-
);
|
|
624
|
+
var built = _delete().where("queueName", queueName).toSql();
|
|
625
|
+
var result = await store.execute(built.sql, built.params);
|
|
565
626
|
return result.rowCount || 0;
|
|
566
627
|
}
|
|
567
628
|
|
|
@@ -575,10 +636,12 @@ function create(config) {
|
|
|
575
636
|
// to JSON for the dependsOn column.
|
|
576
637
|
async function patchFlowDeps(jobId, depIds) {
|
|
577
638
|
cluster.requireLeader();
|
|
578
|
-
var
|
|
579
|
-
"
|
|
580
|
-
|
|
581
|
-
|
|
639
|
+
var built = _update()
|
|
640
|
+
.set("dependsOn", JSON.stringify(depIds))
|
|
641
|
+
.set("availableAt", FLOW_BLOCKED_AVAILABLE_AT)
|
|
642
|
+
.where("_id", jobId)
|
|
643
|
+
.toSql();
|
|
644
|
+
var result = await store.execute(built.sql, built.params);
|
|
582
645
|
return (result.rowCount || 0) > 0;
|
|
583
646
|
}
|
|
584
647
|
|
package/lib/queue-redis.js
CHANGED
|
@@ -310,7 +310,10 @@ function create(opts) {
|
|
|
310
310
|
// contract queue-local returns from _shapeLeasedRow.
|
|
311
311
|
function _shapeLeasedRow(jobId, raw) {
|
|
312
312
|
if (!raw) return null;
|
|
313
|
-
//
|
|
313
|
+
// The cryptoField seal-table registry KEY (matches db.js's registerTable
|
|
314
|
+
// literal), not a SQL table name; this adapter holds no SQL (Redis
|
|
315
|
+
// ZSET/HASH ops). Keep it byte-identical so payload + lastError unseal.
|
|
316
|
+
// allow:hand-rolled-sql — cryptoField seal-table registry KEY, not SQL.
|
|
314
317
|
var unsealed = cryptoField.unsealRow("_blamejs_jobs", raw);
|
|
315
318
|
return {
|
|
316
319
|
jobId: jobId,
|
|
@@ -368,6 +371,9 @@ function create(opts) {
|
|
|
368
371
|
dependsOn: Array.isArray(opts2.dependsOn) && opts2.dependsOn.length > 0
|
|
369
372
|
? JSON.stringify(opts2.dependsOn) : null,
|
|
370
373
|
};
|
|
374
|
+
// cryptoField seal-table registry KEY (db.js registers payload + lastError
|
|
375
|
+
// under this literal), not a SQL table; this Redis adapter holds no SQL.
|
|
376
|
+
// allow:hand-rolled-sql — cryptoField seal-table registry KEY, not SQL.
|
|
371
377
|
var sealed = cryptoField.sealRow("_blamejs_jobs", row);
|
|
372
378
|
|
|
373
379
|
// Pipeline: HSET job + ZADD ready + SADD queues + (if flowId)
|
|
@@ -466,6 +472,7 @@ function create(opts) {
|
|
|
466
472
|
|
|
467
473
|
if (raw.repeatCron) {
|
|
468
474
|
try {
|
|
475
|
+
// allow:hand-rolled-sql — cryptoField seal-table registry KEY, not SQL.
|
|
469
476
|
var unsealed = cryptoField.unsealRow("_blamejs_jobs", raw);
|
|
470
477
|
var cron = scheduler.parseCron(unsealed.repeatCron);
|
|
471
478
|
var nextMs = scheduler.nextCronFire(
|
|
@@ -689,6 +696,7 @@ function create(opts) {
|
|
|
689
696
|
for (var i = 0; i < idStrs.length; i++) {
|
|
690
697
|
var raw = _decodeHash(hashes[i]);
|
|
691
698
|
if (!raw) continue;
|
|
699
|
+
// allow:hand-rolled-sql — cryptoField seal-table registry KEY, not SQL.
|
|
692
700
|
var unsealed = cryptoField.unsealRow("_blamejs_jobs", raw);
|
|
693
701
|
out.push({
|
|
694
702
|
jobId: idStrs[i],
|
package/lib/queue-sqs.js
CHANGED
|
@@ -175,6 +175,11 @@ function create(opts) {
|
|
|
175
175
|
enqueueOpts = enqueueOpts || {};
|
|
176
176
|
var queueUrl = queueUrlResolver(queueName);
|
|
177
177
|
var jobId = generateToken(C.BYTES.bytes(16));
|
|
178
|
+
// The cryptoField seal-table registry KEY (matches db.js's registerTable
|
|
179
|
+
// literal), not a SQL table name; this SQS adapter holds no SQL
|
|
180
|
+
// (AWSJsonProtocol over HTTPS). Keep it byte-identical so the sealed
|
|
181
|
+
// message body unseals under the same schema on receive.
|
|
182
|
+
// allow:hand-rolled-sql — cryptoField seal-table registry KEY, not SQL.
|
|
178
183
|
var sealed = cryptoField.sealRow("_blamejs_jobs", {
|
|
179
184
|
_id: jobId,
|
|
180
185
|
queueName: queueName,
|
|
@@ -222,6 +227,7 @@ function create(opts) {
|
|
|
222
227
|
var sealed;
|
|
223
228
|
try { sealed = safeJson.parse(m.Body); }
|
|
224
229
|
catch (_e) { continue; }
|
|
230
|
+
// allow:hand-rolled-sql — cryptoField seal-table registry KEY, not SQL.
|
|
225
231
|
var unsealed = cryptoField.unsealRow("_blamejs_jobs", sealed);
|
|
226
232
|
var payload;
|
|
227
233
|
try {
|