sqlite-zod-orm 3.17.0 → 3.19.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/dist/index.js +80 -0
- package/package.json +1 -1
- package/src/builder.ts +83 -0
- package/src/database.ts +50 -0
package/dist/index.js
CHANGED
|
@@ -4429,6 +4429,26 @@ class QueryBuilder {
|
|
|
4429
4429
|
this.iqo.rawWheres.push({ sql, params });
|
|
4430
4430
|
return this;
|
|
4431
4431
|
}
|
|
4432
|
+
whereIn(column, values) {
|
|
4433
|
+
if (Array.isArray(values)) {
|
|
4434
|
+
const placeholders = values.map(() => "?").join(", ");
|
|
4435
|
+
this.iqo.rawWheres.push({ sql: `"${column}" IN (${placeholders})`, params: values });
|
|
4436
|
+
} else {
|
|
4437
|
+
const inner = compileIQO(values.tableName, values.iqo);
|
|
4438
|
+
this.iqo.rawWheres.push({ sql: `"${column}" IN (${inner.sql})`, params: inner.params });
|
|
4439
|
+
}
|
|
4440
|
+
return this;
|
|
4441
|
+
}
|
|
4442
|
+
whereNotIn(column, values) {
|
|
4443
|
+
if (Array.isArray(values)) {
|
|
4444
|
+
const placeholders = values.map(() => "?").join(", ");
|
|
4445
|
+
this.iqo.rawWheres.push({ sql: `"${column}" NOT IN (${placeholders})`, params: values });
|
|
4446
|
+
} else {
|
|
4447
|
+
const inner = compileIQO(values.tableName, values.iqo);
|
|
4448
|
+
this.iqo.rawWheres.push({ sql: `"${column}" NOT IN (${inner.sql})`, params: inner.params });
|
|
4449
|
+
}
|
|
4450
|
+
return this;
|
|
4451
|
+
}
|
|
4432
4452
|
with(...relations) {
|
|
4433
4453
|
this.iqo.includes.push(...relations);
|
|
4434
4454
|
return this;
|
|
@@ -4552,6 +4572,34 @@ class QueryBuilder {
|
|
|
4552
4572
|
const aggSql = selectSql.replace(/^SELECT .+? FROM/, `SELECT ${groupCols}, COUNT(*) as count FROM`);
|
|
4553
4573
|
return this.executor(aggSql, params, true);
|
|
4554
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
|
+
}
|
|
4555
4603
|
then(onfulfilled, onrejected) {
|
|
4556
4604
|
try {
|
|
4557
4605
|
const result = this.all();
|
|
@@ -5483,6 +5531,38 @@ class _Database {
|
|
|
5483
5531
|
seed(fixtures) {
|
|
5484
5532
|
this.load(fixtures, { append: true });
|
|
5485
5533
|
}
|
|
5534
|
+
diff() {
|
|
5535
|
+
const result = {};
|
|
5536
|
+
const systemCols = new Set(["id", "createdAt", "updatedAt", "deletedAt"]);
|
|
5537
|
+
for (const [tableName, schema] of Object.entries(this.schemas)) {
|
|
5538
|
+
const schemaFields = getStorableFields(schema);
|
|
5539
|
+
const schemaColMap = new Map(schemaFields.map((f) => [f.name, zodTypeToSqlType(f.type)]));
|
|
5540
|
+
const liveColumns = this.columns(tableName);
|
|
5541
|
+
const liveColMap = new Map(liveColumns.map((c) => [c.name, c.type]));
|
|
5542
|
+
const added = [];
|
|
5543
|
+
const removed = [];
|
|
5544
|
+
const typeChanged = [];
|
|
5545
|
+
for (const [col, expectedType] of schemaColMap) {
|
|
5546
|
+
if (!liveColMap.has(col)) {
|
|
5547
|
+
added.push(col);
|
|
5548
|
+
} else {
|
|
5549
|
+
const actualType = liveColMap.get(col);
|
|
5550
|
+
if (actualType !== expectedType) {
|
|
5551
|
+
typeChanged.push({ column: col, expected: expectedType, actual: actualType });
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5555
|
+
for (const col of liveColMap.keys()) {
|
|
5556
|
+
if (!systemCols.has(col) && !schemaColMap.has(col)) {
|
|
5557
|
+
removed.push(col);
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
if (added.length > 0 || removed.length > 0 || typeChanged.length > 0) {
|
|
5561
|
+
result[tableName] = { added, removed, typeChanged };
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
return result;
|
|
5565
|
+
}
|
|
5486
5566
|
}
|
|
5487
5567
|
var Database = _Database;
|
|
5488
5568
|
export {
|
package/package.json
CHANGED
package/src/builder.ts
CHANGED
|
@@ -231,6 +231,39 @@ export class QueryBuilder<T extends Record<string, any>, TResult extends Record<
|
|
|
231
231
|
return this;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Filter by column value being in a list or subquery.
|
|
236
|
+
* ```ts
|
|
237
|
+
* db.users.select().whereIn('id', [1, 2, 3]).all()
|
|
238
|
+
* db.users.select().whereIn('id', db.orders.select('userId')).all()
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
whereIn(column: keyof T & string, values: any[] | QueryBuilder<any, any>): this {
|
|
242
|
+
if (Array.isArray(values)) {
|
|
243
|
+
const placeholders = values.map(() => '?').join(', ');
|
|
244
|
+
this.iqo.rawWheres.push({ sql: `"${column}" IN (${placeholders})`, params: values });
|
|
245
|
+
} else {
|
|
246
|
+
// Subquery: compile the inner QueryBuilder's IQO
|
|
247
|
+
const inner = compileIQO((values as any).tableName, (values as any).iqo);
|
|
248
|
+
this.iqo.rawWheres.push({ sql: `"${column}" IN (${inner.sql})`, params: inner.params });
|
|
249
|
+
}
|
|
250
|
+
return this;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Filter by column value NOT being in a list or subquery.
|
|
255
|
+
*/
|
|
256
|
+
whereNotIn(column: keyof T & string, values: any[] | QueryBuilder<any, any>): this {
|
|
257
|
+
if (Array.isArray(values)) {
|
|
258
|
+
const placeholders = values.map(() => '?').join(', ');
|
|
259
|
+
this.iqo.rawWheres.push({ sql: `"${column}" NOT IN (${placeholders})`, params: values });
|
|
260
|
+
} else {
|
|
261
|
+
const inner = compileIQO((values as any).tableName, (values as any).iqo);
|
|
262
|
+
this.iqo.rawWheres.push({ sql: `"${column}" NOT IN (${inner.sql})`, params: inner.params });
|
|
263
|
+
}
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
|
|
234
267
|
/**
|
|
235
268
|
* Eagerly load a related entity and attach as an array property.
|
|
236
269
|
*
|
|
@@ -432,6 +465,56 @@ export class QueryBuilder<T extends Record<string, any>, TResult extends Record<
|
|
|
432
465
|
return this.executor(aggSql, params, true) as any;
|
|
433
466
|
}
|
|
434
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
|
+
}
|
|
435
518
|
|
|
436
519
|
|
|
437
520
|
// ---------- Thenable (async/await support) ----------
|
package/src/database.ts
CHANGED
|
@@ -473,6 +473,56 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
473
473
|
public seed(fixtures: Record<string, Record<string, any>[]>): void {
|
|
474
474
|
this.load(fixtures, { append: true });
|
|
475
475
|
}
|
|
476
|
+
|
|
477
|
+
// =========================================================================
|
|
478
|
+
// Schema Diffing
|
|
479
|
+
// =========================================================================
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Compare Zod schemas against the live SQLite table structure.
|
|
483
|
+
* Returns a diff object per table: { added, removed, typeChanged }.
|
|
484
|
+
*/
|
|
485
|
+
public diff(): Record<string, { added: string[]; removed: string[]; typeChanged: { column: string; expected: string; actual: string }[] }> {
|
|
486
|
+
const result: Record<string, { added: string[]; removed: string[]; typeChanged: { column: string; expected: string; actual: string }[] }> = {};
|
|
487
|
+
const systemCols = new Set(['id', 'createdAt', 'updatedAt', 'deletedAt']);
|
|
488
|
+
|
|
489
|
+
for (const [tableName, schema] of Object.entries(this.schemas)) {
|
|
490
|
+
const schemaFields = getStorableFields(schema);
|
|
491
|
+
const schemaColMap = new Map(schemaFields.map(f => [f.name, zodTypeToSqlType(f.type)]));
|
|
492
|
+
|
|
493
|
+
const liveColumns = this.columns(tableName);
|
|
494
|
+
const liveColMap = new Map(liveColumns.map(c => [c.name, c.type]));
|
|
495
|
+
|
|
496
|
+
const added: string[] = [];
|
|
497
|
+
const removed: string[] = [];
|
|
498
|
+
const typeChanged: { column: string; expected: string; actual: string }[] = [];
|
|
499
|
+
|
|
500
|
+
// Columns in schema but not in live DB
|
|
501
|
+
for (const [col, expectedType] of schemaColMap) {
|
|
502
|
+
if (!liveColMap.has(col)) {
|
|
503
|
+
added.push(col);
|
|
504
|
+
} else {
|
|
505
|
+
const actualType = liveColMap.get(col)!;
|
|
506
|
+
if (actualType !== expectedType) {
|
|
507
|
+
typeChanged.push({ column: col, expected: expectedType, actual: actualType });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Columns in live DB but not in schema (excluding system columns)
|
|
513
|
+
for (const col of liveColMap.keys()) {
|
|
514
|
+
if (!systemCols.has(col) && !schemaColMap.has(col)) {
|
|
515
|
+
removed.push(col);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (added.length > 0 || removed.length > 0 || typeChanged.length > 0) {
|
|
520
|
+
result[tableName] = { added, removed, typeChanged };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
476
526
|
}
|
|
477
527
|
|
|
478
528
|
// =============================================================================
|