@prisma-next/migration-tools 0.4.0-dev.9 → 0.4.2

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 (56) hide show
  1. package/README.md +1 -1
  2. package/dist/{attestation-DnebS4XZ.mjs → attestation-BnzTb0Qp.mjs} +24 -23
  3. package/dist/attestation-BnzTb0Qp.mjs.map +1 -0
  4. package/dist/{errors-C_XuSbX7.mjs → errors-BmiSgz1j.mjs} +14 -7
  5. package/dist/errors-BmiSgz1j.mjs.map +1 -0
  6. package/dist/exports/attestation.d.mts +20 -6
  7. package/dist/exports/attestation.d.mts.map +1 -1
  8. package/dist/exports/attestation.mjs +3 -3
  9. package/dist/exports/dag.d.mts +8 -6
  10. package/dist/exports/dag.d.mts.map +1 -1
  11. package/dist/exports/dag.mjs +181 -107
  12. package/dist/exports/dag.mjs.map +1 -1
  13. package/dist/exports/io.d.mts +16 -13
  14. package/dist/exports/io.d.mts.map +1 -1
  15. package/dist/exports/io.mjs +2 -2
  16. package/dist/exports/migration-ts.d.mts +15 -21
  17. package/dist/exports/migration-ts.d.mts.map +1 -1
  18. package/dist/exports/migration-ts.mjs +28 -36
  19. package/dist/exports/migration-ts.mjs.map +1 -1
  20. package/dist/exports/migration.d.mts +48 -18
  21. package/dist/exports/migration.d.mts.map +1 -1
  22. package/dist/exports/migration.mjs +75 -85
  23. package/dist/exports/migration.mjs.map +1 -1
  24. package/dist/exports/refs.d.mts +11 -5
  25. package/dist/exports/refs.d.mts.map +1 -1
  26. package/dist/exports/refs.mjs +106 -30
  27. package/dist/exports/refs.mjs.map +1 -1
  28. package/dist/exports/types.d.mts +2 -2
  29. package/dist/exports/types.mjs +2 -16
  30. package/dist/{io-Cun81AIZ.mjs → io-Cd6GLyjK.mjs} +18 -22
  31. package/dist/io-Cd6GLyjK.mjs.map +1 -0
  32. package/dist/types-DyGXcWWp.d.mts +71 -0
  33. package/dist/types-DyGXcWWp.d.mts.map +1 -0
  34. package/package.json +5 -4
  35. package/src/attestation.ts +34 -26
  36. package/src/dag.ts +140 -154
  37. package/src/errors.ts +8 -0
  38. package/src/exports/attestation.ts +2 -1
  39. package/src/exports/io.ts +1 -1
  40. package/src/exports/migration-ts.ts +1 -1
  41. package/src/exports/migration.ts +8 -1
  42. package/src/exports/refs.ts +10 -2
  43. package/src/exports/types.ts +2 -8
  44. package/src/graph-ops.ts +65 -0
  45. package/src/io.ts +23 -24
  46. package/src/migration-base.ts +99 -101
  47. package/src/migration-ts.ts +28 -50
  48. package/src/queue.ts +37 -0
  49. package/src/refs.ts +148 -37
  50. package/src/types.ts +15 -55
  51. package/dist/attestation-DnebS4XZ.mjs.map +0 -1
  52. package/dist/errors-C_XuSbX7.mjs.map +0 -1
  53. package/dist/exports/types.mjs.map +0 -1
  54. package/dist/io-Cun81AIZ.mjs.map +0 -1
  55. package/dist/types-D2uX4ql7.d.mts +0 -100
  56. package/dist/types-D2uX4ql7.d.mts.map +0 -1
@@ -1,8 +1,107 @@
1
- import { f as errorNoInitialMigration, i as errorDuplicateMigrationId, m as errorSameSourceAndTarget, n as errorAmbiguousTarget, p as errorNoTarget } from "../errors-C_XuSbX7.mjs";
1
+ import { h as errorSameSourceAndTarget, i as errorDuplicateMigrationId, m as errorNoTarget, n as errorAmbiguousTarget, p as errorNoInitialMigration } from "../errors-BmiSgz1j.mjs";
2
2
  import { t as EMPTY_CONTRACT_HASH } from "../constants-BRi0X7B_.mjs";
3
3
  import { ifDefined } from "@prisma-next/utils/defined";
4
4
 
5
+ //#region src/queue.ts
6
+ /**
7
+ * FIFO queue with amortised O(1) push and shift.
8
+ *
9
+ * Uses a head-index cursor over a backing array rather than
10
+ * `Array.prototype.shift()`, which is O(n) on V8. Intended for BFS-shaped
11
+ * traversals where the queue is drained in a single pass — it does not
12
+ * reclaim memory for already-shifted items, so it is not suitable for
13
+ * long-lived queues with many push/shift cycles.
14
+ */
15
+ var Queue = class {
16
+ items;
17
+ head = 0;
18
+ constructor(initial = []) {
19
+ this.items = [...initial];
20
+ }
21
+ push(item) {
22
+ this.items.push(item);
23
+ }
24
+ /**
25
+ * Remove and return the next item. Caller must check `isEmpty` first —
26
+ * shifting an empty queue throws.
27
+ */
28
+ shift() {
29
+ if (this.head >= this.items.length) throw new Error("Queue.shift called on empty queue");
30
+ return this.items[this.head++];
31
+ }
32
+ get isEmpty() {
33
+ return this.head >= this.items.length;
34
+ }
35
+ };
36
+
37
+ //#endregion
38
+ //#region src/graph-ops.ts
39
+ /**
40
+ * Generic breadth-first traversal.
41
+ *
42
+ * Direction (forward/reverse) is expressed by the caller's `neighbours`
43
+ * closure: return `{ next, edge }` pairs where `next` is the node to visit
44
+ * next and `edge` is the edge that connects them. Callers that don't need
45
+ * path reconstruction can ignore the `parent`/`incomingEdge` fields of each
46
+ * yielded step.
47
+ *
48
+ * Stops are intrinsic — callers `break` out of the `for..of` loop when
49
+ * they've found what they're looking for.
50
+ *
51
+ * `ordering`, if provided, controls the order in which neighbours of each
52
+ * node are enqueued. Only matters for path-finding: a deterministic ordering
53
+ * makes BFS return a deterministic shortest path when multiple exist.
54
+ */
55
+ function* bfs(starts, neighbours, ordering) {
56
+ const visited = /* @__PURE__ */ new Set();
57
+ const parentMap = /* @__PURE__ */ new Map();
58
+ const queue = new Queue();
59
+ for (const start of starts) if (!visited.has(start)) {
60
+ visited.add(start);
61
+ queue.push(start);
62
+ }
63
+ while (!queue.isEmpty) {
64
+ const current = queue.shift();
65
+ const parentInfo = parentMap.get(current);
66
+ yield {
67
+ node: current,
68
+ parent: parentInfo?.parent ?? null,
69
+ incomingEdge: parentInfo?.edge ?? null
70
+ };
71
+ const items = neighbours(current);
72
+ const toVisit = ordering ? ordering([...items]) : items;
73
+ for (const { next, edge } of toVisit) if (!visited.has(next)) {
74
+ visited.add(next);
75
+ parentMap.set(next, {
76
+ parent: current,
77
+ edge
78
+ });
79
+ queue.push(next);
80
+ }
81
+ }
82
+ }
83
+
84
+ //#endregion
5
85
  //#region src/dag.ts
