admindb 1.0.1 → 1.1.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.
Files changed (48) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +354 -325
  3. package/dist/db/database.d.ts +61 -39
  4. package/dist/db/database.js +251 -51
  5. package/dist/db/database.js.map +1 -1
  6. package/dist/db/manager.d.ts +2 -2
  7. package/dist/db/manager.js +3 -2
  8. package/dist/db/manager.js.map +1 -1
  9. package/dist/public/css/app.css +1 -1
  10. package/dist/public/css/input.css +232 -179
  11. package/dist/public/js/api.js +47 -47
  12. package/dist/public/js/app.js +120 -120
  13. package/dist/public/js/browse.js +877 -123
  14. package/dist/public/js/databases.js +45 -45
  15. package/dist/public/js/designer.js +205 -205
  16. package/dist/public/js/forms.js +244 -244
  17. package/dist/public/js/query.js +232 -232
  18. package/dist/public/js/schema.js +314 -314
  19. package/dist/routes/api.js +261 -97
  20. package/dist/routes/api.js.map +1 -1
  21. package/dist/routes/pages.js +50 -30
  22. package/dist/routes/pages.js.map +1 -1
  23. package/dist/sql/generator.d.ts +0 -22
  24. package/dist/sql/generator.js +0 -48
  25. package/dist/sql/generator.js.map +1 -1
  26. package/dist/test/database.test.js +241 -63
  27. package/dist/test/database.test.js.map +1 -1
  28. package/dist/test/generator.test.js +0 -32
  29. package/dist/test/generator.test.js.map +1 -1
  30. package/dist/util.d.ts +13 -1
  31. package/dist/util.js +24 -2
  32. package/dist/util.js.map +1 -1
  33. package/dist/views/layouts/main.hbs +49 -49
  34. package/dist/views/pages/databases.hbs +102 -102
  35. package/dist/views/pages/designer.hbs +81 -81
  36. package/dist/views/pages/error.hbs +12 -12
  37. package/dist/views/pages/form.hbs +62 -62
  38. package/dist/views/pages/home.hbs +123 -123
  39. package/dist/views/pages/query.hbs +94 -94
  40. package/dist/views/pages/schema.hbs +290 -290
  41. package/dist/views/pages/table.hbs +256 -203
  42. package/dist/views/partials/navbar.hbs +50 -50
  43. package/dist/views/partials/sidebar.hbs +116 -128
  44. package/package.json +66 -62
  45. package/dist/public/js/triggers.js +0 -155
  46. package/dist/public/js/views.js +0 -176
  47. package/dist/views/pages/triggers.hbs +0 -162
  48. package/dist/views/pages/views.hbs +0 -134
