Skip to content
  • RKGMS
  • SDK GUIDES
  • JavaScript

RelationalAI SDK for JavaScript

This guide presents the main features of the RelationalAI SDK for JavaScript, which can be used to interact with RelationalAI’s Relational Knowledge Graph Management System (RKGMS).

JavaScript logo

The rai-sdk-javascript package is open source and is available in the RelationalAI GitHub repository:


RelationalAI/rai-sdk-javascript

It includes self-contained examples of the main API functionality. Contributions and pull requests are welcome. This guide uses Typescript in the code examples. Additionally, the SDK ships with Typescript types.

The last section describes how the different RAI Console notebook cell types can be implemented with the RelationalAI SDK for Javascript.

Requirements

You can check the rai-sdk-javascript repository for the latest version requirements to interact with the RKGMS using the RelationalAI SDK for JavaScript.

Installation

You can install the RelationalAI SDK for JavaScript through npm as follows:

npm install @relationalai/rai-sdk-javascript

Configuration

The RelationalAI SDK for JavaScript can access your RAI Server credentials using a configuration file. See SDK Configuration for more details.

The rai-sdk-javascript package provides a readConfig helper that reads the configuration file:

import { Client, readConfig } from '@relationalai/rai-sdk-javascript';
 
const config = await readConfig();
const client = new Client(config);

To load a different configuration, you can provide a profile argument when calling readConfig. You can also specify the location of your configuration file through the configPath parameter.

Instead of loading the configuration from a file, you can also build the config object as follows:

import { Client, ClientCredentials } from '@relationalai/rai-sdk-javascript';
 
const credentials = new ClientCredentials(
  'your client_id',
  'your client_secret',
  'https://login.relationalai.com/oauth/token',
);
const config = {
  credentials,
  host: 'azure.relationalai.com',
  scheme: 'https',
  port: '443',
};
const client = new Client(config);
 
const result = await client.listEngines();

Creating a Client

Most API operations use a Client object with the necessary methods for making requests against the RelationalAI REST APIs. To create a client using the default profile in your ~/.rai/config file, you can use:

import { Client, readConfig } from '@relationalai/rai-sdk-javascript';
 
async function run(profile?: string) {
  const config = await readConfig(profile);
  const client = new Client(config);
}
 

You can test your configuration and client by running a small example that lists all the databases. Here is the code:

import { Client, readConfig } from '@relationalai/rai-sdk-javascript';
 
async function run() {
    const config = await readConfig();
    const client = new Client(config);
 
    const result = await client.listDatabases();
 
    console.log(result);
}
 
 
(async () => {
    try {
        await run();
    } catch (error: any) {
        console.error(error.toString());
    }
})();
 

You can create a listDBs.ts file with the code above, or you can directly download it. You can now test whether your configuration works:

tsc listDBs.ts && node listDBs.js

This should print a list with database info, assuming your keys have the corresponding permissions. See Listing Databases below for more information.

The remaining code examples in this document assume that the client variable refers to a valid client. For example:

import { Client, readConfig } from '@relationalai/rai-sdk-javascript';
 
async function run(profile?: string) {
  const config = await readConfig(profile);
  const client = new Client(config);
}

Managing Users

A client with the right permissions can create, disable, and list the users under the account associated with its credentials.

Creating a User

You can create a user as follows:

const result = await client.createUser(email, [role]);

Here, email is a string, identifying the user, and role is a list of UserRole objects, each one representing a role. The value of role should be "user" or "admin". If the role argument is not provided, it defaults to "user".

Deleting a User

You can delete a user with the deleteUser method:

const result = await client.deleteUser(userId);

In this case, userId is a string representing a given user’s ID, as stored within a user object.

Disabling and Enabling a User

You can disable a user through:

const result = await client.disableUser(userId);

Again, userId is a string representing a given user’s ID. You can reenable the user as follows:

const result = await client.enableUser(userId);

Listing Users

You can list users as follows:

const result = await client.listUsers();
 
