{"version":3,"file":"migration.mjs","names":[],"sources":["../../src/migration-base.ts"],"sourcesContent":["import { realpathSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport type {\n  ControlStack,\n  MigrationPlan,\n  MigrationPlanOperation,\n} from '@prisma-next/framework-components/control';\nimport { type } from 'arktype';\nimport { errorInvalidOperationEntry } from './errors';\nimport { computeMigrationHash } from './hash';\nimport { deriveProvidedInvariants } from './invariants';\nimport type { MigrationMetadata } from './metadata';\nimport { MigrationOpSchema } from './op-schema';\nimport type { MigrationOps } from './package';\n\nexport interface MigrationMeta {\n  readonly from: string | null;\n  readonly to: string;\n}\n\n// `from` rejects empty strings to mirror `MigrationMetadataSchema` in\n// `./io.ts`. Without this match, an authored migration could `describe()` with\n// `from: ''` and pass `buildMigrationArtifacts`'s validation, only to have\n// `readMigrationPackage` reject the resulting `migration.json` later — the\n// two validators must agree on the legal value space.\nconst MigrationMetaSchema = type({\n  from: 'string > 0 | null',\n  to: 'string',\n});\n\n/**\n * Base class for migrations.\n *\n * A `Migration` subclass is itself a `MigrationPlan`: CLI commands and the\n * runner can consume it directly via `targetId`, `operations`, `origin`, and\n * `destination`. The metadata-shaped inputs come from `describe()`, which\n * every migration must implement — `migration.json` is required for a\n * migration to be valid.\n */\nexport abstract class Migration<\n  _TOperation extends MigrationPlanOperation = MigrationPlanOperation,\n  TFamilyId extends string = string,\n  TTargetId extends string = string,\n> implements MigrationPlan\n{\n  abstract readonly targetId: string;\n\n  /**\n   * Assembled `ControlStack` injected by the orchestrator (`runMigration`).\n   *\n   * Subclasses (e.g. `PostgresMigration`) read the stack to materialize their\n   * adapter once per instance. Optional at the abstract level so unit tests can\n   * construct `Migration` instances purely for `operations` / `describe`\n   * assertions without needing a real stack; concrete subclasses that need the\n   * stack at runtime should narrow the parameter to required.\n   */\n  protected readonly stack: ControlStack<TFamilyId, TTargetId> | undefined;\n\n  constructor(stack?: ControlStack<TFamilyId, TTargetId>) {\n    this.stack = stack;\n  }\n\n  /**\n   * Ordered list of operations this migration performs.\n   *\n   * Implemented as a getter so that subclasses can either precompute the list\n   * in their constructor or build it lazily per access. Entries may be Promises\n   * when the target requires async codec resolution (e.g. DDL literal defaults).\n   */\n  abstract get operations(): readonly (MigrationPlanOperation | Promise<MigrationPlanOperation>)[];\n\n  /**\n   * Metadata inputs used to build `migration.json` and to derive the plan's\n   * origin/destination identities. Every migration must provide this —\n   * omitting it would produce an invalid on-disk migration package.\n   */\n  abstract describe(): MigrationMeta;\n\n  get origin(): { readonly storageHash: string } | null {\n    const from = this.describe().from;\n    return from === null ? null : { storageHash: from };\n  }\n\n  get destination(): { readonly storageHash: string } {\n    return { storageHash: this.describe().to };\n  }\n}\n\n/**\n * Returns true when `import.meta.url` resolves to the same file that was\n * invoked as the node entrypoint (`process.argv[1]`). Used by\n * `MigrationCLI.run` (in `@prisma-next/cli/migration-cli`) to no-op when\n * the migration module is being imported (e.g. by another script) rather\n * than executed directly.\n */\nexport function isDirectEntrypoint(importMetaUrl: string): boolean {\n  const metaFilename = fileURLToPath(importMetaUrl);\n  const argv1 = process.argv[1];\n  if (!argv1) return false;\n  try {\n    return realpathSync(metaFilename) === realpathSync(argv1);\n  } catch {\n    return false;\n  }\n}\n\n/**\n * In-memory artifacts produced from a `Migration` instance: the\n * serialized `ops.json` body, the `migration.json` metadata object, and\n * its serialized form. Returned by `buildMigrationArtifacts` so callers\n * (today: `MigrationCLI.run` in `@prisma-next/cli/migration-cli`) can\n * decide how to persist them — write to disk, print in dry-run, ship\n * over the wire — without coupling artifact construction to file I/O.\n *\n * `metadataJson` is `JSON.stringify(metadata, null, 2)` — the canonical\n * on-disk shape that the arktype loader-schema in `./io` validates.\n */\nexport interface MigrationArtifacts {\n  readonly opsJson: string;\n  readonly metadata: MigrationMetadata;\n  readonly metadataJson: string;\n}\n\n/**\n * Build the attested metadata from `describe()`-derived metadata, the\n * operations list, and the previously-scaffolded metadata (if any).\n *\n * When a `migration.json` already exists for this package (the common\n * case: it was scaffolded by `migration plan`), preserve `createdAt`\n * set there — that field is owned by the CLI scaffolder, not the authored\n * class. Only the `describe()`-derived fields (`from`, `to`) and the\n * operations change as the author iterates. When no metadata exists yet\n * (a bare `migration.ts` run from scratch), synthesize a minimal but\n * schema-conformant record so the resulting package can still be read,\n * verified, and applied.\n *\n * The `migrationHash` is recomputed against the current metadata + ops so\n * the on-disk artifacts are always fully attested.\n */\nfunction buildAttestedMetadata(\n  meta: MigrationMeta,\n  ops: MigrationOps,\n  existing: Partial<MigrationMetadata> | null,\n): MigrationMetadata {\n  const baseMetadata: Omit<MigrationMetadata, 'migrationHash'> = {\n    from: meta.from,\n    to: meta.to,\n    providedInvariants: deriveProvidedInvariants(ops),\n    createdAt: existing?.createdAt ?? new Date().toISOString(),\n  };\n\n  const migrationHash = computeMigrationHash(baseMetadata, ops);\n  return { ...baseMetadata, migrationHash };\n}\n\n/**\n * Pure conversion from a `Migration` instance (plus the previously\n * scaffolded metadata, when one exists on disk) to the in-memory\n * artifacts that downstream tooling persists. Owns metadata validation,\n * metadata synthesis/preservation, and the content-addressed\n * `migrationHash` computation, but performs no file I/O — callers handle\n * reads (to source `existing`) and writes (to persist `opsJson` /\n * `metadataJson`).\n */\nexport async function buildMigrationArtifacts(\n  instance: Migration,\n  existing: Partial<MigrationMetadata> | null,\n): Promise<MigrationArtifacts> {\n  const rawOps = instance.operations;\n  if (!Array.isArray(rawOps)) {\n    throw new Error('operations must be an array');\n  }\n  const ops = await Promise.all(rawOps);\n\n  for (let index = 0; index < ops.length; index++) {\n    const result = MigrationOpSchema(ops[index]);\n    if (result instanceof type.errors) {\n      throw errorInvalidOperationEntry(index, result.summary);\n    }\n  }\n\n  const rawMeta: unknown = instance.describe();\n  const parsed = MigrationMetaSchema(rawMeta);\n  if (parsed instanceof type.errors) {\n    throw new Error(`describe() returned invalid metadata: ${parsed.summary}`);\n  }\n\n  const metadata = buildAttestedMetadata(parsed, ops, existing);\n\n  return {\n    opsJson: JSON.stringify(ops, null, 2),\n    metadata,\n    metadataJson: JSON.stringify(metadata, null, 2),\n  };\n}\n"],"mappings":";;;;;;;;AAyBA,MAAM,sBAAsB,KAAK;CAC/B,MAAM;CACN,IAAI;AACN,CAAC;;;;;;;;;;AAWD,IAAsB,YAAtB,MAKA;;;;;;;;;;CAYE;CAEA,YAAY,OAA4C;EACtD,KAAK,QAAQ;CACf;CAkBA,IAAI,SAAkD;EACpD,MAAM,OAAO,KAAK,SAAS,CAAC,CAAC;EAC7B,OAAO,SAAS,OAAO,OAAO,EAAE,aAAa,KAAK;CACpD;CAEA,IAAI,cAAgD;EAClD,OAAO,EAAE,aAAa,KAAK,SAAS,CAAC,CAAC,GAAG;CAC3C;AACF;;;;;;;;AASA,SAAgB,mBAAmB,eAAgC;CACjE,MAAM,eAAe,cAAc,aAAa;CAChD,MAAM,QAAQ,QAAQ,KAAK;CAC3B,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI;EACF,OAAO,aAAa,YAAY,MAAM,aAAa,KAAK;CAC1D,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;AAmCA,SAAS,sBACP,MACA,KACA,UACmB;CACnB,MAAM,eAAyD;EAC7D,MAAM,KAAK;EACX,IAAI,KAAK;EACT,oBAAoB,yBAAyB,GAAG;EAChD,WAAW,UAAU,8BAAa,IAAI,KAAK,EAAA,CAAE,YAAY;CAC3D;CAEA,MAAM,gBAAgB,qBAAqB,cAAc,GAAG;CAC5D,OAAO;EAAE,GAAG;EAAc;CAAc;AAC1C;;;;;;;;;;AAWA,eAAsB,wBACpB,UACA,UAC6B;CAC7B,MAAM,SAAS,SAAS;CACxB,IAAI,CAAC,MAAM,QAAQ,MAAM,GACvB,MAAM,IAAI,MAAM,6BAA6B;CAE/C,MAAM,MAAM,MAAM,QAAQ,IAAI,MAAM;CAEpC,KAAK,IAAI,QAAQ,GAAG,QAAQ,IAAI,QAAQ,SAAS;EAC/C,MAAM,SAAS,kBAAkB,IAAI,MAAM;EAC3C,IAAI,kBAAkB,KAAK,QACzB,MAAM,2BAA2B,OAAO,OAAO,OAAO;CAE1D;CAGA,MAAM,SAAS,oBADU,SAAS,SACO,CAAC;CAC1C,IAAI,kBAAkB,KAAK,QACzB,MAAM,IAAI,MAAM,yCAAyC,OAAO,SAAS;CAG3E,MAAM,WAAW,sBAAsB,QAAQ,KAAK,QAAQ;CAE5D,OAAO;EACL,SAAS,KAAK,UAAU,KAAK,MAAM,CAAC;EACpC;EACA,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC;CAChD;AACF"}