@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.
- package/README.md +6 -4
- package/lib/archive/archive-accessor.d.ts +2 -2
- package/lib/archive/archive-accessor.js +2 -2
- package/lib/archive/archive-lock.d.ts +7 -0
- package/lib/archive/archive-lock.js +7 -0
- package/lib/archive/archive.d.ts +175 -13
- package/lib/archive/archive.js +198 -14
- package/lib/archive/cache/compute-archive-cache-key.d.ts +39 -0
- package/lib/archive/cache/compute-archive-cache-key.js +95 -0
- package/lib/archive/cache/extract-archive-to-cache.d.ts +43 -0
- package/lib/archive/cache/extract-archive-to-cache.js +309 -0
- package/lib/archive/cache/get-archive-cache-root.d.ts +20 -0
- package/lib/archive/cache/get-archive-cache-root.js +53 -0
- package/lib/archive/cache/is-archive-cache-disabled.d.ts +24 -0
- package/lib/archive/cache/is-archive-cache-disabled.js +34 -0
- package/lib/archive/cache/resolve-archive-cache-dir.d.ts +26 -0
- package/lib/archive/cache/resolve-archive-cache-dir.js +32 -0
- package/lib/archive/create-adjunct-tables.d.ts +43 -0
- package/lib/archive/create-adjunct-tables.js +213 -0
- package/lib/archive/create-entity-tables.d.ts +173 -0
- package/lib/archive/create-entity-tables.js +318 -0
- package/lib/archive/create-progress-reporter.d.ts +30 -0
- package/lib/archive/create-progress-reporter.js +38 -0
- package/lib/archive/create-ref-tables.d.ts +35 -0
- package/lib/archive/create-ref-tables.js +188 -0
- package/lib/archive/database.d.ts +130 -182
- package/lib/archive/database.js +524 -1777
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.d.ts +27 -0
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +34 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.d.ts +17 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.js +26 -0
- package/lib/archive/db-ops/_shared/decode-json-ref.d.ts +17 -0
- package/lib/archive/db-ops/_shared/decode-json-ref.js +31 -0
- package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.d.ts +20 -0
- package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.js +53 -0
- package/lib/archive/db-ops/_shared/resolve-content-item-id.d.ts +61 -0
- package/lib/archive/db-ops/_shared/resolve-content-item-id.js +111 -0
- package/lib/archive/db-ops/_shared/resolve-url-or-blob.d.ts +23 -0
- package/lib/archive/db-ops/_shared/resolve-url-or-blob.js +29 -0
- package/lib/archive/db-ops/_shared/retry-setting.d.ts +16 -0
- package/lib/archive/db-ops/_shared/retry-setting.js +18 -0
- package/lib/archive/db-ops/_shared/safe-parse-json.d.ts +11 -0
- package/lib/archive/db-ops/_shared/safe-parse-json.js +18 -0
- package/lib/archive/db-ops/_shared/types.d.ts +53 -0
- package/lib/archive/db-ops/_shared/types.js +1 -0
- package/lib/archive/db-ops/_shared/upsert-blob-ref.d.ts +25 -0
- package/lib/archive/db-ops/_shared/upsert-blob-ref.js +48 -0
- package/lib/archive/db-ops/_shared/upsert-content-type-ref.d.ts +30 -0
- package/lib/archive/db-ops/_shared/upsert-content-type-ref.js +45 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.d.ts +22 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.js +41 -0
- package/lib/archive/db-ops/_shared/upsert-response-headers.d.ts +35 -0
- package/lib/archive/db-ops/_shared/upsert-response-headers.js +49 -0
- package/lib/archive/db-ops/_shared/upsert-url-ref.d.ts +39 -0
- package/lib/archive/db-ops/_shared/upsert-url-ref.js +62 -0
- package/lib/archive/db-ops/analysis/replace-analysis-violations.d.ts +28 -0
- package/lib/archive/db-ops/analysis/replace-analysis-violations.js +152 -0
- package/lib/archive/db-ops/anchors/get-anchors-on-page.d.ts +10 -0
- package/lib/archive/db-ops/anchors/get-anchors-on-page.js +21 -0
- package/lib/archive/db-ops/config/get-base-url.d.ts +8 -0
- package/lib/archive/db-ops/config/get-base-url.js +14 -0
- package/lib/archive/db-ops/config/get-config.d.ts +10 -0
- package/lib/archive/db-ops/config/get-config.js +27 -0
- package/lib/archive/db-ops/config/get-name.d.ts +8 -0
- package/lib/archive/db-ops/config/get-name.js +14 -0
- package/lib/archive/db-ops/config/info-column-allowlist.d.ts +7 -0
- package/lib/archive/db-ops/config/info-column-allowlist.js +26 -0
- package/lib/archive/db-ops/config/info-json-columns.d.ts +5 -0
- package/lib/archive/db-ops/config/info-json-columns.js +10 -0
- package/lib/archive/db-ops/config/set-config.d.ts +12 -0
- package/lib/archive/db-ops/config/set-config.js +21 -0
- package/lib/archive/db-ops/config/update-config.d.ts +17 -0
- package/lib/archive/db-ops/config/update-config.js +36 -0
- package/lib/archive/db-ops/errors/insert-crawl-error.d.ts +15 -0
- package/lib/archive/db-ops/errors/insert-crawl-error.js +21 -0
- package/lib/archive/db-ops/errors/insert-page-error.d.ts +21 -0
- package/lib/archive/db-ops/errors/insert-page-error.js +28 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +22 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +141 -0
- package/lib/archive/db-ops/html/get-html-of-page-by-id.d.ts +18 -0
- package/lib/archive/db-ops/html/get-html-of-page-by-id.js +29 -0
- package/lib/archive/db-ops/inventory/record-inventory-run.d.ts +21 -0
- package/lib/archive/db-ops/inventory/record-inventory-run.js +38 -0
- package/lib/archive/db-ops/lifecycle/checkpoint.d.ts +8 -0
- package/lib/archive/db-ops/lifecycle/checkpoint.js +9 -0
- package/lib/archive/db-ops/lifecycle/destroy.d.ts +6 -0
- package/lib/archive/db-ops/lifecycle/destroy.js +7 -0
- package/lib/archive/db-ops/lifecycle/init.d.ts +22 -0
- package/lib/archive/db-ops/lifecycle/init.js +42 -0
- package/lib/archive/db-ops/meta/get-jsonld-of-page.d.ts +13 -0
- package/lib/archive/db-ops/meta/get-jsonld-of-page.js +27 -0
- package/lib/archive/db-ops/meta/get-tags-of-page.d.ts +12 -0
- package/lib/archive/db-ops/meta/get-tags-of-page.js +28 -0
- package/lib/archive/db-ops/pages/order/set-url-order.d.ts +8 -0
- package/lib/archive/db-ops/pages/order/set-url-order.js +32 -0
- package/lib/archive/db-ops/pages/read/build-page-query.d.ts +18 -0
- package/lib/archive/db-ops/pages/read/build-page-query.js +40 -0
- package/lib/archive/db-ops/pages/read/get-crawling-state.d.ts +70 -0
- package/lib/archive/db-ops/pages/read/get-crawling-state.js +98 -0
- package/lib/archive/db-ops/pages/read/get-existing-page-urls.d.ts +15 -0
- package/lib/archive/db-ops/pages/read/get-existing-page-urls.js +30 -0
- package/lib/archive/db-ops/pages/read/get-page-count.d.ts +12 -0
- package/lib/archive/db-ops/pages/read/get-page-count.js +21 -0
- package/lib/archive/db-ops/pages/read/get-page-source-by-url.d.ts +24 -0
- package/lib/archive/db-ops/pages/read/get-page-source-by-url.js +28 -0
- package/lib/archive/db-ops/pages/read/get-pages-with-rels.d.ts +38 -0
- package/lib/archive/db-ops/pages/read/get-pages-with-rels.js +107 -0
- package/lib/archive/db-ops/pages/read/get-pages.d.ts +11 -0
- package/lib/archive/db-ops/pages/read/get-pages.js +51 -0
- package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.d.ts +18 -0
- package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.js +25 -0
- package/lib/archive/db-ops/pages/read/reconstruct-page-rows.d.ts +31 -0
- package/lib/archive/db-ops/pages/read/reconstruct-page-rows.js +32 -0
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.d.ts +24 -0
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +93 -0
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +47 -0
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +124 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-seeds.d.ts +37 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-seeds.js +72 -0
- package/lib/archive/db-ops/pages/write/insert-jsonld.d.ts +17 -0
- package/lib/archive/db-ops/pages/write/insert-jsonld.js +49 -0
- package/lib/archive/db-ops/pages/write/insert-page.d.ts +36 -0
- package/lib/archive/db-ops/pages/write/insert-page.js +208 -0
- package/lib/archive/db-ops/pages/write/insert-tags.d.ts +16 -0
- package/lib/archive/db-ops/pages/write/insert-tags.js +34 -0
- package/lib/archive/db-ops/pages/write/link-redirect-sources.d.ts +36 -0
- package/lib/archive/db-ops/pages/write/link-redirect-sources.js +93 -0
- package/lib/archive/db-ops/pages/write/record-redirect.d.ts +35 -0
- package/lib/archive/db-ops/pages/write/record-redirect.js +100 -0
- package/lib/archive/db-ops/pages/write/set-skipped-page.d.ts +13 -0
- package/lib/archive/db-ops/pages/write/set-skipped-page.js +22 -0
- package/lib/archive/db-ops/pages/write/update-page.d.ts +29 -0
- package/lib/archive/db-ops/pages/write/update-page.js +334 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.d.ts +19 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.js +41 -0
- package/lib/archive/db-ops/referrers/get-redirects-for-pages.d.ts +9 -0
- package/lib/archive/db-ops/referrers/get-redirects-for-pages.js +15 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-page.d.ts +17 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-page.js +32 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-resource.d.ts +8 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-resource.js +15 -0
- package/lib/archive/db-ops/resources/build-resource-query.d.ts +25 -0
- package/lib/archive/db-ops/resources/build-resource-query.js +29 -0
- package/lib/archive/db-ops/resources/get-existing-resource-urls.d.ts +9 -0
- package/lib/archive/db-ops/resources/get-existing-resource-urls.js +24 -0
- package/lib/archive/db-ops/resources/get-resource-by-url.d.ts +13 -0
- package/lib/archive/db-ops/resources/get-resource-by-url.js +22 -0
- package/lib/archive/db-ops/resources/get-resource-url-list.d.ts +9 -0
- package/lib/archive/db-ops/resources/get-resource-url-list.js +13 -0
- package/lib/archive/db-ops/resources/get-resources.d.ts +8 -0
- package/lib/archive/db-ops/resources/get-resources.js +11 -0
- package/lib/archive/db-ops/resources/insert-inventory-resources.d.ts +24 -0
- package/lib/archive/db-ops/resources/insert-inventory-resources.js +64 -0
- package/lib/archive/db-ops/resources/insert-resource-referrers.d.ts +15 -0
- package/lib/archive/db-ops/resources/insert-resource-referrers.js +54 -0
- package/lib/archive/db-ops/resources/insert-resource.d.ts +34 -0
- package/lib/archive/db-ops/resources/insert-resource.js +73 -0
- package/lib/archive/db-ops/resources/reconstruct-resource-rows.d.ts +26 -0
- package/lib/archive/db-ops/resources/reconstruct-resource-rows.js +30 -0
- package/lib/archive/decode-html-blob.d.ts +18 -0
- package/lib/archive/decode-html-blob.js +31 -0
- package/lib/archive/derive-lineage-from-parent.d.ts +37 -0
- package/lib/archive/derive-lineage-from-parent.js +42 -0
- package/lib/archive/drop-legacy-tables.d.ts +45 -0
- package/lib/archive/drop-legacy-tables.js +56 -0
- package/lib/archive/filesystem/rename.js +1 -1
- package/lib/archive/get-failed-page-messages.d.ts +44 -0
- package/lib/archive/get-failed-page-messages.js +132 -0
- package/lib/archive/init-schema.d.ts +35 -39
- package/lib/archive/init-schema.js +111 -320
- package/lib/archive/is-inventory-source.d.ts +21 -0
- package/lib/archive/is-inventory-source.js +22 -0
- package/lib/archive/limited-page-ids.d.ts +2 -1
- package/lib/archive/limited-page-ids.js +5 -4
- package/lib/archive/meta/assert-compatible-version.d.ts +24 -3
- package/lib/archive/meta/assert-compatible-version.js +24 -3
- package/lib/archive/meta/types.d.ts +87 -1
- package/lib/archive/meta/types.js +34 -2
- package/lib/archive/migrate-entity-tables.d.ts +45 -0
- package/lib/archive/migrate-entity-tables.js +56 -0
- package/lib/archive/migrate-ref-tables.d.ts +25 -0
- package/lib/archive/migrate-ref-tables.js +38 -0
- package/lib/archive/page-meta-column-maps.d.ts +32 -0
- package/lib/archive/page-meta-column-maps.js +43 -0
- package/lib/archive/page.d.ts +6 -6
- package/lib/archive/page.js +5 -5
- package/lib/archive/peek-archive-lock.d.ts +2 -2
- package/lib/archive/peek-archive-lock.js +2 -2
- package/lib/archive/populate-entity-tables/collapse-anchor-rows.d.ts +41 -0
- package/lib/archive/populate-entity-tables/collapse-anchor-rows.js +87 -0
- package/lib/archive/populate-entity-tables/derive-dom-path.d.ts +35 -0
- package/lib/archive/populate-entity-tables/derive-dom-path.js +72 -0
- package/lib/archive/populate-entity-tables/is-blob-ref-value.d.ts +16 -0
- package/lib/archive/populate-entity-tables/is-blob-ref-value.js +19 -0
- package/lib/archive/populate-entity-tables/match-images-to-dom-paths.d.ts +66 -0
- package/lib/archive/populate-entity-tables/match-images-to-dom-paths.js +96 -0
- package/lib/archive/populate-entity-tables/populate-anchor-edges.d.ts +33 -0
- package/lib/archive/populate-entity-tables/populate-anchor-edges.js +153 -0
- package/lib/archive/populate-entity-tables/populate-content-items.d.ts +40 -0
- package/lib/archive/populate-entity-tables/populate-content-items.js +141 -0
- package/lib/archive/populate-entity-tables/populate-entities.d.ts +81 -0
- package/lib/archive/populate-entity-tables/populate-entities.js +111 -0
- package/lib/archive/populate-entity-tables/populate-image-items.d.ts +91 -0
- package/lib/archive/populate-entity-tables/populate-image-items.js +223 -0
- package/lib/archive/populate-entity-tables/populate-page-meta.d.ts +33 -0
- package/lib/archive/populate-entity-tables/populate-page-meta.js +267 -0
- package/lib/archive/populate-entity-tables/populate-resource-items.d.ts +22 -0
- package/lib/archive/populate-entity-tables/populate-resource-items.js +114 -0
- package/lib/archive/populate-entity-tables/populate-resource-ref-edges.d.ts +31 -0
- package/lib/archive/populate-entity-tables/populate-resource-ref-edges.js +33 -0
- package/lib/archive/populate-entity-tables/resolve-blob-refs.d.ts +31 -0
- package/lib/archive/populate-entity-tables/resolve-blob-refs.js +100 -0
- package/lib/archive/populate-entity-tables/resolve-content-type-refs.d.ts +22 -0
- package/lib/archive/populate-entity-tables/resolve-content-type-refs.js +27 -0
- package/lib/archive/populate-entity-tables/resolve-header-sets.d.ts +49 -0
- package/lib/archive/populate-entity-tables/resolve-header-sets.js +122 -0
- package/lib/archive/populate-entity-tables/resolve-json-refs.d.ts +25 -0
- package/lib/archive/populate-entity-tables/resolve-json-refs.js +67 -0
- package/lib/archive/populate-entity-tables/resolve-text-refs.d.ts +30 -0
- package/lib/archive/populate-entity-tables/resolve-text-refs.js +61 -0
- package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.d.ts +21 -0
- package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.js +27 -0
- package/lib/archive/populate-entity-tables/resolve-url-refs.d.ts +33 -0
- package/lib/archive/populate-entity-tables/resolve-url-refs.js +60 -0
- package/lib/archive/populate-entity-tables/test-utils/count-rows.d.ts +17 -0
- package/lib/archive/populate-entity-tables/test-utils/count-rows.js +20 -0
- package/lib/archive/populate-entity-tables/test-utils/seed-content-items.d.ts +25 -0
- package/lib/archive/populate-entity-tables/test-utils/seed-content-items.js +42 -0
- package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.d.ts +23 -0
- package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.js +178 -0
- package/lib/archive/populate-entity-tables/types.d.ts +157 -0
- package/lib/archive/populate-entity-tables/types.js +12 -0
- package/lib/archive/populate-entity-tables/upsert-text-refs.d.ts +38 -0
- package/lib/archive/populate-entity-tables/upsert-text-refs.js +78 -0
- package/lib/archive/populate-ref-tables/classify-content-type.d.ts +16 -0
- package/lib/archive/populate-ref-tables/classify-content-type.js +52 -0
- package/lib/archive/populate-ref-tables/compute-content-hash.d.ts +22 -0
- package/lib/archive/populate-ref-tables/compute-content-hash.js +26 -0
- package/lib/archive/populate-ref-tables/compute-header-flags.d.ts +16 -0
- package/lib/archive/populate-ref-tables/compute-header-flags.js +70 -0
- package/lib/archive/populate-ref-tables/content-type-rules.d.ts +38 -0
- package/lib/archive/populate-ref-tables/content-type-rules.js +133 -0
- package/lib/archive/populate-ref-tables/create-header-table-caches.d.ts +25 -0
- package/lib/archive/populate-ref-tables/create-header-table-caches.js +49 -0
- package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.d.ts +15 -0
- package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.js +15 -0
- package/lib/archive/populate-ref-tables/decode-data-uri.d.ts +21 -0
- package/lib/archive/populate-ref-tables/decode-data-uri.js +126 -0
- package/lib/archive/populate-ref-tables/decompose-header-set.d.ts +29 -0
- package/lib/archive/populate-ref-tables/decompose-header-set.js +157 -0
- package/lib/archive/populate-ref-tables/decompose-url.d.ts +25 -0
- package/lib/archive/populate-ref-tables/decompose-url.js +70 -0
- package/lib/archive/populate-ref-tables/header-stability.d.ts +19 -0
- package/lib/archive/populate-ref-tables/header-stability.js +22 -0
- package/lib/archive/populate-ref-tables/header-value-cache-key.d.ts +17 -0
- package/lib/archive/populate-ref-tables/header-value-cache-key.js +19 -0
- package/lib/archive/populate-ref-tables/normalize-mime.d.ts +24 -0
- package/lib/archive/populate-ref-tables/normalize-mime.js +36 -0
- package/lib/archive/populate-ref-tables/populate-blob-refs.d.ts +38 -0
- package/lib/archive/populate-ref-tables/populate-blob-refs.js +134 -0
- package/lib/archive/populate-ref-tables/populate-content-type-refs.d.ts +27 -0
- package/lib/archive/populate-ref-tables/populate-content-type-refs.js +70 -0
- package/lib/archive/populate-ref-tables/populate-header-tables.d.ts +35 -0
- package/lib/archive/populate-ref-tables/populate-header-tables.js +80 -0
- package/lib/archive/populate-ref-tables/populate-json-refs.d.ts +29 -0
- package/lib/archive/populate-ref-tables/populate-json-refs.js +101 -0
- package/lib/archive/populate-ref-tables/populate-refs.d.ts +51 -0
- package/lib/archive/populate-ref-tables/populate-refs.js +62 -0
- package/lib/archive/populate-ref-tables/populate-text-refs.d.ts +32 -0
- package/lib/archive/populate-ref-tables/populate-text-refs.js +133 -0
- package/lib/archive/populate-ref-tables/populate-url-refs.d.ts +28 -0
- package/lib/archive/populate-ref-tables/populate-url-refs.js +148 -0
- package/lib/archive/populate-ref-tables/test-utils/count-rows.d.ts +15 -0
- package/lib/archive/populate-ref-tables/test-utils/count-rows.js +17 -0
- package/lib/archive/populate-ref-tables/types.d.ts +197 -0
- package/lib/archive/populate-ref-tables/types.js +7 -0
- package/lib/archive/populate-ref-tables/upsert-one-header-set.d.ts +34 -0
- package/lib/archive/populate-ref-tables/upsert-one-header-set.js +208 -0
- package/lib/archive/populate-ref-tables/volatile-header-names.d.ts +20 -0
- package/lib/archive/populate-ref-tables/volatile-header-names.js +33 -0
- package/lib/archive/redirect-table.d.ts +4 -2
- package/lib/archive/redirect-table.js +15 -10
- package/lib/archive/resolve-redirect-chain.d.ts +3 -3
- package/lib/archive/resolve-redirect-chain.js +2 -2
- package/lib/archive/resource.d.ts +1 -1
- package/lib/archive/retarget-legacy-fk-tables.d.ts +47 -0
- package/lib/archive/retarget-legacy-fk-tables.js +107 -0
- package/lib/archive/test-utils/fk-parent-tables.d.ts +15 -0
- package/lib/archive/test-utils/fk-parent-tables.js +19 -0
- package/lib/archive/test-utils/seed-content-item.d.ts +35 -0
- package/lib/archive/test-utils/seed-content-item.js +42 -0
- package/lib/archive/test-utils/setup-legacy-fk-db.d.ts +33 -0
- package/lib/archive/test-utils/setup-legacy-fk-db.js +270 -0
- package/lib/archive/types.d.ts +151 -15
- package/lib/archive/verify-migration/capture-rejection.d.ts +24 -0
- package/lib/archive/verify-migration/capture-rejection.js +31 -0
- package/lib/archive/verify-migration/check-anchor-edges-count.d.ts +34 -0
- package/lib/archive/verify-migration/check-anchor-edges-count.js +72 -0
- package/lib/archive/verify-migration/check-anchor-edges-sum.d.ts +13 -0
- package/lib/archive/verify-migration/check-anchor-edges-sum.js +27 -0
- package/lib/archive/verify-migration/check-content-items-count.d.ts +16 -0
- package/lib/archive/verify-migration/check-content-items-count.js +30 -0
- package/lib/archive/verify-migration/check-content-type-preservation.d.ts +22 -0
- package/lib/archive/verify-migration/check-content-type-preservation.js +40 -0
- package/lib/archive/verify-migration/check-foreign-key-integrity.d.ts +31 -0
- package/lib/archive/verify-migration/check-foreign-key-integrity.js +47 -0
- package/lib/archive/verify-migration/check-image-items-count.d.ts +12 -0
- package/lib/archive/verify-migration/check-image-items-count.js +26 -0
- package/lib/archive/verify-migration/check-page-meta-count.d.ts +15 -0
- package/lib/archive/verify-migration/check-page-meta-count.js +31 -0
- package/lib/archive/verify-migration/check-reader-parity.d.ts +23 -0
- package/lib/archive/verify-migration/check-reader-parity.js +211 -0
- package/lib/archive/verify-migration/check-resource-items-count.d.ts +17 -0
- package/lib/archive/verify-migration/check-resource-items-count.js +33 -0
- package/lib/archive/verify-migration/check-url-round-trip.d.ts +43 -0
- package/lib/archive/verify-migration/check-url-round-trip.js +112 -0
- package/lib/archive/verify-migration/types.d.ts +70 -0
- package/lib/archive/verify-migration/types.js +63 -0
- package/lib/archive/verify-migration/verify-migration.d.ts +41 -0
- package/lib/archive/verify-migration/verify-migration.js +120 -0
- package/lib/classify-error-kind.d.ts +19 -0
- package/lib/classify-error-kind.js +122 -0
- package/lib/crawler/build-js-redirect-edge.d.ts +68 -0
- package/lib/crawler/build-js-redirect-edge.js +57 -0
- package/lib/crawler/build-redirect-event.d.ts +24 -0
- package/lib/crawler/build-redirect-event.js +28 -0
- package/lib/crawler/capture-image-dom-paths.d.ts +33 -0
- package/lib/crawler/capture-image-dom-paths.js +39 -0
- package/lib/crawler/clear-dns-burned-host-cache.d.ts +6 -0
- package/lib/crawler/clear-dns-burned-host-cache.js +11 -0
- package/lib/crawler/collect-image-dom-paths.d.ts +23 -0
- package/lib/crawler/collect-image-dom-paths.js +64 -0
- package/lib/crawler/crawler.d.ts +22 -1
- package/lib/crawler/crawler.js +679 -117
- package/lib/crawler/derive-js-redirect-target.d.ts +68 -0
- package/lib/crawler/derive-js-redirect-target.js +129 -0
- package/lib/crawler/derive-resource-source.d.ts +25 -15
- package/lib/crawler/derive-resource-source.js +28 -17
- package/lib/crawler/dns-burned-host-cache.d.ts +26 -0
- package/lib/crawler/dns-burned-host-cache.js +25 -0
- package/lib/crawler/dns-burned-host-short-circuit-counter.d.ts +13 -0
- package/lib/crawler/dns-burned-host-short-circuit-counter.js +11 -0
- package/lib/crawler/fetch-destination.d.ts +12 -4
- package/lib/crawler/fetch-destination.js +94 -16
- package/lib/crawler/inject-scope-auth.d.ts +1 -1
- package/lib/crawler/inject-scope-auth.js +1 -1
- package/lib/crawler/is-js-redirect-error-shape.d.ts +40 -0
- package/lib/crawler/is-js-redirect-error-shape.js +53 -0
- package/lib/crawler/is-puppeteer-fallback-candidate.d.ts +16 -0
- package/lib/crawler/is-puppeteer-fallback-candidate.js +63 -0
- package/lib/crawler/link-list.d.ts +21 -1
- package/lib/crawler/link-list.js +23 -3
- package/lib/crawler/normalize-content-type.d.ts +1 -1
- package/lib/crawler/normalize-content-type.js +1 -1
- package/lib/crawler/plan-sub-resource-emits.d.ts +63 -0
- package/lib/crawler/plan-sub-resource-emits.js +44 -0
- package/lib/crawler/preload-short-circuit-error.d.ts +22 -0
- package/lib/crawler/preload-short-circuit-error.js +25 -0
- package/lib/crawler/should-burn-host.d.ts +78 -0
- package/lib/crawler/should-burn-host.js +61 -0
- package/lib/crawler/should-get-fallback-on-head-failure.d.ts +38 -0
- package/lib/crawler/should-get-fallback-on-head-failure.js +46 -0
- package/lib/crawler/types.d.ts +108 -1
- package/lib/crawler-orchestrator.d.ts +22 -3
- package/lib/crawler-orchestrator.js +312 -73
- package/lib/crawler.d.ts +15 -2
- package/lib/crawler.js +24 -1
- package/lib/permanent-error-kinds.d.ts +43 -0
- package/lib/permanent-error-kinds.js +48 -0
- package/lib/types.d.ts +84 -0
- package/lib/utils/compute-file-sha256.d.ts +24 -0
- package/lib/utils/compute-file-sha256.js +56 -0
- package/lib/utils/error/emit-error-with-retry.d.ts +40 -0
- package/lib/utils/error/emit-error-with-retry.js +44 -0
- package/lib/utils/error/emit-error.d.ts +39 -0
- package/lib/utils/error/emit-error.js +41 -0
- package/package.json +12 -12
- package/lib/archive/migrate-crawl-errors.d.ts +0 -20
- package/lib/archive/migrate-crawl-errors.js +0 -38
- package/lib/archive/migrate-html-blob-tables.d.ts +0 -24
- package/lib/archive/migrate-html-blob-tables.js +0 -53
- package/lib/archive/migrate-page-errors.d.ts +0 -16
- package/lib/archive/migrate-page-errors.js +0 -35
- package/lib/archive/migrate-pages-resources-source.d.ts +0 -16
- package/lib/archive/migrate-pages-resources-source.js +0 -46
- package/lib/utils/error/error-emitter.d.ts +0 -18
- package/lib/utils/error/error-emitter.js +0 -29
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# @nitpicker/crawler
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
ヘッドレスブラウザでWebサイトをクロールし、`.nitpicker` アーカイブを生成・更新する内部パッケージです。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
通常は [@nitpicker/cli](../cli/README.md) の `crawl` コマンドから利用します。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## 関連リンク
|
|
8
8
|
|
|
9
|
-
|
|
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
|
|
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<
|
|
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
|
|
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`;
|
package/lib/archive/archive.d.ts
CHANGED
|
@@ -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<
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
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
|
-
|
|
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.
|
package/lib/archive/archive.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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>;
|