@gscdump/lakehouse 0.37.7 → 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.
- package/dist/_chunks/libs/fzstd.mjs +5 -1
- package/dist/_chunks/libs/hyparquet-writer.d.mts +23 -0
- package/dist/_chunks/libs/icebird.d.mts +103 -152
- package/dist/_chunks/libs/icebird.mjs +113 -78
- package/dist/_chunks/libs/ioredis.d.mts +2 -1
- package/dist/_chunks/libs/unstorage.d.mts +4 -0
- package/package.json +4 -4
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
var ab = ArrayBuffer
|
|
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
|
|
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;
|
|
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[];
|
|
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>;
|
|
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[]
|
|
353
|
-
table?: string
|
|
354
|
-
tableUrl?: string
|
|
355
|
-
resolver?: Resolver
|
|
269
|
+
namespace?: string | string[];
|
|
270
|
+
table?: string;
|
|
271
|
+
tableUrl?: string;
|
|
272
|
+
resolver?: Resolver;
|
|
356
273
|
records: Record<string, any>[];
|
|
357
|
-
sortOrderId?: number
|
|
358
|
-
snapshotProperties?: Record<string, string
|
|
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[]
|
|
392
|
-
table?: string
|
|
393
|
-
tableUrl?: string
|
|
394
|
-
schema?: Schema
|
|
395
|
-
partitionSpec?: PartitionSpec
|
|
396
|
-
sortOrder?: SortOrder
|
|
397
|
-
properties?: Record<string, string
|
|
398
|
-
formatVersion?: 2 | 3
|
|
399
|
-
stageCreate?: boolean
|
|
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[]
|
|
426
|
-
table?: string
|
|
427
|
-
tableUrl?: string
|
|
428
|
-
lister?: Lister
|
|
429
|
-
purgeRequested?: boolean
|
|
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
|
|
459
|
-
requestInit?: RequestInit
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
49
|
+
"@types/node": "^26.1.1",
|
|
50
50
|
"hyparquet": "^1.26.2",
|
|
51
|
-
"icebird": "^0.8.
|
|
51
|
+
"icebird": "^0.8.15",
|
|
52
52
|
"unstorage": "^1.17.5",
|
|
53
|
-
"vitest": "^4.1.
|
|
53
|
+
"vitest": "^4.1.10"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"dev": "obuild --stub",
|