Local Storage
How Hyperbasis stores data on the device using FileManager.
Storage Location
All data is stored in the app's Documents directory:
~/Documents/Hyperbasis/
├── spaces/
│ ├── {uuid}.json
│ └── ...
├── anchors/
│ ├── {uuid}.json
│ └── ...
├── events/
│ └── {spaceId}/
│ └── events.jsonl
├── pending_operations.json
└── sync_metadata.jsonFile Formats
Space Files
Each space is stored as a JSON file containing:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Living Room",
"worldMapData": "base64-encoded-compressed-data...",
"isCompressed": true,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:00Z"
}The worldMapData field contains the base64-encoded ARWorldMap, optionally compressed with zlib.
Anchor Files
Each anchor is stored as a JSON file:
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"spaceId": "550e8400-e29b-41d4-a716-446655440000",
"transform": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.5, 1.2, -2.0, 1],
"metadata": {
"text": "Hello World",
"priority": 1
},
"createdAt": "2024-01-15T11:00:00Z",
"updatedAt": "2024-01-15T11:00:00Z",
"deletedAt": null
}Pending Operations
Failed cloud operations are queued for retry:
[
{
"id": "operation-uuid",
"type": "uploadSpace",
"entityId": "space-uuid",
"createdAt": "2024-01-15T11:00:00Z",
"retryCount": 2
}
]Sync Metadata
Tracks the last successful sync:
{
"lastSyncDate": "2024-01-15T10:00:00Z"
}Event Files
Events are stored in JSONL format (one JSON object per line):
{"id":"...","anchorId":"...","spaceId":"...","type":"created","timestamp":"2024-01-15T10:00:00Z","version":1,"transform":[1,0,0,0,...],"metadata":{"text":"Hello"}}
{"id":"...","anchorId":"...","spaceId":"...","type":"moved","timestamp":"2024-01-15T10:05:00Z","version":2,"transform":[1,0,0,0,...]}
{"id":"...","anchorId":"...","spaceId":"...","type":"updated","timestamp":"2024-01-15T10:10:00Z","version":3,"metadata":{"text":"Updated"}}This append-only format ensures:
- •Fast writes (no file rewriting)
- •Immutable history
- •Small file sizes (~200-500 bytes per event)
HBLocalStore API
HBLocalStore is the internal class that manages file operations. While you typically interact with HBStorage, understanding the local store helps with debugging.
Space Operations
// Save space
func saveSpace(_ space: HBStorageSpace) throws
// Load space by ID
func loadSpace(id: UUID) throws -> HBStorageSpace?
// Load all spaces
func loadAllSpaces() throws -> [HBStorageSpace]
// Delete space
func deleteSpace(id: UUID) throws
// Load spaces modified since date (for sync)
func loadSpacesModifiedSince(_ date: Date) throws -> [HBStorageSpace]Anchor Operations
// Save anchor
func saveAnchor(_ anchor: HBAnchor) throws
// Load anchor by ID
func loadAnchor(id: UUID) throws -> HBAnchor?
// Load anchors for space
func loadAnchors(spaceId: UUID) throws -> [HBAnchor]
// Purge old deleted anchors
func purgeDeletedAnchors(before: Date) throws
// Load anchors modified since date (for sync)
func loadAnchorsModifiedSince(_ date: Date) throws -> [HBAnchor]Event Operations
// Append an event to the log
func appendEvent(_ event: HBAnchorEvent) throws
// Load all events for a space
func loadEvents(spaceId: UUID) throws -> [HBAnchorEvent]
// Load events for a specific anchor
func loadEvents(anchorId: UUID, spaceId: UUID) throws -> [HBAnchorEvent]
// Get current version for an anchor
func currentVersion(anchorId: UUID, spaceId: UUID) throws -> Int
// Delete all events for a space
func deleteEvents(spaceId: UUID) throwsUtilities
// Clear all data
func clearAll() throws
// Get total storage size
func totalSize() throws -> IntFile System Considerations
Thread Safety
All file operations are performed synchronously. HBStorage wraps these in async contexts to avoid blocking the main thread.
Atomic Writes
Files are written atomically using Data.write(to:options:) with .atomic option to prevent corruption.
Error Handling
public enum HBStorageError: LocalizedError {
case fileSystemError(underlying: Error)
case encodingFailed(underlying: Error)
case decodingFailed(underlying: Error)
case notFound(type: String, id: UUID)
}Storage Size
ARWorldMap data is the primary storage consumer:
| Environment | Typical World Map Size |
|---|---|
| Small room | 5-10 MB |
| Large room | 10-25 MB |
| Multi-room | 25-50 MB |
With compression enabled, sizes are reduced by ~40%.
Monitoring Storage
let sizeInBytes = try storage.localStorageSize()
let sizeInMB = Double(sizeInBytes) / 1_000_000
print("Storage: \(String(format: "%.1f", sizeInMB)) MB")Debugging
Inspecting Files
Files are stored in the app's Documents directory. You can access them via:
- •Xcode: Window > Devices and Simulators > [Device] > Download Container
- •Simulator:
~/Library/Developer/CoreSimulator/Devices/.../Documents/Hyperbasis/
Common Issues
Large storage size: Old world maps accumulate. Consider implementing cleanup:
// Delete spaces older than 90 days
let cutoff = Calendar.current.date(byAdding: .day, value: -90, to: Date())!
let spaces = try await storage.loadAllSpaces()
for space in spaces where space.updatedAt < cutoff {
try await storage.deleteSpace(id: space.id)
}Corrupted files: If a file fails to decode, it's skipped. Check logs for decoding errors.
Next Steps
- •Cloud Sync - Enable cloud backup
- •Compression - How compression works