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.
- package/LICENSE +21 -0
- package/README.md +325 -0
- package/dist/app.d.ts +36 -0
- package/dist/app.js +186 -0
- package/dist/app.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +23 -0
- package/dist/config.js.map +1 -0
- package/dist/csv.d.ts +15 -0
- package/dist/csv.js +80 -0
- package/dist/csv.js.map +1 -0
- package/dist/db/database.d.ts +225 -0
- package/dist/db/database.js +497 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/export.d.ts +12 -0
- package/dist/db/export.js +37 -0
- package/dist/db/export.js.map +1 -0
- package/dist/db/manager.d.ts +62 -0
- package/dist/db/manager.js +206 -0
- package/dist/db/manager.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +10 -0
- package/dist/logger.js +25 -0
- package/dist/logger.js.map +1 -0
- package/dist/public/css/app.css +1 -0
- package/dist/public/css/input.css +179 -0
- package/dist/public/js/api.js +47 -0
- package/dist/public/js/app.js +120 -0
- package/dist/public/js/browse.js +123 -0
- package/dist/public/js/databases.js +45 -0
- package/dist/public/js/designer.js +205 -0
- package/dist/public/js/forms.js +244 -0
- package/dist/public/js/query.js +232 -0
- package/dist/public/js/schema.js +314 -0
- package/dist/public/js/triggers.js +155 -0
- package/dist/public/js/views.js +176 -0
- package/dist/routes/api.d.ts +9 -0
- package/dist/routes/api.js +622 -0
- package/dist/routes/api.js.map +1 -0
- package/dist/routes/databases.d.ts +19 -0
- package/dist/routes/databases.js +99 -0
- package/dist/routes/databases.js.map +1 -0
- package/dist/routes/pages.d.ts +9 -0
- package/dist/routes/pages.js +253 -0
- package/dist/routes/pages.js.map +1 -0
- package/dist/sql/classifier.d.ts +19 -0
- package/dist/sql/classifier.js +37 -0
- package/dist/sql/classifier.js.map +1 -0
- package/dist/sql/generator.d.ts +99 -0
- package/dist/sql/generator.js +259 -0
- package/dist/sql/generator.js.map +1 -0
- package/dist/test/classifier.test.d.ts +1 -0
- package/dist/test/classifier.test.js +44 -0
- package/dist/test/classifier.test.js.map +1 -0
- package/dist/test/csv.test.d.ts +1 -0
- package/dist/test/csv.test.js +64 -0
- package/dist/test/csv.test.js.map +1 -0
- package/dist/test/database.test.d.ts +1 -0
- package/dist/test/database.test.js +224 -0
- package/dist/test/database.test.js.map +1 -0
- package/dist/test/generator.test.d.ts +1 -0
- package/dist/test/generator.test.js +164 -0
- package/dist/test/generator.test.js.map +1 -0
- package/dist/test/manager.test.d.ts +1 -0
- package/dist/test/manager.test.js +146 -0
- package/dist/test/manager.test.js.map +1 -0
- package/dist/util.d.ts +29 -0
- package/dist/util.js +127 -0
- package/dist/util.js.map +1 -0
- package/dist/views/layouts/main.hbs +49 -0
- package/dist/views/pages/databases.hbs +102 -0
- package/dist/views/pages/designer.hbs +81 -0
- package/dist/views/pages/error.hbs +12 -0
- package/dist/views/pages/form.hbs +62 -0
- package/dist/views/pages/home.hbs +123 -0
- package/dist/views/pages/query.hbs +94 -0
- package/dist/views/pages/schema.hbs +290 -0
- package/dist/views/pages/table.hbs +203 -0
- package/dist/views/pages/triggers.hbs +162 -0
- package/dist/views/pages/views.hbs +134 -0
- package/dist/views/partials/navbar.hbs +50 -0
- package/dist/views/partials/sidebar.hbs +128 -0
- 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
|
+

|
|
162
|
+
|
|
163
|
+
Table browser — sortable columns, sticky header, and per-row actions.
|
|
164
|
+
|
|
165
|
+

|
|
166
|
+
|
|
167
|
+
Query editor — line-numbered editor with a results table.
|
|
168
|
+
|
|
169
|
+

|
|
170
|
+
|
|
171
|
+
Table designer — visual columns with a live SQL preview.
|
|
172
|
+
|
|
173
|
+

|
|
174
|
+
|
|
175
|
+
Insert / edit form — column defaults are pre-filled.
|
|
176
|
+
|
|
177
|
+

|
|
178
|
+
|
|
179
|
+
Schema editor — rename the table, add / rename / drop columns safely.
|
|
180
|
+
|
|
181
|
+

|
|
182
|
+
|
|
183
|
+
Databases landing page (multi-db mode) — manage multiple SQLite files.
|
|
184
|
+
|
|
185
|
+

|
|
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
|
package/dist/app.js.map
ADDED
|
@@ -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
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
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|