{"record":{"id":"683a5a7f87593d72","repo":"drawdb-io/drawdb","slug":"the-ai-import-returned-no-tables","errorCode":null,"errorMessage":"The AI import returned no tables.","messagePattern":"The AI import returned no tables\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/utils/importAiDiagram.js","lineNumber":60,"sourceCode":"  const declared = declaredNames.get(upper);\n  if (declared) return { type: declared };\n\n  for (const alias of TYPE_ALIASES[upper] ?? []) {\n    if (validTypes[alias]) return { type: alias };\n  }\n\n  const fallback = FALLBACK_TYPES.find((candidate) => validTypes[candidate]);\n  return {\n    type: fallback ?? upper,\n    warning: trimmed\n      ? `Type \"${trimmed}\" is not available for this database, imported as ${fallback}.`\n      : `A column had no type, imported as ${fallback}.`,\n  };\n}\n\nexport function normalizeAiDiagram(raw, database) {\n  if (!raw || !Array.isArray(raw.tables) || !raw.tables.length) {\n    throw new Error(\"The AI import returned no tables.\");\n  }\n\n  const warnings = [];\n  const validTypes = dbToTypes[database] || {};\n  const enums = Array.isArray(raw.enums) ? raw.enums : [];\n  const types = Array.isArray(raw.types) ? raw.types : [];\n\n  const declaredNames = new Map();\n  for (const entry of [...enums, ...types]) {\n    if (entry?.name) declaredNames.set(String(entry.name).toUpperCase(), entry.name);\n  }\n\n  const tables = raw.tables.map((table) => ({\n    ...table,\n    fields: (table.fields ?? []).map((field) => {\n      const { type, warning } = resolveType(field.type, validTypes, declaredNames);\n      if (warning) warnings.push(warning);\n      return { ...field, type };","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/drawdb-io/drawdb/blob/e7086e7fc272b452706e7f402abc69c6b8384452/src/utils/importAiDiagram.js#L42-L78","documentation":"Thrown by normalizeAiDiagram at the top of normalization as a hard precondition: the AI payload must be a non-null object whose `tables` is a non-empty array. It fails fast before any type/relationship resolution so downstream code never iterates an undefined or empty table list. The single source site is the guard `if (!raw || !Array.isArray(raw.tables) || !raw.tables.length)` at src/utils/importAiDiagram.js:59.","triggerScenarios":"Calling `normalizeAiDiagram(raw, database)` where (a) `raw` is null/undefined, (b) `raw.tables` is missing or not an array (e.g. an object, a string, a number), or (c) `raw.tables` is an array of length 0. In the app this is reached via Modal.jsx:203 as `normalizeAiDiagram(result.diagram, database)` after `importSqlWithAi(...)`, so any AI service response whose body lacks a populated `tables` field triggers it.","commonSituations":"The AI endpoint returned 200 but with an empty/malformed JSON body (e.g. `{}` or `{ \"error\": \"...\" }`); the model returned prose or a markdown code fence instead of a table array; an upstream JSON.parse collapsed the payload to null; the model hit a token/quota limit and emitted a truncated object; the import prompt was changed and no longer asks for a `tables` array; a retry returned a cached empty degenerate response.","solutions":["Log `result.diagram` (keys + `tables?.length`) right before the call in Modal.jsx:203 to see exactly what the AI returned.","Add an explicit pre-check in `importSourceWithAi` and surface a user-facing 'AI returned no tables' error without invoking normalizeAiDiagram.","Inspect the AI service call (`importSqlWithAi`) for non-2xx / empty-body / parse-failure paths that silently resolve to an object without `tables`.","Tighten the system prompt sent to the AI to always emit `{ \"tables\": [ ... ], ... }` with at least one table, and validate the parsed JSON contract.","Retry the AI request once on a detected empty payload before reporting a hard failure."],"exampleFix":"// before\nconst { diagram, warnings } = normalizeAiDiagram(result.diagram, database);\n\n// after\nconst raw = result?.diagram;\nif (!raw || !Array.isArray(raw.tables) || raw.tables.length === 0) {\n  setError({ type: STATUS.ERROR, message: \"AI returned no tables. Refine the input and try again.\" });\n  return;\n}\nconst { diagram, warnings } = normalizeAiDiagram(raw, database);","handlingStrategy":"validation","validationCode":"// Run this BEFORE normalizeAiDiagram to guarantee the precondition.\nfunction hasAiTables(raw) {\n  return (\n    !!raw &&\n    typeof raw === \"object\" &&\n    Array.isArray(raw.tables) &&\n    raw.tables.length > 0\n  );\n}\n\n// In importSourceWithAi (Modal.jsx) before line 203:\nconst raw = result?.diagram;\nif (!hasAiTables(raw)) {\n  setError({\n    type: STATUS.ERROR,\n    message: \"The AI service returned no tables. Refine the input and retry.\",\n  });\n  return;\n}\nconst { diagram, warnings } = normalizeAiDiagram(raw, database);","typeGuard":"// JSDoc-narrowed predicate (plain JS; works as a TS guard if you adopt TS).\n/**\n * @param {unknown} raw\n * @returns {raw is { tables: unknown[]; enums?: unknown[]; types?: unknown[]; relationships?: unknown[] }}\n */\nfunction isAiDiagramPayload(raw) {\n  if (!raw || typeof raw !== \"object\") return false;\n  const tables = /** @type {any} */ (raw).tables;\n  return Array.isArray(tables) && tables.length > 0;\n}","tryCatchPattern":"// Keep the existing try/catch in importSourceWithAi, but branch on this message\n// so a missing-tables payload is reported cleanly instead of leaking the generic error.\ntry {\n  const { diagram, warnings } = normalizeAiDiagram(result.diagram, database);\n  // ...applyImportedDiagram(diagram)...\n} catch (e) {\n  if (e?.message === \"The AI import returned no tables.\") {\n    setError({\n      type: STATUS.ERROR,\n      message: \"The AI produced no tables. Adjust the prompt/SQL and try again.\",\n    });\n    return;\n  }\n  throw e; // unknown error — let the outer handler decide\n}","preventionTips":["Always log the shape of `result.diagram` (Object.keys + tables.length) in dev builds before invoking normalizeAiDiagram.","Treat the AI service contract as `{ tables: Array, ... }` and assert it at the boundary in importSqlWithAi, not at the UI.","Retry once on an empty payload before surfacing a hard error — empty bodies are frequently transient.","Pin the AI prompt to require a non-empty `tables` array and include a one-shot example of the minimal object."],"tags":["ai-import","validation","schema","precondition"],"backgroundTag":null,"analyzedSha":"e7086e7fc272b452706e7f402abc69c6b8384452","analyzedAt":"2026-08-13T04:16:27.367Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}