@modern-js/runtime-utils 2.68.1 → 2.68.3

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.
@@ -13,50 +13,6 @@ const CacheTime = {
13
13
  WEEK: 7 * 24 * 60 * 60 * 1e3,
14
14
  MONTH: 30 * 24 * 60 * 60 * 1e3
15
15
  };
16
- const isServer = typeof window === "undefined";
17
- const requestCacheMap = /* @__PURE__ */ new WeakMap();
18
- let lruCache;
19
- let cacheConfig = {
20
- maxSize: CacheSize.GB
21
- };
22
- const tagKeyMap = /* @__PURE__ */ new Map();
23
- function addTagKeyRelation(tag, key) {
24
- let keys = tagKeyMap.get(tag);
25
- if (!keys) {
26
- keys = /* @__PURE__ */ new Set();
27
- tagKeyMap.set(tag, keys);
28
- }
29
- keys.add(key);
30
- }
31
- function configureCache(config) {
32
- cacheConfig = {
33
- ...cacheConfig,
34
- ...config
35
- };
36
- }
37
- function getLRUCache() {
38
- if (!lruCache) {
39
- var _cacheConfig_maxSize;
40
- lruCache = new LRUCache({
41
- maxSize: (_cacheConfig_maxSize = cacheConfig.maxSize) !== null && _cacheConfig_maxSize !== void 0 ? _cacheConfig_maxSize : CacheSize.GB,
42
- sizeCalculation: (value) => {
43
- if (!value.size) {
44
- return 1;
45
- }
46
- let size = 0;
47
- for (const [k, item] of value.entries()) {
48
- size += k.length * 2;
49
- size += estimateObjectSize(item.data);
50
- size += 8;
51
- }
52
- return size;
53
- },
54
- updateAgeOnGet: true,
55
- updateAgeOnHas: true
56
- });
57
- }
58
- return lruCache;
59
- }
60
16
  function estimateObjectSize(data) {
61
17
  const type = typeof data;
62
18
  if (type === "number")
@@ -84,6 +40,67 @@ function estimateObjectSize(data) {
84
40
  }
85
41
  return 1;
86
42
  }
