@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,13 @@
1
+ import type { JsonLdRow } from '../../meta/types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Retrieves all `page_jsonld` rows for the given page id, parsed back into
5
+ * {@link JsonLdRow} shape (with `parsed` deserialised from its JSON column).
6
+ *
7
+ * Read-side counterpart to `insertJsonLd`. Returns rows in insertion order
8
+ * by `id` so the order observed by `get-page-jsonld` matches the order the
9
+ * scraper saw them.
10
+ * @param knex - Knex query builder connected to the archive DB.
11
+ * @param pageId
12
+ */
13
+ export declare function getJsonLdOfPage(knex: Knex, pageId: number): Promise<JsonLdRow[]>;
@@ -0,0 +1,27 @@
1
+ import { safeParseJson } from '../_shared/safe-parse-json.js';
2
+ /**
3
+ * Retrieves all `page_jsonld` rows for the given page id, parsed back into
4
+ * {@link JsonLdRow} shape (with `parsed` deserialised from its JSON column).
5
+ *
6
+ * Read-side counterpart to `insertJsonLd`. Returns rows in insertion order
7
+ * by `id` so the order observed by `get-page-jsonld` matches the order the
8
+ * scraper saw them.
9
+ * @param knex - Knex query builder connected to the archive DB.
10
+ * @param pageId
11
+ */
12
+ export async function getJsonLdOfPage(knex, pageId) {
13
+ const rows = await knex
14
+ .select('id', 'pageId', 'kind', 'type', 'raw', 'parsed', 'parseError')
15
+ .from('page_jsonld')
16
+ .where('pageId', pageId)
17
+ .orderBy('id', 'asc');
18
+ return rows.map((r) => ({
19
+ id: r.id,
20
+ pageId: r.pageId,
21
+ kind: r.kind === 'speculationrules' ? 'speculationrules' : 'ld+json',
22
+ type: r.type,
23
+ raw: r.raw,
24
+ parsed: r.parsed === null ? null : safeParseJson(r.parsed),
25
+ parseError: r.parseError,
26
+ }));
27
+ }
@@ -0,0 +1,12 @@
1
+ import type { TagRow } from '../../meta/types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Retrieves all `page_tags` rows for the given page id, parsed back into
5
+ * {@link TagRow} shape (with `categories` and `sources` JSON columns
6
+ * deserialised).
7
+ *
8
+ * Read-side counterpart to `insertTags`.
9
+ * @param knex - Knex query builder connected to the archive DB.
10
+ * @param pageId
11
+ */
12
+ export declare function getTagsOfPage(knex: Knex, pageId: number): Promise<TagRow[]>;
@@ -0,0 +1,28 @@
1
+ import { safeParseJson } from '../_shared/safe-parse-json.js';
2
+ /**
3
+ * Retrieves all `page_tags` rows for the given page id, parsed back into
4
+ * {@link TagRow} shape (with `categories` and `sources` JSON columns
5
+ * deserialised).
6
+ *
7
+ * Read-side counterpart to `insertTags`.
8
+ * @param knex - Knex query builder connected to the archive DB.
9
+ * @param pageId
10
+ */
11
+ export async function getTagsOfPage(knex, pageId) {
12
+ const rows = await knex
13
+ .select('id', 'pageId', 'provider', 'category', 'externalId', 'version', 'confidence', 'categories', 'sources')
14
+ .from('page_tags')
15
+ .where('pageId', pageId)
16
+ .orderBy('id', 'asc');
17
+ return rows.map((r) => ({
18
+ id: r.id,
19
+ pageId: r.pageId,
20
+ provider: r.provider,
21
+ category: r.category,
22
+ externalId: r.externalId,
23
+ version: r.version,
24
+ confidence: r.confidence,
25
+ categories: r.categories === null ? [] : (safeParseJson(r.categories) ?? []),
26
+ sources: r.sources === null ? [] : (safeParseJson(r.sources) ?? []),
27
+ }));
28
+ }
@@ -0,0 +1,8 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Assigns natural URL sort order values (`content_items.crawl_order`) to
4
+ * all internal pages. Pages are sorted using {@link pathComparator} and
5
+ * assigned sequential order numbers.
6
+ * @param knex - Knex query builder connected to the archive DB.
7
+ */
8
+ export declare function setUrlOrder(knex: Knex): Promise<void>;
@@ -0,0 +1,32 @@
1
+ import { pathComparator } from '@d-zero/shared/sort/path';
2
+ import { dbLog } from '../../../debug.js';
3
+ /**
4
+ * Assigns natural URL sort order values (`content_items.crawl_order`) to
5
+ * all internal pages. Pages are sorted using {@link pathComparator} and
6
+ * assigned sequential order numbers.
7
+ * @param knex - Knex query builder connected to the archive DB.
8
+ */
9
+ export async function setUrlOrder(knex) {
10
+ dbLog('Set URL Order');
11
+ const res = (await knex
12
+ .select('ci.id', 'ur.url')
13
+ .from('content_items as ci')
14
+ .join('url_refs as ur', 'ur.id', 'ci.url_id')
15
+ .where('ci.is_external', '=', 0));
16
+ const sorted = res.toSorted((a, b) => pathComparator(a.url, b.url));
17
+ // Batch update using chunked CASE statements to avoid N+1 queries
18
+ const BATCH_SIZE = 500;
19
+ for (let i = 0; i < sorted.length; i += BATCH_SIZE) {
20
+ const batch = sorted.slice(i, i + BATCH_SIZE);
21
+ const ids = batch.map((row) => row.id);
22
+ const bindings = [];
23
+ const cases = batch
24
+ .map((row, j) => {
25
+ bindings.push(row.id, i + j + 1);
26
+ return 'WHEN ? THEN ?';
27
+ })
28
+ .join(' ');
29
+ const placeholders = ids.map(() => '?').join(',');
30
+ await knex.raw(`UPDATE content_items SET crawl_order = CASE id ${cases} END WHERE id IN (${placeholders})`, [...bindings, ...ids]);
31
+ }
32
+ }
@@ -0,0 +1,18 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Builds a `content_items` query joined against `page_meta` and every ref
4
+ * table needed to reconstruct a flat, legacy-shaped page row. Selected
5
+ * columns are aliased to the `DB_Page` field names; `responseHeaders` and
6
+ * `meta_extras` are NOT reconstructed here (they need a second, batched
7
+ * pass — see {@link ../read/reconstruct-page-rows.js}) so this query stays
8
+ * a single-pass join with no N+1 subqueries. `headerSetId` and the
9
+ * `extras_body` / `extras_codec` pair are selected as intermediates for
10
+ * that second pass.
11
+ * @param knex - Knex query builder connected to the archive DB.
12
+ * @returns A query builder pre-configured with every join `get-pages.ts` /
13
+ * `get-pages-with-rels.ts` need; callers add `.where()` / `.limit()` /
14
+ * `.offset()` on top.
15
+ * @example
16
+ * const rows = await buildPageQuery(knex).where('ci.is_target', 1);
17
+ */
18
+ export declare function buildPageQuery(knex: Knex): Knex.QueryBuilder;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Builds a `content_items` query joined against `page_meta` and every ref
3
+ * table needed to reconstruct a flat, legacy-shaped page row. Selected
4
+ * columns are aliased to the `DB_Page` field names; `responseHeaders` and
5
+ * `meta_extras` are NOT reconstructed here (they need a second, batched
6
+ * pass — see {@link ../read/reconstruct-page-rows.js}) so this query stays
7
+ * a single-pass join with no N+1 subqueries. `headerSetId` and the
8
+ * `extras_body` / `extras_codec` pair are selected as intermediates for
9
+ * that second pass.
10
+ * @param knex - Knex query builder connected to the archive DB.
11
+ * @returns A query builder pre-configured with every join `get-pages.ts` /
12
+ * `get-pages-with-rels.ts` need; callers add `.where()` / `.limit()` /
13
+ * `.offset()` on top.
14
+ * @example
15
+ * const rows = await buildPageQuery(knex).where('ci.is_target', 1);
16
+ */
17
+ export function buildPageQuery(knex) {
18
+ return knex('content_items as ci')
19
+ .join('url_refs as ur', 'ur.id', 'ci.url_id')
20
+ .leftJoin('content_type_refs as ctr', 'ctr.id', 'ci.content_type_id')
21
+ .leftJoin('page_meta as pm', 'pm.page_id', 'ci.id')
22
+ .leftJoin('text_refs as title_ref', 'title_ref.id', 'pm.title_text_id')
23
+ .leftJoin('text_refs as description_ref', 'description_ref.id', 'pm.description_text_id')
24
+ .leftJoin('text_refs as keywords_ref', 'keywords_ref.id', 'pm.keywords_text_id')
25
+ .leftJoin('text_refs as robots_raw_ref', 'robots_raw_ref.id', 'pm.robots_raw_text_id')
26
+ .leftJoin('text_refs as og_title_ref', 'og_title_ref.id', 'pm.og_title_text_id')
27
+ .leftJoin('text_refs as og_description_ref', 'og_description_ref.id', 'pm.og_description_text_id')
28
+ .leftJoin('text_refs as twitter_title_ref', 'twitter_title_ref.id', 'pm.twitter_title_text_id')
29
+ .leftJoin('text_refs as twitter_description_ref', 'twitter_description_ref.id', 'pm.twitter_description_text_id')
30
+ .leftJoin('url_refs as canonical_ur', 'canonical_ur.id', 'pm.canonical_url_id')
31
+ .leftJoin('url_refs as amphtml_ur', 'amphtml_ur.id', 'pm.amphtml_url_id')
32
+ .leftJoin('url_refs as manifest_ur', 'manifest_ur.id', 'pm.manifest_url_id')
33
+ .leftJoin('url_refs as icon_ur', 'icon_ur.id', 'pm.icon_url_id')
34
+ .leftJoin('url_refs as apple_ur', 'apple_ur.id', 'pm.apple_touch_icon_url_id')
35
+ .leftJoin('url_refs as og_url_ur', 'og_url_ur.id', 'pm.og_url_id')
36
+ .leftJoin('url_refs as og_image_ur', 'og_image_ur.id', 'pm.og_image_url_id')
37
+ .leftJoin('url_refs as twitter_image_ur', 'twitter_image_ur.id', 'pm.twitter_image_url_id')
38
+ .leftJoin('json_refs as extras_ref', 'extras_ref.id', 'pm.meta_extras_json_id')
39
+ .select('ci.id as id', 'ur.url as url', 'ci.redirect_dest_id as redirectDestId', 'ci.scraped as scraped', 'ci.is_target as isTarget', 'ci.is_external as isExternal', 'ci.status as status', 'ci.status_text as statusText', 'ctr.raw as contentType', 'ci.content_length as contentLength', 'ci.header_set_id as headerSetId', 'pm.lang as lang', 'pm.dir as dir', 'pm.charset as charset', 'pm.base_href as baseHref', 'pm.viewport_raw as viewport_raw', 'pm.theme_color as themeColor', 'pm.application_name as applicationName', 'pm.author as author', 'pm.generator as generator', 'pm.publisher as publisher', 'title_ref.text as title', 'description_ref.text as description', 'keywords_ref.text as keywords', 'robots_raw_ref.text as robots_raw', 'pm.robots_noindex as robots_noindex', 'pm.robots_nofollow as robots_nofollow', 'pm.robots_noarchive as robots_noarchive', 'pm.robots_noimageindex as robots_noimageindex', 'pm.googlebot as googlebot', 'canonical_ur.url as canonical', 'amphtml_ur.url as amphtml', 'manifest_ur.url as manifest', 'icon_ur.url as icon_href', 'apple_ur.url as appleTouchIcon_href', 'pm.og_type as og_type', 'og_title_ref.text as og_title', 'og_url_ur.url as og_url', 'pm.og_site_name as og_site_name', 'og_description_ref.text as og_description', 'og_image_ur.url as og_image', 'pm.og_image_alt as og_image_alt', 'pm.og_image_width as og_image_width', 'pm.og_image_height as og_image_height', 'pm.og_locale as og_locale', 'pm.og_article_published_time as og_article_published_time', 'pm.og_article_modified_time as og_article_modified_time', 'pm.twitter_card as twitter_card', 'pm.twitter_site as twitter_site', 'pm.twitter_creator as twitter_creator', 'twitter_title_ref.text as twitter_title', 'twitter_description_ref.text as twitter_description', 'twitter_image_ur.url as twitter_image', 'pm.fb_app_id as fb_app_id', 'pm.verification_google as verification_google', 'pm.format_detection_telephone as formatDetection_telephone', 'ci.first_crawled_at as firstCrawledAt', 'ci.last_crawled_at as lastCrawledAt', 'pm.tag_count as tag_count', 'pm.jsonld_count as jsonld_count', 'pm.tags_providers_csv as tags_providers_csv', 'extras_ref.json_text as extras_body', 'extras_ref.codec as extras_codec', 'ci.is_skipped as isSkipped', 'ci.skip_reason as skipReason', 'ci.crawl_order as order', 'ci.source as source');
40
+ }
@@ -0,0 +1,70 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Retrieves the current crawling state by listing scraped and pending URLs.
4
+ *
5
+ * `scraped` is straightforward: every page row whose `scraped` flag is `1`
6
+ * — that is, every URL the crawl reached a terminal state on, including
7
+ * setSkippedPage / setExternalPage / outright setPage success or failure.
8
+ *
9
+ * `pending` is intentionally STRICT — not "every `scraped = 0` row".
10
+ * Three filters apply:
11
+ *
12
+ * 1. `scraped = 0` — work still incomplete.
13
+ * 2. `is_external = 0` — only in-scope work. External URLs go through a
14
+ * HEAD-only path that always lands on `scraped = 1` (either setPage or
15
+ * setExternalPage). A row with `is_external = 1 AND scraped = 0` is
16
+ * therefore a data anomaly, and resume / inventory / append have no
17
+ * business retrying it on the next session.
18
+ * 3. `EXISTS (anchor_edges with href_page_id = content_items.id) OR source
19
+ * != 'crawled'` — the row was either discovered as an anchor destination
20
+ * during a previous scrape OR was explicitly tagged with a non-default
21
+ * source label (`'inventory-seed'`, `'inventory-discovered'`, …). Both
22
+ * halves of the OR represent "deliberately enqueued, expected to be
23
+ * processed", which is exactly what `resume` should pick up.
24
+ *
25
+ * The orphan filter targets the **predicted-discard leak** in
26
+ * `crawler.ts` where `shouldDiscardPredicted` returns true but no
27
+ * `emit('skip')` follows. Such placeholders are inserted with the
28
+ * DB DEFAULT `source = 'crawled'` (no caller explicitly labels
29
+ * them) AND have no anchor referrer (predicted URLs are
30
+ * synthesised from pagination patterns, never anchored from a
31
+ * rendered page) — both halves of the OR are therefore false and
32
+ * the leak is excluded.
33
+ *
34
+ * The `source != 'crawled'` clause specifically saves the
35
+ * `--inventory` × `--retry-failed` interaction: an inventory-seed
36
+ * URL came from the operator's URL list (no anchor referrer) and
37
+ * `resetFailedPages` puts it back at `scraped = 0`. Without this
38
+ * clause those legitimate retries would be dropped on resume.
39
+ *
40
+ * The defensive shape is on purpose: the data source can drift into
41
+ * anomalous states under interruption, but the reader must never throw
42
+ * or feed garbage back into the dealer. A real in-scope URL that was
43
+ * truly interrupted mid-crawl will always have at least one anchor
44
+ * referrer (otherwise the dealer would not have queued it), so the
45
+ * strict filter loses no legitimate pending work.
46
+ *
47
+ * Seeds passed directly to `Crawler.start()` are NOT in the strict
48
+ * pending set when they were never picked by the dealer — they have no
49
+ * DB row at all in that case (`linkList.add` is purely in-memory until
50
+ * `setPage` runs). A Ctrl-C between dealer pick and `setPage` likewise
51
+ * leaves no row to recover. Recovery of un-picked seeds is the
52
+ * responsibility of the caller (e.g. re-running `--inventory ./list.txt`
53
+ * with the same URL list).
54
+ *
55
+ * The query uses an explicit `ci` alias on the `content_items` table so the
56
+ * correlated `EXISTS` subquery can join via `whereRaw('anchor_edges.href_page_id
57
+ * = ci.id')`. A future refactor that renames the alias must update both
58
+ * sites — the raw string in the subquery cannot be grep-resolved
59
+ * automatically. Read-only / stub viewer connections never call this
60
+ * method (they do not need to know about pending state), so the EXISTS
61
+ * shape is safe to use without the `migrate*` guards that other writer
62
+ * methods carry.
63
+ * @param knex - Knex query builder connected to the archive DB.
64
+ * @returns An object with `scraped` (completed URLs) and `pending` (the
65
+ * strict set of in-scope, anchor-referenced, unfinished URLs).
66
+ */
67
+ export declare function getCrawlingState(knex: Knex): Promise<{
68
+ scraped: string[];
69
+ pending: string[];
70
+ }>;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Retrieves the current crawling state by listing scraped and pending URLs.
3
+ *
4
+ * `scraped` is straightforward: every page row whose `scraped` flag is `1`
5
+ * — that is, every URL the crawl reached a terminal state on, including
6
+ * setSkippedPage / setExternalPage / outright setPage success or failure.
7
+ *
8
+ * `pending` is intentionally STRICT — not "every `scraped = 0` row".
9
+ * Three filters apply:
10
+ *
11
+ * 1. `scraped = 0` — work still incomplete.
12
+ * 2. `is_external = 0` — only in-scope work. External URLs go through a
13
+ * HEAD-only path that always lands on `scraped = 1` (either setPage or
14
+ * setExternalPage). A row with `is_external = 1 AND scraped = 0` is
15
+ * therefore a data anomaly, and resume / inventory / append have no
16
+ * business retrying it on the next session.
17
+ * 3. `EXISTS (anchor_edges with href_page_id = content_items.id) OR source
18
+ * != 'crawled'` — the row was either discovered as an anchor destination
19
+ * during a previous scrape OR was explicitly tagged with a non-default
20
+ * source label (`'inventory-seed'`, `'inventory-discovered'`, …). Both
21
+ * halves of the OR represent "deliberately enqueued, expected to be
22
+ * processed", which is exactly what `resume` should pick up.
23
+ *
24
+ * The orphan filter targets the **predicted-discard leak** in
25
+ * `crawler.ts` where `shouldDiscardPredicted` returns true but no
26
+ * `emit('skip')` follows. Such placeholders are inserted with the
27
+ * DB DEFAULT `source = 'crawled'` (no caller explicitly labels
28
+ * them) AND have no anchor referrer (predicted URLs are
29
+ * synthesised from pagination patterns, never anchored from a
30
+ * rendered page) — both halves of the OR are therefore false and
31
+ * the leak is excluded.
32
+ *
33
+ * The `source != 'crawled'` clause specifically saves the
34
+ * `--inventory` × `--retry-failed` interaction: an inventory-seed
35
+ * URL came from the operator's URL list (no anchor referrer) and
36
+ * `resetFailedPages` puts it back at `scraped = 0`. Without this
37
+ * clause those legitimate retries would be dropped on resume.
38
+ *
39
+ * The defensive shape is on purpose: the data source can drift into
40
+ * anomalous states under interruption, but the reader must never throw
41
+ * or feed garbage back into the dealer. A real in-scope URL that was
42
+ * truly interrupted mid-crawl will always have at least one anchor
43
+ * referrer (otherwise the dealer would not have queued it), so the
44
+ * strict filter loses no legitimate pending work.
45
+ *
46
+ * Seeds passed directly to `Crawler.start()` are NOT in the strict
47
+ * pending set when they were never picked by the dealer — they have no
48
+ * DB row at all in that case (`linkList.add` is purely in-memory until
49
+ * `setPage` runs). A Ctrl-C between dealer pick and `setPage` likewise
50
+ * leaves no row to recover. Recovery of un-picked seeds is the
51
+ * responsibility of the caller (e.g. re-running `--inventory ./list.txt`
52
+ * with the same URL list).
53
+ *
54
+ * The query uses an explicit `ci` alias on the `content_items` table so the
55
+ * correlated `EXISTS` subquery can join via `whereRaw('anchor_edges.href_page_id
56
+ * = ci.id')`. A future refactor that renames the alias must update both
57
+ * sites — the raw string in the subquery cannot be grep-resolved
58
+ * automatically. Read-only / stub viewer connections never call this
59
+ * method (they do not need to know about pending state), so the EXISTS
60
+ * shape is safe to use without the `migrate*` guards that other writer
61
+ * methods carry.
62
+ * @param knex - Knex query builder connected to the archive DB.
63
+ * @returns An object with `scraped` (completed URLs) and `pending` (the
64
+ * strict set of in-scope, anchor-referenced, unfinished URLs).
65
+ */
66
+ export async function getCrawlingState(knex) {
67
+ const ex = (r) => r.url;
68
+ const $scraped = await knex('content_items')
69
+ .join('url_refs', 'url_refs.id', 'content_items.url_id')
70
+ .select('url_refs.url as url')
71
+ .where('content_items.scraped', 1);
72
+ const scraped = $scraped.map(ex);
73
+ const $pending = await knex
74
+ .select('ur.url as url')
75
+ .from({ ci: 'content_items' })
76
+ .join({ ur: 'url_refs' }, 'ur.id', 'ci.url_id')
77
+ .where('ci.scraped', 0)
78
+ .where('ci.is_external', 0)
79
+ .where((qb) => {
80
+ // "Anchored OR explicitly labelled". Either side is evidence
81
+ // that the row was deliberately enqueued for processing —
82
+ // only the predicted-discard leak (DEFAULT 'crawled' + no
83
+ // anchor) fails both halves. The `whereExists` callback
84
+ // uses `select('*')` since the column list is irrelevant
85
+ // inside an EXISTS check; calling through `client.raw(...)`
86
+ // would reach a private builder field.
87
+ qb.whereExists(function () {
88
+ this.select('*')
89
+ .from('anchor_edges')
90
+ .whereRaw('anchor_edges.href_page_id = ci.id');
91
+ }).orWhereNot('ci.source', 'crawled');
92
+ });
93
+ const pending = $pending.map(ex);
94
+ return {
95
+ scraped,
96
+ pending,
97
+ };
98
+ }
@@ -0,0 +1,15 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Return the subset of `urls` that already exist as `content_items` rows.
4
+ * Chunked into batches so SQLite's `IN (?, ?, …)` parameter limit
5
+ * (`SQLITE_MAX_VARIABLE_NUMBER`, default 999) cannot be hit even when the
6
+ * inventory list contains tens of thousands of URLs.
7
+ *
8
+ * Read-only — no transaction, no lock contention with the crawler write
9
+ * pipeline (callers run this BEFORE the `<archive>.bak` is taken and the
10
+ * crawl is started).
11
+ * @param knex - Knex query builder connected to the archive DB.
12
+ * @param urls - URL strings to probe (already in `withoutHashAndAuth` form).
13
+ * @returns URLs found among `content_items`. Order is not preserved.
14
+ */
15
+ export declare function getExistingPageUrls(knex: Knex, urls: readonly string[]): Promise<string[]>;
@@ -0,0 +1,30 @@
1
+ import { eachSplitted } from '../../../../utils/array/each-splitted.js';
2
+ /**
3
+ * Return the subset of `urls` that already exist as `content_items` rows.
4
+ * Chunked into batches so SQLite's `IN (?, ?, …)` parameter limit
5
+ * (`SQLITE_MAX_VARIABLE_NUMBER`, default 999) cannot be hit even when the
6
+ * inventory list contains tens of thousands of URLs.
7
+ *
8
+ * Read-only — no transaction, no lock contention with the crawler write
9
+ * pipeline (callers run this BEFORE the `<archive>.bak` is taken and the
10
+ * crawl is started).
11
+ * @param knex - Knex query builder connected to the archive DB.
12
+ * @param urls - URL strings to probe (already in `withoutHashAndAuth` form).
13
+ * @returns URLs found among `content_items`. Order is not preserved.
14
+ */
15
+ export async function getExistingPageUrls(knex, urls) {
16
+ if (urls.length === 0) {
17
+ return [];
18
+ }
19
+ const found = [];
20
+ await eachSplitted([...urls], 500, async (chunk) => {
21
+ const rows = await knex('content_items')
22
+ .join('url_refs', 'url_refs.id', 'content_items.url_id')
23
+ .select('url_refs.url as url')
24
+ .whereIn('url_refs.url', chunk);
25
+ for (const row of rows) {
26
+ found.push(row.url);
27
+ }
28
+ });
29
+ return found;
30
+ }
@@ -0,0 +1,12 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Counts the total number of pages in the database.
4
+ *
5
+ * Pages live in `content_items` (resources are a physically separate
6
+ * `resource_items` table), so a bare count needs no type discriminator
7
+ * and no join.
8
+ * @param knex - Knex query builder connected to the archive DB.
9
+ * @returns The total page count.
10
+ * @throws {Error} If the count query fails.
11
+ */
12
+ export declare function getPageCount(knex: Knex): Promise<number>;
@@ -0,0 +1,21 @@
1
+ import { dbLog } from '../../../debug.js';
2
+ /**
3
+ * Counts the total number of pages in the database.
4
+ *
5
+ * Pages live in `content_items` (resources are a physically separate
6
+ * `resource_items` table), so a bare count needs no type discriminator
7
+ * and no join.
8
+ * @param knex - Knex query builder connected to the archive DB.
9
+ * @returns The total page count.
10
+ * @throws {Error} If the count query fails.
11
+ */
12
+ export async function getPageCount(knex) {
13
+ const selected = await knex.count('id').from('content_items');
14
+ if (!selected[0]) {
15
+ throw new Error('No count');
16
+ }
17
+ // @ts-expect-error
18
+ const count = selected[0]['count(`id`)'];
19
+ dbLog('Number of pages: %d', count);
20
+ return count;
21
+ }
@@ -0,0 +1,24 @@
1
+ import type { PageSource } from '../../../types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Look up the `source` column of a single page by its URL key. Used by
5
+ * the orchestrator's `PageSourceLookup` injection so the Crawler can
6
+ * resolve a parent page's lineage on `--resume` / `--retry-failed`
7
+ * sessions, where the in-memory `inventoryMode` is no longer
8
+ * available but the DB still remembers what label was last persisted.
9
+ *
10
+ * Returns `undefined` when the URL has no `content_items` row (e.g. a
11
+ * brand-new URL that has not been seen yet) so the caller can fall
12
+ * through to its default behaviour without distinguishing "row absent"
13
+ * from "row present with NULL source" — the schema's
14
+ * `NOT NULL DEFAULT 'crawled'` makes a NULL value impossible in practice.
15
+ *
16
+ * Read-only — no transaction. URL text lives in `url_refs` (a UNIQUE
17
+ * column), so the lookup is a single indexed seek plus one PK join to
18
+ * `content_items`. The Crawler calls this at most once per page render,
19
+ * NOT per sub-resource, so the N+1 risk does not apply.
20
+ * @param knex - Knex query builder connected to the archive DB.
21
+ * @param url - URL key in `url.withoutHashAndAuth` form.
22
+ * @returns The recorded `source`, or `undefined` when no row exists.
23
+ */
24
+ export declare function getPageSourceByUrl(knex: Knex, url: string): Promise<PageSource | undefined>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Look up the `source` column of a single page by its URL key. Used by
3
+ * the orchestrator's `PageSourceLookup` injection so the Crawler can
4
+ * resolve a parent page's lineage on `--resume` / `--retry-failed`
5
+ * sessions, where the in-memory `inventoryMode` is no longer
6
+ * available but the DB still remembers what label was last persisted.
7
+ *
8
+ * Returns `undefined` when the URL has no `content_items` row (e.g. a
9
+ * brand-new URL that has not been seen yet) so the caller can fall
10
+ * through to its default behaviour without distinguishing "row absent"
11
+ * from "row present with NULL source" — the schema's
12
+ * `NOT NULL DEFAULT 'crawled'` makes a NULL value impossible in practice.
13
+ *
14
+ * Read-only — no transaction. URL text lives in `url_refs` (a UNIQUE
15
+ * column), so the lookup is a single indexed seek plus one PK join to
16
+ * `content_items`. The Crawler calls this at most once per page render,
17
+ * NOT per sub-resource, so the N+1 risk does not apply.
18
+ * @param knex - Knex query builder connected to the archive DB.
19
+ * @param url - URL key in `url.withoutHashAndAuth` form.
20
+ * @returns The recorded `source`, or `undefined` when no row exists.
21
+ */
22
+ export async function getPageSourceByUrl(knex, url) {
23
+ const [row] = await knex('content_items')
24
+ .join('url_refs', 'url_refs.id', 'content_items.url_id')
25
+ .select('content_items.source as source')
26
+ .where('url_refs.url', url);
27
+ return row?.source;
28
+ }
@@ -0,0 +1,38 @@
1
+ import type { DB_Anchor, DB_Page, DB_Redirect, DB_Referrer } from '../../../types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Retrieves pages along with their related redirect, anchor, and referrer
5
+ * data. Results are ordered by the natural URL sort order (materialised
6
+ * through {@link setUrlOrder} into `content_items.crawl_order` before the
7
+ * join queries run). Only non-redirected pages are returned in the
8
+ * top-level `pages` array; redirect sources are surfaced only through the
9
+ * `redirects` array so downstream reports (Sheets export) can attribute
10
+ * every source without double-counting the destination.
11
+ *
12
+ * **Redirect resolution stays a read-time join.** `anchor_edges.href_page_id`
13
+ * (like the legacy `anchors.hrefId` it replaces) is resolved by URL only at
14
+ * write time (`resolveContentItemId`), with no redirect-chain following —
15
+ * an anchor's target may itself become a redirect source discovered later
16
+ * in the crawl. `redirectTable()` joins the single `redirect_dest_id` hop
17
+ * at read time so `anchors[].url` reflects the final destination while
18
+ * `anchors[].href` keeps the immediate (pre-redirect) target URL; one hop
19
+ * suffices only because `linkRedirectSources` pre-flattens every hop's
20
+ * `redirect_dest_id` to the final destination at write time.
21
+ *
22
+ * **Deduped anchor identity.** `anchor_edges` collapses repeated
23
+ * `(page_id, href_page_id)` instances into one row with a `count`; `hash` /
24
+ * `textContent` below come from `first_hash` / `first_text_id` (the first
25
+ * observed instance), not every instance — an intentional behavior change
26
+ * from the legacy `anchors` table's one-row-per-instance model.
27
+ * @param knex - Knex query builder connected to the archive DB.
28
+ * @param offset - The number of rows to skip.
29
+ * @param limit - The maximum number of pages to return.
30
+ * @returns An object containing `pages`, `redirects`, `anchors`, and
31
+ * `referrers` arrays.
32
+ */
33
+ export declare function getPagesWithRels(knex: Knex, offset: number, limit: number): Promise<{
34
+ pages: DB_Page[];
35
+ redirects: DB_Redirect[];
36
+ anchors: DB_Anchor[];
37
+ referrers: DB_Referrer[];
38
+ }>;
@@ -0,0 +1,107 @@
1
+ import { dbLog } from '../../../debug.js';
2
+ import { limitedPageIds } from '../../../limited-page-ids.js';
3
+ import { redirectTable } from '../../../redirect-table.js';
4
+ import { setUrlOrder } from '../order/set-url-order.js';
5
+ import { buildPageQuery } from './build-page-query.js';
6
+ import { reconstructPageRows } from './reconstruct-page-rows.js';
7
+ /**
8
+ * Retrieves pages along with their related redirect, anchor, and referrer
9
+ * data. Results are ordered by the natural URL sort order (materialised
10
+ * through {@link setUrlOrder} into `content_items.crawl_order` before the
11
+ * join queries run). Only non-redirected pages are returned in the
12
+ * top-level `pages` array; redirect sources are surfaced only through the
13
+ * `redirects` array so downstream reports (Sheets export) can attribute
14
+ * every source without double-counting the destination.
15
+ *
16
+ * **Redirect resolution stays a read-time join.** `anchor_edges.href_page_id`
17
+ * (like the legacy `anchors.hrefId` it replaces) is resolved by URL only at
18
+ * write time (`resolveContentItemId`), with no redirect-chain following —
19
+ * an anchor's target may itself become a redirect source discovered later
20
+ * in the crawl. `redirectTable()` joins the single `redirect_dest_id` hop
21
+ * at read time so `anchors[].url` reflects the final destination while
22
+ * `anchors[].href` keeps the immediate (pre-redirect) target URL; one hop
23
+ * suffices only because `linkRedirectSources` pre-flattens every hop's
24
+ * `redirect_dest_id` to the final destination at write time.
25
+ *
26
+ * **Deduped anchor identity.** `anchor_edges` collapses repeated
27
+ * `(page_id, href_page_id)` instances into one row with a `count`; `hash` /
28
+ * `textContent` below come from `first_hash` / `first_text_id` (the first
29
+ * observed instance), not every instance — an intentional behavior change
30
+ * from the legacy `anchors` table's one-row-per-instance model.
31
+ * @param knex - Knex query builder connected to the archive DB.
32
+ * @param offset - The number of rows to skip.
33
+ * @param limit - The maximum number of pages to return.
34
+ * @returns An object containing `pages`, `redirects`, `anchors`, and
35
+ * `referrers` arrays.
36
+ */
37
+ export async function getPagesWithRels(knex, offset, limit) {
38
+ await setUrlOrder(knex);
39
+ dbLog('Get Pages');
40
+ const rawRows = await buildPageQuery(knex)
41
+ .whereNull('ci.redirect_dest_id')
42
+ .orderByRaw('`ci`.`crawl_order` ASC NULLS LAST')
43
+ .limit(limit)
44
+ .offset(offset);
45
+ const pages = await reconstructPageRows(knex, rawRows);
46
+ // When empty
47
+ if (pages.length === 0) {
48
+ return {
49
+ pages: [],
50
+ redirects: [],
51
+ referrers: [],
52
+ anchors: [],
53
+ };
54
+ }
55
+ dbLog('Get Pages: Redirects');
56
+ const redirects = await knex
57
+ .with('limitedPages', limitedPageIds(limit, offset))
58
+ .with('redirect', redirectTable(false))
59
+ .select('id as pageId', 'from', 'fromId')
60
+ .from('redirect')
61
+ // Filter
62
+ .join('limitedPages', 'redirect.toId', '=', 'limitedPages.id')
63
+ // Sort
64
+ .orderBy('id', 'asc');
65
+ dbLog('Get Pages: Anchors');
66
+ const anchors = await knex
67
+ .with('limitedPages', limitedPageIds(limit, offset))
68
+ .with('redirect', redirectTable())
69
+ .select('limitedPages.id as pageId', 'href_url.url as url', 'redirect.from as href', 'href.is_external as isExternal', 'href_title.text as title', 'href.status as status', 'href.status_text as statusText', 'href_ctr.raw as contentType', 'anchor_edges.first_hash as hash', 'anchor_text.text as textContent')
70
+ .from('anchor_edges')
71
+ // Filters
72
+ .join('limitedPages', 'anchor_edges.page_id', '=', 'limitedPages.id')
73
+ // Resolves redirect
74
+ .join('redirect', 'anchor_edges.href_page_id', '=', 'redirect.fromId')
75
+ // Target
76
+ .join('content_items as href', 'redirect.toId', '=', 'href.id')
77
+ .join('url_refs as href_url', 'href.url_id', '=', 'href_url.id')
78
+ .leftJoin('content_type_refs as href_ctr', 'href.content_type_id', '=', 'href_ctr.id')
79
+ .leftJoin('page_meta as href_pm', 'href.id', '=', 'href_pm.page_id')
80
+ .leftJoin('text_refs as href_title', 'href_pm.title_text_id', '=', 'href_title.id')
81
+ .leftJoin('text_refs as anchor_text', 'anchor_edges.first_text_id', '=', 'anchor_text.id')
82
+ // Sort
83
+ .orderBy('anchor_edges.id', 'asc');
84
+ dbLog('Get Pages: Referrers');
85
+ const referrers = await knex
86
+ .with('limitedPages', limitedPageIds(limit, offset))
87
+ .with('redirect', redirectTable())
88
+ .select('redirect.toId as pageId', 'referrer_url.url as url', 'redirect.from as through', 'redirect.fromId as throughId', 'anchor_edges.first_hash as hash', 'anchor_text.text as textContent')
89
+ .from('anchor_edges')
90
+ // Resolves redirect
91
+ .join('redirect', 'anchor_edges.href_page_id', '=', 'redirect.fromId')
92
+ // Referrer
93
+ .join('content_items as referrer', 'anchor_edges.page_id', '=', 'referrer.id')
94
+ .join('url_refs as referrer_url', 'referrer.url_id', '=', 'referrer_url.id')
95
+ // Filters
96
+ .join('limitedPages', 'redirect.toId', '=', 'limitedPages.id')
97
+ .leftJoin('text_refs as anchor_text', 'anchor_edges.first_text_id', '=', 'anchor_text.id')
98
+ // Sort
99
+ .orderBy('anchor_edges.id', 'asc');
100
+ dbLog('Get Pages: Done');
101
+ return {
102
+ pages,
103
+ redirects,
104
+ anchors,
105
+ referrers,
106
+ };
107
+ }