@dockstat/sqlite-wrapper 1.3.3 → 1.3.4

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
@@ -12,22 +12,26 @@ Schema-first table helpers, an expressive chainable QueryBuilder, safe defaults
12
12
  ## 🆕 What's New in v1.3
13
13
 
14
14
  ### Bug Fixes
15
+
15
16
  - **Fixed Boolean parsing** — Boolean columns now correctly convert SQLite's `0`/`1` to JavaScript `true`/`false`
16
17
  - **Fixed Wrong packing** — Before the `publish` script was added, workspace dependencies were not correctly propagated
17
18
 
18
19
  ### New Features
20
+
19
21
  - **Auto-detection of JSON & Boolean columns** — No more manual parser configuration! Columns using `column.json()` or `column.boolean()` are automatically detected from schema
20
22
  - **Automatic backups with retention** — Configure `autoBackup` to create periodic backups with automatic cleanup of old files
21
23
  - **Backup & Restore API** — New `backup()`, `restore()`, and `listBackups()` methods
22
24
  - **`getPath()` method** — Get the database file path
23
25
 
24
26
  ### Architecture Improvements
27
+
25
28
  - **New `utils/` module** — Reusable utilities for SQL building, logging, and row transformation
26
29
  - **Structured logging** — Cleaner, more consistent log output with dedicated loggers per component
27
30
  - **Reduced code duplication** — Extracted common patterns into shared utilities
28
31
  - **Better maintainability** — Clearer separation of concerns across modules
29
32
 
30
33
  ### Breaking Changes
34
+
31
35
  - None! v1.3 is fully backward compatible with v1.2.x
32
36
 
33
37
  ---
@@ -284,7 +288,10 @@ const activeAdmins = userTable
284
288
  .all();
285
289
 
286
290
  // Get first match
287
- const user = userTable.select(["*"]).where({ email: "alice@example.com" }).first();
291
+ const user = userTable
292
+ .select(["*"])
293
+ .where({ email: "alice@example.com" })
294
+ .first();
288
295
 
289
296
  // Count records
290
297
  const count = userTable.where({ active: true }).count();
@@ -321,7 +328,10 @@ userTable.insertOrIgnore({ email: "existing@example.com", name: "Name" });
321
328
  userTable.insertOrReplace({ email: "existing@example.com", name: "New Name" });
322
329
 
323
330
  // Insert and get the row back
