@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
|
@@ -68,6 +68,27 @@ export declare class ArchiveAccessor extends EventEmitter<DatabaseEvent> {
|
|
|
68
68
|
* @returns An array of anchor records found on the page.
|
|
69
69
|
*/
|
|
70
70
|
getAnchorsOnPage(pageId: number): Promise<any[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Retrieves the audios within the given page's detected main content
|
|
73
|
+
* region, from `page_main_content_audios`.
|
|
74
|
+
* @param pageId - The database id of the page.
|
|
75
|
+
* @returns Ordered audio rows.
|
|
76
|
+
*/
|
|
77
|
+
getAudiosOfPage(pageId: number): Promise<import("./meta/types.js").MainContentAudioRow[]>;
|
|
78
|
+
/**
|
|
79
|
+
* Retrieves the button-like elements within the given page's detected
|
|
80
|
+
* main content region, from `page_main_content_buttons`.
|
|
81
|
+
* @param pageId - The database id of the page.
|
|
82
|
+
* @returns Ordered button rows.
|
|
83
|
+
*/
|
|
84
|
+
getButtonsOfPage(pageId: number): Promise<import("./meta/types.js").MainContentButtonRow[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Retrieves the canvases within the given page's detected main content
|
|
87
|
+
* region, from `page_main_content_canvases`.
|
|
88
|
+
* @param pageId - The database id of the page.
|
|
89
|
+
* @returns Ordered canvas rows.
|
|
90
|
+
*/
|
|
91
|
+
getCanvasesOfPage(pageId: number): Promise<import("./meta/types.js").MainContentCanvasRow[]>;
|
|
71
92
|
/**
|
|
72
93
|
* Retrieves the crawl configuration stored in the archive database.
|
|
73
94
|
* @returns The parsed {@link Config} object.
|
|
@@ -87,6 +108,13 @@ export declare class ArchiveAccessor extends EventEmitter<DatabaseEvent> {
|
|
|
87
108
|
* @returns The file contents as a string.
|
|
88
109
|
*/
|
|
89
110
|
getData(name: string, format?: 'txt' | 'html'): Promise<string>;
|
|
111
|
+
/**
|
|
112
|
+
* Retrieves the headings within the given page's detected main content
|
|
113
|
+
* region, from `page_main_content_headings`.
|
|
114
|
+
* @param pageId - The database id of the page.
|
|
115
|
+
* @returns Ordered heading rows.
|
|
116
|
+
*/
|
|
117
|
+
getHeadingsOfPage(pageId: number): Promise<import("./meta/types.js").MainContentHeadingRow[]>;
|
|
90
118
|
/**
|
|
91
119
|
* Reads the HTML snapshot of a page from the archive.
|
|
92
120
|
*
|
|
@@ -117,6 +145,13 @@ export declare class ArchiveAccessor extends EventEmitter<DatabaseEvent> {
|
|
|
117
145
|
* }
|
|
118
146
|
*/
|
|
119
147
|
getHtmlOfPage(pageId: number): Promise<string | null>;
|
|
148
|
+
/**
|
|
149
|
+
* Retrieves the iframes within the given page's detected main content
|
|
150
|
+
* region, from `page_main_content_iframes`.
|
|
151
|
+
* @param pageId - The database id of the page.
|
|
152
|
+
* @returns Ordered iframe rows.
|
|
153
|
+
*/
|
|
154
|
+
getIframesOfPage(pageId: number): Promise<import("./meta/types.js").MainContentIframeRow[]>;
|
|
120
155
|
/**
|
|
121
156
|
* Retrieves the JSON-LD / SpeculationRules entries for the given page,
|
|
122
157
|
* parsed back from the `page_jsonld` table.
|
|
@@ -131,6 +166,20 @@ export declare class ArchiveAccessor extends EventEmitter<DatabaseEvent> {
|
|
|
131
166
|
* @returns The Knex instance connected to the SQLite database.
|
|
132
167
|
*/
|
|
133
168
|
getKnex(): import("knex").Knex<any, any[]>;
|
|
169
|
+
/**
|
|
170
|
+
* Retrieves the images within the given page's detected main content
|
|
171
|
+
* region, from `page_main_content_images`.
|
|
172
|
+
* @param pageId - The database id of the page.
|
|
173
|
+
* @returns Ordered image rows.
|
|
174
|
+
*/
|
|
175
|
+
getMainContentImagesOfPage(pageId: number): Promise<import("./meta/types.js").MainContentImageRow[]>;
|
|
176
|
+
/**
|
|
177
|
+
* Retrieves the tables within the given page's detected main content
|
|
178
|
+
* region, from `page_main_content_tables`.
|
|
179
|
+
* @param pageId - The database id of the page.
|
|
180
|
+
* @returns Ordered table rows.
|
|
181
|
+
*/
|
|
182
|
+
getMainContentTablesOfPage(pageId: number): Promise<import("./meta/types.js").MainContentTableRow[]>;
|
|
134
183
|
/**
|
|
135
184
|
* Retrieves all pages from the archive, optionally filtered by type.
|
|
136
185
|
* Eagerly loads redirect relationships (`redirectFrom`) but does NOT load
|
|
@@ -179,6 +228,13 @@ export declare class ArchiveAccessor extends EventEmitter<DatabaseEvent> {
|
|
|
179
228
|
* @returns Ordered entries with provider, category, externalId, etc.
|
|
180
229
|
*/
|
|
181
230
|
getTagsOfPage(pageId: number): Promise<import("./meta/types.js").TagRow[]>;
|
|
231
|
+
/**
|
|
232
|
+
* Retrieves the videos within the given page's detected main content
|
|
233
|
+
* region, from `page_main_content_videos`.
|
|
234
|
+
* @param pageId - The database id of the page.
|
|
235
|
+
* @returns Ordered video rows.
|
|
236
|
+
*/
|
|
237
|
+
getVideosOfPage(pageId: number): Promise<import("./meta/types.js").MainContentVideoRow[]>;
|
|
182
238
|
/**
|
|
183
239
|
* Stores custom data in the archive under the configured namespace.
|
|
184
240
|
* Requires a namespace to be set on this accessor; throws if namespace is null.
|
|
@@ -128,6 +128,33 @@ export class ArchiveAccessor extends EventEmitter {
|
|
|
128
128
|
const refs = await this.#db.getAnchorsOnPage(pageId);
|
|
129
129
|
return refs;
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Retrieves the audios within the given page's detected main content
|
|
133
|
+
* region, from `page_main_content_audios`.
|
|
134
|
+
* @param pageId - The database id of the page.
|
|
135
|
+
* @returns Ordered audio rows.
|
|
136
|
+
*/
|
|
137
|
+
async getAudiosOfPage(pageId) {
|
|
138
|
+
return this.#db.getAudiosOfPage(pageId);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Retrieves the button-like elements within the given page's detected
|
|
142
|
+
* main content region, from `page_main_content_buttons`.
|
|
143
|
+
* @param pageId - The database id of the page.
|
|
144
|
+
* @returns Ordered button rows.
|
|
145
|
+
*/
|
|
146
|
+
async getButtonsOfPage(pageId) {
|
|
147
|
+
return this.#db.getButtonsOfPage(pageId);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Retrieves the canvases within the given page's detected main content
|
|
151
|
+
* region, from `page_main_content_canvases`.
|
|
152
|
+
* @param pageId - The database id of the page.
|
|
153
|
+
* @returns Ordered canvas rows.
|
|
154
|
+
*/
|
|
155
|
+
async getCanvasesOfPage(pageId) {
|
|
156
|
+
return this.#db.getCanvasesOfPage(pageId);
|
|
157
|
+
}
|
|
131
158
|
/**
|
|
132
159
|
* Retrieves the crawl configuration stored in the archive database.
|
|
133
160
|
* @returns The parsed {@link Config} object.
|
|
@@ -143,6 +170,15 @@ export class ArchiveAccessor extends EventEmitter {
|
|
|
143
170
|
}
|
|
144
171
|
return await readText(filePath);
|
|
145
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Retrieves the headings within the given page's detected main content
|
|
175
|
+
* region, from `page_main_content_headings`.
|
|
176
|
+
* @param pageId - The database id of the page.
|
|
177
|
+
* @returns Ordered heading rows.
|
|
178
|
+
*/
|
|
179
|
+
async getHeadingsOfPage(pageId) {
|
|
180
|
+
return this.#db.getHeadingsOfPage(pageId);
|
|
181
|
+
}
|
|
146
182
|
/**
|
|
147
183
|
* Reads the HTML snapshot of a page from the archive.
|
|
148
184
|
*
|
|
@@ -179,6 +215,15 @@ export class ArchiveAccessor extends EventEmitter {
|
|
|
179
215
|
}
|
|
180
216
|
return html;
|
|
181
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Retrieves the iframes within the given page's detected main content
|
|
220
|
+
* region, from `page_main_content_iframes`.
|
|
221
|
+
* @param pageId - The database id of the page.
|
|
222
|
+
* @returns Ordered iframe rows.
|
|
223
|
+
*/
|
|
224
|
+
async getIframesOfPage(pageId) {
|
|
225
|
+
return this.#db.getIframesOfPage(pageId);
|
|
226
|
+
}
|
|
182
227
|
/**
|
|
183
228
|
* Retrieves the JSON-LD / SpeculationRules entries for the given page,
|
|
184
229
|
* parsed back from the `page_jsonld` table.
|
|
@@ -197,6 +242,24 @@ export class ArchiveAccessor extends EventEmitter {
|
|
|
197
242
|
getKnex() {
|
|
198
243
|
return this.#db.getKnex();
|
|
199
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Retrieves the images within the given page's detected main content
|
|
247
|
+
* region, from `page_main_content_images`.
|
|
248
|
+
* @param pageId - The database id of the page.
|
|
249
|
+
* @returns Ordered image rows.
|
|
250
|
+
*/
|
|
251
|
+
async getMainContentImagesOfPage(pageId) {
|
|
252
|
+
return this.#db.getMainContentImagesOfPage(pageId);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Retrieves the tables within the given page's detected main content
|
|
256
|
+
* region, from `page_main_content_tables`.
|
|
257
|
+
* @param pageId - The database id of the page.
|
|
258
|
+
* @returns Ordered table rows.
|
|
259
|
+
*/
|
|
260
|
+
async getMainContentTablesOfPage(pageId) {
|
|
261
|
+
return this.#db.getMainContentTablesOfPage(pageId);
|
|
262
|
+
}
|
|
200
263
|
/**
|
|
201
264
|
* Retrieves all pages from the archive, optionally filtered by type.
|
|
202
265
|
* Eagerly loads redirect relationships (`redirectFrom`) but does NOT load
|
|
@@ -289,6 +352,15 @@ export class ArchiveAccessor extends EventEmitter {
|
|
|
289
352
|
async getTagsOfPage(pageId) {
|
|
290
353
|
return this.#db.getTagsOfPage(pageId);
|
|
291
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Retrieves the videos within the given page's detected main content
|
|
357
|
+
* region, from `page_main_content_videos`.
|
|
358
|
+
* @param pageId - The database id of the page.
|
|
359
|
+
* @returns Ordered video rows.
|
|
360
|
+
*/
|
|
361
|
+
async getVideosOfPage(pageId) {
|
|
362
|
+
return this.#db.getVideosOfPage(pageId);
|
|
363
|
+
}
|
|
292
364
|
/**
|
|
293
365
|
* Stores custom data in the archive under the configured namespace.
|
|
294
366
|
* Requires a namespace to be set on this accessor; throws if namespace is null.
|
package/lib/archive/archive.d.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TemplateClusterReason } from './db-ops/analysis/types.js';
|
|
2
|
+
import type { Config, InsertNetworkOutageParams, InventoryRunMeta, PageSource } from './types.js';
|
|
3
|
+
import type { OutageWindow } from '../is-within-outage-window.js';
|
|
2
4
|
import type { PageData, CrawlerError, Resource } from '../utils/types/types.js';
|
|
5
|
+
import type { ConsoleLogEntry } from '@d-zero/beholder';
|
|
3
6
|
import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
4
7
|
import { ArchiveAccessor } from './archive-accessor.js';
|
|
5
8
|
/**
|
|
6
9
|
* Main archive class for creating, opening, resuming, and writing Nitpicker
|
|
7
10
|
* archive files (`.nitpicker`).
|
|
8
11
|
*
|
|
9
|
-
* An Archive wraps a
|
|
10
|
-
*
|
|
11
|
-
* tar
|
|
12
|
+
* An Archive wraps a SQLite database into a tar archive. HTML bodies live
|
|
13
|
+
* inside the same DB as zstd-compressed BLOBs (see #75), so `db.sqlite` is
|
|
14
|
+
* normally the tar's only entry — but it is not the only entry the format
|
|
15
|
+
* allows: {@link ArchiveAccessor.setData} (namespace-scoped analyze output)
|
|
16
|
+
* and {@link Archive.saveInventorySourceList} (a saved `--inventory`
|
|
17
|
+
* source list) add plain files alongside it. It extends
|
|
12
18
|
* {@link ArchiveAccessor} to provide read access to stored data.
|
|
13
19
|
*
|
|
14
20
|
* Use the static factory methods ({@link Archive.create}, {@link Archive.open},
|
|
@@ -66,6 +72,14 @@ export default class Archive extends ArchiveAccessor {
|
|
|
66
72
|
* @param isExternal - Whether the URL is external. Defaults to `false`.
|
|
67
73
|
*/
|
|
68
74
|
addPageError(url: string, phase: string, message: string, isExternal?: boolean): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Closes an outage row by stamping `ended_at` — a no-op if already closed.
|
|
77
|
+
*
|
|
78
|
+
* Thin facade over {@link Database.closeNetworkOutage}.
|
|
79
|
+
* @param id - The `network_outages.id` to close.
|
|
80
|
+
* @param endedAt - Epoch ms the outage is considered to have ended.
|
|
81
|
+
*/
|
|
82
|
+
closeNetworkOutage(id: number, endedAt: number): Promise<void>;
|
|
69
83
|
/**
|
|
70
84
|
* Retrieves the current crawling state, including lists of scraped and pending URLs.
|
|
71
85
|
* @returns An object with `scraped` and `pending` URL arrays.
|
|
@@ -151,6 +165,15 @@ export default class Archive extends ArchiveAccessor {
|
|
|
151
165
|
* @param urls - HTML seed URLs to pre-insert. No-op when empty.
|
|
152
166
|
*/
|
|
153
167
|
insertInventorySeeds(urls: readonly ExURL[]): Promise<void>;
|
|
168
|
+
/**
|
|
169
|
+
* Appends one open row to the `network_outages` journal.
|
|
170
|
+
*
|
|
171
|
+
* Thin facade over {@link Database.insertNetworkOutage} — see
|
|
172
|
+
* {@link recordInventoryRun}'s docstring for why this indirection exists.
|
|
173
|
+
* @param params - The confirmed-outage fields to record.
|
|
174
|
+
* @returns The autoincremented `id` of the inserted row.
|
|
175
|
+
*/
|
|
176
|
+
insertNetworkOutage(params: InsertNetworkOutageParams): Promise<number>;
|
|
154
177
|
/**
|
|
155
178
|
* Hostnames whose `crawl_errors` history is consistently DNS failures and
|
|
156
179
|
* for which no recent 2xx/3xx page or resource is recorded. Consumed by
|
|
@@ -165,6 +188,13 @@ export default class Archive extends ArchiveAccessor {
|
|
|
165
188
|
* @returns Lower-cased hostnames safe to short-circuit.
|
|
166
189
|
*/
|
|
167
190
|
listDnsBurnedHostCandidates(): Promise<string[]>;
|
|
191
|
+
/**
|
|
192
|
+
* Lists every recorded outage as a resolved {@link OutageWindow}.
|
|
193
|
+
*
|
|
194
|
+
* Thin facade over {@link Database.listNetworkOutages}.
|
|
195
|
+
* @returns Resolved outage windows, or `[]` if none have been recorded.
|
|
196
|
+
*/
|
|
197
|
+
listNetworkOutages(): Promise<OutageWindow[]>;
|
|
168
198
|
/**
|
|
169
199
|
* Appends one row to the `inventory_runs` audit log.
|
|
170
200
|
*
|
|
@@ -206,6 +236,21 @@ export default class Archive extends ArchiveAccessor {
|
|
|
206
236
|
line?: number | null;
|
|
207
237
|
col?: number | null;
|
|
208
238
|
}[]): Promise<void>;
|
|
239
|
+
/**
|
|
240
|
+
* Replaces the archive's DOM-structure template classification
|
|
241
|
+
* (`--templates`) with a fresh SQL-backed set.
|
|
242
|
+
*
|
|
243
|
+
* Thin facade over {@link Database.replacePageTemplates}; kept on
|
|
244
|
+
* `Archive` so the analyze pipeline can persist template keys without
|
|
245
|
+
* reaching into the low-level database class directly.
|
|
246
|
+
* @param templateKeysByUrl - Page URL → template key, as produced by
|
|
247
|
+
* `@nitpicker/core`'s `classifyPageTemplates`.
|
|
248
|
+
* @param clusterReasonsByTemplateKey - Template key → cluster-selection
|
|
249
|
+
* evidence, if the caller captured it. Omitting this always clears the
|
|
250
|
+
* previously-stored reasons too — "no reason" means "not captured for
|
|
251
|
+
* this run", never "carry over the previous run's reasons".
|
|
252
|
+
*/
|
|
253
|
+
replacePageTemplates(templateKeysByUrl: ReadonlyMap<string, string>, clusterReasonsByTemplateKey?: ReadonlyMap<string, TemplateClusterReason>): Promise<void>;
|
|
209
254
|
/**
|
|
210
255
|
* Promote previously-external pages that now fall under the (possibly extended)
|
|
211
256
|
* scope back to a pending state so that the crawler re-scrapes them as fully
|
|
@@ -223,11 +268,46 @@ export default class Archive extends ArchiveAccessor {
|
|
|
223
268
|
* @returns The URLs of the pages that were reset to pending.
|
|
224
269
|
*/
|
|
225
270
|
resetFailedPages(): Promise<string[]>;
|
|
271
|
+
/**
|
|
272
|
+
* Persists the raw bytes of an `--inventory` source URL list into the
|
|
273
|
+
* archive's tar payload, at `inventory/<sha256>.txt`.
|
|
274
|
+
*
|
|
275
|
+
* The file name is the content hash rather than the original file name:
|
|
276
|
+
* re-applying the same list is then a no-op write (`fs.writeFile`
|
|
277
|
+
* overwrites identical bytes), and the original name — which may embed a
|
|
278
|
+
* client/project identifier — is never retained (the archive already
|
|
279
|
+
* omits the source file's absolute path for the same reason; see
|
|
280
|
+
* `CrawlerOrchestrator.inventory`'s `source` param).
|
|
281
|
+
*
|
|
282
|
+
* This bypasses the namespace-scoped {@link ArchiveAccessor.setData} API
|
|
283
|
+
* (that one is reserved for analyze plugins and requires a namespace) —
|
|
284
|
+
* this always lands under the fixed `inventory/` prefix regardless of
|
|
285
|
+
* how this accessor was constructed. Callers that need to read the
|
|
286
|
+
* saved list back can use the inherited `getData(`inventory/${sha256}`,
|
|
287
|
+
* 'txt')`, since it resolves to the same path when no namespace is set.
|
|
288
|
+
*
|
|
289
|
+
* No entry is ever removed here — same accepted gap as `page_html_blobs`
|
|
290
|
+
* (a future #23 GC pass will sweep unreachable hashes across both). A
|
|
291
|
+
* source list that differs byte-for-byte on every run (e.g. a
|
|
292
|
+
* regenerated doc-root export with fresh timestamps) adds one entry per
|
|
293
|
+
* run with no pruning of superseded ones.
|
|
294
|
+
* @param sha256 - Lower-case hex SHA-256 digest of `bytes` (used as the file name).
|
|
295
|
+
* @param bytes - The exact bytes of the source list file, written verbatim.
|
|
296
|
+
*/
|
|
297
|
+
saveInventorySourceList(sha256: string, bytes: Buffer): Promise<void>;
|
|
226
298
|
/**
|
|
227
299
|
* Stores the crawl configuration into the archive database.
|
|
228
300
|
* @param config - The configuration object to store.
|
|
229
301
|
*/
|
|
230
302
|
setConfig(config: Config): Promise<number[]>;
|
|
303
|
+
/**
|
|
304
|
+
* Replaces one page's captured console messages / page errors in the
|
|
305
|
+
* archive database.
|
|
306
|
+
* @param pageUrl - The originally-requested URL, normalised (`withoutHashAndAuth` form).
|
|
307
|
+
* @param redirectPaths - The redirect chain hops captured during fetch, in order.
|
|
308
|
+
* @param entries - The console log entries to persist.
|
|
309
|
+
*/
|
|
310
|
+
setConsoleLogs(pageUrl: string, redirectPaths: readonly string[], entries: readonly ConsoleLogEntry[]): Promise<void>;
|
|
231
311
|
/**
|
|
232
312
|
* Stores an external page's data in the archive database without storing
|
|
233
313
|
* an HTML snapshot. External-page rows carry only metadata (status, title,
|
|
@@ -300,10 +380,17 @@ export default class Archive extends ArchiveAccessor {
|
|
|
300
380
|
*
|
|
301
381
|
* Checkpoints the SQLite WAL so the database is self-contained inside
|
|
302
382
|
* `db.sqlite`, renames the temporary working directory to the archive's
|
|
303
|
-
* basename, and tars
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
383
|
+
* basename, and tars the **entire tmpDir**. `db.sqlite` is normally the
|
|
384
|
+
* only entry (HTML lives as BLOBs in the DB, not a `snapshot-html.zip`),
|
|
385
|
+
* but a namespace-scoped `setData` write (analyze output) or
|
|
386
|
+
* `saveInventorySourceList` (a saved `--inventory` source list) adds
|
|
387
|
+
* extra files under tmpDir that get tarred right alongside it.
|
|
388
|
+
*
|
|
389
|
+
* This is why every writer path that reaches `write()` must open with
|
|
390
|
+
* `openPluginData: true` — `Archive.open`'s default extracts only
|
|
391
|
+
* `db.sqlite`, so a re-crawl (`append` / `inventory` / `retryFailed`)
|
|
392
|
+
* opened without it would tar back a tmpDir missing those extra files,
|
|
393
|
+
* silently dropping them from the rewritten archive.
|
|
307
394
|
*/
|
|
308
395
|
write(): Promise<void>;
|
|
309
396
|
/** The file extension for Nitpicker archive files (without the leading dot). */
|
|
@@ -471,7 +558,19 @@ type ArchiveOptions = {
|
|
|
471
558
|
* Additional options for opening an existing archive.
|
|
472
559
|
*/
|
|
473
560
|
type ArchiveOpenOptions = {
|
|
474
|
-
/**
|
|
561
|
+
/**
|
|
562
|
+
* When `false` (the default), only `db.sqlite` is extracted into tmpDir.
|
|
563
|
+
* When `true`, every tar entry is extracted, including non-namespace
|
|
564
|
+
* files written via {@link ArchiveAccessor.setData} (analyze output) or
|
|
565
|
+
* {@link Archive.saveInventorySourceList} (a saved `--inventory` source
|
|
566
|
+
* list).
|
|
567
|
+
*
|
|
568
|
+
* Every writer path that later calls {@link Archive.write} MUST pass
|
|
569
|
+
* `true`: `write()` re-tars whatever is currently in tmpDir, so a
|
|
570
|
+
* re-crawl (`append` / `inventory` / `retryFailed`) opened with the
|
|
571
|
+
* default would tar back a tmpDir missing those extra files, silently
|
|
572
|
+
* dropping them from the rewritten archive.
|
|
573
|
+
*/
|
|
475
574
|
openPluginData?: boolean;
|
|
476
575
|
};
|
|
477
576
|
export {};
|
package/lib/archive/archive.js
CHANGED
|
@@ -10,18 +10,23 @@ import { dbLog, log, saveLog } from './debug.js';
|
|
|
10
10
|
import { appendText } from './filesystem/append-text.js';
|
|
11
11
|
import { exists } from './filesystem/exists.js';
|
|
12
12
|
import { isDir } from './filesystem/is-dir.js';
|
|
13
|
+
import { outputBinary } from './filesystem/output-binary.js';
|
|
13
14
|
import { peekTarTopDir } from './filesystem/peek-tar-top-dir.js';
|
|
14
15
|
import { remove } from './filesystem/remove.js';
|
|
15
16
|
import { rename } from './filesystem/rename.js';
|
|
16
17
|
import { tar } from './filesystem/tar.js';
|
|
17
18
|
import { untar } from './filesystem/untar.js';
|
|
19
|
+
import { safePath } from './safe-path.js';
|
|
18
20
|
/**
|
|
19
21
|
* Main archive class for creating, opening, resuming, and writing Nitpicker
|
|
20
22
|
* archive files (`.nitpicker`).
|
|
21
23
|
*
|
|
22
|
-
* An Archive wraps a
|
|
23
|
-
*
|
|
24
|
-
* tar
|
|
24
|
+
* An Archive wraps a SQLite database into a tar archive. HTML bodies live
|
|
25
|
+
* inside the same DB as zstd-compressed BLOBs (see #75), so `db.sqlite` is
|
|
26
|
+
* normally the tar's only entry — but it is not the only entry the format
|
|
27
|
+
* allows: {@link ArchiveAccessor.setData} (namespace-scoped analyze output)
|
|
28
|
+
* and {@link Archive.saveInventorySourceList} (a saved `--inventory`
|
|
29
|
+
* source list) add plain files alongside it. It extends
|
|
25
30
|
* {@link ArchiveAccessor} to provide read access to stored data.
|
|
26
31
|
*
|
|
27
32
|
* Use the static factory methods ({@link Archive.create}, {@link Archive.open},
|
|
@@ -119,6 +124,17 @@ export default class Archive extends ArchiveAccessor {
|
|
|
119
124
|
dbLog('Add page error: %s [%s]', url, phase);
|
|
120
125
|
await this.#db.insertPageError(url, phase, message, isExternal);
|
|
121
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Closes an outage row by stamping `ended_at` — a no-op if already closed.
|
|
129
|
+
*
|
|
130
|
+
* Thin facade over {@link Database.closeNetworkOutage}.
|
|
131
|
+
* @param id - The `network_outages.id` to close.
|
|
132
|
+
* @param endedAt - Epoch ms the outage is considered to have ended.
|
|
133
|
+
*/
|
|
134
|
+
async closeNetworkOutage(id, endedAt) {
|
|
135
|
+
dbLog('Close network outage id=%d endedAt=%d', id, endedAt);
|
|
136
|
+
return await this.#db.closeNetworkOutage(id, endedAt);
|
|
137
|
+
}
|
|
122
138
|
/**
|
|
123
139
|
* Retrieves the current crawling state, including lists of scraped and pending URLs.
|
|
124
140
|
* @returns An object with `scraped` and `pending` URL arrays.
|
|
@@ -227,6 +243,18 @@ export default class Archive extends ArchiveAccessor {
|
|
|
227
243
|
dbLog('Insert inventory seeds: %d URL(s)', urls.length);
|
|
228
244
|
await this.#db.insertInventorySeeds(urls.map((u) => u.withoutHashAndAuth));
|
|
229
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Appends one open row to the `network_outages` journal.
|
|
248
|
+
*
|
|
249
|
+
* Thin facade over {@link Database.insertNetworkOutage} — see
|
|
250
|
+
* {@link recordInventoryRun}'s docstring for why this indirection exists.
|
|
251
|
+
* @param params - The confirmed-outage fields to record.
|
|
252
|
+
* @returns The autoincremented `id` of the inserted row.
|
|
253
|
+
*/
|
|
254
|
+
async insertNetworkOutage(params) {
|
|
255
|
+
dbLog('Insert network outage: startedAt=%d probeHost=%s', params.startedAt, params.probeHost);
|
|
256
|
+
return await this.#db.insertNetworkOutage(params);
|
|
257
|
+
}
|
|
230
258
|
/**
|
|
231
259
|
* Hostnames whose `crawl_errors` history is consistently DNS failures and
|
|
232
260
|
* for which no recent 2xx/3xx page or resource is recorded. Consumed by
|
|
@@ -243,6 +271,15 @@ export default class Archive extends ArchiveAccessor {
|
|
|
243
271
|
async listDnsBurnedHostCandidates() {
|
|
244
272
|
return this.#db.listDnsBurnedHostCandidates();
|
|
245
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Lists every recorded outage as a resolved {@link OutageWindow}.
|
|
276
|
+
*
|
|
277
|
+
* Thin facade over {@link Database.listNetworkOutages}.
|
|
278
|
+
* @returns Resolved outage windows, or `[]` if none have been recorded.
|
|
279
|
+
*/
|
|
280
|
+
async listNetworkOutages() {
|
|
281
|
+
return await this.#db.listNetworkOutages();
|
|
282
|
+
}
|
|
246
283
|
/**
|
|
247
284
|
* Appends one row to the `inventory_runs` audit log.
|
|
248
285
|
*
|
|
@@ -286,6 +323,23 @@ export default class Archive extends ArchiveAccessor {
|
|
|
286
323
|
async replaceAnalysisViolations(violations) {
|
|
287
324
|
await this.#db.replaceAnalysisViolations(violations);
|
|
288
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Replaces the archive's DOM-structure template classification
|
|
328
|
+
* (`--templates`) with a fresh SQL-backed set.
|
|
329
|
+
*
|
|
330
|
+
* Thin facade over {@link Database.replacePageTemplates}; kept on
|
|
331
|
+
* `Archive` so the analyze pipeline can persist template keys without
|
|
332
|
+
* reaching into the low-level database class directly.
|
|
333
|
+
* @param templateKeysByUrl - Page URL → template key, as produced by
|
|
334
|
+
* `@nitpicker/core`'s `classifyPageTemplates`.
|
|
335
|
+
* @param clusterReasonsByTemplateKey - Template key → cluster-selection
|
|
336
|
+
* evidence, if the caller captured it. Omitting this always clears the
|
|
337
|
+
* previously-stored reasons too — "no reason" means "not captured for
|
|
338
|
+
* this run", never "carry over the previous run's reasons".
|
|
339
|
+
*/
|
|
340
|
+
async replacePageTemplates(templateKeysByUrl, clusterReasonsByTemplateKey) {
|
|
341
|
+
await this.#db.replacePageTemplates(templateKeysByUrl, clusterReasonsByTemplateKey);
|
|
342
|
+
}
|
|
289
343
|
/**
|
|
290
344
|
* Promote previously-external pages that now fall under the (possibly extended)
|
|
291
345
|
* scope back to a pending state so that the crawler re-scrapes them as fully
|
|
@@ -309,6 +363,36 @@ export default class Archive extends ArchiveAccessor {
|
|
|
309
363
|
dbLog('Reset failed pages back to pending');
|
|
310
364
|
return this.#db.resetFailedPages();
|
|
311
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Persists the raw bytes of an `--inventory` source URL list into the
|
|
368
|
+
* archive's tar payload, at `inventory/<sha256>.txt`.
|
|
369
|
+
*
|
|
370
|
+
* The file name is the content hash rather than the original file name:
|
|
371
|
+
* re-applying the same list is then a no-op write (`fs.writeFile`
|
|
372
|
+
* overwrites identical bytes), and the original name — which may embed a
|
|
373
|
+
* client/project identifier — is never retained (the archive already
|
|
374
|
+
* omits the source file's absolute path for the same reason; see
|
|
375
|
+
* `CrawlerOrchestrator.inventory`'s `source` param).
|
|
376
|
+
*
|
|
377
|
+
* This bypasses the namespace-scoped {@link ArchiveAccessor.setData} API
|
|
378
|
+
* (that one is reserved for analyze plugins and requires a namespace) —
|
|
379
|
+
* this always lands under the fixed `inventory/` prefix regardless of
|
|
380
|
+
* how this accessor was constructed. Callers that need to read the
|
|
381
|
+
* saved list back can use the inherited `getData(`inventory/${sha256}`,
|
|
382
|
+
* 'txt')`, since it resolves to the same path when no namespace is set.
|
|
383
|
+
*
|
|
384
|
+
* No entry is ever removed here — same accepted gap as `page_html_blobs`
|
|
385
|
+
* (a future #23 GC pass will sweep unreachable hashes across both). A
|
|
386
|
+
* source list that differs byte-for-byte on every run (e.g. a
|
|
387
|
+
* regenerated doc-root export with fresh timestamps) adds one entry per
|
|
388
|
+
* run with no pruning of superseded ones.
|
|
389
|
+
* @param sha256 - Lower-case hex SHA-256 digest of `bytes` (used as the file name).
|
|
390
|
+
* @param bytes - The exact bytes of the source list file, written verbatim.
|
|
391
|
+
*/
|
|
392
|
+
async saveInventorySourceList(sha256, bytes) {
|
|
393
|
+
const filePath = safePath(this.tmpDir, 'inventory', `${sha256}.txt`);
|
|
394
|
+
await outputBinary(filePath, bytes);
|
|
395
|
+
}
|
|
312
396
|
/**
|
|
313
397
|
* Stores the crawl configuration into the archive database.
|
|
314
398
|
* @param config - The configuration object to store.
|
|
@@ -317,6 +401,17 @@ export default class Archive extends ArchiveAccessor {
|
|
|
317
401
|
dbLog('Set config: %O', config);
|
|
318
402
|
return this.#db.setConfig(config);
|
|
319
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Replaces one page's captured console messages / page errors in the
|
|
406
|
+
* archive database.
|
|
407
|
+
* @param pageUrl - The originally-requested URL, normalised (`withoutHashAndAuth` form).
|
|
408
|
+
* @param redirectPaths - The redirect chain hops captured during fetch, in order.
|
|
409
|
+
* @param entries - The console log entries to persist.
|
|
410
|
+
*/
|
|
411
|
+
async setConsoleLogs(pageUrl, redirectPaths, entries) {
|
|
412
|
+
dbLog('Set console logs: %d entries on %s', entries.length, pageUrl);
|
|
413
|
+
await this.#db.replaceConsoleLogs(pageUrl, redirectPaths, entries);
|
|
414
|
+
}
|
|
320
415
|
/**
|
|
321
416
|
* Stores an external page's data in the archive database without storing
|
|
322
417
|
* an HTML snapshot. External-page rows carry only metadata (status, title,
|
|
@@ -410,10 +505,17 @@ export default class Archive extends ArchiveAccessor {
|
|
|
410
505
|
*
|
|
411
506
|
* Checkpoints the SQLite WAL so the database is self-contained inside
|
|
412
507
|
* `db.sqlite`, renames the temporary working directory to the archive's
|
|
413
|
-
* basename, and tars
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
508
|
+
* basename, and tars the **entire tmpDir**. `db.sqlite` is normally the
|
|
509
|
+
* only entry (HTML lives as BLOBs in the DB, not a `snapshot-html.zip`),
|
|
510
|
+
* but a namespace-scoped `setData` write (analyze output) or
|
|
511
|
+
* `saveInventorySourceList` (a saved `--inventory` source list) adds
|
|
512
|
+
* extra files under tmpDir that get tarred right alongside it.
|
|
513
|
+
*
|
|
514
|
+
* This is why every writer path that reaches `write()` must open with
|
|
515
|
+
* `openPluginData: true` — `Archive.open`'s default extracts only
|
|
516
|
+
* `db.sqlite`, so a re-crawl (`append` / `inventory` / `retryFailed`)
|
|
517
|
+
* opened without it would tar back a tmpDir missing those extra files,
|
|
518
|
+
* silently dropping them from the rewritten archive.
|
|
417
519
|
*/
|
|
418
520
|
async write() {
|
|
419
521
|
saveLog('Starts: %s', this.#filePath);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes a content hash of a page's `<body>`, after normalizing away the
|
|
3
|
+
* kinds of incidental variance that would otherwise make two structurally
|
|
4
|
+
* identical pages hash differently: `/index.{ext}` URL-suffix forms and
|
|
5
|
+
* embedded dynamic tokens (cache-busting hashes, session/order ids, per-build
|
|
6
|
+
* CSS-module suffixes).
|
|
7
|
+
*
|
|
8
|
+
* Only the resulting hash is persisted (`page_meta.body_hash`) — the masked
|
|
9
|
+
* intermediate string is never stored. The unmasked original HTML remains
|
|
10
|
+
* fully recoverable from `page_html_blobs`, so nothing is lost by discarding
|
|
11
|
+
* it here.
|
|
12
|
+
* @param html - A full HTML document string (or fragment).
|
|
13
|
+
* @returns 32-byte SHA-256 hash of the masked `<body>` content, ready to
|
|
14
|
+
* insert into a `BLOB` column.
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const hashA = computeBodyHash('<body><a href="/p/a1b2c3d4">x</a></body>');
|
|
18
|
+
* const hashB = computeBodyHash('<body><a href="/p/z9y8x7w6">x</a></body>');
|
|
19
|
+
* hashA.equals(hashB); // true — the differing token is masked before hashing
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function computeBodyHash(html: string): Buffer;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { computeContentHash } from '../populate-ref-tables/compute-content-hash.js';
|
|
2
|
+
import { extractBody } from './extract-body.js';
|
|
3
|
+
import { maskDynamicIds } from './mask-dynamic-ids.js';
|
|
4
|
+
import { normalizeUrlLikeStrings } from './normalize-url-like-strings.js';
|
|
5
|
+
/**
|
|
6
|
+
* Computes a content hash of a page's `<body>`, after normalizing away the
|
|
7
|
+
* kinds of incidental variance that would otherwise make two structurally
|
|
8
|
+
* identical pages hash differently: `/index.{ext}` URL-suffix forms and
|
|
9
|
+
* embedded dynamic tokens (cache-busting hashes, session/order ids, per-build
|
|
10
|
+
* CSS-module suffixes).
|
|
11
|
+
*
|
|
12
|
+
* Only the resulting hash is persisted (`page_meta.body_hash`) — the masked
|
|
13
|
+
* intermediate string is never stored. The unmasked original HTML remains
|
|
14
|
+
* fully recoverable from `page_html_blobs`, so nothing is lost by discarding
|
|
15
|
+
* it here.
|
|
16
|
+
* @param html - A full HTML document string (or fragment).
|
|
17
|
+
* @returns 32-byte SHA-256 hash of the masked `<body>` content, ready to
|
|
18
|
+
* insert into a `BLOB` column.
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const hashA = computeBodyHash('<body><a href="/p/a1b2c3d4">x</a></body>');
|
|
22
|
+
* const hashB = computeBodyHash('<body><a href="/p/z9y8x7w6">x</a></body>');
|
|
23
|
+
* hashA.equals(hashB); // true — the differing token is masked before hashing
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function computeBodyHash(html) {
|
|
27
|
+
const body = extractBody(html);
|
|
28
|
+
const normalized = normalizeUrlLikeStrings(body);
|
|
29
|
+
const masked = maskDynamicIds(normalized);
|
|
30
|
+
return computeContentHash(masked);
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the inner HTML of the first `<body>` element from a full HTML
|
|
3
|
+
* document string.
|
|
4
|
+
*
|
|
5
|
+
* Uses a greedy match (`[\s\S]*`, not `[\s\S]*?`) so a literal `<body>`
|
|
6
|
+
* substring appearing inside the real body (e.g. an inline code sample) does
|
|
7
|
+
* not truncate the extracted content at that inner occurrence — the match
|
|
8
|
+
* always extends to the last `</body>` in the document.
|
|
9
|
+
*
|
|
10
|
+
* Falls back to returning the full input unchanged when no `<body>` tag is
|
|
11
|
+
* found (fragment HTML, a page that failed to render, or a snapshot cut off
|
|
12
|
+
* mid-render) rather than throwing, so callers never need a separate
|
|
13
|
+
* not-found branch.
|
|
14
|
+
* @param html - A full HTML document string, or a fragment.
|
|
15
|
+
* @returns The content between `<body...>` and `</body>`, or `html` unchanged
|
|
16
|
+
* if no `<body>` tag is present.
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractBody(html: string): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The opening tag's attribute span is matched attribute-aware
|
|
2
|
+
// (`(?:"[^"]*"|'[^']*'|[^"'>])*`, not a plain `[^>]*`) so a literal `>`
|
|
3
|
+
// inside a quoted attribute value (e.g. `<body data-x="a>b">`) — legal HTML,
|
|
4
|
+
// since only the delimiting quote character itself must not appear
|
|
5
|
+
// unescaped inside the value — does not end the match early. A plain
|
|
6
|
+
// `[^>]*` would stop at that inner `>`, so the captured body would start
|
|
7
|
+
// mid-attribute instead of at the real content.
|
|
8
|
+
const BODY_PATTERN = /<body(?:"[^"]*"|'[^']*'|[^"'>])*>([\s\S]*)<\/body>/i;
|
|
9
|
+
/**
|
|
10
|
+
* Extracts the inner HTML of the first `<body>` element from a full HTML
|
|
11
|
+
* document string.
|
|
12
|
+
*
|
|
13
|
+
* Uses a greedy match (`[\s\S]*`, not `[\s\S]*?`) so a literal `<body>`
|
|
14
|
+
* substring appearing inside the real body (e.g. an inline code sample) does
|
|
15
|
+
* not truncate the extracted content at that inner occurrence — the match
|
|
16
|
+
* always extends to the last `</body>` in the document.
|
|
17
|
+
*
|
|
18
|
+
* Falls back to returning the full input unchanged when no `<body>` tag is
|
|
19
|
+
* found (fragment HTML, a page that failed to render, or a snapshot cut off
|
|
20
|
+
* mid-render) rather than throwing, so callers never need a separate
|
|
21
|
+
* not-found branch.
|
|
22
|
+
* @param html - A full HTML document string, or a fragment.
|
|
23
|
+
* @returns The content between `<body...>` and `</body>`, or `html` unchanged
|
|
24
|
+
* if no `<body>` tag is present.
|
|
25
|
+
*/
|
|
26
|
+
export function extractBody(html) {
|
|
27
|
+
const match = BODY_PATTERN.exec(html);
|
|
28
|
+
return match?.[1] ?? html;
|
|
29
|
+
}
|