Skip to content
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 System (RKGS).

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 (opens in a new tab) 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.

Requirements

You can check the rai-sdk-javascript (opens in a new tab) repository for the latest version requirements to interact with the RKGS 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 (opens in a new tab) 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

This section covers the API functions that you need to manage users.

🔎

Each user has a role associated with specific permissions. These permissions determine the operations that the user can execute. See User Roles in the Managing Users and OAuth Clients guide for more details.

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".

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);

Getting Information for a User

You can get information for a user as follows:

const result = await client.getUser(userId);

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

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 (opens in a new tab) object.

Managing OAuth Clients

This section covers the API functions that you need to manage OAuth clients.

🔎

Each OAuth client has a specific set of permissions. These permissions determine the operations that the OAuth client can execute. See [Permissions for OAuth Clients](/rkgms/console/user-management(#permissions-for-oauth-clients) in the Managing Users and OAuth Clients guide for more details.

Creating an OAuth Client

You can create an OAuth client as follows:

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:engine
  • create:oauth_client
  • create:user
  • delete:engine
  • delete:database
  • delete:oauth_client
  • list:accesskey
  • list:engine
  • list:database
  • list:oauth_client
  • list:permission
  • list:role
  • list:user
  • read:engine
  • read:credits_usage
  • read:oauth_client
  • read:role
  • read:user
  • rotate:oauth_client_secret
  • run:transaction
  • update:database
  • update:oauth_client
  • update:user

Listing OAuth Clients

You can get a list of OAuth clients as follows:

const result = await client.listOAuthClients();

Getting Information for an OAuth Client

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

const result = await client.getOAuthClient(clientId);

Deleting an OAuth Client

You can delete an OAuth client identified by the string clientId as follows:

  const result = await client.deleteOAuthClient(clientId);

Managing Engines

This section covers the API methods you need to use to manage engines.

Creating an Engine

You can create a new engine as follows:

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

By default, the size is XS. You can create an engine of a different size by specifying the size parameter:

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

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).
💡

Your engine may take some time to reach the “PROVISIONED” state, where it is ready for queries. It is in the “PROVISIONING” state until then.

Listing Engines

You can list all currently provisioned engines as follows:

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

This returns a JSON array containing details for each engine:

[
    {
        "id": "ca******",
        "name": "my_engine",
        "region": "us-east",
        "account_name": "******",
        "created_by": "******",
        "created_on": "2023-07-10T17:15:22.000Z",
        "size": "S",
        "state": "PROVISIONED"
    }
]

You can also get a list of engines for a set of states:

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

Possible states are:

  • "REQUESTED".
  • "PROVISIONING".
  • "PROVISIONED".
  • "DEPROVISIONING".

Getting Information for an Engine

You can get information for a specific engine as follows:

const engineName = "my_engine";
const engine = await client.getEngine(engineName);

This gives you the following output:

{
    "id": "******",
    "name": "my_engine",
    "region": "us-east",
    "account_name": "******",
    "created_by": "******",
    "created_on": "2023-07-10T17:15:22.000Z",
    "size": "S",
    "state": "PROVISIONED"
}

An error is returned if the engine does not exist.

Deleting an Engine

You can delete an engine with:

const engineName = "my_engine";
const result = await client.deleteEngine(engineName);

If successful, this is the output:

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

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

Managing Databases

This section covers the API methods you need to use to manage databases.

Creating a Database

You can create a database as follows:

const databaseName = "my_database";
const database = await client.createDatabase(databaseName);

The result from a successful createDatabase call looks like this:

{
    "id": "******",
    "name": "my_database",
    "region": "us-east",
    "account_name": "******",
    "created_by": "******",
    "created_on": "2023-07-10T17:15:22.000Z",
    "state": "CREATED"
}

Cloning a Database

You can clone a database by specifying the target and source databases:

const sourceDatabase = "my_database";
const targetDatabase = "my_clone_database";
 
const result = await client.createDatabase(targetDatabase, sourceDatabase);

Any subsequent changes to either database will not affect the other. Cloning a database fails if the source database does not exist.

You cannot clone from a database until an engine has executed at least one transaction on that database.

Listing Databases

You can list the available databases associated with your account as follows:

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

This returns a JSON array containing details for each database:

[
    {
        "id": "******",
        "name": "my_database",
        "region": "us-east",
        "account_name": "******",
        "created_by": "******",
        "created_on": "2023-07-20T08:03:03.616Z",
        "state": "CREATED"
    }
]

To filter databases by state, you can use the state parameter. For instance:

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

Possible states are:

  • "CREATED".
  • "CREATING".
  • "CREATION_FAILED".
  • "DELETED".

Getting Information for a Database

You can get information for a specific database as follows:

const databaseName = "my_database";
const database = await client.getDatabase(databaseName);

It gives this output:

{
    "id": "******",
    "name": "my_database",
    "region": "us-east",
    "account_name": "******",
    "created_by": "******",
    "created_on": "2023-07-20T08:03:03.616Z",
    "state": "CREATED"
}

If the database does not exist, an undefined error is returned.

Deleting a Database

You can delete a database with:

const databaseName = "my_database";
const result = await client.deleteDatabase(databaseName);

If successful, this is the output:

{"name": "my_database", "message": "deleted successfully"}

Deleting a database cannot be undone.

🔎

The remaining code examples in this guide assume that you have a running engine in engineName and a database in databaseName.

Managing Rel Models

This section covers the API methods you can use to manage Rel models.

Rel models are collections of Rel code that can be added, updated, or deleted from a dedicated database. A running engine — and a database — is required to perform operations on models.

Loading a Model

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

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

Here’s an example where the model my_model is provided as a string:

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

Here’s an example where the model my_model is provided through a file called hello.rel:

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

Loading Multiple Models

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

Here’s an example that loads multiple models at once:

const databaseName = "my_database";
const engineName = "my_engine";
 
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 model with the same given name, it is replaced by the new one.

Listing Models

You can list the models in a database as follows:

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

This returns a JSON array of names:

[
  "rel/alglib",
  "rel/display",
  "rel/graph-basics",
  "rel/graph-centrality",
  "rel/graph-components",
  "rel/graph-degree",
  "rel/graph-measures",
  "rel/graph-paths",
  "rel/histogram",
  "rel/intrinsics",
  "rel/mathopt",
  "rel/mirror",
  "rel/net",
  "rel/stdlib",
  "rel/vega",
  "rel/vegalite"
]

In the example above, you can see all the built-in models associated with a database.

Getting Information for a Model

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

const modelName = "my_model";
const result = await client.getModel(databaseName, engineName, modelName);

Here, modelName is the name of the model. This gives the following output:

{
  "name": "my_model",
  "value": "def my_range(x) = range(1, 10, 1, x)"
}

In the example above, my_model defines a specific range.

Deleting Models

You can delete a model 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 model or models to be deleted.

Querying a Database

The API function for executing queries against the database is exec. It is a synchronous function, meaning that the running code is blocked until the transaction is completed or there are several timeouts indicating that the system may be inaccessible. Each query is a complete transaction, executed in the context of the provided database.

The exec function 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,
)

