fs-memo.ts 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import path from 'node:path';
  2. import { Cache } from './cache-filesystem';
  3. import type { CacheApplyOption } from './cache-filesystem';
  4. import { isCI } from 'ci-info';
  5. import { Typeson, set, map, typedArrays } from 'typeson-registry';
  6. import picocolors from 'picocolors';
  7. import { identity } from './misc';
  8. const typeson = new Typeson().register([
  9. typedArrays,
  10. set,
  11. map
  12. ]);
  13. const fsMemoCache = new Cache({ cachePath: path.resolve(__dirname, '../../.cache'), tableName: 'fs_memo_cache' });
  14. const TTL = isCI
  15. // We run CI daily, so 1.5 days TTL is enough to persist the cache across runs
  16. ? 1.5 * 86400 * 1000
  17. // We run locally less frequently, so we need to persist the cache for longer, 7 days
  18. : 7 * 86400 * 1000;
  19. type TypesonValue =
  20. | string
  21. | number
  22. | boolean
  23. | null
  24. | Set<any>
  25. | Map<any, any>
  26. | TypesonObject
  27. | TypesonArray;
  28. interface TypesonObject {
  29. [key: string]: TypesonValue
  30. }
  31. interface TypesonArray extends Array<TypesonValue> { }
  32. export type FsMemoCacheOptions<T> = CacheApplyOption<T, string> & {
  33. ttl?: undefined | never
  34. };
  35. export function cache<Args extends TypesonValue[], T>(
  36. fn: (...args: Args) => Promise<T>,
  37. opt: FsMemoCacheOptions<T>
  38. ): (...args: Args) => Promise<T> {
  39. // TODO if cb.toString() is long we should hash it
  40. const fixedKey = fn.toString();
  41. return async function cachedCb(...args: Args) {
  42. // Construct the complete cache key for this function invocation
  43. // typeson.stringify is still limited. For now we uses typescript to guard the args.
  44. const cacheKey = `${fixedKey}|${typeson.stringifySync(args)}`;
  45. const cacheName = fn.name || cacheKey;
  46. const { temporaryBypass, incrementTtlWhenHit } = opt;
  47. if (temporaryBypass) {
  48. return fn(...args);
  49. }
  50. const cached = fsMemoCache.get(cacheKey);
  51. if (cached == null) {
  52. console.log(picocolors.yellow('[cache] miss'), picocolors.gray(cacheName || cacheKey));
  53. const serializer = 'serializer' in opt ? opt.serializer : identity as any;
  54. const value = await fn(...args);
  55. fsMemoCache.set(cacheKey, serializer(value), TTL);
  56. return value;
  57. }
  58. console.log(picocolors.green('[cache] hit'), picocolors.gray(cacheName || cacheKey));
  59. if (incrementTtlWhenHit) {
  60. fsMemoCache.updateTtl(cacheKey, TTL);
  61. }
  62. const deserializer = 'deserializer' in opt ? opt.deserializer : identity as any;
  63. return deserializer(cached);
  64. };
  65. }