@prisma-next/migration-tools 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.
Files changed (130) hide show
  1. package/README.md +34 -22
  2. package/dist/{constants-BRi0X7B_.mjs → constants-DWV9_o2Z.mjs} +2 -2
  3. package/dist/{constants-BRi0X7B_.mjs.map → constants-DWV9_o2Z.mjs.map} +1 -1
  4. package/dist/errors-EPL_9p9f.mjs +297 -0
  5. package/dist/errors-EPL_9p9f.mjs.map +1 -0
  6. package/dist/exports/aggregate.d.mts +599 -0
  7. package/dist/exports/aggregate.d.mts.map +1 -0
  8. package/dist/exports/aggregate.mjs +599 -0
  9. package/dist/exports/aggregate.mjs.map +1 -0
  10. package/dist/exports/constants.d.mts.map +1 -1
  11. package/dist/exports/constants.mjs +2 -3
  12. package/dist/exports/errors.d.mts +68 -0
  13. package/dist/exports/errors.d.mts.map +1 -0
  14. package/dist/exports/errors.mjs +2 -0
  15. package/dist/exports/graph.d.mts +2 -0
  16. package/dist/exports/graph.mjs +1 -0
  17. package/dist/exports/hash.d.mts +52 -0
  18. package/dist/exports/hash.d.mts.map +1 -0
  19. package/dist/exports/hash.mjs +2 -0
  20. package/dist/exports/invariants.d.mts +39 -0
  21. package/dist/exports/invariants.d.mts.map +1 -0
  22. package/dist/exports/invariants.mjs +2 -0
  23. package/dist/exports/io.d.mts +66 -6
  24. package/dist/exports/io.d.mts.map +1 -1
  25. package/dist/exports/io.mjs +2 -3
  26. package/dist/exports/metadata.d.mts +2 -0
  27. package/dist/exports/metadata.mjs +1 -0
  28. package/dist/exports/migration-graph.d.mts +2 -0
  29. package/dist/exports/migration-graph.mjs +2 -0
  30. package/dist/exports/migration-ts.d.mts.map +1 -1
  31. package/dist/exports/migration-ts.mjs +2 -4
  32. package/dist/exports/migration-ts.mjs.map +1 -1
  33. package/dist/exports/migration.d.mts +15 -14
  34. package/dist/exports/migration.d.mts.map +1 -1
  35. package/dist/exports/migration.mjs +70 -43
  36. package/dist/exports/migration.mjs.map +1 -1
  37. package/dist/exports/package.d.mts +3 -0
  38. package/dist/exports/package.mjs +1 -0
  39. package/dist/exports/refs.d.mts.map +1 -1
  40. package/dist/exports/refs.mjs +3 -4
  41. package/dist/exports/refs.mjs.map +1 -1
  42. package/dist/exports/spaces.d.mts +526 -0
  43. package/dist/exports/spaces.d.mts.map +1 -0
  44. package/dist/exports/spaces.mjs +266 -0
  45. package/dist/exports/spaces.mjs.map +1 -0
  46. package/dist/graph-HMWAldoR.d.mts +28 -0
  47. package/dist/graph-HMWAldoR.d.mts.map +1 -0
  48. package/dist/hash-By50zM_E.mjs +74 -0
  49. package/dist/hash-By50zM_E.mjs.map +1 -0
  50. package/dist/invariants-qgQGlsrV.mjs +57 -0
  51. package/dist/invariants-qgQGlsrV.mjs.map +1 -0
  52. package/dist/io-D5YYptRO.mjs +239 -0
  53. package/dist/io-D5YYptRO.mjs.map +1 -0
  54. package/dist/metadata-CFvm3ayn.d.mts +2 -0
  55. package/dist/migration-graph-DGNnKDY5.mjs +523 -0
  56. package/dist/migration-graph-DGNnKDY5.mjs.map +1 -0
  57. package/dist/migration-graph-DulOITvG.d.mts +124 -0
  58. package/dist/migration-graph-DulOITvG.d.mts.map +1 -0
  59. package/dist/op-schema-D5qkXfEf.mjs +13 -0
  60. package/dist/op-schema-D5qkXfEf.mjs.map +1 -0
  61. package/dist/package-BjiZ7KDy.d.mts +21 -0
  62. package/dist/package-BjiZ7KDy.d.mts.map +1 -0
  63. package/dist/read-contract-space-contract-Cme8KZk_.mjs +259 -0
  64. package/dist/read-contract-space-contract-Cme8KZk_.mjs.map +1 -0
  65. package/package.json +42 -17
  66. package/src/aggregate/loader.ts +379 -0
  67. package/src/aggregate/marker-types.ts +16 -0
  68. package/src/aggregate/planner-types.ts +171 -0
  69. package/src/aggregate/planner.ts +159 -0
  70. package/src/aggregate/project-schema-to-space.ts +64 -0
  71. package/src/aggregate/strategies/graph-walk.ts +118 -0
  72. package/src/aggregate/strategies/synth.ts +122 -0
  73. package/src/aggregate/types.ts +89 -0
  74. package/src/aggregate/verifier.ts +230 -0
  75. package/src/assert-descriptor-self-consistency.ts +70 -0
  76. package/src/compute-extension-space-apply-path.ts +152 -0
  77. package/src/concatenate-space-apply-inputs.ts +90 -0
  78. package/src/contract-space-from-json.ts +63 -0
  79. package/src/emit-contract-space-artefacts.ts +70 -0
  80. package/src/errors.ts +251 -17
  81. package/src/exports/aggregate.ts +42 -0
  82. package/src/exports/errors.ts +8 -0
  83. package/src/exports/graph.ts +1 -0
  84. package/src/exports/hash.ts +2 -0
  85. package/src/exports/invariants.ts +1 -0
  86. package/src/exports/io.ts +3 -1
  87. package/src/exports/metadata.ts +1 -0
  88. package/src/exports/{dag.ts → migration-graph.ts} +3 -2
  89. package/src/exports/migration.ts +0 -1
  90. package/src/exports/package.ts +2 -0
  91. package/src/exports/spaces.ts +45 -0
  92. package/src/gather-disk-contract-space-state.ts +62 -0
  93. package/src/graph-ops.ts +57 -30
  94. package/src/graph.ts +25 -0
  95. package/src/hash.ts +91 -0
  96. package/src/invariants.ts +61 -0
  97. package/src/io.ts +163 -40
  98. package/src/metadata.ts +1 -0
  99. package/src/migration-base.ts +97 -56
  100. package/src/migration-graph.ts +676 -0
  101. package/src/op-schema.ts +11 -0
  102. package/src/package.ts +21 -0
  103. package/src/plan-all-spaces.ts +76 -0
  104. package/src/read-contract-space-contract.ts +44 -0
  105. package/src/read-contract-space-head-ref.ts +63 -0
  106. package/src/space-layout.ts +48 -0
  107. package/src/verify-contract-spaces.ts +272 -0
  108. package/dist/attestation-BnzTb0Qp.mjs +0 -65
  109. package/dist/attestation-BnzTb0Qp.mjs.map +0 -1
  110. package/dist/errors-BmiSgz1j.mjs +0 -160
  111. package/dist/errors-BmiSgz1j.mjs.map +0 -1
  112. package/dist/exports/attestation.d.mts +0 -37
  113. package/dist/exports/attestation.d.mts.map +0 -1
  114. package/dist/exports/attestation.mjs +0 -4
  115. package/dist/exports/dag.d.mts +0 -51
  116. package/dist/exports/dag.d.mts.map +0 -1
  117. package/dist/exports/dag.mjs +0 -386
  118. package/dist/exports/dag.mjs.map +0 -1
  119. package/dist/exports/types.d.mts +0 -35
  120. package/dist/exports/types.d.mts.map +0 -1
  121. package/dist/exports/types.mjs +0 -3
  122. package/dist/io-Cd6GLyjK.mjs +0 -153
  123. package/dist/io-Cd6GLyjK.mjs.map +0 -1
  124. package/dist/types-DyGXcWWp.d.mts +0 -71
  125. package/dist/types-DyGXcWWp.d.mts.map +0 -1
  126. package/src/attestation.ts +0 -81
  127. package/src/dag.ts +0 -426
  128. package/src/exports/attestation.ts +0 -2
  129. package/src/exports/types.ts +0 -10
  130. package/src/types.ts +0 -66
