@dockstat/sqlite-wrapper 1.0.0 → 1.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,578 +1,53 @@
1
- # @dockstat/sql-wrapper
1
+ # @dockstat/sqlite-wrapper
2
2
 
3
- A tiny TypeScript wrapper around Bun's `bun:sqlite` (`Database`) that gives you:
3
+ ![Bun](https://img.shields.io/badge/Bun-%23000000.svg?style=for-the-badge&logo=bun&logoColor=white)
4
+ ![SQLite](https://img.shields.io/badge/sqlite-%2307405e.svg?style=for-the-badge&logo=sqlite&logoColor=white)
5
+ ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)
4
6
 
5
- * **`DB`** (default export) — open/close DB, create tables, run `PRAGMA` statements, load extensions, and get a typed `QueryBuilder<T>`.
6
- * **`QueryBuilder<T>`** expressive, chainable query builder with:
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.
7
9
 
8
- **SELECT operations:**
9
- * plain equality `.where(...)`
10
- * regex `.whereRgx(...)` (applied client-side)
11
- * raw fragments `.whereRaw(...)` / `.whereExpr(...)`
12
- * `IN` helper `.whereIn(...)`
13
- * operator helper `.whereOp(...)`
14
- * ordering, limit, offset, and result helpers: `all()`, `get()`, `first()`, `count()`
10
+ ## Install
11
+ > Requires **Bun** runtime
15
12
 
16
- **INSERT operations:**
17
- * single/bulk inserts `.insert(...)`
18
- * conflict resolution `.insertOrIgnore(...)`, `.insertOrReplace(...)`
13
+ ```bash
14
+ bun add @dockstat/sqlite-wrapper
15
+ ````
19
16
 
20
- **UPDATE operations:**
21
- * safe updates `.update(...)` (requires WHERE conditions)
22
-
23
- **DELETE operations:**
24
- * safe deletes `.delete()` (requires WHERE conditions)
25
-
26
- > **Important** — Bun's `bun:sqlite` currently doesn't provide a JS `registerFunction` to add custom SQL functions. Regex conditions added with `whereRgx()` are collected and applied **in JavaScript** after fetching rows that match the non-regex SQL conditions. See the **Performance** section below for implications.
27
-
28
- ---
29
-
30
- ## Quick start
17
+ ## 10-second quickstart
31
18
 
32
19
  ```ts
33
- import DB, { QueryBuilder } from "./db";
20
+ import { DB, column } from "@dockstat/sqlite-wrapper";
34
21
 
35
- interface User {
36
- id: number;
37
- name: string;
38
- email: string;
39
- type: string;
40
- created_at: number;
41
- }
22
+ const db = new DB("app.db", { pragmas: [["journal_mode","WAL"], ["foreign_keys","ON"]] });
42
23
 
43
- // Create DB with PRAGMA settings and an extension
44
- const db = new DB("app.db", {
45
- pragmas: [
46
- ["journal_mode", "WAL"],
47
- ["synchronous", "NORMAL"],
48
- ["foreign_keys", "ON"]
49
- ],
50
- loadExtensions: [
51
- "/absolute/path/to/my_extension" // optional compiled SQLite extension
52
- ]
24
+ db.createTable("users", {
25
+ id: column.id(),
26
+ name: column.text({ notNull: true }),
27
+ email: column.text({ unique: true, notNull: true }),
28
+ created_at: column.createdAt()
53
29
  });
54
30
 
55
- // create table (object style)
56
- db.createTable(
57
- "users",
58
- {
59
- id: "INTEGER PRIMARY KEY AUTOINCREMENT",
60
- name: "TEXT NOT NULL",
61
- email: "TEXT UNIQUE NOT NULL",
62
- type: "TEXT NOT NULL",
63
- created_at: "INTEGER NOT NULL DEFAULT (strftime('%s','now'))"
64
- },
65
- { ifNotExists: true }
66
- );
67
-
68
- // SELECT - basic select (SQL filter only)
69
- const containers = db
70
- .table<User>("users")
71
- .select(["id", "name", "email"])
72
- .where({ type: "container" }) // SQL: type = 'container'
73
- .orderBy("id")
74
- .desc()
31
+ const users = db.table("users")
32
+ .select(["id","name","email"])
33
+ .where({ active: true })
34
+ .orderBy("created_at").desc()
75
35
  .limit(10)
76
36
  .all();
77
-
78
- console.log(containers);
79
-
80
- // SELECT - regex + SQL filters together
81
- const gmailUsers = db
82
- .table<User>("users")
83
- .select(["id", "email"])
84
- .where({ type: "container" }) // SQL
85
- .whereRgx({ email: /@gmail\.com$/i }) // client-side regex
86
- .all();
87
-
88
- console.log(gmailUsers);
89
-
90
- // INSERT - single row
91
- const insertResult = db
92
- .table<User>("users")
93
- .insert({
94
- name: "John Doe",
95
- email: "john@example.com",
96
- type: "container"
97
- });
98
-
99
- console.log(`Inserted user with ID: ${insertResult.insertId}`);
100
-
101
- // INSERT - multiple rows with conflict resolution
102
- const bulkResult = db
103
- .table<User>("users")
104
- .insertOrIgnore([
105
- { name: "Alice", email: "alice@example.com", type: "container" },
106
- { name: "Bob", email: "bob@example.com", type: "container" }
107
- ]);
108
-
109
- console.log(`Inserted ${bulkResult.changes} users`);
110
-
111
- // UPDATE - with WHERE conditions
112
- const updateResult = db
113
- .table<User>("users")
114
- .where({ type: "container" })
115
- .whereRgx({ email: /@gmail\.com$/i })
116
- .update({ type: "gmail_user" });
117
-
118
- console.log(`Updated ${updateResult.changes} users`);
119
-
120
- // DELETE - with WHERE conditions
121
- const deleteResult = db
122
- .table<User>("users")
123
- .where({ type: "gmail_user" })
124
- .delete();
125
-
126
- console.log(`Deleted ${deleteResult.changes} users`);
127
-
128
- db.close();
129
- ```
130
-
131
- ---
132
-
133
- ## 1) Creating tables
134
-
135
- **Object style (recommended):**
136
-
137
- ```ts
138
- db.createTable("posts", {
139
- id: "INTEGER PRIMARY KEY AUTOINCREMENT",
140
- title: "TEXT NOT NULL",
141
- body: "TEXT",
142
- author_id: "INTEGER NOT NULL",
143
- published: "INTEGER NOT NULL DEFAULT 0"
144
- }, { ifNotExists: true });
145
- ```
146
-
147
- **String style:**
148
-
149
- ```ts
150
- db.createTable(
151
- "events",
152
- `id INTEGER PRIMARY KEY,
153
- name TEXT NOT NULL,
154
- occurred_at INTEGER NOT NULL`
155
- );
156
- ```
157
-
158
- ---
159
-
160
- ## 2) Running PRAGMA statements
161
-
162
- You can pass PRAGMAs at construction:
163
-
164
- ```ts
165
- const db = new DB("app.db", {
166
- pragmas: [
167
- ["journal_mode", "WAL"],
168
- ["cache_size", -64000]
169
- ]
170
- });
171
- ```
172
-
173
- Or run them later:
174
-
175
- ```ts
176
- db.pragma("cache_size", -32000);
177
- const foreignKeysOn = db.pragma("foreign_keys");
178
- console.log(foreignKeysOn); // should print ON
179
- ```
180
-
181
- ---
182
-
183
- ## 3) Loading SQLite extensions
184
-
185
- At DB construction:
186
-
187
- ```ts
188
- const db = new DB("app.db", {
189
- loadExtensions: [
190
- "/absolute/path/to/regexp_extension"
191
- ]
192
- });
193
- ```
194
-
195
- Or after creation:
196
-
197
- ```ts
198
- db.loadExtension("/absolute/path/to/my_extension");
199
- ```
200
-
201
- ---
202
-
203
- ## 4) Basic QueryBuilder usage
204
-
205
- **Select specific columns:**
206
-
207
- ```ts
208
- const users = db
209
- .table<User>("users")
210
- .select(["id", "name"])
211
- .where({ type: "container" })
212
- .all();
213
- ```
214
-
215
- **Select all columns:**
216
-
217
- ```ts
218
- const allUsers = db.table<User>("users").select(["*"]).all();
219
- ```
220
-
221
- **Get a single row:**
222
-
223
- ```ts
224
- const userOrNull = db.table<User>("users").where({ id: 1 }).get();
225
- ```
226
-
227
- **Get first matching row:**
228
-
229
- ```ts
230
- const first = db.table<User>("users").where({ type: "container" }).first();
231
- ```
232
-
233
- **Count rows:**
234
-
235
- ```ts
236
- const count = db.table<User>("users").where({ type: "container" }).count();
237
- ```
238
-
239
- ---
240
-
241
- ## 5) Mixing equality and regex
242
-
243
- Plain equality uses `.where()` (SQL). Regex uses `.whereRgx()` (client-side):
244
-
245
- ```ts
246
- const matches = db
247
- .table<User>("users")
248
- .select(["id", "name", "email", "type"])
249
- .where({ type: "container" }) // SQL
250
- .whereRgx({ email: /@example\.com$/i }) // JS regex after SQL fetch
251
- .orderBy("created_at")
252
- .desc()
253
- .limit(50)
254
- .all();
255
- ```
256
-
257
- > **Note:** If `.whereRgx()` is used, ordering, offset, and limit are applied in JS after regex filtering.
258
-
259
- ---
260
-
261
- ## 6) Raw expressions, IN, and operators
262
-
263
- **Raw WHERE fragment:**
264
-
265
- ```ts
266
- const rows = db
267
- .table<User>("users")
268
- .whereRaw("created_at > ? AND published = ?", [1625097600, 1])
269
- .all();
270
37
  ```
271
38
 
272
- **IN clause:**
273
-
274
- ```ts
275
- const some = db.table<User>("users").whereIn("id", [1, 2, 5, 7]).all();
276
- ```
277
-
278
- **Operator helper:**
279
-
280
- ```ts
281
- db.table<User>("users")
282
- .whereOp("created_at", ">", 1622505600)
283
- .whereOp("email", "LIKE", "%@gmail.com")
284
- .all();
285
- ```
286
-
287
- ---
288
-
289
- ## 7) Pagination
290
-
291
- ```ts
292
- const page = 2;
293
- const pageSize = 20;
294
-
295
- const paged = db
296
- .table<User>("users")
297
- .where({ type: "container" })
298
- .orderBy("created_at")
299
- .desc()
300
- .offset((page - 1) * pageSize)
301
- .limit(pageSize)
302
- .all();
303
- ```
304
-
305
- > With `.whereRgx()`, paging happens in JS after filtering.
306
-
307
- ---
308
-
309
- ## 8) INSERT operations
310
-
311
- **Single row insert:**
312
-
313
- ```ts
314
- const result = db
315
- .table<User>("users")
316
- .insert({
317
- name: "John Doe",
318
- email: "john@example.com",
319
- type: "container"
320
- });
321
-
322
- console.log(result.insertId, result.changes);
323
- ```
324
-
325
- **Bulk insert:**
326
-
327
- ```ts
328
- const users = [
329
- { name: "Alice", email: "alice@example.com", type: "container" },
330
- { name: "Bob", email: "bob@example.com", type: "container" }
331
- ];
332
-
333
- const result = db.table<User>("users").insert(users);
334
- console.log(`Inserted ${result.changes} users`);
335
- ```
336
-
337
- **Insert with conflict resolution:**
338
-
339
- ```ts
340
- // INSERT OR IGNORE - skip duplicates
341
- const result1 = db.table<User>("users").insertOrIgnore({
342
- name: "Duplicate User",
343
- email: "existing@example.com"
344
- });
345
-
346
- // INSERT OR REPLACE - replace duplicates
347
- const result2 = db.table<User>("users").insertOrReplace({
348
- name: "Updated User",
349
- email: "existing@example.com"
350
- });
351
-
352
- // Custom conflict resolution
353
- const result3 = db.table<User>("users").insert(userData, {
354
- orIgnore: true,
355
- // or: orReplace, orAbort, orFail, orRollback
356
- });
357
- ```
358
-
359
- ---
360
-
361
- ## 9) UPDATE operations
362
-
363
- **Basic update (requires WHERE conditions):**
364
-
365
- ```ts
366
- const result = db
367
- .table<User>("users")
368
- .where({ id: 1 })
369
- .update({ name: "Updated Name", type: "admin" });
370
-
371
- console.log(`Updated ${result.changes} rows`);
372
- ```
373
-
374
- **Update with complex conditions:**
375
-
376
- ```ts
377
- const result = db
378
- .table<User>("users")
379
- .where({ type: "container" })
380
- .whereRgx({ email: /@gmail\.com$/i })
381
- .update({ type: "gmail_container" });
382
- ```
383
-
384
- **Update with raw WHERE conditions:**
385
-
386
- ```ts
387
- const result = db
388
- .table<User>("users")
389
- .whereRaw("created_at > ? AND active = ?", [Date.now() - 86400000, true])
390
- .update({ last_active: Date.now() });
391
- ```
392
-
393
- > **Safety Note:** UPDATE operations require at least one WHERE condition to prevent accidental full table updates.
394
-
395
- ---
396
-
397
- ## 10) DELETE operations
398
-
399
- **Basic delete (requires WHERE conditions):**
400
-
401
- ```ts
402
- const result = db
403
- .table<User>("users")
404
- .where({ active: false })
405
- .delete();
406
-
407
- console.log(`Deleted ${result.changes} rows`);
408
- ```
409
-
410
- **Delete with complex conditions:**
411
-
412
- ```ts
413
- const result = db
414
- .table<User>("users")
415
- .where({ type: "temporary" })
416
- .whereOp("created_at", "<", Date.now() - 604800000) // older than 1 week
417
- .delete();
418
- ```
419
-
420
- **Delete with regex conditions:**
421
-
422
- ```ts
423
- const result = db
424
- .table<User>("users")
425
- .whereRgx({ email: /^test.*@example\.com$/i })
426
- .delete();
427
- ```
428
-
429
- > **Safety Note:** DELETE operations require at least one WHERE condition to prevent accidental full table deletion.
430
-
431
- ---
432
-
433
- ## 11) Complex query example
434
-
435
- ```ts
436
- const results = db
437
- .table<User>("users")
438
- .select(["id", "name", "email", "created_at"])
439
- .whereRaw("created_at > ?", [1672531200])
440
- .whereIn("id", [10, 20, 30, 40])
441
- .whereRgx({ email: /\b(hey@example|test@example)\.com$/i })
442
- .orderBy("created_at")
443
- .desc()
444
- .limit(100)
445
- .all();
446
- ```
447
-
448
- ---
449
-
450
- ## 12) Result types and error handling
451
-
452
- **INSERT results:**
453
-
454
- ```ts
455
- interface InsertResult {
456
- insertId: number; // rowid of the last inserted row
457
- changes: number; // number of rows inserted
458
- }
459
- ```
460
-
461
- **UPDATE/DELETE results:**
462
-
463
- ```ts
464
- interface UpdateResult {
465
- changes: number; // number of rows affected
466
- }
467
-
468
- interface DeleteResult {
469
- changes: number; // number of rows deleted
470
- }
471
- ```
472
-
473
- **Error handling:**
474
-
475
- ```ts
476
- try {
477
- // This will throw - UPDATE without WHERE conditions
478
- db.table<User>("users").update({ active: true });
479
- } catch (error) {
480
- console.error("UPDATE operation requires WHERE conditions");
481
- }
482
-
483
- try {
484
- // This will throw - empty insert data
485
- db.table<User>("users").insert({});
486
- } catch (error) {
487
- console.error("Insert data cannot be empty");
488
- }
489
- ```
490
-
491
- ---
492
-
493
- ## Advanced: Native `REGEXP` support
494
-
495
- If you load a compiled SQLite extension that adds `REGEXP`, you can do regex filtering directly in SQL:
496
-
497
- ```ts
498
- db.loadExtension("/path/to/regexp_extension");
499
- const rows = db
500
- .table<User>("users")
501
- .whereExpr("email REGEXP ?", ["@example\\.com$"])
502
- .all();
503
- ```
504
-
505
- ---
506
-
507
- ## Performance & Practical tips
508
-
509
- ### SELECT performance:
510
- * Prefer `.where()` SQL filters for large datasets — `.whereRgx()` fetches rows and filters in JS.
511
- * Use `.whereIn()` and `.whereRaw()` to reduce rows before regex filtering.
512
- * Use indexes on frequently queried columns.
513
-
514
- ### INSERT performance:
515
- * Use bulk inserts with arrays for multiple rows instead of individual insert calls.
516
- * Consider transaction wrapping for large batch operations.
517
- * Use appropriate conflict resolution (`OR IGNORE`, `OR REPLACE`) to avoid exception handling.
518
-
519
- ### UPDATE/DELETE with regex:
520
- * When using `.whereRgx()`, the operation fetches candidate rows first, then applies regex filtering client-side.
521
- * For better performance with regex conditions, consider using SQL `LIKE` patterns where possible.
522
-
523
- ### General tips:
524
- * Use PRAGMAs to tune performance (e.g. WAL mode, cache size).
525
- * Load extensions to enable advanced SQLite features without manual filtering.
526
- * Always use WHERE conditions for UPDATE/DELETE to prevent accidents.
527
- * The library automatically handles parameter binding to prevent SQL injection.
528
-
529
- ---
530
-
531
- ## API Reference
532
-
533
- ### `DB` (default export)
534
-
535
- * `constructor(path: string, opts?: { pragmas?: [string, any][], loadExtensions?: string[] })`
536
- * `table<T>(tableName: string): QueryBuilder<T>`
537
- * `createTable(tableName: string, columns: string | Record<string,string>, options?: { ifNotExists?: boolean; withoutRowId?: boolean })`
538
- * `pragma(name: string, value?: any): any`
539
- * `loadExtension(path: string): void`
540
- * `close(): void`
541
-
542
- ### `QueryBuilder<T>` (named export)
543
-
544
- **WHERE methods (shared across operations):**
545
- * `where(cond: Partial<Record<keyof T, string | number | boolean | null>>)`
546
- * `whereRgx(cond: Partial<Record<keyof T, string | RegExp>>)`
547
- * `whereExpr(expr: string, params?: any[])`
548
- * `whereRaw(expr: string, params?: any[])`
549
- * `whereIn(column: keyof T, values: any[])`
550
- * `whereOp(column: keyof T, op: string, value: any)`
551
-
552
- **SELECT methods:**
553
- * `select(columns: Array<keyof T> | ["*"])`
554
- * `orderBy(column: keyof T)`
555
- * `asc()`, `desc()`
556
- * `limit(n)`, `offset(n)`
557
- * `all(): T[]`, `get(): T | null`, `first(): T | null`, `count(): number`
39
+ ## Why use it?
558
40
 
559
- **INSERT methods:**
560
- * `insert(data: Partial<T> | Partial<T>[], options?: InsertOptions): InsertResult`
561
- * `insertOrIgnore(data: Partial<T> | Partial<T>[]): InsertResult`
562
- * `insertOrReplace(data: Partial<T> | Partial<T>[]): InsertResult`
41
+ * ⚡ Bun-native, high-performance bindings
42
+ * 🔒 Type-safe table & query APIs (compile-time checks)
43
+ * 🧭 Full SQLite feature support: JSON, generated columns, foreign keys, indexes
44
+ * 🛡️ Safety-first defaults prevents accidental full-table updates/deletes
45
+ * 🚀 Designed for production workflows: WAL, pragmatic PRAGMAs, bulk ops, transactions
563
46
 
564
- **UPDATE methods:**
565
- * `update(data: Partial<T>): UpdateResult` *(requires WHERE conditions)*
47
+ ## Docs & examples
566
48
 
567
- **DELETE methods:**
568
- * `delete(): DeleteResult` *(requires WHERE conditions)*
49
+ See full technical docs in `docs/technical.md` and `examples/` for e-commerce, migrations, and transactions.
569
50
 
570
- ### Types (named exports)
51
+ ## License
571
52
 
572
- * `InsertResult` `{ insertId: number, changes: number }`
573
- * `UpdateResult` — `{ changes: number }`
574
- * `DeleteResult` — `{ changes: number }`
575
- * `InsertOptions` — conflict resolution options
576
- * `ColumnNames<T>` — column selection type
577
- * `WhereCondition<T>` — WHERE condition type
578
- * `RegexCondition<T>` — regex condition type
53
+ MITmaintained by Dockstat. Contributions welcome.