@kevisual/api 0.0.60 → 0.0.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/query/query-login/browser-cache/cache-store.ts +123 -0
- package/query/query-login/browser-cache/cache.ts +29 -0
- package/query/query-login/login-cache.ts +13 -11
- package/query/query-login/query-login-browser.ts +1 -1
- package/query/query-login/query-login.ts +23 -5
- package/query/query-proxy/proxy.ts +23 -5
- package/query/query-proxy/router-api-proxy.ts +4 -0
package/package.json
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createStore, UseStore, get, set, del, clear, keys, values, entries, update, setMany, getMany, delMany } from 'idb-keyval';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 缓存存储选项
|
|
5
|
+
*/
|
|
6
|
+
export type CacheStoreOpts = {
|
|
7
|
+
/**
|
|
8
|
+
* 数据库名称
|
|
9
|
+
*/
|
|
10
|
+
dbName?: string;
|
|
11
|
+
/**
|
|
12
|
+
* 存储空间名称
|
|
13
|
+
*/
|
|
14
|
+
storeName?: string;
|
|
15
|
+
};
|
|
16
|
+
export class BaseCacheStore {
|
|
17
|
+
store: UseStore;
|
|
18
|
+
constructor(opts?: CacheStoreOpts) {
|
|
19
|
+
this.store = createStore(opts?.dbName || 'default-db', opts?.storeName || 'cache-store');
|
|
20
|
+
}
|
|
21
|
+
async get(key: string) {
|
|
22
|
+
return get(key, this.store);
|
|
23
|
+
}
|
|
24
|
+
async set(key: string, value: any) {
|
|
25
|
+
return set(key, value, this.store);
|
|
26
|
+
}
|
|
27
|
+
async del(key: string) {
|
|
28
|
+
return del(key, this.store);
|
|
29
|
+
}
|
|
30
|
+
async clear() {
|
|
31
|
+
return clear(this.store);
|
|
32
|
+
}
|
|
33
|
+
async keys() {
|
|
34
|
+
return keys(this.store);
|
|
35
|
+
}
|
|
36
|
+
async values() {
|
|
37
|
+
return values(this.store);
|
|
38
|
+
}
|
|
39
|
+
async entries() {
|
|
40
|
+
return entries(this.store);
|
|
41
|
+
}
|
|
42
|
+
async update(key: string, updater: (value: any) => any) {
|
|
43
|
+
return update(key, updater, this.store);
|
|
44
|
+
}
|
|
45
|
+
async setMany(entries: [string, any][]) {
|
|
46
|
+
return setMany(entries, this.store);
|
|
47
|
+
}
|
|
48
|
+
async getMany(keys: string[]) {
|
|
49
|
+
return getMany(keys, this.store);
|
|
50
|
+
}
|
|
51
|
+
async delMany(keys: string[]) {
|
|
52
|
+
return delMany(keys, this.store);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 缓存存储
|
|
58
|
+
*/
|
|
59
|
+
export class CacheStore extends BaseCacheStore {
|
|
60
|
+
constructor(opts?: CacheStoreOpts) {
|
|
61
|
+
super(opts);
|
|
62
|
+
}
|
|
63
|
+
async getData<T = any>(key: string) {
|
|
64
|
+
const data = await this.get(key);
|
|
65
|
+
return data.data as T;
|
|
66
|
+
}
|
|
67
|
+
async setData(key: string, data: any) {
|
|
68
|
+
return this.set(key, data);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 获取缓存数据,并检查是否过期
|
|
72
|
+
* @param key 缓存键
|
|
73
|
+
* @returns 缓存数据
|
|
74
|
+
*/
|
|
75
|
+
async getCheckData<T = any>(key: string) {
|
|
76
|
+
const data = await this.get(key);
|
|
77
|
+
if (data.expireTime && data.expireTime < Date.now()) {
|
|
78
|
+
await super.del(key);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return data.data as T;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 设置缓存数据,并检查是否过期
|
|
85
|
+
* @param key 缓存键
|
|
86
|
+
* @param data 缓存数据
|
|
87
|
+
* @param opts 缓存选项
|
|
88
|
+
* @returns 缓存数据
|
|
89
|
+
*/
|
|
90
|
+
async setCheckData(key: string, data: any, opts?: { expireTime?: number; updatedAt?: number }) {
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const expireTime = now + (opts?.expireTime || 1000 * 60 * 60 * 24 * 10);
|
|
93
|
+
const newData = {
|
|
94
|
+
data,
|
|
95
|
+
updatedAt: opts?.updatedAt || Date.now(),
|
|
96
|
+
expireTime,
|
|
97
|
+
};
|
|
98
|
+
await this.set(key, newData);
|
|
99
|
+
return data;
|
|
100
|
+
}
|
|
101
|
+
async checkNew(key: string, data: any): Promise<boolean> {
|
|
102
|
+
const existing = await this.get(key);
|
|
103
|
+
if (!existing) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (!data?.updatedAt) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const updatedAt = new Date(data.updatedAt).getTime();
|
|
110
|
+
if (isNaN(updatedAt)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return updatedAt > existing.updatedAt;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 删除缓存数据
|
|
117
|
+
* @param key 缓存键
|
|
118
|
+
* @returns 缓存数据
|
|
119
|
+
*/
|
|
120
|
+
async delCheckData(key: string) {
|
|
121
|
+
return this.del(key);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { CacheStore, BaseCacheStore } from './cache-store.ts'
|
|
2
|
+
import { CacheStore } from './cache-store.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 一个简单的缓存类,用于存储字符串。
|
|
6
|
+
* 对数据进行添加对比内容。
|
|
7
|
+
*/
|
|
8
|
+
export class MyCache<T = any> extends CacheStore {
|
|
9
|
+
key: string;
|
|
10
|
+
constructor(opts?: { key?: string }) {
|
|
11
|
+
const { key, ...rest } = opts || {};
|
|
12
|
+
super(rest);
|
|
13
|
+
this.key = key || 'my-cache';
|
|
14
|
+
}
|
|
15
|
+
async getData<U = T>(key: string = this.key): Promise<U> {
|
|
16
|
+
return super.getCheckData<U>(key) as any;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 设置缓存数据,默认过期时间为10天
|
|
20
|
+
* @param data
|
|
21
|
+
* @param opts
|
|
22
|
+
*/
|
|
23
|
+
async setData<U = T>(data: U, opts?: { expireTime?: number, updatedAt?: number }) {
|
|
24
|
+
super.setCheckData(this.key, data, opts);
|
|
25
|
+
}
|
|
26
|
+
async del(): Promise<void> {
|
|
27
|
+
await super.del(this.key);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -146,19 +146,21 @@ export class LoginCacheStore<T extends Cache = Cache> implements CacheStore<T> {
|
|
|
146
146
|
/**
|
|
147
147
|
* 初始化,设置默认值
|
|
148
148
|
*/
|
|
149
|
-
async init() {
|
|
149
|
+
async init(): Promise<CacheLogin> {
|
|
150
150
|
const defaultData: CacheLogin = { ...this.cacheData };
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
return new Promise(async (resolve) => {
|
|
152
|
+
if (this.cache.init) {
|
|
153
|
+
try {
|
|
154
|
+
const cacheData = await this.cache.init();
|
|
155
|
+
this.cacheData = cacheData || defaultData;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.log('cacheInit error', error);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
this.cacheData = (await this.getValue()) || defaultData;
|
|
157
161
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
return this.cacheData;
|
|
162
|
+
resolve(this.cacheData);
|
|
163
|
+
});
|
|
162
164
|
}
|
|
163
165
|
/**
|
|
164
166
|
* 设置当前用户
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { QueryLogin, QueryLoginOpts } from './query-login.ts';
|
|
2
|
-
import { MyCache } from '
|
|
2
|
+
import { MyCache } from './browser-cache/cache.ts';
|
|
3
3
|
type QueryLoginNodeOptsWithoutCache = Omit<QueryLoginOpts, 'cache'>;
|
|
4
4
|
|
|
5
5
|
export class QueryLoginBrowser extends QueryLogin {
|
|
@@ -3,6 +3,7 @@ import type { Result, DataOpts } from '@kevisual/query/query';
|
|
|
3
3
|
import { LoginCacheStore, CacheStore, User } from './login-cache.ts';
|
|
4
4
|
import { Cache } from './login-cache.ts';
|
|
5
5
|
import { BaseLoad } from '@kevisual/load';
|
|
6
|
+
import { EventEmitter } from 'eventemitter3'
|
|
6
7
|
export type QueryLoginOpts<T extends Cache = Cache> = {
|
|
7
8
|
query?: Query;
|
|
8
9
|
isBrowser?: boolean;
|
|
@@ -26,9 +27,11 @@ export class QueryLogin<T extends Cache = Cache> extends BaseQuery {
|
|
|
26
27
|
*/
|
|
27
28
|
cacheStore: CacheStore<T>;
|
|
28
29
|
isBrowser: boolean;
|
|
29
|
-
load?: boolean;
|
|
30
30
|
storage: Storage;
|
|
31
|
+
load: boolean = false;
|
|
32
|
+
status: 'init' | 'logining' | 'loginSuccess' | 'loginError' = 'init';
|
|
31
33
|
onLoad?: () => void;
|
|
34
|
+
emitter = new EventEmitter();
|
|
32
35
|
|
|
33
36
|
constructor(opts?: QueryLoginOpts<T>) {
|
|
34
37
|
super({
|
|
@@ -42,14 +45,29 @@ export class QueryLogin<T extends Cache = Cache> extends BaseQuery {
|
|
|
42
45
|
if (!this.storage) {
|
|
43
46
|
throw new Error('storage is required');
|
|
44
47
|
}
|
|
48
|
+
this.cacheStore.init().then(() => {
|
|
49
|
+
this.onLoad?.();
|
|
50
|
+
this.load = true;
|
|
51
|
+
this.emitter.emit('load');
|
|
52
|
+
});
|
|
45
53
|
}
|
|
46
54
|
setQuery(query: Query) {
|
|
47
55
|
this.query = query;
|
|
48
56
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
async init() {
|
|
58
|
+
if (this.load) {
|
|
59
|
+
return this.cacheStore.cacheData;
|
|
60
|
+
}
|
|
61
|
+
return new Promise(async (resolve) => {
|
|
62
|
+
const timer = setTimeout(() => {
|
|
63
|
+
resolve(this.cacheStore.cacheData);
|
|
64
|
+
}, 1000 * 20); // 20秒超时,避免一直等待
|
|
65
|
+
const listener = () => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
resolve(this.cacheStore.cacheData);
|
|
68
|
+
}
|
|
69
|
+
this.emitter.once('load', listener);
|
|
70
|
+
});
|
|
53
71
|
}
|
|
54
72
|
async post<T = any>(data: any, opts?: DataOpts) {
|
|
55
73
|
try {
|
|
@@ -4,6 +4,7 @@ import { filter } from '@kevisual/js-filter'
|
|
|
4
4
|
import { EventEmitter } from 'eventemitter3';
|
|
5
5
|
import { initApi } from './router-api-proxy.ts';
|
|
6
6
|
import Fuse from 'fuse.js';
|
|
7
|
+
import { cloneDeep } from 'es-toolkit';
|
|
7
8
|
|
|
8
9
|
export const RouteTypeList = ['api', 'context', 'worker', 'page'] as const;
|
|
9
10
|
export type RouterViewItemInfo = RouterViewApi | RouterViewContext | RouterViewWorker | RouteViewPage;
|
|
@@ -26,6 +27,10 @@ type RouteViewBase = {
|
|
|
26
27
|
* 默认动作配置
|
|
27
28
|
*/
|
|
28
29
|
action?: { path?: string; key?: string; id?: string; payload?: any;[key: string]: any };
|
|
30
|
+
/**
|
|
31
|
+
* 本地状态,loading、active、error等
|
|
32
|
+
*/
|
|
33
|
+
routerStatus?: 'loading' | 'active' | 'inactive' | 'error';
|
|
29
34
|
}
|
|
30
35
|
export type RouterViewApi = {
|
|
31
36
|
type: 'api',
|
|
@@ -67,7 +72,7 @@ export type RouterViewWorker = {
|
|
|
67
72
|
* @returns
|
|
68
73
|
*/
|
|
69
74
|
export const pickRouterViewData = (item: RouterViewItem) => {
|
|
70
|
-
const { action, response, _id, ...rest } = item;
|
|
75
|
+
const { action, response, _id, ...rest } = cloneDeep(item);
|
|
71
76
|
if (rest.type === 'api') {
|
|
72
77
|
if (rest.api) {
|
|
73
78
|
delete rest.api.query;
|
|
@@ -83,6 +88,7 @@ export const pickRouterViewData = (item: RouterViewItem) => {
|
|
|
83
88
|
delete rest.context.router;
|
|
84
89
|
}
|
|
85
90
|
}
|
|
91
|
+
delete rest.routerStatus;
|
|
86
92
|
return rest
|
|
87
93
|
}
|
|
88
94
|
/**
|
|
@@ -98,7 +104,7 @@ export type RouteViewPage = {
|
|
|
98
104
|
export type RouterViewQuery = {
|
|
99
105
|
id: string,
|
|
100
106
|
query: string,
|
|
101
|
-
title: string
|
|
107
|
+
title: string,
|
|
102
108
|
}
|
|
103
109
|
/**
|
|
104
110
|
* 后端存储结构
|
|
@@ -143,6 +149,7 @@ export class QueryProxy {
|
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
private initRouterView(item: RouterViewItem) {
|
|
152
|
+
item.routerStatus = 'loading';
|
|
146
153
|
if (item.type === 'api' && item.api?.url) {
|
|
147
154
|
const url = item.api.url;
|
|
148
155
|
if (item?.api?.query) return item;
|
|
@@ -245,10 +252,14 @@ export class QueryProxy {
|
|
|
245
252
|
// @ts-ignore
|
|
246
253
|
const context = globalThis['context'] || {}
|
|
247
254
|
const router = item?.context?.router || context[item?.context?.key] as QueryRouterServer;
|
|
255
|
+
if (item) {
|
|
256
|
+
item.routerStatus = router ? 'active' : 'error';
|
|
257
|
+
}
|
|
248
258
|
if (!router) {
|
|
249
259
|
console.warn(`未发现Context router ${item?.context?.key}`);
|
|
250
260
|
return
|
|
251
261
|
}
|
|
262
|
+
|
|
252
263
|
const routes = router.getList();
|
|
253
264
|
// TODO: args
|
|
254
265
|
// const args = fromJSONSchema(r);
|
|
@@ -308,6 +319,9 @@ export class QueryProxy {
|
|
|
308
319
|
}
|
|
309
320
|
const viewItem = item.worker;
|
|
310
321
|
const worker = viewItem?.worker;
|
|
322
|
+
if (item) {
|
|
323
|
+
item.routerStatus = worker ? 'active' : 'error';
|
|
324
|
+
}
|
|
311
325
|
if (!worker) {
|
|
312
326
|
console.warn('Worker not initialized');
|
|
313
327
|
return;
|
|
@@ -377,11 +391,15 @@ export class QueryProxy {
|
|
|
377
391
|
const url = item.page.url;
|
|
378
392
|
try {
|
|
379
393
|
if (typeof window !== 'undefined') {
|
|
380
|
-
await import(url)
|
|
381
|
-
|
|
382
|
-
|
|
394
|
+
await import(url)
|
|
395
|
+
if (item) {
|
|
396
|
+
item.routerStatus = 'active';
|
|
397
|
+
}
|
|
383
398
|
}
|
|
384
399
|
} catch (e) {
|
|
400
|
+
if (item) {
|
|
401
|
+
item.routerStatus = 'error';
|
|
402
|
+
}
|
|
385
403
|
console.warn('引入Page脚本失败:', url, e);
|
|
386
404
|
return;
|
|
387
405
|
}
|
|
@@ -17,12 +17,16 @@ export const initApi = async (opts: {
|
|
|
17
17
|
const token = opts?.token;
|
|
18
18
|
const query = item?.api?.query || new Query({ url: item?.api?.url || '/api/router' })
|
|
19
19
|
const res = await query.post<{ list: RouterItem[] }>({ path: "router", key: 'list', token: token });
|
|
20
|
+
if (item) {
|
|
21
|
+
item.routerStatus = res?.code === 200 ? 'active' : 'error';
|
|
22
|
+
}
|
|
20
23
|
if (res.code !== 200) {
|
|
21
24
|
return {
|
|
22
25
|
code: res.code,
|
|
23
26
|
message: `初始化路由失败: ${res.message}, url: ${query.url}`
|
|
24
27
|
}
|
|
25
28
|
}
|
|
29
|
+
|
|
26
30
|
let _list = res.data?.list || []
|
|
27
31
|
if (opts?.exclude) {
|
|
28
32
|
if (opts?.exclude) {
|