CLAUDE.md@packages/api · git:20260328.16667d9 · 2026-03-28 · sha256 eed1e353e2c73e22

CLAUDE.md@packages/api git:20260328.16667d9A

Immutable. This exact content never changes and is served at /api/v1/blob/eed1e353e2c73e22.

# @cyberfabric/api

API communication protocols and service registry for FrontX applications.

## SDK Layer

This package is part of the **SDK Layer (L1)** - it has zero @cyberfabric dependencies and can be used independently. It has `axios` as a peer dependency.

## Core Concepts

### BaseApiService

Abstract base class for domain-specific API services:

```typescript
import { BaseApiService, RestProtocol } from '@cyberfabric/api';

class AccountsApiService extends BaseApiService {
  constructor() {
    super(
      { baseURL: '/api/accounts' },
      new RestProtocol()
    );
  }

  async getCurrentUser(): Promise<User> {
    return this.protocol(RestProtocol).get('/user/current');
  }

  async updateProfile(data: ProfileUpdate): Promise<User> {
    return this.protocol(RestProtocol).put('/user/profile', data);
  }
}
```

### API Registry

Central registry for all API services:

```typescript
import { apiRegistry } from '@cyberfabric/api';

// Register service with class reference (type-safe)
apiRegistry.register(AccountsApiService);

// Get service (type-safe with class reference)
const accounts = apiRegistry.getService(AccountsApiService);
const user = await accounts.getCurrentUser();
```

### Mock Support

Configure mocks via RestMockPlugin using the `registerPlugin()` pattern. Framework controls mock plugin activation via the Mock API toggle.

```typescript
import { BaseApiService, RestProtocol, RestMockPlugin } from '@cyberfabric/api';

// Register mock plugins in service constructor
// Framework activates/deactivates based on mock mode state
class ChatApiService extends BaseApiService {
  constructor() {
    const restProtocol = new RestProtocol({ timeout: 30000 });
    super({ baseURL: '/api/chat' }, restProtocol);

    // Register mock plugin (framework controls when it's active)
    this.registerPlugin(restProtocol, new RestMockPlugin({
      mockMap: {
        'GET /api/chat/threads': () => [{ id: '1', title: 'Thread 1' }],
        'POST /api/chat/messages': (body) => ({ id: '2', ...body }),
      },
      delay: 100,
    }));
  }
}

// Global mocks (for cross-cutting concerns)
apiRegistry.plugins.add(RestProtocol, new RestMockPlugin({
  mockMap: {
    'GET /api/health': () => ({ status: 'ok' }),
  },
  delay: 100,
}));
```

### Plugin System

Create plugins by extending ApiPluginBase or ApiPlugin<TConfig>:

```typescript
import { ApiPlugin, ApiPluginBase, ApiRequestContext, ApiResponseContext } from '@cyberfabric/api';

// Simple plugin (no config)
class LoggingPlugin extends ApiPluginBase {
  onRequest(ctx: ApiRequestContext) {
    console.log(`[${ctx.method}] ${ctx.url}`);
    return ctx;
  }

  onResponse(response: ApiResponseContext, request: ApiRequestContext) {
    console.log(`[${response.status}] ${request.url}`);
    return response;
  }
}

// Plugin with config
class AuthPlugin extends ApiPlugin<{ getToken: () => string | null }> {
  onRequest(ctx: ApiRequestContext) {
    const token = this.config.getToken();
    if (!token) return ctx;
    return {
      ...ctx,
      headers: { ...ctx.headers, Authorization: `Bearer ${token}` }
    };
  }
}

// Register on service
service.plugins.add(new LoggingPlugin());
service.plugins.add(new AuthPlugin({ getToken: () => localStorage.getItem('token') }));

// Or register globally
apiRegistry.plugins.add(new LoggingPlugin());
```

## Protocol Support

### RestProtocol

HTTP REST API calls via axios:

```typescript
import { RestProtocol } from '@cyberfabric/api';

const restProtocol = new RestProtocol({
  timeout: 30000,
  withCredentials: true,
  contentType: 'application/json'
});
```

## Mock Mode

Mock mode is controlled centrally by the framework via the `toggleMockMode()` action. Services register mock plugins using `registerPlugin()`, and the framework activates/deactivates them based on mock mode state.

