@gscdump/engine 0.40.2 → 1.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.
Files changed (47) hide show
  1. package/README.md +4 -4
  2. package/dist/_chunks/duckdb.d.mts +2 -9
  3. package/dist/_chunks/engine.mjs +3 -7
  4. package/dist/_chunks/entities.mjs +1 -1
  5. package/dist/_chunks/index.d.mts +3 -8
  6. package/dist/_chunks/index2.d.mts +1 -2
  7. package/dist/_chunks/libs/hyparquet-compressors.mjs +1 -1
  8. package/dist/_chunks/libs/hyparquet.mjs +1 -63
  9. package/dist/_chunks/libs/icebird.d.mts +1 -81
  10. package/dist/_chunks/libs/icebird.mjs +4 -330
  11. package/dist/_chunks/parquet-plan.mjs +248 -3
  12. package/dist/_chunks/resolver.mjs +16 -2
  13. package/dist/_chunks/schema.d.mts +2 -2
  14. package/dist/_chunks/sink.d.mts +4 -5
  15. package/dist/_chunks/source.mjs +3 -3
  16. package/dist/_chunks/storage.d.mts +1 -43
  17. package/dist/_chunks/types.d.mts +8 -0
  18. package/dist/adapters/node.d.mts +1 -11
  19. package/dist/adapters/node.mjs +1 -1
  20. package/dist/analyzer/index.d.mts +1 -1
  21. package/dist/entities.d.mts +2 -10
  22. package/dist/entities.mjs +2 -2
  23. package/dist/iceberg/index.d.mts +2 -3
  24. package/dist/iceberg/index.mjs +12 -12
  25. package/dist/index.d.mts +83 -2
  26. package/dist/index.mjs +365 -6
  27. package/dist/period/index.d.mts +2 -2
  28. package/dist/period/index.mjs +1 -1
  29. package/dist/planner.mjs +1 -2
  30. package/dist/resolver/index.d.mts +2 -9
  31. package/dist/resolver/index.mjs +2 -2
  32. package/dist/rollups.d.mts +3 -20
  33. package/dist/rollups.mjs +5 -5
  34. package/dist/scope.d.mts +1 -2
  35. package/dist/scope.mjs +1 -1
  36. package/dist/source/index.d.mts +2 -2
  37. package/dist/source/index.mjs +2 -2
  38. package/dist/sql-bind.d.mts +1 -29
  39. package/dist/sql-bind.mjs +1 -1
  40. package/package.json +7 -22
  41. package/dist/_chunks/compaction.mjs +0 -251
  42. package/dist/adapters/r2-manifest.d.mts +0 -82
  43. package/dist/adapters/r2-manifest.mjs +0 -364
  44. package/dist/compaction-public.d.mts +0 -15
  45. package/dist/compaction-public.mjs +0 -5
  46. package/dist/snapshot.d.mts +0 -2
  47. package/dist/snapshot.mjs +0 -1
package/README.md CHANGED
@@ -27,7 +27,6 @@ Optional peers (install only what your runtime needs):
27
27
  | `@gscdump/engine/contracts` | `StorageEngine`, `Row`, `TableName`, `WriteCtx`, `ManifestEntry`, ... |
28
28
  | `@gscdump/engine/schema` | `SCHEMAS`, `allTables`, `inferTable`, column metadata. |
29
29
  | `@gscdump/engine/planner` | `resolveToSQL`, `enumeratePartitions`, partition planning. |
30
- | `@gscdump/engine/snapshot` | `SnapshotIndex` contract for hot/cold snapshot files. |
31
30
  | `@gscdump/engine/ingest` | GSC row → storage row helpers (`createRowAccumulator`, `transformGscRow`). |
32
31
  | `@gscdump/engine/sql` | SQL literal binding helpers (`bindLiterals`, `formatLiteral`). |
33
32
  | `@gscdump/engine/sql-fragments` | Reusable SQL fragments shared across analyzers. |
@@ -40,8 +39,7 @@ Optional peers (install only what your runtime needs):
40
39
  | `@gscdump/engine/filesystem` | Node-only `DataSource` + `ManifestStore` adapters. |
41
40
  | `@gscdump/engine/hyparquet` | Pure-JS `ParquetCodec`. |
42
41
  | `@gscdump/engine/r2` | Cloudflare R2 `DataSource` (structurally typed against `R2Bucket`). |
43
- | `@gscdump/engine/r2-manifest` | R2-backed `ManifestStore` for hosted deployments. |
44
- | `@gscdump/engine/iceberg` | Edge-safe GSC Iceberg schema/catalog/append sink. |
42
+ | `@gscdump/engine/iceberg` | Edge-safe GSC dataset schemas, catalog wrappers, and append sink. Generic Iceberg APIs live in `@gscdump/lakehouse`. |
45
43
  | `@gscdump/engine/sink-node` | Node-only Iceberg overwrite/delete recovery writer. |
46
44
  | `@gscdump/engine/source` | Query-source contracts and factories. |
47
45
 
