@shihengtech/utils 0.0.4 → 0.0.6
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/dist/sh-utils.js +2 -2
- package/dist/sh-utils.js.map +1 -1
- package/es/index.js +26 -28
- package/es/index.js.map +1 -1
- package/lib/index.d.ts +12 -10
- package/lib/index.js +25 -28
- package/lib/index.js.map +1 -1
- package/package.json +15 -14
package/es/index.js
CHANGED
|
@@ -88,9 +88,10 @@ var db = localforage.createInstance({
|
|
|
88
88
|
});
|
|
89
89
|
function createQueryWithCache(key, fn, options) {
|
|
90
90
|
const {
|
|
91
|
+
deps,
|
|
91
92
|
retry,
|
|
92
93
|
maxAge,
|
|
93
|
-
version
|
|
94
|
+
version,
|
|
94
95
|
compareBeforeUpdate,
|
|
95
96
|
remoteMemoryCache,
|
|
96
97
|
equals: equals$1,
|
|
@@ -100,15 +101,28 @@ function createQueryWithCache(key, fn, options) {
|
|
|
100
101
|
onError,
|
|
101
102
|
errorHandler
|
|
102
103
|
} = __spreadValues({
|
|
103
|
-
|
|
104
|
+
deps: [],
|
|
104
105
|
retry: 0,
|
|
106
|
+
equals: (a, b) => a.length === b.length && a.every((item, index) => item === b[index]),
|
|
105
107
|
compareBeforeUpdate: equals,
|
|
106
108
|
remoteMemoryCache: false
|
|
107
109
|
}, options);
|
|
108
110
|
const cacheUpdateSubject = new ReplaySubject(1);
|
|
111
|
+
const resolveDeps = () => deps.map((dep) => typeof dep === "function" ? dep() : dep);
|
|
112
|
+
const queryFnEnhancer = (fn2, options2) => ((args) => {
|
|
113
|
+
const promiseResult = fn2(args);
|
|
114
|
+
promiseResult.then((result) => {
|
|
115
|
+
var _a;
|
|
116
|
+
const res = { type: options2.type, result };
|
|
117
|
+
(_a = options2.onSuccess) == null ? void 0 : _a.call(options2, res);
|
|
118
|
+
return res;
|
|
119
|
+
}, options2.onError).catch(() => {
|
|
120
|
+
});
|
|
121
|
+
return promiseResult;
|
|
122
|
+
});
|
|
109
123
|
const getLocalCache = (args) => __async(null, null, function* () {
|
|
110
124
|
const cache = yield db.getItem(key);
|
|
111
|
-
if (cache && (!
|
|
125
|
+
if (cache && (!version || cache.version === version) && (!cache.expires || cache.expires > Date.now()) && equals$1(cache.deps, args.concat(resolveDeps()))) {
|
|
112
126
|
return cache.result;
|
|
113
127
|
}
|
|
114
128
|
throw new Error("Cache not found");
|
|
@@ -142,7 +156,7 @@ function createQueryWithCache(key, fn, options) {
|
|
|
142
156
|
}).catch(clearMemoryCache);
|
|
143
157
|
return { type: "remote", promiseResult: result };
|
|
144
158
|
};
|
|
145
|
-
const
|
|
159
|
+
const queryRemoteWithEnhancer = queryFnEnhancer((args) => __async(null, null, function* () {
|
|
146
160
|
const rs = {
|
|
147
161
|
type: "remote",
|
|
148
162
|
result: null
|
|
@@ -153,11 +167,11 @@ function createQueryWithCache(key, fn, options) {
|
|
|
153
167
|
type === "remote" && (!cacheEnabled || (yield cacheEnabled(args))) && Promise.resolve().then(() => __async(null, null, function* () {
|
|
154
168
|
const oldCache = yield getLocalCache(args).catch(() => null);
|
|
155
169
|
const cacheData = __spreadValues(__spreadValues({
|
|
156
|
-
args,
|
|
170
|
+
deps: args.concat(resolveDeps()),
|
|
157
171
|
result
|
|
158
|
-
},
|
|
172
|
+
}, version && { version }), maxAge && { expires: Date.now() + maxAge });
|
|
159
173
|
db.setItem(key, cacheData);
|
|
160
|
-
if (compareBeforeUpdate && oldCache && compareBeforeUpdate(oldCache
|
|
174
|
+
if (compareBeforeUpdate && oldCache && compareBeforeUpdate(oldCache, result)) {
|
|
161
175
|
return;
|
|
162
176
|
}
|
|
163
177
|
cacheUpdateSubject.next({
|
|
@@ -174,33 +188,20 @@ function createQueryWithCache(key, fn, options) {
|
|
|
174
188
|
}
|
|
175
189
|
}
|
|
176
190
|
return rs.result;
|
|
177
|
-
});
|
|
191
|
+
}), { type: "remote", onSuccess, onError });
|
|
178
192
|
const _fn = (...args) => __async(null, null, function* () {
|
|
179
193
|
yield beforeRequest == null ? void 0 : beforeRequest();
|
|
180
194
|
const [cacheResult, remoteResult] = [
|
|
181
|
-
getLocalCache(args),
|
|
182
|
-
|
|
195
|
+
queryFnEnhancer(getLocalCache, { type: "local", onSuccess })(args),
|
|
196
|
+
queryRemoteWithEnhancer(args)
|
|
183
197
|
];
|
|
184
|
-
cacheResult.then(
|
|
185
|
-
(result2) => onSuccess == null ? void 0 : onSuccess({
|
|
186
|
-
type: "local",
|
|
187
|
-
result: result2
|
|
188
|
-
})
|
|
189
|
-
).catch(() => {
|
|
190
|
-
});
|
|
191
|
-
remoteResult.then(
|
|
192
|
-
(result2) => onSuccess == null ? void 0 : onSuccess({
|
|
193
|
-
type: "remote",
|
|
194
|
-
result: result2
|
|
195
|
-
})
|
|
196
|
-
).catch((error) => onError == null ? void 0 : onError(error)).catch(() => {
|
|
197
|
-
});
|
|
198
198
|
const result = yield Promise.race([
|
|
199
199
|
cacheResult.catch(() => remoteResult),
|
|
200
200
|
remoteResult
|
|
201
201
|
]);
|
|
202
202
|
return result;
|
|
203
203
|
});
|
|
204
|
+
_fn.refresh = (...args) => queryRemoteWithEnhancer(args);
|
|
204
205
|
_fn.subscribeCacheUpdate = (fn2) => cacheUpdateSubject.subscribe(fn2);
|
|
205
206
|
_fn.clearCache = () => __async(null, null, function* () {
|
|
206
207
|
clearMemoryCache();
|
|
@@ -219,9 +220,6 @@ createQueryWithCache.useDb = (newDb) => {
|
|
|
219
220
|
}
|
|
220
221
|
};
|
|
221
222
|
|
|
222
|
-
|
|
223
|
-
var version = "0.0.3";
|
|
224
|
-
|
|
225
|
-
export { ReplaySubject, createQueryWithCache, fnRunner, version };
|
|
223
|
+
export { ReplaySubject, createQueryWithCache, fnRunner };
|
|
226
224
|
//# sourceMappingURL=index.js.map
|
|
227
225
|
//# sourceMappingURL=index.js.map
|
package/es/index.js.map
CHANGED
|
@@ -1 +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 extends (...args: any[]) => Promise<any>>(fn: T, retryTimes: number = 0): Promise<Awaited<ReturnType<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\ntype DBType = Pick<\n LocalForage,\n 'getItem' | 'setItem' | 'removeItem' | 'clear'\n // 这些可以暂时不考虑\n // | 'clear' | 'keys' | 'length'\n>\n\nconst DB_NAME = 'sh-cache-common'\n\nlet db: DBType = localforage.createInstance({\n name: DB_NAME,\n storeName: 'main-store',\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 } satisfies 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!,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.success = true\n const result = (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 }\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.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\n/**\n * 使用自定义数据库实例\n * @param newDb 数据库实例或 storeName,如果为字符串,则使用 localforage 创建一个实例\n */\ncreateQueryWithCache.useDb = <T extends string>(newDb: DBType | (T extends 'main-store' ? never : T)) => {\n if (typeof newDb === 'string') {\n db = localforage.createInstance({\n name: DB_NAME,\n storeName: newDb,\n })\n }\n else {\n db = newDb\n }\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.3'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/class/ReplaySubject/index.ts","../src/utils/fnRunner/index.ts","../src/utils/createQueryWithCache/index.ts"],"names":["equals","deepEquals","fn","options","result"],"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 extends (...args: any[]) => Promise<any>>(fn: T, retryTimes: number = 0): Promise<Awaited<ReturnType<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\ntype DBType = Pick<\n LocalForage,\n 'getItem' | 'setItem' | 'removeItem' | 'clear'\n // 这些可以暂时不考虑\n // | 'clear' | 'keys' | 'length'\n>\n\nconst DB_NAME = 'sh-cache-common'\n\nlet db: DBType = localforage.createInstance({\n name: DB_NAME,\n storeName: 'main-store',\n // driver: [localforage.LOCALSTORAGE, localforage.INDEXEDDB, localforage.WEBSQL],\n})\n\ninterface CacheItem<T extends (...args: any[]) => Promise<any>> {\n /** 缓存参数 */\n deps: any[]\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 缓存过期时间,为空表示永不过期 */\n expires?: number\n /** 缓存结果 */\n result: Awaited<ReturnType<T>>\n}\n\n/** 依赖项类型,支持直接值或函数形式(运行时取值) */\ntype DepItem = unknown | (() => unknown)\n\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 依赖项,用于缓存依赖项变更。支持函数形式,函数会在运行时调用取值 */\n deps?: DepItem[];\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 /**\n * 比较依赖项(函数参数及 deps)是否相等\n * @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index])\n * */\n equals?: (prev: any[], next: any[]) => 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 deps,\n retry,\n maxAge,\n version,\n compareBeforeUpdate,\n remoteMemoryCache,\n equals,\n cacheEnabled,\n beforeRequest,\n onSuccess,\n onError,\n errorHandler,\n } = {\n deps: [],\n retry: 0,\n equals: (a, b) =>\n a.length === b.length && a.every((item, index) => item === b[index]),\n compareBeforeUpdate: deepEquals,\n remoteMemoryCache: false,\n ...options,\n } satisfies CacheOptions<T>\n\n const cacheUpdateSubject = new ReplaySubject<CacheUpdateEvent<T>>(1)\n\n /** 解析 deps,如果元素是函数则调用获取值 */\n const resolveDeps = () => deps.map(dep => typeof dep === 'function' ? dep() : dep)\n\n const queryFnEnhancer = (fn: (args: Parameters<T>) => Promise<ReturnType<T>>, options: Pick<CacheOptions<T>, 'onSuccess' | 'onError'> & { type: 'remote' | 'local' }) => (((args) => {\n const promiseResult = fn(args);\n\n promiseResult\n .then(result => {\n const res = { type: options.type, result: result as Awaited<ReturnType<T>> }\n options.onSuccess?.(res)\n return res\n }, options.onError)\n .catch(() => {})\n\n return promiseResult\n }) as typeof fn)\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.deps, args.concat(resolveDeps()))\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!,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.success = true\n const result = (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 }\n }\n\n const queryRemoteWithEnhancer = queryFnEnhancer(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: CacheItem<T> = {\n deps: args.concat(resolveDeps()),\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)\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 }, { type: 'remote' as const, onSuccess, onError })\n\n const _fn = async (...args: Parameters<T>) => {\n await beforeRequest?.()\n\n const [cacheResult, remoteResult] = [\n queryFnEnhancer(getLocalCache, { type: 'local' as const, onSuccess })(args),\n queryRemoteWithEnhancer(args),\n ]\n const result = await Promise.race([\n cacheResult.catch(() => remoteResult),\n remoteResult,\n ])\n\n return result\n }\n\n _fn.refresh = (...args: Parameters<T>) => queryRemoteWithEnhancer(args)\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\n/**\n * 使用自定义数据库实例\n * @param newDb 数据库实例或 storeName,如果为字符串,则使用 localforage 创建一个实例\n */\ncreateQueryWithCache.useDb = <T extends string>(newDb: DBType | (T extends 'main-store' ? never : T)) => {\n if (typeof newDb === 'string') {\n db = localforage.createInstance({\n name: DB_NAME,\n storeName: newDb,\n })\n }\n else {\n db = newDb\n }\n}\n\nexport { createQueryWithCache }\n"]}
|
package/lib/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ declare class ReplaySubject<T> {
|
|
|
11
11
|
type DBType = Pick<LocalForage, 'getItem' | 'setItem' | 'removeItem' | 'clear'>;
|
|
12
12
|
interface CacheItem<T extends (...args: any[]) => Promise<any>> {
|
|
13
13
|
/** 缓存参数 */
|
|
14
|
-
|
|
14
|
+
deps: any[];
|
|
15
15
|
/** 缓存版本,用于缓存数据结构变更 */
|
|
16
16
|
version?: string;
|
|
17
17
|
/** 缓存过期时间,为空表示永不过期 */
|
|
@@ -19,11 +19,15 @@ interface CacheItem<T extends (...args: any[]) => Promise<any>> {
|
|
|
19
19
|
/** 缓存结果 */
|
|
20
20
|
result: Awaited<ReturnType<T>>;
|
|
21
21
|
}
|
|
22
|
+
/** 依赖项类型,支持直接值或函数形式(运行时取值) */
|
|
23
|
+
type DepItem = unknown | (() => unknown);
|
|
22
24
|
interface CacheOptions<T extends (...args: any[]) => Promise<any>> {
|
|
23
25
|
/** 缓存过期时间,为空表示永不过期 */
|
|
24
26
|
maxAge?: number;
|
|
25
27
|
/** 缓存版本,用于缓存数据结构变更 */
|
|
26
28
|
version?: string;
|
|
29
|
+
/** 依赖项,用于缓存依赖项变更。支持函数形式,函数会在运行时调用取值 */
|
|
30
|
+
deps?: DepItem[];
|
|
27
31
|
/**
|
|
28
32
|
* 重试次数,为0表示不重试
|
|
29
33
|
* @default 0
|
|
@@ -43,8 +47,11 @@ interface CacheOptions<T extends (...args: any[]) => Promise<any>> {
|
|
|
43
47
|
* @default false
|
|
44
48
|
*/
|
|
45
49
|
remoteMemoryCache?: boolean;
|
|
46
|
-
/**
|
|
47
|
-
|
|
50
|
+
/**
|
|
51
|
+
* 比较依赖项(函数参数及 deps)是否相等
|
|
52
|
+
* @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index])
|
|
53
|
+
* */
|
|
54
|
+
equals?: (prev: any[], next: any[]) => boolean;
|
|
48
55
|
/**
|
|
49
56
|
* 根据入参决定是否添加缓存
|
|
50
57
|
* 不传的话默认启用缓存
|
|
@@ -69,6 +76,7 @@ interface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {
|
|
|
69
76
|
}
|
|
70
77
|
declare function createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>): {
|
|
71
78
|
(...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
79
|
+
refresh(...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
72
80
|
subscribeCacheUpdate(fn: (data: CacheUpdateEvent<T>) => void): () => void;
|
|
73
81
|
clearCache(): Promise<void>;
|
|
74
82
|
};
|
|
@@ -78,10 +86,4 @@ declare namespace createQueryWithCache {
|
|
|
78
86
|
|
|
79
87
|
declare function fnRunner<T extends (...args: any[]) => Promise<any>>(fn: T, retryTimes?: number): Promise<Awaited<ReturnType<T>>>;
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
* @shihengtech/utils - A collection of utility functions
|
|
83
|
-
*/
|
|
84
|
-
|
|
85
|
-
declare const version = "0.0.3";
|
|
86
|
-
|
|
87
|
-
export { ReplaySubject, createQueryWithCache, fnRunner, version };
|
|
89
|
+
export { ReplaySubject, createQueryWithCache, fnRunner };
|
package/lib/index.js
CHANGED
|
@@ -94,9 +94,10 @@ var db = localforage__default.default.createInstance({
|
|
|
94
94
|
});
|
|
95
95
|
function createQueryWithCache(key, fn, options) {
|
|
96
96
|
const {
|
|
97
|
+
deps,
|
|
97
98
|
retry,
|
|
98
99
|
maxAge,
|
|
99
|
-
version
|
|
100
|
+
version,
|
|
100
101
|
compareBeforeUpdate,
|
|
101
102
|
remoteMemoryCache,
|
|
102
103
|
equals,
|
|
@@ -106,15 +107,28 @@ function createQueryWithCache(key, fn, options) {
|
|
|
106
107
|
onError,
|
|
107
108
|
errorHandler
|
|
108
109
|
} = __spreadValues({
|
|
109
|
-
|
|
110
|
+
deps: [],
|
|
110
111
|
retry: 0,
|
|
112
|
+
equals: (a, b) => a.length === b.length && a.every((item, index) => item === b[index]),
|
|
111
113
|
compareBeforeUpdate: ramda.equals,
|
|
112
114
|
remoteMemoryCache: false
|
|
113
115
|
}, options);
|
|
114
116
|
const cacheUpdateSubject = new ReplaySubject(1);
|
|
117
|
+
const resolveDeps = () => deps.map((dep) => typeof dep === "function" ? dep() : dep);
|
|
118
|
+
const queryFnEnhancer = (fn2, options2) => ((args) => {
|
|
119
|
+
const promiseResult = fn2(args);
|
|
120
|
+
promiseResult.then((result) => {
|
|
121
|
+
var _a;
|
|
122
|
+
const res = { type: options2.type, result };
|
|
123
|
+
(_a = options2.onSuccess) == null ? void 0 : _a.call(options2, res);
|
|
124
|
+
return res;
|
|
125
|
+
}, options2.onError).catch(() => {
|
|
126
|
+
});
|
|
127
|
+
return promiseResult;
|
|
128
|
+
});
|
|
115
129
|
const getLocalCache = (args) => __async(null, null, function* () {
|
|
116
130
|
const cache = yield db.getItem(key);
|
|
117
|
-
if (cache && (!
|
|
131
|
+
if (cache && (!version || cache.version === version) && (!cache.expires || cache.expires > Date.now()) && equals(cache.deps, args.concat(resolveDeps()))) {
|
|
118
132
|
return cache.result;
|
|
119
133
|
}
|
|
120
134
|
throw new Error("Cache not found");
|
|
@@ -148,7 +162,7 @@ function createQueryWithCache(key, fn, options) {
|
|
|
148
162
|
}).catch(clearMemoryCache);
|
|
149
163
|
return { type: "remote", promiseResult: result };
|
|
150
164
|
};
|
|
151
|
-
const
|
|
165
|
+
const queryRemoteWithEnhancer = queryFnEnhancer((args) => __async(null, null, function* () {
|
|
152
166
|
const rs = {
|
|
153
167
|
type: "remote",
|
|
154
168
|
result: null
|
|
@@ -159,11 +173,11 @@ function createQueryWithCache(key, fn, options) {
|
|
|
159
173
|
type === "remote" && (!cacheEnabled || (yield cacheEnabled(args))) && Promise.resolve().then(() => __async(null, null, function* () {
|
|
160
174
|
const oldCache = yield getLocalCache(args).catch(() => null);
|
|
161
175
|
const cacheData = __spreadValues(__spreadValues({
|
|
162
|
-
args,
|
|
176
|
+
deps: args.concat(resolveDeps()),
|
|
163
177
|
result
|
|
164
|
-
},
|
|
178
|
+
}, version && { version }), maxAge && { expires: Date.now() + maxAge });
|
|
165
179
|
db.setItem(key, cacheData);
|
|
166
|
-
if (compareBeforeUpdate && oldCache && compareBeforeUpdate(oldCache
|
|
180
|
+
if (compareBeforeUpdate && oldCache && compareBeforeUpdate(oldCache, result)) {
|
|
167
181
|
return;
|
|
168
182
|
}
|
|
169
183
|
cacheUpdateSubject.next({
|
|
@@ -180,33 +194,20 @@ function createQueryWithCache(key, fn, options) {
|
|
|
180
194
|
}
|
|
181
195
|
}
|
|
182
196
|
return rs.result;
|
|
183
|
-
});
|
|
197
|
+
}), { type: "remote", onSuccess, onError });
|
|
184
198
|
const _fn = (...args) => __async(null, null, function* () {
|
|
185
199
|
yield beforeRequest == null ? void 0 : beforeRequest();
|
|
186
200
|
const [cacheResult, remoteResult] = [
|
|
187
|
-
getLocalCache(args),
|
|
188
|
-
|
|
201
|
+
queryFnEnhancer(getLocalCache, { type: "local", onSuccess })(args),
|
|
202
|
+
queryRemoteWithEnhancer(args)
|
|
189
203
|
];
|
|
190
|
-
cacheResult.then(
|
|
191
|
-
(result2) => onSuccess == null ? void 0 : onSuccess({
|
|
192
|
-
type: "local",
|
|
193
|
-
result: result2
|
|
194
|
-
})
|
|
195
|
-
).catch(() => {
|
|
196
|
-
});
|
|
197
|
-
remoteResult.then(
|
|
198
|
-
(result2) => onSuccess == null ? void 0 : onSuccess({
|
|
199
|
-
type: "remote",
|
|
200
|
-
result: result2
|
|
201
|
-
})
|
|
202
|
-
).catch((error) => onError == null ? void 0 : onError(error)).catch(() => {
|
|
203
|
-
});
|
|
204
204
|
const result = yield Promise.race([
|
|
205
205
|
cacheResult.catch(() => remoteResult),
|
|
206
206
|
remoteResult
|
|
207
207
|
]);
|
|
208
208
|
return result;
|
|
209
209
|
});
|
|
210
|
+
_fn.refresh = (...args) => queryRemoteWithEnhancer(args);
|
|
210
211
|
_fn.subscribeCacheUpdate = (fn2) => cacheUpdateSubject.subscribe(fn2);
|
|
211
212
|
_fn.clearCache = () => __async(null, null, function* () {
|
|
212
213
|
clearMemoryCache();
|
|
@@ -225,12 +226,8 @@ createQueryWithCache.useDb = (newDb) => {
|
|
|
225
226
|
}
|
|
226
227
|
};
|
|
227
228
|
|
|
228
|
-
// src/index.ts
|
|
229
|
-
var version = "0.0.3";
|
|
230
|
-
|
|
231
229
|
exports.ReplaySubject = ReplaySubject;
|
|
232
230
|
exports.createQueryWithCache = createQueryWithCache;
|
|
233
231
|
exports.fnRunner = fnRunner;
|
|
234
|
-
exports.version = version;
|
|
235
232
|
//# sourceMappingURL=index.js.map
|
|
236
233
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +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.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 extends (...args: any[]) => Promise<any>>(fn: T, retryTimes: number = 0): Promise<Awaited<ReturnType<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\ntype DBType = Pick<\n LocalForage,\n 'getItem' | 'setItem' | 'removeItem' | 'clear'\n // 这些可以暂时不考虑\n // | 'clear' | 'keys' | 'length'\n>\n\nconst DB_NAME = 'sh-cache-common'\n\nlet db: DBType = localforage.createInstance({\n name: DB_NAME,\n storeName: 'main-store',\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 } satisfies 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!,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.success = true\n const result = (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 }\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.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\n/**\n * 使用自定义数据库实例\n * @param newDb 数据库实例或 storeName,如果为字符串,则使用 localforage 创建一个实例\n */\ncreateQueryWithCache.useDb = <T extends string>(newDb: DBType | (T extends 'main-store' ? never : T)) => {\n if (typeof newDb === 'string') {\n db = localforage.createInstance({\n name: DB_NAME,\n storeName: newDb,\n })\n }\n else {\n db = newDb\n }\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.3'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/class/ReplaySubject/index.ts","../src/utils/fnRunner/index.ts","../src/utils/createQueryWithCache/index.ts"],"names":["localforage","deepEquals","fn","options","result"],"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 extends (...args: any[]) => Promise<any>>(fn: T, retryTimes: number = 0): Promise<Awaited<ReturnType<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\ntype DBType = Pick<\n LocalForage,\n 'getItem' | 'setItem' | 'removeItem' | 'clear'\n // 这些可以暂时不考虑\n // | 'clear' | 'keys' | 'length'\n>\n\nconst DB_NAME = 'sh-cache-common'\n\nlet db: DBType = localforage.createInstance({\n name: DB_NAME,\n storeName: 'main-store',\n // driver: [localforage.LOCALSTORAGE, localforage.INDEXEDDB, localforage.WEBSQL],\n})\n\ninterface CacheItem<T extends (...args: any[]) => Promise<any>> {\n /** 缓存参数 */\n deps: any[]\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 缓存过期时间,为空表示永不过期 */\n expires?: number\n /** 缓存结果 */\n result: Awaited<ReturnType<T>>\n}\n\n/** 依赖项类型,支持直接值或函数形式(运行时取值) */\ntype DepItem = unknown | (() => unknown)\n\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 依赖项,用于缓存依赖项变更。支持函数形式,函数会在运行时调用取值 */\n deps?: DepItem[];\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 /**\n * 比较依赖项(函数参数及 deps)是否相等\n * @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index])\n * */\n equals?: (prev: any[], next: any[]) => 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 deps,\n retry,\n maxAge,\n version,\n compareBeforeUpdate,\n remoteMemoryCache,\n equals,\n cacheEnabled,\n beforeRequest,\n onSuccess,\n onError,\n errorHandler,\n } = {\n deps: [],\n retry: 0,\n equals: (a, b) =>\n a.length === b.length && a.every((item, index) => item === b[index]),\n compareBeforeUpdate: deepEquals,\n remoteMemoryCache: false,\n ...options,\n } satisfies CacheOptions<T>\n\n const cacheUpdateSubject = new ReplaySubject<CacheUpdateEvent<T>>(1)\n\n /** 解析 deps,如果元素是函数则调用获取值 */\n const resolveDeps = () => deps.map(dep => typeof dep === 'function' ? dep() : dep)\n\n const queryFnEnhancer = (fn: (args: Parameters<T>) => Promise<ReturnType<T>>, options: Pick<CacheOptions<T>, 'onSuccess' | 'onError'> & { type: 'remote' | 'local' }) => (((args) => {\n const promiseResult = fn(args);\n\n promiseResult\n .then(result => {\n const res = { type: options.type, result: result as Awaited<ReturnType<T>> }\n options.onSuccess?.(res)\n return res\n }, options.onError)\n .catch(() => {})\n\n return promiseResult\n }) as typeof fn)\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.deps, args.concat(resolveDeps()))\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!,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.success = true\n const result = (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 }\n }\n\n const queryRemoteWithEnhancer = queryFnEnhancer(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: CacheItem<T> = {\n deps: args.concat(resolveDeps()),\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)\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 }, { type: 'remote' as const, onSuccess, onError })\n\n const _fn = async (...args: Parameters<T>) => {\n await beforeRequest?.()\n\n const [cacheResult, remoteResult] = [\n queryFnEnhancer(getLocalCache, { type: 'local' as const, onSuccess })(args),\n queryRemoteWithEnhancer(args),\n ]\n const result = await Promise.race([\n cacheResult.catch(() => remoteResult),\n remoteResult,\n ])\n\n return result\n }\n\n _fn.refresh = (...args: Parameters<T>) => queryRemoteWithEnhancer(args)\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\n/**\n * 使用自定义数据库实例\n * @param newDb 数据库实例或 storeName,如果为字符串,则使用 localforage 创建一个实例\n */\ncreateQueryWithCache.useDb = <T extends string>(newDb: DBType | (T extends 'main-store' ? never : T)) => {\n if (typeof newDb === 'string') {\n db = localforage.createInstance({\n name: DB_NAME,\n storeName: newDb,\n })\n }\n else {\n db = newDb\n }\n}\n\nexport { createQueryWithCache }\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shihengtech/utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "A collection of utility tools for frontend projects",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "asakura",
|
|
@@ -42,6 +42,19 @@
|
|
|
42
42
|
"access": "public",
|
|
43
43
|
"registry": "https://registry.npmjs.org/"
|
|
44
44
|
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"dev": "tsup --watch",
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"test": "vitest",
|
|
49
|
+
"test:run": "vitest run",
|
|
50
|
+
"test:coverage": "vitest run --coverage",
|
|
51
|
+
"docs:dev": "vitepress dev docs",
|
|
52
|
+
"docs:build": "vitepress build docs",
|
|
53
|
+
"docs:preview": "vitepress preview docs",
|
|
54
|
+
"lint": "eslint .",
|
|
55
|
+
"lint:fix": "eslint . --fix",
|
|
56
|
+
"prepublishOnly": "npm run build"
|
|
57
|
+
},
|
|
45
58
|
"dependencies": {
|
|
46
59
|
"localforage": "^1.10.0",
|
|
47
60
|
"ramda": "^0.32.0",
|
|
@@ -58,17 +71,5 @@
|
|
|
58
71
|
"typescript": "^5.3.2",
|
|
59
72
|
"vitepress": "^1.0.0-rc.31",
|
|
60
73
|
"vitest": "^1.0.0"
|
|
61
|
-
},
|
|
62
|
-
"scripts": {
|
|
63
|
-
"dev": "tsup --watch",
|
|
64
|
-
"build": "tsup",
|
|
65
|
-
"test": "vitest",
|
|
66
|
-
"test:run": "vitest run",
|
|
67
|
-
"test:coverage": "vitest run --coverage",
|
|
68
|
-
"docs:dev": "vitepress dev docs",
|
|
69
|
-
"docs:build": "vitepress build docs",
|
|
70
|
-
"docs:preview": "vitepress preview docs",
|
|
71
|
-
"lint": "eslint .",
|
|
72
|
-
"lint:fix": "eslint . --fix"
|
|
73
74
|
}
|
|
74
|
-
}
|
|
75
|
+
}
|