@blamejs/core 0.5.16 → 0.5.18

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 CHANGED
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.5.x
10
10
 
11
+ - **0.5.17** (2026-05-01) — primitive-drift sweep: csv unify + 3 new shared primitives
12
+ - **0.5.16** (2026-04-30) — b.otelExport: OTLP/HTTP exporter for b.observability
11
13
  - **0.5.15** (2026-04-30) — b.archive: ZIP creation
12
14
  - **0.5.14** (2026-04-30) — b.time: timezone-aware datetime arithmetic + formatting
13
15
  - **0.5.13** (2026-04-30) — b.testing.request: supertest-style chainable HTTP test helper
@@ -60,9 +60,9 @@
60
60
  */
61
61
 
62
62
  var fs = require("fs");
63
- var nodeCrypto = require("node:crypto");
64
63
  var os = require("os");
65
64
  var path = require("path");
65
+ var crypto = require("../crypto");
66
66
  var atomicFile = require("../atomic-file");
67
67
  var backupBundle = require("./bundle");
68
68
  var lazyRequire = require("../lazy-require");
@@ -80,7 +80,7 @@ function _isValidBundleId(s) {
80
80
  }
81
81
 
82
82
  function _generateBundleId() {
83
- return atomicFile.pathTimestamp() + "-" + nodeCrypto.randomBytes(4).toString("hex");
83
+ return atomicFile.pathTimestamp() + "-" + crypto.generateToken(4);
84
84
  }
85
85
 