@@ -58,9 +56,11 @@ Optional peers (install only what your runtime needs):
58
56
  ## Related
59
57
 
60
58
  - [`gscdump`](../gscdump) — REST client + query builder (edge-safe peer dep).
61
- - [`@gscdump/analysis`](../analysis) — analyzers; consumes `StorageEngine` via `createEngine` factories.
59
+ - [`@gscdump/analysis`](../analysis) — analyzers; consumes storage through `AnalysisQuerySource` adapters.
62
60
  - [`@gscdump/engine-duckdb-wasm`](../engine-duckdb-wasm) — DuckDB-WASM browser adapter.
63
61
  - [`@gscdump/engine-sqlite`](../engine-sqlite) — SQLite / D1 adapter.
62
+ - [`@gscdump/lakehouse`](../lakehouse) — dataset-agnostic Iceberg catalog and
63
+ dataset registry APIs.
64
64
  - [`@gscdump/cli`](../cli) — CLI wrapping engine + analysis.
65
65
 
66
66
  ## License
@@ -1,4 +1,4 @@
1
- import { ParquetCodec, QueryExecutor, Row, TableName } from "./storage.mjs";
1
+ import { ParquetCodec, QueryExecutor, Row } from "./storage.mjs";
2
2
  interface DuckDBHandle {
3
3
  query: (sql: string, params?: unknown[]) => Promise<Row[]>;
4
4
  registerFileBuffer: (name: string, bytes: Uint8Array) => Promise<void>;
@@ -24,11 +24,4 @@ interface DuckDBReadOptions {
24
24
  }
25
25
  declare function createDuckDBCodec(factory: DuckDBFactory, options?: DuckDBReadOptions): ParquetCodec;
26
26
  declare function createDuckDBExecutor(factory: DuckDBFactory, options?: DuckDBReadOptions): QueryExecutor;
27
- /**
28
- * Canonical "empty-file" SELECT clause for a table. Codecs that need to
29
- * emit a schema-correct empty Parquet can wrap this in:
30
- * `COPY (SELECT * FROM <clause> WHERE FALSE) TO '<key>' (FORMAT PARQUET, COMPRESSION ZSTD)`
31
- * to satisfy the ParquetCodec empty-rows invariant.
32
- */
33
- declare function canonicalEmptyParquetSchema(table: TableName): string;
34
- export { DuckDBFactory, DuckDBHandle, canonicalEmptyParquetSchema, createDuckDBCodec, createDuckDBExecutor };
27
+ export { DuckDBFactory, DuckDBHandle, createDuckDBCodec, createDuckDBExecutor };
@@ -1,9 +1,8 @@
1
- import { coerceRows } from "./coerce.mjs";
2
1
  import { dayPartition, hourPartition, inferSearchType, objectKey, tenantPrefix } from "./layout.mjs";
2
+ import { coerceRows } from "./coerce.mjs";
3
3
  import { SCHEMAS, TABLE_METADATA, currentSchemaVersion, dateColumnsFor, dedupeByNaturalKey } from "./schema.mjs";
4
- import { compactTieredImpl, dedupeOverlappingTiers, splitOverlappingTiers } from "./compaction.mjs";
4
+ import { compactTieredImpl, compileLogicalQueryPlan, dedupeOverlappingTiers, splitOverlappingTiers, substituteNamedFiles } from "./parquet-plan.mjs";
5
5
  import { dateReplaceClause as dateReplaceClause$1 } from "../sql-fragments.mjs";
6
- import { compileLogicalQueryPlan, substituteNamedFiles } from "./parquet-plan.mjs";
7
6
  import { sqlEscape } from "../sql-bind.mjs";
8
7
  import { encodeJsonBigintSafe } from "@gscdump/lakehouse";
9
8
  import { buildLogicalPlan } from "gscdump/query/plan";
@@ -187,9 +186,6 @@ function createDuckDBExecutor(factory, options = {}) {
187
186
  function emptyTableSchema(table) {
188
187
  return `(FROM (VALUES ${placeholderValues(table)}) t(${columnList(table)}))`;
189
188
  }
190
- function canonicalEmptyParquetSchema(table) {
191
- return emptyTableSchema(table);
192
- }
193
189
  function dateReplaceClause(table) {
194
190
  if (!table) return "";
195
191
  return dateReplaceClause$1(dateColumnsFor(table), "string");
@@ -732,4 +728,4 @@ function createStorageEngine(opts) {
732
728
  readObject: (key) => dataSource.read(key)
733
729
  };
734
730
  }
735
- export { MAX_DAY_BYTES, canonicalEmptyParquetSchema, createDuckDBCodec, createDuckDBExecutor, createStorageEngine };
731
+ export { MAX_DAY_BYTES, createDuckDBCodec, createDuckDBExecutor, createStorageEngine };
@@ -1052,4 +1052,4 @@ function createEmptyTypesStore(opts) {
1052
1052
  }
1053
1053
  };
1054
1054
  }
