diff --git a/packages/core/classes/pipeline/Node.class.php b/packages/core/classes/pipeline/Node.class.php
new file mode 100644
index 000000000..be917891d
--- /dev/null
+++ b/packages/core/classes/pipeline/Node.class.php
@@ -0,0 +1,67 @@
+
+ Some Rights Reserved, Cedric Francoys, 2010-2021
+ Licensed under GNU GPL 3 license
+*/
+
+namespace core\pipeline;
+
+use equal\orm\Model;
+
+class Node extends Model
+{
+
+ public static function getColumns()
+ {
+ return [
+ 'name' => [
+ 'type' => 'string',
+ 'required' => true
+ ],
+
+ 'description' => [
+ 'type' => 'string'
+ ],
+
+ 'pipeline_id' => [
+ 'type' => 'many2one',
+ 'foreign_object' => 'core\pipeline\Pipeline',
+ 'required' => true
+ ],
+
+ 'out_links_ids' => [
+ 'type' => 'one2many',
+ 'foreign_object' => 'core\pipeline\NodeLink',
+ 'foreign_field' => 'source_node_id'
+ ],
+
+ 'in_links_ids' => [
+ 'type' => 'one2many',
+ 'foreign_object' => 'core\pipeline\NodeLink',
+ 'foreign_field' => 'target_node_id'
+ ],
+
+ 'operation_controller' => [
+ 'type' => 'string'
+ ],
+
+ 'operation_type' => [
+ 'type' => 'string'
+ ],
+
+ 'params_ids' => [
+ 'type' => 'one2many',
+ 'foreign_object' => 'core\pipeline\Parameter',
+ 'foreign_field' => 'node_id'
+ ]
+ ];
+ }
+
+ public function getUnique()
+ {
+ return [
+ ['name', 'pipeline_id']
+ ];
+ }
+}
diff --git a/packages/core/classes/pipeline/NodeLink.class.php b/packages/core/classes/pipeline/NodeLink.class.php
new file mode 100644
index 000000000..551c7c82e
--- /dev/null
+++ b/packages/core/classes/pipeline/NodeLink.class.php
@@ -0,0 +1,40 @@
+
+ Some Rights Reserved, Cedric Francoys, 2010-2021
+ Licensed under GNU GPL 3 license
+*/
+
+namespace core\pipeline;
+
+use equal\orm\Model;
+
+class NodeLink extends Model
+{
+
+ public static function getColumns()
+ {
+ return [
+ 'reference_node_id' => [
+ 'type' => 'integer',
+ 'required' => true
+ ],
+
+ 'source_node_id' => [
+ 'type' => 'many2one',
+ 'foreign_object' => 'core\pipeline\Node',
+ 'required' => true
+ ],
+
+ 'target_node_id' => [
+ 'type' => 'many2one',
+ 'foreign_object' => 'core\pipeline\Node',
+ 'required' => true
+ ],
+
+ 'target_param' => [
+ 'type' => 'string'
+ ]
+ ];
+ }
+}
diff --git a/packages/core/classes/pipeline/Parameter.class.php b/packages/core/classes/pipeline/Parameter.class.php
new file mode 100644
index 000000000..29ee3d10b
--- /dev/null
+++ b/packages/core/classes/pipeline/Parameter.class.php
@@ -0,0 +1,34 @@
+
+ Some Rights Reserved, Yesbabylon SRL, 2020-2021
+ Licensed under GNU AGPL 3 license
+*/
+
+namespace core\pipeline;
+
+use equal\orm\Model;
+
+class Parameter extends Model
+{
+
+ public static function getColumns()
+ {
+ return [
+ 'node_id' => [
+ 'type' => 'many2one',
+ 'foreign_object' => 'core\pipeline\Node'
+ ],
+
+ 'value' => [
+ 'type' => 'string',
+ 'required' => true
+ ],
+
+ 'param' => [
+ 'type' => 'string',
+ 'required' => true
+ ]
+ ];
+ }
+}
diff --git a/packages/core/classes/pipeline/Pipeline.class.php b/packages/core/classes/pipeline/Pipeline.class.php
new file mode 100644
index 000000000..08fdafbde
--- /dev/null
+++ b/packages/core/classes/pipeline/Pipeline.class.php
@@ -0,0 +1,31 @@
+
+ Some Rights Reserved, Cedric Francoys, 2010-2021
+ Licensed under GNU GPL 3 license
+*/
+
+namespace core\pipeline;
+
+use equal\orm\Model;
+
+class Pipeline extends Model
+{
+
+ public static function getColumns()
+ {
+ return [
+ 'nodes_ids' => [
+ 'type' => 'one2many',
+ 'foreign_object' => 'core\pipeline\Node',
+ 'foreign_field' => 'pipeline_id'
+ ],
+
+ 'name' => [
+ 'type' => 'string',
+ 'required' => true,
+ 'unique' => true
+ ],
+ ];
+ }
+}
diff --git a/packages/core/classes/pipeline/PipelineExecution.class.php b/packages/core/classes/pipeline/PipelineExecution.class.php
new file mode 100644
index 000000000..b4e03b80b
--- /dev/null
+++ b/packages/core/classes/pipeline/PipelineExecution.class.php
@@ -0,0 +1,30 @@
+
+ Some Rights Reserved, Cedric Francoys, 2010-2021
+ Licensed under GNU GPL 3 license
+*/
+
+namespace core\pipeline;
+
+use equal\orm\Model;
+
+class PipelineExecution extends Model
+{
+
+ public static function getColumns()
+ {
+ return [
+
+ 'pipeline_id' => [
+ 'type' => 'many2one',
+ 'foreign_object' => 'core\pipeline\Pipeline',
+ 'required' => true
+ ],
+
+ 'status' => [
+ 'type' => 'string'
+ ]
+ ];
+ }
+}
diff --git a/packages/core/classes/pipeline/PipelineNodeExecution.class.php b/packages/core/classes/pipeline/PipelineNodeExecution.class.php
new file mode 100644
index 000000000..2264b70e5
--- /dev/null
+++ b/packages/core/classes/pipeline/PipelineNodeExecution.class.php
@@ -0,0 +1,40 @@
+
+ Some Rights Reserved, Cedric Francoys, 2010-2021
+ Licensed under GNU GPL 3 license
+*/
+
+namespace core\pipeline;
+
+use equal\orm\Model;
+
+class PipelineNodeExecution extends Model
+{
+
+ public static function getColumns()
+ {
+ return [
+
+ 'pipeline_execution_id' => [
+ 'type' => 'many2one',
+ 'foreign_object' => 'core\pipeline\PipelineExecution',
+ 'required' => true
+ ],
+
+ 'node_id' => [
+ 'type' => 'many2one',
+ 'foreign_object' => 'core\pipeline\Node',
+ 'required' => true
+ ],
+
+ 'status' => [
+ 'type' => 'string'
+ ],
+
+ 'result' => [
+ 'type' => 'string'
+ ]
+ ];
+ }
+}
diff --git a/packages/core/data/check-pipeline.php b/packages/core/data/check-pipeline.php
new file mode 100644
index 000000000..03b41322b
--- /dev/null
+++ b/packages/core/data/check-pipeline.php
@@ -0,0 +1,96 @@
+
+ Some Rights Reserved, Cedric Francoys, 2010-2021
+ Licensed under GNU LGPL 3 license
+*/
+
+use core\pipeline\Pipeline;
+
+list($params, $providers) = eQual::announce([
+ 'description' => 'Run the given pipeline.',
+ 'params' => [
+ 'pipeline_id' => [
+ 'description' => 'Pipeline\'s id',
+ 'type' => 'integer'
+ ]
+ ],
+ 'response' => [
+ 'content-type' => 'application/json',
+ 'charset' => 'UTF-8',
+ 'accept-origin' => '*',
+ 'schema' => [
+ 'type' => '',
+ 'qty' => ''
+ ]
+ ],
+ 'access' => [
+ 'visibility' => 'protected'
+ ],
+ 'providers' => ['context']
+]);
+
+/**
+ * @var \equal\php\Context $context
+ */
+$context = $providers['context'];
+
+$pipeline = Pipeline::id($params['pipeline_id'])
+ ->read([
+ 'nodes_ids' => [
+ 'id',
+ 'in_links_ids' => ['source_node_id'],
+ 'out_links_ids' => ['target_node_id']
+ ]
+ ])
+ ->first();
+
+$pipeline_nodes = $pipeline['nodes_ids']->get(true);
+
+$graph = [];
+
+foreach ($pipeline_nodes as $node) {
+ $graph[$node['id']] = [];
+ foreach ($node['in_links_ids'] as $link) {
+ $graph[$node['id']][] = $link['source_node_id'];
+ }
+ foreach ($node['out_links_ids'] as $link) {
+ $graph[$node['id']][] = $link['target_node_id'];
+ }
+ $graph[$node['id']] = array_unique($graph[$node['id']]);
+}
+
+if (!isGraphConnected($graph)) {
+ throw new Exception('non-compliant_pipeline', QN_ERROR_UNKNOWN);
+}
+
+$context->httpResponse()
+ ->body(['success' => true])
+ ->send();
+
+function isGraphConnected($graph)
+{
+ $visited = [];
+ $start_node = array_key_first($graph);
+
+ depthSearch($graph, $start_node, $visited);
+
+ foreach ($graph as $node => $adjacent_nodes) {
+ if (!isset($visited[$node])) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+function depthSearch($graph, $node, &$visited)
+{
+ $visited[$node] = true;
+
+ foreach ($graph[$node] as $adjacent_node) {
+ if (!isset($visited[$adjacent_node])) {
+ depthSearch($graph, $adjacent_node, $visited);
+ }
+ }
+};
diff --git a/packages/core/data/pipeline/test-divide.php b/packages/core/data/pipeline/test-divide.php
new file mode 100644
index 000000000..d4dedfb27
--- /dev/null
+++ b/packages/core/data/pipeline/test-divide.php
@@ -0,0 +1,49 @@
+
+ Some Rights Reserved, Cedric Francoys, 2010-2021
+ Licensed under GNU LGPL 3 license
+*/
+list($params, $providers) = eQual::announce([
+ 'description' => 'Returns the division of two values.',
+ 'params' => [
+ 'numerator' => [
+ 'description' => 'Numerator',
+ 'type' => 'integer',
+ 'usage' => 'numeric/integer',
+ 'required' => true
+ ],
+ 'denominator' => [
+ 'description' => 'Denominator',
+ 'type' => 'integer',
+ 'usage' => 'numeric/integer',
+ 'required' => true
+ ]
+ ],
+ 'response' => [
+ 'content-type' => 'application/json',
+ 'charset' => 'UTF-8',
+ 'accept-origin' => '*',
+ 'schema' => [
+ 'type' => 'integer',
+ 'usage' => 'numeric/integer',
+ 'qty' => 'one'
+ ]
+ ],
+ 'access' => [
+ 'visibility' => 'public',
+ ],
+ 'providers' => ['context']
+]);
+
+list($context) = [$providers['context']];
+
+$result = 0;
+
+if ($params['denominator'] != 0) {
+ $result = intdiv($params['numerator'], $params['denominator']);
+}
+
+$context->httpResponse()
+ ->body($result)
+ ->send();
diff --git a/packages/core/data/pipeline/test-sum-list.php b/packages/core/data/pipeline/test-sum-list.php
new file mode 100644
index 000000000..f3bded8d3
--- /dev/null
+++ b/packages/core/data/pipeline/test-sum-list.php
@@ -0,0 +1,42 @@
+
+ Some Rights Reserved, Cedric Francoys, 2010-2021
+ Licensed under GNU LGPL 3 license
+*/
+list($params, $providers) = eQual::announce([
+ 'description' => 'Returns the sum of a list of integer',
+ 'params' => [
+ 'list' => [
+ 'description' => 'list of integer',
+ 'type' => 'array',
+ 'required' => true
+ ]
+ ],
+ 'response' => [
+ 'content-type' => 'application/json',
+ 'charset' => 'UTF-8',
+ 'accept-origin' => '*',
+ 'schema' => [
+ 'type' => 'integer',
+ 'usage' => 'numeric/integer',
+ 'qty' => 'one'
+ ]
+ ],
+ 'access' => [
+ 'visibility' => 'public',
+ ],
+ 'providers' => ['context']
+]);
+
+list($context) = [$providers['context']];
+
+$result = 0;
+
+foreach ($params['list'] as $element) {
+ $result += $element;
+}
+
+$context->httpResponse()
+ ->body($result)
+ ->send();
diff --git a/packages/core/data/pipeline/test-sum.php b/packages/core/data/pipeline/test-sum.php
new file mode 100644
index 000000000..3960bd18e
--- /dev/null
+++ b/packages/core/data/pipeline/test-sum.php
@@ -0,0 +1,45 @@
+
+ Some Rights Reserved, Cedric Francoys, 2010-2021
+ Licensed under GNU LGPL 3 license
+*/
+list($params, $providers) = eQual::announce([
+ 'description' => 'Returns the sum of two values.',
+ 'params' => [
+ 'first_value' => [
+ 'description' => 'First value',
+ 'type' => 'integer',
+ 'usage' => 'numeric/integer',
+ 'required' => true
+ ],
+ 'second_value' => [
+ 'description' => 'Second value',
+ 'type' => 'integer',
+ 'usage' => 'numeric/integer',
+ 'required' => true
+ ]
+ ],
+ 'response' => [
+ 'content-type' => 'application/json',
+ 'charset' => 'UTF-8',
+ 'accept-origin' => '*',
+ 'schema' => [
+ 'type' => 'integer',
+ 'usage' => 'numeric/integer',
+ 'qty' => 'one'
+ ]
+ ],
+ 'access' => [
+ 'visibility' => 'public',
+ ],
+ 'providers' => ['context']
+]);
+
+list($context) = [$providers['context']];
+
+$result = $params['first_value'] + $params['second_value'];
+
+$context->httpResponse()
+ ->body($result)
+ ->send();
diff --git a/packages/core/data/run-pipeline.php b/packages/core/data/run-pipeline.php
new file mode 100644
index 000000000..8d929a24e
--- /dev/null
+++ b/packages/core/data/run-pipeline.php
@@ -0,0 +1,107 @@
+
+ Some Rights Reserved, Cedric Francoys, 2010-2021
+ Licensed under GNU LGPL 3 license
+*/
+
+use core\pipeline\Pipeline;
+
+list($params, $providers) = eQual::announce([
+ 'description' => 'Run the given pipeline.',
+ 'params' => [
+ 'pipeline_id' => [
+ 'description' => 'Pipeline\'s id',
+ 'type' => 'integer'
+ ]
+ ],
+ 'response' => [
+ 'content-type' => 'application/json',
+ 'charset' => 'UTF-8',
+ 'accept-origin' => '*',
+ 'schema' => [
+ 'type' => '',
+ 'qty' => ''
+ ]
+ ],
+ 'access' => [
+ 'visibility' => 'protected'
+ ],
+ 'providers' => ['context']
+]);
+
+/**
+ * @var \equal\php\Context $context
+ */
+$context = $providers['context'];
+
+eQual::run("get", "core_check-pipeline", $params, true);
+
+$pipeline = Pipeline::id($params['pipeline_id'])
+ ->read([
+ 'nodes_ids' => [
+ 'id',
+ 'name',
+ 'operation_controller',
+ 'operation_type',
+ 'in_links_ids' => ['reference_node_id', 'source_node_id', 'target_param'],
+ 'out_links_ids' => ['target_node_id'],
+ 'params_ids' => ['value', 'param']
+ ]
+ ])
+ ->first();
+
+$pipeline_nodes = $pipeline['nodes_ids']->get(true);
+
+$count = 0;
+
+$result_map = $name_map = [];
+
+foreach ($pipeline_nodes as $node) {
+ if ($node['operation_type'] != null) {
+ $result_map[$node['id']] = null;
+ $name_map[$node['id']] = $node['name'];
+ if (empty($node['in_links_ids'])) {
+ $parameters = [];
+ foreach ($node['params_ids'] as $param) {
+ $parameters[$param['param']] = json_decode($param['value']);
+ }
+ $result_map[$node['id']] = eQual::run($node['operation_type'], $node['operation_controller'], $parameters, true);
+ $count++;
+ }
+ }
+}
+
+while ($count != count($result_map)) {
+ foreach ($pipeline_nodes as $node) {
+ if ($node['operation_type'] != null && $result_map[$node['id']] == null) {
+ $is_computable = true;
+ $parameters = [];
+ foreach ($node['in_links_ids'] as $link) {
+ if ($result_map[$link['reference_node_id']] != null) {
+ $parameters[$link['target_param']] = $result_map[$link['reference_node_id']];
+ } else {
+ $is_computable = false;
+ break;
+ }
+ }
+ if ($is_computable) {
+ foreach ($node['params_ids'] as $param) {
+ $parameters[$param['param']] = json_decode($param['value']);
+ }
+ $result_map[$node['id']] = eQual::run($node['operation_type'], $node['operation_controller'], $parameters, true);
+ $count++;
+ }
+ }
+ }
+}
+
+$res = [];
+
+foreach ($name_map as $key => $value) {
+ $res[$value] = $result_map[$key];
+}
+
+$context->httpResponse()
+ ->body($res)
+ ->send();