首页
/ Supabase Studio 错误处理模式:数据层分类 + ErrorMatcher 排障组件的完整实现

Supabase Studio 错误处理模式:数据层分类 + ErrorMatcher 排障组件的完整实现

2026-09-04 22:05:51作者:胡唯隽

Supabase Studio(apps/studio)针对失败 API 请求采用了一套「数据层分类、展示层只读」的错误处理模式:正则匹配只发生在数据层的 handleError 中,UI 组件 ErrorMatcher 拿到的是已经被分类成具体错误类的对象,直接渲染对应的排障步骤(重启项目、排障文档、AI 调试)。读完本文,你可以掌握该模式的完整调用链、每个关键文件的职责,以及如何按仓库约定为一种新的错误类型接入自动排障能力。

模式总览:分类发生在数据层

这套模式的核心约定是:组件永远不做正则匹配。整个错误流转链路如下:

handleError() → 抛出 ConnectionTimeoutError → React Query 捕获
→ ErrorMatcher 读取错误类实例 → 渲染对应的 troubleshooting 组件
  • 分类由数据层的 handleError(位于 apps/studio/data/fetchers.ts)完成:它把错误消息与 ERROR_PATTERNS 中的正则逐一比对,命中则抛出对应的错误子类(例如 ConnectionTimeoutError extends ResponseError)。
  • 展示层的 ErrorMatcher 只对错误实例做 instanceof 查找,从 ERROR_MAPPINGS 中取出映射,O(1) 级别完成,绝不触碰正则。
  • 同一错误类型在不同页面可以有不同的标题(title 永远由调用方提供),错误映射本身不感知页面上下文。

关键文件与职责

文件 职责
apps/studio/data/error-patterns.ts { pattern, ErrorClass } 数组——所有正则只住在这里
apps/studio/types/api-errors.ts 错误类、KnownErrorType 联合类型、ClassifiedError 类型
apps/studio/components/interfaces/ErrorHandling/ErrorMatcher.tsx 展示组件——查找错误映射并渲染排障步骤
apps/studio/components/interfaces/ErrorHandling/ErrorMatcher.utils.ts getMappingForError():对错误实例做类匹配,返回映射或 null
apps/studio/components/interfaces/ErrorHandling/error-mappings.tsx Map<错误类, { id, Troubleshooting: ComponentType }>——错误类到排障组件的注册表
apps/studio/components/interfaces/ErrorHandling/errorMappings/ConnectionTimeout.tsx 参考级排障组件示例
apps/studio/components/interfaces/ErrorHandling/TroubleshootingSections.tsx 可复用的手风琴步骤组件(重启数据库 / 排障文档 / AI 调试)
apps/studio/components/interfaces/ErrorHandling/TroubleshootingAccordion.tsx 带遥测(telemetry)的手风琴包装器

数据层:handleError 如何分类错误

正则注册表:用 Map 杜绝重复

apps/studio/data/error-patterns.ts 定义了类型与注册表:

type ErrorConstructor = new (
  ...args: ConstructorParameters<typeof ResponseError>
) => ClassifiedError

export interface ErrorPattern {
  pattern: RegExp
  ErrorClass: ErrorConstructor
}

/**
 * 用 Map 保证每个错误类只能出现一次——重复在结构上就不可能,
 * 而不只是靠测试来兜底。
 */
const ERROR_PATTERN_MAP = new Map<ErrorConstructor, RegExp>([
  [ConnectionTimeoutError, /CONNECTION\s+TERMINATED\s+TO\s+CONNECTION\s+TIMEOUT/i],
])

export const ERROR_PATTERNS: ErrorPattern[] = Array.from(ERROR_PATTERN_MAP.entries()).map(
  ([ErrorClass, pattern]) => ({ ErrorClass, pattern })
)

两个值得注意的细节:

  1. Map<ErrorClass, RegExp> 作为内部存储,对外再展开为 ErrorPattern[]。以类为 key 意味着同一个错误类不可能注册两条正则,注释里明确写了这是「by construction(结构性)而非靠测试」的约束。配套测试 apps/studio/data/error-patterns.test.ts 进一步验证了每个模式确实能匹配到样例消息、且一个消息最多命中一个模式。
  2. 正则大小写不敏感且允许空白变体,例如当前的连接超时模式 CONNECTION\s+TERMINATED\s+DUE\s+TO\s+CONNECTION\s+TIMEOUT(带 /i),能覆盖数据库返回消息中空格数量的差异。

