@nitpicker/crawler 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/lib/archive/archive-accessor.d.ts +2 -2
- package/lib/archive/archive-accessor.js +2 -2
- package/lib/archive/archive-lock.d.ts +7 -0
- package/lib/archive/archive-lock.js +7 -0
- package/lib/archive/archive.d.ts +175 -13
- package/lib/archive/archive.js +198 -14
- package/lib/archive/cache/compute-archive-cache-key.d.ts +39 -0
- package/lib/archive/cache/compute-archive-cache-key.js +95 -0
- package/lib/archive/cache/extract-archive-to-cache.d.ts +43 -0
- package/lib/archive/cache/extract-archive-to-cache.js +309 -0
- package/lib/archive/cache/get-archive-cache-root.d.ts +20 -0
- package/lib/archive/cache/get-archive-cache-root.js +53 -0
- package/lib/archive/cache/is-archive-cache-disabled.d.ts +24 -0
- package/lib/archive/cache/is-archive-cache-disabled.js +34 -0
- package/lib/archive/cache/resolve-archive-cache-dir.d.ts +26 -0
- package/lib/archive/cache/resolve-archive-cache-dir.js +32 -0
- package/lib/archive/create-adjunct-tables.d.ts +43 -0
- package/lib/archive/create-adjunct-tables.js +213 -0
- package/lib/archive/create-entity-tables.d.ts +173 -0
- package/lib/archive/create-entity-tables.js +318 -0
- package/lib/archive/create-progress-reporter.d.ts +30 -0
- package/lib/archive/create-progress-reporter.js +38 -0
- package/lib/archive/create-ref-tables.d.ts +35 -0
- package/lib/archive/create-ref-tables.js +188 -0
- package/lib/archive/database.d.ts +130 -182
- package/lib/archive/database.js +524 -1777
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.d.ts +27 -0
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +34 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.d.ts +17 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.js +26 -0
- package/lib/archive/db-ops/_shared/decode-json-ref.d.ts +17 -0
- package/lib/archive/db-ops/_shared/decode-json-ref.js +31 -0
- package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.d.ts +20 -0
- package/lib/archive/db-ops/_shared/load-response-headers-by-set-ids.js +53 -0
- package/lib/archive/db-ops/_shared/resolve-content-item-id.d.ts +61 -0
- package/lib/archive/db-ops/_shared/resolve-content-item-id.js +111 -0
- package/lib/archive/db-ops/_shared/resolve-url-or-blob.d.ts +23 -0
- package/lib/archive/db-ops/_shared/resolve-url-or-blob.js +29 -0
- package/lib/archive/db-ops/_shared/retry-setting.d.ts +16 -0
- package/lib/archive/db-ops/_shared/retry-setting.js +18 -0
- package/lib/archive/db-ops/_shared/safe-parse-json.d.ts +11 -0
- package/lib/archive/db-ops/_shared/safe-parse-json.js +18 -0
- package/lib/archive/db-ops/_shared/types.d.ts +53 -0
- package/lib/archive/db-ops/_shared/types.js +1 -0
- package/lib/archive/db-ops/_shared/upsert-blob-ref.d.ts +25 -0
- package/lib/archive/db-ops/_shared/upsert-blob-ref.js +48 -0
- package/lib/archive/db-ops/_shared/upsert-content-type-ref.d.ts +30 -0
- package/lib/archive/db-ops/_shared/upsert-content-type-ref.js +45 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.d.ts +22 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.js +41 -0
- package/lib/archive/db-ops/_shared/upsert-response-headers.d.ts +35 -0
- package/lib/archive/db-ops/_shared/upsert-response-headers.js +49 -0
- package/lib/archive/db-ops/_shared/upsert-url-ref.d.ts +39 -0
- package/lib/archive/db-ops/_shared/upsert-url-ref.js +62 -0
- package/lib/archive/db-ops/analysis/replace-analysis-violations.d.ts +28 -0
- package/lib/archive/db-ops/analysis/replace-analysis-violations.js +152 -0
- package/lib/archive/db-ops/anchors/get-anchors-on-page.d.ts +10 -0
- package/lib/archive/db-ops/anchors/get-anchors-on-page.js +21 -0
- package/lib/archive/db-ops/config/get-base-url.d.ts +8 -0
- package/lib/archive/db-ops/config/get-base-url.js +14 -0
- package/lib/archive/db-ops/config/get-config.d.ts +10 -0
- package/lib/archive/db-ops/config/get-config.js +27 -0
- package/lib/archive/db-ops/config/get-name.d.ts +8 -0
- package/lib/archive/db-ops/config/get-name.js +14 -0
- package/lib/archive/db-ops/config/info-column-allowlist.d.ts +7 -0
- package/lib/archive/db-ops/config/info-column-allowlist.js +26 -0
- package/lib/archive/db-ops/config/info-json-columns.d.ts +5 -0
- package/lib/archive/db-ops/config/info-json-columns.js +10 -0
- package/lib/archive/db-ops/config/set-config.d.ts +12 -0
- package/lib/archive/db-ops/config/set-config.js +21 -0
- package/lib/archive/db-ops/config/update-config.d.ts +17 -0
- package/lib/archive/db-ops/config/update-config.js +36 -0
- package/lib/archive/db-ops/errors/insert-crawl-error.d.ts +15 -0
- package/lib/archive/db-ops/errors/insert-crawl-error.js +21 -0
- package/lib/archive/db-ops/errors/insert-page-error.d.ts +21 -0
- package/lib/archive/db-ops/errors/insert-page-error.js +28 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +22 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +141 -0
- package/lib/archive/db-ops/html/get-html-of-page-by-id.d.ts +18 -0
- package/lib/archive/db-ops/html/get-html-of-page-by-id.js +29 -0
- package/lib/archive/db-ops/inventory/record-inventory-run.d.ts +21 -0
- package/lib/archive/db-ops/inventory/record-inventory-run.js +38 -0
- package/lib/archive/db-ops/lifecycle/checkpoint.d.ts +8 -0
- package/lib/archive/db-ops/lifecycle/checkpoint.js +9 -0
- package/lib/archive/db-ops/lifecycle/destroy.d.ts +6 -0
- package/lib/archive/db-ops/lifecycle/destroy.js +7 -0
- package/lib/archive/db-ops/lifecycle/init.d.ts +22 -0
- package/lib/archive/db-ops/lifecycle/init.js +42 -0
- package/lib/archive/db-ops/meta/get-jsonld-of-page.d.ts +13 -0
- package/lib/archive/db-ops/meta/get-jsonld-of-page.js +27 -0
- package/lib/archive/db-ops/meta/get-tags-of-page.d.ts +12 -0
- package/lib/archive/db-ops/meta/get-tags-of-page.js +28 -0
- package/lib/archive/db-ops/pages/order/set-url-order.d.ts +8 -0
- package/lib/archive/db-ops/pages/order/set-url-order.js +32 -0
- package/lib/archive/db-ops/pages/read/build-page-query.d.ts +18 -0
- package/lib/archive/db-ops/pages/read/build-page-query.js +40 -0
- package/lib/archive/db-ops/pages/read/get-crawling-state.d.ts +70 -0
- package/lib/archive/db-ops/pages/read/get-crawling-state.js +98 -0
- package/lib/archive/db-ops/pages/read/get-existing-page-urls.d.ts +15 -0
- package/lib/archive/db-ops/pages/read/get-existing-page-urls.js +30 -0
- package/lib/archive/db-ops/pages/read/get-page-count.d.ts +12 -0
- package/lib/archive/db-ops/pages/read/get-page-count.js +21 -0
- package/lib/archive/db-ops/pages/read/get-page-source-by-url.d.ts +24 -0
- package/lib/archive/db-ops/pages/read/get-page-source-by-url.js +28 -0
- package/lib/archive/db-ops/pages/read/get-pages-with-rels.d.ts +38 -0
- package/lib/archive/db-ops/pages/read/get-pages-with-rels.js +107 -0
- package/lib/archive/db-ops/pages/read/get-pages.d.ts +11 -0
- package/lib/archive/db-ops/pages/read/get-pages.js +51 -0
- package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.d.ts +18 -0
- package/lib/archive/db-ops/pages/read/get-scraped-html-page-count.js +25 -0
- package/lib/archive/db-ops/pages/read/reconstruct-page-rows.d.ts +31 -0
- package/lib/archive/db-ops/pages/read/reconstruct-page-rows.js +32 -0
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.d.ts +24 -0
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +93 -0
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +47 -0
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +124 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-seeds.d.ts +37 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-seeds.js +72 -0
- package/lib/archive/db-ops/pages/write/insert-jsonld.d.ts +17 -0
- package/lib/archive/db-ops/pages/write/insert-jsonld.js +49 -0
- package/lib/archive/db-ops/pages/write/insert-page.d.ts +36 -0
- package/lib/archive/db-ops/pages/write/insert-page.js +208 -0
- package/lib/archive/db-ops/pages/write/insert-tags.d.ts +16 -0
- package/lib/archive/db-ops/pages/write/insert-tags.js +34 -0
- package/lib/archive/db-ops/pages/write/link-redirect-sources.d.ts +36 -0
- package/lib/archive/db-ops/pages/write/link-redirect-sources.js +93 -0
- package/lib/archive/db-ops/pages/write/record-redirect.d.ts +35 -0
- package/lib/archive/db-ops/pages/write/record-redirect.js +100 -0
- package/lib/archive/db-ops/pages/write/set-skipped-page.d.ts +13 -0
- package/lib/archive/db-ops/pages/write/set-skipped-page.js +22 -0
- package/lib/archive/db-ops/pages/write/update-page.d.ts +29 -0
- package/lib/archive/db-ops/pages/write/update-page.js +334 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.d.ts +19 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.js +41 -0
- package/lib/archive/db-ops/referrers/get-redirects-for-pages.d.ts +9 -0
- package/lib/archive/db-ops/referrers/get-redirects-for-pages.js +15 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-page.d.ts +17 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-page.js +32 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-resource.d.ts +8 -0
- package/lib/archive/db-ops/referrers/get-referrers-of-resource.js +15 -0
- package/lib/archive/db-ops/resources/build-resource-query.d.ts +25 -0
- package/lib/archive/db-ops/resources/build-resource-query.js +29 -0
- package/lib/archive/db-ops/resources/get-existing-resource-urls.d.ts +9 -0
- package/lib/archive/db-ops/resources/get-existing-resource-urls.js +24 -0
- package/lib/archive/db-ops/resources/get-resource-by-url.d.ts +13 -0
- package/lib/archive/db-ops/resources/get-resource-by-url.js +22 -0
- package/lib/archive/db-ops/resources/get-resource-url-list.d.ts +9 -0
- package/lib/archive/db-ops/resources/get-resource-url-list.js +13 -0
- package/lib/archive/db-ops/resources/get-resources.d.ts +8 -0
- package/lib/archive/db-ops/resources/get-resources.js +11 -0
- package/lib/archive/db-ops/resources/insert-inventory-resources.d.ts +24 -0
- package/lib/archive/db-ops/resources/insert-inventory-resources.js +64 -0
- package/lib/archive/db-ops/resources/insert-resource-referrers.d.ts +15 -0
- package/lib/archive/db-ops/resources/insert-resource-referrers.js +54 -0
- package/lib/archive/db-ops/resources/insert-resource.d.ts +34 -0
- package/lib/archive/db-ops/resources/insert-resource.js +73 -0
- package/lib/archive/db-ops/resources/reconstruct-resource-rows.d.ts +26 -0
- package/lib/archive/db-ops/resources/reconstruct-resource-rows.js +30 -0
- package/lib/archive/decode-html-blob.d.ts +18 -0
- package/lib/archive/decode-html-blob.js +31 -0
- package/lib/archive/derive-lineage-from-parent.d.ts +37 -0
- package/lib/archive/derive-lineage-from-parent.js +42 -0
- package/lib/archive/drop-legacy-tables.d.ts +45 -0
- package/lib/archive/drop-legacy-tables.js +56 -0
- package/lib/archive/filesystem/rename.js +1 -1
- package/lib/archive/get-failed-page-messages.d.ts +44 -0
- package/lib/archive/get-failed-page-messages.js +132 -0
- package/lib/archive/init-schema.d.ts +35 -39
- package/lib/archive/init-schema.js +111 -320
- package/lib/archive/is-inventory-source.d.ts +21 -0
- package/lib/archive/is-inventory-source.js +22 -0
- package/lib/archive/limited-page-ids.d.ts +2 -1
- package/lib/archive/limited-page-ids.js +5 -4
- package/lib/archive/meta/assert-compatible-version.d.ts +24 -3
- package/lib/archive/meta/assert-compatible-version.js +24 -3
- package/lib/archive/meta/types.d.ts +87 -1
- package/lib/archive/meta/types.js +34 -2
- package/lib/archive/migrate-entity-tables.d.ts +45 -0
- package/lib/archive/migrate-entity-tables.js +56 -0
- package/lib/archive/migrate-ref-tables.d.ts +25 -0
- package/lib/archive/migrate-ref-tables.js +38 -0
- package/lib/archive/page-meta-column-maps.d.ts +32 -0
- package/lib/archive/page-meta-column-maps.js +43 -0
- package/lib/archive/page.d.ts +6 -6
- package/lib/archive/page.js +5 -5
- package/lib/archive/peek-archive-lock.d.ts +2 -2
- package/lib/archive/peek-archive-lock.js +2 -2
- package/lib/archive/populate-entity-tables/collapse-anchor-rows.d.ts +41 -0
- package/lib/archive/populate-entity-tables/collapse-anchor-rows.js +87 -0
- package/lib/archive/populate-entity-tables/derive-dom-path.d.ts +35 -0
- package/lib/archive/populate-entity-tables/derive-dom-path.js +72 -0
- package/lib/archive/populate-entity-tables/is-blob-ref-value.d.ts +16 -0
- package/lib/archive/populate-entity-tables/is-blob-ref-value.js +19 -0
- package/lib/archive/populate-entity-tables/match-images-to-dom-paths.d.ts +66 -0
- package/lib/archive/populate-entity-tables/match-images-to-dom-paths.js +96 -0
- package/lib/archive/populate-entity-tables/populate-anchor-edges.d.ts +33 -0
- package/lib/archive/populate-entity-tables/populate-anchor-edges.js +153 -0
- package/lib/archive/populate-entity-tables/populate-content-items.d.ts +40 -0
- package/lib/archive/populate-entity-tables/populate-content-items.js +141 -0
- package/lib/archive/populate-entity-tables/populate-entities.d.ts +81 -0
- package/lib/archive/populate-entity-tables/populate-entities.js +111 -0
- package/lib/archive/populate-entity-tables/populate-image-items.d.ts +91 -0
- package/lib/archive/populate-entity-tables/populate-image-items.js +223 -0
- package/lib/archive/populate-entity-tables/populate-page-meta.d.ts +33 -0
- package/lib/archive/populate-entity-tables/populate-page-meta.js +267 -0
- package/lib/archive/populate-entity-tables/populate-resource-items.d.ts +22 -0
- package/lib/archive/populate-entity-tables/populate-resource-items.js +114 -0
- package/lib/archive/populate-entity-tables/populate-resource-ref-edges.d.ts +31 -0
- package/lib/archive/populate-entity-tables/populate-resource-ref-edges.js +33 -0
- package/lib/archive/populate-entity-tables/resolve-blob-refs.d.ts +31 -0
- package/lib/archive/populate-entity-tables/resolve-blob-refs.js +100 -0
- package/lib/archive/populate-entity-tables/resolve-content-type-refs.d.ts +22 -0
- package/lib/archive/populate-entity-tables/resolve-content-type-refs.js +27 -0
- package/lib/archive/populate-entity-tables/resolve-header-sets.d.ts +49 -0
- package/lib/archive/populate-entity-tables/resolve-header-sets.js +122 -0
- package/lib/archive/populate-entity-tables/resolve-json-refs.d.ts +25 -0
- package/lib/archive/populate-entity-tables/resolve-json-refs.js +67 -0
- package/lib/archive/populate-entity-tables/resolve-text-refs.d.ts +30 -0
- package/lib/archive/populate-entity-tables/resolve-text-refs.js +61 -0
- package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.d.ts +21 -0
- package/lib/archive/populate-entity-tables/resolve-url-or-blob-from-maps.js +27 -0
- package/lib/archive/populate-entity-tables/resolve-url-refs.d.ts +33 -0
- package/lib/archive/populate-entity-tables/resolve-url-refs.js +60 -0
- package/lib/archive/populate-entity-tables/test-utils/count-rows.d.ts +17 -0
- package/lib/archive/populate-entity-tables/test-utils/count-rows.js +20 -0
- package/lib/archive/populate-entity-tables/test-utils/seed-content-items.d.ts +25 -0
- package/lib/archive/populate-entity-tables/test-utils/seed-content-items.js +42 -0
- package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.d.ts +23 -0
- package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.js +178 -0
- package/lib/archive/populate-entity-tables/types.d.ts +157 -0
- package/lib/archive/populate-entity-tables/types.js +12 -0
- package/lib/archive/populate-entity-tables/upsert-text-refs.d.ts +38 -0
- package/lib/archive/populate-entity-tables/upsert-text-refs.js +78 -0
- package/lib/archive/populate-ref-tables/classify-content-type.d.ts +16 -0
- package/lib/archive/populate-ref-tables/classify-content-type.js +52 -0
- package/lib/archive/populate-ref-tables/compute-content-hash.d.ts +22 -0
- package/lib/archive/populate-ref-tables/compute-content-hash.js +26 -0
- package/lib/archive/populate-ref-tables/compute-header-flags.d.ts +16 -0
- package/lib/archive/populate-ref-tables/compute-header-flags.js +70 -0
- package/lib/archive/populate-ref-tables/content-type-rules.d.ts +38 -0
- package/lib/archive/populate-ref-tables/content-type-rules.js +133 -0
- package/lib/archive/populate-ref-tables/create-header-table-caches.d.ts +25 -0
- package/lib/archive/populate-ref-tables/create-header-table-caches.js +49 -0
- package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.d.ts +15 -0
- package/lib/archive/populate-ref-tables/data-uri-url-refs-limit.js +15 -0
- package/lib/archive/populate-ref-tables/decode-data-uri.d.ts +21 -0
- package/lib/archive/populate-ref-tables/decode-data-uri.js +126 -0
- package/lib/archive/populate-ref-tables/decompose-header-set.d.ts +29 -0
- package/lib/archive/populate-ref-tables/decompose-header-set.js +157 -0
- package/lib/archive/populate-ref-tables/decompose-url.d.ts +25 -0
- package/lib/archive/populate-ref-tables/decompose-url.js +70 -0
- package/lib/archive/populate-ref-tables/header-stability.d.ts +19 -0
- package/lib/archive/populate-ref-tables/header-stability.js +22 -0
- package/lib/archive/populate-ref-tables/header-value-cache-key.d.ts +17 -0
- package/lib/archive/populate-ref-tables/header-value-cache-key.js +19 -0
- package/lib/archive/populate-ref-tables/normalize-mime.d.ts +24 -0
- package/lib/archive/populate-ref-tables/normalize-mime.js +36 -0
- package/lib/archive/populate-ref-tables/populate-blob-refs.d.ts +38 -0
- package/lib/archive/populate-ref-tables/populate-blob-refs.js +134 -0
- package/lib/archive/populate-ref-tables/populate-content-type-refs.d.ts +27 -0
- package/lib/archive/populate-ref-tables/populate-content-type-refs.js +70 -0
- package/lib/archive/populate-ref-tables/populate-header-tables.d.ts +35 -0
- package/lib/archive/populate-ref-tables/populate-header-tables.js +80 -0
- package/lib/archive/populate-ref-tables/populate-json-refs.d.ts +29 -0
- package/lib/archive/populate-ref-tables/populate-json-refs.js +101 -0
- package/lib/archive/populate-ref-tables/populate-refs.d.ts +51 -0
- package/lib/archive/populate-ref-tables/populate-refs.js +62 -0
- package/lib/archive/populate-ref-tables/populate-text-refs.d.ts +32 -0
- package/lib/archive/populate-ref-tables/populate-text-refs.js +133 -0
- package/lib/archive/populate-ref-tables/populate-url-refs.d.ts +28 -0
- package/lib/archive/populate-ref-tables/populate-url-refs.js +148 -0
- package/lib/archive/populate-ref-tables/test-utils/count-rows.d.ts +15 -0
- package/lib/archive/populate-ref-tables/test-utils/count-rows.js +17 -0
- package/lib/archive/populate-ref-tables/types.d.ts +197 -0
- package/lib/archive/populate-ref-tables/types.js +7 -0
- package/lib/archive/populate-ref-tables/upsert-one-header-set.d.ts +34 -0
- package/lib/archive/populate-ref-tables/upsert-one-header-set.js +208 -0
- package/lib/archive/populate-ref-tables/volatile-header-names.d.ts +20 -0
- package/lib/archive/populate-ref-tables/volatile-header-names.js +33 -0
- package/lib/archive/redirect-table.d.ts +4 -2
- package/lib/archive/redirect-table.js +15 -10
- package/lib/archive/resolve-redirect-chain.d.ts +3 -3
- package/lib/archive/resolve-redirect-chain.js +2 -2
- package/lib/archive/resource.d.ts +1 -1
- package/lib/archive/retarget-legacy-fk-tables.d.ts +47 -0
- package/lib/archive/retarget-legacy-fk-tables.js +107 -0
- package/lib/archive/test-utils/fk-parent-tables.d.ts +15 -0
- package/lib/archive/test-utils/fk-parent-tables.js +19 -0
- package/lib/archive/test-utils/seed-content-item.d.ts +35 -0
- package/lib/archive/test-utils/seed-content-item.js +42 -0
- package/lib/archive/test-utils/setup-legacy-fk-db.d.ts +33 -0
- package/lib/archive/test-utils/setup-legacy-fk-db.js +270 -0
- package/lib/archive/types.d.ts +151 -15
- package/lib/archive/verify-migration/capture-rejection.d.ts +24 -0
- package/lib/archive/verify-migration/capture-rejection.js +31 -0
- package/lib/archive/verify-migration/check-anchor-edges-count.d.ts +34 -0
- package/lib/archive/verify-migration/check-anchor-edges-count.js +72 -0
- package/lib/archive/verify-migration/check-anchor-edges-sum.d.ts +13 -0
- package/lib/archive/verify-migration/check-anchor-edges-sum.js +27 -0
- package/lib/archive/verify-migration/check-content-items-count.d.ts +16 -0
- package/lib/archive/verify-migration/check-content-items-count.js +30 -0
- package/lib/archive/verify-migration/check-content-type-preservation.d.ts +22 -0
- package/lib/archive/verify-migration/check-content-type-preservation.js +40 -0
- package/lib/archive/verify-migration/check-foreign-key-integrity.d.ts +31 -0
- package/lib/archive/verify-migration/check-foreign-key-integrity.js +47 -0
- package/lib/archive/verify-migration/check-image-items-count.d.ts +12 -0
- package/lib/archive/verify-migration/check-image-items-count.js +26 -0
- package/lib/archive/verify-migration/check-page-meta-count.d.ts +15 -0
- package/lib/archive/verify-migration/check-page-meta-count.js +31 -0
- package/lib/archive/verify-migration/check-reader-parity.d.ts +23 -0
- package/lib/archive/verify-migration/check-reader-parity.js +211 -0
- package/lib/archive/verify-migration/check-resource-items-count.d.ts +17 -0
- package/lib/archive/verify-migration/check-resource-items-count.js +33 -0
- package/lib/archive/verify-migration/check-url-round-trip.d.ts +43 -0
- package/lib/archive/verify-migration/check-url-round-trip.js +112 -0
- package/lib/archive/verify-migration/types.d.ts +70 -0
- package/lib/archive/verify-migration/types.js +63 -0
- package/lib/archive/verify-migration/verify-migration.d.ts +41 -0
- package/lib/archive/verify-migration/verify-migration.js +120 -0
- package/lib/classify-error-kind.d.ts +19 -0
- package/lib/classify-error-kind.js +122 -0
- package/lib/crawler/build-js-redirect-edge.d.ts +68 -0
- package/lib/crawler/build-js-redirect-edge.js +57 -0
- package/lib/crawler/build-redirect-event.d.ts +24 -0
- package/lib/crawler/build-redirect-event.js +28 -0
- package/lib/crawler/capture-image-dom-paths.d.ts +33 -0
- package/lib/crawler/capture-image-dom-paths.js +39 -0
- package/lib/crawler/clear-dns-burned-host-cache.d.ts +6 -0
- package/lib/crawler/clear-dns-burned-host-cache.js +11 -0
- package/lib/crawler/collect-image-dom-paths.d.ts +23 -0
- package/lib/crawler/collect-image-dom-paths.js +64 -0
- package/lib/crawler/crawler.d.ts +22 -1
- package/lib/crawler/crawler.js +679 -117
- package/lib/crawler/derive-js-redirect-target.d.ts +68 -0
- package/lib/crawler/derive-js-redirect-target.js +129 -0
- package/lib/crawler/derive-resource-source.d.ts +25 -15
- package/lib/crawler/derive-resource-source.js +28 -17
- package/lib/crawler/dns-burned-host-cache.d.ts +26 -0
- package/lib/crawler/dns-burned-host-cache.js +25 -0
- package/lib/crawler/dns-burned-host-short-circuit-counter.d.ts +13 -0
- package/lib/crawler/dns-burned-host-short-circuit-counter.js +11 -0
- package/lib/crawler/fetch-destination.d.ts +12 -4
- package/lib/crawler/fetch-destination.js +94 -16
- package/lib/crawler/inject-scope-auth.d.ts +1 -1
- package/lib/crawler/inject-scope-auth.js +1 -1
- package/lib/crawler/is-js-redirect-error-shape.d.ts +40 -0
- package/lib/crawler/is-js-redirect-error-shape.js +53 -0
- package/lib/crawler/is-puppeteer-fallback-candidate.d.ts +16 -0
- package/lib/crawler/is-puppeteer-fallback-candidate.js +63 -0
- package/lib/crawler/link-list.d.ts +21 -1
- package/lib/crawler/link-list.js +23 -3
- package/lib/crawler/normalize-content-type.d.ts +1 -1
- package/lib/crawler/normalize-content-type.js +1 -1
- package/lib/crawler/plan-sub-resource-emits.d.ts +63 -0
- package/lib/crawler/plan-sub-resource-emits.js +44 -0
- package/lib/crawler/preload-short-circuit-error.d.ts +22 -0
- package/lib/crawler/preload-short-circuit-error.js +25 -0
- package/lib/crawler/should-burn-host.d.ts +78 -0
- package/lib/crawler/should-burn-host.js +61 -0
- package/lib/crawler/should-get-fallback-on-head-failure.d.ts +38 -0
- package/lib/crawler/should-get-fallback-on-head-failure.js +46 -0
- package/lib/crawler/types.d.ts +108 -1
- package/lib/crawler-orchestrator.d.ts +22 -3
- package/lib/crawler-orchestrator.js +312 -73
- package/lib/crawler.d.ts +15 -2
- package/lib/crawler.js +24 -1
- package/lib/permanent-error-kinds.d.ts +43 -0
- package/lib/permanent-error-kinds.js +48 -0
- package/lib/types.d.ts +84 -0
- package/lib/utils/compute-file-sha256.d.ts +24 -0
- package/lib/utils/compute-file-sha256.js +56 -0
- package/lib/utils/error/emit-error-with-retry.d.ts +40 -0
- package/lib/utils/error/emit-error-with-retry.js +44 -0
- package/lib/utils/error/emit-error.d.ts +39 -0
- package/lib/utils/error/emit-error.js +41 -0
- package/package.json +12 -12
- package/lib/archive/migrate-crawl-errors.d.ts +0 -20
- package/lib/archive/migrate-crawl-errors.js +0 -38
- package/lib/archive/migrate-html-blob-tables.d.ts +0 -24
- package/lib/archive/migrate-html-blob-tables.js +0 -53
- package/lib/archive/migrate-page-errors.d.ts +0 -16
- package/lib/archive/migrate-page-errors.js +0 -35
- package/lib/archive/migrate-pages-resources-source.d.ts +0 -16
- package/lib/archive/migrate-pages-resources-source.js +0 -46
- package/lib/utils/error/error-emitter.d.ts +0 -18
- package/lib/utils/error/error-emitter.js +0 -29
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { zstdCompressSync } from 'node:zlib';
|
|
2
|
+
import { computeContentHash } from '../../populate-ref-tables/compute-content-hash.js';
|
|
3
|
+
import { decodeDataUri } from '../../populate-ref-tables/decode-data-uri.js';
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the `blob_refs.id` for one large `data:` URI, decoding and
|
|
6
|
+
* inserting the deduplicated (hash-keyed) payload row when it is new.
|
|
7
|
+
*
|
|
8
|
+
* Callers route values here per the data-URI threshold rule (`data:` URIs
|
|
9
|
+
* longer than `DATA_URI_URL_REFS_LIMIT` — everything else belongs in
|
|
10
|
+
* `url_refs`). The payload bytes are decoded from the base64 /
|
|
11
|
+
* percent-encoded tail, hashed, and stored zstd-compressed — the same
|
|
12
|
+
* storage contract as the rows produced when migrating an existing
|
|
13
|
+
* archive (`populate-ref-tables/populate-blob-refs.ts`).
|
|
14
|
+
*
|
|
15
|
+
* Returns `null` when the data URI fails to decode (malformed base64 /
|
|
16
|
+
* truncated percent escape). A single malformed image `src` must not
|
|
17
|
+
* abort the page write; the caller stores `*_blob_id = null` and the
|
|
18
|
+
* information loss is limited to that one slot.
|
|
19
|
+
* @param qb - Knex instance or transaction connected to the archive DB.
|
|
20
|
+
* @param caches - The connection's write-side id caches; mutated in place.
|
|
21
|
+
* @param dataUri - The raw `data:` URI value.
|
|
22
|
+
* @returns The `blob_refs.id`, or `null` when the URI cannot be decoded.
|
|
23
|
+
* @example
|
|
24
|
+
* const id = await upsertBlobRef(knex, caches, 'data:image/png;base64,<payload>');
|
|
25
|
+
*/
|
|
26
|
+
export async function upsertBlobRef(qb, caches, dataUri) {
|
|
27
|
+
const decoded = decodeDataUri(dataUri);
|
|
28
|
+
if (decoded === null) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const hash = computeContentHash(decoded.bytes);
|
|
32
|
+
const hex = hash.toString('hex');
|
|
33
|
+
const cached = caches.blobIds.get(hex);
|
|
34
|
+
if (cached !== undefined) {
|
|
35
|
+
return cached;
|
|
36
|
+
}
|
|
37
|
+
const compressed = zstdCompressSync(decoded.bytes);
|
|
38
|
+
const rows = await qb.raw(`INSERT INTO blob_refs (hash, body, codec, size_raw, size_stored)
|
|
39
|
+
VALUES (?, ?, 'zstd', ?, ?)
|
|
40
|
+
ON CONFLICT(hash) DO UPDATE SET hash = hash
|
|
41
|
+
RETURNING id`, [hash, compressed, decoded.bytes.byteLength, compressed.byteLength]);
|
|
42
|
+
const first = rows[0];
|
|
43
|
+
if (first === undefined) {
|
|
44
|
+
throw new Error('upsertBlobRef: RETURNING yielded no row');
|
|
45
|
+
}
|
|
46
|
+
caches.blobIds.set(hex, first.id);
|
|
47
|
+
return first.id;
|
|
48
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { WriteRefCaches } from './types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the `content_type_refs.id` for one content-type value,
|
|
5
|
+
* inserting the dictionary row when the value is new.
|
|
6
|
+
*
|
|
7
|
+
* `raw` is expected to be the already-canonicalised form produced by
|
|
8
|
+
* `normalizeContentType` (lower-cased, trimmed) — the write path
|
|
9
|
+
* canonicalises before storing, exactly as the legacy `pages.contentType`
|
|
10
|
+
* column did, so the dictionary's natural key never forks on case or
|
|
11
|
+
* whitespace variants of the same MIME. `normalized` is derived via the
|
|
12
|
+
* shared {@link ../../populate-ref-tables/normalize-mime.ts} (the same
|
|
13
|
+
* derivation the archive-migration populate uses, so live-crawl rows and
|
|
14
|
+
* migrated rows never fork on the same raw value); `category` via
|
|
15
|
+
* {@link ../../populate-ref-tables/classify-content-type.ts}.
|
|
16
|
+
*
|
|
17
|
+
* The content-type space is tiny (well under 1 000 distinct values on
|
|
18
|
+
* even the largest archives) so the cache converges after the first few
|
|
19
|
+
* pages and steady-state calls issue no SQL.
|
|
20
|
+
* @param qb - Knex instance or transaction connected to the archive DB.
|
|
21
|
+
* @param caches - The connection's write-side id caches; mutated in place.
|
|
22
|
+
* @param raw - Canonicalised content-type value. Pass the output of
|
|
23
|
+
* `normalizeContentType`; `null` / empty input must be short-circuited
|
|
24
|
+
* by the caller (a row without a content type stores `content_type_id
|
|
25
|
+
* = null`, not a dictionary entry).
|
|
26
|
+
* @returns The `content_type_refs.id` of the existing or new row.
|
|
27
|
+
* @example
|
|
28
|
+
* const id = await upsertContentTypeRef(knex, caches, 'text/html');
|
|
29
|
+
*/
|
|
30
|
+
export declare function upsertContentTypeRef(qb: Knex | Knex.Transaction, caches: WriteRefCaches, raw: string): Promise<number>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { classifyContentType } from '../../populate-ref-tables/classify-content-type.js';
|
|
2
|
+
import { normalizeMime } from '../../populate-ref-tables/normalize-mime.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the `content_type_refs.id` for one content-type value,
|
|
5
|
+
* inserting the dictionary row when the value is new.
|
|
6
|
+
*
|
|
7
|
+
* `raw` is expected to be the already-canonicalised form produced by
|
|
8
|
+
* `normalizeContentType` (lower-cased, trimmed) — the write path
|
|
9
|
+
* canonicalises before storing, exactly as the legacy `pages.contentType`
|
|
10
|
+
* column did, so the dictionary's natural key never forks on case or
|
|
11
|
+
* whitespace variants of the same MIME. `normalized` is derived via the
|
|
12
|
+
* shared {@link ../../populate-ref-tables/normalize-mime.ts} (the same
|
|
13
|
+
* derivation the archive-migration populate uses, so live-crawl rows and
|
|
14
|
+
* migrated rows never fork on the same raw value); `category` via
|
|
15
|
+
* {@link ../../populate-ref-tables/classify-content-type.ts}.
|
|
16
|
+
*
|
|
17
|
+
* The content-type space is tiny (well under 1 000 distinct values on
|
|
18
|
+
* even the largest archives) so the cache converges after the first few
|
|
19
|
+
* pages and steady-state calls issue no SQL.
|
|
20
|
+
* @param qb - Knex instance or transaction connected to the archive DB.
|
|
21
|
+
* @param caches - The connection's write-side id caches; mutated in place.
|
|
22
|
+
* @param raw - Canonicalised content-type value. Pass the output of
|
|
23
|
+
* `normalizeContentType`; `null` / empty input must be short-circuited
|
|
24
|
+
* by the caller (a row without a content type stores `content_type_id
|
|
25
|
+
* = null`, not a dictionary entry).
|
|
26
|
+
* @returns The `content_type_refs.id` of the existing or new row.
|
|
27
|
+
* @example
|
|
28
|
+
* const id = await upsertContentTypeRef(knex, caches, 'text/html');
|
|
29
|
+
*/
|
|
30
|
+
export async function upsertContentTypeRef(qb, caches, raw) {
|
|
31
|
+
const cached = caches.contentTypeIds.get(raw);
|
|
32
|
+
if (cached !== undefined) {
|
|
33
|
+
return cached;
|
|
34
|
+
}
|
|
35
|
+
const rows = await qb.raw(`INSERT INTO content_type_refs (raw, normalized, category)
|
|
36
|
+
VALUES (?, ?, ?)
|
|
37
|
+
ON CONFLICT(raw) DO UPDATE SET raw = raw
|
|
38
|
+
RETURNING id`, [raw, normalizeMime(raw), classifyContentType(raw)]);
|
|
39
|
+
const first = rows[0];
|
|
40
|
+
if (first === undefined) {
|
|
41
|
+
throw new Error(`upsertContentTypeRef: RETURNING yielded no row for ${raw}`);
|
|
42
|
+
}
|
|
43
|
+
caches.contentTypeIds.set(raw, first.id);
|
|
44
|
+
return first.id;
|
|
45
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { WriteRefCaches } from './types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the `json_refs.id` for one JSON payload string, inserting the
|
|
5
|
+
* deduplicated (hash-keyed) row when the payload is new.
|
|
6
|
+
*
|
|
7
|
+
* The payload is hashed over its raw UTF-8 bytes and stored
|
|
8
|
+
* zstd-compressed (`codec = 'zstd'`) with `size_raw` / `size_stored`
|
|
9
|
+
* bookkeeping — the same storage contract as `page_html_blobs` and the
|
|
10
|
+
* dictionary rows produced when migrating an existing archive
|
|
11
|
+
* (`populate-ref-tables/populate-json-refs.ts`). Identical payloads
|
|
12
|
+
* (e.g. the same `meta_extras` JSON across templated pages) produce one
|
|
13
|
+
* row; the compression cost is paid only on a cache-and-DB miss.
|
|
14
|
+
* @param qb - Knex instance or transaction connected to the archive DB.
|
|
15
|
+
* @param caches - The connection's write-side id caches; mutated in place.
|
|
16
|
+
* @param jsonText - Raw JSON string. Empty / null input must be
|
|
17
|
+
* short-circuited by the caller (`*_json_id = null`).
|
|
18
|
+
* @returns The `json_refs.id` of the existing or new row.
|
|
19
|
+
* @example
|
|
20
|
+
* const id = await upsertJsonRef(knex, caches, JSON.stringify(extras));
|
|
21
|
+
*/
|
|
22
|
+
export declare function upsertJsonRef(qb: Knex | Knex.Transaction, caches: WriteRefCaches, jsonText: string): Promise<number>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { zstdCompressSync } from 'node:zlib';
|
|
2
|
+
import { computeContentHash } from '../../populate-ref-tables/compute-content-hash.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the `json_refs.id` for one JSON payload string, inserting the
|
|
5
|
+
* deduplicated (hash-keyed) row when the payload is new.
|
|
6
|
+
*
|
|
7
|
+
* The payload is hashed over its raw UTF-8 bytes and stored
|
|
8
|
+
* zstd-compressed (`codec = 'zstd'`) with `size_raw` / `size_stored`
|
|
9
|
+
* bookkeeping — the same storage contract as `page_html_blobs` and the
|
|
10
|
+
* dictionary rows produced when migrating an existing archive
|
|
11
|
+
* (`populate-ref-tables/populate-json-refs.ts`). Identical payloads
|
|
12
|
+
* (e.g. the same `meta_extras` JSON across templated pages) produce one
|
|
13
|
+
* row; the compression cost is paid only on a cache-and-DB miss.
|
|
14
|
+
* @param qb - Knex instance or transaction connected to the archive DB.
|
|
15
|
+
* @param caches - The connection's write-side id caches; mutated in place.
|
|
16
|
+
* @param jsonText - Raw JSON string. Empty / null input must be
|
|
17
|
+
* short-circuited by the caller (`*_json_id = null`).
|
|
18
|
+
* @returns The `json_refs.id` of the existing or new row.
|
|
19
|
+
* @example
|
|
20
|
+
* const id = await upsertJsonRef(knex, caches, JSON.stringify(extras));
|
|
21
|
+
*/
|
|
22
|
+
export async function upsertJsonRef(qb, caches, jsonText) {
|
|
23
|
+
const rawBytes = Buffer.from(jsonText, 'utf8');
|
|
24
|
+
const hash = computeContentHash(rawBytes);
|
|
25
|
+
const hex = hash.toString('hex');
|
|
26
|
+
const cached = caches.jsonIds.get(hex);
|
|
27
|
+
if (cached !== undefined) {
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
30
|
+
const compressed = zstdCompressSync(rawBytes);
|
|
31
|
+
const rows = await qb.raw(`INSERT INTO json_refs (hash, json_text, codec, size_raw, size_stored)
|
|
32
|
+
VALUES (?, ?, 'zstd', ?, ?)
|
|
33
|
+
ON CONFLICT(hash) DO UPDATE SET hash = hash
|
|
34
|
+
RETURNING id`, [hash, compressed, rawBytes.byteLength, compressed.byteLength]);
|
|
35
|
+
const first = rows[0];
|
|
36
|
+
if (first === undefined) {
|
|
37
|
+
throw new Error('upsertJsonRef: RETURNING yielded no row');
|
|
38
|
+
}
|
|
39
|
+
caches.jsonIds.set(hex, first.id);
|
|
40
|
+
return first.id;
|
|
41
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { WriteRefCaches } from './types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Decomposes one response's headers and upserts them into the header
|
|
5
|
+
* dictionary tables (`header_name_refs` / `header_value_refs` /
|
|
6
|
+
* `header_sets` / `header_set_entries` / `header_flags`), returning the
|
|
7
|
+
* `header_sets.id` to store on the referring `content_items` /
|
|
8
|
+
* `resource_items` row.
|
|
9
|
+
*
|
|
10
|
+
* Runs per response (incrementally during the crawl), not deferred to
|
|
11
|
+
* crawl-end — the header dictionaries grow as pages arrive and the
|
|
12
|
+
* entity row can reference its `header_set_id` in the same write.
|
|
13
|
+
* Dedup makes the steady-state cost small: CDN-served sites reuse a
|
|
14
|
+
* handful of header shapes, so after the first few pages most calls
|
|
15
|
+
* resolve entirely from the in-process caches (warmed lazily from the DB
|
|
16
|
+
* on the first call, so a crawl that never sees headers pays nothing).
|
|
17
|
+
*
|
|
18
|
+
* Returns `null` for responses with no headers (`null` / `{}` /
|
|
19
|
+
* unparsable) — the referring row stores `header_set_id = null`, which
|
|
20
|
+
* is exactly how such rows read back through `header_flags` joins
|
|
21
|
+
* (absent flags row = no headers recorded).
|
|
22
|
+
* @param qb - Knex instance or transaction connected to the archive DB.
|
|
23
|
+
* @param caches - The connection's write-side id caches; the header
|
|
24
|
+
* sub-caches are created and attached on first use.
|
|
25
|
+
* @param responseHeaders - The response's header record as delivered by
|
|
26
|
+
* the scraper (`Record<name, string | string[]>`), or `null` /
|
|
27
|
+
* `undefined` when the response carried none.
|
|
28
|
+
* @returns The `header_sets.id`, or `null` when there is no set to store.
|
|
29
|
+
* @example
|
|
30
|
+
* const headerSetId = await upsertResponseHeaders(knex, caches, {
|
|
31
|
+
* 'content-type': 'text/html',
|
|
32
|
+
* 'cache-control': 'no-store',
|
|
33
|
+
* });
|
|
34
|
+
*/
|
|
35
|
+
export declare function upsertResponseHeaders(qb: Knex | Knex.Transaction, caches: WriteRefCaches, responseHeaders: Record<string, string | string[] | undefined> | null | undefined): Promise<number | null>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createHeaderTableCaches } from '../../populate-ref-tables/create-header-table-caches.js';
|
|
2
|
+
import { decomposeHeaderSet } from '../../populate-ref-tables/decompose-header-set.js';
|
|
3
|
+
import { upsertOneHeaderSet } from '../../populate-ref-tables/upsert-one-header-set.js';
|
|
4
|
+
/**
|
|
5
|
+
* Decomposes one response's headers and upserts them into the header
|
|
6
|
+
* dictionary tables (`header_name_refs` / `header_value_refs` /
|
|
7
|
+
* `header_sets` / `header_set_entries` / `header_flags`), returning the
|
|
8
|
+
* `header_sets.id` to store on the referring `content_items` /
|
|
9
|
+
* `resource_items` row.
|
|
10
|
+
*
|
|
11
|
+
* Runs per response (incrementally during the crawl), not deferred to
|
|
12
|
+
* crawl-end — the header dictionaries grow as pages arrive and the
|
|
13
|
+
* entity row can reference its `header_set_id` in the same write.
|
|
14
|
+
* Dedup makes the steady-state cost small: CDN-served sites reuse a
|
|
15
|
+
* handful of header shapes, so after the first few pages most calls
|
|
16
|
+
* resolve entirely from the in-process caches (warmed lazily from the DB
|
|
17
|
+
* on the first call, so a crawl that never sees headers pays nothing).
|
|
18
|
+
*
|
|
19
|
+
* Returns `null` for responses with no headers (`null` / `{}` /
|
|
20
|
+
* unparsable) — the referring row stores `header_set_id = null`, which
|
|
21
|
+
* is exactly how such rows read back through `header_flags` joins
|
|
22
|
+
* (absent flags row = no headers recorded).
|
|
23
|
+
* @param qb - Knex instance or transaction connected to the archive DB.
|
|
24
|
+
* @param caches - The connection's write-side id caches; the header
|
|
25
|
+
* sub-caches are created and attached on first use.
|
|
26
|
+
* @param responseHeaders - The response's header record as delivered by
|
|
27
|
+
* the scraper (`Record<name, string | string[]>`), or `null` /
|
|
28
|
+
* `undefined` when the response carried none.
|
|
29
|
+
* @returns The `header_sets.id`, or `null` when there is no set to store.
|
|
30
|
+
* @example
|
|
31
|
+
* const headerSetId = await upsertResponseHeaders(knex, caches, {
|
|
32
|
+
* 'content-type': 'text/html',
|
|
33
|
+
* 'cache-control': 'no-store',
|
|
34
|
+
* });
|
|
35
|
+
*/
|
|
36
|
+
export async function upsertResponseHeaders(qb, caches, responseHeaders) {
|
|
37
|
+
// Serialise exactly like the scraper's own JSON storage form so the
|
|
38
|
+
// decomposition (and its raw_json_hash dedup key) matches what a bulk
|
|
39
|
+
// archive populate would compute for the same response.
|
|
40
|
+
const rawJson = responseHeaders == null ? null : JSON.stringify(responseHeaders);
|
|
41
|
+
const decomposed = decomposeHeaderSet(rawJson);
|
|
42
|
+
if (decomposed === null) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (caches.headers === null) {
|
|
46
|
+
caches.headers = await createHeaderTableCaches(qb);
|
|
47
|
+
}
|
|
48
|
+
return await upsertOneHeaderSet(qb, decomposed, caches.headers);
|
|
49
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { WriteRefCaches } from './types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the `url_refs.id` for one URL string, inserting the row (with
|
|
5
|
+
* its decomposed `scheme` / `host` / `port` / `path` / `query_hash` /
|
|
6
|
+
* `fragment` columns) when the URL is not yet in the dictionary.
|
|
7
|
+
*
|
|
8
|
+
* The write path calls this for every URL it touches — page identities,
|
|
9
|
+
* anchor hrefs, image `src` / `currentSrc`, resource URLs, and URL-shaped
|
|
10
|
+
* meta columns — so the result is cached in `caches.urlIds` and a cache
|
|
11
|
+
* hit issues no SQL at all. Anchor hrefs and CDN-hosted image URLs repeat
|
|
12
|
+
* across thousands of pages, so the hit rate dominates after the first
|
|
13
|
+
* few pages.
|
|
14
|
+
*
|
|
15
|
+
* The cache-miss upsert is a single statement:
|
|
16
|
+
*
|
|
17
|
+
* ```sql
|
|
18
|
+
* INSERT INTO url_refs (...) VALUES (...)
|
|
19
|
+
* ON CONFLICT(url) DO UPDATE SET url = url
|
|
20
|
+
* RETURNING id
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* `DO UPDATE SET url = url` is a deliberate no-op update whose only
|
|
24
|
+
* purpose is to make `RETURNING id` yield the existing row's id on
|
|
25
|
+
* conflict. `INSERT OR IGNORE ... RETURNING id` must NOT be used here:
|
|
26
|
+
* `RETURNING` returns zero rows when the insert is ignored, which would
|
|
27
|
+
* leave the caller with no id and a `null` FK on the next entity INSERT.
|
|
28
|
+
* @param qb - Knex instance or transaction connected to the archive DB.
|
|
29
|
+
* Callers inside a transaction MUST pass the `trx` so the read sees
|
|
30
|
+
* uncommitted rows from the same transaction.
|
|
31
|
+
* @param caches - The connection's write-side id caches; mutated in place.
|
|
32
|
+
* @param url - The URL string to resolve. Stored verbatim as the natural key.
|
|
33
|
+
* @returns The `url_refs.id` of the existing or newly inserted row.
|
|
34
|
+
* @example
|
|
35
|
+
* const id = await upsertUrlRef(knex, caches, 'https://example.com/a');
|
|
36
|
+
* const again = await upsertUrlRef(knex, caches, 'https://example.com/a');
|
|
37
|
+
* // id === again, second call hits the cache and issues no SQL
|
|
38
|
+
*/
|
|
39
|
+
export declare function upsertUrlRef(qb: Knex | Knex.Transaction, caches: WriteRefCaches, url: string): Promise<number>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { decomposeUrl } from '../../populate-ref-tables/decompose-url.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the `url_refs.id` for one URL string, inserting the row (with
|
|
4
|
+
* its decomposed `scheme` / `host` / `port` / `path` / `query_hash` /
|
|
5
|
+
* `fragment` columns) when the URL is not yet in the dictionary.
|
|
6
|
+
*
|
|
7
|
+
* The write path calls this for every URL it touches — page identities,
|
|
8
|
+
* anchor hrefs, image `src` / `currentSrc`, resource URLs, and URL-shaped
|
|
9
|
+
* meta columns — so the result is cached in `caches.urlIds` and a cache
|
|
10
|
+
* hit issues no SQL at all. Anchor hrefs and CDN-hosted image URLs repeat
|
|
11
|
+
* across thousands of pages, so the hit rate dominates after the first
|
|
12
|
+
* few pages.
|
|
13
|
+
*
|
|
14
|
+
* The cache-miss upsert is a single statement:
|
|
15
|
+
*
|
|
16
|
+
* ```sql
|
|
17
|
+
* INSERT INTO url_refs (...) VALUES (...)
|
|
18
|
+
* ON CONFLICT(url) DO UPDATE SET url = url
|
|
19
|
+
* RETURNING id
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* `DO UPDATE SET url = url` is a deliberate no-op update whose only
|
|
23
|
+
* purpose is to make `RETURNING id` yield the existing row's id on
|
|
24
|
+
* conflict. `INSERT OR IGNORE ... RETURNING id` must NOT be used here:
|
|
25
|
+
* `RETURNING` returns zero rows when the insert is ignored, which would
|
|
26
|
+
* leave the caller with no id and a `null` FK on the next entity INSERT.
|
|
27
|
+
* @param qb - Knex instance or transaction connected to the archive DB.
|
|
28
|
+
* Callers inside a transaction MUST pass the `trx` so the read sees
|
|
29
|
+
* uncommitted rows from the same transaction.
|
|
30
|
+
* @param caches - The connection's write-side id caches; mutated in place.
|
|
31
|
+
* @param url - The URL string to resolve. Stored verbatim as the natural key.
|
|
32
|
+
* @returns The `url_refs.id` of the existing or newly inserted row.
|
|
33
|
+
* @example
|
|
34
|
+
* const id = await upsertUrlRef(knex, caches, 'https://example.com/a');
|
|
35
|
+
* const again = await upsertUrlRef(knex, caches, 'https://example.com/a');
|
|
36
|
+
* // id === again, second call hits the cache and issues no SQL
|
|
37
|
+
*/
|
|
38
|
+
export async function upsertUrlRef(qb, caches, url) {
|
|
39
|
+
const cached = caches.urlIds.get(url);
|
|
40
|
+
if (cached !== undefined) {
|
|
41
|
+
return cached;
|
|
42
|
+
}
|
|
43
|
+
const decomposed = decomposeUrl(url);
|
|
44
|
+
const rows = await qb.raw(`INSERT INTO url_refs (url, scheme, host, port, path, query_hash, fragment)
|
|
45
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
46
|
+
ON CONFLICT(url) DO UPDATE SET url = url
|
|
47
|
+
RETURNING id`, [
|
|
48
|
+
url,
|
|
49
|
+
decomposed.scheme,
|
|
50
|
+
decomposed.host,
|
|
51
|
+
decomposed.port,
|
|
52
|
+
decomposed.path,
|
|
53
|
+
decomposed.query_hash,
|
|
54
|
+
decomposed.fragment,
|
|
55
|
+
]);
|
|
56
|
+
const first = rows[0];
|
|
57
|
+
if (first === undefined) {
|
|
58
|
+
throw new Error(`upsertUrlRef: RETURNING yielded no row for ${url}`);
|
|
59
|
+
}
|
|
60
|
+
caches.urlIds.set(url, first.id);
|
|
61
|
+
return first.id;
|
|
62
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the stored analysis violations with a freshly generated set.
|
|
4
|
+
*
|
|
5
|
+
* The function resolves every violation URL to a `content_items.id` (via
|
|
6
|
+
* the `url_refs` dictionary), deduplicates message/code text through
|
|
7
|
+
* `analysis_text_refs`, and rewrites `analysis_violations` in one
|
|
8
|
+
* transaction. This is the storage-side counterpart of the query-layer
|
|
9
|
+
* `getViolations` read path.
|
|
10
|
+
*
|
|
11
|
+
* Violations whose `url` still carries the pre-#225 corrupted position
|
|
12
|
+
* suffix (and that do not already carry explicit `line`/`col`) are repaired
|
|
13
|
+
* in place: {@link parseLegacyCorruptedUrl} splits the suffix off before URL
|
|
14
|
+
* resolution, so both freshly-analyzed and legacy-JSON-backfilled data end
|
|
15
|
+
* up in the same clean shape.
|
|
16
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
17
|
+
* @param violations - Flat violation list from the analyze phase.
|
|
18
|
+
*/
|
|
19
|
+
export declare function replaceAnalysisViolations(knex: Knex, violations: readonly {
|
|
20
|
+
validator: string;
|
|
21
|
+
severity: string;
|
|
22
|
+
rule: string;
|
|
23
|
+
code?: string | null;
|
|
24
|
+
message: string;
|
|
25
|
+
url: string;
|
|
26
|
+
line?: number | null;
|
|
27
|
+
col?: number | null;
|
|
28
|
+
}[]): Promise<void>;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { eachSplitted } from '../../../utils/array/each-splitted.js';
|
|
3
|
+
/**
|
|
4
|
+
* Matches the position suffix that `@nitpicker/analyze-markuplint` and
|
|
5
|
+
* `@nitpicker/analyze-textlint` used to append to `Violation.url` before
|
|
6
|
+
* issue #225 (e.g. `https://example.com/page (5:10)`). Anchored to the end
|
|
7
|
+
* of the string so a URL that legitimately contains a similar-looking
|
|
8
|
+
* substring earlier on is not mistaken for the suffix.
|
|
9
|
+
*/
|
|
10
|
+
const LEGACY_CORRUPTED_URL_PATTERN = /^(.+) \((\d+):(\d+)\)$/;
|
|
11
|
+
/**
|
|
12
|
+
* Splits a legacy corrupted `Violation.url` (page URL + trailing
|
|
13
|
+
* `" (line:col)"`) back into a clean URL and its position, or returns
|
|
14
|
+
* `null` when `url` does not match the corrupted shape (e.g. a clean axe
|
|
15
|
+
* URL, or a URL that already carries explicit `line`/`col`).
|
|
16
|
+
* @param url - The `Violation.url` value as read from storage or input.
|
|
17
|
+
* @returns The recovered `{ url, line, col }`, or `null` when `url` is not corrupted.
|
|
18
|
+
*/
|
|
19
|
+
function parseLegacyCorruptedUrl(url) {
|
|
20
|
+
const match = LEGACY_CORRUPTED_URL_PATTERN.exec(url);
|
|
21
|
+
if (!match) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const [, cleanUrl, line, col] = match;
|
|
25
|
+
return { url: cleanUrl, line: Number(line), col: Number(col) };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Adds the `line`/`col` columns to `analysis_violations` when they are
|
|
29
|
+
* missing, i.e. when the archive's table was provisioned before issue #225.
|
|
30
|
+
*
|
|
31
|
+
* `create-adjunct-tables.ts` never mutates a table it finds already
|
|
32
|
+
* present — catching up an *existing* table normally requires a
|
|
33
|
+
* version-gated migration script (see `scripts/migrate-to-0.13.mjs`). This
|
|
34
|
+
* function relies on the exception documented in ARCHITECTURE.md's
|
|
35
|
+
* invariants list: a nullable, additive column on a table with a single
|
|
36
|
+
* write path may self-heal here without a version bump, because
|
|
37
|
+
* `replaceAnalysisViolations` is that single write path for
|
|
38
|
+
* `analysis_violations`. Runs before the transaction below so the DDL is
|
|
39
|
+
* not mixed with the DML rewrite.
|
|
40
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
41
|
+
*/
|
|
42
|
+
async function ensureLineColColumns(knex) {
|
|
43
|
+
if (!(await knex.schema.hasColumn('analysis_violations', 'line'))) {
|
|
44
|
+
await knex.schema.alterTable('analysis_violations', (table) => {
|
|
45
|
+
table.integer('line').nullable();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (!(await knex.schema.hasColumn('analysis_violations', 'col'))) {
|
|
49
|
+
await knex.schema.alterTable('analysis_violations', (table) => {
|
|
50
|
+
table.integer('col').nullable();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Replaces the stored analysis violations with a freshly generated set.
|
|
56
|
+
*
|
|
57
|
+
* The function resolves every violation URL to a `content_items.id` (via
|
|
58
|
+
* the `url_refs` dictionary), deduplicates message/code text through
|
|
59
|
+
* `analysis_text_refs`, and rewrites `analysis_violations` in one
|
|
60
|
+
* transaction. This is the storage-side counterpart of the query-layer
|
|
61
|
+
* `getViolations` read path.
|
|
62
|
+
*
|
|
63
|
+
* Violations whose `url` still carries the pre-#225 corrupted position
|
|
64
|
+
* suffix (and that do not already carry explicit `line`/`col`) are repaired
|
|
65
|
+
* in place: {@link parseLegacyCorruptedUrl} splits the suffix off before URL
|
|
66
|
+
* resolution, so both freshly-analyzed and legacy-JSON-backfilled data end
|
|
67
|
+
* up in the same clean shape.
|
|
68
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
69
|
+
* @param violations - Flat violation list from the analyze phase.
|
|
70
|
+
*/
|
|
71
|
+
export async function replaceAnalysisViolations(knex, violations) {
|
|
72
|
+
await ensureLineColColumns(knex);
|
|
73
|
+
const repaired = violations.map((violation) => {
|
|
74
|
+
if (violation.line != null || violation.col != null) {
|
|
75
|
+
return violation;
|
|
76
|
+
}
|
|
77
|
+
const parsed = parseLegacyCorruptedUrl(violation.url);
|
|
78
|
+
return parsed
|
|
79
|
+
? { ...violation, url: parsed.url, line: parsed.line, col: parsed.col }
|
|
80
|
+
: violation;
|
|
81
|
+
});
|
|
82
|
+
await knex.transaction(async (trx) => {
|
|
83
|
+
await trx('analysis_violations').delete();
|
|
84
|
+
await trx('analysis_text_refs').delete();
|
|
85
|
+
if (repaired.length === 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const urls = [...new Set(repaired.map((v) => v.url))];
|
|
89
|
+
const pageIdByUrl = new Map();
|
|
90
|
+
await eachSplitted(urls, 500, async (chunk) => {
|
|
91
|
+
const pageRows = await trx('content_items')
|
|
92
|
+
.join('url_refs', 'url_refs.id', 'content_items.url_id')
|
|
93
|
+
.select('content_items.id as id', 'url_refs.url as url')
|
|
94
|
+
.whereIn('url_refs.url', chunk);
|
|
95
|
+
for (const row of pageRows) {
|
|
96
|
+
pageIdByUrl.set(row.url, row.id);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
if (pageIdByUrl.size !== urls.length) {
|
|
100
|
+
const missing = urls.filter((url) => !pageIdByUrl.has(url));
|
|
101
|
+
throw new Error(`replaceAnalysisViolations: could not resolve ${missing.length} page URL(s): ${missing[0]}`);
|
|
102
|
+
}
|
|
103
|
+
const textByValue = new Map();
|
|
104
|
+
const resolveTextId = async (text) => {
|
|
105
|
+
const cached = textByValue.get(text);
|
|
106
|
+
if (cached != null) {
|
|
107
|
+
return cached;
|
|
108
|
+
}
|
|
109
|
+
const sha256 = createHash('sha256').update(text).digest('hex');
|
|
110
|
+
await trx('analysis_text_refs')
|
|
111
|
+
.insert({ text, sha256 })
|
|
112
|
+
.onConflict(['sha256', 'text'])
|
|
113
|
+
.ignore();
|
|
114
|
+
const [existing] = await trx
|
|
115
|
+
.select('id')
|
|
116
|
+
.from('analysis_text_refs')
|
|
117
|
+
.where('sha256', sha256)
|
|
118
|
+
.where('text', text);
|
|
119
|
+
if (!existing) {
|
|
120
|
+
throw new Error(`replaceAnalysisViolations: failed to resolve text ref for ${text}`);
|
|
121
|
+
}
|
|
122
|
+
textByValue.set(text, existing.id);
|
|
123
|
+
return existing.id;
|
|
124
|
+
};
|
|
125
|
+
const rows = [];
|
|
126
|
+
for (const violation of repaired) {
|
|
127
|
+
const pageId = pageIdByUrl.get(violation.url);
|
|
128
|
+
if (!pageId) {
|
|
129
|
+
throw new Error(`replaceAnalysisViolations: could not resolve page URL: ${violation.url}`);
|
|
130
|
+
}
|
|
131
|
+
const messageTextId = await resolveTextId(violation.message);
|
|
132
|
+
const codeValue = violation.code ?? '';
|
|
133
|
+
const codeTextId = codeValue === '' ? null : await resolveTextId(codeValue);
|
|
134
|
+
rows.push({
|
|
135
|
+
page_id: pageId,
|
|
136
|
+
validator: violation.validator,
|
|
137
|
+
severity: violation.severity,
|
|
138
|
+
rule: violation.rule,
|
|
139
|
+
message_text_id: messageTextId,
|
|
140
|
+
line: violation.line ?? null,
|
|
141
|
+
col: violation.col ?? null,
|
|
142
|
+
code_text_id: codeTextId,
|
|
143
|
+
page_url_sort_key: violation.url,
|
|
144
|
+
message_sort_key: violation.message,
|
|
145
|
+
code_sort_key: codeValue,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
await eachSplitted(rows, 500, async (chunk) => {
|
|
149
|
+
await trx('analysis_violations').insert(chunk);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves all anchors (outgoing links) on a specific page.
|
|
4
|
+
* Joins `anchor_edges` with `content_items` (+ `page_meta` for title) to
|
|
5
|
+
* resolve link destinations.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @param pageId - The database ID of the page whose anchors to retrieve.
|
|
8
|
+
* @returns An array of anchor records with resolved URL, title, status, and content type.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getAnchorsOnPage(knex: Knex, pageId: number): Promise<any[]>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves all anchors (outgoing links) on a specific page.
|
|
3
|
+
* Joins `anchor_edges` with `content_items` (+ `page_meta` for title) to
|
|
4
|
+
* resolve link destinations.
|
|
5
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
6
|
+
* @param pageId - The database ID of the page whose anchors to retrieve.
|
|
7
|
+
* @returns An array of anchor records with resolved URL, title, status, and content type.
|
|
8
|
+
*/
|
|
9
|
+
export async function getAnchorsOnPage(knex, pageId) {
|
|
10
|
+
const res = await knex
|
|
11
|
+
.select('dest_url.url as url', 'title_ref.text as title', 'dest.status as status', 'dest.status_text as statusText', 'ctr.raw as contentType', 'anchor_edges.first_hash as hash', 'first_text.text as textContent')
|
|
12
|
+
.from('anchor_edges')
|
|
13
|
+
.join('content_items as dest', 'anchor_edges.href_page_id', '=', 'dest.id')
|
|
14
|
+
.join('url_refs as dest_url', 'dest.url_id', '=', 'dest_url.id')
|
|
15
|
+
.leftJoin('content_type_refs as ctr', 'dest.content_type_id', '=', 'ctr.id')
|
|
16
|
+
.leftJoin('page_meta as pm', 'dest.id', '=', 'pm.page_id')
|
|
17
|
+
.leftJoin('text_refs as title_ref', 'pm.title_text_id', '=', 'title_ref.id')
|
|
18
|
+
.leftJoin('text_refs as first_text', 'anchor_edges.first_text_id', '=', 'first_text.id')
|
|
19
|
+
.where('anchor_edges.page_id', pageId);
|
|
20
|
+
return res;
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves the base URL of the crawl session from the `info` table.
|
|
4
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
5
|
+
* @returns The base URL string.
|
|
6
|
+
* @throws {Error} If no base URL is found in the database.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getBaseUrl(knex: Knex): Promise<string>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves the base URL of the crawl session from the `info` table.
|
|
3
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
4
|
+
* @returns The base URL string.
|
|
5
|
+
* @throws {Error} If no base URL is found in the database.
|
|
6
|
+
*/
|
|
7
|
+
export async function getBaseUrl(knex) {
|
|
8
|
+
const selected = await knex.select('baseUrl').from('info');
|
|
9
|
+
if (!selected[0]) {
|
|
10
|
+
throw new Error('No baseUrl');
|
|
11
|
+
}
|
|
12
|
+
const [{ baseUrl }] = selected;
|
|
13
|
+
return baseUrl || '';
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Config } from '../../types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves the full crawl configuration from the `info` table.
|
|
5
|
+
* Deserializes JSON-encoded fields (`roots`, `excludes`, `excludeKeywords`, `excludeUrls`).
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @returns The parsed {@link Config} object.
|
|
8
|
+
* @throws {Error} If no configuration is found in the database.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getConfig(knex: Knex): Promise<Config>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { dbLog } from '../../debug.js';
|
|
2
|
+
import { getJSON } from '../../get-json.js';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves the full crawl configuration from the `info` table.
|
|
5
|
+
* Deserializes JSON-encoded fields (`roots`, `excludes`, `excludeKeywords`, `excludeUrls`).
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @returns The parsed {@link Config} object.
|
|
8
|
+
* @throws {Error} If no configuration is found in the database.
|
|
9
|
+
*/
|
|
10
|
+
export async function getConfig(knex) {
|
|
11
|
+
const [config] = await knex.select('*').from('info');
|
|
12
|
+
if (!config) {
|
|
13
|
+
throw new Error('No config');
|
|
14
|
+
}
|
|
15
|
+
const opt = {
|
|
16
|
+
...config,
|
|
17
|
+
excludes: getJSON(config.excludes, []),
|
|
18
|
+
excludeKeywords: getJSON(config.excludeKeywords, []),
|
|
19
|
+
excludeUrls: getJSON(config.excludeUrls, []),
|
|
20
|
+
roots: getJSON(config.roots, []),
|
|
21
|
+
retry: config.retry ?? 3,
|
|
22
|
+
};
|
|
23
|
+
// @ts-expect-error — `id` is the primary key, not part of the public Config shape
|
|
24
|
+
delete opt.id;
|
|
25
|
+
dbLog('Table `info`: %O => %O', config, opt);
|
|
26
|
+
return opt;
|
|
27
|
+
}
|