@nitpicker/crawler 0.9.0 → 0.11.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.
Files changed (92) hide show
  1. package/lib/archive/archive-accessor.d.ts +87 -14
  2. package/lib/archive/archive-accessor.js +162 -36
  3. package/lib/archive/archive.d.ts +147 -24
  4. package/lib/archive/archive.js +252 -86
  5. package/lib/archive/database.d.ts +209 -25
  6. package/lib/archive/database.js +928 -108
  7. package/lib/archive/filesystem/peek-tar-top-dir.d.ts +28 -0
  8. package/lib/archive/filesystem/peek-tar-top-dir.js +65 -0
  9. package/lib/archive/init-schema.d.ts +53 -2
  10. package/lib/archive/init-schema.js +247 -15
  11. package/lib/archive/meta/assert-compatible-version.d.ts +39 -0
  12. package/lib/archive/meta/assert-compatible-version.js +72 -0
  13. package/lib/archive/meta/classify-jsonld-type.d.ts +23 -0
  14. package/lib/archive/meta/classify-jsonld-type.js +43 -0
  15. package/lib/archive/meta/compare-semver.d.ts +23 -0
  16. package/lib/archive/meta/compare-semver.js +51 -0
  17. package/lib/archive/meta/compute-page-denormalized.d.ts +21 -0
  18. package/lib/archive/meta/compute-page-denormalized.js +35 -0
  19. package/lib/archive/meta/derive-flat-from-meta.d.ts +35 -0
  20. package/lib/archive/meta/derive-flat-from-meta.js +158 -0
  21. package/lib/archive/meta/derive-meta-extras.d.ts +20 -0
  22. package/lib/archive/meta/derive-meta-extras.js +23 -0
  23. package/lib/archive/meta/extract-tags-for-archive.d.ts +18 -0
  24. package/lib/archive/meta/extract-tags-for-archive.js +36 -0
  25. package/lib/archive/meta/summarize-jsonld.d.ts +17 -0
  26. package/lib/archive/meta/summarize-jsonld.js +29 -0
  27. package/lib/archive/meta/summarize-tags.d.ts +16 -0
  28. package/lib/archive/meta/summarize-tags.js +33 -0
  29. package/lib/archive/meta/types.d.ts +207 -0
  30. package/lib/archive/meta/types.js +33 -0
  31. package/lib/archive/migrate-crawl-errors.d.ts +20 -0
  32. package/lib/archive/migrate-crawl-errors.js +38 -0
  33. package/lib/archive/migrate-html-blob-tables.d.ts +24 -0
  34. package/lib/archive/migrate-html-blob-tables.js +53 -0
  35. package/lib/archive/migrate-page-errors.d.ts +16 -0
  36. package/lib/archive/migrate-page-errors.js +35 -0
  37. package/lib/archive/migrate-pages-resources-source.d.ts +16 -0
  38. package/lib/archive/migrate-pages-resources-source.js +46 -0
  39. package/lib/archive/page.d.ts +187 -49
  40. package/lib/archive/page.js +258 -63
  41. package/lib/archive/peek-archive-lock.d.ts +40 -0
  42. package/lib/archive/peek-archive-lock.js +62 -0
  43. package/lib/archive/resolve-redirect-chain.d.ts +33 -0
  44. package/lib/archive/resolve-redirect-chain.js +27 -0
  45. package/lib/archive/types.d.ts +135 -26
  46. package/lib/crawler/close-browser-safely.d.ts +64 -0
  47. package/lib/crawler/close-browser-safely.js +73 -0
  48. package/lib/crawler/crawler.d.ts +4 -1
  49. package/lib/crawler/crawler.js +290 -32
  50. package/lib/crawler/create-change-phase-handler.d.ts +54 -0
  51. package/lib/crawler/create-change-phase-handler.js +44 -0
  52. package/lib/crawler/derive-page-source.d.ts +23 -0
  53. package/lib/crawler/derive-page-source.js +28 -0
  54. package/lib/crawler/derive-resource-source.d.ts +23 -0
  55. package/lib/crawler/derive-resource-source.js +26 -0
  56. package/lib/crawler/drain-phase-errors.d.ts +48 -0
  57. package/lib/crawler/drain-phase-errors.js +35 -0
  58. package/lib/crawler/fetch-destination.js +38 -2
  59. package/lib/crawler/format-crawl-progress.d.ts +12 -3
  60. package/lib/crawler/format-crawl-progress.js +14 -6
  61. package/lib/crawler/handle-browser-close.d.ts +29 -0
  62. package/lib/crawler/handle-browser-close.js +28 -0
  63. package/lib/crawler/is-html-content-type.d.ts +17 -0
  64. package/lib/crawler/is-html-content-type.js +19 -0
  65. package/lib/crawler/is-likely-html-url.d.ts +22 -0
  66. package/lib/crawler/is-likely-html-url.js +65 -0
  67. package/lib/crawler/kill-process-tree.d.ts +94 -0
  68. package/lib/crawler/kill-process-tree.js +178 -0
  69. package/lib/crawler/link-list.js +2 -1
  70. package/lib/crawler/link-to-page-data.d.ts +13 -5
  71. package/lib/crawler/link-to-page-data.js +26 -5
  72. package/lib/crawler/log-undrained-phase-errors.d.ts +37 -0
  73. package/lib/crawler/log-undrained-phase-errors.js +34 -0
  74. package/lib/crawler/normalize-content-type.d.ts +14 -0
  75. package/lib/crawler/normalize-content-type.js +20 -0
  76. package/lib/crawler/partition-urls-by-html.d.ts +16 -0
  77. package/lib/crawler/partition-urls-by-html.js +23 -0
  78. package/lib/crawler/redirect-dest-key.d.ts +19 -0
  79. package/lib/crawler/redirect-dest-key.js +27 -0
  80. package/lib/crawler/resource-to-page-data.d.ts +28 -0
  81. package/lib/crawler/resource-to-page-data.js +59 -0
  82. package/lib/crawler/types.d.ts +122 -1
  83. package/lib/crawler-orchestrator.d.ts +93 -1
  84. package/lib/crawler-orchestrator.js +389 -12
  85. package/lib/crawler.d.ts +5 -0
  86. package/lib/crawler.js +3 -0
  87. package/lib/resource-row-to-lookup-result.d.ts +13 -0
  88. package/lib/resource-row-to-lookup-result.js +20 -0
  89. package/lib/types.d.ts +11 -1
  90. package/lib/utils/object/parse-response-headers.d.ts +12 -0
  91. package/lib/utils/object/parse-response-headers.js +26 -0
  92. package/package.json +4 -4
