@nitpicker/crawler 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/archive/archive-accessor.d.ts +56 -0
- package/lib/archive/archive-accessor.js +72 -0
- package/lib/archive/archive.d.ts +108 -9
- package/lib/archive/archive.js +109 -7
- package/lib/archive/body-hash/compute-body-hash.d.ts +22 -0
- package/lib/archive/body-hash/compute-body-hash.js +31 -0
- package/lib/archive/body-hash/extract-body.d.ts +18 -0
- package/lib/archive/body-hash/extract-body.js +29 -0
- package/lib/archive/body-hash/mask-dynamic-ids.d.ts +33 -0
- package/lib/archive/body-hash/mask-dynamic-ids.js +44 -0
- package/lib/archive/body-hash/normalize-url-like-strings.d.ts +16 -0
- package/lib/archive/body-hash/normalize-url-like-strings.js +19 -0
- package/lib/archive/cache/clear-archive-cache-entry.d.ts +19 -0
- package/lib/archive/cache/clear-archive-cache-entry.js +25 -0
- package/lib/archive/cache/clear-archive-cache-root.d.ts +26 -0
- package/lib/archive/cache/clear-archive-cache-root.js +32 -0
- package/lib/archive/cache/list-archive-cache-entries.d.ts +23 -0
- package/lib/archive/cache/list-archive-cache-entries.js +125 -0
- package/lib/archive/cache/path-exists.d.ts +9 -0
- package/lib/archive/cache/path-exists.js +18 -0
- package/lib/archive/cache/types.d.ts +33 -0
- package/lib/archive/cache/types.js +1 -0
- package/lib/archive/create-adjunct-tables.d.ts +18 -0
- package/lib/archive/create-adjunct-tables.js +277 -0
- package/lib/archive/create-entity-tables.d.ts +14 -0
- package/lib/archive/create-entity-tables.js +52 -1
- package/lib/archive/database.d.ts +91 -2
- package/lib/archive/database.js +128 -0
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +1 -0
- package/lib/archive/db-ops/_shared/compress-payload.d.ts +26 -0
- package/lib/archive/db-ops/_shared/compress-payload.js +30 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.js +1 -0
- package/lib/archive/db-ops/_shared/types.d.ts +2 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.js +3 -3
- package/lib/archive/db-ops/analysis/replace-page-templates.d.ts +29 -0
- package/lib/archive/db-ops/analysis/replace-page-templates.js +84 -0
- package/lib/archive/db-ops/analysis/types.d.ts +61 -0
- package/lib/archive/db-ops/analysis/types.js +1 -0
- package/lib/archive/db-ops/config/info-column-allowlist.js +1 -0
- package/lib/archive/db-ops/console-logs/compute-console-log-hash.d.ts +19 -0
- package/lib/archive/db-ops/console-logs/compute-console-log-hash.js +30 -0
- package/lib/archive/db-ops/console-logs/replace-console-logs.d.ts +34 -0
- package/lib/archive/db-ops/console-logs/replace-console-logs.js +123 -0
- package/lib/archive/db-ops/console-logs/stringify-console-log-args.d.ts +16 -0
- package/lib/archive/db-ops/console-logs/stringify-console-log-args.js +26 -0
- package/lib/archive/db-ops/console-logs/types.d.ts +38 -0
- package/lib/archive/db-ops/console-logs/types.js +1 -0
- package/lib/archive/db-ops/console-logs/upsert-console-log-item.d.ts +29 -0
- package/lib/archive/db-ops/console-logs/upsert-console-log-item.js +52 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +11 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +21 -4
- package/lib/archive/db-ops/inventory/record-inventory-run.js +1 -0
- package/lib/archive/db-ops/lifecycle/init.d.ts +25 -10
- package/lib/archive/db-ops/lifecycle/init.js +39 -10
- package/lib/archive/db-ops/meta/get-audios-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-audios-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-buttons-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-buttons-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-canvases-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-canvases-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-headings-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-headings-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-iframes-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-iframes-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-main-content-images-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-main-content-images-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-main-content-tables-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-main-content-tables-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-videos-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-videos-of-page.js +15 -0
- package/lib/archive/db-ops/outages/close-network-outage.d.ts +12 -0
- package/lib/archive/db-ops/outages/close-network-outage.js +15 -0
- package/lib/archive/db-ops/outages/close-stale-open-network-outages.d.ts +26 -0
- package/lib/archive/db-ops/outages/close-stale-open-network-outages.js +42 -0
- package/lib/archive/db-ops/outages/compute-outage-clamp-timestamp.d.ts +26 -0
- package/lib/archive/db-ops/outages/compute-outage-clamp-timestamp.js +31 -0
- package/lib/archive/db-ops/outages/insert-network-outage.d.ts +14 -0
- package/lib/archive/db-ops/outages/insert-network-outage.js +29 -0
- package/lib/archive/db-ops/outages/list-network-outages.d.ts +28 -0
- package/lib/archive/db-ops/outages/list-network-outages.js +42 -0
- package/lib/archive/db-ops/pages/read/build-page-query.js +1 -1
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +19 -10
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +18 -3
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +48 -10
- package/lib/archive/db-ops/pages/write/insert-audios.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-audios.js +25 -0
- package/lib/archive/db-ops/pages/write/insert-buttons.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-buttons.js +29 -0
- package/lib/archive/db-ops/pages/write/insert-canvases.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-canvases.js +26 -0
- package/lib/archive/db-ops/pages/write/insert-headings.d.ts +15 -0
- package/lib/archive/db-ops/pages/write/insert-headings.js +27 -0
- package/lib/archive/db-ops/pages/write/insert-iframes.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-iframes.js +28 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-images.d.ts +20 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-images.js +32 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-tables.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-tables.js +29 -0
- package/lib/archive/db-ops/pages/write/insert-page.js +15 -2
- package/lib/archive/db-ops/pages/write/insert-videos.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-videos.js +28 -0
- package/lib/archive/db-ops/pages/write/update-page.js +33 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.js +6 -6
- package/lib/archive/filesystem/output-binary.d.ts +12 -0
- package/lib/archive/filesystem/output-binary.js +17 -0
- package/lib/archive/get-failed-page-messages.d.ts +20 -8
- package/lib/archive/get-failed-page-messages.js +29 -15
- package/lib/archive/init-schema.js +2 -1
- package/lib/archive/meta/compute-main-contents-denormalized.d.ts +22 -0
- package/lib/archive/meta/compute-main-contents-denormalized.js +63 -0
- package/lib/archive/meta/types.d.ts +263 -0
- package/lib/archive/migrate-content-items-alias-of-id.d.ts +47 -0
- package/lib/archive/migrate-content-items-alias-of-id.js +58 -0
- package/lib/archive/migrate-info-main-content-selector.d.ts +11 -0
- package/lib/archive/migrate-info-main-content-selector.js +24 -0
- package/lib/archive/migrate-inventory-runs-invalid-skipped.d.ts +11 -0
- package/lib/archive/migrate-inventory-runs-invalid-skipped.js +24 -0
- package/lib/archive/migrate-main-contents-columns.d.ts +21 -0
- package/lib/archive/migrate-main-contents-columns.js +50 -0
- package/lib/archive/migrate-page-meta-body-hash.d.ts +39 -0
- package/lib/archive/migrate-page-meta-body-hash.js +52 -0
- package/lib/archive/migrate-page-meta-console-error-count.d.ts +22 -0
- package/lib/archive/migrate-page-meta-console-error-count.js +34 -0
- package/lib/archive/page.d.ts +145 -3
- package/lib/archive/page.js +195 -0
- package/lib/archive/types.d.ts +86 -2
- package/lib/archive/url-alias/compute-tier-a-alias-key.d.ts +31 -0
- package/lib/archive/url-alias/compute-tier-a-alias-key.js +36 -0
- package/lib/archive/url-alias/compute-tier-b-alias-key.d.ts +19 -0
- package/lib/archive/url-alias/compute-tier-b-alias-key.js +30 -0
- package/lib/archive/url-alias/format-alias-key.d.ts +13 -0
- package/lib/archive/url-alias/format-alias-key.js +14 -0
- package/lib/archive/url-alias/parse-alias-key-parts.d.ts +15 -0
- package/lib/archive/url-alias/parse-alias-key-parts.js +40 -0
- package/lib/archive/url-alias/types.d.ts +7 -0
- package/lib/archive/url-alias/types.js +1 -0
- package/lib/crawler/choose-probe-host.d.ts +24 -0
- package/lib/crawler/choose-probe-host.js +38 -0
- package/lib/crawler/clear-dns-burned-host-cache.js +2 -0
- package/lib/crawler/crawler.js +261 -2
- package/lib/crawler/dns-burned-host-burn-timestamps.d.ts +17 -0
- package/lib/crawler/dns-burned-host-burn-timestamps.js +17 -0
- package/lib/crawler/evict-network-classified-destination-cache-entries.d.ts +26 -0
- package/lib/crawler/evict-network-classified-destination-cache-entries.js +34 -0
- package/lib/crawler/evict-outage-tainted-dns-burns.d.ts +23 -0
- package/lib/crawler/evict-outage-tainted-dns-burns.js +26 -0
- package/lib/crawler/fetch-destination.js +2 -0
- package/lib/crawler/link-to-page-data.js +2 -0
- package/lib/crawler/network-gate.d.ts +49 -0
- package/lib/crawler/network-gate.js +78 -0
- package/lib/crawler/network-outage-detector.d.ts +51 -0
- package/lib/crawler/network-outage-detector.js +81 -0
- package/lib/crawler/network-outage-summary-counter.d.ts +21 -0
- package/lib/crawler/network-outage-summary-counter.js +18 -0
- package/lib/crawler/probe-network.d.ts +26 -0
- package/lib/crawler/probe-network.js +21 -0
- package/lib/crawler/resource-to-page-data.js +2 -0
- package/lib/crawler/types.d.ts +152 -1
- package/lib/crawler-orchestrator.d.ts +62 -18
- package/lib/crawler-orchestrator.js +163 -26
- package/lib/crawler.d.ts +21 -0
- package/lib/crawler.js +17 -0
- package/lib/is-within-outage-window.d.ts +49 -0
- package/lib/is-within-outage-window.js +33 -0
- package/lib/network-related-error-kinds.d.ts +23 -0
- package/lib/network-related-error-kinds.js +28 -0
- package/lib/types.d.ts +9 -1
- package/lib/utils/array/each-splitted.d.ts +1 -1
- package/lib/utils/compute-file-sha256.d.ts +17 -17
- package/lib/utils/compute-file-sha256.js +18 -47
- package/package.json +3 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MainContentsDenormalizedColumns } from './types.js';
|
|
2
|
+
import type { MainContentsData, ScrollHeightData } from '@d-zero/beholder';
|
|
3
|
+
/**
|
|
4
|
+
* Computes the `page_meta.main_content_*` / `scroll_height_*` denormalised
|
|
5
|
+
* aggregate columns from beholder's `MainContentsData` / `ScrollHeightData`.
|
|
6
|
+
*
|
|
7
|
+
* `mainContents` is `null` for pages that were not fully rendered (external,
|
|
8
|
+
* non-HTML, or metadata-only scrapes) — in that case every column is `null`.
|
|
9
|
+
* This mirrors {@link import('./compute-page-denormalized.js').computePageDenormalized}'s
|
|
10
|
+
* write-once-at-scrape-time pattern so list / detail reads never re-derive
|
|
11
|
+
* counts from the `page_main_content_*` child tables.
|
|
12
|
+
*
|
|
13
|
+
* Accepts `undefined` as well as `null`: `PageData` declares `mainContents`
|
|
14
|
+
* as required, but test fixtures across the codebase predate this field and
|
|
15
|
+
* omit it (`.spec.ts` files are excluded from the `tsc` build, so this goes
|
|
16
|
+
* uncaught at compile time) — `== null` tolerates both without forcing every
|
|
17
|
+
* fixture to be updated.
|
|
18
|
+
* @param mainContents - Beholder's per-page main-content metrics, or `null`/`undefined`.
|
|
19
|
+
* @param scrollHeight - Beholder's per-page scroll-height measurements, or `null`/`undefined`.
|
|
20
|
+
* @returns The seventeen denormalised columns.
|
|
21
|
+
*/
|
|
22
|
+
export declare function computeMainContentsDenormalized(mainContents: MainContentsData | null | undefined, scrollHeight: ScrollHeightData | null | undefined): MainContentsDenormalizedColumns;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes the `page_meta.main_content_*` / `scroll_height_*` denormalised
|
|
3
|
+
* aggregate columns from beholder's `MainContentsData` / `ScrollHeightData`.
|
|
4
|
+
*
|
|
5
|
+
* `mainContents` is `null` for pages that were not fully rendered (external,
|
|
6
|
+
* non-HTML, or metadata-only scrapes) — in that case every column is `null`.
|
|
7
|
+
* This mirrors {@link import('./compute-page-denormalized.js').computePageDenormalized}'s
|
|
8
|
+
* write-once-at-scrape-time pattern so list / detail reads never re-derive
|
|
9
|
+
* counts from the `page_main_content_*` child tables.
|
|
10
|
+
*
|
|
11
|
+
* Accepts `undefined` as well as `null`: `PageData` declares `mainContents`
|
|
12
|
+
* as required, but test fixtures across the codebase predate this field and
|
|
13
|
+
* omit it (`.spec.ts` files are excluded from the `tsc` build, so this goes
|
|
14
|
+
* uncaught at compile time) — `== null` tolerates both without forcing every
|
|
15
|
+
* fixture to be updated.
|
|
16
|
+
* @param mainContents - Beholder's per-page main-content metrics, or `null`/`undefined`.
|
|
17
|
+
* @param scrollHeight - Beholder's per-page scroll-height measurements, or `null`/`undefined`.
|
|
18
|
+
* @returns The seventeen denormalised columns.
|
|
19
|
+
*/
|
|
20
|
+
export function computeMainContentsDenormalized(mainContents, scrollHeight) {
|
|
21
|
+
if (mainContents == null) {
|
|
22
|
+
return {
|
|
23
|
+
main_content_node_name: null,
|
|
24
|
+
main_content_id: null,
|
|
25
|
+
main_content_role: null,
|
|
26
|
+
main_content_selector: null,
|
|
27
|
+
main_content_class_list: null,
|
|
28
|
+
main_content_word_count: null,
|
|
29
|
+
main_content_body_word_count: null,
|
|
30
|
+
main_content_heading_count: null,
|
|
31
|
+
main_content_image_count: null,
|
|
32
|
+
main_content_table_count: null,
|
|
33
|
+
main_content_button_count: null,
|
|
34
|
+
main_content_iframe_count: null,
|
|
35
|
+
main_content_video_count: null,
|
|
36
|
+
main_content_audio_count: null,
|
|
37
|
+
main_content_canvas_count: null,
|
|
38
|
+
scroll_height_desktop: null,
|
|
39
|
+
scroll_height_mobile: null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
main_content_node_name: mainContents.main?.nodeName ?? null,
|
|
44
|
+
main_content_id: mainContents.main?.id ?? null,
|
|
45
|
+
main_content_role: mainContents.main?.role ?? null,
|
|
46
|
+
main_content_selector: mainContents.main?.selector ?? null,
|
|
47
|
+
main_content_class_list: mainContents.main
|
|
48
|
+
? JSON.stringify(mainContents.main.classList)
|
|
49
|
+
: null,
|
|
50
|
+
main_content_word_count: mainContents.wordCount,
|
|
51
|
+
main_content_body_word_count: mainContents.bodyWordCount,
|
|
52
|
+
main_content_heading_count: mainContents.headings.length,
|
|
53
|
+
main_content_image_count: mainContents.images.length,
|
|
54
|
+
main_content_table_count: mainContents.tables.length,
|
|
55
|
+
main_content_button_count: mainContents.buttons.length,
|
|
56
|
+
main_content_iframe_count: mainContents.iframes.length,
|
|
57
|
+
main_content_video_count: mainContents.videos.length,
|
|
58
|
+
main_content_audio_count: mainContents.audios.length,
|
|
59
|
+
main_content_canvas_count: mainContents.canvases.length,
|
|
60
|
+
scroll_height_desktop: scrollHeight?.desktop ?? null,
|
|
61
|
+
scroll_height_mobile: scrollHeight?.mobile ?? null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -93,6 +93,269 @@ export interface PageDenormalizedColumns {
|
|
|
93
93
|
/** Sorted unique providers, comma-separated (empty string when no tags). */
|
|
94
94
|
tags_providers_csv: string;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Denormalised aggregates computed at write time from beholder's
|
|
98
|
+
* `MainContentsData` / `ScrollHeightData`.
|
|
99
|
+
*
|
|
100
|
+
* Stored on `page_meta` following the same pattern as
|
|
101
|
+
* {@link PageDenormalizedColumns} (`tag_count` / `jsonld_count`): the full
|
|
102
|
+
* per-element detail lives in the `page_main_content_*` child tables, while
|
|
103
|
+
* these scalar columns let list / detail reads answer "how many headings?"
|
|
104
|
+
* without joining them. All fields are `null` when the page was not fully
|
|
105
|
+
* rendered (external / non-HTML / metadata-only scrape) — see
|
|
106
|
+
* `compute-main-contents-denormalized.ts` for the `null`-in-null-out contract.
|
|
107
|
+
* @see compute-main-contents-denormalized.ts
|
|
108
|
+
* @example
|
|
109
|
+
* const denorm: MainContentsDenormalizedColumns = {
|
|
110
|
+
* main_content_node_name: 'MAIN',
|
|
111
|
+
* main_content_id: null,
|
|
112
|
+
* main_content_role: null,
|
|
113
|
+
* main_content_selector: 'main.l-main',
|
|
114
|
+
* main_content_class_list: '["l-main"]',
|
|
115
|
+
* main_content_word_count: 1240,
|
|
116
|
+
* main_content_body_word_count: 1580,
|
|
117
|
+
* main_content_heading_count: 6,
|
|
118
|
+
* main_content_image_count: 3,
|
|
119
|
+
* main_content_table_count: 0,
|
|
120
|
+
* main_content_button_count: 1,
|
|
121
|
+
* main_content_iframe_count: 0,
|
|
122
|
+
* main_content_video_count: 0,
|
|
123
|
+
* main_content_audio_count: 0,
|
|
124
|
+
* main_content_canvas_count: 0,
|
|
125
|
+
* scroll_height_desktop: 3200,
|
|
126
|
+
* scroll_height_mobile: 5400,
|
|
127
|
+
* };
|
|
128
|
+
*/
|
|
129
|
+
export interface MainContentsDenormalizedColumns {
|
|
130
|
+
/** Detected main-content element's `nodeName` (e.g. `'MAIN'`), or `null`. */
|
|
131
|
+
main_content_node_name: string | null;
|
|
132
|
+
/** Detected main-content element's `id`, or `null`. */
|
|
133
|
+
main_content_id: string | null;
|
|
134
|
+
/** Detected main-content element's `role` attribute, or `null`. */
|
|
135
|
+
main_content_role: string | null;
|
|
136
|
+
/** Diagnostic tag+id+class selector for the detected element, or `null`. */
|
|
137
|
+
main_content_selector: string | null;
|
|
138
|
+
/** JSON-encoded array of the detected element's CSS classes, or `null`. */
|
|
139
|
+
main_content_class_list: string | null;
|
|
140
|
+
/** Character count of the main region's text content, or `null`. */
|
|
141
|
+
main_content_word_count: number | null;
|
|
142
|
+
/** Character count of `document.body`'s text content, or `null`. */
|
|
143
|
+
main_content_body_word_count: number | null;
|
|
144
|
+
/** Number of headings within the main region, or `null`. */
|
|
145
|
+
main_content_heading_count: number | null;
|
|
146
|
+
/** Number of images within the main region, or `null`. */
|
|
147
|
+
main_content_image_count: number | null;
|
|
148
|
+
/** Number of tables within the main region, or `null`. */
|
|
149
|
+
main_content_table_count: number | null;
|
|
150
|
+
/** Number of button-like elements within the main region, or `null`. */
|
|
151
|
+
main_content_button_count: number | null;
|
|
152
|
+
/** Number of iframes within the main region, or `null`. */
|
|
153
|
+
main_content_iframe_count: number | null;
|
|
154
|
+
/** Number of videos within the main region, or `null`. */
|
|
155
|
+
main_content_video_count: number | null;
|
|
156
|
+
/** Number of audios within the main region, or `null`. */
|
|
157
|
+
main_content_audio_count: number | null;
|
|
158
|
+
/** Number of canvases within the main region, or `null`. */
|
|
159
|
+
main_content_canvas_count: number | null;
|
|
160
|
+
/** `document.body.scrollHeight` at the desktop-compact preset, or `null`. */
|
|
161
|
+
scroll_height_desktop: number | null;
|
|
162
|
+
/** `document.body.scrollHeight` at the mobile-small preset, or `null`. */
|
|
163
|
+
scroll_height_mobile: number | null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* One row in the `page_main_content_headings` table.
|
|
167
|
+
* @example
|
|
168
|
+
* const row: MainContentHeadingRow = { id: 1, pageId: 42, order: 0, text: 'Welcome', level: 1 };
|
|
169
|
+
*/
|
|
170
|
+
export interface MainContentHeadingRow {
|
|
171
|
+
/** Auto-increment primary key. */
|
|
172
|
+
id: number;
|
|
173
|
+
/** FK to `content_items.id`. */
|
|
174
|
+
pageId: number;
|
|
175
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
176
|
+
order: number;
|
|
177
|
+
/** Heading text after whitespace removal, or `null` when empty. */
|
|
178
|
+
text: string | null;
|
|
179
|
+
/** Heading level (1-6) from the tag name. */
|
|
180
|
+
level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* One row in the `page_main_content_images` table.
|
|
184
|
+
* @example
|
|
185
|
+
* const row: MainContentImageRow = {
|
|
186
|
+
* id: 1,
|
|
187
|
+
* pageId: 42,
|
|
188
|
+
* order: 0,
|
|
189
|
+
* src: 'https://example.com/a.png',
|
|
190
|
+
* alt: 'A photo',
|
|
191
|
+
* };
|
|
192
|
+
*/
|
|
193
|
+
export interface MainContentImageRow {
|
|
194
|
+
/** Auto-increment primary key. */
|
|
195
|
+
id: number;
|
|
196
|
+
/** FK to `content_items.id`. */
|
|
197
|
+
pageId: number;
|
|
198
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
199
|
+
order: number;
|
|
200
|
+
/** Resolved absolute `src` URL. */
|
|
201
|
+
src: string;
|
|
202
|
+
/** `alt` attribute value (may be an empty string). */
|
|
203
|
+
alt: string;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* One row in the `page_main_content_tables` table.
|
|
207
|
+
* @example
|
|
208
|
+
* const row: MainContentTableRow = {
|
|
209
|
+
* id: 1,
|
|
210
|
+
* pageId: 42,
|
|
211
|
+
* order: 0,
|
|
212
|
+
* rows: 3,
|
|
213
|
+
* cols: 4,
|
|
214
|
+
* hasHeader: 1,
|
|
215
|
+
* hasFooter: 0,
|
|
216
|
+
* hasMergedCell: 0,
|
|
217
|
+
* };
|
|
218
|
+
*/
|
|
219
|
+
export interface MainContentTableRow {
|
|
220
|
+
/** Auto-increment primary key. */
|
|
221
|
+
id: number;
|
|
222
|
+
/** FK to `content_items.id`. */
|
|
223
|
+
pageId: number;
|
|
224
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
225
|
+
order: number;
|
|
226
|
+
/** Number of `<tr>` elements. */
|
|
227
|
+
rows: number;
|
|
228
|
+
/** Number of `th`/`td` cells in the first row. */
|
|
229
|
+
cols: number;
|
|
230
|
+
/** Whether the table contains a `<thead>` (raw SQLite 0/1; knex does not round-trip `.boolean()` columns back to JS `boolean` on read). */
|
|
231
|
+
hasHeader: 0 | 1;
|
|
232
|
+
/** Whether the table contains a `<tfoot>` (raw SQLite 0/1). */
|
|
233
|
+
hasFooter: 0 | 1;
|
|
234
|
+
/** Whether any cell uses `colspan` or `rowspan` (raw SQLite 0/1). */
|
|
235
|
+
hasMergedCell: 0 | 1;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* One row in the `page_main_content_buttons` table.
|
|
239
|
+
* @example
|
|
240
|
+
* const row: MainContentButtonRow = {
|
|
241
|
+
* id: 1,
|
|
242
|
+
* pageId: 42,
|
|
243
|
+
* order: 0,
|
|
244
|
+
* nodeName: 'BUTTON',
|
|
245
|
+
* role: null,
|
|
246
|
+
* type: 'submit',
|
|
247
|
+
* text: 'Send',
|
|
248
|
+
* disabled: 0,
|
|
249
|
+
* };
|
|
250
|
+
*/
|
|
251
|
+
export interface MainContentButtonRow {
|
|
252
|
+
/** Auto-increment primary key. */
|
|
253
|
+
id: number;
|
|
254
|
+
/** FK to `content_items.id`. */
|
|
255
|
+
pageId: number;
|
|
256
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
257
|
+
order: number;
|
|
258
|
+
/** Element tag name (e.g. `'BUTTON'`, `'A'`, `'DIV'`). */
|
|
259
|
+
nodeName: string;
|
|
260
|
+
/** `role` attribute, or `null` when absent. */
|
|
261
|
+
role: string | null;
|
|
262
|
+
/** `type` for `<button>` / `<input>`, otherwise `null`. */
|
|
263
|
+
type: string | null;
|
|
264
|
+
/** Label text after whitespace removal, or `null` when empty. */
|
|
265
|
+
text: string | null;
|
|
266
|
+
/** `true` when `disabled` or `aria-disabled="true"` (raw SQLite 0/1). */
|
|
267
|
+
disabled: 0 | 1;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* One row in the `page_main_content_iframes` table.
|
|
271
|
+
* @example
|
|
272
|
+
* const row: MainContentIframeRow = {
|
|
273
|
+
* id: 1,
|
|
274
|
+
* pageId: 42,
|
|
275
|
+
* order: 0,
|
|
276
|
+
* src: 'https://example.com/embed',
|
|
277
|
+
* title: null,
|
|
278
|
+
* width: '640',
|
|
279
|
+
* height: '360',
|
|
280
|
+
* };
|
|
281
|
+
*/
|
|
282
|
+
export interface MainContentIframeRow {
|
|
283
|
+
/** Auto-increment primary key. */
|
|
284
|
+
id: number;
|
|
285
|
+
/** FK to `content_items.id`. */
|
|
286
|
+
pageId: number;
|
|
287
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
288
|
+
order: number;
|
|
289
|
+
/** Resolved absolute `src` URL. */
|
|
290
|
+
src: string;
|
|
291
|
+
/** `title` attribute, or `null` when absent. */
|
|
292
|
+
title: string | null;
|
|
293
|
+
/** Raw `width` attribute string, or `null` when absent. */
|
|
294
|
+
width: string | null;
|
|
295
|
+
/** Raw `height` attribute string, or `null` when absent. */
|
|
296
|
+
height: string | null;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* One row in the `page_main_content_videos` table.
|
|
300
|
+
* @example
|
|
301
|
+
* const row: MainContentVideoRow = {
|
|
302
|
+
* id: 1,
|
|
303
|
+
* pageId: 42,
|
|
304
|
+
* order: 0,
|
|
305
|
+
* src: 'https://example.com/v.mp4',
|
|
306
|
+
* poster: null,
|
|
307
|
+
* width: 640,
|
|
308
|
+
* height: 360,
|
|
309
|
+
* };
|
|
310
|
+
*/
|
|
311
|
+
export interface MainContentVideoRow {
|
|
312
|
+
/** Auto-increment primary key. */
|
|
313
|
+
id: number;
|
|
314
|
+
/** FK to `content_items.id`. */
|
|
315
|
+
pageId: number;
|
|
316
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
317
|
+
order: number;
|
|
318
|
+
/** Resolved media URL. */
|
|
319
|
+
src: string;
|
|
320
|
+
/** Resolved `poster` URL, or `null` when unset. */
|
|
321
|
+
poster: string | null;
|
|
322
|
+
/** IDL `width` in pixels. */
|
|
323
|
+
width: number;
|
|
324
|
+
/** IDL `height` in pixels. */
|
|
325
|
+
height: number;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* One row in the `page_main_content_audios` table.
|
|
329
|
+
* @example
|
|
330
|
+
* const row: MainContentAudioRow = { id: 1, pageId: 42, order: 0, src: 'https://example.com/a.mp3' };
|
|
331
|
+
*/
|
|
332
|
+
export interface MainContentAudioRow {
|
|
333
|
+
/** Auto-increment primary key. */
|
|
334
|
+
id: number;
|
|
335
|
+
/** FK to `content_items.id`. */
|
|
336
|
+
pageId: number;
|
|
337
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
338
|
+
order: number;
|
|
339
|
+
/** Resolved media URL. */
|
|
340
|
+
src: string;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* One row in the `page_main_content_canvases` table.
|
|
344
|
+
* @example
|
|
345
|
+
* const row: MainContentCanvasRow = { id: 1, pageId: 42, order: 0, width: 300, height: 150 };
|
|
346
|
+
*/
|
|
347
|
+
export interface MainContentCanvasRow {
|
|
348
|
+
/** Auto-increment primary key. */
|
|
349
|
+
id: number;
|
|
350
|
+
/** FK to `content_items.id`. */
|
|
351
|
+
pageId: number;
|
|
352
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
353
|
+
order: number;
|
|
354
|
+
/** IDL bitmap width. */
|
|
355
|
+
width: number;
|
|
356
|
+
/** IDL bitmap height. */
|
|
357
|
+
height: number;
|
|
358
|
+
}
|
|
96
359
|
/**
|
|
97
360
|
* One row in the `page_jsonld` table.
|
|
98
361
|
*
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Adds the `content_items.alias_of_id` column to archives created before
|
|
4
|
+
* this feature, then ensures its index exists.
|
|
5
|
+
*
|
|
6
|
+
* `content_items` is provisioned via a bare `CREATE TABLE IF NOT EXISTS` in
|
|
7
|
+
* {@link import('./create-entity-tables.js').createEntityTables}, which
|
|
8
|
+
* self-heals a *missing table* on every `initSchema` call but is a no-op
|
|
9
|
+
* against an *existing* table — adding a column to the DDL string never
|
|
10
|
+
* reaches an archive whose `content_items` predates this change. This
|
|
11
|
+
* mirrors {@link import('./migrate-page-meta-body-hash.js').migratePageMetaBodyHash}'s
|
|
12
|
+
* catch-up: a `hasColumn`-guarded `ALTER TABLE` for the one column
|
|
13
|
+
* `CREATE TABLE IF NOT EXISTS` cannot retrofit.
|
|
14
|
+
*
|
|
15
|
+
* Uses a raw `ALTER TABLE` (not the knex schema builder) so the retrofitted
|
|
16
|
+
* column's `REFERENCES content_items(id) DEFERRABLE INITIALLY DEFERRED`
|
|
17
|
+
* constraint matches the fresh-archive DDL bit-for-bit — the same
|
|
18
|
+
* self-referencing shape as `redirect_dest_id` (see
|
|
19
|
+
* `create-entity-tables.ts`'s JSDoc on why that FK must be deferred: a
|
|
20
|
+
* lower-id row can reference a higher-id row within the same write).
|
|
21
|
+
*
|
|
22
|
+
* The index is created here — unconditionally, after the column-add guard,
|
|
23
|
+
* not inside `createEntityTables`'s DDL — for both a fresh archive (where
|
|
24
|
+
* `alias_of_id` already exists from the DDL, so only the index still needs
|
|
25
|
+
* creating) and a legacy archive (where the column is added just above,
|
|
26
|
+
* then the index follows in the same call). `createEntityTables` runs
|
|
27
|
+
* unconditionally on every archive open, including legacy archives that
|
|
28
|
+
* still lack `alias_of_id` at that point; an unconditional
|
|
29
|
+
* `CREATE INDEX ... alias_of_id` there would fail with `no such column`
|
|
30
|
+
* before this migration ever runs. This function is the one place
|
|
31
|
+
* guaranteed to run only after the column is confirmed present, for both
|
|
32
|
+
* archive kinds.
|
|
33
|
+
*
|
|
34
|
+
* Only adds the column and its index — it does not compute values for
|
|
35
|
+
* existing rows (they stay `NULL`). That computation runs separately, from
|
|
36
|
+
* `backfillAliasOfId` during a viewer-read-model build, since it requires
|
|
37
|
+
* comparing every page's title and (for the trailing-slash tier) its
|
|
38
|
+
* `body_hash` against every other page.
|
|
39
|
+
*
|
|
40
|
+
* Idempotent: adding the column is a no-op once it exists (the index
|
|
41
|
+
* creation always runs, but `IF NOT EXISTS` makes repeat runs a no-op too).
|
|
42
|
+
* Guards on `content_items`'s existence defensively, though by the time this
|
|
43
|
+
* runs (after `initSchema`, itself after `assertCompatibleVersion` rejects
|
|
44
|
+
* pre-0.13 archives) the table is always present.
|
|
45
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
46
|
+
*/
|
|
47
|
+
export declare function migrateContentItemsAliasOfId(instance: Knex): Promise<void>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds the `content_items.alias_of_id` column to archives created before
|
|
3
|
+
* this feature, then ensures its index exists.
|
|
4
|
+
*
|
|
5
|
+
* `content_items` is provisioned via a bare `CREATE TABLE IF NOT EXISTS` in
|
|
6
|
+
* {@link import('./create-entity-tables.js').createEntityTables}, which
|
|
7
|
+
* self-heals a *missing table* on every `initSchema` call but is a no-op
|
|
8
|
+
* against an *existing* table — adding a column to the DDL string never
|
|
9
|
+
* reaches an archive whose `content_items` predates this change. This
|
|
10
|
+
* mirrors {@link import('./migrate-page-meta-body-hash.js').migratePageMetaBodyHash}'s
|
|
11
|
+
* catch-up: a `hasColumn`-guarded `ALTER TABLE` for the one column
|
|
12
|
+
* `CREATE TABLE IF NOT EXISTS` cannot retrofit.
|
|
13
|
+
*
|
|
14
|
+
* Uses a raw `ALTER TABLE` (not the knex schema builder) so the retrofitted
|
|
15
|
+
* column's `REFERENCES content_items(id) DEFERRABLE INITIALLY DEFERRED`
|
|
16
|
+
* constraint matches the fresh-archive DDL bit-for-bit — the same
|
|
17
|
+
* self-referencing shape as `redirect_dest_id` (see
|
|
18
|
+
* `create-entity-tables.ts`'s JSDoc on why that FK must be deferred: a
|
|
19
|
+
* lower-id row can reference a higher-id row within the same write).
|
|
20
|
+
*
|
|
21
|
+
* The index is created here — unconditionally, after the column-add guard,
|
|
22
|
+
* not inside `createEntityTables`'s DDL — for both a fresh archive (where
|
|
23
|
+
* `alias_of_id` already exists from the DDL, so only the index still needs
|
|
24
|
+
* creating) and a legacy archive (where the column is added just above,
|
|
25
|
+
* then the index follows in the same call). `createEntityTables` runs
|
|
26
|
+
* unconditionally on every archive open, including legacy archives that
|
|
27
|
+
* still lack `alias_of_id` at that point; an unconditional
|
|
28
|
+
* `CREATE INDEX ... alias_of_id` there would fail with `no such column`
|
|
29
|
+
* before this migration ever runs. This function is the one place
|
|
30
|
+
* guaranteed to run only after the column is confirmed present, for both
|
|
31
|
+
* archive kinds.
|
|
32
|
+
*
|
|
33
|
+
* Only adds the column and its index — it does not compute values for
|
|
34
|
+
* existing rows (they stay `NULL`). That computation runs separately, from
|
|
35
|
+
* `backfillAliasOfId` during a viewer-read-model build, since it requires
|
|
36
|
+
* comparing every page's title and (for the trailing-slash tier) its
|
|
37
|
+
* `body_hash` against every other page.
|
|
38
|
+
*
|
|
39
|
+
* Idempotent: adding the column is a no-op once it exists (the index
|
|
40
|
+
* creation always runs, but `IF NOT EXISTS` makes repeat runs a no-op too).
|
|
41
|
+
* Guards on `content_items`'s existence defensively, though by the time this
|
|
42
|
+
* runs (after `initSchema`, itself after `assertCompatibleVersion` rejects
|
|
43
|
+
* pre-0.13 archives) the table is always present.
|
|
44
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
45
|
+
*/
|
|
46
|
+
export async function migrateContentItemsAliasOfId(instance) {
|
|
47
|
+
const hasContentItems = await instance.schema.hasTable('content_items');
|
|
48
|
+
if (!hasContentItems) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const hasColumn = await instance.schema.hasColumn('content_items', 'alias_of_id');
|
|
52
|
+
if (!hasColumn) {
|
|
53
|
+
await instance.raw('ALTER TABLE content_items ADD COLUMN alias_of_id INTEGER REFERENCES content_items(id) DEFERRABLE INITIALLY DEFERRED');
|
|
54
|
+
// eslint-disable-next-line no-console
|
|
55
|
+
console.error('[migrate] content_items.alias_of_id column added');
|
|
56
|
+
}
|
|
57
|
+
await instance.raw('CREATE INDEX IF NOT EXISTS idx_content_items_alias_of_id ON content_items(alias_of_id)');
|
|
58
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Adds the `info.mainContentSelector` column to archives created before it
|
|
4
|
+
* existed. `CREATE TABLE IF NOT EXISTS` (used for `info` itself) cannot
|
|
5
|
+
* retrofit a new column onto an already-existing table, so this lightweight,
|
|
6
|
+
* `hasColumn`-guarded `ALTER TABLE` runs on every `initSchema` call —
|
|
7
|
+
* idempotent, and self-healing for archives whose provisioning crashed
|
|
8
|
+
* partway through.
|
|
9
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
10
|
+
*/
|
|
11
|
+
export declare function migrateInfoMainContentSelector(instance: Knex): Promise<void>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds the `info.mainContentSelector` column to archives created before it
|
|
3
|
+
* existed. `CREATE TABLE IF NOT EXISTS` (used for `info` itself) cannot
|
|
4
|
+
* retrofit a new column onto an already-existing table, so this lightweight,
|
|
5
|
+
* `hasColumn`-guarded `ALTER TABLE` runs on every `initSchema` call —
|
|
6
|
+
* idempotent, and self-healing for archives whose provisioning crashed
|
|
7
|
+
* partway through.
|
|
8
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
9
|
+
*/
|
|
10
|
+
export async function migrateInfoMainContentSelector(instance) {
|
|
11
|
+
const hasInfo = await instance.schema.hasTable('info');
|
|
12
|
+
if (!hasInfo) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const hasColumn = await instance.schema.hasColumn('info', 'mainContentSelector');
|
|
16
|
+
if (hasColumn) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
await instance.schema.table('info', (t) => {
|
|
20
|
+
t.string('mainContentSelector');
|
|
21
|
+
});
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
console.error('[migrate] info.mainContentSelector column added');
|
|
24
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Adds the `inventory_runs.invalid_skipped` column to archives created
|
|
4
|
+
* before it existed. `CREATE TABLE IF NOT EXISTS` (used for `inventory_runs`
|
|
5
|
+
* itself) cannot retrofit a new column onto an already-existing table, so
|
|
6
|
+
* this lightweight, `hasColumn`-guarded `ALTER TABLE` runs on every
|
|
7
|
+
* `initSchema` call — idempotent, and self-healing for archives whose
|
|
8
|
+
* provisioning crashed partway through.
|
|
9
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
10
|
+
*/
|
|
11
|
+
export declare function migrateInventoryRunsInvalidSkipped(instance: Knex): Promise<void>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds the `inventory_runs.invalid_skipped` column to archives created
|
|
3
|
+
* before it existed. `CREATE TABLE IF NOT EXISTS` (used for `inventory_runs`
|
|
4
|
+
* itself) cannot retrofit a new column onto an already-existing table, so
|
|
5
|
+
* this lightweight, `hasColumn`-guarded `ALTER TABLE` runs on every
|
|
6
|
+
* `initSchema` call — idempotent, and self-healing for archives whose
|
|
7
|
+
* provisioning crashed partway through.
|
|
8
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
9
|
+
*/
|
|
10
|
+
export async function migrateInventoryRunsInvalidSkipped(instance) {
|
|
11
|
+
const hasTable = await instance.schema.hasTable('inventory_runs');
|
|
12
|
+
if (!hasTable) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const hasColumn = await instance.schema.hasColumn('inventory_runs', 'invalid_skipped');
|
|
16
|
+
if (hasColumn) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
await instance.schema.table('inventory_runs', (t) => {
|
|
20
|
+
t.integer('invalid_skipped');
|
|
21
|
+
});
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
console.error('[migrate] inventory_runs.invalid_skipped column added');
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Add the beholder `MainContentsData` / `ScrollHeightData` denormalised
|
|
4
|
+
* aggregate columns to `page_meta` on archives created before this feature.
|
|
5
|
+
*
|
|
6
|
+
* `page_meta` is provisioned via a bare `CREATE TABLE IF NOT EXISTS` in
|
|
7
|
+
* {@link import('./create-entity-tables.js').createEntityTables}, which
|
|
8
|
+
* self-heals a *missing table* on every `initSchema` call but is a no-op
|
|
9
|
+
* against an *existing* table — adding a column to the DDL string never
|
|
10
|
+
* reaches an archive whose `page_meta` predates this change. This mirrors
|
|
11
|
+
* {@link import('./migrate-info-roots.js').migrateInfoRoots}'s `info.roots`
|
|
12
|
+
* catch-up: a `hasColumn`-guarded `ALTER TABLE` for the one column set that
|
|
13
|
+
* `CREATE TABLE IF NOT EXISTS` cannot retrofit.
|
|
14
|
+
*
|
|
15
|
+
* Idempotent: a no-op once the columns exist. Guards on `page_meta`'s
|
|
16
|
+
* existence defensively, though by the time this runs (after `initSchema`,
|
|
17
|
+
* itself after `assertCompatibleVersion` rejects pre-0.13 archives) the
|
|
18
|
+
* table is always present.
|
|
19
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
20
|
+
*/
|
|
21
|
+
export declare function migrateMainContentsColumns(instance: Knex): Promise<void>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add the beholder `MainContentsData` / `ScrollHeightData` denormalised
|
|
3
|
+
* aggregate columns to `page_meta` on archives created before this feature.
|
|
4
|
+
*
|
|
5
|
+
* `page_meta` is provisioned via a bare `CREATE TABLE IF NOT EXISTS` in
|
|
6
|
+
* {@link import('./create-entity-tables.js').createEntityTables}, which
|
|
7
|
+
* self-heals a *missing table* on every `initSchema` call but is a no-op
|
|
8
|
+
* against an *existing* table — adding a column to the DDL string never
|
|
9
|
+
* reaches an archive whose `page_meta` predates this change. This mirrors
|
|
10
|
+
* {@link import('./migrate-info-roots.js').migrateInfoRoots}'s `info.roots`
|
|
11
|
+
* catch-up: a `hasColumn`-guarded `ALTER TABLE` for the one column set that
|
|
12
|
+
* `CREATE TABLE IF NOT EXISTS` cannot retrofit.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent: a no-op once the columns exist. Guards on `page_meta`'s
|
|
15
|
+
* existence defensively, though by the time this runs (after `initSchema`,
|
|
16
|
+
* itself after `assertCompatibleVersion` rejects pre-0.13 archives) the
|
|
17
|
+
* table is always present.
|
|
18
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
19
|
+
*/
|
|
20
|
+
export async function migrateMainContentsColumns(instance) {
|
|
21
|
+
const hasPageMeta = await instance.schema.hasTable('page_meta');
|
|
22
|
+
if (!hasPageMeta) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const hasColumn = await instance.schema.hasColumn('page_meta', 'main_content_word_count');
|
|
26
|
+
if (hasColumn) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await instance.schema.table('page_meta', (t) => {
|
|
30
|
+
t.string('main_content_node_name');
|
|
31
|
+
t.string('main_content_id');
|
|
32
|
+
t.string('main_content_role');
|
|
33
|
+
t.string('main_content_selector');
|
|
34
|
+
t.text('main_content_class_list');
|
|
35
|
+
t.integer('main_content_word_count');
|
|
36
|
+
t.integer('main_content_body_word_count');
|
|
37
|
+
t.integer('main_content_heading_count');
|
|
38
|
+
t.integer('main_content_image_count');
|
|
39
|
+
t.integer('main_content_table_count');
|
|
40
|
+
t.integer('main_content_button_count');
|
|
41
|
+
t.integer('main_content_iframe_count');
|
|
42
|
+
t.integer('main_content_video_count');
|
|
43
|
+
t.integer('main_content_audio_count');
|
|
44
|
+
t.integer('main_content_canvas_count');
|
|
45
|
+
t.integer('scroll_height_desktop');
|
|
46
|
+
t.integer('scroll_height_mobile');
|
|
47
|
+
});
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
console.error('[migrate] page_meta.main_content_* / scroll_height_* columns added');
|
|
50
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Adds the `page_meta.body_hash` column to archives created before this
|
|
4
|
+
* feature, then ensures its index exists.
|
|
5
|
+
*
|
|
6
|
+
* `page_meta` is provisioned via a bare `CREATE TABLE IF NOT EXISTS` in
|
|
7
|
+
* {@link import('./create-entity-tables.js').createEntityTables}, which
|
|
8
|
+
* self-heals a *missing table* on every `initSchema` call but is a no-op
|
|
9
|
+
* against an *existing* table — adding a column to the DDL string never
|
|
10
|
+
* reaches an archive whose `page_meta` predates this change. This mirrors
|
|
11
|
+
* {@link import('./migrate-main-contents-columns.js').migrateMainContentsColumns}'s
|
|
12
|
+
* catch-up: a `hasColumn`-guarded `ALTER TABLE` for the one column
|
|
13
|
+
* `CREATE TABLE IF NOT EXISTS` cannot retrofit.
|
|
14
|
+
*
|
|
15
|
+
* The index is created here — unconditionally, after the column-add guard,
|
|
16
|
+
* not inside `createEntityTables`'s DDL — for both a fresh archive (where
|
|
17
|
+
* `body_hash` already exists from the DDL, so only the index still needs
|
|
18
|
+
* creating) and a legacy archive (where the column is added just above,
|
|
19
|
+
* then the index follows in the same call). `createEntityTables` runs
|
|
20
|
+
* unconditionally on every archive open, including legacy archives that
|
|
21
|
+
* still lack `body_hash` at that point; an unconditional
|
|
22
|
+
* `CREATE INDEX ... body_hash` there would fail with `no such column` before
|
|
23
|
+
* this migration ever runs. This function is the one place guaranteed to
|
|
24
|
+
* run only after the column is confirmed present, for both archive kinds.
|
|
25
|
+
*
|
|
26
|
+
* Only adds the column and its index — it does not backfill values for
|
|
27
|
+
* existing rows (they stay `NULL`). That backfill runs separately, from
|
|
28
|
+
* `backfillBodyHashFromHtmlBlobs` during a viewer-read-model build, since it
|
|
29
|
+
* requires decompressing every page's stored HTML and is too heavy to run on
|
|
30
|
+
* every archive open.
|
|
31
|
+
*
|
|
32
|
+
* Idempotent: adding the column is a no-op once it exists (the index
|
|
33
|
+
* creation always runs, but `IF NOT EXISTS` makes repeat runs a no-op too).
|
|
34
|
+
* Guards on `page_meta`'s existence defensively, though by the time this
|
|
35
|
+
* runs (after `initSchema`, itself after `assertCompatibleVersion` rejects
|
|
36
|
+
* pre-0.13 archives) the table is always present.
|
|
37
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
38
|
+
*/
|
|
39
|
+
export declare function migratePageMetaBodyHash(instance: Knex): Promise<void>;
|