@goplus123/core-api 1.0.3 → 1.0.4

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 CHANGED
@@ -7,7 +7,158 @@
7
7
  - WS 推送订阅与本地事件分发:`notify(type)`
8
8
  - 统一错误归一化与中间件:`apiError.use(...)`
9
9
 
10
- 入口文件:[`src/index.ts`](file:///d:/working/GX/tsup/packages/api/src/index.ts)
10
+ 入口文件:[`src/index.ts`](./src/index.ts)
11
+
12
+ ---
13
+
14
+ ## 设计方案与思路
15
+
16
+ ### 这是什么
17
+
18
+ `core-api` 的定位是“前端/Node 侧统一 API SDK 装配点(bootstrapper)”:
19
+
20
+ - 对外只暴露少量稳定入口:`initSDK / requestApi / notify / apiError / fetchGuid`
21
+ - 对内把 HTTP / WS / gRPC 三套通信能力收敛到一个统一调用模型(EndpointSpec + UnifiedApiClient)
22
+ - 让业务侧用“service + functionName”或“类型安全的 service client”去调用,而不是直接耦合具体传输协议
23
+
24
+ ### 为什么要这样设计(问题拆解)
25
+
26
+ 这个 SDK 解决的核心矛盾是:同一套业务接口在不同环境、不同阶段可能走不同 transport,同时又希望具备统一的鉴权/日志/错误处理/调试能力。
27
+
28
+ 设计上做了三层解耦:
29
+
30
+ - “接口长什么样”(spec 层:EndpointSpec/EndpointGroup,见 [`src/spec/endpoint.ts`](./src/spec/endpoint.ts))
31
+ - “怎么路由到哪个 transport”(routing 层:registry 构建 + pickEndpoint + fallback,见 [`src/routing/router.ts`](./src/routing/router.ts))
32
+ - “具体怎么发请求”(client 层:HttpClient/WsClient/GrpcClient + UnifiedApiClient,见 [`src/client/*`](./src/client/))
33
+
34
+ 这样做带来的直接收益:
35
+
36
+ - 业务调用方式稳定:接口迁移 transport(例如 ws → grpc)时,调用方不需要全量改造
37
+ - 横切能力可复用:鉴权、日志、错误归一化、灰度、重试等可以在统一链路上实现一次
38
+ - 便于渐进式改造:spec 驱动与 legacy modules 并存,允许逐步把旧调用迁移进 spec
39
+
40
+ ### 关键设计原则(对后续重构/AI 重设计很重要)
41
+
42
+ - 单一装配点:只允许通过 `initSDK()` 构建/覆盖全局状态,避免“到处 new client”导致的状态分裂
43
+ - 显式路由表:所有“能被 requestApi 路由”的能力必须可在 spec 中静态看到(便于审计、自动化生成、AI 重构)
44
+ - transport 无关的调用链:上层只关心 EndpointSpec 与请求参数;transport 细节由 UnifiedApiClient 分发
45
+ - 横切能力可插拔:拦截器链(interceptors)是统一入口,避免散落在各 transport 内部
46
+ - 向后兼容优先:spec 未命中时允许落到 legacy serviceInstances 或 raw WS 兜底(便于迁移期稳定)
47
+
48
+ ---
49
+
50
+ ## 架构分层与职责
51
+
52
+ ### 1) SDK 装配层(initSDK)
53
+
54
+ 入口:[`initSDK`](./src/index.ts#L372-L579)
55
+
56
+ `initSDK` 做的事情可以概括为“装配 + 建联 + 暴露能力”:
57
+
58
+ - 创建三种 transport client(HTTP/WS/gRPC):[`RequestApi`](./src/client/requestApi.ts)
59
+ - 构建路由 registry(从 `apiMap`):[`rebuildEndpointRegistry`](./src/routing/router.ts#L45-L84)
60
+ - 初始化 UnifiedApiClient,并注入 interceptors:[`UnifiedApiClient`](./src/client/unifiedApiClient.ts#L139-L274)
61
+ - 初始化 WS(仅在这里 connect):避免重复建连与多实例订阅
62
+ - 暴露两类 client:
63
+ - spec 驱动的 service clients:`sdk.cms/sdk.admin/sdk.platform`(由 [`createApiServiceClient`](./src/index.ts#L592-L683) 生成)
64
+ - legacy modules:`sdk.auth/sdk.player/...`(见 [`src/modules/*`](./src/modules/))
65
+
66
+ ### 2) Spec 层(接口描述与路由表)
67
+
68
+ spec 的职责是提供“可路由的接口清单”,而不是提供具体实现。
69
+
70
+ - EndpointSpec:一个接口在某一种 transport 下的“可调用描述”
71
+ - EndpointGroup:同一业务接口的多 transport 变体(可指定 defaultTransport)
72
+ - apiMap:按 service 聚合 endpoints,用于构建 registry:[`src/spec/index.ts`](./src/spec/index.ts)
73
+
74
+ 这层的价值在于:它是静态、可读、可生成的“接口真相来源”(source of truth)。
75
+
76
+ ### 3) Routing 层(requestApi 路由与兜底)
77
+
78
+ 路由入口:[`requestApi`](./src/routing/router.ts#L203-L263)
79
+
80
+ 核心逻辑:
81
+
82
+ 1. 组装 key:`<service>:<functionName>`
83
+ 2. 查 registry(由 `apiMap` 构建)
84
+ 3. 根据 `args.transport / specDefaultTransport / initSDK.defaultTransport` 选出 endpoint([`pickEndpoint`](./src/routing/router.ts#L267-L296))
85
+ 4. 交给 `unifiedClient.call(endpoint, requestParam, meta)` 分发到具体 transport
86
+ 5. 若 spec 未命中:
87
+ - 尝试 legacy `serviceInstances[service][functionName]`
88
+ - 再兜底到 raw WS:`rpcClient.call(functionName, service, payload, wsOptions)`
89
+
90
+ ### 4) Client 层(具体 transport + 统一分发)
91
+
92
+ - HttpClient:基于 `fetch`,提供 baseURL 拼接、timeout、header/token 注入、错误结构化:[`src/client/httpClient.ts`](./src/client/httpClient.ts)
93
+ - WsClient:管理连接/心跳/重连/请求响应匹配/notify 分发:[`src/client/wsClient.ts`](./src/client/wsClient.ts)
94
+ - WsRpcClient:在 WsClient 基础上做“业务 envelope 解包 + 错误归一化”:[`src/client/wsRpcClient.ts`](./src/client/wsRpcClient.ts)
95
+ - GrpcClient:基于 ConnectRPC 的 web transport,统一注入 headers/token:[`src/client/grpcClient.ts`](./src/client/grpcClient.ts)
96
+ - UnifiedApiClient:把 EndpointSpec + requestParam 统一分发到 http/ws/grpc,并在入口统一承接 interceptors:[`src/client/unifiedApiClient.ts`](./src/client/unifiedApiClient.ts)
97
+
98
+ ---
99
+
100
+ ## 横切能力(鉴权 / 日志 / 错误)
101
+
102
+ ### 拦截器模型(Interceptors)
103
+
104
+ 定义与组合见:[`src/client/interceptors.ts`](./src/client/interceptors.ts)
105
+
106
+ - `ApiCallContext` 是拦截器的“单次调用上下文”,包含:endpoint / request / headers / meta
107
+ - `createAuthInterceptor`:把 `auth.getToken()` 注入为 `Authorization: Bearer <token>`
108
+ - `createLogInterceptor`:围绕一次 endpoint 调用输出 request/response/biz_error/transport_error
109
+
110
+ 拦截器的设计意图是把“与业务接口无关的能力”统一实现一次,三种 transport 复用同一条链路。
111
+
112
+ ### token/header 注入策略
113
+
114
+ `initSDK({ auth })` 提供的 `getToken/getHeaders` 会被注入到:
115
+
116
+ - HTTP:自动补 `Authorization` 与公共头(调用方显式传入同名 header 时不覆盖)
117
+ - gRPC:统一加 `Authorization: Bearer ...`,并合并 callOptions.headers
118
+ - WS:浏览器限制无法加自定义 header,因此在 WS 层通过 URL/query 或消息体(headerConfig)携带
119
+
120
+ 对应实现入口:
121
+
122
+ - HTTP token/header:[`attachToken`](./src/client/httpClient.ts#L107-L134)
123
+ - gRPC token/header:[`createAuthInterceptor`](./src/client/grpcClient.ts#L203-L229)
124
+ - WS headerConfig:[`WsClient`](./src/client/wsClient.ts#L51-L60)
125
+
126
+ ### 错误归一化(ApiError)
127
+
128
+ 统一错误类型:[`ApiError`](./src/client/unifiedApiClient.ts#L19-L31)
129
+
130
+ 归一化目标:
131
+
132
+ - 让上层(UI/日志/监控)用一致结构处理错误,不必分辨来自 HTTP/WS/gRPC
133
+ - 把错误分为三类:`biz / transport / unknown`,用于更合理的告警与重试策略
134
+
135
+ ---
136
+
137
+ ## 依赖与技术方案清单
138
+
139
+ 以下清单以 `@goplus123/core-api` 的 `package.json` 为准:[`package.json`](./package.json)
140
+
141
+ ### 运行时依赖(dependencies)
142
+
143
+ | 依赖包 | 用途 | 在本仓库的落点 |
144
+ |---|---|---|
145
+ | `@connectrpc/connect` | ConnectRPC 核心:client、interceptor、CallOptions 等类型 | [`src/client/grpcClient.ts`](./src/client/grpcClient.ts), [`src/index.ts`](./src/index.ts) |
146
+ | `@connectrpc/connect-web` | 浏览器侧 transport(Connect/GrpcWeb),通过 fetch 发起 gRPC-web 调用 | [`src/client/grpcClient.ts`](./src/client/grpcClient.ts) |
147
+ | `@bufbuild/protobuf` | protobuf message 创建、schema/descriptor、生成代码运行时 | [`src/client/unifiedApiClient.ts`](./src/client/unifiedApiClient.ts), `src/grpcWeb/**` |
148
+ | `axios` | 预留/历史依赖:当前 `src/` 未直接使用(HTTP 目前基于 fetch 实现) | 目前无引用(可评估是否移除) |
149
+
150
+ ### 开发/构建依赖(devDependencies)
151
+
152
+ | 依赖包 | 用途 |
153
+ |---|---|
154
+ | `tsup` | 打包与产物格式(esm/cjs)+ dts 生成 |
155
+ | `typescript` | 类型检查与 dts 生成基础 |
156
+
157
+ ### 内建浏览器能力(不需要额外依赖)
158
+
159
+ - `fetch`:HTTP transport 的基础能力(并提供超时 AbortController 封装)
160
+ - `WebSocket`:WS transport 的基础能力(心跳/重连/订阅在 WsClient 内实现)
161
+ - `AbortController`:HTTP 超时/取消
11
162
 
12
163
  ---
13
164
 
@@ -37,7 +188,7 @@ packages/api
37
188
 
38
189
  1. `sdk.<service>.<method>(payload, options?)`
39
190
  2. `requestApi({ service, functionName, requestParam, wsOptions/callOptions })`
40
- 3. 路由选择与兜底:[`src/routing/router.ts`](file:///d:/working/GX/tsup/packages/api/src/routing/router.ts)
191
+ 3. 路由选择与兜底:[`src/routing/router.ts`](./src/routing/router.ts)
41
192
  - 先查 spec registry(由 `rebuildEndpointRegistry()` 从 `apiMap` 构建)
42
193
  - 命中后交给 `UnifiedApiClient` 分发到 `grpc/ws/http`
43
194
  - 未命中则尝试 legacy `serviceInstances`
@@ -47,7 +198,7 @@ packages/api
47
198
 
48
199
  - 新增 gRPC endpoint:先确认 `src/grpcWeb/grpc/**` 有对应 service/schema,再在 `src/spec/<service>.ts` 增加 `defineEndpoint`
49
200
  - 给同一方法加多协议:使用 `defineEndpointGroup`,同时提供 `grpc/ws/http` 变体
50
- - 改默认 transport 或自动选择策略:调整 [`pickEndpoint`](file:///d:/working/GX/tsup/packages/api/src/routing/router.ts#L267-L296)
201
+ - 改默认 transport 或自动选择策略:调整 [`pickEndpoint`](./src/routing/router.ts#L267-L296)
51
202
  - 新增全局鉴权/日志:通过 `initSDK({ auth, logger })`,或改 `src/client/interceptors.ts`
52
203
 
53
204
  ---
@@ -107,7 +258,7 @@ const sdk = initSDK({
107
258
  - `sdk.cms / sdk.admin / sdk.platform`:类型安全的 service client(由 spec 生成)
108
259
  - `sdk.apiError / sdk.notify`:全局错误与通知中心(同名导出也可直接使用)
109
260
 
110
- 对应实现:[`initSDK`](file:///d:/working/GX/tsup/packages/api/src/index.ts#L372-L578)
261
+ 对应实现:[`initSDK`](./src/index.ts#L372-L578)
111
262
 
112
263
  ### 2) 直接调用(推荐)
113
264
 
@@ -142,7 +293,7 @@ await requestApi({
142
293
  3. `initSDK({ defaultTransport })`
143
294
  4. `'auto'`(默认):按当前 SDK 初始化状态优先选择 `grpc → ws → http`
144
295
 
145
- 对应逻辑:[`pickEndpoint`](file:///d:/working/GX/tsup/packages/api/src/routing/router.ts#L267-L296)
296
+ 对应逻辑:[`pickEndpoint`](./src/routing/router.ts#L267-L296)
146
297
 
147
298
  ---
148
299
 
@@ -198,12 +349,12 @@ apiError.use((error, ctx, next) => {
198
349
 
199
350
  ## API 参考(对外导出)
200
351
 
201
- 来自:[`src/index.ts`](file:///d:/working/GX/tsup/packages/api/src/index.ts)
352
+ 来自:[`src/index.ts`](./src/index.ts)
202
353
 
203
354
  ### initSDK(config)
204
355
 
205
356
  - 作用:初始化(或重置)SDK 单例,并按配置装配 HTTP/WS/gRPC、路由、拦截器与 service clients
206
- - 入参类型:`RequestApiConfig`(见 [`RequestApiConfig`](file:///d:/working/GX/tsup/packages/api/src/client/requestApi.ts#L7-L36))
357
+ - 入参类型:`RequestApiConfig`(见 [`RequestApiConfig`](./src/client/requestApi.ts#L7-L36))
207
358
  - 返回:`{ api, rpc?, client?, wsNotify?, apiError, notify, ...services }`
208
359
 
209
360
  ### destroySDK()
@@ -245,7 +396,7 @@ apiError.use((error, ctx, next) => {
245
396
  - 解析兼容:支持三种历史值格式(UUID / 32 位 hex / base64(bytes))
246
397
  - 缓存写入:若不存在或不可解析,会生成新的 UUID 并写回存储
247
398
  - 默认输出:返回 UUID(带 `-`),例如 `xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx`
248
- - 默认存储策略(见 [`utils/uniqueKey.ts`](file:///d:/working/GX/tsup/packages/api/src/utils/uniqueKey.ts)):
399
+ - 默认存储策略(见 [`utils/uniqueKey.ts`](./src/utils/uniqueKey.ts)):
249
400
  - Web 环境:使用 `localStorage`
250
401
  - 非 Web 或 `localStorage` 不可用:使用进程内 memory storage 兜底(保证同进程稳定)
251
402
 
@@ -297,19 +448,19 @@ setUniqueKeyStorage(appStorage)
297
448
  ### KeyValueStorage
298
449
 
299
450
  - 作用:unikey 存储抽象(只要求 `get/set` 两个方法)
300
- - 定义:[`utils/storage.ts`](file:///d:/working/GX/tsup/packages/api/src/utils/storage.ts)
451
+ - 定义:[`utils/storage.ts`](./src/utils/storage.ts)
301
452
 
302
453
  ---
303
454
 
304
455
  ## spec 驱动的 Service 列表
305
456
 
306
- spec 定义入口:[`spec/index.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/index.ts)
457
+ spec 定义入口:[`spec/index.ts`](./src/spec/index.ts)
307
458
 
308
459
  按 service 拆分的落点文件:
309
460
 
310
- - cms:[`spec/cms.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/cms.ts)
311
- - admin:[`spec/admin.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/admin.ts)
312
- - platform:[`spec/platform.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/platform.ts)
461
+ - cms:[`spec/cms.ts`](./src/spec/cms.ts)
462
+ - admin:[`spec/admin.ts`](./src/spec/admin.ts)
463
+ - platform:[`spec/platform.ts`](./src/spec/platform.ts)
313
464
 
314
465
  ### cms(gRPC 为主,部分接口可多协议)
315
466
 
@@ -349,22 +500,22 @@ spec 定义入口:[`spec/index.ts`](file:///d:/working/GX/tsup/packages/api/sr
349
500
 
350
501
  ## 为接口新增/扩展 transport(spec 写法)
351
502
 
352
- spec 工具位于:[`spec/endpoint.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/endpoint.ts)
503
+ spec 工具位于:[`spec/endpoint.ts`](./src/spec/endpoint.ts)
353
504
 
354
505
  ## 开发指南:如何补充 spec 路由
355
506
 
356
507
  spec 的职责是给 `requestApi` 提供“路由表”:
357
508
 
358
- - runtime 上它会被加载进 `apiMap`,并在 `initSDK()` 时构建 registry(见 [`rebuildEndpointRegistry`](file:///d:/working/GX/tsup/packages/api/src/routing/router.ts#L84-L132))
509
+ - runtime 上它会被加载进 `apiMap`,并在 `initSDK()` 时构建 registry(见 [`rebuildEndpointRegistry`](./src/routing/router.ts#L84-L132))
359
510
  - 调用方可以通过 `sdk.<service>.<method>()` 或 `requestApi({ service, functionName })` 走到对应 transport
360
511
  - 当调用方显式指定 `transport` 时,如果 spec 中没有对应变体,会在路由选择阶段同步抛错
361
512
 
362
513
  ### 1) 选择落点文件
363
514
 
364
- - cms 的 spec:[`src/spec/cms.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/cms.ts)
365
- - admin 的 spec:[`src/spec/admin.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/admin.ts)
366
- - platform(WS)spec:[`src/spec/platform.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/platform.ts)
367
- - 聚合与 apiMap:[`src/spec/index.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/index.ts)
515
+ - cms 的 spec:[`src/spec/cms.ts`](./src/spec/cms.ts)
516
+ - admin 的 spec:[`src/spec/admin.ts`](./src/spec/admin.ts)
517
+ - platform(WS)spec:[`src/spec/platform.ts`](./src/spec/platform.ts)
518
+ - 聚合与 apiMap:[`src/spec/index.ts`](./src/spec/index.ts)
368
519
 
369
520
  ### 2) 约定:id / service / functionName
370
521
 
@@ -490,7 +641,7 @@ await requestApi({
490
641
  3. 在 `src/index.ts` 的 `initSDK` 返回类型里补充 `foo?: ApiServiceClient<'foo'>`
491
642
  4. 在 `initSDK` 里按 transport 条件创建 `sdk.foo = createApiServiceClient('foo')`
492
643
 
493
- `createApiServiceClient` 的实现位置:[`createApiServiceClient`](file:///d:/working/GX/tsup/packages/api/src/index.ts#L592-L683)
644
+ `createApiServiceClient` 的实现位置:[`createApiServiceClient`](./src/index.ts#L592-L683)
494
645
 
495
646
  ### 单协议(defineEndpoint)
496
647
 
@@ -518,3 +669,39 @@ export const demo = defineEndpointGroup({
518
669
  },
519
670
  })
520
671
  ```
672
+
673
+ ---
674
+
675
+ ## 扩展与改造思路(给后续技术升级/AI 重设计用)
676
+
677
+ ### 推荐的扩展入口(优先级从高到低)
678
+
679
+ 1. **扩接口/补路由**:新增或调整 `src/spec/*`,保持 `id` 与 `service/functionName` 约定一致(最安全、最不影响业务)
680
+ 2. **扩横切能力**:在 `initSDK` 注入 interceptors(或扩展内置 interceptors),实现鉴权/日志/重试/缓存/灰度/追踪
681
+ 3. **扩 transport 能力**:增强 HttpClient/WsClient/GrpcClient(例如:统一重试、断路器、requestId 透传、metrics)
682
+ 4. **迁移 legacy modules → spec**:逐个把 `src/modules/*` 中的接口补到 spec,并在业务侧切到 `requestApi`/service client
683
+
684
+ ### 适合做技术改造的“稳定边界”
685
+
686
+ 如果未来要用 AI 或进行架构升级,建议把以下内容视为“对外契约”,优先保持兼容:
687
+
688
+ - `initSDK(config)` 的输入语义:装配 transport、注入 auth/logger、建立 WS 连接、构建 registry、暴露 service clients
689
+ - `requestApi(args)` 的语义:按 spec 路由 + fallback(spec → legacy → raw WS)
690
+ - `EndpointSpec/EndpointGroup` 的数据结构:能静态表达“接口可调用方式”
691
+ - `ApiError` 的归一化结构:上层可用同一结构做提示/告警/埋点
692
+
693
+ 在不破坏上述契约的前提下,内部实现可以替换,例如:
694
+
695
+ - 把 WS 实现从当前协议替换为新协议,只要 `WsRpcClient.call` 语义不变
696
+ - 把 gRPC transport 从 grpc-web 切到 connect,只要 `GrpcClient.getService().method(req, opt)` 语义可映射
697
+ - 把路由策略从“静态 registry + pickEndpoint”升级为“策略引擎”,只要输入输出一致
698
+
699
+ ### 常见改造需求与落点建议
700
+
701
+ | 需求 | 推荐落点 | 原因 |
702
+ |---|---|---|
703
+ | 全局请求链路追踪(traceId/spanId) | interceptor + auth.getHeaders | 不侵入每个接口定义,可统一注入 |
704
+ | 统一重试/退避 | interceptor(基于 isTransportError) | 在统一入口实现一次,三 transport 复用 |
705
+ | gRPC cookie/credentials 支持 | GrpcClient transport 配置 | 属于 transport 细节,不应泄漏到业务侧 |
706
+ | WS 请求响应匹配策略调整 | WsClient/WsRpcClient | 直接影响 requestId、responseFunctionName 等行为 |
707
+ | 业务错误码映射为用户提示 | normalizeApiError / onApiError | 保持 transport 无关,方便统一处理 |