@gscdump/engine 1.4.3 → 1.4.5

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.
@@ -14,7 +14,23 @@ declare function sitemapHistoryKey(ctx: TenantCtx, feedpathHash: string, capture
14
14
  declare function sitemapUrlsPrefix(ctx: TenantCtx): string;
15
15
  declare function sitemapUrlsIndexPrefix(ctx: TenantCtx): string;
16
16
  declare function sitemapUrlsIndexKey(ctx: TenantCtx, feedpathHash: string): string;
17
- declare function sitemapUrlsDeltaKey(ctx: TenantCtx, feedpathHash: string, date: string): string;
17
+ declare function sitemapUrlsProjectionManifestKey(ctx: TenantCtx): string;
18
+ interface SitemapGenerationKey {
19
+ id: string;
20
+ observedAt: number;
21
+ }
22
+ declare function sitemapUrlsDeltaKey(ctx: TenantCtx, feedpathHash: string, generation: SitemapGenerationKey): string;
23
+ declare function parseSitemapUrlsDeltaKey(key: string): {
24
+ date: string;
25
+ feedpathHash: string;
26
+ } | undefined;
27
+ declare function sitemapUrlsEventsPrefix(ctx: TenantCtx): string;
28
+ declare function sitemapUrlsEventKey(ctx: TenantCtx, feedpathHash: string, generation: SitemapGenerationKey): string;
29
+ declare function sitemapUrlsEventSeedKey(ctx: TenantCtx, feedpathHash: string): string;
30
+ declare function sitemapUrlsGenerationKey(ctx: TenantCtx, feedpathHash: string): string;
31
+ declare function sitemapUrlsPendingGenerationsPrefix(ctx: TenantCtx): string;
32
+ declare function sitemapUrlsPendingGenerationKey(ctx: TenantCtx, feedpathHash: string): string;
33
+ declare function sitemapUrlsReconcileGenerationKey(ctx: TenantCtx): string;
18
34
  /** Hash a URL list for deterministic change detection. */
19
35
  declare function hashUrlList(urls: readonly {
20
36
  loc: string;
@@ -24,4 +40,4 @@ declare function hashSortedUrlList(locs: readonly string[]): string;
24
40
  declare function indexingMetadataIndexKey(ctx: TenantCtx): string;
25
41
  declare function queryDimParquetKey(ctx: TenantCtx): string;
26
42
  declare function queryDimMetaKey(ctx: TenantCtx): string;
27
- export { emptyTypesKey, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPrefix };
43
+ export { SitemapGenerationKey, emptyTypesKey, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, parseSitemapUrlsDeltaKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsEventKey, sitemapUrlsEventSeedKey, sitemapUrlsEventsPrefix, sitemapUrlsGenerationKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPendingGenerationKey, sitemapUrlsPendingGenerationsPrefix, sitemapUrlsPrefix, sitemapUrlsProjectionManifestKey, sitemapUrlsReconcileGenerationKey };
@@ -54,8 +54,43 @@ function sitemapUrlsIndexPrefix(ctx) {
54
54
  function sitemapUrlsIndexKey(ctx, feedpathHash) {
55
55
  return `${sitemapUrlsIndexPrefix(ctx)}/${feedpathHash}/index.parquet`;
56
56
  }
57
- function sitemapUrlsDeltaKey(ctx, feedpathHash, date) {
58
- return `${sitemapUrlsPrefix(ctx)}/deltas/${date}__${feedpathHash}.parquet`;
57
+ function sitemapUrlsProjectionManifestKey(ctx) {
58
+ return `${sitemapUrlsPrefix(ctx)}/projection.json`;
59
+ }
60
+ function sitemapGenerationDate(generation) {
61
+ return new Date(generation.observedAt).toISOString().slice(0, 10);
62
+ }
63
+ function sitemapUrlsDeltaKey(ctx, feedpathHash, generation) {
64
+ return `${sitemapUrlsPrefix(ctx)}/deltas/${sitemapGenerationDate(generation)}__${feedpathHash}__${String(generation.observedAt).padStart(13, "0")}__${hashUrl(generation.id)}.parquet`;
65
+ }
66
+ const SITEMAP_URLS_DELTA_KEY_RE = /\/urls\/deltas\/(\d{4}-\d{2}-\d{2})__([0-9a-f]+)(?:__\d+__[0-9a-f]+)?\.parquet$/;
67
+ function parseSitemapUrlsDeltaKey(key) {
68
+ const match = SITEMAP_URLS_DELTA_KEY_RE.exec(key);
69
+ return match?.[1] && match[2] ? {
70
+ date: match[1],
71
+ feedpathHash: match[2]
72
+ } : void 0;
73
+ }
74
+ function sitemapUrlsEventsPrefix(ctx) {
75
+ return `${sitemapUrlsPrefix(ctx)}/events`;
76
+ }
77
+ function sitemapUrlsEventKey(ctx, feedpathHash, generation) {
78
+ return `${sitemapUrlsEventsPrefix(ctx)}/${sitemapGenerationDate(generation)}__${feedpathHash}__${String(generation.observedAt).padStart(13, "0")}__${hashUrl(generation.id)}.parquet`;
79
+ }
80
+ function sitemapUrlsEventSeedKey(ctx, feedpathHash) {
81
+ return `${sitemapUrlsPrefix(ctx)}/event-seeds/${feedpathHash}.json`;
82
+ }
83
+ function sitemapUrlsGenerationKey(ctx, feedpathHash) {
84
+ return `${sitemapUrlsPrefix(ctx)}/generations/by-feed/${feedpathHash}.json`;
85
+ }
86
+ function sitemapUrlsPendingGenerationsPrefix(ctx) {
87
+ return `${sitemapUrlsPrefix(ctx)}/generations/pending`;
88
+ }
89
+ function sitemapUrlsPendingGenerationKey(ctx, feedpathHash) {
90
+ return `${sitemapUrlsPendingGenerationsPrefix(ctx)}/${feedpathHash}.json`;
91
+ }
92
+ function sitemapUrlsReconcileGenerationKey(ctx) {
93
+ return `${sitemapUrlsPrefix(ctx)}/generations/reconcile.json`;
59
94
  }
60
95
  function hashUrlList(urls) {
61
96
  return hashSortedUrlList(urls.map((url) => url.loc).sort());
@@ -90,4 +125,4 @@ function queryDimParquetKey(ctx) {
90
125
  function queryDimMetaKey(ctx) {
91
126
  return `${queryDimPrefix(ctx)}/index.json`;
92
127
  }
93
- export { emptyTypesKey, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPrefix };
128
+ export { emptyTypesKey, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, parseSitemapUrlsDeltaKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsEventKey, sitemapUrlsEventSeedKey, sitemapUrlsEventsPrefix, sitemapUrlsGenerationKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPendingGenerationKey, sitemapUrlsPendingGenerationsPrefix, sitemapUrlsPrefix, sitemapUrlsProjectionManifestKey, sitemapUrlsReconcileGenerationKey };
@@ -2,7 +2,13 @@ import { AnalysisParams, AnalysisResult } from "../analysis-types.mjs";
2
2
  import { ComparisonMode, ResolvedWindow, WindowPreset } from "../period/index.mjs";
3
3
  /** Status vocabulary mirrors `ActionPrioritySourceStatus`. */
4
4
  type ReportStepStatus = 'pending' | 'running' | 'done' | 'skipped' | 'error';
5
- type ReportSeverity = 'info' | 'low' | 'medium' | 'high';
5
+ /**
6
+ * `unknown` is not a rung on the info→high ladder: it means the section's
7
+ * backing analyzer(s) failed, so no severity could be assessed. The report
8
+ * runtime is the only writer — a report's `reduce` never returns it. See
9
+ * `ReportPlanStep.feeds`.
10
+ */
11
+ type ReportSeverity = 'info' | 'low' | 'medium' | 'high' | 'unknown';
6
12
  type ReportEntityKind = 'page' | 'query';
7
13
  type ReportActionKind = 'analyzer' | 'cli' | 'indexing' | 'fix';
8
14
  type ReportCoverage = 'full' | 'partial';
@@ -72,6 +78,23 @@ interface ReportPlanStep {
72
78
  params: Omit<AnalysisParams, 'type'>;
73
79
  /** Required steps fail the report; optional steps degrade `coverage`. */
74
80
  required?: boolean;
81
+ /**
82
+ * Ids of the `ReportSection`s this step's result feeds. Defaults to
83
+ * `[key]`, which is the common case (section id === step key).
84
+ *
85
+ * This is what lets the runtime tell "the analyzer failed" apart from "the
86
+ * analyzer returned zero rows": a section whose feeding steps ALL errored
87
+ * carries no information, and the runtime replaces it with an explicit
88
+ * `severity: 'unknown'` shape instead of letting `reduce` publish
89
+ * aggregates computed over an empty array. A section fed by a mix of
90
+ * failed and successful steps still has real content and is left alone
91
+ * (its `coverage` stays `partial`).
92
+ *
93
+ * Declare it whenever a section id differs from the step key, or a step
94
+ * feeds several sections. `report-unavailable.test.ts` fails on any
95
+ * emitted section no step claims.
96
+ */
97
+ feeds?: readonly string[];
75
98
  }
76
99
  interface ReportStepStateMeta {
77
100
  key: string;
@@ -423,8 +423,8 @@ declare const sitemapHealthRollup: RollupDef;
423
423
  /**
424
424
  * Trailing-28-day sitemap URL changes: per-day per-feedpath {added, removed}
425
425
  * counts plus rolling top-200 added and removed URLs. Streams from
426
- * `SitemapStore.loadDeltas()` so it scales independently of how many feeds
427
- * exist on the site.
426
+ * retained `SitemapReadStore.loadEvents()` history. State compaction cannot
427
+ * erase analytics input; memory scales independently of total site state.
428
428
  */
429
429
  declare const sitemapChanges28dRollup: RollupDef;
430
430
  /**
package/dist/rollups.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  import { engineErrors } from "./errors.mjs";
2
2
  import "./layout.mjs";
3
- import { inspectionParquetKey, sitemapUrlsIndexPrefix } from "./entity-keys.mjs";
3
+ import { inspectionParquetKey, sitemapUrlsIndexPrefix, sitemapUrlsPrefix, sitemapUrlsProjectionManifestKey } from "./entity-keys.mjs";
4
4
  import { encodeRowsToParquetFlex } from "./adapters/hyparquet.mjs";
5
+ import { readOptional } from "./adapters/read-optional.mjs";
6
+ import { decodeSitemapProjectionManifest, selectSitemapProjectionFiles } from "./sitemap-projection.mjs";
5
7
  import { createQueryDimStore } from "./query-dim.mjs";
6
- import { createIndexingMetadataStore, createSitemapStore } from "./entities.mjs";
8
+ import { createIndexingMetadataStore, createSitemapReadStore } from "./entities.mjs";
7
9
  import { MS_PER_DAY } from "gscdump/dates";
8
10
  import { encodeJsonBigintSafe } from "@gscdump/lakehouse/bigint";
9
11
  function rollupPrefix(ctx, searchType) {
@@ -1003,16 +1005,71 @@ const indexingHealthRollup = {
1003
1005
  })) };
1004
1006
  }
1005
1007
  };
1008
+ function currentSitemapProjectionRelation(hasIndexes, hasDeltas) {
1009
+ const sources = [];
1010
+ if (hasIndexes) sources.push(`
1011
+ SELECT
1012
+ feedpath_hash,
1013
+ url_hash,
1014
+ loc,
1015
+ removed_at,
1016
+ greatest(
1017
+ coalesce(removed_at, 0),
1018
+ coalesce(last_seen_at, 0),
1019
+ coalesce(first_seen_at, 0)
1020
+ )::BIGINT AS observed_at,
1021
+ 0::INTEGER AS source_order
1022
+ FROM read_parquet({{URLS_INDEX}}, union_by_name = true)
1023
+ `);
1024
+ if (hasDeltas) sources.push(`
1025
+ SELECT
1026
+ feedpath_hash,
1027
+ url_hash,
1028
+ loc,
1029
+ CASE WHEN op = 'removed' THEN "at" ELSE NULL END AS removed_at,
1030
+ "at"::BIGINT AS observed_at,
1031
+ 1::INTEGER AS source_order
1032
+ FROM read_parquet({{URLS_DELTA}}, union_by_name = true)
1033
+ WHERE op IN ('added', 'removed')
1034
+ `);
1035
+ return `(
1036
+ WITH membership_events AS (
1037
+ ${sources.join("\nUNION ALL\n")}
1038
+ )
1039
+ SELECT feedpath_hash, url_hash, loc, removed_at
1040
+ FROM membership_events
1041
+ QUALIFY row_number() OVER (
1042
+ PARTITION BY feedpath_hash, url_hash
1043
+ ORDER BY observed_at DESC, source_order DESC
1044
+ ) = 1
1045
+ )`;
1046
+ }
1006
1047
  const indexPercentRollup = {
1007
1048
  id: "index_percent",
1008
1049
  windowDays: 90,
1009
1050
  sliceOrthogonal: true,
1010
1051
  async build({ engine, ctx, dataSource, windowAnchorMs, searchType }) {
1011
- const urlsKeys = await dataSource.list(sitemapUrlsIndexPrefix(ctx));
1012
- if (urlsKeys.length === 0) return {
1052
+ const [listedIndexKeys, listedDeltaKeys, manifestBytes] = await Promise.all([
1053
+ dataSource.list(sitemapUrlsIndexPrefix(ctx)),
1054
+ dataSource.list(`${sitemapUrlsPrefix(ctx)}/deltas/`),
1055
+ readOptional(dataSource, sitemapUrlsProjectionManifestKey(ctx))
1056
+ ]);
1057
+ const projection = selectSitemapProjectionFiles(listedIndexKeys, listedDeltaKeys, manifestBytes ? decodeSitemapProjectionManifest(new TextDecoder().decode(manifestBytes)) : void 0);
1058
+ if (projection.indexKeys.length === 0 && projection.deltaKeys.length === 0) return {
1013
1059
  totalSitemapUrls: 0,
1014
1060
  days: []
1015
1061
  };
1062
+ const sitemapFileSets = {
1063
+ ...projection.indexKeys.length > 0 ? { URLS_INDEX: {
1064
+ table: "pages",
1065
+ keys: projection.indexKeys
1066
+ } } : {},
1067
+ ...projection.deltaKeys.length > 0 ? { URLS_DELTA: {
1068
+ table: "pages",
1069
+ keys: projection.deltaKeys
1070
+ } } : {}
1071
+ };
1072
+ const currentMembership = currentSitemapProjectionRelation(projection.indexKeys.length > 0, projection.deltaKeys.length > 0);
1016
1073
  const cutoff = utcDateMinusDays(windowAnchorMs, 90);
1017
1074
  const factSearchType = searchType ?? "web";
1018
1075
  const pagesPartitions = partitionsInRange(await engine.listPartitions({
@@ -1028,10 +1085,7 @@ const indexPercentRollup = {
1028
1085
  table: "pages",
1029
1086
  partitions: pagesPartitions
1030
1087
  },
1031
- URLS: {
1032
- table: "pages",
1033
- keys: urlsKeys
1034
- }
1088
+ ...sitemapFileSets
1035
1089
  },
1036
1090
  searchType: factSearchType,
1037
1091
  sql: `
@@ -1039,7 +1093,7 @@ const indexPercentRollup = {
1039
1093
  p.date AS date,
1040
1094
  COUNT(DISTINCT p.url)::BIGINT AS clicked_urls
1041
1095
  FROM read_parquet({{PAGES}}, union_by_name = true) p
1042
- INNER JOIN read_parquet({{URLS}}, union_by_name = true) s
1096
+ INNER JOIN ${currentMembership} s
1043
1097
  ON s.loc = p.url AND s.removed_at IS NULL
1044
1098
  WHERE p.clicks > 0 AND p.date >= '${cutoff}'
1045
1099
  GROUP BY p.date
@@ -1049,13 +1103,10 @@ const indexPercentRollup = {
1049
1103
  const denom = await engine.runSQL({
1050
1104
  ctx,
1051
1105
  table: "pages",
1052
- fileSets: { URLS: {
1053
- table: "pages",
1054
- keys: urlsKeys
1055
- } },
1106
+ fileSets: sitemapFileSets,
1056
1107
  sql: `
1057
- SELECT COUNT(*)::BIGINT AS total
1058
- FROM read_parquet({{URLS}}, union_by_name = true)
1108
+ SELECT COUNT(DISTINCT loc)::BIGINT AS total
1109
+ FROM ${currentMembership}
1059
1110
  WHERE removed_at IS NULL
1060
1111
  `
1061
1112
  });
@@ -1079,7 +1130,7 @@ const sitemapHealthRollup = {
1079
1130
  windowDays: 90,
1080
1131
  sliceOrthogonal: true,
1081
1132
  async build({ dataSource, ctx, windowAnchorMs }) {
1082
- const index = await createSitemapStore({ dataSource }).loadIndex(ctx);
1133
+ const index = await createSitemapReadStore({ dataSource }).loadIndex(ctx);
1083
1134
  const records = Object.values(index.records);
1084
1135
  const cutoff = utcDateMinusDays(windowAnchorMs, 90);
1085
1136
  const byDay = /* @__PURE__ */ new Map();
@@ -1157,7 +1208,7 @@ const sitemapChanges28dRollup = {
1157
1208
  windowDays: 28,
1158
1209
  sliceOrthogonal: true,
1159
1210
  async build({ dataSource, ctx, windowAnchorMs }) {
1160
- const store = createSitemapStore({ dataSource });
1211
+ const store = createSitemapReadStore({ dataSource });
1161
1212
  const from = utcDateMinusDays(windowAnchorMs, 28);
1162
1213
  const to = utcDateMinusDays(windowAnchorMs, 0);
1163
1214
  const counts = /* @__PURE__ */ new Map();
@@ -1167,11 +1218,11 @@ const sitemapChanges28dRollup = {
1167
1218
  function key(k) {
1168
1219
  return `${k.day}\x00${k.feedpath}`;
1169
1220
  }
1170
- for await (const d of store.loadDeltas(ctx, {
1221
+ for await (const d of store.loadEvents(ctx, {
1171
1222
  from,
1172
1223
  to
1173
1224
  })) {
1174
- const day = new Date(d.at).toISOString().slice(0, 10);
1225
+ const day = new Date(d.observedAt).toISOString().slice(0, 10);
1175
1226
  const k = key({
1176
1227
  day,
1177
1228
  feedpath: d.feedpath
@@ -1185,7 +1236,7 @@ const sitemapChanges28dRollup = {
1185
1236
  const change = {
1186
1237
  loc: d.loc,
1187
1238
  feedpath: d.feedpath,
1188
- at: d.at,
1239
+ at: d.observedAt,
1189
1240
  sequence: sequence++
1190
1241
  };
1191
1242
  if (d.op === "added") {
@@ -0,0 +1,20 @@
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 };
@@ -0,0 +1,57 @@
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 };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/engine",
3
3
  "type": "module",
4
- "version": "1.4.3",
4
+ "version": "1.4.5",
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.3",
185
- "gscdump": "^1.4.3",
186
- "@gscdump/lakehouse": "^1.4.3"
184
+ "@gscdump/contracts": "^1.4.5",
185
+ "@gscdump/lakehouse": "^1.4.5",
186
+ "gscdump": "^1.4.5"
187
187
  },
188
188
  "devDependencies": {
189
189
  "@duckdb/duckdb-wasm": "1.33.1-dev57.0",