PAGES ARCHITECTURE
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
Pages Data Architecture 📄
Date: November 24, 2025
Purpose: Define the complete data model and storage strategy for pages across the platform
🎯 Goals
- Separation of Concerns: Pages are independent from Universes
- Cross-Reference: Universes reference published pages
- Draft System: Support drafts, published versions, and history
- Performance: Optimized storage and retrieval
- MongoDB Structure: Proper collections and relationships
📊 Data Model
1. Pages Collection (MongoDB)
Collection Name: pages
Purpose: Store all pages across all spaces
interface Page {
// Identity
_id: string; // MongoDB ObjectId
id: string; // User-friendly ID: `page-{spaceId}-{slug}-{timestamp}`
// Metadata
title: string; // "Home Page", "About Us", etc.
slug: string; // URL-friendly: "home", "about-us"
description: string; // SEO description
// Content
widgets: Widget[]; // Array of widget configurations
layout: PageLayout; // Layout configuration (zones, responsive)
// Ownership & Context
spaceId: string; // Which space this page belongs to
ownerId: string; // User who created it
// Status & Publishing
status: 'draft' | 'published' | 'archived';
version: number; // Current version number
publishedVersion?: number; // Last published version (if published)
// Timestamps
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
publishedAt?: string; // ISO 8601 (when last published)
// SEO & Meta
meta: {
title?: string; // Page title for SEO
description?: string; // Meta description
keywords?: string[]; // SEO keywords
ogImage?: string; // Open Graph image URL
robots?: string; // robots meta tag
};
// Permissions (who can view/edit)
permissions: {
visibility: 'public' | 'private' | 'restricted';
allowedUsers?: string[]; // User IDs who can access
allowedRoles?: string[]; // Roles who can access
};
// Thumbnail (for page selector)
thumbnail?: {
url: string; // Screenshot URL
generatedAt: string; // When thumbnail was generated
};
// Version History (optional - for advanced features)
history?: PageVersion[];
}
interface PageVersion {
version: number;
widgets: Widget[];
layout: PageLayout;
savedAt: string;
savedBy: string;
notes?: string;
}
interface Widget {
id: string;
type: string; // 'button', 'text', 'image', etc.
config: Record<string, unknown>; // Widget-specific configuration
position: {
zoneId: string; // Which layout zone
order: number; // Order within zone
};
styles: Record<string, unknown>; // CSS styles
events?: WidgetEvent[]; // Singularity flows
}
interface PageLayout {
type: 'flexbox' | 'grid' | 'custom';
zones: LayoutZone[];
responsive: ResponsiveConfig;
}
interface LayoutZone {
id: string;
name: string;
type: 'header' | 'content' | 'sidebar' | 'footer' | 'custom';
flexConfig?: FlexConfig;
gridConfig?: GridConfig;
}
2. Universes Collection (MongoDB)
Collection Name: universes
Purpose: Store universe structures (sections, modules, layouts)
interface Universe {
_id: string;
id: string; // `universe-{spaceId}-{timestamp}`
// Metadata
name: string;
slug: string;
description: string;
// Structure
rootSection: CanvasSection; // Nested tree structure
// Context
spaceId: string;
ownerId: string;
// Publishing
status: 'draft' | 'published' | 'archived';
version: number;
// Timestamps
createdAt: string;
updatedAt: string;
publishedAt?: string;
}
interface CanvasSection {
id: string;
label: string;
layout: 'flexbox' | 'template';
// Content: Reference to Page (NOT embedded!)
content?: {
type: 'module' | 'widget';
moduleId: string; // e.g., 'page-renderer', 'footer'
instanceId: string;
configuration: {
pageId?: string; // 🔗 REFERENCE to Pages collection
// ... other module config
};
};
// Children sections
children: CanvasSection[];
// Template regions (if layout === 'template')
regions?: TemplateRegion[];
}
3. Storage Keys (IndexedDB / GgStorage)
Local Storage (Client-side caching with GgStorage):
Key Pattern Purpose Data Type
───────────────────────────────────────────────────────────────────────────────
pages All pages for current space Page[]
pages:drafts User's draft pages Page[]
pages:{spaceId} Pages filtered by space Page[]
editor/current-page Currently editing page Page
editor/auto-save Auto-saved draft Page
universes:{spaceId} Universes for space Universe[]
universe:{universeId} Specific universe Universe
🔄 Data Flow
Creating a Page
1. User opens Editor (/space/{spaceId}/editor)
2. Editor creates draft: { id: 'page-draft-{timestamp}', status: 'draft' }
3. User adds widgets → auto-saved to GgStorage: 'editor/auto-save'
4. User clicks "Save Draft" → saved to MongoDB: pages collection
5. User clicks "Publish" → status: 'published', publishedAt: now()
Using a Page in Universe
1. User drops "Page Renderer" module into canvas
2. User clicks module → Config panel opens
3. User clicks "Select Page" → Page Selector modal
4. Page Selector loads from:
a. Local cache (GgStorage: 'pages')
b. If empty, fetch from MongoDB
5. User selects page → pageId set in section.content.configuration
6. Universe saved with reference:
{
content: {
moduleId: 'page-renderer',
configuration: { pageId: 'page-home-123' } // 🔗 Reference
}
}
7. When rendering, Page Renderer loads page by ID from MongoDB
🗄️ MongoDB Collections Structure
Database: gograbspace
│
├── spaces/ # Spaces (tenants)
│ └── { _id, name, slug, ownerId, createdAt, ... }
│
├── pages/ # ✨ NEW: Pages collection
│ ├── { _id, id, spaceId, title, widgets, status: 'draft', ... }
│ ├── { _id, id, spaceId, title, widgets, status: 'published', ... }
│ └── ... (indexed by spaceId, status, slug)
│
├── universes/ # Universes (canvas structures)
│ └── { _id, id, spaceId, rootSection, ... }
│
└── users/ # Users
└── { _id, email, name, ... }
Indexes:
// pages collection
db.pages.createIndex({ spaceId: 1, status: 1 });
db.pages.createIndex({ spaceId: 1, slug: 1 });
db.pages.createIndex({ id: 1 }, { unique: true });
db.pages.createIndex({ ownerId: 1, createdAt: -1 });
// universes collection
db.universes.createIndex({ spaceId: 1 });
db.universes.createIndex({ id: 1 }, { unique: true });
🎯 Implementation Checklist
Phase 1: Foundation ✅
-
PagesServicewith GgStorage -
Pageinterface (Worldtype) - Local storage with flash-persistence
- Basic CRUD operations
Phase 2: Editor Integration 🚧
- Editor save to
PagesService - Draft system with auto-save
- Publish workflow
- Page routing (
/editor/:pageId)
Phase 3: MongoDB Backend 🚧
- Create
pagescollection - API endpoints: GET /pages, POST /pages, PUT /pages/:id
- Sync
PagesServicewith backend - Real-time updates (WebSocket)
Phase 4: Universe Integration ✅
- Page Renderer module
- Page Selector component
- Config panel integration
- Widget rendering in Page Renderer
Phase 5: Advanced Features ⏳
- Page thumbnails/screenshots
- Version history
- Page templates
- Duplicate page
- Import/Export pages
🔧 Migration Strategy
From Current State → To New Architecture
Step 1: Unify Storage (Immediate)
// OLD: Two separate storage approaches
EditorModule: await this.storage.put(`editor/pages/${pageId}`, page);
PagesService: await this.storage.put('pages', pagesArray);
// NEW: Single source of truth
PagesService.createPage() → saves to 'pages' array AND syncs to backend
PagesService.updatePage() → updates 'pages' array AND syncs to backend
Step 2: Add Backend Sync (This Week)
// PagesService enhancement
async createPage(page: Partial<Page>): Promise<Page> {
const newPage = { ...page, id: generateId(), status: 'draft', ... };
// 1. Save locally (immediate)
const pages = [...this._pages(), newPage];
await this.storage.put('pages', pages);
this._pages.set(pages);
// 2. Sync to backend (background)
this.syncToBackend(newPage).subscribe();
return newPage;
}
private syncToBackend(page: Page): Observable<void> {
return this.http.post<void>(`/api/spaces/${page.spaceId}/pages`, page).pipe(
tap(() => this.logger.info('Page synced to backend', { pageId: page.id })),
catchError(error => {
this.logger.error('Failed to sync page', { error });
// Queue for retry
return EMPTY;
})
);
}
Step 3: Editor Integration (This Week)
// EditorUiComponent: Replace download-only save
onSave(): void {
const currentPage = this.activePage();
if (currentPage) {
// Update page with current widgets
const updatedPage = {
...currentPage,
widgets: this.widgets(),
updatedAt: new Date().toISOString(),
version: currentPage.version + 1
};
// Save via PagesService (unified)
this.pagesService.updatePage(currentPage.id, updatedPage)
.then(() => {
this.editorService.setStatusMessage('Page saved successfully');
})
.catch(error => {
this.editorService.setStatusMessage('Error saving page');
console.error(error);
});
}
}
📝 Example Queries
Get all published pages for a space
const pages = await pagesService.getPagesBySpace('space-123', { status: 'published' });
Get page by slug
const page = await pagesService.getPageBySlug('space-123', 'home');
Load page in Universe
// In Page Renderer component
const pageId = this.configuration().pageId;
if (pageId) {
const page = await this.pagesService.getPage(pageId);
this.renderPage(page);
}
🚀 Performance Considerations
Caching Strategy
- Local cache (GgStorage) for immediate access
- Background sync with MongoDB
- Cache TTL: 5 minutes for published pages, 30s for drafts
Lazy Loading
- Load page metadata (title, description) first
- Load full widgets on demand
Pagination
- Page Selector loads 20 pages at a time
- Infinite scroll for large libraries
Thumbnails
- Generate on publish (background job)
- Store in CDN (if available) or MongoDB GridFS
End of Document