dbopt-engine 0.2.0 → 0.3.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.
package/.env.example CHANGED
@@ -6,7 +6,7 @@ DATABASE_URL=postgres://postgres:changeme@localhost:5432/mydb
6
6
  # Multiple databases (uncomment; commas in passwords must be encoded as %2C):
7
7
  # DATABASE_URL=postgres://user:pass@host1:5432/appdb,mysql://user:pass@host2:3306/shopdb
8
8
 
9
- PORT=8000
9
+ PORT=1305
10
10
  SCAN_INTERVAL_MINUTES=15
11
11
  MIN_ROWS_FOR_SEQ_SCAN_FLAG=1000
12
12
  MIN_IMPROVEMENT_PCT_TO_RECOMMEND=30
package/README.md CHANGED
@@ -24,7 +24,7 @@ the variables below (copy `.env.example` from the package as a template):
24
24
  ```bash
25
25
  # one database — or several, comma-separated (see "Multiple databases")
26
26
  DATABASE_URL=mysql://user:password@host:3306/mydb
27
- PORT=8000
27
+ PORT=1305
28
28
  SCAN_INTERVAL_MINUTES=15
29
29
  MIN_ROWS_FOR_SEQ_SCAN_FLAG=1000
30
30
  MIN_IMPROVEMENT_PCT_TO_RECOMMEND=30
@@ -41,8 +41,8 @@ Then start it:
41
41
 
42
42
  ```bash
43
43
  dbopt
44
- # dashboard: http://localhost:8000/dashboard
45
- # topology: http://localhost:8000/topology
44
+ # dashboard: http://localhost:1305/dashboard
45
+ # topology: http://localhost:1305/topology
46
46
  ```
47
47
 
48
48
  Or per-project:
@@ -55,6 +55,34 @@ npx dbopt-engine
55
55
 
56
56
  If any variable is missing, startup fails with a list of what's not set.
57
57
 
58
+ ## Embed in your backend (share its port)
59
+
60
+ Instead of running dbopt as a separate process on its own port, you can mount
61
+ it inside an existing Express app — the same way `swagger-ui-express` serves
62
+ docs on your API's port:
63
+
64
+ ```js
65
+ const express = require("express");
66
+ const dbopt = require("dbopt-engine");
67
+
68
+ const app = express();
69
+ // ... your own routes ...
70
+
71
+ await dbopt.attach(app); // mounts under /dbopt
72
+ // or: await dbopt.attach(app, { prefix: "/optimizer" });
73
+
74
+ app.listen(4000);
75
+ // dashboard: http://localhost:4000/dbopt/dashboard
76
+ // topology: http://localhost:4000/dbopt/topology
77
+ ```
78
+
79
+ `attach()` mounts all routes and dashboards under the prefix, connects to the
80
+ configured targets, and starts the analysis scheduler. Configuration still
81
+ comes from environment variables / `.env` (`PORT` is ignored in this mode —
82
+ the host app owns the port). For finer control, `createApp()` returns the bare
83
+ Express app without starting the engine, and `startEngine()` starts the
84
+ scheduler on its own.
85
+
58
86
  ## Connection strings
59
87
 
60
88
  The scheme picks the engine; the port is optional (engine default is used):
@@ -91,7 +119,7 @@ override the ones that have one — flags win).
91
119
  | Flag | Env var | Suggested value | Meaning |
92
120
  |---|---|---|---|
93
121
  | `--database-url` | `DATABASE_URL` | — | one or more comma-separated connection strings |
94
- | `--port` | `PORT` | 8000 | HTTP port for dashboard/API |
122
+ | `--port` | `PORT` | 1305 | HTTP port for dashboard/API |
95
123
  | `--scan-interval` | `SCAN_INTERVAL_MINUTES` | 15 | minutes between analysis cycles |
96
124
  | `--auto-create` | `AUTO_CREATE_INDEXES` | true | create verified indexes automatically |
97
125
  | `--auto-drop` | `AUTO_DROP_UNUSED_INDEXES` | false | drop unused indexes automatically |
package/bin/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  /* Simple CLI: flags map onto env vars before config loads.
5
- * dbopt --database-url mysql://u:p@host:3306/db --port 8000
5
+ * dbopt --database-url mysql://u:p@host:3306/db --port 1305
6
6
  */
7
7
  const args = process.argv.slice(2);
8
8
  const FLAG_TO_ENV = {
@@ -26,7 +26,7 @@ Usage:
26
26
  Options:
27
27
  --database-url <urls> postgres:// | mysql:// | sqlserver:// connection string;
28
28
  comma-separate several to watch multiple databases
29
- --port <n> HTTP port for dashboard/API (default 8000)
29
+ --port <n> HTTP port for dashboard/API (default 1305)
30
30
  --scan-interval <min> analysis cycle interval in minutes (default 15)
31
31
  --auto-create <bool> create verified indexes automatically (default true)
32
32
  --auto-drop <bool> drop unused indexes automatically (default false)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbopt-engine",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Self-optimizing database engine: watches real query traffic, verifies missing indexes before creating them, drops unused ones, with a live dashboard and schema topology explorer. PostgreSQL, MySQL 8, SQL Server.",
5
5
  "license": "MIT",
6
6
  "author": "Evvo Technology <dev@evvotechnology.com>",
package/src/config.js CHANGED
@@ -46,7 +46,7 @@ const REQUIRED_VARS = [
46
46
  }
47
47
 
48
48
  const settings = {
49
- httpPort: parseInt(env("PORT", "8000"), 10),
49
+ httpPort: parseInt(env("PORT", "1305"), 10),
50
50
  scanIntervalMinutes: parseInt(env("SCAN_INTERVAL_MINUTES", "15"), 10),
51
51
  minRowsForSeqScanFlag: parseInt(env("MIN_ROWS_FOR_SEQ_SCAN_FLAG", "1000"), 10),
52
52
  minImprovementPct: parseFloat(env("MIN_IMPROVEMENT_PCT_TO_RECOMMEND", "30")),
package/src/index.js CHANGED
@@ -32,10 +32,30 @@ function createApp() {
32
32
  };
33
33
  app.get("/dashboard", page("dashboard.html"));
34
34
  app.get("/topology", page("topology.html"));
35
- app.get("/", (req, res) => res.redirect("/dashboard"));
35
+ // req.baseUrl carries the mount prefix when embedded in a host app.
36
+ app.get("/", (req, res) => res.redirect(`${req.baseUrl}/dashboard`));
36
37
  return app;
37
38
  }
38
39
 
40
+ /** Connect to the targets and begin the analysis loop (no HTTP server). */
41
+ async function startEngine() {
42
+ for (const t of targets) await t.adapter.ensurePrerequisites(t);
43
+ startScheduler();
44
+ runAnalysisCycle(); // immediate first cycle, like the Python edition
45
+ }
46
+
47
+ /** Embed the optimizer in a host Express app, sharing its port:
48
+ * const dbopt = require("dbopt-engine");
49
+ * await dbopt.attach(app); // routes under /dbopt
50
+ * await dbopt.attach(app, { prefix: "/opt" }); // or a custom prefix
51
+ * Dashboard: <prefix>/dashboard · Topology: <prefix>/topology */
52
+ async function attach(app, { prefix = "/dbopt" } = {}) {
53
+ const sub = createApp();
54
+ app.use(prefix, sub);
55
+ await startEngine();
56
+ return sub;
57
+ }
58
+
39
59
  async function start() {
40
60
  const app = createApp();
41
61
  app.listen(settings.httpPort, () => {
@@ -46,10 +66,8 @@ async function start() {
46
66
  console.log(` dashboard: http://localhost:${settings.httpPort}/dashboard`);