console.log(result);

Retrieving User Details

You can retrieve user details from the system as follows:

const result = await client.getUser(userId);

Again, userId is a string ID uniquely identifying the user, for example, "auth0|XXXXXXXXXXXXXXXXXX".

Managing OAuth Clients

OAuth clients can be managed with the following functions, provided you have the corresponding permissions:

const result = await client.createOAuthClient(
    name,
    permissions.length > 0 ? permissions : undefined,
);

If permissions are not defined, the same permissions as the creating entity are assigned.

Here name is a string identifying the OAuth client. permissions is a list of permissions from the following supported permissions:

  • create:accesskey
  • create:compute
  • create:oauth_client
  • create:user
  • delete:compute
  • delete:database
  • delete:oauth_client
  • list:accesskey
  • list:compute
  • list:database
  • list:oauth_client
  • list:permission
  • list:role
  • list:user
  • read:compute
  • read:credits_usage
  • read:oauth_client
  • read:role
  • read:user
  • rotate:oauth_client_secret
  • run:transaction
  • update:database
  • update:oauth_client
  • update:user

You can get a list of OAuth clients as follows:

const result = await client.listOAuthClients();

You can get details for a specific OAuth client, identified by the string clientId, as follows:

const result = await client.getOAuthClient(clientId);

This is how to delete the OAuth client identified by the string clientId:

  const result = await client.deleteOAuthClient(clientId);

Each OAuth client has its own set of permissions, which determine the operations it can execute. Depending on the permissions, some operations, such as listing other users or creating or deleting compute engines, may fail. Refer to the RAI Console Managing Users guide for further details.

Managing Engines

To query and update RelationalAI databases, you will need a running engine. The following API calls create and manage them.

Creating an Engine

You can create a new engine as follows. Note that the default size is XS:

const engineName = "js_sdk_engine";
 
const engine = await client.createEngine(engineName);

Or, you can specify the size:

import { EngineSize } from '@relationalai/rai-sdk-javascript';
 
const engineName = "js_sdk_engine";
const size = EngineSize.XS;
 
const engine = await client.createEngine(engineName, size);

API requests return objects. For example, the object returned by client.createEngine looks like this:

{
  account_name: '#####',
  id: '#####',
  name: 'js_sdk_engine',
  size: 'XS',
  region: '#####',
  state: 'REQUESTED',
  created_by: '#####',
  requested_on: '2022-11-03T07:58:32.468Z'
}

Valid sizes are strings and can be any of these values:

  • EngineSize.XS (extra small).
  • EngineSize.S (small).
  • EngineSize.M (medium).
  • EngineSize.L (large).
  • EngineSize.XL (extra large).

Note: It may take some time before your engine is in the PROVISIONED state, where it is ready for queries. It will be in the PROVISIONING state before that.

To list all the engines that are currently provisioned, you can use listEngines:

import { EngineState } from "@relationalai/rai-sdk-javascript";
 
const result = await client.listEngines({ state: EngineState.PROVISIONED });

You can get a list of engines for a set of states as well. You can do this using code similar to the following:

import { EngineState } from '@relationalai/rai-sdk-javascript';
const result = await client.listEngines({
  state: [EngineState.PROVISIONED, EngineState.PROVISIONING],
});

If there is an error with the request, an error message will be returned.

Most of the API examples below assume there is a running engine (in the PROVISIONED state) in the engineName string variable, and a test database in the databaseName string variable:

// replace with your values for testing:
const databaseName = "mydatabase";
const engineName = "myengine";

Deleting an Engine

You can delete an engine with:

const result = await client.deleteEngine(engineName);

If successful, this operation will return the following object:

{
  name: 'js_sdk_engine',
  state: 'DELETING',
  message: 'engine "js_sdk_engine" deleted successfully'
}

Note that since RelationalAI decouples computation from storage, deleting an engine does not delete any cloud databases. See Managing Engines for more details.

Getting Info for an Engine

