admindb 1.0.1 → 1.0.3

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