@prisma-next/migration-tools 0.3.0-dev.84 → 0.3.0-dev.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
- import { a as MigrationOps, i as MigrationManifest, o as MigrationPackage } from "../types-CUnzoaLY.mjs";
1
+ import { c as MigrationManifest, i as MigrationBundle, l as MigrationOps } from "../types-9YQfIg6N.mjs";
2
2
 
3
3
  //#region src/io.d.ts
4
4
  declare function writeMigrationPackage(dir: string, manifest: MigrationManifest, ops: MigrationOps): Promise<void>;
5
- declare function readMigrationPackage(dir: string): Promise<MigrationPackage>;
6
- declare function readMigrationsDir(migrationsRoot: string): Promise<readonly MigrationPackage[]>;
5
+ declare function readMigrationPackage(dir: string): Promise<MigrationBundle>;
6
+ declare function readMigrationsDir(migrationsRoot: string): Promise<readonly MigrationBundle[]>;
7
7
  declare function formatMigrationDirName(timestamp: Date, slug: string): string;
8
8
  //#endregion
9
9
  export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationPackage };
@@ -1 +1 @@
1
- {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;iBAyDsB,qBAAA,wBAEV,wBACL,eACJ;iBAgBmB,oBAAA,eAAmC,QAAQ;AApB3C,iBAsFA,iBAAA,CAtFqB,cAAA,EAAA,MAAA,CAAA,EAwFxC,OAxFwC,CAAA,SAwFvB,gBAxFuB,EAAA,CAAA;AAE/B,iBAqHI,sBAAA,CArHJ,SAAA,EAqHsC,IArHtC,EAAA,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA"}
1
+ {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;iBAwDsB,qBAAA,wBAEV,wBACL,eACJ;iBAgBmB,oBAAA,eAAmC,QAAQ;AApB3C,iBAsFA,iBAAA,CAtFqB,cAAA,EAAA,MAAA,CAAA,EAwFxC,OAxFwC,CAAA,SAwFvB,eAxFuB,EAAA,CAAA;AAE/B,iBAqHI,sBAAA,CArHJ,SAAA,EAqHsC,IArHtC,EAAA,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA"}
@@ -1,3 +1,3 @@
1
- import { i as writeMigrationPackage, n as readMigrationPackage, r as readMigrationsDir, t as formatMigrationDirName } from "../io-Dx98-h0p.mjs";
1
+ import { i as writeMigrationPackage, n as readMigrationPackage, r as readMigrationsDir, t as formatMigrationDirName } from "../io-afog-e8J.mjs";
2
2
 
3
3
  export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationPackage };
@@ -0,0 +1,10 @@
1
+ //#region src/refs.d.ts
2
+ type Refs = Readonly<Record<string, string>>;
3
+ declare function validateRefName(name: string): boolean;
4
+ declare function validateRefValue(value: string): boolean;
5
+ declare function readRefs(refsPath: string): Promise<Refs>;
6
+ declare function writeRefs(refsPath: string, refs: Refs): Promise<void>;
7
+ declare function resolveRef(refs: Refs, name: string): string;
8
+ //#endregion
9
+ export { type Refs, readRefs, resolveRef, validateRefName, validateRefValue, writeRefs };
10
+ //# sourceMappingURL=refs.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refs.d.mts","names":[],"sources":["../../src/refs.ts"],"sourcesContent":[],"mappings":";KAUY,IAAA,GAAO,SAAS;AAAhB,iBAKI,eAAA,CALW,IAAA,EAAA,MAAA,CAAA,EAAA,OAAA;AAKX,iBAQA,gBAAA,CARe,KAAA,EAAA,MAAA,CAAA,EAAA,OAAA;AAQf,iBAaM,QAAA,CAbU,QAAA,EAAA,MAAA,CAAA,EAakB,OAblB,CAa0B,IAb1B,CAAA;AAaV,iBA0BA,SAAA,CA1B4B,QAAO,EAAA,MAAA,EAAA,IAAA,EA0BD,IA1BC,CAAA,EA0BM,OA1BN,CAAA,IAAA,CAAA;AA0BnC,iBAoBN,UAAA,CApB+C,IAAA,EAoB9B,IApBqC,EAAA,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA"}
@@ -0,0 +1,73 @@
1
+ import { c as errorInvalidRefValue, l as errorInvalidRefs, s as errorInvalidRefName, t as MigrationToolsError } from "../errors-CqLiJwqA.mjs";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import { type } from "arktype";
4
+ import { dirname, join } from "pathe";
5
+
6
+ //#region src/refs.ts
7
+ const REF_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;
8
+ const REF_VALUE_PATTERN = /^sha256:(empty|[0-9a-f]{64})$/;
9
+ function validateRefName(name) {
10
+ if (name.length === 0) return false;
11
+ if (name.includes("..")) return false;
12
+ if (name.includes("//")) return false;
13
+ if (name.startsWith(".")) return false;
14
+ return REF_NAME_PATTERN.test(name);
15
+ }
16
+ function validateRefValue(value) {
17
+ return REF_VALUE_PATTERN.test(value);
18
+ }
19
+ const RefsSchema = type("Record<string, string>").narrow((refs, ctx) => {
20
+ for (const [key, value] of Object.entries(refs)) {
21
+ if (!validateRefName(key)) return ctx.mustBe(`valid ref names (invalid: "${key}")`);
22
+ if (!validateRefValue(value)) return ctx.mustBe(`valid contract hashes (invalid value for "${key}": "${value}")`);
23
+ }
24
+ return true;
25
+ });
26
+ async function readRefs(refsPath) {
27
+ let raw;
28
+ try {
29
+ raw = await readFile(refsPath, "utf-8");
30
+ } catch (error) {
31
+ if (error instanceof Error && error.code === "ENOENT") return {};
32
+ throw error;
33
+ }
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(raw);
37
+ } catch {
38
+ throw errorInvalidRefs(refsPath, "Failed to parse as JSON");
39
+ }
40
+ const result = RefsSchema(parsed);
41
+ if (result instanceof type.errors) throw errorInvalidRefs(refsPath, result.summary);
42
+ return result;
43
+ }
44
+ async function writeRefs(refsPath, refs) {
45
+ for (const [key, value] of Object.entries(refs)) {
46
+ if (!validateRefName(key)) throw errorInvalidRefName(key);
47
+ if (!validateRefValue(value)) throw errorInvalidRefValue(value);
48
+ }
49
+ const sorted = Object.fromEntries(Object.entries(refs).sort(([a], [b]) => a.localeCompare(b)));
50
+ const dir = dirname(refsPath);
51
+ await mkdir(dir, { recursive: true });
52
+ const tmpPath = join(dir, `.refs.json.${Date.now()}.tmp`);
53
+ await writeFile(tmpPath, `${JSON.stringify(sorted, null, 2)}\n`);
54
+ await rename(tmpPath, refsPath);
55
+ }
56
+ function resolveRef(refs, name) {
57
+ if (!validateRefName(name)) throw errorInvalidRefName(name);
58
+ const hash = refs[name];
59
+ if (hash === void 0) throw new MigrationToolsError("MIGRATION.UNKNOWN_REF", `Unknown ref "${name}"`, {
60
+ why: `No ref named "${name}" exists in refs.json.`,
61
+ fix: `Available refs: ${Object.keys(refs).join(", ") || "(none)"}. Create a ref with: set the "${name}" key in migrations/refs.json.`,
62
+ details: {
63
+ refName: name,
64
+ availableRefs: Object.keys(refs)
65
+ }
66
+ });
67
+ if (!validateRefValue(hash)) throw errorInvalidRefValue(hash);
68
+ return hash;
69
+ }
70
+
71
+ //#endregion
72
+ export { readRefs, resolveRef, validateRefName, validateRefValue, writeRefs };
73
+ //# sourceMappingURL=refs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refs.mjs","names":["raw: string","parsed: unknown"],"sources":["../../src/refs.ts"],"sourcesContent":["import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { type } from 'arktype';\nimport { dirname, join } from 'pathe';\nimport {\n errorInvalidRefName,\n errorInvalidRefs,\n errorInvalidRefValue,\n MigrationToolsError,\n} from './errors';\n\nexport type Refs = Readonly<Record<string, string>>;\n\nconst REF_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;\nconst REF_VALUE_PATTERN = /^sha256:(empty|[0-9a-f]{64})$/;\n\nexport function validateRefName(name: string): boolean {\n if (name.length === 0) return false;\n if (name.includes('..')) return false;\n if (name.includes('//')) return false;\n if (name.startsWith('.')) return false;\n return REF_NAME_PATTERN.test(name);\n}\n\nexport function validateRefValue(value: string): boolean {\n return REF_VALUE_PATTERN.test(value);\n}\n\nconst RefsSchema = type('Record<string, string>').narrow((refs, ctx) => {\n for (const [key, value] of Object.entries(refs)) {\n if (!validateRefName(key)) return ctx.mustBe(`valid ref names (invalid: \"${key}\")`);\n if (!validateRefValue(value))\n return ctx.mustBe(`valid contract hashes (invalid value for \"${key}\": \"${value}\")`);\n }\n return true;\n});\n\nexport async function readRefs(refsPath: string): Promise<Refs> {\n let raw: string;\n try {\n raw = await readFile(refsPath, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return {};\n }\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw errorInvalidRefs(refsPath, 'Failed to parse as JSON');\n }\n\n const result = RefsSchema(parsed);\n if (result instanceof type.errors) {\n throw errorInvalidRefs(refsPath, result.summary);\n }\n\n return result;\n}\n\nexport async function writeRefs(refsPath: string, refs: Refs): Promise<void> {\n for (const [key, value] of Object.entries(refs)) {\n if (!validateRefName(key)) {\n throw errorInvalidRefName(key);\n }\n if (!validateRefValue(value)) {\n throw errorInvalidRefValue(value);\n }\n }\n\n const sorted = Object.fromEntries(Object.entries(refs).sort(([a], [b]) => a.localeCompare(b)));\n\n const dir = dirname(refsPath);\n await mkdir(dir, { recursive: true });\n\n const tmpPath = join(dir, `.refs.json.${Date.now()}.tmp`);\n await writeFile(tmpPath, `${JSON.stringify(sorted, null, 2)}\\n`);\n await rename(tmpPath, refsPath);\n}\n\nexport function resolveRef(refs: Refs, name: string): string {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const hash = refs[name];\n if (hash === undefined) {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref named \"${name}\" exists in refs.json.`,\n fix: `Available refs: ${Object.keys(refs).join(', ') || '(none)'}. Create a ref with: set the \"${name}\" key in migrations/refs.json.`,\n details: { refName: name, availableRefs: Object.keys(refs) },\n });\n }\n\n if (!validateRefValue(hash)) {\n throw errorInvalidRefValue(hash);\n }\n\n return hash;\n}\n"],"mappings":";;;;;;AAYA,MAAM,mBAAmB;AACzB,MAAM,oBAAoB;AAE1B,SAAgB,gBAAgB,MAAuB;AACrD,KAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,KAAI,KAAK,SAAS,KAAK,CAAE,QAAO;AAChC,KAAI,KAAK,SAAS,KAAK,CAAE,QAAO;AAChC,KAAI,KAAK,WAAW,IAAI,CAAE,QAAO;AACjC,QAAO,iBAAiB,KAAK,KAAK;;AAGpC,SAAgB,iBAAiB,OAAwB;AACvD,QAAO,kBAAkB,KAAK,MAAM;;AAGtC,MAAM,aAAa,KAAK,yBAAyB,CAAC,QAAQ,MAAM,QAAQ;AACtE,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;AAC/C,MAAI,CAAC,gBAAgB,IAAI,CAAE,QAAO,IAAI,OAAO,8BAA8B,IAAI,IAAI;AACnF,MAAI,CAAC,iBAAiB,MAAM,CAC1B,QAAO,IAAI,OAAO,6CAA6C,IAAI,MAAM,MAAM,IAAI;;AAEvF,QAAO;EACP;AAEF,eAAsB,SAAS,UAAiC;CAC9D,IAAIA;AACJ,KAAI;AACF,QAAM,MAAM,SAAS,UAAU,QAAQ;UAChC,OAAO;AACd,MAAI,iBAAiB,SAAU,MAA4B,SAAS,SAClE,QAAO,EAAE;AAEX,QAAM;;CAGR,IAAIC;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AACN,QAAM,iBAAiB,UAAU,0BAA0B;;CAG7D,MAAM,SAAS,WAAW,OAAO;AACjC,KAAI,kBAAkB,KAAK,OACzB,OAAM,iBAAiB,UAAU,OAAO,QAAQ;AAGlD,QAAO;;AAGT,eAAsB,UAAU,UAAkB,MAA2B;AAC3E,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;AAC/C,MAAI,CAAC,gBAAgB,IAAI,CACvB,OAAM,oBAAoB,IAAI;AAEhC,MAAI,CAAC,iBAAiB,MAAM,CAC1B,OAAM,qBAAqB,MAAM;;CAIrC,MAAM,SAAS,OAAO,YAAY,OAAO,QAAQ,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;CAE9F,MAAM,MAAM,QAAQ,SAAS;AAC7B,OAAM,MAAM,KAAK,EAAE,WAAW,MAAM,CAAC;CAErC,MAAM,UAAU,KAAK,KAAK,cAAc,KAAK,KAAK,CAAC,MAAM;AACzD,OAAM,UAAU,SAAS,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,IAAI;AAChE,OAAM,OAAO,SAAS,SAAS;;AAGjC,SAAgB,WAAW,MAAY,MAAsB;AAC3D,KAAI,CAAC,gBAAgB,KAAK,CACxB,OAAM,oBAAoB,KAAK;CAGjC,MAAM,OAAO,KAAK;AAClB,KAAI,SAAS,OACX,OAAM,IAAI,oBAAoB,yBAAyB,gBAAgB,KAAK,IAAI;EAC9E,KAAK,iBAAiB,KAAK;EAC3B,KAAK,mBAAmB,OAAO,KAAK,KAAK,CAAC,KAAK,KAAK,IAAI,SAAS,gCAAgC,KAAK;EACtG,SAAS;GAAE,SAAS;GAAM,eAAe,OAAO,KAAK,KAAK;GAAE;EAC7D,CAAC;AAGJ,KAAI,CAAC,iBAAiB,KAAK,CACzB,OAAM,qBAAqB,KAAK;AAGlC,QAAO"}
@@ -1,4 +1,4 @@
1
- import { a as MigrationOps, i as MigrationManifest, n as MigrationGraph, o as MigrationPackage, r as MigrationHints, t as MigrationChainEntry } from "../types-CUnzoaLY.mjs";
1
+ import { a as MigrationChainEntry, c as MigrationManifest, i as MigrationBundle, l as MigrationOps, n as AttestedMigrationManifest, o as MigrationGraph, r as DraftMigrationManifest, s as MigrationHints, t as AttestedMigrationBundle, u as isAttested } from "../types-9YQfIg6N.mjs";
2
2
 
