# Изградете поток от компоненти с Flow.load() и го изпълнете с exec()

> Опишете FlowStream като компоненти и връзките между тях, заредете го в приложението, изпратете съобщение през него и прочетете отговора, и сменете конфигурацията на компонент, докато работи.

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

**FlowStream** е малка програма, направена от **компоненти** (кутийки с входове и изходи) и **връзки** между тях. Total.js я изпълнява вътре във вашето приложение: съобщение влиза в един компонент, компонентът изпраща резултати към изходите си, а връзките ги пренасят към следващите компоненти. Същата схема е това, което чертае дизайнерът на Flow на Total.js; тук е написана на ръка.

`Flow.load(schema, [callback])` стартира FlowStream и дава на callback `(err, instance)`. Схемата е обект:

- `id` и `name`: id е ключът в `Flow.instances` (`Flow.instances.demo`); без id се създава случаен.
- `components`: обект с **изходния код** на всеки компонент, като текст. Текстът е скрипт, `<script total>...</script>` (формата, която запазва дизайнерът).
- `design`: инстанциите на компонентите. Всяка има `id`, `component`, който използва, своя `config` и `connections`, обект, чиито ключове са **id на изходи**, а стойностите са списъци от `{ id, index }`: инстанцията и **id на входа**, които получават онова, което се изпраща към този изход.
- `variables`: стойности, които компонентите могат да четат.

Компонентът е модул: `exports.id`, `exports.name`, `exports.inputs` и `exports.outputs` (списъци от `{ id, name }`), `exports.config` (стойностите по подразбиране) и `exports.make = function(instance, config)`. В `make()` компонентът задава `instance.message = function($) {}`, която получава всяко съобщение: `$.data` са данните, `$.send(outputid, data)` ги изпраща към изход, а `$.destroy()` прекратява съобщение, което не отива по-нататък.

За да използвате потока от кода си, `instance.exec({ id, data, callback })` изпраща `data` към инстанцията `id` и извиква `callback(err, message)`, когато съобщението приключи; последното съобщение във веригата е `message` (така че `message.data` е онова, което е получил последният компонент). `instance.reconfigure(id, config)` променя конфигурацията на работеща инстанция, а `Flow.remove(id)` спира FlowStream и го забравя. `FlowStream` е същият обект като `Flow`: две имена за мениджъра.

**Компонентът е код, който работи с всички права на приложението** (в същата нишка, освен ако схемата не казва `worker`). Никога не зареждайте схема или компонент, които посетител може да напише.

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