@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevisual/api",
3
- "version": "0.0.60",
3
+ "version": "0.0.61",
4
4
  "description": "",
5
5
  "main": "mod.ts",
6
6
  "scripts": {
@@ -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
- if (this.cache.init) {
152
- try {
153
- const cacheData = await this.cache.init();
154
- this.cacheData = cacheData || defaultData;
155
- } catch (error) {
156
- console.log('cacheInit error', error);
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
- } else {
159
- this.cacheData = (await this.getValue()) || defaultData;
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 '@kevisual/cache';
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
- private async init() {
50
- await this.cacheStore.init();
51
- this.load = true;
52
- this.onLoad?.();
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).then((module) => { }).catch((err) => {
381
- console.error('引入Page脚本失败:', url, err);
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) {