EliasDB Event Condition Action Language
=======================================

EliasDB supports a scripting language called [Event Condition Action Language (ECAL)](https://devt.de/krotik/ecal/) to enable rule based scripting functionality. ECAL provides [database trigger](https://en.wikipedia.org/wiki/Database_trigger) functionality for EliasDB.

ECAL was added for the following use-cases:
- Providing a way to manipulate data in response to events
- Enforce certain aspects of a database schema
- Providing back-end logic for web applications using EliasDB

ECAL related config values:
--
| Configuration Option | Description |
| --- | --- |
| EnableECALScripts | Enable ECAL scripting. |
| ECALScriptFolder | Scripting folder for ECAL scripts. |
| ECALEntryScript | Entry script in the script folder. |
| ECALLogFile | File in which the logs should be written (use an empty string for stdout). |
| ECALLogLevel | Log level for the printed logs. |
| EnableECALDebugServer | Start an ECAL debug server. |
| ECALDebugServerHost | Host for the debug server. |
| ECALDebugServerPort | Port for the debug server. |

ECAL Debugging
--
If the debug server is enabled in the config file then it is possible to debug ECAL scripts with [VSCode](https://devt.de/krotik/ecal/src/master/ecal-support/README.md). The debugger supports break points and thread state inspection. It is also possible to restart and reload the scripts.

Using the `-ecal-console` parameter it is possible to open an interactive console into the server process. If used together with the debug server additional debug commands are available also there. Enter `?` to see the build-in documentation.

EliasDB specific events which can be handled:
--
The ECAL interpreter in EliasDB receives the following events:

Web Request | ECAL event kind | Event state contents | Description
-|-|-|-
/db/api/|`db.web.api`| bodyJSON, bodyString, header, method, path, pathList, query | Any web request to /db/api/*. These endpoints are public and never require authentication.
/db/ecal/|`db.web.ecal`| bodyJSON, bodyString, header, method, path, pathList, query | Any web request to /db/ecal/*. These endpoints are considered internal and require authentication if access control is enabled.

EliasDB Graph Event | ECAL event kind | Event state contents | Description
-|-|-|-
graph.EventNodeCreated | `db.node.created` | part, trans, node | A node was created.
graph.EventNodeUpdated | `db.node.updated` | part, trans, node, old_node | A node was updated.
graph.EventNodeDeleted | `db.node.deleted` | part, trans, node | A node was deleted.
graph.EventEdgeCreated | `db.edge.created` | part, trans, edge | An edge was created.
graph.EventEdgeUpdated | `db.edge.updated` | part, trans, edge, old_edge | An edge was updated.
graph.EventEdgeDeleted | `db.edge.deleted` | part, trans, edge | An edge was deleted.
graph.EventNodeStore | `db.node.store` | part, trans, node | A node is about to be stored (always overwriting existing values).
graph.EventNodeUpdate | `db.node.update` | part, trans, node | A node is about to be updated.
graph.EventNodeDelete | `db.node.delete` | part, trans, key, kind | A node is about to be deleted.
graph.EventEdgeStore | `db.edge.store` | part, trans, edge | An edge is about to be stored.
graph.EventEdgeDelete | `db.edge.delete` | part, trans, key, kind | An edge is about to be deleted.

Note: EliasDB will wait for the event cascade to be finished before performing the actual operation (e.g. inserting a node). If the event handling requires a time consuming operation then a new parallel event cascade can be started using `addEvent` with a scope:
```
addEvent("request", "foo.bar.xxx", {
   "payload" : 123
}, {
   "": true  # This scope allows all events
})
```

EliasDB specific functions:
--
The ECAL interpreter in EliasDB supports the following EliasDB specific functions:

#### `db.storeNode(partition, nodeMap, [transaction])`
Inserts or updates a node in EliasDB.

Parameter | Description
-|-
partition | Partition of the node
nodeMap | Node object as a map with at least a key and a kind attribute
transaction | Optional a transaction to group a set of changes

Example:
```
db.storeNode("main", {
  "key" : "foo",
  "kind" : "bar",
  "data" : 123,
})
```

#### `db.updateNode(partition, nodeMap, [transaction])`
Updates a node in EliasDB (only update the given values of the node).

Parameter | Description
-|-
partition | Partition of the node
nodeMap | Node object as a map with at least a key and a kind attribute
transaction | Optional a transaction to group a set of changes

Example:
```
db.updateNode("main", {
  "key" : "foo",
  "kind" : "bar",
  "data" : 123,
})
```

#### `db.removeNode(partition, nodeKey, nodeKind, [transaction])`
Removes a node in EliasDB.

Parameter | Description
-|-
partition | Partition of the node
nodeKey | Key attribute of the node to remove
nodeKind | Kind attribute of the node to remove
transaction | Optional a transaction to group a set of changes

Example:
```
db.removeNode("main", "foo", "bar")
```

#### `db.fetchNode(partition, nodeKey, nodeKind)`
Fetches a node in EliasDB.

Parameter | Description
-|-
partition | Partition of the node
nodeKey | Key attribute of the node to fetch
nodeKind | Kind attribute of the node to fetch

Example:
```
db.fetchNode("main", "foo", "bar")
```

#### `db.storeEdge(partition, edgeMap, [transaction])`
Inserts or updates an edge in EliasDB.

Parameter | Description
-|-
partition | Partition of the edge
edgeMap | Edge object as a map with at least the main attributes: key, kind, end1cascading, end1key, end1kind, end1role, end2cascading, end2key, end2kind, end2role
transaction | Optional a transaction to group a set of changes

Example:
```
db.storeEdge("main", {
  "key":           "123",
  "kind":          "myedges",
  "end1cascading": true,
  "end1key":       "foo",
  "end1kind":      "bar",
  "end1role":      "role1",
  "end2cascading": false,
  "end2key":       "key2",
  "end2kind":      "kind2",
  "end2role":      "role2",
})
```

#### `db.removeEdge(partition, edgeKey, edgeKind, [transaction])`
Removes an edge in EliasDB.

Parameter | Description
-|-
partition | Partition of the edge
edgeKey | Key attribute of the edge to remove
edgeKind | Kind attribute of the edge to remove
transaction | Optional a transaction to group a set of changes

Example:
```
db.removeEdge("main", "123", "myedges")
```

#### `db.fetchEdge(partition, edgeKey, edgeKind)`
Fetches an edge in EliasDB.

Parameter | Description
-|-
partition | Partition of the edge
edgeKey | Key attribute of the edge to fetch
edgeKind | Kind attribute of the edge to fetch

Example:
```
db.fetchEdge("main", "123", "myedges")
```

#### `db.traverse(partition, nodeKey, nodeKind, traversalSpec)`
Traverses an edge in EliasDB from a given node. Returns a list of nodes which were
reached and a list of edges which were followed.

Parameter | Description
-|-
partition | Partition of the node
nodeKey | Key attribute of the node to traverse from
nodeKind | Kind attribute of the node to traverse from
traversalSpec | Traversal spec

Example:
```
[nodes, edges] := db.traverse("main", "foo", "bar", "role1:myedges:role2:kind2")
```

#### `db.newTrans()`
Creates a new transaction for EliasDB.

Example:
```
trans := db.newTrans()
```

#### `db.newRollingTrans(n)`
Creates a new rolling transaction for EliasDB. A rolling transaction commits after n entries.

Parameter | Description
-|-
n | Rolling threshold (number of operations before rolling)

Example:
```
trans := db.newRollingTrans(5)
```

#### `db.commit(transaction)`
Commits an existing transaction for EliasDB.

Parameter | Description
-|-
transaction | Transaction to execute

Example:
```
db.commit(trans)
```

#### `db.query(partition, query)`
Run an EQL query.

Parameter | Description
-|-
partition | Partition to query
query | Query to execute

Example:
```
db.commit("main", "get bar")
```

#### `db.graphQL(partition, query, [variables], [operationName])`
Run a GraphQL query.

Parameter | Description
-|-
partition | Partition to query
query | Query to execute
variables | Map of variables for the query
operationName | Operation to execute (useful if the query defines more than a single operation)

Example:
```
db.graphQL("main", "query myquery($x: string) { bar(key:$x) { data }}", {
  "x": "foo",  
}, "myquery")
```

#### `db.raiseGraphEventHandled()`
When handling a graph event, notify the GraphManager of EliasDB that no further action is necessary.

Example:
```
sink mysink
  kindmatch [ "db.*.*" ],
{
  db.raiseGraphEventHandled()
}
```

#### `db.raiseWebEventHandled()`
"When handling a web event, notify the web API of EliasDB that the web request was handled.

Example:
```
sink mysink
  kindmatch [ "web.*.*" ],
{
  db.raiseWebEventHandled({
    "status" : 200,
    "headers" : {
      "Date": "today"
    },
    "body" : {
      "mydata" : [1,2,3]
    }
  })
}
```
