@international-iot-association/api-bridge 3.0.0-rc.1

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 ADDED
@@ -0,0 +1,4 @@
1
+ # @international-iot-association/api-bridge
2
+
3
+ 基于 Proxy 的类型安全 RPC 桥接层。
4
+ 属于**组 A · 协议锁步组**(与 `plugin-contracts`/`plugin-sdk`/`rct-state` 一起随 `agentApi` major 锁步发布)。
package/dist/index.cjs ADDED
@@ -0,0 +1,399 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ APIProxy: () => APIProxy,
24
+ ApiBridgeError: () => ApiBridgeError,
25
+ BRIDGE_ERROR_SUGGESTIONS: () => BRIDGE_ERROR_SUGGESTIONS,
26
+ createAPIClient: () => createAPIClient,
27
+ createAPIProxy: () => createAPIProxy,
28
+ createAPIServer: () => createAPIServer,
29
+ makeErrorResponse: () => makeErrorResponse,
30
+ sendMessage: () => sendMessage,
31
+ subscribeToMessage: () => subscribeToMessage,
32
+ waitForMessage: () => waitForMessage
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/core/API.proxy.ts
37
+ function createAPIProxy(customCallingFunction) {
38
+ const createHandler = (path) => {
39
+ return {
40
+ get(target, propKey, receiver) {
41
+ if (typeof propKey === "string") {
42
+ switch (propKey) {
43
+ case "call":
44
+ case "callWithOptions":
45
+ case "invoke":
46
+ case "invokeWithOptions":
47
+ case "onRequest":
48
+ case "on":
49
+ case "sendEvent":
50
+ return (...args) => {
51
+ if (customCallingFunction && typeof customCallingFunction === "function") {
52
+ const callingResult = customCallingFunction(
53
+ path,
54
+ args,
55
+ propKey
56
+ );
57
+ if (callingResult) {
58
+ return callingResult;
59
+ }
60
+ }
61
+ return {
62
+ api: path,
63
+ args
64
+ };
65
+ };
66
+ default: {
67
+ const newPath = [...path, propKey];
68
+ return new Proxy({}, createHandler(newPath));
69
+ }
70
+ }
71
+ }
72
+ return Reflect.get(target, propKey, receiver);
73
+ }
74
+ };
75
+ };
76
+ return new Proxy({}, createHandler([]));
77
+ }
78
+ var APIProxy = createAPIProxy();
79
+
80
+ // src/core/errors.ts
81
+ var BRIDGE_ERROR_SUGGESTIONS = {
82
+ TARGET_UNAVAILABLE: "\u786E\u8BA4\u76EE\u6807\u63D2\u4EF6/\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u5904\u4E8E\u8FD0\u884C\u72B6\u6001\u540E\u91CD\u8BD5",
83
+ METHOD_NOT_FOUND: "\u68C0\u67E5 API \u540D\u79F0\u62FC\u5199\u4E0E\u76EE\u6807\u63D2\u4EF6\u7248\u672C\u662F\u5426\u63D0\u4F9B\u8BE5\u65B9\u6CD5",
84
+ REMOTE_HANDLER_ERROR: "\u67E5\u770B\u76EE\u6807 runtime \u65E5\u5FD7\u4E2D\u7684\u9519\u8BEF\u8BE6\u60C5",
85
+ TRANSPORT_CLOSED: "\u901A\u8BAF\u94FE\u8DEF\u5DF2\u5173\u95ED\uFF08runtime \u505C\u6B62/\u9875\u9762\u5378\u8F7D\uFF09\uFF0C\u91CD\u65B0\u5EFA\u7ACB\u540E\u91CD\u8BD5",
86
+ REQUEST_TIMEOUT: "\u786E\u8BA4\u5BF9\u7AEF\u5B58\u6D3B\u5E76\u9002\u5F53\u8C03\u5927 timeoutMs \u540E\u91CD\u8BD5"
87
+ };
88
+ var ApiBridgeError = class extends Error {
89
+ code;
90
+ /** 点分 API 路径,如 "station.runTest"。 */
91
+ apiPath;
92
+ requestId;
93
+ timeoutMs;
94
+ /** 建议动作(人可读,供 UI/日志展示)。 */
95
+ suggestion;
96
+ constructor(code, apiPath, message, options = {}) {
97
+ super(`[${code}] ${apiPath}: ${message}`);
98
+ this.name = "ApiBridgeError";
99
+ this.code = code;
100
+ this.apiPath = apiPath;
101
+ this.requestId = options.requestId;
102
+ this.timeoutMs = options.timeoutMs;
103
+ this.suggestion = options.suggestion ?? BRIDGE_ERROR_SUGGESTIONS[code];
104
+ }
105
+ };
106
+ function makeErrorResponse(request, code, message) {
107
+ return {
108
+ type: "response",
109
+ id: request.id ?? "",
110
+ result: void 0,
111
+ error: { code, message },
112
+ meta: { api: request.api, args: [] }
113
+ };
114
+ }
115
+
116
+ // src/core/API.server.ts
117
+ function createAPIServer(config) {
118
+ const { sendMessageFunc } = config;
119
+ const requestProcessorCache = {};
120
+ const server = createAPIProxy((api, args, callingFunctionName) => {
121
+ switch (callingFunctionName) {
122
+ case "onRequest": {
123
+ const processor = args[0];
124
+ if (processor && typeof processor === "function") {
125
+ requestProcessorCache[api.join(".")] = args[0];
126
+ } else {
127
+ console.error(
128
+ `[Error] register API Server processor error for ${JSON.stringify(
129
+ api
130
+ )}, handler is not a function.`
131
+ );
132
+ }
133
+ break;
134
+ }
135
+ case "sendEvent":
136
+ if (config.sendEventFunc) {
137
+ const event = {
138
+ type: "event",
139
+ name: args[0],
140
+ data: args[1],
141
+ meta: {
142
+ api,
143
+ args
144
+ }
145
+ };
146
+ config.sendEventFunc(event);
147
+ } else {
148
+ console.warn("[Warn] sendEventFunc is not defined.");
149
+ }
150
+ break;
151
+ default:
152
+ break;
153
+ }
154
+ });
155
+ async function serverHandler(request) {
156
+ const { id, api, args, context } = request;
157
+ if (!(api && args)) {
158
+ console.warn(
159
+ "[Warn] [Skip] API Server process, original message:",
160
+ request
161
+ );
162
+ return;
163
+ }
164
+ const apiName = api.join(".");
165
+ const wantsTypedErrors = request.wantsTypedErrors === true;
166
+ if (!requestProcessorCache[apiName]) {
167
+ if (wantsTypedErrors && sendMessageFunc && id) {
168
+ sendMessageFunc(
169
+ makeErrorResponse(request, "METHOD_NOT_FOUND", `no handler registered for API: ${apiName}`)
170
+ );
171
+ return;
172
+ }
173
+ console.warn(`[Warn] There is no handler registered for API: ${apiName}`);
174
+ return;
175
+ }
176
+ let result;
177
+ try {
178
+ result = await requestProcessorCache[apiName].apply(null, [
179
+ ...args,
180
+ context
181
+ ]);
182
+ } catch (err) {
183
+ if (wantsTypedErrors && sendMessageFunc && id) {
184
+ sendMessageFunc(
185
+ makeErrorResponse(
186
+ request,
187
+ "REMOTE_HANDLER_ERROR",
188
+ err instanceof Error ? err.message : String(err)
189
+ )
190
+ );
191
+ return;
192
+ }
193
+ throw err;
194
+ }
195
+ if (sendMessageFunc && id) {
196
+ sendMessageFunc({
197
+ type: "response",
198
+ id,
199
+ result,
200
+ meta: {
201
+ api,
202
+ // v2 响应不回显请求参数(尤其不回显 token/敏感 payload);
203
+ // 1.x 响应按原协议回显以保持兼容。
204
+ args: wantsTypedErrors ? [] : args,
205
+ context: wantsTypedErrors ? void 0 : context
206
+ }
207
+ });
208
+ }
209
+ }
210
+ return { server, serverHandler };
211
+ }
212
+
213
+ // src/core/API.client.ts
214
+ var import_non_secure = require("nanoid/non-secure");
215
+
216
+ // src/utils/RxMessage.ts
217
+ var import_rxjs = require("rxjs");
218
+ var messageDispatcher = new import_rxjs.Subject();
219
+ function sendMessage(id, payload) {
220
+ const message = { id, payload };
221
+ messageDispatcher.next(message);
222
+ }
223
+ var DEFAULT_TIMEOUT_MS = 1e4;
224
+ function subscribeToMessage(targetId, timeoutDurationMs = DEFAULT_TIMEOUT_MS) {
225
+ return messageDispatcher.pipe(
226
+ (0, import_rxjs.filter)((message) => message.id === targetId),
227
+ (0, import_rxjs.map)((message) => message.payload),
228
+ (0, import_rxjs.timeout)({
229
+ first: timeoutDurationMs,
230
+ with: () => (0, import_rxjs.of)(null)
231
+ }),
232
+ (0, import_rxjs.take)(1)
233
+ );
234
+ }
235
+ async function waitForMessage(targetId, timeoutMs) {
236
+ try {
237
+ const payload = await (0, import_rxjs.firstValueFrom)(
238
+ subscribeToMessage(targetId, timeoutMs)
239
+ );
240
+ return payload;
241
+ } catch (error) {
242
+ console.error(`[AsyncWait] Error waiting for ID=${targetId}:`, error);
243
+ return null;
244
+ }
245
+ }
246
+
247
+ // src/core/API.client.ts
248
+ var DEFAULT_INVOKE_TIMEOUT_MS = 1e4;
249
+ function createAPIClient(config) {
250
+ const { debug = false, requestServerFunc } = config;
251
+ const eventHandlerCache = {};
252
+ const pendingInvokes = /* @__PURE__ */ new Map();
253
+ const client = createAPIProxy((api, args, callingFunctionName) => {
254
+ if (debug) {
255
+ console.info(
256
+ "[API Client] API:",
257
+ api,
258
+ "Args:",
259
+ args,
260
+ "callingFunctionName:",
261
+ callingFunctionName
262
+ );
263
+ }
264
+ switch (callingFunctionName) {
265
+ case "call":
266
+ case "callWithOptions": {
267
+ const id = (0, import_non_secure.nanoid)();
268
+ const hasClientOptions = callingFunctionName === "callWithOptions";
269
+ const clientOptions = hasClientOptions ? args[0] ?? {} : void 0;
270
+ const requestArgs = hasClientOptions ? args.slice(1) : args;
271
+ const responseSub = waitForMessage(id, clientOptions?.timeoutMs);
272
+ return (async () => {
273
+ await requestServerFunc({
274
+ type: "request",
275
+ api,
276
+ args: requestArgs,
277
+ id
278
+ });
279
+ return responseSub;
280
+ })();
281
+ }
282
+ case "invoke":
283
+ case "invokeWithOptions": {
284
+ const id = (0, import_non_secure.nanoid)();
285
+ const hasClientOptions = callingFunctionName === "invokeWithOptions";
286
+ const clientOptions = hasClientOptions ? args[0] ?? {} : {};
287
+ const requestArgs = hasClientOptions ? args.slice(1) : args;
288
+ const apiPath = api.join(".");
289
+ const timeoutMs = clientOptions.timeoutMs ?? DEFAULT_INVOKE_TIMEOUT_MS;
290
+ return new Promise((resolve, reject) => {
291
+ const timer = setTimeout(() => {
292
+ pendingInvokes.delete(id);
293
+ reject(
294
+ new ApiBridgeError(
295
+ "REQUEST_TIMEOUT",
296
+ apiPath,
297
+ `no response within ${timeoutMs}ms`,
298
+ { requestId: id, timeoutMs }
299
+ )
300
+ );
301
+ }, timeoutMs);
302
+ pendingInvokes.set(id, { resolve, reject, timer, apiPath });
303
+ Promise.resolve(
304
+ requestServerFunc({
305
+ type: "request",
306
+ api,
307
+ args: requestArgs,
308
+ id,
309
+ wantsTypedErrors: true
310
+ })
311
+ ).catch((err) => {
312
+ const pending = pendingInvokes.get(id);
313
+ if (!pending) return;
314
+ clearTimeout(pending.timer);
315
+ pendingInvokes.delete(id);
316
+ reject(
317
+ new ApiBridgeError(
318
+ "TARGET_UNAVAILABLE",
319
+ apiPath,
320
+ `transport send failed: ${err instanceof Error ? err.message : String(err)}`,
321
+ { requestId: id }
322
+ )
323
+ );
324
+ });
325
+ });
326
+ }
327
+ case "on": {
328
+ const apiName = api.join(".");
329
+ const key = `${apiName}:${args[0]}`;
330
+ const func = args[1];
331
+ eventHandlerCache[key] = func;
332
+ return () => {
333
+ delete eventHandlerCache[key];
334
+ };
335
+ }
336
+ default:
337
+ break;
338
+ }
339
+ });
340
+ function serverEventHandler(event, config2) {
341
+ const { name, data, meta } = event;
342
+ const { debug: debug2 = false } = config2 || {};
343
+ const key = `${meta.api.join(".")}:${name}`;
344
+ if (debug2) {
345
+ console.info("[API Client] Server Event:", key, "Data:", data);
346
+ }
347
+ if (key && eventHandlerCache[key]) {
348
+ eventHandlerCache[key](data);
349
+ }
350
+ if (eventHandlerCache["*"] && eventHandlerCache["*"] instanceof Function) {
351
+ eventHandlerCache["*"](data, event);
352
+ }
353
+ const eventKeyForTargetAPI = `${meta.api.join(".")}:*`;
354
+ if (eventHandlerCache[eventKeyForTargetAPI] && eventHandlerCache[eventKeyForTargetAPI] instanceof Function) {
355
+ eventHandlerCache[eventKeyForTargetAPI](data, event);
356
+ }
357
+ }
358
+ function clientMessageHandler(message) {
359
+ const pending = pendingInvokes.get(message.id);
360
+ if (pending) {
361
+ clearTimeout(pending.timer);
362
+ pendingInvokes.delete(message.id);
363
+ if (message.error) {
364
+ pending.reject(
365
+ new ApiBridgeError(message.error.code, pending.apiPath, message.error.message, {
366
+ requestId: message.id
367
+ })
368
+ );
369
+ } else {
370
+ pending.resolve(message.result);
371
+ }
372
+ return;
373
+ }
374
+ sendMessage(message.id, message);
375
+ }
376
+ function closeTransport(reason = "transport closed") {
377
+ for (const [id, pending] of pendingInvokes) {
378
+ clearTimeout(pending.timer);
379
+ pending.reject(
380
+ new ApiBridgeError("TRANSPORT_CLOSED", pending.apiPath, reason, { requestId: id })
381
+ );
382
+ }
383
+ pendingInvokes.clear();
384
+ }
385
+ return { client, serverEventHandler, clientMessageHandler, closeTransport };
386
+ }
387
+ // Annotate the CommonJS export names for ESM import in node:
388
+ 0 && (module.exports = {
389
+ APIProxy,
390
+ ApiBridgeError,
391
+ BRIDGE_ERROR_SUGGESTIONS,
392
+ createAPIClient,
393
+ createAPIProxy,
394
+ createAPIServer,
395
+ makeErrorResponse,
396
+ sendMessage,
397
+ subscribeToMessage,
398
+ waitForMessage
399
+ });
@@ -0,0 +1,186 @@
1
+ import { Observable } from 'rxjs';
2
+
3
+ type BridgeErrorCode = "TARGET_UNAVAILABLE" | "METHOD_NOT_FOUND" | "REMOTE_HANDLER_ERROR" | "TRANSPORT_CLOSED" | "REQUEST_TIMEOUT";
4
+ /** 各错误码的默认建议动作(诊断展示用,可被覆盖)。 */
5
+ declare const BRIDGE_ERROR_SUGGESTIONS: Record<BridgeErrorCode, string>;
6
+ interface ApiBridgeErrorOptions {
7
+ requestId?: string;
8
+ timeoutMs?: number;
9
+ suggestion?: string;
10
+ }
11
+ declare class ApiBridgeError extends Error {
12
+ readonly code: BridgeErrorCode;
13
+ /** 点分 API 路径,如 "station.runTest"。 */
14
+ readonly apiPath: string;
15
+ readonly requestId?: string;
16
+ readonly timeoutMs?: number;
17
+ /** 建议动作(人可读,供 UI/日志展示)。 */
18
+ readonly suggestion: string;
19
+ constructor(code: BridgeErrorCode, apiPath: string, message: string, options?: ApiBridgeErrorOptions);
20
+ }
21
+ /**
22
+ * 由传输层/服务端合成一个错误响应信封。
23
+ * 注意:错误响应的 meta 不回显请求参数(args 置空),避免 token/敏感 payload
24
+ * 在错误路径上被二次传播。
25
+ */
26
+ declare function makeErrorResponse(request: Pick<Request, "id" | "api">, code: BridgeErrorCode, message: string): Response;
27
+
28
+ interface Meta {
29
+ api: string[];
30
+ args: any[];
31
+ context?: any;
32
+ }
33
+ type Request = Meta & {
34
+ type: "request";
35
+ id?: string;
36
+ /**
37
+ * v2 客户端(invoke/invokeWithOptions)置 true:服务端对缺 handler/handler
38
+ * 抛错应答错误信封。1.x 请求没有该字段,服务端保持旧行为(警告 + 静默)。
39
+ */
40
+ wantsTypedErrors?: boolean;
41
+ };
42
+ interface Response<Result = any> {
43
+ type: "response";
44
+ id: string;
45
+ result: Result;
46
+ meta: Meta;
47
+ /** v2 错误信封:仅发给声明了 wantsTypedErrors 的请求。 */
48
+ error?: {
49
+ code: BridgeErrorCode;
50
+ message: string;
51
+ };
52
+ }
53
+ interface Event {
54
+ type: "event";
55
+ name: string;
56
+ data: any;
57
+ meta: Meta;
58
+ }
59
+ interface CallResult {
60
+ api: string[];
61
+ args: any[];
62
+ }
63
+ type OnRequestHandler<T extends any[]> = (...args: T) => any;
64
+ interface ClientCallOptions {
65
+ timeoutMs?: number;
66
+ }
67
+ type FuncHandler<Args extends any[] = any[], Result = any> = {
68
+ /** 幻影判别符(仅类型层,运行时不存在):供 ApiClient/ApiServer 方向映射识别叶子。 */
69
+ __kind?: "func";
70
+ /** 供 Client 使用(1.x:resolve 完整 envelope;超时/远端错误 resolve null) */
71
+ call: (...args: Args) => Promise<Response<Result>>;
72
+ /** 供 Client 使用,携带本地选项(选项不会发送给服务端) */
73
+ callWithOptions: (options: ClientCallOptions, ...args: Args) => Promise<Response<Result>>;
74
+ /** 供 Client 使用(v2):resolve result 本体;失败以 ApiBridgeError reject */
75
+ invoke: (...args: Args) => Promise<Result>;
76
+ /** 供 Client 使用(v2):带本地选项(如 timeoutMs)的 invoke */
77
+ invokeWithOptions: (options: ClientCallOptions, ...args: Args) => Promise<Result>;
78
+ /** 供 Server 使用 */
79
+ onRequest: (handler: OnRequestHandler<Args>) => any;
80
+ /** 供 Client 监听 Server 事件 */
81
+ on: (eventName: string, handler: (data: any) => void | Promise<void>) => () => void;
82
+ /** 供 Server 使用,向 Client 发送事件数据 */
83
+ sendEvent: (eventName: string, data: any) => void | Promise<void>;
84
+ } & {
85
+ [key: string]: any;
86
+ };
87
+ type EventListener<EventName extends string, EventData> = {
88
+ /** 幻影判别符(仅类型层,运行时不存在):供 ApiClient/ApiServer 方向映射识别叶子。 */
89
+ __kind?: "event";
90
+ /** 供 Client 监听 Server 事件 */
91
+ on: (eventName: EventName, handler: (data: EventData) => void | Promise<void>) => () => void;
92
+ /** 供 Server 使用,向 Client 发送事件数据 */
93
+ sendEvent: (eventName: EventName, data: EventData) => void | Promise<void>;
94
+ } & {
95
+ [key: string]: any;
96
+ };
97
+ type ApiNode<T extends any[], P extends Record<string, any> = Record<string, any>> = {
98
+ [key: string]: ApiNode<T, P>;
99
+ };
100
+ type ApiRoot<T extends any[] = any[]> = {
101
+ [key: string]: ApiNode<T>;
102
+ };
103
+ type DynamicChainedProxy<T extends any[]> = ApiNode<T>;
104
+ declare function createAPIProxy<T extends ApiRoot<any[]>>(customCallingFunction?: (api: string[], args: any[], callingFunctionName: "call" | "callWithOptions" | "invoke" | "invokeWithOptions" | "onRequest" | "on" | "sendEvent") => any): T;
105
+ declare const APIProxy: ApiRoot<any[]>;
106
+
107
+ /** 客户端方法面(请求/响应叶子)。 */
108
+ interface ClientFuncFace<Args extends any[], Result> {
109
+ /** 1.x:resolve 完整 envelope;超时/远端错误 resolve null。 */
110
+ call(...args: Args): Promise<Response<Result>>;
111
+ callWithOptions(options: ClientCallOptions, ...args: Args): Promise<Response<Result>>;
112
+ /** v2:resolve result 本体;失败以 ApiBridgeError reject。 */
113
+ invoke(...args: Args): Promise<Result>;
114
+ invokeWithOptions(options: ClientCallOptions, ...args: Args): Promise<Result>;
115
+ }
116
+ /** 服务端方法面(请求/响应叶子)。 */
117
+ interface ServerFuncFace<Args extends any[], Result> {
118
+ onRequest(handler: (...args: Args) => Result | Promise<Result>): unknown;
119
+ }
120
+ /** 客户端事件面(事件叶子)。 */
121
+ interface ClientEventFace<EventName extends string, EventData> {
122
+ on(eventName: EventName, handler: (data: EventData) => void | Promise<void>): () => void;
123
+ }
124
+ /** 服务端事件面(事件叶子)。 */
125
+ interface ServerEventFace<EventName extends string, EventData> {
126
+ sendEvent(eventName: EventName, data: EventData): void | Promise<void>;
127
+ }
128
+ /** 消费方向视图:UI/服务消费者持有的类型。 */
129
+ type ApiClient<T> = {
130
+ [K in keyof T]: T[K] extends {
131
+ __kind?: "func";
132
+ call: (...args: infer A extends any[]) => Promise<Response<infer R>>;
133
+ } ? ClientFuncFace<A, R> : T[K] extends {
134
+ __kind?: "event";
135
+ on: (eventName: infer N extends string, handler: (data: infer D) => void | Promise<void>) => () => void;
136
+ } ? ClientEventFace<N, D> : ApiClient<T[K]>;
137
+ };
138
+ /** 提供方向视图:runtime/服务提供者持有的类型。 */
139
+ type ApiServer<T> = {
140
+ [K in keyof T]: T[K] extends {
141
+ __kind?: "func";
142
+ call: (...args: infer A extends any[]) => Promise<Response<infer R>>;
143
+ } ? ServerFuncFace<A, R> : T[K] extends {
144
+ __kind?: "event";
145
+ on: (eventName: infer N extends string, handler: (data: infer D) => void | Promise<void>) => () => void;
146
+ } ? ServerEventFace<N, D> : ApiServer<T[K]>;
147
+ };
148
+
149
+ type SendEventFunction = (event: Event) => void;
150
+ type SendMessageFunction = (response: Response) => void;
151
+ interface CreateAPIServerConfig {
152
+ sendEventFunc?: SendEventFunction;
153
+ sendMessageFunc?: SendMessageFunction;
154
+ debug?: boolean;
155
+ }
156
+ declare function createAPIServer<T extends ApiRoot<any[]>>(config: CreateAPIServerConfig): {
157
+ server: T;
158
+ serverHandler: (request: Request) => Promise<void>;
159
+ };
160
+
161
+ type RequestServerFunc = (request: Request) => Promise<any>;
162
+ interface CreateAPIClientConfig {
163
+ /** 是否打印调试信息,默认 false */
164
+ debug?: boolean;
165
+ requestServerFunc: RequestServerFunc;
166
+ }
167
+ declare function createAPIClient<T extends ApiRoot>(config: CreateAPIClientConfig): {
168
+ client: T;
169
+ serverEventHandler: (event: Event, config?: {
170
+ debug?: boolean;
171
+ }) => void;
172
+ clientMessageHandler: (message: Response) => void;
173
+ closeTransport: (reason?: string) => void;
174
+ };
175
+
176
+ /** 以 id 为键将消息推送到总线。 */
177
+ declare function sendMessage<T>(id: string | number, payload: T): void;
178
+ /**
179
+ * 订阅指定 id 的第一条消息。发出 payload,超时则发出 null,
180
+ * 随后完成(自动取消订阅)。
181
+ */
182
+ declare function subscribeToMessage<T = any>(targetId: string | number, timeoutDurationMs?: number): Observable<T | null>;
183
+ /** subscribeToMessage 的 async/await 封装。 */
184
+ declare function waitForMessage<T = any>(targetId: string | number, timeoutMs?: number): Promise<T | null>;
185
+
186
+ export { APIProxy, ApiBridgeError, type ApiBridgeErrorOptions, type ApiClient, type ApiRoot, type ApiServer, BRIDGE_ERROR_SUGGESTIONS, type BridgeErrorCode, type CallResult, type ClientCallOptions, type ClientEventFace, type ClientFuncFace, type DynamicChainedProxy, type Event, type EventListener, type FuncHandler, type Meta, type OnRequestHandler, type Request, type Response, type ServerEventFace, type ServerFuncFace, createAPIClient, createAPIProxy, createAPIServer, makeErrorResponse, sendMessage, subscribeToMessage, waitForMessage };
@@ -0,0 +1,186 @@
1
+ import { Observable } from 'rxjs';
2
+
3
+ type BridgeErrorCode = "TARGET_UNAVAILABLE" | "METHOD_NOT_FOUND" | "REMOTE_HANDLER_ERROR" | "TRANSPORT_CLOSED" | "REQUEST_TIMEOUT";
4
+ /** 各错误码的默认建议动作(诊断展示用,可被覆盖)。 */
5
+ declare const BRIDGE_ERROR_SUGGESTIONS: Record<BridgeErrorCode, string>;
6
+ interface ApiBridgeErrorOptions {
7
+ requestId?: string;
8
+ timeoutMs?: number;
9
+ suggestion?: string;
10
+ }
11
+ declare class ApiBridgeError extends Error {
12
+ readonly code: BridgeErrorCode;
13
+ /** 点分 API 路径,如 "station.runTest"。 */
14
+ readonly apiPath: string;
15
+ readonly requestId?: string;
16
+ readonly timeoutMs?: number;
17
+ /** 建议动作(人可读,供 UI/日志展示)。 */
18
+ readonly suggestion: string;
19
+ constructor(code: BridgeErrorCode, apiPath: string, message: string, options?: ApiBridgeErrorOptions);
20
+ }
21
+ /**
22
+ * 由传输层/服务端合成一个错误响应信封。
23
+ * 注意:错误响应的 meta 不回显请求参数(args 置空),避免 token/敏感 payload
24
+ * 在错误路径上被二次传播。
25
+ */
26
+ declare function makeErrorResponse(request: Pick<Request, "id" | "api">, code: BridgeErrorCode, message: string): Response;
27
+
28
+ interface Meta {
29
+ api: string[];
30
+ args: any[];
31
+ context?: any;
32
+ }
33
+ type Request = Meta & {
34
+ type: "request";
35
+ id?: string;
36
+ /**
37
+ * v2 客户端(invoke/invokeWithOptions)置 true:服务端对缺 handler/handler
38
+ * 抛错应答错误信封。1.x 请求没有该字段,服务端保持旧行为(警告 + 静默)。
39
+ */
40
+ wantsTypedErrors?: boolean;
41
+ };
42
+ interface Response<Result = any> {
43
+ type: "response";
44
+ id: string;
45
+ result: Result;
46
+ meta: Meta;
47
+ /** v2 错误信封:仅发给声明了 wantsTypedErrors 的请求。 */
48
+ error?: {
49
+ code: BridgeErrorCode;
50
+ message: string;
51
+ };
52
+ }
53
+ interface Event {
54
+ type: "event";
55
+ name: string;
56
+ data: any;
57
+ meta: Meta;
58
+ }
59
+ interface CallResult {
60
+ api: string[];
61
+ args: any[];
62
+ }
63
+ type OnRequestHandler<T extends any[]> = (...args: T) => any;
64
+ interface ClientCallOptions {
65
+ timeoutMs?: number;
66
+ }
67
+ type FuncHandler<Args extends any[] = any[], Result = any> = {
68
+ /** 幻影判别符(仅类型层,运行时不存在):供 ApiClient/ApiServer 方向映射识别叶子。 */
69
+ __kind?: "func";
70
+ /** 供 Client 使用(1.x:resolve 完整 envelope;超时/远端错误 resolve null) */
71
+ call: (...args: Args) => Promise<Response<Result>>;
72
+ /** 供 Client 使用,携带本地选项(选项不会发送给服务端) */
73
+ callWithOptions: (options: ClientCallOptions, ...args: Args) => Promise<Response<Result>>;
74
+ /** 供 Client 使用(v2):resolve result 本体;失败以 ApiBridgeError reject */
75
+ invoke: (...args: Args) => Promise<Result>;
76
+ /** 供 Client 使用(v2):带本地选项(如 timeoutMs)的 invoke */
77
+ invokeWithOptions: (options: ClientCallOptions, ...args: Args) => Promise<Result>;
78
+ /** 供 Server 使用 */
79
+ onRequest: (handler: OnRequestHandler<Args>) => any;
80
+ /** 供 Client 监听 Server 事件 */
81
+ on: (eventName: string, handler: (data: any) => void | Promise<void>) => () => void;
82
+ /** 供 Server 使用,向 Client 发送事件数据 */
83
+ sendEvent: (eventName: string, data: any) => void | Promise<void>;
84
+ } & {
85
+ [key: string]: any;
86
+ };
87
+ type EventListener<EventName extends string, EventData> = {
88
+ /** 幻影判别符(仅类型层,运行时不存在):供 ApiClient/ApiServer 方向映射识别叶子。 */
89
+ __kind?: "event";
90
+ /** 供 Client 监听 Server 事件 */
91
+ on: (eventName: EventName, handler: (data: EventData) => void | Promise<void>) => () => void;
92
+ /** 供 Server 使用,向 Client 发送事件数据 */
93
+ sendEvent: (eventName: EventName, data: EventData) => void | Promise<void>;
94
+ } & {
95
+ [key: string]: any;
96
+ };
97
+ type ApiNode<T extends any[], P extends Record<string, any> = Record<string, any>> = {
98
+ [key: string]: ApiNode<T, P>;
99
+ };
100
+ type ApiRoot<T extends any[] = any[]> = {
101
+ [key: string]: ApiNode<T>;
102
+ };
103
+ type DynamicChainedProxy<T extends any[]> = ApiNode<T>;
104
+ declare function createAPIProxy<T extends ApiRoot<any[]>>(customCallingFunction?: (api: string[], args: any[], callingFunctionName: "call" | "callWithOptions" | "invoke" | "invokeWithOptions" | "onRequest" | "on" | "sendEvent") => any): T;
105
+ declare const APIProxy: ApiRoot<any[]>;
106
+
107
+ /** 客户端方法面(请求/响应叶子)。 */
108
+ interface ClientFuncFace<Args extends any[], Result> {
109
+ /** 1.x:resolve 完整 envelope;超时/远端错误 resolve null。 */
110
+ call(...args: Args): Promise<Response<Result>>;
111
+ callWithOptions(options: ClientCallOptions, ...args: Args): Promise<Response<Result>>;
112
+ /** v2:resolve result 本体;失败以 ApiBridgeError reject。 */
113
+ invoke(...args: Args): Promise<Result>;
114
+ invokeWithOptions(options: ClientCallOptions, ...args: Args): Promise<Result>;
115
+ }
116
+ /** 服务端方法面(请求/响应叶子)。 */
117
+ interface ServerFuncFace<Args extends any[], Result> {
118
+ onRequest(handler: (...args: Args) => Result | Promise<Result>): unknown;
119
+ }
120
+ /** 客户端事件面(事件叶子)。 */
121
+ interface ClientEventFace<EventName extends string, EventData> {
122
+ on(eventName: EventName, handler: (data: EventData) => void | Promise<void>): () => void;
123
+ }
124
+ /** 服务端事件面(事件叶子)。 */
125
+ interface ServerEventFace<EventName extends string, EventData> {
126
+ sendEvent(eventName: EventName, data: EventData): void | Promise<void>;
127
+ }
128
+ /** 消费方向视图:UI/服务消费者持有的类型。 */
129
+ type ApiClient<T> = {
130
+ [K in keyof T]: T[K] extends {
131
+ __kind?: "func";
132
+ call: (...args: infer A extends any[]) => Promise<Response<infer R>>;
133
+ } ? ClientFuncFace<A, R> : T[K] extends {
134
+ __kind?: "event";
135
+ on: (eventName: infer N extends string, handler: (data: infer D) => void | Promise<void>) => () => void;
136
+ } ? ClientEventFace<N, D> : ApiClient<T[K]>;
137
+ };
138
+ /** 提供方向视图:runtime/服务提供者持有的类型。 */
139
+ type ApiServer<T> = {
140
+ [K in keyof T]: T[K] extends {
141
+ __kind?: "func";
142
+ call: (...args: infer A extends any[]) => Promise<Response<infer R>>;
143
+ } ? ServerFuncFace<A, R> : T[K] extends {
144
+ __kind?: "event";
145
+ on: (eventName: infer N extends string, handler: (data: infer D) => void | Promise<void>) => () => void;
146
+ } ? ServerEventFace<N, D> : ApiServer<T[K]>;
147
+ };
148
+
149
+ type SendEventFunction = (event: Event) => void;
150
+ type SendMessageFunction = (response: Response) => void;
151
+ interface CreateAPIServerConfig {
152
+ sendEventFunc?: SendEventFunction;
153
+ sendMessageFunc?: SendMessageFunction;
154
+ debug?: boolean;
155
+ }
156
+ declare function createAPIServer<T extends ApiRoot<any[]>>(config: CreateAPIServerConfig): {
157
+ server: T;
158
+ serverHandler: (request: Request) => Promise<void>;
159
+ };
160
+
161
+ type RequestServerFunc = (request: Request) => Promise<any>;
162
+ interface CreateAPIClientConfig {
163
+ /** 是否打印调试信息,默认 false */
164
+ debug?: boolean;
165
+ requestServerFunc: RequestServerFunc;
166
+ }
167
+ declare function createAPIClient<T extends ApiRoot>(config: CreateAPIClientConfig): {
168
+ client: T;
169
+ serverEventHandler: (event: Event, config?: {
170
+ debug?: boolean;
171
+ }) => void;
172
+ clientMessageHandler: (message: Response) => void;
173
+ closeTransport: (reason?: string) => void;
174
+ };
175
+
176
+ /** 以 id 为键将消息推送到总线。 */
177
+ declare function sendMessage<T>(id: string | number, payload: T): void;
178
+ /**
179
+ * 订阅指定 id 的第一条消息。发出 payload,超时则发出 null,
180
+ * 随后完成(自动取消订阅)。
181
+ */
182
+ declare function subscribeToMessage<T = any>(targetId: string | number, timeoutDurationMs?: number): Observable<T | null>;
183
+ /** subscribeToMessage 的 async/await 封装。 */
184
+ declare function waitForMessage<T = any>(targetId: string | number, timeoutMs?: number): Promise<T | null>;
185
+
186
+ export { APIProxy, ApiBridgeError, type ApiBridgeErrorOptions, type ApiClient, type ApiRoot, type ApiServer, BRIDGE_ERROR_SUGGESTIONS, type BridgeErrorCode, type CallResult, type ClientCallOptions, type ClientEventFace, type ClientFuncFace, type DynamicChainedProxy, type Event, type EventListener, type FuncHandler, type Meta, type OnRequestHandler, type Request, type Response, type ServerEventFace, type ServerFuncFace, createAPIClient, createAPIProxy, createAPIServer, makeErrorResponse, sendMessage, subscribeToMessage, waitForMessage };
package/dist/index.js ADDED
@@ -0,0 +1,371 @@
1
+ // src/core/API.proxy.ts
2
+ function createAPIProxy(customCallingFunction) {
3
+ const createHandler = (path) => {
4
+ return {
5
+ get(target, propKey, receiver) {
6
+ if (typeof propKey === "string") {
7
+ switch (propKey) {
8
+ case "call":
9
+ case "callWithOptions":
10
+ case "invoke":
11
+ case "invokeWithOptions":
12
+ case "onRequest":
13
+ case "on":
14
+ case "sendEvent":
15
+ return (...args) => {
16
+ if (customCallingFunction && typeof customCallingFunction === "function") {
17
+ const callingResult = customCallingFunction(
18
+ path,
19
+ args,
20
+ propKey
21
+ );
22
+ if (callingResult) {
23
+ return callingResult;
24
+ }
25
+ }
26
+ return {
27
+ api: path,
28
+ args
29
+ };
30
+ };
31
+ default: {
32
+ const newPath = [...path, propKey];
33
+ return new Proxy({}, createHandler(newPath));
34
+ }
35
+ }
36
+ }
37
+ return Reflect.get(target, propKey, receiver);
38
+ }
39
+ };
40
+ };
41
+ return new Proxy({}, createHandler([]));
42
+ }
43
+ var APIProxy = createAPIProxy();
44
+
45
+ // src/core/errors.ts
46
+ var BRIDGE_ERROR_SUGGESTIONS = {
47
+ TARGET_UNAVAILABLE: "\u786E\u8BA4\u76EE\u6807\u63D2\u4EF6/\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u5904\u4E8E\u8FD0\u884C\u72B6\u6001\u540E\u91CD\u8BD5",
48
+ METHOD_NOT_FOUND: "\u68C0\u67E5 API \u540D\u79F0\u62FC\u5199\u4E0E\u76EE\u6807\u63D2\u4EF6\u7248\u672C\u662F\u5426\u63D0\u4F9B\u8BE5\u65B9\u6CD5",
49
+ REMOTE_HANDLER_ERROR: "\u67E5\u770B\u76EE\u6807 runtime \u65E5\u5FD7\u4E2D\u7684\u9519\u8BEF\u8BE6\u60C5",
50
+ TRANSPORT_CLOSED: "\u901A\u8BAF\u94FE\u8DEF\u5DF2\u5173\u95ED\uFF08runtime \u505C\u6B62/\u9875\u9762\u5378\u8F7D\uFF09\uFF0C\u91CD\u65B0\u5EFA\u7ACB\u540E\u91CD\u8BD5",
51
+ REQUEST_TIMEOUT: "\u786E\u8BA4\u5BF9\u7AEF\u5B58\u6D3B\u5E76\u9002\u5F53\u8C03\u5927 timeoutMs \u540E\u91CD\u8BD5"
52
+ };
53
+ var ApiBridgeError = class extends Error {
54
+ code;
55
+ /** 点分 API 路径,如 "station.runTest"。 */
56
+ apiPath;
57
+ requestId;
58
+ timeoutMs;
59
+ /** 建议动作(人可读,供 UI/日志展示)。 */
60
+ suggestion;
61
+ constructor(code, apiPath, message, options = {}) {
62
+ super(`[${code}] ${apiPath}: ${message}`);
63
+ this.name = "ApiBridgeError";
64
+ this.code = code;
65
+ this.apiPath = apiPath;
66
+ this.requestId = options.requestId;
67
+ this.timeoutMs = options.timeoutMs;
68
+ this.suggestion = options.suggestion ?? BRIDGE_ERROR_SUGGESTIONS[code];
69
+ }
70
+ };
71
+ function makeErrorResponse(request, code, message) {
72
+ return {
73
+ type: "response",
74
+ id: request.id ?? "",
75
+ result: void 0,
76
+ error: { code, message },
77
+ meta: { api: request.api, args: [] }
78
+ };
79
+ }
80
+
81
+ // src/core/API.server.ts
82
+ function createAPIServer(config) {
83
+ const { sendMessageFunc } = config;
84
+ const requestProcessorCache = {};
85
+ const server = createAPIProxy((api, args, callingFunctionName) => {
86
+ switch (callingFunctionName) {
87
+ case "onRequest": {
88
+ const processor = args[0];
89
+ if (processor && typeof processor === "function") {
90
+ requestProcessorCache[api.join(".")] = args[0];
91
+ } else {
92
+ console.error(
93
+ `[Error] register API Server processor error for ${JSON.stringify(
94
+ api
95
+ )}, handler is not a function.`
96
+ );
97
+ }
98
+ break;
99
+ }
100
+ case "sendEvent":
101
+ if (config.sendEventFunc) {
102
+ const event = {
103
+ type: "event",
104
+ name: args[0],
105
+ data: args[1],
106
+ meta: {
107
+ api,
108
+ args
109
+ }
110
+ };
111
+ config.sendEventFunc(event);
112
+ } else {
113
+ console.warn("[Warn] sendEventFunc is not defined.");
114
+ }
115
+ break;
116
+ default:
117
+ break;
118
+ }
119
+ });
120
+ async function serverHandler(request) {
121
+ const { id, api, args, context } = request;
122
+ if (!(api && args)) {
123
+ console.warn(
124
+ "[Warn] [Skip] API Server process, original message:",
125
+ request
126
+ );
127
+ return;
128
+ }
129
+ const apiName = api.join(".");
130
+ const wantsTypedErrors = request.wantsTypedErrors === true;
131
+ if (!requestProcessorCache[apiName]) {
132
+ if (wantsTypedErrors && sendMessageFunc && id) {
133
+ sendMessageFunc(
134
+ makeErrorResponse(request, "METHOD_NOT_FOUND", `no handler registered for API: ${apiName}`)
135
+ );
136
+ return;
137
+ }
138
+ console.warn(`[Warn] There is no handler registered for API: ${apiName}`);
139
+ return;
140
+ }
141
+ let result;
142
+ try {
143
+ result = await requestProcessorCache[apiName].apply(null, [
144
+ ...args,
145
+ context
146
+ ]);
147
+ } catch (err) {
148
+ if (wantsTypedErrors && sendMessageFunc && id) {
149
+ sendMessageFunc(
150
+ makeErrorResponse(
151
+ request,
152
+ "REMOTE_HANDLER_ERROR",
153
+ err instanceof Error ? err.message : String(err)
154
+ )
155
+ );
156
+ return;
157
+ }
158
+ throw err;
159
+ }
160
+ if (sendMessageFunc && id) {
161
+ sendMessageFunc({
162
+ type: "response",
163
+ id,
164
+ result,
165
+ meta: {
166
+ api,
167
+ // v2 响应不回显请求参数(尤其不回显 token/敏感 payload);
168
+ // 1.x 响应按原协议回显以保持兼容。
169
+ args: wantsTypedErrors ? [] : args,
170
+ context: wantsTypedErrors ? void 0 : context
171
+ }
172
+ });
173
+ }
174
+ }
175
+ return { server, serverHandler };
176
+ }
177
+
178
+ // src/core/API.client.ts
179
+ import { nanoid } from "nanoid/non-secure";
180
+
181
+ // src/utils/RxMessage.ts
182
+ import {
183
+ Subject,
184
+ filter,
185
+ firstValueFrom,
186
+ map,
187
+ of,
188
+ take,
189
+ timeout
190
+ } from "rxjs";
191
+ var messageDispatcher = new Subject();
192
+ function sendMessage(id, payload) {
193
+ const message = { id, payload };
194
+ messageDispatcher.next(message);
195
+ }
196
+ var DEFAULT_TIMEOUT_MS = 1e4;
197
+ function subscribeToMessage(targetId, timeoutDurationMs = DEFAULT_TIMEOUT_MS) {
198
+ return messageDispatcher.pipe(
199
+ filter((message) => message.id === targetId),
200
+ map((message) => message.payload),
201
+ timeout({
202
+ first: timeoutDurationMs,
203
+ with: () => of(null)
204
+ }),
205
+ take(1)
206
+ );
207
+ }
208
+ async function waitForMessage(targetId, timeoutMs) {
209
+ try {
210
+ const payload = await firstValueFrom(
211
+ subscribeToMessage(targetId, timeoutMs)
212
+ );
213
+ return payload;
214
+ } catch (error) {
215
+ console.error(`[AsyncWait] Error waiting for ID=${targetId}:`, error);
216
+ return null;
217
+ }
218
+ }
219
+
220
+ // src/core/API.client.ts
221
+ var DEFAULT_INVOKE_TIMEOUT_MS = 1e4;
222
+ function createAPIClient(config) {
223
+ const { debug = false, requestServerFunc } = config;
224
+ const eventHandlerCache = {};
225
+ const pendingInvokes = /* @__PURE__ */ new Map();
226
+ const client = createAPIProxy((api, args, callingFunctionName) => {
227
+ if (debug) {
228
+ console.info(
229
+ "[API Client] API:",
230
+ api,
231
+ "Args:",
232
+ args,
233
+ "callingFunctionName:",
234
+ callingFunctionName
235
+ );
236
+ }
237
+ switch (callingFunctionName) {
238
+ case "call":
239
+ case "callWithOptions": {
240
+ const id = nanoid();
241
+ const hasClientOptions = callingFunctionName === "callWithOptions";
242
+ const clientOptions = hasClientOptions ? args[0] ?? {} : void 0;
243
+ const requestArgs = hasClientOptions ? args.slice(1) : args;
244
+ const responseSub = waitForMessage(id, clientOptions?.timeoutMs);
245
+ return (async () => {
246
+ await requestServerFunc({
247
+ type: "request",
248
+ api,
249
+ args: requestArgs,
250
+ id
251
+ });
252
+ return responseSub;
253
+ })();
254
+ }
255
+ case "invoke":
256
+ case "invokeWithOptions": {
257
+ const id = nanoid();
258
+ const hasClientOptions = callingFunctionName === "invokeWithOptions";
259
+ const clientOptions = hasClientOptions ? args[0] ?? {} : {};
260
+ const requestArgs = hasClientOptions ? args.slice(1) : args;
261
+ const apiPath = api.join(".");
262
+ const timeoutMs = clientOptions.timeoutMs ?? DEFAULT_INVOKE_TIMEOUT_MS;
263
+ return new Promise((resolve, reject) => {
264
+ const timer = setTimeout(() => {
265
+ pendingInvokes.delete(id);
266
+ reject(
267
+ new ApiBridgeError(
268
+ "REQUEST_TIMEOUT",
269
+ apiPath,
270
+ `no response within ${timeoutMs}ms`,
271
+ { requestId: id, timeoutMs }
272
+ )
273
+ );
274
+ }, timeoutMs);
275
+ pendingInvokes.set(id, { resolve, reject, timer, apiPath });
276
+ Promise.resolve(
277
+ requestServerFunc({
278
+ type: "request",
279
+ api,
280
+ args: requestArgs,
281
+ id,
282
+ wantsTypedErrors: true
283
+ })
284
+ ).catch((err) => {
285
+ const pending = pendingInvokes.get(id);
286
+ if (!pending) return;
287
+ clearTimeout(pending.timer);
288
+ pendingInvokes.delete(id);
289
+ reject(
290
+ new ApiBridgeError(
291
+ "TARGET_UNAVAILABLE",
292
+ apiPath,
293
+ `transport send failed: ${err instanceof Error ? err.message : String(err)}`,
294
+ { requestId: id }
295
+ )
296
+ );
297
+ });
298
+ });
299
+ }
300
+ case "on": {
301
+ const apiName = api.join(".");
302
+ const key = `${apiName}:${args[0]}`;
303
+ const func = args[1];
304
+ eventHandlerCache[key] = func;
305
+ return () => {
306
+ delete eventHandlerCache[key];
307
+ };
308
+ }
309
+ default:
310
+ break;
311
+ }
312
+ });
313
+ function serverEventHandler(event, config2) {
314
+ const { name, data, meta } = event;
315
+ const { debug: debug2 = false } = config2 || {};
316
+ const key = `${meta.api.join(".")}:${name}`;
317
+ if (debug2) {
318
+ console.info("[API Client] Server Event:", key, "Data:", data);
319
+ }
320
+ if (key && eventHandlerCache[key]) {
321
+ eventHandlerCache[key](data);
322
+ }
323
+ if (eventHandlerCache["*"] && eventHandlerCache["*"] instanceof Function) {
324
+ eventHandlerCache["*"](data, event);
325
+ }
326
+ const eventKeyForTargetAPI = `${meta.api.join(".")}:*`;
327
+ if (eventHandlerCache[eventKeyForTargetAPI] && eventHandlerCache[eventKeyForTargetAPI] instanceof Function) {
328
+ eventHandlerCache[eventKeyForTargetAPI](data, event);
329
+ }
330
+ }
331
+ function clientMessageHandler(message) {
332
+ const pending = pendingInvokes.get(message.id);
333
+ if (pending) {
334
+ clearTimeout(pending.timer);
335
+ pendingInvokes.delete(message.id);
336
+ if (message.error) {
337
+ pending.reject(
338
+ new ApiBridgeError(message.error.code, pending.apiPath, message.error.message, {
339
+ requestId: message.id
340
+ })
341
+ );
342
+ } else {
343
+ pending.resolve(message.result);
344
+ }
345
+ return;
346
+ }
347
+ sendMessage(message.id, message);
348
+ }
349
+ function closeTransport(reason = "transport closed") {
350
+ for (const [id, pending] of pendingInvokes) {
351
+ clearTimeout(pending.timer);
352
+ pending.reject(
353
+ new ApiBridgeError("TRANSPORT_CLOSED", pending.apiPath, reason, { requestId: id })
354
+ );
355
+ }
356
+ pendingInvokes.clear();
357
+ }
358
+ return { client, serverEventHandler, clientMessageHandler, closeTransport };
359
+ }
360
+ export {
361
+ APIProxy,
362
+ ApiBridgeError,
363
+ BRIDGE_ERROR_SUGGESTIONS,
364
+ createAPIClient,
365
+ createAPIProxy,
366
+ createAPIServer,
367
+ makeErrorResponse,
368
+ sendMessage,
369
+ subscribeToMessage,
370
+ waitForMessage
371
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@international-iot-association/api-bridge",
3
+ "version": "3.0.0-rc.1",
4
+ "license": "MIT",
5
+ "publishConfig": {
6
+ "access": "public",
7
+ "registry": "https://registry.npmjs.org/"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/International-IoT-Association/MES.git",
12
+ "directory": "electron-station-plugins/packages/api-bridge"
13
+ },
14
+ "description": "Proxy-based type-safe RPC bridge (production-validated). v2: typed errors + invoke/invokeWithOptions; 1.x call() semantics preserved.",
15
+ "type": "module",
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.cjs"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "typecheck": "tsc --noEmit",
32
+ "test": "pnpm run build && node --test test/*.test.mjs",
33
+ "clean": "rimraf dist .turbo"
34
+ },
35
+ "dependencies": {
36
+ "nanoid": "^5.1.5",
37
+ "rxjs": "^7.8.2"
38
+ },
39
+ "devDependencies": {
40
+ "@international-iot-association/tsconfig": "1.0.0-rc.1",
41
+ "tsup": "^8.4.0",
42
+ "typescript": "^5.9.3"
43
+ },
44
+ "x-internal-version": "2.0.0",
45
+ "x-source-revision": "410a2f3b85ef03b678f04f22ea0a47718f5ddae1"
46
+ }