86
86
  function _dirSize(p) {
@@ -243,6 +243,11 @@ function create(opts) {
243
243
  // running backup against an external db handle still work).
244
244
  if (flushBeforeBackup === null && opts.flushBeforeBackup !== false) {
245
245
  try {
246
+ // Inline require: backup is a leaf module the framework re-exports
247
+ // without forcing operators to also bring in lib/db. Hoisting to
248
+ // top-of-file would require everyone using `b.backup` from
249
+ // outside the framework to have ./db loadable in the same module
250
+ // graph, which CLI tools and stand-alone backup runners don't.
246
251
  var dbModule = require("../db");
247
252
  if (typeof dbModule.flushToDisk === "function") {
248
253
  flushBeforeBackup = function () { dbModule.flushToDisk(); };
package/lib/cache.js CHANGED
@@ -94,6 +94,7 @@ var clusterStorage = require("./cluster-storage");
94
94
  var C = require("./constants");
95
95
  var lazyRequire = require("./lazy-require");
96
96
  var requestHelpers = require("./request-helpers");
97
+ var safeAsync = require("./safe-async");
97
98
  var validateOpts = require("./validate-opts");
98
99
  var { CacheError } = require("./framework-error");
99
100
 
@@ -434,12 +435,11 @@ function _memoryBackend(cfg) {
434
435
 
435
436
  function _startSweep(intervalMs) {
436
437
  if (sweepTimer) return;
437
- sweepTimer = setInterval(_sweep, intervalMs);
438
- if (typeof sweepTimer.unref === "function") sweepTimer.unref();
438
+ sweepTimer = safeAsync.repeating(_sweep, intervalMs, { name: "cache-sweep" });
439
439
  }
440
440
 
441
441
  async function close() {
442
- if (sweepTimer) { clearInterval(sweepTimer); sweepTimer = null; }
442
+ if (sweepTimer) { sweepTimer.stop(); sweepTimer = null; }
443
443
  entries.clear();
444
444
  tagIndex.clear();
445
445
  totalBytes = 0;
@@ -579,15 +579,11 @@ function _clusterBackend(cfg) {
579
579
  }
580
580
 
581
581
  function _startSweep(intervalMs) {
582
- var t = setInterval(function () {
583
- _sweep().catch(function () { /* sweeper best-effort; next pass picks it up */ });
584
- }, intervalMs);
585
- if (typeof t.unref === "function") t.unref();
586
- cfg._sweepTimer = t;
582
+ cfg._sweepTimer = safeAsync.repeating(_sweep, intervalMs, { name: "cache-sweep-cluster" });
587
583
  }
588
584
 
589
585
  async function close() {
590
- if (cfg._sweepTimer) { clearInterval(cfg._sweepTimer); cfg._sweepTimer = null; }
586
+ if (cfg._sweepTimer) { cfg._sweepTimer.stop(); cfg._sweepTimer = null; }
591
587
  }
592
588
 
593
589
  return {
package/lib/cli.js CHANGED
@@ -35,15 +35,16 @@
35
35
  */
36
36
 
37
37
  var fs = require("node:fs");
38
- var nodeCrypto = require("node:crypto");
39
38
  var os = require("node:os");
40
39
  var path = require("path");
41
40
  var apiSnapshot = require("./api-snapshot");
42
41
  var auditTools = require("./audit-tools");
43
42
  var cliHelpers = require("./cli-helpers");
44
43
  var constants = require("./constants");
44
+ var crypto = require("./crypto");
45
45
  var dev = require("./dev");
46
46
  var migrations = require("./migrations");
47
+ var requestHelpers = require("./request-helpers");
47
48
  var restoreBundle = require("./restore-bundle");
48
49
  var seeders = require("./seeders");
49
50
  var vaultPassphraseOps = require("./vault/passphrase-ops");
@@ -746,7 +747,7 @@ async function _runApiKey(args, ctx) {
746
747
  var scopes = args.flags.scopes;
747
748
  if (!ownerId || ownerId === true) return report.error("--owner-id <id> is required", 2);
748
749
  if (!scopes || scopes === true) return report.error("--scopes <comma-separated> is required", 2);
749
- var scopeList = String(scopes).split(",").map(function (s) { return s.trim(); }).filter(Boolean);
750
+ var scopeList = requestHelpers.parseListHeader(scopes);
750
751
  if (scopeList.length === 0) {
751
752
  return report.error("--scopes must contain at least one non-empty scope", 2);
752
753
  }
@@ -902,7 +903,7 @@ async function _runBackup(args, ctx) {
902
903
 
903
904
  if (sub === "verify") {
904
905
  var stagingDir = path.join(os.tmpdir(),
905
- "blamejs-backup-verify-" + nodeCrypto.randomBytes(8).toString("hex"));
906
+ "blamejs-backup-verify-" + crypto.generateToken(8));
906
907
  try {
907
908
  var r = await restoreBundle.extract({
908
909
  bundleDir: bundleDir,
package/lib/cluster.js CHANGED
@@ -51,10 +51,21 @@
51
51
  * await cluster.shutdown() releases lease, stops heartbeat
52
52
  */
53
53
  var C = require("./constants");
54
+ var clusterProviderDb = require("./cluster-provider-db");
55
+ var crypto = require("./crypto");
56
+ var lazyRequire = require("./lazy-require");
54
57
  var { boot } = require("./log");
58
+ var safeAsync = require("./safe-async");
59
+ var safeJson = require("./safe-json");
55
60
  var safeUrl = require("./safe-url");
56
61
  var { FrameworkError, ClusterError } = require("./framework-error");
57
62
 
63
+ // Lazy: vault → db → cluster forms a load-time chain, and external-db is
64
+ // loaded before its init has run; both are safe to call once cluster
65
+ // reaches runtime, but eager require here would deadlock the load order.
66
+ var externalDb = lazyRequire(function () { return require("./external-db"); });
67
+ var vault = lazyRequire(function () { return require("./vault"); });
68
+
58
69
  var DEFAULT_LEASE_TTL = C.TIME.seconds(30);
59
70
  var DEFAULT_HEARTBEAT = C.TIME.seconds(10);
60
71
  var MIN_LEASE_TTL = C.TIME.seconds(5);
@@ -184,8 +195,7 @@ async function init(opts) {
184
195
  throw _err("INVALID_CONFIG",
185
196
  "cluster.init requires either { provider } or { externalDbBackend }", true);
186
197
  }
187
- var dbProvider = require("./cluster-provider-db");
188
- provider = dbProvider.create({
198
+ provider = clusterProviderDb.create({
189
199
  externalDbBackend: opts.externalDbBackend,
190
200
  dialect: opts.dialect,
191
201
  });
@@ -227,8 +237,7 @@ async function init(opts) {
227
237
  }
228
238
 
229
239
  // Start heartbeat
230
- heartbeatTimer = setInterval(_heartbeat, heartbeatMs);
231
- heartbeatTimer.unref();
240
+ heartbeatTimer = safeAsync.repeating(_heartbeat, heartbeatMs, { name: "cluster-heartbeat" });
232
241
  }
233
242
 
234
243
  // Cluster-mode equivalent of db.js's single-node audit.tip-sidecar
@@ -264,15 +273,9 @@ async function init(opts) {
264
273
  // hash → FATAL via process.exit(1). Same posture as the
265
274
  // single-node audit.tip sidecar rollback check.
266
275
  async function _checkChainTipRollback(chainName, logTable, tipTable) {
267
- // Lazy require because external-db isn't available before
268
- // cluster.init (and cluster is required from many subsystems —
269
- // a top-of-file require would form a circular load with
270
- // external-db's audit emit path).
271
- var externalDb = require("./external-db");
272
-
273
276
  var tipRows;
274
277
  try {
275
- tipRows = await externalDb.query(
278
+ tipRows = await externalDb().query(
276
279
  "SELECT atMonotonicCounter, rowHash FROM " + tipTable +
277
280
  " WHERE scope = '" + chainName + "'",
278
281
  [],
@@ -294,7 +297,7 @@ async function _checkChainTipRollback(chainName, logTable, tipTable) {
294
297
  var tipCounter = Number(tip.atMonotonicCounter);
295
298
  var tipHash = tip.rowHash;
296
299
 
297
- var currentRows = await externalDb.query(
300
+ var currentRows = await externalDb().query(
298
301
  "SELECT MAX(monotonicCounter) AS m FROM " + logTable,
299
302
  [],
300
303
  { backend: configuredExternalDbBackend }
@@ -313,7 +316,7 @@ async function _checkChainTipRollback(chainName, logTable, tipTable) {
313
316
  }
314
317
 
315
318
  if (tipHash) {
316
- var hashRows = await externalDb.query(
319
+ var hashRows = await externalDb().query(
317
320
  "SELECT rowHash FROM " + logTable + " WHERE monotonicCounter = " +
318
321
  (configuredDialect === "postgres" ? "$1" : "?"),
319
322
  [tipCounter],
@@ -347,14 +350,9 @@ async function _checkChainTipRollback(chainName, logTable, tipTable) {
347
350
  // posture as the audit-tip rollback check skipping when there's no
348
351
  // audit-tip table.
349
352
  function _vaultKeyFingerprint() {
350
- // Lazy require to avoid the circular load risk: vault → db → cluster
351
- // (the audit module pulls cluster in, db loads audit at init).
352
- var vault = require("./vault");
353
- var crypto = require("./crypto");
354
- var safeJson = require("./safe-json");
355
353
  var keysJson;
356
354
  try {
357
- keysJson = vault.getKeysJson();
355
+ keysJson = vault().getKeysJson();
358
356
  } catch (e) {
359
357
  // vault.init() not called — gates-only mode. Skip silently.
360
358
  if (/vault.init\(\) must be awaited/.test((e && e.message) || "")) {
@@ -380,7 +378,6 @@ async function _checkVaultKeyConsistency() {
380
378
  log("vault not initialized — skipping vault-key consistency check (cluster gates-only mode)");
381
379
  return;
382
380
  }
383
- var externalDb = require("./external-db");
384
381
  var nowMs = Date.now();
385
382
  var ph = configuredDialect === "postgres";
386
383
 
@@ -390,7 +387,7 @@ async function _checkVaultKeyConsistency() {
390
387
  // compares — any mismatch (including ours after a losing race)
391
388
  // surfaces the drift.
392
389
  try {
393
- await externalDb.query(
390
+ await externalDb().query(
394
391
  "INSERT INTO _blamejs_cluster_state " +
395
392
  " (scope, vaultKeyFp, recordedAt, recordedByNode) " +
396
393
  "VALUES ('state', " +
@@ -413,7 +410,7 @@ async function _checkVaultKeyConsistency() {
413
410
 
414
411
  // Read whatever fingerprint is canonical (ours if first boot,
415
412
  // someone else's if we lost the race or are joining an existing cluster).
416
- var rows = await externalDb.query(
413
+ var rows = await externalDb().query(
417
414
  "SELECT vaultKeyFp, recordedByNode, recordedAt FROM _blamejs_cluster_state " +
418
415
  "WHERE scope = 'state'",
419
416
  [],
@@ -619,7 +616,7 @@ function onTransition(handler) {
619
616
  async function shutdown() {
620
617
  if (!initialized) return;
621
618
  if (heartbeatTimer) {
622
- clearInterval(heartbeatTimer);
619
+ heartbeatTimer.stop();
623
620
  heartbeatTimer = null;
624
621
  }
625
622
  if (lease) {
@@ -649,7 +646,7 @@ async function shutdown() {
649
646
  // ---- test helpers — not part of public contract ----
650
647
 
651
648
  function _resetForTest() {
652
- if (heartbeatTimer) clearInterval(heartbeatTimer);
649
+ if (heartbeatTimer) heartbeatTimer.stop();
653
650
  heartbeatTimer = null;
654
651
  initialized = false;
655
652
  terminated = false;