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; +}