@prisma-next/migration-tools 0.3.0-dev.159 → 0.3.0-dev.161

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 (37) hide show
  1. package/dist/{constants-CZ1Ufe9z.mjs → constants-9f-bCq8Y.mjs} +1 -1
  2. package/dist/{constants-CZ1Ufe9z.mjs.map → constants-9f-bCq8Y.mjs.map} +1 -1
  3. package/dist/{errors-CRYl7qCG.mjs → errors-CSAAto11.mjs} +1 -1
  4. package/dist/{errors-CRYl7qCG.mjs.map → errors-CSAAto11.mjs.map} +1 -1
  5. package/dist/exports/attestation.d.mts +1 -1
  6. package/dist/exports/attestation.d.mts.map +1 -1
  7. package/dist/exports/attestation.mjs +3 -6
  8. package/dist/exports/attestation.mjs.map +1 -1
  9. package/dist/exports/constants.mjs +1 -1
  10. package/dist/exports/dag.d.mts +1 -1
  11. package/dist/exports/dag.mjs +2 -2
  12. package/dist/exports/io.d.mts +6 -4
  13. package/dist/exports/io.d.mts.map +1 -1
  14. package/dist/exports/io.mjs +2 -2
  15. package/dist/exports/migration-ts.d.mts +34 -0
  16. package/dist/exports/migration-ts.d.mts.map +1 -0
  17. package/dist/exports/migration-ts.mjs +125 -0
  18. package/dist/exports/migration-ts.mjs.map +1 -0
  19. package/dist/exports/refs.mjs +1 -1
  20. package/dist/exports/types.d.mts +2 -2
  21. package/dist/exports/types.mjs +5 -2
  22. package/dist/exports/types.mjs.map +1 -1
  23. package/dist/{io-CyVQJBqz.mjs → io-LsuurzNb.mjs} +10 -4
  24. package/dist/io-LsuurzNb.mjs.map +1 -0
  25. package/dist/{types-BmN6uFrd.d.mts → types-BW_pJEe8.d.mts} +9 -5
  26. package/dist/types-BW_pJEe8.d.mts.map +1 -0
  27. package/package.json +8 -4
  28. package/src/attestation.ts +2 -4
  29. package/src/errors.ts +8 -0
  30. package/src/exports/io.ts +2 -0
  31. package/src/exports/migration-ts.ts +6 -0
  32. package/src/exports/types.ts +5 -3
  33. package/src/io.ts +16 -5
  34. package/src/migration-ts.ts +199 -0
  35. package/src/types.ts +11 -3
  36. package/dist/io-CyVQJBqz.mjs.map +0 -1
  37. package/dist/types-BmN6uFrd.d.mts.map +0 -1
@@ -7,4 +7,4 @@ const EMPTY_CONTRACT_HASH = "sha256:empty";
7
7
 
8
8
  //#endregion
9
9
  export { EMPTY_CONTRACT_HASH as t };