```typescript
// In @cyberfabric/framework - toggle mock mode (used by FrontX Studio)
import { toggleMockMode } from '@cyberfabric/framework';
toggleMockMode(true);  // Enable all mock plugins
toggleMockMode(false); // Disable all mock plugins

// Custom mock plugins must be marked with MOCK_PLUGIN symbol
import { ApiPluginBase, MOCK_PLUGIN } from '@cyberfabric/api';

class CustomMockPlugin extends ApiPluginBase {
  static readonly [MOCK_PLUGIN] = true;  // Required for framework to identify as mock plugin
  // ... implementation
}

// Check if a plugin is a mock plugin
import { isMockPlugin } from '@cyberfabric/api';
if (isMockPlugin(plugin)) {
  console.log('This is a mock plugin');
}
```

## Key Rules

1. **Services extend BaseApiService** - Use the base class for protocol management
2. **Register with class reference** - Call `apiRegistry.register(ServiceClass)`
3. **One service per domain** - Each bounded context gets one service
4. **Mock plugins via registerPlugin()** - Use `this.registerPlugin(protocol, mockPlugin)` in constructor
5. **Mock mode via framework** - Framework controls mock plugin lifecycle via `toggleMockMode()`
6. **Plugin identification by class** - Use class references, not string names

## Retry Pattern

Plugins can retry failed requests with optional modifications using `ApiPluginErrorContext`:

```typescript
import { RestPluginWithConfig, ApiPluginErrorContext } from '@cyberfabric/api';

interface AuthConfig {
  getToken: () => string | null;
  refreshToken: () => Promise<string>;
}

class AuthPlugin extends RestPluginWithConfig<AuthConfig> {
  async onRequest(ctx: RestRequestContext): Promise<RestRequestContext> {
    const token = this.config.getToken();
    if (!token) return ctx;
    return {
      ...ctx,
      headers: { ...ctx.headers, Authorization: `Bearer ${token}` }
    };
  }

  async onError(context: ApiPluginErrorContext): Promise<Error | RestResponseContext> {
    // Check if error is 401 and this is the first attempt (not a retry)
    if (this.is401Error(context.error) && context.retryCount === 0) {
      try {
        // Refresh the token
        const newToken = await this.config.refreshToken();

        // Retry the request with the new token
        return context.retry({
          headers: { ...context.request.headers, Authorization: `Bearer ${newToken}` }
        });
      } catch (refreshError) {
        // If refresh fails, return original error
        return context.error;
      }
    }

    // Not a 401 or already retried - propagate error
    return context.error;
  }

  private is401Error(error: Error): boolean {
    return error.message.includes('401') || error.message.includes('Unauthorized');
  }
}
```

Key retry pattern guidelines:

1. **Always check `retryCount`** to prevent infinite retry loops
2. **Use `context.retry()`** to retry with optional request modifications
3. **Limit retries** - typically retry once (`retryCount === 0`)
4. **Return error** if retry should not happen or max attempts reached
5. **Safety net** - `maxRetryDepth` config (default: 10) prevents infinite loops

```typescript
// Configure max retry depth (default: 10)
const restProtocol = new RestProtocol({ maxRetryDepth: 5 });
```

## Exports

- `BaseApiService` - Abstract base class
- `RestProtocol` - REST API protocol
- `SseProtocol` - SSE protocol
- `ApiPluginBase` - Abstract base class for plugins (no config)
- `ApiPlugin` - Abstract generic class for plugins with config
- `RestPlugin` - REST protocol plugin base class
- `RestPluginWithConfig` - REST protocol plugin with config
- `SsePlugin` - SSE protocol plugin base class
- `SsePluginWithConfig` - SSE protocol plugin with config
- `RestMockPlugin` - REST mock data plugin
- `SseMockPlugin` - SSE mock data plugin
- `apiRegistry` - Singleton registry
- `MOCK_PLUGIN` - Symbol for marking mock plugins
- `isMockPlugin` - Type guard for identifying mock plugins
- `ApiPluginErrorContext` - Error context with retry support
- `ApiRequestContext` - Plugin request context type
- `ApiResponseContext` - Plugin response context type
- `RestRequestContext` - REST request context type
- `RestResponseContext` - REST response context type
- `SseConnectContext` - SSE connection context type
- `ShortCircuitResponse` - Short-circuit response wrapper
- `RestShortCircuitResponse` - REST short-circuit response
- `SseShortCircuitResponse` - SSE short-circuit response
- `PluginType` - Type for plugin references
- `ProtocolType` - Type for protocol references
- `ProtocolPluginType` - Type mapping for protocol plugins
- `isShortCircuit` - Type guard for short-circuit responses
- `isRestShortCircuit` - Type guard for REST short-circuit responses
- `isSseShortCircuit` - Type guard for SSE short-circuit responses
- `MockMap` - Mock response map type