Overview

CONNECTIVE is a library to help you create large-scale, complex and long-living reactive flows, in Javascript/Typescript. If you are completely new here, it is a good idea to take a glance at the Quick Dive to get yourself familiar a little bit. For instructions on how to install CONNECTIVE, take a look at the How to Install section of the home page.

What is a reactive flow?

A reactive flow is a general form of any asynchronous program. A reactive flow starts with some sources of events/data. For example, the sources of your flow might be DOM events (user clicks, input change), incoming requests to http end-points, a web-socket, etc. The events/data then can go through some transformation, for example you might extract the actual value of the input changed, or the request body from incoming request. The events/data can also go through some sinks, which will do something according to the incoming event/data. For example a sink might show some text on the DOM in response to a button-click, another one might save a record in some database, etc.
  import { source, map, sink } from '@connectv/core';

let a = source();                    //--> creates a source
let b = map(x => 'hellow ' + x);     //--> says hellow to incoming strings
let c = sink(x => console.log(x));   //--> will log the incoming values

a.to(b).to(c);                       //--> data from a will go to b then to c
c.bind();                            //--> lets bind the sink

a.send('world');                     //--> send 'world' to our flow
The sources of a reactive flow might send events/data at any given time. For example, the user might change the value of an input or click on a button at any given time, multiple times even, or a http request might come to your code at any moment. However, the actual code of the flow does not care when the source emits something (or most of time even how many times):
  import { source, map, sink } from '@connectv/core';

let a = source();                         //--> creates a source
let b = map(x => 'hellow ' + x);          //--> says hellow to incoming strings
let c = sink(x => console.log(x));        //--> will log the incoming values

a.to(b).to(c);                            //--> data from a will go to b then to c
c.bind();                                 //--> lets bind the sink

/*!*/setInterval(() => a.send('world'), 1000); //--> send 'world' to our flow every second.
Each node in a reactive flow might also do its thing either in sync or asynchronously, without the rest of the flow being affected:
  import { source, map, sink } from '@connectv/core';

let a = source();                             //--> creates a source
/*!*/let b = map((x, done) => {                    //--> says hellow to incoming strings ...
/*!*/  setTimeout(() => done('hellow ' + x), 500); //--> but after a brief wait
/*!*/});
let c = sink(x => console.log(x));            //--> will log the incoming values

a.to(b).to(c);                                //--> data from a will go to b then to c
c.bind();                                     //--> lets bind the sink

setInterval(() => a.send('world'), 1000);     //--> send 'world' to our flow every second.


Pins: declarative flows

The first way that CONNECTIVE helps with creating large-scale reactive flows is through the conecpt of Pins. Each Pin is a node on your reactive flow: it might be the source, it might be the sink, or any node in between. In the examples above, all the elements of the flow (a, b, c) are pins.

Pins allow for declarative description of your flow. You can define them and connect them to each other in any manner you see fit. You can connect multiple pins to one pin or one to multiple other pins, or even create loops of pins:
  // so assume a, b, c, d and e are pins ...

//
// you can connect them together like this:
//
a.to(b).to(c).to(d).to(e);

//
// or like this (which is equivalent):
//
e.from(d).from(c).from(b).from(a);

//
// you can do stuff like this,
// which means a goes to b and c,
// and both b and c go to d, and then to e:
//
a.to(b, c).to(d).to(e);

//
// even crazier stuff, which means
// a is connected to b and c,
// b is connected to d and e and
// c is connected to d and e:
//
a.to(b, c).to(d, e);

//
// the same thing in reverse:
//
group(d, e).from(b, c).from(a);

//
// or even loop back if you need to:
//
a.to(b).to(c).to(b).to(d).to(e);

//
// or connect stuff serially, which means
// a is connected to c,
// b is connected to d,
// c and d are connected to e
//
group(a, b).serialTo(c, d).to(e);

//
// the same thing in reverse
//
e.from(c, d).serialFrom(a, b);
When you are done declaring your flow(s), you should then bind the pins (for example using .bind() on sinks, or using .subscribe() on any type of pin). This will cause the pins to form the necessary subscriptions and internal connections that would result in the described behavior of your flow in a really performant and efficient manner.

