@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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Level-triggered gate that dealer worker callbacks await before doing any
|
|
3
|
+
* network work. Achieves "pause the crawl until the network recovers"
|
|
4
|
+
* without touching `@d-zero/dealer` internals — a closed gate just makes
|
|
5
|
+
* the worker's task body a long-running `await`, which is all dealer ever
|
|
6
|
+
* sees.
|
|
7
|
+
*
|
|
8
|
+
* **`epoch`** counts outage generations: it increments on every `close()`
|
|
9
|
+
* transition (open → closed) and holds that value for the remainder of the
|
|
10
|
+
* closed period AND through the following open period, until the next
|
|
11
|
+
* `close()`. This is the hook `destinationCache` / `dnsBurnedHostCache`
|
|
12
|
+
* eviction uses: an entry written while the gate was closed is tagged with
|
|
13
|
+
* `gate.epoch` at write time; after `open()`, the caller purges entries
|
|
14
|
+
* tagged with that same epoch value. Because `epoch` only advances on
|
|
15
|
+
* `close()` (never on `open()`), entries written during ordinary (open)
|
|
16
|
+
* operation are never mistakenly tagged with a soon-to-be-purged epoch —
|
|
17
|
+
* only writes that happen while `isOpen` is `false` get tagged at all.
|
|
18
|
+
*/
|
|
19
|
+
export default class NetworkGate {
|
|
20
|
+
#epoch = 0;
|
|
21
|
+
#open = true;
|
|
22
|
+
#waiters = [];
|
|
23
|
+
/** `true` when the gate is open (the default). */
|
|
24
|
+
get isOpen() {
|
|
25
|
+
return this.#open;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Current outage-generation counter. See the class docstring for how
|
|
29
|
+
* cache-eviction callers are expected to use this value.
|
|
30
|
+
*/
|
|
31
|
+
get epoch() {
|
|
32
|
+
return this.#epoch;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Close the gate, blocking future {@link wait} callers until the next
|
|
36
|
+
* {@link open}. Idempotent — calling `close()` while already closed does
|
|
37
|
+
* nothing (in particular, it does NOT bump {@link epoch} a second time,
|
|
38
|
+
* which would otherwise let a still-open outage's cache entries escape
|
|
39
|
+
* eviction under a stale epoch number).
|
|
40
|
+
*/
|
|
41
|
+
close() {
|
|
42
|
+
if (!this.#open) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.#open = false;
|
|
46
|
+
this.#epoch += 1;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Open the gate, resolving every {@link wait} caller currently pending —
|
|
50
|
+
* including ones that subscribed after `close()` but before this call.
|
|
51
|
+
* Idempotent — calling `open()` while already open does nothing.
|
|
52
|
+
*/
|
|
53
|
+
open() {
|
|
54
|
+
if (this.#open) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.#open = true;
|
|
58
|
+
const waiters = this.#waiters;
|
|
59
|
+
this.#waiters = [];
|
|
60
|
+
for (const resolve of waiters) {
|
|
61
|
+
resolve();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve immediately if the gate is open; otherwise resolve on the next
|
|
66
|
+
* {@link open}. Multiple concurrent callers all resolve on the same
|
|
67
|
+
* `open()` call — no awaiter is dropped regardless of when it subscribed
|
|
68
|
+
* relative to others.
|
|
69
|
+
*/
|
|
70
|
+
wait() {
|
|
71
|
+
if (this.#open) {
|
|
72
|
+
return Promise.resolve();
|
|
73
|
+
}
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
this.#waiters.push(resolve);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { NetworkErrorRecord, NetworkOutageDetectorOptions, OutageSuspect } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sliding-window detector for "this looks like the crawl operator's own
|
|
4
|
+
* network, not the target sites, is having a bad time" — the trigger that
|
|
5
|
+
* hands off to an active probe (see `probe-network.ts`) before the crawler
|
|
6
|
+
* commits to pausing.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately dependency-free: no timers, no DB, no `Crawler` instance.
|
|
9
|
+
* Time flows in via {@link NetworkErrorRecord.at} only, which is what makes
|
|
10
|
+
* window-boundary behaviour testable with exact values instead of
|
|
11
|
+
* `vi.useFakeTimers()`.
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const detector = new NetworkOutageDetector({
|
|
15
|
+
* windowMs: 10_000,
|
|
16
|
+
* errorThreshold: 5,
|
|
17
|
+
* hostThreshold: 2,
|
|
18
|
+
* });
|
|
19
|
+
* const suspect = detector.record({ kind: 'dns', host: 'a.example', at: Date.now() });
|
|
20
|
+
* if (suspect) {
|
|
21
|
+
* // probe before closing the gate
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export default class NetworkOutageDetector {
|
|
26
|
+
#private;
|
|
27
|
+
constructor(options: NetworkOutageDetectorOptions);
|
|
28
|
+
/**
|
|
29
|
+
* Record one observed error and check whether it tips the sliding window
|
|
30
|
+
* over both thresholds.
|
|
31
|
+
*
|
|
32
|
+
* Non-network `kind`s (see `NETWORK_RELATED_ERROR_KINDS`) are silently
|
|
33
|
+
* ignored: they never enter the window and can never contribute to a
|
|
34
|
+
* trigger, regardless of how many arrive.
|
|
35
|
+
* @param record - The observed error.
|
|
36
|
+
* @param record.kind
|
|
37
|
+
* @param record.host
|
|
38
|
+
* @param record.at
|
|
39
|
+
* @returns An {@link OutageSuspect} the instant both thresholds are met,
|
|
40
|
+
* or `null` otherwise. On a trigger, the window is cleared so the same
|
|
41
|
+
* batch of errors cannot fire a second time on the next call.
|
|
42
|
+
*/
|
|
43
|
+
record({ kind, host, at }: NetworkErrorRecord): OutageSuspect | null;
|
|
44
|
+
/**
|
|
45
|
+
* Clear the sliding window. Called at the start of a fresh crawl session
|
|
46
|
+
* (`Crawler.#runDeal`) so error observations from a prior session on the
|
|
47
|
+
* same `Crawler` instance never carry over — mirrors `#successfulHosts
|
|
48
|
+
* .clear()` / `#scrapedDestinations.clear()`'s per-session reset.
|
|
49
|
+
*/
|
|
50
|
+
reset(): void;
|
|
51
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NETWORK_RELATED_ERROR_KINDS } from '../network-related-error-kinds.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sliding-window detector for "this looks like the crawl operator's own
|
|
4
|
+
* network, not the target sites, is having a bad time" — the trigger that
|
|
5
|
+
* hands off to an active probe (see `probe-network.ts`) before the crawler
|
|
6
|
+
* commits to pausing.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately dependency-free: no timers, no DB, no `Crawler` instance.
|
|
9
|
+
* Time flows in via {@link NetworkErrorRecord.at} only, which is what makes
|
|
10
|
+
* window-boundary behaviour testable with exact values instead of
|
|
11
|
+
* `vi.useFakeTimers()`.
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const detector = new NetworkOutageDetector({
|
|
15
|
+
* windowMs: 10_000,
|
|
16
|
+
* errorThreshold: 5,
|
|
17
|
+
* hostThreshold: 2,
|
|
18
|
+
* });
|
|
19
|
+
* const suspect = detector.record({ kind: 'dns', host: 'a.example', at: Date.now() });
|
|
20
|
+
* if (suspect) {
|
|
21
|
+
* // probe before closing the gate
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export default class NetworkOutageDetector {
|
|
26
|
+
#entries = [];
|
|
27
|
+
#errorThreshold;
|
|
28
|
+
#hostThreshold;
|
|
29
|
+
#windowMs;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.#windowMs = options.windowMs;
|
|
32
|
+
this.#errorThreshold = options.errorThreshold;
|
|
33
|
+
this.#hostThreshold = options.hostThreshold;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Record one observed error and check whether it tips the sliding window
|
|
37
|
+
* over both thresholds.
|
|
38
|
+
*
|
|
39
|
+
* Non-network `kind`s (see `NETWORK_RELATED_ERROR_KINDS`) are silently
|
|
40
|
+
* ignored: they never enter the window and can never contribute to a
|
|
41
|
+
* trigger, regardless of how many arrive.
|
|
42
|
+
* @param record - The observed error.
|
|
43
|
+
* @param record.kind
|
|
44
|
+
* @param record.host
|
|
45
|
+
* @param record.at
|
|
46
|
+
* @returns An {@link OutageSuspect} the instant both thresholds are met,
|
|
47
|
+
* or `null` otherwise. On a trigger, the window is cleared so the same
|
|
48
|
+
* batch of errors cannot fire a second time on the next call.
|
|
49
|
+
*/
|
|
50
|
+
record({ kind, host, at }) {
|
|
51
|
+
if (!NETWORK_RELATED_ERROR_KINDS.has(kind)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const cutoff = at - this.#windowMs;
|
|
55
|
+
this.#entries = this.#entries.filter((entry) => entry.at >= cutoff);
|
|
56
|
+
this.#entries.push({ host, at });
|
|
57
|
+
const distinctHosts = new Set(this.#entries.map((entry) => entry.host)).size;
|
|
58
|
+
if (this.#entries.length < this.#errorThreshold ||
|
|
59
|
+
distinctHosts < this.#hostThreshold) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const startedAt = Math.min(...this.#entries.map((entry) => entry.at));
|
|
63
|
+
const suspect = {
|
|
64
|
+
startedAt,
|
|
65
|
+
detectedAt: at,
|
|
66
|
+
triggerErrorCount: this.#entries.length,
|
|
67
|
+
triggerHostCount: distinctHosts,
|
|
68
|
+
};
|
|
69
|
+
this.#entries = [];
|
|
70
|
+
return suspect;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Clear the sliding window. Called at the start of a fresh crawl session
|
|
74
|
+
* (`Crawler.#runDeal`) so error observations from a prior session on the
|
|
75
|
+
* same `Crawler` instance never carry over — mirrors `#successfulHosts
|
|
76
|
+
* .clear()` / `#scrapedDestinations.clear()`'s per-session reset.
|
|
77
|
+
*/
|
|
78
|
+
reset() {
|
|
79
|
+
this.#entries = [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped counter for how many operator-network outages were
|
|
3
|
+
* confirmed, and their total duration. The orchestrator accumulates into
|
|
4
|
+
* this at every `networkOutageRecovered` event, reads it at crawl shutdown
|
|
5
|
+
* to emit a `[network] N outage(s), Ms total` summary line, then zeroes it
|
|
6
|
+
* for the next session (mirrors `dnsBurnedHostShortCircuitCounter`'s
|
|
7
|
+
* lifecycle, including its known limitation: two `CrawlerOrchestrator`
|
|
8
|
+
* crawls running concurrently in the same process would accumulate into and
|
|
9
|
+
* zero the same shared counter, cross-contaminating each other's summary.
|
|
10
|
+
* The CLI only ever runs one crawl per process, so this has never been
|
|
11
|
+
* observed in practice; fixing it would mean threading per-session state
|
|
12
|
+
* through both counters together, out of scope here).
|
|
13
|
+
*
|
|
14
|
+
* Exposed as a mutable object (not a plain `let`) so the counter remains a
|
|
15
|
+
* single shared reference across crawler / orchestrator imports — `let`
|
|
16
|
+
* bindings cannot be mutated from another module.
|
|
17
|
+
*/
|
|
18
|
+
export declare const networkOutageSummaryCounter: {
|
|
19
|
+
confirmedCount: number;
|
|
20
|
+
totalDurationMs: number;
|
|
21
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped counter for how many operator-network outages were
|
|
3
|
+
* confirmed, and their total duration. The orchestrator accumulates into
|
|
4
|
+
* this at every `networkOutageRecovered` event, reads it at crawl shutdown
|
|
5
|
+
* to emit a `[network] N outage(s), Ms total` summary line, then zeroes it
|
|
6
|
+
* for the next session (mirrors `dnsBurnedHostShortCircuitCounter`'s
|
|
7
|
+
* lifecycle, including its known limitation: two `CrawlerOrchestrator`
|
|
8
|
+
* crawls running concurrently in the same process would accumulate into and
|
|
9
|
+
* zero the same shared counter, cross-contaminating each other's summary.
|
|
10
|
+
* The CLI only ever runs one crawl per process, so this has never been
|
|
11
|
+
* observed in practice; fixing it would mean threading per-session state
|
|
12
|
+
* through both counters together, out of scope here).
|
|
13
|
+
*
|
|
14
|
+
* Exposed as a mutable object (not a plain `let`) so the counter remains a
|
|
15
|
+
* single shared reference across crawler / orchestrator imports — `let`
|
|
16
|
+
* bindings cannot be mutated from another module.
|
|
17
|
+
*/
|
|
18
|
+
export const networkOutageSummaryCounter = { confirmedCount: 0, totalDurationMs: 0 };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function shape for an injectable network-reachability probe: given a
|
|
3
|
+
* hostname, resolve to `true` if the network path to it appears healthy,
|
|
4
|
+
* `false` otherwise. Never rejects — a probe failure is a normal outcome
|
|
5
|
+
* (that's the whole point of probing), not an exceptional one.
|
|
6
|
+
*
|
|
7
|
+
* Injected via `CrawlerOptions.networkProbe` so tests can simulate
|
|
8
|
+
* confirmed outages and recoveries deterministically (a function that fails
|
|
9
|
+
* N times then starts succeeding) without touching the real network — the
|
|
10
|
+
* same injection pattern as `ResourceLookup` / `PageSourceLookup`.
|
|
11
|
+
*/
|
|
12
|
+
export type NetworkProbe = (host: string) => Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Default {@link NetworkProbe}: an active `dns.lookup` against `host`.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately does not hit a hardcoded external address (e.g. `1.1.1.1`) —
|
|
17
|
+
* probing a host that has already answered successfully during this crawl
|
|
18
|
+
* session (see `choose-probe-host.ts`) avoids depending on infrastructure
|
|
19
|
+
* outside the sites actually being crawled. `dns.lookup` alone is enough:
|
|
20
|
+
* it exercises the operator's local resolver / network path, which is
|
|
21
|
+
* exactly the layer an operator-side outage breaks.
|
|
22
|
+
* @param host - Hostname to resolve.
|
|
23
|
+
* @returns `true` if the lookup succeeds, `false` on any error (including
|
|
24
|
+
* NXDOMAIN, timeout, or resolver unavailability).
|
|
25
|
+
*/
|
|
26
|
+
export declare function probeNetwork(host: string): Promise<boolean>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { lookup } from 'node:dns';
|
|
2
|
+
/**
|
|
3
|
+
* Default {@link NetworkProbe}: an active `dns.lookup` against `host`.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately does not hit a hardcoded external address (e.g. `1.1.1.1`) —
|
|
6
|
+
* probing a host that has already answered successfully during this crawl
|
|
7
|
+
* session (see `choose-probe-host.ts`) avoids depending on infrastructure
|
|
8
|
+
* outside the sites actually being crawled. `dns.lookup` alone is enough:
|
|
9
|
+
* it exercises the operator's local resolver / network path, which is
|
|
10
|
+
* exactly the layer an operator-side outage breaks.
|
|
11
|
+
* @param host - Hostname to resolve.
|
|
12
|
+
* @returns `true` if the lookup succeeds, `false` on any error (including
|
|
13
|
+
* NXDOMAIN, timeout, or resolver unavailability).
|
|
14
|
+
*/
|
|
15
|
+
export function probeNetwork(host) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
lookup(host, (error) => {
|
|
18
|
+
resolve(!error);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
package/lib/crawler/types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { NetworkProbe } from './probe-network.js';
|
|
1
2
|
import type { PageSource } from '../archive/types.js';
|
|
3
|
+
import type { ErrorKind } from '../types.js';
|
|
2
4
|
import type { PageData, CrawlerError, Resource } from '../utils/types/types.js';
|
|
3
|
-
import type { ChangePhaseEvent, ScrapeResult } from '@d-zero/beholder';
|
|
5
|
+
import type { ChangePhaseEvent, ConsoleLogEntry, ScrapeResult } from '@d-zero/beholder';
|
|
4
6
|
import type { ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
5
7
|
/**
|
|
6
8
|
* Result of resolving a URL that redirects to a destination already rendered
|
|
@@ -129,6 +131,11 @@ export interface CrawlerOptions extends Required<Pick<ParseURLOptions, 'disableQ
|
|
|
129
131
|
userAgent: string;
|
|
130
132
|
/** Whether to ignore robots.txt restrictions. */
|
|
131
133
|
ignoreRobots: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* CSS selector overriding beholder's automatic main-content-region
|
|
136
|
+
* detection, or `null`/undefined to use the automatic heuristic.
|
|
137
|
+
*/
|
|
138
|
+
mainContentSelector?: string | null;
|
|
132
139
|
/**
|
|
133
140
|
* Lookup for previously captured sub-resources, or `null` to disable the
|
|
134
141
|
* resource-reuse optimization. See {@link ResourceLookup}.
|
|
@@ -155,6 +162,34 @@ export interface CrawlerOptions extends Required<Pick<ParseURLOptions, 'disableQ
|
|
|
155
162
|
* the DB DEFAULT `'crawled'` applies.
|
|
156
163
|
*/
|
|
157
164
|
inventoryMode: InventoryMode | null;
|
|
165
|
+
/**
|
|
166
|
+
* Sliding-window size in ms for network-outage suspicion. See
|
|
167
|
+
* `NetworkOutageDetector`.
|
|
168
|
+
*/
|
|
169
|
+
networkOutageWindowMs: number;
|
|
170
|
+
/**
|
|
171
|
+
* Minimum error count within {@link networkOutageWindowMs} to declare a
|
|
172
|
+
* suspect outage.
|
|
173
|
+
*/
|
|
174
|
+
networkOutageErrorThreshold: number;
|
|
175
|
+
/**
|
|
176
|
+
* Minimum distinct-host count within {@link networkOutageWindowMs} to
|
|
177
|
+
* declare a suspect outage. Guards against a single flaky host looking
|
|
178
|
+
* like a network-wide event.
|
|
179
|
+
*/
|
|
180
|
+
networkOutageHostThreshold: number;
|
|
181
|
+
/**
|
|
182
|
+
* Interval in ms between recovery probes while the network gate is
|
|
183
|
+
* closed.
|
|
184
|
+
*/
|
|
185
|
+
networkOutageProbeIntervalMs: number;
|
|
186
|
+
/**
|
|
187
|
+
* Injectable network-reachability probe, or `null` to use the default
|
|
188
|
+
* `dns.lookup`-based `probeNetwork`. Overriding this is the seam tests
|
|
189
|
+
* use to simulate confirmed outages and recoveries deterministically
|
|
190
|
+
* without touching the real network.
|
|
191
|
+
*/
|
|
192
|
+
networkProbe: NetworkProbe | null;
|
|
158
193
|
}
|
|
159
194
|
/**
|
|
160
195
|
* Inventory-mode runtime configuration. Passed from
|
|
@@ -289,6 +324,31 @@ export interface CrawlerEventTypes {
|
|
|
289
324
|
/** The URL of the referenced resource (without hash). */
|
|
290
325
|
src: string;
|
|
291
326
|
};
|
|
327
|
+
/**
|
|
328
|
+
* Emitted once per scrape with the console messages / page errors
|
|
329
|
+
* beholder captured for that page (issue #228). Only emitted when
|
|
330
|
+
* `entries` is non-empty — see `Crawler#handleConsoleLogs` for why a
|
|
331
|
+
* degraded re-scrape that captures nothing must not clear prior good
|
|
332
|
+
* data.
|
|
333
|
+
*/
|
|
334
|
+
consoleLogs: {
|
|
335
|
+
/**
|
|
336
|
+
* The originally-requested URL, normalised (`withoutHashAndAuth`
|
|
337
|
+
* form) — the same identity `updatePage` resolves its redirect
|
|
338
|
+
* chain from, NOT necessarily the page that ends up holding the
|
|
339
|
+
* content.
|
|
340
|
+
*/
|
|
341
|
+
pageUrl: string;
|
|
342
|
+
/**
|
|
343
|
+
* The redirect chain hops captured during fetch, in order. Empty
|
|
344
|
+
* when the page was not redirected, or when the scrape produced no
|
|
345
|
+
* `pageData` (a `'skipped'` / `'error'` result) and no redirect
|
|
346
|
+
* information is available.
|
|
347
|
+
*/
|
|
348
|
+
redirectPaths: readonly string[];
|
|
349
|
+
/** The captured console messages / page errors, in capture order. */
|
|
350
|
+
entries: ConsoleLogEntry[];
|
|
351
|
+
};
|
|
292
352
|
/**
|
|
293
353
|
* Emitted when the entire crawl process has completed or been aborted.
|
|
294
354
|
*/
|
|
@@ -344,4 +404,95 @@ export interface CrawlerEventTypes {
|
|
|
344
404
|
*/
|
|
345
405
|
source: PageSource | undefined;
|
|
346
406
|
};
|
|
407
|
+
/**
|
|
408
|
+
* Emitted the instant `Crawler` closes its internal network gate after a
|
|
409
|
+
* recovery probe CONFIRMS a suspect outage (the sliding-window threshold
|
|
410
|
+
* alone only makes it a suspect — see `NetworkOutageDetector`). The
|
|
411
|
+
* orchestrator persists this via `Archive.insertNetworkOutage` and must
|
|
412
|
+
* remember the returned row id to pass to the matching
|
|
413
|
+
* `networkOutageRecovered` event, since `Crawler` itself never touches
|
|
414
|
+
* the archive and has no way to know the row's id.
|
|
415
|
+
*/
|
|
416
|
+
networkOutageConfirmed: {
|
|
417
|
+
/** Backdated to the earliest error still inside the detector's window at trigger time. */
|
|
418
|
+
startedAt: number;
|
|
419
|
+
/** When the sliding window actually crossed both thresholds. */
|
|
420
|
+
detectedAt: number;
|
|
421
|
+
/** Hostname the recovery probe is targeting, or `null` if none was available. */
|
|
422
|
+
probeHost: string | null;
|
|
423
|
+
triggerErrorCount: number;
|
|
424
|
+
triggerHostCount: number;
|
|
425
|
+
};
|
|
426
|
+
/**
|
|
427
|
+
* Emitted the instant `Crawler` reopens its internal network gate after
|
|
428
|
+
* a recovery probe succeeds. NOT emitted when the gate is opened
|
|
429
|
+
* because the crawl was aborted while paused — in that case the outage
|
|
430
|
+
* row is deliberately left open for the next writer session's
|
|
431
|
+
* boot-time finalizer to resolve (see
|
|
432
|
+
* `db-ops/outages/close-stale-open-network-outages.ts`), since an abort
|
|
433
|
+
* says nothing about whether the network actually recovered.
|
|
434
|
+
*/
|
|
435
|
+
networkOutageRecovered: {
|
|
436
|
+
/** Epoch ms the recovery probe first succeeded. */
|
|
437
|
+
endedAt: number;
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Tunables for `NetworkOutageDetector`.
|
|
442
|
+
*/
|
|
443
|
+
export interface NetworkOutageDetectorOptions {
|
|
444
|
+
/**
|
|
445
|
+
* Sliding-window size in ms (`W`). Before each check, entries older than
|
|
446
|
+
* `at - windowMs` (inclusive boundary — an entry exactly `windowMs` old
|
|
447
|
+
* still counts) are evicted.
|
|
448
|
+
*/
|
|
449
|
+
readonly windowMs: number;
|
|
450
|
+
/** Minimum error count within the window to declare a suspect outage (`N`). */
|
|
451
|
+
readonly errorThreshold: number;
|
|
452
|
+
/**
|
|
453
|
+
* Minimum number of DISTINCT hosts represented in the window to declare
|
|
454
|
+
* a suspect outage (`M`). Guards against one flaky host (a site that is
|
|
455
|
+
* genuinely retrying/failing on its own) looking like a network-wide
|
|
456
|
+
* event — a real local-network blip surfaces across unrelated hosts at
|
|
457
|
+
* once.
|
|
458
|
+
*/
|
|
459
|
+
readonly hostThreshold: number;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* One observed error, as fed to `NetworkOutageDetector.record`.
|
|
463
|
+
*/
|
|
464
|
+
export interface NetworkErrorRecord {
|
|
465
|
+
readonly kind: ErrorKind;
|
|
466
|
+
readonly host: string;
|
|
467
|
+
/**
|
|
468
|
+
* Epoch ms this error was observed. Caller-supplied — the detector never
|
|
469
|
+
* calls `Date.now()` itself, so window-boundary behaviour can be pinned
|
|
470
|
+
* with exact values instead of fake timers. Callers MUST supply
|
|
471
|
+
* non-decreasing values across successive `record()` calls; the window
|
|
472
|
+
* eviction is a simple cutoff against the latest `at` and does not
|
|
473
|
+
* re-sort out-of-order input.
|
|
474
|
+
*/
|
|
475
|
+
readonly at: number;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Emitted by `NetworkOutageDetector.record` the instant the sliding window
|
|
479
|
+
* crosses both thresholds.
|
|
480
|
+
*/
|
|
481
|
+
export interface OutageSuspect {
|
|
482
|
+
/**
|
|
483
|
+
* Backdated to the earliest error still inside the window at trigger
|
|
484
|
+
* time — NOT the trigger instant itself. A sliding-window detector only
|
|
485
|
+
* confirms an outage after `W` seconds and `N` errors have accumulated,
|
|
486
|
+
* so the outage itself started earlier; backdating lets the persisted
|
|
487
|
+
* `network_outages` row (and the failures it retroactively covers) reach
|
|
488
|
+
* back to that earlier point instead of losing everything the detector
|
|
489
|
+
* missed while still accumulating evidence.
|
|
490
|
+
*/
|
|
491
|
+
readonly startedAt: number;
|
|
492
|
+
/** The `at` of the record that tripped the threshold. */
|
|
493
|
+
readonly detectedAt: number;
|
|
494
|
+
/** Window size at trigger time (== `errorThreshold` or more). */
|
|
495
|
+
readonly triggerErrorCount: number;
|
|
496
|
+
/** Distinct host count at trigger time (== `hostThreshold` or more). */
|
|
497
|
+
readonly triggerHostCount: number;
|
|
347
498
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Config } from './archive/types.js';
|
|
2
|
+
import type { NetworkProbe } from './crawler/probe-network.js';
|
|
2
3
|
import type { InventoryMode } from './crawler/types.js';
|
|
3
4
|
import type { CrawlEvent } from './types.js';
|
|
4
5
|
import type { ExURL } from '@d-zero/shared/parse-url';
|
|
@@ -48,6 +49,26 @@ interface CrawlConfig extends Config {
|
|
|
48
49
|
* this `null` so new rows are labelled `'crawled'` by the DB DEFAULT.
|
|
49
50
|
*/
|
|
50
51
|
inventoryMode: InventoryMode | null;
|
|
52
|
+
/**
|
|
53
|
+
* See {@link CrawlerOptions.networkOutageWindowMs}. Omitted (`undefined`
|
|
54
|
+
* on the `Partial<CrawlConfig>` callers actually pass) falls through to
|
|
55
|
+
* `Crawler`'s own default — this field exists so tests can shrink the
|
|
56
|
+
* window for a fast, deterministic outage-detection cycle.
|
|
57
|
+
*/
|
|
58
|
+
networkOutageWindowMs: number;
|
|
59
|
+
/** See {@link CrawlerOptions.networkOutageErrorThreshold}. */
|
|
60
|
+
networkOutageErrorThreshold: number;
|
|
61
|
+
/** See {@link CrawlerOptions.networkOutageHostThreshold}. */
|
|
62
|
+
networkOutageHostThreshold: number;
|
|
63
|
+
/** See {@link CrawlerOptions.networkOutageProbeIntervalMs}. */
|
|
64
|
+
networkOutageProbeIntervalMs: number;
|
|
65
|
+
/**
|
|
66
|
+
* See {@link CrawlerOptions.networkProbe}. The seam tests use to simulate
|
|
67
|
+
* confirmed outages and recoveries deterministically without touching
|
|
68
|
+
* the real network — plumbed through from `CrawlerOrchestrator.crawling`'s
|
|
69
|
+
* `options` so an E2E test can inject it via the public API.
|
|
70
|
+
*/
|
|
71
|
+
networkProbe: NetworkProbe | null;
|
|
51
72
|
}
|
|
52
73
|
/**
|
|
53
74
|
* Callback invoked after the CrawlerOrchestrator instance is fully initialized
|
|
@@ -56,6 +77,20 @@ interface CrawlConfig extends Config {
|
|
|
56
77
|
* @param config - The resolved archive configuration.
|
|
57
78
|
*/
|
|
58
79
|
type CrawlInitializedCallback = (orchestrator: CrawlerOrchestrator, config: Config) => void | Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* The CLI's already-read `--inventory` source list, passed to
|
|
82
|
+
* {@link CrawlerOrchestrator.inventory} instead of a file path — see that
|
|
83
|
+
* method's `source` param for why the path itself never crosses this
|
|
84
|
+
* boundary.
|
|
85
|
+
*/
|
|
86
|
+
interface InventorySource {
|
|
87
|
+
/** Lower-case hex SHA-256 digest of `bytes` (`computeFileSha256(bytes)`). */
|
|
88
|
+
sha256: string;
|
|
89
|
+
/** The exact bytes of the source list file, archived verbatim. */
|
|
90
|
+
bytes: Buffer;
|
|
91
|
+
/** Number of source-file lines the CLI warned-and-dropped for failing URL validation, before `inventoryUrls` was ever built. Recorded on the audit row as `inventory_runs.invalid_skipped`. */
|
|
92
|
+
invalidLineCount: number;
|
|
93
|
+
}
|
|
59
94
|
/**
|
|
60
95
|
* The main entry point for Nitpicker web crawling and archiving.
|
|
61
96
|
*
|
|
@@ -184,22 +219,29 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
|
|
|
184
219
|
* 3. Reject archives with unfinished `pending` URLs — those would inherit
|
|
185
220
|
* the inventory `source` label by mistake. Operator must resume /
|
|
186
221
|
* retry-failed first.
|
|
187
|
-
* 4.
|
|
188
|
-
*
|
|
189
|
-
*
|
|
222
|
+
* 4. If `source` is given, archive its exact bytes under
|
|
223
|
+
* `inventory/<sha256>.txt` (see {@link Archive.saveInventorySourceList}).
|
|
224
|
+
* Done before scope classification so even a run that discards every
|
|
225
|
+
* URL (out of scope or already known) still leaves a recoverable
|
|
226
|
+
* copy of what was fed in.
|
|
227
|
+
* 5. Parse the URL list — the CLI has already warned-and-dropped
|
|
228
|
+
* unparseable-URL lines before calling this method, so every
|
|
229
|
+
* remaining entry parses. Anything outside the archived scope is
|
|
230
|
+
* warned and skipped (inventory is per-server by design).
|
|
231
|
+
* 6. Subtract URLs that already exist in `pages` or `resources` so the
|
|
190
232
|
* second (and N-th) inventory pass is a no-op for known rows — keeps
|
|
191
233
|
* `'inventory-seed'` rows from being silently demoted.
|
|
192
|
-
*
|
|
234
|
+
* 7. Make `<archive>.bak`. Anything thrown beyond this point restores
|
|
193
235
|
* from the backup.
|
|
194
|
-
*
|
|
236
|
+
* 8. HEAD-probe each novel URL. Responses classified as HTML are queued
|
|
195
237
|
* as Crawler seeds (`'inventory-seed'`); everything else is recorded
|
|
196
238
|
* in `resources` directly as `'inventory-seed'` (no browser launch).
|
|
197
|
-
*
|
|
239
|
+
* 9. If any HTML seeds exist, start a Crawler with
|
|
198
240
|
* `inventoryMode = { seedUrls }` so the rendered page and every newly
|
|
199
241
|
* discovered downstream link is labelled correctly. `resume` is fed
|
|
200
242
|
* the existing `scraped` / `resources` sets so links into already-
|
|
201
243
|
* crawled pages stop at the seen-gate without re-rendering.
|
|
202
|
-
*
|
|
244
|
+
* 10. Drop the backup on success; restore it on any throw.
|
|
203
245
|
*
|
|
204
246
|
* Mutually exclusive with `--append` / `--retry-failed` / `--resume` /
|
|
205
247
|
* `--diff` / `--list` / `--list-file` / `--single` / `--output` — the
|
|
@@ -209,20 +251,22 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
|
|
|
209
251
|
* @param inventoryUrls - Pre-read URL list (one URL per element).
|
|
210
252
|
* @param options - Optional config overrides — most callers leave this blank and let the archived config flow through.
|
|
211
253
|
* @param initializedCallback - Hook invoked once the orchestrator is constructed but before `crawling` runs (the CLI uses it to attach progress reporting).
|
|
212
|
-
* @param
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* `
|
|
254
|
+
* @param source - The CLI's already-read source list, as `{ sha256, bytes }`.
|
|
255
|
+
* The orchestrator deliberately does NOT receive the file path: the
|
|
256
|
+
* path is privacy-sensitive (leaks user-home / OS structure when
|
|
257
|
+
* archives are shared) and we want it lifted off this boundary so no
|
|
258
|
+
* future log line / breadcrumb / error message inside the orchestrator
|
|
259
|
+
* can accidentally re-leak it. `bytes` is archived verbatim under
|
|
260
|
+
* `inventory/<sha256>.txt` (see {@link Archive.saveInventorySourceList})
|
|
261
|
+
* before scope classification, so a later `--inventory` run against
|
|
262
|
+
* the same list is an audit no-op even when it discards zero new
|
|
263
|
+
* URLs. Pass `null` for programmatic callers that built
|
|
264
|
+
* `inventoryUrls` in-memory; the audit row's `source_file_sha256`
|
|
265
|
+
* column will be `NULL` and no source list is archived.
|
|
222
266
|
* @returns The orchestrator instance after a successful inventory pass.
|
|
223
267
|
* @throws {Error} When `inventoryUrls` is empty, the archive is in list mode, or pending URLs from a previous crawl remain unresolved.
|
|
224
268
|
*/
|
|
225
|
-
static inventory(archivePath: string, inventoryUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback,
|
|
269
|
+
static inventory(archivePath: string, inventoryUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, source?: InventorySource | null): Promise<CrawlerOrchestrator>;
|
|
226
270
|
/**
|
|
227
271
|
* Re-fetch previously-failed pages in an existing `.nitpicker` archive.
|
|
228
272
|
*
|