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 +1 -1
- package/README.md +32 -4
- package/bin/cli.js +2 -2
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/index.js +23 -5
- package/src/static/dashboard.html +8 -2
- package/src/static/topology.html +8 -2
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=
|
|
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=
|
|
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:
|
|
45
|
-
# topology: http://localhost:
|
|
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` |
|
|
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
|
|
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
|
|
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.
|
|
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", "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/src/static/topology.html
CHANGED
|
@@ -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
|
-
|
|
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;
|