@nitpicker/crawler 0.12.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 (349) 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 +63 -16
  7. package/lib/archive/archive.js +56 -17
  8. package/lib/archive/create-adjunct-tables.d.ts +43 -0
  9. package/lib/archive/create-adjunct-tables.js +213 -0
  10. package/lib/archive/create-entity-tables.d.ts +173 -0
  11. package/lib/archive/create-entity-tables.js +318 -0
  12. package/lib/archive/create-progress-reporter.d.ts +30 -0
  13. package/lib/archive/create-progress-reporter.js +38 -0
  14. package/lib/archive/create-ref-tables.d.ts +35 -0
  15. package/lib/archive/create-ref-tables.js +188 -0
  16. package/lib/archive/database.d.ts +92 -345
  17. package/lib/archive/database.js +168 -1942
  18. package/lib/archive/db-ops/_shared/clear-write-ref-caches.d.ts +27 -0
  19. package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +34 -0
  20. package/lib/archive/db-ops/_shared/create-write-ref-caches.d.ts +17 -0
  21. package/lib/archive/db-ops/_shared/create-write-ref-caches.js +26 -0
  22. package/lib/archive/db-ops/_shared/decode-json-ref.d.ts +17 -0
  23. package/lib/archive/db-ops/_shared/decode-json-ref.js +31 -0
  24. package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.d.ts +20 -0
  25. package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.js +53 -0
  26. package/lib/archive/db-ops/_shared/resolve-content-item-id.d.ts +61 -0
  27. package/lib/archive/db-ops/_shared/resolve-content-item-id.js +111 -0
  28. package/lib/archive/db-ops/_shared/resolve-url-or-blob.d.ts +23 -0
  29. package/lib/archive/db-ops/_shared/resolve-url-or-blob.js +29 -0
  30. package/lib/archive/db-ops/_shared/retry-setting.d.ts +16 -0
  31. package/lib/archive/db-ops/_shared/retry-setting.js +18 -0
  32. package/lib/archive/db-ops/_shared/safe-parse-json.d.ts +11 -0
  33. package/lib/archive/db-ops/_shared/safe-parse-json.js +18 -0
  34. package/lib/archive/db-ops/_shared/types.d.ts +53 -0
  35. package/lib/archive/db-ops/_shared/types.js +1 -0
  36. package/lib/archive/db-ops/_shared/upsert-blob-ref.d.ts +25 -0
  37. package/lib/archive/db-ops/_shared/upsert-blob-ref.js +48 -0
  38. package/lib/archive/db-ops/_shared/upsert-content-type-ref.d.ts +30 -0
  39. package/lib/archive/db-ops/_shared/upsert-content-type-ref.js +45 -0
  40. package/lib/archive/db-ops/_shared/upsert-json-ref.d.ts +22 -0
  41. package/lib/archive/db-ops/_shared/upsert-json-ref.js +41 -0
  42. package/lib/archive/db-ops/_shared/upsert-response-headers.d.ts +35 -0
  43. package/lib/archive/db-ops/_shared/upsert-response-headers.js +49 -0
  44. package/lib/archive/db-ops/_shared/upsert-url-ref.d.ts +39 -0
  45. package/lib/archive/db-ops/_shared/upsert-url-ref.js +62 -0
  46. package/lib/archive/db-ops/analysis/replace-analysis-violations.d.ts +28 -0
  47. package/lib/archive/db-ops/analysis/replace-analysis-violations.js +152 -0
  48. package/lib/archive/db-ops/anchors/get-anchors-on-page.d.ts +10 -0
  49. package/lib/archive/db-ops/anchors/get-anchors-on-page.js +21 -0
  50. package/lib/archive/db-ops/config/get-base-url.d.ts +8 -0
  51. package/lib/archive/db-ops/config/get-base-url.js +14 -0
  52. package/lib/archive/db-ops/config/get-config.d.ts +10 -0
  53. package/lib/archive/db-ops/config/get-config.js +27 -0
  54. package/lib/archive/db-ops/config/get-name.d.ts +8 -0
  55. package/lib/archive/db-ops/config/get-name.js +14 -0
  56. package/lib/archive/db-ops/config/info-column-allowlist.d.ts +7 -0
  57. package/lib/archive/db-ops/config/info-column-allowlist.js +26 -0
  58. package/lib/archive/db-ops/config/info-json-columns.d.ts +5 -0
  59. package/lib/archive/db-ops/config/info-json-columns.js +10 -0
  60. package/lib/archive/db-ops/config/set-config.d.ts +12 -0
  61. package/lib/archive/db-ops/config/set-config.js +21 -0
  62. package/lib/archive/db-ops/config/update-config.d.ts +17 -0
  63. package/lib/archive/db-ops/config/update-config.js +36 -0
  64. package/lib/archive/db-ops/errors/insert-crawl-error.d.ts +15 -0
  65. package/lib/archive/db-ops/errors/insert-crawl-error.js +21 -0
  66. package/lib/archive/db-ops/errors/insert-page-error.d.ts +21 -0
  67. package/lib/archive/db-ops/errors/insert-page-error.js +28 -0
  68. package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +22 -0
  69. package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +141 -0
  70. package/lib/archive/db-ops/html/get-html-of-page-by-id.d.ts +18 -0
  71. package/lib/archive/db-ops/html/get-html-of-page-by-id.js +29 -0
  72. package/lib/archive/db-ops/inventory/record-inventory-run.d.ts +21 -0
  73. package/lib/archive/db-ops/inventory/record-inventory-run.js +38 -0
  74. package/lib/archive/db-ops/lifecycle/checkpoint.d.ts +8 -0
  75. package/lib/archive/db-ops/lifecycle/checkpoint.js +9 -0
  76. package/lib/archive/db-ops/lifecycle/destroy.d.ts +6 -0
  77. package/lib/archive/db-ops/lifecycle/destroy.js +7 -0
  78. package/lib/archive/db-ops/lifecycle/init.d.ts +22 -0
  79. package/lib/archive/db-ops/lifecycle/init.js +42 -0
  80. package/lib/archive/db-ops/meta/get-jsonld-of-page.d.ts +13 -0
  81. package/lib/archive/db-ops/meta/get-jsonld-of-page.js +27 -0
  82. package/lib/archive/db-ops/meta/get-tags-of-page.d.ts +12 -0
  83. package/lib/archive/db-ops/meta/get-tags-of-page.js +28 -0
  84. package/lib/archive/db-ops/pages/order/set-url-order.d.ts +8 -0
  85. package/lib/archive/db-ops/pages/order/set-url-order.js +32 -0
  86. package/lib/archive/db-ops/pages/read/build-page-query.d.ts +18 -0
  87. package/lib/archive/db-ops/pages/read/build-page-query.js +40 -0
  88. package/lib/archive/db-ops/pages/read/get-crawling-state.d.ts +70 -0
  89. package/lib/archive/db-ops/pages/read/get-crawling-state.js +98 -0
  90. package/lib/archive/db-ops/pages/read/get-existing-page-urls.d.ts +15 -0
  91. package/lib/archive/db-ops/pages/read/get-existing-page-urls.js +30 -0
  92. package/lib/archive/db-ops/pages/read/get-page-count.d.ts +12 -0
  93. package/lib/archive/db-ops/pages/read/get-page-count.js +21 -0
  94. package/lib/archive/db-ops/pages/read/get-page-source-by-url.d.ts +24 -0
  95. package/lib/archive/db-ops/pages/read/get-page-source-by-url.js +28 -0
  96. package/lib/archive/db-ops/pages/read/get-pages-with-rels.d.ts +38 -0
  97. package/lib/archive/db-ops/pages/read/get-pages-with-rels.js +107 -0
  98. package/lib/archive/db-ops/pages/read/get-pages.d.ts +11 -0
  99. package/lib/archive/db-ops/pages/read/get-pages.js +51 -0
  100. package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.d.ts +18 -0
  101. package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.js +25 -0
  102. package/lib/archive/db-ops/pages/read/reconstruct-page-rows.d.ts +31 -0
  103. package/lib/archive/db-ops/pages/read/reconstruct-page-rows.js +32 -0
  104. package/lib/archive/db-ops/pages/reset/repromote-external-pages.d.ts +24 -0
  105. package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +93 -0
  106. package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +47 -0
  107. package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +124 -0
  108. package/lib/archive/db-ops/pages/write/insert-inventory-seeds.d.ts +37 -0
  109. package/lib/archive/db-ops/pages/write/insert-inventory-seeds.js +72 -0
  110. package/lib/archive/db-ops/pages/write/insert-jsonld.d.ts +17 -0
  111. package/lib/archive/db-ops/pages/write/insert-jsonld.js +49 -0
  112. package/lib/archive/db-ops/pages/write/insert-page.d.ts +36 -0
  113. package/lib/archive/db-ops/pages/write/insert-page.js +208 -0
  114. package/lib/archive/db-ops/pages/write/insert-tags.d.ts +16 -0
  115. package/lib/archive/db-ops/pages/write/insert-tags.js +34 -0
  116. package/lib/archive/db-ops/pages/write/link-redirect-sources.d.ts +36 -0
  117. package/lib/archive/db-ops/pages/write/link-redirect-sources.js +93 -0
  118. package/lib/archive/db-ops/pages/write/record-redirect.d.ts +35 -0
  119. package/lib/archive/db-ops/pages/write/record-redirect.js +100 -0
  120. package/lib/archive/db-ops/pages/write/set-skipped-page.d.ts +13 -0
  121. package/lib/archive/db-ops/pages/write/set-skipped-page.js +22 -0
  122. package/lib/archive/db-ops/pages/write/update-page.d.ts +29 -0
  123. package/lib/archive/db-ops/pages/write/update-page.js +334 -0
  124. package/lib/archive/db-ops/pages/write/write-page-html-blob.d.ts +19 -0
  125. package/lib/archive/db-ops/pages/write/write-page-html-blob.js +41 -0
  126. package/lib/archive/db-ops/referrers/get-redirects-for-pages.d.ts +9 -0
  127. package/lib/archive/db-ops/referrers/get-redirects-for-pages.js +15 -0
  128. package/lib/archive/db-ops/referrers/get-referrers-of-page.d.ts +17 -0
  129. package/lib/archive/db-ops/referrers/get-referrers-of-page.js +32 -0
  130. package/lib/archive/db-ops/referrers/get-referrers-of-resource.d.ts +8 -0
  131. package/lib/archive/db-ops/referrers/get-referrers-of-resource.js +15 -0
  132. package/lib/archive/db-ops/resources/build-resource-query.d.ts +25 -0
  133. package/lib/archive/db-ops/resources/build-resource-query.js +29 -0
  134. package/lib/archive/db-ops/resources/get-existing-resource-urls.d.ts +9 -0
  135. package/lib/archive/db-ops/resources/get-existing-resource-urls.js +24 -0
  136. package/lib/archive/db-ops/resources/get-resource-by-url.d.ts +13 -0
  137. package/lib/archive/db-ops/resources/get-resource-by-url.js +22 -0
  138. package/lib/archive/db-ops/resources/get-resource-url-list.d.ts +9 -0
  139. package/lib/archive/db-ops/resources/get-resource-url-list.js +13 -0
  140. package/lib/archive/db-ops/resources/get-resources.d.ts +8 -0
  141. package/lib/archive/db-ops/resources/get-resources.js +11 -0
  142. package/lib/archive/db-ops/resources/insert-inventory-resources.d.ts +24 -0
  143. package/lib/archive/db-ops/resources/insert-inventory-resources.js +64 -0
  144. package/lib/archive/db-ops/resources/insert-resource-referrers.d.ts +15 -0
  145. package/lib/archive/db-ops/resources/insert-resource-referrers.js +54 -0
  146. package/lib/archive/db-ops/resources/insert-resource.d.ts +34 -0
  147. package/lib/archive/db-ops/resources/insert-resource.js +73 -0
  148. package/lib/archive/db-ops/resources/reconstruct-resource-rows.d.ts +26 -0
  149. package/lib/archive/db-ops/resources/reconstruct-resource-rows.js +30 -0
  150. package/lib/archive/decode-html-blob.d.ts +18 -0
  151. package/lib/archive/decode-html-blob.js +31 -0
  152. package/lib/archive/derive-lineage-from-parent.d.ts +1 -1
  153. package/lib/archive/derive-lineage-from-parent.js +1 -1
  154. package/lib/archive/drop-legacy-tables.d.ts +45 -0
  155. package/lib/archive/drop-legacy-tables.js +56 -0
  156. package/lib/archive/filesystem/rename.js +1 -1
  157. package/lib/archive/get-failed-page-messages.d.ts +5 -4
  158. package/lib/archive/get-failed-page-messages.js +5 -4
  159. package/lib/archive/init-schema.d.ts +35 -39
  160. package/lib/archive/init-schema.js +99 -460
  161. package/lib/archive/limited-page-ids.d.ts +2 -1
  162. package/lib/archive/limited-page-ids.js +5 -4
  163. package/lib/archive/meta/assert-compatible-version.d.ts +24 -3
  164. package/lib/archive/meta/assert-compatible-version.js +24 -3
  165. package/lib/archive/meta/types.d.ts +87 -1
  166. package/lib/archive/meta/types.js +34 -2
  167. package/lib/archive/migrate-entity-tables.d.ts +45 -0
  168. package/lib/archive/migrate-entity-tables.js +56 -0
  169. package/lib/archive/migrate-ref-tables.d.ts +25 -0
  170. package/lib/archive/migrate-ref-tables.js +38 -0
  171. package/lib/archive/page-meta-column-maps.d.ts +32 -0
  172. package/lib/archive/page-meta-column-maps.js +43 -0
  173. package/lib/archive/page.d.ts +6 -6
  174. package/lib/archive/page.js +5 -5
  175. package/lib/archive/peek-archive-lock.d.ts +2 -2
  176. package/lib/archive/peek-archive-lock.js +2 -2
  177. package/lib/archive/populate-entity-tables/collapse-anchor-rows.d.ts +41 -0
  178. package/lib/archive/populate-entity-tables/collapse-anchor-rows.js +87 -0
  179. package/lib/archive/populate-entity-tables/derive-dom-path.d.ts +35 -0
  180. package/lib/archive/populate-entity-tables/derive-dom-path.js +72 -0
  181. package/lib/archive/populate-entity-tables/is-blob-ref-value.d.ts +16 -0
  182. package/lib/archive/populate-entity-tables/is-blob-ref-value.js +19 -0
  183. package/lib/archive/populate-entity-tables/match-images-to-dom-paths.d.ts +66 -0
  184. package/lib/archive/populate-entity-tables/match-images-to-dom-paths.js +96 -0
  185. package/lib/archive/populate-entity-tables/populate-anchor-edges.d.ts +33 -0
  186. package/lib/archive/populate-entity-tables/populate-anchor-edges.js +153 -0
  187. package/lib/archive/populate-entity-tables/populate-content-items.d.ts +40 -0
  188. package/lib/archive/populate-entity-tables/populate-content-items.js +141 -0
  189. package/lib/archive/populate-entity-tables/populate-entities.d.ts +81 -0
  190. package/lib/archive/populate-entity-tables/populate-entities.js +111 -0
  191. package/lib/archive/populate-entity-tables/populate-image-items.d.ts +91 -0
  192. package/lib/archive/populate-entity-tables/populate-image-items.js +223 -0
  193. package/lib/archive/populate-entity-tables/populate-page-meta.d.ts +33 -0
  194. package/lib/archive/populate-entity-tables/populate-page-meta.js +267 -0
  195. package/lib/archive/populate-entity-tables/populate-resource-items.d.ts +22 -0
  196. package/lib/archive/populate-entity-tables/populate-resource-items.js +114 -0
  197. package/lib/archive/populate-entity-tables/populate-resource-ref-edges.d.ts +31 -0
  198. package/lib/archive/populate-entity-tables/populate-resource-ref-edges.js +33 -0
  199. package/lib/archive/populate-entity-tables/resolve-blob-refs.d.ts +31 -0
  200. package/lib/archive/populate-entity-tables/resolve-blob-refs.js +100 -0
  201. package/lib/archive/populate-entity-tables/resolve-content-type-refs.d.ts +22 -0
  202. package/lib/archive/populate-entity-tables/resolve-content-type-refs.js +27 -0
  203. package/lib/archive/populate-entity-tables/resolve-header-sets.d.ts +49 -0
  204. package/lib/archive/populate-entity-tables/resolve-header-sets.js +122 -0
  205. package/lib/archive/populate-entity-tables/resolve-json-refs.d.ts +25 -0
  206. package/lib/archive/populate-entity-tables/resolve-json-refs.js +67 -0
  207. package/lib/archive/populate-entity-tables/resolve-text-refs.d.ts +30 -0
  208. package/lib/archive/populate-entity-tables/resolve-text-refs.js +61 -0
  209. package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.d.ts +21 -0
  210. package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.js +27 -0
  211. package/lib/archive/populate-entity-tables/resolve-url-refs.d.ts +33 -0
  212. package/lib/archive/populate-entity-tables/resolve-url-refs.js +60 -0
  213. package/lib/archive/populate-entity-tables/test-utils/count-rows.d.ts +17 -0
  214. package/lib/archive/populate-entity-tables/test-utils/count-rows.js +20 -0
  215. package/lib/archive/populate-entity-tables/test-utils/seed-content-items.d.ts +25 -0
  216. package/lib/archive/populate-entity-tables/test-utils/seed-content-items.js +42 -0
  217. package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.d.ts +23 -0
  218. package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.js +178 -0
  219. package/lib/archive/populate-entity-tables/types.d.ts +157 -0
  220. package/lib/archive/populate-entity-tables/types.js +12 -0
  221. package/lib/archive/populate-entity-tables/upsert-text-refs.d.ts +38 -0
  222. package/lib/archive/populate-entity-tables/upsert-text-refs.js +78 -0
  223. package/lib/archive/populate-ref-tables/classify-content-type.d.ts +16 -0
  224. package/lib/archive/populate-ref-tables/classify-content-type.js +52 -0
  225. package/lib/archive/populate-ref-tables/compute-content-hash.d.ts +22 -0
  226. package/lib/archive/populate-ref-tables/compute-content-hash.js +26 -0
  227. package/lib/archive/populate-ref-tables/compute-header-flags.d.ts +16 -0
  228. package/lib/archive/populate-ref-tables/compute-header-flags.js +70 -0
  229. package/lib/archive/populate-ref-tables/content-type-rules.d.ts +38 -0
  230. package/lib/archive/populate-ref-tables/content-type-rules.js +133 -0
  231. package/lib/archive/populate-ref-tables/create-header-table-caches.d.ts +25 -0
  232. package/lib/archive/populate-ref-tables/create-header-table-caches.js +49 -0
  233. package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.d.ts +15 -0
  234. package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.js +15 -0
  235. package/lib/archive/populate-ref-tables/decode-data-uri.d.ts +21 -0
  236. package/lib/archive/populate-ref-tables/decode-data-uri.js +126 -0
  237. package/lib/archive/populate-ref-tables/decompose-header-set.d.ts +29 -0
  238. package/lib/archive/populate-ref-tables/decompose-header-set.js +157 -0
  239. package/lib/archive/populate-ref-tables/decompose-url.d.ts +25 -0
  240. package/lib/archive/populate-ref-tables/decompose-url.js +70 -0
  241. package/lib/archive/populate-ref-tables/header-stability.d.ts +19 -0
  242. package/lib/archive/populate-ref-tables/header-stability.js +22 -0
  243. package/lib/archive/populate-ref-tables/header-value-cache-key.d.ts +17 -0
  244. package/lib/archive/populate-ref-tables/header-value-cache-key.js +19 -0
  245. package/lib/archive/populate-ref-tables/normalize-mime.d.ts +24 -0
  246. package/lib/archive/populate-ref-tables/normalize-mime.js +36 -0
  247. package/lib/archive/populate-ref-tables/populate-blob-refs.d.ts +38 -0
  248. package/lib/archive/populate-ref-tables/populate-blob-refs.js +134 -0
  249. package/lib/archive/populate-ref-tables/populate-content-type-refs.d.ts +27 -0
  250. package/lib/archive/populate-ref-tables/populate-content-type-refs.js +70 -0
  251. package/lib/archive/populate-ref-tables/populate-header-tables.d.ts +35 -0
  252. package/lib/archive/populate-ref-tables/populate-header-tables.js +80 -0
  253. package/lib/archive/populate-ref-tables/populate-json-refs.d.ts +29 -0
  254. package/lib/archive/populate-ref-tables/populate-json-refs.js +101 -0
  255. package/lib/archive/populate-ref-tables/populate-refs.d.ts +51 -0
  256. package/lib/archive/populate-ref-tables/populate-refs.js +62 -0
  257. package/lib/archive/populate-ref-tables/populate-text-refs.d.ts +32 -0
  258. package/lib/archive/populate-ref-tables/populate-text-refs.js +133 -0
  259. package/lib/archive/populate-ref-tables/populate-url-refs.d.ts +28 -0
  260. package/lib/archive/populate-ref-tables/populate-url-refs.js +148 -0
  261. package/lib/archive/populate-ref-tables/test-utils/count-rows.d.ts +15 -0
  262. package/lib/archive/populate-ref-tables/test-utils/count-rows.js +17 -0
  263. package/lib/archive/populate-ref-tables/types.d.ts +197 -0
  264. package/lib/archive/populate-ref-tables/types.js +7 -0
  265. package/lib/archive/populate-ref-tables/upsert-one-header-set.d.ts +34 -0
  266. package/lib/archive/populate-ref-tables/upsert-one-header-set.js +208 -0
  267. package/lib/archive/populate-ref-tables/volatile-header-names.d.ts +20 -0
  268. package/lib/archive/populate-ref-tables/volatile-header-names.js +33 -0
  269. package/lib/archive/redirect-table.d.ts +4 -2
  270. package/lib/archive/redirect-table.js +15 -10
  271. package/lib/archive/resolve-redirect-chain.d.ts +3 -3
  272. package/lib/archive/resolve-redirect-chain.js +2 -2
  273. package/lib/archive/resource.d.ts +1 -1
  274. package/lib/archive/retarget-legacy-fk-tables.d.ts +47 -0
  275. package/lib/archive/retarget-legacy-fk-tables.js +107 -0
  276. package/lib/archive/test-utils/fk-parent-tables.d.ts +15 -0
  277. package/lib/archive/test-utils/fk-parent-tables.js +19 -0
  278. package/lib/archive/test-utils/seed-content-item.d.ts +35 -0
  279. package/lib/archive/test-utils/seed-content-item.js +42 -0
  280. package/lib/archive/test-utils/setup-legacy-fk-db.d.ts +33 -0
  281. package/lib/archive/test-utils/setup-legacy-fk-db.js +270 -0
  282. package/lib/archive/types.d.ts +127 -24
  283. package/lib/archive/verify-migration/capture-rejection.d.ts +24 -0
  284. package/lib/archive/verify-migration/capture-rejection.js +31 -0
  285. package/lib/archive/verify-migration/check-anchor-edges-count.d.ts +34 -0
  286. package/lib/archive/verify-migration/check-anchor-edges-count.js +72 -0
  287. package/lib/archive/verify-migration/check-anchor-edges-sum.d.ts +13 -0
  288. package/lib/archive/verify-migration/check-anchor-edges-sum.js +27 -0
  289. package/lib/archive/verify-migration/check-content-items-count.d.ts +16 -0
  290. package/lib/archive/verify-migration/check-content-items-count.js +30 -0
  291. package/lib/archive/verify-migration/check-content-type-preservation.d.ts +22 -0
  292. package/lib/archive/verify-migration/check-content-type-preservation.js +40 -0
  293. package/lib/archive/verify-migration/check-foreign-key-integrity.d.ts +31 -0
  294. package/lib/archive/verify-migration/check-foreign-key-integrity.js +47 -0
  295. package/lib/archive/verify-migration/check-image-items-count.d.ts +12 -0
  296. package/lib/archive/verify-migration/check-image-items-count.js +26 -0
  297. package/lib/archive/verify-migration/check-page-meta-count.d.ts +15 -0
  298. package/lib/archive/verify-migration/check-page-meta-count.js +31 -0
  299. package/lib/archive/verify-migration/check-reader-parity.d.ts +23 -0
  300. package/lib/archive/verify-migration/check-reader-parity.js +211 -0
  301. package/lib/archive/verify-migration/check-resource-items-count.d.ts +17 -0
  302. package/lib/archive/verify-migration/check-resource-items-count.js +33 -0
  303. package/lib/archive/verify-migration/check-url-round-trip.d.ts +43 -0
  304. package/lib/archive/verify-migration/check-url-round-trip.js +112 -0
  305. package/lib/archive/verify-migration/types.d.ts +70 -0
  306. package/lib/archive/verify-migration/types.js +63 -0
  307. package/lib/archive/verify-migration/verify-migration.d.ts +41 -0
  308. package/lib/archive/verify-migration/verify-migration.js +120 -0
  309. package/lib/crawler/build-redirect-event.d.ts +1 -1
  310. package/lib/crawler/build-redirect-event.js +1 -1
  311. package/lib/crawler/capture-image-dom-paths.d.ts +33 -0
  312. package/lib/crawler/capture-image-dom-paths.js +39 -0
  313. package/lib/crawler/clear-dns-burned-host-cache.d.ts +1 -1
  314. package/lib/crawler/clear-dns-burned-host-cache.js +1 -1
  315. package/lib/crawler/collect-image-dom-paths.d.ts +23 -0
  316. package/lib/crawler/collect-image-dom-paths.js +64 -0
  317. package/lib/crawler/crawler.d.ts +19 -0
  318. package/lib/crawler/crawler.js +40 -26
  319. package/lib/crawler/dns-burned-host-cache.d.ts +3 -3
  320. package/lib/crawler/dns-burned-host-cache.js +3 -3
  321. package/lib/crawler/dns-burned-host-short-circuit-counter.d.ts +2 -2
  322. package/lib/crawler/dns-burned-host-short-circuit-counter.js +2 -2
  323. package/lib/crawler/inject-scope-auth.d.ts +1 -1
  324. package/lib/crawler/inject-scope-auth.js +1 -1
  325. package/lib/crawler/normalize-content-type.d.ts +1 -1
  326. package/lib/crawler/normalize-content-type.js +1 -1
  327. package/lib/crawler/types.d.ts +3 -3
  328. package/lib/crawler-orchestrator.d.ts +9 -0
  329. package/lib/crawler-orchestrator.js +44 -28
  330. package/lib/crawler.d.ts +12 -0
  331. package/lib/crawler.js +21 -0
  332. package/lib/permanent-error-kinds.d.ts +1 -1
  333. package/lib/permanent-error-kinds.js +1 -1
  334. package/lib/types.d.ts +1 -1
  335. package/lib/utils/compute-file-sha256.d.ts +5 -4
  336. package/lib/utils/compute-file-sha256.js +5 -4
  337. package/lib/utils/error/emit-error-with-retry.d.ts +1 -1
  338. package/lib/utils/error/emit-error-with-retry.js +1 -1
  339. package/package.json +10 -10
  340. package/lib/archive/migrate-crawl-errors.d.ts +0 -20
  341. package/lib/archive/migrate-crawl-errors.js +0 -38
  342. package/lib/archive/migrate-html-blob-tables.d.ts +0 -24
  343. package/lib/archive/migrate-html-blob-tables.js +0 -53
  344. package/lib/archive/migrate-inventory-runs.d.ts +0 -29
  345. package/lib/archive/migrate-inventory-runs.js +0 -52
  346. package/lib/archive/migrate-page-errors.d.ts +0 -16
  347. package/lib/archive/migrate-page-errors.js +0 -35
  348. package/lib/archive/migrate-pages-resources-source.d.ts +0 -16
  349. package/lib/archive/migrate-pages-resources-source.js +0 -46
