README

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

@gograb-space/mock-api

A comprehensive HTTP interceptor library for Angular 20 applications, designed for development, testing, and prototyping with intelligent storage integration and AI-friendly APIs.

🚀 Quick Start for AI Code Generators

Installation & Setup Pattern

// app.config.ts - Standard Angular 20 Configuration
import { ApplicationConfig, APP_INITIALIZER } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import {
  mockHttpInterceptor,
  MockInterceptorUtilsService,
  UsersMock,
  ProductsMock
} from '@gograb-space/mock-api';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([mockHttpInterceptor])),
    {
      provide: APP_INITIALIZER,
      multi: true,
      deps: [MockInterceptorUtilsService],
      useFactory: (utils: MockInterceptorUtilsService) => () => {
        utils.configureMockInterceptor(
          {
            enabled: true,
            delay: [100, 300],
            fallbackToNetwork: false,
            logging: { enabled: true, level: 'info' }
          },
          { ...UsersMock, ...ProductsMock }
        );
        return Promise.resolve();
      }
    }
  ]
};

Basic Mock Definition Pattern

// Pattern for creating mock definitions
import { MockDefinition, MockRequest, MockResponse } from '@gograb-space/mock-api';

export const EntityMock: MockDefinition = {
  'GET /api/entities': (req: MockRequest): MockResponse => ({
    status: 200,
    data: { entities: [], total: 0 }
  }),

  'GET /api/entities/:id': (req: MockRequest): MockResponse => ({
    status: 200,
    data: { id: req.params.id, name: 'Entity' }
  }),

  'POST /api/entities': (req: MockRequest): MockResponse => ({
    status: 201,
    data: { ...req.body, id: Date.now() }
  }),

  'PUT /api/entities/:id': (req: MockRequest): MockResponse => ({
    status: 200,
    data: { ...req.body, id: req.params.id }
  }),

  'DELETE /api/entities/:id': (req: MockRequest): MockResponse => ({
    status: 204,
    data: null
  })
};

📚 Core API Reference

Types

// Essential types for AI code generation
interface MockRequest {
  method: string;
  url: string;
  params: Record<string, string>;    // URL parameters (:id)
  query: Record<string, string>;     // Query parameters (?page=1)
  body: any;                         // Request body
  headers: Record<string, string>;   // Request headers
  timestamp?: number;
  requestId?: string;
}

interface MockResponse {
  status: number;                    // HTTP status code
  data: any;                         // Response data
  headers?: Record<string, string>;  // Response headers
  delay?: number;                    // Custom delay in ms
  statusText?: string;               // HTTP status text
}

type MockHandler = (req: MockRequest) => MockResponse | Promise<MockResponse> | Observable<MockResponse>;
type MockDefinition = Record<string, MockHandler>;

// Configuration interface for AI generators
interface GlobalInterceptorConfig {
  enabled: boolean;
  baseURL?: string;
  delay?: number | [number, number];
  fallbackToNetwork?: boolean;
  logging?: {
    enabled: boolean;
    level: 'debug' | 'info' | 'warn' | 'error';
    logRequests?: boolean;
    logResponses?: boolean;
  };
  persistence?: {
    enabled: boolean;
    ttl?: number;
    storage?: 'indexeddb' | 'localstorage' | 'memory' | 'intelligent';
  };
  middleware?: MockMiddleware[];
  defaultHeaders?: Record<string, string>;
}

Service Injection Pattern

// Standard Angular 20 service pattern with mock-api
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class EntityService {
  private http = inject(HttpClient);

  getEntities(params?: { page?: number; limit?: number }): Observable<EntityResponse> {
    return this.http.get<EntityResponse>('/api/entities', { params });
  }

  getEntity(id: string): Observable<Entity> {
    return this.http.get<Entity>(`/api/entities/${id}`);
  }

  createEntity(entity: CreateEntityRequest): Observable<Entity> {
    return this.http.post<Entity>('/api/entities', entity);
  }

  updateEntity(id: string, entity: UpdateEntityRequest): Observable<Entity> {
    return this.http.put<Entity>(`/api/entities/${id}`, entity);
  }

  deleteEntity(id: string): Observable<void> {
    return this.http.delete<void>(`/api/entities/${id}`);
  }
}

