wip:milestone 0 fixes
Some checks failed
CI/CD Pipeline / unit-tests (push) Failing after 1m16s
CI/CD Pipeline / integration-tests (push) Failing after 2m32s
CI/CD Pipeline / lint (push) Successful in 5m22s
CI/CD Pipeline / e2e-tests (push) Has been skipped
CI/CD Pipeline / build (push) Has been skipped

This commit is contained in:
2026-03-15 12:35:42 +02:00
parent 6708cf28a7
commit cffdf8af86
61266 changed files with 4511646 additions and 1938 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Supabase
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,439 @@
# 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.

View File

@@ -0,0 +1,596 @@
'use strict';
// 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"]);
}
exports.IcebergError = IcebergError;
exports.IcebergRestCatalog = IcebergRestCatalog;
exports.getCurrentSchema = getCurrentSchema;
exports.isDecimalType = isDecimalType;
exports.isFixedType = isFixedType;
exports.parseDecimalType = parseDecimalType;
exports.parseFixedType = parseFixedType;
exports.typesEqual = typesEqual;
//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,547 @@
type AuthConfig = {
type: 'none';
} | {
type: 'bearer';
token: string;
} | {
type: 'header';
name: string;
value: string;
} | {
type: 'custom';
getHeaders: () => Record<string, string> | Promise<Record<string, string>>;
};
interface NamespaceIdentifier {
namespace: string[];
}
interface NamespaceMetadata {
properties: Record<string, string>;
}
interface TableIdentifier {
namespace: string[];
name: string;
}
/**
* Primitive types in Iceberg - all represented as strings.
* Parameterized types use string format: decimal(precision,scale) and fixed[length]
*
* Note: The OpenAPI spec defines PrimitiveType as `type: string`, so any string is valid.
* We include known types for autocomplete, plus a catch-all for flexibility.
*/
type PrimitiveType = 'boolean' | 'int' | 'long' | 'float' | 'double' | 'string' | 'timestamp' | 'date' | 'time' | 'timestamptz' | 'uuid' | 'binary' | `decimal(${number},${number})` | `fixed[${number}]` | (string & {});
/**
* Parse a decimal type string into its components.
* Handles any whitespace formatting (e.g., "decimal(10,2)", "decimal(10, 2)", "decimal( 10 , 2 )").
*
* @param type - The type string to parse
* @returns Object with precision and scale, or null if not a valid decimal type
*/
declare function parseDecimalType(type: string): {
precision: number;
scale: number;
} | null;
/**
* Parse a fixed type string into its length.
* Handles any whitespace formatting (e.g., "fixed[16]", "fixed[ 16 ]").
*
* @param type - The type string to parse
* @returns Object with length, or null if not a valid fixed type
*/
declare function parseFixedType(type: string): {
length: number;
} | null;
/**
* Check if a type string is a decimal type.
*/
declare function isDecimalType(type: string): boolean;
/**
* Check if a type string is a fixed type.
*/
declare function isFixedType(type: string): boolean;
/**
* Compare two Iceberg type strings for equality, ignoring whitespace differences.
* This is useful when comparing types from user input vs catalog responses,
* as catalogs may normalize whitespace differently.
*
* @param a - First type string
* @param b - Second type string
* @returns true if the types are equivalent
*/
declare function typesEqual(a: string, b: string): boolean;
/**
* Struct type - a nested structure containing fields.
* Used for nested records within a field.
*/
interface StructType {
type: 'struct';
fields: StructField[];
}
/**
* List type - an array of elements.
*/
interface ListType {
type: 'list';
'element-id': number;
element: IcebergType;
'element-required': boolean;
}
/**
* Map type - a key-value mapping.
*/
interface MapType {
type: 'map';
'key-id': number;
key: IcebergType;
'value-id': number;
value: IcebergType;
'value-required': boolean;
}
/**
* Union of all Iceberg types.
* Can be a primitive type (string) or a complex type (struct, list, map).
*/
type IcebergType = PrimitiveType | StructType | ListType | MapType;
/**
* Primitive type values for default values.
* Represents the possible values for initial-default and write-default.
*/
type PrimitiveTypeValue = boolean | number | string;
/**
* A field within a struct (used in nested StructType).
*/
interface StructField {
id: number;
name: string;
type: IcebergType;
required: boolean;
doc?: string;
'initial-default'?: PrimitiveTypeValue;
'write-default'?: PrimitiveTypeValue;
}
/**
* A field within a table schema (top-level).
* Equivalent to StructField but kept for backwards compatibility.
*/
interface TableField {
id: number;
name: string;
type: IcebergType;
required: boolean;
doc?: string;
'initial-default'?: PrimitiveTypeValue;
'write-default'?: PrimitiveTypeValue;
}
interface TableSchema {
type: 'struct';
fields: TableField[];
'schema-id'?: number;
'identifier-field-ids'?: number[];
}
interface PartitionField {
source_id: number;
field_id: number;
name: string;
transform: string;
}
interface PartitionSpec {
'spec-id': number;
fields: PartitionField[];
}
interface SortField {
source_id: number;
transform: string;
direction: 'asc' | 'desc';
null_order: 'nulls-first' | 'nulls-last';
}
interface SortOrder {
'order-id': number;
fields: SortField[];
}
interface CreateTableRequest {
name: string;
schema: TableSchema;
'partition-spec'?: PartitionSpec;
'write-order'?: SortOrder;
properties?: Record<string, string>;
'stage-create'?: boolean;
}
interface UpdateTableRequest {
schema?: TableSchema;
'partition-spec'?: PartitionSpec;
properties?: Record<string, string>;
}
interface DropTableRequest {
purge?: boolean;
}
interface TableMetadata {
name?: string;
location: string;
schemas: TableSchema[];
'current-schema-id': number;
'partition-specs': PartitionSpec[];
'default-spec-id'?: number;
'sort-orders': SortOrder[];
'default-sort-order-id'?: number;
properties: Record<string, string>;
'metadata-location'?: string;
'current-snapshot-id'?: number;
snapshots?: unknown[];
'snapshot-log'?: unknown[];
'metadata-log'?: unknown[];
refs?: Record<string, unknown>;
'last-updated-ms'?: number;
'last-column-id'?: number;
'last-sequence-number'?: number;
'table-uuid'?: string;
'format-version'?: number;
'last-partition-id'?: number;
}
interface CreateNamespaceResponse {
namespace: string[];
properties?: Record<string, string>;
}
interface CommitTableResponse {
'metadata-location': string;
metadata: TableMetadata;
}
/**
* Gets the current (active) schema from table metadata.
*
* @param metadata - Table metadata containing schemas array and current-schema-id
* @returns The current table schema, or undefined if not found
*/
declare function getCurrentSchema(metadata: TableMetadata): TableSchema | undefined;
/**
* Access delegation mechanisms supported by the Iceberg REST Catalog.
*
* - `vended-credentials`: Server provides temporary credentials for data access
* - `remote-signing`: Server signs requests on behalf of the client
*/
type AccessDelegation = 'vended-credentials' | 'remote-signing';
/**
* Configuration options for the Iceberg REST Catalog client.
*/
interface IcebergRestCatalogOptions {
/** Base URL of the Iceberg REST Catalog API */
baseUrl: string;
/** Optional catalog name prefix for multi-catalog servers */
catalogName?: string;
/** Authentication configuration */
auth?: AuthConfig;
/** Custom fetch implementation (defaults to globalThis.fetch) */
fetch?: typeof fetch;
/**
* Access delegation mechanisms to request from the server.
* When specified, the X-Iceberg-Access-Delegation header will be sent
* with supported operations (createTable, loadTable).
*
* @example ['vended-credentials']
* @example ['vended-credentials', 'remote-signing']
*/
accessDelegation?: AccessDelegation[];
}
/**
* Client for interacting with an Apache Iceberg REST Catalog.
*
* This class provides methods for managing namespaces and tables in an Iceberg catalog.
* It handles authentication, request formatting, and error handling automatically.
*
* @example
* ```typescript
* 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: [...] }
* }
* );
* ```
*/
declare class IcebergRestCatalog {
private readonly client;
private readonly namespaceOps;
private readonly tableOps;
private readonly accessDelegation?;
/**
* Creates a new Iceberg REST Catalog client.
*
* @param options - Configuration options for the catalog client
*/
constructor(options: IcebergRestCatalogOptions);
/**
* 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'] });
* ```
*/
listNamespaces(parent?: NamespaceIdentifier): Promise<NamespaceIdentifier[]>;
/**
* 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', ... }
* ```
*/
createNamespace(id: NamespaceIdentifier, metadata?: NamespaceMetadata): Promise<CreateNamespaceResponse>;
/**
* 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'] });
* ```
*/
dropNamespace(id: NamespaceIdentifier): Promise<void>;
/**
* 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);
* ```
*/
loadNamespaceMetadata(id: NamespaceIdentifier): Promise<NamespaceMetadata>;
/**
* 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' }, ...]
* ```
*/
listTables(namespace: NamespaceIdentifier): Promise<TableIdentifier[]>;
/**
* 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' }
* ]
* }
* }
* );
* ```
*/
createTable(namespace: NamespaceIdentifier, request: CreateTableRequest): Promise<TableMetadata>;
/**
* 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
* ```
*/
updateTable(id: TableIdentifier, request: UpdateTableRequest): Promise<CommitTableResponse>;
/**
* Drops a table from the catalog.
*
* @param id - Table identifier to drop
*
* @example
* ```typescript
* await catalog.dropTable({ namespace: ['analytics'], name: 'events' });
* ```
*/
dropTable(id: TableIdentifier, options?: DropTableRequest): Promise<void>;
/**
* 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);
* ```
*/
loadTable(id: TableIdentifier): Promise<TableMetadata>;
/**
* 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
* ```
*/
namespaceExists(id: NamespaceIdentifier): Promise<boolean>;
/**
* 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
* ```
*/
tableExists(id: TableIdentifier): Promise<boolean>;
/**
* 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');
* }
* ```
*/
createNamespaceIfNotExists(id: NamespaceIdentifier, metadata?: NamespaceMetadata): Promise<CreateNamespaceResponse | void>;
/**
* 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
* }
* }
* );
* ```
*/
createTableIfNotExists(namespace: NamespaceIdentifier, request: CreateTableRequest): Promise<TableMetadata>;
}
interface IcebergErrorResponse {
error: {
message: string;
type: string;
code: number;
stack?: string[];
};
}
declare class IcebergError extends Error {
readonly status: number;
readonly icebergType?: string;
readonly icebergCode?: number;
readonly details?: unknown;
readonly isCommitStateUnknown: boolean;
constructor(message: string, opts: {
status: number;
icebergType?: string;
icebergCode?: number;
details?: unknown;
});
/**
* Returns true if the error is a 404 Not Found error.
*/
isNotFound(): boolean;
/**
* Returns true if the error is a 409 Conflict error.
*/
isConflict(): boolean;
/**
* Returns true if the error is a 419 Authentication Timeout error.
*/
isAuthenticationTimeout(): boolean;
}
export { type AccessDelegation, type AuthConfig, type CommitTableResponse, type CreateNamespaceResponse, type CreateTableRequest, type DropTableRequest, IcebergError, type IcebergErrorResponse, IcebergRestCatalog, type IcebergRestCatalogOptions, type IcebergType, type ListType, type MapType, type NamespaceIdentifier, type NamespaceMetadata, type PartitionField, type PartitionSpec, type PrimitiveType, type PrimitiveTypeValue, type SortField, type SortOrder, type StructField, type StructType, type TableField, type TableIdentifier, type TableMetadata, type TableSchema, type UpdateTableRequest, getCurrentSchema, isDecimalType, isFixedType, parseDecimalType, parseFixedType, typesEqual };