43
+ class MemoryContainer {
44
+ async get(key) {
45
+ return this.lru.get(key);
46
+ }
47
+ async set(key, value, options) {
48
+ if (options === null || options === void 0 ? void 0 : options.ttl) {
49
+ this.lru.set(key, value, {
50
+ ttl: options.ttl * 1e3
51
+ });
52
+ } else {
53
+ this.lru.set(key, value);
54
+ }
55
+ }
56
+ async has(key) {
57
+ return this.lru.has(key);
58
+ }
59
+ async delete(key) {
60
+ return this.lru.delete(key);
61
+ }
62
+ async clear() {
63
+ this.lru.clear();
64
+ }
65
+ constructor(options) {
66
+ var _options_maxSize;
67
+ this.lru = new LRUCache({
68
+ maxSize: (_options_maxSize = options === null || options === void 0 ? void 0 : options.maxSize) !== null && _options_maxSize !== void 0 ? _options_maxSize : CacheSize.GB,
69
+ sizeCalculation: estimateObjectSize,
70
+ updateAgeOnGet: true,
71
+ updateAgeOnHas: true
72
+ });
73
+ }
74
+ }
75
+ const isServer = typeof window === "undefined";
76
+ const requestCacheMap = /* @__PURE__ */ new WeakMap();
77
+ const TAG_PREFIX = "tag:";
78
+ const CACHE_PREFIX = "modernjs_cache:";
79
+ const ongoingRevalidations = /* @__PURE__ */ new Map();
80
+ let storage;
81
+ let cacheConfig = {
82
+ maxSize: CacheSize.GB
83
+ };
84
+ function getStorage() {
85
+ if (storage) {
86
+ return storage;
87
+ }
88
+ if (cacheConfig.container) {
89
+ storage = cacheConfig.container;
90
+ } else {
91
+ storage = new MemoryContainer({
92
+ maxSize: cacheConfig.maxSize
93
+ });
94
+ }
95
+ return storage;
96
+ }
97
+ function configureCache(config) {
98
+ cacheConfig = {
99
+ ...cacheConfig,
100
+ ...config
101
+ };
102
+ storage = void 0;
103
+ }
87
104
  function generateKey(args) {
88
105
  return JSON.stringify(args, (_, value) => {
89
106
  if (value && typeof value === "object" && !Array.isArray(value)) {
@@ -95,24 +112,22 @@ function generateKey(args) {
95
112
  return value;
96
113
  });
97
114
  }
115
+ function generateStableFunctionId(fn) {
116
+ const fnString = fn.toString();
117
+ let hash = 0;
118
+ for (let i = 0; i < fnString.length; i++) {
119
+ const char = fnString.charCodeAt(i);
120
+ hash = (hash << 5) - hash + char;
121
+ hash = hash & hash;
122
+ }
123
+ return `fn_${fn.name || "anonymous"}_${Math.abs(hash).toString(36)}`;
124
+ }
98
125
  function cache(fn, options) {
99
- const { tag = "default", maxAge = CacheTime.MINUTE * 5, revalidate = 0, customKey, onCache, getKey } = options || {};
100
- const store = getLRUCache();
101
- const tags = Array.isArray(tag) ? tag : [
102
- tag
103
- ];
104
- const getCacheKey = (args, generatedKey) => {
105
- return customKey ? customKey({
106
- params: args,
107
- fn,
108
- generatedKey
109
- }) : fn;
110
- };
111
126
  return async (...args) => {
112
127
  if (isServer && typeof options === "undefined") {
113
128
  var _storage_useContext;
114
- const storage = getAsyncLocalStorage();
115
- const request = storage === null || storage === void 0 ? void 0 : (_storage_useContext = storage.useContext()) === null || _storage_useContext === void 0 ? void 0 : _storage_useContext.request;
129
+ const storage2 = getAsyncLocalStorage();
130
+ const request = storage2 === null || storage2 === void 0 ? void 0 : (_storage_useContext = storage2.useContext()) === null || _storage_useContext === void 0 ? void 0 : _storage_useContext.request;
116
131
  if (request) {
117
132
  let shouldDisableCaching = false;
118
133
  if (cacheConfig.unstable_shouldDisable) {
@@ -148,123 +163,231 @@ function cache(fn, options) {
148
163
  }
149
164
  }
150
165
  } else if (typeof options !== "undefined") {
151
- const genKey = getKey ? getKey(...args) : generateKey(args);
152
- const now = Date.now();
153
- const cacheKey = getCacheKey(args, genKey);
154
- const finalKey = typeof cacheKey === "function" ? genKey : cacheKey;
155
- tags.forEach((t) => addTagKeyRelation(t, cacheKey));
156
- let cacheStore = store.get(cacheKey);
157
- if (!cacheStore) {
158
- cacheStore = /* @__PURE__ */ new Map();
159
- }
160
- const storeKey = customKey && typeof cacheKey === "symbol" ? "symbol-key" : genKey;
161
- let shouldDisableCaching = false;
162
- if (isServer && cacheConfig.unstable_shouldDisable) {
163
- var _storage_useContext1;
164
- const storage = getAsyncLocalStorage();
165
- const request = storage === null || storage === void 0 ? void 0 : (_storage_useContext1 = storage.useContext()) === null || _storage_useContext1 === void 0 ? void 0 : _storage_useContext1.request;
166
- if (request) {
167
- shouldDisableCaching = await cacheConfig.unstable_shouldDisable({
168
- request
166
+ try {
167
+ const { tag, maxAge = CacheTime.MINUTE * 5, revalidate = 0, customKey, onCache, getKey, unstable_shouldCache } = options;
168
+ let missReason;
169
+ const currentStorage = getStorage();
170
+ const now = Date.now();
171
+ const tags = tag ? Array.isArray(tag) ? tag : [
172
+ tag
173
+ ] : [];
174
+ const genKey = getKey ? getKey(...args) : generateKey(args);
175
+ let finalKey;
176
+ if (customKey) {
177
+ finalKey = customKey({
178
+ params: args,
179
+ fn,
180
+ generatedKey: genKey
169
181
  });
182
+ } else {
183
+ const functionId = generateStableFunctionId(fn);
184
+ finalKey = `${functionId}:${genKey}`;
170
185
  }
171
- }
172
- const cached = cacheStore.get(storeKey);
173
- if (cached && !shouldDisableCaching) {
174
- const age = now - cached.timestamp;
175
- if (age < maxAge) {
176
- if (onCache) {
177
- onCache({
178
- status: "hit",
179
- key: finalKey,
180
- params: args,
181
- result: cached.data
186
+ const storageKey = `${CACHE_PREFIX}${finalKey}`;
187
+ let shouldDisableCaching = false;
188
+ if (isServer && cacheConfig.unstable_shouldDisable) {
189
+ var _asyncStorage_useContext;
190
+ const asyncStorage = getAsyncLocalStorage();
191
+ const request = asyncStorage === null || asyncStorage === void 0 ? void 0 : (_asyncStorage_useContext = asyncStorage.useContext()) === null || _asyncStorage_useContext === void 0 ? void 0 : _asyncStorage_useContext.request;
192
+ if (request) {
193
+ shouldDisableCaching = await cacheConfig.unstable_shouldDisable({
194
+ request
182
195
  });
183
196
  }
184
- return cached.data;
185
197
  }
186
- if (revalidate > 0 && age < maxAge + revalidate) {
187
- if (onCache) {
188
- onCache({
189
- status: "stale",
190
- key: finalKey,
191
- params: args,
192
- result: cached.data
193
- });
194
- }
195
- if (!cached.isRevalidating) {
196
- cached.isRevalidating = true;
197
- Promise.resolve().then(async () => {
198
- try {
199
- const newData = await fn(...args);
200
- cacheStore.set(storeKey, {
201
- data: newData,
202
- timestamp: Date.now(),
203
- isRevalidating: false
198
+ if (!shouldDisableCaching) {
199
+ const cached = await currentStorage.get(storageKey);
200
+ if (cached) {
201
+ try {
202
+ const cacheItem = cached;
203
+ const age = now - cacheItem.timestamp;
204
+ if (age < maxAge) {
205
+ onCache === null || onCache === void 0 ? void 0 : onCache({
206
+ status: "hit",
207
+ key: finalKey,
208
+ params: args,
209
+ result: cacheItem.data
210
+ });
211
+ return cacheItem.data;
212
+ }
213
+ if (revalidate > 0 && age < maxAge + revalidate) {
214
+ onCache === null || onCache === void 0 ? void 0 : onCache({
215
+ status: "stale",
216
+ key: finalKey,
217
+ params: args,
218
+ result: cacheItem.data
204
219
  });
205
- store.set(cacheKey, cacheStore);
206
- } catch (error) {
207
- cached.isRevalidating = false;
208
- if (isServer) {
209
- var _storage_useContext_monitors, _storage_useContext2;
210
- const storage = getAsyncLocalStorage();
211
- storage === null || storage === void 0 ? void 0 : (_storage_useContext2 = storage.useContext()) === null || _storage_useContext2 === void 0 ? void 0 : (_storage_useContext_monitors = _storage_useContext2.monitors) === null || _storage_useContext_monitors === void 0 ? void 0 : _storage_useContext_monitors.error(error.message);
212
- } else {
213
- console.error("Background revalidation failed:", error);
220
+ if (!ongoingRevalidations.has(storageKey)) {
221
+ const revalidationPromise = (async () => {
222
+ try {
223
+ const newData = await fn(...args);
224
+ let shouldCache = true;
225
+ if (unstable_shouldCache) {
226
+ shouldCache = await unstable_shouldCache({
227
+ params: args,
228
+ result: newData
229
+ });
230
+ }
231
+ if (shouldCache) {
232
+ await setCacheItem(currentStorage, storageKey, newData, tags, maxAge, revalidate);
233
+ }
234
+ } catch (error) {
235
+ if (isServer) {
236
+ var _asyncStorage_useContext_monitors, _asyncStorage_useContext2;
237
+ const asyncStorage = getAsyncLocalStorage();
238
+ asyncStorage === null || asyncStorage === void 0 ? void 0 : (_asyncStorage_useContext2 = asyncStorage.useContext()) === null || _asyncStorage_useContext2 === void 0 ? void 0 : (_asyncStorage_useContext_monitors = _asyncStorage_useContext2.monitors) === null || _asyncStorage_useContext_monitors === void 0 ? void 0 : _asyncStorage_useContext_monitors.error(error.message);
239
+ } else {
240
+ console.error("Background revalidation failed:", error);
241
+ }
242
+ } finally {
243
+ ongoingRevalidations.delete(storageKey);
244
+ }
245
+ })();
246
+ ongoingRevalidations.set(storageKey, revalidationPromise);
214
247
  }
248
+ return cacheItem.data;
215
249
  }
250
+ missReason = 3;
251
+ } catch (error) {
252
+ console.warn("Failed to parse cached data:", error);
253
+ missReason = 4;
254
+ }
255
+ } else {
256
+ missReason = 2;
257
+ }
258
+ } else {
259
+ missReason = 1;
260
+ }
261
+ const data = await fn(...args);
262
+ if (!shouldDisableCaching) {
263
+ let shouldCache = true;
264
+ if (unstable_shouldCache) {
265
+ shouldCache = await unstable_shouldCache({
266
+ params: args,
267
+ result: data
216
268
  });
217
269
  }
218
- return cached.data;
270
+ if (shouldCache) {
271
+ await setCacheItem(currentStorage, storageKey, data, tags, maxAge, revalidate);
272
+ }
219
273
  }
220
- }
221
- const data = await fn(...args);
222
- if (!shouldDisableCaching) {
223
- cacheStore.set(storeKey, {
224
- data,
225
- timestamp: now,
226
- isRevalidating: false
227
- });
228
- store.set(cacheKey, cacheStore);
229
- }
230
- if (onCache) {
231
- onCache({
274
+ onCache === null || onCache === void 0 ? void 0 : onCache({
232
275
  status: "miss",
233
276
  key: finalKey,
234
277
  params: args,
235
- result: data
278
+ result: data,
279
+ reason: missReason
236
280
  });
281
+ return data;
282
+ } catch (error) {
283
+ console.warn("Cache operation failed, falling back to direct execution:", error);
284
+ const data = await fn(...args);
285
+ const { onCache } = options;
286
+ try {
287
+ onCache === null || onCache === void 0 ? void 0 : onCache({
288
+ status: "miss",
289
+ key: "cache_failed",
290
+ params: args,
291
+ result: data,
292
+ reason: 5
293
+ });
294
+ } catch (callbackError) {
295
+ console.warn("Failed to call onCache callback:", callbackError);
296
+ }
297
+ return data;
237
298
  }
238
- return data;
239
299
  } else {
240
300
  console.warn("The cache function will not work because it runs on the browser and there are no options are provided.");
241
301
  return fn(...args);
242
302
  }
243
303
  };
244
304
  }
305
+ async function setCacheItem(storage2, storageKey, data, tags, maxAge, revalidate) {
306
+ const newItem = {
307
+ data,
308
+ timestamp: Date.now(),
309
+ tags: tags.length > 0 ? tags : void 0
310
+ };
311
+ const ttl = (maxAge + revalidate) / 1e3;
312
+ await storage2.set(storageKey, newItem, {
313
+ ttl: ttl > 0 ? ttl : void 0
314
+ });
315
+ await updateTagRelationships(storage2, storageKey, tags);
316
+ }
317
+ async function updateTagRelationships(storage2, storageKey, tags) {
318
+ for (const tag of tags) {
319
+ const tagStoreKey = `${TAG_PREFIX}${tag}`;
320
+ const keyList = await storage2.get(tagStoreKey);
321
+ const keyArray = keyList || [];
322
+ if (!keyArray.includes(storageKey)) {
323
+ keyArray.push(storageKey);
324
+ }
325
+ await storage2.set(tagStoreKey, keyArray);
326
+ }
327
+ }
328
+ async function removeKeyFromTags(storage2, storageKey, tags) {
329
+ for (const tag of tags) {
330
+ const tagStoreKey = `${TAG_PREFIX}${tag}`;
331
+ const keyList = await storage2.get(tagStoreKey);
332
+ if (keyList) {
333
+ try {
334
+ const keyArray = Array.isArray(keyList) ? keyList : [];
335
+ const updatedKeyList = keyArray.filter((key) => key !== storageKey);
336
+ if (updatedKeyList.length > 0) {
337
+ await storage2.set(tagStoreKey, updatedKeyList);
338
+ } else {
339
+ await storage2.delete(tagStoreKey);
340
+ }
341
+ } catch (error) {
342
+ console.warn(`Failed to process tag key list for tag ${tag}:`, error);
343
+ }
344
+ }
345
+ }
346
+ }
245
347
  function withRequestCache(handler) {
246
348
  if (!isServer) {
247
349
  return handler;
248
350
  }
249
351
  return async (req, ...args) => {
250
- const storage = getAsyncLocalStorage();
251
- return storage.run({
352
+ const storage2 = getAsyncLocalStorage();
353
+ return storage2.run({
252
354
  request: req
253
355
  }, () => handler(req, ...args));
254
356
  };
255
357
  }
256
- function revalidateTag(tag) {
257
- const keys = tagKeyMap.get(tag);
258
- if (keys) {
259
- keys.forEach((key) => {
260
- lruCache === null || lruCache === void 0 ? void 0 : lruCache.delete(key);
261
- });
358
+ async function revalidateTag(tag) {
359
+ const currentStorage = getStorage();
360
+ const tagStoreKey = `${TAG_PREFIX}${tag}`;
361
+ const keyList = await currentStorage.get(tagStoreKey);
362
+ if (keyList) {
363
+ try {
364
+ const keyArray = Array.isArray(keyList) ? keyList : [];
365
+ for (const cacheKey of keyArray) {
366
+ const cached = await currentStorage.get(cacheKey);
367
+ if (cached) {
368
+ try {
369
+ const cacheItem = cached;
370
+ if (cacheItem.tags) {
371
+ const otherTags = cacheItem.tags.filter((t) => t !== tag);
372
+ await removeKeyFromTags(currentStorage, cacheKey, otherTags);
373
+ }
374
+ } catch (error) {
375
+ console.warn("Failed to parse cached data while revalidating:", error);
376
+ }
377
+ }
378
+ await currentStorage.delete(cacheKey);
379
+ }
380
+ await currentStorage.delete(tagStoreKey);
381
+ } catch (error) {
382
+ console.warn("Failed to process tag key list:", error);
383
+ }
262
384
  }
263
385
  }
264
- function clearStore() {
265
- lruCache === null || lruCache === void 0 ? void 0 : lruCache.clear();
266
- lruCache = void 0;
267
- tagKeyMap.clear();
386
+ async function clearStore() {
387
+ const currentStorage = getStorage();
388
+ await currentStorage.clear();
389
+ storage = void 0;
390
+ ongoingRevalidations.clear();
268
391
  }
269
392
  export {
270
393
  CacheSize,
@@ -1,3 +1,4 @@
1
+ import type { Buffer } from 'buffer';
1
2
  import Fs from '@modern-js/utils/fs-extra';
2
3
  import type { Storage } from './storer/storage';
3
4
  export declare class FileReader {
@@ -14,32 +14,55 @@ export declare const CacheTime: {
14
14
  export type CacheStatus = 'hit' | 'stale' | 'miss';
15
15
  export interface CacheStatsInfo {
16
16
  status: CacheStatus;
17
- key: string | symbol;
17
+ key: string;
18
18
  params: any[];
19
19
  result: any;
20
+ /**
21
+ * Cache miss reason:
22
+ * 1: Caching is disabled for the current request
23
+ * 2: Item not found in cache
24
+ * 3: Item found in cache but has expired
25
+ * 4: Failed to parse data from cache
26
+ * 5: Execution error
27
+ */
28
+ reason?: number;
20
29
  }
21
- interface CacheOptions {
30
+ export interface Container {
31
+ get: (key: string) => Promise<any | undefined | null>;
32
+ set: (key: string, value: any, options?: {
33
+ ttl?: number;
34
+ }) => Promise<any>;
35
+ has: (key: string) => Promise<boolean>;
36
+ delete: (key: string) => Promise<boolean>;
37
+ clear: () => Promise<void>;
38
+ }
39
+ interface CacheOptions<T extends (...args: any[]) => any> {
22
40
  tag?: string | string[];
23
41
  maxAge?: number;
24
42
  revalidate?: number;
25
- getKey?: <Args extends any[]>(...args: Args) => string;
26
- customKey?: <Args extends any[]>(options: {
27
- params: Args;
28
- fn: (...args: Args) => any;
43
+ getKey?: (...args: Parameters<T>) => string;
44
+ customKey?: (options: {
45
+ params: Parameters<T>;
46
+ fn: T;
29
47
  generatedKey: string;
30
- }) => string | symbol;
48
+ }) => string;
31
49
  onCache?: (info: CacheStatsInfo) => void;
50
+ unstable_shouldCache?: (info: {
51
+ params: Parameters<T>;
52
+ result: Awaited<ReturnType<T>>;
53
+ }) => boolean | Promise<boolean>;
32
54
  }
33
55
  interface CacheConfig {
34
56
  maxSize?: number;
57
+ container?: Container;
35
58
  unstable_shouldDisable?: ({ request, }: {
36
59
  request: Request;
37
60
  }) => boolean | Promise<boolean>;
38
61
  }
39
62
  export declare function configureCache(config: CacheConfig): void;
40
63
  export declare function generateKey(args: unknown[]): string;
41
- export declare function cache<T extends (...args: any[]) => Promise<any>>(fn: T, options?: CacheOptions): T;
64
+ export declare function cache<T extends (...args: any[]) => Promise<any>>(fn: T, options?: CacheOptions<T>): T;
42
65
  export declare function withRequestCache<T extends (req: Request, ...args: any[]) => Promise<any>>(handler: T): T;
43
- export declare function revalidateTag(tag: string): void;
44
- export declare function clearStore(): void;
66
+ export declare function revalidateTag(tag: string): Promise<void>;
67
+ export declare function clearStore(): Promise<void>;
45
68
  export {};
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "modern",
16
16
  "modern.js"
17
17
  ],
18
- "version": "2.68.1",
18
+ "version": "2.68.3",
19
19
  "_comment": "Provide ESM and CJS exports, ESM is used by runtime package, for treeshaking",
20
20
  "exports": {
21
21
  "./router": {
@@ -145,8 +145,8 @@
145
145
  "lru-cache": "^10.4.3",
146
146
  "react-router-dom": "6.27.0",
147
147
  "serialize-javascript": "^6.0.0",
148
- "@modern-js/types": "2.68.1",
149
- "@modern-js/utils": "2.68.1"
148
+ "@modern-js/types": "2.68.3",
149
+ "@modern-js/utils": "2.68.3"
150
150
  },
151
151
  "peerDependencies": {
152
152
  "react": ">=17.0.0",
@@ -161,9 +161,11 @@
161
161
  }
162
162
  },
163
163
  "devDependencies": {
164
+ "@types/ioredis-mock": "^8.2.6",
164
165
  "@types/jest": "^29",
165
- "@types/node": "^14",
166
+ "@types/node": "^18",
166
167
  "@types/serialize-javascript": "^5.0.1",
168
+ "ioredis-mock": "^8.9.0",
167
169
  "jest": "^29",
168
170
  "react": "^18.3.1",
169
171
  "react-dom": "^18.3.1",