1055
- export { INSPECTION_EVENT_COLUMNS, INSPECTION_HISTORY_MAX_BYTES, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapStore, emptyTypesKey, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix };
1055
+ export { INSPECTION_HISTORY_MAX_BYTES, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapStore, emptyTypesKey, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionIndexKey, inspectionParquetKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix };
@@ -1,8 +1,8 @@
1
1
  import { Row, SearchType as SearchType$1, StorageEngine, TenantCtx } from "./storage.mjs";
2
- import { EngineError } from "./errors.mjs";
3
2
  import { AnalysisParams, AnalysisResult } from "./analysis-types.mjs";
4
3
  import { ResolverAdapter } from "./types.mjs";
5
- import { AnalysisQuerySource, AnalysisSourceKind, AnalyzerRegistry, FileSet, QueryRow, SourceCapabilities } from "./registry.mjs";
4
+ import { AnalysisQuerySource, AnalysisSourceKind, AnalyzerRegistry, QueryRow, SourceCapabilities } from "./registry.mjs";
5
+ import { EngineError } from "./errors.mjs";
6
6
  import "./contracts.mjs";
7
7
  import { PlannerCapabilities } from "gscdump/query/plan";
8
8
  import { BuilderState } from "gscdump/query";
@@ -44,11 +44,6 @@ declare class AttachedTableMissingError extends Error {
44
44
  readonly engineError: EngineError;
45
45
  constructor(missing: readonly string[]);
46
46
  }
47
- /**
48
- * Swap `read_parquet({{KEY}}, union_by_name = true)` for `<schema>.<table>`.
49
- * Tolerates whitespace variation. Preserves the rest of the SQL verbatim.
50
- */
51
- declare function rewriteForTableSource(sql: string, schema: string, fileSets: Record<string, FileSet>): string;
52
47
  declare function createAttachedTableSource(runner: AttachedTableRunner, options: AttachedTableSourceOptions): AnalysisQuerySource;