47
67
  console.log(` topology: http://localhost:${settings.httpPort}/topology`);
48
68
  });
49
- for (const t of targets) await t.adapter.ensurePrerequisites(t);
50
- startScheduler();
51
- runAnalysisCycle(); // immediate first cycle, like the Python edition
69
+ await startEngine();
52
70
  return app;
53
71
  }
54
72
 
55
- module.exports = { createApp, start };
73
+ module.exports = { createApp, attach, startEngine, start };
@@ -366,7 +366,13 @@ for (const el of document.querySelectorAll(".info[data-tip]")) {
366
366
  // Every API call is scoped to the database picked in the header selector
367
367
  // (?db=<name>); without it the server uses its first configured target.
368
368
  const DB = new URLSearchParams(location.search).get("db");
369
- const withDb = (url) => DB ? url + (url.includes("?") ? "&" : "?") + "db=" + encodeURIComponent(DB) : url;
369
+ // When the app is mounted inside a host server (e.g. at /dbopt), API calls
370
+ // must carry that prefix; derive it from this page's own path.
371
+ const BASE = location.pathname.replace(/\/(dashboard|topology)\/?$/, "");
372
+ const withDb = (url) => {
373
+ const u = BASE + url;
374
+ return DB ? u + (u.includes("?") ? "&" : "?") + "db=" + encodeURIComponent(DB) : u;
375
+ };
370
376
  async function getJSON(url) {
371
377
  const res = await fetch(withDb(url));
372
378
  if (!res.ok) throw new Error(url + " -> HTTP " + res.status);
@@ -375,8 +381,8 @@ async function getJSON(url) {
375
381
 
376
382
  async function initDbSelect() {
377
383
  try {
378
- const ts = await getJSON("/targets");
379
384
  $("#topo-link").href = withDb("/topology");
385
+ const ts = await getJSON("/targets");
380
386
  if (ts.length < 2) return;
381
387
  const sel = $("#db-select");
382
388
  const current = DB || ts[0].name;
@@ -363,7 +363,13 @@ const history = [];
363
363
  // Every API call is scoped to the database picked in the toolbar selector
364
364
  // (?db=<name>); without it the server uses its first configured target.
365
365
  const DB = new URLSearchParams(location.search).get("db");
366
- const withDb = (url) => DB ? url + (url.includes("?") ? "&" : "?") + "db=" + encodeURIComponent(DB) : url;
366
+ // When the app is mounted inside a host server (e.g. at /dbopt), API calls
367
+ // must carry that prefix; derive it from this page's own path.
368
+ const BASE = location.pathname.replace(/\/(dashboard|topology)\/?$/, "");
369
+ const withDb = (url) => {
370
+ const u = BASE + url;
371
+ return DB ? u + (u.includes("?") ? "&" : "?") + "db=" + encodeURIComponent(DB) : u;
372
+ };
367
373
  async function getJSON(url, opts) {
368
374
  const r = await fetch(withDb(url), opts);
369
375
  if (!r.ok) throw new Error((await r.json().catch(() => ({}))).detail || r.status);
@@ -372,8 +378,8 @@ async function getJSON(url, opts) {
372
378
 
373
379
  async function initDbSelect() {
374
380
  try {
375
- const ts = await getJSON("/targets");
376
381
  $("#dash-link").href = withDb("/dashboard");
382
+ const ts = await getJSON("/targets");
377
383
  if (ts.length < 2) return;
378
384
  const sel = $("#db-select");
379
385
  const current = DB || ts[0].name;