/** * Integration Hub - Zero-Config Integration Manager * * The simplest way to enable external tool integrations. * One line of code, all integrations ready. * * @example * ```typescript * // Zero-config: Enable all integrations * const hub = await IntegrationHub.create(brain) * * // Get your endpoints * console.log(hub.endpoints) * // { * // odata: '/odata', → Excel, Power BI, Tableau * // sheets: '/sheets', → Google Sheets * // sse: '/events', → Real-time streaming * // webhooks: '/webhooks' → Push notifications * // } * ``` */ import { IntegrationBase } from './IntegrationBase.js' import { IntegrationLoader, IntegrationType, INTEGRATION_CATALOG } from './IntegrationLoader.js' import { IntegrationConfig, IntegrationHealthStatus } from './types.js' import { ODataIntegration } from '../odata/ODataIntegration.js' import { GoogleSheetsIntegration } from '../sheets/GoogleSheetsIntegration.js' import { SSEIntegration } from '../sse/SSEIntegration.js' import { WebhookIntegration } from '../webhooks/WebhookIntegration.js' /** * Integration Hub configuration */ export interface IntegrationHubConfig { /** Base path for all endpoints (default: '') */ basePath?: string /** Which integrations to enable (default: all) */ enable?: IntegrationType[] | 'all' /** Per-integration config overrides */ config?: { odata?: IntegrationConfig & { basePath?: string } sheets?: IntegrationConfig & { basePath?: string } sse?: IntegrationConfig & { basePath?: string } webhooks?: IntegrationConfig & { basePath?: string } } } /** * HTTP request for integration routing */ export interface IntegrationRequest { method: string path: string query?: Record headers?: Record body?: any } /** * HTTP response from integration */ export interface IntegrationResponse { status: number headers: Record body: any isSSE?: boolean } /** * Integration Hub - Zero-Configuration Integration Manager * * Provides instant access to: * - OData API (Excel Power Query, Power BI, Tableau) * - Google Sheets API (two-way sync) * - SSE streaming (real-time dashboards) * - Webhooks (push notifications) * * All integrations work in any environment with zero external dependencies. */ export class IntegrationHub { private integrations: Map = new Map() private config: Required /** * Endpoint paths keyed by integration type. Starts empty and gains one key * per integration enabled in initialize() — disabled integrations have no * entry, hence the sparse-record assertion on the initializer. */ private _endpoints: Record = {} as Record /** * Create and initialize the Integration Hub * * @example * ```typescript * // All integrations, default paths * const hub = await IntegrationHub.create(brain) * * // Custom base path * const hub = await IntegrationHub.create(brain, { basePath: '/api/v1' }) * * // Only specific integrations * const hub = await IntegrationHub.create(brain, { enable: ['odata', 'sheets'] }) * ``` */ static async create(brain: any, config?: IntegrationHubConfig): Promise { const hub = new IntegrationHub(config) await hub.initialize(brain) return hub } private constructor(config?: IntegrationHubConfig) { this.config = { basePath: config?.basePath ?? '', enable: config?.enable ?? 'all', config: config?.config ?? {} } } private async initialize(brain: any): Promise { const toEnable = this.config.enable === 'all' ? (['odata', 'sheets', 'sse', 'webhooks'] as IntegrationType[]) : this.config.enable // Create context for integration initialization const context = { brain, storage: brain.getStorage?.() || null, config: {}, log: (message: string, level?: string) => { // Silent logging - integrations handle their own logging } } for (const type of toEnable) { const integration = await this.createIntegration(type) if (integration) { // Initialize the integration with context (BaseAugmentation pattern) await integration.initialize(context) this.integrations.set(type, integration) this._endpoints[type] = this.getBasePath(type) } } } private async createIntegration(type: IntegrationType): Promise { const basePath = this.config.basePath const cfg = this.config.config?.[type] switch (type) { case 'odata': return new ODataIntegration({ ...cfg, basePath: cfg?.basePath ?? `${basePath}/odata` }) case 'sheets': return new GoogleSheetsIntegration({ ...cfg, basePath: cfg?.basePath ?? `${basePath}/sheets` }) case 'sse': return new SSEIntegration({ ...cfg, basePath: cfg?.basePath ?? `${basePath}/events` }) case 'webhooks': return new WebhookIntegration(cfg) default: return null } } private getBasePath(type: IntegrationType): string { const basePath = this.config.basePath const cfg = this.config.config?.[type] switch (type) { case 'odata': return cfg?.basePath ?? `${basePath}/odata` case 'sheets': return cfg?.basePath ?? `${basePath}/sheets` case 'sse': return cfg?.basePath ?? `${basePath}/events` case 'webhooks': return `${basePath}/webhooks` default: return '' } } /** * Get all endpoint paths */ get endpoints(): Record { return { ...this._endpoints } } /** * Handle an HTTP request and route to the appropriate integration * * @example * ```typescript * // Express middleware * app.use('/api', async (req, res) => { * const response = await hub.handleRequest({ * method: req.method, * path: req.path, * query: req.query, * headers: req.headers, * body: req.body * }) * * if (response.isSSE) { * // Handle SSE stream * } else { * res.status(response.status).set(response.headers).json(response.body) * } * }) * ``` */ async handleRequest(request: IntegrationRequest): Promise { const { path } = request // Route to appropriate integration based on path for (const [type, basePath] of Object.entries(this._endpoints)) { if (path.startsWith(basePath) || path === basePath) { const integration = this.integrations.get(type as IntegrationType) if (integration && 'handleRequest' in integration) { // The `in` guard proved the integration exposes an HTTP handler. // Each integration declares its own structurally-compatible // request/response shapes (e.g. the OData request/response pair), // so the hub dispatches through this minimal common signature. const handler = integration as IntegrationBase & { handleRequest(request: IntegrationRequest): Promise } const relativePath = path.slice(basePath.length) || '/' return handler.handleRequest({ ...request, path: relativePath }) } } } return { status: 404, headers: { 'Content-Type': 'application/json' }, body: { error: 'Not found', path } } } /** * Get a specific integration */ get(type: IntegrationType): T | undefined { return this.integrations.get(type) as T | undefined } /** * Get the OData integration */ get odata(): ODataIntegration | undefined { return this.integrations.get('odata') as ODataIntegration } /** * Get the Google Sheets integration */ get sheets(): GoogleSheetsIntegration | undefined { return this.integrations.get('sheets') as GoogleSheetsIntegration } /** * Get the SSE integration */ get sse(): SSEIntegration | undefined { return this.integrations.get('sse') as SSEIntegration } /** * Get the Webhook integration */ get webhooks(): WebhookIntegration | undefined { return this.integrations.get('webhooks') as WebhookIntegration } /** * Check if an integration is enabled */ has(type: IntegrationType): boolean { return this.integrations.has(type) } /** * Get health status of all integrations */ health(): Record { const result: Record = {} for (const [type, integration] of this.integrations) { result[type] = integration.health() } return result as Record } /** * Stop all integrations */ async stop(): Promise { for (const integration of this.integrations.values()) { await integration.stop() } } /** * Get connection instructions for each tool */ getInstructions(): Record { const base = this.config.basePath || 'http://localhost:3000' return { excel: ` Excel Power Query: 1. Data → Get Data → From Other Sources → From OData Feed 2. Enter URL: ${base}/odata 3. Click OK, then Load `.trim(), powerbi: ` Power BI: 1. Get Data → OData Feed 2. Enter URL: ${base}/odata 3. Click OK, then Load `.trim(), googleSheets: ` Google Sheets: 1. Install the Brainy add-on from Google Workspace Marketplace 2. Open sidebar: Extensions → Brainy → Open 3. Connect to: ${base}/sheets 4. Use custom functions: =BRAINY_QUERY("type:Person", 100) `.trim(), realtime: ` Real-time Streaming (SSE): const source = new EventSource('${base}/events') source.onmessage = (event) => console.log(JSON.parse(event.data)) `.trim(), webhooks: ` Webhooks: POST ${base}/webhooks/register { "url": "https://your-server.com/webhook", "events": { "entityTypes": ["noun"], "operations": ["create", "update"] } } `.trim() } } } /** * Create an Integration Hub with zero configuration * * @example * ```typescript * const hub = await createIntegrationHub(brain) * console.log(hub.endpoints) * ``` */ export async function createIntegrationHub( brain: any, config?: IntegrationHubConfig ): Promise { return IntegrationHub.create(brain, config) }