/** * Integration Hub - Integration Loader * * Lazy-loads integrations with environment detection. * Zero external dependencies - works everywhere. */ import { IntegrationBase } from './IntegrationBase.js' import { IntegrationConfig } from './types.js' /** * Supported integration types */ export type IntegrationType = | 'odata' // Excel Power Query, Power BI, Tableau | 'sheets' // Google Sheets two-way sync | 'sse' // Server-Sent Events streaming | 'webhooks' // Push notifications to external URLs /** * Runtime environment */ export type RuntimeEnvironment = | 'node' | 'browser' | 'deno' | 'cloudflare' | 'bun' /** * Integration metadata */ export interface IntegrationInfo { id: IntegrationType name: string description: string environments: RuntimeEnvironment[] tools: string[] // What tools this works with } /** * Integration catalog - all built-in, zero dependencies */ export const INTEGRATION_CATALOG: Record = { odata: { id: 'odata', name: 'OData 4.0 API', description: 'REST API for spreadsheets and BI tools', environments: ['node', 'browser', 'deno', 'cloudflare', 'bun'], tools: ['Excel Power Query', 'Power BI', 'Tableau', 'Qlik', 'SAP'] }, sheets: { id: 'sheets', name: 'Google Sheets', description: 'Two-way sync with Google Sheets', environments: ['node', 'browser', 'deno', 'cloudflare', 'bun'], tools: ['Google Sheets', 'Apps Script'] }, sse: { id: 'sse', name: 'Real-time Streaming', description: 'Server-Sent Events for live updates', environments: ['node', 'browser', 'deno', 'cloudflare', 'bun'], tools: ['Dashboards', 'Live UIs', 'Monitoring'] }, webhooks: { id: 'webhooks', name: 'Webhooks', description: 'Push events to external URLs', environments: ['node', 'browser', 'deno', 'cloudflare', 'bun'], tools: ['Zapier', 'IFTTT', 'n8n', 'Custom APIs'] } } /** * Detect current runtime environment */ export function detectEnvironment(): RuntimeEnvironment { // Runtime-detection boundary: these globals exist only in specific runtimes // (Deno, Bun, Cloudflare Workers), so they aren't on TypeScript's view of // globalThis — probe them through an optional-keyed view. const runtimeGlobals = globalThis as typeof globalThis & { Deno?: unknown Bun?: unknown HTMLRewriter?: unknown } // Deno if (typeof runtimeGlobals.Deno !== 'undefined') { return 'deno' } // Bun if (typeof runtimeGlobals.Bun !== 'undefined') { return 'bun' } // Cloudflare Workers if ( typeof runtimeGlobals.caches !== 'undefined' && typeof runtimeGlobals.HTMLRewriter !== 'undefined' ) { return 'cloudflare' } // Node.js if ( typeof process !== 'undefined' && process.versions && process.versions.node ) { return 'node' } return 'node' } /** * Integration loader configuration */ export interface IntegrationLoaderConfig { /** Which integrations to load: array of types, 'all', or 'none' */ integrations?: IntegrationType[] | 'all' | 'none' /** Override configs per integration */ config?: Partial> /** Custom integrations to add */ custom?: IntegrationBase[] } /** * Lazy-loading integration manager * * @example * ```typescript * // Load all integrations (recommended) * const loader = new IntegrationLoader({ integrations: 'all' }) * const integrations = await loader.load() * * // Load specific integrations * const loader = new IntegrationLoader({ * integrations: ['odata', 'sheets'] * }) * ``` */ export class IntegrationLoader { private config: IntegrationLoaderConfig private environment: RuntimeEnvironment private loaded: Map = new Map() constructor(config: IntegrationLoaderConfig = {}) { this.config = { integrations: config.integrations ?? 'none', config: config.config ?? {}, custom: config.custom ?? [] } this.environment = detectEnvironment() } /** * Get current runtime environment */ getEnvironment(): RuntimeEnvironment { return this.environment } /** * Get all available integrations */ getAvailable(): IntegrationInfo[] { return Object.values(INTEGRATION_CATALOG).filter((info) => info.environments.includes(this.environment) ) } /** * Check if an integration is available */ isAvailable(type: IntegrationType): boolean { const info = INTEGRATION_CATALOG[type] return info?.environments.includes(this.environment) ?? false } /** * Load and instantiate integrations */ async load(): Promise { const toLoad = this.resolveIntegrations() const results: IntegrationBase[] = [] // Load integrations in parallel for speed const loadPromises = toLoad.map(async (type) => { try { const integration = await this.loadOne(type) if (integration) { this.loaded.set(type, integration) return integration } } catch (error: any) { console.warn(`[Brainy] Failed to load ${type}: ${error.message}`) } return null }) const loadedIntegrations = await Promise.all(loadPromises) results.push(...loadedIntegrations.filter((i): i is IntegrationBase => i !== null)) // Add custom integrations for (const custom of this.config.custom ?? []) { results.push(custom) } return results } /** * Get a loaded integration by type */ get(type: IntegrationType): T | undefined { return this.loaded.get(type) as T | undefined } /** * Check if an integration is loaded */ has(type: IntegrationType): boolean { return this.loaded.has(type) } /** * Get all loaded integrations */ all(): IntegrationBase[] { return Array.from(this.loaded.values()) } private resolveIntegrations(): IntegrationType[] { const { integrations } = this.config if (integrations === 'none') { return [] } if (integrations === 'all') { return this.getAvailable().map((info) => info.id) } return (integrations || []).filter((type) => this.isAvailable(type)) } private async loadOne(type: IntegrationType): Promise { const cfg = this.config.config?.[type] switch (type) { case 'odata': { const { ODataIntegration } = await import('../odata/ODataIntegration.js') return new ODataIntegration(cfg) } case 'sheets': { const { GoogleSheetsIntegration } = await import('../sheets/GoogleSheetsIntegration.js') return new GoogleSheetsIntegration(cfg) } case 'sse': { const { SSEIntegration } = await import('../sse/SSEIntegration.js') return new SSEIntegration(cfg) } case 'webhooks': { const { WebhookIntegration } = await import('../webhooks/WebhookIntegration.js') return new WebhookIntegration(cfg) } default: return null } } } /** * Create an integration loader */ export function createIntegrationLoader(config?: IntegrationLoaderConfig): IntegrationLoader { return new IntegrationLoader(config) }