@kevisual/api 0.0.10 → 0.0.12

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.10",
3
+ "version": "0.0.12",
4
4
  "description": "",
5
5
  "main": "mod.ts",
6
6
  "scripts": {
@@ -35,6 +35,7 @@
35
35
  "@kevisual/js-filter": "^0.0.2",
36
36
  "@kevisual/load": "^0.0.6",
37
37
  "es-toolkit": "^1.43.0",
38
+ "eventemitter3": "^5.0.1",
38
39
  "nanoid": "^5.1.6"
39
40
  },
40
41
  "exports": {
@@ -1,67 +1,275 @@
1
- import { Query } from '@kevisual/query/query';
1
+ import { Query, Result } from '@kevisual/query/query';
2
2
  import { QueryRouterServer, Route } from '@kevisual/router/src/route.ts';
3
3
  import { filter } from '@kevisual/js-filter'
4
- export type ProxyItem = {
5
- title?: string;
6
- type?: 'api' | 'context' | 'page';
7
- description?: string;
8
- api?: {
9
- url: string;
10
- },
11
- context?: {
12
- key: string;
13
- },
14
- page?: {},
15
- where?: string;
16
- whereList?: Array<{ title: string; where: string }>;
4
+ import { EventEmitter } from 'eventemitter3';
5
+
6
+ export type RouterViewItem = RouterViewApi | RouterViewContext | RouterViewWorker;
7
+ export type RouterViewApi = {
8
+ title: string;
9
+ description: string;
10
+ type: 'api',
11
+ api: {
12
+ url: string,
13
+ // 已初始化的query实例
14
+ query?: Query
15
+ }
16
+ }
17
+
18
+ export type RouterViewContext = {
19
+ title: string;
20
+ description: string;
21
+ type: 'context',
22
+ context: {
23
+ key: string,
24
+ // 从context中获取router
25
+ router?: QueryRouterServer
26
+ }
27
+ }
28
+ export type RouterViewWorker = {
29
+ title: string;
30
+ description: string;
31
+ type: 'worker',
32
+ worker: {
33
+ type: 'Worker' | 'SharedWorker' | 'serviceWorker',
34
+ url: string,
35
+ // 已初始化的worker实例
36
+ worker?: Worker | SharedWorker | ServiceWorker
37
+ }
38
+ }
39
+ export type RouterViewQuery = {
40
+ id: string,
41
+ query: string,
42
+ title: string
43
+ }
44
+ export type RouterViewData = {
45
+ data: { items: RouterViewItem[]; }
46
+ views: RouterViewQuery[];
47
+ viewId?: string;
48
+ [key: string]: any;
17
49
  }
18
50
 
19
51
  export class QueryProxy {
20
- query: Query;
21
52
  router: QueryRouterServer;
22
53
  token?: string;
23
- constructor(opts?: { query: Query, router?: QueryRouterServer, token?: string }) {
24
- this.query = opts?.query || new Query();
54
+ routerViewItems: RouterViewItem[];
55
+ views: RouterViewQuery[];
56
+ emitter: EventEmitter;
57
+ constructor(opts?: { router?: QueryRouterServer, token?: string, routerViewData?: RouterViewData }) {
25
58
  this.router = opts?.router || new QueryRouterServer();
26
59
  this.token = opts?.token || this.getDefulatToken();
60
+ this.routerViewItems = opts?.routerViewData?.data?.items || [];
61
+ this.views = opts?.routerViewData?.views || [];
62
+ this.initRouterViewQuery();
63
+ this.emitter = new EventEmitter();
27
64
  }
28
65
  getDefulatToken() {
29
66
  try {
30
- if (localStorage) {
67
+ if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {
31
68
  return localStorage.getItem('token') || undefined;
32
69
  }
33
70
  } catch (e) {
34
71
  return undefined;
35
72
  }
36
73
  }
74
+ async initRouterViewQuery() {
75
+ this.routerViewItems = this.routerViewItems?.map(item => {
76
+ if (item.type === 'api' && item.api?.url) {
77
+ const url = item.api.url;
78
+ if (item?.api?.query) return item;
79
+ item['api'] = { url: url, query: new Query({ url: url }) };
80
+ }
81
+ if (item.type === 'worker' && item.worker?.url) {
82
+ let viewItem = item as RouterViewWorker;
83
+ if (item.worker.worker) {
84
+ return item;
85
+ }
86
+ let worker: Worker | SharedWorker | ServiceWorker | undefined = undefined;
87
+ if (item.worker.type === 'SharedWorker') {
88
+ worker = new SharedWorker(item.worker.url);
89
+ worker.port.start();
90
+ } else if (viewItem.worker.type === 'serviceWorker') {
91
+ if ('serviceWorker' in navigator) {
92
+ navigator.serviceWorker.register(viewItem.worker.url).then(function (registration) {
93
+ console.debug('注册serviceWorker成功 ', registration.scope);
94
+ }, function (err) {
95
+ console.debug('注册 serviceWorker 失败: ', err);
96
+ });
97
+ } else {
98
+ console.warn('当前浏览器不支持serviceWorker');
99
+ }
100
+ } else {
101
+ worker = new Worker(viewItem.worker.url);
102
+ }
103
+ viewItem['worker']['worker'] = worker;
104
+ }
105
+ if (item.type === 'context' && item.context?.key) {
106
+ if (item.context?.router) {
107
+ return item;
108
+ }
109
+ // @ts-ignore
110
+ const context = globalThis['context'] || {}
111
+ const router = context[item.context.key] as QueryRouterServer;
112
+ if (router) {
113
+ item['context']['router'] = router;
114
+ }
115
+ }
116
+ return item;
117
+ });
118
+ }
119
+
37
120
  /**
38
121
  * 初始化路由
39
122
  * @returns
40
123
  */
41
124
  async init() {
125
+ const routerViewItems = this.routerViewItems || [];
126
+ if (routerViewItems.length === 0) {
127
+ await this.initApi();
128
+ return;
129
+ }
130
+ for (const item of routerViewItems) {
131
+ switch (item.type) {
132
+ case 'api':
133
+ this.initApi(item);
134
+ break;
135
+ case 'context':
136
+ break;
137
+ case 'worker':
138
+ this.initWorker(item);
139
+ break;
140
+ }
141
+ }
142
+ }
143
+ async initApi(item?: RouterViewApi) {
42
144
  const that = this;
43
- const res = await this.query.post<{ list: RouterItem[] }>({ path: "router", key: 'list', token: this.token });
145
+ const query = item?.api?.query || new Query({ url: item?.api?.url || '/api/router' })
146
+ const res = await query.post<{ list: RouterItem[] }>({ path: "router", key: 'list', token: this.token });
44
147
  if (res.code !== 200) {
45
148
  console.error('Failed to init query proxy router:', res.message);
46
149
  return
47
150
  }
48
151
  const _list = res.data?.list || []
49
- for (const item of _list) {
50
- if (item.path || item.id) {
51
- console.log(`Register route: [${item.path}] ${item?.key}`);
152
+ for (const r of _list) {
153
+ if (r.path || r.id) {
154
+ console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API');
52
155
  this.router.route({
53
- path: item.path,
54
- key: item.key || '',
55
- id: item.id,
56
- description: item.description,
57
- metadata: item.metadata,
156
+ path: r.path,
157
+ key: r.key || '',
158
+ id: r.id,
159
+ description: r.description,
160
+ metadata: r.metadata,
58
161
  }).define(async (ctx) => {
59
162
  const msg = { ...ctx.query };
60
163
  if (msg.token === undefined && that.token !== undefined) {
61
164
  msg.token = that.token;
62
165
  }
63
- const r = await that.query.post<any>({ path: item.path, key: item.key, ...msg });
64
- ctx.forward(r)
166
+ const res = await query.post<any>({ path: r.path, key: r.key, ...msg });
167
+ ctx.forward(res)
168
+ }).addTo(that.router);
169
+ }
170
+ }
171
+ }
172
+ async initContext(item?: RouterViewContext) {
173
+ // @ts-ignore
174
+ const context = globalThis['context'] || {}
175
+ const router = item?.context?.router || context[item?.context?.key] as QueryRouterServer;
176
+ if (!router) {
177
+ console.warn(`未发现Context router ${item?.context?.key}`);
178
+ return
179
+ }
180
+ const routes = router.getList();
181
+ for (const r of routes) {
182
+ console.debug(`注册路由: [${r.path}] ${r?.key}`, 'Context');
183
+ this.router.route({
184
+ path: r.path,
185
+ key: r.key || '',
186
+ id: r.id,
187
+ description: r.description,
188
+ metadata: r.metadata,
189
+ }).define(async (ctx) => {
190
+ const res = await router.run({ path: r.path, key: r.key, ...ctx.query });
191
+ ctx.forward(res)
192
+ }).addTo(this.router);
193
+ }
194
+ }
195
+ generateId() {
196
+ return 'route_' + Math.random().toString(36).substring(2, 9);
197
+ }
198
+ async initWorker(item?: RouterViewWorker) {
199
+ const that = this;
200
+ if (!item?.worker?.url) {
201
+ console.warn('Worker URL not provided');
202
+ return;
203
+ }
204
+ const viewItem = item.worker;
205
+ const worker = viewItem?.worker;
206
+ if (!worker) {
207
+ console.warn('Worker not initialized');
208
+ return;
209
+ }
210
+ if (item.worker.type === 'SharedWorker') {
211
+ const port = (worker as SharedWorker).port;
212
+ port.onmessage = function (e) {
213
+ const msg = e.data;
214
+ const requestId = msg.requestId;
215
+ that.emitter.emit(requestId, msg);
216
+ };
217
+ port.start();
218
+ }
219
+ const callWorker = async (msg: any, viewItem: RouterViewWorker['worker']): Promise<Result> => {
220
+ const requestId = this.generateId();
221
+ const worker = viewItem?.worker;
222
+ if (!worker) {
223
+ return { code: 500, message: 'Worker未初始化' };
224
+ }
225
+ let port: MessagePort | Worker | ServiceWorker;
226
+ if (viewItem.type === 'SharedWorker') {
227
+ port = (worker as SharedWorker).port;
228
+ } else {
229
+ port = worker as Worker | ServiceWorker;
230
+ }
231
+ port.postMessage({
232
+ ...msg,
233
+ requestId: requestId,
234
+ })
235
+ return new Promise((resolve) => {
236
+ const timer = setTimeout(() => {
237
+ that.emitter.removeAllListeners(requestId);
238
+ resolve({ code: 500, message: '请求超时' });
239
+ }, 3 * 60 * 1000); // 3分钟超时
240
+ that.emitter.once(requestId, (res: any) => {
241
+ clearTimeout(timer);
242
+ resolve(res);
243
+ });
244
+ });
245
+ }
246
+ const res = await callWorker({
247
+ path: "router",
248
+ key: 'list',
249
+ token: this.token,
250
+ }, viewItem);
251
+
252
+ if (res.code !== 200) {
253
+ console.error('Failed to init query proxy router:', res.message);
254
+ return;
255
+ }
256
+ const _list = res.data?.list || []
257
+ for (const r of _list) {
258
+ if (r.path || r.id) {
259
+ console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API');
260
+ this.router.route({
261
+ path: r.path,
262
+ key: r.key || '',
263
+ id: r.id,
264
+ description: r.description,
265
+ metadata: r.metadata,
266
+ }).define(async (ctx) => {
267
+ const msg = { ...ctx.query };
268
+ if (msg.token === undefined && that.token !== undefined) {
269
+ msg.token = that.token;
270
+ }
271
+ const res = await callWorker({ path: r.path, key: r.key, ...msg }, viewItem);
272
+ ctx.forward(res)
65
273
  }).addTo(that.router);
66
274
  }
67
275
  }
@@ -72,7 +280,16 @@ export class QueryProxy {
72
280
  * @param query WHERE metadata.tags CONTAINS 'premium'
73
281
  * @returns
74
282
  */
75
- async listRoutes(filterFn?: (item: Route) => boolean, query?: string) {
283
+ async listRoutes(filterFn?: (item: Route) => boolean, opts?: { viewId?: string, query?: string }) {
284
+ let query = opts?.query;
285
+ if (opts?.viewId) {
286
+ const view = this.views.find(v => v.id === opts.viewId);
287
+ if (view) {
288
+ query = view.query;
289
+ }
290
+ } if (opts?.query) {
291
+ query = opts.query;
292
+ }
76
293
  const routes = this.router.routes.filter(filterFn || (() => true));
77
294
  if (query) {
78
295
  return filter(routes, query);