@nitpicker/crawler 0.13.0 → 0.15.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/lib/archive/archive-accessor.d.ts +56 -0
- package/lib/archive/archive-accessor.js +72 -0
- package/lib/archive/archive.d.ts +108 -9
- package/lib/archive/archive.js +109 -7
- package/lib/archive/body-hash/compute-body-hash.d.ts +22 -0
- package/lib/archive/body-hash/compute-body-hash.js +31 -0
- package/lib/archive/body-hash/extract-body.d.ts +18 -0
- package/lib/archive/body-hash/extract-body.js +29 -0
- package/lib/archive/body-hash/mask-dynamic-ids.d.ts +33 -0
- package/lib/archive/body-hash/mask-dynamic-ids.js +44 -0
- package/lib/archive/body-hash/normalize-url-like-strings.d.ts +16 -0
- package/lib/archive/body-hash/normalize-url-like-strings.js +19 -0
- package/lib/archive/cache/clear-archive-cache-entry.d.ts +19 -0
- package/lib/archive/cache/clear-archive-cache-entry.js +25 -0
- package/lib/archive/cache/clear-archive-cache-root.d.ts +26 -0
- package/lib/archive/cache/clear-archive-cache-root.js +32 -0
- package/lib/archive/cache/list-archive-cache-entries.d.ts +23 -0
- package/lib/archive/cache/list-archive-cache-entries.js +125 -0
- package/lib/archive/cache/path-exists.d.ts +9 -0
- package/lib/archive/cache/path-exists.js +18 -0
- package/lib/archive/cache/types.d.ts +33 -0
- package/lib/archive/cache/types.js +1 -0
- package/lib/archive/create-adjunct-tables.d.ts +18 -0
- package/lib/archive/create-adjunct-tables.js +277 -0
- package/lib/archive/create-entity-tables.d.ts +14 -0
- package/lib/archive/create-entity-tables.js +52 -1
- package/lib/archive/database.d.ts +91 -2
- package/lib/archive/database.js +128 -0
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +1 -0
- package/lib/archive/db-ops/_shared/compress-payload.d.ts +26 -0
- package/lib/archive/db-ops/_shared/compress-payload.js +30 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.js +1 -0
- package/lib/archive/db-ops/_shared/types.d.ts +2 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.js +3 -3
- package/lib/archive/db-ops/analysis/replace-page-templates.d.ts +29 -0
- package/lib/archive/db-ops/analysis/replace-page-templates.js +84 -0
- package/lib/archive/db-ops/analysis/types.d.ts +61 -0
- package/lib/archive/db-ops/analysis/types.js +1 -0
- package/lib/archive/db-ops/config/info-column-allowlist.js +1 -0
- package/lib/archive/db-ops/console-logs/compute-console-log-hash.d.ts +19 -0
- package/lib/archive/db-ops/console-logs/compute-console-log-hash.js +30 -0
- package/lib/archive/db-ops/console-logs/replace-console-logs.d.ts +34 -0
- package/lib/archive/db-ops/console-logs/replace-console-logs.js +123 -0
- package/lib/archive/db-ops/console-logs/stringify-console-log-args.d.ts +16 -0
- package/lib/archive/db-ops/console-logs/stringify-console-log-args.js +26 -0
- package/lib/archive/db-ops/console-logs/types.d.ts +38 -0
- package/lib/archive/db-ops/console-logs/types.js +1 -0
- package/lib/archive/db-ops/console-logs/upsert-console-log-item.d.ts +29 -0
- package/lib/archive/db-ops/console-logs/upsert-console-log-item.js +52 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +11 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +21 -4
- package/lib/archive/db-ops/inventory/record-inventory-run.js +1 -0
- package/lib/archive/db-ops/lifecycle/init.d.ts +25 -10
- package/lib/archive/db-ops/lifecycle/init.js +39 -10
- package/lib/archive/db-ops/meta/get-audios-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-audios-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-buttons-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-buttons-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-canvases-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-canvases-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-headings-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-headings-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-iframes-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-iframes-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-main-content-images-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-main-content-images-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-main-content-tables-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-main-content-tables-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-videos-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-videos-of-page.js +15 -0
- package/lib/archive/db-ops/outages/close-network-outage.d.ts +12 -0
- package/lib/archive/db-ops/outages/close-network-outage.js +15 -0
- package/lib/archive/db-ops/outages/close-stale-open-network-outages.d.ts +26 -0
- package/lib/archive/db-ops/outages/close-stale-open-network-outages.js +42 -0
- package/lib/archive/db-ops/outages/compute-outage-clamp-timestamp.d.ts +26 -0
- package/lib/archive/db-ops/outages/compute-outage-clamp-timestamp.js +31 -0
- package/lib/archive/db-ops/outages/insert-network-outage.d.ts +14 -0
- package/lib/archive/db-ops/outages/insert-network-outage.js +29 -0
- package/lib/archive/db-ops/outages/list-network-outages.d.ts +28 -0
- package/lib/archive/db-ops/outages/list-network-outages.js +42 -0
- package/lib/archive/db-ops/pages/read/build-page-query.js +1 -1
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +19 -10
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +18 -3
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +48 -10
- package/lib/archive/db-ops/pages/write/insert-audios.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-audios.js +25 -0
- package/lib/archive/db-ops/pages/write/insert-buttons.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-buttons.js +29 -0
- package/lib/archive/db-ops/pages/write/insert-canvases.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-canvases.js +26 -0
- package/lib/archive/db-ops/pages/write/insert-headings.d.ts +15 -0
- package/lib/archive/db-ops/pages/write/insert-headings.js +27 -0
- package/lib/archive/db-ops/pages/write/insert-iframes.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-iframes.js +28 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-images.d.ts +20 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-images.js +32 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-tables.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-tables.js +29 -0
- package/lib/archive/db-ops/pages/write/insert-page.js +15 -2
- package/lib/archive/db-ops/pages/write/insert-videos.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-videos.js +28 -0
- package/lib/archive/db-ops/pages/write/update-page.js +33 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.js +6 -6
- package/lib/archive/filesystem/output-binary.d.ts +12 -0
- package/lib/archive/filesystem/output-binary.js +17 -0
- package/lib/archive/get-failed-page-messages.d.ts +20 -8
- package/lib/archive/get-failed-page-messages.js +29 -15
- package/lib/archive/init-schema.js +2 -1
- package/lib/archive/meta/compute-main-contents-denormalized.d.ts +22 -0
- package/lib/archive/meta/compute-main-contents-denormalized.js +63 -0
- package/lib/archive/meta/types.d.ts +263 -0
- package/lib/archive/migrate-content-items-alias-of-id.d.ts +47 -0
- package/lib/archive/migrate-content-items-alias-of-id.js +58 -0
- package/lib/archive/migrate-info-main-content-selector.d.ts +11 -0
- package/lib/archive/migrate-info-main-content-selector.js +24 -0
- package/lib/archive/migrate-inventory-runs-invalid-skipped.d.ts +11 -0
- package/lib/archive/migrate-inventory-runs-invalid-skipped.js +24 -0
- package/lib/archive/migrate-main-contents-columns.d.ts +21 -0
- package/lib/archive/migrate-main-contents-columns.js +50 -0
- package/lib/archive/migrate-page-meta-body-hash.d.ts +39 -0
- package/lib/archive/migrate-page-meta-body-hash.js +52 -0
- package/lib/archive/migrate-page-meta-console-error-count.d.ts +22 -0
- package/lib/archive/migrate-page-meta-console-error-count.js +34 -0
- package/lib/archive/page.d.ts +145 -3
- package/lib/archive/page.js +195 -0
- package/lib/archive/types.d.ts +86 -2
- package/lib/archive/url-alias/compute-tier-a-alias-key.d.ts +31 -0
- package/lib/archive/url-alias/compute-tier-a-alias-key.js +36 -0
- package/lib/archive/url-alias/compute-tier-b-alias-key.d.ts +19 -0
- package/lib/archive/url-alias/compute-tier-b-alias-key.js +30 -0
- package/lib/archive/url-alias/format-alias-key.d.ts +13 -0
- package/lib/archive/url-alias/format-alias-key.js +14 -0
- package/lib/archive/url-alias/parse-alias-key-parts.d.ts +15 -0
- package/lib/archive/url-alias/parse-alias-key-parts.js +40 -0
- package/lib/archive/url-alias/types.d.ts +7 -0
- package/lib/archive/url-alias/types.js +1 -0
- package/lib/crawler/choose-probe-host.d.ts +24 -0
- package/lib/crawler/choose-probe-host.js +38 -0
- package/lib/crawler/clear-dns-burned-host-cache.js +2 -0
- package/lib/crawler/crawler.js +261 -2
- package/lib/crawler/dns-burned-host-burn-timestamps.d.ts +17 -0
- package/lib/crawler/dns-burned-host-burn-timestamps.js +17 -0
- package/lib/crawler/evict-network-classified-destination-cache-entries.d.ts +26 -0
- package/lib/crawler/evict-network-classified-destination-cache-entries.js +34 -0
- package/lib/crawler/evict-outage-tainted-dns-burns.d.ts +23 -0
- package/lib/crawler/evict-outage-tainted-dns-burns.js +26 -0
- package/lib/crawler/fetch-destination.js +2 -0
- package/lib/crawler/link-to-page-data.js +2 -0
- package/lib/crawler/network-gate.d.ts +49 -0
- package/lib/crawler/network-gate.js +78 -0
- package/lib/crawler/network-outage-detector.d.ts +51 -0
- package/lib/crawler/network-outage-detector.js +81 -0
- package/lib/crawler/network-outage-summary-counter.d.ts +21 -0
- package/lib/crawler/network-outage-summary-counter.js +18 -0
- package/lib/crawler/probe-network.d.ts +26 -0
- package/lib/crawler/probe-network.js +21 -0
- package/lib/crawler/resource-to-page-data.js +2 -0
- package/lib/crawler/types.d.ts +152 -1
- package/lib/crawler-orchestrator.d.ts +62 -18
- package/lib/crawler-orchestrator.js +163 -26
- package/lib/crawler.d.ts +21 -0
- package/lib/crawler.js +17 -0
- package/lib/is-within-outage-window.d.ts +49 -0
- package/lib/is-within-outage-window.js +33 -0
- package/lib/network-related-error-kinds.d.ts +23 -0
- package/lib/network-related-error-kinds.js +28 -0
- package/lib/types.d.ts +9 -1
- package/lib/utils/array/each-splitted.d.ts +1 -1
- package/lib/utils/compute-file-sha256.d.ts +17 -17
- package/lib/utils/compute-file-sha256.js +18 -47
- package/package.json +3 -3
package/lib/archive/database.js
CHANGED
|
@@ -8,12 +8,14 @@ import { emitError } from '../utils/error/emit-error.js';
|
|
|
8
8
|
import { createWriteRefCaches } from './db-ops/_shared/create-write-ref-caches.js';
|
|
9
9
|
import { retrySetting } from './db-ops/_shared/retry-setting.js';
|
|
10
10
|
import { replaceAnalysisViolations as replaceAnalysisViolationsOp } from './db-ops/analysis/replace-analysis-violations.js';
|
|
11
|
+
import { replacePageTemplates as replacePageTemplatesOp } from './db-ops/analysis/replace-page-templates.js';
|
|
11
12
|
import { getAnchorsOnPage as getAnchorsOnPageOp } from './db-ops/anchors/get-anchors-on-page.js';
|
|
12
13
|
import { getBaseUrl as getBaseUrlOp } from './db-ops/config/get-base-url.js';
|
|
13
14
|
import { getConfig as getConfigOp } from './db-ops/config/get-config.js';
|
|
14
15
|
import { getName as getNameOp } from './db-ops/config/get-name.js';
|
|
15
16
|
import { setConfig as setConfigOp } from './db-ops/config/set-config.js';
|
|
16
17
|
import { updateConfig as updateConfigOp } from './db-ops/config/update-config.js';
|
|
18
|
+
import { replaceConsoleLogs as replaceConsoleLogsOp } from './db-ops/console-logs/replace-console-logs.js';
|
|
17
19
|
import { insertCrawlError as insertCrawlErrorOp } from './db-ops/errors/insert-crawl-error.js';
|
|
18
20
|
import { insertPageError as insertPageErrorOp } from './db-ops/errors/insert-page-error.js';
|
|
19
21
|
import { listDnsBurnedHostCandidates as listDnsBurnedHostCandidatesOp } from './db-ops/errors/list-dns-burned-host-candidates.js';
|
|
@@ -22,8 +24,19 @@ import { recordInventoryRun as recordInventoryRunOp } from './db-ops/inventory/r
|
|
|
22
24
|
import { checkpoint as checkpointOp } from './db-ops/lifecycle/checkpoint.js';
|
|
23
25
|
import { destroy as destroyOp } from './db-ops/lifecycle/destroy.js';
|
|
24
26
|
import { init as initOp } from './db-ops/lifecycle/init.js';
|
|
27
|
+
import { getAudiosOfPage as getAudiosOfPageOp } from './db-ops/meta/get-audios-of-page.js';
|
|
28
|
+
import { getButtonsOfPage as getButtonsOfPageOp } from './db-ops/meta/get-buttons-of-page.js';
|
|
29
|
+
import { getCanvasesOfPage as getCanvasesOfPageOp } from './db-ops/meta/get-canvases-of-page.js';
|
|
30
|
+
import { getHeadingsOfPage as getHeadingsOfPageOp } from './db-ops/meta/get-headings-of-page.js';
|
|
31
|
+
import { getIframesOfPage as getIframesOfPageOp } from './db-ops/meta/get-iframes-of-page.js';
|
|
25
32
|
import { getJsonLdOfPage as getJsonLdOfPageOp } from './db-ops/meta/get-jsonld-of-page.js';
|
|
33
|
+
import { getMainContentImagesOfPage as getMainContentImagesOfPageOp } from './db-ops/meta/get-main-content-images-of-page.js';
|
|
34
|
+
import { getMainContentTablesOfPage as getMainContentTablesOfPageOp } from './db-ops/meta/get-main-content-tables-of-page.js';
|
|
26
35
|
import { getTagsOfPage as getTagsOfPageOp } from './db-ops/meta/get-tags-of-page.js';
|
|
36
|
+
import { getVideosOfPage as getVideosOfPageOp } from './db-ops/meta/get-videos-of-page.js';
|
|
37
|
+
import { closeNetworkOutage as closeNetworkOutageOp } from './db-ops/outages/close-network-outage.js';
|
|
38
|
+
import { insertNetworkOutage as insertNetworkOutageOp } from './db-ops/outages/insert-network-outage.js';
|
|
39
|
+
import { listNetworkOutages as listNetworkOutagesOp } from './db-ops/outages/list-network-outages.js';
|
|
27
40
|
import { setUrlOrder as setUrlOrderOp } from './db-ops/pages/order/set-url-order.js';
|
|
28
41
|
import { getCrawlingState as getCrawlingStateOp } from './db-ops/pages/read/get-crawling-state.js';
|
|
29
42
|
import { getExistingPageUrls as getExistingPageUrlsOp } from './db-ops/pages/read/get-existing-page-urls.js';
|
|
@@ -112,6 +125,15 @@ export class Database extends EventEmitter {
|
|
|
112
125
|
async checkpoint() {
|
|
113
126
|
await checkpointOp(this.#instance);
|
|
114
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Closes an outage row by stamping `ended_at` — a no-op if the row is
|
|
130
|
+
* already closed. Delegates to {@link closeNetworkOutageOp}.
|
|
131
|
+
* @param id - The `network_outages.id` to close.
|
|
132
|
+
* @param endedAt - Epoch ms the outage is considered to have ended.
|
|
133
|
+
*/
|
|
134
|
+
async closeNetworkOutage(id, endedAt) {
|
|
135
|
+
return emitErrorAndRetry(this, 'Database.closeNetworkOutage', async () => await closeNetworkOutageOp(this.#instance, id, endedAt), retrySetting);
|
|
136
|
+
}
|
|
115
137
|
/**
|
|
116
138
|
* Destroys the database connection, releasing all pooled resources.
|
|
117
139
|
* Delegates to {@link destroyOp}.
|
|
@@ -128,6 +150,14 @@ export class Database extends EventEmitter {
|
|
|
128
150
|
async getAnchorsOnPage(pageId) {
|
|
129
151
|
return emitErrorAndRetry(this, 'Database.getAnchorsOnPage', async () => await getAnchorsOnPageOp(this.#instance, pageId), retrySetting);
|
|
130
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Retrieves all `page_main_content_audios` rows for the given page id.
|
|
155
|
+
* Delegates to {@link getAudiosOfPageOp}.
|
|
156
|
+
* @param pageId
|
|
157
|
+
*/
|
|
158
|
+
async getAudiosOfPage(pageId) {
|
|
159
|
+
return emitErrorAndRetry(this, 'Database.getAudiosOfPage', async () => await getAudiosOfPageOp(this.#instance, pageId), retrySetting);
|
|
160
|
+
}
|
|
131
161
|
/**
|
|
132
162
|
* Retrieves the base URL of the crawl session from the `info` table.
|
|
133
163
|
* Delegates to {@link getBaseUrlOp}.
|
|
@@ -137,6 +167,22 @@ export class Database extends EventEmitter {
|
|
|
137
167
|
async getBaseUrl() {
|
|
138
168
|
return emitErrorAndRetry(this, 'Database.getBaseUrl', async () => await getBaseUrlOp(this.#instance), retrySetting);
|
|
139
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Retrieves all `page_main_content_buttons` rows for the given page id.
|
|
172
|
+
* Delegates to {@link getButtonsOfPageOp}.
|
|
173
|
+
* @param pageId
|
|
174
|
+
*/
|
|
175
|
+
async getButtonsOfPage(pageId) {
|
|
176
|
+
return emitErrorAndRetry(this, 'Database.getButtonsOfPage', async () => await getButtonsOfPageOp(this.#instance, pageId), retrySetting);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Retrieves all `page_main_content_canvases` rows for the given page id.
|
|
180
|
+
* Delegates to {@link getCanvasesOfPageOp}.
|
|
181
|
+
* @param pageId
|
|
182
|
+
*/
|
|
183
|
+
async getCanvasesOfPage(pageId) {
|
|
184
|
+
return emitErrorAndRetry(this, 'Database.getCanvasesOfPage', async () => await getCanvasesOfPageOp(this.#instance, pageId), retrySetting);
|
|
185
|
+
}
|
|
140
186
|
/**
|
|
141
187
|
* Retrieves the full crawl configuration from the `info` table.
|
|
142
188
|
* Delegates to {@link getConfigOp}.
|
|
@@ -174,6 +220,14 @@ export class Database extends EventEmitter {
|
|
|
174
220
|
async getExistingResourceUrls(urls) {
|
|
175
221
|
return emitError(this, 'Database.getExistingResourceUrls', async () => await getExistingResourceUrlsOp(this.#instance, urls));
|
|
176
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Retrieves all `page_main_content_headings` rows for the given page id.
|
|
225
|
+
* Delegates to {@link getHeadingsOfPageOp}.
|
|
226
|
+
* @param pageId
|
|
227
|
+
*/
|
|
228
|
+
async getHeadingsOfPage(pageId) {
|
|
229
|
+
return emitErrorAndRetry(this, 'Database.getHeadingsOfPage', async () => await getHeadingsOfPageOp(this.#instance, pageId), retrySetting);
|
|
230
|
+
}
|
|
177
231
|
/**
|
|
178
232
|
* Reads the HTML snapshot stored as a zstd-compressed BLOB for the given page.
|
|
179
233
|
* Delegates to {@link getHtmlOfPageByIdOp}.
|
|
@@ -183,6 +237,14 @@ export class Database extends EventEmitter {
|
|
|
183
237
|
async getHtmlOfPageById(pageId) {
|
|
184
238
|
return emitErrorAndRetry(this, 'Database.getHtmlOfPageById', async () => await getHtmlOfPageByIdOp(this.#instance, pageId), retrySetting);
|
|
185
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Retrieves all `page_main_content_iframes` rows for the given page id.
|
|
242
|
+
* Delegates to {@link getIframesOfPageOp}.
|
|
243
|
+
* @param pageId
|
|
244
|
+
*/
|
|
245
|
+
async getIframesOfPage(pageId) {
|
|
246
|
+
return emitErrorAndRetry(this, 'Database.getIframesOfPage', async () => await getIframesOfPageOp(this.#instance, pageId), retrySetting);
|
|
247
|
+
}
|
|
186
248
|
/**
|
|
187
249
|
* Retrieves all `page_jsonld` rows for the given page id, parsed back into
|
|
188
250
|
* {@link JsonLdRow} shape. Delegates to {@link getJsonLdOfPageOp}.
|
|
@@ -200,6 +262,22 @@ export class Database extends EventEmitter {
|
|
|
200
262
|
getKnex() {
|
|
201
263
|
return this.#instance;
|
|
202
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Retrieves all `page_main_content_images` rows for the given page id.
|
|
267
|
+
* Delegates to {@link getMainContentImagesOfPageOp}.
|
|
268
|
+
* @param pageId
|
|
269
|
+
*/
|
|
270
|
+
async getMainContentImagesOfPage(pageId) {
|
|
271
|
+
return emitErrorAndRetry(this, 'Database.getMainContentImagesOfPage', async () => await getMainContentImagesOfPageOp(this.#instance, pageId), retrySetting);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Retrieves all `page_main_content_tables` rows for the given page id.
|
|
275
|
+
* Delegates to {@link getMainContentTablesOfPageOp}.
|
|
276
|
+
* @param pageId
|
|
277
|
+
*/
|
|
278
|
+
async getMainContentTablesOfPage(pageId) {
|
|
279
|
+
return emitErrorAndRetry(this, 'Database.getMainContentTablesOfPage', async () => await getMainContentTablesOfPageOp(this.#instance, pageId), retrySetting);
|
|
280
|
+
}
|
|
203
281
|
/**
|
|
204
282
|
* Retrieves the crawl session name from the `info` table.
|
|
205
283
|
* Delegates to {@link getNameOp}.
|
|
@@ -326,6 +404,14 @@ export class Database extends EventEmitter {
|
|
|
326
404
|
async getTagsOfPage(pageId) {
|
|
327
405
|
return emitErrorAndRetry(this, 'Database.getTagsOfPage', async () => await getTagsOfPageOp(this.#instance, pageId), retrySetting);
|
|
328
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* Retrieves all `page_main_content_videos` rows for the given page id.
|
|
409
|
+
* Delegates to {@link getVideosOfPageOp}.
|
|
410
|
+
* @param pageId
|
|
411
|
+
*/
|
|
412
|
+
async getVideosOfPage(pageId) {
|
|
413
|
+
return emitErrorAndRetry(this, 'Database.getVideosOfPage', async () => await getVideosOfPageOp(this.#instance, pageId), retrySetting);
|
|
414
|
+
}
|
|
329
415
|
/**
|
|
330
416
|
* Records a crawler-level (`error` channel) failure into `crawl_errors`.
|
|
331
417
|
* Delegates to {@link insertCrawlErrorOp}.
|
|
@@ -354,6 +440,15 @@ export class Database extends EventEmitter {
|
|
|
354
440
|
async insertInventorySeeds(urls) {
|
|
355
441
|
return emitErrorAndRetry(this, 'Database.insertInventorySeeds', async () => await insertInventorySeedsOp(this.#instance, this.#writeRefCaches, urls), retrySetting);
|
|
356
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Appends one open (`ended_at = NULL`) row to the `network_outages`
|
|
445
|
+
* journal. Delegates to {@link insertNetworkOutageOp}.
|
|
446
|
+
* @param params - The confirmed-outage fields to record.
|
|
447
|
+
* @returns The autoincremented `id` of the newly-inserted row.
|
|
448
|
+
*/
|
|
449
|
+
async insertNetworkOutage(params) {
|
|
450
|
+
return emitErrorAndRetry(this, 'Database.insertNetworkOutage', async () => await insertNetworkOutageOp(this.#instance, params), retrySetting);
|
|
451
|
+
}
|
|
357
452
|
/**
|
|
358
453
|
* Records a partial scrape failure against the page identified by `url`.
|
|
359
454
|
* Delegates to {@link insertPageErrorOp}.
|
|
@@ -392,6 +487,15 @@ export class Database extends EventEmitter {
|
|
|
392
487
|
async listDnsBurnedHostCandidates() {
|
|
393
488
|
return emitErrorAndRetry(this, 'Database.listDnsBurnedHostCandidates', async () => await listDnsBurnedHostCandidatesOp(this.#instance), retrySetting);
|
|
394
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* Lists every recorded outage as a resolved {@link OutageWindow}.
|
|
492
|
+
* Delegates to {@link listNetworkOutagesOp}.
|
|
493
|
+
* @returns Resolved outage windows, or `[]` on an archive that predates
|
|
494
|
+
* `network_outages` or has recorded no outages.
|
|
495
|
+
*/
|
|
496
|
+
async listNetworkOutages() {
|
|
497
|
+
return emitErrorAndRetry(this, 'Database.listNetworkOutages', async () => await listNetworkOutagesOp(this.#instance), retrySetting);
|
|
498
|
+
}
|
|
395
499
|
/**
|
|
396
500
|
* Appends one row to the `inventory_runs` audit log.
|
|
397
501
|
* Delegates to {@link recordInventoryRunOp}.
|
|
@@ -421,6 +525,30 @@ export class Database extends EventEmitter {
|
|
|
421
525
|
async replaceAnalysisViolations(violations) {
|
|
422
526
|
return emitErrorAndRetry(this, 'Database.replaceAnalysisViolations', async () => await replaceAnalysisViolationsOp(this.#instance, violations), retrySetting);
|
|
423
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* Replaces one page's `page_console_logs` rows with a freshly captured
|
|
530
|
+
* set of console messages / page errors. Delegates to
|
|
531
|
+
* {@link replaceConsoleLogsOp}.
|
|
532
|
+
* @param pageUrl - The originally-requested URL, normalised (`withoutHashAndAuth` form).
|
|
533
|
+
* @param redirectPaths - The redirect chain hops captured during fetch, in order.
|
|
534
|
+
* @param entries - The console log entries to persist.
|
|
535
|
+
*/
|
|
536
|
+
async replaceConsoleLogs(pageUrl, redirectPaths, entries) {
|
|
537
|
+
return emitErrorAndRetry(this, 'Database.replaceConsoleLogs', async () => await replaceConsoleLogsOp(this.#instance, this.#writeRefCaches, pageUrl, redirectPaths, entries), retrySetting);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Replaces the stored DOM-structure template classification with a
|
|
541
|
+
* freshly generated set. Delegates to {@link replacePageTemplatesOp}.
|
|
542
|
+
* @param templateKeysByUrl - Page URL → template key.
|
|
543
|
+
* @param clusterReasonsByTemplateKey - Template key → cluster-selection
|
|
544
|
+
* evidence, if the caller captured it.
|
|
545
|
+
*/
|
|
546
|
+
async replacePageTemplates(templateKeysByUrl, clusterReasonsByTemplateKey) {
|
|
547
|
+
return emitErrorAndRetry(this, 'Database.replacePageTemplates', async () => await replacePageTemplatesOp(this.#instance, {
|
|
548
|
+
templateKeysByUrl,
|
|
549
|
+
clusterReasonsByTemplateKey,
|
|
550
|
+
}), retrySetting);
|
|
551
|
+
}
|
|
424
552
|
/**
|
|
425
553
|
* Promote previously-external pages whose URL falls under any of the new
|
|
426
554
|
* scope entries back to a "needs scraping" state.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compresses raw bytes for storage in a `codec`/`size_raw`/`size_stored`
|
|
3
|
+
* BLOB column, matching the read side of
|
|
4
|
+
* {@link import('./decode-json-ref.js').decodeJsonRef} and
|
|
5
|
+
* {@link import('../../decode-html-blob.js').decodeStoredBlob}.
|
|
6
|
+
*
|
|
7
|
+
* The one shared primitive behind every `codec: 'zstd'` writer in the
|
|
8
|
+
* archive (`upsert-json-ref.ts`, `write-page-html-blob.ts`,
|
|
9
|
+
* `replace-page-templates.ts`) — a codec change (e.g. a brotli fallback, a
|
|
10
|
+
* different zstd level) only has to happen here. Callers that also need a
|
|
11
|
+
* content hash (`upsert-json-ref.ts`, `write-page-html-blob.ts`) compute it
|
|
12
|
+
* themselves from the same `rawBytes` they pass in here, rather than this
|
|
13
|
+
* function computing one not every caller needs.
|
|
14
|
+
* @param rawBytes - The raw (uncompressed) payload bytes.
|
|
15
|
+
* @returns The compressed body alongside the codec and size bookkeeping the
|
|
16
|
+
* BLOB column's schema expects.
|
|
17
|
+
* @example
|
|
18
|
+
* const rawBytes = Buffer.from(JSON.stringify(reason), 'utf8');
|
|
19
|
+
* const { body, codec, sizeRaw, sizeStored } = compressPayload(rawBytes);
|
|
20
|
+
*/
|
|
21
|
+
export declare function compressPayload(rawBytes: Buffer): {
|
|
22
|
+
readonly body: Buffer;
|
|
23
|
+
readonly codec: 'zstd';
|
|
24
|
+
readonly sizeRaw: number;
|
|
25
|
+
readonly sizeStored: number;
|
|
26
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { zstdCompressSync } from 'node:zlib';
|
|
2
|
+
/**
|
|
3
|
+
* Compresses raw bytes for storage in a `codec`/`size_raw`/`size_stored`
|
|
4
|
+
* BLOB column, matching the read side of
|
|
5
|
+
* {@link import('./decode-json-ref.js').decodeJsonRef} and
|
|
6
|
+
* {@link import('../../decode-html-blob.js').decodeStoredBlob}.
|
|
7
|
+
*
|
|
8
|
+
* The one shared primitive behind every `codec: 'zstd'` writer in the
|
|
9
|
+
* archive (`upsert-json-ref.ts`, `write-page-html-blob.ts`,
|
|
10
|
+
* `replace-page-templates.ts`) — a codec change (e.g. a brotli fallback, a
|
|
11
|
+
* different zstd level) only has to happen here. Callers that also need a
|
|
12
|
+
* content hash (`upsert-json-ref.ts`, `write-page-html-blob.ts`) compute it
|
|
13
|
+
* themselves from the same `rawBytes` they pass in here, rather than this
|
|
14
|
+
* function computing one not every caller needs.
|
|
15
|
+
* @param rawBytes - The raw (uncompressed) payload bytes.
|
|
16
|
+
* @returns The compressed body alongside the codec and size bookkeeping the
|
|
17
|
+
* BLOB column's schema expects.
|
|
18
|
+
* @example
|
|
19
|
+
* const rawBytes = Buffer.from(JSON.stringify(reason), 'utf8');
|
|
20
|
+
* const { body, codec, sizeRaw, sizeStored } = compressPayload(rawBytes);
|
|
21
|
+
*/
|
|
22
|
+
export function compressPayload(rawBytes) {
|
|
23
|
+
const compressed = zstdCompressSync(rawBytes);
|
|
24
|
+
return {
|
|
25
|
+
body: compressed,
|
|
26
|
+
codec: 'zstd',
|
|
27
|
+
sizeRaw: rawBytes.byteLength,
|
|
28
|
+
sizeStored: compressed.byteLength,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -42,6 +42,8 @@ export interface WriteRefCaches {
|
|
|
42
42
|
readonly jsonIds: Map<string, number>;
|
|
43
43
|
/** `hex(content hash) → blob_refs.id`. */
|
|
44
44
|
readonly blobIds: Map<string, number>;
|
|
45
|
+
/** `hex(content hash) → console_log_items.id`. */
|
|
46
|
+
readonly consoleLogIds: Map<string, number>;
|
|
45
47
|
/**
|
|
46
48
|
* Header dictionary caches (`header_name_refs` / `header_value_refs` /
|
|
47
49
|
* `header_sets` id maps). `null` until the first header-set upsert
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { zstdCompressSync } from 'node:zlib';
|
|
2
1
|
import { computeContentHash } from '../../populate-ref-tables/compute-content-hash.js';
|
|
2
|
+
import { compressPayload } from './compress-payload.js';
|
|
3
3
|
/**
|
|
4
4
|
* Resolves the `json_refs.id` for one JSON payload string, inserting the
|
|
5
5
|
* deduplicated (hash-keyed) row when the payload is new.
|
|
@@ -27,11 +27,11 @@ export async function upsertJsonRef(qb, caches, jsonText) {
|
|
|
27
27
|
if (cached !== undefined) {
|
|
28
28
|
return cached;
|
|
29
29
|
}
|
|
30
|
-
const
|
|
30
|
+
const { body, sizeRaw, sizeStored } = compressPayload(rawBytes);
|
|
31
31
|
const rows = await qb.raw(`INSERT INTO json_refs (hash, json_text, codec, size_raw, size_stored)
|
|
32
32
|
VALUES (?, ?, 'zstd', ?, ?)
|
|
33
33
|
ON CONFLICT(hash) DO UPDATE SET hash = hash
|
|
34
|
-
RETURNING id`, [hash,
|
|
34
|
+
RETURNING id`, [hash, body, sizeRaw, sizeStored]);
|
|
35
35
|
const first = rows[0];
|
|
36
36
|
if (first === undefined) {
|
|
37
37
|
throw new Error('upsertJsonRef: RETURNING yielded no row');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ReplacePageTemplatesParams } from './types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the stored DOM-structure template classification (`--templates`)
|
|
5
|
+
* with a freshly generated set.
|
|
6
|
+
*
|
|
7
|
+
* Every classified page is a full-archive, all-or-nothing recomputation
|
|
8
|
+
* (see `@nitpicker/core`'s `classifyPageTemplates`), so this always deletes
|
|
9
|
+
* every existing row before inserting the new set — there is no per-page
|
|
10
|
+
* incremental update path, matching `replaceAnalysisViolations`'s
|
|
11
|
+
* whole-table replace shape. Unlike violations, a page whose URL can't be
|
|
12
|
+
* resolved back to a `content_items` row is silently skipped rather than
|
|
13
|
+
* treated as a hard failure: losing one page's template classification
|
|
14
|
+
* (e.g. a URL-normalization mismatch between the in-memory `Page.url.href`
|
|
15
|
+
* and the stored `url_refs.url`) should not discard the rest of a
|
|
16
|
+
* potentially multi-thousand-page classification run.
|
|
17
|
+
*
|
|
18
|
+
* `page_template_clusters` is always cleared alongside `page_templates`
|
|
19
|
+
* regardless of whether `clusterReasonsByTemplateKey` is passed — "no
|
|
20
|
+
* reason" must mean "not captured for this run", never "carry over the
|
|
21
|
+
* previous run's reason". Reason rows are inserted for every key in
|
|
22
|
+
* `clusterReasonsByTemplateKey` even if some have no surviving member page
|
|
23
|
+
* in `templateKeysByUrl` after URL-resolution skips above — harmless
|
|
24
|
+
* (nothing joins `page_template_clusters` back to `page_templates` by FK;
|
|
25
|
+
* see the table's own JSDoc), and simpler than cross-filtering the two maps.
|
|
26
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
27
|
+
* @param params - See {@link ReplacePageTemplatesParams}.
|
|
28
|
+
*/
|
|
29
|
+
export declare function replacePageTemplates(knex: Knex, params: ReplacePageTemplatesParams): Promise<void>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../utils/array/each-splitted.js';
|
|
2
|
+
import { compressPayload } from '../_shared/compress-payload.js';
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the stored DOM-structure template classification (`--templates`)
|
|
5
|
+
* with a freshly generated set.
|
|
6
|
+
*
|
|
7
|
+
* Every classified page is a full-archive, all-or-nothing recomputation
|
|
8
|
+
* (see `@nitpicker/core`'s `classifyPageTemplates`), so this always deletes
|
|
9
|
+
* every existing row before inserting the new set — there is no per-page
|
|
10
|
+
* incremental update path, matching `replaceAnalysisViolations`'s
|
|
11
|
+
* whole-table replace shape. Unlike violations, a page whose URL can't be
|
|
12
|
+
* resolved back to a `content_items` row is silently skipped rather than
|
|
13
|
+
* treated as a hard failure: losing one page's template classification
|
|
14
|
+
* (e.g. a URL-normalization mismatch between the in-memory `Page.url.href`
|
|
15
|
+
* and the stored `url_refs.url`) should not discard the rest of a
|
|
16
|
+
* potentially multi-thousand-page classification run.
|
|
17
|
+
*
|
|
18
|
+
* `page_template_clusters` is always cleared alongside `page_templates`
|
|
19
|
+
* regardless of whether `clusterReasonsByTemplateKey` is passed — "no
|
|
20
|
+
* reason" must mean "not captured for this run", never "carry over the
|
|
21
|
+
* previous run's reason". Reason rows are inserted for every key in
|
|
22
|
+
* `clusterReasonsByTemplateKey` even if some have no surviving member page
|
|
23
|
+
* in `templateKeysByUrl` after URL-resolution skips above — harmless
|
|
24
|
+
* (nothing joins `page_template_clusters` back to `page_templates` by FK;
|
|
25
|
+
* see the table's own JSDoc), and simpler than cross-filtering the two maps.
|
|
26
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
27
|
+
* @param params - See {@link ReplacePageTemplatesParams}.
|
|
28
|
+
*/
|
|
29
|
+
export async function replacePageTemplates(knex, params) {
|
|
30
|
+
const { templateKeysByUrl, clusterReasonsByTemplateKey } = params;
|
|
31
|
+
// Compressing every reason is pure CPU work independent of the DB — done
|
|
32
|
+
// before opening the transaction below so it doesn't extend how long the
|
|
33
|
+
// SQLite write-lock is held for.
|
|
34
|
+
const reasonRows = clusterReasonsByTemplateKey && clusterReasonsByTemplateKey.size > 0
|
|
35
|
+
? [...clusterReasonsByTemplateKey].map(([templateKey, reason]) => {
|
|
36
|
+
const { body, codec, sizeRaw, sizeStored } = compressPayload(Buffer.from(JSON.stringify(reason), 'utf8'));
|
|
37
|
+
return {
|
|
38
|
+
template_key: templateKey,
|
|
39
|
+
member_count: reason.memberCount,
|
|
40
|
+
reason_json: body,
|
|
41
|
+
codec,
|
|
42
|
+
size_raw: sizeRaw,
|
|
43
|
+
size_stored: sizeStored,
|
|
44
|
+
};
|
|
45
|
+
})
|
|
46
|
+
: [];
|
|
47
|
+
await knex.transaction(async (trx) => {
|
|
48
|
+
await trx('page_templates').delete();
|
|
49
|
+
await trx('page_template_clusters').delete();
|
|
50
|
+
if (reasonRows.length > 0) {
|
|
51
|
+
await eachSplitted(reasonRows, 100, async (chunk) => {
|
|
52
|
+
await trx('page_template_clusters').insert(chunk);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (templateKeysByUrl.size === 0) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const urls = [...templateKeysByUrl.keys()];
|
|
59
|
+
const pageIdByUrl = new Map();
|
|
60
|
+
await eachSplitted(urls, 500, async (chunk) => {
|
|
61
|
+
const pageRows = await trx('content_items')
|
|
62
|
+
.join('url_refs', 'url_refs.id', 'content_items.url_id')
|
|
63
|
+
.select('content_items.id as id', 'url_refs.url as url')
|
|
64
|
+
.whereIn('url_refs.url', chunk);
|
|
65
|
+
for (const row of pageRows) {
|
|
66
|
+
pageIdByUrl.set(row.url, row.id);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const rows = [];
|
|
70
|
+
for (const [url, templateKey] of templateKeysByUrl) {
|
|
71
|
+
const pageId = pageIdByUrl.get(url);
|
|
72
|
+
if (pageId == null) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
rows.push({ page_id: pageId, template_key: templateKey });
|
|
76
|
+
}
|
|
77
|
+
if (rows.length === 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
await eachSplitted(rows, 500, async (chunk) => {
|
|
81
|
+
await trx('page_templates').insert(chunk);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six landmark types `@d-zero/page-cluster`'s `extractLandmarks` and
|
|
3
|
+
* `ClusterReason.landmarks` recognize.
|
|
4
|
+
*/
|
|
5
|
+
export type TemplateClusterLandmarkType = 'header' | 'footer' | 'nav' | 'aside' | 'form' | 'search';
|
|
6
|
+
/**
|
|
7
|
+
* Why a Pass-0 block (one of possibly several that merged into a final
|
|
8
|
+
* cluster) was formed — mirrors `@d-zero/page-cluster`'s `BlockingReason`
|
|
9
|
+
* discriminated union.
|
|
10
|
+
*/
|
|
11
|
+
export type TemplateClusterBlockingReason = {
|
|
12
|
+
readonly kind: 'css';
|
|
13
|
+
readonly distinctiveStylesheetHrefs: readonly string[];
|
|
14
|
+
} | {
|
|
15
|
+
readonly kind: 'path';
|
|
16
|
+
readonly pathKey: string;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: 'orphanMerge';
|
|
19
|
+
readonly pathKey: string;
|
|
20
|
+
};
|
|
21
|
+
/** One block's blocking key and the reason it was formed. */
|
|
22
|
+
export interface TemplateClusterBlockingEvidence {
|
|
23
|
+
readonly blockKey: string;
|
|
24
|
+
readonly reason: TemplateClusterBlockingReason;
|
|
25
|
+
}
|
|
26
|
+
/** How common one landmark type is across a cluster's member pages. */
|
|
27
|
+
export interface TemplateClusterLandmarkProfile {
|
|
28
|
+
readonly presenceRate: number;
|
|
29
|
+
readonly chromeRate: number;
|
|
30
|
+
readonly shellTokens: readonly string[];
|
|
31
|
+
readonly memberCountWithInstance: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* nitpicker's own copy of `@d-zero/page-cluster`'s `ClusterReason` shape.
|
|
35
|
+
* Kept independent of the `@d-zero/page-cluster` package (rather than
|
|
36
|
+
* importing its type directly) so `@nitpicker/query` — which does not
|
|
37
|
+
* depend on `@d-zero/page-cluster` and is consumed by the browser-side
|
|
38
|
+
* viewer build — never has to add that dependency just to type a value
|
|
39
|
+
* read back out of the archive. `@d-zero/page-cluster`'s `ClusterReason`
|
|
40
|
+
* is structurally assignable to this type.
|
|
41
|
+
*/
|
|
42
|
+
export interface TemplateClusterReason {
|
|
43
|
+
readonly memberCount: number;
|
|
44
|
+
readonly blocking: readonly TemplateClusterBlockingEvidence[];
|
|
45
|
+
readonly structuralCoreTokens: readonly string[];
|
|
46
|
+
readonly landmarks: Partial<Record<TemplateClusterLandmarkType, TemplateClusterLandmarkProfile>>;
|
|
47
|
+
readonly siblingClusterKeys: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Params for {@link import('./replace-page-templates.js').replacePageTemplates}.
|
|
51
|
+
*/
|
|
52
|
+
export interface ReplacePageTemplatesParams {
|
|
53
|
+
/** Page URL → template key, as produced by `classifyPageTemplates`. */
|
|
54
|
+
readonly templateKeysByUrl: ReadonlyMap<string, string>;
|
|
55
|
+
/**
|
|
56
|
+
* Template key → `@d-zero/page-cluster`'s cluster-selection evidence for
|
|
57
|
+
* that key, as produced by `classifyPageTemplates`. Omitted entirely (not
|
|
58
|
+
* just empty) when the caller didn't request reasons.
|
|
59
|
+
*/
|
|
60
|
+
readonly clusterReasonsByTemplateKey?: ReadonlyMap<string, TemplateClusterReason>;
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ConsoleLogContent } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Computes the `console_log_items.hash` for one console log entry's content.
|
|
4
|
+
*
|
|
5
|
+
* Hashing over the raw content fields (not their resolved ref ids) keeps the
|
|
6
|
+
* dictionary row's identity independent of ref-table state, matching
|
|
7
|
+
* `text_refs` / `json_refs` / `url_refs`, whose hash columns are likewise
|
|
8
|
+
* computed from source content rather than downstream ids. The same
|
|
9
|
+
* message logged by a shared framework on every page therefore hashes to
|
|
10
|
+
* the same value regardless of insertion order or which page's scrape
|
|
11
|
+
* resolves the dictionary row first.
|
|
12
|
+
* @param entry - The content fields to hash. `argsJson` must already be
|
|
13
|
+
* `stringifyConsoleLogArgs`'s output — this function does not
|
|
14
|
+
* re-serialize `args` itself.
|
|
15
|
+
* @returns 32-byte SHA-256 hash, ready for the `hash BLOB UNIQUE` column.
|
|
16
|
+
* @example
|
|
17
|
+
* const hash = computeConsoleLogHash({ type: 'error', text: 'boom', argsJson: null });
|
|
18
|
+
*/
|
|
19
|
+
export declare function computeConsoleLogHash(entry: ConsoleLogContent): Buffer;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { computeContentHash } from '../../populate-ref-tables/compute-content-hash.js';
|
|
2
|
+
/**
|
|
3
|
+
* Computes the `console_log_items.hash` for one console log entry's content.
|
|
4
|
+
*
|
|
5
|
+
* Hashing over the raw content fields (not their resolved ref ids) keeps the
|
|
6
|
+
* dictionary row's identity independent of ref-table state, matching
|
|
7
|
+
* `text_refs` / `json_refs` / `url_refs`, whose hash columns are likewise
|
|
8
|
+
* computed from source content rather than downstream ids. The same
|
|
9
|
+
* message logged by a shared framework on every page therefore hashes to
|
|
10
|
+
* the same value regardless of insertion order or which page's scrape
|
|
11
|
+
* resolves the dictionary row first.
|
|
12
|
+
* @param entry - The content fields to hash. `argsJson` must already be
|
|
13
|
+
* `stringifyConsoleLogArgs`'s output — this function does not
|
|
14
|
+
* re-serialize `args` itself.
|
|
15
|
+
* @returns 32-byte SHA-256 hash, ready for the `hash BLOB UNIQUE` column.
|
|
16
|
+
* @example
|
|
17
|
+
* const hash = computeConsoleLogHash({ type: 'error', text: 'boom', argsJson: null });
|
|
18
|
+
*/
|
|
19
|
+
export function computeConsoleLogHash(entry) {
|
|
20
|
+
const canonical = JSON.stringify([
|
|
21
|
+
entry.type,
|
|
22
|
+
entry.text,
|
|
23
|
+
entry.argsJson,
|
|
24
|
+
entry.location?.url ?? null,
|
|
25
|
+
entry.location?.lineNumber ?? null,
|
|
26
|
+
entry.location?.columnNumber ?? null,
|
|
27
|
+
entry.stack ?? null,
|
|
28
|
+
]);
|
|
29
|
+
return computeContentHash(canonical);
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { WriteRefCaches } from '../_shared/types.js';
|
|
2
|
+
import type { ConsoleLogEntry } from '@d-zero/beholder';
|
|
3
|
+
import type { Knex } from 'knex';
|
|
4
|
+
/**
|
|
5
|
+
* Replaces one page's `page_console_logs` rows wholesale with a freshly
|
|
6
|
+
* captured set of console messages / page errors (issue #228).
|
|
7
|
+
*
|
|
8
|
+
* Scoped-Replace, the same pattern as `replaceAnchorEdges` /
|
|
9
|
+
* `replaceImageItems`: the page's existing rows are deleted and the new
|
|
10
|
+
* set is inserted in the same transaction, so a re-scrape (`--append` /
|
|
11
|
+
* `--retry-failed` / re-render) never accumulates duplicate occurrences.
|
|
12
|
+
* Unlike those two, there is no non-empty guard on the DELETE here — the
|
|
13
|
+
* caller (`Crawler#handleConsoleLogs`) already skips emitting the event
|
|
14
|
+
* entirely when `entries` is empty, which is what keeps a degraded
|
|
15
|
+
* re-scrape from wiping out a prior good capture.
|
|
16
|
+
*
|
|
17
|
+
* `pageUrl` is the originally-requested URL (matching `updatePage`'s
|
|
18
|
+
* `page.url.withoutHashAndAuth` contract), not necessarily the page that
|
|
19
|
+
* ends up holding the content: `resolveRedirectChain` derives the same
|
|
20
|
+
* redirect destination `updatePage` writes content under, so console logs
|
|
21
|
+
* attach to the row that actually carries the page's `page_meta`, not to
|
|
22
|
+
* an empty placeholder for the pre-redirect URL.
|
|
23
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
24
|
+
* @param caches - The connection's write-side id caches.
|
|
25
|
+
* @param pageUrl - The originally-requested URL, normalised
|
|
26
|
+
* (`withoutHashAndAuth` form).
|
|
27
|
+
* @param redirectPaths - The redirect chain hops captured during fetch, in
|
|
28
|
+
* order (empty when the page was not redirected).
|
|
29
|
+
* @param entries - The console log entries to persist. Must be non-empty —
|
|
30
|
+
* callers should skip calling this function entirely for an empty list.
|
|
31
|
+
* @example
|
|
32
|
+
* await replaceConsoleLogs(knex, caches, page.url.withoutHashAndAuth, page.redirectPaths, entries);
|
|
33
|
+
*/
|
|
34
|
+
export declare function replaceConsoleLogs(knex: Knex, caches: WriteRefCaches, pageUrl: string, redirectPaths: readonly string[], entries: readonly ConsoleLogEntry[]): Promise<void>;
|