# Construire un flux de composants avec Flow.load() et l'exécuter avec exec()

> Décrivez un FlowStream comme des composants et les connexions entre eux, chargez-le dans l'application, envoyez-y un message et lisez la réponse, et changez la configuration d'un composant pendant qu'il tourne.

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

Un **FlowStream** est un petit programme fait de **composants** (des boîtes avec des entrées et des sorties) et de **connexions** entre eux. Total.js l'exécute dans votre application : un message entre dans un composant, le composant envoie des résultats à ses sorties et les connexions les portent aux composants suivants. Le même schéma est ce que dessine le concepteur Flow de Total.js ; ici il est écrit à la main.

`Flow.load(schema, [callback])` démarre un FlowStream et donne au callback `(err, instance)`. Le schéma est un objet :

- `id` et `name` : l'id est la clé dans `Flow.instances` (`Flow.instances.demo`) ; sans id, un id aléatoire est créé.
- `components` : un objet avec le **code source** de chaque composant, sous forme de texte. Le texte est un script, `<script total>...</script>` (la forme que le concepteur enregistre).
- `design` : les instances des composants. Chacune a un `id`, le `component` qu'elle utilise, sa `config` et `connections`, un objet dont les clés sont des **ids de sortie** et dont les valeurs sont des listes de `{ id, index }` : l'instance et l'**id d'entrée** qui reçoivent ce qui est envoyé à cette sortie.
- `variables` : des valeurs que les composants peuvent lire.

Un composant est un module : `exports.id`, `exports.name`, `exports.inputs` et `exports.outputs` (listes de `{ id, name }`), `exports.config` (les valeurs par défaut) et `exports.make = function(instance, config)`. Dans `make()`, le composant définit `instance.message = function($) {}`, qui reçoit chaque message : `$.data` est la donnée, `$.send(outputid, data)` l'envoie à une sortie et `$.destroy()` termine un message qui ne va pas plus loin.

Pour utiliser le flux depuis votre code, `instance.exec({ id, data, callback })` envoie `data` à l'instance `id` et appelle `callback(err, message)` quand le message se termine ; le dernier message de la chaîne est `message` (donc `message.data` est ce que le dernier composant a reçu). `instance.reconfigure(id, config)` change la configuration d'une instance en marche et `Flow.remove(id)` arrête le FlowStream et l'oublie. `FlowStream` est le même objet que `Flow` : deux noms pour le gestionnaire.

**Un composant est du code qui s'exécute avec tous les droits de l'application** (dans le même thread, sauf si le schéma dit `worker`). Ne chargez jamais un schéma ou un composant qu'un visiteur peut écrire.

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