@nitpicker/crawler 0.13.0 → 0.14.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 +11 -0
- package/lib/archive/archive.js +13 -0
- package/lib/archive/create-adjunct-tables.d.ts +7 -0
- package/lib/archive/create-adjunct-tables.js +150 -0
- package/lib/archive/create-entity-tables.d.ts +14 -0
- package/lib/archive/create-entity-tables.js +32 -1
- package/lib/archive/database.d.ts +55 -1
- package/lib/archive/database.js +81 -0
- package/lib/archive/db-ops/analysis/replace-page-templates.d.ts +20 -0
- package/lib/archive/db-ops/analysis/replace-page-templates.js +52 -0
- package/lib/archive/db-ops/config/info-column-allowlist.js +1 -0
- package/lib/archive/db-ops/lifecycle/init.d.ts +16 -10
- package/lib/archive/db-ops/lifecycle/init.js +20 -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/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 +5 -3
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +13 -3
- 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 +21 -0
- 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-info-main-content-selector.d.ts +11 -0
- package/lib/archive/migrate-info-main-content-selector.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/page.d.ts +144 -2
- package/lib/archive/page.js +195 -0
- package/lib/archive/types.d.ts +39 -0
- package/lib/crawler/crawler.js +4 -2
- package/lib/crawler/fetch-destination.js +2 -0
- package/lib/crawler/link-to-page-data.js +2 -0
- package/lib/crawler/resource-to-page-data.js +2 -0
- package/lib/crawler/types.d.ts +5 -0
- package/lib/crawler-orchestrator.js +2 -0
- 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
|
@@ -206,6 +206,17 @@ export default class Archive extends ArchiveAccessor {
|
|
|
206
206
|
line?: number | null;
|
|
207
207
|
col?: number | null;
|
|
208
208
|
}[]): Promise<void>;
|
|
209
|
+
/**
|
|
210
|
+
* Replaces the archive's DOM-structure template classification
|
|
211
|
+
* (`--templates`) with a fresh SQL-backed set.
|
|
212
|
+
*
|
|
213
|
+
* Thin facade over {@link Database.replacePageTemplates}; kept on
|
|
214
|
+
* `Archive` so the analyze pipeline can persist template keys without
|
|
215
|
+
* reaching into the low-level database class directly.
|
|
216
|
+
* @param templateKeysByUrl - Page URL → template key, as produced by
|
|
217
|
+
* `@nitpicker/core`'s `classifyPageTemplates`.
|
|
218
|
+
*/
|
|
219
|
+
replacePageTemplates(templateKeysByUrl: ReadonlyMap<string, string>): Promise<void>;
|
|
209
220
|
/**
|
|
210
221
|
* Promote previously-external pages that now fall under the (possibly extended)
|
|
211
222
|
* scope back to a pending state so that the crawler re-scrapes them as fully
|
package/lib/archive/archive.js
CHANGED
|
@@ -286,6 +286,19 @@ export default class Archive extends ArchiveAccessor {
|
|
|
286
286
|
async replaceAnalysisViolations(violations) {
|
|
287
287
|
await this.#db.replaceAnalysisViolations(violations);
|
|
288
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Replaces the archive's DOM-structure template classification
|
|
291
|
+
* (`--templates`) with a fresh SQL-backed set.
|
|
292
|
+
*
|
|
293
|
+
* Thin facade over {@link Database.replacePageTemplates}; kept on
|
|
294
|
+
* `Archive` so the analyze pipeline can persist template keys without
|
|
295
|
+
* reaching into the low-level database class directly.
|
|
296
|
+
* @param templateKeysByUrl - Page URL → template key, as produced by
|
|
297
|
+
* `@nitpicker/core`'s `classifyPageTemplates`.
|
|
298
|
+
*/
|
|
299
|
+
async replacePageTemplates(templateKeysByUrl) {
|
|
300
|
+
await this.#db.replacePageTemplates(templateKeysByUrl);
|
|
301
|
+
}
|
|
289
302
|
/**
|
|
290
303
|
* Promote previously-external pages that now fall under the (possibly extended)
|
|
291
304
|
* scope back to a pending state so that the crawler re-scrapes them as fully
|
|
@@ -9,9 +9,16 @@ import type { Knex } from 'knex';
|
|
|
9
9
|
* an external link that failed DNS, or null for a process-level error)
|
|
10
10
|
* - `page_tags` — Wappalyzer detections, FK → `content_items(id)`
|
|
11
11
|
* - `page_jsonld` — JSON-LD / SpeculationRules, FK → `content_items(id)`
|
|
12
|
+
* - `page_main_content_headings` / `_images` / `_tables` / `_buttons` /
|
|
13
|
+
* `_iframes` / `_videos` / `_audios` / `_canvases` — beholder
|
|
14
|
+
* `MainContentsData` sub-entity arrays, one row per DOM element, FK →
|
|
15
|
+
* `content_items(id)`
|
|
12
16
|
* - `inventory_runs` — `--inventory` audit log (no FK; append-only)
|
|
13
17
|
* - `analysis_text_refs` + `analysis_violations` — analyze-phase findings,
|
|
14
18
|
* FK → `content_items(id)`
|
|
19
|
+
* - `page_templates` — DOM-structure template classification (`--templates`,
|
|
20
|
+
* `@nitpicker/core`'s `template-classification/`), one row per classified
|
|
21
|
+
* page, FK → `content_items(id)`
|
|
15
22
|
* - `page_html_blobs` + `page_html_ref` — content-addressable HTML
|
|
16
23
|
* snapshots, FK → `content_items(id)`
|
|
17
24
|
*
|
|
@@ -8,9 +8,16 @@
|
|
|
8
8
|
* an external link that failed DNS, or null for a process-level error)
|
|
9
9
|
* - `page_tags` — Wappalyzer detections, FK → `content_items(id)`
|
|
10
10
|
* - `page_jsonld` — JSON-LD / SpeculationRules, FK → `content_items(id)`
|
|
11
|
+
* - `page_main_content_headings` / `_images` / `_tables` / `_buttons` /
|
|
12
|
+
* `_iframes` / `_videos` / `_audios` / `_canvases` — beholder
|
|
13
|
+
* `MainContentsData` sub-entity arrays, one row per DOM element, FK →
|
|
14
|
+
* `content_items(id)`
|
|
11
15
|
* - `inventory_runs` — `--inventory` audit log (no FK; append-only)
|
|
12
16
|
* - `analysis_text_refs` + `analysis_violations` — analyze-phase findings,
|
|
13
17
|
* FK → `content_items(id)`
|
|
18
|
+
* - `page_templates` — DOM-structure template classification (`--templates`,
|
|
19
|
+
* `@nitpicker/core`'s `template-classification/`), one row per classified
|
|
20
|
+
* page, FK → `content_items(id)`
|
|
14
21
|
* - `page_html_blobs` + `page_html_ref` — content-addressable HTML
|
|
15
22
|
* snapshots, FK → `content_items(id)`
|
|
16
23
|
*
|
|
@@ -129,6 +136,134 @@ export async function createAdjunctTables(instance) {
|
|
|
129
136
|
// `list_pages_by_jsonld_type` JOINs.
|
|
130
137
|
await instance.raw('CREATE INDEX page_jsonld_type_pageId ON page_jsonld(type, pageId)');
|
|
131
138
|
}
|
|
139
|
+
// Beholder `MainContentsData` sub-entities, one adjunct table per array
|
|
140
|
+
// (headings/images/tables/buttons/iframes/videos/audios/canvases). Same
|
|
141
|
+
// shape as `page_tags` / `page_jsonld`: `pageId` FK → `content_items(id)`
|
|
142
|
+
// ON DELETE CASCADE, individually guarded so any subset can pre-exist.
|
|
143
|
+
// `order` preserves the DOM traversal order beholder returns the array
|
|
144
|
+
// in (0-based); it is not itself an index target since these tables are
|
|
145
|
+
// always read whole-page via `WHERE pageId = ? ORDER BY "order"`.
|
|
146
|
+
if (!(await instance.schema.hasTable('page_main_content_headings'))) {
|
|
147
|
+
await instance.schema.createTable('page_main_content_headings', (t) => {
|
|
148
|
+
t.increments('id');
|
|
149
|
+
t.integer('pageId')
|
|
150
|
+
.notNullable()
|
|
151
|
+
.unsigned()
|
|
152
|
+
.references('content_items.id')
|
|
153
|
+
.onDelete('CASCADE');
|
|
154
|
+
t.integer('order').notNullable();
|
|
155
|
+
t.text('text');
|
|
156
|
+
t.integer('level').notNullable();
|
|
157
|
+
t.index('pageId');
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (!(await instance.schema.hasTable('page_main_content_images'))) {
|
|
161
|
+
await instance.schema.createTable('page_main_content_images', (t) => {
|
|
162
|
+
t.increments('id');
|
|
163
|
+
t.integer('pageId')
|
|
164
|
+
.notNullable()
|
|
165
|
+
.unsigned()
|
|
166
|
+
.references('content_items.id')
|
|
167
|
+
.onDelete('CASCADE');
|
|
168
|
+
t.integer('order').notNullable();
|
|
169
|
+
t.string('src', 8190).notNullable();
|
|
170
|
+
t.text('alt').notNullable();
|
|
171
|
+
t.index('pageId');
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (!(await instance.schema.hasTable('page_main_content_tables'))) {
|
|
175
|
+
await instance.schema.createTable('page_main_content_tables', (t) => {
|
|
176
|
+
t.increments('id');
|
|
177
|
+
t.integer('pageId')
|
|
178
|
+
.notNullable()
|
|
179
|
+
.unsigned()
|
|
180
|
+
.references('content_items.id')
|
|
181
|
+
.onDelete('CASCADE');
|
|
182
|
+
t.integer('order').notNullable();
|
|
183
|
+
t.integer('rows').notNullable();
|
|
184
|
+
t.integer('cols').notNullable();
|
|
185
|
+
t.boolean('hasHeader').notNullable();
|
|
186
|
+
t.boolean('hasFooter').notNullable();
|
|
187
|
+
t.boolean('hasMergedCell').notNullable();
|
|
188
|
+
t.index('pageId');
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (!(await instance.schema.hasTable('page_main_content_buttons'))) {
|
|
192
|
+
await instance.schema.createTable('page_main_content_buttons', (t) => {
|
|
193
|
+
t.increments('id');
|
|
194
|
+
t.integer('pageId')
|
|
195
|
+
.notNullable()
|
|
196
|
+
.unsigned()
|
|
197
|
+
.references('content_items.id')
|
|
198
|
+
.onDelete('CASCADE');
|
|
199
|
+
t.integer('order').notNullable();
|
|
200
|
+
t.string('nodeName').notNullable();
|
|
201
|
+
t.string('role');
|
|
202
|
+
t.string('type');
|
|
203
|
+
t.text('text');
|
|
204
|
+
t.boolean('disabled').notNullable();
|
|
205
|
+
t.index('pageId');
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (!(await instance.schema.hasTable('page_main_content_iframes'))) {
|
|
209
|
+
await instance.schema.createTable('page_main_content_iframes', (t) => {
|
|
210
|
+
t.increments('id');
|
|
211
|
+
t.integer('pageId')
|
|
212
|
+
.notNullable()
|
|
213
|
+
.unsigned()
|
|
214
|
+
.references('content_items.id')
|
|
215
|
+
.onDelete('CASCADE');
|
|
216
|
+
t.integer('order').notNullable();
|
|
217
|
+
t.string('src', 8190).notNullable();
|
|
218
|
+
t.text('title');
|
|
219
|
+
t.string('width');
|
|
220
|
+
t.string('height');
|
|
221
|
+
t.index('pageId');
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (!(await instance.schema.hasTable('page_main_content_videos'))) {
|
|
225
|
+
await instance.schema.createTable('page_main_content_videos', (t) => {
|
|
226
|
+
t.increments('id');
|
|
227
|
+
t.integer('pageId')
|
|
228
|
+
.notNullable()
|
|
229
|
+
.unsigned()
|
|
230
|
+
.references('content_items.id')
|
|
231
|
+
.onDelete('CASCADE');
|
|
232
|
+
t.integer('order').notNullable();
|
|
233
|
+
t.string('src', 8190).notNullable();
|
|
234
|
+
t.string('poster', 8190);
|
|
235
|
+
t.integer('width').notNullable();
|
|
236
|
+
t.integer('height').notNullable();
|
|
237
|
+
t.index('pageId');
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (!(await instance.schema.hasTable('page_main_content_audios'))) {
|
|
241
|
+
await instance.schema.createTable('page_main_content_audios', (t) => {
|
|
242
|
+
t.increments('id');
|
|
243
|
+
t.integer('pageId')
|
|
244
|
+
.notNullable()
|
|
245
|
+
.unsigned()
|
|
246
|
+
.references('content_items.id')
|
|
247
|
+
.onDelete('CASCADE');
|
|
248
|
+
t.integer('order').notNullable();
|
|
249
|
+
t.string('src', 8190).notNullable();
|
|
250
|
+
t.index('pageId');
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (!(await instance.schema.hasTable('page_main_content_canvases'))) {
|
|
254
|
+
await instance.schema.createTable('page_main_content_canvases', (t) => {
|
|
255
|
+
t.increments('id');
|
|
256
|
+
t.integer('pageId')
|
|
257
|
+
.notNullable()
|
|
258
|
+
.unsigned()
|
|
259
|
+
.references('content_items.id')
|
|
260
|
+
.onDelete('CASCADE');
|
|
261
|
+
t.integer('order').notNullable();
|
|
262
|
+
t.integer('width').notNullable();
|
|
263
|
+
t.integer('height').notNullable();
|
|
264
|
+
t.index('pageId');
|
|
265
|
+
});
|
|
266
|
+
}
|
|
132
267
|
if (!(await instance.schema.hasTable('inventory_runs'))) {
|
|
133
268
|
await instance.schema.createTable('inventory_runs', (t) => {
|
|
134
269
|
// One row per successful `--inventory <list>` invocation. The
|
|
@@ -184,6 +319,21 @@ export async function createAdjunctTables(instance) {
|
|
|
184
319
|
await instance.raw('CREATE INDEX av_code_order ON analysis_violations(code_sort_key, id)');
|
|
185
320
|
await instance.raw('CREATE INDEX av_page ON analysis_violations(page_id, id)');
|
|
186
321
|
}
|
|
322
|
+
// DOM-structure template classification (`--templates`). One row per
|
|
323
|
+
// internal HTML page that was classified; `page_id` is both the PK and
|
|
324
|
+
// the natural key (1:1 with `content_items`), so — unlike
|
|
325
|
+
// `analysis_violations`, which is 1:many and needs a surrogate `id` —
|
|
326
|
+
// there's nothing to index beyond the PK itself. `WITHOUT ROWID` packs
|
|
327
|
+
// rows directly in the PK b-tree, matching `page_html_ref`'s shape
|
|
328
|
+
// (small fixed-width row, PK-only lookups).
|
|
329
|
+
if (!(await instance.schema.hasTable('page_templates'))) {
|
|
330
|
+
await instance.raw(`
|
|
331
|
+
CREATE TABLE page_templates (
|
|
332
|
+
page_id INTEGER PRIMARY KEY REFERENCES content_items(id),
|
|
333
|
+
template_key TEXT NOT NULL
|
|
334
|
+
) WITHOUT ROWID
|
|
335
|
+
`);
|
|
336
|
+
}
|
|
187
337
|
// Content-addressable HTML blob storage. Knex's schema builder doesn't
|
|
188
338
|
// expose a WITHOUT ROWID toggle, so the BLOB tables are created via raw
|
|
189
339
|
// SQL. WITHOUT ROWID keeps the rows packed inside the b-tree leaves
|
|
@@ -136,6 +136,20 @@ import type { Knex } from 'knex';
|
|
|
136
136
|
* routing so the two entities never disagree on which values count as
|
|
137
137
|
* "large data URI".
|
|
138
138
|
*
|
|
139
|
+
* **`page_meta.main_content_*` / `scroll_height_*` columns.** Denormalised
|
|
140
|
+
* aggregates derived from beholder's `MainContentsData` / `ScrollHeightData`
|
|
141
|
+
* (word/element counts, desktop+mobile scroll height), following the same
|
|
142
|
+
* write-once-at-scrape-time pattern as `tag_count` / `jsonld_count` so list
|
|
143
|
+
* / detail reads never re-derive them from the per-page child tables
|
|
144
|
+
* (`page_main_content_headings` etc., see `create-adjunct-tables.ts`).
|
|
145
|
+
* `main_content_node_name` / `_id` / `_role` / `_selector` / `_class_list`
|
|
146
|
+
* identify the detected main-content element; unlike `title_text_id` /
|
|
147
|
+
* `description_text_id` these are stored as plain `TEXT` rather than routed
|
|
148
|
+
* through `text_refs` — the values are page-specific diagnostics with low
|
|
149
|
+
* cross-page reuse, so the ref-table dedup machinery would add write-path
|
|
150
|
+
* cost without a corresponding storage win. `main_content_class_list` holds
|
|
151
|
+
* a JSON-encoded string array.
|
|
152
|
+
*
|
|
139
153
|
* ### Index rationale
|
|
140
154
|
*
|
|
141
155
|
* Every index below reflects a legacy-baseline single-column index that
|
|
@@ -135,6 +135,20 @@
|
|
|
135
135
|
* routing so the two entities never disagree on which values count as
|
|
136
136
|
* "large data URI".
|
|
137
137
|
*
|
|
138
|
+
* **`page_meta.main_content_*` / `scroll_height_*` columns.** Denormalised
|
|
139
|
+
* aggregates derived from beholder's `MainContentsData` / `ScrollHeightData`
|
|
140
|
+
* (word/element counts, desktop+mobile scroll height), following the same
|
|
141
|
+
* write-once-at-scrape-time pattern as `tag_count` / `jsonld_count` so list
|
|
142
|
+
* / detail reads never re-derive them from the per-page child tables
|
|
143
|
+
* (`page_main_content_headings` etc., see `create-adjunct-tables.ts`).
|
|
144
|
+
* `main_content_node_name` / `_id` / `_role` / `_selector` / `_class_list`
|
|
145
|
+
* identify the detected main-content element; unlike `title_text_id` /
|
|
146
|
+
* `description_text_id` these are stored as plain `TEXT` rather than routed
|
|
147
|
+
* through `text_refs` — the values are page-specific diagnostics with low
|
|
148
|
+
* cross-page reuse, so the ref-table dedup machinery would add write-path
|
|
149
|
+
* cost without a corresponding storage win. `main_content_class_list` holds
|
|
150
|
+
* a JSON-encoded string array.
|
|
151
|
+
*
|
|
138
152
|
* ### Index rationale
|
|
139
153
|
*
|
|
140
154
|
* Every index below reflects a legacy-baseline single-column index that
|
|
@@ -248,7 +262,24 @@ export async function createEntityTables(instance) {
|
|
|
248
262
|
tag_count INTEGER,
|
|
249
263
|
jsonld_count INTEGER,
|
|
250
264
|
tags_providers_csv TEXT,
|
|
251
|
-
meta_extras_json_id INTEGER REFERENCES json_refs(id)
|
|
265
|
+
meta_extras_json_id INTEGER REFERENCES json_refs(id),
|
|
266
|
+
main_content_node_name TEXT,
|
|
267
|
+
main_content_id TEXT,
|
|
268
|
+
main_content_role TEXT,
|
|
269
|
+
main_content_selector TEXT,
|
|
270
|
+
main_content_class_list TEXT,
|
|
271
|
+
main_content_word_count INTEGER,
|
|
272
|
+
main_content_body_word_count INTEGER,
|
|
273
|
+
main_content_heading_count INTEGER,
|
|
274
|
+
main_content_image_count INTEGER,
|
|
275
|
+
main_content_table_count INTEGER,
|
|
276
|
+
main_content_button_count INTEGER,
|
|
277
|
+
main_content_iframe_count INTEGER,
|
|
278
|
+
main_content_video_count INTEGER,
|
|
279
|
+
main_content_audio_count INTEGER,
|
|
280
|
+
main_content_canvas_count INTEGER,
|
|
281
|
+
scroll_height_desktop INTEGER,
|
|
282
|
+
scroll_height_mobile INTEGER
|
|
252
283
|
)
|
|
253
284
|
`);
|
|
254
285
|
await instance.raw('CREATE INDEX IF NOT EXISTS idx_page_meta_og_type ON page_meta(og_type)');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { JsonLdRow, TagRow } from './meta/types.js';
|
|
1
|
+
import type { JsonLdRow, MainContentAudioRow, MainContentButtonRow, MainContentCanvasRow, MainContentHeadingRow, MainContentIframeRow, MainContentImageRow, MainContentTableRow, MainContentVideoRow, TagRow } from './meta/types.js';
|
|
2
2
|
import type { Config, DatabaseOption, DB_Redirect, DB_Resource, DatabaseEvent, InventoryRunMeta, PageFilter, PageSource } from './types.js';
|
|
3
3
|
import type { PageData, Resource } from '../utils/types/types.js';
|
|
4
4
|
import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
@@ -46,6 +46,12 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
46
46
|
* @returns An array of anchor records with resolved URL, title, status, and content type.
|
|
47
47
|
*/
|
|
48
48
|
getAnchorsOnPage(pageId: number): Promise<any[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Retrieves all `page_main_content_audios` rows for the given page id.
|
|
51
|
+
* Delegates to {@link getAudiosOfPageOp}.
|
|
52
|
+
* @param pageId
|
|
53
|
+
*/
|
|
54
|
+
getAudiosOfPage(pageId: number): Promise<MainContentAudioRow[]>;
|
|
49
55
|
/**
|
|
50
56
|
* Retrieves the base URL of the crawl session from the `info` table.
|
|
51
57
|
* Delegates to {@link getBaseUrlOp}.
|
|
@@ -53,6 +59,18 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
53
59
|
* @throws {Error} If no base URL is found in the database.
|
|
54
60
|
*/
|
|
55
61
|
getBaseUrl(): Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Retrieves all `page_main_content_buttons` rows for the given page id.
|
|
64
|
+
* Delegates to {@link getButtonsOfPageOp}.
|
|
65
|
+
* @param pageId
|
|
66
|
+
*/
|
|
67
|
+
getButtonsOfPage(pageId: number): Promise<MainContentButtonRow[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Retrieves all `page_main_content_canvases` rows for the given page id.
|
|
70
|
+
* Delegates to {@link getCanvasesOfPageOp}.
|
|
71
|
+
* @param pageId
|
|
72
|
+
*/
|
|
73
|
+
getCanvasesOfPage(pageId: number): Promise<MainContentCanvasRow[]>;
|
|
56
74
|
/**
|
|
57
75
|
* Retrieves the full crawl configuration from the `info` table.
|
|
58
76
|
* Delegates to {@link getConfigOp}.
|
|
@@ -85,6 +103,12 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
85
103
|
* @returns URLs found in `resources`.
|
|
86
104
|
*/
|
|
87
105
|
getExistingResourceUrls(urls: readonly string[]): Promise<string[]>;
|
|
106
|
+
/**
|
|
107
|
+
* Retrieves all `page_main_content_headings` rows for the given page id.
|
|
108
|
+
* Delegates to {@link getHeadingsOfPageOp}.
|
|
109
|
+
* @param pageId
|
|
110
|
+
*/
|
|
111
|
+
getHeadingsOfPage(pageId: number): Promise<MainContentHeadingRow[]>;
|
|
88
112
|
/**
|
|
89
113
|
* Reads the HTML snapshot stored as a zstd-compressed BLOB for the given page.
|
|
90
114
|
* Delegates to {@link getHtmlOfPageByIdOp}.
|
|
@@ -92,6 +116,12 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
92
116
|
* @returns The decompressed HTML string, or `null` if no snapshot is stored.
|
|
93
117
|
*/
|
|
94
118
|
getHtmlOfPageById(pageId: number): Promise<string | null>;
|
|
119
|
+
/**
|
|
120
|
+
* Retrieves all `page_main_content_iframes` rows for the given page id.
|
|
121
|
+
* Delegates to {@link getIframesOfPageOp}.
|
|
122
|
+
* @param pageId
|
|
123
|
+
*/
|
|
124
|
+
getIframesOfPage(pageId: number): Promise<MainContentIframeRow[]>;
|
|
95
125
|
/**
|
|
96
126
|
* Retrieves all `page_jsonld` rows for the given page id, parsed back into
|
|
97
127
|
* {@link JsonLdRow} shape. Delegates to {@link getJsonLdOfPageOp}.
|
|
@@ -105,6 +135,18 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
105
135
|
* @returns The Knex instance connected to the SQLite database.
|
|
106
136
|
*/
|
|
107
137
|
getKnex(): Knex;
|
|
138
|
+
/**
|
|
139
|
+
* Retrieves all `page_main_content_images` rows for the given page id.
|
|
140
|
+
* Delegates to {@link getMainContentImagesOfPageOp}.
|
|
141
|
+
* @param pageId
|
|
142
|
+
*/
|
|
143
|
+
getMainContentImagesOfPage(pageId: number): Promise<MainContentImageRow[]>;
|
|
144
|
+
/**
|
|
145
|
+
* Retrieves all `page_main_content_tables` rows for the given page id.
|
|
146
|
+
* Delegates to {@link getMainContentTablesOfPageOp}.
|
|
147
|
+
* @param pageId
|
|
148
|
+
*/
|
|
149
|
+
getMainContentTablesOfPage(pageId: number): Promise<MainContentTableRow[]>;
|
|
108
150
|
/**
|
|
109
151
|
* Retrieves the crawl session name from the `info` table.
|
|
110
152
|
* Delegates to {@link getNameOp}.
|
|
@@ -207,6 +249,12 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
207
249
|
* @param pageId
|
|
208
250
|
*/
|
|
209
251
|
getTagsOfPage(pageId: number): Promise<TagRow[]>;
|
|
252
|
+
/**
|
|
253
|
+
* Retrieves all `page_main_content_videos` rows for the given page id.
|
|
254
|
+
* Delegates to {@link getVideosOfPageOp}.
|
|
255
|
+
* @param pageId
|
|
256
|
+
*/
|
|
257
|
+
getVideosOfPage(pageId: number): Promise<MainContentVideoRow[]>;
|
|
210
258
|
/**
|
|
211
259
|
* Records a crawler-level (`error` channel) failure into `crawl_errors`.
|
|
212
260
|
* Delegates to {@link insertCrawlErrorOp}.
|
|
@@ -291,6 +339,12 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
291
339
|
line?: number | null;
|
|
292
340
|
col?: number | null;
|
|
293
341
|
}[]): Promise<void>;
|
|
342
|
+
/**
|
|
343
|
+
* Replaces the stored DOM-structure template classification with a
|
|
344
|
+
* freshly generated set. Delegates to {@link replacePageTemplatesOp}.
|
|
345
|
+
* @param templateKeysByUrl - Page URL → template key.
|
|
346
|
+
*/
|
|
347
|
+
replacePageTemplates(templateKeysByUrl: ReadonlyMap<string, string>): Promise<void>;
|
|
294
348
|
/**
|
|
295
349
|
* Promote previously-external pages whose URL falls under any of the new
|
|
296
350
|
* scope entries back to a "needs scraping" state.
|