@prisma-next/migration-tools 0.3.0-dev.99 → 0.4.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +10 -8
  2. package/dist/constants-DARNL_LD.mjs +10 -0
  3. package/dist/constants-DARNL_LD.mjs.map +1 -0
  4. package/dist/{errors-CqLiJwqA.mjs → errors-C_XuSbX7.mjs} +17 -17
  5. package/dist/errors-C_XuSbX7.mjs.map +1 -0
  6. package/dist/exports/attestation.d.mts +1 -1
  7. package/dist/exports/attestation.d.mts.map +1 -1
  8. package/dist/exports/attestation.mjs +4 -7
  9. package/dist/exports/attestation.mjs.map +1 -1
  10. package/dist/exports/constants.d.mts +9 -0
  11. package/dist/exports/constants.d.mts.map +1 -0
  12. package/dist/exports/constants.mjs +3 -0
  13. package/dist/exports/dag.d.mts +9 -9
  14. package/dist/exports/dag.mjs +18 -18
  15. package/dist/exports/dag.mjs.map +1 -1
  16. package/dist/exports/io.d.mts +6 -4
  17. package/dist/exports/io.d.mts.map +1 -1
  18. package/dist/exports/io.mjs +2 -2
  19. package/dist/exports/migration-ts.d.mts +34 -0
  20. package/dist/exports/migration-ts.d.mts.map +1 -0
  21. package/dist/exports/migration-ts.mjs +125 -0
  22. package/dist/exports/migration-ts.mjs.map +1 -0
  23. package/dist/exports/migration.d.mts +28 -0
  24. package/dist/exports/migration.d.mts.map +1 -0
  25. package/dist/exports/migration.mjs +105 -0
  26. package/dist/exports/migration.mjs.map +1 -0
  27. package/dist/exports/refs.mjs +1 -1
  28. package/dist/exports/types.d.mts +3 -3
  29. package/dist/exports/types.mjs +5 -2
  30. package/dist/exports/types.mjs.map +1 -1
  31. package/dist/{io-afog-e8J.mjs → io-BO18-Evu.mjs} +10 -4
  32. package/dist/io-BO18-Evu.mjs.map +1 -0
  33. package/dist/{types-9YQfIg6N.d.mts → types-DXjq7Fum.d.mts} +13 -9
  34. package/dist/types-DXjq7Fum.d.mts.map +1 -0
  35. package/package.json +18 -6
  36. package/src/attestation.ts +3 -5
  37. package/src/constants.ts +5 -0
  38. package/src/dag.ts +21 -21
  39. package/src/errors.ts +35 -27
  40. package/src/exports/constants.ts +1 -0
  41. package/src/exports/io.ts +2 -0
  42. package/src/exports/migration-ts.ts +6 -0
  43. package/src/exports/migration.ts +1 -0
  44. package/src/exports/types.ts +5 -3
  45. package/src/io.ts +16 -5
  46. package/src/migration-base.ts +141 -0
  47. package/src/migration-ts.ts +199 -0
  48. package/src/types.ts +15 -7
  49. package/dist/errors-CqLiJwqA.mjs.map +0 -1
  50. package/dist/io-afog-e8J.mjs.map +0 -1
  51. package/dist/types-9YQfIg6N.d.mts.map +0 -1
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # @prisma-next/migration-tools
2
2
 
3
- On-disk migration persistence, attestation, and chain reconstruction for Prisma Next.
3
+ On-disk migration persistence, attestation, and history reconstruction for Prisma Next.
4
4
 
5
5
  ## Responsibilities
6
6
 
7
7
  - **Types**: Define the on-disk migration format (`MigrationManifest`, `MigrationOps`, `MigrationPackage`, `MigrationGraph`)
8
8
  - **I/O**: Read and write migration packages to/from disk (`migration.json` + `ops.json`)
9
9
  - **Attestation**: Compute and verify content-addressed migration IDs for tamper detection
10
- - **Chain reconstruction**: Reconstruct and navigate migration history (path finding, latest migration detection, cycle/orphan detection)
10
+ - **History reconstruction**: Reconstruct and navigate migration history (path finding, latest migration detection, cycle/orphan detection)
11
11
 
12
12
  ## Attestation framing
13
13
 
@@ -34,21 +34,22 @@ Full semantic validation happens in target/family migration planners and runners
34
34
  graph TD
35
35
  CLI["CLI commands<br/>(migration plan, apply, verify, show, status)"] --> IO["io.ts<br/>File I/O"]
36
36
  CLI --> ATT["attestation.ts<br/>Migration attestation"]
37
- CLI --> DAG["dag.ts<br/>Chain operations"]
37
+ CLI --> GRAPH["dag.ts<br/>Graph operations"]
38
38
  IO --> TYPES["types.ts<br/>MigrationManifest, etc."]
39
39
  ATT --> IO
40
40
  ATT --> CAN["canonicalize-json.ts"]