🛠️ Advanced Features

Data Generation with DataGenerator

import { DataGenerator, MockDefinition } from '@gograb-space/mock-api';

const generator = DataGenerator.getInstance();

export const ProductsMock: MockDefinition = {
  'GET /api/products': (req: MockRequest): MockResponse => {
    const count = parseInt(req.query.limit || '10');
    const products = generator.generateArray(() => generator.generateProduct(), count);

    return {
      status: 200,
      data: {
        products,
        total: products.length,
        pagination: {
          page: parseInt(req.query.page || '1'),
          limit: count
        }
      }
    };
  }
};

// Available generators
const user = generator.generateUser();
const product = generator.generateProduct();
const address = generator.generateAddress();
const phone = generator.generatePhone();
const array = generator.generateArray(() => generator.generateUser(), 10);

Runtime Mock Management

import { Component, inject } from '@angular/core';
import { MockInterceptorUtilsService } from '@gograb-space/mock-api';

@Component({
  selector: 'app-admin',
  template: `
    <button (click)="enableTestMode()">Enable Test Mode</button>
    <button (click)="disableInterceptor()">Disable Mocks</button>
  `,
  standalone: true
})
export class AdminComponent {
  private mockUtils = inject(MockInterceptorUtilsService);

  enableTestMode(): void {
    const testMocks = {
      'GET /api/test-data': () => ({ status: 200, data: { test: true } })
    };
    this.mockUtils.updateMocks(testMocks);
  }

  disableInterceptor(): void {
    this.mockUtils.setEnabled(false);
  }

  getStats() {
    return this.mockUtils.getStats();
  }
}

Middleware System

import { MockMiddleware, MiddlewareFactory } from '@gograb-space/mock-api';

// Pre-built middleware
const loggingMiddleware = MiddlewareFactory.createLoggingMiddleware({
  logRequests: true,
  logResponses: true
});

const corsMiddleware = MiddlewareFactory.createCorsMiddleware({
  origin: '*',
  credentials: true
});

// Custom middleware pattern
const authMiddleware: MockMiddleware = {
  name: 'auth',
  priority: 800,
  onRequest: (request) => ({
    ...request,
    headers: {
      ...request.headers,
      'Authorization': request.headers['Authorization'] || 'Bearer mock-token'
    }
  }),
  onResponse: (response, request) => ({
    ...response,
    headers: {
      ...response.headers,
      'X-Mock-Auth': 'true'
    }
  })
};

🔧 Common Patterns for AI Generation

CRUD Operations Pattern

// Template for generating CRUD mocks
function createCrudMocks<T>(
  entityName: string,
  basePath: string,
  generator: () => T,
  initialData: T[] = []
): MockDefinition {
  let data = [...initialData];
  let nextId = data.length + 1;

  return {
    [`GET ${basePath}`]: (req: MockRequest): MockResponse => ({
      status: 200,
      data: {
        items: data,
        total: data.length,
        page: parseInt(req.query.page || '1'),
        limit: parseInt(req.query.limit || '10')
      }
    }),

    [`GET ${basePath}/:id`]: (req: MockRequest): MockResponse => {
      const item = data.find(d => d.id === req.params.id);
      return item
        ? { status: 200, data: item }
        : { status: 404, data: { error: `${entityName} not found` } };
    },

    [`POST ${basePath}`]: (req: MockRequest): MockResponse => {
      const newItem = { ...req.body, id: nextId++ };
      data.push(newItem);
      return { status: 201, data: newItem };
    },

    [`PUT ${basePath}/:id`]: (req: MockRequest): MockResponse => {
      const index = data.findIndex(d => d.id === req.params.id);
      if (index === -1) {
        return { status: 404, data: { error: `${entityName} not found` } };
      }
      data[index] = { ...data[index], ...req.body };
      return { status: 200, data: data[index] };
    },

    [`DELETE ${basePath}/:id`]: (req: MockRequest): MockResponse => {
      const index = data.findIndex(d => d.id === req.params.id);
      if (index === -1) {
        return { status: 404, data: { error: `${entityName} not found` } };
      }
      data.splice(index, 1);
      return { status: 204, data: null };
    }
  };
}

