@karmaniverous/jeeves-runner 0.6.0 → 0.7.0

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.
@@ -3,6 +3,7 @@ import { mkdirSync, mkdtempSync, writeFileSync, unlinkSync, existsSync, readFile
3
3
  import { dirname, join, extname, resolve } from 'node:path';
4
4
  import { Command } from 'commander';
5
5
  import { DatabaseSync } from 'node:sqlite';
6
+ import { fileURLToPath } from 'node:url';
6
7
  import { pino } from 'pino';
7
8
  import Fastify from 'fastify';
8
9
  import { createConfigQueryHandler } from '@karmaniverous/jeeves';
@@ -608,13 +609,31 @@ function registerStateRoutes(app, deps) {
608
609
  * Register all API routes on the Fastify instance.
609
610
  */
610
611
  function registerRoutes(app, deps) {
611
- const { db, scheduler, getConfig } = deps;
612
- /** GET /healthHealth check. */
613
- app.get('/health', () => {
612
+ const { db, scheduler, getConfig, version } = deps;
613
+ /** GET /statusUnified status endpoint (single health/metadata entrypoint). */
614
+ app.get('/status', () => {
615
+ const totalJobs = db
616
+ .prepare('SELECT COUNT(*) as count FROM jobs')
617
+ .get();
618
+ const runningCount = scheduler.getRunningJobs().length;
619
+ const failedCount = scheduler.getFailedRegistrations().length;
620
+ const okLastHour = db
621
+ .prepare(`SELECT COUNT(*) as count FROM runs
622
+ WHERE status = 'ok' AND started_at > datetime('now', '-1 hour')`)
623
+ .get();
624
+ const errorsLastHour = db
625
+ .prepare(`SELECT COUNT(*) as count FROM runs
626
+ WHERE status IN ('error', 'timeout') AND started_at > datetime('now', '-1 hour')`)
627
+ .get();
614
628
  return {
615
- ok: true,
629
+ status: 'ok',
630
+ version,
616
631
  uptime: process.uptime(),
617
- failedRegistrations: scheduler.getFailedRegistrations().length,
632
+ totalJobs: totalJobs.count,
633
+ running: runningCount,
634
+ failedRegistrations: failedCount,
635
+ okLastHour: okLastHour.count,
636
+ errorsLastHour: errorsLastHour.count,
618
637
  };
619
638
  });
620
639
  /** GET /jobs — List all jobs with last run status. */
@@ -657,29 +676,6 @@ function registerRoutes(app, deps) {
657
676
  return { error: err instanceof Error ? err.message : 'Unknown error' };
658
677
  }
659
678
  });
660
- /** GET /stats — Aggregate job statistics. */
661
- app.get('/stats', () => {
662
- const totalJobs = db
663
- .prepare('SELECT COUNT(*) as count FROM jobs')
664
- .get();
665
- const runningCount = scheduler.getRunningJobs().length;
666
- const failedCount = scheduler.getFailedRegistrations().length;
667
- const okLastHour = db
668
- .prepare(`SELECT COUNT(*) as count FROM runs
669
- WHERE status = 'ok' AND started_at > datetime('now', '-1 hour')`)
670
- .get();
671
- const errorsLastHour = db
672
- .prepare(`SELECT COUNT(*) as count FROM runs
673
- WHERE status IN ('error', 'timeout') AND started_at > datetime('now', '-1 hour')`)
674
- .get();
675
- return {
676
- totalJobs: totalJobs.count,
677
- running: runningCount,
678
- failedRegistrations: failedCount,
679
- okLastHour: okLastHour.count,
680
- errorsLastHour: errorsLastHour.count,
681
- };
682
- });
683
679
  /** GET /config — Query effective configuration via JSONPath. */
684
680
  const configHandler = createConfigQueryHandler(getConfig);
685
681
  app.get('/config', async (request, reply) => {
@@ -723,6 +719,7 @@ function createServer(deps) {
723
719
  db: deps.db,
724
720
  scheduler: deps.scheduler,
725
721
  getConfig: deps.getConfig,
722
+ version: deps.version,
726
723
  });
727
724
  return app;
728
725
  }
@@ -1602,11 +1599,23 @@ function createRunner(config, deps) {
1602
1599
  });
1603
1600
  scheduler.start();
1604
1601
  logger.info('Scheduler started');
1602
+ // Read package version
1603
+ const pkgVersion = (() => {
1604
+ try {
1605
+ const dir = dirname(fileURLToPath(import.meta.url));
1606
+ const pkg = JSON.parse(readFileSync(resolve(dir, '..', 'package.json'), 'utf8'));
1607
+ return pkg.version ?? 'unknown';
1608
+ }
1609
+ catch {
1610
+ return 'unknown';
1611
+ }
1612
+ })();
1605
1613
  // API server
1606
1614
  server = createServer({
1607
1615
  db,
1608
1616
  scheduler,
1609
1617
  getConfig: () => config,
1618
+ version: pkgVersion,
1610
1619
  loggerConfig: { level: config.log.level, file: config.log.file },
1611
1620
  });
1612
1621
  await server.listen({ port: config.port, host: config.host });
@@ -1962,7 +1971,7 @@ program
1962
1971
  const config = loadConfig(options.config);
1963
1972
  void (async () => {
1964
1973
  try {
1965
- const resp = await fetch(`http://127.0.0.1:${String(config.port)}/stats`);
1974
+ const resp = await fetch(`http://127.0.0.1:${String(config.port)}/status`);
1966
1975
  const stats = (await resp.json());
1967
1976
  console.log(JSON.stringify(stats, null, 2));
1968
1977
  }
package/dist/index.d.ts CHANGED
@@ -102,8 +102,8 @@ type Queue = z.infer<typeof queueSchema>;
102
102
 
103
103
  /** Run status enumeration schema (pending, running, ok, error, timeout, skipped). */
104
104
  declare const runStatusSchema: z.ZodEnum<{
105
- error: "error";
106
105
  pending: "pending";
106
+ error: "error";
107
107
  running: "running";
108
108
  ok: "ok";
109
109
  timeout: "timeout";
@@ -120,8 +120,8 @@ declare const runSchema: z.ZodObject<{
120
120
  id: z.ZodNumber;
121
121
  jobId: z.ZodString;
122
122
  status: z.ZodEnum<{
123
- error: "error";
124
123
  pending: "pending";
124
+ error: "error";
125
125
  running: "running";
126
126
  ok: "ok";
127
127
  timeout: "timeout";
package/dist/mjs/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { z } from 'zod';
2
2
  import { mkdirSync, mkdtempSync, writeFileSync, unlinkSync, existsSync, readFileSync } from 'node:fs';
3
+ import { dirname, join, extname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
3
5
  import { pino } from 'pino';
4
6
  import Fastify from 'fastify';
5
7
  import { createConfigQueryHandler } from '@karmaniverous/jeeves';
6
8
  import { RRStack } from '@karmaniverous/rrstack';
7
9
  import { Cron } from 'croner';
8
10
  import { JSONPath } from 'jsonpath-plus';
9
- import { dirname, join, extname } from 'node:path';
10
11
  import { DatabaseSync } from 'node:sqlite';
11
12
  import { request as request$1 } from 'node:http';
12
13
  import { request } from 'node:https';
@@ -562,13 +563,31 @@ function registerStateRoutes(app, deps) {
562
563
  * Register all API routes on the Fastify instance.
563
564
  */
564
565
  function registerRoutes(app, deps) {
565
- const { db, scheduler, getConfig } = deps;
566
- /** GET /healthHealth check. */
567
- app.get('/health', () => {
566
+ const { db, scheduler, getConfig, version } = deps;
567
+ /** GET /statusUnified status endpoint (single health/metadata entrypoint). */
568
+ app.get('/status', () => {
569
+ const totalJobs = db
570
+ .prepare('SELECT COUNT(*) as count FROM jobs')
571
+ .get();
572
+ const runningCount = scheduler.getRunningJobs().length;
573
+ const failedCount = scheduler.getFailedRegistrations().length;
574
+ const okLastHour = db
575
+ .prepare(`SELECT COUNT(*) as count FROM runs
576
+ WHERE status = 'ok' AND started_at > datetime('now', '-1 hour')`)
577
+ .get();
578
+ const errorsLastHour = db
579
+ .prepare(`SELECT COUNT(*) as count FROM runs
580
+ WHERE status IN ('error', 'timeout') AND started_at > datetime('now', '-1 hour')`)
581
+ .get();
568
582
  return {
569
- ok: true,
583
+ status: 'ok',
584
+ version,
570
585
  uptime: process.uptime(),
571
- failedRegistrations: scheduler.getFailedRegistrations().length,
586
+ totalJobs: totalJobs.count,
587
+ running: runningCount,
588
+ failedRegistrations: failedCount,
589
+ okLastHour: okLastHour.count,
590
+ errorsLastHour: errorsLastHour.count,
572
591
  };
573
592
  });
574
593
  /** GET /jobs — List all jobs with last run status. */
@@ -611,29 +630,6 @@ function registerRoutes(app, deps) {
611
630
  return { error: err instanceof Error ? err.message : 'Unknown error' };
612
631
  }
613
632
  });
614
- /** GET /stats — Aggregate job statistics. */
615
- app.get('/stats', () => {
616
- const totalJobs = db
617
- .prepare('SELECT COUNT(*) as count FROM jobs')
618
- .get();
619
- const runningCount = scheduler.getRunningJobs().length;
620
- const failedCount = scheduler.getFailedRegistrations().length;
621
- const okLastHour = db
622
- .prepare(`SELECT COUNT(*) as count FROM runs
623
- WHERE status = 'ok' AND started_at > datetime('now', '-1 hour')`)
624
- .get();
625
- const errorsLastHour = db
626
- .prepare(`SELECT COUNT(*) as count FROM runs
627
- WHERE status IN ('error', 'timeout') AND started_at > datetime('now', '-1 hour')`)
628
- .get();
629
- return {
630
- totalJobs: totalJobs.count,
631
- running: runningCount,
632
- failedRegistrations: failedCount,
633
- okLastHour: okLastHour.count,
634
- errorsLastHour: errorsLastHour.count,
635
- };
636
- });
637
633
  /** GET /config — Query effective configuration via JSONPath. */
638
634
  const configHandler = createConfigQueryHandler(getConfig);
639
635
  app.get('/config', async (request, reply) => {
@@ -677,6 +673,7 @@ function createServer(deps) {
677
673
  db: deps.db,
678
674
  scheduler: deps.scheduler,
679
675
  getConfig: deps.getConfig,
676
+ version: deps.version,
680
677
  });
681
678
  return app;
682
679
  }
@@ -1762,11 +1759,23 @@ function createRunner(config, deps) {
1762
1759
  });
1763
1760
  scheduler.start();
1764
1761
  logger.info('Scheduler started');
1762
+ // Read package version
1763
+ const pkgVersion = (() => {
1764
+ try {
1765
+ const dir = dirname(fileURLToPath(import.meta.url));
1766
+ const pkg = JSON.parse(readFileSync(resolve(dir, '..', 'package.json'), 'utf8'));
1767
+ return pkg.version ?? 'unknown';
1768
+ }
1769
+ catch {
1770
+ return 'unknown';
1771
+ }
1772
+ })();
1765
1773
  // API server
1766
1774
  server = createServer({
1767
1775
  db,
1768
1776
  scheduler,
1769
1777
  getConfig: () => config,
1778
+ version: pkgVersion,
1770
1779
  loggerConfig: { level: config.log.level, file: config.log.file },
1771
1780
  });
1772
1781
  await server.listen({ port: config.port, host: config.host });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-runner",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "Graph-aware job execution engine with SQLite state. Part of the Jeeves platform.",
6
6
  "license": "BSD-3-Clause",
@@ -46,7 +46,7 @@
46
46
  "node": ">=20"
47
47
  },
48
48
  "dependencies": {
49
- "@karmaniverous/jeeves": "^0.2.0",
49
+ "@karmaniverous/jeeves": "^0.3.0",
50
50
  "@karmaniverous/rrstack": "^0.17.0",
51
51
  "commander": "^14.0.3",
52
52
  "croner": "^10.0.1",