@@ -46,6 +46,28 @@ export interface Config extends Required<Pick<ParseURLOptions, 'disableQueries'>
46
46
  /** Whether robots.txt restrictions were ignored during crawling. */
47
47
  ignoreRobots: boolean;
48
48
  }
49
+ /**
50
+ * Provenance of a page or resource row — which crawler channel originally
51
+ * inserted it. Stored as `pages.source` / `resources.source` in the
52
+ * SQLite schema (NOT NULL DEFAULT `'crawled'`).
53
+ *
54
+ * - `'crawled'` — discovered via the recursive crawl rooted at `info.roots`.
55
+ * Default for pre-`--inventory` archives after the
56
+ * `migratePagesResourcesSource` runtime migration.
57
+ * - `'inventory-seed'` — supplied directly by a `crawl --inventory` URL
58
+ * list. For pages this is the HTML URL that was rendered; for resources
59
+ * this is a non-HTML URL handed in by the list (HEAD-fetched without
60
+ * rendering).
61
+ * - `'inventory-discovered'` — found by following links from an
62
+ * `inventory-seed` page, OR (for resources) loaded by puppeteer while
63
+ * rendering one of those pages.
64
+ *
65
+ * Used by the viewer as a badge and to indicate why a row was added.
66
+ * Isolation queries (`listIsolatedPages` / `listUnusedResources`) judge
67
+ * orphans by `referrer = 0`, NOT by this value — `source` only labels
68
+ * the row.
69
+ */
70
+ export type PageSource = 'crawled' | 'inventory-seed' | 'inventory-discovered';
49
71
  /**
50
72
  * Filter type for querying pages from the database.
51
73
  *
@@ -59,7 +81,13 @@ export interface Config extends Required<Pick<ParseURLOptions, 'disableQueries'>
59
81
  */
60
82
  export type PageFilter = 'page' | 'page-included-no-target' | 'external-page' | 'internal-page' | 'no-page' | 'external-no-page' | 'internal-no-page';
