sqlite-zod-orm 3.18.0 → 3.20.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
@@ -1,18 +1,11 @@
1
1
  # sqlite-zod-orm
2
2
 
3
- **Type-safe SQLite ORM for Bun** — Zod schemas in, fully typed database out. Zero SQL required.
4
-
5
- [![npm](https://img.shields.io/npm/v/sqlite-zod-orm)](https://www.npmjs.com/package/sqlite-zod-orm)
6
- [![license](https://img.shields.io/npm/l/sqlite-zod-orm)](./LICENSE)
7
-
8
- ## Install
3
+ Type-safe SQLite ORM for Bun — Zod schemas, fluent queries, auto relationships, zero SQL.
9
4
 
10
5
  ```bash
11
6
  bun add sqlite-zod-orm
12
7
  ```
13
8
 
14
- > **Requires Bun runtime** — uses `bun:sqlite` under the hood.
15
-
16
9
  ## Quick Start
17
10
 
18
11
  ```typescript
@@ -22,58 +15,224 @@ const db = new Database('app.db', {
22
15
  users: z.object({
23
16
  name: z.string(),
24
17
  email: z.string(),
25
- role: z.string().default('member'),
18
+ score: z.number().default(0),
26
19
  }),
20
+ posts: z.object({
21
+ title: z.string(),
22
+ body: z.string(),
23
+ userId: z.number(),
24
+ }),
25
+ }, {
26
+ relations: { posts: { userId: 'users' } },
27
+ timestamps: true,
28
+ softDeletes: true,
27
29
  });
30
+ ```
28
31
 
32
+ Tables are auto-created and auto-migrated from your Zod schemas. No SQL required.
33
+
34
+ ## CRUD
35
+
36
+ ```typescript
29
37
  // Insert
30
- const alice = db.users.insert({ name: 'Alice', email: 'alice@co.com' });
31
- alice.id; // auto-increment ID
32
- alice.role; // 'member' (from Zod default)
33
-
34
- // Query
35
- const admins = db.users.select()
36
- .where({ role: 'admin' })
37
- .orderBy('name')
38
- .all();
38
+ const user = db.users.insert({ name: 'Alice', email: 'alice@co.com' });
39
39
 
40
- // Update
41
- alice.name = 'Alice Smith'; // auto-persists
40
+ // Read
41
+ const all = db.users.select().all();
42
+ const one = db.users.get(1);
43
+ const found = db.users.select().where({ name: 'Alice' }).first();
44
+
45
+ // Update (auto-persists via proxy)
46
+ user.score = 100; // ← saved to DB automatically
42
47
 
43
48
  // Delete
44
- db.users.delete(alice.id);
49
+ db.users.delete(1);
50
+
51
+ // Batch
52
+ db.users.insertMany([{ name: 'Bob', email: 'b@co.com' }, { name: 'Charlie', email: 'c@co.com' }]);
53
+ db.users.upsertMany([{ id: 1, name: 'Updated Alice' }], 'id');
45
54
  ```
46
55
 
47
- ## Features
56
+ ## Fluent Query Builder
48
57
 
49
- - **Zod schemas → typed database** — define once, types flow everywhere
50
- - **Auto-migration** — new schema fields auto-add columns on startup
51
- - **Fluent query builder** — `.where()`, `.orderBy()`, `.limit()`, `.join()`, `.groupBy()`, `.having()`
52
- - **Rich operators** — `$gt`, `$lt`, `$in`, `$like`, `$isNull`, `$isNotNull`, and more
53
- - **Aggregates** — `.sum()`, `.avg()`, `.min()`, `.max()`, `.count()`
54
- - **Pagination** — `.paginate(page, perPage)` with metadata
55
- - **Relationships** — foreign keys, lazy navigation, fluent joins
56
- - **Reactivity** — `.on('insert' | 'update' | 'delete', callback)` with trigger-based change tracking
57
- - **Transactions** — `db.transaction(() => { ... })`
58
- - **Timestamps** — auto `createdAt`/`updatedAt` with `{ timestamps: true }`
59
- - **Soft deletes** — `{ softDeletes: true }` with `.withTrashed()`, `.onlyTrashed()`, `.restore()`
60
- - **Unique constraints** — `{ unique: { users: [['email']] } }`
61
- - **Schema introspection** — `db.tables()`, `db.columns('users')`
62
- - **Raw SQL** — `db.raw()` / `db.exec()` escape hatch
63
- - **Debug mode** — `{ debug: true }` logs all SQL to console
64
- - **Distinct** — `.distinct()` on queries
65
- - **Proxy queries** — SQL-like DSL with type-safe column references
58
+ ```typescript
59
+ db.users.select()
60
+ .where({ score: { $gte: 50 } })
61
+ .orderBy('score', 'DESC')
62
+ .limit(10)
63
+ .all();
66
64
 
67
- ## Documentation
65
+ // Operators: $gt, $gte, $lt, $lte, $ne, $like, $in, $notIn, $between
66
+ db.users.select().where({ name: { $like: '%ali%' } }).all();
68
67
 
69
- See [SKILL.md](./SKILL.md) for comprehensive documentation with examples for every feature.
68
+ // whereIn / whereNotIn (array or subquery)
69
+ db.users.select().whereIn('id', [1, 2, 3]).all();
70
+ const sub = db.orders.select('userId');
71
+ db.users.select().whereIn('id', sub).all();
70
72
 
71
- ## Tests
73
+ // Raw WHERE fragments
74
+ db.users.select().whereRaw('score > ? AND name != ?', [50, 'Bot']).all();
75
+ ```
72
76
 
73
- ```bash
74
- bun test # 160 tests, ~1.5s
77
+ ## Relationships
78
+
79
+ ```typescript
80
+ // Navigation (lazy, proxy-based)
81
+ const post = db.posts.get(1);
82
+ post.user; // → related user object
83
+ const user = db.users.get(1);
84
+ user.posts; // → array of user's posts
85
+
86
+ // Eager loading (no N+1)
87
+ db.posts.select().with('user').all();
75
88
  ```
76
89
 
90
+ ## Aggregates
91
+
92
+ ```typescript
93
+ db.users.count(); // shorthand
94
+ db.users.select().where({ role: 'admin' }).count(); // filtered
95
+ db.users.select().sum('score');
96
+ db.users.select().avg('score');
97
+ db.users.select().min('score');
98
+ db.users.select().max('score');
99
+ ```
100
+
101
+ ## Batch Mutations
102
+
103
+ ```typescript
104
+ db.users.select().where({ role: 'guest' }).updateAll({ role: 'member' }); // → affected count
105
+ db.users.select().where({ role: 'spam' }).deleteAll(); // → deleted count
106
+ ```
107
+
108
+ ## Pagination
109
+
110
+ ```typescript
111
+ const page = db.users.select().orderBy('name').paginate(1, 20);
112
+ // { data: [...], total: 42, page: 1, perPage: 20, pages: 3 }
113
+ ```
114
+
115
+ ## Select Type Narrowing
116
+
117
+ ```typescript
118
+ const names = db.users.select('name', 'email').all();
119
+ names[0].name; // ✅ string
120
+ names[0].score; // ❌ TypeScript error — not selected
121
+ ```
122
+
123
+ ## Computed Getters
124
+
125
+ ```typescript
126
+ const db = new Database(':memory:', { users: UserSchema }, {
127
+ computed: {
128
+ users: { fullName: (u) => `${u.first} ${u.last}` },
129
+ },
130
+ });
131
+ user.fullName; // 'Alice Smith' — recomputes on access
132
+ ```
133
+
134
+ ## Cascade Deletes
135
+
136
+ ```typescript
137
+ const db = new Database(':memory:', { authors: AuthorSchema, books: BookSchema }, {
138
+ relations: { books: { author_id: 'authors' } },
139
+ cascade: { authors: ['books'] },
140
+ });
141
+ db.authors.delete(1); // → books with author_id=1 also deleted
142
+ ```
143
+
144
+ ## Transactions
145
+
146
+ ```typescript
147
+ db.transaction(() => {
148
+ db.users.insert({ name: 'Alice' });
149
+ db.orders.insert({ userId: 1, amount: 100 });
150
+ }); // auto-commits; rolls back on error
151
+ ```
152
+
153
+ ## Data Import / Export
154
+
155
+ ```typescript
156
+ const backup = db.dump(); // export all tables as JSON
157
+ db.load(backup); // restore (truncates first)
158
+ db.load(backup, { append: true }); // restore without truncating
159
+ db.seed({ users: [{ name: 'Test User' }] }); // additive fixture seeding
160
+ ```
161
+
162
+ ## Schema Diffing
163
+
164
+ ```typescript
165
+ const diff = db.diff();
166
+ // { users: { added: ['bio'], removed: ['legacy'], typeChanged: [] } }
167
+ ```
168
+
169
+ ## Lifecycle Hooks
170
+
171
+ ```typescript
172
+ const db = new Database(':memory:', { users: UserSchema }, {
173
+ hooks: {
174
+ users: {
175
+ beforeInsert: (data) => ({ ...data, name: data.name.trim() }),
176
+ afterInsert: (entity) => console.log('Created:', entity.id),
177
+ beforeUpdate: (id, data) => data,
178
+ afterUpdate: (entity) => {},
179
+ beforeDelete: (id) => true, // return false to cancel
180
+ afterDelete: (id) => {},
181
+ },
182
+ },
183
+ });
184
+ ```
185
+
186
+ ## Soft Deletes & Timestamps
187
+
188
+ ```typescript
189
+ // With softDeletes: true
190
+ db.users.delete(1); // sets deletedAt
191
+ db.users.select().all(); // excludes deleted
192
+ db.users.select().withTrashed().all(); // includes deleted
193
+ db.users.select().onlyTrashed().all(); // only deleted
194
+ db.users.restore(1); // un-deletes
195
+
196
+ // With timestamps: true
197
+ user.createdAt; // auto-set on insert
198
+ user.updatedAt; // auto-bumped on update
199
+ ```
200
+
201
+ ## Raw SQL
202
+
203
+ ```typescript
204
+ db.raw<User>('SELECT * FROM users WHERE score > ?', 50);
205
+ db.exec('UPDATE users SET score = 0 WHERE role = ?', 'guest');
206
+ ```
207
+
208
+ ## Full Feature List
209
+
210
+ - Zod-powered schema definition & runtime validation
211
+ - Auto table creation & migration (add columns)
212
+ - Fluent query builder with 10+ operators
213
+ - Type-safe select narrowing
214
+ - Relationship navigation (lazy proxy + eager loading)
215
+ - Soft deletes, timestamps, auto-persist proxy
216
+ - Lifecycle hooks (before/after insert/update/delete)
217
+ - Aggregates (sum, avg, min, max, count, countGrouped)
218
+ - Batch mutations (insertMany, upsertMany, updateAll, deleteAll, findOrCreate)
219
+ - Cascade deletes
220
+ - Computed/virtual getters
221
+ - Data import/export (dump, load, seed)
222
+ - Schema diffing
223
+ - Transactions
224
+ - Pagination
225
+ - whereIn/whereNotIn with subquery support
226
+ - JSON column auto-serialization
227
+ - Unique constraints
228
+ - Debug mode (SQL logging)
229
+ - Raw SQL escape hatch
230
+
231
+ ## Requirements
232
+
233
+ - **Bun** ≥ 1.0 (uses `bun:sqlite` native bindings)
234
+ - **Zod** ≥ 3.0
235
+
77
236
  ## License
78
237
 
79
238
  MIT
package/dist/index.js CHANGED
@@ -4572,6 +4572,34 @@ class QueryBuilder {
4572
4572
  const aggSql = selectSql.replace(/^SELECT .+? FROM/, `SELECT ${groupCols}, COUNT(*) as count FROM`);
4573
4573
  return this.executor(aggSql, params, true);
4574
4574
  }
4575
+ updateAll(data) {
4576
+ const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
4577
+ const whereMatch = selectSql.match(/WHERE (.+?)(?:\s+ORDER|\s+LIMIT|\s+GROUP|\s+HAVING|$)/s);
4578
+ const wherePart = whereMatch ? whereMatch[1] : "1=1";
4579
+ const setClauses = [];
4580
+ const setParams = [];
4581
+ for (const [col, val] of Object.entries(data)) {
4582
+ setClauses.push(`"${col}" = ?`);
4583
+ if (val !== null && val !== undefined && typeof val === "object" && !(val instanceof Buffer) && !(val instanceof Date)) {
4584
+ setParams.push(JSON.stringify(val));
4585
+ } else {
4586
+ setParams.push(val);
4587
+ }
4588
+ }
4589
+ const updateSql = `UPDATE "${this.tableName}" SET ${setClauses.join(", ")} WHERE ${wherePart}`;
4590
+ this.executor(updateSql, [...setParams, ...params], true);
4591
+ const result = this.executor(`SELECT changes() as c`, [], true);
4592
+ return result[0]?.c ?? 0;
4593
+ }
4594
+ deleteAll() {
4595
+ const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
4596
+ const whereMatch = selectSql.match(/WHERE (.+?)(?:\s+ORDER|\s+LIMIT|\s+GROUP|\s+HAVING|$)/s);
4597
+ const wherePart = whereMatch ? whereMatch[1] : "1=1";
4598
+ const deleteSql = `DELETE FROM "${this.tableName}" WHERE ${wherePart}`;
4599
+ this.executor(deleteSql, params, true);
4600
+ const result = this.executor(`SELECT changes() as c`, [], true);
4601
+ return result[0]?.c ?? 0;
4602
+ }
4575
4603
  then(onfulfilled, onrejected) {
4576
4604
  try {
4577
4605
  const result = this.all();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlite-zod-orm",
3
- "version": "3.18.0",
3
+ "version": "3.20.0",
4
4
  "description": "Type-safe SQLite ORM for Bun — Zod schemas, fluent queries, auto relationships, zero SQL",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/builder.ts CHANGED
@@ -465,6 +465,56 @@ export class QueryBuilder<T extends Record<string, any>, TResult extends Record<
465
465
  return this.executor(aggSql, params, true) as any;
466
466
  }
467
467
 
468
+ // ---------- Batch Mutations ----------
469
+
470
+ /**
471
+ * Update all rows matching the current query's WHERE conditions.
472
+ * Returns the number of affected rows.
473
+ * ```ts
474
+ * db.users.select().where({ role: 'guest' }).updateAll({ role: 'member' })
475
+ * ```
476
+ */
477
+ updateAll(data: Partial<T>): number {
478
+ const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
479
+ // Extract WHERE clause from compiled SELECT
480
+ const whereMatch = selectSql.match(/WHERE (.+?)(?:\s+ORDER|\s+LIMIT|\s+GROUP|\s+HAVING|$)/s);
481
+ const wherePart = whereMatch ? whereMatch[1] : '1=1';
482
+
483
+ const setClauses: string[] = [];
484
+ const setParams: any[] = [];
485
+ for (const [col, val] of Object.entries(data)) {
486
+ setClauses.push(`"${col}" = ?`);
487
+ if (val !== null && val !== undefined && typeof val === 'object' && !(val instanceof Buffer) && !(val instanceof Date)) {
488
+ setParams.push(JSON.stringify(val));
489
+ } else {
490
+ setParams.push(val);
491
+ }
492
+ }
493
+
494
+ const updateSql = `UPDATE "${this.tableName}" SET ${setClauses.join(', ')} WHERE ${wherePart}`;
495
+ this.executor(updateSql, [...setParams, ...params], true);
496
+ // Return affected rows via changes()
497
+ const result = this.executor(`SELECT changes() as c`, [], true);
498
+ return (result[0] as any)?.c ?? 0;
499
+ }
500
+
501
+ /**
502
+ * Delete all rows matching the current query's WHERE conditions.
503
+ * Returns the number of deleted rows.
504
+ * ```ts
505
+ * db.users.select().where({ role: 'guest' }).deleteAll()
506
+ * ```
507
+ */
508
+ deleteAll(): number {
509
+ const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
510
+ const whereMatch = selectSql.match(/WHERE (.+?)(?:\s+ORDER|\s+LIMIT|\s+GROUP|\s+HAVING|$)/s);
511
+ const wherePart = whereMatch ? whereMatch[1] : '1=1';
512
+
513
+ const deleteSql = `DELETE FROM "${this.tableName}" WHERE ${wherePart}`;
514
+ this.executor(deleteSql, params, true);
515
+ const result = this.executor(`SELECT changes() as c`, [], true);
516
+ return (result[0] as any)?.c ?? 0;
517
+ }
468
518
 
469
519
 
470
520
  // ---------- Thenable (async/await support) ----------