@gscdump/lakehouse 0.35.5 → 0.35.7

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.
@@ -121,6 +121,15 @@ interface IcebergConnection {
121
121
  resolver: ReturnType<typeof cachingResolver>;
122
122
  /** The namespace the dataset's table lives under. */
123
123
  namespace: string;
124
+ /**
125
+ * Catalog identity baked into every cross-isolate cache key. Namespaces are
126
+ * NOT unique across catalogs (every per-team catalog uses the same 'gsc'
127
+ * namespace) while deployments share one KV, so a key of (namespace, table)
128
+ * alone lets one team's snapshot pointer/metadata poison every other team's
129
+ * reads. Set by {@link connectIcebergCatalog}; hand-built connections may
130
+ * omit it ONLY when their cache is not shared across catalogs.
131
+ */
132
+ cacheScope?: string;
124
133
  }
125
134
  declare const ICEBERG_TYPE_MAP: Record<IcebergColumnType, IcebergPrimitiveType>;
126
135
  /** Options for {@link connectIcebergCatalog}. */
@@ -139,6 +148,12 @@ interface ConnectIcebergOptions {
139
148
  * `fetch`, no node builtins.
140
149
  */
141
150
  declare function connectIcebergCatalog(config: IcebergCatalogConfig, opts?: ConnectIcebergOptions): Promise<IcebergConnection>;
151
+ /**
152
+ * The catalog-identity component of every cross-isolate cache key. Exported so
153
+ * write paths that only hold the config (not a connection) can invalidate the
154
+ * exact keys readers populate — see {@link invalidateSnapshotRef}.
155
+ */
156
+ declare function catalogCacheScope(config: Pick<IcebergCatalogConfig, 'catalogUri' | 'warehouse'>): string;
142
157
  /**
143
158
  * Ensure the catalog namespace exists. Idempotent — an "already exists"
144
159
  * response from the REST catalog is swallowed.
@@ -236,6 +251,17 @@ interface ResolveIcebergDataFilesOptions {
236
251
  clock?: () => number;
237
252
  profiler?: QueryProfiler;
238
253
  }
254
+ /**
255
+ * Drop the cached snapshot pointer for `(namespace, table)` so the next read
256
+ * re-loads the table and sees the just-committed snapshot immediately instead
257
+ * of after {@link SNAPSHOT_REF_TTL_MS} (writers call this post-commit; without
258
+ * it worst-case reader staleness is TTL + the downstream ref's own TTL). Only
259
+ * the snapshot REF is dropped: `lh-snapmeta`/`lh-files` entries are keyed by
260
+ * snapshotId, so stale ones age out harmlessly and fresh ones rebuild on the
261
+ * next read. Best-effort like every cache path here — a failed delete only
262
+ * means TTL-bounded staleness, never an error.
263
+ */
264
+ declare function invalidateSnapshotRef(cache: CatalogCache, namespace: string, table: string, cacheScope?: string): Promise<void>;
239
265
  /**
240
266
  * List the parquet data files in the current snapshot of `table`, filtered to
241
267
  * one partition slice (`matches` + `range`). Generic over the partition spec —
@@ -243,4 +269,4 @@ interface ResolveIcebergDataFilesOptions {
243
269
  * the def's own spec + identity/dims values.
244
270
  */
245
271
  declare function resolveIcebergDataFiles(conn: IcebergConnection, opts: ResolveIcebergDataFilesOptions): Promise<IcebergListedDataFile[]>;
246
- export { CatalogCache, CommitRetryOptions, ConnectIcebergOptions, ICEBERG_TYPE_MAP, IcebergCatalogConfig, IcebergConnection, IcebergFieldSummary, IcebergListedDataFile, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergPrimitiveType, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableOpResult, ManifestPartitionFilter, PartitionValueMatch, QueryProfiler, ResolveIcebergDataFilesOptions, buildManifestPartitionFilter, cacheGet, cachePut, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, isCommitRateLimited, listIcebergTables, resolveIcebergDataFiles };
272
+ export { CatalogCache, CommitRetryOptions, ConnectIcebergOptions, ICEBERG_TYPE_MAP, IcebergCatalogConfig, IcebergConnection, IcebergFieldSummary, IcebergListedDataFile, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergPrimitiveType, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableOpResult, ManifestPartitionFilter, PartitionValueMatch, QueryProfiler, ResolveIcebergDataFilesOptions, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, invalidateSnapshotRef, isCommitRateLimited, listIcebergTables, resolveIcebergDataFiles };
@@ -116,9 +116,13 @@ async function connectIcebergCatalog(config, opts = {}) {
116
116
  return {
117
117
  catalog,
118
118
  resolver,
119
- namespace: config.namespace
119
+ namespace: config.namespace,
120
+ cacheScope: catalogCacheScope(config)
120
121
  };
121
122
  }
123
+ function catalogCacheScope(config) {
124
+ return `${config.catalogUri}\0${config.warehouse}`;
125
+ }
122
126
  async function ensureIcebergNamespace(conn) {
123
127
  await restCatalogCreateNamespace(conn.catalog, { namespace: conn.namespace }).catch(() => {});
124
128
  }
@@ -201,14 +205,17 @@ const SNAPSHOT_REF_TTL_MS = 1800 * 1e3;
201
205
  const RESOLVED_FILES_TTL_MS = 1440 * 60 * 1e3;
202
206
  const METADATA_TTL_MS = 1440 * 60 * 1e3;
203
207
  const MAX_CACHED_METADATA_BYTES = 2 * 1024 * 1024;
204
- function snapshotRefKey(namespace, table) {
205
- return `lh-snapref\0${namespace}\0${table}`;
208
+ function snapshotRefKey(scope, namespace, table) {
209
+ return `lh-snapref\0${scope}\0${namespace}\0${table}`;
210
+ }
211
+ async function invalidateSnapshotRef(cache, namespace, table, cacheScope = "") {
212
+ await cache.storage.removeItem(snapshotRefKey(cacheScope, namespace, table)).catch(() => {});
206
213
  }
207
- function metadataRefKey(namespace, table, snapshotId) {
208
- return `lh-snapmeta\0${namespace}\0${table}\0${snapshotId}`;
214
+ function metadataRefKey(scope, namespace, table, snapshotId) {
215
+ return `lh-snapmeta\0${scope}\0${namespace}\0${table}\0${snapshotId}`;
209
216
  }
210
- function resolvedFilesKey(namespace, table, snapshotId, matches, wantedMonths) {
211
- return `lh-files\0${namespace}\0${table}\0${snapshotId}\0${[...matches].map((m) => `${m.field}=${m.value}`).sort().join(",")}\0${[...wantedMonths].sort((a, b) => a - b).join(",")}`;
217
+ function resolvedFilesKey(scope, namespace, table, snapshotId, matches, wantedMonths) {
218
+ return `lh-files\0${scope}\0${namespace}\0${table}\0${snapshotId}\0${[...matches].map((m) => `${m.field}=${m.value}`).sort().join(",")}\0${[...wantedMonths].sort((a, b) => a - b).join(",")}`;
212
219
  }
213
220
  function monthsInRange(range) {
214
221
  const [sy, sm] = range.start.split("-").map(Number);
@@ -237,8 +244,9 @@ function stripBucket(filePath) {
237
244
  return slash >= 0 ? rest.slice(slash + 1) : rest;
238
245
  }
239
246
  async function loadSnapshotId(conn, namespace, table, cache, now) {
247
+ const scope = conn.cacheScope ?? "";
240
248
  if (cache) {
241
- const cached = await cacheGet(cache, snapshotRefKey(namespace, table), now);
249
+ const cached = await cacheGet(cache, snapshotRefKey(scope, namespace, table), now);
242
250
  if (cached !== void 0) return {
243
251
  snapshotId: cached,
244
252
  metadata: null
@@ -251,9 +259,9 @@ async function loadSnapshotId(conn, namespace, table, cache, now) {
251
259
  const raw = metadata["current-snapshot-id"];
252
260
  const snapshotId = raw == null ? null : String(raw);
253
261
  if (cache) {
254
- await cachePut(cache, snapshotRefKey(namespace, table), snapshotId, SNAPSHOT_REF_TTL_MS, now);
262
+ await cachePut(cache, snapshotRefKey(scope, namespace, table), snapshotId, SNAPSHOT_REF_TTL_MS, now);
255
263
  if (snapshotId != null) {
256
- if (JSON.stringify(metadata).length <= MAX_CACHED_METADATA_BYTES) await cachePut(cache, metadataRefKey(namespace, table, snapshotId), metadata, METADATA_TTL_MS, now);
264
+ if (JSON.stringify(metadata).length <= MAX_CACHED_METADATA_BYTES) await cachePut(cache, metadataRefKey(scope, namespace, table, snapshotId), metadata, METADATA_TTL_MS, now);
257
265
  }
258
266
  }
259
267
  return {
@@ -270,7 +278,8 @@ async function resolveIcebergDataFiles(conn, opts) {
270
278
  let { snapshotId, metadata } = await loadSnapshotId(conn, namespace, table, opts.cache, now);
271
279
  endSnapshot?.({ cached: metadata == null && snapshotId != null });
272
280
  if (snapshotId == null) return [];
273
- const filesKey = resolvedFilesKey(namespace, table, snapshotId, opts.matches, wantedMonths);
281
+ const scope = conn.cacheScope ?? "";
282
+ const filesKey = resolvedFilesKey(scope, namespace, table, snapshotId, opts.matches, wantedMonths);
274
283
  if (opts.cache) {
275
284
  const endCache = profiler?.start("iceberg.cache");
276
285
  const cached = await cacheGet(opts.cache, filesKey, now);
@@ -278,7 +287,7 @@ async function resolveIcebergDataFiles(conn, opts) {
278
287
  if (cached !== void 0) return cached;
279
288
  }
280
289
  if (!metadata && opts.cache) {
281
- const cachedMeta = await cacheGet(opts.cache, metadataRefKey(namespace, table, snapshotId), now);
290
+ const cachedMeta = await cacheGet(opts.cache, metadataRefKey(scope, namespace, table, snapshotId), now);
282
291
  if (cachedMeta != null) metadata = cachedMeta;
283
292
  }
284
293
  if (!metadata) {
@@ -323,9 +332,9 @@ async function resolveIcebergDataFiles(conn, opts) {
323
332
  files: out.length
324
333
  });
325
334
  if (opts.cache) {
326
- const freshKey = resolvedFilesKey(namespace, table, snapshotId, opts.matches, wantedMonths);
335
+ const freshKey = resolvedFilesKey(scope, namespace, table, snapshotId, opts.matches, wantedMonths);
327
336
  await cachePut(opts.cache, freshKey, out, RESOLVED_FILES_TTL_MS, now);
328
337
  }
329
338
  return out;
330
339
  }
331
- export { ICEBERG_TYPE_MAP, buildManifestPartitionFilter, cacheGet, cachePut, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, isCommitRateLimited, listIcebergTables, resolveIcebergDataFiles };
340
+ export { ICEBERG_TYPE_MAP, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, invalidateSnapshotRef, isCommitRateLimited, listIcebergTables, resolveIcebergDataFiles };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { CatalogCache, CommitRetryOptions, ConnectIcebergOptions, ICEBERG_TYPE_MAP, IcebergCatalogConfig, IcebergConnection, IcebergFieldSummary, IcebergListedDataFile, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergPrimitiveType, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableOpResult, ManifestPartitionFilter, PartitionValueMatch, QueryProfiler, ResolveIcebergDataFilesOptions, buildManifestPartitionFilter, cacheGet, cachePut, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, listIcebergTables, resolveIcebergDataFiles } from "./_chunks/catalog.mjs";
1
+ import { CatalogCache, CommitRetryOptions, ConnectIcebergOptions, ICEBERG_TYPE_MAP, IcebergCatalogConfig, IcebergConnection, IcebergFieldSummary, IcebergListedDataFile, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergPrimitiveType, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableOpResult, ManifestPartitionFilter, PartitionValueMatch, QueryProfiler, ResolveIcebergDataFilesOptions, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles } from "./_chunks/catalog.mjs";
2
2
  import { DEFAULT_PARTITION_KEY_ENCODING, IcebergColumn, IcebergColumnType, IcebergPartitionField, IcebergPartitionTransform, IcebergS3Config, IcebergTableSpec, PartitionKeyEncoding } from "./_chunks/schema.mjs";
3
3
  /**
4
4
  * Closed identity shapes (ADR-0021 amendments 2 + 4). `'site-int'` is the
@@ -156,4 +156,4 @@ interface RunPyIcebergWriterOptions {
156
156
  }
157
157
  declare function resolvePyIcebergPython(override?: string): string;
158
158
  declare function runPyIcebergWriter<T extends PyIcebergWriterResult>(options: RunPyIcebergWriterOptions): Promise<T>;
159
- export { type AppendCommitOptions, type AppendResult, type AppendSink, type AppendSinkCloseResult, type AppendSinkOptions, type CatalogCache, type CommitRetryOptions, type ConnectIcebergOptions, DEFAULT_PARTITION_KEY_ENCODING, type DatasetDim, type DatasetIdentity, ICEBERG_TYPE_MAP, type IcebergCatalogConfig, type IcebergColumn, type IcebergColumnType, type IcebergConnection, type IcebergDataset, type IcebergDatasetColumnDef, type IcebergDatasetDef, type IcebergDatasetLedger, type IcebergFieldSummary, type IcebergListedDataFile, type IcebergPartitionField, type IcebergPartitionSpec, type IcebergPartitionSpecField, type IcebergPartitionTransform, type IcebergPrimitiveType, type IcebergS3Config, type IcebergSchema, type IcebergSchemaField, type IcebergSortOrder, type IcebergSortOrderField, type IcebergTableOpResult, type IcebergTableSpec, type ManifestPartitionFilter, type PartitionKeyEncoding, type PartitionValueMatch, type PyIcebergWriterResult, type QueryProfiler, type ResolveDataFilesOptions, type ResolveIcebergDataFilesOptions, type RunPyIcebergWriterOptions, buildManifestPartitionFilter, cacheGet, cachePut, connectIcebergCatalog, defineIcebergDataset, deriveTableSpec, dropIcebergTables, ensureIcebergNamespace, listIcebergTables, resolveIcebergDataFiles, resolvePyIcebergPython, runPyIcebergWriter, toIcebergDayCount };
159
+ export { type AppendCommitOptions, type AppendResult, type AppendSink, type AppendSinkCloseResult, type AppendSinkOptions, type CatalogCache, type CommitRetryOptions, type ConnectIcebergOptions, DEFAULT_PARTITION_KEY_ENCODING, type DatasetDim, type DatasetIdentity, ICEBERG_TYPE_MAP, type IcebergCatalogConfig, type IcebergColumn, type IcebergColumnType, type IcebergConnection, type IcebergDataset, type IcebergDatasetColumnDef, type IcebergDatasetDef, type IcebergDatasetLedger, type IcebergFieldSummary, type IcebergListedDataFile, type IcebergPartitionField, type IcebergPartitionSpec, type IcebergPartitionSpecField, type IcebergPartitionTransform, type IcebergPrimitiveType, type IcebergS3Config, type IcebergSchema, type IcebergSchemaField, type IcebergSortOrder, type IcebergSortOrderField, type IcebergTableOpResult, type IcebergTableSpec, type ManifestPartitionFilter, type PartitionKeyEncoding, type PartitionValueMatch, type PyIcebergWriterResult, type QueryProfiler, type ResolveDataFilesOptions, type ResolveIcebergDataFilesOptions, type RunPyIcebergWriterOptions, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, connectIcebergCatalog, defineIcebergDataset, deriveTableSpec, dropIcebergTables, ensureIcebergNamespace, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles, resolvePyIcebergPython, runPyIcebergWriter, toIcebergDayCount };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { icebergCreateTable } from "./_chunks/libs/icebird.mjs";
2
- import { ICEBERG_TYPE_MAP, buildManifestPartitionFilter, cacheGet, cachePut, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, listIcebergTables, resolveIcebergDataFiles } from "./_chunks/catalog.mjs";
2
+ import { ICEBERG_TYPE_MAP, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles } from "./_chunks/catalog.mjs";
3
3
  import process from "node:process";
4
4
  const INT32_MIN = -2147483648;
5
5
  const INT32_MAX = 2147483647;
@@ -353,4 +353,4 @@ async function runPyIcebergWriter(options) {
353
353
  });
354
354
  }
355
355
  const DEFAULT_PARTITION_KEY_ENCODING = "int";
356
- export { DEFAULT_PARTITION_KEY_ENCODING, ICEBERG_TYPE_MAP, buildManifestPartitionFilter, cacheGet, cachePut, connectIcebergCatalog, defineIcebergDataset, deriveTableSpec, dropIcebergTables, ensureIcebergNamespace, listIcebergTables, resolveIcebergDataFiles, resolvePyIcebergPython, runPyIcebergWriter, toIcebergDayCount };
356
+ export { DEFAULT_PARTITION_KEY_ENCODING, ICEBERG_TYPE_MAP, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, connectIcebergCatalog, defineIcebergDataset, deriveTableSpec, dropIcebergTables, ensureIcebergNamespace, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles, resolvePyIcebergPython, runPyIcebergWriter, toIcebergDayCount };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/lakehouse",
3
3
  "type": "module",
4
- "version": "0.35.5",
4
+ "version": "0.35.7",
5
5
  "description": "Dataset-agnostic Iceberg lakehouse layer + dataset registry for R2 Data Catalog producers (ADR-0021).",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",