错误类:携带完整响应上下文的 ResponseError 子类

apps/studio/types/api-errors.ts 中,每个已知错误类都是 ResponseError 的子类,并带一个 as consterrorType 字面量:

export type KnownErrorType = 'connection-timeout'

export class ConnectionTimeoutError extends ResponseError {
  readonly errorType = 'connection-timeout' as const

  constructor(
    message: string | undefined,
    code?: number,
    requestId?: string,
    retryAfter?: number,
    requestPathname?: string,
    metadata?: ErrorMetadata,
    formattedError?: string
  ) {
    super(message, code, requestId, retryAfter, requestPathname, metadata, formattedError)
  }
}

// 未命中任何模式的 API 错误统一归入此类
export class UnknownAPIResponseError extends ResponseError {
  readonly errorType = 'unknown' as const
  // ...同样的构造参数
}

export type ClassifiedError = ConnectionTimeoutError | UnknownAPIResponseError

从源码结构看,ResponseErrorapps/studio/types/base)统一承载了 HTTP 层的 coderequestIdretryAfterrequestPathnamemetadataformattedError——这些字段正是后续「联系支持」工单能带上完整上下文的基础。

handleError 的匹配与抛出逻辑

apps/studio/data/fetchers.tshandleError 在解析出 errorMessage 后(约 L210-L231):

if (errorMessage) {
  const matched = ERROR_PATTERNS.find(({ pattern }) => pattern.test(errorMessage))
  throw matched
    ? new matched.ErrorClass(
        errorMessage, code, requestId, retryAfter, requestPathname, metadata, formattedError
      )
    : new UnknownAPIResponseError(
        errorMessage, code, requestId, retryAfter, requestPathname, metadata, formattedError
      )
}

要点:

  • 命中模式 → 抛出对应的具体错误类实例;未命中 → 抛出 UnknownAPIResponseError 兜底,保证进入 UI 的 API 错误始终是 ResponseError 的已知子类。
  • new matched.ErrorClass(...) 之前,handleError 会逐个校验 error.codeerror.requestIderror.retryAftererror.requestPathnameerror.metadataerror.formattedError 的类型(L192-L208),确保抛出的实例字段干净、类型正确。

展示层:ErrorMatcher 如何渲染

组件接口与行为

ErrorMatcher.tsx 的 props(比文档示例多一个 fallback):

interface ErrorMatcherProps {
  title: string                          // 显示在错误卡片头部,由调用方提供
  error: string | { message: string }    // 传 React Query 的完整 error 对象,而不是 .message
  supportFormParams?: SupportFormParams  // 支持表单 URL 的强类型参数(projectRef、category 等)
  className?: string
  /** 错误未被分类、没有专属排障步骤时显示的兜底节点 */
  fallback?: ReactNode
}

内部逻辑只有三步:

const message = typeof error === 'string' ? error : error.message
const mapping = getMappingForError(error)
const Troubleshooting = mapping?.Troubleshooting

然后渲染 ui-patterns 包中的 ErrorDisplay(错误卡片 + 支持表单入口),把 Troubleshooting 组件或 fallback 作为子内容传入。

匹配实现:instanceof,而非正则

ErrorMatcher.utils.ts 全文只有几行:

export function getMappingForError(error: unknown): ErrorMapping | null {
  const isResponseError = error instanceof ResponseError
  if (!isResponseError) return null
  for (const [ErrorClass, mapping] of ERROR_MAPPINGS) {
    if (error instanceof ErrorClass) return mapping
  }
  return null
}
  • ResponseError(比如普通的 TypeError)直接返回 null,ErrorMatcher 会走通用错误卡片(或 fallback);
  • ERROR_MAPPINGSerror-mappings.tsx)是 Map<ErrorConstructor, ErrorMapping>,当前只注册了 ConnectionTimeoutError → { id: 'connection-timeout', Troubleshooting: ConnectionTimeoutTroubleshooting } 一条。因为 key 是错误类本身,映射的 id 与 KnownErrorType 一一对应。

内建遥测

ErrorMatcher 与排障组件自带埋点,事件名与触发时机:

