feat: 更新版本至 0.0.49,新增 storeList 支持,优化登录缓存,添加 store-auth 和 store-mark 功能

This commit is contained in:
2026-02-14 03:06:16 +08:00
parent d2ffc8d8ec
commit 05c8496469
9 changed files with 360 additions and 26 deletions

View File

@@ -1,5 +1,5 @@
import { buildWithBun } from '@kevisual/code-builder'
import { queryList } from './src/get-query-list.ts'
import { queryList, storeList } from './src/get-query-list.ts'
// await buildWithBun({ naming: "app", entry: "query/index.ts", meta: import.meta, dts: true })
@@ -12,3 +12,14 @@ for (const query of queryList) {
dts: true,
})
}
for (const store of storeList) {
await buildWithBun({
naming: store.name,
entry: `store/${store.name}/index.ts`,
meta: import.meta,
external: ['sonner', 'zustand', '@kevisual/context'],
target: 'browser',
dts: true,
})
}

View File

@@ -1,6 +1,6 @@
{
"name": "@kevisual/api",
"version": "0.0.48",
"version": "0.0.49",
"description": "",
"main": "mod.ts",
"scripts": {
@@ -12,6 +12,7 @@
"files": [
"dist",
"query",
"store",
"mod.ts"
],
"keywords": [],
@@ -28,15 +29,14 @@
"@kevisual/types": "^0.0.12",
"@kevisual/use-config": "^1.0.30",
"@types/bun": "^1.3.9",
"@types/crypto-js": "^4.2.2",
"@types/node": "^25.2.3",
"@types/spark-md5": "^3.0.5",
"crypto-js": "^4.2.0",
"dotenv": "^17.3.1",
"fast-glob": "^3.3.3",
"ws": "npm:@kevisual/ws"
},
"dependencies": {
"@kevisual/context": "^0.0.4",
"@kevisual/js-filter": "^0.0.5",
"@kevisual/load": "^0.0.6",
"es-toolkit": "^1.44.0",
@@ -44,7 +44,9 @@
"fuse.js": "^7.1.0",
"nanoid": "^5.1.6",
"path-browserify-esm": "^1.0.6",
"spark-md5": "^3.0.2"
"sonner": "^2.0.7",
"spark-md5": "^3.0.2",
"zustand": "^5.0.11"
},
"exports": {
".": "./mod.ts",
@@ -54,9 +56,9 @@
"./proxy": "./query/query-proxy/index.ts",
"./secret": "./query/query-secret/index.ts",
"./resources": "./query/query-resources/index.ts",
"./utils": "./dist/utils.js",
"./query-secret": "./dist/query-secret.js",
"./query-mark": "./dist/query-mark.js",
"./utils": "./dist/utils.js",
"./query-upload": "./dist/query-upload.js",
"./query-app": "./dist/query-app.js",
"./query-proxy": "./dist/query-proxy.js",
@@ -65,7 +67,10 @@
"./query-login": "./dist/query-login.js",
"./query-ai": "./dist/query-ai.js",
"./query-resources": "./dist/query-resources.js",
"./store-mark": "./dist/store-mark.js",
"./store-auth": "./dist/store-auth.js",
"./src/*": "./src/*",
"./store/*": "./src/*",
"./query/*": "./query/*"
}
}

68
pnpm-lock.yaml generated
View File