10
- //# sourceMappingURL=constants-CZ1Ufe9z.mjs.map
10
+ //# sourceMappingURL=constants-9f-bCq8Y.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants-CZ1Ufe9z.mjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/**\n * Sentinel value representing the absence of a contract (empty/new project).\n * This is a human-readable marker, not a real SHA-256 hash.\n */\nexport const EMPTY_CONTRACT_HASH = 'sha256:empty' as const;\n"],"mappings":";;;;;AAIA,MAAa,sBAAsB"}
1
+ {"version":3,"file":"constants-9f-bCq8Y.mjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/**\n * Sentinel value representing the absence of a contract (empty/new project).\n * This is a human-readable marker, not a real SHA-256 hash.\n */\nexport const EMPTY_CONTRACT_HASH = 'sha256:empty' as const;\n"],"mappings":";;;;;AAIA,MAAa,sBAAsB"}
@@ -150,4 +150,4 @@ function errorDuplicateMigrationId(migrationId) {
150
150
 
151
151
  //#endregion
152
152
  export { errorInvalidJson as a, errorInvalidRefValue as c, errorMissingFile as d, errorNoInitialMigration as f, errorDuplicateMigrationId as i, errorInvalidRefs as l, errorSameSourceAndTarget as m, errorAmbiguousTarget as n, errorInvalidManifest as o, errorNoTarget as p, errorDirectoryExists as r, errorInvalidRefName as s, MigrationToolsError as t, errorInvalidSlug as u };
153
- //# sourceMappingURL=errors-CRYl7qCG.mjs.map
153
+ //# sourceMappingURL=errors-CSAAto11.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors-CRYl7qCG.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Structured error for migration tooling operations.\n *\n * Follows the NAMESPACE.SUBCODE convention from ADR 027. All codes live under\n * the MIGRATION namespace. These are tooling-time errors (file I/O, attestation,\n * migration history reconstruction), distinct from the runtime MIGRATION.* codes for apply-time\n * failures (PRECHECK_FAILED, POSTCHECK_FAILED, etc.).\n *\n * Fields:\n * - code: Stable machine-readable code (MIGRATION.SUBCODE)\n * - category: Always 'MIGRATION'\n * - why: Explains the cause in plain language\n * - fix: Actionable remediation step\n * - details: Machine-readable structured data for agents\n */\nexport class MigrationToolsError extends Error {\n readonly code: string;\n readonly category = 'MIGRATION' as const;\n readonly why: string;\n readonly fix: string;\n readonly details: Record<string, unknown> | undefined;\n\n constructor(\n code: string,\n summary: string,\n options: {\n readonly why: string;\n readonly fix: string;\n readonly details?: Record<string, unknown>;\n },\n ) {\n super(summary);\n this.name = 'MigrationToolsError';\n this.code = code;\n this.why = options.why;\n this.fix = options.fix;\n this.details = options.details;\n }\n\n static is(error: unknown): error is MigrationToolsError {\n if (!(error instanceof Error)) return false;\n const candidate = error as MigrationToolsError;\n return candidate.name === 'MigrationToolsError' && typeof candidate.code === 'string';\n }\n}\n\nexport function errorDirectoryExists(dir: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.DIR_EXISTS', 'Migration directory already exists', {\n why: `The directory \"${dir}\" already exists. Each migration must have a unique directory.`,\n fix: 'Use --name to pick a different name, or delete the existing directory and re-run.',\n details: { dir },\n });\n}\n\nexport function errorMissingFile(file: string, dir: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.FILE_MISSING', `Missing ${file}`, {\n why: `Expected \"${file}\" in \"${dir}\" but the file does not exist.`,\n fix: 'Ensure the migration directory contains both migration.json and ops.json. If the directory is corrupt, delete it and re-run migration plan.',\n details: { file, dir },\n });\n}\n\nexport function errorInvalidJson(filePath: string, parseError: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_JSON', 'Invalid JSON in migration file', {\n why: `Failed to parse \"${filePath}\": ${parseError}`,\n fix: 'Fix the JSON syntax error, or delete the migration directory and re-run migration plan.',\n details: { filePath, parseError },\n });\n}\n\nexport function errorInvalidManifest(filePath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_MANIFEST', 'Invalid migration manifest', {\n why: `Manifest at \"${filePath}\" is invalid: ${reason}`,\n fix: 'Ensure the manifest has all required fields (from, to, kind, toContract). If corrupt, delete and re-plan.',\n details: { filePath, reason },\n });\n}\n\nexport function errorInvalidSlug(slug: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_NAME', 'Invalid migration name', {\n why: `The slug \"${slug}\" contains no valid characters after sanitization (only a-z, 0-9 are kept).`,\n fix: 'Provide a name with at least one alphanumeric character, e.g. --name add_users.',\n details: { slug },\n });\n}\n\nexport function errorSameSourceAndTarget(dirName: string, hash: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.SAME_SOURCE_AND_TARGET',\n 'Migration has same source and target',\n {\n why: `Migration \"${dirName}\" has from === to === \"${hash}\". A migration must transition between two different contract states.`,\n fix: 'Delete the invalid migration directory and re-run migration plan.',\n details: { dirName, hash },\n },\n );\n}\n\nexport function errorAmbiguousTarget(\n branchTips: readonly string[],\n context?: {\n divergencePoint: string;\n branches: readonly {\n tip: string;\n edges: readonly { dirName: string; from: string; to: string }[];\n }[];\n },\n): MigrationToolsError {\n const divergenceInfo = context\n ? `\\nDivergence point: ${context.divergencePoint}\\nBranches:\\n${context.branches.map((b) => ` → ${b.tip} (${b.edges.length} edge(s): ${b.edges.map((e) => e.dirName).join(' → ') || 'direct'})`).join('\\n')}`\n : '';\n return new MigrationToolsError('MIGRATION.AMBIGUOUS_TARGET', 'Ambiguous migration target', {\n why: `The migration history has diverged into multiple branches: ${branchTips.join(', ')}. This typically happens when two developers plan migrations from the same starting point.${divergenceInfo}`,\n fix: 'Use `migration ref set <name> <hash>` to target a specific branch, delete one of the conflicting migration directories and re-run `migration plan`, or use --from <hash> to explicitly select a starting point.',\n details: {\n branchTips,\n ...(context ? { divergencePoint: context.divergencePoint, branches: context.branches } : {}),\n },\n });\n}\n\nexport function errorNoInitialMigration(nodes: readonly string[]): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.NO_INITIAL_MIGRATION', 'No initial migration found', {\n why: `No migration starts from the empty contract state (known hashes: ${nodes.join(', ')}). At least one migration must originate from the empty state.`,\n fix: 'Inspect the migrations directory for corrupted migration.json files. At least one migration must start from the empty contract hash.',\n details: { nodes },\n });\n}\n\nexport function errorInvalidRefs(refsPath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REFS', 'Invalid refs.json', {\n why: `refs.json at \"${refsPath}\" is invalid: ${reason}`,\n fix: 'Ensure refs.json is a flat object mapping valid ref names to contract hash strings.',\n details: { path: refsPath, reason },\n });\n}\n\nexport function errorInvalidRefName(refName: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_NAME', 'Invalid ref name', {\n why: `Ref name \"${refName}\" is invalid. Names must be lowercase alphanumeric with hyphens or forward slashes (no \".\" or \"..\" segments).`,\n fix: `Use a valid ref name (e.g., \"staging\", \"envs/production\").`,\n details: { refName },\n });\n}\n\nexport function errorNoTarget(reachableHashes: readonly string[]): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.NO_TARGET', 'No migration target could be resolved', {\n why: `The migration history contains cycles and no target can be resolved automatically (reachable hashes: ${reachableHashes.join(', ')}). This typically happens after rollback migrations (e.g., C1→C2→C1).`,\n fix: 'Use --from <hash> to specify the planning origin explicitly.',\n details: { reachableHashes },\n });\n}\n\nexport function errorInvalidRefValue(value: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_VALUE', 'Invalid ref value', {\n why: `Ref value \"${value}\" is not a valid contract hash. Values must be in the format \"sha256:<64 hex chars>\" or \"sha256:empty\".`,\n fix: 'Use a valid storage hash from `prisma-next contract emit` output or an existing migration.',\n details: { value },\n });\n}\n\nexport function errorDuplicateMigrationId(migrationId: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.DUPLICATE_MIGRATION_ID',\n 'Duplicate migrationId in migration graph',\n {\n why: `Multiple migrations share migrationId \"${migrationId}\". Each migration must have a unique content-addressed identity.`,\n fix: 'Regenerate one of the conflicting migrations so each migrationId is unique, then re-run migration commands.',\n details: { migrationId },\n },\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"errors-CSAAto11.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Structured error for migration tooling operations.\n *\n * Follows the NAMESPACE.SUBCODE convention from ADR 027. All codes live under\n * the MIGRATION namespace. These are tooling-time errors (file I/O, attestation,\n * migration history reconstruction), distinct from the runtime MIGRATION.* codes for apply-time\n * failures (PRECHECK_FAILED, POSTCHECK_FAILED, etc.).\n *\n * Fields:\n * - code: Stable machine-readable code (MIGRATION.SUBCODE)\n * - category: Always 'MIGRATION'\n * - why: Explains the cause in plain language\n * - fix: Actionable remediation step\n * - details: Machine-readable structured data for agents\n */\nexport class MigrationToolsError extends Error {\n readonly code: string;\n readonly category = 'MIGRATION' as const;\n readonly why: string;\n readonly fix: string;\n readonly details: Record<string, unknown> | undefined;\n\n constructor(\n code: string,\n summary: string,\n options: {\n readonly why: string;\n readonly fix: string;\n readonly details?: Record<string, unknown>;\n },\n ) {\n super(summary);\n this.name = 'MigrationToolsError';\n this.code = code;\n this.why = options.why;\n this.fix = options.fix;\n this.details = options.details;\n }\n\n static is(error: unknown): error is MigrationToolsError {\n if (!(error instanceof Error)) return false;\n const candidate = error as MigrationToolsError;\n return candidate.name === 'MigrationToolsError' && typeof candidate.code === 'string';\n }\n}\n\nexport function errorDirectoryExists(dir: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.DIR_EXISTS', 'Migration directory already exists', {\n why: `The directory \"${dir}\" already exists. Each migration must have a unique directory.`,\n fix: 'Use --name to pick a different name, or delete the existing directory and re-run.',\n details: { dir },\n });\n}\n\nexport function errorMissingFile(file: string, dir: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.FILE_MISSING', `Missing ${file}`, {\n why: `Expected \"${file}\" in \"${dir}\" but the file does not exist.`,\n fix: 'Ensure the migration directory contains both migration.json and ops.json. If the directory is corrupt, delete it and re-run migration plan.',\n details: { file, dir },\n });\n}\n\nexport function errorInvalidJson(filePath: string, parseError: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_JSON', 'Invalid JSON in migration file', {\n why: `Failed to parse \"${filePath}\": ${parseError}`,\n fix: 'Fix the JSON syntax error, or delete the migration directory and re-run migration plan.',\n details: { filePath, parseError },\n });\n}\n\nexport function errorInvalidManifest(filePath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_MANIFEST', 'Invalid migration manifest', {\n why: `Manifest at \"${filePath}\" is invalid: ${reason}`,\n fix: 'Ensure the manifest has all required fields (from, to, kind, toContract). If corrupt, delete and re-plan.',\n details: { filePath, reason },\n });\n}\n\nexport function errorInvalidSlug(slug: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_NAME', 'Invalid migration name', {\n why: `The slug \"${slug}\" contains no valid characters after sanitization (only a-z, 0-9 are kept).`,\n fix: 'Provide a name with at least one alphanumeric character, e.g. --name add_users.',\n details: { slug },\n });\n}\n\nexport function errorSameSourceAndTarget(dirName: string, hash: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.SAME_SOURCE_AND_TARGET',\n 'Migration has same source and target',\n {\n why: `Migration \"${dirName}\" has from === to === \"${hash}\". A migration must transition between two different contract states.`,\n fix: 'Delete the invalid migration directory and re-run migration plan.',\n details: { dirName, hash },\n },\n );\n}\n\nexport function errorAmbiguousTarget(\n branchTips: readonly string[],\n context?: {\n divergencePoint: string;\n branches: readonly {\n tip: string;\n edges: readonly { dirName: string; from: string; to: string }[];\n }[];\n },\n): MigrationToolsError {\n const divergenceInfo = context\n ? `\\nDivergence point: ${context.divergencePoint}\\nBranches:\\n${context.branches.map((b) => ` → ${b.tip} (${b.edges.length} edge(s): ${b.edges.map((e) => e.dirName).join(' → ') || 'direct'})`).join('\\n')}`\n : '';\n return new MigrationToolsError('MIGRATION.AMBIGUOUS_TARGET', 'Ambiguous migration target', {\n why: `The migration history has diverged into multiple branches: ${branchTips.join(', ')}. This typically happens when two developers plan migrations from the same starting point.${divergenceInfo}`,\n fix: 'Use `migration ref set <name> <hash>` to target a specific branch, delete one of the conflicting migration directories and re-run `migration plan`, or use --from <hash> to explicitly select a starting point.',\n details: {\n branchTips,\n ...(context ? { divergencePoint: context.divergencePoint, branches: context.branches } : {}),\n },\n });\n}\n\nexport function errorNoInitialMigration(nodes: readonly string[]): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.NO_INITIAL_MIGRATION', 'No initial migration found', {\n why: `No migration starts from the empty contract state (known hashes: ${nodes.join(', ')}). At least one migration must originate from the empty state.`,\n fix: 'Inspect the migrations directory for corrupted migration.json files. At least one migration must start from the empty contract hash.',\n details: { nodes },\n });\n}\n\nexport function errorInvalidRefs(refsPath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REFS', 'Invalid refs.json', {\n why: `refs.json at \"${refsPath}\" is invalid: ${reason}`,\n fix: 'Ensure refs.json is a flat object mapping valid ref names to contract hash strings.',\n details: { path: refsPath, reason },\n });\n}\n\nexport function errorInvalidRefFile(filePath: string, reason: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_FILE', 'Invalid ref file', {\n why: `Ref file at \"${filePath}\" is invalid: ${reason}`,\n fix: 'Ensure the ref file contains valid JSON with { \"hash\": \"sha256:<64 hex chars>\", \"invariants\": [\"...\"] }.',\n details: { path: filePath, reason },\n });\n}\n\nexport function errorInvalidRefName(refName: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_NAME', 'Invalid ref name', {\n why: `Ref name \"${refName}\" is invalid. Names must be lowercase alphanumeric with hyphens or forward slashes (no \".\" or \"..\" segments).`,\n fix: `Use a valid ref name (e.g., \"staging\", \"envs/production\").`,\n details: { refName },\n });\n}\n\nexport function errorNoTarget(reachableHashes: readonly string[]): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.NO_TARGET', 'No migration target could be resolved', {\n why: `The migration history contains cycles and no target can be resolved automatically (reachable hashes: ${reachableHashes.join(', ')}). This typically happens after rollback migrations (e.g., C1→C2→C1).`,\n fix: 'Use --from <hash> to specify the planning origin explicitly.',\n details: { reachableHashes },\n });\n}\n\nexport function errorInvalidRefValue(value: string): MigrationToolsError {\n return new MigrationToolsError('MIGRATION.INVALID_REF_VALUE', 'Invalid ref value', {\n why: `Ref value \"${value}\" is not a valid contract hash. Values must be in the format \"sha256:<64 hex chars>\" or \"sha256:empty\".`,\n fix: 'Use a valid storage hash from `prisma-next contract emit` output or an existing migration.',\n details: { value },\n });\n}\n\nexport function errorDuplicateMigrationId(migrationId: string): MigrationToolsError {\n return new MigrationToolsError(\n 'MIGRATION.DUPLICATE_MIGRATION_ID',\n 'Duplicate migrationId in migration graph',\n {\n why: `Multiple migrations share migrationId \"${migrationId}\". Each migration must have a unique content-addressed identity.`,\n fix: 'Regenerate one of the conflicting migrations so each migrationId is unique, then re-run migration commands.',\n details: { migrationId },\n },\n );\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { c as MigrationManifest, l as MigrationOps } from "../types-BmN6uFrd.mjs";
1
+ import { l as MigrationManifest, u as MigrationOps } from "../types-BW_pJEe8.mjs";
2
2
 
3
3
  //#region src/attestation.d.ts
4
4
  interface VerifyResult {
@@ -1 +1 @@
1
- {"version":3,"file":"attestation.d.mts","names":[],"sources":["../../src/attestation.ts"],"sourcesContent":[],"mappings":";;;UAQiB,YAAA;;EAAA,SAAA,MAAY,CAAA,EAAA,OAAA,GAAA,UAAA;EAWb,SAAA,iBAAkB,CAAA,EAAA,MAAW;EA2BvB,SAAA,mBAAe,CAAA,EAAe,MAAA;AAUpD;iBArCgB,kBAAA,WAA6B,wBAAwB;iBA2B/C,eAAA,eAA8B;iBAU9B,eAAA,eAA8B,QAAQ"}
1
+ {"version":3,"file":"attestation.d.mts","names":[],"sources":["../../src/attestation.ts"],"sourcesContent":[],"mappings":";;;UAMiB,YAAA;;EAAA,SAAA,MAAY,CAAA,EAAA,OAAA,GAAA,UAAA;EAWb,SAAA,iBAAkB,CAAA,EAAA,MAAW;EA2BvB,SAAA,mBAAe,CAAA,EAAe,MAAA;AAUpD;iBArCgB,kBAAA,WAA6B,wBAAwB;iBA2B/C,eAAA,eAA8B;iBAU9B,eAAA,eAA8B,QAAQ"}
@@ -1,6 +1,4 @@
1
- import { n as readMigrationPackage } from "../io-CyVQJBqz.mjs";
2
- import { writeFile } from "node:fs/promises";
3
- import { join } from "pathe";
1
+ import { i as writeMigrationManifest, n as readMigrationPackage } from "../io-LsuurzNb.mjs";
4
2
  import { createHash } from "node:crypto";
5
3
  import { canonicalizeContract } from "@prisma-next/contract/hashing";
6
4
 
@@ -33,11 +31,10 @@ function computeMigrationId(manifest, ops) {
33
31
  async function attestMigration(dir) {
34
32
  const pkg = await readMigrationPackage(dir);
35
33
  const migrationId = computeMigrationId(pkg.manifest, pkg.ops);
36
- const updated = {
34
+ await writeMigrationManifest(dir, {
37
35
  ...pkg.manifest,
38
36
  migrationId
39
- };
40
- await writeFile(join(dir, "migration.json"), JSON.stringify(updated, null, 2));
37
+ });
41
38
  return migrationId;
42
39
  }
43
40
  async function verifyMigration(dir) {
@@ -1 +1 @@
1
- {"version":3,"file":"attestation.mjs","names":["sorted: Record<string, unknown>"],"sources":["../../src/canonicalize-json.ts","../../src/attestation.ts"],"sourcesContent":["function sortKeys(value: unknown): unknown {\n if (value === null || typeof value !== 'object') {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map(sortKeys);\n }\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) {\n sorted[key] = sortKeys((value as Record<string, unknown>)[key]);\n }\n return sorted;\n}\n\nexport function canonicalizeJson(value: unknown): string {\n return JSON.stringify(sortKeys(value));\n}\n","import { createHash } from 'node:crypto';\nimport { writeFile } from 'node:fs/promises';\nimport { canonicalizeContract } from '@prisma-next/contract/hashing';\nimport { join } from 'pathe';\nimport { canonicalizeJson } from './canonicalize-json';\nimport { readMigrationPackage } from './io';\nimport type { MigrationManifest, MigrationOps } from './types';\n\nexport interface VerifyResult {\n readonly ok: boolean;\n readonly reason?: 'draft' | 'mismatch';\n readonly storedMigrationId?: string;\n readonly computedMigrationId?: string;\n}\n\nfunction sha256Hex(input: string): string {\n return createHash('sha256').update(input).digest('hex');\n}\n\nexport function computeMigrationId(manifest: MigrationManifest, ops: MigrationOps): string {\n const {\n migrationId: _migrationId,\n signature: _signature,\n fromContract: _fromContract,\n toContract: _toContract,\n ...strippedMeta\n } = manifest;\n\n const canonicalManifest = canonicalizeJson(strippedMeta);\n const canonicalOps = canonicalizeJson(ops);\n\n const canonicalFromContract =\n manifest.fromContract !== null ? canonicalizeContract(manifest.fromContract) : 'null';\n const canonicalToContract = canonicalizeContract(manifest.toContract);\n\n const partHashes = [\n canonicalManifest,\n canonicalOps,\n canonicalFromContract,\n canonicalToContract,\n ].map(sha256Hex);\n const hash = sha256Hex(canonicalizeJson(partHashes));\n\n return `sha256:${hash}`;\n}\n\nexport async function attestMigration(dir: string): Promise<string> {\n const pkg = await readMigrationPackage(dir);\n const migrationId = computeMigrationId(pkg.manifest, pkg.ops);\n\n const updated = { ...pkg.manifest, migrationId };\n await writeFile(join(dir, 'migration.json'), JSON.stringify(updated, null, 2));\n\n return migrationId;\n}\n\nexport async function verifyMigration(dir: string): Promise<VerifyResult> {\n const pkg = await readMigrationPackage(dir);\n\n if (pkg.manifest.migrationId === null) {\n return { ok: false, reason: 'draft' };\n }\n\n const computed = computeMigrationId(pkg.manifest, pkg.ops);\n\n if (pkg.manifest.migrationId === computed) {\n return { ok: true, storedMigrationId: pkg.manifest.migrationId, computedMigrationId: computed };\n }\n\n return {\n ok: false,\n reason: 'mismatch',\n storedMigrationId: pkg.manifest.migrationId,\n computedMigrationId: computed,\n };\n}\n"],"mappings":";;;;;;;AAAA,SAAS,SAAS,OAAyB;AACzC,KAAI,UAAU,QAAQ,OAAO,UAAU,SACrC,QAAO;AAET,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,IAAI,SAAS;CAE5B,MAAMA,SAAkC,EAAE;AAC1C,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,CAAC,MAAM,CACzC,QAAO,OAAO,SAAU,MAAkC,KAAK;AAEjE,QAAO;;AAGT,SAAgB,iBAAiB,OAAwB;AACvD,QAAO,KAAK,UAAU,SAAS,MAAM,CAAC;;;;;ACAxC,SAAS,UAAU,OAAuB;AACxC,QAAO,WAAW,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;AAGzD,SAAgB,mBAAmB,UAA6B,KAA2B;CACzF,MAAM,EACJ,aAAa,cACb,WAAW,YACX,cAAc,eACd,YAAY,aACZ,GAAG,iBACD;AAiBJ,QAAO,UAFM,UAAU,iBANJ;EAPO,iBAAiB,aAAa;EACnC,iBAAiB,IAAI;EAGxC,SAAS,iBAAiB,OAAO,qBAAqB,SAAS,aAAa,GAAG;EACrD,qBAAqB,SAAS,WAAW;EAOpE,CAAC,IAAI,UAAU,CACmC,CAAC;;AAKtD,eAAsB,gBAAgB,KAA8B;CAClE,MAAM,MAAM,MAAM,qBAAqB,IAAI;CAC3C,MAAM,cAAc,mBAAmB,IAAI,UAAU,IAAI,IAAI;CAE7D,MAAM,UAAU;EAAE,GAAG,IAAI;EAAU;EAAa;AAChD,OAAM,UAAU,KAAK,KAAK,iBAAiB,EAAE,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC;AAE9E,QAAO;;AAGT,eAAsB,gBAAgB,KAAoC;CACxE,MAAM,MAAM,MAAM,qBAAqB,IAAI;AAE3C,KAAI,IAAI,SAAS,gBAAgB,KAC/B,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAS;CAGvC,MAAM,WAAW,mBAAmB,IAAI,UAAU,IAAI,IAAI;AAE1D,KAAI,IAAI,SAAS,gBAAgB,SAC/B,QAAO;EAAE,IAAI;EAAM,mBAAmB,IAAI,SAAS;EAAa,qBAAqB;EAAU;AAGjG,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,mBAAmB,IAAI,SAAS;EAChC,qBAAqB;EACtB"}
1
+ {"version":3,"file":"attestation.mjs","names":["sorted: Record<string, unknown>"],"sources":["../../src/canonicalize-json.ts","../../src/attestation.ts"],"sourcesContent":["function sortKeys(value: unknown): unknown {\n if (value === null || typeof value !== 'object') {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map(sortKeys);\n }\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) {\n sorted[key] = sortKeys((value as Record<string, unknown>)[key]);\n }\n return sorted;\n}\n\nexport function canonicalizeJson(value: unknown): string {\n return JSON.stringify(sortKeys(value));\n}\n","import { createHash } from 'node:crypto';\nimport { canonicalizeContract } from '@prisma-next/contract/hashing';\nimport { canonicalizeJson } from './canonicalize-json';\nimport { readMigrationPackage, writeMigrationManifest } from './io';\nimport type { MigrationManifest, MigrationOps } from './types';\n\nexport interface VerifyResult {\n readonly ok: boolean;\n readonly reason?: 'draft' | 'mismatch';\n readonly storedMigrationId?: string;\n readonly computedMigrationId?: string;\n}\n\nfunction sha256Hex(input: string): string {\n return createHash('sha256').update(input).digest('hex');\n}\n\nexport function computeMigrationId(manifest: MigrationManifest, ops: MigrationOps): string {\n const {\n migrationId: _migrationId,\n signature: _signature,\n fromContract: _fromContract,\n toContract: _toContract,\n ...strippedMeta\n } = manifest;\n\n const canonicalManifest = canonicalizeJson(strippedMeta);\n const canonicalOps = canonicalizeJson(ops);\n\n const canonicalFromContract =\n manifest.fromContract !== null ? canonicalizeContract(manifest.fromContract) : 'null';\n const canonicalToContract = canonicalizeContract(manifest.toContract);\n\n const partHashes = [\n canonicalManifest,\n canonicalOps,\n canonicalFromContract,\n canonicalToContract,\n ].map(sha256Hex);\n const hash = sha256Hex(canonicalizeJson(partHashes));\n\n return `sha256:${hash}`;\n}\n\nexport async function attestMigration(dir: string): Promise<string> {\n const pkg = await readMigrationPackage(dir);\n const migrationId = computeMigrationId(pkg.manifest, pkg.ops);\n\n const updated = { ...pkg.manifest, migrationId };\n await writeMigrationManifest(dir, updated);\n\n return migrationId;\n}\n\nexport async function verifyMigration(dir: string): Promise<VerifyResult> {\n const pkg = await readMigrationPackage(dir);\n\n if (pkg.manifest.migrationId === null) {\n return { ok: false, reason: 'draft' };\n }\n\n const computed = computeMigrationId(pkg.manifest, pkg.ops);\n\n if (pkg.manifest.migrationId === computed) {\n return { ok: true, storedMigrationId: pkg.manifest.migrationId, computedMigrationId: computed };\n }\n\n return {\n ok: false,\n reason: 'mismatch',\n storedMigrationId: pkg.manifest.migrationId,\n computedMigrationId: computed,\n };\n}\n"],"mappings":";;;;;AAAA,SAAS,SAAS,OAAyB;AACzC,KAAI,UAAU,QAAQ,OAAO,UAAU,SACrC,QAAO;AAET,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,IAAI,SAAS;CAE5B,MAAMA,SAAkC,EAAE;AAC1C,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,CAAC,MAAM,CACzC,QAAO,OAAO,SAAU,MAAkC,KAAK;AAEjE,QAAO;;AAGT,SAAgB,iBAAiB,OAAwB;AACvD,QAAO,KAAK,UAAU,SAAS,MAAM,CAAC;;;;;ACFxC,SAAS,UAAU,OAAuB;AACxC,QAAO,WAAW,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;AAGzD,SAAgB,mBAAmB,UAA6B,KAA2B;CACzF,MAAM,EACJ,aAAa,cACb,WAAW,YACX,cAAc,eACd,YAAY,aACZ,GAAG,iBACD;AAiBJ,QAAO,UAFM,UAAU,iBANJ;EAPO,iBAAiB,aAAa;EACnC,iBAAiB,IAAI;EAGxC,SAAS,iBAAiB,OAAO,qBAAqB,SAAS,aAAa,GAAG;EACrD,qBAAqB,SAAS,WAAW;EAOpE,CAAC,IAAI,UAAU,CACmC,CAAC;;AAKtD,eAAsB,gBAAgB,KAA8B;CAClE,MAAM,MAAM,MAAM,qBAAqB,IAAI;CAC3C,MAAM,cAAc,mBAAmB,IAAI,UAAU,IAAI,IAAI;AAG7D,OAAM,uBAAuB,KADb;EAAE,GAAG,IAAI;EAAU;EAAa,CACN;AAE1C,QAAO;;AAGT,eAAsB,gBAAgB,KAAoC;CACxE,MAAM,MAAM,MAAM,qBAAqB,IAAI;AAE3C,KAAI,IAAI,SAAS,gBAAgB,KAC/B,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAS;CAGvC,MAAM,WAAW,mBAAmB,IAAI,UAAU,IAAI,IAAI;AAE1D,KAAI,IAAI,SAAS,gBAAgB,SAC/B,QAAO;EAAE,IAAI;EAAM,mBAAmB,IAAI,SAAS;EAAa,qBAAqB;EAAU;AAGjG,QAAO;EACL,IAAI;EACJ,QAAQ;EACR,mBAAmB,IAAI,SAAS;EAChC,qBAAqB;EACtB"}
@@ -1,3 +1,3 @@
1
- import { t as EMPTY_CONTRACT_HASH } from "../constants-CZ1Ufe9z.mjs";
1
+ import { t as EMPTY_CONTRACT_HASH } from "../constants-9f-bCq8Y.mjs";
2
2
 
3
3
  export { EMPTY_CONTRACT_HASH };
@@ -1,4 +1,4 @@
1
- import { a as MigrationChainEntry, o as MigrationGraph, t as AttestedMigrationBundle } from "../types-BmN6uFrd.mjs";
1
+ import { o as MigrationChainEntry, s as MigrationGraph, t as AttestedMigrationBundle } from "../types-BW_pJEe8.mjs";
2
2
 
3
3
  //#region src/dag.d.ts
4
4
  declare function reconstructGraph(packages: readonly AttestedMigrationBundle[]): MigrationGraph;
@@ -1,5 +1,5 @@
1
- import { f as errorNoInitialMigration, i as errorDuplicateMigrationId, m as errorSameSourceAndTarget, n as errorAmbiguousTarget, p as errorNoTarget } from "../errors-CRYl7qCG.mjs";
2
- import { t as EMPTY_CONTRACT_HASH } from "../constants-CZ1Ufe9z.mjs";
1
+ import { f as errorNoInitialMigration, i as errorDuplicateMigrationId, m as errorSameSourceAndTarget, n as errorAmbiguousTarget, p as errorNoTarget } from "../errors-CSAAto11.mjs";
2
+ import { t as EMPTY_CONTRACT_HASH } from "../constants-9f-bCq8Y.mjs";
3
3
  import { ifDefined } from "@prisma-next/utils/defined";
4
4
 
5
5
  //#region src/dag.ts
@@ -1,10 +1,12 @@
1
- import { c as MigrationManifest, i as MigrationBundle, l as MigrationOps } from "../types-BmN6uFrd.mjs";
1
+ import { l as MigrationManifest, r as BaseMigrationBundle, u as MigrationOps } from "../types-BW_pJEe8.mjs";
2
2
 
3
3
  //#region src/io.d.ts
4
4
  declare function writeMigrationPackage(dir: string, manifest: MigrationManifest, ops: MigrationOps): Promise<void>;
5
- declare function readMigrationPackage(dir: string): Promise<MigrationBundle>;
6
- declare function readMigrationsDir(migrationsRoot: string): Promise<readonly MigrationBundle[]>;
5
+ declare function writeMigrationManifest(dir: string, manifest: MigrationManifest): Promise<void>;
6
+ declare function writeMigrationOps(dir: string, ops: MigrationOps): Promise<void>;
7
+ declare function readMigrationPackage(dir: string): Promise<BaseMigrationBundle>;
8
+ declare function readMigrationsDir(migrationsRoot: string): Promise<readonly BaseMigrationBundle[]>;
7
9
  declare function formatMigrationDirName(timestamp: Date, slug: string): string;
8
10
  //#endregion
9
- export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationPackage };
11
+ export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationManifest, writeMigrationOps, writeMigrationPackage };
10
12
  //# sourceMappingURL=io.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;iBAwDsB,qBAAA,wBAEV,wBACL,eACJ;iBAgBmB,oBAAA,eAAmC,QAAQ;AApB3C,iBAsFA,iBAAA,CAtFqB,cAAA,EAAA,MAAA,CAAA,EAwFxC,OAxFwC,CAAA,SAwFvB,eAxFuB,EAAA,CAAA;AAE/B,iBAqHI,sBAAA,CArHJ,SAAA,EAqHsC,IArHtC,EAAA,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA"}
1
+ {"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;iBAwDsB,qBAAA,wBAEV,wBACL,eACJ;iBAgBmB,sBAAA,wBAEV,oBACT;AAvBmB,iBA2BA,iBAAA,CA3BqB,GAAA,EAAA,MAAA,EAAA,GAAA,EA2Be,YA3Bf,CAAA,EA2B8B,OA3B9B,CAAA,IAAA,CAAA;AAE/B,iBA6BU,oBAAA,CA7BV,GAAA,EAAA,MAAA,CAAA,EA6B6C,OA7B7C,CA6BqD,mBA7BrD,CAAA;AACL,iBA8Fe,iBAAA,CA9Ff,cAAA,EAAA,MAAA,CAAA,EAgGJ,OAhGI,CAAA,SAgGa,mBAhGb,EAAA,CAAA;AACJ,iBA8Ha,sBAAA,CA9Hb,SAAA,EA8H+C,IA9H/C,EAAA,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA"}
@@ -1,3 +1,3 @@
1
- import { i as writeMigrationPackage, n as readMigrationPackage, r as readMigrationsDir, t as formatMigrationDirName } from "../io-CyVQJBqz.mjs";
1
+ import { a as writeMigrationOps, i as writeMigrationManifest, n as readMigrationPackage, o as writeMigrationPackage, r as readMigrationsDir, t as formatMigrationDirName } from "../io-LsuurzNb.mjs";
2
2
 
3
- export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationPackage };
3
+ export { formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationManifest, writeMigrationOps, writeMigrationPackage };
@@ -0,0 +1,34 @@
1
+ import { OperationDescriptor } from "@prisma-next/framework-components/control";
2
+
3
+ //#region src/migration-ts.d.ts
4
+
5
+ /**
6
+ * Options for scaffolding a migration.ts file.
7
+ */
8
+ interface ScaffoldOptions {
9
+ /** Operation descriptors to serialize as builder calls. */
10
+ readonly descriptors?: readonly OperationDescriptor[];
11
+ /** Absolute path to contract.json — used to derive contract.d.ts import for typed builders. */
12
+ readonly contractJsonPath?: string;
13
+ }
14
+ /**
15
+ * Scaffolds a migration.ts file in the given package directory.
16
+ * Serializes operation descriptors as builder calls that the user can edit.
17
+ * On verify, this file is re-evaluated to produce the final ops.
18
+ */
19
+ declare function scaffoldMigrationTs(packageDir: string, options?: ScaffoldOptions): Promise<void>;
20
+ /**
21
+ * Checks whether a migration.ts file exists in the package directory.
22
+ */
23
+ declare function hasMigrationTs(packageDir: string): Promise<boolean>;
24
+ /**
25
+ * Evaluates a migration.ts file by loading it via native Node import.
26
+ * Returns the result of calling the default export (expected to be a
27
+ * function returning an array of operation descriptors).
28
+ *
29
+ * Requires Node ≥24 for native TypeScript support.
30
+ */
31
+ declare function evaluateMigrationTs(packageDir: string): Promise<readonly unknown[]>;
32
+ //#endregion
33
+ export { type ScaffoldOptions, evaluateMigrationTs, hasMigrationTs, scaffoldMigrationTs };
34
+ //# sourceMappingURL=migration-ts.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-ts.d.mts","names":[],"sources":["../../src/migration-ts.ts"],"sourcesContent":[],"mappings":";;;;;;;UAmBiB,eAAA;;kCAEiB;;;;;;;;;iBAsFZ,mBAAA,+BAEX,kBACR;;;;iBA6CmB,cAAA,sBAAoC;;;;;;;;iBAgBpC,mBAAA,sBAAyC"}
@@ -0,0 +1,125 @@
1
+ import { stat, writeFile } from "node:fs/promises";
2
+ import { join, relative, resolve } from "pathe";
3
+
4
+ //#region src/migration-ts.ts
5
+ /**
6
+ * Utilities for scaffolding and evaluating migration.ts files.
7
+ *
8
+ * - scaffoldMigrationTs: writes a migration.ts file with boilerplate
9
+ * - evaluateMigrationTs: loads migration.ts via native Node import, returns descriptors
10
+ *
11
+ * Shared by migration plan (scaffold), migration new (scaffold), and
12
+ * migration verify (evaluate).
13
+ */
14
+ const MIGRATION_TS_FILE = "migration.ts";
15
+ function serializeQueryInput(input) {
16
+ if (typeof input === "boolean") return String(input);
17
+ if (typeof input === "symbol") return "TODO /* fill in using db.sql.from(...) */";
18
+ if (input === null || input === void 0) return "null";
19
+ if (Array.isArray(input)) {
20
+ if (input.length === 0) return "[]";
21
+ if (input.every((item) => typeof item === "symbol")) return "[TODO /* fill in using db.sql.from(...) */]";
22
+ return `[${input.map(serializeQueryInput).join(", ")}]`;
23
+ }
24
+ return JSON.stringify(input);
25
+ }
26
+ function descriptorToBuilderCall(desc) {
27
+ switch (desc.kind) {
28
+ case "createTable": return `createTable(${JSON.stringify(desc["table"])})`;
29
+ case "dropTable": return `dropTable(${JSON.stringify(desc["table"])})`;
30
+ case "addColumn": {
31
+ const args = [JSON.stringify(desc["table"]), JSON.stringify(desc["column"])];
32
+ if (desc["overrides"]) args.push(JSON.stringify(desc["overrides"]));
33
+ return `addColumn(${args.join(", ")})`;
34
+ }
35
+ case "dropColumn": return `dropColumn(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["column"])})`;
36
+ case "alterColumnType": {
37
+ const opts = {};
38
+ if (desc["using"]) opts["using"] = desc["using"];
39
+ if (desc["toType"]) opts["toType"] = desc["toType"];
40
+ return Object.keys(opts).length > 0 ? `alterColumnType(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["column"])}, ${JSON.stringify(opts)})` : `alterColumnType(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["column"])})`;
41
+ }
42
+ case "setNotNull": return `setNotNull(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["column"])})`;
43
+ case "dropNotNull": return `dropNotNull(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["column"])})`;
44
+ case "setDefault": return `setDefault(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["column"])})`;
45
+ case "dropDefault": return `dropDefault(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["column"])})`;
46
+ case "addPrimaryKey": return `addPrimaryKey(${JSON.stringify(desc["table"])})`;
47
+ case "addUnique": return `addUnique(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["columns"])})`;
48
+ case "addForeignKey": return `addForeignKey(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["columns"])})`;
49
+ case "dropConstraint": return `dropConstraint(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["constraintName"])})`;
50
+ case "createIndex": return `createIndex(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["columns"])})`;
51
+ case "dropIndex": return `dropIndex(${JSON.stringify(desc["table"])}, ${JSON.stringify(desc["indexName"])})`;
52
+ case "createEnumType": return desc["values"] ? `createEnumType(${JSON.stringify(desc["typeName"])}, ${JSON.stringify(desc["values"])})` : `createEnumType(${JSON.stringify(desc["typeName"])})`;
53
+ case "addEnumValues": return `addEnumValues(${JSON.stringify(desc["typeName"])}, ${JSON.stringify(desc["values"])})`;
54
+ case "dropEnumType": return `dropEnumType(${JSON.stringify(desc["typeName"])})`;
55
+ case "renameType": return `renameType(${JSON.stringify(desc["fromName"])}, ${JSON.stringify(desc["toName"])})`;
56
+ case "createDependency": return `createDependency(${JSON.stringify(desc["dependencyId"])})`;
57
+ case "dataTransform": return `dataTransform(${JSON.stringify(desc["name"])}, {\n check: ${serializeQueryInput(desc["check"])},\n run: ${serializeQueryInput(desc["run"])},\n })`;
58
+ default: throw new Error(`Unknown descriptor kind: ${desc.kind}`);
59
+ }
60
+ }
61
+ /**
62
+ * Scaffolds a migration.ts file in the given package directory.
63
+ * Serializes operation descriptors as builder calls that the user can edit.
64
+ * On verify, this file is re-evaluated to produce the final ops.
65
+ */
66
+ async function scaffoldMigrationTs(packageDir, options = {}) {
67
+ const filePath = join(packageDir, MIGRATION_TS_FILE);
68
+ const descriptors = options.descriptors ?? [];
69
+ const hasDataTransform = descriptors.some((d) => d.kind === "dataTransform");
70
+ const lines = [];
71
+ if (hasDataTransform && options.contractJsonPath) {
72
+ const relativeContractDts = relative(packageDir, options.contractJsonPath).replace(/\.json$/, ".d");
73
+ lines.push(`import type { Contract } from "${relativeContractDts}"`);
74
+ lines.push(`import { createBuilders } from "@prisma-next/target-postgres/migration-builders"`);
75
+ lines.push("");
76
+ const importList = [...new Set(descriptors.map((d) => d.kind))];
77
+ importList.push("TODO");
78
+ lines.push(`const { ${importList.join(", ")} } = createBuilders<Contract>()`);
79
+ } else {
80
+ const importList = [...new Set(descriptors.map((d) => d.kind))];
81
+ if (importList.length === 0) importList.push("createTable");
82
+ if (hasDataTransform) importList.push("TODO");
83
+ lines.push(`import { ${importList.join(", ")} } from "@prisma-next/target-postgres/migration-builders"`);
84
+ }
85
+ const calls = descriptors.map((d) => ` ${descriptorToBuilderCall(d)},`).join("\n");
86
+ const body = calls.length > 0 ? `\n${calls}\n` : "";
87
+ lines.push("");
88
+ lines.push(`export default () => [${body}]`);
89
+ lines.push("");
90
+ await writeFile(filePath, lines.join("\n"));
91
+ }
92
+ /**
93
+ * Checks whether a migration.ts file exists in the package directory.
94
+ */
95
+ async function hasMigrationTs(packageDir) {
96
+ try {
97
+ return (await stat(join(packageDir, MIGRATION_TS_FILE))).isFile();
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+ /**
103
+ * Evaluates a migration.ts file by loading it via native Node import.
104
+ * Returns the result of calling the default export (expected to be a
105
+ * function returning an array of operation descriptors).
106
+ *
107
+ * Requires Node ≥24 for native TypeScript support.
108
+ */
109
+ async function evaluateMigrationTs(packageDir) {
110
+ const filePath = resolve(join(packageDir, MIGRATION_TS_FILE));
111
+ try {
112
+ await stat(filePath);
113
+ } catch {
114
+ throw new Error(`migration.ts not found at "${filePath}"`);
115
+ }
116
+ const mod = await import(filePath);
117
+ if (typeof mod.default !== "function") throw new Error(`migration.ts must export a default function returning an operation list. Got: ${typeof mod.default}`);
118
+ const result = mod.default();
119
+ if (!Array.isArray(result)) throw new Error(`migration.ts default export must return an array of operations. Got: ${typeof result}`);
120
+ return result;
121
+ }
122
+
123
+ //#endregion
124
+ export { evaluateMigrationTs, hasMigrationTs, scaffoldMigrationTs };
125
+ //# sourceMappingURL=migration-ts.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-ts.mjs","names":["opts: Record<string, unknown>","lines: string[]","result: unknown"],"sources":["../../src/migration-ts.ts"],"sourcesContent":["/**\n * Utilities for scaffolding and evaluating migration.ts files.\n *\n * - scaffoldMigrationTs: writes a migration.ts file with boilerplate\n * - evaluateMigrationTs: loads migration.ts via native Node import, returns descriptors\n *\n * Shared by migration plan (scaffold), migration new (scaffold), and\n * migration verify (evaluate).\n */\n\nimport { stat, writeFile } from 'node:fs/promises';\nimport type { OperationDescriptor } from '@prisma-next/framework-components/control';\nimport { join, relative, resolve } from 'pathe';\n\nconst MIGRATION_TS_FILE = 'migration.ts';\n\n/**\n * Options for scaffolding a migration.ts file.\n */\nexport interface ScaffoldOptions {\n /** Operation descriptors to serialize as builder calls. */\n readonly descriptors?: readonly OperationDescriptor[];\n /** Absolute path to contract.json — used to derive contract.d.ts import for typed builders. */\n readonly contractJsonPath?: string;\n}\n\nfunction serializeQueryInput(input: unknown): string {\n if (typeof input === 'boolean') return String(input);\n if (typeof input === 'symbol') return 'TODO /* fill in using db.sql.from(...) */';\n if (input === null || input === undefined) return 'null';\n if (Array.isArray(input)) {\n if (input.length === 0) return '[]';\n if (input.every((item) => typeof item === 'symbol'))\n return '[TODO /* fill in using db.sql.from(...) */]';\n return `[${input.map(serializeQueryInput).join(', ')}]`;\n }\n return JSON.stringify(input);\n}\n\nfunction descriptorToBuilderCall(desc: OperationDescriptor): string {\n switch (desc.kind) {\n case 'createTable':\n return `createTable(${JSON.stringify(desc['table'])})`;\n case 'dropTable':\n return `dropTable(${JSON.stringify(desc['table'])})`;\n case 'addColumn': {\n const args = [JSON.stringify(desc['table']), JSON.stringify(desc['column'])];\n if (desc['overrides']) {\n args.push(JSON.stringify(desc['overrides']));\n }\n return `addColumn(${args.join(', ')})`;\n }\n case 'dropColumn':\n return `dropColumn(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;\n case 'alterColumnType': {\n const opts: Record<string, unknown> = {};\n if (desc['using']) opts['using'] = desc['using'];\n if (desc['toType']) opts['toType'] = desc['toType'];\n const hasOpts = Object.keys(opts).length > 0;\n return hasOpts\n ? `alterColumnType(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])}, ${JSON.stringify(opts)})`\n : `alterColumnType(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;\n }\n case 'setNotNull':\n return `setNotNull(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;\n case 'dropNotNull':\n return `dropNotNull(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;\n case 'setDefault':\n return `setDefault(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;\n case 'dropDefault':\n return `dropDefault(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;\n case 'addPrimaryKey':\n return `addPrimaryKey(${JSON.stringify(desc['table'])})`;\n case 'addUnique':\n return `addUnique(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['columns'])})`;\n case 'addForeignKey':\n return `addForeignKey(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['columns'])})`;\n case 'dropConstraint':\n return `dropConstraint(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['constraintName'])})`;\n case 'createIndex':\n return `createIndex(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['columns'])})`;\n case 'dropIndex':\n return `dropIndex(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['indexName'])})`;\n case 'createEnumType':\n return desc['values']\n ? `createEnumType(${JSON.stringify(desc['typeName'])}, ${JSON.stringify(desc['values'])})`\n : `createEnumType(${JSON.stringify(desc['typeName'])})`;\n case 'addEnumValues':\n return `addEnumValues(${JSON.stringify(desc['typeName'])}, ${JSON.stringify(desc['values'])})`;\n case 'dropEnumType':\n return `dropEnumType(${JSON.stringify(desc['typeName'])})`;\n case 'renameType':\n return `renameType(${JSON.stringify(desc['fromName'])}, ${JSON.stringify(desc['toName'])})`;\n case 'createDependency':\n return `createDependency(${JSON.stringify(desc['dependencyId'])})`;\n case 'dataTransform':\n return `dataTransform(${JSON.stringify(desc['name'])}, {\\n check: ${serializeQueryInput(desc['check'])},\\n run: ${serializeQueryInput(desc['run'])},\\n })`;\n default:\n throw new Error(`Unknown descriptor kind: ${desc.kind}`);\n }\n}\n\n/**\n * Scaffolds a migration.ts file in the given package directory.\n * Serializes operation descriptors as builder calls that the user can edit.\n * On verify, this file is re-evaluated to produce the final ops.\n */\nexport async function scaffoldMigrationTs(\n packageDir: string,\n options: ScaffoldOptions = {},\n): Promise<void> {\n const filePath = join(packageDir, MIGRATION_TS_FILE);\n\n const descriptors = options.descriptors ?? [];\n const hasDataTransform = descriptors.some((d) => d.kind === 'dataTransform');\n\n const lines: string[] = [];\n\n if (hasDataTransform && options.contractJsonPath) {\n const relativeContractDts = relative(packageDir, options.contractJsonPath).replace(\n /\\.json$/,\n '.d',\n );\n lines.push(`import type { Contract } from \"${relativeContractDts}\"`);\n lines.push(`import { createBuilders } from \"@prisma-next/target-postgres/migration-builders\"`);\n lines.push('');\n const importList = [...new Set(descriptors.map((d) => d.kind))];\n importList.push('TODO');\n lines.push(`const { ${importList.join(', ')} } = createBuilders<Contract>()`);\n } else {\n const importList = [...new Set(descriptors.map((d) => d.kind))];\n if (importList.length === 0) {\n importList.push('createTable');\n }\n if (hasDataTransform) {\n importList.push('TODO');\n }\n lines.push(\n `import { ${importList.join(', ')} } from \"@prisma-next/target-postgres/migration-builders\"`,\n );\n }\n\n const calls = descriptors.map((d) => ` ${descriptorToBuilderCall(d)},`).join('\\n');\n const body = calls.length > 0 ? `\\n${calls}\\n` : '';\n\n lines.push('');\n lines.push(`export default () => [${body}]`);\n lines.push('');\n\n await writeFile(filePath, lines.join('\\n'));\n}\n\n/**\n * Checks whether a migration.ts file exists in the package directory.\n */\nexport async function hasMigrationTs(packageDir: string): Promise<boolean> {\n try {\n const s = await stat(join(packageDir, MIGRATION_TS_FILE));\n return s.isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Evaluates a migration.ts file by loading it via native Node import.\n * Returns the result of calling the default export (expected to be a\n * function returning an array of operation descriptors).\n *\n * Requires Node ≥24 for native TypeScript support.\n */\nexport async function evaluateMigrationTs(packageDir: string): Promise<readonly unknown[]> {\n const filePath = resolve(join(packageDir, MIGRATION_TS_FILE));\n\n try {\n await stat(filePath);\n } catch {\n throw new Error(`migration.ts not found at \"${filePath}\"`);\n }\n\n // Use native Node TS import (Node ≥24, stable type stripping)\n const mod = (await import(filePath)) as { default?: unknown };\n\n if (typeof mod.default !== 'function') {\n throw new Error(\n `migration.ts must export a default function returning an operation list. Got: ${typeof mod.default}`,\n );\n }\n\n const result: unknown = mod.default();\n\n if (!Array.isArray(result)) {\n throw new Error(\n `migration.ts default export must return an array of operations. Got: ${typeof result}`,\n );\n }\n\n return result;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { c as errorInvalidRefValue, l as errorInvalidRefs, s as errorInvalidRefName, t as MigrationToolsError } from "../errors-CRYl7qCG.mjs";
1
+ import { c as errorInvalidRefValue, l as errorInvalidRefs, s as errorInvalidRefName, t as MigrationToolsError } from "../errors-CSAAto11.mjs";
2
2
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
3
  import { type } from "arktype";
4
4
  import { dirname, join } from "pathe";
@@ -1,4 +1,4 @@
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-BmN6uFrd.mjs";
1
+ import { a as DraftMigrationManifest, c as MigrationHints, d as isAttested, f as isDraft, i as DraftMigrationBundle, l as MigrationManifest, n as AttestedMigrationManifest, o as MigrationChainEntry, r as BaseMigrationBundle, s as MigrationGraph, t as AttestedMigrationBundle, u as MigrationOps } from "../types-BW_pJEe8.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 AttestedMigrationBundle, type AttestedMigrationManifest, type DraftMigrationManifest, type MigrationBundle, type MigrationBundle as MigrationPackage, type MigrationChainEntry, type MigrationGraph, type MigrationHints, type MigrationManifest, type MigrationOps, MigrationToolsError, isAttested };
34
+ export { type AttestedMigrationBundle, type AttestedMigrationManifest, type BaseMigrationBundle, type BaseMigrationBundle as MigrationBundle, type BaseMigrationBundle as MigrationPackage, type DraftMigrationBundle, type DraftMigrationManifest, type MigrationChainEntry, type MigrationGraph, type MigrationHints, type MigrationManifest, type MigrationOps, MigrationToolsError, isAttested, isDraft };
35
35
  //# sourceMappingURL=types.d.mts.map
@@ -1,4 +1,4 @@
1
- import { t as MigrationToolsError } from "../errors-CRYl7qCG.mjs";
1
+ import { t as MigrationToolsError } from "../errors-CSAAto11.mjs";
2
2
 
3
3
  //#region src/types.ts
4
4
  /**
@@ -8,7 +8,10 @@ import { t as MigrationToolsError } from "../errors-CRYl7qCG.mjs";
8
8
  function isAttested(bundle) {
9
9
  return typeof bundle.manifest.migrationId === "string";
10
10
  }
11
+ function isDraft(bundle) {
12
+ return bundle.manifest.migrationId === null;
13
+ }
11
14
 
12
15
  //#endregion
13
- export { MigrationToolsError, isAttested };
16
+ export { MigrationToolsError, isAttested, isDraft };
14
17
  //# sourceMappingURL=types.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","names":[],"sources":["../../src/types.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport type { MigrationPlanOperation } from '@prisma-next/framework-components/control';\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: Contract | null;\n readonly toContract: Contract;\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
+ {"version":3,"file":"types.mjs","names":[],"sources":["../../src/types.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport type { MigrationPlanOperation } from '@prisma-next/framework-components/control';\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: Contract | null;\n readonly toContract: Contract;\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 BaseMigrationBundle {\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 BaseMigrationBundle {\n readonly manifest: AttestedMigrationManifest;\n}\n\nexport interface DraftMigrationBundle extends BaseMigrationBundle {\n readonly manifest: DraftMigrationManifest;\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: BaseMigrationBundle): bundle is AttestedMigrationBundle {\n return typeof bundle.manifest.migrationId === 'string';\n}\n\nexport function isDraft(bundle: BaseMigrationBundle): bundle is DraftMigrationBundle {\n return bundle.manifest.migrationId === null;\n}\n"],"mappings":";;;;;;;AAmGA,SAAgB,WAAW,QAAgE;AACzF,QAAO,OAAO,OAAO,SAAS,gBAAgB;;AAGhD,SAAgB,QAAQ,QAA6D;AACnF,QAAO,OAAO,SAAS,gBAAgB"}
@@ -1,4 +1,4 @@
1
- import { a as errorInvalidJson, d as errorMissingFile, o as errorInvalidManifest, r as errorDirectoryExists, u as errorInvalidSlug } from "./errors-CRYl7qCG.mjs";
1
+ import { a as errorInvalidJson, d as errorMissingFile, o as errorInvalidManifest, r as errorDirectoryExists, u as errorInvalidSlug } from "./errors-CSAAto11.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";
@@ -37,7 +37,7 @@ const MigrationManifestSchema = type({
37
37
  const MigrationOpsSchema = type({
38
38
  id: "string",
39
39
  label: "string",
40
- operationClass: "'additive' | 'widening' | 'destructive'"
40
+ operationClass: "'additive' | 'widening' | 'destructive' | 'data'"
41
41
  }).array();
42
42
  async function writeMigrationPackage(dir, manifest, ops) {
43
43
  await mkdir(dirname(dir), { recursive: true });
@@ -50,6 +50,12 @@ async function writeMigrationPackage(dir, manifest, ops) {
50
50
  await writeFile(join(dir, MANIFEST_FILE), JSON.stringify(manifest, null, 2), { flag: "wx" });
51
51
  await writeFile(join(dir, OPS_FILE), JSON.stringify(ops, null, 2), { flag: "wx" });
52
52
  }
53
+ async function writeMigrationManifest(dir, manifest) {
54
+ await writeFile(join(dir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`);
55
+ }
56
+ async function writeMigrationOps(dir, ops) {
57
+ await writeFile(join(dir, OPS_FILE), `${JSON.stringify(ops, null, 2)}\n`);
58
+ }
53
59
  async function readMigrationPackage(dir) {
54
60
  const manifestPath = join(dir, MANIFEST_FILE);
55
61
  const opsPath = join(dir, OPS_FILE);
@@ -126,5 +132,5 @@ function formatMigrationDirName(timestamp, slug) {
126
132
  }
127
133
 
128
134
  //#endregion
129
- export { writeMigrationPackage as i, readMigrationPackage as n, readMigrationsDir as r, formatMigrationDirName as t };
130
- //# sourceMappingURL=io-CyVQJBqz.mjs.map
135
+ export { writeMigrationOps as a, writeMigrationManifest as i, readMigrationPackage as n, writeMigrationPackage as o, readMigrationsDir as r, formatMigrationDirName as t };
136
+ //# sourceMappingURL=io-LsuurzNb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io-LsuurzNb.mjs","names":["manifestRaw: string","opsRaw: string","manifest: MigrationManifest","ops: MigrationOps","entries: string[]","packages: BaseMigrationBundle[]"],"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 { BaseMigrationBundle, 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' | 'data'\",\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 writeMigrationManifest(\n dir: string,\n manifest: MigrationManifest,\n): Promise<void> {\n await writeFile(join(dir, MANIFEST_FILE), `${JSON.stringify(manifest, 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<BaseMigrationBundle> {\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 BaseMigrationBundle[]> {\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: BaseMigrationBundle[] = [];\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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"}
@@ -55,7 +55,7 @@ type MigrationOps = readonly MigrationPlanOperation[];
55
55
  * An on-disk migration directory containing a manifest and operations.
56
56
  * The manifest may be draft or attested.
57
57
  */
58
- interface MigrationBundle {
58
+ interface BaseMigrationBundle {
59
59
  readonly dirName: string;
60
60
  readonly dirPath: string;
61
61
  readonly manifest: MigrationManifest;
@@ -65,9 +65,12 @@ interface MigrationBundle {
65
65
  * A bundle known to be attested (migrationId is a string).
66
66
  * Use this after filtering bundles to attested-only.
67
67
  */
68
- interface AttestedMigrationBundle extends MigrationBundle {
68
+ interface AttestedMigrationBundle extends BaseMigrationBundle {
69
69
  readonly manifest: AttestedMigrationManifest;
70
70
  }
71
+ interface DraftMigrationBundle extends BaseMigrationBundle {
72
+ readonly manifest: DraftMigrationManifest;
73
+ }
71
74
  /**
72
75
  * An entry in the migration graph. Only attested migrations appear in the
73
76
  * graph, so `migrationId` is always a string.
@@ -90,7 +93,8 @@ interface MigrationGraph {
90
93
  * Type guard that narrows a MigrationBundle to an AttestedMigrationBundle.
91
94
  * Use with `.filter(isAttested)` to get a typed array of attested bundles.
92
95
  */
93
- declare function isAttested(bundle: MigrationBundle): bundle is AttestedMigrationBundle;
96
+ declare function isAttested(bundle: BaseMigrationBundle): bundle is AttestedMigrationBundle;
97
+ declare function isDraft(bundle: BaseMigrationBundle): bundle is DraftMigrationBundle;
94
98
  //#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-BmN6uFrd.d.mts.map
99
+ export { DraftMigrationManifest as a, MigrationHints as c, isAttested as d, isDraft as f, DraftMigrationBundle as i, MigrationManifest as l, AttestedMigrationManifest as n, MigrationChainEntry as o, BaseMigrationBundle as r, MigrationGraph as s, AttestedMigrationBundle as t, MigrationOps as u };
100
+ //# sourceMappingURL=types-BW_pJEe8.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-BW_pJEe8.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,SAAmB,GAAA,UAGf;EAQJ,SAAA,YAAA,EAlDQ,QAkDgB,GAAA,IACpB;EAGJ,SAAA,UAAA,EArDM,QAqDe;EAQrB,SAAA,KAAA,EA5DC,cA4DkB;EASnB,SAAA,MAAA,EAAc,SAAA,MAAA,EAAA;EACb,SAAA,UAAA,CAAA,EAAA;IACoC,SAAA,MAAA,CAAA,EAAA,MAAA;IAA7B,SAAA,KAAA,CAAA,EAAA,MAAA;EAC6B,CAAA;EAA7B,SAAA,SAAA,CAAA,EAAA;IACqB,SAAA,KAAA,EAAA,MAAA;IAApB,SAAA,KAAA,EAAA,MAAA;EAAW,CAAA,GAAA,IAAA;EAOrB,SAAA,SAAU,EAAA,MAAS;AAInC;;;;;;UAxEiB,sBAAA,SAA+B;;;;;;;UAQ/B,yBAAA,SAAkC;;;;;;;;KASvC,iBAAA,GAAoB,yBAAyB;KAE7C,YAAA,YAAwB;;;;;UAMnB,mBAAA;;;qBAGI;gBACL;;;;;;UAOC,uBAAA,SAAgC;qBAC5B;;UAGJ,oBAAA,SAA6B;qBACzB;;;;;;UAOJ,mBAAA;;;;;;;;UASA,cAAA;kBACC;yBACO,6BAA6B;yBAC7B,6BAA6B;0BAC5B,oBAAoB;;;;;;iBAO9B,UAAA,SAAmB,gCAAgC;iBAInD,OAAA,SAAgB,gCAAgC"}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@prisma-next/migration-tools",
3
- "version": "0.3.0-dev.159",
3
+ "version": "0.3.0-dev.161",
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.159",
11
- "@prisma-next/framework-components": "0.3.0-dev.159",
12
- "@prisma-next/utils": "0.3.0-dev.159"
10
+ "@prisma-next/contract": "0.3.0-dev.161",
11
+ "@prisma-next/framework-components": "0.3.0-dev.161",
12
+ "@prisma-next/utils": "0.3.0-dev.161"
13
13
  },
14
14
  "devDependencies": {
15
15
  "tsdown": "0.18.4",
@@ -50,6 +50,10 @@
50
50
  "types": "./dist/exports/constants.d.mts",
51
51
  "import": "./dist/exports/constants.mjs"
52
52
  },
53
+ "./migration-ts": {
54
+ "types": "./dist/exports/migration-ts.d.mts",
55
+ "import": "./dist/exports/migration-ts.mjs"
56
+ },
53
57
  "./package.json": "./package.json"
54
58
  },
55
59
  "repository": {
@@ -1,9 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { writeFile } from 'node:fs/promises';
3
2
  import { canonicalizeContract } from '@prisma-next/contract/hashing';
4
- import { join } from 'pathe';
5
3
  import { canonicalizeJson } from './canonicalize-json';
6
- import { readMigrationPackage } from './io';
4
+ import { readMigrationPackage, writeMigrationManifest } from './io';
7
5
  import type { MigrationManifest, MigrationOps } from './types';
8
6
 
9
7
  export interface VerifyResult {
@@ -49,7 +47,7 @@ export async function attestMigration(dir: string): Promise<string> {
49
47
  const migrationId = computeMigrationId(pkg.manifest, pkg.ops);
50
48
 
51
49
  const updated = { ...pkg.manifest, migrationId };
52
- await writeFile(join(dir, 'migration.json'), JSON.stringify(updated, null, 2));
50
+ await writeMigrationManifest(dir, updated);
53
51
 
54
52
  return migrationId;
55
53
  }
package/src/errors.ts CHANGED
@@ -135,6 +135,14 @@ export function errorInvalidRefs(refsPath: string, reason: string): MigrationToo
135
135
  });
136
136
  }
137
137
 
138
+ export function errorInvalidRefFile(filePath: string, reason: string): MigrationToolsError {
139
+ return new MigrationToolsError('MIGRATION.INVALID_REF_FILE', 'Invalid ref file', {
140
+ why: `Ref file at "${filePath}" is invalid: ${reason}`,
141
+ fix: 'Ensure the ref file contains valid JSON with { "hash": "sha256:<64 hex chars>", "invariants": ["..."] }.',
142
+ details: { path: filePath, reason },
143
+ });
144
+ }
145
+
138
146
  export function errorInvalidRefName(refName: string): MigrationToolsError {
139
147
  return new MigrationToolsError('MIGRATION.INVALID_REF_NAME', 'Invalid ref name', {
140
148
  why: `Ref name "${refName}" is invalid. Names must be lowercase alphanumeric with hyphens or forward slashes (no "." or ".." segments).`,
package/src/exports/io.ts CHANGED
@@ -2,5 +2,7 @@ export {
2
2
  formatMigrationDirName,
3
3
  readMigrationPackage,
4
4
  readMigrationsDir,
5
+ writeMigrationManifest,
6
+ writeMigrationOps,
5
7
  writeMigrationPackage,
6
8
  } from '../io';
@@ -0,0 +1,6 @@
1
+ export type { ScaffoldOptions } from '../migration-ts';
2
+ export {
3
+ evaluateMigrationTs,
4
+ hasMigrationTs,
5
+ scaffoldMigrationTs,
6
+ } from '../migration-ts';
@@ -2,13 +2,15 @@ export { MigrationToolsError } from '../errors';
2
2
  export type {
3
3
  AttestedMigrationBundle,
4
4
  AttestedMigrationManifest,
5
+ BaseMigrationBundle,
6
+ BaseMigrationBundle as MigrationBundle,
7
+ BaseMigrationBundle as MigrationPackage,
8
+ DraftMigrationBundle,
5
9
  DraftMigrationManifest,
6
- MigrationBundle,
7
- MigrationBundle as MigrationPackage,
8
10
  MigrationChainEntry,
9
11
  MigrationGraph,
10
12
  MigrationHints,
11
13
  MigrationManifest,
12
14
  MigrationOps,
13
15
  } from '../types';
14
- export { isAttested } from '../types';
16
+ export { isAttested, isDraft } from '../types';
package/src/io.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  errorInvalidSlug,
9
9
  errorMissingFile,
10
10
  } from './errors';
11
- import type { MigrationBundle, MigrationManifest, MigrationOps } from './types';
11
+ import type { BaseMigrationBundle, MigrationManifest, MigrationOps } from './types';
12
12
 
13
13
  const MANIFEST_FILE = 'migration.json';
14
14
  const OPS_FILE = 'ops.json';
@@ -48,7 +48,7 @@ const MigrationManifestSchema = type({
48
48
  const MigrationOpSchema = type({
49
49
  id: 'string',
50
50
  label: 'string',
51
- operationClass: "'additive' | 'widening' | 'destructive'",
51
+ operationClass: "'additive' | 'widening' | 'destructive' | 'data'",
52
52
  });
53
53
 
54
54
  // Intentionally shallow: operation-specific payload validation is owned by planner/runner layers.
@@ -74,7 +74,18 @@ export async function writeMigrationPackage(
74
74
  await writeFile(join(dir, OPS_FILE), JSON.stringify(ops, null, 2), { flag: 'wx' });
75
75
  }
76
76
 
77
- export async function readMigrationPackage(dir: string): Promise<MigrationBundle> {
77
+ export async function writeMigrationManifest(
78
+ dir: string,
79
+ manifest: MigrationManifest,
80
+ ): Promise<void> {
81
+ await writeFile(join(dir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`);
82
+ }
83
+
84
+ export async function writeMigrationOps(dir: string, ops: MigrationOps): Promise<void> {
85
+ await writeFile(join(dir, OPS_FILE), `${JSON.stringify(ops, null, 2)}\n`);
86
+ }
87
+
88
+ export async function readMigrationPackage(dir: string): Promise<BaseMigrationBundle> {
78
89
  const manifestPath = join(dir, MANIFEST_FILE);
79
90
  const opsPath = join(dir, OPS_FILE);
80
91
 
@@ -142,7 +153,7 @@ function validateOps(ops: unknown, filePath: string): asserts ops is MigrationOp
142
153
 
143
154
  export async function readMigrationsDir(
144
155
  migrationsRoot: string,
145
- ): Promise<readonly MigrationBundle[]> {
156
+ ): Promise<readonly BaseMigrationBundle[]> {
146
157
  let entries: string[];
147
158
  try {
148
159
  entries = await readdir(migrationsRoot);
@@ -153,7 +164,7 @@ export async function readMigrationsDir(
153
164
  throw error;
154
165
  }
155
166
 
156
- const packages: MigrationBundle[] = [];
167
+ const packages: BaseMigrationBundle[] = [];
157
168
 
158
169
  for (const entry of entries.sort()) {
159
170
  const entryPath = join(migrationsRoot, entry);
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Utilities for scaffolding and evaluating migration.ts files.
3
+ *
4
+ * - scaffoldMigrationTs: writes a migration.ts file with boilerplate
5
+ * - evaluateMigrationTs: loads migration.ts via native Node import, returns descriptors
6
+ *
7
+ * Shared by migration plan (scaffold), migration new (scaffold), and
8
+ * migration verify (evaluate).
9
+ */
10
+
11
+ import { stat, writeFile } from 'node:fs/promises';
12
+ import type { OperationDescriptor } from '@prisma-next/framework-components/control';
13
+ import { join, relative, resolve } from 'pathe';
14
+
15
+ const MIGRATION_TS_FILE = 'migration.ts';
16
+
17
+ /**
18
+ * Options for scaffolding a migration.ts file.
19
+ */
20
+ export interface ScaffoldOptions {
21
+ /** Operation descriptors to serialize as builder calls. */
22
+ readonly descriptors?: readonly OperationDescriptor[];
23
+ /** Absolute path to contract.json — used to derive contract.d.ts import for typed builders. */
24
+ readonly contractJsonPath?: string;
25
+ }
26
+
27
+ function serializeQueryInput(input: unknown): string {
28
+ if (typeof input === 'boolean') return String(input);
29
+ if (typeof input === 'symbol') return 'TODO /* fill in using db.sql.from(...) */';
30
+ if (input === null || input === undefined) return 'null';
31
+ if (Array.isArray(input)) {
32
+ if (input.length === 0) return '[]';
33
+ if (input.every((item) => typeof item === 'symbol'))
34
+ return '[TODO /* fill in using db.sql.from(...) */]';
35
+ return `[${input.map(serializeQueryInput).join(', ')}]`;
36
+ }
37
+ return JSON.stringify(input);
38
+ }
39
+
40
+ function descriptorToBuilderCall(desc: OperationDescriptor): string {
41
+ switch (desc.kind) {
42
+ case 'createTable':
43
+ return `createTable(${JSON.stringify(desc['table'])})`;
44
+ case 'dropTable':
45
+ return `dropTable(${JSON.stringify(desc['table'])})`;
46
+ case 'addColumn': {
47
+ const args = [JSON.stringify(desc['table']), JSON.stringify(desc['column'])];
48
+ if (desc['overrides']) {
49
+ args.push(JSON.stringify(desc['overrides']));
50
+ }
51
+ return `addColumn(${args.join(', ')})`;
52
+ }
53
+ case 'dropColumn':
54
+ return `dropColumn(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;
55
+ case 'alterColumnType': {
56
+ const opts: Record<string, unknown> = {};
57
+ if (desc['using']) opts['using'] = desc['using'];
58
+ if (desc['toType']) opts['toType'] = desc['toType'];
59
+ const hasOpts = Object.keys(opts).length > 0;
60
+ return hasOpts
61
+ ? `alterColumnType(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])}, ${JSON.stringify(opts)})`
62
+ : `alterColumnType(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;
63
+ }
64
+ case 'setNotNull':
65
+ return `setNotNull(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;
66
+ case 'dropNotNull':
67
+ return `dropNotNull(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;
68
+ case 'setDefault':
69
+ return `setDefault(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;
70
+ case 'dropDefault':
71
+ return `dropDefault(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['column'])})`;
72
+ case 'addPrimaryKey':
73
+ return `addPrimaryKey(${JSON.stringify(desc['table'])})`;
74
+ case 'addUnique':
75
+ return `addUnique(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['columns'])})`;
76
+ case 'addForeignKey':
77
+ return `addForeignKey(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['columns'])})`;
78
+ case 'dropConstraint':
79
+ return `dropConstraint(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['constraintName'])})`;
80
+ case 'createIndex':
81
+ return `createIndex(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['columns'])})`;
82
+ case 'dropIndex':
83
+ return `dropIndex(${JSON.stringify(desc['table'])}, ${JSON.stringify(desc['indexName'])})`;
84
+ case 'createEnumType':
85
+ return desc['values']
86
+ ? `createEnumType(${JSON.stringify(desc['typeName'])}, ${JSON.stringify(desc['values'])})`
87
+ : `createEnumType(${JSON.stringify(desc['typeName'])})`;
88
+ case 'addEnumValues':
89
+ return `addEnumValues(${JSON.stringify(desc['typeName'])}, ${JSON.stringify(desc['values'])})`;
90
+ case 'dropEnumType':
91
+ return `dropEnumType(${JSON.stringify(desc['typeName'])})`;
92
+ case 'renameType':
93
+ return `renameType(${JSON.stringify(desc['fromName'])}, ${JSON.stringify(desc['toName'])})`;
94
+ case 'createDependency':
95
+ return `createDependency(${JSON.stringify(desc['dependencyId'])})`;
96
+ case 'dataTransform':
97
+ return `dataTransform(${JSON.stringify(desc['name'])}, {\n check: ${serializeQueryInput(desc['check'])},\n run: ${serializeQueryInput(desc['run'])},\n })`;
98
+ default:
99
+ throw new Error(`Unknown descriptor kind: ${desc.kind}`);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Scaffolds a migration.ts file in the given package directory.
105
+ * Serializes operation descriptors as builder calls that the user can edit.
106
+ * On verify, this file is re-evaluated to produce the final ops.
107
+ */
108
+ export async function scaffoldMigrationTs(
109
+ packageDir: string,
110
+ options: ScaffoldOptions = {},
111
+ ): Promise<void> {
112
+ const filePath = join(packageDir, MIGRATION_TS_FILE);
113
+
114
+ const descriptors = options.descriptors ?? [];
115
+ const hasDataTransform = descriptors.some((d) => d.kind === 'dataTransform');
116
+
117
+ const lines: string[] = [];
118
+
119
+ if (hasDataTransform && options.contractJsonPath) {
120
+ const relativeContractDts = relative(packageDir, options.contractJsonPath).replace(
121
+ /\.json$/,
122
+ '.d',
123
+ );
124
+ lines.push(`import type { Contract } from "${relativeContractDts}"`);
125
+ lines.push(`import { createBuilders } from "@prisma-next/target-postgres/migration-builders"`);
126
+ lines.push('');
127
+ const importList = [...new Set(descriptors.map((d) => d.kind))];
128
+ importList.push('TODO');
129
+ lines.push(`const { ${importList.join(', ')} } = createBuilders<Contract>()`);
130
+ } else {
131
+ const importList = [...new Set(descriptors.map((d) => d.kind))];
132
+ if (importList.length === 0) {
133
+ importList.push('createTable');
134
+ }
135
+ if (hasDataTransform) {
136
+ importList.push('TODO');
137
+ }
138
+ lines.push(
139
+ `import { ${importList.join(', ')} } from "@prisma-next/target-postgres/migration-builders"`,
140
+ );
141
+ }
142
+
143
+ const calls = descriptors.map((d) => ` ${descriptorToBuilderCall(d)},`).join('\n');
144
+ const body = calls.length > 0 ? `\n${calls}\n` : '';
145
+
146
+ lines.push('');
147
+ lines.push(`export default () => [${body}]`);
148
+ lines.push('');
149
+
150
+ await writeFile(filePath, lines.join('\n'));
151
+ }
152
+
153
+ /**
154
+ * Checks whether a migration.ts file exists in the package directory.
155
+ */
156
+ export async function hasMigrationTs(packageDir: string): Promise<boolean> {
157
+ try {
158
+ const s = await stat(join(packageDir, MIGRATION_TS_FILE));
159
+ return s.isFile();
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Evaluates a migration.ts file by loading it via native Node import.
167
+ * Returns the result of calling the default export (expected to be a
168
+ * function returning an array of operation descriptors).
169
+ *
170
+ * Requires Node ≥24 for native TypeScript support.
171
+ */
172
+ export async function evaluateMigrationTs(packageDir: string): Promise<readonly unknown[]> {
173
+ const filePath = resolve(join(packageDir, MIGRATION_TS_FILE));
174
+
175
+ try {
176
+ await stat(filePath);
177
+ } catch {
178
+ throw new Error(`migration.ts not found at "${filePath}"`);
179
+ }
180
+
181
+ // Use native Node TS import (Node ≥24, stable type stripping)
182
+ const mod = (await import(filePath)) as { default?: unknown };
183
+
184
+ if (typeof mod.default !== 'function') {
185
+ throw new Error(
186
+ `migration.ts must export a default function returning an operation list. Got: ${typeof mod.default}`,
187
+ );
188
+ }
189
+
190
+ const result: unknown = mod.default();
191
+
192
+ if (!Array.isArray(result)) {
193
+ throw new Error(
194
+ `migration.ts default export must return an array of operations. Got: ${typeof result}`,
195
+ );
196
+ }
197
+
198
+ return result;
199
+ }
package/src/types.ts CHANGED
@@ -54,7 +54,7 @@ export type MigrationOps = readonly MigrationPlanOperation[];
54
54
  * An on-disk migration directory containing a manifest and operations.
55
55
  * The manifest may be draft or attested.
56
56
  */
57
- export interface MigrationBundle {
57
+ export interface BaseMigrationBundle {
58
58
  readonly dirName: string;
59
59
  readonly dirPath: string;
60
60
  readonly manifest: MigrationManifest;
@@ -65,10 +65,14 @@ export interface MigrationBundle {
65
65
  * A bundle known to be attested (migrationId is a string).
66
66
  * Use this after filtering bundles to attested-only.
67
67
  */
68
- export interface AttestedMigrationBundle extends MigrationBundle {
68
+ export interface AttestedMigrationBundle extends BaseMigrationBundle {
69
69
  readonly manifest: AttestedMigrationManifest;
70
70
  }
71
71
 
72
+ export interface DraftMigrationBundle extends BaseMigrationBundle {
73
+ readonly manifest: DraftMigrationManifest;
74
+ }
75
+
72
76
  /**
73
77
  * An entry in the migration graph. Only attested migrations appear in the
74
78
  * graph, so `migrationId` is always a string.
@@ -93,6 +97,10 @@ export interface MigrationGraph {
93
97
  * Type guard that narrows a MigrationBundle to an AttestedMigrationBundle.
94
98
  * Use with `.filter(isAttested)` to get a typed array of attested bundles.
95
99
  */
96
- export function isAttested(bundle: MigrationBundle): bundle is AttestedMigrationBundle {
100
+ export function isAttested(bundle: BaseMigrationBundle): bundle is AttestedMigrationBundle {
97
101
  return typeof bundle.manifest.migrationId === 'string';
98
102
  }
103
+
104
+ export function isDraft(bundle: BaseMigrationBundle): bundle is DraftMigrationBundle {
105
+ return bundle.manifest.migrationId === null;
106
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"io-CyVQJBqz.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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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-BmN6uFrd.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,QAkDgB,GAAA,IACpB;EAOJ,SAAA,UAAA,EAzDM,QAyDa;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"}