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
- •Go to supabase.com
- •Create a new project
- •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
- •Go to Storage in your Supabase dashboard
- •Create a new bucket named
world-maps - •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
- •Space/anchor is saved locally
- •If
.onSave, sync begins immediately - •For spaces: world map data is uploaded to Storage bucket
- •Metadata is upserted to database
- •On failure: operation is queued
Download Flow
- •On
sync()or when loading missing data - •Fetch remote changes since last sync
- •Download any missing world maps from Storage
- •Merge with local data (last-write-wins)
- •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:
- •Upload compressed world map to
world-maps/{space-id} - •Get signed URL
- •Store URL in
spaces.world_map_url - •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
- •Compression - Reduce upload sizes
- •Error Handling - Handle sync errors