41
- ATT --> CP["@prisma-next/core-control-plane<br/>canonicalizeContract"]
42
- DAG --> TYPES
43
- DAG --> ABS["@prisma-next/core-control-plane<br/>EMPTY_CONTRACT_HASH"]
41
+ ATT --> CP["@prisma-next/emitter<br/>canonicalizeContract"]
42
+ GRAPH --> TYPES
43
+ GRAPH --> ABS["@prisma-next/migration-tools/constants<br/>EMPTY_CONTRACT_HASH"]
44
44
  ```
45
45
 
46
46
  ## Dependencies
47
47
 
48
48
  | Package | Why |
49
49
  |---|---|
50
- | `@prisma-next/contract` | `ContractIR` type for embedded contracts in manifests |
51
- | `@prisma-next/core-control-plane` | `MigrationPlanOperation` types, `EMPTY_CONTRACT_HASH`, `canonicalizeContract` |
50
+ | `@prisma-next/contract` | `Contract` type for embedded contracts in manifests |
51
+ | `@prisma-next/framework-components` | `MigrationPlanOperation` types (via `./control`) |
52
+ | `@prisma-next/emitter` | `canonicalizeContract` |
52
53
  | `arktype` | Runtime shape validation for `migration.json` and `ops.json` |
53
54
  | `@prisma-next/utils` | Workspace utility dependency (currently no direct runtime imports in this package) |
54
55
  | `pathe` | Cross-platform path manipulation |
@@ -65,6 +66,7 @@ graph TD
65
66
  | `./io` | `writeMigrationPackage`, `readMigrationPackage`, `readMigrationsDir`, `formatMigrationDirName` |
66
67
  | `./attestation` | `computeMigrationId`, `attestMigration`, `verifyMigration` |
67
68
  | `./dag` | `reconstructGraph`, `findLeaf`, `findPath`, `detectCycles`, `detectOrphans` |
69
+ | `./constants` | `EMPTY_CONTRACT_HASH` |
68
70
 
69
71
  ## On-Disk Format
70
72
 
@@ -0,0 +1,10 @@
1
+ //#region src/constants.ts
2
+ /**
3
+ * Sentinel value representing the absence of a contract (empty/new project).
4
+ * This is a human-readable marker, not a real SHA-256 hash.
5
+ */
6
+ const EMPTY_CONTRACT_HASH = "sha256:empty";
7
+
8
+ //#endregion
9
+ export { EMPTY_CONTRACT_HASH as t };
10
+ //# sourceMappingURL=constants-DARNL_LD.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants-DARNL_LD.mjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/**\n * Sentinel value representing the absence of a contract (empty/new project).\n * This is a human-readable marker, not a real SHA-256 hash.\n */\nexport const EMPTY_CONTRACT_HASH = 'sha256:empty' as const;\n"],"mappings":";;;;;AAIA,MAAa,sBAAsB"}
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Follows the NAMESPACE.SUBCODE convention from ADR 027. All codes live under
6
6
  * the MIGRATION namespace. These are tooling-time errors (file I/O, attestation,
7
- * migration-chain reconstruction), distinct from the runtime MIGRATION.* codes for apply-time
7
+ * migration history reconstruction), distinct from the runtime MIGRATION.* codes for apply-time
8
8
  * failures (PRECHECK_FAILED, POSTCHECK_FAILED, etc.).
9
9
  *
10
10
  * Fields:
@@ -78,8 +78,8 @@ function errorInvalidSlug(slug) {
78
78
  details: { slug }
79
79
  });
80
80
  }
81
- function errorSelfLoop(dirName, hash) {
82
- return new MigrationToolsError("MIGRATION.SELF_LOOP", "Self-loop in migration graph", {
81
+ function errorSameSourceAndTarget(dirName, hash) {
82
+ return new MigrationToolsError("MIGRATION.SAME_SOURCE_AND_TARGET", "Migration has same source and target", {
83
83
  why: `Migration "${dirName}" has from === to === "${hash}". A migration must transition between two different contract states.`,
84
84
  fix: "Delete the invalid migration directory and re-run migration plan.",
85
85
  details: {
@@ -88,13 +88,13 @@ function errorSelfLoop(dirName, hash) {
88
88
  }
89
89
  });
90
90
  }
91
- function errorAmbiguousLeaf(leaves, context) {
92
- const divergenceInfo = context ? `\nDivergence point: ${context.divergencePoint}\nBranches:\n${context.branches.map((b) => ` → ${b.leaf} (${b.edges.length} edge(s): ${b.edges.map((e) => e.dirName).join(" → ") || "direct"})`).join("\n")}` : "";
93
- return new MigrationToolsError("MIGRATION.AMBIGUOUS_LEAF", "Ambiguous migration graph", {
94
- why: `Multiple leaf nodes found: ${leaves.join(", ")}. The migration graph has diverged — this typically happens when two developers plan migrations from the same starting point.${divergenceInfo}`,
91
+ function errorAmbiguousTarget(branchTips, context) {
92
+ const divergenceInfo = context ? `\nDivergence point: ${context.divergencePoint}\nBranches:\n${context.branches.map((b) => ` → ${b.tip} (${b.edges.length} edge(s): ${b.edges.map((e) => e.dirName).join(" → ") || "direct"})`).join("\n")}` : "";
93
+ return new MigrationToolsError("MIGRATION.AMBIGUOUS_TARGET", "Ambiguous migration target", {
94
+ why: `The migration history has diverged into multiple branches: ${branchTips.join(", ")}. This typically happens when two developers plan migrations from the same starting point.${divergenceInfo}`,
95
95
  fix: "Use `migration ref set <name> <hash>` to target a specific branch, delete one of the conflicting migration directories and re-run `migration plan`, or use --from <hash> to explicitly select a starting point.",
96
96
  details: {
97
- leaves,
97
+ branchTips,
98
98
  ...context ? {
99
99
  divergencePoint: context.divergencePoint,
100
100
  branches: context.branches
@@ -102,9 +102,9 @@ function errorAmbiguousLeaf(leaves, context) {
102
102
  }
103
103
  });
104
104
  }
105
- function errorNoRoot(nodes) {
106
- return new MigrationToolsError("MIGRATION.NO_ROOT", "Migration graph has no root", {
107
- why: `No root migration found in the migration graph (nodes: ${nodes.join(", ")}). No migration starts from the empty contract hash, or all edges form a disconnected subgraph.`,
105
+ function errorNoInitialMigration(nodes) {
106
+ return new MigrationToolsError("MIGRATION.NO_INITIAL_MIGRATION", "No initial migration found", {
107
+ why: `No migration starts from the empty contract state (known hashes: ${nodes.join(", ")}). At least one migration must originate from the empty state.`,
108
108
  fix: "Inspect the migrations directory for corrupted migration.json files. At least one migration must start from the empty contract hash.",
109
109
  details: { nodes }
110
110
  });
@@ -126,11 +126,11 @@ function errorInvalidRefName(refName) {
126
126
  details: { refName }
127
127
  });
128
128
  }
129
- function errorNoResolvableLeaf(reachableNodes) {
130
- return new MigrationToolsError("MIGRATION.NO_RESOLVABLE_LEAF", "Migration graph has no resolvable leaf", {
131
- why: `The migration graph contains cycles and no node has zero outgoing edges (reachable nodes: ${reachableNodes.join(", ")}). This typically happens after rollback migrations (e.g., C1→C2→C1).`,
129
+ function errorNoTarget(reachableHashes) {
130
+ return new MigrationToolsError("MIGRATION.NO_TARGET", "No migration target could be resolved", {
131
+ why: `The migration history contains cycles and no target can be resolved automatically (reachable hashes: ${reachableHashes.join(", ")}). This typically happens after rollback migrations (e.g., C1→C2→C1).`,
132
132
  fix: "Use --from <hash> to specify the planning origin explicitly.",
133
- details: { reachableNodes }
133
+ details: { reachableHashes }
134
134
  });
135
135
  }
136
136
  function errorInvalidRefValue(value) {
@@ -149,5 +149,5 @@ function errorDuplicateMigrationId(migrationId) {
149
149
  }
150
150
 
151
151
  //#endregion
152
- export { errorInvalidJson as a, errorInvalidRefValue as c, errorMissingFile as d, errorNoResolvableLeaf as f, errorDuplicateMigrationId as i, errorInvalidRefs as l, errorSelfLoop as m, errorAmbiguousLeaf as n, errorInvalidManifest as o, errorNoRoot as p, errorDirectoryExists as r, errorInvalidRefName as s, MigrationToolsError as t, errorInvalidSlug as u };
153
- //# sourceMappingURL=errors-CqLiJwqA.mjs.map
152
+ export { errorInvalidJson as a, errorInvalidRefValue as c, errorMissingFile as d, errorNoInitialMigration as f, errorDuplicateMigrationId as i, errorInvalidRefs as l, errorSameSourceAndTarget as m, errorAmbiguousTarget as n, errorInvalidManifest as o, errorNoTarget as p, errorDirectoryExists as r, errorInvalidRefName as s, MigrationToolsError as t, errorInvalidSlug as u };
153
+ //# sourceMappingURL=errors-C_XuSbX7.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-C_XuSbX7.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Structured error for migration tooling operations.\n *\n * Follows the NAMESPACE.SUBCODE convention from ADR 027. All codes live under\n * the MIGRATION namespace. These are tooling-time errors (file I/O, attestation,\n * migration history reconstruction), distinct from the runtime MIGRATION.* codes for apply-time\n * failures (PRECHECK_FAILED, POSTCHECK_FAILED, etc.).\n *\n * Fields:\n * - code: Stable machine-readable code (MIGRATION.SUBCODE)\n * - category: Always 'MIGRATION'\n * - why: Explains the cause in plain language\n * - fix: Actionable remediation step\n * - details: Machine-readable structured data for agents\n */\nexport class MigrationToolsError extends Error {\n readonly code: string;\n readonly category = 'MIGRATION' as const;\n readonly why: string;\n readonly fix: string;\n readonly details: Record<string, unknown> | undefined;\n\n constructor(\n code: string,\n summary: string,\n options: {\n readonly why: string;\n readonly fix: string;\n readonly details?: Record<string, unknown>;\n },\n ) {\n super(summary);\n this.name = 'MigrationToolsError';\n this.code = code;\n this.why = options.why;\n this.fix = options.fix;\n this.details = options.details;\n }\n\n static is(error: unknown): error is MigrationToolsError {\n if (!(error instanceof Error)) return false;\n const candidate = error as MigrationToolsError;\n return candidate.name === 'MigrationToolsError' && typeof candidate.code === 'string';\n }\n}\n\nexport function errorDirectoryExists(dir: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.DIR_EXISTS', 'Migration directory already exists', {\n why: `The directory \"${dir}\" already exists. Each migration must have a unique directory.`,\n fix: 'Use --name to pick a different name, or delete the existing directory and re-run.',\n details: { dir },\n });\n}\n\nexport function errorMissingFile(file: string, dir: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.FILE_MISSING', `Missing ${file}`, {\n why: `Expected \"${file}\" in \"${dir}\" but the file does not exist.`,\n fix: 'Ensure the migration directory contains both migration.json and ops.json. If the directory is corrupt, delete it and re-run migration plan.',\n details: { file, dir },\n });\n}\n\nexport function errorInvalidJson(filePath: string, parseError: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_JSON', 'Invalid JSON in migration file', {\n why: `Failed to parse \"${filePath}\": ${parseError}`,\n fix: 'Fix the JSON syntax error, or delete the migration directory and re-run migration plan.',\n details: { filePath, parseError },\n });\n}\n\nexport function errorInvalidManifest(filePath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_MANIFEST', 'Invalid migration manifest', {\n why: `Manifest at \"${filePath}\" is invalid: ${reason}`,\n fix: 'Ensure the manifest has all required fields (from, to, kind, toContract). If corrupt, delete and re-plan.',\n details: { filePath, reason },\n });\n}\n\nexport function errorInvalidSlug(slug: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_NAME', 'Invalid migration name', {\n why: `The slug \"${slug}\" contains no valid characters after sanitization (only a-z, 0-9 are kept).`,\n fix: 'Provide a name with at least one alphanumeric character, e.g. --name add_users.',\n details: { slug },\n });\n}\n\nexport function errorSameSourceAndTarget(dirName: string, hash: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.SAME_SOURCE_AND_TARGET',\n 'Migration has same source and target',\n {\n why: `Migration \"${dirName}\" has from === to === \"${hash}\". A migration must transition between two different contract states.`,\n fix: 'Delete the invalid migration directory and re-run migration plan.',\n details: { dirName, hash },\n },\n );\n}\n\nexport function errorAmbiguousTarget(\n branchTips: readonly string[],\n context?: {\n divergencePoint: string;\n branches: readonly {\n tip: string;\n edges: readonly { dirName: string; from: string; to: string }[];\n }[];\n },\n): MigrationToolsError {\n const divergenceInfo = context\n ? `\\nDivergence point: ${context.divergencePoint}\\nBranches:\\n${context.branches.map((b) => ` → ${b.tip} (${b.edges.length} edge(s): ${b.edges.map((e) => e.dirName).join(' → ') || 'direct'})`).join('\\n')}`\n : '';\n return new MigrationToolsError('MIGRATION.AMBIGUOUS_TARGET', 'Ambiguous migration target', {\n why: `The migration history has diverged into multiple branches: ${branchTips.join(', ')}. This typically happens when two developers plan migrations from the same starting point.${divergenceInfo}`,\n fix: 'Use `migration ref set <name> <hash>` to target a specific branch, delete one of the conflicting migration directories and re-run `migration plan`, or use --from <hash> to explicitly select a starting point.',\n details: {\n branchTips,\n ...(context ? { divergencePoint: context.divergencePoint, branches: context.branches } : {}),\n },\n });\n}\n\nexport function errorNoInitialMigration(nodes: readonly string[]): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.NO_INITIAL_MIGRATION', 'No initial migration found', {\n why: `No migration starts from the empty contract state (known hashes: ${nodes.join(', ')}). At least one migration must originate from the empty state.`,\n fix: 'Inspect the migrations directory for corrupted migration.json files. At least one migration must start from the empty contract hash.',\n details: { nodes },\n });\n}\n\nexport function errorInvalidRefs(refsPath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REFS', 'Invalid refs.json', {\n why: `refs.json at \"${refsPath}\" is invalid: ${reason}`,\n fix: 'Ensure refs.json is a flat object mapping valid ref names to contract hash strings.',\n details: { path: refsPath, reason },\n });\n}\n\nexport function errorInvalidRefFile(filePath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_FILE', 'Invalid ref file', {\n why: `Ref file at \"${filePath}\" is invalid: ${reason}`,\n fix: 'Ensure the ref file contains valid JSON with { \"hash\": \"sha256:<64 hex chars>\", \"invariants\": [\"...\"] }.',\n details: { path: filePath, reason },\n });\n}\n\nexport function errorInvalidRefName(refName: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_NAME', 'Invalid ref name', {\n why: `Ref name \"${refName}\" is invalid. Names must be lowercase alphanumeric with hyphens or forward slashes (no \".\" or \"..\" segments).`,\n fix: `Use a valid ref name (e.g., \"staging\", \"envs/production\").`,\n details: { refName },\n });\n}\n\nexport function errorNoTarget(reachableHashes: readonly string[]): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.NO_TARGET', 'No migration target could be resolved', {\n why: `The migration history contains cycles and no target can be resolved automatically (reachable hashes: ${reachableHashes.join(', ')}). This typically happens after rollback migrations (e.g., C1→C2→C1).`,\n fix: 'Use --from <hash> to specify the planning origin explicitly.',\n details: { reachableHashes },\n });\n}\n\nexport function errorInvalidRefValue(value: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_VALUE', 'Invalid ref value', {\n why: `Ref value \"${value}\" is not a valid contract hash. Values must be in the format \"sha256:<64 hex chars>\" or \"sha256:empty\".`,\n fix: 'Use a valid storage hash from `prisma-next contract emit` output or an existing migration.',\n details: { value },\n });\n}\n\nexport function errorDuplicateMigrationId(migrationId: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.DUPLICATE_MIGRATION_ID',\n 'Duplicate migrationId in migration graph',\n {\n why: `Multiple migrations share migrationId \"${migrationId}\". Each migration must have a unique content-addressed identity.`,\n fix: 'Regenerate one of the conflicting migrations so each migrationId is unique, then re-run migration commands.',\n details: { migrationId },\n },\n );\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { c as MigrationManifest, l as MigrationOps } from "../types-9YQfIg6N.mjs";
1
+ import { l as MigrationManifest, u as MigrationOps } from "../types-DXjq7Fum.mjs";
2
2
 
