@nitpicker/crawler 0.14.0 → 0.16.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.d.ts +136 -10
- package/lib/archive/archive.js +147 -9
- 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 +14 -0
- package/lib/archive/create-adjunct-tables.js +169 -0
- package/lib/archive/create-entity-tables.js +21 -1
- package/lib/archive/database.d.ts +67 -2
- package/lib/archive/database.js +92 -3
- 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 +12 -3
- package/lib/archive/db-ops/analysis/replace-page-templates.js +35 -3
- 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/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/dedupe-cap/accumulate-dedupe-cap-rejected-count.d.ts +18 -0
- package/lib/archive/db-ops/dedupe-cap/accumulate-dedupe-cap-rejected-count.js +23 -0
- package/lib/archive/db-ops/dedupe-cap/finalize-dedupe-cap-event.d.ts +12 -0
- package/lib/archive/db-ops/dedupe-cap/finalize-dedupe-cap-event.js +15 -0
- package/lib/archive/db-ops/dedupe-cap/insert-dedupe-cap-event.d.ts +14 -0
- package/lib/archive/db-ops/dedupe-cap/insert-dedupe-cap-event.js +30 -0
- package/lib/archive/db-ops/dedupe-cap/list-dedupe-cap-shape-keys.d.ts +21 -0
- package/lib/archive/db-ops/dedupe-cap/list-dedupe-cap-shape-keys.js +27 -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 +11 -2
- package/lib/archive/db-ops/lifecycle/init.js +21 -2
- 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/reset/reset-failed-pages.d.ts +13 -0
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +35 -7
- package/lib/archive/db-ops/pages/write/update-page.js +12 -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/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-inventory-runs-invalid-skipped.d.ts +11 -0
- package/lib/archive/migrate-inventory-runs-invalid-skipped.js +24 -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 +1 -1
- package/lib/archive/types.d.ts +60 -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/classify-error-kind.d.ts +1 -0
- package/lib/classify-error-kind.js +14 -0
- package/lib/crawler/assert-chrome-installed.d.ts +24 -0
- package/lib/crawler/assert-chrome-installed.js +43 -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.d.ts +12 -0
- package/lib/crawler/crawler.js +496 -29
- package/lib/crawler/decode-auth-credential.d.ts +29 -0
- package/lib/crawler/decode-auth-credential.js +39 -0
- package/lib/crawler/dedupe/compute-meta-signature.d.ts +30 -0
- package/lib/crawler/dedupe/compute-meta-signature.js +0 -0
- package/lib/crawler/dedupe/compute-shape-key.d.ts +37 -0
- package/lib/crawler/dedupe/compute-shape-key.js +56 -0
- package/lib/crawler/dedupe/dedupe-cap-tracker.d.ts +84 -0
- package/lib/crawler/dedupe/dedupe-cap-tracker.js +185 -0
- package/lib/crawler/dedupe/is-predicted-content-duplicate.d.ts +24 -0
- package/lib/crawler/dedupe/is-predicted-content-duplicate.js +26 -0
- package/lib/crawler/dedupe/is-shape-capped.d.ts +10 -0
- package/lib/crawler/dedupe/is-shape-capped.js +12 -0
- package/lib/crawler/dedupe/resolve-og-url-mismatch.d.ts +31 -0
- package/lib/crawler/dedupe/resolve-og-url-mismatch.js +40 -0
- package/lib/crawler/dedupe/types.d.ts +42 -0
- package/lib/crawler/dedupe/types.js +1 -0
- 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 +14 -2
- package/lib/crawler/generate-predicted-urls.d.ts +12 -0
- package/lib/crawler/generate-predicted-urls.js +33 -2
- package/lib/crawler/is-puppeteer-fallback-candidate.js +3 -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/types.d.ts +185 -1
- package/lib/crawler-orchestrator.d.ts +74 -18
- package/lib/crawler-orchestrator.js +267 -27
- package/lib/crawler.d.ts +22 -0
- package/lib/crawler.js +18 -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/permanent-error-kinds.d.ts +9 -4
- package/lib/permanent-error-kinds.js +10 -4
- package/lib/types.d.ts +11 -2
- 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
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { TemplateClusterReason } from './db-ops/analysis/types.js';
|
|
1
2
|
import type { JsonLdRow, MainContentAudioRow, MainContentButtonRow, MainContentCanvasRow, MainContentHeadingRow, MainContentIframeRow, MainContentImageRow, MainContentTableRow, MainContentVideoRow, TagRow } from './meta/types.js';
|
|
2
|
-
import type { Config, DatabaseOption, DB_Redirect, DB_Resource, DatabaseEvent, InventoryRunMeta, PageFilter, PageSource } from './types.js';
|
|
3
|
+
import type { Config, DatabaseOption, DB_Redirect, DB_Resource, DatabaseEvent, InsertDedupeCapEventParams, InsertNetworkOutageParams, InventoryRunMeta, PageFilter, PageSource } from './types.js';
|
|
4
|
+
import type { OutageWindow } from '../is-within-outage-window.js';
|
|
3
5
|
import type { PageData, Resource } from '../utils/types/types.js';
|
|
6
|
+
import type { ConsoleLogEntry } from '@d-zero/beholder';
|
|
4
7
|
import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
5
8
|
import type { Knex } from 'knex';
|
|
6
9
|
import { TypedAwaitEventEmitter as EventEmitter } from '@d-zero/shared/typed-await-event-emitter';
|
|
@@ -29,16 +32,39 @@ import { TypedAwaitEventEmitter as EventEmitter } from '@d-zero/shared/typed-awa
|
|
|
29
32
|
export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
30
33
|
#private;
|
|
31
34
|
private constructor();
|
|
35
|
+
/**
|
|
36
|
+
* Adds onto the `rejected_count` of a shape's `dedupe_cap_events` row,
|
|
37
|
+
* looked up by `shape_key` rather than `id`. Delegates to
|
|
38
|
+
* {@link accumulateDedupeCapRejectedCountOp}.
|
|
39
|
+
* @param shapeKey - The capped shape whose rejection count to accumulate.
|
|
40
|
+
* @param rejectedCount - Additional anchors rejected for this shape in the current session.
|
|
41
|
+
*/
|
|
42
|
+
accumulateDedupeCapRejectedCount(shapeKey: string, rejectedCount: number): Promise<void>;
|
|
32
43
|
/**
|
|
33
44
|
* Forces a WAL checkpoint, writing all pending WAL data back to the main
|
|
34
45
|
* database file. Delegates to {@link checkpointOp}.
|
|
35
46
|
*/
|
|
36
47
|
checkpoint(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Closes an outage row by stamping `ended_at` — a no-op if the row is
|
|
50
|
+
* already closed. Delegates to {@link closeNetworkOutageOp}.
|
|
51
|
+
* @param id - The `network_outages.id` to close.
|
|
52
|
+
* @param endedAt - Epoch ms the outage is considered to have ended.
|
|
53
|
+
*/
|
|
54
|
+
closeNetworkOutage(id: number, endedAt: number): Promise<void>;
|
|
37
55
|
/**
|
|
38
56
|
* Destroys the database connection, releasing all pooled resources.
|
|
39
57
|
* Delegates to {@link destroyOp}.
|
|
40
58
|
*/
|
|
41
59
|
destroy(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Finalizes a `dedupe_cap_events` row by stamping `rejected_count` — a
|
|
62
|
+
* no-op if the row is already finalized. Delegates to
|
|
63
|
+
* {@link finalizeDedupeCapEventOp}.
|
|
64
|
+
* @param id - The `dedupe_cap_events.id` to finalize.
|
|
65
|
+
* @param rejectedCount - Number of anchors rejected for this shape after it capped.
|
|
66
|
+
*/
|
|
67
|
+
finalizeDedupeCapEvent(id: number, rejectedCount: number): Promise<void>;
|
|
42
68
|
/**
|
|
43
69
|
* Retrieves all anchors (outgoing links) on a specific page.
|
|
44
70
|
* Delegates to {@link getAnchorsOnPageOp}.
|
|
@@ -263,6 +289,13 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
263
289
|
* @param isExternal - Whether the URL is external to the crawl scope.
|
|
264
290
|
*/
|
|
265
291
|
insertCrawlError(url: string | null, message: string, isExternal?: boolean): Promise<void>;
|
|
292
|
+
/**
|
|
293
|
+
* Appends one row (`rejected_count = NULL`) to the `dedupe_cap_events`
|
|
294
|
+
* journal. Delegates to {@link insertDedupeCapEventOp}.
|
|
295
|
+
* @param params - The newly-capped shape's fields to record.
|
|
296
|
+
* @returns The autoincremented `id` of the newly-inserted row.
|
|
297
|
+
*/
|
|
298
|
+
insertDedupeCapEvent(params: InsertDedupeCapEventParams): Promise<number>;
|
|
266
299
|
/**
|
|
267
300
|
* Pre-insert inventory non-HTML URLs into `resources` as placeholder rows.
|
|
268
301
|
* Delegates to {@link insertInventoryResourcesOp}.
|
|
@@ -277,6 +310,13 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
277
310
|
* @param urls - URL strings already in `withoutHashAndAuth` form.
|
|
278
311
|
*/
|
|
279
312
|
insertInventorySeeds(urls: readonly string[]): Promise<void>;
|
|
313
|
+
/**
|
|
314
|
+
* Appends one open (`ended_at = NULL`) row to the `network_outages`
|
|
315
|
+
* journal. Delegates to {@link insertNetworkOutageOp}.
|
|
316
|
+
* @param params - The confirmed-outage fields to record.
|
|
317
|
+
* @returns The autoincremented `id` of the newly-inserted row.
|
|
318
|
+
*/
|
|
319
|
+
insertNetworkOutage(params: InsertNetworkOutageParams): Promise<number>;
|
|
280
320
|
/**
|
|
281
321
|
* Records a partial scrape failure against the page identified by `url`.
|
|
282
322
|
* Delegates to {@link insertPageErrorOp}.
|
|
@@ -300,6 +340,13 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
300
340
|
* @param pageUrl - The URL of the page that references the resource.
|
|
301
341
|
*/
|
|
302
342
|
insertResourceReferrers(src: string, pageUrl: string): Promise<void>;
|
|
343
|
+
/**
|
|
344
|
+
* Every distinct `dedupe_cap_events.shape_key` recorded in this archive.
|
|
345
|
+
* Delegates to {@link listDedupeCapShapeKeysOp}.
|
|
346
|
+
* @returns Distinct shape keys already confirmed capped, or `[]` on an
|
|
347
|
+
* archive that predates `dedupe_cap_events` or has recorded none.
|
|
348
|
+
*/
|
|
349
|
+
listDedupeCapShapeKeys(): Promise<string[]>;
|
|
303
350
|
/**
|
|
304
351
|
* Hostnames whose `crawl_errors` history is consistently DNS failures and
|
|
305
352
|
* for which no recent 2xx-3xx page or resource is recorded.
|
|
@@ -307,6 +354,13 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
307
354
|
* @returns Lower-cased hostnames safe to short-circuit.
|
|
308
355
|
*/
|
|
309
356
|
listDnsBurnedHostCandidates(): Promise<string[]>;
|
|
357
|
+
/**
|
|
358
|
+
* Lists every recorded outage as a resolved {@link OutageWindow}.
|
|
359
|
+
* Delegates to {@link listNetworkOutagesOp}.
|
|
360
|
+
* @returns Resolved outage windows, or `[]` on an archive that predates
|
|
361
|
+
* `network_outages` or has recorded no outages.
|
|
362
|
+
*/
|
|
363
|
+
listNetworkOutages(): Promise<OutageWindow[]>;
|
|
310
364
|
/**
|
|
311
365
|
* Appends one row to the `inventory_runs` audit log.
|
|
312
366
|
* Delegates to {@link recordInventoryRunOp}.
|
|
@@ -339,12 +393,23 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
339
393
|
line?: number | null;
|
|
340
394
|
col?: number | null;
|
|
341
395
|
}[]): Promise<void>;
|
|
396
|
+
/**
|
|
397
|
+
* Replaces one page's `page_console_logs` rows with a freshly captured
|
|
398
|
+
* set of console messages / page errors. Delegates to
|
|
399
|
+
* {@link replaceConsoleLogsOp}.
|
|
400
|
+
* @param pageUrl - The originally-requested URL, normalised (`withoutHashAndAuth` form).
|
|
401
|
+
* @param redirectPaths - The redirect chain hops captured during fetch, in order.
|
|
402
|
+
* @param entries - The console log entries to persist.
|
|
403
|
+
*/
|
|
404
|
+
replaceConsoleLogs(pageUrl: string, redirectPaths: readonly string[], entries: readonly ConsoleLogEntry[]): Promise<void>;
|
|
342
405
|
/**
|
|
343
406
|
* Replaces the stored DOM-structure template classification with a
|
|
344
407
|
* freshly generated set. Delegates to {@link replacePageTemplatesOp}.
|
|
345
408
|
* @param templateKeysByUrl - Page URL → template key.
|
|
409
|
+
* @param clusterReasonsByTemplateKey - Template key → cluster-selection
|
|
410
|
+
* evidence, if the caller captured it.
|
|
346
411
|
*/
|
|
347
|
-
replacePageTemplates(templateKeysByUrl: ReadonlyMap<string, string>): Promise<void>;
|
|
412
|
+
replacePageTemplates(templateKeysByUrl: ReadonlyMap<string, string>, clusterReasonsByTemplateKey?: ReadonlyMap<string, TemplateClusterReason>): Promise<void>;
|
|
348
413
|
/**
|
|
349
414
|
* Promote previously-external pages whose URL falls under any of the new
|
|
350
415
|
* scope entries back to a "needs scraping" state.
|
package/lib/archive/database.js
CHANGED
|
@@ -15,6 +15,11 @@ import { getConfig as getConfigOp } from './db-ops/config/get-config.js';
|
|
|
15
15
|
import { getName as getNameOp } from './db-ops/config/get-name.js';
|
|
16
16
|
import { setConfig as setConfigOp } from './db-ops/config/set-config.js';
|
|
17
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';
|
|
19
|
+
import { accumulateDedupeCapRejectedCount as accumulateDedupeCapRejectedCountOp } from './db-ops/dedupe-cap/accumulate-dedupe-cap-rejected-count.js';
|
|
20
|
+
import { finalizeDedupeCapEvent as finalizeDedupeCapEventOp } from './db-ops/dedupe-cap/finalize-dedupe-cap-event.js';
|
|
21
|
+
import { insertDedupeCapEvent as insertDedupeCapEventOp } from './db-ops/dedupe-cap/insert-dedupe-cap-event.js';
|
|
22
|
+
import { listDedupeCapShapeKeys as listDedupeCapShapeKeysOp } from './db-ops/dedupe-cap/list-dedupe-cap-shape-keys.js';
|
|
18
23
|
import { insertCrawlError as insertCrawlErrorOp } from './db-ops/errors/insert-crawl-error.js';
|
|
19
24
|
import { insertPageError as insertPageErrorOp } from './db-ops/errors/insert-page-error.js';
|
|
20
25
|
import { listDnsBurnedHostCandidates as listDnsBurnedHostCandidatesOp } from './db-ops/errors/list-dns-burned-host-candidates.js';
|
|
@@ -33,6 +38,9 @@ import { getMainContentImagesOfPage as getMainContentImagesOfPageOp } from './db
|
|
|
33
38
|
import { getMainContentTablesOfPage as getMainContentTablesOfPageOp } from './db-ops/meta/get-main-content-tables-of-page.js';
|
|
34
39
|
import { getTagsOfPage as getTagsOfPageOp } from './db-ops/meta/get-tags-of-page.js';
|
|
35
40
|
import { getVideosOfPage as getVideosOfPageOp } from './db-ops/meta/get-videos-of-page.js';
|
|
41
|
+
import { closeNetworkOutage as closeNetworkOutageOp } from './db-ops/outages/close-network-outage.js';
|
|
42
|
+
import { insertNetworkOutage as insertNetworkOutageOp } from './db-ops/outages/insert-network-outage.js';
|
|
43
|
+
import { listNetworkOutages as listNetworkOutagesOp } from './db-ops/outages/list-network-outages.js';
|
|
36
44
|
import { setUrlOrder as setUrlOrderOp } from './db-ops/pages/order/set-url-order.js';
|
|
37
45
|
import { getCrawlingState as getCrawlingStateOp } from './db-ops/pages/read/get-crawling-state.js';
|
|
38
46
|
import { getExistingPageUrls as getExistingPageUrlsOp } from './db-ops/pages/read/get-existing-page-urls.js';
|
|
@@ -114,6 +122,16 @@ export class Database extends EventEmitter {
|
|
|
114
122
|
},
|
|
115
123
|
});
|
|
116
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Adds onto the `rejected_count` of a shape's `dedupe_cap_events` row,
|
|
127
|
+
* looked up by `shape_key` rather than `id`. Delegates to
|
|
128
|
+
* {@link accumulateDedupeCapRejectedCountOp}.
|
|
129
|
+
* @param shapeKey - The capped shape whose rejection count to accumulate.
|
|
130
|
+
* @param rejectedCount - Additional anchors rejected for this shape in the current session.
|
|
131
|
+
*/
|
|
132
|
+
async accumulateDedupeCapRejectedCount(shapeKey, rejectedCount) {
|
|
133
|
+
return emitErrorAndRetry(this, 'Database.accumulateDedupeCapRejectedCount', async () => await accumulateDedupeCapRejectedCountOp(this.#instance, shapeKey, rejectedCount), retrySetting);
|
|
134
|
+
}
|
|
117
135
|
/**
|
|
118
136
|
* Forces a WAL checkpoint, writing all pending WAL data back to the main
|
|
119
137
|
* database file. Delegates to {@link checkpointOp}.
|
|
@@ -121,6 +139,15 @@ export class Database extends EventEmitter {
|
|
|
121
139
|
async checkpoint() {
|
|
122
140
|
await checkpointOp(this.#instance);
|
|
123
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Closes an outage row by stamping `ended_at` — a no-op if the row is
|
|
144
|
+
* already closed. Delegates to {@link closeNetworkOutageOp}.
|
|
145
|
+
* @param id - The `network_outages.id` to close.
|
|
146
|
+
* @param endedAt - Epoch ms the outage is considered to have ended.
|
|
147
|
+
*/
|
|
148
|
+
async closeNetworkOutage(id, endedAt) {
|
|
149
|
+
return emitErrorAndRetry(this, 'Database.closeNetworkOutage', async () => await closeNetworkOutageOp(this.#instance, id, endedAt), retrySetting);
|
|
150
|
+
}
|
|
124
151
|
/**
|
|
125
152
|
* Destroys the database connection, releasing all pooled resources.
|
|
126
153
|
* Delegates to {@link destroyOp}.
|
|
@@ -128,6 +155,16 @@ export class Database extends EventEmitter {
|
|
|
128
155
|
async destroy() {
|
|
129
156
|
await destroyOp(this.#instance);
|
|
130
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Finalizes a `dedupe_cap_events` row by stamping `rejected_count` — a
|
|
160
|
+
* no-op if the row is already finalized. Delegates to
|
|
161
|
+
* {@link finalizeDedupeCapEventOp}.
|
|
162
|
+
* @param id - The `dedupe_cap_events.id` to finalize.
|
|
163
|
+
* @param rejectedCount - Number of anchors rejected for this shape after it capped.
|
|
164
|
+
*/
|
|
165
|
+
async finalizeDedupeCapEvent(id, rejectedCount) {
|
|
166
|
+
return emitErrorAndRetry(this, 'Database.finalizeDedupeCapEvent', async () => await finalizeDedupeCapEventOp(this.#instance, id, rejectedCount), retrySetting);
|
|
167
|
+
}
|
|
131
168
|
/**
|
|
132
169
|
* Retrieves all anchors (outgoing links) on a specific page.
|
|
133
170
|
* Delegates to {@link getAnchorsOnPageOp}.
|
|
@@ -409,6 +446,15 @@ export class Database extends EventEmitter {
|
|
|
409
446
|
async insertCrawlError(url, message, isExternal = false) {
|
|
410
447
|
return emitErrorAndRetry(this, 'Database.insertCrawlError', async () => await insertCrawlErrorOp(this.#instance, url, message, isExternal), retrySetting);
|
|
411
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Appends one row (`rejected_count = NULL`) to the `dedupe_cap_events`
|
|
451
|
+
* journal. Delegates to {@link insertDedupeCapEventOp}.
|
|
452
|
+
* @param params - The newly-capped shape's fields to record.
|
|
453
|
+
* @returns The autoincremented `id` of the newly-inserted row.
|
|
454
|
+
*/
|
|
455
|
+
async insertDedupeCapEvent(params) {
|
|
456
|
+
return emitErrorAndRetry(this, 'Database.insertDedupeCapEvent', async () => await insertDedupeCapEventOp(this.#instance, params), retrySetting);
|
|
457
|
+
}
|
|
412
458
|
/**
|
|
413
459
|
* Pre-insert inventory non-HTML URLs into `resources` as placeholder rows.
|
|
414
460
|
* Delegates to {@link insertInventoryResourcesOp}.
|
|
@@ -427,6 +473,15 @@ export class Database extends EventEmitter {
|
|
|
427
473
|
async insertInventorySeeds(urls) {
|
|
428
474
|
return emitErrorAndRetry(this, 'Database.insertInventorySeeds', async () => await insertInventorySeedsOp(this.#instance, this.#writeRefCaches, urls), retrySetting);
|
|
429
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* Appends one open (`ended_at = NULL`) row to the `network_outages`
|
|
478
|
+
* journal. Delegates to {@link insertNetworkOutageOp}.
|
|
479
|
+
* @param params - The confirmed-outage fields to record.
|
|
480
|
+
* @returns The autoincremented `id` of the newly-inserted row.
|
|
481
|
+
*/
|
|
482
|
+
async insertNetworkOutage(params) {
|
|
483
|
+
return emitErrorAndRetry(this, 'Database.insertNetworkOutage', async () => await insertNetworkOutageOp(this.#instance, params), retrySetting);
|
|
484
|
+
}
|
|
430
485
|
/**
|
|
431
486
|
* Records a partial scrape failure against the page identified by `url`.
|
|
432
487
|
* Delegates to {@link insertPageErrorOp}.
|
|
@@ -456,6 +511,15 @@ export class Database extends EventEmitter {
|
|
|
456
511
|
async insertResourceReferrers(src, pageUrl) {
|
|
457
512
|
return emitErrorAndRetry(this, 'Database.insertResourceReferrers', async () => await insertResourceReferrersOp(this.#instance, this.#writeRefCaches, src, pageUrl), retrySetting);
|
|
458
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Every distinct `dedupe_cap_events.shape_key` recorded in this archive.
|
|
516
|
+
* Delegates to {@link listDedupeCapShapeKeysOp}.
|
|
517
|
+
* @returns Distinct shape keys already confirmed capped, or `[]` on an
|
|
518
|
+
* archive that predates `dedupe_cap_events` or has recorded none.
|
|
519
|
+
*/
|
|
520
|
+
async listDedupeCapShapeKeys() {
|
|
521
|
+
return emitErrorAndRetry(this, 'Database.listDedupeCapShapeKeys', async () => await listDedupeCapShapeKeysOp(this.#instance), retrySetting);
|
|
522
|
+
}
|
|
459
523
|
/**
|
|
460
524
|
* Hostnames whose `crawl_errors` history is consistently DNS failures and
|
|
461
525
|
* for which no recent 2xx-3xx page or resource is recorded.
|
|
@@ -465,6 +529,15 @@ export class Database extends EventEmitter {
|
|
|
465
529
|
async listDnsBurnedHostCandidates() {
|
|
466
530
|
return emitErrorAndRetry(this, 'Database.listDnsBurnedHostCandidates', async () => await listDnsBurnedHostCandidatesOp(this.#instance), retrySetting);
|
|
467
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Lists every recorded outage as a resolved {@link OutageWindow}.
|
|
534
|
+
* Delegates to {@link listNetworkOutagesOp}.
|
|
535
|
+
* @returns Resolved outage windows, or `[]` on an archive that predates
|
|
536
|
+
* `network_outages` or has recorded no outages.
|
|
537
|
+
*/
|
|
538
|
+
async listNetworkOutages() {
|
|
539
|
+
return emitErrorAndRetry(this, 'Database.listNetworkOutages', async () => await listNetworkOutagesOp(this.#instance), retrySetting);
|
|
540
|
+
}
|
|
468
541
|
/**
|
|
469
542
|
* Appends one row to the `inventory_runs` audit log.
|
|
470
543
|
* Delegates to {@link recordInventoryRunOp}.
|
|
@@ -494,13 +567,29 @@ export class Database extends EventEmitter {
|
|
|
494
567
|
async replaceAnalysisViolations(violations) {
|
|
495
568
|
return emitErrorAndRetry(this, 'Database.replaceAnalysisViolations', async () => await replaceAnalysisViolationsOp(this.#instance, violations), retrySetting);
|
|
496
569
|
}
|
|
570
|
+
/**
|
|
571
|
+
* Replaces one page's `page_console_logs` rows with a freshly captured
|
|
572
|
+
* set of console messages / page errors. Delegates to
|
|
573
|
+
* {@link replaceConsoleLogsOp}.
|
|
574
|
+
* @param pageUrl - The originally-requested URL, normalised (`withoutHashAndAuth` form).
|
|
575
|
+
* @param redirectPaths - The redirect chain hops captured during fetch, in order.
|
|
576
|
+
* @param entries - The console log entries to persist.
|
|
577
|
+
*/
|
|
578
|
+
async replaceConsoleLogs(pageUrl, redirectPaths, entries) {
|
|
579
|
+
return emitErrorAndRetry(this, 'Database.replaceConsoleLogs', async () => await replaceConsoleLogsOp(this.#instance, this.#writeRefCaches, pageUrl, redirectPaths, entries), retrySetting);
|
|
580
|
+
}
|
|
497
581
|
/**
|
|
498
582
|
* Replaces the stored DOM-structure template classification with a
|
|
499
583
|
* freshly generated set. Delegates to {@link replacePageTemplatesOp}.
|
|
500
584
|
* @param templateKeysByUrl - Page URL → template key.
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
585
|
+
* @param clusterReasonsByTemplateKey - Template key → cluster-selection
|
|
586
|
+
* evidence, if the caller captured it.
|
|
587
|
+
*/
|
|
588
|
+
async replacePageTemplates(templateKeysByUrl, clusterReasonsByTemplateKey) {
|
|
589
|
+
return emitErrorAndRetry(this, 'Database.replacePageTemplates', async () => await replacePageTemplatesOp(this.#instance, {
|
|
590
|
+
templateKeysByUrl,
|
|
591
|
+
clusterReasonsByTemplateKey,
|
|
592
|
+
}), retrySetting);
|
|
504
593
|
}
|
|
505
594
|
/**
|
|
506
595
|
* Promote previously-external pages whose URL falls under any of the new
|
|
@@ -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');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReplacePageTemplatesParams } from './types.js';
|
|
1
2
|
import type { Knex } from 'knex';
|
|
2
3
|
/**
|
|
3
4
|
* Replaces the stored DOM-structure template classification (`--templates`)
|
|
@@ -13,8 +14,16 @@ import type { Knex } from 'knex';
|
|
|
13
14
|
* (e.g. a URL-normalization mismatch between the in-memory `Page.url.href`
|
|
14
15
|
* and the stored `url_refs.url`) should not discard the rest of a
|
|
15
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.
|
|
16
26
|
* @param knex - Knex query builder connected to the archive DB.
|
|
17
|
-
* @param
|
|
18
|
-
* `classifyPageTemplates`.
|
|
27
|
+
* @param params - See {@link ReplacePageTemplatesParams}.
|
|
19
28
|
*/
|
|
20
|
-
export declare function replacePageTemplates(knex: Knex,
|
|
29
|
+
export declare function replacePageTemplates(knex: Knex, params: ReplacePageTemplatesParams): Promise<void>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { eachSplitted } from '../../../utils/array/each-splitted.js';
|
|
2
|
+
import { compressPayload } from '../_shared/compress-payload.js';
|
|
2
3
|
/**
|
|
3
4
|
* Replaces the stored DOM-structure template classification (`--templates`)
|
|
4
5
|
* with a freshly generated set.
|
|
@@ -13,13 +14,44 @@ import { eachSplitted } from '../../../utils/array/each-splitted.js';
|
|
|
13
14
|
* (e.g. a URL-normalization mismatch between the in-memory `Page.url.href`
|
|
14
15
|
* and the stored `url_refs.url`) should not discard the rest of a
|
|
15
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.
|
|
16
26
|
* @param knex - Knex query builder connected to the archive DB.
|
|
17
|
-
* @param
|
|
18
|
-
* `classifyPageTemplates`.
|
|
27
|
+
* @param params - See {@link ReplacePageTemplatesParams}.
|
|
19
28
|
*/
|
|
20
|
-
export async function replacePageTemplates(knex,
|
|
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
|
+
: [];
|
|
21
47
|
await knex.transaction(async (trx) => {
|
|
22
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
|
+
}
|
|
23
55
|
if (templateKeysByUrl.size === 0) {
|
|
24
56
|
return;
|
|
25
57
|
}
|
|
@@ -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>;
|