cache.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { LRUCache } from 'lru-cache';
  2. import { canUseCanonicalCacheAlias, isHtmlLikeContentType } from './network';
  3. import type { FetchResult } from './types';
  4. export const CACHE = new LRUCache<string, FetchResult>({
  5. maxSize: 50 * 1024 * 1024,
  6. ttl: 15 * 60 * 1000,
  7. sizeCalculation: (value: FetchResult) => {
  8. if ('binary' in value) return value.data?.byteLength ?? 1024;
  9. const rawContent =
  10. value.rawContent ?? value.html ?? value.markdown ?? value.text ?? '';
  11. return (
  12. Buffer.byteLength(rawContent) +
  13. Buffer.byteLength(value.html) +
  14. Buffer.byteLength(value.markdown) +
  15. Buffer.byteLength(value.text)
  16. );
  17. },
  18. });
  19. export function buildCacheKey(
  20. url: string,
  21. extractMain: boolean,
  22. preferLlmsTxt: 'auto' | 'always' | 'never',
  23. saveBinary: boolean,
  24. ) {
  25. const parsed = new URL(url);
  26. return JSON.stringify({
  27. url: parsed.toString(),
  28. extractMain,
  29. preferLlmsTxt,
  30. saveBinary,
  31. });
  32. }
  33. function cacheKeysFor(
  34. fetchResult: FetchResult,
  35. extractMain: boolean,
  36. preferLlmsTxt: 'auto' | 'always' | 'never',
  37. saveBinary: boolean,
  38. ) {
  39. const keys = new Set<string>();
  40. keys.add(
  41. buildCacheKey(
  42. fetchResult.requestedUrl,
  43. extractMain,
  44. preferLlmsTxt,
  45. saveBinary,
  46. ),
  47. );
  48. keys.add(
  49. buildCacheKey(fetchResult.finalUrl, extractMain, preferLlmsTxt, saveBinary),
  50. );
  51. if (
  52. fetchResult.canonicalUrl &&
  53. canUseCanonicalCacheAlias(fetchResult.finalUrl, fetchResult.canonicalUrl)
  54. ) {
  55. keys.add(
  56. buildCacheKey(
  57. fetchResult.canonicalUrl,
  58. extractMain,
  59. preferLlmsTxt,
  60. saveBinary,
  61. ),
  62. );
  63. }
  64. return [...keys];
  65. }
  66. export function cacheFetchResult(
  67. fetchResult: FetchResult,
  68. extractMain: boolean,
  69. preferLlmsTxt: 'auto' | 'always' | 'never',
  70. saveBinary: boolean,
  71. ) {
  72. for (const key of cacheKeysFor(
  73. fetchResult,
  74. extractMain,
  75. preferLlmsTxt,
  76. saveBinary,
  77. )) {
  78. CACHE.set(key, fetchResult);
  79. }
  80. }
  81. export function isInvalidLlmsResult(fetchResult: FetchResult | undefined) {
  82. if (!fetchResult || 'binary' in fetchResult) return false;
  83. if (!fetchResult.usedLlmsTxt || fetchResult.sourceKind !== 'llms_txt') {
  84. return false;
  85. }
  86. const finalPath = (() => {
  87. try {
  88. return new URL(fetchResult.finalUrl).pathname.toLowerCase();
  89. } catch {
  90. return '';
  91. }
  92. })();
  93. if (
  94. !(finalPath.endsWith('/llms.txt') || finalPath.endsWith('/llms-full.txt'))
  95. ) {
  96. return true;
  97. }
  98. if (isHtmlLikeContentType(fetchResult.contentType)) return true;
  99. if (/^\s*(<!doctype html|<html\b)/i.test(fetchResult.rawContent)) return true;
  100. if (
  101. /<title>\s*(log in|sign in|login)\b/i.test(fetchResult.rawContent) ||
  102. /\blog[ -]?in\b/i.test(fetchResult.finalUrl)
  103. ) {
  104. return true;
  105. }
  106. return false;
  107. }