@type32/tauri-sqlite-orm 0.1.2 → 0.1.3
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.d.mts +54 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +244 -0
- package/dist/index.mjs +244 -0
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -154,6 +154,60 @@ declare class TauriORM {
|
|
|
154
154
|
name?: string;
|
|
155
155
|
track?: boolean;
|
|
156
156
|
}): Promise<void>;
|
|
157
|
+
diffSchema(): Promise<{
|
|
158
|
+
extraTables: string[];
|
|
159
|
+
missingTables: string[];
|
|
160
|
+
tables: Record<string, {
|
|
161
|
+
missingColumns: string[];
|
|
162
|
+
extraColumns: string[];
|
|
163
|
+
changedColumns: Array<{
|
|
164
|
+
name: string;
|
|
165
|
+
diffs: {
|
|
166
|
+
type?: boolean;
|
|
167
|
+
pk?: boolean;
|
|
168
|
+
notNull?: boolean;
|
|
169
|
+
default?: boolean;
|
|
170
|
+
};
|
|
171
|
+
}>;
|
|
172
|
+
}>;
|
|
173
|
+
}>;
|
|
174
|
+
generate(): Promise<{
|
|
175
|
+
statements: string[];
|
|
176
|
+
}>;
|
|
177
|
+
migrateCli(opts?: {
|
|
178
|
+
name?: string;
|
|
179
|
+
track?: boolean;
|
|
180
|
+
}): Promise<void>;
|
|
181
|
+
push(opts?: {
|
|
182
|
+
dropExtraColumns?: boolean;
|
|
183
|
+
preserveData?: boolean;
|
|
184
|
+
}): Promise<void>;
|
|
185
|
+
pull(): Promise<Record<string, any>>;
|
|
186
|
+
studio(): Promise<{
|
|
187
|
+
driver: string;
|
|
188
|
+
path: any;
|
|
189
|
+
}>;
|
|
190
|
+
private ensureSchemaMeta;
|
|
191
|
+
private getSchemaMeta;
|
|
192
|
+
private setSchemaMeta;
|
|
193
|
+
private normalizeColumn;
|
|
194
|
+
private computeModelSignature;
|
|
195
|
+
isSchemaDirty(): Promise<{
|
|
196
|
+
dirty: boolean;
|
|
197
|
+
current: string;
|
|
198
|
+
stored: string | null;
|
|
199
|
+
}>;
|
|
200
|
+
migrateIfDirty(options?: {
|
|
201
|
+
name?: string;
|
|
202
|
+
track?: boolean;
|
|
203
|
+
}): Promise<boolean>;
|
|
204
|
+
pullSchema(): Promise<Record<string, any>>;
|
|
205
|
+
private buildCreateTableSQL;
|
|
206
|
+
private tableExists;
|
|
207
|
+
forcePush(options?: {
|
|
208
|
+
dropExtraColumns?: boolean;
|
|
209
|
+
preserveData?: boolean;
|
|
210
|
+
}): Promise<void>;
|
|
157
211
|
}
|
|
158
212
|
declare const db: TauriORM;
|
|
159
213
|
type OneConfig = {
|
package/dist/index.d.ts
CHANGED
|
@@ -154,6 +154,60 @@ declare class TauriORM {
|
|
|
154
154
|
name?: string;
|
|
155
155
|
track?: boolean;
|
|
156
156
|
}): Promise<void>;
|
|
157
|
+
diffSchema(): Promise<{
|
|
158
|
+
extraTables: string[];
|
|
159
|
+
missingTables: string[];
|
|
160
|
+
tables: Record<string, {
|
|
161
|
+
missingColumns: string[];
|
|
162
|
+
extraColumns: string[];
|
|
163
|
+
changedColumns: Array<{
|
|
164
|
+
name: string;
|
|
165
|
+
diffs: {
|
|
166
|
+
type?: boolean;
|
|
167
|
+
pk?: boolean;
|
|
168
|
+
notNull?: boolean;
|
|
169
|
+
default?: boolean;
|
|
170
|
+
};
|
|
171
|
+
}>;
|
|
172
|
+
}>;
|
|
173
|
+
}>;
|
|
174
|
+
generate(): Promise<{
|
|
175
|
+
statements: string[];
|
|
176
|
+
}>;
|
|
177
|
+
migrateCli(opts?: {
|
|
178
|
+
name?: string;
|
|
179
|
+
track?: boolean;
|
|
180
|
+
}): Promise<void>;
|
|
181
|
+
push(opts?: {
|
|
182
|
+
dropExtraColumns?: boolean;
|
|
183
|
+
preserveData?: boolean;
|
|
184
|
+
}): Promise<void>;
|
|
185
|
+
pull(): Promise<Record<string, any>>;
|
|
186
|
+
studio(): Promise<{
|
|
187
|
+
driver: string;
|
|
188
|
+
path: any;
|
|
189
|
+
}>;
|
|
190
|
+
private ensureSchemaMeta;
|
|
191
|
+
private getSchemaMeta;
|
|
192
|
+
private setSchemaMeta;
|
|
193
|
+
private normalizeColumn;
|
|
194
|
+
private computeModelSignature;
|
|
195
|
+
isSchemaDirty(): Promise<{
|
|
196
|
+
dirty: boolean;
|
|
197
|
+
current: string;
|
|
198
|
+
stored: string | null;
|
|
199
|
+
}>;
|
|
200
|
+
migrateIfDirty(options?: {
|
|
201
|
+
name?: string;
|
|
202
|
+
track?: boolean;
|
|
203
|
+
}): Promise<boolean>;
|
|
204
|
+
pullSchema(): Promise<Record<string, any>>;
|
|
205
|
+
private buildCreateTableSQL;
|
|
206
|
+
private tableExists;
|
|
207
|
+
forcePush(options?: {
|
|
208
|
+
dropExtraColumns?: boolean;
|
|
209
|
+
preserveData?: boolean;
|
|
210
|
+
}): Promise<void>;
|
|
157
211
|
}
|
|
158
212
|
declare const db: TauriORM;
|
|
159
213
|
type OneConfig = {
|
package/dist/index.js
CHANGED
|
@@ -473,6 +473,250 @@ var TauriORM = class {
|
|
|
473
473
|
throw new Error("No tables configured. Call db.configure({...}) first.");
|
|
474
474
|
await this.migrate(Object.values(this._tables), options);
|
|
475
475
|
}
|
|
476
|
+
// --- Schema diff and CLI-like helpers ---
|
|
477
|
+
async diffSchema() {
|
|
478
|
+
if (!this._tables) throw new Error("No tables configured.");
|
|
479
|
+
const dbi = getDb();
|
|
480
|
+
const configuredNames = Object.values(this._tables).map(
|
|
481
|
+
(t) => t._tableName
|
|
482
|
+
);
|
|
483
|
+
const existing = await dbi.select(
|
|
484
|
+
`SELECT name FROM sqlite_master WHERE type='table'`
|
|
485
|
+
);
|
|
486
|
+
const existingNames = existing.map((r) => r.name);
|
|
487
|
+
const extraTables = existingNames.filter(
|
|
488
|
+
(n) => !configuredNames.includes(n)
|
|
489
|
+
);
|
|
490
|
+
const missingTables = configuredNames.filter(
|
|
491
|
+
(n) => !existingNames.includes(n)
|
|
492
|
+
);
|
|
493
|
+
const tables = {};
|
|
494
|
+
for (const tbl of Object.values(this._tables)) {
|
|
495
|
+
const tableName = tbl._tableName;
|
|
496
|
+
if (!existingNames.includes(tableName)) {
|
|
497
|
+
tables[tableName] = {
|
|
498
|
+
missingColumns: Object.keys(tbl._schema),
|
|
499
|
+
extraColumns: [],
|
|
500
|
+
changedColumns: []
|
|
501
|
+
};
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
const cols = await dbi.select(`PRAGMA table_info('${tableName}')`);
|
|
505
|
+
const colMap = new Map(cols.map((c) => [c.name, c]));
|
|
506
|
+
const modelCols = Object.values(
|
|
507
|
+
tbl._schema
|
|
508
|
+
);
|
|
509
|
+
const missingColumns = [];
|
|
510
|
+
const extraColumns = [];
|
|
511
|
+
const changedColumns = [];
|
|
512
|
+
const modelNamesSet = new Set(modelCols.map((c) => c.name));
|
|
513
|
+
for (const m of modelCols) {
|
|
514
|
+
const info = colMap.get(m.name);
|
|
515
|
+
if (!info) {
|
|
516
|
+
missingColumns.push(m.name);
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const diffs = {};
|
|
520
|
+
if ((info.type || "").toUpperCase() !== m.type.toUpperCase())
|
|
521
|
+
diffs.type = true;
|
|
522
|
+
if (!!info.pk !== !!m.isPrimaryKey) diffs.pk = true;
|
|
523
|
+
if (!!info.notnull !== !!m.isNotNull) diffs.notNull = true;
|
|
524
|
+
const modelDv = m.defaultValue && typeof m.defaultValue === "object" && m.defaultValue.raw ? m.defaultValue.raw : m.defaultValue ?? null;
|
|
525
|
+
if ((info.dflt_value ?? null) !== modelDv)
|
|
526
|
+
diffs.default = true;
|
|
527
|
+
if (Object.keys(diffs).length)
|
|
528
|
+
changedColumns.push({ name: m.name, diffs });
|
|
529
|
+
}
|
|
530
|
+
for (const c of cols)
|
|
531
|
+
if (!modelNamesSet.has(c.name)) extraColumns.push(c.name);
|
|
532
|
+
tables[tableName] = { missingColumns, extraColumns, changedColumns };
|
|
533
|
+
}
|
|
534
|
+
return { extraTables, missingTables, tables };
|
|
535
|
+
}
|
|
536
|
+
async generate() {
|
|
537
|
+
if (!this._tables) throw new Error("No tables configured.");
|
|
538
|
+
return {
|
|
539
|
+
statements: Object.values(this._tables).map(
|
|
540
|
+
(t) => this.buildCreateTableSQL(t)
|
|
541
|
+
)
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
async migrateCli(opts) {
|
|
545
|
+
return this.migrateConfigured(opts);
|
|
546
|
+
}
|
|
547
|
+
async push(opts) {
|
|
548
|
+
return this.forcePush(opts);
|
|
549
|
+
}
|
|
550
|
+
async pull() {
|
|
551
|
+
return this.pullSchema();
|
|
552
|
+
}
|
|
553
|
+
async studio() {
|
|
554
|
+
const dbi = getDb();
|
|
555
|
+
return { driver: "sqlite", path: dbi.path };
|
|
556
|
+
}
|
|
557
|
+
// --- Schema detection / signature ---
|
|
558
|
+
async ensureSchemaMeta() {
|
|
559
|
+
await this.run(
|
|
560
|
+
`CREATE TABLE IF NOT EXISTS _schema_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
async getSchemaMeta(key) {
|
|
564
|
+
const dbi = getDb();
|
|
565
|
+
await this.ensureSchemaMeta();
|
|
566
|
+
const rows = await dbi.select(
|
|
567
|
+
`SELECT value FROM _schema_meta WHERE key = ?`,
|
|
568
|
+
[key]
|
|
569
|
+
);
|
|
570
|
+
return rows?.[0]?.value ?? null;
|
|
571
|
+
}
|
|
572
|
+
async setSchemaMeta(key, value) {
|
|
573
|
+
const dbi = getDb();
|
|
574
|
+
await this.ensureSchemaMeta();
|
|
575
|
+
await dbi.execute(
|
|
576
|
+
`INSERT INTO _schema_meta(key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
|
|
577
|
+
[key, value]
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
normalizeColumn(col) {
|
|
581
|
+
return {
|
|
582
|
+
name: col.name,
|
|
583
|
+
type: col.type,
|
|
584
|
+
pk: !!col.isPrimaryKey,
|
|
585
|
+
ai: !!col.autoIncrement,
|
|
586
|
+
nn: !!col.isNotNull,
|
|
587
|
+
dv: col.defaultValue && typeof col.defaultValue === "object" && col.defaultValue.raw ? { raw: col.defaultValue.raw } : col.defaultValue ?? null
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
computeModelSignature() {
|
|
591
|
+
if (!this._tables) return "";
|
|
592
|
+
const entries = Object.entries(this._tables).map(([k, tbl]) => {
|
|
593
|
+
const cols = Object.values(
|
|
594
|
+
tbl._schema
|
|
595
|
+
).map((c) => this.normalizeColumn(c)).sort((a, b) => a.name.localeCompare(b.name));
|
|
596
|
+
return { table: tbl._tableName, columns: cols };
|
|
597
|
+
});
|
|
598
|
+
entries.sort((a, b) => a.table.localeCompare(b.table));
|
|
599
|
+
return JSON.stringify(entries);
|
|
600
|
+
}
|
|
601
|
+
async isSchemaDirty() {
|
|
602
|
+
const sig = this.computeModelSignature();
|
|
603
|
+
const stored = await this.getSchemaMeta("schema_signature");
|
|
604
|
+
return { dirty: sig !== stored, current: sig, stored };
|
|
605
|
+
}
|
|
606
|
+
async migrateIfDirty(options) {
|
|
607
|
+
const status = await this.isSchemaDirty();
|
|
608
|
+
if (!this._tables) throw new Error("No tables configured.");
|
|
609
|
+
if (status.dirty) {
|
|
610
|
+
await this.migrate(Object.values(this._tables), options);
|
|
611
|
+
await this.setSchemaMeta(
|
|
612
|
+
"schema_signature",
|
|
613
|
+
this.computeModelSignature()
|
|
614
|
+
);
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
// Pull current DB schema (minimal) for configured tables
|
|
620
|
+
async pullSchema() {
|
|
621
|
+
if (!this._tables) throw new Error("No tables configured.");
|
|
622
|
+
const dbi = getDb();
|
|
623
|
+
const result = {};
|
|
624
|
+
for (const tbl of Object.values(this._tables)) {
|
|
625
|
+
const name = tbl._tableName;
|
|
626
|
+
const cols = await dbi.select(`PRAGMA table_info('${name}')`);
|
|
627
|
+
result[name] = cols.map((c) => ({
|
|
628
|
+
name: c.name,
|
|
629
|
+
type: c.type,
|
|
630
|
+
notnull: !!c.notnull,
|
|
631
|
+
pk: !!c.pk,
|
|
632
|
+
dflt_value: c.dflt_value ?? null
|
|
633
|
+
}));
|
|
634
|
+
}
|
|
635
|
+
return result;
|
|
636
|
+
}
|
|
637
|
+
buildCreateTableSQL(table) {
|
|
638
|
+
return this.generateCreateTableSql(table);
|
|
639
|
+
}
|
|
640
|
+
async tableExists(name) {
|
|
641
|
+
const dbi = getDb();
|
|
642
|
+
const rows = await dbi.select(
|
|
643
|
+
`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`,
|
|
644
|
+
[name]
|
|
645
|
+
);
|
|
646
|
+
return rows.length > 0;
|
|
647
|
+
}
|
|
648
|
+
// Force push model to DB: add missing tables/columns, rebuild tables if incompatible
|
|
649
|
+
async forcePush(options) {
|
|
650
|
+
if (!this._tables) throw new Error("No tables configured.");
|
|
651
|
+
const dbi = getDb();
|
|
652
|
+
const preserve = options?.preserveData !== false;
|
|
653
|
+
for (const tbl of Object.values(this._tables)) {
|
|
654
|
+
const tableName = tbl._tableName;
|
|
655
|
+
const exists = await this.tableExists(tableName);
|
|
656
|
+
if (!exists) {
|
|
657
|
+
await this.run(this.buildCreateTableSQL(tbl));
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
const existingCols = await dbi.select(
|
|
661
|
+
`PRAGMA table_info('${tableName}')`
|
|
662
|
+
);
|
|
663
|
+
const existingMap = new Map(existingCols.map((c) => [c.name, c]));
|
|
664
|
+
const modelCols = Object.values(
|
|
665
|
+
tbl._schema
|
|
666
|
+
);
|
|
667
|
+
const missing = [];
|
|
668
|
+
let requiresRebuild = false;
|
|
669
|
+
for (const m of modelCols) {
|
|
670
|
+
const info = existingMap.get(m.name);
|
|
671
|
+
if (!info) {
|
|
672
|
+
missing.push(m);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const typeDiff = (info.type || "").toUpperCase() !== m.type.toUpperCase();
|
|
676
|
+
const pkDiff = !!info.pk !== !!m.isPrimaryKey;
|
|
677
|
+
const nnDiff = !!info.notnull !== !!m.isNotNull;
|
|
678
|
+
const modelDv = m.defaultValue && typeof m.defaultValue === "object" && m.defaultValue.raw ? m.defaultValue.raw : m.defaultValue ?? null;
|
|
679
|
+
const defDiff = (info.dflt_value ?? null) !== modelDv;
|
|
680
|
+
if (typeDiff || pkDiff || nnDiff && !modelDv || defDiff) {
|
|
681
|
+
requiresRebuild = true;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (requiresRebuild) {
|
|
685
|
+
const tmp = `_new_${tableName}`;
|
|
686
|
+
await this.run(this.buildCreateTableSQL(tbl));
|
|
687
|
+
await this.run(
|
|
688
|
+
this.buildCreateTableSQL({ ...tbl, _tableName: tmp })
|
|
689
|
+
);
|
|
690
|
+
const existingNames = existingCols.map((c) => c.name);
|
|
691
|
+
const modelNames = modelCols.map((c) => c.name);
|
|
692
|
+
const shared = existingNames.filter((n) => modelNames.includes(n));
|
|
693
|
+
if (preserve && shared.length > 0) {
|
|
694
|
+
await this.run(
|
|
695
|
+
`INSERT INTO ${tmp} (${shared.join(", ")}) SELECT ${shared.join(
|
|
696
|
+
", "
|
|
697
|
+
)} FROM ${tableName}`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
await this.run(`DROP TABLE ${tableName}`);
|
|
701
|
+
await this.run(`ALTER TABLE ${tmp} RENAME TO ${tableName}`);
|
|
702
|
+
} else {
|
|
703
|
+
for (const m of missing) {
|
|
704
|
+
let clause = `${m.name} ${m.type}`;
|
|
705
|
+
if (m.isNotNull) clause += " NOT NULL";
|
|
706
|
+
if (m.defaultValue !== void 0) {
|
|
707
|
+
const dv = m.defaultValue;
|
|
708
|
+
if (dv && typeof dv === "object" && "raw" in dv)
|
|
709
|
+
clause += ` DEFAULT ${dv.raw}`;
|
|
710
|
+
else if (typeof dv === "string")
|
|
711
|
+
clause += ` DEFAULT '${dv.replace(/'/g, "''")}'`;
|
|
712
|
+
else clause += ` DEFAULT ${dv}`;
|
|
713
|
+
}
|
|
714
|
+
await this.run(`ALTER TABLE ${tableName} ADD COLUMN ${clause}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
await this.setSchemaMeta("schema_signature", this.computeModelSignature());
|
|
719
|
+
}
|
|
476
720
|
};
|
|
477
721
|
var db2 = new TauriORM();
|
|
478
722
|
function relations(baseTable, builder) {
|
package/dist/index.mjs
CHANGED
|
@@ -414,6 +414,250 @@ var TauriORM = class {
|
|
|
414
414
|
throw new Error("No tables configured. Call db.configure({...}) first.");
|
|
415
415
|
await this.migrate(Object.values(this._tables), options);
|
|
416
416
|
}
|
|
417
|
+
// --- Schema diff and CLI-like helpers ---
|
|
418
|
+
async diffSchema() {
|
|
419
|
+
if (!this._tables) throw new Error("No tables configured.");
|
|
420
|
+
const dbi = getDb();
|
|
421
|
+
const configuredNames = Object.values(this._tables).map(
|
|
422
|
+
(t) => t._tableName
|
|
423
|
+
);
|
|
424
|
+
const existing = await dbi.select(
|
|
425
|
+
`SELECT name FROM sqlite_master WHERE type='table'`
|
|
426
|
+
);
|
|
427
|
+
const existingNames = existing.map((r) => r.name);
|
|
428
|
+
const extraTables = existingNames.filter(
|
|
429
|
+
(n) => !configuredNames.includes(n)
|
|
430
|
+
);
|
|
431
|
+
const missingTables = configuredNames.filter(
|
|
432
|
+
(n) => !existingNames.includes(n)
|
|
433
|
+
);
|
|
434
|
+
const tables = {};
|
|
435
|
+
for (const tbl of Object.values(this._tables)) {
|
|
436
|
+
const tableName = tbl._tableName;
|
|
437
|
+
if (!existingNames.includes(tableName)) {
|
|
438
|
+
tables[tableName] = {
|
|
439
|
+
missingColumns: Object.keys(tbl._schema),
|
|
440
|
+
extraColumns: [],
|
|
441
|
+
changedColumns: []
|
|
442
|
+
};
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const cols = await dbi.select(`PRAGMA table_info('${tableName}')`);
|
|
446
|
+
const colMap = new Map(cols.map((c) => [c.name, c]));
|
|
447
|
+
const modelCols = Object.values(
|
|
448
|
+
tbl._schema
|
|
449
|
+
);
|
|
450
|
+
const missingColumns = [];
|
|
451
|
+
const extraColumns = [];
|
|
452
|
+
const changedColumns = [];
|
|
453
|
+
const modelNamesSet = new Set(modelCols.map((c) => c.name));
|
|
454
|
+
for (const m of modelCols) {
|
|
455
|
+
const info = colMap.get(m.name);
|
|
456
|
+
if (!info) {
|
|
457
|
+
missingColumns.push(m.name);
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const diffs = {};
|
|
461
|
+
if ((info.type || "").toUpperCase() !== m.type.toUpperCase())
|
|
462
|
+
diffs.type = true;
|
|
463
|
+
if (!!info.pk !== !!m.isPrimaryKey) diffs.pk = true;
|
|
464
|
+
if (!!info.notnull !== !!m.isNotNull) diffs.notNull = true;
|
|
465
|
+
const modelDv = m.defaultValue && typeof m.defaultValue === "object" && m.defaultValue.raw ? m.defaultValue.raw : m.defaultValue ?? null;
|
|
466
|
+
if ((info.dflt_value ?? null) !== modelDv)
|
|
467
|
+
diffs.default = true;
|
|
468
|
+
if (Object.keys(diffs).length)
|
|
469
|
+
changedColumns.push({ name: m.name, diffs });
|
|
470
|
+
}
|
|
471
|
+
for (const c of cols)
|
|
472
|
+
if (!modelNamesSet.has(c.name)) extraColumns.push(c.name);
|
|
473
|
+
tables[tableName] = { missingColumns, extraColumns, changedColumns };
|
|
474
|
+
}
|
|
475
|
+
return { extraTables, missingTables, tables };
|
|
476
|
+
}
|
|
477
|
+
async generate() {
|
|
478
|
+
if (!this._tables) throw new Error("No tables configured.");
|
|
479
|
+
return {
|
|
480
|
+
statements: Object.values(this._tables).map(
|
|
481
|
+
(t) => this.buildCreateTableSQL(t)
|
|
482
|
+
)
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
async migrateCli(opts) {
|
|
486
|
+
return this.migrateConfigured(opts);
|
|
487
|
+
}
|
|
488
|
+
async push(opts) {
|
|
489
|
+
return this.forcePush(opts);
|
|
490
|
+
}
|
|
491
|
+
async pull() {
|
|
492
|
+
return this.pullSchema();
|
|
493
|
+
}
|
|
494
|
+
async studio() {
|
|
495
|
+
const dbi = getDb();
|
|
496
|
+
return { driver: "sqlite", path: dbi.path };
|
|
497
|
+
}
|
|
498
|
+
// --- Schema detection / signature ---
|
|
499
|
+
async ensureSchemaMeta() {
|
|
500
|
+
await this.run(
|
|
501
|
+
`CREATE TABLE IF NOT EXISTS _schema_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
async getSchemaMeta(key) {
|
|
505
|
+
const dbi = getDb();
|
|
506
|
+
await this.ensureSchemaMeta();
|
|
507
|
+
const rows = await dbi.select(
|
|
508
|
+
`SELECT value FROM _schema_meta WHERE key = ?`,
|
|
509
|
+
[key]
|
|
510
|
+
);
|
|
511
|
+
return rows?.[0]?.value ?? null;
|
|
512
|
+
}
|
|
513
|
+
async setSchemaMeta(key, value) {
|
|
514
|
+
const dbi = getDb();
|
|
515
|
+
await this.ensureSchemaMeta();
|
|
516
|
+
await dbi.execute(
|
|
517
|
+
`INSERT INTO _schema_meta(key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
|
|
518
|
+
[key, value]
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
normalizeColumn(col) {
|
|
522
|
+
return {
|
|
523
|
+
name: col.name,
|
|
524
|
+
type: col.type,
|
|
525
|
+
pk: !!col.isPrimaryKey,
|
|
526
|
+
ai: !!col.autoIncrement,
|
|
527
|
+
nn: !!col.isNotNull,
|
|
528
|
+
dv: col.defaultValue && typeof col.defaultValue === "object" && col.defaultValue.raw ? { raw: col.defaultValue.raw } : col.defaultValue ?? null
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
computeModelSignature() {
|
|
532
|
+
if (!this._tables) return "";
|
|
533
|
+
const entries = Object.entries(this._tables).map(([k, tbl]) => {
|
|
534
|
+
const cols = Object.values(
|
|
535
|
+
tbl._schema
|
|
536
|
+
).map((c) => this.normalizeColumn(c)).sort((a, b) => a.name.localeCompare(b.name));
|
|
537
|
+
return { table: tbl._tableName, columns: cols };
|
|
538
|
+
});
|
|
539
|
+
entries.sort((a, b) => a.table.localeCompare(b.table));
|
|
540
|
+
return JSON.stringify(entries);
|
|
541
|
+
}
|
|
542
|
+
async isSchemaDirty() {
|
|
543
|
+
const sig = this.computeModelSignature();
|
|
544
|
+
const stored = await this.getSchemaMeta("schema_signature");
|
|
545
|
+
return { dirty: sig !== stored, current: sig, stored };
|
|
546
|
+
}
|
|
547
|
+
async migrateIfDirty(options) {
|
|
548
|
+
const status = await this.isSchemaDirty();
|
|
549
|
+
if (!this._tables) throw new Error("No tables configured.");
|
|
550
|
+
if (status.dirty) {
|
|
551
|
+
await this.migrate(Object.values(this._tables), options);
|
|
552
|
+
await this.setSchemaMeta(
|
|
553
|
+
"schema_signature",
|
|
554
|
+
this.computeModelSignature()
|
|
555
|
+
);
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
// Pull current DB schema (minimal) for configured tables
|
|
561
|
+
async pullSchema() {
|
|
562
|
+
if (!this._tables) throw new Error("No tables configured.");
|
|
563
|
+
const dbi = getDb();
|
|
564
|
+
const result = {};
|
|
565
|
+
for (const tbl of Object.values(this._tables)) {
|
|
566
|
+
const name = tbl._tableName;
|
|
567
|
+
const cols = await dbi.select(`PRAGMA table_info('${name}')`);
|
|
568
|
+
result[name] = cols.map((c) => ({
|
|
569
|
+
name: c.name,
|
|
570
|
+
type: c.type,
|
|
571
|
+
notnull: !!c.notnull,
|
|
572
|
+
pk: !!c.pk,
|
|
573
|
+
dflt_value: c.dflt_value ?? null
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
buildCreateTableSQL(table) {
|
|
579
|
+
return this.generateCreateTableSql(table);
|
|
580
|
+
}
|
|
581
|
+
async tableExists(name) {
|
|
582
|
+
const dbi = getDb();
|
|
583
|
+
const rows = await dbi.select(
|
|
584
|
+
`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`,
|
|
585
|
+
[name]
|
|
586
|
+
);
|
|
587
|
+
return rows.length > 0;
|
|
588
|
+
}
|
|
589
|
+
// Force push model to DB: add missing tables/columns, rebuild tables if incompatible
|
|
590
|
+
async forcePush(options) {
|
|
591
|
+
if (!this._tables) throw new Error("No tables configured.");
|
|
592
|
+
const dbi = getDb();
|
|
593
|
+
const preserve = options?.preserveData !== false;
|
|
594
|
+
for (const tbl of Object.values(this._tables)) {
|
|
595
|
+
const tableName = tbl._tableName;
|
|
596
|
+
const exists = await this.tableExists(tableName);
|
|
597
|
+
if (!exists) {
|
|
598
|
+
await this.run(this.buildCreateTableSQL(tbl));
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const existingCols = await dbi.select(
|
|
602
|
+
`PRAGMA table_info('${tableName}')`
|
|
603
|
+
);
|
|
604
|
+
const existingMap = new Map(existingCols.map((c) => [c.name, c]));
|
|
605
|
+
const modelCols = Object.values(
|
|
606
|
+
tbl._schema
|
|
607
|
+
);
|
|
608
|
+
const missing = [];
|
|
609
|
+
let requiresRebuild = false;
|
|
610
|
+
for (const m of modelCols) {
|
|
611
|
+
const info = existingMap.get(m.name);
|
|
612
|
+
if (!info) {
|
|
613
|
+
missing.push(m);
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
const typeDiff = (info.type || "").toUpperCase() !== m.type.toUpperCase();
|
|
617
|
+
const pkDiff = !!info.pk !== !!m.isPrimaryKey;
|
|
618
|
+
const nnDiff = !!info.notnull !== !!m.isNotNull;
|
|
619
|
+
const modelDv = m.defaultValue && typeof m.defaultValue === "object" && m.defaultValue.raw ? m.defaultValue.raw : m.defaultValue ?? null;
|
|
620
|
+
const defDiff = (info.dflt_value ?? null) !== modelDv;
|
|
621
|
+
if (typeDiff || pkDiff || nnDiff && !modelDv || defDiff) {
|
|
622
|
+
requiresRebuild = true;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (requiresRebuild) {
|
|
626
|
+
const tmp = `_new_${tableName}`;
|
|
627
|
+
await this.run(this.buildCreateTableSQL(tbl));
|
|
628
|
+
await this.run(
|
|
629
|
+
this.buildCreateTableSQL({ ...tbl, _tableName: tmp })
|
|
630
|
+
);
|
|
631
|
+
const existingNames = existingCols.map((c) => c.name);
|
|
632
|
+
const modelNames = modelCols.map((c) => c.name);
|
|
633
|
+
const shared = existingNames.filter((n) => modelNames.includes(n));
|
|
634
|
+
if (preserve && shared.length > 0) {
|
|
635
|
+
await this.run(
|
|
636
|
+
`INSERT INTO ${tmp} (${shared.join(", ")}) SELECT ${shared.join(
|
|
637
|
+
", "
|
|
638
|
+
)} FROM ${tableName}`
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
await this.run(`DROP TABLE ${tableName}`);
|
|
642
|
+
await this.run(`ALTER TABLE ${tmp} RENAME TO ${tableName}`);
|
|
643
|
+
} else {
|
|
644
|
+
for (const m of missing) {
|
|
645
|
+
let clause = `${m.name} ${m.type}`;
|
|
646
|
+
if (m.isNotNull) clause += " NOT NULL";
|
|
647
|
+
if (m.defaultValue !== void 0) {
|
|
648
|
+
const dv = m.defaultValue;
|
|
649
|
+
if (dv && typeof dv === "object" && "raw" in dv)
|
|
650
|
+
clause += ` DEFAULT ${dv.raw}`;
|
|
651
|
+
else if (typeof dv === "string")
|
|
652
|
+
clause += ` DEFAULT '${dv.replace(/'/g, "''")}'`;
|
|
653
|
+
else clause += ` DEFAULT ${dv}`;
|
|
654
|
+
}
|
|
655
|
+
await this.run(`ALTER TABLE ${tableName} ADD COLUMN ${clause}`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
await this.setSchemaMeta("schema_signature", this.computeModelSignature());
|
|
660
|
+
}
|
|
417
661
|
};
|
|
418
662
|
var db2 = new TauriORM();
|
|
419
663
|
function relations(baseTable, builder) {
|