@shihengtech/utils 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @shihengtech/utils
2
2
 
3
+ [![pipeline status](https://git.sh-internal.com/Front-End-Projects/Base/sh-tools/badges/main/pipeline.svg)](https://git.sh-internal.com/Front-End-Projects/Base/sh-tools/-/commits/main)
4
+ [![coverage report](https://git.sh-internal.com/Front-End-Projects/Base/sh-tools/badges/main/coverage.svg)](https://git.sh-internal.com/Front-End-Projects/Base/sh-tools/-/commits/main)
5
+
3
6
  一个轻量级的 JavaScript/TypeScript 工具函数库。
4
7
 
5
8
  ## ✨ 特性
@@ -25,17 +28,42 @@ yarn add @shihengtech/utils
25
28
 
26
29
  ## 🔨 使用
27
30
 
31
+ ### fnRunner - 带重试的异步执行器
32
+
33
+ ```typescript
34
+ import { fnRunner } from '@shihengtech/utils'
35
+
36
+ // 执行异步函数,失败后最多重试 3 次
37
+ const result = await fnRunner(
38
+ async () => fetch('/api/data').then(r => r.json()),
39
+ 3
40
+ )
41
+ ```
42
+
43
+ ### createQueryWithCache - 带缓存的查询函数
44
+
45
+ ```typescript
46
+ import { createQueryWithCache } from '@shihengtech/utils'
47
+
48
+ const fetchUser = createQueryWithCache(
49
+ 'user',
50
+ async (id: string) => fetch(`/api/users/${id}`).then(r => r.json()),
51
+ { maxAge: 60000 } // 缓存 1 分钟
52
+ )
53
+
54
+ const user = await fetchUser('123')
55
+ ```
56
+
57
+ ### ReplaySubject - 响应式数据流
58
+
28
59
  ```typescript
29
- import { isEmpty, isString } from '@shihengtech/utils'
60
+ import { ReplaySubject } from '@shihengtech/utils'
30
61
 
31
- // 类型判断
32
- isString('hello') // true
33
- isString(123) // false
62
+ const subject = new ReplaySubject<number>(1)
34
63
 
35
- // 空值检查
36
- isEmpty('') // true
37
- isEmpty([]) // true
38
- isEmpty({}) // true
64
+ subject.subscribe(value => console.log(value))
65
+ subject.next(1) // 输出: 1
66
+ subject.next(2) // 输出: 2
39
67
  ```
40
68
 
41
69
  ## 📖 文档
@@ -70,22 +98,30 @@ pnpm docs:dev
70
98
  pnpm docs:build
71
99
  ```
72
100
 
101
+ ## 📊 测试覆盖率
102
+
103
+ | 模块 | 语句 | 分支 | 函数 | 行 |
104
+ |------|------|------|------|-----|
105
+ | **总计** | 92.83% | 87.09% | 88.23% | 92.83% |
106
+ | ReplaySubject | 100% | 100% | 100% | 100% |
107
+ | createQueryWithCache | 96.38% | 88.88% | 100% | 96.38% |
108
+ | fnRunner | 81.25% | 83.33% | 100% | 81.25% |
109
+
110
+ 运行 `pnpm test:coverage` 查看详细覆盖率报告。
111
+
73
112
  ## 📁 项目结构
74
113
 
75
114
  ```
76
115
  @shihengtech/utils/
77
- ├── src/ # 源代码
78
- │ ├── index.ts # 入口文件
79
- │ ├── is.ts # 类型判断工具
80
- │ └── is.test.ts # 测试文件
81
- ├── docs/ # VitePress 文档
82
- ├── .vitepress/ # VitePress 配置
83
- ├── guide/ # 使用指南
84
- │ └── api/ # API 文档
85
- ├── dist/ # 构建输出(git ignored)
86
- ├── tsconfig.json # TypeScript 配置
87
- ├── tsup.config.ts # 构建配置
88
- ├── vitest.config.ts # 测试配置
116
+ ├── src/
117
+ │ ├── index.ts # 入口文件
118
+ │ ├── class/ #
119
+ └── ReplaySubject/ # 响应式数据流
120
+ │ └── utils/ # 工具函数
121
+ ├── fnRunner/ # 重试执行器
122
+ └── createQueryWithCache/ # 缓存查询
123
+ ├── docs/ # VitePress 文档
124
+ ├── dist/ # 构建输出
89
125
  └── package.json
90
126
  ```
91
127
 
package/dist/index.cjs CHANGED
@@ -177,11 +177,17 @@ function createQueryWithCache(key, fn, options) {
177
177
  return _fn;
178
178
  }
179
179
  createQueryWithCache.useDb = (newDb) => {
180
- db = newDb;
180
+ if (typeof newDb === "string") {
181
+ db = localforage__default.default.createInstance({
182
+ name: newDb
183
+ });
184
+ } else {
185
+ db = newDb;
186
+ }
181
187
  };
182
188
 
183
189
  // src/index.ts
184
- var version = "0.0.1";
190
+ var version = "0.0.2";
185
191
 
186
192
  exports.ReplaySubject = ReplaySubject;
187
193
  exports.createQueryWithCache = createQueryWithCache;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/class/ReplaySubject/index.ts","../src/utils/fnRunner/index.ts","../src/utils/createQueryWithCache/index.ts","../src/index.ts"],"names":["localforage","version","deepEquals","result","fn"],"mappings":"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","file":"index.cjs","sourcesContent":["class ReplaySubject<T> {\n private maxBufferSize: number\n private buffer: T[] = []\n\n private subscriptions: ((data: T) => void)[] = []\n\n constructor(maxBufferSize = 1) {\n this.maxBufferSize = maxBufferSize\n }\n\n subscribe(fn: (data: T) => void) {\n this.subscriptions.push(fn)\n this.buffer.forEach(data => fn(data))\n return () => this.unsubscribe(fn)\n }\n\n unsubscribe(fn: (data: T) => void) {\n this.subscriptions = this.subscriptions.filter(f => f !== fn)\n }\n\n next(data: T) {\n this.subscriptions.forEach(fn => fn(data))\n this.buffer.push(data)\n if (this.buffer.length > this.maxBufferSize) {\n this.buffer = this.buffer.slice(1)\n }\n }\n}\n\nexport { ReplaySubject }\n","export async function fnRunner<T>(fn: () => Promise<T>, retryTimes: number = 0): Promise<T> {\n const times = (retryTimes || 0) + 1\n for (let i = 0; i < times; i++) {\n try {\n return await fn()\n }\n catch (error) {\n // Only throw if this was the last attempt\n if (i === times - 1) {\n throw error\n }\n }\n }\n // This line should never be reached, but it's required for type safety.\n throw new Error('fnRunner: Unexpected error - all retries exhausted.')\n}\n","import localforage from 'localforage'\nimport { equals as deepEquals } from 'ramda'\nimport { ReplaySubject } from '../../class'\nimport { fnRunner } from '../fnRunner'\n\n// TODO:\n// 1. 是否需要增加 reload 方法,只请求远程并刷新缓存\n\ntype DBType = Pick<\n LocalForage,\n 'getItem' | 'setItem' | 'removeItem'\n // 这些可以暂时不考虑\n // | 'clear' | 'keys' | 'length'\n>\n\nlet db: DBType = localforage.createInstance({\n name: 'sh-common-cache',\n // driver: [localforage.LOCALSTORAGE, localforage.INDEXEDDB, localforage.WEBSQL],\n})\n\ninterface CacheItem<T extends (...args: any[]) => Promise<any>> {\n /** 缓存参数 */\n args: Parameters<T>\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 缓存过期时间,为空表示永不过期 */\n expires?: number\n /** 缓存结果 */\n result: Awaited<ReturnType<T>>\n}\n\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /**\n * 重试次数,为0表示不重试\n * @default 0\n */\n retry?: number\n /**\n * 是否比较旧缓存与新缓存后再触发 onCacheUpdate\n * 为 true 时,只有当新旧缓存不一致时才触发 onCacheUpdate\n * 为 false 时,总是触发 onCacheUpdate\n * @default R.equals\n */\n compareBeforeUpdate?:\n | ((prev: Awaited<ReturnType<T>>, next: Awaited<ReturnType<T>>) => boolean)\n | false\n /**\n * 是否启用远程内存缓存\n * 为 true 时,启用远程内存缓存\n * 为 false 时,不启用远程内存缓存\n * @default false\n */\n remoteMemoryCache?: boolean\n /** @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index]) */\n equals?: (prev: Parameters<T>, next: Parameters<T>) => boolean\n /**\n * 根据入参决定是否添加缓存\n * 不传的话默认启用缓存\n */\n cacheEnabled?: (args: Parameters<T>) => Promise<boolean>\n /** 请求前回调 */\n beforeRequest?: () => Promise<void> | void\n /** 请求成功回调 */\n onSuccess?: (result: {\n type: 'local' | 'remote'\n result: Awaited<ReturnType<T>>\n }) => void\n /** 请求错误回调 */\n onError?: (error: any) => void\n /** 远程请求错误处理 */\n errorHandler?: (error: any) => any\n // /** 缓存更新回调 */\n // onCacheUpdate?: (\n // result: Awaited<ReturnType<T>>,\n // cacheData: CacheItem<T>,\n // ) => void;\n}\n\ninterface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {\n result: Awaited<ReturnType<T>>\n cacheData: CacheItem<T>\n isCacheHit: boolean\n}\n\nfunction createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>) {\n const {\n retry,\n maxAge,\n version,\n compareBeforeUpdate,\n remoteMemoryCache,\n equals,\n cacheEnabled,\n beforeRequest,\n onSuccess,\n onError,\n errorHandler,\n } = {\n equals: (a, b) =>\n a.length === b.length && a.every((item, index) => item === b[index]),\n retry: 0,\n compareBeforeUpdate: deepEquals,\n remoteMemoryCache: false,\n ...options,\n } as CacheOptions<T>\n\n const cacheUpdateSubject = new ReplaySubject<CacheUpdateEvent<T>>(1)\n\n const getLocalCache = async (args: Parameters<T>) => {\n const cache = await db.getItem<CacheItem<T>>(key)\n if (\n cache\n && (!version || cache.version === version)\n && (!cache.expires || cache.expires > Date.now())\n && equals!(cache.args, args)\n ) {\n return cache.result\n }\n\n throw new Error('Cache not found')\n }\n\n const remoteResultCache: {\n success: boolean\n args: Parameters<T>\n result: ReturnType<T> | null\n } = {\n success: false,\n args: [] as any,\n result: null,\n }\n\n const clearMemoryCache = () => {\n remoteResultCache.success = false\n remoteResultCache.result = null\n remoteResultCache.args = [] as any\n }\n\n const queryWithMemoryCache = (args: Parameters<T>) => {\n if (remoteResultCache.success && equals!(remoteResultCache.args, args)) {\n return {\n type: 'memory' as const,\n promiseResult: remoteResultCache.result! as ReturnType<T>,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.success = true\n const result: ReturnType<T> = (remoteResultCache.result = fnRunner(\n () => fn(...args),\n retry,\n ) as ReturnType<T>)\n\n Promise.resolve(result)\n .then((result) => {\n !remoteMemoryCache && clearMemoryCache()\n return result\n })\n .catch(clearMemoryCache)\n\n return { type: 'remote' as const, promiseResult: result as ReturnType<T> }\n }\n\n const queryRemote = async (args: Parameters<T>) => {\n const rs = {\n type: 'remote' as const,\n result: null as unknown as Awaited<ReturnType<T>>,\n }\n\n try {\n const { type, promiseResult } = queryWithMemoryCache(args)\n const result = (rs.result = await promiseResult)\n\n // 设置缓存\n type === 'remote'\n && (!cacheEnabled || (await cacheEnabled(args)))\n && Promise.resolve().then(async () => {\n const oldCache = await getLocalCache(args).catch(() => null)\n\n const cacheData = {\n args,\n result,\n ...(version && { version }),\n ...(maxAge && { expires: Date.now() + maxAge }),\n }\n db.setItem(key, cacheData)\n\n // 如果配置了比较,且旧缓存存在且与新结果相等,则不触发 onCacheUpdate\n if (\n compareBeforeUpdate\n && oldCache\n && compareBeforeUpdate(oldCache.result, result)\n ) {\n return\n }\n cacheUpdateSubject.next({\n result,\n cacheData,\n isCacheHit: !!oldCache,\n })\n })\n }\n catch (error) {\n if (errorHandler) {\n rs.result = (await errorHandler(error)) as Awaited<ReturnType<T>>\n }\n else {\n throw error\n }\n }\n\n return rs.result\n }\n\n const _fn = async (...args: Parameters<T>) => {\n await beforeRequest?.()\n\n const [cacheResult, remoteResult] = [\n getLocalCache(args),\n queryRemote(args),\n ]\n cacheResult\n .then(result =>\n onSuccess?.({\n type: 'local' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(() => {})\n remoteResult\n .then(result =>\n onSuccess?.({\n type: 'remote' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(error => onError?.(error))\n .catch(() => {})\n const result = await Promise.race([\n cacheResult.catch(() => remoteResult),\n remoteResult,\n ])\n\n return result\n }\n\n // _fn.getMemoryCache = async () => remoteResultCache;\n\n _fn.subscribeCacheUpdate = (fn: (data: CacheUpdateEvent<T>) => void) =>\n cacheUpdateSubject.subscribe(fn)\n\n _fn.clearCache = async () => {\n clearMemoryCache()\n await db.removeItem(key)\n }\n\n return _fn\n}\n\ncreateQueryWithCache.useDb = (newDb: DBType) => {\n db = newDb\n}\n\nexport { createQueryWithCache }\n","/**\n * @shihengtech/utils - A collection of utility functions\n */\n\n// Type checking utilities\nexport * from './class'\nexport * from './utils'\n// Version\nexport const version = '0.0.1'\n"]}
1
+ {"version":3,"sources":["../src/class/ReplaySubject/index.ts","../src/utils/fnRunner/index.ts","../src/utils/createQueryWithCache/index.ts","../src/index.ts"],"names":["localforage","version","deepEquals","result","fn"],"mappings":"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","file":"index.cjs","sourcesContent":["class ReplaySubject<T> {\n private maxBufferSize: number\n private buffer: T[] = []\n\n private subscriptions: ((data: T) => void)[] = []\n\n constructor(maxBufferSize = 1) {\n this.maxBufferSize = maxBufferSize\n }\n\n subscribe(fn: (data: T) => void) {\n this.subscriptions.push(fn)\n this.buffer.forEach(data => fn(data))\n return () => this.unsubscribe(fn)\n }\n\n unsubscribe(fn: (data: T) => void) {\n this.subscriptions = this.subscriptions.filter(f => f !== fn)\n }\n\n next(data: T) {\n this.subscriptions.forEach(fn => fn(data))\n this.buffer.push(data)\n if (this.buffer.length > this.maxBufferSize) {\n this.buffer = this.buffer.slice(1)\n }\n }\n}\n\nexport { ReplaySubject }\n","export async function fnRunner<T>(fn: () => Promise<T>, retryTimes: number = 0): Promise<T> {\n const times = (retryTimes || 0) + 1\n for (let i = 0; i < times; i++) {\n try {\n return await fn()\n }\n catch (error) {\n // Only throw if this was the last attempt\n if (i === times - 1) {\n throw error\n }\n }\n }\n // This line should never be reached, but it's required for type safety.\n throw new Error('fnRunner: Unexpected error - all retries exhausted.')\n}\n","import localforage from 'localforage'\nimport { equals as deepEquals } from 'ramda'\nimport { ReplaySubject } from '../../class'\nimport { fnRunner } from '../fnRunner'\n\n// TODO:\n// 1. 是否需要增加 reload 方法,只请求远程并刷新缓存\n\ntype DBType = Pick<\n LocalForage,\n 'getItem' | 'setItem' | 'removeItem' | 'clear'\n // 这些可以暂时不考虑\n // | 'clear' | 'keys' | 'length'\n>\n\nlet db: DBType = localforage.createInstance({\n name: 'sh-common-cache',\n // driver: [localforage.LOCALSTORAGE, localforage.INDEXEDDB, localforage.WEBSQL],\n})\n\ninterface CacheItem<T extends (...args: any[]) => Promise<any>> {\n /** 缓存参数 */\n args: Parameters<T>\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 缓存过期时间,为空表示永不过期 */\n expires?: number\n /** 缓存结果 */\n result: Awaited<ReturnType<T>>\n}\n\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /**\n * 重试次数,为0表示不重试\n * @default 0\n */\n retry?: number\n /**\n * 是否比较旧缓存与新缓存后再触发 onCacheUpdate\n * 为 true 时,只有当新旧缓存不一致时才触发 onCacheUpdate\n * 为 false 时,总是触发 onCacheUpdate\n * @default R.equals\n */\n compareBeforeUpdate?:\n | ((prev: Awaited<ReturnType<T>>, next: Awaited<ReturnType<T>>) => boolean)\n | false\n /**\n * 是否启用远程内存缓存\n * 为 true 时,启用远程内存缓存\n * 为 false 时,不启用远程内存缓存\n * @default false\n */\n remoteMemoryCache?: boolean\n /** @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index]) */\n equals?: (prev: Parameters<T>, next: Parameters<T>) => boolean\n /**\n * 根据入参决定是否添加缓存\n * 不传的话默认启用缓存\n */\n cacheEnabled?: (args: Parameters<T>) => Promise<boolean>\n /** 请求前回调 */\n beforeRequest?: () => Promise<void> | void\n /** 请求成功回调 */\n onSuccess?: (result: {\n type: 'local' | 'remote'\n result: Awaited<ReturnType<T>>\n }) => void\n /** 请求错误回调 */\n onError?: (error: any) => void\n /** 远程请求错误处理 */\n errorHandler?: (error: any) => any\n // /** 缓存更新回调 */\n // onCacheUpdate?: (\n // result: Awaited<ReturnType<T>>,\n // cacheData: CacheItem<T>,\n // ) => void;\n}\n\ninterface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {\n result: Awaited<ReturnType<T>>\n cacheData: CacheItem<T>\n isCacheHit: boolean\n}\n\nfunction createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>) {\n const {\n retry,\n maxAge,\n version,\n compareBeforeUpdate,\n remoteMemoryCache,\n equals,\n cacheEnabled,\n beforeRequest,\n onSuccess,\n onError,\n errorHandler,\n } = {\n equals: (a, b) =>\n a.length === b.length && a.every((item, index) => item === b[index]),\n retry: 0,\n compareBeforeUpdate: deepEquals,\n remoteMemoryCache: false,\n ...options,\n } as CacheOptions<T>\n\n const cacheUpdateSubject = new ReplaySubject<CacheUpdateEvent<T>>(1)\n\n const getLocalCache = async (args: Parameters<T>) => {\n const cache = await db.getItem<CacheItem<T>>(key)\n if (\n cache\n && (!version || cache.version === version)\n && (!cache.expires || cache.expires > Date.now())\n && equals!(cache.args, args)\n ) {\n return cache.result\n }\n\n throw new Error('Cache not found')\n }\n\n const remoteResultCache: {\n success: boolean\n args: Parameters<T>\n result: ReturnType<T> | null\n } = {\n success: false,\n args: [] as any,\n result: null,\n }\n\n const clearMemoryCache = () => {\n remoteResultCache.success = false\n remoteResultCache.result = null\n remoteResultCache.args = [] as any\n }\n\n const queryWithMemoryCache = (args: Parameters<T>) => {\n if (remoteResultCache.success && equals!(remoteResultCache.args, args)) {\n return {\n type: 'memory' as const,\n promiseResult: remoteResultCache.result! as ReturnType<T>,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.success = true\n const result: ReturnType<T> = (remoteResultCache.result = fnRunner(\n () => fn(...args),\n retry,\n ) as ReturnType<T>)\n\n Promise.resolve(result)\n .then((result) => {\n !remoteMemoryCache && clearMemoryCache()\n return result\n })\n .catch(clearMemoryCache)\n\n return { type: 'remote' as const, promiseResult: result as ReturnType<T> }\n }\n\n const queryRemote = async (args: Parameters<T>) => {\n const rs = {\n type: 'remote' as const,\n result: null as unknown as Awaited<ReturnType<T>>,\n }\n\n try {\n const { type, promiseResult } = queryWithMemoryCache(args)\n const result = (rs.result = await promiseResult)\n\n // 设置缓存\n type === 'remote'\n && (!cacheEnabled || (await cacheEnabled(args)))\n && Promise.resolve().then(async () => {\n const oldCache = await getLocalCache(args).catch(() => null)\n\n const cacheData = {\n args,\n result,\n ...(version && { version }),\n ...(maxAge && { expires: Date.now() + maxAge }),\n }\n db.setItem(key, cacheData)\n\n // 如果配置了比较,且旧缓存存在且与新结果相等,则不触发 onCacheUpdate\n if (\n compareBeforeUpdate\n && oldCache\n && compareBeforeUpdate(oldCache.result, result)\n ) {\n return\n }\n cacheUpdateSubject.next({\n result,\n cacheData,\n isCacheHit: !!oldCache,\n })\n })\n }\n catch (error) {\n if (errorHandler) {\n rs.result = (await errorHandler(error)) as Awaited<ReturnType<T>>\n }\n else {\n throw error\n }\n }\n\n return rs.result\n }\n\n const _fn = async (...args: Parameters<T>) => {\n await beforeRequest?.()\n\n const [cacheResult, remoteResult] = [\n getLocalCache(args),\n queryRemote(args),\n ]\n cacheResult\n .then(result =>\n onSuccess?.({\n type: 'local' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(() => {})\n remoteResult\n .then(result =>\n onSuccess?.({\n type: 'remote' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(error => onError?.(error))\n .catch(() => {})\n const result = await Promise.race([\n cacheResult.catch(() => remoteResult),\n remoteResult,\n ])\n\n return result\n }\n\n _fn.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 数据库实例或数据库名称,如果为字符串,则使用 localforage 创建一个实例\n */\ncreateQueryWithCache.useDb = (newDb: DBType | string) => {\n if (typeof newDb === 'string') {\n db = localforage.createInstance({\n name: newDb,\n })\n }\n else {\n db = newDb\n }\n}\n\nexport { createQueryWithCache }\n","/**\n * @shihengtech/utils - A collection of utility functions\n */\n\n// Type checking utilities\nexport * from './class'\nexport * from './utils'\n// Version\nexport const version = '0.0.2'\n"]}
package/dist/index.d.cts CHANGED
@@ -8,7 +8,7 @@ declare class ReplaySubject<T> {
8
8
  next(data: T): void;
9
9
  }
10
10
 
11
- type DBType = Pick<LocalForage, 'getItem' | 'setItem' | 'removeItem'>;
11
+ type DBType = Pick<LocalForage, 'getItem' | 'setItem' | 'removeItem' | 'clear'>;
12
12
  interface CacheItem<T extends (...args: any[]) => Promise<any>> {
13
13
  /** 缓存参数 */
14
14
  args: Parameters<T>;
@@ -73,7 +73,7 @@ declare function createQueryWithCache<T extends (...args: any[]) => Promise<any>
73
73
  clearCache(): Promise<void>;
74
74
  };
75
75
  declare namespace createQueryWithCache {
76
- var useDb: (newDb: DBType) => void;
76
+ var useDb: (newDb: DBType | string) => void;
77
77
  }
78
78
 
79
79
  declare function fnRunner<T>(fn: () => Promise<T>, retryTimes?: number): Promise<T>;
@@ -82,6 +82,6 @@ declare function fnRunner<T>(fn: () => Promise<T>, retryTimes?: number): Promise
82
82
  * @shihengtech/utils - A collection of utility functions
83
83
  */
84
84
 
85
- declare const version = "0.0.1";
85
+ declare const version = "0.0.2";
86
86
 
87
87
  export { ReplaySubject, createQueryWithCache, fnRunner, version };
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ declare class ReplaySubject<T> {
8
8
  next(data: T): void;
9
9
  }
10
10
 
11
- type DBType = Pick<LocalForage, 'getItem' | 'setItem' | 'removeItem'>;
11
+ type DBType = Pick<LocalForage, 'getItem' | 'setItem' | 'removeItem' | 'clear'>;
12
12
  interface CacheItem<T extends (...args: any[]) => Promise<any>> {
13
13
  /** 缓存参数 */
14
14
  args: Parameters<T>;
@@ -73,7 +73,7 @@ declare function createQueryWithCache<T extends (...args: any[]) => Promise<any>
73
73
  clearCache(): Promise<void>;
74
74
  };
75
75
  declare namespace createQueryWithCache {
76
- var useDb: (newDb: DBType) => void;
76
+ var useDb: (newDb: DBType | string) => void;
77
77
  }
78
78
 
79
79
  declare function fnRunner<T>(fn: () => Promise<T>, retryTimes?: number): Promise<T>;
@@ -82,6 +82,6 @@ declare function fnRunner<T>(fn: () => Promise<T>, retryTimes?: number): Promise
82
82
  * @shihengtech/utils - A collection of utility functions
83
83
  */
84
84
 
85
- declare const version = "0.0.1";
85
+ declare const version = "0.0.2";
86
86
 
87
87
  export { ReplaySubject, createQueryWithCache, fnRunner, version };
package/dist/index.js CHANGED
@@ -171,11 +171,17 @@ function createQueryWithCache(key, fn, options) {
171
171
  return _fn;
172
172
  }
173
173
  createQueryWithCache.useDb = (newDb) => {
174
- db = newDb;
174
+ if (typeof newDb === "string") {
175
+ db = localforage.createInstance({
176
+ name: newDb
177
+ });
178
+ } else {
179
+ db = newDb;
180
+ }
175
181
  };
176
182
 
177
183
  // src/index.ts
178
- var version = "0.0.1";
184
+ var version = "0.0.2";
179
185
 
180
186
  export { ReplaySubject, createQueryWithCache, fnRunner, version };
181
187
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/class/ReplaySubject/index.ts","../src/utils/fnRunner/index.ts","../src/utils/createQueryWithCache/index.ts","../src/index.ts"],"names":["version","equals","deepEquals","result","fn"],"mappings":"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","file":"index.js","sourcesContent":["class ReplaySubject<T> {\n private maxBufferSize: number\n private buffer: T[] = []\n\n private subscriptions: ((data: T) => void)[] = []\n\n constructor(maxBufferSize = 1) {\n this.maxBufferSize = maxBufferSize\n }\n\n subscribe(fn: (data: T) => void) {\n this.subscriptions.push(fn)\n this.buffer.forEach(data => fn(data))\n return () => this.unsubscribe(fn)\n }\n\n unsubscribe(fn: (data: T) => void) {\n this.subscriptions = this.subscriptions.filter(f => f !== fn)\n }\n\n next(data: T) {\n this.subscriptions.forEach(fn => fn(data))\n this.buffer.push(data)\n if (this.buffer.length > this.maxBufferSize) {\n this.buffer = this.buffer.slice(1)\n }\n }\n}\n\nexport { ReplaySubject }\n","export async function fnRunner<T>(fn: () => Promise<T>, retryTimes: number = 0): Promise<T> {\n const times = (retryTimes || 0) + 1\n for (let i = 0; i < times; i++) {\n try {\n return await fn()\n }\n catch (error) {\n // Only throw if this was the last attempt\n if (i === times - 1) {\n throw error\n }\n }\n }\n // This line should never be reached, but it's required for type safety.\n throw new Error('fnRunner: Unexpected error - all retries exhausted.')\n}\n","import localforage from 'localforage'\nimport { equals as deepEquals } from 'ramda'\nimport { ReplaySubject } from '../../class'\nimport { fnRunner } from '../fnRunner'\n\n// TODO:\n// 1. 是否需要增加 reload 方法,只请求远程并刷新缓存\n\ntype DBType = Pick<\n LocalForage,\n 'getItem' | 'setItem' | 'removeItem'\n // 这些可以暂时不考虑\n // | 'clear' | 'keys' | 'length'\n>\n\nlet db: DBType = localforage.createInstance({\n name: 'sh-common-cache',\n // driver: [localforage.LOCALSTORAGE, localforage.INDEXEDDB, localforage.WEBSQL],\n})\n\ninterface CacheItem<T extends (...args: any[]) => Promise<any>> {\n /** 缓存参数 */\n args: Parameters<T>\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 缓存过期时间,为空表示永不过期 */\n expires?: number\n /** 缓存结果 */\n result: Awaited<ReturnType<T>>\n}\n\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /**\n * 重试次数,为0表示不重试\n * @default 0\n */\n retry?: number\n /**\n * 是否比较旧缓存与新缓存后再触发 onCacheUpdate\n * 为 true 时,只有当新旧缓存不一致时才触发 onCacheUpdate\n * 为 false 时,总是触发 onCacheUpdate\n * @default R.equals\n */\n compareBeforeUpdate?:\n | ((prev: Awaited<ReturnType<T>>, next: Awaited<ReturnType<T>>) => boolean)\n | false\n /**\n * 是否启用远程内存缓存\n * 为 true 时,启用远程内存缓存\n * 为 false 时,不启用远程内存缓存\n * @default false\n */\n remoteMemoryCache?: boolean\n /** @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index]) */\n equals?: (prev: Parameters<T>, next: Parameters<T>) => boolean\n /**\n * 根据入参决定是否添加缓存\n * 不传的话默认启用缓存\n */\n cacheEnabled?: (args: Parameters<T>) => Promise<boolean>\n /** 请求前回调 */\n beforeRequest?: () => Promise<void> | void\n /** 请求成功回调 */\n onSuccess?: (result: {\n type: 'local' | 'remote'\n result: Awaited<ReturnType<T>>\n }) => void\n /** 请求错误回调 */\n onError?: (error: any) => void\n /** 远程请求错误处理 */\n errorHandler?: (error: any) => any\n // /** 缓存更新回调 */\n // onCacheUpdate?: (\n // result: Awaited<ReturnType<T>>,\n // cacheData: CacheItem<T>,\n // ) => void;\n}\n\ninterface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {\n result: Awaited<ReturnType<T>>\n cacheData: CacheItem<T>\n isCacheHit: boolean\n}\n\nfunction createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>) {\n const {\n retry,\n maxAge,\n version,\n compareBeforeUpdate,\n remoteMemoryCache,\n equals,\n cacheEnabled,\n beforeRequest,\n onSuccess,\n onError,\n errorHandler,\n } = {\n equals: (a, b) =>\n a.length === b.length && a.every((item, index) => item === b[index]),\n retry: 0,\n compareBeforeUpdate: deepEquals,\n remoteMemoryCache: false,\n ...options,\n } as CacheOptions<T>\n\n const cacheUpdateSubject = new ReplaySubject<CacheUpdateEvent<T>>(1)\n\n const getLocalCache = async (args: Parameters<T>) => {\n const cache = await db.getItem<CacheItem<T>>(key)\n if (\n cache\n && (!version || cache.version === version)\n && (!cache.expires || cache.expires > Date.now())\n && equals!(cache.args, args)\n ) {\n return cache.result\n }\n\n throw new Error('Cache not found')\n }\n\n const remoteResultCache: {\n success: boolean\n args: Parameters<T>\n result: ReturnType<T> | null\n } = {\n success: false,\n args: [] as any,\n result: null,\n }\n\n const clearMemoryCache = () => {\n remoteResultCache.success = false\n remoteResultCache.result = null\n remoteResultCache.args = [] as any\n }\n\n const queryWithMemoryCache = (args: Parameters<T>) => {\n if (remoteResultCache.success && equals!(remoteResultCache.args, args)) {\n return {\n type: 'memory' as const,\n promiseResult: remoteResultCache.result! as ReturnType<T>,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.success = true\n const result: ReturnType<T> = (remoteResultCache.result = fnRunner(\n () => fn(...args),\n retry,\n ) as ReturnType<T>)\n\n Promise.resolve(result)\n .then((result) => {\n !remoteMemoryCache && clearMemoryCache()\n return result\n })\n .catch(clearMemoryCache)\n\n return { type: 'remote' as const, promiseResult: result as ReturnType<T> }\n }\n\n const queryRemote = async (args: Parameters<T>) => {\n const rs = {\n type: 'remote' as const,\n result: null as unknown as Awaited<ReturnType<T>>,\n }\n\n try {\n const { type, promiseResult } = queryWithMemoryCache(args)\n const result = (rs.result = await promiseResult)\n\n // 设置缓存\n type === 'remote'\n && (!cacheEnabled || (await cacheEnabled(args)))\n && Promise.resolve().then(async () => {\n const oldCache = await getLocalCache(args).catch(() => null)\n\n const cacheData = {\n args,\n result,\n ...(version && { version }),\n ...(maxAge && { expires: Date.now() + maxAge }),\n }\n db.setItem(key, cacheData)\n\n // 如果配置了比较,且旧缓存存在且与新结果相等,则不触发 onCacheUpdate\n if (\n compareBeforeUpdate\n && oldCache\n && compareBeforeUpdate(oldCache.result, result)\n ) {\n return\n }\n cacheUpdateSubject.next({\n result,\n cacheData,\n isCacheHit: !!oldCache,\n })\n })\n }\n catch (error) {\n if (errorHandler) {\n rs.result = (await errorHandler(error)) as Awaited<ReturnType<T>>\n }\n else {\n throw error\n }\n }\n\n return rs.result\n }\n\n const _fn = async (...args: Parameters<T>) => {\n await beforeRequest?.()\n\n const [cacheResult, remoteResult] = [\n getLocalCache(args),\n queryRemote(args),\n ]\n cacheResult\n .then(result =>\n onSuccess?.({\n type: 'local' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(() => {})\n remoteResult\n .then(result =>\n onSuccess?.({\n type: 'remote' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(error => onError?.(error))\n .catch(() => {})\n const result = await Promise.race([\n cacheResult.catch(() => remoteResult),\n remoteResult,\n ])\n\n return result\n }\n\n // _fn.getMemoryCache = async () => remoteResultCache;\n\n _fn.subscribeCacheUpdate = (fn: (data: CacheUpdateEvent<T>) => void) =>\n cacheUpdateSubject.subscribe(fn)\n\n _fn.clearCache = async () => {\n clearMemoryCache()\n await db.removeItem(key)\n }\n\n return _fn\n}\n\ncreateQueryWithCache.useDb = (newDb: DBType) => {\n db = newDb\n}\n\nexport { createQueryWithCache }\n","/**\n * @shihengtech/utils - A collection of utility functions\n */\n\n// Type checking utilities\nexport * from './class'\nexport * from './utils'\n// Version\nexport const version = '0.0.1'\n"]}
1
+ {"version":3,"sources":["../src/class/ReplaySubject/index.ts","../src/utils/fnRunner/index.ts","../src/utils/createQueryWithCache/index.ts","../src/index.ts"],"names":["version","equals","deepEquals","result","fn"],"mappings":"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","file":"index.js","sourcesContent":["class ReplaySubject<T> {\n private maxBufferSize: number\n private buffer: T[] = []\n\n private subscriptions: ((data: T) => void)[] = []\n\n constructor(maxBufferSize = 1) {\n this.maxBufferSize = maxBufferSize\n }\n\n subscribe(fn: (data: T) => void) {\n this.subscriptions.push(fn)\n this.buffer.forEach(data => fn(data))\n return () => this.unsubscribe(fn)\n }\n\n unsubscribe(fn: (data: T) => void) {\n this.subscriptions = this.subscriptions.filter(f => f !== fn)\n }\n\n next(data: T) {\n this.subscriptions.forEach(fn => fn(data))\n this.buffer.push(data)\n if (this.buffer.length > this.maxBufferSize) {\n this.buffer = this.buffer.slice(1)\n }\n }\n}\n\nexport { ReplaySubject }\n","export async function fnRunner<T>(fn: () => Promise<T>, retryTimes: number = 0): Promise<T> {\n const times = (retryTimes || 0) + 1\n for (let i = 0; i < times; i++) {\n try {\n return await fn()\n }\n catch (error) {\n // Only throw if this was the last attempt\n if (i === times - 1) {\n throw error\n }\n }\n }\n // This line should never be reached, but it's required for type safety.\n throw new Error('fnRunner: Unexpected error - all retries exhausted.')\n}\n","import localforage from 'localforage'\nimport { equals as deepEquals } from 'ramda'\nimport { ReplaySubject } from '../../class'\nimport { fnRunner } from '../fnRunner'\n\n// TODO:\n// 1. 是否需要增加 reload 方法,只请求远程并刷新缓存\n\ntype DBType = Pick<\n LocalForage,\n 'getItem' | 'setItem' | 'removeItem' | 'clear'\n // 这些可以暂时不考虑\n // | 'clear' | 'keys' | 'length'\n>\n\nlet db: DBType = localforage.createInstance({\n name: 'sh-common-cache',\n // driver: [localforage.LOCALSTORAGE, localforage.INDEXEDDB, localforage.WEBSQL],\n})\n\ninterface CacheItem<T extends (...args: any[]) => Promise<any>> {\n /** 缓存参数 */\n args: Parameters<T>\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /** 缓存过期时间,为空表示永不过期 */\n expires?: number\n /** 缓存结果 */\n result: Awaited<ReturnType<T>>\n}\n\ninterface CacheOptions<T extends (...args: any[]) => Promise<any>> {\n /** 缓存过期时间,为空表示永不过期 */\n maxAge?: number\n /** 缓存版本,用于缓存数据结构变更 */\n version?: string\n /**\n * 重试次数,为0表示不重试\n * @default 0\n */\n retry?: number\n /**\n * 是否比较旧缓存与新缓存后再触发 onCacheUpdate\n * 为 true 时,只有当新旧缓存不一致时才触发 onCacheUpdate\n * 为 false 时,总是触发 onCacheUpdate\n * @default R.equals\n */\n compareBeforeUpdate?:\n | ((prev: Awaited<ReturnType<T>>, next: Awaited<ReturnType<T>>) => boolean)\n | false\n /**\n * 是否启用远程内存缓存\n * 为 true 时,启用远程内存缓存\n * 为 false 时,不启用远程内存缓存\n * @default false\n */\n remoteMemoryCache?: boolean\n /** @default (a, b) => a.length === b.length && a.every((item, index) => item === b[index]) */\n equals?: (prev: Parameters<T>, next: Parameters<T>) => boolean\n /**\n * 根据入参决定是否添加缓存\n * 不传的话默认启用缓存\n */\n cacheEnabled?: (args: Parameters<T>) => Promise<boolean>\n /** 请求前回调 */\n beforeRequest?: () => Promise<void> | void\n /** 请求成功回调 */\n onSuccess?: (result: {\n type: 'local' | 'remote'\n result: Awaited<ReturnType<T>>\n }) => void\n /** 请求错误回调 */\n onError?: (error: any) => void\n /** 远程请求错误处理 */\n errorHandler?: (error: any) => any\n // /** 缓存更新回调 */\n // onCacheUpdate?: (\n // result: Awaited<ReturnType<T>>,\n // cacheData: CacheItem<T>,\n // ) => void;\n}\n\ninterface CacheUpdateEvent<T extends (...args: any[]) => Promise<any>> {\n result: Awaited<ReturnType<T>>\n cacheData: CacheItem<T>\n isCacheHit: boolean\n}\n\nfunction createQueryWithCache<T extends (...args: any[]) => Promise<any>>(key: string, fn: T, options?: CacheOptions<T>) {\n const {\n retry,\n maxAge,\n version,\n compareBeforeUpdate,\n remoteMemoryCache,\n equals,\n cacheEnabled,\n beforeRequest,\n onSuccess,\n onError,\n errorHandler,\n } = {\n equals: (a, b) =>\n a.length === b.length && a.every((item, index) => item === b[index]),\n retry: 0,\n compareBeforeUpdate: deepEquals,\n remoteMemoryCache: false,\n ...options,\n } as CacheOptions<T>\n\n const cacheUpdateSubject = new ReplaySubject<CacheUpdateEvent<T>>(1)\n\n const getLocalCache = async (args: Parameters<T>) => {\n const cache = await db.getItem<CacheItem<T>>(key)\n if (\n cache\n && (!version || cache.version === version)\n && (!cache.expires || cache.expires > Date.now())\n && equals!(cache.args, args)\n ) {\n return cache.result\n }\n\n throw new Error('Cache not found')\n }\n\n const remoteResultCache: {\n success: boolean\n args: Parameters<T>\n result: ReturnType<T> | null\n } = {\n success: false,\n args: [] as any,\n result: null,\n }\n\n const clearMemoryCache = () => {\n remoteResultCache.success = false\n remoteResultCache.result = null\n remoteResultCache.args = [] as any\n }\n\n const queryWithMemoryCache = (args: Parameters<T>) => {\n if (remoteResultCache.success && equals!(remoteResultCache.args, args)) {\n return {\n type: 'memory' as const,\n promiseResult: remoteResultCache.result! as ReturnType<T>,\n }\n }\n\n remoteResultCache.args = args\n remoteResultCache.success = true\n const result: ReturnType<T> = (remoteResultCache.result = fnRunner(\n () => fn(...args),\n retry,\n ) as ReturnType<T>)\n\n Promise.resolve(result)\n .then((result) => {\n !remoteMemoryCache && clearMemoryCache()\n return result\n })\n .catch(clearMemoryCache)\n\n return { type: 'remote' as const, promiseResult: result as ReturnType<T> }\n }\n\n const queryRemote = async (args: Parameters<T>) => {\n const rs = {\n type: 'remote' as const,\n result: null as unknown as Awaited<ReturnType<T>>,\n }\n\n try {\n const { type, promiseResult } = queryWithMemoryCache(args)\n const result = (rs.result = await promiseResult)\n\n // 设置缓存\n type === 'remote'\n && (!cacheEnabled || (await cacheEnabled(args)))\n && Promise.resolve().then(async () => {\n const oldCache = await getLocalCache(args).catch(() => null)\n\n const cacheData = {\n args,\n result,\n ...(version && { version }),\n ...(maxAge && { expires: Date.now() + maxAge }),\n }\n db.setItem(key, cacheData)\n\n // 如果配置了比较,且旧缓存存在且与新结果相等,则不触发 onCacheUpdate\n if (\n compareBeforeUpdate\n && oldCache\n && compareBeforeUpdate(oldCache.result, result)\n ) {\n return\n }\n cacheUpdateSubject.next({\n result,\n cacheData,\n isCacheHit: !!oldCache,\n })\n })\n }\n catch (error) {\n if (errorHandler) {\n rs.result = (await errorHandler(error)) as Awaited<ReturnType<T>>\n }\n else {\n throw error\n }\n }\n\n return rs.result\n }\n\n const _fn = async (...args: Parameters<T>) => {\n await beforeRequest?.()\n\n const [cacheResult, remoteResult] = [\n getLocalCache(args),\n queryRemote(args),\n ]\n cacheResult\n .then(result =>\n onSuccess?.({\n type: 'local' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(() => {})\n remoteResult\n .then(result =>\n onSuccess?.({\n type: 'remote' as const,\n result: result as Awaited<ReturnType<T>>,\n }),\n )\n .catch(error => onError?.(error))\n .catch(() => {})\n const result = await Promise.race([\n cacheResult.catch(() => remoteResult),\n remoteResult,\n ])\n\n return result\n }\n\n _fn.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 数据库实例或数据库名称,如果为字符串,则使用 localforage 创建一个实例\n */\ncreateQueryWithCache.useDb = (newDb: DBType | string) => {\n if (typeof newDb === 'string') {\n db = localforage.createInstance({\n name: newDb,\n })\n }\n else {\n db = newDb\n }\n}\n\nexport { createQueryWithCache }\n","/**\n * @shihengtech/utils - A collection of utility functions\n */\n\n// Type checking utilities\nexport * from './class'\nexport * from './utils'\n// Version\nexport const version = '0.0.2'\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shihengtech/utils",
3
3
  "type": "module",
4
- "version": "0.0.1",
4
+ "version": "0.0.2",
5
5
  "description": "A collection of utility tools",
6
6
  "author": "",
7
7
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "dist"
34
34
  ],
35
35
  "engines": {
36
- "node": ">=18.0.0"
36
+ "node": ">=16.0.0"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public",