// src/errors/IcebergError.ts 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; } /** * Returns true if the error is a 404 Not Found error. */ isNotFound() { return this.status === 404; } /** * Returns true if the error is a 409 Conflict error. */ isConflict() { return this.status === 409; } /** * Returns true if the error is a 419 Authentication Timeout error. */ isAuthenticationTimeout() { return this.status === 419; } }; // src/utils/url.ts 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(); } // src/http/createFetchClient.ts 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 }; } }; } // src/catalog/namespaces.ts 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; } } }; // src/catalog/tables.ts 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; } } }; // src/catalog/IcebergRestCatalog.ts var IcebergRestCatalog = class { /** * Creates a new Iceberg REST Catalog client. * * @param options - Configuration options for the catalog client */ 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); } /** * Lists all namespaces in the catalog. * * @param parent - Optional parent namespace to list children under * @returns Array of namespace identifiers * * @example * ```typescript * // List all top-level namespaces * const namespaces = await catalog.listNamespaces(); * * // List namespaces under a parent * const children = await catalog.listNamespaces({ namespace: ['analytics'] }); * ``` */ async listNamespaces(parent) { return this.namespaceOps.listNamespaces(parent); } /** * Creates a new namespace in the catalog. * * @param id - Namespace identifier to create * @param metadata - Optional metadata properties for the namespace * @returns Response containing the created namespace and its properties * * @example * ```typescript * const response = await catalog.createNamespace( * { namespace: ['analytics'] }, * { properties: { owner: 'data-team' } } * ); * console.log(response.namespace); // ['analytics'] * console.log(response.properties); // { owner: 'data-team', ... } * ``` */ async createNamespace(id, metadata) { return this.namespaceOps.createNamespace(id, metadata); } /** * Drops a namespace from the catalog. * * The namespace must be empty (contain no tables) before it can be dropped. * * @param id - Namespace identifier to drop * * @example * ```typescript * await catalog.dropNamespace({ namespace: ['analytics'] }); * ``` */ async dropNamespace(id) { await this.namespaceOps.dropNamespace(id); } /** * Loads metadata for a namespace. * * @param id - Namespace identifier to load * @returns Namespace metadata including properties * * @example * ```typescript * const metadata = await catalog.loadNamespaceMetadata({ namespace: ['analytics'] }); * console.log(metadata.properties); * ``` */ async loadNamespaceMetadata(id) { return this.namespaceOps.loadNamespaceMetadata(id); } /** * Lists all tables in a namespace. * * @param namespace - Namespace identifier to list tables from * @returns Array of table identifiers * * @example * ```typescript * const tables = await catalog.listTables({ namespace: ['analytics'] }); * console.log(tables); // [{ namespace: ['analytics'], name: 'events' }, ...] * ``` */ async listTables(namespace) { return this.tableOps.listTables(namespace); } /** * Creates a new table in the catalog. * * @param namespace - Namespace to create the table in * @param request - Table creation request including name, schema, partition spec, etc. * @returns Table metadata for the created table * * @example * ```typescript * const metadata = await catalog.createTable( * { namespace: ['analytics'] }, * { * name: 'events', * schema: { * type: 'struct', * fields: [ * { id: 1, name: 'id', type: 'long', required: true }, * { id: 2, name: 'timestamp', type: 'timestamp', required: true } * ], * 'schema-id': 0 * }, * 'partition-spec': { * 'spec-id': 0, * fields: [ * { source_id: 2, field_id: 1000, name: 'ts_day', transform: 'day' } * ] * } * } * ); * ``` */ async createTable(namespace, request) { return this.tableOps.createTable(namespace, request); } /** * Updates an existing table's metadata. * * Can update the schema, partition spec, or properties of a table. * * @param id - Table identifier to update * @param request - Update request with fields to modify * @returns Response containing the metadata location and updated table metadata * * @example * ```typescript * const response = await catalog.updateTable( * { namespace: ['analytics'], name: 'events' }, * { * properties: { 'read.split.target-size': '134217728' } * } * ); * console.log(response['metadata-location']); // s3://... * console.log(response.metadata); // TableMetadata object * ``` */ async updateTable(id, request) { return this.tableOps.updateTable(id, request); } /** * Drops a table from the catalog. * * @param id - Table identifier to drop * * @example * ```typescript * await catalog.dropTable({ namespace: ['analytics'], name: 'events' }); * ``` */ async dropTable(id, options) { await this.tableOps.dropTable(id, options); } /** * Loads metadata for a table. * * @param id - Table identifier to load * @returns Table metadata including schema, partition spec, location, etc. * * @example * ```typescript * const metadata = await catalog.loadTable({ namespace: ['analytics'], name: 'events' }); * console.log(metadata.schema); * console.log(metadata.location); * ``` */ async loadTable(id) { return this.tableOps.loadTable(id); } /** * Checks if a namespace exists in the catalog. * * @param id - Namespace identifier to check * @returns True if the namespace exists, false otherwise * * @example * ```typescript * const exists = await catalog.namespaceExists({ namespace: ['analytics'] }); * console.log(exists); // true or false * ``` */ async namespaceExists(id) { return this.namespaceOps.namespaceExists(id); } /** * Checks if a table exists in the catalog. * * @param id - Table identifier to check * @returns True if the table exists, false otherwise * * @example * ```typescript * const exists = await catalog.tableExists({ namespace: ['analytics'], name: 'events' }); * console.log(exists); // true or false * ``` */ async tableExists(id) { return this.tableOps.tableExists(id); } /** * Creates a namespace if it does not exist. * * If the namespace already exists, returns void. If created, returns the response. * * @param id - Namespace identifier to create * @param metadata - Optional metadata properties for the namespace * @returns Response containing the created namespace and its properties, or void if it already exists * * @example * ```typescript * const response = await catalog.createNamespaceIfNotExists( * { namespace: ['analytics'] }, * { properties: { owner: 'data-team' } } * ); * if (response) { * console.log('Created:', response.namespace); * } else { * console.log('Already exists'); * } * ``` */ async createNamespaceIfNotExists(id, metadata) { return this.namespaceOps.createNamespaceIfNotExists(id, metadata); } /** * Creates a table if it does not exist. * * If the table already exists, returns its metadata instead. * * @param namespace - Namespace to create the table in * @param request - Table creation request including name, schema, partition spec, etc. * @returns Table metadata for the created or existing table * * @example * ```typescript * const metadata = await catalog.createTableIfNotExists( * { namespace: ['analytics'] }, * { * name: 'events', * schema: { * type: 'struct', * fields: [ * { id: 1, name: 'id', type: 'long', required: true }, * { id: 2, name: 'timestamp', type: 'timestamp', required: true } * ], * 'schema-id': 0 * } * } * ); * ``` */ async createTableIfNotExists(namespace, request) { return this.tableOps.createTableIfNotExists(namespace, request); } }; // src/catalog/types.ts 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"]); } export { IcebergError, IcebergRestCatalog, getCurrentSchema, isDecimalType, isFixedType, parseDecimalType, parseFixedType, typesEqual }; //# sourceMappingURL=index.mjs.map //# sourceMappingURL=index.mjs.map