/** * GroupResolver 的 HTTP 实现(契约 C2,Group 团队服务到位后启用, * 经 HUB_GROUP_SERVICE_URL 配置)。 * * 语义红线: * - 失败 → FileLibError(503, group_unavailable)。依赖故障不是"无权限", * 绝不伪装成 404/403(计划 D13)。 * - 我方绝不自己推祖先:返回什么用什么,不在本地补逻辑。 */ import { FileLibError } from "./model.js"; import type { GroupResolver } from "./groupResolver.js"; export interface HttpGroupResolverConfig { readonly baseUrl: string; readonly timeoutMs?: number; /** 测试可注入假 fetch;生产用全局 fetch。 */ readonly fetchFn?: typeof fetch; } export function createHttpGroupResolver(config: HttpGroupResolverConfig): GroupResolver { const timeoutMs = config.timeoutMs ?? 2_000; const fetchFn = config.fetchFn ?? fetch; return { async resolveMemberGroupIds(userId) { const url = `${config.baseUrl.replace(/\/$/, "")}/groups/resolve-member-groups?userId=${encodeURIComponent(userId)}`; let response: Response; try { response = await fetchFn(url, { signal: AbortSignal.timeout(timeoutMs) }); } catch (error) { throw new FileLibError(503, "group_unavailable", `group service unreachable: ${String(error)}`); } if (!response.ok) { throw new FileLibError(503, "group_unavailable", `group service returned ${response.status}`); } let body: unknown; try { body = await response.json(); } catch { throw new FileLibError(503, "group_unavailable", "group service returned malformed JSON"); } const groupIds = (body as { groupIds?: unknown }).groupIds; if (!Array.isArray(groupIds) || groupIds.some((id) => typeof id !== "string")) { throw new FileLibError(503, "group_unavailable", "group service returned malformed payload"); } return groupIds as readonly string[]; }, }; }