@nitpicker/crawler 0.15.0 → 0.17.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.d.ts +52 -1
- package/lib/archive/archive.js +68 -0
- package/lib/archive/create-adjunct-tables.d.ts +3 -0
- package/lib/archive/create-adjunct-tables.js +43 -0
- package/lib/archive/create-entity-tables.js +10 -0
- package/lib/archive/database.d.ts +40 -1
- package/lib/archive/database.js +54 -0
- package/lib/archive/db-ops/config/get-config.js +1 -0
- package/lib/archive/db-ops/dedupe-cap/accumulate-dedupe-cap-rejected-count.d.ts +18 -0
- package/lib/archive/db-ops/dedupe-cap/accumulate-dedupe-cap-rejected-count.js +23 -0
- package/lib/archive/db-ops/dedupe-cap/finalize-dedupe-cap-event.d.ts +12 -0
- package/lib/archive/db-ops/dedupe-cap/finalize-dedupe-cap-event.js +15 -0
- package/lib/archive/db-ops/dedupe-cap/insert-dedupe-cap-event.d.ts +14 -0
- package/lib/archive/db-ops/dedupe-cap/insert-dedupe-cap-event.js +30 -0
- package/lib/archive/db-ops/dedupe-cap/list-dedupe-cap-shape-keys.d.ts +21 -0
- package/lib/archive/db-ops/dedupe-cap/list-dedupe-cap-shape-keys.js +27 -0
- package/lib/archive/db-ops/inventory/record-inventory-run.js +1 -0
- package/lib/archive/db-ops/lifecycle/init.d.ts +4 -2
- package/lib/archive/db-ops/lifecycle/init.js +12 -2
- package/lib/archive/db-ops/pages/write/insert-inventory-content-items.d.ts +38 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-content-items.js +59 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-seeds.d.ts +5 -6
- package/lib/archive/db-ops/pages/write/insert-inventory-seeds.js +17 -41
- package/lib/archive/db-ops/pages/write/insert-inventory-skipped-pages.d.ts +42 -0
- package/lib/archive/db-ops/pages/write/insert-inventory-skipped-pages.js +56 -0
- package/lib/archive/migrate-content-items-dedupe-cap-event-id.d.ts +41 -0
- package/lib/archive/migrate-content-items-dedupe-cap-event-id.js +51 -0
- package/lib/archive/migrate-entity-tables.d.ts +10 -0
- package/lib/archive/migrate-entity-tables.js +10 -0
- package/lib/archive/migrate-inventory-runs-exclude-skipped.d.ts +20 -0
- package/lib/archive/migrate-inventory-runs-exclude-skipped.js +33 -0
- package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.d.ts +7 -0
- package/lib/archive/populate-entity-tables/test-utils/setup-entities-db.js +9 -0
- package/lib/archive/types.d.ts +16 -0
- package/lib/classify-error-kind.d.ts +1 -0
- package/lib/classify-error-kind.js +14 -0
- package/lib/crawler/assert-chrome-installed.d.ts +24 -0
- package/lib/crawler/assert-chrome-installed.js +43 -0
- package/lib/crawler/crawler.d.ts +12 -0
- package/lib/crawler/crawler.js +239 -29
- package/lib/crawler/decode-auth-credential.d.ts +29 -0
- package/lib/crawler/decode-auth-credential.js +39 -0
- package/lib/crawler/dedupe/compute-meta-signature.d.ts +30 -0
- package/lib/crawler/dedupe/compute-meta-signature.js +0 -0
- package/lib/crawler/dedupe/compute-shape-key.d.ts +37 -0
- package/lib/crawler/dedupe/compute-shape-key.js +56 -0
- package/lib/crawler/dedupe/dedupe-cap-tracker.d.ts +84 -0
- package/lib/crawler/dedupe/dedupe-cap-tracker.js +185 -0
- package/lib/crawler/dedupe/is-predicted-content-duplicate.d.ts +24 -0
- package/lib/crawler/dedupe/is-predicted-content-duplicate.js +26 -0
- package/lib/crawler/dedupe/is-shape-capped.d.ts +10 -0
- package/lib/crawler/dedupe/is-shape-capped.js +12 -0
- package/lib/crawler/dedupe/resolve-og-url-mismatch.d.ts +31 -0
- package/lib/crawler/dedupe/resolve-og-url-mismatch.js +40 -0
- package/lib/crawler/dedupe/types.d.ts +42 -0
- package/lib/crawler/dedupe/types.js +1 -0
- package/lib/crawler/fetch-destination.js +14 -2
- package/lib/crawler/generate-predicted-urls.d.ts +12 -0
- package/lib/crawler/generate-predicted-urls.js +33 -2
- package/lib/crawler/is-puppeteer-fallback-candidate.js +3 -0
- package/lib/crawler/types.d.ts +38 -0
- package/lib/crawler-orchestrator.d.ts +39 -10
- package/lib/crawler-orchestrator.js +187 -23
- package/lib/crawler.d.ts +2 -0
- package/lib/crawler.js +2 -0
- package/lib/permanent-error-kinds.d.ts +9 -4
- package/lib/permanent-error-kinds.js +10 -4
- package/lib/types.d.ts +4 -1
- package/package.json +3 -3
package/lib/archive/archive.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TemplateClusterReason } from './db-ops/analysis/types.js';
|
|
2
|
-
import type { Config, InsertNetworkOutageParams, InventoryRunMeta, PageSource } from './types.js';
|
|
2
|
+
import type { Config, InsertDedupeCapEventParams, InsertNetworkOutageParams, InventoryRunMeta, PageSource } from './types.js';
|
|
3
3
|
import type { OutageWindow } from '../is-within-outage-window.js';
|
|
4
4
|
import type { PageData, CrawlerError, Resource } from '../utils/types/types.js';
|
|
5
5
|
import type { ConsoleLogEntry } from '@d-zero/beholder';
|
|
@@ -49,6 +49,17 @@ export default class Archive extends ArchiveAccessor {
|
|
|
49
49
|
* @deprecated This method is no longer functional.
|
|
50
50
|
*/
|
|
51
51
|
abort(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Adds onto the `rejected_count` of a shape's `dedupe_cap_events` row,
|
|
54
|
+
* looked up by `shape_key` rather than `id` — used for a shape that
|
|
55
|
+
* capped in an earlier session (preloaded into `DedupeCapTracker`'s
|
|
56
|
+
* sticky set) and so has no event id from the current session.
|
|
57
|
+
*
|
|
58
|
+
* Thin facade over {@link Database.accumulateDedupeCapRejectedCount}.
|
|
59
|
+
* @param shapeKey - The capped shape whose rejection count to accumulate.
|
|
60
|
+
* @param rejectedCount - Additional anchors rejected for this shape in the current session.
|
|
61
|
+
*/
|
|
62
|
+
accumulateDedupeCapRejectedCount(shapeKey: string, rejectedCount: number): Promise<void>;
|
|
52
63
|
/**
|
|
53
64
|
* Records a crawler-level error to both the human-readable `error.log` (full
|
|
54
65
|
* stack, for debugging) and the structured `crawl_errors` table (queryable,
|
|
@@ -80,6 +91,15 @@ export default class Archive extends ArchiveAccessor {
|
|
|
80
91
|
* @param endedAt - Epoch ms the outage is considered to have ended.
|
|
81
92
|
*/
|
|
82
93
|
closeNetworkOutage(id: number, endedAt: number): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Finalizes a `dedupe_cap_events` row by stamping `rejected_count` — a
|
|
96
|
+
* no-op if already finalized.
|
|
97
|
+
*
|
|
98
|
+
* Thin facade over {@link Database.finalizeDedupeCapEvent}.
|
|
99
|
+
* @param id - The `dedupe_cap_events.id` to finalize.
|
|
100
|
+
* @param rejectedCount - Number of anchors rejected for this shape after it capped.
|
|
101
|
+
*/
|
|
102
|
+
finalizeDedupeCapEvent(id: number, rejectedCount: number): Promise<void>;
|
|
83
103
|
/**
|
|
84
104
|
* Retrieves the current crawling state, including lists of scraped and pending URLs.
|
|
85
105
|
* @returns An object with `scraped` and `pending` URL arrays.
|
|
@@ -136,6 +156,15 @@ export default class Archive extends ArchiveAccessor {
|
|
|
136
156
|
* @returns The base URL string.
|
|
137
157
|
*/
|
|
138
158
|
getUrl(): Promise<string>;
|
|
159
|
+
/**
|
|
160
|
+
* Appends one row (`rejected_count = NULL`) to the `dedupe_cap_events`
|
|
161
|
+
* journal.
|
|
162
|
+
*
|
|
163
|
+
* Thin facade over {@link Database.insertDedupeCapEvent}.
|
|
164
|
+
* @param params - The newly-capped shape's fields to record.
|
|
165
|
+
* @returns The autoincremented `id` of the inserted row.
|
|
166
|
+
*/
|
|
167
|
+
insertDedupeCapEvent(params: InsertDedupeCapEventParams): Promise<number>;
|
|
139
168
|
/**
|
|
140
169
|
* Pre-insert inventory non-HTML URLs as `source='inventory-seed'`
|
|
141
170
|
* placeholders in the `resources` table — the non-HTML counterpart of
|
|
@@ -165,6 +194,19 @@ export default class Archive extends ArchiveAccessor {
|
|
|
165
194
|
* @param urls - HTML seed URLs to pre-insert. No-op when empty.
|
|
166
195
|
*/
|
|
167
196
|
insertInventorySeeds(urls: readonly ExURL[]): Promise<void>;
|
|
197
|
+
/**
|
|
198
|
+
* Records exclude-matched inventory URLs as terminal skipped pages —
|
|
199
|
+
* the same `is_skipped=1, skip_reason='excluded'` state the normal
|
|
200
|
+
* crawl's fetch-time gate writes for link-discovered excluded URLs,
|
|
201
|
+
* labelled `source='inventory-seed'`. Thin facade over
|
|
202
|
+
* {@link Database.insertInventorySkippedPages} — see the underlying
|
|
203
|
+
* op's JSDoc for the parity rationale and crawled-wins safety.
|
|
204
|
+
*
|
|
205
|
+
* `ExURL` inputs are normalised to `withoutHashAndAuth` here for the
|
|
206
|
+
* same storage-key consistency reason as {@link insertInventorySeeds}.
|
|
207
|
+
* @param urls - Exclude-matched URLs to record. No-op when empty.
|
|
208
|
+
*/
|
|
209
|
+
insertInventorySkippedPages(urls: readonly ExURL[]): Promise<void>;
|
|
168
210
|
/**
|
|
169
211
|
* Appends one open row to the `network_outages` journal.
|
|
170
212
|
*
|
|
@@ -174,6 +216,15 @@ export default class Archive extends ArchiveAccessor {
|
|
|
174
216
|
* @returns The autoincremented `id` of the inserted row.
|
|
175
217
|
*/
|
|
176
218
|
insertNetworkOutage(params: InsertNetworkOutageParams): Promise<number>;
|
|
219
|
+
/**
|
|
220
|
+
* Every distinct `dedupe_cap_events.shape_key` recorded in this archive.
|
|
221
|
+
* Consumed by `CrawlerOrchestrator` to preload `DedupeCapTracker`'s
|
|
222
|
+
* sticky set on `--resume` / `--append` / `--retry-failed` /
|
|
223
|
+
* `--inventory`, mirroring {@link listDnsBurnedHostCandidates}'s
|
|
224
|
+
* writer-only exposure.
|
|
225
|
+
* @returns Distinct shape keys already confirmed capped.
|
|
226
|
+
*/
|
|
227
|
+
listDedupeCapShapeKeys(): Promise<string[]>;
|
|
177
228
|
/**
|
|
178
229
|
* Hostnames whose `crawl_errors` history is consistently DNS failures and
|
|
179
230
|
* for which no recent 2xx/3xx page or resource is recorded. Consumed by
|
package/lib/archive/archive.js
CHANGED
|
@@ -94,6 +94,20 @@ export default class Archive extends ArchiveAccessor {
|
|
|
94
94
|
* @deprecated This method is no longer functional.
|
|
95
95
|
*/
|
|
96
96
|
abort() { }
|
|
97
|
+
/**
|
|
98
|
+
* Adds onto the `rejected_count` of a shape's `dedupe_cap_events` row,
|
|
99
|
+
* looked up by `shape_key` rather than `id` — used for a shape that
|
|
100
|
+
* capped in an earlier session (preloaded into `DedupeCapTracker`'s
|
|
101
|
+
* sticky set) and so has no event id from the current session.
|
|
102
|
+
*
|
|
103
|
+
* Thin facade over {@link Database.accumulateDedupeCapRejectedCount}.
|
|
104
|
+
* @param shapeKey - The capped shape whose rejection count to accumulate.
|
|
105
|
+
* @param rejectedCount - Additional anchors rejected for this shape in the current session.
|
|
106
|
+
*/
|
|
107
|
+
async accumulateDedupeCapRejectedCount(shapeKey, rejectedCount) {
|
|
108
|
+
dbLog('Accumulate dedupe cap rejected count shapeKey=%s rejectedCount=%d', shapeKey, rejectedCount);
|
|
109
|
+
return await this.#db.accumulateDedupeCapRejectedCount(shapeKey, rejectedCount);
|
|
110
|
+
}
|
|
97
111
|
/**
|
|
98
112
|
* Records a crawler-level error to both the human-readable `error.log` (full
|
|
99
113
|
* stack, for debugging) and the structured `crawl_errors` table (queryable,
|
|
@@ -135,6 +149,18 @@ export default class Archive extends ArchiveAccessor {
|
|
|
135
149
|
dbLog('Close network outage id=%d endedAt=%d', id, endedAt);
|
|
136
150
|
return await this.#db.closeNetworkOutage(id, endedAt);
|
|
137
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Finalizes a `dedupe_cap_events` row by stamping `rejected_count` — a
|
|
154
|
+
* no-op if already finalized.
|
|
155
|
+
*
|
|
156
|
+
* Thin facade over {@link Database.finalizeDedupeCapEvent}.
|
|
157
|
+
* @param id - The `dedupe_cap_events.id` to finalize.
|
|
158
|
+
* @param rejectedCount - Number of anchors rejected for this shape after it capped.
|
|
159
|
+
*/
|
|
160
|
+
async finalizeDedupeCapEvent(id, rejectedCount) {
|
|
161
|
+
dbLog('Finalize dedupe cap event id=%d rejectedCount=%d', id, rejectedCount);
|
|
162
|
+
return await this.#db.finalizeDedupeCapEvent(id, rejectedCount);
|
|
163
|
+
}
|
|
138
164
|
/**
|
|
139
165
|
* Retrieves the current crawling state, including lists of scraped and pending URLs.
|
|
140
166
|
* @returns An object with `scraped` and `pending` URL arrays.
|
|
@@ -202,6 +228,18 @@ export default class Archive extends ArchiveAccessor {
|
|
|
202
228
|
async getUrl() {
|
|
203
229
|
return this.#db.getBaseUrl();
|
|
204
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Appends one row (`rejected_count = NULL`) to the `dedupe_cap_events`
|
|
233
|
+
* journal.
|
|
234
|
+
*
|
|
235
|
+
* Thin facade over {@link Database.insertDedupeCapEvent}.
|
|
236
|
+
* @param params - The newly-capped shape's fields to record.
|
|
237
|
+
* @returns The autoincremented `id` of the inserted row.
|
|
238
|
+
*/
|
|
239
|
+
async insertDedupeCapEvent(params) {
|
|
240
|
+
dbLog('Insert dedupe cap event: shapeKey=%s', params.shapeKey);
|
|
241
|
+
return await this.#db.insertDedupeCapEvent(params);
|
|
242
|
+
}
|
|
205
243
|
/**
|
|
206
244
|
* Pre-insert inventory non-HTML URLs as `source='inventory-seed'`
|
|
207
245
|
* placeholders in the `resources` table — the non-HTML counterpart of
|
|
@@ -243,6 +281,25 @@ export default class Archive extends ArchiveAccessor {
|
|
|
243
281
|
dbLog('Insert inventory seeds: %d URL(s)', urls.length);
|
|
244
282
|
await this.#db.insertInventorySeeds(urls.map((u) => u.withoutHashAndAuth));
|
|
245
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Records exclude-matched inventory URLs as terminal skipped pages —
|
|
286
|
+
* the same `is_skipped=1, skip_reason='excluded'` state the normal
|
|
287
|
+
* crawl's fetch-time gate writes for link-discovered excluded URLs,
|
|
288
|
+
* labelled `source='inventory-seed'`. Thin facade over
|
|
289
|
+
* {@link Database.insertInventorySkippedPages} — see the underlying
|
|
290
|
+
* op's JSDoc for the parity rationale and crawled-wins safety.
|
|
291
|
+
*
|
|
292
|
+
* `ExURL` inputs are normalised to `withoutHashAndAuth` here for the
|
|
293
|
+
* same storage-key consistency reason as {@link insertInventorySeeds}.
|
|
294
|
+
* @param urls - Exclude-matched URLs to record. No-op when empty.
|
|
295
|
+
*/
|
|
296
|
+
async insertInventorySkippedPages(urls) {
|
|
297
|
+
if (urls.length === 0) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
dbLog('Insert inventory skipped pages: %d URL(s)', urls.length);
|
|
301
|
+
await this.#db.insertInventorySkippedPages(urls.map((u) => u.withoutHashAndAuth));
|
|
302
|
+
}
|
|
246
303
|
/**
|
|
247
304
|
* Appends one open row to the `network_outages` journal.
|
|
248
305
|
*
|
|
@@ -255,6 +312,17 @@ export default class Archive extends ArchiveAccessor {
|
|
|
255
312
|
dbLog('Insert network outage: startedAt=%d probeHost=%s', params.startedAt, params.probeHost);
|
|
256
313
|
return await this.#db.insertNetworkOutage(params);
|
|
257
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Every distinct `dedupe_cap_events.shape_key` recorded in this archive.
|
|
317
|
+
* Consumed by `CrawlerOrchestrator` to preload `DedupeCapTracker`'s
|
|
318
|
+
* sticky set on `--resume` / `--append` / `--retry-failed` /
|
|
319
|
+
* `--inventory`, mirroring {@link listDnsBurnedHostCandidates}'s
|
|
320
|
+
* writer-only exposure.
|
|
321
|
+
* @returns Distinct shape keys already confirmed capped.
|
|
322
|
+
*/
|
|
323
|
+
async listDedupeCapShapeKeys() {
|
|
324
|
+
return this.#db.listDedupeCapShapeKeys();
|
|
325
|
+
}
|
|
258
326
|
/**
|
|
259
327
|
* Hostnames whose `crawl_errors` history is consistently DNS failures and
|
|
260
328
|
* for which no recent 2xx/3xx page or resource is recorded. Consumed by
|
|
@@ -16,6 +16,9 @@ import type { Knex } from 'knex';
|
|
|
16
16
|
* - `inventory_runs` — `--inventory` audit log (no FK; append-only)
|
|
17
17
|
* - `network_outages` — operator-network-outage journal (no FK; append-only
|
|
18
18
|
* except `ended_at`, which is written once on recovery)
|
|
19
|
+
* - `dedupe_cap_events` — `--dedupe-cap` same-cluster soft-cap audit log (no
|
|
20
|
+
* FK; append-only except `rejected_count`, which is written once at
|
|
21
|
+
* `crawlEnd`)
|
|
19
22
|
* - `analysis_text_refs` + `analysis_violations` — analyze-phase findings,
|
|
20
23
|
* FK → `content_items(id)`
|
|
21
24
|
* - `page_templates` — DOM-structure template classification (`--templates`,
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
* - `inventory_runs` — `--inventory` audit log (no FK; append-only)
|
|
16
16
|
* - `network_outages` — operator-network-outage journal (no FK; append-only
|
|
17
17
|
* except `ended_at`, which is written once on recovery)
|
|
18
|
+
* - `dedupe_cap_events` — `--dedupe-cap` same-cluster soft-cap audit log (no
|
|
19
|
+
* FK; append-only except `rejected_count`, which is written once at
|
|
20
|
+
* `crawlEnd`)
|
|
18
21
|
* - `analysis_text_refs` + `analysis_violations` — analyze-phase findings,
|
|
19
22
|
* FK → `content_items(id)`
|
|
20
23
|
* - `page_templates` — DOM-structure template classification (`--templates`,
|
|
@@ -290,6 +293,7 @@ export async function createAdjunctTables(instance) {
|
|
|
290
293
|
t.integer('new_pages').nullable();
|
|
291
294
|
t.integer('new_resources').nullable();
|
|
292
295
|
t.integer('scope_skipped').nullable();
|
|
296
|
+
t.integer('exclude_skipped').nullable();
|
|
293
297
|
t.integer('invalid_skipped').nullable();
|
|
294
298
|
t.text('notes').nullable();
|
|
295
299
|
t.index('ran_at');
|
|
@@ -326,6 +330,45 @@ export async function createAdjunctTables(instance) {
|
|
|
326
330
|
t.integer('trigger_host_count').notNullable();
|
|
327
331
|
});
|
|
328
332
|
}
|
|
333
|
+
if (!(await instance.schema.hasTable('dedupe_cap_events'))) {
|
|
334
|
+
await instance.schema.createTable('dedupe_cap_events', (t) => {
|
|
335
|
+
// One row per URL shape the `--dedupe-cap` same-cluster soft cap
|
|
336
|
+
// (`DedupeCapTracker`) confirmed as a trap during this crawl. No
|
|
337
|
+
// index: a crawl produces at most a handful of these rows (same
|
|
338
|
+
// reasoning as `network_outages`, above).
|
|
339
|
+
t.increments('id');
|
|
340
|
+
// The URL shape key (`computeShapeKey`) that capped — a template
|
|
341
|
+
// with placeholders (e.g. `example.com/news/date/{n}/`), not a
|
|
342
|
+
// literal URL.
|
|
343
|
+
t.string('shape_key').notNullable();
|
|
344
|
+
// One concrete URL matching this shape, captured at cap time so a
|
|
345
|
+
// human reading the audit log can identify what was being
|
|
346
|
+
// crawled — `shape_key` alone is a template, not a navigable URL.
|
|
347
|
+
t.string('sample_url').notNullable();
|
|
348
|
+
// `computeBodyHash` result recorded at cap time. Nullable only in
|
|
349
|
+
// the sense that BLOB columns are nullable by default; every row
|
|
350
|
+
// this feature writes populates it (a page with no rendered
|
|
351
|
+
// `<body>` never reaches the tracker — see `Crawler#handleResult`).
|
|
352
|
+
t.binary('body_hash').nullable();
|
|
353
|
+
// The Misra-Gries threshold that actually triggered the cap,
|
|
354
|
+
// after halving for the `body_hash`-match / `og:url`-mismatch
|
|
355
|
+
// confidence signals — NOT necessarily equal to `--dedupe-cap`'s
|
|
356
|
+
// raw value.
|
|
357
|
+
t.integer('effective_threshold').notNullable();
|
|
358
|
+
// The tracker's Misra-Gries counter value at cap time: a LOWER
|
|
359
|
+
// BOUND on the number of matching-signature pages seen for this
|
|
360
|
+
// shape, not an exact observation count (see `DedupeCapTracker`).
|
|
361
|
+
t.integer('observed_count').notNullable();
|
|
362
|
+
t.integer('detected_at').notNullable();
|
|
363
|
+
// NULL until `crawlEnd` finalizes it (see
|
|
364
|
+
// `Crawler#getDedupeCapRejections`). Unlike `network_outages.ended_at`,
|
|
365
|
+
// a NULL here has no ambiguous "still ongoing" reading — a
|
|
366
|
+
// crawl that never reached `crawlEnd` simply left the count
|
|
367
|
+
// undetermined, so no boot-time reconciliation pass is needed
|
|
368
|
+
// (readers display "unknown", not "0" or "unbounded").
|
|
369
|
+
t.integer('rejected_count').nullable();
|
|
370
|
+
});
|
|
371
|
+
}
|
|
329
372
|
if (!(await instance.schema.hasTable('analysis_text_refs'))) {
|
|
330
373
|
await instance.raw(`
|
|
331
374
|
CREATE TABLE analysis_text_refs (
|
|
@@ -198,6 +198,7 @@ export async function createEntityTables(instance) {
|
|
|
198
198
|
header_set_id INTEGER REFERENCES header_sets(id),
|
|
199
199
|
redirect_dest_id INTEGER REFERENCES content_items(id) DEFERRABLE INITIALLY DEFERRED,
|
|
200
200
|
alias_of_id INTEGER REFERENCES content_items(id) DEFERRABLE INITIALLY DEFERRED,
|
|
201
|
+
dedupe_cap_event_id INTEGER REFERENCES dedupe_cap_events(id) DEFERRABLE INITIALLY DEFERRED,
|
|
201
202
|
source TEXT NOT NULL DEFAULT 'crawled',
|
|
202
203
|
first_crawled_at INTEGER,
|
|
203
204
|
last_crawled_at INTEGER,
|
|
@@ -218,6 +219,15 @@ export async function createEntityTables(instance) {
|
|
|
218
219
|
// `migrateContentItemsAliasOfId` instead, which runs after the
|
|
219
220
|
// column-add guard for both fresh and legacy archives (same reasoning as
|
|
220
221
|
// `page_meta.body_hash`'s index).
|
|
222
|
+
//
|
|
223
|
+
// `dedupe_cap_event_id` gets no index anywhere, not even in its own
|
|
224
|
+
// migration (`migrateContentItemsDedupeCapEventId`) — unlike
|
|
225
|
+
// `alias_of_id`, there is no known hot read path filtering on this
|
|
226
|
+
// column yet (`--dedupe-cap` is opt-in and the marked row count is
|
|
227
|
+
// small: capped shapes × matching URLs). Adding a speculative index
|
|
228
|
+
// without a measured query to justify it violates this archive's
|
|
229
|
+
// "no speculative index" rule; add one later with `EXPLAIN QUERY PLAN`
|
|
230
|
+
// evidence if a real hot path emerges.
|
|
221
231
|
await instance.raw('CREATE INDEX IF NOT EXISTS idx_content_items_content_type_id ON content_items(content_type_id)');
|
|
222
232
|
await instance.raw('CREATE INDEX IF NOT EXISTS idx_content_items_crawl_order ON content_items(crawl_order)');
|
|
223
233
|
await instance.raw('CREATE INDEX IF NOT EXISTS idx_content_items_source ON content_items(source)');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TemplateClusterReason } from './db-ops/analysis/types.js';
|
|
2
2
|
import type { JsonLdRow, MainContentAudioRow, MainContentButtonRow, MainContentCanvasRow, MainContentHeadingRow, MainContentIframeRow, MainContentImageRow, MainContentTableRow, MainContentVideoRow, TagRow } from './meta/types.js';
|
|
3
|
-
import type { Config, DatabaseOption, DB_Redirect, DB_Resource, DatabaseEvent, InsertNetworkOutageParams, InventoryRunMeta, PageFilter, PageSource } from './types.js';
|
|
3
|
+
import type { Config, DatabaseOption, DB_Redirect, DB_Resource, DatabaseEvent, InsertDedupeCapEventParams, InsertNetworkOutageParams, InventoryRunMeta, PageFilter, PageSource } from './types.js';
|
|
4
4
|
import type { OutageWindow } from '../is-within-outage-window.js';
|
|
5
5
|
import type { PageData, Resource } from '../utils/types/types.js';
|
|
6
6
|
import type { ConsoleLogEntry } from '@d-zero/beholder';
|
|
@@ -32,6 +32,14 @@ import { TypedAwaitEventEmitter as EventEmitter } from '@d-zero/shared/typed-awa
|
|
|
32
32
|
export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
33
33
|
#private;
|
|
34
34
|
private constructor();
|
|
35
|
+
/**
|
|
36
|
+
* Adds onto the `rejected_count` of a shape's `dedupe_cap_events` row,
|
|
37
|
+
* looked up by `shape_key` rather than `id`. Delegates to
|
|
38
|
+
* {@link accumulateDedupeCapRejectedCountOp}.
|
|
39
|
+
* @param shapeKey - The capped shape whose rejection count to accumulate.
|
|
40
|
+
* @param rejectedCount - Additional anchors rejected for this shape in the current session.
|
|
41
|
+
*/
|
|
42
|
+
accumulateDedupeCapRejectedCount(shapeKey: string, rejectedCount: number): Promise<void>;
|
|
35
43
|
/**
|
|
36
44
|
* Forces a WAL checkpoint, writing all pending WAL data back to the main
|
|
37
45
|
* database file. Delegates to {@link checkpointOp}.
|
|
@@ -49,6 +57,14 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
49
57
|
* Delegates to {@link destroyOp}.
|
|
50
58
|
*/
|
|
51
59
|
destroy(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Finalizes a `dedupe_cap_events` row by stamping `rejected_count` — a
|
|
62
|
+
* no-op if the row is already finalized. Delegates to
|
|
63
|
+
* {@link finalizeDedupeCapEventOp}.
|
|
64
|
+
* @param id - The `dedupe_cap_events.id` to finalize.
|
|
65
|
+
* @param rejectedCount - Number of anchors rejected for this shape after it capped.
|
|
66
|
+
*/
|
|
67
|
+
finalizeDedupeCapEvent(id: number, rejectedCount: number): Promise<void>;
|
|
52
68
|
/**
|
|
53
69
|
* Retrieves all anchors (outgoing links) on a specific page.
|
|
54
70
|
* Delegates to {@link getAnchorsOnPageOp}.
|
|
@@ -273,6 +289,13 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
273
289
|
* @param isExternal - Whether the URL is external to the crawl scope.
|
|
274
290
|
*/
|
|
275
291
|
insertCrawlError(url: string | null, message: string, isExternal?: boolean): Promise<void>;
|
|
292
|
+
/**
|
|
293
|
+
* Appends one row (`rejected_count = NULL`) to the `dedupe_cap_events`
|
|
294
|
+
* journal. Delegates to {@link insertDedupeCapEventOp}.
|
|
295
|
+
* @param params - The newly-capped shape's fields to record.
|
|
296
|
+
* @returns The autoincremented `id` of the newly-inserted row.
|
|
297
|
+
*/
|
|
298
|
+
insertDedupeCapEvent(params: InsertDedupeCapEventParams): Promise<number>;
|
|
276
299
|
/**
|
|
277
300
|
* Pre-insert inventory non-HTML URLs into `resources` as placeholder rows.
|
|
278
301
|
* Delegates to {@link insertInventoryResourcesOp}.
|
|
@@ -287,6 +310,15 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
287
310
|
* @param urls - URL strings already in `withoutHashAndAuth` form.
|
|
288
311
|
*/
|
|
289
312
|
insertInventorySeeds(urls: readonly string[]): Promise<void>;
|
|
313
|
+
/**
|
|
314
|
+
* Records exclude-matched inventory URLs as terminal skipped pages
|
|
315
|
+
* (`scraped=1`, `is_skipped=1`, `skip_reason='excluded'`,
|
|
316
|
+
* `source='inventory-seed'`). Delegates to
|
|
317
|
+
* {@link insertInventorySkippedPagesOp} — see that op's JSDoc for the
|
|
318
|
+
* normal-crawl parity rationale.
|
|
319
|
+
* @param urls - URL strings already in `withoutHashAndAuth` form.
|
|
320
|
+
*/
|
|
321
|
+
insertInventorySkippedPages(urls: readonly string[]): Promise<void>;
|
|
290
322
|
/**
|
|
291
323
|
* Appends one open (`ended_at = NULL`) row to the `network_outages`
|
|
292
324
|
* journal. Delegates to {@link insertNetworkOutageOp}.
|
|
@@ -317,6 +349,13 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
317
349
|
* @param pageUrl - The URL of the page that references the resource.
|
|
318
350
|
*/
|
|
319
351
|
insertResourceReferrers(src: string, pageUrl: string): Promise<void>;
|
|
352
|
+
/**
|
|
353
|
+
* Every distinct `dedupe_cap_events.shape_key` recorded in this archive.
|
|
354
|
+
* Delegates to {@link listDedupeCapShapeKeysOp}.
|
|
355
|
+
* @returns Distinct shape keys already confirmed capped, or `[]` on an
|
|
356
|
+
* archive that predates `dedupe_cap_events` or has recorded none.
|
|
357
|
+
*/
|
|
358
|
+
listDedupeCapShapeKeys(): Promise<string[]>;
|
|
320
359
|
/**
|
|
321
360
|
* Hostnames whose `crawl_errors` history is consistently DNS failures and
|
|
322
361
|
* for which no recent 2xx-3xx page or resource is recorded.
|
package/lib/archive/database.js
CHANGED
|
@@ -16,6 +16,10 @@ import { getName as getNameOp } from './db-ops/config/get-name.js';
|
|
|
16
16
|
import { setConfig as setConfigOp } from './db-ops/config/set-config.js';
|
|
17
17
|
import { updateConfig as updateConfigOp } from './db-ops/config/update-config.js';
|
|
18
18
|
import { replaceConsoleLogs as replaceConsoleLogsOp } from './db-ops/console-logs/replace-console-logs.js';
|
|
19
|
+
import { accumulateDedupeCapRejectedCount as accumulateDedupeCapRejectedCountOp } from './db-ops/dedupe-cap/accumulate-dedupe-cap-rejected-count.js';
|
|
20
|
+
import { finalizeDedupeCapEvent as finalizeDedupeCapEventOp } from './db-ops/dedupe-cap/finalize-dedupe-cap-event.js';
|
|
21
|
+
import { insertDedupeCapEvent as insertDedupeCapEventOp } from './db-ops/dedupe-cap/insert-dedupe-cap-event.js';
|
|
22
|
+
import { listDedupeCapShapeKeys as listDedupeCapShapeKeysOp } from './db-ops/dedupe-cap/list-dedupe-cap-shape-keys.js';
|
|
19
23
|
import { insertCrawlError as insertCrawlErrorOp } from './db-ops/errors/insert-crawl-error.js';
|
|
20
24
|
import { insertPageError as insertPageErrorOp } from './db-ops/errors/insert-page-error.js';
|
|
21
25
|
import { listDnsBurnedHostCandidates as listDnsBurnedHostCandidatesOp } from './db-ops/errors/list-dns-burned-host-candidates.js';
|
|
@@ -48,6 +52,7 @@ import { getScrapedHtmlPageCount as getScrapedHtmlPageCountOp } from './db-ops/p
|
|
|
48
52
|
import { repromoteExternalPages as repromoteExternalPagesOp } from './db-ops/pages/reset/repromote-external-pages.js';
|
|
49
53
|
import { resetFailedPages as resetFailedPagesOp } from './db-ops/pages/reset/reset-failed-pages.js';
|
|
50
54
|
import { insertInventorySeeds as insertInventorySeedsOp } from './db-ops/pages/write/insert-inventory-seeds.js';
|
|
55
|
+
import { insertInventorySkippedPages as insertInventorySkippedPagesOp } from './db-ops/pages/write/insert-inventory-skipped-pages.js';
|
|
51
56
|
import { recordRedirect as recordRedirectOp } from './db-ops/pages/write/record-redirect.js';
|
|
52
57
|
import { setSkippedPage as setSkippedPageOp } from './db-ops/pages/write/set-skipped-page.js';
|
|
53
58
|
import { updatePage as updatePageOp } from './db-ops/pages/write/update-page.js';
|
|
@@ -118,6 +123,16 @@ export class Database extends EventEmitter {
|
|
|
118
123
|
},
|
|
119
124
|
});
|
|
120
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Adds onto the `rejected_count` of a shape's `dedupe_cap_events` row,
|
|
128
|
+
* looked up by `shape_key` rather than `id`. Delegates to
|
|
129
|
+
* {@link accumulateDedupeCapRejectedCountOp}.
|
|
130
|
+
* @param shapeKey - The capped shape whose rejection count to accumulate.
|
|
131
|
+
* @param rejectedCount - Additional anchors rejected for this shape in the current session.
|
|
132
|
+
*/
|
|
133
|
+
async accumulateDedupeCapRejectedCount(shapeKey, rejectedCount) {
|
|
134
|
+
return emitErrorAndRetry(this, 'Database.accumulateDedupeCapRejectedCount', async () => await accumulateDedupeCapRejectedCountOp(this.#instance, shapeKey, rejectedCount), retrySetting);
|
|
135
|
+
}
|
|
121
136
|
/**
|
|
122
137
|
* Forces a WAL checkpoint, writing all pending WAL data back to the main
|
|
123
138
|
* database file. Delegates to {@link checkpointOp}.
|
|
@@ -141,6 +156,16 @@ export class Database extends EventEmitter {
|
|
|
141
156
|
async destroy() {
|
|
142
157
|
await destroyOp(this.#instance);
|
|
143
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Finalizes a `dedupe_cap_events` row by stamping `rejected_count` — a
|
|
161
|
+
* no-op if the row is already finalized. Delegates to
|
|
162
|
+
* {@link finalizeDedupeCapEventOp}.
|
|
163
|
+
* @param id - The `dedupe_cap_events.id` to finalize.
|
|
164
|
+
* @param rejectedCount - Number of anchors rejected for this shape after it capped.
|
|
165
|
+
*/
|
|
166
|
+
async finalizeDedupeCapEvent(id, rejectedCount) {
|
|
167
|
+
return emitErrorAndRetry(this, 'Database.finalizeDedupeCapEvent', async () => await finalizeDedupeCapEventOp(this.#instance, id, rejectedCount), retrySetting);
|
|
168
|
+
}
|
|
144
169
|
/**
|
|
145
170
|
* Retrieves all anchors (outgoing links) on a specific page.
|
|
146
171
|
* Delegates to {@link getAnchorsOnPageOp}.
|
|
@@ -422,6 +447,15 @@ export class Database extends EventEmitter {
|
|
|
422
447
|
async insertCrawlError(url, message, isExternal = false) {
|
|
423
448
|
return emitErrorAndRetry(this, 'Database.insertCrawlError', async () => await insertCrawlErrorOp(this.#instance, url, message, isExternal), retrySetting);
|
|
424
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Appends one row (`rejected_count = NULL`) to the `dedupe_cap_events`
|
|
452
|
+
* journal. Delegates to {@link insertDedupeCapEventOp}.
|
|
453
|
+
* @param params - The newly-capped shape's fields to record.
|
|
454
|
+
* @returns The autoincremented `id` of the newly-inserted row.
|
|
455
|
+
*/
|
|
456
|
+
async insertDedupeCapEvent(params) {
|
|
457
|
+
return emitErrorAndRetry(this, 'Database.insertDedupeCapEvent', async () => await insertDedupeCapEventOp(this.#instance, params), retrySetting);
|
|
458
|
+
}
|
|
425
459
|
/**
|
|
426
460
|
* Pre-insert inventory non-HTML URLs into `resources` as placeholder rows.
|
|
427
461
|
* Delegates to {@link insertInventoryResourcesOp}.
|
|
@@ -440,6 +474,17 @@ export class Database extends EventEmitter {
|
|
|
440
474
|
async insertInventorySeeds(urls) {
|
|
441
475
|
return emitErrorAndRetry(this, 'Database.insertInventorySeeds', async () => await insertInventorySeedsOp(this.#instance, this.#writeRefCaches, urls), retrySetting);
|
|
442
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Records exclude-matched inventory URLs as terminal skipped pages
|
|
479
|
+
* (`scraped=1`, `is_skipped=1`, `skip_reason='excluded'`,
|
|
480
|
+
* `source='inventory-seed'`). Delegates to
|
|
481
|
+
* {@link insertInventorySkippedPagesOp} — see that op's JSDoc for the
|
|
482
|
+
* normal-crawl parity rationale.
|
|
483
|
+
* @param urls - URL strings already in `withoutHashAndAuth` form.
|
|
484
|
+
*/
|
|
485
|
+
async insertInventorySkippedPages(urls) {
|
|
486
|
+
return emitErrorAndRetry(this, 'Database.insertInventorySkippedPages', async () => await insertInventorySkippedPagesOp(this.#instance, this.#writeRefCaches, urls), retrySetting);
|
|
487
|
+
}
|
|
443
488
|
/**
|
|
444
489
|
* Appends one open (`ended_at = NULL`) row to the `network_outages`
|
|
445
490
|
* journal. Delegates to {@link insertNetworkOutageOp}.
|
|
@@ -478,6 +523,15 @@ export class Database extends EventEmitter {
|
|
|
478
523
|
async insertResourceReferrers(src, pageUrl) {
|
|
479
524
|
return emitErrorAndRetry(this, 'Database.insertResourceReferrers', async () => await insertResourceReferrersOp(this.#instance, this.#writeRefCaches, src, pageUrl), retrySetting);
|
|
480
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* Every distinct `dedupe_cap_events.shape_key` recorded in this archive.
|
|
528
|
+
* Delegates to {@link listDedupeCapShapeKeysOp}.
|
|
529
|
+
* @returns Distinct shape keys already confirmed capped, or `[]` on an
|
|
530
|
+
* archive that predates `dedupe_cap_events` or has recorded none.
|
|
531
|
+
*/
|
|
532
|
+
async listDedupeCapShapeKeys() {
|
|
533
|
+
return emitErrorAndRetry(this, 'Database.listDedupeCapShapeKeys', async () => await listDedupeCapShapeKeysOp(this.#instance), retrySetting);
|
|
534
|
+
}
|
|
481
535
|
/**
|
|
482
536
|
* Hostnames whose `crawl_errors` history is consistently DNS failures and
|
|
483
537
|
* for which no recent 2xx-3xx page or resource is recorded.
|
|
@@ -19,6 +19,7 @@ export async function getConfig(knex) {
|
|
|
19
19
|
excludeUrls: getJSON(config.excludeUrls, []),
|
|
20
20
|
roots: getJSON(config.roots, []),
|
|
21
21
|
retry: config.retry ?? 3,
|
|
22
|
+
maxExcludedDepth: config.maxExcludedDepth ?? 0,
|
|
22
23
|
};
|
|
23
24
|
// @ts-expect-error — `id` is the primary key, not part of the public Config shape
|
|
24
25
|
delete opt.id;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Adds `rejectedCount` onto the `rejected_count` of the `dedupe_cap_events`
|
|
4
|
+
* row for `shapeKey`, treating a still-`NULL` count as `0`. Unlike
|
|
5
|
+
* `finalize-dedupe-cap-event.ts` (which stamps a session's own newly-capped
|
|
6
|
+
* shape exactly once, guarded by `whereNull`), this targets a shape that
|
|
7
|
+
* capped in an EARLIER session and was preloaded into `DedupeCapTracker`'s
|
|
8
|
+
* sticky set (see `DedupeCapTracker`'s constructor JSDoc) — gate rejections
|
|
9
|
+
* for such a shape still occur in the current session, but no `dedupeCap`
|
|
10
|
+
* event (and thus no new row) is ever emitted for it, since the tracker
|
|
11
|
+
* short-circuits on an already-sticky shape before `observe` runs. Matches
|
|
12
|
+
* by `shape_key` rather than `id` because the caller (`CrawlerOrchestrator`)
|
|
13
|
+
* only has the shape key for a preloaded-sticky shape, never its row id.
|
|
14
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
15
|
+
* @param shapeKey - The capped shape whose rejection count to accumulate.
|
|
16
|
+
* @param rejectedCount - Additional anchors rejected for this shape in the current session.
|
|
17
|
+
*/
|
|
18
|
+
export declare function accumulateDedupeCapRejectedCount(knex: Knex, shapeKey: string, rejectedCount: number): Promise<void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds `rejectedCount` onto the `rejected_count` of the `dedupe_cap_events`
|
|
3
|
+
* row for `shapeKey`, treating a still-`NULL` count as `0`. Unlike
|
|
4
|
+
* `finalize-dedupe-cap-event.ts` (which stamps a session's own newly-capped
|
|
5
|
+
* shape exactly once, guarded by `whereNull`), this targets a shape that
|
|
6
|
+
* capped in an EARLIER session and was preloaded into `DedupeCapTracker`'s
|
|
7
|
+
* sticky set (see `DedupeCapTracker`'s constructor JSDoc) — gate rejections
|
|
8
|
+
* for such a shape still occur in the current session, but no `dedupeCap`
|
|
9
|
+
* event (and thus no new row) is ever emitted for it, since the tracker
|
|
10
|
+
* short-circuits on an already-sticky shape before `observe` runs. Matches
|
|
11
|
+
* by `shape_key` rather than `id` because the caller (`CrawlerOrchestrator`)
|
|
12
|
+
* only has the shape key for a preloaded-sticky shape, never its row id.
|
|
13
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
14
|
+
* @param shapeKey - The capped shape whose rejection count to accumulate.
|
|
15
|
+
* @param rejectedCount - Additional anchors rejected for this shape in the current session.
|
|
16
|
+
*/
|
|
17
|
+
export async function accumulateDedupeCapRejectedCount(knex, shapeKey, rejectedCount) {
|
|
18
|
+
await knex('dedupe_cap_events')
|
|
19
|
+
.where({ shape_key: shapeKey })
|
|
20
|
+
.update({
|
|
21
|
+
rejected_count: knex.raw('COALESCE(rejected_count, 0) + ?', [rejectedCount]),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Finalizes a `dedupe_cap_events` row by stamping `rejected_count` — but
|
|
4
|
+
* ONLY if it is still unset. The `whereNull('rejected_count')` guard makes
|
|
5
|
+
* this idempotent, mirroring `close-network-outage.ts`'s `ended_at` guard: a
|
|
6
|
+
* second call matches zero rows and is a silent no-op rather than
|
|
7
|
+
* overwriting an already-finalized count.
|
|
8
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
9
|
+
* @param id - The `dedupe_cap_events.id` to finalize.
|
|
10
|
+
* @param rejectedCount - Number of anchors rejected for this shape after it capped.
|
|
11
|
+
*/
|
|
12
|
+
export declare function finalizeDedupeCapEvent(knex: Knex, id: number, rejectedCount: number): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finalizes a `dedupe_cap_events` row by stamping `rejected_count` — but
|
|
3
|
+
* ONLY if it is still unset. The `whereNull('rejected_count')` guard makes
|
|
4
|
+
* this idempotent, mirroring `close-network-outage.ts`'s `ended_at` guard: a
|
|
5
|
+
* second call matches zero rows and is a silent no-op rather than
|
|
6
|
+
* overwriting an already-finalized count.
|
|
7
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
8
|
+
* @param id - The `dedupe_cap_events.id` to finalize.
|
|
9
|
+
* @param rejectedCount - Number of anchors rejected for this shape after it capped.
|
|
10
|
+
*/
|
|
11
|
+
export async function finalizeDedupeCapEvent(knex, id, rejectedCount) {
|
|
12
|
+
await knex('dedupe_cap_events').where({ id }).whereNull('rejected_count').update({
|
|
13
|
+
rejected_count: rejectedCount,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { InsertDedupeCapEventParams } from '../../types.js';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Appends one row to the `dedupe_cap_events` journal, with `rejected_count`
|
|
5
|
+
* left `NULL` — the row starts life without a finalized rejection count.
|
|
6
|
+
*
|
|
7
|
+
* Called the instant `DedupeCapTracker#observe` confirms a URL shape as a
|
|
8
|
+
* same-cluster trap (the `dedupeCap` event). See
|
|
9
|
+
* `finalize-dedupe-cap-event.ts` for how `rejected_count` is later set.
|
|
10
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
11
|
+
* @param params - The newly-capped shape's fields to record.
|
|
12
|
+
* @returns The autoincremented `id` of the newly-inserted row.
|
|
13
|
+
*/
|
|
14
|
+
export declare function insertDedupeCapEvent(knex: Knex, params: InsertDedupeCapEventParams): Promise<number>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Appends one row to the `dedupe_cap_events` journal, with `rejected_count`
|
|
3
|
+
* left `NULL` — the row starts life without a finalized rejection count.
|
|
4
|
+
*
|
|
5
|
+
* Called the instant `DedupeCapTracker#observe` confirms a URL shape as a
|
|
6
|
+
* same-cluster trap (the `dedupeCap` event). See
|
|
7
|
+
* `finalize-dedupe-cap-event.ts` for how `rejected_count` is later set.
|
|
8
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
9
|
+
* @param params - The newly-capped shape's fields to record.
|
|
10
|
+
* @returns The autoincremented `id` of the newly-inserted row.
|
|
11
|
+
*/
|
|
12
|
+
export async function insertDedupeCapEvent(knex, params) {
|
|
13
|
+
const inserted = await knex
|
|
14
|
+
.from('dedupe_cap_events')
|
|
15
|
+
.insert({
|
|
16
|
+
shape_key: params.shapeKey,
|
|
17
|
+
sample_url: params.sampleUrl,
|
|
18
|
+
body_hash: params.bodyHash,
|
|
19
|
+
effective_threshold: params.effectiveThreshold,
|
|
20
|
+
observed_count: params.observedCount,
|
|
21
|
+
detected_at: params.detectedAt,
|
|
22
|
+
rejected_count: null,
|
|
23
|
+
})
|
|
24
|
+
.returning('id');
|
|
25
|
+
const id = inserted[0]?.id;
|
|
26
|
+
if (typeof id !== 'number') {
|
|
27
|
+
throw new TypeError('insertDedupeCapEvent: INSERT returned no row id');
|
|
28
|
+
}
|
|
29
|
+
return id;
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Every distinct `dedupe_cap_events.shape_key` recorded in this archive —
|
|
4
|
+
* used by `CrawlerOrchestrator` to preload `DedupeCapTracker`'s sticky set
|
|
5
|
+
* on `--resume` / `--append` / `--retry-failed` / `--inventory`, so a trap
|
|
6
|
+
* this crawl already paid the cost of discovering once is not re-admitted
|
|
7
|
+
* in a later session. Fresh (non-resuming) crawls do not call this — there
|
|
8
|
+
* is no archive history to seed from.
|
|
9
|
+
*
|
|
10
|
+
* Unlike `listDnsBurnedHostCandidates`, no additional exclusion logic is
|
|
11
|
+
* needed: once `DedupeCapTracker` confirms a shape as a trap, it stays
|
|
12
|
+
* confirmed — there is no equivalent of "the host might have recovered
|
|
13
|
+
* since".
|
|
14
|
+
*
|
|
15
|
+
* Returns `[]` on legacy archives that pre-date the `dedupe_cap_events`
|
|
16
|
+
* table (self-healed on next writer open, so this is never a permanent
|
|
17
|
+
* state) or that have recorded no capped shapes.
|
|
18
|
+
* @param knex - Knex query builder connected to the archive DB.
|
|
19
|
+
* @returns Distinct shape keys already confirmed capped.
|
|
20
|
+
*/
|
|
21
|
+
export declare function listDedupeCapShapeKeys(knex: Knex): Promise<string[]>;
|