@mark1russell7/docker-sqlite 0.1.0 → 0.2.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.
- package/CHANGELOG.md +48 -23
- package/LICENSE +21 -21
- package/README.md +1089 -1057
- package/dist/.tsbuildinfo +1 -1
- package/dist/connect.d.ts +79 -50
- package/dist/connect.d.ts.map +1 -1
- package/dist/connect.js +120 -14
- package/dist/connect.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts.map +1 -1
- package/dist/migrate.js +151 -12
- package/dist/migrate.js.map +1 -1
- package/dist/query.d.ts +51 -51
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -4
- package/dist/query.js.map +1 -1
- package/package.json +80 -86
- package/schema/logs.sql +21 -21
- package/src/connect.ts +317 -194
- package/src/index.ts +61 -61
- package/src/migrate.ts +293 -159
- package/src/query.ts +146 -147
- package/src/types.ts +62 -62
package/README.md
CHANGED
|
@@ -1,1057 +1,1089 @@
|
|
|
1
|
-
# @mark1russell7/docker-sqlite
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@mark1russell7/docker-sqlite)
|
|
4
|
-
[](./LICENSE)
|
|
5
|
-
[](https://nodejs.org/)
|
|
6
|
-
[](https://mark1russell7.github.io/client/)
|
|
7
|
-
|
|
8
|
-
> Reusable SQLite setup with TypeScript utilities for connection management, queries, and migrations using sql.js (SQLite compiled to WASM).
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
npm install @mark1russell7/docker-sqlite
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
import { withConnection, execute, query } from "@mark1russell7/docker-sqlite";
|
|
16
|
-
|
|
17
|
-
const rows = await withConnection({ dbPath: "./data/app.db" }, (db) => {
|
|
18
|
-
execute(db, "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT)");
|
|
19
|
-
execute(db, "INSERT INTO notes (text) VALUES (?)", ["hello"]);
|
|
20
|
-
return query(db, "SELECT text FROM notes").rows;
|
|
21
|
-
});
|
|
22
|
-
console.log(rows); // [{ text: "hello" }] (one more row on each run)
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
The file is saved only when the database changed, and calls for one file run one at a time. The package is the database layer of `db.*` and `logs.*` in the [client monorepo](https://github.com/mark1russell7/client) (see [the client site](https://mark1russell7.github.io/client/)).
|
|
26
|
-
|
|
27
|
-
## Overview
|
|
28
|
-
|
|
29
|
-
`@mark1russell7/docker-sqlite` provides a complete SQLite development environment with:
|
|
30
|
-
|
|
31
|
-
- **Portable SQLite via WASM** - Uses sql.js for cross-platform compatibility without native dependencies
|
|
32
|
-
- **Type-safe utilities** - Connection management, query execution, and migration system with full TypeScript support
|
|
33
|
-
- **Auto-save functionality** - Changes automatically persisted to file with managed connections
|
|
34
|
-
- **Migration system** - Version-controlled schema migrations with up/down support
|
|
35
|
-
- **Docker integration** - Optional web UI for database browsing via docker-compose
|
|
36
|
-
- **Zero native dependencies** - Works in Node.js, browsers, and edge environments
|
|
37
|
-
|
|
38
|
-
## Table of Contents
|
|
39
|
-
|
|
40
|
-
- [Architecture](#architecture)
|
|
41
|
-
- [Installation](#installation)
|
|
42
|
-
- [Docker Setup](#docker-setup)
|
|
43
|
-
- [TypeScript API](#typescript-api)
|
|
44
|
-
- [Migration System](#migration-system)
|
|
45
|
-
- [Configuration](#configuration)
|
|
46
|
-
- [Integration](#integration)
|
|
47
|
-
- [Examples](#examples)
|
|
48
|
-
- [API Reference](#api-reference)
|
|
49
|
-
|
|
50
|
-
## Architecture
|
|
51
|
-
|
|
52
|
-
```mermaid
|
|
53
|
-
graph TB
|
|
54
|
-
subgraph "Application Layer"
|
|
55
|
-
A[Your Application]
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
subgraph "docker-sqlite Library"
|
|
59
|
-
B[Connection Manager]
|
|
60
|
-
C[Query Utilities]
|
|
61
|
-
D[Migration System]
|
|
62
|
-
|
|
63
|
-
B --> C
|
|
64
|
-
B --> D
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
subgraph "SQLite Engine"
|
|
68
|
-
E[sql.js<br/>WASM]
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
subgraph "Storage"
|
|
72
|
-
F[(File System<br/>*.db files)]
|
|
73
|
-
G[(In-Memory)]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
subgraph "Docker Environment (Optional)"
|
|
77
|
-
H[sqlite-web Container<br/>Web UI]
|
|
78
|
-
I[Volume: ./data]
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
A --> B
|
|
82
|
-
B --> E
|
|
83
|
-
E --> F
|
|
84
|
-
E --> G
|
|
85
|
-
H --> I
|
|
86
|
-
I -.-> F
|
|
87
|
-
|
|
88
|
-
style A fill:#3178c6,color:#fff
|
|
89
|
-
style B fill:#3178c6,color:#fff
|
|
90
|
-
style E fill:#003b57,color:#fff
|
|
91
|
-
style H fill:#0db7ed,color:#fff
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Library Architecture
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
98
|
-
│ Application │
|
|
99
|
-
│ ┌───────────────────────────────────────────────────────┐ │
|
|
100
|
-
│ │ await withConnection({ dbPath: './app.db' }, (db) => │ │
|
|
101
|
-
│ │ runMigrations(db, migrations); │ │
|
|
102
|
-
│ │ execute(db, 'INSERT INTO users ...'); │ │
|
|
103
|
-
│ │ const { rows } = query(db, 'SELECT * FROM users'); │ │
|
|
104
|
-
│ │ }); │ │
|
|
105
|
-
│ └───────────────────────────────────────────────────────┘ │
|
|
106
|
-
└────────────────────────┬────────────────────────────────────┘
|
|
107
|
-
│
|
|
108
|
-
▼
|
|
109
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
110
|
-
│ @mark1russell7/docker-sqlite │
|
|
111
|
-
├─────────────────────────────────────────────────────────────┤
|
|
112
|
-
│ │
|
|
113
|
-
│ ┌────────────────────────────────────────────────────┐ │
|
|
114
|
-
│ │ Connection (connect.ts) │ │
|
|
115
|
-
│ │ ┌──────────────────────────────────────────────┐ │ │
|
|
116
|
-
│ │ │ withConnection(config, callback) │ │ │
|
|
117
|
-
│ │ │ ├─ Load db file (or create in-memory) │ │ │
|
|
118
|
-
│ │ │ ├─ Execute callback with db instance │ │ │
|
|
119
|
-
│ │ │ └─ Auto-save and close on completion │ │ │
|
|
120
|
-
│ │ │ │ │ │
|
|
121
|
-
│ │ │ createConnection(config) │ │ │
|
|
122
|
-
│ │ │ └─ Manual connection management │ │ │
|
|
123
|
-
│ │ └──────────────────────────────────────────────┘ │ │
|
|
124
|
-
│ └────────────────────────────────────────────────────┘ │
|
|
125
|
-
│ │
|
|
126
|
-
│ ┌────────────────────────────────────────────────────┐ │
|
|
127
|
-
│ │ Query (query.ts) │ │
|
|
128
|
-
│ │ ┌──────────────────────────────────────────────┐ │ │
|
|
129
|
-
│ │ │ query(db, sql, params?) │ │ │
|
|
130
|
-
│ │ │ └─ Execute SELECT, return {rows, columns} │ │ │
|
|
131
|
-
│ │ │ │ │ │
|
|
132
|
-
│ │ │ execute(db, sql, params?) │ │ │
|
|
133
|
-
│ │ │ └─ Execute INSERT/UPDATE/DELETE │ │ │
|
|
134
|
-
│ │ │ │ │ │
|
|
135
|
-
│ │ │ execMultiple(db, statements) │ │ │
|
|
136
|
-
│ │ │ └─ Execute multiple statements │ │ │
|
|
137
|
-
│ │ │ │ │ │
|
|
138
|
-
│ │ │ tableExists(db, tableName) │ │ │
|
|
139
|
-
│ │ │ lastInsertRowId(db) │ │ │
|
|
140
|
-
│ │ └──────────────────────────────────────────────┘ │ │
|
|
141
|
-
│ └────────────────────────────────────────────────────┘ │
|
|
142
|
-
│ │
|
|
143
|
-
│ ┌────────────────────────────────────────────────────┐ │
|
|
144
|
-
│ │ Migration (migrate.ts) │ │
|
|
145
|
-
│ │ ┌──────────────────────────────────────────────┐ │ │
|
|
146
|
-
│ │ │ runMigrations(db, migrations) │ │ │
|
|
147
|
-
│ │ │ └─ Apply pending migrations in order │ │ │
|
|
148
|
-
│ │ │ │ │ │
|
|
149
|
-
│ │ │ rollbackMigration(db, migrations) │ │ │
|
|
150
|
-
│ │ │ └─ Rollback last migration │ │ │
|
|
151
|
-
│ │ │ │ │ │
|
|
152
|
-
│ │ │ getMigrationStatus(db, migrations) │ │ │
|
|
153
|
-
│ │ │ └─ Get current version and status │ │ │
|
|
154
|
-
│ │ │ │ │ │
|
|
155
|
-
│ │ │ createMigration(...) │ │ │
|
|
156
|
-
│ │ │ └─ Helper to create migration objects │ │ │
|
|
157
|
-
│ │ └──────────────────────────────────────────────┘ │ │
|
|
158
|
-
│ └────────────────────────────────────────────────────┘ │
|
|
159
|
-
│ │
|
|
160
|
-
└────────────────────────┬────────────────────────────────────┘
|
|
161
|
-
│
|
|
162
|
-
▼
|
|
163
|
-
┌─────────────────┐
|
|
164
|
-
│ sql.js │
|
|
165
|
-
│ (SQLite in WASM)│
|
|
166
|
-
└────────┬────────┘
|
|
167
|
-
│
|
|
168
|
-
┌──────────┴──────────┐
|
|
169
|
-
▼ ▼
|
|
170
|
-
┌─────────────┐ ┌─────────────┐
|
|
171
|
-
│ File System │ │ In-Memory │
|
|
172
|
-
│ *.db │ │ Database │
|
|
173
|
-
└─────────────┘ └─────────────┘
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Data Flow Diagram
|
|
177
|
-
|
|
178
|
-
```mermaid
|
|
179
|
-
sequenceDiagram
|
|
180
|
-
participant App as Application
|
|
181
|
-
participant Lib as docker-sqlite
|
|
182
|
-
participant SQL as sql.js (WASM)
|
|
183
|
-
participant FS as File System
|
|
184
|
-
|
|
185
|
-
App->>Lib: withConnection({ dbPath: './app.db' }, callback)
|
|
186
|
-
Lib->>FS: Read database file
|
|
187
|
-
alt File exists
|
|
188
|
-
FS-->>Lib: File buffer
|
|
189
|
-
else File doesn't exist
|
|
190
|
-
FS-->>Lib: ENOENT error
|
|
191
|
-
Note over Lib: Create new database
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
Lib->>SQL: new Database(buffer)
|
|
195
|
-
SQL-->>Lib: Database instance
|
|
196
|
-
|
|
197
|
-
Lib->>App: callback(db)
|
|
198
|
-
|
|
199
|
-
App->>Lib: execute(db, 'INSERT ...')
|
|
200
|
-
Lib->>SQL: db.run(sql, params)
|
|
201
|
-
SQL-->>Lib: Success
|
|
202
|
-
|
|
203
|
-
App->>Lib: query(db, 'SELECT ...')
|
|
204
|
-
Lib->>SQL: db.prepare(sql).step()
|
|
205
|
-
SQL-->>Lib: Result rows
|
|
206
|
-
Lib-->>App: { rows, columns }
|
|
207
|
-
|
|
208
|
-
App-->>Lib: callback complete
|
|
209
|
-
|
|
210
|
-
Lib->>SQL: db.export()
|
|
211
|
-
SQL-->>Lib: Binary data
|
|
212
|
-
Lib->>FS: Write database file
|
|
213
|
-
FS-->>Lib: Success
|
|
214
|
-
|
|
215
|
-
Lib->>SQL: db.close()
|
|
216
|
-
Lib-->>App: Return callback result
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## Installation
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
npm install @mark1russell7/docker-sqlite
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Requirements
|
|
226
|
-
|
|
227
|
-
- **Node.js**: >= 22
|
|
228
|
-
- **Docker** (optional): For web UI to browse database
|
|
229
|
-
- **Docker Compose** (optional): For orchestration
|
|
230
|
-
|
|
231
|
-
## Docker Setup
|
|
232
|
-
|
|
233
|
-
The package includes an optional Docker Compose configuration that provides a web-based SQLite browser.
|
|
234
|
-
|
|
235
|
-
### Quick Start
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
# Start SQLite Web UI
|
|
239
|
-
docker compose up -d
|
|
240
|
-
|
|
241
|
-
# Open http://localhost:8080 to browse your database
|
|
242
|
-
|
|
243
|
-
# Stop container
|
|
244
|
-
docker compose down
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### Environment Variables
|
|
248
|
-
|
|
249
|
-
Configure the SQLite web viewer using environment variables:
|
|
250
|
-
|
|
251
|
-
| Variable | Default | Description |
|
|
252
|
-
|----------|---------|-------------|
|
|
253
|
-
| `SQLITE_CONTAINER_NAME` | `sqlite-web` | Name of the Docker container |
|
|
254
|
-
| `SQLITE_PORT` | `8080` | Host port for web UI |
|
|
255
|
-
| `SQLITE_DB_PATH` | `./data` | Path to database directory on host |
|
|
256
|
-
| `SQLITE_DB_NAME` | `cli.db` | Database filename to open |
|
|
257
|
-
|
|
258
|
-
#### Example: Custom Configuration
|
|
259
|
-
|
|
260
|
-
Create a `.env` file:
|
|
261
|
-
|
|
262
|
-
```env
|
|
263
|
-
SQLITE_CONTAINER_NAME=myapp-sqlite-ui
|
|
264
|
-
SQLITE_PORT=8090
|
|
265
|
-
SQLITE_DB_PATH=./database
|
|
266
|
-
SQLITE_DB_NAME=myapp.db
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
Then run:
|
|
270
|
-
|
|
271
|
-
```bash
|
|
272
|
-
docker compose up -d
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Container Features
|
|
276
|
-
|
|
277
|
-
- **Web-based UI**: Browse and query SQLite databases via browser
|
|
278
|
-
- **Auto-restart**: Container restarts unless explicitly stopped
|
|
279
|
-
- **Volume mounting**: Live view of database changes
|
|
280
|
-
- **Health checks**: Monitors UI availability every 30s
|
|
281
|
-
- **Read-write mode**: Can modify database through UI
|
|
282
|
-
|
|
283
|
-
### Web UI Usage
|
|
284
|
-
|
|
285
|
-
Once running, navigate to `http://localhost:8080` to:
|
|
286
|
-
|
|
287
|
-
- Browse tables and schema
|
|
288
|
-
- Execute SQL queries
|
|
289
|
-
- View indexes and constraints
|
|
290
|
-
- Export data as CSV/JSON
|
|
291
|
-
- Modify records directly
|
|
292
|
-
|
|
293
|
-
## TypeScript API
|
|
294
|
-
|
|
295
|
-
### Connection Management
|
|
296
|
-
|
|
297
|
-
#### `withConnection<T>(config, callback): Promise<T>`
|
|
298
|
-
|
|
299
|
-
Execute a callback with an auto-managed SQLite connection. The database is automatically loaded, saved, and closed.
|
|
300
|
-
|
|
301
|
-
```typescript
|
|
302
|
-
import { withConnection, query, execute } from '@mark1russell7/docker-sqlite';
|
|
303
|
-
|
|
304
|
-
const users = await withConnection(
|
|
305
|
-
{ dbPath: './data/app.db' },
|
|
306
|
-
(db) => {
|
|
307
|
-
execute(db, 'INSERT INTO users (name) VALUES (?)', ['Alice']);
|
|
308
|
-
return query(db, 'SELECT * FROM users');
|
|
309
|
-
}
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
console.log(users.rows);
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
**Parameters:**
|
|
316
|
-
- `config.dbPath`: Path to database file (use `':memory:'` for in-memory database)
|
|
317
|
-
- `callback`: Function receiving `Database` instance
|
|
318
|
-
|
|
319
|
-
**Returns:** Result of the callback function
|
|
320
|
-
|
|
321
|
-
**Behavior:**
|
|
322
|
-
- Saves the database after the callback completes, only when its content changed. A read does not write the file, so a read-only file can be read, and a read cannot write an old copy over the changes of another process.
|
|
323
|
-
- Makes the database file only when the callback changes the database. A query of a missing file does not make an empty file.
|
|
324
|
-
- Calls for one file run one at a time in this process. On Windows, two spellings of one path (case) share one queue.
|
|
325
|
-
-
|
|
326
|
-
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
```typescript
|
|
354
|
-
import {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
```typescript
|
|
379
|
-
{
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
#### `
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
import {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
```typescript
|
|
488
|
-
import {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
console.log(`
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
---
|
|
570
|
-
|
|
571
|
-
### `
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
```typescript
|
|
576
|
-
import {
|
|
577
|
-
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
})
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
orderBy(column: string, direction
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
.
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
.
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
//
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1
|
+
# @mark1russell7/docker-sqlite
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@mark1russell7/docker-sqlite)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://mark1russell7.github.io/client/)
|
|
7
|
+
|
|
8
|
+
> Reusable SQLite setup with TypeScript utilities for connection management, queries, and migrations using sql.js (SQLite compiled to WASM).
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @mark1russell7/docker-sqlite
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { withConnection, execute, query } from "@mark1russell7/docker-sqlite";
|
|
16
|
+
|
|
17
|
+
const rows = await withConnection({ dbPath: "./data/app.db" }, (db) => {
|
|
18
|
+
execute(db, "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT)");
|
|
19
|
+
execute(db, "INSERT INTO notes (text) VALUES (?)", ["hello"]);
|
|
20
|
+
return query(db, "SELECT text FROM notes").rows;
|
|
21
|
+
});
|
|
22
|
+
console.log(rows); // [{ text: "hello" }] (one more row on each run)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The file is saved only when the database changed, and calls for one file run one at a time. The package is the database layer of `db.*` and `logs.*` in the [client monorepo](https://github.com/mark1russell7/client) (see [the client site](https://mark1russell7.github.io/client/)).
|
|
26
|
+
|
|
27
|
+
## Overview
|
|
28
|
+
|
|
29
|
+
`@mark1russell7/docker-sqlite` provides a complete SQLite development environment with:
|
|
30
|
+
|
|
31
|
+
- **Portable SQLite via WASM** - Uses sql.js for cross-platform compatibility without native dependencies
|
|
32
|
+
- **Type-safe utilities** - Connection management, query execution, and migration system with full TypeScript support
|
|
33
|
+
- **Auto-save functionality** - Changes automatically persisted to file with managed connections
|
|
34
|
+
- **Migration system** - Version-controlled schema migrations with up/down support
|
|
35
|
+
- **Docker integration** - Optional web UI for database browsing via docker-compose
|
|
36
|
+
- **Zero native dependencies** - Works in Node.js, browsers, and edge environments
|
|
37
|
+
|
|
38
|
+
## Table of Contents
|
|
39
|
+
|
|
40
|
+
- [Architecture](#architecture)
|
|
41
|
+
- [Installation](#installation)
|
|
42
|
+
- [Docker Setup](#docker-setup)
|
|
43
|
+
- [TypeScript API](#typescript-api)
|
|
44
|
+
- [Migration System](#migration-system)
|
|
45
|
+
- [Configuration](#configuration)
|
|
46
|
+
- [Integration](#integration)
|
|
47
|
+
- [Examples](#examples)
|
|
48
|
+
- [API Reference](#api-reference)
|
|
49
|
+
|
|
50
|
+
## Architecture
|
|
51
|
+
|
|
52
|
+
```mermaid
|
|
53
|
+
graph TB
|
|
54
|
+
subgraph "Application Layer"
|
|
55
|
+
A[Your Application]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
subgraph "docker-sqlite Library"
|
|
59
|
+
B[Connection Manager]
|
|
60
|
+
C[Query Utilities]
|
|
61
|
+
D[Migration System]
|
|
62
|
+
|
|
63
|
+
B --> C
|
|
64
|
+
B --> D
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
subgraph "SQLite Engine"
|
|
68
|
+
E[sql.js<br/>WASM]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
subgraph "Storage"
|
|
72
|
+
F[(File System<br/>*.db files)]
|
|
73
|
+
G[(In-Memory)]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
subgraph "Docker Environment (Optional)"
|
|
77
|
+
H[sqlite-web Container<br/>Web UI]
|
|
78
|
+
I[Volume: ./data]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
A --> B
|
|
82
|
+
B --> E
|
|
83
|
+
E --> F
|
|
84
|
+
E --> G
|
|
85
|
+
H --> I
|
|
86
|
+
I -.-> F
|
|
87
|
+
|
|
88
|
+
style A fill:#3178c6,color:#fff
|
|
89
|
+
style B fill:#3178c6,color:#fff
|
|
90
|
+
style E fill:#003b57,color:#fff
|
|
91
|
+
style H fill:#0db7ed,color:#fff
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Library Architecture
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
98
|
+
│ Application │
|
|
99
|
+
│ ┌───────────────────────────────────────────────────────┐ │
|
|
100
|
+
│ │ await withConnection({ dbPath: './app.db' }, (db) => │ │
|
|
101
|
+
│ │ runMigrations(db, migrations); │ │
|
|
102
|
+
│ │ execute(db, 'INSERT INTO users ...'); │ │
|
|
103
|
+
│ │ const { rows } = query(db, 'SELECT * FROM users'); │ │
|
|
104
|
+
│ │ }); │ │
|
|
105
|
+
│ └───────────────────────────────────────────────────────┘ │
|
|
106
|
+
└────────────────────────┬────────────────────────────────────┘
|
|
107
|
+
│
|
|
108
|
+
▼
|
|
109
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
110
|
+
│ @mark1russell7/docker-sqlite │
|
|
111
|
+
├─────────────────────────────────────────────────────────────┤
|
|
112
|
+
│ │
|
|
113
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
114
|
+
│ │ Connection (connect.ts) │ │
|
|
115
|
+
│ │ ┌──────────────────────────────────────────────┐ │ │
|
|
116
|
+
│ │ │ withConnection(config, callback) │ │ │
|
|
117
|
+
│ │ │ ├─ Load db file (or create in-memory) │ │ │
|
|
118
|
+
│ │ │ ├─ Execute callback with db instance │ │ │
|
|
119
|
+
│ │ │ └─ Auto-save and close on completion │ │ │
|
|
120
|
+
│ │ │ │ │ │
|
|
121
|
+
│ │ │ createConnection(config) │ │ │
|
|
122
|
+
│ │ │ └─ Manual connection management │ │ │
|
|
123
|
+
│ │ └──────────────────────────────────────────────┘ │ │
|
|
124
|
+
│ └────────────────────────────────────────────────────┘ │
|
|
125
|
+
│ │
|
|
126
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
127
|
+
│ │ Query (query.ts) │ │
|
|
128
|
+
│ │ ┌──────────────────────────────────────────────┐ │ │
|
|
129
|
+
│ │ │ query(db, sql, params?) │ │ │
|
|
130
|
+
│ │ │ └─ Execute SELECT, return {rows, columns} │ │ │
|
|
131
|
+
│ │ │ │ │ │
|
|
132
|
+
│ │ │ execute(db, sql, params?) │ │ │
|
|
133
|
+
│ │ │ └─ Execute INSERT/UPDATE/DELETE │ │ │
|
|
134
|
+
│ │ │ │ │ │
|
|
135
|
+
│ │ │ execMultiple(db, statements) │ │ │
|
|
136
|
+
│ │ │ └─ Execute multiple statements │ │ │
|
|
137
|
+
│ │ │ │ │ │
|
|
138
|
+
│ │ │ tableExists(db, tableName) │ │ │
|
|
139
|
+
│ │ │ lastInsertRowId(db) │ │ │
|
|
140
|
+
│ │ └──────────────────────────────────────────────┘ │ │
|
|
141
|
+
│ └────────────────────────────────────────────────────┘ │
|
|
142
|
+
│ │
|
|
143
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
144
|
+
│ │ Migration (migrate.ts) │ │
|
|
145
|
+
│ │ ┌──────────────────────────────────────────────┐ │ │
|
|
146
|
+
│ │ │ runMigrations(db, migrations) │ │ │
|
|
147
|
+
│ │ │ └─ Apply pending migrations in order │ │ │
|
|
148
|
+
│ │ │ │ │ │
|
|
149
|
+
│ │ │ rollbackMigration(db, migrations) │ │ │
|
|
150
|
+
│ │ │ └─ Rollback last migration │ │ │
|
|
151
|
+
│ │ │ │ │ │
|
|
152
|
+
│ │ │ getMigrationStatus(db, migrations) │ │ │
|
|
153
|
+
│ │ │ └─ Get current version and status │ │ │
|
|
154
|
+
│ │ │ │ │ │
|
|
155
|
+
│ │ │ createMigration(...) │ │ │
|
|
156
|
+
│ │ │ └─ Helper to create migration objects │ │ │
|
|
157
|
+
│ │ └──────────────────────────────────────────────┘ │ │
|
|
158
|
+
│ └────────────────────────────────────────────────────┘ │
|
|
159
|
+
│ │
|
|
160
|
+
└────────────────────────┬────────────────────────────────────┘
|
|
161
|
+
│
|
|
162
|
+
▼
|
|
163
|
+
┌─────────────────┐
|
|
164
|
+
│ sql.js │
|
|
165
|
+
│ (SQLite in WASM)│
|
|
166
|
+
└────────┬────────┘
|
|
167
|
+
│
|
|
168
|
+
┌──────────┴──────────┐
|
|
169
|
+
▼ ▼
|
|
170
|
+
┌─────────────┐ ┌─────────────┐
|
|
171
|
+
│ File System │ │ In-Memory │
|
|
172
|
+
│ *.db │ │ Database │
|
|
173
|
+
└─────────────┘ └─────────────┘
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Data Flow Diagram
|
|
177
|
+
|
|
178
|
+
```mermaid
|
|
179
|
+
sequenceDiagram
|
|
180
|
+
participant App as Application
|
|
181
|
+
participant Lib as docker-sqlite
|
|
182
|
+
participant SQL as sql.js (WASM)
|
|
183
|
+
participant FS as File System
|
|
184
|
+
|
|
185
|
+
App->>Lib: withConnection({ dbPath: './app.db' }, callback)
|
|
186
|
+
Lib->>FS: Read database file
|
|
187
|
+
alt File exists
|
|
188
|
+
FS-->>Lib: File buffer
|
|
189
|
+
else File doesn't exist
|
|
190
|
+
FS-->>Lib: ENOENT error
|
|
191
|
+
Note over Lib: Create new database
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
Lib->>SQL: new Database(buffer)
|
|
195
|
+
SQL-->>Lib: Database instance
|
|
196
|
+
|
|
197
|
+
Lib->>App: callback(db)
|
|
198
|
+
|
|
199
|
+
App->>Lib: execute(db, 'INSERT ...')
|
|
200
|
+
Lib->>SQL: db.run(sql, params)
|
|
201
|
+
SQL-->>Lib: Success
|
|
202
|
+
|
|
203
|
+
App->>Lib: query(db, 'SELECT ...')
|
|
204
|
+
Lib->>SQL: db.prepare(sql).step()
|
|
205
|
+
SQL-->>Lib: Result rows
|
|
206
|
+
Lib-->>App: { rows, columns }
|
|
207
|
+
|
|
208
|
+
App-->>Lib: callback complete
|
|
209
|
+
|
|
210
|
+
Lib->>SQL: db.export()
|
|
211
|
+
SQL-->>Lib: Binary data
|
|
212
|
+
Lib->>FS: Write database file
|
|
213
|
+
FS-->>Lib: Success
|
|
214
|
+
|
|
215
|
+
Lib->>SQL: db.close()
|
|
216
|
+
Lib-->>App: Return callback result
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Installation
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
npm install @mark1russell7/docker-sqlite
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Requirements
|
|
226
|
+
|
|
227
|
+
- **Node.js**: >= 22
|
|
228
|
+
- **Docker** (optional): For web UI to browse database
|
|
229
|
+
- **Docker Compose** (optional): For orchestration
|
|
230
|
+
|
|
231
|
+
## Docker Setup
|
|
232
|
+
|
|
233
|
+
The package includes an optional Docker Compose configuration that provides a web-based SQLite browser.
|
|
234
|
+
|
|
235
|
+
### Quick Start
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Start SQLite Web UI
|
|
239
|
+
docker compose up -d
|
|
240
|
+
|
|
241
|
+
# Open http://localhost:8080 to browse your database
|
|
242
|
+
|
|
243
|
+
# Stop container
|
|
244
|
+
docker compose down
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Environment Variables
|
|
248
|
+
|
|
249
|
+
Configure the SQLite web viewer using environment variables:
|
|
250
|
+
|
|
251
|
+
| Variable | Default | Description |
|
|
252
|
+
|----------|---------|-------------|
|
|
253
|
+
| `SQLITE_CONTAINER_NAME` | `sqlite-web` | Name of the Docker container |
|
|
254
|
+
| `SQLITE_PORT` | `8080` | Host port for web UI |
|
|
255
|
+
| `SQLITE_DB_PATH` | `./data` | Path to database directory on host |
|
|
256
|
+
| `SQLITE_DB_NAME` | `cli.db` | Database filename to open |
|
|
257
|
+
|
|
258
|
+
#### Example: Custom Configuration
|
|
259
|
+
|
|
260
|
+
Create a `.env` file:
|
|
261
|
+
|
|
262
|
+
```env
|
|
263
|
+
SQLITE_CONTAINER_NAME=myapp-sqlite-ui
|
|
264
|
+
SQLITE_PORT=8090
|
|
265
|
+
SQLITE_DB_PATH=./database
|
|
266
|
+
SQLITE_DB_NAME=myapp.db
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Then run:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
docker compose up -d
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Container Features
|
|
276
|
+
|
|
277
|
+
- **Web-based UI**: Browse and query SQLite databases via browser
|
|
278
|
+
- **Auto-restart**: Container restarts unless explicitly stopped
|
|
279
|
+
- **Volume mounting**: Live view of database changes
|
|
280
|
+
- **Health checks**: Monitors UI availability every 30s
|
|
281
|
+
- **Read-write mode**: Can modify database through UI
|
|
282
|
+
|
|
283
|
+
### Web UI Usage
|
|
284
|
+
|
|
285
|
+
Once running, navigate to `http://localhost:8080` to:
|
|
286
|
+
|
|
287
|
+
- Browse tables and schema
|
|
288
|
+
- Execute SQL queries
|
|
289
|
+
- View indexes and constraints
|
|
290
|
+
- Export data as CSV/JSON
|
|
291
|
+
- Modify records directly
|
|
292
|
+
|
|
293
|
+
## TypeScript API
|
|
294
|
+
|
|
295
|
+
### Connection Management
|
|
296
|
+
|
|
297
|
+
#### `withConnection<T>(config, callback): Promise<T>`
|
|
298
|
+
|
|
299
|
+
Execute a callback with an auto-managed SQLite connection. The database is automatically loaded, saved, and closed.
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { withConnection, query, execute } from '@mark1russell7/docker-sqlite';
|
|
303
|
+
|
|
304
|
+
const users = await withConnection(
|
|
305
|
+
{ dbPath: './data/app.db' },
|
|
306
|
+
(db) => {
|
|
307
|
+
execute(db, 'INSERT INTO users (name) VALUES (?)', ['Alice']);
|
|
308
|
+
return query(db, 'SELECT * FROM users');
|
|
309
|
+
}
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
console.log(users.rows);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Parameters:**
|
|
316
|
+
- `config.dbPath`: Path to database file (use `':memory:'` for in-memory database)
|
|
317
|
+
- `callback`: Function receiving `Database` instance
|
|
318
|
+
|
|
319
|
+
**Returns:** Result of the callback function
|
|
320
|
+
|
|
321
|
+
**Behavior:**
|
|
322
|
+
- Saves the database after the callback completes, only when its content changed. A read does not write the file, so a read-only file can be read, and a read cannot write an old copy over the changes of another process.
|
|
323
|
+
- Makes the database file only when the callback changes the database. A query of a missing file does not make an empty file.
|
|
324
|
+
- Calls for one file run one at a time in this process. On Windows, two spellings of one path (case) share one queue.
|
|
325
|
+
- Writes a temporary file, flushes it to the disk and renames it over the database file. A crash or a power loss during the save cannot leave a truncated database.
|
|
326
|
+
- Closes database connection in finally block
|
|
327
|
+
- Creates parent directories as needed
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
#### `createConnection(config): Promise<{db, save}>`
|
|
332
|
+
|
|
333
|
+
Create a manually-managed connection. Caller must handle saving and closing.
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { createConnection, execute } from '@mark1russell7/docker-sqlite';
|
|
337
|
+
|
|
338
|
+
const { db, save } = await createConnection({ dbPath: './data/app.db' });
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
execute(db, 'INSERT INTO logs (message) VALUES (?)', ['Started']);
|
|
342
|
+
await save(); // Explicitly save changes
|
|
343
|
+
} finally {
|
|
344
|
+
db.close();
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Behavior:**
|
|
349
|
+
- `save()` writes the file only when the database changed, with the same atomic save as `withConnection`.
|
|
350
|
+
- `save()` runs in the queue of the file: one call at a time with `withConnection` and the other saves of this process. In a `withConnection` callback for the same file, it runs at once.
|
|
351
|
+
- `save()` throws a `DatabaseFileChangedError` (`code: "DATABASE_FILE_CHANGED"`) and writes nothing when the file changed after the connection loaded it or saved it last. Another connection or process wrote the file, and the old copy of this connection would replace those changes. After the error, close the connection, open a new one and make the change again.
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { createConnection, DatabaseFileChangedError, execute } from '@mark1russell7/docker-sqlite';
|
|
355
|
+
|
|
356
|
+
async function addLog(message: string): Promise<void> {
|
|
357
|
+
const { db, save } = await createConnection({ dbPath: './data/app.db' });
|
|
358
|
+
try {
|
|
359
|
+
execute(db, 'INSERT INTO logs (message) VALUES (?)', [message]);
|
|
360
|
+
await save();
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (error instanceof DatabaseFileChangedError) {
|
|
363
|
+
return addLog(message); // The file changed: load it again and repeat the change
|
|
364
|
+
}
|
|
365
|
+
throw error;
|
|
366
|
+
} finally {
|
|
367
|
+
db.close();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Query Operations
|
|
373
|
+
|
|
374
|
+
#### `query<T>(db, sql, params?): QueryResult<T>`
|
|
375
|
+
|
|
376
|
+
Execute a SELECT query and return typed results.
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { query } from '@mark1russell7/docker-sqlite';
|
|
380
|
+
|
|
381
|
+
interface User {
|
|
382
|
+
id: number;
|
|
383
|
+
name: string;
|
|
384
|
+
email: string;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const result = query<User>(
|
|
388
|
+
db,
|
|
389
|
+
'SELECT * FROM users WHERE age > ?',
|
|
390
|
+
[21]
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
console.log(result.columns); // ['id', 'name', 'email']
|
|
394
|
+
console.log(result.rows); // Array of User objects
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Parameters:**
|
|
398
|
+
- `db`: Database instance
|
|
399
|
+
- `sql`: SQL SELECT statement
|
|
400
|
+
- `params?`: Optional parameter values
|
|
401
|
+
|
|
402
|
+
**Returns:**
|
|
403
|
+
```typescript
|
|
404
|
+
{
|
|
405
|
+
columns: string[]; // Column names
|
|
406
|
+
rows: T[]; // Result rows as typed objects
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
`columns` has the column names also when there are no rows.
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
#### `execute(db, sql, params?): ExecuteResult`
|
|
415
|
+
|
|
416
|
+
Execute an INSERT, UPDATE, or DELETE statement.
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import { execute, lastInsertRowId } from '@mark1russell7/docker-sqlite';
|
|
420
|
+
|
|
421
|
+
const result = execute(
|
|
422
|
+
db,
|
|
423
|
+
'INSERT INTO users (name, email) VALUES (?, ?)',
|
|
424
|
+
['Bob', 'bob@example.com']
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
console.log(result.changes); // 1
|
|
428
|
+
console.log(lastInsertRowId(db)); // New row ID
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
SQL with several statements runs completely when there are no parameters, and `changes` counts the rows of all statements. With parameters, several statements are an error: parameters bind to one statement.
|
|
432
|
+
|
|
433
|
+
**Returns:**
|
|
434
|
+
```typescript
|
|
435
|
+
{
|
|
436
|
+
changes: number; // Number of rows affected
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
#### `execMultiple(db, sql): void`
|
|
443
|
+
|
|
444
|
+
Execute multiple SQL statements at once. Useful for schema setup and batch operations.
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
import { execMultiple } from '@mark1russell7/docker-sqlite';
|
|
448
|
+
|
|
449
|
+
execMultiple(db, `
|
|
450
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
451
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
452
|
+
name TEXT NOT NULL,
|
|
453
|
+
email TEXT UNIQUE
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
457
|
+
|
|
458
|
+
CREATE TABLE IF NOT EXISTS posts (
|
|
459
|
+
id INTEGER PRIMARY KEY,
|
|
460
|
+
user_id INTEGER,
|
|
461
|
+
title TEXT,
|
|
462
|
+
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
463
|
+
);
|
|
464
|
+
`);
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
#### `tableExists(db, tableName): boolean`
|
|
470
|
+
|
|
471
|
+
Check if a table exists in the database.
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
import { tableExists } from '@mark1russell7/docker-sqlite';
|
|
475
|
+
|
|
476
|
+
if (!tableExists(db, 'users')) {
|
|
477
|
+
execute(db, 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
#### `lastInsertRowId(db): number`
|
|
484
|
+
|
|
485
|
+
Get the row ID of the last inserted row.
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
import { execute, lastInsertRowId } from '@mark1russell7/docker-sqlite';
|
|
489
|
+
|
|
490
|
+
execute(db, 'INSERT INTO users (name) VALUES (?)', ['Alice']);
|
|
491
|
+
const userId = lastInsertRowId(db);
|
|
492
|
+
console.log(`Created user with ID: ${userId}`);
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Migration System
|
|
496
|
+
|
|
497
|
+
The package includes a complete migration system for versioned schema management.
|
|
498
|
+
|
|
499
|
+
### Migration Structure
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
interface Migration {
|
|
503
|
+
version: number; // Unique version number (sequential)
|
|
504
|
+
description: string; // Human-readable description
|
|
505
|
+
up: string; // SQL to apply migration
|
|
506
|
+
down?: string; // SQL to rollback migration (optional)
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### `runMigrations(db, migrations): Migration[]`
|
|
511
|
+
|
|
512
|
+
Apply all pending migrations in order.
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
import { withConnection, runMigrations } from '@mark1russell7/docker-sqlite';
|
|
516
|
+
|
|
517
|
+
const migrations = [
|
|
518
|
+
{
|
|
519
|
+
version: 1,
|
|
520
|
+
description: 'Create users table',
|
|
521
|
+
up: `
|
|
522
|
+
CREATE TABLE users (
|
|
523
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
524
|
+
name TEXT NOT NULL,
|
|
525
|
+
email TEXT UNIQUE
|
|
526
|
+
)
|
|
527
|
+
`,
|
|
528
|
+
down: 'DROP TABLE users'
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
version: 2,
|
|
532
|
+
description: 'Add created_at column',
|
|
533
|
+
up: 'ALTER TABLE users ADD COLUMN created_at TEXT DEFAULT CURRENT_TIMESTAMP',
|
|
534
|
+
down: 'ALTER TABLE users DROP COLUMN created_at'
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
version: 3,
|
|
538
|
+
description: 'Create posts table',
|
|
539
|
+
up: `
|
|
540
|
+
CREATE TABLE posts (
|
|
541
|
+
id INTEGER PRIMARY KEY,
|
|
542
|
+
user_id INTEGER,
|
|
543
|
+
title TEXT NOT NULL,
|
|
544
|
+
content TEXT,
|
|
545
|
+
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
546
|
+
)
|
|
547
|
+
`
|
|
548
|
+
}
|
|
549
|
+
];
|
|
550
|
+
|
|
551
|
+
await withConnection({ dbPath: './data/app.db' }, (db) => {
|
|
552
|
+
const applied = runMigrations(db, migrations);
|
|
553
|
+
console.log(`Applied ${applied.length} migrations`);
|
|
554
|
+
applied.forEach(m => console.log(` ✓ ${m.description}`));
|
|
555
|
+
});
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**Behavior:**
|
|
559
|
+
- Creates `_migrations` table automatically to track applied migrations
|
|
560
|
+
- Applies only pending migrations in version order
|
|
561
|
+
- Records each migration with timestamp
|
|
562
|
+
- Skips already-applied migrations
|
|
563
|
+
- Throws error if migration fails (doesn't continue)
|
|
564
|
+
- Runs each migration and its record in a savepoint. A migration that fails leaves no change, so a corrected migration applies on the next run. In a transaction of the caller, the savepoint keeps that transaction open.
|
|
565
|
+
- A migration whose SQL controls transactions itself (`BEGIN`, `COMMIT`, `END`, `ROLLBACK`, `SAVEPOINT`, `RELEASE`, `VACUUM`, `ATTACH`, `DETACH` or `PRAGMA`) runs without the savepoint: in a transaction, these statements fail or do nothing.
|
|
566
|
+
|
|
567
|
+
**Returns:** Array of applied migrations
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
### `getMigrationStatus(db, migrations): MigrationStatus`
|
|
572
|
+
|
|
573
|
+
Get the current migration status.
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
import { getMigrationStatus } from '@mark1russell7/docker-sqlite';
|
|
577
|
+
|
|
578
|
+
const status = getMigrationStatus(db, migrations);
|
|
579
|
+
|
|
580
|
+
console.log(`Current version: ${status.currentVersion}`);
|
|
581
|
+
console.log(`Applied migrations: ${status.appliedMigrations.join(', ')}`);
|
|
582
|
+
console.log(`Pending migrations: ${status.pendingMigrations.length}`);
|
|
583
|
+
|
|
584
|
+
status.pendingMigrations.forEach(m => {
|
|
585
|
+
console.log(` - v${m.version}: ${m.description}`);
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Returns:**
|
|
590
|
+
```typescript
|
|
591
|
+
{
|
|
592
|
+
currentVersion: number; // Latest applied version (0 if none)
|
|
593
|
+
appliedMigrations: number[]; // Array of applied version numbers
|
|
594
|
+
pendingMigrations: Migration[]; // Migrations not yet applied
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
### `rollbackMigration(db, migrations): Migration | null`
|
|
601
|
+
|
|
602
|
+
Rollback the last applied migration.
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
import { rollbackMigration } from '@mark1russell7/docker-sqlite';
|
|
606
|
+
|
|
607
|
+
const rolled = rollbackMigration(db, migrations);
|
|
608
|
+
|
|
609
|
+
if (rolled) {
|
|
610
|
+
console.log(`Rolled back: v${rolled.version} - ${rolled.description}`);
|
|
611
|
+
} else {
|
|
612
|
+
console.log('No migrations to rollback');
|
|
613
|
+
}
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**Requirements:**
|
|
617
|
+
- Migration must have a `down` script
|
|
618
|
+
- Migration must be the current version
|
|
619
|
+
|
|
620
|
+
The `down` script and the removal of its record run in a savepoint, with the same rules as `runMigrations`.
|
|
621
|
+
|
|
622
|
+
**Returns:** The rolled-back migration, or `null` if none
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
### `createMigration(version, description, up, down?): Migration`
|
|
627
|
+
|
|
628
|
+
Helper function to create a migration object.
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
import { createMigration } from '@mark1russell7/docker-sqlite';
|
|
632
|
+
|
|
633
|
+
const migration = createMigration(
|
|
634
|
+
1,
|
|
635
|
+
'Initial schema',
|
|
636
|
+
'CREATE TABLE users (id INTEGER PRIMARY KEY)',
|
|
637
|
+
'DROP TABLE users'
|
|
638
|
+
);
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
## Configuration
|
|
642
|
+
|
|
643
|
+
### Database Paths
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
// In-memory database (no persistence)
|
|
647
|
+
await withConnection({ dbPath: ':memory:' }, (db) => {
|
|
648
|
+
// Data is lost after connection closes
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// Relative path
|
|
652
|
+
await withConnection({ dbPath: './data/app.db' }, (db) => {
|
|
653
|
+
// Saved to ./data/app.db
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// Absolute path
|
|
657
|
+
await withConnection({ dbPath: '/var/lib/myapp/app.db' }, (db) => {
|
|
658
|
+
// Saved to /var/lib/myapp/app.db
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
// Default (in-memory)
|
|
662
|
+
await withConnection({}, (db) => {
|
|
663
|
+
// No persistence
|
|
664
|
+
});
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Common Patterns
|
|
668
|
+
|
|
669
|
+
#### CLI Logging Database
|
|
670
|
+
|
|
671
|
+
The package includes a reference schema for CLI logging in `schema/logs.sql`:
|
|
672
|
+
|
|
673
|
+
```typescript
|
|
674
|
+
import { withConnection, execMultiple } from '@mark1russell7/docker-sqlite';
|
|
675
|
+
import { readFile } from 'node:fs/promises';
|
|
676
|
+
|
|
677
|
+
const schema = await readFile('./node_modules/@mark1russell7/docker-sqlite/schema/logs.sql', 'utf-8');
|
|
678
|
+
|
|
679
|
+
await withConnection({ dbPath: './cli-logs.db' }, (db) => {
|
|
680
|
+
execMultiple(db, schema);
|
|
681
|
+
|
|
682
|
+
execute(db, `
|
|
683
|
+
INSERT INTO logs (level, message, command, session_id)
|
|
684
|
+
VALUES (?, ?, ?, ?)
|
|
685
|
+
`, ['info', 'Command started', 'lib.refresh', sessionId]);
|
|
686
|
+
});
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
#### Testing Pattern
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
import { describe, it } from 'node:test';
|
|
693
|
+
import assert from 'node:assert';
|
|
694
|
+
import { withConnection, query, execute } from '@mark1russell7/docker-sqlite';
|
|
695
|
+
|
|
696
|
+
describe('User repository', () => {
|
|
697
|
+
it('should create and retrieve user', async () => {
|
|
698
|
+
await withConnection({ dbPath: ':memory:' }, (db) => {
|
|
699
|
+
// Setup schema
|
|
700
|
+
execute(db, 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
|
|
701
|
+
|
|
702
|
+
// Test
|
|
703
|
+
execute(db, 'INSERT INTO users (name) VALUES (?)', ['Alice']);
|
|
704
|
+
const result = query(db, 'SELECT * FROM users WHERE name = ?', ['Alice']);
|
|
705
|
+
|
|
706
|
+
assert.strictEqual(result.rows.length, 1);
|
|
707
|
+
assert.strictEqual(result.rows[0].name, 'Alice');
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
#### Migration-based Setup
|
|
714
|
+
|
|
715
|
+
```typescript
|
|
716
|
+
import { withConnection, runMigrations, getMigrationStatus } from '@mark1russell7/docker-sqlite';
|
|
717
|
+
|
|
718
|
+
const migrations = [
|
|
719
|
+
{
|
|
720
|
+
version: 1,
|
|
721
|
+
description: 'Initial schema',
|
|
722
|
+
up: `
|
|
723
|
+
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
|
|
724
|
+
CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER);
|
|
725
|
+
`
|
|
726
|
+
}
|
|
727
|
+
];
|
|
728
|
+
|
|
729
|
+
async function initDatabase(dbPath: string) {
|
|
730
|
+
await withConnection({ dbPath }, (db) => {
|
|
731
|
+
const status = getMigrationStatus(db, migrations);
|
|
732
|
+
|
|
733
|
+
if (status.pendingMigrations.length > 0) {
|
|
734
|
+
console.log(`Applying ${status.pendingMigrations.length} migration(s)...`);
|
|
735
|
+
runMigrations(db, migrations);
|
|
736
|
+
console.log('Database up to date!');
|
|
737
|
+
} else {
|
|
738
|
+
console.log(`Database already at version ${status.currentVersion}`);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
await initDatabase('./data/app.db');
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
## Integration
|
|
747
|
+
|
|
748
|
+
### Environment Configuration
|
|
749
|
+
|
|
750
|
+
Create a `.env` file for your application:
|
|
751
|
+
|
|
752
|
+
```env
|
|
753
|
+
# SQLite database path
|
|
754
|
+
SQLITE_DB_PATH=./data/app.db
|
|
755
|
+
|
|
756
|
+
# Docker Web UI settings (optional)
|
|
757
|
+
SQLITE_CONTAINER_NAME=myapp-sqlite-ui
|
|
758
|
+
SQLITE_PORT=8080
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
Load in your application:
|
|
762
|
+
|
|
763
|
+
```typescript
|
|
764
|
+
import 'dotenv/config';
|
|
765
|
+
import { withConnection } from '@mark1russell7/docker-sqlite';
|
|
766
|
+
|
|
767
|
+
const dbPath = process.env.SQLITE_DB_PATH || './app.db';
|
|
768
|
+
|
|
769
|
+
await withConnection({ dbPath }, (db) => {
|
|
770
|
+
// Your database operations
|
|
771
|
+
});
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### npm Scripts
|
|
775
|
+
|
|
776
|
+
Add to your `package.json`:
|
|
777
|
+
|
|
778
|
+
```json
|
|
779
|
+
{
|
|
780
|
+
"scripts": {
|
|
781
|
+
"db:ui": "docker compose -f node_modules/@mark1russell7/docker-sqlite/docker-compose.yml up -d",
|
|
782
|
+
"db:ui:stop": "docker compose -f node_modules/@mark1russell7/docker-sqlite/docker-compose.yml down",
|
|
783
|
+
"db:migrate": "tsx scripts/migrate.ts",
|
|
784
|
+
"db:rollback": "tsx scripts/rollback.ts"
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
### Ecosystem Integration
|
|
790
|
+
|
|
791
|
+
This package integrates with the broader Mark Russell ecosystem:
|
|
792
|
+
|
|
793
|
+
- **@mark1russell7/cue**: TypeScript configuration inheritance
|
|
794
|
+
- **client-sqlite**: MCP client procedures for SQLite operations
|
|
795
|
+
- **bundle-dev**: Development bundle including database tooling
|
|
796
|
+
|
|
797
|
+
## Examples
|
|
798
|
+
|
|
799
|
+
### Complete Application with Migrations
|
|
800
|
+
|
|
801
|
+
```typescript
|
|
802
|
+
import { withConnection, runMigrations, query, execute } from '@mark1russell7/docker-sqlite';
|
|
803
|
+
|
|
804
|
+
// Define schema migrations
|
|
805
|
+
const migrations = [
|
|
806
|
+
{
|
|
807
|
+
version: 1,
|
|
808
|
+
description: 'Create users table',
|
|
809
|
+
up: `
|
|
810
|
+
CREATE TABLE users (
|
|
811
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
812
|
+
username TEXT UNIQUE NOT NULL,
|
|
813
|
+
email TEXT UNIQUE NOT NULL,
|
|
814
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
815
|
+
);
|
|
816
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
817
|
+
`,
|
|
818
|
+
down: 'DROP TABLE users'
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
version: 2,
|
|
822
|
+
description: 'Create posts table',
|
|
823
|
+
up: `
|
|
824
|
+
CREATE TABLE posts (
|
|
825
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
826
|
+
user_id INTEGER NOT NULL,
|
|
827
|
+
title TEXT NOT NULL,
|
|
828
|
+
content TEXT,
|
|
829
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
830
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
831
|
+
);
|
|
832
|
+
CREATE INDEX idx_posts_user ON posts(user_id);
|
|
833
|
+
`,
|
|
834
|
+
down: 'DROP TABLE posts'
|
|
835
|
+
}
|
|
836
|
+
];
|
|
837
|
+
|
|
838
|
+
// Application setup
|
|
839
|
+
async function setupDatabase(dbPath: string) {
|
|
840
|
+
await withConnection({ dbPath }, (db) => {
|
|
841
|
+
// Apply migrations
|
|
842
|
+
const applied = runMigrations(db, migrations);
|
|
843
|
+
if (applied.length > 0) {
|
|
844
|
+
console.log(`Applied ${applied.length} migration(s)`);
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// User operations
|
|
850
|
+
interface User {
|
|
851
|
+
id: number;
|
|
852
|
+
username: string;
|
|
853
|
+
email: string;
|
|
854
|
+
created_at: string;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
async function createUser(dbPath: string, username: string, email: string): Promise<number> {
|
|
858
|
+
return withConnection({ dbPath }, (db) => {
|
|
859
|
+
execute(db, 'INSERT INTO users (username, email) VALUES (?, ?)', [username, email]);
|
|
860
|
+
return lastInsertRowId(db);
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
async function getUsers(dbPath: string): Promise<User[]> {
|
|
865
|
+
return withConnection({ dbPath }, (db) => {
|
|
866
|
+
const result = query<User>(db, 'SELECT * FROM users ORDER BY created_at DESC');
|
|
867
|
+
return result.rows;
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Usage
|
|
872
|
+
const dbPath = './data/myapp.db';
|
|
873
|
+
|
|
874
|
+
await setupDatabase(dbPath);
|
|
875
|
+
const userId = await createUser(dbPath, 'alice', 'alice@example.com');
|
|
876
|
+
const users = await getUsers(dbPath);
|
|
877
|
+
|
|
878
|
+
console.log(`Created user ID: ${userId}`);
|
|
879
|
+
console.log(`Total users: ${users.length}`);
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### Transaction Pattern (Manual Connection)
|
|
883
|
+
|
|
884
|
+
```typescript
|
|
885
|
+
import { createConnection, execute } from '@mark1russell7/docker-sqlite';
|
|
886
|
+
|
|
887
|
+
async function transferCredits(fromUserId: number, toUserId: number, amount: number) {
|
|
888
|
+
const { db, save } = await createConnection({ dbPath: './data/app.db' });
|
|
889
|
+
|
|
890
|
+
try {
|
|
891
|
+
// Start transaction
|
|
892
|
+
execute(db, 'BEGIN TRANSACTION');
|
|
893
|
+
|
|
894
|
+
// Deduct from sender
|
|
895
|
+
execute(db, 'UPDATE users SET credits = credits - ? WHERE id = ?', [amount, fromUserId]);
|
|
896
|
+
|
|
897
|
+
// Check sender balance
|
|
898
|
+
const result = query(db, 'SELECT credits FROM users WHERE id = ?', [fromUserId]);
|
|
899
|
+
if (result.rows[0].credits < 0) {
|
|
900
|
+
throw new Error('Insufficient credits');
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Add to receiver
|
|
904
|
+
execute(db, 'UPDATE users SET credits = credits + ? WHERE id = ?', [amount, toUserId]);
|
|
905
|
+
|
|
906
|
+
// Commit
|
|
907
|
+
execute(db, 'COMMIT');
|
|
908
|
+
await save();
|
|
909
|
+
|
|
910
|
+
console.log(`Transferred ${amount} credits from user ${fromUserId} to ${toUserId}`);
|
|
911
|
+
} catch (error) {
|
|
912
|
+
// Rollback on error
|
|
913
|
+
execute(db, 'ROLLBACK');
|
|
914
|
+
throw error;
|
|
915
|
+
} finally {
|
|
916
|
+
db.close();
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
### Query Builder Pattern
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
import { withConnection, query } from '@mark1russell7/docker-sqlite';
|
|
925
|
+
|
|
926
|
+
interface QueryBuilder {
|
|
927
|
+
select(columns: string[]): this;
|
|
928
|
+
from(table: string): this;
|
|
929
|
+
where(condition: string, params: unknown[]): this;
|
|
930
|
+
orderBy(column: string, direction?: 'ASC' | 'DESC'): this;
|
|
931
|
+
limit(limit: number): this;
|
|
932
|
+
execute<T>(): QueryResult<T>;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
class SQLiteQueryBuilder implements QueryBuilder {
|
|
936
|
+
private parts: {
|
|
937
|
+
select: string[];
|
|
938
|
+
from: string;
|
|
939
|
+
where: { condition: string; params: unknown[] }[];
|
|
940
|
+
orderBy?: string;
|
|
941
|
+
limit?: number;
|
|
942
|
+
} = { select: ['*'], from: '', where: [] };
|
|
943
|
+
|
|
944
|
+
constructor(private db: Database) {}
|
|
945
|
+
|
|
946
|
+
select(columns: string[]): this {
|
|
947
|
+
this.parts.select = columns;
|
|
948
|
+
return this;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
from(table: string): this {
|
|
952
|
+
this.parts.from = table;
|
|
953
|
+
return this;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
where(condition: string, params: unknown[]): this {
|
|
957
|
+
this.parts.where.push({ condition, params });
|
|
958
|
+
return this;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
|
|
962
|
+
this.parts.orderBy = `${column} ${direction}`;
|
|
963
|
+
return this;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
limit(limit: number): this {
|
|
967
|
+
this.parts.limit = limit;
|
|
968
|
+
return this;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
execute<T>(): QueryResult<T> {
|
|
972
|
+
let sql = `SELECT ${this.parts.select.join(', ')} FROM ${this.parts.from}`;
|
|
973
|
+
const params: unknown[] = [];
|
|
974
|
+
|
|
975
|
+
if (this.parts.where.length > 0) {
|
|
976
|
+
sql += ' WHERE ' + this.parts.where.map(w => w.condition).join(' AND ');
|
|
977
|
+
this.parts.where.forEach(w => params.push(...w.params));
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (this.parts.orderBy) {
|
|
981
|
+
sql += ` ORDER BY ${this.parts.orderBy}`;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (this.parts.limit) {
|
|
985
|
+
sql += ` LIMIT ${this.parts.limit}`;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return query<T>(this.db, sql, params);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// Usage
|
|
993
|
+
await withConnection({ dbPath: './data/app.db' }, (db) => {
|
|
994
|
+
const builder = new SQLiteQueryBuilder(db);
|
|
995
|
+
|
|
996
|
+
const result = builder
|
|
997
|
+
.select(['id', 'username', 'email'])
|
|
998
|
+
.from('users')
|
|
999
|
+
.where('created_at > ?', ['2024-01-01'])
|
|
1000
|
+
.where('email LIKE ?', ['%@example.com'])
|
|
1001
|
+
.orderBy('created_at', 'DESC')
|
|
1002
|
+
.limit(10)
|
|
1003
|
+
.execute<User>();
|
|
1004
|
+
|
|
1005
|
+
console.log(result.rows);
|
|
1006
|
+
});
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
## API Reference
|
|
1010
|
+
|
|
1011
|
+
### Types
|
|
1012
|
+
|
|
1013
|
+
```typescript
|
|
1014
|
+
interface ConnectionConfig {
|
|
1015
|
+
dbPath: string; // Path to database file, or ':memory:' for in-memory
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
type ConnectionCallback<T> = (db: Database) => T | Promise<T>;
|
|
1019
|
+
|
|
1020
|
+
interface QueryResult<T = Record<string, unknown>> {
|
|
1021
|
+
columns: string[]; // Column names
|
|
1022
|
+
rows: T[]; // Result rows as typed objects
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
interface ExecuteResult {
|
|
1026
|
+
changes: number; // Number of rows affected
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
interface Migration {
|
|
1030
|
+
version: number; // Unique version number (sequential)
|
|
1031
|
+
description: string; // Human-readable description
|
|
1032
|
+
up: string; // SQL to apply migration
|
|
1033
|
+
down?: string; // SQL to rollback migration (optional)
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
interface MigrationStatus {
|
|
1037
|
+
currentVersion: number; // Latest applied version (0 if none)
|
|
1038
|
+
appliedMigrations: number[]; // Array of applied version numbers
|
|
1039
|
+
pendingMigrations: Migration[]; // Migrations not yet applied
|
|
1040
|
+
}
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
### Functions
|
|
1044
|
+
|
|
1045
|
+
All functions are fully documented with JSDoc and include TypeScript type definitions. Import from the package root:
|
|
1046
|
+
|
|
1047
|
+
```typescript
|
|
1048
|
+
import {
|
|
1049
|
+
// Connection
|
|
1050
|
+
withConnection,
|
|
1051
|
+
createConnection,
|
|
1052
|
+
DatabaseFileChangedError,
|
|
1053
|
+
|
|
1054
|
+
// Query
|
|
1055
|
+
query,
|
|
1056
|
+
execute,
|
|
1057
|
+
execMultiple,
|
|
1058
|
+
tableExists,
|
|
1059
|
+
lastInsertRowId,
|
|
1060
|
+
|
|
1061
|
+
// Migration
|
|
1062
|
+
runMigrations,
|
|
1063
|
+
rollbackMigration,
|
|
1064
|
+
getMigrationStatus,
|
|
1065
|
+
createMigration,
|
|
1066
|
+
|
|
1067
|
+
// Types
|
|
1068
|
+
type ConnectionConfig,
|
|
1069
|
+
type QueryResult,
|
|
1070
|
+
type Migration,
|
|
1071
|
+
type MigrationStatus,
|
|
1072
|
+
type Database,
|
|
1073
|
+
// ... etc
|
|
1074
|
+
} from '@mark1russell7/docker-sqlite';
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
## License
|
|
1078
|
+
|
|
1079
|
+
MIT
|
|
1080
|
+
|
|
1081
|
+
## Contributing
|
|
1082
|
+
|
|
1083
|
+
Issues and pull requests are welcome at the package repository.
|
|
1084
|
+
|
|
1085
|
+
## Related Packages
|
|
1086
|
+
|
|
1087
|
+
- [@mark1russell7/docker-mongo](../mongo) - MongoDB Docker setup with similar API
|
|
1088
|
+
- [@mark1russell7/cue](https://github.com/mark1russell7/cue) - Shared TypeScript configuration
|
|
1089
|
+
- [sql.js](https://github.com/sql-js/sql.js) - SQLite compiled to JavaScript/WASM
|