iceberg-javascript 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,596 @@
1
+ 'use strict';
2
+
3
+ // src/errors/IcebergError.ts
4
+ var IcebergError = class extends Error {
5
+ constructor(message, opts) {
6
+ super(message);
7
+ this.name = "IcebergError";
8
+ this.status = opts.status;
9
+ this.icebergType = opts.icebergType;
10
+ this.icebergCode = opts.icebergCode;
11
+ this.details = opts.details;
12
+ this.isCommitStateUnknown = opts.icebergType === "CommitStateUnknownException" || [500, 502, 504].includes(opts.status) && opts.icebergType?.includes("CommitState") === true;
13
+ }
14
+ /**
15
+ * Returns true if the error is a 404 Not Found error.
16
+ */
17
+ isNotFound() {
18
+ return this.status === 404;
19
+ }
20
+ /**
21
+ * Returns true if the error is a 409 Conflict error.
22
+ */
23
+ isConflict() {
24
+ return this.status === 409;
25
+ }
26
+ /**
27
+ * Returns true if the error is a 419 Authentication Timeout error.
28
+ */
29
+ isAuthenticationTimeout() {
30
+ return this.status === 419;
31
+ }
32
+ };
33
+
34
+ // src/utils/url.ts
35
+ function buildUrl(baseUrl, path, query) {
36
+ const url = new URL(path, baseUrl);
37
+ if (query) {
38
+ for (const [key, value] of Object.entries(query)) {
39
+ if (value !== void 0) {
40
+ url.searchParams.set(key, value);
41
+ }
42
+ }
43
+ }
44
+ return url.toString();
45
+ }
46
+
47
+ // src/http/createFetchClient.ts
48
+ async function buildAuthHeaders(auth) {
49
+ if (!auth || auth.type === "none") {
50
+ return {};
51
+ }
52
+ if (auth.type === "bearer") {
53
+ return { Authorization: `Bearer ${auth.token}` };
54
+ }
55
+ if (auth.type === "header") {
56
+ return { [auth.name]: auth.value };
57
+ }
58
+ if (auth.type === "custom") {
59
+ return await auth.getHeaders();
60
+ }
61
+ return {};
62
+ }
63
+ function createFetchClient(options) {
64
+ const fetchFn = options.fetchImpl ?? globalThis.fetch;
65
+ return {
66
+ async request({
67
+ method,
68
+ path,
69
+ query,
70
+ body,
71
+ headers
72
+ }) {
73
+ const url = buildUrl(options.baseUrl, path, query);
74
+ const authHeaders = await buildAuthHeaders(options.auth);
75
+ const res = await fetchFn(url, {
76
+ method,
77
+ headers: {
78
+ ...body ? { "Content-Type": "application/json" } : {},
79
+ ...authHeaders,
80
+ ...headers
81
+ },
82
+ body: body ? JSON.stringify(body) : void 0
83
+ });
84
+ const text = await res.text();
85
+ const isJson = (res.headers.get("content-type") || "").includes("application/json");
86
+ const data = isJson && text ? JSON.parse(text) : text;
87
+ if (!res.ok) {
88
+ const errBody = isJson ? data : void 0;
89
+ const errorDetail = errBody?.error;
90
+ throw new IcebergError(
91
+ errorDetail?.message ?? `Request failed with status ${res.status}`,
92
+ {
93
+ status: res.status,
94
+ icebergType: errorDetail?.type,
95
+ icebergCode: errorDetail?.code,
96
+ details: errBody
97
+ }
98
+ );
99
+ }
100
+ return { status: res.status, headers: res.headers, data };
101
+ }
102
+ };
103
+ }
104
+
105
+ // src/catalog/namespaces.ts
106
+ function namespaceToPath(namespace) {
107
+ return namespace.join("");
108
+ }
109
+ var NamespaceOperations = class {
110
+ constructor(client, prefix = "") {
111
+ this.client = client;
112
+ this.prefix = prefix;
113
+ }
114
+ async listNamespaces(parent) {
115
+ const query = parent ? { parent: namespaceToPath(parent.namespace) } : void 0;
116
+ const response = await this.client.request({
117
+ method: "GET",
118
+ path: `${this.prefix}/namespaces`,
119
+ query
120
+ });
121
+ return response.data.namespaces.map((ns) => ({ namespace: ns }));
122
+ }
123
+ async createNamespace(id, metadata) {
124
+ const request = {
125
+ namespace: id.namespace,
126
+ properties: metadata?.properties
127
+ };
128
+ const response = await this.client.request({
129
+ method: "POST",
130
+ path: `${this.prefix}/namespaces`,
131
+ body: request
132
+ });
133
+ return response.data;
134
+ }
135
+ async dropNamespace(id) {
136
+ await this.client.request({
137
+ method: "DELETE",
138
+ path: `${this.prefix}/namespaces/${namespaceToPath(id.namespace)}`
139
+ });
140
+ }
141
+ async loadNamespaceMetadata(id) {
142
+ const response = await this.client.request({
143
+ method: "GET",
144
+ path: `${this.prefix}/namespaces/${namespaceToPath(id.namespace)}`
145
+ });
146
+ return {
147
+ properties: response.data.properties
148
+ };
149
+ }
150
+ async namespaceExists(id) {
151
+ try {
152
+ await this.client.request({
153
+ method: "HEAD",
154
+ path: `${this.prefix}/namespaces/${namespaceToPath(id.namespace)}`
155
+ });
156
+ return true;
157
+ } catch (error) {
158
+ if (error instanceof IcebergError && error.status === 404) {
159
+ return false;
160
+ }
161
+ throw error;
162
+ }
163
+ }
164
+ async createNamespaceIfNotExists(id, metadata) {
165
+ try {
166
+ return await this.createNamespace(id, metadata);
167
+ } catch (error) {
168
+ if (error instanceof IcebergError && error.status === 409) {
169
+ return;
170
+ }
171
+ throw error;
172
+ }
173
+ }
174
+ };
175
+
176
+ // src/catalog/tables.ts
177
+ function namespaceToPath2(namespace) {
178
+ return namespace.join("");
179
+ }
180
+ var TableOperations = class {
181
+ constructor(client, prefix = "", accessDelegation) {
182
+ this.client = client;
183
+ this.prefix = prefix;
184
+ this.accessDelegation = accessDelegation;
185
+ }
186
+ async listTables(namespace) {
187
+ const response = await this.client.request({
188
+ method: "GET",
189
+ path: `${this.prefix}/namespaces/${namespaceToPath2(namespace.namespace)}/tables`
190
+ });
191
+ return response.data.identifiers;
192
+ }
193
+ async createTable(namespace, request) {
194
+ const headers = {};
195
+ if (this.accessDelegation) {
196
+ headers["X-Iceberg-Access-Delegation"] = this.accessDelegation;
197
+ }
198
+ const response = await this.client.request({
199
+ method: "POST",
200
+ path: `${this.prefix}/namespaces/${namespaceToPath2(namespace.namespace)}/tables`,
201
+ body: request,
202
+ headers
203
+ });
204
+ return response.data.metadata;
205
+ }
206
+ async updateTable(id, request) {
207
+ const response = await this.client.request({
208
+ method: "POST",
209
+ path: `${this.prefix}/namespaces/${namespaceToPath2(id.namespace)}/tables/${id.name}`,
210
+ body: request
211
+ });
212
+ return {
213
+ "metadata-location": response.data["metadata-location"],
214
+ metadata: response.data.metadata
215
+ };
216
+ }
217
+ async dropTable(id, options) {
218
+ await this.client.request({
219
+ method: "DELETE",
220
+ path: `${this.prefix}/namespaces/${namespaceToPath2(id.namespace)}/tables/${id.name}`,
221
+ query: { purgeRequested: String(options?.purge ?? false) }
222
+ });
223
+ }
224
+ async loadTable(id) {
225
+ const headers = {};
226
+ if (this.accessDelegation) {
227
+ headers["X-Iceberg-Access-Delegation"] = this.accessDelegation;
228
+ }
229
+ const response = await this.client.request({
230
+ method: "GET",
231
+ path: `${this.prefix}/namespaces/${namespaceToPath2(id.namespace)}/tables/${id.name}`,
232
+ headers
233
+ });
234
+ return response.data.metadata;
235
+ }
236
+ async tableExists(id) {
237
+ const headers = {};
238
+ if (this.accessDelegation) {
239
+ headers["X-Iceberg-Access-Delegation"] = this.accessDelegation;
240
+ }
241
+ try {
242
+ await this.client.request({
243
+ method: "HEAD",
244
+ path: `${this.prefix}/namespaces/${namespaceToPath2(id.namespace)}/tables/${id.name}`,
245
+ headers
246
+ });
247
+ return true;
248
+ } catch (error) {
249
+ if (error instanceof IcebergError && error.status === 404) {
250
+ return false;
251
+ }
252
+ throw error;
253
+ }
254
+ }
255
+ async createTableIfNotExists(namespace, request) {
256
+ try {
257
+ return await this.createTable(namespace, request);
258
+ } catch (error) {
259
+ if (error instanceof IcebergError && error.status === 409) {
260
+ return await this.loadTable({ namespace: namespace.namespace, name: request.name });
261
+ }
262
+ throw error;
263
+ }
264
+ }
265
+ };
266
+
267
+ // src/catalog/IcebergRestCatalog.ts
268
+ var IcebergRestCatalog = class {
269
+ /**
270
+ * Creates a new Iceberg REST Catalog client.
271
+ *
272
+ * @param options - Configuration options for the catalog client
273
+ */
274
+ constructor(options) {
275
+ let prefix = "v1";
276
+ if (options.catalogName) {
277
+ prefix += `/${options.catalogName}`;
278
+ }
279
+ const baseUrl = options.baseUrl.endsWith("/") ? options.baseUrl : `${options.baseUrl}/`;
280
+ this.client = createFetchClient({
281
+ baseUrl,
282
+ auth: options.auth,
283
+ fetchImpl: options.fetch
284
+ });
285
+ this.accessDelegation = options.accessDelegation?.join(",");
286
+ this.namespaceOps = new NamespaceOperations(this.client, prefix);
287
+ this.tableOps = new TableOperations(this.client, prefix, this.accessDelegation);
288
+ }
289
+ /**
290
+ * Lists all namespaces in the catalog.
291
+ *
292
+ * @param parent - Optional parent namespace to list children under
293
+ * @returns Array of namespace identifiers
294
+ *
295
+ * @example
296
+ * ```typescript
297
+ * // List all top-level namespaces
298
+ * const namespaces = await catalog.listNamespaces();
299
+ *
300
+ * // List namespaces under a parent
301
+ * const children = await catalog.listNamespaces({ namespace: ['analytics'] });
302
+ * ```
303
+ */
304
+ async listNamespaces(parent) {
305
+ return this.namespaceOps.listNamespaces(parent);
306
+ }
307
+ /**
308
+ * Creates a new namespace in the catalog.
309
+ *
310
+ * @param id - Namespace identifier to create
311
+ * @param metadata - Optional metadata properties for the namespace
312
+ * @returns Response containing the created namespace and its properties
313
+ *
314
+ * @example
315
+ * ```typescript
316
+ * const response = await catalog.createNamespace(
317
+ * { namespace: ['analytics'] },
318
+ * { properties: { owner: 'data-team' } }
319
+ * );
320
+ * console.log(response.namespace); // ['analytics']
321
+ * console.log(response.properties); // { owner: 'data-team', ... }
322
+ * ```
323
+ */
324
+ async createNamespace(id, metadata) {
325
+ return this.namespaceOps.createNamespace(id, metadata);
326
+ }
327
+ /**
328
+ * Drops a namespace from the catalog.
329
+ *
330
+ * The namespace must be empty (contain no tables) before it can be dropped.
331
+ *
332
+ * @param id - Namespace identifier to drop
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * await catalog.dropNamespace({ namespace: ['analytics'] });
337
+ * ```
338
+ */
339
+ async dropNamespace(id) {
340
+ await this.namespaceOps.dropNamespace(id);
341
+ }
342
+ /**
343
+ * Loads metadata for a namespace.
344
+ *
345
+ * @param id - Namespace identifier to load
346
+ * @returns Namespace metadata including properties
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * const metadata = await catalog.loadNamespaceMetadata({ namespace: ['analytics'] });
351
+ * console.log(metadata.properties);
352
+ * ```
353
+ */
354
+ async loadNamespaceMetadata(id) {
355
+ return this.namespaceOps.loadNamespaceMetadata(id);
356
+ }
357
+ /**
358
+ * Lists all tables in a namespace.
359
+ *
360
+ * @param namespace - Namespace identifier to list tables from
361
+ * @returns Array of table identifiers
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const tables = await catalog.listTables({ namespace: ['analytics'] });
366
+ * console.log(tables); // [{ namespace: ['analytics'], name: 'events' }, ...]
367
+ * ```
368
+ */
369
+ async listTables(namespace) {
370
+ return this.tableOps.listTables(namespace);
371
+ }
372
+ /**
373
+ * Creates a new table in the catalog.
374
+ *
375
+ * @param namespace - Namespace to create the table in
376
+ * @param request - Table creation request including name, schema, partition spec, etc.
377
+ * @returns Table metadata for the created table
378
+ *
379
+ * @example
380
+ * ```typescript
381
+ * const metadata = await catalog.createTable(
382
+ * { namespace: ['analytics'] },
383
+ * {
384
+ * name: 'events',
385
+ * schema: {
386
+ * type: 'struct',
387
+ * fields: [
388
+ * { id: 1, name: 'id', type: 'long', required: true },
389
+ * { id: 2, name: 'timestamp', type: 'timestamp', required: true }
390
+ * ],
391
+ * 'schema-id': 0
392
+ * },
393
+ * 'partition-spec': {
394
+ * 'spec-id': 0,
395
+ * fields: [
396
+ * { source_id: 2, field_id: 1000, name: 'ts_day', transform: 'day' }
397
+ * ]
398
+ * }
399
+ * }
400
+ * );
401
+ * ```
402
+ */
403
+ async createTable(namespace, request) {
404
+ return this.tableOps.createTable(namespace, request);
405
+ }
406
+ /**
407
+ * Updates an existing table's metadata.
408
+ *
409
+ * Can update the schema, partition spec, or properties of a table.
410
+ *
411
+ * @param id - Table identifier to update
412
+ * @param request - Update request with fields to modify
413
+ * @returns Response containing the metadata location and updated table metadata
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * const response = await catalog.updateTable(
418
+ * { namespace: ['analytics'], name: 'events' },
419
+ * {
420
+ * properties: { 'read.split.target-size': '134217728' }
421
+ * }
422
+ * );
423
+ * console.log(response['metadata-location']); // s3://...
424
+ * console.log(response.metadata); // TableMetadata object
425
+ * ```
426
+ */
427
+ async updateTable(id, request) {
428
+ return this.tableOps.updateTable(id, request);
429
+ }
430
+ /**
431
+ * Drops a table from the catalog.
432
+ *
433
+ * @param id - Table identifier to drop
434
+ *
435
+ * @example
436
+ * ```typescript
437
+ * await catalog.dropTable({ namespace: ['analytics'], name: 'events' });
438
+ * ```
439
+ */
440
+ async dropTable(id, options) {
441
+ await this.tableOps.dropTable(id, options);
442
+ }
443
+ /**
444
+ * Loads metadata for a table.
445
+ *
446
+ * @param id - Table identifier to load
447
+ * @returns Table metadata including schema, partition spec, location, etc.
448
+ *
449
+ * @example
450
+ * ```typescript
451
+ * const metadata = await catalog.loadTable({ namespace: ['analytics'], name: 'events' });
452
+ * console.log(metadata.schema);
453
+ * console.log(metadata.location);
454
+ * ```
455
+ */
456
+ async loadTable(id) {
457
+ return this.tableOps.loadTable(id);
458
+ }
459
+ /**
460
+ * Checks if a namespace exists in the catalog.
461
+ *
462
+ * @param id - Namespace identifier to check
463
+ * @returns True if the namespace exists, false otherwise
464
+ *
465
+ * @example
466
+ * ```typescript
467
+ * const exists = await catalog.namespaceExists({ namespace: ['analytics'] });
468
+ * console.log(exists); // true or false
469
+ * ```
470
+ */
471
+ async namespaceExists(id) {
472
+ return this.namespaceOps.namespaceExists(id);
473
+ }
474
+ /**
475
+ * Checks if a table exists in the catalog.
476
+ *
477
+ * @param id - Table identifier to check
478
+ * @returns True if the table exists, false otherwise
479
+ *
480
+ * @example
481
+ * ```typescript
482
+ * const exists = await catalog.tableExists({ namespace: ['analytics'], name: 'events' });
483
+ * console.log(exists); // true or false
484
+ * ```
485
+ */
486
+ async tableExists(id) {
487
+ return this.tableOps.tableExists(id);
488
+ }
489
+ /**
490
+ * Creates a namespace if it does not exist.
491
+ *
492
+ * If the namespace already exists, returns void. If created, returns the response.
493
+ *
494
+ * @param id - Namespace identifier to create
495
+ * @param metadata - Optional metadata properties for the namespace
496
+ * @returns Response containing the created namespace and its properties, or void if it already exists
497
+ *
498
+ * @example
499
+ * ```typescript
500
+ * const response = await catalog.createNamespaceIfNotExists(
501
+ * { namespace: ['analytics'] },
502
+ * { properties: { owner: 'data-team' } }
503
+ * );
504
+ * if (response) {
505
+ * console.log('Created:', response.namespace);
506
+ * } else {
507
+ * console.log('Already exists');
508
+ * }
509
+ * ```
510
+ */
511
+ async createNamespaceIfNotExists(id, metadata) {
512
+ return this.namespaceOps.createNamespaceIfNotExists(id, metadata);
513
+ }
514
+ /**
515
+ * Creates a table if it does not exist.
516
+ *
517
+ * If the table already exists, returns its metadata instead.
518
+ *
519
+ * @param namespace - Namespace to create the table in
520
+ * @param request - Table creation request including name, schema, partition spec, etc.
521
+ * @returns Table metadata for the created or existing table
522
+ *
523
+ * @example
524
+ * ```typescript
525
+ * const metadata = await catalog.createTableIfNotExists(
526
+ * { namespace: ['analytics'] },
527
+ * {
528
+ * name: 'events',
529
+ * schema: {
530
+ * type: 'struct',
531
+ * fields: [
532
+ * { id: 1, name: 'id', type: 'long', required: true },
533
+ * { id: 2, name: 'timestamp', type: 'timestamp', required: true }
534
+ * ],
535
+ * 'schema-id': 0
536
+ * }
537
+ * }
538
+ * );
539
+ * ```
540
+ */
541
+ async createTableIfNotExists(namespace, request) {
542
+ return this.tableOps.createTableIfNotExists(namespace, request);
543
+ }
544
+ };
545
+
546
+ // src/catalog/types.ts
547
+ var DECIMAL_REGEX = /^decimal\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)$/;
548
+ var FIXED_REGEX = /^fixed\s*\[\s*(\d+)\s*\]$/;
549
+ function parseDecimalType(type) {
550
+ const match = type.match(DECIMAL_REGEX);
551
+ if (!match) return null;
552
+ return {
553
+ precision: parseInt(match[1], 10),
554
+ scale: parseInt(match[2], 10)
555
+ };
556
+ }
557
+ function parseFixedType(type) {
558
+ const match = type.match(FIXED_REGEX);
559
+ if (!match) return null;
560
+ return {
561
+ length: parseInt(match[1], 10)
562
+ };
563
+ }
564
+ function isDecimalType(type) {
565
+ return DECIMAL_REGEX.test(type);
566
+ }
567
+ function isFixedType(type) {
568
+ return FIXED_REGEX.test(type);
569
+ }
570
+ function typesEqual(a, b) {
571
+ const decimalA = parseDecimalType(a);
572
+ const decimalB = parseDecimalType(b);
573
+ if (decimalA && decimalB) {
574
+ return decimalA.precision === decimalB.precision && decimalA.scale === decimalB.scale;
575
+ }
576
+ const fixedA = parseFixedType(a);
577
+ const fixedB = parseFixedType(b);
578
+ if (fixedA && fixedB) {
579
+ return fixedA.length === fixedB.length;
580
+ }
581
+ return a === b;
582
+ }
583
+ function getCurrentSchema(metadata) {
584
+ return metadata.schemas.find((s) => s["schema-id"] === metadata["current-schema-id"]);
585
+ }
586
+
587
+ exports.IcebergError = IcebergError;
588
+ exports.IcebergRestCatalog = IcebergRestCatalog;
589
+ exports.getCurrentSchema = getCurrentSchema;
590
+ exports.isDecimalType = isDecimalType;
591
+ exports.isFixedType = isFixedType;
592
+ exports.parseDecimalType = parseDecimalType;
593
+ exports.parseFixedType = parseFixedType;
594
+ exports.typesEqual = typesEqual;
595
+ //# sourceMappingURL=index.cjs.map
596
+ //# sourceMappingURL=index.cjs.map