# Algemene fouten met Total.js 5, met de verkeerde en juiste code

> Correcte algemene Total.js 5 fouten met verkeerde en correcte code voorbeelden.

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

De fouten die mensen en modellen maken Total.js 5, elk met de **wrong** code en de **right** code. `/preferences.jsonl`.

## Een PATCH die niet verzonden velden overschrijft

Een `PATCH` zou alleen de velden van het verzoek moeten wijzigen. Standaard wordt een veld dat in `input` is gedeclareerd maar niet is verzonden, gevuld met de standaardwaarde van zijn type, en een update zou die lege waarde opslaan. De optie `partial: true` bewaart alleen de velden die zijn verzonden.

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

## Een route met flags die nooit wordt bereikt

Routes met flags worden gecontroleerd na de route zonder flags op dezelfde URL. Als beide bestaan, neemt de route zonder flags elk verzoek over. Declareer één route en beslis in de 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' });
}
```

## Een route voor een URL met extensie

Een URL die eindigt op een extensie, zoals `/report.csv`, is een bestandsverzoek. Een `GET`-route komt er niet mee overeen en het verzoek eindigt met een 404. Declareer het met `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');
}
```

## Een CRON-regel met een stap

Schrijf de minuten als lijst: `0,15,30,45` komt precies overeen met die minuten. De `check(date)` van een job vertelt of een datum past, zodat het schema zonder wachten getest kan worden.

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

## Verwachten dat de cache-optie van een actie cachet

Bewaar het antwoord van een actie met `CACHE.set()` en lees het met `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');
};
```

## Code in een map die niet wordt geladen

Total.js voert de bestanden uit van de mappen die op de pagina over de projectstructuur staan. Een bestand in `sources/` wordt niet uitgevoerd, dus code die bij het starten moet draaien hoort 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);
});
```

## Tekst aan PATH.root() plakken

`PATH.root()` geeft het pad zonder slash aan het einde terug, dus `PATH.root() + 'tmp/a.txt'` wordt `/project` + `tmp/a.txt` = `/projecttmp/a.txt`. Elke map heeft zijn eigen helper die het scheidingsteken toevoegt.

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

## Een string verzonden in een WebSocket-route in tekstmodus

In een route met `@text` zijn de berichten die je ontvangt strings, maar `send(message)` codeert wat het verzendt nog steeds als JSON, dus een string komt met aanhalingstekens aan. `send(message, true)` verstuurt hem zoals hij is.

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

## Verwachten dat REQUEST() weigert bij een foutstatus

`REQUEST()` wordt opgelost met het antwoord, ook als de status 404 of 500 is. Hij weigert alleen als het verzoek zelf mislukt (verbinding geweigerd, time-out). Controleer `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`."
 }
}
```