86
+ /** Forward-edge neighbours for BFS: edge `e` from `n` visits `e.to` next. */
87
+ function forwardNeighbours(graph, node) {
88
+ return (graph.forwardChain.get(node) ?? []).map((edge) => ({
89
+ next: edge.to,
90
+ edge
91
+ }));
92
+ }
93
+ /** Reverse-edge neighbours for BFS: edge `e` from `n` visits `e.from` next. */
94
+ function reverseNeighbours(graph, node) {
95
+ return (graph.reverseChain.get(node) ?? []).map((edge) => ({
96
+ next: edge.from,
97
+ edge
98
+ }));
99
+ }
100
+ function appendEdge(map, key, entry) {
101
+ const bucket = map.get(key);
102
+ if (bucket) bucket.push(entry);
103
+ else map.set(key, [entry]);
104
+ }
6
105
  function reconstructGraph(packages) {
7
106
  const nodes = /* @__PURE__ */ new Set();
8
107
  const forwardChain = /* @__PURE__ */ new Map();
@@ -21,16 +120,10 @@ function reconstructGraph(packages) {
21
120
  createdAt: pkg.manifest.createdAt,
22
121
  labels: pkg.manifest.labels
23
122
  };
24
- if (migration.migrationId !== null) {
25
- if (migrationById.has(migration.migrationId)) throw errorDuplicateMigrationId(migration.migrationId);
26
- migrationById.set(migration.migrationId, migration);
27
- }
28
- const fwd = forwardChain.get(from);
29
- if (fwd) fwd.push(migration);
30
- else forwardChain.set(from, [migration]);
31
- const rev = reverseChain.get(to);
32
- if (rev) rev.push(migration);
33
- else reverseChain.set(to, [migration]);
123
+ if (migrationById.has(migration.migrationId)) throw errorDuplicateMigrationId(migration.migrationId);
124
+ migrationById.set(migration.migrationId, migration);
125
+ appendEdge(forwardChain, from, migration);
126
+ appendEdge(reverseChain, to, migration);
34
127
  }
35
128
  return {
36
129
  nodes,
@@ -52,16 +145,21 @@ function labelPriority(labels) {
52
145
  }
53
146
  return best;
54
147
  }
148
+ function compareTieBreak(a, b) {
149
+ const lp = labelPriority(a.labels) - labelPriority(b.labels);
150
+ if (lp !== 0) return lp;
151
+ const ca = a.createdAt.localeCompare(b.createdAt);
152
+ if (ca !== 0) return ca;
153
+ const tc = a.to.localeCompare(b.to);
154
+ if (tc !== 0) return tc;
155
+ return a.migrationId.localeCompare(b.migrationId);
156
+ }
55
157
  function sortedNeighbors(edges) {
56
- return [...edges].sort((a, b) => {
57
- const lp = labelPriority(a.labels) - labelPriority(b.labels);
58
- if (lp !== 0) return lp;
59
- const ca = a.createdAt.localeCompare(b.createdAt);
60
- if (ca !== 0) return ca;
61
- const tc = a.to.localeCompare(b.to);
62
- if (tc !== 0) return tc;
63
- return (a.migrationId ?? "").localeCompare(b.migrationId ?? "");
64
- });
158
+ return [...edges].sort(compareTieBreak);
159
+ }
160
+ /** Ordering adapter for `bfs` — sorts `{next, edge}` pairs by tie-break. */
161
+ function bfsOrdering(items) {
162
+ return items.slice().sort((a, b) => compareTieBreak(a.edge, b.edge));
65
163
  }
66
164
  /**
67
165
  * Find the shortest path from `fromHash` to `toHash` using BFS over the
@@ -73,40 +171,37 @@ function sortedNeighbors(edges) {
73
171
  */