The details for a specific compute engine can be retrieved with getEngine:

const engine = await client.getEngine(name);

An error will be returned if the engine specified in getEngine does not exist.

Managing Databases

Creating a Database

You can create a database by calling a client object’s createDatabase method, as follows:

const database = await client.createDatabase(databaseName);

The result from a successful createDatabase call will look like this:

{
    id: '#########',
    name: 'mydatabase',
    region: '#########',
    account_name: '#########',
    created_by: '#########',
    deleted_on: null,
    deleted_by: null,
    default_compute_name: null,
    state: 'REQUESTED'
}

Note that the state may be different from REQUESTED (for example, it could be CREATING) depending on the amount of time it takes for the database to be created.

Cloning a Database

You can also use client.createDatabase to clone a database by specifying the name of the database to clone as the second argument:

const originalDatabase = "mydatabase";
const newDatabase = "cloneDB";
 
const result = await client.createDatabase(newDatabase, originalDatabase);

This API call creates a new database called "cloneDB", which is identical to "mydatabase". Any subsequent changes to either database will not affect the other. Cloning a database fails if the source database does not exist.

Retrieving Database Details

You can view the details of a database using client.getDatabase:

const database = await client.getDatabase(name);

The response is an object with information about the database. If the database does not exist, undefined is returned.

Listing Databases

The method client.listDatabases will list the databases available to the account:

const result = await client.listDatabases();
 
console.log(result);

A variation of the listDatabases method takes a parameter state as input that can be used to filter databases by state. Example states are: CREATED, CREATING, or CREATION_FAILED.

import { DatabaseState } from "@relationalai/rai-sdk-javascript";
 
const state = DatabaseState.CREATED;
const result = await client.listDatabases({state});
 
console.log(result);

Deleting Databases

You can delete a database with client.deleteDatabase if the client has the right permissions:

const result = await client.deleteDatabase(databaseName);

If successful, the response will be of the form:

{
    name: '#########',
    message: 'deleted successfully'
}

Deleting a database cannot be undone.

Rel Model Sources

Rel model sources are collections of Rel code that can be added, updated, or deleted from a particular database. Since they update a database, a running engine — and a database — is required to perform operations on sources.

Installing Rel Model Sources

The installModels method of a client object installs a Rel model source in a given database:

const result = await client.installModels(database, engine, [model]);

Here is an example where the model source mymodel is provided as a string:

const databaseName = "mydatabase";
const engineName = "myengine";
 
const model = {
    name : "mymodel",
    value : "def R = \"hello\", \"world\"",
}
 
const result = await client.installModels(databaseName, engineName, [model]);

Here is an example where the model source mymodel is provided through a file called hello.rel:

import { promises } from 'fs';
 
const databaseName = "mydatabase";
const engineName = "myengine";
 
const source = await promises.readFile("hello.rel", 'utf-8');
const model = {
    name: "mymodel",
    value: source,
  };
 
const result = await client.installModels(databaseName, engineName, [model]);

You can also provide an array with a collection of models, together with their names.

Here is an example that loads multiple models at once:

const databaseName = "mydatabase";
const engineName = "myengine";
 
const model1 = {
    name : "model1",
    value : "def R = {1 ; \"hello\"}",
}
const model2 = {
    name : "model2",
    value : "def R = {2 ; \"world\"}",
}
 
const result = await client.installModels(databaseName, engineName, [model1, model2]);

Note that if the database already contains an installed source with the same given name, it is replaced by the new source.

Deleting a Rel Model Source

You can delete a model source from a database using the deleteModels method:

const result = await client.deleteModels(databaseName, engineName, [models]);

In this case, the array models contains the names of the sources to be deleted.

Listing Installed Rel Model Sources

You can list the sources in a database using the following code:

const result = await client.listModels(databaseName, engineName);

To see the contents of a named model source, you can use:

const sourceName = "mymodel";
const result = await client.getModel(databaseName, engineName, sourceName);

Here, sourcename is the name of the model.

