@kevisual/api 0.0.10 → 0.0.11

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