@djvlc/runtime-client-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +539 -0
- package/dist/index.d.ts +539 -0
- package/dist/index.js +1 -0
- package/package.json +58 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import { RuntimePorts, UserApiAdapter, PageRuntimePort, ResolvePageInput, ResolvePageResult as ResolvePageResult$1, ActionPort, ActionExecuteInput, ActionExecuteResult, DataPort, DataQueryInput, DataQueryResult, TrackPort, TrackInput, TenantPort, ResolveTenantInput, ResolveTenantResult, DjvlcRuntime } from '@djvlc/runtime-core';
|
|
2
|
+
export { ActionExecuteInput, ActionExecuteResult, ActionPort, AuthInfo, CacheStatus, ClientInfo, DataPort, DataQueryInput, DataQueryResult, ExecuteActionParams, ExecuteQueryParams, ExecuteQueryResult, PagePort, PageRuntimePort, ResolvePageInput, ResolvePageMeta, ResolvePageParams, ResolveTenantInput, ResolveTenantResult, RuntimePorts, TenantPort, TraceInfo, TrackInput, TrackPayload, TrackPort, UserApiAdapter } from '@djvlc/runtime-core';
|
|
3
|
+
import { UserClient } from '@djvlc/openapi-user-client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Trace ID 管理
|
|
7
|
+
*
|
|
8
|
+
* resolve/query/action/track 默认使用同一个 traceId(除非调用方显式覆盖)
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 生成 Trace ID(32 位 hex)
|
|
12
|
+
*/
|
|
13
|
+
declare function createTraceId(): string;
|
|
14
|
+
/**
|
|
15
|
+
* 生成 Span ID(16 位 hex)
|
|
16
|
+
*/
|
|
17
|
+
declare function createSpanId(): string;
|
|
18
|
+
/**
|
|
19
|
+
* 生成 traceparent 头(W3C Trace Context)
|
|
20
|
+
*/
|
|
21
|
+
declare function createTraceparent(traceId?: string, spanId?: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Trace 上下文管理器
|
|
24
|
+
*/
|
|
25
|
+
declare class TraceContext {
|
|
26
|
+
private defaultTraceId;
|
|
27
|
+
constructor(initialTraceId?: string);
|
|
28
|
+
/**
|
|
29
|
+
* 获取当前 Trace ID
|
|
30
|
+
*/
|
|
31
|
+
getTraceId(): string;
|
|
32
|
+
/**
|
|
33
|
+
* 设置新的 Trace ID
|
|
34
|
+
*/
|
|
35
|
+
setTraceId(traceId: string): void;
|
|
36
|
+
/**
|
|
37
|
+
* 重置 Trace ID(生成新的)
|
|
38
|
+
*/
|
|
39
|
+
resetTraceId(): string;
|
|
40
|
+
/**
|
|
41
|
+
* 使用指定 Trace ID 执行函数
|
|
42
|
+
*/
|
|
43
|
+
withTrace<T>(traceId: string, fn: () => T): T;
|
|
44
|
+
/**
|
|
45
|
+
* 使用指定 Trace ID 执行异步函数
|
|
46
|
+
*/
|
|
47
|
+
withTraceAsync<T>(traceId: string, fn: () => Promise<T>): Promise<T>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Runtime Client SDK 创建入口
|
|
52
|
+
*
|
|
53
|
+
* 数据面 API 唯一入口:封装 openapi-user-client,实现 Ports,
|
|
54
|
+
* 提供 header/trace/错误归一/幂等/重试/prefetch
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* SDK 配置选项
|
|
59
|
+
*/
|
|
60
|
+
interface RuntimeClientSdkOptions {
|
|
61
|
+
/** API 基础 URL */
|
|
62
|
+
baseUrl: string;
|
|
63
|
+
/** App ID */
|
|
64
|
+
appId: string;
|
|
65
|
+
/** Runtime 版本 */
|
|
66
|
+
runtimeVersion: string;
|
|
67
|
+
/** Client 版本 */
|
|
68
|
+
clientVersion: string;
|
|
69
|
+
/** 获取 Token 的函数(lazy getter) */
|
|
70
|
+
getToken?: () => string | undefined;
|
|
71
|
+
/** 获取 API Key 的函数(lazy getter) */
|
|
72
|
+
getApiKey?: () => string | undefined;
|
|
73
|
+
/** 额外请求头扩展 */
|
|
74
|
+
extraHeaders?: () => Record<string, string>;
|
|
75
|
+
/** 超时时间(毫秒) */
|
|
76
|
+
timeoutMs?: number;
|
|
77
|
+
/** 是否启用重试 */
|
|
78
|
+
retry?: boolean;
|
|
79
|
+
/** 最大重试次数 */
|
|
80
|
+
maxRetries?: number;
|
|
81
|
+
/** 重试延迟(毫秒) */
|
|
82
|
+
retryDelayMs?: number;
|
|
83
|
+
/** 是否启用调试 */
|
|
84
|
+
debug?: boolean;
|
|
85
|
+
/** 默认租户 ID */
|
|
86
|
+
defaultTenantId?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Runtime Client SDK
|
|
90
|
+
*/
|
|
91
|
+
interface RuntimeClientSdk {
|
|
92
|
+
/** 获取 Runtime Ports */
|
|
93
|
+
getPorts(): RuntimePorts;
|
|
94
|
+
/** 获取 UserApiAdapter(向后兼容) */
|
|
95
|
+
getUserApiAdapter(): UserApiAdapter;
|
|
96
|
+
/** 获取 Trace Context */
|
|
97
|
+
getTraceContext(): TraceContext;
|
|
98
|
+
/** 创建新的 Trace ID */
|
|
99
|
+
createTraceId(): string;
|
|
100
|
+
/** 刷新埋点缓冲区 */
|
|
101
|
+
flushTrack(): Promise<void>;
|
|
102
|
+
/** 销毁 SDK */
|
|
103
|
+
destroy(): Promise<void>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 创建 Runtime Client SDK
|
|
107
|
+
*/
|
|
108
|
+
declare function createRuntimeClientSdk(options: RuntimeClientSdkOptions): RuntimeClientSdk;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Header 注入工具
|
|
112
|
+
*
|
|
113
|
+
* 每个请求都带以下 header:
|
|
114
|
+
* - X-App-Id
|
|
115
|
+
* - X-Trace-Id
|
|
116
|
+
* - Authorization: Bearer <token>(可选)
|
|
117
|
+
* - X-API-Key(可选)
|
|
118
|
+
* - X-Runtime-Version
|
|
119
|
+
* - X-Client-Version
|
|
120
|
+
*/
|
|
121
|
+
interface HeadersConfig {
|
|
122
|
+
/** App ID */
|
|
123
|
+
appId: string;
|
|
124
|
+
/** Runtime 版本 */
|
|
125
|
+
runtimeVersion: string;
|
|
126
|
+
/** Client 版本 */
|
|
127
|
+
clientVersion: string;
|
|
128
|
+
/** 获取 Token 的函数(lazy getter) */
|
|
129
|
+
getToken?: () => string | undefined;
|
|
130
|
+
/** 获取 API Key 的函数(lazy getter) */
|
|
131
|
+
getApiKey?: () => string | undefined;
|
|
132
|
+
/** 额外请求头扩展 */
|
|
133
|
+
extraHeaders?: () => Record<string, string>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 创建请求头生成器
|
|
137
|
+
*/
|
|
138
|
+
declare function createHeadersBuilder(config: HeadersConfig): (traceId?: string) => Record<string, string>;
|
|
139
|
+
type HeadersBuilder = ReturnType<typeof createHeadersBuilder>;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 重试策略
|
|
143
|
+
*
|
|
144
|
+
* - 自动重试仅限幂等读请求(resolve/query),action 默认不重试
|
|
145
|
+
* - 重试 2 次以内,带 backoff(例如 200ms/400ms)
|
|
146
|
+
* - 支持配置:timeoutMs、retry、retryDelay
|
|
147
|
+
*/
|
|
148
|
+
interface RetryConfig {
|
|
149
|
+
/** 是否启用重试 */
|
|
150
|
+
enabled: boolean;
|
|
151
|
+
/** 最大重试次数 */
|
|
152
|
+
maxRetries: number;
|
|
153
|
+
/** 初始重试延迟(毫秒) */
|
|
154
|
+
initialDelayMs: number;
|
|
155
|
+
/** 延迟倍数(指数退避) */
|
|
156
|
+
backoffMultiplier: number;
|
|
157
|
+
/** 最大延迟(毫秒) */
|
|
158
|
+
maxDelayMs: number;
|
|
159
|
+
/** 可重试的 HTTP 状态码 */
|
|
160
|
+
retryableStatusCodes: number[];
|
|
161
|
+
}
|
|
162
|
+
declare const DEFAULT_RETRY_CONFIG: RetryConfig;
|
|
163
|
+
/**
|
|
164
|
+
* 计算重试延迟
|
|
165
|
+
*/
|
|
166
|
+
declare function calculateRetryDelay(attempt: number, config: RetryConfig): number;
|
|
167
|
+
/**
|
|
168
|
+
* 判断是否应该重试
|
|
169
|
+
*/
|
|
170
|
+
declare function shouldRetry(error: unknown, attempt: number, config: RetryConfig): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* 延迟函数
|
|
173
|
+
*/
|
|
174
|
+
declare function delay(ms: number): Promise<void>;
|
|
175
|
+
/**
|
|
176
|
+
* 带重试的执行器
|
|
177
|
+
*/
|
|
178
|
+
declare function withRetry<T>(fn: () => Promise<T>, config?: Partial<RetryConfig>): Promise<T>;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* PageRuntimePort 适配器
|
|
182
|
+
*
|
|
183
|
+
* 实现 @djvlc/runtime-core 的 PageRuntimePort 接口
|
|
184
|
+
* 封装 @djvlc/openapi-user-client 的 pages API
|
|
185
|
+
*/
|
|
186
|
+
|
|
187
|
+
interface PageRuntimeAdapterOptions {
|
|
188
|
+
/** User Client */
|
|
189
|
+
client: UserClient;
|
|
190
|
+
/** Headers Builder */
|
|
191
|
+
headersBuilder: HeadersBuilder;
|
|
192
|
+
/** Trace Context */
|
|
193
|
+
traceContext: TraceContext;
|
|
194
|
+
/** 重试配置 */
|
|
195
|
+
retryConfig?: Partial<RetryConfig>;
|
|
196
|
+
/** 默认超时(毫秒) */
|
|
197
|
+
defaultTimeoutMs?: number;
|
|
198
|
+
}
|
|
199
|
+
type ResolvePageResult = ResolvePageResult$1;
|
|
200
|
+
/**
|
|
201
|
+
* PageRuntimePort 适配器实现
|
|
202
|
+
*/
|
|
203
|
+
declare class PageRuntimeAdapter implements PageRuntimePort {
|
|
204
|
+
private client;
|
|
205
|
+
private headersBuilder;
|
|
206
|
+
private traceContext;
|
|
207
|
+
private retryConfig;
|
|
208
|
+
private defaultTimeoutMs;
|
|
209
|
+
constructor(options: PageRuntimeAdapterOptions);
|
|
210
|
+
resolvePage(input: ResolvePageInput): Promise<ResolvePageResult$1>;
|
|
211
|
+
/**
|
|
212
|
+
* 转换 snapshot 到 PageSchema
|
|
213
|
+
*/
|
|
214
|
+
private convertSnapshotToPageSchema;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* 创建 PageRuntimePort 适配器
|
|
218
|
+
*/
|
|
219
|
+
declare function createPageRuntimeAdapter(options: PageRuntimeAdapterOptions): PageRuntimePort;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* ActionPort 适配器
|
|
223
|
+
*
|
|
224
|
+
* 实现 @djvlc/runtime-core 的 ActionPort 接口
|
|
225
|
+
* 封装 @djvlc/openapi-user-client 的 actions API
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
interface ActionAdapterOptions {
|
|
229
|
+
/** User Client */
|
|
230
|
+
client: UserClient;
|
|
231
|
+
/** Headers Builder */
|
|
232
|
+
headersBuilder: HeadersBuilder;
|
|
233
|
+
/** Trace Context */
|
|
234
|
+
traceContext: TraceContext;
|
|
235
|
+
/** 默认超时(毫秒) */
|
|
236
|
+
defaultTimeoutMs?: number;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* ActionPort 适配器实现
|
|
240
|
+
*
|
|
241
|
+
* 注意:Action 默认不重试(非幂等操作)
|
|
242
|
+
*/
|
|
243
|
+
declare class ActionAdapter implements ActionPort {
|
|
244
|
+
private client;
|
|
245
|
+
private traceContext;
|
|
246
|
+
private defaultTimeoutMs;
|
|
247
|
+
constructor(options: ActionAdapterOptions);
|
|
248
|
+
executeAction<T = unknown>(input: ActionExecuteInput): Promise<ActionExecuteResult<T>>;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* 创建 ActionPort 适配器
|
|
252
|
+
*/
|
|
253
|
+
declare function createActionAdapter(options: ActionAdapterOptions): ActionPort;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* DataPort 适配器
|
|
257
|
+
*
|
|
258
|
+
* 实现 @djvlc/runtime-core 的 DataPort 接口
|
|
259
|
+
* 封装 @djvlc/openapi-user-client 的 queries API
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
interface DataAdapterOptions {
|
|
263
|
+
/** User Client */
|
|
264
|
+
client: UserClient;
|
|
265
|
+
/** Headers Builder */
|
|
266
|
+
headersBuilder: HeadersBuilder;
|
|
267
|
+
/** Trace Context */
|
|
268
|
+
traceContext: TraceContext;
|
|
269
|
+
/** 重试配置 */
|
|
270
|
+
retryConfig?: Partial<RetryConfig>;
|
|
271
|
+
/** 默认超时(毫秒) */
|
|
272
|
+
defaultTimeoutMs?: number;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* DataPort 适配器实现
|
|
276
|
+
*/
|
|
277
|
+
declare class DataAdapter implements DataPort {
|
|
278
|
+
private client;
|
|
279
|
+
private traceContext;
|
|
280
|
+
private retryConfig;
|
|
281
|
+
private defaultTimeoutMs;
|
|
282
|
+
constructor(options: DataAdapterOptions);
|
|
283
|
+
query<T = unknown>(input: DataQueryInput): Promise<DataQueryResult<T>>;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 创建 DataPort 适配器
|
|
287
|
+
*/
|
|
288
|
+
declare function createDataAdapter(options: DataAdapterOptions): DataPort;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* TrackPort 适配器
|
|
292
|
+
*
|
|
293
|
+
* 实现 @djvlc/runtime-core 的 TrackPort 接口
|
|
294
|
+
* 封装 @djvlc/openapi-user-client 的 track API
|
|
295
|
+
*/
|
|
296
|
+
|
|
297
|
+
interface TrackAdapterOptions {
|
|
298
|
+
/** User Client */
|
|
299
|
+
client: UserClient;
|
|
300
|
+
/** Headers Builder */
|
|
301
|
+
headersBuilder: HeadersBuilder;
|
|
302
|
+
/** Trace Context */
|
|
303
|
+
traceContext: TraceContext;
|
|
304
|
+
/** 批量上报阈值 */
|
|
305
|
+
batchSize?: number;
|
|
306
|
+
/** 批量上报间隔(毫秒) */
|
|
307
|
+
flushInterval?: number;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* TrackPort 适配器实现
|
|
311
|
+
*/
|
|
312
|
+
declare class TrackAdapter implements TrackPort {
|
|
313
|
+
private client;
|
|
314
|
+
private batchSize;
|
|
315
|
+
private flushInterval;
|
|
316
|
+
private eventBuffer;
|
|
317
|
+
private flushTimer;
|
|
318
|
+
constructor(options: TrackAdapterOptions);
|
|
319
|
+
track(input: TrackInput): Promise<void>;
|
|
320
|
+
/**
|
|
321
|
+
* 立即刷新缓冲区
|
|
322
|
+
*/
|
|
323
|
+
flush(_traceId?: string): Promise<void>;
|
|
324
|
+
/**
|
|
325
|
+
* 调度刷新
|
|
326
|
+
*/
|
|
327
|
+
private scheduleFlush;
|
|
328
|
+
/**
|
|
329
|
+
* 销毁(刷新剩余事件)
|
|
330
|
+
*/
|
|
331
|
+
destroy(): Promise<void>;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* 创建 TrackPort 适配器
|
|
335
|
+
*/
|
|
336
|
+
declare function createTrackAdapter(options: TrackAdapterOptions): TrackPort & {
|
|
337
|
+
flush: () => Promise<void>;
|
|
338
|
+
destroy: () => Promise<void>;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* TenantPort 适配器
|
|
343
|
+
*
|
|
344
|
+
* 实现 @djvlc/runtime-core 的 TenantPort 接口
|
|
345
|
+
* 目前为简单实现,实际可根据需要扩展
|
|
346
|
+
*/
|
|
347
|
+
|
|
348
|
+
interface TenantAdapterOptions {
|
|
349
|
+
/** 默认租户 ID */
|
|
350
|
+
defaultTenantId?: string;
|
|
351
|
+
/** Headers Builder */
|
|
352
|
+
headersBuilder: HeadersBuilder;
|
|
353
|
+
/** Trace Context */
|
|
354
|
+
traceContext: TraceContext;
|
|
355
|
+
/** 租户配置映射 */
|
|
356
|
+
tenantConfigs?: Map<string, unknown>;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* TenantPort 适配器实现
|
|
360
|
+
*
|
|
361
|
+
* 简单实现:根据 host 或 path 解析租户
|
|
362
|
+
*/
|
|
363
|
+
declare class TenantAdapter implements TenantPort {
|
|
364
|
+
private defaultTenantId;
|
|
365
|
+
private tenantConfigs;
|
|
366
|
+
constructor(options: TenantAdapterOptions);
|
|
367
|
+
resolveTenant(input: ResolveTenantInput): Promise<ResolveTenantResult>;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* 创建 TenantPort 适配器
|
|
371
|
+
*/
|
|
372
|
+
declare function createTenantAdapter(options: TenantAdapterOptions): TenantPort;
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* 幂等键策略
|
|
376
|
+
*
|
|
377
|
+
* 对 claim/signin/form_submit 等高频重复点击动作:
|
|
378
|
+
* - 如果调用方没传 idempotencyKey,SDK 自动生成
|
|
379
|
+
* - 必须"稳定":同一用户/页面版本/组件/动作 + 同 payload → 生成同 key
|
|
380
|
+
* - key 不要太长:sha256 后截断 32~48 chars
|
|
381
|
+
*/
|
|
382
|
+
/**
|
|
383
|
+
* 检查是否需要自动生成幂等键
|
|
384
|
+
*/
|
|
385
|
+
declare function needsIdempotencyKey(actionType: string): boolean;
|
|
386
|
+
interface IdempotencyKeyInput {
|
|
387
|
+
/** 动作类型 */
|
|
388
|
+
actionType: string;
|
|
389
|
+
/** 动作参数 */
|
|
390
|
+
payload: unknown;
|
|
391
|
+
/** 上下文 */
|
|
392
|
+
context: {
|
|
393
|
+
pageId: string;
|
|
394
|
+
pageVersionId: string;
|
|
395
|
+
componentId?: string;
|
|
396
|
+
uid?: string;
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* 生成稳定的幂等键
|
|
401
|
+
*
|
|
402
|
+
* 同一用户/页面版本/组件/动作 + 同 payload → 生成同 key
|
|
403
|
+
*/
|
|
404
|
+
declare function generateIdempotencyKey(input: IdempotencyKeyInput): Promise<string>;
|
|
405
|
+
/**
|
|
406
|
+
* 同步版本(使用简单哈希,用于不支持 async 的场景)
|
|
407
|
+
*/
|
|
408
|
+
declare function generateIdempotencyKeySync(input: IdempotencyKeyInput): string;
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* 超时和取消处理
|
|
412
|
+
*
|
|
413
|
+
* resolve/query 支持 timeoutMs + AbortSignal
|
|
414
|
+
*/
|
|
415
|
+
/**
|
|
416
|
+
* 创建超时 AbortController
|
|
417
|
+
*/
|
|
418
|
+
declare function createTimeoutController(timeoutMs?: number, signal?: AbortSignal): {
|
|
419
|
+
controller: AbortController;
|
|
420
|
+
cleanup: () => void;
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* 带超时的 Promise 包装
|
|
424
|
+
*/
|
|
425
|
+
declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, signal?: AbortSignal): Promise<T>;
|
|
426
|
+
/**
|
|
427
|
+
* 检查是否是超时错误
|
|
428
|
+
*/
|
|
429
|
+
declare function isTimeoutError(error: unknown): boolean;
|
|
430
|
+
/**
|
|
431
|
+
* 检查是否是取消错误
|
|
432
|
+
*/
|
|
433
|
+
declare function isAbortError(error: unknown): boolean;
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* 错误归一
|
|
437
|
+
*
|
|
438
|
+
* 无论网络/超时/abort/http/业务错误,统一成 RuntimeClientError
|
|
439
|
+
*/
|
|
440
|
+
/**
|
|
441
|
+
* 错误类型
|
|
442
|
+
*/
|
|
443
|
+
type RuntimeClientErrorKind = 'Network' | 'Timeout' | 'Abort' | 'Http' | 'Biz' | 'Validation' | 'Unknown';
|
|
444
|
+
/**
|
|
445
|
+
* 统一错误类型
|
|
446
|
+
*/
|
|
447
|
+
interface RuntimeClientError {
|
|
448
|
+
name: 'RuntimeClientError';
|
|
449
|
+
kind: RuntimeClientErrorKind;
|
|
450
|
+
message: string;
|
|
451
|
+
code?: string;
|
|
452
|
+
httpStatus?: number;
|
|
453
|
+
traceId?: string;
|
|
454
|
+
details?: unknown;
|
|
455
|
+
cause?: unknown;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* 创建 RuntimeClientError
|
|
459
|
+
*/
|
|
460
|
+
declare function createRuntimeClientError(kind: RuntimeClientErrorKind, message: string, options?: {
|
|
461
|
+
code?: string;
|
|
462
|
+
httpStatus?: number;
|
|
463
|
+
traceId?: string;
|
|
464
|
+
details?: unknown;
|
|
465
|
+
cause?: unknown;
|
|
466
|
+
}): RuntimeClientError;
|
|
467
|
+
/**
|
|
468
|
+
* 判断是否为 RuntimeClientError
|
|
469
|
+
*/
|
|
470
|
+
declare function isRuntimeClientError(error: unknown): error is RuntimeClientError;
|
|
471
|
+
/**
|
|
472
|
+
* 将任意错误转换为 RuntimeClientError
|
|
473
|
+
*/
|
|
474
|
+
declare function normalizeError(error: unknown, traceId?: string): RuntimeClientError;
|
|
475
|
+
/**
|
|
476
|
+
* 创建 HTTP 错误
|
|
477
|
+
*/
|
|
478
|
+
declare function createHttpError(status: number, message: string, traceId?: string, details?: unknown): RuntimeClientError;
|
|
479
|
+
/**
|
|
480
|
+
* 创建业务错误
|
|
481
|
+
*/
|
|
482
|
+
declare function createBizError(code: string, message: string, traceId?: string, details?: unknown): RuntimeClientError;
|
|
483
|
+
/**
|
|
484
|
+
* 创建校验错误
|
|
485
|
+
*/
|
|
486
|
+
declare function createValidationError(message: string, traceId?: string, details?: unknown): RuntimeClientError;
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* prefetchData 注入 helper
|
|
490
|
+
*
|
|
491
|
+
* 如果 resolvePage 返回 prefetchedData,提供 helper 注入到 runtime
|
|
492
|
+
*/
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* 将预取数据注入到 runtime 的 dataStore(query 缓存)
|
|
496
|
+
*/
|
|
497
|
+
declare function applyPrefetch(runtime: DjvlcRuntime, resolved: ResolvePageResult): void;
|
|
498
|
+
/**
|
|
499
|
+
* 创建初始 runtime 状态(包含预取数据)
|
|
500
|
+
*
|
|
501
|
+
* 用于 SSR 或 hydration 场景
|
|
502
|
+
*/
|
|
503
|
+
declare function createInitialRuntimeState(resolved: ResolvePageResult): {
|
|
504
|
+
pageVersionId: string;
|
|
505
|
+
prefetchedData: Record<string, unknown>;
|
|
506
|
+
};
|
|
507
|
+
/**
|
|
508
|
+
* 从预取数据中获取特定查询的缓存
|
|
509
|
+
*/
|
|
510
|
+
declare function getPrefetchedQueryData<T = unknown>(resolved: ResolvePageResult, queryId: string): T | undefined;
|
|
511
|
+
/**
|
|
512
|
+
* 检查是否有预取数据
|
|
513
|
+
*/
|
|
514
|
+
declare function hasPrefetchedData(resolved: ResolvePageResult): boolean;
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* User Client 封装
|
|
518
|
+
*
|
|
519
|
+
* 封装 @djvlc/openapi-user-client 创建 + 默认中间件
|
|
520
|
+
*/
|
|
521
|
+
|
|
522
|
+
interface CreateWrappedClientOptions {
|
|
523
|
+
/** API 基础 URL */
|
|
524
|
+
baseUrl: string;
|
|
525
|
+
/** 超时时间(毫秒) */
|
|
526
|
+
timeoutMs?: number;
|
|
527
|
+
/** 请求头构建器 */
|
|
528
|
+
headersBuilder: HeadersBuilder;
|
|
529
|
+
/** 是否启用调试 */
|
|
530
|
+
debug?: boolean;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* 创建封装的 User Client
|
|
534
|
+
*
|
|
535
|
+
* 自动注入请求头、错误处理等中间件
|
|
536
|
+
*/
|
|
537
|
+
declare function createWrappedUserClient(options: CreateWrappedClientOptions): UserClient;
|
|
538
|
+
|
|
539
|
+
export { ActionAdapter, type ActionAdapterOptions, type CreateWrappedClientOptions, DEFAULT_RETRY_CONFIG, DataAdapter, type DataAdapterOptions, type HeadersBuilder, type HeadersConfig, type IdempotencyKeyInput, PageRuntimeAdapter, type PageRuntimeAdapterOptions, type ResolvePageResult, type RetryConfig, type RuntimeClientError, type RuntimeClientErrorKind, type RuntimeClientSdk, type RuntimeClientSdkOptions, TenantAdapter, type TenantAdapterOptions, TraceContext, TrackAdapter, type TrackAdapterOptions, applyPrefetch, calculateRetryDelay, createActionAdapter, createBizError, createDataAdapter, createHeadersBuilder, createHttpError, createInitialRuntimeState, createPageRuntimeAdapter, createRuntimeClientError, createRuntimeClientSdk, createSpanId, createTenantAdapter, createTimeoutController, createTraceId, createTraceparent, createTrackAdapter, createValidationError, createWrappedUserClient, delay, generateIdempotencyKey, generateIdempotencyKeySync, getPrefetchedQueryData, hasPrefetchedData, isAbortError, isRuntimeClientError, isTimeoutError, needsIdempotencyKey, normalizeError, shouldRetry, withRetry, withTimeout };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createUserClient as e}from"@djvlc/openapi-user-client";function t(e){return function(t){const r={"Content-Type":"application/json","X-App-Id":e.appId,"X-Runtime-Version":e.runtimeVersion,"X-Client-Version":e.clientVersion};if(t&&(r["X-Trace-Id"]=t),e.getToken){const t=e.getToken();t&&(r.Authorization=`Bearer ${t}`)}if(e.getApiKey){const t=e.getApiKey();t&&(r["X-API-Key"]=t)}if(e.extraHeaders){const t=e.extraHeaders();Object.assign(r,t)}return r}}function r(){return e=32,Array.from({length:e},()=>Math.floor(16*Math.random()).toString(16)).join("");var e}function n(){return e=16,Array.from({length:e},()=>Math.floor(16*Math.random()).toString(16)).join("");var e}function a(e,t){return`00-${e||r()}-${t||n()}-01`}var s=class{constructor(e){this.defaultTraceId=e||r()}getTraceId(){return this.defaultTraceId}setTraceId(e){this.defaultTraceId=e}resetTraceId(){return this.defaultTraceId=r(),this.defaultTraceId}withTrace(e,t){const r=this.defaultTraceId;this.defaultTraceId=e;try{return t()}finally{this.defaultTraceId=r}}async withTraceAsync(e,t){const r=this.defaultTraceId;this.defaultTraceId=e;try{return await t()}finally{this.defaultTraceId=r}}},o={enabled:!0,maxRetries:2,initialDelayMs:200,backoffMultiplier:2,maxDelayMs:2e3,retryableStatusCodes:[408,429,500,502,503,504]};function i(e,t){const r=t.initialDelayMs*Math.pow(t.backoffMultiplier,e),n=.1*r*(2*Math.random()-1);return Math.min(r+n,t.maxDelayMs)}function c(e,t,r){if(!r.enabled||t>=r.maxRetries)return!1;if(e instanceof TypeError)return!0;if(e instanceof Error&&"status"in e){const t=e.status;return r.retryableStatusCodes.includes(t)}return!(!e||"object"!=typeof e||!("httpStatus"in e)||"number"!=typeof e.httpStatus)&&r.retryableStatusCodes.includes(e.httpStatus)}function u(e){return new Promise(t=>setTimeout(t,e))}async function d(e,t={}){const r={...o,...t};let n;for(let t=0;t<=r.maxRetries;t++)try{return await e()}catch(e){if(n=e,!c(e,t,r))throw e;if(t<r.maxRetries){const e=i(t,r);await u(e)}}throw n}function l(e,t){const r=new AbortController;let n;return e&&e>0&&(n=setTimeout(()=>{r.abort(new Error(`Request timeout after ${e}ms`))},e)),t&&(t.aborted?r.abort(t.reason):t.addEventListener("abort",()=>{r.abort(t.reason)})),{controller:r,cleanup:()=>{n&&clearTimeout(n)}}}async function f(e,t,r){const{controller:n,cleanup:a}=l(t,r);try{return await Promise.race([e,new Promise((e,t)=>{n.signal.addEventListener("abort",()=>{t(n.signal.reason||new Error("Request aborted"))})})])}finally{a()}}function h(e){return e instanceof Error&&(e.message.includes("timeout")||e.message.includes("Timeout")||"TimeoutError"===e.name||"AbortError"===e.name)}function p(e){return e instanceof Error?"AbortError"===e.name||e.message.includes("aborted"):e instanceof DOMException&&"AbortError"===e.name}function m(e,t,r){return{name:"RuntimeClientError",kind:e,message:t,code:r?.code,httpStatus:r?.httpStatus,traceId:r?.traceId,details:r?.details,cause:r?.cause}}function g(e){return null!==e&&"object"==typeof e&&"name"in e&&"RuntimeClientError"===e.name}function y(e,t){if(g(e))return{...e,traceId:e.traceId||t};if(e instanceof TypeError&&e.message.includes("fetch"))return m("Network","Network request failed",{traceId:t,cause:e});if(e instanceof DOMException&&"AbortError"===e.name)return m("Abort","Request aborted",{traceId:t,cause:e});if(e instanceof Error){if(e.message.includes("timeout")||e.message.includes("Timeout"))return m("Timeout",e.message,{traceId:t,cause:e});if(e.message.includes("abort")||"AbortError"===e.name)return m("Abort",e.message,{traceId:t,cause:e})}if(e&&"object"==typeof e&&"status"in e&&"number"==typeof e.status){const r=e.status;return m("Http",e instanceof Error?e.message:`HTTP error ${r}`,{httpStatus:r,traceId:t,cause:e})}return e instanceof Error?m("Unknown",e.message,{traceId:t,cause:e}):m("Unknown",String(e),{traceId:t,cause:e})}function I(e,t,r,n){return m("Http",t,{httpStatus:e,traceId:r,details:n})}function T(e,t,r,n){return m("Biz",t,{code:e,traceId:r,details:n})}function x(e,t,r){return m("Validation",e,{code:"VALIDATION_ERROR",traceId:t,details:r})}var v=class{constructor(e){this.client=e.client,this.headersBuilder=e.headersBuilder,this.traceContext=e.traceContext,this.retryConfig=e.retryConfig||{},this.defaultTimeoutMs=e.defaultTimeoutMs||3e4}async resolvePage(e){const t=e.trace?.traceId||this.traceContext.getTraceId(),r=e.timeoutMs||this.defaultTimeoutMs,{controller:n,cleanup:a}=l(r,e.signal);try{const r=await d(async()=>await this.client.pages.resolvePage({pageId:e.pageId,env:"prod",uid:e.uid,deviceId:e.client?.deviceId,channel:void 0},{signal:n.signal}),this.retryConfig);if(!r.data||"object"!=typeof r.data)throw x("Invalid page resolve response",t,{response:r});const a=r.data,s=await fetch(a.snapshotUrl,{signal:n.signal,headers:this.headersBuilder(t)});if(!s.ok)throw y({status:s.status},t);const o=await s.json(),i=this.convertSnapshotToPageSchema(o.page);return{pageVersionId:a.resolvedVersionId,page:i,prefetchedData:o.prefetchedData,meta:{cache:"MISS",resolvedAt:Date.now(),etag:a.etag,cacheTtlSeconds:a.cacheTtlSeconds}}}catch(e){throw y(e,t)}finally{a()}}convertSnapshotToPageSchema(e){return e}};function w(e){return new v(e)}var b=new Set(["claim","signin","form_submit","lottery","checkin","reserve","subscribe"]);function M(e){return b.has(e)}function C(e){let t=2166136261;for(let r=0;r<e.length;r++)t^=e.charCodeAt(r),t=Math.imul(t,16777619);return(t>>>0).toString(16).padStart(8,"0")}async function k(e){const t=[e.actionType,e.context.pageId,e.context.pageVersionId,e.context.componentId||"",e.context.uid||"",JSON.stringify(e.payload,Object.keys(e.payload||{}).sort())].join("|");return(await async function(e){if("undefined"!=typeof crypto&&crypto.subtle){const t=(new TextEncoder).encode(e),r=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(r)).map(e=>e.toString(16).padStart(2,"0")).join("")}return C(e)+C(e.split("").reverse().join(""))}(t)).substring(0,32)}function A(e){const t=[e.actionType,e.context.pageId,e.context.pageVersionId,e.context.componentId||"",e.context.uid||"",JSON.stringify(e.payload,Object.keys(e.payload||{}).sort())].join("|"),r=C(t),n=C(t.split("").reverse().join(""));return r+n+C(t+r)+C(n+t)}var V=class{constructor(e){this.client=e.client,this.traceContext=e.traceContext,this.defaultTimeoutMs=e.defaultTimeoutMs||3e4}async executeAction(e){const t=e.trace?.traceId||this.traceContext.getTraceId(),r=e.timeoutMs||this.defaultTimeoutMs;let n=e.idempotencyKey;!n&&M(e.actionType)&&(n=A({actionType:e.actionType,payload:e.payload,context:{pageId:e.context.pageId,pageVersionId:e.context.pageVersionId,componentId:e.context.componentId,uid:e.context.uid}}));const{controller:a,cleanup:s}=l(r,e.signal);try{const t=await this.client.actions.executeAction({executeActionRequest:{actionType:e.actionType,params:e.payload,context:{pageVersionId:e.context.pageVersionId,deviceId:e.context.deviceId,channel:void 0,extra:{uid:e.context.uid,componentId:e.context.componentId}},idempotencyKey:n}},{signal:a.signal});return t.success?{ok:!0,data:t.data?.result}:{ok:!1,error:{code:t.data?.errorCode||"BIZ_ERROR",message:t.data?.errorMessage||"Action failed",details:t.data}}}catch(e){const r=y(e,t);return{ok:!1,error:{code:r.code||r.kind,message:r.message,details:r.details}}}finally{s()}}};function S(e){return new V(e)}var R=class{constructor(e){this.client=e.client,this.traceContext=e.traceContext,this.retryConfig=e.retryConfig||{},this.defaultTimeoutMs=e.defaultTimeoutMs||3e4}async query(e){const t=e.trace?.traceId||this.traceContext.getTraceId(),r=e.timeoutMs||this.defaultTimeoutMs,{controller:n,cleanup:a}=l(r,e.signal);try{const t=await d(async()=>await this.client.queries.queryData({queryDataRequest:{queryVersionId:e.queryVersionId,params:e.params||{},context:{pageVersionId:e.context.pageVersionId}}},{signal:n.signal}),this.retryConfig);return t.success?{ok:!0,data:t.data}:{ok:!1,error:{code:"QUERY_ERROR",message:t.errorMessage||t.message||"Query failed",details:t.data}}}catch(e){const r=y(e,t);return{ok:!1,error:{code:r.code||r.kind,message:r.message,details:r.details}}}finally{a()}}};function E(e){return new R(e)}var D=class{constructor(e){this.eventBuffer=[],this.flushTimer=null,this.client=e.client,this.batchSize=e.batchSize||10,this.flushInterval=e.flushInterval||5e3}async track(e){for(const t of e.events)this.eventBuffer.push({event:t,context:e.context});this.eventBuffer.length>=this.batchSize?await this.flush():this.scheduleFlush()}async flush(e){if(0===this.eventBuffer.length)return;const t=[...this.eventBuffer];this.eventBuffer=[],this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null);for(const{event:e,context:r}of t)try{await this.client.track.track({trackRequest:{eventName:e.eventName,eventType:e.eventType,properties:e.properties,timestamp:e.timestamp?new Date(e.timestamp):void 0,context:{pageVersionId:r.pageVersionId,sessionId:r.sessionId}}})}catch(e){}}scheduleFlush(){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush().catch(()=>{})},this.flushInterval))}async destroy(){await this.flush()}};function q(e){return new D(e)}var j=class{constructor(e){this.defaultTenantId=e.defaultTenantId||"default",this.tenantConfigs=e.tenantConfigs||new Map}async resolveTenant(e){let t=this.defaultTenantId;if(e.path){const r=e.path.match(/^\/t\/([^/]+)/);r&&(t=r[1])}if(e.host){const r=e.host.match(/^([^.]+)\./);r&&"www"!==r[1]&&(t=r[1])}return{tenantId:t,config:this.tenantConfigs.get(t)||{}}}};function B(e){return new j(e)}function P(n){const a=t({appId:n.appId,runtimeVersion:n.runtimeVersion,clientVersion:n.clientVersion,getToken:n.getToken,getApiKey:n.getApiKey,extraHeaders:n.extraHeaders}),o=new s,i=e({baseUrl:n.baseUrl,timeout:n.timeoutMs||3e4,debug:n.debug,headers:a(o.getTraceId()),enableRetry:!1}),c=n.retry?{enabled:!0,maxRetries:n.maxRetries??2,initialDelayMs:n.retryDelayMs??200}:{enabled:!1},u=new v({client:i,headersBuilder:a,traceContext:o,retryConfig:c,defaultTimeoutMs:n.timeoutMs}),d=new V({client:i,headersBuilder:a,traceContext:o,defaultTimeoutMs:n.timeoutMs}),l=new R({client:i,headersBuilder:a,traceContext:o,retryConfig:c,defaultTimeoutMs:n.timeoutMs}),f=new D({client:i,headersBuilder:a,traceContext:o}),h={tenant:new j({headersBuilder:a,traceContext:o,defaultTenantId:n.defaultTenantId}),page:u,data:l,action:d,track:f},p=function(e){return{resolvePage:async t=>(await e.pages.resolvePage({pageId:t.pageId,env:t.env,uid:t.uid,deviceId:t.deviceId,channel:t.channel})).data,async executeAction(t){const r=await e.actions.executeAction({executeActionRequest:{actionType:t.actionType,params:t.params,context:{pageVersionId:t.context.pageVersionId,deviceId:t.context.deviceId,channel:t.context.channel,extra:{uid:t.context.uid,appId:t.context.appId}},idempotencyKey:t.idempotencyKey}}),n=r.data;return{success:r.success,data:n?.result,errorCode:n?.errorCode,errorMessage:n?.errorMessage}},async executeQuery(t){const r=await e.queries.queryData({queryDataRequest:{queryVersionId:t.queryVersionId,params:t.params??{},context:{pageVersionId:t.context.pageVersionId}}});return{success:r.success,data:r.data??void 0,message:r.message,errorMessage:r.errorMessage}},track(t){e.track.track({trackRequest:{eventName:t.eventName,eventType:t.type??"custom",properties:t.params,timestamp:null!=t.timestamp?new Date(t.timestamp):void 0,context:{pageVersionId:t.context.pageVersionId,sessionId:t.context.userId}}}).catch(()=>{})}}}(i);return{getPorts:()=>h,getUserApiAdapter:()=>p,getTraceContext:()=>o,createTraceId:()=>r(),flushTrack:()=>f.flush(),destroy:async()=>{await f.destroy()}}}function O(e,t){t.prefetchedData&&e.setPrefetchedData(t.prefetchedData)}function U(e){return{pageVersionId:e.pageVersionId,prefetchedData:e.prefetchedData||{}}}function K(e,t){return e.prefetchedData?.[t]}function N(e){return void 0!==e.prefetchedData&&Object.keys(e.prefetchedData).length>0}function H(t){const r={baseUrl:t.baseUrl,timeout:t.timeoutMs||3e4,debug:t.debug,headers:t.headersBuilder(),enableRetry:!1};return e(r)}export{V as ActionAdapter,o as DEFAULT_RETRY_CONFIG,R as DataAdapter,v as PageRuntimeAdapter,j as TenantAdapter,s as TraceContext,D as TrackAdapter,O as applyPrefetch,i as calculateRetryDelay,S as createActionAdapter,T as createBizError,E as createDataAdapter,t as createHeadersBuilder,I as createHttpError,U as createInitialRuntimeState,w as createPageRuntimeAdapter,m as createRuntimeClientError,P as createRuntimeClientSdk,n as createSpanId,B as createTenantAdapter,l as createTimeoutController,r as createTraceId,a as createTraceparent,q as createTrackAdapter,x as createValidationError,H as createWrappedUserClient,u as delay,k as generateIdempotencyKey,A as generateIdempotencyKeySync,K as getPrefetchedQueryData,N as hasPrefetchedData,p as isAbortError,g as isRuntimeClientError,h as isTimeoutError,M as needsIdempotencyKey,y as normalizeError,c as shouldRetry,d as withRetry,f as withTimeout};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@djvlc/runtime-client-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "DJV 低代码平台运行时 Client SDK - 数据面 API 唯一入口",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
25
|
+
"lint": "eslint src --ext .ts",
|
|
26
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"clean": "rimraf dist"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@djvlc/runtime-core": "1.1.2"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"@djvlc/contracts-validators": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.10.0",
|
|
40
|
+
"eslint": "^8.55.0",
|
|
41
|
+
"rimraf": "^5.0.5",
|
|
42
|
+
"terser": "^5.44.1",
|
|
43
|
+
"tsup": "^8.0.0",
|
|
44
|
+
"typescript": "^5.3.0",
|
|
45
|
+
"vitest": "^1.0.0"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"lowcode",
|
|
49
|
+
"runtime",
|
|
50
|
+
"sdk",
|
|
51
|
+
"client",
|
|
52
|
+
"djv"
|
|
53
|
+
],
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"license": "MIT"
|
|
58
|
+
}
|