Querying a Database

The high-level API method for running a single query/transaction against the database is exec. The function call resolves the promise when the transaction is completed or there are several timeouts indicating that the system may be inaccessible. It is specified as follows:

async exec(
    database: string,
    engine: string,
    queryString: string,
    inputs: QueryInput[] = [],
    readonly = true,
    tags: string[] = [],
    interval = 1000, // 1 second
    timeout = Number.POSITIVE_INFINITY,
)

Queries meant to update base relations with insert and delete must use readonly=false.

Here is an example of exec:

const query = "def output = {1;2;3}";
const readonly = true;
const result = await client.exec(databaseName, engineName, query, [], readonly);

This gives the output:

[{
    relationId: '/:output/Int64',
    table: [1,2,3]
}]

As another example, here is an API call to load some CSV data and store them in the base relation my_base_relation:

import { showTransactionResult } from '/path-to/rai-sdk-javascript/examples/show';
 
const data = `
	name,lastname,id
	John,Smith,1
	Peter,Jones,2
	`
const source = `
	def config:schema:name=\"string\"
	def config:schema:lastname=\"string\"
	def config:schema:id=\"int\"
	def config:syntax:header_row=1
	def config:data = mydata
	def delete[:my_base_relation] = mybase_relation
	def insert[:my_base_relation] = load_csv[config]
	`
 
const readonly = false;
 
const input = {
    name : "mydata",
    value : data.trim()
}
 
const result = await client.exec(databaseName, engineName, source, [input], readonly);
 
showTransactionResult(result);

Note that the previous code uses the showTransactionResult utility function from the examples directory of the rai-sdk-javascript source code. You should change the path to the file accordingly in order to use showTransactionResult. Alternatively, you can omit this function and display the result object directly with console.log(result).

The RelationalAI SDK for JavaScript also supports asynchronous transactions, through execAsync that takes similar parameters to exec. In the case of execAsync the promise is resolved quickly but the transaction may not have completed. In this case, you would need to poll to get the result of the transaction started by execAsync. You can do that using a transaction ID that is included in the response from the execAsync request:

const query = "def output = {1;2;3}";
const readonly = true;
const result = await client.execAsync(databaseName, engineName, query, [], readonly);
 
showTransactionResult(result);

Then, you can poll the transaction until it has completed or aborted. Finally, you can fetch the results:

(async () => {
  const result = await this.execAsync(
    database,
    engine,
    query,
    inputs,
    readonly,
    tags,
  );
 
  const txnId = result.transaction.id;
 
  if ('results' in result) {
    return result;    // transaction completed
  }
 
  // poll transaction
  return await this.pollTransaction(txnId, interval, timeout);
})();

You can get the results of a given transaction as identified by a transaction ID txnId as follows:

const result = await client.getTransactionResults(txnId);

Similarly to getTransactionResults, you can also get metadata and problems for a given transaction ID:

const metadata = await client.getTransactionMetadata(txnId);
const problems = await client.getTransactionProblems(txnId);

The query size is limited to 64MB. An error message will be returned if the request exceeds this API limit.

Getting Multiple Relations Back

As in the RAI Console, if you want to return multiple relations, you can define subrelations of output. For example:

const query = `
	def a = 1;2
	def b = 3;4
	def output:one = a
	def output:two = b
	`;
 
const result = await client.exec(
    database,
    engine,
    query,
    [],
    readonly
);
 
showTransactionResult(result);

It gives this output:

