@lark-apaas/cache-service 0.1.1-alpha.0 → 0.1.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import {
3
3
  DEFAULT_SOCKET_TIMEOUT_MS,
4
4
  __name,
5
5
  createCacheStore
6
- } from "./chunk-BSJRPCEN.js";
6
+ } from "./chunk-KVHESGI2.js";
7
7
 
8
8
  // src/nestjs/nestjs-cache.module.ts
9
9
  import { Global, Inject, Module } from "@nestjs/common";
@@ -222,4 +222,4 @@ export {
222
222
  CACHE_LOGGER_ADAPTER,
223
223
  NestjsCacheModule
224
224
  };
225
- //# sourceMappingURL=chunk-SSW7W3K6.js.map
225
+ //# sourceMappingURL=chunk-5GN2M6V6.js.map
@@ -64,10 +64,32 @@ function registerHooks(keyv, observable) {
64
64
  __name(registerHooks, "registerHooks");
65
65
 
66
66
  // src/core/cache-store.ts
67
- import Keyv from "keyv";
68
- import KeyvRedis from "@keyv/redis";
67
+ import KeyvDefault from "keyv";
68
+ import KeyvRedisDefault from "@keyv/redis";
69
+ import * as KeyvModuleNS from "keyv";
70
+ import * as KeyvRedisModuleNS from "@keyv/redis";
69
71
  import { readFile } from "fs/promises";
70
72
  import { watchFile, unwatchFile } from "fs";
73
+ function resolveCtor(primary, ns, moduleName) {
74
+ if (typeof primary === "function") {
75
+ return primary;
76
+ }
77
+ const fromNsDefault = ns?.default;
78
+ if (typeof fromNsDefault === "function") {
79
+ return fromNsDefault;
80
+ }
81
+ const nested = fromNsDefault?.default;
82
+ if (typeof nested === "function") {
83
+ return nested;
84
+ }
85
+ if (typeof ns === "function") {
86
+ return ns;
87
+ }
88
+ throw new Error(`cache-service: unable to resolve default constructor from ${moduleName}`);
89
+ }
90
+ __name(resolveCtor, "resolveCtor");
91
+ var KeyvCtor = resolveCtor(KeyvDefault, KeyvModuleNS, "keyv");
92
+ var KeyvRedisCtor = resolveCtor(KeyvRedisDefault, KeyvRedisModuleNS, "@keyv/redis");
71
93
  function createCacheStore(options) {
72
94
  const { connectionString, connectionTokenFilePath, connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS, socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS, observable, logger } = options;
73
95
  const manager = new StoreManager({
@@ -80,7 +102,7 @@ function createCacheStore(options) {
80
102
  });
81
103
  const initialStore = manager.buildStore(null);
82
104
  manager.setInitialStore(initialStore);
83
- const keyv = new Keyv({
105
+ const keyv = new KeyvCtor({
84
106
  store: initialStore,
85
107
  useKeyPrefix: false,
86
108
  throwOnErrors: false
@@ -125,7 +147,7 @@ var StoreManager = class StoreManager2 {
125
147
  */
126
148
  buildStore(token) {
127
149
  const resolvedConnectionString = this.appendTokenToConnectionString(this.config.connectionString, token);
128
- const store = new KeyvRedis({
150
+ const store = new KeyvRedisCtor({
129
151
  url: resolvedConnectionString,
130
152
  socket: {
131
153
  connectTimeout: this.config.connectTimeoutMs,
@@ -304,4 +326,4 @@ export {
304
326
  registerHooks,
305
327
  createCacheStore
306
328
  };
307
- //# sourceMappingURL=chunk-BSJRPCEN.js.map
329
+ //# sourceMappingURL=chunk-KVHESGI2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/const.ts","../src/core/hooks.ts","../src/core/cache-store.ts"],"sourcesContent":["export const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\nexport const DEFAULT_SOCKET_TIMEOUT_MS = 5_000;\n\nexport const TOKEN_WATCH_INTERVAL_MS = 600_000;\nexport const TOKEN_WATCH_DEBOUNCE_MS = 300;\n\nexport const CACHE_ERROR_CODE_UNAVAILABLE = 'CACHE_SERVICE_UNAVAILABLE';\n","import { KeyvHooks } from 'keyv';\nimport type Keyv from 'keyv';\nimport type { CacheReadResult, ObservableAdapter } from './types';\nimport { CACHE_ERROR_CODE_UNAVAILABLE } from './const';\n\n/**\n * 给 keyv 实例挂 hooks + error 监听,将缓存事件桥接到 Observable\n *\n * 事件契约:\n * - cache.read: cache_key / result(hit|miss) / duration_ms\n * - cache.write: cache_key / duration_ms\n * - cache.delete: cache_key / duration_ms\n * - cache.error: error_code / message\n */\nexport function registerHooks(keyv: Keyv, observable: ObservableAdapter): void {\n const getStart = new WeakMap<object, number>();\n const setStart = new WeakMap<object, number>();\n const delStart = new WeakMap<object, number>();\n\n keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data: { key: string }) => {\n getStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_GET, (data: { key: string; value: unknown }) => {\n const started = getStart.get(data) ?? Date.now();\n const result: CacheReadResult = data.value === undefined ? 'miss' : 'hit';\n observable.log('debug', 'cache.read', {\n event: 'cache.read',\n cache_key: data.key,\n result,\n duration_ms: Date.now() - started,\n });\n getStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n setStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n const started = setStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.write', {\n event: 'cache.write',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n setStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_DELETE, (data: { key: string }) => {\n delStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_DELETE, (data: { key: string; value: unknown }) => {\n const started = delStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.delete', {\n event: 'cache.delete',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n delStart.delete(data);\n });\n\n keyv.on('error', (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message,\n });\n });\n}\n","import KeyvDefault from 'keyv';\nimport KeyvRedisDefault from '@keyv/redis';\nimport * as KeyvModuleNS from 'keyv';\nimport * as KeyvRedisModuleNS from '@keyv/redis';\nimport type Keyv from 'keyv';\nimport type KeyvRedis from '@keyv/redis';\nimport { readFile } from 'node:fs/promises';\nimport { watchFile, unwatchFile } from 'node:fs';\n\nimport type {\n CacheStoreHandle,\n CacheStoreOptions,\n LoggerAdapter,\n ObservableAdapter,\n} from './types';\nimport {\n CACHE_ERROR_CODE_UNAVAILABLE,\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n TOKEN_WATCH_DEBOUNCE_MS,\n TOKEN_WATCH_INTERVAL_MS,\n} from './const';\nimport { registerHooks } from './hooks';\n\n/**\n * `keyv` / `@keyv/redis` 都是 ESM-only 包且只做 default 导出。\n * tsup 生成的 CJS 会用 `__toESM(mod, 1)` 包装:当被包内没有 `__esModule` 标记时,\n * 它会把整个 exports 对象重新塞到 `default` 上,导致 `new mod.default(...)` 报\n * \"not a constructor\"。用一次运行时探测拿到真正的 class 引用(保留类型 default 导入用于 TS 编译)。\n */\nfunction resolveCtor(\n primary: unknown,\n ns: unknown,\n moduleName: string,\n): new (...args: unknown[]) => unknown {\n if (typeof primary === 'function') {\n return primary as new (...args: unknown[]) => unknown;\n }\n const fromNsDefault = (ns as { default?: unknown } | null)?.default;\n if (typeof fromNsDefault === 'function') {\n return fromNsDefault as new (...args: unknown[]) => unknown;\n }\n const nested = (fromNsDefault as { default?: unknown } | null)?.default;\n if (typeof nested === 'function') {\n return nested as new (...args: unknown[]) => unknown;\n }\n if (typeof ns === 'function') {\n return ns as new (...args: unknown[]) => unknown;\n }\n throw new Error(\n `cache-service: unable to resolve default constructor from ${moduleName}`,\n );\n}\n\nconst KeyvCtor = resolveCtor(\n KeyvDefault,\n KeyvModuleNS,\n 'keyv',\n) as unknown as new (...args: ConstructorParameters<typeof Keyv>) => Keyv;\nconst KeyvRedisCtor = resolveCtor(\n KeyvRedisDefault,\n KeyvRedisModuleNS,\n '@keyv/redis',\n) as unknown as new (\n ...args: ConstructorParameters<typeof KeyvRedis>\n) => KeyvRedis<unknown>;\n\ntype AnyKeyvRedis = KeyvRedis<unknown>;\n\n/**\n * 构造一个带 hooks 上报与 token 热刷新能力的 Keyv 实例\n *\n * 关键设计:\n * - `forRoot` 只构造不握手,KeyvRedis 首次读写时才实际连接 Redis\n * - `connectionTokenFilePath` 存在时启动 watchFile 轮询 + 防抖 + 单例锁重建 store\n * - store 替换通过 `keyv.store = newStore` 完成,业务侧 DI 引用保持稳定\n */\nexport function createCacheStore(options: CacheStoreOptions): CacheStoreHandle {\n const {\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS,\n observable,\n logger,\n } = options;\n\n const manager = new StoreManager({\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs,\n socketTimeoutMs,\n observable,\n logger,\n });\n\n const initialStore = manager.buildStore(null);\n manager.setInitialStore(initialStore);\n const keyv = new KeyvCtor({\n store: initialStore,\n useKeyPrefix: false,\n throwOnErrors: false,\n });\n\n registerHooks(keyv, observable);\n manager.attach(keyv);\n manager.startTokenWatcher();\n\n return {\n keyv,\n dispose: async () => {\n manager.stopTokenWatcher();\n await manager.disposeCurrentStore();\n },\n };\n}\n\ninterface StoreManagerConfig {\n connectionString: string;\n connectionTokenFilePath?: string;\n connectTimeoutMs: number;\n socketTimeoutMs: number;\n observable: ObservableAdapter;\n logger?: LoggerAdapter;\n}\n\n/**\n * StoreManager\n *\n * 负责底层 KeyvRedis 的构造、token 热刷新、并发保护。\n * 骨架照搬 @lark-apaas/nestjs-datapaas 的 DrizzleDatabaseManager:\n * - watchFile 轮询 600 s 兜底\n * - 300 ms 防抖\n * - connectionOpPromise 单例锁\n */\nclass StoreManager {\n private keyv: Keyv | null = null;\n private currentStore: AnyKeyvRedis | null = null;\n private currentToken: string | null = null;\n private currentResolvedConnectionString: string | null = null;\n private connectionOpPromise: Promise<void> | null = null;\n private refreshing = false;\n\n private tokenWatchFilePath: string | null = null;\n private tokenWatchDebounceTimer: NodeJS.Timeout | null = null;\n\n constructor(private readonly config: StoreManagerConfig) {}\n\n attach(keyv: Keyv): void {\n this.keyv = keyv;\n }\n\n setInitialStore(store: AnyKeyvRedis): void {\n this.currentStore = store;\n }\n\n /**\n * 基于 connectionString + token 构造新 KeyvRedis\n * 显式列出 socket 超时配置,保持默认值可读\n */\n buildStore(token: string | null): AnyKeyvRedis {\n const resolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n token,\n );\n\n // @keyv/redis v5 直接接 RedisClientOptions;socket 层配置需要下沉到 clientOptions.socket\n const store = new KeyvRedisCtor(\n {\n url: resolvedConnectionString,\n socket: {\n connectTimeout: this.config.connectTimeoutMs,\n // node-redis SocketOptions.socketTimeout:读写空闲超时\n socketTimeout: this.config.socketTimeoutMs,\n },\n },\n {\n // @keyv/redis 层的 connect 超时(覆盖 keyv 侧兜底)\n connectionTimeout: this.config.connectTimeoutMs,\n },\n );\n\n this.currentToken = token;\n this.currentResolvedConnectionString = resolvedConnectionString;\n return store;\n }\n\n startTokenWatcher(): void {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath === tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n\n watchFile(tokenPath, { interval: TOKEN_WATCH_INTERVAL_MS }, () => {\n this.scheduleTokenRefresh();\n });\n this.tokenWatchFilePath = tokenPath;\n }\n\n stopTokenWatcher(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n this.tokenWatchDebounceTimer = null;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n }\n\n async disposeCurrentStore(): Promise<void> {\n await this.shutdownStoreSafely(this.currentStore);\n this.currentStore = null;\n }\n\n private scheduleTokenRefresh(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n }\n this.tokenWatchDebounceTimer = setTimeout(() => {\n void this.refreshIfTokenChanged();\n }, TOKEN_WATCH_DEBOUNCE_MS);\n }\n\n private async refreshIfTokenChanged(): Promise<void> {\n if (this.refreshing) {\n return;\n }\n this.refreshing = true;\n try {\n await this.runWithLock(async () => {\n const nextToken = await this.readTokenFromFile();\n const nextResolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n nextToken,\n );\n\n const sameToken = nextToken === this.currentToken;\n const sameConnStr =\n nextResolvedConnectionString === this.currentResolvedConnectionString;\n if (sameToken && sameConnStr) {\n return;\n }\n\n const previousStore = this.currentStore;\n const nextStore = this.buildStore(nextToken);\n this.currentStore = nextStore;\n\n if (this.keyv) {\n this.keyv.store = nextStore;\n }\n\n await this.shutdownStoreSafely(previousStore);\n });\n } catch (err) {\n this.config.logger?.error(\n 'Failed to refresh cache store from token file',\n err,\n );\n this.config.observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n this.refreshing = false;\n }\n }\n\n /**\n * 单例锁:并发 refresh 只跑一次\n */\n private runWithLock(operation: () => Promise<void>): Promise<void> {\n if (this.connectionOpPromise) {\n return this.connectionOpPromise;\n }\n this.connectionOpPromise = (async () => {\n try {\n await operation();\n } finally {\n this.connectionOpPromise = null;\n }\n })();\n return this.connectionOpPromise;\n }\n\n private async readTokenFromFile(): Promise<string | null> {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return null;\n }\n try {\n const raw = await readFile(tokenPath, 'utf-8');\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to read token file, falling back to previous token',\n { tokenPath, err },\n );\n return null;\n }\n }\n\n /**\n * 追加 -c miaoda.zti_token=<token> 到 URL 的 options 参数\n * 与 nestjs-datapaas 对齐\n */\n private appendTokenToConnectionString(\n connectionString: string,\n token: string | null,\n ): string {\n if (!token) {\n return connectionString;\n }\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to parse connectionString, skipping token append',\n {\n connectionString: redactConnectionString(connectionString),\n err,\n },\n );\n return connectionString;\n }\n\n const currentOptions = url.searchParams.get('options') || '';\n const optionsWithoutToken = currentOptions\n .replace(/(?:^|\\s)-c\\s+miaoda\\.zti_token=[^\\s]*/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n const tokenOption = `-c miaoda.zti_token=${token}`;\n const nextOptions = [optionsWithoutToken, tokenOption]\n .filter(Boolean)\n .join(' ')\n .trim();\n\n url.searchParams.set('options', nextOptions);\n return url.toString();\n }\n\n private async shutdownStoreSafely(store: AnyKeyvRedis | null): Promise<void> {\n if (!store) {\n return;\n }\n try {\n const disconnectable = store as unknown as { disconnect?: () => Promise<void> };\n if (typeof disconnectable.disconnect === 'function') {\n await disconnectable.disconnect();\n }\n } catch (err) {\n this.config.logger?.error(\n 'Error shutting down previous KeyvRedis store',\n err,\n );\n }\n }\n}\n\n/**\n * 屏蔽 URL 中的 password 段,避免打 log 时泄漏凭据\n * redis://user:secret@host:6379 → redis://user:***@host:6379\n * 非法 URL 或无凭据时原样返回\n */\nfunction redactConnectionString(input: string): string {\n return input.replace(/(:\\/\\/[^:@/]*:)[^@/]*(@)/, '$1***$2');\n}\n"],"mappings":"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","names":["DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS","TOKEN_WATCH_INTERVAL_MS","TOKEN_WATCH_DEBOUNCE_MS","CACHE_ERROR_CODE_UNAVAILABLE","KeyvHooks","registerHooks","keyv","observable","getStart","WeakMap","setStart","delStart","hooks","addHandler","KeyvHooks","PRE_GET","data","set","Date","now","POST_GET","started","get","result","value","undefined","log","event","cache_key","key","duration_ms","delete","PRE_SET","POST_SET","PRE_DELETE","POST_DELETE","on","err","message","Error","String","error_code","CACHE_ERROR_CODE_UNAVAILABLE","KeyvDefault","KeyvRedisDefault","KeyvModuleNS","KeyvRedisModuleNS","readFile","watchFile","unwatchFile","resolveCtor","primary","ns","moduleName","fromNsDefault","default","nested","Error","KeyvCtor","KeyvDefault","KeyvModuleNS","KeyvRedisCtor","KeyvRedisDefault","KeyvRedisModuleNS","createCacheStore","options","connectionString","connectionTokenFilePath","connectTimeoutMs","DEFAULT_CONNECT_TIMEOUT_MS","socketTimeoutMs","DEFAULT_SOCKET_TIMEOUT_MS","observable","logger","manager","StoreManager","initialStore","buildStore","setInitialStore","keyv","store","useKeyPrefix","throwOnErrors","registerHooks","attach","startTokenWatcher","dispose","stopTokenWatcher","disposeCurrentStore","currentStore","currentToken","currentResolvedConnectionString","connectionOpPromise","refreshing","tokenWatchFilePath","tokenWatchDebounceTimer","config","token","resolvedConnectionString","appendTokenToConnectionString","url","socket","connectTimeout","socketTimeout","connectionTimeout","tokenPath","unwatchFile","watchFile","interval","TOKEN_WATCH_INTERVAL_MS","scheduleTokenRefresh","clearTimeout","shutdownStoreSafely","setTimeout","refreshIfTokenChanged","TOKEN_WATCH_DEBOUNCE_MS","runWithLock","nextToken","readTokenFromFile","nextResolvedConnectionString","sameToken","sameConnStr","previousStore","nextStore","err","error","log","event","error_code","CACHE_ERROR_CODE_UNAVAILABLE","message","String","operation","raw","readFile","trimmed","trim","length","warn","URL","redactConnectionString","currentOptions","searchParams","get","optionsWithoutToken","replace","tokenOption","nextOptions","filter","Boolean","join","set","toString","disconnectable","disconnect","input"]}
@@ -44,6 +44,8 @@ module.exports = __toCommonJS(core_exports);
44
44
  // src/core/cache-store.ts
45
45
  var import_keyv2 = __toESM(require("keyv"), 1);
46
46
  var import_redis = __toESM(require("@keyv/redis"), 1);
47
+ var KeyvModuleNS = __toESM(require("keyv"), 1);
48
+ var KeyvRedisModuleNS = __toESM(require("@keyv/redis"), 1);
47
49
  var import_promises = require("fs/promises");
48
50
  var import_node_fs = require("fs");
49
51
 
@@ -110,6 +112,26 @@ function registerHooks(keyv, observable) {
110
112
  __name(registerHooks, "registerHooks");
111
113
 
112
114
  // src/core/cache-store.ts
115
+ function resolveCtor(primary, ns, moduleName) {
116
+ if (typeof primary === "function") {
117
+ return primary;
118
+ }
119
+ const fromNsDefault = ns?.default;
120
+ if (typeof fromNsDefault === "function") {
121
+ return fromNsDefault;
122
+ }
123
+ const nested = fromNsDefault?.default;
124
+ if (typeof nested === "function") {
125
+ return nested;
126
+ }
127
+ if (typeof ns === "function") {
128
+ return ns;
129
+ }
130
+ throw new Error(`cache-service: unable to resolve default constructor from ${moduleName}`);
131
+ }
132
+ __name(resolveCtor, "resolveCtor");
133
+ var KeyvCtor = resolveCtor(import_keyv2.default, KeyvModuleNS, "keyv");
134
+ var KeyvRedisCtor = resolveCtor(import_redis.default, KeyvRedisModuleNS, "@keyv/redis");
113
135
  function createCacheStore(options) {
114
136
  const { connectionString, connectionTokenFilePath, connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS, socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS, observable, logger } = options;
115
137
  const manager = new StoreManager({
@@ -122,7 +144,7 @@ function createCacheStore(options) {
122
144
  });
123
145
  const initialStore = manager.buildStore(null);
124
146
  manager.setInitialStore(initialStore);
125
- const keyv = new import_keyv2.default({
147
+ const keyv = new KeyvCtor({
126
148
  store: initialStore,
127
149
  useKeyPrefix: false,
128
150
  throwOnErrors: false
@@ -167,7 +189,7 @@ var StoreManager = class StoreManager2 {
167
189
  */
168
190
  buildStore(token) {
169
191
  const resolvedConnectionString = this.appendTokenToConnectionString(this.config.connectionString, token);
170
- const store = new import_redis.default({
192
+ const store = new KeyvRedisCtor({
171
193
  url: resolvedConnectionString,
172
194
  socket: {
173
195
  connectTimeout: this.config.connectTimeoutMs,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/index.ts","../../src/core/cache-store.ts","../../src/core/const.ts","../../src/core/hooks.ts"],"sourcesContent":["export { createCacheStore } from './cache-store';\nexport { registerHooks } from './hooks';\nexport {\n CACHE_ERROR_CODE_UNAVAILABLE,\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n TOKEN_WATCH_DEBOUNCE_MS,\n TOKEN_WATCH_INTERVAL_MS,\n} from './const';\nexport type {\n CacheReadResult,\n CacheStoreHandle,\n CacheStoreOptions,\n CacheTraceEvent,\n LoggerAdapter,\n ObservableAdapter,\n} from './types';\n","import Keyv from 'keyv';\nimport KeyvRedis from '@keyv/redis';\nimport { readFile } from 'node:fs/promises';\nimport { watchFile, unwatchFile } from 'node:fs';\n\nimport type {\n CacheStoreHandle,\n CacheStoreOptions,\n LoggerAdapter,\n ObservableAdapter,\n} from './types';\nimport {\n CACHE_ERROR_CODE_UNAVAILABLE,\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n TOKEN_WATCH_DEBOUNCE_MS,\n TOKEN_WATCH_INTERVAL_MS,\n} from './const';\nimport { registerHooks } from './hooks';\n\ntype AnyKeyvRedis = KeyvRedis<unknown>;\n\n/**\n * 构造一个带 hooks 上报与 token 热刷新能力的 Keyv 实例\n *\n * 关键设计:\n * - `forRoot` 只构造不握手,KeyvRedis 首次读写时才实际连接 Redis\n * - `connectionTokenFilePath` 存在时启动 watchFile 轮询 + 防抖 + 单例锁重建 store\n * - store 替换通过 `keyv.store = newStore` 完成,业务侧 DI 引用保持稳定\n */\nexport function createCacheStore(options: CacheStoreOptions): CacheStoreHandle {\n const {\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS,\n observable,\n logger,\n } = options;\n\n const manager = new StoreManager({\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs,\n socketTimeoutMs,\n observable,\n logger,\n });\n\n const initialStore = manager.buildStore(null);\n manager.setInitialStore(initialStore);\n const keyv = new Keyv({\n store: initialStore,\n useKeyPrefix: false,\n throwOnErrors: false,\n });\n\n registerHooks(keyv, observable);\n manager.attach(keyv);\n manager.startTokenWatcher();\n\n return {\n keyv,\n dispose: async () => {\n manager.stopTokenWatcher();\n await manager.disposeCurrentStore();\n },\n };\n}\n\ninterface StoreManagerConfig {\n connectionString: string;\n connectionTokenFilePath?: string;\n connectTimeoutMs: number;\n socketTimeoutMs: number;\n observable: ObservableAdapter;\n logger?: LoggerAdapter;\n}\n\n/**\n * StoreManager\n *\n * 负责底层 KeyvRedis 的构造、token 热刷新、并发保护。\n * 骨架照搬 @lark-apaas/nestjs-datapaas 的 DrizzleDatabaseManager:\n * - watchFile 轮询 600 s 兜底\n * - 300 ms 防抖\n * - connectionOpPromise 单例锁\n */\nclass StoreManager {\n private keyv: Keyv | null = null;\n private currentStore: AnyKeyvRedis | null = null;\n private currentToken: string | null = null;\n private currentResolvedConnectionString: string | null = null;\n private connectionOpPromise: Promise<void> | null = null;\n private refreshing = false;\n\n private tokenWatchFilePath: string | null = null;\n private tokenWatchDebounceTimer: NodeJS.Timeout | null = null;\n\n constructor(private readonly config: StoreManagerConfig) {}\n\n attach(keyv: Keyv): void {\n this.keyv = keyv;\n }\n\n setInitialStore(store: AnyKeyvRedis): void {\n this.currentStore = store;\n }\n\n /**\n * 基于 connectionString + token 构造新 KeyvRedis\n * 显式列出 socket 超时配置,保持默认值可读\n */\n buildStore(token: string | null): AnyKeyvRedis {\n const resolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n token,\n );\n\n // @keyv/redis v5 直接接 RedisClientOptions;socket 层配置需要下沉到 clientOptions.socket\n const store = new KeyvRedis(\n {\n url: resolvedConnectionString,\n socket: {\n connectTimeout: this.config.connectTimeoutMs,\n // node-redis SocketOptions.socketTimeout:读写空闲超时\n socketTimeout: this.config.socketTimeoutMs,\n },\n },\n {\n // @keyv/redis 层的 connect 超时(覆盖 keyv 侧兜底)\n connectionTimeout: this.config.connectTimeoutMs,\n },\n );\n\n this.currentToken = token;\n this.currentResolvedConnectionString = resolvedConnectionString;\n return store;\n }\n\n startTokenWatcher(): void {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath === tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n\n watchFile(tokenPath, { interval: TOKEN_WATCH_INTERVAL_MS }, () => {\n this.scheduleTokenRefresh();\n });\n this.tokenWatchFilePath = tokenPath;\n }\n\n stopTokenWatcher(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n this.tokenWatchDebounceTimer = null;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n }\n\n async disposeCurrentStore(): Promise<void> {\n await this.shutdownStoreSafely(this.currentStore);\n this.currentStore = null;\n }\n\n private scheduleTokenRefresh(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n }\n this.tokenWatchDebounceTimer = setTimeout(() => {\n void this.refreshIfTokenChanged();\n }, TOKEN_WATCH_DEBOUNCE_MS);\n }\n\n private async refreshIfTokenChanged(): Promise<void> {\n if (this.refreshing) {\n return;\n }\n this.refreshing = true;\n try {\n await this.runWithLock(async () => {\n const nextToken = await this.readTokenFromFile();\n const nextResolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n nextToken,\n );\n\n const sameToken = nextToken === this.currentToken;\n const sameConnStr =\n nextResolvedConnectionString === this.currentResolvedConnectionString;\n if (sameToken && sameConnStr) {\n return;\n }\n\n const previousStore = this.currentStore;\n const nextStore = this.buildStore(nextToken);\n this.currentStore = nextStore;\n\n if (this.keyv) {\n this.keyv.store = nextStore;\n }\n\n await this.shutdownStoreSafely(previousStore);\n });\n } catch (err) {\n this.config.logger?.error(\n 'Failed to refresh cache store from token file',\n err,\n );\n this.config.observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n this.refreshing = false;\n }\n }\n\n /**\n * 单例锁:并发 refresh 只跑一次\n */\n private runWithLock(operation: () => Promise<void>): Promise<void> {\n if (this.connectionOpPromise) {\n return this.connectionOpPromise;\n }\n this.connectionOpPromise = (async () => {\n try {\n await operation();\n } finally {\n this.connectionOpPromise = null;\n }\n })();\n return this.connectionOpPromise;\n }\n\n private async readTokenFromFile(): Promise<string | null> {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return null;\n }\n try {\n const raw = await readFile(tokenPath, 'utf-8');\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to read token file, falling back to previous token',\n { tokenPath, err },\n );\n return null;\n }\n }\n\n /**\n * 追加 -c miaoda.zti_token=<token> 到 URL 的 options 参数\n * 与 nestjs-datapaas 对齐\n */\n private appendTokenToConnectionString(\n connectionString: string,\n token: string | null,\n ): string {\n if (!token) {\n return connectionString;\n }\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to parse connectionString, skipping token append',\n {\n connectionString: redactConnectionString(connectionString),\n err,\n },\n );\n return connectionString;\n }\n\n const currentOptions = url.searchParams.get('options') || '';\n const optionsWithoutToken = currentOptions\n .replace(/(?:^|\\s)-c\\s+miaoda\\.zti_token=[^\\s]*/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n const tokenOption = `-c miaoda.zti_token=${token}`;\n const nextOptions = [optionsWithoutToken, tokenOption]\n .filter(Boolean)\n .join(' ')\n .trim();\n\n url.searchParams.set('options', nextOptions);\n return url.toString();\n }\n\n private async shutdownStoreSafely(store: AnyKeyvRedis | null): Promise<void> {\n if (!store) {\n return;\n }\n try {\n const disconnectable = store as unknown as { disconnect?: () => Promise<void> };\n if (typeof disconnectable.disconnect === 'function') {\n await disconnectable.disconnect();\n }\n } catch (err) {\n this.config.logger?.error(\n 'Error shutting down previous KeyvRedis store',\n err,\n );\n }\n }\n}\n\n/**\n * 屏蔽 URL 中的 password 段,避免打 log 时泄漏凭据\n * redis://user:secret@host:6379 → redis://user:***@host:6379\n * 非法 URL 或无凭据时原样返回\n */\nfunction redactConnectionString(input: string): string {\n return input.replace(/(:\\/\\/[^:@/]*:)[^@/]*(@)/, '$1***$2');\n}\n","export const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\nexport const DEFAULT_SOCKET_TIMEOUT_MS = 5_000;\n\nexport const TOKEN_WATCH_INTERVAL_MS = 600_000;\nexport const TOKEN_WATCH_DEBOUNCE_MS = 300;\n\nexport const CACHE_ERROR_CODE_UNAVAILABLE = 'CACHE_SERVICE_UNAVAILABLE';\n","import Keyv, { KeyvHooks } from 'keyv';\nimport type { CacheReadResult, ObservableAdapter } from './types';\nimport { CACHE_ERROR_CODE_UNAVAILABLE } from './const';\n\n/**\n * 给 keyv 实例挂 hooks + error 监听,将缓存事件桥接到 Observable\n *\n * 事件契约:\n * - cache.read: cache_key / result(hit|miss) / duration_ms\n * - cache.write: cache_key / duration_ms\n * - cache.delete: cache_key / duration_ms\n * - cache.error: error_code / message\n */\nexport function registerHooks(keyv: Keyv, observable: ObservableAdapter): void {\n const getStart = new WeakMap<object, number>();\n const setStart = new WeakMap<object, number>();\n const delStart = new WeakMap<object, number>();\n\n keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data: { key: string }) => {\n getStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_GET, (data: { key: string; value: unknown }) => {\n const started = getStart.get(data) ?? Date.now();\n const result: CacheReadResult = data.value === undefined ? 'miss' : 'hit';\n observable.log('debug', 'cache.read', {\n event: 'cache.read',\n cache_key: data.key,\n result,\n duration_ms: Date.now() - started,\n });\n getStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n setStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n const started = setStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.write', {\n event: 'cache.write',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n setStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_DELETE, (data: { key: string }) => {\n delStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_DELETE, (data: { key: string; value: unknown }) => {\n const started = delStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.delete', {\n event: 'cache.delete',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n delStart.delete(data);\n });\n\n keyv.on('error', (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message,\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;ACAA,IAAAA,eAAiB;AACjB,mBAAsB;AACtB,sBAAyB;AACzB,qBAAuC;;;ACHhC,IAAMC,6BAA6B;AACnC,IAAMC,4BAA4B;AAElC,IAAMC,0BAA0B;AAChC,IAAMC,0BAA0B;AAEhC,IAAMC,+BAA+B;;;ACN5C,kBAAgC;AAazB,SAASC,cAAcC,MAAYC,YAA6B;AACrE,QAAMC,WAAW,oBAAIC,QAAAA;AACrB,QAAMC,WAAW,oBAAID,QAAAA;AACrB,QAAME,WAAW,oBAAIF,QAAAA;AAErBH,OAAKM,MAAMC,WAAWC,sBAAUC,SAAS,CAACC,SAAAA;AACxCR,aAASS,IAAID,MAAME,KAAKC,IAAG,CAAA;EAC7B,CAAA;AAEAb,OAAKM,MAAMC,WAAWC,sBAAUM,UAAU,CAACJ,SAAAA;AACzC,UAAMK,UAAUb,SAASc,IAAIN,IAAAA,KAASE,KAAKC,IAAG;AAC9C,UAAMI,SAA0BP,KAAKQ,UAAUC,SAAY,SAAS;AACpElB,eAAWmB,IAAI,SAAS,cAAc;MACpCC,OAAO;MACPC,WAAWZ,KAAKa;MAChBN;MACAO,aAAaZ,KAAKC,IAAG,IAAKE;IAC5B,CAAA;AACAb,aAASuB,OAAOf,IAAAA;EAClB,CAAA;AAEAV,OAAKM,MAAMC,WAAWC,sBAAUkB,SAAS,CAAChB,SAAAA;AACxCN,aAASO,IAAID,MAAME,KAAKC,IAAG,CAAA;EAC7B,CAAA;AAEAb,OAAKM,MAAMC,WAAWC,sBAAUmB,UAAU,CAACjB,SAAAA;AACzC,UAAMK,UAAUX,SAASY,IAAIN,IAAAA,KAASE,KAAKC,IAAG;AAC9CZ,eAAWmB,IAAI,SAAS,eAAe;MACrCC,OAAO;MACPC,WAAWZ,KAAKa;MAChBC,aAAaZ,KAAKC,IAAG,IAAKE;IAC5B,CAAA;AACAX,aAASqB,OAAOf,IAAAA;EAClB,CAAA;AAEAV,OAAKM,MAAMC,WAAWC,sBAAUoB,YAAY,CAAClB,SAAAA;AAC3CL,aAASM,IAAID,MAAME,KAAKC,IAAG,CAAA;EAC7B,CAAA;AAEAb,OAAKM,MAAMC,WAAWC,sBAAUqB,aAAa,CAACnB,SAAAA;AAC5C,UAAMK,UAAUV,SAASW,IAAIN,IAAAA,KAASE,KAAKC,IAAG;AAC9CZ,eAAWmB,IAAI,SAAS,gBAAgB;MACtCC,OAAO;MACPC,WAAWZ,KAAKa;MAChBC,aAAaZ,KAAKC,IAAG,IAAKE;IAC5B,CAAA;AACAV,aAASoB,OAAOf,IAAAA;EAClB,CAAA;AAEAV,OAAK8B,GAAG,SAAS,CAACC,QAAAA;AAChB,UAAMC,UAAUD,eAAeE,QAAQF,IAAIC,UAAUE,OAAOH,GAAAA;AAC5D9B,eAAWmB,IAAI,SAAS,eAAe;MACrCC,OAAO;MACPc,YAAYC;MACZJ;IACF,CAAA;EACF,CAAA;AACF;AAzDgBjC;;;AFiBT,SAASsC,iBAAiBC,SAA0B;AACzD,QAAM,EACJC,kBACAC,yBACAC,mBAAmBC,4BACnBC,kBAAkBC,2BAClBC,YACAC,OAAM,IACJR;AAEJ,QAAMS,UAAU,IAAIC,aAAa;IAC/BT;IACAC;IACAC;IACAE;IACAE;IACAC;EACF,CAAA;AAEA,QAAMG,eAAeF,QAAQG,WAAW,IAAA;AACxCH,UAAQI,gBAAgBF,YAAAA;AACxB,QAAMG,OAAO,IAAIC,aAAAA,QAAK;IACpBC,OAAOL;IACPM,cAAc;IACdC,eAAe;EACjB,CAAA;AAEAC,gBAAcL,MAAMP,UAAAA;AACpBE,UAAQW,OAAON,IAAAA;AACfL,UAAQY,kBAAiB;AAEzB,SAAO;IACLP;IACAQ,SAAS,mCAAA;AACPb,cAAQc,iBAAgB;AACxB,YAAMd,QAAQe,oBAAmB;IACnC,GAHS;EAIX;AACF;AAtCgBzB;AA0DhB,IAAMW,eAAN,MAAMA,cAAAA;EAxFN,OAwFMA;;;;EACII,OAAoB;EACpBW,eAAoC;EACpCC,eAA8B;EAC9BC,kCAAiD;EACjDC,sBAA4C;EAC5CC,aAAa;EAEbC,qBAAoC;EACpCC,0BAAiD;EAEzD,YAA6BC,QAA4B;SAA5BA,SAAAA;EAA6B;EAE1DZ,OAAON,MAAkB;AACvB,SAAKA,OAAOA;EACd;EAEAD,gBAAgBG,OAA2B;AACzC,SAAKS,eAAeT;EACtB;;;;;EAMAJ,WAAWqB,OAAoC;AAC7C,UAAMC,2BAA2B,KAAKC,8BACpC,KAAKH,OAAO/B,kBACZgC,KAAAA;AAIF,UAAMjB,QAAQ,IAAIoB,aAAAA,QAChB;MACEC,KAAKH;MACLI,QAAQ;QACNC,gBAAgB,KAAKP,OAAO7B;;QAE5BqC,eAAe,KAAKR,OAAO3B;MAC7B;IACF,GACA;;MAEEoC,mBAAmB,KAAKT,OAAO7B;IACjC,CAAA;AAGF,SAAKuB,eAAeO;AACpB,SAAKN,kCAAkCO;AACvC,WAAOlB;EACT;EAEAK,oBAA0B;AACxB,UAAMqB,YAAY,KAAKV,OAAO9B;AAC9B,QAAI,CAACwC,WAAW;AACd;IACF;AACA,QAAI,KAAKZ,uBAAuBY,WAAW;AACzC;IACF;AACA,QAAI,KAAKZ,oBAAoB;AAC3Ba,sCAAY,KAAKb,kBAAkB;AACnC,WAAKA,qBAAqB;IAC5B;AAEAc,kCAAUF,WAAW;MAAEG,UAAUC;IAAwB,GAAG,MAAA;AAC1D,WAAKC,qBAAoB;IAC3B,CAAA;AACA,SAAKjB,qBAAqBY;EAC5B;EAEAnB,mBAAyB;AACvB,QAAI,KAAKQ,yBAAyB;AAChCiB,mBAAa,KAAKjB,uBAAuB;AACzC,WAAKA,0BAA0B;IACjC;AACA,QAAI,KAAKD,oBAAoB;AAC3Ba,sCAAY,KAAKb,kBAAkB;AACnC,WAAKA,qBAAqB;IAC5B;EACF;EAEA,MAAMN,sBAAqC;AACzC,UAAM,KAAKyB,oBAAoB,KAAKxB,YAAY;AAChD,SAAKA,eAAe;EACtB;EAEQsB,uBAA6B;AACnC,QAAI,KAAKhB,yBAAyB;AAChCiB,mBAAa,KAAKjB,uBAAuB;IAC3C;AACA,SAAKA,0BAA0BmB,WAAW,MAAA;AACxC,WAAK,KAAKC,sBAAqB;IACjC,GAAGC,uBAAAA;EACL;EAEA,MAAcD,wBAAuC;AACnD,QAAI,KAAKtB,YAAY;AACnB;IACF;AACA,SAAKA,aAAa;AAClB,QAAI;AACF,YAAM,KAAKwB,YAAY,YAAA;AACrB,cAAMC,YAAY,MAAM,KAAKC,kBAAiB;AAC9C,cAAMC,+BAA+B,KAAKrB,8BACxC,KAAKH,OAAO/B,kBACZqD,SAAAA;AAGF,cAAMG,YAAYH,cAAc,KAAK5B;AACrC,cAAMgC,cACJF,iCAAiC,KAAK7B;AACxC,YAAI8B,aAAaC,aAAa;AAC5B;QACF;AAEA,cAAMC,gBAAgB,KAAKlC;AAC3B,cAAMmC,YAAY,KAAKhD,WAAW0C,SAAAA;AAClC,aAAK7B,eAAemC;AAEpB,YAAI,KAAK9C,MAAM;AACb,eAAKA,KAAKE,QAAQ4C;QACpB;AAEA,cAAM,KAAKX,oBAAoBU,aAAAA;MACjC,CAAA;IACF,SAASE,KAAK;AACZ,WAAK7B,OAAOxB,QAAQsD,MAClB,iDACAD,GAAAA;AAEF,WAAK7B,OAAOzB,WAAWwD,IAAI,SAAS,eAAe;QACjDC,OAAO;QACPC,YAAYC;QACZC,SAASN,eAAeO,QAAQP,IAAIM,UAAUE,OAAOR,GAAAA;MACvD,CAAA;IACF,UAAA;AACE,WAAKhC,aAAa;IACpB;EACF;;;;EAKQwB,YAAYiB,WAA+C;AACjE,QAAI,KAAK1C,qBAAqB;AAC5B,aAAO,KAAKA;IACd;AACA,SAAKA,uBAAuB,YAAA;AAC1B,UAAI;AACF,cAAM0C,UAAAA;MACR,UAAA;AACE,aAAK1C,sBAAsB;MAC7B;IACF,GAAA;AACA,WAAO,KAAKA;EACd;EAEA,MAAc2B,oBAA4C;AACxD,UAAMb,YAAY,KAAKV,OAAO9B;AAC9B,QAAI,CAACwC,WAAW;AACd,aAAO;IACT;AACA,QAAI;AACF,YAAM6B,MAAM,UAAMC,0BAAS9B,WAAW,OAAA;AACtC,YAAM+B,UAAUF,IAAIG,KAAI;AACxB,aAAOD,QAAQE,SAAS,IAAIF,UAAU;IACxC,SAASZ,KAAK;AACZ,WAAK7B,OAAOxB,QAAQoE,KAClB,4EACA;QAAElC;QAAWmB;MAAI,CAAA;AAEnB,aAAO;IACT;EACF;;;;;EAMQ1B,8BACNlC,kBACAgC,OACQ;AACR,QAAI,CAACA,OAAO;AACV,aAAOhC;IACT;AACA,QAAIoC;AACJ,QAAI;AACFA,YAAM,IAAIwC,IAAI5E,gBAAAA;IAChB,SAAS4D,KAAK;AACZ,WAAK7B,OAAOxB,QAAQoE,KAClB,0EACA;QACE3E,kBAAkB6E,uBAAuB7E,gBAAAA;QACzC4D;MACF,CAAA;AAEF,aAAO5D;IACT;AAEA,UAAM8E,iBAAiB1C,IAAI2C,aAAaC,IAAI,SAAA,KAAc;AAC1D,UAAMC,sBAAsBH,eACzBI,QAAQ,0CAA0C,GAAA,EAClDA,QAAQ,QAAQ,GAAA,EAChBT,KAAI;AACP,UAAMU,cAAc,uBAAuBnD,KAAAA;AAC3C,UAAMoD,cAAc;MAACH;MAAqBE;MACvCE,OAAOC,OAAAA,EACPC,KAAK,GAAA,EACLd,KAAI;AAEPrC,QAAI2C,aAAaS,IAAI,WAAWJ,WAAAA;AAChC,WAAOhD,IAAIqD,SAAQ;EACrB;EAEA,MAAczC,oBAAoBjC,OAA2C;AAC3E,QAAI,CAACA,OAAO;AACV;IACF;AACA,QAAI;AACF,YAAM2E,iBAAiB3E;AACvB,UAAI,OAAO2E,eAAeC,eAAe,YAAY;AACnD,cAAMD,eAAeC,WAAU;MACjC;IACF,SAAS/B,KAAK;AACZ,WAAK7B,OAAOxB,QAAQsD,MAClB,gDACAD,GAAAA;IAEJ;EACF;AACF;AAOA,SAASiB,uBAAuBe,OAAa;AAC3C,SAAOA,MAAMV,QAAQ,4BAA4B,SAAA;AACnD;AAFSL;","names":["import_keyv","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS","TOKEN_WATCH_INTERVAL_MS","TOKEN_WATCH_DEBOUNCE_MS","CACHE_ERROR_CODE_UNAVAILABLE","registerHooks","keyv","observable","getStart","WeakMap","setStart","delStart","hooks","addHandler","KeyvHooks","PRE_GET","data","set","Date","now","POST_GET","started","get","result","value","undefined","log","event","cache_key","key","duration_ms","delete","PRE_SET","POST_SET","PRE_DELETE","POST_DELETE","on","err","message","Error","String","error_code","CACHE_ERROR_CODE_UNAVAILABLE","createCacheStore","options","connectionString","connectionTokenFilePath","connectTimeoutMs","DEFAULT_CONNECT_TIMEOUT_MS","socketTimeoutMs","DEFAULT_SOCKET_TIMEOUT_MS","observable","logger","manager","StoreManager","initialStore","buildStore","setInitialStore","keyv","Keyv","store","useKeyPrefix","throwOnErrors","registerHooks","attach","startTokenWatcher","dispose","stopTokenWatcher","disposeCurrentStore","currentStore","currentToken","currentResolvedConnectionString","connectionOpPromise","refreshing","tokenWatchFilePath","tokenWatchDebounceTimer","config","token","resolvedConnectionString","appendTokenToConnectionString","KeyvRedis","url","socket","connectTimeout","socketTimeout","connectionTimeout","tokenPath","unwatchFile","watchFile","interval","TOKEN_WATCH_INTERVAL_MS","scheduleTokenRefresh","clearTimeout","shutdownStoreSafely","setTimeout","refreshIfTokenChanged","TOKEN_WATCH_DEBOUNCE_MS","runWithLock","nextToken","readTokenFromFile","nextResolvedConnectionString","sameToken","sameConnStr","previousStore","nextStore","err","error","log","event","error_code","CACHE_ERROR_CODE_UNAVAILABLE","message","Error","String","operation","raw","readFile","trimmed","trim","length","warn","URL","redactConnectionString","currentOptions","searchParams","get","optionsWithoutToken","replace","tokenOption","nextOptions","filter","Boolean","join","set","toString","disconnectable","disconnect","input"]}
1
+ {"version":3,"sources":["../../src/core/index.ts","../../src/core/cache-store.ts","../../src/core/const.ts","../../src/core/hooks.ts"],"sourcesContent":["export { createCacheStore } from './cache-store';\nexport { registerHooks } from './hooks';\nexport {\n CACHE_ERROR_CODE_UNAVAILABLE,\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n TOKEN_WATCH_DEBOUNCE_MS,\n TOKEN_WATCH_INTERVAL_MS,\n} from './const';\nexport type {\n CacheReadResult,\n CacheStoreHandle,\n CacheStoreOptions,\n CacheTraceEvent,\n LoggerAdapter,\n ObservableAdapter,\n} from './types';\n","import KeyvDefault from 'keyv';\nimport KeyvRedisDefault from '@keyv/redis';\nimport * as KeyvModuleNS from 'keyv';\nimport * as KeyvRedisModuleNS from '@keyv/redis';\nimport type Keyv from 'keyv';\nimport type KeyvRedis from '@keyv/redis';\nimport { readFile } from 'node:fs/promises';\nimport { watchFile, unwatchFile } from 'node:fs';\n\nimport type {\n CacheStoreHandle,\n CacheStoreOptions,\n LoggerAdapter,\n ObservableAdapter,\n} from './types';\nimport {\n CACHE_ERROR_CODE_UNAVAILABLE,\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n TOKEN_WATCH_DEBOUNCE_MS,\n TOKEN_WATCH_INTERVAL_MS,\n} from './const';\nimport { registerHooks } from './hooks';\n\n/**\n * `keyv` / `@keyv/redis` 都是 ESM-only 包且只做 default 导出。\n * tsup 生成的 CJS 会用 `__toESM(mod, 1)` 包装:当被包内没有 `__esModule` 标记时,\n * 它会把整个 exports 对象重新塞到 `default` 上,导致 `new mod.default(...)` 报\n * \"not a constructor\"。用一次运行时探测拿到真正的 class 引用(保留类型 default 导入用于 TS 编译)。\n */\nfunction resolveCtor(\n primary: unknown,\n ns: unknown,\n moduleName: string,\n): new (...args: unknown[]) => unknown {\n if (typeof primary === 'function') {\n return primary as new (...args: unknown[]) => unknown;\n }\n const fromNsDefault = (ns as { default?: unknown } | null)?.default;\n if (typeof fromNsDefault === 'function') {\n return fromNsDefault as new (...args: unknown[]) => unknown;\n }\n const nested = (fromNsDefault as { default?: unknown } | null)?.default;\n if (typeof nested === 'function') {\n return nested as new (...args: unknown[]) => unknown;\n }\n if (typeof ns === 'function') {\n return ns as new (...args: unknown[]) => unknown;\n }\n throw new Error(\n `cache-service: unable to resolve default constructor from ${moduleName}`,\n );\n}\n\nconst KeyvCtor = resolveCtor(\n KeyvDefault,\n KeyvModuleNS,\n 'keyv',\n) as unknown as new (...args: ConstructorParameters<typeof Keyv>) => Keyv;\nconst KeyvRedisCtor = resolveCtor(\n KeyvRedisDefault,\n KeyvRedisModuleNS,\n '@keyv/redis',\n) as unknown as new (\n ...args: ConstructorParameters<typeof KeyvRedis>\n) => KeyvRedis<unknown>;\n\ntype AnyKeyvRedis = KeyvRedis<unknown>;\n\n/**\n * 构造一个带 hooks 上报与 token 热刷新能力的 Keyv 实例\n *\n * 关键设计:\n * - `forRoot` 只构造不握手,KeyvRedis 首次读写时才实际连接 Redis\n * - `connectionTokenFilePath` 存在时启动 watchFile 轮询 + 防抖 + 单例锁重建 store\n * - store 替换通过 `keyv.store = newStore` 完成,业务侧 DI 引用保持稳定\n */\nexport function createCacheStore(options: CacheStoreOptions): CacheStoreHandle {\n const {\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS,\n observable,\n logger,\n } = options;\n\n const manager = new StoreManager({\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs,\n socketTimeoutMs,\n observable,\n logger,\n });\n\n const initialStore = manager.buildStore(null);\n manager.setInitialStore(initialStore);\n const keyv = new KeyvCtor({\n store: initialStore,\n useKeyPrefix: false,\n throwOnErrors: false,\n });\n\n registerHooks(keyv, observable);\n manager.attach(keyv);\n manager.startTokenWatcher();\n\n return {\n keyv,\n dispose: async () => {\n manager.stopTokenWatcher();\n await manager.disposeCurrentStore();\n },\n };\n}\n\ninterface StoreManagerConfig {\n connectionString: string;\n connectionTokenFilePath?: string;\n connectTimeoutMs: number;\n socketTimeoutMs: number;\n observable: ObservableAdapter;\n logger?: LoggerAdapter;\n}\n\n/**\n * StoreManager\n *\n * 负责底层 KeyvRedis 的构造、token 热刷新、并发保护。\n * 骨架照搬 @lark-apaas/nestjs-datapaas 的 DrizzleDatabaseManager:\n * - watchFile 轮询 600 s 兜底\n * - 300 ms 防抖\n * - connectionOpPromise 单例锁\n */\nclass StoreManager {\n private keyv: Keyv | null = null;\n private currentStore: AnyKeyvRedis | null = null;\n private currentToken: string | null = null;\n private currentResolvedConnectionString: string | null = null;\n private connectionOpPromise: Promise<void> | null = null;\n private refreshing = false;\n\n private tokenWatchFilePath: string | null = null;\n private tokenWatchDebounceTimer: NodeJS.Timeout | null = null;\n\n constructor(private readonly config: StoreManagerConfig) {}\n\n attach(keyv: Keyv): void {\n this.keyv = keyv;\n }\n\n setInitialStore(store: AnyKeyvRedis): void {\n this.currentStore = store;\n }\n\n /**\n * 基于 connectionString + token 构造新 KeyvRedis\n * 显式列出 socket 超时配置,保持默认值可读\n */\n buildStore(token: string | null): AnyKeyvRedis {\n const resolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n token,\n );\n\n // @keyv/redis v5 直接接 RedisClientOptions;socket 层配置需要下沉到 clientOptions.socket\n const store = new KeyvRedisCtor(\n {\n url: resolvedConnectionString,\n socket: {\n connectTimeout: this.config.connectTimeoutMs,\n // node-redis SocketOptions.socketTimeout:读写空闲超时\n socketTimeout: this.config.socketTimeoutMs,\n },\n },\n {\n // @keyv/redis 层的 connect 超时(覆盖 keyv 侧兜底)\n connectionTimeout: this.config.connectTimeoutMs,\n },\n );\n\n this.currentToken = token;\n this.currentResolvedConnectionString = resolvedConnectionString;\n return store;\n }\n\n startTokenWatcher(): void {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath === tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n\n watchFile(tokenPath, { interval: TOKEN_WATCH_INTERVAL_MS }, () => {\n this.scheduleTokenRefresh();\n });\n this.tokenWatchFilePath = tokenPath;\n }\n\n stopTokenWatcher(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n this.tokenWatchDebounceTimer = null;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n }\n\n async disposeCurrentStore(): Promise<void> {\n await this.shutdownStoreSafely(this.currentStore);\n this.currentStore = null;\n }\n\n private scheduleTokenRefresh(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n }\n this.tokenWatchDebounceTimer = setTimeout(() => {\n void this.refreshIfTokenChanged();\n }, TOKEN_WATCH_DEBOUNCE_MS);\n }\n\n private async refreshIfTokenChanged(): Promise<void> {\n if (this.refreshing) {\n return;\n }\n this.refreshing = true;\n try {\n await this.runWithLock(async () => {\n const nextToken = await this.readTokenFromFile();\n const nextResolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n nextToken,\n );\n\n const sameToken = nextToken === this.currentToken;\n const sameConnStr =\n nextResolvedConnectionString === this.currentResolvedConnectionString;\n if (sameToken && sameConnStr) {\n return;\n }\n\n const previousStore = this.currentStore;\n const nextStore = this.buildStore(nextToken);\n this.currentStore = nextStore;\n\n if (this.keyv) {\n this.keyv.store = nextStore;\n }\n\n await this.shutdownStoreSafely(previousStore);\n });\n } catch (err) {\n this.config.logger?.error(\n 'Failed to refresh cache store from token file',\n err,\n );\n this.config.observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n this.refreshing = false;\n }\n }\n\n /**\n * 单例锁:并发 refresh 只跑一次\n */\n private runWithLock(operation: () => Promise<void>): Promise<void> {\n if (this.connectionOpPromise) {\n return this.connectionOpPromise;\n }\n this.connectionOpPromise = (async () => {\n try {\n await operation();\n } finally {\n this.connectionOpPromise = null;\n }\n })();\n return this.connectionOpPromise;\n }\n\n private async readTokenFromFile(): Promise<string | null> {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return null;\n }\n try {\n const raw = await readFile(tokenPath, 'utf-8');\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to read token file, falling back to previous token',\n { tokenPath, err },\n );\n return null;\n }\n }\n\n /**\n * 追加 -c miaoda.zti_token=<token> 到 URL 的 options 参数\n * 与 nestjs-datapaas 对齐\n */\n private appendTokenToConnectionString(\n connectionString: string,\n token: string | null,\n ): string {\n if (!token) {\n return connectionString;\n }\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to parse connectionString, skipping token append',\n {\n connectionString: redactConnectionString(connectionString),\n err,\n },\n );\n return connectionString;\n }\n\n const currentOptions = url.searchParams.get('options') || '';\n const optionsWithoutToken = currentOptions\n .replace(/(?:^|\\s)-c\\s+miaoda\\.zti_token=[^\\s]*/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n const tokenOption = `-c miaoda.zti_token=${token}`;\n const nextOptions = [optionsWithoutToken, tokenOption]\n .filter(Boolean)\n .join(' ')\n .trim();\n\n url.searchParams.set('options', nextOptions);\n return url.toString();\n }\n\n private async shutdownStoreSafely(store: AnyKeyvRedis | null): Promise<void> {\n if (!store) {\n return;\n }\n try {\n const disconnectable = store as unknown as { disconnect?: () => Promise<void> };\n if (typeof disconnectable.disconnect === 'function') {\n await disconnectable.disconnect();\n }\n } catch (err) {\n this.config.logger?.error(\n 'Error shutting down previous KeyvRedis store',\n err,\n );\n }\n }\n}\n\n/**\n * 屏蔽 URL 中的 password 段,避免打 log 时泄漏凭据\n * redis://user:secret@host:6379 → redis://user:***@host:6379\n * 非法 URL 或无凭据时原样返回\n */\nfunction redactConnectionString(input: string): string {\n return input.replace(/(:\\/\\/[^:@/]*:)[^@/]*(@)/, '$1***$2');\n}\n","export const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\nexport const DEFAULT_SOCKET_TIMEOUT_MS = 5_000;\n\nexport const TOKEN_WATCH_INTERVAL_MS = 600_000;\nexport const TOKEN_WATCH_DEBOUNCE_MS = 300;\n\nexport const CACHE_ERROR_CODE_UNAVAILABLE = 'CACHE_SERVICE_UNAVAILABLE';\n","import { KeyvHooks } from 'keyv';\nimport type Keyv from 'keyv';\nimport type { CacheReadResult, ObservableAdapter } from './types';\nimport { CACHE_ERROR_CODE_UNAVAILABLE } from './const';\n\n/**\n * 给 keyv 实例挂 hooks + error 监听,将缓存事件桥接到 Observable\n *\n * 事件契约:\n * - cache.read: cache_key / result(hit|miss) / duration_ms\n * - cache.write: cache_key / duration_ms\n * - cache.delete: cache_key / duration_ms\n * - cache.error: error_code / message\n */\nexport function registerHooks(keyv: Keyv, observable: ObservableAdapter): void {\n const getStart = new WeakMap<object, number>();\n const setStart = new WeakMap<object, number>();\n const delStart = new WeakMap<object, number>();\n\n keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data: { key: string }) => {\n getStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_GET, (data: { key: string; value: unknown }) => {\n const started = getStart.get(data) ?? Date.now();\n const result: CacheReadResult = data.value === undefined ? 'miss' : 'hit';\n observable.log('debug', 'cache.read', {\n event: 'cache.read',\n cache_key: data.key,\n result,\n duration_ms: Date.now() - started,\n });\n getStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n setStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n const started = setStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.write', {\n event: 'cache.write',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n setStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_DELETE, (data: { key: string }) => {\n delStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_DELETE, (data: { key: string; value: unknown }) => {\n const started = delStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.delete', {\n event: 'cache.delete',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n delStart.delete(data);\n });\n\n keyv.on('error', (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message,\n });\n });\n}\n"],"mappings":"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","names":["import_keyv","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS","TOKEN_WATCH_INTERVAL_MS","TOKEN_WATCH_DEBOUNCE_MS","CACHE_ERROR_CODE_UNAVAILABLE","registerHooks","keyv","observable","getStart","WeakMap","setStart","delStart","hooks","addHandler","KeyvHooks","PRE_GET","data","set","Date","now","POST_GET","started","get","result","value","undefined","log","event","cache_key","key","duration_ms","delete","PRE_SET","POST_SET","PRE_DELETE","POST_DELETE","on","err","message","Error","String","error_code","CACHE_ERROR_CODE_UNAVAILABLE","resolveCtor","primary","ns","moduleName","fromNsDefault","default","nested","Error","KeyvCtor","KeyvDefault","KeyvModuleNS","KeyvRedisCtor","KeyvRedisDefault","KeyvRedisModuleNS","createCacheStore","options","connectionString","connectionTokenFilePath","connectTimeoutMs","DEFAULT_CONNECT_TIMEOUT_MS","socketTimeoutMs","DEFAULT_SOCKET_TIMEOUT_MS","observable","logger","manager","StoreManager","initialStore","buildStore","setInitialStore","keyv","store","useKeyPrefix","throwOnErrors","registerHooks","attach","startTokenWatcher","dispose","stopTokenWatcher","disposeCurrentStore","currentStore","currentToken","currentResolvedConnectionString","connectionOpPromise","refreshing","tokenWatchFilePath","tokenWatchDebounceTimer","config","token","resolvedConnectionString","appendTokenToConnectionString","url","socket","connectTimeout","socketTimeout","connectionTimeout","tokenPath","unwatchFile","watchFile","interval","TOKEN_WATCH_INTERVAL_MS","scheduleTokenRefresh","clearTimeout","shutdownStoreSafely","setTimeout","refreshIfTokenChanged","TOKEN_WATCH_DEBOUNCE_MS","runWithLock","nextToken","readTokenFromFile","nextResolvedConnectionString","sameToken","sameConnStr","previousStore","nextStore","err","error","log","event","error_code","CACHE_ERROR_CODE_UNAVAILABLE","message","String","operation","raw","readFile","trimmed","trim","length","warn","URL","redactConnectionString","currentOptions","searchParams","get","optionsWithoutToken","replace","tokenOption","nextOptions","filter","Boolean","join","set","toString","disconnectable","disconnect","input"]}
@@ -6,7 +6,7 @@ import {
6
6
  TOKEN_WATCH_INTERVAL_MS,
7
7
  createCacheStore,
8
8
  registerHooks
9
- } from "../chunk-BSJRPCEN.js";
9
+ } from "../chunk-KVHESGI2.js";
10
10
  export {
11
11
  CACHE_ERROR_CODE_UNAVAILABLE,
12
12
  DEFAULT_CONNECT_TIMEOUT_MS,
package/dist/index.cjs CHANGED
@@ -49,6 +49,8 @@ var import_nestjs_observable = require("@lark-apaas/nestjs-observable");
49
49
  // src/core/cache-store.ts
50
50
  var import_keyv2 = __toESM(require("keyv"), 1);
51
51
  var import_redis = __toESM(require("@keyv/redis"), 1);
52
+ var KeyvModuleNS = __toESM(require("keyv"), 1);
53
+ var KeyvRedisModuleNS = __toESM(require("@keyv/redis"), 1);
52
54
  var import_promises = require("fs/promises");
53
55
  var import_node_fs = require("fs");
54
56
 
@@ -115,6 +117,26 @@ function registerHooks(keyv, observable) {
115
117
  __name(registerHooks, "registerHooks");
116
118
 
117
119
  // src/core/cache-store.ts
120
+ function resolveCtor(primary, ns, moduleName) {
121
+ if (typeof primary === "function") {
122
+ return primary;
123
+ }
124
+ const fromNsDefault = ns?.default;
125
+ if (typeof fromNsDefault === "function") {
126
+ return fromNsDefault;
127
+ }
128
+ const nested = fromNsDefault?.default;
129
+ if (typeof nested === "function") {
130
+ return nested;
131
+ }
132
+ if (typeof ns === "function") {
133
+ return ns;
134
+ }
135
+ throw new Error(`cache-service: unable to resolve default constructor from ${moduleName}`);
136
+ }
137
+ __name(resolveCtor, "resolveCtor");
138
+ var KeyvCtor = resolveCtor(import_keyv2.default, KeyvModuleNS, "keyv");
139
+ var KeyvRedisCtor = resolveCtor(import_redis.default, KeyvRedisModuleNS, "@keyv/redis");
118
140
  function createCacheStore(options) {
119
141
  const { connectionString, connectionTokenFilePath, connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS, socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS, observable, logger } = options;
120
142
  const manager = new StoreManager({
@@ -127,7 +149,7 @@ function createCacheStore(options) {
127
149
  });
128
150
  const initialStore = manager.buildStore(null);
129
151
  manager.setInitialStore(initialStore);
130
- const keyv = new import_keyv2.default({
152
+ const keyv = new KeyvCtor({
131
153
  store: initialStore,
132
154
  useKeyPrefix: false,
133
155
  throwOnErrors: false
@@ -172,7 +194,7 @@ var StoreManager = class StoreManager2 {
172
194
  */
173
195
  buildStore(token) {
174
196
  const resolvedConnectionString = this.appendTokenToConnectionString(this.config.connectionString, token);
175
- const store = new import_redis.default({
197
+ const store = new KeyvRedisCtor({
176
198
  url: resolvedConnectionString,
177
199
  socket: {
178
200
  connectTimeout: this.config.connectTimeoutMs,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/nestjs/nestjs-cache.module.ts","../src/core/cache-store.ts","../src/core/const.ts","../src/core/hooks.ts","../src/nestjs/const.ts"],"sourcesContent":["/**\n * @lark-apaas/cache-service\n *\n * NestJS Cache SDK,基于 cache-manager v6 + keyv v5 + @keyv/redis。\n *\n * 主入口默认导出 nestjs 装配层:\n * ```ts\n * import { NestjsCacheModule } from '@lark-apaas/cache-service';\n *\n * NestjsCacheModule.forRoot({ connectionString: 'redis://...' });\n * ```\n *\n * 若不使用 NestJS(例如 Express、Koa),可直接引入 core 层:\n * ```ts\n * import { createCacheStore } from '@lark-apaas/cache-service/core';\n * ```\n */\n\nexport * from './nestjs/index';\n\n// 便于业务从主入口直接消费 core 类型,避免二次 import 深路径\nexport type {\n CacheStoreOptions,\n CacheStoreHandle,\n ObservableAdapter,\n LoggerAdapter,\n CacheReadResult,\n CacheTraceEvent,\n} from './core/types';\n","import {\n DynamicModule,\n Global,\n Inject,\n InjectionToken,\n Module,\n ModuleMetadata,\n OnApplicationShutdown,\n Provider,\n} from '@nestjs/common';\nimport { CacheModule } from '@nestjs/cache-manager';\nimport {\n OBSERVABLE_SERVICE,\n RequestContextService,\n type ObservableService,\n} from '@lark-apaas/nestjs-common';\nimport { AppLogger } from '@lark-apaas/nestjs-logger';\nimport { Observable } from '@lark-apaas/nestjs-observable';\n\nimport { createCacheStore } from '../core/cache-store';\nimport {\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n} from '../core/const';\nimport type {\n CacheStoreHandle,\n LoggerAdapter,\n ObservableAdapter,\n} from '../core/types';\nimport {\n CACHE_LOGGER_ADAPTER,\n CACHE_OBSERVABLE_ADAPTER,\n CACHE_STORE_HANDLE,\n NESTJS_CACHE_OPTIONS,\n} from './const';\n\n/**\n * 用户传入的运行时配置\n */\nexport interface NestjsCacheModuleOptions {\n /**\n * Redis 连接串(redis:// 或 rediss://)\n */\n connectionString: string;\n /**\n * ZTI Token 文件路径。存在则启动 watchFile 热刷新。\n */\n connectionTokenFilePath?: string;\n /**\n * Socket connect timeout(毫秒)。默认 10_000。\n */\n connectTimeoutMs?: number;\n /**\n * Socket idle timeout(毫秒)。默认 5_000。\n */\n socketTimeoutMs?: number;\n}\n\n/**\n * 异步配置选项\n */\nexport interface NestjsCacheModuleAsyncOptions {\n imports?: ModuleMetadata['imports'];\n useFactory: (\n ...args: unknown[]\n ) => Promise<NestjsCacheModuleOptions> | NestjsCacheModuleOptions;\n inject?: InjectionToken[];\n}\n\n/**\n * 内部核心模块:只负责组装 options + adapters + CACHE_STORE_HANDLE 三层 provider,\n * 并 export 给 CacheModule 通过 imports 消费。\n *\n * 拆出独立 module 的原因:NestJS DynamicModule 里放在 imports 中的 CacheModule\n * 无法看到同级 providers 定义的 token,必须走 module-to-module 的 exports 链路。\n */\n@Global()\n@Module({})\nclass NestjsCacheCoreModule {\n static forRoot(\n optionsProvider: Provider,\n imports: ModuleMetadata['imports'],\n ): DynamicModule {\n return {\n module: NestjsCacheCoreModule,\n imports,\n providers: [\n optionsProvider,\n {\n provide: CACHE_OBSERVABLE_ADAPTER,\n useFactory: (\n observable: ObservableService | null,\n rtx: RequestContextService,\n ): ObservableAdapter => {\n const impl = observable ?? new Observable(rtx);\n return {\n log(level, event, attrs) {\n impl.log(level, event, attrs);\n },\n };\n },\n inject: [\n { token: OBSERVABLE_SERVICE, optional: true },\n RequestContextService,\n ],\n },\n {\n provide: CACHE_LOGGER_ADAPTER,\n useFactory: (appLogger: AppLogger | null): LoggerAdapter => {\n if (!appLogger) {\n return {\n warn: (msg) => console.warn(msg),\n error: (msg, err) => console.error(msg, err),\n };\n }\n return {\n warn: (msg) => appLogger.warn(msg),\n error: (msg, err) => appLogger.error(msg, err),\n };\n },\n inject: [{ token: AppLogger, optional: true }],\n },\n {\n provide: CACHE_STORE_HANDLE,\n useFactory: (\n opts: NestjsCacheModuleOptions,\n observable: ObservableAdapter,\n logger: LoggerAdapter,\n ): CacheStoreHandle =>\n createCacheStore({\n connectionString: opts.connectionString,\n connectionTokenFilePath: opts.connectionTokenFilePath,\n connectTimeoutMs: opts.connectTimeoutMs,\n socketTimeoutMs: opts.socketTimeoutMs,\n observable,\n logger,\n }),\n inject: [\n NESTJS_CACHE_OPTIONS,\n CACHE_OBSERVABLE_ADAPTER,\n CACHE_LOGGER_ADAPTER,\n ],\n },\n ],\n exports: [CACHE_STORE_HANDLE, NESTJS_CACHE_OPTIONS],\n };\n }\n}\n\n@Global()\n@Module({})\nexport class NestjsCacheModule implements OnApplicationShutdown {\n constructor(\n @Inject(CACHE_STORE_HANDLE)\n private readonly storeHandle: CacheStoreHandle,\n ) {}\n\n async onApplicationShutdown(): Promise<void> {\n await this.storeHandle.dispose();\n }\n\n /**\n * 同步注册\n */\n static forRoot(options: NestjsCacheModuleOptions): DynamicModule {\n const coreModule = NestjsCacheCoreModule.forRoot(\n {\n provide: NESTJS_CACHE_OPTIONS,\n useValue: normalizeOptions(options),\n },\n [],\n );\n\n return {\n module: NestjsCacheModule,\n imports: [\n coreModule,\n CacheModule.registerAsync({\n isGlobal: true,\n imports: [coreModule],\n inject: [CACHE_STORE_HANDLE],\n useFactory: (handle: CacheStoreHandle) => ({\n stores: [handle.keyv],\n }),\n }),\n ],\n exports: [CacheModule, coreModule],\n };\n }\n\n /**\n * 异步注册(从 ConfigService 等异步源读配置)\n */\n static forRootAsync(options: NestjsCacheModuleAsyncOptions): DynamicModule {\n const optionsProvider: Provider = {\n provide: NESTJS_CACHE_OPTIONS,\n useFactory: async (...args: unknown[]) => {\n const raw = await options.useFactory(...args);\n return normalizeOptions(raw);\n },\n inject: options.inject ?? [],\n };\n\n const coreModule = NestjsCacheCoreModule.forRoot(\n optionsProvider,\n options.imports ?? [],\n );\n\n return {\n module: NestjsCacheModule,\n imports: [\n coreModule,\n CacheModule.registerAsync({\n isGlobal: true,\n imports: [coreModule],\n inject: [CACHE_STORE_HANDLE],\n useFactory: (handle: CacheStoreHandle) => ({\n stores: [handle.keyv],\n }),\n }),\n ],\n exports: [CacheModule, coreModule],\n };\n }\n}\n\n/**\n * 应用默认值\n */\nfunction normalizeOptions(\n options: NestjsCacheModuleOptions,\n): NestjsCacheModuleOptions {\n return {\n connectionString: options.connectionString,\n connectionTokenFilePath: options.connectionTokenFilePath,\n connectTimeoutMs: options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs: options.socketTimeoutMs ?? DEFAULT_SOCKET_TIMEOUT_MS,\n };\n}\n","import Keyv from 'keyv';\nimport KeyvRedis from '@keyv/redis';\nimport { readFile } from 'node:fs/promises';\nimport { watchFile, unwatchFile } from 'node:fs';\n\nimport type {\n CacheStoreHandle,\n CacheStoreOptions,\n LoggerAdapter,\n ObservableAdapter,\n} from './types';\nimport {\n CACHE_ERROR_CODE_UNAVAILABLE,\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n TOKEN_WATCH_DEBOUNCE_MS,\n TOKEN_WATCH_INTERVAL_MS,\n} from './const';\nimport { registerHooks } from './hooks';\n\ntype AnyKeyvRedis = KeyvRedis<unknown>;\n\n/**\n * 构造一个带 hooks 上报与 token 热刷新能力的 Keyv 实例\n *\n * 关键设计:\n * - `forRoot` 只构造不握手,KeyvRedis 首次读写时才实际连接 Redis\n * - `connectionTokenFilePath` 存在时启动 watchFile 轮询 + 防抖 + 单例锁重建 store\n * - store 替换通过 `keyv.store = newStore` 完成,业务侧 DI 引用保持稳定\n */\nexport function createCacheStore(options: CacheStoreOptions): CacheStoreHandle {\n const {\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS,\n observable,\n logger,\n } = options;\n\n const manager = new StoreManager({\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs,\n socketTimeoutMs,\n observable,\n logger,\n });\n\n const initialStore = manager.buildStore(null);\n manager.setInitialStore(initialStore);\n const keyv = new Keyv({\n store: initialStore,\n useKeyPrefix: false,\n throwOnErrors: false,\n });\n\n registerHooks(keyv, observable);\n manager.attach(keyv);\n manager.startTokenWatcher();\n\n return {\n keyv,\n dispose: async () => {\n manager.stopTokenWatcher();\n await manager.disposeCurrentStore();\n },\n };\n}\n\ninterface StoreManagerConfig {\n connectionString: string;\n connectionTokenFilePath?: string;\n connectTimeoutMs: number;\n socketTimeoutMs: number;\n observable: ObservableAdapter;\n logger?: LoggerAdapter;\n}\n\n/**\n * StoreManager\n *\n * 负责底层 KeyvRedis 的构造、token 热刷新、并发保护。\n * 骨架照搬 @lark-apaas/nestjs-datapaas 的 DrizzleDatabaseManager:\n * - watchFile 轮询 600 s 兜底\n * - 300 ms 防抖\n * - connectionOpPromise 单例锁\n */\nclass StoreManager {\n private keyv: Keyv | null = null;\n private currentStore: AnyKeyvRedis | null = null;\n private currentToken: string | null = null;\n private currentResolvedConnectionString: string | null = null;\n private connectionOpPromise: Promise<void> | null = null;\n private refreshing = false;\n\n private tokenWatchFilePath: string | null = null;\n private tokenWatchDebounceTimer: NodeJS.Timeout | null = null;\n\n constructor(private readonly config: StoreManagerConfig) {}\n\n attach(keyv: Keyv): void {\n this.keyv = keyv;\n }\n\n setInitialStore(store: AnyKeyvRedis): void {\n this.currentStore = store;\n }\n\n /**\n * 基于 connectionString + token 构造新 KeyvRedis\n * 显式列出 socket 超时配置,保持默认值可读\n */\n buildStore(token: string | null): AnyKeyvRedis {\n const resolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n token,\n );\n\n // @keyv/redis v5 直接接 RedisClientOptions;socket 层配置需要下沉到 clientOptions.socket\n const store = new KeyvRedis(\n {\n url: resolvedConnectionString,\n socket: {\n connectTimeout: this.config.connectTimeoutMs,\n // node-redis SocketOptions.socketTimeout:读写空闲超时\n socketTimeout: this.config.socketTimeoutMs,\n },\n },\n {\n // @keyv/redis 层的 connect 超时(覆盖 keyv 侧兜底)\n connectionTimeout: this.config.connectTimeoutMs,\n },\n );\n\n this.currentToken = token;\n this.currentResolvedConnectionString = resolvedConnectionString;\n return store;\n }\n\n startTokenWatcher(): void {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath === tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n\n watchFile(tokenPath, { interval: TOKEN_WATCH_INTERVAL_MS }, () => {\n this.scheduleTokenRefresh();\n });\n this.tokenWatchFilePath = tokenPath;\n }\n\n stopTokenWatcher(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n this.tokenWatchDebounceTimer = null;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n }\n\n async disposeCurrentStore(): Promise<void> {\n await this.shutdownStoreSafely(this.currentStore);\n this.currentStore = null;\n }\n\n private scheduleTokenRefresh(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n }\n this.tokenWatchDebounceTimer = setTimeout(() => {\n void this.refreshIfTokenChanged();\n }, TOKEN_WATCH_DEBOUNCE_MS);\n }\n\n private async refreshIfTokenChanged(): Promise<void> {\n if (this.refreshing) {\n return;\n }\n this.refreshing = true;\n try {\n await this.runWithLock(async () => {\n const nextToken = await this.readTokenFromFile();\n const nextResolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n nextToken,\n );\n\n const sameToken = nextToken === this.currentToken;\n const sameConnStr =\n nextResolvedConnectionString === this.currentResolvedConnectionString;\n if (sameToken && sameConnStr) {\n return;\n }\n\n const previousStore = this.currentStore;\n const nextStore = this.buildStore(nextToken);\n this.currentStore = nextStore;\n\n if (this.keyv) {\n this.keyv.store = nextStore;\n }\n\n await this.shutdownStoreSafely(previousStore);\n });\n } catch (err) {\n this.config.logger?.error(\n 'Failed to refresh cache store from token file',\n err,\n );\n this.config.observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n this.refreshing = false;\n }\n }\n\n /**\n * 单例锁:并发 refresh 只跑一次\n */\n private runWithLock(operation: () => Promise<void>): Promise<void> {\n if (this.connectionOpPromise) {\n return this.connectionOpPromise;\n }\n this.connectionOpPromise = (async () => {\n try {\n await operation();\n } finally {\n this.connectionOpPromise = null;\n }\n })();\n return this.connectionOpPromise;\n }\n\n private async readTokenFromFile(): Promise<string | null> {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return null;\n }\n try {\n const raw = await readFile(tokenPath, 'utf-8');\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to read token file, falling back to previous token',\n { tokenPath, err },\n );\n return null;\n }\n }\n\n /**\n * 追加 -c miaoda.zti_token=<token> 到 URL 的 options 参数\n * 与 nestjs-datapaas 对齐\n */\n private appendTokenToConnectionString(\n connectionString: string,\n token: string | null,\n ): string {\n if (!token) {\n return connectionString;\n }\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to parse connectionString, skipping token append',\n {\n connectionString: redactConnectionString(connectionString),\n err,\n },\n );\n return connectionString;\n }\n\n const currentOptions = url.searchParams.get('options') || '';\n const optionsWithoutToken = currentOptions\n .replace(/(?:^|\\s)-c\\s+miaoda\\.zti_token=[^\\s]*/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n const tokenOption = `-c miaoda.zti_token=${token}`;\n const nextOptions = [optionsWithoutToken, tokenOption]\n .filter(Boolean)\n .join(' ')\n .trim();\n\n url.searchParams.set('options', nextOptions);\n return url.toString();\n }\n\n private async shutdownStoreSafely(store: AnyKeyvRedis | null): Promise<void> {\n if (!store) {\n return;\n }\n try {\n const disconnectable = store as unknown as { disconnect?: () => Promise<void> };\n if (typeof disconnectable.disconnect === 'function') {\n await disconnectable.disconnect();\n }\n } catch (err) {\n this.config.logger?.error(\n 'Error shutting down previous KeyvRedis store',\n err,\n );\n }\n }\n}\n\n/**\n * 屏蔽 URL 中的 password 段,避免打 log 时泄漏凭据\n * redis://user:secret@host:6379 → redis://user:***@host:6379\n * 非法 URL 或无凭据时原样返回\n */\nfunction redactConnectionString(input: string): string {\n return input.replace(/(:\\/\\/[^:@/]*:)[^@/]*(@)/, '$1***$2');\n}\n","export const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\nexport const DEFAULT_SOCKET_TIMEOUT_MS = 5_000;\n\nexport const TOKEN_WATCH_INTERVAL_MS = 600_000;\nexport const TOKEN_WATCH_DEBOUNCE_MS = 300;\n\nexport const CACHE_ERROR_CODE_UNAVAILABLE = 'CACHE_SERVICE_UNAVAILABLE';\n","import Keyv, { KeyvHooks } from 'keyv';\nimport type { CacheReadResult, ObservableAdapter } from './types';\nimport { CACHE_ERROR_CODE_UNAVAILABLE } from './const';\n\n/**\n * 给 keyv 实例挂 hooks + error 监听,将缓存事件桥接到 Observable\n *\n * 事件契约:\n * - cache.read: cache_key / result(hit|miss) / duration_ms\n * - cache.write: cache_key / duration_ms\n * - cache.delete: cache_key / duration_ms\n * - cache.error: error_code / message\n */\nexport function registerHooks(keyv: Keyv, observable: ObservableAdapter): void {\n const getStart = new WeakMap<object, number>();\n const setStart = new WeakMap<object, number>();\n const delStart = new WeakMap<object, number>();\n\n keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data: { key: string }) => {\n getStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_GET, (data: { key: string; value: unknown }) => {\n const started = getStart.get(data) ?? Date.now();\n const result: CacheReadResult = data.value === undefined ? 'miss' : 'hit';\n observable.log('debug', 'cache.read', {\n event: 'cache.read',\n cache_key: data.key,\n result,\n duration_ms: Date.now() - started,\n });\n getStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n setStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n const started = setStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.write', {\n event: 'cache.write',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n setStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_DELETE, (data: { key: string }) => {\n delStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_DELETE, (data: { key: string; value: unknown }) => {\n const started = delStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.delete', {\n event: 'cache.delete',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n delStart.delete(data);\n });\n\n keyv.on('error', (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message,\n });\n });\n}\n","/**\n * @lark-apaas/cache-service - NestJS 层 DI tokens\n */\n\nexport const NESTJS_CACHE_OPTIONS = Symbol('NESTJS_CACHE_OPTIONS');\n\nexport const CACHE_STORE_HANDLE = Symbol('CACHE_STORE_HANDLE');\n\nexport const CACHE_OBSERVABLE_ADAPTER = Symbol('CACHE_OBSERVABLE_ADAPTER');\n\nexport const CACHE_LOGGER_ADAPTER = Symbol('CACHE_LOGGER_ADAPTER');\n"],"mappings":"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","names":["import_keyv","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS","TOKEN_WATCH_INTERVAL_MS","TOKEN_WATCH_DEBOUNCE_MS","CACHE_ERROR_CODE_UNAVAILABLE","registerHooks","keyv","observable","getStart","WeakMap","setStart","delStart","hooks","addHandler","KeyvHooks","PRE_GET","data","set","Date","now","POST_GET","started","get","result","value","undefined","log","event","cache_key","key","duration_ms","delete","PRE_SET","POST_SET","PRE_DELETE","POST_DELETE","on","err","message","Error","String","error_code","CACHE_ERROR_CODE_UNAVAILABLE","createCacheStore","options","connectionString","connectionTokenFilePath","connectTimeoutMs","DEFAULT_CONNECT_TIMEOUT_MS","socketTimeoutMs","DEFAULT_SOCKET_TIMEOUT_MS","observable","logger","manager","StoreManager","initialStore","buildStore","setInitialStore","keyv","Keyv","store","useKeyPrefix","throwOnErrors","registerHooks","attach","startTokenWatcher","dispose","stopTokenWatcher","disposeCurrentStore","currentStore","currentToken","currentResolvedConnectionString","connectionOpPromise","refreshing","tokenWatchFilePath","tokenWatchDebounceTimer","config","token","resolvedConnectionString","appendTokenToConnectionString","KeyvRedis","url","socket","connectTimeout","socketTimeout","connectionTimeout","tokenPath","unwatchFile","watchFile","interval","TOKEN_WATCH_INTERVAL_MS","scheduleTokenRefresh","clearTimeout","shutdownStoreSafely","setTimeout","refreshIfTokenChanged","TOKEN_WATCH_DEBOUNCE_MS","runWithLock","nextToken","readTokenFromFile","nextResolvedConnectionString","sameToken","sameConnStr","previousStore","nextStore","err","error","log","event","error_code","CACHE_ERROR_CODE_UNAVAILABLE","message","Error","String","operation","raw","readFile","trimmed","trim","length","warn","URL","redactConnectionString","currentOptions","searchParams","get","optionsWithoutToken","replace","tokenOption","nextOptions","filter","Boolean","join","set","toString","disconnectable","disconnect","input","NESTJS_CACHE_OPTIONS","Symbol","CACHE_STORE_HANDLE","CACHE_OBSERVABLE_ADAPTER","CACHE_LOGGER_ADAPTER","NestjsCacheCoreModule","forRoot","optionsProvider","imports","module","providers","provide","CACHE_OBSERVABLE_ADAPTER","useFactory","observable","rtx","impl","Observable","log","level","event","attrs","inject","token","OBSERVABLE_SERVICE","optional","RequestContextService","CACHE_LOGGER_ADAPTER","appLogger","warn","msg","console","error","err","AppLogger","CACHE_STORE_HANDLE","opts","logger","createCacheStore","connectionString","connectionTokenFilePath","connectTimeoutMs","socketTimeoutMs","NESTJS_CACHE_OPTIONS","exports","NestjsCacheModule","storeHandle","onApplicationShutdown","dispose","options","coreModule","useValue","normalizeOptions","CacheModule","registerAsync","isGlobal","handle","stores","keyv","forRootAsync","args","raw","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/nestjs/nestjs-cache.module.ts","../src/core/cache-store.ts","../src/core/const.ts","../src/core/hooks.ts","../src/nestjs/const.ts"],"sourcesContent":["/**\n * @lark-apaas/cache-service\n *\n * NestJS Cache SDK,基于 cache-manager v6 + keyv v5 + @keyv/redis。\n *\n * 主入口默认导出 nestjs 装配层:\n * ```ts\n * import { NestjsCacheModule } from '@lark-apaas/cache-service';\n *\n * NestjsCacheModule.forRoot({ connectionString: 'redis://...' });\n * ```\n *\n * 若不使用 NestJS(例如 Express、Koa),可直接引入 core 层:\n * ```ts\n * import { createCacheStore } from '@lark-apaas/cache-service/core';\n * ```\n */\n\nexport * from './nestjs/index';\n\n// 便于业务从主入口直接消费 core 类型,避免二次 import 深路径\nexport type {\n CacheStoreOptions,\n CacheStoreHandle,\n ObservableAdapter,\n LoggerAdapter,\n CacheReadResult,\n CacheTraceEvent,\n} from './core/types';\n","import {\n DynamicModule,\n Global,\n Inject,\n InjectionToken,\n Module,\n ModuleMetadata,\n OnApplicationShutdown,\n Provider,\n} from '@nestjs/common';\nimport { CacheModule } from '@nestjs/cache-manager';\nimport {\n OBSERVABLE_SERVICE,\n RequestContextService,\n type ObservableService,\n} from '@lark-apaas/nestjs-common';\nimport { AppLogger } from '@lark-apaas/nestjs-logger';\nimport { Observable } from '@lark-apaas/nestjs-observable';\n\nimport { createCacheStore } from '../core/cache-store';\nimport {\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n} from '../core/const';\nimport type {\n CacheStoreHandle,\n LoggerAdapter,\n ObservableAdapter,\n} from '../core/types';\nimport {\n CACHE_LOGGER_ADAPTER,\n CACHE_OBSERVABLE_ADAPTER,\n CACHE_STORE_HANDLE,\n NESTJS_CACHE_OPTIONS,\n} from './const';\n\n/**\n * 用户传入的运行时配置\n */\nexport interface NestjsCacheModuleOptions {\n /**\n * Redis 连接串(redis:// 或 rediss://)\n */\n connectionString: string;\n /**\n * ZTI Token 文件路径。存在则启动 watchFile 热刷新。\n */\n connectionTokenFilePath?: string;\n /**\n * Socket connect timeout(毫秒)。默认 10_000。\n */\n connectTimeoutMs?: number;\n /**\n * Socket idle timeout(毫秒)。默认 5_000。\n */\n socketTimeoutMs?: number;\n}\n\n/**\n * 异步配置选项\n */\nexport interface NestjsCacheModuleAsyncOptions {\n imports?: ModuleMetadata['imports'];\n useFactory: (\n ...args: unknown[]\n ) => Promise<NestjsCacheModuleOptions> | NestjsCacheModuleOptions;\n inject?: InjectionToken[];\n}\n\n/**\n * 内部核心模块:只负责组装 options + adapters + CACHE_STORE_HANDLE 三层 provider,\n * 并 export 给 CacheModule 通过 imports 消费。\n *\n * 拆出独立 module 的原因:NestJS DynamicModule 里放在 imports 中的 CacheModule\n * 无法看到同级 providers 定义的 token,必须走 module-to-module 的 exports 链路。\n */\n@Global()\n@Module({})\nclass NestjsCacheCoreModule {\n static forRoot(\n optionsProvider: Provider,\n imports: ModuleMetadata['imports'],\n ): DynamicModule {\n return {\n module: NestjsCacheCoreModule,\n imports,\n providers: [\n optionsProvider,\n {\n provide: CACHE_OBSERVABLE_ADAPTER,\n useFactory: (\n observable: ObservableService | null,\n rtx: RequestContextService,\n ): ObservableAdapter => {\n const impl = observable ?? new Observable(rtx);\n return {\n log(level, event, attrs) {\n impl.log(level, event, attrs);\n },\n };\n },\n inject: [\n { token: OBSERVABLE_SERVICE, optional: true },\n RequestContextService,\n ],\n },\n {\n provide: CACHE_LOGGER_ADAPTER,\n useFactory: (appLogger: AppLogger | null): LoggerAdapter => {\n if (!appLogger) {\n return {\n warn: (msg) => console.warn(msg),\n error: (msg, err) => console.error(msg, err),\n };\n }\n return {\n warn: (msg) => appLogger.warn(msg),\n error: (msg, err) => appLogger.error(msg, err),\n };\n },\n inject: [{ token: AppLogger, optional: true }],\n },\n {\n provide: CACHE_STORE_HANDLE,\n useFactory: (\n opts: NestjsCacheModuleOptions,\n observable: ObservableAdapter,\n logger: LoggerAdapter,\n ): CacheStoreHandle =>\n createCacheStore({\n connectionString: opts.connectionString,\n connectionTokenFilePath: opts.connectionTokenFilePath,\n connectTimeoutMs: opts.connectTimeoutMs,\n socketTimeoutMs: opts.socketTimeoutMs,\n observable,\n logger,\n }),\n inject: [\n NESTJS_CACHE_OPTIONS,\n CACHE_OBSERVABLE_ADAPTER,\n CACHE_LOGGER_ADAPTER,\n ],\n },\n ],\n exports: [CACHE_STORE_HANDLE, NESTJS_CACHE_OPTIONS],\n };\n }\n}\n\n@Global()\n@Module({})\nexport class NestjsCacheModule implements OnApplicationShutdown {\n constructor(\n @Inject(CACHE_STORE_HANDLE)\n private readonly storeHandle: CacheStoreHandle,\n ) {}\n\n async onApplicationShutdown(): Promise<void> {\n await this.storeHandle.dispose();\n }\n\n /**\n * 同步注册\n */\n static forRoot(options: NestjsCacheModuleOptions): DynamicModule {\n const coreModule = NestjsCacheCoreModule.forRoot(\n {\n provide: NESTJS_CACHE_OPTIONS,\n useValue: normalizeOptions(options),\n },\n [],\n );\n\n return {\n module: NestjsCacheModule,\n imports: [\n coreModule,\n CacheModule.registerAsync({\n isGlobal: true,\n imports: [coreModule],\n inject: [CACHE_STORE_HANDLE],\n useFactory: (handle: CacheStoreHandle) => ({\n stores: [handle.keyv],\n }),\n }),\n ],\n exports: [CacheModule, coreModule],\n };\n }\n\n /**\n * 异步注册(从 ConfigService 等异步源读配置)\n */\n static forRootAsync(options: NestjsCacheModuleAsyncOptions): DynamicModule {\n const optionsProvider: Provider = {\n provide: NESTJS_CACHE_OPTIONS,\n useFactory: async (...args: unknown[]) => {\n const raw = await options.useFactory(...args);\n return normalizeOptions(raw);\n },\n inject: options.inject ?? [],\n };\n\n const coreModule = NestjsCacheCoreModule.forRoot(\n optionsProvider,\n options.imports ?? [],\n );\n\n return {\n module: NestjsCacheModule,\n imports: [\n coreModule,\n CacheModule.registerAsync({\n isGlobal: true,\n imports: [coreModule],\n inject: [CACHE_STORE_HANDLE],\n useFactory: (handle: CacheStoreHandle) => ({\n stores: [handle.keyv],\n }),\n }),\n ],\n exports: [CacheModule, coreModule],\n };\n }\n}\n\n/**\n * 应用默认值\n */\nfunction normalizeOptions(\n options: NestjsCacheModuleOptions,\n): NestjsCacheModuleOptions {\n return {\n connectionString: options.connectionString,\n connectionTokenFilePath: options.connectionTokenFilePath,\n connectTimeoutMs: options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs: options.socketTimeoutMs ?? DEFAULT_SOCKET_TIMEOUT_MS,\n };\n}\n","import KeyvDefault from 'keyv';\nimport KeyvRedisDefault from '@keyv/redis';\nimport * as KeyvModuleNS from 'keyv';\nimport * as KeyvRedisModuleNS from '@keyv/redis';\nimport type Keyv from 'keyv';\nimport type KeyvRedis from '@keyv/redis';\nimport { readFile } from 'node:fs/promises';\nimport { watchFile, unwatchFile } from 'node:fs';\n\nimport type {\n CacheStoreHandle,\n CacheStoreOptions,\n LoggerAdapter,\n ObservableAdapter,\n} from './types';\nimport {\n CACHE_ERROR_CODE_UNAVAILABLE,\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n TOKEN_WATCH_DEBOUNCE_MS,\n TOKEN_WATCH_INTERVAL_MS,\n} from './const';\nimport { registerHooks } from './hooks';\n\n/**\n * `keyv` / `@keyv/redis` 都是 ESM-only 包且只做 default 导出。\n * tsup 生成的 CJS 会用 `__toESM(mod, 1)` 包装:当被包内没有 `__esModule` 标记时,\n * 它会把整个 exports 对象重新塞到 `default` 上,导致 `new mod.default(...)` 报\n * \"not a constructor\"。用一次运行时探测拿到真正的 class 引用(保留类型 default 导入用于 TS 编译)。\n */\nfunction resolveCtor(\n primary: unknown,\n ns: unknown,\n moduleName: string,\n): new (...args: unknown[]) => unknown {\n if (typeof primary === 'function') {\n return primary as new (...args: unknown[]) => unknown;\n }\n const fromNsDefault = (ns as { default?: unknown } | null)?.default;\n if (typeof fromNsDefault === 'function') {\n return fromNsDefault as new (...args: unknown[]) => unknown;\n }\n const nested = (fromNsDefault as { default?: unknown } | null)?.default;\n if (typeof nested === 'function') {\n return nested as new (...args: unknown[]) => unknown;\n }\n if (typeof ns === 'function') {\n return ns as new (...args: unknown[]) => unknown;\n }\n throw new Error(\n `cache-service: unable to resolve default constructor from ${moduleName}`,\n );\n}\n\nconst KeyvCtor = resolveCtor(\n KeyvDefault,\n KeyvModuleNS,\n 'keyv',\n) as unknown as new (...args: ConstructorParameters<typeof Keyv>) => Keyv;\nconst KeyvRedisCtor = resolveCtor(\n KeyvRedisDefault,\n KeyvRedisModuleNS,\n '@keyv/redis',\n) as unknown as new (\n ...args: ConstructorParameters<typeof KeyvRedis>\n) => KeyvRedis<unknown>;\n\ntype AnyKeyvRedis = KeyvRedis<unknown>;\n\n/**\n * 构造一个带 hooks 上报与 token 热刷新能力的 Keyv 实例\n *\n * 关键设计:\n * - `forRoot` 只构造不握手,KeyvRedis 首次读写时才实际连接 Redis\n * - `connectionTokenFilePath` 存在时启动 watchFile 轮询 + 防抖 + 单例锁重建 store\n * - store 替换通过 `keyv.store = newStore` 完成,业务侧 DI 引用保持稳定\n */\nexport function createCacheStore(options: CacheStoreOptions): CacheStoreHandle {\n const {\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS,\n observable,\n logger,\n } = options;\n\n const manager = new StoreManager({\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs,\n socketTimeoutMs,\n observable,\n logger,\n });\n\n const initialStore = manager.buildStore(null);\n manager.setInitialStore(initialStore);\n const keyv = new KeyvCtor({\n store: initialStore,\n useKeyPrefix: false,\n throwOnErrors: false,\n });\n\n registerHooks(keyv, observable);\n manager.attach(keyv);\n manager.startTokenWatcher();\n\n return {\n keyv,\n dispose: async () => {\n manager.stopTokenWatcher();\n await manager.disposeCurrentStore();\n },\n };\n}\n\ninterface StoreManagerConfig {\n connectionString: string;\n connectionTokenFilePath?: string;\n connectTimeoutMs: number;\n socketTimeoutMs: number;\n observable: ObservableAdapter;\n logger?: LoggerAdapter;\n}\n\n/**\n * StoreManager\n *\n * 负责底层 KeyvRedis 的构造、token 热刷新、并发保护。\n * 骨架照搬 @lark-apaas/nestjs-datapaas 的 DrizzleDatabaseManager:\n * - watchFile 轮询 600 s 兜底\n * - 300 ms 防抖\n * - connectionOpPromise 单例锁\n */\nclass StoreManager {\n private keyv: Keyv | null = null;\n private currentStore: AnyKeyvRedis | null = null;\n private currentToken: string | null = null;\n private currentResolvedConnectionString: string | null = null;\n private connectionOpPromise: Promise<void> | null = null;\n private refreshing = false;\n\n private tokenWatchFilePath: string | null = null;\n private tokenWatchDebounceTimer: NodeJS.Timeout | null = null;\n\n constructor(private readonly config: StoreManagerConfig) {}\n\n attach(keyv: Keyv): void {\n this.keyv = keyv;\n }\n\n setInitialStore(store: AnyKeyvRedis): void {\n this.currentStore = store;\n }\n\n /**\n * 基于 connectionString + token 构造新 KeyvRedis\n * 显式列出 socket 超时配置,保持默认值可读\n */\n buildStore(token: string | null): AnyKeyvRedis {\n const resolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n token,\n );\n\n // @keyv/redis v5 直接接 RedisClientOptions;socket 层配置需要下沉到 clientOptions.socket\n const store = new KeyvRedisCtor(\n {\n url: resolvedConnectionString,\n socket: {\n connectTimeout: this.config.connectTimeoutMs,\n // node-redis SocketOptions.socketTimeout:读写空闲超时\n socketTimeout: this.config.socketTimeoutMs,\n },\n },\n {\n // @keyv/redis 层的 connect 超时(覆盖 keyv 侧兜底)\n connectionTimeout: this.config.connectTimeoutMs,\n },\n );\n\n this.currentToken = token;\n this.currentResolvedConnectionString = resolvedConnectionString;\n return store;\n }\n\n startTokenWatcher(): void {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath === tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n\n watchFile(tokenPath, { interval: TOKEN_WATCH_INTERVAL_MS }, () => {\n this.scheduleTokenRefresh();\n });\n this.tokenWatchFilePath = tokenPath;\n }\n\n stopTokenWatcher(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n this.tokenWatchDebounceTimer = null;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n }\n\n async disposeCurrentStore(): Promise<void> {\n await this.shutdownStoreSafely(this.currentStore);\n this.currentStore = null;\n }\n\n private scheduleTokenRefresh(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n }\n this.tokenWatchDebounceTimer = setTimeout(() => {\n void this.refreshIfTokenChanged();\n }, TOKEN_WATCH_DEBOUNCE_MS);\n }\n\n private async refreshIfTokenChanged(): Promise<void> {\n if (this.refreshing) {\n return;\n }\n this.refreshing = true;\n try {\n await this.runWithLock(async () => {\n const nextToken = await this.readTokenFromFile();\n const nextResolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n nextToken,\n );\n\n const sameToken = nextToken === this.currentToken;\n const sameConnStr =\n nextResolvedConnectionString === this.currentResolvedConnectionString;\n if (sameToken && sameConnStr) {\n return;\n }\n\n const previousStore = this.currentStore;\n const nextStore = this.buildStore(nextToken);\n this.currentStore = nextStore;\n\n if (this.keyv) {\n this.keyv.store = nextStore;\n }\n\n await this.shutdownStoreSafely(previousStore);\n });\n } catch (err) {\n this.config.logger?.error(\n 'Failed to refresh cache store from token file',\n err,\n );\n this.config.observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n this.refreshing = false;\n }\n }\n\n /**\n * 单例锁:并发 refresh 只跑一次\n */\n private runWithLock(operation: () => Promise<void>): Promise<void> {\n if (this.connectionOpPromise) {\n return this.connectionOpPromise;\n }\n this.connectionOpPromise = (async () => {\n try {\n await operation();\n } finally {\n this.connectionOpPromise = null;\n }\n })();\n return this.connectionOpPromise;\n }\n\n private async readTokenFromFile(): Promise<string | null> {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return null;\n }\n try {\n const raw = await readFile(tokenPath, 'utf-8');\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to read token file, falling back to previous token',\n { tokenPath, err },\n );\n return null;\n }\n }\n\n /**\n * 追加 -c miaoda.zti_token=<token> 到 URL 的 options 参数\n * 与 nestjs-datapaas 对齐\n */\n private appendTokenToConnectionString(\n connectionString: string,\n token: string | null,\n ): string {\n if (!token) {\n return connectionString;\n }\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to parse connectionString, skipping token append',\n {\n connectionString: redactConnectionString(connectionString),\n err,\n },\n );\n return connectionString;\n }\n\n const currentOptions = url.searchParams.get('options') || '';\n const optionsWithoutToken = currentOptions\n .replace(/(?:^|\\s)-c\\s+miaoda\\.zti_token=[^\\s]*/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n const tokenOption = `-c miaoda.zti_token=${token}`;\n const nextOptions = [optionsWithoutToken, tokenOption]\n .filter(Boolean)\n .join(' ')\n .trim();\n\n url.searchParams.set('options', nextOptions);\n return url.toString();\n }\n\n private async shutdownStoreSafely(store: AnyKeyvRedis | null): Promise<void> {\n if (!store) {\n return;\n }\n try {\n const disconnectable = store as unknown as { disconnect?: () => Promise<void> };\n if (typeof disconnectable.disconnect === 'function') {\n await disconnectable.disconnect();\n }\n } catch (err) {\n this.config.logger?.error(\n 'Error shutting down previous KeyvRedis store',\n err,\n );\n }\n }\n}\n\n/**\n * 屏蔽 URL 中的 password 段,避免打 log 时泄漏凭据\n * redis://user:secret@host:6379 → redis://user:***@host:6379\n * 非法 URL 或无凭据时原样返回\n */\nfunction redactConnectionString(input: string): string {\n return input.replace(/(:\\/\\/[^:@/]*:)[^@/]*(@)/, '$1***$2');\n}\n","export const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\nexport const DEFAULT_SOCKET_TIMEOUT_MS = 5_000;\n\nexport const TOKEN_WATCH_INTERVAL_MS = 600_000;\nexport const TOKEN_WATCH_DEBOUNCE_MS = 300;\n\nexport const CACHE_ERROR_CODE_UNAVAILABLE = 'CACHE_SERVICE_UNAVAILABLE';\n","import { KeyvHooks } from 'keyv';\nimport type Keyv from 'keyv';\nimport type { CacheReadResult, ObservableAdapter } from './types';\nimport { CACHE_ERROR_CODE_UNAVAILABLE } from './const';\n\n/**\n * 给 keyv 实例挂 hooks + error 监听,将缓存事件桥接到 Observable\n *\n * 事件契约:\n * - cache.read: cache_key / result(hit|miss) / duration_ms\n * - cache.write: cache_key / duration_ms\n * - cache.delete: cache_key / duration_ms\n * - cache.error: error_code / message\n */\nexport function registerHooks(keyv: Keyv, observable: ObservableAdapter): void {\n const getStart = new WeakMap<object, number>();\n const setStart = new WeakMap<object, number>();\n const delStart = new WeakMap<object, number>();\n\n keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data: { key: string }) => {\n getStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_GET, (data: { key: string; value: unknown }) => {\n const started = getStart.get(data) ?? Date.now();\n const result: CacheReadResult = data.value === undefined ? 'miss' : 'hit';\n observable.log('debug', 'cache.read', {\n event: 'cache.read',\n cache_key: data.key,\n result,\n duration_ms: Date.now() - started,\n });\n getStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n setStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n const started = setStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.write', {\n event: 'cache.write',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n setStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_DELETE, (data: { key: string }) => {\n delStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_DELETE, (data: { key: string; value: unknown }) => {\n const started = delStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.delete', {\n event: 'cache.delete',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n delStart.delete(data);\n });\n\n keyv.on('error', (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message,\n });\n });\n}\n","/**\n * @lark-apaas/cache-service - NestJS 层 DI tokens\n */\n\nexport const NESTJS_CACHE_OPTIONS = Symbol('NESTJS_CACHE_OPTIONS');\n\nexport const CACHE_STORE_HANDLE = Symbol('CACHE_STORE_HANDLE');\n\nexport const CACHE_OBSERVABLE_ADAPTER = Symbol('CACHE_OBSERVABLE_ADAPTER');\n\nexport const CACHE_LOGGER_ADAPTER = Symbol('CACHE_LOGGER_ADAPTER');\n"],"mappings":"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","names":["import_keyv","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS","TOKEN_WATCH_INTERVAL_MS","TOKEN_WATCH_DEBOUNCE_MS","CACHE_ERROR_CODE_UNAVAILABLE","registerHooks","keyv","observable","getStart","WeakMap","setStart","delStart","hooks","addHandler","KeyvHooks","PRE_GET","data","set","Date","now","POST_GET","started","get","result","value","undefined","log","event","cache_key","key","duration_ms","delete","PRE_SET","POST_SET","PRE_DELETE","POST_DELETE","on","err","message","Error","String","error_code","CACHE_ERROR_CODE_UNAVAILABLE","resolveCtor","primary","ns","moduleName","fromNsDefault","default","nested","Error","KeyvCtor","KeyvDefault","KeyvModuleNS","KeyvRedisCtor","KeyvRedisDefault","KeyvRedisModuleNS","createCacheStore","options","connectionString","connectionTokenFilePath","connectTimeoutMs","DEFAULT_CONNECT_TIMEOUT_MS","socketTimeoutMs","DEFAULT_SOCKET_TIMEOUT_MS","observable","logger","manager","StoreManager","initialStore","buildStore","setInitialStore","keyv","store","useKeyPrefix","throwOnErrors","registerHooks","attach","startTokenWatcher","dispose","stopTokenWatcher","disposeCurrentStore","currentStore","currentToken","currentResolvedConnectionString","connectionOpPromise","refreshing","tokenWatchFilePath","tokenWatchDebounceTimer","config","token","resolvedConnectionString","appendTokenToConnectionString","url","socket","connectTimeout","socketTimeout","connectionTimeout","tokenPath","unwatchFile","watchFile","interval","TOKEN_WATCH_INTERVAL_MS","scheduleTokenRefresh","clearTimeout","shutdownStoreSafely","setTimeout","refreshIfTokenChanged","TOKEN_WATCH_DEBOUNCE_MS","runWithLock","nextToken","readTokenFromFile","nextResolvedConnectionString","sameToken","sameConnStr","previousStore","nextStore","err","error","log","event","error_code","CACHE_ERROR_CODE_UNAVAILABLE","message","String","operation","raw","readFile","trimmed","trim","length","warn","URL","redactConnectionString","currentOptions","searchParams","get","optionsWithoutToken","replace","tokenOption","nextOptions","filter","Boolean","join","set","toString","disconnectable","disconnect","input","NESTJS_CACHE_OPTIONS","Symbol","CACHE_STORE_HANDLE","CACHE_OBSERVABLE_ADAPTER","CACHE_LOGGER_ADAPTER","NestjsCacheCoreModule","forRoot","optionsProvider","imports","module","providers","provide","CACHE_OBSERVABLE_ADAPTER","useFactory","observable","rtx","impl","Observable","log","level","event","attrs","inject","token","OBSERVABLE_SERVICE","optional","RequestContextService","CACHE_LOGGER_ADAPTER","appLogger","warn","msg","console","error","err","AppLogger","CACHE_STORE_HANDLE","opts","logger","createCacheStore","connectionString","connectionTokenFilePath","connectTimeoutMs","socketTimeoutMs","NESTJS_CACHE_OPTIONS","exports","NestjsCacheModule","storeHandle","onApplicationShutdown","dispose","options","coreModule","useValue","normalizeOptions","CacheModule","registerAsync","isGlobal","handle","stores","keyv","forRootAsync","args","raw","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS"]}
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  CACHE_STORE_HANDLE,
5
5
  NESTJS_CACHE_OPTIONS,
6
6
  NestjsCacheModule
7
- } from "./chunk-SSW7W3K6.js";
8
- import "./chunk-BSJRPCEN.js";
7
+ } from "./chunk-5GN2M6V6.js";
8
+ import "./chunk-KVHESGI2.js";
9
9
  export {
10
10
  CACHE_LOGGER_ADAPTER,
11
11
  CACHE_OBSERVABLE_ADAPTER,
@@ -49,6 +49,8 @@ var import_nestjs_observable = require("@lark-apaas/nestjs-observable");
49
49
  // src/core/cache-store.ts
50
50
  var import_keyv2 = __toESM(require("keyv"), 1);
51
51
  var import_redis = __toESM(require("@keyv/redis"), 1);
52
+ var KeyvModuleNS = __toESM(require("keyv"), 1);
53
+ var KeyvRedisModuleNS = __toESM(require("@keyv/redis"), 1);
52
54
  var import_promises = require("fs/promises");
53
55
  var import_node_fs = require("fs");
54
56
 
@@ -115,6 +117,26 @@ function registerHooks(keyv, observable) {
115
117
  __name(registerHooks, "registerHooks");
116
118
 
117
119
  // src/core/cache-store.ts
120
+ function resolveCtor(primary, ns, moduleName) {
121
+ if (typeof primary === "function") {
122
+ return primary;
123
+ }
124
+ const fromNsDefault = ns?.default;
125
+ if (typeof fromNsDefault === "function") {
126
+ return fromNsDefault;
127
+ }
128
+ const nested = fromNsDefault?.default;
129
+ if (typeof nested === "function") {
130
+ return nested;
131
+ }
132
+ if (typeof ns === "function") {
133
+ return ns;
134
+ }
135
+ throw new Error(`cache-service: unable to resolve default constructor from ${moduleName}`);
136
+ }
137
+ __name(resolveCtor, "resolveCtor");
138
+ var KeyvCtor = resolveCtor(import_keyv2.default, KeyvModuleNS, "keyv");
139
+ var KeyvRedisCtor = resolveCtor(import_redis.default, KeyvRedisModuleNS, "@keyv/redis");
118
140
  function createCacheStore(options) {
119
141
  const { connectionString, connectionTokenFilePath, connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS, socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS, observable, logger } = options;
120
142
  const manager = new StoreManager({
@@ -127,7 +149,7 @@ function createCacheStore(options) {
127
149
  });
128
150
  const initialStore = manager.buildStore(null);
129
151
  manager.setInitialStore(initialStore);
130
- const keyv = new import_keyv2.default({
152
+ const keyv = new KeyvCtor({
131
153
  store: initialStore,
132
154
  useKeyPrefix: false,
133
155
  throwOnErrors: false
@@ -172,7 +194,7 @@ var StoreManager = class StoreManager2 {
172
194
  */
173
195
  buildStore(token) {
174
196
  const resolvedConnectionString = this.appendTokenToConnectionString(this.config.connectionString, token);
175
- const store = new import_redis.default({
197
+ const store = new KeyvRedisCtor({
176
198
  url: resolvedConnectionString,
177
199
  socket: {
178
200
  connectTimeout: this.config.connectTimeoutMs,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/nestjs/index.ts","../../src/nestjs/nestjs-cache.module.ts","../../src/core/cache-store.ts","../../src/core/const.ts","../../src/core/hooks.ts","../../src/nestjs/const.ts"],"sourcesContent":["/**\n * @lark-apaas/cache-service - NestJS 装配层\n *\n * 提供 `NestjsCacheModule.forRoot` / `forRootAsync` 用于 NestJS 应用注册。\n * 装配层会自动桥接 nestjs-observable 与 nestjs-logger,业务侧只需传连接串。\n */\n\nexport {\n NestjsCacheModule,\n type NestjsCacheModuleOptions,\n type NestjsCacheModuleAsyncOptions,\n} from './nestjs-cache.module';\n\nexport {\n NESTJS_CACHE_OPTIONS,\n CACHE_STORE_HANDLE,\n CACHE_OBSERVABLE_ADAPTER,\n CACHE_LOGGER_ADAPTER,\n} from './const';\n","import {\n DynamicModule,\n Global,\n Inject,\n InjectionToken,\n Module,\n ModuleMetadata,\n OnApplicationShutdown,\n Provider,\n} from '@nestjs/common';\nimport { CacheModule } from '@nestjs/cache-manager';\nimport {\n OBSERVABLE_SERVICE,\n RequestContextService,\n type ObservableService,\n} from '@lark-apaas/nestjs-common';\nimport { AppLogger } from '@lark-apaas/nestjs-logger';\nimport { Observable } from '@lark-apaas/nestjs-observable';\n\nimport { createCacheStore } from '../core/cache-store';\nimport {\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n} from '../core/const';\nimport type {\n CacheStoreHandle,\n LoggerAdapter,\n ObservableAdapter,\n} from '../core/types';\nimport {\n CACHE_LOGGER_ADAPTER,\n CACHE_OBSERVABLE_ADAPTER,\n CACHE_STORE_HANDLE,\n NESTJS_CACHE_OPTIONS,\n} from './const';\n\n/**\n * 用户传入的运行时配置\n */\nexport interface NestjsCacheModuleOptions {\n /**\n * Redis 连接串(redis:// 或 rediss://)\n */\n connectionString: string;\n /**\n * ZTI Token 文件路径。存在则启动 watchFile 热刷新。\n */\n connectionTokenFilePath?: string;\n /**\n * Socket connect timeout(毫秒)。默认 10_000。\n */\n connectTimeoutMs?: number;\n /**\n * Socket idle timeout(毫秒)。默认 5_000。\n */\n socketTimeoutMs?: number;\n}\n\n/**\n * 异步配置选项\n */\nexport interface NestjsCacheModuleAsyncOptions {\n imports?: ModuleMetadata['imports'];\n useFactory: (\n ...args: unknown[]\n ) => Promise<NestjsCacheModuleOptions> | NestjsCacheModuleOptions;\n inject?: InjectionToken[];\n}\n\n/**\n * 内部核心模块:只负责组装 options + adapters + CACHE_STORE_HANDLE 三层 provider,\n * 并 export 给 CacheModule 通过 imports 消费。\n *\n * 拆出独立 module 的原因:NestJS DynamicModule 里放在 imports 中的 CacheModule\n * 无法看到同级 providers 定义的 token,必须走 module-to-module 的 exports 链路。\n */\n@Global()\n@Module({})\nclass NestjsCacheCoreModule {\n static forRoot(\n optionsProvider: Provider,\n imports: ModuleMetadata['imports'],\n ): DynamicModule {\n return {\n module: NestjsCacheCoreModule,\n imports,\n providers: [\n optionsProvider,\n {\n provide: CACHE_OBSERVABLE_ADAPTER,\n useFactory: (\n observable: ObservableService | null,\n rtx: RequestContextService,\n ): ObservableAdapter => {\n const impl = observable ?? new Observable(rtx);\n return {\n log(level, event, attrs) {\n impl.log(level, event, attrs);\n },\n };\n },\n inject: [\n { token: OBSERVABLE_SERVICE, optional: true },\n RequestContextService,\n ],\n },\n {\n provide: CACHE_LOGGER_ADAPTER,\n useFactory: (appLogger: AppLogger | null): LoggerAdapter => {\n if (!appLogger) {\n return {\n warn: (msg) => console.warn(msg),\n error: (msg, err) => console.error(msg, err),\n };\n }\n return {\n warn: (msg) => appLogger.warn(msg),\n error: (msg, err) => appLogger.error(msg, err),\n };\n },\n inject: [{ token: AppLogger, optional: true }],\n },\n {\n provide: CACHE_STORE_HANDLE,\n useFactory: (\n opts: NestjsCacheModuleOptions,\n observable: ObservableAdapter,\n logger: LoggerAdapter,\n ): CacheStoreHandle =>\n createCacheStore({\n connectionString: opts.connectionString,\n connectionTokenFilePath: opts.connectionTokenFilePath,\n connectTimeoutMs: opts.connectTimeoutMs,\n socketTimeoutMs: opts.socketTimeoutMs,\n observable,\n logger,\n }),\n inject: [\n NESTJS_CACHE_OPTIONS,\n CACHE_OBSERVABLE_ADAPTER,\n CACHE_LOGGER_ADAPTER,\n ],\n },\n ],\n exports: [CACHE_STORE_HANDLE, NESTJS_CACHE_OPTIONS],\n };\n }\n}\n\n@Global()\n@Module({})\nexport class NestjsCacheModule implements OnApplicationShutdown {\n constructor(\n @Inject(CACHE_STORE_HANDLE)\n private readonly storeHandle: CacheStoreHandle,\n ) {}\n\n async onApplicationShutdown(): Promise<void> {\n await this.storeHandle.dispose();\n }\n\n /**\n * 同步注册\n */\n static forRoot(options: NestjsCacheModuleOptions): DynamicModule {\n const coreModule = NestjsCacheCoreModule.forRoot(\n {\n provide: NESTJS_CACHE_OPTIONS,\n useValue: normalizeOptions(options),\n },\n [],\n );\n\n return {\n module: NestjsCacheModule,\n imports: [\n coreModule,\n CacheModule.registerAsync({\n isGlobal: true,\n imports: [coreModule],\n inject: [CACHE_STORE_HANDLE],\n useFactory: (handle: CacheStoreHandle) => ({\n stores: [handle.keyv],\n }),\n }),\n ],\n exports: [CacheModule, coreModule],\n };\n }\n\n /**\n * 异步注册(从 ConfigService 等异步源读配置)\n */\n static forRootAsync(options: NestjsCacheModuleAsyncOptions): DynamicModule {\n const optionsProvider: Provider = {\n provide: NESTJS_CACHE_OPTIONS,\n useFactory: async (...args: unknown[]) => {\n const raw = await options.useFactory(...args);\n return normalizeOptions(raw);\n },\n inject: options.inject ?? [],\n };\n\n const coreModule = NestjsCacheCoreModule.forRoot(\n optionsProvider,\n options.imports ?? [],\n );\n\n return {\n module: NestjsCacheModule,\n imports: [\n coreModule,\n CacheModule.registerAsync({\n isGlobal: true,\n imports: [coreModule],\n inject: [CACHE_STORE_HANDLE],\n useFactory: (handle: CacheStoreHandle) => ({\n stores: [handle.keyv],\n }),\n }),\n ],\n exports: [CacheModule, coreModule],\n };\n }\n}\n\n/**\n * 应用默认值\n */\nfunction normalizeOptions(\n options: NestjsCacheModuleOptions,\n): NestjsCacheModuleOptions {\n return {\n connectionString: options.connectionString,\n connectionTokenFilePath: options.connectionTokenFilePath,\n connectTimeoutMs: options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs: options.socketTimeoutMs ?? DEFAULT_SOCKET_TIMEOUT_MS,\n };\n}\n","import Keyv from 'keyv';\nimport KeyvRedis from '@keyv/redis';\nimport { readFile } from 'node:fs/promises';\nimport { watchFile, unwatchFile } from 'node:fs';\n\nimport type {\n CacheStoreHandle,\n CacheStoreOptions,\n LoggerAdapter,\n ObservableAdapter,\n} from './types';\nimport {\n CACHE_ERROR_CODE_UNAVAILABLE,\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n TOKEN_WATCH_DEBOUNCE_MS,\n TOKEN_WATCH_INTERVAL_MS,\n} from './const';\nimport { registerHooks } from './hooks';\n\ntype AnyKeyvRedis = KeyvRedis<unknown>;\n\n/**\n * 构造一个带 hooks 上报与 token 热刷新能力的 Keyv 实例\n *\n * 关键设计:\n * - `forRoot` 只构造不握手,KeyvRedis 首次读写时才实际连接 Redis\n * - `connectionTokenFilePath` 存在时启动 watchFile 轮询 + 防抖 + 单例锁重建 store\n * - store 替换通过 `keyv.store = newStore` 完成,业务侧 DI 引用保持稳定\n */\nexport function createCacheStore(options: CacheStoreOptions): CacheStoreHandle {\n const {\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS,\n observable,\n logger,\n } = options;\n\n const manager = new StoreManager({\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs,\n socketTimeoutMs,\n observable,\n logger,\n });\n\n const initialStore = manager.buildStore(null);\n manager.setInitialStore(initialStore);\n const keyv = new Keyv({\n store: initialStore,\n useKeyPrefix: false,\n throwOnErrors: false,\n });\n\n registerHooks(keyv, observable);\n manager.attach(keyv);\n manager.startTokenWatcher();\n\n return {\n keyv,\n dispose: async () => {\n manager.stopTokenWatcher();\n await manager.disposeCurrentStore();\n },\n };\n}\n\ninterface StoreManagerConfig {\n connectionString: string;\n connectionTokenFilePath?: string;\n connectTimeoutMs: number;\n socketTimeoutMs: number;\n observable: ObservableAdapter;\n logger?: LoggerAdapter;\n}\n\n/**\n * StoreManager\n *\n * 负责底层 KeyvRedis 的构造、token 热刷新、并发保护。\n * 骨架照搬 @lark-apaas/nestjs-datapaas 的 DrizzleDatabaseManager:\n * - watchFile 轮询 600 s 兜底\n * - 300 ms 防抖\n * - connectionOpPromise 单例锁\n */\nclass StoreManager {\n private keyv: Keyv | null = null;\n private currentStore: AnyKeyvRedis | null = null;\n private currentToken: string | null = null;\n private currentResolvedConnectionString: string | null = null;\n private connectionOpPromise: Promise<void> | null = null;\n private refreshing = false;\n\n private tokenWatchFilePath: string | null = null;\n private tokenWatchDebounceTimer: NodeJS.Timeout | null = null;\n\n constructor(private readonly config: StoreManagerConfig) {}\n\n attach(keyv: Keyv): void {\n this.keyv = keyv;\n }\n\n setInitialStore(store: AnyKeyvRedis): void {\n this.currentStore = store;\n }\n\n /**\n * 基于 connectionString + token 构造新 KeyvRedis\n * 显式列出 socket 超时配置,保持默认值可读\n */\n buildStore(token: string | null): AnyKeyvRedis {\n const resolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n token,\n );\n\n // @keyv/redis v5 直接接 RedisClientOptions;socket 层配置需要下沉到 clientOptions.socket\n const store = new KeyvRedis(\n {\n url: resolvedConnectionString,\n socket: {\n connectTimeout: this.config.connectTimeoutMs,\n // node-redis SocketOptions.socketTimeout:读写空闲超时\n socketTimeout: this.config.socketTimeoutMs,\n },\n },\n {\n // @keyv/redis 层的 connect 超时(覆盖 keyv 侧兜底)\n connectionTimeout: this.config.connectTimeoutMs,\n },\n );\n\n this.currentToken = token;\n this.currentResolvedConnectionString = resolvedConnectionString;\n return store;\n }\n\n startTokenWatcher(): void {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath === tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n\n watchFile(tokenPath, { interval: TOKEN_WATCH_INTERVAL_MS }, () => {\n this.scheduleTokenRefresh();\n });\n this.tokenWatchFilePath = tokenPath;\n }\n\n stopTokenWatcher(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n this.tokenWatchDebounceTimer = null;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n }\n\n async disposeCurrentStore(): Promise<void> {\n await this.shutdownStoreSafely(this.currentStore);\n this.currentStore = null;\n }\n\n private scheduleTokenRefresh(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n }\n this.tokenWatchDebounceTimer = setTimeout(() => {\n void this.refreshIfTokenChanged();\n }, TOKEN_WATCH_DEBOUNCE_MS);\n }\n\n private async refreshIfTokenChanged(): Promise<void> {\n if (this.refreshing) {\n return;\n }\n this.refreshing = true;\n try {\n await this.runWithLock(async () => {\n const nextToken = await this.readTokenFromFile();\n const nextResolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n nextToken,\n );\n\n const sameToken = nextToken === this.currentToken;\n const sameConnStr =\n nextResolvedConnectionString === this.currentResolvedConnectionString;\n if (sameToken && sameConnStr) {\n return;\n }\n\n const previousStore = this.currentStore;\n const nextStore = this.buildStore(nextToken);\n this.currentStore = nextStore;\n\n if (this.keyv) {\n this.keyv.store = nextStore;\n }\n\n await this.shutdownStoreSafely(previousStore);\n });\n } catch (err) {\n this.config.logger?.error(\n 'Failed to refresh cache store from token file',\n err,\n );\n this.config.observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n this.refreshing = false;\n }\n }\n\n /**\n * 单例锁:并发 refresh 只跑一次\n */\n private runWithLock(operation: () => Promise<void>): Promise<void> {\n if (this.connectionOpPromise) {\n return this.connectionOpPromise;\n }\n this.connectionOpPromise = (async () => {\n try {\n await operation();\n } finally {\n this.connectionOpPromise = null;\n }\n })();\n return this.connectionOpPromise;\n }\n\n private async readTokenFromFile(): Promise<string | null> {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return null;\n }\n try {\n const raw = await readFile(tokenPath, 'utf-8');\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to read token file, falling back to previous token',\n { tokenPath, err },\n );\n return null;\n }\n }\n\n /**\n * 追加 -c miaoda.zti_token=<token> 到 URL 的 options 参数\n * 与 nestjs-datapaas 对齐\n */\n private appendTokenToConnectionString(\n connectionString: string,\n token: string | null,\n ): string {\n if (!token) {\n return connectionString;\n }\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to parse connectionString, skipping token append',\n {\n connectionString: redactConnectionString(connectionString),\n err,\n },\n );\n return connectionString;\n }\n\n const currentOptions = url.searchParams.get('options') || '';\n const optionsWithoutToken = currentOptions\n .replace(/(?:^|\\s)-c\\s+miaoda\\.zti_token=[^\\s]*/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n const tokenOption = `-c miaoda.zti_token=${token}`;\n const nextOptions = [optionsWithoutToken, tokenOption]\n .filter(Boolean)\n .join(' ')\n .trim();\n\n url.searchParams.set('options', nextOptions);\n return url.toString();\n }\n\n private async shutdownStoreSafely(store: AnyKeyvRedis | null): Promise<void> {\n if (!store) {\n return;\n }\n try {\n const disconnectable = store as unknown as { disconnect?: () => Promise<void> };\n if (typeof disconnectable.disconnect === 'function') {\n await disconnectable.disconnect();\n }\n } catch (err) {\n this.config.logger?.error(\n 'Error shutting down previous KeyvRedis store',\n err,\n );\n }\n }\n}\n\n/**\n * 屏蔽 URL 中的 password 段,避免打 log 时泄漏凭据\n * redis://user:secret@host:6379 → redis://user:***@host:6379\n * 非法 URL 或无凭据时原样返回\n */\nfunction redactConnectionString(input: string): string {\n return input.replace(/(:\\/\\/[^:@/]*:)[^@/]*(@)/, '$1***$2');\n}\n","export const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\nexport const DEFAULT_SOCKET_TIMEOUT_MS = 5_000;\n\nexport const TOKEN_WATCH_INTERVAL_MS = 600_000;\nexport const TOKEN_WATCH_DEBOUNCE_MS = 300;\n\nexport const CACHE_ERROR_CODE_UNAVAILABLE = 'CACHE_SERVICE_UNAVAILABLE';\n","import Keyv, { KeyvHooks } from 'keyv';\nimport type { CacheReadResult, ObservableAdapter } from './types';\nimport { CACHE_ERROR_CODE_UNAVAILABLE } from './const';\n\n/**\n * 给 keyv 实例挂 hooks + error 监听,将缓存事件桥接到 Observable\n *\n * 事件契约:\n * - cache.read: cache_key / result(hit|miss) / duration_ms\n * - cache.write: cache_key / duration_ms\n * - cache.delete: cache_key / duration_ms\n * - cache.error: error_code / message\n */\nexport function registerHooks(keyv: Keyv, observable: ObservableAdapter): void {\n const getStart = new WeakMap<object, number>();\n const setStart = new WeakMap<object, number>();\n const delStart = new WeakMap<object, number>();\n\n keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data: { key: string }) => {\n getStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_GET, (data: { key: string; value: unknown }) => {\n const started = getStart.get(data) ?? Date.now();\n const result: CacheReadResult = data.value === undefined ? 'miss' : 'hit';\n observable.log('debug', 'cache.read', {\n event: 'cache.read',\n cache_key: data.key,\n result,\n duration_ms: Date.now() - started,\n });\n getStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n setStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n const started = setStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.write', {\n event: 'cache.write',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n setStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_DELETE, (data: { key: string }) => {\n delStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_DELETE, (data: { key: string; value: unknown }) => {\n const started = delStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.delete', {\n event: 'cache.delete',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n delStart.delete(data);\n });\n\n keyv.on('error', (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message,\n });\n });\n}\n","/**\n * @lark-apaas/cache-service - NestJS 层 DI tokens\n */\n\nexport const NESTJS_CACHE_OPTIONS = Symbol('NESTJS_CACHE_OPTIONS');\n\nexport const CACHE_STORE_HANDLE = Symbol('CACHE_STORE_HANDLE');\n\nexport const CACHE_OBSERVABLE_ADAPTER = Symbol('CACHE_OBSERVABLE_ADAPTER');\n\nexport const CACHE_LOGGER_ADAPTER = Symbol('CACHE_LOGGER_ADAPTER');\n"],"mappings":"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","names":["import_keyv","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS","TOKEN_WATCH_INTERVAL_MS","TOKEN_WATCH_DEBOUNCE_MS","CACHE_ERROR_CODE_UNAVAILABLE","registerHooks","keyv","observable","getStart","WeakMap","setStart","delStart","hooks","addHandler","KeyvHooks","PRE_GET","data","set","Date","now","POST_GET","started","get","result","value","undefined","log","event","cache_key","key","duration_ms","delete","PRE_SET","POST_SET","PRE_DELETE","POST_DELETE","on","err","message","Error","String","error_code","CACHE_ERROR_CODE_UNAVAILABLE","createCacheStore","options","connectionString","connectionTokenFilePath","connectTimeoutMs","DEFAULT_CONNECT_TIMEOUT_MS","socketTimeoutMs","DEFAULT_SOCKET_TIMEOUT_MS","observable","logger","manager","StoreManager","initialStore","buildStore","setInitialStore","keyv","Keyv","store","useKeyPrefix","throwOnErrors","registerHooks","attach","startTokenWatcher","dispose","stopTokenWatcher","disposeCurrentStore","currentStore","currentToken","currentResolvedConnectionString","connectionOpPromise","refreshing","tokenWatchFilePath","tokenWatchDebounceTimer","config","token","resolvedConnectionString","appendTokenToConnectionString","KeyvRedis","url","socket","connectTimeout","socketTimeout","connectionTimeout","tokenPath","unwatchFile","watchFile","interval","TOKEN_WATCH_INTERVAL_MS","scheduleTokenRefresh","clearTimeout","shutdownStoreSafely","setTimeout","refreshIfTokenChanged","TOKEN_WATCH_DEBOUNCE_MS","runWithLock","nextToken","readTokenFromFile","nextResolvedConnectionString","sameToken","sameConnStr","previousStore","nextStore","err","error","log","event","error_code","CACHE_ERROR_CODE_UNAVAILABLE","message","Error","String","operation","raw","readFile","trimmed","trim","length","warn","URL","redactConnectionString","currentOptions","searchParams","get","optionsWithoutToken","replace","tokenOption","nextOptions","filter","Boolean","join","set","toString","disconnectable","disconnect","input","NESTJS_CACHE_OPTIONS","Symbol","CACHE_STORE_HANDLE","CACHE_OBSERVABLE_ADAPTER","CACHE_LOGGER_ADAPTER","NestjsCacheCoreModule","forRoot","optionsProvider","imports","module","providers","provide","CACHE_OBSERVABLE_ADAPTER","useFactory","observable","rtx","impl","Observable","log","level","event","attrs","inject","token","OBSERVABLE_SERVICE","optional","RequestContextService","CACHE_LOGGER_ADAPTER","appLogger","warn","msg","console","error","err","AppLogger","CACHE_STORE_HANDLE","opts","logger","createCacheStore","connectionString","connectionTokenFilePath","connectTimeoutMs","socketTimeoutMs","NESTJS_CACHE_OPTIONS","exports","NestjsCacheModule","storeHandle","onApplicationShutdown","dispose","options","coreModule","useValue","normalizeOptions","CacheModule","registerAsync","isGlobal","handle","stores","keyv","forRootAsync","args","raw","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS"]}
1
+ {"version":3,"sources":["../../src/nestjs/index.ts","../../src/nestjs/nestjs-cache.module.ts","../../src/core/cache-store.ts","../../src/core/const.ts","../../src/core/hooks.ts","../../src/nestjs/const.ts"],"sourcesContent":["/**\n * @lark-apaas/cache-service - NestJS 装配层\n *\n * 提供 `NestjsCacheModule.forRoot` / `forRootAsync` 用于 NestJS 应用注册。\n * 装配层会自动桥接 nestjs-observable 与 nestjs-logger,业务侧只需传连接串。\n */\n\nexport {\n NestjsCacheModule,\n type NestjsCacheModuleOptions,\n type NestjsCacheModuleAsyncOptions,\n} from './nestjs-cache.module';\n\nexport {\n NESTJS_CACHE_OPTIONS,\n CACHE_STORE_HANDLE,\n CACHE_OBSERVABLE_ADAPTER,\n CACHE_LOGGER_ADAPTER,\n} from './const';\n","import {\n DynamicModule,\n Global,\n Inject,\n InjectionToken,\n Module,\n ModuleMetadata,\n OnApplicationShutdown,\n Provider,\n} from '@nestjs/common';\nimport { CacheModule } from '@nestjs/cache-manager';\nimport {\n OBSERVABLE_SERVICE,\n RequestContextService,\n type ObservableService,\n} from '@lark-apaas/nestjs-common';\nimport { AppLogger } from '@lark-apaas/nestjs-logger';\nimport { Observable } from '@lark-apaas/nestjs-observable';\n\nimport { createCacheStore } from '../core/cache-store';\nimport {\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n} from '../core/const';\nimport type {\n CacheStoreHandle,\n LoggerAdapter,\n ObservableAdapter,\n} from '../core/types';\nimport {\n CACHE_LOGGER_ADAPTER,\n CACHE_OBSERVABLE_ADAPTER,\n CACHE_STORE_HANDLE,\n NESTJS_CACHE_OPTIONS,\n} from './const';\n\n/**\n * 用户传入的运行时配置\n */\nexport interface NestjsCacheModuleOptions {\n /**\n * Redis 连接串(redis:// 或 rediss://)\n */\n connectionString: string;\n /**\n * ZTI Token 文件路径。存在则启动 watchFile 热刷新。\n */\n connectionTokenFilePath?: string;\n /**\n * Socket connect timeout(毫秒)。默认 10_000。\n */\n connectTimeoutMs?: number;\n /**\n * Socket idle timeout(毫秒)。默认 5_000。\n */\n socketTimeoutMs?: number;\n}\n\n/**\n * 异步配置选项\n */\nexport interface NestjsCacheModuleAsyncOptions {\n imports?: ModuleMetadata['imports'];\n useFactory: (\n ...args: unknown[]\n ) => Promise<NestjsCacheModuleOptions> | NestjsCacheModuleOptions;\n inject?: InjectionToken[];\n}\n\n/**\n * 内部核心模块:只负责组装 options + adapters + CACHE_STORE_HANDLE 三层 provider,\n * 并 export 给 CacheModule 通过 imports 消费。\n *\n * 拆出独立 module 的原因:NestJS DynamicModule 里放在 imports 中的 CacheModule\n * 无法看到同级 providers 定义的 token,必须走 module-to-module 的 exports 链路。\n */\n@Global()\n@Module({})\nclass NestjsCacheCoreModule {\n static forRoot(\n optionsProvider: Provider,\n imports: ModuleMetadata['imports'],\n ): DynamicModule {\n return {\n module: NestjsCacheCoreModule,\n imports,\n providers: [\n optionsProvider,\n {\n provide: CACHE_OBSERVABLE_ADAPTER,\n useFactory: (\n observable: ObservableService | null,\n rtx: RequestContextService,\n ): ObservableAdapter => {\n const impl = observable ?? new Observable(rtx);\n return {\n log(level, event, attrs) {\n impl.log(level, event, attrs);\n },\n };\n },\n inject: [\n { token: OBSERVABLE_SERVICE, optional: true },\n RequestContextService,\n ],\n },\n {\n provide: CACHE_LOGGER_ADAPTER,\n useFactory: (appLogger: AppLogger | null): LoggerAdapter => {\n if (!appLogger) {\n return {\n warn: (msg) => console.warn(msg),\n error: (msg, err) => console.error(msg, err),\n };\n }\n return {\n warn: (msg) => appLogger.warn(msg),\n error: (msg, err) => appLogger.error(msg, err),\n };\n },\n inject: [{ token: AppLogger, optional: true }],\n },\n {\n provide: CACHE_STORE_HANDLE,\n useFactory: (\n opts: NestjsCacheModuleOptions,\n observable: ObservableAdapter,\n logger: LoggerAdapter,\n ): CacheStoreHandle =>\n createCacheStore({\n connectionString: opts.connectionString,\n connectionTokenFilePath: opts.connectionTokenFilePath,\n connectTimeoutMs: opts.connectTimeoutMs,\n socketTimeoutMs: opts.socketTimeoutMs,\n observable,\n logger,\n }),\n inject: [\n NESTJS_CACHE_OPTIONS,\n CACHE_OBSERVABLE_ADAPTER,\n CACHE_LOGGER_ADAPTER,\n ],\n },\n ],\n exports: [CACHE_STORE_HANDLE, NESTJS_CACHE_OPTIONS],\n };\n }\n}\n\n@Global()\n@Module({})\nexport class NestjsCacheModule implements OnApplicationShutdown {\n constructor(\n @Inject(CACHE_STORE_HANDLE)\n private readonly storeHandle: CacheStoreHandle,\n ) {}\n\n async onApplicationShutdown(): Promise<void> {\n await this.storeHandle.dispose();\n }\n\n /**\n * 同步注册\n */\n static forRoot(options: NestjsCacheModuleOptions): DynamicModule {\n const coreModule = NestjsCacheCoreModule.forRoot(\n {\n provide: NESTJS_CACHE_OPTIONS,\n useValue: normalizeOptions(options),\n },\n [],\n );\n\n return {\n module: NestjsCacheModule,\n imports: [\n coreModule,\n CacheModule.registerAsync({\n isGlobal: true,\n imports: [coreModule],\n inject: [CACHE_STORE_HANDLE],\n useFactory: (handle: CacheStoreHandle) => ({\n stores: [handle.keyv],\n }),\n }),\n ],\n exports: [CacheModule, coreModule],\n };\n }\n\n /**\n * 异步注册(从 ConfigService 等异步源读配置)\n */\n static forRootAsync(options: NestjsCacheModuleAsyncOptions): DynamicModule {\n const optionsProvider: Provider = {\n provide: NESTJS_CACHE_OPTIONS,\n useFactory: async (...args: unknown[]) => {\n const raw = await options.useFactory(...args);\n return normalizeOptions(raw);\n },\n inject: options.inject ?? [],\n };\n\n const coreModule = NestjsCacheCoreModule.forRoot(\n optionsProvider,\n options.imports ?? [],\n );\n\n return {\n module: NestjsCacheModule,\n imports: [\n coreModule,\n CacheModule.registerAsync({\n isGlobal: true,\n imports: [coreModule],\n inject: [CACHE_STORE_HANDLE],\n useFactory: (handle: CacheStoreHandle) => ({\n stores: [handle.keyv],\n }),\n }),\n ],\n exports: [CacheModule, coreModule],\n };\n }\n}\n\n/**\n * 应用默认值\n */\nfunction normalizeOptions(\n options: NestjsCacheModuleOptions,\n): NestjsCacheModuleOptions {\n return {\n connectionString: options.connectionString,\n connectionTokenFilePath: options.connectionTokenFilePath,\n connectTimeoutMs: options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs: options.socketTimeoutMs ?? DEFAULT_SOCKET_TIMEOUT_MS,\n };\n}\n","import KeyvDefault from 'keyv';\nimport KeyvRedisDefault from '@keyv/redis';\nimport * as KeyvModuleNS from 'keyv';\nimport * as KeyvRedisModuleNS from '@keyv/redis';\nimport type Keyv from 'keyv';\nimport type KeyvRedis from '@keyv/redis';\nimport { readFile } from 'node:fs/promises';\nimport { watchFile, unwatchFile } from 'node:fs';\n\nimport type {\n CacheStoreHandle,\n CacheStoreOptions,\n LoggerAdapter,\n ObservableAdapter,\n} from './types';\nimport {\n CACHE_ERROR_CODE_UNAVAILABLE,\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n TOKEN_WATCH_DEBOUNCE_MS,\n TOKEN_WATCH_INTERVAL_MS,\n} from './const';\nimport { registerHooks } from './hooks';\n\n/**\n * `keyv` / `@keyv/redis` 都是 ESM-only 包且只做 default 导出。\n * tsup 生成的 CJS 会用 `__toESM(mod, 1)` 包装:当被包内没有 `__esModule` 标记时,\n * 它会把整个 exports 对象重新塞到 `default` 上,导致 `new mod.default(...)` 报\n * \"not a constructor\"。用一次运行时探测拿到真正的 class 引用(保留类型 default 导入用于 TS 编译)。\n */\nfunction resolveCtor(\n primary: unknown,\n ns: unknown,\n moduleName: string,\n): new (...args: unknown[]) => unknown {\n if (typeof primary === 'function') {\n return primary as new (...args: unknown[]) => unknown;\n }\n const fromNsDefault = (ns as { default?: unknown } | null)?.default;\n if (typeof fromNsDefault === 'function') {\n return fromNsDefault as new (...args: unknown[]) => unknown;\n }\n const nested = (fromNsDefault as { default?: unknown } | null)?.default;\n if (typeof nested === 'function') {\n return nested as new (...args: unknown[]) => unknown;\n }\n if (typeof ns === 'function') {\n return ns as new (...args: unknown[]) => unknown;\n }\n throw new Error(\n `cache-service: unable to resolve default constructor from ${moduleName}`,\n );\n}\n\nconst KeyvCtor = resolveCtor(\n KeyvDefault,\n KeyvModuleNS,\n 'keyv',\n) as unknown as new (...args: ConstructorParameters<typeof Keyv>) => Keyv;\nconst KeyvRedisCtor = resolveCtor(\n KeyvRedisDefault,\n KeyvRedisModuleNS,\n '@keyv/redis',\n) as unknown as new (\n ...args: ConstructorParameters<typeof KeyvRedis>\n) => KeyvRedis<unknown>;\n\ntype AnyKeyvRedis = KeyvRedis<unknown>;\n\n/**\n * 构造一个带 hooks 上报与 token 热刷新能力的 Keyv 实例\n *\n * 关键设计:\n * - `forRoot` 只构造不握手,KeyvRedis 首次读写时才实际连接 Redis\n * - `connectionTokenFilePath` 存在时启动 watchFile 轮询 + 防抖 + 单例锁重建 store\n * - store 替换通过 `keyv.store = newStore` 完成,业务侧 DI 引用保持稳定\n */\nexport function createCacheStore(options: CacheStoreOptions): CacheStoreHandle {\n const {\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS,\n observable,\n logger,\n } = options;\n\n const manager = new StoreManager({\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs,\n socketTimeoutMs,\n observable,\n logger,\n });\n\n const initialStore = manager.buildStore(null);\n manager.setInitialStore(initialStore);\n const keyv = new KeyvCtor({\n store: initialStore,\n useKeyPrefix: false,\n throwOnErrors: false,\n });\n\n registerHooks(keyv, observable);\n manager.attach(keyv);\n manager.startTokenWatcher();\n\n return {\n keyv,\n dispose: async () => {\n manager.stopTokenWatcher();\n await manager.disposeCurrentStore();\n },\n };\n}\n\ninterface StoreManagerConfig {\n connectionString: string;\n connectionTokenFilePath?: string;\n connectTimeoutMs: number;\n socketTimeoutMs: number;\n observable: ObservableAdapter;\n logger?: LoggerAdapter;\n}\n\n/**\n * StoreManager\n *\n * 负责底层 KeyvRedis 的构造、token 热刷新、并发保护。\n * 骨架照搬 @lark-apaas/nestjs-datapaas 的 DrizzleDatabaseManager:\n * - watchFile 轮询 600 s 兜底\n * - 300 ms 防抖\n * - connectionOpPromise 单例锁\n */\nclass StoreManager {\n private keyv: Keyv | null = null;\n private currentStore: AnyKeyvRedis | null = null;\n private currentToken: string | null = null;\n private currentResolvedConnectionString: string | null = null;\n private connectionOpPromise: Promise<void> | null = null;\n private refreshing = false;\n\n private tokenWatchFilePath: string | null = null;\n private tokenWatchDebounceTimer: NodeJS.Timeout | null = null;\n\n constructor(private readonly config: StoreManagerConfig) {}\n\n attach(keyv: Keyv): void {\n this.keyv = keyv;\n }\n\n setInitialStore(store: AnyKeyvRedis): void {\n this.currentStore = store;\n }\n\n /**\n * 基于 connectionString + token 构造新 KeyvRedis\n * 显式列出 socket 超时配置,保持默认值可读\n */\n buildStore(token: string | null): AnyKeyvRedis {\n const resolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n token,\n );\n\n // @keyv/redis v5 直接接 RedisClientOptions;socket 层配置需要下沉到 clientOptions.socket\n const store = new KeyvRedisCtor(\n {\n url: resolvedConnectionString,\n socket: {\n connectTimeout: this.config.connectTimeoutMs,\n // node-redis SocketOptions.socketTimeout:读写空闲超时\n socketTimeout: this.config.socketTimeoutMs,\n },\n },\n {\n // @keyv/redis 层的 connect 超时(覆盖 keyv 侧兜底)\n connectionTimeout: this.config.connectTimeoutMs,\n },\n );\n\n this.currentToken = token;\n this.currentResolvedConnectionString = resolvedConnectionString;\n return store;\n }\n\n startTokenWatcher(): void {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath === tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n\n watchFile(tokenPath, { interval: TOKEN_WATCH_INTERVAL_MS }, () => {\n this.scheduleTokenRefresh();\n });\n this.tokenWatchFilePath = tokenPath;\n }\n\n stopTokenWatcher(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n this.tokenWatchDebounceTimer = null;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n }\n\n async disposeCurrentStore(): Promise<void> {\n await this.shutdownStoreSafely(this.currentStore);\n this.currentStore = null;\n }\n\n private scheduleTokenRefresh(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n }\n this.tokenWatchDebounceTimer = setTimeout(() => {\n void this.refreshIfTokenChanged();\n }, TOKEN_WATCH_DEBOUNCE_MS);\n }\n\n private async refreshIfTokenChanged(): Promise<void> {\n if (this.refreshing) {\n return;\n }\n this.refreshing = true;\n try {\n await this.runWithLock(async () => {\n const nextToken = await this.readTokenFromFile();\n const nextResolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n nextToken,\n );\n\n const sameToken = nextToken === this.currentToken;\n const sameConnStr =\n nextResolvedConnectionString === this.currentResolvedConnectionString;\n if (sameToken && sameConnStr) {\n return;\n }\n\n const previousStore = this.currentStore;\n const nextStore = this.buildStore(nextToken);\n this.currentStore = nextStore;\n\n if (this.keyv) {\n this.keyv.store = nextStore;\n }\n\n await this.shutdownStoreSafely(previousStore);\n });\n } catch (err) {\n this.config.logger?.error(\n 'Failed to refresh cache store from token file',\n err,\n );\n this.config.observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n this.refreshing = false;\n }\n }\n\n /**\n * 单例锁:并发 refresh 只跑一次\n */\n private runWithLock(operation: () => Promise<void>): Promise<void> {\n if (this.connectionOpPromise) {\n return this.connectionOpPromise;\n }\n this.connectionOpPromise = (async () => {\n try {\n await operation();\n } finally {\n this.connectionOpPromise = null;\n }\n })();\n return this.connectionOpPromise;\n }\n\n private async readTokenFromFile(): Promise<string | null> {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return null;\n }\n try {\n const raw = await readFile(tokenPath, 'utf-8');\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to read token file, falling back to previous token',\n { tokenPath, err },\n );\n return null;\n }\n }\n\n /**\n * 追加 -c miaoda.zti_token=<token> 到 URL 的 options 参数\n * 与 nestjs-datapaas 对齐\n */\n private appendTokenToConnectionString(\n connectionString: string,\n token: string | null,\n ): string {\n if (!token) {\n return connectionString;\n }\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to parse connectionString, skipping token append',\n {\n connectionString: redactConnectionString(connectionString),\n err,\n },\n );\n return connectionString;\n }\n\n const currentOptions = url.searchParams.get('options') || '';\n const optionsWithoutToken = currentOptions\n .replace(/(?:^|\\s)-c\\s+miaoda\\.zti_token=[^\\s]*/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n const tokenOption = `-c miaoda.zti_token=${token}`;\n const nextOptions = [optionsWithoutToken, tokenOption]\n .filter(Boolean)\n .join(' ')\n .trim();\n\n url.searchParams.set('options', nextOptions);\n return url.toString();\n }\n\n private async shutdownStoreSafely(store: AnyKeyvRedis | null): Promise<void> {\n if (!store) {\n return;\n }\n try {\n const disconnectable = store as unknown as { disconnect?: () => Promise<void> };\n if (typeof disconnectable.disconnect === 'function') {\n await disconnectable.disconnect();\n }\n } catch (err) {\n this.config.logger?.error(\n 'Error shutting down previous KeyvRedis store',\n err,\n );\n }\n }\n}\n\n/**\n * 屏蔽 URL 中的 password 段,避免打 log 时泄漏凭据\n * redis://user:secret@host:6379 → redis://user:***@host:6379\n * 非法 URL 或无凭据时原样返回\n */\nfunction redactConnectionString(input: string): string {\n return input.replace(/(:\\/\\/[^:@/]*:)[^@/]*(@)/, '$1***$2');\n}\n","export const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\nexport const DEFAULT_SOCKET_TIMEOUT_MS = 5_000;\n\nexport const TOKEN_WATCH_INTERVAL_MS = 600_000;\nexport const TOKEN_WATCH_DEBOUNCE_MS = 300;\n\nexport const CACHE_ERROR_CODE_UNAVAILABLE = 'CACHE_SERVICE_UNAVAILABLE';\n","import { KeyvHooks } from 'keyv';\nimport type Keyv from 'keyv';\nimport type { CacheReadResult, ObservableAdapter } from './types';\nimport { CACHE_ERROR_CODE_UNAVAILABLE } from './const';\n\n/**\n * 给 keyv 实例挂 hooks + error 监听,将缓存事件桥接到 Observable\n *\n * 事件契约:\n * - cache.read: cache_key / result(hit|miss) / duration_ms\n * - cache.write: cache_key / duration_ms\n * - cache.delete: cache_key / duration_ms\n * - cache.error: error_code / message\n */\nexport function registerHooks(keyv: Keyv, observable: ObservableAdapter): void {\n const getStart = new WeakMap<object, number>();\n const setStart = new WeakMap<object, number>();\n const delStart = new WeakMap<object, number>();\n\n keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data: { key: string }) => {\n getStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_GET, (data: { key: string; value: unknown }) => {\n const started = getStart.get(data) ?? Date.now();\n const result: CacheReadResult = data.value === undefined ? 'miss' : 'hit';\n observable.log('debug', 'cache.read', {\n event: 'cache.read',\n cache_key: data.key,\n result,\n duration_ms: Date.now() - started,\n });\n getStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n setStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n const started = setStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.write', {\n event: 'cache.write',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n setStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_DELETE, (data: { key: string }) => {\n delStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_DELETE, (data: { key: string; value: unknown }) => {\n const started = delStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.delete', {\n event: 'cache.delete',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n delStart.delete(data);\n });\n\n keyv.on('error', (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message,\n });\n });\n}\n","/**\n * @lark-apaas/cache-service - NestJS 层 DI tokens\n */\n\nexport const NESTJS_CACHE_OPTIONS = Symbol('NESTJS_CACHE_OPTIONS');\n\nexport const CACHE_STORE_HANDLE = Symbol('CACHE_STORE_HANDLE');\n\nexport const CACHE_OBSERVABLE_ADAPTER = Symbol('CACHE_OBSERVABLE_ADAPTER');\n\nexport const CACHE_LOGGER_ADAPTER = Symbol('CACHE_LOGGER_ADAPTER');\n"],"mappings":"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","names":["import_keyv","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS","TOKEN_WATCH_INTERVAL_MS","TOKEN_WATCH_DEBOUNCE_MS","CACHE_ERROR_CODE_UNAVAILABLE","registerHooks","keyv","observable","getStart","WeakMap","setStart","delStart","hooks","addHandler","KeyvHooks","PRE_GET","data","set","Date","now","POST_GET","started","get","result","value","undefined","log","event","cache_key","key","duration_ms","delete","PRE_SET","POST_SET","PRE_DELETE","POST_DELETE","on","err","message","Error","String","error_code","CACHE_ERROR_CODE_UNAVAILABLE","resolveCtor","primary","ns","moduleName","fromNsDefault","default","nested","Error","KeyvCtor","KeyvDefault","KeyvModuleNS","KeyvRedisCtor","KeyvRedisDefault","KeyvRedisModuleNS","createCacheStore","options","connectionString","connectionTokenFilePath","connectTimeoutMs","DEFAULT_CONNECT_TIMEOUT_MS","socketTimeoutMs","DEFAULT_SOCKET_TIMEOUT_MS","observable","logger","manager","StoreManager","initialStore","buildStore","setInitialStore","keyv","store","useKeyPrefix","throwOnErrors","registerHooks","attach","startTokenWatcher","dispose","stopTokenWatcher","disposeCurrentStore","currentStore","currentToken","currentResolvedConnectionString","connectionOpPromise","refreshing","tokenWatchFilePath","tokenWatchDebounceTimer","config","token","resolvedConnectionString","appendTokenToConnectionString","url","socket","connectTimeout","socketTimeout","connectionTimeout","tokenPath","unwatchFile","watchFile","interval","TOKEN_WATCH_INTERVAL_MS","scheduleTokenRefresh","clearTimeout","shutdownStoreSafely","setTimeout","refreshIfTokenChanged","TOKEN_WATCH_DEBOUNCE_MS","runWithLock","nextToken","readTokenFromFile","nextResolvedConnectionString","sameToken","sameConnStr","previousStore","nextStore","err","error","log","event","error_code","CACHE_ERROR_CODE_UNAVAILABLE","message","String","operation","raw","readFile","trimmed","trim","length","warn","URL","redactConnectionString","currentOptions","searchParams","get","optionsWithoutToken","replace","tokenOption","nextOptions","filter","Boolean","join","set","toString","disconnectable","disconnect","input","NESTJS_CACHE_OPTIONS","Symbol","CACHE_STORE_HANDLE","CACHE_OBSERVABLE_ADAPTER","CACHE_LOGGER_ADAPTER","NestjsCacheCoreModule","forRoot","optionsProvider","imports","module","providers","provide","CACHE_OBSERVABLE_ADAPTER","useFactory","observable","rtx","impl","Observable","log","level","event","attrs","inject","token","OBSERVABLE_SERVICE","optional","RequestContextService","CACHE_LOGGER_ADAPTER","appLogger","warn","msg","console","error","err","AppLogger","CACHE_STORE_HANDLE","opts","logger","createCacheStore","connectionString","connectionTokenFilePath","connectTimeoutMs","socketTimeoutMs","NESTJS_CACHE_OPTIONS","exports","NestjsCacheModule","storeHandle","onApplicationShutdown","dispose","options","coreModule","useValue","normalizeOptions","CacheModule","registerAsync","isGlobal","handle","stores","keyv","forRootAsync","args","raw","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS"]}
@@ -4,8 +4,8 @@ import {
4
4
  CACHE_STORE_HANDLE,
5
5
  NESTJS_CACHE_OPTIONS,
6
6
  NestjsCacheModule
7
- } from "../chunk-SSW7W3K6.js";
8
- import "../chunk-BSJRPCEN.js";
7
+ } from "../chunk-5GN2M6V6.js";
8
+ import "../chunk-KVHESGI2.js";
9
9
  export {
10
10
  CACHE_LOGGER_ADAPTER,
11
11
  CACHE_OBSERVABLE_ADAPTER,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/cache-service",
3
- "version": "0.1.1-alpha.0",
3
+ "version": "0.1.1-alpha.1",
4
4
  "description": "FullStack cache SDK, NestJS today, Express-ready tomorrow",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/const.ts","../src/core/hooks.ts","../src/core/cache-store.ts"],"sourcesContent":["export const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\nexport const DEFAULT_SOCKET_TIMEOUT_MS = 5_000;\n\nexport const TOKEN_WATCH_INTERVAL_MS = 600_000;\nexport const TOKEN_WATCH_DEBOUNCE_MS = 300;\n\nexport const CACHE_ERROR_CODE_UNAVAILABLE = 'CACHE_SERVICE_UNAVAILABLE';\n","import Keyv, { KeyvHooks } from 'keyv';\nimport type { CacheReadResult, ObservableAdapter } from './types';\nimport { CACHE_ERROR_CODE_UNAVAILABLE } from './const';\n\n/**\n * 给 keyv 实例挂 hooks + error 监听,将缓存事件桥接到 Observable\n *\n * 事件契约:\n * - cache.read: cache_key / result(hit|miss) / duration_ms\n * - cache.write: cache_key / duration_ms\n * - cache.delete: cache_key / duration_ms\n * - cache.error: error_code / message\n */\nexport function registerHooks(keyv: Keyv, observable: ObservableAdapter): void {\n const getStart = new WeakMap<object, number>();\n const setStart = new WeakMap<object, number>();\n const delStart = new WeakMap<object, number>();\n\n keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data: { key: string }) => {\n getStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_GET, (data: { key: string; value: unknown }) => {\n const started = getStart.get(data) ?? Date.now();\n const result: CacheReadResult = data.value === undefined ? 'miss' : 'hit';\n observable.log('debug', 'cache.read', {\n event: 'cache.read',\n cache_key: data.key,\n result,\n duration_ms: Date.now() - started,\n });\n getStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n setStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_SET, (data: { key: string; value: unknown; ttl?: number }) => {\n const started = setStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.write', {\n event: 'cache.write',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n setStart.delete(data);\n });\n\n keyv.hooks.addHandler(KeyvHooks.PRE_DELETE, (data: { key: string }) => {\n delStart.set(data, Date.now());\n });\n\n keyv.hooks.addHandler(KeyvHooks.POST_DELETE, (data: { key: string; value: unknown }) => {\n const started = delStart.get(data) ?? Date.now();\n observable.log('debug', 'cache.delete', {\n event: 'cache.delete',\n cache_key: data.key,\n duration_ms: Date.now() - started,\n });\n delStart.delete(data);\n });\n\n keyv.on('error', (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message,\n });\n });\n}\n","import Keyv from 'keyv';\nimport KeyvRedis from '@keyv/redis';\nimport { readFile } from 'node:fs/promises';\nimport { watchFile, unwatchFile } from 'node:fs';\n\nimport type {\n CacheStoreHandle,\n CacheStoreOptions,\n LoggerAdapter,\n ObservableAdapter,\n} from './types';\nimport {\n CACHE_ERROR_CODE_UNAVAILABLE,\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_SOCKET_TIMEOUT_MS,\n TOKEN_WATCH_DEBOUNCE_MS,\n TOKEN_WATCH_INTERVAL_MS,\n} from './const';\nimport { registerHooks } from './hooks';\n\ntype AnyKeyvRedis = KeyvRedis<unknown>;\n\n/**\n * 构造一个带 hooks 上报与 token 热刷新能力的 Keyv 实例\n *\n * 关键设计:\n * - `forRoot` 只构造不握手,KeyvRedis 首次读写时才实际连接 Redis\n * - `connectionTokenFilePath` 存在时启动 watchFile 轮询 + 防抖 + 单例锁重建 store\n * - store 替换通过 `keyv.store = newStore` 完成,业务侧 DI 引用保持稳定\n */\nexport function createCacheStore(options: CacheStoreOptions): CacheStoreHandle {\n const {\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS,\n socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS,\n observable,\n logger,\n } = options;\n\n const manager = new StoreManager({\n connectionString,\n connectionTokenFilePath,\n connectTimeoutMs,\n socketTimeoutMs,\n observable,\n logger,\n });\n\n const initialStore = manager.buildStore(null);\n manager.setInitialStore(initialStore);\n const keyv = new Keyv({\n store: initialStore,\n useKeyPrefix: false,\n throwOnErrors: false,\n });\n\n registerHooks(keyv, observable);\n manager.attach(keyv);\n manager.startTokenWatcher();\n\n return {\n keyv,\n dispose: async () => {\n manager.stopTokenWatcher();\n await manager.disposeCurrentStore();\n },\n };\n}\n\ninterface StoreManagerConfig {\n connectionString: string;\n connectionTokenFilePath?: string;\n connectTimeoutMs: number;\n socketTimeoutMs: number;\n observable: ObservableAdapter;\n logger?: LoggerAdapter;\n}\n\n/**\n * StoreManager\n *\n * 负责底层 KeyvRedis 的构造、token 热刷新、并发保护。\n * 骨架照搬 @lark-apaas/nestjs-datapaas 的 DrizzleDatabaseManager:\n * - watchFile 轮询 600 s 兜底\n * - 300 ms 防抖\n * - connectionOpPromise 单例锁\n */\nclass StoreManager {\n private keyv: Keyv | null = null;\n private currentStore: AnyKeyvRedis | null = null;\n private currentToken: string | null = null;\n private currentResolvedConnectionString: string | null = null;\n private connectionOpPromise: Promise<void> | null = null;\n private refreshing = false;\n\n private tokenWatchFilePath: string | null = null;\n private tokenWatchDebounceTimer: NodeJS.Timeout | null = null;\n\n constructor(private readonly config: StoreManagerConfig) {}\n\n attach(keyv: Keyv): void {\n this.keyv = keyv;\n }\n\n setInitialStore(store: AnyKeyvRedis): void {\n this.currentStore = store;\n }\n\n /**\n * 基于 connectionString + token 构造新 KeyvRedis\n * 显式列出 socket 超时配置,保持默认值可读\n */\n buildStore(token: string | null): AnyKeyvRedis {\n const resolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n token,\n );\n\n // @keyv/redis v5 直接接 RedisClientOptions;socket 层配置需要下沉到 clientOptions.socket\n const store = new KeyvRedis(\n {\n url: resolvedConnectionString,\n socket: {\n connectTimeout: this.config.connectTimeoutMs,\n // node-redis SocketOptions.socketTimeout:读写空闲超时\n socketTimeout: this.config.socketTimeoutMs,\n },\n },\n {\n // @keyv/redis 层的 connect 超时(覆盖 keyv 侧兜底)\n connectionTimeout: this.config.connectTimeoutMs,\n },\n );\n\n this.currentToken = token;\n this.currentResolvedConnectionString = resolvedConnectionString;\n return store;\n }\n\n startTokenWatcher(): void {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath === tokenPath) {\n return;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n\n watchFile(tokenPath, { interval: TOKEN_WATCH_INTERVAL_MS }, () => {\n this.scheduleTokenRefresh();\n });\n this.tokenWatchFilePath = tokenPath;\n }\n\n stopTokenWatcher(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n this.tokenWatchDebounceTimer = null;\n }\n if (this.tokenWatchFilePath) {\n unwatchFile(this.tokenWatchFilePath);\n this.tokenWatchFilePath = null;\n }\n }\n\n async disposeCurrentStore(): Promise<void> {\n await this.shutdownStoreSafely(this.currentStore);\n this.currentStore = null;\n }\n\n private scheduleTokenRefresh(): void {\n if (this.tokenWatchDebounceTimer) {\n clearTimeout(this.tokenWatchDebounceTimer);\n }\n this.tokenWatchDebounceTimer = setTimeout(() => {\n void this.refreshIfTokenChanged();\n }, TOKEN_WATCH_DEBOUNCE_MS);\n }\n\n private async refreshIfTokenChanged(): Promise<void> {\n if (this.refreshing) {\n return;\n }\n this.refreshing = true;\n try {\n await this.runWithLock(async () => {\n const nextToken = await this.readTokenFromFile();\n const nextResolvedConnectionString = this.appendTokenToConnectionString(\n this.config.connectionString,\n nextToken,\n );\n\n const sameToken = nextToken === this.currentToken;\n const sameConnStr =\n nextResolvedConnectionString === this.currentResolvedConnectionString;\n if (sameToken && sameConnStr) {\n return;\n }\n\n const previousStore = this.currentStore;\n const nextStore = this.buildStore(nextToken);\n this.currentStore = nextStore;\n\n if (this.keyv) {\n this.keyv.store = nextStore;\n }\n\n await this.shutdownStoreSafely(previousStore);\n });\n } catch (err) {\n this.config.logger?.error(\n 'Failed to refresh cache store from token file',\n err,\n );\n this.config.observable.log('error', 'cache.error', {\n event: 'cache.error',\n error_code: CACHE_ERROR_CODE_UNAVAILABLE,\n message: err instanceof Error ? err.message : String(err),\n });\n } finally {\n this.refreshing = false;\n }\n }\n\n /**\n * 单例锁:并发 refresh 只跑一次\n */\n private runWithLock(operation: () => Promise<void>): Promise<void> {\n if (this.connectionOpPromise) {\n return this.connectionOpPromise;\n }\n this.connectionOpPromise = (async () => {\n try {\n await operation();\n } finally {\n this.connectionOpPromise = null;\n }\n })();\n return this.connectionOpPromise;\n }\n\n private async readTokenFromFile(): Promise<string | null> {\n const tokenPath = this.config.connectionTokenFilePath;\n if (!tokenPath) {\n return null;\n }\n try {\n const raw = await readFile(tokenPath, 'utf-8');\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to read token file, falling back to previous token',\n { tokenPath, err },\n );\n return null;\n }\n }\n\n /**\n * 追加 -c miaoda.zti_token=<token> 到 URL 的 options 参数\n * 与 nestjs-datapaas 对齐\n */\n private appendTokenToConnectionString(\n connectionString: string,\n token: string | null,\n ): string {\n if (!token) {\n return connectionString;\n }\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch (err) {\n this.config.logger?.warn(\n 'cache-service: failed to parse connectionString, skipping token append',\n {\n connectionString: redactConnectionString(connectionString),\n err,\n },\n );\n return connectionString;\n }\n\n const currentOptions = url.searchParams.get('options') || '';\n const optionsWithoutToken = currentOptions\n .replace(/(?:^|\\s)-c\\s+miaoda\\.zti_token=[^\\s]*/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n const tokenOption = `-c miaoda.zti_token=${token}`;\n const nextOptions = [optionsWithoutToken, tokenOption]\n .filter(Boolean)\n .join(' ')\n .trim();\n\n url.searchParams.set('options', nextOptions);\n return url.toString();\n }\n\n private async shutdownStoreSafely(store: AnyKeyvRedis | null): Promise<void> {\n if (!store) {\n return;\n }\n try {\n const disconnectable = store as unknown as { disconnect?: () => Promise<void> };\n if (typeof disconnectable.disconnect === 'function') {\n await disconnectable.disconnect();\n }\n } catch (err) {\n this.config.logger?.error(\n 'Error shutting down previous KeyvRedis store',\n err,\n );\n }\n }\n}\n\n/**\n * 屏蔽 URL 中的 password 段,避免打 log 时泄漏凭据\n * redis://user:secret@host:6379 → redis://user:***@host:6379\n * 非法 URL 或无凭据时原样返回\n */\nfunction redactConnectionString(input: string): string {\n return input.replace(/(:\\/\\/[^:@/]*:)[^@/]*(@)/, '$1***$2');\n}\n"],"mappings":"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","names":["DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_SOCKET_TIMEOUT_MS","TOKEN_WATCH_INTERVAL_MS","TOKEN_WATCH_DEBOUNCE_MS","CACHE_ERROR_CODE_UNAVAILABLE","KeyvHooks","registerHooks","keyv","observable","getStart","WeakMap","setStart","delStart","hooks","addHandler","KeyvHooks","PRE_GET","data","set","Date","now","POST_GET","started","get","result","value","undefined","log","event","cache_key","key","duration_ms","delete","PRE_SET","POST_SET","PRE_DELETE","POST_DELETE","on","err","message","Error","String","error_code","CACHE_ERROR_CODE_UNAVAILABLE","Keyv","KeyvRedis","readFile","watchFile","unwatchFile","createCacheStore","options","connectionString","connectionTokenFilePath","connectTimeoutMs","DEFAULT_CONNECT_TIMEOUT_MS","socketTimeoutMs","DEFAULT_SOCKET_TIMEOUT_MS","observable","logger","manager","StoreManager","initialStore","buildStore","setInitialStore","keyv","Keyv","store","useKeyPrefix","throwOnErrors","registerHooks","attach","startTokenWatcher","dispose","stopTokenWatcher","disposeCurrentStore","currentStore","currentToken","currentResolvedConnectionString","connectionOpPromise","refreshing","tokenWatchFilePath","tokenWatchDebounceTimer","config","token","resolvedConnectionString","appendTokenToConnectionString","KeyvRedis","url","socket","connectTimeout","socketTimeout","connectionTimeout","tokenPath","unwatchFile","watchFile","interval","TOKEN_WATCH_INTERVAL_MS","scheduleTokenRefresh","clearTimeout","shutdownStoreSafely","setTimeout","refreshIfTokenChanged","TOKEN_WATCH_DEBOUNCE_MS","runWithLock","nextToken","readTokenFromFile","nextResolvedConnectionString","sameToken","sameConnStr","previousStore","nextStore","err","error","log","event","error_code","CACHE_ERROR_CODE_UNAVAILABLE","message","Error","String","operation","raw","readFile","trimmed","trim","length","warn","URL","redactConnectionString","currentOptions","searchParams","get","optionsWithoutToken","replace","tokenOption","nextOptions","filter","Boolean","join","set","toString","disconnectable","disconnect","input"]}