@shihengtech/utils 0.0.5 → 0.0.7
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 +28 -7
- package/es/index.js.map +1 -1
- package/lib/index.d.ts +6 -4
- package/lib/index.js +28 -7
- package/lib/index.js.map +1 -1
- package/package.json +11 -1
package/es/index.js
CHANGED
|
@@ -108,6 +108,7 @@ function createQueryWithCache(key, fn, options) {
|
|
|
108
108
|
remoteMemoryCache: false
|
|
109
109
|
}, options);
|
|
110
110
|
const cacheUpdateSubject = new ReplaySubject(1);
|
|
111
|
+
const resolveDeps = () => typeof deps === "function" ? deps() : deps;
|
|
111
112
|
const queryFnEnhancer = (fn2, options2) => ((args) => {
|
|
112
113
|
const promiseResult = fn2(args);
|
|
113
114
|
promiseResult.then((result) => {
|
|
@@ -121,7 +122,7 @@ function createQueryWithCache(key, fn, options) {
|
|
|
121
122
|
});
|
|
122
123
|
const getLocalCache = (args) => __async(null, null, function* () {
|
|
123
124
|
const cache = yield db.getItem(key);
|
|
124
|
-
if (cache && (!version || cache.version === version) && (!cache.expires || cache.expires > Date.now()) && equals$1(cache.deps, args.concat(
|
|
125
|
+
if (cache && (!version || cache.version === version) && (!cache.expires || cache.expires > Date.now()) && equals$1(cache.deps, args.concat(resolveDeps()))) {
|
|
125
126
|
return cache.result;
|
|
126
127
|
}
|
|
127
128
|
throw new Error("Cache not found");
|
|
@@ -129,30 +130,40 @@ function createQueryWithCache(key, fn, options) {
|
|
|
129
130
|
const remoteResultCache = {
|
|
130
131
|
success: false,
|
|
131
132
|
args: [],
|
|
133
|
+
deps: [],
|
|
132
134
|
result: null
|
|
133
135
|
};
|
|
134
136
|
const clearMemoryCache = () => {
|
|
135
137
|
remoteResultCache.success = false;
|
|
136
138
|
remoteResultCache.result = null;
|
|
137
139
|
remoteResultCache.args = [];
|
|
140
|
+
remoteResultCache.deps = [];
|
|
138
141
|
};
|
|
139
142
|
const queryWithMemoryCache = (args) => {
|
|
140
|
-
if (
|
|
143
|
+
if (!remoteMemoryCache) {
|
|
144
|
+
return {
|
|
145
|
+
type: "remote",
|
|
146
|
+
promiseResult: fnRunner(
|
|
147
|
+
() => fn(...args),
|
|
148
|
+
retry
|
|
149
|
+
)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const currentDeps = resolveDeps();
|
|
153
|
+
if (remoteResultCache.success && equals$1(remoteResultCache.args, args) && equals$1(remoteResultCache.deps, currentDeps)) {
|
|
141
154
|
return {
|
|
142
155
|
type: "memory",
|
|
143
156
|
promiseResult: remoteResultCache.result
|
|
144
157
|
};
|
|
145
158
|
}
|
|
146
159
|
remoteResultCache.args = args;
|
|
160
|
+
remoteResultCache.deps = currentDeps;
|
|
147
161
|
remoteResultCache.success = true;
|
|
148
162
|
const result = remoteResultCache.result = fnRunner(
|
|
149
163
|
() => fn(...args),
|
|
150
164
|
retry
|
|
151
165
|
);
|
|
152
|
-
Promise.resolve(result).
|
|
153
|
-
!remoteMemoryCache && clearMemoryCache();
|
|
154
|
-
return result2;
|
|
155
|
-
}).catch(clearMemoryCache);
|
|
166
|
+
Promise.resolve(result).catch(clearMemoryCache);
|
|
156
167
|
return { type: "remote", promiseResult: result };
|
|
157
168
|
};
|
|
158
169
|
const queryRemoteWithEnhancer = queryFnEnhancer((args) => __async(null, null, function* () {
|
|
@@ -166,7 +177,7 @@ function createQueryWithCache(key, fn, options) {
|
|
|
166
177
|
type === "remote" && (!cacheEnabled || (yield cacheEnabled(args))) && Promise.resolve().then(() => __async(null, null, function* () {
|
|
167
178
|
const oldCache = yield getLocalCache(args).catch(() => null);
|
|
168
179
|
const cacheData = __spreadValues(__spreadValues({
|
|
169
|
-
deps: args.concat(
|
|
180
|
+
deps: args.concat(resolveDeps()),
|
|
170
181
|
result
|
|
171
182
|
}, version && { version }), maxAge && { expires: Date.now() + maxAge });
|
|
172
183
|
db.setItem(key, cacheData);
|
|
@@ -201,6 +212,16 @@ function createQueryWithCache(key, fn, options) {
|
|
|
201
212
|
return result;
|
|
202
213
|
});
|
|
203
214
|
_fn.refresh = (...args) => queryRemoteWithEnhancer(args);
|
|
215
|
+
_fn.getCache = () => db.getItem(key);
|
|
216
|
+
_fn.updateCache = (value) => __async(null, null, function* () {
|
|
217
|
+
if (typeof value === "function") {
|
|
218
|
+
const prevCache = yield _fn.getCache().catch(() => null);
|
|
219
|
+
const newCache = value(prevCache);
|
|
220
|
+
yield db.setItem(key, newCache);
|
|
221
|
+
} else {
|
|
222
|
+
yield db.setItem(key, value);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
204
225
|
_fn.subscribeCacheUpdate = (fn2) => cacheUpdateSubject.subscribe(fn2);
|
|
205
226
|
_fn.clearCache = () => __async(null, null, function* () {
|
|
206
227
|
clearMemoryCache();
|
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"],"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\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 依赖项,用于缓存依赖项变更 */\n deps?: unknown[];\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 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(deps))\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(deps),\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/class/ReplaySubject/index.ts","../src/utils/fnRunner/index.ts","../src/utils/createQueryWithCache/index.ts"],"names":["equals","deepEquals","fn","options"],"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\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 依赖项,用于缓存依赖项变更。支持函数形式,函数会在运行时调用取值 */\n deps?: readonly unknown[] | (() => readonly unknown[])\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: readonly unknown[], next: readonly unknown[]) => 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 = () => (typeof deps === 'function' ? deps() : deps)\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 deps: readonly unknown[]\n result: ReturnType<T> | null\n } = {\n success: false,\n args: [] as any,\n deps: [],\n result: null,\n }\n\n const clearMemoryCache = () => {\n remoteResultCache.success = false\n remoteResultCache.result = null\n remoteResultCache.args = [] as any\n remoteResultCache.deps = []\n }\n\n const queryWithMemoryCache = (args: Parameters<T>) => {\n if (!remoteMemoryCache) {\n return {\n type: 'remote' as const,\n promiseResult: fnRunner(\n () => fn(...args),\n retry,\n ),\n }\n }\n\n const currentDeps = resolveDeps()\n if (\n remoteResultCache.success\n && equals(remoteResultCache.args, args)\n && equals(remoteResultCache.deps, currentDeps)\n ) {\n return {\n type: 'memory' as const,\n promiseResult: remoteResultCache.result!,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.deps = currentDeps\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 .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.getCache = () => db.getItem<CacheItem<T>>(key)\n\n _fn.updateCache = async (value: (CacheItem<T> | ((prev: CacheItem<T> | null) => CacheItem<T>))) => {\n if (typeof value === 'function') {\n const prevCache = await _fn.getCache().catch(() => null)\n const newCache = value(prevCache)\n await db.setItem(key, newCache)\n }\n else {\n await db.setItem(key, value)\n }\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"]}
|
package/lib/index.d.ts
CHANGED
|
@@ -24,8 +24,8 @@ interface CacheOptions<T extends (...args: any[]) => Promise<any>> {
|
|
|
24
24
|
maxAge?: number;
|
|
25
25
|
/** 缓存版本,用于缓存数据结构变更 */
|
|
26
26
|
version?: string;
|
|
27
|
-
/**
|
|
28
|
-
deps?: unknown[];
|
|
27
|
+
/** 依赖项,用于缓存依赖项变更。支持函数形式,函数会在运行时调用取值 */
|
|
28
|
+
deps?: readonly unknown[] | (() => readonly unknown[]);
|
|
29
29
|
/**
|
|
30
30
|
* 重试次数,为0表示不重试
|
|
31
31
|
* @default 0
|
|
@@ -48,8 +48,8 @@ interface CacheOptions<T extends (...args: any[]) => Promise<any>> {
|
|
|
48
48
|
/**
|
|
49
49
|
* 比较依赖项(函数参数及 deps)是否相等
|
|
50
50
|
* @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index])
|
|
51
|
-
|
|
52
|
-
equals?: (prev:
|
|
51
|
+
*/
|
|
52
|
+
equals?: (prev: readonly unknown[], next: readonly unknown[]) => boolean;
|
|
53
53
|
/**
|
|
54
54
|
* 根据入参决定是否添加缓存
|
|
55
55
|
* 不传的话默认启用缓存
|
|
@@ -75,6 +75,8 @@ interface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {
|
|
|
75
75
|
declare function createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>): {
|
|
76
76
|
(...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
77
77
|
refresh(...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
78
|
+
getCache(): Promise<CacheItem<T> | null>;
|
|
79
|
+
updateCache(value: (CacheItem<T> | ((prev: CacheItem<T> | null) => CacheItem<T>))): Promise<void>;
|
|
78
80
|
subscribeCacheUpdate(fn: (data: CacheUpdateEvent<T>) => void): () => void;
|
|
79
81
|
clearCache(): Promise<void>;
|
|
80
82
|
};
|
package/lib/index.js
CHANGED
|
@@ -114,6 +114,7 @@ function createQueryWithCache(key, fn, options) {
|
|
|
114
114
|
remoteMemoryCache: false
|
|
115
115
|
}, options);
|
|
116
116
|
const cacheUpdateSubject = new ReplaySubject(1);
|
|
117
|
+
const resolveDeps = () => typeof deps === "function" ? deps() : deps;
|
|
117
118
|
const queryFnEnhancer = (fn2, options2) => ((args) => {
|
|
118
119
|
const promiseResult = fn2(args);
|
|
119
120
|
promiseResult.then((result) => {
|
|
@@ -127,7 +128,7 @@ function createQueryWithCache(key, fn, options) {
|
|
|
127
128
|
});
|
|
128
129
|
const getLocalCache = (args) => __async(null, null, function* () {
|
|
129
130
|
const cache = yield db.getItem(key);
|
|
130
|
-
if (cache && (!version || cache.version === version) && (!cache.expires || cache.expires > Date.now()) && equals(cache.deps, args.concat(
|
|
131
|
+
if (cache && (!version || cache.version === version) && (!cache.expires || cache.expires > Date.now()) && equals(cache.deps, args.concat(resolveDeps()))) {
|
|
131
132
|
return cache.result;
|
|
132
133
|
}
|
|
133
134
|
throw new Error("Cache not found");
|
|
@@ -135,30 +136,40 @@ function createQueryWithCache(key, fn, options) {
|
|
|
135
136
|
const remoteResultCache = {
|
|
136
137
|
success: false,
|
|
137
138
|
args: [],
|
|
139
|
+
deps: [],
|
|
138
140
|
result: null
|
|
139
141
|
};
|
|
140
142
|
const clearMemoryCache = () => {
|
|
141
143
|
remoteResultCache.success = false;
|
|
142
144
|
remoteResultCache.result = null;
|
|
143
145
|
remoteResultCache.args = [];
|
|
146
|
+
remoteResultCache.deps = [];
|
|
144
147
|
};
|
|
145
148
|
const queryWithMemoryCache = (args) => {
|
|
146
|
-
if (
|
|
149
|
+
if (!remoteMemoryCache) {
|
|
150
|
+
return {
|
|
151
|
+
type: "remote",
|
|
152
|
+
promiseResult: fnRunner(
|
|
153
|
+
() => fn(...args),
|
|
154
|
+
retry
|
|
155
|
+
)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const currentDeps = resolveDeps();
|
|
159
|
+
if (remoteResultCache.success && equals(remoteResultCache.args, args) && equals(remoteResultCache.deps, currentDeps)) {
|
|
147
160
|
return {
|
|
148
161
|
type: "memory",
|
|
149
162
|
promiseResult: remoteResultCache.result
|
|
150
163
|
};
|
|
151
164
|
}
|
|
152
165
|
remoteResultCache.args = args;
|
|
166
|
+
remoteResultCache.deps = currentDeps;
|
|
153
167
|
remoteResultCache.success = true;
|
|
154
168
|
const result = remoteResultCache.result = fnRunner(
|
|
155
169
|
() => fn(...args),
|
|
156
170
|
retry
|
|
157
171
|
);
|
|
158
|
-
Promise.resolve(result).
|
|
159
|
-
!remoteMemoryCache && clearMemoryCache();
|
|
160
|
-
return result2;
|
|
161
|
-
}).catch(clearMemoryCache);
|
|
172
|
+
Promise.resolve(result).catch(clearMemoryCache);
|
|
162
173
|
return { type: "remote", promiseResult: result };
|
|
163
174
|
};
|
|
164
175
|
const queryRemoteWithEnhancer = queryFnEnhancer((args) => __async(null, null, function* () {
|
|
@@ -172,7 +183,7 @@ function createQueryWithCache(key, fn, options) {
|
|
|
172
183
|
type === "remote" && (!cacheEnabled || (yield cacheEnabled(args))) && Promise.resolve().then(() => __async(null, null, function* () {
|
|
173
184
|
const oldCache = yield getLocalCache(args).catch(() => null);
|
|
174
185
|
const cacheData = __spreadValues(__spreadValues({
|
|
175
|
-
deps: args.concat(
|
|
186
|
+
deps: args.concat(resolveDeps()),
|
|
176
187
|
result
|
|
177
188
|
}, version && { version }), maxAge && { expires: Date.now() + maxAge });
|
|
178
189
|
db.setItem(key, cacheData);
|
|
@@ -207,6 +218,16 @@ function createQueryWithCache(key, fn, options) {
|
|
|
207
218
|
return result;
|
|
208
219
|
});
|
|
209
220
|
_fn.refresh = (...args) => queryRemoteWithEnhancer(args);
|
|
221
|
+
_fn.getCache = () => db.getItem(key);
|
|
222
|
+
_fn.updateCache = (value) => __async(null, null, function* () {
|
|
223
|
+
if (typeof value === "function") {
|
|
224
|
+
const prevCache = yield _fn.getCache().catch(() => null);
|
|
225
|
+
const newCache = value(prevCache);
|
|
226
|
+
yield db.setItem(key, newCache);
|
|
227
|
+
} else {
|
|
228
|
+
yield db.setItem(key, value);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
210
231
|
_fn.subscribeCacheUpdate = (fn2) => cacheUpdateSubject.subscribe(fn2);
|
|
211
232
|
_fn.clearCache = () => __async(null, null, function* () {
|
|
212
233
|
clearMemoryCache();
|
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"],"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\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 依赖项,用于缓存依赖项变更 */\n deps?: unknown[];\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 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(deps))\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(deps),\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/class/ReplaySubject/index.ts","../src/utils/fnRunner/index.ts","../src/utils/createQueryWithCache/index.ts"],"names":["localforage","deepEquals","fn","options"],"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\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 依赖项,用于缓存依赖项变更。支持函数形式,函数会在运行时调用取值 */\n deps?: readonly unknown[] | (() => readonly unknown[])\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: readonly unknown[], next: readonly unknown[]) => 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 = () => (typeof deps === 'function' ? deps() : deps)\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 deps: readonly unknown[]\n result: ReturnType<T> | null\n } = {\n success: false,\n args: [] as any,\n deps: [],\n result: null,\n }\n\n const clearMemoryCache = () => {\n remoteResultCache.success = false\n remoteResultCache.result = null\n remoteResultCache.args = [] as any\n remoteResultCache.deps = []\n }\n\n const queryWithMemoryCache = (args: Parameters<T>) => {\n if (!remoteMemoryCache) {\n return {\n type: 'remote' as const,\n promiseResult: fnRunner(\n () => fn(...args),\n retry,\n ),\n }\n }\n\n const currentDeps = resolveDeps()\n if (\n remoteResultCache.success\n && equals(remoteResultCache.args, args)\n && equals(remoteResultCache.deps, currentDeps)\n ) {\n return {\n type: 'memory' as const,\n promiseResult: remoteResultCache.result!,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.deps = currentDeps\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 .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.getCache = () => db.getItem<CacheItem<T>>(key)\n\n _fn.updateCache = async (value: (CacheItem<T> | ((prev: CacheItem<T> | null) => CacheItem<T>))) => {\n if (typeof value === 'function') {\n const prevCache = await _fn.getCache().catch(() => null)\n const newCache = value(prevCache)\n await db.setItem(key, newCache)\n }\n else {\n await db.setItem(key, value)\n }\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shihengtech/utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "A collection of utility tools for frontend projects",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "asakura",
|
|
@@ -60,13 +60,23 @@
|
|
|
60
60
|
"ramda": "^0.32.0",
|
|
61
61
|
"tslib": "^2.8.1"
|
|
62
62
|
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"react": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
63
71
|
"devDependencies": {
|
|
64
72
|
"@antfu/eslint-config": "^4.13.0",
|
|
65
73
|
"@types/node": "^20.10.0",
|
|
66
74
|
"@types/ramda": "^0.31.1",
|
|
75
|
+
"@types/react": "^18.0.0",
|
|
67
76
|
"@vitest/coverage-v8": "^1.0.0",
|
|
68
77
|
"eslint": "^9.27.0",
|
|
69
78
|
"eslint-plugin-format": "^1.0.2",
|
|
79
|
+
"react": "^18.0.0",
|
|
70
80
|
"tsup": "^8.0.1",
|
|
71
81
|
"typescript": "^5.3.2",
|
|
72
82
|
"vitepress": "^1.0.0-rc.31",
|