@prisma-next/migration-tools 0.5.0-dev.3 → 0.5.0-dev.31

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 (92) hide show
  1. package/README.md +34 -22
  2. package/dist/{constants-BRi0X7B_.mjs → constants-BQEHsaEx.mjs} +1 -1
  3. package/dist/{constants-BRi0X7B_.mjs.map → constants-BQEHsaEx.mjs.map} +1 -1
  4. package/dist/errors-Bl3cKiM8.mjs +244 -0
  5. package/dist/errors-Bl3cKiM8.mjs.map +1 -0
  6. package/dist/exports/constants.mjs +1 -1
  7. package/dist/exports/{types.d.mts → errors.d.mts} +7 -8
  8. package/dist/exports/errors.d.mts.map +1 -0
  9. package/dist/exports/errors.mjs +3 -0
  10. package/dist/exports/graph.d.mts +2 -0
  11. package/dist/exports/graph.mjs +1 -0
  12. package/dist/exports/hash.d.mts +52 -0
  13. package/dist/exports/hash.d.mts.map +1 -0
  14. package/dist/exports/hash.mjs +3 -0
  15. package/dist/exports/invariants.d.mts +24 -0
  16. package/dist/exports/invariants.d.mts.map +1 -0
  17. package/dist/exports/invariants.mjs +4 -0
  18. package/dist/exports/io.d.mts +7 -6
  19. package/dist/exports/io.d.mts.map +1 -1
  20. package/dist/exports/io.mjs +162 -2
  21. package/dist/exports/io.mjs.map +1 -0
  22. package/dist/exports/metadata.d.mts +2 -0
  23. package/dist/exports/metadata.mjs +1 -0
  24. package/dist/exports/{dag.d.mts → migration-graph.d.mts} +31 -10
  25. package/dist/exports/migration-graph.d.mts.map +1 -0
  26. package/dist/exports/{dag.mjs → migration-graph.mjs} +143 -63
  27. package/dist/exports/migration-graph.mjs.map +1 -0
  28. package/dist/exports/migration-ts.mjs +1 -1
  29. package/dist/exports/migration.d.mts +15 -14
  30. package/dist/exports/migration.d.mts.map +1 -1
  31. package/dist/exports/migration.mjs +68 -40
  32. package/dist/exports/migration.mjs.map +1 -1
  33. package/dist/exports/package.d.mts +2 -0
  34. package/dist/exports/package.mjs +1 -0
  35. package/dist/exports/refs.d.mts +11 -5
  36. package/dist/exports/refs.d.mts.map +1 -1
  37. package/dist/exports/refs.mjs +106 -30
  38. package/dist/exports/refs.mjs.map +1 -1
  39. package/dist/graph-BHPv-9Gl.d.mts +28 -0
  40. package/dist/graph-BHPv-9Gl.d.mts.map +1 -0
  41. package/dist/hash-BARZdVgW.mjs +76 -0
  42. package/dist/hash-BARZdVgW.mjs.map +1 -0
  43. package/dist/invariants-BmrTBQ0A.mjs +42 -0
  44. package/dist/invariants-BmrTBQ0A.mjs.map +1 -0
  45. package/dist/metadata-BP1cmU7Z.d.mts +50 -0
  46. package/dist/metadata-BP1cmU7Z.d.mts.map +1 -0
  47. package/dist/op-schema-DZKFua46.mjs +14 -0
  48. package/dist/op-schema-DZKFua46.mjs.map +1 -0
  49. package/dist/package-5HCCg0z-.d.mts +21 -0
  50. package/dist/package-5HCCg0z-.d.mts.map +1 -0
  51. package/package.json +32 -16
  52. package/src/errors.ts +139 -15
  53. package/src/exports/errors.ts +1 -0
  54. package/src/exports/graph.ts +1 -0
  55. package/src/exports/hash.ts +2 -0
  56. package/src/exports/invariants.ts +1 -0
  57. package/src/exports/io.ts +1 -1
  58. package/src/exports/metadata.ts +1 -0
  59. package/src/exports/{dag.ts → migration-graph.ts} +3 -2
  60. package/src/exports/migration.ts +0 -1
  61. package/src/exports/package.ts +1 -0
  62. package/src/exports/refs.ts +10 -2
  63. package/src/graph-ops.ts +57 -30
  64. package/src/graph.ts +25 -0
  65. package/src/hash.ts +91 -0
  66. package/src/invariants.ts +45 -0
  67. package/src/io.ts +57 -31
  68. package/src/metadata.ts +41 -0
  69. package/src/migration-base.ts +97 -56
  70. package/src/{dag.ts → migration-graph.ts} +156 -54
  71. package/src/op-schema.ts +11 -0
  72. package/src/package.ts +18 -0
  73. package/src/refs.ts +148 -37
  74. package/dist/attestation-DtF8tEOM.mjs +0 -65
  75. package/dist/attestation-DtF8tEOM.mjs.map +0 -1
  76. package/dist/errors-BKbRGCJM.mjs +0 -160
  77. package/dist/errors-BKbRGCJM.mjs.map +0 -1
  78. package/dist/exports/attestation.d.mts +0 -37
  79. package/dist/exports/attestation.d.mts.map +0 -1
  80. package/dist/exports/attestation.mjs +0 -4
  81. package/dist/exports/dag.d.mts.map +0 -1
  82. package/dist/exports/dag.mjs.map +0 -1
  83. package/dist/exports/types.d.mts.map +0 -1
  84. package/dist/exports/types.mjs +0 -3
  85. package/dist/io-CCnYsUHU.mjs +0 -153
  86. package/dist/io-CCnYsUHU.mjs.map +0 -1
  87. package/dist/types-DyGXcWWp.d.mts +0 -71
  88. package/dist/types-DyGXcWWp.d.mts.map +0 -1
  89. package/src/attestation.ts +0 -81
  90. package/src/exports/attestation.ts +0 -2
  91. package/src/exports/types.ts +0 -10
  92. package/src/types.ts +0 -66