3
3
  //#region src/errors.d.ts
4
4
 
@@ -31,5 +31,5 @@ declare class MigrationToolsError extends Error {
31
31
  static is(error: unknown): error is MigrationToolsError;
32
32
  }
33
33
  //#endregion
34
- export { type MigrationChainEntry, type MigrationGraph, type MigrationHints, type MigrationManifest, type MigrationOps, type MigrationPackage, MigrationToolsError };
34
+ export { type AttestedMigrationBundle, type AttestedMigrationManifest, type DraftMigrationManifest, type MigrationBundle, type MigrationBundle as MigrationPackage, type MigrationChainEntry, type MigrationGraph, type MigrationHints, type MigrationManifest, type MigrationOps, MigrationToolsError, isAttested };
35
35
  //# sourceMappingURL=types.d.mts.map
@@ -1,3 +1,14 @@
1
- import { t as MigrationToolsError } from "../errors-DdSjGRqx.mjs";
1
+ import { t as MigrationToolsError } from "../errors-CqLiJwqA.mjs";
2
2
 
3
- export { MigrationToolsError };
3
+ //#region src/types.ts
4
+ /**
5
+ * Type guard that narrows a MigrationBundle to an AttestedMigrationBundle.
6
+ * Use with `.filter(isAttested)` to get a typed array of attested bundles.
7
+ */
8
+ function isAttested(bundle) {
9
+ return typeof bundle.manifest.migrationId === "string";
10
+ }
11
+
12
+ //#endregion
13
+ export { MigrationToolsError, isAttested };
14
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../../src/types.ts"],"sourcesContent":["import type { ContractIR } from '@prisma-next/contract/ir';\nimport type { MigrationPlanOperation } from '@prisma-next/core-control-plane/types';\n\nexport interface MigrationHints {\n readonly used: readonly string[];\n readonly applied: readonly string[];\n readonly plannerVersion: string;\n readonly planningStrategy: string;\n}\n\n/**\n * Shared fields for all migration manifests (draft and attested).\n */\ninterface MigrationManifestBase {\n readonly from: string;\n readonly to: string;\n readonly kind: 'regular' | 'baseline';\n readonly fromContract: ContractIR | null;\n readonly toContract: ContractIR;\n readonly hints: MigrationHints;\n readonly labels: readonly string[];\n readonly authorship?: { readonly author?: string; readonly email?: string };\n readonly signature?: { readonly keyId: string; readonly value: string } | null;\n readonly createdAt: string;\n}\n\n/**\n * A draft migration that has been planned but not yet attested.\n * Draft migrations have `migrationId: null` and are excluded from\n * graph reconstruction and apply.\n */\nexport interface DraftMigrationManifest extends MigrationManifestBase {\n readonly migrationId: null;\n}\n\n/**\n * An attested migration with a content-addressed migrationId.\n * Only attested migrations participate in the migration graph.\n */\nexport interface AttestedMigrationManifest extends MigrationManifestBase {\n readonly migrationId: string;\n}\n\n/**\n * Union of draft and attested manifests. This is what the on-disk\n * format represents — `migrationId` is `null` for drafts, a string\n * for attested migrations.\n */\nexport type MigrationManifest = DraftMigrationManifest | AttestedMigrationManifest;\n\nexport type MigrationOps = readonly MigrationPlanOperation[];\n\n/**\n * An on-disk migration directory containing a manifest and operations.\n * The manifest may be draft or attested.\n */\nexport interface MigrationBundle {\n readonly dirName: string;\n readonly dirPath: string;\n readonly manifest: MigrationManifest;\n readonly ops: MigrationOps;\n}\n\n/**\n * A bundle known to be attested (migrationId is a string).\n * Use this after filtering bundles to attested-only.\n */\nexport interface AttestedMigrationBundle extends MigrationBundle {\n readonly manifest: AttestedMigrationManifest;\n}\n\n/**\n * An entry in the migration graph. Only attested migrations appear in the\n * graph, so `migrationId` is always a string.\n */\nexport interface MigrationChainEntry {\n readonly from: string;\n readonly to: string;\n readonly migrationId: string;\n readonly dirName: string;\n readonly createdAt: string;\n readonly labels: readonly string[];\n}\n\nexport interface MigrationGraph {\n readonly nodes: ReadonlySet<string>;\n readonly forwardChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;\n readonly reverseChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;\n readonly migrationById: ReadonlyMap<string, MigrationChainEntry>;\n}\n\n/**\n * Type guard that narrows a MigrationBundle to an AttestedMigrationBundle.\n * Use with `.filter(isAttested)` to get a typed array of attested bundles.\n */\nexport function isAttested(bundle: MigrationBundle): bundle is AttestedMigrationBundle {\n return typeof bundle.manifest.migrationId === 'string';\n}\n"],"mappings":";;;;;;;AA+FA,SAAgB,WAAW,QAA4D;AACrF,QAAO,OAAO,OAAO,SAAS,gBAAgB"}
@@ -1,4 +1,4 @@
1
- import { a as errorInvalidJson, c as errorMissingFile, o as errorInvalidManifest, r as errorDirectoryExists, s as errorInvalidSlug } from "./errors-DdSjGRqx.mjs";
1
+ import { a as errorInvalidJson, d as errorMissingFile, o as errorInvalidManifest, r as errorDirectoryExists, u as errorInvalidSlug } from "./errors-CqLiJwqA.mjs";
2
2
  import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
