@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
|
@@ -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
|
|
@@ -160,6 +162,57 @@ export interface CrawlerOptions extends Required<Pick<ParseURLOptions, 'disableQ
|
|
|
160
162
|
* the DB DEFAULT `'crawled'` applies.
|
|
161
163
|
*/
|
|
162
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;
|
|
193
|
+
/**
|
|
194
|
+
* Same-cluster soft-cap threshold (`--dedupe-cap`), or `null` to disable
|
|
195
|
+
* the feature entirely (the default). When set, {@link Crawler} stops
|
|
196
|
+
* enqueueing newly-discovered URLs whose shape (see `computeShapeKey`)
|
|
197
|
+
* has accumulated this many matching-signature observations (see
|
|
198
|
+
* `DedupeCapTracker`).
|
|
199
|
+
*/
|
|
200
|
+
dedupeCap: number | null;
|
|
201
|
+
/**
|
|
202
|
+
* Hard cap on the number of distinct URL shapes the same-cluster soft
|
|
203
|
+
* cap tracks at once (`--dedupe-map-cap`); the least-recently-touched
|
|
204
|
+
* shape is evicted beyond this. Only relevant when {@link dedupeCap} is
|
|
205
|
+
* non-null.
|
|
206
|
+
*/
|
|
207
|
+
dedupeMapCap: number;
|
|
208
|
+
/**
|
|
209
|
+
* Shape keys already confirmed capped in a prior session
|
|
210
|
+
* (persisted as `dedupe_cap_events.shape_key`), seeded into the
|
|
211
|
+
* tracker's sticky set so `--resume` / `--append` / `--retry-failed` /
|
|
212
|
+
* `--inventory` do not re-admit a trap this crawl already paid the cost
|
|
213
|
+
* of discovering once. Ignored when {@link dedupeCap} is `null`.
|
|
214
|
+
*/
|
|
215
|
+
preloadedStickyShapeKeys: readonly string[];
|
|
163
216
|
}
|
|
164
217
|
/**
|
|
165
218
|
* Inventory-mode runtime configuration. Passed from
|
|
@@ -294,6 +347,31 @@ export interface CrawlerEventTypes {
|
|
|
294
347
|
/** The URL of the referenced resource (without hash). */
|
|
295
348
|
src: string;
|
|
296
349
|
};
|
|
350
|
+
/**
|
|
351
|
+
* Emitted once per scrape with the console messages / page errors
|
|
352
|
+
* beholder captured for that page (issue #228). Only emitted when
|
|
353
|
+
* `entries` is non-empty — see `Crawler#handleConsoleLogs` for why a
|
|
354
|
+
* degraded re-scrape that captures nothing must not clear prior good
|
|
355
|
+
* data.
|
|
356
|
+
*/
|
|
357
|
+
consoleLogs: {
|
|
358
|
+
/**
|
|
359
|
+
* The originally-requested URL, normalised (`withoutHashAndAuth`
|
|
360
|
+
* form) — the same identity `updatePage` resolves its redirect
|
|
361
|
+
* chain from, NOT necessarily the page that ends up holding the
|
|
362
|
+
* content.
|
|
363
|
+
*/
|
|
364
|
+
pageUrl: string;
|
|
365
|
+
/**
|
|
366
|
+
* The redirect chain hops captured during fetch, in order. Empty
|
|
367
|
+
* when the page was not redirected, or when the scrape produced no
|
|
368
|
+
* `pageData` (a `'skipped'` / `'error'` result) and no redirect
|
|
369
|
+
* information is available.
|
|
370
|
+
*/
|
|
371
|
+
redirectPaths: readonly string[];
|
|
372
|
+
/** The captured console messages / page errors, in capture order. */
|
|
373
|
+
entries: ConsoleLogEntry[];
|
|
374
|
+
};
|
|
297
375
|
/**
|
|
298
376
|
* Emitted when the entire crawl process has completed or been aborted.
|
|
299
377
|
*/
|
|
@@ -349,4 +427,110 @@ export interface CrawlerEventTypes {
|
|
|
349
427
|
*/
|
|
350
428
|
source: PageSource | undefined;
|
|
351
429
|
};
|
|
430
|
+
/**
|
|
431
|
+
* Emitted the instant `Crawler` closes its internal network gate after a
|
|
432
|
+
* recovery probe CONFIRMS a suspect outage (the sliding-window threshold
|
|
433
|
+
* alone only makes it a suspect — see `NetworkOutageDetector`). The
|
|
434
|
+
* orchestrator persists this via `Archive.insertNetworkOutage` and must
|
|
435
|
+
* remember the returned row id to pass to the matching
|
|
436
|
+
* `networkOutageRecovered` event, since `Crawler` itself never touches
|
|
437
|
+
* the archive and has no way to know the row's id.
|
|
438
|
+
*/
|
|
439
|
+
networkOutageConfirmed: {
|
|
440
|
+
/** Backdated to the earliest error still inside the detector's window at trigger time. */
|
|
441
|
+
startedAt: number;
|
|
442
|
+
/** When the sliding window actually crossed both thresholds. */
|
|
443
|
+
detectedAt: number;
|
|
444
|
+
/** Hostname the recovery probe is targeting, or `null` if none was available. */
|
|
445
|
+
probeHost: string | null;
|
|
446
|
+
triggerErrorCount: number;
|
|
447
|
+
triggerHostCount: number;
|
|
448
|
+
};
|
|
449
|
+
/**
|
|
450
|
+
* Emitted the instant `Crawler` reopens its internal network gate after
|
|
451
|
+
* a recovery probe succeeds. NOT emitted when the gate is opened
|
|
452
|
+
* because the crawl was aborted while paused — in that case the outage
|
|
453
|
+
* row is deliberately left open for the next writer session's
|
|
454
|
+
* boot-time finalizer to resolve (see
|
|
455
|
+
* `db-ops/outages/close-stale-open-network-outages.ts`), since an abort
|
|
456
|
+
* says nothing about whether the network actually recovered.
|
|
457
|
+
*/
|
|
458
|
+
networkOutageRecovered: {
|
|
459
|
+
/** Epoch ms the recovery probe first succeeded. */
|
|
460
|
+
endedAt: number;
|
|
461
|
+
};
|
|
462
|
+
/**
|
|
463
|
+
* Emitted the instant the opt-in same-cluster soft cap
|
|
464
|
+
* ({@link CrawlerOptions.dedupeCap}) confirms a URL shape as a trap (see
|
|
465
|
+
* `DedupeCapTracker`). The orchestrator persists this via
|
|
466
|
+
* `Archive.insertDedupeCapEvent` and must remember the returned row id so
|
|
467
|
+
* `rejected_count` can be finalized once at `crawlEnd` (`Crawler` itself
|
|
468
|
+
* never touches the archive).
|
|
469
|
+
*/
|
|
470
|
+
dedupeCap: {
|
|
471
|
+
shapeKey: string;
|
|
472
|
+
sampleUrl: string;
|
|
473
|
+
bodyHash: Buffer;
|
|
474
|
+
effectiveThreshold: number;
|
|
475
|
+
observedCount: number;
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Tunables for `NetworkOutageDetector`.
|
|
480
|
+
*/
|
|
481
|
+
export interface NetworkOutageDetectorOptions {
|
|
482
|
+
/**
|
|
483
|
+
* Sliding-window size in ms (`W`). Before each check, entries older than
|
|
484
|
+
* `at - windowMs` (inclusive boundary — an entry exactly `windowMs` old
|
|
485
|
+
* still counts) are evicted.
|
|
486
|
+
*/
|
|
487
|
+
readonly windowMs: number;
|
|
488
|
+
/** Minimum error count within the window to declare a suspect outage (`N`). */
|
|
489
|
+
readonly errorThreshold: number;
|
|
490
|
+
/**
|
|
491
|
+
* Minimum number of DISTINCT hosts represented in the window to declare
|
|
492
|
+
* a suspect outage (`M`). Guards against one flaky host (a site that is
|
|
493
|
+
* genuinely retrying/failing on its own) looking like a network-wide
|
|
494
|
+
* event — a real local-network blip surfaces across unrelated hosts at
|
|
495
|
+
* once.
|
|
496
|
+
*/
|
|
497
|
+
readonly hostThreshold: number;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* One observed error, as fed to `NetworkOutageDetector.record`.
|
|
501
|
+
*/
|
|
502
|
+
export interface NetworkErrorRecord {
|
|
503
|
+
readonly kind: ErrorKind;
|
|
504
|
+
readonly host: string;
|
|
505
|
+
/**
|
|
506
|
+
* Epoch ms this error was observed. Caller-supplied — the detector never
|
|
507
|
+
* calls `Date.now()` itself, so window-boundary behaviour can be pinned
|
|
508
|
+
* with exact values instead of fake timers. Callers MUST supply
|
|
509
|
+
* non-decreasing values across successive `record()` calls; the window
|
|
510
|
+
* eviction is a simple cutoff against the latest `at` and does not
|
|
511
|
+
* re-sort out-of-order input.
|
|
512
|
+
*/
|
|
513
|
+
readonly at: number;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Emitted by `NetworkOutageDetector.record` the instant the sliding window
|
|
517
|
+
* crosses both thresholds.
|
|
518
|
+
*/
|
|
519
|
+
export interface OutageSuspect {
|
|
520
|
+
/**
|
|
521
|
+
* Backdated to the earliest error still inside the window at trigger
|
|
522
|
+
* time — NOT the trigger instant itself. A sliding-window detector only
|
|
523
|
+
* confirms an outage after `W` seconds and `N` errors have accumulated,
|
|
524
|
+
* so the outage itself started earlier; backdating lets the persisted
|
|
525
|
+
* `network_outages` row (and the failures it retroactively covers) reach
|
|
526
|
+
* back to that earlier point instead of losing everything the detector
|
|
527
|
+
* missed while still accumulating evidence.
|
|
528
|
+
*/
|
|
529
|
+
readonly startedAt: number;
|
|
530
|
+
/** The `at` of the record that tripped the threshold. */
|
|
531
|
+
readonly detectedAt: number;
|
|
532
|
+
/** Window size at trigger time (== `errorThreshold` or more). */
|
|
533
|
+
readonly triggerErrorCount: number;
|
|
534
|
+
/** Distinct host count at trigger time (== `hostThreshold` or more). */
|
|
535
|
+
readonly triggerHostCount: number;
|
|
352
536
|
}
|
|
@@ -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,38 @@ 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;
|
|
72
|
+
/** See {@link CrawlerOptions.dedupeCap}. `null`/omitted disables the feature. */
|
|
73
|
+
dedupeCap: number | null;
|
|
74
|
+
/** See {@link CrawlerOptions.dedupeMapCap}. Omitted falls through to `Crawler`'s own default. */
|
|
75
|
+
dedupeMapCap: number;
|
|
76
|
+
/**
|
|
77
|
+
* See {@link CrawlerOptions.preloadedStickyShapeKeys}. Set internally by
|
|
78
|
+
* the four resuming-session static methods
|
|
79
|
+
* (`append`/`inventory`/`retryFailed`/`resume`), each independently
|
|
80
|
+
* calling `archive.listDedupeCapShapeKeys()`; not part of the public
|
|
81
|
+
* options a caller of those methods passes directly.
|
|
82
|
+
*/
|
|
83
|
+
preloadedStickyShapeKeys: readonly string[];
|
|
51
84
|
}
|
|
52
85
|
/**
|
|
53
86
|
* Callback invoked after the CrawlerOrchestrator instance is fully initialized
|
|
@@ -56,6 +89,20 @@ interface CrawlConfig extends Config {
|
|
|
56
89
|
* @param config - The resolved archive configuration.
|
|
57
90
|
*/
|
|
58
91
|
type CrawlInitializedCallback = (orchestrator: CrawlerOrchestrator, config: Config) => void | Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* The CLI's already-read `--inventory` source list, passed to
|
|
94
|
+
* {@link CrawlerOrchestrator.inventory} instead of a file path — see that
|
|
95
|
+
* method's `source` param for why the path itself never crosses this
|
|
96
|
+
* boundary.
|
|
97
|
+
*/
|
|
98
|
+
interface InventorySource {
|
|
99
|
+
/** Lower-case hex SHA-256 digest of `bytes` (`computeFileSha256(bytes)`). */
|
|
100
|
+
sha256: string;
|
|
101
|
+
/** The exact bytes of the source list file, archived verbatim. */
|
|
102
|
+
bytes: Buffer;
|
|
103
|
+
/** 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`. */
|
|
104
|
+
invalidLineCount: number;
|
|
105
|
+
}
|
|
59
106
|
/**
|
|
60
107
|
* The main entry point for Nitpicker web crawling and archiving.
|
|
61
108
|
*
|
|
@@ -184,22 +231,29 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
|
|
|
184
231
|
* 3. Reject archives with unfinished `pending` URLs — those would inherit
|
|
185
232
|
* the inventory `source` label by mistake. Operator must resume /
|
|
186
233
|
* retry-failed first.
|
|
187
|
-
* 4.
|
|
188
|
-
*
|
|
189
|
-
*
|
|
234
|
+
* 4. If `source` is given, archive its exact bytes under
|
|
235
|
+
* `inventory/<sha256>.txt` (see {@link Archive.saveInventorySourceList}).
|
|
236
|
+
* Done before scope classification so even a run that discards every
|
|
237
|
+
* URL (out of scope or already known) still leaves a recoverable
|
|
238
|
+
* copy of what was fed in.
|
|
239
|
+
* 5. Parse the URL list — the CLI has already warned-and-dropped
|
|
240
|
+
* unparseable-URL lines before calling this method, so every
|
|
241
|
+
* remaining entry parses. Anything outside the archived scope is
|
|
242
|
+
* warned and skipped (inventory is per-server by design).
|
|
243
|
+
* 6. Subtract URLs that already exist in `pages` or `resources` so the
|
|
190
244
|
* second (and N-th) inventory pass is a no-op for known rows — keeps
|
|
191
245
|
* `'inventory-seed'` rows from being silently demoted.
|
|
192
|
-
*
|
|
246
|
+
* 7. Make `<archive>.bak`. Anything thrown beyond this point restores
|
|
193
247
|
* from the backup.
|
|
194
|
-
*
|
|
248
|
+
* 8. HEAD-probe each novel URL. Responses classified as HTML are queued
|
|
195
249
|
* as Crawler seeds (`'inventory-seed'`); everything else is recorded
|
|
196
250
|
* in `resources` directly as `'inventory-seed'` (no browser launch).
|
|
197
|
-
*
|
|
251
|
+
* 9. If any HTML seeds exist, start a Crawler with
|
|
198
252
|
* `inventoryMode = { seedUrls }` so the rendered page and every newly
|
|
199
253
|
* discovered downstream link is labelled correctly. `resume` is fed
|
|
200
254
|
* the existing `scraped` / `resources` sets so links into already-
|
|
201
255
|
* crawled pages stop at the seen-gate without re-rendering.
|
|
202
|
-
*
|
|
256
|
+
* 10. Drop the backup on success; restore it on any throw.
|
|
203
257
|
*
|
|
204
258
|
* Mutually exclusive with `--append` / `--retry-failed` / `--resume` /
|
|
205
259
|
* `--diff` / `--list` / `--list-file` / `--single` / `--output` — the
|
|
@@ -209,20 +263,22 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
|
|
|
209
263
|
* @param inventoryUrls - Pre-read URL list (one URL per element).
|
|
210
264
|
* @param options - Optional config overrides — most callers leave this blank and let the archived config flow through.
|
|
211
265
|
* @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
|
-
* `
|
|
266
|
+
* @param source - The CLI's already-read source list, as `{ sha256, bytes }`.
|
|
267
|
+
* The orchestrator deliberately does NOT receive the file path: the
|
|
268
|
+
* path is privacy-sensitive (leaks user-home / OS structure when
|
|
269
|
+
* archives are shared) and we want it lifted off this boundary so no
|
|
270
|
+
* future log line / breadcrumb / error message inside the orchestrator
|
|
271
|
+
* can accidentally re-leak it. `bytes` is archived verbatim under
|
|
272
|
+
* `inventory/<sha256>.txt` (see {@link Archive.saveInventorySourceList})
|
|
273
|
+
* before scope classification, so a later `--inventory` run against
|
|
274
|
+
* the same list is an audit no-op even when it discards zero new
|
|
275
|
+
* URLs. Pass `null` for programmatic callers that built
|
|
276
|
+
* `inventoryUrls` in-memory; the audit row's `source_file_sha256`
|
|
277
|
+
* column will be `NULL` and no source list is archived.
|
|
222
278
|
* @returns The orchestrator instance after a successful inventory pass.
|
|
223
279
|
* @throws {Error} When `inventoryUrls` is empty, the archive is in list mode, or pending URLs from a previous crawl remain unresolved.
|
|
224
280
|
*/
|
|
225
|
-
static inventory(archivePath: string, inventoryUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback,
|
|
281
|
+
static inventory(archivePath: string, inventoryUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, source?: InventorySource | null): Promise<CrawlerOrchestrator>;
|
|
226
282
|
/**
|
|
227
283
|
* Re-fetch previously-failed pages in an existing `.nitpicker` archive.
|
|
228
284
|
*
|