@@ -1,3 +1,163 @@
1
- import { a as writeMigrationManifest, i as readMigrationsDir, n as formatMigrationDirName, o as writeMigrationOps, r as readMigrationPackage, s as writeMigrationPackage, t as copyFilesWithRename } from "../io-CCnYsUHU.mjs";
1
+ import { c as errorInvalidJson, g as errorMissingFile, h as errorMigrationHashMismatch, l as errorInvalidManifest, m as errorInvalidSlug, o as errorInvalidDestName, r as errorDirectoryExists, y as errorProvidedInvariantsMismatch } from "../errors-Bl3cKiM8.mjs";
2
+ import { n as verifyMigrationHash } from "../hash-BARZdVgW.mjs";
3
+ import { t as deriveProvidedInvariants } from "../invariants-BmrTBQ0A.mjs";
4
+ import { n as MigrationOpsSchema } from "../op-schema-DZKFua46.mjs";
5
+ import { basename, dirname, join } from "pathe";
6
+ import { copyFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
7
+ import { type } from "arktype";
2
8
 
3
- export { copyFilesWithRename, formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationManifest, writeMigrationOps, writeMigrationPackage };
9
+ //#region src/io.ts
10
+ const MANIFEST_FILE = "migration.json";
11
+ const OPS_FILE = "ops.json";
12
+ const MAX_SLUG_LENGTH = 64;
13
+ function hasErrnoCode(error, code) {
14
+ return error instanceof Error && error.code === code;
15
+ }
16
+ const MigrationHintsSchema = type({
17
+ used: "string[]",
18
+ applied: "string[]",
19
+ plannerVersion: "string"
20
+ });
21
+ const MigrationMetadataSchema = type({
22
+ "+": "reject",
23
+ from: "string > 0 | null",
24
+ to: "string",
25
+ migrationHash: "string",
26
+ fromContract: "object | null",
27
+ toContract: "object",
28
+ hints: MigrationHintsSchema,
29
+ labels: "string[]",
30
+ providedInvariants: "string[]",
31
+ "authorship?": type({
32
+ "author?": "string",
33
+ "email?": "string"
34
+ }),
35
+ "signature?": type({
36
+ keyId: "string",
37
+ value: "string"
38
+ }).or("null"),
39
+ createdAt: "string"
40
+ });
41
+ async function writeMigrationPackage(dir, metadata, ops) {
42
+ await mkdir(dirname(dir), { recursive: true });
43
+ try {
44
+ await mkdir(dir);
45
+ } catch (error) {
46
+ if (hasErrnoCode(error, "EEXIST")) throw errorDirectoryExists(dir);
47
+ throw error;
48
+ }
49
+ await writeFile(join(dir, MANIFEST_FILE), JSON.stringify(metadata, null, 2), { flag: "wx" });
50
+ await writeFile(join(dir, OPS_FILE), JSON.stringify(ops, null, 2), { flag: "wx" });
51
+ }
52
+ /**
53
+ * Copy a list of files into `destDir`, optionally renaming each one.
54
+ *
55
+ * The destination directory is created (with `recursive: true`) if it
56
+ * does not already exist. Each source path is copied byte-for-byte into
57
+ * `destDir/<destName>`; missing sources throw `ENOENT`. The helper is
58
+ * intentionally generic: callers own the list of files (e.g. a contract
59
+ * emitter's emitted output) and the naming convention (e.g. renaming
60
+ * the destination contract to `end-contract.*` and the source contract
61
+ * to `start-contract.*`).
62
+ */
63
+ async function copyFilesWithRename(destDir, files) {
64
+ await mkdir(destDir, { recursive: true });
65
+ for (const file of files) {
66
+ if (basename(file.destName) !== file.destName) throw errorInvalidDestName(file.destName);
67
+ await copyFile(file.sourcePath, join(destDir, file.destName));
68
+ }
69
+ }
70
+ async function writeMigrationMetadata(dir, metadata) {
71
+ await writeFile(join(dir, MANIFEST_FILE), `${JSON.stringify(metadata, null, 2)}\n`);
72
+ }
73
+ async function writeMigrationOps(dir, ops) {
74
+ await writeFile(join(dir, OPS_FILE), `${JSON.stringify(ops, null, 2)}\n`);
75
+ }
76
+ async function readMigrationPackage(dir) {
77
+ const manifestPath = join(dir, MANIFEST_FILE);
78
+ const opsPath = join(dir, OPS_FILE);
79
+ let manifestRaw;
80
+ try {
81
+ manifestRaw = await readFile(manifestPath, "utf-8");
82
+ } catch (error) {
83
+ if (hasErrnoCode(error, "ENOENT")) throw errorMissingFile(MANIFEST_FILE, dir);
84
+ throw error;
85
+ }
86
+ let opsRaw;
87
+ try {
88
+ opsRaw = await readFile(opsPath, "utf-8");
89
+ } catch (error) {
90
+ if (hasErrnoCode(error, "ENOENT")) throw errorMissingFile(OPS_FILE, dir);
91
+ throw error;
92
+ }
93
+ let metadata;
94
+ try {
95
+ metadata = JSON.parse(manifestRaw);
96
+ } catch (e) {
97
+ throw errorInvalidJson(manifestPath, e instanceof Error ? e.message : String(e));
98
+ }
99
+ let ops;
100
+ try {
101
+ ops = JSON.parse(opsRaw);
102
+ } catch (e) {
103
+ throw errorInvalidJson(opsPath, e instanceof Error ? e.message : String(e));
104
+ }
105
+ validateMetadata(metadata, manifestPath);
106
+ validateOps(ops, opsPath);
107
+ const derivedInvariants = deriveProvidedInvariants(ops);
108
+ if (!arraysEqual(metadata.providedInvariants, derivedInvariants)) throw errorProvidedInvariantsMismatch(manifestPath, metadata.providedInvariants, derivedInvariants);
109
+ const pkg = {
110
+ dirName: basename(dir),
111
+ dirPath: dir,
112
+ metadata,
113
+ ops
114
+ };
115
+ const verification = verifyMigrationHash(pkg);
116
+ if (!verification.ok) throw errorMigrationHashMismatch(dir, verification.storedHash, verification.computedHash);
117
+ return pkg;
118
+ }
119
+ function arraysEqual(a, b) {
120
+ if (a.length !== b.length) return false;
121
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
122
+ return true;
123
+ }
124
+ function validateMetadata(metadata, filePath) {
125
+ const result = MigrationMetadataSchema(metadata);
126
+ if (result instanceof type.errors) throw errorInvalidManifest(filePath, result.summary);
127
+ }
128
+ function validateOps(ops, filePath) {
129
+ const result = MigrationOpsSchema(ops);
130
+ if (result instanceof type.errors) throw errorInvalidManifest(filePath, result.summary);
131
+ }
132
+ async function readMigrationsDir(migrationsRoot) {
133
+ let entries;
134
+ try {
135
+ entries = await readdir(migrationsRoot);
136
+ } catch (error) {
137
+ if (hasErrnoCode(error, "ENOENT")) return [];
138
+ throw error;
139
+ }
140
+ const packages = [];
141
+ for (const entry of entries.sort()) {
142
+ const entryPath = join(migrationsRoot, entry);
143
+ if (!(await stat(entryPath)).isDirectory()) continue;
144
+ const manifestPath = join(entryPath, MANIFEST_FILE);
145
+ try {
146
+ await stat(manifestPath);
147
+ } catch {
148
+ continue;
149
+ }
150
+ packages.push(await readMigrationPackage(entryPath));
151
+ }
152
+ return packages;
153
+ }
154
+ function formatMigrationDirName(timestamp, slug) {
155
+ const sanitized = slug.toLowerCase().replace(/[^a-z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
156
+ if (sanitized.length === 0) throw errorInvalidSlug(slug);
157
+ const truncated = sanitized.slice(0, MAX_SLUG_LENGTH);
158
+ return `${timestamp.getUTCFullYear()}${String(timestamp.getUTCMonth() + 1).padStart(2, "0")}${String(timestamp.getUTCDate()).padStart(2, "0")}T${String(timestamp.getUTCHours()).padStart(2, "0")}${String(timestamp.getUTCMinutes()).padStart(2, "0")}_${truncated}`;
159
+ }
160
+
161
+ //#endregion
162
+ export { copyFilesWithRename, formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationMetadata, writeMigrationOps, writeMigrationPackage };
163
+ //# sourceMappingURL=io.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io.mjs","names":["manifestRaw: string","opsRaw: string","metadata: MigrationMetadata","ops: MigrationOps","pkg: MigrationPackage","entries: string[]","packages: MigrationPackage[]"],"sources":["../../src/io.ts"],"sourcesContent":["import { copyFile, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';\nimport { type } from 'arktype';\nimport { basename, dirname, join } from 'pathe';\nimport {\n errorDirectoryExists,\n errorInvalidDestName,\n errorInvalidJson,\n errorInvalidManifest,\n errorInvalidSlug,\n errorMigrationHashMismatch,\n errorMissingFile,\n errorProvidedInvariantsMismatch,\n} from './errors';\nimport { verifyMigrationHash } from './hash';\nimport { deriveProvidedInvariants } from './invariants';\nimport type { MigrationMetadata } from './metadata';\nimport { MigrationOpsSchema } from './op-schema';\nimport type { MigrationOps, MigrationPackage } from './package';\n\nconst MANIFEST_FILE = 'migration.json';\nconst OPS_FILE = 'ops.json';\nconst MAX_SLUG_LENGTH = 64;\n\nfunction hasErrnoCode(error: unknown, code: string): boolean {\n return error instanceof Error && (error as { code?: string }).code === code;\n}\n\nconst MigrationHintsSchema = type({\n used: 'string[]',\n applied: 'string[]',\n plannerVersion: 'string',\n});\n\nconst MigrationMetadataSchema = type({\n '+': 'reject',\n from: 'string > 0 | null',\n to: 'string',\n migrationHash: 'string',\n fromContract: 'object | null',\n toContract: 'object',\n hints: MigrationHintsSchema,\n labels: 'string[]',\n providedInvariants: 'string[]',\n 'authorship?': type({\n 'author?': 'string',\n 'email?': 'string',\n }),\n 'signature?': type({\n keyId: 'string',\n value: 'string',\n }).or('null'),\n createdAt: 'string',\n});\n\nexport async function writeMigrationPackage(\n dir: string,\n metadata: MigrationMetadata,\n ops: MigrationOps,\n): Promise<void> {\n await mkdir(dirname(dir), { recursive: true });\n\n try {\n await mkdir(dir);\n } catch (error) {\n if (hasErrnoCode(error, 'EEXIST')) {\n throw errorDirectoryExists(dir);\n }\n throw error;\n }\n\n await writeFile(join(dir, MANIFEST_FILE), JSON.stringify(metadata, null, 2), {\n flag: 'wx',\n });\n await writeFile(join(dir, OPS_FILE), JSON.stringify(ops, null, 2), { flag: 'wx' });\n}\n\n/**\n * Copy a list of files into `destDir`, optionally renaming each one.\n *\n * The destination directory is created (with `recursive: true`) if it\n * does not already exist. Each source path is copied byte-for-byte into\n * `destDir/<destName>`; missing sources throw `ENOENT`. The helper is\n * intentionally generic: callers own the list of files (e.g. a contract\n * emitter's emitted output) and the naming convention (e.g. renaming\n * the destination contract to `end-contract.*` and the source contract\n * to `start-contract.*`).\n */\nexport async function copyFilesWithRename(\n destDir: string,\n files: readonly { readonly sourcePath: string; readonly destName: string }[],\n): Promise<void> {\n await mkdir(destDir, { recursive: true });\n for (const file of files) {\n if (basename(file.destName) !== file.destName) {\n throw errorInvalidDestName(file.destName);\n }\n await copyFile(file.sourcePath, join(destDir, file.destName));\n }\n}\n\nexport async function writeMigrationMetadata(\n dir: string,\n metadata: MigrationMetadata,\n): Promise<void> {\n await writeFile(join(dir, MANIFEST_FILE), `${JSON.stringify(metadata, null, 2)}\\n`);\n}\n\nexport async function writeMigrationOps(dir: string, ops: MigrationOps): Promise<void> {\n await writeFile(join(dir, OPS_FILE), `${JSON.stringify(ops, null, 2)}\\n`);\n}\n\nexport async function readMigrationPackage(dir: string): Promise<MigrationPackage> {\n const manifestPath = join(dir, MANIFEST_FILE);\n const opsPath = join(dir, OPS_FILE);\n\n let manifestRaw: string;\n try {\n manifestRaw = await readFile(manifestPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorMissingFile(MANIFEST_FILE, dir);\n }\n throw error;\n }\n\n let opsRaw: string;\n try {\n opsRaw = await readFile(opsPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorMissingFile(OPS_FILE, dir);\n }\n throw error;\n }\n\n let metadata: MigrationMetadata;\n try {\n metadata = JSON.parse(manifestRaw);\n } catch (e) {\n throw errorInvalidJson(manifestPath, e instanceof Error ? e.message : String(e));\n }\n\n let ops: MigrationOps;\n try {\n ops = JSON.parse(opsRaw);\n } catch (e) {\n throw errorInvalidJson(opsPath, e instanceof Error ? e.message : String(e));\n }\n\n validateMetadata(metadata, manifestPath);\n validateOps(ops, opsPath);\n\n // Re-derive before the hash check so format/duplicate diagnostics\n // fire with their dedicated codes rather than as a generic hash mismatch.\n const derivedInvariants = deriveProvidedInvariants(ops);\n if (!arraysEqual(metadata.providedInvariants, derivedInvariants)) {\n throw errorProvidedInvariantsMismatch(\n manifestPath,\n metadata.providedInvariants,\n derivedInvariants,\n );\n }\n\n const pkg: MigrationPackage = {\n dirName: basename(dir),\n dirPath: dir,\n metadata,\n ops,\n };\n\n const verification = verifyMigrationHash(pkg);\n if (!verification.ok) {\n throw errorMigrationHashMismatch(dir, verification.storedHash, verification.computedHash);\n }\n\n return pkg;\n}\n\nfunction arraysEqual(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\nfunction validateMetadata(\n metadata: unknown,\n filePath: string,\n): asserts metadata is MigrationMetadata {\n const result = MigrationMetadataSchema(metadata);\n if (result instanceof type.errors) {\n throw errorInvalidManifest(filePath, result.summary);\n }\n}\n\nfunction validateOps(ops: unknown, filePath: string): asserts ops is MigrationOps {\n const result = MigrationOpsSchema(ops);\n if (result instanceof type.errors) {\n throw errorInvalidManifest(filePath, result.summary);\n }\n}\n\nexport async function readMigrationsDir(\n migrationsRoot: string,\n): Promise<readonly MigrationPackage[]> {\n let entries: string[];\n try {\n entries = await readdir(migrationsRoot);\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n return [];\n }\n throw error;\n }\n\n const packages: MigrationPackage[] = [];\n\n for (const entry of entries.sort()) {\n const entryPath = join(migrationsRoot, entry);\n const entryStat = await stat(entryPath);\n if (!entryStat.isDirectory()) continue;\n\n const manifestPath = join(entryPath, MANIFEST_FILE);\n try {\n await stat(manifestPath);\n } catch {\n continue; // skip non-migration directories\n }\n\n packages.push(await readMigrationPackage(entryPath));\n }\n\n return packages;\n}\n\nexport function formatMigrationDirName(timestamp: Date, slug: string): string {\n const sanitized = slug\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '');\n\n if (sanitized.length === 0) {\n throw errorInvalidSlug(slug);\n }\n\n const truncated = sanitized.slice(0, MAX_SLUG_LENGTH);\n\n const y = timestamp.getUTCFullYear();\n const mo = String(timestamp.getUTCMonth() + 1).padStart(2, '0');\n const d = String(timestamp.getUTCDate()).padStart(2, '0');\n const h = String(timestamp.getUTCHours()).padStart(2, '0');\n const mi = String(timestamp.getUTCMinutes()).padStart(2, '0');\n\n return `${y}${mo}${d}T${h}${mi}_${truncated}`;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { n as MigrationMetadata, t as MigrationHints } from "../metadata-BP1cmU7Z.mjs";
2
+ export { type MigrationHints, type MigrationMetadata };
@@ -0,0 +1 @@
1
+ export { };
@@ -1,18 +1,39 @@
1
- import { n as MigrationChainEntry, r as MigrationGraph, t as MigrationBundle } from "../types-DyGXcWWp.mjs";
1
+ import { n as MigrationGraph, t as MigrationEdge } from "../graph-BHPv-9Gl.mjs";
2
+ import { n as MigrationPackage } from "../package-5HCCg0z-.mjs";
2
3
 
3
- //#region src/dag.d.ts
4
- declare function reconstructGraph(packages: readonly MigrationBundle[]): MigrationGraph;
4
+ //#region src/migration-graph.d.ts
5
+ declare function reconstructGraph(packages: readonly MigrationPackage[]): MigrationGraph;
5
6
  /**
6
7
  * Find the shortest path from `fromHash` to `toHash` using BFS over the
7
8
  * contract-hash graph. Returns the ordered list of edges, or null if no path
8
9
  * exists. Returns an empty array when `fromHash === toHash` (no-op).
9
10
  *
10
11
  * Neighbor ordering is deterministic via the tie-break sort key:
11
- * label priority → createdAt → to → migrationId.
12
+ * label priority → createdAt → to → migrationHash.
12
13
  */
13
- declare function findPath(graph: MigrationGraph, fromHash: string, toHash: string): readonly MigrationChainEntry[] | null;
14
+ declare function findPath(graph: MigrationGraph, fromHash: string, toHash: string): readonly MigrationEdge[] | null;
15
+ /**
16
+ * Find the shortest path from `fromHash` to `toHash` whose edges collectively
17
+ * cover every invariant in `required`. Returns `null` when no such path exists
18
+ * (either `fromHash`→`toHash` is structurally unreachable, or every reachable
19
+ * path leaves at least one required invariant uncovered). When `required` is
20
+ * empty, delegates to `findPath` so the result is byte-identical for that case.
21
+ *
22
+ * Algorithm: BFS over `(node, coveredSubset)` states with state-level dedup.
23
+ * The covered subset is a `Set<string>` of invariant ids; the state's dedup
24
+ * key is `${node}\0${[...covered].sort().join('\0')}`. State keys distinguish
25
+ * distinct `(node, covered)` tuples regardless of node-name length because
26
+ * `\0` cannot appear in any invariant id (validation rejects whitespace and
27
+ * control chars at authoring time).
28
+ *
29
+ * Neighbour ordering when `required ≠ ∅`: edges covering ≥1 still-needed
30
+ * invariant come first, with `labelPriority → createdAt → to → migrationHash`
31
+ * as the secondary key. The heuristic steers BFS toward the satisfying path;
32
+ * correctness (shortest, deterministic) does not depend on it.
33
+ */
34
+ declare function findPathWithInvariants(graph: MigrationGraph, fromHash: string, toHash: string, required: ReadonlySet<string>): readonly MigrationEdge[] | null;
14
35
  interface PathDecision {
15
- readonly selectedPath: readonly MigrationChainEntry[];
36
+ readonly selectedPath: readonly MigrationEdge[];
16
37
  readonly fromHash: string;
17
38
  readonly toHash: string;
18
39
  readonly alternativeCount: number;
@@ -43,9 +64,9 @@ declare function findLeaf(graph: MigrationGraph): string | null;
43
64
  * to the single target. Returns null for an empty graph.
44
65
  * Throws AMBIGUOUS_TARGET if the graph has multiple branch tips.
45
66
  */
46
- declare function findLatestMigration(graph: MigrationGraph): MigrationChainEntry | null;
67
+ declare function findLatestMigration(graph: MigrationGraph): MigrationEdge | null;
47
68
  declare function detectCycles(graph: MigrationGraph): readonly string[][];
48
- declare function detectOrphans(graph: MigrationGraph): readonly MigrationChainEntry[];
69
+ declare function detectOrphans(graph: MigrationGraph): readonly MigrationEdge[];
49
70
  //#endregion
50
- export { type PathDecision, detectCycles, detectOrphans, findLatestMigration, findLeaf, findPath, findPathWithDecision, findReachableLeaves, reconstructGraph };
51
- //# sourceMappingURL=dag.d.mts.map
71
+ export { type PathDecision, detectCycles, detectOrphans, findLatestMigration, findLeaf, findPath, findPathWithDecision, findPathWithInvariants, findReachableLeaves, reconstructGraph };
72
+ //# sourceMappingURL=migration-graph.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-graph.d.mts","names":[],"sources":["../../src/migration-graph.ts"],"sourcesContent":[],"mappings":";;;;iBAsCgB,gBAAA,oBAAoC,qBAAqB;;AAAzE;AAkFA;AAgDA;;;;;AA0FiB,iBA1ID,QAAA,CA2IkB,KAAA,EA1IzB,cA0IsC,EAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,SAvInC,aAuImC,EAAA,GAAA,IAAA;AAY/C;AA8FA;AAkBA;AAwCA;AAQA;AA8DA;;;;;;;;;;;;;;iBArUgB,sBAAA,QACP,4DAGG,+BACA;UAqFK,YAAA;kCACiB;;;;;;;;;;;iBAYlB,oBAAA,QACP,qEAIN;;;;;iBAyFa,mBAAA,QAA2B;;;;;;;;;iBAkB3B,QAAA,QAAgB;;;;;;iBAwChB,mBAAA,QAA2B,iBAAiB;iBAQ5C,YAAA,QAAoB;iBA8DpB,aAAA,QAAqB,0BAA0B"}
@@ -1,5 +1,5 @@
1
- import { h as errorSameSourceAndTarget, i as errorDuplicateMigrationId, m as errorNoTarget, n as errorAmbiguousTarget, p as errorNoInitialMigration } from "../errors-BKbRGCJM.mjs";
2
- import { t as EMPTY_CONTRACT_HASH } from "../constants-BRi0X7B_.mjs";
1
+ import { _ as errorNoInitialMigration, a as errorDuplicateMigrationHash, b as errorSameSourceAndTarget, n as errorAmbiguousTarget, v as errorNoTarget } from "../errors-Bl3cKiM8.mjs";
2
+ import { t as EMPTY_CONTRACT_HASH } from "../constants-BQEHsaEx.mjs";
3
3
  import { ifDefined } from "@prisma-next/utils/defined";
4
4
 
5
5
  //#region src/queue.ts
@@ -36,61 +36,65 @@ var Queue = class {
36
36
 
37
37
  //#endregion
38
38
  //#region src/graph-ops.ts
39
- /**
40
- * Generic breadth-first traversal.
41
- *
42
- * Direction (forward/reverse) is expressed by the caller's `neighbours`
43
- * closure: return `{ next, edge }` pairs where `next` is the node to visit
44
- * next and `edge` is the edge that connects them. Callers that don't need
45
- * path reconstruction can ignore the `parent`/`incomingEdge` fields of each
46
- * yielded step.
47
- *
48
- * Stops are intrinsic — callers `break` out of the `for..of` loop when
49
- * they've found what they're looking for.
50
- *
51
- * `ordering`, if provided, controls the order in which neighbours of each
52
- * node are enqueued. Only matters for path-finding: a deterministic ordering
53
- * makes BFS return a deterministic shortest path when multiple exist.
54
- */
55
- function* bfs(starts, neighbours, ordering) {
39
+ function* bfs(starts, neighbours, key = (state) => state) {
56
40
  const visited = /* @__PURE__ */ new Set();
57
41
  const parentMap = /* @__PURE__ */ new Map();
58
42
  const queue = new Queue();
59
- for (const start of starts) if (!visited.has(start)) {
60
- visited.add(start);
61
- queue.push(start);
43
+ for (const start of starts) {
44
+ const k = key(start);
45
+ if (!visited.has(k)) {
46
+ visited.add(k);
47
+ queue.push({
48
+ state: start,
49
+ key: k
50
+ });
51
+ }
62
52
  }
63
53
  while (!queue.isEmpty) {
64
- const current = queue.shift();
65
- const parentInfo = parentMap.get(current);
54
+ const { state: current, key: curKey } = queue.shift();
55
+ const parentInfo = parentMap.get(curKey);
66
56
  yield {
67
- node: current,
57
+ state: current,
68
58
  parent: parentInfo?.parent ?? null,
69
59
  incomingEdge: parentInfo?.edge ?? null
70
60
  };
71
- const items = neighbours(current);
72
- const toVisit = ordering ? ordering([...items]) : items;
73
- for (const { next, edge } of toVisit) if (!visited.has(next)) {
74
- visited.add(next);
75
- parentMap.set(next, {
76
- parent: current,
77
- edge
78
- });
79
- queue.push(next);
61
+ for (const { next, edge } of neighbours(current)) {
62
+ const k = key(next);
63
+ if (!visited.has(k)) {
64
+ visited.add(k);
65
+ parentMap.set(k, {
66
+ parent: current,
67
+ edge
68
+ });
69
+ queue.push({
70
+ state: next,
71
+ key: k
72
+ });
73
+ }
80
74
  }
81
75
  }
82
76
  }
83
77
 
84
78
  //#endregion
85
- //#region src/dag.ts
86
- /** Forward-edge neighbours for BFS: edge `e` from `n` visits `e.to` next. */
79
+ //#region src/migration-graph.ts
80
+ /** Forward-edge neighbours: edge `e` from `n` visits `e.to` next. */
87
81
  function forwardNeighbours(graph, node) {
88
82
  return (graph.forwardChain.get(node) ?? []).map((edge) => ({
89
83
  next: edge.to,
90
84
  edge
91
85
  }));
92
86
  }
93
- /** Reverse-edge neighbours for BFS: edge `e` from `n` visits `e.from` next. */
87
+ /**
88
+ * Forward-edge neighbours, sorted by the deterministic tie-break.
89
+ * Used by path-finding so the resulting shortest path is stable across runs.
90
+ */
91
+ function sortedForwardNeighbours(graph, node) {
92
+ return [...graph.forwardChain.get(node) ?? []].sort(compareTieBreak).map((edge) => ({
93
+ next: edge.to,
94
+ edge
95
+ }));
96
+ }
97
+ /** Reverse-edge neighbours: edge `e` from `n` visits `e.from` next. */
94
98
  function reverseNeighbours(graph, node) {
95
99
  return (graph.reverseChain.get(node) ?? []).map((edge) => ({
96
100
  next: edge.from,
@@ -106,22 +110,24 @@ function reconstructGraph(packages) {
106
110
  const nodes = /* @__PURE__ */ new Set();
107
111
  const forwardChain = /* @__PURE__ */ new Map();
108
112
  const reverseChain = /* @__PURE__ */ new Map();
109
- const migrationById = /* @__PURE__ */ new Map();
113
+ const migrationByHash = /* @__PURE__ */ new Map();
110
114
  for (const pkg of packages) {
111
- const { from, to } = pkg.manifest;
112
- if (from === to) throw errorSameSourceAndTarget(pkg.dirName, from);
115
+ const from = pkg.metadata.from ?? EMPTY_CONTRACT_HASH;
116
+ const { to } = pkg.metadata;
117
+ if (from === to) throw errorSameSourceAndTarget(pkg.dirPath, from);
113
118
  nodes.add(from);
114
119
  nodes.add(to);
115
120
  const migration = {
116
121
  from,
117
122
  to,
118
- migrationId: pkg.manifest.migrationId,
123
+ migrationHash: pkg.metadata.migrationHash,
119
124
  dirName: pkg.dirName,
120
- createdAt: pkg.manifest.createdAt,
121
- labels: pkg.manifest.labels
125
+ createdAt: pkg.metadata.createdAt,
126
+ labels: pkg.metadata.labels,
127
+ invariants: pkg.metadata.providedInvariants
122
128
  };
123
- if (migrationById.has(migration.migrationId)) throw errorDuplicateMigrationId(migration.migrationId);
124
- migrationById.set(migration.migrationId, migration);
129
+ if (migrationByHash.has(migration.migrationHash)) throw errorDuplicateMigrationHash(migration.migrationHash);
130
+ migrationByHash.set(migration.migrationHash, migration);
125
131
  appendEdge(forwardChain, from, migration);
126
132
  appendEdge(reverseChain, to, migration);
127
133
  }
@@ -129,7 +135,7 @@ function reconstructGraph(packages) {
129
135
  nodes,
130
136
  forwardChain,
131
137
  reverseChain,
132
- migrationById
138
+ migrationByHash
133
139
  };
134
140
  }
135
141
  const LABEL_PRIORITY = {
@@ -152,32 +158,28 @@ function compareTieBreak(a, b) {
152
158
  if (ca !== 0) return ca;
153
159
  const tc = a.to.localeCompare(b.to);
154
160
  if (tc !== 0) return tc;
155
- return a.migrationId.localeCompare(b.migrationId);
161
+ return a.migrationHash.localeCompare(b.migrationHash);
156
162
  }
157
163
  function sortedNeighbors(edges) {
158
164
  return [...edges].sort(compareTieBreak);
159
165
  }
160
- /** Ordering adapter for `bfs` — sorts `{next, edge}` pairs by tie-break. */
161
- function bfsOrdering(items) {
162
- return items.slice().sort((a, b) => compareTieBreak(a.edge, b.edge));
163
- }
164
166
  /**
165
167
  * Find the shortest path from `fromHash` to `toHash` using BFS over the
166
168
  * contract-hash graph. Returns the ordered list of edges, or null if no path
167
169
  * exists. Returns an empty array when `fromHash === toHash` (no-op).
168
170
  *
169
171
  * Neighbor ordering is deterministic via the tie-break sort key:
170
- * label priority → createdAt → to → migrationId.
172
+ * label priority → createdAt → to → migrationHash.
171
173
  */
172
174
  function findPath(graph, fromHash, toHash) {
173
175
  if (fromHash === toHash) return [];
174
176
  const parents = /* @__PURE__ */ new Map();
175
- for (const step of bfs([fromHash], (n) => forwardNeighbours(graph, n), bfsOrdering)) {
176
- if (step.parent !== null && step.incomingEdge !== null) parents.set(step.node, {
177
+ for (const step of bfs([fromHash], (n) => sortedForwardNeighbours(graph, n))) {
178
+ if (step.parent !== null && step.incomingEdge !== null) parents.set(step.state, {
177
179
  parent: step.parent,
178
180
  edge: step.incomingEdge
179
181
  });
180
- if (step.node === toHash) {
182
+ if (step.state === toHash) {
181
183
  const path = [];
182
184
  let cur = toHash;
183
185
  let p = parents.get(cur);
@@ -193,12 +195,90 @@ function findPath(graph, fromHash, toHash) {
193
195
  return null;
194
196
  }
195
197
  /**
198
+ * Find the shortest path from `fromHash` to `toHash` whose edges collectively
199
+ * cover every invariant in `required`. Returns `null` when no such path exists
200
+ * (either `fromHash`→`toHash` is structurally unreachable, or every reachable
201
+ * path leaves at least one required invariant uncovered). When `required` is
202
+ * empty, delegates to `findPath` so the result is byte-identical for that case.
203
+ *
204
+ * Algorithm: BFS over `(node, coveredSubset)` states with state-level dedup.
205
+ * The covered subset is a `Set<string>` of invariant ids; the state's dedup
206
+ * key is `${node}\0${[...covered].sort().join('\0')}`. State keys distinguish
207
+ * distinct `(node, covered)` tuples regardless of node-name length because
208
+ * `\0` cannot appear in any invariant id (validation rejects whitespace and
209
+ * control chars at authoring time).
210
+ *
211
+ * Neighbour ordering when `required ≠ ∅`: edges covering ≥1 still-needed
212
+ * invariant come first, with `labelPriority → createdAt → to → migrationHash`
213
+ * as the secondary key. The heuristic steers BFS toward the satisfying path;
214
+ * correctness (shortest, deterministic) does not depend on it.
215
+ */
216
+ function findPathWithInvariants(graph, fromHash, toHash, required) {
217
+ if (required.size === 0) return findPath(graph, fromHash, toHash);
218
+ if (fromHash === toHash) return null;
219
+ const stateKey = (s) => {
220
+ if (s.covered.size === 0) return `${s.node}\0`;
221
+ return `${s.node}\0${[...s.covered].sort().join("\0")}`;
222
+ };
223
+ const neighbours = (s) => {
224
+ const outgoing = graph.forwardChain.get(s.node) ?? [];
225
+ if (outgoing.length === 0) return [];
226
+ return [...outgoing].map((edge) => {
227
+ let useful = false;
228
+ let next = null;
229
+ for (const inv of edge.invariants) if (required.has(inv) && !s.covered.has(inv)) {
230
+ if (next === null) next = new Set(s.covered);
231
+ next.add(inv);
232
+ useful = true;
233
+ }
234
+ return {
235
+ edge,
236
+ useful,
237
+ nextCovered: next ?? s.covered
238
+ };
239
+ }).sort((a, b) => {
240
+ if (a.useful !== b.useful) return a.useful ? -1 : 1;
241
+ return compareTieBreak(a.edge, b.edge);
242
+ }).map(({ edge, nextCovered }) => ({
243
+ next: {
244
+ node: edge.to,
245
+ covered: nextCovered
246
+ },
247
+ edge
248
+ }));
249
+ };
250
+ const parents = /* @__PURE__ */ new Map();
251
+ for (const step of bfs([{
252
+ node: fromHash,
253
+ covered: /* @__PURE__ */ new Set()
254
+ }], neighbours, stateKey)) {
255
+ const curKey = stateKey(step.state);
256
+ if (step.parent !== null && step.incomingEdge !== null) parents.set(curKey, {
257
+ parentKey: stateKey(step.parent),
258
+ edge: step.incomingEdge
259
+ });
260
+ if (step.state.node === toHash && step.state.covered.size === required.size) {
261
+ const path = [];
262
+ let cur = curKey;
263
+ while (cur !== void 0) {
264
+ const p = parents.get(cur);
265
+ if (!p) break;
266
+ path.push(p.edge);
267
+ cur = p.parentKey;
268
+ }
269
+ path.reverse();
270
+ return path;
271
+ }
272
+ }
273
+ return null;
274
+ }
275
+ /**
196
276
  * Reverse-BFS from `toHash` over `reverseChain` to collect every node from
197
277
  * which `toHash` is reachable (inclusive of `toHash` itself).
198
278
  */
199
279
  function collectNodesReachingTarget(graph, toHash) {
200
280
  const reached = /* @__PURE__ */ new Set();
201
- for (const step of bfs([toHash], (n) => reverseNeighbours(graph, n))) reached.add(step.node);
281
+ for (const step of bfs([toHash], (n) => reverseNeighbours(graph, n))) reached.add(step.state);
202
282
  return reached;
203
283
  }
204
284
  /**
@@ -226,8 +306,8 @@ function findPathWithDecision(graph, fromHash, toHash, refName) {
226
306
  if (reachable.length > 1) {
227
307
  alternativeCount += reachable.length - 1;
228
308
  const sorted = sortedNeighbors(reachable);
229
- if (sorted[0] && sorted[0].migrationId === edge.migrationId) {
230
- if (reachable.some((e) => e.migrationId !== edge.migrationId)) tieBreakReasons.push(`at ${edge.from}: ${reachable.length} candidates, selected by tie-break`);
309
+ if (sorted[0] && sorted[0].migrationHash === edge.migrationHash) {
310
+ if (reachable.some((e) => e.migrationHash !== edge.migrationHash)) tieBreakReasons.push(`at ${edge.from}: ${reachable.length} candidates, selected by tie-break`);
231
311
  }
232
312
  }
233
313
  }
@@ -248,7 +328,7 @@ function findPathWithDecision(graph, fromHash, toHash, refName) {
248
328
  function findDivergencePoint(graph, fromHash, leaves) {
249
329
  const ancestorSets = leaves.map((leaf) => {
250
330
  const ancestors = /* @__PURE__ */ new Set();
251
- for (const step of bfs([leaf], (n) => reverseNeighbours(graph, n))) ancestors.add(step.node);
331
+ for (const step of bfs([leaf], (n) => reverseNeighbours(graph, n))) ancestors.add(step.state);
252
332
  return ancestors;
253
333
  });
254
334
  const commonAncestors = [...ancestorSets[0] ?? []].filter((node) => ancestorSets.every((s) => s.has(node)));
@@ -270,7 +350,7 @@ function findDivergencePoint(graph, fromHash, leaves) {
270
350
  */
271
351
  function findReachableLeaves(graph, fromHash) {
272
352
  const leaves = [];
273
- for (const step of bfs([fromHash], (n) => forwardNeighbours(graph, n))) if (!graph.forwardChain.get(step.node)?.length) leaves.push(step.node);
353
+ for (const step of bfs([fromHash], (n) => forwardNeighbours(graph, n))) if (!graph.forwardChain.get(step.state)?.length) leaves.push(step.state);
274
354
  return leaves;
275
355
  }
276
356
  /**
@@ -375,12 +455,12 @@ function detectOrphans(graph) {
375
455
  for (const edges of graph.forwardChain.values()) for (const edge of edges) allTargets.add(edge.to);
376
456
  for (const node of graph.nodes) if (!allTargets.has(node)) startNodes.push(node);
377
457
  }
378
- for (const step of bfs(startNodes, (n) => forwardNeighbours(graph, n))) reachable.add(step.node);
458
+ for (const step of bfs(startNodes, (n) => forwardNeighbours(graph, n))) reachable.add(step.state);
379
459
  const orphans = [];
380
460
  for (const [from, migrations] of graph.forwardChain) if (!reachable.has(from)) orphans.push(...migrations);
381
461
  return orphans;
382
462
  }
383
463
 
384
464
  //#endregion
385
- export { detectCycles, detectOrphans, findLatestMigration, findLeaf, findPath, findPathWithDecision, findReachableLeaves, reconstructGraph };
386
- //# sourceMappingURL=dag.mjs.map
465
+ export { detectCycles, detectOrphans, findLatestMigration, findLeaf, findPath, findPathWithDecision, findPathWithInvariants, findReachableLeaves, reconstructGraph };
466
+ //# sourceMappingURL=migration-graph.mjs.map