{"version":3,"file":"ref-resolution.mjs","names":[],"sources":["../../src/refs/types.ts","../../src/refs/contract-ref.ts","../../src/refs/migration-ref.ts"],"sourcesContent":["import type { MigrationEdge, MigrationGraph } from '../graph';\nimport type { Refs } from '../refs';\n\n/** Context required to resolve a contract or migration reference. */\nexport interface RefResolutionContext {\n  readonly graph: MigrationGraph;\n  readonly refs: Refs;\n  /**\n   * Hash of the on-disk contract (`contract.json`). Required to resolve the\n   * `@contract` reserved token, which is an offline-resolvable alias for\n   * \"the working contract the app carries.\"\n   */\n  readonly contractHash?: string;\n}\n\nexport type ContractRefProvenance =\n  | { readonly kind: 'hash'; readonly input: string }\n  | { readonly kind: 'ref'; readonly refName: string }\n  | { readonly kind: 'migration-to'; readonly dirName: string }\n  | { readonly kind: 'migration-from'; readonly dirName: string }\n  /**\n   * Resolved from the `@contract` reserved token — the hash of the on-disk\n   * working contract (`contract.json`). Offline-resolvable.\n   */\n  | { readonly kind: 'reserved-contract' }\n  /**\n   * Resolved from the `@db` reserved token — the live database marker.\n   * The `hash` field is a placeholder; callers must resolve the actual hash\n   * via `readAllMarkers()` before using it. Check `provenance.kind ===\n   * 'reserved-db'` to detect this case and perform the DB lookup.\n   */\n  | { readonly kind: 'reserved-db' };\n\n/** A resolved contract reference: the target hash and how it was derived. */\nexport interface ContractRef {\n  readonly hash: string;\n  readonly provenance: ContractRefProvenance;\n}\n\nexport type MigrationRefProvenance =\n  | { readonly kind: 'dir-name'; readonly dirName: string }\n  | { readonly kind: 'hash'; readonly input: string };\n\n/** A resolved migration reference. */\nexport interface MigrationRef {\n  readonly dirName: string;\n  readonly migrationHash: string;\n  readonly from: string;\n  readonly to: string;\n  readonly provenance: MigrationRefProvenance;\n}\n\nexport interface RefResolutionNotFound {\n  readonly kind: 'not-found';\n  readonly input: string;\n  readonly grammar: 'contract' | 'migration';\n}\n\nexport interface RefResolutionAmbiguous {\n  readonly kind: 'ambiguous';\n  readonly input: string;\n  readonly candidates: readonly string[];\n  readonly grammar: 'contract' | 'migration';\n}\n\nexport interface RefResolutionWrongGrammar {\n  readonly kind: 'wrong-grammar';\n  readonly input: string;\n  readonly expectedGrammar: 'contract' | 'migration';\n  readonly message: string;\n  readonly fix: string;\n}\n\nexport interface RefResolutionInvalidFormat {\n  readonly kind: 'invalid-format';\n  readonly input: string;\n  readonly reason: string;\n}\n\nexport type RefResolutionError =\n  | RefResolutionNotFound\n  | RefResolutionAmbiguous\n  | RefResolutionWrongGrammar\n  | RefResolutionInvalidFormat;\n\nconst FULL_HASH_PATTERN = /^sha256:([0-9a-f]{64}|empty)$/;\nconst HEX_PREFIX_PATTERN = /^(sha256:)?[0-9a-f]{6,}$/;\n\nexport function isFullHash(input: string): boolean {\n  return FULL_HASH_PATTERN.test(input);\n}\n\nexport function isHexPrefix(input: string): boolean {\n  return HEX_PREFIX_PATTERN.test(input);\n}\n\nexport function normalizeHashPrefix(input: string): string {\n  return input.startsWith('sha256:') ? input : `sha256:${input}`;\n}\n\nexport function findEdgeByDirName(\n  graph: MigrationGraph,\n  dirName: string,\n): MigrationEdge | undefined {\n  for (const edges of graph.forwardChain.values()) {\n    for (const edge of edges) {\n      if (edge.dirName === dirName) return edge;\n    }\n  }\n  return undefined;\n}\n","import type { Result } from '@prisma-next/utils/result';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport { validateRefName } from '../refs';\nimport type {\n  ContractRef,\n  ContractRefProvenance,\n  RefResolutionContext,\n  RefResolutionError,\n} from './types';\nimport { findEdgeByDirName, isFullHash, isHexPrefix, normalizeHashPrefix } from './types';\n\n/**\n * Resolve a user-supplied string to a contract hash using the unified\n * contract-reference grammar.\n *\n * Accepted forms:\n * - `@contract` — the on-disk working contract hash (offline; requires\n *   `ctx.contractHash` to be set)\n * - `@db` — the live database marker (connection-required); callers MUST\n *   check `result.value.provenance.kind === 'reserved-db'` and resolve the\n *   actual hash via `readAllMarkers()` before using `result.value.hash`\n * - Full storage hash (`sha256:<64 hex>` or `sha256:empty`)\n * - Hex prefix (6+ hex chars, must uniquely identify one contract)\n * - Ref name (looked up in the refs index)\n * - Migration directory name (resolves to the migration's `to`-contract)\n * - `<dir>^` (resolves to the migration's `from`-contract)\n */\nexport function parseContractRef(\n  input: string,\n  ctx: RefResolutionContext,\n): Result<ContractRef, RefResolutionError> {\n  if (!input) {\n    return notOk({ kind: 'invalid-format', input, reason: 'Reference cannot be empty' });\n  }\n\n  if (input === '@contract') {\n    if (ctx.contractHash === undefined) {\n      return notOk({\n        kind: 'not-found',\n        input,\n        grammar: 'contract',\n      });\n    }\n    return ok({ hash: ctx.contractHash, provenance: { kind: 'reserved-contract' } });\n  }\n\n  if (input === '@db') {\n    // The live DB marker is not available offline. Return a sentinel result with\n    // a `reserved-db` provenance; callers must resolve the actual hash via\n    // `readAllMarkers()`. The `hash` placeholder is intentionally empty — it\n    // must NOT be used directly. This is enforced by convention; callers\n    // should check `provenance.kind` before using the hash.\n    return ok({ hash: '', provenance: { kind: 'reserved-db' } });\n  }\n\n  if (isFullHash(input)) {\n    if (ctx.graph.nodes.has(input)) {\n      return ok({ hash: input, provenance: { kind: 'hash', input } });\n    }\n    return notOk({ kind: 'not-found', input, grammar: 'contract' });\n  }\n\n  if (input.endsWith('^')) {\n    const dirName = input.slice(0, -1);\n    if (!dirName) {\n      return notOk({ kind: 'invalid-format', input, reason: 'Missing directory name before ^' });\n    }\n    const edge = findEdgeByDirName(ctx.graph, dirName);\n    if (edge) {\n      return ok({ hash: edge.from, provenance: { kind: 'migration-from', dirName } });\n    }\n    return notOk({ kind: 'not-found', input, grammar: 'contract' });\n  }\n\n  type Candidate = { hash: string; provenance: ContractRefProvenance; label: string };\n  const candidates: Candidate[] = [];\n\n  if (validateRefName(input) && Object.hasOwn(ctx.refs, input)) {\n    const ref = ctx.refs[input];\n    if (ref) {\n      candidates.push({\n        hash: ref.hash,\n        provenance: { kind: 'ref', refName: input },\n        label: `ref \"${input}\"`,\n      });\n    }\n  }\n\n  const edge = findEdgeByDirName(ctx.graph, input);\n  if (edge) {\n    candidates.push({\n      hash: edge.to,\n      provenance: { kind: 'migration-to', dirName: input },\n      label: `migration directory \"${input}\"`,\n    });\n  }\n\n  if (isHexPrefix(input)) {\n    const prefix = normalizeHashPrefix(input);\n    const matches = [...ctx.graph.nodes].filter((n) => n.startsWith(prefix));\n    const [firstMatch] = matches;\n    if (matches.length === 1 && firstMatch !== undefined) {\n      candidates.push({\n        hash: firstMatch,\n        provenance: { kind: 'hash', input },\n        label: `hash prefix \"${input}\"`,\n      });\n    } else if (matches.length > 1) {\n      return notOk({ kind: 'ambiguous', input, candidates: matches, grammar: 'contract' });\n    }\n  }\n\n  const [firstCandidate] = candidates;\n  if (candidates.length === 1 && firstCandidate !== undefined) {\n    return ok({ hash: firstCandidate.hash, provenance: firstCandidate.provenance });\n  }\n\n  if (candidates.length > 1) {\n    return notOk({\n      kind: 'ambiguous',\n      input,\n      candidates: candidates.map((c) => c.label),\n      grammar: 'contract',\n    });\n  }\n\n  return notOk({ kind: 'not-found', input, grammar: 'contract' });\n}\n","import type { Result } from '@prisma-next/utils/result';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport { validateRefName } from '../refs';\nimport type { MigrationRef, RefResolutionContext, RefResolutionError } from './types';\nimport { findEdgeByDirName, isFullHash, isHexPrefix, normalizeHashPrefix } from './types';\n\n/**\n * Resolve a user-supplied string to a migration using the migration-reference\n * grammar.\n *\n * Accepted forms:\n * - Migration directory name (e.g. `20260101-add-users`)\n * - Migration hash (full or 6+ hex prefix)\n *\n * Wrong-grammar diagnostics are produced when the input matches a\n * contract-grammar form (ref name, `<dir>^`, contract-only hash) so the\n * user gets a targeted hint rather than a generic \"not found\".\n */\nexport function parseMigrationRef(\n  input: string,\n  ctx: RefResolutionContext,\n): Result<MigrationRef, RefResolutionError> {\n  if (!input) {\n    return notOk({ kind: 'invalid-format', input, reason: 'Reference cannot be empty' });\n  }\n\n  if (input.endsWith('^')) {\n    return notOk({\n      kind: 'wrong-grammar',\n      input,\n      expectedGrammar: 'migration',\n      message: '`^` syntax addresses contracts, not migrations',\n      fix: 'Pass the migration directory name without `^`, or use a contract-accepting flag like `--to` or `--from`.',\n    });\n  }\n\n  if (validateRefName(input) && Object.hasOwn(ctx.refs, input)) {\n    return notOk({\n      kind: 'wrong-grammar',\n      input,\n      expectedGrammar: 'migration',\n      message: `\"${input}\" is a ref name, not a migration`,\n      fix: 'Refs point at contracts, not migrations. Use a migration directory name or migration hash.',\n    });\n  }\n\n  const edge = findEdgeByDirName(ctx.graph, input);\n  if (edge) {\n    return ok({\n      dirName: edge.dirName,\n      migrationHash: edge.migrationHash,\n      from: edge.from,\n      to: edge.to,\n      provenance: { kind: 'dir-name', dirName: input },\n    });\n  }\n\n  if (isFullHash(input)) {\n    const migEdge = ctx.graph.migrationByHash.get(input);\n    if (migEdge) {\n      return ok({\n        dirName: migEdge.dirName,\n        migrationHash: migEdge.migrationHash,\n        from: migEdge.from,\n        to: migEdge.to,\n        provenance: { kind: 'hash', input },\n      });\n    }\n    if (ctx.graph.nodes.has(input)) {\n      return notOk({\n        kind: 'wrong-grammar',\n        input,\n        expectedGrammar: 'migration',\n        message: 'Hash matched a contract but not a migration',\n        fix: 'Use a contract-accepting flag like `--to` or `--from` to reference contracts by hash. Pass `migration show <dir>` for a specific migration.',\n      });\n    }\n    return notOk({ kind: 'not-found', input, grammar: 'migration' });\n  }\n\n  if (isHexPrefix(input)) {\n    const prefix = normalizeHashPrefix(input);\n    const migMatches = [...ctx.graph.migrationByHash.entries()].filter(([hash]) =>\n      hash.startsWith(prefix),\n    );\n\n    const [firstMigMatch] = migMatches;\n    if (migMatches.length === 1 && firstMigMatch !== undefined) {\n      const [, matchedEdge] = firstMigMatch;\n      return ok({\n        dirName: matchedEdge.dirName,\n        migrationHash: matchedEdge.migrationHash,\n        from: matchedEdge.from,\n        to: matchedEdge.to,\n        provenance: { kind: 'hash', input },\n      });\n    }\n\n    if (migMatches.length > 1) {\n      return notOk({\n        kind: 'ambiguous',\n        input,\n        candidates: migMatches.map(([hash]) => hash),\n        grammar: 'migration',\n      });\n    }\n\n    const contractMatches = [...ctx.graph.nodes].filter((n) => n.startsWith(prefix));\n    if (contractMatches.length > 0) {\n      return notOk({\n        kind: 'wrong-grammar',\n        input,\n        expectedGrammar: 'migration',\n        message: 'Hash matched a contract but not a migration',\n        fix: 'Use a contract-accepting flag like `--to` or `--from` to reference contracts by hash. Pass `migration show <dir>` for a specific migration.',\n      });\n    }\n  }\n\n  return notOk({ kind: 'not-found', input, grammar: 'migration' });\n}\n"],"mappings":"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"}