事件 触发点 携带信息
dashboard_error_created 错误卡片渲染时(onRender),且通过 isDashboardErrorSampled() 采样 source: 'error_display'errorTypehasTroubleshooting
inline_error_troubleshooter_exposed 命中映射、排障组件曝光时 errorType
inline_error_troubleshooter_step_clicked TroubleshootingAccordion 步骤展开/收起 errorTypestepstepTitleexpanded
inline_error_troubleshooter_action_clicked 点击卡片支持入口 / 重启 / 文档 / AI 调试按钮 errorTypectaTypecontact_supportrestart_dbtroubleshooting_guideask_ai

TroubleshootingAccordion 通过 stepTitles prop(步骤号 → 标题)把标题写进埋点,步骤 id 采用 step-${number} 命名,并支持 defaultExpandedStep 指定默认展开项。

参考实现:ConnectionTimeout 排障组件

errorMappings/ConnectionTimeout.tsx 是仓库中的参考样板,三步排障:

const ERROR_TYPE = 'connection-timeout'

const BUILD_PROMPT = () =>
  `The user is encountering connection timeout errors. The error message is:
   "CONNECTION TERMINATED DUE TO CONNECTION TIMEOUT". What are the most likely
   causes of this issue and how can the user resolve it?`

export function ConnectionTimeoutTroubleshooting() {
  const { openSidebar } = useSidebarManagerSnapshot()
  const aiSnap = useAiAssistantStateSnapshot()

  return (
    <TroubleshootingAccordion
      errorType={ERROR_TYPE}
      stepTitles={{
        1: 'Try restarting your project',
        2: 'Try our troubleshooting guide',
        3: 'Debug with AI',
      }}
    >
      <RestartDatabaseTroubleshootingSection number={1} errorType={ERROR_TYPE} />
      <TroubleshootingGuideSection
        number={2}
        errorType={ERROR_TYPE}
        href={`${DOCS_URL}/guides/troubleshooting/failed-to-run-sql-query-connection-terminated-due-to-connection-timeout`}
        description="Follow step-by-step instructions for diagnosing connection timeout issues."
      />
      <FixWithAITroubleshootingSection
        number={3}
        errorType={ERROR_TYPE}
        onDebugWithAI={(prompt) => {
          openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
          aiSnap.newChat({ initialMessage: prompt })
        }}
        buildPrompt={BUILD_PROMPT}
      />
    </TroubleshootingAccordion>
  )
}

其中两个关键设计:

  1. 重启步骤默认自带对话框TroubleshootingSections.tsx 里的 RestartDatabaseTroubleshootingSection 未提供 onRestartProject 时,内部维护 showDialog 状态并渲染 RestartProjectDialogrestartType="database"),排障组件因此不需要外部接线。
  2. AI 调试走 zustand snapshot。组件内部直接调用 useAiAssistantStateSnapshot()useSidebarManagerSnapshot() 打开 AI 助手侧栏并预填 buildPrompt() 生成的消息,而不是把 onDebugWithAI 之类的回调 prop 暴露给上层调用者。

调用方使用方式

在页面组件中,把 React Query 的完整 error 对象传给 ErrorMatcher

{
  isError && (
    <ErrorMatcher
      title="Failed to load tables"
      error={error}
      supportFormParams={{ projectRef }}
    />
  )
}
  • title 由调用方给出——同一错误类型在不同页面标题不同;
  • supportFormParams 类型为 Partial<SupportFormUrlKeys>,自动补全支持 projectReforgSlugcategorysubjectmessageerrorsid 等字段;支持表单 URL 由 SupportForm.utils.tsxcreateSupportFormUrl() 统一构建。

新增一种错误映射:四步接入

官方流程(完整文档见 apps/studio/components/interfaces/ErrorHandling/README.md),假设新增 your-error 类型:

第 1 步:在 types/api-errors.ts 添加错误类

export type KnownErrorType = 'connection-timeout' | 'your-error'

export class YourError extends ResponseError {
  readonly errorType = 'your-error' as const
}

export type ClassifiedError = ConnectionTimeoutError | FailedToRetrieveProjectsError | YourError

第 2 步:在 data/error-patterns.ts 注册正则

import { YourError } from 'types/api-errors'

