# Bouw een stroom van componenten met Flow.load() en voer hem uit met exec()

> Beschrijf een FlowStream als componenten en de verbindingen ertussen, laad hem in de applicatie, stuur er een bericht doorheen en lees het antwoord, en wijzig de configuratie van een component terwijl hij draait.

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

Een **FlowStream** is een klein programma gemaakt van **componenten** (blokjes met ingangen en uitgangen) en **verbindingen** ertussen. Total.js voert het uit binnen je applicatie: een bericht komt een component binnen, de component stuurt resultaten naar zijn uitgangen en de verbindingen brengen ze naar de volgende componenten. Hetzelfde schema is wat de Flow-designer van Total.js tekent; hier is het met de hand geschreven.

`Flow.load(schema, [callback])` start een FlowStream en geeft de callback `(err, instance)`. Het schema is een object:

- `id` en `name`: het id is de sleutel in `Flow.instances` (`Flow.instances.demo`); zonder id wordt een willekeurig id gemaakt.
- `components`: een object met de **broncode** van elke component, als tekst. De tekst is een script, `<script total>...</script>` (de vorm die de designer opslaat).
- `design`: de instanties van de componenten. Elke heeft een `id`, de `component` die hij gebruikt, zijn `config` en `connections`, een object waarvan de sleutels **uitgangs-id's** zijn en de waarden lijsten van `{ id, index }`: de instantie en het **ingangs-id** die ontvangen wat naar die uitgang wordt gestuurd.
- `variables`: waarden die componenten kunnen lezen.

Een component is een module: `exports.id`, `exports.name`, `exports.inputs` en `exports.outputs` (lijsten van `{ id, name }`), `exports.config` (de standaardwaarden) en `exports.make = function(instance, config)`. In `make()` stelt de component `instance.message = function($) {}` in, die elk bericht ontvangt: `$.data` zijn de gegevens, `$.send(outputid, data)` stuurt ze naar een uitgang en `$.destroy()` beëindigt een bericht dat niet verder gaat.

Om de stroom vanuit je code te gebruiken, stuurt `instance.exec({ id, data, callback })` `data` naar de instantie `id` en roept `callback(err, message)` aan wanneer het bericht eindigt; het laatste bericht van de keten is `message` (dus `message.data` is wat de laatste component ontving). `instance.reconfigure(id, config)` wijzigt de configuratie van een draaiende instantie en `Flow.remove(id)` stopt de FlowStream en vergeet hem. `FlowStream` is hetzelfde object als `Flow`: twee namen voor de beheerder.

**Een component is code die met alle rechten van de applicatie draait** (in dezelfde thread tenzij het schema `worker` zegt). Laad nooit een schema of een component die een bezoeker kan schrijven.

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