@mark1russell7/docker-sqlite 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,1057 +1,1064 @@
1
- # @mark1russell7/docker-sqlite
2
-
3
- [![npm version](https://img.shields.io/npm/v/@mark1russell7/docker-sqlite.svg)](https://www.npmjs.com/package/@mark1russell7/docker-sqlite)
4
- [![License: MIT](https://img.shields.io/npm/l/@mark1russell7/docker-sqlite.svg)](./LICENSE)
5
- [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D22-green.svg)](https://nodejs.org/)
6
- [![Part of client](https://img.shields.io/badge/part%20of-client-1f3fbf)](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
- - Closes database connection in finally block
326
- - Creates parent directories as needed
327
-
328
- ---
329
-
330
- #### `createConnection(config): Promise<{db, save}>`
331
-
332
- Create a manually-managed connection. Caller must handle saving and closing.
333
-
334
- ```typescript
335
- import { createConnection, execute } from '@mark1russell7/docker-sqlite';
336
-
337
- const { db, save } = await createConnection({ dbPath: './data/app.db' });
338
-
339
- try {
340
- execute(db, 'INSERT INTO logs (message) VALUES (?)', ['Started']);
341
- await save(); // Explicitly save changes
342
- } finally {
343
- db.close();
344
- }
345
- ```
346
-
347
- ### Query Operations
348
-
349
- #### `query<T>(db, sql, params?): QueryResult<T>`
350
-
351
- Execute a SELECT query and return typed results.
352
-
353
- ```typescript
354
- import { query } from '@mark1russell7/docker-sqlite';
355
-
356
- interface User {
357
- id: number;
358
- name: string;
359
- email: string;
360
- }
361
-
362
- const result = query<User>(
363
- db,
364
- 'SELECT * FROM users WHERE age > ?',
365
- [21]
366
- );
367
-
368
- console.log(result.columns); // ['id', 'name', 'email']
369
- console.log(result.rows); // Array of User objects
370
- ```
371
-
372
- **Parameters:**
373
- - `db`: Database instance
374
- - `sql`: SQL SELECT statement
375
- - `params?`: Optional parameter values
376
-
377
- **Returns:**
378
- ```typescript
379
- {
380
- columns: string[]; // Column names
381
- rows: T[]; // Result rows as typed objects
382
- }
383
- ```
384
-
385
- ---
386
-
387
- #### `execute(db, sql, params?): ExecuteResult`
388
-
389
- Execute an INSERT, UPDATE, or DELETE statement.
390
-
391
- ```typescript
392
- import { execute, lastInsertRowId } from '@mark1russell7/docker-sqlite';
393
-
394
- const result = execute(
395
- db,
396
- 'INSERT INTO users (name, email) VALUES (?, ?)',
397
- ['Bob', 'bob@example.com']
398
- );
399
-
400
- console.log(result.changes); // 1
401
- console.log(lastInsertRowId(db)); // New row ID
402
- ```
403
-
404
- 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.
405
-
406
- **Returns:**
407
- ```typescript
408
- {
409
- changes: number; // Number of rows affected
410
- }
411
- ```
412
-
413
- ---
414
-
415
- #### `execMultiple(db, sql): void`
416
-
417
- Execute multiple SQL statements at once. Useful for schema setup and batch operations.
418
-
419
- ```typescript
420
- import { execMultiple } from '@mark1russell7/docker-sqlite';
421
-
422
- execMultiple(db, `
423
- CREATE TABLE IF NOT EXISTS users (
424
- id INTEGER PRIMARY KEY AUTOINCREMENT,
425
- name TEXT NOT NULL,
426
- email TEXT UNIQUE
427
- );
428
-
429
- CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
430
-
431
- CREATE TABLE IF NOT EXISTS posts (
432
- id INTEGER PRIMARY KEY,
433
- user_id INTEGER,
434
- title TEXT,
435
- FOREIGN KEY (user_id) REFERENCES users(id)
436
- );
437
- `);
438
- ```
439
-
440
- ---
441
-
442
- #### `tableExists(db, tableName): boolean`
443
-
444
- Check if a table exists in the database.
445
-
446
- ```typescript
447
- import { tableExists } from '@mark1russell7/docker-sqlite';
448
-
449
- if (!tableExists(db, 'users')) {
450
- execute(db, 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
451
- }
452
- ```
453
-
454
- ---
455
-
456
- #### `lastInsertRowId(db): number`
457
-
458
- Get the row ID of the last inserted row.
459
-
460
- ```typescript
461
- import { execute, lastInsertRowId } from '@mark1russell7/docker-sqlite';
462
-
463
- execute(db, 'INSERT INTO users (name) VALUES (?)', ['Alice']);
464
- const userId = lastInsertRowId(db);
465
- console.log(`Created user with ID: ${userId}`);
466
- ```
467
-
468
- ## Migration System
469
-
470
- The package includes a complete migration system for versioned schema management.
471
-
472
- ### Migration Structure
473
-
474
- ```typescript
475
- interface Migration {
476
- version: number; // Unique version number (sequential)
477
- description: string; // Human-readable description
478
- up: string; // SQL to apply migration
479
- down?: string; // SQL to rollback migration (optional)
480
- }
481
- ```
482
-
483
- ### `runMigrations(db, migrations): Migration[]`
484
-
485
- Apply all pending migrations in order.
486
-
487
- ```typescript
488
- import { withConnection, runMigrations } from '@mark1russell7/docker-sqlite';
489
-
490
- const migrations = [
491
- {
492
- version: 1,
493
- description: 'Create users table',
494
- up: `
495
- CREATE TABLE users (
496
- id INTEGER PRIMARY KEY AUTOINCREMENT,
497
- name TEXT NOT NULL,
498
- email TEXT UNIQUE
499
- )
500
- `,
501
- down: 'DROP TABLE users'
502
- },
503
- {
504
- version: 2,
505
- description: 'Add created_at column',
506
- up: 'ALTER TABLE users ADD COLUMN created_at TEXT DEFAULT CURRENT_TIMESTAMP',
507
- down: 'ALTER TABLE users DROP COLUMN created_at'
508
- },
509
- {
510
- version: 3,
511
- description: 'Create posts table',
512
- up: `
513
- CREATE TABLE posts (
514
- id INTEGER PRIMARY KEY,
515
- user_id INTEGER,
516
- title TEXT NOT NULL,
517
- content TEXT,
518
- FOREIGN KEY (user_id) REFERENCES users(id)
519
- )
520
- `
521
- }
522
- ];
523
-
524
- await withConnection({ dbPath: './data/app.db' }, (db) => {
525
- const applied = runMigrations(db, migrations);
526
- console.log(`Applied ${applied.length} migrations`);
527
- applied.forEach(m => console.log(` ✓ ${m.description}`));
528
- });
529
- ```
530
-
531
- **Behavior:**
532
- - Creates `_migrations` table automatically to track applied migrations
533
- - Applies only pending migrations in version order
534
- - Records each migration with timestamp
535
- - Skips already-applied migrations
536
- - Throws error if migration fails (doesn't continue)
537
-
538
- **Returns:** Array of applied migrations
539
-
540
- ---
541
-
542
- ### `getMigrationStatus(db, migrations): MigrationStatus`
543
-
544
- Get the current migration status.
545
-
546
- ```typescript
547
- import { getMigrationStatus } from '@mark1russell7/docker-sqlite';
548
-
549
- const status = getMigrationStatus(db, migrations);
550
-
551
- console.log(`Current version: ${status.currentVersion}`);
552
- console.log(`Applied migrations: ${status.appliedMigrations.join(', ')}`);
553
- console.log(`Pending migrations: ${status.pendingMigrations.length}`);
554
-
555
- status.pendingMigrations.forEach(m => {
556
- console.log(` - v${m.version}: ${m.description}`);
557
- });
558
- ```
559
-
560
- **Returns:**
561
- ```typescript
562
- {
563
- currentVersion: number; // Latest applied version (0 if none)
564
- appliedMigrations: number[]; // Array of applied version numbers
565
- pendingMigrations: Migration[]; // Migrations not yet applied
566
- }
567
- ```
568
-
569
- ---
570
-
571
- ### `rollbackMigration(db, migrations): Migration | null`
572
-
573
- Rollback the last applied migration.
574
-
575
- ```typescript
576
- import { rollbackMigration } from '@mark1russell7/docker-sqlite';
577
-
578
- const rolled = rollbackMigration(db, migrations);
579
-
580
- if (rolled) {
581
- console.log(`Rolled back: v${rolled.version} - ${rolled.description}`);
582
- } else {
583
- console.log('No migrations to rollback');
584
- }
585
- ```
586
-
587
- **Requirements:**
588
- - Migration must have a `down` script
589
- - Migration must be the current version
590
-
591
- **Returns:** The rolled-back migration, or `null` if none
592
-
593
- ---
594
-
595
- ### `createMigration(version, description, up, down?): Migration`
596
-
597
- Helper function to create a migration object.
598
-
599
- ```typescript
600
- import { createMigration } from '@mark1russell7/docker-sqlite';
601
-
602
- const migration = createMigration(
603
- 1,
604
- 'Initial schema',
605
- 'CREATE TABLE users (id INTEGER PRIMARY KEY)',
606
- 'DROP TABLE users'
607
- );
608
- ```
609
-
610
- ## Configuration
611
-
612
- ### Database Paths
613
-
614
- ```typescript
615
- // In-memory database (no persistence)
616
- await withConnection({ dbPath: ':memory:' }, (db) => {
617
- // Data is lost after connection closes
618
- });
619
-
620
- // Relative path
621
- await withConnection({ dbPath: './data/app.db' }, (db) => {
622
- // Saved to ./data/app.db
623
- });
624
-
625
- // Absolute path
626
- await withConnection({ dbPath: '/var/lib/myapp/app.db' }, (db) => {
627
- // Saved to /var/lib/myapp/app.db
628
- });
629
-
630
- // Default (in-memory)
631
- await withConnection({}, (db) => {
632
- // No persistence
633
- });
634
- ```
635
-
636
- ### Common Patterns
637
-
638
- #### CLI Logging Database
639
-
640
- The package includes a reference schema for CLI logging in `schema/logs.sql`:
641
-
642
- ```typescript
643
- import { withConnection, execMultiple } from '@mark1russell7/docker-sqlite';
644
- import { readFile } from 'node:fs/promises';
645
-
646
- const schema = await readFile('./node_modules/@mark1russell7/docker-sqlite/schema/logs.sql', 'utf-8');
647
-
648
- await withConnection({ dbPath: './cli-logs.db' }, (db) => {
649
- execMultiple(db, schema);
650
-
651
- execute(db, `
652
- INSERT INTO logs (level, message, command, session_id)
653
- VALUES (?, ?, ?, ?)
654
- `, ['info', 'Command started', 'lib.refresh', sessionId]);
655
- });
656
- ```
657
-
658
- #### Testing Pattern
659
-
660
- ```typescript
661
- import { describe, it } from 'node:test';
662
- import assert from 'node:assert';
663
- import { withConnection, query, execute } from '@mark1russell7/docker-sqlite';
664
-
665
- describe('User repository', () => {
666
- it('should create and retrieve user', async () => {
667
- await withConnection({ dbPath: ':memory:' }, (db) => {
668
- // Setup schema
669
- execute(db, 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
670
-
671
- // Test
672
- execute(db, 'INSERT INTO users (name) VALUES (?)', ['Alice']);
673
- const result = query(db, 'SELECT * FROM users WHERE name = ?', ['Alice']);
674
-
675
- assert.strictEqual(result.rows.length, 1);
676
- assert.strictEqual(result.rows[0].name, 'Alice');
677
- });
678
- });
679
- });
680
- ```
681
-
682
- #### Migration-based Setup
683
-
684
- ```typescript
685
- import { withConnection, runMigrations, getMigrationStatus } from '@mark1russell7/docker-sqlite';
686
-
687
- const migrations = [
688
- {
689
- version: 1,
690
- description: 'Initial schema',
691
- up: `
692
- CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
693
- CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER);
694
- `
695
- }
696
- ];
697
-
698
- async function initDatabase(dbPath: string) {
699
- await withConnection({ dbPath }, (db) => {
700
- const status = getMigrationStatus(db, migrations);
701
-
702
- if (status.pendingMigrations.length > 0) {
703
- console.log(`Applying ${status.pendingMigrations.length} migration(s)...`);
704
- runMigrations(db, migrations);
705
- console.log('Database up to date!');
706
- } else {
707
- console.log(`Database already at version ${status.currentVersion}`);
708
- }
709
- });
710
- }
711
-
712
- await initDatabase('./data/app.db');
713
- ```
714
-
715
- ## Integration
716
-
717
- ### Environment Configuration
718
-
719
- Create a `.env` file for your application:
720
-
721
- ```env
722
- # SQLite database path
723
- SQLITE_DB_PATH=./data/app.db
724
-
725
- # Docker Web UI settings (optional)
726
- SQLITE_CONTAINER_NAME=myapp-sqlite-ui
727
- SQLITE_PORT=8080
728
- ```
729
-
730
- Load in your application:
731
-
732
- ```typescript
733
- import 'dotenv/config';
734
- import { withConnection } from '@mark1russell7/docker-sqlite';
735
-
736
- const dbPath = process.env.SQLITE_DB_PATH || './app.db';
737
-
738
- await withConnection({ dbPath }, (db) => {
739
- // Your database operations
740
- });
741
- ```
742
-
743
- ### npm Scripts
744
-
745
- Add to your `package.json`:
746
-
747
- ```json
748
- {
749
- "scripts": {
750
- "db:ui": "docker compose -f node_modules/@mark1russell7/docker-sqlite/docker-compose.yml up -d",
751
- "db:ui:stop": "docker compose -f node_modules/@mark1russell7/docker-sqlite/docker-compose.yml down",
752
- "db:migrate": "tsx scripts/migrate.ts",
753
- "db:rollback": "tsx scripts/rollback.ts"
754
- }
755
- }
756
- ```
757
-
758
- ### Ecosystem Integration
759
-
760
- This package integrates with the broader Mark Russell ecosystem:
761
-
762
- - **@mark1russell7/cue**: TypeScript configuration inheritance
763
- - **client-sqlite**: MCP client procedures for SQLite operations
764
- - **bundle-dev**: Development bundle including database tooling
765
-
766
- ## Examples
767
-
768
- ### Complete Application with Migrations
769
-
770
- ```typescript
771
- import { withConnection, runMigrations, query, execute } from '@mark1russell7/docker-sqlite';
772
-
773
- // Define schema migrations
774
- const migrations = [
775
- {
776
- version: 1,
777
- description: 'Create users table',
778
- up: `
779
- CREATE TABLE users (
780
- id INTEGER PRIMARY KEY AUTOINCREMENT,
781
- username TEXT UNIQUE NOT NULL,
782
- email TEXT UNIQUE NOT NULL,
783
- created_at TEXT DEFAULT CURRENT_TIMESTAMP
784
- );
785
- CREATE INDEX idx_users_email ON users(email);
786
- `,
787
- down: 'DROP TABLE users'
788
- },
789
- {
790
- version: 2,
791
- description: 'Create posts table',
792
- up: `
793
- CREATE TABLE posts (
794
- id INTEGER PRIMARY KEY AUTOINCREMENT,
795
- user_id INTEGER NOT NULL,
796
- title TEXT NOT NULL,
797
- content TEXT,
798
- created_at TEXT DEFAULT CURRENT_TIMESTAMP,
799
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
800
- );
801
- CREATE INDEX idx_posts_user ON posts(user_id);
802
- `,
803
- down: 'DROP TABLE posts'
804
- }
805
- ];
806
-
807
- // Application setup
808
- async function setupDatabase(dbPath: string) {
809
- await withConnection({ dbPath }, (db) => {
810
- // Apply migrations
811
- const applied = runMigrations(db, migrations);
812
- if (applied.length > 0) {
813
- console.log(`Applied ${applied.length} migration(s)`);
814
- }
815
- });
816
- }
817
-
818
- // User operations
819
- interface User {
820
- id: number;
821
- username: string;
822
- email: string;
823
- created_at: string;
824
- }
825
-
826
- async function createUser(dbPath: string, username: string, email: string): Promise<number> {
827
- return withConnection({ dbPath }, (db) => {
828
- execute(db, 'INSERT INTO users (username, email) VALUES (?, ?)', [username, email]);
829
- return lastInsertRowId(db);
830
- });
831
- }
832
-
833
- async function getUsers(dbPath: string): Promise<User[]> {
834
- return withConnection({ dbPath }, (db) => {
835
- const result = query<User>(db, 'SELECT * FROM users ORDER BY created_at DESC');
836
- return result.rows;
837
- });
838
- }
839
-
840
- // Usage
841
- const dbPath = './data/myapp.db';
842
-
843
- await setupDatabase(dbPath);
844
- const userId = await createUser(dbPath, 'alice', 'alice@example.com');
845
- const users = await getUsers(dbPath);
846
-
847
- console.log(`Created user ID: ${userId}`);
848
- console.log(`Total users: ${users.length}`);
849
- ```
850
-
851
- ### Transaction Pattern (Manual Connection)
852
-
853
- ```typescript
854
- import { createConnection, execute } from '@mark1russell7/docker-sqlite';
855
-
856
- async function transferCredits(fromUserId: number, toUserId: number, amount: number) {
857
- const { db, save } = await createConnection({ dbPath: './data/app.db' });
858
-
859
- try {
860
- // Start transaction
861
- execute(db, 'BEGIN TRANSACTION');
862
-
863
- // Deduct from sender
864
- execute(db, 'UPDATE users SET credits = credits - ? WHERE id = ?', [amount, fromUserId]);
865
-
866
- // Check sender balance
867
- const result = query(db, 'SELECT credits FROM users WHERE id = ?', [fromUserId]);
868
- if (result.rows[0].credits < 0) {
869
- throw new Error('Insufficient credits');
870
- }
871
-
872
- // Add to receiver
873
- execute(db, 'UPDATE users SET credits = credits + ? WHERE id = ?', [amount, toUserId]);
874
-
875
- // Commit
876
- execute(db, 'COMMIT');
877
- await save();
878
-
879
- console.log(`Transferred ${amount} credits from user ${fromUserId} to ${toUserId}`);
880
- } catch (error) {
881
- // Rollback on error
882
- execute(db, 'ROLLBACK');
883
- throw error;
884
- } finally {
885
- db.close();
886
- }
887
- }
888
- ```
889
-
890
- ### Query Builder Pattern
891
-
892
- ```typescript
893
- import { withConnection, query } from '@mark1russell7/docker-sqlite';
894
-
895
- interface QueryBuilder {
896
- select(columns: string[]): this;
897
- from(table: string): this;
898
- where(condition: string, params: unknown[]): this;
899
- orderBy(column: string, direction?: 'ASC' | 'DESC'): this;
900
- limit(limit: number): this;
901
- execute<T>(): QueryResult<T>;
902
- }
903
-
904
- class SQLiteQueryBuilder implements QueryBuilder {
905
- private parts: {
906
- select: string[];
907
- from: string;
908
- where: { condition: string; params: unknown[] }[];
909
- orderBy?: string;
910
- limit?: number;
911
- } = { select: ['*'], from: '', where: [] };
912
-
913
- constructor(private db: Database) {}
914
-
915
- select(columns: string[]): this {
916
- this.parts.select = columns;
917
- return this;
918
- }
919
-
920
- from(table: string): this {
921
- this.parts.from = table;
922
- return this;
923
- }
924
-
925
- where(condition: string, params: unknown[]): this {
926
- this.parts.where.push({ condition, params });
927
- return this;
928
- }
929
-
930
- orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
931
- this.parts.orderBy = `${column} ${direction}`;
932
- return this;
933
- }
934
-
935
- limit(limit: number): this {
936
- this.parts.limit = limit;
937
- return this;
938
- }
939
-
940
- execute<T>(): QueryResult<T> {
941
- let sql = `SELECT ${this.parts.select.join(', ')} FROM ${this.parts.from}`;
942
- const params: unknown[] = [];
943
-
944
- if (this.parts.where.length > 0) {
945
- sql += ' WHERE ' + this.parts.where.map(w => w.condition).join(' AND ');
946
- this.parts.where.forEach(w => params.push(...w.params));
947
- }
948
-
949
- if (this.parts.orderBy) {
950
- sql += ` ORDER BY ${this.parts.orderBy}`;
951
- }
952
-
953
- if (this.parts.limit) {
954
- sql += ` LIMIT ${this.parts.limit}`;
955
- }
956
-
957
- return query<T>(this.db, sql, params);
958
- }
959
- }
960
-
961
- // Usage
962
- await withConnection({ dbPath: './data/app.db' }, (db) => {
963
- const builder = new SQLiteQueryBuilder(db);
964
-
965
- const result = builder
966
- .select(['id', 'username', 'email'])
967
- .from('users')
968
- .where('created_at > ?', ['2024-01-01'])
969
- .where('email LIKE ?', ['%@example.com'])
970
- .orderBy('created_at', 'DESC')
971
- .limit(10)
972
- .execute<User>();
973
-
974
- console.log(result.rows);
975
- });
976
- ```
977
-
978
- ## API Reference
979
-
980
- ### Types
981
-
982
- ```typescript
983
- interface ConnectionConfig {
984
- dbPath: string; // Path to database file, or ':memory:' for in-memory
985
- }
986
-
987
- type ConnectionCallback<T> = (db: Database) => T | Promise<T>;
988
-
989
- interface QueryResult<T = Record<string, unknown>> {
990
- columns: string[]; // Column names
991
- rows: T[]; // Result rows as typed objects
992
- }
993
-
994
- interface ExecuteResult {
995
- changes: number; // Number of rows affected
996
- }
997
-
998
- interface Migration {
999
- version: number; // Unique version number (sequential)
1000
- description: string; // Human-readable description
1001
- up: string; // SQL to apply migration
1002
- down?: string; // SQL to rollback migration (optional)
1003
- }
1004
-
1005
- interface MigrationStatus {
1006
- currentVersion: number; // Latest applied version (0 if none)
1007
- appliedMigrations: number[]; // Array of applied version numbers
1008
- pendingMigrations: Migration[]; // Migrations not yet applied
1009
- }
1010
- ```
1011
-
1012
- ### Functions
1013
-
1014
- All functions are fully documented with JSDoc and include TypeScript type definitions. Import from the package root:
1015
-
1016
- ```typescript
1017
- import {
1018
- // Connection
1019
- withConnection,
1020
- createConnection,
1021
-
1022
- // Query
1023
- query,
1024
- execute,
1025
- execMultiple,
1026
- tableExists,
1027
- lastInsertRowId,
1028
-
1029
- // Migration
1030
- runMigrations,
1031
- rollbackMigration,
1032
- getMigrationStatus,
1033
- createMigration,
1034
-
1035
- // Types
1036
- type ConnectionConfig,
1037
- type QueryResult,
1038
- type Migration,
1039
- type MigrationStatus,
1040
- type Database,
1041
- // ... etc
1042
- } from '@mark1russell7/docker-sqlite';
1043
- ```
1044
-
1045
- ## License
1046
-
1047
- MIT
1048
-
1049
- ## Contributing
1050
-
1051
- Issues and pull requests are welcome at the package repository.
1052
-
1053
- ## Related Packages
1054
-
1055
- - [@mark1russell7/docker-mongo](../mongo) - MongoDB Docker setup with similar API
1056
- - [@mark1russell7/cue](https://github.com/mark1russell7/cue) - Shared TypeScript configuration
1057
- - [sql.js](https://github.com/sql-js/sql.js) - SQLite compiled to JavaScript/WASM
1
+ # @mark1russell7/docker-sqlite
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@mark1russell7/docker-sqlite.svg)](https://www.npmjs.com/package/@mark1russell7/docker-sqlite)
4
+ [![License: MIT](https://img.shields.io/npm/l/@mark1russell7/docker-sqlite.svg)](./LICENSE)
5
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D22-green.svg)](https://nodejs.org/)
6
+ [![Part of client](https://img.shields.io/badge/part%20of-client-1f3fbf)](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
+ ### Query Operations
349
+
350
+ #### `query<T>(db, sql, params?): QueryResult<T>`
351
+
352
+ Execute a SELECT query and return typed results.
353
+
354
+ ```typescript
355
+ import { query } from '@mark1russell7/docker-sqlite';
356
+
357
+ interface User {
358
+ id: number;
359
+ name: string;
360
+ email: string;
361
+ }
362
+
363
+ const result = query<User>(
364
+ db,
365
+ 'SELECT * FROM users WHERE age > ?',
366
+ [21]
367
+ );
368
+
369
+ console.log(result.columns); // ['id', 'name', 'email']
370
+ console.log(result.rows); // Array of User objects
371
+ ```
372
+
373
+ **Parameters:**
374
+ - `db`: Database instance
375
+ - `sql`: SQL SELECT statement
376
+ - `params?`: Optional parameter values
377
+
378
+ **Returns:**
379
+ ```typescript
380
+ {
381
+ columns: string[]; // Column names
382
+ rows: T[]; // Result rows as typed objects
383
+ }
384
+ ```
385
+
386
+ `columns` has the column names also when there are no rows.
387
+
388
+ ---
389
+
390
+ #### `execute(db, sql, params?): ExecuteResult`
391
+
392
+ Execute an INSERT, UPDATE, or DELETE statement.
393
+
394
+ ```typescript
395
+ import { execute, lastInsertRowId } from '@mark1russell7/docker-sqlite';
396
+
397
+ const result = execute(
398
+ db,
399
+ 'INSERT INTO users (name, email) VALUES (?, ?)',
400
+ ['Bob', 'bob@example.com']
401
+ );
402
+
403
+ console.log(result.changes); // 1
404
+ console.log(lastInsertRowId(db)); // New row ID
405
+ ```
406
+
407
+ 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.
408
+
409
+ **Returns:**
410
+ ```typescript
411
+ {
412
+ changes: number; // Number of rows affected
413
+ }
414
+ ```
415
+
416
+ ---
417
+
418
+ #### `execMultiple(db, sql): void`
419
+
420
+ Execute multiple SQL statements at once. Useful for schema setup and batch operations.
421
+
422
+ ```typescript
423
+ import { execMultiple } from '@mark1russell7/docker-sqlite';
424
+
425
+ execMultiple(db, `
426
+ CREATE TABLE IF NOT EXISTS users (
427
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
428
+ name TEXT NOT NULL,
429
+ email TEXT UNIQUE
430
+ );
431
+
432
+ CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
433
+
434
+ CREATE TABLE IF NOT EXISTS posts (
435
+ id INTEGER PRIMARY KEY,
436
+ user_id INTEGER,
437
+ title TEXT,
438
+ FOREIGN KEY (user_id) REFERENCES users(id)
439
+ );
440
+ `);
441
+ ```
442
+
443
+ ---
444
+
445
+ #### `tableExists(db, tableName): boolean`
446
+
447
+ Check if a table exists in the database.
448
+
449
+ ```typescript
450
+ import { tableExists } from '@mark1russell7/docker-sqlite';
451
+
452
+ if (!tableExists(db, 'users')) {
453
+ execute(db, 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
454
+ }
455
+ ```
456
+
457
+ ---
458
+
459
+ #### `lastInsertRowId(db): number`
460
+
461
+ Get the row ID of the last inserted row.
462
+
463
+ ```typescript
464
+ import { execute, lastInsertRowId } from '@mark1russell7/docker-sqlite';
465
+
466
+ execute(db, 'INSERT INTO users (name) VALUES (?)', ['Alice']);
467
+ const userId = lastInsertRowId(db);
468
+ console.log(`Created user with ID: ${userId}`);
469
+ ```
470
+
471
+ ## Migration System
472
+
473
+ The package includes a complete migration system for versioned schema management.
474
+
475
+ ### Migration Structure
476
+
477
+ ```typescript
478
+ interface Migration {
479
+ version: number; // Unique version number (sequential)
480
+ description: string; // Human-readable description
481
+ up: string; // SQL to apply migration
482
+ down?: string; // SQL to rollback migration (optional)
483
+ }
484
+ ```
485
+
486
+ ### `runMigrations(db, migrations): Migration[]`
487
+
488
+ Apply all pending migrations in order.
489
+
490
+ ```typescript
491
+ import { withConnection, runMigrations } from '@mark1russell7/docker-sqlite';
492
+
493
+ const migrations = [
494
+ {
495
+ version: 1,
496
+ description: 'Create users table',
497
+ up: `
498
+ CREATE TABLE users (
499
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
500
+ name TEXT NOT NULL,
501
+ email TEXT UNIQUE
502
+ )
503
+ `,
504
+ down: 'DROP TABLE users'
505
+ },
506
+ {
507
+ version: 2,
508
+ description: 'Add created_at column',
509
+ up: 'ALTER TABLE users ADD COLUMN created_at TEXT DEFAULT CURRENT_TIMESTAMP',
510
+ down: 'ALTER TABLE users DROP COLUMN created_at'
511
+ },
512
+ {
513
+ version: 3,
514
+ description: 'Create posts table',
515
+ up: `
516
+ CREATE TABLE posts (
517
+ id INTEGER PRIMARY KEY,
518
+ user_id INTEGER,
519
+ title TEXT NOT NULL,
520
+ content TEXT,
521
+ FOREIGN KEY (user_id) REFERENCES users(id)
522
+ )
523
+ `
524
+ }
525
+ ];
526
+
527
+ await withConnection({ dbPath: './data/app.db' }, (db) => {
528
+ const applied = runMigrations(db, migrations);
529
+ console.log(`Applied ${applied.length} migrations`);
530
+ applied.forEach(m => console.log(` ✓ ${m.description}`));
531
+ });
532
+ ```
533
+
534
+ **Behavior:**
535
+ - Creates `_migrations` table automatically to track applied migrations
536
+ - Applies only pending migrations in version order
537
+ - Records each migration with timestamp
538
+ - Skips already-applied migrations
539
+ - Throws error if migration fails (doesn't continue)
540
+ - 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.
541
+ - 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.
542
+
543
+ **Returns:** Array of applied migrations
544
+
545
+ ---
546
+
547
+ ### `getMigrationStatus(db, migrations): MigrationStatus`
548
+
549
+ Get the current migration status.
550
+
551
+ ```typescript
552
+ import { getMigrationStatus } from '@mark1russell7/docker-sqlite';
553
+
554
+ const status = getMigrationStatus(db, migrations);
555
+
556
+ console.log(`Current version: ${status.currentVersion}`);
557
+ console.log(`Applied migrations: ${status.appliedMigrations.join(', ')}`);
558
+ console.log(`Pending migrations: ${status.pendingMigrations.length}`);
559
+
560
+ status.pendingMigrations.forEach(m => {
561
+ console.log(` - v${m.version}: ${m.description}`);
562
+ });
563
+ ```
564
+
565
+ **Returns:**
566
+ ```typescript
567
+ {
568
+ currentVersion: number; // Latest applied version (0 if none)
569
+ appliedMigrations: number[]; // Array of applied version numbers
570
+ pendingMigrations: Migration[]; // Migrations not yet applied
571
+ }
572
+ ```
573
+
574
+ ---
575
+
576
+ ### `rollbackMigration(db, migrations): Migration | null`
577
+
578
+ Rollback the last applied migration.
579
+
580
+ ```typescript
581
+ import { rollbackMigration } from '@mark1russell7/docker-sqlite';
582
+
583
+ const rolled = rollbackMigration(db, migrations);
584
+
585
+ if (rolled) {
586
+ console.log(`Rolled back: v${rolled.version} - ${rolled.description}`);
587
+ } else {
588
+ console.log('No migrations to rollback');
589
+ }
590
+ ```
591
+
592
+ **Requirements:**
593
+ - Migration must have a `down` script
594
+ - Migration must be the current version
595
+
596
+ The `down` script and the removal of its record run in a savepoint, with the same rules as `runMigrations`.
597
+
598
+ **Returns:** The rolled-back migration, or `null` if none
599
+
600
+ ---
601
+
602
+ ### `createMigration(version, description, up, down?): Migration`
603
+
604
+ Helper function to create a migration object.
605
+
606
+ ```typescript
607
+ import { createMigration } from '@mark1russell7/docker-sqlite';
608
+
609
+ const migration = createMigration(
610
+ 1,
611
+ 'Initial schema',
612
+ 'CREATE TABLE users (id INTEGER PRIMARY KEY)',
613
+ 'DROP TABLE users'
614
+ );
615
+ ```
616
+
617
+ ## Configuration
618
+
619
+ ### Database Paths
620
+
621
+ ```typescript
622
+ // In-memory database (no persistence)
623
+ await withConnection({ dbPath: ':memory:' }, (db) => {
624
+ // Data is lost after connection closes
625
+ });
626
+
627
+ // Relative path
628
+ await withConnection({ dbPath: './data/app.db' }, (db) => {
629
+ // Saved to ./data/app.db
630
+ });
631
+
632
+ // Absolute path
633
+ await withConnection({ dbPath: '/var/lib/myapp/app.db' }, (db) => {
634
+ // Saved to /var/lib/myapp/app.db
635
+ });
636
+
637
+ // Default (in-memory)
638
+ await withConnection({}, (db) => {
639
+ // No persistence
640
+ });
641
+ ```
642
+
643
+ ### Common Patterns
644
+
645
+ #### CLI Logging Database
646
+
647
+ The package includes a reference schema for CLI logging in `schema/logs.sql`:
648
+
649
+ ```typescript
650
+ import { withConnection, execMultiple } from '@mark1russell7/docker-sqlite';
651
+ import { readFile } from 'node:fs/promises';
652
+
653
+ const schema = await readFile('./node_modules/@mark1russell7/docker-sqlite/schema/logs.sql', 'utf-8');
654
+
655
+ await withConnection({ dbPath: './cli-logs.db' }, (db) => {
656
+ execMultiple(db, schema);
657
+
658
+ execute(db, `
659
+ INSERT INTO logs (level, message, command, session_id)
660
+ VALUES (?, ?, ?, ?)
661
+ `, ['info', 'Command started', 'lib.refresh', sessionId]);
662
+ });
663
+ ```
664
+
665
+ #### Testing Pattern
666
+
667
+ ```typescript
668
+ import { describe, it } from 'node:test';
669
+ import assert from 'node:assert';
670
+ import { withConnection, query, execute } from '@mark1russell7/docker-sqlite';
671
+
672
+ describe('User repository', () => {
673
+ it('should create and retrieve user', async () => {
674
+ await withConnection({ dbPath: ':memory:' }, (db) => {
675
+ // Setup schema
676
+ execute(db, 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
677
+
678
+ // Test
679
+ execute(db, 'INSERT INTO users (name) VALUES (?)', ['Alice']);
680
+ const result = query(db, 'SELECT * FROM users WHERE name = ?', ['Alice']);
681
+
682
+ assert.strictEqual(result.rows.length, 1);
683
+ assert.strictEqual(result.rows[0].name, 'Alice');
684
+ });
685
+ });
686
+ });
687
+ ```
688
+
689
+ #### Migration-based Setup
690
+
691
+ ```typescript
692
+ import { withConnection, runMigrations, getMigrationStatus } from '@mark1russell7/docker-sqlite';
693
+
694
+ const migrations = [
695
+ {
696
+ version: 1,
697
+ description: 'Initial schema',
698
+ up: `
699
+ CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
700
+ CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER);
701
+ `
702
+ }
703
+ ];
704
+
705
+ async function initDatabase(dbPath: string) {
706
+ await withConnection({ dbPath }, (db) => {
707
+ const status = getMigrationStatus(db, migrations);
708
+
709
+ if (status.pendingMigrations.length > 0) {
710
+ console.log(`Applying ${status.pendingMigrations.length} migration(s)...`);
711
+ runMigrations(db, migrations);
712
+ console.log('Database up to date!');
713
+ } else {
714
+ console.log(`Database already at version ${status.currentVersion}`);
715
+ }
716
+ });
717
+ }
718
+
719
+ await initDatabase('./data/app.db');
720
+ ```
721
+
722
+ ## Integration
723
+
724
+ ### Environment Configuration
725
+
726
+ Create a `.env` file for your application:
727
+
728
+ ```env
729
+ # SQLite database path
730
+ SQLITE_DB_PATH=./data/app.db
731
+
732
+ # Docker Web UI settings (optional)
733
+ SQLITE_CONTAINER_NAME=myapp-sqlite-ui
734
+ SQLITE_PORT=8080
735
+ ```
736
+
737
+ Load in your application:
738
+
739
+ ```typescript
740
+ import 'dotenv/config';
741
+ import { withConnection } from '@mark1russell7/docker-sqlite';
742
+
743
+ const dbPath = process.env.SQLITE_DB_PATH || './app.db';
744
+
745
+ await withConnection({ dbPath }, (db) => {
746
+ // Your database operations
747
+ });
748
+ ```
749
+
750
+ ### npm Scripts
751
+
752
+ Add to your `package.json`:
753
+
754
+ ```json
755
+ {
756
+ "scripts": {
757
+ "db:ui": "docker compose -f node_modules/@mark1russell7/docker-sqlite/docker-compose.yml up -d",
758
+ "db:ui:stop": "docker compose -f node_modules/@mark1russell7/docker-sqlite/docker-compose.yml down",
759
+ "db:migrate": "tsx scripts/migrate.ts",
760
+ "db:rollback": "tsx scripts/rollback.ts"
761
+ }
762
+ }
763
+ ```
764
+
765
+ ### Ecosystem Integration
766
+
767
+ This package integrates with the broader Mark Russell ecosystem:
768
+
769
+ - **@mark1russell7/cue**: TypeScript configuration inheritance
770
+ - **client-sqlite**: MCP client procedures for SQLite operations
771
+ - **bundle-dev**: Development bundle including database tooling
772
+
773
+ ## Examples
774
+
775
+ ### Complete Application with Migrations
776
+
777
+ ```typescript
778
+ import { withConnection, runMigrations, query, execute } from '@mark1russell7/docker-sqlite';
779
+
780
+ // Define schema migrations
781
+ const migrations = [
782
+ {
783
+ version: 1,
784
+ description: 'Create users table',
785
+ up: `
786
+ CREATE TABLE users (
787
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
788
+ username TEXT UNIQUE NOT NULL,
789
+ email TEXT UNIQUE NOT NULL,
790
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
791
+ );
792
+ CREATE INDEX idx_users_email ON users(email);
793
+ `,
794
+ down: 'DROP TABLE users'
795
+ },
796
+ {
797
+ version: 2,
798
+ description: 'Create posts table',
799
+ up: `
800
+ CREATE TABLE posts (
801
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
802
+ user_id INTEGER NOT NULL,
803
+ title TEXT NOT NULL,
804
+ content TEXT,
805
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
806
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
807
+ );
808
+ CREATE INDEX idx_posts_user ON posts(user_id);
809
+ `,
810
+ down: 'DROP TABLE posts'
811
+ }
812
+ ];
813
+
814
+ // Application setup
815
+ async function setupDatabase(dbPath: string) {
816
+ await withConnection({ dbPath }, (db) => {
817
+ // Apply migrations
818
+ const applied = runMigrations(db, migrations);
819
+ if (applied.length > 0) {
820
+ console.log(`Applied ${applied.length} migration(s)`);
821
+ }
822
+ });
823
+ }
824
+
825
+ // User operations
826
+ interface User {
827
+ id: number;
828
+ username: string;
829
+ email: string;
830
+ created_at: string;
831
+ }
832
+
833
+ async function createUser(dbPath: string, username: string, email: string): Promise<number> {
834
+ return withConnection({ dbPath }, (db) => {
835
+ execute(db, 'INSERT INTO users (username, email) VALUES (?, ?)', [username, email]);
836
+ return lastInsertRowId(db);
837
+ });
838
+ }
839
+
840
+ async function getUsers(dbPath: string): Promise<User[]> {
841
+ return withConnection({ dbPath }, (db) => {
842
+ const result = query<User>(db, 'SELECT * FROM users ORDER BY created_at DESC');
843
+ return result.rows;
844
+ });
845
+ }
846
+
847
+ // Usage
848
+ const dbPath = './data/myapp.db';
849
+
850
+ await setupDatabase(dbPath);
851
+ const userId = await createUser(dbPath, 'alice', 'alice@example.com');
852
+ const users = await getUsers(dbPath);
853
+
854
+ console.log(`Created user ID: ${userId}`);
855
+ console.log(`Total users: ${users.length}`);
856
+ ```
857
+
858
+ ### Transaction Pattern (Manual Connection)
859
+
860
+ ```typescript
861
+ import { createConnection, execute } from '@mark1russell7/docker-sqlite';
862
+
863
+ async function transferCredits(fromUserId: number, toUserId: number, amount: number) {
864
+ const { db, save } = await createConnection({ dbPath: './data/app.db' });
865
+
866
+ try {
867
+ // Start transaction
868
+ execute(db, 'BEGIN TRANSACTION');
869
+
870
+ // Deduct from sender
871
+ execute(db, 'UPDATE users SET credits = credits - ? WHERE id = ?', [amount, fromUserId]);
872
+
873
+ // Check sender balance
874
+ const result = query(db, 'SELECT credits FROM users WHERE id = ?', [fromUserId]);
875
+ if (result.rows[0].credits < 0) {
876
+ throw new Error('Insufficient credits');
877
+ }
878
+
879
+ // Add to receiver
880
+ execute(db, 'UPDATE users SET credits = credits + ? WHERE id = ?', [amount, toUserId]);
881
+
882
+ // Commit
883
+ execute(db, 'COMMIT');
884
+ await save();
885
+
886
+ console.log(`Transferred ${amount} credits from user ${fromUserId} to ${toUserId}`);
887
+ } catch (error) {
888
+ // Rollback on error
889
+ execute(db, 'ROLLBACK');
890
+ throw error;
891
+ } finally {
892
+ db.close();
893
+ }
894
+ }
895
+ ```
896
+
897
+ ### Query Builder Pattern
898
+
899
+ ```typescript
900
+ import { withConnection, query } from '@mark1russell7/docker-sqlite';
901
+
902
+ interface QueryBuilder {
903
+ select(columns: string[]): this;
904
+ from(table: string): this;
905
+ where(condition: string, params: unknown[]): this;
906
+ orderBy(column: string, direction?: 'ASC' | 'DESC'): this;
907
+ limit(limit: number): this;
908
+ execute<T>(): QueryResult<T>;
909
+ }
910
+
911
+ class SQLiteQueryBuilder implements QueryBuilder {
912
+ private parts: {
913
+ select: string[];
914
+ from: string;
915
+ where: { condition: string; params: unknown[] }[];
916
+ orderBy?: string;
917
+ limit?: number;
918
+ } = { select: ['*'], from: '', where: [] };
919
+
920
+ constructor(private db: Database) {}
921
+
922
+ select(columns: string[]): this {
923
+ this.parts.select = columns;
924
+ return this;
925
+ }
926
+
927
+ from(table: string): this {
928
+ this.parts.from = table;
929
+ return this;
930
+ }
931
+
932
+ where(condition: string, params: unknown[]): this {
933
+ this.parts.where.push({ condition, params });
934
+ return this;
935
+ }
936
+
937
+ orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
938
+ this.parts.orderBy = `${column} ${direction}`;
939
+ return this;
940
+ }
941
+
942
+ limit(limit: number): this {
943
+ this.parts.limit = limit;
944
+ return this;
945
+ }
946
+
947
+ execute<T>(): QueryResult<T> {
948
+ let sql = `SELECT ${this.parts.select.join(', ')} FROM ${this.parts.from}`;
949
+ const params: unknown[] = [];
950
+
951
+ if (this.parts.where.length > 0) {
952
+ sql += ' WHERE ' + this.parts.where.map(w => w.condition).join(' AND ');
953
+ this.parts.where.forEach(w => params.push(...w.params));
954
+ }
955
+
956
+ if (this.parts.orderBy) {
957
+ sql += ` ORDER BY ${this.parts.orderBy}`;
958
+ }
959
+
960
+ if (this.parts.limit) {
961
+ sql += ` LIMIT ${this.parts.limit}`;
962
+ }
963
+
964
+ return query<T>(this.db, sql, params);
965
+ }
966
+ }
967
+
968
+ // Usage
969
+ await withConnection({ dbPath: './data/app.db' }, (db) => {
970
+ const builder = new SQLiteQueryBuilder(db);
971
+
972
+ const result = builder
973
+ .select(['id', 'username', 'email'])
974
+ .from('users')
975
+ .where('created_at > ?', ['2024-01-01'])
976
+ .where('email LIKE ?', ['%@example.com'])
977
+ .orderBy('created_at', 'DESC')
978
+ .limit(10)
979
+ .execute<User>();
980
+
981
+ console.log(result.rows);
982
+ });
983
+ ```
984
+
985
+ ## API Reference
986
+
987
+ ### Types
988
+
989
+ ```typescript
990
+ interface ConnectionConfig {
991
+ dbPath: string; // Path to database file, or ':memory:' for in-memory
992
+ }
993
+
994
+ type ConnectionCallback<T> = (db: Database) => T | Promise<T>;
995
+
996
+ interface QueryResult<T = Record<string, unknown>> {
997
+ columns: string[]; // Column names
998
+ rows: T[]; // Result rows as typed objects
999
+ }
1000
+
1001
+ interface ExecuteResult {
1002
+ changes: number; // Number of rows affected
1003
+ }
1004
+
1005
+ interface Migration {
1006
+ version: number; // Unique version number (sequential)
1007
+ description: string; // Human-readable description
1008
+ up: string; // SQL to apply migration
1009
+ down?: string; // SQL to rollback migration (optional)
1010
+ }
1011
+
1012
+ interface MigrationStatus {
1013
+ currentVersion: number; // Latest applied version (0 if none)
1014
+ appliedMigrations: number[]; // Array of applied version numbers
1015
+ pendingMigrations: Migration[]; // Migrations not yet applied
1016
+ }
1017
+ ```
1018
+
1019
+ ### Functions
1020
+
1021
+ All functions are fully documented with JSDoc and include TypeScript type definitions. Import from the package root:
1022
+
1023
+ ```typescript
1024
+ import {
1025
+ // Connection
1026
+ withConnection,
1027
+ createConnection,
1028
+
1029
+ // Query
1030
+ query,
1031
+ execute,
1032
+ execMultiple,
1033
+ tableExists,
1034
+ lastInsertRowId,
1035
+
1036
+ // Migration
1037
+ runMigrations,
1038
+ rollbackMigration,
1039
+ getMigrationStatus,
1040
+ createMigration,
1041
+
1042
+ // Types
1043
+ type ConnectionConfig,
1044
+ type QueryResult,
1045
+ type Migration,
1046
+ type MigrationStatus,
1047
+ type Database,
1048
+ // ... etc
1049
+ } from '@mark1russell7/docker-sqlite';
1050
+ ```
1051
+
1052
+ ## License
1053
+
1054
+ MIT
1055
+
1056
+ ## Contributing
1057
+
1058
+ Issues and pull requests are welcome at the package repository.
1059
+
1060
+ ## Related Packages
1061
+
1062
+ - [@mark1russell7/docker-mongo](../mongo) - MongoDB Docker setup with similar API
1063
+ - [@mark1russell7/cue](https://github.com/mark1russell7/cue) - Shared TypeScript configuration
1064
+ - [sql.js](https://github.com/sql-js/sql.js) - SQLite compiled to JavaScript/WASM