@prisma-next/migration-tools 0.0.1 → 0.3.0-dev.100

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.
@@ -1,5 +1,6 @@
1
- import { i as errorDuplicateMigrationId, l as errorNoRoot, n as errorAmbiguousLeaf, u as errorSelfLoop } from "../errors-DdSjGRqx.mjs";
1
+ import { f as errorNoResolvableLeaf, i as errorDuplicateMigrationId, m as errorSelfLoop, n as errorAmbiguousLeaf, p as errorNoRoot } from "../errors-CqLiJwqA.mjs";
2
2
  import { EMPTY_CONTRACT_HASH } from "@prisma-next/core-control-plane/constants";
3
+ import { ifDefined } from "@prisma-next/utils/defined";
3
4
 
4
5
  //#region src/dag.ts
5
6
  function reconstructGraph(packages) {
@@ -7,7 +8,6 @@ function reconstructGraph(packages) {
7
8
  const forwardChain = /* @__PURE__ */ new Map();
8
9
  const reverseChain = /* @__PURE__ */ new Map();
9
10
  const migrationById = /* @__PURE__ */ new Map();
10
- const childrenByParentId = /* @__PURE__ */ new Map();
11
11
  for (const pkg of packages) {
12
12
  const { from, to } = pkg.manifest;
13
13
  if (from === to) throw errorSelfLoop(pkg.dirName, from);
@@ -17,7 +17,6 @@ function reconstructGraph(packages) {
17
17
  from,
18
18
  to,
19
19
  migrationId: pkg.manifest.migrationId,
20
- parentMigrationId: pkg.manifest.parentMigrationId,
21
20
  dirName: pkg.dirName,
22
21
  createdAt: pkg.manifest.createdAt,
23
22
  labels: pkg.manifest.labels
@@ -26,10 +25,6 @@ function reconstructGraph(packages) {
26
25
  if (migrationById.has(migration.migrationId)) throw errorDuplicateMigrationId(migration.migrationId);
27
26
  migrationById.set(migration.migrationId, migration);
28
27
  }
29
- const parentId = migration.parentMigrationId;
30
- const siblings = childrenByParentId.get(parentId);
31
- if (siblings) siblings.push(migration);
32
- else childrenByParentId.set(parentId, [migration]);
33
28
  const fwd = forwardChain.get(from);
34
29
  if (fwd) fwd.push(migration);
35
30
  else forwardChain.set(from, [migration]);
@@ -41,91 +36,221 @@ function reconstructGraph(packages) {
41
36
  nodes,
42
37
  forwardChain,
43
38
  reverseChain,
44
- migrationById,
45
- childrenByParentId
39
+ migrationById
46
40
  };
47
41
  }
42
+ const LABEL_PRIORITY = {
43
+ main: 0,
44
+ default: 1,
45
+ feature: 2
46
+ };
47
+ function labelPriority(labels) {
48
+ let best = 3;
49
+ for (const l of labels) {
50
+ const p = LABEL_PRIORITY[l];
51
+ if (p !== void 0 && p < best) best = p;
52
+ }
53
+ return best;
54
+ }
55
+ 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
+ });
65
+ }
48
66
  /**
49
- * Walk the parent-migration chain to find the latest migration.
50
- * Returns the migration with no children, or null for an empty graph.
51
- * Throws AMBIGUOUS_LEAF if the chain branches.
67
+ * Find the shortest path from `fromHash` to `toHash` using BFS over the
68
+ * contract-hash graph. Returns the ordered list of edges, or null if no path
69
+ * exists. Returns an empty array when `fromHash === toHash` (no-op).
70
+ *
71
+ * Neighbor ordering is deterministic via the tie-break sort key:
72
+ * label priority → createdAt → to → migrationId.
52
73
  */
53
- function findLatestMigration(graph) {
54
- if (graph.nodes.size === 0) return null;
55
- const roots = graph.childrenByParentId.get(null);
56
- if (!roots || roots.length === 0) throw errorNoRoot([...graph.nodes].sort());
57
- if (roots.length > 1) throw errorAmbiguousLeaf(roots.map((e) => e.to));
58
- let current = roots[0];
59
- if (!current) throw errorNoRoot([...graph.nodes].sort());
60
- for (let depth = 0; depth < graph.migrationById.size + 1 && current; depth++) {
61
- const children = current.migrationId !== null ? graph.childrenByParentId.get(current.migrationId) : void 0;
62
- if (!children || children.length === 0) return current;
63
- if (children.length > 1) throw errorAmbiguousLeaf(children.map((e) => e.to));
64
- current = children[0];
74
+ function findPath(graph, fromHash, toHash) {
75
+ 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) {
84
+ 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);
92
+ }
93
+ path.reverse();
94
+ return path;
95
+ }
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
+ }
65
106
  }
66
- throw errorNoRoot([...graph.nodes].sort());
107
+ return null;
67
108
  }
68
109
  /**
69
- * Find the leaf contract hash of the migration chain.
70
- * Convenience wrapper around findLatestMigration.
110
+ * Find the shortest path from `fromHash` to `toHash` and return structured
111
+ * path-decision metadata for machine-readable output.
71
112
  */
