@nitpicker/crawler 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/lib/archive/archive-accessor.d.ts +2 -2
- package/lib/archive/archive-accessor.js +2 -2
- package/lib/archive/archive-lock.d.ts +7 -0
- package/lib/archive/archive-lock.js +7 -0
- package/lib/archive/archive.d.ts +63 -16
- package/lib/archive/archive.js +56 -17
- package/lib/archive/create-adjunct-tables.d.ts +43 -0
- package/lib/archive/create-adjunct-tables.js +213 -0
- package/lib/archive/create-entity-tables.d.ts +173 -0
- package/lib/archive/create-entity-tables.js +318 -0
- package/lib/archive/create-progress-reporter.d.ts +30 -0
- package/lib/archive/create-progress-reporter.js +38 -0
- package/lib/archive/create-ref-tables.d.ts +35 -0
- package/lib/archive/create-ref-tables.js +188 -0
- package/lib/archive/database.d.ts +92 -345
- package/lib/archive/database.js +168 -1942
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.d.ts +27 -0
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +34 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.d.ts +17 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.js +26 -0
- package/lib/archive/db-ops/_shared/decode-json-ref.d.ts +17 -0
- package/lib/archive/db-ops/_shared/decode-json-ref.js +31 -0
- package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.d.ts +20 -0
- package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.js +53 -0
- package/lib/archive/db-ops/_shared/resolve-content-item-id.d.ts +61 -0
- package/lib/archive/db-ops/_shared/resolve-content-item-id.js +111 -0
- package/lib/archive/db-ops/_shared/resolve-url-or-blob.d.ts +23 -0
- package/lib/archive/db-ops/_shared/resolve-url-or-blob.js +29 -0
- package/lib/archive/db-ops/_shared/retry-setting.d.ts +16 -0
- package/lib/archive/db-ops/_shared/retry-setting.js +18 -0
- package/lib/archive/db-ops/_shared/safe-parse-json.d.ts +11 -0
- package/lib/archive/db-ops/_shared/safe-parse-json.js +18 -0
- package/lib/archive/db-ops/_shared/types.d.ts +53 -0
- package/lib/archive/db-ops/_shared/types.js +1 -0
- package/lib/archive/db-ops/_shared/upsert-blob-ref.d.ts +25 -0
- package/lib/archive/db-ops/_shared/upsert-blob-ref.js +48 -0
- package/lib/archive/db-ops/_shared/upsert-content-type-ref.d.ts +30 -0
- package/lib/archive/db-ops/_shared/upsert-content-type-ref.js +45 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.d.ts +22 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.js +41 -0
- package/lib/archive/db-ops/_shared/upsert-response-headers.d.ts +35 -0
- package/lib/archive/db-ops/_shared/upsert-response-headers.js +49 -0
- package/lib/archive/db-ops/_shared/upsert-url-ref.d.ts +39 -0
- package/lib/archive/db-ops/_shared/upsert-url-ref.js +62 -0
- package/lib/archive/db-ops/analysis/replace-analysis-violations.d.ts +28 -0
- package/lib/archive/db-ops/analysis/replace-analysis-violations.js +152 -0
- package/lib/archive/db-ops/anchors/get-anchors-on-page.d.ts +10 -0
- package/lib/archive/db-ops/anchors/get-anchors-on-page.js +21 -0
- package/lib/archive/db-ops/config/get-base-url.d.ts +8 -0
- package/lib/archive/db-ops/config/get-base-url.js +14 -0
- package/lib/archive/db-ops/config/get-config.d.ts +10 -0
- package/lib/archive/db-ops/config/get-config.js +27 -0
- package/lib/archive/db-ops/config/get-name.d.ts +8 -0
- package/lib/archive/db-ops/config/get-name.js +14 -0
- package/lib/archive/db-ops/config/info-column-allowlist.d.ts +7 -0
- package/lib/archive/db-ops/config/info-column-allowlist.js +26 -0
- package/lib/archive/db-ops/config/info-json-columns.d.ts +5 -0
- package/lib/archive/db-ops/config/info-json-columns.js +10 -0
- package/lib/archive/db-ops/config/set-config.d.ts +12 -0
- package/lib/archive/db-ops/config/set-config.js +21 -0
- package/lib/archive/db-ops/config/update-config.d.ts +17 -0
- package/lib/archive/db-ops/config/update-config.js +36 -0
- package/lib/archive/db-ops/errors/insert-crawl-error.d.ts +15 -0
- package/lib/archive/db-ops/errors/insert-crawl-error.js +21 -0
- package/lib/archive/db-ops/errors/insert-page-error.d.ts +21 -0
- package/lib/archive/db-ops/errors/insert-page-error.js +28 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +22 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +141 -0
- package/lib/archive/db-ops/html/get-html-of-page-by-id.d.ts +18 -0
- package/lib/archive/db-ops/html/get-html-of-page-by-id.js +29 -0
- package/lib/archive/db-ops/inventory/record-inventory-run.d.ts +21 -0
- package/lib/archive/db-ops/inventory/record-inventory-run.js +38 -0
- package/lib/archive/db-ops/lifecycle/checkpoint.d.ts +8 -0
- package/lib/archive/db-ops/lifecycle/checkpoint.js +9 -0
- package/lib/archive/db-ops/lifecycle/destroy.d.ts +6 -0
- package/lib/archive/db-ops/lifecycle/destroy.js +7 -0
- package/lib/archive/db-ops/lifecycle/init.d.ts +22 -0
- package/lib/archive/db-ops/lifecycle/init.js +42 -0
- package/lib/archive/db-ops/meta/get-jsonld-of-page.d.ts +13 -0
- package/lib/archive/db-ops/meta/get-jsonld-of-page.js +27 -0
- package/lib/archive/db-ops/meta/get-tags-of-page.d.ts +12 -0
- package/lib/archive/db-ops/meta/get-tags-of-page.js +28 -0
- package/lib/archive/db-ops/pages/order/set-url-order.d.ts +8 -0
- package/lib/archive/db-ops/pages/order/set-url-order.js +32 -0
- package/lib/archive/db-ops/pages/read/build-page-query.d.ts +18 -0
- package/lib/archive/db-ops/pages/read/build-page-query.js +40 -0
- package/lib/archive/db-ops/pages/read/get-crawling-state.d.ts +70 -0
- package/lib/archive/db-ops/pages/read/get-crawling-state.js +98 -0
- package/lib/archive/db-ops/pages/read/get-existing-page-urls.d.ts +15 -0
- package/lib/archive/db-ops/pages/read/get-existing-page-urls.js +30 -0
- package/lib/archive/db-ops/pages/read/get-page-count.d.ts +12 -0
- package/lib/archive/db-ops/pages/read/get-page-count.js +21 -0
- package/lib/archive/db-ops/pages/read/get-page-source-by-url.d.ts +24 -0
- package/lib/archive/db-ops/pages/read/get-page-source-by-url.js +28 -0
- package/lib/archive/db-ops/pages/read/get-pages-with-rels.d.ts +38 -0
- package/lib/archive/db-ops/pages/read/get-pages-with-rels.js +107 -0
- package/lib/archive/db-ops/pages/read/get-pages.d.ts +11 -0
- package/lib/archive/db-ops/pages/read/get-pages.js +51 -0
- package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.d.ts +18 -0
- package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.js +25 -0
- package/lib/archive/db-ops/pages/read/reconstruct-page-rows.d.ts +31 -0
- package/lib/archive/db-ops/pages/read/reconstruct-page-rows.js +32 -0
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.d.ts +24 -0
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +93 -0
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +47 -0
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +124 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-seeds.d.ts +37 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-seeds.js +72 -0
- package/lib/archive/db-ops/pages/write/insert-jsonld.d.ts +17 -0
- package/lib/archive/db-ops/pages/write/insert-jsonld.js +49 -0
- package/lib/archive/db-ops/pages/write/insert-page.d.ts +36 -0
- package/lib/archive/db-ops/pages/write/insert-page.js +208 -0
- package/lib/archive/db-ops/pages/write/insert-tags.d.ts +16 -0
- package/lib/archive/db-ops/pages/write/insert-tags.js +34 -0
- package/lib/archive/db-ops/pages/write/link-redirect-sources.d.ts +36 -0
- package/lib/archive/db-ops/pages/write/link-redirect-sources.js +93 -0
- package/lib/archive/db-ops/pages/write/record-redirect.d.ts +35 -0
- package/lib/archive/db-ops/pages/write/record-redirect.js +100 -0
- package/lib/archive/db-ops/pages/write/set-skipped-page.d.ts +13 -0
- package/lib/archive/db-ops/pages/write/set-skipped-page.js +22 -0
- package/lib/archive/db-ops/pages/write/update-page.d.ts +29 -0
- package/lib/archive/db-ops/pages/write/update-page.js +334 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.d.ts +19 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.js +41 -0
- package/lib/archive/db-ops/referrers/get-redirects-for-pages.d.ts +9 -0
- package/lib/archive/db-ops/referrers/get-redirects-for-pages.js +15 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-page.d.ts +17 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-page.js +32 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-resource.d.ts +8 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-resource.js +15 -0
- package/lib/archive/db-ops/resources/build-resource-query.d.ts +25 -0
- package/lib/archive/db-ops/resources/build-resource-query.js +29 -0
- package/lib/archive/db-ops/resources/get-existing-resource-urls.d.ts +9 -0
- package/lib/archive/db-ops/resources/get-existing-resource-urls.js +24 -0
- package/lib/archive/db-ops/resources/get-resource-by-url.d.ts +13 -0
- package/lib/archive/db-ops/resources/get-resource-by-url.js +22 -0
- package/lib/archive/db-ops/resources/get-resource-url-list.d.ts +9 -0
- package/lib/archive/db-ops/resources/get-resource-url-list.js +13 -0
- package/lib/archive/db-ops/resources/get-resources.d.ts +8 -0
- package/lib/archive/db-ops/resources/get-resources.js +11 -0
- package/lib/archive/db-ops/resources/insert-inventory-resources.d.ts +24 -0
- package/lib/archive/db-ops/resources/insert-inventory-resources.js +64 -0
- package/lib/archive/db-ops/resources/insert-resource-referrers.d.ts +15 -0
- package/lib/archive/db-ops/resources/insert-resource-referrers.js +54 -0
- package/lib/archive/db-ops/resources/insert-resource.d.ts +34 -0
- package/lib/archive/db-ops/resources/insert-resource.js +73 -0
- package/lib/archive/db-ops/resources/reconstruct-resource-rows.d.ts +26 -0
- package/lib/archive/db-ops/resources/reconstruct-resource-rows.js +30 -0
- package/lib/archive/decode-html-blob.d.ts +18 -0
- package/lib/archive/decode-html-blob.js +31 -0
- package/lib/archive/derive-lineage-from-parent.d.ts +1 -1
- package/lib/archive/derive-lineage-from-parent.js +1 -1
- package/lib/archive/drop-legacy-tables.d.ts +45 -0
- package/lib/archive/drop-legacy-tables.js +56 -0
- package/lib/archive/filesystem/rename.js +1 -1
- package/lib/archive/get-failed-page-messages.d.ts +5 -4
- package/lib/archive/get-failed-page-messages.js +5 -4
- package/lib/archive/init-schema.d.ts +35 -39
- package/lib/archive/init-schema.js +99 -460
- package/lib/archive/limited-page-ids.d.ts +2 -1
- package/lib/archive/limited-page-ids.js +5 -4
- package/lib/archive/meta/assert-compatible-version.d.ts +24 -3
- package/lib/archive/meta/assert-compatible-version.js +24 -3
- package/lib/archive/meta/types.d.ts +87 -1
- package/lib/archive/meta/types.js +34 -2
- package/lib/archive/migrate-entity-tables.d.ts +45 -0
- package/lib/archive/migrate-entity-tables.js +56 -0
- package/lib/archive/migrate-ref-tables.d.ts +25 -0
- package/lib/archive/migrate-ref-tables.js +38 -0
- package/lib/archive/page-meta-column-maps.d.ts +32 -0
- package/lib/archive/page-meta-column-maps.js +43 -0
- package/lib/archive/page.d.ts +6 -6
- package/lib/archive/page.js +5 -5
- package/lib/archive/peek-archive-lock.d.ts +2 -2
- package/lib/archive/peek-archive-lock.js +2 -2
- package/lib/archive/populate-entity-tables/collapse-anchor-rows.d.ts +41 -0
- package/lib/archive/populate-entity-tables/collapse-anchor-rows.js +87 -0
- package/lib/archive/populate-entity-tables/derive-dom-path.d.ts +35 -0
- package/lib/archive/populate-entity-tables/derive-dom-path.js +72 -0
- package/lib/archive/populate-entity-tables/is-blob-ref-value.d.ts +16 -0
- package/lib/archive/populate-entity-tables/is-blob-ref-value.js +19 -0
- package/lib/archive/populate-entity-tables/match-images-to-dom-paths.d.ts +66 -0
- package/lib/archive/populate-entity-tables/match-images-to-dom-paths.js +96 -0
- package/lib/archive/populate-entity-tables/populate-anchor-edges.d.ts +33 -0
- package/lib/archive/populate-entity-tables/populate-anchor-edges.js +153 -0
- package/lib/archive/populate-entity-tables/populate-content-items.d.ts +40 -0
- package/lib/archive/populate-entity-tables/populate-content-items.js +141 -0
- package/lib/archive/populate-entity-tables/populate-entities.d.ts +81 -0
- package/lib/archive/populate-entity-tables/populate-entities.js +111 -0
- package/lib/archive/populate-entity-tables/populate-image-items.d.ts +91 -0
- package/lib/archive/populate-entity-tables/populate-image-items.js +223 -0
- package/lib/archive/populate-entity-tables/populate-page-meta.d.ts +33 -0
- package/lib/archive/populate-entity-tables/populate-page-meta.js +267 -0
- package/lib/archive/populate-entity-tables/populate-resource-items.d.ts +22 -0
- package/lib/archive/populate-entity-tables/populate-resource-items.js +114 -0
- package/lib/archive/populate-entity-tables/populate-resource-ref-edges.d.ts +31 -0
- package/lib/archive/populate-entity-tables/populate-resource-ref-edges.js +33 -0
- package/lib/archive/populate-entity-tables/resolve-blob-refs.d.ts +31 -0
- package/lib/archive/populate-entity-tables/resolve-blob-refs.js +100 -0
- package/lib/archive/populate-entity-tables/resolve-content-type-refs.d.ts +22 -0
- package/lib/archive/populate-entity-tables/resolve-content-type-refs.js +27 -0
- package/lib/archive/populate-entity-tables/resolve-header-sets.d.ts +49 -0
- package/lib/archive/populate-entity-tables/resolve-header-sets.js +122 -0
- package/lib/archive/populate-entity-tables/resolve-json-refs.d.ts +25 -0
- package/lib/archive/populate-entity-tables/resolve-json-refs.js +67 -0
- package/lib/archive/populate-entity-tables/resolve-text-refs.d.ts +30 -0
- package/lib/archive/populate-entity-tables/resolve-text-refs.js +61 -0
- package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.d.ts +21 -0
- package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.js +27 -0
- package/lib/archive/populate-entity-tables/resolve-url-refs.d.ts +33 -0
- package/lib/archive/populate-entity-tables/resolve-url-refs.js +60 -0
- package/lib/archive/populate-entity-tables/test-utils/count-rows.d.ts +17 -0
- package/lib/archive/populate-entity-tables/test-utils/count-rows.js +20 -0
- package/lib/archive/populate-entity-tables/test-utils/seed-content-items.d.ts +25 -0
- package/lib/archive/populate-entity-tables/test-utils/seed-content-items.js +42 -0
- package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.d.ts +23 -0
- package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.js +178 -0
- package/lib/archive/populate-entity-tables/types.d.ts +157 -0
- package/lib/archive/populate-entity-tables/types.js +12 -0
- package/lib/archive/populate-entity-tables/upsert-text-refs.d.ts +38 -0
- package/lib/archive/populate-entity-tables/upsert-text-refs.js +78 -0
- package/lib/archive/populate-ref-tables/classify-content-type.d.ts +16 -0
- package/lib/archive/populate-ref-tables/classify-content-type.js +52 -0
- package/lib/archive/populate-ref-tables/compute-content-hash.d.ts +22 -0
- package/lib/archive/populate-ref-tables/compute-content-hash.js +26 -0
- package/lib/archive/populate-ref-tables/compute-header-flags.d.ts +16 -0
- package/lib/archive/populate-ref-tables/compute-header-flags.js +70 -0
- package/lib/archive/populate-ref-tables/content-type-rules.d.ts +38 -0
- package/lib/archive/populate-ref-tables/content-type-rules.js +133 -0
- package/lib/archive/populate-ref-tables/create-header-table-caches.d.ts +25 -0
- package/lib/archive/populate-ref-tables/create-header-table-caches.js +49 -0
- package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.d.ts +15 -0
- package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.js +15 -0
- package/lib/archive/populate-ref-tables/decode-data-uri.d.ts +21 -0
- package/lib/archive/populate-ref-tables/decode-data-uri.js +126 -0
- package/lib/archive/populate-ref-tables/decompose-header-set.d.ts +29 -0
- package/lib/archive/populate-ref-tables/decompose-header-set.js +157 -0
- package/lib/archive/populate-ref-tables/decompose-url.d.ts +25 -0
- package/lib/archive/populate-ref-tables/decompose-url.js +70 -0
- package/lib/archive/populate-ref-tables/header-stability.d.ts +19 -0
- package/lib/archive/populate-ref-tables/header-stability.js +22 -0
- package/lib/archive/populate-ref-tables/header-value-cache-key.d.ts +17 -0
- package/lib/archive/populate-ref-tables/header-value-cache-key.js +19 -0
- package/lib/archive/populate-ref-tables/normalize-mime.d.ts +24 -0
- package/lib/archive/populate-ref-tables/normalize-mime.js +36 -0
- package/lib/archive/populate-ref-tables/populate-blob-refs.d.ts +38 -0
- package/lib/archive/populate-ref-tables/populate-blob-refs.js +134 -0
- package/lib/archive/populate-ref-tables/populate-content-type-refs.d.ts +27 -0
- package/lib/archive/populate-ref-tables/populate-content-type-refs.js +70 -0
- package/lib/archive/populate-ref-tables/populate-header-tables.d.ts +35 -0
- package/lib/archive/populate-ref-tables/populate-header-tables.js +80 -0
- package/lib/archive/populate-ref-tables/populate-json-refs.d.ts +29 -0
- package/lib/archive/populate-ref-tables/populate-json-refs.js +101 -0
- package/lib/archive/populate-ref-tables/populate-refs.d.ts +51 -0
- package/lib/archive/populate-ref-tables/populate-refs.js +62 -0
- package/lib/archive/populate-ref-tables/populate-text-refs.d.ts +32 -0
- package/lib/archive/populate-ref-tables/populate-text-refs.js +133 -0
- package/lib/archive/populate-ref-tables/populate-url-refs.d.ts +28 -0
- package/lib/archive/populate-ref-tables/populate-url-refs.js +148 -0
- package/lib/archive/populate-ref-tables/test-utils/count-rows.d.ts +15 -0
- package/lib/archive/populate-ref-tables/test-utils/count-rows.js +17 -0
- package/lib/archive/populate-ref-tables/types.d.ts +197 -0
- package/lib/archive/populate-ref-tables/types.js +7 -0
- package/lib/archive/populate-ref-tables/upsert-one-header-set.d.ts +34 -0
- package/lib/archive/populate-ref-tables/upsert-one-header-set.js +208 -0
- package/lib/archive/populate-ref-tables/volatile-header-names.d.ts +20 -0
- package/lib/archive/populate-ref-tables/volatile-header-names.js +33 -0
- package/lib/archive/redirect-table.d.ts +4 -2
- package/lib/archive/redirect-table.js +15 -10
- package/lib/archive/resolve-redirect-chain.d.ts +3 -3
- package/lib/archive/resolve-redirect-chain.js +2 -2
- package/lib/archive/resource.d.ts +1 -1
- package/lib/archive/retarget-legacy-fk-tables.d.ts +47 -0
- package/lib/archive/retarget-legacy-fk-tables.js +107 -0
- package/lib/archive/test-utils/fk-parent-tables.d.ts +15 -0
- package/lib/archive/test-utils/fk-parent-tables.js +19 -0
- package/lib/archive/test-utils/seed-content-item.d.ts +35 -0
- package/lib/archive/test-utils/seed-content-item.js +42 -0
- package/lib/archive/test-utils/setup-legacy-fk-db.d.ts +33 -0
- package/lib/archive/test-utils/setup-legacy-fk-db.js +270 -0
- package/lib/archive/types.d.ts +127 -24
- package/lib/archive/verify-migration/capture-rejection.d.ts +24 -0
- package/lib/archive/verify-migration/capture-rejection.js +31 -0
- package/lib/archive/verify-migration/check-anchor-edges-count.d.ts +34 -0
- package/lib/archive/verify-migration/check-anchor-edges-count.js +72 -0
- package/lib/archive/verify-migration/check-anchor-edges-sum.d.ts +13 -0
- package/lib/archive/verify-migration/check-anchor-edges-sum.js +27 -0
- package/lib/archive/verify-migration/check-content-items-count.d.ts +16 -0
- package/lib/archive/verify-migration/check-content-items-count.js +30 -0
- package/lib/archive/verify-migration/check-content-type-preservation.d.ts +22 -0
- package/lib/archive/verify-migration/check-content-type-preservation.js +40 -0
- package/lib/archive/verify-migration/check-foreign-key-integrity.d.ts +31 -0
- package/lib/archive/verify-migration/check-foreign-key-integrity.js +47 -0
- package/lib/archive/verify-migration/check-image-items-count.d.ts +12 -0
- package/lib/archive/verify-migration/check-image-items-count.js +26 -0
- package/lib/archive/verify-migration/check-page-meta-count.d.ts +15 -0
- package/lib/archive/verify-migration/check-page-meta-count.js +31 -0
- package/lib/archive/verify-migration/check-reader-parity.d.ts +23 -0
- package/lib/archive/verify-migration/check-reader-parity.js +211 -0
- package/lib/archive/verify-migration/check-resource-items-count.d.ts +17 -0
- package/lib/archive/verify-migration/check-resource-items-count.js +33 -0
- package/lib/archive/verify-migration/check-url-round-trip.d.ts +43 -0
- package/lib/archive/verify-migration/check-url-round-trip.js +112 -0
- package/lib/archive/verify-migration/types.d.ts +70 -0
- package/lib/archive/verify-migration/types.js +63 -0
- package/lib/archive/verify-migration/verify-migration.d.ts +41 -0
- package/lib/archive/verify-migration/verify-migration.js +120 -0
- package/lib/crawler/build-redirect-event.d.ts +1 -1
- package/lib/crawler/build-redirect-event.js +1 -1
- package/lib/crawler/capture-image-dom-paths.d.ts +33 -0
- package/lib/crawler/capture-image-dom-paths.js +39 -0
- package/lib/crawler/clear-dns-burned-host-cache.d.ts +1 -1
- package/lib/crawler/clear-dns-burned-host-cache.js +1 -1
- package/lib/crawler/collect-image-dom-paths.d.ts +23 -0
- package/lib/crawler/collect-image-dom-paths.js +64 -0
- package/lib/crawler/crawler.d.ts +19 -0
- package/lib/crawler/crawler.js +40 -26
- package/lib/crawler/dns-burned-host-cache.d.ts +3 -3
- package/lib/crawler/dns-burned-host-cache.js +3 -3
- package/lib/crawler/dns-burned-host-short-circuit-counter.d.ts +2 -2
- package/lib/crawler/dns-burned-host-short-circuit-counter.js +2 -2
- package/lib/crawler/inject-scope-auth.d.ts +1 -1
- package/lib/crawler/inject-scope-auth.js +1 -1
- package/lib/crawler/normalize-content-type.d.ts +1 -1
- package/lib/crawler/normalize-content-type.js +1 -1
- package/lib/crawler/types.d.ts +3 -3
- package/lib/crawler-orchestrator.d.ts +9 -0
- package/lib/crawler-orchestrator.js +44 -28
- package/lib/crawler.d.ts +12 -0
- package/lib/crawler.js +21 -0
- package/lib/permanent-error-kinds.d.ts +1 -1
- package/lib/permanent-error-kinds.js +1 -1
- package/lib/types.d.ts +1 -1
- package/lib/utils/compute-file-sha256.d.ts +5 -4
- package/lib/utils/compute-file-sha256.js +5 -4
- package/lib/utils/error/emit-error-with-retry.d.ts +1 -1
- package/lib/utils/error/emit-error-with-retry.js +1 -1
- package/package.json +10 -10
- package/lib/archive/migrate-crawl-errors.d.ts +0 -20
- package/lib/archive/migrate-crawl-errors.js +0 -38
- package/lib/archive/migrate-html-blob-tables.d.ts +0 -24
- package/lib/archive/migrate-html-blob-tables.js +0 -53
- package/lib/archive/migrate-inventory-runs.d.ts +0 -29
- package/lib/archive/migrate-inventory-runs.js +0 -52
- package/lib/archive/migrate-page-errors.d.ts +0 -16
- package/lib/archive/migrate-page-errors.js +0 -35
- package/lib/archive/migrate-pages-resources-source.d.ts +0 -16
- package/lib/archive/migrate-pages-resources-source.js +0 -46
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { MigrationVerificationError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #3: the `anchor_edges` dedup does not
|
|
4
|
+
* eliminate rows and does not create phantom rows.
|
|
5
|
+
*
|
|
6
|
+
* `populateAnchorEdges` collapses N `anchors` rows per `(pageId, hrefId)`
|
|
7
|
+
* pair into a single `anchor_edges` row with `count = N`. Two ways it
|
|
8
|
+
* could go wrong:
|
|
9
|
+
*
|
|
10
|
+
* - `count(anchor_edges) == 0 && count(anchors) > 0` — dedup annihilated
|
|
11
|
+
* the graph. A populate bug or a group-by mis-key would trip this.
|
|
12
|
+
* - `count(anchor_edges) > count(anchors)` — dedup produced more rows than
|
|
13
|
+
* inputs. Impossible under a correct `GROUP BY` but a phantom-insert bug
|
|
14
|
+
* in the populate loop would trip this.
|
|
15
|
+
*
|
|
16
|
+
* `count(anchor_edges) == count(anchors)` is **not** a failure. Issue #194
|
|
17
|
+
* spelled the upper bound as `< count(anchors)` (strict), but a legitimate
|
|
18
|
+
* small crawl where every `(pageId, hrefId)` pair is unique produces one
|
|
19
|
+
* edge per anchor (count=1) and satisfies the identity naturally. Enforcing
|
|
20
|
+
* strict `<` would refuse to migrate such archives even though every other
|
|
21
|
+
* invariant holds; the true invariant is "no rows added or dropped by
|
|
22
|
+
* dedup" (`anchor_edges <= anchors`), so the code enforces the weaker,
|
|
23
|
+
* correct clause. Check #4 (`SUM(count) == count(anchors)`) already
|
|
24
|
+
* establishes that no rows were silently discarded when the counts happen
|
|
25
|
+
* to match.
|
|
26
|
+
*
|
|
27
|
+
* A trivial archive with zero `anchors` legitimately produces zero
|
|
28
|
+
* `anchor_edges`, so both counts being zero passes; a non-zero `anchor_edges`
|
|
29
|
+
* with a zero `anchors` still throws — that would mean rows appeared from
|
|
30
|
+
* nowhere.
|
|
31
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
32
|
+
* @throws {MigrationVerificationError} when the invariant is violated.
|
|
33
|
+
*/
|
|
34
|
+
export async function checkAnchorEdgesCount(trx) {
|
|
35
|
+
const anchorEdgesRows = await trx('anchor_edges').count({ n: '*' });
|
|
36
|
+
const anchorsRows = await trx('anchors').count({ n: '*' });
|
|
37
|
+
const anchorEdgesCount = Number(anchorEdgesRows[0].n);
|
|
38
|
+
const anchorsCount = Number(anchorsRows[0].n);
|
|
39
|
+
if (anchorsCount === 0) {
|
|
40
|
+
if (anchorEdgesCount !== 0) {
|
|
41
|
+
throw new MigrationVerificationError({
|
|
42
|
+
check: '#3 anchor_edges row count',
|
|
43
|
+
context: {
|
|
44
|
+
anchor_edges: anchorEdgesCount,
|
|
45
|
+
anchors: anchorsCount,
|
|
46
|
+
reason: 'anchor_edges must be empty when anchors is empty',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (anchorEdgesCount === 0) {
|
|
53
|
+
throw new MigrationVerificationError({
|
|
54
|
+
check: '#3 anchor_edges row count',
|
|
55
|
+
context: {
|
|
56
|
+
anchor_edges: anchorEdgesCount,
|
|
57
|
+
anchors: anchorsCount,
|
|
58
|
+
reason: 'anchor_edges must be greater than zero when anchors is non-empty',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (anchorEdgesCount > anchorsCount) {
|
|
63
|
+
throw new MigrationVerificationError({
|
|
64
|
+
check: '#3 anchor_edges row count',
|
|
65
|
+
context: {
|
|
66
|
+
anchor_edges: anchorEdgesCount,
|
|
67
|
+
anchors: anchorsCount,
|
|
68
|
+
reason: 'anchor_edges must not exceed anchors (dedup should not create rows)',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #4: the sum of `anchor_edges.count` equals the
|
|
4
|
+
* total number of legacy `anchors` rows.
|
|
5
|
+
*
|
|
6
|
+
* `populateAnchorEdges` collapses N `anchors` rows per `(pageId, hrefId)`
|
|
7
|
+
* pair into a single edge with `count = N`; the invariant re-establishes the identity
|
|
8
|
+
* `sum(count) = number of source rows`. Together with invariant #3 this
|
|
9
|
+
* proves the dedup is exact (no rows lost, no rows double-counted).
|
|
10
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
11
|
+
* @throws {MigrationVerificationError} when the sum diverges from the anchors row count.
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkAnchorEdgesSum(trx: Knex): Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MigrationVerificationError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #4: the sum of `anchor_edges.count` equals the
|
|
4
|
+
* total number of legacy `anchors` rows.
|
|
5
|
+
*
|
|
6
|
+
* `populateAnchorEdges` collapses N `anchors` rows per `(pageId, hrefId)`
|
|
7
|
+
* pair into a single edge with `count = N`; the invariant re-establishes the identity
|
|
8
|
+
* `sum(count) = number of source rows`. Together with invariant #3 this
|
|
9
|
+
* proves the dedup is exact (no rows lost, no rows double-counted).
|
|
10
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
11
|
+
* @throws {MigrationVerificationError} when the sum diverges from the anchors row count.
|
|
12
|
+
*/
|
|
13
|
+
export async function checkAnchorEdgesSum(trx) {
|
|
14
|
+
const sumRows = await trx('anchor_edges').sum({ n: 'count' });
|
|
15
|
+
const anchorsRows = await trx('anchors').count({ n: '*' });
|
|
16
|
+
const anchorEdgesSum = Number(sumRows[0].n ?? 0);
|
|
17
|
+
const anchorsCount = Number(anchorsRows[0].n);
|
|
18
|
+
if (anchorEdgesSum !== anchorsCount) {
|
|
19
|
+
throw new MigrationVerificationError({
|
|
20
|
+
check: '#4 anchor_edges count sum',
|
|
21
|
+
context: {
|
|
22
|
+
sum_of_anchor_edges_count: anchorEdgesSum,
|
|
23
|
+
anchors: anchorsCount,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #1: every legacy `pages` row is mirrored by
|
|
4
|
+
* one row in `content_items`.
|
|
5
|
+
*
|
|
6
|
+
* `populateContentItems` fills `content_items` with `id = pages.id` for every page
|
|
7
|
+
* (see `populate-content-items.ts`); the invariant is broken only if the
|
|
8
|
+
* populate loop skipped or double-inserted rows. `INSERT OR IGNORE` on the
|
|
9
|
+
* PK collapses duplicates, so a mismatch always means "populate wrote
|
|
10
|
+
* fewer rows than expected" (extra rows would be impossible via the natural
|
|
11
|
+
* PK). We still emit both counts in the error context so operators can see
|
|
12
|
+
* the direction of the discrepancy at a glance.
|
|
13
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
14
|
+
* @throws {MigrationVerificationError} when the row counts diverge.
|
|
15
|
+
*/
|
|
16
|
+
export declare function checkContentItemsCount(trx: Knex): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MigrationVerificationError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #1: every legacy `pages` row is mirrored by
|
|
4
|
+
* one row in `content_items`.
|
|
5
|
+
*
|
|
6
|
+
* `populateContentItems` fills `content_items` with `id = pages.id` for every page
|
|
7
|
+
* (see `populate-content-items.ts`); the invariant is broken only if the
|
|
8
|
+
* populate loop skipped or double-inserted rows. `INSERT OR IGNORE` on the
|
|
9
|
+
* PK collapses duplicates, so a mismatch always means "populate wrote
|
|
10
|
+
* fewer rows than expected" (extra rows would be impossible via the natural
|
|
11
|
+
* PK). We still emit both counts in the error context so operators can see
|
|
12
|
+
* the direction of the discrepancy at a glance.
|
|
13
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
14
|
+
* @throws {MigrationVerificationError} when the row counts diverge.
|
|
15
|
+
*/
|
|
16
|
+
export async function checkContentItemsCount(trx) {
|
|
17
|
+
const contentItemsRows = await trx('content_items').count({ n: '*' });
|
|
18
|
+
const pagesRows = await trx('pages').count({ n: '*' });
|
|
19
|
+
const contentItemsCount = Number(contentItemsRows[0].n);
|
|
20
|
+
const pagesCount = Number(pagesRows[0].n);
|
|
21
|
+
if (contentItemsCount !== pagesCount) {
|
|
22
|
+
throw new MigrationVerificationError({
|
|
23
|
+
check: '#1 content_items row count',
|
|
24
|
+
context: {
|
|
25
|
+
content_items: contentItemsCount,
|
|
26
|
+
pages: pagesCount,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #7: no page loses its content-type across the
|
|
4
|
+
* `content_items` populate.
|
|
5
|
+
*
|
|
6
|
+
* `populate-content-items.ts` sets `content_type_id = null` whenever the
|
|
7
|
+
* legacy `pages.contentType` is null OR the empty string (the empty string
|
|
8
|
+
* meaning "no content-type recorded"). Any *other* case where `contentType`
|
|
9
|
+
* carries a real value but `content_items.content_type_id` ended up null
|
|
10
|
+
* would indicate a resolver bug — the current populate throws before insert
|
|
11
|
+
* in that path, so this check is a defence in depth against a future
|
|
12
|
+
* regression.
|
|
13
|
+
*
|
|
14
|
+
* The check runs a single `SELECT id, contentType LIMIT 1` filtered to
|
|
15
|
+
* offending rows; if any row comes back the invariant is violated and the
|
|
16
|
+
* error context surfaces that one sample for the operator. A separate
|
|
17
|
+
* `COUNT(*)` would only add cost without adding information — the presence
|
|
18
|
+
* of a single row already means the invariant fails.
|
|
19
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
20
|
+
* @throws {MigrationVerificationError} when any content-type is silently dropped.
|
|
21
|
+
*/
|
|
22
|
+
export declare function checkContentTypePreservation(trx: Knex): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { MigrationVerificationError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #7: no page loses its content-type across the
|
|
4
|
+
* `content_items` populate.
|
|
5
|
+
*
|
|
6
|
+
* `populate-content-items.ts` sets `content_type_id = null` whenever the
|
|
7
|
+
* legacy `pages.contentType` is null OR the empty string (the empty string
|
|
8
|
+
* meaning "no content-type recorded"). Any *other* case where `contentType`
|
|
9
|
+
* carries a real value but `content_items.content_type_id` ended up null
|
|
10
|
+
* would indicate a resolver bug — the current populate throws before insert
|
|
11
|
+
* in that path, so this check is a defence in depth against a future
|
|
12
|
+
* regression.
|
|
13
|
+
*
|
|
14
|
+
* The check runs a single `SELECT id, contentType LIMIT 1` filtered to
|
|
15
|
+
* offending rows; if any row comes back the invariant is violated and the
|
|
16
|
+
* error context surfaces that one sample for the operator. A separate
|
|
17
|
+
* `COUNT(*)` would only add cost without adding information — the presence
|
|
18
|
+
* of a single row already means the invariant fails.
|
|
19
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
20
|
+
* @throws {MigrationVerificationError} when any content-type is silently dropped.
|
|
21
|
+
*/
|
|
22
|
+
export async function checkContentTypePreservation(trx) {
|
|
23
|
+
const sample = await trx('content_items as ci')
|
|
24
|
+
.join('pages as p', 'ci.id', 'p.id')
|
|
25
|
+
.whereNull('ci.content_type_id')
|
|
26
|
+
.whereNotNull('p.contentType')
|
|
27
|
+
.andWhere('p.contentType', '<>', '')
|
|
28
|
+
.select('ci.id as id', 'p.contentType as contentType')
|
|
29
|
+
.limit(1);
|
|
30
|
+
if (sample.length > 0) {
|
|
31
|
+
const offending = sample[0];
|
|
32
|
+
throw new MigrationVerificationError({
|
|
33
|
+
check: '#7 content_type preservation',
|
|
34
|
+
context: {
|
|
35
|
+
sample_page_id: offending.id,
|
|
36
|
+
sample_content_type: offending.contentType,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Asserts that `PRAGMA foreign_key_check` reports zero violations across
|
|
4
|
+
* the whole archive.
|
|
5
|
+
*
|
|
6
|
+
* Runs after `retargetLegacyFkTables` + `dropLegacyTables`, i.e. against
|
|
7
|
+
* the final schema — every FK declaration at that point targets the
|
|
8
|
+
* entity / ref tables, so a clean result proves the rebuilt adjunct
|
|
9
|
+
* tables' rows all resolve against `content_items` and no dangling
|
|
10
|
+
* reference survived the legacy-table removal. Running it earlier would
|
|
11
|
+
* also scan the legacy tables (`anchors` alone can hold millions of
|
|
12
|
+
* rows) for declarations that are about to be dropped anyway.
|
|
13
|
+
*
|
|
14
|
+
* `PRAGMA foreign_key_check` inspects data regardless of the
|
|
15
|
+
* `foreign_keys` enforcement setting, so this check stays valid even
|
|
16
|
+
* though the preceding drop phase runs with enforcement OFF.
|
|
17
|
+
*
|
|
18
|
+
* Not part of {@link verifyMigration}'s chain: that orchestrator runs
|
|
19
|
+
* inside the entity-populate transaction while the legacy tables still
|
|
20
|
+
* exist, a different point in the migration lifecycle than this
|
|
21
|
+
* final-schema assertion.
|
|
22
|
+
* @param trx - Knex instance or transaction on the migrated archive.
|
|
23
|
+
* @throws {MigrationVerificationError} when any row violates an FK
|
|
24
|
+
* declaration; the context carries the violation count and the first
|
|
25
|
+
* offending child/parent table pair.
|
|
26
|
+
* @example
|
|
27
|
+
* await retargetLegacyFkTables(trx);
|
|
28
|
+
* await dropLegacyTables(trx);
|
|
29
|
+
* await checkForeignKeyIntegrity(trx); // throws unless zero violations
|
|
30
|
+
*/
|
|
31
|
+
export declare function checkForeignKeyIntegrity(trx: Knex): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { MigrationVerificationError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Asserts that `PRAGMA foreign_key_check` reports zero violations across
|
|
4
|
+
* the whole archive.
|
|
5
|
+
*
|
|
6
|
+
* Runs after `retargetLegacyFkTables` + `dropLegacyTables`, i.e. against
|
|
7
|
+
* the final schema — every FK declaration at that point targets the
|
|
8
|
+
* entity / ref tables, so a clean result proves the rebuilt adjunct
|
|
9
|
+
* tables' rows all resolve against `content_items` and no dangling
|
|
10
|
+
* reference survived the legacy-table removal. Running it earlier would
|
|
11
|
+
* also scan the legacy tables (`anchors` alone can hold millions of
|
|
12
|
+
* rows) for declarations that are about to be dropped anyway.
|
|
13
|
+
*
|
|
14
|
+
* `PRAGMA foreign_key_check` inspects data regardless of the
|
|
15
|
+
* `foreign_keys` enforcement setting, so this check stays valid even
|
|
16
|
+
* though the preceding drop phase runs with enforcement OFF.
|
|
17
|
+
*
|
|
18
|
+
* Not part of {@link verifyMigration}'s chain: that orchestrator runs
|
|
19
|
+
* inside the entity-populate transaction while the legacy tables still
|
|
20
|
+
* exist, a different point in the migration lifecycle than this
|
|
21
|
+
* final-schema assertion.
|
|
22
|
+
* @param trx - Knex instance or transaction on the migrated archive.
|
|
23
|
+
* @throws {MigrationVerificationError} when any row violates an FK
|
|
24
|
+
* declaration; the context carries the violation count and the first
|
|
25
|
+
* offending child/parent table pair.
|
|
26
|
+
* @example
|
|
27
|
+
* await retargetLegacyFkTables(trx);
|
|
28
|
+
* await dropLegacyTables(trx);
|
|
29
|
+
* await checkForeignKeyIntegrity(trx); // throws unless zero violations
|
|
30
|
+
*/
|
|
31
|
+
export async function checkForeignKeyIntegrity(trx) {
|
|
32
|
+
const rows = await trx
|
|
33
|
+
.select('*')
|
|
34
|
+
.from(trx.raw('pragma_foreign_key_check'));
|
|
35
|
+
if (rows.length === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
throw new MigrationVerificationError({
|
|
39
|
+
check: 'foreign_key_check',
|
|
40
|
+
context: {
|
|
41
|
+
violation_count: rows.length,
|
|
42
|
+
first_offending_table: rows[0]?.table ?? '(unknown)',
|
|
43
|
+
first_offending_parent: rows[0]?.parent ?? '(unknown)',
|
|
44
|
+
first_offending_rowid: rows[0]?.rowid ?? null,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #5: every legacy `images` row is mirrored by
|
|
4
|
+
* one row in `image_items`.
|
|
5
|
+
*
|
|
6
|
+
* `populateImageItems` fills `image_items` with the same PK as `images.id` (see
|
|
7
|
+
* `populate-image-items.ts`); the invariant is broken only if the populate
|
|
8
|
+
* loop skipped rows during URL/blob routing or dom-path derivation.
|
|
9
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
10
|
+
* @throws {MigrationVerificationError} when the row counts diverge.
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkImageItemsCount(trx: Knex): Promise<void>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { MigrationVerificationError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #5: every legacy `images` row is mirrored by
|
|
4
|
+
* one row in `image_items`.
|
|
5
|
+
*
|
|
6
|
+
* `populateImageItems` fills `image_items` with the same PK as `images.id` (see
|
|
7
|
+
* `populate-image-items.ts`); the invariant is broken only if the populate
|
|
8
|
+
* loop skipped rows during URL/blob routing or dom-path derivation.
|
|
9
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
10
|
+
* @throws {MigrationVerificationError} when the row counts diverge.
|
|
11
|
+
*/
|
|
12
|
+
export async function checkImageItemsCount(trx) {
|
|
13
|
+
const imageItemsRows = await trx('image_items').count({ n: '*' });
|
|
14
|
+
const imagesRows = await trx('images').count({ n: '*' });
|
|
15
|
+
const imageItemsCount = Number(imageItemsRows[0].n);
|
|
16
|
+
const imagesCount = Number(imagesRows[0].n);
|
|
17
|
+
if (imageItemsCount !== imagesCount) {
|
|
18
|
+
throw new MigrationVerificationError({
|
|
19
|
+
check: '#5 image_items row count',
|
|
20
|
+
context: {
|
|
21
|
+
image_items: imageItemsCount,
|
|
22
|
+
images: imagesCount,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #2: every scraped page has one `page_meta`
|
|
4
|
+
* row.
|
|
5
|
+
*
|
|
6
|
+
* `page_meta` mirrors the meta columns of `pages` for `scraped = 1` rows
|
|
7
|
+
* only (see `populate-page-meta.ts`); un-scraped pages have no meta to
|
|
8
|
+
* carry. A mismatch usually indicates that the populate loop encountered a
|
|
9
|
+
* scraped page whose meta resolution failed silently (e.g. a text ref that
|
|
10
|
+
* did not deduplicate), in which case the archive would be missing meta for
|
|
11
|
+
* an otherwise valid page.
|
|
12
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
13
|
+
* @throws {MigrationVerificationError} when the row counts diverge.
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkPageMetaCount(trx: Knex): Promise<void>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MigrationVerificationError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies 0.13 invariant #2: every scraped page has one `page_meta`
|
|
4
|
+
* row.
|
|
5
|
+
*
|
|
6
|
+
* `page_meta` mirrors the meta columns of `pages` for `scraped = 1` rows
|
|
7
|
+
* only (see `populate-page-meta.ts`); un-scraped pages have no meta to
|
|
8
|
+
* carry. A mismatch usually indicates that the populate loop encountered a
|
|
9
|
+
* scraped page whose meta resolution failed silently (e.g. a text ref that
|
|
10
|
+
* did not deduplicate), in which case the archive would be missing meta for
|
|
11
|
+
* an otherwise valid page.
|
|
12
|
+
* @param trx - Knex instance or transaction connected to the populated archive.
|
|
13
|
+
* @throws {MigrationVerificationError} when the row counts diverge.
|
|
14
|
+
*/
|
|
15
|
+
export async function checkPageMetaCount(trx) {
|
|
16
|
+
const pageMetaRows = await trx('page_meta').count({ n: '*' });
|
|
17
|
+
const scrapedPagesRows = await trx('pages')
|
|
18
|
+
.where('scraped', true)
|
|
19
|
+
.count({ n: '*' });
|
|
20
|
+
const pageMetaCount = Number(pageMetaRows[0].n);
|
|
21
|
+
const scrapedPagesCount = Number(scrapedPagesRows[0].n);
|
|
22
|
+
if (pageMetaCount !== scrapedPagesCount) {
|
|
23
|
+
throw new MigrationVerificationError({
|
|
24
|
+
check: '#2 page_meta row count',
|
|
25
|
+
context: {
|
|
26
|
+
page_meta: pageMetaCount,
|
|
27
|
+
scraped_pages: scrapedPagesCount,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Compares totals reported by 0.13 readers (which query the 0.13
|
|
4
|
+
* entity tables exclusively) against equivalent inline SQL against the
|
|
5
|
+
* still-present legacy write-model tables (`pages` / `anchors` / `images` /
|
|
6
|
+
* `resources` / `resources-referrers`). A mismatch here means either the
|
|
7
|
+
* 0.13 populates lost rows or one of the new reader implementations
|
|
8
|
+
* has a filter/predicate off-by-one relative to its legacy counterpart.
|
|
9
|
+
*
|
|
10
|
+
* This is deliberately lighter than the "byte-identical items array"
|
|
11
|
+
* verification issue #195 acceptance references — running every reader on
|
|
12
|
+
* every archive and diffing full result sets would balloon the migrator's
|
|
13
|
+
* runtime and require importing `@nitpicker/query` back into the crawler
|
|
14
|
+
* package (a dependency inversion). The eight totals below capture every
|
|
15
|
+
* reader whose scope was flagged in issue #195 and catch the classes of
|
|
16
|
+
* regression the 0.13 row-count invariants cannot: predicate drift
|
|
17
|
+
* inside a reader function (e.g. a `WHERE contentType='text/html'` filter
|
|
18
|
+
* silently swapping meaning between `pages.contentType` and
|
|
19
|
+
* `content_type_refs.raw`).
|
|
20
|
+
* @param trx - The open migration transaction.
|
|
21
|
+
* @throws {MigrationVerificationError} If any pair of totals disagrees.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkReaderParity(trx: Knex): Promise<void>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { MigrationVerificationError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compares totals reported by 0.13 readers (which query the 0.13
|
|
4
|
+
* entity tables exclusively) against equivalent inline SQL against the
|
|
5
|
+
* still-present legacy write-model tables (`pages` / `anchors` / `images` /
|
|
6
|
+
* `resources` / `resources-referrers`). A mismatch here means either the
|
|
7
|
+
* 0.13 populates lost rows or one of the new reader implementations
|
|
8
|
+
* has a filter/predicate off-by-one relative to its legacy counterpart.
|
|
9
|
+
*
|
|
10
|
+
* This is deliberately lighter than the "byte-identical items array"
|
|
11
|
+
* verification issue #195 acceptance references — running every reader on
|
|
12
|
+
* every archive and diffing full result sets would balloon the migrator's
|
|
13
|
+
* runtime and require importing `@nitpicker/query` back into the crawler
|
|
14
|
+
* package (a dependency inversion). The eight totals below capture every
|
|
15
|
+
* reader whose scope was flagged in issue #195 and catch the classes of
|
|
16
|
+
* regression the 0.13 row-count invariants cannot: predicate drift
|
|
17
|
+
* inside a reader function (e.g. a `WHERE contentType='text/html'` filter
|
|
18
|
+
* silently swapping meaning between `pages.contentType` and
|
|
19
|
+
* `content_type_refs.raw`).
|
|
20
|
+
* @param trx - The open migration transaction.
|
|
21
|
+
* @throws {MigrationVerificationError} If any pair of totals disagrees.
|
|
22
|
+
*/
|
|
23
|
+
export async function checkReaderParity(trx) {
|
|
24
|
+
// `analysis_violations` postdates issue #116 — an archive from before
|
|
25
|
+
// that feature landed (and that never ran `nitpicker analyze` after
|
|
26
|
+
// upgrading) has no such table at all. Unlike the other legacy
|
|
27
|
+
// tables here (`pages` / `anchors` / `images` / `resources`), which
|
|
28
|
+
// `init-schema.ts` has always created, this one is genuinely absent
|
|
29
|
+
// on some real archives, so the check below is skipped rather than
|
|
30
|
+
// querying a table that may not exist.
|
|
31
|
+
const hasAnalysisViolations = await trx.schema.hasTable('analysis_violations');
|
|
32
|
+
const checks = [
|
|
33
|
+
{
|
|
34
|
+
label: 'listPages default (scraped=1, redirect null, html-or-null)',
|
|
35
|
+
legacy: async () => scalar(trx('pages')
|
|
36
|
+
.where('scraped', 1)
|
|
37
|
+
.whereNull('redirectDestId')
|
|
38
|
+
.where((qb) => {
|
|
39
|
+
qb.where('isSkipped', 0).orWhereNull('isSkipped');
|
|
40
|
+
})
|
|
41
|
+
.where((qb) => {
|
|
42
|
+
qb.whereNull('contentType').orWhere('contentType', 'text/html');
|
|
43
|
+
})
|
|
44
|
+
.count({ count: '*' })),
|
|
45
|
+
current: async () => scalar(trx('content_items as ci')
|
|
46
|
+
.leftJoin('content_type_refs as ctr', 'ctr.id', 'ci.content_type_id')
|
|
47
|
+
.where('ci.scraped', 1)
|
|
48
|
+
.whereNull('ci.redirect_dest_id')
|
|
49
|
+
.where((qb) => {
|
|
50
|
+
qb.where('ci.is_skipped', 0).orWhereNull('ci.is_skipped');
|
|
51
|
+
})
|
|
52
|
+
.where((qb) => {
|
|
53
|
+
qb.whereNull('ctr.raw').orWhere('ctr.raw', 'text/html');
|
|
54
|
+
})
|
|
55
|
+
.count({ count: '*' })),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
label: 'listImages total',
|
|
59
|
+
legacy: async () => scalar(trx('images').count({ count: '*' })),
|
|
60
|
+
current: async () => scalar(trx('image_items').count({ count: '*' })),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
label: 'listResources total',
|
|
64
|
+
legacy: async () => scalar(trx('resources').count({ count: '*' })),
|
|
65
|
+
current: async () => scalar(trx('resource_items').count({ count: '*' })),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
label: 'listLinks broken (status=404 through redirect)',
|
|
69
|
+
legacy: async () => scalar(trx('anchors')
|
|
70
|
+
.join('pages as dest', 'anchors.hrefId', 'dest.id')
|
|
71
|
+
.leftJoin('pages as canonical', 'dest.redirectDestId', 'canonical.id')
|
|
72
|
+
.whereRaw('COALESCE("canonical"."status", "dest"."status") = 404')
|
|
73
|
+
.count({ count: '*' })),
|
|
74
|
+
current: async () => scalar(trx('anchor_edges as ae')
|
|
75
|
+
.join('content_items as dest', 'ae.href_page_id', 'dest.id')
|
|
76
|
+
.leftJoin('content_items as canonical', 'dest.redirect_dest_id', 'canonical.id')
|
|
77
|
+
.whereRaw('COALESCE("canonical"."status", "dest"."status") = 404')
|
|
78
|
+
.sum({ count: 'ae.count' })),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
label: 'checkHeaders scope (internal html scraped, no redirect)',
|
|
82
|
+
legacy: async () => scalar(trx('pages')
|
|
83
|
+
.where({ scraped: 1, isExternal: 0, contentType: 'text/html' })
|
|
84
|
+
.whereNull('redirectDestId')
|
|
85
|
+
.count({ count: '*' })),
|
|
86
|
+
current: async () => scalar(trx('content_items as ci')
|
|
87
|
+
.join('content_type_refs as ctr', 'ctr.id', 'ci.content_type_id')
|
|
88
|
+
.where({
|
|
89
|
+
'ci.scraped': 1,
|
|
90
|
+
'ci.is_external': 0,
|
|
91
|
+
'ctr.raw': 'text/html',
|
|
92
|
+
})
|
|
93
|
+
.whereNull('ci.redirect_dest_id')
|
|
94
|
+
.count({ count: '*' })),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
label: 'findDuplicates(title) group count',
|
|
98
|
+
legacy: async () => scalar(trx
|
|
99
|
+
.count({ count: '*' })
|
|
100
|
+
.from(trx('pages')
|
|
101
|
+
.select('title')
|
|
102
|
+
.where({ scraped: 1, isExternal: 0, contentType: 'text/html' })
|
|
103
|
+
.whereNull('redirectDestId')
|
|
104
|
+
.whereNotNull('title')
|
|
105
|
+
.whereNot('title', '')
|
|
106
|
+
.groupBy('title')
|
|
107
|
+
.having(trx.raw('count(*) > 1'))
|
|
108
|
+
.as('g'))),
|
|
109
|
+
current: async () => scalar(trx.count({ count: '*' }).from(trx('content_items as ci')
|
|
110
|
+
.join('content_type_refs as ctr', 'ctr.id', 'ci.content_type_id')
|
|
111
|
+
.join('page_meta as pm', 'pm.page_id', 'ci.id')
|
|
112
|
+
.join('text_refs as tr', 'tr.id', 'pm.title_text_id')
|
|
113
|
+
.select('pm.title_text_id')
|
|
114
|
+
.where({
|
|
115
|
+
'ci.scraped': 1,
|
|
116
|
+
'ci.is_external': 0,
|
|
117
|
+
'ctr.raw': 'text/html',
|
|
118
|
+
})
|
|
119
|
+
.whereNull('ci.redirect_dest_id')
|
|
120
|
+
.whereNotNull('pm.title_text_id')
|
|
121
|
+
.whereNot('tr.text', '')
|
|
122
|
+
.groupBy('pm.title_text_id')
|
|
123
|
+
.having(trx.raw('count(*) > 1'))
|
|
124
|
+
.as('g'))),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
label: 'findMismatches(canonical) count',
|
|
128
|
+
legacy: async () => scalar(trx('pages')
|
|
129
|
+
.where({ scraped: 1, isExternal: 0, contentType: 'text/html' })
|
|
130
|
+
.whereNull('redirectDestId')
|
|
131
|
+
.whereNotNull('canonical')
|
|
132
|
+
.whereNot('canonical', '')
|
|
133
|
+
.whereRaw('canonical != url')
|
|
134
|
+
.count({ count: '*' })),
|
|
135
|
+
current: async () => scalar(trx('content_items as ci')
|
|
136
|
+
.join('content_type_refs as ctr', 'ctr.id', 'ci.content_type_id')
|
|
137
|
+
.join('page_meta as pm', 'pm.page_id', 'ci.id')
|
|
138
|
+
.join('url_refs as canonical_ur', 'canonical_ur.id', 'pm.canonical_url_id')
|
|
139
|
+
.where({
|
|
140
|
+
'ci.scraped': 1,
|
|
141
|
+
'ci.is_external': 0,
|
|
142
|
+
'ctr.raw': 'text/html',
|
|
143
|
+
})
|
|
144
|
+
.whereNull('ci.redirect_dest_id')
|
|
145
|
+
.whereNotNull('pm.canonical_url_id')
|
|
146
|
+
.whereNot('canonical_ur.url', '')
|
|
147
|
+
.whereRaw('"pm"."canonical_url_id" != "ci"."url_id"')
|
|
148
|
+
.count({ count: '*' })),
|
|
149
|
+
},
|
|
150
|
+
...(hasAnalysisViolations
|
|
151
|
+
? [
|
|
152
|
+
{
|
|
153
|
+
// `getViolations` joins `analysis_violations` → `content_items`
|
|
154
|
+
// → `url_refs` to project the page URL for each violation. The
|
|
155
|
+
// current-path parity check exercises that JOIN chain so a
|
|
156
|
+
// broken FK / missing `content_items` row surfaces here as a
|
|
157
|
+
// row-count discrepancy (the legacy side counts `pages` rows
|
|
158
|
+
// via `analysis_violations.page_id`, so a lost `content_items`
|
|
159
|
+
// row makes the current-side INNER JOIN drop the violation).
|
|
160
|
+
label: 'getViolations page-URL join (analysis_violations → content_items → url_refs)',
|
|
161
|
+
legacy: async () => scalar(trx('analysis_violations as v')
|
|
162
|
+
.join('pages as p', 'p.id', 'v.page_id')
|
|
163
|
+
.count({ count: '*' })),
|
|
164
|
+
current: async () => scalar(trx('analysis_violations as v')
|
|
165
|
+
.join('content_items as p', 'p.id', 'v.page_id')
|
|
166
|
+
.join('url_refs as ur', 'ur.id', 'p.url_id')
|
|
167
|
+
.count({ count: '*' })),
|
|
168
|
+
},
|
|
169
|
+
]
|
|
170
|
+
: []),
|
|
171
|
+
];
|
|
172
|
+
const failures = [];
|
|
173
|
+
// Zero-vs-zero cannot detect a "populate silently produced no rows"
|
|
174
|
+
// bug, so a check whose LEGACY side returns 0 is skipped rather than
|
|
175
|
+
// counted as pass — the parity check exists to catch predicate drift
|
|
176
|
+
// once both sides have rows to compare. `checkContentItemsCount` etc.
|
|
177
|
+
// already guard the "current is empty when legacy is not" direction
|
|
178
|
+
// upstream in {@link verifyMigration}.
|
|
179
|
+
let comparedChecks = 0;
|
|
180
|
+
for (const { label, legacy, current } of checks) {
|
|
181
|
+
const [legacyValue, currentValue] = await Promise.all([legacy(), current()]);
|
|
182
|
+
if (legacyValue === 0 && currentValue === 0) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
comparedChecks++;
|
|
186
|
+
if (legacyValue !== currentValue) {
|
|
187
|
+
failures.push(`${label}: legacy=${legacyValue}, current=${currentValue}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (failures.length > 0) {
|
|
191
|
+
throw new MigrationVerificationError({
|
|
192
|
+
check: '#9 reader parity',
|
|
193
|
+
context: {
|
|
194
|
+
failures: failures.join('; '),
|
|
195
|
+
compared_checks: comparedChecks,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Extracts the single numeric value from a Knex `count()` / `sum()` result.
|
|
202
|
+
* SQLite returns strings for aggregate expressions; we coerce and treat
|
|
203
|
+
* `null` as `0` (an empty aggregate).
|
|
204
|
+
* @param queryBuilder - The pending Knex query that resolves to a single
|
|
205
|
+
* aggregate row.
|
|
206
|
+
* @returns The aggregate value as a JS number.
|
|
207
|
+
*/
|
|
208
|
+
async function scalar(queryBuilder) {
|
|
209
|
+
const rows = (await queryBuilder);
|
|
210
|
+
return Number(rows[0]?.count ?? 0);
|
|
211
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
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 declare function checkResourceItemsCount(trx: Knex): Promise<void>;
|