@gscdump/engine 5.2.0 → 5.3.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.
@@ -200,16 +200,25 @@ export interface InspectionStore {
200
200
  rowCount: number;
201
201
  }>;
202
202
  /**
203
- * Fold every outstanding event file into the `base.parquet`: latest-per-url
204
- * by max `inspectedAt` (newest-wins), preserving the earliest non-null
205
- * `firstCheckedAt` per url. Writes the new base then deletes the consumed
206
- * event files — file-level only, never row-level (ADR-0002). Idempotent +
207
- * re-runnable: a crash after the base write but before the delete just
208
- * re-folds the same events (newest-wins makes that a no-op). A real read
209
- * failure on the existing base propagates rather than rebuilding from events
210
- * alone (which would drop URLs only the base held).
203
+ * Fold every outstanding event file into a new base: latest-per-url by max
204
+ * `inspectedAt` (newest-wins), preserving the earliest non-null
205
+ * `firstCheckedAt` per url. The first fold reads the legacy in-place
206
+ * `base.parquet`; later folds read the base the manifest names.
211
207
  *
212
- * No-op (no base rewrite) when there are zero outstanding events.
208
+ * Writes the new base under a new immutable key, then publishes the inspection
209
+ * base manifest that names it. The superseded base and the folded event files
210
+ * are retired, not deleted: reads that resolved the previous manifest keep
211
+ * scanning them. A later compaction deletes each retired object once the
212
+ * grace has passed. It also deletes a base that an interrupted compaction
213
+ * wrote but never published. File-level only, never row-level (ADR-0002).
214
+ *
215
+ * Callers serialize compaction per site. Re-runnable: a crash before the
216
+ * manifest write leaves the previous manifest current, and the retry
217
+ * re-folds the same events. A real read failure on the current base
218
+ * propagates rather than rebuilding from events alone.
219
+ *
220
+ * `eventFilesDeleted` counts event files this run deleted, which an earlier
221
+ * run folded. No-op when no event file exists.
213
222
  */