53
48
  interface CreateSqlQuerySourceOptions<TKey extends string> {
54
49
  /** Debug-only identifier surfaced on the source for error messages. */
@@ -106,4 +101,4 @@ declare function runAnalyzerWithEngine(deps: {
106
101
  engine: StorageEngine;
107
102
  }, ctx: TenantCtx, params: AnalysisParams, registry: AnalyzerRegistry): Promise<AnalysisResult>;
108
103
  declare function queryRows<TRow = QueryRow>(source: AnalysisQuerySource, state: BuilderState): Promise<TRow[]>;
109
- export { AttachedTableMissingError, AttachedTableRunner, AttachedTableSourceOptions, CreateSqlQuerySourceOptions, ENGINE_QUERY_CAPABILITIES, EngineQuerySourceOptions, createAttachedTableSource, createEngineQuerySource, createSqlQuerySource, queryRows, rewriteForTableSource, runAnalyzerWithEngine };
104
+ export { AttachedTableMissingError, AttachedTableRunner, AttachedTableSourceOptions, CreateSqlQuerySourceOptions, ENGINE_QUERY_CAPABILITIES, EngineQuerySourceOptions, createAttachedTableSource, createEngineQuerySource, createSqlQuerySource, queryRows, runAnalyzerWithEngine };
@@ -26,7 +26,6 @@ interface ComparisonPeriod {
26
26
  previous: AnalysisPeriod;
27
27
  }
28
28
  declare function defaultEndDate(): string;
29
- declare function defaultStartDate(): string;
30
29
  declare function periodOf(params: AnalysisParams): AnalysisPeriod;
31
30
  declare function comparisonOf(params: AnalysisParams): ComparisonPeriod;
32
31
  declare function resolveWindow(opts: ResolveWindowOptions): ResolvedWindow;
@@ -51,4 +50,4 @@ type DateRowShape = Record<string, unknown> & {
51
50
  * once. Existing dates keep all their rows (grouped timeseries safe).
52
51
  */
53
52
  declare function padTimeseries<T extends DateRowShape = DateRowShape>(rows: readonly T[], options: PadTimeseriesOptions<T>): T[];
54
- export { AnalysisPeriod, ComparisonMode, ComparisonPeriod, PadTimeseriesOptions, ResolveWindowOptions, ResolvedWindow, WindowPreset, comparisonOf, defaultEndDate, defaultStartDate, padTimeseries, periodOf, resolveWindow };
53
+ export { AnalysisPeriod, ComparisonMode, ComparisonPeriod, PadTimeseriesOptions, ResolveWindowOptions, ResolvedWindow, WindowPreset, comparisonOf, defaultEndDate, padTimeseries, periodOf, resolveWindow };
@@ -2793,4 +2793,4 @@ new Uint8Array([
2793
2793
  5,
2794
2794
  5
2795
2795
  ]);
2796
- export { gunzip, snappyUncompress, snappyUncompressor };
2796
+ export { snappyUncompress, snappyUncompressor };
@@ -913,68 +913,6 @@ function equals(a, b, strict = true) {
913
913
  for (const k of aKeys) if (!equals(a[k], b[k], strict)) return false;
914
914
  return true;
915
915
  }
916
- async function byteLengthFromUrlUsingGet(url, requestInit = {}, fetchFn = globalThis.fetch) {
917
- const controller = new AbortController();
918
- const headers = new Headers(requestInit.headers);
919
- headers.set("Range", "bytes=0-0");
920
- const res = await fetchFn(url, {
921
- ...requestInit,
922
- headers,
923
- signal: controller.signal
924
- });
925
- if (!res.ok) throw new Error(`fetch with range failed ${res.status}`);
926
- if (res.status === 206) {
927
- const contentRange = res.headers.get("Content-Range");
928
- if (!contentRange) throw new Error("missing content-range header");
929
- const match = contentRange.match(/bytes \d+-\d+\/(\d+)/);
930
- if (!match) throw new Error(`invalid content-range header: ${contentRange}`);
931
- return parseInt(match[1]);
932
- }
933
- if (res.status === 200) {
934
- const contentLength = res.headers.get("Content-Length");
935
- controller.abort();
936
- if (contentLength) return parseInt(contentLength);
937
- }
938
- throw new Error("server does not support range requests and missing content-length");
939
- }
940
- async function byteLengthFromUrl(url, requestInit, customFetch) {
941
- const fetch = customFetch ?? globalThis.fetch;
942
- const res = await fetch(url, {
943
- ...requestInit,
944
- method: "HEAD"
945
- });
946
- if (res.status === 403) return byteLengthFromUrlUsingGet(url, requestInit, fetch);
947
- if (!res.ok) throw new Error(`fetch head failed ${res.status}`);
948
- const length = res.headers.get("Content-Length");
949
- if (!length) return byteLengthFromUrlUsingGet(url, requestInit, fetch);
950
- return parseInt(length);
951
- }
952
- async function asyncBufferFromUrl({ url, byteLength, requestInit, fetch: customFetch }) {
953
- if (!url) throw new Error("missing url");
954
- const fetch = customFetch ?? globalThis.fetch;
955
- byteLength ??= await byteLengthFromUrl(url, requestInit, fetch);
956
- let buffer = void 0;
957
- const init = requestInit || {};
958
- return {
959
- byteLength,
960
- async slice(start, end) {
961
- if (buffer) return buffer.then((buffer) => buffer.slice(start, end));
962
- const headers = new Headers(init.headers);
963
- const endStr = end === void 0 ? "" : end - 1;
964
- headers.set("Range", `bytes=${start}-${endStr}`);
965
- const res = await fetch(url, {
966
- ...init,
967
- headers
968
- });
969
- if (!res.ok || !res.body) throw new Error(`fetch failed ${res.status}`);
970
- if (res.status === 200) {
971
- buffer = res.arrayBuffer();
972
- return buffer.then((buffer) => buffer.slice(start, end));
973
- } else if (res.status === 206) return res.arrayBuffer();
974
- else throw new Error(`fetch received unexpected status code ${res.status}`);
975
- }
976
- };
977
- }
978
916
  function flatten(chunks) {
979
917
  if (!chunks) return [];
980
918
  if (chunks.length === 1) return chunks[0];
@@ -2655,4 +2593,4 @@ function toJson(obj) {
2655
2593
  return obj;
2656
2594
  }
2657
2595
  new TextDecoder();
2658
- export { BoundaryOrders, CompressionCodecs, ConvertedTypes, EdgeInterpolationAlgorithms, Encodings, FieldRepetitionTypes, PageTypes, ParquetTypes, asyncBufferFromUrl, getMaxDefinitionLevel, getSchemaPath, hashParquetValue, isListLike, isMapLike, parquetReadObjects, parseDecimal, snappyUncompress, toJson };
2596
+ export { BoundaryOrders, CompressionCodecs, ConvertedTypes, EdgeInterpolationAlgorithms, Encodings, FieldRepetitionTypes, PageTypes, ParquetTypes, getMaxDefinitionLevel, getSchemaPath, hashParquetValue, isListLike, isMapLike, parquetReadObjects, snappyUncompress, toJson };
@@ -42,11 +42,6 @@ interface FileCatalog {
42
42
  conditionalCommits?: boolean;
43
43
  }
44
44
  type Catalog = RestCatalogContext | FileCatalog;
45
- interface LoadTableResponse {
46
- metadataLocation?: string;
47
- metadata: TableMetadata;
48
- config: Record<string, string>;
49
- }
50
45
  interface TableMetadata {
51
46
  'format-version': number;
52
47
  'table-uuid': string;
@@ -210,42 +205,12 @@ interface Manifest {
210
205
  // key_metadata?: unknown
211
206
  first_row_id?: bigint | number;
212
207
  }
213
- interface ManifestEntry {
214
- status: 0 | 1 | 2; // 0=existing, 1=added, 2=deleted
215
- snapshot_id?: bigint;
216
- sequence_number?: bigint;
217
- file_sequence_number?: bigint;
218
- partition_spec_id?: number;
219
- data_file: DataFile;
220
- }
221
208
  interface FieldSummary {
222
209
  contains_null: boolean;
223
210
  contains_nan?: boolean | null;
224
211
  lower_bound?: Uint8Array | null;
225
212
  upper_bound?: Uint8Array | null;
226
213
  }
227
- interface DataFile {
228
- content: 0 | 1 | 2; // 0=data, 1=position_delete, 2=equality_delete
229
- file_path: string;
230
- file_format: 'avro' | 'orc' | 'parquet' | 'puffin';
231
- partition: Record<string, unknown>; // keyed by partition-field name in Avro
232
- record_count: bigint;
233
- file_size_in_bytes: bigint;
234
- column_sizes?: Record<number, bigint>;
235
- value_counts?: Record<number, bigint>;
236
- null_value_counts?: Record<number, bigint>;
237
- nan_value_counts?: Record<number, bigint>;
238
- lower_bounds?: Record<number, unknown>;
239
- upper_bounds?: Record<number, unknown>;
240
- // key_metadata?: string
241
- split_offsets?: bigint[];
242
- equality_ids?: number[];
243
- sort_order_id?: number;
244
- first_row_id?: bigint | number;
245
- referenced_data_file?: string;
246
- content_offset?: bigint;
247
- content_size_in_bytes?: bigint;
248
- }
249
214
  /**
250
215
  * Create a new table. REST: delegates to the catalog's create endpoint.
251
216
  * File: writes the initial `v1.metadata.json` and `version-hint.text` under
@@ -276,49 +241,4 @@ declare function icebergCreateTable({ catalog, namespace, table, tableUrl, schem
276
241
  formatVersion?: 2 | 3;
277
242
  stageCreate?: boolean;
278
243
  }): Promise<TableMetadata>;
279
- /**
280
- * Load a single table. Returns the inline TableMetadata, the metadata
281
- * file location, and any per-table config the server returned.
282
- *
283
- * The returned `metadata` and `metadata.location` can be passed directly
284
- * into `icebergRead({ tableUrl: metadata.location, metadata })`.
285
- *
286
- * @param {RestCatalogContext} ctx
287
- * @param {object} options
288
- * @param {string | string[]} options.namespace
289
- * @param {string} options.table
290
- * @returns {Promise<LoadTableResponse>}
291
- */
292
- declare function restCatalogLoadTable(ctx: RestCatalogContext, { namespace, table }: {
293
- namespace: string | string[];
294
- table: string;
295
- }): Promise<LoadTableResponse>;
296
- type ManifestList = {
297
- url: string;
298
- entries: ManifestEntry[];
299
- }[];
300
- /**
301
- * Predicate applied to each manifest's manifest-list `partitions` field-summary
302
- * array before entry fetch. Return `false` to skip the manifest's entry fetch.
303
- */
304
- type ManifestPartitionFilter = (partitions: Manifest['partitions'], partitionSpecId: number, manifest: Manifest) => boolean;
305
- /**
306
- * Returns manifest entries for a snapshot. Defaults to the current snapshot;
307
- * pass `snapshotId` to time-travel to a prior snapshot in the metadata's
308
- * snapshot log.
309
- *
310
- * @import {Resolver, TableMetadata, Manifest, ManifestEntry} from '../src/types.js'
311
- * @typedef {{ url: string, entries: ManifestEntry[] }[]} ManifestList
312
- * @param {object} options
313
- * @param {TableMetadata} options.metadata
314
- * @param {Resolver} [options.resolver]
315
- * @param {number | bigint} [options.snapshotId] - Optional snapshot id; defaults to `current-snapshot-id`.
316
- * @returns {Promise<ManifestList>}
317
- */
318
- declare function icebergManifests({ metadata, resolver, snapshotId, partitionFilter }: {
319
- metadata: TableMetadata;
320
- resolver?: Resolver;
321
- snapshotId?: number | bigint;
322
- partitionFilter?: ManifestPartitionFilter;
323
- }): Promise<ManifestList>;
324
- export { icebergCreateTable, icebergManifests, restCatalogLoadTable };
244
+ export { icebergCreateTable };
@@ -1,189 +1,6 @@
1
- import { asyncBufferFromUrl, parseDecimal } from "./hyparquet.mjs";
2
- import { ByteWriter } from "./hyparquet-writer.mjs";
3
- import { gunzip } from "./hyparquet-compressors.mjs";
4
- function readZigZag(reader) {
5
- let result = 0;
6
- let shift = 0;
7
- while (true) {
8
- const byte = reader.view.getUint8(reader.offset++);
9
- result |= (byte & 127) << shift;
10
- if (!(byte & 128)) return result >>> 1 ^ -(result & 1);
11
- shift += 7;
12
- }
13
- }
14
- function readZigZagBigInt(reader) {
15
- let result = 0n;
16
- let shift = 0n;
17
- while (true) {
18
- const byte = reader.view.getUint8(reader.offset++);
19
- result |= BigInt(byte & 127) << shift;
20
- if (!(byte & 128)) return result >> 1n ^ -(result & 1n);
21
- shift += 7n;
22
- }
23
- }
24
- function readAvroString(reader) {
25
- const length = readZigZag(reader);
26
- const bytes = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length);
27
- reader.offset += length;
28
- return new TextDecoder().decode(bytes);
29
- }
30
- function avroMetadata(reader) {
31
- if (reader.view.getUint32(reader.offset) !== 1331849729) throw new Error("avro invalid magic bytes");
32
- reader.offset += 4;
33
- const metadata = {};
34
- let mapCount = readZigZag(reader);
35
- while (mapCount !== 0) {
36
- if (mapCount < 0) {
37
- mapCount = -mapCount;
38
- readZigZag(reader);
39
- }
40
- for (let i = 0; i < mapCount; i++) {
41
- const key = readAvroString(reader);
42
- metadata[key] = readAvroString(reader);
43
- }
44
- mapCount = readZigZag(reader);
45
- }
46
- metadata["avro.schema"] = JSON.parse(metadata["avro.schema"]);
47
- if (metadata["schema"]) metadata["schema"] = JSON.parse(metadata["schema"]);
48
- if (metadata["iceberg.schema"]) metadata["iceberg.schema"] = JSON.parse(metadata["iceberg.schema"]);
49
- const syncMarker = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, 16);
50
- reader.offset += 16;
51
- return {
52
- metadata,
53
- syncMarker
54
- };
55
- }
56
- function avroRead({ reader, metadata, syncMarker }) {
57
- const blocks = [];
58
- while (reader.offset < reader.view.byteLength) {
59
- let recordCount = readZigZag(reader);
60
- if (recordCount === 0) break;
61
- if (recordCount < 0) recordCount = -recordCount;
62
- const blockSize = readZigZag(reader);
63
- let data = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, blockSize);
64
- reader.offset += blockSize;
65
- const blockSync = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, 16);
66
- reader.offset += 16;
67
- for (let i = 0; i < 16; i++) if (blockSync[i] !== syncMarker[i]) throw new Error("sync marker does not match");
68
- const codec = metadata["avro.codec"];
69
- if (codec === "deflate") data = gunzip(data);
70
- else if (codec !== "null") throw new Error(`unsupported codec: ${codec}`);
71
- const { fields } = metadata["avro.schema"];
72
- const dataReader = {
73
- view: new DataView(data.buffer, data.byteOffset, data.byteLength),
74
- offset: 0
75
- };
76
- for (let i = 0; i < recordCount; i++) {
77
- const obj = {};
78
- for (const field of fields) {
79
- const value = readType(dataReader, field.type);
80
- obj[field.name] = value;
81
- }
82
- blocks.push(obj);
83
- }
84
- }
85
- return blocks;
86
- }
87
- function readType(reader, type) {
88
- if (type === "null") return;
89
- else if (Array.isArray(type)) return readType(reader, type[readZigZag(reader)]);
90
- else if (typeof type === "object" && type.type === "record") {
91
- const obj = {};
92
- for (const subField of type.fields) obj[subField.name] = readType(reader, subField.type);
93
- return obj;
94
- } else if (typeof type === "object" && type.type === "array") {
95
- const arr = [];
96
- while (true) {
97
- let count = readZigZag(reader);
98
- if (count === 0) break;
99
- if (count < 0) {
100
- count = -count;
101
- readZigZag(reader);
102
- }
103
- for (let i = 0; i < count; i++) arr.push(readType(reader, type.items));
104
- }
105
- return arr;
106
- } else if (typeof type === "object" && type.type === "map") {
107
- const map = {};
108
- while (true) {
109
- let count = readZigZag(reader);
110
- if (count === 0) break;
111
- if (count < 0) {
112
- count = -count;
113
- readZigZag(reader);
114
- }
115
- for (let i = 0; i < count; i++) {
116
- const key = readType(reader, "string");
117
- map[key] = readType(reader, type.values);
118
- }
119
- }
120
- return map;
121
- } else if (typeof type === "object" && type.type === "enum") return type.symbols[readZigZag(reader)];
122
- else if (typeof type === "object" && "logicalType" in type && type.logicalType) if (type.logicalType === "date" && type.type === "int") {
123
- const value = readZigZag(reader);
124
- return /* @__PURE__ */ new Date(value * 864e5);
125
- } else if (type.logicalType === "time-millis" && type.type === "int") return readZigZag(reader);
126
- else if (type.logicalType === "time-micros" && type.type === "long") return readZigZagBigInt(reader);
127
- else if (type.logicalType === "timestamp-millis" && type.type === "long") {
128
- const value = readZigZagBigInt(reader);
129
- return new Date(Number(value));
130
- } else if (type.logicalType === "timestamp-micros" && type.type === "long") {
131
- const value = readZigZagBigInt(reader);
132
- return new Date(Number(value / 1000n));
133
- } else if (type.logicalType === "timestamp-nanos" && type.type === "long") {
134
- const value = readZigZagBigInt(reader);
135
- return new Date(Number(value / 1000000n));
136
- } else if (type.logicalType === "decimal" && "precision" in type) {
137
- const bytes = type.type === "fixed" ? readFixed(reader, type.size) : readType(reader, type.type);
138
- const factor = 10 ** -(type.scale || 0);
139
- return parseDecimal(bytes) * factor;
140
- } else if (type.logicalType === "uuid" && type.type === "fixed" && type.size === 16) return bytesToUuid(readFixed(reader, 16));
141
- else {
142
- console.warn(`unknown logical type: ${type.logicalType}`);
143
- return type.type === "fixed" ? readFixed(reader, type.size) : readType(reader, type.type);
144
- }
145
- else if (typeof type === "object" && type.type === "fixed") return readFixed(reader, type.size);
146
- else if (type === "boolean") {
147
- const value = reader.view.getUint8(reader.offset) === 1;
148
- reader.offset++;
149
- return value;
150
- } else if (type === "int") return readZigZag(reader);
151
- else if (type === "long") return readZigZagBigInt(reader);
152
- else if (type === "float") {
153
- const value = reader.view.getFloat32(reader.offset, true);
154
- reader.offset += 4;
155
- return value;
156
- } else if (type === "double") {
157
- const value = reader.view.getFloat64(reader.offset, true);
158
- reader.offset += 8;
159
- return value;
160
- } else if (type === "bytes") {
161
- const length = readZigZag(reader);
162
- const bytes = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length);
163
- reader.offset += length;
164
- return bytes;
165
- } else if (type === "string") {
166
- const length = readZigZag(reader);
167
- const bytes = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length);
168
- const text = new TextDecoder().decode(bytes);
169
- reader.offset += length;
170
- return text;
171
- } else if (typeof type === "object" && typeof type.type === "string") return readType(reader, type.type);
172
- else throw new Error(`unsupported type: ${JSON.stringify(type)}`);
173
- }
174
- function readFixed(reader, size) {
175
- const bytes = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, size);
176
- reader.offset += size;
177
- return bytes;
178
- }
179
- function bytesToUuid(bytes) {
180
- let hex = "";
181
- for (let i = 0; i < 16; i++) {
182
- hex += bytes[i].toString(16).padStart(2, "0");
183
- if (i === 3 || i === 5 || i === 7 || i === 9) hex += "-";
184
- }
185
- return hex;
186
- }
1
+ import "./hyparquet.mjs";
2
+ import "./hyparquet-writer.mjs";
3
+ import "./hyparquet-compressors.mjs";
187
4
  function uuid4() {
188
5
  if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();
189
6
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
@@ -191,72 +8,6 @@ function uuid4() {
191
8
  return (c === "x" ? r : r & 3 | 8).toString(16);
192
9
  });
