@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 +2 -1
- package/query/query-proxy/index.ts +241 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kevisual/api",
|
|
3
|
-
"version": "0.0.
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
|
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
|
|
50
|
-
if (
|
|
51
|
-
console.
|
|
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:
|
|
54
|
-
key:
|
|
55
|
-
id:
|
|
56
|
-
description:
|
|
57
|
-
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
|
|
64
|
-
ctx.forward(
|
|
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);
|