Compare commits
18 Commits
7e167fd4a1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 31e8c0346f | |||
|
|
14a80013e1 | ||
| 67169fc0c6 | |||
| 9532cfbe2e | |||
| 99645e6260 | |||
| 4a2d2a7958 | |||
| 1d92662650 | |||
| 4bd17318a4 | |||
| e8bb129abc | |||
| e5c15bd70a | |||
| eea07b0a8d | |||
| 875195f6a2 | |||
| 2c74755c0d | |||
| 45eedd397b | |||
| abd17b886d | |||
| 7793764baa | |||
| 04a21d7178 | |||
| 230dfefd3b |
56
.cnb.yml
Normal file
56
.cnb.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
# .cnb.yml
|
||||
$:
|
||||
vscode:
|
||||
- docker:
|
||||
image: docker.cnb.cool/kevisual/dev-env:latest
|
||||
services:
|
||||
- vscode
|
||||
- docker
|
||||
imports: https://cnb.cool/kevisual/env/-/blob/main/env.yml
|
||||
# 开发环境启动后会执行的任务
|
||||
# stages:
|
||||
# - name: pnpm install
|
||||
# script: pnpm install
|
||||
|
||||
|
||||
main:
|
||||
web_trigger_sync_to_gitea:
|
||||
- services:
|
||||
- docker
|
||||
imports:
|
||||
- https://cnb.cool/kevisual/env/-/blob/main/env.yml
|
||||
stages:
|
||||
- name: 'show username'
|
||||
script: echo "GITEA_USERNAME is ${GITEA_USERNAME} and GITEA_PASSWORD is ${GITEA_PASSWORD}"
|
||||
- name: sync to gitea
|
||||
image: tencentcom/git-sync
|
||||
settings:
|
||||
target_url: https://git.xiongxiao.me/kevisual/query-awesome.git
|
||||
auth_type: https
|
||||
username: "oauth2"
|
||||
password: ${GITEA_TOKEN}
|
||||
git_user: "abearxiong"
|
||||
git_email: "xiongxiao@xiongxiao.me"
|
||||
sync_mode: rebase
|
||||
branch: main
|
||||
web_trigger_sync_from_gitea:
|
||||
- services:
|
||||
- docker
|
||||
imports:
|
||||
- https://cnb.cool/kevisual/env/-/blob/main/env.yml
|
||||
stages:
|
||||
- name: '添加 gitea的origin'
|
||||
script: |
|
||||
git remote remove gitea 2>/dev/null || true
|
||||
git remote add gitea https://oauth2:${GITEA_TOKEN}@git.xiongxiao.me/kevisual/query-awesome.git
|
||||
- name: '同步gitea代码到当前仓库'
|
||||
script: git pull gitea main
|
||||
- name: '提交到原本的origin'
|
||||
script: git push origin main
|
||||
|
||||
|
||||
"**":
|
||||
web_trigger_test:
|
||||
- stages:
|
||||
- name: 执行任务
|
||||
script: echo "job"
|
||||
11
.cnb/web_trigger.yml
Normal file
11
.cnb/web_trigger.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
# .cnb/web_trigger.yml
|
||||
branch:
|
||||
# 如下按钮在分支名以 release 开头的分支详情页面显示
|
||||
- reg: "^main"
|
||||
buttons:
|
||||
- name: 同步代码到gitea
|
||||
desc: 同步代码到gitea
|
||||
event: web_trigger_sync_to_gitea
|
||||
- name: 同步gitea代码到当前仓库
|
||||
desc: 同步gitea代码到当前仓库
|
||||
event: web_trigger_sync_from_gitea
|
||||
12
.gitmodules
vendored
12
.gitmodules
vendored
@@ -1,12 +0,0 @@
|
||||
[submodule "submodules/query-config"]
|
||||
path = submodules/query-config
|
||||
url = git@git.xiongxiao.me:kevisual/kevisual-query-config.git
|
||||
[submodule "submodules/query-login"]
|
||||
path = submodules/query-login
|
||||
url = git@git.xiongxiao.me:kevisual/kevisual-query-login.git
|
||||
[submodule "submodules/query-upload"]
|
||||
path = submodules/query-upload
|
||||
url = git@git.xiongxiao.me:kevisual/kevisual-query-upload.git
|
||||
[submodule "submodules/query-mark"]
|
||||
path = submodules/query-mark
|
||||
url = git@git.xiongxiao.me:kevisual/kevisual-query-mark.git
|
||||
@@ -1,13 +0,0 @@
|
||||
// @ts-check
|
||||
import { execSync } from 'node:child_process';
|
||||
import glob from 'fast-glob';
|
||||
import fs from 'node:fs';
|
||||
const files = await glob(['packages/*/dist', 'submodules/*/dist'], { onlyDirectories: true });
|
||||
|
||||
console.log('files', files);
|
||||
const clean = 'rimraf dist && mkdir dist';
|
||||
execSync(clean);
|
||||
for (let dir of files) {
|
||||
const rsync = `rsync ${dir}/* ./dist`;
|
||||
execSync(rsync);
|
||||
}
|
||||
46
package.json
46
package.json
@@ -1,37 +1,49 @@
|
||||
{
|
||||
"name": "@kevisual/query-awesome",
|
||||
"version": "0.0.2",
|
||||
"name": "@kevisual/api",
|
||||
"version": "0.0.15",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "mod.ts",
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"build": "bun run bun.config.ts",
|
||||
"postbuild": "bun bun.copy.config.mjs"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"query",
|
||||
"mod.ts"
|
||||
],
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.11.0",
|
||||
"packageManager": "pnpm@10.27.0",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@kevisual/query": "^0.0.18",
|
||||
"@kevisual/router": "^0.0.20",
|
||||
"@kevisual/cache": "^0.0.4",
|
||||
"@kevisual/query": "^0.0.33",
|
||||
"@kevisual/router": "^0.0.52",
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@kevisual/use-config": "^1.0.17",
|
||||
"fast-glob": "^3.3.3",
|
||||
"tsup": "^8.4.0"
|
||||
"@kevisual/use-config": "^1.0.21",
|
||||
"@types/bun": "^1.3.5",
|
||||
"@types/node": "^25.0.3",
|
||||
"dotenv": "^17.2.3",
|
||||
"fast-glob": "^3.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kevisual/js-filter": "^0.0.3",
|
||||
"@kevisual/load": "^0.0.6",
|
||||
"es-toolkit": "^1.43.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"nanoid": "^5.1.6"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/query-login-browser.js"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./dist/*"
|
||||
}
|
||||
".": "./mod.ts",
|
||||
"./login": "./query/query-login/query-login-browser.ts",
|
||||
"./login-node": "./query/query-login/query-login-node.ts",
|
||||
"./config": "./query/query-config/query-config.ts",
|
||||
"./proxy": "./query/query-proxy/index.ts",
|
||||
"./query/**/*.ts": "./query/**/*.ts"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
ignore-workspace-root-check=true
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"$schema": "https://kevisual.xiongxiao.me/root/ai/kevisual/tools/kevisual-sync/schema.json?v=2",
|
||||
"metadata": {
|
||||
"share": "public"
|
||||
},
|
||||
"checkDir": {
|
||||
"query": {
|
||||
"url": "https://kevisual.xiongxiao.me/root/ai/code/registry/query",
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"syncDirectory": [
|
||||
{
|
||||
"files": [
|
||||
"query/**/*"
|
||||
],
|
||||
"ignore": [],
|
||||
"registry": "https://kevisual.xiongxiao.me/root/ai/code/registry"
|
||||
}
|
||||
],
|
||||
"sync": {}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "@kevisual/api",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build2": "bun bun.config.mjs",
|
||||
"download": "ev sync download",
|
||||
"upload": "ev sync upload"
|
||||
},
|
||||
"keywords": [],
|
||||
"files": [
|
||||
"src",
|
||||
"query",
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.6.2",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@kevisual/query": "^0.0.18",
|
||||
"@kevisual/router": "^0.0.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@types/node": "^22.15.27"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"$schema": "https://kevisual.xiongxiao.me/root/ai/kevisual/tools/kevisual-sync/schema.json?v=2",
|
||||
"metadata": {
|
||||
"share": "public"
|
||||
},
|
||||
"checkDir": {
|
||||
"src/query": {
|
||||
"url": "https://kevisual.xiongxiao.me/root/ai/code/registry/query",
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"syncDirectory": [
|
||||
{
|
||||
"files": [
|
||||
"src/query/**/*"
|
||||
],
|
||||
"ignore": [],
|
||||
"registry": "https://kevisual.xiongxiao.me/root/ai/code/registry",
|
||||
"replace": {
|
||||
"src/": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"sync": {}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
import { Query } from '@kevisual/query';
|
||||
import type { Result, DataOpts } from '@kevisual/query/query';
|
||||
|
||||
export type SimpleObject = Record<string, any>;
|
||||
export const markType = ['simple', 'md', 'mdx', 'wallnote', 'excalidraw', 'chat'] as const;
|
||||
export type MarkType = (typeof markType)[number];
|
||||
export type MarkData = {
|
||||
nodes?: any[];
|
||||
edges?: any[];
|
||||
elements?: any[];
|
||||
permission?: any;
|
||||
|
||||
[key: string]: any;
|
||||
};
|
||||
export type Mark = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
markType: MarkType;
|
||||
link: string;
|
||||
data?: MarkData;
|
||||
uid: string;
|
||||
puid: string;
|
||||
summary: string;
|
||||
thumbnail?: string;
|
||||
tags: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
};
|
||||
export type ShowMarkPick = Pick<Mark, 'id' | 'title' | 'description' | 'summary' | 'link' | 'tags' | 'thumbnail' | 'updatedAt'>;
|
||||
|
||||
export type SearchOpts = {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
search?: string;
|
||||
sort?: string; // DESC, ASC
|
||||
markType?: MarkType; // 类型
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type QueryMarkOpts<T extends SimpleObject = SimpleObject> = {
|
||||
query?: Query;
|
||||
isBrowser?: boolean;
|
||||
onLoad?: () => void;
|
||||
} & T;
|
||||
|
||||
export type ResultMarkList = {
|
||||
list: Mark[];
|
||||
pagination: {
|
||||
pageSize: number;
|
||||
current: number;
|
||||
total: number;
|
||||
};
|
||||
};
|
||||
export type QueryMarkData = {
|
||||
id?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
export type QueryMarkResult = {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
};
|
||||
|
||||
export class QueryMarkBase<T extends SimpleObject = SimpleObject> {
|
||||
query: Query;
|
||||
isBrowser: boolean;
|
||||
load?: boolean;
|
||||
storage?: Storage;
|
||||
onLoad?: () => void;
|
||||
|
||||
constructor(opts?: QueryMarkOpts<T>) {
|
||||
this.query = opts?.query || new Query();
|
||||
this.isBrowser = opts?.isBrowser ?? true;
|
||||
this.init();
|
||||
this.onLoad = opts?.onLoad;
|
||||
}
|
||||
setQuery(query: Query) {
|
||||
this.query = query;
|
||||
}
|
||||
private async init() {
|
||||
this.load = true;
|
||||
this.onLoad?.();
|
||||
}
|
||||
|
||||
async post<T = Result<any>>(data: any, opts?: DataOpts): Promise<T> {
|
||||
try {
|
||||
return this.query.post({ path: 'mark', ...data }, opts) as Promise<T>;
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
return {
|
||||
code: 400,
|
||||
} as any;
|
||||
}
|
||||
}
|
||||
|
||||
async getMarkList(search: SearchOpts, opts?: DataOpts) {
|
||||
return this.post<Result<ResultMarkList>>({ key: 'list', ...search }, opts);
|
||||
}
|
||||
|
||||
async getMark(id: string, opts?: DataOpts) {
|
||||
return this.post<Result<Mark>>({ key: 'get', id }, opts);
|
||||
}
|
||||
async getVersion(id: string, opts?: DataOpts) {
|
||||
return this.post<Result<{ version: number; id: string }>>({ key: 'getVersion', id }, opts);
|
||||
}
|
||||
/**
|
||||
* 检查版本
|
||||
* 当需要更新时,返回true
|
||||
* @param id
|
||||
* @param version
|
||||
* @param opts
|
||||
* @returns
|
||||
*/
|
||||
async checkVersion(id: string, version?: number, opts?: DataOpts) {
|
||||
if (!version) {
|
||||
return true;
|
||||
}
|
||||
const res = await this.getVersion(id, opts);
|
||||
if (res.code === 200) {
|
||||
if (res.data!.version > version) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async updateMark(data: any, opts?: DataOpts) {
|
||||
return this.post<Result<Mark>>({ key: 'update', data }, opts);
|
||||
}
|
||||
|
||||
async deleteMark(id: string, opts?: DataOpts) {
|
||||
return this.post<Result<Mark>>({ key: 'delete', id }, opts);
|
||||
}
|
||||
}
|
||||
export class QueryMark extends QueryMarkBase<SimpleObject> {
|
||||
markType: string;
|
||||
constructor(opts?: QueryMarkOpts & { markType?: MarkType }) {
|
||||
super(opts);
|
||||
this.markType = opts?.markType || 'simple';
|
||||
}
|
||||
async getMarkList(search?: SearchOpts, opts?: DataOpts) {
|
||||
return this.post<Result<ResultMarkList>>({ key: 'list', ...search, markType: this.markType }, opts);
|
||||
}
|
||||
async updateMark(data: any, opts?: DataOpts) {
|
||||
if (!data.id) {
|
||||
data.markType = this.markType || 'simple';
|
||||
}
|
||||
return super.updateMark(data, opts);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"extends": "@kevisual/types/json/frontend.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@kevisual"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"query/**/*",
|
||||
],
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// @ts-check
|
||||
import { resolvePath, getDevInputs } from '@kevisual/use-config/env';
|
||||
import { execSync } from 'node:child_process';
|
||||
import glob from 'fast-glob';
|
||||
|
||||
const files = await glob(['src/defines/*.ts', 'src/query/*.ts', 'src/router/*.ts']);
|
||||
const inputs = getDevInputs(files);
|
||||
const external = ['@kevisual/router'];
|
||||
for (let input of inputs) {
|
||||
const entry = input.path;
|
||||
const naming = input.naming;
|
||||
/**
|
||||
* @type {import('bun').BuildConfig}
|
||||
*/
|
||||
await Bun.build({
|
||||
target: 'node',
|
||||
format: 'esm',
|
||||
entrypoints: [resolvePath(entry, { meta: import.meta })],
|
||||
outdir: resolvePath('./dist', { meta: import.meta }),
|
||||
|
||||
naming: {
|
||||
entry: naming + '.js',
|
||||
},
|
||||
external: external,
|
||||
env: 'KEVISUAL_*',
|
||||
});
|
||||
|
||||
const cmd = `dts -i ${entry} -o ${naming}.d.ts`;
|
||||
execSync(cmd, { stdio: 'inherit' });
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "@kevisual/query-list",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "bun bun.config.mjs"
|
||||
},
|
||||
"keywords": [],
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.6.2",
|
||||
"type": "module"
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { QueryUtil } from '@kevisual/router/define';
|
||||
|
||||
export const shopDefine = QueryUtil.create({
|
||||
getRegistry: {
|
||||
path: 'shop',
|
||||
key: 'get-registry',
|
||||
description: '获取应用商店注册表信息',
|
||||
},
|
||||
|
||||
listInstalled: {
|
||||
path: 'shop',
|
||||
key: 'list-installed',
|
||||
description: '列出当前已安装的所有应用',
|
||||
},
|
||||
|
||||
install: {
|
||||
path: 'shop',
|
||||
key: 'install',
|
||||
description: '安装指定的应用,可以指定 id、type、force 和 yes 参数',
|
||||
},
|
||||
|
||||
uninstall: {
|
||||
path: 'shop',
|
||||
key: 'uninstall',
|
||||
description: '卸载指定的应用,可以指定 id 和 type 参数',
|
||||
},
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import { shopDefine } from '../defines/query-shop-define.ts';
|
||||
|
||||
import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
|
||||
|
||||
export { shopDefine };
|
||||
|
||||
export class QueryApp extends BaseQuery {
|
||||
appDefine = shopDefine;
|
||||
constructor(opts?: { query: Query }) {
|
||||
super(opts!);
|
||||
this.appDefine.query = this.query;
|
||||
}
|
||||
get chain() {
|
||||
return this.appDefine.queryChain;
|
||||
}
|
||||
getInstall(data: any, opts?: DataOpts) {
|
||||
return this.appDefine.queryChain('install').post(data, opts);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"extends": "@kevisual/types/json/frontend.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@kevisual"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
],
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// @ts-check
|
||||
import { resolvePath, getDevInputs } from '@kevisual/use-config/env';
|
||||
import { execSync } from 'node:child_process';
|
||||
import glob from 'fast-glob';
|
||||
|
||||
const files = await glob('src/*.ts');
|
||||
const inputs = getDevInputs(files);
|
||||
const external = ['@kevisual/router'];
|
||||
for (let input of inputs) {
|
||||
const entry = input.path;
|
||||
const naming = input.naming;
|
||||
/**
|
||||
* @type {import('bun').BuildConfig}
|
||||
*/
|
||||
await Bun.build({
|
||||
target: 'node',
|
||||
format: 'esm',
|
||||
entrypoints: [resolvePath(entry, { meta: import.meta })],
|
||||
outdir: resolvePath('./dist', { meta: import.meta }),
|
||||
|
||||
naming: {
|
||||
entry: naming + '.js',
|
||||
},
|
||||
external: external,
|
||||
env: 'KEVISUAL_*',
|
||||
});
|
||||
|
||||
const cmd = `dts -i ${entry} -o ${naming}.d.ts`;
|
||||
execSync(cmd, { stdio: 'inherit' });
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "@kevisual/query-app",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "bun bun.config.mjs"
|
||||
},
|
||||
"keywords": [],
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.6.2",
|
||||
"type": "module"
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { QueryUtil } from './common.ts';
|
||||
|
||||
export const appDefine = QueryUtil.create({
|
||||
getApp: {
|
||||
path: 'app',
|
||||
key: 'get',
|
||||
description: '获取应用信息',
|
||||
},
|
||||
|
||||
updateApp: {
|
||||
path: 'app',
|
||||
key: 'update',
|
||||
description: '更新应用信息',
|
||||
},
|
||||
|
||||
deleteApp: {
|
||||
path: 'app',
|
||||
key: 'delete',
|
||||
description: '删除应用信息',
|
||||
},
|
||||
|
||||
listApps: {
|
||||
path: 'app',
|
||||
key: 'list',
|
||||
description: '列出所有应用信息',
|
||||
},
|
||||
|
||||
canUploadFiles: {
|
||||
path: 'app',
|
||||
key: 'canUploadFiles',
|
||||
description: '检查是否可以上传文件',
|
||||
},
|
||||
|
||||
uploadFiles: {
|
||||
path: 'app',
|
||||
key: 'uploadFiles',
|
||||
description: '上传文件',
|
||||
},
|
||||
|
||||
publishApp: {
|
||||
path: 'app',
|
||||
key: 'publish',
|
||||
description: '发布应用',
|
||||
},
|
||||
|
||||
getMinioList: {
|
||||
path: 'app',
|
||||
key: 'get-minio-list',
|
||||
description: '获取 MinIO 文件列表',
|
||||
},
|
||||
|
||||
detectVersionList: {
|
||||
path: 'app',
|
||||
key: 'detectVersionList',
|
||||
description: '检测版本列表并同步 MinIO 数据',
|
||||
},
|
||||
publicList: {
|
||||
path: 'app',
|
||||
key: 'public-list',
|
||||
description: '获取公开应用列表',
|
||||
},
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
export { QueryUtil } from '@kevisual/router/define';
|
||||
@@ -1,3 +0,0 @@
|
||||
import { appDefine } from './app';
|
||||
import { userAppDefine } from './user-app';
|
||||
export { appDefine, userAppDefine };
|
||||
@@ -1,33 +0,0 @@
|
||||
import { QueryUtil } from './common.ts';
|
||||
|
||||
export const userAppDefine = QueryUtil.create({
|
||||
listUserApps: {
|
||||
path: 'user-app',
|
||||
key: 'list',
|
||||
description: '列出当前用户的所有应用(不包含 data 字段)',
|
||||
},
|
||||
|
||||
getUserApp: {
|
||||
path: 'user-app',
|
||||
key: 'get',
|
||||
description: '获取用户应用信息,可以指定 id 或 key',
|
||||
},
|
||||
|
||||
updateUserApp: {
|
||||
path: 'user-app',
|
||||
key: 'update',
|
||||
description: '更新或创建用户应用',
|
||||
},
|
||||
|
||||
deleteUserApp: {
|
||||
path: 'user-app',
|
||||
key: 'delete',
|
||||
description: '删除用户应用及关联数据',
|
||||
},
|
||||
|
||||
testUserApp: {
|
||||
path: 'user-app',
|
||||
key: 'test',
|
||||
description: '对 user-app 的数据进行测试,获取版本信息',
|
||||
},
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
export * from './defines/index.ts';
|
||||
@@ -1,18 +0,0 @@
|
||||
import { appDefine, userAppDefine } from './defines/index.ts';
|
||||
|
||||
import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
|
||||
|
||||
export { appDefine, userAppDefine };
|
||||
|
||||
export class QueryApp extends BaseQuery {
|
||||
appDefine = appDefine;
|
||||
userAppDefine = userAppDefine;
|
||||
constructor(opts?: { query: Query }) {
|
||||
super(opts!);
|
||||
this.appDefine.query = this.query;
|
||||
this.userAppDefine.query = this.query;
|
||||
}
|
||||
getList(data: any, opts?: DataOpts) {
|
||||
return this.appDefine.queryChain('listApps').post(data, opts);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { QueryApp } from '../query-app.ts';
|
||||
import { Query } from '@kevisual/query/query';
|
||||
const query = new Query();
|
||||
const qa = new QueryApp({ query: query });
|
||||
|
||||
qa.appDefine.queryChain('getApp').post({});
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"extends": "@kevisual/types/json/frontend.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@kevisual"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
],
|
||||
}
|
||||
1682
pnpm-lock.yaml
generated
1682
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
packages:
|
||||
- 'submodules/*'
|
||||
- 'packages/*'
|
||||
- 'apps/*'
|
||||
- 'libs/*'
|
||||
21
query/kevisual.json
Normal file
21
query/kevisual.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "kevisual",
|
||||
"share": "public"
|
||||
},
|
||||
"registry": "https://kevisual.cn/root/ai/kevisual/common/query/api",
|
||||
"clone": {
|
||||
".": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"syncd": [
|
||||
{
|
||||
"files": [
|
||||
"**/*"
|
||||
],
|
||||
"registry": ""
|
||||
}
|
||||
],
|
||||
"sync": {}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { QueryUtil } from '@/query/index.ts';
|
||||
import { QueryUtil } from '../../../query/index.ts';
|
||||
|
||||
type Message = {
|
||||
role?: 'user' | 'assistant' | 'system' | 'tool';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { QueryUtil } from '@/query/index.ts';
|
||||
import { QueryUtil } from '../../../query/index.ts';
|
||||
|
||||
export const appDefine = QueryUtil.create({
|
||||
getApp: {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { QueryUtil } from '@/query/index.ts';
|
||||
import { QueryUtil } from '../../../query/index.ts';
|
||||
|
||||
export const userAppDefine = QueryUtil.create({
|
||||
listUserApps: {
|
||||
@@ -15,4 +15,18 @@ export class QueryApp extends BaseQuery {
|
||||
getList(data: any, opts?: DataOpts) {
|
||||
return this.appDefine.queryChain('listApps').post(data, opts);
|
||||
}
|
||||
getPublicApp(data: any, opts?: DataOpts) {
|
||||
return this.query.post({
|
||||
path: 'app',
|
||||
key: 'getApp',
|
||||
data: data,
|
||||
}, opts);
|
||||
}
|
||||
getApp(data: any, opts?: DataOpts) {
|
||||
return this.query.post({
|
||||
path: 'app',
|
||||
key: 'get',
|
||||
data: data,
|
||||
}, opts);
|
||||
}
|
||||
}
|
||||
121
query/query-config/query-config.ts
Normal file
121
query/query-config/query-config.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 配置查询
|
||||
* @updatedAt 2025-12-03 10:33:00
|
||||
*/
|
||||
import { Query } from '@kevisual/query';
|
||||
import type { Result } from '@kevisual/query/query';
|
||||
type QueryConfigOpts = {
|
||||
query?: Query;
|
||||
};
|
||||
export type Config<T = any> = {
|
||||
id?: string;
|
||||
title?: string;
|
||||
key?: string;
|
||||
description?: string;
|
||||
data?: T;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
};
|
||||
export type UploadConfig = {
|
||||
key?: string;
|
||||
version?: string;
|
||||
};
|
||||
type PostOpts = {
|
||||
token?: string;
|
||||
payload?: Record<string, any>;
|
||||
};
|
||||
export const defaultConfigKeys = ['upload.json', 'workspace.json', 'ai.json', 'user.json', 'life.json'] as const;
|
||||
type DefaultConfigKey = (typeof defaultConfigKeys)[number];
|
||||
|
||||
export class QueryConfig {
|
||||
query: Query;
|
||||
constructor(opts?: QueryConfigOpts) {
|
||||
this.query = opts?.query || new Query();
|
||||
}
|
||||
async post<T = Config>(data: any) {
|
||||
return this.query.post<T>({ path: 'config', ...data });
|
||||
}
|
||||
async getConfig({ id, key }: { id?: string; key?: string }, opts?: PostOpts) {
|
||||
return this.post({
|
||||
key: 'get',
|
||||
data: {
|
||||
id,
|
||||
key,
|
||||
},
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
async updateConfig(data: Config, opts?: PostOpts) {
|
||||
return this.post({
|
||||
key: 'update',
|
||||
data,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
async deleteConfig(data: { id?: string, key?: string }, opts?: PostOpts) {
|
||||
console.log('Delete Config Params:', data);
|
||||
return this.post({
|
||||
key: 'delete',
|
||||
data,
|
||||
});
|
||||
}
|
||||
async listConfig(opts?: PostOpts) {
|
||||
return this.post<{ list: Config[] }>({
|
||||
key: 'list',
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取上传配置
|
||||
* @returns
|
||||
*/
|
||||
async getUploadConfig(opts?: PostOpts) {
|
||||
return this.post<Result<Config<UploadConfig>>>({
|
||||
key: 'getUploadConfig',
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 更新上传配置
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
async updateUploadConfig(data: Config, opts?: PostOpts) {
|
||||
return this.post<Result<Config<UploadConfig>>>({
|
||||
key: 'updateUploadConfig',
|
||||
data,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测配置是否存在
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
async detectConfig(opts?: PostOpts) {
|
||||
return this.post<{ updateList: Config[] }>({
|
||||
key: 'detect',
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取配置, 获取默认的配置项
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
async getConfigByKey(key: DefaultConfigKey, opts?: PostOpts) {
|
||||
return this.post<Result<Config>>({
|
||||
key: 'defaultConfig',
|
||||
configKey: key,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
async getByKey<T = any>(key: string, opts?: PostOpts) {
|
||||
return this.post<Result<Config<T>>>({
|
||||
key: 'get',
|
||||
...opts,
|
||||
data: { key },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import type { Result, DataOpts } from '@kevisual/query/query';
|
||||
import { setBaseResponse } from '@kevisual/query/query';
|
||||
import { LoginCacheStore, CacheStore } from './login-cache.ts';
|
||||
import { Cache } from './login-cache.ts';
|
||||
|
||||
import { BaseLoad } from '@kevisual/load';
|
||||
export type QueryLoginOpts = {
|
||||
query?: Query;
|
||||
isBrowser?: boolean;
|
||||
@@ -418,17 +418,74 @@ export class QueryLogin extends BaseQuery {
|
||||
}
|
||||
/**
|
||||
* 使用web登录,创建url地址, 需要MD5和jsonwebtoken
|
||||
*
|
||||
*
|
||||
|
||||
import MD5 from 'crypto-js/md5.js';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
|
||||
*/
|
||||
loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5: any; jsonwebtoken: any }) {
|
||||
loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5?: any; jsonwebtoken?: any }) {
|
||||
const randomId = Math.random().toString(36).substring(2, 15);
|
||||
const timestamp = Date.now();
|
||||
const tokenSecret = 'xiao' + randomId;
|
||||
const sign = MD5(`${tokenSecret}${timestamp}`).toString();
|
||||
const token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
|
||||
let sign = '';
|
||||
if (MD5) {
|
||||
sign = MD5(`${tokenSecret}${timestamp}`).toString();
|
||||
}
|
||||
let token = '';
|
||||
if (jsonwebtoken) {
|
||||
token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
|
||||
// 10分钟过期
|
||||
expiresIn: 60 * 10, // 10分钟
|
||||
});
|
||||
} else {
|
||||
token = tokenSecret;
|
||||
}
|
||||
const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`;
|
||||
return { url, token, tokenSecret };
|
||||
}
|
||||
/**
|
||||
*轮询登录状态
|
||||
*
|
||||
*
|
||||
*
|
||||
const res = queryLogin.loginWithWeb(baseURL, { MD5, jsonwebtoken });
|
||||
await pollLoginStatus(res.token, { tokenSecret: res.tokenSecret });
|
||||
* 轮询登录状态
|
||||
*/
|
||||
async pollLoginStatus(data: { token: string; tokenSecret: string }) {
|
||||
const token = data.token;
|
||||
const load = new BaseLoad();
|
||||
load.load(
|
||||
async () => {
|
||||
const res = await this.checkLoginStatus(token);
|
||||
if (res.code === 500) {
|
||||
load.cancel('check-login-status');
|
||||
}
|
||||
return res;
|
||||
},
|
||||
{
|
||||
key: 'check-login-status',
|
||||
isReRun: true,
|
||||
checkSuccess: (data) => {
|
||||
return data?.code === 200;
|
||||
},
|
||||
},
|
||||
);
|
||||
const res = await load.hasLoaded('check-login-status', {
|
||||
timeout: 60 * 3 * 1000, // 3分钟超时
|
||||
});
|
||||
if (res.code === 200 && res.data?.code === 200) {
|
||||
try {
|
||||
console.log('网页登录成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log('登录失败', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
console.log('登录失败', res);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
405
query/query-proxy/index.ts
Normal file
405
query/query-proxy/index.ts
Normal file
@@ -0,0 +1,405 @@
|
||||
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 const RouteTypeList = ['api', 'context', 'worker', 'page'] as const;
|
||||
export type RouterViewItem = RouterViewApi | RouterViewContext | RouterViewWorker | RouteViewPage;
|
||||
|
||||
type RouteViewBase = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
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;
|
||||
|
||||
/**
|
||||
* 注入 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;
|
||||
}
|
||||
}
|
||||
async initRouterViewQuery() {
|
||||
this.routerViewItems = this.routerViewItems.map(item => {
|
||||
if (item.type === 'api' && item.api?.url) {
|
||||
const url = item.api.url;
|
||||
if (item?.api?.query) return item;
|
||||
item['api'] = { url: url, query: new Query({ url: url }) };
|
||||
}
|
||||
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;
|
||||
}).filter(item => {
|
||||
const enabled = item.enabled ?? true;
|
||||
return enabled;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化路由
|
||||
* @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<boolean> {
|
||||
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<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');
|
||||
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 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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');
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
type RouterItem = {
|
||||
id?: string;
|
||||
path?: string;
|
||||
key?: string;
|
||||
description?: string;
|
||||
middleware?: string[];
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
64
query/query-secret/query-secret.ts
Normal file
64
query/query-secret/query-secret.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 配置查询
|
||||
* @updatedAt 2025-12-03 10:33:00
|
||||
*/
|
||||
import { Query } from '@kevisual/query';
|
||||
type QueryConfigOpts = {
|
||||
query?: Query;
|
||||
};
|
||||
export type Config<T = any> = {
|
||||
id?: string;
|
||||
title?: string;
|
||||
key?: string;
|
||||
description?: string;
|
||||
data?: T;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
};
|
||||
export type UploadConfig = {
|
||||
key?: string;
|
||||
version?: string;
|
||||
};
|
||||
type PostOpts = {
|
||||
token?: string;
|
||||
payload?: Record<string, any>;
|
||||
};
|
||||
|
||||
export class QueryConfig {
|
||||
query: Query;
|
||||
constructor(opts?: QueryConfigOpts) {
|
||||
this.query = opts?.query || new Query();
|
||||
}
|
||||
async post<T = Config>(data: any) {
|
||||
return this.query.post<T>({ path: 'secret', ...data });
|
||||
}
|
||||
async getItem({ id, key }: { id?: string; key?: string }, opts?: PostOpts) {
|
||||
return this.post({
|
||||
key: 'get',
|
||||
data: {
|
||||
id,
|
||||
key,
|
||||
},
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
async updateItem(data: Config, opts?: PostOpts) {
|
||||
return this.post({
|
||||
key: 'update',
|
||||
data,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
async deleteItem(data: { id?: string, key?: string }, opts?: PostOpts) {
|
||||
return this.post({
|
||||
key: 'delete',
|
||||
data,
|
||||
});
|
||||
}
|
||||
async listItems(opts?: PostOpts) {
|
||||
return this.post<{ list: Config[] }>({
|
||||
key: 'list',
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { QueryUtil } from '@/query/index.ts';
|
||||
import { QueryUtil } from '../../../query/index.ts';
|
||||
|
||||
export const shopDefine = QueryUtil.create({
|
||||
getRegistry: {
|
||||
@@ -4,14 +4,13 @@ import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
|
||||
|
||||
export { shopDefine };
|
||||
|
||||
export class QueryShop<T extends Query = Query> extends BaseQuery<T, typeof shopDefine> {
|
||||
export class QueryShop<T extends Query = Query> extends BaseQuery<T> {
|
||||
constructor(opts?: { query: T }) {
|
||||
super({
|
||||
query: opts?.query!,
|
||||
queryDefine: shopDefine,
|
||||
});
|
||||
}
|
||||
getInstall(data: any, opts?: DataOpts) {
|
||||
return this.queryDefine.queryChain('install').post(data, opts);
|
||||
return this.query.post(data, opts);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import { UploadProgress } from './core/upload-progress.ts';
|
||||
|
||||
export { uploadFiles, uploadFileChunked, UploadProgress };
|
||||
|
||||
export * from './utils/to-file.ts';
|
||||
export { toTextFile, toFile, getDirectoryAndName } from './utils/to-file.ts';
|
||||
|
||||
export { randomId } from './utils/random-id.ts';
|
||||
|
||||
export { filterFiles } from './utils/filter-files.ts';
|
||||
168
readme.md
168
readme.md
@@ -1,3 +1,169 @@
|
||||
# query-awesome
|
||||
# @kevisual/api
|
||||
|
||||
对 kevisual 相关的query router 的模块整理
|
||||
|
||||
包含的模块:
|
||||
|
||||
## query-config - 配置管理模块
|
||||
|
||||
提供配置的增删改查功能,支持默认配置项管理。
|
||||
|
||||
**主要功能:**
|
||||
- 配置的获取、更新、删除
|
||||
- 上传配置管理
|
||||
- 默认配置项支持(upload.json, workspace.json, ai.json, user.json, life.json)
|
||||
- 配置检测功能
|
||||
|
||||
**使用示例:**
|
||||
```typescript
|
||||
import { QueryConfig } from './query-config/query-config';
|
||||
|
||||
const config = new QueryConfig();
|
||||
await config.getConfig({ key: 'upload.json' });
|
||||
await config.updateConfig({ key: 'config.json', data: { setting: true } });
|
||||
```
|
||||
|
||||
## query-secret - 密钥管理模块
|
||||
|
||||
用于管理敏感信息和密钥的存储与检索。
|
||||
|
||||
**主要功能:**
|
||||
- 密钥的存储和获取
|
||||
- 支持按ID或key检索
|
||||
- 密钥列表管理
|
||||
|
||||
## query-proxy - 代理路由模块
|
||||
|
||||
提供动态路由代理功能,可以将请求转发到不同的后端服务。
|
||||
|
||||
**主要功能:**
|
||||
- 动态路由注册
|
||||
- 请求转发代理
|
||||
- 路由列表管理
|
||||
- Token认证支持
|
||||
|
||||
**使用示例:**
|
||||
```typescript
|
||||
import { QueryProxy } from './query-proxy/index';
|
||||
|
||||
const proxy = new QueryProxy({
|
||||
query: new Query(),
|
||||
token: 'your-token'
|
||||
});
|
||||
await proxy.init(); // 初始化路由
|
||||
const result = await proxy.run({ path: 'api', key: 'getData' });
|
||||
```
|
||||
|
||||
## query-upload - 文件上传模块
|
||||
|
||||
功能完整的文件上传解决方案,支持多种上传方式。
|
||||
|
||||
**主要功能:**
|
||||
- 普通文件上传
|
||||
- 分片上传(大文件)
|
||||
- 上传进度跟踪
|
||||
- 文件过滤工具
|
||||
- 文件格式转换
|
||||
|
||||
**核心组件:**
|
||||
- `uploadFiles` - 基础文件上传
|
||||
- `uploadFileChunked` - 分片上传
|
||||
- `UploadProgress` - 进度管理
|
||||
- `filterFiles` - 文件过滤
|
||||
- `toFile` - 格式转换
|
||||
|
||||
**使用示例:**
|
||||
```typescript
|
||||
import { uploadFiles, UploadProgress } from './query-upload/query-upload';
|
||||
|
||||
const progress = new UploadProgress();
|
||||
await uploadFiles(files, {
|
||||
onProgress: (loaded, total) => {
|
||||
console.log(`上传进度: ${loaded}/${total}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## query-app - 应用管理模块
|
||||
|
||||
管理应用的注册、获取和列表功能。
|
||||
|
||||
**主要功能:**
|
||||
- 获取应用列表
|
||||
- 获取公开应用
|
||||
- 获取私有应用
|
||||
- 应用定义管理
|
||||
|
||||
## query-shop - 应用商店模块
|
||||
|
||||
应用商店功能,处理应用的安装和获取。
|
||||
|
||||
**主要功能:**
|
||||
- 应用安装
|
||||
- 商店应用管理
|
||||
|
||||
## query-ai - AI对话模块
|
||||
|
||||
集成AI聊天功能,支持多种模型和对话管理。
|
||||
|
||||
**主要功能:**
|
||||
- AI对话(支持GPT等模型)
|
||||
- 模型列表获取
|
||||
- 聊天使用统计
|
||||
- 缓存管理
|
||||
- 使用限制管理
|
||||
|
||||
**使用示例:**
|
||||
```typescript
|
||||
import { QueryAI } from './query-ai/query-ai';
|
||||
|
||||
const ai = new QueryAI({ query: new Query() });
|
||||
const models = await ai.getModelList();
|
||||
const response = await ai.chat(
|
||||
{ message: '你好' },
|
||||
{ username: 'user', model: 'gpt-3.5', group: 'default' }
|
||||
);
|
||||
```
|
||||
|
||||
## query-login - 登录认证模块
|
||||
|
||||
完整的登录认证解决方案,支持token管理和缓存。
|
||||
|
||||
**主要功能:**
|
||||
- 用户登录认证
|
||||
- Token管理(access/refresh token)
|
||||
- 登录状态缓存
|
||||
- 浏览器/Node.js环境支持
|
||||
- 自动token刷新
|
||||
|
||||
**核心组件:**
|
||||
- `QueryLogin` - 主登录类
|
||||
- `LoginCacheStore` - 登录缓存
|
||||
- `Cache` - 通用缓存接口
|
||||
|
||||
## query-resources - 资源管理模块
|
||||
|
||||
管理用户资源的访问和获取。
|
||||
|
||||
**主要功能:**
|
||||
- 资源文件获取
|
||||
- 资源列表管理
|
||||
- 用户认证支持
|
||||
- 文件预览功能
|
||||
|
||||
**使用示例:**
|
||||
```typescript
|
||||
import { QueryResources } from './query-resources/index';
|
||||
|
||||
const resources = new QueryResources({ username: 'user' });
|
||||
const fileList = await resources.getList('images/');
|
||||
const fileContent = await resources.fetchFile('document.pdf');
|
||||
```
|
||||
|
||||
## 架构特点
|
||||
|
||||
- **模块化设计**:每个模块职责单一,可独立使用
|
||||
- **统一接口**:基于 `@kevisual/query` 的统一查询接口
|
||||
- **环境兼容**:支持浏览器和Node.js环境
|
||||
- **类型安全**:完整的TypeScript类型定义
|
||||
- **扩展性**:易于扩展和自定义
|
||||
|
||||
Submodule submodules/query-config deleted from 53cd97454d
Submodule submodules/query-login deleted from bae8275b11
Submodule submodules/query-mark deleted from f12e6896a9
Submodule submodules/query-upload deleted from efa7fbe62e
28
test/proxy.ts
Normal file
28
test/proxy.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { QueryProxy } from "../query/query-proxy";
|
||||
import { Query } from "@kevisual/query/query";
|
||||
import dotenv from "dotenv";
|
||||
import util from 'node:util'
|
||||
dotenv.config();
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
const token = process.env.KEVISUAL_TOKEN;
|
||||
const url = process.env.KEVISUAL_URL;
|
||||
export const showMore = (obj: any) => {
|
||||
return util.inspect(obj, { showHidden: false, depth: null, colors: true });
|
||||
}
|
||||
const query = new Query({ url: url + '/api/router' });
|
||||
const proxy = new QueryProxy({ query, token });
|
||||
const res = await proxy.init();
|
||||
|
||||
console.log('Proxy Init Result:', showMore(res));
|
||||
|
||||
const routes = await proxy.listRoutes((item) => item.path?.startsWith('router') || false);
|
||||
console.log('Filtered Routes:', showMore(routes));
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
// console.log('Running route [user/me]...', proxy.router.routes.length);
|
||||
|
||||
const run = await proxy.run({ path: 'user', key: 'me' });
|
||||
// const run = await proxy.run({ path: 'router', key: 'list' });
|
||||
console.log('Run Result:', showMore(run));
|
||||
22
turbo.json
22
turbo.json
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
"dist/**"
|
||||
]
|
||||
},
|
||||
"dev:lib": {
|
||||
"persistent": true,
|
||||
"cache": true
|
||||
},
|
||||
"build:lib": {
|
||||
"dependsOn": [
|
||||
"^build:lib"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user