@@ -0,0 +1,523 @@
1
+ import { S as errorNoTarget, b as errorNoInitialMigration, n as errorAmbiguousTarget, o as errorDuplicateMigrationHash, w as errorSameSourceAndTarget } from "./errors-EPL_9p9f.mjs";
2
+ import { t as EMPTY_CONTRACT_HASH } from "./constants-DWV9_o2Z.mjs";
3
+ import { ifDefined } from "@prisma-next/utils/defined";
4
+ //#region src/queue.ts
5
+ /**
6
+ * FIFO queue with amortised O(1) push and shift.
7
+ *
8
+ * Uses a head-index cursor over a backing array rather than
9
+ * `Array.prototype.shift()`, which is O(n) on V8. Intended for BFS-shaped
10
+ * traversals where the queue is drained in a single pass — it does not
11
+ * reclaim memory for already-shifted items, so it is not suitable for
12
+ * long-lived queues with many push/shift cycles.
13
+ */
14
+ var Queue = class {
15
+ items;
16
+ head = 0;
17
+ constructor(initial = []) {
18
+ this.items = [...initial];
19
+ }
20
+ push(item) {
21
+ this.items.push(item);
22
+ }
23
+ /**
24
+ * Remove and return the next item. Caller must check `isEmpty` first —
25
+ * shifting an empty queue throws.
26
+ */
27
+ shift() {
28
+ if (this.head >= this.items.length) throw new Error("Queue.shift called on empty queue");
29
+ return this.items[this.head++];
30
+ }
31
+ get isEmpty() {
32
+ return this.head >= this.items.length;
33
+ }
34
+ };
35
+ //#endregion
36
+ //#region src/graph-ops.ts
37
+ function* bfs(starts, neighbours, key = (state) => state) {
38
+ const visited = /* @__PURE__ */ new Set();
39
+ const parentMap = /* @__PURE__ */ new Map();
40
+ const queue = new Queue();
41
+ for (const start of starts) {
42
+ const k = key(start);
43
+ if (!visited.has(k)) {
44
+ visited.add(k);
45
+ queue.push({
46
+ state: start,
47
+ key: k
48
+ });
49
+ }
50
+ }
51
+ while (!queue.isEmpty) {
52
+ const { state: current, key: curKey } = queue.shift();
53
+ const parentInfo = parentMap.get(curKey);
54
+ yield {
55
+ state: current,
56
+ parent: parentInfo?.parent ?? null,
57
+ incomingEdge: parentInfo?.edge ?? null
58
+ };
59
+ for (const { next, edge } of neighbours(current)) {
60
+ const k = key(next);
61
+ if (!visited.has(k)) {
62
+ visited.add(k);
63
+ parentMap.set(k, {
64
+ parent: current,
65
+ edge
66
+ });
67
+ queue.push({
68
+ state: next,
69
+ key: k
70
+ });
71
+ }
72
+ }
73
+ }
74
+ }
75
+ //#endregion
76
+ //#region src/migration-graph.ts
77
+ /** Forward-edge neighbours: edge `e` from `n` visits `e.to` next. */
78
+ function forwardNeighbours(graph, node) {
79
+ return (graph.forwardChain.get(node) ?? []).map((edge) => ({
80
+ next: edge.to,
81
+ edge
82
+ }));
83
+ }
84
+ /**
85
+ * Forward-edge neighbours, sorted by the deterministic tie-break.
86
+ * Used by path-finding so the resulting shortest path is stable across runs.
87
+ */
88
+ function sortedForwardNeighbours(graph, node) {
89
+ return [...graph.forwardChain.get(node) ?? []].sort(compareTieBreak).map((edge) => ({
90
+ next: edge.to,
91
+ edge
92
+ }));
93
+ }
94
+ /** Reverse-edge neighbours: edge `e` from `n` visits `e.from` next. */
95
+ function reverseNeighbours(graph, node) {
96
+ return (graph.reverseChain.get(node) ?? []).map((edge) => ({
97
+ next: edge.from,
98
+ edge
99
+ }));
100
+ }
101
+ function appendEdge(map, key, entry) {
102
+ const bucket = map.get(key);
103
+ if (bucket) bucket.push(entry);
104
+ else map.set(key, [entry]);
105
+ }
106
+ function reconstructGraph(packages) {
107
+ const nodes = /* @__PURE__ */ new Set();
108
+ const forwardChain = /* @__PURE__ */ new Map();
109
+ const reverseChain = /* @__PURE__ */ new Map();
110
+ const migrationByHash = /* @__PURE__ */ new Map();
111
+ for (const pkg of packages) {
112
+ const from = pkg.metadata.from ?? "sha256:empty";
113
+ const { to } = pkg.metadata;
114
+ if (from === to) {
115
+ if (!pkg.ops.some((op) => op.operationClass === "data")) throw errorSameSourceAndTarget(pkg.dirPath, from);
116
+ }
117
+ nodes.add(from);
118
+ nodes.add(to);
119
+ const migration = {
120
+ from,
121
+ to,
122
+ migrationHash: pkg.metadata.migrationHash,
123
+ dirName: pkg.dirName,
124
+ createdAt: pkg.metadata.createdAt,
125
+ labels: pkg.metadata.labels,
126
+ invariants: pkg.metadata.providedInvariants
127
+ };
128
+ if (migrationByHash.has(migration.migrationHash)) throw errorDuplicateMigrationHash(migration.migrationHash);
129
+ migrationByHash.set(migration.migrationHash, migration);
130
+ appendEdge(forwardChain, from, migration);
131
+ appendEdge(reverseChain, to, migration);
132
+ }
133
+ return {
134
+ nodes,
135
+ forwardChain,
136
+ reverseChain,
137
+ migrationByHash
138
+ };
139
+ }
140
+ const LABEL_PRIORITY = {
141
+ main: 0,
142
+ default: 1,
143
+ feature: 2
144
+ };
145
+ function labelPriority(labels) {
146
+ let best = 3;
147
+ for (const l of labels) {
148
+ const p = LABEL_PRIORITY[l];
149
+ if (p !== void 0 && p < best) best = p;
150
+ }
151
+ return best;
152
+ }
153
+ function compareTieBreak(a, b) {
154
+ const lp = labelPriority(a.labels) - labelPriority(b.labels);
155
+ if (lp !== 0) return lp;
156
+ const ca = a.createdAt.localeCompare(b.createdAt);
157
+ if (ca !== 0) return ca;
158
+ const tc = a.to.localeCompare(b.to);
159
+ if (tc !== 0) return tc;
160
+ return a.migrationHash.localeCompare(b.migrationHash);
161
+ }
162
+ function sortedNeighbors(edges) {
163
+ return [...edges].sort(compareTieBreak);
164
+ }
165
+ /**
166
+ * Find the shortest path from `fromHash` to `toHash` using BFS over the
167
+ * contract-hash graph. Returns the ordered list of edges, or null if no path
168
+ * exists. Returns an empty array when `fromHash === toHash` (no-op).
169
+ *
170
+ * Neighbor ordering is deterministic via the tie-break sort key:
171
+ * label priority → createdAt → to → migrationHash.
172
+ */
173
+ function findPath(graph, fromHash, toHash) {
174
+ if (fromHash === toHash) return [];
175
+ const parents = /* @__PURE__ */ new Map();
176
+ for (const step of bfs([fromHash], (n) => sortedForwardNeighbours(graph, n))) {
177
+ if (step.parent !== null && step.incomingEdge !== null) parents.set(step.state, {
178
+ parent: step.parent,
179
+ edge: step.incomingEdge
180
+ });
181
+ if (step.state === toHash) {
182
+ const path = [];
183
+ let cur = toHash;
184
+ let p = parents.get(cur);
185
+ while (p) {
186
+ path.push(p.edge);
187
+ cur = p.parent;
188
+ p = parents.get(cur);
189
+ }
190
+ path.reverse();
191
+ return path;
192
+ }
193
+ }
194
+ return null;
195
+ }
196
+ /**
197
+ * Find the shortest path from `fromHash` to `toHash` whose edges collectively
198
+ * cover every invariant in `required`. Returns `null` when no such path exists
199
+ * (either `fromHash`→`toHash` is structurally unreachable, or every reachable
200
+ * path leaves at least one required invariant uncovered). When `required` is
201
+ * empty, delegates to `findPath` so the result is byte-identical for that case.
202
+ *
203
+ * Algorithm: BFS over `(node, coveredSubset)` states with state-level dedup.
204
+ * The covered subset is a `Set<string>` of invariant ids; the state's dedup
205
+ * key is `${node}\0${[...covered].sort().join('\0')}`. State keys distinguish
206
+ * distinct `(node, covered)` tuples regardless of node-name length because
207
+ * `\0` cannot appear in any invariant id (validation rejects whitespace and
208
+ * control chars at authoring time).
209
+ *
210
+ * Neighbour ordering when `required ≠ ∅`: edges covering ≥1 still-needed
211
+ * invariant come first, with `labelPriority → createdAt → to → migrationHash`
212
+ * as the secondary key. The heuristic steers BFS toward the satisfying path;
213
+ * correctness (shortest, deterministic) does not depend on it.
214
+ */
215
+ function findPathWithInvariants(graph, fromHash, toHash, required) {
216
+ if (required.size === 0) return findPath(graph, fromHash, toHash);
217
+ const stateKey = (s) => {
218
+ if (s.covered.size === 0) return `${s.node}\0`;
219
+ return `${s.node}\0${[...s.covered].sort().join("\0")}`;
220
+ };
221
+ const neighbours = (s) => {
222
+ const outgoing = graph.forwardChain.get(s.node) ?? [];
223
+ if (outgoing.length === 0) return [];
224
+ return [...outgoing].map((edge) => {
225
+ let useful = false;
226
+ let next = null;
227
+ for (const inv of edge.invariants) if (required.has(inv) && !s.covered.has(inv)) {
228
+ if (next === null) next = new Set(s.covered);
229
+ next.add(inv);
230
+ useful = true;
231
+ }
232
+ return {
233
+ edge,
234
+ useful,
235
+ nextCovered: next ?? s.covered
236
+ };
237
+ }).sort((a, b) => {
238
+ if (a.useful !== b.useful) return a.useful ? -1 : 1;
239
+ return compareTieBreak(a.edge, b.edge);
240
+ }).map(({ edge, nextCovered }) => ({
241
+ next: {
242
+ node: edge.to,
243
+ covered: nextCovered
244
+ },
245
+ edge
246
+ }));
247
+ };
248
+ const parents = /* @__PURE__ */ new Map();
249
+ for (const step of bfs([{
250
+ node: fromHash,
251
+ covered: /* @__PURE__ */ new Set()
252
+ }], neighbours, stateKey)) {
253
+ const curKey = stateKey(step.state);
254
+ if (step.parent !== null && step.incomingEdge !== null) parents.set(curKey, {
255
+ parentKey: stateKey(step.parent),
256
+ edge: step.incomingEdge
257
+ });
258
+ if (step.state.node === toHash && step.state.covered.size === required.size) {
259
+ const path = [];
260
+ let cur = curKey;
261
+ while (cur !== void 0) {
262
+ const p = parents.get(cur);
263
+ if (!p) break;
264
+ path.push(p.edge);
265
+ cur = p.parentKey;
266
+ }
267
+ path.reverse();
268
+ return path;
269
+ }
270
+ }
271
+ return null;
272
+ }
273
+ /**
274
+ * Reverse-BFS from `toHash` over `reverseChain` to collect every node from
275
+ * which `toHash` is reachable (inclusive of `toHash` itself).
276
+ */
277
+ function collectNodesReachingTarget(graph, toHash) {
278
+ const reached = /* @__PURE__ */ new Set();
279
+ for (const step of bfs([toHash], (n) => reverseNeighbours(graph, n))) reached.add(step.state);
280
+ return reached;
281
+ }
282
+ /**
283
+ * Find the shortest path from `fromHash` to `toHash` and return structured
284
+ * path-decision metadata for machine-readable output. When `required` is
285
+ * non-empty, the returned path is the shortest one whose edges collectively
286
+ * cover every required invariant.
287
+ *
288
+ * The discriminated return type tells the caller *why* a path could not be
289
+ * found, so the CLI can pick the right structured error without re-running
290
+ * a structural BFS.
291
+ */
292
+ function findPathWithDecision(graph, fromHash, toHash, options = {}) {
293
+ const { refName, required = /* @__PURE__ */ new Set() } = options;
294
+ const requiredInvariants = [...required].sort();
295
+ if (fromHash === toHash && required.size === 0) return {
296
+ kind: "ok",
297
+ decision: {
298
+ selectedPath: [],
299
+ fromHash,
300
+ toHash,
301
+ alternativeCount: 0,
302
+ tieBreakReasons: [],
303
+ requiredInvariants,
304
+ satisfiedInvariants: [],
305
+ ...ifDefined("refName", refName)
306
+ }
307
+ };
308
+ const path = findPathWithInvariants(graph, fromHash, toHash, required);
309
+ if (!path) {
310
+ if (required.size === 0) return { kind: "unreachable" };
311
+ const structural = findPath(graph, fromHash, toHash);
312
+ if (structural === null) return { kind: "unreachable" };
313
+ const coveredByStructural = /* @__PURE__ */ new Set();
314
+ for (const edge of structural) for (const inv of edge.invariants) if (required.has(inv)) coveredByStructural.add(inv);
315
+ return {
316
+ kind: "unsatisfiable",
317
+ structuralPath: structural,
318
+ missing: requiredInvariants.filter((id) => !coveredByStructural.has(id))
319
+ };
320
+ }
321
+ const satisfiedInvariants = computeSatisfiedInvariants(required, path);
322
+ const reachesTarget = collectNodesReachingTarget(graph, toHash);
323
+ const coveragePrefixes = requiredCoveragePrefixes(required, path);
324
+ const tieBreakReasons = [];
325
+ let alternativeCount = 0;
326
+ for (const [i, edge] of path.entries()) {
327
+ const outgoing = graph.forwardChain.get(edge.from);
328
+ if (!outgoing || outgoing.length <= 1) continue;
329
+ const reachable = outgoing.filter((e) => reachesTarget.has(e.to));
330
+ if (reachable.length <= 1) continue;
331
+ let comparisonPool = reachable;
332
+ if (required.size > 0) {
333
+ const prefixSet = coveragePrefixes[i];
334
+ if (prefixSet === void 0) continue;
335
+ comparisonPool = invariantViableAlternativesAtStep(required, prefixSet, reachable);
336
+ }
337
+ alternativeCount += reachable.length - 1;
338
+ if (sortedNeighbors(reachable)[0]?.migrationHash !== edge.migrationHash) continue;
339
+ if (!reachable.some((e) => e.migrationHash !== edge.migrationHash)) continue;
340
+ const sortedViable = sortedNeighbors(comparisonPool);
341
+ if (sortedViable.length > 1 && sortedViable[0]?.migrationHash === edge.migrationHash && sortedViable.some((e) => e.migrationHash !== edge.migrationHash)) tieBreakReasons.push(`at ${edge.from}: ${comparisonPool.length} candidates, selected by tie-break`);
342
+ }
343
+ return {
344
+ kind: "ok",
345
+ decision: {
346
+ selectedPath: path,
347
+ fromHash,
348
+ toHash,
349
+ alternativeCount,
350
+ tieBreakReasons,
351
+ requiredInvariants,
352
+ satisfiedInvariants,
353
+ ...ifDefined("refName", refName)
354
+ }
355
+ };
356
+ }
357
+ function computeSatisfiedInvariants(required, path) {
358
+ if (required.size === 0) return [];
359
+ const covered = /* @__PURE__ */ new Set();
360
+ for (const edge of path) for (const inv of edge.invariants) if (required.has(inv)) covered.add(inv);
361
+ return [...covered].sort();
362
+ }
363
+ /**
364
+ * For each edge on path, invariant coverage accumulated from earlier edges only —
365
+ * `(required ∩ ∪_{j<i} path[j].invariants)` represented as cumulative set along `required`,
366
+ * keyed as "full set of required ids satisfied before taking path[i]".
367
+ */
368
+ function requiredCoveragePrefixes(required, path) {
369
+ const prefixes = [];
370
+ const acc = /* @__PURE__ */ new Set();
371
+ for (const edge of path) {
372
+ prefixes.push(new Set(acc));
373
+ for (const inv of edge.invariants) if (required.has(inv)) acc.add(inv);
374
+ }
375
+ return prefixes;
376
+ }
377
+ function invariantViableAlternativesAtStep(required, coverageBeforeTakingEdge, outgoing) {
378
+ if (required.size === 0) return [...outgoing];
379
+ return outgoing.filter((e) => [...required].every((id) => coverageBeforeTakingEdge.has(id) || e.invariants.includes(id)));
380
+ }
381
+ /**
382
+ * Walk ancestors of each branch tip back to find the last node
383
+ * that appears on all paths. Returns `fromHash` if no shared ancestor is found.
384
+ */
385
+ function findDivergencePoint(graph, fromHash, leaves) {
386
+ const ancestorSets = leaves.map((leaf) => {
387
+ const ancestors = /* @__PURE__ */ new Set();
388
+ for (const step of bfs([leaf], (n) => reverseNeighbours(graph, n))) ancestors.add(step.state);
389
+ return ancestors;
390
+ });
391
+ const commonAncestors = [...ancestorSets[0] ?? []].filter((node) => ancestorSets.every((s) => s.has(node)));
392
+ let deepest = fromHash;
393
+ let deepestDepth = -1;
394
+ for (const ancestor of commonAncestors) {
395
+ const path = findPath(graph, fromHash, ancestor);
396
+ const depth = path ? path.length : 0;
397
+ if (depth > deepestDepth) {
398
+ deepestDepth = depth;
399
+ deepest = ancestor;
400
+ }
401
+ }
402
+ return deepest;
403
+ }
404
+ /**
405
+ * Find all branch tips (nodes with no outgoing edges) reachable from
406
+ * `fromHash` via forward edges.
407
+ */
408
+ function findReachableLeaves(graph, fromHash) {
409
+ const leaves = [];
410
+ for (const step of bfs([fromHash], (n) => forwardNeighbours(graph, n))) if (!graph.forwardChain.get(step.state)?.length) leaves.push(step.state);
411
+ return leaves;
412
+ }
413
+ /**
414
+ * Find the target contract hash of the migration graph reachable from
415
+ * EMPTY_CONTRACT_HASH. Returns `null` for a graph that has no target
416
+ * state (either empty, or containing only the root with no outgoing
417
+ * edges). Throws NO_INITIAL_MIGRATION if the graph has nodes but none
418
+ * originate from the empty hash, and AMBIGUOUS_TARGET if multiple
419
+ * branch tips exist.
420
+ */
421
+ function findLeaf(graph) {
422
+ if (graph.nodes.size === 0) return null;
423
+ if (!graph.nodes.has("sha256:empty")) throw errorNoInitialMigration([...graph.nodes]);
424
+ const leaves = findReachableLeaves(graph, EMPTY_CONTRACT_HASH);
425
+ if (leaves.length === 0) {
426
+ const reachable = [...graph.nodes].filter((n) => n !== EMPTY_CONTRACT_HASH);
427
+ if (reachable.length > 0) throw errorNoTarget(reachable);
428
+ return null;
429
+ }
430
+ if (leaves.length > 1) {
431
+ const divergencePoint = findDivergencePoint(graph, EMPTY_CONTRACT_HASH, leaves);
432
+ throw errorAmbiguousTarget(leaves, {
433
+ divergencePoint,
434
+ branches: leaves.map((tip) => {
435
+ return {
436
+ tip,
437
+ edges: (findPath(graph, divergencePoint, tip) ?? []).map((e) => ({
438
+ dirName: e.dirName,
439
+ from: e.from,
440
+ to: e.to
441
+ }))
442
+ };
443
+ })
444
+ });
445
+ }
446
+ return leaves[0];
447
+ }
448
+ /**
449
+ * Find the latest migration entry by traversing from EMPTY_CONTRACT_HASH
450
+ * to the single target. Returns null for an empty graph.
451
+ * Throws AMBIGUOUS_TARGET if the graph has multiple branch tips.
452
+ */
453
+ function findLatestMigration(graph) {
454
+ const leafHash = findLeaf(graph);
455
+ if (leafHash === null) return null;
456
+ return findPath(graph, "sha256:empty", leafHash)?.at(-1) ?? null;
457
+ }
458
+ function detectCycles(graph) {
459
+ const WHITE = 0;
460
+ const GRAY = 1;
461
+ const BLACK = 2;
462
+ const color = /* @__PURE__ */ new Map();
463
+ const parentMap = /* @__PURE__ */ new Map();
464
+ const cycles = [];
465
+ for (const node of graph.nodes) color.set(node, WHITE);
466
+ const stack = [];
467
+ function pushFrame(u) {
468
+ color.set(u, GRAY);
469
+ stack.push({
470
+ node: u,
471
+ outgoing: graph.forwardChain.get(u) ?? [],
472
+ index: 0
473
+ });
474
+ }
475
+ for (const root of graph.nodes) {
476
+ if (color.get(root) !== WHITE) continue;
477
+ parentMap.set(root, null);
478
+ pushFrame(root);
479
+ while (stack.length > 0) {
480
+ const frame = stack[stack.length - 1];
481
+ if (frame.index >= frame.outgoing.length) {
482
+ color.set(frame.node, BLACK);
483
+ stack.pop();
484
+ continue;
485
+ }
486
+ const v = frame.outgoing[frame.index++].to;
487
+ const vColor = color.get(v);
488
+ if (vColor === GRAY) {
489
+ const cycle = [v];
490
+ let cur = frame.node;
491
+ while (cur !== v) {
492
+ cycle.push(cur);
493
+ cur = parentMap.get(cur) ?? v;
494
+ }
495
+ cycle.reverse();
496
+ cycles.push(cycle);
497
+ } else if (vColor === WHITE) {
498
+ parentMap.set(v, frame.node);
499
+ pushFrame(v);
500
+ }
501
+ }
502
+ }
503
+ return cycles;
504
+ }
505
+ function detectOrphans(graph) {
506
+ if (graph.nodes.size === 0) return [];
507
+ const reachable = /* @__PURE__ */ new Set();
508
+ const startNodes = [];
509
+ if (graph.forwardChain.has("sha256:empty")) startNodes.push(EMPTY_CONTRACT_HASH);
510
+ else {
511
+ const allTargets = /* @__PURE__ */ new Set();
512
+ for (const edges of graph.forwardChain.values()) for (const edge of edges) allTargets.add(edge.to);
513
+ for (const node of graph.nodes) if (!allTargets.has(node)) startNodes.push(node);
514
+ }
515
+ for (const step of bfs(startNodes, (n) => forwardNeighbours(graph, n))) reachable.add(step.state);
516
+ const orphans = [];
517
+ for (const [from, migrations] of graph.forwardChain) if (!reachable.has(from)) orphans.push(...migrations);
518
+ return orphans;
519
+ }
520
+ //#endregion
521
+ export { findPath as a, findReachableLeaves as c, findLeaf as i, reconstructGraph as l, detectOrphans as n, findPathWithDecision as o, findLatestMigration as r, findPathWithInvariants as s, detectCycles as t };
522
+
523
+ //# sourceMappingURL=migration-graph-DGNnKDY5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-graph-DGNnKDY5.mjs","names":[],"sources":["../src/queue.ts","../src/graph-ops.ts","../src/migration-graph.ts"],"sourcesContent":["/**\n * FIFO queue with amortised O(1) push and shift.\n *\n * Uses a head-index cursor over a backing array rather than\n * `Array.prototype.shift()`, which is O(n) on V8. Intended for BFS-shaped\n * traversals where the queue is drained in a single pass — it does not\n * reclaim memory for already-shifted items, so it is not suitable for\n * long-lived queues with many push/shift cycles.\n */\nexport class Queue<T> {\n private readonly items: T[];\n private head = 0;\n\n constructor(initial: Iterable<T> = []) {\n this.items = [...initial];\n }\n\n push(item: T): void {\n this.items.push(item);\n }\n\n /**\n * Remove and return the next item. Caller must check `isEmpty` first —\n * shifting an empty queue throws.\n */\n shift(): T {\n if (this.head >= this.items.length) {\n throw new Error('Queue.shift called on empty queue');\n }\n // biome-ignore lint/style/noNonNullAssertion: bounds-checked on the line above\n return this.items[this.head++]!;\n }\n\n get isEmpty(): boolean {\n return this.head >= this.items.length;\n }\n}\n","import { Queue } from './queue';\n\n/**\n * One step of a BFS traversal.\n *\n * `parent` and `incomingEdge` are `null` for start states — they were not\n * reached via any edge. For every other state they record the predecessor\n * state and the edge by which this state was first reached.\n *\n * `state` is the BFS state, most often a string (graph node identifier) but\n * can be a composite object. The string overload keeps the common case\n * ergonomic; the generic overload accepts a caller-supplied `key` function\n * that produces a stable equality key for dedup.\n */\nexport interface BfsStep<S, E> {\n readonly state: S;\n readonly parent: S | null;\n readonly incomingEdge: E | null;\n}\n\n/**\n * Generic breadth-first traversal.\n *\n * Direction (forward/reverse) is expressed by the caller's `neighbours`\n * closure: return `{ next, edge }` pairs where `next` is the state to visit\n * next and `edge` is the edge that connects them. Callers that don't need\n * path reconstruction can ignore the `parent`/`incomingEdge` fields of each\n * yielded step.\n *\n * Ordering — when the result needs to be deterministic (path-finding) the\n * caller is responsible for sorting inside `neighbours`; this generator\n * does not impose an ordering hook of its own. State-dependent orderings\n * have full access to the source state inside the closure.\n *\n * Stops are intrinsic — callers `break` out of the `for..of` loop when\n * they've found what they're looking for.\n */\nexport function bfs<E>(\n starts: Iterable<string>,\n neighbours: (state: string) => Iterable<{ next: string; edge: E }>,\n): Generator<BfsStep<string, E>>;\nexport function bfs<S, E>(\n starts: Iterable<S>,\n neighbours: (state: S) => Iterable<{ next: S; edge: E }>,\n key: (state: S) => string,\n): Generator<BfsStep<S, E>>;\nexport function* bfs<S, E>(\n starts: Iterable<S>,\n neighbours: (state: S) => Iterable<{ next: S; edge: E }>,\n // Identity default for the string overload. TypeScript can't express\n // \"default applies only when S = string\", so this cast bridges the\n // generic implementation signature to the public overloads — which\n // guarantee `key` is omitted only when S = string at the call site.\n key: (state: S) => string = (state) => state as unknown as string,\n): Generator<BfsStep<S, E>> {\n // Queue entries carry the state alongside its key so we don't recompute\n // key() twice per visit (once on dedup, once on parent lookup). Composite\n // keys can be non-trivial to compute; string-overload callers pay nothing\n // since key() is identity there.\n interface Entry {\n readonly state: S;\n readonly key: string;\n }\n const visited = new Set<string>();\n const parentMap = new Map<string, { parent: S; edge: E }>();\n const queue = new Queue<Entry>();\n for (const start of starts) {\n const k = key(start);\n if (!visited.has(k)) {\n visited.add(k);\n queue.push({ state: start, key: k });\n }\n }\n while (!queue.isEmpty) {\n const { state: current, key: curKey } = queue.shift();\n const parentInfo = parentMap.get(curKey);\n yield {\n state: current,\n parent: parentInfo?.parent ?? null,\n incomingEdge: parentInfo?.edge ?? null,\n };\n\n for (const { next, edge } of neighbours(current)) {\n const k = key(next);\n if (!visited.has(k)) {\n visited.add(k);\n parentMap.set(k, { parent: current, edge });\n queue.push({ state: next, key: k });\n }\n }\n }\n}\n","import { ifDefined } from '@prisma-next/utils/defined';\nimport { EMPTY_CONTRACT_HASH } from './constants';\nimport {\n errorAmbiguousTarget,\n errorDuplicateMigrationHash,\n errorNoInitialMigration,\n errorNoTarget,\n errorSameSourceAndTarget,\n} from './errors';\nimport type { MigrationEdge, MigrationGraph } from './graph';\nimport { bfs } from './graph-ops';\nimport type { OnDiskMigrationPackage } from './package';\n\n/** Forward-edge neighbours: edge `e` from `n` visits `e.to` next. */\nfunction forwardNeighbours(graph: MigrationGraph, node: string) {\n return (graph.forwardChain.get(node) ?? []).map((edge) => ({ next: edge.to, edge }));\n}\n\n/**\n * Forward-edge neighbours, sorted by the deterministic tie-break.\n * Used by path-finding so the resulting shortest path is stable across runs.\n */\nfunction sortedForwardNeighbours(graph: MigrationGraph, node: string) {\n const edges = graph.forwardChain.get(node) ?? [];\n return [...edges].sort(compareTieBreak).map((edge) => ({ next: edge.to, edge }));\n}\n\n/** Reverse-edge neighbours: edge `e` from `n` visits `e.from` next. */\nfunction reverseNeighbours(graph: MigrationGraph, node: string) {\n return (graph.reverseChain.get(node) ?? []).map((edge) => ({ next: edge.from, edge }));\n}\n\nfunction appendEdge(map: Map<string, MigrationEdge[]>, key: string, entry: MigrationEdge): void {\n const bucket = map.get(key);\n if (bucket) bucket.push(entry);\n else map.set(key, [entry]);\n}\n\nexport function reconstructGraph(packages: readonly OnDiskMigrationPackage[]): MigrationGraph {\n const nodes = new Set<string>();\n const forwardChain = new Map<string, MigrationEdge[]>();\n const reverseChain = new Map<string, MigrationEdge[]>();\n const migrationByHash = new Map<string, MigrationEdge>();\n\n for (const pkg of packages) {\n // Manifest `from` is `string | null` (null = baseline). The graph layer\n // is the marker/path layer where \"no prior state\" is encoded as the\n // EMPTY_CONTRACT_HASH sentinel; bridge here so pathfinding stays string-\n // keyed.\n const from = pkg.metadata.from ?? EMPTY_CONTRACT_HASH;\n const { to } = pkg.metadata;\n\n if (from === to) {\n const hasDataOp = pkg.ops.some((op) => op.operationClass === 'data');\n if (!hasDataOp) {\n throw errorSameSourceAndTarget(pkg.dirPath, from);\n }\n }\n\n nodes.add(from);\n nodes.add(to);\n\n const migration: MigrationEdge = {\n from,\n to,\n migrationHash: pkg.metadata.migrationHash,\n dirName: pkg.dirName,\n createdAt: pkg.metadata.createdAt,\n labels: pkg.metadata.labels,\n invariants: pkg.metadata.providedInvariants,\n };\n\n if (migrationByHash.has(migration.migrationHash)) {\n throw errorDuplicateMigrationHash(migration.migrationHash);\n }\n migrationByHash.set(migration.migrationHash, migration);\n\n appendEdge(forwardChain, from, migration);\n appendEdge(reverseChain, to, migration);\n }\n\n return { nodes, forwardChain, reverseChain, migrationByHash };\n}\n\n// ---------------------------------------------------------------------------\n// Deterministic tie-breaking for BFS neighbour order.\n// Used by path-finders only; not a general-purpose utility.\n// Ordering: label priority → createdAt → to → migrationHash.\n// ---------------------------------------------------------------------------\n\nconst LABEL_PRIORITY: Record<string, number> = { main: 0, default: 1, feature: 2 };\n\nfunction labelPriority(labels: readonly string[]): number {\n let best = 3;\n for (const l of labels) {\n const p = LABEL_PRIORITY[l];\n if (p !== undefined && p < best) best = p;\n }\n return best;\n}\n\nfunction compareTieBreak(a: MigrationEdge, b: MigrationEdge): number {\n const lp = labelPriority(a.labels) - labelPriority(b.labels);\n if (lp !== 0) return lp;\n const ca = a.createdAt.localeCompare(b.createdAt);\n if (ca !== 0) return ca;\n const tc = a.to.localeCompare(b.to);\n if (tc !== 0) return tc;\n return a.migrationHash.localeCompare(b.migrationHash);\n}\n\nfunction sortedNeighbors(edges: readonly MigrationEdge[]): readonly MigrationEdge[] {\n return [...edges].sort(compareTieBreak);\n}\n\n/**\n * Find the shortest path from `fromHash` to `toHash` using BFS over the\n * contract-hash graph. Returns the ordered list of edges, or null if no path\n * exists. Returns an empty array when `fromHash === toHash` (no-op).\n *\n * Neighbor ordering is deterministic via the tie-break sort key:\n * label priority → createdAt → to → migrationHash.\n */\nexport function findPath(\n graph: MigrationGraph,\n fromHash: string,\n toHash: string,\n): readonly MigrationEdge[] | null {\n if (fromHash === toHash) return [];\n\n const parents = new Map<string, { parent: string; edge: MigrationEdge }>();\n for (const step of bfs([fromHash], (n) => sortedForwardNeighbours(graph, n))) {\n if (step.parent !== null && step.incomingEdge !== null) {\n parents.set(step.state, { parent: step.parent, edge: step.incomingEdge });\n }\n if (step.state === toHash) {\n const path: MigrationEdge[] = [];\n let cur = toHash;\n let p = parents.get(cur);\n while (p) {\n path.push(p.edge);\n cur = p.parent;\n p = parents.get(cur);\n }\n path.reverse();\n return path;\n }\n }\n\n return null;\n}\n\n/**\n * Find the shortest path from `fromHash` to `toHash` whose edges collectively\n * cover every invariant in `required`. Returns `null` when no such path exists\n * (either `fromHash`→`toHash` is structurally unreachable, or every reachable\n * path leaves at least one required invariant uncovered). When `required` is\n * empty, delegates to `findPath` so the result is byte-identical for that case.\n *\n * Algorithm: BFS over `(node, coveredSubset)` states with state-level dedup.\n * The covered subset is a `Set<string>` of invariant ids; the state's dedup\n * key is `${node}\\0${[...covered].sort().join('\\0')}`. State keys distinguish\n * distinct `(node, covered)` tuples regardless of node-name length because\n * `\\0` cannot appear in any invariant id (validation rejects whitespace and\n * control chars at authoring time).\n *\n * Neighbour ordering when `required ≠ ∅`: edges covering ≥1 still-needed\n * invariant come first, with `labelPriority → createdAt → to → migrationHash`\n * as the secondary key. The heuristic steers BFS toward the satisfying path;\n * correctness (shortest, deterministic) does not depend on it.\n */\nexport function findPathWithInvariants(\n graph: MigrationGraph,\n fromHash: string,\n toHash: string,\n required: ReadonlySet<string>,\n): readonly MigrationEdge[] | null {\n if (required.size === 0) {\n return findPath(graph, fromHash, toHash);\n }\n\n interface InvState {\n readonly node: string;\n readonly covered: ReadonlySet<string>;\n }\n // `\\0` is a safe segment separator: `validateInvariantId` rejects any id\n // containing whitespace or control characters (NUL is U+0000), and node\n // hashes are hex strings. Distinct `(node, covered)` tuples therefore\n // map to distinct strings. If `validateInvariantId` is ever relaxed,\n // re-confirm dedup correctness here.\n const stateKey = (s: InvState): string => {\n if (s.covered.size === 0) return `${s.node}\\0`;\n return `${s.node}\\0${[...s.covered].sort().join('\\0')}`;\n };\n\n const neighbours = (s: InvState): Iterable<{ next: InvState; edge: MigrationEdge }> => {\n const outgoing = graph.forwardChain.get(s.node) ?? [];\n if (outgoing.length === 0) return [];\n return [...outgoing]\n .map((edge) => {\n let useful = false;\n let next: Set<string> | null = null;\n for (const inv of edge.invariants) {\n if (required.has(inv) && !s.covered.has(inv)) {\n if (next === null) next = new Set(s.covered);\n next.add(inv);\n useful = true;\n }\n }\n return { edge, useful, nextCovered: next ?? s.covered };\n })\n .sort((a, b) => {\n if (a.useful !== b.useful) return a.useful ? -1 : 1;\n return compareTieBreak(a.edge, b.edge);\n })\n .map(({ edge, nextCovered }) => ({\n next: { node: edge.to, covered: nextCovered },\n edge,\n }));\n };\n\n // Path reconstruction is consumer-side, keyed on stateKey, same shape as\n // findPath's parents map.\n const parents = new Map<string, { parentKey: string; edge: MigrationEdge }>();\n for (const step of bfs<InvState, MigrationEdge>(\n [{ node: fromHash, covered: new Set() }],\n neighbours,\n stateKey,\n )) {\n const curKey = stateKey(step.state);\n if (step.parent !== null && step.incomingEdge !== null) {\n parents.set(curKey, { parentKey: stateKey(step.parent), edge: step.incomingEdge });\n }\n if (step.state.node === toHash && step.state.covered.size === required.size) {\n const path: MigrationEdge[] = [];\n let cur: string | undefined = curKey;\n while (cur !== undefined) {\n const p = parents.get(cur);\n if (!p) break;\n path.push(p.edge);\n cur = p.parentKey;\n }\n path.reverse();\n return path;\n }\n }\n\n return null;\n}\n\n/**\n * Reverse-BFS from `toHash` over `reverseChain` to collect every node from\n * which `toHash` is reachable (inclusive of `toHash` itself).\n */\nfunction collectNodesReachingTarget(graph: MigrationGraph, toHash: string): Set<string> {\n const reached = new Set<string>();\n for (const step of bfs([toHash], (n) => reverseNeighbours(graph, n))) {\n reached.add(step.state);\n }\n return reached;\n}\n\nexport interface PathDecision {\n readonly selectedPath: readonly MigrationEdge[];\n readonly fromHash: string;\n readonly toHash: string;\n readonly alternativeCount: number;\n readonly tieBreakReasons: readonly string[];\n readonly refName?: string;\n /** The caller-supplied required invariant set, sorted ascending. */\n readonly requiredInvariants: readonly string[];\n /**\n * The subset of `requiredInvariants` actually covered by edges on\n * `selectedPath`. Always a subset of `requiredInvariants` (when the path\n * is satisfying, equal to it); always derived from `selectedPath`.\n */\n readonly satisfiedInvariants: readonly string[];\n}\n\n/**\n * Outcome of {@link findPathWithDecision}. The pathfinder distinguishes\n * three cases up front so callers don't re-derive structural reachability:\n *\n * - `ok` — a path covering `required` exists; `decision` carries the\n * selection metadata and per-edge invariants.\n * - `unreachable` — `from`→`to` has no structural path. Mapped by callers\n * to the existing no-path / `NO_TARGET` diagnostic.\n * - `unsatisfiable` — `from`→`to` is structurally reachable but no path\n * covers every required invariant. `structuralPath` is the\n * `findPath(graph, from, to)` result, included so callers don't have to\n * recompute it when raising `MIGRATION.NO_INVARIANT_PATH`. `missing` is\n * the subset of `required` that the structural path does *not* cover —\n * correctly accounts for partial coverage when some required invariants\n * are met by the fallback path. Only emitted when `required` is\n * non-empty.\n */\nexport type FindPathOutcome =\n | { readonly kind: 'ok'; readonly decision: PathDecision }\n | { readonly kind: 'unreachable' }\n | {\n readonly kind: 'unsatisfiable';\n readonly structuralPath: readonly MigrationEdge[];\n readonly missing: readonly string[];\n };\n\n/**\n * Routing context for {@link findPathWithDecision}. Both fields are optional;\n * `refName` is only used to decorate the resulting `PathDecision` for the\n * JSON envelope, and `required` defaults to an empty set (purely structural\n * routing). They are passed via a single options object so the call sites\n * cannot silently swap two adjacent string parameters.\n */\nexport interface FindPathWithDecisionOptions {\n readonly refName?: string;\n readonly required?: ReadonlySet<string>;\n}\n\n/**\n * Find the shortest path from `fromHash` to `toHash` and return structured\n * path-decision metadata for machine-readable output. When `required` is\n * non-empty, the returned path is the shortest one whose edges collectively\n * cover every required invariant.\n *\n * The discriminated return type tells the caller *why* a path could not be\n * found, so the CLI can pick the right structured error without re-running\n * a structural BFS.\n */\nexport function findPathWithDecision(\n graph: MigrationGraph,\n fromHash: string,\n toHash: string,\n options: FindPathWithDecisionOptions = {},\n): FindPathOutcome {\n const { refName, required = new Set<string>() } = options;\n const requiredInvariants = [...required].sort();\n\n if (fromHash === toHash && required.size === 0) {\n return {\n kind: 'ok',\n decision: {\n selectedPath: [],\n fromHash,\n toHash,\n alternativeCount: 0,\n tieBreakReasons: [],\n requiredInvariants,\n satisfiedInvariants: [],\n ...ifDefined('refName', refName),\n },\n };\n }\n\n const path = findPathWithInvariants(graph, fromHash, toHash, required);\n if (!path) {\n if (required.size === 0) {\n return { kind: 'unreachable' };\n }\n const structural = findPath(graph, fromHash, toHash);\n if (structural === null) {\n return { kind: 'unreachable' };\n }\n const coveredByStructural = new Set<string>();\n for (const edge of structural) {\n for (const inv of edge.invariants) {\n if (required.has(inv)) coveredByStructural.add(inv);\n }\n }\n const missing = requiredInvariants.filter((id) => !coveredByStructural.has(id));\n return { kind: 'unsatisfiable', structuralPath: structural, missing };\n }\n\n const satisfiedInvariants = computeSatisfiedInvariants(required, path);\n\n // Single reverse BFS marks every node from which `toHash` is reachable.\n // Replaces a per-edge `findPath(e.to, toHash)` call inside the loop below,\n // which made the whole function O(|path| · (V + E)) instead of O(V + E).\n const reachesTarget = collectNodesReachingTarget(graph, toHash);\n const coveragePrefixes = requiredCoveragePrefixes(required, path);\n\n const tieBreakReasons: string[] = [];\n let alternativeCount = 0;\n\n for (const [i, edge] of path.entries()) {\n const outgoing = graph.forwardChain.get(edge.from);\n if (!outgoing || outgoing.length <= 1) continue;\n const reachable = outgoing.filter((e) => reachesTarget.has(e.to));\n if (reachable.length <= 1) continue;\n\n let comparisonPool: readonly MigrationEdge[] = reachable;\n if (required.size > 0) {\n // coveragePrefixes is built one-per-edge from path, so the index is\n // always in range here; the explicit guard keeps the type narrowed\n // without a non-null assertion.\n const prefixSet = coveragePrefixes[i];\n if (prefixSet === undefined) continue;\n comparisonPool = invariantViableAlternativesAtStep(required, prefixSet, reachable);\n }\n\n alternativeCount += reachable.length - 1;\n const sorted = sortedNeighbors(reachable);\n if (sorted[0]?.migrationHash !== edge.migrationHash) continue;\n if (!reachable.some((e) => e.migrationHash !== edge.migrationHash)) continue;\n\n const sortedViable = sortedNeighbors(comparisonPool);\n if (\n sortedViable.length > 1 &&\n sortedViable[0]?.migrationHash === edge.migrationHash &&\n sortedViable.some((e) => e.migrationHash !== edge.migrationHash)\n ) {\n tieBreakReasons.push(\n `at ${edge.from}: ${comparisonPool.length} candidates, selected by tie-break`,\n );\n }\n }\n\n return {\n kind: 'ok',\n decision: {\n selectedPath: path,\n fromHash,\n toHash,\n alternativeCount,\n tieBreakReasons,\n requiredInvariants,\n satisfiedInvariants,\n ...ifDefined('refName', refName),\n },\n };\n}\n\nfunction computeSatisfiedInvariants(\n required: ReadonlySet<string>,\n path: readonly MigrationEdge[],\n): readonly string[] {\n if (required.size === 0) return [];\n const covered = new Set<string>();\n for (const edge of path) {\n for (const inv of edge.invariants) {\n if (required.has(inv)) covered.add(inv);\n }\n }\n return [...covered].sort();\n}\n\n/**\n * For each edge on path, invariant coverage accumulated from earlier edges only —\n * `(required ∩ ∪_{j<i} path[j].invariants)` represented as cumulative set along `required`,\n * keyed as \"full set of required ids satisfied before taking path[i]\".\n */\nfunction requiredCoveragePrefixes(\n required: ReadonlySet<string>,\n path: readonly MigrationEdge[],\n): readonly ReadonlySet<string>[] {\n const prefixes: ReadonlySet<string>[] = [];\n const acc = new Set<string>();\n for (const edge of path) {\n prefixes.push(new Set(acc));\n for (const inv of edge.invariants) {\n if (required.has(inv)) acc.add(inv);\n }\n }\n return prefixes;\n}\n\nfunction invariantViableAlternativesAtStep(\n required: ReadonlySet<string>,\n coverageBeforeTakingEdge: ReadonlySet<string>,\n outgoing: readonly MigrationEdge[],\n): readonly MigrationEdge[] {\n if (required.size === 0) return [...outgoing];\n return outgoing.filter((e) =>\n [...required].every((id) => coverageBeforeTakingEdge.has(id) || e.invariants.includes(id)),\n );\n}\n\n/**\n * Walk ancestors of each branch tip back to find the last node\n * that appears on all paths. Returns `fromHash` if no shared ancestor is found.\n */\nfunction findDivergencePoint(\n graph: MigrationGraph,\n fromHash: string,\n leaves: readonly string[],\n): string {\n const ancestorSets = leaves.map((leaf) => {\n const ancestors = new Set<string>();\n for (const step of bfs([leaf], (n) => reverseNeighbours(graph, n))) {\n ancestors.add(step.state);\n }\n return ancestors;\n });\n\n const commonAncestors = [...(ancestorSets[0] ?? [])].filter((node) =>\n ancestorSets.every((s) => s.has(node)),\n );\n\n let deepest = fromHash;\n let deepestDepth = -1;\n for (const ancestor of commonAncestors) {\n const path = findPath(graph, fromHash, ancestor);\n const depth = path ? path.length : 0;\n if (depth > deepestDepth) {\n deepestDepth = depth;\n deepest = ancestor;\n }\n }\n return deepest;\n}\n\n/**\n * Find all branch tips (nodes with no outgoing edges) reachable from\n * `fromHash` via forward edges.\n */\nexport function findReachableLeaves(graph: MigrationGraph, fromHash: string): readonly string[] {\n const leaves: string[] = [];\n for (const step of bfs([fromHash], (n) => forwardNeighbours(graph, n))) {\n if (!graph.forwardChain.get(step.state)?.length) {\n leaves.push(step.state);\n }\n }\n return leaves;\n}\n\n/**\n * Find the target contract hash of the migration graph reachable from\n * EMPTY_CONTRACT_HASH. Returns `null` for a graph that has no target\n * state (either empty, or containing only the root with no outgoing\n * edges). Throws NO_INITIAL_MIGRATION if the graph has nodes but none\n * originate from the empty hash, and AMBIGUOUS_TARGET if multiple\n * branch tips exist.\n */\nexport function findLeaf(graph: MigrationGraph): string | null {\n if (graph.nodes.size === 0) {\n return null;\n }\n\n if (!graph.nodes.has(EMPTY_CONTRACT_HASH)) {\n throw errorNoInitialMigration([...graph.nodes]);\n }\n\n const leaves = findReachableLeaves(graph, EMPTY_CONTRACT_HASH);\n\n if (leaves.length === 0) {\n const reachable = [...graph.nodes].filter((n) => n !== EMPTY_CONTRACT_HASH);\n if (reachable.length > 0) {\n throw errorNoTarget(reachable);\n }\n return null;\n }\n\n if (leaves.length > 1) {\n const divergencePoint = findDivergencePoint(graph, EMPTY_CONTRACT_HASH, leaves);\n const branches = leaves.map((tip) => {\n const path = findPath(graph, divergencePoint, tip);\n return {\n tip,\n edges: (path ?? []).map((e) => ({ dirName: e.dirName, from: e.from, to: e.to })),\n };\n });\n throw errorAmbiguousTarget(leaves, { divergencePoint, branches });\n }\n\n // biome-ignore lint/style/noNonNullAssertion: leaves.length is neither 0 nor >1 per the branches above, so exactly one leaf remains\n return leaves[0]!;\n}\n\n/**\n * Find the latest migration entry by traversing from EMPTY_CONTRACT_HASH\n * to the single target. Returns null for an empty graph.\n * Throws AMBIGUOUS_TARGET if the graph has multiple branch tips.\n */\nexport function findLatestMigration(graph: MigrationGraph): MigrationEdge | null {\n const leafHash = findLeaf(graph);\n if (leafHash === null) return null;\n\n const path = findPath(graph, EMPTY_CONTRACT_HASH, leafHash);\n return path?.at(-1) ?? null;\n}\n\nexport function detectCycles(graph: MigrationGraph): readonly string[][] {\n const WHITE = 0;\n const GRAY = 1;\n const BLACK = 2;\n\n const color = new Map<string, number>();\n const parentMap = new Map<string, string | null>();\n const cycles: string[][] = [];\n\n for (const node of graph.nodes) {\n color.set(node, WHITE);\n }\n\n // Iterative three-color DFS. A frame is (node, outgoing edges, next-index).\n interface Frame {\n node: string;\n outgoing: readonly MigrationEdge[];\n index: number;\n }\n const stack: Frame[] = [];\n\n function pushFrame(u: string): void {\n color.set(u, GRAY);\n stack.push({ node: u, outgoing: graph.forwardChain.get(u) ?? [], index: 0 });\n }\n\n for (const root of graph.nodes) {\n if (color.get(root) !== WHITE) continue;\n parentMap.set(root, null);\n pushFrame(root);\n\n while (stack.length > 0) {\n // biome-ignore lint/style/noNonNullAssertion: stack.length > 0 should guarantee that this cannot be undefined\n const frame = stack[stack.length - 1]!;\n if (frame.index >= frame.outgoing.length) {\n color.set(frame.node, BLACK);\n stack.pop();\n continue;\n }\n // biome-ignore lint/style/noNonNullAssertion: the early-continue above guarantees frame.index < frame.outgoing.length here, so this is defined\n const edge = frame.outgoing[frame.index++]!;\n const v = edge.to;\n const vColor = color.get(v);\n if (vColor === GRAY) {\n const cycle: string[] = [v];\n let cur = frame.node;\n while (cur !== v) {\n cycle.push(cur);\n cur = parentMap.get(cur) ?? v;\n }\n cycle.reverse();\n cycles.push(cycle);\n } else if (vColor === WHITE) {\n parentMap.set(v, frame.node);\n pushFrame(v);\n }\n }\n }\n\n return cycles;\n}\n\nexport function detectOrphans(graph: MigrationGraph): readonly MigrationEdge[] {\n if (graph.nodes.size === 0) return [];\n\n const reachable = new Set<string>();\n const startNodes: string[] = [];\n\n if (graph.forwardChain.has(EMPTY_CONTRACT_HASH)) {\n startNodes.push(EMPTY_CONTRACT_HASH);\n } else {\n const allTargets = new Set<string>();\n for (const edges of graph.forwardChain.values()) {\n for (const edge of edges) {\n allTargets.add(edge.to);\n }\n }\n for (const node of graph.nodes) {\n if (!allTargets.has(node)) {\n startNodes.push(node);\n }\n }\n }\n\n for (const step of bfs(startNodes, (n) => forwardNeighbours(graph, n))) {\n reachable.add(step.state);\n }\n\n const orphans: MigrationEdge[] = [];\n for (const [from, migrations] of graph.forwardChain) {\n if (!reachable.has(from)) {\n orphans.push(...migrations);\n }\n }\n\n return orphans;\n}\n"],"mappings":"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"}