Here’s an example of a read query using 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]
}

Write queries, which update base relations through the control relations insert and delete, must use readonly=false.

Here’s 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 query = `
	def config:schema:name=\"string\"
	def config:schema:lastname=\"string\"
	def config:schema:id=\"int\"
	def config:syntax:header_row=1
	def config:data = my_data
	def delete[:my_base_relation] = my_baserelation
	def insert[:my_base_relation] = load_csv[config]
	`
 
const readonly = false;
 
const input = {
    name : "my_data",
    value : data.trim()
}
 
const result = await client.exec(databaseName, engineName, query, [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

In order 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 keys:

FieldMeaning
TransactionInformation about the transaction status, including the identifier.
MetadataMetadata information about the results key.
ResultsQuery output information.
ProblemsInformation about any existing problems in the database — which are not necessarily caused by the query.

Transaction

The transaction key is a JSON string with the following fields:

FieldMeaning
IDTransaction identifier.
StateTransaction state. See Transaction States for more details.

For example:

{
    "id": "******",
    "state": "COMPLETED"
}

Metadata

The metadata key is a JSON string with the following fields:

FieldMeaning
Relation IDThis is a relation identifier, for example, "/:output/:two/Int64". It describes the relation name /:output/:two followed by its data schema Int64.
TypesThis is a JSON array that contains the key names of the relation and their data type.

For example:

{
    relation_id {
        arguments {
            tag: CONSTANT_TYPE
            constant_type {
                rel_type {
                    tag: PRIMITIVE_TYPE
                    primitive_type: STRING
                }
                value {
                    arguments {
                        tag: STRING
                        string_val: "output"
                    }
                }
            }
        }
        arguments {
            tag: PRIMITIVE_TYPE
            primitive_type: INT_64
        }
    }
}

Results

The results key is a vector with the following fields:

FieldMeaning
Relation IDThis is a key for the relation, for example, "v1". It refers to the column name in the Arrow table that contains the data, where "v" stands for variable, since a relation’s tuples contain several variables.
TableThis contains the results of the query in a JSON-array format.

For example:

v1: [[1,2,3]]

Problems

The problems key is a JSON string with the following fields:

FieldMeaning
error_codeThe type of error that occurred, for example, "PARSE_ERROR".
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, for example, "ClientProblem".

For example:

{
    'is_error': True, 
    'error_code': 'PARSE_ERROR', 
    'path': '', 
    'report': '1| def output = {1; 2; 3\n  ^~~~~~~~\n', 'message': 'Missing closing `}`.', 
    'is_exception': False, 
    'type': 'ClientProblem'
}

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. 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

The loadCsv()​ and loadJson()​ functions allow you to load data into a database. These are not strictly necessary, since the Rel load utilities can also be used for this task. See the CSV Import and JSON Import guides for more details.

💡

It’s advisable to load data using built-in Rel utilities within queries, rather than these specific SDK functions. See Querying a Database for more details.

Loading CSV Data

The loadCsv() function loads CSV data and inserts the result into the base relation named by the relation argument.

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

Here’s an example:

const file = await promises.readFile(filePath, 'utf-8');
const csv = JSON.parse(file);
 
const result = await client.loadCsv(
    database,
    engine,
    "my_csv",
    csv
);

By default, loadCsv() attempts to infer the schema of the data. The CsvConfigSyntax (opens in a new tab) and CsvConfigSchema (opens in a new tab) classes allow you to specify how to parse a given CSV file, including the schema, delimiters, and escape characters, through the use of the syntax and schema arguments. See this example (opens in a new tab) for more details.

Loading JSON Data

The loadJson() function loads JSON data and inserts them into the base relation named by the relation argument:

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

Here’s an example:

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

In both the LoadCsvAsync() and LoadJsonAsync() methods, the base relation relation is not cleared, allowing for multipart, incremental loads.

You can clear a base relation, such as my_base_relation, as follows:

const query = "def delete[:my_base_relation] = my_base_relation";
const result = await client.exec(databaseName, engineName, query, [], false);

Listing Base Relations

You can list the base relations in a given database as follows:

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

The result is a JSON list of objects.

Managing Transactions

This section covers the API functions you can use to manage transactions.

Listing Transactions

You can list the transactions in your context ctx as follows:

const result = await client.listTransactions();

You can also filter results by using the optional argument options. For instance:

const options = {
    engine_name: 'my_engine',
    tags: ['tag1', 'tag2'],
};
 
const result = await client.listTransactions(options);

This argument is a TransactionListOptions (opens in a new tab) object that contains a string array. The array may include database and engine names, transaction states, and a comma-separated list of tags.

Canceling Transactions

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. For instance, result.transaction.id from a previous exec API call.

Was this doc helpful?