diff --git a/package.json b/package.json index ca4fa22..c85a265 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/api", - "version": "0.0.20", + "version": "0.0.21", "description": "", "main": "mod.ts", "scripts": { diff --git a/query/query-proxy/index.ts b/query/query-proxy/index.ts index 363a677..dc7c43b 100644 --- a/query/query-proxy/index.ts +++ b/query/query-proxy/index.ts @@ -1,490 +1,3 @@ -import { QueryClient as Query, Result } from '@kevisual/query'; -import { QueryRouterServer, Route } from '@kevisual/router/src/route.ts'; -import { filter } from '@kevisual/js-filter' -import { EventEmitter } from 'eventemitter3'; +export * from './proxy.ts'; -export const RouteTypeList = ['api', 'context', 'worker', 'page'] as const; -export type RouterViewItemInfo = RouterViewApi | RouterViewContext | RouterViewWorker | RouteViewPage; -export type RouterViewItem = RouterViewItemInfo & T; - -type RouteViewBase = { - /** - * _id 用于纯本地存储标识 - */ - _id?: string; - id?: string; - title?: string; - description?: string; - enabled?: boolean; - /** - * 响应数据 - */ - response?: any; - /** - * 默认动作配置 - */ - action?: { path?: string; key?: string; id?: string; payload?: any;[key: string]: any }; -} -export type RouterViewApi = { - type: 'api', - api: { - url: string, - // 已初始化的query实例,不需要编辑配置 - query?: Query - } -} & RouteViewBase; - -export type RouterViewContext = { - type: 'context', - context: { - key: string, - // 从context中获取router,不需要编辑配置 - router?: QueryRouterServer - } -} & RouteViewBase; -export type RouterViewWorker = { - type: 'worker', - worker: { - type: 'Worker' | 'SharedWorker' | 'serviceWorker', - url: string, - // 已初始化的worker实例,不需要编辑配置 - worker?: Worker | SharedWorker | ServiceWorker, - /** - * worker选项 - * default: { type: 'module' } - */ - workerOptions?: { - type: 'module' | 'classic' - } - } -} & RouteViewBase; - -/** - * 去掉不需要保存都服务器的数据 - * @param item - * @returns - */ -export const pickRouterViewData = (item: RouterViewItem) => { - const { action, response, _id, ...rest } = item; - if (rest.type === 'api') { - if (rest.api) { - delete rest.api.query; - } - } - if (rest.type === 'worker') { - if (rest.worker) { - delete rest.worker.worker; - } - } - if (rest.type === 'context') { - if (rest.context) { - delete rest.context.router; - } - } - return rest -} -/** - * 注入 js 的url地址,使用importScripts加载 - */ -export type RouteViewPage = { - type: 'page', - page: { - url: string, - } -} & RouteViewBase; - -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; - routerViewItems: RouterViewItem[]; - views: RouterViewQuery[]; - emitter: EventEmitter; - constructor(opts?: { router?: QueryRouterServer, token?: string, routerViewData?: RouterViewData }) { - this.router = opts?.router || new QueryRouterServer(); - this.token = opts?.token || this.getDefulatToken(); - this.routerViewItems = opts?.routerViewData?.data?.items || []; - this.views = opts?.routerViewData?.views || []; - this.initRouterViewQuery(); - this.emitter = new EventEmitter(); - } - getDefulatToken() { - try { - if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') { - return localStorage.getItem('token') || undefined; - } - } catch (e) { - return undefined; - } - } - initRouterViewQuery() { - this.routerViewItems = this.routerViewItems.map(item => { - return this.initRouterView(item); - }).filter(item => { - const enabled = item.enabled ?? true; - return enabled; - }); - } - - initRouterView(item: RouterViewItem) { - if (item.type === 'api' && item.api?.url) { - const url = item.api.url; - if (item?.api?.query) return item; - const query = new Query({ url: url }); - item['api'] = { url: url, query: query }; - } - if (item.type === 'worker' && item.worker?.url) { - let viewItem = item as RouterViewWorker; - if (!item.worker?.workerOptions?.type) { - item.worker.workerOptions = { ...item.worker.workerOptions, type: 'module' }; - } - if (item.worker.worker) { - return item; - } - let worker: Worker | SharedWorker | ServiceWorker | undefined = undefined; - if (item.worker.type === 'SharedWorker') { - worker = new SharedWorker(item.worker.url, item.worker.workerOptions); - worker.port.start(); - } else if (viewItem.worker.type === 'serviceWorker') { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.register(viewItem.worker.url, item.worker.workerOptions).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, item.worker.workerOptions); - } - viewItem['worker']['worker'] = worker; - } - if (item.type === 'context' && item.context?.key) { - if (item.context?.router) { - return item; - } - // @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() { - const routerViewItems = this.routerViewItems || []; - if (routerViewItems.length === 0) { - // 默认初始化api类型路由 - await this.initApi(); - return; - } - for (const item of routerViewItems) { - switch (item.type) { - case 'api': - await this.initApi(item); - break; - case 'context': - await this.initContext(item); - break; - case 'worker': - await this.initWorker(item); - break; - case 'page': - await this.initPage(item); - break; - } - } - this.emitter.emit('initComplete'); - } - /** - * 监听初始化完成 - * @returns - */ - async listenInitComplete(): Promise { - return new Promise((resolve) => { - const timer = setTimeout(() => { - this.emitter.removeAllListeners('initComplete'); - resolve(false); - }, 3 * 60000); // 3分钟超时 - const func = () => { - clearTimeout(timer); - resolve(true); - } - this.emitter.once('initComplete', func); - }); - } - 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'); - let metadata = r.metadata || {}; - metadata.viewItem = item; - this.router.route({ - path: r.path, - key: r.key || '', - id: r.id, - description: r.description, - metadata: metadata, - }).define(async (ctx) => { - const msg = { ...ctx.query }; - if (msg.token === undefined && that.token !== undefined) { - msg.token = that.token; - } - const res = await query.post({ 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'); - let metadata = r.metadata || {}; - metadata.viewItem = item; - this.router.route({ - path: r.path, - key: r.key || '', - id: r.id, - description: r.description, - metadata: 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 callWorker(msg: any, viewItem: RouterViewWorker['worker']): Promise { - const that = this; - 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); - }); - }); - } - async initWorker(item?: RouterViewWorker, initRoutes: boolean = true) { - 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; - } - const callResponse = (e: MessageEvent) => { - const msg = e.data; - if (msg.requestId) { - const requestId = msg.requestId; - that.emitter.emit(requestId, msg); - } else { - that.router.run(msg); - } - } - if (item.worker.type === 'SharedWorker') { - const port = (worker as SharedWorker).port; - port.onmessage = callResponse; - port.start(); - } else if (item.worker.type === 'serviceWorker') { - navigator.serviceWorker.addEventListener('message', callResponse); - } else { - (worker as Worker).onmessage = callResponse; - } - if (!initRoutes) { - return; - } - const callWorker = this.callWorker.bind(this); - - 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'); - let metadata = r.metadata || {}; - metadata.viewItem = item; - this.router.route({ - path: r.path, - key: r.key || '', - id: r.id, - description: r.description, - metadata: 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); - } - } - } - async initPage(item?: RouteViewPage) { - if (!item?.page?.url) { - console.warn('Page地址未提供'); - return; - } - const url = item.page.url; - try { - if (typeof window !== 'undefined') { - await import(url).then((module) => { }).catch((err) => { - console.error('引入Page脚本失败:', url, err); - }); - } - } catch (e) { - console.warn('引入Page脚本失败:', url, e); - return; - } - } - /** - * 列出路由 - * @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; - } - async getViewQuery(viewId: string) { - const view = this.views.find(v => v.id === viewId); - if (view) { - return view.query; - } - return undefined; - } - /** - * 运行路由 - * @param msg - * @returns - */ - async run(msg: { id?: string, path?: string, key?: string }) { - return await this.router.run(msg); - } - - async runByRouteView(routeView: RouterViewItem) { - if (routeView.response) { - return routeView; - } - const item = this.initRouterView(routeView); - if (item.type === 'api' && item.api?.url) { - const query = item.api.query!; - const res = await query.post(item.action || {}); - item.response = res; - return item; - } else if (item.type === 'api') { - item.response = { code: 500, message: 'API URL未配置' }; - return item; - } - if (item.type === 'context' && item.context?.router) { - const router = item.context.router; - const res = await router.run(item.action || {}); - item.response = res; - return item; - } - if (item.type === 'page') { - await this.initPage(item); - const res = await this.router.run(item.action || {}); - item.response = res; - return item; - } - if (item.type === 'worker' && item.worker?.worker) { - await this.initWorker(item, false); - const callWorker = this.callWorker.bind(this); - const res = await callWorker(item.action || {}, item.worker); - item.response = res; - return item; - } - item.response = { code: 500, message: '无法处理的路由类型' }; - return item; - } -} - -type RouterItem = { - id?: string; - path?: string; - key?: string; - description?: string; - middleware?: string[]; - metadata?: Record; -} \ No newline at end of file +export { initApi } from './router-api-proxy.ts'; \ No newline at end of file diff --git a/query/query-proxy/proxy.ts b/query/query-proxy/proxy.ts new file mode 100644 index 0000000..cb57b82 --- /dev/null +++ b/query/query-proxy/proxy.ts @@ -0,0 +1,463 @@ +import { QueryClient as Query, Result } from '@kevisual/query'; +import { QueryRouterServer, App, Route } from '@kevisual/router'; +import { filter } from '@kevisual/js-filter' +import { EventEmitter } from 'eventemitter3'; +import { initApi } from './router-api-proxy'; + +export const RouteTypeList = ['api', 'context', 'worker', 'page'] as const; +export type RouterViewItemInfo = RouterViewApi | RouterViewContext | RouterViewWorker | RouteViewPage; +export type RouterViewItem = RouterViewItemInfo & T; + +type RouteViewBase = { + /** + * _id 用于纯本地存储标识 + */ + _id?: string; + id?: string; + title?: string; + description?: string; + enabled?: boolean; + /** + * 响应数据 + */ + response?: any; + /** + * 默认动作配置 + */ + action?: { path?: string; key?: string; id?: string; payload?: any;[key: string]: any }; +} +export type RouterViewApi = { + type: 'api', + api: { + url: string, + // 已初始化的query实例,不需要编辑配置 + query?: Query + } +} & RouteViewBase; + +export type RouterViewContext = { + type: 'context', + context: { + key: string, + // 从context中获取router,不需要编辑配置 + router?: QueryRouterServer + } +} & RouteViewBase; +export type RouterViewWorker = { + type: 'worker', + worker: { + type: 'Worker' | 'SharedWorker' | 'serviceWorker', + url: string, + // 已初始化的worker实例,不需要编辑配置 + worker?: Worker | SharedWorker | ServiceWorker, + /** + * worker选项 + * default: { type: 'module' } + */ + workerOptions?: { + type: 'module' | 'classic' + } + } +} & RouteViewBase; + +/** + * 去掉不需要保存都服务器的数据 + * @param item + * @returns + */ +export const pickRouterViewData = (item: RouterViewItem) => { + const { action, response, _id, ...rest } = item; + if (rest.type === 'api') { + if (rest.api) { + delete rest.api.query; + } + } + if (rest.type === 'worker') { + if (rest.worker) { + delete rest.worker.worker; + } + } + if (rest.type === 'context') { + if (rest.context) { + delete rest.context.router; + } + } + return rest +} +/** + * 注入 js 的url地址,使用importScripts加载 + */ +export type RouteViewPage = { + type: 'page', + page: { + url: string, + } +} & RouteViewBase; + +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; + routerViewItems: RouterViewItem[]; + views: RouterViewQuery[]; + emitter: EventEmitter; + constructor(opts?: { router?: QueryRouterServer, token?: string, routerViewData?: RouterViewData }) { + this.router = opts?.router || new QueryRouterServer(); + this.token = opts?.token || this.getDefulatToken(); + this.routerViewItems = opts?.routerViewData?.data?.items || []; + this.views = opts?.routerViewData?.views || []; + this.initRouterViewQuery(); + this.emitter = new EventEmitter(); + } + getDefulatToken() { + try { + if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') { + return localStorage.getItem('token') || undefined; + } + } catch (e) { + return undefined; + } + } + initRouterViewQuery() { + this.routerViewItems = this.routerViewItems.map(item => { + return this.initRouterView(item); + }).filter(item => { + const enabled = item.enabled ?? true; + return enabled; + }); + } + + initRouterView(item: RouterViewItem) { + if (item.type === 'api' && item.api?.url) { + const url = item.api.url; + if (item?.api?.query) return item; + const query = new Query({ url: url }); + item['api'] = { url: url, query: query }; + } + if (item.type === 'worker' && item.worker?.url) { + let viewItem = item as RouterViewWorker; + if (!item.worker?.workerOptions?.type) { + item.worker.workerOptions = { ...item.worker.workerOptions, type: 'module' }; + } + if (item.worker.worker) { + return item; + } + let worker: Worker | SharedWorker | ServiceWorker | undefined = undefined; + if (item.worker.type === 'SharedWorker') { + worker = new SharedWorker(item.worker.url, item.worker.workerOptions); + worker.port.start(); + } else if (viewItem.worker.type === 'serviceWorker') { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register(viewItem.worker.url, item.worker.workerOptions).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, item.worker.workerOptions); + } + viewItem['worker']['worker'] = worker; + } + if (item.type === 'context' && item.context?.key) { + if (item.context?.router) { + return item; + } + // @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() { + const routerViewItems = this.routerViewItems || []; + if (routerViewItems.length === 0) { + // 默认初始化api类型路由 + await this.initApi(); + return; + } + for (const item of routerViewItems) { + switch (item.type) { + case 'api': + await this.initApi(item); + break; + case 'context': + await this.initContext(item); + break; + case 'worker': + await this.initWorker(item); + break; + case 'page': + await this.initPage(item); + break; + } + } + this.emitter.emit('initComplete'); + } + /** + * 监听初始化完成 + * @returns + */ + async listenInitComplete(): Promise { + return new Promise((resolve) => { + const timer = setTimeout(() => { + this.emitter.removeAllListeners('initComplete'); + resolve(false); + }, 3 * 60000); // 3分钟超时 + const func = () => { + clearTimeout(timer); + resolve(true); + } + this.emitter.once('initComplete', func); + }); + } + async initApi(item?: RouterViewApi) { + initApi({ item: item, router: this.router, token: this.token }); + } + 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'); + let metadata = r.metadata || {}; + metadata.viewItem = item; + this.router.route({ + path: r.path, + key: r.key || '', + id: r.id, + description: r.description, + metadata: 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 callWorker(msg: any, viewItem: RouterViewWorker['worker']): Promise { + const that = this; + 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); + }); + }); + } + async initWorker(item?: RouterViewWorker, initRoutes: boolean = true) { + 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; + } + const callResponse = (e: MessageEvent) => { + const msg = e.data; + if (msg.requestId) { + const requestId = msg.requestId; + that.emitter.emit(requestId, msg); + } else { + that.router.run(msg); + } + } + if (item.worker.type === 'SharedWorker') { + const port = (worker as SharedWorker).port; + port.onmessage = callResponse; + port.start(); + } else if (item.worker.type === 'serviceWorker') { + navigator.serviceWorker.addEventListener('message', callResponse); + } else { + (worker as Worker).onmessage = callResponse; + } + if (!initRoutes) { + return; + } + const callWorker = this.callWorker.bind(this); + + 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'); + let metadata = r.metadata || {}; + metadata.viewItem = item; + this.router.route({ + path: r.path, + key: r.key || '', + id: r.id, + description: r.description, + metadata: 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); + } + } + } + async initPage(item?: RouteViewPage) { + if (!item?.page?.url) { + console.warn('Page地址未提供'); + return; + } + const url = item.page.url; + try { + if (typeof window !== 'undefined') { + await import(url).then((module) => { }).catch((err) => { + console.error('引入Page脚本失败:', url, err); + }); + } + } catch (e) { + console.warn('引入Page脚本失败:', url, e); + return; + } + } + /** + * 列出路由 + * @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; + } + async getViewQuery(viewId: string) { + const view = this.views.find(v => v.id === viewId); + if (view) { + return view.query; + } + return undefined; + } + /** + * 运行路由 + * @param msg + * @returns + */ + async run(msg: { id?: string, path?: string, key?: string }) { + return await this.router.run(msg); + } + + async runByRouteView(routeView: RouterViewItem) { + if (routeView.response) { + return routeView; + } + const item = this.initRouterView(routeView); + if (item.type === 'api' && item.api?.url) { + const query = item.api.query!; + const res = await query.post(item.action || {}); + item.response = res; + return item; + } else if (item.type === 'api') { + item.response = { code: 500, message: 'API URL未配置' }; + return item; + } + if (item.type === 'context' && item.context?.router) { + const router = item.context.router; + const res = await router.run(item.action || {}); + item.response = res; + return item; + } + if (item.type === 'page') { + await this.initPage(item); + const res = await this.router.run(item.action || {}); + item.response = res; + return item; + } + if (item.type === 'worker' && item.worker?.worker) { + await this.initWorker(item, false); + const callWorker = this.callWorker.bind(this); + const res = await callWorker(item.action || {}, item.worker); + item.response = res; + return item; + } + item.response = { code: 500, message: '无法处理的路由类型' }; + return item; + } +} + +export type RouterItem = { + id?: string; + path?: string; + key?: string; + description?: string; + middleware?: string[]; + metadata?: Record; +} \ No newline at end of file diff --git a/query/query-proxy/router-api-proxy.ts b/query/query-proxy/router-api-proxy.ts new file mode 100644 index 0000000..3593588 --- /dev/null +++ b/query/query-proxy/router-api-proxy.ts @@ -0,0 +1,48 @@ +import { Query } from "@kevisual/query"; +import { RouterViewApi, RouterItem } from "."; +import { App, type QueryRouterServer } from "@kevisual/router"; +import { filter } from "@kevisual/js-filter"; +export const initApi = async (opts: { + item?: RouterViewApi, + router: QueryRouterServer | App, + token?: string, + /** + * WHERE path = 'auth' OR path = 'router' + */ + exclude?: string; +}) => { + const router = opts?.router! as QueryRouterServer; + const item = opts?.item; + const token = opts?.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: token }); + if (res.code !== 200) { + console.error('初始化路由失败:', query.url, res.message); + return + } + let _list = res.data?.list || [] + if (opts?.exclude) { + _list = filter(_list, opts.exclude); + } + for (const r of _list) { + if (r.path || r.id) { + console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API'); + let metadata = r.metadata || {}; + metadata.viewItem = item; + router.route({ + path: r.path, + key: r.key || '', + id: r.id, + description: r.description, + metadata: metadata, + }).define(async (ctx) => { + const msg = { ...ctx.query }; + if (msg.token === undefined && token !== undefined) { + msg.token = token; + } + const res = await query.post({ path: r.path, key: r.key, ...msg }); + ctx.forward(res) + }).addTo(router); + } + } +} \ No newline at end of file