@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,11 @@
1
+ import type { DB_Page, PageFilter } from '../../../types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Retrieves pages from the database with optional filtering, pagination via offset and limit.
5
+ * @param knex - Knex query builder connected to the archive DB.
6
+ * @param filter - An optional {@link PageFilter} to narrow results by content type and origin.
7
+ * @param offset - The number of rows to skip. Defaults to `0`.
8
+ * @param limit - The maximum number of rows to return. Defaults to `100000`.
9
+ * @returns An array of reconstructed {@link DB_Page} rows.
10
+ */
11
+ export declare function getPages(knex: Knex, filter?: PageFilter, offset?: number, limit?: number): Promise<DB_Page[]>;
@@ -0,0 +1,51 @@
1
+ import { buildPageQuery } from './build-page-query.js';
2
+ import { reconstructPageRows } from './reconstruct-page-rows.js';
3
+ /**
4
+ * Retrieves pages from the database with optional filtering, pagination via offset and limit.
5
+ * @param knex - Knex query builder connected to the archive DB.
6
+ * @param filter - An optional {@link PageFilter} to narrow results by content type and origin.
7
+ * @param offset - The number of rows to skip. Defaults to `0`.
8
+ * @param limit - The maximum number of rows to return. Defaults to `100000`.
9
+ * @returns An array of reconstructed {@link DB_Page} rows.
10
+ */
11
+ export async function getPages(knex, filter, offset = 0, limit = 100_000) {
12
+ const q = buildPageQuery(knex);
13
+ switch (filter) {
14
+ case 'page': {
15
+ q.where('ctr.raw', 'text/html').andWhere('ci.is_target', 1);
16
+ break;
17
+ }
18
+ case 'page-included-no-target': {
19
+ q.where('ctr.raw', 'text/html');
20
+ break;
21
+ }
22
+ case 'external-page': {
23
+ q.where('ctr.raw', 'text/html').andWhere('ci.is_external', 1);
24
+ break;
25
+ }
26
+ case 'internal-page': {
27
+ q.where('ctr.raw', 'text/html').andWhere('ci.is_external', 0);
28
+ break;
29
+ }
30
+ case 'no-page': {
31
+ q.where((qb) => {
32
+ qb.whereNull('ctr.raw').orWhereNot('ctr.raw', 'text/html');
33
+ });
34
+ break;
35
+ }
36
+ case 'external-no-page': {
37
+ q.where((qb) => {
38
+ qb.whereNull('ctr.raw').orWhereNot('ctr.raw', 'text/html');
39
+ }).andWhere('ci.is_external', 1);
40
+ break;
41
+ }
42
+ case 'internal-no-page': {
43
+ q.where((qb) => {
44
+ qb.whereNull('ctr.raw').orWhereNot('ctr.raw', 'text/html');
45
+ }).andWhere('ci.is_external', 0);
46
+ break;
47
+ }
48
+ }
49
+ const rows = await q.limit(limit).offset(offset);
50
+ return reconstructPageRows(knex, rows);
51
+ }
@@ -0,0 +1,18 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Counts pages that were scraped as crawl targets (full HTML render).
4
+ *
5
+ * Used by the crawler to seed its `pagesScraped` counter on resume so the
6
+ * progress display reflects all browser-rendered HTML pages across sessions,
7
+ * not just the current one.
8
+ *
9
+ * "HTML page" is guaranteed by `content_type_refs.raw = 'text/html'`, NOT by
10
+ * `is_target` alone: `is_target` means "in-scope crawl target" and is set
11
+ * for in-scope non-HTML resources too (e.g. a PDF reached via the HEAD
12
+ * pre-flight is `is_target = 1`). Counting those would over-report the HTML
13
+ * page total, so page-ness is asserted at the read layer here rather than by
14
+ * trusting `is_target`.
15
+ * @param knex - Knex query builder connected to the archive DB.
16
+ * @returns The number of `text/html` rows with `is_target = 1` and `scraped = 1`.
17
+ */
18
+ export declare function getScrapedHtmlPageCount(knex: Knex): Promise<number>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Counts pages that were scraped as crawl targets (full HTML render).
3
+ *
4
+ * Used by the crawler to seed its `pagesScraped` counter on resume so the
5
+ * progress display reflects all browser-rendered HTML pages across sessions,
6
+ * not just the current one.
7
+ *
8
+ * "HTML page" is guaranteed by `content_type_refs.raw = 'text/html'`, NOT by
9
+ * `is_target` alone: `is_target` means "in-scope crawl target" and is set
10
+ * for in-scope non-HTML resources too (e.g. a PDF reached via the HEAD
11
+ * pre-flight is `is_target = 1`). Counting those would over-report the HTML
12
+ * page total, so page-ness is asserted at the read layer here rather than by
13
+ * trusting `is_target`.
14
+ * @param knex - Knex query builder connected to the archive DB.
15
+ * @returns The number of `text/html` rows with `is_target = 1` and `scraped = 1`.
16
+ */
17
+ export async function getScrapedHtmlPageCount(knex) {
18
+ const [row] = await knex('content_items')
19
+ .join('content_type_refs', 'content_type_refs.id', 'content_items.content_type_id')
20
+ .where('content_items.is_target', 1)
21
+ .andWhere('content_items.scraped', 1)
22
+ .andWhere('content_type_refs.raw', 'text/html')
23
+ .count('* as count');
24
+ return row ? Number(row.count) : 0;
25
+ }
@@ -0,0 +1,31 @@
1
+ import type { DB_Page } from '../../../types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Raw row shape produced by {@link ../read/build-page-query.js} before
5
+ * `responseHeaders` / `meta_extras` reconstruction.
6
+ */
7
+ interface RawPageRow extends Omit<DB_Page, 'responseHeaders' | 'meta_extras' | 'networkLogs'> {
8
+ /** `content_items.header_set_id`, or null when no headers were recorded. */
9
+ headerSetId: number | null;
10
+ /** `json_refs.json_text` for `meta_extras`, or null when absent. */
11
+ extras_body: Buffer | string | null;
12
+ /** `json_refs.codec` for `meta_extras` (`'zstd'` or `'none'`), or null when absent. */
13
+ extras_codec: 'zstd' | 'none' | null;
14
+ }
15
+ /**
16
+ * Reconstructs `responseHeaders` (JSON string) and `meta_extras` (JSON
17
+ * string) for a batch of raw page rows. Headers load through
18
+ * {@link ../../_shared/load-response-headers-by-set-ids.js} (chunked
19
+ * batch lookup, never N+1); `meta_extras` decodes through
20
+ * {@link ../../_shared/decode-json-ref.js}. `networkLogs` has no 0.13
21
+ * equivalent (it is a legacy-only field nothing ever wrote past the
22
+ * pre-0.13 write path either) and is always `null`.
23
+ * @param knex - Knex query builder connected to the archive DB.
24
+ * @param rows - Raw rows from {@link ../read/build-page-query.js}.
25
+ * @returns Fully reconstructed `DB_Page` rows, in the same order as `rows`.
26
+ * @example
27
+ * const raw = await buildPageQuery(knex).where('ci.is_target', 1);
28
+ * const pages = await reconstructPageRows(knex, raw);
29
+ */
30
+ export declare function reconstructPageRows(knex: Knex, rows: readonly RawPageRow[]): Promise<DB_Page[]>;
31
+ export {};
@@ -0,0 +1,32 @@
1
+ import { decodeJsonRef } from '../../_shared/decode-json-ref.js';
2
+ import { loadResponseHeadersBySetIds } from '../../_shared/load-response-headers-by-set-ids.js';
3
+ /**
4
+ * Reconstructs `responseHeaders` (JSON string) and `meta_extras` (JSON
5
+ * string) for a batch of raw page rows. Headers load through
6
+ * {@link ../../_shared/load-response-headers-by-set-ids.js} (chunked
7
+ * batch lookup, never N+1); `meta_extras` decodes through
8
+ * {@link ../../_shared/decode-json-ref.js}. `networkLogs` has no 0.13
9
+ * equivalent (it is a legacy-only field nothing ever wrote past the
10
+ * pre-0.13 write path either) and is always `null`.
11
+ * @param knex - Knex query builder connected to the archive DB.
12
+ * @param rows - Raw rows from {@link ../read/build-page-query.js}.
13
+ * @returns Fully reconstructed `DB_Page` rows, in the same order as `rows`.
14
+ * @example
15
+ * const raw = await buildPageQuery(knex).where('ci.is_target', 1);
16
+ * const pages = await reconstructPageRows(knex, raw);
17
+ */
18
+ export async function reconstructPageRows(knex, rows) {
19
+ const headerSetIds = [
20
+ ...new Set(rows.map((r) => r.headerSetId).filter((id) => id != null)),
21
+ ];
22
+ const headersBySetId = await loadResponseHeadersBySetIds(knex, headerSetIds);
23
+ return rows.map((row) => {
24
+ const { headerSetId, extras_body, extras_codec, ...rest } = row;
25
+ return {
26
+ ...rest,
27
+ responseHeaders: JSON.stringify(headerSetId == null ? {} : (headersBySetId.get(headerSetId) ?? {})),
28
+ meta_extras: decodeJsonRef(extras_body, extras_codec),
29
+ networkLogs: null,
30
+ };
31
+ });
32
+ }
@@ -0,0 +1,24 @@
1
+ import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Promote previously-external pages whose URL falls under any of the new scope
5
+ * entries back to a "needs scraping" state so that the next crawl picks them up
6
+ * as full internal pages.
7
+ *
8
+ * For each matching page:
9
+ * - clears the scrape metadata (status, headers, snapshot path, etc.) by
10
+ * deleting the `page_meta` row outright (a re-scrape re-inserts it fresh),
11
+ * - flips `is_external` to `0` and `scraped` to `0` on `content_items`,
12
+ * - removes stale `anchor_edges`, `image_items`, and `resource_ref_edges`
13
+ * rows so that the re-scrape can re-insert fresh ones without duplicates.
14
+ *
15
+ * The page row itself is kept (id is preserved) so existing referrers via
16
+ * `anchor_edges.href_page_id` remain valid. SELECT and UPDATE/DELETE
17
+ * statements are chunked to stay below SQLite's
18
+ * `SQLITE_LIMIT_VARIABLE_NUMBER`.
19
+ * @param knex - Knex query builder connected to the archive DB.
20
+ * @param scopes - The hostname-indexed scope map after the new roots are merged.
21
+ * @param options - URL parsing options forwarded to {@link findScopeEntry}.
22
+ * @returns The URLs of the pages that were promoted.
23
+ */
24
+ export declare function repromoteExternalPages(knex: Knex, scopes: ReadonlyMap<string, readonly ExURL[]>, options?: ParseURLOptions): Promise<string[]>;
@@ -0,0 +1,93 @@
1
+ import { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
2
+ import { findScopeEntry } from '../../../../crawler/find-scope-entry.js';
3
+ import { dbLog } from '../../../debug.js';
4
+ /**
5
+ * Promote previously-external pages whose URL falls under any of the new scope
6
+ * entries back to a "needs scraping" state so that the next crawl picks them up
7
+ * as full internal pages.
8
+ *
9
+ * For each matching page:
10
+ * - clears the scrape metadata (status, headers, snapshot path, etc.) by
11
+ * deleting the `page_meta` row outright (a re-scrape re-inserts it fresh),
12
+ * - flips `is_external` to `0` and `scraped` to `0` on `content_items`,
13
+ * - removes stale `anchor_edges`, `image_items`, and `resource_ref_edges`
14
+ * rows so that the re-scrape can re-insert fresh ones without duplicates.
15
+ *
16
+ * The page row itself is kept (id is preserved) so existing referrers via
17
+ * `anchor_edges.href_page_id` remain valid. SELECT and UPDATE/DELETE
18
+ * statements are chunked to stay below SQLite's
19
+ * `SQLITE_LIMIT_VARIABLE_NUMBER`.
20
+ * @param knex - Knex query builder connected to the archive DB.
21
+ * @param scopes - The hostname-indexed scope map after the new roots are merged.
22
+ * @param options - URL parsing options forwarded to {@link findScopeEntry}.
23
+ * @returns The URLs of the pages that were promoted.
24
+ */
25
+ export async function repromoteExternalPages(knex, scopes, options) {
26
+ if (scopes.size === 0) {
27
+ return [];
28
+ }
29
+ const candidates = await knex('content_items')
30
+ .join('url_refs', 'content_items.url_id', 'url_refs.id')
31
+ .select('content_items.id as id', 'url_refs.url as url')
32
+ .where('content_items.is_external', 1);
33
+ const promotedIds = [];
34
+ const promotedUrls = [];
35
+ for (const row of candidates) {
36
+ const parsed = parseUrl(row.url, options);
37
+ if (!parsed) {
38
+ continue;
39
+ }
40
+ if (findScopeEntry(parsed, scopes, options) === null) {
41
+ continue;
42
+ }
43
+ promotedIds.push(row.id);
44
+ promotedUrls.push(row.url);
45
+ }
46
+ if (promotedIds.length === 0) {
47
+ return [];
48
+ }
49
+ const chunkSize = 500;
50
+ for (let i = 0; i < promotedIds.length; i += chunkSize) {
51
+ const chunk = promotedIds.slice(i, i + chunkSize);
52
+ await knex('content_items').whereIn('id', chunk).update({
53
+ scraped: 0,
54
+ is_external: 0,
55
+ is_skipped: 0,
56
+ skip_reason: null,
57
+ status: null,
58
+ status_text: null,
59
+ content_type_id: null,
60
+ content_length: null,
61
+ header_set_id: null,
62
+ redirect_dest_id: null,
63
+ // `first_crawled_at` / `last_crawled_at` are deliberately
64
+ // left untouched — the last-success timestamp survives the
65
+ // demotion.
66
+ });
67
+ // Clear the prior crawl's data for the repromoted pages. `updatePage`
68
+ // also replaces anchor_edges/image_items/tags/jsonld when it
69
+ // re-scrapes them, but only when the new scrape is non-empty — so
70
+ // this pre-clear is still load-bearing for pages that get
71
+ // repromoted but then re-scrape to nothing (or are never reached
72
+ // again), and it is the only place `resource_ref_edges` is cleared.
73
+ // Deleting the `page_meta` row (rather than nulling every column)
74
+ // clears title / description / og:* / twitter:* / meta_extras in
75
+ // one statement; a re-scrape re-inserts it via
76
+ // `ON CONFLICT(page_id) DO UPDATE`. `page_tags` / `page_jsonld` are
77
+ // cleared explicitly even though both tables also carry ON DELETE
78
+ // CASCADE — we keep the existing pattern of explicit chunked
79
+ // DELETEs rather than relying on CASCADE indirectly (and would not
80
+ // cascade anyway: the parent `content_items` row is updated, not
81
+ // deleted). Orphan blobs in `page_html_blobs` are left behind; #23
82
+ // will add GC.
83
+ await knex('page_meta').whereIn('page_id', chunk).delete();
84
+ await knex('anchor_edges').whereIn('page_id', chunk).delete();
85
+ await knex('image_items').whereIn('page_id', chunk).delete();
86
+ await knex('resource_ref_edges').whereIn('page_id', chunk).delete();
87
+ await knex('page_html_ref').whereIn('page_id', chunk).delete();
88
+ await knex('page_tags').whereIn('pageId', chunk).delete();
89
+ await knex('page_jsonld').whereIn('pageId', chunk).delete();
90
+ }
91
+ dbLog('Repromoted %d external pages back to pending', promotedUrls.length);
92
+ return promotedUrls;
93
+ }
@@ -0,0 +1,47 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Reset previously-attempted pages that ended in a recoverable failure so a
4
+ * follow-up crawl can re-fetch them from scratch.
5
+ *
6
+ * A page qualifies as a recoverable failure when it was already scraped
7
+ * (`scraped = 1`), is not a redirect source (`redirect_dest_id IS NULL`), was
8
+ * not intentionally skipped (`is_skipped` is not `1`), and one of the
9
+ * following holds:
10
+ *
11
+ * - `status = -1` — the sentinel a hard scrape failure (network error,
12
+ * timeout, browser crash) is recorded with (see `handle-scrape-error.ts`);
13
+ * - `status IS NULL` — no status was ever stored for the row;
14
+ * - the row has no `content_type_refs` link — the content type could not be
15
+ * determined;
16
+ * - `status` is in the `5xx` range — a (frequently transient) server error.
17
+ *
18
+ * Definitive `4xx` responses are intentionally excluded: re-fetching a 404
19
+ * almost always yields the same answer.
20
+ *
21
+ * A second exclusion runs in JS after the SQL candidate scan: any page whose
22
+ * latest recorded `page_errors` / `crawl_errors` message classifies into a
23
+ * permanent {@link PERMANENT_ERROR_KINDS} kind (dns / tls / client-blocked /
24
+ * parse-error / connection-refused) is left as-is rather than reset to
25
+ * pending. Without this filter, `--retry-failed` never converges: NXDOMAIN
26
+ * hosts, expired-cert hosts, and `ERR_BLOCKED_BY_CLIENT` ad pixels would be
27
+ * reset every iteration, re-attempted, fail identically, and rejoin the
28
+ * candidate pool for the next iteration. The exclusion keeps the retry
29
+ * target shrinking across `--retry-failed` passes by leaving deterministic
30
+ * dead-ends alone.
31
+ *
32
+ * Matching rows — internal and external alike — are demoted back to pending
33
+ * (`scraped = 0`) and have their stale scrape metadata cleared (the
34
+ * `page_meta` row is deleted outright rather than nulled column-by-column).
35
+ * The page row itself is kept (id preserved) so existing
36
+ * `anchor_edges.href_page_id` referrers stay valid, and `is_external` is
37
+ * left untouched so the next pass re-classifies each page from the crawl
38
+ * scope. Related `anchor_edges`, `image_items`, `resource_ref_edges`, and
39
+ * `page_errors` rows are deleted so the re-scrape can re-insert fresh data
40
+ * without duplicates.
41
+ *
42
+ * SELECT and UPDATE/DELETE statements are chunked to stay below SQLite's
43
+ * `SQLITE_LIMIT_VARIABLE_NUMBER`.
44
+ * @param knex - Knex query builder connected to the archive DB.
45
+ * @returns The URLs of the pages that were reset to pending.
46
+ */
47
+ export declare function resetFailedPages(knex: Knex): Promise<string[]>;
@@ -0,0 +1,124 @@
1
+ import { classifyErrorKind } from '../../../../classify-error-kind.js';
2
+ import { PERMANENT_ERROR_KINDS } from '../../../../permanent-error-kinds.js';
3
+ import { dbLog } from '../../../debug.js';
4
+ import { getFailedPageMessages } from '../../../get-failed-page-messages.js';
5
+ /**
6
+ * Reset previously-attempted pages that ended in a recoverable failure so a
7
+ * follow-up crawl can re-fetch them from scratch.
8
+ *
9
+ * A page qualifies as a recoverable failure when it was already scraped
10
+ * (`scraped = 1`), is not a redirect source (`redirect_dest_id IS NULL`), was
11
+ * not intentionally skipped (`is_skipped` is not `1`), and one of the
12
+ * following holds:
13
+ *
14
+ * - `status = -1` — the sentinel a hard scrape failure (network error,
15
+ * timeout, browser crash) is recorded with (see `handle-scrape-error.ts`);
16
+ * - `status IS NULL` — no status was ever stored for the row;
17
+ * - the row has no `content_type_refs` link — the content type could not be
18
+ * determined;
19
+ * - `status` is in the `5xx` range — a (frequently transient) server error.
20
+ *
21
+ * Definitive `4xx` responses are intentionally excluded: re-fetching a 404
22
+ * almost always yields the same answer.
23
+ *
24
+ * A second exclusion runs in JS after the SQL candidate scan: any page whose
25
+ * latest recorded `page_errors` / `crawl_errors` message classifies into a
26
+ * permanent {@link PERMANENT_ERROR_KINDS} kind (dns / tls / client-blocked /
27
+ * parse-error / connection-refused) is left as-is rather than reset to
28
+ * pending. Without this filter, `--retry-failed` never converges: NXDOMAIN
29
+ * hosts, expired-cert hosts, and `ERR_BLOCKED_BY_CLIENT` ad pixels would be
30
+ * reset every iteration, re-attempted, fail identically, and rejoin the
31
+ * candidate pool for the next iteration. The exclusion keeps the retry
32
+ * target shrinking across `--retry-failed` passes by leaving deterministic
33
+ * dead-ends alone.
34
+ *
35
+ * Matching rows — internal and external alike — are demoted back to pending
36
+ * (`scraped = 0`) and have their stale scrape metadata cleared (the
37
+ * `page_meta` row is deleted outright rather than nulled column-by-column).
38
+ * The page row itself is kept (id preserved) so existing
39
+ * `anchor_edges.href_page_id` referrers stay valid, and `is_external` is
40
+ * left untouched so the next pass re-classifies each page from the crawl
41
+ * scope. Related `anchor_edges`, `image_items`, `resource_ref_edges`, and
42
+ * `page_errors` rows are deleted so the re-scrape can re-insert fresh data
43
+ * without duplicates.
44
+ *
45
+ * SELECT and UPDATE/DELETE statements are chunked to stay below SQLite's
46
+ * `SQLITE_LIMIT_VARIABLE_NUMBER`.
47
+ * @param knex - Knex query builder connected to the archive DB.
48
+ * @returns The URLs of the pages that were reset to pending.
49
+ */
50
+ export async function resetFailedPages(knex) {
51
+ const candidates = await knex('content_items')
52
+ .join('url_refs', 'content_items.url_id', 'url_refs.id')
53
+ .select('content_items.id as id', 'url_refs.url as url')
54
+ .where('content_items.scraped', 1)
55
+ .whereNull('content_items.redirect_dest_id')
56
+ .where((qb) => {
57
+ qb.where('content_items.is_skipped', 0).orWhereNull('content_items.is_skipped');
58
+ })
59
+ .where((qb) => {
60
+ qb.whereNull('content_items.status')
61
+ .orWhere('content_items.status', -1)
62
+ .orWhereNull('content_items.content_type_id')
63
+ .orWhereBetween('content_items.status', [500, 599]);
64
+ });
65
+ if (candidates.length === 0) {
66
+ return [];
67
+ }
68
+ const candidateIds = candidates.map((row) => row.id);
69
+ const candidateUrls = candidates.map((row) => row.url);
70
+ const messages = await getFailedPageMessages(knex, candidateIds, candidateUrls);
71
+ // Drop candidates whose latest recorded message classifies as permanent.
72
+ // An empty/absent message stays in the retry pool — we keep retrying when
73
+ // we don't know it's permanent, erring on the side of investigation.
74
+ const retryable = candidates.filter((row) => {
75
+ const message = messages.get(row.id) ?? '';
76
+ if (message === '') {
77
+ return true;
78
+ }
79
+ return !PERMANENT_ERROR_KINDS.has(classifyErrorKind(message));
80
+ });
81
+ const excludedCount = candidates.length - retryable.length;
82
+ if (excludedCount > 0) {
83
+ dbLog('Excluded %d page(s) from retry — permanent failure kinds (dns/tls/client-blocked/parse-error/connection-refused)', excludedCount);
84
+ }
85
+ if (retryable.length === 0) {
86
+ return [];
87
+ }
88
+ const ids = retryable.map((row) => row.id);
89
+ const urls = retryable.map((row) => row.url);
90
+ const chunkSize = 500;
91
+ for (let i = 0; i < ids.length; i += chunkSize) {
92
+ const chunk = ids.slice(i, i + chunkSize);
93
+ await knex('content_items').whereIn('id', chunk).update({
94
+ scraped: 0,
95
+ status: null,
96
+ status_text: null,
97
+ content_type_id: null,
98
+ content_length: null,
99
+ header_set_id: null,
100
+ // `first_crawled_at` / `last_crawled_at` are deliberately left
101
+ // untouched so the last-success timestamp records survive the
102
+ // demotion (the within-archive observation axis for #11/#17/#19).
103
+ });
104
+ // Clear the prior crawl's per-page data so the re-scrape starts clean.
105
+ // `updatePage` only replaces anchor_edges/image_items/tags/jsonld when
106
+ // the new scrape is non-empty, so this pre-clear is load-bearing for
107
+ // pages that reset but then fail again (or are never reached), and it
108
+ // is the only place `resource_ref_edges` and `page_errors` are
109
+ // cleared. Deleting the `page_meta` row (rather than nulling every
110
+ // column) clears title / description / og:* / twitter:* /
111
+ // meta_extras in one statement; a re-scrape re-inserts it via
112
+ // `ON CONFLICT(page_id) DO UPDATE`.
113
+ await knex('page_meta').whereIn('page_id', chunk).delete();
114
+ await knex('anchor_edges').whereIn('page_id', chunk).delete();
115
+ await knex('image_items').whereIn('page_id', chunk).delete();
116
+ await knex('resource_ref_edges').whereIn('page_id', chunk).delete();
117
+ await knex('page_errors').whereIn('pageId', chunk).delete();
118
+ await knex('page_html_ref').whereIn('page_id', chunk).delete();
119
+ await knex('page_tags').whereIn('pageId', chunk).delete();
120
+ await knex('page_jsonld').whereIn('pageId', chunk).delete();
121
+ }
122
+ dbLog('Reset %d failed pages back to pending', urls.length);
123
+ return urls;
124
+ }
@@ -0,0 +1,37 @@
1
+ import type { WriteRefCaches } from '../../_shared/types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Pre-insert inventory HTML seeds into `content_items` as `scraped = 0`,
5
+ * `source = 'inventory-seed'` placeholders so the URL's existence in the
6
+ * archive is **durable before the scrape phase starts**.
7
+ *
8
+ * Why this is the linchpin of `--inventory` Ctrl+C tolerance: without
9
+ * pre-insertion, HTML seeds live only in the Crawler's in-memory
10
+ * `LinkList` until the dealer eventually calls `setPage`. A Ctrl+C /
11
+ * crash before that point loses the seed without trace, and `--resume`
12
+ * cannot recover it because `getCrawlingState`'s strict pending set
13
+ * requires a `content_items` row. Pre-inserting fills exactly that gap:
14
+ * the strict pending set picks these rows up via its
15
+ * `OR source != 'crawled'` clause, so `--resume` after an interrupted
16
+ * inventory pass picks every seed back up. See `getCrawlingState` for
17
+ * the strict-set rationale.
18
+ *
19
+ * Idempotent: both the `url_refs` and `content_items` inserts are
20
+ * `ON CONFLICT ... IGNORE`, keeping existing rows intact. The
21
+ * `resolveContentItemId` crawled-wins downgrade still fires later when a
22
+ * crawled-lineage anchor reaches one of these seeds — that's the right
23
+ * behaviour (a seed that turned out to be reachable is not an orphan
24
+ * and should not retain the inventory label).
25
+ *
26
+ * Chunked into 500-URL batches so SQLite's bound-parameter limit
27
+ * (`SQLITE_MAX_VARIABLE_NUMBER`) cannot be hit even on a
28
+ * tens-of-thousands inventory list.
29
+ *
30
+ * Called by `CrawlerOrchestrator.inventory` during the
31
+ * `.bak`-protected ingestion phase, so any failure here aborts the run
32
+ * and restores from backup — the operator reruns from scratch.
33
+ * @param knex - Knex query builder connected to the archive DB.
34
+ * @param caches
35
+ * @param urls - URL strings already in `withoutHashAndAuth` form.
36
+ */
37
+ export declare function insertInventorySeeds(knex: Knex, caches: WriteRefCaches, urls: readonly string[]): Promise<void>;
@@ -0,0 +1,72 @@
1
+ import { eachSplitted } from '../../../../utils/array/each-splitted.js';
2
+ import { resolveUrlRefs } from '../../../populate-entity-tables/resolve-url-refs.js';
3
+ import { decomposeUrl } from '../../../populate-ref-tables/decompose-url.js';
4
+ /**
5
+ * Pre-insert inventory HTML seeds into `content_items` as `scraped = 0`,
6
+ * `source = 'inventory-seed'` placeholders so the URL's existence in the
7
+ * archive is **durable before the scrape phase starts**.
8
+ *
9
+ * Why this is the linchpin of `--inventory` Ctrl+C tolerance: without
10
+ * pre-insertion, HTML seeds live only in the Crawler's in-memory
11
+ * `LinkList` until the dealer eventually calls `setPage`. A Ctrl+C /
12
+ * crash before that point loses the seed without trace, and `--resume`
13
+ * cannot recover it because `getCrawlingState`'s strict pending set
14
+ * requires a `content_items` row. Pre-inserting fills exactly that gap:
15
+ * the strict pending set picks these rows up via its
16
+ * `OR source != 'crawled'` clause, so `--resume` after an interrupted
17
+ * inventory pass picks every seed back up. See `getCrawlingState` for
18
+ * the strict-set rationale.
19
+ *
20
+ * Idempotent: both the `url_refs` and `content_items` inserts are
21
+ * `ON CONFLICT ... IGNORE`, keeping existing rows intact. The
22
+ * `resolveContentItemId` crawled-wins downgrade still fires later when a
23
+ * crawled-lineage anchor reaches one of these seeds — that's the right
24
+ * behaviour (a seed that turned out to be reachable is not an orphan
25
+ * and should not retain the inventory label).
26
+ *
27
+ * Chunked into 500-URL batches so SQLite's bound-parameter limit
28
+ * (`SQLITE_MAX_VARIABLE_NUMBER`) cannot be hit even on a
29
+ * tens-of-thousands inventory list.
30
+ *
31
+ * Called by `CrawlerOrchestrator.inventory` during the
32
+ * `.bak`-protected ingestion phase, so any failure here aborts the run
33
+ * and restores from backup — the operator reruns from scratch.
34
+ * @param knex - Knex query builder connected to the archive DB.
35
+ * @param caches
36
+ * @param urls - URL strings already in `withoutHashAndAuth` form.
37
+ */
38
+ export async function insertInventorySeeds(knex, caches, urls) {
39
+ if (urls.length === 0) {
40
+ return;
41
+ }
42
+ await eachSplitted([...urls], 500, async (chunk) => {
43
+ await knex('url_refs')
44
+ .insert(chunk.map((url) => ({ url, ...decomposeUrl(url) })))
45
+ .onConflict('url')
46
+ .ignore();
47
+ const urlIds = await resolveUrlRefs(knex, chunk);
48
+ const rows = chunk.map((url) => {
49
+ const urlId = urlIds.get(url);
50
+ if (urlId === undefined) {
51
+ throw new Error(`insertInventorySeeds: url_refs.id not resolved for ${url}`);
52
+ }
53
+ caches.urlIds.set(url, urlId);
54
+ return {
55
+ url_id: urlId,
56
+ scraped: 0,
57
+ is_external: 0,
58
+ is_target: 0,
59
+ source: 'inventory-seed',
60
+ };
61
+ });
62
+ await knex('content_items').insert(rows).onConflict('url_id').ignore();
63
+ const inserted = (await knex
64
+ .select('ci.id', 'ci.source', 'ur.url')
65
+ .from('content_items as ci')
66
+ .join('url_refs as ur', 'ur.id', 'ci.url_id')
67
+ .whereIn('ur.url', chunk));
68
+ for (const row of inserted) {
69
+ caches.contentItems.set(row.url, { id: row.id, source: row.source });
70
+ }
71
+ });
72
+ }
@@ -0,0 +1,17 @@
1
+ import type { PageData } from '../../../../utils/types/types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Replaces the page's JSON-LD / SpeculationRules rows with the freshly
5
+ * captured set. Called inside `updatePage`'s transaction.
6
+ *
7
+ * `writeHtml = false` branches (`setExternalPage`, metadata-only) skip
8
+ * this entirely — JSON-LD lives inside the HTML body, so external pages
9
+ * that are not rendered have no entries to write. An empty array on a
10
+ * normally-rendered page is treated as a degraded re-scrape: prior rows
11
+ * are kept (same `delete-only-when-replacing` invariant as `anchors` /
12
+ * `images`).
13
+ * @param pageId
14
+ * @param meta
15
+ * @param trx
16
+ */
17
+ export declare function insertJsonLd(pageId: number, meta: PageData['meta'], trx: Knex.Transaction): Promise<void>;
@@ -0,0 +1,49 @@
1
+ import { eachSplitted } from '../../../../utils/array/each-splitted.js';
2
+ import { classifyJsonLdType } from '../../../meta/classify-jsonld-type.js';
3
+ /**
4
+ * Replaces the page's JSON-LD / SpeculationRules rows with the freshly
5
+ * captured set. Called inside `updatePage`'s transaction.
6
+ *
7
+ * `writeHtml = false` branches (`setExternalPage`, metadata-only) skip
8
+ * this entirely — JSON-LD lives inside the HTML body, so external pages
9
+ * that are not rendered have no entries to write. An empty array on a
10
+ * normally-rendered page is treated as a degraded re-scrape: prior rows
11
+ * are kept (same `delete-only-when-replacing` invariant as `anchors` /
12
+ * `images`).
13
+ * @param pageId
14
+ * @param meta
15
+ * @param trx
16
+ */
17
+ export async function insertJsonLd(pageId, meta, trx) {
18
+ // `??` guards tolerate the legacy "minimal meta" shape from older test
19
+ // fixtures. Real beholder 3.0.0 always populates these required fields.
20
+ const jsonLd = meta.jsonLd ?? [];
21
+ const speculationRules = meta.speculationRules ?? [];
22
+ const rows = [];
23
+ for (const entry of jsonLd) {
24
+ rows.push({
25
+ pageId,
26
+ kind: 'ld+json',
27
+ type: classifyJsonLdType(entry),
28
+ raw: entry.raw,
29
+ parsed: entry.parsed === undefined ? null : JSON.stringify(entry.parsed),
30
+ parseError: entry.parseError ?? null,
31
+ });
32
+ }
33
+ for (const entry of speculationRules) {
34
+ rows.push({
35
+ pageId,
36
+ kind: 'speculationrules',
37
+ type: classifyJsonLdType(entry),
38
+ raw: entry.raw,
39
+ parsed: entry.parsed === undefined ? null : JSON.stringify(entry.parsed),
40
+ parseError: entry.parseError ?? null,
41
+ });
42
+ }
43
+ if (rows.length === 0)
44
+ return;
45
+ await trx('page_jsonld').where('pageId', pageId).delete();
46
+ await eachSplitted(rows, 100, async (chunk) => {
47
+ await trx('page_jsonld').insert(chunk);
48
+ });
49
+ }