Parameters
Pre-Defined Parameters
Pre-Defined Objects
Concise Syntax
params Function
Implicit Space Parameter
Multiple Values for Parameters
Debug
Debugging THOPs
What's my Query Again?
Use Debug to Troubleshoot THOPs
Testing
Test a THOP from the Command Line
Tips
Dos and Don'ts
THOP Editor Hidden Gems
Unsupported REST Calls
Using .get() on Connection and Other Query Objects
Asynchronous THOPs (2.0)
Calling THOPs from THOPs
Writing Non-Blocking THOP Code
Why Async I/0?
Parameters
The run function of a Thought Process (THOP) takes a single parameter, which is a multi-map of key/value pairs that were specified when running the THOPs 1.0.
function run(p) {...}
Given the following REST call to a THOP:
/proc/find_connections/result?name=Saffron&category=company&category=location
The parameter p will look like this:
{ name: "Saffron", category: [ "company", "location" ] }
'p' is never null, but it might not have a property.
Pre-defined Parameters
The following are reserved parameter names when calling THOPs:
- space
- When set, you can omit the space name when using queries. That is, instead of writing
connections(p.space)
, you can just useconnections().
access_key- The key used to run this THOP.
- signature
- The signature of the call (this is not useful per se; make sure your THOP does not rely on a parameter called signature.
thop.result=raw- Returns the result of a THOP directly, without wrapping it inside a { result: "..." } object.
Pre-defined Objects
- local
- THOPs 1.0 only.
This is an instance of SaffronMemoryBase pointing to the local SaffronMemoryBase system.local.connections(…) -> connections(…)
- query methods in REST API (using local)
- THOPs 1.0 only.
Analogies, attributes, classifications, connections, episodes, information, networks, trends, resources.
- debug
- quote
- This takes a string and returns a quoted string.
All queries also have a .params(..) function that can be used to define query parameters as a JavaScript object:
connections(‘uksecurity’).params({ q: ‘city:london’, c: ‘country’ }).get();
Concise Syntax
For example, var p = params.p ? params.p : 1;
can be written more concisely as follows:
var p = params.p || 1
params Function
You can specify all parameters for a query using the params({...}) function. Note that params({...}) overwrites any parameters set, so use with care.
Compare the following 2 lines of code:
connections(space).q('john').c('person').get()
connections(space).params({ q: 'john', c: 'person' }).get();
Implicit Space Parameter
You can write THOP code that works for any space by using the implicit space parameter.
Example:
return connections().q('person').get();
Note that no space was specified in the connections function. In that case, callers of the THOP are expected to pass the space name in the reserved parameter 'space'.
Multiple Values for Parameters
If a query parameter can be specified multiple times (like c() in a connection query()), you have a few options to specify them:
connections().c('cat1').c('cat2').c('cat3');
Or like this:
connections().c('cat1','cat2','cat3');
If you have the data in an array already, you can use the apply function.
var cats = ['cat1', 'cat2', 'cat3']; var query = connections(); query.c.apply(query, cats);
If you use the param function, you can use an array as well:
connections().params({ c: ['cat1', 'cat2', 'cat3']});
Multiple identical parameters is also supported.
When using the REST API to call a THOP:
c=city&c=country&c=person
it is translated into an array of values. The params argument looks like this:
{ c: [ 'city', 'country', 'person' ] }
JavaScript Environment
THOPs are written in JavaScript code.
The run-time environment is very different to the one available in browsers. There is no window object, for example. In order to make working with JavaScript easy, the following libraries are loaded and made available in the context in which a THOP runs:
- Lo-Dash (http://lodash.com/)
- Contains dozens of useful functions to manipulate arrays, objects, and collections. If a result returned from a Saffron Query API call has a result array by the name of r, it is automatically wrapped using the
_(...)
function. This makes all lo-dash functions available on r itself.
- JSON.js
- Adds the standard JSON.stringify and JSON.parse functions. Return values from Saffron Query API calls are automatically parsed using JSON.parse.
- resty
- A simple JSON-centered HTTP-client which can be used with any Saffron REST service. It has a single function
getJSON(url, accessKey, secretCode)
and returns the content of the URL as string.
- EcmaScript 5 Shims (https://github.com/es-shims/es5-shim)
- THOPs 1.0 Only.
THOPs 1.0 JS code is evaluated in a Rhino container, enhanced with EcmaScript 5 extensions. THOPs 2.0 does not require this because it runs on an ES5 JavaScript engine.
The Rhino environment also allows access to any Java class. This gives a THOP almost complete access to the web service layer in Saffron. Please use this capability wisely.
All global functions and classes are available in the API documentation (http://yourserver:8080/ws/doc)
Debug
Debugging THOPs
THOPBuilder does not include a proper debugger that can step through each line in a THOP. Below are the current available actions.
debug(...)
can be used to add debug information that is returned as part of the result object.
Example:
function run(p) { debug(p); return [{hello: p.name || 'world!'}]; }
When run like this:
curl http://admin:saffron@localhost:8080/ws/rest/proc/helloworld/result?name=Scaramanga
The resulting JSON will look like this:
{"result":"[{\"hello\":\"Scaramanga\"}]","duration":1,"error":null,"debug":"{\"name\":\"Scaramanga\"}"}
Note the debug section!
If you use THOPBuilder in Intellij, run the THOP as Debug to see the Debug output:
-- Running helloworld -- Debug output: {"name":"Scaramanga","access_key":"demo"} -- Result: helloworld/name=Scaramanga [ {"hello": "Scaramanga" } ]
Tip
If you add parameter log=http
when calling a THOP, the URL called as well as the result will be added to the debug output (make sure you run your THOP using the Debug action in Intellij).
What's My Query Again?
If you are unsure what your current query looks like (before running .get() or .getRaw()), you can use .uri() to look at the actual URL generated. You can use that with debug() to add debug output that will be returned to you in the 'debug' property of the result object.
Here's an example:
function run(p) { debug(connections('jirahadoop').q('test').c('issue').uri()); return {}; }
returns this:
{"result":"{}","duration":3,"error":null,"debug":"http://localhost:8080/ws/spaces/jirahadoop/connections?q=test&c=issue&"}
Use Debug to Troubleshoot THOPS
Since there is no ordinary debugger available for troubleshooting Thought Processes, you can use the debug(...)
method to add debug output alongside the result of your THOP. Debug(...) will automatically convert any object given to JSON. As seen in the first example, the debug output is delivered alongside the result.
If an error occurs when calling a Saffron API, the error message might contain a URL. Open the URL to learn about any errors that occurred when making the call.
Testing
Test a THOP From the Command Line
Below is the simplest way to test a THOP from the command line. An access key is still needed for the moment, but no encoding of the URL is necessary.
curl'http://admin:saffron@localhost:8080/ws/rest/proc/test/result?access_key=demo'
Tips
Dos and Don'ts
Do
- Use debug(...) to help with troubleshooting.
- debug(yourQuery.uri()) will show you the URL used. Use the test harness to run
- Guard against invalid parameters. Anyone with a THOP account
Do not
- Use global variables
The behavior of global variables is undefined. In the current THOPs 1.0 implementation, each THOP has its own instance of the Rhino JavaScript Engine. So, invoking the same THOP in parallel runs in that single engine. If your THOP has global variables, changes to the variables will be visible to all invocations.
- Be careful when using Java classes, such as Packages.java.lang.System.exit(0);
The editor that is used for THOPs contains some hidden gems:
- Alt-F
- Search for keywords (with regexp etc).
- 2xAlt-F
- Search and replace.
Unsupported REST Calls
You can use the THOPs API to make any kind of call to the SaffronMemoryBase REST API if it starts with the ws/spaces/<spacename> prefix by using the space(name, 'path')
function.
Example:
function run(p) { var resource = space('uksecurity','resources').params({rr: 'resourceref' }).get(); }
Using .get() on Connection and Other Query Objects
Please keep in mind that calling .get()
will always make a call to SaffronMemoryBase. This means:
- Hold on to the result of a
.get()
operation if you want to make calls to it repeatedly. - You can re-use connection objects.
Asynchronous THOPs (2.0)
Calling THOPs from THOPs
You can call other THOPs from a THOP by using the thop.runAsync
variable. You can call other THOPs asynchronously and collect the results.
Example:
function runAsync(p, cb) { Promise.all([ thop.runAsync('test-thop', {name:'Darth Vader'}), thop.runAsync('test-thop', {name:'Luke Skywalker'}), thop.runAsync('test-thop', {name:'Walter White'}) ]).then(function(results) { debug(results); cb(results); }); }
The THOP test-thop
can be a synchronous or asynchronous THOP.
In this example, it is simply:
function run(p) { console.log("Console output from within THOP"); debug('name:' + p.name); return "Hello " + (p.name || 'world!'); }
Here is the output when run from THOP builder:
-- Running test-subcall -- Debug output: Output from running test-thop name:Walter White Output from running test-thop name:Darth Vader Output from running test-thop name:Luke Skywalker ["Hello Darth Vader","Hello Luke Skywalker","Hello Walter White"] -- Result: test-subcall/ ["Hello Darth Vader","Hello Luke Skywalker","Hello Walter White” ]
Writing Non-Blocking THOP Code
Here is a simple example of a THOP that uses async I/O versus the current blocking I/O:
function runAsync(p, result) { analogies('uksecurity').q('city:london').a('city:london').rtr(true).async() .then(function (data) { result(data) }); }
Three things are different here:
- The main function needs to be called
runAsync
. - The second parameter of that function is a callback function that the THOP needs to call with the final result.
get()
andgetRaw()
will return right away with a Promise object.
Important: get()
will not give you the result of your query, but will return immediately with a promise that the actual result will be delivered (or fail) in the future.
A Promise object has two interesting functions then(...)
and catch(...)
. Any function you pass to then
will be called with the result of the REST call.
In the example above, the result of analogies call is then returned as a result of the THOP.
Why Async I/O?
Here is an example that shows how we can make better use of SaffronMemoryBase resources and do things in parallel without actually having to resort to multi-threaded programming.
Keep in mind that THOPs run single-threaded, much like JavaScript runs in the browser.
We can still run things concurrently as shown in this example:
function runAsync(p, result) { var p1 = connections('uksecurity').q('london’).async(); var p2 = connections('uksecurity').q('moscow’).async(); Promise.all([p1,p2]).then(function (results) { results[0].r.push(results[1].r); result(results[0].r.value()); }).catch(function (error){ console.log("Oh my"); console.log(error.message); }); }
This example runs two connection queries, combines the results and returns it, but each connection query is run asynchronously. The requests are being run in parallel as the underlying engine will make two HTTP requests as fast as it can, then wait for I/O.
When the requests are complete, the promise objects p1
and p2
become fulfilled in unspecified order and at an unspecified time.
Using Promise.all
, we register a function that is being called when both promises have become fulfilled, returning their results in results
in the same order that we used in the Promise.all
call.
This way we can run any number of queries in parallel and wait for their 'fulfillment' before doing the next step in our processing.