324
- const newUser = userTable.insertAndGet({ name: "Charlie", email: "charlie@example.com" });
331
+ const newUser = userTable.insertAndGet({
332
+ name: "Charlie",
333
+ email: "charlie@example.com",
334
+ });
325
335
  ```
326
336
 
327
337
  ### UPDATE Operations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dockstat/sqlite-wrapper",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "A TypeScript wrapper around bun:sqlite with type-safe query building",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -177,6 +177,9 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
177
177
 
178
178
  // ===== Execution Methods =====
179
179
 
180
+ // Use the protected static helper inherited from WhereQueryBuilder: `safeStringify`
181
+ // (Removed duplicate implementation to avoid static-side conflicts with the base class.)
182
+
180
183
  /**
181
184
  * Execute the query and return all matching rows
182
185
  *
@@ -187,6 +190,9 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
187
190
  const hasRegex = this.hasRegexConditions()
188
191
  const [query, params] = this.buildSelectQuery(!hasRegex)
189
192
 
193
+ // Info-level log for invocation
194
+ this.selectLog.info(`all | query=${query} params=${WhereQueryBuilder.safeStringify(params)}`)
195
+
190
196
  this.selectLog.query("SELECT", query, params)
191
197
 
192
198
  const rows = this.getDb()
@@ -201,6 +207,11 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
201
207
  // Apply client-side operations if needed
202
208
  const result = hasRegex ? this.applyClientSideOperations(transformed) : transformed
203
209
 
210
+ // Info-level log for returned data (length + sample)
211
+ this.selectLog.info(
212
+ `all | returned=${result.length} sample=${WhereQueryBuilder.safeStringify(result[0] ?? null)}`
213
+ )
214
+
204
215
  this.reset()
205
216
  return result
206
217
  }
@@ -216,6 +227,11 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
216
227
  const [query, params] = this.buildSelectQuery(true)
217
228
  const optimizedQuery = `${query} LIMIT 1`
218
229
 
230
+ // Info-level log for invocation
231
+ this.selectLog.info(
232
+ `get | optimizedQuery=${optimizedQuery} params=${WhereQueryBuilder.safeStringify(params)}`
233
+ )
234
+
219
235
  this.selectLog.query("SELECT (get)", optimizedQuery, params)
220
236
 
221
237
  const row = this.getDb()
@@ -225,6 +241,10 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
225
241
  this.selectLog.result("SELECT (get)", row ? 1 : 0)
226
242
 
227
243
  const result = row ? this.transformRowFromDb(row) : null
244
+
245
+ // Info-level log for returned row
246
+ this.selectLog.info(`get | returned=${WhereQueryBuilder.safeStringify(result)}`)
247
+
228
248
  this.reset()
229
249
  return result
230
250
  }
@@ -233,6 +253,9 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
233
253
  if (!this.hasRegexConditions()) {
234
254
  const [query, params] = this.buildSelectQuery(true)
235
255
 
256
+ // Info-level log for invocation
257
+ this.selectLog.info(`get | query=${query} params=${WhereQueryBuilder.safeStringify(params)}`)
258
+
236
259
  this.selectLog.query("SELECT (get)", query, params)
237
260
 
238
261
  const row = this.getDb()
@@ -242,6 +265,10 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
242
265
  this.selectLog.result("SELECT (get)", row ? 1 : 0)
243
266
 
244
267
  const result = row ? this.transformRowFromDb(row) : null
268
+
269
+ // Info-level log for returned row
270
+ this.selectLog.info(`get | returned=${WhereQueryBuilder.safeStringify(result)}`)
271
+
245
272
  this.reset()
246
273
  return result
247
274
  }
@@ -272,6 +299,11 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
272
299
  const [whereClause, whereParams] = this.buildWhereClause()
273
300
  const query = `SELECT COUNT(*) AS __count FROM ${quoteIdentifier(this.getTableName())}${whereClause}`
274
301
 
302
+ // Info-level log
303
+ this.selectLog.info(
304
+ `count | query=${query} params=${WhereQueryBuilder.safeStringify(whereParams)}`
305
+ )
306
+
275
307
  this.selectLog.query("COUNT", query, whereParams)
276
308
 
277
309
  const result = this.getDb()
@@ -279,6 +311,10 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
279
311
  .get(...whereParams) as { __count: number } | null
280
312
 
281
313
  this.reset()
314
+
315
+ // Info-level log for returned count
316
+ this.selectLog.info(`count | returned=${result?.__count ?? 0}`)
317
+
282
318
  return result?.__count ?? 0
283
319
  }
284
320
 
@@ -297,6 +333,11 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
297
333
  const subquery = `SELECT 1 FROM ${quoteIdentifier(this.getTableName())}${whereClause} LIMIT 1`
298
334
  const query = `SELECT EXISTS(${subquery}) AS __exists`
299
335
 
336
+ // Info-level log
337
+ this.selectLog.info(
338
+ `exists | query=${query} params=${WhereQueryBuilder.safeStringify(whereParams)}`
339
+ )
340
+
300
341
  this.selectLog.query("EXISTS", query, whereParams)
301
342
 
302
343
  const result = this.getDb()
@@ -304,6 +345,10 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
304
345
  .get(...whereParams) as { __exists: number } | null
305
346
 
306
347
  this.reset()
348
+
349
+ // Info-level log for returned boolean
350
+ this.selectLog.info(`exists | returned=${Boolean(result?.__exists)}`)
351
+
307
352
  return Boolean(result?.__exists)
308
353
  }
309
354
 
@@ -319,7 +364,14 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
319
364
  */
320
365
  value<K extends keyof T>(column: K): T[K] | null {
321
366
  const row = this.first()
322
- return row ? row[column] : null
367
+ const value = row ? row[column] : null
368
+
369
+ // Info-level log
370
+ this.selectLog.info(
371
+ `value | column=${String(column)} returned=${WhereQueryBuilder.safeStringify(value)}`
372
+ )
373
+
374
+ return value
323
375
  }
324
376
 
325
377
  /**
@@ -330,6 +382,13 @@ export class SelectQueryBuilder<T extends Record<string, unknown>> extends Where
330
382
  */
331
383
  pluck<K extends keyof T>(column: K): T[K][] {
332
384
  const rows = this.all()
333
- return rows.map((row) => row[column])
385
+ const values = rows.map((row) => row[column])
386
+
387
+ // Info-level log
388
+ this.selectLog.info(
389
+ `pluck | column=${String(column)} returned=${WhereQueryBuilder.safeStringify(values)}`
390
+ )
391
+
392
+ return values
334
393
  }
335
394
  }
@@ -54,6 +54,15 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
54
54
 
55
55
  // ===== Public WHERE Methods =====
56
56
 
57
+ // Helper to stringify values safely (converts RegExp to string and falls back)
58
+ protected static safeStringify(obj: unknown): string {
59
+ try {
60
+ return JSON.stringify(obj, (_k, v) => (v instanceof RegExp ? v.toString() : v))
61
+ } catch {
62
+ return String(obj)
63
+ }
64
+ }
65
+
57
66
  /**
58
67
  * Add simple equality conditions to the WHERE clause
59
68
  *
@@ -66,6 +75,9 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
66
75
  * // WHERE "deleted_at" IS NULL
67
76
  */
68
77
  where(conditions: WhereCondition<T>): this {
78
+ // Log invocation with a safe serializer (handles RegExp and other non-JSON types)
79
+ this.log.info(`where | conditions=${WhereQueryBuilder.safeStringify(conditions)}`)
80
+
69
81
  for (const [column, value] of Object.entries(conditions)) {
70
82
  this.removeExistingCondition(column)
71
83
 
@@ -76,6 +88,14 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
76
88
  this.state.whereParams.push(this.toSqliteValue(value))
77
89
  }
78
90
  }
91
+
92
+ // Log resulting WHERE clause state
93
+ this.log.info(
94
+ `where | whereConditions=${WhereQueryBuilder.safeStringify(this.state.whereConditions)} params=${WhereQueryBuilder.safeStringify(
95
+ this.state.whereParams
96
+ )}`
97
+ )
98
+
79
99
  return this
80
100
  }
81
101
 
@@ -86,6 +106,9 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
86
106
  * .whereRgx({ email: /@gmail\.com$/ })
87
107
  */
88
108
  whereRgx(conditions: RegexCondition<T>): this {
109
+ // Log invocation with a safe serializer for regex values
110
+ this.log.info(`whereRgx | conditions=${WhereQueryBuilder.safeStringify(conditions)}`)
111
+
89
112
  for (const [column, value] of Object.entries(conditions)) {
90
113
  this.removeExistingCondition(column)
91
114
 
@@ -105,6 +128,14 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
105
128
  this.state.whereParams.push(value as SQLQueryBindings)
106
129
  }
107
130
  }
131
+
132
+ // Log resulting WHERE/regex state
133
+ this.log.info(
134
+ `whereRgx | whereConditions=${WhereQueryBuilder.safeStringify(this.state.whereConditions)} regexConditions=${WhereQueryBuilder.safeStringify(
135
+ this.state.regexConditions
136
+ )} params=${WhereQueryBuilder.safeStringify(this.state.whereParams)}`
137
+ )
138
+
108
139
  return this
109
140
  }
110
141
 
@@ -116,6 +147,9 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
116
147
  * .whereExpr("created_at > datetime('now', '-1 day')")
117
148
  */
118
149
  whereExpr(expr: string, params: SQLQueryBindings[] = []): this {
150
+ // Log invocation
151
+ this.log.info(`whereExpr | expr=${expr} params=${WhereQueryBuilder.safeStringify(params)}`)
152
+
119
153
  if (!expr || typeof expr !== "string") {
120
154
  throw new Error("whereExpr: expression must be a non-empty string")
121
155
  }
@@ -127,6 +161,13 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
127
161
  this.state.whereParams.push(...params)
128
162
  }
129
163
 
164
+ // Log resulting WHERE clause state
165
+ this.log.info(
166
+ `whereExpr | whereConditions=${WhereQueryBuilder.safeStringify(this.state.whereConditions)} params=${WhereQueryBuilder.safeStringify(
167
+ this.state.whereParams
168
+ )}`
169
+ )
170
+
130
171
  return this
131
172
  }
132
173
 
@@ -134,6 +175,8 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
134
175
  * Alias for whereExpr
135
176
  */
136
177
  whereRaw(expr: string, params: SQLQueryBindings[] = []): this {
178
+ // Log alias invocation
179
+ this.log.info(`whereRaw | expr=${expr} params=${WhereQueryBuilder.safeStringify(params)}`)
137
180
  return this.whereExpr(expr, params)
138
181
  }
139
182
 
@@ -145,6 +188,11 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
145
188
  * // WHERE "status" IN (?, ?)
146
189
  */
147
190
  whereIn(column: keyof T, values: SQLQueryBindings[]): this {
191
+ // Log invocation
192
+ this.log.info(
193
+ `whereIn | column=${String(column)} values=${WhereQueryBuilder.safeStringify(values)}`
194
+ )
195
+
148
196
  if (!Array.isArray(values) || values.length === 0) {
149
197
  throw new Error("whereIn: values must be a non-empty array")
150
198
  }
@@ -155,6 +203,13 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
155
203
  this.state.whereConditions.push(sql)
156
204
  this.state.whereParams.push(...params)
157
205
 
206
+ // Log resulting WHERE clause state
207
+ this.log.info(
208
+ `whereIn | whereConditions=${WhereQueryBuilder.safeStringify(this.state.whereConditions)} params=${WhereQueryBuilder.safeStringify(
209
+ this.state.whereParams
210
+ )}`
211
+ )
212
+
158
213
  return this
159
214
  }
160
215
 
@@ -166,6 +221,11 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
166
221
  * // WHERE "role" NOT IN (?, ?)
167
222
  */
168
223
  whereNotIn(column: keyof T, values: SQLQueryBindings[]): this {
224
+ // Log invocation
225
+ this.log.info(
226
+ `whereNotIn | column=${String(column)} values=${WhereQueryBuilder.safeStringify(values)}`
227
+ )
228
+
169
229
  if (!Array.isArray(values) || values.length === 0) {
170
230
  throw new Error("whereNotIn: values must be a non-empty array")
171
231
  }
@@ -176,6 +236,13 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
176
236
  this.state.whereConditions.push(sql)
177
237
  this.state.whereParams.push(...params)
178
238
 
239
+ // Log resulting WHERE clause state
240
+ this.log.info(
241
+ `whereNotIn | whereConditions=${WhereQueryBuilder.safeStringify(this.state.whereConditions)} params=${WhereQueryBuilder.safeStringify(
242
+ this.state.whereParams
243
+ )}`
244
+ )
245
+
179
246
  return this
180
247
  }
181
248
 
@@ -189,6 +256,11 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
189
256
  * .whereOp("name", "LIKE", "%smith%")
190
257
  */
191
258
  whereOp(column: keyof T, op: string, value: SQLQueryBindings): this {
259
+ // Log invocation
260
+ this.log.info(
261
+ `whereOp | column=${String(column)} op=${op} value=${WhereQueryBuilder.safeStringify(value)}`
262
+ )
263
+
192
264
  const normalizedOp = normalizeOperator(op)
193
265
  const columnStr = String(column)
194
266
 
@@ -196,10 +268,13 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
196
268
  if (value === null || value === undefined) {
197
269
  if (normalizedOp === "=" || normalizedOp === "IS") {
198
270
  this.state.whereConditions.push(`${quoteIdentifier(columnStr)} IS NULL`)
271
+ // Log resulting state for null-case
272
+ this.log.info(`whereOp | added IS NULL for ${columnStr}`)
199
273
  return this
200
274
  }
201
275
  if (normalizedOp === "!=" || normalizedOp === "<>" || normalizedOp === "IS NOT") {
202
276
  this.state.whereConditions.push(`${quoteIdentifier(columnStr)} IS NOT NULL`)
277
+ this.log.info(`whereOp | added IS NOT NULL for ${columnStr}`)
203
278
  return this
204
279
  }
205
280
  }
@@ -207,6 +282,13 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
207
282
  this.state.whereConditions.push(`${quoteIdentifier(columnStr)} ${normalizedOp} ?`)
208
283
  this.state.whereParams.push(value)
209
284
 
285
+ // Log resulting WHERE clause state
286
+ this.log.info(
287
+ `whereOp | whereConditions=${WhereQueryBuilder.safeStringify(this.state.whereConditions)} params=${WhereQueryBuilder.safeStringify(
288
+ this.state.whereParams
289
+ )}`
290
+ )
291
+
210
292
  return this
211
293
  }
212
294
 
@@ -218,12 +300,26 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
218
300
  * // WHERE "age" BETWEEN ? AND ?
219
301
  */
220
302
  whereBetween(column: keyof T, min: SQLQueryBindings, max: SQLQueryBindings): this {
303
+ // Log invocation
304
+ this.log.info(
305
+ `whereBetween | column=${String(column)} min=${WhereQueryBuilder.safeStringify(min)} max=${WhereQueryBuilder.safeStringify(
306
+ max
307
+ )}`
308
+ )
309
+
221
310
  this.removeExistingCondition(String(column), "BETWEEN")
222
311
 
223
312
  const { sql, params } = buildBetweenClause(String(column), min, max, false)
224
313
  this.state.whereConditions.push(sql)
225
314
  this.state.whereParams.push(...params)
226
315
 
316
+ // Log resulting WHERE clause state
317
+ this.log.info(
318
+ `whereBetween | whereConditions=${WhereQueryBuilder.safeStringify(this.state.whereConditions)} params=${WhereQueryBuilder.safeStringify(
319
+ this.state.whereParams
320
+ )}`
321
+ )
322
+
227
323
  return this
228
324
  }
229
325
 
@@ -235,12 +331,26 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
235
331
  * // WHERE "score" NOT BETWEEN ? AND ?
236
332
  */
237
333
  whereNotBetween(column: keyof T, min: SQLQueryBindings, max: SQLQueryBindings): this {
334
+ // Log invocation
335
+ this.log.info(
336
+ `whereNotBetween | column=${String(column)} min=${WhereQueryBuilder.safeStringify(min)} max=${WhereQueryBuilder.safeStringify(
337
+ max
338
+ )}`
339
+ )
340
+
238
341
  this.removeExistingCondition(String(column), "NOT BETWEEN")
239
342
 
240
343
  const { sql, params } = buildBetweenClause(String(column), min, max, true)
241
344
  this.state.whereConditions.push(sql)
242
345
  this.state.whereParams.push(...params)
243
346
 
347
+ // Log resulting WHERE clause state
348
+ this.log.info(
349
+ `whereNotBetween | whereConditions=${WhereQueryBuilder.safeStringify(this.state.whereConditions)} params=${WhereQueryBuilder.safeStringify(
350
+ this.state.whereParams
351
+ )}`
352
+ )
353
+
244
354
  return this
245
355
  }
246
356
 
@@ -252,8 +362,19 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
252
362
  * // WHERE "deleted_at" IS NULL
253
363
  */
254
364
  whereNull(column: keyof T): this {
365
+ // Log invocation
366
+ this.log.info(`whereNull | column=${String(column)}`)
367
+
255
368
  this.removeExistingCondition(String(column))
256
369
  this.state.whereConditions.push(`${quoteIdentifier(String(column))} IS NULL`)
370
+
371
+ // Log resulting WHERE clause state
372
+ this.log.info(
373
+ `whereNull | whereConditions=${WhereQueryBuilder.safeStringify(this.state.whereConditions)} params=${WhereQueryBuilder.safeStringify(
374
+ this.state.whereParams
375
+ )}`
376
+ )
377
+
257
378
  return this
258
379
  }
259
380
 
@@ -265,8 +386,19 @@ export class WhereQueryBuilder<T extends Record<string, unknown>> extends BaseQu
265
386
  * // WHERE "email" IS NOT NULL
266
387
  */
267
388
  whereNotNull(column: keyof T): this {
389
+ // Log invocation
390
+ this.log.info(`whereNotNull | column=${String(column)}`)
391
+
268
392
  this.removeExistingCondition(String(column))
269
393
  this.state.whereConditions.push(`${quoteIdentifier(String(column))} IS NOT NULL`)
394
+
395
+ // Log resulting WHERE clause state
396
+ this.log.info(
397
+ `whereNotNull | whereConditions=${WhereQueryBuilder.safeStringify(this.state.whereConditions)} params=${WhereQueryBuilder.safeStringify(
398
+ this.state.whereParams
399
+ )}`
400
+ )
401
+
270
402
  return this
271
403
  }
272
404
  }