// 加入 ERROR_PATTERN_MAP:
[YourError, /YOUR_ERROR_PATTERN/i],

handleError 会自动拾取——此后任何匹配该模式的 API 错误都会以 YourError 实例的形式抛出。

第 3 步:创建 errorMappings/YourError.tsx,参照 ConnectionTimeout 样板:

import { TroubleshootingAccordion } from '../TroubleshootingAccordion'
import {
  FixWithAITroubleshootingSection,
  TroubleshootingGuideSection,
} from '../TroubleshootingSections'
import { DOCS_URL } from '@/lib/constants'

const ERROR_TYPE = 'your-error'
const BUILD_PROMPT = () => `Describe the issue for the AI assistant.`

export function YourErrorTroubleshooting() {
  return (
    <TroubleshootingAccordion
      errorType={ERROR_TYPE}
      stepTitles={{ 1: 'Troubleshooting guide', 2: 'Debug with AI' }}
    >
      <TroubleshootingGuideSection
        number={1}
        errorType={ERROR_TYPE}
        href={`${DOCS_URL}/guides/...`}
      />
      <FixWithAITroubleshootingSection
        number={2}
        errorType={ERROR_TYPE}
        buildPrompt={BUILD_PROMPT}
        // onDebugWithAI: 通过 state/ai-assistant-state  snapshot 打开侧栏并 newChat
      />
    </TroubleshootingAccordion>
  )
}

第 4 步:注册到 error-mappings.tsx

import { YourErrorTroubleshooting } from './errorMappings/YourError'
import { YourError } from '@/types/api-errors'

export const ERROR_MAPPINGS = new Map<ErrorConstructor, ErrorMapping>([
  // ...existing
  [YourError, { id: 'your-error', Troubleshooting: YourErrorTroubleshooting }],
])

至此完成——ErrorMatcher 会自动拾取新映射,无需改动组件本身。

可复用的排障步骤组件

组件 Props
RestartDatabaseTroubleshootingSection numbererrorTypeonRestartProject?(不传则内部弹出重启对话框)
TroubleshootingGuideSection numbererrorTypehreftitle?description?
FixWithAITroubleshootingSection numbererrorTypebuildPromptonDebugWithAI?

三者都是 AccordionItem 形式,value 为 step-${number},与外层 TroubleshootingAccordiontype="single" collapsible 手风琴行为配合,保证任意时刻只有一个步骤展开,且每次展开/收起都会上报埋点。

常见反模式(What NOT to do)

Skill 文档 .claude/skills/studio-error-handling/SKILL.md 明确列出了六条红线,逐条对应到上面的实现约束:

  1. 不要把 error.message 传给 ErrorMatcher——必须传完整 error 对象,否则错误类信息丢失,getMappingForErrorinstanceof 判断必然失败,永远落到通用错误卡片。
  2. 不要在 error-mappings.tsx 里放正则——正则只属于 data/error-patterns.ts,这是「分类在数据层」这一约定的边界。
  3. 不要用 Object.assign 给 error 对象打 errorType 标记——应抛出正确的 ResponseError 子类,让类型系统(ClassifiedError 联合)和 instanceof 匹配自然生效。
  4. 不要用裸 URL 字符串做支持入口——应传 supportFormParams={{ projectRef }},由 createSupportFormUrl() 生成带完整上下文的表单链接。
  5. 不要把页面标题放进错误映射——标题属于 <ErrorMatcher> 调用方,错误映射应保持与页面无关、可跨页面复用。
  6. 不要给排障组件加 onDebugWithAIonRestartProject 等回调 prop(例外是 RestartDatabaseTroubleshootingSection 的可选覆盖)——AI 助手、侧栏等能力应在组件内部通过 hook(useSidebarManagerSnapshotuseAiAssistantStateSnapshot)接线,保持调用方零接线。

小结

这套模式的分工可以概括为三句话:正则只在数据层error-patterns.ts + handleError)、类型即契约KnownErrorType / ClassifiedError / errorType 字面量贯穿三层)、展示层只做查找与渲染ErrorMatcher + 注册表 ERROR_MAPPINGS)。新增一种错误类型只需四处小改动,且每一步都有 TypeScript 类型与 Map 键约束兜底,避免了 UI 代码里散落正则的常见腐化路径。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384