@nitpicker/crawler 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/lib/archive/archive-accessor.d.ts +2 -2
- package/lib/archive/archive-accessor.js +2 -2
- package/lib/archive/archive-lock.d.ts +7 -0
- package/lib/archive/archive-lock.js +7 -0
- package/lib/archive/archive.d.ts +175 -13
- package/lib/archive/archive.js +198 -14
- package/lib/archive/cache/compute-archive-cache-key.d.ts +39 -0
- package/lib/archive/cache/compute-archive-cache-key.js +95 -0
- package/lib/archive/cache/extract-archive-to-cache.d.ts +43 -0
- package/lib/archive/cache/extract-archive-to-cache.js +309 -0
- package/lib/archive/cache/get-archive-cache-root.d.ts +20 -0
- package/lib/archive/cache/get-archive-cache-root.js +53 -0
- package/lib/archive/cache/is-archive-cache-disabled.d.ts +24 -0
- package/lib/archive/cache/is-archive-cache-disabled.js +34 -0
- package/lib/archive/cache/resolve-archive-cache-dir.d.ts +26 -0
- package/lib/archive/cache/resolve-archive-cache-dir.js +32 -0
- package/lib/archive/create-adjunct-tables.d.ts +43 -0
- package/lib/archive/create-adjunct-tables.js +213 -0
- package/lib/archive/create-entity-tables.d.ts +173 -0
- package/lib/archive/create-entity-tables.js +318 -0
- package/lib/archive/create-progress-reporter.d.ts +30 -0
- package/lib/archive/create-progress-reporter.js +38 -0
- package/lib/archive/create-ref-tables.d.ts +35 -0
- package/lib/archive/create-ref-tables.js +188 -0
- package/lib/archive/database.d.ts +130 -182
- package/lib/archive/database.js +524 -1777
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.d.ts +27 -0
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +34 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.d.ts +17 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.js +26 -0
- package/lib/archive/db-ops/_shared/decode-json-ref.d.ts +17 -0
- package/lib/archive/db-ops/_shared/decode-json-ref.js +31 -0
- package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.d.ts +20 -0
- package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.js +53 -0
- package/lib/archive/db-ops/_shared/resolve-content-item-id.d.ts +61 -0
- package/lib/archive/db-ops/_shared/resolve-content-item-id.js +111 -0
- package/lib/archive/db-ops/_shared/resolve-url-or-blob.d.ts +23 -0
- package/lib/archive/db-ops/_shared/resolve-url-or-blob.js +29 -0
- package/lib/archive/db-ops/_shared/retry-setting.d.ts +16 -0
- package/lib/archive/db-ops/_shared/retry-setting.js +18 -0
- package/lib/archive/db-ops/_shared/safe-parse-json.d.ts +11 -0
- package/lib/archive/db-ops/_shared/safe-parse-json.js +18 -0
- package/lib/archive/db-ops/_shared/types.d.ts +53 -0
- package/lib/archive/db-ops/_shared/types.js +1 -0
- package/lib/archive/db-ops/_shared/upsert-blob-ref.d.ts +25 -0
- package/lib/archive/db-ops/_shared/upsert-blob-ref.js +48 -0
- package/lib/archive/db-ops/_shared/upsert-content-type-ref.d.ts +30 -0
- package/lib/archive/db-ops/_shared/upsert-content-type-ref.js +45 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.d.ts +22 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.js +41 -0
- package/lib/archive/db-ops/_shared/upsert-response-headers.d.ts +35 -0
- package/lib/archive/db-ops/_shared/upsert-response-headers.js +49 -0
- package/lib/archive/db-ops/_shared/upsert-url-ref.d.ts +39 -0
- package/lib/archive/db-ops/_shared/upsert-url-ref.js +62 -0
- package/lib/archive/db-ops/analysis/replace-analysis-violations.d.ts +28 -0
- package/lib/archive/db-ops/analysis/replace-analysis-violations.js +152 -0
- package/lib/archive/db-ops/anchors/get-anchors-on-page.d.ts +10 -0
- package/lib/archive/db-ops/anchors/get-anchors-on-page.js +21 -0
- package/lib/archive/db-ops/config/get-base-url.d.ts +8 -0
- package/lib/archive/db-ops/config/get-base-url.js +14 -0
- package/lib/archive/db-ops/config/get-config.d.ts +10 -0
- package/lib/archive/db-ops/config/get-config.js +27 -0
- package/lib/archive/db-ops/config/get-name.d.ts +8 -0
- package/lib/archive/db-ops/config/get-name.js +14 -0
- package/lib/archive/db-ops/config/info-column-allowlist.d.ts +7 -0
- package/lib/archive/db-ops/config/info-column-allowlist.js +26 -0
- package/lib/archive/db-ops/config/info-json-columns.d.ts +5 -0
- package/lib/archive/db-ops/config/info-json-columns.js +10 -0
- package/lib/archive/db-ops/config/set-config.d.ts +12 -0
- package/lib/archive/db-ops/config/set-config.js +21 -0
- package/lib/archive/db-ops/config/update-config.d.ts +17 -0
- package/lib/archive/db-ops/config/update-config.js +36 -0
- package/lib/archive/db-ops/errors/insert-crawl-error.d.ts +15 -0
- package/lib/archive/db-ops/errors/insert-crawl-error.js +21 -0
- package/lib/archive/db-ops/errors/insert-page-error.d.ts +21 -0
- package/lib/archive/db-ops/errors/insert-page-error.js +28 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +22 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +141 -0
- package/lib/archive/db-ops/html/get-html-of-page-by-id.d.ts +18 -0
- package/lib/archive/db-ops/html/get-html-of-page-by-id.js +29 -0
- package/lib/archive/db-ops/inventory/record-inventory-run.d.ts +21 -0
- package/lib/archive/db-ops/inventory/record-inventory-run.js +38 -0
- package/lib/archive/db-ops/lifecycle/checkpoint.d.ts +8 -0
- package/lib/archive/db-ops/lifecycle/checkpoint.js +9 -0
- package/lib/archive/db-ops/lifecycle/destroy.d.ts +6 -0
- package/lib/archive/db-ops/lifecycle/destroy.js +7 -0
- package/lib/archive/db-ops/lifecycle/init.d.ts +22 -0
- package/lib/archive/db-ops/lifecycle/init.js +42 -0
- package/lib/archive/db-ops/meta/get-jsonld-of-page.d.ts +13 -0
- package/lib/archive/db-ops/meta/get-jsonld-of-page.js +27 -0
- package/lib/archive/db-ops/meta/get-tags-of-page.d.ts +12 -0
- package/lib/archive/db-ops/meta/get-tags-of-page.js +28 -0
- package/lib/archive/db-ops/pages/order/set-url-order.d.ts +8 -0
- package/lib/archive/db-ops/pages/order/set-url-order.js +32 -0
- package/lib/archive/db-ops/pages/read/build-page-query.d.ts +18 -0
- package/lib/archive/db-ops/pages/read/build-page-query.js +40 -0
- package/lib/archive/db-ops/pages/read/get-crawling-state.d.ts +70 -0
- package/lib/archive/db-ops/pages/read/get-crawling-state.js +98 -0
- package/lib/archive/db-ops/pages/read/get-existing-page-urls.d.ts +15 -0
- package/lib/archive/db-ops/pages/read/get-existing-page-urls.js +30 -0
- package/lib/archive/db-ops/pages/read/get-page-count.d.ts +12 -0
- package/lib/archive/db-ops/pages/read/get-page-count.js +21 -0
- package/lib/archive/db-ops/pages/read/get-page-source-by-url.d.ts +24 -0
- package/lib/archive/db-ops/pages/read/get-page-source-by-url.js +28 -0
- package/lib/archive/db-ops/pages/read/get-pages-with-rels.d.ts +38 -0
- package/lib/archive/db-ops/pages/read/get-pages-with-rels.js +107 -0
- package/lib/archive/db-ops/pages/read/get-pages.d.ts +11 -0
- package/lib/archive/db-ops/pages/read/get-pages.js +51 -0
- package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.d.ts +18 -0
- package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.js +25 -0
- package/lib/archive/db-ops/pages/read/reconstruct-page-rows.d.ts +31 -0
- package/lib/archive/db-ops/pages/read/reconstruct-page-rows.js +32 -0
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.d.ts +24 -0
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +93 -0
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +47 -0
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +124 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-seeds.d.ts +37 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-seeds.js +72 -0
- package/lib/archive/db-ops/pages/write/insert-jsonld.d.ts +17 -0
- package/lib/archive/db-ops/pages/write/insert-jsonld.js +49 -0
- package/lib/archive/db-ops/pages/write/insert-page.d.ts +36 -0
- package/lib/archive/db-ops/pages/write/insert-page.js +208 -0
- package/lib/archive/db-ops/pages/write/insert-tags.d.ts +16 -0
- package/lib/archive/db-ops/pages/write/insert-tags.js +34 -0
- package/lib/archive/db-ops/pages/write/link-redirect-sources.d.ts +36 -0
- package/lib/archive/db-ops/pages/write/link-redirect-sources.js +93 -0
- package/lib/archive/db-ops/pages/write/record-redirect.d.ts +35 -0
- package/lib/archive/db-ops/pages/write/record-redirect.js +100 -0
- package/lib/archive/db-ops/pages/write/set-skipped-page.d.ts +13 -0
- package/lib/archive/db-ops/pages/write/set-skipped-page.js +22 -0
- package/lib/archive/db-ops/pages/write/update-page.d.ts +29 -0
- package/lib/archive/db-ops/pages/write/update-page.js +334 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.d.ts +19 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.js +41 -0
- package/lib/archive/db-ops/referrers/get-redirects-for-pages.d.ts +9 -0
- package/lib/archive/db-ops/referrers/get-redirects-for-pages.js +15 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-page.d.ts +17 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-page.js +32 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-resource.d.ts +8 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-resource.js +15 -0
- package/lib/archive/db-ops/resources/build-resource-query.d.ts +25 -0
- package/lib/archive/db-ops/resources/build-resource-query.js +29 -0
- package/lib/archive/db-ops/resources/get-existing-resource-urls.d.ts +9 -0
- package/lib/archive/db-ops/resources/get-existing-resource-urls.js +24 -0
- package/lib/archive/db-ops/resources/get-resource-by-url.d.ts +13 -0
- package/lib/archive/db-ops/resources/get-resource-by-url.js +22 -0
- package/lib/archive/db-ops/resources/get-resource-url-list.d.ts +9 -0
- package/lib/archive/db-ops/resources/get-resource-url-list.js +13 -0
- package/lib/archive/db-ops/resources/get-resources.d.ts +8 -0
- package/lib/archive/db-ops/resources/get-resources.js +11 -0
- package/lib/archive/db-ops/resources/insert-inventory-resources.d.ts +24 -0
- package/lib/archive/db-ops/resources/insert-inventory-resources.js +64 -0
- package/lib/archive/db-ops/resources/insert-resource-referrers.d.ts +15 -0
- package/lib/archive/db-ops/resources/insert-resource-referrers.js +54 -0
- package/lib/archive/db-ops/resources/insert-resource.d.ts +34 -0
- package/lib/archive/db-ops/resources/insert-resource.js +73 -0
- package/lib/archive/db-ops/resources/reconstruct-resource-rows.d.ts +26 -0
- package/lib/archive/db-ops/resources/reconstruct-resource-rows.js +30 -0
- package/lib/archive/decode-html-blob.d.ts +18 -0
- package/lib/archive/decode-html-blob.js +31 -0
- package/lib/archive/derive-lineage-from-parent.d.ts +37 -0
- package/lib/archive/derive-lineage-from-parent.js +42 -0
- package/lib/archive/drop-legacy-tables.d.ts +45 -0
- package/lib/archive/drop-legacy-tables.js +56 -0
- package/lib/archive/filesystem/rename.js +1 -1
- package/lib/archive/get-failed-page-messages.d.ts +44 -0
- package/lib/archive/get-failed-page-messages.js +132 -0
- package/lib/archive/init-schema.d.ts +35 -39
- package/lib/archive/init-schema.js +111 -320
- package/lib/archive/is-inventory-source.d.ts +21 -0
- package/lib/archive/is-inventory-source.js +22 -0
- package/lib/archive/limited-page-ids.d.ts +2 -1
- package/lib/archive/limited-page-ids.js +5 -4
- package/lib/archive/meta/assert-compatible-version.d.ts +24 -3
- package/lib/archive/meta/assert-compatible-version.js +24 -3
- package/lib/archive/meta/types.d.ts +87 -1
- package/lib/archive/meta/types.js +34 -2
- package/lib/archive/migrate-entity-tables.d.ts +45 -0
- package/lib/archive/migrate-entity-tables.js +56 -0
- package/lib/archive/migrate-ref-tables.d.ts +25 -0
- package/lib/archive/migrate-ref-tables.js +38 -0
- package/lib/archive/page-meta-column-maps.d.ts +32 -0
- package/lib/archive/page-meta-column-maps.js +43 -0
- package/lib/archive/page.d.ts +6 -6
- package/lib/archive/page.js +5 -5
- package/lib/archive/peek-archive-lock.d.ts +2 -2
- package/lib/archive/peek-archive-lock.js +2 -2
- package/lib/archive/populate-entity-tables/collapse-anchor-rows.d.ts +41 -0
- package/lib/archive/populate-entity-tables/collapse-anchor-rows.js +87 -0
- package/lib/archive/populate-entity-tables/derive-dom-path.d.ts +35 -0
- package/lib/archive/populate-entity-tables/derive-dom-path.js +72 -0
- package/lib/archive/populate-entity-tables/is-blob-ref-value.d.ts +16 -0
- package/lib/archive/populate-entity-tables/is-blob-ref-value.js +19 -0
- package/lib/archive/populate-entity-tables/match-images-to-dom-paths.d.ts +66 -0
- package/lib/archive/populate-entity-tables/match-images-to-dom-paths.js +96 -0
- package/lib/archive/populate-entity-tables/populate-anchor-edges.d.ts +33 -0
- package/lib/archive/populate-entity-tables/populate-anchor-edges.js +153 -0
- package/lib/archive/populate-entity-tables/populate-content-items.d.ts +40 -0
- package/lib/archive/populate-entity-tables/populate-content-items.js +141 -0
- package/lib/archive/populate-entity-tables/populate-entities.d.ts +81 -0
- package/lib/archive/populate-entity-tables/populate-entities.js +111 -0
- package/lib/archive/populate-entity-tables/populate-image-items.d.ts +91 -0
- package/lib/archive/populate-entity-tables/populate-image-items.js +223 -0
- package/lib/archive/populate-entity-tables/populate-page-meta.d.ts +33 -0
- package/lib/archive/populate-entity-tables/populate-page-meta.js +267 -0
- package/lib/archive/populate-entity-tables/populate-resource-items.d.ts +22 -0
- package/lib/archive/populate-entity-tables/populate-resource-items.js +114 -0
- package/lib/archive/populate-entity-tables/populate-resource-ref-edges.d.ts +31 -0
- package/lib/archive/populate-entity-tables/populate-resource-ref-edges.js +33 -0
- package/lib/archive/populate-entity-tables/resolve-blob-refs.d.ts +31 -0
- package/lib/archive/populate-entity-tables/resolve-blob-refs.js +100 -0
- package/lib/archive/populate-entity-tables/resolve-content-type-refs.d.ts +22 -0
- package/lib/archive/populate-entity-tables/resolve-content-type-refs.js +27 -0
- package/lib/archive/populate-entity-tables/resolve-header-sets.d.ts +49 -0
- package/lib/archive/populate-entity-tables/resolve-header-sets.js +122 -0
- package/lib/archive/populate-entity-tables/resolve-json-refs.d.ts +25 -0
- package/lib/archive/populate-entity-tables/resolve-json-refs.js +67 -0
- package/lib/archive/populate-entity-tables/resolve-text-refs.d.ts +30 -0
- package/lib/archive/populate-entity-tables/resolve-text-refs.js +61 -0
- package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.d.ts +21 -0
- package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.js +27 -0
- package/lib/archive/populate-entity-tables/resolve-url-refs.d.ts +33 -0
- package/lib/archive/populate-entity-tables/resolve-url-refs.js +60 -0
- package/lib/archive/populate-entity-tables/test-utils/count-rows.d.ts +17 -0
- package/lib/archive/populate-entity-tables/test-utils/count-rows.js +20 -0
- package/lib/archive/populate-entity-tables/test-utils/seed-content-items.d.ts +25 -0
- package/lib/archive/populate-entity-tables/test-utils/seed-content-items.js +42 -0
- package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.d.ts +23 -0
- package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.js +178 -0
- package/lib/archive/populate-entity-tables/types.d.ts +157 -0
- package/lib/archive/populate-entity-tables/types.js +12 -0
- package/lib/archive/populate-entity-tables/upsert-text-refs.d.ts +38 -0
- package/lib/archive/populate-entity-tables/upsert-text-refs.js +78 -0
- package/lib/archive/populate-ref-tables/classify-content-type.d.ts +16 -0
- package/lib/archive/populate-ref-tables/classify-content-type.js +52 -0
- package/lib/archive/populate-ref-tables/compute-content-hash.d.ts +22 -0
- package/lib/archive/populate-ref-tables/compute-content-hash.js +26 -0
- package/lib/archive/populate-ref-tables/compute-header-flags.d.ts +16 -0
- package/lib/archive/populate-ref-tables/compute-header-flags.js +70 -0
- package/lib/archive/populate-ref-tables/content-type-rules.d.ts +38 -0
- package/lib/archive/populate-ref-tables/content-type-rules.js +133 -0
- package/lib/archive/populate-ref-tables/create-header-table-caches.d.ts +25 -0
- package/lib/archive/populate-ref-tables/create-header-table-caches.js +49 -0
- package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.d.ts +15 -0
- package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.js +15 -0
- package/lib/archive/populate-ref-tables/decode-data-uri.d.ts +21 -0
- package/lib/archive/populate-ref-tables/decode-data-uri.js +126 -0
- package/lib/archive/populate-ref-tables/decompose-header-set.d.ts +29 -0
- package/lib/archive/populate-ref-tables/decompose-header-set.js +157 -0
- package/lib/archive/populate-ref-tables/decompose-url.d.ts +25 -0
- package/lib/archive/populate-ref-tables/decompose-url.js +70 -0
- package/lib/archive/populate-ref-tables/header-stability.d.ts +19 -0
- package/lib/archive/populate-ref-tables/header-stability.js +22 -0
- package/lib/archive/populate-ref-tables/header-value-cache-key.d.ts +17 -0
- package/lib/archive/populate-ref-tables/header-value-cache-key.js +19 -0
- package/lib/archive/populate-ref-tables/normalize-mime.d.ts +24 -0
- package/lib/archive/populate-ref-tables/normalize-mime.js +36 -0
- package/lib/archive/populate-ref-tables/populate-blob-refs.d.ts +38 -0
- package/lib/archive/populate-ref-tables/populate-blob-refs.js +134 -0
- package/lib/archive/populate-ref-tables/populate-content-type-refs.d.ts +27 -0
- package/lib/archive/populate-ref-tables/populate-content-type-refs.js +70 -0
- package/lib/archive/populate-ref-tables/populate-header-tables.d.ts +35 -0
- package/lib/archive/populate-ref-tables/populate-header-tables.js +80 -0
- package/lib/archive/populate-ref-tables/populate-json-refs.d.ts +29 -0
- package/lib/archive/populate-ref-tables/populate-json-refs.js +101 -0
- package/lib/archive/populate-ref-tables/populate-refs.d.ts +51 -0
- package/lib/archive/populate-ref-tables/populate-refs.js +62 -0
- package/lib/archive/populate-ref-tables/populate-text-refs.d.ts +32 -0
- package/lib/archive/populate-ref-tables/populate-text-refs.js +133 -0
- package/lib/archive/populate-ref-tables/populate-url-refs.d.ts +28 -0
- package/lib/archive/populate-ref-tables/populate-url-refs.js +148 -0
- package/lib/archive/populate-ref-tables/test-utils/count-rows.d.ts +15 -0
- package/lib/archive/populate-ref-tables/test-utils/count-rows.js +17 -0
- package/lib/archive/populate-ref-tables/types.d.ts +197 -0
- package/lib/archive/populate-ref-tables/types.js +7 -0
- package/lib/archive/populate-ref-tables/upsert-one-header-set.d.ts +34 -0
- package/lib/archive/populate-ref-tables/upsert-one-header-set.js +208 -0
- package/lib/archive/populate-ref-tables/volatile-header-names.d.ts +20 -0
- package/lib/archive/populate-ref-tables/volatile-header-names.js +33 -0
- package/lib/archive/redirect-table.d.ts +4 -2
- package/lib/archive/redirect-table.js +15 -10
- package/lib/archive/resolve-redirect-chain.d.ts +3 -3
- package/lib/archive/resolve-redirect-chain.js +2 -2
- package/lib/archive/resource.d.ts +1 -1
- package/lib/archive/retarget-legacy-fk-tables.d.ts +47 -0
- package/lib/archive/retarget-legacy-fk-tables.js +107 -0
- package/lib/archive/test-utils/fk-parent-tables.d.ts +15 -0
- package/lib/archive/test-utils/fk-parent-tables.js +19 -0
- package/lib/archive/test-utils/seed-content-item.d.ts +35 -0
- package/lib/archive/test-utils/seed-content-item.js +42 -0
- package/lib/archive/test-utils/setup-legacy-fk-db.d.ts +33 -0
- package/lib/archive/test-utils/setup-legacy-fk-db.js +270 -0
- package/lib/archive/types.d.ts +151 -15
- package/lib/archive/verify-migration/capture-rejection.d.ts +24 -0
- package/lib/archive/verify-migration/capture-rejection.js +31 -0
- package/lib/archive/verify-migration/check-anchor-edges-count.d.ts +34 -0
- package/lib/archive/verify-migration/check-anchor-edges-count.js +72 -0
- package/lib/archive/verify-migration/check-anchor-edges-sum.d.ts +13 -0
- package/lib/archive/verify-migration/check-anchor-edges-sum.js +27 -0
- package/lib/archive/verify-migration/check-content-items-count.d.ts +16 -0
- package/lib/archive/verify-migration/check-content-items-count.js +30 -0
- package/lib/archive/verify-migration/check-content-type-preservation.d.ts +22 -0
- package/lib/archive/verify-migration/check-content-type-preservation.js +40 -0
- package/lib/archive/verify-migration/check-foreign-key-integrity.d.ts +31 -0
- package/lib/archive/verify-migration/check-foreign-key-integrity.js +47 -0
- package/lib/archive/verify-migration/check-image-items-count.d.ts +12 -0
- package/lib/archive/verify-migration/check-image-items-count.js +26 -0
- package/lib/archive/verify-migration/check-page-meta-count.d.ts +15 -0
- package/lib/archive/verify-migration/check-page-meta-count.js +31 -0
- package/lib/archive/verify-migration/check-reader-parity.d.ts +23 -0
- package/lib/archive/verify-migration/check-reader-parity.js +211 -0
- package/lib/archive/verify-migration/check-resource-items-count.d.ts +17 -0
- package/lib/archive/verify-migration/check-resource-items-count.js +33 -0
- package/lib/archive/verify-migration/check-url-round-trip.d.ts +43 -0
- package/lib/archive/verify-migration/check-url-round-trip.js +112 -0
- package/lib/archive/verify-migration/types.d.ts +70 -0
- package/lib/archive/verify-migration/types.js +63 -0
- package/lib/archive/verify-migration/verify-migration.d.ts +41 -0
- package/lib/archive/verify-migration/verify-migration.js +120 -0
- package/lib/classify-error-kind.d.ts +19 -0
- package/lib/classify-error-kind.js +122 -0
- package/lib/crawler/build-js-redirect-edge.d.ts +68 -0
- package/lib/crawler/build-js-redirect-edge.js +57 -0
- package/lib/crawler/build-redirect-event.d.ts +24 -0
- package/lib/crawler/build-redirect-event.js +28 -0
- package/lib/crawler/capture-image-dom-paths.d.ts +33 -0
- package/lib/crawler/capture-image-dom-paths.js +39 -0
- package/lib/crawler/clear-dns-burned-host-cache.d.ts +6 -0
- package/lib/crawler/clear-dns-burned-host-cache.js +11 -0
- package/lib/crawler/collect-image-dom-paths.d.ts +23 -0
- package/lib/crawler/collect-image-dom-paths.js +64 -0
- package/lib/crawler/crawler.d.ts +22 -1
- package/lib/crawler/crawler.js +679 -117
- package/lib/crawler/derive-js-redirect-target.d.ts +68 -0
- package/lib/crawler/derive-js-redirect-target.js +129 -0
- package/lib/crawler/derive-resource-source.d.ts +25 -15
- package/lib/crawler/derive-resource-source.js +28 -17
- package/lib/crawler/dns-burned-host-cache.d.ts +26 -0
- package/lib/crawler/dns-burned-host-cache.js +25 -0
- package/lib/crawler/dns-burned-host-short-circuit-counter.d.ts +13 -0
- package/lib/crawler/dns-burned-host-short-circuit-counter.js +11 -0
- package/lib/crawler/fetch-destination.d.ts +12 -4
- package/lib/crawler/fetch-destination.js +94 -16
- package/lib/crawler/inject-scope-auth.d.ts +1 -1
- package/lib/crawler/inject-scope-auth.js +1 -1
- package/lib/crawler/is-js-redirect-error-shape.d.ts +40 -0
- package/lib/crawler/is-js-redirect-error-shape.js +53 -0
- package/lib/crawler/is-puppeteer-fallback-candidate.d.ts +16 -0
- package/lib/crawler/is-puppeteer-fallback-candidate.js +63 -0
- package/lib/crawler/link-list.d.ts +21 -1
- package/lib/crawler/link-list.js +23 -3
- package/lib/crawler/normalize-content-type.d.ts +1 -1
- package/lib/crawler/normalize-content-type.js +1 -1
- package/lib/crawler/plan-sub-resource-emits.d.ts +63 -0
- package/lib/crawler/plan-sub-resource-emits.js +44 -0
- package/lib/crawler/preload-short-circuit-error.d.ts +22 -0
- package/lib/crawler/preload-short-circuit-error.js +25 -0
- package/lib/crawler/should-burn-host.d.ts +78 -0
- package/lib/crawler/should-burn-host.js +61 -0
- package/lib/crawler/should-get-fallback-on-head-failure.d.ts +38 -0
- package/lib/crawler/should-get-fallback-on-head-failure.js +46 -0
- package/lib/crawler/types.d.ts +108 -1
- package/lib/crawler-orchestrator.d.ts +22 -3
- package/lib/crawler-orchestrator.js +312 -73
- package/lib/crawler.d.ts +15 -2
- package/lib/crawler.js +24 -1
- package/lib/permanent-error-kinds.d.ts +43 -0
- package/lib/permanent-error-kinds.js +48 -0
- package/lib/types.d.ts +84 -0
- package/lib/utils/compute-file-sha256.d.ts +24 -0
- package/lib/utils/compute-file-sha256.js +56 -0
- package/lib/utils/error/emit-error-with-retry.d.ts +40 -0
- package/lib/utils/error/emit-error-with-retry.js +44 -0
- package/lib/utils/error/emit-error.d.ts +39 -0
- package/lib/utils/error/emit-error.js +41 -0
- package/package.json +12 -12
- package/lib/archive/migrate-crawl-errors.d.ts +0 -20
- package/lib/archive/migrate-crawl-errors.js +0 -38
- package/lib/archive/migrate-html-blob-tables.d.ts +0 -24
- package/lib/archive/migrate-html-blob-tables.js +0 -53
- package/lib/archive/migrate-page-errors.d.ts +0 -16
- package/lib/archive/migrate-page-errors.js +0 -35
- package/lib/archive/migrate-pages-resources-source.d.ts +0 -16
- package/lib/archive/migrate-pages-resources-source.js +0 -46
- package/lib/utils/error/error-emitter.d.ts +0 -18
- package/lib/utils/error/error-emitter.js +0 -29
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { computeContentHash } from './compute-content-hash.js';
|
|
2
|
+
import { isVolatileHeader } from './header-stability.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parses one raw `responseHeaders` JSON string and produces the derived
|
|
5
|
+
* data shape required to insert into `header_sets`, `header_set_entries`,
|
|
6
|
+
* and `header_flags`.
|
|
7
|
+
*
|
|
8
|
+
* Behaviour:
|
|
9
|
+
*
|
|
10
|
+
* - `null` / `""` / `"null"` / `"{}"` / non-object JSON → returns `null`.
|
|
11
|
+
* The caller sets `header_set_id = null` on the referring row.
|
|
12
|
+
* - Multiple values per header name (JSON arrays, e.g.
|
|
13
|
+
* `{ "set-cookie": ["a=1", "b=2"] }`) become multiple
|
|
14
|
+
* {@link HeaderEntry} rows with `occurrence` ordinals 1, 2, ... The
|
|
15
|
+
* composite PK on `header_set_entries` `(header_set_id, name_id,
|
|
16
|
+
* occurrence)` guarantees no truncation.
|
|
17
|
+
* - Names are lower-cased. Values are trimmed of whitespace only in the
|
|
18
|
+
* sorted-hash construction; the raw value is preserved verbatim in
|
|
19
|
+
* the emitted `HeaderEntry.value` (and hence in `header_value_refs`).
|
|
20
|
+
* Trimming inside hash construction lets `content-type: text/html`
|
|
21
|
+
* and `content-type: text/html ` dedup to one stable set.
|
|
22
|
+
* - Sort order for hashing is `(name, occurrence)` in binary form —
|
|
23
|
+
* deterministic regardless of the input JSON's insertion order or
|
|
24
|
+
* how the JSON serializer arranged keys.
|
|
25
|
+
* @param rawJson - The raw JSON string exactly as stored in
|
|
26
|
+
* `pages.responseHeaders` / `resources.responseHeaders`.
|
|
27
|
+
* @returns Decomposed shape, or `null` when the row has no meaningful
|
|
28
|
+
* header set.
|
|
29
|
+
*/
|
|
30
|
+
export function decomposeHeaderSet(rawJson) {
|
|
31
|
+
if (rawJson == null || rawJson === '' || rawJson === 'null' || rawJson === '{}') {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = JSON.parse(rawJson);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const entries = [];
|
|
45
|
+
// `occurrence` runs per (lower-cased name) across ALL sources for that
|
|
46
|
+
// name, so duplicate JSON keys that differ only in case
|
|
47
|
+
// (`{"Cookie":"a=1", "cookie":"b=2"}` — non-conforming but legal JSON,
|
|
48
|
+
// and the crawler stores whatever puppeteer hands it) don't collide on
|
|
49
|
+
// (name_id, occurrence). Without this counter the second entry would
|
|
50
|
+
// land at occurrence=1 too, violating the `header_set_entries` PK and
|
|
51
|
+
// silently getting dropped by INSERT OR IGNORE while `entry_count`
|
|
52
|
+
// still counted it.
|
|
53
|
+
const occurrenceByName = new Map();
|
|
54
|
+
for (const [rawName, rawValue] of Object.entries(parsed)) {
|
|
55
|
+
const name = rawName.toLowerCase();
|
|
56
|
+
const volatile = isVolatileHeader(name);
|
|
57
|
+
const nextOccurrence = () => {
|
|
58
|
+
const next = (occurrenceByName.get(name) ?? 0) + 1;
|
|
59
|
+
occurrenceByName.set(name, next);
|
|
60
|
+
return next;
|
|
61
|
+
};
|
|
62
|
+
if (Array.isArray(rawValue)) {
|
|
63
|
+
for (const one of rawValue) {
|
|
64
|
+
if (typeof one !== 'string') {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
entries.push({
|
|
68
|
+
name,
|
|
69
|
+
value: one,
|
|
70
|
+
occurrence: nextOccurrence(),
|
|
71
|
+
isVolatile: volatile,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (typeof rawValue !== 'string') {
|
|
77
|
+
// parseResponseHeaders's stored shape is `Record<string, string |
|
|
78
|
+
// string[] | undefined>`, but hand-edited or non-conforming
|
|
79
|
+
// archives could smuggle in a nested object / number / boolean
|
|
80
|
+
// — coercing via `String()` would produce "[object Object]" and
|
|
81
|
+
// pollute `header_value_refs` with a garbage dictionary entry.
|
|
82
|
+
// Skipping is the conservative choice; the source row's raw
|
|
83
|
+
// JSON is still preserved via `raw_json_hash`.
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
entries.push({
|
|
87
|
+
name,
|
|
88
|
+
value: rawValue,
|
|
89
|
+
occurrence: nextOccurrence(),
|
|
90
|
+
isVolatile: volatile,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (entries.length === 0) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
entries.sort((a, b) => {
|
|
97
|
+
if (a.name < b.name)
|
|
98
|
+
return -1;
|
|
99
|
+
if (a.name > b.name)
|
|
100
|
+
return 1;
|
|
101
|
+
return a.occurrence - b.occurrence;
|
|
102
|
+
});
|
|
103
|
+
// Canonicalize every entry exactly once, then partition the strings.
|
|
104
|
+
// Canonicalizing per hash pass instead (once in the all-entries pass,
|
|
105
|
+
// again in the stable / volatile passes) would serialize each entry
|
|
106
|
+
// twice, which becomes measurable on large archives where a single
|
|
107
|
+
// response can carry a couple of dozen entries.
|
|
108
|
+
const canonicalStrings = entries.map(canonicalize);
|
|
109
|
+
const stableCanonical = [];
|
|
110
|
+
const volatileCanonical = [];
|
|
111
|
+
let stableEntryCount = 0;
|
|
112
|
+
for (const [i, entry] of entries.entries()) {
|
|
113
|
+
if (entry.isVolatile) {
|
|
114
|
+
volatileCanonical.push(canonicalStrings[i]);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
stableCanonical.push(canonicalStrings[i]);
|
|
118
|
+
stableEntryCount += 1;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const rawJsonHash = computeContentHash(rawJson);
|
|
122
|
+
const rawHash = computeContentHash(canonicalStrings.join('\n'));
|
|
123
|
+
// A response with only volatile headers (tracker endpoints that return
|
|
124
|
+
// just `Date` / `Set-Cookie` / `Age`) hashes to `computeContentHash('')`
|
|
125
|
+
// — the canonical "empty stable profile" hash. All all-volatile
|
|
126
|
+
// responses cluster under this sentinel, which is the intended
|
|
127
|
+
// semantic (same empty stable profile = same cluster), and 0.13's
|
|
128
|
+
// DDL declares `stable_hash BLOB NOT NULL` so a `null` sentinel is not
|
|
129
|
+
// available even if we wanted one. Consumers that need to distinguish
|
|
130
|
+
// "empty stable set" from "populated stable set" should key off
|
|
131
|
+
// `stable_entry_count === 0` on the same row, not off the hash.
|
|
132
|
+
const stableHash = computeContentHash(stableCanonical.join('\n'));
|
|
133
|
+
const volatileHash = volatileCanonical.length === 0
|
|
134
|
+
? null
|
|
135
|
+
: computeContentHash(volatileCanonical.join('\n'));
|
|
136
|
+
return {
|
|
137
|
+
rawJsonHash,
|
|
138
|
+
rawHash,
|
|
139
|
+
stableHash,
|
|
140
|
+
volatileHash,
|
|
141
|
+
entries,
|
|
142
|
+
entryCount: entries.length,
|
|
143
|
+
stableEntryCount,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Serializes one entry into the canonical `name/occurrence=value` form
|
|
148
|
+
* used inside the hash construction. The trailing occurrence keeps
|
|
149
|
+
* multi-value headers distinguishable while still letting single-value
|
|
150
|
+
* ones dedup naturally. Values are trimmed so surrounding whitespace
|
|
151
|
+
* does not fork the dedup key.
|
|
152
|
+
* @param entry - One decomposed header entry.
|
|
153
|
+
* @returns Canonical string joined into the hash pre-image.
|
|
154
|
+
*/
|
|
155
|
+
function canonicalize(entry) {
|
|
156
|
+
return `${entry.name}/${entry.occurrence}=${entry.value.trim()}`;
|
|
157
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DecomposedUrl } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts `scheme` / `host` / `port` / `path` / `query_hash` / `fragment`
|
|
4
|
+
* columns from a URL string for `url_refs` population.
|
|
5
|
+
*
|
|
6
|
+
* Parsing is done via WHATWG `new URL(...)`. URLs that fail to parse (e.g.
|
|
7
|
+
* a malformed href scraped from the wild) still round-trip through
|
|
8
|
+
* `url_refs` — the natural key is the raw `url` string — but every
|
|
9
|
+
* decomposed column becomes `null` so filters that JOIN on `host` or
|
|
10
|
+
* `scheme` cleanly skip malformed rows instead of falsely matching.
|
|
11
|
+
*
|
|
12
|
+
* `port` is only populated when explicit in the URL. `new URL(...)`
|
|
13
|
+
* normalises the scheme's default port away (`https://example.com:443/` →
|
|
14
|
+
* `url.port === ''`), so we never synthesise a default. Two rows differing
|
|
15
|
+
* only by explicit vs implicit default port must not deduplicate anyway —
|
|
16
|
+
* `url_refs.url` (the raw string) is the natural key, not the decomposed
|
|
17
|
+
* columns.
|
|
18
|
+
*
|
|
19
|
+
* `query_hash` is the 32-byte content hash of the query string with the
|
|
20
|
+
* leading `?` stripped. Storing the raw query would defeat dedup on
|
|
21
|
+
* tracker URLs whose per-request keys explode dictionary size.
|
|
22
|
+
* @param url - Raw URL string (may be any WHATWG-parseable form, or malformed).
|
|
23
|
+
* @returns Every column that goes into `url_refs` alongside the raw URL.
|
|
24
|
+
*/
|
|
25
|
+
export declare function decomposeUrl(url: string): DecomposedUrl;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { computeContentHash } from './compute-content-hash.js';
|
|
2
|
+
/**
|
|
3
|
+
* URL schemes whose "pathname" is really an in-band opaque payload
|
|
4
|
+
* (base64-encoded image bytes, javascript source, blob URL fragment,
|
|
5
|
+
* etc.) rather than a routing key. For these schemes we leave
|
|
6
|
+
* `DecomposedUrl.path` as `null` so the indexed `url_refs.path` column
|
|
7
|
+
* does not balloon with per-URL opaque tails and defeat the dedup goal.
|
|
8
|
+
*/
|
|
9
|
+
const OPAQUE_PATH_SCHEMES = new Set([
|
|
10
|
+
'data',
|
|
11
|
+
'blob',
|
|
12
|
+
'javascript',
|
|
13
|
+
'about',
|
|
14
|
+
]);
|
|
15
|
+
/**
|
|
16
|
+
* Extracts `scheme` / `host` / `port` / `path` / `query_hash` / `fragment`
|
|
17
|
+
* columns from a URL string for `url_refs` population.
|
|
18
|
+
*
|
|
19
|
+
* Parsing is done via WHATWG `new URL(...)`. URLs that fail to parse (e.g.
|
|
20
|
+
* a malformed href scraped from the wild) still round-trip through
|
|
21
|
+
* `url_refs` — the natural key is the raw `url` string — but every
|
|
22
|
+
* decomposed column becomes `null` so filters that JOIN on `host` or
|
|
23
|
+
* `scheme` cleanly skip malformed rows instead of falsely matching.
|
|
24
|
+
*
|
|
25
|
+
* `port` is only populated when explicit in the URL. `new URL(...)`
|
|
26
|
+
* normalises the scheme's default port away (`https://example.com:443/` →
|
|
27
|
+
* `url.port === ''`), so we never synthesise a default. Two rows differing
|
|
28
|
+
* only by explicit vs implicit default port must not deduplicate anyway —
|
|
29
|
+
* `url_refs.url` (the raw string) is the natural key, not the decomposed
|
|
30
|
+
* columns.
|
|
31
|
+
*
|
|
32
|
+
* `query_hash` is the 32-byte content hash of the query string with the
|
|
33
|
+
* leading `?` stripped. Storing the raw query would defeat dedup on
|
|
34
|
+
* tracker URLs whose per-request keys explode dictionary size.
|
|
35
|
+
* @param url - Raw URL string (may be any WHATWG-parseable form, or malformed).
|
|
36
|
+
* @returns Every column that goes into `url_refs` alongside the raw URL.
|
|
37
|
+
*/
|
|
38
|
+
export function decomposeUrl(url) {
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = new URL(url);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return {
|
|
45
|
+
scheme: null,
|
|
46
|
+
host: null,
|
|
47
|
+
port: null,
|
|
48
|
+
path: null,
|
|
49
|
+
query_hash: null,
|
|
50
|
+
fragment: null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const scheme = parsed.protocol.slice(0, -1) || null;
|
|
54
|
+
const host = parsed.hostname === '' ? null : parsed.hostname.toLowerCase();
|
|
55
|
+
const port = parsed.port === '' ? null : Number.parseInt(parsed.port, 10);
|
|
56
|
+
const rawSearch = parsed.search.startsWith('?')
|
|
57
|
+
? parsed.search.slice(1)
|
|
58
|
+
: parsed.search;
|
|
59
|
+
const query_hash = rawSearch === '' ? null : computeContentHash(rawSearch);
|
|
60
|
+
const fragment = parsed.hash === '' ? null : parsed.hash.slice(1);
|
|
61
|
+
const path = scheme !== null && OPAQUE_PATH_SCHEMES.has(scheme) ? null : parsed.pathname;
|
|
62
|
+
return {
|
|
63
|
+
scheme,
|
|
64
|
+
host,
|
|
65
|
+
port: port !== null && Number.isFinite(port) ? port : null,
|
|
66
|
+
path,
|
|
67
|
+
query_hash,
|
|
68
|
+
fragment,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classifies a header name as volatile (excluded from `stable_hash`) or
|
|
3
|
+
* stable (included). Names are compared case-insensitively (lower-cased
|
|
4
|
+
* before lookup). Names not in {@link VOLATILE_HEADER_NAMES} default to
|
|
5
|
+
* **stable** — see the docs on that constant for why this direction is
|
|
6
|
+
* the safer default.
|
|
7
|
+
*
|
|
8
|
+
* An explicit stable list could be named instead (`content-type`,
|
|
9
|
+
* `content-length`, `cache-control`, `content-security-policy`,
|
|
10
|
+
* `x-frame-options`, `x-content-type-options`, `strict-transport-
|
|
11
|
+
* security`, `referrer-policy`, `permissions-policy`, `server`, `vary`,
|
|
12
|
+
* `location`, ...); we deliberately do NOT enumerate that set at runtime —
|
|
13
|
+
* every non-volatile header is stable by construction, and enumerating
|
|
14
|
+
* it would create a maintenance surface that the volatile-only lookup
|
|
15
|
+
* avoids.
|
|
16
|
+
* @param name - Header name (any case).
|
|
17
|
+
* @returns `true` when the header is volatile.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isVolatileHeader(name: string): boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { VOLATILE_HEADER_NAMES } from './volatile-header-names.js';
|
|
2
|
+
/**
|
|
3
|
+
* Classifies a header name as volatile (excluded from `stable_hash`) or
|
|
4
|
+
* stable (included). Names are compared case-insensitively (lower-cased
|
|
5
|
+
* before lookup). Names not in {@link VOLATILE_HEADER_NAMES} default to
|
|
6
|
+
* **stable** — see the docs on that constant for why this direction is
|
|
7
|
+
* the safer default.
|
|
8
|
+
*
|
|
9
|
+
* An explicit stable list could be named instead (`content-type`,
|
|
10
|
+
* `content-length`, `cache-control`, `content-security-policy`,
|
|
11
|
+
* `x-frame-options`, `x-content-type-options`, `strict-transport-
|
|
12
|
+
* security`, `referrer-policy`, `permissions-policy`, `server`, `vary`,
|
|
13
|
+
* `location`, ...); we deliberately do NOT enumerate that set at runtime —
|
|
14
|
+
* every non-volatile header is stable by construction, and enumerating
|
|
15
|
+
* it would create a maintenance surface that the volatile-only lookup
|
|
16
|
+
* avoids.
|
|
17
|
+
* @param name - Header name (any case).
|
|
18
|
+
* @returns `true` when the header is volatile.
|
|
19
|
+
*/
|
|
20
|
+
export function isVolatileHeader(name) {
|
|
21
|
+
return VOLATILE_HEADER_NAMES.has(name.toLowerCase());
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composite cache key for `header_value_refs`: hex-encoded hash + a
|
|
3
|
+
* separator + raw value. Two values with the same hash but different
|
|
4
|
+
* strings (astronomically improbable, but a `BLOB` hash column is not
|
|
5
|
+
* a total function per SQL semantics) still resolve to distinct cache
|
|
6
|
+
* entries. Shared by the cache warmer
|
|
7
|
+
* ({@link ./create-header-table-caches.ts}) and the per-set upsert
|
|
8
|
+
* ({@link ./upsert-one-header-set.ts}) so both sides always agree on the
|
|
9
|
+
* key shape.
|
|
10
|
+
* @param hash - 32-byte content hash of `value`.
|
|
11
|
+
* @param value - Header value verbatim.
|
|
12
|
+
* @returns Cache key string.
|
|
13
|
+
* @example
|
|
14
|
+
* headerValueCacheKey(computeContentHash('no-cache'), 'no-cache');
|
|
15
|
+
* // '724cf9…|no-cache'
|
|
16
|
+
*/
|
|
17
|
+
export declare function headerValueCacheKey(hash: Buffer, value: string): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composite cache key for `header_value_refs`: hex-encoded hash + a
|
|
3
|
+
* separator + raw value. Two values with the same hash but different
|
|
4
|
+
* strings (astronomically improbable, but a `BLOB` hash column is not
|
|
5
|
+
* a total function per SQL semantics) still resolve to distinct cache
|
|
6
|
+
* entries. Shared by the cache warmer
|
|
7
|
+
* ({@link ./create-header-table-caches.ts}) and the per-set upsert
|
|
8
|
+
* ({@link ./upsert-one-header-set.ts}) so both sides always agree on the
|
|
9
|
+
* key shape.
|
|
10
|
+
* @param hash - 32-byte content hash of `value`.
|
|
11
|
+
* @param value - Header value verbatim.
|
|
12
|
+
* @returns Cache key string.
|
|
13
|
+
* @example
|
|
14
|
+
* headerValueCacheKey(computeContentHash('no-cache'), 'no-cache');
|
|
15
|
+
* // '724cf9…|no-cache'
|
|
16
|
+
*/
|
|
17
|
+
export function headerValueCacheKey(hash, value) {
|
|
18
|
+
return `${hash.toString('hex')}|${value}`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalises a raw Content-Type header value to the canonical MIME form
|
|
3
|
+
* stored in `content_type_refs.normalized`: parameters (`; charset=...`)
|
|
4
|
+
* removed, C0 control characters (`0x00`..`0x1F`) and DEL (`0x7F`)
|
|
5
|
+
* stripped, trimmed, lower-cased.
|
|
6
|
+
*
|
|
7
|
+
* This is a derived column shared by two writers — the live-crawl upsert
|
|
8
|
+
* (`db-ops/_shared/upsert-content-type-ref.ts`) and the archive-migration
|
|
9
|
+
* populate (`populate-content-type-refs.ts`) — which MUST be
|
|
10
|
+
* same-input-same-output; a fork would split the dictionary so the same
|
|
11
|
+
* raw content-type maps to different normalized values depending on
|
|
12
|
+
* whether the row was crawled live or migrated. Keeping the single
|
|
13
|
+
* implementation here is what enforces that.
|
|
14
|
+
*
|
|
15
|
+
* Control chars are dropped character-by-character rather than via a
|
|
16
|
+
* regex literal so the source file carries no non-printing bytes and
|
|
17
|
+
* stays tool-friendly. `jsdom` / lax parsers occasionally emit them
|
|
18
|
+
* inside a raw Content-Type header (e.g. `text/html\r`).
|
|
19
|
+
* @param raw - Raw Content-Type header, guaranteed non-null non-empty.
|
|
20
|
+
* @returns Lower-cased MIME with parameters and control chars stripped.
|
|
21
|
+
* @example
|
|
22
|
+
* normalizeMime('Text/HTML; charset=UTF-8'); // => 'text/html'
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeMime(raw: string): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalises a raw Content-Type header value to the canonical MIME form
|
|
3
|
+
* stored in `content_type_refs.normalized`: parameters (`; charset=...`)
|
|
4
|
+
* removed, C0 control characters (`0x00`..`0x1F`) and DEL (`0x7F`)
|
|
5
|
+
* stripped, trimmed, lower-cased.
|
|
6
|
+
*
|
|
7
|
+
* This is a derived column shared by two writers — the live-crawl upsert
|
|
8
|
+
* (`db-ops/_shared/upsert-content-type-ref.ts`) and the archive-migration
|
|
9
|
+
* populate (`populate-content-type-refs.ts`) — which MUST be
|
|
10
|
+
* same-input-same-output; a fork would split the dictionary so the same
|
|
11
|
+
* raw content-type maps to different normalized values depending on
|
|
12
|
+
* whether the row was crawled live or migrated. Keeping the single
|
|
13
|
+
* implementation here is what enforces that.
|
|
14
|
+
*
|
|
15
|
+
* Control chars are dropped character-by-character rather than via a
|
|
16
|
+
* regex literal so the source file carries no non-printing bytes and
|
|
17
|
+
* stays tool-friendly. `jsdom` / lax parsers occasionally emit them
|
|
18
|
+
* inside a raw Content-Type header (e.g. `text/html\r`).
|
|
19
|
+
* @param raw - Raw Content-Type header, guaranteed non-null non-empty.
|
|
20
|
+
* @returns Lower-cased MIME with parameters and control chars stripped.
|
|
21
|
+
* @example
|
|
22
|
+
* normalizeMime('Text/HTML; charset=UTF-8'); // => 'text/html'
|
|
23
|
+
*/
|
|
24
|
+
export function normalizeMime(raw) {
|
|
25
|
+
const semi = raw.indexOf(';');
|
|
26
|
+
const head = semi === -1 ? raw : raw.slice(0, semi);
|
|
27
|
+
let output = '';
|
|
28
|
+
for (const ch of head) {
|
|
29
|
+
const code = ch.codePointAt(0);
|
|
30
|
+
if (code < 32 /* 0x20 = SPACE */ || code === 127 /* 0x7F = DEL */) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
output += ch;
|
|
34
|
+
}
|
|
35
|
+
return output.trim().toLowerCase();
|
|
36
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ProgressCallback } from '../create-progress-reporter.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Populates `blob_refs` from every `images.src` / `images.currentSrc` /
|
|
5
|
+
* `resources.url` value that is a data URI longer than
|
|
6
|
+
* {@link DATA_URI_URL_REFS_LIMIT} bytes (issue #191).
|
|
7
|
+
*
|
|
8
|
+
* For each such value:
|
|
9
|
+
*
|
|
10
|
+
* 1. `decodeDataUri` strips the `data:...;base64,` (or `data:...,`) prefix
|
|
11
|
+
* and returns the raw payload bytes.
|
|
12
|
+
* 2. `computeContentHash` hashes the payload bytes (32-byte SHA-256).
|
|
13
|
+
* 3. The payload is zstd-compressed (`codec='zstd'`) — matching
|
|
14
|
+
* `page_html_blobs` / `json_refs`.
|
|
15
|
+
* 4. `INSERT OR IGNORE` on `hash` deduplicates: an `<img>` element and a
|
|
16
|
+
* resource that share the same underlying base64 payload share one
|
|
17
|
+
* `blob_refs` row.
|
|
18
|
+
*
|
|
19
|
+
* Malformed data URIs that `decodeDataUri` cannot decode are logged and
|
|
20
|
+
* skipped — the raw URI string still exists in the source row, and the
|
|
21
|
+
* migration script's operator can hunt it down from the warning log. The
|
|
22
|
+
* alternative — routing malformed large URIs back to `url_refs` — would
|
|
23
|
+
* break the "data URIs > threshold live in blob_refs" contract that 0.13
|
|
24
|
+
* lookups depend on.
|
|
25
|
+
*
|
|
26
|
+
* On the reference archive only ~429 images use data URIs, so the total
|
|
27
|
+
* dictionary is tiny — this step is I/O-cheap even without full
|
|
28
|
+
* chunking, but the same batching shape is used as elsewhere for
|
|
29
|
+
* consistency.
|
|
30
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
31
|
+
* @param onProgress - Optional sink for periodic progress lines (one per
|
|
32
|
+
* ~5% of each source table scanned); see {@link ../create-progress-reporter.ts}.
|
|
33
|
+
* @example
|
|
34
|
+
* await knex.transaction(async (trx) => {
|
|
35
|
+
* await populateBlobRefs(trx);
|
|
36
|
+
* });
|
|
37
|
+
*/
|
|
38
|
+
export declare function populateBlobRefs(trx: Knex, onProgress?: ProgressCallback): Promise<void>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { zstdCompressSync } from 'node:zlib';
|
|
2
|
+
import { createProgressReporter } from '../create-progress-reporter.js';
|
|
3
|
+
import { computeContentHash } from './compute-content-hash.js';
|
|
4
|
+
import { DATA_URI_URL_REFS_LIMIT } from './data-uri-url-refs-limit.js';
|
|
5
|
+
import { decodeDataUri } from './decode-data-uri.js';
|
|
6
|
+
/** Rows scanned per `SELECT`. See `populateTextRefs` for chunking rationale. */
|
|
7
|
+
const READ_CHUNK_SIZE = 2000;
|
|
8
|
+
/**
|
|
9
|
+
* Rows written per `INSERT`. blob_refs rows carry a compressed body BLOB
|
|
10
|
+
* so 100 rows keeps the total payload of a single INSERT statement well
|
|
11
|
+
* under WAL frame limits even for large SVGs.
|
|
12
|
+
*/
|
|
13
|
+
const INSERT_CHUNK_SIZE = 100;
|
|
14
|
+
/**
|
|
15
|
+
* Source table + its URL-shaped column(s) that may hold a data URI large
|
|
16
|
+
* enough to route to `blob_refs`. `resources.url` is the resource's own
|
|
17
|
+
* identity (unlike `pages`/`content_items`, whose own URL is always a
|
|
18
|
+
* real http(s) address in practice) — a `<link>`/CSS sub-resource can
|
|
19
|
+
* legally be captured as an inline `data:` URI.
|
|
20
|
+
*/
|
|
21
|
+
const URL_SOURCES = [
|
|
22
|
+
{ table: 'images', columns: ['src', 'currentSrc'] },
|
|
23
|
+
{ table: 'resources', columns: ['url'] },
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Populates `blob_refs` from every `images.src` / `images.currentSrc` /
|
|
27
|
+
* `resources.url` value that is a data URI longer than
|
|
28
|
+
* {@link DATA_URI_URL_REFS_LIMIT} bytes (issue #191).
|
|
29
|
+
*
|
|
30
|
+
* For each such value:
|
|
31
|
+
*
|
|
32
|
+
* 1. `decodeDataUri` strips the `data:...;base64,` (or `data:...,`) prefix
|
|
33
|
+
* and returns the raw payload bytes.
|
|
34
|
+
* 2. `computeContentHash` hashes the payload bytes (32-byte SHA-256).
|
|
35
|
+
* 3. The payload is zstd-compressed (`codec='zstd'`) — matching
|
|
36
|
+
* `page_html_blobs` / `json_refs`.
|
|
37
|
+
* 4. `INSERT OR IGNORE` on `hash` deduplicates: an `<img>` element and a
|
|
38
|
+
* resource that share the same underlying base64 payload share one
|
|
39
|
+
* `blob_refs` row.
|
|
40
|
+
*
|
|
41
|
+
* Malformed data URIs that `decodeDataUri` cannot decode are logged and
|
|
42
|
+
* skipped — the raw URI string still exists in the source row, and the
|
|
43
|
+
* migration script's operator can hunt it down from the warning log. The
|
|
44
|
+
* alternative — routing malformed large URIs back to `url_refs` — would
|
|
45
|
+
* break the "data URIs > threshold live in blob_refs" contract that 0.13
|
|
46
|
+
* lookups depend on.
|
|
47
|
+
*
|
|
48
|
+
* On the reference archive only ~429 images use data URIs, so the total
|
|
49
|
+
* dictionary is tiny — this step is I/O-cheap even without full
|
|
50
|
+
* chunking, but the same batching shape is used as elsewhere for
|
|
51
|
+
* consistency.
|
|
52
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
53
|
+
* @param onProgress - Optional sink for periodic progress lines (one per
|
|
54
|
+
* ~5% of each source table scanned); see {@link ../create-progress-reporter.ts}.
|
|
55
|
+
* @example
|
|
56
|
+
* await knex.transaction(async (trx) => {
|
|
57
|
+
* await populateBlobRefs(trx);
|
|
58
|
+
* });
|
|
59
|
+
*/
|
|
60
|
+
export async function populateBlobRefs(trx, onProgress) {
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
const pending = [];
|
|
63
|
+
for (const source of URL_SOURCES) {
|
|
64
|
+
const hasTable = await trx.schema.hasTable(source.table);
|
|
65
|
+
if (!hasTable) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const presentColumns = [];
|
|
69
|
+
for (const column of source.columns) {
|
|
70
|
+
if (await trx.schema.hasColumn(source.table, column)) {
|
|
71
|
+
presentColumns.push(column);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (presentColumns.length === 0) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const countRows = await trx(source.table).count({ n: '*' });
|
|
78
|
+
const total = Number(countRows[0]?.n ?? 0);
|
|
79
|
+
const report = createProgressReporter(`blob_refs (${source.table})`, total, onProgress);
|
|
80
|
+
let processed = 0;
|
|
81
|
+
let cursor = 0;
|
|
82
|
+
while (true) {
|
|
83
|
+
const rows = await trx(source.table)
|
|
84
|
+
.select('id', ...presentColumns)
|
|
85
|
+
.where('id', '>', cursor)
|
|
86
|
+
.orderBy('id', 'asc')
|
|
87
|
+
.limit(READ_CHUNK_SIZE);
|
|
88
|
+
if (rows.length === 0) {
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
cursor = rows.at(-1).id;
|
|
92
|
+
processed += rows.length;
|
|
93
|
+
report(processed);
|
|
94
|
+
for (const row of rows) {
|
|
95
|
+
for (const column of presentColumns) {
|
|
96
|
+
const value = row[column];
|
|
97
|
+
if (value == null || value.length <= DATA_URI_URL_REFS_LIMIT) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!value.startsWith('data:')) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const decoded = decodeDataUri(value);
|
|
104
|
+
if (decoded === null) {
|
|
105
|
+
// eslint-disable-next-line no-console
|
|
106
|
+
console.warn(`populateBlobRefs: skipping malformed data URI (length=${value.length}) — raw string still exists in ${source.table} row`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const hash = computeContentHash(decoded.bytes);
|
|
110
|
+
const hex = hash.toString('hex');
|
|
111
|
+
if (seen.has(hex)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
seen.add(hex);
|
|
115
|
+
const compressed = zstdCompressSync(decoded.bytes);
|
|
116
|
+
pending.push({
|
|
117
|
+
hash,
|
|
118
|
+
body: compressed,
|
|
119
|
+
codec: 'zstd',
|
|
120
|
+
size_raw: decoded.bytes.byteLength,
|
|
121
|
+
size_stored: compressed.byteLength,
|
|
122
|
+
});
|
|
123
|
+
if (pending.length >= INSERT_CHUNK_SIZE) {
|
|
124
|
+
await trx('blob_refs').insert(pending).onConflict('hash').ignore();
|
|
125
|
+
pending.length = 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (pending.length > 0) {
|
|
132
|
+
await trx('blob_refs').insert(pending).onConflict('hash').ignore();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Populates `content_type_refs` from every distinct `contentType` value
|
|
4
|
+
* currently stored in `pages` and `resources` (issue #191).
|
|
5
|
+
*
|
|
6
|
+
* Two independent DISTINCT SELECTs (one per table) are merged in JS
|
|
7
|
+
* rather than via SQL `UNION` — the cardinality is small in practice and
|
|
8
|
+
* doing it in JS avoids the knex-`union().select()` column-aliasing
|
|
9
|
+
* quirk (bare `.select()` on a union wraps in `SELECT *` and can lose
|
|
10
|
+
* the column name depending on driver version). Two per-table SELECTs
|
|
11
|
+
* are also fast because `contentType` is indexed on both tables via
|
|
12
|
+
* `idx_pages_listfilter` / natural column index (see `init-schema.ts`).
|
|
13
|
+
*
|
|
14
|
+
* `normalized` and `category` are derived in JS via {@link classifyContentType}
|
|
15
|
+
* so the rule table stays in one place; SQLite has no equivalent
|
|
16
|
+
* expression.
|
|
17
|
+
*
|
|
18
|
+
* `INSERT OR IGNORE` on the natural key `raw` makes this idempotent —
|
|
19
|
+
* re-running this populate after a partial failure never duplicates
|
|
20
|
+
* rows, only appends the new ones.
|
|
21
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
22
|
+
* @example
|
|
23
|
+
* await knex.transaction(async (trx) => {
|
|
24
|
+
* await populateContentTypeRefs(trx);
|
|
25
|
+
* });
|
|
26
|
+
*/
|
|
27
|
+
export declare function populateContentTypeRefs(trx: Knex): Promise<void>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { classifyContentType } from './classify-content-type.js';
|
|
2
|
+
import { normalizeMime } from './normalize-mime.js';
|
|
3
|
+
/**
|
|
4
|
+
* Distinct-`contentType` rows are collected from `pages` + `resources` in
|
|
5
|
+
* chunks of this size before being written into `content_type_refs`. The
|
|
6
|
+
* DISTINCT list is expected to be tiny (< 1000 rows even on the largest
|
|
7
|
+
* archives — the wire content-type space is small) so the chunk size only
|
|
8
|
+
* bounds worst-case parameter counts on `INSERT ... VALUES ...`.
|
|
9
|
+
*/
|
|
10
|
+
const INSERT_CHUNK_SIZE = 500;
|
|
11
|
+
/**
|
|
12
|
+
* Populates `content_type_refs` from every distinct `contentType` value
|
|
13
|
+
* currently stored in `pages` and `resources` (issue #191).
|
|
14
|
+
*
|
|
15
|
+
* Two independent DISTINCT SELECTs (one per table) are merged in JS
|
|
16
|
+
* rather than via SQL `UNION` — the cardinality is small in practice and
|
|
17
|
+
* doing it in JS avoids the knex-`union().select()` column-aliasing
|
|
18
|
+
* quirk (bare `.select()` on a union wraps in `SELECT *` and can lose
|
|
19
|
+
* the column name depending on driver version). Two per-table SELECTs
|
|
20
|
+
* are also fast because `contentType` is indexed on both tables via
|
|
21
|
+
* `idx_pages_listfilter` / natural column index (see `init-schema.ts`).
|
|
22
|
+
*
|
|
23
|
+
* `normalized` and `category` are derived in JS via {@link classifyContentType}
|
|
24
|
+
* so the rule table stays in one place; SQLite has no equivalent
|
|
25
|
+
* expression.
|
|
26
|
+
*
|
|
27
|
+
* `INSERT OR IGNORE` on the natural key `raw` makes this idempotent —
|
|
28
|
+
* re-running this populate after a partial failure never duplicates
|
|
29
|
+
* rows, only appends the new ones.
|
|
30
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
31
|
+
* @example
|
|
32
|
+
* await knex.transaction(async (trx) => {
|
|
33
|
+
* await populateContentTypeRefs(trx);
|
|
34
|
+
* });
|
|
35
|
+
*/
|
|
36
|
+
export async function populateContentTypeRefs(trx) {
|
|
37
|
+
const distinctRaw = new Set();
|
|
38
|
+
if (await trx.schema.hasTable('pages')) {
|
|
39
|
+
const pagesRows = await trx('pages')
|
|
40
|
+
.distinct('contentType')
|
|
41
|
+
.whereNotNull('contentType');
|
|
42
|
+
for (const { contentType } of pagesRows) {
|
|
43
|
+
if (contentType != null && contentType !== '') {
|
|
44
|
+
distinctRaw.add(contentType);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (await trx.schema.hasTable('resources')) {
|
|
49
|
+
const resourcesRows = await trx('resources')
|
|
50
|
+
.distinct('contentType')
|
|
51
|
+
.whereNotNull('contentType');
|
|
52
|
+
for (const { contentType } of resourcesRows) {
|
|
53
|
+
if (contentType != null && contentType !== '') {
|
|
54
|
+
distinctRaw.add(contentType);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (distinctRaw.size === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const inserts = [...distinctRaw].map((raw) => ({
|
|
62
|
+
raw,
|
|
63
|
+
normalized: normalizeMime(raw),
|
|
64
|
+
category: classifyContentType(raw),
|
|
65
|
+
}));
|
|
66
|
+
for (let index = 0; index < inserts.length; index += INSERT_CHUNK_SIZE) {
|
|
67
|
+
const chunk = inserts.slice(index, index + INSERT_CHUNK_SIZE);
|
|
68
|
+
await trx('content_type_refs').insert(chunk).onConflict('raw').ignore();
|
|
69
|
+
}
|
|
70
|
+
}
|