admindb 1.0.1

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +325 -0
  3. package/dist/app.d.ts +36 -0
  4. package/dist/app.js +186 -0
  5. package/dist/app.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +61 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/config.d.ts +17 -0
  10. package/dist/config.js +23 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/csv.d.ts +15 -0
  13. package/dist/csv.js +80 -0
  14. package/dist/csv.js.map +1 -0
  15. package/dist/db/database.d.ts +225 -0
  16. package/dist/db/database.js +497 -0
  17. package/dist/db/database.js.map +1 -0
  18. package/dist/db/export.d.ts +12 -0
  19. package/dist/db/export.js +37 -0
  20. package/dist/db/export.js.map +1 -0
  21. package/dist/db/manager.d.ts +62 -0
  22. package/dist/db/manager.js +206 -0
  23. package/dist/db/manager.js.map +1 -0
  24. package/dist/index.d.ts +16 -0
  25. package/dist/index.js +24 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/logger.d.ts +10 -0
  28. package/dist/logger.js +25 -0
  29. package/dist/logger.js.map +1 -0
  30. package/dist/public/css/app.css +1 -0
  31. package/dist/public/css/input.css +179 -0
  32. package/dist/public/js/api.js +47 -0
  33. package/dist/public/js/app.js +120 -0
  34. package/dist/public/js/browse.js +123 -0
  35. package/dist/public/js/databases.js +45 -0
  36. package/dist/public/js/designer.js +205 -0
  37. package/dist/public/js/forms.js +244 -0
  38. package/dist/public/js/query.js +232 -0
  39. package/dist/public/js/schema.js +314 -0
  40. package/dist/public/js/triggers.js +155 -0
  41. package/dist/public/js/views.js +176 -0
  42. package/dist/routes/api.d.ts +9 -0
  43. package/dist/routes/api.js +622 -0
  44. package/dist/routes/api.js.map +1 -0
  45. package/dist/routes/databases.d.ts +19 -0
  46. package/dist/routes/databases.js +99 -0
  47. package/dist/routes/databases.js.map +1 -0
  48. package/dist/routes/pages.d.ts +9 -0
  49. package/dist/routes/pages.js +253 -0
  50. package/dist/routes/pages.js.map +1 -0
  51. package/dist/sql/classifier.d.ts +19 -0
  52. package/dist/sql/classifier.js +37 -0
  53. package/dist/sql/classifier.js.map +1 -0
  54. package/dist/sql/generator.d.ts +99 -0
  55. package/dist/sql/generator.js +259 -0
  56. package/dist/sql/generator.js.map +1 -0
  57. package/dist/test/classifier.test.d.ts +1 -0
  58. package/dist/test/classifier.test.js +44 -0
  59. package/dist/test/classifier.test.js.map +1 -0
  60. package/dist/test/csv.test.d.ts +1 -0
  61. package/dist/test/csv.test.js +64 -0
  62. package/dist/test/csv.test.js.map +1 -0
  63. package/dist/test/database.test.d.ts +1 -0
  64. package/dist/test/database.test.js +224 -0
  65. package/dist/test/database.test.js.map +1 -0
  66. package/dist/test/generator.test.d.ts +1 -0
  67. package/dist/test/generator.test.js +164 -0
  68. package/dist/test/generator.test.js.map +1 -0
  69. package/dist/test/manager.test.d.ts +1 -0
  70. package/dist/test/manager.test.js +146 -0
  71. package/dist/test/manager.test.js.map +1 -0
  72. package/dist/util.d.ts +29 -0
  73. package/dist/util.js +127 -0
  74. package/dist/util.js.map +1 -0
  75. package/dist/views/layouts/main.hbs +49 -0
  76. package/dist/views/pages/databases.hbs +102 -0
  77. package/dist/views/pages/designer.hbs +81 -0
  78. package/dist/views/pages/error.hbs +12 -0
  79. package/dist/views/pages/form.hbs +62 -0
  80. package/dist/views/pages/home.hbs +123 -0
  81. package/dist/views/pages/query.hbs +94 -0
  82. package/dist/views/pages/schema.hbs +290 -0
  83. package/dist/views/pages/table.hbs +203 -0
  84. package/dist/views/pages/triggers.hbs +162 -0
  85. package/dist/views/pages/views.hbs +134 -0
  86. package/dist/views/partials/navbar.hbs +50 -0
  87. package/dist/views/partials/sidebar.hbs +128 -0
  88. package/package.json +62 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Muhammad Bin Khalid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,325 @@
