@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
package/lib/archive/database.js
CHANGED
|
@@ -8,6 +8,7 @@ import { emitError } from '../utils/error/emit-error.js';
|
|
|
8
8
|
import { createWriteRefCaches } from './db-ops/_shared/create-write-ref-caches.js';
|
|
9
9
|
import { retrySetting } from './db-ops/_shared/retry-setting.js';
|
|
10
10
|
import { replaceAnalysisViolations as replaceAnalysisViolationsOp } from './db-ops/analysis/replace-analysis-violations.js';
|
|
11
|
+
import { replacePageTemplates as replacePageTemplatesOp } from './db-ops/analysis/replace-page-templates.js';
|
|
11
12
|
import { getAnchorsOnPage as getAnchorsOnPageOp } from './db-ops/anchors/get-anchors-on-page.js';
|
|
12
13
|
import { getBaseUrl as getBaseUrlOp } from './db-ops/config/get-base-url.js';
|
|
13
14
|
import { getConfig as getConfigOp } from './db-ops/config/get-config.js';
|
|
@@ -22,8 +23,16 @@ import { recordInventoryRun as recordInventoryRunOp } from './db-ops/inventory/r
|
|
|
22
23
|
import { checkpoint as checkpointOp } from './db-ops/lifecycle/checkpoint.js';
|
|
23
24
|
import { destroy as destroyOp } from './db-ops/lifecycle/destroy.js';
|
|
24
25
|
import { init as initOp } from './db-ops/lifecycle/init.js';
|
|
26
|
+
import { getAudiosOfPage as getAudiosOfPageOp } from './db-ops/meta/get-audios-of-page.js';
|
|
27
|
+
import { getButtonsOfPage as getButtonsOfPageOp } from './db-ops/meta/get-buttons-of-page.js';
|
|
28
|
+
import { getCanvasesOfPage as getCanvasesOfPageOp } from './db-ops/meta/get-canvases-of-page.js';
|
|
29
|
+
import { getHeadingsOfPage as getHeadingsOfPageOp } from './db-ops/meta/get-headings-of-page.js';
|
|
30
|
+
import { getIframesOfPage as getIframesOfPageOp } from './db-ops/meta/get-iframes-of-page.js';
|
|
25
31
|
import { getJsonLdOfPage as getJsonLdOfPageOp } from './db-ops/meta/get-jsonld-of-page.js';
|
|
32
|
+
import { getMainContentImagesOfPage as getMainContentImagesOfPageOp } from './db-ops/meta/get-main-content-images-of-page.js';
|
|
33
|
+
import { getMainContentTablesOfPage as getMainContentTablesOfPageOp } from './db-ops/meta/get-main-content-tables-of-page.js';
|
|
26
34
|
import { getTagsOfPage as getTagsOfPageOp } from './db-ops/meta/get-tags-of-page.js';
|
|
35
|
+
import { getVideosOfPage as getVideosOfPageOp } from './db-ops/meta/get-videos-of-page.js';
|
|
27
36
|
import { setUrlOrder as setUrlOrderOp } from './db-ops/pages/order/set-url-order.js';
|
|
28
37
|
import { getCrawlingState as getCrawlingStateOp } from './db-ops/pages/read/get-crawling-state.js';
|
|
29
38
|
import { getExistingPageUrls as getExistingPageUrlsOp } from './db-ops/pages/read/get-existing-page-urls.js';
|
|
@@ -128,6 +137,14 @@ export class Database extends EventEmitter {
|
|
|
128
137
|
async getAnchorsOnPage(pageId) {
|
|
129
138
|
return emitErrorAndRetry(this, 'Database.getAnchorsOnPage', async () => await getAnchorsOnPageOp(this.#instance, pageId), retrySetting);
|
|
130
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Retrieves all `page_main_content_audios` rows for the given page id.
|
|
142
|
+
* Delegates to {@link getAudiosOfPageOp}.
|
|
143
|
+
* @param pageId
|
|
144
|
+
*/
|
|
145
|
+
async getAudiosOfPage(pageId) {
|
|
146
|
+
return emitErrorAndRetry(this, 'Database.getAudiosOfPage', async () => await getAudiosOfPageOp(this.#instance, pageId), retrySetting);
|
|
147
|
+
}
|
|
131
148
|
/**
|
|
132
149
|
* Retrieves the base URL of the crawl session from the `info` table.
|
|
133
150
|
* Delegates to {@link getBaseUrlOp}.
|
|
@@ -137,6 +154,22 @@ export class Database extends EventEmitter {
|
|
|
137
154
|
async getBaseUrl() {
|
|
138
155
|
return emitErrorAndRetry(this, 'Database.getBaseUrl', async () => await getBaseUrlOp(this.#instance), retrySetting);
|
|
139
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Retrieves all `page_main_content_buttons` rows for the given page id.
|
|
159
|
+
* Delegates to {@link getButtonsOfPageOp}.
|
|
160
|
+
* @param pageId
|
|
161
|
+
*/
|
|
162
|
+
async getButtonsOfPage(pageId) {
|
|
163
|
+
return emitErrorAndRetry(this, 'Database.getButtonsOfPage', async () => await getButtonsOfPageOp(this.#instance, pageId), retrySetting);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Retrieves all `page_main_content_canvases` rows for the given page id.
|
|
167
|
+
* Delegates to {@link getCanvasesOfPageOp}.
|
|
168
|
+
* @param pageId
|
|
169
|
+
*/
|
|
170
|
+
async getCanvasesOfPage(pageId) {
|
|
171
|
+
return emitErrorAndRetry(this, 'Database.getCanvasesOfPage', async () => await getCanvasesOfPageOp(this.#instance, pageId), retrySetting);
|
|
172
|
+
}
|
|
140
173
|
/**
|
|
141
174
|
* Retrieves the full crawl configuration from the `info` table.
|
|
142
175
|
* Delegates to {@link getConfigOp}.
|
|
@@ -174,6 +207,14 @@ export class Database extends EventEmitter {
|
|
|
174
207
|
async getExistingResourceUrls(urls) {
|
|
175
208
|
return emitError(this, 'Database.getExistingResourceUrls', async () => await getExistingResourceUrlsOp(this.#instance, urls));
|
|
176
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Retrieves all `page_main_content_headings` rows for the given page id.
|
|
212
|
+
* Delegates to {@link getHeadingsOfPageOp}.
|
|
213
|
+
* @param pageId
|
|
214
|
+
*/
|
|
215
|
+
async getHeadingsOfPage(pageId) {
|
|
216
|
+
return emitErrorAndRetry(this, 'Database.getHeadingsOfPage', async () => await getHeadingsOfPageOp(this.#instance, pageId), retrySetting);
|
|
217
|
+
}
|
|
177
218
|
/**
|
|
178
219
|
* Reads the HTML snapshot stored as a zstd-compressed BLOB for the given page.
|
|
179
220
|
* Delegates to {@link getHtmlOfPageByIdOp}.
|
|
@@ -183,6 +224,14 @@ export class Database extends EventEmitter {
|
|
|
183
224
|
async getHtmlOfPageById(pageId) {
|
|
184
225
|
return emitErrorAndRetry(this, 'Database.getHtmlOfPageById', async () => await getHtmlOfPageByIdOp(this.#instance, pageId), retrySetting);
|
|
185
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Retrieves all `page_main_content_iframes` rows for the given page id.
|
|
229
|
+
* Delegates to {@link getIframesOfPageOp}.
|
|
230
|
+
* @param pageId
|
|
231
|
+
*/
|
|
232
|
+
async getIframesOfPage(pageId) {
|
|
233
|
+
return emitErrorAndRetry(this, 'Database.getIframesOfPage', async () => await getIframesOfPageOp(this.#instance, pageId), retrySetting);
|
|
234
|
+
}
|
|
186
235
|
/**
|
|
187
236
|
* Retrieves all `page_jsonld` rows for the given page id, parsed back into
|
|
188
237
|
* {@link JsonLdRow} shape. Delegates to {@link getJsonLdOfPageOp}.
|
|
@@ -200,6 +249,22 @@ export class Database extends EventEmitter {
|
|
|
200
249
|
getKnex() {
|
|
201
250
|
return this.#instance;
|
|
202
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Retrieves all `page_main_content_images` rows for the given page id.
|
|
254
|
+
* Delegates to {@link getMainContentImagesOfPageOp}.
|
|
255
|
+
* @param pageId
|
|
256
|
+
*/
|
|
257
|
+
async getMainContentImagesOfPage(pageId) {
|
|
258
|
+
return emitErrorAndRetry(this, 'Database.getMainContentImagesOfPage', async () => await getMainContentImagesOfPageOp(this.#instance, pageId), retrySetting);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Retrieves all `page_main_content_tables` rows for the given page id.
|
|
262
|
+
* Delegates to {@link getMainContentTablesOfPageOp}.
|
|
263
|
+
* @param pageId
|
|
264
|
+
*/
|
|
265
|
+
async getMainContentTablesOfPage(pageId) {
|
|
266
|
+
return emitErrorAndRetry(this, 'Database.getMainContentTablesOfPage', async () => await getMainContentTablesOfPageOp(this.#instance, pageId), retrySetting);
|
|
267
|
+
}
|
|
203
268
|
/**
|
|
204
269
|
* Retrieves the crawl session name from the `info` table.
|
|
205
270
|
* Delegates to {@link getNameOp}.
|
|
@@ -326,6 +391,14 @@ export class Database extends EventEmitter {
|
|
|
326
391
|
async getTagsOfPage(pageId) {
|
|
327
392
|
return emitErrorAndRetry(this, 'Database.getTagsOfPage', async () => await getTagsOfPageOp(this.#instance, pageId), retrySetting);
|
|
328
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Retrieves all `page_main_content_videos` rows for the given page id.
|
|
396
|
+
* Delegates to {@link getVideosOfPageOp}.
|
|
397
|
+
* @param pageId
|
|
398
|
+
*/
|
|
399
|
+
async getVideosOfPage(pageId) {
|
|
400
|
+
return emitErrorAndRetry(this, 'Database.getVideosOfPage', async () => await getVideosOfPageOp(this.#instance, pageId), retrySetting);
|
|
401
|
+
}
|
|
329
402
|
/**
|
|
330
403
|
* Records a crawler-level (`error` channel) failure into `crawl_errors`.
|
|
331
404
|
* Delegates to {@link insertCrawlErrorOp}.
|
|
@@ -421,6 +494,14 @@ export class Database extends EventEmitter {
|
|
|
421
494
|
async replaceAnalysisViolations(violations) {
|
|
422
495
|
return emitErrorAndRetry(this, 'Database.replaceAnalysisViolations', async () => await replaceAnalysisViolationsOp(this.#instance, violations), retrySetting);
|
|
423
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Replaces the stored DOM-structure template classification with a
|
|
499
|
+
* freshly generated set. Delegates to {@link replacePageTemplatesOp}.
|
|
500
|
+
* @param templateKeysByUrl - Page URL → template key.
|
|
501
|
+
*/
|
|
502
|
+
async replacePageTemplates(templateKeysByUrl) {
|
|
503
|
+
return emitErrorAndRetry(this, 'Database.replacePageTemplates', async () => await replacePageTemplatesOp(this.#instance, templateKeysByUrl), retrySetting);
|
|
504
|
+
}
|
|
424
505
|
/**
|
|
425
506
|
* Promote previously-external pages whose URL falls under any of the new
|
|
426
507
|
* scope entries back to a "needs scraping" state.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the stored DOM-structure template classification (`--templates`)
|
|
4
|
+
* with a freshly generated set.
|
|
5
|
+
*
|
|
6
|
+
* Every classified page is a full-archive, all-or-nothing recomputation
|
|
7
|
+
* (see `@nitpicker/core`'s `classifyPageTemplates`), so this always deletes
|
|
8
|
+
* every existing row before inserting the new set — there is no per-page
|
|
9
|
+
* incremental update path, matching `replaceAnalysisViolations`'s
|
|
10
|
+
* whole-table replace shape. Unlike violations, a page whose URL can't be
|
|
11
|
+
* resolved back to a `content_items` row is silently skipped rather than
|
|
12
|
+
* treated as a hard failure: losing one page's template classification
|
|
13
|
+
* (e.g. a URL-normalization mismatch between the in-memory `Page.url.href`
|
|
14
|
+
* and the stored `url_refs.url`) should not discard the rest of a
|
|
15
|
+
* potentially multi-thousand-page classification run.
|
|
16
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
17
|
+
* @param templateKeysByUrl - Page URL → template key, as produced by
|
|
18
|
+
* `classifyPageTemplates`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function replacePageTemplates(knex: Knex, templateKeysByUrl: ReadonlyMap<string, string>): Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../utils/array/each-splitted.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the stored DOM-structure template classification (`--templates`)
|
|
4
|
+
* with a freshly generated set.
|
|
5
|
+
*
|
|
6
|
+
* Every classified page is a full-archive, all-or-nothing recomputation
|
|
7
|
+
* (see `@nitpicker/core`'s `classifyPageTemplates`), so this always deletes
|
|
8
|
+
* every existing row before inserting the new set — there is no per-page
|
|
9
|
+
* incremental update path, matching `replaceAnalysisViolations`'s
|
|
10
|
+
* whole-table replace shape. Unlike violations, a page whose URL can't be
|
|
11
|
+
* resolved back to a `content_items` row is silently skipped rather than
|
|
12
|
+
* treated as a hard failure: losing one page's template classification
|
|
13
|
+
* (e.g. a URL-normalization mismatch between the in-memory `Page.url.href`
|
|
14
|
+
* and the stored `url_refs.url`) should not discard the rest of a
|
|
15
|
+
* potentially multi-thousand-page classification run.
|
|
16
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
17
|
+
* @param templateKeysByUrl - Page URL → template key, as produced by
|
|
18
|
+
* `classifyPageTemplates`.
|
|
19
|
+
*/
|
|
20
|
+
export async function replacePageTemplates(knex, templateKeysByUrl) {
|
|
21
|
+
await knex.transaction(async (trx) => {
|
|
22
|
+
await trx('page_templates').delete();
|
|
23
|
+
if (templateKeysByUrl.size === 0) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const urls = [...templateKeysByUrl.keys()];
|
|
27
|
+
const pageIdByUrl = new Map();
|
|
28
|
+
await eachSplitted(urls, 500, async (chunk) => {
|
|
29
|
+
const pageRows = await trx('content_items')
|
|
30
|
+
.join('url_refs', 'url_refs.id', 'content_items.url_id')
|
|
31
|
+
.select('content_items.id as id', 'url_refs.url as url')
|
|
32
|
+
.whereIn('url_refs.url', chunk);
|
|
33
|
+
for (const row of pageRows) {
|
|
34
|
+
pageIdByUrl.set(row.url, row.id);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const rows = [];
|
|
38
|
+
for (const [url, templateKey] of templateKeysByUrl) {
|
|
39
|
+
const pageId = pageIdByUrl.get(url);
|
|
40
|
+
if (pageId == null) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
rows.push({ page_id: pageId, template_key: templateKey });
|
|
44
|
+
}
|
|
45
|
+
if (rows.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
await eachSplitted(rows, 500, async (chunk) => {
|
|
49
|
+
await trx('page_templates').insert(chunk);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import type { Knex } from 'knex';
|
|
2
2
|
/**
|
|
3
3
|
* Initializes the database schema if tables do not exist, then runs the
|
|
4
|
-
*
|
|
4
|
+
* remaining lightweight migrations (`info.roots`, `info.mainContentSelector`,
|
|
5
|
+
* `page_meta.main_content_*`).
|
|
5
6
|
*
|
|
6
|
-
* There is deliberately no per-table
|
|
7
|
-
* `assertCompatibleVersion` (called below, before any schema work)
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
7
|
+
* There is deliberately no per-table *table-creation* migration chain here:
|
|
8
|
+
* `assertCompatibleVersion` (called below, before any schema work) rejects
|
|
9
|
+
* every archive older than the current format, so a connection that reaches
|
|
10
|
+
* `initSchema` is either brand new (`initSchema` provisions the full schema)
|
|
11
|
+
* or was produced by `scripts/migrate-to-0.13.mjs` (which guarantees the
|
|
12
|
+
* full table set before it repacks) — and `initSchema` itself re-runs
|
|
13
|
+
* `createEntityTables` / `createAdjunctTables` unconditionally on every
|
|
14
|
+
* open, self-healing any *missing table* via their internal
|
|
15
|
+
* `IF NOT EXISTS` / `hasTable` guards. What that self-healing cannot do is
|
|
16
|
+
* retrofit a *new column* onto an entity table that already exists —
|
|
17
|
+
* `CREATE TABLE IF NOT EXISTS` is a no-op once the table is present. Column
|
|
18
|
+
* additions to an existing 0.13 table are therefore the one case that still
|
|
19
|
+
* needs an explicit `hasColumn`-guarded `ALTER TABLE` here (`migrateInfoRoots`,
|
|
20
|
+
* `migrateMainContentsColumns`) rather than a DDL-string change alone.
|
|
15
21
|
*
|
|
16
22
|
* In read-only mode schema init + migration are SKIPPED so the same DB
|
|
17
23
|
* can be opened safely by a viewer attached to a live (or interrupted)
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import { applyConnectionPragmas, initSchema } from '../../init-schema.js';
|
|
2
2
|
import { assertCompatibleVersion } from '../../meta/assert-compatible-version.js';
|
|
3
|
+
import { migrateInfoMainContentSelector } from '../../migrate-info-main-content-selector.js';
|
|
3
4
|
import { migrateInfoRoots } from '../../migrate-info-roots.js';
|
|
5
|
+
import { migrateMainContentsColumns } from '../../migrate-main-contents-columns.js';
|
|
4
6
|
/**
|
|
5
7
|
* Initializes the database schema if tables do not exist, then runs the
|
|
6
|
-
*
|
|
8
|
+
* remaining lightweight migrations (`info.roots`, `info.mainContentSelector`,
|
|
9
|
+
* `page_meta.main_content_*`).
|
|
7
10
|
*
|
|
8
|
-
* There is deliberately no per-table
|
|
9
|
-
* `assertCompatibleVersion` (called below, before any schema work)
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* There is deliberately no per-table *table-creation* migration chain here:
|
|
12
|
+
* `assertCompatibleVersion` (called below, before any schema work) rejects
|
|
13
|
+
* every archive older than the current format, so a connection that reaches
|
|
14
|
+
* `initSchema` is either brand new (`initSchema` provisions the full schema)
|
|
15
|
+
* or was produced by `scripts/migrate-to-0.13.mjs` (which guarantees the
|
|
16
|
+
* full table set before it repacks) — and `initSchema` itself re-runs
|
|
17
|
+
* `createEntityTables` / `createAdjunctTables` unconditionally on every
|
|
18
|
+
* open, self-healing any *missing table* via their internal
|
|
19
|
+
* `IF NOT EXISTS` / `hasTable` guards. What that self-healing cannot do is
|
|
20
|
+
* retrofit a *new column* onto an entity table that already exists —
|
|
21
|
+
* `CREATE TABLE IF NOT EXISTS` is a no-op once the table is present. Column
|
|
22
|
+
* additions to an existing 0.13 table are therefore the one case that still
|
|
23
|
+
* needs an explicit `hasColumn`-guarded `ALTER TABLE` here (`migrateInfoRoots`,
|
|
24
|
+
* `migrateMainContentsColumns`) rather than a DDL-string change alone.
|
|
17
25
|
*
|
|
18
26
|
* In read-only mode schema init + migration are SKIPPED so the same DB
|
|
19
27
|
* can be opened safely by a viewer attached to a live (or interrupted)
|
|
@@ -39,4 +47,6 @@ export async function init(knex, readOnly) {
|
|
|
39
47
|
}
|
|
40
48
|
await initSchema(knex);
|
|
41
49
|
await migrateInfoRoots(knex);
|
|
50
|
+
await migrateInfoMainContentSelector(knex);
|
|
51
|
+
await migrateMainContentsColumns(knex);
|
|
42
52
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MainContentAudioRow } from '../../meta/types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves all `page_main_content_audios` rows for the given page id, in
|
|
5
|
+
* DOM traversal order.
|
|
6
|
+
*
|
|
7
|
+
* Read-side counterpart to `insertAudios`.
|
|
8
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
9
|
+
* @param pageId
|
|
10
|
+
*/
|
|
11
|
+
export declare function getAudiosOfPage(knex: Knex, pageId: number): Promise<MainContentAudioRow[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves all `page_main_content_audios` rows for the given page id, in
|
|
3
|
+
* DOM traversal order.
|
|
4
|
+
*
|
|
5
|
+
* Read-side counterpart to `insertAudios`.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @param pageId
|
|
8
|
+
*/
|
|
9
|
+
export async function getAudiosOfPage(knex, pageId) {
|
|
10
|
+
return knex
|
|
11
|
+
.select('id', 'pageId', 'order', 'src')
|
|
12
|
+
.from('page_main_content_audios')
|
|
13
|
+
.where('pageId', pageId)
|
|
14
|
+
.orderBy('order', 'asc');
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MainContentButtonRow } from '../../meta/types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves all `page_main_content_buttons` rows for the given page id, in
|
|
5
|
+
* DOM traversal order.
|
|
6
|
+
*
|
|
7
|
+
* Read-side counterpart to `insertButtons`.
|
|
8
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
9
|
+
* @param pageId
|
|
10
|
+
*/
|
|
11
|
+
export declare function getButtonsOfPage(knex: Knex, pageId: number): Promise<MainContentButtonRow[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves all `page_main_content_buttons` rows for the given page id, in
|
|
3
|
+
* DOM traversal order.
|
|
4
|
+
*
|
|
5
|
+
* Read-side counterpart to `insertButtons`.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @param pageId
|
|
8
|
+
*/
|
|
9
|
+
export async function getButtonsOfPage(knex, pageId) {
|
|
10
|
+
return knex
|
|
11
|
+
.select('id', 'pageId', 'order', 'nodeName', 'role', 'type', 'text', 'disabled')
|
|
12
|
+
.from('page_main_content_buttons')
|
|
13
|
+
.where('pageId', pageId)
|
|
14
|
+
.orderBy('order', 'asc');
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MainContentCanvasRow } from '../../meta/types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves all `page_main_content_canvases` rows for the given page id, in
|
|
5
|
+
* DOM traversal order.
|
|
6
|
+
*
|
|
7
|
+
* Read-side counterpart to `insertCanvases`.
|
|
8
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
9
|
+
* @param pageId
|
|
10
|
+
*/
|
|
11
|
+
export declare function getCanvasesOfPage(knex: Knex, pageId: number): Promise<MainContentCanvasRow[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves all `page_main_content_canvases` rows for the given page id, in
|
|
3
|
+
* DOM traversal order.
|
|
4
|
+
*
|
|
5
|
+
* Read-side counterpart to `insertCanvases`.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @param pageId
|
|
8
|
+
*/
|
|
9
|
+
export async function getCanvasesOfPage(knex, pageId) {
|
|
10
|
+
return knex
|
|
11
|
+
.select('id', 'pageId', 'order', 'width', 'height')
|
|
12
|
+
.from('page_main_content_canvases')
|
|
13
|
+
.where('pageId', pageId)
|
|
14
|
+
.orderBy('order', 'asc');
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MainContentHeadingRow } from '../../meta/types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves all `page_main_content_headings` rows for the given page id, in
|
|
5
|
+
* DOM traversal order.
|
|
6
|
+
*
|
|
7
|
+
* Read-side counterpart to `insertHeadings`.
|
|
8
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
9
|
+
* @param pageId
|
|
10
|
+
*/
|
|
11
|
+
export declare function getHeadingsOfPage(knex: Knex, pageId: number): Promise<MainContentHeadingRow[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves all `page_main_content_headings` rows for the given page id, in
|
|
3
|
+
* DOM traversal order.
|
|
4
|
+
*
|
|
5
|
+
* Read-side counterpart to `insertHeadings`.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @param pageId
|
|
8
|
+
*/
|
|
9
|
+
export async function getHeadingsOfPage(knex, pageId) {
|
|
10
|
+
return knex
|
|
11
|
+
.select('id', 'pageId', 'order', 'text', 'level')
|
|
12
|
+
.from('page_main_content_headings')
|
|
13
|
+
.where('pageId', pageId)
|
|
14
|
+
.orderBy('order', 'asc');
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MainContentIframeRow } from '../../meta/types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves all `page_main_content_iframes` rows for the given page id, in
|
|
5
|
+
* DOM traversal order.
|
|
6
|
+
*
|
|
7
|
+
* Read-side counterpart to `insertIframes`.
|
|
8
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
9
|
+
* @param pageId
|
|
10
|
+
*/
|
|
11
|
+
export declare function getIframesOfPage(knex: Knex, pageId: number): Promise<MainContentIframeRow[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves all `page_main_content_iframes` rows for the given page id, in
|
|
3
|
+
* DOM traversal order.
|
|
4
|
+
*
|
|
5
|
+
* Read-side counterpart to `insertIframes`.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @param pageId
|
|
8
|
+
*/
|
|
9
|
+
export async function getIframesOfPage(knex, pageId) {
|
|
10
|
+
return knex
|
|
11
|
+
.select('id', 'pageId', 'order', 'src', 'title', 'width', 'height')
|
|
12
|
+
.from('page_main_content_iframes')
|
|
13
|
+
.where('pageId', pageId)
|
|
14
|
+
.orderBy('order', 'asc');
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MainContentImageRow } from '../../meta/types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves all `page_main_content_images` rows for the given page id, in
|
|
5
|
+
* DOM traversal order.
|
|
6
|
+
*
|
|
7
|
+
* Read-side counterpart to `insertMainContentImages`.
|
|
8
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
9
|
+
* @param pageId
|
|
10
|
+
*/
|
|
11
|
+
export declare function getMainContentImagesOfPage(knex: Knex, pageId: number): Promise<MainContentImageRow[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves all `page_main_content_images` rows for the given page id, in
|
|
3
|
+
* DOM traversal order.
|
|
4
|
+
*
|
|
5
|
+
* Read-side counterpart to `insertMainContentImages`.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @param pageId
|
|
8
|
+
*/
|
|
9
|
+
export async function getMainContentImagesOfPage(knex, pageId) {
|
|
10
|
+
return knex
|
|
11
|
+
.select('id', 'pageId', 'order', 'src', 'alt')
|
|
12
|
+
.from('page_main_content_images')
|
|
13
|
+
.where('pageId', pageId)
|
|
14
|
+
.orderBy('order', 'asc');
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MainContentTableRow } from '../../meta/types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves all `page_main_content_tables` rows for the given page id, in
|
|
5
|
+
* DOM traversal order.
|
|
6
|
+
*
|
|
7
|
+
* Read-side counterpart to `insertMainContentTables`.
|
|
8
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
9
|
+
* @param pageId
|
|
10
|
+
*/
|
|
11
|
+
export declare function getMainContentTablesOfPage(knex: Knex, pageId: number): Promise<MainContentTableRow[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves all `page_main_content_tables` rows for the given page id, in
|
|
3
|
+
* DOM traversal order.
|
|
4
|
+
*
|
|
5
|
+
* Read-side counterpart to `insertMainContentTables`.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @param pageId
|
|
8
|
+
*/
|
|
9
|
+
export async function getMainContentTablesOfPage(knex, pageId) {
|
|
10
|
+
return knex
|
|
11
|
+
.select('id', 'pageId', 'order', 'rows', 'cols', 'hasHeader', 'hasFooter', 'hasMergedCell')
|
|
12
|
+
.from('page_main_content_tables')
|
|
13
|
+
.where('pageId', pageId)
|
|
14
|
+
.orderBy('order', 'asc');
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MainContentVideoRow } from '../../meta/types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves all `page_main_content_videos` rows for the given page id, in
|
|
5
|
+
* DOM traversal order.
|
|
6
|
+
*
|
|
7
|
+
* Read-side counterpart to `insertVideos`.
|
|
8
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
9
|
+
* @param pageId
|
|
10
|
+
*/
|
|
11
|
+
export declare function getVideosOfPage(knex: Knex, pageId: number): Promise<MainContentVideoRow[]>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves all `page_main_content_videos` rows for the given page id, in
|
|
3
|
+
* DOM traversal order.
|
|
4
|
+
*
|
|
5
|
+
* Read-side counterpart to `insertVideos`.
|
|
6
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
7
|
+
* @param pageId
|
|
8
|
+
*/
|
|
9
|
+
export async function getVideosOfPage(knex, pageId) {
|
|
10
|
+
return knex
|
|
11
|
+
.select('id', 'pageId', 'order', 'src', 'poster', 'width', 'height')
|
|
12
|
+
.from('page_main_content_videos')
|
|
13
|
+
.where('pageId', pageId)
|
|
14
|
+
.orderBy('order', 'asc');
|
|
15
|
+
}
|
|
@@ -36,5 +36,5 @@ export function buildPageQuery(knex) {
|
|
|
36
36
|
.leftJoin('url_refs as og_image_ur', 'og_image_ur.id', 'pm.og_image_url_id')
|
|
37
37
|
.leftJoin('url_refs as twitter_image_ur', 'twitter_image_ur.id', 'pm.twitter_image_url_id')
|
|
38
38
|
.leftJoin('json_refs as extras_ref', 'extras_ref.id', 'pm.meta_extras_json_id')
|
|
39
|
-
.select('ci.id as id', 'ur.url as url', 'ci.redirect_dest_id as redirectDestId', 'ci.scraped as scraped', 'ci.is_target as isTarget', 'ci.is_external as isExternal', 'ci.status as status', 'ci.status_text as statusText', 'ctr.raw as contentType', 'ci.content_length as contentLength', 'ci.header_set_id as headerSetId', 'pm.lang as lang', 'pm.dir as dir', 'pm.charset as charset', 'pm.base_href as baseHref', 'pm.viewport_raw as viewport_raw', 'pm.theme_color as themeColor', 'pm.application_name as applicationName', 'pm.author as author', 'pm.generator as generator', 'pm.publisher as publisher', 'title_ref.text as title', 'description_ref.text as description', 'keywords_ref.text as keywords', 'robots_raw_ref.text as robots_raw', 'pm.robots_noindex as robots_noindex', 'pm.robots_nofollow as robots_nofollow', 'pm.robots_noarchive as robots_noarchive', 'pm.robots_noimageindex as robots_noimageindex', 'pm.googlebot as googlebot', 'canonical_ur.url as canonical', 'amphtml_ur.url as amphtml', 'manifest_ur.url as manifest', 'icon_ur.url as icon_href', 'apple_ur.url as appleTouchIcon_href', 'pm.og_type as og_type', 'og_title_ref.text as og_title', 'og_url_ur.url as og_url', 'pm.og_site_name as og_site_name', 'og_description_ref.text as og_description', 'og_image_ur.url as og_image', 'pm.og_image_alt as og_image_alt', 'pm.og_image_width as og_image_width', 'pm.og_image_height as og_image_height', 'pm.og_locale as og_locale', 'pm.og_article_published_time as og_article_published_time', 'pm.og_article_modified_time as og_article_modified_time', 'pm.twitter_card as twitter_card', 'pm.twitter_site as twitter_site', 'pm.twitter_creator as twitter_creator', 'twitter_title_ref.text as twitter_title', 'twitter_description_ref.text as twitter_description', 'twitter_image_ur.url as twitter_image', 'pm.fb_app_id as fb_app_id', 'pm.verification_google as verification_google', 'pm.format_detection_telephone as formatDetection_telephone', 'ci.first_crawled_at as firstCrawledAt', 'ci.last_crawled_at as lastCrawledAt', 'pm.tag_count as tag_count', 'pm.jsonld_count as jsonld_count', 'pm.tags_providers_csv as tags_providers_csv', 'extras_ref.json_text as extras_body', 'extras_ref.codec as extras_codec', 'ci.is_skipped as isSkipped', 'ci.skip_reason as skipReason', 'ci.crawl_order as order', 'ci.source as source');
|
|
39
|
+
.select('ci.id as id', 'ur.url as url', 'ci.redirect_dest_id as redirectDestId', 'ci.scraped as scraped', 'ci.is_target as isTarget', 'ci.is_external as isExternal', 'ci.status as status', 'ci.status_text as statusText', 'ctr.raw as contentType', 'ci.content_length as contentLength', 'ci.header_set_id as headerSetId', 'pm.lang as lang', 'pm.dir as dir', 'pm.charset as charset', 'pm.base_href as baseHref', 'pm.viewport_raw as viewport_raw', 'pm.theme_color as themeColor', 'pm.application_name as applicationName', 'pm.author as author', 'pm.generator as generator', 'pm.publisher as publisher', 'title_ref.text as title', 'description_ref.text as description', 'keywords_ref.text as keywords', 'robots_raw_ref.text as robots_raw', 'pm.robots_noindex as robots_noindex', 'pm.robots_nofollow as robots_nofollow', 'pm.robots_noarchive as robots_noarchive', 'pm.robots_noimageindex as robots_noimageindex', 'pm.googlebot as googlebot', 'canonical_ur.url as canonical', 'amphtml_ur.url as amphtml', 'manifest_ur.url as manifest', 'icon_ur.url as icon_href', 'apple_ur.url as appleTouchIcon_href', 'pm.og_type as og_type', 'og_title_ref.text as og_title', 'og_url_ur.url as og_url', 'pm.og_site_name as og_site_name', 'og_description_ref.text as og_description', 'og_image_ur.url as og_image', 'pm.og_image_alt as og_image_alt', 'pm.og_image_width as og_image_width', 'pm.og_image_height as og_image_height', 'pm.og_locale as og_locale', 'pm.og_article_published_time as og_article_published_time', 'pm.og_article_modified_time as og_article_modified_time', 'pm.twitter_card as twitter_card', 'pm.twitter_site as twitter_site', 'pm.twitter_creator as twitter_creator', 'twitter_title_ref.text as twitter_title', 'twitter_description_ref.text as twitter_description', 'twitter_image_ur.url as twitter_image', 'pm.fb_app_id as fb_app_id', 'pm.verification_google as verification_google', 'pm.format_detection_telephone as formatDetection_telephone', 'ci.first_crawled_at as firstCrawledAt', 'ci.last_crawled_at as lastCrawledAt', 'pm.tag_count as tag_count', 'pm.jsonld_count as jsonld_count', 'pm.tags_providers_csv as tags_providers_csv', 'pm.main_content_node_name as main_content_node_name', 'pm.main_content_id as main_content_id', 'pm.main_content_role as main_content_role', 'pm.main_content_selector as main_content_selector', 'pm.main_content_class_list as main_content_class_list', 'pm.main_content_word_count as main_content_word_count', 'pm.main_content_body_word_count as main_content_body_word_count', 'pm.main_content_heading_count as main_content_heading_count', 'pm.main_content_image_count as main_content_image_count', 'pm.main_content_table_count as main_content_table_count', 'pm.main_content_button_count as main_content_button_count', 'pm.main_content_iframe_count as main_content_iframe_count', 'pm.main_content_video_count as main_content_video_count', 'pm.main_content_audio_count as main_content_audio_count', 'pm.main_content_canvas_count as main_content_canvas_count', 'pm.scroll_height_desktop as scroll_height_desktop', 'pm.scroll_height_mobile as scroll_height_mobile', 'extras_ref.json_text as extras_body', 'extras_ref.codec as extras_codec', 'ci.is_skipped as isSkipped', 'ci.skip_reason as skipReason', 'ci.crawl_order as order', 'ci.source as source');
|
|
40
40
|
}
|
|
@@ -65,19 +65,20 @@ export async function repromoteExternalPages(knex, scopes, options) {
|
|
|
65
65
|
// demotion.
|
|
66
66
|
});
|
|
67
67
|
// Clear the prior crawl's data for the repromoted pages. `updatePage`
|
|
68
|
-
// also replaces anchor_edges/image_items/tags/jsonld
|
|
69
|
-
// re-scrapes them, but only when the new scrape is non-empty —
|
|
70
|
-
// this pre-clear is still load-bearing for pages that get
|
|
68
|
+
// also replaces anchor_edges/image_items/tags/jsonld/page_main_content_*
|
|
69
|
+
// when it re-scrapes them, but only when the new scrape is non-empty —
|
|
70
|
+
// so this pre-clear is still load-bearing for pages that get
|
|
71
71
|
// repromoted but then re-scrape to nothing (or are never reached
|
|
72
72
|
// again), and it is the only place `resource_ref_edges` is cleared.
|
|
73
73
|
// Deleting the `page_meta` row (rather than nulling every column)
|
|
74
|
-
// clears title / description / og:* / twitter:* / meta_extras
|
|
75
|
-
// one statement; a re-scrape re-inserts it via
|
|
76
|
-
// `ON CONFLICT(page_id) DO UPDATE`. `page_tags` / `page_jsonld`
|
|
77
|
-
// cleared explicitly even though
|
|
78
|
-
// CASCADE — we keep the existing pattern
|
|
79
|
-
// DELETEs rather than relying on CASCADE
|
|
80
|
-
// cascade anyway: the parent
|
|
74
|
+
// clears title / description / og:* / twitter:* / meta_extras /
|
|
75
|
+
// main_content_* in one statement; a re-scrape re-inserts it via
|
|
76
|
+
// `ON CONFLICT(page_id) DO UPDATE`. `page_tags` / `page_jsonld` /
|
|
77
|
+
// `page_main_content_*` are cleared explicitly even though all of
|
|
78
|
+
// them also carry ON DELETE CASCADE — we keep the existing pattern
|
|
79
|
+
// of explicit chunked DELETEs rather than relying on CASCADE
|
|
80
|
+
// indirectly (and would not cascade anyway: the parent
|
|
81
|
+
// `content_items` row is updated, not
|
|
81
82
|
// deleted). Orphan blobs in `page_html_blobs` are left behind; #23
|
|
82
83
|
// will add GC.
|
|
83
84
|
await knex('page_meta').whereIn('page_id', chunk).delete();
|
|
@@ -87,6 +88,14 @@ export async function repromoteExternalPages(knex, scopes, options) {
|
|
|
87
88
|
await knex('page_html_ref').whereIn('page_id', chunk).delete();
|
|
88
89
|
await knex('page_tags').whereIn('pageId', chunk).delete();
|
|
89
90
|
await knex('page_jsonld').whereIn('pageId', chunk).delete();
|
|
91
|
+
await knex('page_main_content_headings').whereIn('pageId', chunk).delete();
|
|
92
|
+
await knex('page_main_content_images').whereIn('pageId', chunk).delete();
|
|
93
|
+
await knex('page_main_content_tables').whereIn('pageId', chunk).delete();
|
|
94
|
+
await knex('page_main_content_buttons').whereIn('pageId', chunk).delete();
|
|
95
|
+
await knex('page_main_content_iframes').whereIn('pageId', chunk).delete();
|
|
96
|
+
await knex('page_main_content_videos').whereIn('pageId', chunk).delete();
|
|
97
|
+
await knex('page_main_content_audios').whereIn('pageId', chunk).delete();
|
|
98
|
+
await knex('page_main_content_canvases').whereIn('pageId', chunk).delete();
|
|
90
99
|
}
|
|
91
100
|
dbLog('Repromoted %d external pages back to pending', promotedUrls.length);
|
|
92
101
|
return promotedUrls;
|
|
@@ -35,9 +35,11 @@ import type { Knex } from 'knex';
|
|
|
35
35
|
* The page row itself is kept (id preserved) so existing
|
|
36
36
|
* `anchor_edges.href_page_id` referrers stay valid, and `is_external` is
|
|
37
37
|
* left untouched so the next pass re-classifies each page from the crawl
|
|
38
|
-
* scope. Related `anchor_edges`, `image_items`, `resource_ref_edges`,
|
|
39
|
-
* `page_errors`
|
|
40
|
-
* without duplicates
|
|
38
|
+
* scope. Related `anchor_edges`, `image_items`, `resource_ref_edges`,
|
|
39
|
+
* `page_errors`, and the `page_main_content_*` child tables are deleted so
|
|
40
|
+
* the re-scrape can re-insert fresh data without duplicates — kept in sync
|
|
41
|
+
* with the `page_meta` row deletion above so a reset page's `main_content_*`
|
|
42
|
+
* counts and its child-table detail never disagree.
|
|
41
43
|
*
|
|
42
44
|
* SELECT and UPDATE/DELETE statements are chunked to stay below SQLite's
|
|
43
45
|
* `SQLITE_LIMIT_VARIABLE_NUMBER`.
|