feat(portal-shell): add lib/api skeleton with errors and types
M1 Task 1: 创建 4 层数据抽象层的骨架文件 - errors.ts: ApiError + GraphQLErrorCode 枚举 - types.ts: Pagination / PaginatedResult / UseQueryResult / UseMutationResult - internal.ts: normalizeError 把 ApolloError 转为 ApiError - index.ts: barrel 出口
This commit is contained in:
29
apps/portal-shell/src/lib/api/errors.ts
Normal file
29
apps/portal-shell/src/lib/api/errors.ts
Normal file
@@ -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";
|
||||
}
|
||||
}
|
||||
10
apps/portal-shell/src/lib/api/index.ts
Normal file
10
apps/portal-shell/src/lib/api/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* API 层统一出口
|
||||
*
|
||||
* widget 通过 `import { useParentChildren } from "@/lib/api"` 调用。
|
||||
* 各 domain 文件在 M2 阶段逐步加入。
|
||||
*
|
||||
* 关联:spec §2.2
|
||||
*/
|
||||
export * from "./errors";
|
||||
export * from "./types";
|
||||
18
apps/portal-shell/src/lib/api/internal.ts
Normal file
18
apps/portal-shell/src/lib/api/internal.ts
Normal file
@@ -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);
|
||||
}
|
||||
29
apps/portal-shell/src/lib/api/types.ts
Normal file
29
apps/portal-shell/src/lib/api/types.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* API 层共享类型
|
||||
*
|
||||
* 跨 domain 复用的分页、筛选等通用类型。
|
||||
* 领域专属类型(如 ChildSummary)放在各自的 domain 文件。
|
||||
*
|
||||
* 关联:spec §2.2
|
||||
*/
|
||||
export interface Pagination {
|
||||
limit: number;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export interface PaginatedResult<T> {
|
||||
items: T[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface UseQueryResult<TData> {
|
||||
data: TData | undefined;
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
refetch: () => Promise<unknown>;
|
||||
}
|
||||
|
||||
export interface UseMutationResult {
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
}
|
||||
Reference in New Issue
Block a user