@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,141 @@
|
|
|
1
|
+
import { createProgressReporter } from '../create-progress-reporter.js';
|
|
2
|
+
import { loadContentTypeRefs } from './resolve-content-type-refs.js';
|
|
3
|
+
import { resolveHeaderSets } from './resolve-header-sets.js';
|
|
4
|
+
import { resolveUrlRefs } from './resolve-url-refs.js';
|
|
5
|
+
/**
|
|
6
|
+
* Rows scanned per keyset-paginated `SELECT` chunk against `pages`. The
|
|
7
|
+
* migration reads every page column that maps to `content_items` in one
|
|
8
|
+
* SELECT per chunk, so scaling this too high would blow up the result-set
|
|
9
|
+
* size for long meta text columns. 500 keeps peak memory bounded at
|
|
10
|
+
* ≈ 30 MB for a chunk of `pages` with populated meta and header sets.
|
|
11
|
+
*/
|
|
12
|
+
const READ_CHUNK_SIZE = 500;
|
|
13
|
+
/**
|
|
14
|
+
* Rows sent per `INSERT INTO content_items ... VALUES (...)` statement.
|
|
15
|
+
* Each row binds 16 params (id + 15 core columns), so 300 rows =
|
|
16
|
+
* 4 800 params — safely under SQLite's default variable limit of 32 766.
|
|
17
|
+
*/
|
|
18
|
+
const INSERT_CHUNK_SIZE = 300;
|
|
19
|
+
/**
|
|
20
|
+
* Populates `content_items` from `pages` (issue #193).
|
|
21
|
+
*
|
|
22
|
+
* Strategy per chunk:
|
|
23
|
+
*
|
|
24
|
+
* 1. **Keyset-paginate** `pages` in id order so partial-failure re-runs
|
|
25
|
+
* resume from the last committed row without reading the full table.
|
|
26
|
+
* Same pattern as {@link ../populate-ref-tables/populate-url-refs.ts}.
|
|
27
|
+
* 2. **Batch-resolve** `url_refs.id`, `content_type_refs.id`, and
|
|
28
|
+
* `header_sets.id` for every distinct value in the chunk — one round
|
|
29
|
+
* trip per ref table instead of N round trips per row.
|
|
30
|
+
* 3. **Bulk INSERT** with explicit `id = pages.id`. Reusing the legacy
|
|
31
|
+
* PK preserves every FK reference in `page_errors` / `page_tags` /
|
|
32
|
+
* `page_jsonld` / `page_html_ref` without a per-row UPDATE.
|
|
33
|
+
*
|
|
34
|
+
* `redirect_dest_id` is copied verbatim from `redirectDestId` — the FK
|
|
35
|
+
* self-reference is `DEFERRABLE INITIALLY DEFERRED` (see
|
|
36
|
+
* {@link ../create-entity-tables.ts}) so a redirect source
|
|
37
|
+
* inserted before its destination is validated only at COMMIT time.
|
|
38
|
+
*
|
|
39
|
+
* `INSERT OR IGNORE` on the natural PK makes the step idempotent — a
|
|
40
|
+
* re-run after a partial failure only inserts the rows that are still
|
|
41
|
+
* missing.
|
|
42
|
+
*
|
|
43
|
+
* `content_type_refs` is preloaded once at the start because its
|
|
44
|
+
* cardinality is small (see {@link ./resolve-content-type-refs.ts}); the
|
|
45
|
+
* URL and header-set resolvers are called per chunk because their
|
|
46
|
+
* per-archive cardinality can be very large (one row per distinct URL /
|
|
47
|
+
* response JSON).
|
|
48
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
49
|
+
* @param onProgress - Optional sink for periodic progress lines (one per
|
|
50
|
+
* ~5% of `pages` scanned); see {@link ../create-progress-reporter.ts}.
|
|
51
|
+
* @example
|
|
52
|
+
* await knex.transaction(async (trx) => {
|
|
53
|
+
* await populateContentItems(trx);
|
|
54
|
+
* });
|
|
55
|
+
*/
|
|
56
|
+
export async function populateContentItems(trx, onProgress) {
|
|
57
|
+
const contentTypeIds = await loadContentTypeRefs(trx);
|
|
58
|
+
const countRows = await trx('pages').count({ n: '*' });
|
|
59
|
+
const total = Number(countRows[0]?.n ?? 0);
|
|
60
|
+
const report = createProgressReporter('content_items (pages)', total, onProgress);
|
|
61
|
+
let processed = 0;
|
|
62
|
+
let cursor = 0;
|
|
63
|
+
while (true) {
|
|
64
|
+
const rows = await trx('pages')
|
|
65
|
+
.select('id', 'url', 'redirectDestId', 'scraped', 'isTarget', 'isExternal', 'status', 'statusText', 'contentType', 'contentLength', 'responseHeaders', 'firstCrawledAt', 'lastCrawledAt', 'order', 'isSkipped', 'skipReason', 'source')
|
|
66
|
+
.where('id', '>', cursor)
|
|
67
|
+
.orderBy('id', 'asc')
|
|
68
|
+
.limit(READ_CHUNK_SIZE);
|
|
69
|
+
if (rows.length === 0) {
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
cursor = rows.at(-1).id;
|
|
73
|
+
processed += rows.length;
|
|
74
|
+
report(processed);
|
|
75
|
+
const urls = new Set();
|
|
76
|
+
const headerJsonStrings = new Set();
|
|
77
|
+
for (const row of rows) {
|
|
78
|
+
urls.add(row.url);
|
|
79
|
+
if (typeof row.responseHeaders === 'string' && row.responseHeaders !== '') {
|
|
80
|
+
headerJsonStrings.add(row.responseHeaders);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const urlIds = await resolveUrlRefs(trx, urls);
|
|
84
|
+
const headerSetIds = await resolveHeaderSets(trx, headerJsonStrings);
|
|
85
|
+
const inserts = rows.map((row) => {
|
|
86
|
+
const urlId = urlIds.get(row.url) ?? null;
|
|
87
|
+
if (urlId === null) {
|
|
88
|
+
throw new Error(`populateContentItems: url_refs.id not resolved for page id=${row.id} url=${row.url} — populateUrlRefs must run first`);
|
|
89
|
+
}
|
|
90
|
+
let contentTypeId = null;
|
|
91
|
+
if (row.contentType != null && row.contentType !== '') {
|
|
92
|
+
const resolved = contentTypeIds.get(row.contentType);
|
|
93
|
+
if (resolved === undefined) {
|
|
94
|
+
throw new Error(`populateContentItems: content_type_refs.id not resolved for page id=${row.id} contentType=${row.contentType} — populateContentTypeRefs must run first`);
|
|
95
|
+
}
|
|
96
|
+
contentTypeId = resolved;
|
|
97
|
+
}
|
|
98
|
+
let headerSetId = null;
|
|
99
|
+
if (typeof row.responseHeaders === 'string' && row.responseHeaders !== '') {
|
|
100
|
+
const resolved = headerSetIds.get(row.responseHeaders);
|
|
101
|
+
if (resolved === undefined) {
|
|
102
|
+
// `resolveHeaderSets` skips sentinel values ('{}' / 'null')
|
|
103
|
+
// AND payloads that `decomposeHeaderSet` cannot parse into
|
|
104
|
+
// any entries — in both cases the source row genuinely had
|
|
105
|
+
// no header set to point at, so `header_set_id = null` is
|
|
106
|
+
// the correct final state. `resolveHeaderSets` already ran
|
|
107
|
+
// the raw_hash fallback, so this branch only fires when the
|
|
108
|
+
// row has no persistable header set (parse error, or every
|
|
109
|
+
// entry stripped for non-string values).
|
|
110
|
+
headerSetId = null;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
headerSetId = resolved;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
id: row.id,
|
|
118
|
+
url_id: urlId,
|
|
119
|
+
is_external: row.isExternal == null ? 0 : row.isExternal ? 1 : 0,
|
|
120
|
+
scraped: row.scraped ? 1 : 0,
|
|
121
|
+
is_target: row.isTarget ? 1 : 0,
|
|
122
|
+
status: row.status ?? null,
|
|
123
|
+
status_text: row.statusText ?? null,
|
|
124
|
+
content_type_id: contentTypeId,
|
|
125
|
+
content_length: row.contentLength ?? null,
|
|
126
|
+
header_set_id: headerSetId,
|
|
127
|
+
redirect_dest_id: row.redirectDestId ?? null,
|
|
128
|
+
source: row.source,
|
|
129
|
+
first_crawled_at: row.firstCrawledAt ?? null,
|
|
130
|
+
last_crawled_at: row.lastCrawledAt ?? null,
|
|
131
|
+
crawl_order: row.order ?? null,
|
|
132
|
+
is_skipped: row.isSkipped == null ? null : row.isSkipped ? 1 : 0,
|
|
133
|
+
skip_reason: row.skipReason ?? null,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
for (let index = 0; index < inserts.length; index += INSERT_CHUNK_SIZE) {
|
|
137
|
+
const chunk = inserts.slice(index, index + INSERT_CHUNK_SIZE);
|
|
138
|
+
await trx('content_items').insert(chunk).onConflict('id').ignore();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ProgressCallback } from '../create-progress-reporter.js';
|
|
2
|
+
import type { PageDomPathResolver } from './populate-image-items.js';
|
|
3
|
+
import type { Knex } from 'knex';
|
|
4
|
+
/**
|
|
5
|
+
* Runs the six 0.13 entity/edge populates (issue #193) in dependency
|
|
6
|
+
* order against an already-connected archive.
|
|
7
|
+
*
|
|
8
|
+
* Order rationale:
|
|
9
|
+
*
|
|
10
|
+
* 1. **`content_items`** — every downstream populate's FKs reference
|
|
11
|
+
* `content_items(id)`. Must land first so the FKs are valid at
|
|
12
|
+
* COMMIT time. `redirect_dest_id` is `DEFERRABLE INITIALLY DEFERRED`
|
|
13
|
+
* (see {@link ../create-entity-tables.ts}) so a redirect
|
|
14
|
+
* source inserted before its destination is validated only at
|
|
15
|
+
* COMMIT — the insert order within this populate is not
|
|
16
|
+
* load-bearing.
|
|
17
|
+
* 2. **`page_meta`** — FK to `content_items(id)`.
|
|
18
|
+
* 3. **`resource_items`** — required before `resource_ref_edges`.
|
|
19
|
+
* 4. **`anchor_edges`** — FKs to `content_items(id)` on both
|
|
20
|
+
* sides.
|
|
21
|
+
* 5. **`resource_ref_edges`** — FKs to `resource_items(id)` and
|
|
22
|
+
* `content_items(id)`.
|
|
23
|
+
* 6. **`image_items`** — FK to `content_items(id)`; the
|
|
24
|
+
* dom-path text_refs upsert is scoped to this populate so re-runs are
|
|
25
|
+
* self-contained.
|
|
26
|
+
*
|
|
27
|
+
* Every sub-populate is independently idempotent via `INSERT OR IGNORE` on
|
|
28
|
+
* its natural key or PK. On a **re-crawl** (`crawl --append` /
|
|
29
|
+
* `--retry-failed` / `--inventory` all UPDATE existing `pages` /
|
|
30
|
+
* `resources` rows in place, and `#insertPage` deletes + re-inserts
|
|
31
|
+
* `anchors` / `images` per page), running populate again against the
|
|
32
|
+
* previously-populated archive would leave `content_items` / `page_meta`
|
|
33
|
+
* / `resource_items` at their FIRST-crawl values (source-priority
|
|
34
|
+
* upgrades, refreshed status, changed metadata never propagate) and
|
|
35
|
+
* `anchor_edges` / `image_items` / `resource_ref_edges` would keep stale
|
|
36
|
+
* rows keyed by the now-deleted legacy ids. To keep the reader-side
|
|
37
|
+
* view of the archive faithful to `pages` / `resources` on re-crawl,
|
|
38
|
+
* every entity + edge table is TRUNCATEd (in child-first order so no FK
|
|
39
|
+
* check trips) at the top of this function before the six sub-populates
|
|
40
|
+
* re-insert from the current legacy state. Ref tables (`url_refs`,
|
|
41
|
+
* `text_refs`, `content_type_refs`, `header_*`) are NOT truncated —
|
|
42
|
+
* they are content-addressable and additive; re-inserting the same
|
|
43
|
+
* value hits `INSERT OR IGNORE` and is a no-op.
|
|
44
|
+
*
|
|
45
|
+
* The whole invocation is expected to run inside one writer transaction
|
|
46
|
+
* with `.bak` protection at the caller level (a single WAL transaction
|
|
47
|
+
* with `.bak` rollback on failure);
|
|
48
|
+
* this function does not open its own transaction so the
|
|
49
|
+
* caller controls the boundary — see {@link
|
|
50
|
+
* ../populate-ref-tables/populate-refs.ts} for the same convention.
|
|
51
|
+
*
|
|
52
|
+
* **`PRAGMA foreign_keys = ON` is REQUIRED** on the underlying
|
|
53
|
+
* connection before this function is called. `content_items.redirect_dest_id`
|
|
54
|
+
* is `DEFERRABLE INITIALLY DEFERRED` (see
|
|
55
|
+
* {@link ../create-entity-tables.ts}); without foreign-key
|
|
56
|
+
* enforcement libsql commits `content_items` rows whose `redirect_dest_id`
|
|
57
|
+
* points at a non-existent page and the deferrable-FK-enforcement-at-COMMIT
|
|
58
|
+
* invariant is silently broken. The
|
|
59
|
+
* migration script sets the pragma explicitly; any other caller must do
|
|
60
|
+
* the same.
|
|
61
|
+
* HTML BLOB reads happen inline inside {@link populateImageItems} against
|
|
62
|
+
* the same `trx` — no `getPageHtml` callback here — because a callback
|
|
63
|
+
* that routed through `Database.getHtmlOfPageById` would re-enter the
|
|
64
|
+
* connection pool from inside this writer transaction and deadlock on
|
|
65
|
+
* libsql's single writer connection. See `./populate-image-items.ts` for
|
|
66
|
+
* the detailed rationale.
|
|
67
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
68
|
+
* @param resolvePageDomPaths - Callback that returns dom_path strings
|
|
69
|
+
* for one page's images. Injected rather than hard-coded so
|
|
70
|
+
* `@nitpicker/crawler` does not become a jsdom consumer at runtime.
|
|
71
|
+
* @param onProgress - Optional sink threaded to every sub-populate for
|
|
72
|
+
* periodic progress lines; see {@link ../create-progress-reporter.ts}.
|
|
73
|
+
* @example
|
|
74
|
+
* const archive = await Archive.open(archivePath);
|
|
75
|
+
* const knex = archive.getKnex();
|
|
76
|
+
* await knex.transaction(async (trx) => {
|
|
77
|
+
* await populateEntityTables(trx, jsdomResolver);
|
|
78
|
+
* });
|
|
79
|
+
* await archive.write();
|
|
80
|
+
*/
|
|
81
|
+
export declare function populateEntityTables(trx: Knex, resolvePageDomPaths: PageDomPathResolver, onProgress?: ProgressCallback): Promise<void>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { populateAnchorEdges } from './populate-anchor-edges.js';
|
|
2
|
+
import { populateContentItems } from './populate-content-items.js';
|
|
3
|
+
import { populateImageItems } from './populate-image-items.js';
|
|
4
|
+
import { populatePageMeta } from './populate-page-meta.js';
|
|
5
|
+
import { populateResourceItems } from './populate-resource-items.js';
|
|
6
|
+
import { populateResourceRefEdges } from './populate-resource-ref-edges.js';
|
|
7
|
+
/**
|
|
8
|
+
* Runs the six 0.13 entity/edge populates (issue #193) in dependency
|
|
9
|
+
* order against an already-connected archive.
|
|
10
|
+
*
|
|
11
|
+
* Order rationale:
|
|
12
|
+
*
|
|
13
|
+
* 1. **`content_items`** — every downstream populate's FKs reference
|
|
14
|
+
* `content_items(id)`. Must land first so the FKs are valid at
|
|
15
|
+
* COMMIT time. `redirect_dest_id` is `DEFERRABLE INITIALLY DEFERRED`
|
|
16
|
+
* (see {@link ../create-entity-tables.ts}) so a redirect
|
|
17
|
+
* source inserted before its destination is validated only at
|
|
18
|
+
* COMMIT — the insert order within this populate is not
|
|
19
|
+
* load-bearing.
|
|
20
|
+
* 2. **`page_meta`** — FK to `content_items(id)`.
|
|
21
|
+
* 3. **`resource_items`** — required before `resource_ref_edges`.
|
|
22
|
+
* 4. **`anchor_edges`** — FKs to `content_items(id)` on both
|
|
23
|
+
* sides.
|
|
24
|
+
* 5. **`resource_ref_edges`** — FKs to `resource_items(id)` and
|
|
25
|
+
* `content_items(id)`.
|
|
26
|
+
* 6. **`image_items`** — FK to `content_items(id)`; the
|
|
27
|
+
* dom-path text_refs upsert is scoped to this populate so re-runs are
|
|
28
|
+
* self-contained.
|
|
29
|
+
*
|
|
30
|
+
* Every sub-populate is independently idempotent via `INSERT OR IGNORE` on
|
|
31
|
+
* its natural key or PK. On a **re-crawl** (`crawl --append` /
|
|
32
|
+
* `--retry-failed` / `--inventory` all UPDATE existing `pages` /
|
|
33
|
+
* `resources` rows in place, and `#insertPage` deletes + re-inserts
|
|
34
|
+
* `anchors` / `images` per page), running populate again against the
|
|
35
|
+
* previously-populated archive would leave `content_items` / `page_meta`
|
|
36
|
+
* / `resource_items` at their FIRST-crawl values (source-priority
|
|
37
|
+
* upgrades, refreshed status, changed metadata never propagate) and
|
|
38
|
+
* `anchor_edges` / `image_items` / `resource_ref_edges` would keep stale
|
|
39
|
+
* rows keyed by the now-deleted legacy ids. To keep the reader-side
|
|
40
|
+
* view of the archive faithful to `pages` / `resources` on re-crawl,
|
|
41
|
+
* every entity + edge table is TRUNCATEd (in child-first order so no FK
|
|
42
|
+
* check trips) at the top of this function before the six sub-populates
|
|
43
|
+
* re-insert from the current legacy state. Ref tables (`url_refs`,
|
|
44
|
+
* `text_refs`, `content_type_refs`, `header_*`) are NOT truncated —
|
|
45
|
+
* they are content-addressable and additive; re-inserting the same
|
|
46
|
+
* value hits `INSERT OR IGNORE` and is a no-op.
|
|
47
|
+
*
|
|
48
|
+
* The whole invocation is expected to run inside one writer transaction
|
|
49
|
+
* with `.bak` protection at the caller level (a single WAL transaction
|
|
50
|
+
* with `.bak` rollback on failure);
|
|
51
|
+
* this function does not open its own transaction so the
|
|
52
|
+
* caller controls the boundary — see {@link
|
|
53
|
+
* ../populate-ref-tables/populate-refs.ts} for the same convention.
|
|
54
|
+
*
|
|
55
|
+
* **`PRAGMA foreign_keys = ON` is REQUIRED** on the underlying
|
|
56
|
+
* connection before this function is called. `content_items.redirect_dest_id`
|
|
57
|
+
* is `DEFERRABLE INITIALLY DEFERRED` (see
|
|
58
|
+
* {@link ../create-entity-tables.ts}); without foreign-key
|
|
59
|
+
* enforcement libsql commits `content_items` rows whose `redirect_dest_id`
|
|
60
|
+
* points at a non-existent page and the deferrable-FK-enforcement-at-COMMIT
|
|
61
|
+
* invariant is silently broken. The
|
|
62
|
+
* migration script sets the pragma explicitly; any other caller must do
|
|
63
|
+
* the same.
|
|
64
|
+
* HTML BLOB reads happen inline inside {@link populateImageItems} against
|
|
65
|
+
* the same `trx` — no `getPageHtml` callback here — because a callback
|
|
66
|
+
* that routed through `Database.getHtmlOfPageById` would re-enter the
|
|
67
|
+
* connection pool from inside this writer transaction and deadlock on
|
|
68
|
+
* libsql's single writer connection. See `./populate-image-items.ts` for
|
|
69
|
+
* the detailed rationale.
|
|
70
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
71
|
+
* @param resolvePageDomPaths - Callback that returns dom_path strings
|
|
72
|
+
* for one page's images. Injected rather than hard-coded so
|
|
73
|
+
* `@nitpicker/crawler` does not become a jsdom consumer at runtime.
|
|
74
|
+
* @param onProgress - Optional sink threaded to every sub-populate for
|
|
75
|
+
* periodic progress lines; see {@link ../create-progress-reporter.ts}.
|
|
76
|
+
* @example
|
|
77
|
+
* const archive = await Archive.open(archivePath);
|
|
78
|
+
* const knex = archive.getKnex();
|
|
79
|
+
* await knex.transaction(async (trx) => {
|
|
80
|
+
* await populateEntityTables(trx, jsdomResolver);
|
|
81
|
+
* });
|
|
82
|
+
* await archive.write();
|
|
83
|
+
*/
|
|
84
|
+
export async function populateEntityTables(trx, resolvePageDomPaths, onProgress) {
|
|
85
|
+
// Truncate child-first so no outgoing FK check ever sees a broken
|
|
86
|
+
// reference mid-delete. Order is: leaf edge/entity tables that have
|
|
87
|
+
// no incoming FKs (`image_items`, `anchor_edges`, `resource_ref_edges`),
|
|
88
|
+
// then the parent entities they referenced (`page_meta` and
|
|
89
|
+
// `resource_items`; `page_meta` references `content_items` so it
|
|
90
|
+
// must be dropped before `content_items`; `resource_items` was
|
|
91
|
+
// pointed at by `resource_ref_edges` which is now empty), and
|
|
92
|
+
// finally `content_items` itself — the only remaining incoming
|
|
93
|
+
// reference is its own DEFERRABLE INITIALLY DEFERRED
|
|
94
|
+
// `redirect_dest_id`, which is validated at COMMIT so a mid-trx
|
|
95
|
+
// wipe-and-refill is legal. No external table has an enforced FK
|
|
96
|
+
// into `content_items` (viewer read-model tables and
|
|
97
|
+
// `analysis_violations` reference `pages(id)` or hold logical-only
|
|
98
|
+
// pointers), so the truncation stops here.
|
|
99
|
+
await trx('image_items').delete();
|
|
100
|
+
await trx('anchor_edges').delete();
|
|
101
|
+
await trx('resource_ref_edges').delete();
|
|
102
|
+
await trx('page_meta').delete();
|
|
103
|
+
await trx('resource_items').delete();
|
|
104
|
+
await trx('content_items').delete();
|
|
105
|
+
await populateContentItems(trx, onProgress);
|
|
106
|
+
await populatePageMeta(trx, onProgress);
|
|
107
|
+
await populateResourceItems(trx, onProgress);
|
|
108
|
+
await populateAnchorEdges(trx, onProgress);
|
|
109
|
+
await populateResourceRefEdges(trx);
|
|
110
|
+
await populateImageItems(trx, resolvePageDomPaths, onProgress);
|
|
111
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ProgressCallback } from '../create-progress-reporter.js';
|
|
2
|
+
import type { DomPathResult } from './types.js';
|
|
3
|
+
import type { Knex } from 'knex';
|
|
4
|
+
/**
|
|
5
|
+
* Callback that resolves DOM-path strings for one page's images. The
|
|
6
|
+
* callback receives the page's HTML string (or `null` when no snapshot
|
|
7
|
+
* is stored) plus the page's `images` rows, and must return one
|
|
8
|
+
* {@link DomPathResult} per row.
|
|
9
|
+
*
|
|
10
|
+
* Injected rather than hard-coded so `@nitpicker/crawler` does not
|
|
11
|
+
* become a jsdom consumer at runtime — the migration script
|
|
12
|
+
* (`scripts/migrate-to-0.13.mjs`) wires a jsdom-backed
|
|
13
|
+
* implementation, while unit tests inject stubs. A future crawler-time
|
|
14
|
+
* DOM-path capture can use the same signature with
|
|
15
|
+
* puppeteer-backed elements.
|
|
16
|
+
*/
|
|
17
|
+
export type PageDomPathResolver = (pageId: number, htmlString: string | null, images: readonly ImageRowForResolver[]) => Promise<ReadonlyMap<number, DomPathResult>>;
|
|
18
|
+
/**
|
|
19
|
+
* The projection of `images` rows passed to a
|
|
20
|
+
* {@link PageDomPathResolver}. Kept as a narrow shape (only the fields
|
|
21
|
+
* the resolver needs) so callers of the resolver do not depend on the
|
|
22
|
+
* full row width.
|
|
23
|
+
*/
|
|
24
|
+
export interface ImageRowForResolver {
|
|
25
|
+
/** Legacy `images.id`. */
|
|
26
|
+
id: number;
|
|
27
|
+
/** Legacy `images.sourceCode`. */
|
|
28
|
+
sourceCode: string | null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Populates `image_items` from `images` (issue #193).
|
|
32
|
+
*
|
|
33
|
+
* The outer loop iterates **pages**, not images. Each page's images are
|
|
34
|
+
* processed as one whole unit — dom-path derivation, HTML BLOB fetch,
|
|
35
|
+
* and ref lookups all happen once per page. This design closes three
|
|
36
|
+
* separate correctness / efficiency traps that a purely image-chunked
|
|
37
|
+
* loop would expose:
|
|
38
|
+
*
|
|
39
|
+
* 1. **Ordinal-cursor bleed across chunks** — `matchImagesToDomPaths`
|
|
40
|
+
* tracks per-outerHTML cursor state, so a page with 1 000 identical
|
|
41
|
+
* `<img>` tags spanning two image-chunks would reset the cursor at
|
|
42
|
+
* the boundary and duplicate dom_paths already assigned in the
|
|
43
|
+
* previous chunk. Iterating by page keeps every page's cursor state
|
|
44
|
+
* self-contained.
|
|
45
|
+
* 2. **Repeated `getPageHtml` + jsdom parse** — a page whose images
|
|
46
|
+
* straddle N image-chunks would fetch and re-parse the same multi-MB
|
|
47
|
+
* HTML snapshot N times. Iterating by page makes this exactly once
|
|
48
|
+
* per page.
|
|
49
|
+
* 3. **Data-URI routing gap** — legacy `images.src` can be either a
|
|
50
|
+
* plain URL (goes to `url_refs`) or a large `data:` URI (goes to
|
|
51
|
+
* `blob_refs`, per the 512-byte threshold). The lookups partition
|
|
52
|
+
* by that rule, so a data URI is never bound into
|
|
53
|
+
* `resolveUrlRefs`' `WHERE url IN (?)` — which could otherwise
|
|
54
|
+
* exceed SQLite's `SQLITE_MAX_SQL_LENGTH` for multi-KB URIs.
|
|
55
|
+
*
|
|
56
|
+
* Per outer iteration:
|
|
57
|
+
*
|
|
58
|
+
* 1. **Keyset-paginate** distinct `pageId` values from `images`. Peak
|
|
59
|
+
* memory ≈ 500 integers.
|
|
60
|
+
* 2. **Fetch all images** for those page ids in one query, ordered by
|
|
61
|
+
* `(pageId, id)` so JS-side grouping is a linear pass.
|
|
62
|
+
* 3. **Resolve dom paths per page** — one `getPageHtml` + one resolver
|
|
63
|
+
* call each. `dom_path` texts are collected across the whole batch
|
|
64
|
+
* so `upsertTextRefs` runs once per batch.
|
|
65
|
+
* 4. **Partition src / currentSrc** by the data-URI routing rule and
|
|
66
|
+
* batch-resolve url refs, blob refs, and alt text refs across the
|
|
67
|
+
* whole batch.
|
|
68
|
+
* 5. **Bulk INSERT** with explicit `id = images.id`.
|
|
69
|
+
*
|
|
70
|
+
* `INSERT OR IGNORE` on the natural PK makes the step idempotent.
|
|
71
|
+
*
|
|
72
|
+
* HTML BLOBs are read inline via the same `trx` handle rather than
|
|
73
|
+
* through a caller-supplied `getPageHtml` callback: a callback that
|
|
74
|
+
* queried `Database.getHtmlOfPageById` (which uses the non-transactional
|
|
75
|
+
* Knex instance) would re-enter the pool from inside this writer
|
|
76
|
+
* transaction and deadlock on libsql's single writer connection. Reading
|
|
77
|
+
* `page_html_ref` + `page_html_blobs` via `trx` keeps every read on the
|
|
78
|
+
* same connection the outer trx already owns.
|
|
79
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
80
|
+
* @param resolvePageDomPaths - Callback that returns dom_path strings
|
|
81
|
+
* for one page's images (see {@link PageDomPathResolver}).
|
|
82
|
+
* @param onProgress - Optional sink for periodic progress lines (one per
|
|
83
|
+
* ~5% of distinct `images.pageId` values scanned); see
|
|
84
|
+
* {@link ../create-progress-reporter.ts}.
|
|
85
|
+
* @example
|
|
86
|
+
* const jsdomResolver = createJsdomResolver();
|
|
87
|
+
* await knex.transaction(async (trx) => {
|
|
88
|
+
* await populateImageItems(trx, jsdomResolver);
|
|
89
|
+
* });
|
|
90
|
+
*/
|
|
91
|
+
export declare function populateImageItems(trx: Knex, resolvePageDomPaths: PageDomPathResolver, onProgress?: ProgressCallback): Promise<void>;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { createProgressReporter } from '../create-progress-reporter.js';
|
|
2
|
+
import { decodeStoredBlob } from '../decode-html-blob.js';
|
|
3
|
+
import { isBlobRefValue } from './is-blob-ref-value.js';
|
|
4
|
+
import { resolveBlobRefs } from './resolve-blob-refs.js';
|
|
5
|
+
import { resolveTextRefs } from './resolve-text-refs.js';
|
|
6
|
+
import { resolveUrlOrBlobFromMaps } from './resolve-url-or-blob-from-maps.js';
|
|
7
|
+
import { resolveUrlRefs } from './resolve-url-refs.js';
|
|
8
|
+
import { upsertTextRefs } from './upsert-text-refs.js';
|
|
9
|
+
/**
|
|
10
|
+
* Page ids scanned per keyset-paginated `SELECT DISTINCT pageId` chunk.
|
|
11
|
+
* Batches the outer loop so 470 K pages worth of `images` rows are
|
|
12
|
+
* traversed in ~1 K round trips instead of one round-trip per page,
|
|
13
|
+
* without holding the entire distinct-pageId list in memory at once.
|
|
14
|
+
*/
|
|
15
|
+
const PAGE_ID_CHUNK_SIZE = 500;
|
|
16
|
+
/**
|
|
17
|
+
* Rows sent per `INSERT INTO image_items ... VALUES (...)` statement.
|
|
18
|
+
* Each row binds 13 params, so 300 rows = 3 900 params — safely under
|
|
19
|
+
* the SQLite variable limit.
|
|
20
|
+
*/
|
|
21
|
+
const INSERT_CHUNK_SIZE = 300;
|
|
22
|
+
/**
|
|
23
|
+
* Populates `image_items` from `images` (issue #193).
|
|
24
|
+
*
|
|
25
|
+
* The outer loop iterates **pages**, not images. Each page's images are
|
|
26
|
+
* processed as one whole unit — dom-path derivation, HTML BLOB fetch,
|
|
27
|
+
* and ref lookups all happen once per page. This design closes three
|
|
28
|
+
* separate correctness / efficiency traps that a purely image-chunked
|
|
29
|
+
* loop would expose:
|
|
30
|
+
*
|
|
31
|
+
* 1. **Ordinal-cursor bleed across chunks** — `matchImagesToDomPaths`
|
|
32
|
+
* tracks per-outerHTML cursor state, so a page with 1 000 identical
|
|
33
|
+
* `<img>` tags spanning two image-chunks would reset the cursor at
|
|
34
|
+
* the boundary and duplicate dom_paths already assigned in the
|
|
35
|
+
* previous chunk. Iterating by page keeps every page's cursor state
|
|
36
|
+
* self-contained.
|
|
37
|
+
* 2. **Repeated `getPageHtml` + jsdom parse** — a page whose images
|
|
38
|
+
* straddle N image-chunks would fetch and re-parse the same multi-MB
|
|
39
|
+
* HTML snapshot N times. Iterating by page makes this exactly once
|
|
40
|
+
* per page.
|
|
41
|
+
* 3. **Data-URI routing gap** — legacy `images.src` can be either a
|
|
42
|
+
* plain URL (goes to `url_refs`) or a large `data:` URI (goes to
|
|
43
|
+
* `blob_refs`, per the 512-byte threshold). The lookups partition
|
|
44
|
+
* by that rule, so a data URI is never bound into
|
|
45
|
+
* `resolveUrlRefs`' `WHERE url IN (?)` — which could otherwise
|
|
46
|
+
* exceed SQLite's `SQLITE_MAX_SQL_LENGTH` for multi-KB URIs.
|
|
47
|
+
*
|
|
48
|
+
* Per outer iteration:
|
|
49
|
+
*
|
|
50
|
+
* 1. **Keyset-paginate** distinct `pageId` values from `images`. Peak
|
|
51
|
+
* memory ≈ 500 integers.
|
|
52
|
+
* 2. **Fetch all images** for those page ids in one query, ordered by
|
|
53
|
+
* `(pageId, id)` so JS-side grouping is a linear pass.
|
|
54
|
+
* 3. **Resolve dom paths per page** — one `getPageHtml` + one resolver
|
|
55
|
+
* call each. `dom_path` texts are collected across the whole batch
|
|
56
|
+
* so `upsertTextRefs` runs once per batch.
|
|
57
|
+
* 4. **Partition src / currentSrc** by the data-URI routing rule and
|
|
58
|
+
* batch-resolve url refs, blob refs, and alt text refs across the
|
|
59
|
+
* whole batch.
|
|
60
|
+
* 5. **Bulk INSERT** with explicit `id = images.id`.
|
|
61
|
+
*
|
|
62
|
+
* `INSERT OR IGNORE` on the natural PK makes the step idempotent.
|
|
63
|
+
*
|
|
64
|
+
* HTML BLOBs are read inline via the same `trx` handle rather than
|
|
65
|
+
* through a caller-supplied `getPageHtml` callback: a callback that
|
|
66
|
+
* queried `Database.getHtmlOfPageById` (which uses the non-transactional
|
|
67
|
+
* Knex instance) would re-enter the pool from inside this writer
|
|
68
|
+
* transaction and deadlock on libsql's single writer connection. Reading
|
|
69
|
+
* `page_html_ref` + `page_html_blobs` via `trx` keeps every read on the
|
|
70
|
+
* same connection the outer trx already owns.
|
|
71
|
+
* @param trx - Knex instance or transaction connected to the archive DB.
|
|
72
|
+
* @param resolvePageDomPaths - Callback that returns dom_path strings
|
|
73
|
+
* for one page's images (see {@link PageDomPathResolver}).
|
|
74
|
+
* @param onProgress - Optional sink for periodic progress lines (one per
|
|
75
|
+
* ~5% of distinct `images.pageId` values scanned); see
|
|
76
|
+
* {@link ../create-progress-reporter.ts}.
|
|
77
|
+
* @example
|
|
78
|
+
* const jsdomResolver = createJsdomResolver();
|
|
79
|
+
* await knex.transaction(async (trx) => {
|
|
80
|
+
* await populateImageItems(trx, jsdomResolver);
|
|
81
|
+
* });
|
|
82
|
+
*/
|
|
83
|
+
export async function populateImageItems(trx, resolvePageDomPaths, onProgress) {
|
|
84
|
+
const countRows = await trx('images').countDistinct({ n: 'pageId' });
|
|
85
|
+
const totalRow = countRows[0];
|
|
86
|
+
const total = Number(totalRow?.n ?? 0);
|
|
87
|
+
const report = createProgressReporter('image_items (distinct pages)', total, onProgress);
|
|
88
|
+
let processedPages = 0;
|
|
89
|
+
let cursorPageId = 0;
|
|
90
|
+
while (true) {
|
|
91
|
+
const pageIdRows = await trx('images')
|
|
92
|
+
.distinct('pageId')
|
|
93
|
+
.where('pageId', '>', cursorPageId)
|
|
94
|
+
.orderBy('pageId', 'asc')
|
|
95
|
+
.limit(PAGE_ID_CHUNK_SIZE);
|
|
96
|
+
if (pageIdRows.length === 0) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
const pageIds = pageIdRows.map((r) => r.pageId);
|
|
100
|
+
cursorPageId = pageIds.at(-1);
|
|
101
|
+
processedPages += pageIds.length;
|
|
102
|
+
report(processedPages);
|
|
103
|
+
const rows = await trx('images')
|
|
104
|
+
.select('id', 'pageId', 'src', 'currentSrc', 'alt', 'width', 'height', 'naturalWidth', 'naturalHeight', 'isLazy', 'viewportWidth', 'sourceCode')
|
|
105
|
+
.whereIn('pageId', pageIds)
|
|
106
|
+
.orderBy([
|
|
107
|
+
{ column: 'pageId', order: 'asc' },
|
|
108
|
+
{ column: 'id', order: 'asc' },
|
|
109
|
+
]);
|
|
110
|
+
if (rows.length === 0) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const byPage = new Map();
|
|
114
|
+
for (const row of rows) {
|
|
115
|
+
const bucket = byPage.get(row.pageId);
|
|
116
|
+
if (bucket === undefined) {
|
|
117
|
+
byPage.set(row.pageId, [row]);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
bucket.push(row);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const domPaths = new Map();
|
|
124
|
+
for (const [pageId, pageImages] of byPage) {
|
|
125
|
+
const html = await readPageHtmlInTrx(trx, pageId);
|
|
126
|
+
const resolved = await resolvePageDomPaths(pageId, html, pageImages);
|
|
127
|
+
for (const [imageId, entry] of resolved) {
|
|
128
|
+
domPaths.set(imageId, entry);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const urls = new Set();
|
|
132
|
+
const dataUris = new Set();
|
|
133
|
+
const alts = new Set();
|
|
134
|
+
const domPathTexts = new Set();
|
|
135
|
+
for (const row of rows) {
|
|
136
|
+
for (const value of [row.src, row.currentSrc]) {
|
|
137
|
+
if (typeof value !== 'string' || value === '') {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (isBlobRefValue(value)) {
|
|
141
|
+
dataUris.add(value);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
urls.add(value);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (typeof row.alt === 'string' && row.alt !== '') {
|
|
148
|
+
alts.add(row.alt);
|
|
149
|
+
}
|
|
150
|
+
const domPath = domPaths.get(row.id);
|
|
151
|
+
if (domPath !== undefined) {
|
|
152
|
+
domPathTexts.add(domPath.path);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const urlIds = await resolveUrlRefs(trx, urls);
|
|
156
|
+
const blobIds = await resolveBlobRefs(trx, dataUris);
|
|
157
|
+
const altIds = await resolveTextRefs(trx, alts);
|
|
158
|
+
// `dom_path` strings are synthesised right here and were not
|
|
159
|
+
// among the columns the `text_refs` populate scanned
|
|
160
|
+
// (`populate-ref-tables/populate-text-refs.ts`) — insert every distinct
|
|
161
|
+
// derived path (including `unknown/<id>` fallbacks) before
|
|
162
|
+
// resolving its id. The upsert is idempotent so re-runs across
|
|
163
|
+
// partial failures do not duplicate rows.
|
|
164
|
+
const domPathIds = await upsertTextRefs(trx, domPathTexts);
|
|
165
|
+
const inserts = [];
|
|
166
|
+
for (const row of rows) {
|
|
167
|
+
const domPath = domPaths.get(row.id);
|
|
168
|
+
if (domPath === undefined) {
|
|
169
|
+
throw new Error(`populateImageItems: dom_path not resolved for image id=${row.id}`);
|
|
170
|
+
}
|
|
171
|
+
const domPathId = domPathIds.get(domPath.path);
|
|
172
|
+
if (domPathId === undefined) {
|
|
173
|
+
throw new Error(`populateImageItems: text_refs.id not resolved for dom_path=${domPath.path} — the upsertTextRefs above must have inserted it`);
|
|
174
|
+
}
|
|
175
|
+
const srcSlot = resolveUrlOrBlobFromMaps(row.src, urlIds, blobIds);
|
|
176
|
+
const currentSrcSlot = resolveUrlOrBlobFromMaps(row.currentSrc, urlIds, blobIds);
|
|
177
|
+
inserts.push({
|
|
178
|
+
id: row.id,
|
|
179
|
+
page_id: row.pageId,
|
|
180
|
+
src_url_id: srcSlot.url,
|
|
181
|
+
current_src_url_id: currentSrcSlot.url,
|
|
182
|
+
src_blob_id: srcSlot.blob,
|
|
183
|
+
current_src_blob_id: currentSrcSlot.blob,
|
|
184
|
+
alt_text_id: typeof row.alt === 'string' && row.alt !== ''
|
|
185
|
+
? (altIds.get(row.alt) ?? null)
|
|
186
|
+
: null,
|
|
187
|
+
width: row.width,
|
|
188
|
+
height: row.height,
|
|
189
|
+
natural_width: row.naturalWidth,
|
|
190
|
+
natural_height: row.naturalHeight,
|
|
191
|
+
is_lazy: row.isLazy == null ? null : row.isLazy ? 1 : 0,
|
|
192
|
+
viewport_width: row.viewportWidth,
|
|
193
|
+
dom_path_text_id: domPathId,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
for (let index = 0; index < inserts.length; index += INSERT_CHUNK_SIZE) {
|
|
197
|
+
const chunk = inserts.slice(index, index + INSERT_CHUNK_SIZE);
|
|
198
|
+
await trx('image_items').insert(chunk).onConflict('id').ignore();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Reads the stored HTML snapshot for one legacy page id, via the same
|
|
204
|
+
* `trx` handle that the outer populate transaction owns. Mirrors
|
|
205
|
+
* `Database.getHtmlOfPageById` but keeps every read on the trx
|
|
206
|
+
* connection so the populate does not re-enter the pool from
|
|
207
|
+
* inside its own writer transaction (see {@link populateImageItems}'s
|
|
208
|
+
* doc for the deadlock this avoids).
|
|
209
|
+
* @param trx - The open migration transaction.
|
|
210
|
+
* @param pageId - `pages.id` (== `content_items.id`) to read HTML for.
|
|
211
|
+
* @returns Decoded HTML string, or `null` when no snapshot is stored.
|
|
212
|
+
*/
|
|
213
|
+
async function readPageHtmlInTrx(trx, pageId) {
|
|
214
|
+
const row = (await trx('page_html_ref')
|
|
215
|
+
.join('page_html_blobs', 'page_html_ref.hash', '=', 'page_html_blobs.hash')
|
|
216
|
+
.select('page_html_blobs.body as body', 'page_html_blobs.codec as codec')
|
|
217
|
+
.where('page_html_ref.page_id', pageId)
|
|
218
|
+
.first());
|
|
219
|
+
if (!row) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return decodeStoredBlob(row.body, row.codec);
|
|
223
|
+
}
|