// Usage
export const ProductsMock = createCrudMocks(
  'Product',
  '/api/products',
  () => generator.generateProduct(),
  initialProducts
);

Error Handling Patterns

// Standard error responses for AI generation
const ErrorResponses = {
  notFound: (resource: string, id: string): MockResponse => ({
    status: 404,
    data: {
      error: 'Not Found',
      message: `${resource} with ID ${id} not found`,
      code: `${resource.toUpperCase()}_NOT_FOUND`
    }
  }),

  validationError: (fields: Record<string, string>): MockResponse => ({
    status: 400,
    data: {
      error: 'Validation Error',
      message: 'Invalid input data',
      code: 'VALIDATION_ERROR',
      fields
    }
  }),

  conflict: (message: string): MockResponse => ({
    status: 409,
    data: {
      error: 'Conflict',
      message,
      code: 'RESOURCE_CONFLICT'
    }
  }),

  unauthorized: (): MockResponse => ({
    status: 401,
    data: {
      error: 'Unauthorized',
      message: 'Authentication required',
      code: 'NOT_AUTHENTICATED'
    }
  }),

  forbidden: (): MockResponse => ({
    status: 403,
    data: {
      error: 'Forbidden',
      message: 'Insufficient permissions',
      code: 'ACCESS_DENIED'
    }
  })
};

Pagination Pattern

// Reusable pagination logic
function createPaginatedResponse<T>(
  items: T[],
  page: number = 1,
  limit: number = 10
): { data: T[]; pagination: PaginationInfo } {
  const startIndex = (page - 1) * limit;
  const endIndex = startIndex + limit;
  const paginatedItems = items.slice(startIndex, endIndex);
  const totalPages = Math.ceil(items.length / limit);

  return {
    data: paginatedItems,
    pagination: {
      page,
      limit,
      total: items.length,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1
    }
  };
}

interface PaginationInfo {
  page: number;
  limit: number;
  total: number;
  totalPages: number;
  hasNext: boolean;
  hasPrev: boolean;
}

📋 Code Generation Templates

Entity Mock Template

