Storage System - Proper MongoDB Integration Plan
Comprehensive plan to use storage-system's built-in MongoDB sync instead of custom SyncEngine
🔄 Storage System - Proper MongoDB Integration Plan
🎯 The Problem
We built a custom SyncEngineService that:
- ❌ Duplicates functionality already in
@storage-system - ❌ Requires manual sync calls (
syncEngine.write()) - ❌ Requires manual layout extraction and duplication
- ❌ Complex queue management
- ❌ Manual retry logic
- ❌ Doesn't leverage storage-system's intelligence
Example of Current Complexity:
// ❌ Current approach - TOO COMPLICATED
await this.syncEngine.write(universeKey, updatedUniverse, 'universe');
// Then manually extract layout for grid-canvas polling
if (mergedUniverse.layout) {
const layoutKey = `modular-universe/layout/${universeId}/universe`;
await this.storage.put(layoutKey, mergedUniverse.layout);
}
✨ The Solution
Use @storage-system's built-in MongoDB sync with autoFlashState or provideMongoDBSync
The storage-system already has:
- ✅ Intelligent Sync Engine with priority-based queuing
- ✅ Auto-sync on data changes (no manual calls needed!)
- ✅ Collection-based strategies (offline-first, online-first, realtime)
- ✅ Conflict resolution (last-write-wins, merge, manual)
- ✅ Real-time via Change Streams (optional)
- ✅ Smart batching and exponential backoff
- ✅ Performance monitoring and analytics
Example of Simplified Approach:
// ✅ New approach - SIMPLE & AUTOMATIC
const universe = autoFlashState({
key: 'universe_1763427157418',
database: 'gograb-api',
collection: 'universes',
initialValue: { name: 'My Universe', layout: { ... } },
syncStrategy: 'offline-first', // Automatic sync!
priority: 'high' // Syncs in 10-30s
});
// That's it! Changes auto-sync to MongoDB!
universe.set({ ...universe(), layout: newLayout });
📊 Current Architecture (Over-Complicated)
graph TD
A[Component: grid-canvas] --> B[Manual Save Button]
B --> C[grid-canvas.saveLayoutConfiguration]
C --> D[storage.put - layout key]
C --> E[storage.put - universe key]
E --> F[SyncEngineService.write]
F --> G[Custom Queue Management]
G --> H[SpaceApiService]
H --> I[Backend API]
I --> J[MongoDB]
K[SyncEngineService.pullFromBackend] --> L[Manually extract layout]
L --> M[storage.put - AGAIN for layout key]
M --> N[grid-canvas polls for changes]
style A fill:#ff6b6b
style F fill:#ff6b6b
style G fill:#ff6b6b
style K fill:#ff6b6b
style L fill:#ff6b6b
style M fill:#ff6b6b
Problems:
- Duplicate Storage Keys:
universe_${id}ANDmodular-universe/layout/${id}/universe - Manual Sync Calls: Have to explicitly call
syncEngine.write() - Manual Layout Extraction: Backend pulls universe, then manually extracts layout
- Polling for Changes: grid-canvas polls storage every 3 seconds
- Custom Queue Logic: Reinventing what storage-system already does
🎯 Target Architecture (Simple & Automatic)
graph TD
A[Component: grid-canvas] --> B[autoFlashState universe]
B --> C[Change detection automatic]
C --> D[storage-system IntelligentSyncEngine]
D --> E[Auto-batch & priority queue]
E --> F[MongoDBSyncAdapter]
F --> G[MongoDB]
H[MongoDB Change Streams] --> I[Real-time updates]
I --> J[autoFlashState signal updates]
J --> K[UI auto-updates via Angular signals]
style B fill:#51cf66
style D fill:#51cf66
style F fill:#51cf66
style I fill:#51cf66
style J fill:#51cf66
Benefits:
- Single Storage Key: Only
universe_${id}needed - Auto-Sync: storage-system detects changes automatically
- No Manual Extraction: Real-time updates via Change Streams
- Signal-Based: No polling needed - Angular signals react
- Built-in Intelligence: Leverages storage-system's proven sync engine
📋 Implementation Plan
Phase 1: Configure MongoDB Sync (1 hour)
1.1 Add MongoDB Providers to apps/gograb-space/src/app/app.config.ts
import { provideMongoDBSync } from '@storage-system';
import { environment } from '../environments/environment';
export const appConfig: ApplicationConfig = {
providers: [
// ... existing providers ...
// ✨ NEW: MongoDB Sync Integration
provideMongoDBSync({
connectionString: environment.mongoDbConnectionString,
database: environment.mongoDbDatabase || 'gograb-api',
autoConnect: true,
enableRealtime: true, // Enable Change Streams for cross-browser sync
defaultStrategy: {
mode: 'offline-first', // Local first, sync in background
priority: 'normal', // 1-5 min sync for most data
conflictResolution: 'last-write-wins'
},
// Per-collection strategies
collectionStrategies: {
'universes': {
mode: 'offline-first',
priority: 'high', // 10-30s sync for universes
enableRealtime: true // Real-time updates via Change Streams
},
'spaces': {
mode: 'offline-first',
priority: 'high', // 10-30s sync
enableRealtime: true
},
'pages': {
mode: 'offline-first',
priority: 'normal', // 1-5 min sync
enableRealtime: false
},
'widgets': {
mode: 'offline-first',
priority: 'normal',
enableRealtime: false
}
}
})
]
};
Result: MongoDB sync configured globally, auto-connects on app start.
1.2 Add Environment Variables
// apps/gograb-space/src/environments/environment.ts
export const environment = {
// ... existing ...
// 🔒 Get from .env.local - NEVER hardcode credentials
mongoDbConnectionString: process.env['NX_MONGODB_CONNECTION_STRING'] || '',
mongoDbDatabase: 'gograb-api',
};
Phase 2: Convert Space to autoFlashState (2 hours)
2.1 Update SpaceService to Use autoFlashState
Current:
// ❌ Complex manual sync
private readonly _currentSpace = signal<Space | null>(null);
await this.syncEngine.write(spaceKey, space, 'space');
New:
// ✅ Simple auto-sync
import { autoFlashState } from '@storage-system';
private readonly _currentSpace = autoFlashState<Space | null>({
key: 'space_68fbe3ac4740d821e041f9df', // User's space
database: 'gograb-api',
collection: 'spaces',
initialValue: null,
syncStrategy: 'offline-first',
priority: 'high' // Sync in 10-30s
});
// Changes auto-sync!
this._currentSpace.set(newSpace);
Result: Space changes automatically sync to MongoDB, no manual syncEngine.write() needed.
2.2 Update Universe Loading to Use autoFlashState
Current:
// ❌ Manual sync for each universe
const universe = await this.syncEngine.read<UniverseWithDeployment>(`universe_${universeId}`);
await this.syncEngine.write(universeKey, updatedUniverse, 'universe');
New:
// ✅ Auto-sync for universes
const universeState = autoFlashState<UniverseWithDeployment>({
key: `universe_${universeId}`,
database: 'gograb-api',
collection: 'universes',
initialValue: defaultUniverse,
syncStrategy: 'offline-first',
priority: 'high',
enableRealtime: true // Enable Change Streams
});
// Changes auto-sync!
universeState.set(updatedUniverse);
Result: Universe changes automatically sync, Change Streams enable real-time cross-browser updates.
Phase 3: Simplify Grid Canvas (1 hour)
3.1 Remove Manual Layout Storage
Current:
// ❌ Duplicate storage keys
await this.storage.put(`modular-universe/layout/${universeId}/universe`, layout);
await this.storage.put(`universe_${universeId}`, universe);
New:
// ✅ Single storage key with nested layout
const universeState = inject(UniverseStateService).getUniverse(universeId);
// Just update the universe - layout is part of it
universeState.set({
...universeState(),
layout: newLayout,
updatedAt: new Date().toISOString()
});
// That's it! Auto-syncs to MongoDB!
Result: No duplicate keys, simpler code, auto-sync.
3.2 Remove Polling, Use Signal Effects
Current:
// ❌ Poll storage every 3 seconds
setInterval(async () => {
const key = `modular-universe/layout/${universeId}/universe`;
const config = await this.storage.get<LayoutConfiguration>(key);
if (lastLayoutHash !== currentHash) {
this.layoutConfig.set(config);
}
}, 3000);
New:
// ✅ Use Angular effect to react to signal changes
import { effect } from '@angular/core';
constructor() {
const universeState = inject(UniverseStateService).getUniverse(this.universeId);
// Automatically updates when universe changes (including from Change Streams!)
effect(() => {
const universe = universeState();
if (universe?.layout) {
this.layoutConfig.set(universe.layout);
}
});
}
Result: No polling, reactive updates via signals, works with Change Streams.
Phase 4: Remove Custom SyncEngine (1 hour)
4.1 Delete Redundant Code
Files to Remove:
libs/libs-foundational/core-angular/src/lib/services/sync-engine.service.ts(entire file)- All manual
syncEngine.write()calls - All manual
syncEngine.read()calls - Manual layout extraction logic
Files to Update:
SpaceService: UseautoFlashStateinstead of manual syncgrid-canvas.component.ts: Use signals instead of pollingmodular-universe-builder.component.ts: Use signals instead of manual save
4.2 Create UniverseStateService (Centralized)
// libs/libs-foundational/core-angular/src/lib/services/universe-state.service.ts
import { Injectable } from '@angular/core';
import { autoFlashState } from '@storage-system';
@Injectable({ providedIn: 'root' })
export class UniverseStateService {
private universeStates = new Map();
getUniverse(universeId: string) {
if (!this.universeStates.has(universeId)) {
const state = autoFlashState({
key: `universe_${universeId}`,
database: 'gograb-api',
collection: 'universes',
initialValue: null,
syncStrategy: 'offline-first',
priority: 'high',
enableRealtime: true // Real-time cross-browser sync!
});
this.universeStates.set(universeId, state);
}
return this.universeStates.get(universeId);
}
}
Result: Centralized universe state management with auto-sync.
Phase 5: Backend Simplification (30 min)
5.1 Simplify SpaceController
Current:
- Complex manual layout extraction
- Separate universe sync logic
- Manual createdAt/updatedAt handling
New:
- storage-system handles everything
- Backend just provides REST endpoints for initial load
- Change Streams handle real-time updates
Result: Backend becomes simpler, just a data store.
🎯 Migration Strategy
Step 1: Add MongoDB Sync (Non-Breaking)
- Add
provideMongoDBSynctoapp.config.ts - Test that connection works
- No functionality changes yet
Step 2: Convert One Component (Test)
- Convert
grid-canvasto useautoFlashState - Test auto-sync works
- Verify Change Streams work
Step 3: Convert All Components
- Convert
SpaceService - Convert universe loading
- Convert other state management
Step 4: Remove Custom SyncEngine
- Delete
SyncEngineService - Remove manual sync calls
- Test everything works
Step 5: Clean Up Backend
- Simplify controllers
- Remove manual layout logic
- Update documentation
📊 Before vs After Comparison
| Aspect | Before (Custom) | After (storage-system) |
|---|---|---|
| Lines of Code | ~2000 lines | ~200 lines (10x reduction!) |
| Storage Keys | 2 per universe | 1 per universe |
| Sync Calls | Manual everywhere | Automatic |
| Cross-Browser Sync | Polling (3s delay) | Real-time Change Streams |
| Conflict Resolution | Custom logic | Built-in strategies |
| Queue Management | Custom | Intelligent engine |
| Performance | Good | Excellent (optimized) |
| Maintainability | Complex | Simple |
| Reliability | Custom testing | Battle-tested library |
🚀 Benefits
For Developers
- ✅ 10x Less Code: Simpler, easier to understand
- ✅ Auto-Sync: No manual sync calls needed
- ✅ Reactive: Angular signals handle updates
- ✅ Type-Safe: Full TypeScript support
- ✅ Testable: Less custom logic to test
For Users
- ✅ Faster: Real-time updates via Change Streams
- ✅ Reliable: Battle-tested sync engine
- ✅ Seamless: Works across tabs/browsers automatically
- ✅ Offline: Full functionality without network
For Architecture
- ✅ DRY: Don't Repeat storage-system's work
- ✅ Proven: Leverages well-tested library
- ✅ Scalable: Handles large data automatically
- ✅ Flexible: Easy to add new synced entities
🎯 Success Criteria
Phase 1 Complete When:
- MongoDB connection established
- Change Streams working
- Collection strategies configured
Phase 2 Complete When:
- Space uses
autoFlashState - Changes auto-sync to MongoDB
- No manual sync calls
Phase 3 Complete When:
- Grid canvas uses signals
- No polling logic
- Layout updates reactive
Phase 4 Complete When:
-
SyncEngineServicedeleted - All components use
autoFlashState - Tests pass
Phase 5 Complete When:
- Backend simplified
- Documentation updated
- Cross-browser sync tested
📚 Documentation Updates Needed
Update SAVE-SYNC-ARCHITECTURE-COMPLETE.md
- Show new simple architecture
- Remove SyncEngine references
- Add autoFlashState examples
Create STORAGE-SYSTEM-MIGRATION-GUIDE.md
- Step-by-step migration from custom to storage-system
- Before/after code examples
- Troubleshooting guide
Update API Documentation
- Remove SyncEngine API docs
- Add UniverseStateService docs
- Add autoFlashState examples
⏱️ Estimated Timeline
- Phase 1: 1 hour (MongoDB configuration)
- Phase 2: 2 hours (Space/Universe conversion)
- Phase 3: 1 hour (Grid canvas simplification)
- Phase 4: 1 hour (Remove SyncEngine)
- Phase 5: 30 min (Backend cleanup)
- Documentation: 1 hour
Total: ~6.5 hours of focused work
🎓 Key Learnings
- Don't Reinvent the Wheel: storage-system already had everything we needed
- Read Documentation First: Could have saved days of custom development
- Trust the Library: storage-system is battle-tested and optimized
- Simplicity Wins: Less code = fewer bugs = easier maintenance
✅ Next Steps
- Review this plan with the team
- Start with Phase 1 (MongoDB configuration)
- Test thoroughly after each phase
- Update documentation as we go
- Celebrate when we're done! 🎉
Created: November 18, 2025 - 10:15 AM
Session: 146.1
Status: Ready to implement
Priority: High (architectural improvement)