View File

@@ -0,0 +1,547 @@
type AuthConfig = {
type: 'none';
} | {
type: 'bearer';
token: string;
} | {
type: 'header';
name: string;
value: string;
} | {
type: 'custom';
getHeaders: () => Record<string, string> | Promise<Record<string, string>>;
};
interface NamespaceIdentifier {
namespace: string[];
}
interface NamespaceMetadata {
properties: Record<string, string>;
}
interface TableIdentifier {
namespace: string[];
name: string;
}
/**
* Primitive types in Iceberg - all represented as strings.
* Parameterized types use string format: decimal(precision,scale) and fixed[length]
*
* Note: The OpenAPI spec defines PrimitiveType as `type: string`, so any string is valid.
* We include known types for autocomplete, plus a catch-all for flexibility.
*/
type PrimitiveType = 'boolean' | 'int' | 'long' | 'float' | 'double' | 'string' | 'timestamp' | 'date' | 'time' | 'timestamptz' | 'uuid' | 'binary' | `decimal(${number},${number})` | `fixed[${number}]` | (string & {});
/**
* Parse a decimal type string into its components.
* Handles any whitespace formatting (e.g., "decimal(10,2)", "decimal(10, 2)", "decimal( 10 , 2 )").
*
* @param type - The type string to parse
* @returns Object with precision and scale, or null if not a valid decimal type
*/
declare function parseDecimalType(type: string): {
precision: number;
scale: number;
} | null;
/**
* Parse a fixed type string into its length.
* Handles any whitespace formatting (e.g., "fixed[16]", "fixed[ 16 ]").
*
* @param type - The type string to parse
* @returns Object with length, or null if not a valid fixed type
*/
declare function parseFixedType(type: string): {
length: number;
} | null;
/**
* Check if a type string is a decimal type.
*/
declare function isDecimalType(type: string): boolean;
/**
* Check if a type string is a fixed type.
*/
declare function isFixedType(type: string): boolean;
/**
* Compare two Iceberg type strings for equality, ignoring whitespace differences.
* This is useful when comparing types from user input vs catalog responses,
* as catalogs may normalize whitespace differently.
*
* @param a - First type string
* @param b - Second type string
* @returns true if the types are equivalent
*/
declare function typesEqual(a: string, b: string): boolean;
/**
* Struct type - a nested structure containing fields.
* Used for nested records within a field.
*/
interface StructType {
type: 'struct';
fields: StructField[];
}
/**
* List type - an array of elements.
*/
interface ListType {
type: 'list';
'element-id': number;
element: IcebergType;
'element-required': boolean;
}
/**
* Map type - a key-value mapping.
*/
interface MapType {
type: 'map';
'key-id': number;
key: IcebergType;
'value-id': number;
value: IcebergType;
'value-required': boolean;
}
/**
* Union of all Iceberg types.
* Can be a primitive type (string) or a complex type (struct, list, map).
*/
type IcebergType = PrimitiveType | StructType | ListType | MapType;
/**
* Primitive type values for default values.
* Represents the possible values for initial-default and write-default.
*/
type PrimitiveTypeValue = boolean | number | string;
/**
* A field within a struct (used in nested StructType).
*/
interface StructField {
id: number;
name: string;
type: IcebergType;
required: boolean;
doc?: string;
'initial-default'?: PrimitiveTypeValue;
'write-default'?: PrimitiveTypeValue;
}
/**
* A field within a table schema (top-level).
* Equivalent to StructField but kept for backwards compatibility.
*/
interface TableField {
id: number;
name: string;
type: IcebergType;
required: boolean;
doc?: string;
'initial-default'?: PrimitiveTypeValue;
'write-default'?: PrimitiveTypeValue;
}
interface TableSchema {
type: 'struct';
fields: TableField[];
'schema-id'?: number;
'identifier-field-ids'?: number[];
}
interface PartitionField {
source_id: number;
field_id: number;
name: string;
transform: string;
}
interface PartitionSpec {
'spec-id': number;
fields: PartitionField[];
}
interface SortField {
source_id: number;
transform: string;
direction: 'asc' | 'desc';
null_order: 'nulls-first' | 'nulls-last';
}
interface SortOrder {
'order-id': number;
fields: SortField[];
}
interface CreateTableRequest {
name: string;
schema: TableSchema;
'partition-spec'?: PartitionSpec;
'write-order'?: SortOrder;
properties?: Record<string, string>;
'stage-create'?: boolean;
}
interface UpdateTableRequest {
schema?: TableSchema;
'partition-spec'?: PartitionSpec;
properties?: Record<string, string>;
}
interface DropTableRequest {
purge?: boolean;
}
interface TableMetadata {
name?: string;
location: string;
schemas: TableSchema[];
'current-schema-id': number;
'partition-specs': PartitionSpec[];
'default-spec-id'?: number;
'sort-orders': SortOrder[];
'default-sort-order-id'?: number;
properties: Record<string, string>;
'metadata-location'?: string;
'current-snapshot-id'?: number;
snapshots?: unknown[];
'snapshot-log'?: unknown[];
'metadata-log'?: unknown[];
refs?: Record<string, unknown>;
'last-updated-ms'?: number;
'last-column-id'?: number;
'last-sequence-number'?: number;
'table-uuid'?: string;
'format-version'?: number;
'last-partition-id'?: number;
}
interface CreateNamespaceResponse {
namespace: string[];
properties?: Record<string, string>;
}
interface CommitTableResponse {
'metadata-location': string;
metadata: TableMetadata;
}
/**
* Gets the current (active) schema from table metadata.
*
* @param metadata - Table metadata containing schemas array and current-schema-id
* @returns The current table schema, or undefined if not found
*/
declare function getCurrentSchema(metadata: TableMetadata): TableSchema | undefined;
/**
* Access delegation mechanisms supported by the Iceberg REST Catalog.
*
* - `vended-credentials`: Server provides temporary credentials for data access
* - `remote-signing`: Server signs requests on behalf of the client
*/
type AccessDelegation = 'vended-credentials' | 'remote-signing';
/**
* Configuration options for the Iceberg REST Catalog client.
*/
interface IcebergRestCatalogOptions {
/** Base URL of the Iceberg REST Catalog API */
baseUrl: string;
/** Optional catalog name prefix for multi-catalog servers */
catalogName?: string;
/** Authentication configuration */
auth?: AuthConfig;
/** Custom fetch implementation (defaults to globalThis.fetch) */
fetch?: typeof fetch;
/**
* Access delegation mechanisms to request from the server.
* When specified, the X-Iceberg-Access-Delegation header will be sent
* with supported operations (createTable, loadTable).
*
* @example ['vended-credentials']
* @example ['vended-credentials', 'remote-signing']
*/
accessDelegation?: AccessDelegation[];
}
/**
* Client for interacting with an Apache Iceberg REST Catalog.
*
* This class provides methods for managing namespaces and tables in an Iceberg catalog.
* It handles authentication, request formatting, and error handling automatically.
*
* @example
* ```typescript
* 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: [...] }
* }
* );
* ```
*/
declare class IcebergRestCatalog {
private readonly client;
private readonly namespaceOps;
private readonly tableOps;
private readonly accessDelegation?;
/**
* Creates a new Iceberg REST Catalog client.
*
* @param options - Configuration options for the catalog client
*/
constructor(options: IcebergRestCatalogOptions);
/**
* 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'] });
* ```
*/
listNamespaces(parent?: NamespaceIdentifier): Promise<NamespaceIdentifier[]>;
/**
* 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', ... }
* ```
*/
createNamespace(id: NamespaceIdentifier, metadata?: NamespaceMetadata): Promise<CreateNamespaceResponse>;
/**
* 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'] });
* ```
*/
dropNamespace(id: NamespaceIdentifier): Promise<void>;
/**
* 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);
* ```
*/
loadNamespaceMetadata(id: NamespaceIdentifier): Promise<NamespaceMetadata>;
/**
* 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' }, ...]
* ```
*/
listTables(namespace: NamespaceIdentifier): Promise<TableIdentifier[]>;
/**
* 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' }
* ]
* }
* }
* );
* ```
*/
createTable(namespace: NamespaceIdentifier, request: CreateTableRequest): Promise<TableMetadata>;
/**
* 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
* ```
*/
updateTable(id: TableIdentifier, request: UpdateTableRequest): Promise<CommitTableResponse>;
/**
* Drops a table from the catalog.
*
* @param id - Table identifier to drop
*
* @example
* ```typescript
* await catalog.dropTable({ namespace: ['analytics'], name: 'events' });
* ```
*/
dropTable(id: TableIdentifier, options?: DropTableRequest): Promise<void>;
/**
* 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);
* ```
*/
loadTable(id: TableIdentifier): Promise<TableMetadata>;
/**
* 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
* ```
*/
namespaceExists(id: NamespaceIdentifier): Promise<boolean>;
/**
* 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
* ```
*/
tableExists(id: TableIdentifier): Promise<boolean>;
/**
* 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');
* }
* ```
*/
createNamespaceIfNotExists(id: NamespaceIdentifier, metadata?: NamespaceMetadata): Promise<CreateNamespaceResponse | void>;
/**
* 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
* }
* }
* );
* ```
*/
createTableIfNotExists(namespace: NamespaceIdentifier, request: CreateTableRequest): Promise<TableMetadata>;
}
interface IcebergErrorResponse {
error: {
message: string;
type: string;
code: number;
stack?: string[];
};
}
declare class IcebergError extends Error {
readonly status: number;
readonly icebergType?: string;
readonly icebergCode?: number;
readonly details?: unknown;
readonly isCommitStateUnknown: boolean;
constructor(message: string, opts: {
status: number;
icebergType?: string;
icebergCode?: number;
details?: unknown;
});
/**
* Returns true if the error is a 404 Not Found error.
*/
isNotFound(): boolean;
/**
* Returns true if the error is a 409 Conflict error.
*/
isConflict(): boolean;
/**
* Returns true if the error is a 419 Authentication Timeout error.
*/
isAuthenticationTimeout(): boolean;
}
export { type AccessDelegation, type AuthConfig, type CommitTableResponse, type CreateNamespaceResponse, type CreateTableRequest, type DropTableRequest, IcebergError, type IcebergErrorResponse, IcebergRestCatalog, type IcebergRestCatalogOptions, type IcebergType, type ListType, type MapType, type NamespaceIdentifier, type NamespaceMetadata, type PartitionField, type PartitionSpec, type PrimitiveType, type PrimitiveTypeValue, type SortField, type SortOrder, type StructField, type StructType, type TableField, type TableIdentifier, type TableMetadata, type TableSchema, type UpdateTableRequest, getCurrentSchema, isDecimalType, isFixedType, parseDecimalType, parseFixedType, typesEqual };

