@gscdump/engine 0.37.6 → 0.38.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.
@@ -1 +1 @@
1
- export { };
1
+ import "./storage.mjs";
@@ -3,6 +3,7 @@ import { EngineError } from "./errors.mjs";
3
3
  import { AnalysisParams, AnalysisResult } from "./analysis-types.mjs";
4
4
  import { ResolverAdapter } from "./types.mjs";
5
5
  import { AnalysisQuerySource, AnalysisSourceKind, AnalyzerRegistry, FileSet, QueryRow, SourceCapabilities } from "./registry.mjs";
6
+ import "./contracts.mjs";
6
7
  import { PlannerCapabilities } from "gscdump/query/plan";
7
8
  import { BuilderState } from "gscdump/query";
8
9
  interface AttachedTableRunner {
@@ -1,4 +1,8 @@
1
- var ab = ArrayBuffer, u8 = Uint8Array, u16 = Uint16Array, i16 = Int16Array, i32 = Int32Array;
1
+ var ab = ArrayBuffer;
2
+ var u8 = Uint8Array;
3
+ var u16 = Uint16Array;
4
+ var i16 = Int16Array;
5
+ var i32 = Int32Array;
2
6
  var slc = function(v, s, e) {
3
7
  if (u8.prototype.slice) return u8.prototype.slice.call(v, s, e);
4
8
  if (s == null || s < 0) s = 0;
@@ -0,0 +1,23 @@
1
+ interface Writer$1 {
2
+ buffer: ArrayBuffer;
3
+ view: DataView;
4
+ offset: number; // total bytes written
5
+ ensure(size: number): void;
6
+ flush?(): void | Promise<void>;
7
+ finish(): void | Promise<void>;
8
+ getBuffer(): ArrayBuffer;
9
+ getBytes(): Uint8Array;
10
+ appendUint8(value: number): void;
11
+ appendUint32(value: number): void;
12
+ appendInt32(value: number): void;
13
+ appendInt64(value: bigint): void;
14
+ appendFloat32(value: number): void;
15
+ appendFloat64(value: number): void;
16
+ appendBuffer(buffer: ArrayBuffer): void;
17
+ appendBytes(value: Uint8Array): void;
18
+ appendVarInt(value: number): void;
19
+ appendVarBigInt(value: bigint): void;
20
+ appendZigZag(value: number | bigint): void;
21
+ }
22
+ type Writer = Writer$1;
23
+ export { Writer };
@@ -1,5 +1,5 @@
1
1
  import { AsyncBuffer } from "./hyparquet.mjs";
2
- import { Writer } from "hyparquet-writer/src/types.js";
2
+ import { Writer } from "./hyparquet-writer.mjs";
3
3
  interface WriterOptions {
4
4
  /**
5
5
  * If `'*'`, the writer must create the object only if it does not already
@@ -24,6 +24,8 @@ interface RestCatalogContext {
24
24
  defaults: Record<string, string>;
25
25
  overrides: Record<string, string>;
26
26
  requestInit?: RequestInit;
27
+ /** Optional per-request auth hook (e.g. SigV4 signing). Called after merging requestInit. */
28
+ signRequest?: (url: string, init?: RequestInit) => Promise<RequestInit>;
27
29
  }
28
30
  interface FileCatalog {
29
31
  type: 'file';
@@ -136,7 +138,8 @@ interface Snapshot {
136
138
  manifests?: Manifest[];
137
139
  summary: {
138
140
  // spec: "value of these fields should be of string type"
139
- operation: string; // 'spark.app.id'?: string
141
+ operation: string;
142
+ // 'spark.app.id'?: string
140
143
  'added-data-files'?: string;
141
144
  'added-records'?: string;
142
145
  'deleted-data-files'?: string;
@@ -185,9 +188,6 @@ interface EncryptionKey {
185
188
  'key-id': string;
186
189
  'key-metadata': string;
187
190
  }
188
- /**
189
- * Iceberg REST `TableRequirement`s recognized by `checkRequirements`.
190
- */
191
191
  interface MetadataLog {
192
192
  'timestamp-ms': number;
193
193
  'metadata-file': string;
@@ -206,7 +206,8 @@ interface Manifest {
206
206
  added_rows_count: bigint;
207
207
  existing_rows_count: bigint;
208
208
  deleted_rows_count: bigint;
209
- partitions?: FieldSummary[]; // key_metadata?: unknown
209
+ partitions?: FieldSummary[];
210
+ // key_metadata?: unknown
210
211
  first_row_id?: bigint | number;
211
212
  }
212
213
  interface ManifestEntry {
@@ -235,7 +236,8 @@ interface DataFile {
235
236
  null_value_counts?: Record<number, bigint>;
236
237
  nan_value_counts?: Record<number, bigint>;
237
238
  lower_bounds?: Record<number, unknown>;
238
- upper_bounds?: Record<number, unknown>; // key_metadata?: string
239
+ upper_bounds?: Record<number, unknown>;
240
+ // key_metadata?: string
239
241
  split_offsets?: bigint[];
240
242
  equality_ids?: number[];
241
243
  sort_order_id?: number;
@@ -244,44 +246,6 @@ interface DataFile {
244
246
  content_offset?: bigint;
245
247
  content_size_in_bytes?: bigint;
246
248
  }
247
- /**
248
- * Returns manifest entries for a snapshot. Defaults to the current snapshot;
249
- * pass `snapshotId` to time-travel to a prior snapshot in the metadata's
250
- * snapshot log.
251
- *
252
- * @import {Resolver, TableMetadata, Manifest, ManifestEntry} from '../src/types.js'
253
- * @typedef {{ url: string, entries: ManifestEntry[] }[]} ManifestList
254
- * @param {object} options
255
- * @param {TableMetadata} options.metadata
256
- * @param {Resolver} [options.resolver]
257
- * @param {number | bigint} [options.snapshotId] - Optional snapshot id; defaults to `current-snapshot-id`.
258
- * @returns {Promise<ManifestList>}
259
- */
260
- declare function icebergManifests({
261
- metadata,
262
- resolver,
263
- snapshotId,
264
- partitionFilter
265
- }: {
266
- metadata: TableMetadata;
267
- resolver?: Resolver | undefined;
268
- snapshotId?: number | bigint | undefined;
269
- partitionFilter?: ManifestPartitionFilter | undefined;
270
- }): Promise<ManifestList>;
271
- /**
272
- * Predicate applied to each manifest's manifest-list `partitions` field-summary
273
- * array before entry fetch. Return `false` to skip the manifest's entry fetch.
274
- */
275
- type ManifestPartitionFilter = (partitions: Manifest['partitions'], partitionSpecId: number, manifest: Manifest) => boolean;
276
- /**
277
- * Returns manifest entries for a snapshot. Defaults to the current snapshot;
278
- * pass `snapshotId` to time-travel to a prior snapshot in the metadata's
279
- * snapshot log.
280
- */
281
- type ManifestList = {
282
- url: string;
283
- entries: ManifestEntry[];
284
- }[];
285
249
  /**
286
250
  * Create a new table. REST: delegates to the catalog's create endpoint.
287
251
  * File: writes the initial `v1.metadata.json` and `version-hint.text` under
@@ -300,28 +264,17 @@ type ManifestList = {
300
264
  * @param {boolean} [options.stageCreate] - REST catalog only.
301
265
  * @returns {Promise<TableMetadata>}
302
266
  */
303
- declare function icebergCreateTable({
304
- catalog,
305
- namespace,
306
- table,
307
- tableUrl,
308
- schema,
309
- partitionSpec,
310
- sortOrder,
311
- properties,
312
- formatVersion,
313
- stageCreate
314
- }: {
267
+ declare function icebergCreateTable({ catalog, namespace, table, tableUrl, schema, partitionSpec, sortOrder, properties, formatVersion, stageCreate }: {
315
268
  catalog: Catalog;
316
- namespace?: string | string[] | undefined;
317
- table?: string | undefined;
318
- tableUrl?: string | undefined;
319
- schema?: Schema | undefined;
320
- partitionSpec?: PartitionSpec | undefined;
321
- sortOrder?: SortOrder | undefined;
322
- properties?: Record<string, string> | undefined;
323
- formatVersion?: 2 | 3 | undefined;
324
- stageCreate?: boolean | undefined;
269
+ namespace?: string | string[];
270
+ table?: string;
271
+ tableUrl?: string;
272
+ schema?: Schema;
273
+ partitionSpec?: PartitionSpec;
274
+ sortOrder?: SortOrder;
275
+ properties?: Record<string, string>;
276
+ formatVersion?: 2 | 3;
277
+ stageCreate?: boolean;
325
278
  }): Promise<TableMetadata>;
326
279
  /**
327
280
  * Load a single table. Returns the inline TableMetadata, the metadata
@@ -336,11 +289,36 @@ declare function icebergCreateTable({
336
289
  * @param {string} options.table
337
290
  * @returns {Promise<LoadTableResponse>}
338
291
  */
339
- declare function restCatalogLoadTable(ctx: RestCatalogContext, {
340
- namespace,
341
- table
342
- }: {
292
+ declare function restCatalogLoadTable(ctx: RestCatalogContext, { namespace, table }: {
343
293
  namespace: string | string[];
344
294
  table: string;
345
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>;
346
324
  export { icebergCreateTable, icebergManifests, restCatalogLoadTable };
@@ -103,7 +103,23 @@ function readType(reader, type) {
103
103
  for (let i = 0; i < count; i++) arr.push(readType(reader, type.items));
104
104
  }
105
105
  return arr;
106
- } else if (typeof type === "object" && type.logicalType) if (type.logicalType === "date" && type.type === "int") {
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") {
107
123
  const value = readZigZag(reader);
108
124
  return /* @__PURE__ */ new Date(value * 864e5);
109
125
  } else if (type.logicalType === "time-millis" && type.type === "int") return readZigZag(reader);
@@ -152,7 +168,8 @@ function readType(reader, type) {
152
168
  const text = new TextDecoder().decode(bytes);
153
169
  reader.offset += length;
154
170
  return text;
155
- } else throw new Error(`unsupported type: ${type}`);
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)}`);
156
173
  }
157
174
  function readFixed(reader, size) {
158
175
  const bytes = new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, size);
@@ -422,7 +439,8 @@ function encodeNamespace(namespace) {
422
439
  async function restFetch(ctx, path, init) {
423
440
  const prefixSegment = ctx.prefix ? `${ctx.prefix.replace(/^\/|\/$/g, "")}/` : "";
424
441
  const fullUrl = `${ctx.url}/v1/${prefixSegment}${path}`;
425
- const merged = mergeRequestInit(ctx.requestInit, init);
442
+ let merged = mergeRequestInit(ctx.requestInit, init);
443
+ if (ctx.signRequest) merged = await ctx.signRequest(fullUrl, merged);
426
444
  const res = await fetch(fullUrl, merged);
427
445
  if (!res.ok) await throwRestError(res);
428
446
  return res;
@@ -868,4 +886,5 @@ new TextEncoder();
868
886
  }
869
887
  return () => new Promise((resolve) => setTimeout(resolve, 0));
870
888
  })();
889
+ new TextEncoder();
871
890
  export { icebergCreateTable, icebergManifests, restCatalogLoadTable };
@@ -1,2 +1,3 @@
1
1
  import { CodecCtx, CompactionTier, DataSource, EngineOptions, FileSetRef, GcCtx, ListLiveFilter, LockScope, ManifestEntry, ManifestStore, ParquetCodec, QueryCtx, QueryExecuteOptions, QueryExecuteResult, QueryExecutor, QueryResult, Row, RunSQLOptions, SearchType, StorageEngine, SyncState, SyncStateDetail, SyncStateFilter, SyncStateKind, SyncStateScope, TableName, TenantCtx, Watermark, WatermarkFilter, WatermarkScope, WriteCtx, WriteResult } from "./_chunks/storage.mjs";
2
+ import "./_chunks/contracts.mjs";
2
3
  export type { CodecCtx, CompactionTier, DataSource, EngineOptions, FileSetRef, GcCtx, ListLiveFilter, LockScope, ManifestEntry, ManifestStore, ParquetCodec, QueryCtx, QueryExecuteOptions, QueryExecuteResult, QueryExecutor, QueryResult, Row, RunSQLOptions, SearchType, StorageEngine, SyncState, SyncStateDetail, SyncStateFilter, SyncStateKind, SyncStateScope, TableName, TenantCtx, Watermark, WatermarkFilter, WatermarkScope, WriteCtx, WriteResult };
@@ -50,9 +50,7 @@ interface QueryDimStore {
50
50
  /** Decode the dimension rows (test/inspection; reads JOIN the parquet by key). */
51
51
  loadRecords: (ctx: TenantCtx) => Promise<QueryDimRecord[]>;
52
52
  }
53
- declare function createQueryDimStore({
54
- dataSource
55
- }: {
53
+ declare function createQueryDimStore({ dataSource }: {
56
54
  dataSource: DataSource;
57
55
  }): QueryDimStore;
58
56
  /**
package/dist/ingest.mjs CHANGED
@@ -219,7 +219,8 @@ function assembleDatesRow(date, totalsRow, deviceRows, queryGrainedImpressions)
219
219
  sum_position_tablet: 0
220
220
  };
221
221
  for (const dr of deviceRows) {
222
- const suffix = DEVICE_SUFFIX[String(dr.keys?.[1] ?? dr.keys?.[0] ?? "").toUpperCase()];
222
+ const deviceKey = String(dr.keys?.[1] ?? dr.keys?.[0] ?? "").toUpperCase();
223
+ const suffix = DEVICE_SUFFIX[deviceKey];
223
224
  if (!suffix) continue;
224
225
  const dImpr = dr.impressions || 0;
225
226
  row[`clicks_${suffix}`] = dr.clicks || 0;
@@ -1,6 +1,7 @@
1
1
  import { DataSource, FileSetRef, Row as Row$1, TableName as TableName$1 } from "./_chunks/storage.mjs";
2
2
  import { ColumnDef as ColumnDef$1 } from "./_chunks/schema.mjs";
3
3
  import { EngineError } from "./_chunks/errors.mjs";
4
+ import "./_chunks/contracts.mjs";
4
5
  import { SearchType } from "gscdump/query";
5
6
  import { TenantCtx } from "@gscdump/contracts";
6
7
  interface RollupCtx extends TenantCtx {
@@ -287,11 +288,13 @@ declare function runWindowed(opts: {
287
288
  * windowed `FILES`). Use to JOIN a non-windowed sidecar (e.g. the query
288
289
  * dimension parquet via `{ QUERY_DIM: { keys: [...] } }`) inside `sqlFor`.
289
290
  */
290
- extraFileSets?: Record<string, FileSetRef>; /** Page each window's output by a total-order key. See `runPagedQuery`. */
291
+ extraFileSets?: Record<string, FileSetRef>;
292
+ /** Page each window's output by a total-order key. See `runPagedQuery`. */
291
293
  paginate?: {
292
294
  orderBy: string;
293
295
  pageRows: number;
294
- }; /** Cap each window's day span (output-cardinality bound). See `planRollupWindows`. */
296
+ };
297
+ /** Cap each window's day span (output-cardinality bound). See `planRollupWindows`. */
295
298
  maxWindowDays?: number;
296
299
  }): Promise<Row$1[]>;
297
300
  /**
@@ -379,10 +382,14 @@ declare function rebuildCanonicalDailyResumable(opts: {
379
382
  dataSource: DataSource;
380
383
  searchType?: SearchType;
381
384
  builtAt: number;
382
- windowOffset: number; /** Resume the `windowOffset` window at this shard offset (0 = window start). */
383
- pageOffset?: number; /** Output rows per page (default `ROLLUP_PAGE_ROWS_DAILY`). Injectable for tests. */
384
- pageRows?: number; /** Cap each input window's day span (default `DAILY_MAX_WINDOW_DAYS`). */
385
- maxWindowDays?: number; /** Split each date window by raw-query hash before grouping (1 = no sharding). */
385
+ windowOffset: number;
386
+ /** Resume the `windowOffset` window at this shard offset (0 = window start). */
387
+ pageOffset?: number;
388
+ /** Output rows per page (default `ROLLUP_PAGE_ROWS_DAILY`). Injectable for tests. */
389
+ pageRows?: number;
390
+ /** Cap each input window's day span (default `DAILY_MAX_WINDOW_DAYS`). */
391
+ maxWindowDays?: number;
392
+ /** Split each date window by raw-query hash before grouping (1 = no sharding). */
386
393
  shardCount?: number;
387
394
  deadlineMs: number;
388
395
  }): Promise<{
@@ -38,7 +38,7 @@ function createIcebergOverwriteWriter(opts) {
38
38
  const siteId = slice.ctx.siteId;
39
39
  if (!siteId) throw new Error("overwriteSlice: slice.ctx.siteId is required for the Iceberg partition key");
40
40
  const table = assertIcebergTable(slice.table);
41
- const res = await backend({
41
+ const job = {
42
42
  op: "overwrite",
43
43
  catalogUri: catalog.catalogUri,
44
44
  namespace: catalog.namespace,
@@ -50,7 +50,8 @@ function createIcebergOverwriteWriter(opts) {
50
50
  searchType: slice.searchType,
51
51
  date: slice.date,
52
52
  rows
53
- });
53
+ };
54
+ const res = await backend(job);
54
55
  if (res.error) throw new Error(`overwriteSlice: PyIceberg backend failed: ${res.error}`);
55
56
  return { rowCount: res.rowCount ?? rows.length };
56
57
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/engine",
3
3
  "type": "module",
4
- "version": "0.37.6",
4
+ "version": "0.38.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",
@@ -181,21 +181,21 @@
181
181
  "dependencies": {
182
182
  "drizzle-orm": "1.0.0-rc.3",
183
183
  "proper-lockfile": "^4.1.2",
184
- "@gscdump/contracts": "0.37.6",
185
- "@gscdump/lakehouse": "0.37.6",
186
- "gscdump": "0.37.6"
184
+ "@gscdump/lakehouse": "0.38.0",
185
+ "gscdump": "0.38.0",
186
+ "@gscdump/contracts": "0.38.0"
187
187
  },
188
188
  "devDependencies": {
189
189
  "@duckdb/duckdb-wasm": "^1.32.0",
190
- "@types/node": "^26.1.0",
190
+ "@types/node": "^26.1.1",
191
191
  "@types/proper-lockfile": "^4.1.4",
192
192
  "aws4fetch": "^1.0.20",
193
193
  "hyparquet": "^1.26.2",
194
194
  "hyparquet-writer": "^0.16.1",
195
- "icebird": "^0.8.13",
196
- "tsx": "^4.23.0",
195
+ "icebird": "^0.8.15",
196
+ "tsx": "^4.23.1",
197
197
  "unstorage": "^1.17.5",
198
- "vitest": "^4.1.9"
198
+ "vitest": "^4.1.10"
199
199
  },
200
200
  "scripts": {
201
201
  "dev": "obuild --stub",