@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,334 @@
|
|
|
1
|
+
import { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
|
|
2
|
+
import { isHtmlContentType } from '../../../../crawler/is-html-content-type.js';
|
|
3
|
+
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
4
|
+
import { dbLog } from '../../../debug.js';
|
|
5
|
+
import { deriveLineageFromParent } from '../../../derive-lineage-from-parent.js';
|
|
6
|
+
import { matchImagesToDomPaths } from '../../../populate-entity-tables/match-images-to-dom-paths.js';
|
|
7
|
+
import { upsertTextRefs } from '../../../populate-entity-tables/upsert-text-refs.js';
|
|
8
|
+
import { resolveRedirectChain } from '../../../resolve-redirect-chain.js';
|
|
9
|
+
import { clearWriteRefCaches } from '../../_shared/clear-write-ref-caches.js';
|
|
10
|
+
import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
|
|
11
|
+
import { resolveUrlOrBlob } from '../../_shared/resolve-url-or-blob.js';
|
|
12
|
+
import { insertJsonLd } from './insert-jsonld.js';
|
|
13
|
+
import { insertPage } from './insert-page.js';
|
|
14
|
+
import { insertTags } from './insert-tags.js';
|
|
15
|
+
import { linkRedirectSources } from './link-redirect-sources.js';
|
|
16
|
+
import { writePageHtmlBlob } from './write-page-html-blob.js';
|
|
17
|
+
/**
|
|
18
|
+
* Inserts or updates a crawled page — `content_items` + `page_meta` plus its
|
|
19
|
+
* redirect chain, `anchor_edges`, `image_items`, and (when `writeHtml`) its
|
|
20
|
+
* compressed HTML snapshot BLOB.
|
|
21
|
+
*
|
|
22
|
+
* Self-redirects (where the source URL equals the destination URL after
|
|
23
|
+
* normalization) are skipped to avoid marking a page as redirected to itself
|
|
24
|
+
* — a situation caused by authentication challenges (e.g. Basic Auth 302)
|
|
25
|
+
* that would otherwise exclude the page from reports via the
|
|
26
|
+
* `whereNull('redirect_dest_id')` filter.
|
|
27
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
28
|
+
* @param caches - The connection's write-side id caches.
|
|
29
|
+
* @param page - The page data to store, optionally carrying the in-browser
|
|
30
|
+
* dom-path capture (see {@link PageDataWithDomPaths}).
|
|
31
|
+
* @param writeHtml - When `true`, this call is allowed to insert (or clear)
|
|
32
|
+
* the page's HTML blob. `setExternalPage` passes `false` because external
|
|
33
|
+
* metadata-only scrapes never carry HTML and must not perturb an already
|
|
34
|
+
* stored body.
|
|
35
|
+
* @param isTarget - Whether this page is a crawl target.
|
|
36
|
+
* @param source - Provenance label written ONLY when the row is freshly
|
|
37
|
+
* inserted. Existing rows keep their original `source` (this is why a
|
|
38
|
+
* second `crawl --inventory` does not "demote" an `'inventory-seed'` row
|
|
39
|
+
* that was discovered earlier).
|
|
40
|
+
* @returns The database `pageId` (`content_items.id`) of the inserted or updated row.
|
|
41
|
+
*/
|
|
42
|
+
export async function updatePage(knex, caches, page, writeHtml, isTarget, source) {
|
|
43
|
+
const { destUrl, sources } = resolveRedirectChain(page.url.withoutHashAndAuth, page.redirectPaths);
|
|
44
|
+
const destUrlObject = parseUrl(destUrl);
|
|
45
|
+
if (!destUrlObject) {
|
|
46
|
+
throw new Error(`Failed to parse URL: ${destUrl}`);
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return await knex.transaction(async (trx) => {
|
|
50
|
+
return await updatePageInTransaction(trx, knex, caches, page, destUrlObject, sources, writeHtml, isTarget, source);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// A rolled-back transaction can leave ids cached that no longer
|
|
55
|
+
// correspond to any row (AUTOINCREMENT never rewinds) — see
|
|
56
|
+
// `clearWriteRefCaches` for why a full clear, not a partial one,
|
|
57
|
+
// is required. `emitErrorAndRetry` may retry this whole call, so
|
|
58
|
+
// the cache must be clean before the next attempt.
|
|
59
|
+
clearWriteRefCaches(caches);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The transactional body of {@link updatePage}, extracted so the
|
|
65
|
+
* transaction boundary in the caller can wrap it in a try/catch for
|
|
66
|
+
* cache invalidation on rollback.
|
|
67
|
+
* @param trx - The active transaction.
|
|
68
|
+
* @param knex - Knex query builder connected to the archive DB (passed
|
|
69
|
+
* through to `insertPage` for its `trx ?? knex` fallback contract).
|
|
70
|
+
* @param caches - The connection's write-side id caches.
|
|
71
|
+
* @param page - The page data to store.
|
|
72
|
+
* @param destUrlObject - The parsed redirect-chain destination URL.
|
|
73
|
+
* @param sources - Redirect-source URLs from the chain resolution.
|
|
74
|
+
* @param writeHtml - See {@link updatePage}.
|
|
75
|
+
* @param isTarget - See {@link updatePage}.
|
|
76
|
+
* @param source - See {@link updatePage}.
|
|
77
|
+
* @returns The `content_items.id` of the inserted or updated row.
|
|
78
|
+
*/
|
|
79
|
+
async function updatePageInTransaction(trx, knex, caches, page, destUrlObject, sources, writeHtml, isTarget, source) {
|
|
80
|
+
const pageId = await insertPage(knex, caches, {
|
|
81
|
+
...page,
|
|
82
|
+
url: destUrlObject,
|
|
83
|
+
}, isTarget, trx, source);
|
|
84
|
+
// Wappalyzer tag detection is HTML-body independent (relies on
|
|
85
|
+
// `<script src>` / `<iframe src>` / window globals / response
|
|
86
|
+
// headers) so it runs for every page including external /
|
|
87
|
+
// metadata-only. JSON-LD on the other hand lives inside the
|
|
88
|
+
// rendered HTML body, so we only write it when there is HTML to
|
|
89
|
+
// scrape — see the same `writeHtml` gate as `writePageHtmlBlob`
|
|
90
|
+
// below.
|
|
91
|
+
await insertTags(pageId, page.meta, trx);
|
|
92
|
+
if (writeHtml) {
|
|
93
|
+
await insertJsonLd(pageId, page.meta, trx);
|
|
94
|
+
}
|
|
95
|
+
// Chain lineage propagates FROM the originating URL
|
|
96
|
+
// (`page.url`), NOT from the destination. See the matching
|
|
97
|
+
// rationale in `recordRedirect`: intermediates are reached
|
|
98
|
+
// transitively from the originating URL's render, so they
|
|
99
|
+
// inherit its lineage. The `source` argument is the
|
|
100
|
+
// authoritative origin label when inventoryMode is live; fall
|
|
101
|
+
// through to a DB lookup of `page.url` for the resume /
|
|
102
|
+
// retry-failed path where the call-site has no source.
|
|
103
|
+
let originatingSource = source;
|
|
104
|
+
if (originatingSource === undefined) {
|
|
105
|
+
originatingSource = await readSourceByUrl(trx, caches, page.url.withoutHashAndAuth);
|
|
106
|
+
}
|
|
107
|
+
const chainLineageSource = deriveLineageFromParent(originatingSource, 'crawled');
|
|
108
|
+
await linkRedirectSources(trx, caches, sources, pageId, destUrlObject.withoutHashAndAuth, page.isExternal, chainLineageSource);
|
|
109
|
+
// Only insert a snapshot blob when there is actual HTML to write.
|
|
110
|
+
// `page.html.length > 0` is the precise signal: the scraper returns
|
|
111
|
+
// `html: ''` for everything that is not a rendered `text/html` document
|
|
112
|
+
// (non-HTML responses, metadata-only, external, degraded renders), so a
|
|
113
|
+
// non-empty `html` is exactly "a rendered HTML body exists". Gating on
|
|
114
|
+
// `isTarget` alone would store an empty body for every internal non-HTML
|
|
115
|
+
// resource — PDF / zip / images are isTarget=1 (#72).
|
|
116
|
+
//
|
|
117
|
+
// `isTarget` is intentionally NOT part of this condition: it is implied by
|
|
118
|
+
// `html.length > 0` (only in-scope target pages are browser-rendered into a
|
|
119
|
+
// non-empty body; metadata-only and external pages carry `html: ''`), so the
|
|
120
|
+
// content check alone expresses the intent without a redundant term.
|
|
121
|
+
if (writeHtml && page.html.length > 0) {
|
|
122
|
+
await writePageHtmlBlob(pageId, page.html, trx);
|
|
123
|
+
}
|
|
124
|
+
else if (writeHtml &&
|
|
125
|
+
page.contentType !== null &&
|
|
126
|
+
!isHtmlContentType(page.contentType)) {
|
|
127
|
+
// The page is now a *known* non-HTML type. If a previous scrape stored
|
|
128
|
+
// an HTML body for this URL (e.g. it served HTML then was replaced by
|
|
129
|
+
// a PDF across `crawl --resume` / `--append`), drop the stale ref so
|
|
130
|
+
// `page_html_ref` never contradicts the content type. A degraded HTML
|
|
131
|
+
// re-scrape (text/html or unknown content type with empty html) is NOT
|
|
132
|
+
// cleared — the last good snapshot is preserved, mirroring the
|
|
133
|
+
// anchors / images empty-guard below. Gated on `writeHtml` because a
|
|
134
|
+
// stale ref can only have been written by a snapshot-capable call
|
|
135
|
+
// (`setPage`); `setExternalPage` passes `writeHtml = false` and never
|
|
136
|
+
// sets `html`, so it has nothing to clear.
|
|
137
|
+
await trx('page_html_ref').where('page_id', pageId).delete();
|
|
138
|
+
}
|
|
139
|
+
// Re-scrape semantics: the same URL can be scraped more than once
|
|
140
|
+
// (e.g. `crawl --resume`, re-visits, `--append` re-promotion). Edges
|
|
141
|
+
// are replaced wholesale per page on every non-empty scrape — the
|
|
142
|
+
// delete is paired with, and guarded by, a non-empty new list: a
|
|
143
|
+
// degraded re-scrape (navigation timeout / partial render) can return
|
|
144
|
+
// an empty `anchorList` for a page that previously had links, and
|
|
145
|
+
// wiping the prior good data in that case would be destructive. We
|
|
146
|
+
// cannot tell a transient empty result apart from a page that has
|
|
147
|
+
// legitimately lost all its links, so we err on the side of keeping
|
|
148
|
+
// what we already had. The accepted trade-off is that a page which
|
|
149
|
+
// genuinely dropped to zero links keeps its stale rows until the next
|
|
150
|
+
// non-empty re-scrape replaces them (#70).
|
|
151
|
+
//
|
|
152
|
+
// Lineage propagation: read the current page's merged source
|
|
153
|
+
// (post-UPDATE by `insertPage`) so anchor placeholder rows inherit a
|
|
154
|
+
// label that reflects the parent's chain. A `'crawled'`-lineage
|
|
155
|
+
// parent passes `'crawled'` explicitly so the crawled-wins downgrade
|
|
156
|
+
// in `resolveContentItemId` fires when an anchor hits an existing
|
|
157
|
+
// `'inventory-*'` row. An inventory-lineage parent passes
|
|
158
|
+
// `'inventory-discovered'` to label transitively-reached URLs
|
|
159
|
+
// correctly without the orchestrator needing to rehydrate
|
|
160
|
+
// `inventoryMode` from disk.
|
|
161
|
+
const [parentRow] = (await trx
|
|
162
|
+
.select('source')
|
|
163
|
+
.from('content_items')
|
|
164
|
+
.where('id', pageId));
|
|
165
|
+
const anchorLineageSource = deriveLineageFromParent(parentRow?.source, 'crawled');
|
|
166
|
+
await replaceAnchorEdges(trx, caches, pageId, page, anchorLineageSource);
|
|
167
|
+
await replaceImageItems(trx, caches, pageId, page);
|
|
168
|
+
// Clear this page's resource_ref_edges unconditionally (no non-empty
|
|
169
|
+
// guard, unlike anchors/images above): the crawler always emits this
|
|
170
|
+
// page's `responseReferrers` events right after its `page` event (see
|
|
171
|
+
// `Crawler#handleResources`, called immediately after `#handleResult`
|
|
172
|
+
// for the same scrape), through the same serialized WriteQueue, so the
|
|
173
|
+
// fresh INSERT is guaranteed to follow this DELETE in commit order —
|
|
174
|
+
// no writer can interleave a stale re-insert between them. This is a
|
|
175
|
+
// write-ordering guarantee only: the DELETE and the follow-up INSERT
|
|
176
|
+
// are still two separate transactions, so a concurrent read-only
|
|
177
|
+
// connection (viewer / MCP open on the same archive during an active
|
|
178
|
+
// `--append` / `--retry-failed` run) can observe a momentary window
|
|
179
|
+
// with zero rows for this page and misclassify its resources as
|
|
180
|
+
// unused. The window closes as soon as the next transaction commits,
|
|
181
|
+
// so this is a transient display artifact, not a durable data loss —
|
|
182
|
+
// accepted rather than merging the two writes into one transaction.
|
|
183
|
+
// A degraded re-scrape that legitimately captures zero sub-resources
|
|
184
|
+
// leaves this page referrer-less until its next non-empty re-scrape —
|
|
185
|
+
// accepted, since resource_items rows for no-longer-referenced
|
|
186
|
+
// resources are themselves allowed to become orphaned (no cross-page
|
|
187
|
+
// cleanup is attempted for those either).
|
|
188
|
+
await trx('resource_ref_edges').where('page_id', pageId).delete();
|
|
189
|
+
return pageId;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Reads the `source` of the row identified by `url` without creating a
|
|
193
|
+
* placeholder — the cache-first mirror of the legacy by-URL SELECT.
|
|
194
|
+
* Returns `undefined` when no row exists yet.
|
|
195
|
+
* @param trx - The active transaction.
|
|
196
|
+
* @param caches - The connection's write-side id caches (read-only here;
|
|
197
|
+
* a DB fallback does not populate the identity cache because the id is
|
|
198
|
+
* not needed).
|
|
199
|
+
* @param url - Normalised URL key.
|
|
200
|
+
*/
|
|
201
|
+
async function readSourceByUrl(trx, caches, url) {
|
|
202
|
+
const cached = caches.contentItems.get(url);
|
|
203
|
+
if (cached !== undefined) {
|
|
204
|
+
return cached.source;
|
|
205
|
+
}
|
|
206
|
+
const [row] = (await trx
|
|
207
|
+
.select('ci.source')
|
|
208
|
+
.from('content_items as ci')
|
|
209
|
+
.join('url_refs as ur', 'ur.id', 'ci.url_id')
|
|
210
|
+
.where('ur.url', url));
|
|
211
|
+
return row?.source;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Replaces the page's `anchor_edges` rows with the aggregate of the new
|
|
215
|
+
* scrape's `anchorList` (empty-guarded; see the caller's re-scrape
|
|
216
|
+
* comment). Instances sharing the same resolved `href_page_id` collapse
|
|
217
|
+
* into one edge whose `count` is the instance count and whose
|
|
218
|
+
* `first_hash` / `first_text_id` capture the first instance encountered
|
|
219
|
+
* in list order — the same first-wins rule the archive migration applies
|
|
220
|
+
* to legacy `anchors` rows.
|
|
221
|
+
* @param trx - The active transaction.
|
|
222
|
+
* @param caches - The connection's write-side id caches.
|
|
223
|
+
* @param pageId - The owning `content_items.id`.
|
|
224
|
+
* @param page - The scraped page payload.
|
|
225
|
+
* @param anchorLineageSource - Lineage label for anchor-target
|
|
226
|
+
* placeholder rows (see the caller's lineage comment).
|
|
227
|
+
*/
|
|
228
|
+
async function replaceAnchorEdges(trx, caches, pageId, page, anchorLineageSource) {
|
|
229
|
+
if (page.anchorList.length === 0) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const edges = new Map();
|
|
233
|
+
for (const anchor of page.anchorList) {
|
|
234
|
+
const hrefId = await resolveContentItemId(trx, caches, anchor.href.withoutHashAndAuth, anchor.isExternal ? 1 : 0, anchorLineageSource);
|
|
235
|
+
const existing = edges.get(hrefId);
|
|
236
|
+
if (existing !== undefined) {
|
|
237
|
+
existing.count += 1;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
edges.set(hrefId, {
|
|
241
|
+
count: 1,
|
|
242
|
+
firstHash: anchor.href.hash || null,
|
|
243
|
+
firstText: anchor.textContent || null,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const texts = new Set();
|
|
247
|
+
for (const edge of edges.values()) {
|
|
248
|
+
if (edge.firstText !== null) {
|
|
249
|
+
texts.add(edge.firstText);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const textIds = await upsertTextRefs(trx, texts);
|
|
253
|
+
dbLog('Replace anchor_edges: %d edges (%d instances)', edges.size, page.anchorList.length);
|
|
254
|
+
await trx('anchor_edges').where('page_id', pageId).delete();
|
|
255
|
+
const rows = [...edges.entries()].map(([hrefPageId, edge]) => ({
|
|
256
|
+
page_id: pageId,
|
|
257
|
+
href_page_id: hrefPageId,
|
|
258
|
+
count: edge.count,
|
|
259
|
+
first_hash: edge.firstHash,
|
|
260
|
+
first_text_id: edge.firstText === null ? null : (textIds.get(edge.firstText) ?? null),
|
|
261
|
+
}));
|
|
262
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
263
|
+
await trx('anchor_edges').insert(chunk);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Replaces the page's `image_items` rows with the new scrape's
|
|
268
|
+
* `imageList` (empty-guarded; see the caller's re-scrape comment).
|
|
269
|
+
* `src` / `currentSrc` route to `url_refs` or (for large `data:` URIs)
|
|
270
|
+
* `blob_refs`; `dom_path_text_id` resolves by matching each image's
|
|
271
|
+
* `sourceCode` against the in-browser capture carried on
|
|
272
|
+
* `page.imageDomPaths`, falling back to the synthetic `unknown/<n>`
|
|
273
|
+
* marker per image when the capture is absent or ambiguous.
|
|
274
|
+
* @param trx - The active transaction.
|
|
275
|
+
* @param caches - The connection's write-side id caches.
|
|
276
|
+
* @param pageId - The owning `content_items.id`.
|
|
277
|
+
* @param page - The scraped page payload.
|
|
278
|
+
*/
|
|
279
|
+
async function replaceImageItems(trx, caches, pageId, page) {
|
|
280
|
+
if (page.imageList.length === 0) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
// Match by list index: ids only need to be unique within this call and
|
|
284
|
+
// ordered in insertion order, which the list order is by construction.
|
|
285
|
+
const domPaths = matchImagesToDomPaths(page.imageList.map((image, index) => ({
|
|
286
|
+
id: index,
|
|
287
|
+
sourceCode: image.sourceCode || null,
|
|
288
|
+
})), page.imageDomPaths ?? []);
|
|
289
|
+
const texts = new Set();
|
|
290
|
+
for (const image of page.imageList) {
|
|
291
|
+
if (typeof image.alt === 'string' && image.alt !== '') {
|
|
292
|
+
texts.add(image.alt);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
for (const entry of domPaths.values()) {
|
|
296
|
+
texts.add(entry.path);
|
|
297
|
+
}
|
|
298
|
+
const textIds = await upsertTextRefs(trx, texts);
|
|
299
|
+
const rows = [];
|
|
300
|
+
for (const [index, image] of page.imageList.entries()) {
|
|
301
|
+
const domPath = domPaths.get(index);
|
|
302
|
+
if (domPath === undefined) {
|
|
303
|
+
throw new Error(`replaceImageItems: dom_path not resolved for image #${index}`);
|
|
304
|
+
}
|
|
305
|
+
const domPathId = textIds.get(domPath.path);
|
|
306
|
+
if (domPathId === undefined) {
|
|
307
|
+
throw new Error(`replaceImageItems: text_refs.id not resolved for dom_path=${domPath.path}`);
|
|
308
|
+
}
|
|
309
|
+
const srcSlot = await resolveUrlOrBlob(trx, caches, image.src);
|
|
310
|
+
const currentSrcSlot = await resolveUrlOrBlob(trx, caches, image.currentSrc);
|
|
311
|
+
rows.push({
|
|
312
|
+
page_id: pageId,
|
|
313
|
+
src_url_id: srcSlot.url,
|
|
314
|
+
current_src_url_id: currentSrcSlot.url,
|
|
315
|
+
src_blob_id: srcSlot.blob,
|
|
316
|
+
current_src_blob_id: currentSrcSlot.blob,
|
|
317
|
+
alt_text_id: typeof image.alt === 'string' && image.alt !== ''
|
|
318
|
+
? (textIds.get(image.alt) ?? null)
|
|
319
|
+
: null,
|
|
320
|
+
width: image.width,
|
|
321
|
+
height: image.height,
|
|
322
|
+
natural_width: image.naturalWidth,
|
|
323
|
+
natural_height: image.naturalHeight,
|
|
324
|
+
is_lazy: image.isLazy == null ? null : image.isLazy ? 1 : 0,
|
|
325
|
+
viewport_width: image.viewportWidth,
|
|
326
|
+
dom_path_text_id: domPathId,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
dbLog('Replace image_items: %d rows', rows.length);
|
|
330
|
+
await trx('image_items').where('page_id', pageId).delete();
|
|
331
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
332
|
+
await trx('image_items').insert(chunk);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Encodes, dedups, and persists a page's HTML snapshot.
|
|
4
|
+
*
|
|
5
|
+
* Computes SHA-256 over the raw UTF-8 bytes, compresses them with zstd,
|
|
6
|
+
* inserts into `page_html_blobs` only if the hash is new (so identical
|
|
7
|
+
* bodies — 404 templates, error pages, redirect destinations — share a
|
|
8
|
+
* single row), and then upserts `page_html_ref(page_id → hash)` so the
|
|
9
|
+
* latest scrape always points at the right body.
|
|
10
|
+
*
|
|
11
|
+
* Runs entirely inside the caller's transaction; a failure here rolls
|
|
12
|
+
* back the rest of `updatePage`, which is the desired semantics (an
|
|
13
|
+
* archive that lost its HTML for a page would otherwise serve stale
|
|
14
|
+
* meta against a missing body).
|
|
15
|
+
* @param pageId - The database id of the page.
|
|
16
|
+
* @param html - The raw HTML string (UTF-8).
|
|
17
|
+
* @param trx - The active transaction.
|
|
18
|
+
*/
|
|
19
|
+
export declare function writePageHtmlBlob(pageId: number, html: string, trx: Knex.Transaction): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { zstdCompressSync } from 'node:zlib';
|
|
3
|
+
/**
|
|
4
|
+
* Encodes, dedups, and persists a page's HTML snapshot.
|
|
5
|
+
*
|
|
6
|
+
* Computes SHA-256 over the raw UTF-8 bytes, compresses them with zstd,
|
|
7
|
+
* inserts into `page_html_blobs` only if the hash is new (so identical
|
|
8
|
+
* bodies — 404 templates, error pages, redirect destinations — share a
|
|
9
|
+
* single row), and then upserts `page_html_ref(page_id → hash)` so the
|
|
10
|
+
* latest scrape always points at the right body.
|
|
11
|
+
*
|
|
12
|
+
* Runs entirely inside the caller's transaction; a failure here rolls
|
|
13
|
+
* back the rest of `updatePage`, which is the desired semantics (an
|
|
14
|
+
* archive that lost its HTML for a page would otherwise serve stale
|
|
15
|
+
* meta against a missing body).
|
|
16
|
+
* @param pageId - The database id of the page.
|
|
17
|
+
* @param html - The raw HTML string (UTF-8).
|
|
18
|
+
* @param trx - The active transaction.
|
|
19
|
+
*/
|
|
20
|
+
export async function writePageHtmlBlob(pageId, html, trx) {
|
|
21
|
+
const rawBytes = Buffer.from(html, 'utf8');
|
|
22
|
+
const hash = createHash('sha256').update(rawBytes).digest();
|
|
23
|
+
const compressed = zstdCompressSync(rawBytes);
|
|
24
|
+
await trx('page_html_blobs')
|
|
25
|
+
.insert({
|
|
26
|
+
hash,
|
|
27
|
+
body: compressed,
|
|
28
|
+
codec: 'zstd',
|
|
29
|
+
size_raw: rawBytes.byteLength,
|
|
30
|
+
size_stored: compressed.byteLength,
|
|
31
|
+
})
|
|
32
|
+
.onConflict('hash')
|
|
33
|
+
.ignore();
|
|
34
|
+
// Upsert so a re-scrape's body cleanly supersedes the prior pointer.
|
|
35
|
+
// The old blob row is intentionally left in place — a future #23 GC
|
|
36
|
+
// pass will sweep unreachable hashes.
|
|
37
|
+
await trx('page_html_ref')
|
|
38
|
+
.insert({ page_id: pageId, hash })
|
|
39
|
+
.onConflict('page_id')
|
|
40
|
+
.merge(['hash']);
|
|
41
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DB_Redirect } from '../../types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves redirect sources for the given page IDs in bulk.
|
|
5
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
6
|
+
* @param pageIds - The database IDs of the destination pages.
|
|
7
|
+
* @returns An array of {@link DB_Redirect} records mapping destination pages to their redirect sources.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getRedirectsForPages(knex: Knex, pageIds: number[]): Promise<DB_Redirect[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves redirect sources for the given page IDs in bulk.
|
|
3
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
4
|
+
* @param pageIds - The database IDs of the destination pages.
|
|
5
|
+
* @returns An array of {@link DB_Redirect} records mapping destination pages to their redirect sources.
|
|
6
|
+
*/
|
|
7
|
+
export async function getRedirectsForPages(knex, pageIds) {
|
|
8
|
+
if (pageIds.length === 0)
|
|
9
|
+
return [];
|
|
10
|
+
return knex
|
|
11
|
+
.select('content_items.redirect_dest_id as pageId', 'url_refs.url as from', 'content_items.id as fromId')
|
|
12
|
+
.from('content_items')
|
|
13
|
+
.join('url_refs', 'url_refs.id', '=', 'content_items.url_id')
|
|
14
|
+
.whereIn('content_items.redirect_dest_id', pageIds);
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves pages that link to a specific page (incoming links / referrers).
|
|
4
|
+
*
|
|
5
|
+
* Incoming links are resolved **through redirects**: an anchor pointing at a
|
|
6
|
+
* redirect source (e.g. `http://x` that 301s to `https://x`) counts as a
|
|
7
|
+
* referrer of the redirect's final destination, not of the source. This keeps
|
|
8
|
+
* backlinks merged on the canonical page instead of splitting them across the
|
|
9
|
+
* `http`/`https` (or any redirect source/dest) pair. The resolution mirrors
|
|
10
|
+
* `getPagesWithRels`' redirect handling — `redirect_dest_id` is
|
|
11
|
+
* pre-flattened to the final destination, so
|
|
12
|
+
* `COALESCE(target.redirect_dest_id, target.id)` is a single hop.
|
|
13
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
14
|
+
* @param pageId - The database ID of the target page.
|
|
15
|
+
* @returns An array of referrer records with URL, hash, and text content.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getReferrersOfPage(knex: Knex, pageId: number): Promise<any[]>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves pages that link to a specific page (incoming links / referrers).
|
|
3
|
+
*
|
|
4
|
+
* Incoming links are resolved **through redirects**: an anchor pointing at a
|
|
5
|
+
* redirect source (e.g. `http://x` that 301s to `https://x`) counts as a
|
|
6
|
+
* referrer of the redirect's final destination, not of the source. This keeps
|
|
7
|
+
* backlinks merged on the canonical page instead of splitting them across the
|
|
8
|
+
* `http`/`https` (or any redirect source/dest) pair. The resolution mirrors
|
|
9
|
+
* `getPagesWithRels`' redirect handling — `redirect_dest_id` is
|
|
10
|
+
* pre-flattened to the final destination, so
|
|
11
|
+
* `COALESCE(target.redirect_dest_id, target.id)` is a single hop.
|
|
12
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
13
|
+
* @param pageId - The database ID of the target page.
|
|
14
|
+
* @returns An array of referrer records with URL, hash, and text content.
|
|
15
|
+
*/
|
|
16
|
+
export async function getReferrersOfPage(knex, pageId) {
|
|
17
|
+
const res = await knex
|
|
18
|
+
.select('referrer_url.url as url',
|
|
19
|
+
// `through` / `throughId` = the URL the anchor actually pointed at (the
|
|
20
|
+
// redirect source, e.g. `http://x`), mirroring `getPagesWithRels`'
|
|
21
|
+
// `redirect.from` / `redirect.fromId`. Lets report code print the
|
|
22
|
+
// "[REDIRECTED FROM]" note even on this (non-preloaded) referrer path.
|
|
23
|
+
'target_url.url as through', 'target.id as throughId', 'anchor_edges.first_hash as hash', 'first_text.text as textContent')
|
|
24
|
+
.from('anchor_edges')
|
|
25
|
+
.join('content_items as referrer', 'anchor_edges.page_id', '=', 'referrer.id')
|
|
26
|
+
.join('url_refs as referrer_url', 'referrer.url_id', '=', 'referrer_url.id')
|
|
27
|
+
.join('content_items as target', 'anchor_edges.href_page_id', '=', 'target.id')
|
|
28
|
+
.join('url_refs as target_url', 'target.url_id', '=', 'target_url.id')
|
|
29
|
+
.leftJoin('text_refs as first_text', 'anchor_edges.first_text_id', '=', 'first_text.id')
|
|
30
|
+
.whereRaw('coalesce("target"."redirect_dest_id", "target"."id") = ?', [pageId]);
|
|
31
|
+
return res;
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves the page URLs that reference a specific resource.
|
|
4
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
5
|
+
* @param id - The database ID of the resource.
|
|
6
|
+
* @returns An array of page URL strings that reference the resource.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getReferrersOfResource(knex: Knex, id: number): Promise<string[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves the page URLs that reference a specific resource.
|
|
3
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
4
|
+
* @param id - The database ID of the resource.
|
|
5
|
+
* @returns An array of page URL strings that reference the resource.
|
|
6
|
+
*/
|
|
7
|
+
export async function getReferrersOfResource(knex, id) {
|
|
8
|
+
const res = await knex
|
|
9
|
+
.select('url_refs.url as url')
|
|
10
|
+
.from('resource_ref_edges')
|
|
11
|
+
.join('content_items', 'content_items.id', '=', 'resource_ref_edges.page_id')
|
|
12
|
+
.join('url_refs', 'url_refs.id', '=', 'content_items.url_id')
|
|
13
|
+
.where('resource_ref_edges.resource_id', id);
|
|
14
|
+
return res.map((r) => r.url);
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a `resource_items` query joined against `url_refs` and
|
|
4
|
+
* `content_type_refs`, reconstructing a flat, legacy-shaped resource row.
|
|
5
|
+
* Selected columns are aliased to `DB_Resource` field names. `responseHeaders`
|
|
6
|
+
* is NOT reconstructed here (it needs a second, batched pass — see
|
|
7
|
+
* {@link ../resources/reconstruct-resource-rows.js}) so this query stays a
|
|
8
|
+
* single-pass join with no N+1 subqueries; `headerSetId` is selected as an
|
|
9
|
+
* intermediate for that second pass.
|
|
10
|
+
*
|
|
11
|
+
* The `url_refs` join is a LEFT JOIN, not INNER: a resource whose own
|
|
12
|
+
* identity URL is a large `data:` URI has `url_id = NULL` (routed to
|
|
13
|
+
* `url_blob_id` / `blob_refs` instead — see `create-entity-tables.ts`'s
|
|
14
|
+
* `resource_items url / blob mutual-exclusion CHECK`). An INNER JOIN
|
|
15
|
+
* would silently drop such rows from every reader. `url` reads back as
|
|
16
|
+
* `null` for those rows, matching the established `image_items` src /
|
|
17
|
+
* blob convention (`list-images.ts`) — recovering the raw data URI would
|
|
18
|
+
* require zstd-decompression in JS, which no current caller needs.
|
|
19
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
20
|
+
* @returns A query builder pre-configured with the joins `get-resources.ts` /
|
|
21
|
+
* `get-resource-by-url.ts` need; callers add `.where()` on top.
|
|
22
|
+
* @example
|
|
23
|
+
* const rows = await buildResourceQuery(knex).whereIn('ur.url', urls);
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildResourceQuery(knex: Knex): Knex.QueryBuilder;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a `resource_items` query joined against `url_refs` and
|
|
3
|
+
* `content_type_refs`, reconstructing a flat, legacy-shaped resource row.
|
|
4
|
+
* Selected columns are aliased to `DB_Resource` field names. `responseHeaders`
|
|
5
|
+
* is NOT reconstructed here (it needs a second, batched pass — see
|
|
6
|
+
* {@link ../resources/reconstruct-resource-rows.js}) so this query stays a
|
|
7
|
+
* single-pass join with no N+1 subqueries; `headerSetId` is selected as an
|
|
8
|
+
* intermediate for that second pass.
|
|
9
|
+
*
|
|
10
|
+
* The `url_refs` join is a LEFT JOIN, not INNER: a resource whose own
|
|
11
|
+
* identity URL is a large `data:` URI has `url_id = NULL` (routed to
|
|
12
|
+
* `url_blob_id` / `blob_refs` instead — see `create-entity-tables.ts`'s
|
|
13
|
+
* `resource_items url / blob mutual-exclusion CHECK`). An INNER JOIN
|
|
14
|
+
* would silently drop such rows from every reader. `url` reads back as
|
|
15
|
+
* `null` for those rows, matching the established `image_items` src /
|
|
16
|
+
* blob convention (`list-images.ts`) — recovering the raw data URI would
|
|
17
|
+
* require zstd-decompression in JS, which no current caller needs.
|
|
18
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
19
|
+
* @returns A query builder pre-configured with the joins `get-resources.ts` /
|
|
20
|
+
* `get-resource-by-url.ts` need; callers add `.where()` on top.
|
|
21
|
+
* @example
|
|
22
|
+
* const rows = await buildResourceQuery(knex).whereIn('ur.url', urls);
|
|
23
|
+
*/
|
|
24
|
+
export function buildResourceQuery(knex) {
|
|
25
|
+
return knex('resource_items as ri')
|
|
26
|
+
.leftJoin('url_refs as ur', 'ur.id', 'ri.url_id')
|
|
27
|
+
.leftJoin('content_type_refs as ctr', 'ctr.id', 'ri.content_type_id')
|
|
28
|
+
.select('ri.id as id', 'ur.url as url', 'ri.is_external as isExternal', 'ri.status as status', 'ri.status_text as statusText', 'ctr.raw as contentType', 'ri.content_length as contentLength', 'ri.header_set_id as headerSetId', 'ri.compress as compress', 'ri.cdn as cdn', 'ri.source as source');
|
|
29
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Return the subset of `urls` that already exist as `resource_items` rows.
|
|
4
|
+
* See `getExistingPageUrls` — same chunking strategy.
|
|
5
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
6
|
+
* @param urls - URL strings to probe.
|
|
7
|
+
* @returns URLs found among `resource_items`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getExistingResourceUrls(knex: Knex, urls: readonly string[]): Promise<string[]>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../utils/array/each-splitted.js';
|
|
2
|
+
/**
|
|
3
|
+
* Return the subset of `urls` that already exist as `resource_items` rows.
|
|
4
|
+
* See `getExistingPageUrls` — same chunking strategy.
|
|
5
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
6
|
+
* @param urls - URL strings to probe.
|
|
7
|
+
* @returns URLs found among `resource_items`.
|
|
8
|
+
*/
|
|
9
|
+
export async function getExistingResourceUrls(knex, urls) {
|
|
10
|
+
if (urls.length === 0) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
const found = [];
|
|
14
|
+
await eachSplitted([...urls], 500, async (chunk) => {
|
|
15
|
+
const rows = await knex('resource_items')
|
|
16
|
+
.join('url_refs', 'url_refs.id', 'resource_items.url_id')
|
|
17
|
+
.select('url_refs.url as url')
|
|
18
|
+
.whereIn('url_refs.url', chunk);
|
|
19
|
+
for (const row of rows) {
|
|
20
|
+
found.push(row.url);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return found;
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DB_Resource } from '../../types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves a single sub-resource by its URL.
|
|
5
|
+
*
|
|
6
|
+
* Accepts multiple URL candidates because the stored key is the resource's
|
|
7
|
+
* `href` while callers may only know the hash-stripped form; the first match
|
|
8
|
+
* wins.
|
|
9
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
10
|
+
* @param urls - URL candidates to match against `url_refs.url`.
|
|
11
|
+
* @returns The reconstructed {@link DB_Resource} row, or `null` if none match.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getResourceByUrl(knex: Knex, urls: readonly string[]): Promise<DB_Resource | null>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { buildResourceQuery } from './build-resource-query.js';
|
|
2
|
+
import { reconstructResourceRows } from './reconstruct-resource-rows.js';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves a single sub-resource by its URL.
|
|
5
|
+
*
|
|
6
|
+
* Accepts multiple URL candidates because the stored key is the resource's
|
|
7
|
+
* `href` while callers may only know the hash-stripped form; the first match
|
|
8
|
+
* wins.
|
|
9
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
10
|
+
* @param urls - URL candidates to match against `url_refs.url`.
|
|
11
|
+
* @returns The reconstructed {@link DB_Resource} row, or `null` if none match.
|
|
12
|
+
*/
|
|
13
|
+
export async function getResourceByUrl(knex, urls) {
|
|
14
|
+
const row = await buildResourceQuery(knex)
|
|
15
|
+
.whereIn('ur.url', [...urls])
|
|
16
|
+
.first();
|
|
17
|
+
if (!row) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const [reconstructed] = await reconstructResourceRows(knex, [row]);
|
|
21
|
+
return reconstructed ?? null;
|
|
22
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves a flat list of all resource URLs from the `resource_items`
|
|
4
|
+
* table. URL text is normalised into `url_refs`, so the read joins the
|
|
5
|
+
* two tables and returns the resolved strings.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @returns An array of resource URL strings.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getResourceUrlList(knex: Knex): Promise<string[]>;
|