Adaptador Total.js
O adapter Total.js conecta o runtime Total.js aos contratos HTTP do Jumentix. Ele fica na borda: recebe request, normaliza entrada, chama use cases e transforma o resultado em response.
Tecnologia integrada
Framework Node.js full-stack com tooling integrado para web, API e aplicação.
- Modelo de runtime: Processo Node gerenciado pelo framework
- Contrato Jumentix: handlers chamam controllers/use cases sem vazar tipos do framework para o domínio.
Quando usar
Use quando: Aplicações que querem uma casca de framework mais ampla mantendo os use cases Jumentix isolados.
Quando evitar
Evite para times padronizando runtimes menores apenas de HTTP.
Como iniciar ou compor
Ponto de entrada real:
apps/backend-template/src/interface/HTTP/adapters/total-js/total-js.tsapps/backend-template/src/interface/HTTP/adapters/start-rest-api.tsquando o adapter usa bootstrap por ambiente
Este adapter é composto pelo runtime/plataforma e não tem script dedicado de dev:*.
# This adapter is composed by its platform runtime.
# Keep controllers framework-free and wire them from the adapter entrypoint.Exemplo completo: Task e Category na borda HTTP
type Category = {
id: string;
name: string;
};
type Task = {
id: string;
title: string;
categoryId: string;
completed: boolean;
};
type CreateTaskRequest = {
title: string;
categoryId: string;
};
type HttpRequest = {
body: unknown;
};
type HttpResponse = {
status: number;
body: unknown;
};
const adapterProfile = {
adapter: 'total-js',
framework: 'Total.js',
runtime: 'Framework-managed Node process',
entrypoint: 'apps/backend-template/src/interface/HTTP/adapters/total-js/total-js.ts'
} as const;
class TaskCatalog {
private readonly categories = new Map<string, Category>();
private readonly tasks = new Map<string, Task>();
createCategory(name: string): Category {
const category = { id: crypto.randomUUID(), name };
this.categories.set(category.id, category);
return category;
}
createTask(input: CreateTaskRequest): Task {
if (!this.categories.has(input.categoryId)) {
throw new Error('Category not found');
}
const task = {
id: crypto.randomUUID(),
title: input.title.trim(),
categoryId: input.categoryId,
completed: false
};
this.tasks.set(task.id, task);
return task;
}
listTasksByCategory(categoryId: string): Task[] {
return [...this.tasks.values()].filter((task) => task.categoryId === categoryId);
}
}
const catalog = new TaskCatalog();
const delivery = catalog.createCategory('Delivery');
const finance = catalog.createCategory('Finance');
catalog.createTask({ title: 'Prepare invoice batch', categoryId: finance.id });
export async function createTaskController(request: HttpRequest): Promise<HttpResponse> {
const input = request.body as Partial<CreateTaskRequest>;
if (!input.title || !input.categoryId) {
return { status: 400, body: { error: 'title and categoryId are required' } };
}
try {
const task = catalog.createTask({ title: input.title, categoryId: input.categoryId });
return { status: 201, body: { adapterProfile, task } };
} catch (error) {
return {
status: 404,
body: { error: error instanceof Error ? error.message : 'Unknown error' }
};
}
}
export async function listDeliveryTasksController(): Promise<HttpResponse> {
return {
status: 200,
body: { category: delivery, tasks: catalog.listTasksByCategory(delivery.id) }
};
}Checklist de adoção
- O adapter fica restrito à camada HTTP.
- Controllers recebem dados normalizados e chamam use cases.
TaskeCategorypertencem ao domínio, não ao framework.- Erros são convertidos para responses na borda.