193
10
  }
194
- function translateS3Url(url) {
195
- if (url.startsWith("s3a://") || url.startsWith("s3://")) {
196
- const rest = url.slice(url.indexOf("://") + 3);
197
- const slashIndex = rest.indexOf("/");
198
- if (slashIndex === -1) throw new Error("Invalid S3 URL, missing \"/\" after bucket");
199
- return `https://${rest.slice(0, slashIndex)}.s3.amazonaws.com${rest.slice(slashIndex)}`;
200
- }
201
- return url;
202
- }
203
- function urlResolver({ requestInit } = {}) {
204
- return {
205
- reader(url, byteLength) {
206
- return asyncBufferFromUrl({
207
- url: translateS3Url(url),
208
- byteLength,
209
- requestInit
210
- });
211
- },
212
- writer(url, options) {
213
- const w = new ByteWriter();
214
- w.finish = async function() {
215
- const target = translateS3Url(url);
216
- const body = w.getBytes().slice();
217
- const headers = {};
218
- if (requestInit?.headers) new Headers(requestInit.headers).forEach((v, k) => {
219
- headers[k] = v;
220
- });
221
- if (options?.ifNoneMatch) headers["If-None-Match"] = options.ifNoneMatch;
222
- const res = await fetch(target, {
223
- ...requestInit,
224
- method: "PUT",
225
- headers,
226
- body
227
- });
228
- if (!res.ok) {
229
- const err = /* @__PURE__ */ new Error(`PUT ${url}: ${res.status} ${res.statusText}`);
230
- err.status = res.status;
231
- throw err;
232
- }
233
- };
234
- return w;
235
- },
236
- async deleter(url) {
237
- const res = await fetch(translateS3Url(url), {
238
- ...requestInit,
239
- method: "DELETE"
240
- });
241
- if (!res.ok && res.status !== 404) throw new Error(`DELETE ${url}: ${res.status} ${res.statusText}`);
242
- }
243
- };
244
- }
245
- async function fetchAvroRecords(url, resolver, byteLength) {
246
- const lengthHint = byteLength !== void 0 && Number.isFinite(byteLength) ? byteLength : void 0;
247
- const ab = await resolver.reader(url, lengthHint);
248
- const buffer = await ab.slice(0, ab.byteLength);
249
- const reader = {
250
- view: new DataView(buffer),
251
- offset: 0
252
- };
253
- const { metadata, syncMarker } = await avroMetadata(reader);
254
- return await avroRead({
255
- reader,
256
- metadata,
257
- syncMarker
258
- });
259
- }
260
11
  const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
