# Einen Fluss aus Komponenten mit Flow.load() bauen und mit exec() ausführen

> Beschreibe einen FlowStream als Komponenten und die Verbindungen zwischen ihnen, lade ihn in die Anwendung, sende eine Nachricht hindurch und lies die Antwort, und ändere die Konfiguration einer Komponente während sie läuft.

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

Ein **FlowStream** ist ein kleines Programm aus **Komponenten** (Kästchen mit Ein- und Ausgängen) und **Verbindungen** zwischen ihnen. Total.js führt es in deiner Anwendung aus: Eine Nachricht tritt in eine Komponente ein, die Komponente sendet Ergebnisse an ihre Ausgänge, und die Verbindungen tragen sie zu den nächsten Komponenten. Dasselbe Schema zeichnet der Flow-Designer von Total.js; hier ist es von Hand geschrieben.

`Flow.load(schema, [callback])` startet einen FlowStream und gibt dem Callback `(err, instance)`. Das Schema ist ein Objekt:

- `id` und `name`: Die ID ist der Schlüssel in `Flow.instances` (`Flow.instances.demo`); ohne ID wird eine zufällige erzeugt.
- `components`: ein Objekt mit dem **Quellcode** jeder Komponente als Text. Der Text ist ein Skript, `<script total>...</script>` (die Form, die der Designer speichert).
- `design`: die Instanzen der Komponenten. Jede hat eine `id`, die `component`, die sie verwendet, ihre `config` und `connections`, ein Objekt, dessen Schlüssel **Ausgangs-IDs** und dessen Werte Listen von `{ id, index }` sind: die Instanz und die **Eingangs-ID**, die erhalten, was an diesen Ausgang gesendet wird.
- `variables`: Werte, die Komponenten lesen können.

Eine Komponente ist ein Modul: `exports.id`, `exports.name`, `exports.inputs` und `exports.outputs` (Listen von `{ id, name }`), `exports.config` (die Standardwerte) und `exports.make = function(instance, config)`. In `make()` setzt die Komponente `instance.message = function($) {}`, die jede Nachricht erhält: `$.data` sind die Daten, `$.send(outputid, data)` sendet sie an einen Ausgang und `$.destroy()` beendet eine Nachricht, die nicht weitergeht.

Um den Fluss aus deinem Code zu benutzen, sendet `instance.exec({ id, data, callback })` `data` an die Instanz `id` und ruft `callback(err, message)` auf, wenn die Nachricht endet; die letzte Nachricht der Kette ist `message` (`message.data` ist also das, was die letzte Komponente erhalten hat). `instance.reconfigure(id, config)` ändert die Konfiguration einer laufenden Instanz, und `Flow.remove(id)` stoppt den FlowStream und vergisst ihn. `FlowStream` ist dasselbe Objekt wie `Flow`: zwei Namen für den Manager.

**Eine Komponente ist Code, der mit allen Rechten der Anwendung läuft** (im selben Thread, sofern das Schema nicht `worker` sagt). Lade niemals ein Schema oder eine Komponente, die ein Besucher schreiben kann.

```js definitions/flow.js
const upper = `<script total>
exports.id = 'upper';
exports.name = 'Upper case';
exports.inputs = [{ id: 'input', name: 'Input' }];
exports.outputs = [{ id: 'output', name: 'Output' }];
exports.config = { prefix: '' };
exports.make = function(instance, config) {
	instance.message = function($) {
		$.send('output', config.prefix + String($.data).toUpperCase());
	};
};
</script>`;

const collect = `<script total>
exports.id = 'collect';
exports.name = 'Collect';
exports.inputs = [{ id: 'input', name: 'Input' }];
exports.make = function(instance) {
	instance.message = function($) {
		MAIN.collected.push($.data);
		$.destroy();
	};
};
</script>`;

MAIN.collected = [];

MAIN.flow = null;

Flow.load({
	id: 'demo',
	name: 'Demo',
	components: { upper: upper, collect: collect },
	design: {
		a: { id: 'a', component: 'upper', config: { prefix: '> ' }, connections: { output: [{ id: 'b', index: 'input' }] } },
		b: { id: 'b', component: 'collect', config: {}, connections: {} }
	},
	variables: {}
}, function(err, instance) {
	MAIN.flow = instance;
});
```

```js controllers/flow.js
exports.install = function() {
	ROUTE('GET /shout/', shout);
	ROUTE('POST /prefix/ @json', prefix);
	ROUTE('GET /collected/', collected);
	ROUTE('GET /info/', info);
	ROUTE('POST /stop/', stop);
};

function shout($) {
	if (!MAIN.flow)
		return $.invalid('The flow is not running');
	MAIN.flow.exec({ id: 'a', data: $.query.text, callback: (err, message) => $.json({ result: message ? message.data : null }) });
}

function prefix($) {
	MAIN.flow.reconfigure('a', { prefix: $.body.prefix });
	$.json({ ok: true });
}

function collected($) {
	$.json(MAIN.collected);
}

function info($) {
	$.json({ flows: Object.keys(Flow.instances), instances: Object.keys(MAIN.flow.flow.meta.flow), same: Flow === FlowStream });
}

function stop($) {
	Flow.remove('demo');
	MAIN.flow = null;
	$.json({ flows: Object.keys(Flow.instances) });
}
```
