@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
package/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # @nitpicker/crawler
2
2
 
3
- ヘッドレスブラウザによる Web クローラーエンジン。
3
+ ヘッドレスブラウザでWebサイトをクロールし、`.nitpicker` アーカイブを生成・更新する内部パッケージです。
4
4
 
5
- ## 概要
5
+ 通常は [@nitpicker/cli](../cli/README.md) の `crawl` コマンドから利用します。
6
6
 
7
- Puppeteer を使用して Web サイトをクロールし、各ページのメタデータ・リンク構造・ネットワークリソース・レンダリング後 HTML スナップショットを SQLite ベースのアーカイブ(`.nitpicker`)に保存します。
7
+ ## 関連リンク
8
8
 
9
- このパッケージは [Nitpicker](../../README.md) モノレポの内部パッケージです。単体での利用は想定していません。
9
+ - [Nitpicker README](../../../README.md)
10
+ - [CLI crawl docs](../cli/docs/crawl.md)
11
+ - [ARCHITECTURE.md](../../../ARCHITECTURE.md)
10
12
 
11
13
  ## ライセンス
12
14
 
@@ -39,7 +39,7 @@ export declare class ArchiveAccessor extends EventEmitter<DatabaseEvent> {
39
39
  * tmpDir), where touching the filesystem would race with — or destroy —
40
40
  * the live crawler's working state.
41
41
  *
42
- * Subclasses that own the archive's lifecycle (notably {@link Archive})
42
+ * Subclasses that own the archive's lifecycle (notably `Archive`)
43
43
  * override this to add write/cleanup steps.
44
44
  *
45
45
  * **Idempotent and concurrent-safe**: the first invocation captures the
