# Erros comuns com Total.js 5, com código errado e correto

> Corrigir os erros comuns do Total.js 5 com exemplos de código errados e corretos.

Framework: total5
Product: Total.js 5 (total5@0.0.19)
Language: pt
Content-SHA256: 19760c57a48b975f2faf34a9e6bbb8d1f9974b01b1f896e038b996f93e527429
Snapshot: c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c
Sources: total5-installed@total5@0.0.19
Review: not-recorded

Erros que os modelos e pessoas cometem Total.js 5, cada um com o código **wrong** e o código**. os pares também são publicados como dados de preferência em `/preferences.jsonl`.

## Um PATCH que sobrescreve os campos que não foram enviados

Um `PATCH` deve alterar apenas os campos do pedido. Por predefinição, um campo declarado em `input` mas não enviado é preenchido com o valor predefinido do seu tipo, e uma atualização guardaria esse valor vazio. A opção `partial: true` mantém apenas os campos que foram enviados.

```js controllers/wrong-patch.js
NEWACTION('Wrong/patch', {
	name: 'Patch',
	input: 'name:String, price:Number',
	action: function($, model) {
		$.callback(model);
	}
});

exports.install = function() {
	ROUTE('PATCH /wrong/patch/ --> *Wrong/patch');
};
```

```js controllers/right-patch.js
NEWACTION('Right/patch', {
	name: 'Patch',
	partial: true,
	input: 'name:String, price:Number',
	action: function($, model) {
		$.callback(model);
	}
});

exports.install = function() {
	ROUTE('PATCH /right/patch/ --> *Right/patch');
};
```

## Uma rota com flags que nunca é alcançada

As rotas com flags são verificadas depois da rota sem flags do mesmo URL. Se ambas existirem, a rota sem flags fica com todos os pedidos. Declare uma só rota e decida no manipulador.

```js controllers/wrong-route-flags.js
exports.install = function() {
	ROUTE('GET /wrong/flags/', normal);
	ROUTE('GET /wrong/flags/ @xhr', ajax);
};

function normal($) {
	$.json({ handler: 'normal' });
}

function ajax($) {
	$.json({ handler: 'ajax' });
}
```

```js controllers/right-route-flags.js
exports.install = function() {
	ROUTE('GET /right/flags/', answer);
};

function answer($) {
	$.json({ handler: $.xhr ? 'ajax' : 'normal' });
}
```

## Uma rota para um URL com extensão

Um URL que termina com uma extensão, como `/report.csv`, é um pedido de ficheiro. Uma rota `GET` não o apanha e o pedido termina com um 404. Declare-o com `FILE`.

```js controllers/wrong-extension-url.js
exports.install = function() {
	ROUTE('GET /wrong-report.csv', report);
};

function report($) {
	$.plain('a,b\n1,2\n');
}
```

```js controllers/right-extension-url.js
exports.install = function() {
	ROUTE('FILE /right-report.csv', report);
};

function report($) {
	$.plain('a,b\n1,2\n');
}
```

## Uma linha CRON com um passo

Escreva os minutos como uma lista: `0,15,30,45` corresponde exatamente a esses minutos. O `check(date)` de uma tarefa diz se uma data corresponde, por isso o agendamento pode ser testado sem esperar.

```js controllers/wrong-cron-step.js
var job = CRON('*/15 * * * *', function() {});

exports.install = function() {
	ROUTE('GET /wrong/cron/', function($) {
		$.json({ minute31: job.check(new Date(2026, 0, 5, 9, 31)) });
	});
};
```

```js controllers/right-cron-step.js
var job = CRON('0,15,30,45 * * * *', function() {});

exports.install = function() {
	ROUTE('GET /right/cron/', function($) {
		$.json({ minute31: job.check(new Date(2026, 0, 5, 9, 31)), minute30: job.check(new Date(2026, 0, 5, 9, 30)) });
	});
};
```

## Esperar que a opção cache de uma ação faça cache

Guarde a resposta de uma ação com `CACHE.set()` e leia-a com `CACHE.get()`.

```js controllers/wrong-action-cache.js
var runs = { wrong: 0 };

NEWACTION('Wrong/cached', {
	name: 'Cached',
	cache: { expire: '1 minute' },
	action: function($) {
		runs.wrong++;
		$.callback({ runs: runs.wrong });
	}
});

exports.install = function() {
	ROUTE('GET /wrong/cache/ --> *Wrong/cached');
};
```

```js controllers/right-action-cache.js
var runs = { right: 0 };

NEWACTION('Right/cached', {
	name: 'Cached',
	action: function($) {
		var cached = CACHE.get('right-cached');
		if (cached)
			return $.callback(cached);
		runs.right++;
		var value = { runs: runs.right };
		CACHE.set('right-cached', value, '1 minute');
		$.callback(value);
	}
});

exports.install = function() {
	ROUTE('GET /right/cache/ --> *Right/cached');
};
```

## Código numa pasta que não é carregada

O Total.js executa os ficheiros das pastas indicadas na página sobre a estrutura do projeto. Um ficheiro em `sources/` não é executado, por isso o código que tem de correr no arranque pertence a `definitions/`.

