@gscdump/lakehouse 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,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';
@@ -140,7 +142,8 @@ interface Snapshot {
140
142
  manifests?: Manifest[];
141
143
  summary: {
142
144
  // spec: "value of these fields should be of string type"
143
- operation: string; // 'spark.app.id'?: string
145
+ operation: string;
146
+ // 'spark.app.id'?: string
144
147
  'added-data-files'?: string;
145
148
  'added-records'?: string;
146
149
  'deleted-data-files'?: string;
@@ -189,9 +192,6 @@ interface EncryptionKey {
189
192
  'key-id': string;
190
193
  'key-metadata': string;
191
194
  }
192
- /**
193
- * Iceberg REST `TableRequirement`s recognized by `checkRequirements`.
194
- */
195
195
  interface MetadataLog {
196
196
  'timestamp-ms': number;
197
197
  'metadata-file': string;
@@ -210,7 +210,8 @@ interface Manifest {
210
210
  added_rows_count: bigint;
211
211
  existing_rows_count: bigint;
212
212
  deleted_rows_count: bigint;
213
- partitions?: FieldSummary[]; // key_metadata?: unknown
213
+ partitions?: FieldSummary[];
214
+ // key_metadata?: unknown
214
215
  first_row_id?: bigint | number;
215
216
  }
216
217
  interface ManifestEntry {
@@ -239,7 +240,8 @@ interface DataFile {
239
240
  null_value_counts?: Record<number, bigint>;
240
241
  nan_value_counts?: Record<number, bigint>;
241
242
  lower_bounds?: Record<number, unknown>;
242
- upper_bounds?: Record<number, unknown>; // key_metadata?: string
243
+ upper_bounds?: Record<number, unknown>;
244
+ // key_metadata?: string
243
245
  split_offsets?: bigint[];
244
246
  equality_ids?: number[];
245
247
  sort_order_id?: number;
@@ -248,82 +250,6 @@ interface DataFile {
248
250
  content_offset?: bigint;
249
251
  content_size_in_bytes?: bigint;
250
252
  }
251
- /**
252
- * Build a SigV4-signing `Resolver` for private S3-compatible buckets (AWS S3,
253
- * Cloudflare R2, MinIO, etc.). Signs every request with AWS Signature V4
254
- * using static credentials. Works in browsers and Node via the Web Crypto API.
255
- *
256
- * Credentials are taken as inputs; profile/STS/IMDS chains are out of scope.
257
- * For Iceberg REST catalogs that vend per-table credentials (R2, AWS Glue),
258
- * pass the `s3.*` keys from `restCatalogLoadCredentials` or `LoadTable.config`.
259
- *
260
- * Accepts paths as `s3://bucket/key`, `s3a://bucket/key`, or full https URLs.
261
- *
262
- * @param {object} options
263
- * @param {string} options.accessKeyId
264
- * @param {string} options.secretAccessKey
265
- * @param {string} [options.sessionToken] - For temporary credentials (STS/vended).
266
- * @param {string} options.region - e.g. 'us-east-1', or 'auto' for R2.
267
- * @param {string} [options.endpoint] - HTTPS endpoint root for non-AWS providers
268
- * (e.g. `https://<account>.r2.cloudflarestorage.com`). Defaults to AWS S3.
269
- * @param {boolean} [options.pathStyle] - When true, URLs are
270
- * `<endpoint>/<bucket>/<key>`; when false (default), virtual-hosted-style
271
- * `<bucket>.<endpoint-host>/<key>`. R2 requires true.
272
- * @returns {Resolver}
273
- */
274
- declare function s3SignedResolver({
275
- accessKeyId,
276
- secretAccessKey,
277
- sessionToken,
278
- region,
279
- endpoint,
280
- pathStyle
281
- }: {
282
- accessKeyId: string;
283
- secretAccessKey: string;
284
- sessionToken?: string | undefined;
285
- region: string;
286
- endpoint?: string | undefined;
287
- pathStyle?: boolean | undefined;
288
- }): Resolver;
289
- /**
290
- * Returns manifest entries for a snapshot. Defaults to the current snapshot;
291
- * pass `snapshotId` to time-travel to a prior snapshot in the metadata's
292
- * snapshot log.
293
- *
294
- * @import {Resolver, TableMetadata, Manifest, ManifestEntry} from '../src/types.js'
295
- * @typedef {{ url: string, entries: ManifestEntry[] }[]} ManifestList
296
- * @param {object} options
297
- * @param {TableMetadata} options.metadata
298
- * @param {Resolver} [options.resolver]
299
- * @param {number | bigint} [options.snapshotId] - Optional snapshot id; defaults to `current-snapshot-id`.
300
- * @returns {Promise<ManifestList>}
301
- */
302
- declare function icebergManifests({
303
- metadata,
304
- resolver,
305
- snapshotId,
306
- partitionFilter
307
- }: {
308
- metadata: TableMetadata;
309
- resolver?: Resolver | undefined;
310
- snapshotId?: number | bigint | undefined;
311
- partitionFilter?: ManifestPartitionFilter | undefined;
312
- }): Promise<ManifestList>;
313
- /**
314
- * Predicate applied to each manifest's manifest-list `partitions` field-summary
315
- * array before entry fetch. Return `false` to skip the manifest's entry fetch.
316
- */
317
- type ManifestPartitionFilter = (partitions: Manifest['partitions'], partitionSpecId: number, manifest: Manifest) => boolean;
318
- /**
319
- * Returns manifest entries for a snapshot. Defaults to the current snapshot;
320
- * pass `snapshotId` to time-travel to a prior snapshot in the metadata's
321
- * snapshot log.
322
- */
323
- type ManifestList = {
324
- url: string;
325
- entries: ManifestEntry[];
326
- }[];
327
253
  /**
328
254
  * Append rows to a table in one call: load metadata, stage the parquet writes
329
255
  * + manifest + new snapshot, commit through the catalog.
@@ -338,24 +264,15 @@ type ManifestList = {
338
264
  * @param {number} [options.sortOrderId] - Sort order id to apply; defaults to the table default.
339
265
  * @returns {Promise<TableMetadata>}
340
266
  */
341
- declare function icebergAppend({
342
- catalog,
343
- namespace,
344
- table,
345
- tableUrl,
346
- resolver,
347
- records,
348
- sortOrderId,
349
- snapshotProperties
350
- }: {
267
+ declare function icebergAppend({ catalog, namespace, table, tableUrl, resolver, records, sortOrderId, snapshotProperties }: {
351
268
  catalog: Catalog;
352
- namespace?: string | string[] | undefined;
353
- table?: string | undefined;
354
- tableUrl?: string | undefined;
355
- resolver?: Resolver | undefined;
269
+ namespace?: string | string[];
270
+ table?: string;
271
+ tableUrl?: string;
272
+ resolver?: Resolver;
356
273
  records: Record<string, any>[];
357
- sortOrderId?: number | undefined;
358
- snapshotProperties?: Record<string, string> | undefined;
274
+ sortOrderId?: number;
275
+ snapshotProperties?: Record<string, string>;
359
276
  }): Promise<TableMetadata>;
360
277
  /**
361
278
  * Create a new table. REST: delegates to the catalog's create endpoint.
@@ -375,28 +292,17 @@ declare function icebergAppend({
375
292
  * @param {boolean} [options.stageCreate] - REST catalog only.
376
293
  * @returns {Promise<TableMetadata>}
377
294
  */
378
- declare function icebergCreateTable({
379
- catalog,
380
- namespace,
381
- table,
382
- tableUrl,
383
- schema,
384
- partitionSpec,
385
- sortOrder,
386
- properties,
387
- formatVersion,
388
- stageCreate
389
- }: {
295
+ declare function icebergCreateTable({ catalog, namespace, table, tableUrl, schema, partitionSpec, sortOrder, properties, formatVersion, stageCreate }: {
390
296
  catalog: Catalog;
391
- namespace?: string | string[] | undefined;
392
- table?: string | undefined;
393
- tableUrl?: string | undefined;
394
- schema?: Schema | undefined;
395
- partitionSpec?: PartitionSpec | undefined;
396
- sortOrder?: SortOrder | undefined;
397
- properties?: Record<string, string> | undefined;
398
- formatVersion?: 2 | 3 | undefined;
399
- stageCreate?: boolean | undefined;
297
+ namespace?: string | string[];
298
+ table?: string;
299
+ tableUrl?: string;
300
+ schema?: Schema;
301
+ partitionSpec?: PartitionSpec;
302
+ sortOrder?: SortOrder;
303
+ properties?: Record<string, string>;
304
+ formatVersion?: 2 | 3;
305
+ stageCreate?: boolean;
400
306
  }): Promise<TableMetadata>;
401
307
  /**
402
308
  * Drop a table. REST: delegates to the catalog's drop endpoint, forwarding
@@ -413,20 +319,13 @@ declare function icebergCreateTable({
413
319
  * @param {boolean} [options.purgeRequested] - REST: forwarded to the server. File: also delete `data/`.
414
320
  * @returns {Promise<void>}
415
321
  */
416
- declare function icebergDropTable({
417
- catalog,
418
- namespace,
419
- table,
420
- tableUrl,
421
- lister,
422
- purgeRequested
423
- }: {
322
+ declare function icebergDropTable({ catalog, namespace, table, tableUrl, lister, purgeRequested }: {
424
323
  catalog: Catalog;
425
- namespace?: string | string[] | undefined;
426
- table?: string | undefined;
427
- tableUrl?: string | undefined;
428
- lister?: Lister | undefined;
429
- purgeRequested?: boolean | undefined;
324
+ namespace?: string | string[];
325
+ table?: string;
326
+ tableUrl?: string;
327
+ lister?: Lister;
328
+ purgeRequested?: boolean;
430
329
  }): Promise<void>;
431
330
  /**
432
331
  * Iceberg REST Catalog client.
@@ -447,16 +346,14 @@ declare function icebergDropTable({
447
346
  * @param {string} options.url - catalog base URL, with or without trailing slash
448
347
  * @param {string} [options.warehouse] - optional warehouse query param sent to /v1/config
449
348
  * @param {RequestInit} [options.requestInit] - fetch options (e.g. Authorization header)
349
+ * @param {(url: string, init?: RequestInit) => Promise<RequestInit>} [options.signRequest] - per-request auth hook
450
350
  * @returns {Promise<RestCatalogContext>}
451
351
  */
452
- declare function restCatalogConnect({
453
- url,
454
- warehouse,
455
- requestInit
456
- }: {
352
+ declare function restCatalogConnect({ url, warehouse, requestInit, signRequest }: {
457
353
  url: string;
458
- warehouse?: string | undefined;
459
- requestInit?: RequestInit | undefined;
354
+ warehouse?: string;
355
+ requestInit?: RequestInit;
356
+ signRequest?: (url: string, init?: RequestInit) => Promise<RequestInit>;
460
357
  }): Promise<RestCatalogContext>;
461
358
  /**
462
359
  * List tables within a namespace.
@@ -467,9 +364,7 @@ declare function restCatalogConnect({
467
364
  * @param {string | string[]} options.namespace
468
365
  * @returns {Promise<TableIdentifier[]>}
469
366
  */
470
- declare function restCatalogListTables(ctx: RestCatalogContext, {
471
- namespace
472
- }: {
367
+ declare function restCatalogListTables(ctx: RestCatalogContext, { namespace }: {
473
368
  namespace: string | string[];
474
369
  }): Promise<TableIdentifier[]>;
475
370
  /**
@@ -485,10 +380,7 @@ declare function restCatalogListTables(ctx: RestCatalogContext, {
485
380
  * @param {string} options.table
486
381
  * @returns {Promise<LoadTableResponse>}
487
382
  */
488
- declare function restCatalogLoadTable(ctx: RestCatalogContext, {
489
- namespace,
490
- table
491
- }: {
383
+ declare function restCatalogLoadTable(ctx: RestCatalogContext, { namespace, table }: {
492
384
  namespace: string | string[];
493
385
  table: string;
494
386
  }): Promise<LoadTableResponse>;
@@ -502,12 +394,9 @@ declare function restCatalogLoadTable(ctx: RestCatalogContext, {
502
394
  * @param {Record<string, string>} [options.properties]
503
395
  * @returns {Promise<{namespace: string[], properties: Record<string, string>}>}
504
396
  */
505
- declare function restCatalogCreateNamespace(ctx: RestCatalogContext, {
506
- namespace,
507
- properties
508
- }: {
397
+ declare function restCatalogCreateNamespace(ctx: RestCatalogContext, { namespace, properties }: {
509
398
  namespace: string | string[];
510
- properties?: Record<string, string> | undefined;
399
+ properties?: Record<string, string>;
511
400
  }): Promise<{
512
401
  namespace: string[];
513
402
  properties: Record<string, string>;
@@ -546,4 +435,66 @@ declare function restCatalogCreateNamespace(ctx: RestCatalogContext, {
546
435
  * @returns {Resolver}
547
436
  */
548
437
  declare function cachingResolver(base: Resolver): Resolver;
438
+ /**
439
+ * @import {Resolver} from '../src/types.js'
440
+ */
441
+ /**
442
+ * Build a SigV4-signing `Resolver` for private S3-compatible buckets (AWS S3,
443
+ * Cloudflare R2, MinIO, etc.). Signs every request with AWS Signature V4
444
+ * using static credentials. Works in browsers and Node via the Web Crypto API.
445
+ *
446
+ * Credentials are taken as inputs; profile/STS/IMDS chains are out of scope.
447
+ * For Iceberg REST catalogs that vend per-table credentials (R2, AWS Glue),
448
+ * pass the `s3.*` keys from `restCatalogLoadCredentials` or `LoadTable.config`.
449
+ *
450
+ * Accepts paths as `s3://bucket/key`, `s3a://bucket/key`, or full https URLs.
451
+ *
452
+ * @param {object} options
453
+ * @param {string} options.accessKeyId
454
+ * @param {string} options.secretAccessKey
455
+ * @param {string} [options.sessionToken] - For temporary credentials (STS/vended).
456
+ * @param {string} options.region - e.g. 'us-east-1', or 'auto' for R2.
457
+ * @param {string} [options.endpoint] - HTTPS endpoint root for non-AWS providers
458
+ * (e.g. `https://<account>.r2.cloudflarestorage.com`). Defaults to AWS S3.
459
+ * @param {boolean} [options.pathStyle] - When true, URLs are
460
+ * `<endpoint>/<bucket>/<key>`; when false (default), virtual-hosted-style
461
+ * `<bucket>.<endpoint-host>/<key>`. R2 requires true.
462
+ * @returns {Resolver}
463
+ */
464
+ declare function s3SignedResolver({ accessKeyId, secretAccessKey, sessionToken, region, endpoint, pathStyle }: {
465
+ accessKeyId: string;
466
+ secretAccessKey: string;
467
+ sessionToken?: string;
468
+ region: string;
469
+ endpoint?: string;
470
+ pathStyle?: boolean;
471
+ }): Resolver;
472
+ type ManifestList = {
473
+ url: string;
474
+ entries: ManifestEntry[];
475
+ }[];
476
+ /**
477
+ * Predicate applied to each manifest's manifest-list `partitions` field-summary
478
+ * array before entry fetch. Return `false` to skip the manifest's entry fetch.
479
+ */
480
+ type ManifestPartitionFilter = (partitions: Manifest['partitions'], partitionSpecId: number, manifest: Manifest) => boolean;
481
+ /**
482
+ * Returns manifest entries for a snapshot. Defaults to the current snapshot;
483
+ * pass `snapshotId` to time-travel to a prior snapshot in the metadata's
484
+ * snapshot log.
485
+ *
486
+ * @import {Resolver, TableMetadata, Manifest, ManifestEntry} from '../src/types.js'
487
+ * @typedef {{ url: string, entries: ManifestEntry[] }[]} ManifestList
488
+ * @param {object} options
489
+ * @param {TableMetadata} options.metadata
490
+ * @param {Resolver} [options.resolver]
491
+ * @param {number | bigint} [options.snapshotId] - Optional snapshot id; defaults to `current-snapshot-id`.
492
+ * @returns {Promise<ManifestList>}
493
+ */
494
+ declare function icebergManifests({ metadata, resolver, snapshotId, partitionFilter }: {
495
+ metadata: TableMetadata;
496
+ resolver?: Resolver;
497
+ snapshotId?: number | bigint;
498
+ partitionFilter?: ManifestPartitionFilter;
499
+ }): Promise<ManifestList>;
549
500
  export { cachingResolver, icebergAppend, icebergCreateTable, icebergDropTable, icebergManifests, restCatalogConnect, restCatalogCreateNamespace, restCatalogListTables, restCatalogLoadTable, s3SignedResolver };
@@ -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);
@@ -638,23 +655,27 @@ function metadataFilePreference(file, paddedVersion) {
638
655
  if (file.startsWith(`${paddedVersion}-`) && file.endsWith(".metadata.json.gz")) return 5;
639
656
  return 6;
640
657
  }
641
- async function restCatalogConnect({ url, warehouse, requestInit }) {
658
+ async function restCatalogConnect({ url, warehouse, requestInit, signRequest }) {
642
659
  const base = url.replace(/\/$/, "");
643
660
  const configUrl = warehouse ? `${base}/v1/config?warehouse=${encodeURIComponent(warehouse)}` : `${base}/v1/config`;
644
- const res = await fetch(configUrl, requestInit);
661
+ let init = requestInit;
662
+ if (signRequest) init = await signRequest(configUrl, init);
663
+ const res = await fetch(configUrl, init);
645
664
  if (!res.ok) await throwRestError(res);
646
665
  const body = parseIcebergJson(await res.text());
647
666
  const defaults = body.defaults ?? {};
648
667
  const overrides = body.overrides ?? {};
649
668
  const prefix = overrides.prefix ?? defaults.prefix ?? "";
650
- return Object.freeze({
669
+ const ctx = {
651
670
  type: "rest",
652
671
  url: base,
653
672
  prefix: typeof prefix === "string" ? prefix : "",
654
673
  defaults,
655
674
  overrides,
656
675
  requestInit
657
- });
676
+ };
677
+ if (signRequest) ctx.signRequest = signRequest;
678
+ return Object.freeze(ctx);
658
679
  }
659
680
  function restCatalogListTables(ctx, { namespace }) {
660
681
  const ns = encodeNamespace(namespace);
@@ -735,7 +756,8 @@ function encodeNamespace(namespace) {
735
756
  async function restFetch(ctx, path, init) {
736
757
  const prefixSegment = ctx.prefix ? `${ctx.prefix.replace(/^\/|\/$/g, "")}/` : "";
737
758
  const fullUrl = `${ctx.url}/v1/${prefixSegment}${path}`;
738
- const merged = mergeRequestInit(ctx.requestInit, init);
759
+ let merged = mergeRequestInit(ctx.requestInit, init);
760
+ if (ctx.signRequest) merged = await ctx.signRequest(fullUrl, merged);
739
761
  const res = await fetch(fullUrl, merged);
740
762
  if (!res.ok) await throwRestError(res);
741
763
  return res;
@@ -1843,7 +1865,7 @@ function writeType(writer, schema, value) {
1843
1865
  if (Array.isArray(schema)) {
1844
1866
  const unionIndex = schema.findIndex((s) => {
1845
1867
  if (Array.isArray(s)) throw new Error("nested unions not supported");
1846
- const tag = typeof s === "string" ? s : s.type === "record" || s.type === "array" || s.type === "fixed" ? s.type : s.logicalType;
1868
+ const tag = typeof s === "string" ? s : s.type === "record" || s.type === "array" || s.type === "fixed" ? s.type : "logicalType" in s && s.logicalType ? s.logicalType : s.type;
1847
1869
  if (value == null) return tag === "null";
1848
1870
  if (tag === "boolean") return typeof value === "boolean";
1849
1871
  if (tag === "int") return typeof value === "number" && Number.isInteger(value);
@@ -3552,6 +3574,74 @@ function isCommitConflict(err) {
3552
3574
  return status === 412 || status === 409;
3553
3575
  }
3554
3576
  const enc = new TextEncoder();
3577
+ async function signRequest(method, url, body, { accessKeyId, secretAccessKey, sessionToken, region, service, extraHeaders = {} }) {
3578
+ const u = new URL(url);
3579
+ const xAmzDate = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]|\.\d{3}/g, "");
3580
+ const dStamp = xAmzDate.slice(0, 8);
3581
+ const payloadHash = body !== void 0 ? await sha256hex(body) : await sha256hex("");
3582
+ const lc = {};
3583
+ for (const [k, v] of Object.entries(extraHeaders)) lc[k.toLowerCase()] = String(v);
3584
+ lc["host"] = u.host;
3585
+ lc["x-amz-date"] = xAmzDate;
3586
+ lc["x-amz-content-sha256"] = payloadHash;
3587
+ if (sessionToken) lc["x-amz-security-token"] = sessionToken;
3588
+ const sortedKeys = Object.keys(lc).sort();
3589
+ const canonicalHeaders = sortedKeys.map((k) => `${k}:${lc[k].trim().replace(/\s+/g, " ")}\n`).join("");
3590
+ const signedHeaders = sortedKeys.join(";");
3591
+ const doubleEncodePath = service !== "s3";
3592
+ const canonicalRequest = [
3593
+ method,
3594
+ u.pathname.split("/").map((seg) => doubleEncodePath ? encodeRfc3986(seg) : encodeRfc3986(decodeURIComponent(seg))).join("/"),
3595
+ [...u.searchParams.entries()].sort((a, b) => {
3596
+ if (a[0] !== b[0]) return a[0] < b[0] ? -1 : 1;
3597
+ return a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0;
3598
+ }).map(([k, v]) => `${encodeRfc3986(k)}=${encodeRfc3986(v)}`).join("&"),
3599
+ canonicalHeaders,
3600
+ signedHeaders,
3601
+ payloadHash
3602
+ ].join("\n");
3603
+ const credentialScope = `${dStamp}/${region}/${service}/aws4_request`;
3604
+ const stringToSign = [
3605
+ "AWS4-HMAC-SHA256",
3606
+ xAmzDate,
3607
+ credentialScope,
3608
+ await sha256hex(canonicalRequest)
3609
+ ].join("\n");
3610
+ const signature = bytesToHex(await hmac(await deriveSigningKey(secretAccessKey, dStamp, region, service), stringToSign));
3611
+ const out = {};
3612
+ for (const [k, v] of Object.entries(lc)) {
3613
+ if (k === "host") continue;
3614
+ out[k] = v;
3615
+ }
3616
+ out["Authorization"] = `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
3617
+ return out;
3618
+ }
3619
+ async function sha256hex(data) {
3620
+ const bytes = typeof data === "string" ? enc.encode(data) : data;
3621
+ const hash = await crypto.subtle.digest("SHA-256", bytes);
3622
+ return bytesToHex(new Uint8Array(hash));
3623
+ }
3624
+ async function hmac(key, data) {
3625
+ const keyBytes = typeof key === "string" ? enc.encode(key) : key;
3626
+ const dataBytes = typeof data === "string" ? enc.encode(data) : data;
3627
+ const cryptoKey = await crypto.subtle.importKey("raw", keyBytes, {
3628
+ name: "HMAC",
3629
+ hash: "SHA-256"
3630
+ }, false, ["sign"]);
3631
+ const sig = await crypto.subtle.sign("HMAC", cryptoKey, dataBytes);
3632
+ return new Uint8Array(sig);
3633
+ }
3634
+ async function deriveSigningKey(secret, dateStamp, region, service) {
3635
+ return await hmac(await hmac(await hmac(await hmac(`AWS4${secret}`, dateStamp), region), service), "aws4_request");
3636
+ }
3637
+ function encodeRfc3986(str) {
3638
+ return encodeURIComponent(str).replace(/[!*'()]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase());
3639
+ }
3640
+ function bytesToHex(bytes) {
3641
+ let s = "";
3642
+ for (const b of bytes) s += b.toString(16).padStart(2, "0");
3643
+ return s;
3644
+ }
3555
3645
  function s3SignedResolver({ accessKeyId, secretAccessKey, sessionToken, region, endpoint, pathStyle = false }) {
3556
3646
  const ep = endpoint ? new URL(endpoint.replace(/\/$/, "") + "/") : void 0;
3557
3647
  function toHttps(url) {
@@ -3565,55 +3655,25 @@ function s3SignedResolver({ accessKeyId, secretAccessKey, sessionToken, region,
3565
3655
  if (pathStyle) return `${ep.origin}${ep.pathname}${bucket}/${key}`;
3566
3656
  return `${ep.protocol}//${bucket}.${ep.host}/${key}`;
3567
3657
  }
3658
+ if (region && region !== "us-east-1" && region !== "auto") return `https://${bucket}.s3.${region}.amazonaws.com/${key}`;
3568
3659
  return `https://${bucket}.s3.amazonaws.com/${key}`;
3569
3660
  }
3570
- async function signRequest(method, url, body, extra = {}) {
3571
- const u = new URL(url);
3572
- const xAmzDate = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]|\.\d{3}/g, "");
3573
- const dStamp = xAmzDate.slice(0, 8);
3574
- const payloadHash = body !== void 0 ? await sha256hex(body) : await sha256hex("");
3575
- const lc = {};
3576
- for (const [k, v] of Object.entries(extra)) lc[k.toLowerCase()] = String(v);
3577
- lc["host"] = u.host;
3578
- lc["x-amz-date"] = xAmzDate;
3579
- lc["x-amz-content-sha256"] = payloadHash;
3580
- if (sessionToken) lc["x-amz-security-token"] = sessionToken;
3581
- const sortedKeys = Object.keys(lc).sort();
3582
- const canonicalHeaders = sortedKeys.map((k) => `${k}:${lc[k].trim().replace(/\s+/g, " ")}\n`).join("");
3583
- const signedHeaders = sortedKeys.join(";");
3584
- const canonicalRequest = [
3585
- method,
3586
- u.pathname.split("/").map((seg) => encodeRfc3986(decodeURIComponent(seg))).join("/"),
3587
- [...u.searchParams.entries()].sort((a, b) => {
3588
- if (a[0] !== b[0]) return a[0] < b[0] ? -1 : 1;
3589
- return a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0;
3590
- }).map(([k, v]) => `${encodeRfc3986(k)}=${encodeRfc3986(v)}`).join("&"),
3591
- canonicalHeaders,
3592
- signedHeaders,
3593
- payloadHash
3594
- ].join("\n");
3595
- const credentialScope = `${dStamp}/${region}/s3/aws4_request`;
3596
- const stringToSign = [
3597
- "AWS4-HMAC-SHA256",
3598
- xAmzDate,
3599
- credentialScope,
3600
- await sha256hex(canonicalRequest)
3601
- ].join("\n");
3602
- const signature = bytesToHex(await hmac(await deriveSigningKey(secretAccessKey, dStamp, region, "s3"), stringToSign));
3603
- const out = {};
3604
- for (const [k, v] of Object.entries(lc)) {
3605
- if (k === "host") continue;
3606
- out[k] = v;
3607
- }
3608
- out["Authorization"] = `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
3609
- return out;
3661
+ function signRequest$1(method, url, body, extra = {}) {
3662
+ return signRequest(method, url, body, {
3663
+ accessKeyId,
3664
+ secretAccessKey,
3665
+ sessionToken,
3666
+ region,
3667
+ service: "s3",
3668
+ extraHeaders: extra
3669
+ });
3610
3670
  }
3611
3671
  return {
3612
3672
  async reader(path, byteLength) {
3613
3673
  const url = toHttps(path);
3614
3674
  let len = byteLength;
3615
3675
  if (len === void 0) {
3616
- const headers = await signRequest("HEAD", url);
3676
+ const headers = await signRequest$1("HEAD", url);
3617
3677
  const res = await fetch(url, {
3618
3678
  method: "HEAD",
3619
3679
  headers
@@ -3627,7 +3687,7 @@ function s3SignedResolver({ accessKeyId, secretAccessKey, sessionToken, region,
3627
3687
  byteLength: fileLength,
3628
3688
  async slice(start, end) {
3629
3689
  const range = `bytes=${start}-${(end ?? fileLength) - 1}`;
3630
- const headers = await signRequest("GET", url, void 0, { range });
3690
+ const headers = await signRequest$1("GET", url, void 0, { range });
3631
3691
  const res = await fetch(url, {
3632
3692
  method: "GET",
3633
3693
  headers
@@ -3644,7 +3704,7 @@ function s3SignedResolver({ accessKeyId, secretAccessKey, sessionToken, region,
3644
3704
  const body = w.getBytes().slice();
3645
3705
  const extra = {};
3646
3706
  if (options?.ifNoneMatch) extra["if-none-match"] = options.ifNoneMatch;
3647
- const headers = await signRequest("PUT", url, body, extra);
3707
+ const headers = await signRequest$1("PUT", url, body, extra);
3648
3708
  const res = await fetch(url, {
3649
3709
  method: "PUT",
3650
3710
  headers,
@@ -3660,7 +3720,7 @@ function s3SignedResolver({ accessKeyId, secretAccessKey, sessionToken, region,
3660
3720
  },
3661
3721
  async deleter(path) {
3662
3722
  const url = toHttps(path);
3663
- const headers = await signRequest("DELETE", url);
3723
+ const headers = await signRequest$1("DELETE", url);
3664
3724
  const res = await fetch(url, {
3665
3725
  method: "DELETE",
3666
3726
  headers
@@ -3669,32 +3729,6 @@ function s3SignedResolver({ accessKeyId, secretAccessKey, sessionToken, region,
3669
3729
  }
3670
3730
  };
3671
3731
  }
3672
- async function sha256hex(data) {
3673
- const bytes = typeof data === "string" ? enc.encode(data) : data;
3674
- const hash = await crypto.subtle.digest("SHA-256", bytes);
3675
- return bytesToHex(new Uint8Array(hash));
3676
- }
3677
- async function hmac(key, data) {
3678
- const keyBytes = typeof key === "string" ? enc.encode(key) : key;
3679
- const dataBytes = typeof data === "string" ? enc.encode(data) : data;
3680
- const cryptoKey = await crypto.subtle.importKey("raw", keyBytes, {
3681
- name: "HMAC",
3682
- hash: "SHA-256"
3683
- }, false, ["sign"]);
3684
- const sig = await crypto.subtle.sign("HMAC", cryptoKey, dataBytes);
3685
- return new Uint8Array(sig);
3686
- }
3687
- async function deriveSigningKey(secret, dateStamp, region, service) {
3688
- return await hmac(await hmac(await hmac(await hmac(`AWS4${secret}`, dateStamp), region), service), "aws4_request");
3689
- }
3690
- function encodeRfc3986(str) {
3691
- return encodeURIComponent(str).replace(/[!*'()]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase());
3692
- }
3693
- function bytesToHex(bytes) {
3694
- let s = "";
3695
- for (const b of bytes) s += b.toString(16).padStart(2, "0");
3696
- return s;
3697
- }
3698
3732
  (() => {
3699
3733
  if (typeof setImmediate === "function") return () => new Promise((resolve) => setImmediate(resolve));
3700
3734
  if (typeof MessageChannel !== "undefined") {
@@ -3711,4 +3745,5 @@ function bytesToHex(bytes) {
3711
3745
  }
3712
3746
  return () => new Promise((resolve) => setTimeout(resolve, 0));
3713
3747
  })();
3748
+ new TextEncoder();
3714
3749
  export { cachingResolver, icebergAppend, icebergCreateTable, icebergDropTable, icebergManifests, restCatalogConnect, restCatalogCreateNamespace, restCatalogListTables, restCatalogLoadTable, s3SignedResolver };
@@ -1 +1,2 @@
1
- export { };
1
+ /// <reference types="node" />
2
+ import "./denque.mjs";
@@ -1,3 +1,7 @@
1
+ import "./db0.mjs";
2
+ import "./chokidar.mjs";
3
+ import "./lru-cache.mjs";
4
+ import "./ioredis.mjs";
1
5
  type StorageValue = null | string | number | boolean | object;
2
6
  type WatchEvent = "update" | "remove";
3
7
  type WatchCallback = (event: WatchEvent, key: string) => any;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/lakehouse",
3
3
  "type": "module",
4
- "version": "0.37.6",
4
+ "version": "0.38.0",
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",
@@ -46,11 +46,11 @@
46
46
  "node": ">=18"
47
47
  },
48
48
  "devDependencies": {
49
- "@types/node": "^26.1.0",
49
+ "@types/node": "^26.1.1",
50
50
  "hyparquet": "^1.26.2",
51
- "icebird": "^0.8.13",
51
+ "icebird": "^0.8.15",
52
52
  "unstorage": "^1.17.5",
53
- "vitest": "^4.1.9"
53
+ "vitest": "^4.1.10"
54
54
  },
55
55
  "scripts": {
56
56
  "dev": "obuild --stub",