@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,33 @@
1
+ import { MigrationVerificationError } from './types.js';
2
+ /**
3
+ * Verifies 0.13 invariant #6: every legacy `resources` row is mirrored
4
+ * by one row in `resource_items`.
5
+ *
6
+ * `populateResourceItems` fills `resource_items` with the same PK as
7
+ * `resources.id`
8
+ * (see `populate-resource-items.ts`); the invariant is broken only if the
9
+ * populate loop skipped rows during URL / header-set / content-type
10
+ * resolution. The paired invariant `resource_ref_edges = "resources-referrers"`
11
+ * is left unchecked here because issue #194's spec list stops at the
12
+ * six enumerated checks, and `resource_ref_edges` populate is a straight
13
+ * `INSERT … SELECT` (no dedup, no resolution) with negligible failure surface.
14
+ * @param trx - Knex instance or transaction connected to the populated archive.
15
+ * @throws {MigrationVerificationError} when the row counts diverge.
16
+ */
17
+ export async function checkResourceItemsCount(trx) {
18
+ const resourceItemsRows = await trx('resource_items').count({
19
+ n: '*',
20
+ });
21
+ const resourcesRows = await trx('resources').count({ n: '*' });
22
+ const resourceItemsCount = Number(resourceItemsRows[0].n);
23
+ const resourcesCount = Number(resourcesRows[0].n);
24
+ if (resourceItemsCount !== resourcesCount) {
25
+ throw new MigrationVerificationError({
26
+ check: '#6 resource_items row count',
27
+ context: {
28
+ resource_items: resourceItemsCount,
29
+ resources: resourcesCount,
30
+ },
31
+ });
32
+ }
33
+ }
@@ -0,0 +1,43 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Verifies 0.13 invariant #8: `content_items.url_id → url_refs.url`
4
+ * round-trips back to the original `pages.url` on a deterministic
5
+ * ≤ {@link SAMPLE_SIZE} sample.
6
+ *
7
+ * A mismatch means one of two things:
8
+ *
9
+ * - `populate-url-refs.ts` collapsed two distinct URLs into the same
10
+ * `url_refs.id` (BINARY collation bug, whitespace normalisation, …).
11
+ * - `populate-content-items.ts` associated a page id with the wrong
12
+ * `url_ref.id` (row-order dependency in the batch resolver).
13
+ *
14
+ * Additionally an FK gap (`content_items.url_id` pointing at a
15
+ * non-existent `url_refs.id`) or a missing `pages` row surfaces as a
16
+ * `roundTripUrl` / `sourceUrl` of `null`; the check treats either as a
17
+ * round-trip failure. LEFT JOINs are used deliberately so orphan rows
18
+ * are observable — an INNER JOIN would silently drop them and let the
19
+ * bug the invariant is meant to catch slip through.
20
+ *
21
+ * Sampling is **deterministic**: stride = ⌈count(content_items) / N⌉
22
+ * gives approximately {@link SAMPLE_SIZE} rows spread across the id
23
+ * range for large archives, and the full table for archives with
24
+ * ≤ {@link SAMPLE_SIZE} rows. Deterministic sampling means the same
25
+ * archive always produces the same verdict; a random sample would let a
26
+ * failing archive pass on retry if the offending rows happen to fall
27
+ * outside the second draw.
28
+ *
29
+ * The check throws when the sampled row count is smaller than expected
30
+ * (LEFT JOINs may return `null` sides but never fewer rows than the
31
+ * driving `content_items` set — a shortfall means SQLite silently
32
+ * dropped rows we intended to inspect). "Expected" is a separate
33
+ * `COUNT(*)` against the same `(id % stride) = 0` predicate, not a
34
+ * `floor(total / stride)` estimate: `content_items.id` is copied
35
+ * verbatim from the legacy `pages.id`, which accumulates gaps over an
36
+ * archive's crawl history (redirect consolidation, deleted pages, …).
37
+ * A density estimate over-counts whenever the id range is sparser than
38
+ * `[1, total]`, which produced false-positive failures on real
39
+ * multi-thousand-page archives.
40
+ * @param trx - Knex instance or transaction connected to the populated archive.
41
+ * @throws {MigrationVerificationError} when at least one sampled URL does not round-trip.
42
+ */
43
+ export declare function checkUrlRoundTrip(trx: Knex): Promise<void>;
@@ -0,0 +1,112 @@
1
+ import { MigrationVerificationError } from './types.js';
2
+ /**
3
+ * Sample size for the URL round-trip smoke test — a 1,000-row sample per
4
+ * issue #194.
5
+ * Not statistical coverage — URL normalisation bugs tend to be systematic
6
+ * (all rows with a particular scheme or host), so a modest evenly-spread
7
+ * sample catches them reliably.
8
+ */
9
+ const SAMPLE_SIZE = 1000;
10
+ /**
11
+ * Verifies 0.13 invariant #8: `content_items.url_id → url_refs.url`
12
+ * round-trips back to the original `pages.url` on a deterministic
13
+ * ≤ {@link SAMPLE_SIZE} sample.
14
+ *
15
+ * A mismatch means one of two things:
16
+ *
17
+ * - `populate-url-refs.ts` collapsed two distinct URLs into the same
18
+ * `url_refs.id` (BINARY collation bug, whitespace normalisation, …).
19
+ * - `populate-content-items.ts` associated a page id with the wrong
20
+ * `url_ref.id` (row-order dependency in the batch resolver).
21
+ *
22
+ * Additionally an FK gap (`content_items.url_id` pointing at a
23
+ * non-existent `url_refs.id`) or a missing `pages` row surfaces as a
24
+ * `roundTripUrl` / `sourceUrl` of `null`; the check treats either as a
25
+ * round-trip failure. LEFT JOINs are used deliberately so orphan rows
26
+ * are observable — an INNER JOIN would silently drop them and let the
27
+ * bug the invariant is meant to catch slip through.
28
+ *
29
+ * Sampling is **deterministic**: stride = ⌈count(content_items) / N⌉
30
+ * gives approximately {@link SAMPLE_SIZE} rows spread across the id
31
+ * range for large archives, and the full table for archives with
32
+ * ≤ {@link SAMPLE_SIZE} rows. Deterministic sampling means the same
33
+ * archive always produces the same verdict; a random sample would let a
34
+ * failing archive pass on retry if the offending rows happen to fall
35
+ * outside the second draw.
36
+ *
37
+ * The check throws when the sampled row count is smaller than expected
38
+ * (LEFT JOINs may return `null` sides but never fewer rows than the
39
+ * driving `content_items` set — a shortfall means SQLite silently
40
+ * dropped rows we intended to inspect). "Expected" is a separate
41
+ * `COUNT(*)` against the same `(id % stride) = 0` predicate, not a
42
+ * `floor(total / stride)` estimate: `content_items.id` is copied
43
+ * verbatim from the legacy `pages.id`, which accumulates gaps over an
44
+ * archive's crawl history (redirect consolidation, deleted pages, …).
45
+ * A density estimate over-counts whenever the id range is sparser than
46
+ * `[1, total]`, which produced false-positive failures on real
47
+ * multi-thousand-page archives.
48
+ * @param trx - Knex instance or transaction connected to the populated archive.
49
+ * @throws {MigrationVerificationError} when at least one sampled URL does not round-trip.
50
+ */
51
+ export async function checkUrlRoundTrip(trx) {
52
+ const totalRows = await trx('content_items').count({ n: '*' });
53
+ const total = Number(totalRows[0].n);
54
+ if (total === 0) {
55
+ return;
56
+ }
57
+ // stride = ⌈total / SAMPLE_SIZE⌉ so `id % stride = 0` picks approximately
58
+ // SAMPLE_SIZE rows evenly across the id range for large archives, and the
59
+ // full table for archives ≤ SAMPLE_SIZE.
60
+ const stride = Math.max(1, Math.ceil(total / SAMPLE_SIZE));
61
+ const matchingRows = await trx('content_items')
62
+ .whereRaw('(id % ?) = 0', [stride])
63
+ .count({ n: '*' });
64
+ const expectedSize = Math.min(SAMPLE_SIZE, Number(matchingRows[0].n));
65
+ const sample = await trx('content_items as ci')
66
+ .leftJoin('pages as p', 'p.id', 'ci.id')
67
+ .leftJoin('url_refs as ur', 'ur.id', 'ci.url_id')
68
+ .select('ci.id as id', 'p.url as sourceUrl', 'ur.url as roundTripUrl')
69
+ .whereRaw('(ci.id % ?) = 0', [stride])
70
+ .orderBy('ci.id')
71
+ .limit(SAMPLE_SIZE);
72
+ if (sample.length < expectedSize) {
73
+ // LEFT JOINs preserve driving-table row counts (`p.id` / `url_refs.id`
74
+ // are PKs → 1:1 or 0:1 match). A shortfall means SQLite silently
75
+ // dropped rows the WHERE clause matched — a real integrity signal.
76
+ throw new MigrationVerificationError({
77
+ check: '#8 URL round-trip',
78
+ context: {
79
+ content_items_total: total,
80
+ stride,
81
+ expected_sample_size: expectedSize,
82
+ actual_sample_size: sample.length,
83
+ reason: 'sample query returned fewer rows than expected',
84
+ },
85
+ });
86
+ }
87
+ for (const row of sample) {
88
+ if (row.sourceUrl === null || row.roundTripUrl === null) {
89
+ throw new MigrationVerificationError({
90
+ check: '#8 URL round-trip',
91
+ context: {
92
+ page_id: row.id,
93
+ source_url: row.sourceUrl,
94
+ round_trip_url: row.roundTripUrl,
95
+ sample_size: sample.length,
96
+ reason: 'orphan row — pages or url_refs join returned null (FK gap)',
97
+ },
98
+ });
99
+ }
100
+ if (row.sourceUrl !== row.roundTripUrl) {
101
+ throw new MigrationVerificationError({
102
+ check: '#8 URL round-trip',
103
+ context: {
104
+ page_id: row.id,
105
+ source_url: row.sourceUrl,
106
+ round_trip_url: row.roundTripUrl,
107
+ sample_size: sample.length,
108
+ },
109
+ });
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Shared type definitions for 0.13 migration verification.
3
+ *
4
+ * These are the acceptance-invariant checks that `scripts/migrate-to-0.13.mjs`
5
+ * runs at the end of the migration to confirm that the 0.13 entity /
6
+ * edge populates produced a consistent archive. Each check is one file
7
+ * under `verify-migration/` and throws {@link MigrationVerificationError} on mismatch;
8
+ * the orchestrator {@link ./verify-migration.ts} chains them in the
9
+ * order defined by issue #194.
10
+ * @module
11
+ */
12
+ /**
13
+ * Row-count snapshot collected by every successful verify run and returned
14
+ * by {@link import('./verify-migration.js').verifyMigration}.
15
+ * The migration script logs these on the successful path so operators can
16
+ * eyeball migration outcomes from stdout without re-opening the archive.
17
+ */
18
+ export interface MigrationVerificationSummary {
19
+ /** `count(*) FROM content_items` — matches `count(pages)`. */
20
+ readonly contentItems: number;
21
+ /** `count(*) FROM page_meta` — matches `count(pages WHERE scraped=1)`. */
22
+ readonly pageMeta: number;
23
+ /** `count(*) FROM anchor_edges` — always ≤ `count(anchors)`. */
24
+ readonly anchorEdges: number;
25
+ /** `SUM(count) FROM anchor_edges` — matches `count(anchors)`. */
26
+ readonly anchorEdgesSum: number;
27
+ /** `count(*) FROM image_items` — matches `count(images)`. */
28
+ readonly imageItems: number;
29
+ /** `count(*) FROM resource_items` — matches `count(resources)`. */
30
+ readonly resourceItems: number;
31
+ }
32
+ /**
33
+ * Structured details of a single migration invariant that failed. Attached to
34
+ * {@link MigrationVerificationError} so operators can see which check tripped
35
+ * without parsing the error message.
36
+ */
37
+ export interface MigrationVerificationErrorDetails {
38
+ /**
39
+ * Human-readable check identifier matching issue #194's numbered list
40
+ * (e.g. `'#3'` for the anchor-edge dedup range check). Included in the
41
+ * error message so migration logs point at the exact clause.
42
+ */
43
+ readonly check: string;
44
+ /**
45
+ * Optional structured payload — typically `expected` / `actual` counts or
46
+ * the offending page id from the URL round-trip sample. Rendered into the
47
+ * error message so operators can diagnose without re-running the check.
48
+ */
49
+ readonly context?: Readonly<Record<string, string | number | null>>;
50
+ }
51
+ /**
52
+ * Error thrown by any 0.13 check when an invariant does not hold. The
53
+ * migration script catches this at the top level and aborts with a non-zero
54
+ * exit code; the enclosing transaction rolls back so the archive returns to
55
+ * its pre-populate state (ref tables from ref populate stay populated but are
56
+ * additive).
57
+ *
58
+ * Prefer a single error class with a structured {@link details} payload over
59
+ * one subclass per check: eight subclasses would create eight nearly
60
+ * identical constructors, and callers (script + integration test) only need
61
+ * to distinguish "verification failed" from other error kinds.
62
+ */
63
+ export declare class MigrationVerificationError extends Error {
64
+ readonly details: MigrationVerificationErrorDetails;
65
+ /**
66
+ * @param details - Structured description of which check failed and why.
67
+ * Rendered into the error message on construction.
68
+ */
69
+ constructor(details: MigrationVerificationErrorDetails);
70
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared type definitions for 0.13 migration verification.
3
+ *
4
+ * These are the acceptance-invariant checks that `scripts/migrate-to-0.13.mjs`
5
+ * runs at the end of the migration to confirm that the 0.13 entity /
6
+ * edge populates produced a consistent archive. Each check is one file
7
+ * under `verify-migration/` and throws {@link MigrationVerificationError} on mismatch;
8
+ * the orchestrator {@link ./verify-migration.ts} chains them in the
9
+ * order defined by issue #194.
10
+ * @module
11
+ */
12
+ /**
13
+ * Error thrown by any 0.13 check when an invariant does not hold. The
14
+ * migration script catches this at the top level and aborts with a non-zero
15
+ * exit code; the enclosing transaction rolls back so the archive returns to
16
+ * its pre-populate state (ref tables from ref populate stay populated but are
17
+ * additive).
18
+ *
19
+ * Prefer a single error class with a structured {@link details} payload over
20
+ * one subclass per check: eight subclasses would create eight nearly
21
+ * identical constructors, and callers (script + integration test) only need
22
+ * to distinguish "verification failed" from other error kinds.
23
+ */
24
+ export class MigrationVerificationError extends Error {
25
+ details;
26
+ /**
27
+ * @param details - Structured description of which check failed and why.
28
+ * Rendered into the error message on construction.
29
+ */
30
+ constructor(details) {
31
+ const contextText = details.context === undefined
32
+ ? ''
33
+ : ' — ' +
34
+ Object.entries(details.context)
35
+ .map(([key, value]) =>
36
+ // Render null as the parenthesised literal `(null)` so
37
+ // operators reading the log can distinguish "the sample
38
+ // query returned no rows" from a caller that passed the
39
+ // four-character string `"null"`. Strings get quoted for
40
+ // the same reason.
41
+ `${key}=${formatContextValue(value)}`)
42
+ .join(', ');
43
+ super(`migration verification failed ${details.check}${contextText}`);
44
+ this.details = details;
45
+ this.name = 'MigrationVerificationError';
46
+ }
47
+ }
48
+ /**
49
+ * Renders one context value for {@link MigrationVerificationError}'s message.
50
+ * `null` becomes the literal `(null)`; strings are wrapped in double quotes;
51
+ * numbers pass through as their decimal representation. The intent is to
52
+ * make missing-vs-present distinguishable in operator logs.
53
+ * @param value - Value from `MigrationVerificationErrorDetails.context`.
54
+ */
55
+ function formatContextValue(value) {
56
+ if (value === null) {
57
+ return '(null)';
58
+ }
59
+ if (typeof value === 'string') {
60
+ return JSON.stringify(value);
61
+ }
62
+ return String(value);
63
+ }
@@ -0,0 +1,41 @@
1
+ import type { MigrationVerificationSummary } from './types.js';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Runs every 0.13 migration acceptance invariant against a populated archive and
5
+ * returns the verified row-count summary on success. The migration script
6
+ * (`scripts/migrate-to-0.13.mjs`) calls this **inside** the same
7
+ * `knex.transaction()` block that ran `populateEntityTables` so a thrown
8
+ * {@link MigrationVerificationError} rolls back the entire entity populate — ref
9
+ * tables from ref populate stay committed because they live in a separate, additive
10
+ * transaction and never lose facts on re-run.
11
+ *
12
+ * The returned {@link MigrationVerificationSummary} is echoed to stdout by the
13
+ * migration script so operators can eyeball migration outcomes (`did we lose
14
+ * rows silently within the invariant window?`) from batch pipeline logs
15
+ * without re-opening the archive. Callers must run this inside a `db.transaction()`
16
+ * for the counts and the JOIN-based checks (#7 / #8) to see a consistent
17
+ * snapshot of the archive.
18
+ *
19
+ * Checks run sequentially in issue #194's numbered order so error messages
20
+ * pinpoint the earliest broken invariant. Sequential (rather than parallel)
21
+ * execution keeps the failure diagnostics focused: fanning eight COUNTs out
22
+ * at once would return the first-completed rejection rather than the
23
+ * lowest-numbered one, which surprises operators reading migration logs.
24
+ * The individual COUNT queries are fast enough at any real archive scale
25
+ * for sequential wall-clock cost to be irrelevant.
26
+ *
27
+ * Non-{@link MigrationVerificationError} exceptions from a check (e.g. a
28
+ * transient libsql error or a schema-drift SqliteError) are wrapped in a
29
+ * {@link MigrationVerificationError} so the migration script's `catch` sees a
30
+ * uniform failure surface — otherwise operators grepping stderr for
31
+ * `migration verification failed` would miss driver-side errors.
32
+ * @param trx - Knex instance or transaction connected to the archive
33
+ * **after** every 0.13 migration populate has run. Must be a transaction
34
+ * in production so ref counts, JOIN samples, and error rollback see the
35
+ * same snapshot; non-transactional callers (unit tests) work but do not
36
+ * get snapshot isolation.
37
+ * @returns Row-count summary of the verified archive.
38
+ * @throws {MigrationVerificationError} on the first invariant that does not
39
+ * hold; subsequent checks are skipped.
40
+ */
41
+ export declare function verifyMigration(trx: Knex): Promise<MigrationVerificationSummary>;
@@ -0,0 +1,120 @@
1
+ import { checkAnchorEdgesCount } from './check-anchor-edges-count.js';
2
+ import { checkAnchorEdgesSum } from './check-anchor-edges-sum.js';
3
+ import { checkContentItemsCount } from './check-content-items-count.js';
4
+ import { checkContentTypePreservation } from './check-content-type-preservation.js';
5
+ import { checkImageItemsCount } from './check-image-items-count.js';
6
+ import { checkPageMetaCount } from './check-page-meta-count.js';
7
+ import { checkReaderParity } from './check-reader-parity.js';
8
+ import { checkResourceItemsCount } from './check-resource-items-count.js';
9
+ import { checkUrlRoundTrip } from './check-url-round-trip.js';
10
+ import { MigrationVerificationError } from './types.js';
11
+ /**
12
+ * Runs every 0.13 migration acceptance invariant against a populated archive and
13
+ * returns the verified row-count summary on success. The migration script
14
+ * (`scripts/migrate-to-0.13.mjs`) calls this **inside** the same
15
+ * `knex.transaction()` block that ran `populateEntityTables` so a thrown
16
+ * {@link MigrationVerificationError} rolls back the entire entity populate — ref
17
+ * tables from ref populate stay committed because they live in a separate, additive
18
+ * transaction and never lose facts on re-run.
19
+ *
20
+ * The returned {@link MigrationVerificationSummary} is echoed to stdout by the
21
+ * migration script so operators can eyeball migration outcomes (`did we lose
22
+ * rows silently within the invariant window?`) from batch pipeline logs
23
+ * without re-opening the archive. Callers must run this inside a `db.transaction()`
24
+ * for the counts and the JOIN-based checks (#7 / #8) to see a consistent
25
+ * snapshot of the archive.
26
+ *
27
+ * Checks run sequentially in issue #194's numbered order so error messages
28
+ * pinpoint the earliest broken invariant. Sequential (rather than parallel)
29
+ * execution keeps the failure diagnostics focused: fanning eight COUNTs out
30
+ * at once would return the first-completed rejection rather than the
31
+ * lowest-numbered one, which surprises operators reading migration logs.
32
+ * The individual COUNT queries are fast enough at any real archive scale
33
+ * for sequential wall-clock cost to be irrelevant.
34
+ *
35
+ * Non-{@link MigrationVerificationError} exceptions from a check (e.g. a
36
+ * transient libsql error or a schema-drift SqliteError) are wrapped in a
37
+ * {@link MigrationVerificationError} so the migration script's `catch` sees a
38
+ * uniform failure surface — otherwise operators grepping stderr for
39
+ * `migration verification failed` would miss driver-side errors.
40
+ * @param trx - Knex instance or transaction connected to the archive
41
+ * **after** every 0.13 migration populate has run. Must be a transaction
42
+ * in production so ref counts, JOIN samples, and error rollback see the
43
+ * same snapshot; non-transactional callers (unit tests) work but do not
44
+ * get snapshot isolation.
45
+ * @returns Row-count summary of the verified archive.
46
+ * @throws {MigrationVerificationError} on the first invariant that does not
47
+ * hold; subsequent checks are skipped.
48
+ */
49
+ export async function verifyMigration(trx) {
50
+ try {
51
+ await checkContentItemsCount(trx);
52
+ await checkPageMetaCount(trx);
53
+ await checkAnchorEdgesCount(trx);
54
+ await checkAnchorEdgesSum(trx);
55
+ await checkImageItemsCount(trx);
56
+ await checkResourceItemsCount(trx);
57
+ await checkContentTypePreservation(trx);
58
+ await checkUrlRoundTrip(trx);
59
+ // Reader-level parity between the legacy tables and the
60
+ // new entity tables (see `checkReaderParity` for the eight totals
61
+ // this catches that the row-count checks above miss).
62
+ await checkReaderParity(trx);
63
+ }
64
+ catch (error) {
65
+ if (error instanceof MigrationVerificationError) {
66
+ throw error;
67
+ }
68
+ throw new MigrationVerificationError({
69
+ check: 'runtime',
70
+ context: {
71
+ underlying_error: error instanceof Error ? error.message : String(error),
72
+ },
73
+ });
74
+ }
75
+ return collectSummary(trx);
76
+ }
77
+ /**
78
+ * Runs the six row counts that populate {@link MigrationVerificationSummary}.
79
+ * Separate from the invariant checks so those stay side-effect-free asserts,
80
+ * and this runs once at the end when all invariants have already held.
81
+ * @param trx - Same Knex handle passed to {@link verifyMigration}.
82
+ */
83
+ async function collectSummary(trx) {
84
+ const [contentItems, pageMeta, anchorEdges, anchorEdgesSum, imageItems, resourceItems] = await Promise.all([
85
+ countOf(trx, 'content_items'),
86
+ countOf(trx, 'page_meta'),
87
+ countOf(trx, 'anchor_edges'),
88
+ sumOf(trx, 'anchor_edges', 'count'),
89
+ countOf(trx, 'image_items'),
90
+ countOf(trx, 'resource_items'),
91
+ ]);
92
+ return {
93
+ contentItems,
94
+ pageMeta,
95
+ anchorEdges,
96
+ anchorEdgesSum,
97
+ imageItems,
98
+ resourceItems,
99
+ };
100
+ }
101
+ /**
102
+ * `SELECT count(*) FROM <table>` returning a plain number.
103
+ * @param trx - Knex handle.
104
+ * @param table - Table to count.
105
+ */
106
+ async function countOf(trx, table) {
107
+ const rows = await trx(table).count({ n: '*' });
108
+ return Number(rows[0].n);
109
+ }
110
+ /**
111
+ * `SELECT SUM(<column>) FROM <table>` returning a plain number (`null` sum
112
+ * on an empty table collapses to zero).
113
+ * @param trx - Knex handle.
114
+ * @param table - Table to sum over.
115
+ * @param column - Column to sum.
116
+ */
117
+ async function sumOf(trx, table, column) {
118
+ const rows = await trx(table).sum({ n: column });
119
+ return Number(rows[0].n ?? 0);
120
+ }
@@ -0,0 +1,19 @@
1
+ import type { ErrorKind } from './types.js';
2
+ /**
3
+ * Classify a raw crawler/scraper error message into a coarse {@link ErrorKind}.
4
+ *
5
+ * Pure and deterministic: the same message always yields the same kind, which
6
+ * is why the kind is derived on read rather than persisted — it can be applied
7
+ * uniformly to freshly captured `crawl_errors`, legacy `error.log` lines, and
8
+ * `page_errors` alike.
9
+ * @param message - The raw error message (a single line is sufficient; the
10
+ * cause token such as `ENOTFOUND` or `Navigation timeout` lives there).
11
+ * @returns The matched kind, or `unknown` when no matcher applies.
12
+ * @example
13
+ * ```ts
14
+ * classifyErrorKind('getaddrinfo ENOTFOUND www.example.com'); // 'dns'
15
+ * classifyErrorKind('gave up after 3 retries — Race 180,000ms'); // 'timeout'
16
+ * classifyErrorKind('Protocol error (Page.reload): Target closed'); // 'protocol'
17
+ * ```
18
+ */
19
+ export declare function classifyErrorKind(message: string): ErrorKind;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Ordered message matchers. The first pattern that matches wins, so more
3
+ * specific transport causes (DNS, TLS, connection-*) are tested before the
4
+ * broader `protocol` / `timeout` buckets — e.g. `ETIMEDOUT` must classify as
5
+ * `connection-timeout`, not the page-level `timeout`, and a puppeteer
6
+ * `Protocol error` must not be swallowed by the `timeout` matcher.
7
+ */
8
+ const MATCHERS = [
9
+ // `dns-transient` must be evaluated before `dns`: an `EAI_AGAIN` line also
10
+ // carries the `getaddrinfo` token, so the more specific transient pattern
11
+ // has to win. Splitting it out from `dns` keeps the DNS-burned host cache
12
+ // (which marks on `kind === 'dns'`) from punishing a host whose only sin
13
+ // was a local resolver hiccup.
14
+ { kind: 'dns-transient', pattern: /EAI_AGAIN|\bEREFUSED\b/i },
15
+ {
16
+ kind: 'dns',
17
+ pattern: /ENOTFOUND|getaddrinfo|ERR_NAME_NOT_RESOLVED|ERR_NAME_RESOLUTION_FAILED/i,
18
+ },
19
+ {
20
+ kind: 'tls',
21
+ // `Hostname/IP does not match certificate's altnames` is Node's
22
+ // node:tls hostname mismatch error and is emphatically a TLS issue;
23
+ // adding it here (alongside the OpenSSL / Chromium tokens) keeps
24
+ // hosts that serve the wrong-name cert (common with misconfigured
25
+ // edge / load-balancer setups) out of `unknown`. `altnames` is
26
+ // anchored to the preceding `certificate` token so a request whose
27
+ // error message merely mentions a path containing the substring
28
+ // `altnames` (e.g. `https://api.example.com/altnames/lookup` in a
29
+ // 5xx body) does NOT get misclassified into `tls` (which is a
30
+ // `PERMANENT_ERROR_KINDS` member — a false-positive would
31
+ // permanently exclude that page from `--retry-failed`).
32
+ pattern: /ERR_CERT|ERR_SSL|\bCERT_|SSL routines|ERR_BAD_SSL|UNABLE_TO_VERIFY|unable to verify|self.signed certificate|certificate has expired\s*$|\bERR_TLS|Hostname\/IP does not match certificate|certificate'?s? altnames/i,
33
+ },
34
+ { kind: 'connection-refused', pattern: /ECONNREFUSED|ERR_CONNECTION_REFUSED/i },
35
+ {
36
+ kind: 'connection-reset',
37
+ pattern: /ECONNRESET|socket hang up|ERR_CONNECTION_RESET|ERR_CONNECTION_CLOSED|ERR_EMPTY_RESPONSE/i,
38
+ },
39
+ {
40
+ kind: 'connection-timeout',
41
+ pattern: /ETIMEDOUT|ERR_CONNECTION_TIMED_OUT|ERR_TIMED_OUT/i,
42
+ },
43
+ // `local-network` is evaluated AFTER the connection-* matchers so a
44
+ // concrete cause (refused / reset / timeout) wins when both apply. Only
45
+ // "local network is unreachable / changed" symptoms — and the OS-level
46
+ // errors that surface them — land here. Short tokens (`EPIPE`, `EREFUSED`)
47
+ // are word-bounded so unrelated identifiers don't false-positive.
48
+ {
49
+ kind: 'local-network',
50
+ pattern: /ERR_INTERNET_DISCONNECTED|ERR_NETWORK_CHANGED|ERR_NETWORK_IO_SUSPENDED|ERR_ADDRESS_UNREACHABLE|ERR_NETWORK_UNREACHABLE|ENETUNREACH|EHOSTUNREACH|EADDRNOTAVAIL|ENOTCONN|\bEPIPE\b/i,
51
+ },
52
+ {
53
+ kind: 'parse-error',
54
+ pattern: /Parse Error|Expected HTTP\/|Unexpected end of stream/i,
55
+ },
56
+ // `client-blocked` covers Chromium's ERR_BLOCKED_* family — the browser
57
+ // actively decided to reject the request (ad/tracker heuristics, CSP,
58
+ // CORB / ORB, administrator block list, fingerprinting protection,
59
+ // cleartext policy, …). Per the upstream `net/base/net_error_list.h`,
60
+ // `ERR_BLOCKED_BY_CLIENT` is documented as "The client chose to block
61
+ // the request." — i.e. the server was never the deciding party. Listed
62
+ // before `protocol` so puppeteer's generic "Protocol error" wrapper
63
+ // (which sometimes embeds the underlying net error code) is correctly
64
+ // attributed to the blocked layer rather than the protocol layer.
65
+ {
66
+ kind: 'client-blocked',
67
+ pattern: /ERR_BLOCKED_BY_CLIENT|ERR_BLOCKED_BY_ADMINISTRATOR|ERR_BLOCKED_IN_INCOGNITO_BY_ADMINISTRATOR|ERR_BLOCKED_BY_RESPONSE|ERR_BLOCKED_BY_CSP|ERR_BLOCKED_BY_ORB|ERR_BLOCKED_BY_FINGERPRINTING_PROTECTION|ERR_CLEARTEXT_NOT_PERMITTED|ERR_NETWORK_ACCESS_REVOKED/i,
68
+ },
69
+ {
70
+ kind: 'protocol',
71
+ // `detached frame` is anchored to puppeteer's exact prefix
72
+ // `Attempted to use detached Frame` (its current Frame.ts
73
+ // emitter; the `i` flag below catches the lowercase variant
74
+ // automatically), not the bare two-token substring. The bare
75
+ // form would match unrelated diagnostics like a console message
76
+ // "detached frame ref leaked" echoed through a logger. The older
77
+ // Page-domain `frame (?:was |got )?detached` form is kept as a
78
+ // separate alternative because Chromium still surfaces that
79
+ // phrasing in some legacy code paths. Without one of these, the
80
+ // "Attempted to use detached Frame ..." messages observed on a
81
+ // real archive would slip into `unknown`.
82
+ pattern: /Protocol error|Target closed|Session closed|Execution context was destroyed|frame (?:was |got )?detached|Attempted to use detached frame|Navigating frame was detached|Cannot find context|Node with given id|Page\.\w+ returned/i,
83
+ },
84
+ {
85
+ kind: 'timeout',
86
+ // `Timeout: https?:` matches the NetTimeoutError "Timeout: <url>"
87
+ // form. Looking for the URL-shaped tail (rather than anchoring at
88
+ // line start) is what lets us catch the beholder-wrapped variant
89
+ // `[Retried N times] Timeout: https://...` that gets stored in
90
+ // `crawl_errors` / `error.log` after retry exhaustion — the bare
91
+ // `^Timeout:` form would only fire on the immediate failure and
92
+ // miss every retry-exhausted record (the ones that actually land
93
+ // in the archive). Required for slow-server timeouts that
94
+ // previously fell into `unknown`.
95
+ pattern: /Race \d|Navigation timeout|timeout of \d+\s*ms exceeded|TimeoutError|Timed out|Timeout: https?:/i,
96
+ },
97
+ ];
98
+ /**
99
+ * Classify a raw crawler/scraper error message into a coarse {@link ErrorKind}.
100
+ *
101
+ * Pure and deterministic: the same message always yields the same kind, which
102
+ * is why the kind is derived on read rather than persisted — it can be applied
103
+ * uniformly to freshly captured `crawl_errors`, legacy `error.log` lines, and
104
+ * `page_errors` alike.
105
+ * @param message - The raw error message (a single line is sufficient; the
106
+ * cause token such as `ENOTFOUND` or `Navigation timeout` lives there).
107
+ * @returns The matched kind, or `unknown` when no matcher applies.
108
+ * @example
109
+ * ```ts
110
+ * classifyErrorKind('getaddrinfo ENOTFOUND www.example.com'); // 'dns'
111
+ * classifyErrorKind('gave up after 3 retries — Race 180,000ms'); // 'timeout'
112
+ * classifyErrorKind('Protocol error (Page.reload): Target closed'); // 'protocol'
113
+ * ```
114
+ */
115
+ export function classifyErrorKind(message) {
116
+ for (const { kind, pattern } of MATCHERS) {
117
+ if (pattern.test(message)) {
118
+ return kind;
119
+ }
120
+ }
121
+ return 'unknown';
122
+ }