@nitpicker/crawler 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/README.md +6 -4
  2. package/lib/archive/archive-accessor.d.ts +2 -2
  3. package/lib/archive/archive-accessor.js +2 -2
  4. package/lib/archive/archive-lock.d.ts +7 -0
  5. package/lib/archive/archive-lock.js +7 -0
  6. package/lib/archive/archive.d.ts +63 -16
  7. package/lib/archive/archive.js +56 -17
  8. package/lib/archive/create-adjunct-tables.d.ts +43 -0
  9. package/lib/archive/create-adjunct-tables.js +213 -0
  10. package/lib/archive/create-entity-tables.d.ts +173 -0
  11. package/lib/archive/create-entity-tables.js +318 -0
  12. package/lib/archive/create-progress-reporter.d.ts +30 -0
  13. package/lib/archive/create-progress-reporter.js +38 -0
  14. package/lib/archive/create-ref-tables.d.ts +35 -0
  15. package/lib/archive/create-ref-tables.js +188 -0
  16. package/lib/archive/database.d.ts +92 -345
  17. package/lib/archive/database.js +168 -1942
  18. package/lib/archive/db-ops/_shared/clear-write-ref-caches.d.ts +27 -0
  19. package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +34 -0
  20. package/lib/archive/db-ops/_shared/create-write-ref-caches.d.ts +17 -0
  21. package/lib/archive/db-ops/_shared/create-write-ref-caches.js +26 -0
  22. package/lib/archive/db-ops/_shared/decode-json-ref.d.ts +17 -0
  23. package/lib/archive/db-ops/_shared/decode-json-ref.js +31 -0
  24. package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.d.ts +20 -0
  25. package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.js +53 -0
  26. package/lib/archive/db-ops/_shared/resolve-content-item-id.d.ts +61 -0
  27. package/lib/archive/db-ops/_shared/resolve-content-item-id.js +111 -0
  28. package/lib/archive/db-ops/_shared/resolve-url-or-blob.d.ts +23 -0
  29. package/lib/archive/db-ops/_shared/resolve-url-or-blob.js +29 -0
  30. package/lib/archive/db-ops/_shared/retry-setting.d.ts +16 -0
  31. package/lib/archive/db-ops/_shared/retry-setting.js +18 -0
  32. package/lib/archive/db-ops/_shared/safe-parse-json.d.ts +11 -0
  33. package/lib/archive/db-ops/_shared/safe-parse-json.js +18 -0
  34. package/lib/archive/db-ops/_shared/types.d.ts +53 -0
  35. package/lib/archive/db-ops/_shared/types.js +1 -0
  36. package/lib/archive/db-ops/_shared/upsert-blob-ref.d.ts +25 -0
  37. package/lib/archive/db-ops/_shared/upsert-blob-ref.js +48 -0
  38. package/lib/archive/db-ops/_shared/upsert-content-type-ref.d.ts +30 -0
  39. package/lib/archive/db-ops/_shared/upsert-content-type-ref.js +45 -0
  40. package/lib/archive/db-ops/_shared/upsert-json-ref.d.ts +22 -0
  41. package/lib/archive/db-ops/_shared/upsert-json-ref.js +41 -0
  42. package/lib/archive/db-ops/_shared/upsert-response-headers.d.ts +35 -0
  43. package/lib/archive/db-ops/_shared/upsert-response-headers.js +49 -0
  44. package/lib/archive/db-ops/_shared/upsert-url-ref.d.ts +39 -0
  45. package/lib/archive/db-ops/_shared/upsert-url-ref.js +62 -0
  46. package/lib/archive/db-ops/analysis/replace-analysis-violations.d.ts +28 -0
  47. package/lib/archive/db-ops/analysis/replace-analysis-violations.js +152 -0
  48. package/lib/archive/db-ops/anchors/get-anchors-on-page.d.ts +10 -0
  49. package/lib/archive/db-ops/anchors/get-anchors-on-page.js +21 -0
  50. package/lib/archive/db-ops/config/get-base-url.d.ts +8 -0
  51. package/lib/archive/db-ops/config/get-base-url.js +14 -0
  52. package/lib/archive/db-ops/config/get-config.d.ts +10 -0
  53. package/lib/archive/db-ops/config/get-config.js +27 -0
  54. package/lib/archive/db-ops/config/get-name.d.ts +8 -0
  55. package/lib/archive/db-ops/config/get-name.js +14 -0
  56. package/lib/archive/db-ops/config/info-column-allowlist.d.ts +7 -0
  57. package/lib/archive/db-ops/config/info-column-allowlist.js +26 -0
  58. package/lib/archive/db-ops/config/info-json-columns.d.ts +5 -0
  59. package/lib/archive/db-ops/config/info-json-columns.js +10 -0
  60. package/lib/archive/db-ops/config/set-config.d.ts +12 -0
  61. package/lib/archive/db-ops/config/set-config.js +21 -0
  62. package/lib/archive/db-ops/config/update-config.d.ts +17 -0
  63. package/lib/archive/db-ops/config/update-config.js +36 -0
  64. package/lib/archive/db-ops/errors/insert-crawl-error.d.ts +15 -0
  65. package/lib/archive/db-ops/errors/insert-crawl-error.js +21 -0
  66. package/lib/archive/db-ops/errors/insert-page-error.d.ts +21 -0
  67. package/lib/archive/db-ops/errors/insert-page-error.js +28 -0
  68. package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +22 -0
  69. package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +141 -0
  70. package/lib/archive/db-ops/html/get-html-of-page-by-id.d.ts +18 -0
  71. package/lib/archive/db-ops/html/get-html-of-page-by-id.js +29 -0
  72. package/lib/archive/db-ops/inventory/record-inventory-run.d.ts +21 -0
  73. package/lib/archive/db-ops/inventory/record-inventory-run.js +38 -0
  74. package/lib/archive/db-ops/lifecycle/checkpoint.d.ts +8 -0
  75. package/lib/archive/db-ops/lifecycle/checkpoint.js +9 -0
  76. package/lib/archive/db-ops/lifecycle/destroy.d.ts +6 -0
  77. package/lib/archive/db-ops/lifecycle/destroy.js +7 -0
  78. package/lib/archive/db-ops/lifecycle/init.d.ts +22 -0
  79. package/lib/archive/db-ops/lifecycle/init.js +42 -0
  80. package/lib/archive/db-ops/meta/get-jsonld-of-page.d.ts +13 -0
  81. package/lib/archive/db-ops/meta/get-jsonld-of-page.js +27 -0
  82. package/lib/archive/db-ops/meta/get-tags-of-page.d.ts +12 -0
  83. package/lib/archive/db-ops/meta/get-tags-of-page.js +28 -0
  84. package/lib/archive/db-ops/pages/order/set-url-order.d.ts +8 -0
  85. package/lib/archive/db-ops/pages/order/set-url-order.js +32 -0
  86. package/lib/archive/db-ops/pages/read/build-page-query.d.ts +18 -0
  87. package/lib/archive/db-ops/pages/read/build-page-query.js +40 -0
  88. package/lib/archive/db-ops/pages/read/get-crawling-state.d.ts +70 -0
  89. package/lib/archive/db-ops/pages/read/get-crawling-state.js +98 -0
  90. package/lib/archive/db-ops/pages/read/get-existing-page-urls.d.ts +15 -0
  91. package/lib/archive/db-ops/pages/read/get-existing-page-urls.js +30 -0
  92. package/lib/archive/db-ops/pages/read/get-page-count.d.ts +12 -0
  93. package/lib/archive/db-ops/pages/read/get-page-count.js +21 -0
  94. package/lib/archive/db-ops/pages/read/get-page-source-by-url.d.ts +24 -0
  95. package/lib/archive/db-ops/pages/read/get-page-source-by-url.js +28 -0
  96. package/lib/archive/db-ops/pages/read/get-pages-with-rels.d.ts +38 -0
  97. package/lib/archive/db-ops/pages/read/get-pages-with-rels.js +107 -0
  98. package/lib/archive/db-ops/pages/read/get-pages.d.ts +11 -0
  99. package/lib/archive/db-ops/pages/read/get-pages.js +51 -0
  100. package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.d.ts +18 -0
  101. package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.js +25 -0
  102. package/lib/archive/db-ops/pages/read/reconstruct-page-rows.d.ts +31 -0
  103. package/lib/archive/db-ops/pages/read/reconstruct-page-rows.js +32 -0
  104. package/lib/archive/db-ops/pages/reset/repromote-external-pages.d.ts +24 -0
  105. package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +93 -0
  106. package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +47 -0
  107. package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +124 -0
  108. package/lib/archive/db-ops/pages/write/insert-inventory-seeds.d.ts +37 -0
  109. package/lib/archive/db-ops/pages/write/insert-inventory-seeds.js +72 -0
  110. package/lib/archive/db-ops/pages/write/insert-jsonld.d.ts +17 -0
  111. package/lib/archive/db-ops/pages/write/insert-jsonld.js +49 -0
  112. package/lib/archive/db-ops/pages/write/insert-page.d.ts +36 -0
  113. package/lib/archive/db-ops/pages/write/insert-page.js +208 -0
  114. package/lib/archive/db-ops/pages/write/insert-tags.d.ts +16 -0
  115. package/lib/archive/db-ops/pages/write/insert-tags.js +34 -0
  116. package/lib/archive/db-ops/pages/write/link-redirect-sources.d.ts +36 -0
  117. package/lib/archive/db-ops/pages/write/link-redirect-sources.js +93 -0
  118. package/lib/archive/db-ops/pages/write/record-redirect.d.ts +35 -0
  119. package/lib/archive/db-ops/pages/write/record-redirect.js +100 -0
  120. package/lib/archive/db-ops/pages/write/set-skipped-page.d.ts +13 -0
  121. package/lib/archive/db-ops/pages/write/set-skipped-page.js +22 -0
  122. package/lib/archive/db-ops/pages/write/update-page.d.ts +29 -0
  123. package/lib/archive/db-ops/pages/write/update-page.js +334 -0
  124. package/lib/archive/db-ops/pages/write/write-page-html-blob.d.ts +19 -0
  125. package/lib/archive/db-ops/pages/write/write-page-html-blob.js +41 -0
  126. package/lib/archive/db-ops/referrers/get-redirects-for-pages.d.ts +9 -0
  127. package/lib/archive/db-ops/referrers/get-redirects-for-pages.js +15 -0
  128. package/lib/archive/db-ops/referrers/get-referrers-of-page.d.ts +17 -0
  129. package/lib/archive/db-ops/referrers/get-referrers-of-page.js +32 -0
  130. package/lib/archive/db-ops/referrers/get-referrers-of-resource.d.ts +8 -0
  131. package/lib/archive/db-ops/referrers/get-referrers-of-resource.js +15 -0
  132. package/lib/archive/db-ops/resources/build-resource-query.d.ts +25 -0
  133. package/lib/archive/db-ops/resources/build-resource-query.js +29 -0
  134. package/lib/archive/db-ops/resources/get-existing-resource-urls.d.ts +9 -0
  135. package/lib/archive/db-ops/resources/get-existing-resource-urls.js +24 -0
  136. package/lib/archive/db-ops/resources/get-resource-by-url.d.ts +13 -0
  137. package/lib/archive/db-ops/resources/get-resource-by-url.js +22 -0
  138. package/lib/archive/db-ops/resources/get-resource-url-list.d.ts +9 -0
  139. package/lib/archive/db-ops/resources/get-resource-url-list.js +13 -0
  140. package/lib/archive/db-ops/resources/get-resources.d.ts +8 -0
  141. package/lib/archive/db-ops/resources/get-resources.js +11 -0
  142. package/lib/archive/db-ops/resources/insert-inventory-resources.d.ts +24 -0
  143. package/lib/archive/db-ops/resources/insert-inventory-resources.js +64 -0
  144. package/lib/archive/db-ops/resources/insert-resource-referrers.d.ts +15 -0
  145. package/lib/archive/db-ops/resources/insert-resource-referrers.js +54 -0
  146. package/lib/archive/db-ops/resources/insert-resource.d.ts +34 -0
  147. package/lib/archive/db-ops/resources/insert-resource.js +73 -0
  148. package/lib/archive/db-ops/resources/reconstruct-resource-rows.d.ts +26 -0
  149. package/lib/archive/db-ops/resources/reconstruct-resource-rows.js +30 -0
  150. package/lib/archive/decode-html-blob.d.ts +18 -0
  151. package/lib/archive/decode-html-blob.js +31 -0
  152. package/lib/archive/derive-lineage-from-parent.d.ts +1 -1
  153. package/lib/archive/derive-lineage-from-parent.js +1 -1
  154. package/lib/archive/drop-legacy-tables.d.ts +45 -0
  155. package/lib/archive/drop-legacy-tables.js +56 -0
  156. package/lib/archive/filesystem/rename.js +1 -1
  157. package/lib/archive/get-failed-page-messages.d.ts +5 -4
  158. package/lib/archive/get-failed-page-messages.js +5 -4
  159. package/lib/archive/init-schema.d.ts +35 -39
  160. package/lib/archive/init-schema.js +99 -460
  161. package/lib/archive/limited-page-ids.d.ts +2 -1
  162. package/lib/archive/limited-page-ids.js +5 -4
  163. package/lib/archive/meta/assert-compatible-version.d.ts +24 -3
  164. package/lib/archive/meta/assert-compatible-version.js +24 -3
  165. package/lib/archive/meta/types.d.ts +87 -1
  166. package/lib/archive/meta/types.js +34 -2
  167. package/lib/archive/migrate-entity-tables.d.ts +45 -0
  168. package/lib/archive/migrate-entity-tables.js +56 -0
  169. package/lib/archive/migrate-ref-tables.d.ts +25 -0
  170. package/lib/archive/migrate-ref-tables.js +38 -0
  171. package/lib/archive/page-meta-column-maps.d.ts +32 -0
  172. package/lib/archive/page-meta-column-maps.js +43 -0
  173. package/lib/archive/page.d.ts +6 -6
  174. package/lib/archive/page.js +5 -5
  175. package/lib/archive/peek-archive-lock.d.ts +2 -2
  176. package/lib/archive/peek-archive-lock.js +2 -2
  177. package/lib/archive/populate-entity-tables/collapse-anchor-rows.d.ts +41 -0
  178. package/lib/archive/populate-entity-tables/collapse-anchor-rows.js +87 -0
  179. package/lib/archive/populate-entity-tables/derive-dom-path.d.ts +35 -0
  180. package/lib/archive/populate-entity-tables/derive-dom-path.js +72 -0
  181. package/lib/archive/populate-entity-tables/is-blob-ref-value.d.ts +16 -0
  182. package/lib/archive/populate-entity-tables/is-blob-ref-value.js +19 -0
  183. package/lib/archive/populate-entity-tables/match-images-to-dom-paths.d.ts +66 -0
  184. package/lib/archive/populate-entity-tables/match-images-to-dom-paths.js +96 -0
  185. package/lib/archive/populate-entity-tables/populate-anchor-edges.d.ts +33 -0
  186. package/lib/archive/populate-entity-tables/populate-anchor-edges.js +153 -0
  187. package/lib/archive/populate-entity-tables/populate-content-items.d.ts +40 -0
  188. package/lib/archive/populate-entity-tables/populate-content-items.js +141 -0
  189. package/lib/archive/populate-entity-tables/populate-entities.d.ts +81 -0
  190. package/lib/archive/populate-entity-tables/populate-entities.js +111 -0
  191. package/lib/archive/populate-entity-tables/populate-image-items.d.ts +91 -0
  192. package/lib/archive/populate-entity-tables/populate-image-items.js +223 -0
  193. package/lib/archive/populate-entity-tables/populate-page-meta.d.ts +33 -0
  194. package/lib/archive/populate-entity-tables/populate-page-meta.js +267 -0
  195. package/lib/archive/populate-entity-tables/populate-resource-items.d.ts +22 -0
  196. package/lib/archive/populate-entity-tables/populate-resource-items.js +114 -0
  197. package/lib/archive/populate-entity-tables/populate-resource-ref-edges.d.ts +31 -0
  198. package/lib/archive/populate-entity-tables/populate-resource-ref-edges.js +33 -0
  199. package/lib/archive/populate-entity-tables/resolve-blob-refs.d.ts +31 -0
  200. package/lib/archive/populate-entity-tables/resolve-blob-refs.js +100 -0
  201. package/lib/archive/populate-entity-tables/resolve-content-type-refs.d.ts +22 -0
  202. package/lib/archive/populate-entity-tables/resolve-content-type-refs.js +27 -0
  203. package/lib/archive/populate-entity-tables/resolve-header-sets.d.ts +49 -0
  204. package/lib/archive/populate-entity-tables/resolve-header-sets.js +122 -0
  205. package/lib/archive/populate-entity-tables/resolve-json-refs.d.ts +25 -0
  206. package/lib/archive/populate-entity-tables/resolve-json-refs.js +67 -0
  207. package/lib/archive/populate-entity-tables/resolve-text-refs.d.ts +30 -0
  208. package/lib/archive/populate-entity-tables/resolve-text-refs.js +61 -0
  209. package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.d.ts +21 -0
  210. package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.js +27 -0
  211. package/lib/archive/populate-entity-tables/resolve-url-refs.d.ts +33 -0
  212. package/lib/archive/populate-entity-tables/resolve-url-refs.js +60 -0
  213. package/lib/archive/populate-entity-tables/test-utils/count-rows.d.ts +17 -0
  214. package/lib/archive/populate-entity-tables/test-utils/count-rows.js +20 -0
  215. package/lib/archive/populate-entity-tables/test-utils/seed-content-items.d.ts +25 -0
  216. package/lib/archive/populate-entity-tables/test-utils/seed-content-items.js +42 -0
  217. package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.d.ts +23 -0
  218. package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.js +178 -0
  219. package/lib/archive/populate-entity-tables/types.d.ts +157 -0
  220. package/lib/archive/populate-entity-tables/types.js +12 -0
  221. package/lib/archive/populate-entity-tables/upsert-text-refs.d.ts +38 -0
  222. package/lib/archive/populate-entity-tables/upsert-text-refs.js +78 -0
  223. package/lib/archive/populate-ref-tables/classify-content-type.d.ts +16 -0
  224. package/lib/archive/populate-ref-tables/classify-content-type.js +52 -0
  225. package/lib/archive/populate-ref-tables/compute-content-hash.d.ts +22 -0
  226. package/lib/archive/populate-ref-tables/compute-content-hash.js +26 -0
  227. package/lib/archive/populate-ref-tables/compute-header-flags.d.ts +16 -0
  228. package/lib/archive/populate-ref-tables/compute-header-flags.js +70 -0
  229. package/lib/archive/populate-ref-tables/content-type-rules.d.ts +38 -0
  230. package/lib/archive/populate-ref-tables/content-type-rules.js +133 -0
  231. package/lib/archive/populate-ref-tables/create-header-table-caches.d.ts +25 -0
  232. package/lib/archive/populate-ref-tables/create-header-table-caches.js +49 -0
  233. package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.d.ts +15 -0
  234. package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.js +15 -0
  235. package/lib/archive/populate-ref-tables/decode-data-uri.d.ts +21 -0
  236. package/lib/archive/populate-ref-tables/decode-data-uri.js +126 -0
  237. package/lib/archive/populate-ref-tables/decompose-header-set.d.ts +29 -0
  238. package/lib/archive/populate-ref-tables/decompose-header-set.js +157 -0
  239. package/lib/archive/populate-ref-tables/decompose-url.d.ts +25 -0
  240. package/lib/archive/populate-ref-tables/decompose-url.js +70 -0
  241. package/lib/archive/populate-ref-tables/header-stability.d.ts +19 -0
  242. package/lib/archive/populate-ref-tables/header-stability.js +22 -0
  243. package/lib/archive/populate-ref-tables/header-value-cache-key.d.ts +17 -0
  244. package/lib/archive/populate-ref-tables/header-value-cache-key.js +19 -0
  245. package/lib/archive/populate-ref-tables/normalize-mime.d.ts +24 -0
  246. package/lib/archive/populate-ref-tables/normalize-mime.js +36 -0
  247. package/lib/archive/populate-ref-tables/populate-blob-refs.d.ts +38 -0
  248. package/lib/archive/populate-ref-tables/populate-blob-refs.js +134 -0
  249. package/lib/archive/populate-ref-tables/populate-content-type-refs.d.ts +27 -0
  250. package/lib/archive/populate-ref-tables/populate-content-type-refs.js +70 -0
  251. package/lib/archive/populate-ref-tables/populate-header-tables.d.ts +35 -0
  252. package/lib/archive/populate-ref-tables/populate-header-tables.js +80 -0
  253. package/lib/archive/populate-ref-tables/populate-json-refs.d.ts +29 -0
  254. package/lib/archive/populate-ref-tables/populate-json-refs.js +101 -0
  255. package/lib/archive/populate-ref-tables/populate-refs.d.ts +51 -0
  256. package/lib/archive/populate-ref-tables/populate-refs.js +62 -0
  257. package/lib/archive/populate-ref-tables/populate-text-refs.d.ts +32 -0
  258. package/lib/archive/populate-ref-tables/populate-text-refs.js +133 -0
  259. package/lib/archive/populate-ref-tables/populate-url-refs.d.ts +28 -0
  260. package/lib/archive/populate-ref-tables/populate-url-refs.js +148 -0
  261. package/lib/archive/populate-ref-tables/test-utils/count-rows.d.ts +15 -0
  262. package/lib/archive/populate-ref-tables/test-utils/count-rows.js +17 -0
  263. package/lib/archive/populate-ref-tables/types.d.ts +197 -0
  264. package/lib/archive/populate-ref-tables/types.js +7 -0
  265. package/lib/archive/populate-ref-tables/upsert-one-header-set.d.ts +34 -0
  266. package/lib/archive/populate-ref-tables/upsert-one-header-set.js +208 -0
  267. package/lib/archive/populate-ref-tables/volatile-header-names.d.ts +20 -0
  268. package/lib/archive/populate-ref-tables/volatile-header-names.js +33 -0
  269. package/lib/archive/redirect-table.d.ts +4 -2
  270. package/lib/archive/redirect-table.js +15 -10
  271. package/lib/archive/resolve-redirect-chain.d.ts +3 -3
  272. package/lib/archive/resolve-redirect-chain.js +2 -2
  273. package/lib/archive/resource.d.ts +1 -1
  274. package/lib/archive/retarget-legacy-fk-tables.d.ts +47 -0
  275. package/lib/archive/retarget-legacy-fk-tables.js +107 -0
  276. package/lib/archive/test-utils/fk-parent-tables.d.ts +15 -0
  277. package/lib/archive/test-utils/fk-parent-tables.js +19 -0
  278. package/lib/archive/test-utils/seed-content-item.d.ts +35 -0
  279. package/lib/archive/test-utils/seed-content-item.js +42 -0
  280. package/lib/archive/test-utils/setup-legacy-fk-db.d.ts +33 -0
  281. package/lib/archive/test-utils/setup-legacy-fk-db.js +270 -0
  282. package/lib/archive/types.d.ts +127 -24
  283. package/lib/archive/verify-migration/capture-rejection.d.ts +24 -0
  284. package/lib/archive/verify-migration/capture-rejection.js +31 -0
  285. package/lib/archive/verify-migration/check-anchor-edges-count.d.ts +34 -0
  286. package/lib/archive/verify-migration/check-anchor-edges-count.js +72 -0
  287. package/lib/archive/verify-migration/check-anchor-edges-sum.d.ts +13 -0
  288. package/lib/archive/verify-migration/check-anchor-edges-sum.js +27 -0
  289. package/lib/archive/verify-migration/check-content-items-count.d.ts +16 -0
  290. package/lib/archive/verify-migration/check-content-items-count.js +30 -0
  291. package/lib/archive/verify-migration/check-content-type-preservation.d.ts +22 -0
  292. package/lib/archive/verify-migration/check-content-type-preservation.js +40 -0
  293. package/lib/archive/verify-migration/check-foreign-key-integrity.d.ts +31 -0
  294. package/lib/archive/verify-migration/check-foreign-key-integrity.js +47 -0
  295. package/lib/archive/verify-migration/check-image-items-count.d.ts +12 -0
  296. package/lib/archive/verify-migration/check-image-items-count.js +26 -0
  297. package/lib/archive/verify-migration/check-page-meta-count.d.ts +15 -0
  298. package/lib/archive/verify-migration/check-page-meta-count.js +31 -0
  299. package/lib/archive/verify-migration/check-reader-parity.d.ts +23 -0
  300. package/lib/archive/verify-migration/check-reader-parity.js +211 -0
  301. package/lib/archive/verify-migration/check-resource-items-count.d.ts +17 -0
  302. package/lib/archive/verify-migration/check-resource-items-count.js +33 -0
  303. package/lib/archive/verify-migration/check-url-round-trip.d.ts +43 -0
  304. package/lib/archive/verify-migration/check-url-round-trip.js +112 -0
  305. package/lib/archive/verify-migration/types.d.ts +70 -0
  306. package/lib/archive/verify-migration/types.js +63 -0
  307. package/lib/archive/verify-migration/verify-migration.d.ts +41 -0
  308. package/lib/archive/verify-migration/verify-migration.js +120 -0
  309. package/lib/crawler/build-redirect-event.d.ts +1 -1
  310. package/lib/crawler/build-redirect-event.js +1 -1
  311. package/lib/crawler/capture-image-dom-paths.d.ts +33 -0
  312. package/lib/crawler/capture-image-dom-paths.js +39 -0
  313. package/lib/crawler/clear-dns-burned-host-cache.d.ts +1 -1
  314. package/lib/crawler/clear-dns-burned-host-cache.js +1 -1
  315. package/lib/crawler/collect-image-dom-paths.d.ts +23 -0
  316. package/lib/crawler/collect-image-dom-paths.js +64 -0
  317. package/lib/crawler/crawler.d.ts +19 -0
  318. package/lib/crawler/crawler.js +40 -26
  319. package/lib/crawler/dns-burned-host-cache.d.ts +3 -3
  320. package/lib/crawler/dns-burned-host-cache.js +3 -3
  321. package/lib/crawler/dns-burned-host-short-circuit-counter.d.ts +2 -2
  322. package/lib/crawler/dns-burned-host-short-circuit-counter.js +2 -2
  323. package/lib/crawler/inject-scope-auth.d.ts +1 -1
  324. package/lib/crawler/inject-scope-auth.js +1 -1
  325. package/lib/crawler/normalize-content-type.d.ts +1 -1
  326. package/lib/crawler/normalize-content-type.js +1 -1
  327. package/lib/crawler/types.d.ts +3 -3
  328. package/lib/crawler-orchestrator.d.ts +9 -0
  329. package/lib/crawler-orchestrator.js +44 -28
  330. package/lib/crawler.d.ts +12 -0
  331. package/lib/crawler.js +21 -0
  332. package/lib/permanent-error-kinds.d.ts +1 -1
  333. package/lib/permanent-error-kinds.js +1 -1
  334. package/lib/types.d.ts +1 -1
  335. package/lib/utils/compute-file-sha256.d.ts +5 -4
  336. package/lib/utils/compute-file-sha256.js +5 -4
  337. package/lib/utils/error/emit-error-with-retry.d.ts +1 -1
  338. package/lib/utils/error/emit-error-with-retry.js +1 -1
  339. package/package.json +10 -10
  340. package/lib/archive/migrate-crawl-errors.d.ts +0 -20
  341. package/lib/archive/migrate-crawl-errors.js +0 -38
  342. package/lib/archive/migrate-html-blob-tables.d.ts +0 -24
  343. package/lib/archive/migrate-html-blob-tables.js +0 -53
  344. package/lib/archive/migrate-inventory-runs.d.ts +0 -29
  345. package/lib/archive/migrate-inventory-runs.js +0 -52
  346. package/lib/archive/migrate-page-errors.d.ts +0 -16
  347. package/lib/archive/migrate-page-errors.js +0 -35
  348. package/lib/archive/migrate-pages-resources-source.d.ts +0 -16
  349. package/lib/archive/migrate-pages-resources-source.js +0 -46
