@shihengtech/utils 0.0.6 → 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 +26 -6
- package/es/index.js.map +1 -1
- package/lib/index.d.ts +5 -5
- package/lib/index.js +26 -6
- package/lib/index.js.map +1 -1
- package/package.json +11 -1
package/es/index.js
CHANGED
|
@@ -108,7 +108,7 @@ function createQueryWithCache(key, fn, options) {
|
|
|
108
108
|
remoteMemoryCache: false
|
|
109
109
|
}, options);
|
|
110
110
|
const cacheUpdateSubject = new ReplaySubject(1);
|
|
111
|
-
const resolveDeps = () =>
|
|
111
|
+
const resolveDeps = () => typeof deps === "function" ? deps() : deps;
|
|
112
112
|
const queryFnEnhancer = (fn2, options2) => ((args) => {
|
|
113
113
|
const promiseResult = fn2(args);
|
|
114
114
|
promiseResult.then((result) => {
|
|
@@ -130,30 +130,40 @@ function createQueryWithCache(key, fn, options) {
|
|
|
130
130
|
const remoteResultCache = {
|
|
131
131
|
success: false,
|
|
132
132
|
args: [],
|
|
133
|
+
deps: [],
|
|
133
134
|
result: null
|
|
134
135
|
};
|
|
135
136
|
const clearMemoryCache = () => {
|
|
136
137
|
remoteResultCache.success = false;
|
|
137
138
|
remoteResultCache.result = null;
|
|
138
139
|
remoteResultCache.args = [];
|
|
140
|
+
remoteResultCache.deps = [];
|
|
139
141
|
};
|
|
140
142
|
const queryWithMemoryCache = (args) => {
|
|
141
|
-
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)) {
|
|
142
154
|
return {
|
|
143
155
|
type: "memory",
|
|
144
156
|
promiseResult: remoteResultCache.result
|
|
145
157
|
};
|
|
146
158
|
}
|
|
147
159
|
remoteResultCache.args = args;
|
|
160
|
+
remoteResultCache.deps = currentDeps;
|
|
148
161
|
remoteResultCache.success = true;
|
|
149
162
|
const result = remoteResultCache.result = fnRunner(
|
|
150
163
|
() => fn(...args),
|
|
151
164
|
retry
|
|
152
165
|
);
|
|
153
|
-
Promise.resolve(result).
|
|
154
|
-
!remoteMemoryCache && clearMemoryCache();
|
|
155
|
-
return result2;
|
|
156
|
-
}).catch(clearMemoryCache);
|
|
166
|
+
Promise.resolve(result).catch(clearMemoryCache);
|
|
157
167
|
return { type: "remote", promiseResult: result };
|
|
158
168
|
};
|
|
159
169
|
const queryRemoteWithEnhancer = queryFnEnhancer((args) => __async(null, null, function* () {
|
|
@@ -202,6 +212,16 @@ function createQueryWithCache(key, fn, options) {
|
|
|
202
212
|
return result;
|
|
203
213
|
});
|
|
204
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
|
+
});
|
|
205
225
|
_fn.subscribeCacheUpdate = (fn2) => cacheUpdateSubject.subscribe(fn2);
|
|
206
226
|
_fn.clearCache = () => __async(null, null, function* () {
|
|
207
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAM,gBAAN,MAAuB;AAAA,EAMrB,WAAA,CAAY,gBAAgB,CAAA,EAAG;AAJ/B,IAAA,IAAA,CAAQ,SAAc,EAAC;AAEvB,IAAA,IAAA,CAAQ,gBAAuC,EAAC;AAG9C,IAAA,IAAA,CAAK,aAAA,GAAgB,aAAA;AAAA,EACvB;AAAA,EAEA,UAAU,EAAA,EAAuB;AAC/B,IAAA,IAAA,CAAK,aAAA,CAAc,KAAK,EAAE,CAAA;AAC1B,IAAA,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,CAAA,IAAA,KAAQ,EAAA,CAAG,IAAI,CAAC,CAAA;AACpC,IAAA,OAAO,MAAM,IAAA,CAAK,WAAA,CAAY,EAAE,CAAA;AAAA,EAClC;AAAA,EAEA,YAAY,EAAA,EAAuB;AACjC,IAAA,IAAA,CAAK,gBAAgB,IAAA,CAAK,aAAA,CAAc,MAAA,CAAO,CAAA,CAAA,KAAK,MAAM,EAAE,CAAA;AAAA,EAC9D;AAAA,EAEA,KAAK,IAAA,EAAS;AACZ,IAAA,IAAA,CAAK,aAAA,CAAc,OAAA,CAAQ,CAAA,EAAA,KAAM,EAAA,CAAG,IAAI,CAAC,CAAA;AACzC,IAAA,IAAA,CAAK,MAAA,CAAO,KAAK,IAAI,CAAA;AACrB,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,MAAA,GAAS,IAAA,CAAK,aAAA,EAAe;AAC3C,MAAA,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA;AAAA,IACnC;AAAA,EACF;AACF;;;AC3BA,SAAsB,QAAA,CAAqD,EAAA,EAAO,UAAA,GAAqB,CAAA,EAAoC;AAAA,EAAA,OAAA,OAAA,CAAA,IAAA,EAAA,IAAA,EAAA,aAAA;AACzI,IAAA,MAAM,KAAA,GAAA,CAAS,cAAc,CAAA,IAAK,CAAA;AAClC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,MAAA,IAAI;AACF,QAAA,OAAO,MAAM,EAAA,EAAG;AAAA,MAClB,SACO,KAAA,EAAO;AAEZ,QAAA,IAAI,CAAA,KAAM,QAAQ,CAAA,EAAG;AACnB,UAAA,MAAM,KAAA;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,IAAI,MAAM,qDAAqD,CAAA;AAAA,EACvE,CAAA,CAAA;AAAA;;;ACHA,IAAM,OAAA,GAAU,iBAAA;AAEhB,IAAI,EAAA,GAAa,YAAY,cAAA,CAAe;AAAA,EAC1C,IAAA,EAAM,OAAA;AAAA,EACN,SAAA,EAAW;AAAA;AAEb,CAAC,CAAA;AA8ED,SAAS,oBAAA,CAAiE,GAAA,EAAa,EAAA,EAAO,OAAA,EAA2B;AACvH,EAAA,MAAM;AAAA,IACJ,IAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,mBAAA;AAAA,IACA,iBAAA;AAAA,YACAA,QAAA;AAAA,IACA,YAAA;AAAA,IACA,aAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACF,GAAI,cAAA,CAAA;AAAA,IACF,MAAM,EAAC;AAAA,IACP,KAAA,EAAO,CAAA;AAAA,IACP,QAAQ,CAAC,CAAA,EAAG,CAAA,KACV,CAAA,CAAE,WAAW,CAAA,CAAE,MAAA,IAAU,CAAA,CAAE,KAAA,CAAM,CAAC,IAAA,EAAM,KAAA,KAAU,IAAA,KAAS,CAAA,CAAE,KAAK,CAAC,CAAA;AAAA,IACrE,mBAAA,EAAqBC,MAAA;AAAA,IACrB,iBAAA,EAAmB;AAAA,GAAA,EAChB,OAAA,CAAA;AAGL,EAAA,MAAM,kBAAA,GAAqB,IAAI,aAAA,CAAmC,CAAC,CAAA;AAGnE,EAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,GAAA,CAAI,CAAA,GAAA,KAAO,OAAO,GAAA,KAAQ,UAAA,GAAa,GAAA,EAAI,GAAI,GAAG,CAAA;AAEjF,EAAA,MAAM,eAAA,GAAkB,CAACC,GAAAA,EAAqDC,QAAAA,MAA6F,CAAC,IAAA,KAAS;AACnL,IAAA,MAAM,aAAA,GAAgBD,IAAG,IAAI,CAAA;AAE7B,IAAA,aAAA,CACG,KAAK,CAAA,MAAA,KAAU;AAjItB,MAAA,IAAA,EAAA;AAkIQ,MAAA,MAAM,GAAA,GAAM,EAAE,IAAA,EAAMC,QAAAA,CAAQ,MAAM,MAAA,EAAyC;AAC3E,MAAA,CAAA,EAAA,GAAAA,QAAAA,CAAQ,SAAA,KAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAAA,QAAAA,EAAoB,GAAA,CAAA;AACpB,MAAA,OAAO,GAAA;AAAA,IACT,CAAA,EAAGA,QAAAA,CAAQ,OAAO,CAAA,CACjB,MAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AAEjB,IAAA,OAAO,aAAA;AAAA,EACT,CAAA,CAAA;AAEA,EAAA,MAAM,aAAA,GAAgB,CAAO,IAAA,KAAwB,OAAA,CAAA,IAAA,EAAA,IAAA,EAAA,aAAA;AACnD,IAAA,MAAM,KAAA,GAAQ,MAAM,EAAA,CAAG,OAAA,CAAsB,GAAG,CAAA;AAChD,IAAA,IACE,KAAA,KACI,CAAC,OAAA,IAAW,KAAA,CAAM,YAAY,OAAA,CAAA,KAC9B,CAAC,KAAA,CAAM,OAAA,IAAW,KAAA,CAAM,OAAA,GAAU,KAAK,GAAA,EAAI,CAAA,IAC5CH,SAAO,KAAA,CAAM,IAAA,EAAM,KAAK,MAAA,CAAO,WAAA,EAAa,CAAC,CAAA,EAChD;AACA,MAAA,OAAO,KAAA,CAAM,MAAA;AAAA,IACf;AAEA,IAAA,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAAA,EACnC,CAAA,CAAA;AAEA,EAAA,MAAM,iBAAA,GAIF;AAAA,IACF,OAAA,EAAS,KAAA;AAAA,IACT,MAAM,EAAC;AAAA,IACP,MAAA,EAAQ;AAAA,GACV;AAEA,EAAA,MAAM,mBAAmB,MAAM;AAC7B,IAAA,iBAAA,CAAkB,OAAA,GAAU,KAAA;AAC5B,IAAA,iBAAA,CAAkB,MAAA,GAAS,IAAA;AAC3B,IAAA,iBAAA,CAAkB,OAAO,EAAC;AAAA,EAC5B,CAAA;AAEA,EAAA,MAAM,oBAAA,GAAuB,CAAC,IAAA,KAAwB;AACpD,IAAA,IAAI,kBAAkB,OAAA,IAAWA,QAAA,CAAO,iBAAA,CAAkB,IAAA,EAAM,IAAI,CAAA,EAAG;AACrE,MAAA,OAAO;AAAA,QACL,IAAA,EAAM,QAAA;AAAA,QACN,eAAe,iBAAA,CAAkB;AAAA,OACnC;AAAA,IACF;AAEA,IAAA,iBAAA,CAAkB,IAAA,GAAO,IAAA;AACzB,IAAA,iBAAA,CAAkB,OAAA,GAAU,IAAA;AAC5B,IAAA,MAAM,MAAA,GAAU,kBAAkB,MAAA,GAAS,QAAA;AAAA,MACzC,MAAM,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,MAChB;AAAA,KACF;AAEA,IAAA,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA,CACnB,IAAA,CAAK,CAACI,OAAAA,KAAW;AAChB,MAAA,CAAC,qBAAqB,gBAAA,EAAiB;AACvC,MAAA,OAAOA,OAAAA;AAAA,IACT,CAAC,CAAA,CACA,KAAA,CAAM,gBAAgB,CAAA;AAEzB,IAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAmB,aAAA,EAAe,MAAA,EAAO;AAAA,EAC1D,CAAA;AAEA,EAAA,MAAM,uBAAA,GAA0B,eAAA,CAAgB,CAAO,IAAA,KAAwB,OAAA,CAAA,IAAA,EAAA,IAAA,EAAA,aAAA;AAC7E,IAAA,MAAM,EAAA,GAAK;AAAA,MACT,IAAA,EAAM,QAAA;AAAA,MACN,MAAA,EAAQ;AAAA,KACV;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,IAAA,EAAM,aAAA,EAAc,GAAI,qBAAqB,IAAI,CAAA;AACzD,MAAA,MAAM,MAAA,GAAU,EAAA,CAAG,MAAA,GAAS,MAAM,aAAA;AAGlC,MAAA,IAAA,KAAS,QAAA,KACL,CAAC,YAAA,KAAiB,MAAM,YAAA,CAAa,IAAI,CAAA,CAAA,CAAA,IAC1C,OAAA,CAAQ,OAAA,EAAQ,CAAE,IAAA,CAAK,MAAY,OAAA,CAAA,IAAA,EAAA,IAAA,EAAA,aAAA;AACpC,QAAA,MAAM,WAAW,MAAM,aAAA,CAAc,IAAI,CAAA,CAAE,KAAA,CAAM,MAAM,IAAI,CAAA;AAE3D,QAAA,MAAM,SAAA,GAA0B,cAAA,CAAA,cAAA,CAAA;AAAA,UAC9B,IAAA,EAAM,IAAA,CAAK,MAAA,CAAO,WAAA,EAAa,CAAA;AAAA,UAC/B;AAAA,SAAA,EACI,OAAA,IAAW,EAAE,OAAA,EAAQ,CAAA,EACrB,MAAA,IAAU,EAAE,OAAA,EAAS,IAAA,CAAK,GAAA,EAAI,GAAI,MAAA,EAAO,CAAA;AAE/C,QAAA,EAAA,CAAG,OAAA,CAAQ,KAAK,SAAS,CAAA;AAGzB,QAAA,IACE,mBAAA,IACG,QAAA,IACA,mBAAA,CAAoB,QAAA,EAAU,MAAM,CAAA,EACvC;AACA,UAAA;AAAA,QACF;AACA,QAAA,kBAAA,CAAmB,IAAA,CAAK;AAAA,UACtB,MAAA;AAAA,UACA,SAAA;AAAA,UACA,UAAA,EAAY,CAAC,CAAC;AAAA,SACf,CAAA;AAAA,MACH,CAAA,CAAC,CAAA;AAAA,IACH,SACO,KAAA,EAAO;AACZ,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,EAAA,CAAG,MAAA,GAAU,MAAM,YAAA,CAAa,KAAK,CAAA;AAAA,MACvC,CAAA,MACK;AACH,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF;AAEA,IAAA,OAAO,EAAA,CAAG,MAAA;AAAA,EACZ,IAAG,EAAE,IAAA,EAAM,QAAA,EAAmB,SAAA,EAAW,SAAS,CAAA;AAElD,EAAA,MAAM,GAAA,GAAM,IAAU,IAAA,KAAwB,OAAA,CAAA,IAAA,EAAA,IAAA,EAAA,aAAA;AAC5C,IAAA,MAAM,aAAA,IAAA,IAAA,GAAA,MAAA,GAAA,aAAA,EAAA;AAEN,IAAA,MAAM,CAAC,WAAA,EAAa,YAAY,CAAA,GAAI;AAAA,MAClC,eAAA,CAAgB,eAAe,EAAE,IAAA,EAAM,SAAkB,SAAA,EAAW,EAAE,IAAI,CAAA;AAAA,MAC1E,wBAAwB,IAAI;AAAA,KAC9B;AACA,IAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,IAAA,CAAK;AAAA,MAChC,WAAA,CAAY,KAAA,CAAM,MAAM,YAAY,CAAA;AAAA,MACpC;AAAA,KACD,CAAA;AAED,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,CAAA;AAEA,EAAA,GAAA,CAAI,OAAA,GAAU,CAAA,GAAI,IAAA,KAAwB,uBAAA,CAAwB,IAAI,CAAA;AAEtE,EAAA,GAAA,CAAI,oBAAA,GAAuB,CAACF,GAAAA,KAC1B,kBAAA,CAAmB,UAAUA,GAAE,CAAA;AAEjC,EAAA,GAAA,CAAI,aAAa,MAAY,OAAA,CAAA,IAAA,EAAA,IAAA,EAAA,aAAA;AAC3B,IAAA,gBAAA,EAAiB;AACjB,IAAA,MAAM,EAAA,CAAG,WAAW,GAAG,CAAA;AAAA,EACzB,CAAA,CAAA;AAEA,EAAA,OAAO,GAAA;AACT;AAMA,oBAAA,CAAqB,KAAA,GAAQ,CAAmB,KAAA,KAAyD;AACvG,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,EAAA,GAAK,YAAY,cAAA,CAAe;AAAA,MAC9B,IAAA,EAAM,OAAA;AAAA,MACN,SAAA,EAAW;AAAA,KACZ,CAAA;AAAA,EACH,CAAA,MACK;AACH,IAAA,EAAA,GAAK,KAAA;AAAA,EACP;AACF,CAAA","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"]}
|
|
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
|
@@ -19,15 +19,13 @@ interface CacheItem<T extends (...args: any[]) => Promise<any>> {
|
|
|
19
19
|
/** 缓存结果 */
|
|
20
20
|
result: Awaited<ReturnType<T>>;
|
|
21
21
|
}
|
|
22
|
-
/** 依赖项类型,支持直接值或函数形式(运行时取值) */
|
|
23
|
-
type DepItem = unknown | (() => unknown);
|
|
24
22
|
interface CacheOptions<T extends (...args: any[]) => Promise<any>> {
|
|
25
23
|
/** 缓存过期时间,为空表示永不过期 */
|
|
26
24
|
maxAge?: number;
|
|
27
25
|
/** 缓存版本,用于缓存数据结构变更 */
|
|
28
26
|
version?: string;
|
|
29
27
|
/** 依赖项,用于缓存依赖项变更。支持函数形式,函数会在运行时调用取值 */
|
|
30
|
-
deps?:
|
|
28
|
+
deps?: readonly unknown[] | (() => readonly unknown[]);
|
|
31
29
|
/**
|
|
32
30
|
* 重试次数,为0表示不重试
|
|
33
31
|
* @default 0
|
|
@@ -50,8 +48,8 @@ interface CacheOptions<T extends (...args: any[]) => Promise<any>> {
|
|
|
50
48
|
/**
|
|
51
49
|
* 比较依赖项(函数参数及 deps)是否相等
|
|
52
50
|
* @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index])
|
|
53
|
-
|
|
54
|
-
equals?: (prev:
|
|
51
|
+
*/
|
|
52
|
+
equals?: (prev: readonly unknown[], next: readonly unknown[]) => boolean;
|
|
55
53
|
/**
|
|
56
54
|
* 根据入参决定是否添加缓存
|
|
57
55
|
* 不传的话默认启用缓存
|
|
@@ -77,6 +75,8 @@ interface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {
|
|
|
77
75
|
declare function createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>): {
|
|
78
76
|
(...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
79
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>;
|
|
80
80
|
subscribeCacheUpdate(fn: (data: CacheUpdateEvent<T>) => void): () => void;
|
|
81
81
|
clearCache(): Promise<void>;
|
|
82
82
|
};
|
package/lib/index.js
CHANGED
|
@@ -114,7 +114,7 @@ function createQueryWithCache(key, fn, options) {
|
|
|
114
114
|
remoteMemoryCache: false
|
|
115
115
|
}, options);
|
|
116
116
|
const cacheUpdateSubject = new ReplaySubject(1);
|
|
117
|
-
const resolveDeps = () =>
|
|
117
|
+
const resolveDeps = () => typeof deps === "function" ? deps() : deps;
|
|
118
118
|
const queryFnEnhancer = (fn2, options2) => ((args) => {
|
|
119
119
|
const promiseResult = fn2(args);
|
|
120
120
|
promiseResult.then((result) => {
|
|
@@ -136,30 +136,40 @@ function createQueryWithCache(key, fn, options) {
|
|
|
136
136
|
const remoteResultCache = {
|
|
137
137
|
success: false,
|
|
138
138
|
args: [],
|
|
139
|
+
deps: [],
|
|
139
140
|
result: null
|
|
140
141
|
};
|
|
141
142
|
const clearMemoryCache = () => {
|
|
142
143
|
remoteResultCache.success = false;
|
|
143
144
|
remoteResultCache.result = null;
|
|
144
145
|
remoteResultCache.args = [];
|
|
146
|
+
remoteResultCache.deps = [];
|
|
145
147
|
};
|
|
146
148
|
const queryWithMemoryCache = (args) => {
|
|
147
|
-
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)) {
|
|
148
160
|
return {
|
|
149
161
|
type: "memory",
|
|
150
162
|
promiseResult: remoteResultCache.result
|
|
151
163
|
};
|
|
152
164
|
}
|
|
153
165
|
remoteResultCache.args = args;
|
|
166
|
+
remoteResultCache.deps = currentDeps;
|
|
154
167
|
remoteResultCache.success = true;
|
|
155
168
|
const result = remoteResultCache.result = fnRunner(
|
|
156
169
|
() => fn(...args),
|
|
157
170
|
retry
|
|
158
171
|
);
|
|
159
|
-
Promise.resolve(result).
|
|
160
|
-
!remoteMemoryCache && clearMemoryCache();
|
|
161
|
-
return result2;
|
|
162
|
-
}).catch(clearMemoryCache);
|
|
172
|
+
Promise.resolve(result).catch(clearMemoryCache);
|
|
163
173
|
return { type: "remote", promiseResult: result };
|
|
164
174
|
};
|
|
165
175
|
const queryRemoteWithEnhancer = queryFnEnhancer((args) => __async(null, null, function* () {
|
|
@@ -208,6 +218,16 @@ function createQueryWithCache(key, fn, options) {
|
|
|
208
218
|
return result;
|
|
209
219
|
});
|
|
210
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
|
+
});
|
|
211
231
|
_fn.subscribeCacheUpdate = (fn2) => cacheUpdateSubject.subscribe(fn2);
|
|
212
232
|
_fn.clearCache = () => __async(null, null, function* () {
|
|
213
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\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"]}
|
|
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",
|