@gscdump/lakehouse 0.36.4 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,16 @@
1
1
  import { cachingResolver, icebergAppend, icebergDropTable, icebergManifests, restCatalogConnect, restCatalogCreateNamespace, restCatalogListTables, restCatalogLoadTable, s3SignedResolver } from "./libs/icebird.mjs";
2
+ function bigintJsonReplacer(_key, value) {
3
+ return typeof value === "bigint" ? value.toString() : value;
4
+ }
5
+ function stringifyBigintSafe(value, space) {
6
+ return JSON.stringify(value, bigintJsonReplacer, space);
7
+ }
8
+ function encodeJsonBigintSafe(value) {
9
+ return new TextEncoder().encode(stringifyBigintSafe(value));
10
+ }
11
+ function coerceBigIntToNumber(value) {
12
+ return typeof value === "bigint" ? Number(value) : value;
13
+ }
2
14
  async function cacheGet(cache, key, now) {
3
15
  const boxed = await cache.storage.getItem(key).catch(() => null);
4
16
  if (!boxed || typeof boxed.exp !== "number" || boxed.exp <= now) return void 0;
@@ -297,7 +309,7 @@ async function loadSnapshotId(conn, namespace, table, cache, now) {
297
309
  if (cache) {
298
310
  await cachePut(cache, snapshotRefKey(scope, namespace, table), snapshotId, SNAPSHOT_REF_TTL_MS, now);
299
311
  if (snapshotId != null) {
300
- if (JSON.stringify(metadata).length <= MAX_CACHED_METADATA_BYTES) await cachePut(cache, metadataRefKey(scope, namespace, table, snapshotId), metadata, METADATA_TTL_MS, now);
312
+ if (stringifyBigintSafe(metadata).length <= MAX_CACHED_METADATA_BYTES) await cachePut(cache, metadataRefKey(scope, namespace, table, snapshotId), metadata, METADATA_TTL_MS, now);
301
313
  }
302
314
  }
303
315
  return {
@@ -373,4 +385,4 @@ async function resolveIcebergDataFiles(conn, opts) {
373
385
  }
374
386
  return out;
375
387
  }
376
- export { ICEBERG_TYPE_MAP, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, invalidateSnapshotRef, isCommitRateLimited, listIcebergTables, resolveIcebergDataFiles };
388
+ export { ICEBERG_TYPE_MAP, bigintJsonReplacer, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, coerceBigIntToNumber, connectIcebergCatalog, dropIcebergTables, encodeJsonBigintSafe, ensureIcebergNamespace, icebergAppendRetrying, invalidateSnapshotRef, isCommitRateLimited, listIcebergTables, resolveIcebergDataFiles, stringifyBigintSafe };
@@ -527,7 +527,7 @@ function icebergLatestVersion({ tableUrl, resolver, lister }) {
527
527
  return versions[versions.length - 1];
528
528
  });
529
529
  }).catch((err) => {
530
- throw new Error(`failed to determine latest iceberg version: ${err.message}`);
530
+ throw new Error(`failed to determine latest iceberg version of ${tableUrl}: ${err.message}`);
531
531
  });
532
532
  }
