@prisma-next/target-sqlite 0.14.0-dev.5 → 0.14.0-dev.51

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.
Files changed (70) hide show
  1. package/dist/contract-free.mjs +1 -1
  2. package/dist/control.d.mts +3 -4
  3. package/dist/control.d.mts.map +1 -1
  4. package/dist/control.mjs +16 -27
  5. package/dist/control.mjs.map +1 -1
  6. package/dist/{descriptor-meta-DxmEeTJ-.mjs → descriptor-meta-D1fap-5t.mjs} +4 -11
  7. package/dist/descriptor-meta-D1fap-5t.mjs.map +1 -0
  8. package/dist/descriptor-meta-runtime-BkXK3OjD.mjs +12 -0
  9. package/dist/descriptor-meta-runtime-BkXK3OjD.mjs.map +1 -0
  10. package/dist/migration.d.mts +2 -2
  11. package/dist/migration.mjs +2 -2
  12. package/dist/native-type-normalizer.mjs +13 -1
  13. package/dist/native-type-normalizer.mjs.map +1 -0
  14. package/dist/{op-factory-call-DmdfD1yd.mjs → op-factory-call-D_eYsHTp.mjs} +134 -116
  15. package/dist/op-factory-call-D_eYsHTp.mjs.map +1 -0
  16. package/dist/op-factory-call.d.mts +2 -2
  17. package/dist/op-factory-call.d.mts.map +1 -1
  18. package/dist/op-factory-call.mjs +1 -1
  19. package/dist/pack.mjs +1 -1
  20. package/dist/planner-DhvFrgPT.mjs +854 -0
  21. package/dist/planner-DhvFrgPT.mjs.map +1 -0
  22. package/dist/{planner-produced-sqlite-migration-CpgsY-M9.d.mts → planner-produced-sqlite-migration-B9UZVDPa.d.mts} +3 -3
  23. package/dist/{planner-produced-sqlite-migration-CpgsY-M9.d.mts.map → planner-produced-sqlite-migration-B9UZVDPa.d.mts.map} +1 -1
  24. package/dist/{planner-produced-sqlite-migration-0xPEm3R1.mjs → planner-produced-sqlite-migration-DyRmcteq.mjs} +46 -17
  25. package/dist/planner-produced-sqlite-migration-DyRmcteq.mjs.map +1 -0
  26. package/dist/planner-produced-sqlite-migration.d.mts +1 -1
  27. package/dist/planner-produced-sqlite-migration.mjs +1 -1
  28. package/dist/planner.d.mts +47 -11
  29. package/dist/planner.d.mts.map +1 -1
  30. package/dist/planner.mjs +1 -1
  31. package/dist/runtime.d.mts +6 -4
  32. package/dist/runtime.d.mts.map +1 -1
  33. package/dist/runtime.mjs +3 -3
  34. package/dist/{shared-Dhc8mLK1.d.mts → shared-C6sM-wel.d.mts} +2 -2
  35. package/dist/{shared-Dhc8mLK1.d.mts.map → shared-C6sM-wel.d.mts.map} +1 -1
  36. package/dist/{sqlite-contract-serializer--iaDgC8e.mjs → sqlite-contract-view-BEwi0mUC.mjs} +34 -9
  37. package/dist/sqlite-contract-view-BEwi0mUC.mjs.map +1 -0
  38. package/dist/sqlite-contract-view-J32K82Gi.d.mts +29 -0
  39. package/dist/sqlite-contract-view-J32K82Gi.d.mts.map +1 -0
  40. package/dist/{sqlite-migration-A0rwqPOG.mjs → sqlite-migration-D93wCdSD.mjs} +29 -2
  41. package/dist/sqlite-migration-D93wCdSD.mjs.map +1 -0
  42. package/dist/{sqlite-migration-DVfhQwN_.d.mts → sqlite-migration-_BhTcnIe.d.mts} +27 -4
  43. package/dist/sqlite-migration-_BhTcnIe.d.mts.map +1 -0
  44. package/package.json +18 -18
  45. package/src/core/control-target.ts +13 -18
  46. package/src/core/migrations/column-ddl-rendering.ts +177 -0
  47. package/src/core/migrations/diff-database-schema.ts +240 -0
  48. package/src/core/migrations/issue-planner.ts +359 -488
  49. package/src/core/migrations/op-factory-call.ts +5 -0
  50. package/src/core/migrations/operations/tables.ts +107 -67
  51. package/src/core/migrations/planner-strategies.ts +118 -137
  52. package/src/core/migrations/planner.ts +117 -30
  53. package/src/core/migrations/render-typescript.ts +42 -15
  54. package/src/core/migrations/runner.ts +4 -10
  55. package/src/core/migrations/sqlite-migration.ts +42 -1
  56. package/src/core/sqlite-contract-serializer.ts +29 -14
  57. package/src/core/sqlite-contract-view.ts +41 -0
  58. package/src/core/sqlite-schema-verifier.ts +6 -3
  59. package/src/core/sqlite-unbound-database.ts +8 -8
  60. package/src/exports/runtime.ts +1 -0
  61. package/dist/descriptor-meta-DxmEeTJ-.mjs.map +0 -1
  62. package/dist/native-type-normalizer-CiSyVmMP.mjs +0 -14
  63. package/dist/native-type-normalizer-CiSyVmMP.mjs.map +0 -1
  64. package/dist/op-factory-call-DmdfD1yd.mjs.map +0 -1
  65. package/dist/planner-Ciq8p_dL.mjs +0 -622
  66. package/dist/planner-Ciq8p_dL.mjs.map +0 -1
  67. package/dist/planner-produced-sqlite-migration-0xPEm3R1.mjs.map +0 -1
  68. package/dist/sqlite-contract-serializer--iaDgC8e.mjs.map +0 -1
  69. package/dist/sqlite-migration-A0rwqPOG.mjs.map +0 -1
  70. package/dist/sqlite-migration-DVfhQwN_.d.mts.map +0 -1
