admindb 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +276 -228
  2. package/dist/app.d.ts +12 -0
  3. package/dist/app.js +7 -0
  4. package/dist/app.js.map +1 -1
  5. package/dist/args.d.ts +28 -0
  6. package/dist/args.js +187 -0
  7. package/dist/args.js.map +1 -0
  8. package/dist/cli.js +85 -15
  9. package/dist/cli.js.map +1 -1
  10. package/dist/data/generator.d.ts +86 -0
  11. package/dist/data/generator.js +1031 -0
  12. package/dist/data/generator.js.map +1 -0
  13. package/dist/db/database.d.ts +4 -0
  14. package/dist/db/database.js +28 -1
  15. package/dist/db/database.js.map +1 -1
  16. package/dist/db/manager.d.ts +6 -0
  17. package/dist/db/manager.js +31 -0
  18. package/dist/db/manager.js.map +1 -1
  19. package/dist/public/css/app.css +1 -1
  20. package/dist/public/css/input.css +9 -2
  21. package/dist/public/js/browse.js +8 -1
  22. package/dist/public/js/databases.js +99 -1
  23. package/dist/public/js/seed.js +377 -0
  24. package/dist/routes/api.js +62 -0
  25. package/dist/routes/api.js.map +1 -1
  26. package/dist/routes/databases.d.ts +10 -0
  27. package/dist/routes/databases.js +97 -0
  28. package/dist/routes/databases.js.map +1 -1
  29. package/dist/routes/pages.js +24 -0
  30. package/dist/routes/pages.js.map +1 -1
  31. package/dist/util.d.ts +5 -0
  32. package/dist/util.js +13 -0
  33. package/dist/util.js.map +1 -1
  34. package/dist/views/layouts/main.hbs +1 -1
  35. package/dist/views/pages/databases.hbs +41 -0
  36. package/dist/views/pages/query.hbs +2 -2
  37. package/dist/views/pages/seed.hbs +86 -0
  38. package/dist/views/pages/table.hbs +5 -0
  39. package/dist/views/partials/navbar.hbs +1 -6
  40. package/dist/views/partials/sidebar.hbs +1 -6
  41. package/package.json +2 -2
package/README.md CHANGED
@@ -1,60 +1,101 @@
1
1
  # AdminDB
2
2
 