3
3
  import { type } from "arktype";
4
4
  import { basename, dirname, join } from "pathe";
@@ -14,7 +14,6 @@ const MigrationManifestSchema = type({
14
14
  from: "string",
15
15
  to: "string",
16
16
  migrationId: "string | null",
17
- parentMigrationId: "string | null",
18
17
  kind: "'regular' | 'baseline'",
19
18
  fromContract: "object | null",
20
19
  toContract: "object",
@@ -128,4 +127,4 @@ function formatMigrationDirName(timestamp, slug) {
128
127
 
129
128
  //#endregion
130
129
  export { writeMigrationPackage as i, readMigrationPackage as n, readMigrationsDir as r, formatMigrationDirName as t };
131
- //# sourceMappingURL=io-Dx98-h0p.mjs.map
130
+ //# sourceMappingURL=io-afog-e8J.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io-afog-e8J.mjs","names":["manifestRaw: string","opsRaw: string","manifest: MigrationManifest","ops: MigrationOps","entries: string[]","packages: MigrationBundle[]"],"sources":["../src/io.ts"],"sourcesContent":["import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';\nimport { type } from 'arktype';\nimport { basename, dirname, join } from 'pathe';\nimport {\n errorDirectoryExists,\n errorInvalidJson,\n errorInvalidManifest,\n errorInvalidSlug,\n errorMissingFile,\n} from './errors';\nimport type { MigrationBundle, MigrationManifest, MigrationOps } from './types';\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 planningStrategy: 'string',\n});\n\nconst MigrationManifestSchema = type({\n from: 'string',\n to: 'string',\n migrationId: 'string | null',\n kind: \"'regular' | 'baseline'\",\n fromContract: 'object | null',\n toContract: 'object',\n hints: MigrationHintsSchema,\n labels: '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\nconst MigrationOpSchema = type({\n id: 'string',\n label: 'string',\n operationClass: \"'additive' | 'widening' | 'destructive'\",\n});\n\n// Intentionally shallow: operation-specific payload validation is owned by planner/runner layers.\nconst MigrationOpsSchema = MigrationOpSchema.array();\n\nexport async function writeMigrationPackage(\n dir: string,\n manifest: MigrationManifest,\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(manifest, null, 2), { flag: 'wx' });\n await writeFile(join(dir, OPS_FILE), JSON.stringify(ops, null, 2), { flag: 'wx' });\n}\n\nexport async function readMigrationPackage(dir: string): Promise<MigrationBundle> {\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 manifest: MigrationManifest;\n try {\n manifest = 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 validateManifest(manifest, manifestPath);\n validateOps(ops, opsPath);\n\n return {\n dirName: basename(dir),\n dirPath: dir,\n manifest,\n ops,\n };\n}\n\nfunction validateManifest(\n manifest: unknown,\n filePath: string,\n): asserts manifest is MigrationManifest {\n const result = MigrationManifestSchema(manifest);\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 MigrationBundle[]> {\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: MigrationBundle[] = [];\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,96 @@
1
+ import { ContractIR } from "@prisma-next/contract/ir";
2
+ import { MigrationPlanOperation } from "@prisma-next/core-control-plane/types";
3
+
4
+ //#region src/types.d.ts
5
+ interface MigrationHints {
6
+ readonly used: readonly string[];
7
+ readonly applied: readonly string[];
8
+ readonly plannerVersion: string;
9
+ readonly planningStrategy: string;
10
+ }
11
+ /**
12
+ * Shared fields for all migration manifests (draft and attested).
13
+ */
14
+ interface MigrationManifestBase {
15
+ readonly from: string;
16
+ readonly to: string;
17
+ readonly kind: 'regular' | 'baseline';
18
+ readonly fromContract: ContractIR | null;
19
+ readonly toContract: ContractIR;
20
+ readonly hints: MigrationHints;
21
+ readonly labels: readonly string[];
22
+ readonly authorship?: {
23
+ readonly author?: string;
24
+ readonly email?: string;
25
+ };
26
+ readonly signature?: {
27
+ readonly keyId: string;
28
+ readonly value: string;
29
+ } | null;
30
+ readonly createdAt: string;
31
+ }
32
+ /**
33
+ * A draft migration that has been planned but not yet attested.
34
+ * Draft migrations have `migrationId: null` and are excluded from
35
+ * graph reconstruction and apply.
36
+ */
37
+ interface DraftMigrationManifest extends MigrationManifestBase {
38
+ readonly migrationId: null;
39
+ }
40
+ /**
41
+ * An attested migration with a content-addressed migrationId.
42
+ * Only attested migrations participate in the migration graph.
43
+ */
44
+ interface AttestedMigrationManifest extends MigrationManifestBase {
45
+ readonly migrationId: string;
46
+ }
47
+ /**
48
+ * Union of draft and attested manifests. This is what the on-disk
49
+ * format represents — `migrationId` is `null` for drafts, a string
50
+ * for attested migrations.
51
+ */
52
+ type MigrationManifest = DraftMigrationManifest | AttestedMigrationManifest;
53
+ type MigrationOps = readonly MigrationPlanOperation[];
54
+ /**
55
+ * An on-disk migration directory containing a manifest and operations.
56
+ * The manifest may be draft or attested.
57
+ */
58
+ interface MigrationBundle {
59
+ readonly dirName: string;
60
+ readonly dirPath: string;
61
+ readonly manifest: MigrationManifest;
62
+ readonly ops: MigrationOps;
63
+ }
64
+ /**
65
+ * A bundle known to be attested (migrationId is a string).
66
+ * Use this after filtering bundles to attested-only.
67
+ */
68
+ interface AttestedMigrationBundle extends MigrationBundle {
69
+ readonly manifest: AttestedMigrationManifest;
70
+ }
71
+ /**
72
+ * An entry in the migration graph. Only attested migrations appear in the
73
+ * graph, so `migrationId` is always a string.
74
+ */
75
+ interface MigrationChainEntry {
76
+ readonly from: string;
77
+ readonly to: string;
78
+ readonly migrationId: string;
79
+ readonly dirName: string;
80
+ readonly createdAt: string;
81
+ readonly labels: readonly string[];
82
+ }
83
+ interface MigrationGraph {
84
+ readonly nodes: ReadonlySet<string>;
85
+ readonly forwardChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;
86
+ readonly reverseChain: ReadonlyMap<string, readonly MigrationChainEntry[]>;
87
+ readonly migrationById: ReadonlyMap<string, MigrationChainEntry>;
88
+ }
89
+ /**
90
+ * Type guard that narrows a MigrationBundle to an AttestedMigrationBundle.
91
+ * Use with `.filter(isAttested)` to get a typed array of attested bundles.
92
+ */
93
+ declare function isAttested(bundle: MigrationBundle): bundle is AttestedMigrationBundle;
94
+ //#endregion
95
+ export { MigrationChainEntry as a, MigrationManifest as c, MigrationBundle as i, MigrationOps as l, AttestedMigrationManifest as n, MigrationGraph as o, DraftMigrationManifest as r, MigrationHints as s, AttestedMigrationBundle as t, isAttested as u };
96
+ //# sourceMappingURL=types-9YQfIg6N.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-9YQfIg6N.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;UAGiB,cAAA;;EAAA,SAAA,OAAA,EAAc,SAAA,MAAA,EAAA;EAUrB,SAAA,cAAA,EAAqB,MAAA;EAIN,SAAA,gBAAA,EAAA,MAAA;;;;AAczB;AAQA,UA1BU,qBAAA,CA0BiC;EAS/B,SAAA,IAAA,EAAA,MAAiB;EAEjB,SAAA,EAAA,EAAA,MAAY;EAMP,SAAA,IAAA,EAAA,SAAe,GAAA,UAGX;EAQJ,SAAA,YAAA,EAlDQ,UAkDgB,GAAA,IACpB;EAOJ,SAAA,UAAA,EAzDM,UAyDa;EASnB,SAAA,KAAA,EAjEC,cAiEa;EACb,SAAA,MAAA,EAAA,SAAA,MAAA,EAAA;EACoC,SAAA,UAAA,CAAA,EAAA;IAA7B,SAAA,MAAA,CAAA,EAAA,MAAA;IAC6B,SAAA,KAAA,CAAA,EAAA,MAAA;EAA7B,CAAA;EACqB,SAAA,SAAA,CAAA,EAAA;IAApB,SAAA,KAAA,EAAA,MAAA;IAAW,SAAA,KAAA,EAAA,MAAA;EAOrB,CAAA,GAAA,IAAA;;;;;;;;UAhEC,sBAAA,SAA+B;;;;;;;UAQ/B,yBAAA,SAAkC;;;;;;;;KASvC,iBAAA,GAAoB,yBAAyB;KAE7C,YAAA,YAAwB;;;;;UAMnB,eAAA;;;qBAGI;gBACL;;;;;;UAOC,uBAAA,SAAgC;qBAC5B;;;;;;UAOJ,mBAAA;;;;;;;;UASA,cAAA;kBACC;yBACO,6BAA6B;yBAC7B,6BAA6B;0BAC5B,oBAAoB;;;;;;iBAO9B,UAAA,SAAmB,4BAA4B"}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@prisma-next/migration-tools",
3
- "version": "0.3.0-dev.84",
3
+ "version": "0.3.0-dev.86",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "On-disk migration persistence, attestation, and chain reconstruction for Prisma Next",
7
7
  "dependencies": {
8
8
  "arktype": "^2.1.29",
9
9
  "pathe": "^2.0.3",
10
- "@prisma-next/contract": "0.3.0-dev.84",
11
- "@prisma-next/core-control-plane": "0.3.0-dev.84",
12
- "@prisma-next/utils": "0.3.0-dev.84"
10
+ "@prisma-next/contract": "0.3.0-dev.86",
11
+ "@prisma-next/core-control-plane": "0.3.0-dev.86",
12
+ "@prisma-next/utils": "0.3.0-dev.86"
13
13
  },
14
14
  "devDependencies": {
15
15
  "tsdown": "0.18.4",
@@ -42,6 +42,10 @@
42
42
  "types": "./dist/exports/dag.d.mts",
43
43
  "import": "./dist/exports/dag.mjs"
44
44
  },
45
+ "./refs": {
46
+ "types": "./dist/exports/refs.d.mts",
47
+ "import": "./dist/exports/refs.mjs"
48
+ },
45
49
  "./package.json": "./package.json"
46
50
  },
47
51
  "repository": {