prisma/prisma · warning

PN_CONTRACT_TYPED_FALLBACK_AVAILABLE

PN_CONTRACT_TYPED_FALLBACK_AVAILABLE

Error message

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.

What it means

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.

Source

Thrown at packages/2-sql/2-authoring/contract-ts/src/contract-warnings.ts:114

  relation: Extract<RelationState, { kind: 'manyToMany' }>,
  throughDisplay: string,
): string {
  const targetDisplay = formatRelationModelDisplay(relation.toModel);
  const from = formatFieldSelection(normalizeRelationFieldNames(relation.from));
  const to = formatFieldSelection(normalizeRelationFieldNames(relation.to));
  return `rel.manyToMany(${targetDisplay}, { through: ${throughDisplay}, from: ${from}, to: ${to} })`;
}

const WARNING_BATCH_THRESHOLD = 5;

function flushWarnings(warnings: readonly string[]): void {
  if (warnings.length === 0) {
    return;
  }

  if (warnings.length <= WARNING_BATCH_THRESHOLD) {
    for (const message of warnings) {
      process.emitWarning(message, { code: 'PN_CONTRACT_TYPED_FALLBACK_AVAILABLE' });
    }
    return;
  }

  process.emitWarning(
    `${warnings.length} contract references use string fallbacks where typed alternatives are available. ` +
      'Use named model tokens and typed storage type refs for autocomplete and type safety.\n' +
      warnings.map((w) => `  - ${w}`).join('\n'),
    { code: 'PN_CONTRACT_TYPED_FALLBACK_AVAILABLE' },
  );
}

function formatFallbackWarning(location: string, current: string, suggested: string): string {
  return (
    `Contract ${location} uses ${current}. ` +
    `Use ${suggested} when the named model token is available in the same contract to keep typed relation targets and model refs.`
  );
}

View on GitHub (pinned to 1243cdffbe)

Solutions

  1. Replace `field.namedType('MyType')` with `field.namedType(types.MyType)` wherever the type is declared in the same contract.
  2. 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.
  3. Run the contract emit again to confirm the warning clears for the addressed fields.

Example fix

// before
model.field('email', field.namedType('Email'))

// after
model.field('email', field.namedType(types.Email))
Defensive patterns

Strategy: fallback

Validate before calling

// Before defining a field, prefer the typed token over the string fallback.
// `storageTypes` is the same-contract types map; if the name resolves there, pass the token.
import type { StorageTypeInstance } from '@prisma-next/sql-contract/types';

function safeNamedType(typeRef: string, types: Record<string, StorageTypeInstance>) {
  // If declared in this contract, return the typed token; otherwise fall back to the string.
  return typeRef in types ? types[typeRef] : typeRef;
}

// Usage: field.namedType(safeNamedType('MyType', contractStorageTypes)) instead of field.namedType('MyType').
// Drive the per-field warning to zero by never passing a bare string when a token exists.

Type guard

function hasTypedToken(typeRef: string, types: Record<string, unknown>): typeRef is keyof typeof types & string {
  return typeRef in types;
}

Try / catch

// This is a process warning (process.emitWarning), not a thrown error.
// Intercept and count it to fail your build instead of letting it pass silently:
const fallbackWarnings: string[] = [];
process.on('warning', (w) => {
  if (w.code === 'PN_CONTRACT_TYPED_FALLBACK_AVAILABLE' && !String(w.message).startsWith('Contract field')) return;
  if (w.code === 'PN_CONTRACT_TYPED_FALLBACK_AVAILABLE') fallbackWarnings.push(w.message);
});
// After contract build:
if (fallbackWarnings.length > 0) {
  throw new Error('Per-field typed-fallback warnings detected; switch string refs to typed tokens.');
}

Prevention

When it happens

Trigger: 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)`.

Common situations: 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.

Related errors


AI-assisted analysis of prisma/prisma@1243cdffbe (2026-08-01). Data as JSON: /data/errors/82088b5789d59c9c.json. Report an issue: GitHub.