Cross-Library Integration
How Singularity integrates with Space-Map and Editor
Cross-Library Integration
The Singularity library integrates seamlessly with other parts of the GoGrab Space platform through dedicated integration services. This document explains how to leverage these integrations.
Architecture Overview
┌──────────────────────────────────────────┐
│ space-map-lib │
│ (Universe Management) │
│ • Create/manage universes │
│ • Organize galaxies and worlds │
└──────────────▲───────────────────────────┘
│
│ SpaceMapIntegrationService
│ (Logic statistics & navigation)
│
┌──────────────────────────────────────────┐
│ singularity-lib │
│ (Logic Building & Execution) │
│ • Build logic flows │
│ • Compose flows │
│ • Execute logic │
└──────────────▼───────────────────────────┘
│
│ EditorIntegrationService
│ (Widget event bindings)
│
┌──────────────────────────────────────────┐
│ editor-web-lib │
│ (Page & Widget Management) │
│ • Create pages │
│ • Design widgets │
└──────────────────────────────────────────┘
Space-Map Integration
Purpose
The SpaceMapIntegrationService allows space-map components to:
- Display logic statistics for universes
- Navigate to logic building features
- Check if universes have any logic defined
Getting Logic Statistics
import { SpaceMapIntegrationService } from '@libs-structural/singularity-lib';
export class UniverseDetailComponent {
private spaceMapIntegration = inject(SpaceMapIntegrationService);
async loadUniverseLogicStats(universeId: string) {
const stats = await this.spaceMapIntegration.getUniverseLogicStats(universeId);
console.log(`Universe has ${stats.flowCount} flows`);
console.log(`Universe has ${stats.compositionCount} compositions`);
console.log(`Universe has ${stats.blockCount} total blocks`);
}
}
Observable Statistics
For reactive UI updates:
// Subscribe to stats changes
this.spaceMapIntegration
.getUniverseLogicStats$(universeId)
.subscribe(stats => {
if (stats) {
this.logicStats.set(stats);
}
});
Navigation
// Navigate from universe detail to logic builder
navigateToLogicBuilder(universeId: string) {
const url = this.spaceMapIntegration.getLogicBuilderUrl(universeId);
this.router.navigateByUrl(url);
// Navigates to: /singularity/logic-builder?universeId=xxx
}
// Navigate to flow composer
navigateToFlowComposer(universeId: string) {
const url = this.spaceMapIntegration.getFlowComposerUrl(universeId);
this.router.navigateByUrl(url);
// Navigates to: /singularity/flow-composer?universeId=xxx
}
Checking for Logic
// Check if universe has any logic before showing options
async checkLogic(universeId: string) {
const hasLogic = await this.spaceMapIntegration.hasLogic(universeId);
if (hasLogic) {
this.showLogicBuilderOption.set(true);
}
}
Cache Management
// Refresh statistics after creating/deleting flows
async onFlowCreated(universeId: string) {
await this.spaceMapIntegration.refreshUniverseStats(universeId);
// Stats are recalculated
}
// Clear all cached statistics
this.spaceMapIntegration.clearCache();
Complete Example: Universe Card
import { Component, input, signal, inject, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { SpaceMapIntegrationService, UniverseLogicMetadata } from '@libs-structural/singularity-lib';
@Component({
selector: 'app-universe-card',
template: `
<div class="universe-card">
<h3>{{ universe().name }}</h3>
@if (logicStats()) {
<div class="stats">
<div class="stat">
<span class="label">Flows:</span>
<span class="value">{{ logicStats()!.flowCount }}</span>
</div>
<div class="stat">
<span class="label">Compositions:</span>
<span class="value">{{ logicStats()!.compositionCount }}</span>
</div>
<div class="stat">
<span class="label">Blocks:</span>
<span class="value">{{ logicStats()!.blockCount }}</span>
</div>
</div>
}
<div class="actions">
<button (click)="openLogicBuilder()">
Open Logic Builder
</button>
<button (click)="openFlowComposer()">
Open Flow Composer
</button>
</div>
</div>
`
})
export class UniverseCardComponent implements OnInit {
readonly universe = input.required<any>();
readonly logicStats = signal<UniverseLogicMetadata | null>(null);
private spaceMapIntegration = inject(SpaceMapIntegrationService);
private router = inject(Router);
async ngOnInit() {
const stats = await this.spaceMapIntegration.getUniverseLogicStats(
this.universe().id
);
this.logicStats.set(stats);
}
openLogicBuilder() {
const url = this.spaceMapIntegration.getLogicBuilderUrl(this.universe().id);
this.router.navigateByUrl(url);
}
openFlowComposer() {
const url = this.spaceMapIntegration.getFlowComposerUrl(this.universe().id);
this.router.navigateByUrl(url);
}
}
Editor Integration
Purpose
The EditorIntegrationService allows editor components to:
- Execute flows when widget events fire
- Get available flows for binding
- Create widget-to-flow bindings
- Monitor flow execution
Executing Flows from Widgets
import { EditorIntegrationService } from '@libs-structural/singularity-lib';
export class WidgetComponent {
private editorIntegration = inject(EditorIntegrationService);
async onButtonClick(event: MouseEvent) {
// Execute bound flow
const result = await this.editorIntegration.executeFlowFromWidget(
'flow-id-123',
{ clickX: event.clientX, clickY: event.clientY },
{
widgetId: this.widgetId,
pageId: this.pageId,
universeId: this.universeId
}
);
if (result.success) {
console.log('Flow executed successfully:', result.data);
} else {
console.error('Flow execution failed:', result.error);
}
}
}
Getting Available Flows
For a flow selection dropdown:
export class PropertyPanelComponent {
private editorIntegration = inject(EditorIntegrationService);
readonly availableFlows = signal<FlowBinding[]>([]);
async ngOnInit() {
// Get flows for current universe
const flows = await this.editorIntegration.getAvailableFlows(
this.universeId
);
this.availableFlows.set(flows);
}
}
Creating Widget Bindings
// Create a binding configuration
const binding = this.editorIntegration.createWidgetBinding({
widgetId: 'submit-button',
eventName: 'onClick',
flowId: 'validation-flow',
flowType: 'flow',
dataMapping: {
'formData': 'input',
'userId': 'context.userId'
},
enabled: true
});
// Store binding in widget configuration
widget.bindings.onClick = binding;
Monitoring Execution Events
export class ExecutionMonitorComponent {
private editorIntegration = inject(EditorIntegrationService);
ngOnInit() {
this.editorIntegration.executionEvents$.subscribe(event => {
switch (event.type) {
case 'start':
console.log(`Flow ${event.flowId} started`);
this.showLoadingIndicator();
break;
case 'complete':
console.log(`Flow completed in ${event.executionTime}ms`);
this.hideLoadingIndicator();
break;
case 'error':
console.error(`Flow error: ${event.error}`);
this.showErrorMessage(event.error);
break;
}
});
}
}
Complete Example: Interactive Button
import { Component, input, signal, inject } from '@angular/core';
import { EditorIntegrationService, FlowBinding } from '@libs-structural/singularity-lib';
@Component({
selector: 'app-interactive-button',
template: `
<div class="interactive-button">
<button
[disabled]="isExecuting()"
(click)="handleClick($event)"
>
@if (isExecuting()) {
<span class="spinner"></span>
}
{{ label() }}
</button>
@if (lastResult()) {
<div class="result" [class.error]="!lastResult()!.success">
{{ lastResult()!.success ? 'Success!' : 'Error: ' + lastResult()!.error }}
</div>
}
</div>
`
})
export class InteractiveButtonComponent {
readonly label = input<string>('Click Me');
readonly flowBinding = input<FlowBinding | null>(null);
readonly isExecuting = signal(false);
readonly lastResult = signal<any>(null);
private editorIntegration = inject(EditorIntegrationService);
async handleClick(event: MouseEvent) {
const binding = this.flowBinding();
if (!binding) return;
this.isExecuting.set(true);
this.lastResult.set(null);
try {
const result = await this.editorIntegration.executeFlowFromWidget(
binding.flowId,
{ clickX: event.clientX, clickY: event.clientY },
{
widgetId: binding.widgetId,
// Additional context...
}
);
this.lastResult.set(result);
} finally {
this.isExecuting.set(false);
}
}
}
Data Flow Examples
Example 1: User Creates Flow
User creates flow in Logic Builder
↓
Flow is saved with universeId
↓
SpaceMapIntegrationService.refreshUniverseStats(universeId)
↓
Space-map UI shows updated flow count
↓
User can now bind widgets to this flow
Example 2: Widget Triggers Flow
User clicks widget in Editor
↓
Editor checks widget.bindings.onClick
↓
EditorIntegrationService.executeFlowFromWidget()
↓
Flow executes in Singularity
↓
Result returned to Editor
↓
Widget updates based on result
Example 3: Universe Statistics Display
User opens universe detail in Space-Map
↓
SpaceMapIntegrationService.getUniverseLogicStats(universeId)
↓
Service queries Singularity database
↓
Returns { flowCount, compositionCount, blockCount }
↓
Space-map displays: "5 flows, 2 compositions"
Type Definitions
UniverseLogicMetadata
interface UniverseLogicMetadata {
universeId: string;
flowCount: number;
compositionCount: number;
blockCount: number;
flowCategories: string[];
sharedLogicBlocks: string[];
sharedFlows: string[];
lastAccessed: Date;
modified: Date;
}
FlowBinding
interface FlowBinding {
id: string;
name: string;
description: string;
type: 'flow' | 'composition';
inputs: FlowInput[];
outputs: FlowOutput[];
}
FlowExecutionResult
interface FlowExecutionResult {
success: boolean;
data?: any;
error?: string;
executionTime: number;
flowId: string;
}
WidgetBinding
interface WidgetBinding {
id: string;
widgetId: string;
eventName: string;
flowId: string;
flowType: 'flow' | 'composition';
dataMapping?: Record<string, string>;
enabled: boolean;
createdAt: Date;
}
Best Practices
For Space-Map Developers
- Always use integration service: Never access singularity services directly
- Cache appropriately: Use observable streams for reactive updates
- Handle errors: Statistics loading can fail, show fallback UI
- Refresh on changes: Call
refreshUniverseStatsafter flow changes
For Editor Developers
- Validate bindings: Check flow exists before executing
- Handle errors gracefully: Flow execution can fail
- Show loading states: Execution can take time
- Monitor events: Use execution events for real-time feedback
For All Developers
- Type safety: Import types from
@singularity-lib - Error handling: Always handle promise rejections
- Performance: Cache data when appropriate
- Documentation: Document cross-library interactions
Troubleshooting
Problem: Statistics not updating
Solution: Call refreshUniverseStats after flow changes
await this.flowService.createFlow(data);
await this.spaceMapIntegration.refreshUniverseStats(universeId);
Problem: Flow execution fails
Solution: Check flow exists and is valid
const flows = await this.editorIntegration.getAvailableFlows(universeId);
const flowExists = flows.some(f => f.id === flowId);
if (!flowExists) {
console.error('Flow not found');
}
Problem: Type errors
Solution: Import types correctly
import {
SpaceMapIntegrationService,
EditorIntegrationService,
UniverseLogicMetadata,
FlowBinding
} from '@libs-structural/singularity-lib';
See Also
- Flow Composer - Visual flow composition
- Logic Builder - Building individual flows
- Space-Map Library - Universe management
- Editor Library - Page and widget management
Remember: Integration services are the proper way to communicate between libraries. Never access services directly across library boundaries!