@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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether a browser-scrape error message is the specific
|
|
3
|
+
* `Page.goto() returned null` shape that the JS-redirect rescue is
|
|
4
|
+
* designed to recover from.
|
|
5
|
+
*
|
|
6
|
+
* **Why this gate exists:** before the gate, the rescue fired on *any*
|
|
7
|
+
* thrown error from `scraper.scrapeStart` as long as `page.url()` happened
|
|
8
|
+
* to report a different http(s) URL. That made every browser failure
|
|
9
|
+
* (TLS, target-crashed, OOM, navigation timeout, …) that incidentally
|
|
10
|
+
* left the page on a follow-up URL look like a JS redirect, hiding the
|
|
11
|
+
* real failure mode and stamping a phantom `status = 301` on the source.
|
|
12
|
+
*
|
|
13
|
+
* The narrow trigger only fires on the upstream's exact sentinel —
|
|
14
|
+
* `Page.goto returned null` — which beholder's scraper throws *only*
|
|
15
|
+
* when puppeteer's `page.goto()` resolved to `null`. Substring match (not
|
|
16
|
+
* equality) so wrapped variants like `[Retried 3 times] The method
|
|
17
|
+
* Page.goto returned null` (which surface in `crawl_errors` after retry
|
|
18
|
+
* exhaustion at outer layers) still classify, even though the rescue
|
|
19
|
+
* sees the bare form. Case-insensitive on the marker so a future
|
|
20
|
+
* beholder bump that lowercases the message keeps working.
|
|
21
|
+
*
|
|
22
|
+
* The trigger keys off the message *string*, not the message-classifier
|
|
23
|
+
* `kind`, because the rescue runs *before* the kind decision: the kind
|
|
24
|
+
* classifier would already wash this into `protocol`, and `protocol`
|
|
25
|
+
* covers more than just goto-null (Target closed / Session closed /
|
|
26
|
+
* detached Frame …) — none of which leave puppeteer with a meaningful
|
|
27
|
+
* post-navigation URL to recover.
|
|
28
|
+
* @param message - The raw error message from
|
|
29
|
+
* `BrowserScrapeResult.error.message` (or any string that may carry
|
|
30
|
+
* the sentinel inside a wrapper). `null` / `undefined` returns `false`.
|
|
31
|
+
* @returns `true` iff the message carries the `Page.goto returned null`
|
|
32
|
+
* sentinel.
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* isJsRedirectErrorShape('The method Page.goto returned null'); // → true
|
|
36
|
+
* isJsRedirectErrorShape('Navigation timeout of 60000 ms exceeded'); // → false
|
|
37
|
+
* isJsRedirectErrorShape(undefined); // → false
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function isJsRedirectErrorShape(message: string | null | undefined): boolean;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel string emitted by `@d-zero/beholder`'s scraper when
|
|
3
|
+
* `await page.goto(...)` resolves to `null`. Pinned here because the
|
|
4
|
+
* JS-redirect rescue in `Crawler.#scrapePage` keys off the exact text —
|
|
5
|
+
* an upstream rename would silently disable the rescue, but the
|
|
6
|
+
* spec on this helper would also break, surfacing the drift in CI.
|
|
7
|
+
*/
|
|
8
|
+
const PAGE_GOTO_NULL_MARKER = 'Page.goto returned null';
|
|
9
|
+
/**
|
|
10
|
+
* Decide whether a browser-scrape error message is the specific
|
|
11
|
+
* `Page.goto() returned null` shape that the JS-redirect rescue is
|
|
12
|
+
* designed to recover from.
|
|
13
|
+
*
|
|
14
|
+
* **Why this gate exists:** before the gate, the rescue fired on *any*
|
|
15
|
+
* thrown error from `scraper.scrapeStart` as long as `page.url()` happened
|
|
16
|
+
* to report a different http(s) URL. That made every browser failure
|
|
17
|
+
* (TLS, target-crashed, OOM, navigation timeout, …) that incidentally
|
|
18
|
+
* left the page on a follow-up URL look like a JS redirect, hiding the
|
|
19
|
+
* real failure mode and stamping a phantom `status = 301` on the source.
|
|
20
|
+
*
|
|
21
|
+
* The narrow trigger only fires on the upstream's exact sentinel —
|
|
22
|
+
* `Page.goto returned null` — which beholder's scraper throws *only*
|
|
23
|
+
* when puppeteer's `page.goto()` resolved to `null`. Substring match (not
|
|
24
|
+
* equality) so wrapped variants like `[Retried 3 times] The method
|
|
25
|
+
* Page.goto returned null` (which surface in `crawl_errors` after retry
|
|
26
|
+
* exhaustion at outer layers) still classify, even though the rescue
|
|
27
|
+
* sees the bare form. Case-insensitive on the marker so a future
|
|
28
|
+
* beholder bump that lowercases the message keeps working.
|
|
29
|
+
*
|
|
30
|
+
* The trigger keys off the message *string*, not the message-classifier
|
|
31
|
+
* `kind`, because the rescue runs *before* the kind decision: the kind
|
|
32
|
+
* classifier would already wash this into `protocol`, and `protocol`
|
|
33
|
+
* covers more than just goto-null (Target closed / Session closed /
|
|
34
|
+
* detached Frame …) — none of which leave puppeteer with a meaningful
|
|
35
|
+
* post-navigation URL to recover.
|
|
36
|
+
* @param message - The raw error message from
|
|
37
|
+
* `BrowserScrapeResult.error.message` (or any string that may carry
|
|
38
|
+
* the sentinel inside a wrapper). `null` / `undefined` returns `false`.
|
|
39
|
+
* @returns `true` iff the message carries the `Page.goto returned null`
|
|
40
|
+
* sentinel.
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* isJsRedirectErrorShape('The method Page.goto returned null'); // → true
|
|
44
|
+
* isJsRedirectErrorShape('Navigation timeout of 60000 ms exceeded'); // → false
|
|
45
|
+
* isJsRedirectErrorShape(undefined); // → false
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function isJsRedirectErrorShape(message) {
|
|
49
|
+
if (typeof message !== 'string' || message === '') {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return message.toLowerCase().includes(PAGE_GOTO_NULL_MARKER.toLowerCase());
|
|
53
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether a failed HEAD/GET pre-flight error message warrants one
|
|
3
|
+
* puppeteer fallback attempt. Pure and deterministic — the same message
|
|
4
|
+
* always gives the same answer, so the decision can be unit-tested without
|
|
5
|
+
* spinning up a browser.
|
|
6
|
+
* @param message - The pre-flight error message (typically the last rejected
|
|
7
|
+
* `retryCall` attempt).
|
|
8
|
+
* @returns `true` when puppeteer should be tried once, `false` to give up.
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* isPuppeteerFallbackCandidate('Timeout: https://slow.example.org/'); // true
|
|
12
|
+
* isPuppeteerFallbackCandidate('getaddrinfo ENOTFOUND host.invalid'); // false
|
|
13
|
+
* isPuppeteerFallbackCandidate('net::ERR_CERT_DATE_INVALID'); // false
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function isPuppeteerFallbackCandidate(message: string): boolean;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { classifyErrorKind } from '../classify-error-kind.js';
|
|
2
|
+
/**
|
|
3
|
+
* Error kinds where a full puppeteer navigation has a realistic chance of
|
|
4
|
+
* succeeding even though the HEAD pre-flight (and its GET fallback) failed.
|
|
5
|
+
*
|
|
6
|
+
* These are the failure modes that a misconfigured WAF / middlebox / slow
|
|
7
|
+
* origin tends to produce against a bare HEAD/GET probe while still letting
|
|
8
|
+
* a real browser through — the browser uses a different request shape (full
|
|
9
|
+
* navigation lifecycle, JS-capable Accept headers, real cookies, optionally
|
|
10
|
+
* client TLS hints), and some hostile middleboxes only inspect the cheap
|
|
11
|
+
* shape. The fallback is one attempt only; if puppeteer also fails the URL
|
|
12
|
+
* is recorded as `status = -1` like before.
|
|
13
|
+
*
|
|
14
|
+
* Excluded kinds:
|
|
15
|
+
* - **dns / dns-transient** — DNS resolution happens at the OS level before
|
|
16
|
+
* any browser request; puppeteer hits the same `getaddrinfo` outcome.
|
|
17
|
+
* - **tls** — Chromium will refuse the same certificate the Node TLS stack
|
|
18
|
+
* refused (expired, wrong SAN, untrusted CA).
|
|
19
|
+
* - **client-blocked** — by definition the browser is the one rejecting.
|
|
20
|
+
* - **connection-refused** — TCP RST from the listener; same answer regardless
|
|
21
|
+
* of client.
|
|
22
|
+
* - **connection-timeout** — `ETIMEDOUT` at the TCP connect stage means the
|
|
23
|
+
* packets never reached the host (no SYN-ACK); puppeteer issues the same
|
|
24
|
+
* `connect()` call and gets the same answer. Reserved for the middlebox
|
|
25
|
+
* case (request reached the server, response timed out), which classifies
|
|
26
|
+
* as `timeout` via the `NetTimeoutError "Timeout: <url>"` shape.
|
|
27
|
+
* - **local-network** — operator-side connectivity loss; nothing on this
|
|
28
|
+
* machine will reach the host.
|
|
29
|
+
* - **protocol** — puppeteer lifecycle race; bouncing back to puppeteer
|
|
30
|
+
* reproduces the same race.
|
|
31
|
+
* - **unknown** — by design. Spinning up a fresh Chromium for every
|
|
32
|
+
* unclassifiable error is too expensive; if a real-world WAF / middlebox
|
|
33
|
+
* pattern lands in `unknown`, add a matcher to {@link classifyErrorKind}
|
|
34
|
+
* so it lands in one of the four included kinds above (where the fallback
|
|
35
|
+
* has a meaningful chance of succeeding) instead of widening this set.
|
|
36
|
+
*
|
|
37
|
+
* `PreloadShortCircuitError`'s synthesised `getaddrinfo ENOTFOUND` message
|
|
38
|
+
* classifies into `dns` and is therefore filtered out automatically — no
|
|
39
|
+
* separate instanceof guard is needed at the call site.
|
|
40
|
+
*/
|
|
41
|
+
const PUPPETEER_FALLBACK_KINDS = new Set([
|
|
42
|
+
'timeout',
|
|
43
|
+
'connection-reset',
|
|
44
|
+
'parse-error',
|
|
45
|
+
]);
|
|
46
|
+
/**
|
|
47
|
+
* Decide whether a failed HEAD/GET pre-flight error message warrants one
|
|
48
|
+
* puppeteer fallback attempt. Pure and deterministic — the same message
|
|
49
|
+
* always gives the same answer, so the decision can be unit-tested without
|
|
50
|
+
* spinning up a browser.
|
|
51
|
+
* @param message - The pre-flight error message (typically the last rejected
|
|
52
|
+
* `retryCall` attempt).
|
|
53
|
+
* @returns `true` when puppeteer should be tried once, `false` to give up.
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* isPuppeteerFallbackCandidate('Timeout: https://slow.example.org/'); // true
|
|
57
|
+
* isPuppeteerFallbackCandidate('getaddrinfo ENOTFOUND host.invalid'); // false
|
|
58
|
+
* isPuppeteerFallbackCandidate('net::ERR_CERT_DATE_INVALID'); // false
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function isPuppeteerFallbackCandidate(message) {
|
|
62
|
+
return PUPPETEER_FALLBACK_KINDS.has(classifyErrorKind(message));
|
|
63
|
+
}
|
|
@@ -45,12 +45,32 @@ export default class LinkList {
|
|
|
45
45
|
* @param resource.page - The scraped page data, if the scrape succeeded.
|
|
46
46
|
* @param resource.error - The error object, if the scrape failed.
|
|
47
47
|
* @param options - URL parsing options (e.g., `disableQueries`).
|
|
48
|
+
* @param completion - Behaviour overrides for how the redirect chain is folded into
|
|
49
|
+
* the done-set.
|
|
50
|
+
* @param completion.includeRedirectPaths - When `false`, do NOT mark the URLs in
|
|
51
|
+
* `resource.page.redirectPaths` as done. The default `true` preserves the
|
|
52
|
+
* long-standing behaviour where a redirect chain (`/a → /b → /c`) folds every
|
|
53
|
+
* intermediate URL into the done-set in a single sweep — correct for HTTP-layer
|
|
54
|
+
* chains because the browser actually followed each hop, so reaching `/b` later
|
|
55
|
+
* is a no-op. Pass `false` from the JS-redirect rescue: there `redirectPaths`
|
|
56
|
+
* contains a single URL (the JS target Chromium navigated to after `page.goto()`
|
|
57
|
+
* returned null), which the browser has NOT yet rendered. Folding it into the
|
|
58
|
+
* done-set would make a subsequent `linkList.add(destinationUrl)` no-op (the
|
|
59
|
+
* add() guard at line 51-53 refuses keys already in `#done`), so the dest URL
|
|
60
|
+
* never enters `#pending` and the dealer never sees a push to enqueue. The
|
|
61
|
+
* dealer's own `seen` Set in `#runDeal` (the gate `onPush` consults) is a
|
|
62
|
+
* separate registry from `#done` — they are NOT kept in sync — but here it
|
|
63
|
+
* does not matter: the rescue's `add()` is what feeds the eventual `enqueue()`
|
|
64
|
+
* call, so blocking `add()` alone is enough to silently lose the JS target
|
|
65
|
+
* from the archive.
|
|
48
66
|
* @returns The constructed {@link Link} object, or `null` if the URL was not in the queue.
|
|
49
67
|
*/
|
|
50
68
|
done(url: ExURL, scope: ReadonlyMap<string, readonly ExURL[]>, resource: {
|
|
51
69
|
page?: PageData;
|
|
52
70
|
error?: Error;
|
|
53
|
-
}, options: ParseURLOptions
|
|
71
|
+
}, options: ParseURLOptions, completion?: {
|
|
72
|
+
includeRedirectPaths?: boolean;
|
|
73
|
+
}): Link | null;
|
|
54
74
|
/**
|
|
55
75
|
* Get the current pending and in-progress URL lists.
|
|
56
76
|
* @returns An object containing arrays of pending and in-progress URL strings.
|
package/lib/crawler/link-list.js
CHANGED
|
@@ -64,9 +64,27 @@ export default class LinkList {
|
|
|
64
64
|
* @param resource.page - The scraped page data, if the scrape succeeded.
|
|
65
65
|
* @param resource.error - The error object, if the scrape failed.
|
|
66
66
|
* @param options - URL parsing options (e.g., `disableQueries`).
|
|
67
|
+
* @param completion - Behaviour overrides for how the redirect chain is folded into
|
|
68
|
+
* the done-set.
|
|
69
|
+
* @param completion.includeRedirectPaths - When `false`, do NOT mark the URLs in
|
|
70
|
+
* `resource.page.redirectPaths` as done. The default `true` preserves the
|
|
71
|
+
* long-standing behaviour where a redirect chain (`/a → /b → /c`) folds every
|
|
72
|
+
* intermediate URL into the done-set in a single sweep — correct for HTTP-layer
|
|
73
|
+
* chains because the browser actually followed each hop, so reaching `/b` later
|
|
74
|
+
* is a no-op. Pass `false` from the JS-redirect rescue: there `redirectPaths`
|
|
75
|
+
* contains a single URL (the JS target Chromium navigated to after `page.goto()`
|
|
76
|
+
* returned null), which the browser has NOT yet rendered. Folding it into the
|
|
77
|
+
* done-set would make a subsequent `linkList.add(destinationUrl)` no-op (the
|
|
78
|
+
* add() guard at line 51-53 refuses keys already in `#done`), so the dest URL
|
|
79
|
+
* never enters `#pending` and the dealer never sees a push to enqueue. The
|
|
80
|
+
* dealer's own `seen` Set in `#runDeal` (the gate `onPush` consults) is a
|
|
81
|
+
* separate registry from `#done` — they are NOT kept in sync — but here it
|
|
82
|
+
* does not matter: the rescue's `add()` is what feeds the eventual `enqueue()`
|
|
83
|
+
* call, so blocking `add()` alone is enough to silently lose the JS target
|
|
84
|
+
* from the archive.
|
|
67
85
|
* @returns The constructed {@link Link} object, or `null` if the URL was not in the queue.
|
|
68
86
|
*/
|
|
69
|
-
done(url, scope, resource, options) {
|
|
87
|
+
done(url, scope, resource, options, completion) {
|
|
70
88
|
const key = protocolAgnosticKey(url.withoutHashAndAuth);
|
|
71
89
|
if (!(this.#pending.has(key) || this.#progress.has(key))) {
|
|
72
90
|
return null;
|
|
@@ -96,8 +114,10 @@ export default class LinkList {
|
|
|
96
114
|
responseHeaders: resource.page.responseHeaders,
|
|
97
115
|
title: resource.page.meta.title,
|
|
98
116
|
};
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
if (completion?.includeRedirectPaths !== false) {
|
|
118
|
+
for (const path of resource.page.redirectPaths) {
|
|
119
|
+
urlList.add(protocolAgnosticKey(path));
|
|
120
|
+
}
|
|
101
121
|
}
|
|
102
122
|
}
|
|
103
123
|
if (resource.error?.message.includes('ERR_NAME_NOT_RESOLVED')) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* normalization, so `Text/HTML` or `text/html ` can otherwise reach the
|
|
8
8
|
* database. Storing the canonical (trimmed, lower-cased) form lets the exact
|
|
9
9
|
* SQL page-ness predicate (`WHERE contentType = 'text/html'`) agree with the
|
|
10
|
-
* code-level
|
|
10
|
+
* code-level `isHtmlContentType` check, which trims and lower-cases.
|
|
11
11
|
* @param contentType - The raw media type, or `null` when unknown.
|
|
12
12
|
* @returns The trimmed, lower-cased media type, or `null` when unknown/blank.
|
|
13
13
|
*/
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* normalization, so `Text/HTML` or `text/html ` can otherwise reach the
|
|
8
8
|
* database. Storing the canonical (trimmed, lower-cased) form lets the exact
|
|
9
9
|
* SQL page-ness predicate (`WHERE contentType = 'text/html'`) agree with the
|
|
10
|
-
* code-level
|
|
10
|
+
* code-level `isHtmlContentType` check, which trims and lower-cases.
|
|
11
11
|
* @param contentType - The raw media type, or `null` when unknown.
|
|
12
12
|
* @returns The trimmed, lower-cased media type, or `null` when unknown/blank.
|
|
13
13
|
*/
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { CrawlerEventTypes } from './types.js';
|
|
2
|
+
import type { PageSource } from '../archive/types.js';
|
|
3
|
+
import type { ResourceEntry } from '@d-zero/beholder';
|
|
4
|
+
/**
|
|
5
|
+
* Planned `response` emit produced by {@link planSubResourceEmits}.
|
|
6
|
+
*/
|
|
7
|
+
export interface PlannedResponseEmit {
|
|
8
|
+
/** The resource payload to attach to the `response` event. */
|
|
9
|
+
resource: CrawlerEventTypes['response']['resource'];
|
|
10
|
+
/**
|
|
11
|
+
* The `source` field propagated to the `response` event. Resolved from
|
|
12
|
+
* the parent page's lineage via {@link deriveResourceSource} — pinning
|
|
13
|
+
* this through a planning step (rather than computing it inline in
|
|
14
|
+
* `#handleResources`) lets the wire-up be unit-tested without spinning
|
|
15
|
+
* up the puppeteer mock stack.
|
|
16
|
+
*/
|
|
17
|
+
source: PageSource | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Planned `responseReferrers` emit produced by {@link planSubResourceEmits}.
|
|
21
|
+
* Always emitted, regardless of whether the resource is new — `isNew` only
|
|
22
|
+
* gates the `response` event.
|
|
23
|
+
*/
|
|
24
|
+
export interface PlannedReferrerEmit {
|
|
25
|
+
/** The page URL that triggered the sub-resource fetch. */
|
|
26
|
+
url: string;
|
|
27
|
+
/** The resource URL (hash stripped to match the storage key). */
|
|
28
|
+
src: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Output of {@link planSubResourceEmits}: the deduped `response` plan and
|
|
32
|
+
* the per-resource `responseReferrers` plan, side-by-side.
|
|
33
|
+
*/
|
|
34
|
+
export interface SubResourceEmitPlan {
|
|
35
|
+
/** `response` events to emit (new resources only). */
|
|
36
|
+
responseEmits: PlannedResponseEmit[];
|
|
37
|
+
/** `responseReferrers` events to emit (every resource, even seen ones). */
|
|
38
|
+
referrerEmits: PlannedReferrerEmit[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Decide which sub-resource `response` / `responseReferrers` events the
|
|
42
|
+
* crawler should emit for a page render, with the parent's source lineage
|
|
43
|
+
* baked into every `response` event's `source` field.
|
|
44
|
+
*
|
|
45
|
+
* Pure function — takes the resources captured during the render plus the
|
|
46
|
+
* seen-resource set and the parent's source, returns the emit plan. The
|
|
47
|
+
* caller (`Crawler.#handleResources`) is responsible for iterating the
|
|
48
|
+
* plan through its event emitter. Splitting "decide what to emit" from
|
|
49
|
+
* "actually emit" is what makes the lineage propagation contract
|
|
50
|
+
* unit-testable: the previous shape inlined `emit('response', { ...
|
|
51
|
+
* source: deriveResourceSource(...) })` and could only be exercised via a
|
|
52
|
+
* full scrape with a mocked puppeteer stack, which left the `source`
|
|
53
|
+
* value half of the contract effectively un-pinned.
|
|
54
|
+
*
|
|
55
|
+
* Mutates `seenResources` as a side effect — every captured resource is
|
|
56
|
+
* recorded as seen so the next call dedupes correctly. This mirrors the
|
|
57
|
+
* `Crawler.#resources` Set semantics that the planner is designed to share.
|
|
58
|
+
* @param resources - Sub-resource entries captured during the page render.
|
|
59
|
+
* @param parentSource - Merged source of the page being rendered, as resolved by `Crawler.#resolveParentSource`.
|
|
60
|
+
* @param seenResources - Mutable set of already-seen resource keys (mutated in place).
|
|
61
|
+
* @returns The plan of `response` + `responseReferrers` emits to dispatch.
|
|
62
|
+
*/
|
|
63
|
+
export declare function planSubResourceEmits(resources: ResourceEntry[], parentSource: PageSource | undefined, seenResources: Set<string>): SubResourceEmitPlan;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { deriveResourceSource } from './derive-resource-source.js';
|
|
2
|
+
import { handleResourceResponse } from './handle-resource-response.js';
|
|
3
|
+
/**
|
|
4
|
+
* Decide which sub-resource `response` / `responseReferrers` events the
|
|
5
|
+
* crawler should emit for a page render, with the parent's source lineage
|
|
6
|
+
* baked into every `response` event's `source` field.
|
|
7
|
+
*
|
|
8
|
+
* Pure function — takes the resources captured during the render plus the
|
|
9
|
+
* seen-resource set and the parent's source, returns the emit plan. The
|
|
10
|
+
* caller (`Crawler.#handleResources`) is responsible for iterating the
|
|
11
|
+
* plan through its event emitter. Splitting "decide what to emit" from
|
|
12
|
+
* "actually emit" is what makes the lineage propagation contract
|
|
13
|
+
* unit-testable: the previous shape inlined `emit('response', { ...
|
|
14
|
+
* source: deriveResourceSource(...) })` and could only be exercised via a
|
|
15
|
+
* full scrape with a mocked puppeteer stack, which left the `source`
|
|
16
|
+
* value half of the contract effectively un-pinned.
|
|
17
|
+
*
|
|
18
|
+
* Mutates `seenResources` as a side effect — every captured resource is
|
|
19
|
+
* recorded as seen so the next call dedupes correctly. This mirrors the
|
|
20
|
+
* `Crawler.#resources` Set semantics that the planner is designed to share.
|
|
21
|
+
* @param resources - Sub-resource entries captured during the page render.
|
|
22
|
+
* @param parentSource - Merged source of the page being rendered, as resolved by `Crawler.#resolveParentSource`.
|
|
23
|
+
* @param seenResources - Mutable set of already-seen resource keys (mutated in place).
|
|
24
|
+
* @returns The plan of `response` + `responseReferrers` emits to dispatch.
|
|
25
|
+
*/
|
|
26
|
+
export function planSubResourceEmits(resources, parentSource, seenResources) {
|
|
27
|
+
const subResourceSource = deriveResourceSource(parentSource);
|
|
28
|
+
const responseEmits = [];
|
|
29
|
+
const referrerEmits = [];
|
|
30
|
+
for (const { resource, pageUrl } of resources) {
|
|
31
|
+
const { isNew } = handleResourceResponse(resource, seenResources);
|
|
32
|
+
if (isNew) {
|
|
33
|
+
responseEmits.push({
|
|
34
|
+
resource: resource,
|
|
35
|
+
source: subResourceSource,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
referrerEmits.push({
|
|
39
|
+
url: pageUrl,
|
|
40
|
+
src: resource.url.withoutHash,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return { responseEmits, referrerEmits };
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by `Crawler.#sendHeadRequest` when the target URL's hostname is in
|
|
3
|
+
* `dnsBurnedHostCache` — both session-learned and preload-seeded burns
|
|
4
|
+
* land here.
|
|
5
|
+
*
|
|
6
|
+
* The orchestrator's `crawler.on('error', …)` handler tests `instanceof` and
|
|
7
|
+
* skips writing this error to `crawl_errors` / `error.log`, so the same
|
|
8
|
+
* preload data isn't re-amplified on subsequent crawls. `pages.status = -1`
|
|
9
|
+
* still gets set through the normal scrape-error path.
|
|
10
|
+
*
|
|
11
|
+
* The message embeds the `ENOTFOUND` token so any downstream consumer that
|
|
12
|
+
* runs `classifyErrorKind` over it (e.g. dealer log forwarders) still gets
|
|
13
|
+
* the `'dns'` classification.
|
|
14
|
+
*/
|
|
15
|
+
export declare class PreloadShortCircuitError extends Error {
|
|
16
|
+
/** Sniffable flag for callers that prefer duck-typing over instanceof. */
|
|
17
|
+
readonly isPreloadShortCircuit: true;
|
|
18
|
+
/**
|
|
19
|
+
* @param host - The DNS-burned hostname (already lowercased / Punycoded).
|
|
20
|
+
*/
|
|
21
|
+
constructor(host: string);
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by `Crawler.#sendHeadRequest` when the target URL's hostname is in
|
|
3
|
+
* `dnsBurnedHostCache` — both session-learned and preload-seeded burns
|
|
4
|
+
* land here.
|
|
5
|
+
*
|
|
6
|
+
* The orchestrator's `crawler.on('error', …)` handler tests `instanceof` and
|
|
7
|
+
* skips writing this error to `crawl_errors` / `error.log`, so the same
|
|
8
|
+
* preload data isn't re-amplified on subsequent crawls. `pages.status = -1`
|
|
9
|
+
* still gets set through the normal scrape-error path.
|
|
10
|
+
*
|
|
11
|
+
* The message embeds the `ENOTFOUND` token so any downstream consumer that
|
|
12
|
+
* runs `classifyErrorKind` over it (e.g. dealer log forwarders) still gets
|
|
13
|
+
* the `'dns'` classification.
|
|
14
|
+
*/
|
|
15
|
+
export class PreloadShortCircuitError extends Error {
|
|
16
|
+
/** Sniffable flag for callers that prefer duck-typing over instanceof. */
|
|
17
|
+
isPreloadShortCircuit = true;
|
|
18
|
+
/**
|
|
19
|
+
* @param host - The DNS-burned hostname (already lowercased / Punycoded).
|
|
20
|
+
*/
|
|
21
|
+
constructor(host) {
|
|
22
|
+
super(`getaddrinfo ENOTFOUND ${host}`);
|
|
23
|
+
this.name = 'PreloadShortCircuitError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ErrorKind } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Inputs to {@link shouldBurnHost}.
|
|
4
|
+
*/
|
|
5
|
+
export interface ShouldBurnHostParams {
|
|
6
|
+
/**
|
|
7
|
+
* The {@link ErrorKind} classified from the final-attempt error message
|
|
8
|
+
* (i.e. the error that ended the retry loop in `Crawler.#sendHeadRequest`'s
|
|
9
|
+
* `onGiveUp`).
|
|
10
|
+
*/
|
|
11
|
+
errorKind: ErrorKind;
|
|
12
|
+
/**
|
|
13
|
+
* Lower-cased hostname whose URL just exhausted retries. Must already be
|
|
14
|
+
* normalised by the caller — `dnsBurnedHostCache` keys are
|
|
15
|
+
* `url.hostname.toLowerCase()`, so this guard reuses that exact form to
|
|
16
|
+
* stay consistent across the two sites.
|
|
17
|
+
*/
|
|
18
|
+
host: string;
|
|
19
|
+
/**
|
|
20
|
+
* Set of hostnames that have had at least one successful
|
|
21
|
+
* `fetchDestination` response in this session. `ReadonlySet` because the
|
|
22
|
+
* decision is read-only — populating the set is the caller's job.
|
|
23
|
+
*/
|
|
24
|
+
successfulHosts: ReadonlySet<string>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Burn the host iff the final-attempt kind is `'dns'` AND the host has no
|
|
28
|
+
* session-success record. Pure function — unit-testable in isolation from
|
|
29
|
+
* the `Crawler` instance, the in-memory caches, and the dealer's retry
|
|
30
|
+
* plumbing.
|
|
31
|
+
*
|
|
32
|
+
* **Why the session-success gate exists**: the first worker to exhaust
|
|
33
|
+
* retries with `getaddrinfo ENOTFOUND` would otherwise burn the host and
|
|
34
|
+
* make every subsequent URL on it short-circuit immediately via
|
|
35
|
+
* `PreloadShortCircuitError`, draining the dealer's work queue in seconds
|
|
36
|
+
* and collapsing the crawl into a degenerate `crawlEnd`. When the cause is
|
|
37
|
+
* a local-network blip (operator's WiFi → tethering / VPN flip / ISP DNS
|
|
38
|
+
* hiccup mid-crawl) rather than a dead domain, the host was demonstrably
|
|
39
|
+
* alive moments earlier — earlier successes on it are recorded in
|
|
40
|
+
* `successfulHosts`, so the cascade is suppressed.
|
|
41
|
+
*
|
|
42
|
+
* **Why `'dns-transient'` is excluded**: `EAI_AGAIN` / `EREFUSED` are
|
|
43
|
+
* absorbed by the retry layer within the session; a final-attempt
|
|
44
|
+
* `'dns-transient'` is rare enough that we'd rather pay the per-URL retry
|
|
45
|
+
* cost than wrongly fast-fail a healthy host that flapped briefly. Only the
|
|
46
|
+
* stronger `'dns'` kind is a candidate for burning.
|
|
47
|
+
*
|
|
48
|
+
* **Known limitation — first-URL false positives**: a host whose very first
|
|
49
|
+
* URL of the session hits a real network blip exhausts its retry budget
|
|
50
|
+
* before any URL has succeeded, so `successfulHosts` is still empty and the
|
|
51
|
+
* host IS burned. That URL's siblings on the same host then short-circuit.
|
|
52
|
+
* Acceptable trade-off: the alternative ("never burn anything") regresses
|
|
53
|
+
* the dead-domain fast-fail behavior that the burn cache exists for. The
|
|
54
|
+
* pause-dealer-on-outage layer (separate issue) covers this gap by
|
|
55
|
+
* detecting the outage BEFORE the first retry budget runs out.
|
|
56
|
+
*
|
|
57
|
+
* **Known limitation — preload-seeded burns are not un-burned**: a host
|
|
58
|
+
* added by `#preloadDnsBurnedHostCache` from the archive's `crawl_errors`
|
|
59
|
+
* trips `PreloadShortCircuitError` at the top of `#sendHeadRequest` and
|
|
60
|
+
* never reaches `fetchDestination`, so `successfulHosts` is never
|
|
61
|
+
* populated for it in this session. This is intentional — preload only
|
|
62
|
+
* seeds hosts whose archive evidence is "DNS-failed with no recovery", so
|
|
63
|
+
* un-burning them on a single transient success could re-introduce the
|
|
64
|
+
* cascade we are trying to prevent.
|
|
65
|
+
*
|
|
66
|
+
* **What does NOT contribute to `successfulHosts`**: external pages
|
|
67
|
+
* traversed via `fetchExternal: false` skip the HEAD pre-flight entirely
|
|
68
|
+
* (the crawler stamps a synthetic `PageData` without touching the
|
|
69
|
+
* network), so the host is not recorded as alive even if the same host
|
|
70
|
+
* appears in the crawled-internal scope. Callers must populate the set
|
|
71
|
+
* from real HTTP-response observations only.
|
|
72
|
+
* @param params - See {@link ShouldBurnHostParams}.
|
|
73
|
+
* @param params.errorKind
|
|
74
|
+
* @param params.host
|
|
75
|
+
* @param params.successfulHosts
|
|
76
|
+
* @returns `true` if the burn cache should record this host, `false` otherwise.
|
|
77
|
+
*/
|
|
78
|
+
export declare function shouldBurnHost({ errorKind, host, successfulHosts, }: ShouldBurnHostParams): boolean;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Burn the host iff the final-attempt kind is `'dns'` AND the host has no
|
|
3
|
+
* session-success record. Pure function — unit-testable in isolation from
|
|
4
|
+
* the `Crawler` instance, the in-memory caches, and the dealer's retry
|
|
5
|
+
* plumbing.
|
|
6
|
+
*
|
|
7
|
+
* **Why the session-success gate exists**: the first worker to exhaust
|
|
8
|
+
* retries with `getaddrinfo ENOTFOUND` would otherwise burn the host and
|
|
9
|
+
* make every subsequent URL on it short-circuit immediately via
|
|
10
|
+
* `PreloadShortCircuitError`, draining the dealer's work queue in seconds
|
|
11
|
+
* and collapsing the crawl into a degenerate `crawlEnd`. When the cause is
|
|
12
|
+
* a local-network blip (operator's WiFi → tethering / VPN flip / ISP DNS
|
|
13
|
+
* hiccup mid-crawl) rather than a dead domain, the host was demonstrably
|
|
14
|
+
* alive moments earlier — earlier successes on it are recorded in
|
|
15
|
+
* `successfulHosts`, so the cascade is suppressed.
|
|
16
|
+
*
|
|
17
|
+
* **Why `'dns-transient'` is excluded**: `EAI_AGAIN` / `EREFUSED` are
|
|
18
|
+
* absorbed by the retry layer within the session; a final-attempt
|
|
19
|
+
* `'dns-transient'` is rare enough that we'd rather pay the per-URL retry
|
|
20
|
+
* cost than wrongly fast-fail a healthy host that flapped briefly. Only the
|
|
21
|
+
* stronger `'dns'` kind is a candidate for burning.
|
|
22
|
+
*
|
|
23
|
+
* **Known limitation — first-URL false positives**: a host whose very first
|
|
24
|
+
* URL of the session hits a real network blip exhausts its retry budget
|
|
25
|
+
* before any URL has succeeded, so `successfulHosts` is still empty and the
|
|
26
|
+
* host IS burned. That URL's siblings on the same host then short-circuit.
|
|
27
|
+
* Acceptable trade-off: the alternative ("never burn anything") regresses
|
|
28
|
+
* the dead-domain fast-fail behavior that the burn cache exists for. The
|
|
29
|
+
* pause-dealer-on-outage layer (separate issue) covers this gap by
|
|
30
|
+
* detecting the outage BEFORE the first retry budget runs out.
|
|
31
|
+
*
|
|
32
|
+
* **Known limitation — preload-seeded burns are not un-burned**: a host
|
|
33
|
+
* added by `#preloadDnsBurnedHostCache` from the archive's `crawl_errors`
|
|
34
|
+
* trips `PreloadShortCircuitError` at the top of `#sendHeadRequest` and
|
|
35
|
+
* never reaches `fetchDestination`, so `successfulHosts` is never
|
|
36
|
+
* populated for it in this session. This is intentional — preload only
|
|
37
|
+
* seeds hosts whose archive evidence is "DNS-failed with no recovery", so
|
|
38
|
+
* un-burning them on a single transient success could re-introduce the
|
|
39
|
+
* cascade we are trying to prevent.
|
|
40
|
+
*
|
|
41
|
+
* **What does NOT contribute to `successfulHosts`**: external pages
|
|
42
|
+
* traversed via `fetchExternal: false` skip the HEAD pre-flight entirely
|
|
43
|
+
* (the crawler stamps a synthetic `PageData` without touching the
|
|
44
|
+
* network), so the host is not recorded as alive even if the same host
|
|
45
|
+
* appears in the crawled-internal scope. Callers must populate the set
|
|
46
|
+
* from real HTTP-response observations only.
|
|
47
|
+
* @param params - See {@link ShouldBurnHostParams}.
|
|
48
|
+
* @param params.errorKind
|
|
49
|
+
* @param params.host
|
|
50
|
+
* @param params.successfulHosts
|
|
51
|
+
* @returns `true` if the burn cache should record this host, `false` otherwise.
|
|
52
|
+
*/
|
|
53
|
+
export function shouldBurnHost({ errorKind, host, successfulHosts, }) {
|
|
54
|
+
if (errorKind !== 'dns') {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (successfulHosts.has(host)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether a HEAD pre-flight failure should trigger a GET retry and
|
|
3
|
+
* stay OUT of `destinationCache`.
|
|
4
|
+
*
|
|
5
|
+
* The two contracts are intentionally tied to one helper: anything we'll GET
|
|
6
|
+
* later because the HEAD answer might be wrong is also the same thing we
|
|
7
|
+
* must NOT freeze into the per-session cache (or the second attempt would
|
|
8
|
+
* hit the stale cached failure and skip the live retry that
|
|
9
|
+
* `Crawler.#sendHeadRequest`'s `HEAD_TIMEOUT_ESCALATION_MS` is supposed to
|
|
10
|
+
* pay for).
|
|
11
|
+
*
|
|
12
|
+
* The eligible kinds are:
|
|
13
|
+
*
|
|
14
|
+
* - **`NetTimeoutError`** — the HEAD pre-flight race fired without a
|
|
15
|
+
* server response. The escalating retry can still succeed against a
|
|
16
|
+
* slow-but-reachable host.
|
|
17
|
+
* - **`parse-error`** — `Parse Error` / `Expected HTTP/` / `Unexpected end
|
|
18
|
+
* of stream`. Usually a middlebox rewriting / truncating the HEAD reply
|
|
19
|
+
* the GET path traverses differently.
|
|
20
|
+
* - **`connection-reset`** — `ECONNRESET` / `ERR_CONNECTION_RESET` etc.
|
|
21
|
+
* middlebox dropping the connection mid-response; a retry frequently
|
|
22
|
+
* succeeds.
|
|
23
|
+
*
|
|
24
|
+
* Everything else — DNS, TLS, refused, blocked, plain timeout — is treated
|
|
25
|
+
* as a persistent within-session verdict and IS cached so repeated calls on
|
|
26
|
+
* the same host pay the network cost once.
|
|
27
|
+
* @param error - The `Error` raised by the HEAD attempt.
|
|
28
|
+
* @returns `true` when the error warrants a GET fallback AND a cache skip.
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* shouldGetFallbackOnHeadFailure(new NetTimeoutError(url)); // true
|
|
32
|
+
* shouldGetFallbackOnHeadFailure(new Error('read ECONNRESET')); // true
|
|
33
|
+
* shouldGetFallbackOnHeadFailure(new Error('Parse Error')); // true
|
|
34
|
+
* shouldGetFallbackOnHeadFailure(new Error('getaddrinfo ENOTFOUND host')); // false
|
|
35
|
+
* shouldGetFallbackOnHeadFailure(new Error('ERR_CERT_DATE_INVALID')); // false
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function shouldGetFallbackOnHeadFailure(error: Error): boolean;
|