Files
query-awesome/query/query-proxy/index.ts
2025-12-31 11:25:35 +08:00

309 lines
9.1 KiB
TypeScript

import { Query, Result } from '@kevisual/query/query';
import { QueryRouterServer, Route } from '@kevisual/router/src/route.ts';
import { filter } from '@kevisual/js-filter'
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 {
router: QueryRouterServer;
token?: string;
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 (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(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 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 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 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);
}
}
}
/**
* 列出路由
* @param filter
* @param query WHERE metadata.tags CONTAINS 'premium'
* @returns
*/
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);
}
return routes;
}
/**
* 运行路由
* @param msg
* @returns
*/
async run(msg: { id?: string, path?: string, key?: string }) {
return await this.router.run(msg);
}
}
type RouterItem = {
id?: string;
path?: string;
key?: string;
description?: string;
middleware?: string[];
metadata?: Record<string, any>;
}