@@ -171,7 +171,7 @@ export declare class ArchiveAccessor extends EventEmitter<DatabaseEvent> {
171
171
  * Retrieves a flat list of all resource URLs stored in the archive.
172
172
  * @returns An array of resource URL strings.
173
173
  */
174
- getResourceUrlList(): Promise<any[]>;
174
+ getResourceUrlList(): Promise<string[]>;
175
175
  /**
176
176
  * Retrieves the Wappalyzer tag entries for the given page, parsed back
177
177
  * from the `page_tags` table.
@@ -30,7 +30,7 @@ export class ArchiveAccessor extends EventEmitter {
30
30
  * Promise tracking an in-progress (or completed) close. `null` means the
31
31
  * accessor is open and idle; a settled promise means we are closed (the
32
32
  * accessor stays "closed" even if `db.destroy()` rejected, because there
33
- * is nothing safe to retry — see {@link close}).
33
+ * is nothing safe to retry — see {@link ArchiveAccessor.close}).
34
34
  */
35
35
  #closeOnce = null;
36
36
  /** The SQLite database instance for querying archived data. */
@@ -91,7 +91,7 @@ export class ArchiveAccessor extends EventEmitter {
91
91
  * tmpDir), where touching the filesystem would race with — or destroy —
92
92
  * the live crawler's working state.
93
93
  *
94
- * Subclasses that own the archive's lifecycle (notably {@link Archive})
94
+ * Subclasses that own the archive's lifecycle (notably `Archive`)
95
95
  * override this to add write/cleanup steps.
96
96
  *
97
97
  * **Idempotent and concurrent-safe**: the first invocation captures the
@@ -34,5 +34,12 @@ export declare class ArchiveLockError extends Error {
34
34
  * @param tmpDir - Absolute path to the archive's temporary working directory.
35
35
  * @returns A release function to be called when the work is done.
36
36
  * @throws {ArchiveLockError} When the lock cannot be acquired even after a stale-lock retry.
37
+ * @example
38
+ * const releaseLock = await acquireArchiveLock(tmpDir);
39
+ * try {
40
+ * // ... exclusive work against tmpDir ...
41
+ * } finally {
42
+ * await releaseLock();
43
+ * }
37
44
  */
38
45
  export declare function acquireArchiveLock(tmpDir: string): Promise<() => Promise<void>>;
@@ -42,6 +42,13 @@ export class ArchiveLockError extends Error {
42
42
  * @param tmpDir - Absolute path to the archive's temporary working directory.
43
43
  * @returns A release function to be called when the work is done.
44
44
  * @throws {ArchiveLockError} When the lock cannot be acquired even after a stale-lock retry.
45
+ * @example
46
+ * const releaseLock = await acquireArchiveLock(tmpDir);
47
+ * try {
48
+ * // ... exclusive work against tmpDir ...
49
+ * } finally {
50
+ * await releaseLock();
51
+ * }
45
52
  */
46
53
  export async function acquireArchiveLock(tmpDir) {
47
54
  const lockPath = `${tmpDir}.lock`;
@@ -1,4 +1,4 @@
1
- import type { Config, PageSource } from './types.js';
1
+ import type { Config, InventoryRunMeta, PageSource } from './types.js';
2
2
  import type { PageData, CrawlerError, Resource } from '../utils/types/types.js';
3
3
  import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
4
4
  import { ArchiveAccessor } from './archive-accessor.js';
@@ -14,6 +14,15 @@ import { ArchiveAccessor } from './archive-accessor.js';
14
14
  * Use the static factory methods ({@link Archive.create}, {@link Archive.open},
15
15
  * {@link Archive.resume}, {@link Archive.connect}) to obtain instances.
16
16
  * The constructor is private.
17
+ * @example
18
+ * const archive = await Archive.create({ filePath: '/path/to/site.nitpicker' });
19
+ * try {
20
+ * await archive.setConfig(config);
21
+ * const pageId = await archive.setPage(pageData);
22
+ * } finally {
23
+ * // Writes the `.nitpicker` tar (if absent), removes tmpDir, releases the lock.
24
+ * await archive.close();
25
+ * }
17
26
  */
18
27
  export default class Archive extends ArchiveAccessor {
19
28
  #private;
@@ -84,6 +93,16 @@ export default class Archive extends ArchiveAccessor {
84
93
  * @returns URLs already present in `resources`.
85
94
  */
86
95
  getExistingResourceUrls(urls: readonly string[]): Promise<string[]>;
96
+ /**
97
+ * Look up the `source` column of a single page row by its URL key. Thin
98
+ * facade over {@link Database.getPageSourceByUrl} — exposes the lookup
99
+ * to the orchestrator so it can inject a `PageSourceLookup` into the
100
+ * Crawler for sub-resource lineage propagation on `--resume` /
101
+ * `--retry-failed` sessions.
102
+ * @param url - URL key in `url.withoutHashAndAuth` form.
103
+ * @returns The recorded `source`, or `undefined` when no row exists.
104
+ */
105
+ getPageSourceByUrl(url: string): Promise<PageSource | undefined>;
87
106
  /**
88
107
  * Retrieves a single recorded sub-resource by its URL.
89
108
  * @param urls - URL candidates to match against the stored resource URL.
@@ -102,7 +121,61 @@ export default class Archive extends ArchiveAccessor {
102
121
  * Retrieves the base URL of the crawl session from the archive database.
103
122
  * @returns The base URL string.
104
123
  */
105
- getUrl(): Promise<any>;
124
+ getUrl(): Promise<string>;
125
+ /**
126
+ * Pre-insert inventory non-HTML URLs as `source='inventory-seed'`
127
+ * placeholders in the `resources` table — the non-HTML counterpart of
128
+ * {@link Archive.insertInventorySeeds}. Rows are committed in chunked
129
+ * bulk inserts (500 per round-trip) rather than per-URL awaits — a
130
+ * per-URL loop would keep a 50k-URL inventory list inside the `.bak`
131
+ * window for minutes instead of seconds.
132
+ *
133
+ * Thin facade over {@link Database.insertInventoryResources}.
134
+ * `ExURL.href` is the storage key for `resources.url` (matches what
135
+ * `insertResource` writes for the per-URL path); we normalise here so
136
+ * the orchestrator stays decoupled from the storage form.
137
+ * @param urls - Non-HTML inventory URLs to record. No-op when empty.
138
+ */
139
+ insertInventoryResources(urls: readonly ExURL[]): Promise<void>;
140
+ /**
141
+ * Pre-insert inventory HTML seeds as `scraped=0`, `source='inventory-seed'`
142
+ * placeholder pages so the URL is durably tracked in the archive **before**
143
+ * the scrape phase starts. Thin facade over
144
+ * {@link Database.insertInventorySeeds} — see that method's JSDoc for the
145
+ * Ctrl+C-tolerance rationale and the `getCrawlingState` interaction.
146
+ *
147
+ * `ExURL` inputs are normalised to `withoutHashAndAuth` here so the storage
148
+ * key matches what `resolveContentItemId` writes for crawled rows, keeping the
149
+ * crawled-wins downgrade and the existing-URL filter (`getExistingPageUrls`)
150
+ * lookups consistent.
151
+ * @param urls - HTML seed URLs to pre-insert. No-op when empty.
152
+ */
153
+ insertInventorySeeds(urls: readonly ExURL[]): Promise<void>;
154
+ /**
155
+ * Hostnames whose `crawl_errors` history is consistently DNS failures and
156
+ * for which no recent 2xx/3xx page or resource is recorded. Consumed by
157
+ * `CrawlerOrchestrator.#preloadDnsBurnedHostCache` to seed the DNS-burned
158
+ * host cache at re-open (append / inventory / retryFailed / resume), so
159
+ * the next crawl skips HEAD pre-flight on hosts the previous crawl
160
+ * already proved unreachable.
161
+ *
162
+ * Deliberately exposed only on `Archive` (writer-side) — read-only
163
+ * `ArchiveAccessor` (stub viewer) does not see this method so the
164
+ * stub's no-migration contract is preserved.
165
+ * @returns Lower-cased hostnames safe to short-circuit.
166
+ */
167
+ listDnsBurnedHostCandidates(): Promise<string[]>;
168
+ /**
169
+ * Appends one row to the `inventory_runs` audit log.
170
+ *
171
+ * Thin facade over {@link Database.recordInventoryRun} — keeps the
172
+ * orchestrator decoupled from the knex layer and gives a single
173
+ * write entry point that future Archive-level concerns (locking,
174
+ * mirror sync, etc.) can hook into without touching every caller.
175
+ * @param meta - The run metadata. Only `ran_at` is required.
176
+ * @returns The autoincremented `id` of the inserted row.
177
+ */
178
+ recordInventoryRun(meta: InventoryRunMeta): Promise<number>;
106
179
  /**
107
180
  * Releases the SQLite handle and the advisory lock **without** writing
108
181
  * the archive or removing `tmpDir`.
@@ -115,6 +188,24 @@ export default class Archive extends ArchiveAccessor {
115
188
  * are mutually exclusive (the first one called wins).
116
189
  */
117
190
  releaseHandle(): Promise<void>;
191
+ /**
192
+ * Replaces the archive's analysis violations with a fresh SQL-backed set.
193
+ *
194
+ * Thin facade over {@link Database.replaceAnalysisViolations}; kept on
195
+ * `Archive` so the analyze pipeline can persist violations without
196
+ * reaching into the low-level database class directly.
197
+ * @param violations - Flat analyze violations.
198
+ */
199
+ replaceAnalysisViolations(violations: readonly {
200
+ validator: string;
201
+ severity: string;
202
+ rule: string;
203
+ code?: string | null;
204
+ message: string;
205
+ url: string;
206
+ line?: number | null;
207
+ col?: number | null;
208
+ }[]): Promise<void>;
118
209
  /**
119
210
  * Promote previously-external pages that now fall under the (possibly extended)
120
211
  * scope back to a pending state so that the crawler re-scrapes them as fully
@@ -163,8 +254,13 @@ export default class Archive extends ArchiveAccessor {
163
254
  * destination edge is written, leaving the destination's stored title / meta /
164
255
  * anchors / images untouched.
165
256
  * @param pageInfo - The HEAD-resolved page data carrying the redirect chain.
257
+ * @param source - Inventory provenance for a brand-new destination row.
258
+ * Forwarded to `recordRedirect` so the destination's `source` (and the
259
+ * chain-intermediate `source` derived from it) lands on the inventory
260
+ * label instead of the DB DEFAULT `'crawled'` when the orchestrator is
261
+ * running an inventory pass. `undefined` keeps the DB DEFAULT.
166
262
  */
167
- setRedirect(pageInfo: PageData): Promise<void>;
263
+ setRedirect(pageInfo: PageData, source?: PageSource): Promise<void>;
168
264
  /**
169
265
  * Stores a sub-resource (CSS, JS, image, etc.) in the archive database.
170
266
  * @param resource - The resource data to store.
@@ -217,23 +313,89 @@ export default class Archive extends ArchiveAccessor {
217
313
  /** The prefix used for temporary working directories during archive operations. */
218
314
  static TMP_DIR_PREFIX: string;
219
315
  /**
220
- * Opens a read-only connection to an existing archive's database.
316
+ * Opens a connection to an existing archive's database, defaulting to
317
+ * read-only.
221
318
  *
222
- * Returns an {@link ArchiveAccessor} that provides query methods
223
- * without the ability to modify or write the archive. The DB is opened
224
- * in **read-only mode**: no schema migrations run, and the connection
319
+ * Returns an {@link ArchiveAccessor} that provides query methods. In the
320
+ * default read-only mode, no schema migrations run and the connection
225
321
  * refuses to resurrect a missing parent directory or db file (so a
226
322
  * TOCTOU window between source classification and this call cannot
227
- * silently produce an empty phantom tmpDir).
323
+ * silently produce an empty phantom tmpDir); the returned accessor is
324
+ * also marked read-only so consumer-facing helpers (e.g.
325
+ * {@link ArchiveAccessor.getHtmlOfPage}) avoid any filesystem mutation
326
+ * on the user's tmpDir.
228
327
  *
229
- * The returned accessor is also marked read-only so consumer-facing
230
- * helpers (e.g. {@link ArchiveAccessor.getHtmlOfPage}) avoid any
231
- * filesystem mutation on the user's tmpDir.
328
+ * `options.readOnly: false` is a narrow escape hatch for opening a
329
+ * second, writable connection to a `tmpDir` that {@link Archive.openCached}
330
+ * already extracted (and migrated) into an OS-temp cache directory —
331
+ * never the caller's live/interrupted crawl tmpDir, which must stay
332
+ * read-only. A read-only open (`Archive.openCached`/`ArchiveManager.open`)
333
+ * must never take this path itself — blocking or writing during what
334
+ * must be a read-only open is forbidden (issue #177). This escape
335
+ * hatch has no current production caller; any future
336
+ * one is responsible for its own cross-process coordination (see
337
+ * `acquireArchiveLock`) — this method does not acquire any lock itself.
232
338
  * @param tmpDir - The path to the temporary directory containing the database.
233
339
  * @param namespace - An optional namespace for scoping data access within the archive.
340
+ * @param options - Connection options.
341
+ * @param options.readOnly - Defaults to `true`. Pass `false` to obtain a
342
+ * writable accessor against an already-extracted cache directory.
234
343
  * @returns An ArchiveAccessor instance for querying the archive data.
235
- */
236
- static connect(tmpDir: string, namespace?: string | null): Promise<ArchiveAccessor>;
344
+ * @example
345
+ * // Default (read-only) safe for stub mode and cache reads:
346
+ * const accessor = await Archive.connect(tmpDir);
347
+ * @example
348
+ * // Writable escape hatch — only against a tar-cache extraction:
349
+ * const writable = await Archive.connect(cacheDir, null, { readOnly: false });
350
+ */
351
+ static connect(tmpDir: string, namespace?: string | null, options?: {
352
+ readOnly?: boolean;
353
+ }): Promise<ArchiveAccessor>;
354
+ /**
355
+ * Open a `.nitpicker` archive through the read-only tar cache.
356
+ *
357
+ * This is the fast path for read-only consumers (viewer, MCP, query
358
+ * CLI). It diverges from {@link Archive.open} in two important ways:
359
+ *
360
+ * 1. The extracted contents land in an OS-temp-scoped cache directory
361
+ * keyed by the archive's `size + mtime_ns + ctime_ns` (see
362
+ * {@link computeArchiveCacheKey}). Subsequent opens of the same
363
+ * unchanged archive skip the untar entirely. A fresh 10 GB archive
364
+ * pays the ~10 s untar cost once; reopens are instant.
365
+ * 2. The returned value is an {@link ArchiveAccessor} (read-only), not
366
+ * an `Archive` (writer). Closing it tears down the DB handle but
367
+ * leaves the cache directory in place for the next reader. The
368
+ * OS's own temp-directory cleanup (macOS reboot, Linux
369
+ * `systemd-tmpfiles`, Windows Disk Cleanup) reclaims stale
370
+ * entries — we do not own eviction.
371
+ *
372
+ * Migrations: the writer-side migration stack
373
+ * (`initSchema` / `migrate*`) runs once at cache-miss extraction, so
374
+ * the cache directory always lands on the current schema before the
375
+ * read-only re-open. Cache hits then skip migrations entirely.
376
+ *
377
+ * Override the cache location with `NITPICKER_TAR_CACHE_DIR`. The
378
+ * disable switch (`NITPICKER_DISABLE_TAR_CACHE=1`) is honoured by
379
+ * the caller (`ArchiveManager.open` falls back to {@link Archive.open}
380
+ * in that case); this function itself always goes through the cache.
381
+ *
382
+ * Writer entry points (`crawl --append`, `crawl --retry-failed`) must
383
+ * NOT use this path — they need the lock + write-back semantics of
384
+ * {@link Archive.open}.
385
+ * @param filePath - Absolute path to the `.nitpicker` file.
386
+ * @param namespace - Optional namespace forwarded to {@link ArchiveAccessor}.
387
+ * @returns A read-only {@link ArchiveAccessor} backed by the cache directory.
388
+ * @example
389
+ * ```ts
390
+ * const accessor = await Archive.openCached('/path/to/site.nitpicker');
391
+ * try {
392
+ * const summary = await getSummary(accessor);
393
+ * } finally {
394
+ * await accessor.close(); // tears down DB handle, cacheDir persists.
395
+ * }
396
+ * ```
397
+ */
398
+ static openCached(filePath: string, namespace?: string | null): Promise<ArchiveAccessor>;
237
399
  /**
238
400
  * Creates a new archive at the specified file path.
239
401
  * Initializes a temporary working directory and a fresh SQLite database.
@@ -1,6 +1,10 @@
1
1
  import path from 'node:path';
2
2
  import { ArchiveAccessor } from './archive-accessor.js';
3
3
  import { acquireArchiveLock } from './archive-lock.js';
4
+ import { computeArchiveCacheKey } from './cache/compute-archive-cache-key.js';
5
+ import { extractArchiveToCache } from './cache/extract-archive-to-cache.js';
6
+ import { getArchiveCacheRoot } from './cache/get-archive-cache-root.js';
7
+ import { resolveArchiveCacheDir } from './cache/resolve-archive-cache-dir.js';
4
8
  import { Database } from './database.js';
5
9
  import { dbLog, log, saveLog } from './debug.js';
6
10
  import { appendText } from './filesystem/append-text.js';
@@ -23,6 +27,15 @@ import { untar } from './filesystem/untar.js';
23
27
  * Use the static factory methods ({@link Archive.create}, {@link Archive.open},
24
28
  * {@link Archive.resume}, {@link Archive.connect}) to obtain instances.
25
29
  * The constructor is private.
30
+ * @example
31
+ * const archive = await Archive.create({ filePath: '/path/to/site.nitpicker' });
32
+ * try {
33
+ * await archive.setConfig(config);
34
+ * const pageId = await archive.setPage(pageData);
35
+ * } finally {
36
+ * // Writes the `.nitpicker` tar (if absent), removes tmpDir, releases the lock.
37
+ * await archive.close();
38
+ * }
26
39
  */
27
40
  export default class Archive extends ArchiveAccessor {
28
41
  /**
@@ -136,6 +149,18 @@ export default class Archive extends ArchiveAccessor {
136
149
  async getExistingResourceUrls(urls) {
137
150
  return this.#db.getExistingResourceUrls(urls);
138
151
  }
152
+ /**
153
+ * Look up the `source` column of a single page row by its URL key. Thin
154
+ * facade over {@link Database.getPageSourceByUrl} — exposes the lookup
155
+ * to the orchestrator so it can inject a `PageSourceLookup` into the
156
+ * Crawler for sub-resource lineage propagation on `--resume` /
157
+ * `--retry-failed` sessions.
158
+ * @param url - URL key in `url.withoutHashAndAuth` form.
159
+ * @returns The recorded `source`, or `undefined` when no row exists.
160
+ */
161
+ async getPageSourceByUrl(url) {
162
+ return this.#db.getPageSourceByUrl(url);
163
+ }
139
164
  /**
140
165
  * Retrieves a single recorded sub-resource by its URL.
141
166
  * @param urls - URL candidates to match against the stored resource URL.
@@ -161,6 +186,77 @@ export default class Archive extends ArchiveAccessor {
161
186
  async getUrl() {
162
187
  return this.#db.getBaseUrl();
163
188
  }
189
+ /**
190
+ * Pre-insert inventory non-HTML URLs as `source='inventory-seed'`
191
+ * placeholders in the `resources` table — the non-HTML counterpart of
192
+ * {@link Archive.insertInventorySeeds}. Rows are committed in chunked
193
+ * bulk inserts (500 per round-trip) rather than per-URL awaits — a
194
+ * per-URL loop would keep a 50k-URL inventory list inside the `.bak`
195
+ * window for minutes instead of seconds.
196
+ *
197
+ * Thin facade over {@link Database.insertInventoryResources}.
198
+ * `ExURL.href` is the storage key for `resources.url` (matches what
199
+ * `insertResource` writes for the per-URL path); we normalise here so
200
+ * the orchestrator stays decoupled from the storage form.
201
+ * @param urls - Non-HTML inventory URLs to record. No-op when empty.
202
+ */
203
+ async insertInventoryResources(urls) {
204
+ if (urls.length === 0) {
205
+ return;
206
+ }
207
+ dbLog('Insert inventory resources: %d URL(s)', urls.length);
208
+ await this.#db.insertInventoryResources(urls.map((u) => u.href));
209
+ }
210
+ /**
211
+ * Pre-insert inventory HTML seeds as `scraped=0`, `source='inventory-seed'`
212
+ * placeholder pages so the URL is durably tracked in the archive **before**
213
+ * the scrape phase starts. Thin facade over
214
+ * {@link Database.insertInventorySeeds} — see that method's JSDoc for the
215
+ * Ctrl+C-tolerance rationale and the `getCrawlingState` interaction.
216
+ *
217
+ * `ExURL` inputs are normalised to `withoutHashAndAuth` here so the storage
218
+ * key matches what `resolveContentItemId` writes for crawled rows, keeping the
219
+ * crawled-wins downgrade and the existing-URL filter (`getExistingPageUrls`)
220
+ * lookups consistent.
221
+ * @param urls - HTML seed URLs to pre-insert. No-op when empty.
222
+ */
223
+ async insertInventorySeeds(urls) {
224
+ if (urls.length === 0) {
225
+ return;
226
+ }
227
+ dbLog('Insert inventory seeds: %d URL(s)', urls.length);
228
+ await this.#db.insertInventorySeeds(urls.map((u) => u.withoutHashAndAuth));
229
+ }
230
+ /**
231
+ * Hostnames whose `crawl_errors` history is consistently DNS failures and
232
+ * for which no recent 2xx/3xx page or resource is recorded. Consumed by
233
+ * `CrawlerOrchestrator.#preloadDnsBurnedHostCache` to seed the DNS-burned
234
+ * host cache at re-open (append / inventory / retryFailed / resume), so
235
+ * the next crawl skips HEAD pre-flight on hosts the previous crawl
236
+ * already proved unreachable.
237
+ *
238
+ * Deliberately exposed only on `Archive` (writer-side) — read-only
239
+ * `ArchiveAccessor` (stub viewer) does not see this method so the
240
+ * stub's no-migration contract is preserved.
241
+ * @returns Lower-cased hostnames safe to short-circuit.
242
+ */
243
+ async listDnsBurnedHostCandidates() {
244
+ return this.#db.listDnsBurnedHostCandidates();
245
+ }
246
+ /**
247
+ * Appends one row to the `inventory_runs` audit log.
248
+ *
249
+ * Thin facade over {@link Database.recordInventoryRun} — keeps the
250
+ * orchestrator decoupled from the knex layer and gives a single
251
+ * write entry point that future Archive-level concerns (locking,
252
+ * mirror sync, etc.) can hook into without touching every caller.
253
+ * @param meta - The run metadata. Only `ran_at` is required.
254
+ * @returns The autoincremented `id` of the inserted row.
255
+ */
256
+ async recordInventoryRun(meta) {
257
+ dbLog('Record inventory run: %s', meta.list_label ?? meta.ran_at);
258
+ return await this.#db.recordInventoryRun(meta);
259
+ }
164
260
  /**
165
261
  * Releases the SQLite handle and the advisory lock **without** writing
166
262
  * the archive or removing `tmpDir`.
@@ -179,6 +275,17 @@ export default class Archive extends ArchiveAccessor {
179
275
  this.#closeOnce = this.#runReleaseHandle();
180
276
  return this.#closeOnce;
181
277
  }
278
+ /**
279
+ * Replaces the archive's analysis violations with a fresh SQL-backed set.
280
+ *
281
+ * Thin facade over {@link Database.replaceAnalysisViolations}; kept on
282
+ * `Archive` so the analyze pipeline can persist violations without
283
+ * reaching into the low-level database class directly.
284
+ * @param violations - Flat analyze violations.
285
+ */
286
+ async replaceAnalysisViolations(violations) {
287
+ await this.#db.replaceAnalysisViolations(violations);
288
+ }
182
289
  /**
183
290
  * Promote previously-external pages that now fall under the (possibly extended)
184
291
  * scope back to a pending state so that the crawler re-scrapes them as fully
@@ -242,10 +349,15 @@ export default class Archive extends ArchiveAccessor {
242
349
  * destination edge is written, leaving the destination's stored title / meta /
243
350
  * anchors / images untouched.
244
351
  * @param pageInfo - The HEAD-resolved page data carrying the redirect chain.
352
+ * @param source - Inventory provenance for a brand-new destination row.
353
+ * Forwarded to `recordRedirect` so the destination's `source` (and the
354
+ * chain-intermediate `source` derived from it) lands on the inventory
355
+ * label instead of the DB DEFAULT `'crawled'` when the orchestrator is
356
+ * running an inventory pass. `undefined` keeps the DB DEFAULT.
245
357
  */
246
- async setRedirect(pageInfo) {
358
+ async setRedirect(pageInfo, source) {
247
359
  dbLog('Set redirect: %s', pageInfo.url.href);
248
- await this.#db.recordRedirect(pageInfo);
360
+ await this.#db.recordRedirect(pageInfo, source);
249
361
  }
250
362
  /**
251
363
  * Stores a sub-resource (CSS, JS, image, etc.) in the archive database.
@@ -359,27 +471,99 @@ export default class Archive extends ArchiveAccessor {
359
471
  /** The prefix used for temporary working directories during archive operations. */
360
472
  static TMP_DIR_PREFIX = '._nitpicker-';
361
473
  /**
362
- * Opens a read-only connection to an existing archive's database.
474
+ * Opens a connection to an existing archive's database, defaulting to
475
+ * read-only.
363
476
  *
364
- * Returns an {@link ArchiveAccessor} that provides query methods
365
- * without the ability to modify or write the archive. The DB is opened
366
- * in **read-only mode**: no schema migrations run, and the connection
477
+ * Returns an {@link ArchiveAccessor} that provides query methods. In the
478
+ * default read-only mode, no schema migrations run and the connection
367
479
  * refuses to resurrect a missing parent directory or db file (so a
368
480
  * TOCTOU window between source classification and this call cannot
369
- * silently produce an empty phantom tmpDir).
481
+ * silently produce an empty phantom tmpDir); the returned accessor is
482
+ * also marked read-only so consumer-facing helpers (e.g.
483
+ * {@link ArchiveAccessor.getHtmlOfPage}) avoid any filesystem mutation
484
+ * on the user's tmpDir.
370
485
  *
371
- * The returned accessor is also marked read-only so consumer-facing
372
- * helpers (e.g. {@link ArchiveAccessor.getHtmlOfPage}) avoid any
373
- * filesystem mutation on the user's tmpDir.
486
+ * `options.readOnly: false` is a narrow escape hatch for opening a
487
+ * second, writable connection to a `tmpDir` that {@link Archive.openCached}
488
+ * already extracted (and migrated) into an OS-temp cache directory —
489
+ * never the caller's live/interrupted crawl tmpDir, which must stay
490
+ * read-only. A read-only open (`Archive.openCached`/`ArchiveManager.open`)
491
+ * must never take this path itself — blocking or writing during what
492
+ * must be a read-only open is forbidden (issue #177). This escape
493
+ * hatch has no current production caller; any future
494
+ * one is responsible for its own cross-process coordination (see
495
+ * `acquireArchiveLock`) — this method does not acquire any lock itself.
374
496
  * @param tmpDir - The path to the temporary directory containing the database.
375
497
  * @param namespace - An optional namespace for scoping data access within the archive.
498
+ * @param options - Connection options.
499
+ * @param options.readOnly - Defaults to `true`. Pass `false` to obtain a
500
+ * writable accessor against an already-extracted cache directory.
376
501
  * @returns An ArchiveAccessor instance for querying the archive data.
377
- */
378
- static async connect(tmpDir, namespace = null) {
379
- const db = await Archive.#connectDB(tmpDir, { readOnly: true });
380
- const archive = new ArchiveAccessor(tmpDir, db, namespace, { readOnly: true });
502
+ * @example
503
+ * // Default (read-only) safe for stub mode and cache reads:
504
+ * const accessor = await Archive.connect(tmpDir);
505
+ * @example
506
+ * // Writable escape hatch — only against a tar-cache extraction:
507
+ * const writable = await Archive.connect(cacheDir, null, { readOnly: false });
508
+ */
509
+ static async connect(tmpDir, namespace = null, options = {}) {
510
+ const readOnly = options.readOnly ?? true;
511
+ const db = await Archive.#connectDB(tmpDir, { readOnly });
512
+ const archive = new ArchiveAccessor(tmpDir, db, namespace, { readOnly });
381
513
  return archive;
382
514
  }
515
+ /**
516
+ * Open a `.nitpicker` archive through the read-only tar cache.
517
+ *
518
+ * This is the fast path for read-only consumers (viewer, MCP, query
519
+ * CLI). It diverges from {@link Archive.open} in two important ways:
520
+ *
521
+ * 1. The extracted contents land in an OS-temp-scoped cache directory
522
+ * keyed by the archive's `size + mtime_ns + ctime_ns` (see
523
+ * {@link computeArchiveCacheKey}). Subsequent opens of the same
524
+ * unchanged archive skip the untar entirely. A fresh 10 GB archive
525
+ * pays the ~10 s untar cost once; reopens are instant.
526
+ * 2. The returned value is an {@link ArchiveAccessor} (read-only), not
527
+ * an `Archive` (writer). Closing it tears down the DB handle but
528
+ * leaves the cache directory in place for the next reader. The
529
+ * OS's own temp-directory cleanup (macOS reboot, Linux
530
+ * `systemd-tmpfiles`, Windows Disk Cleanup) reclaims stale
531
+ * entries — we do not own eviction.
532
+ *
533
+ * Migrations: the writer-side migration stack
534
+ * (`initSchema` / `migrate*`) runs once at cache-miss extraction, so
535
+ * the cache directory always lands on the current schema before the
536
+ * read-only re-open. Cache hits then skip migrations entirely.
537
+ *
538
+ * Override the cache location with `NITPICKER_TAR_CACHE_DIR`. The
539
+ * disable switch (`NITPICKER_DISABLE_TAR_CACHE=1`) is honoured by
540
+ * the caller (`ArchiveManager.open` falls back to {@link Archive.open}
541
+ * in that case); this function itself always goes through the cache.
542
+ *
543
+ * Writer entry points (`crawl --append`, `crawl --retry-failed`) must
544
+ * NOT use this path — they need the lock + write-back semantics of
545
+ * {@link Archive.open}.
546
+ * @param filePath - Absolute path to the `.nitpicker` file.
547
+ * @param namespace - Optional namespace forwarded to {@link ArchiveAccessor}.
548
+ * @returns A read-only {@link ArchiveAccessor} backed by the cache directory.
549
+ * @example
550
+ * ```ts
551
+ * const accessor = await Archive.openCached('/path/to/site.nitpicker');
552
+ * try {
553
+ * const summary = await getSummary(accessor);
554
+ * } finally {
555
+ * await accessor.close(); // tears down DB handle, cacheDir persists.
556
+ * }
557
+ * ```
558
+ */
559
+ static async openCached(filePath, namespace = null) {
560
+ const cacheRoot = getArchiveCacheRoot();
561
+ const cacheKey = await computeArchiveCacheKey(filePath);
562
+ const cacheDir = resolveArchiveCacheDir(cacheRoot, cacheKey, filePath);
563
+ log('Open cached: %s (cacheDir=%s)', filePath, cacheDir);
564
+ await extractArchiveToCache(filePath, cacheRoot, cacheDir, cacheKey);
565
+ return await Archive.connect(cacheDir, namespace);
566
+ }
383
567
  /**
384
568
  * Creates a new archive at the specified file path.
385
569
  * Initializes a temporary working directory and a fresh SQLite database.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Derive a stable cache key from a `.nitpicker` file's inode metadata
3
+ * plus a head+tail content sample.
4
+ *
5
+ * Metadata fields:
6
+ *
7
+ * - `size` covers the bulk of accidental cache collision risk in O(1).
8
+ * - `mtime` (mod time) changes whenever the file's content is rewritten,
9
+ * which is the common case (`crawl --append`, `crawl --retry-failed`,
10
+ * `cp -f`, rsync).
11
+ * - `ctime` (inode change time) closes the `touch -m -t <past>` loophole:
12
+ * even if a user resets `mtime` to fake "unchanged", the act of touching
13
+ * bumps `ctime` on POSIX so the key still rolls.
14
+ *
15
+ * Head + tail digest:
16
+ *
17
+ * - On filesystems with low-resolution timestamps (FAT / exFAT / NFSv3 /
18
+ * some Docker volume mounts where mtime/ctime are second-granular and
19
+ * sometimes don't move on small appends), `size + mtime + ctime`
20
+ * alone can stay identical across an in-place rewrite.
21
+ * - We hash the first {@link HEAD_TAIL_HASH_BYTES} bytes and the last
22
+ * {@link HEAD_TAIL_HASH_BYTES} bytes to detect this. For tar archives
23
+ * the head holds the first entry's header (which moves when the inner
24
+ * directory's name changes) and the tail holds the last entry's data
25
+ * (which always moves on `crawl --append` because the appended pages
26
+ * land near the end of the tar stream).
27
+ * - Full-content sha256 was rejected: ~20-30 s on a 10 GB archive,
28
+ * which is slower than the untar this cache is meant to avoid.
29
+ *
30
+ * The key is stable across symlinks (the caller resolves via
31
+ * `fs.realpath` upstream) but **not** across hardlinks pointing at a
32
+ * mutated inode — that is intentional, hardlinking is a power-user
33
+ * move and the user is expected to know the cache will share an entry.
34
+ * @param archivePath - Absolute path to the `.nitpicker` file.
35
+ * @returns A string of the form
36
+ * `<size>-<mtime_ns>-<ctime_ns>-<headHex>-<tailHex>` suitable for use
37
+ * as a directory-name component.
38
+ */
39
+ export declare function computeArchiveCacheKey(archivePath: string): Promise<string>;