HyperbasisHyperbasisDocs
Home

Cloud Sync

Enable multi-device sync with Supabase.

Overview

Hyperbasis uses Supabase for cloud storage, providing:

  • Cross-device synchronization
  • Cloud backup of AR spaces
  • Real-time collaboration (future)

Supabase Setup

1. Create a Supabase Project

  1. Go to supabase.com
  2. Create a new project
  3. Note your project URL and anon key

2. Create Database Tables

Run this SQL in the Supabase SQL Editor:

-- Spaces table
CREATE TABLE spaces (
    id UUID PRIMARY KEY,
    name TEXT,
    world_map_url TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Anchors table
CREATE TABLE anchors (
    id UUID PRIMARY KEY,
    space_id UUID REFERENCES spaces(id) ON DELETE CASCADE,
    transform FLOAT8[] NOT NULL,
    metadata JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMPTZ
);

-- Indexes
CREATE INDEX idx_anchors_space_id ON anchors(space_id);
CREATE INDEX idx_anchors_updated_at ON anchors(updated_at);
CREATE INDEX idx_spaces_updated_at ON spaces(updated_at);

3. Create Storage Bucket

  1. Go to Storage in your Supabase dashboard
  2. Create a new bucket named world-maps
  3. Set the bucket to public or configure RLS policies

4. Configure Row Level Security (Optional)

For authenticated access, add RLS policies:

-- Enable RLS
ALTER TABLE spaces ENABLE ROW LEVEL SECURITY;
ALTER TABLE anchors ENABLE ROW LEVEL SECURITY;

-- Allow authenticated users to manage their own data
CREATE POLICY "Users can manage own spaces"
ON spaces FOR ALL
USING (auth.uid() = user_id);

CREATE POLICY "Users can manage own anchors"
ON anchors FOR ALL
USING (space_id IN (
    SELECT id FROM spaces WHERE user_id = auth.uid()
));

SDK Configuration

Basic Setup

import Hyperbasis

let storage = HBStorage(config: HBStorageConfig(
    backend: .supabase(
        url: "https://your-project.supabase.co",
        anonKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    ),
    syncStrategy: .onSave,
    compression: .balanced
))

Environment-based Configuration

struct Environment {
    static var supabaseUrl: String {
        ProcessInfo.processInfo.environment["SUPABASE_URL"] ?? ""
    }

    static var supabaseAnonKey: String {
        ProcessInfo.processInfo.environment["SUPABASE_ANON_KEY"] ?? ""
    }
}

let storage = HBStorage(config: HBStorageConfig(
    backend: .supabase(
        url: Environment.supabaseUrl,
        anonKey: Environment.supabaseAnonKey
    ),
    syncStrategy: .onSave,
    compression: .balanced
))

Sync Strategies

Automatic Sync (.onSave)

Data syncs immediately after each save:

let config = HBStorageConfig(
    backend: .supabase(url: url, anonKey: key),
    syncStrategy: .onSave,
    compression: .balanced
)
  • Saves always succeed locally first
  • Cloud sync happens in background
  • Failed syncs are queued for retry

Manual Sync (.manual)

Sync only when explicitly requested:

let config = HBStorageConfig(
    backend: .supabase(url: url, anonKey: key),
    syncStrategy: .manual,
    compression: .balanced
)

// Trigger sync manually
try await storage.sync()

How Sync Works

Upload Flow

  1. Space/anchor is saved locally
  2. If .onSave, sync begins immediately
  3. For spaces: world map data is uploaded to Storage bucket
  4. Metadata is upserted to database
  5. On failure: operation is queued

Download Flow

  1. On sync() or when loading missing data
  2. Fetch remote changes since last sync
  3. Download any missing world maps from Storage
  4. Merge with local data (last-write-wins)
  5. Update local cache

Conflict Resolution

Hyperbasis uses last-write-wins based on updatedAt:

// Remote wins if newer
if remote.updatedAt > local.updatedAt {
    // Use remote version
}

World Map Storage

World maps are stored in Supabase Storage, not the database:

  1. Upload compressed world map to world-maps/{space-id}
  2. Get signed URL
  3. Store URL in spaces.world_map_url
  4. On download, fetch from signed URL
Note

World maps are typically 5-50 MB. Storing them in blob storage is more efficient than database BLOBs.

Offline Support

Hyperbasis is offline-first:

  • All operations work offline
  • Data is stored locally first
  • Sync happens when connectivity is available
  • Pending operations are persisted and retried

Checking Pending Operations

let pending = storage.pendingOperationCount
if pending > 0 {
    print("\(pending) operations waiting to sync")
}

Manual Retry

// Force retry of pending operations
try await storage.sync()

Error Handling

do {
    try await storage.sync()
} catch HBStorageError.cloudNotConfigured {
    print("Cloud sync not configured")
} catch HBStorageError.networkError(let error) {
    print("Network error: \(error)")
} catch HBStorageError.unauthorized {
    print("Authentication required")
} catch HBStorageError.serverError(let code, let message) {
    print("Server error \(code): \(message ?? "Unknown")")
}

Next Steps