@@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@kevisual/context':
specifier: ^0.0.4
version: 0.0.4
'@kevisual/js-filter':
specifier: ^0.0.5
version: 0.0.5
@@ -29,9 +32,15 @@ importers:
path-browserify-esm:
specifier: ^1.0.6
version: 1.0.6
sonner:
specifier: ^2.0.7
version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
spark-md5:
specifier: ^3.0.2
version: 3.0.2
zustand:
specifier: ^5.0.11
version: 5.0.11(react@19.2.4)
devDependencies:
'@kevisual/cache':
specifier: ^0.0.5
@@ -108,6 +117,9 @@ packages:
resolution: {integrity: sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw==}
hasBin: true
'@kevisual/context@0.0.4':
resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==}
'@kevisual/js-filter@0.0.5':
resolution: {integrity: sha512-+S+Sf3K/aP6XtZI2s7TgKOr35UuvUvtpJ9YDW30a+mY0/N8gRuzyKhieBzQN7Ykayzz70uoMavBXut2rUlLgzw==}
@@ -398,6 +410,15 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
react-dom@19.2.4:
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
peerDependencies:
react: ^19.2.4
react@19.2.4:
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'}
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -405,10 +426,19 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
selfsigned@2.4.1:
resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==}
engines: {node: '>=10'}
sonner@2.0.7:
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
spark-md5@3.0.2:
resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==}
@@ -441,6 +471,24 @@ packages:
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
zustand@5.0.11:
resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=18.0.0'
immer: '>=9.0.6'
react: '>=18.0.0'
use-sync-external-store: '>=1.2.0'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
snapshots:
'@kevisual/cache@0.0.5':
@@ -451,6 +499,8 @@ snapshots:
'@kevisual/code-builder@0.0.6': {}
'@kevisual/context@0.0.4': {}
'@kevisual/js-filter@0.0.5': {}
'@kevisual/load@0.0.6':
@@ -723,17 +773,31 @@ snapshots:
queue-microtask@1.2.3: {}
react-dom@19.2.4(react@19.2.4):
dependencies:
react: 19.2.4
scheduler: 0.27.0
react@19.2.4: {}
reusify@1.1.0: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
scheduler@0.27.0: {}
selfsigned@2.4.1:
dependencies:
'@types/node-forge': 1.3.11
node-forge: 1.3.1
sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
spark-md5@3.0.2: {}
to-regex-range@5.0.1:
@@ -758,3 +822,7 @@ snapshots:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
zustand@5.0.11(react@19.2.4):
optionalDependencies:
react: 19.2.4

View File

