@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,148 @@
|
|
|
1
|
+
import { createProgressReporter } from '../create-progress-reporter.js';
|
|
2
|
+
import { DATA_URI_URL_REFS_LIMIT } from './data-uri-url-refs-limit.js';
|
|
3
|
+
import { decomposeUrl } from './decompose-url.js';
|
|
4
|
+
/**
|
|
5
|
+
* Rows inserted per `INSERT INTO url_refs ... VALUES (...)` statement. Kept
|
|
6
|
+
* well under SQLite's `SQLITE_MAX_VARIABLE_NUMBER` (default 32766 on
|
|
7
|
+
* modern builds) — each row binds 7 parameters (url + scheme + host +
|
|
8
|
+
* port + path + query_hash + fragment), so 500 rows = 3500 params.
|
|
9
|
+
*/
|
|
10
|
+
const INSERT_CHUNK_SIZE = 500;
|
|
11
|
+
/**
|
|
12
|
+
* Rows scanned per keyset-paginated `SELECT` chunk. One SELECT reads all
|
|
13
|
+
* URL columns for the source table's chunk simultaneously so the pages
|
|
14
|
+
* table is scanned exactly once per populate call, not N-times-once-
|
|
15
|
+
* per-URL-column — a per-column scan would multiply migration wall-clock
|
|
16
|
+
* by the count of URL-shaped columns.
|
|
17
|
+
*/
|
|
18
|
+
const READ_CHUNK_SIZE = 5000;
|
|
19
|
+
/**
|
|
20
|
+
* Source table + its URL-shaped columns. Each column is scanned in
|
|
21
|
+
* lock-step per row, then filtered through
|
|
22
|
+
* {@link routeDataUriAwayFromUrlRefs} so large data URIs skip `url_refs`
|
|
23
|
+
* and route to `blob_refs` instead (handled by
|
|
24
|
+
* {@link ./populate-blob-refs.ts}).
|
|
25
|
+
*
|
|
26
|
+
* The filter is applied to EVERY URL column (not just image columns):
|
|
27
|
+
* `og:image`, `icon_href`, `apple-touch-icon`, `twitter:image`, and even
|
|
28
|
+
* `og:url` / `canonical` can legally hold data URIs in the wild, and a
|
|
29
|
+
* >512-byte data URI in any of those must land in `blob_refs`, not
|
|
30
|
+
* pollute the URL dictionary.
|
|
31
|
+
*/
|
|
32
|
+
const URL_SOURCES = [
|
|
33
|
+
{
|
|
34
|
+
table: 'pages',
|
|
35
|
+
columns: [
|
|
36
|
+
'url',
|
|
37
|
+
'canonical',
|
|
38
|
+
'og_url',
|
|
39
|
+
'og_image',
|
|
40
|
+
'icon_href',
|
|
41
|
+
'appleTouchIcon_href',
|
|
42
|
+
'amphtml',
|
|
43
|
+
'manifest',
|
|
44
|
+
'twitter_image',
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{ table: 'resources', columns: ['url'] },
|
|
48
|
+
{ table: 'images', columns: ['src', 'currentSrc'] },
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Predicate applied to every URL column value: keep the value in the
|
|
52
|
+
* `url_refs` stream unless it is a data URI larger than
|
|
53
|
+
* {@link DATA_URI_URL_REFS_LIMIT}. Large data URIs route to `blob_refs`
|
|
54
|
+
* instead (via {@link ./populate-blob-refs.ts}).
|
|
55
|
+
* @param value - Raw column value (non-null, non-empty by contract).
|
|
56
|
+
* @returns `true` if the value belongs in `url_refs`.
|
|
57
|
+
*/
|
|
58
|
+
function routeDataUriAwayFromUrlRefs(value) {
|
|
59
|
+
if (!value.startsWith('data:')) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return value.length <= DATA_URI_URL_REFS_LIMIT;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Populates `url_refs` from every URL-shaped column across `pages`,
|
|
66
|
+
* `resources`, and `images` (issue #191).
|
|
67
|
+
*
|
|
68
|
+
* Runs two passes per source table:
|
|
69
|
+
*
|
|
70
|
+
* 1. **Collect distinct URLs** into an in-process `Set<string>`, streaming
|
|
71
|
+
* rows from each source table by keyset pagination on `id`. All the
|
|
72
|
+
* table's URL columns are read in a single SELECT so the pages scan
|
|
73
|
+
* is O(rows), not O(rows × columns). Peak memory is bounded by the
|
|
74
|
+
* count of distinct URLs, not the row count.
|
|
75
|
+
* 2. **Bulk-insert** into `url_refs` in chunks of {@link INSERT_CHUNK_SIZE},
|
|
76
|
+
* with the decomposed columns (`scheme` / `host` / `port` / `path` /
|
|
77
|
+
* `query_hash` / `fragment`) derived in JS by {@link decomposeUrl}.
|
|
78
|
+
*
|
|
79
|
+
* `INSERT OR IGNORE` on `url_refs.url` makes the step idempotent —
|
|
80
|
+
* repeated invocations after partial failure only add new URLs.
|
|
81
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
82
|
+
* @param onProgress - Optional sink for periodic progress lines (one per
|
|
83
|
+
* ~5% of each source table scanned); see {@link ../create-progress-reporter.ts}.
|
|
84
|
+
* @example
|
|
85
|
+
* await knex.transaction(async (trx) => {
|
|
86
|
+
* await populateUrlRefs(trx);
|
|
87
|
+
* });
|
|
88
|
+
*/
|
|
89
|
+
export async function populateUrlRefs(trx, onProgress) {
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
for (const source of URL_SOURCES) {
|
|
92
|
+
const hasTable = await trx.schema.hasTable(source.table);
|
|
93
|
+
if (!hasTable) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const presentColumns = [];
|
|
97
|
+
for (const column of source.columns) {
|
|
98
|
+
if (await trx.schema.hasColumn(source.table, column)) {
|
|
99
|
+
presentColumns.push(column);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (presentColumns.length === 0) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const countRows = await trx(source.table).count({ n: '*' });
|
|
106
|
+
const total = Number(countRows[0]?.n ?? 0);
|
|
107
|
+
const report = createProgressReporter(`url_refs (${source.table})`, total, onProgress);
|
|
108
|
+
let processed = 0;
|
|
109
|
+
let cursor = 0;
|
|
110
|
+
while (true) {
|
|
111
|
+
const rows = await trx(source.table)
|
|
112
|
+
.select('id', ...presentColumns)
|
|
113
|
+
.where('id', '>', cursor)
|
|
114
|
+
.orderBy('id', 'asc')
|
|
115
|
+
.limit(READ_CHUNK_SIZE);
|
|
116
|
+
if (rows.length === 0) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
cursor = rows.at(-1).id;
|
|
120
|
+
processed += rows.length;
|
|
121
|
+
report(processed);
|
|
122
|
+
for (const row of rows) {
|
|
123
|
+
for (const column of presentColumns) {
|
|
124
|
+
const value = row[column];
|
|
125
|
+
if (typeof value !== 'string' || value === '') {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (!routeDataUriAwayFromUrlRefs(value)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
seen.add(value);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (seen.size === 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const inserts = [];
|
|
140
|
+
for (const url of seen) {
|
|
141
|
+
const decomposed = decomposeUrl(url);
|
|
142
|
+
inserts.push({ url, ...decomposed });
|
|
143
|
+
}
|
|
144
|
+
for (let index = 0; index < inserts.length; index += INSERT_CHUNK_SIZE) {
|
|
145
|
+
const chunk = inserts.slice(index, index + INSERT_CHUNK_SIZE);
|
|
146
|
+
await trx('url_refs').insert(chunk).onConflict('url').ignore();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Runs `SELECT count(?) FROM <table>` and returns the count as a plain
|
|
4
|
+
* JS number. Exists purely to keep spec files inside populate-ref-tables/ compliant
|
|
5
|
+
* with `unicorn/no-await-expression-member` — inlining
|
|
6
|
+
* `Number((await db(t).count(...))[0]!.n)` reads fine but violates the
|
|
7
|
+
* rule at every callsite.
|
|
8
|
+
* @param db - Knex instance (typically a spec-local in-memory DB).
|
|
9
|
+
* @param table - Table name.
|
|
10
|
+
* @param countColumn - Column to count; defaults to `'id'` because every
|
|
11
|
+
* populate-refs table except `header_set_entries` / `header_flags` uses
|
|
12
|
+
* that PK name. Pass `'header_set_id'` for the two exceptions.
|
|
13
|
+
* @returns Row count.
|
|
14
|
+
*/
|
|
15
|
+
export declare function countRows(db: Knex, table: string, countColumn?: string): Promise<number>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `SELECT count(?) FROM <table>` and returns the count as a plain
|
|
3
|
+
* JS number. Exists purely to keep spec files inside populate-ref-tables/ compliant
|
|
4
|
+
* with `unicorn/no-await-expression-member` — inlining
|
|
5
|
+
* `Number((await db(t).count(...))[0]!.n)` reads fine but violates the
|
|
6
|
+
* rule at every callsite.
|
|
7
|
+
* @param db - Knex instance (typically a spec-local in-memory DB).
|
|
8
|
+
* @param table - Table name.
|
|
9
|
+
* @param countColumn - Column to count; defaults to `'id'` because every
|
|
10
|
+
* populate-refs table except `header_set_entries` / `header_flags` uses
|
|
11
|
+
* that PK name. Pass `'header_set_id'` for the two exceptions.
|
|
12
|
+
* @returns Row count.
|
|
13
|
+
*/
|
|
14
|
+
export async function countRows(db, table, countColumn = 'id') {
|
|
15
|
+
const rows = await db(table).count({ n: countColumn });
|
|
16
|
+
return Number(rows[0].n);
|
|
17
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain types for the 0.13 population step (issue #191). Every
|
|
3
|
+
* shared interface / type alias lives here per the repo-wide
|
|
4
|
+
* `型は types.ts に集約` rule; implementation modules export functions
|
|
5
|
+
* only.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Decomposed URL components that populate `url_refs.{scheme,host,port,path,
|
|
9
|
+
* query_hash,fragment}`. All slots are populated in a single pass over the
|
|
10
|
+
* `url` string so callers do not have to re-parse per column.
|
|
11
|
+
*/
|
|
12
|
+
export interface DecomposedUrl {
|
|
13
|
+
/**
|
|
14
|
+
* URL scheme without the trailing colon (`https`, `http`, `mailto`, ...).
|
|
15
|
+
* `null` for URLs that fail `new URL(...)` parsing.
|
|
16
|
+
*/
|
|
17
|
+
scheme: string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Host lower-cased (`www.example.com`). `null` when the URL has no
|
|
20
|
+
* `authority` component (e.g. `mailto:`, `data:`, `javascript:`), and
|
|
21
|
+
* `null` on parse failure.
|
|
22
|
+
*/
|
|
23
|
+
host: string | null;
|
|
24
|
+
/**
|
|
25
|
+
* TCP port when explicit in the URL; `null` when the URL uses the
|
|
26
|
+
* scheme default (WHATWG URL normalises default ports away — we do NOT
|
|
27
|
+
* synthesise a default here because two rows differing only by explicit
|
|
28
|
+
* vs implicit `:443` should hash to the same URL, and `url_refs.url` is
|
|
29
|
+
* the natural key, not this column).
|
|
30
|
+
*/
|
|
31
|
+
port: number | null;
|
|
32
|
+
/**
|
|
33
|
+
* Path portion (`/foo/bar`). `null` when the URL has no path or the
|
|
34
|
+
* URL uses an opaque scheme whose "pathname" is really an in-band
|
|
35
|
+
* payload (`data:`, `blob:`, `javascript:`) — see
|
|
36
|
+
* {@link ../decompose-url.ts} for the opaque-scheme handling.
|
|
37
|
+
*/
|
|
38
|
+
path: string | null;
|
|
39
|
+
/**
|
|
40
|
+
* 32-byte content hash of the raw query string exactly as it appears in
|
|
41
|
+
* the URL (`?a=1&b=2` — the leading `?` is stripped before hashing).
|
|
42
|
+
* `null` when the URL has no query string, when the query string is
|
|
43
|
+
* empty, or on parse failure.
|
|
44
|
+
*
|
|
45
|
+
* Storing the raw query would defeat dedup on tracker URLs whose
|
|
46
|
+
* per-request query keys blow up dictionary size; the hash retains the
|
|
47
|
+
* same-URL-⇒-same-row property without the payload.
|
|
48
|
+
*/
|
|
49
|
+
query_hash: Buffer | null;
|
|
50
|
+
/**
|
|
51
|
+
* Fragment (`#section-2`) with the leading `#` stripped. `null` when
|
|
52
|
+
* the URL has no fragment or on parse failure.
|
|
53
|
+
*/
|
|
54
|
+
fragment: string | null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Successful decode of a `data:` URI: the raw payload bytes.
|
|
58
|
+
*/
|
|
59
|
+
export interface DecodedDataUri {
|
|
60
|
+
/** Payload bytes after base64 or percent-decoding. */
|
|
61
|
+
bytes: Buffer;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One header entry after decomposition — flattened out of the parsed JSON
|
|
65
|
+
* `Record<name, string | string[]>` so that:
|
|
66
|
+
*
|
|
67
|
+
* - Multiple values for the same name (e.g. two `Set-Cookie` lines) each
|
|
68
|
+
* get their own entry with a distinct `occurrence` ordinal starting at 1.
|
|
69
|
+
* - `isVolatile` is pre-computed once per entry (rather than re-looked-up
|
|
70
|
+
* at hash time) so both `stableHash` and the `header_set_entries`
|
|
71
|
+
* inserts see identical classification.
|
|
72
|
+
*/
|
|
73
|
+
export interface HeaderEntry {
|
|
74
|
+
/** Lower-cased header name; comparison and dedup keys always use this form. */
|
|
75
|
+
name: string;
|
|
76
|
+
/** Raw header value (each element of `string[]` inputs becomes one entry). */
|
|
77
|
+
value: string;
|
|
78
|
+
/**
|
|
79
|
+
* 1-based ordinal among entries sharing the same `name` within one
|
|
80
|
+
* response. Always `1` for single-value headers; ≥ 2 when a name
|
|
81
|
+
* repeats (e.g. multiple `Set-Cookie` lines).
|
|
82
|
+
*/
|
|
83
|
+
occurrence: number;
|
|
84
|
+
/** Pre-computed volatility. */
|
|
85
|
+
isVolatile: boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Result of decomposing one raw `responseHeaders` JSON string into the
|
|
89
|
+
* shape required for `header_sets`, `header_set_entries`, and
|
|
90
|
+
* `header_flags` inserts. `null` results from the decompose helper
|
|
91
|
+
* indicate "no header set to insert" (null / `{}` / parse failure);
|
|
92
|
+
* callers write `header_set_id = null` to the referring row.
|
|
93
|
+
*/
|
|
94
|
+
export interface DecomposedHeaderSet {
|
|
95
|
+
/**
|
|
96
|
+
* 32-byte hash of the raw JSON string exactly as stored on the source
|
|
97
|
+
* row. Populates `header_sets.raw_json_hash` — a temporary column used
|
|
98
|
+
* by the 0.13 lookup to map an old `pages.responseHeaders` /
|
|
99
|
+
* `resources.responseHeaders` value back to its `header_sets.id`
|
|
100
|
+
* without any SQL function call.
|
|
101
|
+
*/
|
|
102
|
+
rawJsonHash: Buffer;
|
|
103
|
+
/**
|
|
104
|
+
* 32-byte hash of the sorted `name=value` pairs of **all** entries
|
|
105
|
+
* (stable + volatile). Populates `header_sets.raw_hash` — a UNIQUE
|
|
106
|
+
* column so a JS-side upsert can dedup on identical decoded sets.
|
|
107
|
+
*/
|
|
108
|
+
rawHash: Buffer;
|
|
109
|
+
/**
|
|
110
|
+
* 32-byte hash of the sorted `name=value` pairs of stable entries
|
|
111
|
+
* only. Populates `header_sets.stable_hash` — indexed so 0.13
|
|
112
|
+
* readers can answer "how many responses share this stable header
|
|
113
|
+
* profile" without scanning every entry.
|
|
114
|
+
*/
|
|
115
|
+
stableHash: Buffer;
|
|
116
|
+
/**
|
|
117
|
+
* 32-byte hash of the sorted `name=value` pairs of volatile entries
|
|
118
|
+
* only, or `null` when the set has no volatile entries. Populates
|
|
119
|
+
* `header_sets.volatile_hash`.
|
|
120
|
+
*/
|
|
121
|
+
volatileHash: Buffer | null;
|
|
122
|
+
/** Every decomposed entry, ordered by (name, occurrence). */
|
|
123
|
+
entries: readonly HeaderEntry[];
|
|
124
|
+
/** Total entry count — matches `header_sets.entry_count`. */
|
|
125
|
+
entryCount: number;
|
|
126
|
+
/**
|
|
127
|
+
* Number of stable entries — matches `header_sets.stable_entry_count`.
|
|
128
|
+
* `volatile_entry_count` is derived as `entry_count - stable_entry_count`
|
|
129
|
+
* and not stored separately.
|
|
130
|
+
*/
|
|
131
|
+
stableEntryCount: number;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Shape written into `header_flags` for one `header_set_id`. `has_*`
|
|
135
|
+
* columns are `INTEGER NOT NULL` (0/1) per the 0.13 DDL;
|
|
136
|
+
* `cache_policy` is nullable and holds a compact summary of the
|
|
137
|
+
* `Cache-Control` value (or `null` when absent).
|
|
138
|
+
*/
|
|
139
|
+
export interface HeaderFlagsRow {
|
|
140
|
+
/** `1` when at least one entry's name equals `content-security-policy`. */
|
|
141
|
+
has_csp: 0 | 1;
|
|
142
|
+
/** `1` when at least one entry's name equals `x-frame-options`. */
|
|
143
|
+
has_x_frame_options: 0 | 1;
|
|
144
|
+
/** `1` when at least one entry's name equals `x-content-type-options`. */
|
|
145
|
+
has_x_content_type_options: 0 | 1;
|
|
146
|
+
/** `1` when at least one entry's name equals `strict-transport-security`. */
|
|
147
|
+
has_hsts: 0 | 1;
|
|
148
|
+
/** `1` when at least one entry's name equals `referrer-policy`. */
|
|
149
|
+
has_referrer_policy: 0 | 1;
|
|
150
|
+
/** `1` when at least one entry's name equals `permissions-policy`. */
|
|
151
|
+
has_permissions_policy: 0 | 1;
|
|
152
|
+
/** `1` when at least one entry's name equals `set-cookie`. */
|
|
153
|
+
has_set_cookie: 0 | 1;
|
|
154
|
+
/**
|
|
155
|
+
* The `cache-control` value, verbatim, when present; `null` when
|
|
156
|
+
* absent. Multi-value `Cache-Control` headers are joined with `, ` so
|
|
157
|
+
* the summary captures every directive without losing per-occurrence
|
|
158
|
+
* context.
|
|
159
|
+
*/
|
|
160
|
+
cache_policy: string | null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* In-process caches shared by every header-table upsert against one
|
|
164
|
+
* archive connection. The three id-maps resolve `header_name_refs.id` /
|
|
165
|
+
* `header_value_refs.id` / `header_sets.id` without hitting the DB once
|
|
166
|
+
* a value has been seen (or preloaded from existing rows).
|
|
167
|
+
*
|
|
168
|
+
* `setIdsProcessedThisRun` tracks the subset of `setIdByRawJsonHash` /
|
|
169
|
+
* `setIdByRawHash` that the current process has already written
|
|
170
|
+
* entries + flags for. A preloaded id from a prior run is present in the
|
|
171
|
+
* id caches but NOT in this set — so the entries + flags inserts still
|
|
172
|
+
* run once (safe under `INSERT OR IGNORE`) to repair any missing rows
|
|
173
|
+
* left by a crashed writer. Once entries + flags for a given setId have
|
|
174
|
+
* been written this run, the shortcut skips redundant no-op inserts.
|
|
175
|
+
*/
|
|
176
|
+
export interface HeaderTableCaches {
|
|
177
|
+
/** `lower-cased name → header_name_refs.id`. */
|
|
178
|
+
readonly nameIdCache: Map<string, number>;
|
|
179
|
+
/** `hexHash|value → header_value_refs.id`. */
|
|
180
|
+
readonly valueIdCache: Map<string, number>;
|
|
181
|
+
/** `hex(raw_json_hash) → header_sets.id`. */
|
|
182
|
+
readonly setIdByRawJsonHash: Map<string, number>;
|
|
183
|
+
/** `hex(raw_hash) → header_sets.id`. */
|
|
184
|
+
readonly setIdByRawHash: Map<string, number>;
|
|
185
|
+
/** `header_sets.id` values already fully processed by this process. */
|
|
186
|
+
readonly setIdsProcessedThisRun: Set<number>;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Category label attached to each content-type rule. Mirrors the union
|
|
190
|
+
* `ContentTypeCategory` in `@nitpicker/query/src/types.ts` — kept as a
|
|
191
|
+
* plain string literal here so `@nitpicker/crawler`'s 0.13 step
|
|
192
|
+
* does not need to depend on `@nitpicker/query` (which itself depends
|
|
193
|
+
* on this package). If the two copies drift, `classify-content-type.spec.ts`
|
|
194
|
+
* here and `content-type-rules.spec.ts` on the query side will disagree
|
|
195
|
+
* on the same fixture.
|
|
196
|
+
*/
|
|
197
|
+
export type ContentTypeCategory = 'html' | 'pdf' | 'csv' | 'word' | 'excel' | 'powerpoint' | 'image' | 'audio' | 'video' | 'font' | 'css' | 'javascript' | 'json' | 'xml' | 'archive' | 'text' | 'other' | 'unknown';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DecomposedHeaderSet, HeaderTableCaches } from './types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Upserts one decomposed header set and its entries + flags, returning
|
|
5
|
+
* the `header_sets.id`. Shared by the bulk archive populate
|
|
6
|
+
* ({@link ./populate-header-tables.ts}) and the crawler's per-response
|
|
7
|
+
* write path, so both produce byte-identical header rows for the same
|
|
8
|
+
* raw `responseHeaders` value.
|
|
9
|
+
*
|
|
10
|
+
* Set-id resolution order:
|
|
11
|
+
*
|
|
12
|
+
* 1. `raw_json_hash` cache — same JSON key ordering as a previous row.
|
|
13
|
+
* 2. `raw_hash` cache — same sorted-entries identity, different JSON key
|
|
14
|
+
* ordering. This branch links the new `raw_json_hash` to the existing
|
|
15
|
+
* setId in the in-process map so subsequent same-JSON hits fast-path.
|
|
16
|
+
* 3. Otherwise INSERT a new `header_sets` row.
|
|
17
|
+
*
|
|
18
|
+
* Entries + flags are inserted once per `setId` per process (guarded by
|
|
19
|
+
* `setIdsProcessedThisRun`); already-inserted rows short-circuit
|
|
20
|
+
* cheaply, and preloaded-but-unprocessed setIds (partial prior run) get
|
|
21
|
+
* their entries / flags repaired via `INSERT OR IGNORE`.
|
|
22
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
23
|
+
* @param decomposed - Result of `decomposeHeaderSet` (never `null` — the
|
|
24
|
+
* caller short-circuits null decompositions to `header_set_id = null`).
|
|
25
|
+
* @param caches - The in-process caches, mutated in place. Create via
|
|
26
|
+
* {@link ./create-header-table-caches.ts}.
|
|
27
|
+
* @returns The `header_sets.id` for this set.
|
|
28
|
+
* @example
|
|
29
|
+
* const decomposed = decomposeHeaderSet(rawJson);
|
|
30
|
+
* if (decomposed !== null) {
|
|
31
|
+
* const setId = await upsertOneHeaderSet(knex, decomposed, caches);
|
|
32
|
+
* }
|
|
33
|
+
*/
|
|
34
|
+
export declare function upsertOneHeaderSet(trx: Knex, decomposed: DecomposedHeaderSet, caches: HeaderTableCaches): Promise<number>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { computeContentHash } from './compute-content-hash.js';
|
|
2
|
+
import { computeHeaderFlags } from './compute-header-flags.js';
|
|
3
|
+
import { headerValueCacheKey } from './header-value-cache-key.js';
|
|
4
|
+
/**
|
|
5
|
+
* Rows inserted per INSERT statement into `header_set_entries`. Each row
|
|
6
|
+
* binds 5 params so 500 rows = 2500 params — safely under SQLite's
|
|
7
|
+
* default variable limit.
|
|
8
|
+
*/
|
|
9
|
+
const ENTRY_INSERT_CHUNK_SIZE = 500;
|
|
10
|
+
/**
|
|
11
|
+
* Upserts one decomposed header set and its entries + flags, returning
|
|
12
|
+
* the `header_sets.id`. Shared by the bulk archive populate
|
|
13
|
+
* ({@link ./populate-header-tables.ts}) and the crawler's per-response
|
|
14
|
+
* write path, so both produce byte-identical header rows for the same
|
|
15
|
+
* raw `responseHeaders` value.
|
|
16
|
+
*
|
|
17
|
+
* Set-id resolution order:
|
|
18
|
+
*
|
|
19
|
+
* 1. `raw_json_hash` cache — same JSON key ordering as a previous row.
|
|
20
|
+
* 2. `raw_hash` cache — same sorted-entries identity, different JSON key
|
|
21
|
+
* ordering. This branch links the new `raw_json_hash` to the existing
|
|
22
|
+
* setId in the in-process map so subsequent same-JSON hits fast-path.
|
|
23
|
+
* 3. Otherwise INSERT a new `header_sets` row.
|
|
24
|
+
*
|
|
25
|
+
* Entries + flags are inserted once per `setId` per process (guarded by
|
|
26
|
+
* `setIdsProcessedThisRun`); already-inserted rows short-circuit
|
|
27
|
+
* cheaply, and preloaded-but-unprocessed setIds (partial prior run) get
|
|
28
|
+
* their entries / flags repaired via `INSERT OR IGNORE`.
|
|
29
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
30
|
+
* @param decomposed - Result of `decomposeHeaderSet` (never `null` — the
|
|
31
|
+
* caller short-circuits null decompositions to `header_set_id = null`).
|
|
32
|
+
* @param caches - The in-process caches, mutated in place. Create via
|
|
33
|
+
* {@link ./create-header-table-caches.ts}.
|
|
34
|
+
* @returns The `header_sets.id` for this set.
|
|
35
|
+
* @example
|
|
36
|
+
* const decomposed = decomposeHeaderSet(rawJson);
|
|
37
|
+
* if (decomposed !== null) {
|
|
38
|
+
* const setId = await upsertOneHeaderSet(knex, decomposed, caches);
|
|
39
|
+
* }
|
|
40
|
+
*/
|
|
41
|
+
export async function upsertOneHeaderSet(trx, decomposed, caches) {
|
|
42
|
+
const rawJsonHashHex = decomposed.rawJsonHash.toString('hex');
|
|
43
|
+
const rawHashHex = decomposed.rawHash.toString('hex');
|
|
44
|
+
let setId = caches.setIdByRawJsonHash.get(rawJsonHashHex);
|
|
45
|
+
if (setId === undefined) {
|
|
46
|
+
setId = caches.setIdByRawHash.get(rawHashHex);
|
|
47
|
+
if (setId === undefined) {
|
|
48
|
+
setId = await insertHeaderSet(trx, decomposed);
|
|
49
|
+
caches.setIdByRawJsonHash.set(rawJsonHashHex, setId);
|
|
50
|
+
caches.setIdByRawHash.set(rawHashHex, setId);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Same sorted-entries identity as an existing row, different JSON
|
|
54
|
+
// key ordering. Reuse the existing setId; the raw_json_hash of
|
|
55
|
+
// this variant has no persistent home in the 0.13 schema
|
|
56
|
+
// (raw_json_hash is UNIQUE per row) — linking it in the
|
|
57
|
+
// in-process map is enough for id resolution.
|
|
58
|
+
caches.setIdByRawJsonHash.set(rawJsonHashHex, setId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (caches.setIdsProcessedThisRun.has(setId)) {
|
|
62
|
+
return setId;
|
|
63
|
+
}
|
|
64
|
+
const nameIds = await resolveNameIds(trx, decomposed.entries, caches.nameIdCache);
|
|
65
|
+
const valueIds = await resolveValueIds(trx, decomposed.entries, caches.valueIdCache);
|
|
66
|
+
const entryRows = decomposed.entries.map((entry, index) => ({
|
|
67
|
+
header_set_id: setId,
|
|
68
|
+
name_id: nameIds[index],
|
|
69
|
+
occurrence: entry.occurrence,
|
|
70
|
+
value_id: valueIds[index],
|
|
71
|
+
is_volatile: entry.isVolatile ? 1 : 0,
|
|
72
|
+
}));
|
|
73
|
+
for (let index = 0; index < entryRows.length; index += ENTRY_INSERT_CHUNK_SIZE) {
|
|
74
|
+
const chunk = entryRows.slice(index, index + ENTRY_INSERT_CHUNK_SIZE);
|
|
75
|
+
await trx('header_set_entries')
|
|
76
|
+
.insert(chunk)
|
|
77
|
+
.onConflict(['header_set_id', 'name_id', 'occurrence'])
|
|
78
|
+
.ignore();
|
|
79
|
+
}
|
|
80
|
+
const flags = computeHeaderFlags(decomposed.entries);
|
|
81
|
+
await trx('header_flags')
|
|
82
|
+
.insert({ header_set_id: setId, ...flags })
|
|
83
|
+
.onConflict('header_set_id')
|
|
84
|
+
.ignore();
|
|
85
|
+
caches.setIdsProcessedThisRun.add(setId);
|
|
86
|
+
return setId;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Inserts a new `header_sets` row and returns its id. Neither
|
|
90
|
+
* `raw_json_hash` nor `raw_hash` collided with an existing row when
|
|
91
|
+
* this is called — the caller has already checked both caches.
|
|
92
|
+
* @param trx - Knex instance.
|
|
93
|
+
* @param decomposed - Result of `decomposeHeaderSet`.
|
|
94
|
+
* @returns The newly-inserted `header_sets.id`.
|
|
95
|
+
*/
|
|
96
|
+
async function insertHeaderSet(trx, decomposed) {
|
|
97
|
+
const rows = await trx('header_sets')
|
|
98
|
+
.insert({
|
|
99
|
+
raw_json_hash: decomposed.rawJsonHash,
|
|
100
|
+
raw_hash: decomposed.rawHash,
|
|
101
|
+
stable_hash: decomposed.stableHash,
|
|
102
|
+
volatile_hash: decomposed.volatileHash,
|
|
103
|
+
entry_count: decomposed.entryCount,
|
|
104
|
+
stable_entry_count: decomposed.stableEntryCount,
|
|
105
|
+
})
|
|
106
|
+
.returning('id');
|
|
107
|
+
const first = rows[0];
|
|
108
|
+
if (first === undefined) {
|
|
109
|
+
throw new Error('upsertOneHeaderSet: header_sets INSERT returned no rows despite absent conflict');
|
|
110
|
+
}
|
|
111
|
+
return first.id;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolves the `name_id` for every entry in `entries`, inserting new
|
|
115
|
+
* names into `header_name_refs` as needed. Mutates `cache` with any new
|
|
116
|
+
* ids.
|
|
117
|
+
* @param trx - Knex instance.
|
|
118
|
+
* @param entries - Decomposed entries.
|
|
119
|
+
* @param cache - Name → id cache; mutated in place.
|
|
120
|
+
* @returns Parallel array of name ids (same order as `entries`).
|
|
121
|
+
*/
|
|
122
|
+
async function resolveNameIds(trx, entries, cache) {
|
|
123
|
+
const missing = new Set();
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
if (!cache.has(entry.name)) {
|
|
126
|
+
missing.add(entry.name);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (missing.size > 0) {
|
|
130
|
+
const missingNames = [...missing];
|
|
131
|
+
// Chunked like the entries insert below: knex compiles a multi-row
|
|
132
|
+
// insert-with-onConflict into a compound SELECT, and SQLite rejects
|
|
133
|
+
// more than 500 compound terms per statement.
|
|
134
|
+
for (let index = 0; index < missingNames.length; index += ENTRY_INSERT_CHUNK_SIZE) {
|
|
135
|
+
const chunk = missingNames.slice(index, index + ENTRY_INSERT_CHUNK_SIZE);
|
|
136
|
+
await trx('header_name_refs')
|
|
137
|
+
.insert(chunk.map((name) => ({ name })))
|
|
138
|
+
.onConflict('name')
|
|
139
|
+
.ignore();
|
|
140
|
+
const inserted = await trx('header_name_refs')
|
|
141
|
+
.select('id', 'name')
|
|
142
|
+
.whereIn('name', chunk);
|
|
143
|
+
for (const row of inserted) {
|
|
144
|
+
cache.set(row.name, row.id);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return entries.map((entry) => {
|
|
149
|
+
const id = cache.get(entry.name);
|
|
150
|
+
if (id === undefined) {
|
|
151
|
+
throw new Error(`upsertOneHeaderSet: name_id not resolved for ${entry.name}`);
|
|
152
|
+
}
|
|
153
|
+
return id;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Resolves the `value_id` for every entry in `entries`, inserting new
|
|
158
|
+
* values into `header_value_refs` as needed. Values are hashed once and
|
|
159
|
+
* deduped by `(hash, value)` per the 0.13 UNIQUE constraint.
|
|
160
|
+
*
|
|
161
|
+
* The read-back after INSERT narrows on `.whereIn('value', ...)` — the
|
|
162
|
+
* string values just written — instead of binding the Buffer hashes into
|
|
163
|
+
* a `WHERE hash IN (...)`: a Buffer[] IN would return the correct rows
|
|
164
|
+
* in theory but leans on libsql's BLOB-parameter round-trip behaviour,
|
|
165
|
+
* while string values compare reliably. The cache key still includes the
|
|
166
|
+
* hash, so hash-distinct entries stay disambiguated.
|
|
167
|
+
* @param trx - Knex instance.
|
|
168
|
+
* @param entries - Decomposed entries.
|
|
169
|
+
* @param cache - `hexHash|value → id` cache; mutated in place.
|
|
170
|
+
* @returns Parallel array of value ids (same order as `entries`).
|
|
171
|
+
*/
|
|
172
|
+
async function resolveValueIds(trx, entries, cache) {
|
|
173
|
+
const hashByEntryIndex = entries.map((entry) => computeContentHash(entry.value));
|
|
174
|
+
const missing = [];
|
|
175
|
+
const missingKeys = new Set();
|
|
176
|
+
for (const [i, entry] of entries.entries()) {
|
|
177
|
+
const hash = hashByEntryIndex[i];
|
|
178
|
+
const key = headerValueCacheKey(hash, entry.value);
|
|
179
|
+
if (!cache.has(key) && !missingKeys.has(key)) {
|
|
180
|
+
missing.push({ key, hash, value: entry.value });
|
|
181
|
+
missingKeys.add(key);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Chunked like the entries insert: knex compiles a multi-row
|
|
185
|
+
// insert-with-onConflict into a compound SELECT, and SQLite rejects
|
|
186
|
+
// more than 500 compound terms per statement.
|
|
187
|
+
for (let index = 0; index < missing.length; index += ENTRY_INSERT_CHUNK_SIZE) {
|
|
188
|
+
const chunk = missing.slice(index, index + ENTRY_INSERT_CHUNK_SIZE);
|
|
189
|
+
await trx('header_value_refs')
|
|
190
|
+
.insert(chunk.map(({ hash, value }) => ({ hash, value })))
|
|
191
|
+
.onConflict(['hash', 'value'])
|
|
192
|
+
.ignore();
|
|
193
|
+
const inserted = await trx('header_value_refs')
|
|
194
|
+
.select('id', 'hash', 'value')
|
|
195
|
+
.whereIn('value', chunk.map((m) => m.value));
|
|
196
|
+
for (const row of inserted) {
|
|
197
|
+
cache.set(headerValueCacheKey(Buffer.from(row.hash), row.value), row.id);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return entries.map((entry, i) => {
|
|
201
|
+
const key = headerValueCacheKey(hashByEntryIndex[i], entry.value);
|
|
202
|
+
const id = cache.get(key);
|
|
203
|
+
if (id === undefined) {
|
|
204
|
+
throw new Error(`upsertOneHeaderSet: value_id not resolved for ${entry.value.slice(0, 40)}…`);
|
|
205
|
+
}
|
|
206
|
+
return id;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header names — always lower-cased — that are EXCLUDED from `stable_hash`.
|
|
3
|
+
* These change on every response (timestamps / request IDs / cache
|
|
4
|
+
* telemetry) and would defeat the dedup goal if hashed alongside the
|
|
5
|
+
* stable set.
|
|
6
|
+
*
|
|
7
|
+
* `set-cookie` sits here because per-session cookies rotate constantly on
|
|
8
|
+
* authenticated sites — it is deliberately classified as volatile even
|
|
9
|
+
* though it is technically a stable security signal.
|
|
10
|
+
*
|
|
11
|
+
* Any header not in this set is
|
|
12
|
+
* treated as **stable** by {@link ./header-stability.ts:isVolatileHeader}
|
|
13
|
+
* — the safer default because a genuinely volatile header misclassified
|
|
14
|
+
* as stable only weakens dedup (its per-response values fork
|
|
15
|
+
* otherwise-identical stable profiles into separate `stable_hash`
|
|
16
|
+
* clusters), while a stable header misclassified as volatile would drop
|
|
17
|
+
* a meaningful signal from `stable_hash` and falsely cluster distinct
|
|
18
|
+
* header configurations together.
|
|
19
|
+
*/
|
|
20
|
+
export declare const VOLATILE_HEADER_NAMES: ReadonlySet<string>;
|