@kevisual/api 0.0.20 → 0.0.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevisual/api",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "",
5
5
  "main": "mod.ts",
6
6
  "scripts": {
@@ -1,490 +1,3 @@
1
- import { QueryClient as Query, Result } from '@kevisual/query';
2
- import { QueryRouterServer, Route } from '@kevisual/router/src/route.ts';
3
- import { filter } from '@kevisual/js-filter'
4
- import { EventEmitter } from 'eventemitter3';
1
+ export * from './proxy.ts';
5
2
 
6
- export const RouteTypeList = ['api', 'context', 'worker', 'page'] as const;
7
- export type RouterViewItemInfo = RouterViewApi | RouterViewContext | RouterViewWorker | RouteViewPage;
8
- export type RouterViewItem<T = {}> = RouterViewItemInfo & T;
9
-
10
- type RouteViewBase = {
11
- /**
12
- * _id 用于纯本地存储标识
13
- */
14
- _id?: string;
15
- id?: string;
16
- title?: string;
17
- description?: string;
18
- enabled?: boolean;
19
- /**
20
- * 响应数据
21
- */
22
- response?: any;
23
- /**
24
- * 默认动作配置
25
- */
26
- action?: { path?: string; key?: string; id?: string; payload?: any;[key: string]: any };
27
- }
28
- export type RouterViewApi = {
29
- type: 'api',
30
- api: {
31
- url: string,
32
- // 已初始化的query实例,不需要编辑配置
33
- query?: Query
34
- }
35
- } & RouteViewBase;
36
-
37
- export type RouterViewContext = {
38
- type: 'context',
39
- context: {
40
- key: string,
41
- // 从context中获取router,不需要编辑配置
42
- router?: QueryRouterServer
43
- }
44
- } & RouteViewBase;
45
- export type RouterViewWorker = {
46
- type: 'worker',
47
- worker: {
48
- type: 'Worker' | 'SharedWorker' | 'serviceWorker',
49
- url: string,
50
- // 已初始化的worker实例,不需要编辑配置
51
- worker?: Worker | SharedWorker | ServiceWorker,
52
- /**
53
- * worker选项
54
- * default: { type: 'module' }
55
- */
56
- workerOptions?: {
57
- type: 'module' | 'classic'
58
- }
59
- }
60
- } & RouteViewBase;
61
-
62
- /**
63
- * 去掉不需要保存都服务器的数据
64
- * @param item
65
- * @returns
66
- */
67
- export const pickRouterViewData = (item: RouterViewItem) => {
68
- const { action, response, _id, ...rest } = item;
69
- if (rest.type === 'api') {
70
- if (rest.api) {
71
- delete rest.api.query;
72
- }
73
- }
74
- if (rest.type === 'worker') {
75
- if (rest.worker) {
76
- delete rest.worker.worker;
77
- }
78
- }
79
- if (rest.type === 'context') {
80
- if (rest.context) {
81
- delete rest.context.router;
82
- }
83
- }
84
- return rest
85
- }
86
- /**
87
- * 注入 js 的url地址,使用importScripts加载
88
- */
89
- export type RouteViewPage = {
90
- type: 'page',
91
- page: {
92
- url: string,
93
- }
94
- } & RouteViewBase;
95
-
96
- export type RouterViewQuery = {
97
- id: string,
98
- query: string,
99
- title: string
100
- }
101
- export type RouterViewData = {
102
- data: { items: RouterViewItem[]; }
103
- views: RouterViewQuery[];
104
- viewId?: string;
105
- [key: string]: any;
106
- }
107
-
108
- export class QueryProxy {
109
- router: QueryRouterServer;
110
- token?: string;
111
- routerViewItems: RouterViewItem[];
112
- views: RouterViewQuery[];
113
- emitter: EventEmitter;
114
- constructor(opts?: { router?: QueryRouterServer, token?: string, routerViewData?: RouterViewData }) {
115
- this.router = opts?.router || new QueryRouterServer();
116
- this.token = opts?.token || this.getDefulatToken();
117
- this.routerViewItems = opts?.routerViewData?.data?.items || [];
118
- this.views = opts?.routerViewData?.views || [];
119
- this.initRouterViewQuery();
120
- this.emitter = new EventEmitter();
121
- }
122
- getDefulatToken() {
123
- try {
124
- if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {
125
- return localStorage.getItem('token') || undefined;
126
- }
127
- } catch (e) {
128
- return undefined;
129
- }
130
- }
131
- initRouterViewQuery() {
132
- this.routerViewItems = this.routerViewItems.map(item => {
133
- return this.initRouterView(item);
134
- }).filter(item => {
135
- const enabled = item.enabled ?? true;
136
- return enabled;
137
- });
138
- }
139
-
140
- initRouterView(item: RouterViewItem) {
141
- if (item.type === 'api' && item.api?.url) {
142
- const url = item.api.url;
143
- if (item?.api?.query) return item;
144
- const query = new Query({ url: url });
145
- item['api'] = { url: url, query: query };
146
- }
147
- if (item.type === 'worker' && item.worker?.url) {
148
- let viewItem = item as RouterViewWorker;
149
- if (!item.worker?.workerOptions?.type) {
150
- item.worker.workerOptions = { ...item.worker.workerOptions, type: 'module' };
151
- }
152
- if (item.worker.worker) {
153
- return item;
154
- }
155
- let worker: Worker | SharedWorker | ServiceWorker | undefined = undefined;
156
- if (item.worker.type === 'SharedWorker') {
157
- worker = new SharedWorker(item.worker.url, item.worker.workerOptions);
158
- worker.port.start();
159
- } else if (viewItem.worker.type === 'serviceWorker') {
160
- if ('serviceWorker' in navigator) {
161
- navigator.serviceWorker.register(viewItem.worker.url, item.worker.workerOptions).then(function (registration) {
162
- console.debug('注册serviceWorker成功 ', registration.scope);
163
- }, function (err) {
164
- console.debug('注册 serviceWorker 失败: ', err);
165
- });
166
- } else {
167
- console.warn('当前浏览器不支持serviceWorker');
168
- }
169
- } else {
170
- worker = new Worker(viewItem.worker.url, item.worker.workerOptions);
171
- }
172
- viewItem['worker']['worker'] = worker;
173
- }
174
- if (item.type === 'context' && item.context?.key) {
175
- if (item.context?.router) {
176
- return item;
177
- }
178
- // @ts-ignore
179
- const context = globalThis['context'] || {}
180
- const router = context[item.context.key] as QueryRouterServer;
181
- if (router) {
182
- item['context']['router'] = router;
183
- }
184
- }
185
- return item;
186
-
187
- }
188
-
189
- /**
190
- * 初始化路由
191
- * @returns
192
- */
193
- async init() {
194
- const routerViewItems = this.routerViewItems || [];
195
- if (routerViewItems.length === 0) {
196
- // 默认初始化api类型路由
197
- await this.initApi();
198
- return;
199
- }
200
- for (const item of routerViewItems) {
201
- switch (item.type) {
202
- case 'api':
203
- await this.initApi(item);
204
- break;
205
- case 'context':
206
- await this.initContext(item);
207
- break;
208
- case 'worker':
209
- await this.initWorker(item);
210
- break;
211
- case 'page':
212
- await this.initPage(item);
213
- break;
214
- }
215
- }
216
- this.emitter.emit('initComplete');
217
- }
218
- /**
219
- * 监听初始化完成
220
- * @returns
221
- */
222
- async listenInitComplete(): Promise<boolean> {
223
- return new Promise((resolve) => {
224
- const timer = setTimeout(() => {
225
- this.emitter.removeAllListeners('initComplete');
226
- resolve(false);
227
- }, 3 * 60000); // 3分钟超时
228
- const func = () => {
229
- clearTimeout(timer);
230
- resolve(true);
231
- }
232
- this.emitter.once('initComplete', func);
233
- });
234
- }
235
- async initApi(item?: RouterViewApi) {
236
- const that = this;
237
- const query = item?.api?.query || new Query({ url: item?.api?.url || '/api/router' })
238
- const res = await query.post<{ list: RouterItem[] }>({ path: "router", key: 'list', token: this.token });
239
- if (res.code !== 200) {
240
- console.error('Failed to init query proxy router:', res.message);
241
- return
242
- }
243
- const _list = res.data?.list || []
244
- for (const r of _list) {
245
- if (r.path || r.id) {
246
- console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API');
247
- let metadata = r.metadata || {};
248
- metadata.viewItem = item;
249
- this.router.route({
250
- path: r.path,
251
- key: r.key || '',
252
- id: r.id,
253
- description: r.description,
254
- metadata: metadata,
255
- }).define(async (ctx) => {
256
- const msg = { ...ctx.query };
257
- if (msg.token === undefined && that.token !== undefined) {
258
- msg.token = that.token;
259
- }
260
- const res = await query.post<any>({ path: r.path, key: r.key, ...msg });
261
- ctx.forward(res)
262
- }).addTo(that.router);
263
- }
264
- }
265
- }
266
- async initContext(item?: RouterViewContext) {
267
- // @ts-ignore
268
- const context = globalThis['context'] || {}
269
- const router = item?.context?.router || context[item?.context?.key] as QueryRouterServer;
270
- if (!router) {
271
- console.warn(`未发现Context router ${item?.context?.key}`);
272
- return
273
- }
274
- const routes = router.getList();
275
- for (const r of routes) {
276
- console.debug(`注册路由: [${r.path}] ${r?.key}`, 'Context');
277
- let metadata = r.metadata || {};
278
- metadata.viewItem = item;
279
- this.router.route({
280
- path: r.path,
281
- key: r.key || '',
282
- id: r.id,
283
- description: r.description,
284
- metadata: metadata,
285
- }).define(async (ctx) => {
286
- const res = await router.run({ path: r.path, key: r.key, ...ctx.query });
287
- ctx.forward(res)
288
- }).addTo(this.router);
289
- }
290
- }
291
- generateId() {
292
- return 'route_' + Math.random().toString(36).substring(2, 9);
293
- }
294
- async callWorker(msg: any, viewItem: RouterViewWorker['worker']): Promise<Result> {
295
- const that = this;
296
- const requestId = this.generateId();
297
- const worker = viewItem?.worker;
298
- if (!worker) {
299
- return { code: 500, message: 'Worker未初始化' };
300
- }
301
- let port: MessagePort | Worker | ServiceWorker;
302
- if (viewItem.type === 'SharedWorker') {
303
- port = (worker as SharedWorker).port;
304
- } else {
305
- port = worker as Worker | ServiceWorker;
306
- }
307
- port.postMessage({
308
- ...msg,
309
- requestId: requestId,
310
- })
311
- return new Promise((resolve) => {
312
- const timer = setTimeout(() => {
313
- that.emitter.removeAllListeners(requestId);
314
- resolve({ code: 500, message: '请求超时' });
315
- }, 3 * 60 * 1000); // 3分钟超时
316
- that.emitter.once(requestId, (res: any) => {
317
- clearTimeout(timer);
318
- resolve(res);
319
- });
320
- });
321
- }
322
- async initWorker(item?: RouterViewWorker, initRoutes: boolean = true) {
323
- const that = this;
324
- if (!item?.worker?.url) {
325
- console.warn('Worker URL not provided');
326
- return;
327
- }
328
- const viewItem = item.worker;
329
- const worker = viewItem?.worker;
330
- if (!worker) {
331
- console.warn('Worker not initialized');
332
- return;
333
- }
334
- const callResponse = (e: MessageEvent) => {
335
- const msg = e.data;
336
- if (msg.requestId) {
337
- const requestId = msg.requestId;
338
- that.emitter.emit(requestId, msg);
339
- } else {
340
- that.router.run(msg);
341
- }
342
- }
343
- if (item.worker.type === 'SharedWorker') {
344
- const port = (worker as SharedWorker).port;
345
- port.onmessage = callResponse;
346
- port.start();
347
- } else if (item.worker.type === 'serviceWorker') {
348
- navigator.serviceWorker.addEventListener('message', callResponse);
349
- } else {
350
- (worker as Worker).onmessage = callResponse;
351
- }
352
- if (!initRoutes) {
353
- return;
354
- }
355
- const callWorker = this.callWorker.bind(this);
356
-
357
- const res = await callWorker({
358
- path: "router",
359
- key: 'list',
360
- token: this.token,
361
- }, viewItem);
362
-
363
- if (res.code !== 200) {
364
- console.error('Failed to init query proxy router:', res.message);
365
- return;
366
- }
367
- const _list = res.data?.list || []
368
- for (const r of _list) {
369
- if (r.path || r.id) {
370
- console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API');
371
- let metadata = r.metadata || {};
372
- metadata.viewItem = item;
373
- this.router.route({
374
- path: r.path,
375
- key: r.key || '',
376
- id: r.id,
377
- description: r.description,
378
- metadata: metadata,
379
- }).define(async (ctx) => {
380
- const msg = { ...ctx.query };
381
- if (msg.token === undefined && that.token !== undefined) {
382
- msg.token = that.token;
383
- }
384
- const res = await callWorker({ path: r.path, key: r.key, ...msg }, viewItem);
385
- ctx.forward(res)
386
- }).addTo(that.router);
387
- }
388
- }
389
- }
390
- async initPage(item?: RouteViewPage) {
391
- if (!item?.page?.url) {
392
- console.warn('Page地址未提供');
393
- return;
394
- }
395
- const url = item.page.url;
396
- try {
397
- if (typeof window !== 'undefined') {
398
- await import(url).then((module) => { }).catch((err) => {
399
- console.error('引入Page脚本失败:', url, err);
400
- });
401
- }
402
- } catch (e) {
403
- console.warn('引入Page脚本失败:', url, e);
404
- return;
405
- }
406
- }
407
- /**
408
- * 列出路由
409
- * @param filter
410
- * @param query WHERE metadata.tags CONTAINS 'premium'
411
- * @returns
412
- */
413
- async listRoutes(filterFn?: (item: Route) => boolean, opts?: { viewId?: string, query?: string }) {
414
- let query = opts?.query;
415
- if (opts?.viewId) {
416
- const view = this.views.find(v => v.id === opts.viewId);
417
- if (view) {
418
- query = view.query;
419
- }
420
- } if (opts?.query) {
421
- query = opts.query;
422
- }
423
- const routes = this.router.routes.filter(filterFn || (() => true));
424
- if (query) {
425
- return filter(routes, query);
426
- }
427
- return routes;
428
- }
429
- async getViewQuery(viewId: string) {
430
- const view = this.views.find(v => v.id === viewId);
431
- if (view) {
432
- return view.query;
433
- }
434
- return undefined;
435
- }
436
- /**
437
- * 运行路由
438
- * @param msg
439
- * @returns
440
- */
441
- async run(msg: { id?: string, path?: string, key?: string }) {
442
- return await this.router.run(msg);
443
- }
444
-
445
- async runByRouteView(routeView: RouterViewItem) {
446
- if (routeView.response) {
447
- return routeView;
448
- }
449
- const item = this.initRouterView(routeView);
450
- if (item.type === 'api' && item.api?.url) {
451
- const query = item.api.query!;
452
- const res = await query.post<any>(item.action || {});
453
- item.response = res;
454
- return item;
455
- } else if (item.type === 'api') {
456
- item.response = { code: 500, message: 'API URL未配置' };
457
- return item;
458
- }
459
- if (item.type === 'context' && item.context?.router) {
460
- const router = item.context.router;
461
- const res = await router.run(item.action || {});
462
- item.response = res;
463
- return item;
464
- }
465
- if (item.type === 'page') {
466
- await this.initPage(item);
467
- const res = await this.router.run(item.action || {});
468
- item.response = res;
469
- return item;
470
- }
471
- if (item.type === 'worker' && item.worker?.worker) {
472
- await this.initWorker(item, false);
473
- const callWorker = this.callWorker.bind(this);
474
- const res = await callWorker(item.action || {}, item.worker);
475
- item.response = res;
476
- return item;
477
- }
478
- item.response = { code: 500, message: '无法处理的路由类型' };
479
- return item;
480
- }
481
- }
482
-
483
- type RouterItem = {
484
- id?: string;
485
- path?: string;
486
- key?: string;
487
- description?: string;
488
- middleware?: string[];
489
- metadata?: Record<string, any>;
490
- }
3
+ export { initApi } from './router-api-proxy.ts';
@@ -0,0 +1,463 @@
1
+ import { QueryClient as Query, Result } from '@kevisual/query';
2
+ import { QueryRouterServer, App, Route } from '@kevisual/router';
3
+ import { filter } from '@kevisual/js-filter'
4
+ import { EventEmitter } from 'eventemitter3';
5
+ import { initApi } from './router-api-proxy';
6
+
7
+ export const RouteTypeList = ['api', 'context', 'worker', 'page'] as const;
8
+ export type RouterViewItemInfo = RouterViewApi | RouterViewContext | RouterViewWorker | RouteViewPage;
9
+ export type RouterViewItem<T = {}> = RouterViewItemInfo & T;
10
+
11
+ type RouteViewBase = {
12
+ /**
13
+ * _id 用于纯本地存储标识
14
+ */
15
+ _id?: string;
16
+ id?: string;
17
+ title?: string;
18
+ description?: string;
19
+ enabled?: boolean;
20
+ /**
21
+ * 响应数据
22
+ */
23
+ response?: any;
24
+ /**
25
+ * 默认动作配置
26
+ */
27
+ action?: { path?: string; key?: string; id?: string; payload?: any;[key: string]: any };
28
+ }
29
+ export type RouterViewApi = {
30
+ type: 'api',
31
+ api: {
32
+ url: string,
33
+ // 已初始化的query实例,不需要编辑配置
34
+ query?: Query
35
+ }
36
+ } & RouteViewBase;
37
+
38
+ export type RouterViewContext = {
39
+ type: 'context',
40
+ context: {
41
+ key: string,
42
+ // 从context中获取router,不需要编辑配置
43
+ router?: QueryRouterServer
44
+ }
45
+ } & RouteViewBase;
46
+ export type RouterViewWorker = {
47
+ type: 'worker',
48
+ worker: {
49
+ type: 'Worker' | 'SharedWorker' | 'serviceWorker',
50
+ url: string,
51
+ // 已初始化的worker实例,不需要编辑配置
52
+ worker?: Worker | SharedWorker | ServiceWorker,
53
+ /**
54
+ * worker选项
55
+ * default: { type: 'module' }
56
+ */
57
+ workerOptions?: {
58
+ type: 'module' | 'classic'
59
+ }
60
+ }
61
+ } & RouteViewBase;
62
+
63
+ /**
64
+ * 去掉不需要保存都服务器的数据
65
+ * @param item
66
+ * @returns
67
+ */
68
+ export const pickRouterViewData = (item: RouterViewItem) => {
69
+ const { action, response, _id, ...rest } = item;
70
+ if (rest.type === 'api') {
71
+ if (rest.api) {
72
+ delete rest.api.query;
73
+ }
74
+ }
75
+ if (rest.type === 'worker') {
76
+ if (rest.worker) {
77
+ delete rest.worker.worker;
78
+ }
79
+ }
80
+ if (rest.type === 'context') {
81
+ if (rest.context) {
82
+ delete rest.context.router;
83
+ }
84
+ }
85
+ return rest
86
+ }
87
+ /**
88
+ * 注入 js 的url地址,使用importScripts加载
89
+ */
90
+ export type RouteViewPage = {
91
+ type: 'page',
92
+ page: {
93
+ url: string,
94
+ }
95
+ } & RouteViewBase;
96
+
97
+ export type RouterViewQuery = {
98
+ id: string,
99
+ query: string,
100
+ title: string
101
+ }
102
+ export type RouterViewData = {
103
+ data: { items: RouterViewItem[]; }
104
+ views: RouterViewQuery[];
105
+ viewId?: string;
106
+ [key: string]: any;
107
+ }
108
+
109
+ export class QueryProxy {
110
+ router: QueryRouterServer;
111
+ token?: string;
112
+ routerViewItems: RouterViewItem[];
113
+ views: RouterViewQuery[];
114
+ emitter: EventEmitter;
115
+ constructor(opts?: { router?: QueryRouterServer, token?: string, routerViewData?: RouterViewData }) {
116
+ this.router = opts?.router || new QueryRouterServer();
117
+ this.token = opts?.token || this.getDefulatToken();
118
+ this.routerViewItems = opts?.routerViewData?.data?.items || [];
119
+ this.views = opts?.routerViewData?.views || [];
120
+ this.initRouterViewQuery();
121
+ this.emitter = new EventEmitter();
122
+ }
123
+ getDefulatToken() {
124
+ try {
125
+ if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {
126
+ return localStorage.getItem('token') || undefined;
127
+ }
128
+ } catch (e) {
129
+ return undefined;
130
+ }
131
+ }
132
+ initRouterViewQuery() {
133
+ this.routerViewItems = this.routerViewItems.map(item => {
134
+ return this.initRouterView(item);
135
+ }).filter(item => {
136
+ const enabled = item.enabled ?? true;
137
+ return enabled;
138
+ });
139
+ }
140
+
141
+ initRouterView(item: RouterViewItem) {
142
+ if (item.type === 'api' && item.api?.url) {
143
+ const url = item.api.url;
144
+ if (item?.api?.query) return item;
145
+ const query = new Query({ url: url });
146
+ item['api'] = { url: url, query: query };
147
+ }
148
+ if (item.type === 'worker' && item.worker?.url) {
149
+ let viewItem = item as RouterViewWorker;
150
+ if (!item.worker?.workerOptions?.type) {
151
+ item.worker.workerOptions = { ...item.worker.workerOptions, type: 'module' };
152
+ }
153
+ if (item.worker.worker) {
154
+ return item;
155
+ }
156
+ let worker: Worker | SharedWorker | ServiceWorker | undefined = undefined;
157
+ if (item.worker.type === 'SharedWorker') {
158
+ worker = new SharedWorker(item.worker.url, item.worker.workerOptions);
159
+ worker.port.start();
160
+ } else if (viewItem.worker.type === 'serviceWorker') {
161
+ if ('serviceWorker' in navigator) {
162
+ navigator.serviceWorker.register(viewItem.worker.url, item.worker.workerOptions).then(function (registration) {
163
+ console.debug('注册serviceWorker成功 ', registration.scope);
164
+ }, function (err) {
165
+ console.debug('注册 serviceWorker 失败: ', err);
166
+ });
167
+ } else {
168
+ console.warn('当前浏览器不支持serviceWorker');
169
+ }
170
+ } else {
171
+ worker = new Worker(viewItem.worker.url, item.worker.workerOptions);
172
+ }
173
+ viewItem['worker']['worker'] = worker;
174
+ }
175
+ if (item.type === 'context' && item.context?.key) {
176
+ if (item.context?.router) {
177
+ return item;
178
+ }
179
+ // @ts-ignore
180
+ const context = globalThis['context'] || {}
181
+ const router = context[item.context.key] as QueryRouterServer;
182
+ if (router) {
183
+ item['context']['router'] = router;
184
+ }
185
+ }
186
+ return item;
187
+
188
+ }
189
+
190
+ /**
191
+ * 初始化路由
192
+ * @returns
193
+ */
194
+ async init() {
195
+ const routerViewItems = this.routerViewItems || [];
196
+ if (routerViewItems.length === 0) {
197
+ // 默认初始化api类型路由
198
+ await this.initApi();
199
+ return;
200
+ }
201
+ for (const item of routerViewItems) {
202
+ switch (item.type) {
203
+ case 'api':
204
+ await this.initApi(item);
205
+ break;
206
+ case 'context':
207
+ await this.initContext(item);
208
+ break;
209
+ case 'worker':
210
+ await this.initWorker(item);
211
+ break;
212
+ case 'page':
213
+ await this.initPage(item);
214
+ break;
215
+ }
216
+ }
217
+ this.emitter.emit('initComplete');
218
+ }
219
+ /**
220
+ * 监听初始化完成
221
+ * @returns
222
+ */
223
+ async listenInitComplete(): Promise<boolean> {
224
+ return new Promise((resolve) => {
225
+ const timer = setTimeout(() => {
226
+ this.emitter.removeAllListeners('initComplete');
227
+ resolve(false);
228
+ }, 3 * 60000); // 3分钟超时
229
+ const func = () => {
230
+ clearTimeout(timer);
231
+ resolve(true);
232
+ }
233
+ this.emitter.once('initComplete', func);
234
+ });
235
+ }
236
+ async initApi(item?: RouterViewApi) {
237
+ initApi({ item: item, router: this.router, token: this.token });
238
+ }
239
+ async initContext(item?: RouterViewContext) {
240
+ // @ts-ignore
241
+ const context = globalThis['context'] || {}
242
+ const router = item?.context?.router || context[item?.context?.key] as QueryRouterServer;
243
+ if (!router) {
244
+ console.warn(`未发现Context router ${item?.context?.key}`);
245
+ return
246
+ }
247
+ const routes = router.getList();
248
+ for (const r of routes) {
249
+ console.debug(`注册路由: [${r.path}] ${r?.key}`, 'Context');
250
+ let metadata = r.metadata || {};
251
+ metadata.viewItem = item;
252
+ this.router.route({
253
+ path: r.path,
254
+ key: r.key || '',
255
+ id: r.id,
256
+ description: r.description,
257
+ metadata: metadata,
258
+ }).define(async (ctx) => {
259
+ const res = await router.run({ path: r.path, key: r.key, ...ctx.query });
260
+ ctx.forward(res)
261
+ }).addTo(this.router);
262
+ }
263
+ }
264
+ generateId() {
265
+ return 'route_' + Math.random().toString(36).substring(2, 9);
266
+ }
267
+ async callWorker(msg: any, viewItem: RouterViewWorker['worker']): Promise<Result> {
268
+ const that = this;
269
+ const requestId = this.generateId();
270
+ const worker = viewItem?.worker;
271
+ if (!worker) {
272
+ return { code: 500, message: 'Worker未初始化' };
273
+ }
274
+ let port: MessagePort | Worker | ServiceWorker;
275
+ if (viewItem.type === 'SharedWorker') {
276
+ port = (worker as SharedWorker).port;
277
+ } else {
278
+ port = worker as Worker | ServiceWorker;
279
+ }
280
+ port.postMessage({
281
+ ...msg,
282
+ requestId: requestId,
283
+ })
284
+ return new Promise((resolve) => {
285
+ const timer = setTimeout(() => {
286
+ that.emitter.removeAllListeners(requestId);
287
+ resolve({ code: 500, message: '请求超时' });
288
+ }, 3 * 60 * 1000); // 3分钟超时
289
+ that.emitter.once(requestId, (res: any) => {
290
+ clearTimeout(timer);
291
+ resolve(res);
292
+ });
293
+ });
294
+ }
295
+ async initWorker(item?: RouterViewWorker, initRoutes: boolean = true) {
296
+ const that = this;
297
+ if (!item?.worker?.url) {
298
+ console.warn('Worker URL not provided');
299
+ return;
300
+ }
301
+ const viewItem = item.worker;
302
+ const worker = viewItem?.worker;
303
+ if (!worker) {
304
+ console.warn('Worker not initialized');
305
+ return;
306
+ }
307
+ const callResponse = (e: MessageEvent) => {
308
+ const msg = e.data;
309
+ if (msg.requestId) {
310
+ const requestId = msg.requestId;
311
+ that.emitter.emit(requestId, msg);
312
+ } else {
313
+ that.router.run(msg);
314
+ }
315
+ }
316
+ if (item.worker.type === 'SharedWorker') {
317
+ const port = (worker as SharedWorker).port;
318
+ port.onmessage = callResponse;
319
+ port.start();
320
+ } else if (item.worker.type === 'serviceWorker') {
321
+ navigator.serviceWorker.addEventListener('message', callResponse);
322
+ } else {
323
+ (worker as Worker).onmessage = callResponse;
324
+ }
325
+ if (!initRoutes) {
326
+ return;
327
+ }
328
+ const callWorker = this.callWorker.bind(this);
329
+
330
+ const res = await callWorker({
331
+ path: "router",
332
+ key: 'list',
333
+ token: this.token,
334
+ }, viewItem);
335
+
336
+ if (res.code !== 200) {
337
+ console.error('Failed to init query proxy router:', res.message);
338
+ return;
339
+ }
340
+ const _list = res.data?.list || []
341
+ for (const r of _list) {
342
+ if (r.path || r.id) {
343
+ console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API');
344
+ let metadata = r.metadata || {};
345
+ metadata.viewItem = item;
346
+ this.router.route({
347
+ path: r.path,
348
+ key: r.key || '',
349
+ id: r.id,
350
+ description: r.description,
351
+ metadata: metadata,
352
+ }).define(async (ctx) => {
353
+ const msg = { ...ctx.query };
354
+ if (msg.token === undefined && that.token !== undefined) {
355
+ msg.token = that.token;
356
+ }
357
+ const res = await callWorker({ path: r.path, key: r.key, ...msg }, viewItem);
358
+ ctx.forward(res)
359
+ }).addTo(that.router);
360
+ }
361
+ }
362
+ }
363
+ async initPage(item?: RouteViewPage) {
364
+ if (!item?.page?.url) {
365
+ console.warn('Page地址未提供');
366
+ return;
367
+ }
368
+ const url = item.page.url;
369
+ try {
370
+ if (typeof window !== 'undefined') {
371
+ await import(url).then((module) => { }).catch((err) => {
372
+ console.error('引入Page脚本失败:', url, err);
373
+ });
374
+ }
375
+ } catch (e) {
376
+ console.warn('引入Page脚本失败:', url, e);
377
+ return;
378
+ }
379
+ }
380
+ /**
381
+ * 列出路由
382
+ * @param filter
383
+ * @param query WHERE metadata.tags CONTAINS 'premium'
384
+ * @returns
385
+ */
386
+ async listRoutes(filterFn?: (item: Route) => boolean, opts?: { viewId?: string, query?: string }) {
387
+ let query = opts?.query;
388
+ if (opts?.viewId) {
389
+ const view = this.views.find(v => v.id === opts.viewId);
390
+ if (view) {
391
+ query = view.query;
392
+ }
393
+ } if (opts?.query) {
394
+ query = opts.query;
395
+ }
396
+ const routes = this.router.routes.filter(filterFn || (() => true));
397
+ if (query) {
398
+ return filter(routes, query);
399
+ }
400
+ return routes;
401
+ }
402
+ async getViewQuery(viewId: string) {
403
+ const view = this.views.find(v => v.id === viewId);
404
+ if (view) {
405
+ return view.query;
406
+ }
407
+ return undefined;
408
+ }
409
+ /**
410
+ * 运行路由
411
+ * @param msg
412
+ * @returns
413
+ */
414
+ async run(msg: { id?: string, path?: string, key?: string }) {
415
+ return await this.router.run(msg);
416
+ }
417
+
418
+ async runByRouteView(routeView: RouterViewItem) {
419
+ if (routeView.response) {
420
+ return routeView;
421
+ }
422
+ const item = this.initRouterView(routeView);
423
+ if (item.type === 'api' && item.api?.url) {
424
+ const query = item.api.query!;
425
+ const res = await query.post<any>(item.action || {});
426
+ item.response = res;
427
+ return item;
428
+ } else if (item.type === 'api') {
429
+ item.response = { code: 500, message: 'API URL未配置' };
430
+ return item;
431
+ }
432
+ if (item.type === 'context' && item.context?.router) {
433
+ const router = item.context.router;
434
+ const res = await router.run(item.action || {});
435
+ item.response = res;
436
+ return item;
437
+ }
438
+ if (item.type === 'page') {
439
+ await this.initPage(item);
440
+ const res = await this.router.run(item.action || {});
441
+ item.response = res;
442
+ return item;
443
+ }
444
+ if (item.type === 'worker' && item.worker?.worker) {
445
+ await this.initWorker(item, false);
446
+ const callWorker = this.callWorker.bind(this);
447
+ const res = await callWorker(item.action || {}, item.worker);
448
+ item.response = res;
449
+ return item;
450
+ }
451
+ item.response = { code: 500, message: '无法处理的路由类型' };
452
+ return item;
453
+ }
454
+ }
455
+
456
+ export type RouterItem = {
457
+ id?: string;
458
+ path?: string;
459
+ key?: string;
460
+ description?: string;
461
+ middleware?: string[];
462
+ metadata?: Record<string, any>;
463
+ }
@@ -0,0 +1,48 @@
1
+ import { Query } from "@kevisual/query";
2
+ import { RouterViewApi, RouterItem } from ".";
3
+ import { App, type QueryRouterServer } from "@kevisual/router";
4
+ import { filter } from "@kevisual/js-filter";
5
+ export const initApi = async (opts: {
6
+ item?: RouterViewApi,
7
+ router: QueryRouterServer | App,
8
+ token?: string,
9
+ /**
10
+ * WHERE path = 'auth' OR path = 'router'
11
+ */
12
+ exclude?: string;
13
+ }) => {
14
+ const router = opts?.router! as QueryRouterServer;
15
+ const item = opts?.item;
16
+ const token = opts?.token;
17
+ const query = item?.api?.query || new Query({ url: item?.api?.url || '/api/router' })
18
+ const res = await query.post<{ list: RouterItem[] }>({ path: "router", key: 'list', token: token });
19
+ if (res.code !== 200) {
20
+ console.error('初始化路由失败:', query.url, res.message);
21
+ return
22
+ }
23
+ let _list = res.data?.list || []
24
+ if (opts?.exclude) {
25
+ _list = filter(_list, opts.exclude);
26
+ }
27
+ for (const r of _list) {
28
+ if (r.path || r.id) {
29
+ console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API');
30
+ let metadata = r.metadata || {};
31
+ metadata.viewItem = item;
32
+ router.route({
33
+ path: r.path,
34
+ key: r.key || '',
35
+ id: r.id,
36
+ description: r.description,
37
+ metadata: metadata,
38
+ }).define(async (ctx) => {
39
+ const msg = { ...ctx.query };
40
+ if (msg.token === undefined && token !== undefined) {
41
+ msg.token = token;
42
+ }
43
+ const res = await query.post<any>({ path: r.path, key: r.key, ...msg });
44
+ ctx.forward(res)
45
+ }).addTo(router);
46
+ }
47
+ }
48
+ }