@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,36 @@
1
+ import type { PageData } from '../../../../utils/types/types.js';
2
+ import type { PageSource } from '../../../types.js';
3
+ import type { WriteRefCaches } from '../../_shared/types.js';
4
+ import type { Knex } from 'knex';
5
+ /**
6
+ * Upserts page data into `content_items` + `page_meta` (inserts the
7
+ * identity row if new, updates if existing).
8
+ *
9
+ * `source` is intentionally NOT in the UPDATE clause — provenance is set
10
+ * once at INSERT time inside `resolveContentItemId`, and existing rows
11
+ * keep whatever label they were first inserted with, except for the
12
+ * priority lattice applied below: `'crawled'` > `'inventory-seed'` >
13
+ * `'inventory-discovered'`. The inventory feature exists to surface
14
+ * orphans (= URLs NOT reachable from the original crawl roots), so
15
+ * anything reachable via the crawled chain must be labelled `'crawled'`
16
+ * even if previously labelled `'inventory-*'`.
17
+ *
18
+ * The page's response headers are decomposed and written into the
19
+ * header dictionary tables here — per response, not deferred to
20
+ * crawl-end — and the resulting `header_set_id` lands on the same
21
+ * `content_items` UPDATE. Meta text / URL / JSON values are interned
22
+ * into `text_refs` / `url_refs` / `json_refs` and `page_meta` stores
23
+ * only the FK ids; on a re-scrape the `page_meta` row is fully replaced
24
+ * (`ON CONFLICT(page_id) DO UPDATE` over every column) exactly as the
25
+ * legacy flat-column UPDATE overwrote every column.
26
+ * @param knex - Knex query builder connected to the archive DB. Used as the
27
+ * fallback when `trx` is not provided.
28
+ * @param caches - The connection's write-side id caches.
29
+ * @param page - The scraped page data.
30
+ * @param isTarget - Whether this page is a crawl target.
31
+ * @param trx - Optional transaction all statements run through.
32
+ * @param source - Inventory provenance for the INSERT path. Ignored on UPDATE
33
+ * except through the priority lattice described above.
34
+ * @returns The `content_items.id` of the inserted or updated row.
35
+ */
36
+ export declare function insertPage(knex: Knex, caches: WriteRefCaches, page: PageData, isTarget: boolean, trx?: Knex.Transaction, source?: PageSource): Promise<number>;
@@ -0,0 +1,208 @@
1
+ import { normalizeContentType } from '../../../../crawler/normalize-content-type.js';
2
+ import { computePageDenormalized } from '../../../meta/compute-page-denormalized.js';
3
+ import { deriveFlatFromMeta } from '../../../meta/derive-flat-from-meta.js';
4
+ import { deriveMetaExtras } from '../../../meta/derive-meta-extras.js';
5
+ import { PAGE_META_COLUMN_MAPS } from '../../../page-meta-column-maps.js';
6
+ import { upsertTextRefs } from '../../../populate-entity-tables/upsert-text-refs.js';
7
+ import { DATA_URI_URL_REFS_LIMIT } from '../../../populate-ref-tables/data-uri-url-refs-limit.js';
8
+ import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
9
+ import { upsertContentTypeRef } from '../../_shared/upsert-content-type-ref.js';
10
+ import { upsertJsonRef } from '../../_shared/upsert-json-ref.js';
11
+ import { upsertResponseHeaders } from '../../_shared/upsert-response-headers.js';
12
+ import { upsertUrlRef } from '../../_shared/upsert-url-ref.js';
13
+ /**
14
+ * Upserts page data into `content_items` + `page_meta` (inserts the
15
+ * identity row if new, updates if existing).
16
+ *
17
+ * `source` is intentionally NOT in the UPDATE clause — provenance is set
18
+ * once at INSERT time inside `resolveContentItemId`, and existing rows
19
+ * keep whatever label they were first inserted with, except for the
20
+ * priority lattice applied below: `'crawled'` > `'inventory-seed'` >
21
+ * `'inventory-discovered'`. The inventory feature exists to surface
22
+ * orphans (= URLs NOT reachable from the original crawl roots), so
23
+ * anything reachable via the crawled chain must be labelled `'crawled'`
24
+ * even if previously labelled `'inventory-*'`.
25
+ *
26
+ * The page's response headers are decomposed and written into the
27
+ * header dictionary tables here — per response, not deferred to
28
+ * crawl-end — and the resulting `header_set_id` lands on the same
29
+ * `content_items` UPDATE. Meta text / URL / JSON values are interned
30
+ * into `text_refs` / `url_refs` / `json_refs` and `page_meta` stores
31
+ * only the FK ids; on a re-scrape the `page_meta` row is fully replaced
32
+ * (`ON CONFLICT(page_id) DO UPDATE` over every column) exactly as the
33
+ * legacy flat-column UPDATE overwrote every column.
34
+ * @param knex - Knex query builder connected to the archive DB. Used as the
35
+ * fallback when `trx` is not provided.
36
+ * @param caches - The connection's write-side id caches.
37
+ * @param page - The scraped page data.
38
+ * @param isTarget - Whether this page is a crawl target.
39
+ * @param trx - Optional transaction all statements run through.
40
+ * @param source - Inventory provenance for the INSERT path. Ignored on UPDATE
41
+ * except through the priority lattice described above.
42
+ * @returns The `content_items.id` of the inserted or updated row.
43
+ */
44
+ export async function insertPage(knex, caches, page, isTarget, trx, source) {
45
+ const qb = trx ?? knex;
46
+ const pageId = await resolveContentItemId(qb, caches, page.url.withoutHashAndAuth, undefined, source);
47
+ const flat = deriveFlatFromMeta(page.meta, page.url.href);
48
+ const denorm = computePageDenormalized(page.meta);
49
+ const extras = deriveMetaExtras(page.meta);
50
+ const now = Date.now();
51
+ // Canonicalize so the stored dictionary value matches the exact-string
52
+ // page-ness predicate (`content_type_refs.raw = 'text/html'`) used by
53
+ // the read layer and the case-insensitive `isHtmlContentType` used in
54
+ // code. Responses are recorded verbatim upstream, so `Text/HTML` /
55
+ // `text/html ` can otherwise fork dictionary rows and silently
56
+ // misclassify.
57
+ const contentType = normalizeContentType(page.contentType);
58
+ const contentTypeId = contentType == null || contentType === ''
59
+ ? null
60
+ : await upsertContentTypeRef(qb, caches, contentType);
61
+ const headerSetId = await upsertResponseHeaders(qb, caches, page.responseHeaders);
62
+ // Source priority on UPDATE: 'crawled' > 'inventory-seed' >
63
+ // 'inventory-discovered'. In current callers, `source` only arrives as
64
+ // `'inventory-seed'` / `'inventory-discovered'` / `undefined`
65
+ // (`derivePageSource` never emits `'crawled'`, and outside inventory
66
+ // mode `source` is `undefined` so this CASE never runs). The
67
+ // `? = 'crawled'` branch is therefore reachable only via a future
68
+ // call site that wants to explicitly assert a crawled lineage —
69
+ // today the actual crawled-wins downgrade fires in
70
+ // `resolveContentItemId` when an anchor lineage `'crawled'` lands on
71
+ // an existing `'inventory-*'` row. The branch is kept so the CASE
72
+ // completely describes the priority lattice in one place.
73
+ const sourceUpdate = source === undefined
74
+ ? {}
75
+ : {
76
+ source: qb.raw(`CASE
77
+ WHEN source = 'crawled' OR ? = 'crawled' THEN 'crawled'
78
+ WHEN source = 'inventory-seed' OR ? = 'inventory-seed' THEN 'inventory-seed'
79
+ WHEN source = 'inventory-discovered' OR ? = 'inventory-discovered' THEN 'inventory-discovered'
80
+ ELSE source
81
+ END`, [source, source, source]),
82
+ };
83
+ await qb('content_items')
84
+ .where('id', pageId)
85
+ .update({
86
+ scraped: 1,
87
+ is_target: isTarget ? 1 : 0,
88
+ is_external: page.isExternal ? 1 : 0,
89
+ status: page.status,
90
+ status_text: page.statusText,
91
+ content_type_id: contentTypeId,
92
+ content_length: page.contentLength,
93
+ header_set_id: headerSetId,
94
+ // Timestamps: `first_crawled_at` is set only on first scrape —
95
+ // `COALESCE` preserves the existing value so a re-scrape
96
+ // (`--append`, `--retry-failed`) does not erase the discovery time.
97
+ // `last_crawled_at` is updated every successful scrape.
98
+ first_crawled_at: qb.raw('COALESCE(first_crawled_at, ?)', [now]),
99
+ last_crawled_at: now,
100
+ is_skipped: page.isSkipped ? 1 : 0,
101
+ ...sourceUpdate,
102
+ });
103
+ // The cache's `source` mirror only tracks the resolveContentItemId
104
+ // lattice; when the CASE above rewrites the column, refresh the cache
105
+ // from the caller-supplied label so a later crawled-wins check sees
106
+ // the current value. (`'crawled'` never arrives here today — see the
107
+ // CASE comment — so the only observable effect is seed-over-discovered
108
+ // promotion, which the lattice below reproduces.)
109
+ if (source !== undefined) {
110
+ const entry = caches.contentItems.get(page.url.withoutHashAndAuth);
111
+ if (entry !== undefined && entry.source === 'inventory-discovered') {
112
+ entry.source = source;
113
+ }
114
+ }
115
+ await upsertPageMeta(qb, caches, pageId, flat, denorm, extras);
116
+ return pageId;
117
+ }
118
+ /**
119
+ * Builds and upserts the `page_meta` row for one scraped page: interns
120
+ * text / URL / JSON values into their ref tables, then fully replaces
121
+ * the row (every column participates in the `DO UPDATE`) so a re-scrape
122
+ * behaves exactly like the legacy per-column UPDATE.
123
+ * @param qb - Knex instance or transaction.
124
+ * @param caches - The connection's write-side id caches.
125
+ * @param pageId - The owning `content_items.id`.
126
+ * @param flat - Flat meta columns from `deriveFlatFromMeta`.
127
+ * @param denorm - Denormalised aggregates from `computePageDenormalized`.
128
+ * @param extras - Nested-Meta catch-all from `deriveMetaExtras`.
129
+ */
130
+ async function upsertPageMeta(qb, caches, pageId, flat, denorm, extras) {
131
+ const texts = new Set();
132
+ for (const { source } of PAGE_META_COLUMN_MAPS.text) {
133
+ const value = flat[source];
134
+ if (typeof value === 'string' && value !== '') {
135
+ texts.add(value);
136
+ }
137
+ }
138
+ const textIds = await upsertTextRefs(qb, texts);
139
+ const row = {
140
+ page_id: pageId,
141
+ lang: flat.lang,
142
+ dir: flat.dir,
143
+ charset: flat.charset,
144
+ base_href: flat.baseHref,
145
+ viewport_raw: flat.viewport_raw,
146
+ theme_color: flat.themeColor,
147
+ application_name: flat.applicationName,
148
+ author: flat.author,
149
+ generator: flat.generator,
150
+ publisher: flat.publisher,
151
+ robots_noindex: flat.robots_noindex,
152
+ robots_nofollow: flat.robots_nofollow,
153
+ robots_noarchive: flat.robots_noarchive,
154
+ robots_noimageindex: flat.robots_noimageindex,
155
+ googlebot: flat.googlebot,
156
+ og_type: flat.og_type,
157
+ og_site_name: flat.og_site_name,
158
+ og_image_alt: flat.og_image_alt,
159
+ og_image_width: flat.og_image_width,
160
+ og_image_height: flat.og_image_height,
161
+ og_locale: flat.og_locale,
162
+ og_article_published_time: flat.og_article_published_time,
163
+ og_article_modified_time: flat.og_article_modified_time,
164
+ twitter_card: flat.twitter_card,
165
+ twitter_site: flat.twitter_site,
166
+ twitter_creator: flat.twitter_creator,
167
+ fb_app_id: flat.fb_app_id,
168
+ verification_google: flat.verification_google,
169
+ format_detection_telephone: flat.formatDetection_telephone,
170
+ // Denormalised aggregates: written once at scrape time so list reads
171
+ // (Sheets, page-detail summary) can answer "how many JSON-LD
172
+ // entries?" and "which Wappalyzer providers?" by selecting a single
173
+ // column rather than running a GROUP BY join on every read.
174
+ tag_count: denorm.tag_count,
175
+ jsonld_count: denorm.jsonld_count,
176
+ tags_providers_csv: denorm.tags_providers_csv,
177
+ };
178
+ for (const { source, target } of PAGE_META_COLUMN_MAPS.text) {
179
+ const value = flat[source];
180
+ row[target] =
181
+ typeof value === 'string' && value !== '' ? (textIds.get(value) ?? null) : null;
182
+ }
183
+ for (const { source, target } of PAGE_META_COLUMN_MAPS.url) {
184
+ const value = flat[source];
185
+ if (typeof value !== 'string' || value === '' || isLargeDataUri(value)) {
186
+ // `page_meta` has no `*_blob_id` companion columns, so a large
187
+ // data URI in a URL slot (a base64 favicon, typically) is
188
+ // dropped rather than ballooning `url_refs` with an opaque
189
+ // payload — matching the archive-migration behaviour.
190
+ row[target] = null;
191
+ continue;
192
+ }
193
+ row[target] = await upsertUrlRef(qb, caches, value);
194
+ }
195
+ const extrasJson = JSON.stringify(extras);
196
+ row.meta_extras_json_id =
197
+ extrasJson === '' ? null : await upsertJsonRef(qb, caches, extrasJson);
198
+ await qb('page_meta').insert(row).onConflict('page_id').merge();
199
+ }
200
+ /**
201
+ * Returns `true` when `value` is a `data:` URI larger than the routing
202
+ * threshold — the same rule that routes image `src` values to
203
+ * `blob_refs` instead of `url_refs`.
204
+ * @param value - Raw URL column value.
205
+ */
206
+ function isLargeDataUri(value) {
207
+ return value.startsWith('data:') && value.length > DATA_URI_URL_REFS_LIMIT;
208
+ }
@@ -0,0 +1,16 @@
1
+ import type { PageData } from '../../../../utils/types/types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Replaces the page's Wappalyzer tag rows with the freshly captured set.
5
+ * Called inside `updatePage`'s transaction unconditionally — tag
6
+ * detection draws on `<script src>` / `<iframe src>` / window globals /
7
+ * response headers, not the HTML body, so external pages that skip
8
+ * rendering still contribute tags.
9
+ *
10
+ * Same empty-guard as `insertJsonLd`: an empty array does not wipe
11
+ * prior rows on a degraded re-scrape.
12
+ * @param pageId
13
+ * @param meta
14
+ * @param trx
15
+ */
16
+ export declare function insertTags(pageId: number, meta: PageData['meta'], trx: Knex.Transaction): Promise<void>;
@@ -0,0 +1,34 @@
1
+ import { eachSplitted } from '../../../../utils/array/each-splitted.js';
2
+ import { extractTagsForArchive } from '../../../meta/extract-tags-for-archive.js';
3
+ /**
4
+ * Replaces the page's Wappalyzer tag rows with the freshly captured set.
5
+ * Called inside `updatePage`'s transaction unconditionally — tag
6
+ * detection draws on `<script src>` / `<iframe src>` / window globals /
7
+ * response headers, not the HTML body, so external pages that skip
8
+ * rendering still contribute tags.
9
+ *
10
+ * Same empty-guard as `insertJsonLd`: an empty array does not wipe
11
+ * prior rows on a degraded re-scrape.
12
+ * @param pageId
13
+ * @param meta
14
+ * @param trx
15
+ */
16
+ export async function insertTags(pageId, meta, trx) {
17
+ const partial = extractTagsForArchive(meta.tags);
18
+ if (partial.length === 0)
19
+ return;
20
+ const rows = partial.map((p) => ({
21
+ pageId,
22
+ provider: p.provider,
23
+ category: p.category,
24
+ externalId: p.externalId,
25
+ version: p.version,
26
+ confidence: p.confidence,
27
+ categories: JSON.stringify(p.categories),
28
+ sources: JSON.stringify(p.sources),
29
+ }));
30
+ await trx('page_tags').where('pageId', pageId).delete();
31
+ await eachSplitted(rows, 100, async (chunk) => {
32
+ await trx('page_tags').insert(chunk);
33
+ });
34
+ }
@@ -0,0 +1,36 @@
1
+ import type { PageSource } from '../../../types.js';
2
+ import type { WriteRefCaches } from '../../_shared/types.js';
3
+ import type { Knex } from 'knex';
4
+ /**
5
+ * Points each redirect-source URL at the destination page, marking it scraped
6
+ * and clearing any content it owned in a former life.
7
+ *
8
+ * Shared by `updatePage` (which also renders and stores the destination)
9
+ * and `recordRedirect` (which only records the edge for a destination
10
+ * rendered elsewhere). Self-redirects (source equal to the destination) are
11
+ * skipped so a page is never marked as redirecting to itself — that would
12
+ * exclude it from reports via the `whereNull('redirect_dest_id')` filter.
13
+ * @param trx - The active transaction. All SQL below is executed exclusively
14
+ * through this transaction; there is no separate `knex` fallback because
15
+ * the callers (`updatePage` / `recordRedirect`) always invoke this helper
16
+ * from within a transaction.
17
+ * @param caches - The connection's write-side id caches.
18
+ * @param sources - Redirect-source URLs (normalised): the original URL plus
19
+ * any intermediate hops. Empty when the page was not redirected.
20
+ * @param destId - `content_items.id` of the redirect destination page.
21
+ * @param destUrlNormalized - Normalised destination URL, used to detect and
22
+ * skip self-redirects.
23
+ * @param isExternal - Whether the sources are external to the crawl scope.
24
+ * @param chainLineageSource - Lineage label propagated to each intermediate
25
+ * hop's row (passed through to {@link resolveContentItemId}). Derived by
26
+ * the caller from the **originating** page's source (`page.url`), not from
27
+ * the destination — intermediates are reached transitively from the
28
+ * originating render, so they inherit its lineage. Pass `'inventory-discovered'`
29
+ * for chains rooted at inventory-seed/discovered pages so new intermediates
30
+ * stay in the inventory chain; pass `'crawled'` for crawled chains so the
31
+ * crawled-wins downgrade inside `resolveContentItemId` fires on existing
32
+ * `'inventory-*'` intermediates a crawled chain reaches. Pass `undefined`
33
+ * to fall back to the DB DEFAULT (`'crawled'`) on INSERT without
34
+ * triggering the downgrade on existing rows.
35
+ */
36
+ export declare function linkRedirectSources(trx: Knex.Transaction, caches: WriteRefCaches, sources: readonly string[], destId: number, destUrlNormalized: string, isExternal: boolean, chainLineageSource?: PageSource): Promise<void>;
@@ -0,0 +1,93 @@
1
+ import { dbLog } from '../../../debug.js';
2
+ import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
3
+ /**
4
+ * Points each redirect-source URL at the destination page, marking it scraped
5
+ * and clearing any content it owned in a former life.
6
+ *
7
+ * Shared by `updatePage` (which also renders and stores the destination)
8
+ * and `recordRedirect` (which only records the edge for a destination
9
+ * rendered elsewhere). Self-redirects (source equal to the destination) are
10
+ * skipped so a page is never marked as redirecting to itself — that would
11
+ * exclude it from reports via the `whereNull('redirect_dest_id')` filter.
12
+ * @param trx - The active transaction. All SQL below is executed exclusively
13
+ * through this transaction; there is no separate `knex` fallback because
14
+ * the callers (`updatePage` / `recordRedirect`) always invoke this helper
15
+ * from within a transaction.
16
+ * @param caches - The connection's write-side id caches.
17
+ * @param sources - Redirect-source URLs (normalised): the original URL plus
18
+ * any intermediate hops. Empty when the page was not redirected.
19
+ * @param destId - `content_items.id` of the redirect destination page.
20
+ * @param destUrlNormalized - Normalised destination URL, used to detect and
21
+ * skip self-redirects.
22
+ * @param isExternal - Whether the sources are external to the crawl scope.
23
+ * @param chainLineageSource - Lineage label propagated to each intermediate
24
+ * hop's row (passed through to {@link resolveContentItemId}). Derived by
25
+ * the caller from the **originating** page's source (`page.url`), not from
26
+ * the destination — intermediates are reached transitively from the
27
+ * originating render, so they inherit its lineage. Pass `'inventory-discovered'`
28
+ * for chains rooted at inventory-seed/discovered pages so new intermediates
29
+ * stay in the inventory chain; pass `'crawled'` for crawled chains so the
30
+ * crawled-wins downgrade inside `resolveContentItemId` fires on existing
31
+ * `'inventory-*'` intermediates a crawled chain reaches. Pass `undefined`
32
+ * to fall back to the DB DEFAULT (`'crawled'`) on INSERT without
33
+ * triggering the downgrade on existing rows.
34
+ */
35
+ export async function linkRedirectSources(trx, caches, sources, destId, destUrlNormalized, isExternal, chainLineageSource) {
36
+ for (const redirect of sources) {
37
+ if (redirect === destUrlNormalized) {
38
+ dbLog('Skip self-redirect: %s', redirect);
39
+ continue;
40
+ }
41
+ dbLog('Set redirected url: %s -> id:%d', redirect, destId);
42
+ // Pass `chainLineageSource` through so a brand-new intermediate
43
+ // hop INSERTed here inherits the originating page's lineage label
44
+ // (inventory-discovered when the originating chain is in the
45
+ // inventory chain, undefined otherwise). The crawled-wins
46
+ // downgrade inside `resolveContentItemId` still fires when this
47
+ // argument is `'crawled'`, matching the anchor-lineage propagation
48
+ // contract — an existing inventory-* intermediate that is later
49
+ // traversed by a `'crawled'` chain gets downgraded.
50
+ const redirectId = await resolveContentItemId(trx, caches, redirect, undefined, chainLineageSource);
51
+ await trx('content_items')
52
+ .where('id', redirectId)
53
+ .update({
54
+ scraped: 1,
55
+ redirect_dest_id: destId,
56
+ is_external: isExternal ? 1 : 0,
57
+ });
58
+ // Conditional `301 Moved Permanently` stamp — applied ONLY when the
59
+ // row carries no definitive status yet (NULL or the `-1`
60
+ // hard-failure sentinel). HEAD pre-flight does not retain each
61
+ // hop's individual status code (`redirectPaths` is a URL[] without
62
+ // statuses), so the only honest answer for an unknown-status hop is
63
+ // "some 3xx" — 301 is the canonical representative.
64
+ //
65
+ // We deliberately do NOT overwrite an existing definitive status
66
+ // (200 / 302 / 307 / etc.): a row that already captured a concrete
67
+ // status from a prior direct scrape would lose accuracy. The stamp
68
+ // only flips two cases:
69
+ // - NULL: a placeholder row created by `resolveContentItemId`
70
+ // because the URL was reached only as a redirect target / source,
71
+ // never directly scraped. Without the stamp the row is invisible
72
+ // on the Errors view's status distribution.
73
+ // - -1: a row that recorded a hard scrape failure (e.g. a puppeteer
74
+ // goto returned null on a HTTPS→HTTP downgrade redirect) BEFORE
75
+ // the chain was understood. That `-1` then conflated "real
76
+ // failure" with "actually a redirect source we now know about",
77
+ // polluting the `-1` bucket AND inflating the `--retry-failed`
78
+ // target (via the `whereNull('redirect_dest_id')` filter — the
79
+ // redirect_dest_id update above already excludes the row from
80
+ // retry; this stamp restores the visible identity).
81
+ await trx('content_items')
82
+ .where('id', redirectId)
83
+ .where((qb) => qb.whereNull('status').orWhere('status', -1))
84
+ .update({ status: 301, status_text: 'Moved Permanently' });
85
+ // A page that used to be scraped as content can later turn into a
86
+ // redirect source. It owns no content anymore, so drop any anchors /
87
+ // images it captured in its former life — otherwise they linger and
88
+ // leak into referrer / incoming-link reads (which do not filter out
89
+ // redirect sources).
90
+ await trx('anchor_edges').where('page_id', redirectId).delete();
91
+ await trx('image_items').where('page_id', redirectId).delete();
92
+ }
93
+ }
@@ -0,0 +1,35 @@
1
+ import type { PageData } from '../../../../utils/types/types.js';
2
+ import type { PageSource } from '../../../types.js';
3
+ import type { WriteRefCaches } from '../../_shared/types.js';
4
+ import type { Knex } from 'knex';
5
+ /**
6
+ * Records a redirect edge (source → destination) **without** re-storing the
7
+ * destination's content.
8
+ *
9
+ * The crawler renders a many-to-one redirect destination exactly once. For
10
+ * every subsequent source URL that redirects to that already-rendered
11
+ * destination, it calls this instead of {@link ./update-page.ts} (#73).
12
+ * Routing a content-less HEAD result through `updatePage` would funnel it
13
+ * into `insertPage` and overwrite the destination's good title / meta with
14
+ * empty values, so the dedicated edge-only path is required.
15
+ *
16
+ * The destination row is resolved (created on demand if a concurrent
17
+ * in-flight render has not committed it yet) so the edge always points at a
18
+ * valid id; the single render fills in the destination's content under that
19
+ * same id. The destination's existing anchors / images are never touched
20
+ * here.
21
+ * @param knex - Knex query builder connected to the archive DB.
22
+ * @param caches - The connection's write-side id caches.
23
+ * @param page - HEAD-resolved page data carrying the redirect chain. Its
24
+ * `anchorList` / `imageList` are ignored (a redirect source owns no content).
25
+ * @param source - Inventory provenance forwarded by the orchestrator
26
+ * (`Archive.setRedirect` → here) for the redirect-edge fast path. Used
27
+ * as the fallback when the originating URL's row does NOT yet exist in
28
+ * the archive (`#73` convergence on first sight, js-redirect rescue
29
+ * before any prior write). When the originating row already exists
30
+ * (e.g. anchor-lineage INSERT from a prior pass), its stored `source`
31
+ * takes precedence so transitive lineage is preserved across resume /
32
+ * retry-failed sessions. `undefined` keeps the DB DEFAULT `'crawled'`
33
+ * on a brand-new destination row.
34
+ */
35
+ export declare function recordRedirect(knex: Knex, caches: WriteRefCaches, page: PageData, source?: PageSource): Promise<void>;
@@ -0,0 +1,100 @@
1
+ import { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
2
+ import { dbLog } from '../../../debug.js';
3
+ import { deriveLineageFromParent } from '../../../derive-lineage-from-parent.js';
4
+ import { resolveRedirectChain } from '../../../resolve-redirect-chain.js';
5
+ import { clearWriteRefCaches } from '../../_shared/clear-write-ref-caches.js';
6
+ import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
7
+ import { linkRedirectSources } from './link-redirect-sources.js';
8
+ /**
9
+ * Records a redirect edge (source → destination) **without** re-storing the
10
+ * destination's content.
11
+ *
12
+ * The crawler renders a many-to-one redirect destination exactly once. For
13
+ * every subsequent source URL that redirects to that already-rendered
14
+ * destination, it calls this instead of {@link ./update-page.ts} (#73).
15
+ * Routing a content-less HEAD result through `updatePage` would funnel it
16
+ * into `insertPage` and overwrite the destination's good title / meta with
17
+ * empty values, so the dedicated edge-only path is required.
18
+ *
19
+ * The destination row is resolved (created on demand if a concurrent
20
+ * in-flight render has not committed it yet) so the edge always points at a
21
+ * valid id; the single render fills in the destination's content under that
22
+ * same id. The destination's existing anchors / images are never touched
23
+ * here.
24
+ * @param knex - Knex query builder connected to the archive DB.
25
+ * @param caches - The connection's write-side id caches.
26
+ * @param page - HEAD-resolved page data carrying the redirect chain. Its
27
+ * `anchorList` / `imageList` are ignored (a redirect source owns no content).
28
+ * @param source - Inventory provenance forwarded by the orchestrator
29
+ * (`Archive.setRedirect` → here) for the redirect-edge fast path. Used
30
+ * as the fallback when the originating URL's row does NOT yet exist in
31
+ * the archive (`#73` convergence on first sight, js-redirect rescue
32
+ * before any prior write). When the originating row already exists
33
+ * (e.g. anchor-lineage INSERT from a prior pass), its stored `source`
34
+ * takes precedence so transitive lineage is preserved across resume /
35
+ * retry-failed sessions. `undefined` keeps the DB DEFAULT `'crawled'`
36
+ * on a brand-new destination row.
37
+ */
38
+ export async function recordRedirect(knex, caches, page, source) {
39
+ const { destUrl, sources } = resolveRedirectChain(page.url.withoutHashAndAuth, page.redirectPaths);
40
+ // No redirect chain (the URL is itself the already-rendered destination,
41
+ // reached both directly and via a redirect) → there is no edge to write.
42
+ // Returning here avoids opening a transaction and, crucially, avoids
43
+ // `resolveContentItemId` inserting a content-less placeholder row for a
44
+ // destination that may not have been written yet.
45
+ if (sources.length === 0) {
46
+ return;
47
+ }
48
+ const destUrlObject = parseUrl(destUrl);
49
+ if (!destUrlObject) {
50
+ // A malformed redirect target should not abort the whole crawl (this
51
+ // runs inside the WriteQueue, whose rejection aborts the run). Recording
52
+ // a single redirect edge is best-effort, so skip it and move on. Unlike
53
+ // `updatePage`, there is no page content at stake here.
54
+ dbLog('recordRedirect: skip malformed destination URL: %s', destUrl);
55
+ return;
56
+ }
57
+ try {
58
+ await knex.transaction(async (trx) => {
59
+ // Pass the caller-supplied `source` straight through so a brand-new
60
+ // destination row INSERTed here picks up the inventory lineage
61
+ // (instead of the DB DEFAULT `'crawled'`) when the caller is in the
62
+ // inventory chain — without the pass-through, inventory lineage
63
+ // would be laundered to `'crawled'` for js-redirect rescue / #73
64
+ // convergence destinations that have not yet been rendered.
65
+ const destId = await resolveContentItemId(trx, caches, destUrlObject.withoutHashAndAuth, undefined, source);
66
+ // Chain lineage propagates FROM the originating URL (`page.url`),
67
+ // NOT from the destination. The originating URL is what initiated
68
+ // the redirect chain, so its lineage is what every intermediate hop
69
+ // transitively inherits. Reading from the destination would
70
+ // mis-propagate in "inventory-seed → ... → existing crawled dest"
71
+ // chains: the intermediates are reached only via the inventory
72
+ // chain, so they belong to the inventory chain even though the
73
+ // chain happens to land on a crawled URL. The `'crawled'` fallback
74
+ // arms the crawled-wins downgrade for existing `'inventory-*'`
75
+ // intermediates that a crawled chain reaches.
76
+ const cachedOriginating = caches.contentItems.get(page.url.withoutHashAndAuth);
77
+ let originatingSource = cachedOriginating?.source;
78
+ if (originatingSource === undefined) {
79
+ const [originatingRow] = (await trx
80
+ .select('ci.source')
81
+ .from('content_items as ci')
82
+ .join('url_refs as ur', 'ur.id', 'ci.url_id')
83
+ .where('ur.url', page.url.withoutHashAndAuth));
84
+ originatingSource = originatingRow?.source ?? source;
85
+ }
86
+ const chainLineageSource = deriveLineageFromParent(originatingSource, 'crawled');
87
+ await linkRedirectSources(trx, caches, sources, destId, destUrlObject.withoutHashAndAuth, page.isExternal, chainLineageSource);
88
+ });
89
+ }
90
+ catch (error) {
91
+ // A rolled-back transaction can leave ids cached that no longer
92
+ // correspond to any row (AUTOINCREMENT never rewinds) — see
93
+ // `clearWriteRefCaches` for why a full clear, not a partial one, is
94
+ // required. `emitErrorAndRetry` may retry this whole call, so the
95
+ // cache must be clean before the next attempt (same guard as
96
+ // `updatePage`).
97
+ clearWriteRefCaches(caches);
98
+ throw error;
99
+ }
100
+ }
@@ -0,0 +1,13 @@
1
+ import type { WriteRefCaches } from '../../_shared/types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Marks a page as skipped in the database with the given reason.
5
+ * Creates the `content_items` row (with its `url_refs` entry) if it does
6
+ * not already exist.
7
+ * @param knex - Knex query builder connected to the archive DB.
8
+ * @param caches - The connection's write-side id caches.
9
+ * @param url - The URL of the skipped page.
10
+ * @param reason - The reason the page was skipped.
11
+ * @param isExternal - Whether the page is on an external domain. Defaults to `false`.
12
+ */
13
+ export declare function setSkippedPage(knex: Knex, caches: WriteRefCaches, url: string, reason: string, isExternal?: boolean): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
2
+ /**
3
+ * Marks a page as skipped in the database with the given reason.
4
+ * Creates the `content_items` row (with its `url_refs` entry) if it does
5
+ * not already exist.
6
+ * @param knex - Knex query builder connected to the archive DB.
7
+ * @param caches - The connection's write-side id caches.
8
+ * @param url - The URL of the skipped page.
9
+ * @param reason - The reason the page was skipped.
10
+ * @param isExternal - Whether the page is on an external domain. Defaults to `false`.
11
+ */
12
+ export async function setSkippedPage(knex, caches, url, reason, isExternal = false) {
13
+ const pageId = await resolveContentItemId(knex, caches, url, isExternal ? 1 : 0);
14
+ await knex('content_items')
15
+ .where('id', pageId)
16
+ .update({
17
+ scraped: 1,
18
+ is_external: isExternal ? 1 : 0,
19
+ is_skipped: 1,
20
+ skip_reason: reason,
21
+ });
22
+ }
@@ -0,0 +1,29 @@
1
+ import type { PageDataWithDomPaths, PageSource } from '../../../types.js';
2
+ import type { WriteRefCaches } from '../../_shared/types.js';
3
+ import type { Knex } from 'knex';
4
+ /**
5
+ * Inserts or updates a crawled page — `content_items` + `page_meta` plus its
6
+ * redirect chain, `anchor_edges`, `image_items`, and (when `writeHtml`) its
7
+ * compressed HTML snapshot BLOB.
8
+ *
9
+ * Self-redirects (where the source URL equals the destination URL after
10
+ * normalization) are skipped to avoid marking a page as redirected to itself
11
+ * — a situation caused by authentication challenges (e.g. Basic Auth 302)
12
+ * that would otherwise exclude the page from reports via the
13
+ * `whereNull('redirect_dest_id')` filter.
14
+ * @param knex - Knex query builder connected to the archive DB.
15
+ * @param caches - The connection's write-side id caches.
16
+ * @param page - The page data to store, optionally carrying the in-browser
17
+ * dom-path capture (see {@link PageDataWithDomPaths}).
18
+ * @param writeHtml - When `true`, this call is allowed to insert (or clear)
19
+ * the page's HTML blob. `setExternalPage` passes `false` because external
20
+ * metadata-only scrapes never carry HTML and must not perturb an already
21
+ * stored body.
22
+ * @param isTarget - Whether this page is a crawl target.
23
+ * @param source - Provenance label written ONLY when the row is freshly
24
+ * inserted. Existing rows keep their original `source` (this is why a
25
+ * second `crawl --inventory` does not "demote" an `'inventory-seed'` row
26
+ * that was discovered earlier).
27
+ * @returns The database `pageId` (`content_items.id`) of the inserted or updated row.
28
+ */
29
+ export declare function updatePage(knex: Knex, caches: WriteRefCaches, page: PageDataWithDomPaths, writeHtml: boolean, isTarget: boolean, source?: PageSource): Promise<number>;