72
- function findLeaf(graph) {
73
- const migration = findLatestMigration(graph);
74
- return migration ? migration.to : EMPTY_CONTRACT_HASH;
113
+ function findPathWithDecision(graph, fromHash, toHash, refName) {
114
+ if (fromHash === toHash) return {
115
+ selectedPath: [],
116
+ fromHash,
117
+ toHash,
118
+ alternativeCount: 0,
119
+ tieBreakReasons: [],
120
+ ...ifDefined("refName", refName)
121
+ };
122
+ const path = findPath(graph, fromHash, toHash);
123
+ if (!path) return null;
124
+ const tieBreakReasons = [];
125
+ let alternativeCount = 0;
126
+ for (const edge of path) {
127
+ const outgoing = graph.forwardChain.get(edge.from);
128
+ if (outgoing && outgoing.length > 1) {
129
+ const reachable = outgoing.filter((e) => {
130
+ return findPath(graph, e.to, toHash) !== null || e.to === toHash;
131
+ });
132
+ if (reachable.length > 1) {
133
+ alternativeCount += reachable.length - 1;
134
+ const sorted = sortedNeighbors(reachable);
135
+ if (sorted[0] && sorted[0].migrationId === edge.migrationId) {
136
+ if (reachable.some((e) => e.migrationId !== edge.migrationId)) tieBreakReasons.push(`at ${edge.from}: ${reachable.length} candidates, selected by tie-break`);
137
+ }
138
+ }
139
+ }
140
+ }
141
+ return {
142
+ selectedPath: path,
143
+ fromHash,
144
+ toHash,
145
+ alternativeCount,
146
+ tieBreakReasons,
147
+ ...ifDefined("refName", refName)
148
+ };
75
149
  }
76
150
  /**
77
- * Find the ordered chain of migrations from `fromHash` to `toHash` by walking the
78
- * parent-migration chain. Returns the sub-sequence of migrations whose cumulative path
79
- * goes from `fromHash` to `toHash`.
80
- *
81
- * This reconstructs the full chain from root to leaf via parent pointers, then
82
- * extracts the segment between the two hashes. This correctly handles revisited
83
- * contract hashes (e.g. A→B→A) because it operates on migrations, not nodes.
151
+ * Walk ancestors of each leaf back from the leaves to find the last node
152
+ * that appears on all paths. Returns `fromHash` if no shared ancestor is found.
84
153
  */
85
- function findPath(graph, fromHash, toHash) {
86
- if (fromHash === toHash) return [];
87
- const chain = buildChain(graph);
88
- if (!chain) return null;
89
- let startIdx = -1;
90
- if (chain.length > 0 && chain[0]?.from === fromHash) startIdx = 0;
91
- else for (let i = chain.length - 1; i >= 0; i--) if (chain[i]?.to === fromHash) {
92
- startIdx = i + 1;
93
- break;
154
+ function findDivergencePoint(graph, fromHash, leaves) {
155
+ const ancestorSets = leaves.map((leaf) => {
156
+ 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
+ }
165
+ return ancestors;
166
+ });
167
+ const commonAncestors = [...ancestorSets[0] ?? []].filter((node) => ancestorSets.every((s) => s.has(node)));
168
+ let deepest = fromHash;
169
+ let deepestDepth = -1;
170
+ for (const ancestor of commonAncestors) {
171
+ const path = findPath(graph, fromHash, ancestor);
172
+ const depth = path ? path.length : 0;
173
+ if (depth > deepestDepth) {
174
+ deepestDepth = depth;
175
+ deepest = ancestor;
176
+ }
94
177
  }
95
- if (startIdx === -1) return null;
96
- let endIdx = -1;
97
- for (let i = chain.length - 1; i >= startIdx; i--) if (chain[i]?.to === toHash) {
98
- endIdx = i + 1;
99
- break;
178
+ return deepest;
179
+ }
180
+ /**
181
+ * Find all leaf nodes reachable from `fromHash` via forward edges.
182
+ * A leaf is a node with no outgoing edges in the graph.
183
+ */
184
+ function findReachableLeaves(graph, fromHash) {
185
+ const visited = /* @__PURE__ */ new Set();
186
+ const queue = [fromHash];
187
+ visited.add(fromHash);
188
+ 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
+ }
100
198
  }
101
- if (endIdx === -1) return null;
102
- return chain.slice(startIdx, endIdx);
199
+ return leaves;
103
200
  }
104
201
  /**
105
- * Build the full ordered chain of migrations from root to leaf by following
106
- * parent pointers. Returns null if the chain cannot be reconstructed
107
- * (e.g. missing root, branches).
202
+ * Find the leaf contract hash of the migration graph reachable from
203
+ * EMPTY_CONTRACT_HASH. Throws NO_ROOT if the graph has nodes but none
204
+ * originate from the empty hash (e.g. root migration was deleted).
205
+ * Throws AMBIGUOUS_LEAF if multiple leaves exist.
108
206
  */
109
- function buildChain(graph) {
110
- const roots = graph.childrenByParentId.get(null);
111
- if (!roots || roots.length !== 1) return null;
112
- const chain = [];
113
- let current = roots[0];
114
- for (let depth = 0; depth < graph.migrationById.size + 1 && current; depth++) {
115
- chain.push(current);
116
- const children = current.migrationId !== null ? graph.childrenByParentId.get(current.migrationId) : void 0;
117
- if (!children || children.length === 0) break;
118
- if (children.length > 1) return null;
119
- current = children[0];
207
+ function findLeaf(graph) {
208
+ if (graph.nodes.size === 0) return EMPTY_CONTRACT_HASH;
209
+ if (!graph.nodes.has(EMPTY_CONTRACT_HASH)) throw errorNoRoot([...graph.nodes]);
210
+ const leaves = findReachableLeaves(graph, EMPTY_CONTRACT_HASH);
211
+ if (leaves.length === 0) {
212
+ const reachable = [...graph.nodes].filter((n) => n !== EMPTY_CONTRACT_HASH);
213
+ if (reachable.length > 0) throw errorNoResolvableLeaf(reachable);
214
+ return EMPTY_CONTRACT_HASH;
215
+ }
216
+ if (leaves.length > 1) {
217
+ const divergencePoint = findDivergencePoint(graph, EMPTY_CONTRACT_HASH, leaves);
218
+ throw errorAmbiguousLeaf(leaves, {
219
+ divergencePoint,
220
+ branches: leaves.map((leaf$1) => {
221
+ return {
222
+ leaf: leaf$1,
223
+ edges: (findPath(graph, divergencePoint, leaf$1) ?? []).map((e) => ({
224
+ dirName: e.dirName,
225
+ from: e.from,
226
+ to: e.to
227
+ }))
228
+ };
229
+ })
230
+ });
120
231
  }
121
- return chain;
232
+ const leaf = leaves[0];
233
+ return leaf !== void 0 ? leaf : EMPTY_CONTRACT_HASH;
234
+ }
235
+ /**
236
+ * Find the latest migration entry by traversing from EMPTY_CONTRACT_HASH
237
+ * to the single leaf. Returns null for an empty graph.
238
+ * Throws AMBIGUOUS_LEAF if the graph has multiple leaves.
239
+ */
240
+ function findLatestMigration(graph) {
241
+ if (graph.nodes.size === 0) return null;
242
+ 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;
122
247
  }
123
248
  function detectCycles(graph) {
124
249
  const WHITE = 0;
125
250
  const GRAY = 1;
126
251
  const BLACK = 2;
127
252
  const color = /* @__PURE__ */ new Map();
128
- const parent = /* @__PURE__ */ new Map();
253
+ const parentMap = /* @__PURE__ */ new Map();
129
254
  const cycles = [];
130
255
  for (const node of graph.nodes) color.set(node, WHITE);
131
256
  function dfs(u) {
@@ -138,19 +263,19 @@ function detectCycles(graph) {
138
263
  let cur = u;
139
264
  while (cur !== v) {
140
265
  cycle.push(cur);
141
- cur = parent.get(cur) ?? v;
266
+ cur = parentMap.get(cur) ?? v;
142
267
  }
143
268
  cycle.reverse();
144
269
  cycles.push(cycle);
145
270
  } else if (color.get(v) === WHITE) {
146
- parent.set(v, u);
271
+ parentMap.set(v, u);
147
272
  dfs(v);
148
273
  }
149
274
  }
150
275
  color.set(u, BLACK);
151
276
  }
152
277
  for (const node of graph.nodes) if (color.get(node) === WHITE) {
153
- parent.set(node, null);
278
+ parentMap.set(node, null);
154
279
  dfs(node);
155
280
  }
156
281
  return cycles;
@@ -158,9 +283,14 @@ function detectCycles(graph) {
158
283
  function detectOrphans(graph) {
159
284
  if (graph.nodes.size === 0) return [];
160
285
  const reachable = /* @__PURE__ */ new Set();
161
- const rootMigrations = graph.childrenByParentId.get(null) ?? [];
162
- const rootHashes = rootMigrations.some((migration) => migration.from === EMPTY_CONTRACT_HASH) ? [EMPTY_CONTRACT_HASH] : [...new Set(rootMigrations.map((migration) => migration.from))];
163
- const queue = rootHashes.length > 0 ? rootHashes : [EMPTY_CONTRACT_HASH];
286
+ const startNodes = [];
287
+ if (graph.forwardChain.has(EMPTY_CONTRACT_HASH)) startNodes.push(EMPTY_CONTRACT_HASH);
288
+ else {
289
+ const allTargets = /* @__PURE__ */ new Set();
290
+ for (const edges of graph.forwardChain.values()) for (const edge of edges) allTargets.add(edge.to);
291
+ for (const node of graph.nodes) if (!allTargets.has(node)) startNodes.push(node);
292
+ }
293
+ const queue = [...startNodes];
164
294
  for (const hash of queue) reachable.add(hash);
165
295
  while (queue.length > 0) {
166
296
  const node = queue.shift();
@@ -178,5 +308,5 @@ function detectOrphans(graph) {
178
308
  }
179
309
 
180
310
  //#endregion
181
- export { detectCycles, detectOrphans, findLatestMigration, findLeaf, findPath, reconstructGraph };
311
+ export { detectCycles, detectOrphans, findLatestMigration, findLeaf, findPath, findPathWithDecision, findReachableLeaves, reconstructGraph };
182
312
  //# sourceMappingURL=dag.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"dag.mjs","names":["migration: MigrationChainEntry","children: readonly MigrationChainEntry[] | undefined","chain: MigrationChainEntry[]","current: MigrationChainEntry | undefined","cycles: string[][]","cycle: string[]","queue: string[]","orphans: MigrationChainEntry[]"],"sources":["../../src/dag.ts"],"sourcesContent":["import { EMPTY_CONTRACT_HASH } from '@prisma-next/core-control-plane/constants';\nimport {\n errorAmbiguousLeaf,\n errorDuplicateMigrationId,\n errorNoRoot,\n errorSelfLoop,\n} from './errors';\nimport type { MigrationChainEntry, MigrationGraph, MigrationPackage } from './types';\n\nexport function reconstructGraph(packages: readonly MigrationPackage[]): 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 const childrenByParentId = new Map<string | null, MigrationChainEntry[]>();\n\n for (const pkg of packages) {\n const { from, to } = pkg.manifest;\n\n if (from === to) {\n throw errorSelfLoop(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 parentMigrationId: pkg.manifest.parentMigrationId,\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 parentId = migration.parentMigrationId;\n const siblings = childrenByParentId.get(parentId);\n if (siblings) {\n siblings.push(migration);\n } else {\n childrenByParentId.set(parentId, [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, childrenByParentId };\n}\n\n/**\n * Walk the parent-migration chain to find the latest migration.\n * Returns the migration with no children, or null for an empty graph.\n * Throws AMBIGUOUS_LEAF if the chain branches.\n */\nexport function findLatestMigration(graph: MigrationGraph): MigrationChainEntry | null {\n if (graph.nodes.size === 0) {\n return null;\n }\n\n const roots = graph.childrenByParentId.get(null);\n if (!roots || roots.length === 0) {\n throw errorNoRoot([...graph.nodes].sort());\n }\n\n if (roots.length > 1) {\n throw errorAmbiguousLeaf(roots.map((e) => e.to));\n }\n\n let current = roots[0];\n if (!current) {\n throw errorNoRoot([...graph.nodes].sort());\n }\n\n for (let depth = 0; depth < graph.migrationById.size + 1 && current; depth++) {\n const children: readonly MigrationChainEntry[] | undefined =\n current.migrationId !== null ? graph.childrenByParentId.get(current.migrationId) : undefined;\n\n if (!children || children.length === 0) {\n return current;\n }\n\n if (children.length > 1) {\n throw errorAmbiguousLeaf(children.map((e) => e.to));\n }\n\n current = children[0];\n }\n\n throw errorNoRoot([...graph.nodes].sort());\n}\n\n/**\n * Find the leaf contract hash of the migration chain.\n * Convenience wrapper around findLatestMigration.\n */\nexport function findLeaf(graph: MigrationGraph): string {\n const migration = findLatestMigration(graph);\n return migration ? migration.to : EMPTY_CONTRACT_HASH;\n}\n\n/**\n * Find the ordered chain of migrations from `fromHash` to `toHash` by walking the\n * parent-migration chain. Returns the sub-sequence of migrations whose cumulative path\n * goes from `fromHash` to `toHash`.\n *\n * This reconstructs the full chain from root to leaf via parent pointers, then\n * extracts the segment between the two hashes. This correctly handles revisited\n * contract hashes (e.g. A→B→A) because it operates on migrations, not nodes.\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 chain = buildChain(graph);\n if (!chain) return null;\n\n let startIdx = -1;\n if (chain.length > 0 && chain[0]?.from === fromHash) {\n startIdx = 0;\n } else {\n for (let i = chain.length - 1; i >= 0; i--) {\n if (chain[i]?.to === fromHash) {\n startIdx = i + 1;\n break;\n }\n }\n }\n\n if (startIdx === -1) return null;\n\n let endIdx = -1;\n for (let i = chain.length - 1; i >= startIdx; i--) {\n if (chain[i]?.to === toHash) {\n endIdx = i + 1;\n break;\n }\n }\n\n if (endIdx === -1) return null;\n\n return chain.slice(startIdx, endIdx);\n}\n\n/**\n * Build the full ordered chain of migrations from root to leaf by following\n * parent pointers. Returns null if the chain cannot be reconstructed\n * (e.g. missing root, branches).\n */\nfunction buildChain(graph: MigrationGraph): readonly MigrationChainEntry[] | null {\n const roots = graph.childrenByParentId.get(null);\n if (!roots || roots.length !== 1) return null;\n\n const chain: MigrationChainEntry[] = [];\n let current: MigrationChainEntry | undefined = roots[0];\n\n for (let depth = 0; depth < graph.migrationById.size + 1 && current; depth++) {\n chain.push(current);\n const children =\n current.migrationId !== null ? graph.childrenByParentId.get(current.migrationId) : undefined;\n if (!children || children.length === 0) break;\n if (children.length > 1) return null;\n current = children[0];\n }\n\n return chain;\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 parent = 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 // Back edge found — reconstruct cycle\n const cycle: string[] = [v];\n let cur = u;\n while (cur !== v) {\n cycle.push(cur);\n cur = parent.get(cur) ?? v;\n }\n cycle.reverse();\n cycles.push(cycle);\n } else if (color.get(v) === WHITE) {\n parent.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 parent.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 rootMigrations = graph.childrenByParentId.get(null) ?? [];\n const emptyRootExists = rootMigrations.some(\n (migration) => migration.from === EMPTY_CONTRACT_HASH,\n );\n const rootHashes = emptyRootExists\n ? [EMPTY_CONTRACT_HASH]\n : [...new Set(rootMigrations.map((migration) => migration.from))];\n const queue: string[] = rootHashes.length > 0 ? rootHashes : [EMPTY_CONTRACT_HASH];\n\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>","queue: string[]","path: MigrationChainEntry[]","tieBreakReasons: string[]","leaves: string[]","leaf","cycles: string[][]","cycle: string[]","startNodes: string[]","orphans: MigrationChainEntry[]"],"sources":["../../src/dag.ts"],"sourcesContent":["import { EMPTY_CONTRACT_HASH } from '@prisma-next/core-control-plane/constants';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport {\n errorAmbiguousLeaf,\n errorDuplicateMigrationId,\n errorNoResolvableLeaf,\n errorNoRoot,\n errorSelfLoop,\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 errorSelfLoop(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 leaf back from the leaves 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 leaf nodes reachable from `fromHash` via forward edges.\n * A leaf is a node with no outgoing edges in the graph.\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 leaf contract hash of the migration graph reachable from\n * EMPTY_CONTRACT_HASH. Throws NO_ROOT if the graph has nodes but none\n * originate from the empty hash (e.g. root migration was deleted).\n * Throws AMBIGUOUS_LEAF if multiple leaves 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 errorNoRoot([...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 errorNoResolvableLeaf(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((leaf) => {\n const path = findPath(graph, divergencePoint, leaf);\n return {\n leaf,\n edges: (path ?? []).map((e) => ({ dirName: e.dirName, from: e.from, to: e.to })),\n };\n });\n throw errorAmbiguousLeaf(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 leaf. Returns null for an empty graph.\n * Throws AMBIGUOUS_LEAF if the graph has multiple leaves.\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":";;;;;AAWA,SAAgB,iBAAiB,UAA8D;CAC7F,MAAM,wBAAQ,IAAI,KAAa;CAC/B,MAAM,+BAAe,IAAI,KAAoC;CAC7D,MAAM,+BAAe,IAAI,KAAoC;CAC7D,MAAM,gCAAgB,IAAI,KAAkC;AAE5D,MAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,EAAE,MAAM,OAAO,IAAI;AAEzB,MAAI,SAAS,GACX,OAAM,cAAc,IAAI,SAAS,KAAK;AAGxC,QAAM,IAAI,KAAK;AACf,QAAM,IAAI,GAAG;EAEb,MAAMA,YAAiC;GACrC;GACA;GACA,aAAa,IAAI,SAAS;GAC1B,SAAS,IAAI;GACb,WAAW,IAAI,SAAS;GACxB,QAAQ,IAAI,SAAS;GACtB;AAED,MAAI,UAAU,gBAAgB,MAAM;AAClC,OAAI,cAAc,IAAI,UAAU,YAAY,CAC1C,OAAM,0BAA0B,UAAU,YAAY;AAExD,iBAAc,IAAI,UAAU,aAAa,UAAU;;EAGrD,MAAM,MAAM,aAAa,IAAI,KAAK;AAClC,MAAI,IACF,KAAI,KAAK,UAAU;MAEnB,cAAa,IAAI,MAAM,CAAC,UAAU,CAAC;EAGrC,MAAM,MAAM,aAAa,IAAI,GAAG;AAChC,MAAI,IACF,KAAI,KAAK,UAAU;MAEnB,cAAa,IAAI,IAAI,CAAC,UAAU,CAAC;;AAIrC,QAAO;EAAE;EAAO;EAAc;EAAc;EAAe;;AAG7D,MAAMC,iBAAyC;CAAE,MAAM;CAAG,SAAS;CAAG,SAAS;CAAG;AAElF,SAAS,cAAc,QAAmC;CACxD,IAAI,OAAO;AACX,MAAK,MAAM,KAAK,QAAQ;EACtB,MAAM,IAAI,eAAe;AACzB,MAAI,MAAM,UAAa,IAAI,KAAM,QAAO;;AAE1C,QAAO;;AAGT,SAAS,gBAAgB,OAAuE;AAC9F,QAAO,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM;EAC/B,MAAM,KAAK,cAAc,EAAE,OAAO,GAAG,cAAc,EAAE,OAAO;AAC5D,MAAI,OAAO,EAAG,QAAO;EACrB,MAAM,KAAK,EAAE,UAAU,cAAc,EAAE,UAAU;AACjD,MAAI,OAAO,EAAG,QAAO;EACrB,MAAM,KAAK,EAAE,GAAG,cAAc,EAAE,GAAG;AACnC,MAAI,OAAO,EAAG,QAAO;AACrB,UAAQ,EAAE,eAAe,IAAI,cAAc,EAAE,eAAe,GAAG;GAC/D;;;;;;;;;;AAWJ,SAAgB,SACd,OACA,UACA,QACuC;AACvC,KAAI,aAAa,OAAQ,QAAO,EAAE;CAElC,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAM,yBAAS,IAAI,KAA0D;CAC7E,MAAMC,QAAkB,CAAC,SAAS;AAClC,SAAQ,IAAI,SAAS;AAErB,QAAO,MAAM,SAAS,GAAG;EACvB,MAAM,UAAU,MAAM,OAAO;AAC7B,MAAI,YAAY,OAAW;AAE3B,MAAI,YAAY,QAAQ;GACtB,MAAMC,OAA8B,EAAE;GACtC,IAAI,OAAO;GACX,IAAI,QAAQ,OAAO,IAAI,KAAK;AAC5B,UAAO,OAAO;IACZ,MAAM,EAAE,MAAM,MAAM,SAAS;AAC7B,SAAK,KAAK,KAAK;AACf,WAAO;AACP,YAAQ,OAAO,IAAI,KAAK;;AAE1B,QAAK,SAAS;AACd,UAAO;;EAGT,MAAM,WAAW,MAAM,aAAa,IAAI,QAAQ;AAChD,MAAI,CAAC,SAAU;AAEf,OAAK,MAAM,QAAQ,gBAAgB,SAAS,CAC1C,KAAI,CAAC,QAAQ,IAAI,KAAK,GAAG,EAAE;AACzB,WAAQ,IAAI,KAAK,GAAG;AACpB,UAAO,IAAI,KAAK,IAAI;IAAE,MAAM;IAAS;IAAM,CAAC;AAC5C,SAAM,KAAK,KAAK,GAAG;;;AAKzB,QAAO;;;;;;AAgBT,SAAgB,qBACd,OACA,UACA,QACA,SACqB;AACrB,KAAI,aAAa,OACf,QAAO;EACL,cAAc,EAAE;EAChB;EACA;EACA,kBAAkB;EAClB,iBAAiB,EAAE;EACnB,GAAG,UAAU,WAAW,QAAQ;EACjC;CAGH,MAAM,OAAO,SAAS,OAAO,UAAU,OAAO;AAC9C,KAAI,CAAC,KAAM,QAAO;CAElB,MAAMC,kBAA4B,EAAE;CACpC,IAAI,mBAAmB;AAEvB,MAAK,MAAM,QAAQ,MAAM;EACvB,MAAM,WAAW,MAAM,aAAa,IAAI,KAAK,KAAK;AAClD,MAAI,YAAY,SAAS,SAAS,GAAG;GACnC,MAAM,YAAY,SAAS,QAAQ,MAAM;AAEvC,WADkB,SAAS,OAAO,EAAE,IAAI,OAAO,KAC1B,QAAQ,EAAE,OAAO;KACtC;AACF,OAAI,UAAU,SAAS,GAAG;AACxB,wBAAoB,UAAU,SAAS;IACvC,MAAM,SAAS,gBAAgB,UAAU;AACzC,QAAI,OAAO,MAAM,OAAO,GAAG,gBAAgB,KAAK,aAC9C;SAAI,UAAU,MAAM,MAAM,EAAE,gBAAgB,KAAK,YAAY,CAC3D,iBAAgB,KACd,MAAM,KAAK,KAAK,IAAI,UAAU,OAAO,oCACtC;;;;;AAOX,QAAO;EACL,cAAc;EACd;EACA;EACA;EACA;EACA,GAAG,UAAU,WAAW,QAAQ;EACjC;;;;;;AAOH,SAAS,oBACP,OACA,UACA,QACQ;CACR,MAAM,eAAe,OAAO,KAAK,SAAS;EACxC,MAAM,4BAAY,IAAI,KAAa;EACnC,MAAM,QAAQ,CAAC,KAAK;AACpB,SAAO,MAAM,SAAS,GAAG;GACvB,MAAM,UAAU,MAAM,OAAO;AAC7B,OAAI,UAAU,IAAI,QAAQ,CAAE;AAC5B,aAAU,IAAI,QAAQ;GACtB,MAAM,WAAW,MAAM,aAAa,IAAI,QAAQ;AAChD,OAAI,SACF,MAAK,MAAM,QAAQ,SACjB,OAAM,KAAK,KAAK,KAAK;;AAI3B,SAAO;GACP;CAEF,MAAM,kBAAkB,CAAC,GAAI,aAAa,MAAM,EAAE,CAAE,CAAC,QAAQ,SAC3D,aAAa,OAAO,MAAM,EAAE,IAAI,KAAK,CAAC,CACvC;CAED,IAAI,UAAU;CACd,IAAI,eAAe;AACnB,MAAK,MAAM,YAAY,iBAAiB;EACtC,MAAM,OAAO,SAAS,OAAO,UAAU,SAAS;EAChD,MAAM,QAAQ,OAAO,KAAK,SAAS;AACnC,MAAI,QAAQ,cAAc;AACxB,kBAAe;AACf,aAAU;;;AAGd,QAAO;;;;;;AAOT,SAAgB,oBAAoB,OAAuB,UAAqC;CAC9F,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAMF,QAAkB,CAAC,SAAS;AAClC,SAAQ,IAAI,SAAS;CACrB,MAAMG,SAAmB,EAAE;AAE3B,QAAO,MAAM,SAAS,GAAG;EACvB,MAAM,UAAU,MAAM,OAAO;AAC7B,MAAI,YAAY,OAAW;EAC3B,MAAM,WAAW,MAAM,aAAa,IAAI,QAAQ;AAEhD,MAAI,CAAC,YAAY,SAAS,WAAW,EACnC,QAAO,KAAK,QAAQ;MAEpB,MAAK,MAAM,QAAQ,SACjB,KAAI,CAAC,QAAQ,IAAI,KAAK,GAAG,EAAE;AACzB,WAAQ,IAAI,KAAK,GAAG;AACpB,SAAM,KAAK,KAAK,GAAG;;;AAM3B,QAAO;;;;;;;;AAST,SAAgB,SAAS,OAA+B;AACtD,KAAI,MAAM,MAAM,SAAS,EACvB,QAAO;AAGT,KAAI,CAAC,MAAM,MAAM,IAAI,oBAAoB,CACvC,OAAM,YAAY,CAAC,GAAG,MAAM,MAAM,CAAC;CAGrC,MAAM,SAAS,oBAAoB,OAAO,oBAAoB;AAE9D,KAAI,OAAO,WAAW,GAAG;EACvB,MAAM,YAAY,CAAC,GAAG,MAAM,MAAM,CAAC,QAAQ,MAAM,MAAM,oBAAoB;AAC3E,MAAI,UAAU,SAAS,EACrB,OAAM,sBAAsB,UAAU;AAExC,SAAO;;AAGT,KAAI,OAAO,SAAS,GAAG;EACrB,MAAM,kBAAkB,oBAAoB,OAAO,qBAAqB,OAAO;AAQ/E,QAAM,mBAAmB,QAAQ;GAAE;GAAiB,UAPnC,OAAO,KAAK,WAAS;AAEpC,WAAO;KACL;KACA,QAHW,SAAS,OAAO,iBAAiBC,OAAK,IAGjC,EAAE,EAAE,KAAK,OAAO;MAAE,SAAS,EAAE;MAAS,MAAM,EAAE;MAAM,IAAI,EAAE;MAAI,EAAE;KACjF;KACD;GAC4D,CAAC;;CAGjE,MAAM,OAAO,OAAO;AACpB,QAAO,SAAS,SAAY,OAAO;;;;;;;AAQrC,SAAgB,oBAAoB,OAAmD;AACrF,KAAI,MAAM,MAAM,SAAS,EACvB,QAAO;CAGT,MAAM,WAAW,SAAS,MAAM;AAChC,KAAI,aAAa,oBACf,QAAO;CAGT,MAAM,OAAO,SAAS,OAAO,qBAAqB,SAAS;AAC3D,KAAI,CAAC,QAAQ,KAAK,WAAW,EAC3B,QAAO;AAGT,QAAO,KAAK,KAAK,SAAS,MAAM;;AAGlC,SAAgB,aAAa,OAA4C;CACvE,MAAM,QAAQ;CACd,MAAM,OAAO;CACb,MAAM,QAAQ;CAEd,MAAM,wBAAQ,IAAI,KAAqB;CACvC,MAAM,4BAAY,IAAI,KAA4B;CAClD,MAAMC,SAAqB,EAAE;AAE7B,MAAK,MAAM,QAAQ,MAAM,MACvB,OAAM,IAAI,MAAM,MAAM;CAGxB,SAAS,IAAI,GAAiB;AAC5B,QAAM,IAAI,GAAG,KAAK;EAElB,MAAM,WAAW,MAAM,aAAa,IAAI,EAAE;AAC1C,MAAI,SACF,MAAK,MAAM,QAAQ,UAAU;GAC3B,MAAM,IAAI,KAAK;AACf,OAAI,MAAM,IAAI,EAAE,KAAK,MAAM;IACzB,MAAMC,QAAkB,CAAC,EAAE;IAC3B,IAAI,MAAM;AACV,WAAO,QAAQ,GAAG;AAChB,WAAM,KAAK,IAAI;AACf,WAAM,UAAU,IAAI,IAAI,IAAI;;AAE9B,UAAM,SAAS;AACf,WAAO,KAAK,MAAM;cACT,MAAM,IAAI,EAAE,KAAK,OAAO;AACjC,cAAU,IAAI,GAAG,EAAE;AACnB,QAAI,EAAE;;;AAKZ,QAAM,IAAI,GAAG,MAAM;;AAGrB,MAAK,MAAM,QAAQ,MAAM,MACvB,KAAI,MAAM,IAAI,KAAK,KAAK,OAAO;AAC7B,YAAU,IAAI,MAAM,KAAK;AACzB,MAAI,KAAK;;AAIb,QAAO;;AAGT,SAAgB,cAAc,OAAuD;AACnF,KAAI,MAAM,MAAM,SAAS,EAAG,QAAO,EAAE;CAErC,MAAM,4BAAY,IAAI,KAAa;CACnC,MAAMC,aAAuB,EAAE;AAE/B,KAAI,MAAM,aAAa,IAAI,oBAAoB,CAC7C,YAAW,KAAK,oBAAoB;MAC/B;EACL,MAAM,6BAAa,IAAI,KAAa;AACpC,OAAK,MAAM,SAAS,MAAM,aAAa,QAAQ,CAC7C,MAAK,MAAM,QAAQ,MACjB,YAAW,IAAI,KAAK,GAAG;AAG3B,OAAK,MAAM,QAAQ,MAAM,MACvB,KAAI,CAAC,WAAW,IAAI,KAAK,CACvB,YAAW,KAAK,KAAK;;CAK3B,MAAM,QAAQ,CAAC,GAAG,WAAW;AAC7B,MAAK,MAAM,QAAQ,MACjB,WAAU,IAAI,KAAK;AAGrB,QAAO,MAAM,SAAS,GAAG;EACvB,MAAM,OAAO,MAAM,OAAO;AAC1B,MAAI,SAAS,OAAW;EACxB,MAAM,WAAW,MAAM,aAAa,IAAI,KAAK;AAC7C,MAAI,CAAC,SAAU;AAEf,OAAK,MAAM,aAAa,SACtB,KAAI,CAAC,UAAU,IAAI,UAAU,GAAG,EAAE;AAChC,aAAU,IAAI,UAAU,GAAG;AAC3B,SAAM,KAAK,UAAU,GAAG;;;CAK9B,MAAMC,UAAiC,EAAE;AACzC,MAAK,MAAM,CAAC,MAAM,eAAe,MAAM,aACrC,KAAI,CAAC,UAAU,IAAI,KAAK,CACtB,SAAQ,KAAK,GAAG,WAAW;AAI/B,QAAO"}
@@ -1,9 +1,9 @@
1
- import { a as MigrationOps, i as MigrationManifest, o as MigrationPackage } from "../types-CUnzoaLY.mjs";
1
+ import { c as MigrationManifest, i as MigrationBundle, l as MigrationOps } from "../types-9YQfIg6N.mjs";
2
2
 
3
3
  //#region src/io.d.ts
4
4
  declare function writeMigrationPackage(dir: string, manifest: MigrationManifest, ops: MigrationOps): Promise<void>;
5
- declare function readMigrationPackage(dir: string): Promise<MigrationPackage>;
6
- declare function readMigrationsDir(migrationsRoot: string): Promise<readonly MigrationPackage[]>;
5
+ declare function readMigrationPackage(dir: string): Promise<MigrationBundle>;
6
+ declare function readMigrationsDir(migrationsRoot: string): Promise<readonly MigrationBundle[]>;
7
7
  declare function formatMigrationDirName(timestamp: Date, slug: string): string;
8
8
  //#endregion
9
9
  export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationPackage };
@@ -1 +1 @@
1
- {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;iBAyDsB,qBAAA,wBAEV,wBACL,eACJ;iBAgBmB,oBAAA,eAAmC,QAAQ;AApB3C,iBAsFA,iBAAA,CAtFqB,cAAA,EAAA,MAAA,CAAA,EAwFxC,OAxFwC,CAAA,SAwFvB,gBAxFuB,EAAA,CAAA;AAE/B,iBAqHI,sBAAA,CArHJ,SAAA,EAqHsC,IArHtC,EAAA,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA"}
1
+ {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;iBAwDsB,qBAAA,wBAEV,wBACL,eACJ;iBAgBmB,oBAAA,eAAmC,QAAQ;AApB3C,iBAsFA,iBAAA,CAtFqB,cAAA,EAAA,MAAA,CAAA,EAwFxC,OAxFwC,CAAA,SAwFvB,eAxFuB,EAAA,CAAA;AAE/B,iBAqHI,sBAAA,CArHJ,SAAA,EAqHsC,IArHtC,EAAA,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA"}
@@ -1,3 +1,3 @@
1
- import { i as writeMigrationPackage, n as readMigrationPackage, r as readMigrationsDir, t as formatMigrationDirName } from "../io-Dx98-h0p.mjs";
1
+ import { i as writeMigrationPackage, n as readMigrationPackage, r as readMigrationsDir, t as formatMigrationDirName } from "../io-afog-e8J.mjs";
2
2
 
3
3
  export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationPackage };
@@ -0,0 +1,10 @@
1
+ //#region src/refs.d.ts
2
+ type Refs = Readonly<Record<string, string>>;
3
+ declare function validateRefName(name: string): boolean;
4
+ declare function validateRefValue(value: string): boolean;
5
+ declare function readRefs(refsPath: string): Promise<Refs>;
6
+ declare function writeRefs(refsPath: string, refs: Refs): Promise<void>;
7
+ declare function resolveRef(refs: Refs, name: string): string;
8
+ //#endregion
9
+ export { type Refs, readRefs, resolveRef, validateRefName, validateRefValue, writeRefs };
10
+ //# sourceMappingURL=refs.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refs.d.mts","names":[],"sources":["../../src/refs.ts"],"sourcesContent":[],"mappings":";KAUY,IAAA,GAAO,SAAS;AAAhB,iBAKI,eAAA,CALW,IAAA,EAAA,MAAA,CAAA,EAAA,OAAA;AAKX,iBAQA,gBAAA,CARe,KAAA,EAAA,MAAA,CAAA,EAAA,OAAA;AAQf,iBAaM,QAAA,CAbU,QAAA,EAAA,MAAA,CAAA,EAakB,OAblB,CAa0B,IAb1B,CAAA;AAaV,iBA0BA,SAAA,CA1B4B,QAAO,EAAA,MAAA,EAAA,IAAA,EA0BD,IA1BC,CAAA,EA0BM,OA1BN,CAAA,IAAA,CAAA;AA0BnC,iBAoBN,UAAA,CApB+C,IAAA,EAoB9B,IApBqC,EAAA,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA"}
@@ -0,0 +1,73 @@
1
+ import { c as errorInvalidRefValue, l as errorInvalidRefs, s as errorInvalidRefName, t as MigrationToolsError } from "../errors-CqLiJwqA.mjs";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import { type } from "arktype";
4
+ import { dirname, join } from "pathe";
5
+
6
+ //#region src/refs.ts
7
+ const REF_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;
8
+ const REF_VALUE_PATTERN = /^sha256:(empty|[0-9a-f]{64})$/;
9
+ function validateRefName(name) {
10
+ if (name.length === 0) return false;
11
+ if (name.includes("..")) return false;
12
+ if (name.includes("//")) return false;
13
+ if (name.startsWith(".")) return false;
14
+ return REF_NAME_PATTERN.test(name);
15
+ }
16
+ function validateRefValue(value) {
17
+ return REF_VALUE_PATTERN.test(value);
18
+ }
19
+ const RefsSchema = type("Record<string, string>").narrow((refs, ctx) => {
20
+ for (const [key, value] of Object.entries(refs)) {
21
+ if (!validateRefName(key)) return ctx.mustBe(`valid ref names (invalid: "${key}")`);
22
+ if (!validateRefValue(value)) return ctx.mustBe(`valid contract hashes (invalid value for "${key}": "${value}")`);
23
+ }
24
+ return true;
25
+ });
26
+ async function readRefs(refsPath) {
27
+ let raw;
28
+ try {
29
+ raw = await readFile(refsPath, "utf-8");
30
+ } catch (error) {
31
+ if (error instanceof Error && error.code === "ENOENT") return {};
32
+ throw error;
33
+ }
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(raw);
37
+ } catch {
38
+ throw errorInvalidRefs(refsPath, "Failed to parse as JSON");
39
+ }
40
+ const result = RefsSchema(parsed);
41
+ if (result instanceof type.errors) throw errorInvalidRefs(refsPath, result.summary);
42
+ return result;
43
+ }
44
+ async function writeRefs(refsPath, refs) {
45
+ for (const [key, value] of Object.entries(refs)) {
46
+ if (!validateRefName(key)) throw errorInvalidRefName(key);
47
+ if (!validateRefValue(value)) throw errorInvalidRefValue(value);
48
+ }
49
+ const sorted = Object.fromEntries(Object.entries(refs).sort(([a], [b]) => a.localeCompare(b)));
50
+ const dir = dirname(refsPath);
51
+ await mkdir(dir, { recursive: true });
52
+ const tmpPath = join(dir, `.refs.json.${Date.now()}.tmp`);
53
+ await writeFile(tmpPath, `${JSON.stringify(sorted, null, 2)}\n`);
54
+ await rename(tmpPath, refsPath);
55
+ }
56
+ function resolveRef(refs, name) {
57
+ if (!validateRefName(name)) throw errorInvalidRefName(name);
58
+ const hash = refs[name];
59
+ if (hash === void 0) throw new MigrationToolsError("MIGRATION.UNKNOWN_REF", `Unknown ref "${name}"`, {
60
+ why: `No ref named "${name}" exists in refs.json.`,
61
+ fix: `Available refs: ${Object.keys(refs).join(", ") || "(none)"}. Create a ref with: set the "${name}" key in migrations/refs.json.`,
62
+ details: {
63
+ refName: name,
64
+ availableRefs: Object.keys(refs)
65
+ }
66
+ });
67
+ if (!validateRefValue(hash)) throw errorInvalidRefValue(hash);
68
+ return hash;
69
+ }
70
+
71
+ //#endregion
72
+ export { readRefs, resolveRef, validateRefName, validateRefValue, writeRefs };
73
+ //# sourceMappingURL=refs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refs.mjs","names":["raw: string","parsed: unknown"],"sources":["../../src/refs.ts"],"sourcesContent":["import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { type } from 'arktype';\nimport { dirname, join } from 'pathe';\nimport {\n errorInvalidRefName,\n errorInvalidRefs,\n errorInvalidRefValue,\n MigrationToolsError,\n} from './errors';\n\nexport type Refs = Readonly<Record<string, string>>;\n\nconst REF_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;\nconst REF_VALUE_PATTERN = /^sha256:(empty|[0-9a-f]{64})$/;\n\nexport function validateRefName(name: string): boolean {\n if (name.length === 0) return false;\n if (name.includes('..')) return false;\n if (name.includes('//')) return false;\n if (name.startsWith('.')) return false;\n return REF_NAME_PATTERN.test(name);\n}\n\nexport function validateRefValue(value: string): boolean {\n return REF_VALUE_PATTERN.test(value);\n}\n\nconst RefsSchema = type('Record<string, string>').narrow((refs, ctx) => {\n for (const [key, value] of Object.entries(refs)) {\n if (!validateRefName(key)) return ctx.mustBe(`valid ref names (invalid: \"${key}\")`);\n if (!validateRefValue(value))\n return ctx.mustBe(`valid contract hashes (invalid value for \"${key}\": \"${value}\")`);\n }\n return true;\n});\n\nexport async function readRefs(refsPath: string): Promise<Refs> {\n let raw: string;\n try {\n raw = await readFile(refsPath, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return {};\n }\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw errorInvalidRefs(refsPath, 'Failed to parse as JSON');\n }\n\n const result = RefsSchema(parsed);\n if (result instanceof type.errors) {\n throw errorInvalidRefs(refsPath, result.summary);\n }\n\n return result;\n}\n\nexport async function writeRefs(refsPath: string, refs: Refs): Promise<void> {\n for (const [key, value] of Object.entries(refs)) {\n if (!validateRefName(key)) {\n throw errorInvalidRefName(key);\n }\n if (!validateRefValue(value)) {\n throw errorInvalidRefValue(value);\n }\n }\n\n const sorted = Object.fromEntries(Object.entries(refs).sort(([a], [b]) => a.localeCompare(b)));\n\n const dir = dirname(refsPath);\n await mkdir(dir, { recursive: true });\n\n const tmpPath = join(dir, `.refs.json.${Date.now()}.tmp`);\n await writeFile(tmpPath, `${JSON.stringify(sorted, null, 2)}\\n`);\n await rename(tmpPath, refsPath);\n}\n\nexport function resolveRef(refs: Refs, name: string): string {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const hash = refs[name];\n if (hash === undefined) {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref named \"${name}\" exists in refs.json.`,\n fix: `Available refs: ${Object.keys(refs).join(', ') || '(none)'}. Create a ref with: set the \"${name}\" key in migrations/refs.json.`,\n details: { refName: name, availableRefs: Object.keys(refs) },\n });\n }\n\n if (!validateRefValue(hash)) {\n throw errorInvalidRefValue(hash);\n }\n\n return hash;\n}\n"],"mappings":";;;;;;AAYA,MAAM,mBAAmB;AACzB,MAAM,oBAAoB;AAE1B,SAAgB,gBAAgB,MAAuB;AACrD,KAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,KAAI,KAAK,SAAS,KAAK,CAAE,QAAO;AAChC,KAAI,KAAK,SAAS,KAAK,CAAE,QAAO;AAChC,KAAI,KAAK,WAAW,IAAI,CAAE,QAAO;AACjC,QAAO,iBAAiB,KAAK,KAAK;;AAGpC,SAAgB,iBAAiB,OAAwB;AACvD,QAAO,kBAAkB,KAAK,MAAM;;AAGtC,MAAM,aAAa,KAAK,yBAAyB,CAAC,QAAQ,MAAM,QAAQ;AACtE,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;AAC/C,MAAI,CAAC,gBAAgB,IAAI,CAAE,QAAO,IAAI,OAAO,8BAA8B,IAAI,IAAI;AACnF,MAAI,CAAC,iBAAiB,MAAM,CAC1B,QAAO,IAAI,OAAO,6CAA6C,IAAI,MAAM,MAAM,IAAI;;AAEvF,QAAO;EACP;AAEF,eAAsB,SAAS,UAAiC;CAC9D,IAAIA;AACJ,KAAI;AACF,QAAM,MAAM,SAAS,UAAU,QAAQ;UAChC,OAAO;AACd,MAAI,iBAAiB,SAAU,MAA4B,SAAS,SAClE,QAAO,EAAE;AAEX,QAAM;;CAGR,IAAIC;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AACN,QAAM,iBAAiB,UAAU,0BAA0B;;CAG7D,MAAM,SAAS,WAAW,OAAO;AACjC,KAAI,kBAAkB,KAAK,OACzB,OAAM,iBAAiB,UAAU,OAAO,QAAQ;AAGlD,QAAO;;AAGT,eAAsB,UAAU,UAAkB,MAA2B;AAC3E,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;AAC/C,MAAI,CAAC,gBAAgB,IAAI,CACvB,OAAM,oBAAoB,IAAI;AAEhC,MAAI,CAAC,iBAAiB,MAAM,CAC1B,OAAM,qBAAqB,MAAM;;CAIrC,MAAM,SAAS,OAAO,YAAY,OAAO,QAAQ,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;CAE9F,MAAM,MAAM,QAAQ,SAAS;AAC7B,OAAM,MAAM,KAAK,EAAE,WAAW,MAAM,CAAC;CAErC,MAAM,UAAU,KAAK,KAAK,cAAc,KAAK,KAAK,CAAC,MAAM;AACzD,OAAM,UAAU,SAAS,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,IAAI;AAChE,OAAM,OAAO,SAAS,SAAS;;AAGjC,SAAgB,WAAW,MAAY,MAAsB;AAC3D,KAAI,CAAC,gBAAgB,KAAK,CACxB,OAAM,oBAAoB,KAAK;CAGjC,MAAM,OAAO,KAAK;AAClB,KAAI,SAAS,OACX,OAAM,IAAI,oBAAoB,yBAAyB,gBAAgB,KAAK,IAAI;EAC9E,KAAK,iBAAiB,KAAK;EAC3B,KAAK,mBAAmB,OAAO,KAAK,KAAK,CAAC,KAAK,KAAK,IAAI,SAAS,gCAAgC,KAAK;EACtG,SAAS;GAAE,SAAS;GAAM,eAAe,OAAO,KAAK,KAAK;GAAE;EAC7D,CAAC;AAGJ,KAAI,CAAC,iBAAiB,KAAK,CACzB,OAAM,qBAAqB,KAAK;AAGlC,QAAO"}
@@ -1,4 +1,4 @@
1
- import { a as MigrationOps, i as MigrationManifest, n as MigrationGraph, o as MigrationPackage, r as MigrationHints, t as MigrationChainEntry } from "../types-CUnzoaLY.mjs";
1
+ import { a as MigrationChainEntry, c as MigrationManifest, i as MigrationBundle, l as MigrationOps, n as AttestedMigrationManifest, o as MigrationGraph, r as DraftMigrationManifest, s as MigrationHints, t as AttestedMigrationBundle, u as isAttested } from "../types-9YQfIg6N.mjs";
2
2
 
3
3
  //#region src/errors.d.ts
4
4
 
@@ -31,5 +31,5 @@ declare class MigrationToolsError extends Error {
31
31
  static is(error: unknown): error is MigrationToolsError;
32
32
  }
33
33
  //#endregion
34
- export { type MigrationChainEntry, type MigrationGraph, type MigrationHints, type MigrationManifest, type MigrationOps, type MigrationPackage, MigrationToolsError };
34
+ export { type AttestedMigrationBundle, type AttestedMigrationManifest, type DraftMigrationManifest, type MigrationBundle, type MigrationBundle as MigrationPackage, type MigrationChainEntry, type MigrationGraph, type MigrationHints, type MigrationManifest, type MigrationOps, MigrationToolsError, isAttested };
35
35
  //# sourceMappingURL=types.d.mts.map
@@ -1,3 +1,14 @@
1
- import { t as MigrationToolsError } from "../errors-DdSjGRqx.mjs";
1
+ import { t as MigrationToolsError } from "../errors-CqLiJwqA.mjs";
2
2
 
3
- export { MigrationToolsError };
3
+ //#region src/types.ts
4
+ /**
5
+ * Type guard that narrows a MigrationBundle to an AttestedMigrationBundle.
6
+ * Use with `.filter(isAttested)` to get a typed array of attested bundles.
7
+ */
8
+ function isAttested(bundle) {
9
+ return typeof bundle.manifest.migrationId === "string";
10
+ }
11
+
12
+ //#endregion
13
+ export { MigrationToolsError, isAttested };
14
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../../src/types.ts"],"sourcesContent":["import type { ContractIR } from '@prisma-next/contract/ir';\nimport type { MigrationPlanOperation } from '@prisma-next/core-control-plane/types';\n\nexport interface MigrationHints {\n readonly used: readonly string[];\n readonly applied: readonly string[];\n readonly plannerVersion: string;\n readonly planningStrategy: string;\n}\n\n/**\n * Shared fields for all migration manifests (draft and attested).\n */\ninterface MigrationManifestBase {\n readonly from: string;\n readonly to: string;\n readonly kind: 'regular' | 'baseline';\n readonly fromContract: ContractIR | null;\n readonly toContract: ContractIR;\n readonly hints: MigrationHints;\n readonly labels: readonly string[];\n readonly authorship?: { readonly author?: string; readonly email?: string };\n readonly signature?: { readonly keyId: string; readonly value: string } | null;\n readonly createdAt: string;\n}\n\n/**\n * A draft migration that has been planned but not yet attested.\n * Draft migrations have `migrationId: null` and are excluded from\n * graph reconstruction and apply.\n */\nexport interface DraftMigrationManifest extends MigrationManifestBase {\n readonly migrationId: null;\n}\n\n/**\n * An attested migration with a content-addressed migrationId.\n * Only attested migrations participate in the migration graph.\n */\nexport interface AttestedMigrationManifest extends MigrationManifestBase {\n readonly migrationId: string;\n}\n\n/**\n * Union of draft and attested manifests. This is what the on-disk\n * format represents — `migrationId` is `null` for drafts, a string\n * for attested migrations.\n */\nexport type MigrationManifest = DraftMigrationManifest | AttestedMigrationManifest;\n\nexport type MigrationOps = readonly MigrationPlanOperation[];\n\n/**\n * An on-disk migration directory containing a manifest and operations.\n * The manifest may be draft or attested.\n */\nexport interface MigrationBundle {\n readonly dirName: string;\n readonly dirPath: string;\n readonly manifest: MigrationManifest;\n readonly ops: MigrationOps;\n}\n\n/**\n * A bundle known to be attested (migrationId is a string).\n * Use this after filtering bundles to attested-only.\n */\nexport interface AttestedMigrationBundle extends MigrationBundle {\n readonly manifest: AttestedMigrationManifest;\n}\n\n/**\n * An entry in the migration graph. Only attested migrations appear in the\n * graph, so `migrationId` is always a string.\n */\nexport interface MigrationChainEntry {\n readonly from: string;\n readonly to: string;\n readonly migrationId: string;\n readonly dirName: string;\n readonly createdAt: string;\n readonly labels: readonly string[];\n}\n\nexport interface MigrationGraph {\n readonly nodes: ReadonlySet<string>;\n readonly forwardChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;\n readonly reverseChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;\n readonly migrationById: ReadonlyMap<string, MigrationChainEntry>;\n}\n\n/**\n * Type guard that narrows a MigrationBundle to an AttestedMigrationBundle.\n * Use with `.filter(isAttested)` to get a typed array of attested bundles.\n */\nexport function isAttested(bundle: MigrationBundle): bundle is AttestedMigrationBundle {\n return typeof bundle.manifest.migrationId === 'string';\n}\n"],"mappings":";;;;;;;AA+FA,SAAgB,WAAW,QAA4D;AACrF,QAAO,OAAO,OAAO,SAAS,gBAAgB"}
@@ -1,4 +1,4 @@
1
- import { a as errorInvalidJson, c as errorMissingFile, o as errorInvalidManifest, r as errorDirectoryExists, s as errorInvalidSlug } from "./errors-DdSjGRqx.mjs";
1
+ import { a as errorInvalidJson, d as errorMissingFile, o as errorInvalidManifest, r as errorDirectoryExists, u as errorInvalidSlug } from "./errors-CqLiJwqA.mjs";
2
2
  import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
3
3
  import { type } from "arktype";
4
4
  import { basename, dirname, join } from "pathe";
@@ -14,7 +14,6 @@ const MigrationManifestSchema = type({
14
14
  from: "string",
15
15
  to: "string",
16
16
  migrationId: "string | null",
17
- parentMigrationId: "string | null",
18
17
  kind: "'regular' | 'baseline'",
19
18
  fromContract: "object | null",
20
19
  toContract: "object",
@@ -128,4 +127,4 @@ function formatMigrationDirName(timestamp, slug) {
128
127
 
129
128
  //#endregion
130
129
  export { writeMigrationPackage as i, readMigrationPackage as n, readMigrationsDir as r, formatMigrationDirName as t };
131
- //# sourceMappingURL=io-Dx98-h0p.mjs.map
130
+ //# sourceMappingURL=io-afog-e8J.mjs.map