@nitpicker/crawler 0.8.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
@@ -1,10 +1,27 @@
1
1
  import type { ArchiveAccessor } from './archive-accessor.js';
2
+ import type { JsonLdRow, JsonLdSummary, TagRow, TagsSummary } from './meta/types.js';
2
3
  import type { Anchor, Redirect, Referrer, DB_Anchor, DB_Page, DB_Redirect, DB_Referrer } from './types.js';
4
+ /**
5
+ * Subset of {@link DB_Page} that maps to the flat meta columns derived from
6
+ * beholder's nested Meta. Used by {@link Page.metaFlat} so consumers can
7
+ * iterate every meta column without enumerating each one.
8
+ *
9
+ * Keep in sync with {@link import('./meta/types.js').FlatPageMetaColumns} —
10
+ * one row of `pages` has the same shape.
11
+ */
12
+ declare const FLAT_META_COLUMNS: readonly ["lang", "dir", "charset", "baseHref", "viewport_raw", "themeColor", "applicationName", "author", "generator", "publisher", "title", "description", "keywords", "robots_raw", "robots_noindex", "robots_nofollow", "robots_noarchive", "robots_noimageindex", "googlebot", "canonical", "amphtml", "manifest", "icon_href", "appleTouchIcon_href", "og_type", "og_title", "og_url", "og_site_name", "og_description", "og_image", "og_image_alt", "og_image_width", "og_image_height", "og_locale", "og_article_published_time", "og_article_modified_time", "twitter_card", "twitter_site", "twitter_creator", "twitter_title", "twitter_description", "twitter_image", "fb_app_id", "verification_google", "formatDetection_telephone", "tag_count", "jsonld_count", "tags_providers_csv"];
3
13
  /**
4
14
  * Represents a crawled page stored in the archive.
5
15
  *
6
- * Provides access to the page's metadata (title, status, SEO tags, etc.),
7
- * its relationships (anchors, referrers, redirects), and its HTML snapshot.
16
+ * Provides typed getters for the most-used meta columns (title, canonical,
17
+ * og:*, twitter_card, robots flags, lang), plus {@link metaFlat} as an
18
+ * iterable view over all ~47 flat meta columns and {@link metaExtras} for
19
+ * the JSON catch-all of nested sub-objects not flattened to columns.
20
+ *
21
+ * JSON-LD entries and Wappalyzer tag rows live in dedicated tables and are
22
+ * fetched on demand via {@link jsonLd} / {@link tags} (lazy reads, same
23
+ * pattern as {@link getAnchors}).
24
+ *
8
25
  * Instances are created by {@link ArchiveAccessor.getPages} or
9
26
  * {@link ArchiveAccessor.getPagesWithRefs}.
10
27
  */