{
  "transaction": {
    "id": "12345678-1234-1234-1234-123456789012",
    "response_format_version": "2.0.4",
    "state": "COMPLETED"
  },
  "problems": []
}
/:output/:one/Int64
{
  "arguments": [
    {
      "tag": 3,
      "primitiveType": 0,
      "constantType": {
        "relType": {
          "tag": 1,
          "primitiveType": 16
        },
        "value": {
          "arguments": [
            {
              "tag": 16,
              "value": {
                "oneofKind": "stringVal",
                "stringVal": {
                  "0": 111,
                  "1": 117,
                  "2": 116,
                  "3": 112,
                  "4": 117,
                  "5": 116
                }
              }
            }
          ]
        }
      }
    },
    {
      "tag": 3,
      "primitiveType": 0,
      "constantType": {
        "relType": {
          "tag": 1,
          "primitiveType": 16
        },
        "value": {
          "arguments": [
            {
              "tag": 16,
              "value": {
                "oneofKind": "stringVal",
                "stringVal": {
                  "0": 111,
                  "1": 110,
                  "2": 101
                }
              }
            }
          ]
        }
      }
    },
    {
      "tag": 1,
      "primitiveType": 2
    }
  ]
}
┌─────────────────┬──────────────┬───────┐
│ String(:output) │ String(:one) │ Int64
├─────────────────┼──────────────┼───────┤
│         :output │         :one │     1
│         :output │         :one │     2
└─────────────────┴──────────────┴───────┘
 
/:output/:two/Int64
{
  "arguments": [
    {
      "tag": 3,
      "primitiveType": 0,
      "constantType": {
        "relType": {
          "tag": 1,
          "primitiveType": 16
        },
        "value": {
          "arguments": [
            {
              "tag": 16,
              "value": {
                "oneofKind": "stringVal",
                "stringVal": {
                  "0": 111,
                  "1": 117,
                  "2": 116,
                  "3": 112,
                  "4": 117,
                  "5": 116
                }
              }
            }
          ]
        }
      }
    },
    {
      "tag": 3,
      "primitiveType": 0,
      "constantType": {
        "relType": {
          "tag": 1,
          "primitiveType": 16
        },
        "value": {
          "arguments": [
            {
              "tag": 16,
              "value": {
                "oneofKind": "stringVal",
                "stringVal": {
                  "0": 116,
                  "1": 119,
                  "2": 111
                }
              }
            }
          ]
        }
      }
    },
    {
      "tag": 1,
      "primitiveType": 2
    }
  ]
}
┌─────────────────┬──────────────┬───────┐
│ String(:output) │ String(:two) │ Int64
├─────────────────┼──────────────┼───────┤
│         :output │         :two │     3
│         :output │         :two │     4
└─────────────────┴──────────────┴───────┘

Result Structure

The response contains the following fields:

FieldMeaning
transactionInformation about transaction status, including identifier.
metadataAn object with metadata about the results and their data type. It is a Protobuf message.
resultsAn array of objects containing the query output information.
problemsInformation about any existing problems in the database — which are not necessarily caused by the query.

Each query is a complete transaction, executed in the context of the provided database.

Problems are also objects with the following fields:

FieldMeaning
error_codeThe type of error that happened. Refer to Error Messages for the full list.
is_errorWhether an error occurred or there was some other problem.
is_exceptionWhether an exception occurred or there was some other problem.
messageA short description of the problem.
pathA file path for the cases when such a path was used.
reportA long description of the problem.
typeThe type of problem, in this case, "ClientProblem".

When an error is an IntegrityConstraintViolation, it is an object with the following fields:

FieldMeaning
sources:rel_keyThe relation key.
sources:sourceThe source that caused the integrity violation.
sources:typeThe type of the integrity violation.
typeThe type of problem, in this case, "IntegrityConstraintViolation".

Specifying Inputs

The exec method implements an optional inputs array that can be used to map relation names to string constants for the duration of the query, analogous to the file upload feature of RAI Console notebooks. For example:

const input = {
    name : "foo",
    value : "asdf"
}
 
const query = "def output = foo";
 
const result = await client.exec(database, engine, query, [input], true);
 
showTransactionResult(result);

This will return the string "asdf" back.

Functions that transform a file and write the results to a base relation can be written like this. The relations load_csv and load_json can be used in this way, via the data parameter, to write results to a base relation. See, for example, the sample code using load_csv in Querying a Database.

Loading Data: LoadCsvAsync and LoadJsonAsync