214
223
  compactInspections: (ctx: TenantCtx, opts?: {
215
224
  /**
@@ -224,8 +233,9 @@ export interface InspectionStore {
224
233
  transitionsWritten: number;
225
234
  }>;
226
235
  /**
227
- * Rewrite a legacy latest-only base with the canonical kind derived from the
228
- * canonical pair it already retains. Outstanding events must be compacted first.
236
+ * Publish a copy of the current base with the canonical kind derived from the
237
+ * canonical pair it already retains. Outstanding events must be compacted
238
+ * first. Like compaction, it writes a new immutable base and retires the old one.
229
239
  */
230
240
  backfillCanonicalMismatchKinds: (ctx: TenantCtx) => Promise<{
231
241
  baseRowCount: number;
@@ -246,5 +256,50 @@ export interface InspectionStore {
246
256
  }
247
257
  export interface CreateInspectionStoreOptions {
248
258
  dataSource: DataSource;
259
+ /** Clock for retirement times and the grace check. Defaults to `Date.now`. */
260
+ now?: () => number;
261
+ /** How long a retired object stays readable. Defaults to {@link INSPECTION_RETIRED_GRACE_MS}. */
262
+ graceMs?: number;
249
263
  }
264
+ /**
265
+ * How long compaction keeps a superseded base or a folded event file after it
266
+ * retires it. Every read must finish within this window after it resolves its
267
+ * {@link InspectionReadPlan}.
268
+ *
269
+ * The longest hosted reader is a queue job that holds one plan across several
270
+ * DuckDB queries. The Workers queue wall clock caps that job at 15 minutes, so
271
+ * one hour leaves a wide margin.
272
+ */
273
+ export declare const INSPECTION_RETIRED_GRACE_MS: number;
274
+ /**
275
+ * The base one read scans.
276
+ *
277
+ * - `legacy`: no manifest exists yet. The key is the in-place base an older
278
+ * engine wrote, which may be absent. It stays readable until the first
279
+ * compaction under the manifest retires it.
280
+ * - `published`: an immutable base the manifest names. It never changes, and it
281
+ * exists for at least the grace after a later manifest supersedes it.
282
+ */
283
+ export type InspectionBaseRef = {
284
+ _tag: 'legacy';
285
+ key: string;
286
+ } | {
287
+ _tag: 'published';
288
+ key: string;
289
+ };
290
+ /**
291
+ * What one read scans. Resolve it once per read, then list event files, then
292
+ * scan. Skip each listed event key in `foldedEventKeys`: the base already holds
293
+ * its rows, and the file remains only for reads that resolved an older plan.
294
+ */
295
+ export interface InspectionReadPlan {
296
+ base: InspectionBaseRef;
297
+ foldedEventKeys: ReadonlySet<string>;
298
+ }
299
+ /**
300
+ * Resolve the base a read scans and the event files it must skip. Reads one
301
+ * small manifest object; never the base itself. Throws when the manifest is
302
+ * corrupt, because no base choice is safe then.
303
+ */
304
+ export declare function resolveInspectionReadPlan(source: Pick<DataSource, 'read'>, ctx: TenantCtx): Promise<InspectionReadPlan>;
250
305
  export declare function createInspectionStore(opts: CreateInspectionStoreOptions): InspectionStore;
@@ -1,4 +1,4 @@
1
- import { inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionParquetKey, inspectionTransitionsMonthKey } from "../entity-keys.mjs";
1
+ import { inspectionBaseKey, inspectionBaseManifestKey, inspectionBasesPrefix, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionParquetKey, inspectionTransitionsMonthKey } from "../entity-keys.mjs";
2
2
  import { readOptional } from "../adapters/read-optional.mjs";
3
3
  import { decodeParquetToRows, encodeRowsToParquetFlex } from "../adapters/hyparquet.mjs";
4
4
  import { mapEntityIo } from "./io.mjs";
@@ -6,8 +6,82 @@ import { encodeJsonBigintSafe } from "@gscdump/lakehouse/bigint";
6
6
  import { GSCDUMP_INDEXING_TRANSITION_FIELDS } from "@gscdump/contracts";
7
7
  import { classifyCanonicalDifference } from "gscdump";
8
8
  const YEAR_MONTH_RE = /^(\d{4})-(\d{2})-/;
9
- const INSPECTION_EVENT_KEY_RE = /\/inspections\/events\/\d{4}-\d{2}\/[^/]+\.parquet$/;
10
9
  const INSPECTION_HISTORY_MAX_BYTES = 5242880;
10
+ const INSPECTION_RETIRED_GRACE_MS = 36e5;
11
+ const EVENT_FILE_RE = /^\d{4}-\d{2}\/[^/]+\.parquet$/;
12
+ const IMMUTABLE_BASE_FILE_RE = /^(\d{13,})__[\w-]+\.parquet$/;
13
+ function isInspectionEventKey(ctx, key) {
14
+ const prefix = `${inspectionEventsPrefix(ctx)}/`;
15
+ return key.startsWith(prefix) && EVENT_FILE_RE.test(key.slice(prefix.length));
16
+ }
17
+ function immutableBaseKey(ctx, writtenAt, id) {
18
+ return `${inspectionBasesPrefix(ctx)}/${String(writtenAt).padStart(13, "0")}__${id}.parquet`;
19
+ }
20
+ function immutableBaseWrittenAt(ctx, key) {
21
+ const prefix = `${inspectionBasesPrefix(ctx)}/`;
22
+ if (!key.startsWith(prefix)) return void 0;
23
+ const match = IMMUTABLE_BASE_FILE_RE.exec(key.slice(prefix.length));
24
+ return match ? Number(match[1]) : void 0;
25
+ }
26
+ function invalidManifest(key, reason) {
27
+ throw new Error(`invalid inspection base manifest ${key}: ${reason}`);
28
+ }
29
+ function parseBaseManifest(ctx, key, bytes) {
30
+ let input;
31
+ try {
32
+ input = JSON.parse(new TextDecoder().decode(bytes));
33
+ } catch {
34
+ return invalidManifest(key, "not JSON");
35
+ }
36
+ if (!input || typeof input !== "object" || Array.isArray(input)) return invalidManifest(key, "not an object");
37
+ const manifest = input;
38
+ if (manifest.version !== 1) return invalidManifest(key, `unknown version ${String(manifest.version)}`);
39
+ const baseKey = manifest.baseKey;
40
+ if (typeof baseKey !== "string" || immutableBaseWrittenAt(ctx, baseKey) === void 0) return invalidManifest(key, "base key is not an immutable base of this tenant");
41
+ if (!Array.isArray(manifest.retired)) return invalidManifest(key, "retired is not an array");
42
+ return {
43
+ version: 1,
44
+ baseKey,
45
+ retired: manifest.retired.map((entry) => {
46
+ if (!entry || typeof entry !== "object") return invalidManifest(key, "retired entry is not an object");
47
+ const { key: retiredKey, retiredAt } = entry;
48
+ if (typeof retiredKey !== "string" || retiredKey === baseKey || !(retiredKey === inspectionBaseKey(ctx) || immutableBaseWrittenAt(ctx, retiredKey) !== void 0 || isInspectionEventKey(ctx, retiredKey))) return invalidManifest(key, `retired key ${String(retiredKey)} is not a superseded inspection file`);
49
+ if (typeof retiredAt !== "number" || !Number.isSafeInteger(retiredAt) || retiredAt < 0) return invalidManifest(key, `retired time for ${retiredKey} is invalid`);
50
+ return {
51
+ key: retiredKey,
52
+ retiredAt
53
+ };
54
+ })
55
+ };
56
+ }
57
+ async function readBaseState(source, ctx) {
58
+ const key = inspectionBaseManifestKey(ctx);
59
+ const bytes = await readOptional(source, key);
60
+ return bytes ? {
61
+ _tag: "published",
62
+ manifest: parseBaseManifest(ctx, key, bytes)
63
+ } : {
64
+ _tag: "legacy",
65
+ key: inspectionBaseKey(ctx)
66
+ };
67
+ }
68
+ async function resolveInspectionReadPlan(source, ctx) {
69
+ const state = await readBaseState(source, ctx);
70
+ if (state._tag === "legacy") return {
71
+ base: {
72
+ _tag: "legacy",
73
+ key: state.key
74
+ },
75
+ foldedEventKeys: /* @__PURE__ */ new Set()
76
+ };
77
+ return {
78
+ base: {
79
+ _tag: "published",
80
+ key: state.manifest.baseKey
81
+ },
82
+ foldedEventKeys: new Set(state.manifest.retired.map((entry) => entry.key).filter((key) => isInspectionEventKey(ctx, key)))
83
+ };
84
+ }
11
85
  const INSPECTION_PARQUET_COLUMNS = [
12
86
  {
13
87
  name: "urlHash",
@@ -246,8 +320,40 @@ async function appendTransitions(ds, ctx, rows) {
246
320
  function transitionMonth(row) {
247
321
  return String(row.changedBefore ?? "").slice(0, 7) || "unknown";
248
322
  }
323
+ const EMPTY_COMPACTION = {
324
+ baseRowCount: 0,
325
+ eventsFolded: 0,
326
+ eventFilesDeleted: 0,
327
+ transitionsWritten: 0
328
+ };
249
329
  function createInspectionStore(opts) {
250
330
  const ds = opts.dataSource;
331
+ const now = () => Math.floor((opts.now ?? Date.now)());
332
+ const graceMs = opts.graceMs ?? 36e5;
333
+ async function writeImmutableBase(ctx, bytes) {
334
+ const key = immutableBaseKey(ctx, now(), randomBatchId());
335
+ await ds.write(key, bytes);
336
+ return key;
337
+ }
338
+ async function publishManifest(ctx, manifest) {
339
+ await ds.write(inspectionBaseManifestKey(ctx), encodeJsonBigintSafe(manifest));
340
+ }
341
+ async function sweepRetired(ctx, retired, live, at) {
342
+ const retiredKeys = new Set(retired.map((entry) => entry.key));
343
+ const expired = retired.filter((entry) => entry.retiredAt + graceMs <= at).map((entry) => entry.key);
344
+ const unpublished = (await ds.list(`${inspectionBasesPrefix(ctx)}/`)).filter((key) => {
345
+ const writtenAt = immutableBaseWrittenAt(ctx, key);
346
+ return writtenAt !== void 0 && !retiredKeys.has(key) && writtenAt + graceMs <= at;
347
+ });
348
+ const doomed = [...expired, ...unpublished].filter((key) => !live.has(key));
349
+ if (doomed.length > 0) await ds.delete(doomed);
350
+ return new Set(doomed);
351
+ }
352
+ function countEventKeys(ctx, keys) {
353
+ let count = 0;
354
+ for (const key of keys) if (isInspectionEventKey(ctx, key)) count++;
355
+ return count;
356
+ }
251
357
  function shardFor(record) {
252
358
  const m = YEAR_MONTH_RE.exec(record.inspectedAt);
253
359
  return m ? `${m[1]}-${m[2]}` : "unknown";
@@ -358,15 +464,28 @@ function createInspectionStore(opts) {
358
464
  };
359
465
  },
360
466
  async compactInspections(ctx, opts) {
361
- const eventKeys = (await ds.list(`${inspectionEventsPrefix(ctx)}/`)).filter((k) => INSPECTION_EVENT_KEY_RE.test(k));
362
- if (eventKeys.length === 0) return {
363
- baseRowCount: 0,
364
- eventsFolded: 0,
365
- eventFilesDeleted: 0,
366
- transitionsWritten: 0
367
- };
368
- const baseKey = inspectionBaseKey(ctx);
467
+ const listed = (await ds.list(`${inspectionEventsPrefix(ctx)}/`)).filter((key) => isInspectionEventKey(ctx, key));
468
+ if (listed.length === 0) return EMPTY_COMPACTION;
469
+ const startedAt = now();
470
+ const state = await readBaseState(ds, ctx);
471
+ const retired = state._tag === "published" ? state.manifest.retired : [];
472
+ const retiredKeys = new Set(retired.map((entry) => entry.key));
473
+ const eventKeys = listed.filter((key) => !retiredKeys.has(key));
474
+ const baseKey = state._tag === "published" ? state.manifest.baseKey : state.key;
475
+ if (eventKeys.length === 0) {
476
+ const deleted = await sweepRetired(ctx, retired, /* @__PURE__ */ new Set([baseKey]), startedAt);
477
+ if (state._tag === "published" && retired.some((entry) => deleted.has(entry.key))) await publishManifest(ctx, {
478
+ version: 1,
479
+ baseKey,
480
+ retired: retired.filter((entry) => !deleted.has(entry.key))
481
+ });
482
+ return {
483
+ ...EMPTY_COMPACTION,
484
+ eventFilesDeleted: countEventKeys(ctx, deleted)
485
+ };
486
+ }
369
487
  const baseBytes = await readOptional(ds, baseKey);
488
+ if (!baseBytes && state._tag === "published") throw new Error(`published inspection base missing: ${baseKey}`);
370
489
  const baseRows = baseBytes ? await decodeParquetToRows(baseBytes) : [];
371
490
  const latest = /* @__PURE__ */ new Map();
372
491
  const earliestChecked = /* @__PURE__ */ new Map();
@@ -418,23 +537,43 @@ function createInspectionStore(opts) {
418
537
  sortKey: ["urlHash"]
419
538
  });
420
539
  const transitionsWritten = opts?.transitions ? await appendTransitions(ds, ctx, transitions) : 0;
421
- await ds.write(baseKey, bytes);
422
- if (consumed.length > 0) await ds.delete(consumed);
540
+ const nextBaseKey = await writeImmutableBase(ctx, bytes);
541
+ const deleted = await sweepRetired(ctx, retired, /* @__PURE__ */ new Set([baseKey, nextBaseKey]), startedAt);
542
+ const retiredAt = now();
543
+ await publishManifest(ctx, {
544
+ version: 1,
545
+ baseKey: nextBaseKey,
546
+ retired: [
547
+ ...retired.filter((entry) => !deleted.has(entry.key)),
548
+ ...baseBytes ? [{
549
+ key: baseKey,
550
+ retiredAt
551
+ }] : [],
552
+ ...consumed.map((key) => ({
553
+ key,
554
+ retiredAt
555
+ }))
556
+ ]
557
+ });
423
558
  return {
424
559
  baseRowCount: merged.length,
425
560
  eventsFolded,
426
- eventFilesDeleted: consumed.length,
561
+ eventFilesDeleted: countEventKeys(ctx, deleted),
427
562
  transitionsWritten
428
563
  };
429
564
  },
430
565
  async backfillCanonicalMismatchKinds(ctx) {
431
- const baseKey = inspectionBaseKey(ctx);
566
+ const state = await readBaseState(ds, ctx);
567
+ const baseKey = state._tag === "published" ? state.manifest.baseKey : state.key;
432
568
  const baseBytes = await readOptional(ds, baseKey);
433
- if (!baseBytes) return {
434
- baseRowCount: 0,
435
- rowsBackfilled: 0,
436
- rewritten: false
437
- };
569
+ if (!baseBytes) {
570
+ if (state._tag === "published") throw new Error(`published inspection base missing: ${baseKey}`);
571
+ return {
572
+ baseRowCount: 0,
573
+ rowsBackfilled: 0,
574
+ rewritten: false
575
+ };
576
+ }
438
577
  const rows = await decodeParquetToRows(baseBytes);
439
578
  let rowsBackfilled = 0;
440
579
  for (const row of rows) if (populateCanonicalMismatchKind(row)) rowsBackfilled++;
@@ -443,10 +582,17 @@ function createInspectionStore(opts) {
443
582
  rowsBackfilled: 0,
444
583
  rewritten: false
445
584
  };
446
- await ds.write(baseKey, encodeRowsToParquetFlex(rows, {
447
- columns: INSPECTION_EVENT_COLUMNS,
448
- sortKey: ["urlHash"]
449
- }));
585
+ await publishManifest(ctx, {
586
+ version: 1,
587
+ baseKey: await writeImmutableBase(ctx, encodeRowsToParquetFlex(rows, {
588
+ columns: INSPECTION_EVENT_COLUMNS,
589
+ sortKey: ["urlHash"]
590
+ })),
591
+ retired: [...state._tag === "published" ? state.manifest.retired : [], {
592
+ key: baseKey,
593
+ retiredAt: now()
594
+ }]
595
+ });
450
596
  return {
451
597
  baseRowCount: rows.length,
452
598
  rowsBackfilled,
@@ -458,4 +604,4 @@ function createInspectionStore(opts) {
458
604
  }
459
605
  };
460
606
  }
461
- export { INSPECTION_HISTORY_MAX_BYTES, createInspectionStore };
607
+ export { INSPECTION_HISTORY_MAX_BYTES, INSPECTION_RETIRED_GRACE_MS, createInspectionStore, resolveInspectionReadPlan };
@@ -1,10 +1,10 @@
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
- import { CreateInspectionStoreOptions, INSPECTION_HISTORY_MAX_BYTES, InspectionEventRow, InspectionHistoryShard, InspectionIndex, InspectionParquetRow, InspectionRecord, InspectionStore, createInspectionStore } from "./entities/inspection.mjs";
4
- import { SitemapFeedIdentityResult, SitemapGenerationKey, emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapListKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix } from "./entity-keys.mjs";
3
+ import { CreateInspectionStoreOptions, INSPECTION_HISTORY_MAX_BYTES, INSPECTION_RETIRED_GRACE_MS, InspectionBaseRef, InspectionEventRow, InspectionHistoryShard, InspectionIndex, InspectionParquetRow, InspectionReadPlan, InspectionRecord, InspectionStore, createInspectionStore, resolveInspectionReadPlan } from "./entities/inspection.mjs";
4
+ import { SitemapFeedIdentityResult, SitemapGenerationKey, emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionBaseManifestKey, inspectionBasesPrefix, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapListKey, 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
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
8
  import { CreateSitemapListStoreOptions, SitemapListContent, SitemapListDoc, SitemapListEntry, SitemapListStore, createSitemapListStore } from "./entities/sitemap-list.mjs";
9
9
  import { createSitemapStore } from "./entities/sitemap-write.mjs";
10
- export { type CompleteSitemapGeneration, type CompleteSitemapTraversal, type CreateEmptyTypesStoreOptions, type CreateIndexingMetadataStoreOptions, CreateInspectionStoreOptions, type CreateSitemapListStoreOptions, 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 SitemapListContent, type SitemapListDoc, type SitemapListEntry, type SitemapListStore, type SitemapMembershipEvent, type SitemapMutation, type SitemapReadStore, type SitemapSiteGenerationManifest, type SitemapStagedFeed, type SitemapStore, type SitemapUrlRecord, type StageSitemapGenerationFeedResult, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapListStore, createSitemapReadStore, createSitemapStore, emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapListKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix };
10
+ export { type CompleteSitemapGeneration, type CompleteSitemapTraversal, type CreateEmptyTypesStoreOptions, type CreateIndexingMetadataStoreOptions, CreateInspectionStoreOptions, type CreateSitemapListStoreOptions, type CreateSitemapReadStoreOptions, type CreateSitemapStoreOptions, type DateRange, type EmptyTypesDoc, type EmptyTypesStore, type FinalizeSitemapGenerationResult, INSPECTION_HISTORY_MAX_BYTES, INSPECTION_RETIRED_GRACE_MS, type IndexingMetadataIndex, type IndexingMetadataRecord, type IndexingMetadataStore, InspectionBaseRef, InspectionEventRow, InspectionHistoryShard, InspectionIndex, InspectionParquetRow, InspectionReadPlan, 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 SitemapListContent, type SitemapListDoc, type SitemapListEntry, type SitemapListStore, type SitemapMembershipEvent, type SitemapMutation, type SitemapReadStore, type SitemapSiteGenerationManifest, type SitemapStagedFeed, type SitemapStore, type SitemapUrlRecord, type StageSitemapGenerationFeedResult, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapListStore, createSitemapReadStore, createSitemapStore, emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionBaseManifestKey, inspectionBasesPrefix, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, resolveInspectionReadPlan, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapListKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix };
package/dist/entities.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapListKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix } from "./entity-keys.mjs";
1
+ import { emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionBaseManifestKey, inspectionBasesPrefix, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapListKey, 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
- import { INSPECTION_HISTORY_MAX_BYTES, createInspectionStore } from "./entities/inspection.mjs";
4
+ import { INSPECTION_HISTORY_MAX_BYTES, INSPECTION_RETIRED_GRACE_MS, createInspectionStore, resolveInspectionReadPlan } from "./entities/inspection.mjs";
5
5
  import { buildQueryDimRecords, createQueryDimStore } from "./entities/query-dim.mjs";
6
6
  import { createSitemapReadStore } from "./entities/sitemap.mjs";
7
7
  import { createSitemapListStore } from "./entities/sitemap-list.mjs";
8
8
  import { createSitemapStore } from "./entities/sitemap-write.mjs";
9
- export { INSPECTION_HISTORY_MAX_BYTES, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapListStore, createSitemapReadStore, createSitemapStore, emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapListKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix };
9
+ export { INSPECTION_HISTORY_MAX_BYTES, INSPECTION_RETIRED_GRACE_MS, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapListStore, createSitemapReadStore, createSitemapStore, emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionBaseManifestKey, inspectionBasesPrefix, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, resolveInspectionReadPlan, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapListKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix };
@@ -4,7 +4,19 @@ export declare function emptyTypesKey(ctx: TenantCtx): string;
4
4
  export declare function inspectionParquetKey(ctx: TenantCtx): string;
5
5
  export declare function inspectionEventsPrefix(ctx: TenantCtx): string;
6
6
  export declare function inspectionEventKey(ctx: TenantCtx, yearMonth: string, batchId: string): string;
7
+ /**
8
+ * @deprecated Names only the legacy base that compaction rewrote in place.
9
+ * Compaction now publishes immutable bases through the inspection base
10
+ * manifest. Resolve the current base with `resolveInspectionReadPlan`.
11
+ */
7
12
  export declare function inspectionBaseKey(ctx: TenantCtx): string;
13
+ /**
14
+ * The inspection base manifest: the mutable pointer to the current immutable
15
+ * base. Compaction writes it last, so it is the only publication point.
16
+ */
17
+ export declare function inspectionBaseManifestKey(ctx: TenantCtx): string;
18
+ /** Directory of immutable inspection bases. Each compaction writes one new key. */
19
+ export declare function inspectionBasesPrefix(ctx: TenantCtx): string;
8
20
  /**
9
21
  * Append-only record of URL-inspection STATE CHANGES, partitioned by month.
10
22
  *
@@ -19,6 +19,12 @@ function inspectionEventKey(ctx, yearMonth, batchId) {
19
19
  function inspectionBaseKey(ctx) {
20
20
  return `${tenantEntityPrefix(ctx)}/inspections/base.parquet`;
21
21
  }
22
+ function inspectionBaseManifestKey(ctx) {
23
+ return `${tenantEntityPrefix(ctx)}/inspections/base-manifest.json`;
24
+ }
25
+ function inspectionBasesPrefix(ctx) {
26
+ return `${tenantEntityPrefix(ctx)}/inspections/bases`;
27
+ }
22
28
  function inspectionTransitionsPrefix(ctx) {
23
29
  return `${tenantEntityPrefix(ctx)}/inspections/transitions`;
24
30
  }
@@ -144,4 +150,4 @@ function queryDimParquetKey(ctx) {
144
150
  function queryDimMetaKey(ctx) {
145
151
  return `${queryDimPrefix(ctx)}/index.json`;
146
152
  }
147
- export { emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapListKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix };
153
+ export { emptyTypesKey, hashUrl, indexingMetadataIndexKey, inspectionBaseKey, inspectionBaseManifestKey, inspectionBasesPrefix, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, inspectionTransitionsMonthKey, inspectionTransitionsPrefix, parseSitemapFeedIdentity, queryDimMetaKey, queryDimParquetKey, sitemapImmutableBaseKey, sitemapLegacyImportKey, sitemapListKey, sitemapPayloadHashV1, sitemapSiteGenerationManifestKey, sitemapSiteManifestKey, sitemapSiteManifestsPrefix, sitemapStagedFeedKey, sitemapStagedGenerationPrefix, sitemapUrlsEventKey, sitemapUrlsEventsPrefix, sitemapUrlsPrefix };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/engine",
3
3
  "type": "module",
4
- "version": "5.2.0",
4
+ "version": "5.3.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",
@@ -189,10 +189,10 @@
189
189
  }
190
190
  },
191
191
  "dependencies": {
192
- "@gscdump/contracts": "^5.2.0",
193
- "@gscdump/lakehouse": "^5.2.0",
192
+ "@gscdump/contracts": "^5.3.0",
193
+ "@gscdump/lakehouse": "^5.3.0",
194
194
  "drizzle-orm": "1.0.0-rc.4",
195
- "gscdump": "^5.2.0",
195
+ "gscdump": "^5.3.0",
196
196
  "pathe": "^2.0.3",
197
197
  "proper-lockfile": "^4.1.2"
198
198
  },