Skip to content

Api Sdk

worable edited this page Oct 12, 2026 · 1 revision

SDK 客户端

@stackpanel/sdk 是类型安全的 API 客户端。它只负责请求、响应校验与错误归一化, 不含业务逻辑。

安装

SDK 发布在 npm(@stackpanel/sdk@0.4.0,publishConfig.access=public,带 provenance):

pnpm add @stackpanel/sdk

在 monorepo 内部则用 workspace 协议:

{
  "dependencies": {
    "@stackpanel/sdk": "workspace:*"
  }
}

SDK 版本与内核契约版本(@stackpanel/spec 的 KERNEL_API_VERSION)联动: .github/workflows/publish-packages.yml 在 sdk-v* 标签时校验两者一致后发布。

创建客户端

import { ApiClient } from '@stackpanel/sdk';

const api = new ApiClient({
  baseUrl: 'http://127.0.0.1:3001',
  timeoutMs: 10_000, // 可选,默认 10000
  token: 'sess_...', // 可选,作为 Authorization: Bearer 发送;也可传 platform API Token
  fetchImpl: fetch, // 可选,注入 fetch(测试用)
});

基础方法

api.get<T>(path: string, schema: z.ZodType<T>): Promise<T>
api.post<T>(path: string, body: unknown, schema: z.ZodType<T>): Promise<T>
api.patch<T>(path: string, body: unknown, schema: z.ZodType<T>): Promise<T>
api.del<T>(path: string, schema: z.ZodType<T>): Promise<T>

每个方法都会用 zod schema 验证响应,校验失败或非 2xx 响应抛出 ApiError。

内置方法

以下是按领域分组的常用方法,方法名与签名以 packages/sdk/src/client.ts 为准。

认证

api.login(email: string, password: string): Promise<LoginResponse>          // POST /login
api.register(email: string, password: string): Promise<LoginResponse>       // POST /register
api.logout(): Promise<void>                                                 // POST /logout
api.getCurrentUser(): Promise<CurrentUserResponse>                          // GET /auth/me
api.getSession(): Promise<SessionInfoResponse>                              // GET /auth/session
api.getConnectedIdentities(): Promise<ConnectedIdentityListResponse>        // GET /auth/identities
api.getOAuthProviders(): Promise<OAuthProviderListResponse>
api.getOAuthAuthorizeUrl(providerId, state, options?): Promise<OAuthAuthorizeResponse>
api.completeOAuthLogin(providerId, code, state, options?): Promise<LoginResponse>

API Token

api.getApiTokens(): Promise<ApiTokenListResponse>                           // GET /me/api-tokens
api.getApiTokenScopes(): Promise<ApiTokenScopesResponse>
api.createApiToken(input): Promise<ApiTokenCreateResponse>                  // 明文仅返回一次
api.updateApiToken(id, input): Promise<ApiTokenUpdateResponse>
api.revokeApiToken(id): Promise<{ ok: boolean }>

健康检查 / 平台信息 / 导航

api.getHealth(): Promise<Health>
api.getReadiness(): Promise<Readiness>
api.getPlatformInfo(): Promise<PlatformInfoResponse>
api.getPlatformBrand(): Promise<PlatformBrandResponse>
api.getAdminPlatformInfo(): Promise<PlatformInfoResponse>
api.updatePlatformInfo(platform): Promise<PlatformInfoResponse>
api.getNav(surface: 'public' | 'account' | 'admin'): Promise<NavListResponse>

主题

api.getThemes(): Promise<ThemesResponse>
api.getActiveTheme(): Promise<ActiveThemeResponse>
api.getThemeFrontend(id: string): Promise<FrontendDescriptor>
api.getThemeSettingsSchema(id: string): Promise<SettingsSchemaResponse>
api.getThemeSettings(id: string): Promise<SettingsResponse>
api.updateThemeSettings(id: string, settings: FrontendSettings): Promise<SettingsResponse>
api.getThemePreview(): Promise<ThemePreviewResponse>
api.setThemePreview(themeId: string): Promise<ThemePreviewMutationResponse>
api.clearThemePreview(): Promise<void>