61
83
  /**
62
- * Raw database row representing a crawled page in the `pages` table.
84
+ * Raw database row representing a crawled page in the `pages` table (v2 schema).
85
+ *
86
+ * Maps 1:1 to the columns defined by `archive/init-schema.ts`. Most meta
87
+ * fields are derived from beholder 3.0.0's nested {@link import('@d-zero/beholder').Meta}
88
+ * via `archive/meta/derive-flat-from-meta.ts` and are stored as plain
89
+ * scalars for SQL-level filter / projection. The catch-all `meta_extras`
90
+ * JSON column preserves nested sub-objects not flattened above.
63
91
  */
64
92
  export interface DB_Page {
65
93
  /** Auto-incremented primary key. */
@@ -84,48 +112,118 @@ export interface DB_Page {
84
112
  contentLength: number | null;
85
113
  /** JSON-serialized HTTP response headers. */
86
114
  responseHeaders: string;
87
- /** The `lang` attribute value from the HTML element, or null if not present. */
115
+ /** The `lang` attribute value from the HTML element, or null. */
88
116
  lang: string | null;
89
- /** The page title from the `<title>` element, or null if not present. */
117
+ /** The `dir` attribute value, or null. */
118
+ dir: string | null;
119
+ /** The `<meta charset>` value, or null. */
120
+ charset: string | null;
121
+ /** Absolutised `<base href>`, or null. */
122
+ baseHref: string | null;
123
+ /** The raw `<meta name="viewport">` content, or null. */
124
+ viewport_raw: string | null;
125
+ /** The primary `<meta name="theme-color">` (no media), or null. */
126
+ themeColor: string | null;
127
+ /** `<meta name="application-name">`, or null. */
128
+ applicationName: string | null;
129
+ /** `<meta name="author">`, or null. */
130
+ author: string | null;
131
+ /** `<meta name="generator">`, or null. */
132
+ generator: string | null;
133
+ /** `<meta name="publisher">`, or null. */
134
+ publisher: string | null;
135
+ /** The page title from the `<title>` element, or null. */
90
136
  title: string | null;
91
- /** The meta description content, or null if not present. */
137
+ /** The meta description content, or null. */
92
138
  description: string | null;
93
- /** The meta keywords content, or null if not present. */
139
+ /** The meta keywords content, or null. */
94
140
  keywords: string | null;
95
- /** Whether the noindex robots directive is set (SQLite INTEGER 0/1). */
96
- noindex: number | null;
97
- /** Whether the nofollow robots directive is set (SQLite INTEGER 0/1). */
98
- nofollow: number | null;
99
- /** Whether the noarchive robots directive is set (SQLite INTEGER 0/1). */
100
- noarchive: number | null;
101
- /** The canonical URL from `<link rel="canonical">`, or null if not present. */
141
+ /** The raw `<meta name="robots">` content, or null. */
142
+ robots_raw: string | null;
143
+ /** Whether the noindex directive is set (SQLite INTEGER 0/1). */
144
+ robots_noindex: number | null;
145
+ /** Whether the nofollow directive is set (SQLite INTEGER 0/1). */
146
+ robots_nofollow: number | null;
147
+ /** Whether the noarchive directive is set (SQLite INTEGER 0/1). */
148
+ robots_noarchive: number | null;
149
+ /** Whether the noimageindex directive is set (SQLite INTEGER 0/1). */
150
+ robots_noimageindex: number | null;
151
+ /** `<meta name="googlebot">` content, or null. */
152
+ googlebot: string | null;
153
+ /** Absolutised `<link rel="canonical">` href, or null. */
102
154
  canonical: string | null;
103
- /** The alternate URL from `<link rel="alternate">`, or null if not present. */
104
- alternate: string | null;
105
- /** The Open Graph type (`og:type`), or null if not present. */
155
+ /** Absolutised `<link rel="amphtml">` href, or null. */
156
+ amphtml: string | null;
157
+ /** Absolutised `<link rel="manifest">` href, or null. */
158
+ manifest: string | null;
159
+ /** Absolutised `<link rel="icon">` href, or null. */
160
+ icon_href: string | null;
161
+ /** Absolutised `<link rel="apple-touch-icon">` href, or null. */
162
+ appleTouchIcon_href: string | null;
163
+ /** og:type, or null. */
106
164
  og_type: string | null;
107
- /** The Open Graph title (`og:title`), or null if not present. */
165
+ /** og:title, or null. */
108
166
  og_title: string | null;
109
- /** The Open Graph site name (`og:site_name`), or null if not present. */
167
+ /** Absolutised og:url, or null. */
168
+ og_url: string | null;
169
+ /** og:site_name, or null. */
110
170
  og_site_name: string | null;
111
- /** The Open Graph description (`og:description`), or null if not present. */
171
+ /** og:description, or null. */
112
172
  og_description: string | null;
113
- /** The Open Graph URL (`og:url`), or null if not present. */
114
- og_url: string | null;
115
- /** The Open Graph image URL (`og:image`), or null if not present. */
173
+ /** Absolutised og:image (first if multiple), or null. */
116
174
  og_image: string | null;
117
- /** The Twitter Card type (`twitter:card`), or null if not present. */
175
+ /** og:image:alt, or null. */
176
+ og_image_alt: string | null;
177
+ /** og:image:width as a string (per spec), or null. */
178
+ og_image_width: string | null;
179
+ /** og:image:height as a string (per spec), or null. */
180
+ og_image_height: string | null;
181
+ /** og:locale, or null. */
182
+ og_locale: string | null;
183
+ /** og:article:published_time, or null. */
184
+ og_article_published_time: string | null;
185
+ /** og:article:modified_time, or null. */
186
+ og_article_modified_time: string | null;
187
+ /** twitter:card, or null. */
118
188
  twitter_card: string | null;
189
+ /** twitter:site, or null. */
190
+ twitter_site: string | null;
191
+ /** twitter:creator, or null. */
192
+ twitter_creator: string | null;
193
+ /** twitter:title, or null. */
194
+ twitter_title: string | null;
195
+ /** twitter:description, or null. */
196
+ twitter_description: string | null;
197
+ /** Absolutised twitter:image (or twitter:image:src fallback), or null. */
198
+ twitter_image: string | null;
199
+ /** Facebook app id (`fb:app_id`), or null. */
200
+ fb_app_id: string | null;
201
+ /** Google site verification token, or null. */
202
+ verification_google: string | null;
203
+ /** `format-detection` telephone (SQLite INTEGER 0/1), or null. */
204
+ formatDetection_telephone: number | null;
205
+ /** First time this page row was inserted (UNIX ms), or null on legacy rows. */
206
+ firstCrawledAt: number | null;
207
+ /** Last successful re-scrape time (UNIX ms), or null on legacy rows. */
208
+ lastCrawledAt: number | null;
209
+ /** Number of `page_tags` rows belonging to this page. */
210
+ tag_count: number | null;
211
+ /** `meta.jsonLd.length + meta.speculationRules.length` at scrape time. */
212
+ jsonld_count: number | null;
213
+ /** Sorted unique provider names, comma-separated (empty string when none). */
214
+ tags_providers_csv: string | null;
215
+ /** JSON-serialised nested Meta sub-objects not flattened above. */
216
+ meta_extras: string | null;
119
217
  /** JSON-serialized network logs captured during scraping, or null if not collected. */
120
218
  networkLogs: string | null;
121
- /** Relative file path to the saved HTML snapshot, or null if not saved. */
122
- html: string | null;
123
219
  /** Whether the page was skipped during crawling (1) or processed normally (0). */
124
220
  isSkipped: 0 | 1;
125
221
  /** The reason the page was skipped, or null if it was not skipped. */
126
222
  skipReason: string | null;
127
223
  /** The natural URL sort order index, or null if not yet assigned. */
128
224
  order: number | null;
225
+ /** Provenance of the row — see {@link PageSource}. */
226
+ source: PageSource;
129
227
  }
130
228
  /**
131
229
  * Raw database row representing a redirect relationship.
@@ -283,13 +381,24 @@ export interface DB_Resource {
283
381
  cdn: string | 0;
284
382
  /** JSON-serialized HTTP response headers, or null if not available. */
285
383
  responseHeaders: string | null;
384
+ /** Provenance of the row — see {@link PageSource}. */
385
+ source: PageSource;
286
386
  }
