@gscdump/engine 1.4.8 → 1.4.11

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.
@@ -4,6 +4,7 @@ import { SitemapGenerationKey, emptyTypesKey, hashSortedUrlList, hashUrl, hashUr
4
4
  import { QueryDimDeps, QueryDimMeta, QueryDimRecord, QueryDimStore, buildQueryDimRecords, createQueryDimStore } from "./query-dim.mjs";
5
5
  import { SITEMAP_PROJECTION_GRACE_MS, SitemapProjectionFeed, SitemapProjectionFiles, SitemapProjectionManifest, decodeSitemapProjectionManifest, emptySitemapProjectionManifest, parseSitemapProjectionManifest, selectSitemapProjectionFiles, withSitemapProjectionFeed } from "./sitemap-projection.mjs";
6
6
  import { TenantCtx } from "@gscdump/contracts";
7
+ import { CanonicalDifferenceKind } from "gscdump";
7
8
  /**
8
9
  * GSC URL inspection result fields we persist. Mirrors the
9
10
  * `searchconsole_v1.Schema$UrlInspectionResult` shape but as plain JSON
@@ -106,6 +107,8 @@ interface InspectionParquetRow {
106
107
  * newest-by-`inspectedAt` event.
107
108
  */
108
109
  interface InspectionEventRow extends InspectionParquetRow {
110
+ /** Declared-vs-selected canonical classification, computed at inspection ingest. */
111
+ canonicalMismatchKind: CanonicalDifferenceKind;
109
112
  crawlingUserAgent: string | null;
110
113
  /** JSON-encoded `RichResultsItem[]`. */
111
114
  richResultsItems: string | null;
@@ -212,6 +215,15 @@ interface InspectionStore {
212
215
  eventFilesDeleted: number;
213
216
  transitionsWritten: number;
214
217
  }>;
218
+ /**
219
+ * Rewrite a legacy latest-only base with the canonical kind derived from the
220
+ * canonical pair it already retains. Outstanding events must be compacted first.
221
+ */
222
+ backfillCanonicalMismatchKinds: (ctx: TenantCtx) => Promise<{
223
+ baseRowCount: number;
224
+ rowsBackfilled: number;
225
+ rewritten: boolean;
226
+ }>;
215
227
  /**
216
228
  * DuckDB-resolvable URI for the materialised parquet sidecar, or
217
229
  * `undefined` if the underlying `DataSource` has no native URI shape
package/dist/entities.mjs CHANGED
@@ -5,6 +5,7 @@ import { SITEMAP_PROJECTION_GRACE_MS, decodeSitemapProjectionManifest, emptySite
5
5
  import { buildQueryDimRecords, createQueryDimStore } from "./query-dim.mjs";
6
6
  import { encodeJsonBigintSafe } from "@gscdump/lakehouse/bigint";
7
7
  import { GSCDUMP_INDEXING_TRANSITION_FIELDS } from "@gscdump/contracts";
8
+ import { classifyCanonicalDifference } from "gscdump";
8
9
  const YEAR_MONTH_RE = /^(\d{4})-(\d{2})-/;
9
10
  const ENTITY_IO_CONCURRENCY = 8;
10
11
  async function mapEntityIo(items, fn) {
@@ -107,6 +108,11 @@ const INSPECTION_PARQUET_COLUMNS = [
107
108
  ];
108
109
  const INSPECTION_EVENT_COLUMNS = [
109
110
  ...INSPECTION_PARQUET_COLUMNS,
111
+ {
112
+ name: "canonicalMismatchKind",
113
+ type: "VARCHAR",
114
+ nullable: false
115
+ },
110
116
  {
111
117
  name: "crawlingUserAgent",
112
118
  type: "VARCHAR",
@@ -158,6 +164,19 @@ const INSPECTION_EVENT_COLUMNS = [
158
164
  nullable: true
159
165
  }
160
166
  ];
167
+ const CANONICAL_DIFFERENCE_KINDS = /* @__PURE__ */ new Set([
168
+ "none",
169
+ "formatting",
170
+ "path",
171
+ "cross_domain"
172
+ ]);
173
+ function populateCanonicalMismatchKind(row) {
174
+ const current = row.canonicalMismatchKind;
175
+ const expected = classifyCanonicalDifference(typeof row.userCanonical === "string" ? row.userCanonical : null, typeof row.googleCanonical === "string" ? row.googleCanonical : null);
176
+ const missingOrInvalid = typeof current !== "string" || !CANONICAL_DIFFERENCE_KINDS.has(current);
177
+ row.canonicalMismatchKind = expected;
178
+ return missingOrInvalid || current !== expected;
179
+ }
161
180
  const INSPECTION_TRANSITION_COLUMNS = [
162
181
  {
163
182
  name: "urlHash",
@@ -389,6 +408,7 @@ function createInspectionStore(opts) {
389
408
  for (const [h, row] of latest) {
390
409
  const fc = earliestChecked.get(h);
391
410
  if (fc !== void 0) row.firstCheckedAt = fc;
411
+ populateCanonicalMismatchKind(row);
392
412
  merged.push(row);
393
413
  }
394
414
  const bytes = encodeRowsToParquetFlex(merged, {
@@ -405,6 +425,32 @@ function createInspectionStore(opts) {
405
425
  transitionsWritten
406
426
  };
407
427
  },
428
+ async backfillCanonicalMismatchKinds(ctx) {
429
+ const baseKey = inspectionBaseKey(ctx);
430
+ const baseBytes = await readOptional(ds, baseKey);
431
+ if (!baseBytes) return {
432
+ baseRowCount: 0,
433
+ rowsBackfilled: 0,
434
+ rewritten: false
435
+ };
436
+ const rows = await decodeParquetToRows(baseBytes);
437
+ let rowsBackfilled = 0;
438
+ for (const row of rows) if (populateCanonicalMismatchKind(row)) rowsBackfilled++;
439
+ if (rowsBackfilled === 0) return {
440
+ baseRowCount: rows.length,
441
+ rowsBackfilled: 0,
442
+ rewritten: false
443
+ };
444
+ await ds.write(baseKey, encodeRowsToParquetFlex(rows, {
445
+ columns: INSPECTION_EVENT_COLUMNS,
446
+ sortKey: ["urlHash"]
447
+ }));
448
+ return {
449
+ baseRowCount: rows.length,
450
+ rowsBackfilled,
451
+ rewritten: true
452
+ };
453
+ },
408
454
  parquetUri(ctx) {
409
455
  return ds.uri?.(inspectionParquetKey(ctx));
410
456
  }
package/dist/rollups.mjs CHANGED
@@ -978,7 +978,7 @@ const indexingHealthRollup = {
978
978
  SUM(CASE WHEN CAST(pageFetchState AS VARCHAR) = 'NOT_FOUND' THEN 1 ELSE 0 END)::BIGINT AS not_found,
979
979
  SUM(CASE WHEN CAST(mobileUsabilityVerdict AS VARCHAR) = 'PASS' THEN 1 ELSE 0 END)::BIGINT AS mobile_passes,
980
980
  SUM(CASE WHEN CAST(richResultsVerdict AS VARCHAR) = 'PASS' THEN 1 ELSE 0 END)::BIGINT AS rich_results_passes,
981
- SUM(CASE WHEN userCanonical IS NOT NULL AND googleCanonical IS NOT NULL AND CAST(userCanonical AS VARCHAR) <> CAST(googleCanonical AS VARCHAR) THEN 1 ELSE 0 END)::BIGINT AS canonical_mismatches
981
+ SUM(CASE WHEN canonicalMismatchKind IN ('path', 'cross_domain') THEN 1 ELSE 0 END)::BIGINT AS canonical_mismatches
982
982
  FROM read_parquet({{INSPECTIONS}}, union_by_name = true)
983
983
  WHERE substr(CAST(inspectedAt AS VARCHAR), 1, 10) >= '${utcDateMinusDays(windowAnchorMs, 90)}'
984
984
  GROUP BY 1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/engine",
3
3
  "type": "module",
4
- "version": "1.4.8",
4
+ "version": "1.4.11",
5
5
  "description": "Append-only Parquet/DuckDB storage engine + planner + adapters for the gscdump pipeline. Node + edge runtimes; opt-in heavy peers.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -181,9 +181,9 @@
181
181
  "dependencies": {
182
182
  "drizzle-orm": "1.0.0-rc.3",
183
183
  "proper-lockfile": "^4.1.2",
184
- "@gscdump/contracts": "^1.4.8",
185
- "@gscdump/lakehouse": "^1.4.8",
186
- "gscdump": "^1.4.8"
184
+ "@gscdump/contracts": "^1.4.11",
185
+ "@gscdump/lakehouse": "^1.4.11",
186
+ "gscdump": "^1.4.11"
187
187
  },
188
188
  "devDependencies": {
189
189
  "@duckdb/duckdb-wasm": "1.33.1-dev57.0",