sqlite-zod-orm 3.16.0 → 3.18.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 +86 -0
- package/package.json +1 -1
- package/src/builder.ts +33 -0
- package/src/database.ts +103 -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;
|
|
@@ -5449,6 +5469,72 @@ class _Database {
|
|
|
5449
5469
|
columns(tableName) {
|
|
5450
5470
|
return this.db.query(`PRAGMA table_info("${tableName}")`).all();
|
|
5451
5471
|
}
|
|
5472
|
+
dump() {
|
|
5473
|
+
const result = {};
|
|
5474
|
+
for (const tableName of Object.keys(this.schemas)) {
|
|
5475
|
+
result[tableName] = this.db.query(`SELECT * FROM "${tableName}"`).all();
|
|
5476
|
+
}
|
|
5477
|
+
return result;
|
|
5478
|
+
}
|
|
5479
|
+
load(data, options) {
|
|
5480
|
+
const txn = this.db.transaction(() => {
|
|
5481
|
+
for (const [tableName, rows] of Object.entries(data)) {
|
|
5482
|
+
if (!this.schemas[tableName])
|
|
5483
|
+
continue;
|
|
5484
|
+
if (!options?.append) {
|
|
5485
|
+
this.db.run(`DELETE FROM "${tableName}"`);
|
|
5486
|
+
}
|
|
5487
|
+
for (const row of rows) {
|
|
5488
|
+
const cols = Object.keys(row).filter((k) => k !== "id");
|
|
5489
|
+
const placeholders = cols.map(() => "?").join(", ");
|
|
5490
|
+
const values = cols.map((c) => {
|
|
5491
|
+
const v = row[c];
|
|
5492
|
+
if (v !== null && v !== undefined && typeof v === "object" && !(v instanceof Buffer)) {
|
|
5493
|
+
return JSON.stringify(v);
|
|
5494
|
+
}
|
|
5495
|
+
return v;
|
|
5496
|
+
});
|
|
5497
|
+
this.db.query(`INSERT INTO "${tableName}" (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`).run(...values);
|
|
5498
|
+
}
|
|
5499
|
+
}
|
|
5500
|
+
});
|
|
5501
|
+
txn();
|
|
5502
|
+
}
|
|
5503
|
+
seed(fixtures) {
|
|
5504
|
+
this.load(fixtures, { append: true });
|
|
5505
|
+
}
|
|
5506
|
+
diff() {
|
|
5507
|
+
const result = {};
|
|
5508
|
+
const systemCols = new Set(["id", "createdAt", "updatedAt", "deletedAt"]);
|
|
5509
|
+
for (const [tableName, schema] of Object.entries(this.schemas)) {
|
|
5510
|
+
const schemaFields = getStorableFields(schema);
|
|
5511
|
+
const schemaColMap = new Map(schemaFields.map((f) => [f.name, zodTypeToSqlType(f.type)]));
|
|
5512
|
+
const liveColumns = this.columns(tableName);
|
|
5513
|
+
const liveColMap = new Map(liveColumns.map((c) => [c.name, c.type]));
|
|
5514
|
+
const added = [];
|
|
5515
|
+
const removed = [];
|
|
5516
|
+
const typeChanged = [];
|
|
5517
|
+
for (const [col, expectedType] of schemaColMap) {
|
|
5518
|
+
if (!liveColMap.has(col)) {
|
|
5519
|
+
added.push(col);
|
|
5520
|
+
} else {
|
|
5521
|
+
const actualType = liveColMap.get(col);
|
|
5522
|
+
if (actualType !== expectedType) {
|
|
5523
|
+
typeChanged.push({ column: col, expected: expectedType, actual: actualType });
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
for (const col of liveColMap.keys()) {
|
|
5528
|
+
if (!systemCols.has(col) && !schemaColMap.has(col)) {
|
|
5529
|
+
removed.push(col);
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
if (added.length > 0 || removed.length > 0 || typeChanged.length > 0) {
|
|
5533
|
+
result[tableName] = { added, removed, typeChanged };
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
return result;
|
|
5537
|
+
}
|
|
5452
5538
|
}
|
|
5453
5539
|
var Database = _Database;
|
|
5454
5540
|
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
|
*
|
package/src/database.ts
CHANGED
|
@@ -420,6 +420,109 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
420
420
|
public columns(tableName: string): { name: string; type: string; notnull: number; pk: number }[] {
|
|
421
421
|
return this.db.query(`PRAGMA table_info("${tableName}")`).all() as any[];
|
|
422
422
|
}
|
|
423
|
+
|
|
424
|
+
// =========================================================================
|
|
425
|
+
// Data Import / Export
|
|
426
|
+
// =========================================================================
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Export all data as a JSON-serializable object.
|
|
430
|
+
* Each key is a table name, value is an array of raw row objects.
|
|
431
|
+
*/
|
|
432
|
+
public dump(): Record<string, any[]> {
|
|
433
|
+
const result: Record<string, any[]> = {};
|
|
434
|
+
for (const tableName of Object.keys(this.schemas)) {
|
|
435
|
+
result[tableName] = this.db.query(`SELECT * FROM "${tableName}"`).all();
|
|
436
|
+
}
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Import data from a dump object. Truncates existing data first.
|
|
442
|
+
* Use `{ append: true }` to insert without truncating.
|
|
443
|
+
*/
|
|
444
|
+
public load(data: Record<string, any[]>, options?: { append?: boolean }): void {
|
|
445
|
+
const txn = this.db.transaction(() => {
|
|
446
|
+
for (const [tableName, rows] of Object.entries(data)) {
|
|
447
|
+
if (!this.schemas[tableName]) continue;
|
|
448
|
+
if (!options?.append) {
|
|
449
|
+
this.db.run(`DELETE FROM "${tableName}"`);
|
|
450
|
+
}
|
|
451
|
+
for (const row of rows) {
|
|
452
|
+
const cols = Object.keys(row).filter(k => k !== 'id');
|
|
453
|
+
const placeholders = cols.map(() => '?').join(', ');
|
|
454
|
+
const values = cols.map(c => {
|
|
455
|
+
const v = row[c];
|
|
456
|
+
// Auto-serialize objects/arrays
|
|
457
|
+
if (v !== null && v !== undefined && typeof v === 'object' && !(v instanceof Buffer)) {
|
|
458
|
+
return JSON.stringify(v);
|
|
459
|
+
}
|
|
460
|
+
return v;
|
|
461
|
+
});
|
|
462
|
+
this.db.query(`INSERT INTO "${tableName}" (${cols.map(c => `"${c}"`).join(', ')}) VALUES (${placeholders})`).run(...values);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
txn();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Seed tables with fixture data. Each key is a table name, value is an
|
|
471
|
+
* array of records to insert. Does NOT truncate — use for additive seeding.
|
|
472
|
+
*/
|
|
473
|
+
public seed(fixtures: Record<string, Record<string, any>[]>): void {
|
|
474
|
+
this.load(fixtures, { append: true });
|
|
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
|
+
}
|
|
423
526
|
}
|
|
424
527
|
|
|
425
528
|
// =============================================================================
|