View File

@@ -0,0 +1,587 @@
// 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

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,77 @@
{
"name": "iceberg-js",
"version": "0.8.1",
"description": "A small, framework-agnostic JavaScript/TypeScript client for the Apache Iceberg REST Catalog",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
},
"default": "./dist/index.mjs"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"docs": "typedoc src/index.ts",
"format": "prettier --write .",
"lint": "eslint .",
"type-check": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest watch",
"test:integration": "bash scripts/test-integration.sh",
"test:integration:ci": "bash scripts/test-integration.sh --cleanup",
"test:compatibility": "bash test/compatibility/run-all.sh",
"check": "pnpm lint && pnpm type-check && pnpm test && pnpm build"
},
"keywords": [
"iceberg",
"apache-iceberg",
"rest-catalog",
"data-lake",
"catalog"
],
"author": "mandarini",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/supabase/iceberg-js"
},
"bugs": {
"url": "https://github.com/supabase/iceberg-js/issues"
},
"homepage": "https://github.com/supabase/iceberg-js#readme",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@eslint/json": "^0.14.0",
"@eslint/markdown": "^7.5.1",
"@types/node": "^20.0.0",
"eslint": "^9.39.1",
"globals": "^16.5.0",
"jiti": "^2.6.1",
"prettier": "^3.6.2",
"tsup": "^8.5.1",
"typedoc": "^0.28.14",
"typescript": "^5.9.3",
"typescript-eslint": "^8.47.0",
"vitest": "^4.0.12"
},
"engines": {
"node": ">=20.0.0"
}
}