3
-
4
3
  [![Version](https://img.shields.io/npm/v/admindb.svg)](https://www.npmjs.com/package/admindb)
5
4
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
5
  [![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
6
  [![Downloads](https://img.shields.io/npm/dm/admindb.svg)](https://www.npmjs.com/package/admindb)
7
+ [![Publish](https://github.com/MIbnEKhalid/admindb/actions/workflows/publish.yml/badge.svg?branch=main)](https://github.com/MIbnEKhalid/admindb/actions/workflows/publish.yml)
9
8
 
9
+ **A browser-based SQLite administration tool.** Manage a SQLite database
10
+ entirely from the browser — browse and edit rows, run SQL, design schemas,
11
+ import/export and seed data — with no separate frontend app to build or deploy.
10
12
 
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.
13
+ - **No frontend build step.** Handlebars server-rendered UI + vanilla JS +
14
+ Tailwind/DaisyUI; one Express process serves pages, static assets, and the
15
+ JSON API.
16
+ - **Standalone or embeddable.** Run it from the CLI against a file or a folder
17
+ of databases, or mount it inside an existing Express app under any path on
18
+ the same port.
19
+ - **Safe SQL by construction.** Every identifier is quoted and every literal is
20
+ escaped when SQL is generated, and the "Get query / preview" modes only
21
+ produce SQL strings — they never execute.
17
22
 
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
+ ![Home dashboard](docs/screenshots/home.png)
23
24
 
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.
25
+ > More screenshots in [`docs/screenshots/`](docs/screenshots/): table browser,
26
+ > query editor, table designer, insert/edit form, schema editor, and the
27
+ > databases landing page.
28
+
29
+ ## 30-Second Start
30
+
31
+ No install needed:
41
32
 
42
- ---
33
+ ```bash
34
+ npx admindb
35
+ # → AdminDB is listening on http://localhost:3000
36
+ ```
37
+
38
+ With no arguments the server starts in **manager mode**: a **Databases** landing
39
+ page where you can browse the filesystem and open any SQLite database file
40
+ (`.db` / `.sqlite` / `.sqlite3`), or create new ones. Or point it straight at a
41
+ file:
42
+
43
+ ```bash
44
+ npx admindb ./data/app.db # open one database
45
+ npx admindb -d ./dbs # manage a folder of databases
46
+ ```
47
+
48
+ From a clone:
49
+
50
+ ```bash
51
+ git clone https://github.com/MIbnEKhalid/admindb.git
52
+ cd admindb
53
+ npm install
54
+ npm run build
55
+ npm start # serves http://localhost:3000
56
+ ```
57
+
58
+ > Requires **Node.js ≥ 20**. Runtime dependencies are `express`,
59
+ > `express-handlebars`, and `better-sqlite3`; the rest is the application.
60
+
61
+ ## Why This Exists
62
+
63
+ AdminDB exists because SQLite deserves a proper web admin UI, and desktop tools
64
+ (DBeaver, DB Browser for SQLite) live outside both your browser and your stack.
65
+ Web admin tools like phpMyAdmin target MySQL/Postgres, not SQLite. This one is
66
+ server-rendered, so there is no frontend build step and nothing extra to deploy
67
+ — the same Express process serves pages, assets, and the JSON API. Run it
68
+ standalone from the CLI, or mount it under any path of an existing Express app
69
+ on the same port.
43
70
 
44
71
  ## Install
45
72
 
73
+ ### Try it (no install)
74
+
75
+ ```bash
76
+ npx admindb -p 8080 # run on port 8080 without installing
77
+ npx admindb ./app.db # open a database file directly
78
+ ```
79
+
80
+ ### Install as a standalone CLI tool
81
+
82
+ Requires **Node.js ≥ 20**:
83
+
46
84
  ```bash
47
- npm install admindb
85
+ npm install -g admindb
86
+ admindb # starts the server → open http://localhost:3000
48
87
  ```
49
88
 
50
- Requires **Node.js ≥ 20**.
89
+ The `admindb` command starts the built-in server and prints the URL to open in
90
+ your browser. See the [CLI reference](#cli-reference) for `--port`,
91
+ `--open <file>`, `--dir <folder>`, `--readonly`, and more.
51
92
 
52
- ## Using as an npm package
93
+ ### Embed AdminDB in your own app
53
94
 
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.
95
+ AdminDB is an Express app you can mount inside your own application, under your
96
+ own path, on the same port as the rest of your server.
56
97
 
57
- ### Minimal example
98
+ #### Minimal example
58
99
 
59
100
  ```ts
60
101
  import express from 'express';
@@ -73,7 +114,7 @@ app.listen(3000);
73
114
  `createRouter(options)` returns a fully wired Express app (pages + JSON API +
74
115
  static assets + view engine). Mounting it is just `app.use('/path', router)`.
75
116
 
76
- ### Options
117
+ #### Options
77
118
 
78
119
  | Option | Type | Description |
79
120
  | ---------- | -------------------- | -------------------------------------------------------------- |
@@ -83,6 +124,8 @@ static assets + view engine). Mounting it is just `app.use('/path', router)`.
83
124
  | `basePath` | `string` | URL prefix used by templates/assets (e.g. `/admin`). Pass the same prefix you mount at |
84
125
  | `logger` | `Logger` | Custom logger (see `createLogger`) |
85
126
  | `logLevel` | `'debug'\|'info'\|'warn'\|'error'` | Log verbosity (used when no logger is passed) |
127
+ | `allowBrowse` | `boolean` | Manager mode: show the filesystem file-browser on the databases page. Set `false` to disable it (e.g. when the server was started with specific database files). Default: `true` |
128
+ | `browseRoot` | `string` | Manager mode: restrict the file-browser to this folder (absolute path) — it cannot navigate above it and only databases inside it can be opened |
86
129
  | `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
130
 
88
131
  Example with a custom logger and prefix:
@@ -100,7 +143,7 @@ app.use('/tools/db', createRouter({
100
143
  app.listen(3000);
101
144
  ```
102
145
 
103
- ### Multiple databases
146
+ #### Multiple databases
104
147
 
105
148
  Pass a `DbManager` to manage several database files — either from a directory,
106
149
  from an explicit list of file paths, or both:
@@ -113,9 +156,9 @@ const app = express();
113
156
  app.use('/admin', createRouter({
114
157
  manager: new DbManager(
115
158
  {
116
- dir: './data', // scan a directory
159
+ dir: './data', // scan a directory
117
160
  files: ['/srv/legacy/app.db', './shared.sqlite'], // and/or explicit paths
118
- readonly: true, // open all databases read-only
161
+ readonly: true, // open all databases read-only
119
162
  },
120
163
  createLogger('info'),
121
164
  ),
@@ -130,224 +173,229 @@ In multi-db mode:
130
173
  `/admin/app.db/tables/users` and `/admin/app.db/api/tables`,
131
174
  - file names that collide across sources are deduped (`name__2.db`).
132
175
 
176
+ ### CLI reference
177
+
178
+ The standalone server is a full web app. With no arguments it runs in
179
+ **manager mode**: a **Databases** landing page where you can browse the
180
+ filesystem and open any SQLite database file (`.db` / `.sqlite` / `.sqlite3`),
181
+ or create new ones.
182
+
183
+ | Flag | Description |
184
+ | -------------------- | -------------------------------------------------------- |
185
+ | `-p, --port <port>` | Port to listen on (default `3000`) |
186
+ | `-H, --host <host>` | Host / interface to bind (default `0.0.0.0`) |
187
+ | `-o, --open <file>` | Open a single database file directly |
188
+ | `-d, --dir <dir>` | Manage a folder of database files |
189
+ | `--files <list>` | Comma-separated database file paths to manage |
190
+ | `-b, --base-path <p>`| URL prefix to serve under (default `/`) |
191
+ | `-r, --readonly` | Open databases read-only (all writes disabled) |
192
+ | `-l, --log-level <l>`| `debug` \| `info` \| `warn` \| `error` (default `info`) |
193
+ | `-h, --help` | Show help |
194
+ | `-v, --version` | Show the version |
195
+
196
+ A positional `path` argument opens a database file directly, or manages a
197
+ folder when it is a directory.
198
+
199
+ Every flag has a matching environment variable; **flags override the
200
+ environment**:
201
+
202
+ | Variable | Default | Description |
203
+ | ----------- | ------- | ------------------------------------ |
204
+ | `PORT` | `3000` | Port to listen on |
205
+ | `HOST` | `0.0.0.0` | Host / interface to bind |
206
+ | `DB_PATH` | — | Single SQLite database file |
207
+ | `DB_DIR` | — | Folder of `.db`/`.sqlite` files |
208
+ | `DB_FILES` | — | Comma-separated explicit database file paths |
209
+ | `READONLY` | — | `1` / `true` / `yes` / `on` opens the database(s) read-only |
210
+ | `BASE_PATH` | `''` | URL prefix (e.g. `/admin`) |
211
+ | `LOG_LEVEL` | `info` | `debug` \| `info` \| `warn` \| `error` |
212
+
213
+ Examples:
214
+
215
+ ```bash
216
+ admindb # manager UI on http://localhost:3000
217
+ admindb -p 8080 # same, on port 8080
218
+ admindb ./data/app.db # open a single database file
219
+ admindb --open ~/notes.sqlite # open a file directly
220
+ admindb -d ./dbs # manage a folder of databases
221
+ admindb --files a.db,b.db -r # open two files read-only
222
+ ```
223
+
224
+ ```powershell
225
+ $env:DB_DIR='./db'; npm start # PowerShell
226
+ # bash/zsh: DB_DIR=./db npm start
227
+ ```
228
+
229
+ When the server runs without an explicit single file, the **Databases** landing
230
+ page lists the managed databases and includes an **"Open an existing
231
+ database"** file browser: navigate folders, pick a database file, and open it.
232
+ Opened files are added to the list so you can switch between databases freely.
233
+
234
+ **File-browser policy:**
235
+
236
+ - When a **folder** is given (`--dir`, a directory path, or `DB_DIR`), browsing
237
+ is limited to that folder — it cannot navigate above it and only databases
238
+ inside it can be opened.
239
+ - When specific **files** are given (`--files`, or `DB_FILES`) without a folder,
240
+ file browsing is **disabled** entirely; only the configured databases are
241
+ listed.
242
+ - With no folder or files, browsing is unrestricted.
243
+
133
244
  ## Features
134
245
 
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.
246
+ ### 🔍 Browse & edit rows
247
+
248
+ - **Table browser** — every table with pagination, sorting (click a column
249
+ header), a sticky header, and per-row actions; composite primary keys are
250
+ supported (values are URL-encoded and comma-joined in the row endpoints).
251
+ - **Type-aware filters** — per-column filter controls: foreign-key dropdowns,
252
+ boolean toggles, date and numeric range inputs, plus the exact (`=value`),
253
+ comparison (`>5`, `<=10`), prefix (`pre*`) and substring (plain text)
254
+ operators on text columns. Filters survive sorting and pagination.
140
255
  - **Inline (spreadsheet-style) editing** — double-click any cell to edit it in
141
256
  place with a type-aware control (FK dropdown, boolean toggle, date picker,
142
257
  number/text input). Changes are staged and highlighted in the grid, then
143
258
  applied all at once in a single transaction, or discarded.
144
- - Insert and delete rows (full CRUD). FK columns become dropdowns.
259
+ - **Insert and delete rows** (full CRUD). FK columns become dropdowns; insert
260
+ forms pre-fill column defaults from the schema.
145
261
  - **Bulk row operations** — select rows with checkboxes (or "select all"), then
146
262
  **delete** them in one transaction (with a warning listing how many rows in
147
263
  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
264
+ - **Related rows** — every table that has a foreign key pointing at a table gets
265
+ its own column at the end of that table's grid; each cell shows how many of its
266
+ rows reference that record. Click it to open a nested table with all of the
267
+ referencing table's columns and data for that specific record.
268
+
269
+ ### ⚡ Query & run SQL
270
+
271
+ - **Arbitrary SQL runner** — SELECTs render as a table, `COUNT` queries show a
272
+ readable summary, and write statements execute and report affected rows.
273
+ - **Saved named queries** — save a query and reload it from a dropdown.
274
+ - **"Get query" / preview mode** — generate `CREATE` / `INSERT` / `UPDATE` SQL
275
+ from the UI without executing it. The preview modes only return the SQL
276
+ string; they never touch the database.
277
+ - **SQL dump** — export the whole database as a downloadable `CREATE` + `INSERT`
278
+ SQL file.
279
+
280
+ ### 🗂️ Design & manage schema
281
+
282
+ - **Visual table designer** — create a table with name, type, primary key,
283
+ not-null / unique, default value, and foreign-key references, with a live
284
+ `CREATE TABLE` SQL preview.
285
+ - **Schema editor** — rename the table, add / rename / drop columns, and drop
286
+ tables, with relationship-safety checks: drops are refused when the column is
287
+ a primary key, has a UNIQUE constraint, is used by an index, is part of a
288
+ foreign key, or is referenced by another table's foreign key. Tables
289
+ referenced by other tables cannot be dropped. Internal (`_`-prefixed) tables
290
+ cannot be renamed or dropped.
291
+ - **Indexes** — create indexes (plain or unique, on one or many columns — pick
161
292
  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
293
+ the schema editor; automatic SQLite (primary-key/unique) indexes are
294
+ protected.
295
+
296
+ ### 🔄 Import, export & seed data
297
+
298
+ - **Export** — table rows or query results as **CSV or JSON** (a whole table or
299
+ only selected rows).
300
+ - **CSV import** — paste or upload CSV into a table; the header must match
301
+ existing columns, and the import runs in a single transaction (a failed row
302
+ rolls everything back).
303
+ - **Seed data generator** — fill a table with realistic rows in one go. Each
304
+ column gets an auto-detected strategy (first/last/full name, email, phone,
305
+ city, country, UUID, random integer/decimal/date/datetime/boolean/bytes, a few
306
+ words, a sentence, a fixed value, a random value from a list, or "skip — let
307
+ the DB default apply"); foreign-key columns can sample real values from the
308
+ referenced table. Insert up to 5,000 rows transactionally, or preview the
309
+ generated `INSERT` SQL without executing it.
310
+
311
+ ### 🚀 Deploy
312
+
313
+ - **Read-only mode** — open the database(s) without write access: the file is
168
314
  opened `SQLITE_OPEN_READONLY` + `query_only`, every write route returns `403`,
169
315
  and the UI hides/disables all write controls and shows a banner.
170
- - **Multiple databases:** directory scanning and/or explicit file lists, each
316
+ - **Standalone CLI / file browser** — `admindb` runs as a full web app; with no
317
+ arguments it opens a **Databases** landing page where you can browse the
318
+ filesystem and open any SQLite database file, or create new ones. Flags set
319
+ the port, open a file, manage a folder, and more (`admindb --help`).
320
+ - **Multiple databases** — directory scanning and/or explicit file lists, each
171
321
  with its own workspace under `/{db}/…`.
172
322
 
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.
323
+ ## Security warning
192
324
 
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:
325
+ > ⚠️ **This tool exposes full, unauthenticated database AND filesystem access.**
326
+ > Every page and API route (browse, edit, delete, run arbitrary SQL, change the
327
+ > schema, export the whole database, and — in manager mode — browse the
328
+ > filesystem to open database files) is available to **anyone who can reach the
329
+ > server**.
330
+ >
331
+ > - **No authentication or authorization is built in.** The routes are **not
332
+ > protected**.
333
+ > - **It is your responsibility to protect access.** Do **not** expose
334
+ > AdminDB to the public internet or to untrusted networks.
335
+ > - Recommended ways to protect it:
336
+ > - bind the standalone server to `127.0.0.1` (`HOST=127.0.0.1`) and use it
337
+ > only from your own machine, and/or
338
+ > - run it behind a reverse proxy that requires authentication (Basic auth,
339
+ > OAuth, mTLS, …) or inside a VPN / private network.
340
+ >
341
+ > Treat AdminDB as if it were a remote `sqlite3` shell with write access.
206
342
 
207
- ```bash
208
- npm install
209
- npm run build
210
- npm start # open http://localhost:3000
343
+ ## API
344
+
345
+ All routes live under `basePath` and return the consistent shape
346
+ `{ success, data?, error? }`. In multi-db mode, every route is scoped under the
347
+ database, e.g. `/api/app.db/tables`.
348
+
349
+ ```text
350
+ GET /api/tables List tables
351
+ GET /api/tables/:table/rows Paginated rows (filters, sorting)
352
+ POST /api/tables/:table/rows Insert row
353
+ PUT /api/tables/:table/row/:id Update row
354
+ DELETE /api/tables/:table/row/:id Delete row
355
+ POST /api/query Run arbitrary SQL
356
+ POST /api/tables Create table
357
+ GET /api/tables/:table/export Download all rows as csv|json
358
+ POST /api/tables/:table/seed Generate and insert seed rows
211
359
  ```
212
360
 
213
- Configuration via environment variables:
361
+ Plus bulk row operations, CSV import, schema and index management, saved
362
+ queries, a data generator — and, in manager mode, database-file management and
363
+ the filesystem browser.
214
364
 
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
- ```
365
+ > **Full reference:** every endpoint (including seed config/generate, bulk
366
+ > update/delete/export, and the multi-db manager endpoints) is documented in
367
+ > **[`docs/API.md`](docs/API.md)**.
232
368
 
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
369
+ **Behaviour notes:**
274
370
 
275
371
  - **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.
372
+ apply; `0` is a valid value and is never treated as empty. Insert forms
373
+ pre-fill column defaults from the schema (string/number/boolean literals and
374
+ `CURRENT_TIMESTAMP`-style defaults).
287
375
  - **Inline editing is staged, not instant.** Double-click a cell to edit it in
288
376
  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.
377
+ written immediately. Press **Apply** to write every pending change in a single
378
+ transaction (the page then reloads so related-row counts stay accurate), or
379
+ **Discard** to revert. Clearing a text/date/number field stages a `NULL`.
380
+ - **Bulk operations** run per page with "select all"; delete first shows an
381
+ FK-impact warning (rows may be cascaded away or orphaned depending on the
382
+ foreign-key action, and the delete can fail if a constraint blocks it). Both
383
+ delete and export are capped at 1000 rows per batch.
384
+ - **CSV import** runs in a single transaction — a failed row rolls everything
385
+ back.
312
386
  - **Identifiers are quoted and string values escaped** everywhere SQL is built,
313
- so generated SQL is correct and safe.
387
+ so generated SQL is correct and safe; **"Get query" never executes** — it only
388
+ returns the generated SQL string.
314
389
  - **`COUNT` queries** return a readable summary message instead of a table.
315
- - **"Get query" never executes** — it only returns the generated SQL string.
316
390
  - **Internal table** `_saved_queries` stores saved queries and is kept out of
317
391
  user-facing FK pickers. Schema initialization is idempotent and safe to run
318
392
  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
393
 
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
- ```
394
+ ## Contributing
348
395
 
349
- Update `name`/`version` in `package.json` to match your intended package name
350
- and add an `author`/`repository` if desired.
396
+ PRs are welcome. Development workflow (`npm run dev` for live reload, `npm test`
397
+ to build and run the unit suite), project layout, and contribution guidelines
398
+ live in **[`CONTRIBUTING.md`](CONTRIBUTING.md)**.
351
399
 
352
400
  ## License
353
401
 
package/dist/app.d.ts CHANGED
@@ -15,6 +15,18 @@ export interface AppOptions {
15
15
  manager?: DbManager;
16
16
  /** Open the database read-only — all write routes are rejected with 403. */
17
17
  readonly?: boolean;
18
+ /**
19
+ * Manager mode: show the filesystem file-browser on the databases landing
20
+ * page. Defaults to `true`. Set to `false` to disable browsing (e.g. when the
21
+ * server was started with specific database files only).
22
+ */
23
+ allowBrowse?: boolean;
24
+ /**
25
+ * Manager mode: restrict the file-browser to this folder (absolute path).
26
+ * When set, the browser cannot navigate above it and only databases inside it
27
+ * can be opened.
28
+ */
29
+ browseRoot?: string;
18
30
  /** Internal: URL of the databases list (used by per-db apps to link "switch database"). */
19
31
  databasesUrl?: string;
20
32
  /** Internal: current database id (for locals/display). */