From 989603e318e435595b41da24871246498a39885e Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:06:01 +0800 Subject: [PATCH] feat(portal-shell): add lib/api skeleton with errors and types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M1 Task 1: 创建 4 层数据抽象层的骨架文件 - errors.ts: ApiError + GraphQLErrorCode 枚举 - types.ts: Pagination / PaginatedResult / UseQueryResult / UseMutationResult - internal.ts: normalizeError 把 ApolloError 转为 ApiError - index.ts: barrel 出口 --- apps/portal-shell/src/lib/api/errors.ts | 29 +++++++++++++++++++++++ apps/portal-shell/src/lib/api/index.ts | 10 ++++++++ apps/portal-shell/src/lib/api/internal.ts | 18 ++++++++++++++ apps/portal-shell/src/lib/api/types.ts | 29 +++++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 apps/portal-shell/src/lib/api/errors.ts create mode 100644 apps/portal-shell/src/lib/api/index.ts create mode 100644 apps/portal-shell/src/lib/api/internal.ts create mode 100644 apps/portal-shell/src/lib/api/types.ts diff --git a/apps/portal-shell/src/lib/api/errors.ts b/apps/portal-shell/src/lib/api/errors.ts new file mode 100644 index 0000000..ce995d1 --- /dev/null +++ b/apps/portal-shell/src/lib/api/errors.ts @@ -0,0 +1,29 @@ +/** + * API 层统一错误类型 + * + * 所有 lib/api/ 函数抛出的错误都用 ApiError 包装, + * 便于 widget 层按 code 分类处理(401 跳登录、403 显示无权限等)。 + * + * 关联:spec §6.1 + */ +export type GraphQLErrorCode = + | "UNAUTHORIZED" + | "FORBIDDEN" + | "NOT_FOUND" + | "VALIDATION_ERROR" + | "QUERY_DEPTH_EXCEEDED" + | "QUERY_COMPLEXITY_EXCEEDED" + | "PERSISTED_QUERY_NOT_FOUND" + | "INTERNAL_ERROR"; + +export class ApiError extends Error { + constructor( + message: string, + public readonly code: GraphQLErrorCode, + public readonly statusCode: number = 500, + public readonly fields?: string[], + ) { + super(message); + this.name = "ApiError"; + } +} diff --git a/apps/portal-shell/src/lib/api/index.ts b/apps/portal-shell/src/lib/api/index.ts new file mode 100644 index 0000000..5aa48fe --- /dev/null +++ b/apps/portal-shell/src/lib/api/index.ts @@ -0,0 +1,10 @@ +/** + * API 层统一出口 + * + * widget 通过 `import { useParentChildren } from "@/lib/api"` 调用。 + * 各 domain 文件在 M2 阶段逐步加入。 + * + * 关联:spec §2.2 + */ +export * from "./errors"; +export * from "./types"; diff --git a/apps/portal-shell/src/lib/api/internal.ts b/apps/portal-shell/src/lib/api/internal.ts new file mode 100644 index 0000000..86d4ed9 --- /dev/null +++ b/apps/portal-shell/src/lib/api/internal.ts @@ -0,0 +1,18 @@ +/** + * API 层共享工具 + * + * 错误归一化:把 ApolloError 转为 ApiError, + * 让 widget 层可以用 instanceof ApiError 判断。 + * + * 关联:spec §6.2 + */ +import type { ApolloError } from "@apollo/client"; +import { ApiError, type GraphQLErrorCode } from "./errors"; + +export function normalizeError(error: ApolloError): ApiError { + const gqlError = error.graphQLErrors?.[0]; + const code = (gqlError?.extensions?.code ?? + "INTERNAL_ERROR") as GraphQLErrorCode; + const statusCode = (gqlError?.extensions?.statusCode ?? 500) as number; + return new ApiError(gqlError?.message ?? error.message, code, statusCode); +} diff --git a/apps/portal-shell/src/lib/api/types.ts b/apps/portal-shell/src/lib/api/types.ts new file mode 100644 index 0000000..c373c7f --- /dev/null +++ b/apps/portal-shell/src/lib/api/types.ts @@ -0,0 +1,29 @@ +/** + * API 层共享类型 + * + * 跨 domain 复用的分页、筛选等通用类型。 + * 领域专属类型(如 ChildSummary)放在各自的 domain 文件。 + * + * 关联:spec §2.2 + */ +export interface Pagination { + limit: number; + offset: number; +} + +export interface PaginatedResult { + items: T[]; + total: number; +} + +export interface UseQueryResult { + data: TData | undefined; + loading: boolean; + error: unknown; + refetch: () => Promise; +} + +export interface UseMutationResult { + loading: boolean; + error: unknown; +}