@@ -0,0 +1,854 @@
1
+ import { a as DropColumnCall, d as buildRecreateSummary, h as renderDefaultLiteral, i as DataTransformCall, l as RecreateTableCall, m as buildColumnTypeSql, n as CreateIndexCall, o as DropIndexCall, p as buildColumnDefaultSql, r as CreateTableCall, s as DropTableCall, t as AddColumnCall, u as buildRecreatePostchecks } from "./op-factory-call-D_eYsHTp.mjs";
2
+ import { t as CONTROL_TABLE_NAMES } from "./control-tables-7KwMyJ6i.mjs";
3
+ import { t as TypeScriptRenderableSqliteMigration } from "./planner-produced-sqlite-migration-DyRmcteq.mjs";
4
+ import { DdlColumn, ForeignKeyConstraint, FunctionColumnDefault, LiteralColumnDefault, PrimaryKeyConstraint, UniqueConstraint } from "@prisma-next/sql-relational-core/ast";
5
+ import { RelationalSchemaNodeKind, SqlSchemaIR, relationalNodeGranularity } from "@prisma-next/sql-schema-ir/types";
6
+ import { UNBOUND_NAMESPACE_ID, entityAt } from "@prisma-next/framework-components/ir";
7
+ import { buildNativeTypeExpander, contractToSchemaIR, extractCodecControlHooks, planFieldEventOperations, plannerFailure } from "@prisma-next/family-sql/control";
8
+ import { neutralizeFlatExpectedFkSchemas, normalizeFlatActualForDiff, verifySqlSchemaByDiff } from "@prisma-next/family-sql/diff";
9
+ import { diffSchemas } from "@prisma-next/framework-components/control";
10
+ import { blindCast } from "@prisma-next/utils/casts";
11
+ import { ifDefined } from "@prisma-next/utils/defined";
12
+ import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
13
+ import { notOk, ok } from "@prisma-next/utils/result";
14
+ //#region src/core/migrations/diff-database-schema.ts
15
+ /** Renders a column default for the SQLite dialect. */
16
+ function sqliteRenderDefault(def, _column) {
17
+ if (def.kind === "function") {
18
+ if (def.expression === "now()") return "datetime('now')";
19
+ return def.expression;
20
+ }
21
+ return renderDefaultLiteral(def.value);
22
+ }
23
+ /**
24
+ * The SQLite expected-side projection: contract → flat relational schema IR.
25
+ *
26
+ * `extras` thread the plan-time derivation input: the native-type expander,
27
+ * so the expected side carries resolved native types (like the verify
28
+ * side). Every expected column also carries its `codecRef` unconditionally
29
+ * (Decision 5) — the planner's op-builders resolve DDL rendering from it at
30
+ * plan time, so no separate render stamper is threaded here.
31
+ */
32
+ function sqliteContractToSchema(contract, extras) {
33
+ return contractToSchemaIR(contract, {
34
+ annotationNamespace: "sqlite",
35
+ renderDefault: sqliteRenderDefault,
36
+ ...ifDefined("expandNativeType", extras?.expandNativeType)
37
+ });
38
+ }
39
+ /**
40
+ * The SQLite schema verify: the full-tree node-diff verdict wrapped in the
41
+ * issue-based result envelope. Used by the runner's post-apply check; the
42
+ * family `verifySchema` runs the same composition via the descriptor hook.
43
+ */
44
+ function verifySqliteDatabaseSchema(input) {
45
+ return verifySqlSchemaByDiff({
46
+ contract: input.contract,
47
+ schema: input.actualSchema,
48
+ strict: input.strict,
49
+ frameworkComponents: input.frameworkComponents,
50
+ diffSchema: diffSqliteSchema,
51
+ granularityOf: relationalNodeGranularity
52
+ });
53
+ }
54
+ /**
55
+ * Resolves a verdict-diff issue's subject table's declared control policy
56
+ * directly from the contract. SQLite's expected tree is flat, so the issue
57
+ * path carries no namespace segment — `path[1]` is the table name (`path[0]`
58
+ * is the tree root's own id); every contract namespace is searched for that
59
+ * table name (table names are globally unique in the flat tree — duplicates
60
+ * across namespaces are rejected earlier, at `contractToSchemaIR`).
61
+ */
62
+ function resolveControlPolicy(issue, contract) {
63
+ const tableName = issue.path[1];
64
+ if (tableName === void 0) return void 0;
65
+ for (const namespaceId of Object.keys(contract.storage.namespaces)) {
66
+ const table = entityAt(contract.storage, {
67
+ namespaceId,
68
+ entityKind: "table",
69
+ entityName: tableName
70
+ });
71
+ if (table !== void 0) return table.control;
72
+ }
73
+ }
74
+ /**
75
+ * The SQLite full-tree node diff for the family verify verdict: derive the
76
+ * expected flat tree with resolved leaf values (expander threaded so
77
+ * parameterized types compare expanded), neutralize the FK schema segment
78
+ * (single-schema target — introspection stamps none), normalize the actual
79
+ * tree for semantic satisfaction, and run the generic differ. Flat targets
80
+ * need no ownership scoping. The codec `verifyType` hooks run once per
81
+ * contract namespace with tables, each against the sole flat actual root —
82
+ * exactly the legacy per-namespace pairing.
83
+ */
84
+ function diffSqliteSchema(input) {
85
+ const expandNativeType = buildNativeTypeExpander(input.frameworkComponents);
86
+ const expected = neutralizeFlatExpectedFkSchemas(contractToSchemaIR(input.contract, {
87
+ annotationNamespace: "sqlite",
88
+ renderDefault: sqliteRenderDefault,
89
+ ...ifDefined("expandNativeType", expandNativeType)
90
+ }));
91
+ const actual = input.schema instanceof SqlSchemaIR ? input.schema : blindCast(input.schema);
92
+ return {
93
+ issues: diffSchemas(expected, normalizeFlatActualForDiff(expected, actual)),
94
+ resolveControlPolicy: (issue) => resolveControlPolicy(issue, input.contract),
95
+ namespacePairs: Object.values(input.contract.storage.namespaces).filter((ns) => Object.keys(ns.entries.table ?? {}).length > 0).map(() => ({ actual }))
96
+ };
97
+ }
98
+ /**
99
+ * The SQLite planner's diff input: the same tree-building
100
+ * `diffSqliteSchema` uses (expander threaded, FK schema segment
101
+ * neutralized, actual tree normalized for semantic satisfaction). One differ
102
+ * drives both verify and plan; this is the plan-side derivation — column DDL
103
+ * resolves from each expected column's `codecRef` at plan time
104
+ * (`column-ddl-rendering.ts`), so no separate render stamping happens here.
105
+ */
106
+ function buildSqlitePlanDiff(input) {
107
+ const expandNativeType = buildNativeTypeExpander(input.frameworkComponents);
108
+ const expected = neutralizeFlatExpectedFkSchemas(sqliteContractToSchema(input.contract, { ...ifDefined("expandNativeType", expandNativeType) }));
109
+ const actual = normalizeFlatActualForDiff(expected, new SqlSchemaIR(withRecordKeyNames(input.actualSchema)));
110
+ return {
111
+ expected,
112
+ actual,
113
+ issues: diffSchemas(expected, actual)
114
+ };
115
+ }
116
+ /**
117
+ * Every schema-tree builder in this codebase derives a table's / column's
118
+ * `name` from the record key it's stored under (`contractToSchemaIR`,
119
+ * the SQLite introspection adapter) rather than trusting a redundant
120
+ * embedded field — the record key IS the identity. Mirrors that discipline
121
+ * for the actual/live tree before construction, so `SqlTableIR.id` /
122
+ * `SqlColumnIR.id` (both derived from `.name`) are always correct without
123
+ * requiring every caller to duplicate the key onto the value. A no-op for a
124
+ * tree that already carries matching names (the real introspection adapter
125
+ * always does).
126
+ */
127
+ function withRecordKeyNames(actualSchema) {
128
+ const raw = blindCast(actualSchema);
129
+ const tables = {};
130
+ for (const [tableName, table] of Object.entries(raw.tables ?? {})) {
131
+ const rawTable = blindCast(table);
132
+ const columns = {};
133
+ for (const [columnName, column] of Object.entries(blindCast(table).columns ?? {})) columns[columnName] = {
134
+ ...blindCast(column),
135
+ name: columnName
136
+ };
137
+ tables[tableName] = {
138
+ ...rawTable,
139
+ name: tableName,
140
+ columns,
141
+ foreignKeys: rawTable.foreignKeys ?? [],
142
+ uniques: rawTable.uniques ?? [],
143
+ indexes: rawTable.indexes ?? []
144
+ };
145
+ }
146
+ return { tables };
147
+ }
148
+ //#endregion
149
+ //#region src/core/migrations/column-ddl-rendering.ts
150
+ /**
151
+ * Reconstructs the `StorageColumn`-shaped fields `buildColumnTypeSql` /
152
+ * `buildColumnDefaultSql` expect, from a column node's own stamped codec
153
+ * identity (`codecRef` / `codecBaseNativeType`, Decision 5) — never the
154
+ * contract. SQLite's type renderer only uppercases the resolved base type
155
+ * (no parameterized expansion, no named-type quoting), so `typeRef` is
156
+ * deliberately left unset here: setting it would send `buildColumnTypeSql`
157
+ * back through a live `storageTypes` lookup the node's fields have already
158
+ * resolved past, which throws for an unrecognized reference (unlike
159
+ * Postgres's lenient fallback).
160
+ */
161
+ function columnLike(column) {
162
+ if (column.codecRef === void 0 || column.codecBaseNativeType === void 0) throw new Error(`columnLike: expected column "${column.name}" carries no codec identity — the expected tree must be derived via contractToSchemaIR for planning`);
163
+ return {
164
+ nativeType: column.codecBaseNativeType,
165
+ codecId: column.codecRef.codecId,
166
+ nullable: column.nullable,
167
+ ...(column.many ?? column.codecRef.many) !== void 0 ? { many: column.many ?? column.codecRef.many } : {},
168
+ ...column.codecRef.typeParams !== void 0 ? { typeParams: blindCast(column.codecRef.typeParams) } : {},
169
+ ...column.resolvedDefault !== void 0 ? { default: column.resolvedDefault } : {}
170
+ };
171
+ }
172
+ function sqliteDefaultToDdlColumnDefault(columnDefault) {
173
+ if (!columnDefault) return void 0;
174
+ switch (columnDefault.kind) {
175
+ case "literal": return new LiteralColumnDefault(columnDefault.value);
176
+ case "function":
177
+ if (columnDefault.expression === "autoincrement()") return void 0;
178
+ return new FunctionColumnDefault(columnDefault.expression);
179
+ default: throw new Error(`sqliteDefaultToDdlColumnDefault: unhandled kind "${blindCast(columnDefault).kind}"`);
180
+ }
181
+ }
182
+ /**
183
+ * True when the column is rendered inline as `INTEGER PRIMARY KEY
184
+ * AUTOINCREMENT` — the sole member of the table's primary key with an
185
+ * `autoincrement()` default. Node-based sibling of the retired
186
+ * `isInlineAutoincrementPrimaryKey` (which read the raw `StorageTable`);
187
+ * reads the table/column nodes instead.
188
+ */
189
+ function isInlineAutoincrementPrimaryKeyNode(table, column) {
190
+ if (table.primaryKey?.columns.length !== 1) return false;
191
+ if (table.primaryKey.columns[0] !== column.name) return false;
192
+ return column.resolvedDefault?.kind === "function" && column.resolvedDefault.expression === "autoincrement()";
193
+ }
194
+ /**
195
+ * Builds the flat `SqliteColumnSpec` `AddColumnCall` / `RecreateTableCall`
196
+ * need, resolved from the column node's codec identity — the same builders
197
+ * the pre-`plan(start, end)` op-path called, so the output is
198
+ * byte-identical.
199
+ */
200
+ function columnSpecFromNode(column, inline) {
201
+ const like = columnLike(column);
202
+ const typeSql = buildColumnTypeSql(like, {});
203
+ const defaultSql = buildColumnDefaultSql(like.default);
204
+ return {
205
+ name: column.name,
206
+ typeSql,
207
+ defaultSql,
208
+ nullable: column.nullable,
209
+ ...inline ? { inlineAutoincrementPrimaryKey: true } : {}
210
+ };
211
+ }
212
+ /**
213
+ * Builds the `DdlColumn` the `CreateTableCall` path needs, resolved from the
214
+ * column node's codec identity.
215
+ */
216
+ function ddlColumnFromNode(column, inline) {
217
+ const like = columnLike(column);
218
+ const typeSql = buildColumnTypeSql(like, {});
219
+ if (inline) return new DdlColumn({
220
+ name: column.name,
221
+ type: `${typeSql} PRIMARY KEY AUTOINCREMENT`
222
+ });
223
+ const colDefault = sqliteDefaultToDdlColumnDefault(like.default);
224
+ return new DdlColumn({
225
+ name: column.name,
226
+ type: typeSql,
227
+ ...!column.nullable ? { notNull: true } : {},
228
+ ...colDefault !== void 0 ? { default: colDefault } : {},
229
+ ...column.codecRef !== void 0 ? { codecRef: column.codecRef } : {}
230
+ });
231
+ }
232
+ /**
233
+ * Builds the table-level constraints (PK / unique / FK) for a `CreateTable`
234
+ * path from the table node — the node-sourced sibling of the retired
235
+ * contract-based `tableToDdlParts`'s constraint half.
236
+ */
237
+ function tableConstraintsFromNode(table, hasInlinePk) {
238
+ const constraints = [];
239
+ if (table.primaryKey && !hasInlinePk) constraints.push(new PrimaryKeyConstraint({ columns: table.primaryKey.columns }));
240
+ for (const u of table.uniques) constraints.push(new UniqueConstraint({
241
+ columns: u.columns,
242
+ ...u.name !== void 0 ? { name: u.name } : {}
243
+ }));
244
+ for (const fk of table.foreignKeys) constraints.push(new ForeignKeyConstraint({
245
+ columns: fk.columns,
246
+ refTable: fk.referencedTable,
247
+ refColumns: fk.referencedColumns,
248
+ ...fk.name !== void 0 ? { name: fk.name } : {},
249
+ ...fk.onDelete !== void 0 ? { onDelete: fk.onDelete } : {},
250
+ ...fk.onUpdate !== void 0 ? { onUpdate: fk.onUpdate } : {}
251
+ }));
252
+ return constraints;
253
+ }
254
+ //#endregion
255
+ //#region src/core/migrations/planner-strategies.ts
256
+ /**
257
+ * Classifies a node issue into the operation class a recreate absorbing it
258
+ * would need, or `null` when the strategy doesn't handle this node/reason at
259
+ * all (table/column not-found-or-not-expected, and index issues — those are
260
+ * standalone ops, never folded into a recreate).
261
+ *
262
+ * Column drift is a single `not-equal` issue now (type AND nullability
263
+ * compared together by `SqlColumnIR.isEqualTo`), so this reads both fields
264
+ * off the node pair directly rather than trusting a separate issue kind per
265
+ * attribute: a type change is always destructive; a pure nullability change
266
+ * is destructive when tightening (NOT NULL required) and widening when
267
+ * relaxing.
268
+ */
269
+ function classifyNodeIssue(issue) {
270
+ const node = issue.expected ?? issue.actual;
271
+ if (node === void 0) return null;
272
+ switch (blindCast(node).nodeKind) {
273
+ case RelationalSchemaNodeKind.column: {
274
+ if (issue.reason !== "not-equal") return null;
275
+ const expected = blindCast(issue.expected);
276
+ if (columnTypeChanged(expected, blindCast(issue.actual))) return "destructive";
277
+ return expected.nullable ? "widening" : "destructive";
278
+ }
279
+ case RelationalSchemaNodeKind.columnDefault: return issue.reason === "not-expected" ? "destructive" : "widening";
280
+ case RelationalSchemaNodeKind.primaryKey:
281
+ case RelationalSchemaNodeKind.foreignKey:
282
+ case RelationalSchemaNodeKind.unique: return "destructive";
283
+ default: return null;
284
+ }
285
+ }
286
+ /**
287
+ * Groups recreate-eligible issues by table, decides per-table operation class
288
+ * (destructive wins over widening), and emits one `RecreateTableCall` per
289
+ * table. Returns unchanged-or-smaller issue list — issues the strategy
290
+ * consumed are removed so `mapNodeIssueToCall` doesn't double-handle them.
291
+ *
292
+ * The full desired/live table shapes come from `ctx.expected`/`ctx.actual`
293
+ * directly (keyed by table name — SQLite is a flat, single-namespace target)
294
+ * rather than from any individual issue, since a single drifted attribute's
295
+ * issue only carries that attribute's own node, never the whole table.
296
+ */
297
+ const recreateTableStrategy = (issues, ctx) => {
298
+ const byTable = /* @__PURE__ */ new Map();
299
+ const consumed = /* @__PURE__ */ new Set();
300
+ for (const issue of issues) {
301
+ const cls = classifyNodeIssue(issue);
302
+ if (!cls) continue;
303
+ const tableName = issue.path[1];
304
+ if (tableName === void 0) continue;
305
+ const entry = byTable.get(tableName);
306
+ if (entry) {
307
+ entry.issues.push(issue);
308
+ if (cls === "destructive") entry.hasDestructive = true;
309
+ } else byTable.set(tableName, {
310
+ issues: [issue],
311
+ hasDestructive: cls === "destructive"
312
+ });
313
+ consumed.add(issue);
314
+ }
315
+ if (byTable.size === 0) return { kind: "no_match" };
316
+ const calls = [];
317
+ for (const [tableName, entry] of byTable) {
318
+ const expectedTable = ctx.expected.tables[tableName];
319
+ const actualTable = ctx.actual.tables[tableName];
320
+ if (!expectedTable || !actualTable) continue;
321
+ const operationClass = entry.hasDestructive ? "destructive" : "widening";
322
+ const tableSpec = tableSpecFromNode(expectedTable);
323
+ const indexes = expectedTable.indexes.map((idx) => ({
324
+ name: idx.name ?? defaultIndexName(tableName, idx.columns),
325
+ columns: idx.columns
326
+ }));
327
+ calls.push(new RecreateTableCall({
328
+ tableName,
329
+ contractTable: tableSpec,
330
+ schemaColumnNames: Object.keys(actualTable.columns),
331
+ indexes,
332
+ summary: buildRecreateSummary(tableName, entry.issues),
333
+ postchecks: buildRecreatePostchecks(tableName, entry.issues, tableSpec),
334
+ operationClass
335
+ }));
336
+ }
337
+ return {
338
+ kind: "match",
339
+ issues: issues.filter((i) => !consumed.has(i)),
340
+ calls,
341
+ recipe: true
342
+ };
343
+ };
344
+ /**
345
+ * When the policy allows `'data'` and the expected tree tightens one or more
346
+ * columns from nullable to NOT NULL, emit a `DataTransformCall` stub per
347
+ * tightened column. The user fills the backfill `UPDATE` in the rendered
348
+ * `migration.ts` before the subsequent `RecreateTableCall` copies data into
349
+ * the tightened schema (whose `INSERT INTO temp SELECT … FROM old` would
350
+ * otherwise fail at runtime if any `NULL`s remain).
351
+ *
352
+ * Does NOT consume the tightening issue — `recreateTableStrategy` still
353
+ * needs it to produce the actual recreate that enforces the NOT NULL at
354
+ * the schema level. The backfill op and the recreate op end up in the
355
+ * recipe slot in strategy order (backfill first, recreate second), which
356
+ * matches the required execution order.
357
+ *
358
+ * Mirrors Postgres's `nullableTighteningCallStrategy` / `'data'`-class
359
+ * gating. When `'data'` is not in the policy (the default `db update` /
360
+ * `db init` path), the strategy short-circuits and the recreate alone
361
+ * runs with its current destructive-class gating — preserving today's
362
+ * behavior where a tightening blows up at runtime if NULLs are present.
363
+ */
364
+ const nullabilityTighteningBackfillStrategy = (issues, ctx) => {
365
+ if (!ctx.policy.allowedOperationClasses.includes("data")) return { kind: "no_match" };
366
+ const calls = [];
367
+ for (const issue of issues) {
368
+ if (issue.reason !== "not-equal" || issue.expected === void 0 || issue.actual === void 0) continue;
369
+ if (blindCast(issue.expected).nodeKind !== RelationalSchemaNodeKind.column) continue;
370
+ const expectedColumn = blindCast(issue.expected);
371
+ const actualColumn = blindCast(issue.actual);
372
+ if (expectedColumn.nullable === actualColumn.nullable) continue;
373
+ if (expectedColumn.nullable) continue;
374
+ const tableName = issue.path[1];
375
+ if (tableName === void 0) continue;
376
+ calls.push(new DataTransformCall(`data_migration.backfill-${tableName}-${expectedColumn.name}`, `Backfill NULLs in "${tableName}"."${expectedColumn.name}" before NOT NULL tightening`, tableName, expectedColumn.name));
377
+ }
378
+ if (calls.length === 0) return { kind: "no_match" };
379
+ return {
380
+ kind: "match",
381
+ issues,
382
+ calls,
383
+ recipe: true
384
+ };
385
+ };
386
+ const sqlitePlannerStrategies = [nullabilityTighteningBackfillStrategy, recreateTableStrategy];
387
+ //#endregion
388
+ //#region src/core/migrations/issue-planner.ts
389
+ /**
390
+ * Re-keys the legacy `ISSUE_KIND_ORDER` (kind string → priority number) on
391
+ * `(nodeKind, reason)`. Numbers are preserved from the legacy table so the
392
+ * dependency intent stays legible; the final emission order is actually
393
+ * fixed downstream by category bucketing (create-table → add-column →
394
+ * create-index → recreate → drop-column → drop-index → drop-table), so this
395
+ * only breaks ties within a single bucket.
396
+ */
397
+ function nodeIssueOrder(issue) {
398
+ const node = issueNode(issue);
399
+ if (node === void 0) return 99;
400
+ switch (node.nodeKind) {
401
+ case RelationalSchemaNodeKind.foreignKey: return issue.reason === "not-expected" ? 10 : 60;
402
+ case RelationalSchemaNodeKind.unique: return issue.reason === "not-expected" ? 11 : 51;
403
+ case RelationalSchemaNodeKind.primaryKey: return issue.reason === "not-expected" ? 12 : 50;
404
+ case RelationalSchemaNodeKind.index: return issue.reason === "not-expected" ? 13 : 52;
405
+ case RelationalSchemaNodeKind.columnDefault:
406
+ if (issue.reason === "not-expected") return 14;
407
+ return issue.reason === "not-found" ? 42 : 43;
408
+ case RelationalSchemaNodeKind.column:
409
+ if (issue.reason === "not-expected") return 15;
410
+ return issue.reason === "not-found" ? 30 : 40;
411
+ case RelationalSchemaNodeKind.table: return issue.reason === "not-expected" ? 16 : 20;
412
+ case RelationalSchemaNodeKind.check:
413
+ if (issue.reason === "not-found") return 53;
414
+ return issue.reason === "not-expected" ? 54 : 55;
415
+ default: return 99;
416
+ }
417
+ }
418
+ /** Deterministic tiebreak within an order bucket: the diff path itself already encodes table → child → grandchild. */
419
+ function nodeIssueKey(issue) {
420
+ return issue.path.join(" ");
421
+ }
422
+ /**
423
+ * The generic differ is total: a missing/extra table (or column) emits an
424
+ * issue for itself AND for every node in its subtree (columns, defaults,
425
+ * constraints, indexes). `CreateTableCall`/`DropTableCall` and
426
+ * `AddColumnCall`/`DropColumnCall` already account for the whole subtree
427
+ * (reading it directly off the table/column node), so the nested issues are
428
+ * redundant — coalescing them is "the planner's responsibility" the differ's
429
+ * own contract assigns (`schema-diff.ts`). Drops any issue whose path is a
430
+ * strict descendant of a `not-found`/`not-expected` issue's path.
431
+ */
432
+ function coalesceSubtreeIssues(issues) {
433
+ const collapsingPaths = issues.filter((issue) => issue.reason === "not-found" || issue.reason === "not-expected").map((issue) => issue.path);
434
+ if (collapsingPaths.length === 0) return issues;
435
+ return issues.filter((issue) => !collapsingPaths.some((ancestor) => isStrictDescendantPath(issue.path, ancestor)));
436
+ }
437
+ function isStrictDescendantPath(path, ancestor) {
438
+ if (path.length <= ancestor.length) return false;
439
+ for (let i = 0; i < ancestor.length; i += 1) if (path[i] !== ancestor[i]) return false;
440
+ return true;
441
+ }
442
+ function issueNode(issue) {
443
+ const node = issue.expected ?? issue.actual;
444
+ if (node === void 0) return void 0;
445
+ return blindCast(node);
446
+ }
447
+ /** Whether the expected/actual native type (resolved, or raw+many fallback) differs — mirrors `SqlColumnIR.isEqualTo`'s type comparison. */
448
+ function columnTypeChanged(expected, actual) {
449
+ if (expected.resolvedNativeType !== void 0 && actual.resolvedNativeType !== void 0) return expected.resolvedNativeType !== actual.resolvedNativeType;
450
+ return expected.nativeType !== actual.nativeType || Boolean(expected.many) !== Boolean(actual.many);
451
+ }
452
+ /**
453
+ * Builds the flat `SqliteTableSpec` `RecreateTableCall` needs from the
454
+ * expected table node — the node-sourced equivalent of the retired
455
+ * `toTableSpec` (which read a raw contract `StorageTable`). Every column's
456
+ * spec is resolved from its `codecRef` via `columnSpecFromNode`.
457
+ */
458
+ function tableSpecFromNode(table) {
459
+ const columns = Object.values(table.columns).map((c) => columnSpecFromNode(c, isInlineAutoincrementPrimaryKeyNode(table, c)));
460
+ const uniques = table.uniques.map((u) => ({
461
+ columns: u.columns,
462
+ ...u.name !== void 0 ? { name: u.name } : {}
463
+ }));
464
+ const foreignKeys = table.foreignKeys.map((fk) => ({
465
+ columns: fk.columns,
466
+ references: {
467
+ table: fk.referencedTable,
468
+ columns: fk.referencedColumns
469
+ },
470
+ constraint: true,
471
+ ...fk.name !== void 0 ? { name: fk.name } : {},
472
+ ...fk.onDelete !== void 0 ? { onDelete: fk.onDelete } : {},
473
+ ...fk.onUpdate !== void 0 ? { onUpdate: fk.onUpdate } : {}
474
+ }));
475
+ return {
476
+ columns,
477
+ ...table.primaryKey ? { primaryKey: { columns: table.primaryKey.columns } } : {},
478
+ uniques,
479
+ foreignKeys
480
+ };
481
+ }
482
+ function issueConflict(kind, summary, location) {
483
+ return {
484
+ kind,
485
+ summary,
486
+ why: "Use `migration new` to author a custom migration for this change.",
487
+ ...location ? { location } : {}
488
+ };
489
+ }
490
+ function issueLocation(tableName, columnName) {
491
+ return columnName !== void 0 ? {
492
+ table: tableName,
493
+ column: columnName
494
+ } : { table: tableName };
495
+ }
496
+ /**
497
+ * Conflict kind for a node kind that `recreateTableStrategy` absorbs for
498
+ * every reachable production issue. Reaching `mapNodeIssueToCall` for one of
499
+ * these means the recreate strategy didn't run — mirrors the legacy
500
+ * `conflictKindForIssue` per-kind categorization.
501
+ */
502
+ function absorbedConflictKind(nodeKind) {
503
+ switch (nodeKind) {
504
+ case RelationalSchemaNodeKind.primaryKey:
505
+ case RelationalSchemaNodeKind.unique: return "indexIncompatible";
506
+ case RelationalSchemaNodeKind.foreignKey: return "foreignKeyConflict";
507
+ default: return "missingButNonAdditive";
508
+ }
509
+ }
510
+ /**
511
+ * Builds the `CreateTableCall` + per-index `CreateIndexCall`s for a
512
+ * newly-expected table. Reads only the table node's own children — indexes
513
+ * (declared + FK-backing, deduped) are already merged and ordered at
514
+ * derivation (`contractToSchemaIR`'s `convertTable`).
515
+ */
516
+ function buildCreateTableCalls(table) {
517
+ const columns = Object.values(table.columns).map((c) => ddlColumnFromNode(c, isInlineAutoincrementPrimaryKeyNode(table, c)));
518
+ const constraints = tableConstraintsFromNode(table, Object.values(table.columns).some((c) => isInlineAutoincrementPrimaryKeyNode(table, c)));
519
+ const calls = [new CreateTableCall(table.name, columns, constraints.length > 0 ? constraints : void 0)];
520
+ for (const index of table.indexes) {
521
+ const indexName = index.name ?? defaultIndexName(table.name, index.columns);
522
+ calls.push(new CreateIndexCall(table.name, indexName, index.columns));
523
+ }
524
+ return calls;
525
+ }
526
+ function mapTableIssue(issue) {
527
+ if (issue.reason === "not-found") return ok(buildCreateTableCalls(blindCast(issue.expected)));
528
+ if (issue.reason === "not-expected") {
529
+ const table = blindCast(issue.actual);
530
+ if (CONTROL_TABLE_NAMES.has(table.name)) return ok([]);
531
+ return ok([new DropTableCall(table.name)]);
532
+ }
533
+ return notOk(issueConflict("unsupportedOperation", `Unexpected table drift: ${issue.message}`));
534
+ }
535
+ function mapColumnIssue(issue, ctx) {
536
+ const tableName = issue.path[1];
537
+ if (tableName === void 0) return notOk(issueConflict("unsupportedOperation", `Column issue has no table in its path: ${issue.message}`));
538
+ if (issue.reason === "not-found") {
539
+ const column = blindCast(issue.expected);
540
+ const table = ctx.expected.tables[tableName];
541
+ return ok([new AddColumnCall(tableName, columnSpecFromNode(column, table !== void 0 && isInlineAutoincrementPrimaryKeyNode(table, column)))]);
542
+ }
543
+ if (issue.reason === "not-expected") return ok([new DropColumnCall(tableName, blindCast(issue.actual).name)]);
544
+ const expected = blindCast(issue.expected);
545
+ return notOk(issueConflict(columnTypeChanged(expected, blindCast(issue.actual)) ? "typeMismatch" : "nullabilityConflict", issue.message, issueLocation(tableName, expected.name)));
546
+ }
547
+ function mapIndexIssue(issue) {
548
+ const tableName = issue.path[1];
549
+ if (tableName === void 0) return notOk(issueConflict("unsupportedOperation", `Index issue has no table in its path: ${issue.message}`));
550
+ if (issue.reason === "not-found") {
551
+ const index = blindCast(issue.expected);
552
+ return ok([new CreateIndexCall(tableName, index.name ?? defaultIndexName(tableName, index.columns), index.columns)]);
553
+ }
554
+ if (issue.reason === "not-expected") {
555
+ const index = blindCast(issue.actual);
556
+ return ok([new DropIndexCall(tableName, index.name ?? defaultIndexName(tableName, index.columns))]);
557
+ }
558
+ return notOk(issueConflict("indexIncompatible", issue.message, issueLocation(tableName)));
559
+ }
560
+ function mapNodeIssueToCall(issue, ctx) {
561
+ const node = issueNode(issue);
562
+ if (node === void 0) return notOk(issueConflict("unsupportedOperation", `Issue carries neither an expected nor an actual node: ${issue.message}`));
563
+ switch (node.nodeKind) {
564
+ case RelationalSchemaNodeKind.table: return mapTableIssue(issue);
565
+ case RelationalSchemaNodeKind.column: return mapColumnIssue(issue, ctx);
566
+ case RelationalSchemaNodeKind.index: return mapIndexIssue(issue);
567
+ case RelationalSchemaNodeKind.columnDefault:
568
+ case RelationalSchemaNodeKind.primaryKey:
569
+ case RelationalSchemaNodeKind.foreignKey:
570
+ case RelationalSchemaNodeKind.unique: return notOk(issueConflict(absorbedConflictKind(node.nodeKind), issue.message));
571
+ case RelationalSchemaNodeKind.check: return notOk(issueConflict("unsupportedOperation", `SQLite does not support CHECK constraint DDL: ${issue.message}`));
572
+ default: return notOk(issueConflict("unsupportedOperation", `Unhandled node kind: ${node.nodeKind}`));
573
+ }
574
+ }
575
+ function classifyCall(call) {
576
+ switch (call.factoryName) {
577
+ case "createTable": return "create-table";
578
+ case "addColumn": return "add-column";
579
+ case "createIndex": return "create-index";
580
+ case "dropColumn": return "drop-column";
581
+ case "dropIndex": return "drop-index";
582
+ case "dropTable": return "drop-table";
583
+ case "recreateTable": return null;
584
+ default: return null;
585
+ }
586
+ }
587
+ const DEFAULT_POLICY = { allowedOperationClasses: [
588
+ "additive",
589
+ "widening",
590
+ "destructive",
591
+ "data"
592
+ ] };
593
+ function planIssues(options) {
594
+ const policyProvided = options.policy !== void 0;
595
+ const policy = options.policy ?? DEFAULT_POLICY;
596
+ const frameworkComponents = options.frameworkComponents ?? [];
597
+ const context = {
598
+ expected: options.expected ?? emptySchemaIR(),
599
+ actual: options.actual ?? emptySchemaIR(),
600
+ policy,
601
+ frameworkComponents
602
+ };
603
+ const strategies = options.strategies ?? sqlitePlannerStrategies;
604
+ let remaining = options.issues;
605
+ const recipeCalls = [];
606
+ const bucketableCalls = [];
607
+ for (const strategy of strategies) {
608
+ const result = strategy(remaining, context);
609
+ if (result.kind === "match") {
610
+ remaining = result.issues;
611
+ if (result.recipe) recipeCalls.push(...result.calls);
612
+ else bucketableCalls.push(...result.calls);
613
+ }
614
+ }
615
+ const sorted = [...remaining].sort((a, b) => {
616
+ const kindDelta = nodeIssueOrder(a) - nodeIssueOrder(b);
617
+ if (kindDelta !== 0) return kindDelta;
618
+ const keyA = nodeIssueKey(a);
619
+ const keyB = nodeIssueKey(b);
620
+ return keyA < keyB ? -1 : keyA > keyB ? 1 : 0;
621
+ });
622
+ const defaultCalls = [];
623
+ const conflicts = [];
624
+ for (const issue of sorted) {
625
+ const result = mapNodeIssueToCall(issue, context);
626
+ if (result.ok) defaultCalls.push(...result.value);
627
+ else conflicts.push(result.failure);
628
+ }
629
+ const allowed = policy.allowedOperationClasses;
630
+ let gatedRecipe = recipeCalls;
631
+ let gatedBucketable = bucketableCalls;
632
+ let gatedDefault = defaultCalls;
633
+ if (policyProvided) {
634
+ const sink = (acc) => (call) => {
635
+ if (allowed.includes(call.operationClass)) {
636
+ acc.push(call);
637
+ return;
638
+ }
639
+ conflicts.push(conflictForDisallowedCall(call, allowed));
640
+ };
641
+ const gatedRecipeBucket = [];
642
+ const gatedBucketableBucket = [];
643
+ const gatedDefaultBucket = [];
644
+ recipeCalls.forEach(sink(gatedRecipeBucket));
645
+ bucketableCalls.forEach(sink(gatedBucketableBucket));
646
+ defaultCalls.forEach(sink(gatedDefaultBucket));
647
+ gatedRecipe = gatedRecipeBucket;
648
+ gatedBucketable = gatedBucketableBucket;
649
+ gatedDefault = gatedDefaultBucket;
650
+ }
651
+ if (conflicts.length > 0) return notOk(conflicts);
652
+ const combined = [...gatedDefault, ...gatedBucketable];
653
+ const byCategory = (cat) => combined.filter((c) => classifyCall(c) === cat);
654
+ return ok({ calls: [
655
+ ...byCategory("create-table"),
656
+ ...byCategory("add-column"),
657
+ ...byCategory("create-index"),
658
+ ...gatedRecipe,
659
+ ...byCategory("drop-column"),
660
+ ...byCategory("drop-index"),
661
+ ...byCategory("drop-table")
662
+ ] });
663
+ }
664
+ function emptySchemaIR() {
665
+ return new SqlSchemaIR({ tables: {} });
666
+ }
667
+ function conflictForDisallowedCall(call, allowed) {
668
+ const summary = `Operation "${call.label}" requires class "${call.operationClass}", but policy allows only: ${allowed.join(", ")}`;
669
+ const location = locationForCall(call);
670
+ return {
671
+ kind: conflictKindForCall(call),
672
+ summary,
673
+ why: "Use `migration new` to author a custom migration for this change.",
674
+ ...location ? { location } : {}
675
+ };
676
+ }
677
+ function conflictKindForCall(call) {
678
+ switch (call.factoryName) {
679
+ case "createIndex":
680
+ case "dropIndex": return "indexIncompatible";
681
+ default: return "missingButNonAdditive";
682
+ }
683
+ }
684
+ function locationForCall(call) {
685
+ const location = {};
686
+ if ("tableName" in call) location.table = call.tableName;
687
+ if ("columnName" in call) location.column = call.columnName;
688
+ if ("indexName" in call) location.index = call.indexName;
689
+ return Object.keys(location).length > 0 ? location : void 0;
690
+ }
691
+ //#endregion
692
+ //#region src/core/migrations/planner.ts
693
+ function createSqliteMigrationPlanner(lowerer) {
694
+ return new SqliteMigrationPlanner(lowerer);
695
+ }
696
+ /**
697
+ * SQLite migration planner — a thin wrapper over `planIssues`.
698
+ *
699
+ * `plan()` diffs the live schema against the target contract via the one
700
+ * differ (`buildSqlitePlanDiff`, producing node-typed `SchemaDiffIssue[]`)
701
+ * and delegates to `planIssues` with the registered strategies. Strategies
702
+ * absorb groups of related issues into composite recipes (e.g. recreating a
703
+ * table to apply type/nullability/default/constraint changes at once);
704
+ * anything not absorbed by a strategy flows through `mapNodeIssueToCall` in
705
+ * the issue planner as a one-off call.
706
+ *
707
+ * FK-backing indexes are already merged into the expected table node's
708
+ * `indexes` at derivation (`contractToSchemaIR`'s `convertTable`), so
709
+ * `mapNodeIssueToCall` handles them uniformly alongside user-declared
710
+ * indexes — no separate expansion step in the planner.
711
+ */
712
+ var SqliteMigrationPlanner = class {
713
+ #lowerer;
714
+ constructor(lowerer) {
715
+ this.#lowerer = lowerer;
716
+ }
717
+ plan(options) {
718
+ return this.planSql(options);
719
+ }
720
+ emptyMigration(context, spaceId) {
721
+ return new TypeScriptRenderableSqliteMigration([], {
722
+ from: context.fromHash,
723
+ to: context.toHash
724
+ }, spaceId, void 0, this.#lowerer);
725
+ }
726
+ planSql(options) {
727
+ const policyResult = this.ensureAdditivePolicy(options.policy);
728
+ if (policyResult) return policyResult;
729
+ const { expected, actual, issues } = this.collectSchemaIssues(options);
730
+ const codecHooks = extractCodecControlHooks(options.frameworkComponents);
731
+ const result = planIssues({
732
+ issues,
733
+ expected,
734
+ actual,
735
+ policy: options.policy,
736
+ frameworkComponents: options.frameworkComponents,
737
+ strategies: sqlitePlannerStrategies
738
+ });
739
+ if (!result.ok) return plannerFailure(result.failure);
740
+ const fieldEventOps = planFieldEventOperations({
741
+ priorContract: options.fromContract,
742
+ newContract: options.contract,
743
+ codecHooks
744
+ });
745
+ const calls = [...result.value.calls, ...fieldEventOps];
746
+ const destination = {
747
+ storageHash: options.contract.storage.storageHash,
748
+ ...options.contract.profileHash !== void 0 ? { profileHash: options.contract.profileHash } : {}
749
+ };
750
+ return {
751
+ kind: "success",
752
+ plan: new TypeScriptRenderableSqliteMigration(calls, {
753
+ from: options.fromContract?.storage.storageHash ?? null,
754
+ to: options.contract.storage.storageHash
755
+ }, options.spaceId, destination, this.#lowerer)
756
+ };
757
+ }
758
+ ensureAdditivePolicy(policy) {
759
+ if (!policy.allowedOperationClasses.includes("additive")) return plannerFailure([{
760
+ kind: "unsupportedOperation",
761
+ summary: "Migration planner requires additive operations be allowed",
762
+ why: "The planner requires the \"additive\" operation class to be allowed in the policy."
763
+ }]);
764
+ return null;
765
+ }
766
+ /**
767
+ * Diffs the target contract against the live schema via the one differ
768
+ * (the same tree-building `diffSqliteSchema` uses, plus the
769
+ * op-render stamper) and prepares the issue list `planIssues` consumes.
770
+ *
771
+ * Three passes, in order:
772
+ * 1. Subtree coalescing — the differ is total (a missing/extra table also
773
+ * emits an issue for every column/constraint under it); those nested
774
+ * issues are redundant once the table-level `CreateTable`/`DropTable`
775
+ * call already accounts for the whole subtree. Runs FIRST, over the
776
+ * complete diff: a sibling-owned extra table's column issues must
777
+ * collapse into its one table-level issue before the ownership pass,
778
+ * because a bare column node carries no table reference — if coalescing
779
+ * ran after ownership dropped the table-level issue, the orphaned column
780
+ * issues would survive and the planner would emit drops against a
781
+ * sibling space's table.
782
+ * 2. Ownership — a live extra is only this plan's to drop when no contract
783
+ * space owns it. The differ ran against THIS space's contract, so a
784
+ * table a sibling owns surfaces here as `not-expected`; the planner asks
785
+ * the ownership oracle (the passive aggregate) whether any space declares
786
+ * it and, if so, leaves it alone. A table no space owns stays a genuine
787
+ * extra. Ownership lives in the aggregate; the planner only asks. No
788
+ * oracle (single-space) keeps every extra.
789
+ * 3. Strict-mode extras gating — `not-expected` (extra table/column/
790
+ * constraint) issues are dropped entirely outside strict mode, mirroring
791
+ * the retired coordinate walk's `if (strict) { ...extra_* } }` guards:
792
+ * an additive-only plan must never even consider dropping an unclaimed
793
+ * object, not just refuse to emit the drop.
794
+ */
795
+ collectSchemaIssues(options) {
796
+ const allowed = options.policy.allowedOperationClasses;
797
+ const strict = allowed.includes("widening") || allowed.includes("destructive");
798
+ const { expected, actual, issues: rawIssues } = buildSqlitePlanDiff({
799
+ contract: options.contract,
800
+ actualSchema: options.schema,
801
+ frameworkComponents: options.frameworkComponents
802
+ });
803
+ const owned = retainUnownedExtras(coalesceSubtreeIssues(rawIssues), options.ownership);
804
+ return {
805
+ expected,
806
+ actual,
807
+ issues: strict ? owned : owned.filter((issue) => issue.reason !== "not-expected")
808
+ };
809
+ }
810
+ };
811
+ /**
812
+ * Drops a `not-expected` issue when it is a whole extra TABLE that some
813
+ * contract space owns, asking the ownership oracle per node.
814
+ *
815
+ * The consultation applies ONLY to table-level extras — a live table this
816
+ * space's contract lacks — identified by asking the issue's own node
817
+ * (`nodeKind === table`), never by counting path segments. SQLite is a
818
+ * single-namespace target, so every coordinate is qualified with the shared
819
+ * `UNBOUND_NAMESPACE_ID` sentinel (the same one the aggregate's declared
820
+ * entities carry for this target). `entityKind` is the literal `'table'`:
821
+ * this function only ever asks about a node already confirmed to be
822
+ * `RelationalSchemaNodeKind.table` (checked just above), and that's a
823
+ * diff-tree `nodeKind` spelling (`'sql-table'`) distinct from the storage
824
+ * `entries` vocabulary `elementCoordinates` walks (`'table'`) — the literal
825
+ * is that storage-entries spelling. `declaresEntity` answers over the whole
826
+ * composition (self included), so a positive answer on such a table means
827
+ * another space owns it (a table this space owned would be in its expected
828
+ * tree, never an extra): leave it. A negative answer means no space owns
829
+ * it — a genuine orphan to drop.
830
+ *
831
+ * A DEEPER extra (an extra column/constraint on a table this space DOES own —
832
+ * only the child drifted, so the table is in the expected tree) is this
833
+ * space's own drift and is always kept for dropping; asking the oracle there
834
+ * would wrongly suppress it, because the owned table answers `true`. No oracle
835
+ * ⇒ every extra is kept (single-space plan).
836
+ */
837
+ function retainUnownedExtras(issues, ownership) {
838
+ if (ownership === void 0) return issues;
839
+ return issues.filter((issue) => {
840
+ if (issue.reason !== "not-expected") return true;
841
+ const node = issueNode(issue);
842
+ if (node === void 0 || node.nodeKind !== RelationalSchemaNodeKind.table) return true;
843
+ const tableName = issue.path[1];
844
+ return tableName === void 0 || !ownership.declaresEntity({
845
+ namespaceId: UNBOUND_NAMESPACE_ID,
846
+ entityKind: "table",
847
+ entityName: tableName
848
+ });
849
+ });
850
+ }
851
+ //#endregion
852
+ export { verifySqliteDatabaseSchema as a, sqliteContractToSchema as i, createSqliteMigrationPlanner as n, diffSqliteSchema as r, SqliteMigrationPlanner as t };
853
+
854
+ //# sourceMappingURL=planner-DhvFrgPT.mjs.map