@lobomfz/db 0.4.1 → 0.5.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 +55 -4
- package/package.json +1 -1
- package/src/database.ts +93 -385
- package/src/index.ts +3 -0
- package/src/migration/data-loss-error.ts +29 -0
- package/src/migration/diff.ts +79 -28
- package/src/migration/drift-error.ts +9 -0
- package/src/migration/execute.ts +36 -26
- package/src/migration/guided.ts +136 -0
- package/src/migration/introspect.ts +11 -6
- package/src/migration/registry.ts +43 -0
- package/src/migration/types.ts +42 -0
- package/src/schema-compiler.ts +362 -0
- package/src/types.ts +17 -1
package/src/migration/diff.ts
CHANGED
|
@@ -14,6 +14,10 @@ import {
|
|
|
14
14
|
import type { FtsTokenizer } from "../types.js";
|
|
15
15
|
import type {
|
|
16
16
|
ColumnSchema,
|
|
17
|
+
DesiredSchema,
|
|
18
|
+
DesiredTable,
|
|
19
|
+
DestructiveChange,
|
|
20
|
+
ExistingSchema,
|
|
17
21
|
IntrospectedFts,
|
|
18
22
|
IntrospectedTable,
|
|
19
23
|
IntrospectedVectorIndex,
|
|
@@ -21,38 +25,33 @@ import type {
|
|
|
21
25
|
MigrationOp,
|
|
22
26
|
} from "./types.js";
|
|
23
27
|
|
|
24
|
-
export interface DesiredColumn extends ColumnSchema {
|
|
25
|
-
addable: boolean;
|
|
26
|
-
columnDef: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type DesiredIndex = {
|
|
30
|
-
name: string;
|
|
31
|
-
columns: string[];
|
|
32
|
-
sql: string;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export type DesiredTable = {
|
|
36
|
-
name: string;
|
|
37
|
-
sql: string;
|
|
38
|
-
columns: DesiredColumn[];
|
|
39
|
-
indexes?: DesiredIndex[];
|
|
40
|
-
};
|
|
41
|
-
|
|
42
28
|
export class Differ {
|
|
43
29
|
private ops: MigrationOp[] = [];
|
|
44
30
|
private desiredNames: Set<string>;
|
|
45
31
|
private rebuiltTables = new Set<string>();
|
|
46
32
|
|
|
33
|
+
private desired: DesiredTable[];
|
|
34
|
+
private existing: Map<string, IntrospectedTable>;
|
|
35
|
+
private desiredFts: ResolvedFtsPlan[];
|
|
36
|
+
private existingFts: Map<string, IntrospectedFts>;
|
|
37
|
+
private desiredVectorIndexes: ResolvedVectorIndex[];
|
|
38
|
+
private existingVectorIndexes: Map<string, IntrospectedVectorIndex>;
|
|
39
|
+
|
|
40
|
+
readonly destructive: DestructiveChange[] = [];
|
|
41
|
+
|
|
47
42
|
constructor(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
private
|
|
51
|
-
private existingFts: Map<string, IntrospectedFts> = new Map(),
|
|
52
|
-
private desiredVectorIndexes: ResolvedVectorIndex[] = [],
|
|
53
|
-
private existingVectorIndexes: Map<string, IntrospectedVectorIndex> = new Map(),
|
|
43
|
+
desired: DesiredSchema,
|
|
44
|
+
existing: ExistingSchema,
|
|
45
|
+
private options: { guided?: boolean } = {},
|
|
54
46
|
) {
|
|
55
|
-
this.
|
|
47
|
+
this.desired = desired.desiredTables;
|
|
48
|
+
this.existing = existing.existing;
|
|
49
|
+
this.desiredFts = desired.desiredFts ?? [];
|
|
50
|
+
this.existingFts = existing.existingFts ?? new Map();
|
|
51
|
+
this.desiredVectorIndexes = desired.desiredVectorIndexes ?? [];
|
|
52
|
+
this.existingVectorIndexes = existing.existingVectorIndexes ?? new Map();
|
|
53
|
+
|
|
54
|
+
this.desiredNames = new Set(this.desired.map((t) => t.name));
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
diff() {
|
|
@@ -106,6 +105,16 @@ export class Differ {
|
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
private buildRebuild(table: DesiredTable, existingTable: IntrospectedTable) {
|
|
108
|
+
if (this.options.guided) {
|
|
109
|
+
this.buildGuidedRebuild(table, existingTable);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const desiredNames = new Set(table.columns.map((c) => c.name));
|
|
114
|
+
const droppedColumns = [...existingTable.columns.keys()].filter(
|
|
115
|
+
(name) => !desiredNames.has(name),
|
|
116
|
+
);
|
|
117
|
+
const typeChanged: string[] = [];
|
|
109
118
|
const columnCopies: ColumnCopy[] = [];
|
|
110
119
|
|
|
111
120
|
for (const col of table.columns) {
|
|
@@ -122,6 +131,7 @@ export class Differ {
|
|
|
122
131
|
);
|
|
123
132
|
}
|
|
124
133
|
|
|
134
|
+
typeChanged.push(col.name);
|
|
125
135
|
continue;
|
|
126
136
|
}
|
|
127
137
|
|
|
@@ -141,6 +151,41 @@ export class Differ {
|
|
|
141
151
|
}
|
|
142
152
|
}
|
|
143
153
|
|
|
154
|
+
if (existingTable.hasData && droppedColumns.length > 0) {
|
|
155
|
+
this.destructive.push({ table: table.name, kind: "DropColumn", columns: droppedColumns });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (existingTable.hasData && typeChanged.length > 0) {
|
|
159
|
+
this.destructive.push({ table: table.name, kind: "TypeChange", columns: typeChanged });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.ops.push({
|
|
163
|
+
type: "RebuildTable",
|
|
164
|
+
table: table.name,
|
|
165
|
+
createSql: table.sql,
|
|
166
|
+
columnCopies,
|
|
167
|
+
});
|
|
168
|
+
this.rebuiltTables.add(table.name);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private buildGuidedRebuild(table: DesiredTable, existingTable: IntrospectedTable) {
|
|
172
|
+
const columnCopies: ColumnCopy[] = [];
|
|
173
|
+
|
|
174
|
+
for (const col of table.columns) {
|
|
175
|
+
if (!existingTable.columns.has(col.name)) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (col.notnull && col.defaultValue !== null) {
|
|
180
|
+
columnCopies.push({
|
|
181
|
+
name: col.name,
|
|
182
|
+
expr: `COALESCE("${col.name}", ${col.defaultValue})`,
|
|
183
|
+
});
|
|
184
|
+
} else {
|
|
185
|
+
columnCopies.push({ name: col.name, expr: `"${col.name}"` });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
144
189
|
this.ops.push({
|
|
145
190
|
type: "RebuildTable",
|
|
146
191
|
table: table.name,
|
|
@@ -193,9 +238,15 @@ export class Differ {
|
|
|
193
238
|
}
|
|
194
239
|
|
|
195
240
|
private dropOrphans() {
|
|
196
|
-
for (const [name] of this.existing) {
|
|
197
|
-
if (
|
|
198
|
-
|
|
241
|
+
for (const [name, table] of this.existing) {
|
|
242
|
+
if (this.desiredNames.has(name)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.ops.push({ type: "DropTable", table: name });
|
|
247
|
+
|
|
248
|
+
if (!this.options.guided && table.hasData) {
|
|
249
|
+
this.destructive.push({ table: name, kind: "DropTable" });
|
|
199
250
|
}
|
|
200
251
|
}
|
|
201
252
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export class MigrationDriftError extends Error {
|
|
2
|
+
constructor(readonly id: string) {
|
|
3
|
+
super(
|
|
4
|
+
`Migration "${id}" was already applied with a different body. ` +
|
|
5
|
+
`Migrations are immutable once applied: revert the change, or give the new logic a fresh id.`,
|
|
6
|
+
);
|
|
7
|
+
this.name = "MigrationDriftError";
|
|
8
|
+
}
|
|
9
|
+
}
|
package/src/migration/execute.ts
CHANGED
|
@@ -14,36 +14,14 @@ export class Executor {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const hasRebuild = this.ops.some((op) => op.type === "RebuildTable");
|
|
17
|
-
|
|
18
|
-
let restoreFk = false;
|
|
19
|
-
|
|
20
|
-
if (hasRebuild) {
|
|
21
|
-
const { foreign_keys } = this.db.prepare("PRAGMA foreign_keys").get() as {
|
|
22
|
-
foreign_keys: number;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
if (foreign_keys === 1) {
|
|
26
|
-
this.db.run("PRAGMA foreign_keys = OFF");
|
|
27
|
-
restoreFk = true;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
17
|
+
const restoreFk = hasRebuild && Executor.disableForeignKeys(this.db);
|
|
30
18
|
|
|
31
19
|
try {
|
|
32
20
|
this.db.transaction(() => {
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
this.executeOp(op);
|
|
36
|
-
} catch (e) {
|
|
37
|
-
throw this.parseError(e, op);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
21
|
+
this.applyWithin();
|
|
40
22
|
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (violations.length > 0) {
|
|
45
|
-
throw new Error("Foreign key check failed after rebuild");
|
|
46
|
-
}
|
|
23
|
+
if (hasRebuild) {
|
|
24
|
+
Executor.assertForeignKeysIntact(this.db);
|
|
47
25
|
}
|
|
48
26
|
})();
|
|
49
27
|
} finally {
|
|
@@ -53,6 +31,38 @@ export class Executor {
|
|
|
53
31
|
}
|
|
54
32
|
}
|
|
55
33
|
|
|
34
|
+
applyWithin() {
|
|
35
|
+
for (const op of this.ops) {
|
|
36
|
+
try {
|
|
37
|
+
this.executeOp(op);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
throw this.parseError(e, op);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static disableForeignKeys(db: Database): boolean {
|
|
45
|
+
const { foreign_keys } = db.prepare("PRAGMA foreign_keys").get() as {
|
|
46
|
+
foreign_keys: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (foreign_keys !== 1) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
db.run("PRAGMA foreign_keys = OFF");
|
|
54
|
+
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static assertForeignKeysIntact(db: Database) {
|
|
59
|
+
const violations = db.prepare("PRAGMA foreign_key_check").all();
|
|
60
|
+
|
|
61
|
+
if (violations.length > 0) {
|
|
62
|
+
throw new Error("Foreign key check failed after rebuild");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
56
66
|
private executeOp(op: MigrationOp) {
|
|
57
67
|
switch (op.type) {
|
|
58
68
|
case "CreateTable": {
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
import type { Kysely } from "kysely";
|
|
4
|
+
|
|
5
|
+
import type { DataMigration } from "../types.js";
|
|
6
|
+
import { Differ } from "./diff.js";
|
|
7
|
+
import { Executor } from "./execute.js";
|
|
8
|
+
import { Introspector } from "./introspect.js";
|
|
9
|
+
import type { MigrationRegistry } from "./registry.js";
|
|
10
|
+
import type {
|
|
11
|
+
DesiredSchema,
|
|
12
|
+
ExistingSchema,
|
|
13
|
+
IntrospectedTable,
|
|
14
|
+
MigrationOp,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
|
|
17
|
+
export type GuidedMigrationDeps = {
|
|
18
|
+
sqlite: Database;
|
|
19
|
+
kysely: Kysely<any>;
|
|
20
|
+
desired: DesiredSchema;
|
|
21
|
+
existing: Map<string, IntrospectedTable>;
|
|
22
|
+
migration: DataMigration<any, any, any>;
|
|
23
|
+
checksum: string;
|
|
24
|
+
registry: MigrationRegistry;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export class GuidedMigrationRunner {
|
|
28
|
+
constructor(private deps: GuidedMigrationDeps) {}
|
|
29
|
+
|
|
30
|
+
async run() {
|
|
31
|
+
const expandOps = this.expandOps();
|
|
32
|
+
|
|
33
|
+
const restoreFk = Executor.disableForeignKeys(this.deps.sqlite);
|
|
34
|
+
|
|
35
|
+
this.deps.sqlite.run("BEGIN");
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
new Executor(this.deps.sqlite, expandOps).applyWithin();
|
|
39
|
+
|
|
40
|
+
await this.deps.migration.run({ db: this.deps.kysely });
|
|
41
|
+
|
|
42
|
+
const { ops, existing } = this.contractPlan();
|
|
43
|
+
|
|
44
|
+
this.logDrops(ops, existing);
|
|
45
|
+
|
|
46
|
+
new Executor(this.deps.sqlite, ops).applyWithin();
|
|
47
|
+
|
|
48
|
+
this.deps.registry.record(this.deps.migration.id, this.deps.checksum);
|
|
49
|
+
|
|
50
|
+
Executor.assertForeignKeysIntact(this.deps.sqlite);
|
|
51
|
+
|
|
52
|
+
this.deps.sqlite.run("COMMIT");
|
|
53
|
+
} catch (e) {
|
|
54
|
+
this.deps.sqlite.run("ROLLBACK");
|
|
55
|
+
throw e;
|
|
56
|
+
} finally {
|
|
57
|
+
if (restoreFk) {
|
|
58
|
+
this.deps.sqlite.run("PRAGMA foreign_keys = ON");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private expandOps() {
|
|
64
|
+
const creates: MigrationOp[] = [];
|
|
65
|
+
const adds: MigrationOp[] = [];
|
|
66
|
+
|
|
67
|
+
for (const table of this.deps.desired.desiredTables) {
|
|
68
|
+
const existing = this.deps.existing.get(table.name);
|
|
69
|
+
|
|
70
|
+
if (!existing) {
|
|
71
|
+
creates.push({ type: "CreateTable", table: table.name, sql: table.sql });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const col of table.columns) {
|
|
76
|
+
if (!existing.columns.has(col.name)) {
|
|
77
|
+
adds.push({ type: "AddColumn", table: table.name, columnDef: col.nullableAddDef });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return [...creates, ...adds];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private contractPlan() {
|
|
86
|
+
const introspector = new Introspector(this.deps.sqlite);
|
|
87
|
+
const existing = introspector.introspect();
|
|
88
|
+
|
|
89
|
+
const existingState: ExistingSchema = {
|
|
90
|
+
existing,
|
|
91
|
+
existingFts: introspector.introspectFts(),
|
|
92
|
+
existingVectorIndexes: introspector.introspectVectorIndexes(),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const ops = new Differ(this.deps.desired, existingState, { guided: true }).diff();
|
|
96
|
+
|
|
97
|
+
return { ops, existing };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private logDrops(ops: MigrationOp[], existing: Map<string, IntrospectedTable>) {
|
|
101
|
+
const drops: string[] = [];
|
|
102
|
+
|
|
103
|
+
for (const op of ops) {
|
|
104
|
+
if (op.type === "DropTable") {
|
|
105
|
+
drops.push(`table "${op.table}"`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (op.type !== "RebuildTable") {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const before = existing.get(op.table);
|
|
114
|
+
|
|
115
|
+
if (!before) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const kept = new Set(op.columnCopies.map((c) => c.name));
|
|
120
|
+
|
|
121
|
+
for (const name of before.columns.keys()) {
|
|
122
|
+
if (!kept.has(name)) {
|
|
123
|
+
drops.push(`column "${name}" from "${op.table}"`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (drops.length === 0) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.warn(
|
|
133
|
+
`Migration "${this.deps.migration.id}" will drop:\n${drops.map((d) => ` - ${d}`).join("\n")}`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -2,12 +2,13 @@ import type { Database } from "bun:sqlite";
|
|
|
2
2
|
|
|
3
3
|
import { FTS_TRIGGER_PREFIX } from "../fts/sql.js";
|
|
4
4
|
import { VEC_TRIGGER_PREFIX } from "../vector-index/sql.js";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import {
|
|
6
|
+
MIGRATIONS_TABLE,
|
|
7
|
+
type IntrospectedColumn,
|
|
8
|
+
type IntrospectedFts,
|
|
9
|
+
type IntrospectedIndex,
|
|
10
|
+
type IntrospectedTable,
|
|
11
|
+
type IntrospectedVectorIndex,
|
|
11
12
|
} from "./types.js";
|
|
12
13
|
|
|
13
14
|
type TableListRow = {
|
|
@@ -60,6 +61,10 @@ export class Introspector {
|
|
|
60
61
|
continue;
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
if (row.name === MIGRATIONS_TABLE) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
if (virtualPrefixes.some((prefix) => row.name.startsWith(prefix))) {
|
|
64
69
|
continue;
|
|
65
70
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
import { MigrationDriftError } from "./drift-error.js";
|
|
4
|
+
import { MIGRATIONS_TABLE } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export class MigrationRegistry {
|
|
7
|
+
constructor(private db: Database) {}
|
|
8
|
+
|
|
9
|
+
ensureTable() {
|
|
10
|
+
this.db.run(
|
|
11
|
+
`CREATE TABLE IF NOT EXISTS "${MIGRATIONS_TABLE}" ` +
|
|
12
|
+
`("id" TEXT PRIMARY KEY, "checksum" TEXT NOT NULL, "applied_at" INTEGER NOT NULL)`,
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
appliedChecksum(id: string) {
|
|
17
|
+
const row = this.db
|
|
18
|
+
.prepare(`SELECT "checksum" FROM "${MIGRATIONS_TABLE}" WHERE "id" = ?`)
|
|
19
|
+
.get(id) as { checksum: string } | null;
|
|
20
|
+
|
|
21
|
+
return row?.checksum ?? null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
assertNoDrift(id: string, checksum: string) {
|
|
25
|
+
const applied = this.appliedChecksum(id);
|
|
26
|
+
|
|
27
|
+
if (applied !== null && applied !== checksum) {
|
|
28
|
+
throw new MigrationDriftError(id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
record(id: string, checksum: string) {
|
|
33
|
+
this.db
|
|
34
|
+
.prepare(
|
|
35
|
+
`INSERT INTO "${MIGRATIONS_TABLE}" ("id", "checksum", "applied_at") VALUES (?, ?, ?)`,
|
|
36
|
+
)
|
|
37
|
+
.run(id, checksum, Date.now());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static checksum(run: (...args: any[]) => unknown) {
|
|
41
|
+
return new Bun.CryptoHasher("sha256").update(run.toString()).digest("hex");
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/migration/types.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ResolvedFtsPlan } from "../fts/resolve.js";
|
|
2
|
+
import type { ResolvedVectorIndex } from "../vector-index/resolve.js";
|
|
3
|
+
|
|
1
4
|
export type CreateTableOp = {
|
|
2
5
|
type: "CreateTable";
|
|
3
6
|
table: string;
|
|
@@ -101,6 +104,14 @@ export type MigrationOp =
|
|
|
101
104
|
| DropVectorIndexOp
|
|
102
105
|
| RebuildVectorIndexOp;
|
|
103
106
|
|
|
107
|
+
export const MIGRATIONS_TABLE = "__migrations";
|
|
108
|
+
|
|
109
|
+
export type DestructiveChange = {
|
|
110
|
+
table: string;
|
|
111
|
+
kind: "DropTable" | "DropColumn" | "TypeChange";
|
|
112
|
+
columns?: string[];
|
|
113
|
+
};
|
|
114
|
+
|
|
104
115
|
export type ColumnSchema = {
|
|
105
116
|
name: string;
|
|
106
117
|
type: string;
|
|
@@ -139,3 +150,34 @@ export type IntrospectedVectorIndex = {
|
|
|
139
150
|
sql: string;
|
|
140
151
|
triggers: Map<string, string>;
|
|
141
152
|
};
|
|
153
|
+
|
|
154
|
+
export interface DesiredColumn extends ColumnSchema {
|
|
155
|
+
addable: boolean;
|
|
156
|
+
columnDef: string;
|
|
157
|
+
nullableAddDef: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type DesiredIndex = {
|
|
161
|
+
name: string;
|
|
162
|
+
columns: string[];
|
|
163
|
+
sql: string;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export type DesiredTable = {
|
|
167
|
+
name: string;
|
|
168
|
+
sql: string;
|
|
169
|
+
columns: DesiredColumn[];
|
|
170
|
+
indexes?: DesiredIndex[];
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export type DesiredSchema = {
|
|
174
|
+
desiredTables: DesiredTable[];
|
|
175
|
+
desiredFts?: ResolvedFtsPlan[];
|
|
176
|
+
desiredVectorIndexes?: ResolvedVectorIndex[];
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export type ExistingSchema = {
|
|
180
|
+
existing: Map<string, IntrospectedTable>;
|
|
181
|
+
existingFts?: Map<string, IntrospectedFts>;
|
|
182
|
+
existingVectorIndexes?: Map<string, IntrospectedVectorIndex>;
|
|
183
|
+
};
|