| 'use strict'; |
|
|
| |
| var IcebergError = class extends Error { |
| constructor(message, opts) { |
| super(message); |
| this.name = "IcebergError"; |
| this.status = opts.status; |
| this.icebergType = opts.icebergType; |
| this.icebergCode = opts.icebergCode; |
| this.details = opts.details; |
| this.isCommitStateUnknown = opts.icebergType === "CommitStateUnknownException" || [500, 502, 504].includes(opts.status) && opts.icebergType?.includes("CommitState") === true; |
| } |
| |
| |
| |
| isNotFound() { |
| return this.status === 404; |
| } |
| |
| |
| |
| isConflict() { |
| return this.status === 409; |
| } |
| |
| |
| |
| isAuthenticationTimeout() { |
| return this.status === 419; |
| } |
| }; |
|
|
| |
| function buildUrl(baseUrl, path, query) { |
| const url = new URL(path, baseUrl); |
| if (query) { |
| for (const [key, value] of Object.entries(query)) { |
| if (value !== void 0) { |
| url.searchParams.set(key, value); |
| } |
| } |
| } |
| return url.toString(); |
| } |
|
|
| |
| async function buildAuthHeaders(auth) { |
| if (!auth || auth.type === "none") { |
| return {}; |
| } |
| if (auth.type === "bearer") { |
| return { Authorization: `Bearer ${auth.token}` }; |
| } |
| if (auth.type === "header") { |
| return { [auth.name]: auth.value }; |
| } |
| if (auth.type === "custom") { |
| return await auth.getHeaders(); |
| } |
| return {}; |
| } |
| function createFetchClient(options) { |
| const fetchFn = options.fetchImpl ?? globalThis.fetch; |
| return { |
| async request({ |
| method, |
| path, |
| query, |
| body, |
| headers |
| }) { |
| const url = buildUrl(options.baseUrl, path, query); |
| const authHeaders = await buildAuthHeaders(options.auth); |
| const res = await fetchFn(url, { |
| method, |
| headers: { |
| ...body ? { "Content-Type": "application/json" } : {}, |
| ...authHeaders, |
| ...headers |
| }, |
| body: body ? JSON.stringify(body) : void 0 |
| }); |
| const text = await res.text(); |
| const isJson = (res.headers.get("content-type") || "").includes("application/json"); |
| const data = isJson && text ? JSON.parse(text) : text; |
| if (!res.ok) { |
| const errBody = isJson ? data : void 0; |
| const errorDetail = errBody?.error; |
| throw new IcebergError( |
| errorDetail?.message ?? `Request failed with status ${res.status}`, |
| { |
| status: res.status, |
| icebergType: errorDetail?.type, |
| icebergCode: errorDetail?.code, |
| details: errBody |
| } |
| ); |
| } |
| return { status: res.status, headers: res.headers, data }; |
| } |
| }; |
| } |
|
|
| |
| function namespaceToPath(namespace) { |
| return namespace.join(""); |
| } |
| var NamespaceOperations = class { |
| constructor(client, prefix = "") { |
| this.client = client; |
| this.prefix = prefix; |
| } |
| async listNamespaces(parent) { |
| const query = parent ? { parent: namespaceToPath(parent.namespace) } : void 0; |
| const response = await this.client.request({ |
| method: "GET", |
| path: `${this.prefix}/namespaces`, |
| query |
| }); |
| return response.data.namespaces.map((ns) => ({ namespace: ns })); |
| } |
| async createNamespace(id, metadata) { |
| const request = { |
| namespace: id.namespace, |
| properties: metadata?.properties |
| }; |
| const response = await this.client.request({ |
| method: "POST", |
| path: `${this.prefix}/namespaces`, |
| body: request |
| }); |
| return response.data; |
| } |
| async dropNamespace(id) { |
| await this.client.request({ |
| method: "DELETE", |
| path: `${this.prefix}/namespaces/${namespaceToPath(id.namespace)}` |
| }); |
| } |
| async loadNamespaceMetadata(id) { |
| const response = await this.client.request({ |
| method: "GET", |
| path: `${this.prefix}/namespaces/${namespaceToPath(id.namespace)}` |
| }); |
| return { |
| properties: response.data.properties |
| }; |
| } |
| async namespaceExists(id) { |
| try { |
| await this.client.request({ |
| method: "HEAD", |
| path: `${this.prefix}/namespaces/${namespaceToPath(id.namespace)}` |
| }); |
| return true; |
| } catch (error) { |
| if (error instanceof IcebergError && error.status === 404) { |
| return false; |
| } |
| throw error; |
| } |
| } |
| async createNamespaceIfNotExists(id, metadata) { |
| try { |
| return await this.createNamespace(id, metadata); |
| } catch (error) { |
| if (error instanceof IcebergError && error.status === 409) { |
| return; |
| } |
| throw error; |
| } |
| } |
| }; |
|
|
| |
| function namespaceToPath2(namespace) { |
| return namespace.join(""); |
| } |
| var TableOperations = class { |
| constructor(client, prefix = "", accessDelegation) { |
| this.client = client; |
| this.prefix = prefix; |
| this.accessDelegation = accessDelegation; |
| } |
| async listTables(namespace) { |
| const response = await this.client.request({ |
| method: "GET", |
| path: `${this.prefix}/namespaces/${namespaceToPath2(namespace.namespace)}/tables` |
| }); |
| return response.data.identifiers; |
| } |
| async createTable(namespace, request) { |
| const headers = {}; |
| if (this.accessDelegation) { |
| headers["X-Iceberg-Access-Delegation"] = this.accessDelegation; |
| } |
| const response = await this.client.request({ |
| method: "POST", |
| path: `${this.prefix}/namespaces/${namespaceToPath2(namespace.namespace)}/tables`, |
| body: request, |
| headers |
| }); |
| return response.data.metadata; |
| } |
| async updateTable(id, request) { |
| const response = await this.client.request({ |
| method: "POST", |
| path: `${this.prefix}/namespaces/${namespaceToPath2(id.namespace)}/tables/${id.name}`, |
| body: request |
| }); |
| return { |
| "metadata-location": response.data["metadata-location"], |
| metadata: response.data.metadata |
| }; |
| } |
| async dropTable(id, options) { |
| await this.client.request({ |
| method: "DELETE", |
| path: `${this.prefix}/namespaces/${namespaceToPath2(id.namespace)}/tables/${id.name}`, |
| query: { purgeRequested: String(options?.purge ?? false) } |
| }); |
| } |
| async loadTable(id) { |
| const headers = {}; |
| if (this.accessDelegation) { |
| headers["X-Iceberg-Access-Delegation"] = this.accessDelegation; |
| } |
| const response = await this.client.request({ |
| method: "GET", |
| path: `${this.prefix}/namespaces/${namespaceToPath2(id.namespace)}/tables/${id.name}`, |
| headers |
| }); |
| return response.data.metadata; |
| } |
| async tableExists(id) { |
| const headers = {}; |
| if (this.accessDelegation) { |
| headers["X-Iceberg-Access-Delegation"] = this.accessDelegation; |
| } |
| try { |
| await this.client.request({ |
| method: "HEAD", |
| path: `${this.prefix}/namespaces/${namespaceToPath2(id.namespace)}/tables/${id.name}`, |
| headers |
| }); |
| return true; |
| } catch (error) { |
| if (error instanceof IcebergError && error.status === 404) { |
| return false; |
| } |
| throw error; |
| } |
| } |
| async createTableIfNotExists(namespace, request) { |
| try { |
| return await this.createTable(namespace, request); |
| } catch (error) { |
| if (error instanceof IcebergError && error.status === 409) { |
| return await this.loadTable({ namespace: namespace.namespace, name: request.name }); |
| } |
| throw error; |
| } |
| } |
| }; |
|
|
| |
| var IcebergRestCatalog = class { |
| |
| |
| |
| |
| |
| constructor(options) { |
| let prefix = "v1"; |
| if (options.catalogName) { |
| prefix += `/${options.catalogName}`; |
| } |
| const baseUrl = options.baseUrl.endsWith("/") ? options.baseUrl : `${options.baseUrl}/`; |
| this.client = createFetchClient({ |
| baseUrl, |
| auth: options.auth, |
| fetchImpl: options.fetch |
| }); |
| this.accessDelegation = options.accessDelegation?.join(","); |
| this.namespaceOps = new NamespaceOperations(this.client, prefix); |
| this.tableOps = new TableOperations(this.client, prefix, this.accessDelegation); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async listNamespaces(parent) { |
| return this.namespaceOps.listNamespaces(parent); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async createNamespace(id, metadata) { |
| return this.namespaceOps.createNamespace(id, metadata); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async dropNamespace(id) { |
| await this.namespaceOps.dropNamespace(id); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async loadNamespaceMetadata(id) { |
| return this.namespaceOps.loadNamespaceMetadata(id); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async listTables(namespace) { |
| return this.tableOps.listTables(namespace); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async createTable(namespace, request) { |
| return this.tableOps.createTable(namespace, request); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async updateTable(id, request) { |
| return this.tableOps.updateTable(id, request); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async dropTable(id, options) { |
| await this.tableOps.dropTable(id, options); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async loadTable(id) { |
| return this.tableOps.loadTable(id); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async namespaceExists(id) { |
| return this.namespaceOps.namespaceExists(id); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async tableExists(id) { |
| return this.tableOps.tableExists(id); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async createNamespaceIfNotExists(id, metadata) { |
| return this.namespaceOps.createNamespaceIfNotExists(id, metadata); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async createTableIfNotExists(namespace, request) { |
| return this.tableOps.createTableIfNotExists(namespace, request); |
| } |
| }; |
|
|
| |
| var DECIMAL_REGEX = /^decimal\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)$/; |
| var FIXED_REGEX = /^fixed\s*\[\s*(\d+)\s*\]$/; |
| function parseDecimalType(type) { |
| const match = type.match(DECIMAL_REGEX); |
| if (!match) return null; |
| return { |
| precision: parseInt(match[1], 10), |
| scale: parseInt(match[2], 10) |
| }; |
| } |
| function parseFixedType(type) { |
| const match = type.match(FIXED_REGEX); |
| if (!match) return null; |
| return { |
| length: parseInt(match[1], 10) |
| }; |
| } |
| function isDecimalType(type) { |
| return DECIMAL_REGEX.test(type); |
| } |
| function isFixedType(type) { |
| return FIXED_REGEX.test(type); |
| } |
| function typesEqual(a, b) { |
| const decimalA = parseDecimalType(a); |
| const decimalB = parseDecimalType(b); |
| if (decimalA && decimalB) { |
| return decimalA.precision === decimalB.precision && decimalA.scale === decimalB.scale; |
| } |
| const fixedA = parseFixedType(a); |
| const fixedB = parseFixedType(b); |
| if (fixedA && fixedB) { |
| return fixedA.length === fixedB.length; |
| } |
| return a === b; |
| } |
| function getCurrentSchema(metadata) { |
| return metadata.schemas.find((s) => s["schema-id"] === metadata["current-schema-id"]); |
| } |
|
|
| exports.IcebergError = IcebergError; |
| exports.IcebergRestCatalog = IcebergRestCatalog; |
| exports.getCurrentSchema = getCurrentSchema; |
| exports.isDecimalType = isDecimalType; |
| exports.isFixedType = isFixedType; |
| exports.parseDecimalType = parseDecimalType; |
| exports.parseFixedType = parseFixedType; |
| exports.typesEqual = typesEqual; |
| |
| |