Repository navigation
Api 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.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); // 字段级校验错误(如有)
}
}| 属性 | 类型 | 说明 |
|---|---|---|
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 客户端用于插件的前端包
(如果有的话)。
核心开发
插件开发
主题开发
前端包开发
RESTful API