@djvlc/runtime-client-sdk
v1.1.2
Published
DJV 低代码平台运行时 Client SDK - 数据面 API 唯一入口
Readme
@djvlc/runtime-client-sdk
DJV 低代码平台运行时 Client SDK - 数据面 API 唯一入口
功能特性
| 能力 | 说明 |
|------|------|
| Header 注入 | 每个请求自动带 X-App-Id/X-Trace-Id/Authorization/X-Runtime-Version/X-Client-Version |
| TraceId 贯穿 | resolve/query/action/track 默认使用同一个 traceId(除非显式覆盖) |
| 错误归一 | 统一 RuntimeClientError 类型,支持 Network/Timeout/Abort/Http/Biz/Validation/Unknown |
| 幂等键策略 | 自动为 claim/signin/form_submit 等高频动作生成稳定幂等键 |
| prefetchData 注入 | applyPrefetch(runtime, resolved) 将预取数据注入 runtime |
| 重试/超时/取消 | 支持 timeoutMs + AbortSignal,读请求自动重试(可配置) |
安装
pnpm add @djvlc/runtime-client-sdk快速开始
import { createRuntimeClientSdk } from '@djvlc/runtime-client-sdk';
// 创建 SDK
const sdk = createRuntimeClientSdk({
baseUrl: 'https://api.example.com',
appId: 'my-app',
runtimeVersion: '1.0.0',
clientVersion: '1.0.0',
getToken: () => localStorage.getItem('token') || undefined,
retry: true,
maxRetries: 2,
});
// 获取 UserApiAdapter(向后兼容)
const userApiAdapter = sdk.getUserApiAdapter();
// 获取 Ports(新 API)
const ports = sdk.getPorts();
// 解析页面
const result = await ports.page.resolvePage({
pageId: 'page_123',
trace: { traceId: sdk.createTraceId() },
});
// 执行动作
const actionResult = await ports.action.executeAction({
actionType: 'claim',
payload: { activityId: 'act_123' },
context: {
pageId: 'page_123',
pageVersionId: result.pageVersionId,
},
});
// 查询数据
const queryResult = await ports.data.query({
queryVersionId: 'qv_user_info',
params: { uid: 'user_123' },
context: {
pageId: 'page_123',
pageVersionId: result.pageVersionId,
},
});
// 埋点上报
await ports.track.track({
events: [{ eventName: 'page_view', eventType: 'page_view' }],
context: {
pageId: 'page_123',
pageVersionId: result.pageVersionId,
},
});
// 销毁
await sdk.destroy();与 @djvlc/runtime-core 配合使用
import { createRuntime } from '@djvlc/runtime-core';
import { createRuntimeClientSdk, applyPrefetch } from '@djvlc/runtime-client-sdk';
// 创建 SDK
const sdk = createRuntimeClientSdk({
baseUrl: 'https://api.example.com',
appId: 'my-app',
runtimeVersion: '1.0.0',
clientVersion: '1.0.0',
});
// 创建 Runtime(使用 SDK 的 UserApiAdapter)
const runtime = createRuntime({
container: '#app',
pageId: 'page_123',
apiBaseUrl: 'https://api.example.com',
cdnBaseUrl: 'https://cdn.example.com',
userApiAdapter: sdk.getUserApiAdapter(),
});
// 初始化并加载
await runtime.init();
const resolved = await sdk.getPorts().page.resolvePage({ pageId: 'page_123' });
// 应用预取数据
applyPrefetch(runtime, resolved);
// 渲染
await runtime.loadFromResolved(resolved.pageResolveResult!);
await runtime.render();配置选项
interface RuntimeClientSdkOptions {
/** API 基础 URL */
baseUrl: string;
/** App ID */
appId: string;
/** Runtime 版本 */
runtimeVersion: string;
/** Client 版本 */
clientVersion: string;
/** 获取 Token 的函数(lazy getter) */
getToken?: () => string | undefined;
/** 获取 API Key 的函数(lazy getter) */
getApiKey?: () => string | undefined;
/** 额外请求头扩展 */
extraHeaders?: () => Record<string, string>;
/** 超时时间(毫秒),默认 30000 */
timeoutMs?: number;
/** 是否启用重试,默认 false */
retry?: boolean;
/** 最大重试次数,默认 2 */
maxRetries?: number;
/** 重试延迟(毫秒),默认 200 */
retryDelayMs?: number;
/** 是否启用调试 */
debug?: boolean;
/** 默认租户 ID */
defaultTenantId?: string;
}错误处理
所有错误都会被归一为 RuntimeClientError:
interface RuntimeClientError {
name: 'RuntimeClientError';
kind: 'Network' | 'Timeout' | 'Abort' | 'Http' | 'Biz' | 'Validation' | 'Unknown';
message: string;
code?: string;
httpStatus?: number;
traceId?: string;
details?: unknown;
cause?: unknown;
}使用方式:
import { isRuntimeClientError, normalizeError } from '@djvlc/runtime-client-sdk';
try {
const result = await ports.action.executeAction({ ... });
if (!result.ok) {
console.error('Action failed:', result.error);
}
} catch (error) {
if (isRuntimeClientError(error)) {
switch (error.kind) {
case 'Network':
// 网络错误
break;
case 'Timeout':
// 超时
break;
case 'Http':
// HTTP 错误
console.error('HTTP', error.httpStatus, error.message);
break;
case 'Biz':
// 业务错误
console.error('Business error:', error.code);
break;
}
}
}幂等键
对于 claim/signin/form_submit/lottery 等动作,SDK 会自动生成稳定的幂等键:
- 同一用户 + 同一页面版本 + 同一组件 + 同一动作 + 同一参数 → 生成相同的 key
- key 长度:32 字符(SHA-256 截断)
import { needsIdempotencyKey, generateIdempotencyKey } from '@djvlc/runtime-client-sdk';
// 检查是否需要幂等键
needsIdempotencyKey('claim'); // true
needsIdempotencyKey('custom'); // false
// 手动生成
const key = await generateIdempotencyKey({
actionType: 'claim',
payload: { activityId: 'act_123' },
context: {
pageId: 'page_1',
pageVersionId: 'pv_1',
componentId: 'btn_1',
uid: 'user_1',
},
});Trace ID 管理
import { TraceContext, createTraceId } from '@djvlc/runtime-client-sdk';
// 创建 Trace Context
const ctx = new TraceContext();
// 获取当前 Trace ID
const traceId = ctx.getTraceId();
// 使用临时 Trace ID 执行操作
await ctx.withTraceAsync('custom-trace-id', async () => {
// 这里的所有操作都使用 custom-trace-id
await someOperation();
});
// 操作完成后恢复原来的 Trace IDAPI
SDK Methods
| 方法 | 说明 |
|------|------|
| getPorts() | 获取 RuntimePorts 对象 |
| getUserApiAdapter() | 获取向后兼容的 UserApiAdapter |
| getTraceContext() | 获取 TraceContext 实例 |
| createTraceId() | 创建新的 Trace ID |
| flushTrack() | 立即刷新埋点缓冲区 |
| destroy() | 销毁 SDK(刷新剩余埋点) |
Port Interfaces
| Port | 方法 | 说明 |
|------|------|------|
| PageRuntimePort | resolvePage(input) | 解析页面 |
| ActionPort | executeAction(input) | 执行动作 |
| DataPort | query(input) | 查询数据 |
| TrackPort | track(input) | 埋点上报 |
| TenantPort | resolveTenant(input) | 解析租户 |
依赖关系
- 运行时依赖:
@djvlc/runtime-core - peer 依赖:
@djvlc/contracts-types- 类型定义@djvlc/openapi-user-client- User API 客户端@djvlc/contracts-validators(可选) - 响应校验
许可证
MIT
