File size: 12,840 Bytes
c212805
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# 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<NamespaceIdentifier[]>`

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<void>`

Create a new namespace with optional properties.

```typescript
await catalog.createNamespace({ namespace: ['analytics'] }, { properties: { owner: 'data-team' } })
```

#### `dropNamespace(id: NamespaceIdentifier): Promise<void>`

Drop a namespace. The namespace must be empty.

```typescript
await catalog.dropNamespace({ namespace: ['analytics'] })
```

#### `loadNamespaceMetadata(id: NamespaceIdentifier): Promise<NamespaceMetadata>`

Load namespace metadata and properties.

```typescript
const metadata = await catalog.loadNamespaceMetadata({ namespace: ['analytics'] })
// { properties: { owner: 'data-team', ... } }
```

### Table Operations

#### `listTables(namespace: NamespaceIdentifier): Promise<TableIdentifier[]>`

List all tables in a namespace.

```typescript
const tables = await catalog.listTables({ namespace: ['analytics'] })
// [{ namespace: ['analytics'], name: 'events' }]
```

#### `createTable(namespace: NamespaceIdentifier, request: CreateTableRequest): Promise<TableMetadata>`

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<TableMetadata>`

Load table metadata.

```typescript
const metadata = await catalog.loadTable({
  namespace: ['analytics'],
  name: 'events',
})
```

#### `updateTable(id: TableIdentifier, request: UpdateTableRequest): Promise<TableMetadata>`

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<void>`

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                  | `<script type="module">`                | ✅ Modern browsers    |
| Deno                | ESM                  | `import` from npm:                      | ✅ With npm specifier |

**Package exports:**

- ESM: `dist/index.mjs` with `dist/index.d.ts`
- CommonJS: `dist/index.cjs` with `dist/index.d.cts`
- Proper `exports` field for Node.js 12+ module resolution

All scenarios are tested in CI on Node.js 20 and 22.

## Browser Usage

The library works in modern browsers that support native `fetch`:

```typescript
import { IcebergRestCatalog } from 'iceberg-js'

const catalog = new IcebergRestCatalog({
  baseUrl: 'https://public-catalog.example.com/iceberg/v1',
  auth: { type: 'none' },
})

const namespaces = await catalog.listNamespaces()
```

## Node.js Usage

Node.js 20+ includes native `fetch` support. For older versions, provide a custom fetch implementation:

```typescript
import { IcebergRestCatalog } from 'iceberg-js'
import fetch from 'node-fetch'

const catalog = new IcebergRestCatalog({
  baseUrl: 'https://catalog.example.com/iceberg/v1',
  auth: { type: 'bearer', token: 'token' },
  fetch: fetch as any,
})
```

## Limitations (v0.1.0)

This is a catalog client only. The following are **not supported**:

- Reading table data (scanning Parquet files)
- Writing data to tables
- Advanced table operations (commits, snapshots, time travel)
- Views support
- Multi-table transactions

## Development

```bash
# Install dependencies
pnpm install

# Build the library
pnpm run build

# Run unit tests
pnpm test

# Run integration tests (requires Docker)
pnpm test:integration

# Run integration tests with cleanup (for CI)
pnpm test:integration:ci

# Run compatibility tests (all module systems)
pnpm test:compatibility

# Format code
pnpm run format

# Lint and test
pnpm run check
```

### Testing with Docker

Integration tests run against a local Iceberg REST Catalog in Docker. See [TESTING-DOCKER.md](./test/integration/TESTING-DOCKER.md) for details.

```bash
# Start Docker services and run integration tests
pnpm test:integration

# Or manually
docker compose up -d
npx tsx test/integration/test-local-catalog.ts
docker compose down -v
```

### Compatibility Testing

The `test:compatibility` script verifies the package works correctly in all JavaScript/TypeScript environments:

- **Pure JavaScript ESM** - Projects with `"type": "module"`
- **Pure JavaScript CommonJS** - Traditional Node.js projects
- **TypeScript ESM** - TypeScript with `module: "ESNext"`
- **TypeScript CommonJS** - TypeScript with `module: "CommonJS"`

These tests ensure proper module resolution, type definitions, and runtime behavior across all supported environments. See [test/compatibility/README.md](./test/compatibility/README.md) for more details.

## License

MIT

## Releases

This project uses [release-please](https://github.com/googleapis/release-please) for automated releases. Here's how it works:

1. **Commit with conventional commits**: Use [Conventional Commits](https://www.conventionalcommits.org/) format for your commits:
   - `feat:` for new features (minor version bump)
   - `fix:` for bug fixes (patch version bump)
   - `feat!:` or `BREAKING CHANGE:` for breaking changes (major version bump)
   - `chore:`, `docs:`, `test:`, etc. for non-release commits

2. **Release PR is created automatically**: When you push to `main`, release-please creates/updates a release PR with:
   - Version bump in `package.json`
   - Updated `CHANGELOG.md`
   - Release notes

3. **Merge the release PR**: When you're ready to release, merge the PR. This will:
   - Create a GitHub release and git tag
   - Automatically publish to npm with provenance (using trusted publishing, no secrets needed)

**Example commits:**

```bash
git commit -m "feat: add support for view operations"
git commit -m "fix: handle empty namespace list correctly"
git commit -m "feat!: change auth config structure"
```

## Contributing

Contributions are welcome! This library aims to be a minimal, generic client for the Iceberg REST Catalog API.