@tragedy-labs/sprite
Version:
A TypeScript driver for ArcadeDB
329 lines • 14.8 kB
JavaScript
import { Rest } from '../rest/Rest.js';
import { Routes } from './routes.js';
import { SpriteDatabase } from '../database/SpriteDatabase.js';
import { ArcadeValidation } from '../validation/ArcadeValidation.js';
import { CLOSE_DATABASE, CONNECT_CLUSTER, CREATE_DATABASE, CREATE_USER, DISCONNECT_CLUSTER, DROP_DATABASE, DROP_USER, GET_SERVER_EVENTS, OPEN_DATABASE, SHUTDOWN } from './commands/index.js';
import { ArcadeFetchError } from '../errors/ArcadeFetchError.js';
var CommandResponse;
(function (CommandResponse) {
CommandResponse["OK"] = "ok";
})(CommandResponse || (CommandResponse = {}));
/**
* Static methods to interact with an ArcadeDB server.
*/
class Server {
/**
* Returns a `boolean` value indicating if the ArcadeDB server is ready.\
* Useful for remote monitoring of server readiness.
* @param session The session to use to check the server status.
* @returns `true` if the server is ready, otherwise `false`.
* @throws `Error` if the server status could not be checked.
*/
static serverReady = async (session) => {
try {
const response = await Rest.get(Routes.READY, session);
return response.status === 204;
}
catch (error) {
throw new Error('Unable to check the server status.', { cause: error });
}
};
/**
* Close a database on the ArcadeDB server.
* @param session The session to use to close the database.
* @param databaseName The name of the database to close.
* @returns The response from the server.
* @throws `Error` if the database could not be closed.
*/
static closeDatabase = async (session, databaseName) => {
try {
return await this._booleanCommand(session, CLOSE_DATABASE(databaseName));
}
catch (error) {
throw new Error(`Unabled to close database "${databaseName}`, {
cause: error
});
}
};
/**
* Open a database on the ArcadeDB server
* @param session The session to use to open the database.
* @param databaseName The name of the database to open.
* @returns `true` if the database was opened.
* @throws `Error` if the database could not be opened.
*/
static openDatabase = async (session, databaseName) => {
try {
return await this._booleanCommand(session, OPEN_DATABASE(databaseName));
}
catch (error) {
throw new Error(`Unabled to open database "${databaseName}`, {
cause: error
});
}
};
/**
* Returns an `SpriteDatabase` instance for the supplied `databaseName`,
* using the authorization details of the `Session` instance.
* @param session The session to use to create the database client.
* @param databaseName The name of the database to create a client for.
* @returns An instance of `SpriteDatabase`.
* @throws `Error` if the database could not be created.
*/
static database = (session, databaseName) => new SpriteDatabase({ session: session, databaseName });
/**
* Sends a command to the ArcadeDB server and returns the response.
* @param session The session to use to send the command.
* @param command The command to send to the server.
* @returns The response from the server.
* @throws `Error` if the command could not be executed.
*/
static command = async (session, command) => {
try {
return await Rest.postJson(Routes.COMMAND, JSON.stringify({
command
}), session);
}
catch (error) {
throw new Error(`There was an error when attemping to execute a command on the ArcadeDB Server. See aggregate error for details.`, { cause: error });
}
};
/**
* Internal method for sending commands to the server in which a JSON response
* containing an `ok` value in the `result` property is expected. `ok` is then
* returned as a simple boolean (`true`) value
* @param session The session to use to send the command.
* @param command The [command](https://docs.arcadedb.com/#HTTP-ServerCommand) to send to the server, such as `CREATE DATABASE`.
* @returns `true` if the command was successful.
* @throws `Error` if the command could not be executed.
*/
static _booleanCommand = async (session, command) => {
const response = await this.command(session, command);
if (response === CommandResponse.OK) {
return true;
}
else {
return false;
}
};
/**
* Connects this server to a cluster with `address`.
* @param session The session to use to connect to the cluster.
* @param address The address of the cluster to connect (i.e. 192.168.0.1)
* @returns The response from the server.
* @throws `Error` if the cluster could not be connected.
*/
static connectCluster = async (session, address) => {
try {
return await this._booleanCommand(session, CONNECT_CLUSTER(address));
}
catch (error) {
throw new Error(`There was an error attempting to connect cluster at: ${address}`, { cause: error });
}
};
/**
* Create a database
* @param session The session to use to create the database.
* @param databaseName The name of the database to create.
* @returns An instance of `SpriteDatabase`, targeting the created database.
* @throws `Error` if the database could not be created.
*/
static createDatabase = async (session, databaseName) => {
try {
ArcadeValidation.databaseName(databaseName);
const created = await this._booleanCommand(session, CREATE_DATABASE(databaseName));
if (created) {
return this.database(session, databaseName);
}
else {
throw new Error(`Received an unexpected response from the server when attempting to create database "${databaseName}"`);
}
}
catch (error) {
throw new Error(`Failed to create database "${databaseName}".`, {
cause: error
});
}
};
/**
* Create a user. `username`, `password`, and access controls to multiple databases
* can be established using the `databases` property of the input parameters.
* @param session The session to use to create the user.
* @param username The `username` of the user to create.
* @param password The `password` of the user to create.
* @param databases An object of databases to add the user to, and their permissions (groups they belong to).
* @returns `true` if the user was created successfully.
* @throws `Error` if the user could not be created.
*/
static createUser = async (session, params) => {
try {
if (!Object.prototype.hasOwnProperty.call(params, 'username') ||
!Object.prototype.hasOwnProperty.call(params, 'password') ||
!Object.prototype.hasOwnProperty.call(params, 'databases')) {
throw new TypeError(`The object supplied as an argument must contain 'username', 'password', and 'databases' properties. Received: ${JSON.stringify(params)}`);
}
// TODO:
// ArcadeDB by default requires 4 character minimum length for passwords.
// The problem is they return a 403 if it's under 4 characters. That could
// be confusing because it isn't really explained and could be confused with a
// 403 for invalid credentials from user error establishing authorization
// through the client. Further question: the manual indicates that it's a 8
// character minimum, but in practice it's 4 (for non-root users)
if (!(params.password.length > 3)) {
throw new TypeError(`The password must be at least 4 characters in length, received: ${params.password}, which is ${params.password.length} characters long.`);
}
// ArcadeDB uses a 'name' property for the user account.
// This is fine if all you are collecting is a 'username',
// but in many use cases a user's 'name' and their 'username'
// will be different fields, and to avoid complication for
// end users, we are collecting 'username' and manually
// changing it to 'name' as arcadedb expects.
const expectedParameters = {
name: params.username,
password: params.password,
databases: params.databases
};
return await this._booleanCommand(session, CREATE_USER(expectedParameters));
}
catch (error) {
const databaseListString = Object.keys(params.databases).join(', ');
throw new Error(`Could not create user ${params.username}. In database(s): ${databaseListString}`, { cause: error });
}
};
/**
* Disconnects the server from the cluster.
* @param session The session to use to disconnect from the cluster.
* @returns The response from the server.
* @throws `Error` if the cluster could not be disconnected.
*/
static disconnectCluster = async (session) => {
try {
return await this._booleanCommand(session, DISCONNECT_CLUSTER);
}
catch (error) {
throw new Error('There was an error when attempting to disconnect from the cluster.', { cause: error });
}
};
/**
* Drop a database
* @param session The session to use to drop the database.
* @param databaseName The name of the database to drop.
* @returns `true` if successfully dropped.
* @throws `Error` if the database could not be dropped.
*/
static dropDatabase = async (session, databaseName) => {
try {
return await this._booleanCommand(session, DROP_DATABASE(databaseName));
}
catch (error) {
throw new Error(`Failed to drop database.`, {
cause: error
});
}
};
/**
* Drop a user from the ArcadeDB server.
* @param session The session to use to drop the user.
* @param username The `username` of the user to drop from the ArcadeDB server.
* @returns `true` if the user was successfully dropped.
* @throws `Error` if the user could not be dropped.
*/
static dropUser = async (session, username) => {
try {
return await this._booleanCommand(session, DROP_USER(username));
}
catch (error) {
throw new Error(`Could not drop user ${username}.`, { cause: error });
}
};
/**
* Retrieves a list of server events, optionally a filename of the form
* `server-event-log-yyyymmdd-HHMMSS.INDEX.jsonl` (where INDEX is a integer, i.e. 0)
* can be given to retrieve older event logs.
* @param session The session to use to retrieve the server events.
* @returns An object containing he server events from the server, and filenames of the associated logs.
* @throws `Error` if there was a problem fetching the event logs.
*/
static getEvents = async (session) => {
try {
return await this.command(session, GET_SERVER_EVENTS);
}
catch (error) {
throw new Error(`There was an error when attempting to retrieve ArcadeDB server event logs.`, { cause: error });
}
};
/**
* Returns the current configuration.
* @param session The session to use to retrieve the configuration.
* @param mode The level of informatio detail to return.
* * `basic` returns minimal server information
* * `default` returns full server configuration (default value when no parameter is given)
* * `cluster` returns the cluster layout
* @returns The server information.
* @throws `Error` if the server information could not be retrieved.
*/
static getInformation = async (session, mode) => {
try {
// TODO: There should be a method to modify the URL
// in the Rest class with parameters like this, for now this
// works
// TODO: The repetition of the RequestInit is less
// than ideal
const response = await fetch(`${session.address}/api/v1/server?mode=${mode || 'default'}`, {
method: 'GET',
headers: session.headers,
keepalive: true
});
switch (response.status) {
case 200: {
const jsonResponse = await response.json();
return jsonResponse.result;
}
case 403:
case 404:
case 500:
default:
throw new ArcadeFetchError(response);
}
}
catch (error) {
throw new Error(`Could not get ArcadeDB server information.`, {
cause: error
});
}
};
/**
* Returns a list of database names that are present on the server.
* @param session The session to use to retrieve the database list.
* @returns {Promise<Array<string>>} A list (array) of database names present on the server.
* @throws `Error` if the database list could not be retrieved.
*/
static listDatabases = async (session) => {
try {
return await Rest.getJson(Routes.DATABASES, session);
}
catch (error) {
throw new Error('Encountered an error when attemping to fetch list of databases from the server.', { cause: error });
}
};
/**
* Gracefully shutdown the server.\
* `TODO:` This works, in that it does shutdown the server, but the fetch throws
* before it resolves, guessing because the server is shutting down. A CURL, however,
* returns an empty `204` response as the documentation indicates.
* @param session The session to use to shutdown the server.
* @returns `true` if the server is successfully shutdown.
* @throws `Error` if there is a problem attempting the shutdown.
*/
static shutdown = async (session) => {
try {
const response = await Rest.post(Routes.COMMAND, { command: SHUTDOWN }, session);
return response.status === 204;
}
catch (error) {
throw new Error(`There was an error when attempting to shutdown the ArcadeDB server at.`, { cause: error });
}
};
}
export { Server };
//# sourceMappingURL=Server.js.map