@@ -0,0 +1,72 @@
1
+ import { MigrationVerificationError } from './types.js';
2
+ /**
3
+ * Verifies 0.13 invariant #3: the `anchor_edges` dedup does not
4
+ * eliminate rows and does not create phantom rows.
5
+ *
6
+ * `populateAnchorEdges` collapses N `anchors` rows per `(pageId, hrefId)`
7
+ * pair into a single `anchor_edges` row with `count = N`. Two ways it
8
+ * could go wrong:
9
+ *
10
+ * - `count(anchor_edges) == 0 && count(anchors) > 0` — dedup annihilated
11
+ * the graph. A populate bug or a group-by mis-key would trip this.
12
+ * - `count(anchor_edges) > count(anchors)` — dedup produced more rows than
13
+ * inputs. Impossible under a correct `GROUP BY` but a phantom-insert bug
14
+ * in the populate loop would trip this.
15
+ *
16
+ * `count(anchor_edges) == count(anchors)` is **not** a failure. Issue #194
17
+ * spelled the upper bound as `< count(anchors)` (strict), but a legitimate
18
+ * small crawl where every `(pageId, hrefId)` pair is unique produces one
19
+ * edge per anchor (count=1) and satisfies the identity naturally. Enforcing
20
+ * strict `<` would refuse to migrate such archives even though every other
21
+ * invariant holds; the true invariant is "no rows added or dropped by
22
+ * dedup" (`anchor_edges <= anchors`), so the code enforces the weaker,
23
+ * correct clause. Check #4 (`SUM(count) == count(anchors)`) already
24
+ * establishes that no rows were silently discarded when the counts happen
25
+ * to match.
26
+ *
27
+ * A trivial archive with zero `anchors` legitimately produces zero
28
+ * `anchor_edges`, so both counts being zero passes; a non-zero `anchor_edges`
29
+ * with a zero `anchors` still throws — that would mean rows appeared from
30
+ * nowhere.
31
+ * @param trx - Knex instance or transaction connected to the populated archive.
32
+ * @throws {MigrationVerificationError} when the invariant is violated.
33
+ */
34
+ export async function checkAnchorEdgesCount(trx) {
35
+ const anchorEdgesRows = await trx('anchor_edges').count({ n: '*' });
36
+ const anchorsRows = await trx('anchors').count({ n: '*' });
37
+ const anchorEdgesCount = Number(anchorEdgesRows[0].n);
38
+ const anchorsCount = Number(anchorsRows[0].n);
39
+ if (anchorsCount === 0) {
40
+ if (anchorEdgesCount !== 0) {
41
+ throw new MigrationVerificationError({
42
+ check: '#3 anchor_edges row count',
43
+ context: {
44
+ anchor_edges: anchorEdgesCount,
45
+ anchors: anchorsCount,
46
+ reason: 'anchor_edges must be empty when anchors is empty',
47
+ },
48
+ });
49
+ }
50
+ return;
51
+ }
52
+ if (anchorEdgesCount === 0) {
53
+ throw new MigrationVerificationError({
54
+ check: '#3 anchor_edges row count',
55
+ context: {
56
+ anchor_edges: anchorEdgesCount,
57
+ anchors: anchorsCount,
58
+ reason: 'anchor_edges must be greater than zero when anchors is non-empty',
59
+ },
60
+ });
61
+ }
62
+ if (anchorEdgesCount > anchorsCount) {
63
+ throw new MigrationVerificationError({
64
+ check: '#3 anchor_edges row count',
65
+ context: {
66
+ anchor_edges: anchorEdgesCount,
67
+ anchors: anchorsCount,
68
+ reason: 'anchor_edges must not exceed anchors (dedup should not create rows)',
69
+ },
70
+ });
71
+ }
72
+ }
@@ -0,0 +1,13 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Verifies 0.13 invariant #4: the sum of `anchor_edges.count` equals the
4
+ * total number of legacy `anchors` rows.
5
+ *
6
+ * `populateAnchorEdges` collapses N `anchors` rows per `(pageId, hrefId)`
7
+ * pair into a single edge with `count = N`; the invariant re-establishes the identity
8
+ * `sum(count) = number of source rows`. Together with invariant #3 this
9
+ * proves the dedup is exact (no rows lost, no rows double-counted).
10
+ * @param trx - Knex instance or transaction connected to the populated archive.
11
+ * @throws {MigrationVerificationError} when the sum diverges from the anchors row count.
12
+ */
13
+ export declare function checkAnchorEdgesSum(trx: Knex): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { MigrationVerificationError } from './types.js';
2
+ /**
3
+ * Verifies 0.13 invariant #4: the sum of `anchor_edges.count` equals the
4
+ * total number of legacy `anchors` rows.
5
+ *
6
+ * `populateAnchorEdges` collapses N `anchors` rows per `(pageId, hrefId)`
7
+ * pair into a single edge with `count = N`; the invariant re-establishes the identity
8
+ * `sum(count) = number of source rows`. Together with invariant #3 this
9
+ * proves the dedup is exact (no rows lost, no rows double-counted).
10
+ * @param trx - Knex instance or transaction connected to the populated archive.
11
+ * @throws {MigrationVerificationError} when the sum diverges from the anchors row count.
12
+ */
13
+ export async function checkAnchorEdgesSum(trx) {
14
+ const sumRows = await trx('anchor_edges').sum({ n: 'count' });
15
+ const anchorsRows = await trx('anchors').count({ n: '*' });
16
+ const anchorEdgesSum = Number(sumRows[0].n ?? 0);
17
+ const anchorsCount = Number(anchorsRows[0].n);
18
+ if (anchorEdgesSum !== anchorsCount) {
19
+ throw new MigrationVerificationError({
20
+ check: '#4 anchor_edges count sum',
21
+ context: {
22
+ sum_of_anchor_edges_count: anchorEdgesSum,
23
+ anchors: anchorsCount,
24
+ },
25
+ });
26
+ }
27
+ }
@@ -0,0 +1,16 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Verifies 0.13 invariant #1: every legacy `pages` row is mirrored by
4
+ * one row in `content_items`.
5
+ *
6
+ * `populateContentItems` fills `content_items` with `id = pages.id` for every page
7
+ * (see `populate-content-items.ts`); the invariant is broken only if the
8
+ * populate loop skipped or double-inserted rows. `INSERT OR IGNORE` on the
9
+ * PK collapses duplicates, so a mismatch always means "populate wrote
10
+ * fewer rows than expected" (extra rows would be impossible via the natural
11
+ * PK). We still emit both counts in the error context so operators can see
12
+ * the direction of the discrepancy at a glance.
13
+ * @param trx - Knex instance or transaction connected to the populated archive.
14
+ * @throws {MigrationVerificationError} when the row counts diverge.
15
+ */
16
+ export declare function checkContentItemsCount(trx: Knex): Promise<void>;
@@ -0,0 +1,30 @@
1
+ import { MigrationVerificationError } from './types.js';
2
+ /**
3
+ * Verifies 0.13 invariant #1: every legacy `pages` row is mirrored by
4
+ * one row in `content_items`.
5
+ *
6
+ * `populateContentItems` fills `content_items` with `id = pages.id` for every page
7
+ * (see `populate-content-items.ts`); the invariant is broken only if the
8
+ * populate loop skipped or double-inserted rows. `INSERT OR IGNORE` on the
9
+ * PK collapses duplicates, so a mismatch always means "populate wrote
10
+ * fewer rows than expected" (extra rows would be impossible via the natural
11
+ * PK). We still emit both counts in the error context so operators can see
12
+ * the direction of the discrepancy at a glance.
13
+ * @param trx - Knex instance or transaction connected to the populated archive.
14
+ * @throws {MigrationVerificationError} when the row counts diverge.
15
+ */
16
+ export async function checkContentItemsCount(trx) {
17
+ const contentItemsRows = await trx('content_items').count({ n: '*' });
18
+ const pagesRows = await trx('pages').count({ n: '*' });
19
+ const contentItemsCount = Number(contentItemsRows[0].n);
20
+ const pagesCount = Number(pagesRows[0].n);
21
+ if (contentItemsCount !== pagesCount) {
22
+ throw new MigrationVerificationError({
23
+ check: '#1 content_items row count',
24
+ context: {
25
+ content_items: contentItemsCount,
26
+ pages: pagesCount,
27
+ },
28
+ });
29
+ }
30
+ }
@@ -0,0 +1,22 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Verifies 0.13 invariant #7: no page loses its content-type across the
4
+ * `content_items` populate.
5
+ *
6
+ * `populate-content-items.ts` sets `content_type_id = null` whenever the
7
+ * legacy `pages.contentType` is null OR the empty string (the empty string
8
+ * meaning "no content-type recorded"). Any *other* case where `contentType`
9
+ * carries a real value but `content_items.content_type_id` ended up null
10
+ * would indicate a resolver bug — the current populate throws before insert
11
+ * in that path, so this check is a defence in depth against a future
12
+ * regression.
13
+ *
14
+ * The check runs a single `SELECT id, contentType LIMIT 1` filtered to
15
+ * offending rows; if any row comes back the invariant is violated and the
16
+ * error context surfaces that one sample for the operator. A separate
17
+ * `COUNT(*)` would only add cost without adding information — the presence
18
+ * of a single row already means the invariant fails.
19
+ * @param trx - Knex instance or transaction connected to the populated archive.
20
+ * @throws {MigrationVerificationError} when any content-type is silently dropped.
21
+ */
22
+ export declare function checkContentTypePreservation(trx: Knex): Promise<void>;
@@ -0,0 +1,40 @@
1
+ import { MigrationVerificationError } from './types.js';
2
+ /**
3
+ * Verifies 0.13 invariant #7: no page loses its content-type across the
4
+ * `content_items` populate.
5
+ *
6
+ * `populate-content-items.ts` sets `content_type_id = null` whenever the
7
+ * legacy `pages.contentType` is null OR the empty string (the empty string
8
+ * meaning "no content-type recorded"). Any *other* case where `contentType`
9
+ * carries a real value but `content_items.content_type_id` ended up null
10
+ * would indicate a resolver bug — the current populate throws before insert
11
+ * in that path, so this check is a defence in depth against a future
12
+ * regression.
13
+ *
14
+ * The check runs a single `SELECT id, contentType LIMIT 1` filtered to
15
+ * offending rows; if any row comes back the invariant is violated and the
16
+ * error context surfaces that one sample for the operator. A separate
17
+ * `COUNT(*)` would only add cost without adding information — the presence
18
+ * of a single row already means the invariant fails.
19
+ * @param trx - Knex instance or transaction connected to the populated archive.
20
+ * @throws {MigrationVerificationError} when any content-type is silently dropped.
21
+ */
22
+ export async function checkContentTypePreservation(trx) {
23
+ const sample = await trx('content_items as ci')
24
+ .join('pages as p', 'ci.id', 'p.id')
25
+ .whereNull('ci.content_type_id')
26
+ .whereNotNull('p.contentType')
27
+ .andWhere('p.contentType', '<>', '')
28
+ .select('ci.id as id', 'p.contentType as contentType')
29
+ .limit(1);
30
+ if (sample.length > 0) {
31
+ const offending = sample[0];
32
+ throw new MigrationVerificationError({
33
+ check: '#7 content_type preservation',
34
+ context: {
35
+ sample_page_id: offending.id,
36
+ sample_content_type: offending.contentType,
37
+ },
38
+ });
39
+ }
40
+ }
@@ -0,0 +1,31 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Asserts that `PRAGMA foreign_key_check` reports zero violations across
4
+ * the whole archive.
5
+ *
6
+ * Runs after `retargetLegacyFkTables` + `dropLegacyTables`, i.e. against
7
+ * the final schema — every FK declaration at that point targets the
8
+ * entity / ref tables, so a clean result proves the rebuilt adjunct
9
+ * tables' rows all resolve against `content_items` and no dangling
10
+ * reference survived the legacy-table removal. Running it earlier would
11
+ * also scan the legacy tables (`anchors` alone can hold millions of
12
+ * rows) for declarations that are about to be dropped anyway.
13
+ *
14
+ * `PRAGMA foreign_key_check` inspects data regardless of the
15
+ * `foreign_keys` enforcement setting, so this check stays valid even
16
+ * though the preceding drop phase runs with enforcement OFF.
17
+ *
18
+ * Not part of {@link verifyMigration}'s chain: that orchestrator runs
19
+ * inside the entity-populate transaction while the legacy tables still
20
+ * exist, a different point in the migration lifecycle than this
21
+ * final-schema assertion.
22
+ * @param trx - Knex instance or transaction on the migrated archive.
23
+ * @throws {MigrationVerificationError} when any row violates an FK
24
+ * declaration; the context carries the violation count and the first
25
+ * offending child/parent table pair.
26
+ * @example
27
+ * await retargetLegacyFkTables(trx);
28
+ * await dropLegacyTables(trx);
29
+ * await checkForeignKeyIntegrity(trx); // throws unless zero violations
30
+ */
31
+ export declare function checkForeignKeyIntegrity(trx: Knex): Promise<void>;
@@ -0,0 +1,47 @@
1
+ import { MigrationVerificationError } from './types.js';
2
+ /**
3
+ * Asserts that `PRAGMA foreign_key_check` reports zero violations across
4
+ * the whole archive.
5
+ *
6
+ * Runs after `retargetLegacyFkTables` + `dropLegacyTables`, i.e. against
7
+ * the final schema — every FK declaration at that point targets the
8
+ * entity / ref tables, so a clean result proves the rebuilt adjunct
9
+ * tables' rows all resolve against `content_items` and no dangling
10
+ * reference survived the legacy-table removal. Running it earlier would
11
+ * also scan the legacy tables (`anchors` alone can hold millions of
12
+ * rows) for declarations that are about to be dropped anyway.
13
+ *
14
+ * `PRAGMA foreign_key_check` inspects data regardless of the
15
+ * `foreign_keys` enforcement setting, so this check stays valid even
16
+ * though the preceding drop phase runs with enforcement OFF.
17
+ *
18
+ * Not part of {@link verifyMigration}'s chain: that orchestrator runs
19
+ * inside the entity-populate transaction while the legacy tables still
20
+ * exist, a different point in the migration lifecycle than this
21
+ * final-schema assertion.
22
+ * @param trx - Knex instance or transaction on the migrated archive.
23
+ * @throws {MigrationVerificationError} when any row violates an FK
24
+ * declaration; the context carries the violation count and the first
25
+ * offending child/parent table pair.
26
+ * @example
27
+ * await retargetLegacyFkTables(trx);
28
+ * await dropLegacyTables(trx);
29
+ * await checkForeignKeyIntegrity(trx); // throws unless zero violations
30
+ */
31
+ export async function checkForeignKeyIntegrity(trx) {
32
+ const rows = await trx
33
+ .select('*')
34
+ .from(trx.raw('pragma_foreign_key_check'));
35
+ if (rows.length === 0) {
36
+ return;
37
+ }
38
+ throw new MigrationVerificationError({
39
+ check: 'foreign_key_check',
40
+ context: {
41
+ violation_count: rows.length,
42
+ first_offending_table: rows[0]?.table ?? '(unknown)',
43
+ first_offending_parent: rows[0]?.parent ?? '(unknown)',
44
+ first_offending_rowid: rows[0]?.rowid ?? null,
45
+ },
46
+ });
47
+ }
@@ -0,0 +1,12 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Verifies 0.13 invariant #5: every legacy `images` row is mirrored by
4
+ * one row in `image_items`.
5
+ *
6
+ * `populateImageItems` fills `image_items` with the same PK as `images.id` (see
7
+ * `populate-image-items.ts`); the invariant is broken only if the populate
8
+ * loop skipped rows during URL/blob routing or dom-path derivation.
9
+ * @param trx - Knex instance or transaction connected to the populated archive.
10
+ * @throws {MigrationVerificationError} when the row counts diverge.
11
+ */
12
+ export declare function checkImageItemsCount(trx: Knex): Promise<void>;
@@ -0,0 +1,26 @@
1
+ import { MigrationVerificationError } from './types.js';
2
+ /**
3
+ * Verifies 0.13 invariant #5: every legacy `images` row is mirrored by
4
+ * one row in `image_items`.
5
+ *
6
+ * `populateImageItems` fills `image_items` with the same PK as `images.id` (see
7
+ * `populate-image-items.ts`); the invariant is broken only if the populate
8
+ * loop skipped rows during URL/blob routing or dom-path derivation.
9
+ * @param trx - Knex instance or transaction connected to the populated archive.
10
+ * @throws {MigrationVerificationError} when the row counts diverge.
11
+ */
12
+ export async function checkImageItemsCount(trx) {
13
+ const imageItemsRows = await trx('image_items').count({ n: '*' });
14
+ const imagesRows = await trx('images').count({ n: '*' });
15
+ const imageItemsCount = Number(imageItemsRows[0].n);
16
+ const imagesCount = Number(imagesRows[0].n);
17
+ if (imageItemsCount !== imagesCount) {
18
+ throw new MigrationVerificationError({
19
+ check: '#5 image_items row count',
20
+ context: {
21
+ image_items: imageItemsCount,
22
+ images: imagesCount,
23
+ },
24
+ });
25
+ }
26
+ }
@@ -0,0 +1,15 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Verifies 0.13 invariant #2: every scraped page has one `page_meta`
4
+ * row.
5
+ *
6
+ * `page_meta` mirrors the meta columns of `pages` for `scraped = 1` rows
7
+ * only (see `populate-page-meta.ts`); un-scraped pages have no meta to
8
+ * carry. A mismatch usually indicates that the populate loop encountered a
9
+ * scraped page whose meta resolution failed silently (e.g. a text ref that
10
+ * did not deduplicate), in which case the archive would be missing meta for
11
+ * an otherwise valid page.
12
+ * @param trx - Knex instance or transaction connected to the populated archive.
13
+ * @throws {MigrationVerificationError} when the row counts diverge.
14
+ */
15
+ export declare function checkPageMetaCount(trx: Knex): Promise<void>;
@@ -0,0 +1,31 @@
1
+ import { MigrationVerificationError } from './types.js';
2
+ /**
3
+ * Verifies 0.13 invariant #2: every scraped page has one `page_meta`
4
+ * row.
5
+ *
6
+ * `page_meta` mirrors the meta columns of `pages` for `scraped = 1` rows
7
+ * only (see `populate-page-meta.ts`); un-scraped pages have no meta to
8
+ * carry. A mismatch usually indicates that the populate loop encountered a
9
+ * scraped page whose meta resolution failed silently (e.g. a text ref that
10
+ * did not deduplicate), in which case the archive would be missing meta for
11
+ * an otherwise valid page.
12
+ * @param trx - Knex instance or transaction connected to the populated archive.
13
+ * @throws {MigrationVerificationError} when the row counts diverge.
14
+ */
15
+ export async function checkPageMetaCount(trx) {
16
+ const pageMetaRows = await trx('page_meta').count({ n: '*' });
17
+ const scrapedPagesRows = await trx('pages')
18
+ .where('scraped', true)
19
+ .count({ n: '*' });
20
+ const pageMetaCount = Number(pageMetaRows[0].n);
21
+ const scrapedPagesCount = Number(scrapedPagesRows[0].n);
22
+ if (pageMetaCount !== scrapedPagesCount) {
23
+ throw new MigrationVerificationError({
24
+ check: '#2 page_meta row count',
25
+ context: {
26
+ page_meta: pageMetaCount,
27
+ scraped_pages: scrapedPagesCount,
28
+ },
29
+ });
30
+ }
31
+ }
@@ -0,0 +1,23 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Compares totals reported by 0.13 readers (which query the 0.13
4
+ * entity tables exclusively) against equivalent inline SQL against the
5
+ * still-present legacy write-model tables (`pages` / `anchors` / `images` /
6
+ * `resources` / `resources-referrers`). A mismatch here means either the
7
+ * 0.13 populates lost rows or one of the new reader implementations
8
+ * has a filter/predicate off-by-one relative to its legacy counterpart.
9
+ *
10
+ * This is deliberately lighter than the "byte-identical items array"
11
+ * verification issue #195 acceptance references — running every reader on
12
+ * every archive and diffing full result sets would balloon the migrator's
13
+ * runtime and require importing `@nitpicker/query` back into the crawler
14
+ * package (a dependency inversion). The eight totals below capture every
15
+ * reader whose scope was flagged in issue #195 and catch the classes of
16
+ * regression the 0.13 row-count invariants cannot: predicate drift
17
+ * inside a reader function (e.g. a `WHERE contentType='text/html'` filter
18
+ * silently swapping meaning between `pages.contentType` and
19
+ * `content_type_refs.raw`).
20
+ * @param trx - The open migration transaction.
21
+ * @throws {MigrationVerificationError} If any pair of totals disagrees.
22
+ */
23
+ export declare function checkReaderParity(trx: Knex): Promise<void>;
@@ -0,0 +1,211 @@
1
+ import { MigrationVerificationError } from './types.js';
2
+ /**
3
+ * Compares totals reported by 0.13 readers (which query the 0.13
4
+ * entity tables exclusively) against equivalent inline SQL against the
5
+ * still-present legacy write-model tables (`pages` / `anchors` / `images` /
6
+ * `resources` / `resources-referrers`). A mismatch here means either the
7
+ * 0.13 populates lost rows or one of the new reader implementations
8
+ * has a filter/predicate off-by-one relative to its legacy counterpart.
9
+ *
10
+ * This is deliberately lighter than the "byte-identical items array"
11
+ * verification issue #195 acceptance references — running every reader on
12
+ * every archive and diffing full result sets would balloon the migrator's
13
+ * runtime and require importing `@nitpicker/query` back into the crawler
14
+ * package (a dependency inversion). The eight totals below capture every
15
+ * reader whose scope was flagged in issue #195 and catch the classes of
16
+ * regression the 0.13 row-count invariants cannot: predicate drift
17
+ * inside a reader function (e.g. a `WHERE contentType='text/html'` filter
18
+ * silently swapping meaning between `pages.contentType` and
19
+ * `content_type_refs.raw`).
20
+ * @param trx - The open migration transaction.
21
+ * @throws {MigrationVerificationError} If any pair of totals disagrees.
22
+ */
23
+ export async function checkReaderParity(trx) {
24
+ // `analysis_violations` postdates issue #116 — an archive from before
25
+ // that feature landed (and that never ran `nitpicker analyze` after
26
+ // upgrading) has no such table at all. Unlike the other legacy
27
+ // tables here (`pages` / `anchors` / `images` / `resources`), which
28
+ // `init-schema.ts` has always created, this one is genuinely absent
29
+ // on some real archives, so the check below is skipped rather than
30
+ // querying a table that may not exist.
31
+ const hasAnalysisViolations = await trx.schema.hasTable('analysis_violations');
32
+ const checks = [
33
+ {
34
+ label: 'listPages default (scraped=1, redirect null, html-or-null)',
35
+ legacy: async () => scalar(trx('pages')
36
+ .where('scraped', 1)
37
+ .whereNull('redirectDestId')
38
+ .where((qb) => {
39
+ qb.where('isSkipped', 0).orWhereNull('isSkipped');
40
+ })
41
+ .where((qb) => {
42
+ qb.whereNull('contentType').orWhere('contentType', 'text/html');
43
+ })
44
+ .count({ count: '*' })),
45
+ current: async () => scalar(trx('content_items as ci')
46
+ .leftJoin('content_type_refs as ctr', 'ctr.id', 'ci.content_type_id')
47
+ .where('ci.scraped', 1)
48
+ .whereNull('ci.redirect_dest_id')
49
+ .where((qb) => {
50
+ qb.where('ci.is_skipped', 0).orWhereNull('ci.is_skipped');
51
+ })
52
+ .where((qb) => {
53
+ qb.whereNull('ctr.raw').orWhere('ctr.raw', 'text/html');
54
+ })
55
+ .count({ count: '*' })),
56
+ },
57
+ {
58
+ label: 'listImages total',
59
+ legacy: async () => scalar(trx('images').count({ count: '*' })),
60
+ current: async () => scalar(trx('image_items').count({ count: '*' })),
61
+ },
62
+ {
63
+ label: 'listResources total',
64
+ legacy: async () => scalar(trx('resources').count({ count: '*' })),
65
+ current: async () => scalar(trx('resource_items').count({ count: '*' })),
66
+ },
67
+ {
68
+ label: 'listLinks broken (status=404 through redirect)',
69
+ legacy: async () => scalar(trx('anchors')
70
+ .join('pages as dest', 'anchors.hrefId', 'dest.id')
71
+ .leftJoin('pages as canonical', 'dest.redirectDestId', 'canonical.id')
72
+ .whereRaw('COALESCE("canonical"."status", "dest"."status") = 404')
73
+ .count({ count: '*' })),
74
+ current: async () => scalar(trx('anchor_edges as ae')
75
+ .join('content_items as dest', 'ae.href_page_id', 'dest.id')
76
+ .leftJoin('content_items as canonical', 'dest.redirect_dest_id', 'canonical.id')
77
+ .whereRaw('COALESCE("canonical"."status", "dest"."status") = 404')
78
+ .sum({ count: 'ae.count' })),
79
+ },
80
+ {
81
+ label: 'checkHeaders scope (internal html scraped, no redirect)',
82
+ legacy: async () => scalar(trx('pages')
83
+ .where({ scraped: 1, isExternal: 0, contentType: 'text/html' })
84
+ .whereNull('redirectDestId')
85
+ .count({ count: '*' })),
86
+ current: async () => scalar(trx('content_items as ci')
87
+ .join('content_type_refs as ctr', 'ctr.id', 'ci.content_type_id')
88
+ .where({
89
+ 'ci.scraped': 1,
90
+ 'ci.is_external': 0,
91
+ 'ctr.raw': 'text/html',
92
+ })
93
+ .whereNull('ci.redirect_dest_id')
94
+ .count({ count: '*' })),
95
+ },
96
+ {
97
+ label: 'findDuplicates(title) group count',
98
+ legacy: async () => scalar(trx
99
+ .count({ count: '*' })
100
+ .from(trx('pages')
101
+ .select('title')
102
+ .where({ scraped: 1, isExternal: 0, contentType: 'text/html' })
103
+ .whereNull('redirectDestId')
104
+ .whereNotNull('title')
105
+ .whereNot('title', '')
106
+ .groupBy('title')
107
+ .having(trx.raw('count(*) > 1'))
108
+ .as('g'))),
109
+ current: async () => scalar(trx.count({ count: '*' }).from(trx('content_items as ci')
110
+ .join('content_type_refs as ctr', 'ctr.id', 'ci.content_type_id')
111
+ .join('page_meta as pm', 'pm.page_id', 'ci.id')
112
+ .join('text_refs as tr', 'tr.id', 'pm.title_text_id')
113
+ .select('pm.title_text_id')
114
+ .where({
115
+ 'ci.scraped': 1,
116
+ 'ci.is_external': 0,
117
+ 'ctr.raw': 'text/html',
118
+ })
119
+ .whereNull('ci.redirect_dest_id')
120
+ .whereNotNull('pm.title_text_id')
121
+ .whereNot('tr.text', '')
122
+ .groupBy('pm.title_text_id')
123
+ .having(trx.raw('count(*) > 1'))
124
+ .as('g'))),
125
+ },
126
+ {
127
+ label: 'findMismatches(canonical) count',
128
+ legacy: async () => scalar(trx('pages')
129
+ .where({ scraped: 1, isExternal: 0, contentType: 'text/html' })
130
+ .whereNull('redirectDestId')
131
+ .whereNotNull('canonical')
132
+ .whereNot('canonical', '')
133
+ .whereRaw('canonical != url')
134
+ .count({ count: '*' })),
135
+ current: async () => scalar(trx('content_items as ci')
136
+ .join('content_type_refs as ctr', 'ctr.id', 'ci.content_type_id')
137
+ .join('page_meta as pm', 'pm.page_id', 'ci.id')
138
+ .join('url_refs as canonical_ur', 'canonical_ur.id', 'pm.canonical_url_id')
139
+ .where({
140
+ 'ci.scraped': 1,
141
+ 'ci.is_external': 0,
142
+ 'ctr.raw': 'text/html',
143
+ })
144
+ .whereNull('ci.redirect_dest_id')
145
+ .whereNotNull('pm.canonical_url_id')
146
+ .whereNot('canonical_ur.url', '')
147
+ .whereRaw('"pm"."canonical_url_id" != "ci"."url_id"')
148
+ .count({ count: '*' })),
149
+ },
150
+ ...(hasAnalysisViolations
151
+ ? [
152
+ {
153
+ // `getViolations` joins `analysis_violations` → `content_items`
154
+ // → `url_refs` to project the page URL for each violation. The
155
+ // current-path parity check exercises that JOIN chain so a
156
+ // broken FK / missing `content_items` row surfaces here as a
157
+ // row-count discrepancy (the legacy side counts `pages` rows
158
+ // via `analysis_violations.page_id`, so a lost `content_items`
159
+ // row makes the current-side INNER JOIN drop the violation).
160
+ label: 'getViolations page-URL join (analysis_violations → content_items → url_refs)',
161
+ legacy: async () => scalar(trx('analysis_violations as v')
162
+ .join('pages as p', 'p.id', 'v.page_id')
163
+ .count({ count: '*' })),
164
+ current: async () => scalar(trx('analysis_violations as v')
165
+ .join('content_items as p', 'p.id', 'v.page_id')
166
+ .join('url_refs as ur', 'ur.id', 'p.url_id')
167
+ .count({ count: '*' })),
168
+ },
169
+ ]
170
+ : []),
171
+ ];
172
+ const failures = [];
173
+ // Zero-vs-zero cannot detect a "populate silently produced no rows"
174
+ // bug, so a check whose LEGACY side returns 0 is skipped rather than
175
+ // counted as pass — the parity check exists to catch predicate drift
176
+ // once both sides have rows to compare. `checkContentItemsCount` etc.
177
+ // already guard the "current is empty when legacy is not" direction
178
+ // upstream in {@link verifyMigration}.
179
+ let comparedChecks = 0;
180
+ for (const { label, legacy, current } of checks) {
181
+ const [legacyValue, currentValue] = await Promise.all([legacy(), current()]);
182
+ if (legacyValue === 0 && currentValue === 0) {
183
+ continue;
184
+ }
185
+ comparedChecks++;
186
+ if (legacyValue !== currentValue) {
187
+ failures.push(`${label}: legacy=${legacyValue}, current=${currentValue}`);
188
+ }
189
+ }
190
+ if (failures.length > 0) {
191
+ throw new MigrationVerificationError({
192
+ check: '#9 reader parity',
193
+ context: {
194
+ failures: failures.join('; '),
195
+ compared_checks: comparedChecks,
196
+ },
197
+ });
198
+ }
199
+ }
200
+ /**
201
+ * Extracts the single numeric value from a Knex `count()` / `sum()` result.
202
+ * SQLite returns strings for aggregate expressions; we coerce and treat
203
+ * `null` as `0` (an empty aggregate).
204
+ * @param queryBuilder - The pending Knex query that resolves to a single
205
+ * aggregate row.
206
+ * @returns The aggregate value as a JS number.
207
+ */
208
+ async function scalar(queryBuilder) {
209
+ const rows = (await queryBuilder);
210
+ return Number(rows[0]?.count ?? 0);
211
+ }
@@ -0,0 +1,17 @@
1
+ import type { Knex } from 'knex';
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 declare function checkResourceItemsCount(trx: Knex): Promise<void>;