# iceberg-js [![CI](https://github.com/supabase/iceberg-js/actions/workflows/ci.yml/badge.svg)](https://github.com/supabase/iceberg-js/actions/workflows/ci.yml) [![npm version](https://badge.fury.io/js/iceberg-js.svg)](https://www.npmjs.com/package/iceberg-js) [![pkg.pr.new](https://pkg.pr.new/badge/supabase/iceberg-js)](https://pkg.pr.new/~/supabase/iceberg-js) A small, framework-agnostic JavaScript/TypeScript client for the **Apache Iceberg REST Catalog**. ## Features - **Generic**: Works with any Iceberg REST Catalog implementation, not tied to any specific vendor - **Minimal**: Thin HTTP wrapper over the official REST API, no engine-specific logic - **Type-safe**: First-class TypeScript support with strongly-typed request/response models - **Fetch-based**: Uses native `fetch` API with support for custom implementations - **Universal**: Targets Node 20+ and modern browsers (ES2020) - **Catalog-only**: Focused on catalog operations (no data reading/Parquet support in v0.1.0) ## Documentation 📚 **Full API documentation**: [supabase.github.io/iceberg-js](https://supabase.github.io/iceberg-js/) ## Installation ```bash npm install iceberg-js ``` ## Quick Start ```typescript import { IcebergRestCatalog } from 'iceberg-js' const catalog = new IcebergRestCatalog({ baseUrl: 'https://my-catalog.example.com/iceberg/v1', auth: { type: 'bearer', token: process.env.ICEBERG_TOKEN, }, }) // Create a namespace await catalog.createNamespace({ namespace: ['analytics'] }) // Create a table 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 }, { id: 3, name: 'user_id', type: 'string', required: false }, ], 'schema-id': 0, 'identifier-field-ids': [1], }, 'partition-spec': { 'spec-id': 0, fields: [], }, 'write-order': { 'order-id': 0, fields: [], }, properties: { 'write.format.default': 'parquet', }, } ) ``` ## API Reference ### Constructor #### `new IcebergRestCatalog(options)` Creates a new catalog client instance. **Options:** - `baseUrl` (string, required): Base URL of the REST catalog - `auth` (AuthConfig, optional): Authentication configuration - `catalogName` (string, optional): Catalog name for multi-catalog servers. When specified, requests are sent to `{baseUrl}/v1/{catalogName}/...`. For example, with `baseUrl: 'https://host.com'` and `catalogName: 'prod'`, requests go to `https://host.com/v1/prod/namespaces` - `fetch` (typeof fetch, optional): Custom fetch implementation - `accessDelegation` (AccessDelegation[], optional): Access delegation mechanisms to request from the server **Authentication types:** ```typescript // No authentication { type: 'none' } // Bearer token { type: 'bearer', token: 'your-token' } // Custom header { type: 'header', name: 'X-Custom-Auth', value: 'secret' } // Custom function { type: 'custom', getHeaders: async () => ({ 'Authorization': 'Bearer ...' }) } ``` **Access Delegation:** Access delegation allows the catalog server to provide temporary credentials or sign requests on your behalf: ```typescript import { IcebergRestCatalog } from 'iceberg-js' const catalog = new IcebergRestCatalog({ baseUrl: 'https://catalog.example.com/iceberg/v1', auth: { type: 'bearer', token: 'your-token' }, // Request vended credentials for data access accessDelegation: ['vended-credentials'], }) // The server may return temporary credentials in the table metadata const metadata = await catalog.loadTable({ namespace: ['analytics'], name: 'events' }) // Use credentials from metadata.config to access table data files ``` Supported delegation mechanisms: - `vended-credentials`: Server provides temporary credentials (e.g., AWS STS tokens) for accessing table data - `remote-signing`: Server signs data access requests on behalf of the client ### Namespace Operations #### `listNamespaces(parent?: NamespaceIdentifier): Promise` List all namespaces, optionally under a parent namespace. ```typescript const namespaces = await catalog.listNamespaces() // [{ namespace: ['default'] }, { namespace: ['analytics'] }] const children = await catalog.listNamespaces({ namespace: ['analytics'] }) // [{ namespace: ['analytics', 'prod'] }] ``` #### `createNamespace(id: NamespaceIdentifier, metadata?: NamespaceMetadata): Promise` Create a new namespace with optional properties. ```typescript await catalog.createNamespace({ namespace: ['analytics'] }, { properties: { owner: 'data-team' } }) ``` #### `dropNamespace(id: NamespaceIdentifier): Promise` Drop a namespace. The namespace must be empty. ```typescript await catalog.dropNamespace({ namespace: ['analytics'] }) ``` #### `loadNamespaceMetadata(id: NamespaceIdentifier): Promise` Load namespace metadata and properties. ```typescript const metadata = await catalog.loadNamespaceMetadata({ namespace: ['analytics'] }) // { properties: { owner: 'data-team', ... } } ``` ### Table Operations #### `listTables(namespace: NamespaceIdentifier): Promise` List all tables in a namespace. ```typescript const tables = await catalog.listTables({ namespace: ['analytics'] }) // [{ namespace: ['analytics'], name: 'events' }] ``` #### `createTable(namespace: NamespaceIdentifier, request: CreateTableRequest): Promise` Create a new table. ```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', }, ], }, } ) ``` #### `loadTable(id: TableIdentifier): Promise` Load table metadata. ```typescript const metadata = await catalog.loadTable({ namespace: ['analytics'], name: 'events', }) ``` #### `updateTable(id: TableIdentifier, request: UpdateTableRequest): Promise` Update table metadata (schema, partition spec, or properties). ```typescript const updated = await catalog.updateTable( { namespace: ['analytics'], name: 'events' }, { properties: { 'read.split.target-size': '134217728' }, } ) ``` #### `dropTable(id: TableIdentifier): Promise` Drop a table from the catalog. ```typescript await catalog.dropTable({ namespace: ['analytics'], name: 'events' }) ``` ## Error Handling All API errors throw an `IcebergError` with details from the server: ```typescript import { IcebergError } from 'iceberg-js' try { await catalog.loadTable({ namespace: ['test'], name: 'missing' }) } catch (error) { if (error instanceof IcebergError) { console.log(error.status) // 404 console.log(error.icebergType) // 'NoSuchTableException' console.log(error.message) // 'Table does not exist' } } ``` ## TypeScript Types The library exports all relevant types: ```typescript import type { NamespaceIdentifier, TableIdentifier, TableSchema, TableField, IcebergType, PartitionSpec, SortOrder, CreateTableRequest, TableMetadata, AuthConfig, AccessDelegation, } from 'iceberg-js' ``` ## Supported Iceberg Types The following Iceberg primitive types are supported: - `boolean`, `int`, `long`, `float`, `double` - `string`, `uuid`, `binary` - `date`, `time`, `timestamp`, `timestamptz` - `decimal(precision, scale)`, `fixed(length)` ## Compatibility This package is built to work in **all** Node.js and JavaScript environments: | Environment | Module System | Import Method | Status | | ------------------- | -------------------- | --------------------------------------- | --------------------- | | Node.js ESM | `"type": "module"` | `import { ... } from 'iceberg-js'` | ✅ Fully supported | | Node.js CommonJS | Default | `const { ... } = require('iceberg-js')` | ✅ Fully supported | | TypeScript ESM | `module: "ESNext"` | `import { ... } from 'iceberg-js'` | ✅ Full type support | | TypeScript CommonJS | `module: "CommonJS"` | `import { ... } from 'iceberg-js'` | ✅ Full type support | | Bundlers | Any | Webpack, Vite, esbuild, Rollup, etc. | ✅ Auto-detected | | Browsers | ESM | `