# Les erreurs courantes avec Total.js 5 avec le code erroné et correct

> Correction des erreurs courantes de Total.js 5 avec des exemples de code incorrect et correct.

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

Les erreurs que les modèles et les gens font Total.js 5, chacune avec le code **wrong** et le code *right**. Les paires sont également publiés comme données de préférence à `/preferences.jsonl`.

## Un PATCH qui écrase les champs non envoyés

Un `PATCH` ne devrait modifier que les champs de la requête. Par défaut, un champ déclaré dans `input` mais non envoyé est rempli avec la valeur par défaut de son type, et une mise à jour enregistrerait cette valeur vide. L'option `partial: true` ne conserve que les champs envoyés.

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

## Une route avec drapeaux qui n'est jamais atteinte

Les routes avec drapeaux sont vérifiées après la route sans drapeaux de la même URL. Si les deux existent, la route sans drapeaux prend toutes les requêtes. Déclarez une seule route et décidez dans le gestionnaire.

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

## Une route pour une URL avec extension

Une URL qui se termine par une extension, comme `/report.csv`, est une requête de fichier. Une route `GET` ne la reconnaît pas et la requête se termine par un 404. Déclarez-la avec `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');
}
```

## Une ligne CRON avec un pas

Écrivez les minutes sous forme de liste : `0,15,30,45` correspond exactement à ces minutes. Le `check(date)` d’une tâche indique si une date correspond, ce qui permet de tester le calendrier sans attendre.

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

## S'attendre à ce que l'option cache d'une action mette en cache

Conservez la réponse d’une action avec `CACHE.set()` et lisez-la avec `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');
};
```

## Du code dans un dossier qui n'est pas chargé

Total.js exécute les fichiers des dossiers listés dans la page sur la structure du projet. Un fichier dans `sources/` n'est pas exécuté ; le code qui doit s'exécuter au démarrage doit donc aller dans `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);
});
```

## Concaténer du texte à PATH.root()

`PATH.root()` renvoie le chemin sans barre oblique finale, donc `PATH.root() + 'tmp/a.txt'` devient `/project` + `tmp/a.txt` = `/projecttmp/a.txt`. Chaque dossier a son propre helper qui ajoute le séparateur.

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

## Une chaîne envoyée dans une route WebSocket en mode texte

Dans une route avec `@text`, les messages reçus sont des chaînes, mais `send(message)` encode toujours ce qu'il envoie en JSON, donc une chaîne arrive avec des guillemets. `send(message, true)` l'envoie telle quelle.

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

## S'attendre à ce que REQUEST() rejette sur un statut d'erreur

`REQUEST()` se résout avec la réponse même lorsque le statut est 404 ou 500. Il ne rejette que lorsque la requête elle-même échoue (connexion refusée, délai dépassé). Vérifiez `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`."
 }
}
```
