@dockstat/sqlite-wrapper 1.2.7 → 1.3.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/README.md CHANGED
@@ -5,14 +5,39 @@
5
5
  ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)
6
6
 
7
7
  **A fast, type-safe TypeScript wrapper for Bun's `bun:sqlite`.**
8
- Schema-first table helpers, an expressive chainable QueryBuilder, safe defaults (WHERE required for destructive ops), JSON + generated columns, and production-minded pragmas & transactions.
8
+ Schema-first table helpers, an expressive chainable QueryBuilder, safe defaults (WHERE required for destructive ops), JSON + Boolean auto-detection, automatic backups with retention, and production-minded pragmas & transactions.
9
+
10
+ ---
11
+
12
+ ## 🆕 What's New in v1.3
13
+
14
+ ### Bug Fixes
15
+ - **Fixed Boolean parsing** — Boolean columns now correctly convert SQLite's `0`/`1` to JavaScript `true`/`false`
16
+
17
+ ### New Features
18
+ - **Auto-detection of JSON & Boolean columns** — No more manual parser configuration! Columns using `column.json()` or `column.boolean()` are automatically detected from schema
19
+ - **Automatic backups with retention** — Configure `autoBackup` to create periodic backups with automatic cleanup of old files
20
+ - **Backup & Restore API** — New `backup()`, `restore()`, and `listBackups()` methods
21
+ - **`getPath()` method** — Get the database file path
22
+
23
+ ### Architecture Improvements
24
+ - **New `utils/` module** — Reusable utilities for SQL building, logging, and row transformation
25
+ - **Structured logging** — Cleaner, more consistent log output with dedicated loggers per component
26
+ - **Reduced code duplication** — Extracted common patterns into shared utilities
27
+ - **Better maintainability** — Clearer separation of concerns across modules
28
+
29
+ ### Breaking Changes
30
+ - None! v1.3 is fully backward compatible with v1.2.x
31
+
32
+ ---
9
33
 
10
34
  ## Install
35
+
11
36
  > Requires **Bun** runtime
12
37
 
13
38
  ```bash
14
39
  bun add @dockstat/sqlite-wrapper
15
- ````
40
+ ```
16
41
 
17
42
  ## 10-second quickstart
18
43
 
@@ -20,77 +45,506 @@ bun add @dockstat/sqlite-wrapper
20
45
  import { DB, column } from "@dockstat/sqlite-wrapper";
21
46
 
22
47
  type User = {
23
- id?: number,
24
- name: string,
25
- active: boolean,
26
- email: string,
27
- }
48
+ id?: number;
49
+ name: string;
50
+ active: boolean;
51
+ email: string;
52
+ metadata: object;
53
+ };
28
54
 
29
55
  const db = new DB("app.db", {
30
56
  pragmas: [
31
- ["journal_mode","WAL"],
32
- ["foreign_keys","ON"]
33
- ]
57
+ ["journal_mode", "WAL"],
58
+ ["foreign_keys", "ON"],
59
+ ],
34
60
  });
35
61
 
36
62
  const userTable = db.createTable<User>("users", {
37
63
  id: column.id(),
38
64
  name: column.text({ notNull: true }),
39
- active: column.boolean(),
65
+ active: column.boolean(), // Auto-detected as BOOLEAN ✨
40
66
  email: column.text({ unique: true, notNull: true }),
41
- created_at: column.createdAt()
67
+ metadata: column.json(), // Auto-detected as JSON ✨
68
+ created_at: column.createdAt(),
42
69
  });
43
70
 
71
+ // Insert with automatic JSON serialization & boolean handling
72
+ userTable.insert({
73
+ name: "Alice",
74
+ active: true,
75
+ email: "alice@example.com",
76
+ metadata: { role: "admin", preferences: { theme: "dark" } },
77
+ });
78
+
79
+ // Query with automatic JSON parsing & boolean conversion
44
80
  const users = userTable
45
- .select(["id","name","email"])
81
+ .select(["id", "name", "email", "metadata"])
46
82
  .where({ active: true })
47
- .orderBy("created_at").desc()
83
+ .orderBy("created_at")
84
+ .desc()
48
85
  .limit(10)
49
86
  .all();