261
12
  function parseIcebergJson(text) {
262
13
  let i = 0;
@@ -403,14 +154,6 @@ function stringifyIcebergJson(value, indent = 2) {
403
154
  }
404
155
  return serialize(value, 0);
405
156
  }
406
- async function restCatalogLoadTable(ctx, { namespace, table }) {
407
- const body = parseIcebergJson(await (await restFetch(ctx, `namespaces/${encodeNamespace(namespace)}/tables/${encodeURIComponent(table)}`)).text());
408
- return {
409
- metadataLocation: body["metadata-location"],
410
- metadata: body.metadata,
411
- config: body.config ?? {}
412
- };
413
- }
414
157
  async function restCatalogCreateTable(ctx, { namespace, table, schema, location, partitionSpec, writeOrder, stageCreate, properties }) {
415
158
  const ns = encodeNamespace(namespace);
416
159
  const body = {
@@ -765,75 +508,6 @@ function maxPartitionFieldId(partitionFields = []) {
765
508
  for (const pf of partitionFields) if (max < pf["field-id"]) max = pf["field-id"];
766
509
  return max;
767
510
  }
768
- async function icebergManifests({ metadata, resolver, snapshotId, partitionFilter }) {
769
- resolver ??= urlResolver();
770
- const rawTarget = snapshotId ?? metadata["current-snapshot-id"];
771
- if (rawTarget == null || rawTarget < 0) throw new Error("No current snapshot id found in table metadata");
772
- const targetId = BigInt(rawTarget);
773
- const snapshot = metadata.snapshots?.find((s) => BigInt(s["snapshot-id"]) === targetId);
774
- if (!snapshot) throw new Error(`Snapshot ${rawTarget} not found in metadata`);
775
- let manifests = [];
776
- if (snapshot["manifest-list"]) {
777
- const manifestListUrl = snapshot["manifest-list"];
778
- manifests = await fetchAvroRecords(manifestListUrl, resolver);
779
- } else if (snapshot.manifests) manifests = snapshot.manifests;
780
- else throw new Error("No manifest information found in snapshot");
781
- if (partitionFilter) manifests = manifests.filter((manifest) => {
782
- let keep = true;
783
- try {
784
- keep = partitionFilter(manifest.partitions, manifest.partition_spec_id ?? 0, manifest) !== false;
785
- } catch {
786
- keep = true;
787
- }
788
- return keep;
789
- });
790
- return await fetchManifests(manifests, resolver);
791
- }
792
- const MANIFEST_FETCH_CONCURRENCY = 8;
793
- async function fetchOneManifest(manifest, resolver) {
794
- const url = manifest.manifest_path;
795
- const entries = await fetchAvroRecords(url, resolver, Number(manifest.manifest_length));
796
- for (const entry of entries) {
797
- entry.partition_spec_id = manifest.partition_spec_id ?? 0;
798
- if (entry.sequence_number === void 0) entry.sequence_number = manifest.sequence_number ?? 0n;
799
- if (entry.status === 1) {
800
- if (entry.sequence_number === void 0) entry.sequence_number = manifest.sequence_number;
801
- if (entry.file_sequence_number === void 0) entry.file_sequence_number = manifest.sequence_number;
802
- } else if (entry.sequence_number === void 0 || entry.file_sequence_number === void 0) throw new Error("iceberg manifest entry missing sequence number");
803
- }
804
- assignFirstRowIds(manifest, entries);
805
- return {
806
- url,
807
- entries
808
- };
809
- }
810
- async function fetchManifests(manifests, resolver) {
811
- const results = new Array(manifests.length);
812
- let next = 0;
813
- async function worker() {
814
- while (next < manifests.length) {
815
- const i = next++;
816
- results[i] = await fetchOneManifest(manifests[i], resolver);
817
- }
818
- }
819
- const poolSize = Math.min(MANIFEST_FETCH_CONCURRENCY, manifests.length);
820
- const workers = [];
821
- for (let w = 0; w < poolSize; w++) workers.push(worker());
822
- await Promise.all(workers);
823
- return results;
824
- }
825
- function assignFirstRowIds(manifest, entries) {
826
- if (manifest.content !== 0 || manifest.first_row_id == null) return;
827
- let nextFirstRowId = BigInt(manifest.first_row_id);
828
- for (const entry of entries) {
829
- const dataFile = entry.data_file;
830
- if (dataFile.content !== 0) continue;
831
- if (dataFile.first_row_id == null) {
832
- dataFile.first_row_id = nextFirstRowId;
833
- nextFirstRowId += BigInt(dataFile.record_count);
834
- }
835
- }
836
- }
837
511
  Object.freeze({
838
512
  maxAttempts: 50,
839
513
  initialMs: 50,
@@ -887,4 +561,4 @@ new TextEncoder();
887
561
  return () => new Promise((resolve) => setTimeout(resolve, 0));
888
562
  })();
889
563
  new TextEncoder();
890
- export { icebergCreateTable, icebergManifests, restCatalogLoadTable };
564
+ export { icebergCreateTable };