@prisma-next/migration-tools 0.5.0-dev.6 → 0.5.0-dev.61
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.
- package/README.md +34 -22
- package/dist/{constants-BRi0X7B_.mjs → constants-BQEHsaEx.mjs} +1 -1
- package/dist/{constants-BRi0X7B_.mjs.map → constants-BQEHsaEx.mjs.map} +1 -1
- package/dist/errors-CfmjBeK0.mjs +272 -0
- package/dist/errors-CfmjBeK0.mjs.map +1 -0
- package/dist/exports/constants.mjs +1 -1
- package/dist/exports/errors.d.mts +63 -0
- package/dist/exports/errors.d.mts.map +1 -0
- package/dist/exports/errors.mjs +3 -0
- package/dist/exports/graph.d.mts +2 -0
- package/dist/exports/graph.mjs +1 -0
- package/dist/exports/hash.d.mts +52 -0
- package/dist/exports/hash.d.mts.map +1 -0
- package/dist/exports/hash.mjs +3 -0
- package/dist/exports/invariants.d.mts +24 -0
- package/dist/exports/invariants.d.mts.map +1 -0
- package/dist/exports/invariants.mjs +4 -0
- package/dist/exports/io.d.mts +7 -6
- package/dist/exports/io.d.mts.map +1 -1
- package/dist/exports/io.mjs +162 -2
- package/dist/exports/io.mjs.map +1 -0
- package/dist/exports/metadata.d.mts +2 -0
- package/dist/exports/metadata.mjs +1 -0
- package/dist/exports/migration-graph.d.mts +124 -0
- package/dist/exports/migration-graph.d.mts.map +1 -0
- package/dist/exports/migration-graph.mjs +526 -0
- package/dist/exports/migration-graph.mjs.map +1 -0
- package/dist/exports/migration-ts.mjs +1 -1
- package/dist/exports/migration.d.mts +15 -14
- package/dist/exports/migration.d.mts.map +1 -1
- package/dist/exports/migration.mjs +69 -41
- package/dist/exports/migration.mjs.map +1 -1
- package/dist/exports/package.d.mts +2 -0
- package/dist/exports/package.mjs +1 -0
- package/dist/exports/refs.mjs +2 -2
- package/dist/graph-BHPv-9Gl.d.mts +28 -0
- package/dist/graph-BHPv-9Gl.d.mts.map +1 -0
- package/dist/hash-BARZdVgW.mjs +76 -0
- package/dist/hash-BARZdVgW.mjs.map +1 -0
- package/dist/invariants-30VA65sB.mjs +42 -0
- package/dist/invariants-30VA65sB.mjs.map +1 -0
- package/dist/metadata-BP1cmU7Z.d.mts +50 -0
- package/dist/metadata-BP1cmU7Z.d.mts.map +1 -0
- package/dist/op-schema-DZKFua46.mjs +14 -0
- package/dist/op-schema-DZKFua46.mjs.map +1 -0
- package/dist/package-5HCCg0z-.d.mts +21 -0
- package/dist/package-5HCCg0z-.d.mts.map +1 -0
- package/package.json +31 -14
- package/src/errors.ts +210 -17
- package/src/exports/errors.ts +7 -0
- package/src/exports/graph.ts +1 -0
- package/src/exports/hash.ts +2 -0
- package/src/exports/invariants.ts +1 -0
- package/src/exports/io.ts +1 -1
- package/src/exports/metadata.ts +1 -0
- package/src/exports/{dag.ts → migration-graph.ts} +3 -2
- package/src/exports/migration.ts +0 -1
- package/src/exports/package.ts +1 -0
- package/src/graph-ops.ts +57 -30
- package/src/graph.ts +25 -0
- package/src/hash.ts +91 -0
- package/src/invariants.ts +45 -0
- package/src/io.ts +57 -31
- package/src/metadata.ts +41 -0
- package/src/migration-base.ts +97 -56
- package/src/migration-graph.ts +676 -0
- package/src/op-schema.ts +11 -0
- package/src/package.ts +18 -0
- package/dist/attestation-BnzTb0Qp.mjs +0 -65
- package/dist/attestation-BnzTb0Qp.mjs.map +0 -1
- package/dist/errors-BmiSgz1j.mjs +0 -160
- package/dist/errors-BmiSgz1j.mjs.map +0 -1
- package/dist/exports/attestation.d.mts +0 -37
- package/dist/exports/attestation.d.mts.map +0 -1
- package/dist/exports/attestation.mjs +0 -4
- package/dist/exports/dag.d.mts +0 -51
- package/dist/exports/dag.d.mts.map +0 -1
- package/dist/exports/dag.mjs +0 -386
- package/dist/exports/dag.mjs.map +0 -1
- package/dist/exports/types.d.mts +0 -35
- package/dist/exports/types.d.mts.map +0 -1
- package/dist/exports/types.mjs +0 -3
- package/dist/io-Cd6GLyjK.mjs +0 -153
- package/dist/io-Cd6GLyjK.mjs.map +0 -1
- package/dist/types-DyGXcWWp.d.mts +0 -71
- package/dist/types-DyGXcWWp.d.mts.map +0 -1
- package/src/attestation.ts +0 -81
- package/src/dag.ts +0 -426
- package/src/exports/attestation.ts +0 -2
- package/src/exports/types.ts +0 -10
- package/src/types.ts +0 -66
package/dist/exports/io.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as MigrationMetadata } from "../metadata-BP1cmU7Z.mjs";
|
|
2
|
+
import { n as MigrationPackage, t as MigrationOps } from "../package-5HCCg0z-.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/io.d.ts
|
|
4
|
-
declare function writeMigrationPackage(dir: string,
|
|
5
|
+
declare function writeMigrationPackage(dir: string, metadata: MigrationMetadata, ops: MigrationOps): Promise<void>;
|
|
5
6
|
/**
|
|
6
7
|
* Copy a list of files into `destDir`, optionally renaming each one.
|
|
7
8
|
*
|
|
@@ -17,11 +18,11 @@ declare function copyFilesWithRename(destDir: string, files: readonly {
|
|
|
17
18
|
readonly sourcePath: string;
|
|
18
19
|
readonly destName: string;
|
|
19
20
|
}[]): Promise<void>;
|
|
20
|
-
declare function
|
|
21
|
+
declare function writeMigrationMetadata(dir: string, metadata: MigrationMetadata): Promise<void>;
|
|
21
22
|
declare function writeMigrationOps(dir: string, ops: MigrationOps): Promise<void>;
|
|
22
|
-
declare function readMigrationPackage(dir: string): Promise<
|
|
23
|
-
declare function readMigrationsDir(migrationsRoot: string): Promise<readonly
|
|
23
|
+
declare function readMigrationPackage(dir: string): Promise<MigrationPackage>;
|
|
24
|
+
declare function readMigrationsDir(migrationsRoot: string): Promise<readonly MigrationPackage[]>;
|
|
24
25
|
declare function formatMigrationDirName(timestamp: Date, slug: string): string;
|
|
25
26
|
//#endregion
|
|
26
|
-
export { copyFilesWithRename, formatMigrationDirName, readMigrationPackage, readMigrationsDir,
|
|
27
|
+
export { copyFilesWithRename, formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationMetadata, writeMigrationOps, writeMigrationPackage };
|
|
27
28
|
//# sourceMappingURL=io.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;;iBAsDsB,qBAAA,wBAEV,wBACL,eACJ;;AAJH;;;;;AAiCA;AAaA;AAOA;AAIA;AA4FA;AAiCgB,iBArJM,mBAAA,CAqJ4B,OAAI,EAAA,MAAA,EAAA,KAAA,EAAA,SAAA;;;MAlJnD;iBAUmB,sBAAA,wBAEV,oBACT;iBAImB,iBAAA,mBAAoC,eAAe;iBAInD,oBAAA,eAAmC,QAAQ;iBA4F3C,iBAAA,0BAEnB,iBAAiB;iBA+BJ,sBAAA,YAAkC"}
|
package/dist/exports/io.mjs
CHANGED
|
@@ -1,3 +1,163 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { b as errorProvidedInvariantsMismatch, c as errorInvalidJson, g as errorMissingFile, h as errorMigrationHashMismatch, l as errorInvalidManifest, m as errorInvalidSlug, o as errorInvalidDestName, r as errorDirectoryExists } from "../errors-CfmjBeK0.mjs";
|
|
2
|
+
import { n as verifyMigrationHash } from "../hash-BARZdVgW.mjs";
|
|
3
|
+
import { t as deriveProvidedInvariants } from "../invariants-30VA65sB.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
|
-
|
|
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 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { n as MigrationGraph, t as MigrationEdge } from "../graph-BHPv-9Gl.mjs";
|
|
2
|
+
import { n as MigrationPackage } from "../package-5HCCg0z-.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/migration-graph.d.ts
|
|
5
|
+
declare function reconstructGraph(packages: readonly MigrationPackage[]): MigrationGraph;
|
|
6
|
+
/**
|
|
7
|
+
* Find the shortest path from `fromHash` to `toHash` using BFS over the
|
|
8
|
+
* contract-hash graph. Returns the ordered list of edges, or null if no path
|
|
9
|
+
* exists. Returns an empty array when `fromHash === toHash` (no-op).
|
|
10
|
+
*
|
|
11
|
+
* Neighbor ordering is deterministic via the tie-break sort key:
|
|
12
|
+
* label priority → createdAt → to → migrationHash.
|
|
13
|
+
*/
|
|
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;
|
|
35
|
+
interface PathDecision {
|
|
36
|
+
readonly selectedPath: readonly MigrationEdge[];
|
|
37
|
+
readonly fromHash: string;
|
|
38
|
+
readonly toHash: string;
|
|
39
|
+
readonly alternativeCount: number;
|
|
40
|
+
readonly tieBreakReasons: readonly string[];
|
|
41
|
+
readonly refName?: string;
|
|
42
|
+
/** The caller-supplied required invariant set, sorted ascending. */
|
|
43
|
+
readonly requiredInvariants: readonly string[];
|
|
44
|
+
/**
|
|
45
|
+
* The subset of `requiredInvariants` actually covered by edges on
|
|
46
|
+
* `selectedPath`. Always a subset of `requiredInvariants` (when the path
|
|
47
|
+
* is satisfying, equal to it); always derived from `selectedPath`.
|
|
48
|
+
*/
|
|
49
|
+
readonly satisfiedInvariants: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Outcome of {@link findPathWithDecision}. The pathfinder distinguishes
|
|
53
|
+
* three cases up front so callers don't re-derive structural reachability:
|
|
54
|
+
*
|
|
55
|
+
* - `ok` — a path covering `required` exists; `decision` carries the
|
|
56
|
+
* selection metadata and per-edge invariants.
|
|
57
|
+
* - `unreachable` — `from`→`to` has no structural path. Mapped by callers
|
|
58
|
+
* to the existing no-path / `NO_TARGET` diagnostic.
|
|
59
|
+
* - `unsatisfiable` — `from`→`to` is structurally reachable but no path
|
|
60
|
+
* covers every required invariant. `structuralPath` is the
|
|
61
|
+
* `findPath(graph, from, to)` result, included so callers don't have to
|
|
62
|
+
* recompute it when raising `MIGRATION.NO_INVARIANT_PATH`. `missing` is
|
|
63
|
+
* the subset of `required` that the structural path does *not* cover —
|
|
64
|
+
* correctly accounts for partial coverage when some required invariants
|
|
65
|
+
* are met by the fallback path. Only emitted when `required` is
|
|
66
|
+
* non-empty.
|
|
67
|
+
*/
|
|
68
|
+
type FindPathOutcome = {
|
|
69
|
+
readonly kind: 'ok';
|
|
70
|
+
readonly decision: PathDecision;
|
|
71
|
+
} | {
|
|
72
|
+
readonly kind: 'unreachable';
|
|
73
|
+
} | {
|
|
74
|
+
readonly kind: 'unsatisfiable';
|
|
75
|
+
readonly structuralPath: readonly MigrationEdge[];
|
|
76
|
+
readonly missing: readonly string[];
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Routing context for {@link findPathWithDecision}. Both fields are optional;
|
|
80
|
+
* `refName` is only used to decorate the resulting `PathDecision` for the
|
|
81
|
+
* JSON envelope, and `required` defaults to an empty set (purely structural
|
|
82
|
+
* routing). They are passed via a single options object so the call sites
|
|
83
|
+
* cannot silently swap two adjacent string parameters.
|
|
84
|
+
*/
|
|
85
|
+
interface FindPathWithDecisionOptions {
|
|
86
|
+
readonly refName?: string;
|
|
87
|
+
readonly required?: ReadonlySet<string>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Find the shortest path from `fromHash` to `toHash` and return structured
|
|
91
|
+
* path-decision metadata for machine-readable output. When `required` is
|
|
92
|
+
* non-empty, the returned path is the shortest one whose edges collectively
|
|
93
|
+
* cover every required invariant.
|
|
94
|
+
*
|
|
95
|
+
* The discriminated return type tells the caller *why* a path could not be
|
|
96
|
+
* found, so the CLI can pick the right structured error without re-running
|
|
97
|
+
* a structural BFS.
|
|
98
|
+
*/
|
|
99
|
+
declare function findPathWithDecision(graph: MigrationGraph, fromHash: string, toHash: string, options?: FindPathWithDecisionOptions): FindPathOutcome;
|
|
100
|
+
/**
|
|
101
|
+
* Find all branch tips (nodes with no outgoing edges) reachable from
|
|
102
|
+
* `fromHash` via forward edges.
|
|
103
|
+
*/
|
|
104
|
+
declare function findReachableLeaves(graph: MigrationGraph, fromHash: string): readonly string[];
|
|
105
|
+
/**
|
|
106
|
+
* Find the target contract hash of the migration graph reachable from
|
|
107
|
+
* EMPTY_CONTRACT_HASH. Returns `null` for a graph that has no target
|
|
108
|
+
* state (either empty, or containing only the root with no outgoing
|
|
109
|
+
* edges). Throws NO_INITIAL_MIGRATION if the graph has nodes but none
|
|
110
|
+
* originate from the empty hash, and AMBIGUOUS_TARGET if multiple
|
|
111
|
+
* branch tips exist.
|
|
112
|
+
*/
|
|
113
|
+
declare function findLeaf(graph: MigrationGraph): string | null;
|
|
114
|
+
/**
|
|
115
|
+
* Find the latest migration entry by traversing from EMPTY_CONTRACT_HASH
|
|
116
|
+
* to the single target. Returns null for an empty graph.
|
|
117
|
+
* Throws AMBIGUOUS_TARGET if the graph has multiple branch tips.
|
|
118
|
+
*/
|
|
119
|
+
declare function findLatestMigration(graph: MigrationGraph): MigrationEdge | null;
|
|
120
|
+
declare function detectCycles(graph: MigrationGraph): readonly string[][];
|
|
121
|
+
declare function detectOrphans(graph: MigrationGraph): readonly MigrationEdge[];
|
|
122
|
+
//#endregion
|
|
123
|
+
export { type PathDecision, detectCycles, detectOrphans, findLatestMigration, findLeaf, findPath, findPathWithDecision, findPathWithInvariants, findReachableLeaves, reconstructGraph };
|
|
124
|
+
//# 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;AAqFA;AAgDA;;;;;AA2FiB,iBA3ID,QAAA,CA4IkB,KAAA,EA3IzB,cA2IsC,EAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,SAxInC,aAwImC,EAAA,GAAA,IAAA;AAiC/C;AAgBA;AAeA;;;;;AA0LA;AAkBA;AAwCA;AAQA;AA8DA;;;;;;;;iBAtdgB,sBAAA,QACP,4DAGG,+BACA;UAsFK,YAAA;kCACiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiCtB,eAAA;;qBACkC;;;;;oCAIN;;;;;;;;;;UAWvB,2BAAA;;sBAEK;;;;;;;;;;;;iBAaN,oBAAA,QACP,4DAGE,8BACR;;;;;iBAqLa,mBAAA,QAA2B;;;;;;;;;iBAkB3B,QAAA,QAAgB;;;;;;iBAwChB,mBAAA,QAA2B,iBAAiB;iBAQ5C,YAAA,QAAoB;iBA8DpB,aAAA,QAAqB,0BAA0B"}
|