```js sources/wrong-folder.js
(MAIN.started = MAIN.started || []).push('sources');
```

```js definitions/right-folder.js
(MAIN.started = MAIN.started || []).push('definitions');

ROUTE('GET /started/', function($) {
	$.json(MAIN.started);
});
```

## Juntar texto a PATH.root()

`PATH.root()` devolve o caminho sem barra final, pelo que `PATH.root() + 'tmp/a.txt'` passa a `/project` + `tmp/a.txt` = `/projecttmp/a.txt`. Cada pasta tem o seu próprio helper que acrescenta o separador.

```js controllers/wrong-path.js
exports.install = function() {
	ROUTE('GET /wrong/path/', function($) {
		$.json({ path: PATH.root() + 'tmp/a.txt' });
	});
};
```

```js controllers/right-path.js
exports.install = function() {
	ROUTE('GET /right/path/', function($) {
		$.json({ path: PATH.tmp('a.txt'), root: PATH.root() });
	});
};
```

## Uma string enviada numa rota WebSocket em modo de texto

Numa rota com `@text` as mensagens que recebe são strings, mas `send(message)` continua a codificar o que envia como JSON, por isso uma string chega com aspas. `send(message, true)` envia-a tal como está.

```js controllers/wrong-socket-text.js
exports.install = function() {
	ROUTE('SOCKET /wrong/text/ @text', function() {
		this.on('message', function(client, message) {
			client.send('echo: ' + message);
		});
	});
};
```

```js controllers/right-socket-text.js
exports.install = function() {
	ROUTE('SOCKET /right/text/ @text', function() {
		this.on('message', function(client, message) {
			client.send('echo: ' + message, true);
		});
	});
};
```

## Esperar que REQUEST() rejeite com um estado de erro

`REQUEST()` resolve com a resposta mesmo quando o estado é 404 ou 500. Só rejeita quando o próprio pedido falha (ligação recusada, tempo esgotado). Verifique `response.status`.

```js controllers/wrong-request-status.js
exports.install = function() {
	ROUTE('GET /wrong/request/', async function($) {
		try {
			var response = await REQUEST({ url: $.hostname('/missing-page/') });
			$.json({ ok: true, body: response.body.length > 0 });
		} catch (e) {
			$.json({ ok: false });
		}
	});
};
```

```js controllers/right-request-status.js
exports.install = function() {
	ROUTE('GET /right/request/', async function($) {
		var response = await REQUEST({ url: $.hostname('/missing-page/') });
		$.json({ ok: response.status < 400, status: response.status });
	});
};
```

```js controllers/where.js
exports.install = function() {
	ROUTE('GET /where/', function($) {
		$.json({ url: $.hostname('/') });
	});
};
```

```json pitfalls.json
{
 "patch": {
  "prompt": "Write a Total.js 5 action for a PATCH route that changes only the fields that were sent.",
  "why": "Without `partial: true` a field that was not sent is filled with the default of its type (an empty string), and saving the model overwrites the stored value."
 },
 "route-flags": {
  "prompt": "Write Total.js 5 routes that answer an AJAX request differently from a normal request on the same URL.",
  "why": "A route without flags on a URL is checked before a route with flags on the same URL and takes every request, so the flagged route is never reached. Use one route and read `$.xhr` in the handler."
 },
 "extension-url": {
  "prompt": "Write a Total.js 5 route that serves a URL with a file extension, such as /report.csv.",
  "why": "A `GET` route never matches a URL with an extension; URLs such as `/report.csv` need a `FILE` route."
 },
 "cron-step": {
  "prompt": "Write a Total.js 5 CRON line that runs every 15 minutes.",
  "why": "Write the minutes as a list, for example `0,15,30,45`, so that the job runs exactly at those minutes."
 },
 "action-cache": {
  "prompt": "Write a Total.js 5 action that keeps its answer for a minute so that the same request does not run it again.",
  "why": "Keep the result of an action yourself with `CACHE`: `CACHE.set()` stores it and `CACHE.get()` reads it."
 },
 "folder": {
  "prompt": "Where do I put code that must run when a Total.js 5 application starts?",
  "why": "Total.js loads and runs the files of `definitions/`, `controllers/`, `actions/`, `schemas/`, `models/`, `modules/`, `extensions/` and `middleware/`. A file in `sources/`, `scripts/` or `transforms/` is not run."
 },
 "path": {
  "prompt": "Write Total.js 5 code that builds the path of a file in the tmp folder.",
  "why": "`PATH.root()` has no trailing slash, so joining text to it glues the names together. Use the helper of the folder: `PATH.tmp(name)`."
 },
 "socket-text": {
  "prompt": "Write a Total.js 5 WebSocket route in @text mode that answers with a plain string.",
  "why": "In a text route `send(message)` still encodes what it sends as JSON, so the string arrives with quotes. Pass `true` as the second argument to send it as it is."
 },
 "request-status": {
  "prompt": "Write Total.js 5 code that calls another server with REQUEST() and handles an error answer such as 404.",
  "why": "REQUEST() resolves with the answer even when the status is an error such as 404; it only rejects when the request itself fails. Check `response.status`."
 }
}
```
