Logo

12053488
Statements

169684
Actors

1087718
Activities

3274
LRSs

Back to TOC

User Manual

Saved Scripts

The saved script feature can be used to store scripts in the VQL or JavaScript format. These scripts can be attached to the read or write functions of an access key, or the outgoing data that is sent via a forwarding rule. In the future, more areas of the xAPI data flow will support scripts.

Scripting allow you fine grained control of what data will be sent to an upstream, or what data can be read or written by an access key. In addition to filtering the dataset to remove items, you can also transform the statements.

Whether written in JS or VQL, the script must return an array of valid xAPI statements, or an empty array. In VQL mode, each xAPI statement is process through the VQL pipeline and the results collected into an array, so the array return type is implicit. However, you must ensure that the logic in the query does not modify the incoming data such that it is no longer valid xAPI.

Whenever a script generates an error, the input array is passed without modification. This means that an error in your script will never cause data loss. However, if you are relying on a script to anonymize or control data access, a script error might expose data. Script errors can be reviewed in the Logs section of the LRS.

VQL Mode

VQL mode is preferable because it's performance is 4 times greater than JS mode.

The below example filters the data. When statements are received, if they are any "actor.name" other than "Rob C", they will pass, either to the upstream, to the database (when attached to the write command on an access key), or the the API results (when attached to 'read');

{
    filter:{
        "actor.name":{
            $ne:"Rob C"
        }
    },
    process:[
    ]
}

You can also append additional data. This adds an extension to all statements. Note that we must manually escape this extension name

{
    filter:{
    
    },
    process:[
        {
            $addFields:{
                "context.extensions.http://myExtension*`*com":"Some Value"
            }
        }
    ]
}

You can reference existing values, and modify them with expressions. This script makes the actor anonymous in a deterministic way.

{
    filter:{
    
    },
    process:[
        {
            $addFields:{
                "actor":{
                    "account":{
                        "homePage":"http://anonymous.com",
                        "name":{
                            $hash:"$actor"
                        }
                    }
                }
            }
        }
    ]
}

You can use a switch expression to conditionally apply some transformation. This script makes the actor anonymous when the actor.name property is "Rob C". Otherwise, the actor is left as is.

{
    filter:{
    
    },
    "process":[
        {
            "$addFields":{
                "actor":{
                    "$switch":{
                        "branches": [
                            { 
                                "case": { $eq: ["$actor.name", "Rob C"] }, then: { "account":{
                                    "homePage":"http://anonymous.com",
                                    "name":{
                                        $hash:"$actor"
                                    }
                                }}
                            },
                        ],
                        "default": "$actor"
                    }
                }
            }
        }
    ]
}

Javascript Mode

Some logic can be difficult to express in VQL. You may write imperative JavaScript. JavaScript executed by our servers runs in a sandbox JavaScript interpreter. Because of this important security consideration, execution can have low performance. If performance is a concern, rewrite your logic in VQL. The JS environment has no access to any global state, npm modules, external scripts, HTTP, DOM or any other resources - it can only be used to express logic over strings, objects and numbers.

When writing JS scripts, there are 2 special case variables to be aware of

  • $input - this is the array of input statements
  • $output - this is the resulting array. You must populate this with an array value

We do expose a very limited set of functions to the interpreter for your convenance. You can use normal JS language features like Math.random or Array.isArray

  • $hash - compute a hash from any JS object
  • $uuid - generate a random UUID

The below example filters the data. When statements are received, if they are any "actor.name" other than "Rob C", they will pass, either to the upstream, to the database (when attached to the write command on an access key), or the the API results (when attached to 'read');

{
    let out = [];
    for(let i in $input)
    {
        if($input[i].actor.name !== "Rob C")
        {
            out.push($input[i])
        }
    }
    $output = out;
}

You can also append additional data. This adds an extension to all statements. Note that we must manually escape this extension name, and that for simplicity's sake, this example destroys the existing extensions.

{
    let out = [];
    for(let i in $input)
    {
        $input[i].context = {
            extensions: {
                "http://myExtension*`*com" : "Some Value"
            }
        }
        out.push($input[i])
    }
    $output = out;
}

You can reference existing values, and modify them with expressions. This script makes the actor anonymous in a deterministic way.

{
    let out = [];
    for(let i in $input)
    {
        $input[i].actor = {
           account:{
               homePage:"http://anonymous.com",
               name: $hash($input[i].actor)
           }
        }
        out.push($input[i])
    }
    $output = out;
}