@nitpicker/crawler 0.11.0 → 0.13.0

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.
Files changed (387) hide show
  1. package/README.md +6 -4
  2. package/lib/archive/archive-accessor.d.ts +2 -2
  3. package/lib/archive/archive-accessor.js +2 -2
  4. package/lib/archive/archive-lock.d.ts +7 -0
  5. package/lib/archive/archive-lock.js +7 -0
  6. package/lib/archive/archive.d.ts +175 -13
  7. package/lib/archive/archive.js +198 -14
  8. package/lib/archive/cache/compute-archive-cache-key.d.ts +39 -0
  9. package/lib/archive/cache/compute-archive-cache-key.js +95 -0
  10. package/lib/archive/cache/extract-archive-to-cache.d.ts +43 -0
  11. package/lib/archive/cache/extract-archive-to-cache.js +309 -0
  12. package/lib/archive/cache/get-archive-cache-root.d.ts +20 -0
  13. package/lib/archive/cache/get-archive-cache-root.js +53 -0
  14. package/lib/archive/cache/is-archive-cache-disabled.d.ts +24 -0
  15. package/lib/archive/cache/is-archive-cache-disabled.js +34 -0
  16. package/lib/archive/cache/resolve-archive-cache-dir.d.ts +26 -0
  17. package/lib/archive/cache/resolve-archive-cache-dir.js +32 -0
  18. package/lib/archive/create-adjunct-tables.d.ts +43 -0
  19. package/lib/archive/create-adjunct-tables.js +213 -0
  20. package/lib/archive/create-entity-tables.d.ts +173 -0
  21. package/lib/archive/create-entity-tables.js +318 -0
  22. package/lib/archive/create-progress-reporter.d.ts +30 -0
  23. package/lib/archive/create-progress-reporter.js +38 -0
  24. package/lib/archive/create-ref-tables.d.ts +35 -0
  25. package/lib/archive/create-ref-tables.js +188 -0
  26. package/lib/archive/database.d.ts +130 -182
  27. package/lib/archive/database.js +524 -1777
  28. package/lib/archive/db-ops/_shared/clear-write-ref-caches.d.ts +27 -0
  29. package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +34 -0
  30. package/lib/archive/db-ops/_shared/create-write-ref-caches.d.ts +17 -0
  31. package/lib/archive/db-ops/_shared/create-write-ref-caches.js +26 -0
  32. package/lib/archive/db-ops/_shared/decode-json-ref.d.ts +17 -0
  33. package/lib/archive/db-ops/_shared/decode-json-ref.js +31 -0
  34. package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.d.ts +20 -0
  35. package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.js +53 -0
  36. package/lib/archive/db-ops/_shared/resolve-content-item-id.d.ts +61 -0
  37. package/lib/archive/db-ops/_shared/resolve-content-item-id.js +111 -0
  38. package/lib/archive/db-ops/_shared/resolve-url-or-blob.d.ts +23 -0
  39. package/lib/archive/db-ops/_shared/resolve-url-or-blob.js +29 -0
  40. package/lib/archive/db-ops/_shared/retry-setting.d.ts +16 -0
  41. package/lib/archive/db-ops/_shared/retry-setting.js +18 -0
  42. package/lib/archive/db-ops/_shared/safe-parse-json.d.ts +11 -0
  43. package/lib/archive/db-ops/_shared/safe-parse-json.js +18 -0
  44. package/lib/archive/db-ops/_shared/types.d.ts +53 -0
  45. package/lib/archive/db-ops/_shared/types.js +1 -0
  46. package/lib/archive/db-ops/_shared/upsert-blob-ref.d.ts +25 -0
  47. package/lib/archive/db-ops/_shared/upsert-blob-ref.js +48 -0
  48. package/lib/archive/db-ops/_shared/upsert-content-type-ref.d.ts +30 -0
  49. package/lib/archive/db-ops/_shared/upsert-content-type-ref.js +45 -0
  50. package/lib/archive/db-ops/_shared/upsert-json-ref.d.ts +22 -0
  51. package/lib/archive/db-ops/_shared/upsert-json-ref.js +41 -0
  52. package/lib/archive/db-ops/_shared/upsert-response-headers.d.ts +35 -0
  53. package/lib/archive/db-ops/_shared/upsert-response-headers.js +49 -0
  54. package/lib/archive/db-ops/_shared/upsert-url-ref.d.ts +39 -0
  55. package/lib/archive/db-ops/_shared/upsert-url-ref.js +62 -0
  56. package/lib/archive/db-ops/analysis/replace-analysis-violations.d.ts +28 -0
  57. package/lib/archive/db-ops/analysis/replace-analysis-violations.js +152 -0
  58. package/lib/archive/db-ops/anchors/get-anchors-on-page.d.ts +10 -0
  59. package/lib/archive/db-ops/anchors/get-anchors-on-page.js +21 -0
  60. package/lib/archive/db-ops/config/get-base-url.d.ts +8 -0
  61. package/lib/archive/db-ops/config/get-base-url.js +14 -0
  62. package/lib/archive/db-ops/config/get-config.d.ts +10 -0
  63. package/lib/archive/db-ops/config/get-config.js +27 -0
  64. package/lib/archive/db-ops/config/get-name.d.ts +8 -0
  65. package/lib/archive/db-ops/config/get-name.js +14 -0
  66. package/lib/archive/db-ops/config/info-column-allowlist.d.ts +7 -0
  67. package/lib/archive/db-ops/config/info-column-allowlist.js +26 -0
  68. package/lib/archive/db-ops/config/info-json-columns.d.ts +5 -0
  69. package/lib/archive/db-ops/config/info-json-columns.js +10 -0
  70. package/lib/archive/db-ops/config/set-config.d.ts +12 -0
  71. package/lib/archive/db-ops/config/set-config.js +21 -0
  72. package/lib/archive/db-ops/config/update-config.d.ts +17 -0
  73. package/lib/archive/db-ops/config/update-config.js +36 -0
  74. package/lib/archive/db-ops/errors/insert-crawl-error.d.ts +15 -0
  75. package/lib/archive/db-ops/errors/insert-crawl-error.js +21 -0
  76. package/lib/archive/db-ops/errors/insert-page-error.d.ts +21 -0
  77. package/lib/archive/db-ops/errors/insert-page-error.js +28 -0
  78. package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +22 -0
  79. package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +141 -0
  80. package/lib/archive/db-ops/html/get-html-of-page-by-id.d.ts +18 -0
  81. package/lib/archive/db-ops/html/get-html-of-page-by-id.js +29 -0
  82. package/lib/archive/db-ops/inventory/record-inventory-run.d.ts +21 -0
  83. package/lib/archive/db-ops/inventory/record-inventory-run.js +38 -0
  84. package/lib/archive/db-ops/lifecycle/checkpoint.d.ts +8 -0
  85. package/lib/archive/db-ops/lifecycle/checkpoint.js +9 -0
  86. package/lib/archive/db-ops/lifecycle/destroy.d.ts +6 -0
  87. package/lib/archive/db-ops/lifecycle/destroy.js +7 -0
  88. package/lib/archive/db-ops/lifecycle/init.d.ts +22 -0
  89. package/lib/archive/db-ops/lifecycle/init.js +42 -0
  90. package/lib/archive/db-ops/meta/get-jsonld-of-page.d.ts +13 -0
  91. package/lib/archive/db-ops/meta/get-jsonld-of-page.js +27 -0
  92. package/lib/archive/db-ops/meta/get-tags-of-page.d.ts +12 -0
  93. package/lib/archive/db-ops/meta/get-tags-of-page.js +28 -0
  94. package/lib/archive/db-ops/pages/order/set-url-order.d.ts +8 -0
  95. package/lib/archive/db-ops/pages/order/set-url-order.js +32 -0
  96. package/lib/archive/db-ops/pages/read/build-page-query.d.ts +18 -0
  97. package/lib/archive/db-ops/pages/read/build-page-query.js +40 -0
  98. package/lib/archive/db-ops/pages/read/get-crawling-state.d.ts +70 -0
  99. package/lib/archive/db-ops/pages/read/get-crawling-state.js +98 -0
  100. package/lib/archive/db-ops/pages/read/get-existing-page-urls.d.ts +15 -0
  101. package/lib/archive/db-ops/pages/read/get-existing-page-urls.js +30 -0
  102. package/lib/archive/db-ops/pages/read/get-page-count.d.ts +12 -0
  103. package/lib/archive/db-ops/pages/read/get-page-count.js +21 -0
  104. package/lib/archive/db-ops/pages/read/get-page-source-by-url.d.ts +24 -0
  105. package/lib/archive/db-ops/pages/read/get-page-source-by-url.js +28 -0
  106. package/lib/archive/db-ops/pages/read/get-pages-with-rels.d.ts +38 -0
  107. package/lib/archive/db-ops/pages/read/get-pages-with-rels.js +107 -0
  108. package/lib/archive/db-ops/pages/read/get-pages.d.ts +11 -0
  109. package/lib/archive/db-ops/pages/read/get-pages.js +51 -0
  110. package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.d.ts +18 -0
  111. package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.js +25 -0
  112. package/lib/archive/db-ops/pages/read/reconstruct-page-rows.d.ts +31 -0
  113. package/lib/archive/db-ops/pages/read/reconstruct-page-rows.js +32 -0
  114. package/lib/archive/db-ops/pages/reset/repromote-external-pages.d.ts +24 -0
  115. package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +93 -0
  116. package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +47 -0
  117. package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +124 -0
  118. package/lib/archive/db-ops/pages/write/insert-inventory-seeds.d.ts +37 -0
  119. package/lib/archive/db-ops/pages/write/insert-inventory-seeds.js +72 -0
  120. package/lib/archive/db-ops/pages/write/insert-jsonld.d.ts +17 -0
  121. package/lib/archive/db-ops/pages/write/insert-jsonld.js +49 -0
  122. package/lib/archive/db-ops/pages/write/insert-page.d.ts +36 -0
  123. package/lib/archive/db-ops/pages/write/insert-page.js +208 -0
  124. package/lib/archive/db-ops/pages/write/insert-tags.d.ts +16 -0
  125. package/lib/archive/db-ops/pages/write/insert-tags.js +34 -0
  126. package/lib/archive/db-ops/pages/write/link-redirect-sources.d.ts +36 -0
  127. package/lib/archive/db-ops/pages/write/link-redirect-sources.js +93 -0
  128. package/lib/archive/db-ops/pages/write/record-redirect.d.ts +35 -0
  129. package/lib/archive/db-ops/pages/write/record-redirect.js +100 -0
  130. package/lib/archive/db-ops/pages/write/set-skipped-page.d.ts +13 -0
  131. package/lib/archive/db-ops/pages/write/set-skipped-page.js +22 -0
  132. package/lib/archive/db-ops/pages/write/update-page.d.ts +29 -0
  133. package/lib/archive/db-ops/pages/write/update-page.js +334 -0
  134. package/lib/archive/db-ops/pages/write/write-page-html-blob.d.ts +19 -0
  135. package/lib/archive/db-ops/pages/write/write-page-html-blob.js +41 -0
  136. package/lib/archive/db-ops/referrers/get-redirects-for-pages.d.ts +9 -0
  137. package/lib/archive/db-ops/referrers/get-redirects-for-pages.js +15 -0
  138. package/lib/archive/db-ops/referrers/get-referrers-of-page.d.ts +17 -0
  139. package/lib/archive/db-ops/referrers/get-referrers-of-page.js +32 -0
  140. package/lib/archive/db-ops/referrers/get-referrers-of-resource.d.ts +8 -0
  141. package/lib/archive/db-ops/referrers/get-referrers-of-resource.js +15 -0
  142. package/lib/archive/db-ops/resources/build-resource-query.d.ts +25 -0
  143. package/lib/archive/db-ops/resources/build-resource-query.js +29 -0
  144. package/lib/archive/db-ops/resources/get-existing-resource-urls.d.ts +9 -0
  145. package/lib/archive/db-ops/resources/get-existing-resource-urls.js +24 -0
  146. package/lib/archive/db-ops/resources/get-resource-by-url.d.ts +13 -0
  147. package/lib/archive/db-ops/resources/get-resource-by-url.js +22 -0
  148. package/lib/archive/db-ops/resources/get-resource-url-list.d.ts +9 -0
  149. package/lib/archive/db-ops/resources/get-resource-url-list.js +13 -0
  150. package/lib/archive/db-ops/resources/get-resources.d.ts +8 -0
  151. package/lib/archive/db-ops/resources/get-resources.js +11 -0
  152. package/lib/archive/db-ops/resources/insert-inventory-resources.d.ts +24 -0
  153. package/lib/archive/db-ops/resources/insert-inventory-resources.js +64 -0
  154. package/lib/archive/db-ops/resources/insert-resource-referrers.d.ts +15 -0
  155. package/lib/archive/db-ops/resources/insert-resource-referrers.js +54 -0
  156. package/lib/archive/db-ops/resources/insert-resource.d.ts +34 -0
  157. package/lib/archive/db-ops/resources/insert-resource.js +73 -0
  158. package/lib/archive/db-ops/resources/reconstruct-resource-rows.d.ts +26 -0
  159. package/lib/archive/db-ops/resources/reconstruct-resource-rows.js +30 -0
  160. package/lib/archive/decode-html-blob.d.ts +18 -0
  161. package/lib/archive/decode-html-blob.js +31 -0
  162. package/lib/archive/derive-lineage-from-parent.d.ts +37 -0
  163. package/lib/archive/derive-lineage-from-parent.js +42 -0
  164. package/lib/archive/drop-legacy-tables.d.ts +45 -0
  165. package/lib/archive/drop-legacy-tables.js +56 -0
  166. package/lib/archive/filesystem/rename.js +1 -1
  167. package/lib/archive/get-failed-page-messages.d.ts +44 -0
  168. package/lib/archive/get-failed-page-messages.js +132 -0
  169. package/lib/archive/init-schema.d.ts +35 -39
  170. package/lib/archive/init-schema.js +111 -320
  171. package/lib/archive/is-inventory-source.d.ts +21 -0
  172. package/lib/archive/is-inventory-source.js +22 -0
  173. package/lib/archive/limited-page-ids.d.ts +2 -1
  174. package/lib/archive/limited-page-ids.js +5 -4
  175. package/lib/archive/meta/assert-compatible-version.d.ts +24 -3
  176. package/lib/archive/meta/assert-compatible-version.js +24 -3
  177. package/lib/archive/meta/types.d.ts +87 -1
  178. package/lib/archive/meta/types.js +34 -2
  179. package/lib/archive/migrate-entity-tables.d.ts +45 -0
  180. package/lib/archive/migrate-entity-tables.js +56 -0
  181. package/lib/archive/migrate-ref-tables.d.ts +25 -0
  182. package/lib/archive/migrate-ref-tables.js +38 -0
  183. package/lib/archive/page-meta-column-maps.d.ts +32 -0
  184. package/lib/archive/page-meta-column-maps.js +43 -0
  185. package/lib/archive/page.d.ts +6 -6
  186. package/lib/archive/page.js +5 -5
  187. package/lib/archive/peek-archive-lock.d.ts +2 -2
  188. package/lib/archive/peek-archive-lock.js +2 -2
  189. package/lib/archive/populate-entity-tables/collapse-anchor-rows.d.ts +41 -0
  190. package/lib/archive/populate-entity-tables/collapse-anchor-rows.js +87 -0
  191. package/lib/archive/populate-entity-tables/derive-dom-path.d.ts +35 -0
  192. package/lib/archive/populate-entity-tables/derive-dom-path.js +72 -0
  193. package/lib/archive/populate-entity-tables/is-blob-ref-value.d.ts +16 -0
  194. package/lib/archive/populate-entity-tables/is-blob-ref-value.js +19 -0
  195. package/lib/archive/populate-entity-tables/match-images-to-dom-paths.d.ts +66 -0
  196. package/lib/archive/populate-entity-tables/match-images-to-dom-paths.js +96 -0
  197. package/lib/archive/populate-entity-tables/populate-anchor-edges.d.ts +33 -0
  198. package/lib/archive/populate-entity-tables/populate-anchor-edges.js +153 -0
  199. package/lib/archive/populate-entity-tables/populate-content-items.d.ts +40 -0
  200. package/lib/archive/populate-entity-tables/populate-content-items.js +141 -0
  201. package/lib/archive/populate-entity-tables/populate-entities.d.ts +81 -0
  202. package/lib/archive/populate-entity-tables/populate-entities.js +111 -0
  203. package/lib/archive/populate-entity-tables/populate-image-items.d.ts +91 -0
  204. package/lib/archive/populate-entity-tables/populate-image-items.js +223 -0
  205. package/lib/archive/populate-entity-tables/populate-page-meta.d.ts +33 -0
  206. package/lib/archive/populate-entity-tables/populate-page-meta.js +267 -0
  207. package/lib/archive/populate-entity-tables/populate-resource-items.d.ts +22 -0
  208. package/lib/archive/populate-entity-tables/populate-resource-items.js +114 -0
  209. package/lib/archive/populate-entity-tables/populate-resource-ref-edges.d.ts +31 -0
  210. package/lib/archive/populate-entity-tables/populate-resource-ref-edges.js +33 -0
  211. package/lib/archive/populate-entity-tables/resolve-blob-refs.d.ts +31 -0
  212. package/lib/archive/populate-entity-tables/resolve-blob-refs.js +100 -0
  213. package/lib/archive/populate-entity-tables/resolve-content-type-refs.d.ts +22 -0
  214. package/lib/archive/populate-entity-tables/resolve-content-type-refs.js +27 -0
  215. package/lib/archive/populate-entity-tables/resolve-header-sets.d.ts +49 -0
  216. package/lib/archive/populate-entity-tables/resolve-header-sets.js +122 -0
  217. package/lib/archive/populate-entity-tables/resolve-json-refs.d.ts +25 -0
  218. package/lib/archive/populate-entity-tables/resolve-json-refs.js +67 -0
  219. package/lib/archive/populate-entity-tables/resolve-text-refs.d.ts +30 -0
  220. package/lib/archive/populate-entity-tables/resolve-text-refs.js +61 -0
  221. package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.d.ts +21 -0
  222. package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.js +27 -0
  223. package/lib/archive/populate-entity-tables/resolve-url-refs.d.ts +33 -0
  224. package/lib/archive/populate-entity-tables/resolve-url-refs.js +60 -0
  225. package/lib/archive/populate-entity-tables/test-utils/count-rows.d.ts +17 -0
  226. package/lib/archive/populate-entity-tables/test-utils/count-rows.js +20 -0
  227. package/lib/archive/populate-entity-tables/test-utils/seed-content-items.d.ts +25 -0
  228. package/lib/archive/populate-entity-tables/test-utils/seed-content-items.js +42 -0
  229. package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.d.ts +23 -0
  230. package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.js +178 -0
  231. package/lib/archive/populate-entity-tables/types.d.ts +157 -0
  232. package/lib/archive/populate-entity-tables/types.js +12 -0
  233. package/lib/archive/populate-entity-tables/upsert-text-refs.d.ts +38 -0
  234. package/lib/archive/populate-entity-tables/upsert-text-refs.js +78 -0
  235. package/lib/archive/populate-ref-tables/classify-content-type.d.ts +16 -0
  236. package/lib/archive/populate-ref-tables/classify-content-type.js +52 -0
  237. package/lib/archive/populate-ref-tables/compute-content-hash.d.ts +22 -0
  238. package/lib/archive/populate-ref-tables/compute-content-hash.js +26 -0
  239. package/lib/archive/populate-ref-tables/compute-header-flags.d.ts +16 -0
  240. package/lib/archive/populate-ref-tables/compute-header-flags.js +70 -0
  241. package/lib/archive/populate-ref-tables/content-type-rules.d.ts +38 -0
  242. package/lib/archive/populate-ref-tables/content-type-rules.js +133 -0
  243. package/lib/archive/populate-ref-tables/create-header-table-caches.d.ts +25 -0
  244. package/lib/archive/populate-ref-tables/create-header-table-caches.js +49 -0
  245. package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.d.ts +15 -0
  246. package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.js +15 -0
  247. package/lib/archive/populate-ref-tables/decode-data-uri.d.ts +21 -0
  248. package/lib/archive/populate-ref-tables/decode-data-uri.js +126 -0
  249. package/lib/archive/populate-ref-tables/decompose-header-set.d.ts +29 -0
  250. package/lib/archive/populate-ref-tables/decompose-header-set.js +157 -0
  251. package/lib/archive/populate-ref-tables/decompose-url.d.ts +25 -0
  252. package/lib/archive/populate-ref-tables/decompose-url.js +70 -0
  253. package/lib/archive/populate-ref-tables/header-stability.d.ts +19 -0
  254. package/lib/archive/populate-ref-tables/header-stability.js +22 -0
  255. package/lib/archive/populate-ref-tables/header-value-cache-key.d.ts +17 -0
  256. package/lib/archive/populate-ref-tables/header-value-cache-key.js +19 -0
  257. package/lib/archive/populate-ref-tables/normalize-mime.d.ts +24 -0
  258. package/lib/archive/populate-ref-tables/normalize-mime.js +36 -0
  259. package/lib/archive/populate-ref-tables/populate-blob-refs.d.ts +38 -0
  260. package/lib/archive/populate-ref-tables/populate-blob-refs.js +134 -0
  261. package/lib/archive/populate-ref-tables/populate-content-type-refs.d.ts +27 -0
  262. package/lib/archive/populate-ref-tables/populate-content-type-refs.js +70 -0
  263. package/lib/archive/populate-ref-tables/populate-header-tables.d.ts +35 -0
  264. package/lib/archive/populate-ref-tables/populate-header-tables.js +80 -0
  265. package/lib/archive/populate-ref-tables/populate-json-refs.d.ts +29 -0
  266. package/lib/archive/populate-ref-tables/populate-json-refs.js +101 -0
  267. package/lib/archive/populate-ref-tables/populate-refs.d.ts +51 -0
  268. package/lib/archive/populate-ref-tables/populate-refs.js +62 -0
  269. package/lib/archive/populate-ref-tables/populate-text-refs.d.ts +32 -0
  270. package/lib/archive/populate-ref-tables/populate-text-refs.js +133 -0
  271. package/lib/archive/populate-ref-tables/populate-url-refs.d.ts +28 -0
  272. package/lib/archive/populate-ref-tables/populate-url-refs.js +148 -0
  273. package/lib/archive/populate-ref-tables/test-utils/count-rows.d.ts +15 -0
  274. package/lib/archive/populate-ref-tables/test-utils/count-rows.js +17 -0
  275. package/lib/archive/populate-ref-tables/types.d.ts +197 -0
  276. package/lib/archive/populate-ref-tables/types.js +7 -0
  277. package/lib/archive/populate-ref-tables/upsert-one-header-set.d.ts +34 -0
  278. package/lib/archive/populate-ref-tables/upsert-one-header-set.js +208 -0
  279. package/lib/archive/populate-ref-tables/volatile-header-names.d.ts +20 -0
  280. package/lib/archive/populate-ref-tables/volatile-header-names.js +33 -0
  281. package/lib/archive/redirect-table.d.ts +4 -2
  282. package/lib/archive/redirect-table.js +15 -10
  283. package/lib/archive/resolve-redirect-chain.d.ts +3 -3
  284. package/lib/archive/resolve-redirect-chain.js +2 -2
  285. package/lib/archive/resource.d.ts +1 -1
  286. package/lib/archive/retarget-legacy-fk-tables.d.ts +47 -0
  287. package/lib/archive/retarget-legacy-fk-tables.js +107 -0
  288. package/lib/archive/test-utils/fk-parent-tables.d.ts +15 -0
  289. package/lib/archive/test-utils/fk-parent-tables.js +19 -0
  290. package/lib/archive/test-utils/seed-content-item.d.ts +35 -0
  291. package/lib/archive/test-utils/seed-content-item.js +42 -0
  292. package/lib/archive/test-utils/setup-legacy-fk-db.d.ts +33 -0
  293. package/lib/archive/test-utils/setup-legacy-fk-db.js +270 -0
  294. package/lib/archive/types.d.ts +151 -15
  295. package/lib/archive/verify-migration/capture-rejection.d.ts +24 -0
  296. package/lib/archive/verify-migration/capture-rejection.js +31 -0
  297. package/lib/archive/verify-migration/check-anchor-edges-count.d.ts +34 -0
  298. package/lib/archive/verify-migration/check-anchor-edges-count.js +72 -0
  299. package/lib/archive/verify-migration/check-anchor-edges-sum.d.ts +13 -0
  300. package/lib/archive/verify-migration/check-anchor-edges-sum.js +27 -0
  301. package/lib/archive/verify-migration/check-content-items-count.d.ts +16 -0
  302. package/lib/archive/verify-migration/check-content-items-count.js +30 -0
  303. package/lib/archive/verify-migration/check-content-type-preservation.d.ts +22 -0
  304. package/lib/archive/verify-migration/check-content-type-preservation.js +40 -0
  305. package/lib/archive/verify-migration/check-foreign-key-integrity.d.ts +31 -0
  306. package/lib/archive/verify-migration/check-foreign-key-integrity.js +47 -0
  307. package/lib/archive/verify-migration/check-image-items-count.d.ts +12 -0
  308. package/lib/archive/verify-migration/check-image-items-count.js +26 -0
  309. package/lib/archive/verify-migration/check-page-meta-count.d.ts +15 -0
  310. package/lib/archive/verify-migration/check-page-meta-count.js +31 -0
  311. package/lib/archive/verify-migration/check-reader-parity.d.ts +23 -0
  312. package/lib/archive/verify-migration/check-reader-parity.js +211 -0
  313. package/lib/archive/verify-migration/check-resource-items-count.d.ts +17 -0
  314. package/lib/archive/verify-migration/check-resource-items-count.js +33 -0
  315. package/lib/archive/verify-migration/check-url-round-trip.d.ts +43 -0
  316. package/lib/archive/verify-migration/check-url-round-trip.js +112 -0
  317. package/lib/archive/verify-migration/types.d.ts +70 -0
  318. package/lib/archive/verify-migration/types.js +63 -0
  319. package/lib/archive/verify-migration/verify-migration.d.ts +41 -0
  320. package/lib/archive/verify-migration/verify-migration.js +120 -0
  321. package/lib/classify-error-kind.d.ts +19 -0
  322. package/lib/classify-error-kind.js +122 -0
  323. package/lib/crawler/build-js-redirect-edge.d.ts +68 -0
  324. package/lib/crawler/build-js-redirect-edge.js +57 -0
  325. package/lib/crawler/build-redirect-event.d.ts +24 -0
  326. package/lib/crawler/build-redirect-event.js +28 -0
  327. package/lib/crawler/capture-image-dom-paths.d.ts +33 -0
  328. package/lib/crawler/capture-image-dom-paths.js +39 -0
  329. package/lib/crawler/clear-dns-burned-host-cache.d.ts +6 -0
  330. package/lib/crawler/clear-dns-burned-host-cache.js +11 -0
  331. package/lib/crawler/collect-image-dom-paths.d.ts +23 -0
  332. package/lib/crawler/collect-image-dom-paths.js +64 -0
  333. package/lib/crawler/crawler.d.ts +22 -1
  334. package/lib/crawler/crawler.js +679 -117
  335. package/lib/crawler/derive-js-redirect-target.d.ts +68 -0
  336. package/lib/crawler/derive-js-redirect-target.js +129 -0
  337. package/lib/crawler/derive-resource-source.d.ts +25 -15
  338. package/lib/crawler/derive-resource-source.js +28 -17
  339. package/lib/crawler/dns-burned-host-cache.d.ts +26 -0
  340. package/lib/crawler/dns-burned-host-cache.js +25 -0
  341. package/lib/crawler/dns-burned-host-short-circuit-counter.d.ts +13 -0
  342. package/lib/crawler/dns-burned-host-short-circuit-counter.js +11 -0
  343. package/lib/crawler/fetch-destination.d.ts +12 -4
  344. package/lib/crawler/fetch-destination.js +94 -16
  345. package/lib/crawler/inject-scope-auth.d.ts +1 -1
  346. package/lib/crawler/inject-scope-auth.js +1 -1
  347. package/lib/crawler/is-js-redirect-error-shape.d.ts +40 -0
  348. package/lib/crawler/is-js-redirect-error-shape.js +53 -0
  349. package/lib/crawler/is-puppeteer-fallback-candidate.d.ts +16 -0
  350. package/lib/crawler/is-puppeteer-fallback-candidate.js +63 -0
  351. package/lib/crawler/link-list.d.ts +21 -1
  352. package/lib/crawler/link-list.js +23 -3
  353. package/lib/crawler/normalize-content-type.d.ts +1 -1
  354. package/lib/crawler/normalize-content-type.js +1 -1
  355. package/lib/crawler/plan-sub-resource-emits.d.ts +63 -0
  356. package/lib/crawler/plan-sub-resource-emits.js +44 -0
  357. package/lib/crawler/preload-short-circuit-error.d.ts +22 -0
  358. package/lib/crawler/preload-short-circuit-error.js +25 -0
  359. package/lib/crawler/should-burn-host.d.ts +78 -0
  360. package/lib/crawler/should-burn-host.js +61 -0
  361. package/lib/crawler/should-get-fallback-on-head-failure.d.ts +38 -0
  362. package/lib/crawler/should-get-fallback-on-head-failure.js +46 -0
  363. package/lib/crawler/types.d.ts +108 -1
  364. package/lib/crawler-orchestrator.d.ts +22 -3
  365. package/lib/crawler-orchestrator.js +312 -73
  366. package/lib/crawler.d.ts +15 -2
  367. package/lib/crawler.js +24 -1
  368. package/lib/permanent-error-kinds.d.ts +43 -0
  369. package/lib/permanent-error-kinds.js +48 -0
  370. package/lib/types.d.ts +84 -0
  371. package/lib/utils/compute-file-sha256.d.ts +24 -0
  372. package/lib/utils/compute-file-sha256.js +56 -0
  373. package/lib/utils/error/emit-error-with-retry.d.ts +40 -0
  374. package/lib/utils/error/emit-error-with-retry.js +44 -0
  375. package/lib/utils/error/emit-error.d.ts +39 -0
  376. package/lib/utils/error/emit-error.js +41 -0
  377. package/package.json +12 -12
  378. package/lib/archive/migrate-crawl-errors.d.ts +0 -20
  379. package/lib/archive/migrate-crawl-errors.js +0 -38
  380. package/lib/archive/migrate-html-blob-tables.d.ts +0 -24
  381. package/lib/archive/migrate-html-blob-tables.js +0 -53
  382. package/lib/archive/migrate-page-errors.d.ts +0 -16
  383. package/lib/archive/migrate-page-errors.js +0 -35
  384. package/lib/archive/migrate-pages-resources-source.d.ts +0 -16
  385. package/lib/archive/migrate-pages-resources-source.js +0 -46
  386. package/lib/utils/error/error-emitter.d.ts +0 -18
  387. package/lib/utils/error/error-emitter.js +0 -29