@@ -0,0 +1,334 @@
1
+ import { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
2
+ import { isHtmlContentType } from '../../../../crawler/is-html-content-type.js';
3
+ import { eachSplitted } from '../../../../utils/array/each-splitted.js';
4
+ import { dbLog } from '../../../debug.js';
5
+ import { deriveLineageFromParent } from '../../../derive-lineage-from-parent.js';
6
+ import { matchImagesToDomPaths } from '../../../populate-entity-tables/match-images-to-dom-paths.js';
7
+ import { upsertTextRefs } from '../../../populate-entity-tables/upsert-text-refs.js';
8
+ import { resolveRedirectChain } from '../../../resolve-redirect-chain.js';
9
+ import { clearWriteRefCaches } from '../../_shared/clear-write-ref-caches.js';
10
+ import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
11
+ import { resolveUrlOrBlob } from '../../_shared/resolve-url-or-blob.js';
12
+ import { insertJsonLd } from './insert-jsonld.js';
13
+ import { insertPage } from './insert-page.js';
14
+ import { insertTags } from './insert-tags.js';
15
+ import { linkRedirectSources } from './link-redirect-sources.js';
16
+ import { writePageHtmlBlob } from './write-page-html-blob.js';
17
+ /**
18
+ * Inserts or updates a crawled page — `content_items` + `page_meta` plus its
19
+ * redirect chain, `anchor_edges`, `image_items`, and (when `writeHtml`) its
20
+ * compressed HTML snapshot BLOB.
21
+ *
22
+ * Self-redirects (where the source URL equals the destination URL after
23
+ * normalization) are skipped to avoid marking a page as redirected to itself
24
+ * — a situation caused by authentication challenges (e.g. Basic Auth 302)
25
+ * that would otherwise exclude the page from reports via the
26
+ * `whereNull('redirect_dest_id')` filter.
27
+ * @param knex - Knex query builder connected to the archive DB.
28
+ * @param caches - The connection's write-side id caches.
29
+ * @param page - The page data to store, optionally carrying the in-browser
30
+ * dom-path capture (see {@link PageDataWithDomPaths}).
31
+ * @param writeHtml - When `true`, this call is allowed to insert (or clear)
32
+ * the page's HTML blob. `setExternalPage` passes `false` because external
33
+ * metadata-only scrapes never carry HTML and must not perturb an already
34
+ * stored body.
35
+ * @param isTarget - Whether this page is a crawl target.
36
+ * @param source - Provenance label written ONLY when the row is freshly
37
+ * inserted. Existing rows keep their original `source` (this is why a
38
+ * second `crawl --inventory` does not "demote" an `'inventory-seed'` row
39
+ * that was discovered earlier).
40
+ * @returns The database `pageId` (`content_items.id`) of the inserted or updated row.
41
+ */
42
+ export async function updatePage(knex, caches, page, writeHtml, isTarget, source) {
43
+ const { destUrl, sources } = resolveRedirectChain(page.url.withoutHashAndAuth, page.redirectPaths);
44
+ const destUrlObject = parseUrl(destUrl);
45
+ if (!destUrlObject) {
46
+ throw new Error(`Failed to parse URL: ${destUrl}`);
47
+ }
48
+ try {
49
+ return await knex.transaction(async (trx) => {
50
+ return await updatePageInTransaction(trx, knex, caches, page, destUrlObject, sources, writeHtml, isTarget, source);
51
+ });
52
+ }
53
+ catch (error) {
54
+ // A rolled-back transaction can leave ids cached that no longer
55
+ // correspond to any row (AUTOINCREMENT never rewinds) — see
56
+ // `clearWriteRefCaches` for why a full clear, not a partial one,
57
+ // is required. `emitErrorAndRetry` may retry this whole call, so
58
+ // the cache must be clean before the next attempt.
59
+ clearWriteRefCaches(caches);
60
+ throw error;
61
+ }
62
+ }
63
+ /**
64
+ * The transactional body of {@link updatePage}, extracted so the
65
+ * transaction boundary in the caller can wrap it in a try/catch for
66
+ * cache invalidation on rollback.
67
+ * @param trx - The active transaction.
68
+ * @param knex - Knex query builder connected to the archive DB (passed
69
+ * through to `insertPage` for its `trx ?? knex` fallback contract).
70
+ * @param caches - The connection's write-side id caches.
71
+ * @param page - The page data to store.
72
+ * @param destUrlObject - The parsed redirect-chain destination URL.
73
+ * @param sources - Redirect-source URLs from the chain resolution.
74
+ * @param writeHtml - See {@link updatePage}.
75
+ * @param isTarget - See {@link updatePage}.
76
+ * @param source - See {@link updatePage}.
77
+ * @returns The `content_items.id` of the inserted or updated row.
78
+ */
79
+ async function updatePageInTransaction(trx, knex, caches, page, destUrlObject, sources, writeHtml, isTarget, source) {
80
+ const pageId = await insertPage(knex, caches, {
81
+ ...page,
82
+ url: destUrlObject,
83
+ }, isTarget, trx, source);
84
+ // Wappalyzer tag detection is HTML-body independent (relies on
85
+ // `<script src>` / `<iframe src>` / window globals / response
86
+ // headers) so it runs for every page including external /
87
+ // metadata-only. JSON-LD on the other hand lives inside the
88
+ // rendered HTML body, so we only write it when there is HTML to
89
+ // scrape — see the same `writeHtml` gate as `writePageHtmlBlob`
90
+ // below.
91
+ await insertTags(pageId, page.meta, trx);
92
+ if (writeHtml) {
93
+ await insertJsonLd(pageId, page.meta, trx);
94
+ }
95
+ // Chain lineage propagates FROM the originating URL
96
+ // (`page.url`), NOT from the destination. See the matching
97
+ // rationale in `recordRedirect`: intermediates are reached
98
+ // transitively from the originating URL's render, so they
99
+ // inherit its lineage. The `source` argument is the
100
+ // authoritative origin label when inventoryMode is live; fall
101
+ // through to a DB lookup of `page.url` for the resume /
102
+ // retry-failed path where the call-site has no source.
103
+ let originatingSource = source;
104
+ if (originatingSource === undefined) {
105
+ originatingSource = await readSourceByUrl(trx, caches, page.url.withoutHashAndAuth);
106
+ }
107
+ const chainLineageSource = deriveLineageFromParent(originatingSource, 'crawled');
108
+ await linkRedirectSources(trx, caches, sources, pageId, destUrlObject.withoutHashAndAuth, page.isExternal, chainLineageSource);
109
+ // Only insert a snapshot blob when there is actual HTML to write.
110
+ // `page.html.length > 0` is the precise signal: the scraper returns
111
+ // `html: ''` for everything that is not a rendered `text/html` document
112
+ // (non-HTML responses, metadata-only, external, degraded renders), so a
113
+ // non-empty `html` is exactly "a rendered HTML body exists". Gating on
114
+ // `isTarget` alone would store an empty body for every internal non-HTML
115
+ // resource — PDF / zip / images are isTarget=1 (#72).
116
+ //
117
+ // `isTarget` is intentionally NOT part of this condition: it is implied by
118
+ // `html.length > 0` (only in-scope target pages are browser-rendered into a
119
+ // non-empty body; metadata-only and external pages carry `html: ''`), so the
120
+ // content check alone expresses the intent without a redundant term.
121
+ if (writeHtml && page.html.length > 0) {
122
+ await writePageHtmlBlob(pageId, page.html, trx);
123
+ }
124
+ else if (writeHtml &&
125
+ page.contentType !== null &&
126
+ !isHtmlContentType(page.contentType)) {
127
+ // The page is now a *known* non-HTML type. If a previous scrape stored
128
+ // an HTML body for this URL (e.g. it served HTML then was replaced by
129
+ // a PDF across `crawl --resume` / `--append`), drop the stale ref so
130
+ // `page_html_ref` never contradicts the content type. A degraded HTML
131
+ // re-scrape (text/html or unknown content type with empty html) is NOT
132
+ // cleared — the last good snapshot is preserved, mirroring the
133
+ // anchors / images empty-guard below. Gated on `writeHtml` because a
134
+ // stale ref can only have been written by a snapshot-capable call
135
+ // (`setPage`); `setExternalPage` passes `writeHtml = false` and never
136
+ // sets `html`, so it has nothing to clear.
137
+ await trx('page_html_ref').where('page_id', pageId).delete();
138
+ }
139
+ // Re-scrape semantics: the same URL can be scraped more than once
140
+ // (e.g. `crawl --resume`, re-visits, `--append` re-promotion). Edges
141
+ // are replaced wholesale per page on every non-empty scrape — the
142
+ // delete is paired with, and guarded by, a non-empty new list: a
143
+ // degraded re-scrape (navigation timeout / partial render) can return
144
+ // an empty `anchorList` for a page that previously had links, and
145
+ // wiping the prior good data in that case would be destructive. We
146
+ // cannot tell a transient empty result apart from a page that has
147
+ // legitimately lost all its links, so we err on the side of keeping
148
+ // what we already had. The accepted trade-off is that a page which
149
+ // genuinely dropped to zero links keeps its stale rows until the next
150
+ // non-empty re-scrape replaces them (#70).
151
+ //
152
+ // Lineage propagation: read the current page's merged source
153
+ // (post-UPDATE by `insertPage`) so anchor placeholder rows inherit a
154
+ // label that reflects the parent's chain. A `'crawled'`-lineage
155
+ // parent passes `'crawled'` explicitly so the crawled-wins downgrade
156
+ // in `resolveContentItemId` fires when an anchor hits an existing
157
+ // `'inventory-*'` row. An inventory-lineage parent passes
158
+ // `'inventory-discovered'` to label transitively-reached URLs
159
+ // correctly without the orchestrator needing to rehydrate
160
+ // `inventoryMode` from disk.
161
+ const [parentRow] = (await trx
162
+ .select('source')
163
+ .from('content_items')
164
+ .where('id', pageId));
165
+ const anchorLineageSource = deriveLineageFromParent(parentRow?.source, 'crawled');
166
+ await replaceAnchorEdges(trx, caches, pageId, page, anchorLineageSource);
167
+ await replaceImageItems(trx, caches, pageId, page);
168
+ // Clear this page's resource_ref_edges unconditionally (no non-empty
169
+ // guard, unlike anchors/images above): the crawler always emits this
170
+ // page's `responseReferrers` events right after its `page` event (see
171
+ // `Crawler#handleResources`, called immediately after `#handleResult`
172
+ // for the same scrape), through the same serialized WriteQueue, so the
173
+ // fresh INSERT is guaranteed to follow this DELETE in commit order —
174
+ // no writer can interleave a stale re-insert between them. This is a
175
+ // write-ordering guarantee only: the DELETE and the follow-up INSERT
176
+ // are still two separate transactions, so a concurrent read-only
177
+ // connection (viewer / MCP open on the same archive during an active
178
+ // `--append` / `--retry-failed` run) can observe a momentary window
179
+ // with zero rows for this page and misclassify its resources as
180
+ // unused. The window closes as soon as the next transaction commits,
181
+ // so this is a transient display artifact, not a durable data loss —
182
+ // accepted rather than merging the two writes into one transaction.
183
+ // A degraded re-scrape that legitimately captures zero sub-resources
184
+ // leaves this page referrer-less until its next non-empty re-scrape —
185
+ // accepted, since resource_items rows for no-longer-referenced
186
+ // resources are themselves allowed to become orphaned (no cross-page
187
+ // cleanup is attempted for those either).
188
+ await trx('resource_ref_edges').where('page_id', pageId).delete();
189
+ return pageId;
190
+ }
191
+ /**
192
+ * Reads the `source` of the row identified by `url` without creating a
193
+ * placeholder — the cache-first mirror of the legacy by-URL SELECT.
194
+ * Returns `undefined` when no row exists yet.
195
+ * @param trx - The active transaction.
196
+ * @param caches - The connection's write-side id caches (read-only here;
197
+ * a DB fallback does not populate the identity cache because the id is
198
+ * not needed).
199
+ * @param url - Normalised URL key.
200
+ */
201
+ async function readSourceByUrl(trx, caches, url) {
202
+ const cached = caches.contentItems.get(url);
203
+ if (cached !== undefined) {
204
+ return cached.source;
205
+ }
206
+ const [row] = (await trx
207
+ .select('ci.source')
208
+ .from('content_items as ci')
209
+ .join('url_refs as ur', 'ur.id', 'ci.url_id')
210
+ .where('ur.url', url));
211
+ return row?.source;
212
+ }
213
+ /**
214
+ * Replaces the page's `anchor_edges` rows with the aggregate of the new
215
+ * scrape's `anchorList` (empty-guarded; see the caller's re-scrape
216
+ * comment). Instances sharing the same resolved `href_page_id` collapse
217
+ * into one edge whose `count` is the instance count and whose
218
+ * `first_hash` / `first_text_id` capture the first instance encountered
219
+ * in list order — the same first-wins rule the archive migration applies
220
+ * to legacy `anchors` rows.
221
+ * @param trx - The active transaction.
222
+ * @param caches - The connection's write-side id caches.
223
+ * @param pageId - The owning `content_items.id`.
224
+ * @param page - The scraped page payload.
225
+ * @param anchorLineageSource - Lineage label for anchor-target
226
+ * placeholder rows (see the caller's lineage comment).
227
+ */
228
+ async function replaceAnchorEdges(trx, caches, pageId, page, anchorLineageSource) {
229
+ if (page.anchorList.length === 0) {
230
+ return;
231
+ }
232
+ const edges = new Map();
233
+ for (const anchor of page.anchorList) {
234
+ const hrefId = await resolveContentItemId(trx, caches, anchor.href.withoutHashAndAuth, anchor.isExternal ? 1 : 0, anchorLineageSource);
235
+ const existing = edges.get(hrefId);
236
+ if (existing !== undefined) {
237
+ existing.count += 1;
238
+ continue;
239
+ }
240
+ edges.set(hrefId, {
241
+ count: 1,
242
+ firstHash: anchor.href.hash || null,
243
+ firstText: anchor.textContent || null,
244
+ });
245
+ }
246
+ const texts = new Set();
247
+ for (const edge of edges.values()) {
248
+ if (edge.firstText !== null) {
249
+ texts.add(edge.firstText);
250
+ }
251
+ }
252
+ const textIds = await upsertTextRefs(trx, texts);
253
+ dbLog('Replace anchor_edges: %d edges (%d instances)', edges.size, page.anchorList.length);
254
+ await trx('anchor_edges').where('page_id', pageId).delete();
255
+ const rows = [...edges.entries()].map(([hrefPageId, edge]) => ({
256
+ page_id: pageId,
257
+ href_page_id: hrefPageId,
258
+ count: edge.count,
259
+ first_hash: edge.firstHash,
260
+ first_text_id: edge.firstText === null ? null : (textIds.get(edge.firstText) ?? null),
261
+ }));
262
+ await eachSplitted(rows, 100, async (chunk) => {
263
+ await trx('anchor_edges').insert(chunk);
264
+ });
265
+ }
266
+ /**
267
+ * Replaces the page's `image_items` rows with the new scrape's
268
+ * `imageList` (empty-guarded; see the caller's re-scrape comment).
269
+ * `src` / `currentSrc` route to `url_refs` or (for large `data:` URIs)
270
+ * `blob_refs`; `dom_path_text_id` resolves by matching each image's
271
+ * `sourceCode` against the in-browser capture carried on
272
+ * `page.imageDomPaths`, falling back to the synthetic `unknown/<n>`
273
+ * marker per image when the capture is absent or ambiguous.
274
+ * @param trx - The active transaction.
275
+ * @param caches - The connection's write-side id caches.
276
+ * @param pageId - The owning `content_items.id`.
277
+ * @param page - The scraped page payload.
278
+ */
279
+ async function replaceImageItems(trx, caches, pageId, page) {
280
+ if (page.imageList.length === 0) {
281
+ return;
282
+ }
283
+ // Match by list index: ids only need to be unique within this call and
284
+ // ordered in insertion order, which the list order is by construction.
285
+ const domPaths = matchImagesToDomPaths(page.imageList.map((image, index) => ({
286
+ id: index,
287
+ sourceCode: image.sourceCode || null,
288
+ })), page.imageDomPaths ?? []);
289
+ const texts = new Set();
290
+ for (const image of page.imageList) {
291
+ if (typeof image.alt === 'string' && image.alt !== '') {
292
+ texts.add(image.alt);
293
+ }
294
+ }
295
+ for (const entry of domPaths.values()) {
296
+ texts.add(entry.path);
297
+ }
298
+ const textIds = await upsertTextRefs(trx, texts);
299
+ const rows = [];
300
+ for (const [index, image] of page.imageList.entries()) {
301
+ const domPath = domPaths.get(index);
302
+ if (domPath === undefined) {
303
+ throw new Error(`replaceImageItems: dom_path not resolved for image #${index}`);
304
+ }
305
+ const domPathId = textIds.get(domPath.path);
306
+ if (domPathId === undefined) {
307
+ throw new Error(`replaceImageItems: text_refs.id not resolved for dom_path=${domPath.path}`);
308
+ }
309
+ const srcSlot = await resolveUrlOrBlob(trx, caches, image.src);
310
+ const currentSrcSlot = await resolveUrlOrBlob(trx, caches, image.currentSrc);
311
+ rows.push({
312
+ page_id: pageId,
313
+ src_url_id: srcSlot.url,
314
+ current_src_url_id: currentSrcSlot.url,
315
+ src_blob_id: srcSlot.blob,
316
+ current_src_blob_id: currentSrcSlot.blob,
317
+ alt_text_id: typeof image.alt === 'string' && image.alt !== ''
318
+ ? (textIds.get(image.alt) ?? null)
319
+ : null,
320
+ width: image.width,
321
+ height: image.height,
322
+ natural_width: image.naturalWidth,
323
+ natural_height: image.naturalHeight,
324
+ is_lazy: image.isLazy == null ? null : image.isLazy ? 1 : 0,
325
+ viewport_width: image.viewportWidth,
326
+ dom_path_text_id: domPathId,
327
+ });
328
+ }
329
+ dbLog('Replace image_items: %d rows', rows.length);
330
+ await trx('image_items').where('page_id', pageId).delete();
331
+ await eachSplitted(rows, 100, async (chunk) => {
332
+ await trx('image_items').insert(chunk);
333
+ });
334
+ }
@@ -0,0 +1,19 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Encodes, dedups, and persists a page's HTML snapshot.
4
+ *
5
+ * Computes SHA-256 over the raw UTF-8 bytes, compresses them with zstd,
6
+ * inserts into `page_html_blobs` only if the hash is new (so identical
7
+ * bodies — 404 templates, error pages, redirect destinations — share a
8
+ * single row), and then upserts `page_html_ref(page_id → hash)` so the
9
+ * latest scrape always points at the right body.
10
+ *
11
+ * Runs entirely inside the caller's transaction; a failure here rolls
12
+ * back the rest of `updatePage`, which is the desired semantics (an
13
+ * archive that lost its HTML for a page would otherwise serve stale
14
+ * meta against a missing body).
15
+ * @param pageId - The database id of the page.
16
+ * @param html - The raw HTML string (UTF-8).
17
+ * @param trx - The active transaction.
18
+ */
19
+ export declare function writePageHtmlBlob(pageId: number, html: string, trx: Knex.Transaction): Promise<void>;
@@ -0,0 +1,41 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { zstdCompressSync } from 'node:zlib';
3
+ /**
4
+ * Encodes, dedups, and persists a page's HTML snapshot.
5
+ *
6
+ * Computes SHA-256 over the raw UTF-8 bytes, compresses them with zstd,
7
+ * inserts into `page_html_blobs` only if the hash is new (so identical
8
+ * bodies — 404 templates, error pages, redirect destinations — share a
9
+ * single row), and then upserts `page_html_ref(page_id → hash)` so the
10
+ * latest scrape always points at the right body.
11
+ *
12
+ * Runs entirely inside the caller's transaction; a failure here rolls
13
+ * back the rest of `updatePage`, which is the desired semantics (an
14
+ * archive that lost its HTML for a page would otherwise serve stale
15
+ * meta against a missing body).
16
+ * @param pageId - The database id of the page.
17
+ * @param html - The raw HTML string (UTF-8).
18
+ * @param trx - The active transaction.
19
+ */
20
+ export async function writePageHtmlBlob(pageId, html, trx) {
21
+ const rawBytes = Buffer.from(html, 'utf8');
22
+ const hash = createHash('sha256').update(rawBytes).digest();
23
+ const compressed = zstdCompressSync(rawBytes);
24
+ await trx('page_html_blobs')
25
+ .insert({
26
+ hash,
27
+ body: compressed,
28
+ codec: 'zstd',
29
+ size_raw: rawBytes.byteLength,
30
+ size_stored: compressed.byteLength,
31
+ })
32
+ .onConflict('hash')
33
+ .ignore();
34
+ // Upsert so a re-scrape's body cleanly supersedes the prior pointer.
35
+ // The old blob row is intentionally left in place — a future #23 GC
36
+ // pass will sweep unreachable hashes.
37
+ await trx('page_html_ref')
38
+ .insert({ page_id: pageId, hash })
39
+ .onConflict('page_id')
40
+ .merge(['hash']);
41
+ }
@@ -0,0 +1,9 @@
1
+ import type { DB_Redirect } from '../../types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Retrieves redirect sources for the given page IDs in bulk.
5
+ * @param knex - Knex query builder connected to the archive DB.
6
+ * @param pageIds - The database IDs of the destination pages.
7
+ * @returns An array of {@link DB_Redirect} records mapping destination pages to their redirect sources.
8
+ */
9
+ export declare function getRedirectsForPages(knex: Knex, pageIds: number[]): Promise<DB_Redirect[]>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Retrieves redirect sources for the given page IDs in bulk.
3
+ * @param knex - Knex query builder connected to the archive DB.
4
+ * @param pageIds - The database IDs of the destination pages.
5
+ * @returns An array of {@link DB_Redirect} records mapping destination pages to their redirect sources.
6
+ */
7
+ export async function getRedirectsForPages(knex, pageIds) {
8
+ if (pageIds.length === 0)
9
+ return [];
10
+ return knex
11
+ .select('content_items.redirect_dest_id as pageId', 'url_refs.url as from', 'content_items.id as fromId')
12
+ .from('content_items')
13
+ .join('url_refs', 'url_refs.id', '=', 'content_items.url_id')
14
+ .whereIn('content_items.redirect_dest_id', pageIds);
15
+ }
@@ -0,0 +1,17 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Retrieves pages that link to a specific page (incoming links / referrers).
4
+ *
5
+ * Incoming links are resolved **through redirects**: an anchor pointing at a
6
+ * redirect source (e.g. `http://x` that 301s to `https://x`) counts as a
7
+ * referrer of the redirect's final destination, not of the source. This keeps
8
+ * backlinks merged on the canonical page instead of splitting them across the
9
+ * `http`/`https` (or any redirect source/dest) pair. The resolution mirrors
10
+ * `getPagesWithRels`' redirect handling — `redirect_dest_id` is
11
+ * pre-flattened to the final destination, so
12
+ * `COALESCE(target.redirect_dest_id, target.id)` is a single hop.
13
+ * @param knex - Knex query builder connected to the archive DB.
14
+ * @param pageId - The database ID of the target page.
15
+ * @returns An array of referrer records with URL, hash, and text content.
16
+ */
17
+ export declare function getReferrersOfPage(knex: Knex, pageId: number): Promise<any[]>;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Retrieves pages that link to a specific page (incoming links / referrers).
3
+ *
4
+ * Incoming links are resolved **through redirects**: an anchor pointing at a
5
+ * redirect source (e.g. `http://x` that 301s to `https://x`) counts as a
6
+ * referrer of the redirect's final destination, not of the source. This keeps
7
+ * backlinks merged on the canonical page instead of splitting them across the
8
+ * `http`/`https` (or any redirect source/dest) pair. The resolution mirrors
9
+ * `getPagesWithRels`' redirect handling — `redirect_dest_id` is
10
+ * pre-flattened to the final destination, so
11
+ * `COALESCE(target.redirect_dest_id, target.id)` is a single hop.
12
+ * @param knex - Knex query builder connected to the archive DB.
13
+ * @param pageId - The database ID of the target page.
14
+ * @returns An array of referrer records with URL, hash, and text content.
15
+ */
16
+ export async function getReferrersOfPage(knex, pageId) {
17
+ const res = await knex
18
+ .select('referrer_url.url as url',
19
+ // `through` / `throughId` = the URL the anchor actually pointed at (the
20
+ // redirect source, e.g. `http://x`), mirroring `getPagesWithRels`'
21
+ // `redirect.from` / `redirect.fromId`. Lets report code print the
22
+ // "[REDIRECTED FROM]" note even on this (non-preloaded) referrer path.
23
+ 'target_url.url as through', 'target.id as throughId', 'anchor_edges.first_hash as hash', 'first_text.text as textContent')
24
+ .from('anchor_edges')
25
+ .join('content_items as referrer', 'anchor_edges.page_id', '=', 'referrer.id')
26
+ .join('url_refs as referrer_url', 'referrer.url_id', '=', 'referrer_url.id')
27
+ .join('content_items as target', 'anchor_edges.href_page_id', '=', 'target.id')
28
+ .join('url_refs as target_url', 'target.url_id', '=', 'target_url.id')
29
+ .leftJoin('text_refs as first_text', 'anchor_edges.first_text_id', '=', 'first_text.id')
30
+ .whereRaw('coalesce("target"."redirect_dest_id", "target"."id") = ?', [pageId]);
31
+ return res;
32
+ }
@@ -0,0 +1,8 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Retrieves the page URLs that reference a specific resource.
4
+ * @param knex - Knex query builder connected to the archive DB.
5
+ * @param id - The database ID of the resource.
6
+ * @returns An array of page URL strings that reference the resource.
7
+ */
8
+ export declare function getReferrersOfResource(knex: Knex, id: number): Promise<string[]>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Retrieves the page URLs that reference a specific resource.
3
+ * @param knex - Knex query builder connected to the archive DB.
4
+ * @param id - The database ID of the resource.
5
+ * @returns An array of page URL strings that reference the resource.
6
+ */
7
+ export async function getReferrersOfResource(knex, id) {
8
+ const res = await knex
9
+ .select('url_refs.url as url')
10
+ .from('resource_ref_edges')
11
+ .join('content_items', 'content_items.id', '=', 'resource_ref_edges.page_id')
12
+ .join('url_refs', 'url_refs.id', '=', 'content_items.url_id')
13
+ .where('resource_ref_edges.resource_id', id);
14
+ return res.map((r) => r.url);
15
+ }
@@ -0,0 +1,25 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Builds a `resource_items` query joined against `url_refs` and
4
+ * `content_type_refs`, reconstructing a flat, legacy-shaped resource row.
5
+ * Selected columns are aliased to `DB_Resource` field names. `responseHeaders`
6
+ * is NOT reconstructed here (it needs a second, batched pass — see
7
+ * {@link ../resources/reconstruct-resource-rows.js}) so this query stays a
8
+ * single-pass join with no N+1 subqueries; `headerSetId` is selected as an
9
+ * intermediate for that second pass.
10
+ *
11
+ * The `url_refs` join is a LEFT JOIN, not INNER: a resource whose own
12
+ * identity URL is a large `data:` URI has `url_id = NULL` (routed to
13
+ * `url_blob_id` / `blob_refs` instead — see `create-entity-tables.ts`'s
14
+ * `resource_items url / blob mutual-exclusion CHECK`). An INNER JOIN
15
+ * would silently drop such rows from every reader. `url` reads back as
16
+ * `null` for those rows, matching the established `image_items` src /
17
+ * blob convention (`list-images.ts`) — recovering the raw data URI would
18
+ * require zstd-decompression in JS, which no current caller needs.
19
+ * @param knex - Knex query builder connected to the archive DB.
20
+ * @returns A query builder pre-configured with the joins `get-resources.ts` /
21
+ * `get-resource-by-url.ts` need; callers add `.where()` on top.
22
+ * @example
23
+ * const rows = await buildResourceQuery(knex).whereIn('ur.url', urls);
24
+ */
25
+ export declare function buildResourceQuery(knex: Knex): Knex.QueryBuilder;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Builds a `resource_items` query joined against `url_refs` and
3
+ * `content_type_refs`, reconstructing a flat, legacy-shaped resource row.
4
+ * Selected columns are aliased to `DB_Resource` field names. `responseHeaders`
5
+ * is NOT reconstructed here (it needs a second, batched pass — see
6
+ * {@link ../resources/reconstruct-resource-rows.js}) so this query stays a
7
+ * single-pass join with no N+1 subqueries; `headerSetId` is selected as an
8
+ * intermediate for that second pass.
9
+ *
10
+ * The `url_refs` join is a LEFT JOIN, not INNER: a resource whose own
11
+ * identity URL is a large `data:` URI has `url_id = NULL` (routed to
12
+ * `url_blob_id` / `blob_refs` instead — see `create-entity-tables.ts`'s
13
+ * `resource_items url / blob mutual-exclusion CHECK`). An INNER JOIN
14
+ * would silently drop such rows from every reader. `url` reads back as
15
+ * `null` for those rows, matching the established `image_items` src /
16
+ * blob convention (`list-images.ts`) — recovering the raw data URI would
17
+ * require zstd-decompression in JS, which no current caller needs.
18
+ * @param knex - Knex query builder connected to the archive DB.
19
+ * @returns A query builder pre-configured with the joins `get-resources.ts` /
20
+ * `get-resource-by-url.ts` need; callers add `.where()` on top.
21
+ * @example
22
+ * const rows = await buildResourceQuery(knex).whereIn('ur.url', urls);
23
+ */
24
+ export function buildResourceQuery(knex) {
25
+ return knex('resource_items as ri')
26
+ .leftJoin('url_refs as ur', 'ur.id', 'ri.url_id')
27
+ .leftJoin('content_type_refs as ctr', 'ctr.id', 'ri.content_type_id')
28
+ .select('ri.id as id', 'ur.url as url', 'ri.is_external as isExternal', 'ri.status as status', 'ri.status_text as statusText', 'ctr.raw as contentType', 'ri.content_length as contentLength', 'ri.header_set_id as headerSetId', 'ri.compress as compress', 'ri.cdn as cdn', 'ri.source as source');
29
+ }
@@ -0,0 +1,9 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Return the subset of `urls` that already exist as `resource_items` rows.
4
+ * See `getExistingPageUrls` — same chunking strategy.
5
+ * @param knex - Knex query builder connected to the archive DB.
6
+ * @param urls - URL strings to probe.
7
+ * @returns URLs found among `resource_items`.
8
+ */
9
+ export declare function getExistingResourceUrls(knex: Knex, urls: readonly string[]): Promise<string[]>;
@@ -0,0 +1,24 @@
1
+ import { eachSplitted } from '../../../utils/array/each-splitted.js';
2
+ /**
3
+ * Return the subset of `urls` that already exist as `resource_items` rows.
4
+ * See `getExistingPageUrls` — same chunking strategy.
5
+ * @param knex - Knex query builder connected to the archive DB.
6
+ * @param urls - URL strings to probe.
7
+ * @returns URLs found among `resource_items`.
8
+ */
9
+ export async function getExistingResourceUrls(knex, urls) {
10
+ if (urls.length === 0) {
11
+ return [];
12
+ }
13
+ const found = [];
14
+ await eachSplitted([...urls], 500, async (chunk) => {
15
+ const rows = await knex('resource_items')
16
+ .join('url_refs', 'url_refs.id', 'resource_items.url_id')
17
+ .select('url_refs.url as url')
18
+ .whereIn('url_refs.url', chunk);
19
+ for (const row of rows) {
20
+ found.push(row.url);
21
+ }
22
+ });
23
+ return found;
24
+ }
@@ -0,0 +1,13 @@
1
+ import type { DB_Resource } from '../../types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Retrieves a single sub-resource by its URL.
5
+ *
6
+ * Accepts multiple URL candidates because the stored key is the resource's
7
+ * `href` while callers may only know the hash-stripped form; the first match
8
+ * wins.
9
+ * @param knex - Knex query builder connected to the archive DB.
10
+ * @param urls - URL candidates to match against `url_refs.url`.
11
+ * @returns The reconstructed {@link DB_Resource} row, or `null` if none match.
12
+ */
13
+ export declare function getResourceByUrl(knex: Knex, urls: readonly string[]): Promise<DB_Resource | null>;
@@ -0,0 +1,22 @@
1
+ import { buildResourceQuery } from './build-resource-query.js';
2
+ import { reconstructResourceRows } from './reconstruct-resource-rows.js';
3
+ /**
4
+ * Retrieves a single sub-resource by its URL.
5
+ *
6
+ * Accepts multiple URL candidates because the stored key is the resource's
7
+ * `href` while callers may only know the hash-stripped form; the first match
8
+ * wins.
9
+ * @param knex - Knex query builder connected to the archive DB.
10
+ * @param urls - URL candidates to match against `url_refs.url`.
11
+ * @returns The reconstructed {@link DB_Resource} row, or `null` if none match.
12
+ */
13
+ export async function getResourceByUrl(knex, urls) {
14
+ const row = await buildResourceQuery(knex)
15
+ .whereIn('ur.url', [...urls])
16
+ .first();
17
+ if (!row) {
18
+ return null;
19
+ }
20
+ const [reconstructed] = await reconstructResourceRows(knex, [row]);
21
+ return reconstructed ?? null;
22
+ }
@@ -0,0 +1,9 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Retrieves a flat list of all resource URLs from the `resource_items`
4
+ * table. URL text is normalised into `url_refs`, so the read joins the
5
+ * two tables and returns the resolved strings.
6
+ * @param knex - Knex query builder connected to the archive DB.
7
+ * @returns An array of resource URL strings.
8
+ */
9
+ export declare function getResourceUrlList(knex: Knex): Promise<string[]>;