@type32/tauri-sqlite-orm 0.1.1 → 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/README.md +1 -0
- package/dist/index.d.mts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +257 -2
- package/dist/index.mjs +257 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ export const users = defineTable("users", {
|
|
|
35
35
|
export const posts = defineTable("posts", {
|
|
36
36
|
id: integer("id", { isPrimaryKey: true }).primaryKey(),
|
|
37
37
|
content: text("content"),
|
|
38
|
+
randomId: text("random_id").$defaultFn(() => crypto.randomUUID()),
|
|
38
39
|
authorId: integer("author_id"),
|
|
39
40
|
});
|
|
40
41
|
|
package/dist/index.d.mts
CHANGED
|
@@ -25,6 +25,7 @@ interface Column<T = any> {
|
|
|
25
25
|
autoIncrement?: boolean;
|
|
26
26
|
isNotNull?: boolean;
|
|
27
27
|
defaultValue?: T | SQLExpression;
|
|
28
|
+
defaultFn?: () => any;
|
|
28
29
|
references?: {
|
|
29
30
|
table: string;
|
|
30
31
|
column: string;
|
|
@@ -46,6 +47,7 @@ type ColumnBuilder<T> = Column<T> & {
|
|
|
46
47
|
notNull: () => ColumnBuilder<T>;
|
|
47
48
|
default: (value: T | SQLExpression) => ColumnBuilder<T>;
|
|
48
49
|
$type: <U>() => ColumnBuilder<U>;
|
|
50
|
+
$defaultFn: (fn: () => any) => ColumnBuilder<T>;
|
|
49
51
|
references: (target: () => Column<any>, actions?: {
|
|
50
52
|
onDelete?: UpdateDeleteAction;
|
|
51
53
|
onUpdate?: UpdateDeleteAction;
|
|
@@ -152,6 +154,60 @@ declare class TauriORM {
|
|
|
152
154
|
name?: string;
|
|
153
155
|
track?: boolean;
|
|
154
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>;
|
|
155
211
|
}
|
|
156
212
|
declare const db: TauriORM;
|
|
157
213
|
type OneConfig = {
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ interface Column<T = any> {
|
|
|
25
25
|
autoIncrement?: boolean;
|
|
26
26
|
isNotNull?: boolean;
|
|
27
27
|
defaultValue?: T | SQLExpression;
|
|
28
|
+
defaultFn?: () => any;
|
|
28
29
|
references?: {
|
|
29
30
|
table: string;
|
|
30
31
|
column: string;
|
|
@@ -46,6 +47,7 @@ type ColumnBuilder<T> = Column<T> & {
|
|
|
46
47
|
notNull: () => ColumnBuilder<T>;
|
|
47
48
|
default: (value: T | SQLExpression) => ColumnBuilder<T>;
|
|
48
49
|
$type: <U>() => ColumnBuilder<U>;
|
|
50
|
+
$defaultFn: (fn: () => any) => ColumnBuilder<T>;
|
|
49
51
|
references: (target: () => Column<any>, actions?: {
|
|
50
52
|
onDelete?: UpdateDeleteAction;
|
|
51
53
|
onUpdate?: UpdateDeleteAction;
|
|
@@ -152,6 +154,60 @@ declare class TauriORM {
|
|
|
152
154
|
name?: string;
|
|
153
155
|
track?: boolean;
|
|
154
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>;
|
|
155
211
|
}
|
|
156
212
|
declare const db: TauriORM;
|
|
157
213
|
type OneConfig = {
|
package/dist/index.js
CHANGED
|
@@ -95,6 +95,10 @@ function createColumn(params) {
|
|
|
95
95
|
return col;
|
|
96
96
|
};
|
|
97
97
|
col.$type = () => col;
|
|
98
|
+
col.$defaultFn = (fn) => {
|
|
99
|
+
col.defaultFn = fn;
|
|
100
|
+
return col;
|
|
101
|
+
};
|
|
98
102
|
col.references = (target, actions) => {
|
|
99
103
|
const t = target();
|
|
100
104
|
col.references = {
|
|
@@ -289,8 +293,15 @@ var TauriORM = class {
|
|
|
289
293
|
async execute() {
|
|
290
294
|
const db3 = getDb();
|
|
291
295
|
for (const data of this._rows) {
|
|
292
|
-
const
|
|
293
|
-
const
|
|
296
|
+
const finalData = { ...data };
|
|
297
|
+
const schema = this._table._schema;
|
|
298
|
+
for (const [key, col] of Object.entries(schema)) {
|
|
299
|
+
if (finalData[key] === void 0 && col.defaultFn) {
|
|
300
|
+
finalData[key] = col.defaultFn();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const keys = Object.keys(finalData);
|
|
304
|
+
const values = Object.values(finalData);
|
|
294
305
|
const placeholders = values.map(() => "?").join(", ");
|
|
295
306
|
const query = `INSERT INTO ${this._table._tableName} (${keys.join(
|
|
296
307
|
", "
|
|
@@ -462,6 +473,250 @@ var TauriORM = class {
|
|
|
462
473
|
throw new Error("No tables configured. Call db.configure({...}) first.");
|
|
463
474
|
await this.migrate(Object.values(this._tables), options);
|
|
464
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
|
+
}
|
|
465
720
|
};
|
|
466
721
|
var db2 = new TauriORM();
|
|
467
722
|
function relations(baseTable, builder) {
|
package/dist/index.mjs
CHANGED
|
@@ -36,6 +36,10 @@ function createColumn(params) {
|
|
|
36
36
|
return col;
|
|
37
37
|
};
|
|
38
38
|
col.$type = () => col;
|
|
39
|
+
col.$defaultFn = (fn) => {
|
|
40
|
+
col.defaultFn = fn;
|
|
41
|
+
return col;
|
|
42
|
+
};
|
|
39
43
|
col.references = (target, actions) => {
|
|
40
44
|
const t = target();
|
|
41
45
|
col.references = {
|
|
@@ -230,8 +234,15 @@ var TauriORM = class {
|
|
|
230
234
|
async execute() {
|
|
231
235
|
const db3 = getDb();
|
|
232
236
|
for (const data of this._rows) {
|
|
233
|
-
const
|
|
234
|
-
const
|
|
237
|
+
const finalData = { ...data };
|
|
238
|
+
const schema = this._table._schema;
|
|
239
|
+
for (const [key, col] of Object.entries(schema)) {
|
|
240
|
+
if (finalData[key] === void 0 && col.defaultFn) {
|
|
241
|
+
finalData[key] = col.defaultFn();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const keys = Object.keys(finalData);
|
|
245
|
+
const values = Object.values(finalData);
|
|
235
246
|
const placeholders = values.map(() => "?").join(", ");
|
|
236
247
|
const query = `INSERT INTO ${this._table._tableName} (${keys.join(
|
|
237
248
|
", "
|
|
@@ -403,6 +414,250 @@ var TauriORM = class {
|
|
|
403
414
|
throw new Error("No tables configured. Call db.configure({...}) first.");
|
|
404
415
|
await this.migrate(Object.values(this._tables), options);
|
|
405
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
|
+
}
|
|
406
661
|
};
|
|
407
662
|
var db2 = new TauriORM();
|
|
408
663
|
function relations(baseTable, builder) {
|