@@ -0,0 +1,157 @@
1
+ import { computeContentHash } from './compute-content-hash.js';
2
+ import { isVolatileHeader } from './header-stability.js';
3
+ /**
4
+ * Parses one raw `responseHeaders` JSON string and produces the derived
5
+ * data shape required to insert into `header_sets`, `header_set_entries`,
6
+ * and `header_flags`.
7
+ *
8
+ * Behaviour:
9
+ *
10
+ * - `null` / `""` / `"null"` / `"{}"` / non-object JSON → returns `null`.
11
+ * The caller sets `header_set_id = null` on the referring row.
12
+ * - Multiple values per header name (JSON arrays, e.g.
13
+ * `{ "set-cookie": ["a=1", "b=2"] }`) become multiple
14
+ * {@link HeaderEntry} rows with `occurrence` ordinals 1, 2, ... The
15
+ * composite PK on `header_set_entries` `(header_set_id, name_id,
16
+ * occurrence)` guarantees no truncation.
17
+ * - Names are lower-cased. Values are trimmed of whitespace only in the
18
+ * sorted-hash construction; the raw value is preserved verbatim in
19
+ * the emitted `HeaderEntry.value` (and hence in `header_value_refs`).
20
+ * Trimming inside hash construction lets `content-type: text/html`
21
+ * and `content-type: text/html ` dedup to one stable set.
22
+ * - Sort order for hashing is `(name, occurrence)` in binary form —
23
+ * deterministic regardless of the input JSON's insertion order or
24
+ * how the JSON serializer arranged keys.
25
+ * @param rawJson - The raw JSON string exactly as stored in
26
+ * `pages.responseHeaders` / `resources.responseHeaders`.
27
+ * @returns Decomposed shape, or `null` when the row has no meaningful
28
+ * header set.
29
+ */
30
+ export function decomposeHeaderSet(rawJson) {
31
+ if (rawJson == null || rawJson === '' || rawJson === 'null' || rawJson === '{}') {
32
+ return null;
33
+ }
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(rawJson);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
42
+ return null;
43
+ }
44
+ const entries = [];
45
+ // `occurrence` runs per (lower-cased name) across ALL sources for that
46
+ // name, so duplicate JSON keys that differ only in case
47
+ // (`{"Cookie":"a=1", "cookie":"b=2"}` — non-conforming but legal JSON,
48
+ // and the crawler stores whatever puppeteer hands it) don't collide on
49
+ // (name_id, occurrence). Without this counter the second entry would
50
+ // land at occurrence=1 too, violating the `header_set_entries` PK and
51
+ // silently getting dropped by INSERT OR IGNORE while `entry_count`
52
+ // still counted it.
53
+ const occurrenceByName = new Map();
54
+ for (const [rawName, rawValue] of Object.entries(parsed)) {
55
+ const name = rawName.toLowerCase();
56
+ const volatile = isVolatileHeader(name);
57
+ const nextOccurrence = () => {
58
+ const next = (occurrenceByName.get(name) ?? 0) + 1;
59
+ occurrenceByName.set(name, next);
60
+ return next;
61
+ };
62
+ if (Array.isArray(rawValue)) {
63
+ for (const one of rawValue) {
64
+ if (typeof one !== 'string') {
65
+ continue;
66
+ }
67
+ entries.push({
68
+ name,
69
+ value: one,
70
+ occurrence: nextOccurrence(),
71
+ isVolatile: volatile,
72
+ });
73
+ }
74
+ continue;
75
+ }
76
+ if (typeof rawValue !== 'string') {
77
+ // parseResponseHeaders's stored shape is `Record<string, string |
78
+ // string[] | undefined>`, but hand-edited or non-conforming
79
+ // archives could smuggle in a nested object / number / boolean
80
+ // — coercing via `String()` would produce "[object Object]" and
81
+ // pollute `header_value_refs` with a garbage dictionary entry.
82
+ // Skipping is the conservative choice; the source row's raw
83
+ // JSON is still preserved via `raw_json_hash`.
84
+ continue;
85
+ }
86
+ entries.push({
87
+ name,
88
+ value: rawValue,
89
+ occurrence: nextOccurrence(),
90
+ isVolatile: volatile,
91
+ });
92
+ }
93
+ if (entries.length === 0) {
94
+ return null;
95
+ }
96
+ entries.sort((a, b) => {
97
+ if (a.name < b.name)
98
+ return -1;
99
+ if (a.name > b.name)
100
+ return 1;
101
+ return a.occurrence - b.occurrence;
102
+ });
103
+ // Canonicalize every entry exactly once, then partition the strings.
104
+ // Canonicalizing per hash pass instead (once in the all-entries pass,
105
+ // again in the stable / volatile passes) would serialize each entry
106
+ // twice, which becomes measurable on large archives where a single
107
+ // response can carry a couple of dozen entries.
108
+ const canonicalStrings = entries.map(canonicalize);
109
+ const stableCanonical = [];
110
+ const volatileCanonical = [];
111
+ let stableEntryCount = 0;
112
+ for (const [i, entry] of entries.entries()) {
113
+ if (entry.isVolatile) {
114
+ volatileCanonical.push(canonicalStrings[i]);
115
+ }
116
+ else {
117
+ stableCanonical.push(canonicalStrings[i]);
118
+ stableEntryCount += 1;
119
+ }
120
+ }
121
+ const rawJsonHash = computeContentHash(rawJson);
122
+ const rawHash = computeContentHash(canonicalStrings.join('\n'));
123
+ // A response with only volatile headers (tracker endpoints that return
124
+ // just `Date` / `Set-Cookie` / `Age`) hashes to `computeContentHash('')`
125
+ // — the canonical "empty stable profile" hash. All all-volatile
126
+ // responses cluster under this sentinel, which is the intended
127
+ // semantic (same empty stable profile = same cluster), and 0.13's
128
+ // DDL declares `stable_hash BLOB NOT NULL` so a `null` sentinel is not
129
+ // available even if we wanted one. Consumers that need to distinguish
130
+ // "empty stable set" from "populated stable set" should key off
131
+ // `stable_entry_count === 0` on the same row, not off the hash.
132
+ const stableHash = computeContentHash(stableCanonical.join('\n'));
133
+ const volatileHash = volatileCanonical.length === 0
134
+ ? null
135
+ : computeContentHash(volatileCanonical.join('\n'));
136
+ return {
137
+ rawJsonHash,
138
+ rawHash,
139
+ stableHash,
140
+ volatileHash,
141
+ entries,
142
+ entryCount: entries.length,
143
+ stableEntryCount,
144
+ };
145
+ }
146
+ /**
147
+ * Serializes one entry into the canonical `name/occurrence=value` form
148
+ * used inside the hash construction. The trailing occurrence keeps
149
+ * multi-value headers distinguishable while still letting single-value
150
+ * ones dedup naturally. Values are trimmed so surrounding whitespace
151
+ * does not fork the dedup key.
152
+ * @param entry - One decomposed header entry.
153
+ * @returns Canonical string joined into the hash pre-image.
154
+ */
155
+ function canonicalize(entry) {
156
+ return `${entry.name}/${entry.occurrence}=${entry.value.trim()}`;
157
+ }
@@ -0,0 +1,25 @@
1
+ import type { DecomposedUrl } from './types.js';
2
+ /**
3
+ * Extracts `scheme` / `host` / `port` / `path` / `query_hash` / `fragment`
4
+ * columns from a URL string for `url_refs` population.
5
+ *
6
+ * Parsing is done via WHATWG `new URL(...)`. URLs that fail to parse (e.g.
7
+ * a malformed href scraped from the wild) still round-trip through
8
+ * `url_refs` — the natural key is the raw `url` string — but every
9
+ * decomposed column becomes `null` so filters that JOIN on `host` or
10
+ * `scheme` cleanly skip malformed rows instead of falsely matching.
11
+ *
12
+ * `port` is only populated when explicit in the URL. `new URL(...)`
13
+ * normalises the scheme's default port away (`https://example.com:443/` →
14
+ * `url.port === ''`), so we never synthesise a default. Two rows differing
15
+ * only by explicit vs implicit default port must not deduplicate anyway —
16
+ * `url_refs.url` (the raw string) is the natural key, not the decomposed
17
+ * columns.
18
+ *
19
+ * `query_hash` is the 32-byte content hash of the query string with the
20
+ * leading `?` stripped. Storing the raw query would defeat dedup on
21
+ * tracker URLs whose per-request keys explode dictionary size.
22
+ * @param url - Raw URL string (may be any WHATWG-parseable form, or malformed).
23
+ * @returns Every column that goes into `url_refs` alongside the raw URL.
24
+ */
25
+ export declare function decomposeUrl(url: string): DecomposedUrl;
@@ -0,0 +1,70 @@
1
+ import { computeContentHash } from './compute-content-hash.js';
2
+ /**
3
+ * URL schemes whose "pathname" is really an in-band opaque payload
4
+ * (base64-encoded image bytes, javascript source, blob URL fragment,
5
+ * etc.) rather than a routing key. For these schemes we leave
6
+ * `DecomposedUrl.path` as `null` so the indexed `url_refs.path` column
7
+ * does not balloon with per-URL opaque tails and defeat the dedup goal.
8
+ */
9
+ const OPAQUE_PATH_SCHEMES = new Set([
10
+ 'data',
11
+ 'blob',
12
+ 'javascript',
13
+ 'about',
14
+ ]);
15
+ /**
16
+ * Extracts `scheme` / `host` / `port` / `path` / `query_hash` / `fragment`
17
+ * columns from a URL string for `url_refs` population.
18
+ *
19
+ * Parsing is done via WHATWG `new URL(...)`. URLs that fail to parse (e.g.
20
+ * a malformed href scraped from the wild) still round-trip through
21
+ * `url_refs` — the natural key is the raw `url` string — but every
22
+ * decomposed column becomes `null` so filters that JOIN on `host` or
23
+ * `scheme` cleanly skip malformed rows instead of falsely matching.
24
+ *
25
+ * `port` is only populated when explicit in the URL. `new URL(...)`
26
+ * normalises the scheme's default port away (`https://example.com:443/` →
27
+ * `url.port === ''`), so we never synthesise a default. Two rows differing
28
+ * only by explicit vs implicit default port must not deduplicate anyway —
29
+ * `url_refs.url` (the raw string) is the natural key, not the decomposed
30
+ * columns.
31
+ *
32
+ * `query_hash` is the 32-byte content hash of the query string with the
33
+ * leading `?` stripped. Storing the raw query would defeat dedup on
34
+ * tracker URLs whose per-request keys explode dictionary size.
35
+ * @param url - Raw URL string (may be any WHATWG-parseable form, or malformed).
36
+ * @returns Every column that goes into `url_refs` alongside the raw URL.
37
+ */
38
+ export function decomposeUrl(url) {
39
+ let parsed;
40
+ try {
41
+ parsed = new URL(url);
42
+ }
43
+ catch {
44
+ return {
45
+ scheme: null,
46
+ host: null,
47
+ port: null,
48
+ path: null,
49
+ query_hash: null,
50
+ fragment: null,
51
+ };
52
+ }
53
+ const scheme = parsed.protocol.slice(0, -1) || null;
54
+ const host = parsed.hostname === '' ? null : parsed.hostname.toLowerCase();
55
+ const port = parsed.port === '' ? null : Number.parseInt(parsed.port, 10);
56
+ const rawSearch = parsed.search.startsWith('?')
57
+ ? parsed.search.slice(1)
58
+ : parsed.search;
59
+ const query_hash = rawSearch === '' ? null : computeContentHash(rawSearch);
60
+ const fragment = parsed.hash === '' ? null : parsed.hash.slice(1);
61
+ const path = scheme !== null && OPAQUE_PATH_SCHEMES.has(scheme) ? null : parsed.pathname;
62
+ return {
63
+ scheme,
64
+ host,
65
+ port: port !== null && Number.isFinite(port) ? port : null,
66
+ path,
67
+ query_hash,
68
+ fragment,
69
+ };
70
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Classifies a header name as volatile (excluded from `stable_hash`) or
3
+ * stable (included). Names are compared case-insensitively (lower-cased
4
+ * before lookup). Names not in {@link VOLATILE_HEADER_NAMES} default to
5
+ * **stable** — see the docs on that constant for why this direction is
6
+ * the safer default.
7
+ *
8
+ * An explicit stable list could be named instead (`content-type`,
9
+ * `content-length`, `cache-control`, `content-security-policy`,
10
+ * `x-frame-options`, `x-content-type-options`, `strict-transport-
11
+ * security`, `referrer-policy`, `permissions-policy`, `server`, `vary`,
12
+ * `location`, ...); we deliberately do NOT enumerate that set at runtime —
13
+ * every non-volatile header is stable by construction, and enumerating
14
+ * it would create a maintenance surface that the volatile-only lookup
15
+ * avoids.
16
+ * @param name - Header name (any case).
17
+ * @returns `true` when the header is volatile.
18
+ */
19
+ export declare function isVolatileHeader(name: string): boolean;
@@ -0,0 +1,22 @@
1
+ import { VOLATILE_HEADER_NAMES } from './volatile-header-names.js';
2
+ /**
3
+ * Classifies a header name as volatile (excluded from `stable_hash`) or
4
+ * stable (included). Names are compared case-insensitively (lower-cased
5
+ * before lookup). Names not in {@link VOLATILE_HEADER_NAMES} default to
6
+ * **stable** — see the docs on that constant for why this direction is
7
+ * the safer default.
8
+ *
9
+ * An explicit stable list could be named instead (`content-type`,
10
+ * `content-length`, `cache-control`, `content-security-policy`,
11
+ * `x-frame-options`, `x-content-type-options`, `strict-transport-
12
+ * security`, `referrer-policy`, `permissions-policy`, `server`, `vary`,
13
+ * `location`, ...); we deliberately do NOT enumerate that set at runtime —
14
+ * every non-volatile header is stable by construction, and enumerating
15
+ * it would create a maintenance surface that the volatile-only lookup
16
+ * avoids.
17
+ * @param name - Header name (any case).
18
+ * @returns `true` when the header is volatile.
19
+ */
20
+ export function isVolatileHeader(name) {
21
+ return VOLATILE_HEADER_NAMES.has(name.toLowerCase());
22
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Composite cache key for `header_value_refs`: hex-encoded hash + a
3
+ * separator + raw value. Two values with the same hash but different
4
+ * strings (astronomically improbable, but a `BLOB` hash column is not
5
+ * a total function per SQL semantics) still resolve to distinct cache
6
+ * entries. Shared by the cache warmer
7
+ * ({@link ./create-header-table-caches.ts}) and the per-set upsert
8
+ * ({@link ./upsert-one-header-set.ts}) so both sides always agree on the
9
+ * key shape.
10
+ * @param hash - 32-byte content hash of `value`.
11
+ * @param value - Header value verbatim.
12
+ * @returns Cache key string.
13
+ * @example
14
+ * headerValueCacheKey(computeContentHash('no-cache'), 'no-cache');
15
+ * // '724cf9…|no-cache'
16
+ */
17
+ export declare function headerValueCacheKey(hash: Buffer, value: string): string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Composite cache key for `header_value_refs`: hex-encoded hash + a
3
+ * separator + raw value. Two values with the same hash but different
4
+ * strings (astronomically improbable, but a `BLOB` hash column is not
5
+ * a total function per SQL semantics) still resolve to distinct cache
6
+ * entries. Shared by the cache warmer
7
+ * ({@link ./create-header-table-caches.ts}) and the per-set upsert
8
+ * ({@link ./upsert-one-header-set.ts}) so both sides always agree on the
9
+ * key shape.
10
+ * @param hash - 32-byte content hash of `value`.
11
+ * @param value - Header value verbatim.
12
+ * @returns Cache key string.
13
+ * @example
14
+ * headerValueCacheKey(computeContentHash('no-cache'), 'no-cache');
15
+ * // '724cf9…|no-cache'
16
+ */
17
+ export function headerValueCacheKey(hash, value) {
18
+ return `${hash.toString('hex')}|${value}`;
19
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Normalises a raw Content-Type header value to the canonical MIME form
3
+ * stored in `content_type_refs.normalized`: parameters (`; charset=...`)
4
+ * removed, C0 control characters (`0x00`..`0x1F`) and DEL (`0x7F`)
5
+ * stripped, trimmed, lower-cased.
6
+ *
7
+ * This is a derived column shared by two writers — the live-crawl upsert
8
+ * (`db-ops/_shared/upsert-content-type-ref.ts`) and the archive-migration
9
+ * populate (`populate-content-type-refs.ts`) — which MUST be
10
+ * same-input-same-output; a fork would split the dictionary so the same
11
+ * raw content-type maps to different normalized values depending on
12
+ * whether the row was crawled live or migrated. Keeping the single
13
+ * implementation here is what enforces that.
14
+ *
15
+ * Control chars are dropped character-by-character rather than via a
16
+ * regex literal so the source file carries no non-printing bytes and
17
+ * stays tool-friendly. `jsdom` / lax parsers occasionally emit them
18
+ * inside a raw Content-Type header (e.g. `text/html\r`).
19
+ * @param raw - Raw Content-Type header, guaranteed non-null non-empty.
20
+ * @returns Lower-cased MIME with parameters and control chars stripped.
21
+ * @example
22
+ * normalizeMime('Text/HTML; charset=UTF-8'); // => 'text/html'
23
+ */
24
+ export declare function normalizeMime(raw: string): string;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Normalises a raw Content-Type header value to the canonical MIME form
3
+ * stored in `content_type_refs.normalized`: parameters (`; charset=...`)
4
+ * removed, C0 control characters (`0x00`..`0x1F`) and DEL (`0x7F`)
5
+ * stripped, trimmed, lower-cased.
6
+ *
7
+ * This is a derived column shared by two writers — the live-crawl upsert
8
+ * (`db-ops/_shared/upsert-content-type-ref.ts`) and the archive-migration
9
+ * populate (`populate-content-type-refs.ts`) — which MUST be
10
+ * same-input-same-output; a fork would split the dictionary so the same
11
+ * raw content-type maps to different normalized values depending on
12
+ * whether the row was crawled live or migrated. Keeping the single
13
+ * implementation here is what enforces that.
14
+ *
15
+ * Control chars are dropped character-by-character rather than via a
16
+ * regex literal so the source file carries no non-printing bytes and
17
+ * stays tool-friendly. `jsdom` / lax parsers occasionally emit them
18
+ * inside a raw Content-Type header (e.g. `text/html\r`).
19
+ * @param raw - Raw Content-Type header, guaranteed non-null non-empty.
20
+ * @returns Lower-cased MIME with parameters and control chars stripped.
21
+ * @example
22
+ * normalizeMime('Text/HTML; charset=UTF-8'); // => 'text/html'
23
+ */
24
+ export function normalizeMime(raw) {
25
+ const semi = raw.indexOf(';');
26
+ const head = semi === -1 ? raw : raw.slice(0, semi);
27
+ let output = '';
28
+ for (const ch of head) {
29
+ const code = ch.codePointAt(0);
30
+ if (code < 32 /* 0x20 = SPACE */ || code === 127 /* 0x7F = DEL */) {
31
+ continue;
32
+ }
33
+ output += ch;
34
+ }
35
+ return output.trim().toLowerCase();
36
+ }
@@ -0,0 +1,38 @@
1
+ import type { ProgressCallback } from '../create-progress-reporter.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Populates `blob_refs` from every `images.src` / `images.currentSrc` /
5
+ * `resources.url` value that is a data URI longer than
6
+ * {@link DATA_URI_URL_REFS_LIMIT} bytes (issue #191).
7
+ *
8
+ * For each such value:
9
+ *
10
+ * 1. `decodeDataUri` strips the `data:...;base64,` (or `data:...,`) prefix
11
+ * and returns the raw payload bytes.
12
+ * 2. `computeContentHash` hashes the payload bytes (32-byte SHA-256).
13
+ * 3. The payload is zstd-compressed (`codec='zstd'`) — matching
14
+ * `page_html_blobs` / `json_refs`.
15
+ * 4. `INSERT OR IGNORE` on `hash` deduplicates: an `<img>` element and a
16
+ * resource that share the same underlying base64 payload share one
17
+ * `blob_refs` row.
18
+ *
19
+ * Malformed data URIs that `decodeDataUri` cannot decode are logged and
20
+ * skipped — the raw URI string still exists in the source row, and the
21
+ * migration script's operator can hunt it down from the warning log. The
22
+ * alternative — routing malformed large URIs back to `url_refs` — would
23
+ * break the "data URIs > threshold live in blob_refs" contract that 0.13
24
+ * lookups depend on.
25
+ *
26
+ * On the reference archive only ~429 images use data URIs, so the total
27
+ * dictionary is tiny — this step is I/O-cheap even without full
28
+ * chunking, but the same batching shape is used as elsewhere for
29
+ * consistency.
30
+ * @param trx - Knex instance or transaction connected to the archive DB.
31
+ * @param onProgress - Optional sink for periodic progress lines (one per
32
+ * ~5% of each source table scanned); see {@link ../create-progress-reporter.ts}.
33
+ * @example
34
+ * await knex.transaction(async (trx) => {
35
+ * await populateBlobRefs(trx);
36
+ * });
37
+ */
38
+ export declare function populateBlobRefs(trx: Knex, onProgress?: ProgressCallback): Promise<void>;
@@ -0,0 +1,134 @@
1
+ import { zstdCompressSync } from 'node:zlib';
2
+ import { createProgressReporter } from '../create-progress-reporter.js';
3
+ import { computeContentHash } from './compute-content-hash.js';
4
+ import { DATA_URI_URL_REFS_LIMIT } from './data-uri-url-refs-limit.js';
5
+ import { decodeDataUri } from './decode-data-uri.js';
6
+ /** Rows scanned per `SELECT`. See `populateTextRefs` for chunking rationale. */
7
+ const READ_CHUNK_SIZE = 2000;
8
+ /**
9
+ * Rows written per `INSERT`. blob_refs rows carry a compressed body BLOB
10
+ * so 100 rows keeps the total payload of a single INSERT statement well
11
+ * under WAL frame limits even for large SVGs.
12
+ */
13
+ const INSERT_CHUNK_SIZE = 100;
14
+ /**
15
+ * Source table + its URL-shaped column(s) that may hold a data URI large
16
+ * enough to route to `blob_refs`. `resources.url` is the resource's own
17
+ * identity (unlike `pages`/`content_items`, whose own URL is always a
18
+ * real http(s) address in practice) — a `<link>`/CSS sub-resource can
19
+ * legally be captured as an inline `data:` URI.
20
+ */
21
+ const URL_SOURCES = [
22
+ { table: 'images', columns: ['src', 'currentSrc'] },
23
+ { table: 'resources', columns: ['url'] },
24
+ ];
25
+ /**
26
+ * Populates `blob_refs` from every `images.src` / `images.currentSrc` /
27
+ * `resources.url` value that is a data URI longer than
28
+ * {@link DATA_URI_URL_REFS_LIMIT} bytes (issue #191).
29
+ *
30
+ * For each such value:
31
+ *
32
+ * 1. `decodeDataUri` strips the `data:...;base64,` (or `data:...,`) prefix
33
+ * and returns the raw payload bytes.
34
+ * 2. `computeContentHash` hashes the payload bytes (32-byte SHA-256).
35
+ * 3. The payload is zstd-compressed (`codec='zstd'`) — matching
36
+ * `page_html_blobs` / `json_refs`.
37
+ * 4. `INSERT OR IGNORE` on `hash` deduplicates: an `<img>` element and a
38
+ * resource that share the same underlying base64 payload share one
39
+ * `blob_refs` row.
40
+ *
41
+ * Malformed data URIs that `decodeDataUri` cannot decode are logged and
42
+ * skipped — the raw URI string still exists in the source row, and the
43
+ * migration script's operator can hunt it down from the warning log. The
44
+ * alternative — routing malformed large URIs back to `url_refs` — would
45
+ * break the "data URIs > threshold live in blob_refs" contract that 0.13
46
+ * lookups depend on.
47
+ *
48
+ * On the reference archive only ~429 images use data URIs, so the total
49
+ * dictionary is tiny — this step is I/O-cheap even without full
50
+ * chunking, but the same batching shape is used as elsewhere for
51
+ * consistency.
52
+ * @param trx - Knex instance or transaction connected to the archive DB.
53
+ * @param onProgress - Optional sink for periodic progress lines (one per
54
+ * ~5% of each source table scanned); see {@link ../create-progress-reporter.ts}.
55
+ * @example
56
+ * await knex.transaction(async (trx) => {
57
+ * await populateBlobRefs(trx);
58
+ * });
59
+ */
60
+ export async function populateBlobRefs(trx, onProgress) {
61
+ const seen = new Set();
62
+ const pending = [];
63
+ for (const source of URL_SOURCES) {
64
+ const hasTable = await trx.schema.hasTable(source.table);
65
+ if (!hasTable) {
66
+ continue;
67
+ }
68
+ const presentColumns = [];
69
+ for (const column of source.columns) {
70
+ if (await trx.schema.hasColumn(source.table, column)) {
71
+ presentColumns.push(column);
72
+ }
73
+ }
74
+ if (presentColumns.length === 0) {
75
+ continue;
76
+ }
77
+ const countRows = await trx(source.table).count({ n: '*' });
78
+ const total = Number(countRows[0]?.n ?? 0);
79
+ const report = createProgressReporter(`blob_refs (${source.table})`, total, onProgress);
80
+ let processed = 0;
81
+ let cursor = 0;
82
+ while (true) {
83
+ const rows = await trx(source.table)
84
+ .select('id', ...presentColumns)
85
+ .where('id', '>', cursor)
86
+ .orderBy('id', 'asc')
87
+ .limit(READ_CHUNK_SIZE);
88
+ if (rows.length === 0) {
89
+ break;
90
+ }
91
+ cursor = rows.at(-1).id;
92
+ processed += rows.length;
93
+ report(processed);
94
+ for (const row of rows) {
95
+ for (const column of presentColumns) {
96
+ const value = row[column];
97
+ if (value == null || value.length <= DATA_URI_URL_REFS_LIMIT) {
98
+ continue;
99
+ }
100
+ if (!value.startsWith('data:')) {
101
+ continue;
102
+ }
103
+ const decoded = decodeDataUri(value);
104
+ if (decoded === null) {
105
+ // eslint-disable-next-line no-console
106
+ console.warn(`populateBlobRefs: skipping malformed data URI (length=${value.length}) — raw string still exists in ${source.table} row`);
107
+ continue;
108
+ }
109
+ const hash = computeContentHash(decoded.bytes);
110
+ const hex = hash.toString('hex');
111
+ if (seen.has(hex)) {
112
+ continue;
113
+ }
114
+ seen.add(hex);
115
+ const compressed = zstdCompressSync(decoded.bytes);
116
+ pending.push({
117
+ hash,
118
+ body: compressed,
119
+ codec: 'zstd',
120
+ size_raw: decoded.bytes.byteLength,
121
+ size_stored: compressed.byteLength,
122
+ });
123
+ if (pending.length >= INSERT_CHUNK_SIZE) {
124
+ await trx('blob_refs').insert(pending).onConflict('hash').ignore();
125
+ pending.length = 0;
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ if (pending.length > 0) {
132
+ await trx('blob_refs').insert(pending).onConflict('hash').ignore();
133
+ }
134
+ }
@@ -0,0 +1,27 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Populates `content_type_refs` from every distinct `contentType` value
4
+ * currently stored in `pages` and `resources` (issue #191).
5
+ *
6
+ * Two independent DISTINCT SELECTs (one per table) are merged in JS
7
+ * rather than via SQL `UNION` — the cardinality is small in practice and
8
+ * doing it in JS avoids the knex-`union().select()` column-aliasing
9
+ * quirk (bare `.select()` on a union wraps in `SELECT *` and can lose
10
+ * the column name depending on driver version). Two per-table SELECTs
11
+ * are also fast because `contentType` is indexed on both tables via
12
+ * `idx_pages_listfilter` / natural column index (see `init-schema.ts`).
13
+ *
14
+ * `normalized` and `category` are derived in JS via {@link classifyContentType}
15
+ * so the rule table stays in one place; SQLite has no equivalent
16
+ * expression.
17
+ *
18
+ * `INSERT OR IGNORE` on the natural key `raw` makes this idempotent —
19
+ * re-running this populate after a partial failure never duplicates
20
+ * rows, only appends the new ones.
21
+ * @param trx - Knex instance or transaction connected to the archive DB.
22
+ * @example
23
+ * await knex.transaction(async (trx) => {
24
+ * await populateContentTypeRefs(trx);
25
+ * });
26
+ */
27
+ export declare function populateContentTypeRefs(trx: Knex): Promise<void>;
@@ -0,0 +1,70 @@
1
+ import { classifyContentType } from './classify-content-type.js';
2
+ import { normalizeMime } from './normalize-mime.js';
3
+ /**
4
+ * Distinct-`contentType` rows are collected from `pages` + `resources` in
5
+ * chunks of this size before being written into `content_type_refs`. The
6
+ * DISTINCT list is expected to be tiny (< 1000 rows even on the largest
7
+ * archives — the wire content-type space is small) so the chunk size only
8
+ * bounds worst-case parameter counts on `INSERT ... VALUES ...`.
9
+ */
10
+ const INSERT_CHUNK_SIZE = 500;
11
+ /**
12
+ * Populates `content_type_refs` from every distinct `contentType` value
13
+ * currently stored in `pages` and `resources` (issue #191).
14
+ *
15
+ * Two independent DISTINCT SELECTs (one per table) are merged in JS
16
+ * rather than via SQL `UNION` — the cardinality is small in practice and
17
+ * doing it in JS avoids the knex-`union().select()` column-aliasing
18
+ * quirk (bare `.select()` on a union wraps in `SELECT *` and can lose
19
+ * the column name depending on driver version). Two per-table SELECTs
20
+ * are also fast because `contentType` is indexed on both tables via
21
+ * `idx_pages_listfilter` / natural column index (see `init-schema.ts`).
22
+ *
23
+ * `normalized` and `category` are derived in JS via {@link classifyContentType}
24
+ * so the rule table stays in one place; SQLite has no equivalent
25
+ * expression.
26
+ *
27
+ * `INSERT OR IGNORE` on the natural key `raw` makes this idempotent —
28
+ * re-running this populate after a partial failure never duplicates
29
+ * rows, only appends the new ones.
30
+ * @param trx - Knex instance or transaction connected to the archive DB.
31
+ * @example
32
+ * await knex.transaction(async (trx) => {
33
+ * await populateContentTypeRefs(trx);
34
+ * });
35
+ */
36
+ export async function populateContentTypeRefs(trx) {
37
+ const distinctRaw = new Set();
38
+ if (await trx.schema.hasTable('pages')) {
39
+ const pagesRows = await trx('pages')
40
+ .distinct('contentType')
41
+ .whereNotNull('contentType');
42
+ for (const { contentType } of pagesRows) {
43
+ if (contentType != null && contentType !== '') {
44
+ distinctRaw.add(contentType);
45
+ }
46
+ }
47
+ }
48
+ if (await trx.schema.hasTable('resources')) {
49
+ const resourcesRows = await trx('resources')
50
+ .distinct('contentType')
51
+ .whereNotNull('contentType');
52
+ for (const { contentType } of resourcesRows) {
53
+ if (contentType != null && contentType !== '') {
54
+ distinctRaw.add(contentType);
55
+ }
56
+ }
57
+ }
58
+ if (distinctRaw.size === 0) {
59
+ return;
60
+ }
61
+ const inserts = [...distinctRaw].map((raw) => ({
62
+ raw,
63
+ normalized: normalizeMime(raw),
64
+ category: classifyContentType(raw),
65
+ }));
66
+ for (let index = 0; index < inserts.length; index += INSERT_CHUNK_SIZE) {
67
+ const chunk = inserts.slice(index, index + INSERT_CHUNK_SIZE);
68
+ await trx('content_type_refs').insert(chunk).onConflict('raw').ignore();
69
+ }
70
+ }