@merittdev/horus 0.1.8 → 0.1.10

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 (2) hide show
  1. package/dist/index.cjs +68 -15
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -50314,7 +50314,7 @@ init_cjs_shims();
50314
50314
 
50315
50315
  // ../../packages/core/src/version.ts
50316
50316
  init_cjs_shims();
50317
- var HORUS_VERSION = true ? "0.1.8" : "dev";
50317
+ var HORUS_VERSION = true ? "0.1.10" : "dev";
50318
50318
  var PINNED_AXON_VERSION = "1.0.7";
50319
50319
  var PINNED_SOURCE_VERSION = PINNED_AXON_VERSION;
50320
50320
 
@@ -57084,12 +57084,16 @@ var BullMQRedisClient = class {
57084
57084
  );
57085
57085
  }
57086
57086
  /**
57087
- * Discover queue names by scanning for BullMQ wait-list keys.
57087
+ * Discover queue names by scanning for BullMQ `:meta` keys.
57088
+ *
57089
+ * `:meta` is written when a Queue is instantiated and persists regardless of job
57090
+ * state, so it finds idle queues too. Scanning `:wait` (the previous approach) only
57091
+ * surfaced queues with pending jobs, so idle-but-real queues were invisible.
57088
57092
  * Returns at most `limit` queue names.
57089
57093
  */