287
387
  /**
288
388
  * Connection options for the archive's libsql-backed database.
289
389
  */
290
390
  export interface DatabaseOption {
291
- /** The working directory for the database (used for resolving relative paths). */
292
- workingDir: string;
293
391
  /** The absolute file path to the SQLite database file. */
294
392
  filename: string;
393
+ /**
394
+ * When `true`, open the database for read-only inspection:
395
+ *
396
+ * - Skip schema migrations (no `ALTER TABLE` / `UPDATE` on the user's file).
397
+ * - Refuse to mkdir/create the parent dir or the db file — fail loudly if
398
+ * either is missing instead of resurrecting them.
399
+ *
400
+ * Used by the viewer / MCP server when attaching to an in-progress crawl's
401
+ * tmpDir, where any write would race the live crawler.
402
+ */
403
+ readOnly?: boolean;
295
404
  }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Minimal structural subset of a Puppeteer `Browser` required to close it safely.
3
+ *
4
+ * Declared structurally (rather than importing Puppeteer's `Browser`) so the
5
+ * function stays unit-testable with a lightweight stub and free of a Puppeteer
6
+ * import.
7
+ */
8
+ export interface ClosableBrowser {
9
+ /** Gracefully closes the browser and all of its pages over the CDP connection. */
10
+ close(): Promise<void>;
11
+ /**
12
+ * Returns the underlying Chromium child process, or `null` when the browser
13
+ * was connected to (rather than launched) and therefore owns no process.
14
+ */
15
+ process(): {
16
+ /** PID of the Chromium parent process, or `undefined` before spawn settles. */
17
+ readonly pid?: number;
18
+ /** Sends a signal to the process; returns whether it was delivered. */
19
+ kill(signal?: NodeJS.Signals | number): boolean;
20
+ /** Whether a signal has already been successfully sent to the process. */
21
+ readonly killed: boolean;
22
+ } | null;
23
+ }
24
+ /**
25
+ * Dependency overrides for {@link closeBrowserSafely}. Used only by tests
26
+ * to substitute the tree-kill orchestration.
27
+ */
28
+ export interface CloseBrowserSafelyDeps {
29
+ /**
30
+ * Kills a process and every descendant. Defaults to {@link killProcessTree}.
31
+ */
32
+ killTree?: (pid: number, signal: NodeJS.Signals | number) => Promise<void>;
33
+ }
34
+ /**
35
+ * Closes a Puppeteer browser, falling back to a hard tree-kill if the graceful
36
+ * close hangs.
37
+ *
38
+ * WHY: When a page's Chromium session dies mid-scrape (e.g. a viewport change
39
+ * detaches the frame, surfacing `Attempted to use detached Frame` or
40
+ * `Session closed`), the CDP connection can be left wedged. A bare
41
+ * `await browser.close()` then never settles, stalling the `deal()` worker and
42
+ * hanging the whole crawl. Racing the close against a timeout and SIGKILLing
43
+ * the Chromium process tree (parent + renderer/network/zygote children) on
44
+ * expiry guarantees the worker always completes and no orphan subprocesses are
45
+ * left behind.
46
+ *
47
+ * The losing timer is cleared explicitly in `.finally()` so it never keeps the
48
+ * event loop alive after the race settles (a plain `delay()` in `Promise.race`
49
+ * would leak the timer until it fires).
50
+ *
51
+ * The tree-kill happens via {@link killProcessTree}, which enumerates
52
+ * descendants through `ps` (POSIX) or delegates to `taskkill /T /F` (Windows).
53
+ * `childProcess.kill('SIGKILL')` is still invoked on the parent up-front
54
+ * because Node's `ChildProcess.killed` flag governs how Node treats the
55
+ * spawn handle (reaping etc.); without it the parent would linger in Node's
56
+ * process table even after the OS-level kill.
57
+ * @param browser - The browser to close.
58
+ * @param timeoutMs - Milliseconds to wait for a graceful close before force-killing.
59
+ * Defaults to {@link DEFAULT_CLOSE_TIMEOUT_MS}.
60
+ * @param deps - Test-time overrides (default-free for production callers).
61
+ * @returns `true` if the graceful close timed out (and a tree-kill was
62
+ * attempted), `false` if `close()` settled in time.
63
+ */
64
+ export declare function closeBrowserSafely(browser: ClosableBrowser, timeoutMs?: number, deps?: CloseBrowserSafelyDeps): Promise<boolean>;
@@ -0,0 +1,73 @@
1
+ import { crawlerLog } from '../debug.js';
2
+ import { killProcessTree } from './kill-process-tree.js';
3
+ /**
4
+ * Default time to wait for a graceful `browser.close()` before force-killing
5
+ * the underlying Chromium process, in milliseconds.
6
+ */
7
+ const DEFAULT_CLOSE_TIMEOUT_MS = 30 * 1000;
8
+ /**
9
+ * Closes a Puppeteer browser, falling back to a hard tree-kill if the graceful
10
+ * close hangs.
11
+ *
12
+ * WHY: When a page's Chromium session dies mid-scrape (e.g. a viewport change
13
+ * detaches the frame, surfacing `Attempted to use detached Frame` or
14
+ * `Session closed`), the CDP connection can be left wedged. A bare
15
+ * `await browser.close()` then never settles, stalling the `deal()` worker and
16
+ * hanging the whole crawl. Racing the close against a timeout and SIGKILLing
17
+ * the Chromium process tree (parent + renderer/network/zygote children) on
18
+ * expiry guarantees the worker always completes and no orphan subprocesses are
19
+ * left behind.
20
+ *
21
+ * The losing timer is cleared explicitly in `.finally()` so it never keeps the
22
+ * event loop alive after the race settles (a plain `delay()` in `Promise.race`
23
+ * would leak the timer until it fires).
24
+ *
25
+ * The tree-kill happens via {@link killProcessTree}, which enumerates
26
+ * descendants through `ps` (POSIX) or delegates to `taskkill /T /F` (Windows).
27
+ * `childProcess.kill('SIGKILL')` is still invoked on the parent up-front
28
+ * because Node's `ChildProcess.killed` flag governs how Node treats the
29
+ * spawn handle (reaping etc.); without it the parent would linger in Node's
30
+ * process table even after the OS-level kill.
31
+ * @param browser - The browser to close.
32
+ * @param timeoutMs - Milliseconds to wait for a graceful close before force-killing.
33
+ * Defaults to {@link DEFAULT_CLOSE_TIMEOUT_MS}.
34
+ * @param deps - Test-time overrides (default-free for production callers).
35
+ * @returns `true` if the graceful close timed out (and a tree-kill was
36
+ * attempted), `false` if `close()` settled in time.
37
+ */
38
+ export async function closeBrowserSafely(browser, timeoutMs = DEFAULT_CLOSE_TIMEOUT_MS, deps = {}) {
39
+ // Capture the process up-front: after a successful close() puppeteer
40
+ // releases its internal reference and process() returns null, so we would
41
+ // have no handle to tree-kill on timeout.
42
+ const childProcess = browser.process();
43
+ let timeoutHandle;
44
+ const timedOut = await Promise.race([
45
+ browser
46
+ .close()
47
+ .then(() => false)
48
+ .catch(() => false),
49
+ new Promise((resolve) => {
50
+ timeoutHandle = setTimeout(() => resolve(true), timeoutMs);
51
+ }),
52
+ ]).finally(() => {
53
+ if (timeoutHandle) {
54
+ clearTimeout(timeoutHandle);
55
+ }
56
+ });
57
+ if (timedOut && childProcess && !childProcess.killed) {
58
+ // Mark the Node ChildProcess as killed so Node's reaping logic treats
59
+ // it correctly; then walk the OS process tree.
60
+ childProcess.kill('SIGKILL');
61
+ // Capture pid once: ChildProcess.pid is technically `number | undefined`
62
+ // (undefined before spawn settles), and reading it twice across the
63
+ // `await` below would force the second read to re-widen back to
64
+ // `number | undefined` regardless of the typeof guard. Snapshotting
65
+ // makes the type and the runtime value match.
66
+ const pid = childProcess.pid;
67
+ if (typeof pid === 'number') {
68
+ const killTree = deps.killTree ?? ((p, sig) => killProcessTree(p, sig, { log: crawlerLog }));
69
+ await killTree(pid, 'SIGKILL');
70
+ }
71
+ }
72
+ return timedOut;
73
+ }
@@ -53,8 +53,11 @@ export default class Crawler extends EventEmitter<CrawlerEventTypes> {
53
53
  * @param pending - URLs that were pending (not yet scraped) in the previous session.
54
54
  * @param scraped - URLs that were already scraped in the previous session.
55
55
  * @param resources - Resource URLs that were already captured in the previous session.
56
+ * @param pagesScrapedOffset - Number of HTML pages already rendered in previous
57
+ * sessions, used to seed the session-spanning progress counter. Defaults to 0
58
+ * for callers that don't need cross-session accuracy in the progress display.
56
59
  */
57
- resume(pending: string[], scraped: string[], resources: string[]): void;
60
+ resume(pending: string[], scraped: string[], resources: string[], pagesScrapedOffset?: number): void;
58
61
  /**
59
62
  * Start crawling from one or more root URLs.
60
63
  *