package/README.md CHANGED
@@ -1,325 +1,354 @@
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.
1
+ # AdminDB
2
+
3
+
4
+ [![Version](https://img.shields.io/npm/v/admindb.svg)](https://www.npmjs.com/package/admindb)
5
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen.svg)](https://nodejs.org/)
7
+ [![Publish](https://github.com/MIbnEKhalid/admindb/actions/workflows/publish.yml/badge.svg?branch=main)](https://github.com/MIbnEKhalid/admindb/actions/workflows/publish.yml)
8
+ [![Downloads](https://img.shields.io/npm/dm/admindb.svg)](https://www.npmjs.com/package/admindb)
9
+
10
+
11
+ A browser-based SQLite database administration tool. Manage a SQLite database
12
+ entirely from the browser: browse tables with full CRUD, inline
13
+ spreadsheet-style grid editing, type-aware filters, bulk row operations, a
14
+ visual table designer, an arbitrary SQL query runner, saved named queries,
15
+ SQL-dump export, a "generate SQL" preview mode that never executes, a schema
16
+ editor, and multi-database support.
17
+
18
+ - **Backend:** Node.js + TypeScript (compiled to plain JS) on Express.
19
+ - **Frontend:** Handlebars server-rendered templates, Tailwind CSS + DaisyUI,
20
+ plain JavaScript (no frontend framework).
21
+ - **Database:** SQLite via [better-sqlite3](https://github.com/WiseLibs/better-sqlite3). Requires
22
+ **Node.js ≥ 20**.
23
+
24
+ > ## ⚠️ Security warning — read first
25
+ >
26
+ > **This tool exposes full, unauthenticated database access.** Every page and
27
+ > API route (browse, edit, delete, run arbitrary SQL, change the schema, export
28
+ > the whole database) is available to **anyone who can reach the server**.
29
+ >
30
+ > - **No authentication or authorization is built in.** The routes are **not
31
+ > protected**.
32
+ > - **It is your responsibility to protect access.** Do **not** expose
33
+ > AdminDB to the public internet or to untrusted networks.
34
+ > - Recommended ways to protect it:
35
+ > - bind the standalone server to `127.0.0.1` (`HOST=127.0.0.1`) and use it
36
+ > only from your own machine, and/or
37
+ > - run it behind a reverse proxy that requires authentication (Basic auth,
38
+ > OAuth, mTLS, …) or inside a VPN / private network.
39
+ >
40
+ > Treat AdminDB as if it were a remote `sqlite3` shell with write access.
41
+
42
+ ---
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ npm install admindb
48
+ ```
49
+
50
+ Requires **Node.js ≥ 20**.
51
+
52
+ ## Using as an npm package
53
+
54
+ AdminDB is an Express app you can mount inside your own application, under
55
+ your own path, on the same port as the rest of your server.
56
+
57
+ ### Minimal example
58
+
59
+ ```ts
60
+ import express from 'express';
61
+ import { createRouter } from 'admindb';
62
+
63
+ const app = express();
64
+
65
+ app.get('/', (_req, res) => res.send('My main app'));
66
+
67
+ // All AdminDB routes live under /admin on the same port.
68
+ app.use('/admin', createRouter({ dbPath: '/data/my.db', basePath: '/admin' }));
69
+
70
+ app.listen(3000);
71
+ ```
72
+
73
+ `createRouter(options)` returns a fully wired Express app (pages + JSON API +
74
+ static assets + view engine). Mounting it is just `app.use('/path', router)`.
75
+
76
+ ### Options
77
+
78
+ | Option | Type | Description |
79
+ | ---------- | -------------------- | -------------------------------------------------------------- |
80
+ | `dbPath` | `string` | Path to a single SQLite file (single-db mode). Default: `admindb.db` |
81
+ | `db` | `SqliteDatabase` | An already-open database instance (advanced embedding) |
82
+ | `manager` | `DbManager` | Enables multi-database mode (see below) |
83
+ | `basePath` | `string` | URL prefix used by templates/assets (e.g. `/admin`). Pass the same prefix you mount at |
84
+ | `logger` | `Logger` | Custom logger (see `createLogger`) |
85
+ | `logLevel` | `'debug'\|'info'\|'warn'\|'error'` | Log verbosity (used when no logger is passed) |
86
+ | `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` |
87
+
88
+ Example with a custom logger and prefix:
89
+
90
+ ```ts
91
+ import express from 'express';
92
+ import { createRouter, createLogger } from 'admindb';
93
+
94
+ const app = express();
95
+ app.use('/tools/db', createRouter({
96
+ dbPath: './data/app.db',
97
+ basePath: '/tools/db',
98
+ logLevel: 'info',
99
+ }));
100
+ app.listen(3000);
101
+ ```
102
+
103
+ ### Multiple databases
104
+
105
+ Pass a `DbManager` to manage several database files — either from a directory,
106
+ from an explicit list of file paths, or both:
107
+
108
+ ```ts
109
+ import express from 'express';
110
+ import { createRouter, DbManager, createLogger } from 'admindb';
111
+
112
+ const app = express();
113
+ app.use('/admin', createRouter({
114
+ manager: new DbManager(
115
+ {
116
+ dir: './data', // scan a directory
117
+ files: ['/srv/legacy/app.db', './shared.sqlite'], // and/or explicit paths
118
+ readonly: true, // open all databases read-only
119
+ },
120
+ createLogger('info'),
121
+ ),
122
+ basePath: '/admin',
123
+ }));
124
+ ```
125
+
126
+ In multi-db mode:
127
+
128
+ - a **"Databases" landing page** lets you open, create, and delete database files,
129
+ - every database is scoped under its file name, e.g.
130
+ `/admin/app.db/tables/users` and `/admin/app.db/api/tables`,
131
+ - file names that collide across sources are deduped (`name__2.db`).
132
+
133
+ ## Features
134
+
135
+ - Browse every table and its rows (paginated, with primary-key awareness).
136
+ - **Filter rows by column** — type-aware, per-column filter controls: foreign-key
137
+ dropdowns, boolean toggles, date and numeric range inputs, plus the exact
138
+ (`=value`), comparison (`>5`, `<=10`), prefix (`pre*`) and substring (plain
139
+ text) operators on text columns. Filters survive sorting and pagination.
140
+ - **Inline (spreadsheet-style) editing** — double-click any cell to edit it in
141
+ place with a type-aware control (FK dropdown, boolean toggle, date picker,
142
+ number/text input). Changes are staged and highlighted in the grid, then
143
+ applied all at once in a single transaction, or discarded.
144
+ - Insert and delete rows (full CRUD). FK columns become dropdowns.
145
+ - **Bulk row operations** — select rows with checkboxes (or "select all"), then
146
+ **delete** them in one transaction (with a warning listing how many rows in
147
+ other tables reference them) or **export** only the selected rows as CSV/JSON.
148
+ - **Export** table rows or query results as **CSV or JSON**, and **import a CSV
149
+ file** (or pasted CSV) into a table.
150
+ - Create tables visually: name, type, primary key, not-null / unique,
151
+ default value, and foreign-key references.
152
+ - Write and run arbitrary SQL — SELECTs render as a table, `COUNT` queries show
153
+ a readable summary, write statements execute and report affected rows.
154
+ - Save named queries and reload them from a dropdown.
155
+ - Export the whole database as a downloadable SQL dump (`CREATE` + `INSERT`).
156
+ - **Get query / preview mode:** generate `CREATE` / `INSERT` / `UPDATE` SQL from
157
+ the UI without executing it.
158
+ - **Edit schema:** rename the table, add / rename / drop columns (with
159
+ relationship-safety checks), and drop tables.
160
+ - **Indexes:** create indexes (plain or unique, on one or many columns — pick
161
+ columns in order, with a live `CREATE INDEX` SQL preview) and drop them from
162
+ the schema editor; automatic SQLite indexes are protected.
163
+ - **Related rows:** every table that has a foreign key pointing at a table gets
164
+ its own column at the end of that table's grid — each cell shows how many of
165
+ its rows reference that record; click it to open a nested table with all of
166
+ the referencing table's columns and data for that specific record.
167
+ - **Read-only mode:** open the database(s) without write access — the file is
168
+ opened `SQLITE_OPEN_READONLY` + `query_only`, every write route returns `403`,
169
+ and the UI hides/disables all write controls and shows a banner.
170
+ - **Multiple databases:** directory scanning and/or explicit file lists, each
171
+ with its own workspace under `/{db}/…`.
172
+
173
+ ## Screenshots
174
+
175
+ Home dashboard — stat cards and the table list.
176
+
177
+ ![Home dashboard](docs/screenshots/home.png)
178
+
179
+ Table browser — sortable columns, sticky header, and per-row actions.
180
+
181
+ ![Table browser](docs/screenshots/table.png)
182
+
183
+ Query editor — line-numbered editor with a results table.
184
+
185
+ ![Query editor](docs/screenshots/query.png)
186
+
187
+ Table designer — visual columns with a live SQL preview.
188
+
189
+ ![Table designer](docs/screenshots/designer.png)
190
+
191
+ Insert / edit form — column defaults are pre-filled.
192
+
193
+ ![Insert form](docs/screenshots/form.png)
194
+
195
+ Schema editor — rename the table, add / rename / drop columns safely.
196
+
197
+ ![Schema editor](docs/screenshots/schema.png)
198
+
199
+ Databases landing page (multi-db mode) — manage multiple SQLite files.
200
+
201
+ ![Databases](docs/screenshots/databases.png)
202
+
203
+ ## Run the built-in server
204
+
205
+ For convenience a standalone server is included. From a clone of the repo:
206
+
207
+ ```bash
208
+ npm install
209
+ npm run build
210
+ npm start # open http://localhost:3000
211
+ ```
212
+
213
+ Configuration via environment variables:
214
+
215
+ | Variable | Default | Description |
216
+ | ----------- | ------------------ | ------------------------------------ |
217
+ | `PORT` | `3000` | Port to listen on |
218
+ | `HOST` | `0.0.0.0` | Host / interface to bind |
219
+ | `DB_PATH` | `admindb.db` | Single SQLite database file |
220
+ | `DB_DIR` | — | Multi-db source 1: a directory of `.db`/`.sqlite` files |
221
+ | `DB_FILES` | — | Multi-db source 2: comma-separated explicit database file paths |
222
+ | `READONLY` | — | `1` / `true` / `yes` / `on` opens the database(s) read-only |
223
+ | `BASE_PATH` | `''` | URL prefix (e.g. `/admin`) |
224
+ | `LOG_LEVEL` | `info` | `debug` \| `info` \| `warn` \| `error` |
225
+
226
+ Multi-db mode activates when `DB_DIR` and/or `DB_FILES` is set:
227
+
228
+ ```powershell
229
+ $env:DB_DIR='./db'; npm start # PowerShell
230
+ # bash/zsh: DB_DIR=./db npm start
231
+ ```
232
+
233
+ ## The JSON REST API (under `basePath`)
234
+
235
+ | Method | Path | Purpose |
236
+ | ------ | ---------------------------------------- | -------------------------------- |
237
+ | GET | `/api/tables` | List tables |
238
+ | GET | `/api/tables/:table/info` | Column + FK metadata, PK columns |
239
+ | GET | `/api/tables/:table/fk-options` | Values for FK dropdowns |
240
+ | GET | `/api/tables/:table/rows?page&limit&f` | Paginated rows (`f` = URL-encoded JSON filters — legacy strings like `{"age":">35"}` or structured conditions like `{"balance":{"op":"gte","value":"100"}}`) |
241
+ | GET | `/api/tables/:table/row/:id` | Single row by (encoded) PK |
242
+ | POST | `/api/tables/:table/rows` | Insert row |
243
+ | POST | `/api/tables/:table/rows/generate` | Generate INSERT SQL (no execute) |
244
+ | POST | `/api/tables/:table/rows/import` | Import CSV (`{ csv }`, header row must match columns) |
245
+ | GET | `/api/tables/:table/export?format=` | Download all rows as `csv` or `json` |
246
+ | PUT | `/api/tables/:table/row/:id` | Update row (`{ values, nulls? }` — `nulls` explicitly sets columns to NULL) |
247
+ | PUT | `/api/tables/:table/row/:id/generate` | Generate UPDATE SQL (no execute) |
248
+ | DELETE | `/api/tables/:table/row/:id` | Delete row |
249
+ | POST | `/api/tables/:table/rows/bulk-impact` | FK-impact preview: how many rows in other tables reference the selected rows |
250
+ | POST | `/api/tables/:table/rows/bulk-delete` | Delete selected rows (`{ ids, confirmImpact }`; transactional) |
251
+ | POST | `/api/tables/:table/rows/bulk-export` | Export only the selected rows as `csv`/`json` (`{ ids, format }`) |
252
+ | POST | `/api/tables/:table/rows/bulk-update` | Apply staged inline edits (`{ updates: [{ id, values?, nulls? }] }`; transactional) |
253
+ | POST | `/api/tables` | Create table |
254
+ | POST | `/api/tables/generate` | Generate CREATE SQL (no execute) |
255
+ | GET | `/api/tables/:table/schema` | Full schema (constraints, indexes, FK refs) |
256
+ | POST | `/api/tables/:table/rename` | Rename the table |
257
+ | POST | `/api/tables/:table/columns` | Add a column |
258
+ | PUT | `/api/tables/:table/columns/:column` | Rename a column |
259
+ | DELETE | `/api/tables/:table/columns/:column` | Drop a column (safety-checked) |
260
+ | DELETE | `/api/tables/:table` | Drop the table (safety-checked) |
261
+ | POST | `/api/tables/:table/indexes` | Create an index (`{ name?, columns[], unique? }`) |
262
+ | DELETE | `/api/tables/:table/indexes/:index` | Drop an index (auto indexes refused) |
263
+ | POST | `/api/query` | Run arbitrary SQL |
264
+ | POST | `/api/query/export` | Run a SELECT and download as `csv`/`json` |
265
+ | GET | `/api/queries` | List saved queries |
266
+ | POST | `/api/queries` | Save a named query |
267
+ | DELETE | `/api/queries/:id` | Delete a saved query |
268
+
269
+ In multi-db mode, every route is scoped under the database, e.g.
270
+ `/api/app.db/tables`. Every API response uses the consistent shape
271
+ `{ success, data?, error? }`.
272
+
273
+ ## Behaviour notes
274
+
275
+ - **Empty input = "not set".** Empty form fields are omitted so DB defaults
276
+ apply; `0` is a valid value and is never treated as empty.
277
+ - **Insert forms pre-fill defaults.** On the "new row" form, columns that have
278
+ a schema default are pre-filled (string/number/boolean literals and
279
+ `CURRENT_TIMESTAMP`-style defaults) so you can see and adjust them.
280
+ - **Row filters.** The filter panel adapts to each column's type: foreign keys
281
+ become dropdowns (with a "not set / NULL" option), booleans become
282
+ any/true/false toggles, and dates and numbers become min/max range inputs.
283
+ Text columns keep the operator syntax: exact match (`=value`), comparison
284
+ (`>5`, `>=5`, `<5`, `<=5`, `!=value`), prefix (`pre*`) and case-insensitive
285
+ substring (plain text). Filters are carried in the URL (as legacy strings or
286
+ structured conditions) and survive sorting and pagination.
287
+ - **Inline editing is staged, not instant.** Double-click a cell to edit it in
288
+ place; edits are buffered locally and highlighted in the grid rather than
289
+ written immediately. Press **Apply** to write every pending change to the
290
+ database in a single transaction (the page then reloads so related-row counts
291
+ stay accurate), or **Discard** to revert everything back to the saved values.
292
+ Clearing a text/date/number field stages a `NULL`.
293
+ - **Bulk operations.** Checkboxes select rows on the current page; "select all"
294
+ checks every visible row. **Delete** first shows a warning listing each table
295
+ that references the selected rows and how many rows point at them (these may
296
+ be cascaded away or orphaned depending on the foreign-key action, and the
297
+ delete can fail if a constraint blocks it). **Export CSV / JSON** downloads
298
+ only the selected rows. Both delete and export are capped at 1000 rows per
299
+ batch.
300
+ - **CSV import.** The first row must be a header whose names match existing
301
+ columns (unknown or duplicate names are rejected). Empty cells are treated as
302
+ "not set" so database defaults apply. The whole import runs in a single
303
+ transaction — a failed row rolls everything back.
304
+ - **Indexes.** Creating an index takes one or more columns and an optional
305
+ unique flag (the index name is optional too). Only explicitly created indexes
306
+ (`origin = 'c'`) can be dropped from the UI — automatic primary-key/unique
307
+ indexes are protected.
308
+ - **Schema editor.** Drops are refused when the column is a primary key, has a
309
+ UNIQUE constraint, is used by an index, is part of a foreign key, or is
310
+ referenced by another table's foreign key. Tables referenced by other tables
311
+ cannot be dropped. Internal (`_`-prefixed) tables cannot be renamed or dropped.
312
+ - **Identifiers are quoted and string values escaped** everywhere SQL is built,
313
+ so generated SQL is correct and safe.
314
+ - **`COUNT` queries** return a readable summary message instead of a table.
315
+ - **"Get query" never executes** — it only returns the generated SQL string.
316
+ - **Internal table** `_saved_queries` stores saved queries and is kept out of
317
+ user-facing FK pickers. Schema initialization is idempotent and safe to run
318
+ repeatedly.
319
+ - Composite primary keys are supported (values are URL-encoded and comma-joined
320
+ in the row endpoints).
321
+
322
+ ## Development
323
+
324
+ ```bash
325
+ npm install
326
+ npm run build # TypeScript → dist + Tailwind CSS
327
+ npm run dev # tsx watch server + Tailwind watch
328
+ npm test # builds and runs the unit tests
329
+ ```
330
+
331
+ `npm test` compiles TypeScript to `dist/` and runs the `node:test` suite
332
+ covering the SQL generator (INSERT / UPDATE / CREATE output, type mapping,
333
+ quoting, rejection of unsupported types), the SQL classifier, CSV parsing and
334
+ serialization, row filters (legacy and structured conditions), the database
335
+ layer (pagination, transactions, bulk update/delete, read-only enforcement),
336
+ and the database manager.
337
+
338
+ ## Publishing to npm
339
+
340
+ The package ships the compiled `dist/` (with type declarations), this `README`,
341
+ and the `LICENSE`. When you are ready to publish:
342
+
343
+ ```bash
344
+ npm run build # happens automatically via the prepack script
345
+ npm login
346
+ npm publish
347
+ ```
348
+
349
+ Update `name`/`version` in `package.json` to match your intended package name
350
+ and add an `author`/`repository` if desired.
351
+
352
+ ## License
353
+
354
+ [MIT](./LICENSE) — see the [LICENSE](LICENSE) file.