57090
57094
  async discoverQueues(limit = 50) {
57091
- const pattern = `${this.prefix}:*:wait`;
57092
- const suffixLen = ":wait".length;
57095
+ const pattern = `${this.prefix}:*:meta`;
57096
+ const suffixLen = ":meta".length;
57093
57097
  const prefixLen = this.prefix.length + 1;
57094
57098
  const names = [];
57095
57099
  let cursor = "0";
@@ -57272,6 +57276,10 @@ var BullMQRuntimeProvider = class {
57272
57276
  const queues = await Promise.all(names.map((name) => this.inspectQueue(name)));
57273
57277
  return { prefix: this.client.prefix, collectedAt, queues };
57274
57278
  }
57279
+ /** Discover queue names present in Redis under the configured prefix. */
57280
+ discoverQueues() {
57281
+ return this.client.discoverQueues();
57282
+ }
57275
57283
  async inspectQueue(name) {
57276
57284
  const [waiting, active, failed, delayed, completed, paused] = await Promise.all([
57277
57285
  this.client.listLen(this.client.queueKey(name, "wait")),
@@ -66795,9 +66803,37 @@ async function checkEnv(renv, deps) {
66795
66803
  const redisCfg = renv.connectors.redis;
66796
66804
  if (redisCfg?.url) {
66797
66805
  const safeUrl = redactRedisUrl(redisCfg.url);
66798
- console.log(
66799
- ` ${mark("pending")} ${import_picocolors.default.bold("Redis")} ${import_picocolors.default.dim(`configured \xB7 ${safeUrl}`)}`
66800
- );
66806
+ const queueProvider = (deps?.queueFactory ?? queueForEnv)(renv);
66807
+ if (queueProvider) {
66808
+ try {
66809
+ const h = await queueProvider.health();
66810
+ if (h.ok) {
66811
+ let queueInfo = "";
66812
+ try {
66813
+ const discovered = await queueProvider.discoverQueues();
66814
+ queueInfo = ` \xB7 ${discovered.length} queue(s)`;
66815
+ } catch {
66816
+ }
66817
+ console.log(
66818
+ ` ${mark(true)} ${import_picocolors.default.bold("Redis")} ${import_picocolors.default.dim(`reachable \xB7 ${safeUrl}${queueInfo}`)}`
66819
+ );
66820
+ } else {
66821
+ const authFailed = /WRONGPASS|NOAUTH|invalid password/i.test(h.detail);
66822
+ const state = authFailed ? "auth failed" : "unreachable";
66823
+ console.log(
66824
+ ` ${mark(false)} ${import_picocolors.default.bold("Redis")} ${import_picocolors.default.dim(`${state} \xB7 ${safeUrl} \xB7 ${h.detail}`)}`
66825
+ );
66826
+ allOk = false;
66827
+ }
66828
+ } finally {
66829
+ await queueProvider.close().catch(() => {
66830
+ });
66831
+ }
66832
+ } else {
66833
+ console.log(
66834
+ ` ${mark("pending")} ${import_picocolors.default.bold("Redis")} ${import_picocolors.default.dim(`configured \xB7 ${safeUrl}`)}`
66835
+ );
66836
+ }
66801
66837
  } else {
66802
66838
  console.log(
66803
66839
  ` ${mark("pending")} ${import_picocolors.default.bold("Redis")} ${import_picocolors.default.dim("not configured")}`
@@ -66869,7 +66905,7 @@ Horus ${HORUS_VERSION}`));
66869
66905
  console.error(import_picocolors.default.red(err.message));
66870
66906
  return 1;
66871
66907
  }
66872
- const ok = await checkEnv(renv, { mongoFactory: opts?._mongoFactory });
66908
+ const ok = await checkEnv(renv, { mongoFactory: opts?._mongoFactory, queueFactory: opts?._queueFactory });
66873
66909
  return ok ? 0 : 1;
66874
66910
  }
66875
66911
  let allHealthy = true;
@@ -66882,7 +66918,7 @@ Horus ${HORUS_VERSION}`));
66882
66918
  allHealthy = false;
66883
66919
  continue;
66884
66920
  }
66885
- const ok = await checkEnv(renv, { mongoFactory: opts?._mongoFactory });
66921
+ const ok = await checkEnv(renv, { mongoFactory: opts?._mongoFactory, queueFactory: opts?._queueFactory });
66886
66922
  if (!ok) allHealthy = false;
66887
66923
  }
66888
66924
  return checks.some((c) => c.ok === false && c.fatal) ? 1 : 0;
@@ -67311,7 +67347,7 @@ async function runIndex(opts) {
67311
67347
  ` investigate: horus investigate --name ${name} "<hint>" (or from this repo: horus investigate "<hint>")`
67312
67348
  )
67313
67349
  );
67314
- } else if (!configuredHost) {
67350
+ } else if (hostUrl !== configuredHost) {
67315
67351
  const existingPath = discoverLocalConfig(root);
67316
67352
  if (existingPath) {
67317
67353
  const file = readLocalConfig(existingPath);
@@ -67348,7 +67384,14 @@ async function runQueues(name, opts) {
67348
67384
  const config = await loadConfig(opts.config, { name: opts.name });
67349
67385
  const { db, sql: sql2 } = createDb(config.database.url);
67350
67386
  try {
67351
- const rows = await listQueueEdges(db, { project: opts.project, queueName: name });
67387
+ let project = opts.project;
67388
+ if (project === void 0) {
67389
+ try {
67390
+ project = resolveEnvironment(config, { project: opts.project }).project;
67391
+ } catch {
67392
+ }
67393
+ }
67394
+ const rows = await listQueueEdges(db, { project, queueName: name });
67352
67395
  console.log(
67353
67396
  import_picocolors4.default.bold("Queue topology") + import_picocolors4.default.dim(" \xB7 source: code / source intelligence \xB7 static (run horus index to refresh)")
67354
67397
  );
@@ -67455,8 +67498,15 @@ async function runLiveMode(config, rows, nameFilter) {
67455
67498
  console.log(import_picocolors4.default.red(` \u2717 Redis unreachable: ${health.detail}`));
67456
67499
  return;
67457
67500
  }
67458
- const topologyNames = [...buildQueueMap(rows).keys()];
67459
- const queueNames = nameFilter !== void 0 ? [nameFilter] : topologyNames.length > 0 ? topologyNames : void 0;
67501
+ const staticNames = new Set(buildQueueMap(rows).keys());
67502
+ let queueNames;
67503
+ if (nameFilter !== void 0) {
67504
+ queueNames = [nameFilter];
67505
+ } else {
67506
+ const discovered = await queueProvider.discoverQueues().catch(() => []);
67507
+ const union2 = /* @__PURE__ */ new Set([...staticNames, ...discovered]);
67508
+ queueNames = union2.size > 0 ? [...union2] : void 0;
67509
+ }
67460
67510
  const state = await queueProvider.analyzeQueues({ queueNames });
67461
67511
  const collectedAt = new Date(state.collectedAt).toLocaleTimeString();
67462
67512
  console.log(
@@ -67473,7 +67523,7 @@ async function runLiveMode(config, rows, nameFilter) {
67473
67523
  );
67474
67524
  return;
67475
67525
  }
67476
- printLiveTable(state.queues);
67526
+ printLiveTable(state.queues, staticNames);
67477
67527
  } catch (err) {
67478
67528
  if (!headerPrinted) {
67479
67529
  console.log(import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(" \xB7 source: Redis/BullMQ"));
@@ -67484,7 +67534,7 @@ async function runLiveMode(config, rows, nameFilter) {
67484
67534
  });
67485
67535
  }
67486
67536
  }
67487
- function printLiveTable(queues) {
67537
+ function printLiveTable(queues, staticNames = /* @__PURE__ */ new Set()) {
67488
67538
  const nameWidth = Math.max(10, ...queues.map((q) => q.queueName.length));
67489
67539
  const numWidth = 7;
67490
67540
  const header = " " + "queue".padEnd(nameWidth) + " " + "waiting".padStart(numWidth) + " " + "active".padStart(numWidth) + " " + "failed".padStart(numWidth) + " " + "delayed".padStart(numWidth) + " " + "paused".padStart(numWidth);
@@ -67495,6 +67545,9 @@ function printLiveTable(queues) {
67495
67545
  const color = hasIssue ? import_picocolors4.default.yellow : (s) => s;
67496
67546
  const row = " " + q.queueName.padEnd(nameWidth) + " " + String(q.waiting).padStart(numWidth) + " " + String(q.active).padStart(numWidth) + " " + String(q.failed).padStart(numWidth) + " " + String(q.delayed).padStart(numWidth) + " " + (q.isPaused ? import_picocolors4.default.yellow("paused") : String(q.paused).padStart(numWidth));
67497
67547
  console.log(color(row));
67548
+ if (!staticNames.has(q.queueName)) {
67549
+ console.log(import_picocolors4.default.dim(" runtime-only \xB7 no static producer/worker mapping"));
67550
+ }
67498
67551
  if (q.oldestWaitingMs !== void 0) {
67499
67552
  const age = formatAge(q.oldestWaitingMs);
67500
67553
  console.log(import_picocolors4.default.dim(` oldest waiting: ${age}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {