Skip to content

feat(storage): add analytics bucket and Iceberg namespace/table management (alpha) - #1183

Draft
grdsdev wants to merge 13 commits into
mainfrom
feat-storage-analytics-iceberg
Draft

feat(storage): add analytics bucket and Iceberg namespace/table management (alpha)#1183
grdsdev wants to merge 13 commits into
mainfrom
feat-storage-analytics-iceberg

Conversation

@grdsdev

@grdsdev grdsdev commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #1181 (which is stacked on #1153). Implements SDK-1301: Supabase Storage's alpha analytics buckets (Iceberg-backed) feature.

  • storage.analytics.{createBucket,listBuckets,deleteBucket} — analytics bucket CRUD.
  • storage.analytics.from(_:) -> AnalyticsBucketClient — bucket-scoped Iceberg namespace management: createNamespace, getNamespace, listNamespaces, namespaceExists, deleteNamespace.
  • AnalyticsBucketClient.namespace(_:) -> IcebergNamespaceClient — namespace-scoped table management: createTable, getTable, listTables, tableExists, deleteTable.

Implemented by hand on the existing StorageApi/StorageHTTPSession HTTP stack (no code generation), matching:

  • @supabase/storage-js's StorageAnalyticsClient for the bucket CRUD shape.
  • The supabase/storage backend's own rest-catalog-client.ts TypeScript types (TableMetadata, Schema, PartitionSpec, SortOrder, etc.) for the exact wire format — this is the backend's authoritative, narrower-than-generic-Iceberg-spec contract (e.g. no sort-orders/refs/snapshots on TableMetadata in this backend's actual implementation), so IcebergTableMetadata models exactly what the server returns rather than the full Apache Iceberg spec.

Notable fix: StorageError nested error shape

The analytics/Iceberg routes run through a different backend error formatter than the rest of Storage: { error: { message, type, code } } instead of the flat { error, message, statusCode } shape used everywhere else (confirmed by reading error-handler.ts vs. iceberg/index.ts's setErrorHandler call). StorageError now tries the flat shape first and falls back to the nested one, so callers get a correctly-populated StorageError regardless of which shape the backend used — no new public API, no behavior change for existing endpoints.

API design notes

  • IcebergType is a recursive indirect enum (primitive/structType/listType/mapType) with ExpressibleByStringLiteral for primitives, so IcebergStructField(id: 1, name: "id", type: "long", required: true) reads naturally.
  • IcebergSortDirection/IcebergNullOrder/AnalyticsBucketSortColumn follow this module's existing open string-backed-struct convention (ResizeMode/ImageFormat/SortOrder); listBuckets reuses the existing SortOrder type directly.
  • IcebergNamespace.name and table identifiers are flattened to plain String (not [String]) since this backend only supports single-level namespaces — the wire format still sends/receives arrays for forward-compatibility with the general Iceberg spec, but exposing that generality here would just be friction for a constraint the backend itself enforces.
  • namespaceExists/tableExists return Bool (catching 400/404), matching the existing StorageFileApi.exists(path:) convention instead of forcing callers to catch exceptions for a common existence check.
  • AnalyticsClient/AnalyticsBucketClient/IcebergNamespaceClient are structs holding a StorageApi dependency (composition, matching feat(storage): add vector bucket CRUD (alpha) #1153/feat(storage): add vector index and vector data operations (alpha) #1181's vector clients), not StorageApi subclasses. from(_:)/namespace(_:) thread that same dependency through each scoping step, and SupabaseStorageClient.analytics passes self as the dependency.

Out of scope

  • updateTable (commit-table-changes / schema evolution): the backend itself types this endpoint's response as additionalProperties: true (opaque), and the requirements/updates payload is a complex tagged-union shape. Not part of the SDK-1301 capability list either.
  • iceberg.getConfig: bootstraps a generic external Iceberg client (e.g. pyiceberg); not needed since this SDK's namespace/table clients already know their own base URL and headers.

Test plan

  • swift build
  • swift test (full suite, 1100 tests)
  • Unit tests (Mocker) for all new methods, including both StorageError shapes.
  • Integration tests written and verified to build/run correctly, but gated behind a separate ICEBERG_INTEGRATION_TESTS flag (not the default INTEGRATION_TESTS): confirmed via supabase start that analytics/Iceberg routes 404 on the CLI's local dev stack today — the backend requires ICEBERG_ENABLED=true plus a separate Iceberg REST Catalog server and warehouse/shard config (per the backend's own .env.test.sample) that isn't wired into the CLI. This avoids breaking CI's integration-tests job, which runs INTEGRATION_TESTS=1 against the same CLI stack.
  • ./scripts/format.sh
  • ./scripts/spell-check.sh
  • ./scripts/test-docs.sh

grdsdev added 7 commits August 6, 2026 17:38
Implements the storage.vectors client by hand, using the same
StorageApi/StorageHTTPSession HTTP stack as the rest of the Storage
module, instead of a generated OpenAPI client.
Verified against a local Supabase stack (supabase start): create,
get, list (incl. prefix filtering), and delete, plus the 404 error
shape for a missing bucket.
Adds storage.vectors.from(_:) for bucket-scoped index management
(createIndex/getIndex/listIndexes/deleteIndex) and .index(_:) for
vector data operations (putVectors/getVectors/listVectors/
queryVectors/deleteVectors), implemented by hand on the existing
StorageApi HTTP stack, matching supabase-js's storage-js vectors
client and the supabase/storage backend's wire format.
The wire value is a raw UNIX timestamp (seconds), not an ISO8601
string, so Int obscured the semantics. Matches Session.expiresAt's
existing TimeInterval convention for UNIX timestamps in this codebase.
Same fix as the base branch's VectorBucket.creationTime: the wire
value is a raw UNIX timestamp (seconds), not an ISO8601 string.
…ement (alpha)

Adds storage.analytics.{createBucket,listBuckets,deleteBucket,from} and,
via AnalyticsBucketClient/IcebergNamespaceClient, Iceberg namespace CRUD
(createNamespace/getNamespace/listNamespaces/namespaceExists/deleteNamespace)
and table CRUD (createTable/getTable/listTables/tableExists/deleteTable).

Implemented by hand on the existing StorageApi HTTP stack, matching
supabase-js's StorageAnalyticsClient for the public API shape and the
supabase/storage backend's rest-catalog-client.ts types for the exact
wire format. Also fixes StorageError to parse the nested
{error: {message, type, code}} shape used by the analytics/Iceberg
error formatter, in addition to the flat shape used elsewhere.

Table commit/schema-evolution (updateTable) is out of scope for this
pass, matching the backend's own additionalProperties:true (opaque)
response typing for that endpoint.
@grdsdev
grdsdev requested a review from a team as a code owner August 6, 2026 22:02
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a0ee1aa4-7d2f-4598-a7ab-ff463274d7ce

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@grdsdev
grdsdev marked this pull request as draft August 6, 2026 22:07
grdsdev added 6 commits August 6, 2026 19:14
Composition instead of subclassing StorageApi: the client now holds a
StorageApi dependency passed in at init, rather than inheriting execute()
and configuration directly. SupabaseStorageClient.vectors now passes
itself as that dependency, so custom headers set via setHeader() are
shared between the main client and .vectors.
…ndexes

# Conflicts:
#	Sources/Storage/StorageVectorsClient.swift
…over StorageApi

Same composition-over-inheritance change as StorageVectorsClient:
these now hold a StorageApi dependency passed in at init instead of
subclassing it, threaded through from(_:)/index(_:).
…eApi

Same composition-over-inheritance change as the vector clients:
AnalyticsClient, AnalyticsBucketClient, and IcebergNamespaceClient now
hold a StorageApi dependency passed in at init instead of subclassing
it, threaded through from(_:)/namespace(_:).
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Capability matrix drift detected

The following capabilities are marked implemented in swift but have no registered symbols to verify:

  • auth.passkey.register_passkey (no symbols list — cannot confirm implementation exists)
  • auth.passkey.sign_in_with_passkey (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.third_party_auth (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.cross_client_token_sync (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.oauth_flow_type (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.session_url_detection (no symbols list — cannot confirm implementation exists)
  • client.session_management.custom_storage (no symbols list — cannot confirm implementation exists)
  • client.session_management.persist_session (no symbols list — cannot confirm implementation exists)
  • client.request_configuration.global_headers (no symbols list — cannot confirm implementation exists)
  • client.observability.trace_propagation (no symbols list — cannot confirm implementation exists)
  • database.query.from_table (no symbols list — cannot confirm implementation exists)
  • database.query.select (no symbols list — cannot confirm implementation exists)
  • database.query.schema_selection (no symbols list — cannot confirm implementation exists)
  • database.query.rpc (no symbols list — cannot confirm implementation exists)
  • database.mutate.insert (no symbols list — cannot confirm implementation exists)
  • database.mutate.update (no symbols list — cannot confirm implementation exists)
  • database.mutate.upsert (no symbols list — cannot confirm implementation exists)
  • database.mutate.delete (no symbols list — cannot confirm implementation exists)
  • database.mutate.select_after_mutation (no symbols list — cannot confirm implementation exists)
  • database.using_filters.eq (no symbols list — cannot confirm implementation exists)
  • database.using_filters.neq (no symbols list — cannot confirm implementation exists)
  • database.using_filters.gt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.gte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.lt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.lte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.like (no symbols list — cannot confirm implementation exists)
  • database.using_filters.ilike (no symbols list — cannot confirm implementation exists)
  • database.using_filters.is (no symbols list — cannot confirm implementation exists)
  • database.using_filters.in (no symbols list — cannot confirm implementation exists)
  • database.using_filters.contains (no symbols list — cannot confirm implementation exists)
  • database.using_filters.contained_by (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_gt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_gte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_lt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_lte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_adjacent (no symbols list — cannot confirm implementation exists)
  • database.using_filters.overlaps (no symbols list — cannot confirm implementation exists)
  • database.using_filters.text_search (no symbols list — cannot confirm implementation exists)
  • database.using_filters.match (no symbols list — cannot confirm implementation exists)
  • database.using_filters.not (no symbols list — cannot confirm implementation exists)
  • database.using_filters.or (no symbols list — cannot confirm implementation exists)
  • database.using_filters.raw (no symbols list — cannot confirm implementation exists)
  • database.using_filters.regex (no symbols list — cannot confirm implementation exists)
  • database.using_filters.regex_icase (no symbols list — cannot confirm implementation exists)
  • database.using_filters.is_distinct (no symbols list — cannot confirm implementation exists)
  • database.using_filters.like_all (no symbols list — cannot confirm implementation exists)
  • database.using_filters.like_any (no symbols list — cannot confirm implementation exists)
  • database.using_filters.ilike_all (no symbols list — cannot confirm implementation exists)
  • database.using_filters.ilike_any (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.order (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.limit (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.range (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.single_row (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.strip_nulls (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.format_csv (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.format_geojson (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.max_affected_rows (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.request_cancellation (no symbols list — cannot confirm implementation exists)
  • database.configuration.auto_retry (no symbols list — cannot confirm implementation exists)
  • functions.invocation.invoke (no symbols list — cannot confirm implementation exists)
  • functions.invocation.set_auth_token (no symbols list — cannot confirm implementation exists)
  • functions.invocation.method_override (no symbols list — cannot confirm implementation exists)
  • functions.invocation.streaming_response (no symbols list — cannot confirm implementation exists)
  • functions.invocation.request_cancellation (no symbols list — cannot confirm implementation exists)
  • realtime.client.connect (no symbols list — cannot confirm implementation exists)
  • realtime.client.disconnect (no symbols list — cannot confirm implementation exists)
  • realtime.client.get_channels (no symbols list — cannot confirm implementation exists)
  • realtime.client.remove_channel (no symbols list — cannot confirm implementation exists)
  • realtime.client.remove_all_channels (no symbols list — cannot confirm implementation exists)
  • realtime.client.connection_state (no symbols list — cannot confirm implementation exists)
  • realtime.client.listen_heartbeats (no symbols list — cannot confirm implementation exists)
  • realtime.client.set_auth_token (no symbols list — cannot confirm implementation exists)
  • realtime.client.channel (no symbols list — cannot confirm implementation exists)
  • realtime.channel.subscribe (no symbols list — cannot confirm implementation exists)
  • realtime.channel.unsubscribe (no symbols list — cannot confirm implementation exists)
  • realtime.channel.send (no symbols list — cannot confirm implementation exists)
  • realtime.channel.broadcast_http (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.postgres_changes (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.subscribe_presence (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.private_channel (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.broadcast_self (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.broadcast_ack (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.broadcast_replay (no symbols list — cannot confirm implementation exists)
  • realtime.presence.track (no symbols list — cannot confirm implementation exists)
  • realtime.presence.untrack (no symbols list — cannot confirm implementation exists)
  • realtime.presence.presence_key (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.custom_websocket_transport (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.reconnect_backoff (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.heartbeat_interval (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.access_token_callback (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.deferred_disconnect (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.custom_logger (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.binary_protocol (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.get_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.list_file_buckets (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.update_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.delete_file_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.empty_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.access_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.upload (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.download (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.move (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.copy (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.remove (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.create_signed_url (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.create_signed_urls (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.create_signed_upload_url (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.upload_with_signed_url (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.update_file (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.file_exists (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.file_info (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.list_files_paginated (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.copy_cross_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.move_cross_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.upload_with_metadata (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.url_cache_nonce (no symbols list — cannot confirm implementation exists)

These may have been renamed, removed, or never registered. Please update the capability matrix.
See: https://github.com/supabase/sdk/blob/main/docs/capability-matrix.md

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 31160582079

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Warning

No base build found for commit 3adc68f on feat-storage-vector-indexes.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 83.832%

Details

  • Patch coverage: 86 uncovered changes across 5 files (279 of 365 lines covered, 76.44%).

Uncovered Changes

File Changed Covered %
Sources/Storage/IcebergTypes.swift 140 60 42.86%
Sources/Storage/SupabaseStorage.swift 3 0 0.0%
Sources/Storage/AnalyticsBucketClient.swift 84 83 98.81%
Sources/Storage/AnalyticsClient.swift 43 42 97.67%
Sources/Storage/IcebergNamespaceClient.swift 82 81 98.78%
Total (6 files) 365 279 76.44%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 10688
Covered Lines: 8960
Line Coverage: 83.83%
Coverage Strength: 38.34 hits per line

💛 - Coveralls

Base automatically changed from feat-storage-vector-indexes to main August 7, 2026 13:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants