@nitpicker/archive 0.0.0 → 0.25.1
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/LICENSE +191 -0
- package/README.md +106 -0
- package/lib/apply-connection-pragmas.d.ts +18 -0
- package/lib/apply-connection-pragmas.js +40 -0
- package/lib/archive-accessor.d.ts +265 -0
- package/lib/archive-accessor.js +477 -0
- package/lib/archive-lock.d.ts +45 -0
- package/lib/archive-lock.js +154 -0
- package/lib/archive.d.ts +736 -0
- package/lib/archive.js +1055 -0
- package/lib/body-hash/compute-body-hash.d.ts +32 -0
- package/lib/body-hash/compute-body-hash.js +35 -0
- package/lib/cache/clear-archive-cache-entry.d.ts +18 -0
- package/lib/cache/clear-archive-cache-entry.js +24 -0
- package/lib/cache/clear-archive-cache-root.d.ts +26 -0
- package/lib/cache/clear-archive-cache-root.js +32 -0
- package/lib/cache/compute-archive-cache-key.d.ts +39 -0
- package/lib/cache/compute-archive-cache-key.js +95 -0
- package/lib/cache/extract-archive-to-cache.d.ts +57 -0
- package/lib/cache/extract-archive-to-cache.js +326 -0
- package/lib/cache/get-archive-cache-root.d.ts +20 -0
- package/lib/cache/get-archive-cache-root.js +53 -0
- package/lib/cache/is-archive-cache-disabled.d.ts +24 -0
- package/lib/cache/is-archive-cache-disabled.js +34 -0
- package/lib/cache/list-archive-cache-entries.d.ts +23 -0
- package/lib/cache/list-archive-cache-entries.js +130 -0
- package/lib/cache/path-exists.d.ts +9 -0
- package/lib/cache/path-exists.js +18 -0
- package/lib/cache/resolve-archive-cache-dir.d.ts +26 -0
- package/lib/cache/resolve-archive-cache-dir.js +32 -0
- package/lib/cache/types.d.ts +33 -0
- package/lib/cache/types.js +1 -0
- package/lib/content-type/is-html-content-type.d.ts +17 -0
- package/lib/content-type/is-html-content-type.js +19 -0
- package/lib/content-type/normalize-content-type.d.ts +14 -0
- package/lib/content-type/normalize-content-type.js +20 -0
- package/lib/create-adjunct-tables.d.ts +66 -0
- package/lib/create-adjunct-tables.js +510 -0
- package/lib/create-entity-tables.d.ts +191 -0
- package/lib/create-entity-tables.js +387 -0
- package/lib/create-progress-reporter.d.ts +30 -0
- package/lib/create-progress-reporter.js +38 -0
- package/lib/create-ref-tables.d.ts +35 -0
- package/lib/create-ref-tables.js +185 -0
- package/lib/create-template-tables.d.ts +28 -0
- package/lib/create-template-tables.js +92 -0
- package/lib/database.d.ts +549 -0
- package/lib/database.js +803 -0
- package/lib/db-ops/_shared/clear-write-ref-caches.d.ts +27 -0
- package/lib/db-ops/_shared/clear-write-ref-caches.js +35 -0
- package/lib/db-ops/_shared/compress-payload.d.ts +26 -0
- package/lib/db-ops/_shared/compress-payload.js +30 -0
- package/lib/db-ops/_shared/create-write-ref-caches.d.ts +17 -0
- package/lib/db-ops/_shared/create-write-ref-caches.js +27 -0
- package/lib/db-ops/_shared/decode-json-ref.d.ts +17 -0
- package/lib/db-ops/_shared/decode-json-ref.js +31 -0
- package/lib/db-ops/_shared/keyset-paginate-by-id.d.ts +49 -0
- package/lib/db-ops/_shared/keyset-paginate-by-id.js +69 -0
- package/lib/db-ops/_shared/load-response-headers-by-set-ids.d.ts +20 -0
- package/lib/db-ops/_shared/load-response-headers-by-set-ids.js +53 -0
- package/lib/db-ops/_shared/resolve-content-item-id.d.ts +62 -0
- package/lib/db-ops/_shared/resolve-content-item-id.js +143 -0
- package/lib/db-ops/_shared/resolve-url-or-blob.d.ts +23 -0
- package/lib/db-ops/_shared/resolve-url-or-blob.js +29 -0
- package/lib/db-ops/_shared/retry-setting.d.ts +16 -0
- package/lib/db-ops/_shared/retry-setting.js +18 -0
- package/lib/db-ops/_shared/safe-parse-json.d.ts +11 -0
- package/lib/db-ops/_shared/safe-parse-json.js +18 -0
- package/lib/db-ops/_shared/types.d.ts +94 -0
- package/lib/db-ops/_shared/types.js +1 -0
- package/lib/db-ops/_shared/upsert-blob-ref.d.ts +25 -0
- package/lib/db-ops/_shared/upsert-blob-ref.js +48 -0
- package/lib/db-ops/_shared/upsert-content-type-ref.d.ts +30 -0
- package/lib/db-ops/_shared/upsert-content-type-ref.js +45 -0
- package/lib/db-ops/_shared/upsert-json-ref.d.ts +22 -0
- package/lib/db-ops/_shared/upsert-json-ref.js +41 -0
- package/lib/db-ops/_shared/upsert-response-headers.d.ts +35 -0
- package/lib/db-ops/_shared/upsert-response-headers.js +49 -0
- package/lib/db-ops/_shared/upsert-url-ref.d.ts +39 -0
- package/lib/db-ops/_shared/upsert-url-ref.js +62 -0
- package/lib/db-ops/anchors/get-anchors-on-page.d.ts +10 -0
- package/lib/db-ops/anchors/get-anchors-on-page.js +21 -0
- package/lib/db-ops/config/get-base-url.d.ts +8 -0
- package/lib/db-ops/config/get-base-url.js +14 -0
- package/lib/db-ops/config/get-config.d.ts +10 -0
- package/lib/db-ops/config/get-config.js +29 -0
- package/lib/db-ops/config/get-created-cwd.d.ts +11 -0
- package/lib/db-ops/config/get-created-cwd.js +13 -0
- package/lib/db-ops/config/get-name.d.ts +8 -0
- package/lib/db-ops/config/get-name.js +14 -0
- package/lib/db-ops/config/info-column-allowlist.d.ts +7 -0
- package/lib/db-ops/config/info-column-allowlist.js +29 -0
- package/lib/db-ops/config/info-json-columns.d.ts +5 -0
- package/lib/db-ops/config/info-json-columns.js +11 -0
- package/lib/db-ops/config/set-config.d.ts +12 -0
- package/lib/db-ops/config/set-config.js +21 -0
- package/lib/db-ops/config/update-config.d.ts +17 -0
- package/lib/db-ops/config/update-config.js +36 -0
- package/lib/db-ops/console-logs/compute-console-log-hash.d.ts +19 -0
- package/lib/db-ops/console-logs/compute-console-log-hash.js +30 -0
- package/lib/db-ops/console-logs/replace-console-logs.d.ts +34 -0
- package/lib/db-ops/console-logs/replace-console-logs.js +123 -0
- package/lib/db-ops/console-logs/stringify-console-log-args.d.ts +16 -0
- package/lib/db-ops/console-logs/stringify-console-log-args.js +26 -0
- package/lib/db-ops/console-logs/types.d.ts +38 -0
- package/lib/db-ops/console-logs/types.js +1 -0
- package/lib/db-ops/console-logs/upsert-console-log-item.d.ts +29 -0
- package/lib/db-ops/console-logs/upsert-console-log-item.js +52 -0
- package/lib/db-ops/dedupe-cap/accumulate-dedupe-cap-rejected-count.d.ts +18 -0
- package/lib/db-ops/dedupe-cap/accumulate-dedupe-cap-rejected-count.js +23 -0
- package/lib/db-ops/dedupe-cap/finalize-dedupe-cap-event.d.ts +12 -0
- package/lib/db-ops/dedupe-cap/finalize-dedupe-cap-event.js +15 -0
- package/lib/db-ops/dedupe-cap/insert-dedupe-cap-event.d.ts +14 -0
- package/lib/db-ops/dedupe-cap/insert-dedupe-cap-event.js +30 -0
- package/lib/db-ops/dedupe-cap/list-dedupe-cap-observations.d.ts +57 -0
- package/lib/db-ops/dedupe-cap/list-dedupe-cap-observations.js +87 -0
- package/lib/db-ops/dedupe-cap/list-dedupe-cap-shape-keys.d.ts +21 -0
- package/lib/db-ops/dedupe-cap/list-dedupe-cap-shape-keys.js +27 -0
- package/lib/db-ops/errors/insert-crawl-error.d.ts +15 -0
- package/lib/db-ops/errors/insert-crawl-error.js +21 -0
- package/lib/db-ops/errors/insert-page-error.d.ts +21 -0
- package/lib/db-ops/errors/insert-page-error.js +30 -0
- package/lib/db-ops/errors/list-dns-burned-host-candidates.d.ts +33 -0
- package/lib/db-ops/errors/list-dns-burned-host-candidates.js +158 -0
- package/lib/db-ops/html/read-page-html-blob.d.ts +17 -0
- package/lib/db-ops/html/read-page-html-blob.js +20 -0
- package/lib/db-ops/lifecycle/checkpoint.d.ts +8 -0
- package/lib/db-ops/lifecycle/checkpoint.js +9 -0
- package/lib/db-ops/lifecycle/destroy.d.ts +6 -0
- package/lib/db-ops/lifecycle/destroy.js +7 -0
- package/lib/db-ops/lifecycle/init.d.ts +58 -0
- package/lib/db-ops/lifecycle/init.js +122 -0
- package/lib/db-ops/list-reconcile/record-list-reconcile-run.d.ts +22 -0
- package/lib/db-ops/list-reconcile/record-list-reconcile-run.js +41 -0
- package/lib/db-ops/meta/get-audios-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-audios-of-page.js +15 -0
- package/lib/db-ops/meta/get-buttons-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-buttons-of-page.js +15 -0
- package/lib/db-ops/meta/get-canvases-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-canvases-of-page.js +15 -0
- package/lib/db-ops/meta/get-custom-elements-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-custom-elements-of-page.js +15 -0
- package/lib/db-ops/meta/get-headings-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-headings-of-page.js +15 -0
- package/lib/db-ops/meta/get-iframes-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-iframes-of-page.js +15 -0
- package/lib/db-ops/meta/get-jsonld-of-page.d.ts +13 -0
- package/lib/db-ops/meta/get-jsonld-of-page.js +27 -0
- package/lib/db-ops/meta/get-main-content-images-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-main-content-images-of-page.js +15 -0
- package/lib/db-ops/meta/get-main-content-tables-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-main-content-tables-of-page.js +15 -0
- package/lib/db-ops/meta/get-page-technologies-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-page-technologies-of-page.js +15 -0
- package/lib/db-ops/meta/get-technology-signals-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-technology-signals-of-page.js +15 -0
- package/lib/db-ops/meta/get-videos-of-page.d.ts +11 -0
- package/lib/db-ops/meta/get-videos-of-page.js +15 -0
- package/lib/db-ops/outages/close-network-outage.d.ts +12 -0
- package/lib/db-ops/outages/close-network-outage.js +15 -0
- package/lib/db-ops/outages/close-stale-open-network-outages.d.ts +26 -0
- package/lib/db-ops/outages/close-stale-open-network-outages.js +42 -0
- package/lib/db-ops/outages/compute-outage-clamp-timestamp.d.ts +26 -0
- package/lib/db-ops/outages/compute-outage-clamp-timestamp.js +31 -0
- package/lib/db-ops/outages/insert-network-outage.d.ts +14 -0
- package/lib/db-ops/outages/insert-network-outage.js +29 -0
- package/lib/db-ops/outages/list-network-outages.d.ts +28 -0
- package/lib/db-ops/outages/list-network-outages.js +42 -0
- package/lib/db-ops/pages/order/set-url-order.d.ts +13 -0
- package/lib/db-ops/pages/order/set-url-order.js +38 -0
- package/lib/db-ops/pages/read/build-page-query.d.ts +18 -0
- package/lib/db-ops/pages/read/build-page-query.js +40 -0
- package/lib/db-ops/pages/read/get-crawling-state.d.ts +117 -0
- package/lib/db-ops/pages/read/get-crawling-state.js +161 -0
- package/lib/db-ops/pages/read/get-existing-page-urls.d.ts +15 -0
- package/lib/db-ops/pages/read/get-existing-page-urls.js +30 -0
- package/lib/db-ops/pages/read/get-page-count.d.ts +12 -0
- package/lib/db-ops/pages/read/get-page-count.js +21 -0
- package/lib/db-ops/pages/read/get-page-source-by-url.d.ts +24 -0
- package/lib/db-ops/pages/read/get-page-source-by-url.js +28 -0
- package/lib/db-ops/pages/read/get-pages-with-rels.d.ts +38 -0
- package/lib/db-ops/pages/read/get-pages-with-rels.js +107 -0
- package/lib/db-ops/pages/read/get-pages.d.ts +11 -0
- package/lib/db-ops/pages/read/get-pages.js +51 -0
- package/lib/db-ops/pages/read/get-scraped-html-page-count.d.ts +18 -0
- package/lib/db-ops/pages/read/get-scraped-html-page-count.js +25 -0
- package/lib/db-ops/pages/read/reconstruct-page-rows.d.ts +31 -0
- package/lib/db-ops/pages/read/reconstruct-page-rows.js +32 -0
- package/lib/db-ops/pages/reset/clear-page-derived-rows.d.ts +20 -0
- package/lib/db-ops/pages/reset/clear-page-derived-rows.js +37 -0
- package/lib/db-ops/pages/reset/repromote-external-pages.d.ts +30 -0
- package/lib/db-ops/pages/reset/repromote-external-pages.js +91 -0
- package/lib/db-ops/pages/reset/reset-failed-pages.d.ts +88 -0
- package/lib/db-ops/pages/reset/reset-failed-pages.js +216 -0
- package/lib/db-ops/pages/reset/reset-pages-by-urls.d.ts +80 -0
- package/lib/db-ops/pages/reset/reset-pages-by-urls.js +151 -0
- package/lib/db-ops/pages/reset/retryable-image-scan-codes.d.ts +24 -0
- package/lib/db-ops/pages/reset/retryable-image-scan-codes.js +24 -0
- package/lib/db-ops/pages/write/insert-audios.d.ts +14 -0
- package/lib/db-ops/pages/write/insert-audios.js +25 -0
- package/lib/db-ops/pages/write/insert-buttons.d.ts +14 -0
- package/lib/db-ops/pages/write/insert-buttons.js +29 -0
- package/lib/db-ops/pages/write/insert-canvases.d.ts +14 -0
- package/lib/db-ops/pages/write/insert-canvases.js +26 -0
- package/lib/db-ops/pages/write/insert-custom-elements.d.ts +27 -0
- package/lib/db-ops/pages/write/insert-custom-elements.js +40 -0
- package/lib/db-ops/pages/write/insert-headings.d.ts +15 -0
- package/lib/db-ops/pages/write/insert-headings.js +27 -0
- package/lib/db-ops/pages/write/insert-iframes.d.ts +14 -0
- package/lib/db-ops/pages/write/insert-iframes.js +28 -0
- package/lib/db-ops/pages/write/insert-inventory-content-items.d.ts +38 -0
- package/lib/db-ops/pages/write/insert-inventory-content-items.js +60 -0
- package/lib/db-ops/pages/write/insert-inventory-seeds.d.ts +36 -0
- package/lib/db-ops/pages/write/insert-inventory-seeds.js +48 -0
- package/lib/db-ops/pages/write/insert-inventory-skipped-pages.d.ts +42 -0
- package/lib/db-ops/pages/write/insert-inventory-skipped-pages.js +56 -0
- package/lib/db-ops/pages/write/insert-jsonld.d.ts +17 -0
- package/lib/db-ops/pages/write/insert-jsonld.js +49 -0
- package/lib/db-ops/pages/write/insert-main-content-images.d.ts +20 -0
- package/lib/db-ops/pages/write/insert-main-content-images.js +32 -0
- package/lib/db-ops/pages/write/insert-main-content-tables.d.ts +14 -0
- package/lib/db-ops/pages/write/insert-main-content-tables.js +29 -0
- package/lib/db-ops/pages/write/insert-page.d.ts +56 -0
- package/lib/db-ops/pages/write/insert-page.js +259 -0
- package/lib/db-ops/pages/write/insert-technologies.d.ts +25 -0
- package/lib/db-ops/pages/write/insert-technologies.js +45 -0
- package/lib/db-ops/pages/write/insert-videos.d.ts +14 -0
- package/lib/db-ops/pages/write/insert-videos.js +28 -0
- package/lib/db-ops/pages/write/link-redirect-sources.d.ts +42 -0
- package/lib/db-ops/pages/write/link-redirect-sources.js +101 -0
- package/lib/db-ops/pages/write/record-redirect.d.ts +35 -0
- package/lib/db-ops/pages/write/record-redirect.js +100 -0
- package/lib/db-ops/pages/write/set-skipped-page.d.ts +13 -0
- package/lib/db-ops/pages/write/set-skipped-page.js +24 -0
- package/lib/db-ops/pages/write/update-page.d.ts +40 -0
- package/lib/db-ops/pages/write/update-page.js +419 -0
- package/lib/db-ops/pages/write/write-page-html-blob.d.ts +19 -0
- package/lib/db-ops/pages/write/write-page-html-blob.js +41 -0
- package/lib/db-ops/referrers/get-redirects-for-pages.d.ts +9 -0
- package/lib/db-ops/referrers/get-redirects-for-pages.js +15 -0
- package/lib/db-ops/referrers/get-referrers-of-page.d.ts +17 -0
- package/lib/db-ops/referrers/get-referrers-of-page.js +32 -0
- package/lib/db-ops/referrers/get-referrers-of-resource.d.ts +8 -0
- package/lib/db-ops/referrers/get-referrers-of-resource.js +15 -0
- package/lib/db-ops/resources/build-resource-query.d.ts +25 -0
- package/lib/db-ops/resources/build-resource-query.js +29 -0
- package/lib/db-ops/resources/get-existing-resource-urls.d.ts +9 -0
- package/lib/db-ops/resources/get-existing-resource-urls.js +24 -0
- package/lib/db-ops/resources/get-resource-by-url.d.ts +13 -0
- package/lib/db-ops/resources/get-resource-by-url.js +22 -0
- package/lib/db-ops/resources/get-resource-url-list.d.ts +17 -0
- package/lib/db-ops/resources/get-resource-url-list.js +26 -0
- package/lib/db-ops/resources/get-resources.d.ts +8 -0
- package/lib/db-ops/resources/get-resources.js +11 -0
- package/lib/db-ops/resources/insert-inventory-resources.d.ts +24 -0
- package/lib/db-ops/resources/insert-inventory-resources.js +64 -0
- package/lib/db-ops/resources/insert-resource-referrers.d.ts +15 -0
- package/lib/db-ops/resources/insert-resource-referrers.js +54 -0
- package/lib/db-ops/resources/insert-resource.d.ts +34 -0
- package/lib/db-ops/resources/insert-resource.js +73 -0
- package/lib/db-ops/resources/reconstruct-resource-rows.d.ts +26 -0
- package/lib/db-ops/resources/reconstruct-resource-rows.js +30 -0
- package/lib/db-ops/templates/assign-template-labels.d.ts +60 -0
- package/lib/db-ops/templates/assign-template-labels.js +187 -0
- package/lib/db-ops/templates/replace-page-templates.d.ts +44 -0
- package/lib/db-ops/templates/replace-page-templates.js +174 -0
- package/lib/db-ops/templates/serialize-template-cluster-reason.d.ts +16 -0
- package/lib/db-ops/templates/serialize-template-cluster-reason.js +17 -0
- package/lib/db-ops/templates/types.d.ts +103 -0
- package/lib/db-ops/templates/types.js +1 -0
- package/lib/debug.d.ts +8 -0
- package/lib/debug.js +9 -0
- package/lib/decode-html-blob.d.ts +20 -0
- package/lib/decode-html-blob.js +23 -0
- package/lib/decode-page-html-blob-bytes.d.ts +25 -0
- package/lib/decode-page-html-blob-bytes.js +29 -0
- package/lib/decode-stored-blob-bytes.d.ts +25 -0
- package/lib/decode-stored-blob-bytes.js +47 -0
- package/lib/derive-lineage-from-parent.d.ts +37 -0
- package/lib/derive-lineage-from-parent.js +42 -0
- package/lib/drop-legacy-tables.d.ts +45 -0
- package/lib/drop-legacy-tables.js +56 -0
- package/lib/error-kind/classify-error-kind.d.ts +20 -0
- package/lib/error-kind/classify-error-kind.js +136 -0
- package/lib/error-kind/network-related-error-kinds.d.ts +23 -0
- package/lib/error-kind/network-related-error-kinds.js +28 -0
- package/lib/error-kind/permanent-error-kinds.d.ts +48 -0
- package/lib/error-kind/permanent-error-kinds.js +54 -0
- package/lib/error-kind/types.d.ts +54 -0
- package/lib/error-kind/types.js +1 -0
- package/lib/filesystem/append-text.d.ts +9 -0
- package/lib/filesystem/append-text.js +14 -0
- package/lib/filesystem/copy-dir-sync.d.ts +6 -0
- package/lib/filesystem/copy-dir-sync.js +9 -0
- package/lib/filesystem/copy-dir.d.ts +7 -0
- package/lib/filesystem/copy-dir.js +13 -0
- package/lib/filesystem/copy-file-with-progress.d.ts +20 -0
- package/lib/filesystem/copy-file-with-progress.js +34 -0
- package/lib/filesystem/exists.d.ts +6 -0
- package/lib/filesystem/exists.js +9 -0
- package/lib/filesystem/get-file-list.d.ts +8 -0
- package/lib/filesystem/get-file-list.js +12 -0
- package/lib/filesystem/is-dir.d.ts +6 -0
- package/lib/filesystem/is-dir.js +10 -0
- package/lib/filesystem/mkdir.d.ts +8 -0
- package/lib/filesystem/mkdir.js +15 -0
- package/lib/filesystem/output-binary.d.ts +12 -0
- package/lib/filesystem/output-binary.js +17 -0
- package/lib/filesystem/output-json.d.ts +9 -0
- package/lib/filesystem/output-json.js +14 -0
- package/lib/filesystem/output-text.d.ts +11 -0
- package/lib/filesystem/output-text.js +32 -0
- package/lib/filesystem/parse-pax-path.d.ts +18 -0
- package/lib/filesystem/parse-pax-path.js +45 -0
- package/lib/filesystem/parse-tar-size-field.d.ts +13 -0
- package/lib/filesystem/parse-tar-size-field.js +37 -0
- package/lib/filesystem/peek-tar-top-dir.d.ts +35 -0
- package/lib/filesystem/peek-tar-top-dir.js +215 -0
- package/lib/filesystem/read-json.d.ts +7 -0
- package/lib/filesystem/read-json.js +11 -0
- package/lib/filesystem/read-text.d.ts +6 -0
- package/lib/filesystem/read-text.js +10 -0
- package/lib/filesystem/readline.d.ts +11 -0
- package/lib/filesystem/readline.js +26 -0
- package/lib/filesystem/remove.d.ts +5 -0
- package/lib/filesystem/remove.js +10 -0
- package/lib/filesystem/rename.d.ts +16 -0
- package/lib/filesystem/rename.js +48 -0
- package/lib/filesystem/tar.d.ts +23 -0
- package/lib/filesystem/tar.js +78 -0
- package/lib/filesystem/untar.d.ts +33 -0
- package/lib/filesystem/untar.js +84 -0
- package/lib/get-failed-page-messages.d.ts +56 -0
- package/lib/get-failed-page-messages.js +146 -0
- package/lib/get-json.d.ts +10 -0
- package/lib/get-json.js +25 -0
- package/lib/html-snapshot/match-selector-html.d.ts +25 -0
- package/lib/html-snapshot/match-selector-html.js +28 -0
- package/lib/html-snapshot/match-selector-snapshots.d.ts +33 -0
- package/lib/html-snapshot/match-selector-snapshots.js +33 -0
- package/lib/html-snapshot/search-html-document.d.ts +29 -0
- package/lib/html-snapshot/search-html-document.js +27 -0
- package/lib/html-snapshot/search-html-snapshots.d.ts +36 -0
- package/lib/html-snapshot/search-html-snapshots.js +35 -0
- package/lib/html-snapshot/types.d.ts +111 -0
- package/lib/html-snapshot/types.js +7 -0
- package/lib/html-snapshot/validate-search-pattern.d.ts +15 -0
- package/lib/html-snapshot/validate-search-pattern.js +17 -0
- package/lib/init-schema.d.ts +48 -0
- package/lib/init-schema.js +124 -0
- package/lib/is-inventory-source.d.ts +21 -0
- package/lib/is-inventory-source.js +22 -0
- package/lib/is-within-outage-window.d.ts +49 -0
- package/lib/is-within-outage-window.js +33 -0
- package/lib/isolated-cluster/create-isolated-cluster-run.d.ts +24 -0
- package/lib/isolated-cluster/create-isolated-cluster-run.js +26 -0
- package/lib/libsql-dialect.d.ts +115 -0
- package/lib/libsql-dialect.js +116 -0
- package/lib/limited-page-ids.d.ts +9 -0
- package/lib/limited-page-ids.js +18 -0
- package/lib/meta/assert-compatible-version.d.ts +60 -0
- package/lib/meta/assert-compatible-version.js +93 -0
- package/lib/meta/classify-jsonld-type.d.ts +23 -0
- package/lib/meta/classify-jsonld-type.js +43 -0
- package/lib/meta/compare-semver.d.ts +23 -0
- package/lib/meta/compare-semver.js +51 -0
- package/lib/meta/compute-main-contents-denormalized.d.ts +41 -0
- package/lib/meta/compute-main-contents-denormalized.js +88 -0
- package/lib/meta/compute-page-denormalized.d.ts +20 -0
- package/lib/meta/compute-page-denormalized.js +35 -0
- package/lib/meta/derive-flat-from-meta.d.ts +34 -0
- package/lib/meta/derive-flat-from-meta.js +158 -0
- package/lib/meta/derive-meta-extras.d.ts +20 -0
- package/lib/meta/derive-meta-extras.js +23 -0
- package/lib/meta/extract-tags-for-archive.d.ts +17 -0
- package/lib/meta/extract-tags-for-archive.js +36 -0
- package/lib/meta/parse-main-content-class-list.d.ts +19 -0
- package/lib/meta/parse-main-content-class-list.js +34 -0
- package/lib/meta/summarize-jsonld.d.ts +17 -0
- package/lib/meta/summarize-jsonld.js +29 -0
- package/lib/meta/technologies/combine-technology-confidence.d.ts +46 -0
- package/lib/meta/technologies/combine-technology-confidence.js +93 -0
- package/lib/meta/technologies/convert-legacy-page-tags-to-inserts.d.ts +45 -0
- package/lib/meta/technologies/convert-legacy-page-tags-to-inserts.js +53 -0
- package/lib/meta/technologies/convert-tag-row-to-wappalyzer-signal.d.ts +33 -0
- package/lib/meta/technologies/convert-tag-row-to-wappalyzer-signal.js +46 -0
- package/lib/meta/technologies/extract-technologies-for-archive.d.ts +33 -0
- package/lib/meta/technologies/extract-technologies-for-archive.js +30 -0
- package/lib/meta/technologies/match-js-license-comment-signal.d.ts +21 -0
- package/lib/meta/technologies/match-js-license-comment-signal.js +23 -0
- package/lib/meta/technologies/match-structural-technology-signals.d.ts +26 -0
- package/lib/meta/technologies/match-structural-technology-signals.js +48 -0
- package/lib/meta/technologies/normalize-wappalyzer-entries.d.ts +25 -0
- package/lib/meta/technologies/normalize-wappalyzer-entries.js +35 -0
- package/lib/meta/technologies/technology-signal-definitions.d.ts +44 -0
- package/lib/meta/technologies/technology-signal-definitions.js +60 -0
- package/lib/meta/technologies/types.d.ts +70 -0
- package/lib/meta/technologies/types.js +13 -0
- package/lib/meta/types.d.ts +1490 -0
- package/lib/meta/types.js +65 -0
- package/lib/migrate-content-items-alias-of-id.d.ts +52 -0
- package/lib/migrate-content-items-alias-of-id.js +69 -0
- package/lib/migrate-content-items-dedupe-cap-event-id.d.ts +46 -0
- package/lib/migrate-content-items-dedupe-cap-event-id.js +62 -0
- package/lib/migrate-content-items-is-metadata-only.d.ts +44 -0
- package/lib/migrate-content-items-is-metadata-only.js +60 -0
- package/lib/migrate-entity-tables.d.ts +55 -0
- package/lib/migrate-entity-tables.js +66 -0
- package/lib/migrate-info-created-cwd.d.ts +17 -0
- package/lib/migrate-info-created-cwd.js +36 -0
- package/lib/migrate-info-main-content-selector.d.ts +16 -0
- package/lib/migrate-info-main-content-selector.js +35 -0
- package/lib/migrate-info-request-header-names.d.ts +16 -0
- package/lib/migrate-info-request-header-names.js +35 -0
- package/lib/migrate-info-roots.d.ts +20 -0
- package/lib/migrate-info-roots.js +52 -0
- package/lib/migrate-inventory-runs-to-list-reconcile-runs.d.ts +34 -0
- package/lib/migrate-inventory-runs-to-list-reconcile-runs.js +51 -0
- package/lib/migrate-list-reconcile-runs-exclude-skipped.d.ts +25 -0
- package/lib/migrate-list-reconcile-runs-exclude-skipped.js +44 -0
- package/lib/migrate-list-reconcile-runs-invalid-skipped.d.ts +16 -0
- package/lib/migrate-list-reconcile-runs-invalid-skipped.js +35 -0
- package/lib/migrate-main-contents-columns.d.ts +26 -0
- package/lib/migrate-main-contents-columns.js +61 -0
- package/lib/migrate-page-meta-body-hash.d.ts +44 -0
- package/lib/migrate-page-meta-body-hash.js +63 -0
- package/lib/migrate-page-meta-console-error-count.d.ts +27 -0
- package/lib/migrate-page-meta-console-error-count.js +45 -0
- package/lib/migrate-page-meta-custom-element-count.d.ts +33 -0
- package/lib/migrate-page-meta-custom-element-count.js +51 -0
- package/lib/migrate-page-meta-image-scan.d.ts +27 -0
- package/lib/migrate-page-meta-image-scan.js +52 -0
- package/lib/migrate-page-tags-to-page-technologies.d.ts +39 -0
- package/lib/migrate-page-tags-to-page-technologies.js +79 -0
- package/lib/migrate-ref-tables.d.ts +25 -0
- package/lib/migrate-ref-tables.js +38 -0
- package/lib/native-executor/check-native-executor-config.d.ts +17 -0
- package/lib/native-executor/check-native-executor-config.js +20 -0
- package/lib/page-meta-column-maps.d.ts +32 -0
- package/lib/page-meta-column-maps.js +43 -0
- package/lib/page.d.ts +546 -0
- package/lib/page.js +764 -0
- package/lib/peek-archive-lock.d.ts +40 -0
- package/lib/peek-archive-lock.js +62 -0
- package/lib/populate-entity-tables/collapse-anchor-rows.d.ts +41 -0
- package/lib/populate-entity-tables/collapse-anchor-rows.js +87 -0
- package/lib/populate-entity-tables/derive-dom-path.d.ts +35 -0
- package/lib/populate-entity-tables/derive-dom-path.js +72 -0
- package/lib/populate-entity-tables/is-blob-ref-value.d.ts +16 -0
- package/lib/populate-entity-tables/is-blob-ref-value.js +19 -0
- package/lib/populate-entity-tables/match-images-to-dom-paths.d.ts +66 -0
- package/lib/populate-entity-tables/match-images-to-dom-paths.js +96 -0
- package/lib/populate-entity-tables/populate-anchor-edges.d.ts +33 -0
- package/lib/populate-entity-tables/populate-anchor-edges.js +153 -0
- package/lib/populate-entity-tables/populate-content-items.d.ts +40 -0
- package/lib/populate-entity-tables/populate-content-items.js +141 -0
- package/lib/populate-entity-tables/populate-entities.d.ts +81 -0
- package/lib/populate-entity-tables/populate-entities.js +110 -0
- package/lib/populate-entity-tables/populate-image-items.d.ts +91 -0
- package/lib/populate-entity-tables/populate-image-items.js +226 -0
- package/lib/populate-entity-tables/populate-page-meta.d.ts +33 -0
- package/lib/populate-entity-tables/populate-page-meta.js +267 -0
- package/lib/populate-entity-tables/populate-resource-items.d.ts +22 -0
- package/lib/populate-entity-tables/populate-resource-items.js +114 -0
- package/lib/populate-entity-tables/populate-resource-ref-edges.d.ts +31 -0
- package/lib/populate-entity-tables/populate-resource-ref-edges.js +33 -0
- package/lib/populate-entity-tables/resolve-blob-refs.d.ts +31 -0
- package/lib/populate-entity-tables/resolve-blob-refs.js +100 -0
- package/lib/populate-entity-tables/resolve-content-type-refs.d.ts +22 -0
- package/lib/populate-entity-tables/resolve-content-type-refs.js +27 -0
- package/lib/populate-entity-tables/resolve-header-sets.d.ts +49 -0
- package/lib/populate-entity-tables/resolve-header-sets.js +122 -0
- package/lib/populate-entity-tables/resolve-json-refs.d.ts +25 -0
- package/lib/populate-entity-tables/resolve-json-refs.js +67 -0
- package/lib/populate-entity-tables/resolve-text-refs.d.ts +30 -0
- package/lib/populate-entity-tables/resolve-text-refs.js +61 -0
- package/lib/populate-entity-tables/resolve-url-or-blob-from-maps.d.ts +21 -0
- package/lib/populate-entity-tables/resolve-url-or-blob-from-maps.js +27 -0
- package/lib/populate-entity-tables/resolve-url-refs.d.ts +33 -0
- package/lib/populate-entity-tables/resolve-url-refs.js +60 -0
- package/lib/populate-entity-tables/test-utils/count-rows.d.ts +17 -0
- package/lib/populate-entity-tables/test-utils/count-rows.js +20 -0
- package/lib/populate-entity-tables/test-utils/seed-content-items.d.ts +25 -0
- package/lib/populate-entity-tables/test-utils/seed-content-items.js +42 -0
- package/lib/populate-entity-tables/test-utils/setup-entities-db.d.ts +30 -0
- package/lib/populate-entity-tables/test-utils/setup-entities-db.js +175 -0
- package/lib/populate-entity-tables/types.d.ts +157 -0
- package/lib/populate-entity-tables/types.js +12 -0
- package/lib/populate-entity-tables/upsert-text-refs.d.ts +38 -0
- package/lib/populate-entity-tables/upsert-text-refs.js +78 -0
- package/lib/populate-ref-tables/classify-content-type.d.ts +16 -0
- package/lib/populate-ref-tables/classify-content-type.js +52 -0
- package/lib/populate-ref-tables/compute-content-hash.d.ts +22 -0
- package/lib/populate-ref-tables/compute-content-hash.js +26 -0
- package/lib/populate-ref-tables/compute-header-flags.d.ts +16 -0
- package/lib/populate-ref-tables/compute-header-flags.js +70 -0
- package/lib/populate-ref-tables/content-type-rules.d.ts +38 -0
- package/lib/populate-ref-tables/content-type-rules.js +133 -0
- package/lib/populate-ref-tables/create-header-table-caches.d.ts +25 -0
- package/lib/populate-ref-tables/create-header-table-caches.js +49 -0
- package/lib/populate-ref-tables/data-uri-url-refs-limit.d.ts +15 -0
- package/lib/populate-ref-tables/data-uri-url-refs-limit.js +15 -0
- package/lib/populate-ref-tables/decode-data-uri.d.ts +21 -0
- package/lib/populate-ref-tables/decode-data-uri.js +126 -0
- package/lib/populate-ref-tables/decompose-header-set.d.ts +29 -0
- package/lib/populate-ref-tables/decompose-header-set.js +157 -0
- package/lib/populate-ref-tables/decompose-url.d.ts +25 -0
- package/lib/populate-ref-tables/decompose-url.js +70 -0
- package/lib/populate-ref-tables/header-stability.d.ts +19 -0
- package/lib/populate-ref-tables/header-stability.js +22 -0
- package/lib/populate-ref-tables/header-value-cache-key.d.ts +17 -0
- package/lib/populate-ref-tables/header-value-cache-key.js +19 -0
- package/lib/populate-ref-tables/normalize-mime.d.ts +24 -0
- package/lib/populate-ref-tables/normalize-mime.js +36 -0
- package/lib/populate-ref-tables/populate-blob-refs.d.ts +38 -0
- package/lib/populate-ref-tables/populate-blob-refs.js +134 -0
- package/lib/populate-ref-tables/populate-content-type-refs.d.ts +27 -0
- package/lib/populate-ref-tables/populate-content-type-refs.js +70 -0
- package/lib/populate-ref-tables/populate-header-tables.d.ts +35 -0
- package/lib/populate-ref-tables/populate-header-tables.js +80 -0
- package/lib/populate-ref-tables/populate-json-refs.d.ts +29 -0
- package/lib/populate-ref-tables/populate-json-refs.js +101 -0
- package/lib/populate-ref-tables/populate-refs.d.ts +51 -0
- package/lib/populate-ref-tables/populate-refs.js +62 -0
- package/lib/populate-ref-tables/populate-text-refs.d.ts +32 -0
- package/lib/populate-ref-tables/populate-text-refs.js +133 -0
- package/lib/populate-ref-tables/populate-url-refs.d.ts +28 -0
- package/lib/populate-ref-tables/populate-url-refs.js +148 -0
- package/lib/populate-ref-tables/test-utils/count-rows.d.ts +15 -0
- package/lib/populate-ref-tables/test-utils/count-rows.js +17 -0
- package/lib/populate-ref-tables/types.d.ts +197 -0
- package/lib/populate-ref-tables/types.js +7 -0
- package/lib/populate-ref-tables/upsert-one-header-set.d.ts +34 -0
- package/lib/populate-ref-tables/upsert-one-header-set.js +208 -0
- package/lib/populate-ref-tables/volatile-header-names.d.ts +20 -0
- package/lib/populate-ref-tables/volatile-header-names.js +33 -0
- package/lib/redirect-table.d.ts +9 -0
- package/lib/redirect-table.js +28 -0
- package/lib/release-finished-statements.d.ts +42 -0
- package/lib/release-finished-statements.js +45 -0
- package/lib/resolve-redirect-chain.d.ts +33 -0
- package/lib/resolve-redirect-chain.js +27 -0
- package/lib/resource.d.ts +46 -0
- package/lib/resource.js +62 -0
- package/lib/retarget-legacy-fk-tables.d.ts +61 -0
- package/lib/retarget-legacy-fk-tables.js +137 -0
- package/lib/safe-path.d.ts +9 -0
- package/lib/safe-path.js +17 -0
- package/lib/scope/build-scope-map.d.ts +21 -0
- package/lib/scope/build-scope-map.js +32 -0
- package/lib/scope/find-scope-entry.d.ts +25 -0
- package/lib/scope/find-scope-entry.js +45 -0
- package/lib/template-classification/classify-archive-page-templates.d.ts +42 -0
- package/lib/template-classification/classify-archive-page-templates.js +70 -0
- package/lib/template-classification/classify-page-templates.d.ts +33 -0
- package/lib/template-classification/classify-page-templates.js +60 -0
- package/lib/template-classification/collect-page-ids-with-html.d.ts +24 -0
- package/lib/template-classification/collect-page-ids-with-html.js +30 -0
- package/lib/template-classification/collect-page-stylesheet-urls.d.ts +32 -0
- package/lib/template-classification/collect-page-stylesheet-urls.js +132 -0
- package/lib/template-classification/create-content-root-hint.d.ts +41 -0
- package/lib/template-classification/create-content-root-hint.js +51 -0
- package/lib/template-classification/create-page-cluster-factory.d.ts +36 -0
- package/lib/template-classification/create-page-cluster-factory.js +52 -0
- package/lib/template-classification/load-page-cluster-candidates.d.ts +32 -0
- package/lib/template-classification/load-page-cluster-candidates.js +93 -0
- package/lib/template-classification/page-cluster/resolve-page-cluster-keys.d.ts +34 -0
- package/lib/template-classification/page-cluster/resolve-page-cluster-keys.js +236 -0
- package/lib/template-classification/page-cluster/types.d.ts +123 -0
- package/lib/template-classification/page-cluster/types.js +1 -0
- package/lib/template-classification/types.d.ts +234 -0
- package/lib/template-classification/types.js +1 -0
- package/lib/test-utils/build-full-schema-test-db.d.ts +34 -0
- package/lib/test-utils/build-full-schema-test-db.js +74 -0
- package/lib/test-utils/compare-strings.d.ts +12 -0
- package/lib/test-utils/compare-strings.js +14 -0
- package/lib/test-utils/create-legacy-analysis-tables.d.ts +13 -0
- package/lib/test-utils/create-legacy-analysis-tables.js +37 -0
- package/lib/test-utils/fk-parent-tables.d.ts +15 -0
- package/lib/test-utils/fk-parent-tables.js +19 -0
- package/lib/test-utils/seed-content-item.d.ts +35 -0
- package/lib/test-utils/seed-content-item.js +42 -0
- package/lib/test-utils/setup-legacy-fk-db.d.ts +34 -0
- package/lib/test-utils/setup-legacy-fk-db.js +271 -0
- package/lib/transfer/attach-source-database.d.ts +31 -0
- package/lib/transfer/attach-source-database.js +40 -0
- package/lib/transfer/build-split-config.d.ts +30 -0
- package/lib/transfer/build-split-config.js +42 -0
- package/lib/transfer/clear-replaced-page-rows.d.ts +23 -0
- package/lib/transfer/clear-replaced-page-rows.js +61 -0
- package/lib/transfer/concat-archives.d.ts +55 -0
- package/lib/transfer/concat-archives.js +95 -0
- package/lib/transfer/concat-source-transfer-phases.d.ts +10 -0
- package/lib/transfer/concat-source-transfer-phases.js +17 -0
- package/lib/transfer/content-item-rank-sql.d.ts +21 -0
- package/lib/transfer/content-item-rank-sql.js +23 -0
- package/lib/transfer/copy-anchor-edges.d.ts +28 -0
- package/lib/transfer/copy-anchor-edges.js +42 -0
- package/lib/transfer/copy-dictionaries-for-concat.d.ts +39 -0
- package/lib/transfer/copy-dictionaries-for-concat.js +81 -0
- package/lib/transfer/copy-dictionaries-for-split.d.ts +29 -0
- package/lib/transfer/copy-dictionaries-for-split.js +224 -0
- package/lib/transfer/copy-header-set-children.d.ts +27 -0
- package/lib/transfer/copy-header-set-children.js +51 -0
- package/lib/transfer/copy-image-items.d.ts +21 -0
- package/lib/transfer/copy-image-items.js +62 -0
- package/lib/transfer/copy-inventory-source-lists.d.ts +30 -0
- package/lib/transfer/copy-inventory-source-lists.js +61 -0
- package/lib/transfer/copy-journal-tables.d.ts +30 -0
- package/lib/transfer/copy-journal-tables.js +56 -0
- package/lib/transfer/copy-page-console-logs.d.ts +16 -0
- package/lib/transfer/copy-page-console-logs.js +27 -0
- package/lib/transfer/copy-page-html-blobs.d.ts +31 -0
- package/lib/transfer/copy-page-html-blobs.js +43 -0
- package/lib/transfer/copy-page-meta.d.ts +27 -0
- package/lib/transfer/copy-page-meta.js +68 -0
- package/lib/transfer/copy-page-template-clusters.d.ts +31 -0
- package/lib/transfer/copy-page-template-clusters.js +56 -0
- package/lib/transfer/copy-page-template-labels.d.ts +34 -0
- package/lib/transfer/copy-page-template-labels.js +88 -0
- package/lib/transfer/copy-resource-items.d.ts +29 -0
- package/lib/transfer/copy-resource-items.js +89 -0
- package/lib/transfer/copy-resource-ref-edges.d.ts +20 -0
- package/lib/transfer/copy-resource-ref-edges.js +33 -0
- package/lib/transfer/copy-simple-page-scoped-tables.d.ts +15 -0
- package/lib/transfer/copy-simple-page-scoped-tables.js +34 -0
- package/lib/transfer/count-external-pages-in-scope.d.ts +26 -0
- package/lib/transfer/count-external-pages-in-scope.js +40 -0
- package/lib/transfer/create-transfer-temp-tables.d.ts +30 -0
- package/lib/transfer/create-transfer-temp-tables.js +59 -0
- package/lib/transfer/dictionary-copy-specs.d.ts +32 -0
- package/lib/transfer/dictionary-copy-specs.js +28 -0
- package/lib/transfer/drop-transfer-temp-tables.d.ts +9 -0
- package/lib/transfer/drop-transfer-temp-tables.js +18 -0
- package/lib/transfer/flatten-redirect-chains.d.ts +44 -0
- package/lib/transfer/flatten-redirect-chains.js +74 -0
- package/lib/transfer/insert-content-items.d.ts +35 -0
- package/lib/transfer/insert-content-items.js +116 -0
- package/lib/transfer/list-transfer-columns.d.ts +35 -0
- package/lib/transfer/list-transfer-columns.js +45 -0
- package/lib/transfer/mark-in-scope-content-items.d.ts +31 -0
- package/lib/transfer/mark-in-scope-content-items.js +54 -0
- package/lib/transfer/merge-archive-configs.d.ts +51 -0
- package/lib/transfer/merge-archive-configs.js +85 -0
- package/lib/transfer/plan-content-items-for-concat.d.ts +33 -0
- package/lib/transfer/plan-content-items-for-concat.js +51 -0
- package/lib/transfer/plan-content-items-for-split.d.ts +42 -0
- package/lib/transfer/plan-content-items-for-split.js +83 -0
- package/lib/transfer/plan-resource-items.d.ts +40 -0
- package/lib/transfer/plan-resource-items.js +69 -0
- package/lib/transfer/quote-transfer-identifier.d.ts +18 -0
- package/lib/transfer/quote-transfer-identifier.js +20 -0
- package/lib/transfer/reclassify-resource-externality.d.ts +37 -0
- package/lib/transfer/reclassify-resource-externality.js +66 -0
- package/lib/transfer/remap-redirect-dest-ids.d.ts +34 -0
- package/lib/transfer/remap-redirect-dest-ids.js +51 -0
- package/lib/transfer/replace-content-items.d.ts +39 -0
- package/lib/transfer/replace-content-items.js +83 -0
- package/lib/transfer/simple-page-scoped-tables.d.ts +25 -0
- package/lib/transfer/simple-page-scoped-tables.js +38 -0
- package/lib/transfer/split-archive.d.ts +55 -0
- package/lib/transfer/split-archive.js +98 -0
- package/lib/transfer/split-source-transfer-phases.d.ts +11 -0
- package/lib/transfer/split-source-transfer-phases.js +18 -0
- package/lib/transfer/transfer-action.d.ts +34 -0
- package/lib/transfer/transfer-action.js +32 -0
- package/lib/transfer/transfer-archive-rows.d.ts +65 -0
- package/lib/transfer/transfer-archive-rows.js +156 -0
- package/lib/transfer/transfer-dictionary-tables.d.ts +12 -0
- package/lib/transfer/transfer-dictionary-tables.js +22 -0
- package/lib/transfer/transfer-post-phases.d.ts +10 -0
- package/lib/transfer/transfer-post-phases.js +13 -0
- package/lib/transfer/transfer-source-alias.d.ts +9 -0
- package/lib/transfer/transfer-source-alias.js +9 -0
- package/lib/transfer/types.d.ts +111 -0
- package/lib/transfer/types.js +35 -0
- package/lib/types.d.ts +772 -0
- package/lib/types.js +1 -0
- package/lib/url-alias/compute-tier-a-alias-key.d.ts +44 -0
- package/lib/url-alias/compute-tier-a-alias-key.js +47 -0
- package/lib/url-alias/compute-tier-b-alias-key.d.ts +23 -0
- package/lib/url-alias/compute-tier-b-alias-key.js +26 -0
- package/lib/url-alias/group-alias-candidates.d.ts +27 -0
- package/lib/url-alias/group-alias-candidates.js +29 -0
- package/lib/url-alias/types.d.ts +12 -0
- package/lib/url-alias/types.js +1 -0
- package/lib/url-pattern/compute-shape-key.d.ts +58 -0
- package/lib/url-pattern/compute-shape-key.js +129 -0
- package/lib/url-pattern/decompose-url.d.ts +33 -0
- package/lib/url-pattern/decompose-url.js +79 -0
- package/lib/url-pattern/reconstruct-url.d.ts +15 -0
- package/lib/url-pattern/reconstruct-url.js +33 -0
- package/lib/url-sort/compute-url-sort-ranks.d.ts +30 -0
- package/lib/url-sort/compute-url-sort-ranks.js +33 -0
- package/lib/utils/array/each-splitted.d.ts +10 -0
- package/lib/utils/array/each-splitted.js +14 -0
- package/lib/utils/debug.d.ts +8 -0
- package/lib/utils/debug.js +8 -0
- package/lib/utils/error/emit-error-with-retry.d.ts +40 -0
- package/lib/utils/error/emit-error-with-retry.js +44 -0
- package/lib/utils/error/emit-error.d.ts +39 -0
- package/lib/utils/error/emit-error.js +41 -0
- package/lib/utils/object/parse-response-headers.d.ts +12 -0
- package/lib/utils/object/parse-response-headers.js +26 -0
- package/lib/utils/object/redact-request-headers.d.ts +15 -0
- package/lib/utils/object/redact-request-headers.js +27 -0
- package/lib/utils/types/types.d.ts +383 -0
- package/lib/utils/types/types.js +1 -0
- package/lib/verify-migration/capture-rejection.d.ts +24 -0
- package/lib/verify-migration/capture-rejection.js +31 -0
- package/lib/verify-migration/check-anchor-edges-count.d.ts +34 -0
- package/lib/verify-migration/check-anchor-edges-count.js +72 -0
- package/lib/verify-migration/check-anchor-edges-sum.d.ts +13 -0
- package/lib/verify-migration/check-anchor-edges-sum.js +27 -0
- package/lib/verify-migration/check-content-items-count.d.ts +16 -0
- package/lib/verify-migration/check-content-items-count.js +30 -0
- package/lib/verify-migration/check-content-type-preservation.d.ts +22 -0
- package/lib/verify-migration/check-content-type-preservation.js +40 -0
- package/lib/verify-migration/check-foreign-key-integrity.d.ts +31 -0
- package/lib/verify-migration/check-foreign-key-integrity.js +47 -0
- package/lib/verify-migration/check-image-items-count.d.ts +12 -0
- package/lib/verify-migration/check-image-items-count.js +26 -0
- package/lib/verify-migration/check-page-meta-count.d.ts +15 -0
- package/lib/verify-migration/check-page-meta-count.js +31 -0
- package/lib/verify-migration/check-reader-parity.d.ts +23 -0
- package/lib/verify-migration/check-reader-parity.js +182 -0
- package/lib/verify-migration/check-resource-items-count.d.ts +17 -0
- package/lib/verify-migration/check-resource-items-count.js +33 -0
- package/lib/verify-migration/check-url-round-trip.d.ts +43 -0
- package/lib/verify-migration/check-url-round-trip.js +112 -0
- package/lib/verify-migration/types.d.ts +70 -0
- package/lib/verify-migration/types.js +63 -0
- package/lib/verify-migration/verify-migration.d.ts +41 -0
- package/lib/verify-migration/verify-migration.js +120 -0
- package/lib/with-bulk-write-pragmas.d.ts +51 -0
- package/lib/with-bulk-write-pragmas.js +132 -0
- package/lib/write-queue.d.ts +32 -0
- package/lib/write-queue.js +55 -0
- package/package.json +350 -3
package/lib/archive.js
ADDED
|
@@ -0,0 +1,1055 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ArchiveAccessor } from './archive-accessor.js';
|
|
3
|
+
import { acquireArchiveLock } from './archive-lock.js';
|
|
4
|
+
import { computeArchiveCacheKey } from './cache/compute-archive-cache-key.js';
|
|
5
|
+
import { extractArchiveToCache } from './cache/extract-archive-to-cache.js';
|
|
6
|
+
import { getArchiveCacheRoot } from './cache/get-archive-cache-root.js';
|
|
7
|
+
import { resolveArchiveCacheDir } from './cache/resolve-archive-cache-dir.js';
|
|
8
|
+
import { Database } from './database.js';
|
|
9
|
+
import { dbLog, log, saveLog } from './debug.js';
|
|
10
|
+
import { appendText } from './filesystem/append-text.js';
|
|
11
|
+
import { exists } from './filesystem/exists.js';
|
|
12
|
+
import { isDir } from './filesystem/is-dir.js';
|
|
13
|
+
import { outputBinary } from './filesystem/output-binary.js';
|
|
14
|
+
import { peekTarTopDir } from './filesystem/peek-tar-top-dir.js';
|
|
15
|
+
import { remove } from './filesystem/remove.js';
|
|
16
|
+
import { rename } from './filesystem/rename.js';
|
|
17
|
+
import { tar } from './filesystem/tar.js';
|
|
18
|
+
import { untar } from './filesystem/untar.js';
|
|
19
|
+
import { safePath } from './safe-path.js';
|
|
20
|
+
import { redactRequestHeaders } from './utils/object/redact-request-headers.js';
|
|
21
|
+
/**
|
|
22
|
+
* Main archive class for creating, opening, resuming, and writing Nitpicker
|
|
23
|
+
* archive files (`.nitpicker`).
|
|
24
|
+
*
|
|
25
|
+
* An Archive wraps a SQLite database into a tar archive. HTML bodies live
|
|
26
|
+
* inside the same DB as zstd-compressed BLOBs (see #75), so `db.sqlite` is
|
|
27
|
+
* normally the tar's only entry — but it is not the only entry the format
|
|
28
|
+
* allows: {@link Archive.saveInventorySourceList} (a saved `--inventory`
|
|
29
|
+
* source list) adds plain files alongside it. It extends
|
|
30
|
+
* {@link ArchiveAccessor} to provide read access to stored data.
|
|
31
|
+
*
|
|
32
|
+
* Use the static factory methods ({@link Archive.create}, {@link Archive.open},
|
|
33
|
+
* {@link Archive.resume}, {@link Archive.connect}) to obtain instances.
|
|
34
|
+
* The constructor is private. Implements `Symbol.asyncDispose` (inherited
|
|
35
|
+
* from {@link ArchiveAccessor}) so callers can use `await using` instead of
|
|
36
|
+
* a manual `try`/`finally` around {@link close}.
|
|
37
|
+
* @example
|
|
38
|
+
* await using archive = await Archive.create({ filePath: '/path/to/site.nitpicker' });
|
|
39
|
+
* await archive.setConfig(config);
|
|
40
|
+
* const pageId = await archive.setPage(pageData);
|
|
41
|
+
* // Writes the `.nitpicker` tar (if absent), removes tmpDir, releases the
|
|
42
|
+
* // lock — all on scope exit, whether by fallthrough or thrown error.
|
|
43
|
+
*/
|
|
44
|
+
export default class Archive extends ArchiveAccessor {
|
|
45
|
+
/**
|
|
46
|
+
* Promise tracking an in-progress {@link Archive.close} (or
|
|
47
|
+
* {@link Archive.releaseHandle}). Acts as the override's idempotency
|
|
48
|
+
* guard so a second call — e.g. from a signal handler racing the
|
|
49
|
+
* primary teardown — does not re-enter the destructive prologue
|
|
50
|
+
* (write/remove) on a half-mutated state.
|
|
51
|
+
*/
|
|
52
|
+
#closeOnce = null;
|
|
53
|
+
/** The SQLite database instance for reading and writing crawl data. */
|
|
54
|
+
#db;
|
|
55
|
+
/** Absolute path to the output `.nitpicker` archive file. */
|
|
56
|
+
#filePath;
|
|
57
|
+
/** Lock release function held while the writer owns the archive. */
|
|
58
|
+
#releaseLock;
|
|
59
|
+
/** Absolute path to the temporary working directory containing the SQLite DB. */
|
|
60
|
+
#tmpDir;
|
|
61
|
+
/**
|
|
62
|
+
* The absolute file path of the archive (`.nitpicker` file).
|
|
63
|
+
*/
|
|
64
|
+
get filePath() {
|
|
65
|
+
return this.#filePath;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The intermediate directory `Archive.write()` produces by renaming
|
|
69
|
+
* `tmpDir` before tarring (`{cwd}/{archiveName}`). Exposed so the
|
|
70
|
+
* manager can include it in its cleanup-on-failure path: if `tar()`
|
|
71
|
+
* fails after the rename, this directory is orphaned and would
|
|
72
|
+
* otherwise be invisible to a `rmSync(tmpDir)` recovery.
|
|
73
|
+
*/
|
|
74
|
+
get renamedDir() {
|
|
75
|
+
return path.resolve(path.dirname(this.#filePath), path.basename(this.#filePath, path.extname(this.#filePath)));
|
|
76
|
+
}
|
|
77
|
+
constructor(filePath, tmpDir, db, releaseLock) {
|
|
78
|
+
super(tmpDir, db);
|
|
79
|
+
this.#filePath = filePath;
|
|
80
|
+
this.#tmpDir = tmpDir;
|
|
81
|
+
this.#db = db;
|
|
82
|
+
this.#releaseLock = releaseLock;
|
|
83
|
+
log('create instance: %O', {
|
|
84
|
+
filePath,
|
|
85
|
+
tmpDir,
|
|
86
|
+
});
|
|
87
|
+
this.#db.on('error', (e) => {
|
|
88
|
+
void this.emit('error', e);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @deprecated This method is no longer functional.
|
|
93
|
+
*/
|
|
94
|
+
abort() { }
|
|
95
|
+
/**
|
|
96
|
+
* Adds onto the `rejected_count` of a shape's `dedupe_cap_events` row,
|
|
97
|
+
* looked up by `shape_key` rather than `id` — used for a shape that
|
|
98
|
+
* capped in an earlier session (preloaded into `DedupeCapTracker`'s
|
|
99
|
+
* sticky set) and so has no event id from the current session.
|
|
100
|
+
*
|
|
101
|
+
* Thin facade over {@link Database.accumulateDedupeCapRejectedCount}.
|
|
102
|
+
* @param shapeKey - The capped shape whose rejection count to accumulate.
|
|
103
|
+
* @param rejectedCount - Additional anchors rejected for this shape in the current session.
|
|
104
|
+
*/
|
|
105
|
+
async accumulateDedupeCapRejectedCount(shapeKey, rejectedCount) {
|
|
106
|
+
dbLog('Accumulate dedupe cap rejected count shapeKey=%s rejectedCount=%d', shapeKey, rejectedCount);
|
|
107
|
+
return await this.#db.accumulateDedupeCapRejectedCount(shapeKey, rejectedCount);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Records a crawler-level error to both the human-readable `error.log` (full
|
|
111
|
+
* stack, for debugging) and the structured `crawl_errors` table (queryable,
|
|
112
|
+
* for the `error-kinds` analysis). The cause is not classified here — it is
|
|
113
|
+
* derived on read. `error.log` keeps the full stack while `crawl_errors`
|
|
114
|
+
* stores `error.message`; both normally carry the same cause token (e.g.
|
|
115
|
+
* `ENOTFOUND`), so classification agrees across the two — only an error whose
|
|
116
|
+
* cause lives solely in deeper stack frames could differ.
|
|
117
|
+
* @param error - The crawler error object containing process and URL information.
|
|
118
|
+
*/
|
|
119
|
+
async addError(error) {
|
|
120
|
+
const logFile = path.resolve(this.#tmpDir, 'error.log');
|
|
121
|
+
await appendText(logFile, `[${error.pid}(${error.isMainProcess ? 'main' : 'sub'})] ${error.url} ${error.error.stack ?? error.error}`);
|
|
122
|
+
await this.#db.insertCrawlError(error.url, error.error.message, error.isExternal);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Records a partial scrape failure against the page identified by `url`.
|
|
126
|
+
*
|
|
127
|
+
* The corresponding `pages` row is created on demand (or matched if it
|
|
128
|
+
* already exists), so the call works even if the page's normal data has
|
|
129
|
+
* not been written yet.
|
|
130
|
+
* @param url - URL of the affected page.
|
|
131
|
+
* @param phase - Scrape phase name (typically `'retryExhausted'`).
|
|
132
|
+
* @param message - Human-readable failure message.
|
|
133
|
+
* @param isExternal - Whether the URL is external. Defaults to `false`.
|
|
134
|
+
*/
|
|
135
|
+
async addPageError(url, phase, message, isExternal = false) {
|
|
136
|
+
dbLog('Add page error: %s [%s]', url, phase);
|
|
137
|
+
await this.#db.insertPageError(url, phase, message, isExternal);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Closes an outage row by stamping `ended_at` — a no-op if already closed.
|
|
141
|
+
*
|
|
142
|
+
* Thin facade over {@link Database.closeNetworkOutage}.
|
|
143
|
+
* @param id - The `network_outages.id` to close.
|
|
144
|
+
* @param endedAt - Epoch ms the outage is considered to have ended.
|
|
145
|
+
*/
|
|
146
|
+
async closeNetworkOutage(id, endedAt) {
|
|
147
|
+
dbLog('Close network outage id=%d endedAt=%d', id, endedAt);
|
|
148
|
+
return await this.#db.closeNetworkOutage(id, endedAt);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Finalizes a `dedupe_cap_events` row by stamping `rejected_count` — a
|
|
152
|
+
* no-op if already finalized.
|
|
153
|
+
*
|
|
154
|
+
* Thin facade over {@link Database.finalizeDedupeCapEvent}.
|
|
155
|
+
* @param id - The `dedupe_cap_events.id` to finalize.
|
|
156
|
+
* @param rejectedCount - Number of anchors rejected for this shape after it capped.
|
|
157
|
+
*/
|
|
158
|
+
async finalizeDedupeCapEvent(id, rejectedCount) {
|
|
159
|
+
dbLog('Finalize dedupe cap event id=%d rejectedCount=%d', id, rejectedCount);
|
|
160
|
+
return await this.#db.finalizeDedupeCapEvent(id, rejectedCount);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Retrieves the current crawling state, including lists of scraped and pending URLs.
|
|
164
|
+
* @returns An object with `scraped`, `pending`, and `pendingMetadataOnly` URL arrays.
|
|
165
|
+
*/
|
|
166
|
+
async getCrawlingState() {
|
|
167
|
+
return this.#db.getCrawlingState();
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Retrieves the `info.createdCwd` value stamped when this stub was
|
|
171
|
+
* created — see {@link Archive.resume} for how it is used.
|
|
172
|
+
* @returns The recorded cwd, or `null` if never stamped.
|
|
173
|
+
*/
|
|
174
|
+
async getCreatedCwd() {
|
|
175
|
+
return this.#db.getCreatedCwd();
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Return the subset of `urls` that already exist as `pages.url`. Used by
|
|
179
|
+
* `CrawlerOrchestrator.inventory` to filter the user-supplied URL list
|
|
180
|
+
* down to "URLs that are NOT yet in the archive" — only those reach the
|
|
181
|
+
* HEAD / scrape pipeline. Existing URLs are skipped to keep the second
|
|
182
|
+
* (and N-th) `--inventory` pass non-destructive.
|
|
183
|
+
* @param urls - Candidate URLs in `withoutHashAndAuth` form.
|
|
184
|
+
* @returns URLs already present in `pages`.
|
|
185
|
+
*/
|
|
186
|
+
async getExistingPageUrls(urls) {
|
|
187
|
+
return this.#db.getExistingPageUrls(urls);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Return the subset of `urls` that already exist as `resources.url`. See
|
|
191
|
+
* {@link Archive.getExistingPageUrls} — the resource-side counterpart used
|
|
192
|
+
* by inventory mode to skip URLs that are already tracked as
|
|
193
|
+
* sub-resources.
|
|
194
|
+
* @param urls - Candidate URLs.
|
|
195
|
+
* @returns URLs already present in `resources`.
|
|
196
|
+
*/
|
|
197
|
+
async getExistingResourceUrls(urls) {
|
|
198
|
+
return this.#db.getExistingResourceUrls(urls);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Look up the `source` column of a single page row by its URL key. Thin
|
|
202
|
+
* facade over {@link Database.getPageSourceByUrl} — exposes the lookup
|
|
203
|
+
* to the orchestrator so it can inject a `PageSourceLookup` into the
|
|
204
|
+
* Crawler for sub-resource lineage propagation on `--resume` /
|
|
205
|
+
* `--retry-failed` sessions.
|
|
206
|
+
* @param url - URL key in `url.withoutHashAndAuth` form.
|
|
207
|
+
* @returns The recorded `source`, or `undefined` when no row exists.
|
|
208
|
+
*/
|
|
209
|
+
async getPageSourceByUrl(url) {
|
|
210
|
+
return this.#db.getPageSourceByUrl(url);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Retrieves a single recorded sub-resource by its URL.
|
|
214
|
+
* @param urls - URL candidates to match against the stored resource URL.
|
|
215
|
+
* @returns The raw resource row, or `null` if none match.
|
|
216
|
+
*/
|
|
217
|
+
async getResourceByUrl(urls) {
|
|
218
|
+
return this.#db.getResourceByUrl(urls);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Counts the number of pages already scraped as crawl targets in the archive.
|
|
222
|
+
*
|
|
223
|
+
* Lets the crawler initialize its session-progress counter on resume so the
|
|
224
|
+
* displayed HTML-page count accounts for previously-rendered pages.
|
|
225
|
+
* @returns The count of pages with `isTarget = 1` and `scraped = 1`.
|
|
226
|
+
*/
|
|
227
|
+
async getScrapedHtmlPageCount() {
|
|
228
|
+
return this.#db.getScrapedHtmlPageCount();
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Retrieves the base URL of the crawl session from the archive database.
|
|
232
|
+
* @returns The base URL string.
|
|
233
|
+
*/
|
|
234
|
+
async getUrl() {
|
|
235
|
+
return this.#db.getBaseUrl();
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Appends one row (`rejected_count = NULL`) to the `dedupe_cap_events`
|
|
239
|
+
* journal.
|
|
240
|
+
*
|
|
241
|
+
* Thin facade over {@link Database.insertDedupeCapEvent}.
|
|
242
|
+
* @param params - The newly-capped shape's fields to record.
|
|
243
|
+
* @returns The autoincremented `id` of the inserted row.
|
|
244
|
+
*/
|
|
245
|
+
async insertDedupeCapEvent(params) {
|
|
246
|
+
dbLog('Insert dedupe cap event: shapeKey=%s', params.shapeKey);
|
|
247
|
+
return await this.#db.insertDedupeCapEvent(params);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Pre-insert inventory non-HTML URLs as `source='inventory-seed'`
|
|
251
|
+
* placeholders in the `resources` table — the non-HTML counterpart of
|
|
252
|
+
* {@link Archive.insertInventorySeeds}. Rows are committed in chunked
|
|
253
|
+
* bulk inserts (500 per round-trip) rather than per-URL awaits — a
|
|
254
|
+
* per-URL loop would keep a 50k-URL inventory list inside the `.bak`
|
|
255
|
+
* window for minutes instead of seconds.
|
|
256
|
+
*
|
|
257
|
+
* Thin facade over {@link Database.insertInventoryResources}.
|
|
258
|
+
* `ExURL.href` is the storage key for `resources.url` (matches what
|
|
259
|
+
* `insertResource` writes for the per-URL path); we normalise here so
|
|
260
|
+
* the orchestrator stays decoupled from the storage form.
|
|
261
|
+
* @param urls - Non-HTML inventory URLs to record. No-op when empty.
|
|
262
|
+
*/
|
|
263
|
+
async insertInventoryResources(urls) {
|
|
264
|
+
if (urls.length === 0) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
dbLog('Insert inventory resources: %d URL(s)', urls.length);
|
|
268
|
+
await this.#db.insertInventoryResources(urls.map((u) => u.href));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Pre-insert inventory HTML seeds as `scraped=0`, `source='inventory-seed'`
|
|
272
|
+
* placeholder pages so the URL is durably tracked in the archive **before**
|
|
273
|
+
* the scrape phase starts. Thin facade over
|
|
274
|
+
* {@link Database.insertInventorySeeds} — see that method's JSDoc for the
|
|
275
|
+
* Ctrl+C-tolerance rationale and the `getCrawlingState` interaction.
|
|
276
|
+
*
|
|
277
|
+
* `ExURL` inputs are normalised to `withoutHashAndAuth` here so the storage
|
|
278
|
+
* key matches what `resolveContentItemId` writes for crawled rows, keeping the
|
|
279
|
+
* crawled-wins downgrade and the existing-URL filter (`getExistingPageUrls`)
|
|
280
|
+
* lookups consistent.
|
|
281
|
+
* @param urls - HTML seed URLs to pre-insert. No-op when empty.
|
|
282
|
+
*/
|
|
283
|
+
async insertInventorySeeds(urls) {
|
|
284
|
+
if (urls.length === 0) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
dbLog('Insert inventory seeds: %d URL(s)', urls.length);
|
|
288
|
+
await this.#db.insertInventorySeeds(urls.map((u) => u.withoutHashAndAuth));
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Records exclude-matched inventory URLs as terminal skipped pages —
|
|
292
|
+
* the same `is_skipped=1, skip_reason='excluded'` state the normal
|
|
293
|
+
* crawl's fetch-time gate writes for link-discovered excluded URLs,
|
|
294
|
+
* labelled `source='inventory-seed'`. Thin facade over
|
|
295
|
+
* {@link Database.insertInventorySkippedPages} — see the underlying
|
|
296
|
+
* op's JSDoc for the parity rationale and crawled-wins safety.
|
|
297
|
+
*
|
|
298
|
+
* `ExURL` inputs are normalised to `withoutHashAndAuth` here for the
|
|
299
|
+
* same storage-key consistency reason as {@link insertInventorySeeds}.
|
|
300
|
+
* @param urls - Exclude-matched URLs to record. No-op when empty.
|
|
301
|
+
*/
|
|
302
|
+
async insertInventorySkippedPages(urls) {
|
|
303
|
+
if (urls.length === 0) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
dbLog('Insert inventory skipped pages: %d URL(s)', urls.length);
|
|
307
|
+
await this.#db.insertInventorySkippedPages(urls.map((u) => u.withoutHashAndAuth));
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Appends one open row to the `network_outages` journal.
|
|
311
|
+
*
|
|
312
|
+
* Thin facade over {@link Database.insertNetworkOutage} — see
|
|
313
|
+
* {@link recordListReconcileRun}'s docstring for why this indirection exists.
|
|
314
|
+
* @param params - The confirmed-outage fields to record.
|
|
315
|
+
* @returns The autoincremented `id` of the inserted row.
|
|
316
|
+
*/
|
|
317
|
+
async insertNetworkOutage(params) {
|
|
318
|
+
dbLog('Insert network outage: startedAt=%d probeHost=%s', params.startedAt, params.probeHost);
|
|
319
|
+
return await this.#db.insertNetworkOutage(params);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Every previously-scraped internal page's raw fields, in the shape
|
|
323
|
+
* `buildDedupeCapObservation` needs to replay this archive's prior
|
|
324
|
+
* `DedupeCapTracker` observations (not just its confirmed-capped
|
|
325
|
+
* shapes — see {@link listDedupeCapShapeKeys}) into a fresh tracker
|
|
326
|
+
* instance on `--resume` / `--append` / `--retry-failed` /
|
|
327
|
+
* `--inventory` / `--recrawl`. Without this, every not-yet-capped
|
|
328
|
+
* shape's Misra-Gries counter restarts at 0 each session, even one
|
|
329
|
+
* that was one observation away from confirming a trap.
|
|
330
|
+
* @param onProgress - Called after each internal chunk read, with the
|
|
331
|
+
* highest `content_items.id` scanned so far and the max id in the
|
|
332
|
+
* table. Omit for no reporting.
|
|
333
|
+
* @returns Every qualifying page's raw fields, in discovery order.
|
|
334
|
+
*/
|
|
335
|
+
async listDedupeCapObservations(onProgress) {
|
|
336
|
+
return this.#db.listDedupeCapObservations(onProgress);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Every distinct `dedupe_cap_events.shape_key` recorded in this archive.
|
|
340
|
+
* Consumed by `CrawlerOrchestrator` to preload `DedupeCapTracker`'s
|
|
341
|
+
* sticky set on `--resume` / `--append` / `--retry-failed` /
|
|
342
|
+
* `--inventory`, mirroring {@link listDnsBurnedHostCandidates}'s
|
|
343
|
+
* writer-only exposure.
|
|
344
|
+
* @returns Distinct shape keys already confirmed capped.
|
|
345
|
+
*/
|
|
346
|
+
async listDedupeCapShapeKeys() {
|
|
347
|
+
return this.#db.listDedupeCapShapeKeys();
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Hostnames whose `crawl_errors` history is consistently DNS failures and
|
|
351
|
+
* for which no recent 2xx/3xx page or resource is recorded. Consumed by
|
|
352
|
+
* `CrawlerOrchestrator.#preloadDnsBurnedHostCache` to seed the DNS-burned
|
|
353
|
+
* host cache at re-open (append / inventory / retryFailed / resume), so
|
|
354
|
+
* the next crawl skips HEAD pre-flight on hosts the previous crawl
|
|
355
|
+
* already proved unreachable.
|
|
356
|
+
*
|
|
357
|
+
* Deliberately exposed only on `Archive` (writer-side) — read-only
|
|
358
|
+
* `ArchiveAccessor` (stub viewer) does not see this method so the
|
|
359
|
+
* stub's no-migration contract is preserved.
|
|
360
|
+
* @returns Lower-cased hostnames safe to short-circuit.
|
|
361
|
+
*/
|
|
362
|
+
async listDnsBurnedHostCandidates() {
|
|
363
|
+
return this.#db.listDnsBurnedHostCandidates();
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Lists every recorded outage as a resolved {@link OutageWindow}.
|
|
367
|
+
*
|
|
368
|
+
* Thin facade over {@link Database.listNetworkOutages}.
|
|
369
|
+
* @returns Resolved outage windows, or `[]` if none have been recorded.
|
|
370
|
+
*/
|
|
371
|
+
async listNetworkOutages() {
|
|
372
|
+
return await this.#db.listNetworkOutages();
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Appends one row to the `list_reconcile_runs` audit log.
|
|
376
|
+
*
|
|
377
|
+
* Thin facade over {@link Database.recordListReconcileRun} — keeps the
|
|
378
|
+
* orchestrator decoupled from the knex layer and gives a single
|
|
379
|
+
* write entry point that future Archive-level concerns (locking,
|
|
380
|
+
* mirror sync, etc.) can hook into without touching every caller.
|
|
381
|
+
* @param meta - The run metadata. Only `ran_at` is required.
|
|
382
|
+
* @returns The autoincremented `id` of the inserted row.
|
|
383
|
+
*/
|
|
384
|
+
async recordListReconcileRun(meta) {
|
|
385
|
+
dbLog('Record list reconcile run: %s', meta.list_label ?? meta.ran_at);
|
|
386
|
+
return await this.#db.recordListReconcileRun(meta);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Releases the SQLite handle and the advisory lock **without** writing
|
|
390
|
+
* the archive or removing `tmpDir`.
|
|
391
|
+
*
|
|
392
|
+
* Use this when you need to detach from a freshly-created `Archive`
|
|
393
|
+
* without finalising it — fixtures producing a stub state for tests,
|
|
394
|
+
* tooling that wants to leave the tmpDir alive for `crawl --resume`,
|
|
395
|
+
* or any non-orchestrator caller that owns the lifecycle externally.
|
|
396
|
+
* Shares the same idempotency guard as {@link close}, so the two paths
|
|
397
|
+
* are mutually exclusive (the first one called wins).
|
|
398
|
+
*/
|
|
399
|
+
async releaseHandle() {
|
|
400
|
+
if (this.#closeOnce) {
|
|
401
|
+
return this.#closeOnce;
|
|
402
|
+
}
|
|
403
|
+
this.#closeOnce = this.#runReleaseHandle();
|
|
404
|
+
return this.#closeOnce;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Replaces the archive's DOM-structure template classification
|
|
408
|
+
* with a fresh SQL-backed set.
|
|
409
|
+
*
|
|
410
|
+
* Thin facade over {@link Database.replacePageTemplates}; kept on
|
|
411
|
+
* `Archive` so the crawl-end classification step
|
|
412
|
+
* (`classifyArchivePageTemplates`) can persist template keys without
|
|
413
|
+
* reaching into the low-level database class directly.
|
|
414
|
+
* @param templateKeysByUrl - Page URL → template key, as produced by
|
|
415
|
+
* `classifyPageTemplates`.
|
|
416
|
+
* @param clusterReasonsByTemplateKey - Template key → cluster-selection
|
|
417
|
+
* evidence, if the caller captured it. Omitting this always clears the
|
|
418
|
+
* previously-stored reasons too — "no reason" means "not captured for
|
|
419
|
+
* this run", never "carry over the previous run's reasons".
|
|
420
|
+
*/
|
|
421
|
+
async replacePageTemplates(templateKeysByUrl, clusterReasonsByTemplateKey) {
|
|
422
|
+
await this.#db.replacePageTemplates(templateKeysByUrl, clusterReasonsByTemplateKey);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Promote previously-external pages that now fall under the (possibly extended)
|
|
426
|
+
* scope back to a pending state so that the crawler re-scrapes them as fully
|
|
427
|
+
* internal pages on the next pass.
|
|
428
|
+
* @param scopes - Hostname-indexed scope map representing the new scope.
|
|
429
|
+
* @param options - URL parsing options forwarded to the scope-entry lookup.
|
|
430
|
+
* @param onProgress - Forwarded to {@link Database.repromoteExternalPages}
|
|
431
|
+
* — see that method's docs.
|
|
432
|
+
* @returns The URLs that were repromoted.
|
|
433
|
+
*/
|
|
434
|
+
async repromoteExternalPages(scopes, options, onProgress) {
|
|
435
|
+
dbLog('Repromote external pages with %d hostnames in scope', scopes.size);
|
|
436
|
+
return this.#db.repromoteExternalPages(scopes, options, onProgress);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Reset previously-failed pages back to pending so a follow-up crawl re-fetches them.
|
|
440
|
+
*
|
|
441
|
+
* Delegates to {@link Database.resetFailedPages}. See that method for the
|
|
442
|
+
* exact failure criteria (missing status / content type, or a 5xx status).
|
|
443
|
+
* @param onProgress - Forwarded to {@link Database.resetFailedPages} —
|
|
444
|
+
* see that method's docs.
|
|
445
|
+
* @returns The URLs of the pages that were reset to pending.
|
|
446
|
+
*/
|
|
447
|
+
async resetFailedPages(onProgress) {
|
|
448
|
+
dbLog('Reset failed pages back to pending');
|
|
449
|
+
return this.#db.resetFailedPages(onProgress);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Reset pages matching an operator-supplied URL list back to pending so a
|
|
453
|
+
* follow-up crawl re-fetches them from scratch.
|
|
454
|
+
*
|
|
455
|
+
* Delegates to {@link Database.resetPagesByUrls}. See that method for the
|
|
456
|
+
* conservative exclusion rules (redirect sources / intentionally-skipped /
|
|
457
|
+
* external pages are matched but not reset).
|
|
458
|
+
* @param urls - URL strings to match, already in `withoutHashAndAuth` form.
|
|
459
|
+
* @param onProgress - Forwarded to {@link Database.resetPagesByUrls} — see
|
|
460
|
+
* that method's docs.
|
|
461
|
+
* @returns The reset URLs plus the excluded URLs grouped by reason.
|
|
462
|
+
*/
|
|
463
|
+
async resetPagesByUrls(urls, onProgress) {
|
|
464
|
+
dbLog('Reset %d URL-list-matched page(s) back to pending', urls.length);
|
|
465
|
+
return this.#db.resetPagesByUrls(urls, onProgress);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Persists the raw bytes of an `--inventory` source URL list into the
|
|
469
|
+
* archive's tar payload, at `inventory/<sha256>.txt`.
|
|
470
|
+
*
|
|
471
|
+
* The file name is the content hash rather than the original file name:
|
|
472
|
+
* re-applying the same list is then a no-op write (`fs.writeFile`
|
|
473
|
+
* overwrites identical bytes), and the original name — which may embed a
|
|
474
|
+
* client/project identifier — is never retained (the archive already
|
|
475
|
+
* omits the source file's absolute path for the same reason; see
|
|
476
|
+
* `CrawlerOrchestrator.inventory`'s `source` param).
|
|
477
|
+
*
|
|
478
|
+
* The file always lands under the fixed `inventory/` prefix. Callers that
|
|
479
|
+
* need to read the saved list back can use the inherited
|
|
480
|
+
* `getData(`inventory/${sha256}`, 'txt')`, which resolves to the same
|
|
481
|
+
* path under tmpDir.
|
|
482
|
+
*
|
|
483
|
+
* No entry is ever removed here — same accepted gap as `page_html_blobs`
|
|
484
|
+
* (a future #23 GC pass will sweep unreachable hashes across both). A
|
|
485
|
+
* source list that differs byte-for-byte on every run (e.g. a
|
|
486
|
+
* regenerated doc-root export with fresh timestamps) adds one entry per
|
|
487
|
+
* run with no pruning of superseded ones.
|
|
488
|
+
* @param sha256 - Lower-case hex SHA-256 digest of `bytes` (used as the file name).
|
|
489
|
+
* @param bytes - The exact bytes of the source list file, written verbatim.
|
|
490
|
+
*/
|
|
491
|
+
async saveInventorySourceList(sha256, bytes) {
|
|
492
|
+
const filePath = safePath(this.tmpDir, 'inventory', `${sha256}.txt`);
|
|
493
|
+
await outputBinary(filePath, bytes);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Stores the crawl configuration into the archive database.
|
|
497
|
+
* @param config - The configuration object to store.
|
|
498
|
+
*/
|
|
499
|
+
async setConfig(config) {
|
|
500
|
+
dbLog('Set config: %O', redactRequestHeaders(config));
|
|
501
|
+
return this.#db.setConfig(config);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Replaces one page's captured console messages / page errors in the
|
|
505
|
+
* archive database.
|
|
506
|
+
* @param pageUrl - The originally-requested URL, normalised (`withoutHashAndAuth` form).
|
|
507
|
+
* @param redirectPaths - The redirect chain hops captured during fetch, in order.
|
|
508
|
+
* @param entries - The console log entries to persist.
|
|
509
|
+
*/
|
|
510
|
+
async setConsoleLogs(pageUrl, redirectPaths, entries) {
|
|
511
|
+
dbLog('Set console logs: %d entries on %s', entries.length, pageUrl);
|
|
512
|
+
await this.#db.replaceConsoleLogs(pageUrl, redirectPaths, entries);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Stores an external page's data in the archive database without storing
|
|
516
|
+
* an HTML snapshot. External-page rows carry only metadata (status, title,
|
|
517
|
+
* content-type), never a rendered body.
|
|
518
|
+
* @param pageInfo - The page data to store.
|
|
519
|
+
* @param source - Provenance label for new rows. `undefined` leaves the DB DEFAULT (`'crawled'`).
|
|
520
|
+
* @param recursive - The crawl session's `recursive` option, forwarded so
|
|
521
|
+
* `is_metadata_only` on any newly-discovered anchor is computed
|
|
522
|
+
* correctly (#369). Defaults to `true` (never persist an anchor as
|
|
523
|
+
* metadata-only) since an external page's `anchorList` is always
|
|
524
|
+
* empty — this only matters for callers that pass one anyway.
|
|
525
|
+
* @example
|
|
526
|
+
* await archive.setExternalPage(externalPageData, undefined, options.recursive);
|
|
527
|
+
*/
|
|
528
|
+
async setExternalPage(pageInfo, source, recursive) {
|
|
529
|
+
dbLog('Set external page: %s', pageInfo.url.href);
|
|
530
|
+
await this.#db.updatePage(pageInfo, false, false, source, undefined, recursive);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Stores a crawled page's data in the archive database, persisting the
|
|
534
|
+
* rendered HTML body as a zstd-compressed BLOB inside the same SQLite
|
|
535
|
+
* transaction. Storage is content-addressable: identical bodies across
|
|
536
|
+
* pages share a single `page_html_blobs` row.
|
|
537
|
+
* @param pageInfo - The page data to store.
|
|
538
|
+
* @param source - Provenance label for new rows. `undefined` leaves the DB DEFAULT (`'crawled'`).
|
|
539
|
+
* @param bodyHash - Precomputed body hash for the page's HTML (see
|
|
540
|
+
* `CrawlerEventTypes.page.bodyHash`). `undefined`/`null` falls back to
|
|
541
|
+
* computing it from the HTML instead.
|
|
542
|
+
* @param recursive - The crawl session's `recursive` option, forwarded so
|
|
543
|
+
* any anchor discovered on this page gets the correct
|
|
544
|
+
* `is_metadata_only` value (#369). Defaults to `true` (never persist
|
|
545
|
+
* an anchor as metadata-only) for callers that do not track the
|
|
546
|
+
* option.
|
|
547
|
+
* @returns The database ID of the stored page.
|
|
548
|
+
* @example
|
|
549
|
+
* const pageId = await archive.setPage(pageData, undefined, undefined, options.recursive);
|
|
550
|
+
*/
|
|
551
|
+
async setPage(pageInfo, source, bodyHash, recursive) {
|
|
552
|
+
dbLog('Set page: %s', pageInfo.url.href);
|
|
553
|
+
return await this.#db.updatePage(pageInfo, true, pageInfo.isTarget, source, bodyHash, recursive);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Records a redirect edge without re-storing the destination's content.
|
|
557
|
+
*
|
|
558
|
+
* The crawler calls this (instead of {@link setPage}) when a URL redirects to
|
|
559
|
+
* a destination that has already been rendered (#73): only the source →
|
|
560
|
+
* destination edge is written, leaving the destination's stored title / meta /
|
|
561
|
+
* anchors / images untouched.
|
|
562
|
+
* @param pageInfo - The HEAD-resolved page data carrying the redirect chain.
|
|
563
|
+
* @param source - Inventory provenance for a brand-new destination row.
|
|
564
|
+
* Forwarded to `recordRedirect` so the destination's `source` (and the
|
|
565
|
+
* chain-intermediate `source` derived from it) lands on the inventory
|
|
566
|
+
* label instead of the DB DEFAULT `'crawled'` when the orchestrator is
|
|
567
|
+
* running an inventory pass. `undefined` keeps the DB DEFAULT.
|
|
568
|
+
*/
|
|
569
|
+
async setRedirect(pageInfo, source) {
|
|
570
|
+
dbLog('Set redirect: %s', pageInfo.url.href);
|
|
571
|
+
await this.#db.recordRedirect(pageInfo, source);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Stores a sub-resource (CSS, JS, image, etc.) in the archive database.
|
|
575
|
+
* @param resource - The resource data to store.
|
|
576
|
+
* @param source - Provenance label for new rows. `undefined` leaves the DB DEFAULT (`'crawled'`).
|
|
577
|
+
*/
|
|
578
|
+
async setResources(resource, source) {
|
|
579
|
+
dbLog('Set resource: %s', resource.url.href);
|
|
580
|
+
await this.#db.insertResource(resource, source);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Stores the referrer relationship between a resource and the page that references it.
|
|
584
|
+
* @param params - An object containing `url` (the page URL) and `src` (the resource URL).
|
|
585
|
+
* @param params.url
|
|
586
|
+
* @param params.src
|
|
587
|
+
*/
|
|
588
|
+
async setResourcesReferrers({ url, src }) {
|
|
589
|
+
dbLog("Set resource's referrers: %s on %s", src, url);
|
|
590
|
+
await this.#db.insertResourceReferrers(src, url);
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Marks a page as skipped in the archive database with the given reason.
|
|
594
|
+
* @param url - The URL of the page to mark as skipped.
|
|
595
|
+
* @param reason - The reason the page was skipped.
|
|
596
|
+
* @param isExternal - Whether the page is on an external domain. Defaults to `false`.
|
|
597
|
+
*/
|
|
598
|
+
async setSkippedPage(url, reason, isExternal = false) {
|
|
599
|
+
dbLog('Set skipped page: %s', url);
|
|
600
|
+
await this.#db.setSkippedPage(url, reason, isExternal);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Assigns natural URL sort order values to all pages in the database
|
|
604
|
+
* that do not yet have an `order` field set.
|
|
605
|
+
* @param onProgress - Forwarded to {@link Database.setUrlOrder} — see that
|
|
606
|
+
* method's docs.
|
|
607
|
+
*/
|
|
608
|
+
async setUrlOrder(onProgress) {
|
|
609
|
+
dbLog("Pages didn't have `order` field. So set URL order.");
|
|
610
|
+
await this.#db.setUrlOrder(onProgress);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Updates a subset of fields on the archive's `info` row. Used by the append
|
|
614
|
+
* flow to extend `roots` / `scope` without rewriting the entire config.
|
|
615
|
+
* @param patch - Partial {@link Config} fields to overwrite. `undefined` values are ignored.
|
|
616
|
+
*/
|
|
617
|
+
async updateConfig(patch) {
|
|
618
|
+
dbLog('Update config: %O', redactRequestHeaders(patch));
|
|
619
|
+
await this.#db.updateConfig(patch);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Writes the archive to disk as a `.nitpicker` tar file.
|
|
623
|
+
*
|
|
624
|
+
* Checkpoints the SQLite WAL so the database is self-contained inside
|
|
625
|
+
* `db.sqlite`, renames the temporary working directory to the archive's
|
|
626
|
+
* basename, and tars the **entire tmpDir**. `db.sqlite` is normally the
|
|
627
|
+
* only entry (HTML lives as BLOBs in the DB, not a `snapshot-html.zip`),
|
|
628
|
+
* but `saveInventorySourceList` (a saved `--inventory` source list) adds
|
|
629
|
+
* extra files under tmpDir that get tarred right alongside it.
|
|
630
|
+
*
|
|
631
|
+
* This is why every writer path that reaches `write()` must open with
|
|
632
|
+
* `openPluginData: true` — `Archive.open`'s default extracts only
|
|
633
|
+
* `db.sqlite`, so a re-crawl (`append` / `inventory` / `retryFailed`)
|
|
634
|
+
* opened without it would tar back a tmpDir missing those extra files,
|
|
635
|
+
* silently dropping them from the rewritten archive.
|
|
636
|
+
* @param options - Optional write settings.
|
|
637
|
+
* @param options.onTarProgress - Called as archive bytes are written
|
|
638
|
+
* during the tar step, with the bytes written so far and the estimated
|
|
639
|
+
* total (issue #294: tarring a large archive takes minutes, and
|
|
640
|
+
* without this the CLI shows nothing until `write` returns). Omit for
|
|
641
|
+
* a silent write (the default).
|
|
642
|
+
* @param options.onStep - Called once at the start of each of this
|
|
643
|
+
* method's four steps (issue #294): `checkpoint` (WAL fold-back —
|
|
644
|
+
* single synchronous PRAGMA, no countable progress) and `remove`
|
|
645
|
+
* (deleting the tarred-away tmpDir) have no progress signal of their
|
|
646
|
+
* own, so without this a large archive's write looks frozen between
|
|
647
|
+
* the `tar` step's byte updates and completion. `rename` is nearly
|
|
648
|
+
* instant (same-filesystem directory move) but included for
|
|
649
|
+
* completeness — a caller displaying phase labels shouldn't have a
|
|
650
|
+
* gap where the operation is silently between named steps.
|
|
651
|
+
*/
|
|
652
|
+
async write(options) {
|
|
653
|
+
saveLog('Starts: %s', this.#filePath);
|
|
654
|
+
// `.nitpicker` files are routinely shared between users — scrub the
|
|
655
|
+
// stub-local `createdCwd` (see `Config.createdCwd`'s JSDoc) before it
|
|
656
|
+
// gets folded into `db.sqlite` and tarred, so a packaged archive never
|
|
657
|
+
// carries another user's local absolute path. Bundled into the
|
|
658
|
+
// `checkpoint` step below (a single UPDATE ahead of the WAL fold-back)
|
|
659
|
+
// rather than its own `onStep` phase — both are near-instant and
|
|
660
|
+
// reporting them separately would add a phase label for no visible
|
|
661
|
+
// wait.
|
|
662
|
+
await this.#db.updateConfig({ createdCwd: null });
|
|
663
|
+
options?.onStep?.('checkpoint');
|
|
664
|
+
await this.#db.checkpoint();
|
|
665
|
+
const filePathWithoutExt = path.resolve(path.dirname(this.#filePath), path.basename(this.#filePath, path.extname(this.#filePath)));
|
|
666
|
+
saveLog('Rename temporary dir: %s to %s', this.#tmpDir, filePathWithoutExt);
|
|
667
|
+
options?.onStep?.('rename');
|
|
668
|
+
await rename(this.#tmpDir, filePathWithoutExt, true);
|
|
669
|
+
saveLog('Tar temporary dir to file: %s to %s', filePathWithoutExt, this.#filePath);
|
|
670
|
+
options?.onStep?.('tar');
|
|
671
|
+
await tar(filePathWithoutExt, this.#filePath, options?.onTarProgress);
|
|
672
|
+
saveLog('Remove temporary dir: %s', filePathWithoutExt);
|
|
673
|
+
options?.onStep?.('remove');
|
|
674
|
+
await remove(filePathWithoutExt);
|
|
675
|
+
saveLog('Done: %s', this.#filePath);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Worker for {@link close}. Performs the destructive prologue
|
|
679
|
+
* (write or remove), drops the DB handle via the base class, then
|
|
680
|
+
* releases the lock in a `finally` so the lock never leaks even on
|
|
681
|
+
* partial failure.
|
|
682
|
+
* @param options - See {@link close}.
|
|
683
|
+
* @param options.timeoutMs
|
|
684
|
+
* @param options.onRecoveryStart
|
|
685
|
+
* @param options.onTarProgress
|
|
686
|
+
* @param options.onStep
|
|
687
|
+
*/
|
|
688
|
+
async #runFullClose(options) {
|
|
689
|
+
log('Closing');
|
|
690
|
+
try {
|
|
691
|
+
if (!exists(this.#filePath)) {
|
|
692
|
+
log("Save the file because it doesn't exist");
|
|
693
|
+
options?.onRecoveryStart?.();
|
|
694
|
+
await this.write(options);
|
|
695
|
+
}
|
|
696
|
+
else if (exists(this.#tmpDir)) {
|
|
697
|
+
log('Remove temporary dir');
|
|
698
|
+
await remove(this.#tmpDir);
|
|
699
|
+
}
|
|
700
|
+
await super.close({ timeoutMs: options?.timeoutMs });
|
|
701
|
+
}
|
|
702
|
+
finally {
|
|
703
|
+
await this.#releaseLock();
|
|
704
|
+
}
|
|
705
|
+
log('Closing done');
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Worker for {@link releaseHandle}. Drops the SQLite handle and the
|
|
709
|
+
* advisory lock with no filesystem mutation.
|
|
710
|
+
*/
|
|
711
|
+
async #runReleaseHandle() {
|
|
712
|
+
log('Releasing handle (no write, no remove)');
|
|
713
|
+
try {
|
|
714
|
+
await super.close();
|
|
715
|
+
}
|
|
716
|
+
finally {
|
|
717
|
+
await this.#releaseLock();
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
/** The file extension for Nitpicker archive files (without the leading dot). */
|
|
721
|
+
static FILE_EXTENSION = 'nitpicker';
|
|
722
|
+
/** The filename of the SQLite database within the archive. */
|
|
723
|
+
static SQLITE_DB_FILE_NAME = 'db.sqlite';
|
|
724
|
+
/** The prefix used for temporary working directories during archive operations. */
|
|
725
|
+
static TMP_DIR_PREFIX = '._nitpicker-';
|
|
726
|
+
/**
|
|
727
|
+
* Opens a connection to an existing archive's database, defaulting to
|
|
728
|
+
* read-only.
|
|
729
|
+
*
|
|
730
|
+
* Returns an {@link ArchiveAccessor} that provides query methods. In the
|
|
731
|
+
* default read-only mode, no schema migrations run and the connection
|
|
732
|
+
* refuses to resurrect a missing parent directory or db file (so a
|
|
733
|
+
* TOCTOU window between source classification and this call cannot
|
|
734
|
+
* silently produce an empty phantom tmpDir); the returned accessor is
|
|
735
|
+
* also marked read-only so consumer-facing helpers (e.g.
|
|
736
|
+
* {@link ArchiveAccessor.getHtmlOfPage}) avoid any filesystem mutation
|
|
737
|
+
* on the user's tmpDir.
|
|
738
|
+
*
|
|
739
|
+
* `options.readOnly: false` is a narrow escape hatch for opening a
|
|
740
|
+
* second, writable connection to a `tmpDir` the caller's own process
|
|
741
|
+
* already owns and extracted itself. The one production caller is the
|
|
742
|
+
* viewer-read-model worker thread (`@nitpicker/query`'s
|
|
743
|
+
* `viewer-read-model-worker-entry.ts`, issue #294): the parent thread
|
|
744
|
+
* holds the archive via `Archive.open` (lock included — worker threads
|
|
745
|
+
* share the parent's PID, so the PID-based `acquireArchiveLock` guard
|
|
746
|
+
* stays valid), sits idle awaiting the worker, and re-tars the tmpDir
|
|
747
|
+
* afterward. What this hatch must NEVER target is a live/interrupted
|
|
748
|
+
* crawl tmpDir owned by a *different* process (the stub-mode
|
|
749
|
+
* `ArchiveManager.open` path attaches to exactly such directories, and
|
|
750
|
+
* must stay read-only): writable connects run the self-healing
|
|
751
|
+
* migrations, and mutating a directory out from under its owner is how
|
|
752
|
+
* archives corrupt. A read-only open (`Archive.openCached`/
|
|
753
|
+
* `ArchiveManager.open`) must never take this path itself — blocking or
|
|
754
|
+
* writing during what must be a read-only open is forbidden (issue
|
|
755
|
+
* #177). Any new caller is responsible for its own coordination with
|
|
756
|
+
* the tmpDir's owner (see `acquireArchiveLock` for the cross-process
|
|
757
|
+
* case) — this method does not acquire any lock itself.
|
|
758
|
+
* @param tmpDir - The path to the temporary directory containing the database.
|
|
759
|
+
* @param options - Connection options.
|
|
760
|
+
* @param options.readOnly - Defaults to `true`. Pass `false` to obtain a
|
|
761
|
+
* writable accessor against a tmpDir the calling process itself owns.
|
|
762
|
+
* @returns An ArchiveAccessor instance for querying the archive data.
|
|
763
|
+
* @example
|
|
764
|
+
* // Default (read-only) — safe for stub mode and cache reads:
|
|
765
|
+
* const accessor = await Archive.connect(tmpDir);
|
|
766
|
+
* @example
|
|
767
|
+
* // Writable escape hatch — only against a tmpDir this process owns
|
|
768
|
+
* // (e.g. the viewer-read-model worker thread reconnecting to the
|
|
769
|
+
* // parent's Archive.open extraction):
|
|
770
|
+
* const writable = await Archive.connect(ownTmpDir, { readOnly: false });
|
|
771
|
+
*/
|
|
772
|
+
static async connect(tmpDir, options = {}) {
|
|
773
|
+
const readOnly = options.readOnly ?? true;
|
|
774
|
+
const db = await Archive.#connectDB(tmpDir, { readOnly });
|
|
775
|
+
const archive = new ArchiveAccessor(tmpDir, db, { readOnly });
|
|
776
|
+
return archive;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Open a `.nitpicker` archive through the read-only tar cache.
|
|
780
|
+
*
|
|
781
|
+
* This is the fast path for read-only consumers (viewer, MCP, query
|
|
782
|
+
* CLI). It diverges from {@link Archive.open} in two important ways:
|
|
783
|
+
*
|
|
784
|
+
* 1. The extracted contents land in an OS-temp-scoped cache directory
|
|
785
|
+
* keyed by the archive's `size + mtime_ns + ctime_ns` (see
|
|
786
|
+
* {@link computeArchiveCacheKey}). Subsequent opens of the same
|
|
787
|
+
* unchanged archive skip the untar entirely. A fresh 10 GB archive
|
|
788
|
+
* pays the ~10 s untar cost once; reopens are instant.
|
|
789
|
+
* 2. The returned value is an {@link ArchiveAccessor} (read-only), not
|
|
790
|
+
* an `Archive` (writer). Closing it tears down the DB handle but
|
|
791
|
+
* leaves the cache directory in place for the next reader. The
|
|
792
|
+
* OS's own temp-directory cleanup (macOS reboot, Linux
|
|
793
|
+
* `systemd-tmpfiles`, Windows Disk Cleanup) reclaims stale
|
|
794
|
+
* entries — we do not own eviction.
|
|
795
|
+
*
|
|
796
|
+
* Migrations: the writer-side migration stack
|
|
797
|
+
* (`initSchema` / `migrate*`) runs once at cache-miss extraction, so
|
|
798
|
+
* the cache directory always lands on the current schema before the
|
|
799
|
+
* read-only re-open. Cache hits then skip migrations entirely.
|
|
800
|
+
*
|
|
801
|
+
* Override the cache location with `NITPICKER_TAR_CACHE_DIR`. The
|
|
802
|
+
* disable switch (`NITPICKER_DISABLE_TAR_CACHE=1`) is honoured by
|
|
803
|
+
* the caller (`ArchiveManager.open` falls back to {@link Archive.open}
|
|
804
|
+
* in that case); this function itself always goes through the cache.
|
|
805
|
+
*
|
|
806
|
+
* Writer entry points (`crawl --append`, `crawl --retry-failed`) must
|
|
807
|
+
* NOT use this path — they need the lock + write-back semantics of
|
|
808
|
+
* {@link Archive.open}.
|
|
809
|
+
* @param filePath - Absolute path to the `.nitpicker` file.
|
|
810
|
+
* @param onExtractProgress - Forwarded to {@link extractArchiveToCache} —
|
|
811
|
+
* see that function's docs for the cache-hit/miss contract.
|
|
812
|
+
* @param onLog - Forwarded to the cache-miss migration pass (issue #294)
|
|
813
|
+
* — a legacy archive's self-healing schema migrations otherwise fall
|
|
814
|
+
* back to a bare `console.error`, which can print mid-redraw of a
|
|
815
|
+
* caller's `Lanes`/`TaskList` display. Omit for that fallback
|
|
816
|
+
* (existing callers — the viewer, MCP, query CLI — do not pass this;
|
|
817
|
+
* only callers driving a `TaskList` row for the extraction, like
|
|
818
|
+
* `concat`/`split`, need to).
|
|
819
|
+
* @returns A read-only {@link ArchiveAccessor} backed by the cache directory.
|
|
820
|
+
* @example
|
|
821
|
+
* ```ts
|
|
822
|
+
* await using accessor = await Archive.openCached('/path/to/site.nitpicker');
|
|
823
|
+
* const summary = await getSummary(accessor);
|
|
824
|
+
* // tears down DB handle on scope exit; cacheDir persists.
|
|
825
|
+
* ```
|
|
826
|
+
*/
|
|
827
|
+
static async openCached(filePath, onExtractProgress, onLog) {
|
|
828
|
+
const cacheRoot = getArchiveCacheRoot();
|
|
829
|
+
const cacheKey = await computeArchiveCacheKey(filePath);
|
|
830
|
+
const cacheDir = resolveArchiveCacheDir(cacheRoot, cacheKey, filePath);
|
|
831
|
+
log('Open cached: %s (cacheDir=%s)', filePath, cacheDir);
|
|
832
|
+
await extractArchiveToCache(filePath, cacheRoot, cacheDir, cacheKey, onExtractProgress, onLog);
|
|
833
|
+
return await Archive.connect(cacheDir);
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Creates a new archive at the specified file path.
|
|
837
|
+
* Initializes a temporary working directory and a fresh SQLite database.
|
|
838
|
+
* @param options - Options including the file path and optional working directory.
|
|
839
|
+
* @returns A new Archive instance ready for writing crawl data.
|
|
840
|
+
*/
|
|
841
|
+
static async create(options) {
|
|
842
|
+
const { filePath } = options;
|
|
843
|
+
const cwd = options.cwd ?? process.cwd();
|
|
844
|
+
log('Create: %O', {
|
|
845
|
+
filePath,
|
|
846
|
+
cwd,
|
|
847
|
+
});
|
|
848
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
849
|
+
const tmpDir = path.resolve(cwd, Archive.TMP_DIR_PREFIX + fileName);
|
|
850
|
+
const releaseLock = await acquireArchiveLock(tmpDir);
|
|
851
|
+
try {
|
|
852
|
+
return await Archive.#init(filePath, tmpDir, releaseLock);
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
await releaseLock();
|
|
856
|
+
throw error;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Joins path segments into an absolute path.
|
|
861
|
+
* @param pathes - The path segments to join.
|
|
862
|
+
* @returns The resolved absolute path.
|
|
863
|
+
*/
|
|
864
|
+
static joinPath(...pathes) {
|
|
865
|
+
return path.resolve(...pathes);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Opens an existing archive file (`.nitpicker`) by extracting it to a temporary directory.
|
|
869
|
+
* @param options - Options including the file path, optional working directory,
|
|
870
|
+
* and whether to extract non-db tar entries.
|
|
871
|
+
* @returns An Archive instance with the extracted data loaded.
|
|
872
|
+
*/
|
|
873
|
+
static async open(options) {
|
|
874
|
+
const { filePath, openPluginData, onExtractProgress, onLog } = options;
|
|
875
|
+
const cwd = options.cwd ?? process.cwd();
|
|
876
|
+
log('Open: %O', {
|
|
877
|
+
filePath,
|
|
878
|
+
cwd,
|
|
879
|
+
openPluginData,
|
|
880
|
+
});
|
|
881
|
+
// Read the tar's actual top-level directory name instead of deriving
|
|
882
|
+
// it from the outer file's basename. `.nitpicker` files are plain
|
|
883
|
+
// tar archives and users routinely rename them (`mv X.nitpicker
|
|
884
|
+
// Y.nitpicker`) — that operation must not break `open`. The inner
|
|
885
|
+
// directory keeps whatever name `Archive.write()` baked in at write
|
|
886
|
+
// time, and `tmpDir` mirrors the OUTER basename (so concurrent
|
|
887
|
+
// crawls on differently-named copies of the same archive don't
|
|
888
|
+
// collide on the lockfile).
|
|
889
|
+
const outerBasename = path.basename(filePath, path.extname(filePath));
|
|
890
|
+
const innerDirName = await peekTarTopDir(filePath);
|
|
891
|
+
const tmpDir = path.resolve(cwd, Archive.TMP_DIR_PREFIX + outerBasename);
|
|
892
|
+
const releaseLock = await acquireArchiveLock(tmpDir);
|
|
893
|
+
try {
|
|
894
|
+
const openFiles = [];
|
|
895
|
+
if (!openPluginData) {
|
|
896
|
+
const relDdPath = path.join(innerDirName, Archive.SQLITE_DB_FILE_NAME);
|
|
897
|
+
openFiles.push(relDdPath);
|
|
898
|
+
}
|
|
899
|
+
log('Unzip file: %s (%O)', filePath, openFiles);
|
|
900
|
+
await untar(filePath, {
|
|
901
|
+
cwd,
|
|
902
|
+
fileList: openFiles.length > 0 ? openFiles : undefined,
|
|
903
|
+
onProgress: onExtractProgress,
|
|
904
|
+
});
|
|
905
|
+
const extractedDir = path.resolve(cwd, innerDirName);
|
|
906
|
+
log('Move directory: %s to %s', extractedDir, tmpDir);
|
|
907
|
+
await rename(extractedDir, tmpDir, true);
|
|
908
|
+
return await Archive.#init(filePath, tmpDir, releaseLock, onLog);
|
|
909
|
+
}
|
|
910
|
+
catch (error) {
|
|
911
|
+
await releaseLock();
|
|
912
|
+
throw error;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Resumes an archive from an existing temporary directory
|
|
917
|
+
* (e.g., after an interrupted crawl session).
|
|
918
|
+
* @param targetPath - The path to the temporary directory to resume from.
|
|
919
|
+
* @param onLog - Forwarded to {@link Database.connect} — see
|
|
920
|
+
* {@link ArchiveOpenOptions.onLog}'s docs (this writable reconnect
|
|
921
|
+
* runs the same self-healing migrations `Archive.open` does).
|
|
922
|
+
* @returns An Archive instance reconnected to the existing data.
|
|
923
|
+
* @throws {Error} If the specified path is not a directory.
|
|
924
|
+
*/
|
|
925
|
+
static async resume(targetPath, onLog) {
|
|
926
|
+
log('Resume: %s', targetPath);
|
|
927
|
+
if (await isDir(targetPath)) {
|
|
928
|
+
const tmpDir = targetPath;
|
|
929
|
+
const releaseLock = await acquireArchiveLock(tmpDir);
|
|
930
|
+
try {
|
|
931
|
+
const db = await Archive.#connectDB(tmpDir, { onLog });
|
|
932
|
+
const name = (await db.getName()) ||
|
|
933
|
+
path.basename(targetPath).replace(Archive.TMP_DIR_PREFIX, '');
|
|
934
|
+
// Reconstruct the output path from the cwd the interrupted
|
|
935
|
+
// session was originally started from (see `Config.createdCwd`),
|
|
936
|
+
// not this invocation's own `process.cwd()` — `crawl --resume
|
|
937
|
+
// <stub>` is routinely run from a different directory than the
|
|
938
|
+
// original `crawl`/`--append`/etc. call. Falls back to this
|
|
939
|
+
// invocation's cwd for a stub that predates this column.
|
|
940
|
+
const createdCwd = await db.getCreatedCwd();
|
|
941
|
+
const filePath = path.resolve(createdCwd ?? process.cwd(), name + '.' + Archive.FILE_EXTENSION);
|
|
942
|
+
return new Archive(filePath, tmpDir, db, releaseLock);
|
|
943
|
+
}
|
|
944
|
+
catch (error) {
|
|
945
|
+
await releaseLock();
|
|
946
|
+
throw error;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
throw new Error('The specified path is not a directory. Please ensure the path points to a valid directory.');
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Generates a timestamp string in the format `YYYYMMDDHHmmssSSS`
|
|
953
|
+
* suitable for use in file names.
|
|
954
|
+
* @returns A formatted timestamp string.
|
|
955
|
+
*/
|
|
956
|
+
static timestamp() {
|
|
957
|
+
const now = new Date();
|
|
958
|
+
const year = now.getFullYear().toString();
|
|
959
|
+
const month = (now.getMonth() + 1).toLocaleString('en-US', {
|
|
960
|
+
minimumIntegerDigits: 2,
|
|
961
|
+
});
|
|
962
|
+
const date = now.getDate().toLocaleString('en-US', { minimumIntegerDigits: 2 });
|
|
963
|
+
const hours = now.getHours().toLocaleString('en-US', { minimumIntegerDigits: 2 });
|
|
964
|
+
const minutes = now.getMinutes().toLocaleString('en-US', { minimumIntegerDigits: 2 });
|
|
965
|
+
const seconds = now.getSeconds().toLocaleString('en-US', { minimumIntegerDigits: 2 });
|
|
966
|
+
const ms = now.getMilliseconds().toLocaleString('en-US', { minimumIntegerDigits: 3 });
|
|
967
|
+
return year + month + date + hours + minutes + seconds + ms;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Connects to (or creates) the SQLite database in the given directory.
|
|
971
|
+
* @param tmpDir - Directory containing `db.sqlite`
|
|
972
|
+
* @param options - Optional connection flags forwarded to
|
|
973
|
+
* {@link Database.connect}. Used by {@link Archive.connect} to pass
|
|
974
|
+
* `readOnly: true` so no migrations run and a missing tmpDir is not
|
|
975
|
+
* resurrected.
|
|
976
|
+
* @param options.readOnly
|
|
977
|
+
* @param options.onLog
|
|
978
|
+
*/
|
|
979
|
+
static async #connectDB(tmpDir, options) {
|
|
980
|
+
const dbPath = path.resolve(tmpDir, Archive.SQLITE_DB_FILE_NAME);
|
|
981
|
+
dbLog('connects database: %s (readOnly=%s)', dbPath, options?.readOnly ?? false);
|
|
982
|
+
return await Database.connect({
|
|
983
|
+
filename: dbPath,
|
|
984
|
+
readOnly: options?.readOnly,
|
|
985
|
+
onLog: options?.onLog,
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Initializes an Archive instance by connecting to the database.
|
|
990
|
+
*
|
|
991
|
+
* The advisory lock must already be acquired by the caller; this helper just
|
|
992
|
+
* threads the release function through to the resulting `Archive` so that
|
|
993
|
+
* {@link Archive.close} can drop the lock when work is done.
|
|
994
|
+
* @param filePath - Output `.nitpicker` file path
|
|
995
|
+
* @param tmpDir - Temporary working directory path
|
|
996
|
+
* @param releaseLock - Function returned by {@link acquireArchiveLock}.
|
|
997
|
+
* @param onLog - Forwarded to {@link Database.connect} — see
|
|
998
|
+
* {@link ArchiveOpenOptions.onLog}'s docs.
|
|
999
|
+
*/
|
|
1000
|
+
static async #init(filePath, tmpDir, releaseLock, onLog) {
|
|
1001
|
+
const db = await Archive.#connectDB(tmpDir, { onLog });
|
|
1002
|
+
const archive = new Archive(filePath, tmpDir, db, releaseLock);
|
|
1003
|
+
return archive;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Closes the archive. If the archive file does not yet exist on disk,
|
|
1007
|
+
* it writes the archive first. If the temporary directory still exists,
|
|
1008
|
+
* it is removed. The database connection is then closed via
|
|
1009
|
+
* {@link ArchiveAccessor.close} (the base class owns the SQLite handle),
|
|
1010
|
+
* and finally the archive's advisory lock is released.
|
|
1011
|
+
*
|
|
1012
|
+
* **Idempotent**: the first invocation captures the close promise;
|
|
1013
|
+
* subsequent invocations (signal handlers, parallel teardowns, retried
|
|
1014
|
+
* orchestrator paths) await the same promise instead of re-entering
|
|
1015
|
+
* the destructive prologue on a half-mutated state. If the first
|
|
1016
|
+
* close fails (e.g. ENOSPC during tar), the rejection propagates to
|
|
1017
|
+
* all awaiters and the archive stays latched closed — there is no
|
|
1018
|
+
* safe way to retry `write()` once `tmpDir` has been renamed.
|
|
1019
|
+
*
|
|
1020
|
+
* **Read-only consumers must not reach this override.** Anything that
|
|
1021
|
+
* obtains an archive view via {@link Archive.connect} receives an
|
|
1022
|
+
* {@link ArchiveAccessor} (not an `Archive`), so `close()` resolves to
|
|
1023
|
+
* the safe base implementation — no `write()`, no `remove()`, no lock
|
|
1024
|
+
* release — leaving the tmpDir intact for the live crawler.
|
|
1025
|
+
* @param options - Optional close settings. `timeoutMs` is accepted for
|
|
1026
|
+
* compatibility with {@link ArchiveAccessor.close}'s signature (forwarded
|
|
1027
|
+
* to the base `super.close()` call below); the rest are progress
|
|
1028
|
+
* callbacks (issue #294) forwarded to {@link write} when this call ends
|
|
1029
|
+
* up taking the recovery-write branch (the archive file doesn't exist
|
|
1030
|
+
* yet).
|
|
1031
|
+
* @param options.timeoutMs - See {@link ArchiveAccessor.close}.
|
|
1032
|
+
* @param options.onRecoveryStart - Called once, only when this `close()`
|
|
1033
|
+
* is about to write the archive because the file doesn't exist on
|
|
1034
|
+
* disk yet — e.g. a caller's own explicit `write()` threw before
|
|
1035
|
+
* finishing, or was never called at all. Without this, a listener
|
|
1036
|
+
* that already tore down its display after that earlier failure has
|
|
1037
|
+
* no way to know a second, recovery write is happening.
|
|
1038
|
+
* @param options.onTarProgress - See {@link write}.
|
|
1039
|
+
* @param options.onStep - See {@link write}.
|
|
1040
|
+
*/
|
|
1041
|
+
async close(options) {
|
|
1042
|
+
if (this.#closeOnce) {
|
|
1043
|
+
return this.#closeOnce;
|
|
1044
|
+
}
|
|
1045
|
+
this.#closeOnce = this.#runFullClose(options);
|
|
1046
|
+
return this.#closeOnce;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Retrieves the crawl configuration stored in the archive database.
|
|
1050
|
+
* @returns The configuration object.
|
|
1051
|
+
*/
|
|
1052
|
+
async getConfig() {
|
|
1053
|
+
return this.#db.getConfig();
|
|
1054
|
+
}
|
|
1055
|
+
}
|