@gscdump/engine 1.5.4 → 2.0.1
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/dist/entities/sitemap-generation.mjs +553 -0
- package/dist/entities/sitemap-shared.d.mts +191 -148
- package/dist/entities/sitemap-shared.mjs +12 -131
- package/dist/entities/sitemap-write.mjs +3 -522
- package/dist/entities/sitemap.mjs +52 -76
- package/dist/entities.d.mts +3 -4
- package/dist/entities.mjs +2 -3
- package/dist/entity-keys.d.mts +20 -20
- package/dist/entity-keys.mjs +49 -39
- package/dist/rollups/indexing.d.mts +5 -9
- package/dist/rollups/indexing.mjs +59 -88
- package/package.json +4 -4
- package/dist/entities/sitemap-projection.d.mts +0 -20
- package/dist/entities/sitemap-projection.mjs +0 -57
package/dist/entities.d.mts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { CreateEmptyTypesStoreOptions, EmptyTypesDoc, EmptyTypesStore, createEmptyTypesStore } from "./entities/empty-types.mjs";
|
|
2
2
|
import { CreateIndexingMetadataStoreOptions, IndexingMetadataIndex, IndexingMetadataRecord, IndexingMetadataStore, createIndexingMetadataStore } from "./entities/indexing-metadata.mjs";
|
|
3
3
|
import { CreateInspectionStoreOptions, INSPECTION_HISTORY_MAX_BYTES, InspectionEventRow, InspectionHistoryShard, InspectionIndex, InspectionParquetRow, InspectionRecord, InspectionStore, createInspectionStore } from "./entities/inspection.mjs";
|
|
4
|
-
import { SitemapGenerationKey, emptyTypesKey,
|
|
4
|
+
import { SitemapFeedIdentityResult, SitemapGenerationKey, emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix } from "./entity-keys.mjs";
|
|
5
5
|
import { QueryDimDeps, QueryDimMeta, QueryDimRecord, QueryDimStore, buildQueryDimRecords, createQueryDimStore } from "./entities/query-dim.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { CompleteSitemapGeneration, CompleteSitemapTraversal, CreateSitemapReadStoreOptions, CreateSitemapStoreOptions, DateRange, FinalizeSitemapGenerationResult, IterateSitemapGenerationUrlsOptions, IterateSitemapGenerationUrlsResult, ListSitemapGenerationUrlsOptions, ListSitemapGenerationUrlsResult, ParsedUrl, QuerySitemapGenerationMembershipResult, RecordSitemapLegacyImportResult, SitemapGenerationFeedManifest, SitemapGenerationReadResult, SitemapLegacyImportEvidence, SitemapMembershipEvent, SitemapMutation, SitemapReadStore, SitemapSiteGenerationManifest, SitemapStagedFeed, SitemapStore, SitemapUrlRecord, StageSitemapGenerationFeedResult } from "./entities/sitemap-shared.mjs";
|
|
7
7
|
import { createSitemapReadStore } from "./entities/sitemap.mjs";
|
|
8
|
-
import { SITEMAP_PROJECTION_GRACE_MS, SitemapProjectionFeed, SitemapProjectionFiles, SitemapProjectionManifest, decodeSitemapProjectionManifest, emptySitemapProjectionManifest, parseSitemapProjectionManifest, selectSitemapProjectionFiles, withSitemapProjectionFeed } from "./entities/sitemap-projection.mjs";
|
|
9
8
|
import { createSitemapStore } from "./entities/sitemap-write.mjs";
|
|
10
|
-
export { type
|
|
9
|
+
export { type CompleteSitemapGeneration, type CompleteSitemapTraversal, type CreateEmptyTypesStoreOptions, type CreateIndexingMetadataStoreOptions, CreateInspectionStoreOptions, type CreateSitemapReadStoreOptions, type CreateSitemapStoreOptions, type DateRange, type EmptyTypesDoc, type EmptyTypesStore, type FinalizeSitemapGenerationResult, INSPECTION_HISTORY_MAX_BYTES, type IndexingMetadataIndex, type IndexingMetadataRecord, type IndexingMetadataStore, InspectionEventRow, InspectionHistoryShard, InspectionIndex, InspectionParquetRow, InspectionRecord, InspectionStore, type IterateSitemapGenerationUrlsOptions, type IterateSitemapGenerationUrlsResult, type ListSitemapGenerationUrlsOptions, type ListSitemapGenerationUrlsResult, type ParsedUrl, QueryDimDeps, QueryDimMeta, QueryDimRecord, QueryDimStore, type QuerySitemapGenerationMembershipResult, type RecordSitemapLegacyImportResult, SitemapFeedIdentityResult, type SitemapGenerationFeedManifest, SitemapGenerationKey, type SitemapGenerationReadResult, type SitemapLegacyImportEvidence, type SitemapMembershipEvent, type SitemapMutation, type SitemapReadStore, type SitemapSiteGenerationManifest, type SitemapStagedFeed, type SitemapStore, type SitemapUrlRecord, type StageSitemapGenerationFeedResult, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapReadStore, createSitemapStore, emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix };
|
package/dist/entities.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { emptyTypesKey,
|
|
1
|
+
import { emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix } from "./entity-keys.mjs";
|
|
2
2
|
import { createEmptyTypesStore } from "./entities/empty-types.mjs";
|
|
3
3
|
import { createIndexingMetadataStore } from "./entities/indexing-metadata.mjs";
|
|
4
4
|
import { INSPECTION_HISTORY_MAX_BYTES, createInspectionStore } from "./entities/inspection.mjs";
|
|
5
5
|
import { buildQueryDimRecords, createQueryDimStore } from "./entities/query-dim.mjs";
|
|
6
|
-
import { SITEMAP_PROJECTION_GRACE_MS, decodeSitemapProjectionManifest, emptySitemapProjectionManifest, parseSitemapProjectionManifest, selectSitemapProjectionFiles, withSitemapProjectionFeed } from "./entities/sitemap-projection.mjs";
|
|
7
6
|
import { createSitemapReadStore } from "./entities/sitemap.mjs";
|
|
8
7
|
import { createSitemapStore } from "./entities/sitemap-write.mjs";
|
|
9
|
-
export { INSPECTION_HISTORY_MAX_BYTES,
|
|
8
|
+
export { INSPECTION_HISTORY_MAX_BYTES, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapReadStore, createSitemapStore, emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix };
|
package/dist/entity-keys.d.mts
CHANGED
|
@@ -20,35 +20,35 @@ declare function inspectionHistoryPrefix(ctx: TenantCtx, yearMonth: string): str
|
|
|
20
20
|
declare function inspectionHistoryShardKey(ctx: TenantCtx, yearMonth: string, batchId: string): string;
|
|
21
21
|
/** Stable, URL-safe FNV-1a hash used for entity keys. */
|
|
22
22
|
declare function hashUrl(url: string): string;
|
|
23
|
-
declare function sitemapIndexKey(ctx: TenantCtx): string;
|
|
24
|
-
declare function sitemapHistoryKey(ctx: TenantCtx, feedpathHash: string, capturedAtMs: number): string;
|
|
25
23
|
declare function sitemapUrlsPrefix(ctx: TenantCtx): string;
|
|
26
|
-
declare function sitemapUrlsIndexPrefix(ctx: TenantCtx): string;
|
|
27
|
-
declare function sitemapUrlsIndexKey(ctx: TenantCtx, feedpathHash: string): string;
|
|
28
|
-
declare function sitemapUrlsProjectionManifestKey(ctx: TenantCtx): string;
|
|
29
24
|
interface SitemapGenerationKey {
|
|
30
25
|
id: string;
|
|
31
26
|
observedAt: number;
|
|
32
27
|
}
|
|
33
|
-
declare function sitemapUrlsDeltaKey(ctx: TenantCtx, feedpathHash: string, generation: SitemapGenerationKey): string;
|
|
34
|
-
declare function parseSitemapUrlsDeltaKey(key: string): {
|
|
35
|
-
date: string;
|
|
36
|
-
feedpathHash: string;
|
|
37
|
-
} | undefined;
|
|
38
28
|
declare function sitemapUrlsEventsPrefix(ctx: TenantCtx): string;
|
|
39
29
|
declare function sitemapUrlsEventKey(ctx: TenantCtx, feedpathHash: string, generation: SitemapGenerationKey): string;
|
|
40
|
-
declare function
|
|
41
|
-
declare function
|
|
42
|
-
declare function
|
|
43
|
-
declare function
|
|
44
|
-
declare function
|
|
45
|
-
|
|
46
|
-
declare function
|
|
30
|
+
declare function sitemapSiteManifestKey(ctx: TenantCtx): string;
|
|
31
|
+
declare function sitemapSiteManifestsPrefix(ctx: TenantCtx): string;
|
|
32
|
+
declare function sitemapSiteGenerationManifestKey(ctx: TenantCtx, generation: SitemapGenerationKey): string;
|
|
33
|
+
declare function sitemapStagedGenerationPrefix(ctx: TenantCtx, generation: SitemapGenerationKey): string;
|
|
34
|
+
declare function sitemapStagedFeedKey(ctx: TenantCtx, generation: SitemapGenerationKey, feedpathHash: string): string;
|
|
35
|
+
declare function sitemapImmutableBaseKey(ctx: TenantCtx, generation: SitemapGenerationKey, feedpathHash: string): string;
|
|
36
|
+
declare function sitemapLegacyImportKey(ctx: TenantCtx): string;
|
|
37
|
+
/** WHATWG-serialized HTTP(S) feed identity with the fragment removed. */
|
|
38
|
+
type SitemapFeedIdentityResult = {
|
|
39
|
+
_tag: 'ok';
|
|
40
|
+
url: string;
|
|
41
|
+
} | {
|
|
42
|
+
_tag: 'invalid';
|
|
43
|
+
reason: 'credentials' | 'empty' | 'invalid_url' | 'unsupported_protocol';
|
|
44
|
+
};
|
|
45
|
+
declare function parseSitemapFeedIdentity(input: string): SitemapFeedIdentityResult;
|
|
46
|
+
/** Versioned exact payload hash. Lastmod-only changes are intentionally visible. */
|
|
47
|
+
declare function sitemapPayloadHashV1(urls: readonly {
|
|
47
48
|
loc: string;
|
|
49
|
+
lastmod?: string;
|
|
48
50
|
}[]): string;
|
|
49
|
-
/** Hash sorted URL strings as though joined by a newline, without allocating the join. */
|
|
50
|
-
declare function hashSortedUrlList(locs: readonly string[]): string;
|
|
51
51
|
declare function indexingMetadataIndexKey(ctx: TenantCtx): string;
|
|
52
52
|
declare function queryDimParquetKey(ctx: TenantCtx): string;
|
|
53
53
|
declare function queryDimMetaKey(ctx: TenantCtx): string;
|
|
54
|
-
export { SitemapGenerationKey, emptyTypesKey,
|
|
54
|
+
export { SitemapFeedIdentityResult, SitemapGenerationKey, emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix };
|
package/dist/entity-keys.mjs
CHANGED
|
@@ -45,61 +45,71 @@ function hashUrl(url) {
|
|
|
45
45
|
}
|
|
46
46
|
return (hi >>> 0).toString(16).padStart(8, "0") + (lo >>> 0).toString(16).padStart(8, "0");
|
|
47
47
|
}
|
|
48
|
-
function sitemapIndexKey(ctx) {
|
|
49
|
-
return `${tenantEntityPrefix(ctx)}/sitemaps/index.json`;
|
|
50
|
-
}
|
|
51
|
-
function sitemapHistoryKey(ctx, feedpathHash, capturedAtMs) {
|
|
52
|
-
return `${tenantEntityPrefix(ctx)}/sitemaps/history/${feedpathHash}__${capturedAtMs}.json`;
|
|
53
|
-
}
|
|
54
48
|
function sitemapUrlsPrefix(ctx) {
|
|
55
49
|
return `${tenantEntityPrefix(ctx)}/sitemaps/urls`;
|
|
56
50
|
}
|
|
57
|
-
function sitemapUrlsIndexPrefix(ctx) {
|
|
58
|
-
return `${sitemapUrlsPrefix(ctx)}/by-feed`;
|
|
59
|
-
}
|
|
60
|
-
function sitemapUrlsIndexKey(ctx, feedpathHash) {
|
|
61
|
-
return `${sitemapUrlsIndexPrefix(ctx)}/${feedpathHash}/index.parquet`;
|
|
62
|
-
}
|
|
63
|
-
function sitemapUrlsProjectionManifestKey(ctx) {
|
|
64
|
-
return `${sitemapUrlsPrefix(ctx)}/projection.json`;
|
|
65
|
-
}
|
|
66
51
|
function sitemapGenerationDate(generation) {
|
|
67
52
|
return new Date(generation.observedAt).toISOString().slice(0, 10);
|
|
68
53
|
}
|
|
69
|
-
function sitemapUrlsDeltaKey(ctx, feedpathHash, generation) {
|
|
70
|
-
return `${sitemapUrlsPrefix(ctx)}/deltas/${sitemapGenerationDate(generation)}__${feedpathHash}__${String(generation.observedAt).padStart(13, "0")}__${hashUrl(generation.id)}.parquet`;
|
|
71
|
-
}
|
|
72
|
-
const SITEMAP_URLS_DELTA_KEY_RE = /\/urls\/deltas\/(\d{4}-\d{2}-\d{2})__([0-9a-f]+)(?:__\d+__[0-9a-f]+)?\.parquet$/;
|
|
73
|
-
function parseSitemapUrlsDeltaKey(key) {
|
|
74
|
-
const match = SITEMAP_URLS_DELTA_KEY_RE.exec(key);
|
|
75
|
-
return match?.[1] && match[2] ? {
|
|
76
|
-
date: match[1],
|
|
77
|
-
feedpathHash: match[2]
|
|
78
|
-
} : void 0;
|
|
79
|
-
}
|
|
80
54
|
function sitemapUrlsEventsPrefix(ctx) {
|
|
81
55
|
return `${sitemapUrlsPrefix(ctx)}/events`;
|
|
82
56
|
}
|
|
83
57
|
function sitemapUrlsEventKey(ctx, feedpathHash, generation) {
|
|
84
58
|
return `${sitemapUrlsEventsPrefix(ctx)}/${sitemapGenerationDate(generation)}__${feedpathHash}__${String(generation.observedAt).padStart(13, "0")}__${hashUrl(generation.id)}.parquet`;
|
|
85
59
|
}
|
|
86
|
-
function
|
|
87
|
-
return `${sitemapUrlsPrefix(ctx)}/
|
|
60
|
+
function sitemapSiteManifestKey(ctx) {
|
|
61
|
+
return `${sitemapUrlsPrefix(ctx)}/site-manifest.json`;
|
|
62
|
+
}
|
|
63
|
+
function sitemapSiteManifestsPrefix(ctx) {
|
|
64
|
+
return `${sitemapUrlsPrefix(ctx)}/site-manifests`;
|
|
88
65
|
}
|
|
89
|
-
function
|
|
90
|
-
return `${
|
|
66
|
+
function sitemapSiteGenerationManifestKey(ctx, generation) {
|
|
67
|
+
return `${sitemapSiteManifestsPrefix(ctx)}/${String(generation.observedAt).padStart(13, "0")}__${hashUrl(generation.id)}.json`;
|
|
91
68
|
}
|
|
92
|
-
function
|
|
93
|
-
return `${sitemapUrlsPrefix(ctx)}/
|
|
69
|
+
function sitemapStagedGenerationPrefix(ctx, generation) {
|
|
70
|
+
return `${sitemapUrlsPrefix(ctx)}/staged/${String(generation.observedAt).padStart(13, "0")}__${hashUrl(generation.id)}`;
|
|
94
71
|
}
|
|
95
|
-
function
|
|
96
|
-
return `${
|
|
72
|
+
function sitemapStagedFeedKey(ctx, generation, feedpathHash) {
|
|
73
|
+
return `${sitemapStagedGenerationPrefix(ctx, generation)}/${feedpathHash}.json`;
|
|
97
74
|
}
|
|
98
|
-
function
|
|
99
|
-
return `${sitemapUrlsPrefix(ctx)}/
|
|
75
|
+
function sitemapImmutableBaseKey(ctx, generation, feedpathHash) {
|
|
76
|
+
return `${sitemapUrlsPrefix(ctx)}/bases/${feedpathHash}/${String(generation.observedAt).padStart(13, "0")}__${hashUrl(generation.id)}.parquet`;
|
|
100
77
|
}
|
|
101
|
-
function
|
|
102
|
-
return
|
|
78
|
+
function sitemapLegacyImportKey(ctx) {
|
|
79
|
+
return `${sitemapUrlsPrefix(ctx)}/legacy-import.json`;
|
|
80
|
+
}
|
|
81
|
+
function parseSitemapFeedIdentity(input) {
|
|
82
|
+
if (!input) return {
|
|
83
|
+
_tag: "invalid",
|
|
84
|
+
reason: "empty"
|
|
85
|
+
};
|
|
86
|
+
let parsed;
|
|
87
|
+
try {
|
|
88
|
+
parsed = new URL(input);
|
|
89
|
+
} catch {
|
|
90
|
+
return {
|
|
91
|
+
_tag: "invalid",
|
|
92
|
+
reason: "invalid_url"
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return {
|
|
96
|
+
_tag: "invalid",
|
|
97
|
+
reason: "unsupported_protocol"
|
|
98
|
+
};
|
|
99
|
+
if (parsed.username || parsed.password) return {
|
|
100
|
+
_tag: "invalid",
|
|
101
|
+
reason: "credentials"
|
|
102
|
+
};
|
|
103
|
+
parsed.hash = "";
|
|
104
|
+
return {
|
|
105
|
+
_tag: "ok",
|
|
106
|
+
url: parsed.toString()
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function sitemapPayloadHashV1(urls) {
|
|
110
|
+
const byLoc = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const url of urls) byLoc.set(url.loc, url.lastmod ?? null);
|
|
112
|
+
return `v1:${hashSortedUrlList([...byLoc].map(([loc, lastmod]) => JSON.stringify([loc, lastmod])).sort())}`;
|
|
103
113
|
}
|
|
104
114
|
function hashSortedUrlList(locs) {
|
|
105
115
|
let hi = 2166136261;
|
|
@@ -131,4 +141,4 @@ function queryDimParquetKey(ctx) {
|
|
|
131
141
|
function queryDimMetaKey(ctx) {
|
|
132
142
|
return `${queryDimPrefix(ctx)}/index.json`;
|
|
133
143
|
}
|
|
134
|
-
export { emptyTypesKey,
|
|
144
|
+
export { emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix };
|
|
@@ -22,18 +22,14 @@ declare const indexingMetadataRollup: RollupDef;
|
|
|
22
22
|
declare const indexingHealthRollup: RollupDef;
|
|
23
23
|
/**
|
|
24
24
|
* Per-day index-percent: ratio of (sitemap URLs that received GSC clicks on
|
|
25
|
-
* that date) / (total
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* numerator is per-day distinct loc count where pages.clicks > 0.
|
|
25
|
+
* that date) / (total published sitemap URLs). The published generation
|
|
26
|
+
* manifest supplies the exact immutable feed bases. No storage listing or
|
|
27
|
+
* derived projection participates in authority.
|
|
29
28
|
*/
|
|
30
29
|
declare const indexPercentRollup: RollupDef;
|
|
31
30
|
/**
|
|
32
|
-
* Sitemap-health
|
|
33
|
-
*
|
|
34
|
-
* `contentHash`, and `lastDownloaded`. We bucket records by the day of their
|
|
35
|
-
* `capturedAt` (or `lastDownloaded` fallback) and emit per-day aggregates plus
|
|
36
|
-
* a snapshot of per-feed stats at the most recent capture.
|
|
31
|
+
* Sitemap-health history derives exclusively from published complete
|
|
32
|
+
* generations. The newest generation observed on each day is retained.
|
|
37
33
|
*/
|
|
38
34
|
declare const sitemapHealthRollup: RollupDef;
|
|
39
35
|
/**
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import "../layout.mjs";
|
|
2
|
-
import { inspectionParquetKey
|
|
2
|
+
import { inspectionParquetKey } from "../entity-keys.mjs";
|
|
3
3
|
import { readOptional } from "../adapters/read-optional.mjs";
|
|
4
4
|
import { createIndexingMetadataStore } from "../entities/indexing-metadata.mjs";
|
|
5
|
-
import { decodeSitemapProjectionManifest, selectSitemapProjectionFiles } from "../entities/sitemap-projection.mjs";
|
|
6
5
|
import { createSitemapReadStore } from "../entities/sitemap.mjs";
|
|
7
6
|
import "../entities.mjs";
|
|
8
7
|
import { utcDateMinusDays } from "./dates.mjs";
|
|
@@ -95,71 +94,26 @@ const indexingHealthRollup = {
|
|
|
95
94
|
})) };
|
|
96
95
|
}
|
|
97
96
|
};
|
|
98
|
-
function currentSitemapProjectionRelation(hasIndexes, hasDeltas) {
|
|
99
|
-
const sources = [];
|
|
100
|
-
if (hasIndexes) sources.push(`
|
|
101
|
-
SELECT
|
|
102
|
-
feedpath_hash,
|
|
103
|
-
url_hash,
|
|
104
|
-
loc,
|
|
105
|
-
removed_at,
|
|
106
|
-
greatest(
|
|
107
|
-
coalesce(removed_at, 0),
|
|
108
|
-
coalesce(last_seen_at, 0),
|
|
109
|
-
coalesce(first_seen_at, 0)
|
|
110
|
-
)::BIGINT AS observed_at,
|
|
111
|
-
0::INTEGER AS source_order
|
|
112
|
-
FROM read_parquet({{URLS_INDEX}}, union_by_name = true)
|
|
113
|
-
`);
|
|
114
|
-
if (hasDeltas) sources.push(`
|
|
115
|
-
SELECT
|
|
116
|
-
feedpath_hash,
|
|
117
|
-
url_hash,
|
|
118
|
-
loc,
|
|
119
|
-
CASE WHEN op = 'removed' THEN "at" ELSE NULL END AS removed_at,
|
|
120
|
-
"at"::BIGINT AS observed_at,
|
|
121
|
-
1::INTEGER AS source_order
|
|
122
|
-
FROM read_parquet({{URLS_DELTA}}, union_by_name = true)
|
|
123
|
-
WHERE op IN ('added', 'removed')
|
|
124
|
-
`);
|
|
125
|
-
return `(
|
|
126
|
-
WITH membership_events AS (
|
|
127
|
-
${sources.join("\nUNION ALL\n")}
|
|
128
|
-
)
|
|
129
|
-
SELECT feedpath_hash, url_hash, loc, removed_at
|
|
130
|
-
FROM membership_events
|
|
131
|
-
QUALIFY row_number() OVER (
|
|
132
|
-
PARTITION BY feedpath_hash, url_hash
|
|
133
|
-
ORDER BY observed_at DESC, source_order DESC
|
|
134
|
-
) = 1
|
|
135
|
-
)`;
|
|
136
|
-
}
|
|
137
97
|
const indexPercentRollup = {
|
|
138
98
|
id: "index_percent",
|
|
139
99
|
windowDays: 90,
|
|
140
100
|
sliceOrthogonal: true,
|
|
141
101
|
async build({ engine, ctx, dataSource, windowAnchorMs, searchType }) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
dataSource.list(`${sitemapUrlsPrefix(ctx)}/deltas/`),
|
|
145
|
-
readOptional(dataSource, sitemapUrlsProjectionManifestKey(ctx))
|
|
146
|
-
]);
|
|
147
|
-
const projection = selectSitemapProjectionFiles(listedIndexKeys, listedDeltaKeys, manifestBytes ? decodeSitemapProjectionManifest(new TextDecoder().decode(manifestBytes)) : void 0);
|
|
148
|
-
if (projection.indexKeys.length === 0 && projection.deltaKeys.length === 0) return {
|
|
102
|
+
const generation = await createSitemapReadStore({ dataSource }).getSitemapGeneration(ctx);
|
|
103
|
+
if (generation._tag === "unavailable") return {
|
|
149
104
|
totalSitemapUrls: 0,
|
|
150
105
|
days: []
|
|
151
106
|
};
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
} } : {},
|
|
157
|
-
...projection.deltaKeys.length > 0 ? { URLS_DELTA: {
|
|
158
|
-
table: "pages",
|
|
159
|
-
keys: projection.deltaKeys
|
|
160
|
-
} } : {}
|
|
107
|
+
const baseKeys = Object.values(generation.manifest.feeds).sort((left, right) => left.feedpath.localeCompare(right.feedpath)).map((feed) => feed.baseKey);
|
|
108
|
+
if (baseKeys.length === 0) return {
|
|
109
|
+
totalSitemapUrls: 0,
|
|
110
|
+
days: []
|
|
161
111
|
};
|
|
162
|
-
const
|
|
112
|
+
const sitemapFileSets = { SITEMAP_BASES: {
|
|
113
|
+
table: "pages",
|
|
114
|
+
keys: baseKeys
|
|
115
|
+
} };
|
|
116
|
+
const currentMembership = "read_parquet({{SITEMAP_BASES}}, union_by_name = true)";
|
|
163
117
|
const cutoff = utcDateMinusDays(windowAnchorMs, 90);
|
|
164
118
|
const factSearchType = searchType ?? "web";
|
|
165
119
|
const pagesPartitions = partitionsInRange(await engine.listPartitions({
|
|
@@ -184,7 +138,7 @@ const indexPercentRollup = {
|
|
|
184
138
|
COUNT(DISTINCT p.url)::BIGINT AS clicked_urls
|
|
185
139
|
FROM read_parquet({{PAGES}}, union_by_name = true) p
|
|
186
140
|
INNER JOIN ${currentMembership} s
|
|
187
|
-
ON s.loc = p.url
|
|
141
|
+
ON s.loc = p.url
|
|
188
142
|
WHERE p.clicks > 0 AND p.date >= '${cutoff}'
|
|
189
143
|
GROUP BY p.date
|
|
190
144
|
ORDER BY p.date
|
|
@@ -197,7 +151,6 @@ const indexPercentRollup = {
|
|
|
197
151
|
sql: `
|
|
198
152
|
SELECT COUNT(DISTINCT loc)::BIGINT AS total
|
|
199
153
|
FROM ${currentMembership}
|
|
200
|
-
WHERE removed_at IS NULL
|
|
201
154
|
`
|
|
202
155
|
});
|
|
203
156
|
const total = Number(denom.rows[0]?.total ?? 0);
|
|
@@ -215,47 +168,64 @@ const indexPercentRollup = {
|
|
|
215
168
|
};
|
|
216
169
|
}
|
|
217
170
|
};
|
|
171
|
+
const SITEMAP_HEALTH_GENERATION_LIMIT = 1e4;
|
|
172
|
+
async function loadSitemapGenerationAncestry(dataSource, current, cutoff) {
|
|
173
|
+
const ancestry = [];
|
|
174
|
+
const seen = /* @__PURE__ */ new Set();
|
|
175
|
+
let cursor = current;
|
|
176
|
+
while (cursor) {
|
|
177
|
+
if (new Date(cursor.observedAt).toISOString().slice(0, 10) < cutoff) break;
|
|
178
|
+
if (ancestry.length >= SITEMAP_HEALTH_GENERATION_LIMIT) throw new Error("sitemap health generation ancestry limit exceeded");
|
|
179
|
+
ancestry.push(cursor);
|
|
180
|
+
if (!cursor.previousManifestKey) break;
|
|
181
|
+
if (seen.has(cursor.previousManifestKey)) throw new Error("sitemap generation manifest ancestry cycle");
|
|
182
|
+
seen.add(cursor.previousManifestKey);
|
|
183
|
+
const bytes = await readOptional(dataSource, cursor.previousManifestKey);
|
|
184
|
+
if (!bytes) throw new Error("sitemap generation manifest ancestry is incomplete");
|
|
185
|
+
const previous = JSON.parse(new TextDecoder().decode(bytes));
|
|
186
|
+
if (previous.generationId !== cursor.previousGenerationId) throw new Error("sitemap generation manifest ancestry is incomplete");
|
|
187
|
+
cursor = previous;
|
|
188
|
+
}
|
|
189
|
+
return ancestry;
|
|
190
|
+
}
|
|
218
191
|
const sitemapHealthRollup = {
|
|
219
192
|
id: "sitemap_health",
|
|
220
193
|
windowDays: 90,
|
|
221
194
|
sliceOrthogonal: true,
|
|
222
195
|
async build({ dataSource, ctx, windowAnchorMs }) {
|
|
223
|
-
const
|
|
224
|
-
|
|
196
|
+
const generation = await createSitemapReadStore({ dataSource }).getSitemapGeneration(ctx);
|
|
197
|
+
if (generation._tag === "unavailable") return {
|
|
198
|
+
days: [],
|
|
199
|
+
feeds: []
|
|
200
|
+
};
|
|
225
201
|
const cutoff = utcDateMinusDays(windowAnchorMs, 90);
|
|
202
|
+
const ancestry = await loadSitemapGenerationAncestry(dataSource, generation.manifest, cutoff);
|
|
226
203
|
const byDay = /* @__PURE__ */ new Map();
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const warnings = Number(r.warnings ?? 0);
|
|
233
|
-
const urlCount = Number(r.urlCount ?? 0);
|
|
234
|
-
const bucket = byDay.get(day) ?? {
|
|
204
|
+
for (const manifest of ancestry) {
|
|
205
|
+
const day = new Date(manifest.observedAt).toISOString().slice(0, 10);
|
|
206
|
+
if (byDay.has(day)) continue;
|
|
207
|
+
const manifestFeeds = Object.values(manifest.feeds);
|
|
208
|
+
byDay.set(day, {
|
|
235
209
|
day,
|
|
236
|
-
feeds:
|
|
237
|
-
total_urls: 0,
|
|
210
|
+
feeds: manifestFeeds.length,
|
|
211
|
+
total_urls: manifestFeeds.reduce((total, feed) => total + feed.urlCount, 0),
|
|
238
212
|
errors: 0,
|
|
239
213
|
warnings: 0
|
|
240
|
-
};
|
|
241
|
-
bucket.feeds += 1;
|
|
242
|
-
bucket.total_urls += urlCount;
|
|
243
|
-
bucket.errors += errors;
|
|
244
|
-
bucket.warnings += warnings;
|
|
245
|
-
byDay.set(day, bucket);
|
|
246
|
-
feeds.push({
|
|
247
|
-
path: r.path,
|
|
248
|
-
urlCount,
|
|
249
|
-
errors,
|
|
250
|
-
warnings,
|
|
251
|
-
contentHash: r.contentHash ?? null,
|
|
252
|
-
lastDownloaded: r.lastDownloaded ?? null,
|
|
253
|
-
capturedAt: r.capturedAt
|
|
254
214
|
});
|
|
255
215
|
}
|
|
216
|
+
const days = Array.from(byDay.values()).sort((a, b) => a.day < b.day ? -1 : 1);
|
|
217
|
+
const capturedAt = new Date(generation.manifest.observedAt).toISOString();
|
|
256
218
|
return {
|
|
257
|
-
days
|
|
258
|
-
feeds
|
|
219
|
+
days,
|
|
220
|
+
feeds: Object.values(generation.manifest.feeds).sort((left, right) => left.feedpath.localeCompare(right.feedpath)).map((feed) => ({
|
|
221
|
+
path: feed.feedpath,
|
|
222
|
+
urlCount: feed.urlCount,
|
|
223
|
+
errors: 0,
|
|
224
|
+
warnings: 0,
|
|
225
|
+
contentHash: feed.payloadHash,
|
|
226
|
+
lastDownloaded: null,
|
|
227
|
+
capturedAt
|
|
228
|
+
}))
|
|
259
229
|
};
|
|
260
230
|
}
|
|
261
231
|
};
|
|
@@ -312,6 +282,7 @@ const sitemapChanges28dRollup = {
|
|
|
312
282
|
from,
|
|
313
283
|
to
|
|
314
284
|
})) {
|
|
285
|
+
if (d.op === "updated") continue;
|
|
315
286
|
const day = new Date(d.observedAt).toISOString().slice(0, 10);
|
|
316
287
|
const k = key({
|
|
317
288
|
day,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/engine",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.1",
|
|
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",
|
|
@@ -186,9 +186,9 @@
|
|
|
186
186
|
"dependencies": {
|
|
187
187
|
"drizzle-orm": "1.0.0-rc.3",
|
|
188
188
|
"proper-lockfile": "^4.1.2",
|
|
189
|
-
"@gscdump/contracts": "^
|
|
190
|
-
"
|
|
191
|
-
"gscdump": "^
|
|
189
|
+
"@gscdump/contracts": "^2.0.1",
|
|
190
|
+
"gscdump": "^2.0.1",
|
|
191
|
+
"@gscdump/lakehouse": "^2.0.1"
|
|
192
192
|
},
|
|
193
193
|
"devDependencies": {
|
|
194
194
|
"@duckdb/duckdb-wasm": "1.33.1-dev57.0",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
declare const SITEMAP_PROJECTION_GRACE_MS: number;
|
|
2
|
-
interface SitemapProjectionFeed {
|
|
3
|
-
compactedThrough: string;
|
|
4
|
-
/** Unix epoch milliseconds when readers started filtering through this key. */
|
|
5
|
-
publishedAt: number;
|
|
6
|
-
}
|
|
7
|
-
interface SitemapProjectionManifest {
|
|
8
|
-
version: 1;
|
|
9
|
-
feeds: Record<string, SitemapProjectionFeed>;
|
|
10
|
-
}
|
|
11
|
-
interface SitemapProjectionFiles {
|
|
12
|
-
indexKeys: string[];
|
|
13
|
-
deltaKeys: string[];
|
|
14
|
-
}
|
|
15
|
-
declare function emptySitemapProjectionManifest(): SitemapProjectionManifest;
|
|
16
|
-
declare function parseSitemapProjectionManifest(value: unknown): SitemapProjectionManifest;
|
|
17
|
-
declare function decodeSitemapProjectionManifest(text: string): SitemapProjectionManifest;
|
|
18
|
-
declare function selectSitemapProjectionFiles(indexKeys: readonly string[], deltaKeys: readonly string[], manifest: SitemapProjectionManifest | undefined): SitemapProjectionFiles;
|
|
19
|
-
declare function withSitemapProjectionFeed(manifest: SitemapProjectionManifest, feedpathHash: string, feed: SitemapProjectionFeed): SitemapProjectionManifest;
|
|
20
|
-
export { SITEMAP_PROJECTION_GRACE_MS, SitemapProjectionFeed, SitemapProjectionFiles, SitemapProjectionManifest, decodeSitemapProjectionManifest, emptySitemapProjectionManifest, parseSitemapProjectionManifest, selectSitemapProjectionFiles, withSitemapProjectionFeed };
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { parseSitemapUrlsDeltaKey } from "../entity-keys.mjs";
|
|
2
|
-
const SITEMAP_PROJECTION_GRACE_MS = 900 * 1e3;
|
|
3
|
-
function emptySitemapProjectionManifest() {
|
|
4
|
-
return {
|
|
5
|
-
version: 1,
|
|
6
|
-
feeds: {}
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
function objectRecord(value) {
|
|
10
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
11
|
-
}
|
|
12
|
-
function parseSitemapProjectionManifest(value) {
|
|
13
|
-
const manifest = objectRecord(value);
|
|
14
|
-
const rawFeeds = objectRecord(manifest?.feeds);
|
|
15
|
-
if (manifest?.version !== 1 || !rawFeeds) throw new Error("invalid sitemap projection manifest");
|
|
16
|
-
const feeds = {};
|
|
17
|
-
for (const [feedpathHash, rawFeed] of Object.entries(rawFeeds)) {
|
|
18
|
-
const feed = objectRecord(rawFeed);
|
|
19
|
-
const compactedThrough = feed?.compactedThrough;
|
|
20
|
-
const publishedAt = feed?.publishedAt;
|
|
21
|
-
if (typeof compactedThrough !== "string") throw new Error(`invalid sitemap projection manifest feed: ${feedpathHash}`);
|
|
22
|
-
const parsedKey = parseSitemapUrlsDeltaKey(compactedThrough);
|
|
23
|
-
if (!/^[0-9a-f]+$/.test(feedpathHash) || parsedKey?.feedpathHash !== feedpathHash || !Number.isSafeInteger(publishedAt) || Number(publishedAt) < 0) throw new Error(`invalid sitemap projection manifest feed: ${feedpathHash}`);
|
|
24
|
-
feeds[feedpathHash] = {
|
|
25
|
-
compactedThrough,
|
|
26
|
-
publishedAt: Number(publishedAt)
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
version: 1,
|
|
31
|
-
feeds
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function decodeSitemapProjectionManifest(text) {
|
|
35
|
-
return parseSitemapProjectionManifest(JSON.parse(text));
|
|
36
|
-
}
|
|
37
|
-
function selectSitemapProjectionFiles(indexKeys, deltaKeys, manifest) {
|
|
38
|
-
return {
|
|
39
|
-
indexKeys: indexKeys.filter((key) => key.endsWith("/index.parquet")),
|
|
40
|
-
deltaKeys: deltaKeys.filter((key) => {
|
|
41
|
-
const parsed = parseSitemapUrlsDeltaKey(key);
|
|
42
|
-
if (!parsed) return false;
|
|
43
|
-
const feed = manifest?.feeds[parsed.feedpathHash];
|
|
44
|
-
return !feed || key > feed.compactedThrough;
|
|
45
|
-
})
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
function withSitemapProjectionFeed(manifest, feedpathHash, feed) {
|
|
49
|
-
return {
|
|
50
|
-
version: 1,
|
|
51
|
-
feeds: {
|
|
52
|
-
...manifest.feeds,
|
|
53
|
-
[feedpathHash]: feed
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
export { SITEMAP_PROJECTION_GRACE_MS, decodeSitemapProjectionManifest, emptySitemapProjectionManifest, parseSitemapProjectionManifest, selectSitemapProjectionFiles, withSitemapProjectionFeed };
|