You can read about pins in more detail here. CONNECTIVE comes with a host of various pin types that allow you to achieve all sorts of complex reactive behaviors easily, and you can find the list in the left-side table of contents, which you can access by clicking on the hamburger menu icon in the footer .

You can also easily create your own custom pin types using pipe() and RxJS's pipeable operators. If you are curious on how CONNECTIVE creates the internal machinery of your reactive flows, take a look at this entry.



Agents: flow re-use

Just like synchronous programming, when a reactive flow grows in size, it needs some abstraction for re-using repeated parts of the flow instead of repeating them all over. In CONNECTIVE, that abstraction is an Agent: it is a partial reactive flow with some input pins and some output pins. It is called Agent, as it can be seen as a black-box that responds to incoming events/data/messages by processing them somehow and then emitting some other events/data/messages.

The main way to define your own custom agent is composition(). It is called composition as it directly represents a part of a reactive flow (while other agents might not even utilize reactive flows in their description):
/*!*/import { composition, source, pin, filter, sink } from '@connectv/core';

/*!*/const evenOdd = composition(() => {              // --> define the composition ...
/*!*/  let input = pin();                             // ... define the pins that go in
/*!*/  let odd = input.to(filter(x => x % 2 == 1));   // ... and the two pins that go out
/*!*/  let even = input.to(filter(x => x % 2 == 0));
/*!*/
/*!*/  return [{ input }, { odd, even }];             // --> return the input and output pins
/*!*/});

//
// now using the composition:
//

let a = source();
/*!*/a.to(evenOdd()).serialTo(
  sink(v => console.log('ODD:: ' + v)),
  sink(v => console.log('EVEN:: ' + v))
).subscribe();

a.send(2);
a.send(3);
a.send(4);
Notice how the composition is used:
a.to(evenOdd()).serialTo(...)
When you connect something to an agent or composition, basically you are connecting it to all of its inputs, and when you connect an agent or composition to something, you are basically connecting all of its outputs. So this snippet simply means that a is connected to all of the evenOdd()'s inputs, and its outputs are serially connected to what is provided to .serialTo().

Alternatively, you could access inputs and outputs of a composition explicitly:
// this code does the same thing
let e = evenOdd();
a.to(e.in('input'));
group(e.out('odd'), e.out('even')).serialTo(
  sink(v => console.log('ODD:: ' + v)),
  sink(v => console.log('EVEN:: ' + v))
).subscribe();
// this does the same thing too
let e = evenOdd();
a.to(e.in('input'));
e.out('odd').to(sink(v => console.log('ODD:: ' + v))).subscribe();
e.out('even').to(sink(v => console.log('EVEN:: ' + v))).subscribe();


Besides full-fledged compositions, you can also utilize expr() to turn simpler functions into agents or node() to turn much more complex functions into agents.

Similar to pins, CONNECTIVE offers different pre-built agents for creating powerful flows. You can find the list of these agents in the left-side table of contents, and you can access the table of contents by clicking on the sweet hamburger menu icon in the footer .



About this document

This document is intended as an intuitive guide of concepts and usage of the toolset offered by CONNECTIVE. It does not cover features that are still under consideration, nor does it features that are yet to be fully covered by tests.

Each class or function is similarly described in an intuitive and guide-like manner, rather than a precise specification. The precise specification of each class or function is the test-suites designed to outline and ensure its intended properties and behaviors. The link(s) to the test suites for each function or class can be found on the end of its corresponding entry.

Note that you might find further classes/functions/properties/methods/etc in the code than in this documentation and/or in the test-suites, which means for whatever reason they are not yet part of CONNECTIVE's specification and are potentially subject to change. This holds true even for functionality that might be outlined in this documentation but not yet guaranteed by the test suite, though usually this corresponds to a relatively stable functionality that is missing sufficient test coverage.

SVG SMIL animations are used through-out this documentation to visualize how different pins or agents work. Though almost all modern browsers support the format, most of them do so in a half-hearted and generally buggy way. For this reason, I would recommend reading these docs on Firefox, as per our limited testing, it offers the smoothest and most bug-free support for the format.


Copied to Clipboard!