@nitpicker/crawler 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +63 -16
- package/lib/archive/archive.js +56 -17
- 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 +92 -345
- package/lib/archive/database.js +168 -1942
- 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 +1 -1
- package/lib/archive/derive-lineage-from-parent.js +1 -1
- 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 +5 -4
- package/lib/archive/get-failed-page-messages.js +5 -4
- package/lib/archive/init-schema.d.ts +35 -39
- package/lib/archive/init-schema.js +99 -460
- 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 +127 -24
- 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/crawler/build-redirect-event.d.ts +1 -1
- package/lib/crawler/build-redirect-event.js +1 -1
- 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 +1 -1
- package/lib/crawler/clear-dns-burned-host-cache.js +1 -1
- 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 +19 -0
- package/lib/crawler/crawler.js +40 -26
- package/lib/crawler/dns-burned-host-cache.d.ts +3 -3
- package/lib/crawler/dns-burned-host-cache.js +3 -3
- package/lib/crawler/dns-burned-host-short-circuit-counter.d.ts +2 -2
- package/lib/crawler/dns-burned-host-short-circuit-counter.js +2 -2
- package/lib/crawler/inject-scope-auth.d.ts +1 -1
- package/lib/crawler/inject-scope-auth.js +1 -1
- package/lib/crawler/normalize-content-type.d.ts +1 -1
- package/lib/crawler/normalize-content-type.js +1 -1
- package/lib/crawler/types.d.ts +3 -3
- package/lib/crawler-orchestrator.d.ts +9 -0
- package/lib/crawler-orchestrator.js +44 -28
- package/lib/crawler.d.ts +12 -0
- package/lib/crawler.js +21 -0
- package/lib/permanent-error-kinds.d.ts +1 -1
- package/lib/permanent-error-kinds.js +1 -1
- package/lib/types.d.ts +1 -1
- package/lib/utils/compute-file-sha256.d.ts +5 -4
- package/lib/utils/compute-file-sha256.js +5 -4
- package/lib/utils/error/emit-error-with-retry.d.ts +1 -1
- package/lib/utils/error/emit-error-with-retry.js +1 -1
- package/package.json +10 -10
- 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-inventory-runs.d.ts +0 -29
- package/lib/archive/migrate-inventory-runs.js +0 -52
- 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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PageData } from '../../../../utils/types/types.js';
|
|
2
|
+
import type { PageSource } from '../../../types.js';
|
|
3
|
+
import type { WriteRefCaches } from '../../_shared/types.js';
|
|
4
|
+
import type { Knex } from 'knex';
|
|
5
|
+
/**
|
|
6
|
+
* Upserts page data into `content_items` + `page_meta` (inserts the
|
|
7
|
+
* identity row if new, updates if existing).
|
|
8
|
+
*
|
|
9
|
+
* `source` is intentionally NOT in the UPDATE clause — provenance is set
|
|
10
|
+
* once at INSERT time inside `resolveContentItemId`, and existing rows
|
|
11
|
+
* keep whatever label they were first inserted with, except for the
|
|
12
|
+
* priority lattice applied below: `'crawled'` > `'inventory-seed'` >
|
|
13
|
+
* `'inventory-discovered'`. The inventory feature exists to surface
|
|
14
|
+
* orphans (= URLs NOT reachable from the original crawl roots), so
|
|
15
|
+
* anything reachable via the crawled chain must be labelled `'crawled'`
|
|
16
|
+
* even if previously labelled `'inventory-*'`.
|
|
17
|
+
*
|
|
18
|
+
* The page's response headers are decomposed and written into the
|
|
19
|
+
* header dictionary tables here — per response, not deferred to
|
|
20
|
+
* crawl-end — and the resulting `header_set_id` lands on the same
|
|
21
|
+
* `content_items` UPDATE. Meta text / URL / JSON values are interned
|
|
22
|
+
* into `text_refs` / `url_refs` / `json_refs` and `page_meta` stores
|
|
23
|
+
* only the FK ids; on a re-scrape the `page_meta` row is fully replaced
|
|
24
|
+
* (`ON CONFLICT(page_id) DO UPDATE` over every column) exactly as the
|
|
25
|
+
* legacy flat-column UPDATE overwrote every column.
|
|
26
|
+
* @param knex - Knex query builder connected to the archive DB. Used as the
|
|
27
|
+
* fallback when `trx` is not provided.
|
|
28
|
+
* @param caches - The connection's write-side id caches.
|
|
29
|
+
* @param page - The scraped page data.
|
|
30
|
+
* @param isTarget - Whether this page is a crawl target.
|
|
31
|
+
* @param trx - Optional transaction all statements run through.
|
|
32
|
+
* @param source - Inventory provenance for the INSERT path. Ignored on UPDATE
|
|
33
|
+
* except through the priority lattice described above.
|
|
34
|
+
* @returns The `content_items.id` of the inserted or updated row.
|
|
35
|
+
*/
|
|
36
|
+
export declare function insertPage(knex: Knex, caches: WriteRefCaches, page: PageData, isTarget: boolean, trx?: Knex.Transaction, source?: PageSource): Promise<number>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { normalizeContentType } from '../../../../crawler/normalize-content-type.js';
|
|
2
|
+
import { computePageDenormalized } from '../../../meta/compute-page-denormalized.js';
|
|
3
|
+
import { deriveFlatFromMeta } from '../../../meta/derive-flat-from-meta.js';
|
|
4
|
+
import { deriveMetaExtras } from '../../../meta/derive-meta-extras.js';
|
|
5
|
+
import { PAGE_META_COLUMN_MAPS } from '../../../page-meta-column-maps.js';
|
|
6
|
+
import { upsertTextRefs } from '../../../populate-entity-tables/upsert-text-refs.js';
|
|
7
|
+
import { DATA_URI_URL_REFS_LIMIT } from '../../../populate-ref-tables/data-uri-url-refs-limit.js';
|
|
8
|
+
import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
|
|
9
|
+
import { upsertContentTypeRef } from '../../_shared/upsert-content-type-ref.js';
|
|
10
|
+
import { upsertJsonRef } from '../../_shared/upsert-json-ref.js';
|
|
11
|
+
import { upsertResponseHeaders } from '../../_shared/upsert-response-headers.js';
|
|
12
|
+
import { upsertUrlRef } from '../../_shared/upsert-url-ref.js';
|
|
13
|
+
/**
|
|
14
|
+
* Upserts page data into `content_items` + `page_meta` (inserts the
|
|
15
|
+
* identity row if new, updates if existing).
|
|
16
|
+
*
|
|
17
|
+
* `source` is intentionally NOT in the UPDATE clause — provenance is set
|
|
18
|
+
* once at INSERT time inside `resolveContentItemId`, and existing rows
|
|
19
|
+
* keep whatever label they were first inserted with, except for the
|
|
20
|
+
* priority lattice applied below: `'crawled'` > `'inventory-seed'` >
|
|
21
|
+
* `'inventory-discovered'`. The inventory feature exists to surface
|
|
22
|
+
* orphans (= URLs NOT reachable from the original crawl roots), so
|
|
23
|
+
* anything reachable via the crawled chain must be labelled `'crawled'`
|
|
24
|
+
* even if previously labelled `'inventory-*'`.
|
|
25
|
+
*
|
|
26
|
+
* The page's response headers are decomposed and written into the
|
|
27
|
+
* header dictionary tables here — per response, not deferred to
|
|
28
|
+
* crawl-end — and the resulting `header_set_id` lands on the same
|
|
29
|
+
* `content_items` UPDATE. Meta text / URL / JSON values are interned
|
|
30
|
+
* into `text_refs` / `url_refs` / `json_refs` and `page_meta` stores
|
|
31
|
+
* only the FK ids; on a re-scrape the `page_meta` row is fully replaced
|
|
32
|
+
* (`ON CONFLICT(page_id) DO UPDATE` over every column) exactly as the
|
|
33
|
+
* legacy flat-column UPDATE overwrote every column.
|
|
34
|
+
* @param knex - Knex query builder connected to the archive DB. Used as the
|
|
35
|
+
* fallback when `trx` is not provided.
|
|
36
|
+
* @param caches - The connection's write-side id caches.
|
|
37
|
+
* @param page - The scraped page data.
|
|
38
|
+
* @param isTarget - Whether this page is a crawl target.
|
|
39
|
+
* @param trx - Optional transaction all statements run through.
|
|
40
|
+
* @param source - Inventory provenance for the INSERT path. Ignored on UPDATE
|
|
41
|
+
* except through the priority lattice described above.
|
|
42
|
+
* @returns The `content_items.id` of the inserted or updated row.
|
|
43
|
+
*/
|
|
44
|
+
export async function insertPage(knex, caches, page, isTarget, trx, source) {
|
|
45
|
+
const qb = trx ?? knex;
|
|
46
|
+
const pageId = await resolveContentItemId(qb, caches, page.url.withoutHashAndAuth, undefined, source);
|
|
47
|
+
const flat = deriveFlatFromMeta(page.meta, page.url.href);
|
|
48
|
+
const denorm = computePageDenormalized(page.meta);
|
|
49
|
+
const extras = deriveMetaExtras(page.meta);
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
// Canonicalize so the stored dictionary value matches the exact-string
|
|
52
|
+
// page-ness predicate (`content_type_refs.raw = 'text/html'`) used by
|
|
53
|
+
// the read layer and the case-insensitive `isHtmlContentType` used in
|
|
54
|
+
// code. Responses are recorded verbatim upstream, so `Text/HTML` /
|
|
55
|
+
// `text/html ` can otherwise fork dictionary rows and silently
|
|
56
|
+
// misclassify.
|
|
57
|
+
const contentType = normalizeContentType(page.contentType);
|
|
58
|
+
const contentTypeId = contentType == null || contentType === ''
|
|
59
|
+
? null
|
|
60
|
+
: await upsertContentTypeRef(qb, caches, contentType);
|
|
61
|
+
const headerSetId = await upsertResponseHeaders(qb, caches, page.responseHeaders);
|
|
62
|
+
// Source priority on UPDATE: 'crawled' > 'inventory-seed' >
|
|
63
|
+
// 'inventory-discovered'. In current callers, `source` only arrives as
|
|
64
|
+
// `'inventory-seed'` / `'inventory-discovered'` / `undefined`
|
|
65
|
+
// (`derivePageSource` never emits `'crawled'`, and outside inventory
|
|
66
|
+
// mode `source` is `undefined` so this CASE never runs). The
|
|
67
|
+
// `? = 'crawled'` branch is therefore reachable only via a future
|
|
68
|
+
// call site that wants to explicitly assert a crawled lineage —
|
|
69
|
+
// today the actual crawled-wins downgrade fires in
|
|
70
|
+
// `resolveContentItemId` when an anchor lineage `'crawled'` lands on
|
|
71
|
+
// an existing `'inventory-*'` row. The branch is kept so the CASE
|
|
72
|
+
// completely describes the priority lattice in one place.
|
|
73
|
+
const sourceUpdate = source === undefined
|
|
74
|
+
? {}
|
|
75
|
+
: {
|
|
76
|
+
source: qb.raw(`CASE
|
|
77
|
+
WHEN source = 'crawled' OR ? = 'crawled' THEN 'crawled'
|
|
78
|
+
WHEN source = 'inventory-seed' OR ? = 'inventory-seed' THEN 'inventory-seed'
|
|
79
|
+
WHEN source = 'inventory-discovered' OR ? = 'inventory-discovered' THEN 'inventory-discovered'
|
|
80
|
+
ELSE source
|
|
81
|
+
END`, [source, source, source]),
|
|
82
|
+
};
|
|
83
|
+
await qb('content_items')
|
|
84
|
+
.where('id', pageId)
|
|
85
|
+
.update({
|
|
86
|
+
scraped: 1,
|
|
87
|
+
is_target: isTarget ? 1 : 0,
|
|
88
|
+
is_external: page.isExternal ? 1 : 0,
|
|
89
|
+
status: page.status,
|
|
90
|
+
status_text: page.statusText,
|
|
91
|
+
content_type_id: contentTypeId,
|
|
92
|
+
content_length: page.contentLength,
|
|
93
|
+
header_set_id: headerSetId,
|
|
94
|
+
// Timestamps: `first_crawled_at` is set only on first scrape —
|
|
95
|
+
// `COALESCE` preserves the existing value so a re-scrape
|
|
96
|
+
// (`--append`, `--retry-failed`) does not erase the discovery time.
|
|
97
|
+
// `last_crawled_at` is updated every successful scrape.
|
|
98
|
+
first_crawled_at: qb.raw('COALESCE(first_crawled_at, ?)', [now]),
|
|
99
|
+
last_crawled_at: now,
|
|
100
|
+
is_skipped: page.isSkipped ? 1 : 0,
|
|
101
|
+
...sourceUpdate,
|
|
102
|
+
});
|
|
103
|
+
// The cache's `source` mirror only tracks the resolveContentItemId
|
|
104
|
+
// lattice; when the CASE above rewrites the column, refresh the cache
|
|
105
|
+
// from the caller-supplied label so a later crawled-wins check sees
|
|
106
|
+
// the current value. (`'crawled'` never arrives here today — see the
|
|
107
|
+
// CASE comment — so the only observable effect is seed-over-discovered
|
|
108
|
+
// promotion, which the lattice below reproduces.)
|
|
109
|
+
if (source !== undefined) {
|
|
110
|
+
const entry = caches.contentItems.get(page.url.withoutHashAndAuth);
|
|
111
|
+
if (entry !== undefined && entry.source === 'inventory-discovered') {
|
|
112
|
+
entry.source = source;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
await upsertPageMeta(qb, caches, pageId, flat, denorm, extras);
|
|
116
|
+
return pageId;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Builds and upserts the `page_meta` row for one scraped page: interns
|
|
120
|
+
* text / URL / JSON values into their ref tables, then fully replaces
|
|
121
|
+
* the row (every column participates in the `DO UPDATE`) so a re-scrape
|
|
122
|
+
* behaves exactly like the legacy per-column UPDATE.
|
|
123
|
+
* @param qb - Knex instance or transaction.
|
|
124
|
+
* @param caches - The connection's write-side id caches.
|
|
125
|
+
* @param pageId - The owning `content_items.id`.
|
|
126
|
+
* @param flat - Flat meta columns from `deriveFlatFromMeta`.
|
|
127
|
+
* @param denorm - Denormalised aggregates from `computePageDenormalized`.
|
|
128
|
+
* @param extras - Nested-Meta catch-all from `deriveMetaExtras`.
|
|
129
|
+
*/
|
|
130
|
+
async function upsertPageMeta(qb, caches, pageId, flat, denorm, extras) {
|
|
131
|
+
const texts = new Set();
|
|
132
|
+
for (const { source } of PAGE_META_COLUMN_MAPS.text) {
|
|
133
|
+
const value = flat[source];
|
|
134
|
+
if (typeof value === 'string' && value !== '') {
|
|
135
|
+
texts.add(value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const textIds = await upsertTextRefs(qb, texts);
|
|
139
|
+
const row = {
|
|
140
|
+
page_id: pageId,
|
|
141
|
+
lang: flat.lang,
|
|
142
|
+
dir: flat.dir,
|
|
143
|
+
charset: flat.charset,
|
|
144
|
+
base_href: flat.baseHref,
|
|
145
|
+
viewport_raw: flat.viewport_raw,
|
|
146
|
+
theme_color: flat.themeColor,
|
|
147
|
+
application_name: flat.applicationName,
|
|
148
|
+
author: flat.author,
|
|
149
|
+
generator: flat.generator,
|
|
150
|
+
publisher: flat.publisher,
|
|
151
|
+
robots_noindex: flat.robots_noindex,
|
|
152
|
+
robots_nofollow: flat.robots_nofollow,
|
|
153
|
+
robots_noarchive: flat.robots_noarchive,
|
|
154
|
+
robots_noimageindex: flat.robots_noimageindex,
|
|
155
|
+
googlebot: flat.googlebot,
|
|
156
|
+
og_type: flat.og_type,
|
|
157
|
+
og_site_name: flat.og_site_name,
|
|
158
|
+
og_image_alt: flat.og_image_alt,
|
|
159
|
+
og_image_width: flat.og_image_width,
|
|
160
|
+
og_image_height: flat.og_image_height,
|
|
161
|
+
og_locale: flat.og_locale,
|
|
162
|
+
og_article_published_time: flat.og_article_published_time,
|
|
163
|
+
og_article_modified_time: flat.og_article_modified_time,
|
|
164
|
+
twitter_card: flat.twitter_card,
|
|
165
|
+
twitter_site: flat.twitter_site,
|
|
166
|
+
twitter_creator: flat.twitter_creator,
|
|
167
|
+
fb_app_id: flat.fb_app_id,
|
|
168
|
+
verification_google: flat.verification_google,
|
|
169
|
+
format_detection_telephone: flat.formatDetection_telephone,
|
|
170
|
+
// Denormalised aggregates: written once at scrape time so list reads
|
|
171
|
+
// (Sheets, page-detail summary) can answer "how many JSON-LD
|
|
172
|
+
// entries?" and "which Wappalyzer providers?" by selecting a single
|
|
173
|
+
// column rather than running a GROUP BY join on every read.
|
|
174
|
+
tag_count: denorm.tag_count,
|
|
175
|
+
jsonld_count: denorm.jsonld_count,
|
|
176
|
+
tags_providers_csv: denorm.tags_providers_csv,
|
|
177
|
+
};
|
|
178
|
+
for (const { source, target } of PAGE_META_COLUMN_MAPS.text) {
|
|
179
|
+
const value = flat[source];
|
|
180
|
+
row[target] =
|
|
181
|
+
typeof value === 'string' && value !== '' ? (textIds.get(value) ?? null) : null;
|
|
182
|
+
}
|
|
183
|
+
for (const { source, target } of PAGE_META_COLUMN_MAPS.url) {
|
|
184
|
+
const value = flat[source];
|
|
185
|
+
if (typeof value !== 'string' || value === '' || isLargeDataUri(value)) {
|
|
186
|
+
// `page_meta` has no `*_blob_id` companion columns, so a large
|
|
187
|
+
// data URI in a URL slot (a base64 favicon, typically) is
|
|
188
|
+
// dropped rather than ballooning `url_refs` with an opaque
|
|
189
|
+
// payload — matching the archive-migration behaviour.
|
|
190
|
+
row[target] = null;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
row[target] = await upsertUrlRef(qb, caches, value);
|
|
194
|
+
}
|
|
195
|
+
const extrasJson = JSON.stringify(extras);
|
|
196
|
+
row.meta_extras_json_id =
|
|
197
|
+
extrasJson === '' ? null : await upsertJsonRef(qb, caches, extrasJson);
|
|
198
|
+
await qb('page_meta').insert(row).onConflict('page_id').merge();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Returns `true` when `value` is a `data:` URI larger than the routing
|
|
202
|
+
* threshold — the same rule that routes image `src` values to
|
|
203
|
+
* `blob_refs` instead of `url_refs`.
|
|
204
|
+
* @param value - Raw URL column value.
|
|
205
|
+
*/
|
|
206
|
+
function isLargeDataUri(value) {
|
|
207
|
+
return value.startsWith('data:') && value.length > DATA_URI_URL_REFS_LIMIT;
|
|
208
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PageData } from '../../../../utils/types/types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the page's Wappalyzer tag rows with the freshly captured set.
|
|
5
|
+
* Called inside `updatePage`'s transaction unconditionally — tag
|
|
6
|
+
* detection draws on `<script src>` / `<iframe src>` / window globals /
|
|
7
|
+
* response headers, not the HTML body, so external pages that skip
|
|
8
|
+
* rendering still contribute tags.
|
|
9
|
+
*
|
|
10
|
+
* Same empty-guard as `insertJsonLd`: an empty array does not wipe
|
|
11
|
+
* prior rows on a degraded re-scrape.
|
|
12
|
+
* @param pageId
|
|
13
|
+
* @param meta
|
|
14
|
+
* @param trx
|
|
15
|
+
*/
|
|
16
|
+
export declare function insertTags(pageId: number, meta: PageData['meta'], trx: Knex.Transaction): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
2
|
+
import { extractTagsForArchive } from '../../../meta/extract-tags-for-archive.js';
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the page's Wappalyzer tag rows with the freshly captured set.
|
|
5
|
+
* Called inside `updatePage`'s transaction unconditionally — tag
|
|
6
|
+
* detection draws on `<script src>` / `<iframe src>` / window globals /
|
|
7
|
+
* response headers, not the HTML body, so external pages that skip
|
|
8
|
+
* rendering still contribute tags.
|
|
9
|
+
*
|
|
10
|
+
* Same empty-guard as `insertJsonLd`: an empty array does not wipe
|
|
11
|
+
* prior rows on a degraded re-scrape.
|
|
12
|
+
* @param pageId
|
|
13
|
+
* @param meta
|
|
14
|
+
* @param trx
|
|
15
|
+
*/
|
|
16
|
+
export async function insertTags(pageId, meta, trx) {
|
|
17
|
+
const partial = extractTagsForArchive(meta.tags);
|
|
18
|
+
if (partial.length === 0)
|
|
19
|
+
return;
|
|
20
|
+
const rows = partial.map((p) => ({
|
|
21
|
+
pageId,
|
|
22
|
+
provider: p.provider,
|
|
23
|
+
category: p.category,
|
|
24
|
+
externalId: p.externalId,
|
|
25
|
+
version: p.version,
|
|
26
|
+
confidence: p.confidence,
|
|
27
|
+
categories: JSON.stringify(p.categories),
|
|
28
|
+
sources: JSON.stringify(p.sources),
|
|
29
|
+
}));
|
|
30
|
+
await trx('page_tags').where('pageId', pageId).delete();
|
|
31
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
32
|
+
await trx('page_tags').insert(chunk);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PageSource } from '../../../types.js';
|
|
2
|
+
import type { WriteRefCaches } from '../../_shared/types.js';
|
|
3
|
+
import type { Knex } from 'knex';
|
|
4
|
+
/**
|
|
5
|
+
* Points each redirect-source URL at the destination page, marking it scraped
|
|
6
|
+
* and clearing any content it owned in a former life.
|
|
7
|
+
*
|
|
8
|
+
* Shared by `updatePage` (which also renders and stores the destination)
|
|
9
|
+
* and `recordRedirect` (which only records the edge for a destination
|
|
10
|
+
* rendered elsewhere). Self-redirects (source equal to the destination) are
|
|
11
|
+
* skipped so a page is never marked as redirecting to itself — that would
|
|
12
|
+
* exclude it from reports via the `whereNull('redirect_dest_id')` filter.
|
|
13
|
+
* @param trx - The active transaction. All SQL below is executed exclusively
|
|
14
|
+
* through this transaction; there is no separate `knex` fallback because
|
|
15
|
+
* the callers (`updatePage` / `recordRedirect`) always invoke this helper
|
|
16
|
+
* from within a transaction.
|
|
17
|
+
* @param caches - The connection's write-side id caches.
|
|
18
|
+
* @param sources - Redirect-source URLs (normalised): the original URL plus
|
|
19
|
+
* any intermediate hops. Empty when the page was not redirected.
|
|
20
|
+
* @param destId - `content_items.id` of the redirect destination page.
|
|
21
|
+
* @param destUrlNormalized - Normalised destination URL, used to detect and
|
|
22
|
+
* skip self-redirects.
|
|
23
|
+
* @param isExternal - Whether the sources are external to the crawl scope.
|
|
24
|
+
* @param chainLineageSource - Lineage label propagated to each intermediate
|
|
25
|
+
* hop's row (passed through to {@link resolveContentItemId}). Derived by
|
|
26
|
+
* the caller from the **originating** page's source (`page.url`), not from
|
|
27
|
+
* the destination — intermediates are reached transitively from the
|
|
28
|
+
* originating render, so they inherit its lineage. Pass `'inventory-discovered'`
|
|
29
|
+
* for chains rooted at inventory-seed/discovered pages so new intermediates
|
|
30
|
+
* stay in the inventory chain; pass `'crawled'` for crawled chains so the
|
|
31
|
+
* crawled-wins downgrade inside `resolveContentItemId` fires on existing
|
|
32
|
+
* `'inventory-*'` intermediates a crawled chain reaches. Pass `undefined`
|
|
33
|
+
* to fall back to the DB DEFAULT (`'crawled'`) on INSERT without
|
|
34
|
+
* triggering the downgrade on existing rows.
|
|
35
|
+
*/
|
|
36
|
+
export declare function linkRedirectSources(trx: Knex.Transaction, caches: WriteRefCaches, sources: readonly string[], destId: number, destUrlNormalized: string, isExternal: boolean, chainLineageSource?: PageSource): Promise<void>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { dbLog } from '../../../debug.js';
|
|
2
|
+
import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
|
|
3
|
+
/**
|
|
4
|
+
* Points each redirect-source URL at the destination page, marking it scraped
|
|
5
|
+
* and clearing any content it owned in a former life.
|
|
6
|
+
*
|
|
7
|
+
* Shared by `updatePage` (which also renders and stores the destination)
|
|
8
|
+
* and `recordRedirect` (which only records the edge for a destination
|
|
9
|
+
* rendered elsewhere). Self-redirects (source equal to the destination) are
|
|
10
|
+
* skipped so a page is never marked as redirecting to itself — that would
|
|
11
|
+
* exclude it from reports via the `whereNull('redirect_dest_id')` filter.
|
|
12
|
+
* @param trx - The active transaction. All SQL below is executed exclusively
|
|
13
|
+
* through this transaction; there is no separate `knex` fallback because
|
|
14
|
+
* the callers (`updatePage` / `recordRedirect`) always invoke this helper
|
|
15
|
+
* from within a transaction.
|
|
16
|
+
* @param caches - The connection's write-side id caches.
|
|
17
|
+
* @param sources - Redirect-source URLs (normalised): the original URL plus
|
|
18
|
+
* any intermediate hops. Empty when the page was not redirected.
|
|
19
|
+
* @param destId - `content_items.id` of the redirect destination page.
|
|
20
|
+
* @param destUrlNormalized - Normalised destination URL, used to detect and
|
|
21
|
+
* skip self-redirects.
|
|
22
|
+
* @param isExternal - Whether the sources are external to the crawl scope.
|
|
23
|
+
* @param chainLineageSource - Lineage label propagated to each intermediate
|
|
24
|
+
* hop's row (passed through to {@link resolveContentItemId}). Derived by
|
|
25
|
+
* the caller from the **originating** page's source (`page.url`), not from
|
|
26
|
+
* the destination — intermediates are reached transitively from the
|
|
27
|
+
* originating render, so they inherit its lineage. Pass `'inventory-discovered'`
|
|
28
|
+
* for chains rooted at inventory-seed/discovered pages so new intermediates
|
|
29
|
+
* stay in the inventory chain; pass `'crawled'` for crawled chains so the
|
|
30
|
+
* crawled-wins downgrade inside `resolveContentItemId` fires on existing
|
|
31
|
+
* `'inventory-*'` intermediates a crawled chain reaches. Pass `undefined`
|
|
32
|
+
* to fall back to the DB DEFAULT (`'crawled'`) on INSERT without
|
|
33
|
+
* triggering the downgrade on existing rows.
|
|
34
|
+
*/
|
|
35
|
+
export async function linkRedirectSources(trx, caches, sources, destId, destUrlNormalized, isExternal, chainLineageSource) {
|
|
36
|
+
for (const redirect of sources) {
|
|
37
|
+
if (redirect === destUrlNormalized) {
|
|
38
|
+
dbLog('Skip self-redirect: %s', redirect);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
dbLog('Set redirected url: %s -> id:%d', redirect, destId);
|
|
42
|
+
// Pass `chainLineageSource` through so a brand-new intermediate
|
|
43
|
+
// hop INSERTed here inherits the originating page's lineage label
|
|
44
|
+
// (inventory-discovered when the originating chain is in the
|
|
45
|
+
// inventory chain, undefined otherwise). The crawled-wins
|
|
46
|
+
// downgrade inside `resolveContentItemId` still fires when this
|
|
47
|
+
// argument is `'crawled'`, matching the anchor-lineage propagation
|
|
48
|
+
// contract — an existing inventory-* intermediate that is later
|
|
49
|
+
// traversed by a `'crawled'` chain gets downgraded.
|
|
50
|
+
const redirectId = await resolveContentItemId(trx, caches, redirect, undefined, chainLineageSource);
|
|
51
|
+
await trx('content_items')
|
|
52
|
+
.where('id', redirectId)
|
|
53
|
+
.update({
|
|
54
|
+
scraped: 1,
|
|
55
|
+
redirect_dest_id: destId,
|
|
56
|
+
is_external: isExternal ? 1 : 0,
|
|
57
|
+
});
|
|
58
|
+
// Conditional `301 Moved Permanently` stamp — applied ONLY when the
|
|
59
|
+
// row carries no definitive status yet (NULL or the `-1`
|
|
60
|
+
// hard-failure sentinel). HEAD pre-flight does not retain each
|
|
61
|
+
// hop's individual status code (`redirectPaths` is a URL[] without
|
|
62
|
+
// statuses), so the only honest answer for an unknown-status hop is
|
|
63
|
+
// "some 3xx" — 301 is the canonical representative.
|
|
64
|
+
//
|
|
65
|
+
// We deliberately do NOT overwrite an existing definitive status
|
|
66
|
+
// (200 / 302 / 307 / etc.): a row that already captured a concrete
|
|
67
|
+
// status from a prior direct scrape would lose accuracy. The stamp
|
|
68
|
+
// only flips two cases:
|
|
69
|
+
// - NULL: a placeholder row created by `resolveContentItemId`
|
|
70
|
+
// because the URL was reached only as a redirect target / source,
|
|
71
|
+
// never directly scraped. Without the stamp the row is invisible
|
|
72
|
+
// on the Errors view's status distribution.
|
|
73
|
+
// - -1: a row that recorded a hard scrape failure (e.g. a puppeteer
|
|
74
|
+
// goto returned null on a HTTPS→HTTP downgrade redirect) BEFORE
|
|
75
|
+
// the chain was understood. That `-1` then conflated "real
|
|
76
|
+
// failure" with "actually a redirect source we now know about",
|
|
77
|
+
// polluting the `-1` bucket AND inflating the `--retry-failed`
|
|
78
|
+
// target (via the `whereNull('redirect_dest_id')` filter — the
|
|
79
|
+
// redirect_dest_id update above already excludes the row from
|
|
80
|
+
// retry; this stamp restores the visible identity).
|
|
81
|
+
await trx('content_items')
|
|
82
|
+
.where('id', redirectId)
|
|
83
|
+
.where((qb) => qb.whereNull('status').orWhere('status', -1))
|
|
84
|
+
.update({ status: 301, status_text: 'Moved Permanently' });
|
|
85
|
+
// A page that used to be scraped as content can later turn into a
|
|
86
|
+
// redirect source. It owns no content anymore, so drop any anchors /
|
|
87
|
+
// images it captured in its former life — otherwise they linger and
|
|
88
|
+
// leak into referrer / incoming-link reads (which do not filter out
|
|
89
|
+
// redirect sources).
|
|
90
|
+
await trx('anchor_edges').where('page_id', redirectId).delete();
|
|
91
|
+
await trx('image_items').where('page_id', redirectId).delete();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { PageData } from '../../../../utils/types/types.js';
|
|
2
|
+
import type { PageSource } from '../../../types.js';
|
|
3
|
+
import type { WriteRefCaches } from '../../_shared/types.js';
|
|
4
|
+
import type { Knex } from 'knex';
|
|
5
|
+
/**
|
|
6
|
+
* Records a redirect edge (source → destination) **without** re-storing the
|
|
7
|
+
* destination's content.
|
|
8
|
+
*
|
|
9
|
+
* The crawler renders a many-to-one redirect destination exactly once. For
|
|
10
|
+
* every subsequent source URL that redirects to that already-rendered
|
|
11
|
+
* destination, it calls this instead of {@link ./update-page.ts} (#73).
|
|
12
|
+
* Routing a content-less HEAD result through `updatePage` would funnel it
|
|
13
|
+
* into `insertPage` and overwrite the destination's good title / meta with
|
|
14
|
+
* empty values, so the dedicated edge-only path is required.
|
|
15
|
+
*
|
|
16
|
+
* The destination row is resolved (created on demand if a concurrent
|
|
17
|
+
* in-flight render has not committed it yet) so the edge always points at a
|
|
18
|
+
* valid id; the single render fills in the destination's content under that
|
|
19
|
+
* same id. The destination's existing anchors / images are never touched
|
|
20
|
+
* here.
|
|
21
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
22
|
+
* @param caches - The connection's write-side id caches.
|
|
23
|
+
* @param page - HEAD-resolved page data carrying the redirect chain. Its
|
|
24
|
+
* `anchorList` / `imageList` are ignored (a redirect source owns no content).
|
|
25
|
+
* @param source - Inventory provenance forwarded by the orchestrator
|
|
26
|
+
* (`Archive.setRedirect` → here) for the redirect-edge fast path. Used
|
|
27
|
+
* as the fallback when the originating URL's row does NOT yet exist in
|
|
28
|
+
* the archive (`#73` convergence on first sight, js-redirect rescue
|
|
29
|
+
* before any prior write). When the originating row already exists
|
|
30
|
+
* (e.g. anchor-lineage INSERT from a prior pass), its stored `source`
|
|
31
|
+
* takes precedence so transitive lineage is preserved across resume /
|
|
32
|
+
* retry-failed sessions. `undefined` keeps the DB DEFAULT `'crawled'`
|
|
33
|
+
* on a brand-new destination row.
|
|
34
|
+
*/
|
|
35
|
+
export declare function recordRedirect(knex: Knex, caches: WriteRefCaches, page: PageData, source?: PageSource): Promise<void>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
|
|
2
|
+
import { dbLog } from '../../../debug.js';
|
|
3
|
+
import { deriveLineageFromParent } from '../../../derive-lineage-from-parent.js';
|
|
4
|
+
import { resolveRedirectChain } from '../../../resolve-redirect-chain.js';
|
|
5
|
+
import { clearWriteRefCaches } from '../../_shared/clear-write-ref-caches.js';
|
|
6
|
+
import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
|
|
7
|
+
import { linkRedirectSources } from './link-redirect-sources.js';
|
|
8
|
+
/**
|
|
9
|
+
* Records a redirect edge (source → destination) **without** re-storing the
|
|
10
|
+
* destination's content.
|
|
11
|
+
*
|
|
12
|
+
* The crawler renders a many-to-one redirect destination exactly once. For
|
|
13
|
+
* every subsequent source URL that redirects to that already-rendered
|
|
14
|
+
* destination, it calls this instead of {@link ./update-page.ts} (#73).
|
|
15
|
+
* Routing a content-less HEAD result through `updatePage` would funnel it
|
|
16
|
+
* into `insertPage` and overwrite the destination's good title / meta with
|
|
17
|
+
* empty values, so the dedicated edge-only path is required.
|
|
18
|
+
*
|
|
19
|
+
* The destination row is resolved (created on demand if a concurrent
|
|
20
|
+
* in-flight render has not committed it yet) so the edge always points at a
|
|
21
|
+
* valid id; the single render fills in the destination's content under that
|
|
22
|
+
* same id. The destination's existing anchors / images are never touched
|
|
23
|
+
* here.
|
|
24
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
25
|
+
* @param caches - The connection's write-side id caches.
|
|
26
|
+
* @param page - HEAD-resolved page data carrying the redirect chain. Its
|
|
27
|
+
* `anchorList` / `imageList` are ignored (a redirect source owns no content).
|
|
28
|
+
* @param source - Inventory provenance forwarded by the orchestrator
|
|
29
|
+
* (`Archive.setRedirect` → here) for the redirect-edge fast path. Used
|
|
30
|
+
* as the fallback when the originating URL's row does NOT yet exist in
|
|
31
|
+
* the archive (`#73` convergence on first sight, js-redirect rescue
|
|
32
|
+
* before any prior write). When the originating row already exists
|
|
33
|
+
* (e.g. anchor-lineage INSERT from a prior pass), its stored `source`
|
|
34
|
+
* takes precedence so transitive lineage is preserved across resume /
|
|
35
|
+
* retry-failed sessions. `undefined` keeps the DB DEFAULT `'crawled'`
|
|
36
|
+
* on a brand-new destination row.
|
|
37
|
+
*/
|
|
38
|
+
export async function recordRedirect(knex, caches, page, source) {
|
|
39
|
+
const { destUrl, sources } = resolveRedirectChain(page.url.withoutHashAndAuth, page.redirectPaths);
|
|
40
|
+
// No redirect chain (the URL is itself the already-rendered destination,
|
|
41
|
+
// reached both directly and via a redirect) → there is no edge to write.
|
|
42
|
+
// Returning here avoids opening a transaction and, crucially, avoids
|
|
43
|
+
// `resolveContentItemId` inserting a content-less placeholder row for a
|
|
44
|
+
// destination that may not have been written yet.
|
|
45
|
+
if (sources.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const destUrlObject = parseUrl(destUrl);
|
|
49
|
+
if (!destUrlObject) {
|
|
50
|
+
// A malformed redirect target should not abort the whole crawl (this
|
|
51
|
+
// runs inside the WriteQueue, whose rejection aborts the run). Recording
|
|
52
|
+
// a single redirect edge is best-effort, so skip it and move on. Unlike
|
|
53
|
+
// `updatePage`, there is no page content at stake here.
|
|
54
|
+
dbLog('recordRedirect: skip malformed destination URL: %s', destUrl);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
await knex.transaction(async (trx) => {
|
|
59
|
+
// Pass the caller-supplied `source` straight through so a brand-new
|
|
60
|
+
// destination row INSERTed here picks up the inventory lineage
|
|
61
|
+
// (instead of the DB DEFAULT `'crawled'`) when the caller is in the
|
|
62
|
+
// inventory chain — without the pass-through, inventory lineage
|
|
63
|
+
// would be laundered to `'crawled'` for js-redirect rescue / #73
|
|
64
|
+
// convergence destinations that have not yet been rendered.
|
|
65
|
+
const destId = await resolveContentItemId(trx, caches, destUrlObject.withoutHashAndAuth, undefined, source);
|
|
66
|
+
// Chain lineage propagates FROM the originating URL (`page.url`),
|
|
67
|
+
// NOT from the destination. The originating URL is what initiated
|
|
68
|
+
// the redirect chain, so its lineage is what every intermediate hop
|
|
69
|
+
// transitively inherits. Reading from the destination would
|
|
70
|
+
// mis-propagate in "inventory-seed → ... → existing crawled dest"
|
|
71
|
+
// chains: the intermediates are reached only via the inventory
|
|
72
|
+
// chain, so they belong to the inventory chain even though the
|
|
73
|
+
// chain happens to land on a crawled URL. The `'crawled'` fallback
|
|
74
|
+
// arms the crawled-wins downgrade for existing `'inventory-*'`
|
|
75
|
+
// intermediates that a crawled chain reaches.
|
|
76
|
+
const cachedOriginating = caches.contentItems.get(page.url.withoutHashAndAuth);
|
|
77
|
+
let originatingSource = cachedOriginating?.source;
|
|
78
|
+
if (originatingSource === undefined) {
|
|
79
|
+
const [originatingRow] = (await trx
|
|
80
|
+
.select('ci.source')
|
|
81
|
+
.from('content_items as ci')
|
|
82
|
+
.join('url_refs as ur', 'ur.id', 'ci.url_id')
|
|
83
|
+
.where('ur.url', page.url.withoutHashAndAuth));
|
|
84
|
+
originatingSource = originatingRow?.source ?? source;
|
|
85
|
+
}
|
|
86
|
+
const chainLineageSource = deriveLineageFromParent(originatingSource, 'crawled');
|
|
87
|
+
await linkRedirectSources(trx, caches, sources, destId, destUrlObject.withoutHashAndAuth, page.isExternal, chainLineageSource);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// A rolled-back transaction can leave ids cached that no longer
|
|
92
|
+
// correspond to any row (AUTOINCREMENT never rewinds) — see
|
|
93
|
+
// `clearWriteRefCaches` for why a full clear, not a partial one, is
|
|
94
|
+
// required. `emitErrorAndRetry` may retry this whole call, so the
|
|
95
|
+
// cache must be clean before the next attempt (same guard as
|
|
96
|
+
// `updatePage`).
|
|
97
|
+
clearWriteRefCaches(caches);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WriteRefCaches } from '../../_shared/types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Marks a page as skipped in the database with the given reason.
|
|
5
|
+
* Creates the `content_items` row (with its `url_refs` entry) if it does
|
|
6
|
+
* not already exist.
|
|
7
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
8
|
+
* @param caches - The connection's write-side id caches.
|
|
9
|
+
* @param url - The URL of the skipped page.
|
|
10
|
+
* @param reason - The reason the page was skipped.
|
|
11
|
+
* @param isExternal - Whether the page is on an external domain. Defaults to `false`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function setSkippedPage(knex: Knex, caches: WriteRefCaches, url: string, reason: string, isExternal?: boolean): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
|
|
2
|
+
/**
|
|
3
|
+
* Marks a page as skipped in the database with the given reason.
|
|
4
|
+
* Creates the `content_items` row (with its `url_refs` entry) if it does
|
|
5
|
+
* not already exist.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @param caches - The connection's write-side id caches.
|
|
8
|
+
* @param url - The URL of the skipped page.
|
|
9
|
+
* @param reason - The reason the page was skipped.
|
|
10
|
+
* @param isExternal - Whether the page is on an external domain. Defaults to `false`.
|
|
11
|
+
*/
|
|
12
|
+
export async function setSkippedPage(knex, caches, url, reason, isExternal = false) {
|
|
13
|
+
const pageId = await resolveContentItemId(knex, caches, url, isExternal ? 1 : 0);
|
|
14
|
+
await knex('content_items')
|
|
15
|
+
.where('id', pageId)
|
|
16
|
+
.update({
|
|
17
|
+
scraped: 1,
|
|
18
|
+
is_external: isExternal ? 1 : 0,
|
|
19
|
+
is_skipped: 1,
|
|
20
|
+
skip_reason: reason,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PageDataWithDomPaths, PageSource } from '../../../types.js';
|
|
2
|
+
import type { WriteRefCaches } from '../../_shared/types.js';
|
|
3
|
+
import type { Knex } from 'knex';
|
|
4
|
+
/**
|
|
5
|
+
* Inserts or updates a crawled page — `content_items` + `page_meta` plus its
|
|
6
|
+
* redirect chain, `anchor_edges`, `image_items`, and (when `writeHtml`) its
|
|
7
|
+
* compressed HTML snapshot BLOB.
|
|
8
|
+
*
|
|
9
|
+
* Self-redirects (where the source URL equals the destination URL after
|
|
10
|
+
* normalization) are skipped to avoid marking a page as redirected to itself
|
|
11
|
+
* — a situation caused by authentication challenges (e.g. Basic Auth 302)
|
|
12
|
+
* that would otherwise exclude the page from reports via the
|
|
13
|
+
* `whereNull('redirect_dest_id')` filter.
|
|
14
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
15
|
+
* @param caches - The connection's write-side id caches.
|
|
16
|
+
* @param page - The page data to store, optionally carrying the in-browser
|
|
17
|
+
* dom-path capture (see {@link PageDataWithDomPaths}).
|
|
18
|
+
* @param writeHtml - When `true`, this call is allowed to insert (or clear)
|
|
19
|
+
* the page's HTML blob. `setExternalPage` passes `false` because external
|
|
20
|
+
* metadata-only scrapes never carry HTML and must not perturb an already
|
|
21
|
+
* stored body.
|
|
22
|
+
* @param isTarget - Whether this page is a crawl target.
|
|
23
|
+
* @param source - Provenance label written ONLY when the row is freshly
|
|
24
|
+
* inserted. Existing rows keep their original `source` (this is why a
|
|
25
|
+
* second `crawl --inventory` does not "demote" an `'inventory-seed'` row
|
|
26
|
+
* that was discovered earlier).
|
|
27
|
+
* @returns The database `pageId` (`content_items.id`) of the inserted or updated row.
|
|
28
|
+
*/
|
|
29
|
+
export declare function updatePage(knex: Knex, caches: WriteRefCaches, page: PageDataWithDomPaths, writeHtml: boolean, isTarget: boolean, source?: PageSource): Promise<number>;
|