update
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kevisual/api",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11",
|
||||
"description": "",
|
||||
"main": "mod.ts",
|
||||
"scripts": {
|
||||
@@ -35,6 +35,7 @@
|
||||
"@kevisual/js-filter": "^0.0.2",
|
||||
"@kevisual/load": "^0.0.6",
|
||||
"es-toolkit": "^1.43.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"nanoid": "^5.1.6"
|
||||
},
|
||||
"exports": {
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -17,6 +17,9 @@ importers:
|
||||
es-toolkit:
|
||||
specifier: ^1.43.0
|
||||
version: 1.43.0
|
||||
eventemitter3:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1
|
||||
nanoid:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
|
||||
@@ -1,67 +1,268 @@
|
||||
import { Query } from '@kevisual/query/query';
|
||||
import { Query, Result } from '@kevisual/query/query';
|
||||
import { QueryRouterServer, Route } from '@kevisual/router/src/route.ts';
|
||||
import { filter } from '@kevisual/js-filter'
|
||||
export type ProxyItem = {
|
||||
title?: string;
|
||||
type?: 'api' | 'context' | 'page';
|
||||
description?: string;
|
||||
api?: {
|
||||
url: string;
|
||||
},
|
||||
context?: {
|
||||
key: string;
|
||||
},
|
||||
page?: {},
|
||||
where?: string;
|
||||
whereList?: Array<{ title: string; where: string }>;
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
|
||||
export type RouterViewItem = RouterViewApi | RouterViewContext | RouterViewWorker;
|
||||
export type RouterViewApi = {
|
||||
title: string;
|
||||
description: string;
|
||||
type: 'api',
|
||||
api: {
|
||||
url: string,
|
||||
// 已初始化的query实例
|
||||
query?: Query
|
||||
}
|
||||
}
|
||||
|
||||
export type RouterViewContext = {
|
||||
title: string;
|
||||
description: string;
|
||||
type: 'context',
|
||||
context: {
|
||||
key: string,
|
||||
// 从context中获取router
|
||||
router?: QueryRouterServer
|
||||
}
|
||||
}
|
||||
export type RouterViewWorker = {
|
||||
title: string;
|
||||
description: string;
|
||||
type: 'worker',
|
||||
worker: {
|
||||
type: 'Worker' | 'SharedWorker' | 'serviceWorker',
|
||||
url: string,
|
||||
// 已初始化的worker实例
|
||||
worker?: Worker | SharedWorker | ServiceWorker
|
||||
}
|
||||
}
|
||||
export type RouterViewQuery = {
|
||||
id: string,
|
||||
query: string,
|
||||
title: string
|
||||
}
|
||||
export type RouterViewData = {
|
||||
data: { items: RouterViewItem[]; }
|
||||
views: RouterViewQuery[];
|
||||
viewId?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class QueryProxy {
|
||||
query: Query;
|
||||
router: QueryRouterServer;
|
||||
token?: string;
|
||||
constructor(opts?: { query: Query, router?: QueryRouterServer, token?: string }) {
|
||||
this.query = opts?.query || new Query();
|
||||
routeViewItems: RouterViewItem[];
|
||||
views: RouterViewQuery[];
|
||||
emitter: EventEmitter;
|
||||
constructor(opts?: { query: Query, router?: QueryRouterServer, token?: string, routeViewData?: RouterViewData }) {
|
||||
this.router = opts?.router || new QueryRouterServer();
|
||||
this.token = opts?.token || this.getDefulatToken();
|
||||
this.routeViewItems = opts?.routeViewData?.data?.items || [];
|
||||
this.views = opts?.routeViewData?.views || [];
|
||||
this.initRouteViewQuery();
|
||||
this.emitter = new EventEmitter();
|
||||
}
|
||||
getDefulatToken() {
|
||||
try {
|
||||
if (localStorage) {
|
||||
if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {
|
||||
return localStorage.getItem('token') || undefined;
|
||||
}
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
async initRouteViewQuery() {
|
||||
this.routeViewItems = this.routeViewItems?.map(item => {
|
||||
if (item.type === 'api' && item.api?.url) {
|
||||
const url = item.api.url;
|
||||
item['api'] = { url: url, query: new Query({ url: url }) };
|
||||
}
|
||||
if (item.type === 'worker' && item.worker?.url) {
|
||||
let viewItem = item as RouterViewWorker;
|
||||
let worker: Worker | SharedWorker | ServiceWorker | undefined = undefined;
|
||||
if (item.worker.type === 'SharedWorker') {
|
||||
worker = new SharedWorker(item.worker.url);
|
||||
worker.port.start();
|
||||
} else if (viewItem.worker.type === 'serviceWorker') {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register(viewItem.worker.url).then(function (registration) {
|
||||
console.debug('注册serviceWorker成功 ', registration.scope);
|
||||
}, function (err) {
|
||||
console.debug('注册 serviceWorker 失败: ', err);
|
||||
});
|
||||
} else {
|
||||
console.warn('当前浏览器不支持serviceWorker');
|
||||
}
|
||||
} else {
|
||||
worker = new Worker(viewItem.worker.url);
|
||||
}
|
||||
viewItem['worker']['worker'] = worker;
|
||||
}
|
||||
if (item.type === 'context' && item.context?.key) {
|
||||
// @ts-ignore
|
||||
const context = globalThis['context'] || {}
|
||||
const router = context[item.context.key] as QueryRouterServer;
|
||||
if (router) {
|
||||
item['context']['router'] = router;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化路由
|
||||
* @returns
|
||||
*/
|
||||
async init() {
|
||||
async init(routeViewData?: RouterViewData) {
|
||||
const routes = routeViewData?.data?.items || [];
|
||||
if (routes.length === 0) {
|
||||
await this.initApi();
|
||||
return;
|
||||
}
|
||||
for (const item of routes) {
|
||||
switch (item.type) {
|
||||
case 'api':
|
||||
this.initApi(item);
|
||||
break;
|
||||
case 'context':
|
||||
break;
|
||||
case 'worker':
|
||||
this.initWorker(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
async initApi(item?: RouterViewApi) {
|
||||
const that = this;
|
||||
const res = await this.query.post<{ list: RouterItem[] }>({ path: "router", key: 'list', token: this.token });
|
||||
const query = item?.api?.query || new Query({ url: item?.api?.url || '/api/router' })
|
||||
const res = await query.post<{ list: RouterItem[] }>({ path: "router", key: 'list', token: this.token });
|
||||
if (res.code !== 200) {
|
||||
console.error('Failed to init query proxy router:', res.message);
|
||||
return
|
||||
}
|
||||
const _list = res.data?.list || []
|
||||
for (const item of _list) {
|
||||
if (item.path || item.id) {
|
||||
console.log(`Register route: [${item.path}] ${item?.key}`);
|
||||
for (const r of _list) {
|
||||
if (r.path || r.id) {
|
||||
console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API');
|
||||
this.router.route({
|
||||
path: item.path,
|
||||
key: item.key || '',
|
||||
id: item.id,
|
||||
description: item.description,
|
||||
metadata: item.metadata,
|
||||
path: r.path,
|
||||
key: r.key || '',
|
||||
id: r.id,
|
||||
description: r.description,
|
||||
metadata: r.metadata,
|
||||
}).define(async (ctx) => {
|
||||
const msg = { ...ctx.query };
|
||||
if (msg.token === undefined && that.token !== undefined) {
|
||||
msg.token = that.token;
|
||||
}
|
||||
const r = await that.query.post<any>({ path: item.path, key: item.key, ...msg });
|
||||
ctx.forward(r)
|
||||
const res = await query.post<any>({ path: r.path, key: r.key, ...msg });
|
||||
ctx.forward(res)
|
||||
}).addTo(that.router);
|
||||
}
|
||||
}
|
||||
}
|
||||
async initContext(item?: RouterViewContext) {
|
||||
// @ts-ignore
|
||||
const context = globalThis['context'] || {}
|
||||
const router = item?.context?.router || context[item?.context?.key] as QueryRouterServer;
|
||||
if (!router) {
|
||||
console.warn(`未发现Context router ${item?.context?.key}`);
|
||||
return
|
||||
}
|
||||
const routes = router.getList();
|
||||
for (const r of routes) {
|
||||
console.debug(`注册路由: [${r.path}] ${r?.key}`, 'Context');
|
||||
this.router.route({
|
||||
path: r.path,
|
||||
key: r.key || '',
|
||||
id: r.id,
|
||||
description: r.description,
|
||||
metadata: r.metadata,
|
||||
}).define(async (ctx) => {
|
||||
const res = await router.run({ path: r.path, key: r.key, ...ctx.query });
|
||||
ctx.forward(res)
|
||||
}).addTo(this.router);
|
||||
}
|
||||
}
|
||||
generateId() {
|
||||
return 'route_' + Math.random().toString(36).substring(2, 9);
|
||||
}
|
||||
async initWorker(item?: RouterViewWorker) {
|
||||
const that = this;
|
||||
if (!item?.worker?.url) {
|
||||
console.warn('Worker URL not provided');
|
||||
return;
|
||||
}
|
||||
const viewItem = item.worker;
|
||||
const worker = viewItem?.worker;
|
||||
if (!worker) {
|
||||
console.warn('Worker not initialized');
|
||||
return;
|
||||
}
|
||||
if (item.worker.type === 'SharedWorker') {
|
||||
const port = (worker as SharedWorker).port;
|
||||
port.onmessage = function (e) {
|
||||
const msg = e.data;
|
||||
const requestId = msg.requestId;
|
||||
that.emitter.emit(requestId, msg);
|
||||
};
|
||||
port.start();
|
||||
}
|
||||
const callWorker = async (msg: any, viewItem: RouterViewWorker['worker']): Promise<Result> => {
|
||||
const requestId = this.generateId();
|
||||
const worker = viewItem?.worker;
|
||||
if (!worker) {
|
||||
return { code: 500, message: 'Worker未初始化' };
|
||||
}
|
||||
let port: MessagePort | Worker | ServiceWorker;
|
||||
if (viewItem.type === 'SharedWorker') {
|
||||
port = (worker as SharedWorker).port;
|
||||
} else {
|
||||
port = worker as Worker | ServiceWorker;
|
||||
}
|
||||
port.postMessage({
|
||||
...msg,
|
||||
requestId: requestId,
|
||||
})
|
||||
return new Promise((resolve) => {
|
||||
const timer = setTimeout(() => {
|
||||
that.emitter.removeAllListeners(requestId);
|
||||
resolve({ code: 500, message: '请求超时' });
|
||||
}, 3 * 60 * 1000); // 3分钟超时
|
||||
that.emitter.once(requestId, (res: any) => {
|
||||
clearTimeout(timer);
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
const res = await callWorker({
|
||||
path: "router",
|
||||
key: 'list',
|
||||
token: this.token,
|
||||
}, viewItem);
|
||||
|
||||
if (res.code !== 200) {
|
||||
console.error('Failed to init query proxy router:', res.message);
|
||||
return;
|
||||
}
|
||||
const _list = res.data?.list || []
|
||||
for (const r of _list) {
|
||||
if (r.path || r.id) {
|
||||
console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API');
|
||||
this.router.route({
|
||||
path: r.path,
|
||||
key: r.key || '',
|
||||
id: r.id,
|
||||
description: r.description,
|
||||
metadata: r.metadata,
|
||||
}).define(async (ctx) => {
|
||||
const msg = { ...ctx.query };
|
||||
if (msg.token === undefined && that.token !== undefined) {
|
||||
msg.token = that.token;
|
||||
}
|
||||
const res = await callWorker({ path: r.path, key: r.key, ...msg }, viewItem);
|
||||
ctx.forward(res)
|
||||
}).addTo(that.router);
|
||||
}
|
||||
}
|
||||
@@ -72,7 +273,16 @@ export class QueryProxy {
|
||||
* @param query WHERE metadata.tags CONTAINS 'premium'
|
||||
* @returns
|
||||
*/
|
||||
async listRoutes(filterFn?: (item: Route) => boolean, query?: string) {
|
||||
async listRoutes(filterFn?: (item: Route) => boolean, opts?: { viewId?: string, query?: string }) {
|
||||
let query = opts?.query;
|
||||
if (opts?.viewId) {
|
||||
const view = this.views.find(v => v.id === opts.viewId);
|
||||
if (view) {
|
||||
query = view.query;
|
||||
}
|
||||
} if (opts?.query) {
|
||||
query = opts.query;
|
||||
}
|
||||
const routes = this.router.routes.filter(filterFn || (() => true));
|
||||
if (query) {
|
||||
return filter(routes, query);
|
||||
|
||||
Reference in New Issue
Block a user