@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,141 @@
1
+ import { createProgressReporter } from '../create-progress-reporter.js';
2
+ import { loadContentTypeRefs } from './resolve-content-type-refs.js';
3
+ import { resolveHeaderSets } from './resolve-header-sets.js';
4
+ import { resolveUrlRefs } from './resolve-url-refs.js';
5
+ /**
6
+ * Rows scanned per keyset-paginated `SELECT` chunk against `pages`. The
7
+ * migration reads every page column that maps to `content_items` in one
8
+ * SELECT per chunk, so scaling this too high would blow up the result-set
9
+ * size for long meta text columns. 500 keeps peak memory bounded at
10
+ * ≈ 30 MB for a chunk of `pages` with populated meta and header sets.
11
+ */
12
+ const READ_CHUNK_SIZE = 500;
13
+ /**
14
+ * Rows sent per `INSERT INTO content_items ... VALUES (...)` statement.
15
+ * Each row binds 16 params (id + 15 core columns), so 300 rows =
16
+ * 4 800 params — safely under SQLite's default variable limit of 32 766.
17
+ */
18
+ const INSERT_CHUNK_SIZE = 300;
19
+ /**
20
+ * Populates `content_items` from `pages` (issue #193).
21
+ *
22
+ * Strategy per chunk:
23
+ *
24
+ * 1. **Keyset-paginate** `pages` in id order so partial-failure re-runs
25
+ * resume from the last committed row without reading the full table.
26
+ * Same pattern as {@link ../populate-ref-tables/populate-url-refs.ts}.
27
+ * 2. **Batch-resolve** `url_refs.id`, `content_type_refs.id`, and
28
+ * `header_sets.id` for every distinct value in the chunk — one round
29
+ * trip per ref table instead of N round trips per row.
30
+ * 3. **Bulk INSERT** with explicit `id = pages.id`. Reusing the legacy
31
+ * PK preserves every FK reference in `page_errors` / `page_tags` /
32
+ * `page_jsonld` / `page_html_ref` without a per-row UPDATE.
33
+ *
34
+ * `redirect_dest_id` is copied verbatim from `redirectDestId` — the FK
35
+ * self-reference is `DEFERRABLE INITIALLY DEFERRED` (see
36
+ * {@link ../create-entity-tables.ts}) so a redirect source
37
+ * inserted before its destination is validated only at COMMIT time.
38
+ *
39
+ * `INSERT OR IGNORE` on the natural PK makes the step idempotent — a
40
+ * re-run after a partial failure only inserts the rows that are still
41
+ * missing.
42
+ *
43
+ * `content_type_refs` is preloaded once at the start because its
44
+ * cardinality is small (see {@link ./resolve-content-type-refs.ts}); the
45
+ * URL and header-set resolvers are called per chunk because their
46
+ * per-archive cardinality can be very large (one row per distinct URL /
47
+ * response JSON).
48
+ * @param trx - Knex instance or transaction connected to the archive DB.
49
+ * @param onProgress - Optional sink for periodic progress lines (one per
50
+ * ~5% of `pages` scanned); see {@link ../create-progress-reporter.ts}.
51
+ * @example
52
+ * await knex.transaction(async (trx) => {
53
+ * await populateContentItems(trx);
54
+ * });
55
+ */
56
+ export async function populateContentItems(trx, onProgress) {
57
+ const contentTypeIds = await loadContentTypeRefs(trx);
58
+ const countRows = await trx('pages').count({ n: '*' });
59
+ const total = Number(countRows[0]?.n ?? 0);
60
+ const report = createProgressReporter('content_items (pages)', total, onProgress);
61
+ let processed = 0;
62
+ let cursor = 0;
63
+ while (true) {
64
+ const rows = await trx('pages')
65
+ .select('id', 'url', 'redirectDestId', 'scraped', 'isTarget', 'isExternal', 'status', 'statusText', 'contentType', 'contentLength', 'responseHeaders', 'firstCrawledAt', 'lastCrawledAt', 'order', 'isSkipped', 'skipReason', 'source')
66
+ .where('id', '>', cursor)
67
+ .orderBy('id', 'asc')
68
+ .limit(READ_CHUNK_SIZE);
69
+ if (rows.length === 0) {
70
+ break;
71
+ }
72
+ cursor = rows.at(-1).id;
73
+ processed += rows.length;
74
+ report(processed);
75
+ const urls = new Set();
76
+ const headerJsonStrings = new Set();
77
+ for (const row of rows) {
78
+ urls.add(row.url);
79
+ if (typeof row.responseHeaders === 'string' && row.responseHeaders !== '') {
80
+ headerJsonStrings.add(row.responseHeaders);
81
+ }
82
+ }
83
+ const urlIds = await resolveUrlRefs(trx, urls);
84
+ const headerSetIds = await resolveHeaderSets(trx, headerJsonStrings);
85
+ const inserts = rows.map((row) => {
86
+ const urlId = urlIds.get(row.url) ?? null;
87
+ if (urlId === null) {
88
+ throw new Error(`populateContentItems: url_refs.id not resolved for page id=${row.id} url=${row.url} — populateUrlRefs must run first`);
89
+ }
90
+ let contentTypeId = null;
91
+ if (row.contentType != null && row.contentType !== '') {
92
+ const resolved = contentTypeIds.get(row.contentType);
93
+ if (resolved === undefined) {
94
+ throw new Error(`populateContentItems: content_type_refs.id not resolved for page id=${row.id} contentType=${row.contentType} — populateContentTypeRefs must run first`);
95
+ }
96
+ contentTypeId = resolved;
97
+ }
98
+ let headerSetId = null;
99
+ if (typeof row.responseHeaders === 'string' && row.responseHeaders !== '') {
100
+ const resolved = headerSetIds.get(row.responseHeaders);
101
+ if (resolved === undefined) {
102
+ // `resolveHeaderSets` skips sentinel values ('{}' / 'null')
103
+ // AND payloads that `decomposeHeaderSet` cannot parse into
104
+ // any entries — in both cases the source row genuinely had
105
+ // no header set to point at, so `header_set_id = null` is
106
+ // the correct final state. `resolveHeaderSets` already ran
107
+ // the raw_hash fallback, so this branch only fires when the
108
+ // row has no persistable header set (parse error, or every
109
+ // entry stripped for non-string values).
110
+ headerSetId = null;
111
+ }
112
+ else {
113
+ headerSetId = resolved;
114
+ }
115
+ }
116
+ return {
117
+ id: row.id,
118
+ url_id: urlId,
119
+ is_external: row.isExternal == null ? 0 : row.isExternal ? 1 : 0,
120
+ scraped: row.scraped ? 1 : 0,
121
+ is_target: row.isTarget ? 1 : 0,
122
+ status: row.status ?? null,
123
+ status_text: row.statusText ?? null,
124
+ content_type_id: contentTypeId,
125
+ content_length: row.contentLength ?? null,
126
+ header_set_id: headerSetId,
127
+ redirect_dest_id: row.redirectDestId ?? null,
128
+ source: row.source,
129
+ first_crawled_at: row.firstCrawledAt ?? null,
130
+ last_crawled_at: row.lastCrawledAt ?? null,
131
+ crawl_order: row.order ?? null,
132
+ is_skipped: row.isSkipped == null ? null : row.isSkipped ? 1 : 0,
133
+ skip_reason: row.skipReason ?? null,
134
+ };
135
+ });
136
+ for (let index = 0; index < inserts.length; index += INSERT_CHUNK_SIZE) {
137
+ const chunk = inserts.slice(index, index + INSERT_CHUNK_SIZE);
138
+ await trx('content_items').insert(chunk).onConflict('id').ignore();
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,81 @@
1
+ import type { ProgressCallback } from '../create-progress-reporter.js';
2
+ import type { PageDomPathResolver } from './populate-image-items.js';
3
+ import type { Knex } from 'knex';
4
+ /**
5
+ * Runs the six 0.13 entity/edge populates (issue #193) in dependency
6
+ * order against an already-connected archive.
7
+ *
8
+ * Order rationale:
9
+ *
10
+ * 1. **`content_items`** — every downstream populate's FKs reference
11
+ * `content_items(id)`. Must land first so the FKs are valid at
12
+ * COMMIT time. `redirect_dest_id` is `DEFERRABLE INITIALLY DEFERRED`
13
+ * (see {@link ../create-entity-tables.ts}) so a redirect
14
+ * source inserted before its destination is validated only at
15
+ * COMMIT — the insert order within this populate is not
16
+ * load-bearing.
17
+ * 2. **`page_meta`** — FK to `content_items(id)`.
18
+ * 3. **`resource_items`** — required before `resource_ref_edges`.
19
+ * 4. **`anchor_edges`** — FKs to `content_items(id)` on both
20
+ * sides.
21
+ * 5. **`resource_ref_edges`** — FKs to `resource_items(id)` and
22
+ * `content_items(id)`.
23
+ * 6. **`image_items`** — FK to `content_items(id)`; the
24
+ * dom-path text_refs upsert is scoped to this populate so re-runs are
25
+ * self-contained.
26
+ *
27
+ * Every sub-populate is independently idempotent via `INSERT OR IGNORE` on
28
+ * its natural key or PK. On a **re-crawl** (`crawl --append` /
29
+ * `--retry-failed` / `--inventory` all UPDATE existing `pages` /
30
+ * `resources` rows in place, and `#insertPage` deletes + re-inserts
31
+ * `anchors` / `images` per page), running populate again against the
32
+ * previously-populated archive would leave `content_items` / `page_meta`
33
+ * / `resource_items` at their FIRST-crawl values (source-priority
34
+ * upgrades, refreshed status, changed metadata never propagate) and
35
+ * `anchor_edges` / `image_items` / `resource_ref_edges` would keep stale
36
+ * rows keyed by the now-deleted legacy ids. To keep the reader-side
37
+ * view of the archive faithful to `pages` / `resources` on re-crawl,
38
+ * every entity + edge table is TRUNCATEd (in child-first order so no FK
39
+ * check trips) at the top of this function before the six sub-populates
40
+ * re-insert from the current legacy state. Ref tables (`url_refs`,
41
+ * `text_refs`, `content_type_refs`, `header_*`) are NOT truncated —
42
+ * they are content-addressable and additive; re-inserting the same
43
+ * value hits `INSERT OR IGNORE` and is a no-op.
44
+ *
45
+ * The whole invocation is expected to run inside one writer transaction
46
+ * with `.bak` protection at the caller level (a single WAL transaction
47
+ * with `.bak` rollback on failure);
48
+ * this function does not open its own transaction so the
49
+ * caller controls the boundary — see {@link
50
+ * ../populate-ref-tables/populate-refs.ts} for the same convention.
51
+ *
52
+ * **`PRAGMA foreign_keys = ON` is REQUIRED** on the underlying
53
+ * connection before this function is called. `content_items.redirect_dest_id`
54
+ * is `DEFERRABLE INITIALLY DEFERRED` (see
55
+ * {@link ../create-entity-tables.ts}); without foreign-key
56
+ * enforcement libsql commits `content_items` rows whose `redirect_dest_id`
57
+ * points at a non-existent page and the deferrable-FK-enforcement-at-COMMIT
58
+ * invariant is silently broken. The
59
+ * migration script sets the pragma explicitly; any other caller must do
60
+ * the same.
61
+ * HTML BLOB reads happen inline inside {@link populateImageItems} against
62
+ * the same `trx` — no `getPageHtml` callback here — because a callback
63
+ * that routed through `Database.getHtmlOfPageById` would re-enter the
64
+ * connection pool from inside this writer transaction and deadlock on
65
+ * libsql's single writer connection. See `./populate-image-items.ts` for
66
+ * the detailed rationale.
67
+ * @param trx - Knex instance or transaction connected to the archive DB.
68
+ * @param resolvePageDomPaths - Callback that returns dom_path strings
69
+ * for one page's images. Injected rather than hard-coded so
70
+ * `@nitpicker/crawler` does not become a jsdom consumer at runtime.
71
+ * @param onProgress - Optional sink threaded to every sub-populate for
72
+ * periodic progress lines; see {@link ../create-progress-reporter.ts}.
73
+ * @example
74
+ * const archive = await Archive.open(archivePath);
75
+ * const knex = archive.getKnex();
76
+ * await knex.transaction(async (trx) => {
77
+ * await populateEntityTables(trx, jsdomResolver);
78
+ * });
79
+ * await archive.write();
80
+ */
81
+ export declare function populateEntityTables(trx: Knex, resolvePageDomPaths: PageDomPathResolver, onProgress?: ProgressCallback): Promise<void>;
@@ -0,0 +1,111 @@
1
+ import { populateAnchorEdges } from './populate-anchor-edges.js';
2
+ import { populateContentItems } from './populate-content-items.js';
3
+ import { populateImageItems } from './populate-image-items.js';
4
+ import { populatePageMeta } from './populate-page-meta.js';
5
+ import { populateResourceItems } from './populate-resource-items.js';
6
+ import { populateResourceRefEdges } from './populate-resource-ref-edges.js';
7
+ /**
8
+ * Runs the six 0.13 entity/edge populates (issue #193) in dependency
9
+ * order against an already-connected archive.
10
+ *
11
+ * Order rationale:
12
+ *
13
+ * 1. **`content_items`** — every downstream populate's FKs reference
14
+ * `content_items(id)`. Must land first so the FKs are valid at
15
+ * COMMIT time. `redirect_dest_id` is `DEFERRABLE INITIALLY DEFERRED`
16
+ * (see {@link ../create-entity-tables.ts}) so a redirect
17
+ * source inserted before its destination is validated only at
18
+ * COMMIT — the insert order within this populate is not
19
+ * load-bearing.
20
+ * 2. **`page_meta`** — FK to `content_items(id)`.
21
+ * 3. **`resource_items`** — required before `resource_ref_edges`.
22
+ * 4. **`anchor_edges`** — FKs to `content_items(id)` on both
23
+ * sides.
24
+ * 5. **`resource_ref_edges`** — FKs to `resource_items(id)` and
25
+ * `content_items(id)`.
26
+ * 6. **`image_items`** — FK to `content_items(id)`; the
27
+ * dom-path text_refs upsert is scoped to this populate so re-runs are
28
+ * self-contained.
29
+ *
30
+ * Every sub-populate is independently idempotent via `INSERT OR IGNORE` on
31
+ * its natural key or PK. On a **re-crawl** (`crawl --append` /
32
+ * `--retry-failed` / `--inventory` all UPDATE existing `pages` /
33
+ * `resources` rows in place, and `#insertPage` deletes + re-inserts
34
+ * `anchors` / `images` per page), running populate again against the
35
+ * previously-populated archive would leave `content_items` / `page_meta`
36
+ * / `resource_items` at their FIRST-crawl values (source-priority
37
+ * upgrades, refreshed status, changed metadata never propagate) and
38
+ * `anchor_edges` / `image_items` / `resource_ref_edges` would keep stale
39
+ * rows keyed by the now-deleted legacy ids. To keep the reader-side
40
+ * view of the archive faithful to `pages` / `resources` on re-crawl,
41
+ * every entity + edge table is TRUNCATEd (in child-first order so no FK
42
+ * check trips) at the top of this function before the six sub-populates
43
+ * re-insert from the current legacy state. Ref tables (`url_refs`,
44
+ * `text_refs`, `content_type_refs`, `header_*`) are NOT truncated —
45
+ * they are content-addressable and additive; re-inserting the same
46
+ * value hits `INSERT OR IGNORE` and is a no-op.
47
+ *
48
+ * The whole invocation is expected to run inside one writer transaction
49
+ * with `.bak` protection at the caller level (a single WAL transaction
50
+ * with `.bak` rollback on failure);
51
+ * this function does not open its own transaction so the
52
+ * caller controls the boundary — see {@link
53
+ * ../populate-ref-tables/populate-refs.ts} for the same convention.
54
+ *
55
+ * **`PRAGMA foreign_keys = ON` is REQUIRED** on the underlying
56
+ * connection before this function is called. `content_items.redirect_dest_id`
57
+ * is `DEFERRABLE INITIALLY DEFERRED` (see
58
+ * {@link ../create-entity-tables.ts}); without foreign-key
59
+ * enforcement libsql commits `content_items` rows whose `redirect_dest_id`
60
+ * points at a non-existent page and the deferrable-FK-enforcement-at-COMMIT
61
+ * invariant is silently broken. The
62
+ * migration script sets the pragma explicitly; any other caller must do
63
+ * the same.
64
+ * HTML BLOB reads happen inline inside {@link populateImageItems} against
65
+ * the same `trx` — no `getPageHtml` callback here — because a callback
66
+ * that routed through `Database.getHtmlOfPageById` would re-enter the
67
+ * connection pool from inside this writer transaction and deadlock on
68
+ * libsql's single writer connection. See `./populate-image-items.ts` for
69
+ * the detailed rationale.
70
+ * @param trx - Knex instance or transaction connected to the archive DB.
71
+ * @param resolvePageDomPaths - Callback that returns dom_path strings
72
+ * for one page's images. Injected rather than hard-coded so
73
+ * `@nitpicker/crawler` does not become a jsdom consumer at runtime.
74
+ * @param onProgress - Optional sink threaded to every sub-populate for
75
+ * periodic progress lines; see {@link ../create-progress-reporter.ts}.
76
+ * @example
77
+ * const archive = await Archive.open(archivePath);
78
+ * const knex = archive.getKnex();
79
+ * await knex.transaction(async (trx) => {
80
+ * await populateEntityTables(trx, jsdomResolver);
81
+ * });
82
+ * await archive.write();
83
+ */
84
+ export async function populateEntityTables(trx, resolvePageDomPaths, onProgress) {
85
+ // Truncate child-first so no outgoing FK check ever sees a broken
86
+ // reference mid-delete. Order is: leaf edge/entity tables that have
87
+ // no incoming FKs (`image_items`, `anchor_edges`, `resource_ref_edges`),
88
+ // then the parent entities they referenced (`page_meta` and
89
+ // `resource_items`; `page_meta` references `content_items` so it
90
+ // must be dropped before `content_items`; `resource_items` was
91
+ // pointed at by `resource_ref_edges` which is now empty), and
92
+ // finally `content_items` itself — the only remaining incoming
93
+ // reference is its own DEFERRABLE INITIALLY DEFERRED
94
+ // `redirect_dest_id`, which is validated at COMMIT so a mid-trx
95
+ // wipe-and-refill is legal. No external table has an enforced FK
96
+ // into `content_items` (viewer read-model tables and
97
+ // `analysis_violations` reference `pages(id)` or hold logical-only
98
+ // pointers), so the truncation stops here.
99
+ await trx('image_items').delete();
100
+ await trx('anchor_edges').delete();
101
+ await trx('resource_ref_edges').delete();
102
+ await trx('page_meta').delete();
103
+ await trx('resource_items').delete();
104
+ await trx('content_items').delete();
105
+ await populateContentItems(trx, onProgress);
106
+ await populatePageMeta(trx, onProgress);
107
+ await populateResourceItems(trx, onProgress);
108
+ await populateAnchorEdges(trx, onProgress);
109
+ await populateResourceRefEdges(trx);
110
+ await populateImageItems(trx, resolvePageDomPaths, onProgress);
111
+ }
@@ -0,0 +1,91 @@
1
+ import type { ProgressCallback } from '../create-progress-reporter.js';
2
+ import type { DomPathResult } from './types.js';
3
+ import type { Knex } from 'knex';
4
+ /**
5
+ * Callback that resolves DOM-path strings for one page's images. The
6
+ * callback receives the page's HTML string (or `null` when no snapshot
7
+ * is stored) plus the page's `images` rows, and must return one
8
+ * {@link DomPathResult} per row.
9
+ *
10
+ * Injected rather than hard-coded so `@nitpicker/crawler` does not
11
+ * become a jsdom consumer at runtime — the migration script
12
+ * (`scripts/migrate-to-0.13.mjs`) wires a jsdom-backed
13
+ * implementation, while unit tests inject stubs. A future crawler-time
14
+ * DOM-path capture can use the same signature with
15
+ * puppeteer-backed elements.
16
+ */
17
+ export type PageDomPathResolver = (pageId: number, htmlString: string | null, images: readonly ImageRowForResolver[]) => Promise<ReadonlyMap<number, DomPathResult>>;
18
+ /**
19
+ * The projection of `images` rows passed to a
20
+ * {@link PageDomPathResolver}. Kept as a narrow shape (only the fields
21
+ * the resolver needs) so callers of the resolver do not depend on the
22
+ * full row width.
23
+ */
24
+ export interface ImageRowForResolver {
25
+ /** Legacy `images.id`. */
26
+ id: number;
27
+ /** Legacy `images.sourceCode`. */
28
+ sourceCode: string | null;
29
+ }
30
+ /**
31
+ * Populates `image_items` from `images` (issue #193).
32
+ *
33
+ * The outer loop iterates **pages**, not images. Each page's images are
34
+ * processed as one whole unit — dom-path derivation, HTML BLOB fetch,
35
+ * and ref lookups all happen once per page. This design closes three
36
+ * separate correctness / efficiency traps that a purely image-chunked
37
+ * loop would expose:
38
+ *
39
+ * 1. **Ordinal-cursor bleed across chunks** — `matchImagesToDomPaths`
40
+ * tracks per-outerHTML cursor state, so a page with 1 000 identical
41
+ * `<img>` tags spanning two image-chunks would reset the cursor at
42
+ * the boundary and duplicate dom_paths already assigned in the
43
+ * previous chunk. Iterating by page keeps every page's cursor state
44
+ * self-contained.
45
+ * 2. **Repeated `getPageHtml` + jsdom parse** — a page whose images
46
+ * straddle N image-chunks would fetch and re-parse the same multi-MB
47
+ * HTML snapshot N times. Iterating by page makes this exactly once
48
+ * per page.
49
+ * 3. **Data-URI routing gap** — legacy `images.src` can be either a
50
+ * plain URL (goes to `url_refs`) or a large `data:` URI (goes to
51
+ * `blob_refs`, per the 512-byte threshold). The lookups partition
52
+ * by that rule, so a data URI is never bound into
53
+ * `resolveUrlRefs`' `WHERE url IN (?)` — which could otherwise
54
+ * exceed SQLite's `SQLITE_MAX_SQL_LENGTH` for multi-KB URIs.
55
+ *
56
+ * Per outer iteration:
57
+ *
58
+ * 1. **Keyset-paginate** distinct `pageId` values from `images`. Peak
59
+ * memory ≈ 500 integers.
60
+ * 2. **Fetch all images** for those page ids in one query, ordered by
61
+ * `(pageId, id)` so JS-side grouping is a linear pass.
62
+ * 3. **Resolve dom paths per page** — one `getPageHtml` + one resolver
63
+ * call each. `dom_path` texts are collected across the whole batch
64
+ * so `upsertTextRefs` runs once per batch.
65
+ * 4. **Partition src / currentSrc** by the data-URI routing rule and
66
+ * batch-resolve url refs, blob refs, and alt text refs across the
67
+ * whole batch.
68
+ * 5. **Bulk INSERT** with explicit `id = images.id`.
69
+ *
70
+ * `INSERT OR IGNORE` on the natural PK makes the step idempotent.
71
+ *
72
+ * HTML BLOBs are read inline via the same `trx` handle rather than
73
+ * through a caller-supplied `getPageHtml` callback: a callback that
74
+ * queried `Database.getHtmlOfPageById` (which uses the non-transactional
75
+ * Knex instance) would re-enter the pool from inside this writer
76
+ * transaction and deadlock on libsql's single writer connection. Reading
77
+ * `page_html_ref` + `page_html_blobs` via `trx` keeps every read on the
78
+ * same connection the outer trx already owns.
79
+ * @param trx - Knex instance or transaction connected to the archive DB.
80
+ * @param resolvePageDomPaths - Callback that returns dom_path strings
81
+ * for one page's images (see {@link PageDomPathResolver}).
82
+ * @param onProgress - Optional sink for periodic progress lines (one per
83
+ * ~5% of distinct `images.pageId` values scanned); see
84
+ * {@link ../create-progress-reporter.ts}.
85
+ * @example
86
+ * const jsdomResolver = createJsdomResolver();
87
+ * await knex.transaction(async (trx) => {
88
+ * await populateImageItems(trx, jsdomResolver);
89
+ * });
90
+ */
91
+ export declare function populateImageItems(trx: Knex, resolvePageDomPaths: PageDomPathResolver, onProgress?: ProgressCallback): Promise<void>;
@@ -0,0 +1,223 @@
1
+ import { createProgressReporter } from '../create-progress-reporter.js';
2
+ import { decodeStoredBlob } from '../decode-html-blob.js';
3
+ import { isBlobRefValue } from './is-blob-ref-value.js';
4
+ import { resolveBlobRefs } from './resolve-blob-refs.js';
5
+ import { resolveTextRefs } from './resolve-text-refs.js';
6
+ import { resolveUrlOrBlobFromMaps } from './resolve-url-or-blob-from-maps.js';
7
+ import { resolveUrlRefs } from './resolve-url-refs.js';
8
+ import { upsertTextRefs } from './upsert-text-refs.js';
9
+ /**
10
+ * Page ids scanned per keyset-paginated `SELECT DISTINCT pageId` chunk.
11
+ * Batches the outer loop so 470 K pages worth of `images` rows are
12
+ * traversed in ~1 K round trips instead of one round-trip per page,
13
+ * without holding the entire distinct-pageId list in memory at once.
14
+ */
15
+ const PAGE_ID_CHUNK_SIZE = 500;
16
+ /**
17
+ * Rows sent per `INSERT INTO image_items ... VALUES (...)` statement.
18
+ * Each row binds 13 params, so 300 rows = 3 900 params — safely under
19
+ * the SQLite variable limit.
20
+ */
21
+ const INSERT_CHUNK_SIZE = 300;
22
+ /**
23
+ * Populates `image_items` from `images` (issue #193).
24
+ *
25
+ * The outer loop iterates **pages**, not images. Each page's images are
26
+ * processed as one whole unit — dom-path derivation, HTML BLOB fetch,
27
+ * and ref lookups all happen once per page. This design closes three
28
+ * separate correctness / efficiency traps that a purely image-chunked
29
+ * loop would expose:
30
+ *
31
+ * 1. **Ordinal-cursor bleed across chunks** — `matchImagesToDomPaths`
32
+ * tracks per-outerHTML cursor state, so a page with 1 000 identical
33
+ * `<img>` tags spanning two image-chunks would reset the cursor at
34
+ * the boundary and duplicate dom_paths already assigned in the
35
+ * previous chunk. Iterating by page keeps every page's cursor state
36
+ * self-contained.
37
+ * 2. **Repeated `getPageHtml` + jsdom parse** — a page whose images
38
+ * straddle N image-chunks would fetch and re-parse the same multi-MB
39
+ * HTML snapshot N times. Iterating by page makes this exactly once
40
+ * per page.
41
+ * 3. **Data-URI routing gap** — legacy `images.src` can be either a
42
+ * plain URL (goes to `url_refs`) or a large `data:` URI (goes to
43
+ * `blob_refs`, per the 512-byte threshold). The lookups partition
44
+ * by that rule, so a data URI is never bound into
45
+ * `resolveUrlRefs`' `WHERE url IN (?)` — which could otherwise
46
+ * exceed SQLite's `SQLITE_MAX_SQL_LENGTH` for multi-KB URIs.
47
+ *
48
+ * Per outer iteration:
49
+ *
50
+ * 1. **Keyset-paginate** distinct `pageId` values from `images`. Peak
51
+ * memory ≈ 500 integers.
52
+ * 2. **Fetch all images** for those page ids in one query, ordered by
53
+ * `(pageId, id)` so JS-side grouping is a linear pass.
54
+ * 3. **Resolve dom paths per page** — one `getPageHtml` + one resolver
55
+ * call each. `dom_path` texts are collected across the whole batch
56
+ * so `upsertTextRefs` runs once per batch.
57
+ * 4. **Partition src / currentSrc** by the data-URI routing rule and
58
+ * batch-resolve url refs, blob refs, and alt text refs across the
59
+ * whole batch.
60
+ * 5. **Bulk INSERT** with explicit `id = images.id`.
61
+ *
62
+ * `INSERT OR IGNORE` on the natural PK makes the step idempotent.
63
+ *
64
+ * HTML BLOBs are read inline via the same `trx` handle rather than
65
+ * through a caller-supplied `getPageHtml` callback: a callback that
66
+ * queried `Database.getHtmlOfPageById` (which uses the non-transactional
67
+ * Knex instance) would re-enter the pool from inside this writer
68
+ * transaction and deadlock on libsql's single writer connection. Reading
69
+ * `page_html_ref` + `page_html_blobs` via `trx` keeps every read on the
70
+ * same connection the outer trx already owns.
71
+ * @param trx - Knex instance or transaction connected to the archive DB.
72
+ * @param resolvePageDomPaths - Callback that returns dom_path strings
73
+ * for one page's images (see {@link PageDomPathResolver}).
74
+ * @param onProgress - Optional sink for periodic progress lines (one per
75
+ * ~5% of distinct `images.pageId` values scanned); see
76
+ * {@link ../create-progress-reporter.ts}.
77
+ * @example
78
+ * const jsdomResolver = createJsdomResolver();
79
+ * await knex.transaction(async (trx) => {
80
+ * await populateImageItems(trx, jsdomResolver);
81
+ * });
82
+ */
83
+ export async function populateImageItems(trx, resolvePageDomPaths, onProgress) {
84
+ const countRows = await trx('images').countDistinct({ n: 'pageId' });
85
+ const totalRow = countRows[0];
86
+ const total = Number(totalRow?.n ?? 0);
87
+ const report = createProgressReporter('image_items (distinct pages)', total, onProgress);
88
+ let processedPages = 0;
89
+ let cursorPageId = 0;
90
+ while (true) {
91
+ const pageIdRows = await trx('images')
92
+ .distinct('pageId')
93
+ .where('pageId', '>', cursorPageId)
94
+ .orderBy('pageId', 'asc')
95
+ .limit(PAGE_ID_CHUNK_SIZE);
96
+ if (pageIdRows.length === 0) {
97
+ break;
98
+ }
99
+ const pageIds = pageIdRows.map((r) => r.pageId);
100
+ cursorPageId = pageIds.at(-1);
101
+ processedPages += pageIds.length;
102
+ report(processedPages);
103
+ const rows = await trx('images')
104
+ .select('id', 'pageId', 'src', 'currentSrc', 'alt', 'width', 'height', 'naturalWidth', 'naturalHeight', 'isLazy', 'viewportWidth', 'sourceCode')
105
+ .whereIn('pageId', pageIds)
106
+ .orderBy([
107
+ { column: 'pageId', order: 'asc' },
108
+ { column: 'id', order: 'asc' },
109
+ ]);
110
+ if (rows.length === 0) {
111
+ continue;
112
+ }
113
+ const byPage = new Map();
114
+ for (const row of rows) {
115
+ const bucket = byPage.get(row.pageId);
116
+ if (bucket === undefined) {
117
+ byPage.set(row.pageId, [row]);
118
+ }
119
+ else {
120
+ bucket.push(row);
121
+ }
122
+ }
123
+ const domPaths = new Map();
124
+ for (const [pageId, pageImages] of byPage) {
125
+ const html = await readPageHtmlInTrx(trx, pageId);
126
+ const resolved = await resolvePageDomPaths(pageId, html, pageImages);
127
+ for (const [imageId, entry] of resolved) {
128
+ domPaths.set(imageId, entry);
129
+ }
130
+ }
131
+ const urls = new Set();
132
+ const dataUris = new Set();
133
+ const alts = new Set();
134
+ const domPathTexts = new Set();
135
+ for (const row of rows) {
136
+ for (const value of [row.src, row.currentSrc]) {
137
+ if (typeof value !== 'string' || value === '') {
138
+ continue;
139
+ }
140
+ if (isBlobRefValue(value)) {
141
+ dataUris.add(value);
142
+ }
143
+ else {
144
+ urls.add(value);
145
+ }
146
+ }
147
+ if (typeof row.alt === 'string' && row.alt !== '') {
148
+ alts.add(row.alt);
149
+ }
150
+ const domPath = domPaths.get(row.id);
151
+ if (domPath !== undefined) {
152
+ domPathTexts.add(domPath.path);
153
+ }
154
+ }
155
+ const urlIds = await resolveUrlRefs(trx, urls);
156
+ const blobIds = await resolveBlobRefs(trx, dataUris);
157
+ const altIds = await resolveTextRefs(trx, alts);
158
+ // `dom_path` strings are synthesised right here and were not
159
+ // among the columns the `text_refs` populate scanned
160
+ // (`populate-ref-tables/populate-text-refs.ts`) — insert every distinct
161
+ // derived path (including `unknown/<id>` fallbacks) before
162
+ // resolving its id. The upsert is idempotent so re-runs across
163
+ // partial failures do not duplicate rows.
164
+ const domPathIds = await upsertTextRefs(trx, domPathTexts);
165
+ const inserts = [];
166
+ for (const row of rows) {
167
+ const domPath = domPaths.get(row.id);
168
+ if (domPath === undefined) {
169
+ throw new Error(`populateImageItems: dom_path not resolved for image id=${row.id}`);
170
+ }
171
+ const domPathId = domPathIds.get(domPath.path);
172
+ if (domPathId === undefined) {
173
+ throw new Error(`populateImageItems: text_refs.id not resolved for dom_path=${domPath.path} — the upsertTextRefs above must have inserted it`);
174
+ }
175
+ const srcSlot = resolveUrlOrBlobFromMaps(row.src, urlIds, blobIds);
176
+ const currentSrcSlot = resolveUrlOrBlobFromMaps(row.currentSrc, urlIds, blobIds);
177
+ inserts.push({
178
+ id: row.id,
179
+ page_id: row.pageId,
180
+ src_url_id: srcSlot.url,
181
+ current_src_url_id: currentSrcSlot.url,
182
+ src_blob_id: srcSlot.blob,
183
+ current_src_blob_id: currentSrcSlot.blob,
184
+ alt_text_id: typeof row.alt === 'string' && row.alt !== ''
185
+ ? (altIds.get(row.alt) ?? null)
186
+ : null,
187
+ width: row.width,
188
+ height: row.height,
189
+ natural_width: row.naturalWidth,
190
+ natural_height: row.naturalHeight,
191
+ is_lazy: row.isLazy == null ? null : row.isLazy ? 1 : 0,
192
+ viewport_width: row.viewportWidth,
193
+ dom_path_text_id: domPathId,
194
+ });
195
+ }
196
+ for (let index = 0; index < inserts.length; index += INSERT_CHUNK_SIZE) {
197
+ const chunk = inserts.slice(index, index + INSERT_CHUNK_SIZE);
198
+ await trx('image_items').insert(chunk).onConflict('id').ignore();
199
+ }
200
+ }
201
+ }
202
+ /**
203
+ * Reads the stored HTML snapshot for one legacy page id, via the same
204
+ * `trx` handle that the outer populate transaction owns. Mirrors
205
+ * `Database.getHtmlOfPageById` but keeps every read on the trx
206
+ * connection so the populate does not re-enter the pool from
207
+ * inside its own writer transaction (see {@link populateImageItems}'s
208
+ * doc for the deadlock this avoids).
209
+ * @param trx - The open migration transaction.
210
+ * @param pageId - `pages.id` (== `content_items.id`) to read HTML for.
211
+ * @returns Decoded HTML string, or `null` when no snapshot is stored.
212
+ */
213
+ async function readPageHtmlInTrx(trx, pageId) {
214
+ const row = (await trx('page_html_ref')
215
+ .join('page_html_blobs', 'page_html_ref.hash', '=', 'page_html_blobs.hash')
216
+ .select('page_html_blobs.body as body', 'page_html_blobs.codec as codec')
217
+ .where('page_html_ref.page_id', pageId)
218
+ .first());
219
+ if (!row) {
220
+ return null;
221
+ }
222
+ return decodeStoredBlob(row.body, row.codec);
223
+ }