@gscdump/engine 1.5.4 → 2.0.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.
@@ -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, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapUrlsDeltaKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsEventKey, sitemapUrlsEventSeedKey, sitemapUrlsEventsPrefix, sitemapUrlsGenerationKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPendingGenerationKey, sitemapUrlsPendingGenerationsPrefix, sitemapUrlsPrefix, sitemapUrlsProjectionManifestKey, sitemapUrlsReconcileGenerationKey } from "./entity-keys.mjs";
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 { CompactUrlsOptions, CompactUrlsResult, CompleteSitemapGeneration, CreateSitemapReadStoreOptions, CreateSitemapStoreOptions, DateRange, DeltaEntry, LoadUrlsOptions, ParsedUrl, ReconcileResult, SitemapHistoryDoc, SitemapIndex, SitemapMembershipEvent, SitemapMutation, SitemapReadStore, SitemapRecord, SitemapStore, SitemapUrlRecord, SnapshotUrlsResult } from "./entities/sitemap-shared.mjs";
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 CompactUrlsOptions, type CompactUrlsResult, type CompleteSitemapGeneration, type CreateEmptyTypesStoreOptions, type CreateIndexingMetadataStoreOptions, CreateInspectionStoreOptions, type CreateSitemapReadStoreOptions, type CreateSitemapStoreOptions, type DateRange, type DeltaEntry, type EmptyTypesDoc, type EmptyTypesStore, INSPECTION_HISTORY_MAX_BYTES, type IndexingMetadataIndex, type IndexingMetadataRecord, type IndexingMetadataStore, InspectionEventRow, InspectionHistoryShard, InspectionIndex, InspectionParquetRow, InspectionRecord, InspectionStore, type LoadUrlsOptions, type ParsedUrl, QueryDimDeps, QueryDimMeta, QueryDimRecord, QueryDimStore, type ReconcileResult, SITEMAP_PROJECTION_GRACE_MS, SitemapGenerationKey, type SitemapHistoryDoc, type SitemapIndex, type SitemapMembershipEvent, type SitemapMutation, SitemapProjectionFeed, SitemapProjectionFiles, SitemapProjectionManifest, type SitemapReadStore, type SitemapRecord, type SitemapStore, type SitemapUrlRecord, type SnapshotUrlsResult, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapReadStore, createSitemapStore, decodeSitemapProjectionManifest, emptySitemapProjectionManifest, emptyTypesKey, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapProjectionManifest, parseSitemapUrlsDeltaKey, queryDimMetaKey, queryDimParquetKey, selectSitemapProjectionFiles, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsEventKey, sitemapUrlsEventSeedKey, sitemapUrlsEventsPrefix, sitemapUrlsGenerationKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPendingGenerationKey, sitemapUrlsPendingGenerationsPrefix, sitemapUrlsPrefix, sitemapUrlsProjectionManifestKey, sitemapUrlsReconcileGenerationKey, withSitemapProjectionFeed };
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, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapUrlsDeltaKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsEventKey, sitemapUrlsEventSeedKey, sitemapUrlsEventsPrefix, sitemapUrlsGenerationKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPendingGenerationKey, sitemapUrlsPendingGenerationsPrefix, sitemapUrlsPrefix, sitemapUrlsProjectionManifestKey, sitemapUrlsReconcileGenerationKey } from "./entity-keys.mjs";
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, SITEMAP_PROJECTION_GRACE_MS, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapReadStore, createSitemapStore, decodeSitemapProjectionManifest, emptySitemapProjectionManifest, emptyTypesKey, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapProjectionManifest, parseSitemapUrlsDeltaKey, queryDimMetaKey, queryDimParquetKey, selectSitemapProjectionFiles, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsEventKey, sitemapUrlsEventSeedKey, sitemapUrlsEventsPrefix, sitemapUrlsGenerationKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPendingGenerationKey, sitemapUrlsPendingGenerationsPrefix, sitemapUrlsPrefix, sitemapUrlsProjectionManifestKey, sitemapUrlsReconcileGenerationKey, withSitemapProjectionFeed };
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 };
@@ -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 sitemapUrlsEventSeedKey(ctx: TenantCtx, feedpathHash: string): string;
41
- declare function sitemapUrlsGenerationKey(ctx: TenantCtx, feedpathHash: string): string;
42
- declare function sitemapUrlsPendingGenerationsPrefix(ctx: TenantCtx): string;
43
- declare function sitemapUrlsPendingGenerationKey(ctx: TenantCtx, feedpathHash: string): string;
44
- declare function sitemapUrlsReconcileGenerationKey(ctx: TenantCtx): string;
45
- /** Hash a URL list for deterministic change detection. */
46
- declare function hashUrlList(urls: readonly {
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, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapUrlsDeltaKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsEventKey, sitemapUrlsEventSeedKey, sitemapUrlsEventsPrefix, sitemapUrlsGenerationKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPendingGenerationKey, sitemapUrlsPendingGenerationsPrefix, sitemapUrlsPrefix, sitemapUrlsProjectionManifestKey, sitemapUrlsReconcileGenerationKey };
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 };
@@ -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 sitemapUrlsEventSeedKey(ctx, feedpathHash) {
87
- return `${sitemapUrlsPrefix(ctx)}/event-seeds/${feedpathHash}.json`;
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 sitemapUrlsGenerationKey(ctx, feedpathHash) {
90
- return `${sitemapUrlsPrefix(ctx)}/generations/by-feed/${feedpathHash}.json`;
66
+ function sitemapSiteGenerationManifestKey(ctx, generation) {
67
+ return `${sitemapSiteManifestsPrefix(ctx)}/${String(generation.observedAt).padStart(13, "0")}__${hashUrl(generation.id)}.json`;
91
68
  }
92
- function sitemapUrlsPendingGenerationsPrefix(ctx) {
93
- return `${sitemapUrlsPrefix(ctx)}/generations/pending`;
69
+ function sitemapStagedGenerationPrefix(ctx, generation) {
70
+ return `${sitemapUrlsPrefix(ctx)}/staged/${String(generation.observedAt).padStart(13, "0")}__${hashUrl(generation.id)}`;
94
71
  }
95
- function sitemapUrlsPendingGenerationKey(ctx, feedpathHash) {
96
- return `${sitemapUrlsPendingGenerationsPrefix(ctx)}/${feedpathHash}.json`;
72
+ function sitemapStagedFeedKey(ctx, generation, feedpathHash) {
73
+ return `${sitemapStagedGenerationPrefix(ctx, generation)}/${feedpathHash}.json`;
97
74
  }
98
- function sitemapUrlsReconcileGenerationKey(ctx) {
99
- return `${sitemapUrlsPrefix(ctx)}/generations/reconcile.json`;
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 hashUrlList(urls) {
102
- return hashSortedUrlList(urls.map((url) => url.loc).sort());
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, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapUrlsDeltaKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsEventKey, sitemapUrlsEventSeedKey, sitemapUrlsEventsPrefix, sitemapUrlsGenerationKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPendingGenerationKey, sitemapUrlsPendingGenerationsPrefix, sitemapUrlsPrefix, sitemapUrlsProjectionManifestKey, sitemapUrlsReconcileGenerationKey };
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 live sitemap URLs). Uses a DuckDB JOIN between the
26
- * sitemap urls parquet (`SitemapStore.urlsParquetUri`) and the `pages` fact
27
- * parquet. Total denominator is the count of live URLs in the urls index;
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 per-day series materialized from the sitemap-store JSON
33
- * index. Each `SitemapRecord` carries `urlCount`, `errors`, `warnings`,
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, sitemapUrlsIndexPrefix, sitemapUrlsPrefix, sitemapUrlsProjectionManifestKey } from "../entity-keys.mjs";
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 [listedIndexKeys, listedDeltaKeys, manifestBytes] = await Promise.all([
143
- dataSource.list(sitemapUrlsIndexPrefix(ctx)),
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 sitemapFileSets = {
153
- ...projection.indexKeys.length > 0 ? { URLS_INDEX: {
154
- table: "pages",
155
- keys: projection.indexKeys
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 currentMembership = currentSitemapProjectionRelation(projection.indexKeys.length > 0, projection.deltaKeys.length > 0);
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 AND s.removed_at IS NULL
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 index = await createSitemapReadStore({ dataSource }).loadIndex(ctx);
224
- const records = Object.values(index.records);
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 feeds = [];
228
- for (const r of records) {
229
- const day = (r.capturedAt ?? r.lastDownloaded ?? "").slice(0, 10);
230
- if (!day || day < cutoff) continue;
231
- const errors = Number(r.errors ?? 0);
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: 0,
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: Array.from(byDay.values()).sort((a, b) => a.day < b.day ? -1 : 1),
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": "1.5.4",
4
+ "version": "2.0.0",
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": "^1.5.4",
190
- "@gscdump/lakehouse": "^1.5.4",
191
- "gscdump": "^1.5.4"
189
+ "@gscdump/lakehouse": "^2.0.0",
190
+ "@gscdump/contracts": "^2.0.0",
191
+ "gscdump": "^2.0.0"
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 };