50
87
  ```
51
88
 
52
89
  ## Why use it?
53
90
 
54
- * ⚡ Bun-native, high-performance bindings
55
- * 🔒 Type-safe table & query APIs (compile-time checks)
56
- * 🧭 Full SQLite feature support: JSON, generated columns, foreign keys, indexes
57
- * 🛡️ Safety-first defaults — prevents accidental full-table updates/deletes
58
- * 🚀 Designed for production workflows: WAL, pragmatic PRAGMAs, bulk ops, transactions
91
+ - ⚡ Bun-native, high-performance bindings
92
+ - 🔒 Type-safe table & query APIs (compile-time checks)
93
+ - 🧭 Full SQLite feature support: JSON, generated columns, foreign keys, indexes
94
+ - 🛡️ Safety-first defaults — prevents accidental full-table updates/deletes
95
+ - 🚀 Designed for production workflows: WAL, pragmatic PRAGMAs, bulk ops, transactions
96
+ - 🔄 **Automatic JSON/Boolean detection** — no manual parser configuration needed
97
+ - 💾 **Built-in backup & restore** — with automatic retention policies
98
+
99
+ ---
59
100
 
60
101
  ## Core Features
61
102
 
62
103
  ### Type Safety
63
104
 
64
- * **Compile-time validation** of column names and data shapes
65
- * **IntelliSense support** for all operations
66
- * **Generic interfaces** that adapt to your data models
67
- * **Type-safe column definitions** with comprehensive constraint support
105
+ - **Compile-time validation** of column names and data shapes
106
+ - **IntelliSense support** for all operations
107
+ - **Generic interfaces** that adapt to your data models
108
+ - **Type-safe column definitions** with comprehensive constraint support
68
109
 
69
110
  ### Safety-First Design
70
111
 
71
- * **Mandatory WHERE conditions** for UPDATE and DELETE operations to prevent accidental data loss
72
- * **Parameter binding** for all queries to prevent SQL injection
73
- * **Prepared statements** used internally for optimal performance
74
- * **Transaction support** with automatic rollback on errors
112
+ - **Mandatory WHERE conditions** for UPDATE and DELETE operations to prevent accidental data loss
113
+ - **Parameter binding** for all queries to prevent SQL injection
114
+ - **Prepared statements** used internally for optimal performance
115
+ - **Transaction support** with automatic rollback on errors
75
116
 
76
117
  ### Production Ready
77
118
 
78
- * **WAL mode** support for concurrent read/write operations
79
- * **Comprehensive PRAGMA management** for performance tuning
80
- * **Connection pooling** considerations built-in
81
- * **Bulk operation** support with transaction batching
82
- * **Schema introspection** tools for migrations and debugging
119
+ - **WAL mode** support for concurrent read/write operations
120
+ - **Comprehensive PRAGMA management** for performance tuning
121
+ - **Connection pooling** considerations built-in
122
+ - **Bulk operation** support with transaction batching
123
+ - **Schema introspection** tools for migrations and debugging
124
+ - **Automatic backups** with configurable retention policies
83
125
 
84
126
  ### Complete SQLite Support
85
127
 
86
- * **All SQLite data types** with proper TypeScript mappings
87
- * **Generated columns** (both VIRTUAL and STORED)
88
- * **Foreign key constraints** with cascade options
89
- * **JSON columns** with validation and transformation
90
- * **Full-text search** preparation
91
- * **Custom functions** and extensions support
128
+ - **All SQLite data types** with proper TypeScript mappings
129
+ - **Generated columns** (both VIRTUAL and STORED)
130
+ - **Foreign key constraints** with cascade options
131
+ - **JSON columns** with automatic serialization/deserialization
132
+ - **Boolean columns** with automatic conversion (SQLite stores as 0/1)
133
+ - **Full-text search** preparation
134
+ - **Custom functions** and extensions support
135
+
136
+ ---
137
+
138
+ ## Automatic Type Detection
139
+
140
+ When using `column.json()` or `column.boolean()`, the wrapper **automatically detects** these columns and handles serialization/deserialization for you. No manual parser configuration required!
141
+
142
+ ```typescript
143
+ // JSON and Boolean columns are auto-detected from schema
144
+ const table = db.createTable<{
145
+ id: number;
146
+ settings: object;
147
+ isActive: boolean;
148
+ }>("config", {
149
+ id: column.id(),
150
+ settings: column.json(), // Auto-detected ✨
151
+ isActive: column.boolean(), // Auto-detected ✨
152
+ });
153
+
154
+ // Insert - objects are automatically JSON.stringify'd, booleans work natively
155
+ table.insert({
156
+ settings: { theme: "dark", notifications: true },
157
+ isActive: true,
158
+ });
159
+
160
+ // Select - JSON is automatically parsed, 0/1 converted to true/false
161
+ const row = table.select(["*"]).where({ id: 1 }).first();
162
+ console.log(row.settings.theme); // "dark" (not a string!)
163
+ console.log(row.isActive); // true (not 1!)
164
+ ```
165
+
166
+ ### Manual Parser Override
167
+
168
+ You can still manually specify parsers if needed (e.g., for existing tables or edge cases):
169
+
170
+ ```typescript
171
+ const table = db.createTable<MyType>(
172
+ "my_table",
173
+ { ... },
174
+ {
175
+ parser: {
176
+ JSON: ["customJsonColumn"],
177
+ BOOLEAN: ["customBoolColumn"],
178
+ },
179
+ }
180
+ );
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Automatic Backups with Retention
186
+
187
+ Configure automatic backups with retention policies to protect your data:
188
+
189
+ ```typescript
190
+ const db = new DB("app.db", {
191
+ pragmas: [["journal_mode", "WAL"]],
192
+ autoBackup: {
193
+ enabled: true,
194
+ directory: "./backups",
195
+ intervalMs: 60 * 60 * 1000, // Backup every hour
196
+ maxBackups: 10, // Keep only the 10 most recent backups
197
+ filenamePrefix: "app_backup", // Optional custom prefix
198
+ },
199
+ });
200
+
201
+ // Backups are created automatically:
202
+ // - On database open (initial backup)
203
+ // - At the specified interval
204
+ // - Old backups are automatically removed based on maxBackups
205
+ ```
206
+
207
+ ### Auto-Backup Options
208
+
209
+ | Option | Type | Default | Description |
210
+ | ---------------- | ------- | ---------- | -------------------------------------- |
211
+ | `enabled` | boolean | - | Enable/disable automatic backups |
212
+ | `directory` | string | - | Directory to store backup files |
213
+ | `intervalMs` | number | `3600000` | Backup interval in milliseconds (1 hr) |
214
+ | `maxBackups` | number | `10` | Maximum number of backups to retain |
215
+ | `filenamePrefix` | string | `"backup"` | Prefix for backup filenames |
216
+
217
+ ---
218
+
219
+ ## Manual Backup & Restore
220
+
221
+ ### Creating Backups
222
+
223
+ ```typescript
224
+ // Backup to auto-generated path (if auto-backup configured)
225
+ const backupPath = db.backup();
226
+
227
+ // Backup to custom path
228
+ const customPath = db.backup("./my-backup.db");
229
+ ```
230
+
231
+ ### Listing Backups
232
+
233
+ ```typescript
234
+ const backups = db.listBackups();
235
+ // Returns: Array<{ filename, path, size, created }>
236
+
237
+ console.log(backups[0]);
238
+ // {
239
+ // filename: "backup_2024-01-15T10-30-00-000Z.db",
240
+ // path: "/app/backups/backup_2024-01-15T10-30-00-000Z.db",
241
+ // size: 1048576,
242
+ // created: Date object
243
+ // }
244
+ ```
245
+
246
+ ### Restoring from Backup
247
+
248
+ ```typescript
249
+ // Restore to the original database (closes and reopens connection)
250
+ db.restore("./backups/backup_2024-01-15.db");
251
+
252
+ // Restore to a different path
253
+ db.restore("./backups/backup_2024-01-15.db", "./restored.db");
254
+ ```
255
+
256
+ ### Stopping Auto-Backup
257
+
258
+ ```typescript
259
+ // Stop the automatic backup timer
260
+ db.stopAutoBackup();
261
+
262
+ // Note: close() automatically stops auto-backup
263
+ db.close();
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Query Builder Examples
269
+
270
+ ### SELECT Operations
271
+
272
+ ```typescript
273
+ // Select all columns
274
+ const allUsers = userTable.select(["*"]).all();
275
+
276
+ // Select specific columns with conditions
277
+ const activeAdmins = userTable
278
+ .select(["id", "name", "email"])
279
+ .where({ active: true, role: "admin" })
280
+ .orderBy("created_at")
281
+ .desc()
282
+ .limit(10)
283
+ .all();
284
+
285
+ // Get first match
286
+ const user = userTable.select(["*"]).where({ email: "alice@example.com" }).first();
287
+
288
+ // Count records
289
+ const count = userTable.where({ active: true }).count();
290
+
291
+ // Check existence
292
+ const exists = userTable.where({ email: "test@example.com" }).exists();
293
+
294
+ // Get single column value
295
+ const name = userTable.where({ id: 1 }).value("name");
296
+
297
+ // Pluck column values
298
+ const allEmails = userTable.where({ active: true }).pluck("email");
299
+ ```
300
+
301
+ ### INSERT Operations
302
+
303
+ ```typescript
304
+ // Single insert
305
+ const result = userTable.insert({
306
+ name: "Bob",
307
+ email: "bob@example.com",
308
+ active: true,
309
+ });
310
+ console.log(result.insertId); // New row ID
311
+
312
+ // Bulk insert
313
+ userTable.insertBatch([
314
+ { name: "User 1", email: "user1@example.com" },
315
+ { name: "User 2", email: "user2@example.com" },
316
+ ]);
317
+
318
+ // Insert with conflict resolution
319
+ userTable.insertOrIgnore({ email: "existing@example.com", name: "Name" });
320
+ userTable.insertOrReplace({ email: "existing@example.com", name: "New Name" });
321
+
322
+ // Insert and get the row back
323
+ const newUser = userTable.insertAndGet({ name: "Charlie", email: "charlie@example.com" });
324
+ ```
325
+
326
+ ### UPDATE Operations
327
+
328
+ ```typescript
329
+ // Update with WHERE (required!)
330
+ userTable.where({ id: 1 }).update({ name: "Updated Name" });
331
+
332
+ // Increment/decrement numeric columns
333
+ userTable.where({ id: 1 }).increment("login_count");
334
+ userTable.where({ id: 1 }).decrement("credits", 10);
335
+
336
+ // Upsert (insert or replace)
337
+ userTable.upsert({ email: "alice@example.com", name: "Alice Updated" });
338
+
339
+ // Batch update
340
+ userTable.updateBatch([
341
+ { where: { id: 1 }, data: { active: false } },
342
+ { where: { id: 2 }, data: { active: true } },
343
+ ]);
344
+ ```
345
+
346
+ ### DELETE Operations
347
+
348
+ ```typescript
349
+ // Delete with WHERE (required!)
350
+ userTable.where({ id: 1 }).delete();
351
+
352
+ // Soft delete (sets a timestamp column)
353
+ userTable.where({ id: 1 }).softDelete("deleted_at");
354
+
355
+ // Restore soft deleted
356
+ userTable.where({ id: 1 }).restore("deleted_at");
357
+
358
+ // Truncate table (delete all rows)
359
+ userTable.truncate();
360
+
361
+ // Delete older than timestamp
362
+ userTable.deleteOlderThan("created_at", Date.now() - 86400000);
363
+ ```
364
+
365
+ ### WHERE Conditions
366
+
367
+ ```typescript
368
+ // Simple equality
369
+ userTable.where({ active: true, role: "admin" });
370
+
371
+ // Comparison operators
372
+ userTable.whereOp("age", ">=", 18);
373
+ userTable.whereOp("created_at", "<", Date.now());
374
+
375
+ // IN / NOT IN
376
+ userTable.whereIn("status", ["active", "pending"]);
377
+ userTable.whereNotIn("role", ["banned", "suspended"]);
378
+
379
+ // BETWEEN
380
+ userTable.whereBetween("age", 18, 65);
381
+ userTable.whereNotBetween("score", 0, 50);
382
+
383
+ // NULL checks
384
+ userTable.whereNull("deleted_at");
385
+ userTable.whereNotNull("email");
386
+
387
+ // Raw expressions
388
+ userTable.whereRaw("LENGTH(name) > ?", [5]);
389
+
390
+ // Regex (client-side filtering)
391
+ userTable.whereRgx({ email: /@gmail\.com$/ });
392
+ ```
393
+
394
+ ---
395
+
396
+ ## Utility Methods
397
+
398
+ ```typescript
399
+ // Get database file path
400
+ const path = db.getPath(); // "app.db" or ":memory:"
401
+
402
+ // Direct SQL execution
403
+ db.run("CREATE INDEX idx_email ON users(email)");
404
+
405
+ // Prepare statements
406
+ const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
407
+
408
+ // Transactions
409
+ db.transaction(() => {
410
+ userTable.insert({ name: "User 1" });
411
+ userTable.insert({ name: "User 2" });
412
+ });
413
+
414
+ // Manual transaction control
415
+ db.begin();
416
+ try {
417
+ // ... operations
418
+ db.commit();
419
+ } catch (e) {
420
+ db.rollback();
421
+ }
422
+
423
+ // Savepoints
424
+ db.savepoint("my_savepoint");
425
+ db.releaseSavepoint("my_savepoint");
426
+ db.rollbackToSavepoint("my_savepoint");
427
+
428
+ // Database maintenance
429
+ db.vacuum();
430
+ db.analyze();
431
+ const integrity = db.integrityCheck();
432
+
433
+ // Schema inspection
434
+ const schema = db.getSchema();
435
+ const tableInfo = db.getTableInfo("users");
436
+ const indexes = db.getIndexes("users");
437
+ const foreignKeys = db.getForeignKeys("users");
438
+ ```
439
+
440
+ ---
441
+
442
+ ## Column Helpers
443
+
444
+ ```typescript
445
+ import { column } from "@dockstat/sqlite-wrapper";
446
+
447
+ column.id(); // INTEGER PRIMARY KEY AUTOINCREMENT
448
+ column.text({ notNull: true, unique: true });
449
+ column.integer({ default: 0 });
450
+ column.real();
451
+ column.blob();
452
+ column.boolean({ default: false });
453
+ column.json({ validateJson: true });
454
+ column.date();
455
+ column.datetime();
456
+ column.timestamp();
457
+ column.varchar(255);
458
+ column.char(10);
459
+ column.numeric({ precision: 10, scale: 2 });
460
+ column.uuid({ generateDefault: true });
461
+ column.createdAt();
462
+ column.updatedAt();
463
+ column.foreignKey("other_table", "id", { onDelete: "CASCADE" });
464
+ column.enum(["pending", "active", "completed"]);
465
+ ```
466
+
467
+ ---
468
+
469
+ ## Package Structure
470
+
471
+ The package is organized into modular components for maintainability:
472
+
473
+ ```
474
+ @dockstat/sqlite-wrapper
475
+ ├── index.ts # Main exports & DB class
476
+ ├── types.ts # Type definitions & column helpers
477
+ ├── query-builder/
478
+ │ ├── index.ts # QueryBuilder facade
479
+ │ ├── base.ts # Base class with shared functionality
480
+ │ ├── where.ts # WHERE clause building
481
+ │ ├── select.ts # SELECT operations
482
+ │ ├── insert.ts # INSERT operations
483
+ │ ├── update.ts # UPDATE operations
484
+ │ └── delete.ts # DELETE operations
485
+ └── utils/
486
+ ├── index.ts # Utility exports
487
+ ├── logger.ts # Structured logging (wraps @dockstat/logger)
488
+ ├── sql.ts # SQL building utilities
489
+ └── transformer.ts # Row serialization/deserialization
490
+ ```
491
+
492
+ ### Using Utilities Directly
493
+
494
+ The `utils` module is exported for advanced use cases:
495
+
496
+ ```typescript
497
+ import {
498
+ quoteIdentifier,
499
+ buildPlaceholders,
500
+ transformFromDb,
501
+ createLogger,
502
+ } from "@dockstat/sqlite-wrapper/utils";
503
+
504
+ // Quote identifiers safely
505
+ const quoted = quoteIdentifier("user name"); // "user name"
506
+
507
+ // Build placeholders
508
+ const placeholders = buildPlaceholders(3); // "?, ?, ?"
509
+
510
+ // Create a custom logger
511
+ const myLogger = createLogger("my-component");
512
+ myLogger.info("Custom log message");
513
+ ```
514
+
515
+ ---
516
+
517
+ ## Logging
518
+
519
+ The package uses `@dockstat/logger` with structured, component-specific logging:
520
+
521
+ ```typescript
522
+ // Log output examples:
523
+ // 16:30:00 INFO [db:sqlite] — Database open: app.db
524
+ // 16:30:00 DEBUG [table:sqlite] — CREATE TABLE users | columns=[id, name, email]
525
+ // 16:30:00 DEBUG [select:sqlite] — SELECT | SELECT * FROM "users" WHERE "id" = ? | params=[1]
526
+ // 16:30:00 DEBUG [select:sqlite] — SELECT | rows=1
527
+ // 16:30:00 INFO [backup:sqlite] — Backup create: ./backups/backup_2024-01-15.db
528
+ ```
529
+
530
+ ### Configure Logging
531
+
532
+ Control log levels via environment variables:
533
+
534
+ ```bash
535
+ # Set log level (error, warn, info, debug)
536
+ DOCKSTAT_LOGGER_LEVEL=info
537
+
538
+ # Disable specific loggers
539
+ DOCKSTAT_LOGGER_DISABLED_LOGGERS=select,insert
540
+
541
+ # Show only specific loggers
542
+ DOCKSTAT_LOGGER_ONLY_SHOW=db,backup
543
+ ```
544
+
545
+ ---
92
546
 
93
- ## Docs & examples
547
+ ## Docs & Examples
94
548
 
95
549
  See full technical docs [here](https://outline.itsnik.de/s/9d88c471-373e-4ef2-a955-b1058eb7dc99/doc/dockstatsqlite-wrapper-Lxt4IphXI5).
96
550