插件

api.getPluginFrontends(): Promise<PluginFrontendsResponse>
api.getPluginFrontend(id: string): Promise<FrontendDescriptor>
api.getPluginSettingsSchema(id: string): Promise<SettingsSchemaResponse>
api.getPluginSettings(id: string): Promise<SettingsResponse>
api.updatePluginSettings(id: string, settings: FrontendSettings): Promise<SettingsResponse>
api.checkPermission(permission: string): Promise<PermissionCheckResponse>
api.recordFrontendAudit(action, pluginId, resourceId?, meta?): Promise<{ ok: boolean }>

通知

api.getNotifications(options?): Promise<NotificationListResponse>
api.getNotificationUnreadCount(): Promise<NotificationUnreadCountResponse>
api.markNotificationRead(id: string): Promise<void>
api.markAllNotificationsRead(): Promise<NotificationMarkAllResponse>

管理后台

api.getAdminPlugins(): Promise<AdminPluginsResponse>
api.togglePlugin(id, enabled): Promise<PluginToggleResponse>
api.deletePlugin(id: string): Promise<void>
api.getFrontendApplyStatus(): Promise<FrontendApplyStatusResponse>
api.getAdminThemes(): Promise<AdminThemesResponse>
api.activateTheme(id: string): Promise<ThemePatchResponse>
api.deleteTheme(id: string): Promise<void>
api.getAdminDashboardWidgets(): Promise<AdminDashboardWidgetsResponse>
api.getRbacTemplates(): Promise<RbacTemplatesResponse>
api.getAdminActions(): Promise<AdminActionsResponse>
api.getSigningStatus(): Promise<SigningResponse>
api.setSigningPublicKey(publicKey: string): Promise<SigningResponse>
api.getAuditLog(page?, pageSize?, filters?): Promise<AuditLogResponse>
api.getPermissionGroups(): Promise<PermissionGroupListResponse>
api.createPermissionGroup(input): Promise<PermissionGroupMutationResponse>
api.updatePermissionGroup(id, input): Promise<PermissionGroupPatchResponse>
api.deletePermissionGroup(id): Promise<PermissionGroupDeleteResponse>
api.getPermissions(): Promise<PermissionInfoListResponse>

管理用户 / 商店 / 开发者控制台

api.listAdminUsers(options?): Promise<UserListResponse>
api.getAdminUser(id: string): Promise<AdminUserDetailResponse>
api.createAdminUser(input): Promise<AdminUserCreateResponse>
api.updateAdminUser(id, input): Promise<AdminUserMutationResponse>
api.adjustUserWallet(id, input): Promise<WalletAdjustResponse>
api.impersonateUser(id: string): Promise<ImpersonateResponse>
api.getAdminUserServices(id: string): Promise<AdminUserServicesResponse>
api.giftUserService(id, input): Promise<AdminUserServicesResponse>
api.updateAdminUserService(userId, serviceId, input): Promise<AdminServiceMutationResponse>
api.deleteAdminUserService(userId, serviceId): Promise<void>
api.getAdminUserUpstreamServices(id: string): Promise<AdminUserUpstreamServicesResponse>
api.bindAdminUserUpstreamService(id, input): Promise<AdminUserUpstreamServicesResponse>
api.unbindAdminUserUpstreamService(userId, serviceId): Promise<void>
api.getAdminProducts(): Promise<AdminProductsResponse>
api.getStoreProductTypes(): Promise<StoreProductTypeListResponse>
api.getUpstreamSources(): Promise<UpstreamSourceListResponse>
api.searchUpstreamProducts(providerId, query?): Promise<UpstreamProductsResponse>
api.getUpstreamProduct(providerId, productId): Promise<UpstreamProductResponse>
api.getMarketPlugins(): Promise<MarketPluginsResponse>
api.getMarketThemes(): Promise<MarketThemesResponse>
api.installMarketPlugin(id: string): Promise<MarketInstallResponse>
api.installMarketTheme(id: string): Promise<MarketInstallResponse>
api.getDeveloperOverview(): Promise<DeveloperOverviewResponse>
api.listResellers(page?, pageSize?): Promise<ResellerListResponse>
api.getReseller(id: string): Promise<ResellerDetailResponse>
api.createReseller(input): Promise<ResellerKeyResponse>
api.updateReseller(id, input): Promise<ResellerMutationResponse>
api.rotateResellerKey(id: string): Promise<ResellerKeyResponse>
api.deleteReseller(id: string): Promise<ResellerDeleteResponse>
api.listWebhookDeliveries(page?, pageSize?, filters?): Promise<WebhookDeliveryListResponse>
api.listDeveloperCalls(page?, pageSize?, filters?): Promise<AuditLogResponse>

