{"record":{"id":"d9abd53f3f5245a8","repo":"Budibase/budibase","slug":"err-message-err-rethrown-as-httperror","errorCode":null,"errorMessage":"err?.message || err (rethrown as HTTPError)","messagePattern":"err\\?\\.message \\|\\| err \\(rethrown as HTTPError\\)","errorType":"http","errorClass":"HTTPError","httpStatus":500,"severity":"error","filePath":"packages/server/src/sdk/workspace/tables/external/index.ts","lineNumber":118,"sourceCode":"  }\n  if (!table._id) {\n    throw new Error(\"No table ID supplied\")\n  }\n  return breakExternalTableId(table._id).datasourceId\n}\n\nexport async function create(table: WithoutDocMetadata<Table>) {\n  const datasourceId = getDatasourceId(table)\n\n  const tableToCreate = { ...table, created: true }\n  try {\n    const result = await save(datasourceId!, tableToCreate)\n    return result.table\n  } catch (err: any) {\n    if (err instanceof Error) {\n      throw new HTTPError(err.message, 400)\n    } else {\n      throw new HTTPError(err?.message || err, err.status || 500)\n    }\n  }\n}\n\nexport async function save(\n  datasourceId: string,\n  update: Table,\n  opts?: { tableId?: string; renaming?: RenameColumn }\n) {\n  let tableToSave: TableRequest = {\n    ...update,\n    type: \"table\",\n    _id: buildExternalTableId(datasourceId, update.name),\n    sourceId: datasourceId,\n  }\n\n  const tableId = opts?.tableId || update._id\n  let oldTable: Table | undefined","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/tables/external/index.ts#L100-L136","documentation":"The same create() catch block handles non-Error throwables (or Errors without message): it rethrows HTTPError(err?.message || err, err.status || 500). This branch fires when the thrown value is not an Error instance (e.g. a string thrown by a connector) or when err.message is undefined, defaulting to status 500.","triggerScenarios":"A connector or plugin throws a non-Error value (string/object) during external table save, or an Error subclass lacking a message — caught at the same wrapper and converted to a 500 HTTPError.","commonSituations":"Custom SQL connectors throwing plain strings; undefined err.message from third-party datasource drivers; proxy/fetch failures throwing non-Error objects.","solutions":["Inspect err.message in the resulting HTTPError for the raw thrown value","Fix the connector/driver that throws non-Error values (throw new Error(...) instead)","Check err.status on the HTTPError to distinguish 400 (client) from 500 (internal) responses","Test the datasource connection directly to isolate driver misbehavior"],"exampleFix":"// before (in a connector)\nthrow \"connection timed out\"\n// after\nthrow new Error(\"connection timed out\")","handlingStrategy":"try-catch","validationCode":"// ensure connector calls only throw Error instances\nif (typeof result === \"undefined\") throw new Error(\"Connector returned undefined\")","typeGuard":"function isErrorWithMessage(err: unknown): err is Error {\n  return err instanceof Error && typeof err.message === \"string\"\n}","tryCatchPattern":"try {\n  await tables.external.create(table)\n} catch (err) {\n  const status = err instanceof HTTPError ? err.status : 500\n  const msg = err instanceof Error ? err.message : String(err)\n  console.error(`Table create failed (${status}):`, msg)\n}","preventionTips":["Make custom connectors throw Error objects, not strings","Handle both Error and non-Error throwables in wrappers","Surface err.status to distinguish client vs server faults"],"tags":["http-error","external-datasource","error-wrapping"],"backgroundTag":"non-error-thrown","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}