533
533
  async function resolveMetadata({ tableUrl, metadataFileName, resolver, lister }) {
package/dist/index.d.mts CHANGED
@@ -1,5 +1,43 @@
1
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
+ /**
4
+ * BigInt at JSON boundaries — the single place this policy lives.
5
+ *
6
+ * int64 reaches serialization from two origins:
7
+ * 1. DuckDB `SUM`/`COUNT` aggregates — already coerced to `number` at the
8
+ * source-factory boundary (`@gscdump/engine` `coerceRows`), so rows are
9
+ * BigInt-free by the time they cross the query boundary.
10
+ * 2. icebird / REST-catalog metadata (`current-snapshot-id`, `sequence-number`,
11
+ * `snapshot-id`) and other identity ids — these never pass through a source,
12
+ * so they arrive at envelope / cache / log serializers still as BigInt.
13
+ *
14
+ * A plain `JSON.stringify` throws `Do not know how to serialize a BigInt`, which
15
+ * is how a stray identity int64 turns into a request 500. Any boundary that can
16
+ * see origin (2) must serialize through here.
17
+ *
18
+ * TWO policies, because the correct target type depends on what the value is:
19
+ *
20
+ * - Identity / precision values (snapshot ids, large row ids) can exceed 2^53
21
+ * and must round-trip losslessly → serialize as a decimal STRING. Use
22
+ * {@link stringifyBigintSafe} / {@link bigintJsonReplacer} / {@link encodeJsonBigintSafe}
23
+ * for storage / transport / log serialization. Where values are already
24
+ * number-coerced (origin 1), the replacer is a no-op backstop.
25
+ *
26
+ * - Analytics aggregates that you want as real numbers downstream → coerce with
27
+ * {@link coerceBigIntToNumber}. Lossy above 2^53 by design; click / impression
28
+ * columns never reach that range. This is the scalar behind engine `coerceRows`.
29
+ */
30
+ /** `JSON.stringify` replacer: BigInt → decimal string (lossless). Pass-through otherwise. */
31
+ declare function bigintJsonReplacer(_key: string, value: unknown): unknown;
32
+ /** `JSON.stringify` that never throws on BigInt; identity ids stay lossless as strings. */
33
+ declare function stringifyBigintSafe(value: unknown, space?: string | number): string;
34
+ /** UTF-8 JSON bytes, BigInt-safe — the payload shape for `DataSource.write`. */
35
+ declare function encodeJsonBigintSafe(value: unknown): Uint8Array;
36
+ /**
37
+ * BigInt → number for analytics aggregates (< 2^53). Lossy above; acceptable for
38
+ * click / impression sums. Non-BigInt values pass through untouched.
39
+ */
40
+ declare function coerceBigIntToNumber<T>(value: T): T | number;
3
41
  /**
4
42
  * Closed identity shapes (ADR-0021 amendments 2 + 4). `'site-int'` is the
5
43
  * team-scoped numeric Catalog Site Id (nuxtseo ADR-0091) stored under the
@@ -156,4 +194,4 @@ interface RunPyIcebergWriterOptions {
156
194
  }
157
195
  declare function resolvePyIcebergPython(override?: string): string;
158
196
  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, catalogCacheScope, connectIcebergCatalog, defineIcebergDataset, deriveTableSpec, dropIcebergTables, ensureIcebergNamespace, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles, resolvePyIcebergPython, runPyIcebergWriter, toIcebergDayCount };
197
+ 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, bigintJsonReplacer, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, coerceBigIntToNumber, connectIcebergCatalog, defineIcebergDataset, deriveTableSpec, dropIcebergTables, encodeJsonBigintSafe, ensureIcebergNamespace, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles, resolvePyIcebergPython, runPyIcebergWriter, stringifyBigintSafe, toIcebergDayCount };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
+ import { ICEBERG_TYPE_MAP, bigintJsonReplacer, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, coerceBigIntToNumber, connectIcebergCatalog, dropIcebergTables, encodeJsonBigintSafe, ensureIcebergNamespace, icebergAppendRetrying, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles, stringifyBigintSafe } from "./_chunks/catalog.mjs";
1
2
  import { icebergCreateTable } from "./_chunks/libs/icebird.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;
@@ -113,9 +113,6 @@ function toIcebergDayCount(value) {
113
113
  if (Number.isNaN(ms)) throw new TypeError(`toIcebergDayCount: invalid date string '${value}'`);
114
114
  return Math.floor(ms / DAY_MILLIS);
115
115
  }
116
- function coerceJsonSafe(value) {
117
- return typeof value === "bigint" ? Number(value) : value;
118
- }
119
116
  function asInt32(value) {
120
117
  if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") return null;
121
118
  const n = Number(value);
@@ -141,7 +138,7 @@ function buildRowProcessor(def, tableSpec) {
141
138
  }
142
139
  }
143
140
  for (const name of dimNames) out[name] = def.dims[name].toPartitionValue(String(row[name]));
144
- for (const col of def.columns) out[col.name] = coerceJsonSafe(row[col.name]);
141
+ for (const col of def.columns) out[col.name] = coerceBigIntToNumber(row[col.name]);
145
142
  return out;
146
143
  }
147
144
  function dedupe(rows) {
@@ -353,4 +350,4 @@ async function runPyIcebergWriter(options) {
353
350
  });
354
351
  }
355
352
  const DEFAULT_PARTITION_KEY_ENCODING = "int";
356
- export { DEFAULT_PARTITION_KEY_ENCODING, ICEBERG_TYPE_MAP, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, connectIcebergCatalog, defineIcebergDataset, deriveTableSpec, dropIcebergTables, ensureIcebergNamespace, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles, resolvePyIcebergPython, runPyIcebergWriter, toIcebergDayCount };
353
+ export { DEFAULT_PARTITION_KEY_ENCODING, ICEBERG_TYPE_MAP, bigintJsonReplacer, buildManifestPartitionFilter, cacheGet, cachePut, catalogCacheScope, coerceBigIntToNumber, connectIcebergCatalog, defineIcebergDataset, deriveTableSpec, dropIcebergTables, encodeJsonBigintSafe, ensureIcebergNamespace, invalidateSnapshotRef, listIcebergTables, resolveIcebergDataFiles, resolvePyIcebergPython, runPyIcebergWriter, stringifyBigintSafe, toIcebergDayCount };
@@ -1,5 +1,5 @@
1
- import { icebergAppend, icebergCreateTable, icebergDropTable, icebergManifests, restCatalogConnect, restCatalogCreateNamespace, restCatalogListTables, restCatalogLoadTable, s3SignedResolver } from "./_chunks/libs/icebird.mjs";
2
1
  import { icebergAppendRetrying, isCommitRateLimited } from "./_chunks/catalog.mjs";
2
+ import { icebergAppend, icebergCreateTable, icebergDropTable, icebergManifests, restCatalogConnect, restCatalogCreateNamespace, restCatalogListTables, restCatalogLoadTable, s3SignedResolver } from "./_chunks/libs/icebird.mjs";
3
3
  const DEFAULT_GRACE_HOURS = 48;
4
4
  const DEFAULT_MAX_DELETES = 500;
5
5
  const HOUR_MS = 3600 * 1e3;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/lakehouse",
3
3
  "type": "module",
4
- "version": "0.36.4",
4
+ "version": "0.37.1",
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",
@@ -48,7 +48,7 @@
48
48
  "devDependencies": {
49
49
  "@types/node": "^26.1.0",
50
50
  "hyparquet": "^1.26.2",
51
- "icebird": "^0.8.12",
51
+ "icebird": "^0.8.13",
52
52
  "unstorage": "^1.17.5",
53
53
  "vitest": "^4.1.9"
54
54
  },