@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,33 @@
|
|
|
1
|
+
import { MigrationVerificationError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #6: every legacy `resources` row is mirrored
|
|
4
|
+
* by one row in `resource_items`.
|
|
5
|
+
*
|
|
6
|
+
* `populateResourceItems` fills `resource_items` with the same PK as
|
|
7
|
+
* `resources.id`
|
|
8
|
+
* (see `populate-resource-items.ts`); the invariant is broken only if the
|
|
9
|
+
* populate loop skipped rows during URL / header-set / content-type
|
|
10
|
+
* resolution. The paired invariant `resource_ref_edges = "resources-referrers"`
|
|
11
|
+
* is left unchecked here because issue #194's spec list stops at the
|
|
12
|
+
* six enumerated checks, and `resource_ref_edges` populate is a straight
|
|
13
|
+
* `INSERT … SELECT` (no dedup, no resolution) with negligible failure surface.
|
|
14
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
15
|
+
* @throws {MigrationVerificationError} when the row counts diverge.
|
|
16
|
+
*/
|
|
17
|
+
export async function checkResourceItemsCount(trx) {
|
|
18
|
+
const resourceItemsRows = await trx('resource_items').count({
|
|
19
|
+
n: '*',
|
|
20
|
+
});
|
|
21
|
+
const resourcesRows = await trx('resources').count({ n: '*' });
|
|
22
|
+
const resourceItemsCount = Number(resourceItemsRows[0].n);
|
|
23
|
+
const resourcesCount = Number(resourcesRows[0].n);
|
|
24
|
+
if (resourceItemsCount !== resourcesCount) {
|
|
25
|
+
throw new MigrationVerificationError({
|
|
26
|
+
check: '#6 resource_items row count',
|
|
27
|
+
context: {
|
|
28
|
+
resource_items: resourceItemsCount,
|
|
29
|
+
resources: resourcesCount,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #8: `content_items.url_id → url_refs.url`
|
|
4
|
+
* round-trips back to the original `pages.url` on a deterministic
|
|
5
|
+
* ≤ {@link SAMPLE_SIZE} sample.
|
|
6
|
+
*
|
|
7
|
+
* A mismatch means one of two things:
|
|
8
|
+
*
|
|
9
|
+
* - `populate-url-refs.ts` collapsed two distinct URLs into the same
|
|
10
|
+
* `url_refs.id` (BINARY collation bug, whitespace normalisation, …).
|
|
11
|
+
* - `populate-content-items.ts` associated a page id with the wrong
|
|
12
|
+
* `url_ref.id` (row-order dependency in the batch resolver).
|
|
13
|
+
*
|
|
14
|
+
* Additionally an FK gap (`content_items.url_id` pointing at a
|
|
15
|
+
* non-existent `url_refs.id`) or a missing `pages` row surfaces as a
|
|
16
|
+
* `roundTripUrl` / `sourceUrl` of `null`; the check treats either as a
|
|
17
|
+
* round-trip failure. LEFT JOINs are used deliberately so orphan rows
|
|
18
|
+
* are observable — an INNER JOIN would silently drop them and let the
|
|
19
|
+
* bug the invariant is meant to catch slip through.
|
|
20
|
+
*
|
|
21
|
+
* Sampling is **deterministic**: stride = ⌈count(content_items) / N⌉
|
|
22
|
+
* gives approximately {@link SAMPLE_SIZE} rows spread across the id
|
|
23
|
+
* range for large archives, and the full table for archives with
|
|
24
|
+
* ≤ {@link SAMPLE_SIZE} rows. Deterministic sampling means the same
|
|
25
|
+
* archive always produces the same verdict; a random sample would let a
|
|
26
|
+
* failing archive pass on retry if the offending rows happen to fall
|
|
27
|
+
* outside the second draw.
|
|
28
|
+
*
|
|
29
|
+
* The check throws when the sampled row count is smaller than expected
|
|
30
|
+
* (LEFT JOINs may return `null` sides but never fewer rows than the
|
|
31
|
+
* driving `content_items` set — a shortfall means SQLite silently
|
|
32
|
+
* dropped rows we intended to inspect). "Expected" is a separate
|
|
33
|
+
* `COUNT(*)` against the same `(id % stride) = 0` predicate, not a
|
|
34
|
+
* `floor(total / stride)` estimate: `content_items.id` is copied
|
|
35
|
+
* verbatim from the legacy `pages.id`, which accumulates gaps over an
|
|
36
|
+
* archive's crawl history (redirect consolidation, deleted pages, …).
|
|
37
|
+
* A density estimate over-counts whenever the id range is sparser than
|
|
38
|
+
* `[1, total]`, which produced false-positive failures on real
|
|
39
|
+
* multi-thousand-page archives.
|
|
40
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
41
|
+
* @throws {MigrationVerificationError} when at least one sampled URL does not round-trip.
|
|
42
|
+
*/
|
|
43
|
+
export declare function checkUrlRoundTrip(trx: Knex): Promise<void>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { MigrationVerificationError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sample size for the URL round-trip smoke test — a 1,000-row sample per
|
|
4
|
+
* issue #194.
|
|
5
|
+
* Not statistical coverage — URL normalisation bugs tend to be systematic
|
|
6
|
+
* (all rows with a particular scheme or host), so a modest evenly-spread
|
|
7
|
+
* sample catches them reliably.
|
|
8
|
+
*/
|
|
9
|
+
const SAMPLE_SIZE = 1000;
|
|
10
|
+
/**
|
|
11
|
+
* Verifies 0.13 invariant #8: `content_items.url_id → url_refs.url`
|
|
12
|
+
* round-trips back to the original `pages.url` on a deterministic
|
|
13
|
+
* ≤ {@link SAMPLE_SIZE} sample.
|
|
14
|
+
*
|
|
15
|
+
* A mismatch means one of two things:
|
|
16
|
+
*
|
|
17
|
+
* - `populate-url-refs.ts` collapsed two distinct URLs into the same
|
|
18
|
+
* `url_refs.id` (BINARY collation bug, whitespace normalisation, …).
|
|
19
|
+
* - `populate-content-items.ts` associated a page id with the wrong
|
|
20
|
+
* `url_ref.id` (row-order dependency in the batch resolver).
|
|
21
|
+
*
|
|
22
|
+
* Additionally an FK gap (`content_items.url_id` pointing at a
|
|
23
|
+
* non-existent `url_refs.id`) or a missing `pages` row surfaces as a
|
|
24
|
+
* `roundTripUrl` / `sourceUrl` of `null`; the check treats either as a
|
|
25
|
+
* round-trip failure. LEFT JOINs are used deliberately so orphan rows
|
|
26
|
+
* are observable — an INNER JOIN would silently drop them and let the
|
|
27
|
+
* bug the invariant is meant to catch slip through.
|
|
28
|
+
*
|
|
29
|
+
* Sampling is **deterministic**: stride = ⌈count(content_items) / N⌉
|
|
30
|
+
* gives approximately {@link SAMPLE_SIZE} rows spread across the id
|
|
31
|
+
* range for large archives, and the full table for archives with
|
|
32
|
+
* ≤ {@link SAMPLE_SIZE} rows. Deterministic sampling means the same
|
|
33
|
+
* archive always produces the same verdict; a random sample would let a
|
|
34
|
+
* failing archive pass on retry if the offending rows happen to fall
|
|
35
|
+
* outside the second draw.
|
|
36
|
+
*
|
|
37
|
+
* The check throws when the sampled row count is smaller than expected
|
|
38
|
+
* (LEFT JOINs may return `null` sides but never fewer rows than the
|
|
39
|
+
* driving `content_items` set — a shortfall means SQLite silently
|
|
40
|
+
* dropped rows we intended to inspect). "Expected" is a separate
|
|
41
|
+
* `COUNT(*)` against the same `(id % stride) = 0` predicate, not a
|
|
42
|
+
* `floor(total / stride)` estimate: `content_items.id` is copied
|
|
43
|
+
* verbatim from the legacy `pages.id`, which accumulates gaps over an
|
|
44
|
+
* archive's crawl history (redirect consolidation, deleted pages, …).
|
|
45
|
+
* A density estimate over-counts whenever the id range is sparser than
|
|
46
|
+
* `[1, total]`, which produced false-positive failures on real
|
|
47
|
+
* multi-thousand-page archives.
|
|
48
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
49
|
+
* @throws {MigrationVerificationError} when at least one sampled URL does not round-trip.
|
|
50
|
+
*/
|
|
51
|
+
export async function checkUrlRoundTrip(trx) {
|
|
52
|
+
const totalRows = await trx('content_items').count({ n: '*' });
|
|
53
|
+
const total = Number(totalRows[0].n);
|
|
54
|
+
if (total === 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// stride = ⌈total / SAMPLE_SIZE⌉ so `id % stride = 0` picks approximately
|
|
58
|
+
// SAMPLE_SIZE rows evenly across the id range for large archives, and the
|
|
59
|
+
// full table for archives ≤ SAMPLE_SIZE.
|
|
60
|
+
const stride = Math.max(1, Math.ceil(total / SAMPLE_SIZE));
|
|
61
|
+
const matchingRows = await trx('content_items')
|
|
62
|
+
.whereRaw('(id % ?) = 0', [stride])
|
|
63
|
+
.count({ n: '*' });
|
|
64
|
+
const expectedSize = Math.min(SAMPLE_SIZE, Number(matchingRows[0].n));
|
|
65
|
+
const sample = await trx('content_items as ci')
|
|
66
|
+
.leftJoin('pages as p', 'p.id', 'ci.id')
|
|
67
|
+
.leftJoin('url_refs as ur', 'ur.id', 'ci.url_id')
|
|
68
|
+
.select('ci.id as id', 'p.url as sourceUrl', 'ur.url as roundTripUrl')
|
|
69
|
+
.whereRaw('(ci.id % ?) = 0', [stride])
|
|
70
|
+
.orderBy('ci.id')
|
|
71
|
+
.limit(SAMPLE_SIZE);
|
|
72
|
+
if (sample.length < expectedSize) {
|
|
73
|
+
// LEFT JOINs preserve driving-table row counts (`p.id` / `url_refs.id`
|
|
74
|
+
// are PKs → 1:1 or 0:1 match). A shortfall means SQLite silently
|
|
75
|
+
// dropped rows the WHERE clause matched — a real integrity signal.
|
|
76
|
+
throw new MigrationVerificationError({
|
|
77
|
+
check: '#8 URL round-trip',
|
|
78
|
+
context: {
|
|
79
|
+
content_items_total: total,
|
|
80
|
+
stride,
|
|
81
|
+
expected_sample_size: expectedSize,
|
|
82
|
+
actual_sample_size: sample.length,
|
|
83
|
+
reason: 'sample query returned fewer rows than expected',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
for (const row of sample) {
|
|
88
|
+
if (row.sourceUrl === null || row.roundTripUrl === null) {
|
|
89
|
+
throw new MigrationVerificationError({
|
|
90
|
+
check: '#8 URL round-trip',
|
|
91
|
+
context: {
|
|
92
|
+
page_id: row.id,
|
|
93
|
+
source_url: row.sourceUrl,
|
|
94
|
+
round_trip_url: row.roundTripUrl,
|
|
95
|
+
sample_size: sample.length,
|
|
96
|
+
reason: 'orphan row — pages or url_refs join returned null (FK gap)',
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (row.sourceUrl !== row.roundTripUrl) {
|
|
101
|
+
throw new MigrationVerificationError({
|
|
102
|
+
check: '#8 URL round-trip',
|
|
103
|
+
context: {
|
|
104
|
+
page_id: row.id,
|
|
105
|
+
source_url: row.sourceUrl,
|
|
106
|
+
round_trip_url: row.roundTripUrl,
|
|
107
|
+
sample_size: sample.length,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for 0.13 migration verification.
|
|
3
|
+
*
|
|
4
|
+
* These are the acceptance-invariant checks that `scripts/migrate-to-0.13.mjs`
|
|
5
|
+
* runs at the end of the migration to confirm that the 0.13 entity /
|
|
6
|
+
* edge populates produced a consistent archive. Each check is one file
|
|
7
|
+
* under `verify-migration/` and throws {@link MigrationVerificationError} on mismatch;
|
|
8
|
+
* the orchestrator {@link ./verify-migration.ts} chains them in the
|
|
9
|
+
* order defined by issue #194.
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Row-count snapshot collected by every successful verify run and returned
|
|
14
|
+
* by {@link import('./verify-migration.js').verifyMigration}.
|
|
15
|
+
* The migration script logs these on the successful path so operators can
|
|
16
|
+
* eyeball migration outcomes from stdout without re-opening the archive.
|
|
17
|
+
*/
|
|
18
|
+
export interface MigrationVerificationSummary {
|
|
19
|
+
/** `count(*) FROM content_items` — matches `count(pages)`. */
|
|
20
|
+
readonly contentItems: number;
|
|
21
|
+
/** `count(*) FROM page_meta` — matches `count(pages WHERE scraped=1)`. */
|
|
22
|
+
readonly pageMeta: number;
|
|
23
|
+
/** `count(*) FROM anchor_edges` — always ≤ `count(anchors)`. */
|
|
24
|
+
readonly anchorEdges: number;
|
|
25
|
+
/** `SUM(count) FROM anchor_edges` — matches `count(anchors)`. */
|
|
26
|
+
readonly anchorEdgesSum: number;
|
|
27
|
+
/** `count(*) FROM image_items` — matches `count(images)`. */
|
|
28
|
+
readonly imageItems: number;
|
|
29
|
+
/** `count(*) FROM resource_items` — matches `count(resources)`. */
|
|
30
|
+
readonly resourceItems: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Structured details of a single migration invariant that failed. Attached to
|
|
34
|
+
* {@link MigrationVerificationError} so operators can see which check tripped
|
|
35
|
+
* without parsing the error message.
|
|
36
|
+
*/
|
|
37
|
+
export interface MigrationVerificationErrorDetails {
|
|
38
|
+
/**
|
|
39
|
+
* Human-readable check identifier matching issue #194's numbered list
|
|
40
|
+
* (e.g. `'#3'` for the anchor-edge dedup range check). Included in the
|
|
41
|
+
* error message so migration logs point at the exact clause.
|
|
42
|
+
*/
|
|
43
|
+
readonly check: string;
|
|
44
|
+
/**
|
|
45
|
+
* Optional structured payload — typically `expected` / `actual` counts or
|
|
46
|
+
* the offending page id from the URL round-trip sample. Rendered into the
|
|
47
|
+
* error message so operators can diagnose without re-running the check.
|
|
48
|
+
*/
|
|
49
|
+
readonly context?: Readonly<Record<string, string | number | null>>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Error thrown by any 0.13 check when an invariant does not hold. The
|
|
53
|
+
* migration script catches this at the top level and aborts with a non-zero
|
|
54
|
+
* exit code; the enclosing transaction rolls back so the archive returns to
|
|
55
|
+
* its pre-populate state (ref tables from ref populate stay populated but are
|
|
56
|
+
* additive).
|
|
57
|
+
*
|
|
58
|
+
* Prefer a single error class with a structured {@link details} payload over
|
|
59
|
+
* one subclass per check: eight subclasses would create eight nearly
|
|
60
|
+
* identical constructors, and callers (script + integration test) only need
|
|
61
|
+
* to distinguish "verification failed" from other error kinds.
|
|
62
|
+
*/
|
|
63
|
+
export declare class MigrationVerificationError extends Error {
|
|
64
|
+
readonly details: MigrationVerificationErrorDetails;
|
|
65
|
+
/**
|
|
66
|
+
* @param details - Structured description of which check failed and why.
|
|
67
|
+
* Rendered into the error message on construction.
|
|
68
|
+
*/
|
|
69
|
+
constructor(details: MigrationVerificationErrorDetails);
|
|
70
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for 0.13 migration verification.
|
|
3
|
+
*
|
|
4
|
+
* These are the acceptance-invariant checks that `scripts/migrate-to-0.13.mjs`
|
|
5
|
+
* runs at the end of the migration to confirm that the 0.13 entity /
|
|
6
|
+
* edge populates produced a consistent archive. Each check is one file
|
|
7
|
+
* under `verify-migration/` and throws {@link MigrationVerificationError} on mismatch;
|
|
8
|
+
* the orchestrator {@link ./verify-migration.ts} chains them in the
|
|
9
|
+
* order defined by issue #194.
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Error thrown by any 0.13 check when an invariant does not hold. The
|
|
14
|
+
* migration script catches this at the top level and aborts with a non-zero
|
|
15
|
+
* exit code; the enclosing transaction rolls back so the archive returns to
|
|
16
|
+
* its pre-populate state (ref tables from ref populate stay populated but are
|
|
17
|
+
* additive).
|
|
18
|
+
*
|
|
19
|
+
* Prefer a single error class with a structured {@link details} payload over
|
|
20
|
+
* one subclass per check: eight subclasses would create eight nearly
|
|
21
|
+
* identical constructors, and callers (script + integration test) only need
|
|
22
|
+
* to distinguish "verification failed" from other error kinds.
|
|
23
|
+
*/
|
|
24
|
+
export class MigrationVerificationError extends Error {
|
|
25
|
+
details;
|
|
26
|
+
/**
|
|
27
|
+
* @param details - Structured description of which check failed and why.
|
|
28
|
+
* Rendered into the error message on construction.
|
|
29
|
+
*/
|
|
30
|
+
constructor(details) {
|
|
31
|
+
const contextText = details.context === undefined
|
|
32
|
+
? ''
|
|
33
|
+
: ' — ' +
|
|
34
|
+
Object.entries(details.context)
|
|
35
|
+
.map(([key, value]) =>
|
|
36
|
+
// Render null as the parenthesised literal `(null)` so
|
|
37
|
+
// operators reading the log can distinguish "the sample
|
|
38
|
+
// query returned no rows" from a caller that passed the
|
|
39
|
+
// four-character string `"null"`. Strings get quoted for
|
|
40
|
+
// the same reason.
|
|
41
|
+
`${key}=${formatContextValue(value)}`)
|
|
42
|
+
.join(', ');
|
|
43
|
+
super(`migration verification failed ${details.check}${contextText}`);
|
|
44
|
+
this.details = details;
|
|
45
|
+
this.name = 'MigrationVerificationError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Renders one context value for {@link MigrationVerificationError}'s message.
|
|
50
|
+
* `null` becomes the literal `(null)`; strings are wrapped in double quotes;
|
|
51
|
+
* numbers pass through as their decimal representation. The intent is to
|
|
52
|
+
* make missing-vs-present distinguishable in operator logs.
|
|
53
|
+
* @param value - Value from `MigrationVerificationErrorDetails.context`.
|
|
54
|
+
*/
|
|
55
|
+
function formatContextValue(value) {
|
|
56
|
+
if (value === null) {
|
|
57
|
+
return '(null)';
|
|
58
|
+
}
|
|
59
|
+
if (typeof value === 'string') {
|
|
60
|
+
return JSON.stringify(value);
|
|
61
|
+
}
|
|
62
|
+
return String(value);
|
|
63
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { MigrationVerificationSummary } from './types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Runs every 0.13 migration acceptance invariant against a populated archive and
|
|
5
|
+
* returns the verified row-count summary on success. The migration script
|
|
6
|
+
* (`scripts/migrate-to-0.13.mjs`) calls this **inside** the same
|
|
7
|
+
* `knex.transaction()` block that ran `populateEntityTables` so a thrown
|
|
8
|
+
* {@link MigrationVerificationError} rolls back the entire entity populate — ref
|
|
9
|
+
* tables from ref populate stay committed because they live in a separate, additive
|
|
10
|
+
* transaction and never lose facts on re-run.
|
|
11
|
+
*
|
|
12
|
+
* The returned {@link MigrationVerificationSummary} is echoed to stdout by the
|
|
13
|
+
* migration script so operators can eyeball migration outcomes (`did we lose
|
|
14
|
+
* rows silently within the invariant window?`) from batch pipeline logs
|
|
15
|
+
* without re-opening the archive. Callers must run this inside a `db.transaction()`
|
|
16
|
+
* for the counts and the JOIN-based checks (#7 / #8) to see a consistent
|
|
17
|
+
* snapshot of the archive.
|
|
18
|
+
*
|
|
19
|
+
* Checks run sequentially in issue #194's numbered order so error messages
|
|
20
|
+
* pinpoint the earliest broken invariant. Sequential (rather than parallel)
|
|
21
|
+
* execution keeps the failure diagnostics focused: fanning eight COUNTs out
|
|
22
|
+
* at once would return the first-completed rejection rather than the
|
|
23
|
+
* lowest-numbered one, which surprises operators reading migration logs.
|
|
24
|
+
* The individual COUNT queries are fast enough at any real archive scale
|
|
25
|
+
* for sequential wall-clock cost to be irrelevant.
|
|
26
|
+
*
|
|
27
|
+
* Non-{@link MigrationVerificationError} exceptions from a check (e.g. a
|
|
28
|
+
* transient libsql error or a schema-drift SqliteError) are wrapped in a
|
|
29
|
+
* {@link MigrationVerificationError} so the migration script's `catch` sees a
|
|
30
|
+
* uniform failure surface — otherwise operators grepping stderr for
|
|
31
|
+
* `migration verification failed` would miss driver-side errors.
|
|
32
|
+
* @param trx - Knex instance or transaction connected to the archive
|
|
33
|
+
* **after** every 0.13 migration populate has run. Must be a transaction
|
|
34
|
+
* in production so ref counts, JOIN samples, and error rollback see the
|
|
35
|
+
* same snapshot; non-transactional callers (unit tests) work but do not
|
|
36
|
+
* get snapshot isolation.
|
|
37
|
+
* @returns Row-count summary of the verified archive.
|
|
38
|
+
* @throws {MigrationVerificationError} on the first invariant that does not
|
|
39
|
+
* hold; subsequent checks are skipped.
|
|
40
|
+
*/
|
|
41
|
+
export declare function verifyMigration(trx: Knex): Promise<MigrationVerificationSummary>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { checkAnchorEdgesCount } from './check-anchor-edges-count.js';
|
|
2
|
+
import { checkAnchorEdgesSum } from './check-anchor-edges-sum.js';
|
|
3
|
+
import { checkContentItemsCount } from './check-content-items-count.js';
|
|
4
|
+
import { checkContentTypePreservation } from './check-content-type-preservation.js';
|
|
5
|
+
import { checkImageItemsCount } from './check-image-items-count.js';
|
|
6
|
+
import { checkPageMetaCount } from './check-page-meta-count.js';
|
|
7
|
+
import { checkReaderParity } from './check-reader-parity.js';
|
|
8
|
+
import { checkResourceItemsCount } from './check-resource-items-count.js';
|
|
9
|
+
import { checkUrlRoundTrip } from './check-url-round-trip.js';
|
|
10
|
+
import { MigrationVerificationError } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Runs every 0.13 migration acceptance invariant against a populated archive and
|
|
13
|
+
* returns the verified row-count summary on success. The migration script
|
|
14
|
+
* (`scripts/migrate-to-0.13.mjs`) calls this **inside** the same
|
|
15
|
+
* `knex.transaction()` block that ran `populateEntityTables` so a thrown
|
|
16
|
+
* {@link MigrationVerificationError} rolls back the entire entity populate — ref
|
|
17
|
+
* tables from ref populate stay committed because they live in a separate, additive
|
|
18
|
+
* transaction and never lose facts on re-run.
|
|
19
|
+
*
|
|
20
|
+
* The returned {@link MigrationVerificationSummary} is echoed to stdout by the
|
|
21
|
+
* migration script so operators can eyeball migration outcomes (`did we lose
|
|
22
|
+
* rows silently within the invariant window?`) from batch pipeline logs
|
|
23
|
+
* without re-opening the archive. Callers must run this inside a `db.transaction()`
|
|
24
|
+
* for the counts and the JOIN-based checks (#7 / #8) to see a consistent
|
|
25
|
+
* snapshot of the archive.
|
|
26
|
+
*
|
|
27
|
+
* Checks run sequentially in issue #194's numbered order so error messages
|
|
28
|
+
* pinpoint the earliest broken invariant. Sequential (rather than parallel)
|
|
29
|
+
* execution keeps the failure diagnostics focused: fanning eight COUNTs out
|
|
30
|
+
* at once would return the first-completed rejection rather than the
|
|
31
|
+
* lowest-numbered one, which surprises operators reading migration logs.
|
|
32
|
+
* The individual COUNT queries are fast enough at any real archive scale
|
|
33
|
+
* for sequential wall-clock cost to be irrelevant.
|
|
34
|
+
*
|
|
35
|
+
* Non-{@link MigrationVerificationError} exceptions from a check (e.g. a
|
|
36
|
+
* transient libsql error or a schema-drift SqliteError) are wrapped in a
|
|
37
|
+
* {@link MigrationVerificationError} so the migration script's `catch` sees a
|
|
38
|
+
* uniform failure surface — otherwise operators grepping stderr for
|
|
39
|
+
* `migration verification failed` would miss driver-side errors.
|
|
40
|
+
* @param trx - Knex instance or transaction connected to the archive
|
|
41
|
+
* **after** every 0.13 migration populate has run. Must be a transaction
|
|
42
|
+
* in production so ref counts, JOIN samples, and error rollback see the
|
|
43
|
+
* same snapshot; non-transactional callers (unit tests) work but do not
|
|
44
|
+
* get snapshot isolation.
|
|
45
|
+
* @returns Row-count summary of the verified archive.
|
|
46
|
+
* @throws {MigrationVerificationError} on the first invariant that does not
|
|
47
|
+
* hold; subsequent checks are skipped.
|
|
48
|
+
*/
|
|
49
|
+
export async function verifyMigration(trx) {
|
|
50
|
+
try {
|
|
51
|
+
await checkContentItemsCount(trx);
|
|
52
|
+
await checkPageMetaCount(trx);
|
|
53
|
+
await checkAnchorEdgesCount(trx);
|
|
54
|
+
await checkAnchorEdgesSum(trx);
|
|
55
|
+
await checkImageItemsCount(trx);
|
|
56
|
+
await checkResourceItemsCount(trx);
|
|
57
|
+
await checkContentTypePreservation(trx);
|
|
58
|
+
await checkUrlRoundTrip(trx);
|
|
59
|
+
// Reader-level parity between the legacy tables and the
|
|
60
|
+
// new entity tables (see `checkReaderParity` for the eight totals
|
|
61
|
+
// this catches that the row-count checks above miss).
|
|
62
|
+
await checkReaderParity(trx);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error instanceof MigrationVerificationError) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
throw new MigrationVerificationError({
|
|
69
|
+
check: 'runtime',
|
|
70
|
+
context: {
|
|
71
|
+
underlying_error: error instanceof Error ? error.message : String(error),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return collectSummary(trx);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Runs the six row counts that populate {@link MigrationVerificationSummary}.
|
|
79
|
+
* Separate from the invariant checks so those stay side-effect-free asserts,
|
|
80
|
+
* and this runs once at the end when all invariants have already held.
|
|
81
|
+
* @param trx - Same Knex handle passed to {@link verifyMigration}.
|
|
82
|
+
*/
|
|
83
|
+
async function collectSummary(trx) {
|
|
84
|
+
const [contentItems, pageMeta, anchorEdges, anchorEdgesSum, imageItems, resourceItems] = await Promise.all([
|
|
85
|
+
countOf(trx, 'content_items'),
|
|
86
|
+
countOf(trx, 'page_meta'),
|
|
87
|
+
countOf(trx, 'anchor_edges'),
|
|
88
|
+
sumOf(trx, 'anchor_edges', 'count'),
|
|
89
|
+
countOf(trx, 'image_items'),
|
|
90
|
+
countOf(trx, 'resource_items'),
|
|
91
|
+
]);
|
|
92
|
+
return {
|
|
93
|
+
contentItems,
|
|
94
|
+
pageMeta,
|
|
95
|
+
anchorEdges,
|
|
96
|
+
anchorEdgesSum,
|
|
97
|
+
imageItems,
|
|
98
|
+
resourceItems,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* `SELECT count(*) FROM <table>` returning a plain number.
|
|
103
|
+
* @param trx - Knex handle.
|
|
104
|
+
* @param table - Table to count.
|
|
105
|
+
*/
|
|
106
|
+
async function countOf(trx, table) {
|
|
107
|
+
const rows = await trx(table).count({ n: '*' });
|
|
108
|
+
return Number(rows[0].n);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* `SELECT SUM(<column>) FROM <table>` returning a plain number (`null` sum
|
|
112
|
+
* on an empty table collapses to zero).
|
|
113
|
+
* @param trx - Knex handle.
|
|
114
|
+
* @param table - Table to sum over.
|
|
115
|
+
* @param column - Column to sum.
|
|
116
|
+
*/
|
|
117
|
+
async function sumOf(trx, table, column) {
|
|
118
|
+
const rows = await trx(table).sum({ n: column });
|
|
119
|
+
return Number(rows[0].n ?? 0);
|
|
120
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ErrorKind } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Classify a raw crawler/scraper error message into a coarse {@link ErrorKind}.
|
|
4
|
+
*
|
|
5
|
+
* Pure and deterministic: the same message always yields the same kind, which
|
|
6
|
+
* is why the kind is derived on read rather than persisted — it can be applied
|
|
7
|
+
* uniformly to freshly captured `crawl_errors`, legacy `error.log` lines, and
|
|
8
|
+
* `page_errors` alike.
|
|
9
|
+
* @param message - The raw error message (a single line is sufficient; the
|
|
10
|
+
* cause token such as `ENOTFOUND` or `Navigation timeout` lives there).
|
|
11
|
+
* @returns The matched kind, or `unknown` when no matcher applies.
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* classifyErrorKind('getaddrinfo ENOTFOUND www.example.com'); // 'dns'
|
|
15
|
+
* classifyErrorKind('gave up after 3 retries — Race 180,000ms'); // 'timeout'
|
|
16
|
+
* classifyErrorKind('Protocol error (Page.reload): Target closed'); // 'protocol'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function classifyErrorKind(message: string): ErrorKind;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordered message matchers. The first pattern that matches wins, so more
|
|
3
|
+
* specific transport causes (DNS, TLS, connection-*) are tested before the
|
|
4
|
+
* broader `protocol` / `timeout` buckets — e.g. `ETIMEDOUT` must classify as
|
|
5
|
+
* `connection-timeout`, not the page-level `timeout`, and a puppeteer
|
|
6
|
+
* `Protocol error` must not be swallowed by the `timeout` matcher.
|
|
7
|
+
*/
|
|
8
|
+
const MATCHERS = [
|
|
9
|
+
// `dns-transient` must be evaluated before `dns`: an `EAI_AGAIN` line also
|
|
10
|
+
// carries the `getaddrinfo` token, so the more specific transient pattern
|
|
11
|
+
// has to win. Splitting it out from `dns` keeps the DNS-burned host cache
|
|
12
|
+
// (which marks on `kind === 'dns'`) from punishing a host whose only sin
|
|
13
|
+
// was a local resolver hiccup.
|
|
14
|
+
{ kind: 'dns-transient', pattern: /EAI_AGAIN|\bEREFUSED\b/i },
|
|
15
|
+
{
|
|
16
|
+
kind: 'dns',
|
|
17
|
+
pattern: /ENOTFOUND|getaddrinfo|ERR_NAME_NOT_RESOLVED|ERR_NAME_RESOLUTION_FAILED/i,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
kind: 'tls',
|
|
21
|
+
// `Hostname/IP does not match certificate's altnames` is Node's
|
|
22
|
+
// node:tls hostname mismatch error and is emphatically a TLS issue;
|
|
23
|
+
// adding it here (alongside the OpenSSL / Chromium tokens) keeps
|
|
24
|
+
// hosts that serve the wrong-name cert (common with misconfigured
|
|
25
|
+
// edge / load-balancer setups) out of `unknown`. `altnames` is
|
|
26
|
+
// anchored to the preceding `certificate` token so a request whose
|
|
27
|
+
// error message merely mentions a path containing the substring
|
|
28
|
+
// `altnames` (e.g. `https://api.example.com/altnames/lookup` in a
|
|
29
|
+
// 5xx body) does NOT get misclassified into `tls` (which is a
|
|
30
|
+
// `PERMANENT_ERROR_KINDS` member — a false-positive would
|
|
31
|
+
// permanently exclude that page from `--retry-failed`).
|
|
32
|
+
pattern: /ERR_CERT|ERR_SSL|\bCERT_|SSL routines|ERR_BAD_SSL|UNABLE_TO_VERIFY|unable to verify|self.signed certificate|certificate has expired\s*$|\bERR_TLS|Hostname\/IP does not match certificate|certificate'?s? altnames/i,
|
|
33
|
+
},
|
|
34
|
+
{ kind: 'connection-refused', pattern: /ECONNREFUSED|ERR_CONNECTION_REFUSED/i },
|
|
35
|
+
{
|
|
36
|
+
kind: 'connection-reset',
|
|
37
|
+
pattern: /ECONNRESET|socket hang up|ERR_CONNECTION_RESET|ERR_CONNECTION_CLOSED|ERR_EMPTY_RESPONSE/i,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
kind: 'connection-timeout',
|
|
41
|
+
pattern: /ETIMEDOUT|ERR_CONNECTION_TIMED_OUT|ERR_TIMED_OUT/i,
|
|
42
|
+
},
|
|
43
|
+
// `local-network` is evaluated AFTER the connection-* matchers so a
|
|
44
|
+
// concrete cause (refused / reset / timeout) wins when both apply. Only
|
|
45
|
+
// "local network is unreachable / changed" symptoms — and the OS-level
|
|
46
|
+
// errors that surface them — land here. Short tokens (`EPIPE`, `EREFUSED`)
|
|
47
|
+
// are word-bounded so unrelated identifiers don't false-positive.
|
|
48
|
+
{
|
|
49
|
+
kind: 'local-network',
|
|
50
|
+
pattern: /ERR_INTERNET_DISCONNECTED|ERR_NETWORK_CHANGED|ERR_NETWORK_IO_SUSPENDED|ERR_ADDRESS_UNREACHABLE|ERR_NETWORK_UNREACHABLE|ENETUNREACH|EHOSTUNREACH|EADDRNOTAVAIL|ENOTCONN|\bEPIPE\b/i,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
kind: 'parse-error',
|
|
54
|
+
pattern: /Parse Error|Expected HTTP\/|Unexpected end of stream/i,
|
|
55
|
+
},
|
|
56
|
+
// `client-blocked` covers Chromium's ERR_BLOCKED_* family — the browser
|
|
57
|
+
// actively decided to reject the request (ad/tracker heuristics, CSP,
|
|
58
|
+
// CORB / ORB, administrator block list, fingerprinting protection,
|
|
59
|
+
// cleartext policy, …). Per the upstream `net/base/net_error_list.h`,
|
|
60
|
+
// `ERR_BLOCKED_BY_CLIENT` is documented as "The client chose to block
|
|
61
|
+
// the request." — i.e. the server was never the deciding party. Listed
|
|
62
|
+
// before `protocol` so puppeteer's generic "Protocol error" wrapper
|
|
63
|
+
// (which sometimes embeds the underlying net error code) is correctly
|
|
64
|
+
// attributed to the blocked layer rather than the protocol layer.
|
|
65
|
+
{
|
|
66
|
+
kind: 'client-blocked',
|
|
67
|
+
pattern: /ERR_BLOCKED_BY_CLIENT|ERR_BLOCKED_BY_ADMINISTRATOR|ERR_BLOCKED_IN_INCOGNITO_BY_ADMINISTRATOR|ERR_BLOCKED_BY_RESPONSE|ERR_BLOCKED_BY_CSP|ERR_BLOCKED_BY_ORB|ERR_BLOCKED_BY_FINGERPRINTING_PROTECTION|ERR_CLEARTEXT_NOT_PERMITTED|ERR_NETWORK_ACCESS_REVOKED/i,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
kind: 'protocol',
|
|
71
|
+
// `detached frame` is anchored to puppeteer's exact prefix
|
|
72
|
+
// `Attempted to use detached Frame` (its current Frame.ts
|
|
73
|
+
// emitter; the `i` flag below catches the lowercase variant
|
|
74
|
+
// automatically), not the bare two-token substring. The bare
|
|
75
|
+
// form would match unrelated diagnostics like a console message
|
|
76
|
+
// "detached frame ref leaked" echoed through a logger. The older
|
|
77
|
+
// Page-domain `frame (?:was |got )?detached` form is kept as a
|
|
78
|
+
// separate alternative because Chromium still surfaces that
|
|
79
|
+
// phrasing in some legacy code paths. Without one of these, the
|
|
80
|
+
// "Attempted to use detached Frame ..." messages observed on a
|
|
81
|
+
// real archive would slip into `unknown`.
|
|
82
|
+
pattern: /Protocol error|Target closed|Session closed|Execution context was destroyed|frame (?:was |got )?detached|Attempted to use detached frame|Navigating frame was detached|Cannot find context|Node with given id|Page\.\w+ returned/i,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
kind: 'timeout',
|
|
86
|
+
// `Timeout: https?:` matches the NetTimeoutError "Timeout: <url>"
|
|
87
|
+
// form. Looking for the URL-shaped tail (rather than anchoring at
|
|
88
|
+
// line start) is what lets us catch the beholder-wrapped variant
|
|
89
|
+
// `[Retried N times] Timeout: https://...` that gets stored in
|
|
90
|
+
// `crawl_errors` / `error.log` after retry exhaustion — the bare
|
|
91
|
+
// `^Timeout:` form would only fire on the immediate failure and
|
|
92
|
+
// miss every retry-exhausted record (the ones that actually land
|
|
93
|
+
// in the archive). Required for slow-server timeouts that
|
|
94
|
+
// previously fell into `unknown`.
|
|
95
|
+
pattern: /Race \d|Navigation timeout|timeout of \d+\s*ms exceeded|TimeoutError|Timed out|Timeout: https?:/i,
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
/**
|
|
99
|
+
* Classify a raw crawler/scraper error message into a coarse {@link ErrorKind}.
|
|
100
|
+
*
|
|
101
|
+
* Pure and deterministic: the same message always yields the same kind, which
|
|
102
|
+
* is why the kind is derived on read rather than persisted — it can be applied
|
|
103
|
+
* uniformly to freshly captured `crawl_errors`, legacy `error.log` lines, and
|
|
104
|
+
* `page_errors` alike.
|
|
105
|
+
* @param message - The raw error message (a single line is sufficient; the
|
|
106
|
+
* cause token such as `ENOTFOUND` or `Navigation timeout` lives there).
|
|
107
|
+
* @returns The matched kind, or `unknown` when no matcher applies.
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* classifyErrorKind('getaddrinfo ENOTFOUND www.example.com'); // 'dns'
|
|
111
|
+
* classifyErrorKind('gave up after 3 retries — Race 180,000ms'); // 'timeout'
|
|
112
|
+
* classifyErrorKind('Protocol error (Page.reload): Target closed'); // 'protocol'
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export function classifyErrorKind(message) {
|
|
116
|
+
for (const { kind, pattern } of MATCHERS) {
|
|
117
|
+
if (pattern.test(message)) {
|
|
118
|
+
return kind;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return 'unknown';
|
|
122
|
+
}
|