@@ -17,13 +34,14 @@ export default class Page {
17
34
  */
18
35
  readonly redirectFrom: Redirect[];
19
36
  /**
20
- * The alternate URL from the `<link rel="alternate">` tag, or null if not present.
37
+ * The canonical URL from `<link rel="canonical">` (absolutised against the
38
+ * page URL at write time), or null if not present.
21
39
  */
22
- get alternate(): string | null;
40
+ get canonical(): string | null;
23
41
  /**
24
- * The canonical URL from the `<link rel="canonical">` tag, or null if not present.
42
+ * The `<meta charset>` value, or null if not present.
25
43
  */
26
- get canonical(): string | null;
44
+ get charset(): string | null;
27
45
  /**
28
46
  * The content length of the HTTP response in bytes, or null if unknown.
29
47
  */
@@ -36,6 +54,12 @@ export default class Page {
36
54
  * The meta description content, or null if not present.
37
55
  */
38
56
  get description(): string | null;
57
+ /**
58
+ * UNIX ms timestamp of the first time this page row was inserted, or
59
+ * null for legacy rows. Survives `resetFailedPages` so the discovery
60
+ * time of a page is preserved across retries.
61
+ */
62
+ get firstCrawledAt(): number | null;
39
63
  /**
40
64
  * Whether this page is on an external domain (outside the crawl scope).
41
65
  */
@@ -49,9 +73,10 @@ export default class Page {
49
73
  */
50
74
  get isTarget(): boolean;
51
75
  /**
52
- * The reason this page was skipped during crawling, or null if it was not skipped.
76
+ * Number of JSON-LD + SpeculationRules entries detected on this page
77
+ * (denormalised aggregate written at scrape time).
53
78
  */
54
- get skipReason(): string | null;
79
+ get jsonldCount(): number | null;
55
80
  /**
56
81
  * The meta keywords content, or null if not present.
57
82
  */
@@ -61,46 +86,81 @@ export default class Page {
61
86
  */
62
87
  get lang(): string | null;
63
88
  /**
64
- * Whether the noarchive robots directive is set.
89
+ * UNIX ms timestamp of the most recent successful scrape for this page,
90
+ * or null for legacy rows / never-scraped pages.
65
91
  */
66
- get noarchive(): boolean;
92
+ get lastCrawledAt(): number | null;
67
93
  /**
68
- * Whether the nofollow robots directive is set.
94
+ * Iterable view over every flat meta column (~47 fields). Returns a frozen
95
+ * record so consumers can pick fields by name without re-enumerating
96
+ * typed getters.
97
+ *
98
+ * Use the typed getters for high-frequency fields (title, canonical, og_*
99
+ * etc.); use `metaFlat` for bulk projection (Sheets row generation,
100
+ * `toJSON`, debug dumps).
69
101
  */
70
- get nofollow(): boolean;
102
+ get metaFlat(): Readonly<Record<(typeof FLAT_META_COLUMNS)[number], string | number | null>>;
71
103
  /**
72
- * Whether the noindex robots directive is set.
104
+ * Parsed `meta_extras` JSON: nested Meta sub-objects (referrer, viewport,
105
+ * httpEquiv, og.image[], twitter.*, apple.*, msapplication.*, geo,
106
+ * citation, link.alternateHreflang[], others.*, etc.) that were not
107
+ * flattened to dedicated columns.
108
+ *
109
+ * Returns an empty object when the column is null or invalid JSON.
73
110
  */
74
- get noindex(): boolean;
111
+ get metaExtras(): Record<string, unknown>;
75
112
  /**
76
- * The Open Graph description (`og:description`), or null if not present.
113
+ * Open Graph description, or null if not present.
77
114
  */
78
115
  get og_description(): string | null;
79
116
  /**
80
- * The Open Graph image URL (`og:image`), or null if not present.
117
+ * Open Graph image URL (first image when og:image is multi-valued;
118
+ * absolutised against the page URL at write time), or null if not present.
81
119
  */
82
120
  get og_image(): string | null;
83
121
  /**
84
- * The Open Graph site name (`og:site_name`), or null if not present.
122
+ * Open Graph site name, or null if not present.
85
123
  */
86
124
  get og_site_name(): string | null;
87
125
  /**
88
- * The Open Graph title (`og:title`), or null if not present.
126
+ * Open Graph title, or null if not present.
89
127
  */
90
128
  get og_title(): string | null;
91
129
  /**
92
- * The Open Graph type (`og:type`), or null if not present.
130
+ * Open Graph type, or null if not present.
93
131
  */
94
132
  get og_type(): string | null;
95
133
  /**
96
- * The Open Graph URL (`og:url`), or null if not present.
134
+ * Open Graph URL (absolutised), or null if not present.
97
135
  */
98
136
  get og_url(): string | null;
99
137
  /**
100
138
  * The parsed HTTP response headers as a key-value record.
101
- * Returns an empty object if headers cannot be parsed.
139
+ * Header values may be arrays for multi-value headers (e.g. `set-cookie`).
140
+ * Returns an empty object if headers are absent or cannot be parsed.
102
141
  */
103
- get responseHeaders(): Record<string, string>;
142
+ get responseHeaders(): Record<string, string | string[] | undefined>;
143
+ /**
144
+ * Whether the robots:noarchive directive is set.
145
+ */
146
+ get robots_noarchive(): boolean;
147
+ /**
148
+ * Whether the robots:nofollow directive is set.
149
+ */
150
+ get robots_nofollow(): boolean;
151
+ /**
152
+ * Whether the robots:noindex directive is set.
153
+ */
154
+ get robots_noindex(): boolean;
155
+ /**
156
+ * Raw `<meta name="robots">` content, or null if not present.
157
+ * Use for diagnostics; specific directive flags live on `robots_*` getters.
158
+ */
159
+ get robots_raw(): string | null;
160
+ /**
161
+ * The reason this page was skipped during crawling, or null if it was not skipped.
162
+ */
163
+ get skipReason(): string | null;
104
164
  /**
105
165
  * The HTTP response status code, or null if the page has not been fetched.
106
166
  */
@@ -109,13 +169,24 @@ export default class Page {
109
169
  * The HTTP response status text (e.g., `"OK"`, `"Not Found"`), or null if not fetched.
110
170
  */
111
171
  get statusText(): string | null;
172
+ /**
173
+ * Number of Wappalyzer tag entries detected on this page (denormalised
174
+ * aggregate written at scrape time).
175
+ */
176
+ get tagCount(): number | null;
177
+ /**
178
+ * Sorted unique Wappalyzer provider names, comma-separated, empty string
179
+ * when no tags. Denormalised aggregate; for the structured form fetch
180
+ * {@link tags} (lazy).
181
+ */
182
+ get tagsProvidersCsv(): string;
112
183
  /**
113
184
  * The page title from the `<title>` element.
114
185
  * Returns an empty string if no title is set.
115
186
  */
116
187
  get title(): string;
117
188
  /**
118
- * The Twitter Card type (`twitter:card`), or null if not present.
189
+ * Twitter Card type (`twitter:card`), or null if not present.
119
190
  */
120
191
  get twitter_card(): string | null;
121
192
  /**
@@ -140,10 +211,25 @@ export default class Page {
140
211
  */
141
212
  getAnchors(): Promise<Anchor[]>;
142
213
  /**
143
- * Reads the HTML snapshot content of this page from the archive.
144
- * @returns The HTML content as a string, or null if no snapshot was saved.
214
+ * Thin wrapper that forwards this page's id to the accessor's
215
+ * BLOB-read path. Lets callers hold a `Page` reference and ask for its
216
+ * HTML without having to thread the page id through.
217
+ * @returns The HTML content, or `null` if no snapshot was saved.
218
+ * @see {@link ArchiveAccessor.getHtmlOfPage} for the resolution rules.
219
+ * @example
220
+ * for (const page of await archive.getPages()) {
221
+ * const html = await page.getHtml();
222
+ * if (html !== null) processBody(html);
223
+ * }
145
224
  */
146
225
  getHtml(): Promise<string | null>;
226
+ /**
227
+ * Retrieves the JSON-LD entries for this page from `page_jsonld`.
228
+ * Lazy — runs a single SELECT per call. Returns entries in insertion
229
+ * order (matches the scraper's traversal order).
230
+ * @returns Ordered JSON-LD / SpeculationRules rows.
231
+ */
232
+ getJsonLd(): Promise<readonly JsonLdRow[]>;
147
233
  /**
148
234
  * Retrieves the referrers (incoming links) pointing to this page.
149
235
  * Uses pre-loaded data if available, otherwise queries the database.
@@ -156,6 +242,12 @@ export default class Page {
156
242
  * @returns An array of {@link Referrer} objects.
157
243
  */
158
244
  getRequests(): Promise<Referrer[]>;
245
+ /**
246
+ * Retrieves the Wappalyzer tag rows for this page from `page_tags`.
247
+ * Lazy — runs a single SELECT per call.
248
+ * @returns Ordered tag rows.
249
+ */
250
+ getTags(): Promise<readonly TagRow[]>;
159
251
  /**
160
252
  * Checks whether this page is an internal HTML page (not external and has `text/html` content type).
161
253
  * @returns `true` if this is an internal HTML page, `false` otherwise.
@@ -167,42 +259,88 @@ export default class Page {
167
259
  */
168
260
  isPage(): boolean;
169
261
  /**
170
- * Serializes the page data to a plain JSON object,
171
- * including resolved anchors and referrers.
262
+ * Serializes the page data to a plain JSON object including the full flat
263
+ * meta column set, the `meta_extras` catch-all, and **summaries** of the
264
+ * JSON-LD and tag rows.
265
+ *
266
+ * Summaries (not raw entries) are inlined so a Page detail payload stays
267
+ * token-bounded for MCP / LLM consumers — the full `raw` JSON-LD payload
268
+ * is fetched separately via `getJsonLd()` / the dedicated CLI/MCP
269
+ * endpoints.
270
+ *
271
+ * Anchors and referrers are still resolved eagerly because consumers
272
+ * (Sheets `eachPage`, viewer detail) depend on having them inline.
172
273
  * @returns A plain object containing all page metadata and relationships.
173
274
  */
174
275
  toJSON(): Promise<{
276
+ metaExtras: Record<string, unknown>;
277
+ jsonLd: JsonLdSummary;
278
+ tags: TagsSummary;
279
+ redirectFrom: Redirect[];
280
+ isPage: boolean;
281
+ isInternalPage: boolean;
282
+ getAnchors: Anchor[];
283
+ getReferrers: Referrer[];
284
+ dir: string | number | null;
285
+ lang: string | number | null;
286
+ charset: string | number | null;
287
+ baseHref: string | number | null;
288
+ viewport_raw: string | number | null;
289
+ themeColor: string | number | null;
290
+ applicationName: string | number | null;
291
+ author: string | number | null;
292
+ generator: string | number | null;
293
+ publisher: string | number | null;
294
+ title: string | number | null;
295
+ description: string | number | null;
296
+ keywords: string | number | null;
297
+ robots_raw: string | number | null;
298
+ robots_noindex: string | number | null;
299
+ robots_nofollow: string | number | null;
300
+ robots_noarchive: string | number | null;
301
+ robots_noimageindex: string | number | null;
302
+ googlebot: string | number | null;
303
+ canonical: string | number | null;
304
+ amphtml: string | number | null;
305
+ manifest: string | number | null;
306
+ icon_href: string | number | null;
307
+ appleTouchIcon_href: string | number | null;
308
+ og_type: string | number | null;
309
+ og_title: string | number | null;
310
+ og_url: string | number | null;
311
+ og_site_name: string | number | null;
312
+ og_description: string | number | null;
313
+ og_image: string | number | null;
314
+ og_image_alt: string | number | null;
315
+ og_image_width: string | number | null;
316
+ og_image_height: string | number | null;
317
+ og_locale: string | number | null;
318
+ og_article_published_time: string | number | null;
319
+ og_article_modified_time: string | number | null;
320
+ twitter_card: string | number | null;
321
+ twitter_site: string | number | null;
322
+ twitter_creator: string | number | null;
323
+ twitter_title: string | number | null;
324
+ twitter_description: string | number | null;
325
+ twitter_image: string | number | null;
326
+ fb_app_id: string | number | null;
327
+ verification_google: string | number | null;
328
+ formatDetection_telephone: string | number | null;
329
+ tag_count: string | number | null;
330
+ jsonld_count: string | number | null;
331
+ tags_providers_csv: string | number | null;
175
332
  url: string;
176
- title: string;
177
333
  status: number | null;
178
334
  statusText: string | null;
179
335
  contentType: string | null;
180
336
  contentLength: number | null;
181
- responseHeaders: Record<string, string>;
337
+ responseHeaders: Record<string, string | string[] | undefined>;
182
338
  isExternal: boolean;
183
339
  isSkipped: boolean;
184
340
  skipReason: string | null;
185
341
  isTarget: boolean;
186
- lang: string | null;
187
- description: string | null;
188
- keywords: string | null;
189
- noindex: boolean;
190
- nofollow: boolean;
191
- noarchive: boolean;
192
- canonical: string | null;
193
- alternate: string | null;
194
- twitter_card: string | null;
195
- og_site_name: string | null;
196
- og_url: string | null;
197
- og_title: string | null;
198
- og_description: string | null;
199
- og_type: string | null;
200
- og_image: string | null;
201
- redirectFrom: Redirect[];
202
- isPage: boolean;
203
- isInternalPage: boolean;
204
- getAnchors: Anchor[];
205
- getReferrers: Referrer[];
342
+ firstCrawledAt: number | null;
343
+ lastCrawledAt: number | null;
206
344
  }>;
207
345
  }
208
346
  /**