// Template for AI to generate entity-specific mocks
export const {{EntityName}}Mock: MockDefinition = {
  'GET /api/{{entityPath}}': (req: MockRequest): MockResponse => {
    const page = parseInt(req.query.page || '1');
    const limit = parseInt(req.query.limit || '10');
    const search = req.query.search || '';

    // Apply search filter if needed
    let filtered{{EntityName}}s = {{entityName}}sDB;
    if (search) {
      filtered{{EntityName}}s = {{entityName}}sDB.filter(item =>
        item.name.toLowerCase().includes(search.toLowerCase())
      );
    }

    const { data, pagination } = createPaginatedResponse(filtered{{EntityName}}s, page, limit);

    return {
      status: 200,
      data: { {{entityName}}s: data, pagination }
    };
  },

  'GET /api/{{entityPath}}/:id': (req: MockRequest): MockResponse => {
    const {{entityName}} = {{entityName}}sDB.find(item => item.id === req.params.id);
    return {{entityName}}
      ? { status: 200, data: { {{entityName}} } }
      : ErrorResponses.notFound('{{EntityName}}', req.params.id);
  },

  'POST /api/{{entityPath}}': (req: MockRequest): MockResponse => {
    // Validation
    const required = ['{{requiredField1}}', '{{requiredField2}}'];
    const missing = required.filter(field => !req.body[field]);

    if (missing.length > 0) {
      const fields = missing.reduce((acc, field) => {
        acc[field] = 'Required';
        return acc;
      }, {} as Record<string, string>);
      return ErrorResponses.validationError(fields);
    }

    const new{{EntityName}} = {
      ...req.body,
      id: next{{EntityName}}Id++,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };

    {{entityName}}sDB.push(new{{EntityName}});

    return {
      status: 201,
      data: { {{entityName}}: new{{EntityName}} },
      headers: { Location: `/api/{{entityPath}}/${new{{EntityName}}.id}` }
    };
  },

  'PUT /api/{{entityPath}}/:id': (req: MockRequest): MockResponse => {
    const index = {{entityName}}sDB.findIndex(item => item.id === req.params.id);

    if (index === -1) {
      return ErrorResponses.notFound('{{EntityName}}', req.params.id);
    }

    {{entityName}}sDB[index] = {
      ...{{entityName}}sDB[index],
      ...req.body,
      updatedAt: new Date().toISOString()
    };

    return {
      status: 200,
      data: { {{entityName}}: {{entityName}}sDB[index] }
    };
  },

  'DELETE /api/{{entityPath}}/:id': (req: MockRequest): MockResponse => {
    const index = {{entityName}}sDB.findIndex(item => item.id === req.params.id);

    if (index === -1) {
      return ErrorResponses.notFound('{{EntityName}}', req.params.id);
    }

    const deleted{{EntityName}} = {{entityName}}sDB.splice(index, 1)[0];

    return {
      status: 200,
      data: {
        message: '{{EntityName}} deleted successfully',
        deleted{{EntityName}}: { id: deleted{{EntityName}}.id }
      }
    };
  }
};

// Initialize data
let {{entityName}}sDB = DataGenerator.getInstance()
  .generateArray(() => DataGenerator.getInstance().generate{{EntityName}}(), 50);
let next{{EntityName}}Id = {{entityName}}sDB.length + 1;

🚀 Quick Commands for Development

# Generate new mock
npx nx g @nx/angular:service {{entity-name}}-mock --project=mock-api --no-spec

# Run tests
npx nx test mock-api

# Build library
npx nx build mock-api

# Lint code
npx nx lint mock-api

🔍 Debugging and Testing

// Debug utilities
import { MockInterceptorUtilsService } from '@gograb-space/mock-api';

const mockUtils = inject(MockInterceptorUtilsService);

// Get interceptor statistics
const stats = mockUtils.getStats();
console.log('Total requests:', stats.totalRequests);
console.log('Requests by route:', stats.requestsByRoute);

// Clear all mocks
mockUtils.clearMocks();

// Enable/disable at runtime
mockUtils.setEnabled(false); // Disable mocks
mockUtils.setEnabled(true);  // Enable mocks

🎯 Integration Examples

Testing Integration

// TestBed configuration
beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [
      provideHttpClient(withInterceptors([mockHttpInterceptor])),
      MockInterceptorUtilsService
    ]
  });

  const mockUtils = TestBed.inject(MockInterceptorUtilsService);
  mockUtils.configureMockInterceptor(
    { enabled: true, fallbackToNetwork: false },
    TestMocks
  );
});

Environment-based Configuration

// environments/environment.ts
export const environment = {
  production: false,
  mockApi: {
    enabled: true,
    delay: [100, 300],
    logging: true
  }
};

// app.config.ts
utils.configureMockInterceptor({
  enabled: environment.mockApi.enabled,
  delay: environment.mockApi.delay,
  logging: { enabled: environment.mockApi.logging }
}, mocks);

This documentation provides comprehensive patterns and templates optimized for AI code generators, enabling rapid development of mock APIs with consistent structure and best practices.