@shihengtech/utils 0.0.1
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/README.md +94 -0
- package/dist/index.cjs +191 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +87 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +182 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# @shihengtech/utils
|
|
2
|
+
|
|
3
|
+
一个轻量级的 JavaScript/TypeScript 工具函数库。
|
|
4
|
+
|
|
5
|
+
## ✨ 特性
|
|
6
|
+
|
|
7
|
+
- 🚀 **轻量高效** - 精简的代码实现,最小化包体积
|
|
8
|
+
- 📦 **双格式支持** - 同时支持 ESM 和 CommonJS
|
|
9
|
+
- 🔒 **类型安全** - 使用 TypeScript 编写,提供完整的类型定义
|
|
10
|
+
- 🌲 **Tree Shaking** - 支持按需引入,减少打包体积
|
|
11
|
+
- ✅ **完善测试** - 高覆盖率的单元测试
|
|
12
|
+
|
|
13
|
+
## 📦 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# npm
|
|
17
|
+
npm install @shihengtech/utils
|
|
18
|
+
|
|
19
|
+
# pnpm
|
|
20
|
+
pnpm add @shihengtech/utils
|
|
21
|
+
|
|
22
|
+
# yarn
|
|
23
|
+
yarn add @shihengtech/utils
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 🔨 使用
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { isEmpty, isString } from '@shihengtech/utils'
|
|
30
|
+
|
|
31
|
+
// 类型判断
|
|
32
|
+
isString('hello') // true
|
|
33
|
+
isString(123) // false
|
|
34
|
+
|
|
35
|
+
// 空值检查
|
|
36
|
+
isEmpty('') // true
|
|
37
|
+
isEmpty([]) // true
|
|
38
|
+
isEmpty({}) // true
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 📖 文档
|
|
42
|
+
|
|
43
|
+
查看完整文档:[文档站点](./docs)
|
|
44
|
+
|
|
45
|
+
## 🛠️ 开发
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 安装依赖
|
|
49
|
+
pnpm install
|
|
50
|
+
|
|
51
|
+
# 开发模式(监听文件变化)
|
|
52
|
+
pnpm dev
|
|
53
|
+
|
|
54
|
+
# 构建
|
|
55
|
+
pnpm build
|
|
56
|
+
|
|
57
|
+
# 运行测试
|
|
58
|
+
pnpm test
|
|
59
|
+
|
|
60
|
+
# 运行测试(单次)
|
|
61
|
+
pnpm test:run
|
|
62
|
+
|
|
63
|
+
# 运行测试(覆盖率)
|
|
64
|
+
pnpm test:coverage
|
|
65
|
+
|
|
66
|
+
# 启动文档开发服务器
|
|
67
|
+
pnpm docs:dev
|
|
68
|
+
|
|
69
|
+
# 构建文档
|
|
70
|
+
pnpm docs:build
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 📁 项目结构
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
@shihengtech/utils/
|
|
77
|
+
├── src/ # 源代码
|
|
78
|
+
│ ├── index.ts # 入口文件
|
|
79
|
+
│ ├── is.ts # 类型判断工具
|
|
80
|
+
│ └── is.test.ts # 测试文件
|
|
81
|
+
├── docs/ # VitePress 文档
|
|
82
|
+
│ ├── .vitepress/ # VitePress 配置
|
|
83
|
+
│ ├── guide/ # 使用指南
|
|
84
|
+
│ └── api/ # API 文档
|
|
85
|
+
├── dist/ # 构建输出(git ignored)
|
|
86
|
+
├── tsconfig.json # TypeScript 配置
|
|
87
|
+
├── tsup.config.ts # 构建配置
|
|
88
|
+
├── vitest.config.ts # 测试配置
|
|
89
|
+
└── package.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 📄 License
|
|
93
|
+
|
|
94
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var localforage = require('localforage');
|
|
4
|
+
var ramda = require('ramda');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var localforage__default = /*#__PURE__*/_interopDefault(localforage);
|
|
9
|
+
|
|
10
|
+
// src/class/ReplaySubject/index.ts
|
|
11
|
+
var ReplaySubject = class {
|
|
12
|
+
constructor(maxBufferSize = 1) {
|
|
13
|
+
this.buffer = [];
|
|
14
|
+
this.subscriptions = [];
|
|
15
|
+
this.maxBufferSize = maxBufferSize;
|
|
16
|
+
}
|
|
17
|
+
subscribe(fn) {
|
|
18
|
+
this.subscriptions.push(fn);
|
|
19
|
+
this.buffer.forEach((data) => fn(data));
|
|
20
|
+
return () => this.unsubscribe(fn);
|
|
21
|
+
}
|
|
22
|
+
unsubscribe(fn) {
|
|
23
|
+
this.subscriptions = this.subscriptions.filter((f) => f !== fn);
|
|
24
|
+
}
|
|
25
|
+
next(data) {
|
|
26
|
+
this.subscriptions.forEach((fn) => fn(data));
|
|
27
|
+
this.buffer.push(data);
|
|
28
|
+
if (this.buffer.length > this.maxBufferSize) {
|
|
29
|
+
this.buffer = this.buffer.slice(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// src/utils/fnRunner/index.ts
|
|
35
|
+
async function fnRunner(fn, retryTimes = 0) {
|
|
36
|
+
const times = (retryTimes || 0) + 1;
|
|
37
|
+
for (let i = 0; i < times; i++) {
|
|
38
|
+
try {
|
|
39
|
+
return await fn();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (i === times - 1) {
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
throw new Error("fnRunner: Unexpected error - all retries exhausted.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/utils/createQueryWithCache/index.ts
|
|
50
|
+
var db = localforage__default.default.createInstance({
|
|
51
|
+
name: "sh-common-cache"
|
|
52
|
+
// driver: [localforage.LOCALSTORAGE, localforage.INDEXEDDB, localforage.WEBSQL],
|
|
53
|
+
});
|
|
54
|
+
function createQueryWithCache(key, fn, options) {
|
|
55
|
+
const {
|
|
56
|
+
retry,
|
|
57
|
+
maxAge,
|
|
58
|
+
version: version2,
|
|
59
|
+
compareBeforeUpdate,
|
|
60
|
+
remoteMemoryCache,
|
|
61
|
+
equals,
|
|
62
|
+
cacheEnabled,
|
|
63
|
+
beforeRequest,
|
|
64
|
+
onSuccess,
|
|
65
|
+
onError,
|
|
66
|
+
errorHandler
|
|
67
|
+
} = {
|
|
68
|
+
equals: (a, b) => a.length === b.length && a.every((item, index) => item === b[index]),
|
|
69
|
+
retry: 0,
|
|
70
|
+
compareBeforeUpdate: ramda.equals,
|
|
71
|
+
remoteMemoryCache: false,
|
|
72
|
+
...options
|
|
73
|
+
};
|
|
74
|
+
const cacheUpdateSubject = new ReplaySubject(1);
|
|
75
|
+
const getLocalCache = async (args) => {
|
|
76
|
+
const cache = await db.getItem(key);
|
|
77
|
+
if (cache && (!version2 || cache.version === version2) && (!cache.expires || cache.expires > Date.now()) && equals(cache.args, args)) {
|
|
78
|
+
return cache.result;
|
|
79
|
+
}
|
|
80
|
+
throw new Error("Cache not found");
|
|
81
|
+
};
|
|
82
|
+
const remoteResultCache = {
|
|
83
|
+
success: false,
|
|
84
|
+
args: [],
|
|
85
|
+
result: null
|
|
86
|
+
};
|
|
87
|
+
const clearMemoryCache = () => {
|
|
88
|
+
remoteResultCache.success = false;
|
|
89
|
+
remoteResultCache.result = null;
|
|
90
|
+
remoteResultCache.args = [];
|
|
91
|
+
};
|
|
92
|
+
const queryWithMemoryCache = (args) => {
|
|
93
|
+
if (remoteResultCache.success && equals(remoteResultCache.args, args)) {
|
|
94
|
+
return {
|
|
95
|
+
type: "memory",
|
|
96
|
+
promiseResult: remoteResultCache.result
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
remoteResultCache.args = args;
|
|
100
|
+
remoteResultCache.success = true;
|
|
101
|
+
const result = remoteResultCache.result = fnRunner(
|
|
102
|
+
() => fn(...args),
|
|
103
|
+
retry
|
|
104
|
+
);
|
|
105
|
+
Promise.resolve(result).then((result2) => {
|
|
106
|
+
!remoteMemoryCache && clearMemoryCache();
|
|
107
|
+
return result2;
|
|
108
|
+
}).catch(clearMemoryCache);
|
|
109
|
+
return { type: "remote", promiseResult: result };
|
|
110
|
+
};
|
|
111
|
+
const queryRemote = async (args) => {
|
|
112
|
+
const rs = {
|
|
113
|
+
type: "remote",
|
|
114
|
+
result: null
|
|
115
|
+
};
|
|
116
|
+
try {
|
|
117
|
+
const { type, promiseResult } = queryWithMemoryCache(args);
|
|
118
|
+
const result = rs.result = await promiseResult;
|
|
119
|
+
type === "remote" && (!cacheEnabled || await cacheEnabled(args)) && Promise.resolve().then(async () => {
|
|
120
|
+
const oldCache = await getLocalCache(args).catch(() => null);
|
|
121
|
+
const cacheData = {
|
|
122
|
+
args,
|
|
123
|
+
result,
|
|
124
|
+
...version2 && { version: version2 },
|
|
125
|
+
...maxAge && { expires: Date.now() + maxAge }
|
|
126
|
+
};
|
|
127
|
+
db.setItem(key, cacheData);
|
|
128
|
+
if (compareBeforeUpdate && oldCache && compareBeforeUpdate(oldCache.result, result)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
cacheUpdateSubject.next({
|
|
132
|
+
result,
|
|
133
|
+
cacheData,
|
|
134
|
+
isCacheHit: !!oldCache
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (errorHandler) {
|
|
139
|
+
rs.result = await errorHandler(error);
|
|
140
|
+
} else {
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return rs.result;
|
|
145
|
+
};
|
|
146
|
+
const _fn = async (...args) => {
|
|
147
|
+
await beforeRequest?.();
|
|
148
|
+
const [cacheResult, remoteResult] = [
|
|
149
|
+
getLocalCache(args),
|
|
150
|
+
queryRemote(args)
|
|
151
|
+
];
|
|
152
|
+
cacheResult.then(
|
|
153
|
+
(result2) => onSuccess?.({
|
|
154
|
+
type: "local",
|
|
155
|
+
result: result2
|
|
156
|
+
})
|
|
157
|
+
).catch(() => {
|
|
158
|
+
});
|
|
159
|
+
remoteResult.then(
|
|
160
|
+
(result2) => onSuccess?.({
|
|
161
|
+
type: "remote",
|
|
162
|
+
result: result2
|
|
163
|
+
})
|
|
164
|
+
).catch((error) => onError?.(error)).catch(() => {
|
|
165
|
+
});
|
|
166
|
+
const result = await Promise.race([
|
|
167
|
+
cacheResult.catch(() => remoteResult),
|
|
168
|
+
remoteResult
|
|
169
|
+
]);
|
|
170
|
+
return result;
|
|
171
|
+
};
|
|
172
|
+
_fn.subscribeCacheUpdate = (fn2) => cacheUpdateSubject.subscribe(fn2);
|
|
173
|
+
_fn.clearCache = async () => {
|
|
174
|
+
clearMemoryCache();
|
|
175
|
+
await db.removeItem(key);
|
|
176
|
+
};
|
|
177
|
+
return _fn;
|
|
178
|
+
}
|
|
179
|
+
createQueryWithCache.useDb = (newDb) => {
|
|
180
|
+
db = newDb;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// src/index.ts
|
|
184
|
+
var version = "0.0.1";
|
|
185
|
+
|
|
186
|
+
exports.ReplaySubject = ReplaySubject;
|
|
187
|
+
exports.createQueryWithCache = createQueryWithCache;
|
|
188
|
+
exports.fnRunner = fnRunner;
|
|
189
|
+
exports.version = version;
|
|
190
|
+
//# sourceMappingURL=index.cjs.map
|
|
191
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/class/ReplaySubject/index.ts","../src/utils/fnRunner/index.ts","../src/utils/createQueryWithCache/index.ts","../src/index.ts"],"names":["localforage","version","deepEquals","result","fn"],"mappings":"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","file":"index.cjs","sourcesContent":["class ReplaySubject<T> {\n private maxBufferSize: number\n private buffer: T[] = []\n\n private subscriptions: ((data: T) => void)[] = []\n\n constructor(maxBufferSize = 1) {\n this.maxBufferSize = maxBufferSize\n }\n\n subscribe(fn: (data: T) => void) {\n this.subscriptions.push(fn)\n this.buffer.forEach(data => fn(data))\n return () => this.unsubscribe(fn)\n }\n\n unsubscribe(fn: (data: T) => void) {\n this.subscriptions = this.subscriptions.filter(f => f !== fn)\n }\n\n next(data: T) {\n this.subscriptions.forEach(fn => fn(data))\n this.buffer.push(data)\n if (this.buffer.length > this.maxBufferSize) {\n this.buffer = this.buffer.slice(1)\n }\n }\n}\n\nexport { ReplaySubject }\n","export async function fnRunner<T>(fn: () => Promise<T>, retryTimes: number = 0): Promise<T> {\n const times = (retryTimes || 0) + 1\n for (let i = 0; i < times; i++) {\n try {\n return await fn()\n }\n catch (error) {\n // Only throw if this was the last attempt\n if (i === times - 1) {\n throw error\n }\n }\n }\n // This line should never be reached, but it's required for type safety.\n throw new Error('fnRunner: Unexpected error - all retries exhausted.')\n}\n","import localforage from 'localforage'\nimport { equals as deepEquals } from 'ramda'\nimport { ReplaySubject } from '../../class'\nimport { fnRunner } from '../fnRunner'\n\n// TODO:\n// 1. 是否需要增加 reload 方法,只请求远程并刷新缓存\n\ntype DBType = Pick<\n LocalForage,\n 'getItem' | 'setItem' | 'removeItem'\n // 这些可以暂时不考虑\n // | 'clear' | 'keys' | 'length'\n>\n\nlet db: DBType = localforage.createInstance({\n name: 'sh-common-cache',\n // driver: [localforage.LOCALSTORAGE, localforage.INDEXEDDB, localforage.WEBSQL],\n})\n\ninterface CacheItem<T extends (...args: any[]) => Promise<any>> {\n /** 缓存参数 */\n args: Parameters<T>\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 缓存过期时间,为空表示永不过期 */\n expires?: number\n /** 缓存结果 */\n result: Awaited<ReturnType<T>>\n}\n\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /**\n * 重试次数,为0表示不重试\n * @default 0\n */\n retry?: number\n /**\n * 是否比较旧缓存与新缓存后再触发 onCacheUpdate\n * 为 true 时,只有当新旧缓存不一致时才触发 onCacheUpdate\n * 为 false 时,总是触发 onCacheUpdate\n * @default R.equals\n */\n compareBeforeUpdate?:\n | ((prev: Awaited<ReturnType<T>>, next: Awaited<ReturnType<T>>) => boolean)\n | false\n /**\n * 是否启用远程内存缓存\n * 为 true 时,启用远程内存缓存\n * 为 false 时,不启用远程内存缓存\n * @default false\n */\n remoteMemoryCache?: boolean\n /** @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index]) */\n equals?: (prev: Parameters<T>, next: Parameters<T>) => boolean\n /**\n * 根据入参决定是否添加缓存\n * 不传的话默认启用缓存\n */\n cacheEnabled?: (args: Parameters<T>) => Promise<boolean>\n /** 请求前回调 */\n beforeRequest?: () => Promise<void> | void\n /** 请求成功回调 */\n onSuccess?: (result: {\n type: 'local' | 'remote'\n result: Awaited<ReturnType<T>>\n }) => void\n /** 请求错误回调 */\n onError?: (error: any) => void\n /** 远程请求错误处理 */\n errorHandler?: (error: any) => any\n // /** 缓存更新回调 */\n // onCacheUpdate?: (\n // result: Awaited<ReturnType<T>>,\n // cacheData: CacheItem<T>,\n // ) => void;\n}\n\ninterface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {\n result: Awaited<ReturnType<T>>\n cacheData: CacheItem<T>\n isCacheHit: boolean\n}\n\nfunction createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>) {\n const {\n retry,\n maxAge,\n version,\n compareBeforeUpdate,\n remoteMemoryCache,\n equals,\n cacheEnabled,\n beforeRequest,\n onSuccess,\n onError,\n errorHandler,\n } = {\n equals: (a, b) =>\n a.length === b.length && a.every((item, index) => item === b[index]),\n retry: 0,\n compareBeforeUpdate: deepEquals,\n remoteMemoryCache: false,\n ...options,\n } as CacheOptions<T>\n\n const cacheUpdateSubject = new ReplaySubject<CacheUpdateEvent<T>>(1)\n\n const getLocalCache = async (args: Parameters<T>) => {\n const cache = await db.getItem<CacheItem<T>>(key)\n if (\n cache\n && (!version || cache.version === version)\n && (!cache.expires || cache.expires > Date.now())\n && equals!(cache.args, args)\n ) {\n return cache.result\n }\n\n throw new Error('Cache not found')\n }\n\n const remoteResultCache: {\n success: boolean\n args: Parameters<T>\n result: ReturnType<T> | null\n } = {\n success: false,\n args: [] as any,\n result: null,\n }\n\n const clearMemoryCache = () => {\n remoteResultCache.success = false\n remoteResultCache.result = null\n remoteResultCache.args = [] as any\n }\n\n const queryWithMemoryCache = (args: Parameters<T>) => {\n if (remoteResultCache.success && equals!(remoteResultCache.args, args)) {\n return {\n type: 'memory' as const,\n promiseResult: remoteResultCache.result! as ReturnType<T>,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.success = true\n const result: ReturnType<T> = (remoteResultCache.result = fnRunner(\n () => fn(...args),\n retry,\n ) as ReturnType<T>)\n\n Promise.resolve(result)\n .then((result) => {\n !remoteMemoryCache && clearMemoryCache()\n return result\n })\n .catch(clearMemoryCache)\n\n return { type: 'remote' as const, promiseResult: result as ReturnType<T> }\n }\n\n const queryRemote = async (args: Parameters<T>) => {\n const rs = {\n type: 'remote' as const,\n result: null as unknown as Awaited<ReturnType<T>>,\n }\n\n try {\n const { type, promiseResult } = queryWithMemoryCache(args)\n const result = (rs.result = await promiseResult)\n\n // 设置缓存\n type === 'remote'\n && (!cacheEnabled || (await cacheEnabled(args)))\n && Promise.resolve().then(async () => {\n const oldCache = await getLocalCache(args).catch(() => null)\n\n const cacheData = {\n args,\n result,\n ...(version && { version }),\n ...(maxAge && { expires: Date.now() + maxAge }),\n }\n db.setItem(key, cacheData)\n\n // 如果配置了比较,且旧缓存存在且与新结果相等,则不触发 onCacheUpdate\n if (\n compareBeforeUpdate\n && oldCache\n && compareBeforeUpdate(oldCache.result, result)\n ) {\n return\n }\n cacheUpdateSubject.next({\n result,\n cacheData,\n isCacheHit: !!oldCache,\n })\n })\n }\n catch (error) {\n if (errorHandler) {\n rs.result = (await errorHandler(error)) as Awaited<ReturnType<T>>\n }\n else {\n throw error\n }\n }\n\n return rs.result\n }\n\n const _fn = async (...args: Parameters<T>) => {\n await beforeRequest?.()\n\n const [cacheResult, remoteResult] = [\n getLocalCache(args),\n queryRemote(args),\n ]\n cacheResult\n .then(result =>\n onSuccess?.({\n type: 'local' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(() => {})\n remoteResult\n .then(result =>\n onSuccess?.({\n type: 'remote' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(error => onError?.(error))\n .catch(() => {})\n const result = await Promise.race([\n cacheResult.catch(() => remoteResult),\n remoteResult,\n ])\n\n return result\n }\n\n // _fn.getMemoryCache = async () => remoteResultCache;\n\n _fn.subscribeCacheUpdate = (fn: (data: CacheUpdateEvent<T>) => void) =>\n cacheUpdateSubject.subscribe(fn)\n\n _fn.clearCache = async () => {\n clearMemoryCache()\n await db.removeItem(key)\n }\n\n return _fn\n}\n\ncreateQueryWithCache.useDb = (newDb: DBType) => {\n db = newDb\n}\n\nexport { createQueryWithCache }\n","/**\n * @shihengtech/utils - A collection of utility functions\n */\n\n// Type checking utilities\nexport * from './class'\nexport * from './utils'\n// Version\nexport const version = '0.0.1'\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
declare class ReplaySubject<T> {
|
|
2
|
+
private maxBufferSize;
|
|
3
|
+
private buffer;
|
|
4
|
+
private subscriptions;
|
|
5
|
+
constructor(maxBufferSize?: number);
|
|
6
|
+
subscribe(fn: (data: T) => void): () => void;
|
|
7
|
+
unsubscribe(fn: (data: T) => void): void;
|
|
8
|
+
next(data: T): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type DBType = Pick<LocalForage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
12
|
+
interface CacheItem<T extends (...args: any[]) => Promise<any>> {
|
|
13
|
+
/** 缓存参数 */
|
|
14
|
+
args: Parameters<T>;
|
|
15
|
+
/** 缓存版本,用于缓存数据结构变更 */
|
|
16
|
+
version?: string;
|
|
17
|
+
/** 缓存过期时间,为空表示永不过期 */
|
|
18
|
+
expires?: number;
|
|
19
|
+
/** 缓存结果 */
|
|
20
|
+
result: Awaited<ReturnType<T>>;
|
|
21
|
+
}
|
|
22
|
+
interface CacheOptions<T extends (...args: any[]) => Promise<any>> {
|
|
23
|
+
/** 缓存过期时间,为空表示永不过期 */
|
|
24
|
+
maxAge?: number;
|
|
25
|
+
/** 缓存版本,用于缓存数据结构变更 */
|
|
26
|
+
version?: string;
|
|
27
|
+
/**
|
|
28
|
+
* 重试次数,为0表示不重试
|
|
29
|
+
* @default 0
|
|
30
|
+
*/
|
|
31
|
+
retry?: number;
|
|
32
|
+
/**
|
|
33
|
+
* 是否比较旧缓存与新缓存后再触发 onCacheUpdate
|
|
34
|
+
* 为 true 时,只有当新旧缓存不一致时才触发 onCacheUpdate
|
|
35
|
+
* 为 false 时,总是触发 onCacheUpdate
|
|
36
|
+
* @default R.equals
|
|
37
|
+
*/
|
|
38
|
+
compareBeforeUpdate?: ((prev: Awaited<ReturnType<T>>, next: Awaited<ReturnType<T>>) => boolean) | false;
|
|
39
|
+
/**
|
|
40
|
+
* 是否启用远程内存缓存
|
|
41
|
+
* 为 true 时,启用远程内存缓存
|
|
42
|
+
* 为 false 时,不启用远程内存缓存
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
remoteMemoryCache?: boolean;
|
|
46
|
+
/** @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index]) */
|
|
47
|
+
equals?: (prev: Parameters<T>, next: Parameters<T>) => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* 根据入参决定是否添加缓存
|
|
50
|
+
* 不传的话默认启用缓存
|
|
51
|
+
*/
|
|
52
|
+
cacheEnabled?: (args: Parameters<T>) => Promise<boolean>;
|
|
53
|
+
/** 请求前回调 */
|
|
54
|
+
beforeRequest?: () => Promise<void> | void;
|
|
55
|
+
/** 请求成功回调 */
|
|
56
|
+
onSuccess?: (result: {
|
|
57
|
+
type: 'local' | 'remote';
|
|
58
|
+
result: Awaited<ReturnType<T>>;
|
|
59
|
+
}) => void;
|
|
60
|
+
/** 请求错误回调 */
|
|
61
|
+
onError?: (error: any) => void;
|
|
62
|
+
/** 远程请求错误处理 */
|
|
63
|
+
errorHandler?: (error: any) => any;
|
|
64
|
+
}
|
|
65
|
+
interface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {
|
|
66
|
+
result: Awaited<ReturnType<T>>;
|
|
67
|
+
cacheData: CacheItem<T>;
|
|
68
|
+
isCacheHit: boolean;
|
|
69
|
+
}
|
|
70
|
+
declare function createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>): {
|
|
71
|
+
(...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
72
|
+
subscribeCacheUpdate(fn: (data: CacheUpdateEvent<T>) => void): () => void;
|
|
73
|
+
clearCache(): Promise<void>;
|
|
74
|
+
};
|
|
75
|
+
declare namespace createQueryWithCache {
|
|
76
|
+
var useDb: (newDb: DBType) => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare function fnRunner<T>(fn: () => Promise<T>, retryTimes?: number): Promise<T>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @shihengtech/utils - A collection of utility functions
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
declare const version = "0.0.1";
|
|
86
|
+
|
|
87
|
+
export { ReplaySubject, createQueryWithCache, fnRunner, version };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
declare class ReplaySubject<T> {
|
|
2
|
+
private maxBufferSize;
|
|
3
|
+
private buffer;
|
|
4
|
+
private subscriptions;
|
|
5
|
+
constructor(maxBufferSize?: number);
|
|
6
|
+
subscribe(fn: (data: T) => void): () => void;
|
|
7
|
+
unsubscribe(fn: (data: T) => void): void;
|
|
8
|
+
next(data: T): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type DBType = Pick<LocalForage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
12
|
+
interface CacheItem<T extends (...args: any[]) => Promise<any>> {
|
|
13
|
+
/** 缓存参数 */
|
|
14
|
+
args: Parameters<T>;
|
|
15
|
+
/** 缓存版本,用于缓存数据结构变更 */
|
|
16
|
+
version?: string;
|
|
17
|
+
/** 缓存过期时间,为空表示永不过期 */
|
|
18
|
+
expires?: number;
|
|
19
|
+
/** 缓存结果 */
|
|
20
|
+
result: Awaited<ReturnType<T>>;
|
|
21
|
+
}
|
|
22
|
+
interface CacheOptions<T extends (...args: any[]) => Promise<any>> {
|
|
23
|
+
/** 缓存过期时间,为空表示永不过期 */
|
|
24
|
+
maxAge?: number;
|
|
25
|
+
/** 缓存版本,用于缓存数据结构变更 */
|
|
26
|
+
version?: string;
|
|
27
|
+
/**
|
|
28
|
+
* 重试次数,为0表示不重试
|
|
29
|
+
* @default 0
|
|
30
|
+
*/
|
|
31
|
+
retry?: number;
|
|
32
|
+
/**
|
|
33
|
+
* 是否比较旧缓存与新缓存后再触发 onCacheUpdate
|
|
34
|
+
* 为 true 时,只有当新旧缓存不一致时才触发 onCacheUpdate
|
|
35
|
+
* 为 false 时,总是触发 onCacheUpdate
|
|
36
|
+
* @default R.equals
|
|
37
|
+
*/
|
|
38
|
+
compareBeforeUpdate?: ((prev: Awaited<ReturnType<T>>, next: Awaited<ReturnType<T>>) => boolean) | false;
|
|
39
|
+
/**
|
|
40
|
+
* 是否启用远程内存缓存
|
|
41
|
+
* 为 true 时,启用远程内存缓存
|
|
42
|
+
* 为 false 时,不启用远程内存缓存
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
remoteMemoryCache?: boolean;
|
|
46
|
+
/** @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index]) */
|
|
47
|
+
equals?: (prev: Parameters<T>, next: Parameters<T>) => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* 根据入参决定是否添加缓存
|
|
50
|
+
* 不传的话默认启用缓存
|
|
51
|
+
*/
|
|
52
|
+
cacheEnabled?: (args: Parameters<T>) => Promise<boolean>;
|
|
53
|
+
/** 请求前回调 */
|
|
54
|
+
beforeRequest?: () => Promise<void> | void;
|
|
55
|
+
/** 请求成功回调 */
|
|
56
|
+
onSuccess?: (result: {
|
|
57
|
+
type: 'local' | 'remote';
|
|
58
|
+
result: Awaited<ReturnType<T>>;
|
|
59
|
+
}) => void;
|
|
60
|
+
/** 请求错误回调 */
|
|
61
|
+
onError?: (error: any) => void;
|
|
62
|
+
/** 远程请求错误处理 */
|
|
63
|
+
errorHandler?: (error: any) => any;
|
|
64
|
+
}
|
|
65
|
+
interface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {
|
|
66
|
+
result: Awaited<ReturnType<T>>;
|
|
67
|
+
cacheData: CacheItem<T>;
|
|
68
|
+
isCacheHit: boolean;
|
|
69
|
+
}
|
|
70
|
+
declare function createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>): {
|
|
71
|
+
(...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
72
|
+
subscribeCacheUpdate(fn: (data: CacheUpdateEvent<T>) => void): () => void;
|
|
73
|
+
clearCache(): Promise<void>;
|
|
74
|
+
};
|
|
75
|
+
declare namespace createQueryWithCache {
|
|
76
|
+
var useDb: (newDb: DBType) => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare function fnRunner<T>(fn: () => Promise<T>, retryTimes?: number): Promise<T>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @shihengtech/utils - A collection of utility functions
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
declare const version = "0.0.1";
|
|
86
|
+
|
|
87
|
+
export { ReplaySubject, createQueryWithCache, fnRunner, version };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import localforage from 'localforage';
|
|
2
|
+
import { equals } from 'ramda';
|
|
3
|
+
|
|
4
|
+
// src/class/ReplaySubject/index.ts
|
|
5
|
+
var ReplaySubject = class {
|
|
6
|
+
constructor(maxBufferSize = 1) {
|
|
7
|
+
this.buffer = [];
|
|
8
|
+
this.subscriptions = [];
|
|
9
|
+
this.maxBufferSize = maxBufferSize;
|
|
10
|
+
}
|
|
11
|
+
subscribe(fn) {
|
|
12
|
+
this.subscriptions.push(fn);
|
|
13
|
+
this.buffer.forEach((data) => fn(data));
|
|
14
|
+
return () => this.unsubscribe(fn);
|
|
15
|
+
}
|
|
16
|
+
unsubscribe(fn) {
|
|
17
|
+
this.subscriptions = this.subscriptions.filter((f) => f !== fn);
|
|
18
|
+
}
|
|
19
|
+
next(data) {
|
|
20
|
+
this.subscriptions.forEach((fn) => fn(data));
|
|
21
|
+
this.buffer.push(data);
|
|
22
|
+
if (this.buffer.length > this.maxBufferSize) {
|
|
23
|
+
this.buffer = this.buffer.slice(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/utils/fnRunner/index.ts
|
|
29
|
+
async function fnRunner(fn, retryTimes = 0) {
|
|
30
|
+
const times = (retryTimes || 0) + 1;
|
|
31
|
+
for (let i = 0; i < times; i++) {
|
|
32
|
+
try {
|
|
33
|
+
return await fn();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (i === times - 1) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw new Error("fnRunner: Unexpected error - all retries exhausted.");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/utils/createQueryWithCache/index.ts
|
|
44
|
+
var db = localforage.createInstance({
|
|
45
|
+
name: "sh-common-cache"
|
|
46
|
+
// driver: [localforage.LOCALSTORAGE, localforage.INDEXEDDB, localforage.WEBSQL],
|
|
47
|
+
});
|
|
48
|
+
function createQueryWithCache(key, fn, options) {
|
|
49
|
+
const {
|
|
50
|
+
retry,
|
|
51
|
+
maxAge,
|
|
52
|
+
version: version2,
|
|
53
|
+
compareBeforeUpdate,
|
|
54
|
+
remoteMemoryCache,
|
|
55
|
+
equals: equals$1,
|
|
56
|
+
cacheEnabled,
|
|
57
|
+
beforeRequest,
|
|
58
|
+
onSuccess,
|
|
59
|
+
onError,
|
|
60
|
+
errorHandler
|
|
61
|
+
} = {
|
|
62
|
+
equals: (a, b) => a.length === b.length && a.every((item, index) => item === b[index]),
|
|
63
|
+
retry: 0,
|
|
64
|
+
compareBeforeUpdate: equals,
|
|
65
|
+
remoteMemoryCache: false,
|
|
66
|
+
...options
|
|
67
|
+
};
|
|
68
|
+
const cacheUpdateSubject = new ReplaySubject(1);
|
|
69
|
+
const getLocalCache = async (args) => {
|
|
70
|
+
const cache = await db.getItem(key);
|
|
71
|
+
if (cache && (!version2 || cache.version === version2) && (!cache.expires || cache.expires > Date.now()) && equals$1(cache.args, args)) {
|
|
72
|
+
return cache.result;
|
|
73
|
+
}
|
|
74
|
+
throw new Error("Cache not found");
|
|
75
|
+
};
|
|
76
|
+
const remoteResultCache = {
|
|
77
|
+
success: false,
|
|
78
|
+
args: [],
|
|
79
|
+
result: null
|
|
80
|
+
};
|
|
81
|
+
const clearMemoryCache = () => {
|
|
82
|
+
remoteResultCache.success = false;
|
|
83
|
+
remoteResultCache.result = null;
|
|
84
|
+
remoteResultCache.args = [];
|
|
85
|
+
};
|
|
86
|
+
const queryWithMemoryCache = (args) => {
|
|
87
|
+
if (remoteResultCache.success && equals$1(remoteResultCache.args, args)) {
|
|
88
|
+
return {
|
|
89
|
+
type: "memory",
|
|
90
|
+
promiseResult: remoteResultCache.result
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
remoteResultCache.args = args;
|
|
94
|
+
remoteResultCache.success = true;
|
|
95
|
+
const result = remoteResultCache.result = fnRunner(
|
|
96
|
+
() => fn(...args),
|
|
97
|
+
retry
|
|
98
|
+
);
|
|
99
|
+
Promise.resolve(result).then((result2) => {
|
|
100
|
+
!remoteMemoryCache && clearMemoryCache();
|
|
101
|
+
return result2;
|
|
102
|
+
}).catch(clearMemoryCache);
|
|
103
|
+
return { type: "remote", promiseResult: result };
|
|
104
|
+
};
|
|
105
|
+
const queryRemote = async (args) => {
|
|
106
|
+
const rs = {
|
|
107
|
+
type: "remote",
|
|
108
|
+
result: null
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
const { type, promiseResult } = queryWithMemoryCache(args);
|
|
112
|
+
const result = rs.result = await promiseResult;
|
|
113
|
+
type === "remote" && (!cacheEnabled || await cacheEnabled(args)) && Promise.resolve().then(async () => {
|
|
114
|
+
const oldCache = await getLocalCache(args).catch(() => null);
|
|
115
|
+
const cacheData = {
|
|
116
|
+
args,
|
|
117
|
+
result,
|
|
118
|
+
...version2 && { version: version2 },
|
|
119
|
+
...maxAge && { expires: Date.now() + maxAge }
|
|
120
|
+
};
|
|
121
|
+
db.setItem(key, cacheData);
|
|
122
|
+
if (compareBeforeUpdate && oldCache && compareBeforeUpdate(oldCache.result, result)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
cacheUpdateSubject.next({
|
|
126
|
+
result,
|
|
127
|
+
cacheData,
|
|
128
|
+
isCacheHit: !!oldCache
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (errorHandler) {
|
|
133
|
+
rs.result = await errorHandler(error);
|
|
134
|
+
} else {
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return rs.result;
|
|
139
|
+
};
|
|
140
|
+
const _fn = async (...args) => {
|
|
141
|
+
await beforeRequest?.();
|
|
142
|
+
const [cacheResult, remoteResult] = [
|
|
143
|
+
getLocalCache(args),
|
|
144
|
+
queryRemote(args)
|
|
145
|
+
];
|
|
146
|
+
cacheResult.then(
|
|
147
|
+
(result2) => onSuccess?.({
|
|
148
|
+
type: "local",
|
|
149
|
+
result: result2
|
|
150
|
+
})
|
|
151
|
+
).catch(() => {
|
|
152
|
+
});
|
|
153
|
+
remoteResult.then(
|
|
154
|
+
(result2) => onSuccess?.({
|
|
155
|
+
type: "remote",
|
|
156
|
+
result: result2
|
|
157
|
+
})
|
|
158
|
+
).catch((error) => onError?.(error)).catch(() => {
|
|
159
|
+
});
|
|
160
|
+
const result = await Promise.race([
|
|
161
|
+
cacheResult.catch(() => remoteResult),
|
|
162
|
+
remoteResult
|
|
163
|
+
]);
|
|
164
|
+
return result;
|
|
165
|
+
};
|
|
166
|
+
_fn.subscribeCacheUpdate = (fn2) => cacheUpdateSubject.subscribe(fn2);
|
|
167
|
+
_fn.clearCache = async () => {
|
|
168
|
+
clearMemoryCache();
|
|
169
|
+
await db.removeItem(key);
|
|
170
|
+
};
|
|
171
|
+
return _fn;
|
|
172
|
+
}
|
|
173
|
+
createQueryWithCache.useDb = (newDb) => {
|
|
174
|
+
db = newDb;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/index.ts
|
|
178
|
+
var version = "0.0.1";
|
|
179
|
+
|
|
180
|
+
export { ReplaySubject, createQueryWithCache, fnRunner, version };
|
|
181
|
+
//# sourceMappingURL=index.js.map
|
|
182
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/class/ReplaySubject/index.ts","../src/utils/fnRunner/index.ts","../src/utils/createQueryWithCache/index.ts","../src/index.ts"],"names":["version","equals","deepEquals","result","fn"],"mappings":"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","file":"index.js","sourcesContent":["class ReplaySubject<T> {\n private maxBufferSize: number\n private buffer: T[] = []\n\n private subscriptions: ((data: T) => void)[] = []\n\n constructor(maxBufferSize = 1) {\n this.maxBufferSize = maxBufferSize\n }\n\n subscribe(fn: (data: T) => void) {\n this.subscriptions.push(fn)\n this.buffer.forEach(data => fn(data))\n return () => this.unsubscribe(fn)\n }\n\n unsubscribe(fn: (data: T) => void) {\n this.subscriptions = this.subscriptions.filter(f => f !== fn)\n }\n\n next(data: T) {\n this.subscriptions.forEach(fn => fn(data))\n this.buffer.push(data)\n if (this.buffer.length > this.maxBufferSize) {\n this.buffer = this.buffer.slice(1)\n }\n }\n}\n\nexport { ReplaySubject }\n","export async function fnRunner<T>(fn: () => Promise<T>, retryTimes: number = 0): Promise<T> {\n const times = (retryTimes || 0) + 1\n for (let i = 0; i < times; i++) {\n try {\n return await fn()\n }\n catch (error) {\n // Only throw if this was the last attempt\n if (i === times - 1) {\n throw error\n }\n }\n }\n // This line should never be reached, but it's required for type safety.\n throw new Error('fnRunner: Unexpected error - all retries exhausted.')\n}\n","import localforage from 'localforage'\nimport { equals as deepEquals } from 'ramda'\nimport { ReplaySubject } from '../../class'\nimport { fnRunner } from '../fnRunner'\n\n// TODO:\n// 1. 是否需要增加 reload 方法,只请求远程并刷新缓存\n\ntype DBType = Pick<\n LocalForage,\n 'getItem' | 'setItem' | 'removeItem'\n // 这些可以暂时不考虑\n // | 'clear' | 'keys' | 'length'\n>\n\nlet db: DBType = localforage.createInstance({\n name: 'sh-common-cache',\n // driver: [localforage.LOCALSTORAGE, localforage.INDEXEDDB, localforage.WEBSQL],\n})\n\ninterface CacheItem<T extends (...args: any[]) => Promise<any>> {\n /** 缓存参数 */\n args: Parameters<T>\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 缓存过期时间,为空表示永不过期 */\n expires?: number\n /** 缓存结果 */\n result: Awaited<ReturnType<T>>\n}\n\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /**\n * 重试次数,为0表示不重试\n * @default 0\n */\n retry?: number\n /**\n * 是否比较旧缓存与新缓存后再触发 onCacheUpdate\n * 为 true 时,只有当新旧缓存不一致时才触发 onCacheUpdate\n * 为 false 时,总是触发 onCacheUpdate\n * @default R.equals\n */\n compareBeforeUpdate?:\n | ((prev: Awaited<ReturnType<T>>, next: Awaited<ReturnType<T>>) => boolean)\n | false\n /**\n * 是否启用远程内存缓存\n * 为 true 时,启用远程内存缓存\n * 为 false 时,不启用远程内存缓存\n * @default false\n */\n remoteMemoryCache?: boolean\n /** @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index]) */\n equals?: (prev: Parameters<T>, next: Parameters<T>) => boolean\n /**\n * 根据入参决定是否添加缓存\n * 不传的话默认启用缓存\n */\n cacheEnabled?: (args: Parameters<T>) => Promise<boolean>\n /** 请求前回调 */\n beforeRequest?: () => Promise<void> | void\n /** 请求成功回调 */\n onSuccess?: (result: {\n type: 'local' | 'remote'\n result: Awaited<ReturnType<T>>\n }) => void\n /** 请求错误回调 */\n onError?: (error: any) => void\n /** 远程请求错误处理 */\n errorHandler?: (error: any) => any\n // /** 缓存更新回调 */\n // onCacheUpdate?: (\n // result: Awaited<ReturnType<T>>,\n // cacheData: CacheItem<T>,\n // ) => void;\n}\n\ninterface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {\n result: Awaited<ReturnType<T>>\n cacheData: CacheItem<T>\n isCacheHit: boolean\n}\n\nfunction createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>) {\n const {\n retry,\n maxAge,\n version,\n compareBeforeUpdate,\n remoteMemoryCache,\n equals,\n cacheEnabled,\n beforeRequest,\n onSuccess,\n onError,\n errorHandler,\n } = {\n equals: (a, b) =>\n a.length === b.length && a.every((item, index) => item === b[index]),\n retry: 0,\n compareBeforeUpdate: deepEquals,\n remoteMemoryCache: false,\n ...options,\n } as CacheOptions<T>\n\n const cacheUpdateSubject = new ReplaySubject<CacheUpdateEvent<T>>(1)\n\n const getLocalCache = async (args: Parameters<T>) => {\n const cache = await db.getItem<CacheItem<T>>(key)\n if (\n cache\n && (!version || cache.version === version)\n && (!cache.expires || cache.expires > Date.now())\n && equals!(cache.args, args)\n ) {\n return cache.result\n }\n\n throw new Error('Cache not found')\n }\n\n const remoteResultCache: {\n success: boolean\n args: Parameters<T>\n result: ReturnType<T> | null\n } = {\n success: false,\n args: [] as any,\n result: null,\n }\n\n const clearMemoryCache = () => {\n remoteResultCache.success = false\n remoteResultCache.result = null\n remoteResultCache.args = [] as any\n }\n\n const queryWithMemoryCache = (args: Parameters<T>) => {\n if (remoteResultCache.success && equals!(remoteResultCache.args, args)) {\n return {\n type: 'memory' as const,\n promiseResult: remoteResultCache.result! as ReturnType<T>,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.success = true\n const result: ReturnType<T> = (remoteResultCache.result = fnRunner(\n () => fn(...args),\n retry,\n ) as ReturnType<T>)\n\n Promise.resolve(result)\n .then((result) => {\n !remoteMemoryCache && clearMemoryCache()\n return result\n })\n .catch(clearMemoryCache)\n\n return { type: 'remote' as const, promiseResult: result as ReturnType<T> }\n }\n\n const queryRemote = async (args: Parameters<T>) => {\n const rs = {\n type: 'remote' as const,\n result: null as unknown as Awaited<ReturnType<T>>,\n }\n\n try {\n const { type, promiseResult } = queryWithMemoryCache(args)\n const result = (rs.result = await promiseResult)\n\n // 设置缓存\n type === 'remote'\n && (!cacheEnabled || (await cacheEnabled(args)))\n && Promise.resolve().then(async () => {\n const oldCache = await getLocalCache(args).catch(() => null)\n\n const cacheData = {\n args,\n result,\n ...(version && { version }),\n ...(maxAge && { expires: Date.now() + maxAge }),\n }\n db.setItem(key, cacheData)\n\n // 如果配置了比较,且旧缓存存在且与新结果相等,则不触发 onCacheUpdate\n if (\n compareBeforeUpdate\n && oldCache\n && compareBeforeUpdate(oldCache.result, result)\n ) {\n return\n }\n cacheUpdateSubject.next({\n result,\n cacheData,\n isCacheHit: !!oldCache,\n })\n })\n }\n catch (error) {\n if (errorHandler) {\n rs.result = (await errorHandler(error)) as Awaited<ReturnType<T>>\n }\n else {\n throw error\n }\n }\n\n return rs.result\n }\n\n const _fn = async (...args: Parameters<T>) => {\n await beforeRequest?.()\n\n const [cacheResult, remoteResult] = [\n getLocalCache(args),\n queryRemote(args),\n ]\n cacheResult\n .then(result =>\n onSuccess?.({\n type: 'local' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(() => {})\n remoteResult\n .then(result =>\n onSuccess?.({\n type: 'remote' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(error => onError?.(error))\n .catch(() => {})\n const result = await Promise.race([\n cacheResult.catch(() => remoteResult),\n remoteResult,\n ])\n\n return result\n }\n\n // _fn.getMemoryCache = async () => remoteResultCache;\n\n _fn.subscribeCacheUpdate = (fn: (data: CacheUpdateEvent<T>) => void) =>\n cacheUpdateSubject.subscribe(fn)\n\n _fn.clearCache = async () => {\n clearMemoryCache()\n await db.removeItem(key)\n }\n\n return _fn\n}\n\ncreateQueryWithCache.useDb = (newDb: DBType) => {\n db = newDb\n}\n\nexport { createQueryWithCache }\n","/**\n * @shihengtech/utils - A collection of utility functions\n */\n\n// Type checking utilities\nexport * from './class'\nexport * from './utils'\n// Version\nexport const version = '0.0.1'\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shihengtech/utils",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "A collection of utility tools",
|
|
6
|
+
"author": "",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": ""
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"tools",
|
|
14
|
+
"utils",
|
|
15
|
+
"utilities"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"require": {
|
|
24
|
+
"types": "./dist/index.d.cts",
|
|
25
|
+
"default": "./dist/index.cjs"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/index.cjs",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public",
|
|
40
|
+
"registry": "https://registry.npmjs.org/"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"test": "vitest",
|
|
46
|
+
"test:run": "vitest run",
|
|
47
|
+
"test:coverage": "vitest run --coverage",
|
|
48
|
+
"docs:dev": "vitepress dev docs",
|
|
49
|
+
"docs:build": "vitepress build docs",
|
|
50
|
+
"docs:preview": "vitepress preview docs",
|
|
51
|
+
"lint": "eslint .",
|
|
52
|
+
"lint:fix": "eslint . --fix",
|
|
53
|
+
"prepublishOnly": "npm run build"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"localforage": "^1.10.0",
|
|
57
|
+
"ramda": "^0.32.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@antfu/eslint-config": "^4.13.0",
|
|
61
|
+
"@types/node": "^20.10.0",
|
|
62
|
+
"@types/ramda": "^0.31.1",
|
|
63
|
+
"@vitest/coverage-v8": "^1.0.0",
|
|
64
|
+
"eslint": "^9.27.0",
|
|
65
|
+
"eslint-plugin-format": "^1.0.2",
|
|
66
|
+
"tsup": "^8.0.1",
|
|
67
|
+
"typescript": "^5.3.2",
|
|
68
|
+
"vitepress": "^1.0.0-rc.31",
|
|
69
|
+
"vitest": "^1.0.0"
|
|
70
|
+
}
|
|
71
|
+
}
|