1
+ # AdminDB
2
+
3
+ A browser-based SQLite database administration tool. Manage a SQLite database
4
+ entirely from the browser: browse tables, full CRUD, a visual table designer, an
5
+ arbitrary SQL query runner, saved named queries, SQL-dump export, a
6
+ "generate SQL" preview mode that never executes, a schema editor, and
7
+ multi-database support.
8
+
9
+ - **Backend:** Node.js + TypeScript (compiled to plain JS) on Express.
10
+ - **Frontend:** Handlebars server-rendered templates, Tailwind CSS + DaisyUI,
11
+ plain JavaScript (no frontend framework).
12
+ - **Database:** SQLite via the standard Node driver (`node:sqlite`). Requires
13
+ **Node.js ≥ 22.5**.
14
+
15
+ > ## ⚠️ Security warning — read first
16
+ >
17
+ > **This tool exposes full, unauthenticated database access.** Every page and
18
+ > API route (browse, edit, delete, run arbitrary SQL, change the schema, export
19
+ > the whole database) is available to **anyone who can reach the server**.
20
+ >
21
+ > - **No authentication or authorization is built in.** The routes are **not
22
+ > protected**.
23
+ > - **It is your responsibility to protect access.** Do **not** expose
24
+ > AdminDB to the public internet or to untrusted networks.
25
+ > - Recommended ways to protect it:
26
+ > - bind the standalone server to `127.0.0.1` (`HOST=127.0.0.1`) and use it
27
+ > only from your own machine, and/or
28
+ > - run it behind a reverse proxy that requires authentication (Basic auth,
29
+ > OAuth, mTLS, …) or inside a VPN / private network.
30
+ >
31
+ > Treat AdminDB as if it were a remote `sqlite3` shell with write access.
32
+
33
+ ---
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ npm install admindb
39
+ ```
40
+
41
+ Requires **Node.js ≥ 22.5** (for the built-in `node:sqlite` driver).
42
+
43
+ ## Using as an npm package
44
+
45
+ AdminDB is an Express app you can mount inside your own application, under
46
+ your own path, on the same port as the rest of your server.
47
+
48
+ ### Minimal example
49
+
50
+ ```ts
51
+ import express from 'express';
52
+ import { createRouter } from 'admindb';
53
+
54
+ const app = express();
55
+
56
+ app.get('/', (_req, res) => res.send('My main app'));
57
+
58
+ // All AdminDB routes live under /admin on the same port.
59
+ app.use('/admin', createRouter({ dbPath: '/data/my.db', basePath: '/admin' }));
60
+
61
+ app.listen(3000);
62
+ ```
63
+
64
+ `createRouter(options)` returns a fully wired Express app (pages + JSON API +
65
+ static assets + view engine). Mounting it is just `app.use('/path', router)`.
66
+
67
+ ### Options
68
+
69
+ | Option | Type | Description |
70
+ | ---------- | -------------------- | -------------------------------------------------------------- |
71
+ | `dbPath` | `string` | Path to a single SQLite file (single-db mode). Default: `admindb.db` |
72
+ | `db` | `SqliteDatabase` | An already-open database instance (advanced embedding) |
73
+ | `manager` | `DbManager` | Enables multi-database mode (see below) |
74
+ | `basePath` | `string` | URL prefix used by templates/assets (e.g. `/admin`). Pass the same prefix you mount at |
75
+ | `logger` | `Logger` | Custom logger (see `createLogger`) |
76
+ | `logLevel` | `'debug'\|'info'\|'warn'\|'error'` | Log verbosity (used when no logger is passed) |
77
+ | `readonly` | `boolean` | Open the database(s) **read-only**: every write is rejected (403), write controls are disabled in the UI, and a banner is shown. The DB file is opened with `SQLITE_OPEN_READONLY` + `PRAGMA query_only` as a belt-and-suspenders guard. Default: `false` |
78
+
79
+ Example with a custom logger and prefix:
80
+
81
+ ```ts
82
+ import express from 'express';
83
+ import { createRouter, createLogger } from 'admindb';
84
+
85
+ const app = express();
86
+ app.use('/tools/db', createRouter({
87
+ dbPath: './data/app.db',
88
+ basePath: '/tools/db',
89
+ logLevel: 'info',
90
+ }));
91
+ app.listen(3000);
92
+ ```
93
+
94
+ ### Multiple databases
95
+
96
+ Pass a `DbManager` to manage several database files — either from a directory,
97
+ from an explicit list of file paths, or both:
98
+
99
+ ```ts
100
+ import express from 'express';
101
+ import { createRouter, DbManager, createLogger } from 'admindb';
102
+
103
+ const app = express();
104
+ app.use('/admin', createRouter({
105
+ manager: new DbManager(
106
+ {
107
+ dir: './data', // scan a directory
108
+ files: ['/srv/legacy/app.db', './shared.sqlite'], // and/or explicit paths
109
+ readonly: true, // open all databases read-only
110
+ },
111
+ createLogger('info'),
112
+ ),
113
+ basePath: '/admin',
114
+ }));
115
+ ```
116
+
117
+ In multi-db mode:
118
+
119
+ - a **"Databases" landing page** lets you open, create, and delete database files,
120
+ - every database is scoped under its file name, e.g.
121
+ `/admin/app.db/tables/users` and `/admin/app.db/api/tables`,
122
+ - file names that collide across sources are deduped (`name__2.db`).
123
+
124
+ ## Features
125
+
126
+ - Browse every table and its rows (paginated, with primary-key awareness).
127
+ - **Filter rows by column** — exact (`=value`), comparison (`>5`, `<=10`), prefix
128
+ (`pre*`) or substring (plain text) matching; filters survive sorting and
129
+ pagination.
130
+ - Insert, edit, and delete rows (full CRUD). FK columns become dropdowns.
131
+ - **Export** table rows or query results as **CSV or JSON**, and **import a CSV
132
+ file** (or pasted CSV) into a table.
133
+ - Create tables visually: name, type, primary key, not-null / unique,
134
+ default value, and foreign-key references.
135
+ - Write and run arbitrary SQL — SELECTs render as a table, `COUNT` queries show
136
+ a readable summary, write statements execute and report affected rows.
137
+ - Save named queries and reload them from a dropdown.
138
+ - Export the whole database as a downloadable SQL dump (`CREATE` + `INSERT`).
139
+ - **Get query / preview mode:** generate `CREATE` / `INSERT` / `UPDATE` SQL from
140
+ the UI without executing it.
141
+ - **Edit schema:** rename the table, add / rename / drop columns (with
142
+ relationship-safety checks), and drop tables.
143
+ - **Indexes:** create indexes (plain or unique, on one or many columns — pick
144
+ columns in order, with a live `CREATE INDEX` SQL preview) and drop them from
145
+ the schema editor; automatic SQLite indexes are protected.
146
+ - **Views:** create / drop `CREATE VIEW` definitions, preview the rows they
147
+ return, and inspect their SQL — from a dedicated Views page.
148
+ - **Triggers:** create / drop triggers with a structured form (timing, single
149
+ event, optional `WHEN`, body with a live `CREATE TRIGGER` SQL preview) and
150
+ inspect existing trigger SQL — from a dedicated Triggers page.
151
+ - **Read-only mode:** open the database(s) without write access — the file is
152
+ opened `SQLITE_OPEN_READONLY` + `query_only`, every write route returns `403`,
153
+ and the UI hides/disables all write controls and shows a banner.
154
+ - **Multiple databases:** directory scanning and/or explicit file lists, each
155
+ with its own workspace under `/{db}/…`.
156
+
157
+ ## Screenshots
158
+
159
+ Home dashboard — stat cards and the table list.
160
+
161
+ ![Home dashboard](docs/screenshots/home.png)
162
+
163
+ Table browser — sortable columns, sticky header, and per-row actions.
164
+
165
+ ![Table browser](docs/screenshots/table.png)
166
+
167
+ Query editor — line-numbered editor with a results table.
168
+
169
+ ![Query editor](docs/screenshots/query.png)
170
+
171
+ Table designer — visual columns with a live SQL preview.
172
+
173
+ ![Table designer](docs/screenshots/designer.png)
174
+
175
+ Insert / edit form — column defaults are pre-filled.
176
+
177
+ ![Insert form](docs/screenshots/form.png)
178
+
179
+ Schema editor — rename the table, add / rename / drop columns safely.
180
+
181
+ ![Schema editor](docs/screenshots/schema.png)
182
+
183
+ Databases landing page (multi-db mode) — manage multiple SQLite files.
184
+
185
+ ![Databases](docs/screenshots/databases.png)
186
+
187
+ ## Run the built-in server
188
+
189
+ For convenience a standalone server is included. From a clone of the repo:
190
+
191
+ ```bash
192
+ npm install
193
+ npm run build
194
+ npm start # open http://localhost:3000
195
+ ```
196
+
197
+ Configuration via environment variables:
198
+
199
+ | Variable | Default | Description |
200
+ | ----------- | ------------------ | ------------------------------------ |
201
+ | `PORT` | `3000` | Port to listen on |
202
+ | `HOST` | `0.0.0.0` | Host / interface to bind |
203
+ | `DB_PATH` | `admindb.db` | Single SQLite database file |
204
+ | `DB_DIR` | — | Multi-db source 1: a directory of `.db`/`.sqlite` files |
205
+ | `DB_FILES` | — | Multi-db source 2: comma-separated explicit database file paths |
206
+ | `READONLY` | — | `1` / `true` / `yes` / `on` opens the database(s) read-only |
207
+ | `BASE_PATH` | `''` | URL prefix (e.g. `/admin`) |
208
+ | `LOG_LEVEL` | `info` | `debug` \| `info` \| `warn` \| `error` |
209
+
210
+ Multi-db mode activates when `DB_DIR` and/or `DB_FILES` is set:
211
+
212
+ ```powershell
213
+ $env:DB_DIR='./db'; npm start # PowerShell
214
+ # bash/zsh: DB_DIR=./db npm start
215
+ ```
216
+
217
+ ## The JSON REST API (under `basePath`)
218
+
219
+ | Method | Path | Purpose |
220
+ | ------ | ---------------------------------------- | -------------------------------- |
221
+ | GET | `/api/tables` | List tables |
222
+ | GET | `/api/tables/:table/info` | Column + FK metadata, PK columns |
223
+ | GET | `/api/tables/:table/fk-options` | Values for FK dropdowns |
224
+ | GET | `/api/tables/:table/rows?page&limit&f` | Paginated rows (`f` = URL-encoded JSON filters, e.g. `{"age":">35"}`) |
225
+ | GET | `/api/tables/:table/row/:id` | Single row by (encoded) PK |
226
+ | POST | `/api/tables/:table/rows` | Insert row |
227
+ | POST | `/api/tables/:table/rows/generate` | Generate INSERT SQL (no execute) |
228
+ | POST | `/api/tables/:table/rows/import` | Import CSV (`{ csv }`, header row must match columns) |
229
+ | GET | `/api/tables/:table/export?format=` | Download all rows as `csv` or `json` |
230
+ | PUT | `/api/tables/:table/row/:id` | Update row |
231
+ | PUT | `/api/tables/:table/row/:id/generate` | Generate UPDATE SQL (no execute) |
232
+ | DELETE | `/api/tables/:table/row/:id` | Delete row |
233
+ | POST | `/api/tables` | Create table |
234
+ | POST | `/api/tables/generate` | Generate CREATE SQL (no execute) |
235
+ | GET | `/api/tables/:table/schema` | Full schema (constraints, indexes, FK refs) |
236
+ | POST | `/api/tables/:table/rename` | Rename the table |
237
+ | POST | `/api/tables/:table/columns` | Add a column |
238
+ | PUT | `/api/tables/:table/columns/:column` | Rename a column |
239
+ | DELETE | `/api/tables/:table/columns/:column` | Drop a column (safety-checked) |
240
+ | DELETE | `/api/tables/:table` | Drop the table (safety-checked) |
241
+ | POST | `/api/tables/:table/indexes` | Create an index (`{ name?, columns[], unique? }`) |
242
+ | DELETE | `/api/tables/:table/indexes/:index` | Drop an index (auto indexes refused) |
243
+ | GET | `/api/views` | List views |
244
+ | GET | `/api/views/:name/rows` | Preview a view's rows |
245
+ | POST | `/api/views/generate` | Generate CREATE VIEW SQL (no execute) |
246
+ | POST | `/api/views` | Create a view (`{ name, sql }`) |
247
+ | DELETE | `/api/views/:name` | Drop a view |
248
+ | GET | `/api/triggers` | List triggers |
249
+ | POST | `/api/triggers/generate` | Generate CREATE TRIGGER SQL (no execute) |
250
+ | POST | `/api/triggers` | Create a trigger (`{ name, table, timing, event, when?, body }`) |
251
+ | DELETE | `/api/triggers/:name` | Drop a trigger |
252
+ | POST | `/api/query` | Run arbitrary SQL |
253
+ | POST | `/api/query/export` | Run a SELECT and download as `csv`/`json` |
254
+ | GET | `/api/queries` | List saved queries |
255
+ | POST | `/api/queries` | Save a named query |
256
+ | DELETE | `/api/queries/:id` | Delete a saved query |
257
+
258
+ In multi-db mode, every route is scoped under the database, e.g.
259
+ `/api/app.db/tables`. Every API response uses the consistent shape
260
+ `{ success, data?, error? }`.
261
+
262
+ ## Behaviour notes
263
+
264
+ - **Empty input = "not set".** Empty form fields are omitted so DB defaults
265
+ apply; `0` is a valid value and is never treated as empty.
266
+ - **Insert forms pre-fill defaults.** On the "new row" form, columns that have
267
+ a schema default are pre-filled (string/number/boolean literals and
268
+ `CURRENT_TIMESTAMP`-style defaults) so you can see and adjust them.
269
+ - **Row filters.** Each column's filter supports exact match (`=value`),
270
+ comparison (`>5`, `>=5`, `<5`, `<=5`, `!=value`), prefix (`pre*`) and
271
+ case-insensitive substring (plain text). Filters are carried in the URL and
272
+ survive sorting and pagination.
273
+ - **CSV import.** The first row must be a header whose names match existing
274
+ columns (unknown or duplicate names are rejected). Empty cells are treated as
275
+ "not set" so database defaults apply. The whole import runs in a single
276
+ transaction — a failed row rolls everything back.
277
+ - **Indexes.** Creating an index takes one or more columns and an optional
278
+ unique flag (the index name is optional too). Only explicitly created indexes
279
+ (`origin = 'c'`) can be dropped from the UI — automatic primary-key/unique
280
+ indexes are protected.
281
+ - **Schema editor.** Drops are refused when the column is a primary key, has a
282
+ UNIQUE constraint, is used by an index, is part of a foreign key, or is
283
+ referenced by another table's foreign key. Tables referenced by other tables
284
+ cannot be dropped. Internal (`_`-prefixed) tables cannot be renamed or dropped.
285
+ - **Identifiers are quoted and string values escaped** everywhere SQL is built,
286
+ so generated SQL is correct and safe.
287
+ - **`COUNT` queries** return a readable summary message instead of a table.
288
+ - **"Get query" never executes** — it only returns the generated SQL string.
289
+ - **Internal table** `_saved_queries` stores saved queries and is kept out of
290
+ user-facing FK pickers. Schema initialization is idempotent and safe to run
291
+ repeatedly.
292
+ - Composite primary keys are supported (values are URL-encoded and comma-joined
293
+ in the row endpoints).
294
+
295
+ ## Development
296
+
297
+ ```bash
298
+ npm install
299
+ npm run build # TypeScript → dist + Tailwind CSS
300
+ npm run dev # tsx watch server + Tailwind watch
301
+ npm test # builds and runs the unit tests
302
+ ```
303
+
304
+ `npm test` compiles TypeScript to `dist/` and runs the `node:test` suite
305
+ covering the SQL generator (INSERT / UPDATE / CREATE output, type mapping,
306
+ quoting, rejection of unsupported types), the SQL classifier, and the database
307
+ manager.
308
+
309
+ ## Publishing to npm
310
+
311
+ The package ships the compiled `dist/` (with type declarations), this `README`,
312
+ and the `LICENSE`. When you are ready to publish:
313
+
314
+ ```bash
315
+ npm run build # happens automatically via the prepack script
316
+ npm login
317
+ npm publish
318
+ ```
319
+
320
+ Update `name`/`version` in `package.json` to match your intended package name
321
+ and add an `author`/`repository` if desired.
322
+
323
+ ## License
324
+
325
+ [MIT](./LICENSE) — see the [LICENSE](LICENSE) file.
package/dist/app.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import express from 'express';
2
+ import { SqliteDatabase } from './db/database';
3
+ import { DbManager } from './db/manager';
4
+ import { type Logger, type LogLevel } from './logger';
5
+ export interface AppOptions {
6
+ /** Path to the SQLite file (single-db mode). Defaults to `admindb.db`. */
7
+ dbPath?: string;
8
+ /** URL prefix used by templates/static assets (e.g. `/admin`). Defaults to `''`. */
9
+ basePath?: string;
10
+ logger?: Logger;
11
+ logLevel?: LogLevel;
12
+ /** Provide an existing database instance (single-db embedding). */
13
+ db?: SqliteDatabase;
14
+ /** Enable multi-database mode with a manager over a directory of `.db` files. */
15
+ manager?: DbManager;
16
+ /** Open the database read-only — all write routes are rejected with 403. */
17
+ readonly?: boolean;
18
+ /** Internal: URL of the databases list (used by per-db apps to link "switch database"). */
19
+ databasesUrl?: string;
20
+ /** Internal: current database id (for locals/display). */
21
+ dbId?: string;
22
+ }
23
+ /**
24
+ * Build a fully configured, mountable Express app (pages + JSON API + static
25
+ * assets + view engine).
26
+ *
27
+ * - Single database: pass `dbPath` (or `db`). Mount under your own prefix with
28
+ * `basePath`. This is also the mode used per-database in multi-db mode.
29
+ * - Multiple databases: pass a `manager`. The app then shows a "Databases"
30
+ * landing page and scopes every database under `/{dbFile}/…` (e.g.
31
+ * `/{base}/app.db/tables/users`, `/{base}/app.db/api/tables`).
32
+ *
33
+ * An Express app is valid middleware, so `app.use('/admin', createRouter({...}))`
34
+ * works and shares the same port.
35
+ */
36
+ export declare function createRouter(options?: AppOptions): express.Express;
package/dist/app.js ADDED
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createRouter = createRouter;
7
+ const express_1 = __importDefault(require("express"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const express_handlebars_1 = require("express-handlebars");
10
+ const database_1 = require("./db/database");
11
+ const logger_1 = require("./logger");
12
+ const pages_1 = require("./routes/pages");
13
+ const api_1 = require("./routes/api");
14
+ const databases_1 = require("./routes/databases");
15
+ const util_1 = require("./util");
16
+ /**
17
+ * Build a fully configured, mountable Express app (pages + JSON API + static
18
+ * assets + view engine).
19
+ *
20
+ * - Single database: pass `dbPath` (or `db`). Mount under your own prefix with
21
+ * `basePath`. This is also the mode used per-database in multi-db mode.
22
+ * - Multiple databases: pass a `manager`. The app then shows a "Databases"
23
+ * landing page and scopes every database under `/{dbFile}/…` (e.g.
24
+ * `/{base}/app.db/tables/users`, `/{base}/app.db/api/tables`).
25
+ *
26
+ * An Express app is valid middleware, so `app.use('/admin', createRouter({...}))`
27
+ * works and shares the same port.
28
+ */
29
+ function createRouter(options = {}) {
30
+ return options.manager ? createManagerApp(options) : createSingleDbApp(options);
31
+ }
32
+ function setupViewEngine(app, logger) {
33
+ app.engine('hbs', (0, express_handlebars_1.engine)({
34
+ extname: '.hbs',
35
+ defaultLayout: 'main',
36
+ layoutsDir: node_path_1.default.join(__dirname, 'views', 'layouts'),
37
+ partialsDir: node_path_1.default.join(__dirname, 'views', 'partials'),
38
+ helpers: {
39
+ eq: (a, b) => a === b,
40
+ startsWith: (a, b) => String(a ?? '').startsWith(String(b ?? '')),
41
+ isNull: (v) => v === null || v === undefined,
42
+ truncate: (v, n) => {
43
+ const s = v == null ? '' : String(v);
44
+ return s.length > n ? `${s.slice(0, n)}…` : s;
45
+ },
46
+ json: (v) => JSON.stringify(v).replace(/</g, '\\u003c'),
47
+ add: (a, b) => Number(a) + Number(b),
48
+ sub: (a, b) => Number(a) - Number(b),
49
+ gt: (a, b) => Number(a) > Number(b),
50
+ lt: (a, b) => Number(a) < Number(b),
51
+ join: (arr, sep) => (Array.isArray(arr) ? arr.join(String(sep ?? ',')) : String(arr ?? '')),
52
+ },
53
+ }));
54
+ app.set('view engine', 'hbs');
55
+ app.set('views', node_path_1.default.join(__dirname, 'views'));
56
+ }
57
+ function addErrorHandlers(app, logger) {
58
+ // 404 handler.
59
+ app.use((req, res) => {
60
+ if (req.path.startsWith('/api/')) {
61
+ return res.status(404).json({ success: false, error: 'Not found.' });
62
+ }
63
+ res.status(404).render('pages/error', { title: 'Not found', status: 404, error: 'The page you requested does not exist.' });
64
+ });
65
+ // Central error handler.
66
+ app.use((err, req, res, _next) => {
67
+ logger.error('Unhandled error', err);
68
+ if (req.path.startsWith('/api/')) {
69
+ return res.status(500).json({ success: false, error: (0, util_1.errorMessage)(err) });
70
+ }
71
+ res.status(500).render('pages/error', { title: 'Error', status: 500, error: (0, util_1.errorMessage)(err) });
72
+ });
73
+ }
74
+ /** Single-database app (also used as the per-database app in multi-db mode). */
75
+ function createSingleDbApp(options) {
76
+ const basePath = (options.basePath ?? '').replace(/\/+$/, '');
77
+ const logger = options.logger ?? (0, logger_1.createLogger)(options.logLevel ?? 'info', 'admindb');
78
+ const db = options.db ?? new database_1.SqliteDatabase(options.dbPath ?? 'admindb.db', logger, { readonly: options.readonly });
79
+ const databasesUrl = options.databasesUrl;
80
+ const router = (0, express_1.default)();
81
+ router.disable('x-powered-by');
82
+ // Static assets (relative to the mount point, so a prefix "just works").
83
+ router.use(express_1.default.static(node_path_1.default.join(__dirname, 'public')));
84
+ setupViewEngine(router, logger);
85
+ router.use(express_1.default.json({ limit: '10mb' }));
86
+ // Page locals (skipped for API + static paths).
87
+ router.use(async (req, res, next) => {
88
+ try {
89
+ res.locals.basePath = basePath;
90
+ res.locals.currentPath = req.path;
91
+ res.locals.currentTable = null;
92
+ res.locals.dbPath = db.path;
93
+ res.locals.dbId = options.dbId ?? null;
94
+ res.locals.databasesUrl = databasesUrl ?? null;
95
+ res.locals.databasesMode = false;
96
+ res.locals.readonly = db.isReadOnly;
97
+ if (!req.path.startsWith('/api/')) {
98
+ const all = await db.listTables();
99
+ const names = (all.data ?? []).map((t) => t.name);
100
+ res.locals.tables = names.filter((n) => !n.startsWith('_'));
101
+ res.locals.internalTables = names.filter((n) => n.startsWith('_'));
102
+ }
103
+ next();
104
+ }
105
+ catch (err) {
106
+ next(err);
107
+ }
108
+ });
109
+ (0, pages_1.registerPages)(router, { db, logger });
110
+ (0, api_1.registerApi)(router, { db, logger });
111
+ addErrorHandlers(router, logger);
112
+ return router;
113
+ }
114
+ /** Multi-database app: databases landing page + a per-database sub-app per file. */
115
+ function createManagerApp(options) {
116
+ const manager = options.manager;
117
+ const basePath = (options.basePath ?? '').replace(/\/+$/, '');
118
+ const logger = options.logger ?? (0, logger_1.createLogger)(options.logLevel ?? 'info', 'admindb');
119
+ const readonly = !!options.readonly;
120
+ const app = (0, express_1.default)();
121
+ app.disable('x-powered-by');
122
+ app.use(express_1.default.static(node_path_1.default.join(__dirname, 'public')));
123
+ setupViewEngine(app, logger);
124
+ app.use(express_1.default.json({ limit: '10mb' }));
125
+ // Locals for the databases landing page.
126
+ app.use(async (req, res, next) => {
127
+ try {
128
+ res.locals.basePath = basePath;
129
+ res.locals.currentPath = req.path;
130
+ res.locals.currentTable = null;
131
+ res.locals.dbPath = manager.directory;
132
+ res.locals.dbId = null;
133
+ res.locals.databasesUrl = null;
134
+ res.locals.databasesMode = true;
135
+ res.locals.readonly = readonly;
136
+ if (!req.path.startsWith('/api/')) {
137
+ res.locals.tables = [];
138
+ res.locals.internalTables = [];
139
+ }
140
+ next();
141
+ }
142
+ catch (err) {
143
+ next(err);
144
+ }
145
+ });
146
+ // Per-database dispatch: mount a cached single-db app at /:dbId.
147
+ const subApps = new Map();
148
+ (0, databases_1.registerDatabasesRoutes)(app, {
149
+ manager,
150
+ logger,
151
+ basePath,
152
+ readonly,
153
+ invalidate: (id) => subApps.delete(id),
154
+ });
155
+ app.use('/:dbId', (req, res, next) => {
156
+ const dbId = String(req.params.dbId);
157
+ if (!manager.has(dbId)) {
158
+ if (req.path.startsWith('/api/')) {
159
+ return res.status(404).json({ success: false, error: `Database "${dbId}" does not exist.` });
160
+ }
161
+ return res.status(404).render('pages/error', {
162
+ title: 'Not found',
163
+ status: 404,
164
+ error: `Database "${dbId}" does not exist.`,
165
+ });
166
+ }
167
+ let sub = subApps.get(dbId);
168
+ if (!sub) {
169
+ const subBase = `${basePath ? basePath : ''}/${encodeURIComponent(dbId)}`;
170
+ sub = createSingleDbApp({
171
+ db: manager.open(dbId),
172
+ basePath: subBase,
173
+ logger,
174
+ logLevel: options.logLevel,
175
+ dbId,
176
+ databasesUrl: `${basePath ? basePath : ''}/`,
177
+ readonly,
178
+ });
179
+ subApps.set(dbId, sub);
180
+ }
181
+ return sub(req, res, next);
182
+ });
183
+ addErrorHandlers(app, logger);
184
+ return app;
185
+ }
186
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const express_1 = __importDefault(require("express"));
8
+ const app_1 = require("./app");
9
+ const config_1 = require("./config");
10
+ const logger_1 = require("./logger");
11
+ const manager_1 = require("./db/manager");
12
+ /**
13
+ * Standalone CLI / server entry point. Run `npm start` (or `npx admindb`).
14
+ * Configure via PORT, HOST, DB_PATH, DB_DIR, DB_FILES, BASE_PATH and LOG_LEVEL
15
+ * env vars. Set DB_DIR and/or DB_FILES to enable multi-database mode.
16
+ *
17
+ * SECURITY: this server exposes full, unauthenticated database access. It must
18
+ * NOT be exposed to the public internet — protect it yourself (bind to
19
+ * localhost, put it behind an authenticated reverse proxy, a VPN, etc.).
20
+ */
21
+ const config = (0, config_1.loadConfig)();
22
+ const logger = (0, logger_1.createLogger)(config.logLevel, 'admindb');
23
+ const app = (0, express_1.default)();
24
+ app.disable('x-powered-by');
25
+ const multiDb = Boolean(config.dbDir || (config.dbFiles && config.dbFiles.length > 0));
26
+ const router = multiDb
27
+ ? (0, app_1.createRouter)({
28
+ manager: new manager_1.DbManager({ dir: config.dbDir, files: config.dbFiles, readonly: config.readonly }, logger),
29
+ basePath: config.basePath,
30
+ logger,
31
+ readonly: config.readonly,
32
+ })
33
+ : (0, app_1.createRouter)({ dbPath: config.dbPath, basePath: config.basePath, logger, readonly: config.readonly });
34
+ app.use(config.basePath || '/', router);
35
+ const server = app.listen(config.port, config.host, () => {
36
+ const url = `http://${config.host}:${config.port}${config.basePath}/`;
37
+ logger.info(`AdminDB listening on ${url}`);
38
+ if (multiDb) {
39
+ const sources = [config.dbDir ? `dir:${config.dbDir}` : null, config.dbFiles?.length ? `files:[${config.dbFiles.join(', ')}]` : null]
40
+ .filter(Boolean)
41
+ .join(' · ');
42
+ logger.info(`Multi-database mode — ${sources}`);
43
+ }
44
+ else {
45
+ logger.info(`Database file: ${config.dbPath}`);
46
+ }
47
+ if (config.readonly) {
48
+ logger.info('Read-only mode enabled — all writes are disabled.');
49
+ }
50
+ if (config.host === '0.0.0.0') {
51
+ logger.warn('Listening on 0.0.0.0 — this admin tool has NO authentication. Do not expose it to untrusted networks.');
52
+ }
53
+ });
54
+ const shutdown = (signal) => {
55
+ logger.info(`Received ${signal}, shutting down…`);
56
+ server.close(() => process.exit(0));
57
+ setTimeout(() => process.exit(0), 2000).unref();
58
+ };
59
+ process.on('SIGINT', () => shutdown('SIGINT'));
60
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
61
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}