Generation Patterns
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
Code Generation Patterns - @gograb-space/mock-api
This document provides specific patterns and templates optimized for AI code generators creating mock APIs.
🎯 Quick Generation Templates
1. Basic Entity Mock Template
// Template: {{EntityName}}Mock
import { MockDefinition, MockRequest, MockResponse, DataGenerator } from '@gograb-space/mock-api';
const generator = DataGenerator.getInstance();
let {{entityName}}sDB = generator.generateArray(() => generator.generate{{EntityName}}(), 50);
let next{{EntityName}}Id = {{entityName}}sDB.length + 1;
export const {{EntityName}}Mock: MockDefinition = {
// List with pagination and search
'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 || '';
let filtered = {{entityName}}sDB;
if (search) {
filtered = {{entityName}}sDB.filter(item =>
item.name.toLowerCase().includes(search.toLowerCase()) ||
item.description?.toLowerCase().includes(search.toLowerCase())
);
}
const start = (page - 1) * limit;
const items = filtered.slice(start, start + limit);
return {
status: 200,
data: {
{{entityName}}s: items,
pagination: {
page,
limit,
total: filtered.length,
totalPages: Math.ceil(filtered.length / limit)
}
}
};
},
// Get single item
'GET /api/{{entityPath}}/:id': (req: MockRequest): MockResponse => {
const {{entityName}} = {{entityName}}sDB.find(item => item.id === req.params.id);
return {{entityName}}
? { status: 200, data: { {{entityName}} } }
: { status: 404, data: { error: '{{EntityName}} not found', code: '{{ENTITY_NAME}}_NOT_FOUND' } };
},
// Create new item
'POST /api/{{entityPath}}': (req: MockRequest): MockResponse => {
const requiredFields = [{{#each requiredFields}}'{{this}}'{{#unless @last}}, {{/unless}}{{/each}}];
const missing = requiredFields.filter(field => !req.body[field]);
if (missing.length > 0) {
return {
status: 400,
data: {
error: 'Validation Error',
message: 'Missing required fields',
code: 'VALIDATION_ERROR',
fields: missing.reduce((acc, field) => ({ ...acc, [field]: 'Required' }), {})
}
};
}
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}` }
};
},
// Update item
'PUT /api/{{entityPath}}/:id': (req: MockRequest): MockResponse => {
const index = {{entityName}}sDB.findIndex(item => item.id === req.params.id);
if (index === -1) {
return { status: 404, data: { error: '{{EntityName}} not found', code: '{{ENTITY_NAME}}_NOT_FOUND' } };
}
{{entityName}}sDB[index] = {
...{{entityName}}sDB[index],
...req.body,
updatedAt: new Date().toISOString()
};
return { status: 200, data: { {{entityName}}: {{entityName}}sDB[index] } };
},
// Delete item
'DELETE /api/{{entityPath}}/:id': (req: MockRequest): MockResponse => {
const index = {{entityName}}sDB.findIndex(item => item.id === req.params.id);
if (index === -1) {
return { status: 404, data: { error: '{{EntityName}} not found', code: '{{ENTITY_NAME}}_NOT_FOUND' } };
}
const deleted = {{entityName}}sDB.splice(index, 1)[0];
return {
status: 200,
data: {
message: '{{EntityName}} deleted successfully',
deleted{{EntityName}}: { id: deleted.id, name: deleted.name }
}
};
}
};
2. Service Integration Template
// Template: {{EntityName}}Service
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface {{EntityName}} {
id: string | number;
{{#each fields}}
{{name}}: {{type}};
{{/each}}
createdAt: string;
updatedAt: string;
}
export interface {{EntityName}}ListResponse {
{{entityName}}s: {{EntityName}}[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
export interface Create{{EntityName}}Request {
{{#each requiredFields}}
{{name}}: {{type}};
{{/each}}
{{#each optionalFields}}
{{name}}?: {{type}};
{{/each}}
}
@Injectable({ providedIn: 'root' })
export class {{EntityName}}Service {
private http = inject(HttpClient);
private baseUrl = '/api/{{entityPath}}';
get{{EntityName}}s(params?: {
page?: number;
limit?: number;
search?: string;
[key: string]: any;
}): Observable<{{EntityName}}ListResponse> {
return this.http.get<{{EntityName}}ListResponse>(this.baseUrl, { params });
}
get{{EntityName}}(id: string | number): Observable<{ {{entityName}}: {{EntityName}} }> {
return this.http.get<{ {{entityName}}: {{EntityName}} }>(`${this.baseUrl}/${id}`);
}
create{{EntityName}}({{entityName}}: Create{{EntityName}}Request): Observable<{ {{entityName}}: {{EntityName}} }> {
return this.http.post<{ {{entityName}}: {{EntityName}} }>(this.baseUrl, {{entityName}});
}
update{{EntityName}}(id: string | number, {{entityName}}: Partial<{{EntityName}}>): Observable<{ {{entityName}}: {{EntityName}} }> {
return this.http.put<{ {{entityName}}: {{EntityName}} }>(`${this.baseUrl}/${id}`, {{entityName}});
}
delete{{EntityName}}(id: string | number): Observable<{ message: string; deleted{{EntityName}}: Partial<{{EntityName}}> }> {
return this.http.delete<{ message: string; deleted{{EntityName}}: Partial<{{EntityName}}> }>(`${this.baseUrl}/${id}`);
}
}
3. Component Integration Template
// Template: {{EntityName}}ListComponent
import { Component, inject, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { debounceTime, distinctUntilChanged, switchMap, startWith } from 'rxjs/operators';
import { {{EntityName}}Service, {{EntityName}} } from './{{entityName}}.service';
@Component({
selector: 'app-{{entityName}}-list',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
template: `
<div class="{{entityName}}-list">
<div class="header">
<h2>{{EntityName}}s</h2>
<button (click)="create{{EntityName}}()">Add {{EntityName}}</button>
</div>
<div class="search">
<input
type="text"
placeholder="Search {{entityName}}s..."
[formControl]="searchControl"
/>
</div>
<div class="list">
@for ({{entityName}} of {{entityName}}s(); track {{entityName}}.id) {
<div class="{{entityName}}-item">
<h3>{{ {{entityName}}.name }}</h3>
<p>{{ {{entityName}}.description }}</p>
<div class="actions">
<button (click)="edit{{EntityName}}({{entityName}})">Edit</button>
<button (click)="delete{{EntityName}}({{entityName}}.id)" class="danger">Delete</button>
</div>
</div>
} @empty {
<div class="empty">No {{entityName}}s found</div>
}
</div>
<div class="pagination">
<button
(click)="loadPage(currentPage() - 1)"
[disabled]="currentPage() <= 1"
>
Previous
</button>
<span>Page {{ currentPage() }} of {{ totalPages() }}</span>
<button
(click)="loadPage(currentPage() + 1)"
[disabled]="currentPage() >= totalPages()"
>
Next
</button>
</div>
</div>
`,
styles: [`
.{{entityName}}-list {
padding: 1rem;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.search input {
width: 100%;
padding: 0.5rem;
margin-bottom: 1rem;
}
.{{entityName}}-item {
border: 1px solid #ddd;
padding: 1rem;
margin-bottom: 0.5rem;
border-radius: 4px;
}
.actions {
margin-top: 0.5rem;
}
.actions button {
margin-right: 0.5rem;
padding: 0.25rem 0.5rem;
}
.danger {
background-color: #dc3545;
color: white;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 1rem;
}
.pagination button {
margin: 0 0.5rem;
}
.empty {
text-align: center;
padding: 2rem;
color: #666;
}
`]
})
export class {{EntityName}}ListComponent {
private {{entityName}}Service = inject({{EntityName}}Service);
// Signals
{{entityName}}s = signal<{{EntityName}}[]>([]);
currentPage = signal(1);
totalPages = signal(1);
loading = signal(false);
// Form controls
searchControl = new FormControl('');
constructor() {
// Setup search
this.searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
startWith(''),
switchMap(search => {
this.loading.set(true);
return this.{{entityName}}Service.get{{EntityName}}s({
page: 1,
limit: 10,
search: search || undefined
});
})
).subscribe({
next: (response) => {
this.{{entityName}}s.set(response.{{entityName}}s);
this.currentPage.set(response.pagination.page);
this.totalPages.set(response.pagination.totalPages);
this.loading.set(false);
},
error: (error) => {
console.error('Error loading {{entityName}}s:', error);
this.loading.set(false);
}
});
}
loadPage(page: number): void {
if (page < 1 || page > this.totalPages()) return;
this.loading.set(true);
this.{{entityName}}Service.get{{EntityName}}s({
page,
limit: 10,
search: this.searchControl.value || undefined
}).subscribe({
next: (response) => {
this.{{entityName}}s.set(response.{{entityName}}s);
this.currentPage.set(response.pagination.page);
this.loading.set(false);
},
error: (error) => {
console.error('Error loading page:', error);
this.loading.set(false);
}
});
}
create{{EntityName}}(): void {
// Implementation for creating new {{entityName}}
console.log('Create {{entityName}}');
}
edit{{EntityName}}({{entityName}}: {{EntityName}}): void {
// Implementation for editing {{entityName}}
console.log('Edit {{entityName}}:', {{entityName}});
}
delete{{EntityName}}(id: string | number): void {
if (!confirm('Are you sure you want to delete this {{entityName}}?')) return;
this.{{entityName}}Service.delete{{EntityName}}(id).subscribe({
next: () => {
// Remove from list
this.{{entityName}}s.update(items => items.filter(item => item.id !== id));
},
error: (error) => {
console.error('Error deleting {{entityName}}:', error);
}
});
}
}
🚀 Advanced Generation Patterns
4. Relationship-Aware Mock Template
// Template for entities with relationships
export const {{EntityName}}MockWithRelations: MockDefinition = {
'GET /api/{{entityPath}}/:id/{{relationName}}': (req: MockRequest): MockResponse => {
const {{entityName}}Id = req.params.id;
const {{entityName}} = {{entityName}}sDB.find(item => item.id === {{entityName}}Id);
if (!{{entityName}}) {
return { status: 404, data: { error: '{{EntityName}} not found' } };
}
// Get related entities
const related{{RelationName}} = {{relationName}}sDB.filter(item =>
item.{{entityName}}Id === {{entityName}}Id
);
return {
status: 200,
data: {
{{entityName}}Id,
{{relationName}}: related{{RelationName}},
total: related{{RelationName}}.length
}
};
},
'POST /api/{{entityPath}}/:id/{{relationName}}': (req: MockRequest): MockResponse => {
const {{entityName}}Id = req.params.id;
const {{entityName}} = {{entityName}}sDB.find(item => item.id === {{entityName}}Id);
if (!{{entityName}}) {
return { status: 404, data: { error: '{{EntityName}} not found' } };
}
const new{{RelationName}} = {
...req.body,
id: next{{RelationName}}Id++,
{{entityName}}Id,
createdAt: new Date().toISOString()
};
{{relationName}}sDB.push(new{{RelationName}});
return {
status: 201,
data: { {{relationName}}: new{{RelationName}} }
};
}
};
5. Filtered Search Mock Template
// Template for advanced filtering
export const {{EntityName}}SearchMock: MockDefinition = {
'POST /api/{{entityPath}}/search': (req: MockRequest): MockResponse => {
const filters = req.body;
let results = {{entityName}}sDB;
// Apply filters
{{#each filterFields}}
if (filters.{{name}}) {
{{#if isString}}
results = results.filter(item =>
item.{{name}}.toLowerCase().includes(filters.{{name}}.toLowerCase())
);
{{else if isNumber}}
results = results.filter(item => {
if (filters.{{name}}.min !== undefined && item.{{name}} < filters.{{name}}.min) return false;
if (filters.{{name}}.max !== undefined && item.{{name}} > filters.{{name}}.max) return false;
return true;
});
{{else if isBoolean}}
results = results.filter(item => item.{{name}} === filters.{{name}});
{{else if isEnum}}
results = results.filter(item =>
filters.{{name}}.includes ? filters.{{name}}.includes(item.{{name}}) : item.{{name}} === filters.{{name}}
);
{{/if}}
}
{{/each}}
// Apply sorting
if (filters.sortBy) {
results.sort((a, b) => {
const direction = filters.sortOrder === 'desc' ? -1 : 1;
if (a[filters.sortBy] < b[filters.sortBy]) return -1 * direction;
if (a[filters.sortBy] > b[filters.sortBy]) return 1 * direction;
return 0;
});
}
// Apply pagination
const page = filters.page || 1;
const limit = filters.limit || 10;
const start = (page - 1) * limit;
const paginatedResults = results.slice(start, start + limit);
return {
status: 200,
data: {
results: paginatedResults,
pagination: {
page,
limit,
total: results.length,
totalPages: Math.ceil(results.length / limit)
},
filters: filters,
appliedAt: new Date().toISOString()
}
};
}
};
6. Authentication-Aware Mock Template
// Template with authentication
export const {{EntityName}}AuthMock: MockDefinition = {
'GET /api/{{entityPath}}/my': (req: MockRequest): MockResponse => {
const authHeader = req.headers['Authorization'] || req.headers['authorization'];
if (!authHeader?.startsWith('Bearer ')) {
return {
status: 401,
data: { error: 'Unauthorized', code: 'MISSING_AUTH_TOKEN' }
};
}
// Extract user from token (mock implementation)
const token = authHeader.replace('Bearer ', '');
const userId = extractUserIdFromToken(token); // Mock function
if (!userId) {
return {
status: 401,
data: { error: 'Invalid token', code: 'INVALID_AUTH_TOKEN' }
};
}
const user{{EntityName}}s = {{entityName}}sDB.filter(item => item.userId === userId);
return {
status: 200,
data: {
{{entityName}}s: user{{EntityName}}s,
userId,
total: user{{EntityName}}s.length
}
};
}
};
// Mock token extraction function
function extractUserIdFromToken(token: string): string | null {
// In real app, this would decode JWT
if (token === 'mock-token') return 'user-1';
if (token === 'admin-token') return 'admin-1';
return null;
}
🎨 Style and Convention Patterns
Naming Conventions
// Entity naming patterns
const entityPatterns = {
// Singular form for types and interfaces
entityType: '{{EntityName}}',
// Plural form for collections
collectionName: '{{entityName}}s',
// Service naming
serviceName: '{{EntityName}}Service',
// Mock definition naming
mockName: '{{EntityName}}Mock',
// Database variable naming
dbVariable: '{{entityName}}sDB',
// ID variable naming
idVariable: 'next{{EntityName}}Id',
// API path naming (kebab-case)
apiPath: '/api/{{entity-path}}',
// Component naming
componentName: '{{EntityName}}ListComponent',
// Error code naming
errorCode: '{{ENTITY_NAME}}_NOT_FOUND'
};
Response Structure Patterns
// Standard response patterns
const responsePatterns = {
// Single item response
singleItem: {
status: 200,
data: { {{entityName}}: item }
},
// List response
listItems: {
status: 200,
data: {
{{entityName}}s: items,
pagination: {
page: number,
limit: number,
total: number,
totalPages: number
}
}
},
// Created response
created: {
status: 201,
data: { {{entityName}}: newItem },
headers: { Location: '/api/{{entityPath}}/{{id}}' }
},
// Deleted response
deleted: {
status: 200,
data: {
message: '{{EntityName}} deleted successfully',
deleted{{EntityName}}: { id: item.id }
}
},
// Error responses
notFound: {
status: 404,
data: {
error: '{{EntityName}} not found',
code: '{{ENTITY_NAME}}_NOT_FOUND',
message: '{{EntityName}} with ID {{id}} not found'
}
},
validationError: {
status: 400,
data: {
error: 'Validation Error',
code: 'VALIDATION_ERROR',
fields: { fieldName: 'Error message' }
}
}
};
File Organization Pattern
src/mocks/
├── index.ts # Export all mocks
├── {{entityName}}/
│ ├── {{entityName}}.mock.ts # Main mock definition
│ ├── {{entityName}}.types.ts # Type definitions
│ └── {{entityName}}.utils.ts # Helper utilities
└── shared/
├── error-responses.ts # Common error patterns
├── pagination.utils.ts # Pagination helpers
└── validation.utils.ts # Validation helpers
🔧 Integration Helpers
Mock Registration Helper
// Helper for registering multiple mocks
export function registerEntityMocks(
mockUtils: MockInterceptorUtilsService,
entityMocks: Record<string, MockDefinition>
): void {
const allMocks = Object.values(entityMocks).reduce(
(acc, mock) => ({ ...acc, ...mock }),
{}
);
mockUtils.updateMocks(allMocks);
}
// Usage
registerEntityMocks(mockUtils, {
users: UsersMock,
products: ProductsMock,
orders: OrdersMock
});
Environment-Based Mock Loading
// Helper for conditional mock loading
export function loadMocksForEnvironment(
environment: string,
mockUtils: MockInterceptorUtilsService
): void {
const mockConfigs = {
development: {
enabled: true,
delay: [100, 300],
logging: { enabled: true, level: 'info' as const },
mocks: { ...DevelopmentMocks }
},
testing: {
enabled: true,
delay: 0,
logging: { enabled: false, level: 'error' as const },
mocks: { ...TestingMocks }
},
demo: {
enabled: true,
delay: [200, 500],
logging: { enabled: true, level: 'info' as const },
mocks: { ...DemoMocks }
}
};
const config = mockConfigs[environment];
if (config) {
mockUtils.configureMockInterceptor(config, config.mocks);
}
}
This documentation provides comprehensive patterns optimized for AI code generators, enabling consistent and efficient mock API development.