# Gli errori comuni con Total.js 5, con il codice sbagliato e corretto

> Corretto comune Total.js 5 errori con esempi di codice sbagliati e corretti.

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

Gli errori che i modelli e le persone fanno con Total.js 5, ognuno con il codice **wrong** e il codico **right**. i coppie sono pubblicati anche come dati di preferenza a `/preferences.jsonl`.

## Un PATCH che sovrascrive i campi non inviati

Un `PATCH` dovrebbe modificare solo i campi della richiesta. Per impostazione predefinita un campo dichiarato in `input` ma non inviato viene riempito con il valore predefinito del suo tipo, e un aggiornamento salverebbe quel valore vuoto. L'opzione `partial: true` mantiene solo i campi inviati.

```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');
};
```

## Una route con flag che non viene mai raggiunta

Le route con flag vengono controllate dopo la route senza flag sullo stesso URL. Se esistono entrambe, la route senza flag prende tutte le richieste. Dichiara una sola route e decidi nell'handler.

```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' });
}
```

## Una route per un URL con estensione

Un URL che termina con un'estensione, come `/report.csv`, è una richiesta di file. Una route `GET` non lo intercetta e la richiesta termina con un 404. Dichiaralo con `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');
}
```

## Una riga CRON con un passo

Scrivi i minuti come elenco: `0,15,30,45` corrisponde esattamente a quei minuti. Il `check(date)` di un job dice se una data corrisponde, così la pianificazione si può provare senza aspettare.

```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)) });
	});
};
```

## Aspettarsi che l'opzione cache di un'azione faccia caching

Conserva la risposta di un’azione con `CACHE.set()` e leggila con `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');
};
```

## Codice in una cartella che non viene caricata

Total.js esegue i file delle cartelle elencate nella pagina sulla struttura del progetto. Un file in `sources/` non viene eseguito, quindi il codice che deve girare all'avvio va in `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);
});
```

## Concatenare testo a PATH.root()

`PATH.root()` restituisce il percorso senza barra finale, quindi `PATH.root() + 'tmp/a.txt'` diventa `/project` + `tmp/a.txt` = `/projecttmp/a.txt`. Ogni cartella ha il proprio helper che aggiunge il separatore.

```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() });
	});
};
```

## Una stringa inviata in una route WebSocket in modalità testo

In una route con `@text` i messaggi ricevuti sono stringhe, ma `send(message)` codifica comunque ciò che invia come JSON, quindi una stringa arriva con le virgolette. `send(message, true)` la invia così com'è.

```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);
		});
	});
};
```

## Aspettarsi che REQUEST() rifiuti con uno stato di errore

`REQUEST()` si risolve con la risposta anche quando lo stato è 404 o 500. Rifiuta solo quando la richiesta stessa fallisce (connessione rifiutata, timeout). Controlla `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`."
 }
}
```
