Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 21 additions & 18 deletions packages/bridge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { PerformanceTracker } from './core/performance';
import { OperationInterceptor } from './core/interceptor';
import { SubscriptionTracker } from './core/subscription-tracker';
import { NotificationMonitor } from './core/notification-monitor';
import { wrapApolloClient } from './adapters/graphql';
import { wrapRestClient, interceptWebSocketFrames } from './adapters/rest';

const MAX_GETTER_TRACES = 200;
const MAX_SUB_UPDATES = 500;
Expand All @@ -23,15 +23,15 @@ const MAX_LANGUAGES = 100;
let nextGetterTraceId = 1;

/**
* Initialize the AD4M DevTools bridge for GraphQL/Apollo transport.
* Initialize the AD4M DevTools bridge for REST + WebSocket transport.
*
* Call this from the Ad4mClient constructor:
* ```typescript
* import { initDevToolsBridge } from '@ad4m-devtools/bridge';
* initDevToolsBridge(this);
* ```
*
* @param client - The Ad4mClient instance (must have #apolloClient accessible)
* @param client - The Ad4mClient instance (REST-based, with optional WebSocket RPC)
*/
export function initDevToolsBridge(client: any): void {
if (typeof globalThis === 'undefined') return;
Expand All @@ -57,24 +57,24 @@ export function initDevToolsBridge(client: any): void {

const connectionState = () => {
const activeClient = getClient();
// For GraphQL/Apollo, connection info comes from the Apollo link/transport
const url = activeClient?.executorUrl || activeClient?.baseUrl || '';
const url = activeClient?.baseUrl || activeClient?.executorUrl || '';
const authenticated = Boolean(activeClient?.hasAuthToken || activeClient?.authenticated);
const activeEventStreams = Number(activeClient?.activeEventStreams || 0);

return {
connected: Boolean(activeClient),
transport: 'graphql' as const,
transport: 'rest' as const,
url,
authenticated,
eventStreamConnected: subscriptions.getActiveCount() > 0,
activeEventStreams: subscriptions.getActiveCount(),
eventStreamConnected: activeEventStreams > 0,
activeEventStreams,
};
};

const enrichErrors = (errors?: any[]): ErrorDetail[] | undefined =>
errors?.map(e => ({
message: e?.message || String(e),
type: e?.type || e?.name || e?.constructor?.name || (e?.extensions?.code ? `GraphQL: ${e.extensions.code}` : 'Error'),
type: e?.type || e?.name || e?.constructor?.name || (e?.status ? `HTTP ${e.status}` : 'Error'),
stack: e?.stack,
nested: e?.networkError
? [{
Expand Down Expand Up @@ -236,19 +236,22 @@ export function initDevToolsBridge(client: any): void {
(globalThis as any).window.__AD4M_DEVTOOLS__ = devtools;
}

// Attempt to wrap the Apollo client link chain
// The Ad4mClient stores it as a private field — we need to access it
// via the client reference passed in
// Wrap the REST client to intercept HTTP and WebSocket calls
try {
// Try to get the apollo client — it may be a private field
const apolloClient = client._apolloClient || client.apolloClient;
if (apolloClient) {
wrapApolloClient(apolloClient, devtools);
const restClient = client._restClient || client.restClient;
if (restClient) {
wrapRestClient(restClient, devtools);

// If WebSocket is already connected, intercept frames
const ws = restClient._ws || restClient.ws;
if (ws) {
interceptWebSocketFrames(ws, devtools);
}
}
} catch {
// Apollo client not accessible — bridge will still work via manual instrumentation
// RestClient not accessible — bridge will still work via manual instrumentation
}
}

export { createDevToolsLink, wrapApolloClient } from './adapters/graphql';
export { wrapRestClient, interceptWebSocketFrames } from './adapters/rest';
export type { AD4MDevTools, DevToolsState, OperationRecord, SubscriptionRecord, SubscriptionUpdateRecord, NotificationRecord, PerformanceState, GetterTraceRecord, LanguageRecord, ErrorDetail } from './core/types';