ForgeDokumentation
de

Dokumentation

Falsche Fehler mit Total.js 5 mit falscher und richtiger Code

Total.js 5 · total5@0.0.19 guidemistakespitfalls Mit Total.js 5 ausgeführt

Fehler, die Modelle und Menschen machen Total.js 5, jeder mit dem wrong Code und dem right Code. Die Paare werden auch als Präferenzdaten veröffentlicht /preferences.jsonl.

Ein PATCH, der nicht gesendete Felder überschreibt

Ein PATCH sollte nur die Felder der Anfrage ändern. Standardmäßig wird ein in input deklariertes, aber nicht gesendetes Feld mit dem Standardwert seines Typs gefüllt, und ein Update würde diesen leeren Wert speichern. Die Option partial: true behält nur die gesendeten Felder.

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

Eine Route mit Flags, die nie erreicht wird

Routen mit Flags werden nach der Route ohne Flags derselben URL geprüft. Gibt es beide, übernimmt die Route ohne Flags jede Anfrage. Deklariere eine Route und entscheide im Handler.

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' });
}
controllers/right-route-flags.js
exports.install = function() {
	ROUTE('GET /right/flags/', answer);
};

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

Eine Route für eine URL mit Endung

Eine URL, die mit einer Endung wie /report.csv endet, ist eine Dateianfrage. Eine GET-Route passt nicht darauf, und die Anfrage endet mit einem 404. Deklariere sie mit FILE.

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

function report($) {
	$.plain('a,b\n1,2\n');
}
controllers/right-extension-url.js
exports.install = function() {
	ROUTE('FILE /right-report.csv', report);
};

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

Eine CRON-Zeile mit Schrittweite

Schreiben Sie die Minuten als Liste: 0,15,30,45 trifft genau diese Minuten. Das check(date) eines Jobs verrät, ob ein Datum passt, sodass sich der Zeitplan ohne Warten testen lässt.

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

Erwarten, dass die cache-Option einer Action cacht

Speichern Sie die Antwort einer Action mit CACHE.set() und lesen Sie sie mit CACHE.get().

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');
};
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 einem Ordner, der nicht geladen wird

Total.js führt die Dateien der Ordner aus, die auf der Seite zur Projektstruktur aufgeführt sind. Eine Datei in sources/ wird nicht ausgeführt; Code, der beim Start laufen soll, gehört daher nach definitions/.

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

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

Text an PATH.root() anhängen

PATH.root() liefert den Pfad ohne abschließenden Schrägstrich, sodass PATH.root() + 'tmp/a.txt' zu /project + tmp/a.txt = /projecttmp/a.txt wird. Jeder Ordner hat seinen eigenen Helper, der das Trennzeichen ergänzt.

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

Ein String in einer WebSocket-Route im Textmodus

In einer Route mit @text sind die empfangenen Nachrichten Strings, aber send(message) kodiert das Gesendete weiterhin als JSON, sodass ein String mit Anführungszeichen ankommt. send(message, true) sendet ihn unverändert.

controllers/wrong-socket-text.js
exports.install = function() {
	ROUTE('SOCKET /wrong/text/ @text', function() {
		this.on('message', function(client, message) {
			client.send('echo: ' + message);
		});
	});
};
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);
		});
	});
};

Erwarten, dass REQUEST() bei einem Fehlerstatus ablehnt

REQUEST() wird auch bei Status 404 oder 500 mit der Antwort aufgelöst. Es lehnt nur ab, wenn die Anfrage selbst fehlschlägt (Verbindung verweigert, Timeout). Prüfe response.status.

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 });
		}
	});
};
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 });
	});
};
controllers/where.js
exports.install = function() {
	ROUTE('GET /where/', function($) {
		$.json({ url: $.hostname('/') });
	});
};
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`."
 }
}