3
3
  //#region src/attestation.d.ts
4
4
  interface VerifyResult {
@@ -1 +1 @@
1
- {"version":3,"file":"attestation.d.mts","names":[],"sources":["../../src/attestation.ts"],"sourcesContent":[],"mappings":";;;UAQiB,YAAA;;EAAA,SAAA,MAAY,CAAA,EAAA,OAAA,GAAA,UAAA;EAWb,SAAA,iBAAkB,CAAA,EAAA,MAAW;EA2BvB,SAAA,mBAAe,CAAA,EAAe,MAAA;AAUpD;iBArCgB,kBAAA,WAA6B,wBAAwB;iBA2B/C,eAAA,eAA8B;iBAU9B,eAAA,eAA8B,QAAQ"}
1
+ {"version":3,"file":"attestation.d.mts","names":[],"sources":["../../src/attestation.ts"],"sourcesContent":[],"mappings":";;;UAMiB,YAAA;;EAAA,SAAA,MAAY,CAAA,EAAA,OAAA,GAAA,UAAA;EAWb,SAAA,iBAAkB,CAAA,EAAA,MAAW;EA2BvB,SAAA,mBAAe,CAAA,EAAe,MAAA;AAUpD;iBArCgB,kBAAA,WAA6B,wBAAwB;iBA2B/C,eAAA,eAA8B;iBAU9B,eAAA,eAA8B,QAAQ"}
@@ -1,8 +1,6 @@
1
- import { n as readMigrationPackage } from "../io-afog-e8J.mjs";
2
- import { writeFile } from "node:fs/promises";
3
- import { join } from "pathe";
1
+ import { i as writeMigrationManifest, n as readMigrationPackage } from "../io-BO18-Evu.mjs";
4
2
  import { createHash } from "node:crypto";
5
- import { canonicalizeContract } from "@prisma-next/core-control-plane/emission";
3
+ import { canonicalizeContract } from "@prisma-next/contract/hashing";
6
4
 
7
5
  //#region src/canonicalize-json.ts
8
6
  function sortKeys(value) {
@@ -33,11 +31,10 @@ function computeMigrationId(manifest, ops) {
33
31
  async function attestMigration(dir) {
34
32
  const pkg = await readMigrationPackage(dir);
35
33
  const migrationId = computeMigrationId(pkg.manifest, pkg.ops);
36
- const updated = {
34
+ await writeMigrationManifest(dir, {
37
35
  ...pkg.manifest,
38
36
  migrationId
39
- };
40
- await writeFile(join(dir, "migration.json"), JSON.stringify(updated, null, 2));
37
+ });
41
38
  return migrationId;
42
39
  }
43
40
  async function verifyMigration(dir) {
@@ -1 +1 @@
1
- {"version":3,"file":"attestation.mjs","names":["sorted: Record<string, unknown>"],"sources":["../../src/canonicalize-json.ts","../../src/attestation.ts"],"sourcesContent":["function sortKeys(value: unknown): unknown {\n if (value === null || typeof value !== 'object') {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map(sortKeys);\n }\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) {\n sorted[key] = sortKeys((value as Record<string, unknown>)[key]);\n }\n return sorted;\n}\n\nexport function canonicalizeJson(value: unknown): string {\n return JSON.stringify(sortKeys(value));\n}\n","import { createHash } from 'node:crypto';\nimport { writeFile } from 'node:fs/promises';\nimport { canonicalizeContract } from '@prisma-next/core-control-plane/emission';\nimport { join } from 'pathe';\nimport { canonicalizeJson } from './canonicalize-json';\nimport { readMigrationPackage } from './io';\nimport type { MigrationManifest, MigrationOps } from './types';\n\nexport interface VerifyResult {\n readonly ok: boolean;\n readonly reason?: 'draft' | 'mismatch';\n readonly storedMigrationId?: string;\n readonly computedMigrationId?: string;\n}\n\nfunction sha256Hex(input: string): string {\n return createHash('sha256').update(input).digest('hex');\n}\n\nexport function computeMigrationId(manifest: MigrationManifest, ops: MigrationOps): string {\n const {\n migrationId: _migrationId,\n signature: _signature,\n fromContract: _fromContract,\n toContract: _toContract,\n ...strippedMeta\n } = manifest;\n\n const canonicalManifest = canonicalizeJson(strippedMeta);\n const canonicalOps = canonicalizeJson(ops);\n\n const canonicalFromContract =\n manifest.fromContract !== null ? canonicalizeContract(manifest.fromContract) : 'null';\n const canonicalToContract = canonicalizeContract(manifest.toContract);\n\n const partHashes = [\n canonicalManifest,\n canonicalOps,\n canonicalFromContract,\n canonicalToContract,\n ].map(sha256Hex);\n const hash = sha256Hex(canonicalizeJson(partHashes));\n\n return `sha256:${hash}`;\n}\n\nexport async function attestMigration(dir: string): Promise<string> {\n const pkg = await readMigrationPackage(dir);\n const migrationId = computeMigrationId(pkg.manifest, pkg.ops);\n\n const updated = { ...pkg.manifest, migrationId };\n await writeFile(join(dir, 'migration.json'), JSON.stringify(updated, null, 2));\n\n return migrationId;\n}\n\nexport async function verifyMigration(dir: string): Promise<VerifyResult> {\n const pkg = await readMigrationPackage(dir);\n\n if (pkg.manifest.migrationId === null) {\n return { ok: false, reason: 'draft' };\n }\n\n const computed = computeMigrationId(pkg.manifest, pkg.ops);\n\n if (pkg.manifest.migrationId === computed) {\n return { ok: true, storedMigrationId: pkg.manifest.migrationId, computedMigrationId: computed };\n }\n\n return {\n ok: false,\n reason: 'mismatch',\n storedMigrationId: pkg.manifest.migrationId,\n computedMigrationId: computed,\n };\n}\n"],"mappings":";;;;;;;AAAA,SAAS,SAAS,OAAyB;AACzC,KAAI,UAAU,QAAQ,OAAO,UAAU,SACrC,QAAO;AAET,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,IAAI,SAAS;CAE5B,MAAMA,SAAkC,EAAE;AAC1C,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,CAAC,MAAM,CACzC,QAAO,OAAO,SAAU,MAAkC,KAAK;AAEjE,QAAO;;AAGT,SAAgB,iBAAiB,OAAwB;AACvD,QAAO,KAAK,UAAU,SAAS,MAAM,CAAC;;;;;ACAxC,SAAS,UAAU,OAAuB;AACxC,QAAO,WAAW,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;AAGzD,SAAgB,mBAAmB,UAA6B,KAA2B;CACzF,MAAM,EACJ,aAAa,cACb,WAAW,YACX,cAAc,eACd,YAAY,aACZ,GAAG,iBACD;AAiBJ,QAAO,UAFM,UAAU,iBANJ;EAPO,iBAAiB,aAAa;EACnC,iBAAiB,IAAI;EAGxC,SAAS,iBAAiB,OAAO,qBAAqB,SAAS,aAAa,GAAG;EACrD,qBAAqB,SAAS,WAAW;EAOpE,CAAC,IAAI,UAAU,CACmC,CAAC;;AAKtD,eAAsB,gBAAgB,KAA8B;CAClE,MAAM,MAAM,MAAM,qBAAqB,IAAI;CAC3C,MAAM,cAAc,mBAAmB,IAAI,UAAU,IAAI,IAAI;CAE7D,MAAM,UAAU;EAAE,GAAG,IAAI;EAAU;EAAa;AAChD,OAAM,UAAU,KAAK,KAAK,iBAAiB,EAAE,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC;AAE9E,QAAO;;AAGT,eAAsB,gBAAgB,KAAoC;CACxE,MAAM,MAAM,MAAM,qBAAqB,IAAI;AAE3C,KAAI,IAAI,SAAS,gBAAgB,KAC/B,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAS;CAGvC,MAAM,WAAW,mBAAmB,IAAI,UAAU,IAAI,IAAI;AAE1D,KAAI,IAAI,SAAS,gBAAgB,SAC/B,QAAO;EAAE,IAAI;EAAM,mBAAmB,IAAI,SAAS;EAAa,qBAAqB;EAAU;AAGjG,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,mBAAmB,IAAI,SAAS;EAChC,qBAAqB;EACtB"}
1
+ {"version":3,"file":"attestation.mjs","names":["sorted: Record<string, unknown>"],"sources":["../../src/canonicalize-json.ts","../../src/attestation.ts"],"sourcesContent":["function sortKeys(value: unknown): unknown {\n if (value === null || typeof value !== 'object') {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map(sortKeys);\n }\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) {\n sorted[key] = sortKeys((value as Record<string, unknown>)[key]);\n }\n return sorted;\n}\n\nexport function canonicalizeJson(value: unknown): string {\n return JSON.stringify(sortKeys(value));\n}\n","import { createHash } from 'node:crypto';\nimport { canonicalizeContract } from '@prisma-next/contract/hashing';\nimport { canonicalizeJson } from './canonicalize-json';\nimport { readMigrationPackage, writeMigrationManifest } from './io';\nimport type { MigrationManifest, MigrationOps } from './types';\n\nexport interface VerifyResult {\n readonly ok: boolean;\n readonly reason?: 'draft' | 'mismatch';\n readonly storedMigrationId?: string;\n readonly computedMigrationId?: string;\n}\n\nfunction sha256Hex(input: string): string {\n return createHash('sha256').update(input).digest('hex');\n}\n\nexport function computeMigrationId(manifest: MigrationManifest, ops: MigrationOps): string {\n const {\n migrationId: _migrationId,\n signature: _signature,\n fromContract: _fromContract,\n toContract: _toContract,\n ...strippedMeta\n } = manifest;\n\n const canonicalManifest = canonicalizeJson(strippedMeta);\n const canonicalOps = canonicalizeJson(ops);\n\n const canonicalFromContract =\n manifest.fromContract !== null ? canonicalizeContract(manifest.fromContract) : 'null';\n const canonicalToContract = canonicalizeContract(manifest.toContract);\n\n const partHashes = [\n canonicalManifest,\n canonicalOps,\n canonicalFromContract,\n canonicalToContract,\n ].map(sha256Hex);\n const hash = sha256Hex(canonicalizeJson(partHashes));\n\n return `sha256:${hash}`;\n}\n\nexport async function attestMigration(dir: string): Promise<string> {\n const pkg = await readMigrationPackage(dir);\n const migrationId = computeMigrationId(pkg.manifest, pkg.ops);\n\n const updated = { ...pkg.manifest, migrationId };\n await writeMigrationManifest(dir, updated);\n\n return migrationId;\n}\n\nexport async function verifyMigration(dir: string): Promise<VerifyResult> {\n const pkg = await readMigrationPackage(dir);\n\n if (pkg.manifest.migrationId === null) {\n return { ok: false, reason: 'draft' };\n }\n\n const computed = computeMigrationId(pkg.manifest, pkg.ops);\n\n if (pkg.manifest.migrationId === computed) {\n return { ok: true, storedMigrationId: pkg.manifest.migrationId, computedMigrationId: computed };\n }\n\n return {\n ok: false,\n reason: 'mismatch',\n storedMigrationId: pkg.manifest.migrationId,\n computedMigrationId: computed,\n };\n}\n"],"mappings":";;;;;AAAA,SAAS,SAAS,OAAyB;AACzC,KAAI,UAAU,QAAQ,OAAO,UAAU,SACrC,QAAO;AAET,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,IAAI,SAAS;CAE5B,MAAMA,SAAkC,EAAE;AAC1C,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,CAAC,MAAM,CACzC,QAAO,OAAO,SAAU,MAAkC,KAAK;AAEjE,QAAO;;AAGT,SAAgB,iBAAiB,OAAwB;AACvD,QAAO,KAAK,UAAU,SAAS,MAAM,CAAC;;;;;ACFxC,SAAS,UAAU,OAAuB;AACxC,QAAO,WAAW,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;AAGzD,SAAgB,mBAAmB,UAA6B,KAA2B;CACzF,MAAM,EACJ,aAAa,cACb,WAAW,YACX,cAAc,eACd,YAAY,aACZ,GAAG,iBACD;AAiBJ,QAAO,UAFM,UAAU,iBANJ;EAPO,iBAAiB,aAAa;EACnC,iBAAiB,IAAI;EAGxC,SAAS,iBAAiB,OAAO,qBAAqB,SAAS,aAAa,GAAG;EACrD,qBAAqB,SAAS,WAAW;EAOpE,CAAC,IAAI,UAAU,CACmC,CAAC;;AAKtD,eAAsB,gBAAgB,KAA8B;CAClE,MAAM,MAAM,MAAM,qBAAqB,IAAI;CAC3C,MAAM,cAAc,mBAAmB,IAAI,UAAU,IAAI,IAAI;AAG7D,OAAM,uBAAuB,KADb;EAAE,GAAG,IAAI;EAAU;EAAa,CACN;AAE1C,QAAO;;AAGT,eAAsB,gBAAgB,KAAoC;CACxE,MAAM,MAAM,MAAM,qBAAqB,IAAI;AAE3C,KAAI,IAAI,SAAS,gBAAgB,KAC/B,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAS;CAGvC,MAAM,WAAW,mBAAmB,IAAI,UAAU,IAAI,IAAI;AAE1D,KAAI,IAAI,SAAS,gBAAgB,SAC/B,QAAO;EAAE,IAAI;EAAM,mBAAmB,IAAI,SAAS;EAAa,qBAAqB;EAAU;AAGjG,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,mBAAmB,IAAI,SAAS;EAChC,qBAAqB;EACtB"}
@@ -0,0 +1,9 @@
1
+ //#region src/constants.d.ts
2
+ /**
3
+ * Sentinel value representing the absence of a contract (empty/new project).
4
+ * This is a human-readable marker, not a real SHA-256 hash.
5
+ */
6
+ declare const EMPTY_CONTRACT_HASH: "sha256:empty";
7
+ //#endregion
8
+ export { EMPTY_CONTRACT_HASH };
9
+ //# sourceMappingURL=constants.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.mts","names":[],"sources":["../../src/constants.ts"],"sourcesContent":[],"mappings":";;AAIA;;;cAAa"}
@@ -0,0 +1,3 @@
1
+ import { t as EMPTY_CONTRACT_HASH } from "../constants-DARNL_LD.mjs";
2
+
3
+ export { EMPTY_CONTRACT_HASH };
@@ -1,4 +1,4 @@
1
- import { a as MigrationChainEntry, o as MigrationGraph, t as AttestedMigrationBundle } from "../types-9YQfIg6N.mjs";
1
+ import { o as MigrationChainEntry, s as MigrationGraph, t as AttestedMigrationBundle } from "../types-DXjq7Fum.mjs";
2
2
 
3
3
  //#region src/dag.d.ts
4
4
  declare function reconstructGraph(packages: readonly AttestedMigrationBundle[]): MigrationGraph;
@@ -25,21 +25,21 @@ interface PathDecision {
25
25
  */
26
26
  declare function findPathWithDecision(graph: MigrationGraph, fromHash: string, toHash: string, refName?: string): PathDecision | null;
27
27
  /**
28
- * Find all leaf nodes reachable from `fromHash` via forward edges.
29
- * A leaf is a node with no outgoing edges in the graph.
28
+ * Find all branch tips (nodes with no outgoing edges) reachable from
29
+ * `fromHash` via forward edges.
30
30
  */
31
31
  declare function findReachableLeaves(graph: MigrationGraph, fromHash: string): readonly string[];
32
32
  /**
33
- * Find the leaf contract hash of the migration graph reachable from
34
- * EMPTY_CONTRACT_HASH. Throws NO_ROOT if the graph has nodes but none
35
- * originate from the empty hash (e.g. root migration was deleted).
36
- * Throws AMBIGUOUS_LEAF if multiple leaves exist.
33
+ * Find the target contract hash of the migration graph reachable from
34
+ * EMPTY_CONTRACT_HASH. Throws NO_INITIAL_MIGRATION if the graph has
35
+ * nodes but none originate from the empty hash.
36
+ * Throws AMBIGUOUS_TARGET if multiple branch tips exist.
37
37
  */
38
38
  declare function findLeaf(graph: MigrationGraph): string;
39
39
  /**
40
40
  * Find the latest migration entry by traversing from EMPTY_CONTRACT_HASH
41
- * to the single leaf. Returns null for an empty graph.
42
- * Throws AMBIGUOUS_LEAF if the graph has multiple leaves.
41
+ * to the single target. Returns null for an empty graph.
42
+ * Throws AMBIGUOUS_TARGET if the graph has multiple branch tips.
43
43
  */
44
44
  declare function findLatestMigration(graph: MigrationGraph): MigrationChainEntry | null;
45
45
  declare function detectCycles(graph: MigrationGraph): readonly string[][];
@@ -1,5 +1,5 @@
1
- import { f as errorNoResolvableLeaf, i as errorDuplicateMigrationId, m as errorSelfLoop, n as errorAmbiguousLeaf, p as errorNoRoot } from "../errors-CqLiJwqA.mjs";
2
- import { EMPTY_CONTRACT_HASH } from "@prisma-next/core-control-plane/constants";
1
+ import { f as errorNoInitialMigration, i as errorDuplicateMigrationId, m as errorSameSourceAndTarget, n as errorAmbiguousTarget, p as errorNoTarget } from "../errors-C_XuSbX7.mjs";
2
+ import { t as EMPTY_CONTRACT_HASH } from "../constants-DARNL_LD.mjs";
3
3
  import { ifDefined } from "@prisma-next/utils/defined";
4
4
 
5
5
  //#region src/dag.ts
@@ -10,7 +10,7 @@ function reconstructGraph(packages) {
10
10
  const migrationById = /* @__PURE__ */ new Map();
11
11
  for (const pkg of packages) {
12
12
  const { from, to } = pkg.manifest;
13
- if (from === to) throw errorSelfLoop(pkg.dirName, from);
13
+ if (from === to) throw errorSameSourceAndTarget(pkg.dirName, from);
14
14
  nodes.add(from);
15
15
  nodes.add(to);
16
16
  const migration = {
@@ -148,7 +148,7 @@ function findPathWithDecision(graph, fromHash, toHash, refName) {
148
148
  };
149
149
  }
150
150
  /**
151
- * Walk ancestors of each leaf back from the leaves to find the last node
151
+ * Walk ancestors of each branch tip back to find the last node
152
152
  * that appears on all paths. Returns `fromHash` if no shared ancestor is found.
153
153
  */
154
154
  function findDivergencePoint(graph, fromHash, leaves) {
@@ -178,8 +178,8 @@ function findDivergencePoint(graph, fromHash, leaves) {
178
178
  return deepest;
179
179
  }
180
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.
181
+ * Find all branch tips (nodes with no outgoing edges) reachable from
182
+ * `fromHash` via forward edges.
183
183
  */
184
184
  function findReachableLeaves(graph, fromHash) {
185
185
  const visited = /* @__PURE__ */ new Set();
@@ -199,28 +199,28 @@ function findReachableLeaves(graph, fromHash) {
199
199
  return leaves;
200
200
  }
201
201
  /**
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.
202
+ * 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.
206
206
  */
207
207
  function findLeaf(graph) {
208
208
  if (graph.nodes.size === 0) return EMPTY_CONTRACT_HASH;
209
- if (!graph.nodes.has(EMPTY_CONTRACT_HASH)) throw errorNoRoot([...graph.nodes]);
209
+ if (!graph.nodes.has(EMPTY_CONTRACT_HASH)) throw errorNoInitialMigration([...graph.nodes]);
210
210
  const leaves = findReachableLeaves(graph, EMPTY_CONTRACT_HASH);
211
211
  if (leaves.length === 0) {
212
212
  const reachable = [...graph.nodes].filter((n) => n !== EMPTY_CONTRACT_HASH);
213
- if (reachable.length > 0) throw errorNoResolvableLeaf(reachable);
213
+ if (reachable.length > 0) throw errorNoTarget(reachable);
214
214
  return EMPTY_CONTRACT_HASH;
215
215
  }
216
216
  if (leaves.length > 1) {
217
217
  const divergencePoint = findDivergencePoint(graph, EMPTY_CONTRACT_HASH, leaves);
218
- throw errorAmbiguousLeaf(leaves, {
218
+ throw errorAmbiguousTarget(leaves, {
219
219
  divergencePoint,
220
- branches: leaves.map((leaf$1) => {
220
+ branches: leaves.map((tip) => {
221
221
  return {
222
- leaf: leaf$1,
223
- edges: (findPath(graph, divergencePoint, leaf$1) ?? []).map((e) => ({
222
+ tip,
223
+ edges: (findPath(graph, divergencePoint, tip) ?? []).map((e) => ({
224
224
  dirName: e.dirName,
225
225
  from: e.from,
226
226
  to: e.to
@@ -234,8 +234,8 @@ function findLeaf(graph) {
234
234
  }
235
235
  /**
236
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.
237
+ * to the single target. Returns null for an empty graph.
238
+ * Throws AMBIGUOUS_TARGET if the graph has multiple branch tips.
239
239
  */
240
240
  function findLatestMigration(graph) {
241
241
  if (graph.nodes.size === 0) return null;
@@ -1 +1 @@
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":"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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[]","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,10 +1,12 @@
1
- import { c as MigrationManifest, i as MigrationBundle, l as MigrationOps } from "../types-9YQfIg6N.mjs";
1
+ import { l as MigrationManifest, r as BaseMigrationBundle, u as MigrationOps } from "../types-DXjq7Fum.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<MigrationBundle>;
6
- declare function readMigrationsDir(migrationsRoot: string): Promise<readonly MigrationBundle[]>;
5
+ declare function writeMigrationManifest(dir: string, manifest: MigrationManifest): Promise<void>;
6
+ declare function writeMigrationOps(dir: string, ops: MigrationOps): Promise<void>;
7
+ declare function readMigrationPackage(dir: string): Promise<BaseMigrationBundle>;
8
+ declare function readMigrationsDir(migrationsRoot: string): Promise<readonly BaseMigrationBundle[]>;
7
9
  declare function formatMigrationDirName(timestamp: Date, slug: string): string;
8
10
  //#endregion
9
- export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationPackage };
11
+ export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationManifest, writeMigrationOps, writeMigrationPackage };
10
12
  //# 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;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
+ {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;iBAwDsB,qBAAA,wBAEV,wBACL,eACJ;iBAgBmB,sBAAA,wBAEV,oBACT;AAvBmB,iBA2BA,iBAAA,CA3BqB,GAAA,EAAA,MAAA,EAAA,GAAA,EA2Be,YA3Bf,CAAA,EA2B8B,OA3B9B,CAAA,IAAA,CAAA;AAE/B,iBA6BU,oBAAA,CA7BV,GAAA,EAAA,MAAA,CAAA,EA6B6C,OA7B7C,CA6BqD,mBA7BrD,CAAA;AACL,iBA8Fe,iBAAA,CA9Ff,cAAA,EAAA,MAAA,CAAA,EAgGJ,OAhGI,CAAA,SAgGa,mBAhGb,EAAA,CAAA;AACJ,iBA8Ha,sBAAA,CA9Hb,SAAA,EA8H+C,IA9H/C,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-afog-e8J.mjs";
1
+ import { a as writeMigrationOps, i as writeMigrationManifest, n as readMigrationPackage, o as writeMigrationPackage, r as readMigrationsDir, t as formatMigrationDirName } from "../io-BO18-Evu.mjs";
2
2
 
3
- export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationPackage };
3
+ export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationManifest, writeMigrationOps, writeMigrationPackage };
@@ -0,0 +1,34 @@
1
+ import { OperationDescriptor } from "@prisma-next/framework-components/control";
2
+
3
+ //#region src/migration-ts.d.ts
4
+
5
+ /**
6
+ * Options for scaffolding a migration.ts file.
7
+ */
8
+ interface ScaffoldOptions {
9
+ /** Operation descriptors to serialize as builder calls. */
10
+ readonly descriptors?: readonly OperationDescriptor[];
11
+ /** Absolute path to contract.json — used to derive contract.d.ts import for typed builders. */
12
+ readonly contractJsonPath?: string;
13
+ }
14
+ /**
15
+ * Scaffolds a migration.ts file in the given package directory.
16
+ * Serializes operation descriptors as builder calls that the user can edit.
17
+ * On verify, this file is re-evaluated to produce the final ops.
18
+ */
19
+ declare function scaffoldMigrationTs(packageDir: string, options?: ScaffoldOptions): Promise<void>;
20
+ /**
21
+ * Checks whether a migration.ts file exists in the package directory.
22
+ */
23
+ declare function hasMigrationTs(packageDir: string): Promise<boolean>;
24
+ /**
25
+ * Evaluates a migration.ts file by loading it via native Node import.
26
+ * Returns the result of calling the default export (expected to be a
27
+ * function returning an array of operation descriptors).
28
+ *
29
+ * Requires Node ≥24 for native TypeScript support.
30
+ */
31
+ declare function evaluateMigrationTs(packageDir: string): Promise<readonly unknown[]>;
32
+ //#endregion
33
+ export { type ScaffoldOptions, evaluateMigrationTs, hasMigrationTs, scaffoldMigrationTs };
34
+ //# sourceMappingURL=migration-ts.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-ts.d.mts","names":[],"sources":["../../src/migration-ts.ts"],"sourcesContent":[],"mappings":";;;;;;;UAmBiB,eAAA;;kCAEiB;;;;;;;;;iBAsFZ,mBAAA,+BAEX,kBACR;;;;iBA6CmB,cAAA,sBAAoC;;;;;;;;iBAgBpC,mBAAA,sBAAyC"}