@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/types.d.ts
CHANGED
|
@@ -67,6 +67,11 @@ export interface Config extends Required<Pick<ParseURLOptions, 'disableQueries'>
|
|
|
67
67
|
userAgent: string;
|
|
68
68
|
/** Whether robots.txt restrictions were ignored during crawling. */
|
|
69
69
|
ignoreRobots: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* CSS selector overriding beholder's automatic main-content-region
|
|
72
|
+
* detection, or `null`/omitted to use the automatic heuristic.
|
|
73
|
+
*/
|
|
74
|
+
mainContentSelector?: string | null;
|
|
70
75
|
}
|
|
71
76
|
/**
|
|
72
77
|
* Provenance of a page or resource row — which crawler channel originally
|
|
@@ -123,9 +128,9 @@ export interface InventoryRunMeta {
|
|
|
123
128
|
ran_at: string;
|
|
124
129
|
/** Human-readable identifier (e.g. `'prod-2026-06-21'`). `null` when the caller did not supply one. */
|
|
125
130
|
list_label?: string | null;
|
|
126
|
-
/** SHA-256 hex digest of the source file. `null`
|
|
131
|
+
/** SHA-256 hex digest of the source file. `null` for programmatic callers that built the URL list in-memory (no source file to hash). */
|
|
127
132
|
source_file_sha256?: string | null;
|
|
128
|
-
/** Number of
|
|
133
|
+
/** Number of valid URLs in the input list, after the CLI has warned-and-dropped unparseable-URL lines but before scope filtering. */
|
|
129
134
|
total_lines?: number | null;
|
|
130
135
|
/** Number of new HTML seeds inserted as `content_items` rows by this run. */
|
|
131
136
|
new_pages?: number | null;
|
|
@@ -133,9 +138,54 @@ export interface InventoryRunMeta {
|
|
|
133
138
|
new_resources?: number | null;
|
|
134
139
|
/** Number of input URLs dropped because they fell outside the archived scope. */
|
|
135
140
|
scope_skipped?: number | null;
|
|
141
|
+
/** Number of source-file lines dropped by the CLI for failing URL validation, before this row's `total_lines` was counted. `null` for programmatic callers that built the URL list in-memory (no source file to have invalid lines). */
|
|
142
|
+
invalid_skipped?: number | null;
|
|
136
143
|
/** Free-form text for backfill annotations or operator notes. */
|
|
137
144
|
notes?: string | null;
|
|
138
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* A row in `network_outages` — one detected operator-network outage.
|
|
148
|
+
*
|
|
149
|
+
* Append-only except `ended_at`: it is written once, `NULL`, when the
|
|
150
|
+
* outage is first detected, and updated exactly once when a recovery probe
|
|
151
|
+
* succeeds. A row can also be left `ended_at = NULL` forever if the crawl
|
|
152
|
+
* process is killed mid-outage — readers must resolve this via a clamp
|
|
153
|
+
* (see `is-within-outage-window.ts` and the `db-ops/outages/` writer that
|
|
154
|
+
* closes stale-open rows on the next writer session), never by treating
|
|
155
|
+
* `NULL` as an unbounded window.
|
|
156
|
+
*/
|
|
157
|
+
export interface NetworkOutageRow {
|
|
158
|
+
id: number;
|
|
159
|
+
/** Epoch ms, backdated to the earliest error still inside the detector's sliding window at trigger time. */
|
|
160
|
+
started_at: number;
|
|
161
|
+
/** Epoch ms the sliding window actually crossed both thresholds. */
|
|
162
|
+
detected_at: number;
|
|
163
|
+
/** Epoch ms the recovery probe first succeeded, or `null` while still open / if the session crashed before recovery. */
|
|
164
|
+
ended_at: number | null;
|
|
165
|
+
/** Hostname the recovery probe targeted, or `null` if none was available (see `choose-probe-host.ts`). */
|
|
166
|
+
probe_host: string | null;
|
|
167
|
+
/** Error count in the detector's window at trigger time. */
|
|
168
|
+
trigger_error_count: number;
|
|
169
|
+
/** Distinct host count in the detector's window at trigger time. */
|
|
170
|
+
trigger_host_count: number;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Fields required to record a newly-detected outage via
|
|
174
|
+
* `Database.insertNetworkOutage`. camelCase (unlike {@link NetworkOutageRow}
|
|
175
|
+
* / {@link InventoryRunMeta}) because callers build this directly from
|
|
176
|
+
* `NetworkOutageDetector`'s camelCase `OutageSuspect` plus a probe host —
|
|
177
|
+
* the db-op does the camelCase → snake_case column mapping on write.
|
|
178
|
+
*/
|
|
179
|
+
export interface InsertNetworkOutageParams {
|
|
180
|
+
/** Backdated to the earliest error still inside the detector's window at trigger time. */
|
|
181
|
+
startedAt: number;
|
|
182
|
+
/** When the sliding window actually crossed both thresholds. */
|
|
183
|
+
detectedAt: number;
|
|
184
|
+
/** Hostname the recovery probe will target, or `null` if none was available. */
|
|
185
|
+
probeHost: string | null;
|
|
186
|
+
triggerErrorCount: number;
|
|
187
|
+
triggerHostCount: number;
|
|
188
|
+
}
|
|
139
189
|
/**
|
|
140
190
|
* Filter type for querying pages from the database.
|
|
141
191
|
*
|
|
@@ -292,6 +342,40 @@ export interface DB_Page {
|
|
|
292
342
|
jsonld_count: number | null;
|
|
293
343
|
/** Sorted unique provider names, comma-separated (empty string when none). */
|
|
294
344
|
tags_providers_csv: string | null;
|
|
345
|
+
/** Detected main-content element's `nodeName` (e.g. `'MAIN'`), or null. */
|
|
346
|
+
main_content_node_name: string | null;
|
|
347
|
+
/** Detected main-content element's `id`, or null. */
|
|
348
|
+
main_content_id: string | null;
|
|
349
|
+
/** Detected main-content element's `role` attribute, or null. */
|
|
350
|
+
main_content_role: string | null;
|
|
351
|
+
/** Diagnostic tag+id+class selector for the detected element, or null. */
|
|
352
|
+
main_content_selector: string | null;
|
|
353
|
+
/** JSON-encoded array of the detected element's CSS classes, or null. */
|
|
354
|
+
main_content_class_list: string | null;
|
|
355
|
+
/** Character count of the main region's text content, or null. */
|
|
356
|
+
main_content_word_count: number | null;
|
|
357
|
+
/** Character count of `document.body`'s text content, or null. */
|
|
358
|
+
main_content_body_word_count: number | null;
|
|
359
|
+
/** Number of headings within the main region, or null. */
|
|
360
|
+
main_content_heading_count: number | null;
|
|
361
|
+
/** Number of images within the main region, or null. */
|
|
362
|
+
main_content_image_count: number | null;
|
|
363
|
+
/** Number of tables within the main region, or null. */
|
|
364
|
+
main_content_table_count: number | null;
|
|
365
|
+
/** Number of button-like elements within the main region, or null. */
|
|
366
|
+
main_content_button_count: number | null;
|
|
367
|
+
/** Number of iframes within the main region, or null. */
|
|
368
|
+
main_content_iframe_count: number | null;
|
|
369
|
+
/** Number of videos within the main region, or null. */
|
|
370
|
+
main_content_video_count: number | null;
|
|
371
|
+
/** Number of audios within the main region, or null. */
|
|
372
|
+
main_content_audio_count: number | null;
|
|
373
|
+
/** Number of canvases within the main region, or null. */
|
|
374
|
+
main_content_canvas_count: number | null;
|
|
375
|
+
/** `document.body.scrollHeight` at the desktop-compact preset, or null. */
|
|
376
|
+
scroll_height_desktop: number | null;
|
|
377
|
+
/** `document.body.scrollHeight` at the mobile-small preset, or null. */
|
|
378
|
+
scroll_height_mobile: number | null;
|
|
295
379
|
/** JSON-serialised nested Meta sub-objects not flattened above. */
|
|
296
380
|
meta_extras: string | null;
|
|
297
381
|
/** JSON-serialized network logs captured during scraping, or null if not collected. */
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes the Tier A alias key for `url`: two URLs share a Tier A key when
|
|
3
|
+
* they differ only by scheme (`http` vs `https` are folded together),
|
|
4
|
+
* host letter-casing, or a `/index.{ext}` path suffix — differences that
|
|
5
|
+
* are safe to treat as "the same resource" from the URL string alone, with
|
|
6
|
+
* no need to compare rendered content.
|
|
7
|
+
*
|
|
8
|
+
* This is a canonical-key function, not a pairwise comparator: grouping
|
|
9
|
+
* rows by exact string equality of this function's output is definitionally
|
|
10
|
+
* an equivalence relation (reflexive, symmetric, transitive), unlike
|
|
11
|
+
* `compareUrlSortKeys`'s pairwise "roughly equal" comparison (see
|
|
12
|
+
* ARCHITECTURE.md's "URL natural-sort comparator は推移律を保証しない" —
|
|
13
|
+
* that non-transitivity problem is inherent to pairwise comparators, and
|
|
14
|
+
* does not apply to a canonical-key partition like this one).
|
|
15
|
+
*
|
|
16
|
+
* Deliberately does not touch the query string or fragment: a query-string
|
|
17
|
+
* difference is not addressed by this feature (out of scope — see the
|
|
18
|
+
* `body_hash` masking, a separate content-hash-based mechanism, for
|
|
19
|
+
* detecting meaningless-parameter duplicates), and fragments are
|
|
20
|
+
* client-side only and never affect server-rendered content.
|
|
21
|
+
* @param url - The URL string to compute a key for.
|
|
22
|
+
* @returns The Tier A key, or `null` if `url` is not a parseable http(s) URL.
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* computeTierAAliasKey('http://Example.com/about/index.html');
|
|
26
|
+
* // 'example.com/about/'
|
|
27
|
+
* computeTierAAliasKey('https://example.com/about/');
|
|
28
|
+
* // 'example.com/about/' -- same key: scheme and /index.html are folded
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function computeTierAAliasKey(url: string): string | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { formatAliasKey } from './format-alias-key.js';
|
|
2
|
+
import { parseAliasKeyParts } from './parse-alias-key-parts.js';
|
|
3
|
+
/**
|
|
4
|
+
* Computes the Tier A alias key for `url`: two URLs share a Tier A key when
|
|
5
|
+
* they differ only by scheme (`http` vs `https` are folded together),
|
|
6
|
+
* host letter-casing, or a `/index.{ext}` path suffix — differences that
|
|
7
|
+
* are safe to treat as "the same resource" from the URL string alone, with
|
|
8
|
+
* no need to compare rendered content.
|
|
9
|
+
*
|
|
10
|
+
* This is a canonical-key function, not a pairwise comparator: grouping
|
|
11
|
+
* rows by exact string equality of this function's output is definitionally
|
|
12
|
+
* an equivalence relation (reflexive, symmetric, transitive), unlike
|
|
13
|
+
* `compareUrlSortKeys`'s pairwise "roughly equal" comparison (see
|
|
14
|
+
* ARCHITECTURE.md's "URL natural-sort comparator は推移律を保証しない" —
|
|
15
|
+
* that non-transitivity problem is inherent to pairwise comparators, and
|
|
16
|
+
* does not apply to a canonical-key partition like this one).
|
|
17
|
+
*
|
|
18
|
+
* Deliberately does not touch the query string or fragment: a query-string
|
|
19
|
+
* difference is not addressed by this feature (out of scope — see the
|
|
20
|
+
* `body_hash` masking, a separate content-hash-based mechanism, for
|
|
21
|
+
* detecting meaningless-parameter duplicates), and fragments are
|
|
22
|
+
* client-side only and never affect server-rendered content.
|
|
23
|
+
* @param url - The URL string to compute a key for.
|
|
24
|
+
* @returns The Tier A key, or `null` if `url` is not a parseable http(s) URL.
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* computeTierAAliasKey('http://Example.com/about/index.html');
|
|
28
|
+
* // 'example.com/about/'
|
|
29
|
+
* computeTierAAliasKey('https://example.com/about/');
|
|
30
|
+
* // 'example.com/about/' -- same key: scheme and /index.html are folded
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function computeTierAAliasKey(url) {
|
|
34
|
+
const parts = parseAliasKeyParts(url);
|
|
35
|
+
return parts ? formatAliasKey(parts) : null;
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes the Tier B alias key for `url`: like `computeTierAAliasKey`,
|
|
3
|
+
* but additionally strips one trailing `/` from the path (the bare root
|
|
4
|
+
* path `/` is left alone). Two URLs sharing a Tier B key differ only by
|
|
5
|
+
* trailing-slash presence — a resource-identity signal considerably weaker
|
|
6
|
+
* than Tier A's (a web server can legitimately serve different content at
|
|
7
|
+
* `/foo` and `/foo/`), so callers must additionally require a matching
|
|
8
|
+
* `page_meta.body_hash` before treating a Tier B match as the same page.
|
|
9
|
+
* @param url - The URL string to compute a key for.
|
|
10
|
+
* @returns The Tier B key, or `null` if `url` is not a parseable http(s) URL.
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* computeTierBAliasKey('https://example.com/foo');
|
|
14
|
+
* computeTierBAliasKey('https://example.com/foo/');
|
|
15
|
+
* // both: 'example.com/foo' -- same key, but the caller must still confirm
|
|
16
|
+
* // a matching body_hash before merging these as the same page.
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function computeTierBAliasKey(url: string): string | null;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { formatAliasKey } from './format-alias-key.js';
|
|
2
|
+
import { parseAliasKeyParts } from './parse-alias-key-parts.js';
|
|
3
|
+
/**
|
|
4
|
+
* Computes the Tier B alias key for `url`: like `computeTierAAliasKey`,
|
|
5
|
+
* but additionally strips one trailing `/` from the path (the bare root
|
|
6
|
+
* path `/` is left alone). Two URLs sharing a Tier B key differ only by
|
|
7
|
+
* trailing-slash presence — a resource-identity signal considerably weaker
|
|
8
|
+
* than Tier A's (a web server can legitimately serve different content at
|
|
9
|
+
* `/foo` and `/foo/`), so callers must additionally require a matching
|
|
10
|
+
* `page_meta.body_hash` before treating a Tier B match as the same page.
|
|
11
|
+
* @param url - The URL string to compute a key for.
|
|
12
|
+
* @returns The Tier B key, or `null` if `url` is not a parseable http(s) URL.
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* computeTierBAliasKey('https://example.com/foo');
|
|
16
|
+
* computeTierBAliasKey('https://example.com/foo/');
|
|
17
|
+
* // both: 'example.com/foo' -- same key, but the caller must still confirm
|
|
18
|
+
* // a matching body_hash before merging these as the same page.
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function computeTierBAliasKey(url) {
|
|
22
|
+
const parts = parseAliasKeyParts(url);
|
|
23
|
+
if (!parts) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const path = parts.path.length > 1 && parts.path.endsWith('/')
|
|
27
|
+
? parts.path.slice(0, -1)
|
|
28
|
+
: parts.path;
|
|
29
|
+
return formatAliasKey({ ...parts, path });
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AliasKeyParts } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Formats parsed alias-key parts into a single string key. `host`/`port`
|
|
4
|
+
* never contain `/` or `?`, and `path`/`search` always start with those
|
|
5
|
+
* characters respectively (or `search` is empty), so concatenation without
|
|
6
|
+
* an explicit delimiter is unambiguous.
|
|
7
|
+
* @param parts - The parts to format.
|
|
8
|
+
* @returns The formatted key string.
|
|
9
|
+
* @example
|
|
10
|
+
* formatAliasKey({ host: 'example.com', port: '', path: '/about/', search: '' });
|
|
11
|
+
* // 'example.com/about/'
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatAliasKey(parts: AliasKeyParts): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats parsed alias-key parts into a single string key. `host`/`port`
|
|
3
|
+
* never contain `/` or `?`, and `path`/`search` always start with those
|
|
4
|
+
* characters respectively (or `search` is empty), so concatenation without
|
|
5
|
+
* an explicit delimiter is unambiguous.
|
|
6
|
+
* @param parts - The parts to format.
|
|
7
|
+
* @returns The formatted key string.
|
|
8
|
+
* @example
|
|
9
|
+
* formatAliasKey({ host: 'example.com', port: '', path: '/about/', search: '' });
|
|
10
|
+
* // 'example.com/about/'
|
|
11
|
+
*/
|
|
12
|
+
export function formatAliasKey(parts) {
|
|
13
|
+
return `${parts.host}${parts.port ? `:${parts.port}` : ''}${parts.path}${parts.search}`;
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AliasKeyParts } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses `url` and extracts the components Tier A / Tier B keys are built
|
|
4
|
+
* from. Returns `null` for anything that isn't a parseable `http`/`https`
|
|
5
|
+
* URL — such a row is defensively excluded from alias candidacy entirely
|
|
6
|
+
* (a non-http(s) URL should not occur for anything the crawler itself
|
|
7
|
+
* stored, since every `url_refs.url` was already parsed once during
|
|
8
|
+
* crawling).
|
|
9
|
+
* @param url - The URL string to parse.
|
|
10
|
+
* @returns The extracted parts, or `null` if unparseable / not http(s).
|
|
11
|
+
* @example
|
|
12
|
+
* parseAliasKeyParts('https://Example.com/about/index.html');
|
|
13
|
+
* // { host: 'example.com', port: '', path: '/about/', search: '' }
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseAliasKeyParts(url: string): AliasKeyParts | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const INDEX_SUFFIX_PATTERN = /\/index\.\w+$/i;
|
|
2
|
+
/**
|
|
3
|
+
* Parses `url` and extracts the components Tier A / Tier B keys are built
|
|
4
|
+
* from. Returns `null` for anything that isn't a parseable `http`/`https`
|
|
5
|
+
* URL — such a row is defensively excluded from alias candidacy entirely
|
|
6
|
+
* (a non-http(s) URL should not occur for anything the crawler itself
|
|
7
|
+
* stored, since every `url_refs.url` was already parsed once during
|
|
8
|
+
* crawling).
|
|
9
|
+
* @param url - The URL string to parse.
|
|
10
|
+
* @returns The extracted parts, or `null` if unparseable / not http(s).
|
|
11
|
+
* @example
|
|
12
|
+
* parseAliasKeyParts('https://Example.com/about/index.html');
|
|
13
|
+
* // { host: 'example.com', port: '', path: '/about/', search: '' }
|
|
14
|
+
*/
|
|
15
|
+
export function parseAliasKeyParts(url) {
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = new URL(url);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
// WHATWG URL already lowercases `hostname` during parsing; the
|
|
28
|
+
// explicit `.toLowerCase()` documents that intent rather than
|
|
29
|
+
// relying on it silently.
|
|
30
|
+
host: parsed.hostname.toLowerCase(),
|
|
31
|
+
// Empty string when the port is the scheme's default (or
|
|
32
|
+
// unspecified) — this is why `http://example.com/` and
|
|
33
|
+
// `https://example.com/` fold to the same key below (both have
|
|
34
|
+
// `port === ''`) while an explicit non-default port on either
|
|
35
|
+
// scheme is preserved and kept distinct.
|
|
36
|
+
port: parsed.port,
|
|
37
|
+
path: parsed.pathname.replace(INDEX_SUFFIX_PATTERN, '/'),
|
|
38
|
+
search: parsed.search,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick the hostname a network-outage probe should target.
|
|
3
|
+
*
|
|
4
|
+
* Prefers a host that has already answered successfully during this crawl
|
|
5
|
+
* session (`Crawler.#successfulHosts` — the same "proven alive" evidence
|
|
6
|
+
* `shouldBurnHost` uses) over a root URL's hostname, and never falls back to
|
|
7
|
+
* a hardcoded external address (e.g. `1.1.1.1`): the probe should depend
|
|
8
|
+
* only on infrastructure the crawl is already touching. `ReadonlySet`
|
|
9
|
+
* iteration order in JS is insertion order, so this deterministically picks
|
|
10
|
+
* the first host to have succeeded this session.
|
|
11
|
+
* @param successfulHosts - Hostnames observed to respond in this session.
|
|
12
|
+
* @param roots - The crawl's configured root URLs (`CrawlerOptions.roots`),
|
|
13
|
+
* used as a fallback before any host has succeeded yet.
|
|
14
|
+
* @returns A probe target hostname, or `null` if neither source yields one
|
|
15
|
+
* (e.g. a fresh session with a malformed/empty roots list and no
|
|
16
|
+
* successes yet — the caller should treat this as "cannot probe").
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* chooseProbeHost(new Set(['a.example']), ['https://b.example/']); // 'a.example'
|
|
20
|
+
* chooseProbeHost(new Set(), ['https://b.example/']); // 'b.example'
|
|
21
|
+
* chooseProbeHost(new Set(), []); // null
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function chooseProbeHost(successfulHosts: ReadonlySet<string>, roots: readonly string[]): string | null;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick the hostname a network-outage probe should target.
|
|
3
|
+
*
|
|
4
|
+
* Prefers a host that has already answered successfully during this crawl
|
|
5
|
+
* session (`Crawler.#successfulHosts` — the same "proven alive" evidence
|
|
6
|
+
* `shouldBurnHost` uses) over a root URL's hostname, and never falls back to
|
|
7
|
+
* a hardcoded external address (e.g. `1.1.1.1`): the probe should depend
|
|
8
|
+
* only on infrastructure the crawl is already touching. `ReadonlySet`
|
|
9
|
+
* iteration order in JS is insertion order, so this deterministically picks
|
|
10
|
+
* the first host to have succeeded this session.
|
|
11
|
+
* @param successfulHosts - Hostnames observed to respond in this session.
|
|
12
|
+
* @param roots - The crawl's configured root URLs (`CrawlerOptions.roots`),
|
|
13
|
+
* used as a fallback before any host has succeeded yet.
|
|
14
|
+
* @returns A probe target hostname, or `null` if neither source yields one
|
|
15
|
+
* (e.g. a fresh session with a malformed/empty roots list and no
|
|
16
|
+
* successes yet — the caller should treat this as "cannot probe").
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* chooseProbeHost(new Set(['a.example']), ['https://b.example/']); // 'a.example'
|
|
20
|
+
* chooseProbeHost(new Set(), ['https://b.example/']); // 'b.example'
|
|
21
|
+
* chooseProbeHost(new Set(), []); // null
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function chooseProbeHost(successfulHosts, roots) {
|
|
25
|
+
const [firstSuccessfulHost] = successfulHosts;
|
|
26
|
+
if (firstSuccessfulHost !== undefined) {
|
|
27
|
+
return firstSuccessfulHost;
|
|
28
|
+
}
|
|
29
|
+
for (const root of roots) {
|
|
30
|
+
try {
|
|
31
|
+
return new URL(root).hostname;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dnsBurnedHostBurnTimestamps } from './dns-burned-host-burn-timestamps.js';
|
|
1
2
|
import { dnsBurnedHostCache } from './dns-burned-host-cache.js';
|
|
2
3
|
import { dnsBurnedHostShortCircuitCounter } from './dns-burned-host-short-circuit-counter.js';
|
|
3
4
|
/**
|
|
@@ -8,4 +9,5 @@ import { dnsBurnedHostShortCircuitCounter } from './dns-burned-host-short-circui
|
|
|
8
9
|
export function clearDnsBurnedHostCache() {
|
|
9
10
|
dnsBurnedHostCache.clear();
|
|
10
11
|
dnsBurnedHostShortCircuitCounter.count = 0;
|
|
12
|
+
dnsBurnedHostBurnTimestamps.clear();
|
|
11
13
|
}
|