74
172
  function findPath(graph, fromHash, toHash) {
75
173
  if (fromHash === toHash) return [];
76
- const visited = /* @__PURE__ */ new Set();
77
- const parent = /* @__PURE__ */ new Map();
78
- const queue = [fromHash];
79
- visited.add(fromHash);
80
- while (queue.length > 0) {
81
- const current = queue.shift();
82
- if (current === void 0) break;
83
- if (current === toHash) {
174
+ const parents = /* @__PURE__ */ new Map();
175
+ for (const step of bfs([fromHash], (n) => forwardNeighbours(graph, n), bfsOrdering)) {
176
+ if (step.parent !== null && step.incomingEdge !== null) parents.set(step.node, {
177
+ parent: step.parent,
178
+ edge: step.incomingEdge
179
+ });
180
+ if (step.node === toHash) {
84
181
  const path = [];
85
- let node = toHash;
86
- let entry = parent.get(node);
87
- while (entry) {
88
- const { node: prev, edge } = entry;
89
- path.push(edge);
90
- node = prev;
91
- entry = parent.get(node);
182
+ let cur = toHash;
183
+ let p = parents.get(cur);
184
+ while (p) {
185
+ path.push(p.edge);
186
+ cur = p.parent;
187
+ p = parents.get(cur);
92
188
  }
93
189
  path.reverse();
94
190
  return path;
95
191
  }
96
- const outgoing = graph.forwardChain.get(current);
97
- if (!outgoing) continue;
98
- for (const edge of sortedNeighbors(outgoing)) if (!visited.has(edge.to)) {
99
- visited.add(edge.to);
100
- parent.set(edge.to, {
101
- node: current,
102
- edge
103
- });
104
- queue.push(edge.to);
105
- }
106
192
  }
107
193
  return null;
108
194
  }
109
195
  /**
196
+ * Reverse-BFS from `toHash` over `reverseChain` to collect every node from
197
+ * which `toHash` is reachable (inclusive of `toHash` itself).
198
+ */
199
+ function collectNodesReachingTarget(graph, toHash) {
200
+ const reached = /* @__PURE__ */ new Set();
201
+ for (const step of bfs([toHash], (n) => reverseNeighbours(graph, n))) reached.add(step.node);
202
+ return reached;
203
+ }
204
+ /**
110
205
  * Find the shortest path from `fromHash` to `toHash` and return structured
111
206
  * path-decision metadata for machine-readable output.
112
207
  */
@@ -121,14 +216,13 @@ function findPathWithDecision(graph, fromHash, toHash, refName) {
121
216
  };
122
217
  const path = findPath(graph, fromHash, toHash);
123
218
  if (!path) return null;
219
+ const reachesTarget = collectNodesReachingTarget(graph, toHash);
124
220
  const tieBreakReasons = [];
125
221
  let alternativeCount = 0;
126
222
  for (const edge of path) {
127
223
  const outgoing = graph.forwardChain.get(edge.from);
128
224
  if (outgoing && outgoing.length > 1) {
129
- const reachable = outgoing.filter((e) => {
130
- return findPath(graph, e.to, toHash) !== null || e.to === toHash;
131
- });
225
+ const reachable = outgoing.filter((e) => reachesTarget.has(e.to));
132
226
  if (reachable.length > 1) {
133
227
  alternativeCount += reachable.length - 1;
134
228
  const sorted = sortedNeighbors(reachable);
@@ -154,14 +248,7 @@ function findPathWithDecision(graph, fromHash, toHash, refName) {
154
248
  function findDivergencePoint(graph, fromHash, leaves) {
155
249
  const ancestorSets = leaves.map((leaf) => {
156
250
  const ancestors = /* @__PURE__ */ new Set();
157
- const queue = [leaf];
158
- while (queue.length > 0) {
159
- const current = queue.shift();
160
- if (ancestors.has(current)) continue;
161
- ancestors.add(current);
162
- const incoming = graph.reverseChain.get(current);
163
- if (incoming) for (const edge of incoming) queue.push(edge.from);
164
- }
251
+ for (const step of bfs([leaf], (n) => reverseNeighbours(graph, n))) ancestors.add(step.node);
165
252
  return ancestors;
166
253
  });
167
254
  const commonAncestors = [...ancestorSets[0] ?? []].filter((node) => ancestorSets.every((s) => s.has(node)));
@@ -182,36 +269,26 @@ function findDivergencePoint(graph, fromHash, leaves) {
182
269
  * `fromHash` via forward edges.
183
270
  */
184
271
  function findReachableLeaves(graph, fromHash) {
185
- const visited = /* @__PURE__ */ new Set();
186
- const queue = [fromHash];
187
- visited.add(fromHash);
188
272
  const leaves = [];
189
- while (queue.length > 0) {
190
- const current = queue.shift();
191
- if (current === void 0) break;
192
- const outgoing = graph.forwardChain.get(current);
193
- if (!outgoing || outgoing.length === 0) leaves.push(current);
194
- else for (const edge of outgoing) if (!visited.has(edge.to)) {
195
- visited.add(edge.to);
196
- queue.push(edge.to);
197
- }
198
- }
273
+ for (const step of bfs([fromHash], (n) => forwardNeighbours(graph, n))) if (!graph.forwardChain.get(step.node)?.length) leaves.push(step.node);
199
274
  return leaves;
200
275
  }
201
276
  /**
202
277
  * Find the target contract hash of the migration graph reachable from
203
- * EMPTY_CONTRACT_HASH. Throws NO_INITIAL_MIGRATION if the graph has
204
- * nodes but none originate from the empty hash.
205
- * Throws AMBIGUOUS_TARGET if multiple branch tips exist.
278
+ * EMPTY_CONTRACT_HASH. Returns `null` for a graph that has no target
279
+ * state (either empty, or containing only the root with no outgoing
280
+ * edges). Throws NO_INITIAL_MIGRATION if the graph has nodes but none
281
+ * originate from the empty hash, and AMBIGUOUS_TARGET if multiple
282
+ * branch tips exist.
206
283
  */
207
284
  function findLeaf(graph) {
208
- if (graph.nodes.size === 0) return EMPTY_CONTRACT_HASH;
285
+ if (graph.nodes.size === 0) return null;
209
286
  if (!graph.nodes.has(EMPTY_CONTRACT_HASH)) throw errorNoInitialMigration([...graph.nodes]);
210
287
  const leaves = findReachableLeaves(graph, EMPTY_CONTRACT_HASH);
211
288
  if (leaves.length === 0) {
212
289
  const reachable = [...graph.nodes].filter((n) => n !== EMPTY_CONTRACT_HASH);
213
290
  if (reachable.length > 0) throw errorNoTarget(reachable);
214
- return EMPTY_CONTRACT_HASH;
291
+ return null;
215
292
  }
216
293
  if (leaves.length > 1) {
217
294
  const divergencePoint = findDivergencePoint(graph, EMPTY_CONTRACT_HASH, leaves);
@@ -229,8 +306,7 @@ function findLeaf(graph) {
229
306
  })
230
307
  });
231
308
  }
232
- const leaf = leaves[0];
233
- return leaf !== void 0 ? leaf : EMPTY_CONTRACT_HASH;
309
+ return leaves[0];
234
310
  }
235
311
  /**
236
312
  * Find the latest migration entry by traversing from EMPTY_CONTRACT_HASH
@@ -238,12 +314,9 @@ function findLeaf(graph) {
238
314
  * Throws AMBIGUOUS_TARGET if the graph has multiple branch tips.
239
315
  */
240
316
  function findLatestMigration(graph) {
241
- if (graph.nodes.size === 0) return null;
242
317
  const leafHash = findLeaf(graph);
243
- if (leafHash === EMPTY_CONTRACT_HASH) return null;
244
- const path = findPath(graph, EMPTY_CONTRACT_HASH, leafHash);
245
- if (!path || path.length === 0) return null;
246
- return path[path.length - 1] ?? null;
318
+ if (leafHash === null) return null;
319
+ return findPath(graph, EMPTY_CONTRACT_HASH, leafHash)?.at(-1) ?? null;
247
320
  }
248
321
  function detectCycles(graph) {
249
322
  const WHITE = 0;
@@ -253,30 +326,42 @@ function detectCycles(graph) {
253
326
  const parentMap = /* @__PURE__ */ new Map();
254
327
  const cycles = [];
255
328
  for (const node of graph.nodes) color.set(node, WHITE);
256
- function dfs(u) {
329
+ const stack = [];
330
+ function pushFrame(u) {
257
331
  color.set(u, GRAY);
258
- const outgoing = graph.forwardChain.get(u);
259
- if (outgoing) for (const edge of outgoing) {
260
- const v = edge.to;
261
- if (color.get(v) === GRAY) {
332
+ stack.push({
333
+ node: u,
334
+ outgoing: graph.forwardChain.get(u) ?? [],
335
+ index: 0
336
+ });
337
+ }
338
+ for (const root of graph.nodes) {
339
+ if (color.get(root) !== WHITE) continue;
340
+ parentMap.set(root, null);
341
+ pushFrame(root);
342
+ while (stack.length > 0) {
343
+ const frame = stack[stack.length - 1];
344
+ if (frame.index >= frame.outgoing.length) {
345
+ color.set(frame.node, BLACK);
346
+ stack.pop();
347
+ continue;
348
+ }
349
+ const v = frame.outgoing[frame.index++].to;
350
+ const vColor = color.get(v);
351
+ if (vColor === GRAY) {
262
352
  const cycle = [v];
263
- let cur = u;
353
+ let cur = frame.node;
264
354
  while (cur !== v) {
265
355
  cycle.push(cur);
266
356
  cur = parentMap.get(cur) ?? v;
267
357
  }
268
358
  cycle.reverse();
269
359
  cycles.push(cycle);
270
- } else if (color.get(v) === WHITE) {
271
- parentMap.set(v, u);
272
- dfs(v);
360
+ } else if (vColor === WHITE) {
361
+ parentMap.set(v, frame.node);
362
+ pushFrame(v);
273
363
  }
274
364
  }
275
- color.set(u, BLACK);
276
- }
277
- for (const node of graph.nodes) if (color.get(node) === WHITE) {
278
- parentMap.set(node, null);
279
- dfs(node);
280
365
  }
281
366
  return cycles;
282
367
  }
@@ -290,18 +375,7 @@ function detectOrphans(graph) {
290
375
  for (const edges of graph.forwardChain.values()) for (const edge of edges) allTargets.add(edge.to);
291
376
  for (const node of graph.nodes) if (!allTargets.has(node)) startNodes.push(node);
292
377
  }
293
- const queue = [...startNodes];
294
- for (const hash of queue) reachable.add(hash);
295
- while (queue.length > 0) {
296
- const node = queue.shift();
297
- if (node === void 0) break;
298
- const outgoing = graph.forwardChain.get(node);
299
- if (!outgoing) continue;
300
- for (const migration of outgoing) if (!reachable.has(migration.to)) {
301
- reachable.add(migration.to);
302
- queue.push(migration.to);
303
- }
304
- }
378
+ for (const step of bfs(startNodes, (n) => forwardNeighbours(graph, n))) reachable.add(step.node);
305
379
  const orphans = [];
306
380
  for (const [from, migrations] of graph.forwardChain) if (!reachable.has(from)) orphans.push(...migrations);
307
381
  return orphans;
@@ -1 +1 @@
1
- {"version":3,"file":"dag.mjs","names":["migration: MigrationChainEntry","LABEL_PRIORITY: Record<string, number>","queue: string[]","path: MigrationChainEntry[]","tieBreakReasons: string[]","leaves: string[]","cycles: string[][]","cycle: string[]","startNodes: string[]","orphans: MigrationChainEntry[]"],"sources":["../../src/dag.ts"],"sourcesContent":["import { ifDefined } from '@prisma-next/utils/defined';\nimport { EMPTY_CONTRACT_HASH } from './constants';\nimport {\n errorAmbiguousTarget,\n errorDuplicateMigrationId,\n errorNoInitialMigration,\n errorNoTarget,\n errorSameSourceAndTarget,\n} from './errors';\nimport type { AttestedMigrationBundle, MigrationChainEntry, MigrationGraph } from './types';\n\nexport function reconstructGraph(packages: readonly AttestedMigrationBundle[]): MigrationGraph {\n const nodes = new Set<string>();\n const forwardChain = new Map<string, MigrationChainEntry[]>();\n const reverseChain = new Map<string, MigrationChainEntry[]>();\n const migrationById = new Map<string, MigrationChainEntry>();\n\n for (const pkg of packages) {\n const { from, to } = pkg.manifest;\n\n if (from === to) {\n throw errorSameSourceAndTarget(pkg.dirName, from);\n }\n\n nodes.add(from);\n nodes.add(to);\n\n const migration: MigrationChainEntry = {\n from,\n to,\n migrationId: pkg.manifest.migrationId,\n dirName: pkg.dirName,\n createdAt: pkg.manifest.createdAt,\n labels: pkg.manifest.labels,\n };\n\n if (migration.migrationId !== null) {\n if (migrationById.has(migration.migrationId)) {\n throw errorDuplicateMigrationId(migration.migrationId);\n }\n migrationById.set(migration.migrationId, migration);\n }\n\n const fwd = forwardChain.get(from);\n if (fwd) {\n fwd.push(migration);\n } else {\n forwardChain.set(from, [migration]);\n }\n\n const rev = reverseChain.get(to);\n if (rev) {\n rev.push(migration);\n } else {\n reverseChain.set(to, [migration]);\n }\n }\n\n return { nodes, forwardChain, reverseChain, migrationById };\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 sortedNeighbors(edges: readonly MigrationChainEntry[]): readonly MigrationChainEntry[] {\n return [...edges].sort((a, b) => {\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.migrationId ?? '').localeCompare(b.migrationId ?? '');\n });\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 → migrationId.\n */\nexport function findPath(\n graph: MigrationGraph,\n fromHash: string,\n toHash: string,\n): readonly MigrationChainEntry[] | null {\n if (fromHash === toHash) return [];\n\n const visited = new Set<string>();\n const parent = new Map<string, { node: string; edge: MigrationChainEntry }>();\n const queue: string[] = [fromHash];\n visited.add(fromHash);\n\n while (queue.length > 0) {\n const current = queue.shift();\n if (current === undefined) break;\n\n if (current === toHash) {\n const path: MigrationChainEntry[] = [];\n let node = toHash;\n let entry = parent.get(node);\n while (entry) {\n const { node: prev, edge } = entry;\n path.push(edge);\n node = prev;\n entry = parent.get(node);\n }\n path.reverse();\n return path;\n }\n\n const outgoing = graph.forwardChain.get(current);\n if (!outgoing) continue;\n\n for (const edge of sortedNeighbors(outgoing)) {\n if (!visited.has(edge.to)) {\n visited.add(edge.to);\n parent.set(edge.to, { node: current, edge });\n queue.push(edge.to);\n }\n }\n }\n\n return null;\n}\n\nexport interface PathDecision {\n readonly selectedPath: readonly MigrationChainEntry[];\n readonly fromHash: string;\n readonly toHash: string;\n readonly alternativeCount: number;\n readonly tieBreakReasons: readonly string[];\n readonly refName?: string;\n}\n\n/**\n * Find the shortest path from `fromHash` to `toHash` and return structured\n * path-decision metadata for machine-readable output.\n */\nexport function findPathWithDecision(\n graph: MigrationGraph,\n fromHash: string,\n toHash: string,\n refName?: string,\n): PathDecision | null {\n if (fromHash === toHash) {\n return {\n selectedPath: [],\n fromHash,\n toHash,\n alternativeCount: 0,\n tieBreakReasons: [],\n ...ifDefined('refName', refName),\n };\n }\n\n const path = findPath(graph, fromHash, toHash);\n if (!path) return null;\n\n const tieBreakReasons: string[] = [];\n let alternativeCount = 0;\n\n for (const edge of path) {\n const outgoing = graph.forwardChain.get(edge.from);\n if (outgoing && outgoing.length > 1) {\n const reachable = outgoing.filter((e) => {\n const pathFromE = findPath(graph, e.to, toHash);\n return pathFromE !== null || e.to === toHash;\n });\n if (reachable.length > 1) {\n alternativeCount += reachable.length - 1;\n const sorted = sortedNeighbors(reachable);\n if (sorted[0] && sorted[0].migrationId === edge.migrationId) {\n if (reachable.some((e) => e.migrationId !== edge.migrationId)) {\n tieBreakReasons.push(\n `at ${edge.from}: ${reachable.length} candidates, selected by tie-break`,\n );\n }\n }\n }\n }\n }\n\n return {\n selectedPath: path,\n fromHash,\n toHash,\n alternativeCount,\n tieBreakReasons,\n ...ifDefined('refName', refName),\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 const queue = [leaf];\n while (queue.length > 0) {\n const current = queue.shift() as string;\n if (ancestors.has(current)) continue;\n ancestors.add(current);\n const incoming = graph.reverseChain.get(current);\n if (incoming) {\n for (const edge of incoming) {\n queue.push(edge.from);\n }\n }\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 visited = new Set<string>();\n const queue: string[] = [fromHash];\n visited.add(fromHash);\n const leaves: string[] = [];\n\n while (queue.length > 0) {\n const current = queue.shift();\n if (current === undefined) break;\n const outgoing = graph.forwardChain.get(current);\n\n if (!outgoing || outgoing.length === 0) {\n leaves.push(current);\n } else {\n for (const edge of outgoing) {\n if (!visited.has(edge.to)) {\n visited.add(edge.to);\n queue.push(edge.to);\n }\n }\n }\n }\n\n return leaves;\n}\n\n/**\n * Find the target contract hash of the migration graph reachable from\n * EMPTY_CONTRACT_HASH. Throws NO_INITIAL_MIGRATION if the graph has\n * nodes but none originate from the empty hash.\n * Throws AMBIGUOUS_TARGET if multiple branch tips exist.\n */\nexport function findLeaf(graph: MigrationGraph): string {\n if (graph.nodes.size === 0) {\n return EMPTY_CONTRACT_HASH;\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 EMPTY_CONTRACT_HASH;\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 const leaf = leaves[0];\n return leaf !== undefined ? leaf : EMPTY_CONTRACT_HASH;\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): MigrationChainEntry | null {\n if (graph.nodes.size === 0) {\n return null;\n }\n\n const leafHash = findLeaf(graph);\n if (leafHash === EMPTY_CONTRACT_HASH) {\n return null;\n }\n\n const path = findPath(graph, EMPTY_CONTRACT_HASH, leafHash);\n if (!path || path.length === 0) {\n return null;\n }\n\n return path[path.length - 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 function dfs(u: string): void {\n color.set(u, GRAY);\n\n const outgoing = graph.forwardChain.get(u);\n if (outgoing) {\n for (const edge of outgoing) {\n const v = edge.to;\n if (color.get(v) === GRAY) {\n const cycle: string[] = [v];\n let cur = u;\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 (color.get(v) === WHITE) {\n parentMap.set(v, u);\n dfs(v);\n }\n }\n }\n\n color.set(u, BLACK);\n }\n\n for (const node of graph.nodes) {\n if (color.get(node) === WHITE) {\n parentMap.set(node, null);\n dfs(node);\n }\n }\n\n return cycles;\n}\n\nexport function detectOrphans(graph: MigrationGraph): readonly MigrationChainEntry[] {\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 const queue = [...startNodes];\n for (const hash of queue) {\n reachable.add(hash);\n }\n\n while (queue.length > 0) {\n const node = queue.shift();\n if (node === undefined) break;\n const outgoing = graph.forwardChain.get(node);\n if (!outgoing) continue;\n\n for (const migration of outgoing) {\n if (!reachable.has(migration.to)) {\n reachable.add(migration.to);\n queue.push(migration.to);\n }\n }\n }\n\n const orphans: MigrationChainEntry[] = [];\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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"}
1
+ {"version":3,"file":"dag.mjs","names":["migration: MigrationChainEntry","LABEL_PRIORITY: Record<string, number>","path: MigrationChainEntry[]","tieBreakReasons: string[]","leaves: string[]","cycles: string[][]","stack: Frame[]","cycle: string[]","startNodes: string[]","orphans: MigrationChainEntry[]"],"sources":["../../src/queue.ts","../../src/graph-ops.ts","../../src/dag.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 nodes — they were not\n * reached via any edge. For every other node they record the node and edge\n * by which this node was first reached.\n */\nexport interface BfsStep<E> {\n readonly node: string;\n readonly parent: string | 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 node 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 * Stops are intrinsic — callers `break` out of the `for..of` loop when\n * they've found what they're looking for.\n *\n * `ordering`, if provided, controls the order in which neighbours of each\n * node are enqueued. Only matters for path-finding: a deterministic ordering\n * makes BFS return a deterministic shortest path when multiple exist.\n */\nexport function* bfs<E>(\n starts: Iterable<string>,\n neighbours: (node: string) => Iterable<{ next: string; edge: E }>,\n ordering?: (items: readonly { next: string; edge: E }[]) => readonly { next: string; edge: E }[],\n): Generator<BfsStep<E>> {\n const visited = new Set<string>();\n const parentMap = new Map<string, { parent: string; edge: E }>();\n const queue = new Queue<string>();\n for (const start of starts) {\n if (!visited.has(start)) {\n visited.add(start);\n queue.push(start);\n }\n }\n while (!queue.isEmpty) {\n const current = queue.shift();\n const parentInfo = parentMap.get(current);\n yield {\n node: current,\n parent: parentInfo?.parent ?? null,\n incomingEdge: parentInfo?.edge ?? null,\n };\n\n const items = neighbours(current);\n const toVisit = ordering ? ordering([...items]) : items;\n for (const { next, edge } of toVisit) {\n if (!visited.has(next)) {\n visited.add(next);\n parentMap.set(next, { parent: current, edge });\n queue.push(next);\n }\n }\n }\n}\n","import { ifDefined } from '@prisma-next/utils/defined';\nimport { EMPTY_CONTRACT_HASH } from './constants';\nimport {\n errorAmbiguousTarget,\n errorDuplicateMigrationId,\n errorNoInitialMigration,\n errorNoTarget,\n errorSameSourceAndTarget,\n} from './errors';\nimport { bfs } from './graph-ops';\nimport type { MigrationBundle, MigrationChainEntry, MigrationGraph } from './types';\n\n/** Forward-edge neighbours for BFS: 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/** Reverse-edge neighbours for BFS: 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(\n map: Map<string, MigrationChainEntry[]>,\n key: string,\n entry: MigrationChainEntry,\n): 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 MigrationBundle[]): MigrationGraph {\n const nodes = new Set<string>();\n const forwardChain = new Map<string, MigrationChainEntry[]>();\n const reverseChain = new Map<string, MigrationChainEntry[]>();\n const migrationById = new Map<string, MigrationChainEntry>();\n\n for (const pkg of packages) {\n const { from, to } = pkg.manifest;\n\n if (from === to) {\n throw errorSameSourceAndTarget(pkg.dirName, from);\n }\n\n nodes.add(from);\n nodes.add(to);\n\n const migration: MigrationChainEntry = {\n from,\n to,\n migrationId: pkg.manifest.migrationId,\n dirName: pkg.dirName,\n createdAt: pkg.manifest.createdAt,\n labels: pkg.manifest.labels,\n };\n\n if (migrationById.has(migration.migrationId)) {\n throw errorDuplicateMigrationId(migration.migrationId);\n }\n migrationById.set(migration.migrationId, migration);\n\n appendEdge(forwardChain, from, migration);\n appendEdge(reverseChain, to, migration);\n }\n\n return { nodes, forwardChain, reverseChain, migrationById };\n}\n\n// ---------------------------------------------------------------------------\n// Deterministic tie-breaking for BFS neighbour order.\n// Used by `findPath` and `findPathWithDecision` only; not a general-purpose\n// utility. Ordering: label priority → createdAt → to → migrationId.\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: MigrationChainEntry, b: MigrationChainEntry): 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.migrationId.localeCompare(b.migrationId);\n}\n\nfunction sortedNeighbors(edges: readonly MigrationChainEntry[]): readonly MigrationChainEntry[] {\n return [...edges].sort(compareTieBreak);\n}\n\n/** Ordering adapter for `bfs` — sorts `{next, edge}` pairs by tie-break. */\nfunction bfsOrdering(\n items: readonly { next: string; edge: MigrationChainEntry }[],\n): readonly { next: string; edge: MigrationChainEntry }[] {\n return items.slice().sort((a, b) => compareTieBreak(a.edge, b.edge));\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 → migrationId.\n */\nexport function findPath(\n graph: MigrationGraph,\n fromHash: string,\n toHash: string,\n): readonly MigrationChainEntry[] | null {\n if (fromHash === toHash) return [];\n\n const parents = new Map<string, { parent: string; edge: MigrationChainEntry }>();\n for (const step of bfs([fromHash], (n) => forwardNeighbours(graph, n), bfsOrdering)) {\n if (step.parent !== null && step.incomingEdge !== null) {\n parents.set(step.node, { parent: step.parent, edge: step.incomingEdge });\n }\n if (step.node === toHash) {\n const path: MigrationChainEntry[] = [];\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 * 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.node);\n }\n return reached;\n}\n\nexport interface PathDecision {\n readonly selectedPath: readonly MigrationChainEntry[];\n readonly fromHash: string;\n readonly toHash: string;\n readonly alternativeCount: number;\n readonly tieBreakReasons: readonly string[];\n readonly refName?: string;\n}\n\n/**\n * Find the shortest path from `fromHash` to `toHash` and return structured\n * path-decision metadata for machine-readable output.\n */\nexport function findPathWithDecision(\n graph: MigrationGraph,\n fromHash: string,\n toHash: string,\n refName?: string,\n): PathDecision | null {\n if (fromHash === toHash) {\n return {\n selectedPath: [],\n fromHash,\n toHash,\n alternativeCount: 0,\n tieBreakReasons: [],\n ...ifDefined('refName', refName),\n };\n }\n\n const path = findPath(graph, fromHash, toHash);\n if (!path) return null;\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\n const tieBreakReasons: string[] = [];\n let alternativeCount = 0;\n\n for (const edge of path) {\n const outgoing = graph.forwardChain.get(edge.from);\n if (outgoing && outgoing.length > 1) {\n const reachable = outgoing.filter((e) => reachesTarget.has(e.to));\n if (reachable.length > 1) {\n alternativeCount += reachable.length - 1;\n const sorted = sortedNeighbors(reachable);\n if (sorted[0] && sorted[0].migrationId === edge.migrationId) {\n if (reachable.some((e) => e.migrationId !== edge.migrationId)) {\n tieBreakReasons.push(\n `at ${edge.from}: ${reachable.length} candidates, selected by tie-break`,\n );\n }\n }\n }\n }\n }\n\n return {\n selectedPath: path,\n fromHash,\n toHash,\n alternativeCount,\n tieBreakReasons,\n ...ifDefined('refName', refName),\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.node);\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.node)?.length) {\n leaves.push(step.node);\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): MigrationChainEntry | 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 MigrationChainEntry[];\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 MigrationChainEntry[] {\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.node);\n }\n\n const orphans: MigrationChainEntry[] = [];\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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"}
@@ -1,24 +1,27 @@
1
- import { l as MigrationManifest, r as BaseMigrationBundle, u as MigrationOps } from "../types-D2uX4ql7.mjs";
1
+ import { a as MigrationManifest, o as MigrationOps, t as MigrationBundle } from "../types-DyGXcWWp.mjs";
2
2
 
3
3
  //#region src/io.d.ts
4
4
  declare function writeMigrationPackage(dir: string, manifest: MigrationManifest, ops: MigrationOps): Promise<void>;
5
5
  /**
6
- * Copy the destination contract artifacts (`contract.json` and the
7
- * colocated `contract.d.ts`) into the migration package directory so
8
- * authors of the scaffolded `migration.ts` can import the typed
9
- * contract relative to the migration directory
10
- * (`import type { Contract } from './contract'`).
6
+ * Copy a list of files into `destDir`, optionally renaming each one.
11
7
  *
12
- * A missing `.d.ts` is tolerated (only the `.json` is required) so the
13
- * helper stays usable in tests that hand-roll a bare `contract.json`.
14
- * A missing `contract.json` or any other I/O failure — throws.
8
+ * The destination directory is created (with `recursive: true`) if it
9
+ * does not already exist. Each source path is copied byte-for-byte into
10
+ * `destDir/<destName>`; missing sources throw `ENOENT`. The helper is
11
+ * intentionally generic: callers own the list of files (e.g. a contract
12
+ * emitter's emitted output) and the naming convention (e.g. renaming
13
+ * the destination contract to `end-contract.*` and the source contract
14
+ * to `start-contract.*`).
15
15
  */
16
- declare function copyContractToMigrationDir(packageDir: string, contractJsonPath: string): Promise<void>;
16
+ declare function copyFilesWithRename(destDir: string, files: readonly {
17
+ readonly sourcePath: string;
18
+ readonly destName: string;
19
+ }[]): Promise<void>;
17
20
  declare function writeMigrationManifest(dir: string, manifest: MigrationManifest): Promise<void>;
18
21
  declare function writeMigrationOps(dir: string, ops: MigrationOps): Promise<void>;
19
- declare function readMigrationPackage(dir: string): Promise<BaseMigrationBundle>;
20
- declare function readMigrationsDir(migrationsRoot: string): Promise<readonly BaseMigrationBundle[]>;
22
+ declare function readMigrationPackage(dir: string): Promise<MigrationBundle>;
23
+ declare function readMigrationsDir(migrationsRoot: string): Promise<readonly MigrationBundle[]>;
21
24
  declare function formatMigrationDirName(timestamp: Date, slug: string): string;
22
25
  //#endregion
23
- export { copyContractToMigrationDir, formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationManifest, writeMigrationOps, writeMigrationPackage };
26
+ export { copyFilesWithRename, formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationManifest, writeMigrationOps, writeMigrationPackage };
24
27
  //# sourceMappingURL=io.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;iBAwDsB,qBAAA,wBAEV,wBACL,eACJ;;AAJH;;;;;AA+BA;AAcA;AAOA;AAIA;AAkEA;AAiCgB,iBA5HM,0BAAA,CA4HgC,UAAA,EAAA,MAAA,EAAA,gBAAA,EAAA,MAAA,CAAA,EAzHnD,OAyHmD,CAAA,IAAA,CAAA;iBA9GhC,sBAAA,wBAEV,oBACT;iBAImB,iBAAA,mBAAoC,eAAe;iBAInD,oBAAA,eAAmC,QAAQ;iBAkE3C,iBAAA,0BAEnB,iBAAiB;iBA+BJ,sBAAA,YAAkC"}
1
+ {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;iBAwDsB,qBAAA,wBAEV,wBACL,eACJ;;AAJH;;;;;AA+BA;AAaA;AAOA;AAIA;AAkEA;AAiCgB,iBA3HM,mBAAA,CA2H4B,OAAI,EAAA,MAAA,EAAA,KAAA,EAAA,SAAA;;;MAxHnD;iBAUmB,sBAAA,wBAEV,oBACT;iBAImB,iBAAA,mBAAoC,eAAe;iBAInD,oBAAA,eAAmC,QAAQ;iBAkE3C,iBAAA,0BAEnB,iBAAiB;iBA+BJ,sBAAA,YAAkC"}
@@ -1,3 +1,3 @@
1
- import { a as writeMigrationManifest, i as readMigrationsDir, n as formatMigrationDirName, o as writeMigrationOps, r as readMigrationPackage, s as writeMigrationPackage, t as copyContractToMigrationDir } from "../io-Cun81AIZ.mjs";
1
+ import { a as writeMigrationManifest, i as readMigrationsDir, n as formatMigrationDirName, o as writeMigrationOps, r as readMigrationPackage, s as writeMigrationPackage, t as copyFilesWithRename } from "../io-Cd6GLyjK.mjs";
2
2
 
3
- export { copyContractToMigrationDir, formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationManifest, writeMigrationOps, writeMigrationPackage };
3
+ export { copyFilesWithRename, formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationManifest, writeMigrationOps, writeMigrationPackage };
@@ -2,42 +2,36 @@
2
2
  /**
3
3
  * Utilities for reading/writing `migration.ts` files.
4
4
  *
5
- * Rendering migration.ts source is now the target's responsibility — the CLI
6
- * obtains source strings either from a class-flow planner's
7
- * `plan.renderTypeScript()` or from a descriptor-flow target's
8
- * `migrations.renderDescriptorTypeScript(descriptors, context)`. The helper
9
- * here is limited to file I/O: writing the returned source with the right
10
- * executable bit, probing for existence, and evaluating legacy descriptor-
11
- * flow files.
5
+ * Rendering migration.ts source is the target's responsibility — the CLI
6
+ * obtains source strings from a planner's `plan.renderTypeScript()`. The
7
+ * helper here is limited to file I/O: writing the returned source with the
8
+ * right executable bit and probing for existence.
12
9
  */
13
10
  /**
14
11
  * Writes a pre-rendered `migration.ts` source string to the given package
15
12
  * directory. If the source begins with a shebang, the file is written with
16
13
  * executable permissions (0o755) so it can be run directly via
17
- * `./migration.ts` by the authoring class's `Migration.run(...)` guard.
14
+ * `./migration.ts` the rendered scaffold ends with
15
+ * `MigrationCLI.run(import.meta.url, M)` from
16
+ * `@prisma-next/cli/migration-cli` (re-exported by the postgres facade),
17
+ * which guards on the entrypoint and serializes when the file is the main
18
+ * module.
19
+ *
20
+ * The source is run through prettier before writing so migration renderers
21
+ * can produce structurally-correct but loosely-indented source and rely on
22
+ * a single canonical format on disk. Matches what `@prisma-next/emitter`
23
+ * already does for generated `contract.d.ts`.
18
24
  */
19
25
  declare function writeMigrationTs(packageDir: string, content: string): Promise<void>;
20
26
  /**
21
27
  * Checks whether a migration.ts file exists in the package directory.
22
28
  */
23
29
  declare function hasMigrationTs(packageDir: string): Promise<boolean>;
24
- /**
25
- * Evaluates a descriptor-flow migration.ts file by loading it via native
26
- * Node import. Returns the result of calling the default export (expected
27
- * to be a function returning an array of operation descriptors).
28
- *
29
- * Class-flow migration.ts files use a different shape — their default
30
- * export is a class that extends `Migration` — and are evaluated by the
31
- * target's `emit` capability, not this helper.
32
- *
33
- * Requires Node ≥24 for native TypeScript support.
34
- */
35
- declare function evaluateMigrationTs(packageDir: string): Promise<readonly unknown[]>;
36
30
  //#endregion
37
31
  //#region src/runtime-detection.d.ts
38
32
  type ScaffoldRuntime = 'node' | 'bun' | 'deno';
39
33
  declare function detectScaffoldRuntime(): ScaffoldRuntime;
40
34
  declare function shebangLineFor(runtime: ScaffoldRuntime): string;
41
35
  //#endregion
42
- export { type ScaffoldRuntime, detectScaffoldRuntime, evaluateMigrationTs, hasMigrationTs, shebangLineFor, writeMigrationTs };
36
+ export { type ScaffoldRuntime, detectScaffoldRuntime, hasMigrationTs, shebangLineFor, writeMigrationTs };
43
37
  //# sourceMappingURL=migration-ts.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migration-ts.d.mts","names":[],"sources":["../../src/migration-ts.ts","../../src/runtime-detection.ts"],"sourcesContent":[],"mappings":";;AAuBA;AAYA;AAoBA;;;;ACvDA;AAEA;AAMA;;;;;;;;iBDesB,gBAAA,uCAAuD;;;;iBAYvD,cAAA,sBAAoC;;;;;;;;;;;;iBAoBpC,mBAAA,sBAAyC;;;KCvDnD,eAAA;ADuBU,iBCrBN,qBAAA,CAAA,CDqBoE,ECrB3C,eDqB2C;AAY9D,iBC3BN,cAAA,CD2B0C,OAAO,EC3BzB,eD2ByB,CAAA,EAAA,MAAA"}
1
+ {"version":3,"file":"migration-ts.d.mts","names":[],"sources":["../../src/migration-ts.ts","../../src/runtime-detection.ts"],"sourcesContent":[],"mappings":";;AA8BA;AAsBA;;;;ACpDA;AAEA;AAMA;;;;;;;;;;;;;;;iBDsBsB,gBAAA,uCAAuD;;;;iBAsBvD,cAAA,sBAAoC;;;KCpD9C,eAAA;AD8BU,iBC5BN,qBAAA,CAAA,CD4BoE,EC5B3C,eD4B2C;AAsB9D,iBC5CN,cAAA,CD4C0C,OAAO,EC5CzB,eD4CyB,CAAA,EAAA,MAAA"}