@@ -31,6 +31,8 @@ export type CacheLoginUser = {
id?: string;
accessToken?: string;
refreshToken?: string;
accessTokenExpiresIn?: number;
createdAt?: number;
};
type CacheLogin = {
loginUsers: CacheLoginUser[];
@@ -162,6 +164,8 @@ export class LoginCacheStore implements CacheStore<any> {
this.cacheData.id = user.id;
this.cacheData.accessToken = user.accessToken;
this.cacheData.refreshToken = user.refreshToken;
this.cacheData.accessTokenExpiresIn = user.accessTokenExpiresIn;
this.cacheData.createdAt = user.createdAt;
await this.setValue(this.cacheData);
}

View File

@@ -39,7 +39,7 @@ export class QueryLogin extends BaseQuery {
this.isBrowser = opts?.isBrowser ?? true;
this.init();
this.onLoad = opts?.onLoad;
this.storage = opts?.storage || localStorage;
this.storage = opts?.storage || globalThis?.localStorage;
}
setQuery(query: Query) {
this.query = query;
@@ -67,9 +67,9 @@ export class QueryLogin extends BaseQuery {
async login(data: QueryLoginData) {
const res = await this.post<QueryLoginResult>({ key: 'login', ...data });
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
const { accessToken, refreshToken, accessTokenExpiresIn } = res?.data || {};
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
await this.beforeSetLoginUser({ accessToken, refreshToken, accessTokenExpiresIn });
}
return res;
}
@@ -81,9 +81,9 @@ export class QueryLogin extends BaseQuery {
async loginByCode(data: { phone: string; code: string }) {
const res = await this.post<QueryLoginResult>({ path: 'sms', key: 'login', data });
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
const { accessToken, refreshToken, accessTokenExpiresIn } = res?.data || {};
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
await this.beforeSetLoginUser({ accessToken, refreshToken, accessTokenExpiresIn });
}
return res;
}
@@ -91,17 +91,17 @@ export class QueryLogin extends BaseQuery {
* 设置token
* @param token
*/
async setLoginToken(token: { accessToken: string; refreshToken: string }) {
const { accessToken, refreshToken } = token;
async setLoginToken(token: { accessToken: string; refreshToken: string; accessTokenExpiresIn?: number }) {
const { accessToken, refreshToken, accessTokenExpiresIn } = token;
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
await this.beforeSetLoginUser({ accessToken, refreshToken, accessTokenExpiresIn });
}
async loginByWechat(data: { code: string }) {
const res = await this.post<QueryLoginResult>({ path: 'wx', key: 'open-login', code: data.code });
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
const { accessToken, refreshToken, accessTokenExpiresIn } = res?.data || {};
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
await this.beforeSetLoginUser({ accessToken, refreshToken, accessTokenExpiresIn });
}
return res;
}
@@ -126,7 +126,7 @@ export class QueryLogin extends BaseQuery {
* 登陆成功,需要获取用户信息进行缓存
* @param param0
*/
async beforeSetLoginUser({ accessToken, refreshToken, check401 }: { accessToken?: string; refreshToken?: string; check401?: boolean }) {
async beforeSetLoginUser({ accessToken, refreshToken, check401, accessTokenExpiresIn }: { accessTokenExpiresIn?: number, accessToken?: string; refreshToken?: string; check401?: boolean }) {
if (accessToken && refreshToken) {
const resUser = await this.getMe(accessToken, check401);
if (resUser.code === 200) {
@@ -137,6 +137,8 @@ export class QueryLogin extends BaseQuery {
id: user.id,
accessToken,
refreshToken,
accessTokenExpiresIn,
createdAt: Date.now(),
});
} else {
console.error('登录失败');
@@ -185,9 +187,9 @@ export class QueryLogin extends BaseQuery {
if (hasRefreshToken) {
const res = await that.queryRefreshToken(hasRefreshToken);
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
const { accessToken, refreshToken, accessTokenExpiresIn } = res?.data || {};
that.storage.setItem('token', accessToken || '');
await that.beforeSetLoginUser({ accessToken, refreshToken, check401: false });
await that.beforeSetLoginUser({ accessToken, refreshToken, accessTokenExpiresIn, check401: false });
if (refetch && ctx && ctx.req && ctx.req.url && ctx.fetch) {
await new Promise((resolve) => setTimeout(resolve, 1500));
const url = ctx.req?.url;
@@ -336,12 +338,17 @@ export class QueryLogin extends BaseQuery {
const user = localUserList.find((userItem) => userItem.user!.username === username);
if (user) {
this.storage.setItem('token', user.accessToken || '');
await this.beforeSetLoginUser({ accessToken: user.accessToken, refreshToken: user.refreshToken });
await this.beforeSetLoginUser({
accessToken: user.accessToken,
refreshToken: user.refreshToken,
accessTokenExpiresIn: user.accessTokenExpiresIn
});
return {
code: 200,
data: {
accessToken: user.accessToken,
refreshToken: user.refreshToken,
accessTokenExpiresIn: user.accessTokenExpiresIn,
},
success: true,
message: '切换用户成功',
@@ -350,9 +357,9 @@ export class QueryLogin extends BaseQuery {
const res = await this.postSwitchUser(username);
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
const { accessToken, refreshToken, accessTokenExpiresIn } = res?.data || {};
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
await this.beforeSetLoginUser({ accessToken, refreshToken, accessTokenExpiresIn });
}
return res;
}
@@ -409,9 +416,9 @@ export class QueryLogin extends BaseQuery {
loginToken: token,
});
if (res.code === 200) {
const accessToken = res.data?.accessToken;
const { accessTokenExpiresIn, accessToken, refreshToken } = res.data;
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken: res.data?.refreshToken });
await this.beforeSetLoginUser({ accessToken, refreshToken, accessTokenExpiresIn });
return res;
}
return false;
@@ -421,7 +428,7 @@ export class QueryLogin extends BaseQuery {
*
*
import MD5 from 'crypto-js/md5.js';
// import MD5 from 'crypto-js/md5.js';
import jsonwebtoken from 'jsonwebtoken';
*/

View File

@@ -17,3 +17,21 @@ export const queryList = lists.map((filePath) => {
const pkgs = queryList.map(q => q.pkgs).join('\n');
console.log('Package Entries:\n\r', pkgs);
const storeLists = glob.sync('store/*/index.ts', {
absolute: true,
});
export const storeList = storeLists.map((filePath) => {
const segments = filePath.split('/');
const storeName = segments[segments.length - 2];
return {
name: storeName,
pkgs: `"./${storeName}": "./dist/${storeName}.js",`
};
});
// console.log('Store List:', storeList);
const storePkgs = storeList.map(s => s.pkgs).join('\n');
console.log('Store Entries:\n\r', storePkgs);

66
store/store-auth/index.ts Normal file
View File

@@ -0,0 +1,66 @@
'use strict';
import { create } from 'zustand';
import { toast } from 'sonner';
import { useContextKey } from '@kevisual/context';
import { type QueryLoginBrowser } from '@/query/query-login/index.ts'
const queryLogin = useContextKey<QueryLoginBrowser>('queryLogin');
type Me = {
id?: string;
username?: string;
nickname?: string | null;
needChangePassword?: boolean;
role?: string;
description?: string | null;
type?: 'user' | 'org';
orgs?: string[];
avatar?: string;
};
export type LayoutStore = {
open: boolean;
setOpen: (open: boolean) => void;
me: Me;
setMe: (me: Me) => void;
getMe: () => Promise<void>;
openUser: boolean;
setOpenUser: (openUser: boolean) => void;
switchOrg: (username?: string, type?: 'user' | 'org') => Promise<void>;
isAdmin: boolean;
setIsAdmin: (isAdmin: boolean) => void;
checkHasOrg: () => boolean;
};
export const useLayoutStore = create<LayoutStore>((set, get) => ({
open: false,
setOpen: (open) => set({ open }),
me: {},
setMe: (me) => set({ me }),
getMe: async () => {
const res = await queryLogin.getMe();
if (res.code === 200) {
set({ me: res.data });
set({ isAdmin: res.data.orgs?.includes('admin') });
}
},
openUser: false,
setOpenUser: (openUser) => set({ openUser }),
switchOrg: async (username?: string, type?: string) => {
const res = await queryLogin.switchUser(username || '');
if (res.code === 200) {
toast.success('Switch success');
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
toast.error(res.message || 'Request failed');
}
},
isAdmin: false,
setIsAdmin: (isAdmin) => set({ isAdmin }),
checkHasOrg: () => {
const user = get().me || {};
if (!user.orgs) {
return false;
}
return user?.orgs?.length > 0;
},
}));

132
store/store-mark/index.ts Normal file
View File

@@ -0,0 +1,132 @@
import { create } from 'zustand';
import { type Result } from '@kevisual/query/query';
import { MarkType, Mark, QueryMark } from '@kevisual/api/query-mark';
import { uniqBy } from 'es-toolkit';
import { useContextKey } from '@kevisual/context';
import { type QueryLoginBrowser } from '@/query/query-login/index.ts'
const queryClient = useContextKey<QueryLoginBrowser>('queryLogin');
type ManagerStore = {
/** 当前选中的Mark */
currentMarkId: string;
setCurrentMarkId: (markId: string) => void;
markData?: Mark | undefined;
setMarkData: (mark?: Partial<Mark>) => void;
/** 获取Mark列表 */
getList: () => Promise<any>;
getMarkFromList: (markId: string) => Mark | undefined;
updateMark: (mark: Mark) => Promise<any>;
getMark: (markId: string) => Promise<Result<Mark>>;
deleteMark: (markId: string) => Promise<any>;
/** Mark列表 */
list: Mark[];
setList: (list: Mark[]) => void;
pagination: {
current: number;
pageSize: number;
total: number;
};
setPagination: (pagination: { current: number; pageSize: number; total: number }) => void;
/** 搜索 */
search: string;
setSearch: (search: string) => void;
/** 初始化 */
init: (markType: MarkType) => Promise<void>;
queryMark?: QueryMark;
markType?: MarkType;
open: boolean;
setOpen: (open: boolean) => void;
};
export const useMarkStore = create<ManagerStore>((set, get) => {
return {
currentMarkId: '',
setCurrentMarkId: (markId: string) => set(() => ({ currentMarkId: markId })),
open: false,
setOpen: (open: boolean) => set(() => ({ open })),
getList: async () => {
const queryMark = get().queryMark!;
const { search, pagination } = get();
const res = await queryMark.getMarkList({ page: pagination.current, pageSize: pagination.pageSize, search });
const oldList = get().list;
if (res.code === 200) {
const { pagination, list } = res.data || {};
const newList = [...oldList, ...list!];
const uniqueList = uniqBy(newList, (item) => item.id);
set(() => ({ list: uniqueList }));
set(() => ({ pagination: { current: pagination!.current, pageSize: pagination!.pageSize, total: pagination!.total } }));
}
},
getMarkFromList: (markId: string) => {
return get().list.find((item) => item.id === markId);
},
updateMark: async (mark: Mark) => {
const queryMark = get().queryMark!;
const res = await queryMark.updateMark(mark);
if (res.code === 200) {
set((state) => {
const oldList = state.list;
const resMark = res.data!;
const newList = oldList.map((item) => (item.id === mark.id ? mark : item));
if (!mark.id) {
newList.unshift(resMark);
}
return {
list: newList,
};
});
}
return res;
},
getMark: async (markId: string) => {
const queryMark = get().queryMark!;
const res = await queryMark.getMark(markId);
return res;
},
list: [],
setList: (list: any[]) => set(() => ({ list })),
init: async (markType: MarkType = 'wallnote') => {
// await get().getList();
console.log('init', set, get);
const queryMark = new QueryMark({
query: queryClient as any,
markType,
});
const url = new URL(window.location.href);
const pageSize = url.searchParams.get('pageSize') || '10';
set({ queryMark, markType, list: [], pagination: { current: 1, pageSize: parseInt(pageSize), total: 0 }, currentMarkId: '', markData: undefined });
setTimeout(async () => {
console.log('get', get);
get().getList();
}, 1000);
},
deleteMark: async (markId: string) => {
const queryMark = get().queryMark!;
const res = await queryMark.deleteMark(markId);
const currentMarkId = get().currentMarkId;
if (res.code === 200) {
// get().getList();
set((state) => ({
list: state.list.filter((item) => item.id !== markId),
}));
if (currentMarkId === markId) {
set(() => ({ currentMarkId: '', markData: undefined }));
}
}
return res;
},
queryMark: undefined,
markType: 'simple',
markData: undefined,
setMarkData: (mark?: Partial<Mark>) => set(() => ({ markData: mark as Mark })),
pagination: {
current: 1,
pageSize: 10,
total: 0,
},
setPagination: (pagination: { current: number; pageSize: number; total: number }) => set(() => ({ pagination })),
/** 搜索 */
search: '',
setSearch: (search: string) => set(() => ({ search, list: [], pagination: { current: 1, pageSize: 10, total: 0 } })),
};
});

23
tsconfig.json Normal file
View File

@@ -0,0 +1,23 @@
{
"extends": "@kevisual/types/json/frontend.json",
"compilerOptions": {
"baseUrl": "./",
"jsx": "react-jsx",
"paths": {
"@/query*": [
"query/*"
],
"@/store*": [
"store/*"
],
"@/*": [
"src/*"
]
},
},
"include": [
"src",
"query",
"store",
]
}