// 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
function getCurrentSchema(metadata) {
return metadata.schemas.find((s) => s["schema-id"] === metadata["current-schema-id"]);
}
export { IcebergError, IcebergRestCatalog, getCurrentSchema };
//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map