错误处理

import { ApiClient, ApiError } from '@stackpanel/sdk';

try {
  const result = await api.login(email, password);
} catch (err) {
  if (err instanceof ApiError) {
    console.log(err.status); // HTTP 状态码
    console.log(err.code); // problem `code`,或 timeout / network / circuit_open / unknown
    console.log(err.message); // detail 优先,其次 title
    console.log(err.details); // 原始响应体 JSON
    console.log(err.requestId); // problem requestId(如有)
    console.log(err.errors); // 字段级校验错误(如有)
  }
}

ApiError

属性 类型 说明
status number HTTP 状态码(网络错误为 0)
code string problem code;传输层为 timeout / network / circuit_open / unknown
message string detail 优先,其次 title
details unknown 原始响应体 JSON(如有)
requestId string | undefined problem requestId
errors FieldError[] | undefined problem errors[]

韧性传输

ApiClient 接受自定义 fetchImpl,可以把重试、超时与熔断装在传输层,而不改动任何 调用点。apps/web/src/lib/resilience.ts 的 createResilientFetch 是一个现成实现:

  • 超时按单次尝试计算,每次重试使用新的 AbortController;timeoutMs 为 0 时不施加 包装层超时。
  • 重试只覆盖幂等方法(GET / HEAD / OPTIONS / PUT / DELETE);POST 与 PATCH 不会盲目重发。可重试的情形包括网络失败、超时/中止、408、429 与 5xx, 退避为指数加抖动。
  • 熔断器在连续失败达到阈值后打开,冷却期内快速失败并返回 circuit_open,随后进入 半开状态探测,连续成功达到阈值后闭合。时钟、休眠、随机数与 fetch 均可注入,便于 测试。
// apps/web/src/lib/api.ts
import { ApiClient } from '@stackpanel/sdk';
import { createResilientFetch, parseResilienceConfig } from './resilience';

const transport = createResilientFetch({ /* timeoutMs / retry / circuit */ });

export function getApiClient(): ApiClient {
  return new ApiClient({ baseUrl: baseUrl(), timeoutMs: aggregateTimeoutMs, fetchImpl: transport });
}

export async function getAuthedApiClient(): Promise<ApiClient> {
  const token = await getSessionToken();
  return new ApiClient({ baseUrl: baseUrl(), timeoutMs: aggregateTimeoutMs, fetchImpl: transport, ...(token ? { token } : {}) });
}

getAuthedApiClient() 从 Cookie(经 getSessionToken())取会话 token 并创建带 token 的 ApiClient。apps/web/src/proxy.ts 只负责 /admin 的边缘鉴权路由,不参与 API 转发。

在插件中使用

插件后端不使用 ApiClient,也不持有数据库句柄:插件只能经 Extension 引擎读写自己在 defineModel 中声明的模型,事件经 ctx.events 发布。API 客户端用于插件的前端包 (如果有的话)。

Clone this wiki locally