From 31ae05e6f5aca636e219edb031aafbd02b09004c Mon Sep 17 00:00:00 2001 From: El-isha Dangana Date: Sun, 29 Mar 2026 14:22:25 +0100 Subject: [PATCH 1/4] feat(web): add i18n support with language switcher (en/fr) --- apps/web/app/layout.tsx | 15 ++++--- apps/web/app/page.tsx | 34 ++++++++-------- apps/web/components/I18nProvider.tsx | 16 ++++++++ apps/web/components/LanguageSwitcher.tsx | 50 ++++++++++++++++++++++++ apps/web/components/OfflineBanner.tsx | 7 +++- apps/web/components/VersionDisplay.tsx | 4 +- apps/web/i18n.ts | 37 ++++++++++++++++++ apps/web/locales/en.json | 21 ++++++++++ apps/web/locales/fr.json | 21 ++++++++++ 9 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 apps/web/components/I18nProvider.tsx create mode 100644 apps/web/components/LanguageSwitcher.tsx create mode 100644 apps/web/i18n.ts create mode 100644 apps/web/locales/en.json create mode 100644 apps/web/locales/fr.json diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index ab85393..8dc7480 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -6,6 +6,7 @@ import { TransactionProvider, } from '@bridgewise/ui-components'; import { OfflineBanner } from '../components/OfflineBanner'; +import { I18nProvider } from '../components/I18nProvider'; import './globals.css'; const customTheme = { @@ -47,12 +48,14 @@ export default function RootLayout({ - - - - {children} - - + + + + + {children} + + + ); diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index e5f5d97..2b632e1 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -2,6 +2,7 @@ 'use client'; import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { BridgeWiseProvider, TransactionHeartbeat, @@ -9,6 +10,7 @@ import { BridgeStatus, } from '@bridgewise/ui-components'; import VersionDisplay from '../components/VersionDisplay'; +import { LanguageSwitcher } from '../components/LanguageSwitcher'; const customTheme = { primaryColor: '#22c55e', @@ -37,28 +39,29 @@ function TransactionDemo() { let nextProgress = state.progress + 5; let nextStep = state.step; - if (nextProgress > 20 && nextProgress < 40) nextStep = 'Confirming on source chain...'; - if (nextProgress > 50 && nextProgress < 70) nextStep = 'Bridging assets...'; - if (nextProgress > 80) nextStep = 'Finalizing on destination...'; + if (nextProgress > 20 && nextProgress < 40) nextStep = t('app.statusConfirming'); + if (nextProgress > 50 && nextProgress < 70) nextStep = t('app.statusBridging'); + if (nextProgress > 80) nextStep = t('app.statusFinalizing'); updateState({ progress: Math.min(nextProgress, 100), step: nextStep }); }, 800); return () => clearInterval(interval); - }, [state, updateState]); + }, [state, updateState, t]); + + const { t } = useTranslation(); return (
+

- BridgeWise Theming Demo + {t('app.title')}

- This page demonstrates the BridgeWise theme system. The heartbeat and status components - are styled via CSS variables injected by BridgeWiseProvider, with a custom - primary color and dark background. + {t('app.description')}

@@ -66,23 +69,23 @@ function TransactionDemo() { onClick={() => startTransaction('tx-' + Date.now())} className="px-6 py-3 rounded-lg text-sm font-medium text-white bg-emerald-500 hover:bg-emerald-600 active:scale-95 transition" > - Start Transaction + {t('app.startTransaction')}

- Inline BridgeStatus + {t('app.inlineStatusTitle')}

- An inline status card using the same theme variables as the floating heartbeat. + {t('app.inlineStatusText')}

- Component-level Overrides + {t('app.componentOverridesTitle')}

- The floating heartbeat below uses a custom className to adjust its - position while still inheriting all theme variables. + {t('app.componentOverridesText')}

- Trigger a transaction and you'll see the heartbeat appear in the bottom-left corner. + {t('app.componentOverridesHint')}

diff --git a/apps/web/components/I18nProvider.tsx b/apps/web/components/I18nProvider.tsx new file mode 100644 index 0000000..9a90e38 --- /dev/null +++ b/apps/web/components/I18nProvider.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { ReactNode, useEffect } from 'react'; +import { I18nextProvider } from 'react-i18next'; +import i18n from '../i18n'; + +export function I18nProvider({ children }: { children: ReactNode }) { + useEffect(() => { + const storedLanguage = window.localStorage.getItem('bridgewise-language'); + if (storedLanguage && storedLanguage !== i18n.language) { + i18n.changeLanguage(storedLanguage); + } + }, []); + + return {children}; +} diff --git a/apps/web/components/LanguageSwitcher.tsx b/apps/web/components/LanguageSwitcher.tsx new file mode 100644 index 0000000..5471602 --- /dev/null +++ b/apps/web/components/LanguageSwitcher.tsx @@ -0,0 +1,50 @@ +'use client'; + +import { ChangeEvent, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +const AVAILABLE_LANGUAGES = [ + { code: 'en', label: 'English' }, + { code: 'fr', label: 'Français' }, +]; + +export function LanguageSwitcher() { + const { i18n, t } = useTranslation(); + const [loaded, setLoaded] = useState(false); + + useEffect(() => { + const stored = window.localStorage.getItem('bridgewise-language'); + if (stored && stored !== i18n.language) { + i18n.changeLanguage(stored); + } + setLoaded(true); + }, [i18n]); + + const handleChange = (event: ChangeEvent) => { + const nextLng = event.target.value; + i18n.changeLanguage(nextLng); + window.localStorage.setItem('bridgewise-language', nextLng); + }; + + if (!loaded) return null; + + return ( +
+ + +
+ ); +} diff --git a/apps/web/components/OfflineBanner.tsx b/apps/web/components/OfflineBanner.tsx index 8099d65..5b07be6 100644 --- a/apps/web/components/OfflineBanner.tsx +++ b/apps/web/components/OfflineBanner.tsx @@ -1,9 +1,11 @@ 'use client'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import { useOfflineDetection } from '../hooks/useOfflineDetection'; export function OfflineBanner() { + const { t } = useTranslation(); const { isOffline, cache } = useOfflineDetection(); if (!isOffline) return null; @@ -33,8 +35,9 @@ export function OfflineBanner() { /> - You are offline. Showing{' '} - {cachedAt ? `cached data from ${cachedAt}` : 'limited functionality'}. + {t('app.offline', { + cached: cachedAt ? t('app.offlineCached', { when: cachedAt }) : t('app.offlineLimited'), + })}
); diff --git a/apps/web/components/VersionDisplay.tsx b/apps/web/components/VersionDisplay.tsx index af8d50b..0b4a433 100644 --- a/apps/web/components/VersionDisplay.tsx +++ b/apps/web/components/VersionDisplay.tsx @@ -1,6 +1,7 @@ 'use client'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import { useVersion, VersionData } from '../hooks/useVersion'; export interface VersionDisplayProps { @@ -29,6 +30,7 @@ export const VersionDisplay: React.FC = ({ apiUrl, onClick, }) => { + const { t } = useTranslation(); const { version, loading, error } = useVersion({ apiUrl, enableLogging, @@ -64,7 +66,7 @@ export const VersionDisplay: React.FC = ({ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> - Loading... + {t('app.loading')} ); } diff --git a/apps/web/i18n.ts b/apps/web/i18n.ts new file mode 100644 index 0000000..b6d6f11 --- /dev/null +++ b/apps/web/i18n.ts @@ -0,0 +1,37 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; + +import en from './locales/en.json'; +import fr from './locales/fr.json'; + +const resources = { + en: { + translation: en, + }, + fr: { + translation: fr, + }, +}; + +if (!i18n.isInitialized) { + i18n + .use(initReactI18next) + .init({ + resources, + lng: 'en', + fallbackLng: 'en', + supportedLngs: ['en', 'fr'], + interpolation: { + escapeValue: false, + }, + react: { + useSuspense: false, + }, + }) + .catch((error) => { + // eslint-disable-next-line no-console + console.error('i18next init failed', error); + }); +} + +export default i18n; diff --git a/apps/web/locales/en.json b/apps/web/locales/en.json new file mode 100644 index 0000000..b6e8b13 --- /dev/null +++ b/apps/web/locales/en.json @@ -0,0 +1,21 @@ +{ + "app": { + "title": "BridgeWise Theming Demo", + "description": "This page demonstrates the BridgeWise theme system. The heartbeat and status components are styled via CSS variables injected by BridgeWiseProvider, with a custom primary color and dark background.", + "startTransaction": "Start Transaction", + "clearState": "Clear State", + "inlineStatusTitle": "Inline BridgeStatus", + "inlineStatusText": "An inline status card using the same theme variables as the floating heartbeat.", + "componentOverridesTitle": "Component-level Overrides", + "componentOverridesText": "The floating heartbeat below uses a custom className to adjust its position while still inheriting all theme variables.", + "componentOverridesHint": "Trigger a transaction and you'll see the heartbeat appear in the bottom-left corner.", + "statusConfirming": "Confirming on source chain...", + "statusBridging": "Bridging assets...", + "statusFinalizing": "Finalizing on destination...", + "offline": "You are offline. Showing {{cached}}.", + "offlineCached": "cached data from {{when}}", + "offlineLimited": "limited functionality", + "language": "Language", + "loading": "Loading..." + } +} diff --git a/apps/web/locales/fr.json b/apps/web/locales/fr.json new file mode 100644 index 0000000..cdc032a --- /dev/null +++ b/apps/web/locales/fr.json @@ -0,0 +1,21 @@ +{ + "app": { + "title": "Démo de thème BridgeWise", + "description": "Cette page démontre le système de thème BridgeWise. Les composants heartbeat et status sont stylés via des variables CSS injectées par BridgeWiseProvider, avec une couleur principale personnalisée et un fond sombre.", + "startTransaction": "Démarrer la transaction", + "clearState": "Réinitialiser l'état", + "inlineStatusTitle": "BridgeStatus en ligne", + "inlineStatusText": "Une carte d'état en ligne utilisant les mêmes variables de thème que le heartbeat flottant.", + "componentOverridesTitle": "Remplacements au niveau du composant", + "componentOverridesText": "Le heartbeat flottant ci-dessous utilise un className personnalisé pour ajuster sa position tout en héritant de toutes les variables de thème.", + "componentOverridesHint": "Déclenchez une transaction et vous verrez le heartbeat apparaître en bas à gauche.", + "statusConfirming": "Confirmation sur la chaîne source...", + "statusBridging": "Transfert d'actifs...", + "statusFinalizing": "Finalisation sur la chaîne de destination...", + "offline": "Vous êtes hors ligne. Affichage de {{cached}}.", + "offlineCached": "données mises en cache depuis {{when}}", + "offlineLimited": "fonctionnalité limitée", + "language": "Langue", + "loading": "Chargement..." + } +} From 7d078da5ef7cf92acff5c46882575082d9835dc6 Mon Sep 17 00:00:00 2001 From: El-isha Dangana Date: Sun, 29 Mar 2026 14:26:29 +0100 Subject: [PATCH 2/4] feat(ui): add batch transaction workflow + per-item error handling --- apps/web/app/page.tsx | 72 ++++++++++- .../TransactionContext.tsx | 115 +++++++++++++++++- 2 files changed, 184 insertions(+), 3 deletions(-) diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 2b632e1..1b6123e 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,7 +1,7 @@ 'use client'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { BridgeWiseProvider, @@ -23,7 +23,9 @@ const customTheme = { }; function TransactionDemo() { - const { state, updateState, startTransaction, clearState } = useTransaction(); + const { t } = useTranslation(); + const { state, updateState, startTransaction, clearState, executeBatch } = useTransaction(); + const [batchResult, setBatchResult] = useState<{ id: string; txHash: string; success: boolean; error?: string; }[] | null>(null); // Simulate progress useEffect(() => { @@ -77,8 +79,74 @@ function TransactionDemo() { > {t('app.clearState')} + + {batchResult && ( +
+

Batch result

+
    + {batchResult.map((item) => ( +
  • + {item.id} + + {item.success ? '✅ success' : '❌ failed'} + {item.error ? `: ${item.error}` : ''} + +
  • + ))} +
+
+ )} +

diff --git a/packages/ui/src/components/TransactionHeartbeat/TransactionContext.tsx b/packages/ui/src/components/TransactionHeartbeat/TransactionContext.tsx index 0741e46..76a18f0 100644 --- a/packages/ui/src/components/TransactionHeartbeat/TransactionContext.tsx +++ b/packages/ui/src/components/TransactionHeartbeat/TransactionContext.tsx @@ -38,6 +38,7 @@ interface TransactionContextType { clearState: () => void; startTransaction: (id: string, initialState?: Partial) => void; recordBridgeTransaction: (transaction: Partial) => Promise; + executeBatch: (items: BatchTransactionInput[]) => Promise; } const TransactionContext = createContext(undefined); @@ -79,6 +80,21 @@ const normalizeBridgeTransaction = ( }; }; +export interface BatchTransactionInput extends Partial { + id: string; +} + +export interface BatchExecutionResultItem { + id: string; + txHash: string; + success: boolean; + error?: string; +} + +export interface BatchExecutionResult { + results: BatchExecutionResultItem[]; +} + export interface TransactionProviderProps { children: ReactNode; historyConfig?: TransactionHistoryConfig; @@ -179,6 +195,96 @@ export const TransactionProvider = ({ }); }, []); + const executeBatch = useCallback( + async (items: BatchTransactionInput[]): Promise => { + const results: BatchExecutionResultItem[] = []; + const total = items.length; + + for (let index = 0; index < items.length; index += 1) { + const item = items[index]; + const progress = Math.round(((index + 1) / total) * 100); + + startTransaction(item.id, { + txHash: item.txHash, + bridgeName: item.bridgeName, + sourceChain: item.sourceChain, + destinationChain: item.destinationChain, + sourceToken: item.sourceToken, + destinationToken: item.destinationToken, + amount: item.amount, + fee: item.fee, + slippagePercent: item.slippagePercent, + account: item.account, + status: 'pending', + step: `Processing batch item ${index + 1}/${total}`, + progress, + }); + + try { + // Simulate delay for transaction processing; replace with real API call if available + await new Promise((resolve) => setTimeout(resolve, 250)); + + const normalized = normalizeBridgeTransaction({ + ...item, + status: 'confirmed', + timestamp: new Date(), + }); + + await historyStorage.upsertTransaction(normalized); + onTransactionTracked?.(normalized); + + results.push({ + id: item.id, + txHash: normalized.txHash, + success: true, + }); + + updateState({ + status: 'success', + step: `Batch item ${index + 1}/${total} confirmed`, + progress, + txHash: normalized.txHash, + }); + } catch (error) { + const errMessage = error instanceof Error ? error.message : 'Unknown error'; + + const failedRecord = normalizeBridgeTransaction({ + ...item, + status: 'failed', + timestamp: new Date(), + }); + + await historyStorage.upsertTransaction(failedRecord); + onTransactionTracked?.(failedRecord); + + results.push({ + id: item.id, + txHash: failedRecord.txHash, + success: false, + error: errMessage, + }); + + updateState({ + status: 'failed', + step: `Batch item ${index + 1}/${total} failed: ${errMessage}`, + progress, + txHash: failedRecord.txHash, + }); + } + } + + setState((prev) => ({ + ...prev, + status: results.every((x) => x.success) ? 'success' : 'failed', + step: `Batch completed: ${results.filter((x) => x.success).length}/${total} succeeded`, + progress: 100, + })); + + return { results }; + }, + [historyStorage, onTransactionTracked, startTransaction, updateState], + ); + const recordBridgeTransaction = useCallback( async (transaction: Partial) => { const normalized = normalizeBridgeTransaction(transaction); @@ -190,7 +296,14 @@ export const TransactionProvider = ({ return ( {children} From 7bc1761c3320e756cadc97569c0e57ffe7c4708e Mon Sep 17 00:00:00 2001 From: El-isha Dangana Date: Sun, 29 Mar 2026 14:31:24 +0100 Subject: [PATCH 3/4] feat(wallet): show wallet address QR code for Action --- packages/ui/src/wallet/WalletConnector.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/wallet/WalletConnector.tsx b/packages/ui/src/wallet/WalletConnector.tsx index e56008b..713bbc8 100644 --- a/packages/ui/src/wallet/WalletConnector.tsx +++ b/packages/ui/src/wallet/WalletConnector.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { QRCodeCanvas } from 'qrcode.react'; import { useMultiWalletContext } from './MultiWalletProvider'; export const WalletConnector: React.FC = () => { @@ -41,7 +42,17 @@ export const WalletConnector: React.FC = () => { Connect {wallet.name} ))} -
Active Account: {activeAccount ? activeAccount.address : 'None'}
+
+ Active Account: {activeAccount ? activeAccount.address : 'None'} +
+ + {activeAccount && ( +
+ Scan wallet address QR + +
+ )} + {error &&
Error: {error.message}
}

); From 93560d3983854c377f153757d01814a6a0ed3c53 Mon Sep 17 00:00:00 2001 From: El-isha Dangana Date: Sun, 29 Mar 2026 14:38:37 +0100 Subject: [PATCH 4/4] Plugin system implemented (issue #140) --- .../dynamic-bridge-discovery/bridge.loader.ts | 27 ----------- .../bridge.registry.ts | 7 --- .../demo-bridge.adapter.ts | 37 +++++++++++++++ .../http-bridge.adapter.ts | 8 ---- .../api/src/dynamic-bridge-discovery/index.ts | 1 + .../websocket-bridge.adapter.ts | 8 ---- apps/web/app/page.tsx | 2 - docs/BRIDGE_PLUGIN_SYSTEM.md | 47 +++++++++++++++++++ 8 files changed, 85 insertions(+), 52 deletions(-) create mode 100644 apps/api/src/dynamic-bridge-discovery/demo-bridge.adapter.ts create mode 100644 docs/BRIDGE_PLUGIN_SYSTEM.md diff --git a/apps/api/src/dynamic-bridge-discovery/bridge.loader.ts b/apps/api/src/dynamic-bridge-discovery/bridge.loader.ts index ce6e340..0c7e0e1 100644 --- a/apps/api/src/dynamic-bridge-discovery/bridge.loader.ts +++ b/apps/api/src/dynamic-bridge-discovery/bridge.loader.ts @@ -1,19 +1,6 @@ import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; -<<<<<<< HEAD:src/dynamic-bridge-discovery/bridge.loader.ts -import { - BridgeAdapter, - BridgeAdapterConstructor, -} from '../interfaces/bridge-adapter.interface'; -import { BridgeModuleConfig } from '../interfaces/bridge-config.interface'; -import { - BridgeInitializationException, - BridgeLoadException, -} from '../exceptions/bridge.exceptions'; -import { BridgeRegistry } from '../registry/bridge.registry'; -import { BRIDGE_ADAPTER_METADATA } from '../decorators/bridge.decorators'; -======= import { BridgeRegistry } from './bridge.registry'; import { BridgeModuleConfig } from './bridge-config.interface'; import { @@ -25,7 +12,6 @@ import { BridgeLoadException, } from './bridge.exceptions'; import { BRIDGE_ADAPTER_METADATA } from './bridge.decorators'; ->>>>>>> 902330b94c4294029cf45eb84c6121443fbb0427:apps/api/src/dynamic-bridge-discovery/bridge.loader.ts @Injectable() export class BridgeLoader implements OnModuleInit { @@ -141,25 +127,12 @@ export class BridgeLoader implements OnModuleInit { } <<<<<<< HEAD:src/dynamic-bridge-discovery/bridge.loader.ts - const mergedConfig = { - ...(this.config.globalConfig ?? {}), - ...(adapterConfig.options ?? {}), - }; - const instance: BridgeAdapter = new AdapterClass(mergedConfig); - - await this.initializeAdapter(instance); - this.registry.register(instance, { - source: resolvedPath, - configKey: name, - }); -======= await this.createAndRegisterAdapter( AdapterClass, resolvedPath, adapterConfig.options ?? {}, name, ); ->>>>>>> 902330b94c4294029cf45eb84c6121443fbb0427:apps/api/src/dynamic-bridge-discovery/bridge.loader.ts } catch (err) { throw new BridgeLoadException(resolvedPath, err as Error); } diff --git a/apps/api/src/dynamic-bridge-discovery/bridge.registry.ts b/apps/api/src/dynamic-bridge-discovery/bridge.registry.ts index cadea6f..8afdc87 100644 --- a/apps/api/src/dynamic-bridge-discovery/bridge.registry.ts +++ b/apps/api/src/dynamic-bridge-discovery/bridge.registry.ts @@ -1,12 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; -<<<<<<< HEAD:src/dynamic-bridge-discovery/bridge.registry.ts -import { - BridgeAdapter, - BridgeCapability, -} from '../interfaces/bridge-adapter.interface'; -======= import { BridgeAdapter, BridgeCapability } from './bridge-adapter.interface'; ->>>>>>> 902330b94c4294029cf45eb84c6121443fbb0427:apps/api/src/dynamic-bridge-discovery/bridge.registry.ts import { BridgeCapabilityNotFoundException, BridgeDuplicateException, diff --git a/apps/api/src/dynamic-bridge-discovery/demo-bridge.adapter.ts b/apps/api/src/dynamic-bridge-discovery/demo-bridge.adapter.ts new file mode 100644 index 0000000..28c8a50 --- /dev/null +++ b/apps/api/src/dynamic-bridge-discovery/demo-bridge.adapter.ts @@ -0,0 +1,37 @@ +import { BridgeAdapter, BridgeCapability } from './bridge-adapter.interface'; +import { BridgePlugin } from './bridge.decorators'; + +@BridgePlugin({ name: 'demo-bridge', version: '0.1.0' }) +export class DemoBridgeAdapter implements BridgeAdapter { + readonly name = 'demo-bridge'; + readonly version = '0.1.0'; + readonly capabilities: BridgeCapability[] = [ + { name: 'demo', version: '1.0.0', description: 'Demo bridge for plugin architecture' }, + ]; + + private initialized = false; + + async initialize(config?: Record): Promise { + this.initialized = true; + } + + async isHealthy(): Promise { + return this.initialized; + } + + async shutdown(): Promise { + this.initialized = false; + } + + async execute(operation: string, payload: T): Promise { + if (!this.initialized) { + throw new Error('DemoBridgeAdapter not initialized'); + } + + return { + operation, + payload, + handledBy: this.name, + } as unknown as R; + } +} diff --git a/apps/api/src/dynamic-bridge-discovery/http-bridge.adapter.ts b/apps/api/src/dynamic-bridge-discovery/http-bridge.adapter.ts index 001d6dd..08d513f 100644 --- a/apps/api/src/dynamic-bridge-discovery/http-bridge.adapter.ts +++ b/apps/api/src/dynamic-bridge-discovery/http-bridge.adapter.ts @@ -1,13 +1,5 @@ -<<<<<<< HEAD:src/dynamic-bridge-discovery/http-bridge.adapter.ts -import { - BridgeAdapter, - BridgeCapability, -} from '../interfaces/bridge-adapter.interface'; -import { BridgePlugin } from '../decorators/bridge.decorators'; -======= import { BridgeAdapter, BridgeCapability } from './bridge-adapter.interface'; import { BridgePlugin } from './bridge.decorators'; ->>>>>>> 902330b94c4294029cf45eb84c6121443fbb0427:apps/api/src/dynamic-bridge-discovery/http-bridge.adapter.ts @BridgePlugin({ name: 'http-bridge', version: '1.0.0' }) export class HttpBridgeAdapter implements BridgeAdapter { diff --git a/apps/api/src/dynamic-bridge-discovery/index.ts b/apps/api/src/dynamic-bridge-discovery/index.ts index ebb4e55..2270d6c 100644 --- a/apps/api/src/dynamic-bridge-discovery/index.ts +++ b/apps/api/src/dynamic-bridge-discovery/index.ts @@ -29,3 +29,4 @@ export { // Example adapters (not for production use — illustrative only) export { HttpBridgeAdapter } from './http-bridge.adapter'; export { WebSocketBridgeAdapter } from './websocket-bridge.adapter'; +export { DemoBridgeAdapter } from './demo-bridge.adapter'; diff --git a/apps/api/src/dynamic-bridge-discovery/websocket-bridge.adapter.ts b/apps/api/src/dynamic-bridge-discovery/websocket-bridge.adapter.ts index 33bc455..0b0e247 100644 --- a/apps/api/src/dynamic-bridge-discovery/websocket-bridge.adapter.ts +++ b/apps/api/src/dynamic-bridge-discovery/websocket-bridge.adapter.ts @@ -1,13 +1,5 @@ -<<<<<<< HEAD:src/dynamic-bridge-discovery/websocket-bridge.adapter.ts -import { - BridgeAdapter, - BridgeCapability, -} from '../interfaces/bridge-adapter.interface'; -import { BridgePlugin } from '../decorators/bridge.decorators'; -======= import { BridgeAdapter, BridgeCapability } from './bridge-adapter.interface'; import { BridgePlugin } from './bridge.decorators'; ->>>>>>> 902330b94c4294029cf45eb84c6121443fbb0427:apps/api/src/dynamic-bridge-discovery/websocket-bridge.adapter.ts @BridgePlugin({ name: 'ws-bridge', version: '1.0.0' }) export class WebSocketBridgeAdapter implements BridgeAdapter { diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 1b6123e..65d1828 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -51,8 +51,6 @@ function TransactionDemo() { return () => clearInterval(interval); }, [state, updateState, t]); - const { t } = useTranslation(); - return (
diff --git a/docs/BRIDGE_PLUGIN_SYSTEM.md b/docs/BRIDGE_PLUGIN_SYSTEM.md new file mode 100644 index 0000000..7630636 --- /dev/null +++ b/docs/BRIDGE_PLUGIN_SYSTEM.md @@ -0,0 +1,47 @@ +# Bridge Plugin System + +This project supports dynamic registration of bridge adapters using the `BridgeModule` API and the `BridgePlugin` decorator. + +## Plugin interface + +- `BridgeAdapter` (interface): defines `name`, `version`, `capabilities`, `initialize`, `isHealthy`, `shutdown`, `execute`. +- `BridgePlugin` (decorator): annotate classes for auto-discovery and metadata. + +## Core classes + +- `BridgeRegistry` (singleton): manages adapters by name +- `BridgeLoader` (OnModuleInit): loads adapters from filesystem or config +- `BridgeService`: executes operations via registered bridges and runtime plugin registration + +## Usage + +1. Create a plugin adapter: + +```ts +import { BridgeAdapter, BridgeCapability } from '@bridgewise/api/dynamic-bridge-discovery'; +import { BridgePlugin } from '@bridgewise/api/dynamic-bridge-discovery'; + +@BridgePlugin({ name: 'my-bridge', version: '1.0.0' }) +export class MyBridgeAdapter implements BridgeAdapter { ... } +``` + +2. Register via runtime injection: + +```ts +bridgeService.registerBridge(new MyBridgeAdapter()); +``` + +3. Or set up auto-discovery by config: + +```ts +BridgeModule.forRoot({ + autoDiscover: true, + bridgesDirectory: './plugins', +}); +``` + +## Acceptance criteria + +- plugin interface implemented +- dynamic bridge registration without core code change +- runtime and config-driven plugin loading