As a convenience, the RelationalAI SDK for JavaScript includes loadCsv and loadJson member functions in the client class. This is an alternative way to perform loading of data similar to the sample code using loadCSV in Querying a Database.

The JavaScript method loadCsv loads the csv string and inserts the result into the base relation named by the relation argument. Additionally, loadCsv attempts to guess the schema of the data. You can specify the syntax and the schema through the syntax and schema parameters. Alternatively, you can use a non-read-only exec query using the inputs option.

async loadCsv(
    database: string,
    engine: string,
    relation: string,
    csv: string,
    syntax?: CsvConfigSyntax,
    schema?: CsvConfigSchema,
)

The CsvConfigSyntax and CsvConfigSchema classes allow you to specify how to parse a given CSV file. Through a CSVConfigSyntax object you can specify, for example, the delimiter and the escape character of a given file.

Similarly to loadCsv, loadJson loads the csv string as JSON and inserts it into the base relation named by the relation argument:

async loadJson(
    database: string,
    engine: string,
    relation: string,
    json: any,
)

Example:

const result = await client.loadJson(
    database,
    engine,
    relation,
    "myjson",
    "{\"a\" : \"b\"}"
);

Note: In both cases, the relation base relation is not cleared, allowing for multipart, incremental loads. To clear it, you can issue a non-read-only query of the form:

def delete[:relation] = relation

Listing Base Relations

You can list base relations as follows:

const result = await client.listEdbs(databaseName, engineName);

This will list the base relations in the given database. The result is an array of objects.

Transaction Cancelation

You can cancel an ongoing transaction as follows:

const result = await client.cancelTransaction(txnId);

The argument txnId is a string that represents the transaction ID.

Notebook Cell Types

As explained in Working With RAI Notebooks there are four notebook cell types: Query, Install, Update, and Markdown. This section describes how the three cell types Query, Install, and Update map to the RelationalAI SDK for JavaScript API calls.

Query Cells

The Query cell in a RAI Console notebook corresponds to an exec (or execAsync) function call, as shown below:

const query = "def output = {1;2;3}";
const result = await client.exec(database, engine, query, [], true);

Note that no rules/definitions are persisted in this case.

Install Cells

To persist derived relation definitions, you can use the installModels API function of a client object. That is, the Install cell in a RAI Console notebook corresponds to the instalModels JavaScript method, as shown below:

const model = {
    name: "arity1_k_def",
    value: "def k = {2;3;50}",
};
 
const result = await client.installModels(databaseName, engineName, [model]);

You can check that the relation above was persisted using the exec API method:

const result = await client.exec(database, engine, "def output = k", [], true);

Output:

┌─────────────────┬───────┐
│ String(:output) │ Int64
├─────────────────┼───────┤
│         :output │     2
│         :output │     3
│         :output │    50
└─────────────────┴───────┘

Update Cells

The Update cell in a RAI Console notebook corresponds to an exec function call with the parameter readonly=false. For example:

const result = await client.exec(
    database,
    engine,
    "def insert:employee = {(1, \"Han Solo\"); (2, \"Bart Simpson\")}",
    [],
    false
);

You can modify the employee base relation:

const result = await client.exec(
    database,
    engine,
    "def insert:employee = {(3, \"King\"); (4, \"Queen\")}",
    [],
    false
);

You can now query the modified employee base relation:

const result = await client.exec(
    database,
    engine,
    "def output=employee",
    [],
    true
);
 
showTransactionResult(result);
┌─────────────────┬───────┬──────────────┐
│ String(:output) │ Int64 │       String │
├─────────────────┼───────┼──────────────┤
│         :output │     1 │     Han Solo │
│         :output │     2 │ Bart Simpson │
│         :output │     3 │         King │
│         :output │     4 │        Queen │
└─────────────────┴───────┴──────────────┘

To update base relations, always use the readonly=false parameter in the ExecuteWaitAsync call.

Was this doc helpful?