{"id":"82088b5789d59c9c","repo":"prisma/prisma","slug":"pn-contract-typed-fallback-available","errorCode":"PN_CONTRACT_TYPED_FALLBACK_AVAILABLE","errorMessage":"Contract field \"${modelName}.${fieldName}\" uses field.namedType('${fieldState.typeRef}'). Use field.namedType(types.${fieldState.typeRef}) when the storage type is declared in the same contract to keep autocomplete and typed local refs.","messagePattern":"Contract field \"(.+?)\\.(.+?)\" uses field\\.namedType\\('(.+?)'\\)\\. Use field\\.namedType\\(types\\.(.+?)\\) when the storage type is declared in the same contract to keep autocomplete and typed local refs\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/2-sql/2-authoring/contract-ts/src/contract-warnings.ts","lineNumber":114,"sourceCode":"  relation: Extract<RelationState, { kind: 'manyToMany' }>,\n  throughDisplay: string,\n): string {\n  const targetDisplay = formatRelationModelDisplay(relation.toModel);\n  const from = formatFieldSelection(normalizeRelationFieldNames(relation.from));\n  const to = formatFieldSelection(normalizeRelationFieldNames(relation.to));\n  return `rel.manyToMany(${targetDisplay}, { through: ${throughDisplay}, from: ${from}, to: ${to} })`;\n}\n\nconst WARNING_BATCH_THRESHOLD = 5;\n\nfunction flushWarnings(warnings: readonly string[]): void {\n  if (warnings.length === 0) {\n    return;\n  }\n\n  if (warnings.length <= WARNING_BATCH_THRESHOLD) {\n    for (const message of warnings) {\n      process.emitWarning(message, { code: 'PN_CONTRACT_TYPED_FALLBACK_AVAILABLE' });\n    }\n    return;\n  }\n\n  process.emitWarning(\n    `${warnings.length} contract references use string fallbacks where typed alternatives are available. ` +\n      'Use named model tokens and typed storage type refs for autocomplete and type safety.\\n' +\n      warnings.map((w) => `  - ${w}`).join('\\n'),\n    { code: 'PN_CONTRACT_TYPED_FALLBACK_AVAILABLE' },\n  );\n}\n\nfunction formatFallbackWarning(location: string, current: string, suggested: string): string {\n  return (\n    `Contract ${location} uses ${current}. ` +\n    `Use ${suggested} when the named model token is available in the same contract to keep typed relation targets and model refs.`\n  );\n}","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/prisma/prisma/blob/1243cdffbec0506c595ac8fd5865fe258c336fa0/packages/2-sql/2-authoring/contract-ts/src/contract-warnings.ts#L96-L132","documentation":"Emitted (≤5 occurrences, individually) by `emitTypedNamedTypeFallbackWarnings` when a contract field is authored with `field.namedType('MyType')` (string form) but `MyType` is a storage type declared in the same contract and therefore reachable via the typed `types.MyType` token. The string form works at runtime but loses autocomplete and literal-type propagation, so the library nudges toward the typed form. It is a `process.emitWarning` (Node warning), not a thrown error.","triggerScenarios":"Authoring a model field with `field.namedType('Email')` where `Email` was registered into the contract's `storageTypes`. The warning fires once per `modelName.fieldName` pair during `emitTypedNamedTypeFallbackWarnings(models, storageTypes)`.","commonSituations":"Porting hand-written string-based contracts to the typed DSL piecemeal. Copying examples that predate the `types.*` token API. Refactoring a storage type from external to in-contract without updating call sites.","solutions":["Replace `field.namedType('MyType')` with `field.namedType(types.MyType)` wherever the type is declared in the same contract.","If the type is genuinely external (not in `storageTypes`), the string form is correct and the warning can be ignored — but verify the type name matches exactly.","Run the contract emit again to confirm the warning clears for the addressed fields."],"exampleFix":"// before\nmodel.field('email', field.namedType('Email'))\n\n// after\nmodel.field('email', field.namedType(types.Email))","handlingStrategy":"fallback","validationCode":"// Before defining a field, prefer the typed token over the string fallback.\n// `storageTypes` is the same-contract types map; if the name resolves there, pass the token.\nimport type { StorageTypeInstance } from '@prisma-next/sql-contract/types';\n\nfunction safeNamedType(typeRef: string, types: Record<string, StorageTypeInstance>) {\n  // If declared in this contract, return the typed token; otherwise fall back to the string.\n  return typeRef in types ? types[typeRef] : typeRef;\n}\n\n// Usage: field.namedType(safeNamedType('MyType', contractStorageTypes)) instead of field.namedType('MyType').\n// Drive the per-field warning to zero by never passing a bare string when a token exists.","typeGuard":"function hasTypedToken(typeRef: string, types: Record<string, unknown>): typeRef is keyof typeof types & string {\n  return typeRef in types;\n}","tryCatchPattern":"// This is a process warning (process.emitWarning), not a thrown error.\n// Intercept and count it to fail your build instead of letting it pass silently:\nconst fallbackWarnings: string[] = [];\nprocess.on('warning', (w) => {\n  if (w.code === 'PN_CONTRACT_TYPED_FALLBACK_AVAILABLE' && !String(w.message).startsWith('Contract field')) return;\n  if (w.code === 'PN_CONTRACT_TYPED_FALLBACK_AVAILABLE') fallbackWarnings.push(w.message);\n});\n// After contract build:\nif (fallbackWarnings.length > 0) {\n  throw new Error('Per-field typed-fallback warnings detected; switch string refs to typed tokens.');\n}","preventionTips":["Always pass the typed storage token (`types.MyType`) to `field.namedType(...)` when the storage type is declared in the same contract — reserve the string form for cross-contract/external types only.","Keep storage type declarations above or within the same contract build so the typed token is in scope at field-definition time.","Wire a process 'warning' listener in tests/CI that fails on any 'PN_CONTRACT_TYPED_FALLBACK_AVAILABLE' code."],"tags":["contract-ts","typescript","dsl","typed-refs","warnings"],"analyzedSha":"1243cdffbec0506c595ac8fd5865fe258c336fa0","analyzedAt":"2026-08-01T19:42:02.087Z","schemaVersion":2}