@prisma-next/target-sqlite 0.5.0-dev.9 → 0.5.1
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/codec-ids-CYwMu3-4.d.mts +13 -0
- package/dist/codec-ids-CYwMu3-4.d.mts.map +1 -0
- package/dist/codec-ids-CuUxYcd0.mjs +13 -0
- package/dist/codec-ids-CuUxYcd0.mjs.map +1 -0
- package/dist/codec-ids.d.mts +2 -0
- package/dist/codec-ids.mjs +2 -0
- package/dist/codec-types-xLeyb_u4.d.mts +23 -0
- package/dist/codec-types-xLeyb_u4.d.mts.map +1 -0
- package/dist/codec-types.d.mts +3 -0
- package/dist/codec-types.mjs +2 -0
- package/dist/codecs-DEjc7dem.d.mts +203 -0
- package/dist/codecs-DEjc7dem.d.mts.map +1 -0
- package/dist/codecs-DVnHtVWW.mjs +220 -0
- package/dist/codecs-DVnHtVWW.mjs.map +1 -0
- package/dist/codecs.d.mts +2 -0
- package/dist/codecs.mjs +13 -0
- package/dist/codecs.mjs.map +1 -0
- package/dist/control.d.mts +4 -3
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +427 -5
- package/dist/control.mjs.map +1 -1
- package/dist/default-normalizer-3Fccw7yw.mjs +69 -0
- package/dist/default-normalizer-3Fccw7yw.mjs.map +1 -0
- package/dist/default-normalizer.d.mts +7 -0
- package/dist/default-normalizer.d.mts.map +1 -0
- package/dist/default-normalizer.mjs +2 -0
- package/dist/descriptor-meta-CE2Kbn9b.mjs +17 -0
- package/dist/descriptor-meta-CE2Kbn9b.mjs.map +1 -0
- package/dist/migration.d.mts +85 -0
- package/dist/migration.d.mts.map +1 -0
- package/dist/migration.mjs +49 -0
- package/dist/migration.mjs.map +1 -0
- package/dist/native-type-normalizer-BlN5XfD-.mjs +14 -0
- package/dist/native-type-normalizer-BlN5XfD-.mjs.map +1 -0
- package/dist/native-type-normalizer.d.mts +11 -0
- package/dist/native-type-normalizer.d.mts.map +1 -0
- package/dist/native-type-normalizer.mjs +2 -0
- package/dist/op-factory-call-DRKKURAO.mjs +279 -0
- package/dist/op-factory-call-DRKKURAO.mjs.map +1 -0
- package/dist/op-factory-call.d.mts +151 -0
- package/dist/op-factory-call.d.mts.map +1 -0
- package/dist/op-factory-call.mjs +2 -0
- package/dist/pack.d.mts +35 -1
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +2 -3
- package/dist/planner-A7dqS0u6.mjs +522 -0
- package/dist/planner-A7dqS0u6.mjs.map +1 -0
- package/dist/planner-produced-sqlite-migration-CI9LdXPr.d.mts +29 -0
- package/dist/planner-produced-sqlite-migration-CI9LdXPr.d.mts.map +1 -0
- package/dist/planner-produced-sqlite-migration-C_TzWbT0.mjs +110 -0
- package/dist/planner-produced-sqlite-migration-C_TzWbT0.mjs.map +1 -0
- package/dist/planner-produced-sqlite-migration.d.mts +2 -0
- package/dist/planner-produced-sqlite-migration.mjs +2 -0
- package/dist/planner-target-details-Bm71XPKb.mjs +15 -0
- package/dist/planner-target-details-Bm71XPKb.mjs.map +1 -0
- package/dist/planner-target-details-vhvZDWK1.d.mts +12 -0
- package/dist/planner-target-details-vhvZDWK1.d.mts.map +1 -0
- package/dist/planner-target-details.d.mts +2 -0
- package/dist/planner-target-details.mjs +2 -0
- package/dist/planner.d.mts +59 -0
- package/dist/planner.d.mts.map +1 -0
- package/dist/planner.mjs +2 -0
- package/dist/render-ops-CSRDT4YL.mjs +8 -0
- package/dist/render-ops-CSRDT4YL.mjs.map +1 -0
- package/dist/render-ops.d.mts +10 -0
- package/dist/render-ops.d.mts.map +1 -0
- package/dist/render-ops.mjs +2 -0
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +4 -8
- package/dist/runtime.mjs.map +1 -1
- package/dist/shared-qLsgTOZs.d.mts +69 -0
- package/dist/shared-qLsgTOZs.d.mts.map +1 -0
- package/dist/sql-utils-DhevMgef.mjs +35 -0
- package/dist/sql-utils-DhevMgef.mjs.map +1 -0
- package/dist/sql-utils.d.mts +22 -0
- package/dist/sql-utils.d.mts.map +1 -0
- package/dist/sql-utils.mjs +2 -0
- package/dist/sqlite-migration-BBJktVVw.mjs +16 -0
- package/dist/sqlite-migration-BBJktVVw.mjs.map +1 -0
- package/dist/sqlite-migration-DAb2NEX6.d.mts +17 -0
- package/dist/sqlite-migration-DAb2NEX6.d.mts.map +1 -0
- package/dist/statement-builders-Dne-LkAV.mjs +158 -0
- package/dist/statement-builders-Dne-LkAV.mjs.map +1 -0
- package/dist/statement-builders.d.mts +68 -0
- package/dist/statement-builders.d.mts.map +1 -0
- package/dist/statement-builders.mjs +2 -0
- package/dist/tables-D84zfPZI.mjs +403 -0
- package/dist/tables-D84zfPZI.mjs.map +1 -0
- package/package.json +31 -9
- package/src/core/authoring.ts +9 -0
- package/src/core/codec-helpers.ts +11 -0
- package/src/core/codec-ids.ts +13 -0
- package/src/core/codecs.ts +337 -0
- package/src/core/control-target.ts +53 -11
- package/src/core/default-normalizer.ts +92 -0
- package/src/core/descriptor-meta.ts +5 -1
- package/src/core/migrations/issue-planner.ts +586 -0
- package/src/core/migrations/op-factory-call.ts +369 -0
- package/src/core/migrations/operations/columns.ts +62 -0
- package/src/core/migrations/operations/data-transform.ts +51 -0
- package/src/core/migrations/operations/indexes.ts +52 -0
- package/src/core/migrations/operations/raw.ts +12 -0
- package/src/core/migrations/operations/shared.ts +120 -0
- package/src/core/migrations/operations/tables.ts +388 -0
- package/src/core/migrations/planner-ddl-builders.ts +142 -0
- package/src/core/migrations/planner-produced-sqlite-migration.ts +70 -0
- package/src/core/migrations/planner-strategies.ts +231 -0
- package/src/core/migrations/planner-target-details.ts +33 -0
- package/src/core/migrations/planner.ts +183 -0
- package/src/core/migrations/render-ops.ts +15 -0
- package/src/core/migrations/render-typescript.ts +91 -0
- package/src/core/migrations/runner.ts +724 -0
- package/src/core/migrations/sqlite-migration.ts +13 -0
- package/src/core/migrations/statement-builders.ts +212 -0
- package/src/core/native-type-normalizer.ts +9 -0
- package/src/core/registry.ts +11 -0
- package/src/core/runtime-target.ts +1 -3
- package/src/core/sql-utils.ts +47 -0
- package/src/exports/codec-ids.ts +13 -0
- package/src/exports/codec-types.ts +43 -0
- package/src/exports/codecs.ts +20 -0
- package/src/exports/control.ts +1 -0
- package/src/exports/default-normalizer.ts +1 -0
- package/src/exports/migration.ts +24 -0
- package/src/exports/native-type-normalizer.ts +1 -0
- package/src/exports/op-factory-call.ts +12 -0
- package/src/exports/planner-produced-sqlite-migration.ts +4 -0
- package/src/exports/planner-target-details.ts +2 -0
- package/src/exports/planner.ts +2 -0
- package/src/exports/render-ops.ts +1 -0
- package/src/exports/sql-utils.ts +1 -0
- package/src/exports/statement-builders.ts +12 -0
- package/dist/descriptor-meta-DbuuziYA.mjs +0 -14
- package/dist/descriptor-meta-DbuuziYA.mjs.map +0 -1
package/dist/pack.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CodecTypes } from "
|
|
1
|
+
import { t as CodecTypes } from "./codec-types-xLeyb_u4.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/core/descriptor-meta.d.ts
|
|
4
4
|
declare const sqliteTargetDescriptorMetaBase: {
|
|
@@ -8,6 +8,40 @@ declare const sqliteTargetDescriptorMetaBase: {
|
|
|
8
8
|
readonly id: "sqlite";
|
|
9
9
|
readonly version: "0.0.1";
|
|
10
10
|
readonly capabilities: {};
|
|
11
|
+
readonly authoring: {
|
|
12
|
+
readonly field: {
|
|
13
|
+
readonly temporal: {
|
|
14
|
+
readonly createdAt: {
|
|
15
|
+
readonly kind: "fieldPreset";
|
|
16
|
+
readonly output: {
|
|
17
|
+
readonly codecId: "sqlite/datetime@1";
|
|
18
|
+
readonly nativeType: "text";
|
|
19
|
+
readonly default: {
|
|
20
|
+
readonly kind: "function";
|
|
21
|
+
readonly expression: "now()";
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
readonly updatedAt: {
|
|
26
|
+
readonly kind: "fieldPreset";
|
|
27
|
+
readonly output: {
|
|
28
|
+
readonly codecId: "sqlite/datetime@1";
|
|
29
|
+
readonly nativeType: "text";
|
|
30
|
+
readonly executionDefaults: {
|
|
31
|
+
readonly onCreate: {
|
|
32
|
+
readonly kind: "generator";
|
|
33
|
+
readonly id: "timestampNow";
|
|
34
|
+
};
|
|
35
|
+
readonly onUpdate: {
|
|
36
|
+
readonly kind: "generator";
|
|
37
|
+
readonly id: "timestampNow";
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
11
45
|
};
|
|
12
46
|
declare const sqliteTargetDescriptorMeta: typeof sqliteTargetDescriptorMetaBase & {
|
|
13
47
|
readonly __codecTypes?: CodecTypes;
|
package/dist/pack.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pack.d.mts","names":[],"sources":["../src/core/descriptor-meta.ts"],"
|
|
1
|
+
{"version":3,"file":"pack.d.mts","names":[],"sources":["../src/core/descriptor-meta.ts"],"mappings":";;;cAGM,8BAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAYO,0BAAA,SAAmC,8BAAA;EAAA,SACrC,YAAA,GAAe,UAAA;AAAA"}
|
package/dist/pack.mjs
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import { t as sqliteTargetDescriptorMeta } from "./descriptor-meta-
|
|
2
|
-
|
|
3
|
-
export { sqliteTargetDescriptorMeta as default };
|
|
1
|
+
import { t as sqliteTargetDescriptorMeta } from "./descriptor-meta-CE2Kbn9b.mjs";
|
|
2
|
+
export { sqliteTargetDescriptorMeta as default };
|
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import { t as parseSqliteDefault } from "./default-normalizer-3Fccw7yw.mjs";
|
|
2
|
+
import { t as normalizeSqliteNativeType } from "./native-type-normalizer-BlN5XfD-.mjs";
|
|
3
|
+
import { c as buildColumnDefaultSql, l as buildColumnTypeSql, n as buildRecreateSummary, t as buildRecreatePostchecks, u as isInlineAutoincrementPrimaryKey } from "./tables-D84zfPZI.mjs";
|
|
4
|
+
import { a as DropColumnCall, i as DataTransformCall, l as RecreateTableCall, n as CreateIndexCall, o as DropIndexCall, r as CreateTableCall, s as DropTableCall, t as AddColumnCall } from "./op-factory-call-DRKKURAO.mjs";
|
|
5
|
+
import { n as CONTROL_TABLE_NAMES } from "./statement-builders-Dne-LkAV.mjs";
|
|
6
|
+
import { t as TypeScriptRenderableSqliteMigration } from "./planner-produced-sqlite-migration-C_TzWbT0.mjs";
|
|
7
|
+
import { extractCodecControlHooks, planFieldEventOperations, plannerFailure } from "@prisma-next/family-sql/control";
|
|
8
|
+
import { verifySqlSchema } from "@prisma-next/family-sql/schema-verify";
|
|
9
|
+
import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
|
|
10
|
+
import { notOk, ok } from "@prisma-next/utils/result";
|
|
11
|
+
//#region src/core/migrations/planner-strategies.ts
|
|
12
|
+
const WIDENING_ISSUE_KINDS = new Set(["default_mismatch", "default_missing"]);
|
|
13
|
+
const DESTRUCTIVE_ISSUE_KINDS = new Set([
|
|
14
|
+
"extra_default",
|
|
15
|
+
"type_mismatch",
|
|
16
|
+
"primary_key_mismatch",
|
|
17
|
+
"foreign_key_mismatch",
|
|
18
|
+
"unique_constraint_mismatch",
|
|
19
|
+
"extra_foreign_key",
|
|
20
|
+
"extra_unique_constraint",
|
|
21
|
+
"extra_primary_key"
|
|
22
|
+
]);
|
|
23
|
+
function classifyIssue(issue) {
|
|
24
|
+
if (issue.kind === "enum_values_changed") return null;
|
|
25
|
+
if (!issue.table) return null;
|
|
26
|
+
if (issue.kind === "nullability_mismatch") return issue.expected === "true" ? "widening" : "destructive";
|
|
27
|
+
if (WIDENING_ISSUE_KINDS.has(issue.kind)) return "widening";
|
|
28
|
+
if (DESTRUCTIVE_ISSUE_KINDS.has(issue.kind)) return "destructive";
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Groups recreate-eligible issues by table, decides per-table operation class
|
|
33
|
+
* (destructive wins over widening), and emits one `RecreateTableCall` per
|
|
34
|
+
* table. Returns unchanged-or-smaller issue list — issues the strategy
|
|
35
|
+
* consumed are removed so `mapIssueToCall` doesn't double-handle them.
|
|
36
|
+
*/
|
|
37
|
+
const recreateTableStrategy = (issues, ctx) => {
|
|
38
|
+
const byTable = /* @__PURE__ */ new Map();
|
|
39
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
40
|
+
for (const issue of issues) {
|
|
41
|
+
const cls = classifyIssue(issue);
|
|
42
|
+
if (!cls) continue;
|
|
43
|
+
if (issue.kind === "enum_values_changed") continue;
|
|
44
|
+
if (!issue.table) continue;
|
|
45
|
+
const table = issue.table;
|
|
46
|
+
const entry = byTable.get(table);
|
|
47
|
+
if (entry) {
|
|
48
|
+
entry.issues.push(issue);
|
|
49
|
+
if (cls === "destructive") entry.hasDestructive = true;
|
|
50
|
+
} else byTable.set(table, {
|
|
51
|
+
issues: [issue],
|
|
52
|
+
hasDestructive: cls === "destructive"
|
|
53
|
+
});
|
|
54
|
+
consumed.add(issue);
|
|
55
|
+
}
|
|
56
|
+
if (byTable.size === 0) return { kind: "no_match" };
|
|
57
|
+
const calls = [];
|
|
58
|
+
for (const [tableName, entry] of byTable) {
|
|
59
|
+
const contractTable = ctx.toContract.storage.tables[tableName];
|
|
60
|
+
const schemaTable = ctx.schema.tables[tableName];
|
|
61
|
+
if (!contractTable || !schemaTable) continue;
|
|
62
|
+
const operationClass = entry.hasDestructive ? "destructive" : "widening";
|
|
63
|
+
const tableSpec = toTableSpec(contractTable, ctx.storageTypes);
|
|
64
|
+
const seenIndexColumnKeys = /* @__PURE__ */ new Set();
|
|
65
|
+
const indexes = [];
|
|
66
|
+
for (const idx of contractTable.indexes) {
|
|
67
|
+
const key = idx.columns.join(",");
|
|
68
|
+
if (seenIndexColumnKeys.has(key)) continue;
|
|
69
|
+
seenIndexColumnKeys.add(key);
|
|
70
|
+
indexes.push({
|
|
71
|
+
name: idx.name ?? defaultIndexName(tableName, idx.columns),
|
|
72
|
+
columns: idx.columns
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
for (const fk of contractTable.foreignKeys) {
|
|
76
|
+
if (fk.index === false) continue;
|
|
77
|
+
const key = fk.columns.join(",");
|
|
78
|
+
if (seenIndexColumnKeys.has(key)) continue;
|
|
79
|
+
seenIndexColumnKeys.add(key);
|
|
80
|
+
indexes.push({
|
|
81
|
+
name: defaultIndexName(tableName, fk.columns),
|
|
82
|
+
columns: fk.columns
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
calls.push(new RecreateTableCall({
|
|
86
|
+
tableName,
|
|
87
|
+
contractTable: tableSpec,
|
|
88
|
+
schemaColumnNames: Object.keys(schemaTable.columns),
|
|
89
|
+
indexes,
|
|
90
|
+
summary: buildRecreateSummary(tableName, entry.issues),
|
|
91
|
+
postchecks: buildRecreatePostchecks(tableName, entry.issues, tableSpec),
|
|
92
|
+
operationClass
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
kind: "match",
|
|
97
|
+
issues: issues.filter((i) => !consumed.has(i)),
|
|
98
|
+
calls,
|
|
99
|
+
recipe: true
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* When the policy allows `'data'` and the contract tightens one or more
|
|
104
|
+
* columns from nullable to NOT NULL, emit a `DataTransformCall` stub per
|
|
105
|
+
* tightened column. The user fills the backfill `UPDATE` in the rendered
|
|
106
|
+
* `migration.ts` before the subsequent `RecreateTableCall` copies data into
|
|
107
|
+
* the tightened schema (whose `INSERT INTO temp SELECT … FROM old` would
|
|
108
|
+
* otherwise fail at runtime if any `NULL`s remain).
|
|
109
|
+
*
|
|
110
|
+
* Does NOT consume the tightening issue — `recreateTableStrategy` still
|
|
111
|
+
* needs it to produce the actual recreate that enforces the NOT NULL at
|
|
112
|
+
* the schema level. The backfill op and the recreate op end up in the
|
|
113
|
+
* recipe slot in strategy order (backfill first, recreate second), which
|
|
114
|
+
* matches the required execution order.
|
|
115
|
+
*
|
|
116
|
+
* Mirrors Postgres's `nullableTighteningCallStrategy` / `'data'`-class
|
|
117
|
+
* gating. When `'data'` is not in the policy (the default `db update` /
|
|
118
|
+
* `db init` path), the strategy short-circuits and the recreate alone
|
|
119
|
+
* runs with its current destructive-class gating — preserving today's
|
|
120
|
+
* behavior where a tightening blows up at runtime if NULLs are present.
|
|
121
|
+
*/
|
|
122
|
+
const nullabilityTighteningBackfillStrategy = (issues, ctx) => {
|
|
123
|
+
if (!ctx.policy.allowedOperationClasses.includes("data")) return { kind: "no_match" };
|
|
124
|
+
const calls = [];
|
|
125
|
+
for (const issue of issues) {
|
|
126
|
+
if (issue.kind !== "nullability_mismatch") continue;
|
|
127
|
+
if (!issue.table || !issue.column) continue;
|
|
128
|
+
if (issue.expected === "true") continue;
|
|
129
|
+
const column = ctx.toContract.storage.tables[issue.table]?.columns[issue.column];
|
|
130
|
+
if (!column || column.nullable === true) continue;
|
|
131
|
+
calls.push(new DataTransformCall(`data_migration.backfill-${issue.table}-${issue.column}`, `Backfill NULLs in "${issue.table}"."${issue.column}" before NOT NULL tightening`, issue.table, issue.column));
|
|
132
|
+
}
|
|
133
|
+
if (calls.length === 0) return { kind: "no_match" };
|
|
134
|
+
return {
|
|
135
|
+
kind: "match",
|
|
136
|
+
issues,
|
|
137
|
+
calls,
|
|
138
|
+
recipe: true
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
const sqlitePlannerStrategies = [nullabilityTighteningBackfillStrategy, recreateTableStrategy];
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/core/migrations/issue-planner.ts
|
|
144
|
+
const ISSUE_KIND_ORDER = {
|
|
145
|
+
extra_foreign_key: 10,
|
|
146
|
+
extra_unique_constraint: 11,
|
|
147
|
+
extra_primary_key: 12,
|
|
148
|
+
extra_index: 13,
|
|
149
|
+
extra_default: 14,
|
|
150
|
+
extra_column: 15,
|
|
151
|
+
extra_table: 16,
|
|
152
|
+
missing_table: 20,
|
|
153
|
+
missing_column: 30,
|
|
154
|
+
type_mismatch: 40,
|
|
155
|
+
nullability_mismatch: 41,
|
|
156
|
+
default_missing: 42,
|
|
157
|
+
default_mismatch: 43,
|
|
158
|
+
primary_key_mismatch: 50,
|
|
159
|
+
unique_constraint_mismatch: 51,
|
|
160
|
+
index_mismatch: 52,
|
|
161
|
+
foreign_key_mismatch: 60
|
|
162
|
+
};
|
|
163
|
+
function issueOrder(issue) {
|
|
164
|
+
return ISSUE_KIND_ORDER[issue.kind] ?? 99;
|
|
165
|
+
}
|
|
166
|
+
function issueKey(issue) {
|
|
167
|
+
return `${"table" in issue && typeof issue.table === "string" ? issue.table : ""}\u0000${"column" in issue && typeof issue.column === "string" ? issue.column : ""}\u0000${"indexOrConstraint" in issue && typeof issue.indexOrConstraint === "string" ? issue.indexOrConstraint : ""}`;
|
|
168
|
+
}
|
|
169
|
+
function issueConflict(kind, summary, location) {
|
|
170
|
+
return {
|
|
171
|
+
kind,
|
|
172
|
+
summary,
|
|
173
|
+
why: "Use `migration new` to author a custom migration for this change.",
|
|
174
|
+
...location ? { location } : {}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function conflictKindForIssue(issue) {
|
|
178
|
+
switch (issue.kind) {
|
|
179
|
+
case "type_mismatch": return "typeMismatch";
|
|
180
|
+
case "nullability_mismatch": return "nullabilityConflict";
|
|
181
|
+
case "primary_key_mismatch":
|
|
182
|
+
case "unique_constraint_mismatch":
|
|
183
|
+
case "index_mismatch":
|
|
184
|
+
case "extra_primary_key":
|
|
185
|
+
case "extra_unique_constraint": return "indexIncompatible";
|
|
186
|
+
case "foreign_key_mismatch":
|
|
187
|
+
case "extra_foreign_key": return "foreignKeyConflict";
|
|
188
|
+
default: return "missingButNonAdditive";
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function issueLocation(issue) {
|
|
192
|
+
if (issue.kind === "enum_values_changed") return void 0;
|
|
193
|
+
const location = {};
|
|
194
|
+
if (issue.table) location.table = issue.table;
|
|
195
|
+
if (issue.column) location.column = issue.column;
|
|
196
|
+
if (issue.indexOrConstraint) location.constraint = issue.indexOrConstraint;
|
|
197
|
+
return Object.keys(location).length > 0 ? location : void 0;
|
|
198
|
+
}
|
|
199
|
+
function conflictForDisallowedCall(call, allowed) {
|
|
200
|
+
const summary = `Operation "${call.label}" requires class "${call.operationClass}", but policy allows only: ${allowed.join(", ")}`;
|
|
201
|
+
const location = locationForCall(call);
|
|
202
|
+
return {
|
|
203
|
+
kind: conflictKindForCall(call),
|
|
204
|
+
summary,
|
|
205
|
+
why: "Use `migration new` to author a custom migration for this change.",
|
|
206
|
+
...location ? { location } : {}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function conflictKindForCall(call) {
|
|
210
|
+
switch (call.factoryName) {
|
|
211
|
+
case "createIndex":
|
|
212
|
+
case "dropIndex": return "indexIncompatible";
|
|
213
|
+
default: return "missingButNonAdditive";
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function locationForCall(call) {
|
|
217
|
+
const location = {};
|
|
218
|
+
if ("tableName" in call) location.table = call.tableName;
|
|
219
|
+
if ("columnName" in call) location.column = call.columnName;
|
|
220
|
+
if ("indexName" in call) location.index = call.indexName;
|
|
221
|
+
return Object.keys(location).length > 0 ? location : void 0;
|
|
222
|
+
}
|
|
223
|
+
function isMissing(issue) {
|
|
224
|
+
if (issue.kind === "enum_values_changed") return false;
|
|
225
|
+
return issue.actual === void 0;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Resolves codec / `typeRef` / default rendering into a flat
|
|
229
|
+
* `SqliteColumnSpec`. Mirrors Postgres's `toColumnSpec`. Once a column is
|
|
230
|
+
* flattened, downstream Calls and operation factories never see
|
|
231
|
+
* `StorageColumn` again — they deal in pre-rendered SQL fragments.
|
|
232
|
+
*/
|
|
233
|
+
function toColumnSpec(name, column, storageTypes, inlineAutoincrementPrimaryKey = false) {
|
|
234
|
+
return {
|
|
235
|
+
name,
|
|
236
|
+
typeSql: buildColumnTypeSql(column, storageTypes),
|
|
237
|
+
defaultSql: buildColumnDefaultSql(column.default),
|
|
238
|
+
nullable: column.nullable,
|
|
239
|
+
...inlineAutoincrementPrimaryKey ? { inlineAutoincrementPrimaryKey: true } : {}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Flattens a `StorageTable` into a `SqliteTableSpec` ready for
|
|
244
|
+
* `CreateTableCall` / `RecreateTableCall`. Sole-column AUTOINCREMENT
|
|
245
|
+
* primary keys are detected here and marked on the column spec so the
|
|
246
|
+
* renderer emits `INTEGER PRIMARY KEY AUTOINCREMENT` inline.
|
|
247
|
+
*/
|
|
248
|
+
function toTableSpec(table, storageTypes) {
|
|
249
|
+
const columns = Object.entries(table.columns).map(([name, column]) => toColumnSpec(name, column, storageTypes, isInlineAutoincrementPrimaryKey(table, name)));
|
|
250
|
+
const uniques = table.uniques.map((u) => ({
|
|
251
|
+
columns: u.columns,
|
|
252
|
+
...u.name !== void 0 ? { name: u.name } : {}
|
|
253
|
+
}));
|
|
254
|
+
const foreignKeys = table.foreignKeys.map((fk) => ({
|
|
255
|
+
columns: fk.columns,
|
|
256
|
+
references: {
|
|
257
|
+
table: fk.references.table,
|
|
258
|
+
columns: fk.references.columns
|
|
259
|
+
},
|
|
260
|
+
constraint: fk.constraint !== false,
|
|
261
|
+
...fk.name !== void 0 ? { name: fk.name } : {},
|
|
262
|
+
...fk.onDelete !== void 0 ? { onDelete: fk.onDelete } : {},
|
|
263
|
+
...fk.onUpdate !== void 0 ? { onUpdate: fk.onUpdate } : {}
|
|
264
|
+
}));
|
|
265
|
+
return {
|
|
266
|
+
columns,
|
|
267
|
+
...table.primaryKey ? { primaryKey: { columns: table.primaryKey.columns } } : {},
|
|
268
|
+
uniques,
|
|
269
|
+
foreignKeys
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const DEFAULT_POLICY = { allowedOperationClasses: [
|
|
273
|
+
"additive",
|
|
274
|
+
"widening",
|
|
275
|
+
"destructive",
|
|
276
|
+
"data"
|
|
277
|
+
] };
|
|
278
|
+
function emptySchemaIR() {
|
|
279
|
+
return { tables: {} };
|
|
280
|
+
}
|
|
281
|
+
function mapIssueToCall(issue, ctx) {
|
|
282
|
+
switch (issue.kind) {
|
|
283
|
+
case "missing_table": {
|
|
284
|
+
if (!issue.table) return notOk(issueConflict("unsupportedOperation", "Missing table issue has no table name"));
|
|
285
|
+
const contractTable = ctx.toContract.storage.tables[issue.table];
|
|
286
|
+
if (!contractTable) return notOk(issueConflict("unsupportedOperation", `Table "${issue.table}" reported missing but not found in destination contract`));
|
|
287
|
+
const tableSpec = toTableSpec(contractTable, ctx.storageTypes);
|
|
288
|
+
const calls = [new CreateTableCall(issue.table, tableSpec)];
|
|
289
|
+
const declaredIndexColumnKeys = /* @__PURE__ */ new Set();
|
|
290
|
+
for (const index of contractTable.indexes) {
|
|
291
|
+
const indexName = index.name ?? defaultIndexName(issue.table, index.columns);
|
|
292
|
+
declaredIndexColumnKeys.add(index.columns.join(","));
|
|
293
|
+
calls.push(new CreateIndexCall(issue.table, indexName, index.columns));
|
|
294
|
+
}
|
|
295
|
+
for (const fk of contractTable.foreignKeys) {
|
|
296
|
+
if (fk.index === false) continue;
|
|
297
|
+
if (declaredIndexColumnKeys.has(fk.columns.join(","))) continue;
|
|
298
|
+
const indexName = defaultIndexName(issue.table, fk.columns);
|
|
299
|
+
calls.push(new CreateIndexCall(issue.table, indexName, fk.columns));
|
|
300
|
+
}
|
|
301
|
+
return ok(calls);
|
|
302
|
+
}
|
|
303
|
+
case "missing_column": {
|
|
304
|
+
if (!issue.table || !issue.column) return notOk(issueConflict("unsupportedOperation", "Missing column issue has no table/column name"));
|
|
305
|
+
const column = ctx.toContract.storage.tables[issue.table]?.columns[issue.column];
|
|
306
|
+
if (!column) return notOk(issueConflict("unsupportedOperation", `Column "${issue.table}"."${issue.column}" not in destination contract`));
|
|
307
|
+
const contractTable = ctx.toContract.storage.tables[issue.table];
|
|
308
|
+
const columnSpec = toColumnSpec(issue.column, column, ctx.storageTypes, contractTable ? isInlineAutoincrementPrimaryKey(contractTable, issue.column) : false);
|
|
309
|
+
return ok([new AddColumnCall(issue.table, columnSpec)]);
|
|
310
|
+
}
|
|
311
|
+
case "index_mismatch": {
|
|
312
|
+
if (!issue.table) return notOk(issueConflict("indexIncompatible", "Index issue has no table name"));
|
|
313
|
+
if (!isMissing(issue) || !issue.expected) return notOk(issueConflict("indexIncompatible", `Index on "${issue.table}" differs (expected: ${issue.expected}, actual: ${issue.actual})`, { table: issue.table }));
|
|
314
|
+
const columns = issue.expected.split(", ");
|
|
315
|
+
const contractTable = ctx.toContract.storage.tables[issue.table];
|
|
316
|
+
if (!contractTable) return notOk(issueConflict("unsupportedOperation", `Table "${issue.table}" not found in destination contract`));
|
|
317
|
+
const indexName = contractTable.indexes.find((idx) => idx.columns.join(",") === columns.join(","))?.name ?? defaultIndexName(issue.table, columns);
|
|
318
|
+
return ok([new CreateIndexCall(issue.table, indexName, columns)]);
|
|
319
|
+
}
|
|
320
|
+
case "extra_table":
|
|
321
|
+
if (!issue.table) return notOk(issueConflict("unsupportedOperation", "Extra table issue has no table name"));
|
|
322
|
+
if (CONTROL_TABLE_NAMES.has(issue.table)) return ok([]);
|
|
323
|
+
return ok([new DropTableCall(issue.table)]);
|
|
324
|
+
case "extra_column":
|
|
325
|
+
if (!issue.table || !issue.column) return notOk(issueConflict("unsupportedOperation", "Extra column issue has no table/column name"));
|
|
326
|
+
return ok([new DropColumnCall(issue.table, issue.column)]);
|
|
327
|
+
case "extra_index":
|
|
328
|
+
if (!issue.table || !issue.indexOrConstraint) return notOk(issueConflict("unsupportedOperation", "Extra index issue has no table/index name"));
|
|
329
|
+
return ok([new DropIndexCall(issue.table, issue.indexOrConstraint)]);
|
|
330
|
+
case "enum_values_changed": return notOk(issueConflict("unsupportedOperation", "Received enum_values_changed against a SQLite schema (sql.enums: false) — verifier bug"));
|
|
331
|
+
case "type_mismatch":
|
|
332
|
+
case "nullability_mismatch":
|
|
333
|
+
case "default_mismatch":
|
|
334
|
+
case "default_missing":
|
|
335
|
+
case "extra_default":
|
|
336
|
+
case "primary_key_mismatch":
|
|
337
|
+
case "unique_constraint_mismatch":
|
|
338
|
+
case "foreign_key_mismatch":
|
|
339
|
+
case "extra_foreign_key":
|
|
340
|
+
case "extra_unique_constraint":
|
|
341
|
+
case "extra_primary_key": return notOk(issueConflict(conflictKindForIssue(issue), issue.message, issueLocation(issue)));
|
|
342
|
+
default: return notOk(issueConflict("unsupportedOperation", `Unhandled issue kind: ${issue.kind}`));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function classifyCall(call) {
|
|
346
|
+
switch (call.factoryName) {
|
|
347
|
+
case "createTable": return "create-table";
|
|
348
|
+
case "addColumn": return "add-column";
|
|
349
|
+
case "createIndex": return "create-index";
|
|
350
|
+
case "dropColumn": return "drop-column";
|
|
351
|
+
case "dropIndex": return "drop-index";
|
|
352
|
+
case "dropTable": return "drop-table";
|
|
353
|
+
case "recreateTable": return null;
|
|
354
|
+
default: return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function planIssues(options) {
|
|
358
|
+
const policyProvided = options.policy !== void 0;
|
|
359
|
+
const policy = options.policy ?? DEFAULT_POLICY;
|
|
360
|
+
const schema = options.schema ?? emptySchemaIR();
|
|
361
|
+
const frameworkComponents = options.frameworkComponents ?? [];
|
|
362
|
+
const context = {
|
|
363
|
+
toContract: options.toContract,
|
|
364
|
+
fromContract: options.fromContract,
|
|
365
|
+
codecHooks: options.codecHooks,
|
|
366
|
+
storageTypes: options.storageTypes,
|
|
367
|
+
schema,
|
|
368
|
+
policy,
|
|
369
|
+
frameworkComponents
|
|
370
|
+
};
|
|
371
|
+
const strategies = options.strategies ?? sqlitePlannerStrategies;
|
|
372
|
+
let remaining = options.issues;
|
|
373
|
+
const recipeCalls = [];
|
|
374
|
+
const bucketableCalls = [];
|
|
375
|
+
for (const strategy of strategies) {
|
|
376
|
+
const result = strategy(remaining, context);
|
|
377
|
+
if (result.kind === "match") {
|
|
378
|
+
remaining = result.issues;
|
|
379
|
+
if (result.recipe) recipeCalls.push(...result.calls);
|
|
380
|
+
else bucketableCalls.push(...result.calls);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const sorted = [...remaining].sort((a, b) => {
|
|
384
|
+
const kindDelta = issueOrder(a) - issueOrder(b);
|
|
385
|
+
if (kindDelta !== 0) return kindDelta;
|
|
386
|
+
const keyA = issueKey(a);
|
|
387
|
+
const keyB = issueKey(b);
|
|
388
|
+
return keyA < keyB ? -1 : keyA > keyB ? 1 : 0;
|
|
389
|
+
});
|
|
390
|
+
const defaultCalls = [];
|
|
391
|
+
const conflicts = [];
|
|
392
|
+
for (const issue of sorted) {
|
|
393
|
+
const result = mapIssueToCall(issue, context);
|
|
394
|
+
if (result.ok) defaultCalls.push(...result.value);
|
|
395
|
+
else conflicts.push(result.failure);
|
|
396
|
+
}
|
|
397
|
+
const allowed = policy.allowedOperationClasses;
|
|
398
|
+
let gatedRecipe = recipeCalls;
|
|
399
|
+
let gatedBucketable = bucketableCalls;
|
|
400
|
+
let gatedDefault = defaultCalls;
|
|
401
|
+
if (policyProvided) {
|
|
402
|
+
const sink = (acc) => (call) => {
|
|
403
|
+
if (allowed.includes(call.operationClass)) {
|
|
404
|
+
acc.push(call);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
conflicts.push(conflictForDisallowedCall(call, allowed));
|
|
408
|
+
};
|
|
409
|
+
const gatedRecipeBucket = [];
|
|
410
|
+
const gatedBucketableBucket = [];
|
|
411
|
+
const gatedDefaultBucket = [];
|
|
412
|
+
recipeCalls.forEach(sink(gatedRecipeBucket));
|
|
413
|
+
bucketableCalls.forEach(sink(gatedBucketableBucket));
|
|
414
|
+
defaultCalls.forEach(sink(gatedDefaultBucket));
|
|
415
|
+
gatedRecipe = gatedRecipeBucket;
|
|
416
|
+
gatedBucketable = gatedBucketableBucket;
|
|
417
|
+
gatedDefault = gatedDefaultBucket;
|
|
418
|
+
}
|
|
419
|
+
if (conflicts.length > 0) return notOk(conflicts);
|
|
420
|
+
const combined = [...gatedDefault, ...gatedBucketable];
|
|
421
|
+
const byCategory = (cat) => combined.filter((c) => classifyCall(c) === cat);
|
|
422
|
+
return ok({ calls: [
|
|
423
|
+
...byCategory("create-table"),
|
|
424
|
+
...byCategory("add-column"),
|
|
425
|
+
...byCategory("create-index"),
|
|
426
|
+
...gatedRecipe,
|
|
427
|
+
...byCategory("drop-column"),
|
|
428
|
+
...byCategory("drop-index"),
|
|
429
|
+
...byCategory("drop-table")
|
|
430
|
+
] });
|
|
431
|
+
}
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/core/migrations/planner.ts
|
|
434
|
+
function createSqliteMigrationPlanner() {
|
|
435
|
+
return new SqliteMigrationPlanner();
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* SQLite migration planner — a thin wrapper over `planIssues`.
|
|
439
|
+
*
|
|
440
|
+
* `plan()` verifies the live schema against the target contract (producing
|
|
441
|
+
* `SchemaIssue[]`) and delegates to `planIssues` with the registered
|
|
442
|
+
* strategies. Strategies absorb groups of related issues into composite
|
|
443
|
+
* recipes (e.g. recreating a table to apply type/nullability/default/
|
|
444
|
+
* constraint changes at once); anything not absorbed by a strategy flows
|
|
445
|
+
* through `mapIssueToCall` in the issue planner as a one-off call.
|
|
446
|
+
*
|
|
447
|
+
* FK-backing indexes are surfaced by `verifySqlSchema`'s index expansion
|
|
448
|
+
* (see `verify-sql-schema.ts:459-469`), so `mapIssueToCall` handles them
|
|
449
|
+
* uniformly alongside user-declared indexes.
|
|
450
|
+
*/
|
|
451
|
+
var SqliteMigrationPlanner = class {
|
|
452
|
+
plan(options) {
|
|
453
|
+
return this.planSql(options);
|
|
454
|
+
}
|
|
455
|
+
emptyMigration(context, spaceId) {
|
|
456
|
+
return new TypeScriptRenderableSqliteMigration([], {
|
|
457
|
+
from: context.fromHash,
|
|
458
|
+
to: context.toHash
|
|
459
|
+
}, spaceId);
|
|
460
|
+
}
|
|
461
|
+
planSql(options) {
|
|
462
|
+
const policyResult = this.ensureAdditivePolicy(options.policy);
|
|
463
|
+
if (policyResult) return policyResult;
|
|
464
|
+
const schemaIssues = this.collectSchemaIssues(options);
|
|
465
|
+
const codecHooks = extractCodecControlHooks(options.frameworkComponents);
|
|
466
|
+
const storageTypes = options.contract.storage.types ?? {};
|
|
467
|
+
const result = planIssues({
|
|
468
|
+
issues: schemaIssues,
|
|
469
|
+
toContract: options.contract,
|
|
470
|
+
fromContract: options.fromContract,
|
|
471
|
+
codecHooks,
|
|
472
|
+
storageTypes,
|
|
473
|
+
schema: options.schema,
|
|
474
|
+
policy: options.policy,
|
|
475
|
+
frameworkComponents: options.frameworkComponents,
|
|
476
|
+
strategies: sqlitePlannerStrategies
|
|
477
|
+
});
|
|
478
|
+
if (!result.ok) return plannerFailure(result.failure);
|
|
479
|
+
const fieldEventOps = planFieldEventOperations({
|
|
480
|
+
priorContract: options.fromContract,
|
|
481
|
+
newContract: options.contract,
|
|
482
|
+
codecHooks
|
|
483
|
+
});
|
|
484
|
+
const calls = [...result.value.calls, ...fieldEventOps];
|
|
485
|
+
const destination = {
|
|
486
|
+
storageHash: options.contract.storage.storageHash,
|
|
487
|
+
...options.contract.profileHash !== void 0 ? { profileHash: options.contract.profileHash } : {}
|
|
488
|
+
};
|
|
489
|
+
return {
|
|
490
|
+
kind: "success",
|
|
491
|
+
plan: new TypeScriptRenderableSqliteMigration(calls, {
|
|
492
|
+
from: options.fromContract?.storage.storageHash ?? null,
|
|
493
|
+
to: options.contract.storage.storageHash
|
|
494
|
+
}, options.spaceId, destination)
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
ensureAdditivePolicy(policy) {
|
|
498
|
+
if (!policy.allowedOperationClasses.includes("additive")) return plannerFailure([{
|
|
499
|
+
kind: "unsupportedOperation",
|
|
500
|
+
summary: "Migration planner requires additive operations be allowed",
|
|
501
|
+
why: "The planner requires the \"additive\" operation class to be allowed in the policy."
|
|
502
|
+
}]);
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
collectSchemaIssues(options) {
|
|
506
|
+
const allowed = options.policy.allowedOperationClasses;
|
|
507
|
+
const strict = allowed.includes("widening") || allowed.includes("destructive");
|
|
508
|
+
return verifySqlSchema({
|
|
509
|
+
contract: options.contract,
|
|
510
|
+
schema: options.schema,
|
|
511
|
+
strict,
|
|
512
|
+
typeMetadataRegistry: /* @__PURE__ */ new Map(),
|
|
513
|
+
frameworkComponents: options.frameworkComponents,
|
|
514
|
+
normalizeDefault: parseSqliteDefault,
|
|
515
|
+
normalizeNativeType: normalizeSqliteNativeType
|
|
516
|
+
}).schema.issues;
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
//#endregion
|
|
520
|
+
export { createSqliteMigrationPlanner as n, SqliteMigrationPlanner as t };
|
|
521
|
+
|
|
522
|
+
//# sourceMappingURL=planner-A7dqS0u6.mjs.map
|